<?xml version="1.0" encoding="UTF-8"?><?xml-stylesheet href="/rss/feed.xsl" type="text/xsl"?><rss version="2.0" xmlns:content="http://purl.org/rss/1.0/modules/content/"><channel><title>江无没有月の博客</title><description>愿风有雨花有诗，愿江有月木有枝</description><link>https://39miku.space</link><item><title>Python 官方教程综合练习手册：Oh My Pi Terminal Edition</title><link>https://39miku.space/post/python-official-practice-manual</link><guid isPermaLink="false">python-official-practice-manual</guid><description>按 Python 官方教程重组的 4 周项目化练习手册：用 Oh My Pi 终端美学讲清解释器、函数、数据结构、模块、文件、CLI、类、生成器、浮点数和 toy quant backtest。</description><pubDate>Mon, 27 Jul 2026 04:30:00 GMT</pubDate><content:encoded>
&lt;div&gt;
  &lt;div&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;oh-my-pi python manual · terminal edition&lt;/span&gt;&lt;/div&gt;
  &lt;div&gt;
    &lt;div&gt;&lt;span&gt;oh-my-pi@python&lt;/span&gt;&lt;span&gt;:~/official-tutorial&lt;/span&gt;$ &lt;span&gt;cat beginner-practice-manual.md&lt;/span&gt;&lt;/div&gt;
    &lt;div&gt;
      &lt;div&gt;
        &lt;div&gt;Python Official Tutorial × AI / Quant Practice&lt;/div&gt;
        &lt;h2&gt;把官方教程变成能敲、能跑、能解释的 4 周终端练习。&lt;/h2&gt;
        &lt;p&gt;每个练习都先讲函数怎么用，再拆代码逻辑，最后给验收问题。目标不是背语法，而是能进入 pandas、sklearn、PyTorch 和回测项目前不再被 Python 基础卡住。&lt;/p&gt;
        &lt;div&gt;
          &lt;a href=&quot;#0-使用方式&quot;&gt;start&lt;/a&gt;
          &lt;a href=&quot;#1-week-a解释器虚拟环境基本类型控制流&quot;&gt;week-a&lt;/a&gt;
          &lt;a href=&quot;#2-week-b函数参数数据结构编码风格&quot;&gt;week-b&lt;/a&gt;
          &lt;a href=&quot;#3-week-c模块文件json异常cli&quot;&gt;week-c&lt;/a&gt;
          &lt;a href=&quot;#4-week-d类迭代器标准库浮点数综合项目&quot;&gt;week-d&lt;/a&gt;
          &lt;a href=&quot;/post/ustc-stat-ai-quant-plan/&quot;&gt;AI/Quant 总路线&lt;/a&gt;
        &lt;/div&gt;
      &lt;/div&gt;
      &lt;div&gt;
        &lt;span&gt;&lt;/span&gt;
        &lt;span&gt;&lt;/span&gt;
        &lt;span&gt;&lt;/span&gt;
        &lt;span&gt;&lt;/span&gt;
        &lt;span&gt;&lt;/span&gt;
        &lt;span&gt;&lt;/span&gt;
        &lt;span&gt;&lt;/span&gt;
        &lt;span&gt;&lt;/span&gt;
        &lt;span&gt;&lt;/span&gt;
        &lt;span&gt;&lt;/span&gt;
        &lt;span&gt;&lt;/span&gt;
        &lt;span&gt;&lt;/span&gt;
        &lt;span&gt;&lt;/span&gt;
        &lt;span&gt;&lt;/span&gt;
        &lt;span&gt;&lt;/span&gt;
        &lt;span&gt;&lt;/span&gt;
        &lt;span&gt;&lt;/span&gt;
        &lt;span&gt;&lt;/span&gt;
        &lt;span&gt;&lt;/span&gt;
        &lt;span&gt;&lt;/span&gt;
        &lt;span&gt;&lt;/span&gt;
        &lt;span&gt;&lt;/span&gt;
        &lt;span&gt;&lt;/span&gt;
        &lt;span&gt;&lt;/span&gt;
        &lt;span&gt;&lt;/span&gt;
        &lt;span&gt;&lt;/span&gt;
        &lt;span&gt;&lt;/span&gt;
        &lt;span&gt;&lt;/span&gt;
        &lt;span&gt;&lt;/span&gt;
        &lt;span&gt;&lt;/span&gt;
        &lt;span&gt;&lt;/span&gt;
        &lt;span&gt;&lt;/span&gt;
        &lt;span&gt;&lt;/span&gt;
        &lt;span&gt;&lt;/span&gt;
        &lt;span&gt;&lt;/span&gt;
        &lt;span&gt;&lt;/span&gt;
        &lt;span&gt;&lt;/span&gt;
        &lt;span&gt;&lt;/span&gt;
        &lt;span&gt;&lt;/span&gt;
        &lt;span&gt;&lt;/span&gt;
        &lt;span&gt;&lt;/span&gt;
        &lt;span&gt;&lt;/span&gt;
        &lt;span&gt;&lt;/span&gt;
        &lt;span&gt;&lt;/span&gt;
        &lt;span&gt;&lt;/span&gt;
        &lt;span&gt;&lt;/span&gt;
        &lt;span&gt;&lt;/span&gt;
        &lt;span&gt;&lt;/span&gt;
        &lt;span&gt;&lt;/span&gt;
        &lt;span&gt;&lt;/span&gt;
        &lt;span&gt;&lt;/span&gt;
        &lt;span&gt;&lt;/span&gt;
        &lt;span&gt;&lt;/span&gt;
        &lt;span&gt;&lt;/span&gt;
        &lt;span&gt;&lt;/span&gt;
        &lt;span&gt;&lt;/span&gt;
        &lt;span&gt;&lt;/span&gt;
        &lt;span&gt;&lt;/span&gt;
        &lt;span&gt;&lt;/span&gt;
        &lt;span&gt;&lt;/span&gt;
        &lt;span&gt;&lt;/span&gt;
        &lt;span&gt;&lt;/span&gt;
        &lt;span&gt;&lt;/span&gt;
        &lt;span&gt;&lt;/span&gt;
        &lt;span&gt;&lt;/span&gt;
        &lt;span&gt;&lt;/span&gt;
        &lt;span&gt;&lt;/span&gt;
        &lt;span&gt;&lt;/span&gt;
        &lt;span&gt;&lt;/span&gt;
        &lt;span&gt;&lt;/span&gt;
        &lt;span&gt;&lt;/span&gt;
        &lt;span&gt;&lt;/span&gt;
        &lt;span&gt;&lt;/span&gt;
        &lt;span&gt;&lt;/span&gt;
        &lt;span&gt;&lt;/span&gt;
        &lt;span&gt;&lt;/span&gt;
        &lt;span&gt;&lt;/span&gt;
        &lt;span&gt;&lt;/span&gt;
        &lt;span&gt;&lt;/span&gt;
        &lt;span&gt;&lt;/span&gt;
        &lt;span&gt;&lt;/span&gt;
        &lt;span&gt;&lt;/span&gt;
        &lt;span&gt;&lt;/span&gt;
        &lt;span&gt;&lt;/span&gt;
        &lt;span&gt;&lt;/span&gt;
        &lt;span&gt;&lt;/span&gt;
        &lt;span&gt;&lt;/span&gt;
        &lt;span&gt;&lt;/span&gt;
        &lt;span&gt;&lt;/span&gt;
        &lt;span&gt;&lt;/span&gt;
        &lt;span&gt;&lt;/span&gt;
        &lt;span&gt;&lt;/span&gt;
        &lt;span&gt;&lt;/span&gt;
        &lt;span&gt;&lt;/span&gt;
        &lt;span&gt;&lt;/span&gt;
        &lt;span&gt;&lt;/span&gt;
        &lt;span&gt;&lt;/span&gt;
        &lt;span&gt;&lt;/span&gt;
        &lt;span&gt;&lt;/span&gt;
        &lt;span&gt;&lt;/span&gt;
        &lt;span&gt;&lt;/span&gt;
        &lt;span&gt;&lt;/span&gt;
        &lt;span&gt;&lt;/span&gt;
        &lt;span&gt;&lt;/span&gt;
        &lt;span&gt;&lt;/span&gt;
        &lt;span&gt;&lt;/span&gt;
        &lt;span&gt;&lt;/span&gt;
        &lt;span&gt;&lt;/span&gt;
        &lt;span&gt;&lt;/span&gt;
        &lt;span&gt;&lt;/span&gt;
        &lt;span&gt;&lt;/span&gt;
        &lt;span&gt;&lt;/span&gt;
        &lt;span&gt;&lt;/span&gt;
        &lt;span&gt;&lt;/span&gt;
        &lt;span&gt;&lt;/span&gt;
        &lt;span&gt;&lt;/span&gt;
        &lt;span&gt;&lt;/span&gt;
        &lt;span&gt;&lt;/span&gt;
        &lt;span&gt;&lt;/span&gt;
        &lt;span&gt;&lt;/span&gt;
        &lt;span&gt;&lt;/span&gt;
        &lt;span&gt;&lt;/span&gt;
        &lt;span&gt;&lt;/span&gt;
        &lt;span&gt;&lt;/span&gt;
        &lt;span&gt;&lt;/span&gt;
        &lt;span&gt;&lt;/span&gt;
        &lt;span&gt;&lt;/span&gt;
        &lt;span&gt;&lt;/span&gt;
        &lt;span&gt;&lt;/span&gt;
        &lt;span&gt;&lt;/span&gt;
        &lt;span&gt;&lt;/span&gt;
        &lt;span&gt;&lt;/span&gt;
        &lt;span&gt;&lt;/span&gt;
        &lt;span&gt;&lt;/span&gt;
        &lt;span&gt;&lt;/span&gt;
        &lt;span&gt;&lt;/span&gt;
        &lt;span&gt;&lt;/span&gt;
        &lt;span&gt;&lt;/span&gt;
        &lt;span&gt;&lt;/span&gt;
        &lt;span&gt;&lt;/span&gt;
        &lt;span&gt;&lt;/span&gt;
        &lt;span&gt;&lt;/span&gt;
        &lt;span&gt;&lt;/span&gt;
        &lt;span&gt;&lt;/span&gt;
      &lt;/div&gt;
    &lt;/div&gt;
  &lt;/div&gt;
&lt;/div&gt;
&lt;h1&gt;Python 官方教程综合练习手册：Oh My Pi Terminal Edition&lt;a href=&quot;#python-官方教程综合练习手册oh-my-pi-terminal-edition&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/h1&gt;
&lt;blockquote&gt;
&lt;p&gt;这版按“先教概念 → 再看函数怎么用 → 再拆代码逻辑 → 最后做练习”的顺序写。目标读者是：会一点 C，知道变量、函数、循环这些词，但 Python 还不系统。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2&gt;0. 使用方式&lt;a href=&quot;#0-使用方式&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;不要把这份文档当成“看完就会”的文章。它是练习手册。每个练习都按同一个学习节奏来：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;1. 先理解这个语法/函数解决什么问题&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;2. 看最小例子，确认会运行&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;3. 拆代码逻辑，知道每一行为什么存在&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;4. 再写练习，不要跳过验收&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;5. 把错误记录下来，下一周会用到&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;0.1 官方教程映射&lt;a href=&quot;#01-官方教程映射&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/h3&gt;





















































&lt;table&gt;&lt;thead&gt;&lt;tr&gt;&lt;th&gt;官方教程&lt;/th&gt;&lt;th&gt;本手册用途&lt;/th&gt;&lt;/tr&gt;&lt;/thead&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td&gt;&lt;a href=&quot;https://docs.python.org/3/tutorial/interpreter.html&quot;&gt;2. Using the Python Interpreter&lt;/a&gt;&lt;/td&gt;&lt;td&gt;解释器、脚本、命令行运行&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;&lt;a href=&quot;https://docs.python.org/3/tutorial/introduction.html&quot;&gt;3. An Informal Introduction to Python&lt;/a&gt;&lt;/td&gt;&lt;td&gt;数字、文本、列表、第一段程序&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;&lt;a href=&quot;https://docs.python.org/3/tutorial/controlflow.html&quot;&gt;4. More Control Flow Tools&lt;/a&gt;&lt;/td&gt;&lt;td&gt;&lt;code&gt;if&lt;/code&gt;、&lt;code&gt;for&lt;/code&gt;、&lt;code&gt;range&lt;/code&gt;、&lt;code&gt;match&lt;/code&gt;、函数、参数、编码风格&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;&lt;a href=&quot;https://docs.python.org/3/tutorial/datastructures.html&quot;&gt;5. Data Structures&lt;/a&gt;&lt;/td&gt;&lt;td&gt;list、tuple、set、dict、comprehension、循环技巧&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;&lt;a href=&quot;https://docs.python.org/3/tutorial/modules.html&quot;&gt;6. Modules&lt;/a&gt;&lt;/td&gt;&lt;td&gt;模块、包、&lt;code&gt;__name__ == &quot;__main__&quot;&lt;/code&gt;、导入规范&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;&lt;a href=&quot;https://docs.python.org/3/tutorial/inputoutput.html&quot;&gt;7. Input and Output&lt;/a&gt;&lt;/td&gt;&lt;td&gt;f-string、文件读写、JSON&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;&lt;a href=&quot;https://docs.python.org/3/tutorial/errors.html&quot;&gt;8. Errors and Exceptions&lt;/a&gt;&lt;/td&gt;&lt;td&gt;语法错误、异常、&lt;code&gt;try&lt;/code&gt;、&lt;code&gt;raise&lt;/code&gt;、清理动作&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;&lt;a href=&quot;https://docs.python.org/3/tutorial/classes.html&quot;&gt;9. Classes&lt;/a&gt;&lt;/td&gt;&lt;td&gt;对象、作用域、类、实例、迭代器、生成器&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;&lt;a href=&quot;https://docs.python.org/3/tutorial/stdlib.html&quot;&gt;10. Brief Tour of the Standard Library&lt;/a&gt;&lt;/td&gt;&lt;td&gt;&lt;code&gt;pathlib&lt;/code&gt;、&lt;code&gt;csv&lt;/code&gt;、&lt;code&gt;json&lt;/code&gt;、&lt;code&gt;argparse&lt;/code&gt;、&lt;code&gt;statistics&lt;/code&gt;、&lt;code&gt;logging&lt;/code&gt;&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;&lt;a href=&quot;https://docs.python.org/3/tutorial/venv.html&quot;&gt;12. Virtual Environments and Packages&lt;/a&gt;&lt;/td&gt;&lt;td&gt;虚拟环境、包管理、依赖复现&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;&lt;a href=&quot;https://docs.python.org/3/tutorial/floatingpoint.html&quot;&gt;15. Floating-Point Arithmetic&lt;/a&gt;&lt;/td&gt;&lt;td&gt;浮点误差、&lt;code&gt;math.isclose&lt;/code&gt;、金融/统计小数风险&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;
&lt;h3&gt;0.2 最终项目结构&lt;a href=&quot;#02-最终项目结构&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;最终你会得到一个小项目：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;python-official-practice/&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;├── README.md&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;├── pyproject.toml&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;├── data/&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;│   ├── scores.csv&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;│   └── prices.csv&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;├── src/&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;│   ├── basics.py&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;│   ├── stats_utils.py&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;│   ├── data_io.py&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;│   ├── metrics.py&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;│   ├── cli.py&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;│   ├── toy_backtest.py&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;│   └── experiment.py&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;├── reports/&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;│   ├── python_basics_report.md&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;│   ├── data_summary.md&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;│   ├── backtest_report.md&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;│   └── floating_point_notes.md&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;└── notes/&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    └── official_tutorial_mapping.md&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;它不是大项目，但它覆盖进入 AI / Quant 之前真正要会的东西：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;变量 → 列表 → 字符串 → 条件 → 循环 → 函数 → 数据结构&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  → 模块 → 文件 → JSON → 异常 → CLI → 类 → 迭代器 → 浮点数&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;0.3 四周路线&lt;a href=&quot;#03-四周路线&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/h3&gt;






























&lt;table&gt;&lt;thead&gt;&lt;tr&gt;&lt;th&gt;周&lt;/th&gt;&lt;th&gt;主题&lt;/th&gt;&lt;th&gt;最终交付物&lt;/th&gt;&lt;/tr&gt;&lt;/thead&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td&gt;Week A&lt;/td&gt;&lt;td&gt;环境、基本类型、控制流&lt;/td&gt;&lt;td&gt;&lt;code&gt;src/basics.py&lt;/code&gt;、&lt;code&gt;reports/python_basics_report.md&lt;/code&gt;&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;Week B&lt;/td&gt;&lt;td&gt;函数、参数、数据结构、风格&lt;/td&gt;&lt;td&gt;&lt;code&gt;src/stats_utils.py&lt;/code&gt;、&lt;code&gt;src/metrics.py&lt;/code&gt;&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;Week C&lt;/td&gt;&lt;td&gt;模块、文件、JSON、异常、CLI&lt;/td&gt;&lt;td&gt;&lt;code&gt;src/data_io.py&lt;/code&gt;、&lt;code&gt;src/cli.py&lt;/code&gt;、&lt;code&gt;reports/data_summary.md&lt;/code&gt;&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;Week D&lt;/td&gt;&lt;td&gt;类、迭代器、标准库、浮点数、综合项目&lt;/td&gt;&lt;td&gt;&lt;code&gt;src/toy_backtest.py&lt;/code&gt;、&lt;code&gt;src/experiment.py&lt;/code&gt;、&lt;code&gt;reports/backtest_report.md&lt;/code&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;
&lt;hr /&gt;
&lt;h2&gt;1. Week A：解释器、虚拟环境、基本类型、控制流&lt;a href=&quot;#1-week-a解释器虚拟环境基本类型控制流&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Week A 的目标不是“背 Python 语法”，而是能把一段数据放进变量，写几行逻辑处理它，然后在命令行跑起来。&lt;/p&gt;
&lt;h3&gt;1.1 先理解：解释器、脚本、虚拟环境&lt;a href=&quot;#11-先理解解释器脚本虚拟环境&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;Python 有两种常见运行方式。&lt;/p&gt;
&lt;p&gt;第一种是交互式解释器：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;python&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;进去以后输入：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;1&lt;/span&gt;&lt;span&gt; +&lt;/span&gt;&lt;span&gt; 1&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;它会立刻返回结果。这个方式适合试语法。&lt;/p&gt;
&lt;p&gt;第二种是脚本文件：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;python&lt;/span&gt;&lt;span&gt; src/basics.py&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;这个方式适合正式练习。你后面写的 AI / Quant 项目基本都应该放在 &lt;code&gt;.py&lt;/code&gt; 文件里，而不是只写在 notebook 里。&lt;/p&gt;
&lt;p&gt;虚拟环境的作用是隔离依赖。你这个项目需要的包，不应该污染整个系统 Python。&lt;/p&gt;
&lt;h4&gt;函数和命令怎么用&lt;a href=&quot;#函数和命令怎么用&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/h4&gt;
&lt;p&gt;这里先不用自己写函数，只学会几个命令：&lt;/p&gt;





























&lt;table&gt;&lt;thead&gt;&lt;tr&gt;&lt;th&gt;命令&lt;/th&gt;&lt;th&gt;作用&lt;/th&gt;&lt;/tr&gt;&lt;/thead&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td&gt;&lt;code&gt;uv init&lt;/code&gt;&lt;/td&gt;&lt;td&gt;初始化 Python 项目&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;&lt;code&gt;uv venv&lt;/code&gt;&lt;/td&gt;&lt;td&gt;创建虚拟环境&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;&lt;code&gt;source .venv/bin/activate.fish&lt;/code&gt;&lt;/td&gt;&lt;td&gt;在 fish shell 里启用虚拟环境&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;&lt;code&gt;python --version&lt;/code&gt;&lt;/td&gt;&lt;td&gt;查看当前 Python 版本&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;&lt;code&gt;python -c &quot;...&quot;&lt;/code&gt;&lt;/td&gt;&lt;td&gt;直接执行一小段 Python 代码&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;
&lt;h4&gt;代码逻辑&lt;a href=&quot;#代码逻辑&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/h4&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;mkdir 创建目录&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;cd 进入目录&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;uv init 创建 pyproject.toml&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;uv venv 创建 .venv&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;source 激活 .venv&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;python --version 验证当前 Python 可用&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h4&gt;Exercise A-00：创建练习项目&lt;a href=&quot;#exercise-a-00创建练习项目&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/h4&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;mkdir&lt;/span&gt;&lt;span&gt; -p ~/Code/python-official-practice&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;cd&lt;/span&gt;&lt;span&gt; ~/Code/python-official-practice&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;uv&lt;/span&gt;&lt;span&gt; init&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;uv&lt;/span&gt;&lt;span&gt; venv&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;source&lt;/span&gt;&lt;span&gt; .venv/bin/activate.fish&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;python&lt;/span&gt;&lt;span&gt; --version&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;python&lt;/span&gt;&lt;span&gt; -c &lt;/span&gt;&lt;span&gt;&quot;print(&apos;hello python&apos;)&quot;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;mkdir&lt;/span&gt;&lt;span&gt; -p src data reports notes&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;验收：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;当前目录有 &lt;code&gt;pyproject.toml&lt;/code&gt;。&lt;/li&gt;
&lt;li&gt;&lt;code&gt;command -v python&lt;/code&gt; 指向 &lt;code&gt;.venv/bin/python&lt;/code&gt;。&lt;/li&gt;
&lt;li&gt;&lt;code&gt;python -c &quot;print(&apos;hello python&apos;)&quot;&lt;/code&gt; 能输出 &lt;code&gt;hello python&lt;/code&gt;。&lt;/li&gt;
&lt;/ul&gt;
&lt;hr /&gt;
&lt;h3&gt;1.2 数字：先把统计量算出来&lt;a href=&quot;#12-数字先把统计量算出来&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;AI / Quant 会大量处理数字。先从最小统计任务开始：一组分数，求数量、均值、最小值、最大值。&lt;/p&gt;
&lt;h4&gt;你要学的函数&lt;a href=&quot;#你要学的函数&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/h4&gt;



































&lt;table&gt;&lt;thead&gt;&lt;tr&gt;&lt;th&gt;函数&lt;/th&gt;&lt;th&gt;怎么用&lt;/th&gt;&lt;th&gt;含义&lt;/th&gt;&lt;/tr&gt;&lt;/thead&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td&gt;&lt;code&gt;len(xs)&lt;/code&gt;&lt;/td&gt;&lt;td&gt;&lt;code&gt;len([1, 2, 3])&lt;/code&gt;&lt;/td&gt;&lt;td&gt;返回元素个数&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;&lt;code&gt;sum(xs)&lt;/code&gt;&lt;/td&gt;&lt;td&gt;&lt;code&gt;sum([1, 2, 3])&lt;/code&gt;&lt;/td&gt;&lt;td&gt;返回总和&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;&lt;code&gt;min(xs)&lt;/code&gt;&lt;/td&gt;&lt;td&gt;&lt;code&gt;min([1, 2, 3])&lt;/code&gt;&lt;/td&gt;&lt;td&gt;返回最小值&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;&lt;code&gt;max(xs)&lt;/code&gt;&lt;/td&gt;&lt;td&gt;&lt;code&gt;max([1, 2, 3])&lt;/code&gt;&lt;/td&gt;&lt;td&gt;返回最大值&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;&lt;code&gt;print(x)&lt;/code&gt;&lt;/td&gt;&lt;td&gt;&lt;code&gt;print(&quot;mean&quot;, 2.0)&lt;/code&gt;&lt;/td&gt;&lt;td&gt;把结果显示到终端&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;
&lt;p&gt;最小例子：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;scores &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; [&lt;/span&gt;&lt;span&gt;80&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;92&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;75&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;88&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;95&lt;/span&gt;&lt;span&gt;]&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;mean_score &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; sum&lt;/span&gt;&lt;span&gt;(scores) &lt;/span&gt;&lt;span&gt;/&lt;/span&gt;&lt;span&gt; len&lt;/span&gt;&lt;span&gt;(scores)&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;print&lt;/span&gt;&lt;span&gt;(mean_score)&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h4&gt;代码逻辑&lt;a href=&quot;#代码逻辑-1&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/h4&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;scores = [...]              把多个分数放进一个 list&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;sum(scores)                 求总分&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;len(scores)                 求人数&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;sum(scores) / len(scores)   总分除以人数，得到平均分&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;print(...)                  把结果显示出来&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;这和 C 的数组很像，但 Python 的 &lt;code&gt;list&lt;/code&gt; 更灵活：它知道自己的长度，所以可以直接 &lt;code&gt;len(scores)&lt;/code&gt;。&lt;/p&gt;
&lt;h4&gt;Exercise A-01：均值、最小值、最大值&lt;a href=&quot;#exercise-a-01均值最小值最大值&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/h4&gt;
&lt;p&gt;文件：&lt;code&gt;src/basics.py&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;写入：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;scores &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; [&lt;/span&gt;&lt;span&gt;80&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;92&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;75&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;88&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;95&lt;/span&gt;&lt;span&gt;]&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;print&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&quot;n =&quot;&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;len&lt;/span&gt;&lt;span&gt;(scores))&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;print&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&quot;mean =&quot;&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;sum&lt;/span&gt;&lt;span&gt;(scores) &lt;/span&gt;&lt;span&gt;/&lt;/span&gt;&lt;span&gt; len&lt;/span&gt;&lt;span&gt;(scores))&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;print&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&quot;min =&quot;&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;min&lt;/span&gt;&lt;span&gt;(scores))&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;print&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&quot;max =&quot;&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;max&lt;/span&gt;&lt;span&gt;(scores))&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;运行：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;python&lt;/span&gt;&lt;span&gt; src/basics.py&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;验收：你能解释这四个问题：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;scores&lt;/code&gt; 是什么类型？&lt;/li&gt;
&lt;li&gt;&lt;code&gt;len(scores)&lt;/code&gt; 返回什么？&lt;/li&gt;
&lt;li&gt;为什么均值要除以 &lt;code&gt;len(scores)&lt;/code&gt;？&lt;/li&gt;
&lt;li&gt;&lt;code&gt;print(&quot;mean =&quot;, value)&lt;/code&gt; 为什么会显示两段内容？&lt;/li&gt;
&lt;/ul&gt;
&lt;hr /&gt;
&lt;h3&gt;1.3 字符串：把脏列名清洗成规范列名&lt;a href=&quot;#13-字符串把脏列名清洗成规范列名&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;真实数据里，列名经常不干净。例如：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;&quot; Final Score &quot;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;&quot;Student ID&quot;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;&quot;Group Name&quot;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;后续 pandas / SQL / sklearn 里，更推荐列名像这样：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;final_score&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;student_id&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;group_name&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h4&gt;你要学的方法&lt;a href=&quot;#你要学的方法&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/h4&gt;
&lt;p&gt;字符串后面可以接方法调用：&lt;/p&gt;

























&lt;table&gt;&lt;thead&gt;&lt;tr&gt;&lt;th&gt;方法&lt;/th&gt;&lt;th&gt;怎么用&lt;/th&gt;&lt;th&gt;含义&lt;/th&gt;&lt;/tr&gt;&lt;/thead&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td&gt;&lt;code&gt;strip()&lt;/code&gt;&lt;/td&gt;&lt;td&gt;&lt;code&gt;&quot; A &quot;.strip()&lt;/code&gt;&lt;/td&gt;&lt;td&gt;去掉两边空白&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;&lt;code&gt;lower()&lt;/code&gt;&lt;/td&gt;&lt;td&gt;&lt;code&gt;&quot;ABC&quot;.lower()&lt;/code&gt;&lt;/td&gt;&lt;td&gt;转小写&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;&lt;code&gt;replace(a, b)&lt;/code&gt;&lt;/td&gt;&lt;td&gt;&lt;code&gt;&quot;A B&quot;.replace(&quot; &quot;, &quot;_&quot;)&lt;/code&gt;&lt;/td&gt;&lt;td&gt;把 a 替换成 b&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;
&lt;p&gt;最小例子：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;raw_name &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; &quot; Final Score &quot;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;clean_name &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; raw_name.strip().lower().replace(&lt;/span&gt;&lt;span&gt;&quot; &quot;&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;&quot;_&quot;&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;print&lt;/span&gt;&lt;span&gt;(clean_name)&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h4&gt;代码逻辑&lt;a href=&quot;#代码逻辑-2&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/h4&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;raw_name.strip()             &quot; Final Score &quot; → &quot;Final Score&quot;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;.lower()                     &quot;Final Score&quot; → &quot;final score&quot;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;.replace(&quot; &quot;, &quot;_&quot;)          &quot;final score&quot; → &quot;final_score&quot;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;这叫链式调用。左边一步的结果，会传给右边下一步。&lt;/p&gt;
&lt;h4&gt;Exercise A-02：清洗字段名&lt;a href=&quot;#exercise-a-02清洗字段名&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/h4&gt;
&lt;p&gt;输入：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;columns &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; [&lt;/span&gt;&lt;span&gt;&quot; Student ID &quot;&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;&quot;Final Score&quot;&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;&quot;Group Name&quot;&lt;/span&gt;&lt;span&gt;]&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;目标输出：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;[&lt;/span&gt;&lt;span&gt;&quot;student_id&quot;&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;&quot;final_score&quot;&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;&quot;group_name&quot;&lt;/span&gt;&lt;span&gt;]&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;推荐先用普通循环，不要一开始就写列表推导式：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;columns &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; [&lt;/span&gt;&lt;span&gt;&quot; Student ID &quot;&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;&quot;Final Score&quot;&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;&quot;Group Name&quot;&lt;/span&gt;&lt;span&gt;]&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;clean_columns &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; []&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;for&lt;/span&gt;&lt;span&gt; name &lt;/span&gt;&lt;span&gt;in&lt;/span&gt;&lt;span&gt; columns:&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    clean_name &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; name.strip().lower().replace(&lt;/span&gt;&lt;span&gt;&quot; &quot;&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;&quot;_&quot;&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    clean_columns.append(clean_name)&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;print&lt;/span&gt;&lt;span&gt;(clean_columns)&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;验收：你能解释：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;clean_columns = []&lt;/code&gt; 为什么要先创建空列表？&lt;/li&gt;
&lt;li&gt;&lt;code&gt;for name in columns&lt;/code&gt; 每次拿到的是什么？&lt;/li&gt;
&lt;li&gt;&lt;code&gt;append&lt;/code&gt; 是把元素加到哪里？&lt;/li&gt;
&lt;/ul&gt;
&lt;hr /&gt;
&lt;h3&gt;1.4 列表过滤：去掉缺失值和异常值&lt;a href=&quot;#14-列表过滤去掉缺失值和异常值&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;真实数据经常有缺失值。Python 里常用 &lt;code&gt;None&lt;/code&gt; 表示“没有值”。有些旧数据会用 &lt;code&gt;-999&lt;/code&gt; 这种特殊数字表示异常占位。&lt;/p&gt;
&lt;h4&gt;你要学的语法&lt;a href=&quot;#你要学的语法&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/h4&gt;

























&lt;table&gt;&lt;thead&gt;&lt;tr&gt;&lt;th&gt;语法&lt;/th&gt;&lt;th&gt;含义&lt;/th&gt;&lt;/tr&gt;&lt;/thead&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td&gt;&lt;code&gt;x is not None&lt;/code&gt;&lt;/td&gt;&lt;td&gt;判断 x 不是缺失值&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;&lt;code&gt;x != -999&lt;/code&gt;&lt;/td&gt;&lt;td&gt;判断 x 不是异常占位值&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;&lt;code&gt;and&lt;/code&gt;&lt;/td&gt;&lt;td&gt;两个条件都要满足&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;&lt;code&gt;append&lt;/code&gt;&lt;/td&gt;&lt;td&gt;把合格数据放进新列表&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;
&lt;p&gt;最小例子：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;values &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; [&lt;/span&gt;&lt;span&gt;1.2&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;None&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;3.4&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;-&lt;/span&gt;&lt;span&gt;999&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;2.8&lt;/span&gt;&lt;span&gt;]&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;clean &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; []&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;for&lt;/span&gt;&lt;span&gt; x &lt;/span&gt;&lt;span&gt;in&lt;/span&gt;&lt;span&gt; values:&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    if&lt;/span&gt;&lt;span&gt; x &lt;/span&gt;&lt;span&gt;is&lt;/span&gt;&lt;span&gt; not&lt;/span&gt;&lt;span&gt; None&lt;/span&gt;&lt;span&gt; and&lt;/span&gt;&lt;span&gt; x &lt;/span&gt;&lt;span&gt;!=&lt;/span&gt;&lt;span&gt; -&lt;/span&gt;&lt;span&gt;999&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;        clean.append(x)&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;print&lt;/span&gt;&lt;span&gt;(clean)&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h4&gt;代码逻辑&lt;a href=&quot;#代码逻辑-3&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/h4&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;values 保存原始数据&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;clean 保存清洗后的数据&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;for x in values 逐个检查&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;if 条件成立，说明 x 是有效值&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;clean.append(x) 保存有效值&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;这里不要急着用“高级写法”。先把循环和条件看懂。&lt;/p&gt;
&lt;h4&gt;Exercise A-03：过滤缺失值&lt;a href=&quot;#exercise-a-03过滤缺失值&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/h4&gt;
&lt;p&gt;输入：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;values &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; [&lt;/span&gt;&lt;span&gt;1.2&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;None&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;3.4&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;-&lt;/span&gt;&lt;span&gt;999&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;2.8&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;None&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;5.0&lt;/span&gt;&lt;span&gt;]&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;要求输出：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;有效数据列表。&lt;/li&gt;
&lt;li&gt;有效样本数量。&lt;/li&gt;
&lt;li&gt;有效数据均值。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;提示：均值仍然用 &lt;code&gt;sum(clean) / len(clean)&lt;/code&gt;。&lt;/p&gt;
&lt;p&gt;验收：如果 &lt;code&gt;clean&lt;/code&gt; 为空，你能说明为什么不能直接除以 &lt;code&gt;len(clean)&lt;/code&gt;。&lt;/p&gt;
&lt;hr /&gt;
&lt;h3&gt;1.5 条件函数：把分数变成等级&lt;a href=&quot;#15-条件函数把分数变成等级&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;现在开始写第一个函数。函数的作用是：给一个输入，返回一个结果。&lt;/p&gt;
&lt;h4&gt;函数怎么用&lt;a href=&quot;#函数怎么用&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/h4&gt;
&lt;p&gt;你希望这样使用它：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;grade &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; score_to_grade(&lt;/span&gt;&lt;span&gt;95&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;print&lt;/span&gt;&lt;span&gt;(grade)&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;也就是说，&lt;code&gt;score_to_grade&lt;/code&gt; 接收一个分数，返回一个等级。&lt;/p&gt;
&lt;h4&gt;函数定义长什么样&lt;a href=&quot;#函数定义长什么样&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/h4&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;def&lt;/span&gt;&lt;span&gt; score_to_grade&lt;/span&gt;&lt;span&gt;(score: &lt;/span&gt;&lt;span&gt;float&lt;/span&gt;&lt;span&gt;) -&amp;gt; &lt;/span&gt;&lt;span&gt;str&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    if&lt;/span&gt;&lt;span&gt; score &lt;/span&gt;&lt;span&gt;&amp;gt;=&lt;/span&gt;&lt;span&gt; 90&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;        return&lt;/span&gt;&lt;span&gt; &quot;A&quot;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    if&lt;/span&gt;&lt;span&gt; score &lt;/span&gt;&lt;span&gt;&amp;gt;=&lt;/span&gt;&lt;span&gt; 80&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;        return&lt;/span&gt;&lt;span&gt; &quot;B&quot;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    if&lt;/span&gt;&lt;span&gt; score &lt;/span&gt;&lt;span&gt;&amp;gt;=&lt;/span&gt;&lt;span&gt; 70&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;        return&lt;/span&gt;&lt;span&gt; &quot;C&quot;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    return&lt;/span&gt;&lt;span&gt; &quot;D&quot;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h4&gt;代码逻辑&lt;a href=&quot;#代码逻辑-4&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/h4&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;def                      定义函数&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;score_to_grade           函数名&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;score: float             参数叫 score，期望是浮点数&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;-&amp;gt; str                   返回值期望是字符串&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;if score &amp;gt;= 90           如果分数至少 90&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;return &quot;A&quot;               立刻返回 A，函数结束&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;return &quot;D&quot;               前面条件都不满足，返回 D&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;注意：&lt;code&gt;return&lt;/code&gt; 后函数就结束了。因此这里不一定需要 &lt;code&gt;elif&lt;/code&gt;。&lt;/p&gt;
&lt;h4&gt;Exercise A-04：分数等级&lt;a href=&quot;#exercise-a-04分数等级&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/h4&gt;
&lt;p&gt;文件：&lt;code&gt;src/basics.py&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;实现：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;def&lt;/span&gt;&lt;span&gt; score_to_grade&lt;/span&gt;&lt;span&gt;(score: &lt;/span&gt;&lt;span&gt;float&lt;/span&gt;&lt;span&gt;) -&amp;gt; &lt;/span&gt;&lt;span&gt;str&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    if&lt;/span&gt;&lt;span&gt; score &lt;/span&gt;&lt;span&gt;&amp;gt;=&lt;/span&gt;&lt;span&gt; 90&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;        return&lt;/span&gt;&lt;span&gt; &quot;A&quot;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    if&lt;/span&gt;&lt;span&gt; score &lt;/span&gt;&lt;span&gt;&amp;gt;=&lt;/span&gt;&lt;span&gt; 80&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;        return&lt;/span&gt;&lt;span&gt; &quot;B&quot;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    if&lt;/span&gt;&lt;span&gt; score &lt;/span&gt;&lt;span&gt;&amp;gt;=&lt;/span&gt;&lt;span&gt; 70&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;        return&lt;/span&gt;&lt;span&gt; &quot;C&quot;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    return&lt;/span&gt;&lt;span&gt; &quot;D&quot;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;print&lt;/span&gt;&lt;span&gt;(score_to_grade(&lt;/span&gt;&lt;span&gt;95&lt;/span&gt;&lt;span&gt;))&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;print&lt;/span&gt;&lt;span&gt;(score_to_grade(&lt;/span&gt;&lt;span&gt;85&lt;/span&gt;&lt;span&gt;))&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;print&lt;/span&gt;&lt;span&gt;(score_to_grade(&lt;/span&gt;&lt;span&gt;75&lt;/span&gt;&lt;span&gt;))&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;print&lt;/span&gt;&lt;span&gt;(score_to_grade(&lt;/span&gt;&lt;span&gt;60&lt;/span&gt;&lt;span&gt;))&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;然后加上自检：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;assert&lt;/span&gt;&lt;span&gt; score_to_grade(&lt;/span&gt;&lt;span&gt;95&lt;/span&gt;&lt;span&gt;) &lt;/span&gt;&lt;span&gt;==&lt;/span&gt;&lt;span&gt; &quot;A&quot;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;assert&lt;/span&gt;&lt;span&gt; score_to_grade(&lt;/span&gt;&lt;span&gt;85&lt;/span&gt;&lt;span&gt;) &lt;/span&gt;&lt;span&gt;==&lt;/span&gt;&lt;span&gt; &quot;B&quot;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;assert&lt;/span&gt;&lt;span&gt; score_to_grade(&lt;/span&gt;&lt;span&gt;75&lt;/span&gt;&lt;span&gt;) &lt;/span&gt;&lt;span&gt;==&lt;/span&gt;&lt;span&gt; &quot;C&quot;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;assert&lt;/span&gt;&lt;span&gt; score_to_grade(&lt;/span&gt;&lt;span&gt;60&lt;/span&gt;&lt;span&gt;) &lt;/span&gt;&lt;span&gt;==&lt;/span&gt;&lt;span&gt; &quot;D&quot;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;验收：你能解释为什么 85 不会返回 &lt;code&gt;A&lt;/code&gt;。&lt;/p&gt;
&lt;hr /&gt;
&lt;h3&gt;1.6 &lt;code&gt;match&lt;/code&gt;：根据命令选择行为&lt;a href=&quot;#16-match根据命令选择行为&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;&lt;code&gt;match&lt;/code&gt; 类似其他语言里的 &lt;code&gt;switch&lt;/code&gt;，适合处理“命令分发”。&lt;/p&gt;
&lt;h4&gt;函数怎么用&lt;a href=&quot;#函数怎么用-1&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/h4&gt;
&lt;p&gt;你希望这样调用：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;message &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; handle_command(&lt;/span&gt;&lt;span&gt;&quot;summary&quot;&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;print&lt;/span&gt;&lt;span&gt;(message)&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h4&gt;最小例子&lt;a href=&quot;#最小例子&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/h4&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;def&lt;/span&gt;&lt;span&gt; handle_command&lt;/span&gt;&lt;span&gt;(command: &lt;/span&gt;&lt;span&gt;str&lt;/span&gt;&lt;span&gt;) -&amp;gt; &lt;/span&gt;&lt;span&gt;str&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    match&lt;/span&gt;&lt;span&gt; command:&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;        case&lt;/span&gt;&lt;span&gt; &quot;summary&quot;&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;            return&lt;/span&gt;&lt;span&gt; &quot;run data summary&quot;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;        case&lt;/span&gt;&lt;span&gt; &quot;metrics&quot;&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;            return&lt;/span&gt;&lt;span&gt; &quot;run metrics&quot;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;        case&lt;/span&gt;&lt;span&gt; &quot;backtest&quot;&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;            return&lt;/span&gt;&lt;span&gt; &quot;run backtest&quot;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;        case&lt;/span&gt;&lt;span&gt; _:&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;            return&lt;/span&gt;&lt;span&gt; &quot;unknown command&quot;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h4&gt;代码逻辑&lt;a href=&quot;#代码逻辑-5&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/h4&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;match command       检查 command 的值&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;case &quot;summary&quot;      如果值等于 summary&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;return ...          返回对应说明&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;case _              兜底分支，匹配所有未知输入&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;code&gt;case _&lt;/code&gt; 很重要。没有它，用户输错命令时，你的程序可能没有清楚反馈。&lt;/p&gt;
&lt;h4&gt;Exercise A-05：实现命令分发&lt;a href=&quot;#exercise-a-05实现命令分发&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/h4&gt;
&lt;p&gt;文件：&lt;code&gt;src/basics.py&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;要求支持：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;summary&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;metrics&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;backtest&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;验收：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;assert&lt;/span&gt;&lt;span&gt; handle_command(&lt;/span&gt;&lt;span&gt;&quot;summary&quot;&lt;/span&gt;&lt;span&gt;) &lt;/span&gt;&lt;span&gt;==&lt;/span&gt;&lt;span&gt; &quot;run data summary&quot;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;assert&lt;/span&gt;&lt;span&gt; handle_command(&lt;/span&gt;&lt;span&gt;&quot;metrics&quot;&lt;/span&gt;&lt;span&gt;) &lt;/span&gt;&lt;span&gt;==&lt;/span&gt;&lt;span&gt; &quot;run metrics&quot;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;assert&lt;/span&gt;&lt;span&gt; handle_command(&lt;/span&gt;&lt;span&gt;&quot;backtest&quot;&lt;/span&gt;&lt;span&gt;) &lt;/span&gt;&lt;span&gt;==&lt;/span&gt;&lt;span&gt; &quot;run backtest&quot;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;assert&lt;/span&gt;&lt;span&gt; handle_command(&lt;/span&gt;&lt;span&gt;&quot;abc&quot;&lt;/span&gt;&lt;span&gt;) &lt;/span&gt;&lt;span&gt;==&lt;/span&gt;&lt;span&gt; &quot;unknown command&quot;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;hr /&gt;
&lt;h2&gt;2. Week B：函数、参数、数据结构、编码风格&lt;a href=&quot;#2-week-b函数参数数据结构编码风格&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Week B 的目标是把零散代码变成可复用函数。以后你写 pandas、sklearn、PyTorch，都离不开函数。&lt;/p&gt;
&lt;h3&gt;2.1 均值函数：从公式变成可复用函数&lt;a href=&quot;#21-均值函数从公式变成可复用函数&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/h3&gt;
&lt;h4&gt;函数怎么用&lt;a href=&quot;#函数怎么用-2&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/h4&gt;
&lt;p&gt;你希望以后这样写：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;from&lt;/span&gt;&lt;span&gt; stats_utils &lt;/span&gt;&lt;span&gt;import&lt;/span&gt;&lt;span&gt; mean&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;score_mean &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; mean([&lt;/span&gt;&lt;span&gt;80&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;92&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;75&lt;/span&gt;&lt;span&gt;])&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;print&lt;/span&gt;&lt;span&gt;(score_mean)&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;这样比每次写 &lt;code&gt;sum(xs) / len(xs)&lt;/code&gt; 更清楚。&lt;/p&gt;
&lt;h4&gt;先写最小版本&lt;a href=&quot;#先写最小版本&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/h4&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;def&lt;/span&gt;&lt;span&gt; mean&lt;/span&gt;&lt;span&gt;(xs: list[&lt;/span&gt;&lt;span&gt;float&lt;/span&gt;&lt;span&gt;]) -&amp;gt; &lt;/span&gt;&lt;span&gt;float&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    return&lt;/span&gt;&lt;span&gt; sum&lt;/span&gt;&lt;span&gt;(xs) &lt;/span&gt;&lt;span&gt;/&lt;/span&gt;&lt;span&gt; len&lt;/span&gt;&lt;span&gt;(xs)&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;这个版本有问题：如果 &lt;code&gt;xs&lt;/code&gt; 是空列表，会发生除以 0。&lt;/p&gt;
&lt;h4&gt;加上错误处理&lt;a href=&quot;#加上错误处理&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/h4&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;def&lt;/span&gt;&lt;span&gt; mean&lt;/span&gt;&lt;span&gt;(xs: list[&lt;/span&gt;&lt;span&gt;float&lt;/span&gt;&lt;span&gt;]) -&amp;gt; &lt;/span&gt;&lt;span&gt;float&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    &quot;&quot;&quot;Return arithmetic mean of a non-empty list.&quot;&quot;&quot;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    if&lt;/span&gt;&lt;span&gt; not&lt;/span&gt;&lt;span&gt; xs:&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;        raise&lt;/span&gt;&lt;span&gt; ValueError&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&quot;xs must not be empty&quot;&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    return&lt;/span&gt;&lt;span&gt; sum&lt;/span&gt;&lt;span&gt;(xs) &lt;/span&gt;&lt;span&gt;/&lt;/span&gt;&lt;span&gt; len&lt;/span&gt;&lt;span&gt;(xs)&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h4&gt;代码逻辑&lt;a href=&quot;#代码逻辑-6&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/h4&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;if not xs                         如果列表为空&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;raise ValueError(...)             主动报错，告诉调用者输入不合法&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;return sum(xs) / len(xs)          正常情况下返回均值&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;code&gt;raise&lt;/code&gt; 不是程序崩溃的坏习惯。相反，它是在错误输入出现时给出明确解释。&lt;/p&gt;
&lt;h4&gt;Exercise B-01：统计函数库&lt;a href=&quot;#exercise-b-01统计函数库&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/h4&gt;
&lt;p&gt;文件：&lt;code&gt;src/stats_utils.py&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;实现：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;def&lt;/span&gt;&lt;span&gt; mean&lt;/span&gt;&lt;span&gt;(xs: list[&lt;/span&gt;&lt;span&gt;float&lt;/span&gt;&lt;span&gt;]) -&amp;gt; &lt;/span&gt;&lt;span&gt;float&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    &quot;&quot;&quot;Return arithmetic mean of a non-empty list.&quot;&quot;&quot;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    if&lt;/span&gt;&lt;span&gt; not&lt;/span&gt;&lt;span&gt; xs:&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;        raise&lt;/span&gt;&lt;span&gt; ValueError&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&quot;xs must not be empty&quot;&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    return&lt;/span&gt;&lt;span&gt; sum&lt;/span&gt;&lt;span&gt;(xs) &lt;/span&gt;&lt;span&gt;/&lt;/span&gt;&lt;span&gt; len&lt;/span&gt;&lt;span&gt;(xs)&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;def&lt;/span&gt;&lt;span&gt; variance&lt;/span&gt;&lt;span&gt;(xs: list[&lt;/span&gt;&lt;span&gt;float&lt;/span&gt;&lt;span&gt;]) -&amp;gt; &lt;/span&gt;&lt;span&gt;float&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    &quot;&quot;&quot;Return population variance of a non-empty list.&quot;&quot;&quot;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    m &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; mean(xs)&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    return&lt;/span&gt;&lt;span&gt; mean([(x &lt;/span&gt;&lt;span&gt;-&lt;/span&gt;&lt;span&gt; m) &lt;/span&gt;&lt;span&gt;**&lt;/span&gt;&lt;span&gt; 2&lt;/span&gt;&lt;span&gt; for&lt;/span&gt;&lt;span&gt; x &lt;/span&gt;&lt;span&gt;in&lt;/span&gt;&lt;span&gt; xs])&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;然后自己补：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;def&lt;/span&gt;&lt;span&gt; std&lt;/span&gt;&lt;span&gt;(xs: list[&lt;/span&gt;&lt;span&gt;float&lt;/span&gt;&lt;span&gt;]) -&amp;gt; &lt;/span&gt;&lt;span&gt;float&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    ...&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;def&lt;/span&gt;&lt;span&gt; covariance&lt;/span&gt;&lt;span&gt;(xs: list[&lt;/span&gt;&lt;span&gt;float&lt;/span&gt;&lt;span&gt;], ys: list[&lt;/span&gt;&lt;span&gt;float&lt;/span&gt;&lt;span&gt;]) -&amp;gt; &lt;/span&gt;&lt;span&gt;float&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    ...&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;def&lt;/span&gt;&lt;span&gt; zscore&lt;/span&gt;&lt;span&gt;(xs: list[&lt;/span&gt;&lt;span&gt;float&lt;/span&gt;&lt;span&gt;]) -&amp;gt; list[&lt;/span&gt;&lt;span&gt;float&lt;/span&gt;&lt;span&gt;]:&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    ...&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;验收：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;assert&lt;/span&gt;&lt;span&gt; mean([&lt;/span&gt;&lt;span&gt;1&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;2&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;3&lt;/span&gt;&lt;span&gt;]) &lt;/span&gt;&lt;span&gt;==&lt;/span&gt;&lt;span&gt; 2&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;assert&lt;/span&gt;&lt;span&gt; variance([&lt;/span&gt;&lt;span&gt;1&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;2&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;3&lt;/span&gt;&lt;span&gt;]) &lt;/span&gt;&lt;span&gt;==&lt;/span&gt;&lt;span&gt; 2&lt;/span&gt;&lt;span&gt; /&lt;/span&gt;&lt;span&gt; 3&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;思考：&lt;code&gt;variance&lt;/code&gt; 里为什么可以调用自己写的 &lt;code&gt;mean&lt;/code&gt;？&lt;/p&gt;
&lt;hr /&gt;
&lt;h3&gt;2.2 默认参数：不要让多个调用共享同一个列表&lt;a href=&quot;#22-默认参数不要让多个调用共享同一个列表&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;Python 官方教程特别提醒：默认参数只在函数定义时创建一次。&lt;/p&gt;
&lt;h4&gt;错误函数怎么用会出问题&lt;a href=&quot;#错误函数怎么用会出问题&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/h4&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;def&lt;/span&gt;&lt;span&gt; add_tag_bad&lt;/span&gt;&lt;span&gt;(tag: &lt;/span&gt;&lt;span&gt;str&lt;/span&gt;&lt;span&gt;, tags: list[&lt;/span&gt;&lt;span&gt;str&lt;/span&gt;&lt;span&gt;] &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; []) -&amp;gt; list[&lt;/span&gt;&lt;span&gt;str&lt;/span&gt;&lt;span&gt;]:&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    tags.append(tag)&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    return&lt;/span&gt;&lt;span&gt; tags&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;print&lt;/span&gt;&lt;span&gt;(add_tag_bad(&lt;/span&gt;&lt;span&gt;&quot;ai&quot;&lt;/span&gt;&lt;span&gt;))&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;print&lt;/span&gt;&lt;span&gt;(add_tag_bad(&lt;/span&gt;&lt;span&gt;&quot;quant&quot;&lt;/span&gt;&lt;span&gt;))&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;你可能以为第二次输出只有 &lt;code&gt;quant&lt;/code&gt;，但实际会把第一次的 &lt;code&gt;ai&lt;/code&gt; 也留下。&lt;/p&gt;
&lt;h4&gt;正确函数怎么用&lt;a href=&quot;#正确函数怎么用&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/h4&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;def&lt;/span&gt;&lt;span&gt; add_tag&lt;/span&gt;&lt;span&gt;(tag: &lt;/span&gt;&lt;span&gt;str&lt;/span&gt;&lt;span&gt;, tags: list[&lt;/span&gt;&lt;span&gt;str&lt;/span&gt;&lt;span&gt;] &lt;/span&gt;&lt;span&gt;|&lt;/span&gt;&lt;span&gt; None&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; None&lt;/span&gt;&lt;span&gt;) -&amp;gt; list[&lt;/span&gt;&lt;span&gt;str&lt;/span&gt;&lt;span&gt;]:&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    if&lt;/span&gt;&lt;span&gt; tags &lt;/span&gt;&lt;span&gt;is&lt;/span&gt;&lt;span&gt; None&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;        tags &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; []&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    tags.append(tag)&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    return&lt;/span&gt;&lt;span&gt; tags&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h4&gt;代码逻辑&lt;a href=&quot;#代码逻辑-7&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/h4&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;tags 默认是 None              不共享可变列表&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;if tags is None               如果调用者没传列表&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    tags = []                 创建一个新的空列表&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;tags.append(tag)              加入标签&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;return tags                   返回结果&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h4&gt;Exercise B-02：安全默认参数&lt;a href=&quot;#exercise-b-02安全默认参数&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/h4&gt;
&lt;p&gt;文件：&lt;code&gt;src/stats_utils.py&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;实现：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;def&lt;/span&gt;&lt;span&gt; add_tag&lt;/span&gt;&lt;span&gt;(tag: &lt;/span&gt;&lt;span&gt;str&lt;/span&gt;&lt;span&gt;, tags: list[&lt;/span&gt;&lt;span&gt;str&lt;/span&gt;&lt;span&gt;] &lt;/span&gt;&lt;span&gt;|&lt;/span&gt;&lt;span&gt; None&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; None&lt;/span&gt;&lt;span&gt;) -&amp;gt; list[&lt;/span&gt;&lt;span&gt;str&lt;/span&gt;&lt;span&gt;]:&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    if&lt;/span&gt;&lt;span&gt; tags &lt;/span&gt;&lt;span&gt;is&lt;/span&gt;&lt;span&gt; None&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;        tags &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; []&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    tags.append(tag)&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    return&lt;/span&gt;&lt;span&gt; tags&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;验收：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;assert&lt;/span&gt;&lt;span&gt; add_tag(&lt;/span&gt;&lt;span&gt;&quot;ai&quot;&lt;/span&gt;&lt;span&gt;) &lt;/span&gt;&lt;span&gt;==&lt;/span&gt;&lt;span&gt; [&lt;/span&gt;&lt;span&gt;&quot;ai&quot;&lt;/span&gt;&lt;span&gt;]&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;assert&lt;/span&gt;&lt;span&gt; add_tag(&lt;/span&gt;&lt;span&gt;&quot;quant&quot;&lt;/span&gt;&lt;span&gt;) &lt;/span&gt;&lt;span&gt;==&lt;/span&gt;&lt;span&gt; [&lt;/span&gt;&lt;span&gt;&quot;quant&quot;&lt;/span&gt;&lt;span&gt;]&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;assert&lt;/span&gt;&lt;span&gt; add_tag(&lt;/span&gt;&lt;span&gt;&quot;python&quot;&lt;/span&gt;&lt;span&gt;, [&lt;/span&gt;&lt;span&gt;&quot;study&quot;&lt;/span&gt;&lt;span&gt;]) &lt;/span&gt;&lt;span&gt;==&lt;/span&gt;&lt;span&gt; [&lt;/span&gt;&lt;span&gt;&quot;study&quot;&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;&quot;python&quot;&lt;/span&gt;&lt;span&gt;]&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;hr /&gt;
&lt;h3&gt;2.3 数据结构：选择合适的容器&lt;a href=&quot;#23-数据结构选择合适的容器&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;Python 的数据结构不是装饰品。选错结构，代码会绕。&lt;/p&gt;






























&lt;table&gt;&lt;thead&gt;&lt;tr&gt;&lt;th&gt;结构&lt;/th&gt;&lt;th&gt;适合保存&lt;/th&gt;&lt;th&gt;示例&lt;/th&gt;&lt;/tr&gt;&lt;/thead&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td&gt;&lt;code&gt;list&lt;/code&gt;&lt;/td&gt;&lt;td&gt;有顺序、可重复的数据&lt;/td&gt;&lt;td&gt;收益率序列&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;&lt;code&gt;tuple&lt;/code&gt;&lt;/td&gt;&lt;td&gt;固定组合结果&lt;/td&gt;&lt;td&gt;&lt;code&gt;(train, valid)&lt;/code&gt;&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;&lt;code&gt;set&lt;/code&gt;&lt;/td&gt;&lt;td&gt;去重、集合差异&lt;/td&gt;&lt;td&gt;用户 ID 集合&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;&lt;code&gt;dict&lt;/code&gt;&lt;/td&gt;&lt;td&gt;名字到值的映射&lt;/td&gt;&lt;td&gt;指标字典&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;
&lt;h4&gt;函数怎么用&lt;a href=&quot;#函数怎么用-3&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/h4&gt;
&lt;p&gt;你希望指标函数返回清楚的字典：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;counts &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; confusion_counts([&lt;/span&gt;&lt;span&gt;1&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;0&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;1&lt;/span&gt;&lt;span&gt;], [&lt;/span&gt;&lt;span&gt;1&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;0&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;0&lt;/span&gt;&lt;span&gt;])&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;print&lt;/span&gt;&lt;span&gt;(counts[&lt;/span&gt;&lt;span&gt;&quot;tp&quot;&lt;/span&gt;&lt;span&gt;])&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h4&gt;代码逻辑&lt;a href=&quot;#代码逻辑-8&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/h4&gt;
&lt;p&gt;二分类里：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;y_true = 1, y_pred = 1 → tp&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;y_true = 0, y_pred = 0 → tn&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;y_true = 0, y_pred = 1 → fp&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;y_true = 1, y_pred = 0 → fn&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h4&gt;Exercise B-03：指标字典&lt;a href=&quot;#exercise-b-03指标字典&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/h4&gt;
&lt;p&gt;文件：&lt;code&gt;src/metrics.py&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;实现：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;def&lt;/span&gt;&lt;span&gt; accuracy&lt;/span&gt;&lt;span&gt;(y_true: list[&lt;/span&gt;&lt;span&gt;int&lt;/span&gt;&lt;span&gt;], y_pred: list[&lt;/span&gt;&lt;span&gt;int&lt;/span&gt;&lt;span&gt;]) -&amp;gt; &lt;/span&gt;&lt;span&gt;float&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    if&lt;/span&gt;&lt;span&gt; len&lt;/span&gt;&lt;span&gt;(y_true) &lt;/span&gt;&lt;span&gt;!=&lt;/span&gt;&lt;span&gt; len&lt;/span&gt;&lt;span&gt;(y_pred):&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;        raise&lt;/span&gt;&lt;span&gt; ValueError&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&quot;length mismatch&quot;&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    if&lt;/span&gt;&lt;span&gt; not&lt;/span&gt;&lt;span&gt; y_true:&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;        raise&lt;/span&gt;&lt;span&gt; ValueError&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&quot;empty labels&quot;&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    correct &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; 0&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    for&lt;/span&gt;&lt;span&gt; a, b &lt;/span&gt;&lt;span&gt;in&lt;/span&gt;&lt;span&gt; zip&lt;/span&gt;&lt;span&gt;(y_true, y_pred):&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;        if&lt;/span&gt;&lt;span&gt; a &lt;/span&gt;&lt;span&gt;==&lt;/span&gt;&lt;span&gt; b:&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;            correct &lt;/span&gt;&lt;span&gt;+=&lt;/span&gt;&lt;span&gt; 1&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    return&lt;/span&gt;&lt;span&gt; correct &lt;/span&gt;&lt;span&gt;/&lt;/span&gt;&lt;span&gt; len&lt;/span&gt;&lt;span&gt;(y_true)&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;def&lt;/span&gt;&lt;span&gt; confusion_counts&lt;/span&gt;&lt;span&gt;(y_true: list[&lt;/span&gt;&lt;span&gt;int&lt;/span&gt;&lt;span&gt;], y_pred: list[&lt;/span&gt;&lt;span&gt;int&lt;/span&gt;&lt;span&gt;]) -&amp;gt; dict[&lt;/span&gt;&lt;span&gt;str&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;int&lt;/span&gt;&lt;span&gt;]:&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    if&lt;/span&gt;&lt;span&gt; len&lt;/span&gt;&lt;span&gt;(y_true) &lt;/span&gt;&lt;span&gt;!=&lt;/span&gt;&lt;span&gt; len&lt;/span&gt;&lt;span&gt;(y_pred):&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;        raise&lt;/span&gt;&lt;span&gt; ValueError&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&quot;length mismatch&quot;&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    counts &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;span&gt;&quot;tp&quot;&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;0&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;&quot;tn&quot;&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;0&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;&quot;fp&quot;&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;0&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;&quot;fn&quot;&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;0&lt;/span&gt;&lt;span&gt;}&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    for&lt;/span&gt;&lt;span&gt; a, b &lt;/span&gt;&lt;span&gt;in&lt;/span&gt;&lt;span&gt; zip&lt;/span&gt;&lt;span&gt;(y_true, y_pred):&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;        if&lt;/span&gt;&lt;span&gt; a &lt;/span&gt;&lt;span&gt;==&lt;/span&gt;&lt;span&gt; 1&lt;/span&gt;&lt;span&gt; and&lt;/span&gt;&lt;span&gt; b &lt;/span&gt;&lt;span&gt;==&lt;/span&gt;&lt;span&gt; 1&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;            counts[&lt;/span&gt;&lt;span&gt;&quot;tp&quot;&lt;/span&gt;&lt;span&gt;] &lt;/span&gt;&lt;span&gt;+=&lt;/span&gt;&lt;span&gt; 1&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;        elif&lt;/span&gt;&lt;span&gt; a &lt;/span&gt;&lt;span&gt;==&lt;/span&gt;&lt;span&gt; 0&lt;/span&gt;&lt;span&gt; and&lt;/span&gt;&lt;span&gt; b &lt;/span&gt;&lt;span&gt;==&lt;/span&gt;&lt;span&gt; 0&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;            counts[&lt;/span&gt;&lt;span&gt;&quot;tn&quot;&lt;/span&gt;&lt;span&gt;] &lt;/span&gt;&lt;span&gt;+=&lt;/span&gt;&lt;span&gt; 1&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;        elif&lt;/span&gt;&lt;span&gt; a &lt;/span&gt;&lt;span&gt;==&lt;/span&gt;&lt;span&gt; 0&lt;/span&gt;&lt;span&gt; and&lt;/span&gt;&lt;span&gt; b &lt;/span&gt;&lt;span&gt;==&lt;/span&gt;&lt;span&gt; 1&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;            counts[&lt;/span&gt;&lt;span&gt;&quot;fp&quot;&lt;/span&gt;&lt;span&gt;] &lt;/span&gt;&lt;span&gt;+=&lt;/span&gt;&lt;span&gt; 1&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;        elif&lt;/span&gt;&lt;span&gt; a &lt;/span&gt;&lt;span&gt;==&lt;/span&gt;&lt;span&gt; 1&lt;/span&gt;&lt;span&gt; and&lt;/span&gt;&lt;span&gt; b &lt;/span&gt;&lt;span&gt;==&lt;/span&gt;&lt;span&gt; 0&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;            counts[&lt;/span&gt;&lt;span&gt;&quot;fn&quot;&lt;/span&gt;&lt;span&gt;] &lt;/span&gt;&lt;span&gt;+=&lt;/span&gt;&lt;span&gt; 1&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;        else&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;            raise&lt;/span&gt;&lt;span&gt; ValueError&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&quot;labels must be 0 or 1&quot;&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    return&lt;/span&gt;&lt;span&gt; counts&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;验收：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;y_true &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; [&lt;/span&gt;&lt;span&gt;1&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;0&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;1&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;1&lt;/span&gt;&lt;span&gt;]&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;y_pred &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; [&lt;/span&gt;&lt;span&gt;1&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;0&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;0&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;1&lt;/span&gt;&lt;span&gt;]&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;assert&lt;/span&gt;&lt;span&gt; accuracy(y_true, y_pred) &lt;/span&gt;&lt;span&gt;==&lt;/span&gt;&lt;span&gt; 0.75&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;assert&lt;/span&gt;&lt;span&gt; confusion_counts(y_true, y_pred) &lt;/span&gt;&lt;span&gt;==&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;span&gt;&quot;tp&quot;&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;2&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;&quot;tn&quot;&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;1&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;&quot;fp&quot;&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;0&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;&quot;fn&quot;&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;1&lt;/span&gt;&lt;span&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;hr /&gt;
&lt;h3&gt;2.4 列表推导式：把循环写短，但不要写晦涩&lt;a href=&quot;#24-列表推导式把循环写短但不要写晦涩&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;列表推导式是 Python 常用写法。它适合“从旧列表生成新列表”。&lt;/p&gt;
&lt;h4&gt;普通循环写法&lt;a href=&quot;#普通循环写法&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/h4&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;names &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; [&lt;/span&gt;&lt;span&gt;&quot; Alice &quot;&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;&quot;&quot;&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;&quot;BOB&quot;&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;&quot;  Carol&quot;&lt;/span&gt;&lt;span&gt;]&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;clean &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; []&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;for&lt;/span&gt;&lt;span&gt; name &lt;/span&gt;&lt;span&gt;in&lt;/span&gt;&lt;span&gt; names:&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    name &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; name.strip()&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    if&lt;/span&gt;&lt;span&gt; name:&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;        clean.append(name.lower())&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h4&gt;函数怎么用&lt;a href=&quot;#函数怎么用-4&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/h4&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;clean &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; [name.strip().lower() &lt;/span&gt;&lt;span&gt;for&lt;/span&gt;&lt;span&gt; name &lt;/span&gt;&lt;span&gt;in&lt;/span&gt;&lt;span&gt; names &lt;/span&gt;&lt;span&gt;if&lt;/span&gt;&lt;span&gt; name.strip()]&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h4&gt;代码逻辑&lt;a href=&quot;#代码逻辑-9&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/h4&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;name.strip().lower()      新列表里的元素长什么样&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;for name in names         从 names 里逐个拿元素&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;if name.strip()           只保留非空字符串&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h4&gt;Exercise B-04：批量清洗字符串&lt;a href=&quot;#exercise-b-04批量清洗字符串&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/h4&gt;
&lt;p&gt;输入：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;names &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; [&lt;/span&gt;&lt;span&gt;&quot; Alice &quot;&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;&quot;&quot;&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;&quot;BOB&quot;&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;&quot;  Carol&quot;&lt;/span&gt;&lt;span&gt;]&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;输出：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;[&lt;/span&gt;&lt;span&gt;&quot;alice&quot;&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;&quot;bob&quot;&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;&quot;carol&quot;&lt;/span&gt;&lt;span&gt;]&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;验收：你能把列表推导式改回普通 &lt;code&gt;for&lt;/code&gt; 循环。&lt;/p&gt;
&lt;hr /&gt;
&lt;h2&gt;3. Week C：模块、文件、JSON、异常、CLI&lt;a href=&quot;#3-week-c模块文件json异常cli&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Week C 的目标是把“能跑的脚本”变成“能复用的小工具”。这是从学习代码走向项目代码的关键一步。&lt;/p&gt;
&lt;h3&gt;3.1 模块：把函数放进文件&lt;a href=&quot;#31-模块把函数放进文件&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;当代码变长，不应该所有东西都写在一个文件里。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;src/&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;├── stats_utils.py      统计函数&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;├── metrics.py          模型指标函数&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;├── data_io.py          文件读写函数&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;└── cli.py              命令行入口&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h4&gt;模块怎么用&lt;a href=&quot;#模块怎么用&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/h4&gt;
&lt;p&gt;如果 &lt;code&gt;src/stats_utils.py&lt;/code&gt; 里有：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;def&lt;/span&gt;&lt;span&gt; mean&lt;/span&gt;&lt;span&gt;(xs: list[&lt;/span&gt;&lt;span&gt;float&lt;/span&gt;&lt;span&gt;]) -&amp;gt; &lt;/span&gt;&lt;span&gt;float&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    if&lt;/span&gt;&lt;span&gt; not&lt;/span&gt;&lt;span&gt; xs:&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;        raise&lt;/span&gt;&lt;span&gt; ValueError&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&quot;xs must not be empty&quot;&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    return&lt;/span&gt;&lt;span&gt; sum&lt;/span&gt;&lt;span&gt;(xs) &lt;/span&gt;&lt;span&gt;/&lt;/span&gt;&lt;span&gt; len&lt;/span&gt;&lt;span&gt;(xs)&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;那么其他文件可以导入：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;from&lt;/span&gt;&lt;span&gt; stats_utils &lt;/span&gt;&lt;span&gt;import&lt;/span&gt;&lt;span&gt; mean&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;print&lt;/span&gt;&lt;span&gt;(mean([&lt;/span&gt;&lt;span&gt;1&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;2&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;3&lt;/span&gt;&lt;span&gt;]))&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h4&gt;代码逻辑&lt;a href=&quot;#代码逻辑-10&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/h4&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;from stats_utils import mean       从 stats_utils.py 里拿 mean 函数&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;mean([1, 2, 3])                    调用这个函数&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h4&gt;Exercise C-01：模块拆分&lt;a href=&quot;#exercise-c-01模块拆分&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/h4&gt;
&lt;p&gt;要求：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;mean&lt;/code&gt;、&lt;code&gt;variance&lt;/code&gt; 放到 &lt;code&gt;src/stats_utils.py&lt;/code&gt;。&lt;/li&gt;
&lt;li&gt;&lt;code&gt;accuracy&lt;/code&gt;、&lt;code&gt;confusion_counts&lt;/code&gt; 放到 &lt;code&gt;src/metrics.py&lt;/code&gt;。&lt;/li&gt;
&lt;li&gt;&lt;code&gt;src/cli.py&lt;/code&gt; 负责调用它们。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;验收：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;python&lt;/span&gt;&lt;span&gt; src/cli.py&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;能看到一个最小输出，例如：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;mean = 2.0&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;accuracy = 0.75&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;hr /&gt;
&lt;h3&gt;3.2 文件读取：从磁盘拿数据&lt;a href=&quot;#32-文件读取从磁盘拿数据&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;真实项目不会永远把数据写死在代码里。你要学会从 CSV 文件读数据。&lt;/p&gt;
&lt;h4&gt;函数怎么用&lt;a href=&quot;#函数怎么用-5&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/h4&gt;
&lt;p&gt;你希望这样使用：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;from&lt;/span&gt;&lt;span&gt; pathlib &lt;/span&gt;&lt;span&gt;import&lt;/span&gt;&lt;span&gt; Path&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;from&lt;/span&gt;&lt;span&gt; data_io &lt;/span&gt;&lt;span&gt;import&lt;/span&gt;&lt;span&gt; read_scores_csv&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;rows &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; read_scores_csv(Path(&lt;/span&gt;&lt;span&gt;&quot;data/scores.csv&quot;&lt;/span&gt;&lt;span&gt;))&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;print&lt;/span&gt;&lt;span&gt;(rows)&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h4&gt;你要学的标准库&lt;a href=&quot;#你要学的标准库&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/h4&gt;





















&lt;table&gt;&lt;thead&gt;&lt;tr&gt;&lt;th&gt;模块/对象&lt;/th&gt;&lt;th&gt;作用&lt;/th&gt;&lt;/tr&gt;&lt;/thead&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td&gt;&lt;code&gt;pathlib.Path&lt;/code&gt;&lt;/td&gt;&lt;td&gt;表示文件路径&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;&lt;code&gt;csv.DictReader&lt;/code&gt;&lt;/td&gt;&lt;td&gt;把 CSV 每一行读成字典&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;&lt;code&gt;with ... as f&lt;/code&gt;&lt;/td&gt;&lt;td&gt;打开文件，并确保最后关闭&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;
&lt;h4&gt;最小数据&lt;a href=&quot;#最小数据&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/h4&gt;
&lt;p&gt;文件：&lt;code&gt;data/scores.csv&lt;/code&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;student_id,&lt;/span&gt;&lt;span&gt;group,&lt;/span&gt;&lt;span&gt;score&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;s1,&lt;/span&gt;&lt;span&gt;A,&lt;/span&gt;&lt;span&gt;80&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;s2,&lt;/span&gt;&lt;span&gt;A,&lt;/span&gt;&lt;span&gt;92&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;s3,&lt;/span&gt;&lt;span&gt;B,&lt;/span&gt;&lt;span&gt;75&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;s4,&lt;/span&gt;&lt;span&gt;B,&lt;/span&gt;&lt;span&gt;88&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h4&gt;代码逻辑&lt;a href=&quot;#代码逻辑-11&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/h4&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;import&lt;/span&gt;&lt;span&gt; csv&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;from&lt;/span&gt;&lt;span&gt; pathlib &lt;/span&gt;&lt;span&gt;import&lt;/span&gt;&lt;span&gt; Path&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;def&lt;/span&gt;&lt;span&gt; read_scores_csv&lt;/span&gt;&lt;span&gt;(path: Path) -&amp;gt; list[dict[&lt;/span&gt;&lt;span&gt;str&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;str&lt;/span&gt;&lt;span&gt;]]:&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    with&lt;/span&gt;&lt;span&gt; path.open(&lt;/span&gt;&lt;span&gt;&quot;r&quot;&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;encoding&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;&quot;utf-8&quot;&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;newline&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;&quot;&quot;&lt;/span&gt;&lt;span&gt;) &lt;/span&gt;&lt;span&gt;as&lt;/span&gt;&lt;span&gt; f:&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;        return&lt;/span&gt;&lt;span&gt; list&lt;/span&gt;&lt;span&gt;(csv.DictReader(f))&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;逐行解释：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;import csv                         使用 Python 标准库 csv&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;from pathlib import Path            使用 Path 表示路径&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;path.open(...)                      打开文件&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;encoding=&quot;utf-8&quot;                   按 UTF-8 读取文本&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;newline=&quot;&quot;                         让 csv 模块正确处理换行&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;csv.DictReader(f)                   第一行作为列名，每行变成 dict&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;list(...)                           把可迭代结果转成列表&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h4&gt;Exercise C-02：CSV 读写&lt;a href=&quot;#exercise-c-02csv-读写&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/h4&gt;
&lt;p&gt;文件：&lt;code&gt;src/data_io.py&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;实现 &lt;code&gt;read_scores_csv&lt;/code&gt;，然后写一个函数把 &lt;code&gt;score&lt;/code&gt; 转成数字：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;def&lt;/span&gt;&lt;span&gt; parse_scores&lt;/span&gt;&lt;span&gt;(rows: list[dict[&lt;/span&gt;&lt;span&gt;str&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;str&lt;/span&gt;&lt;span&gt;]]) -&amp;gt; list[&lt;/span&gt;&lt;span&gt;float&lt;/span&gt;&lt;span&gt;]:&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    scores &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; []&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    for&lt;/span&gt;&lt;span&gt; row &lt;/span&gt;&lt;span&gt;in&lt;/span&gt;&lt;span&gt; rows:&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;        scores.append(&lt;/span&gt;&lt;span&gt;float&lt;/span&gt;&lt;span&gt;(row[&lt;/span&gt;&lt;span&gt;&quot;score&quot;&lt;/span&gt;&lt;span&gt;]))&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    return&lt;/span&gt;&lt;span&gt; scores&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;验收：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;rows &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; read_scores_csv(Path(&lt;/span&gt;&lt;span&gt;&quot;data/scores.csv&quot;&lt;/span&gt;&lt;span&gt;))&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;scores &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; parse_scores(rows)&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;assert&lt;/span&gt;&lt;span&gt; scores &lt;/span&gt;&lt;span&gt;==&lt;/span&gt;&lt;span&gt; [&lt;/span&gt;&lt;span&gt;80.0&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;92.0&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;75.0&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;88.0&lt;/span&gt;&lt;span&gt;]&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;hr /&gt;
&lt;h3&gt;3.3 JSON：保存配置&lt;a href=&quot;#33-json保存配置&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;CSV 适合表格数据。JSON 适合配置。&lt;/p&gt;
&lt;h4&gt;函数怎么用&lt;a href=&quot;#函数怎么用-6&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/h4&gt;
&lt;p&gt;你希望这样读取配置：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;config &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; read_json(Path(&lt;/span&gt;&lt;span&gt;&quot;config.json&quot;&lt;/span&gt;&lt;span&gt;))&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;print&lt;/span&gt;&lt;span&gt;(config[&lt;/span&gt;&lt;span&gt;&quot;input&quot;&lt;/span&gt;&lt;span&gt;])&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h4&gt;最小配置&lt;a href=&quot;#最小配置&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/h4&gt;
&lt;p&gt;文件：&lt;code&gt;config.json&lt;/code&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;{&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  &quot;input&quot;&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;&quot;data/scores.csv&quot;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  &quot;output&quot;&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;&quot;reports/data_summary.md&quot;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  &quot;pass_score&quot;&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;60&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h4&gt;代码逻辑&lt;a href=&quot;#代码逻辑-12&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/h4&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;import&lt;/span&gt;&lt;span&gt; json&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;from&lt;/span&gt;&lt;span&gt; pathlib &lt;/span&gt;&lt;span&gt;import&lt;/span&gt;&lt;span&gt; Path&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;def&lt;/span&gt;&lt;span&gt; read_json&lt;/span&gt;&lt;span&gt;(path: Path) -&amp;gt; &lt;/span&gt;&lt;span&gt;dict&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    text &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; path.read_text(&lt;/span&gt;&lt;span&gt;encoding&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;&quot;utf-8&quot;&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    return&lt;/span&gt;&lt;span&gt; json.loads(text)&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;逐行解释：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;path.read_text(...)     把整个文件读成字符串&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;json.loads(text)        把 JSON 字符串转成 Python dict&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;return                  把 dict 返回给调用者&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h4&gt;Exercise C-03：实验配置&lt;a href=&quot;#exercise-c-03实验配置&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/h4&gt;
&lt;p&gt;文件：&lt;code&gt;src/data_io.py&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;实现：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;def&lt;/span&gt;&lt;span&gt; read_json&lt;/span&gt;&lt;span&gt;(path: Path) -&amp;gt; &lt;/span&gt;&lt;span&gt;dict&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    text &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; path.read_text(&lt;/span&gt;&lt;span&gt;encoding&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;&quot;utf-8&quot;&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    return&lt;/span&gt;&lt;span&gt; json.loads(text)&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;验收：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;config &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; read_json(Path(&lt;/span&gt;&lt;span&gt;&quot;config.json&quot;&lt;/span&gt;&lt;span&gt;))&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;assert&lt;/span&gt;&lt;span&gt; config[&lt;/span&gt;&lt;span&gt;&quot;input&quot;&lt;/span&gt;&lt;span&gt;] &lt;/span&gt;&lt;span&gt;==&lt;/span&gt;&lt;span&gt; &quot;data/scores.csv&quot;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;assert&lt;/span&gt;&lt;span&gt; config[&lt;/span&gt;&lt;span&gt;&quot;pass_score&quot;&lt;/span&gt;&lt;span&gt;] &lt;/span&gt;&lt;span&gt;==&lt;/span&gt;&lt;span&gt; 60&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;hr /&gt;
&lt;h3&gt;3.4 异常：让错误变得可理解&lt;a href=&quot;#34-异常让错误变得可理解&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;初学者常见错误是：程序错了，但不知道错在哪里。异常处理不是为了隐藏错误，而是为了把错误解释清楚。&lt;/p&gt;
&lt;h4&gt;函数怎么用&lt;a href=&quot;#函数怎么用-7&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/h4&gt;
&lt;p&gt;你希望用户给错路径时看到：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;input file not found: data/missing.csv&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;而不是一大段看不懂的 traceback。&lt;/p&gt;
&lt;h4&gt;错误写法&lt;a href=&quot;#错误写法&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/h4&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;try&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    rows &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; read_scores_csv(path)&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;except&lt;/span&gt;&lt;span&gt; Exception&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    pass&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;这会把错误吞掉。不要这样写。&lt;/p&gt;
&lt;h4&gt;正确写法&lt;a href=&quot;#正确写法&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/h4&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;try&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    rows &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; read_scores_csv(path)&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;except&lt;/span&gt;&lt;span&gt; FileNotFoundError&lt;/span&gt;&lt;span&gt; as&lt;/span&gt;&lt;span&gt; exc:&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    raise&lt;/span&gt;&lt;span&gt; SystemExit&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;f&lt;/span&gt;&lt;span&gt;&quot;input file not found: &lt;/span&gt;&lt;span&gt;{&lt;/span&gt;&lt;span&gt;path&lt;/span&gt;&lt;span&gt;}&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;) &lt;/span&gt;&lt;span&gt;from&lt;/span&gt;&lt;span&gt; exc&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h4&gt;代码逻辑&lt;a href=&quot;#代码逻辑-13&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/h4&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;try                         尝试运行可能失败的代码&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;except FileNotFoundError     只捕获文件不存在这一类错误&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;as exc                       保存原始错误&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;raise SystemExit(...)        给命令行用户一个清楚提示&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;from exc                     保留原始错误链，方便调试&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h4&gt;Exercise C-04：异常条件&lt;a href=&quot;#exercise-c-04异常条件&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/h4&gt;
&lt;p&gt;在 &lt;code&gt;src/data_io.py&lt;/code&gt; 或 &lt;code&gt;src/cli.py&lt;/code&gt; 中处理：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;输入文件不存在。&lt;/li&gt;
&lt;li&gt;CSV 缺少 &lt;code&gt;score&lt;/code&gt; 列。&lt;/li&gt;
&lt;li&gt;&lt;code&gt;score&lt;/code&gt; 不能转为数字。&lt;/li&gt;
&lt;li&gt;输出目录不存在时自动创建。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;验收：故意把输入路径写错，程序必须输出清楚错误，而不是静默结束。&lt;/p&gt;
&lt;hr /&gt;
&lt;h3&gt;3.5 CLI：从命令行传参数&lt;a href=&quot;#35-cli从命令行传参数&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;CLI 是 command line interface。你可以不用改代码，只通过命令传不同输入输出。&lt;/p&gt;
&lt;h4&gt;命令怎么用&lt;a href=&quot;#命令怎么用&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/h4&gt;
&lt;p&gt;目标命令：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;python&lt;/span&gt;&lt;span&gt; src/cli.py summary --input data/scores.csv --output reports/data_summary.md&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;含义：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;python src/cli.py        运行 cli.py&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;summary                  子任务：做数据摘要&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;--input ...              输入文件&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;--output ...             输出报告&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h4&gt;代码逻辑&lt;a href=&quot;#代码逻辑-14&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/h4&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;from&lt;/span&gt;&lt;span&gt; argparse &lt;/span&gt;&lt;span&gt;import&lt;/span&gt;&lt;span&gt; ArgumentParser&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;def&lt;/span&gt;&lt;span&gt; parse_args&lt;/span&gt;&lt;span&gt;():&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    parser &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; ArgumentParser(&lt;/span&gt;&lt;span&gt;description&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;&quot;Summarize score CSV&quot;&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    parser.add_argument(&lt;/span&gt;&lt;span&gt;&quot;command&quot;&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;choices&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;[&lt;/span&gt;&lt;span&gt;&quot;summary&quot;&lt;/span&gt;&lt;span&gt;])&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    parser.add_argument(&lt;/span&gt;&lt;span&gt;&quot;--input&quot;&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;required&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;True&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    parser.add_argument(&lt;/span&gt;&lt;span&gt;&quot;--output&quot;&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;required&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;True&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    return&lt;/span&gt;&lt;span&gt; parser.parse_args()&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;逐行解释：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;ArgumentParser(...)              创建参数解析器&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;add_argument(&quot;command&quot;)          读取位置参数 summary&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;choices=[&quot;summary&quot;]              限制只能输入 summary&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;add_argument(&quot;--input&quot;)          读取输入路径&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;required=True                    必须提供这个参数&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;parse_args()                     从命令行解析参数&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h4&gt;Exercise C-05：生成 Markdown 摘要报告&lt;a href=&quot;#exercise-c-05生成-markdown-摘要报告&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/h4&gt;
&lt;p&gt;文件：&lt;code&gt;src/cli.py&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;目标：读 &lt;code&gt;data/scores.csv&lt;/code&gt;，输出 &lt;code&gt;reports/data_summary.md&lt;/code&gt;。&lt;/p&gt;
&lt;p&gt;报告至少包含：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;# Data Summary&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;-&lt;/span&gt;&lt;span&gt; rows: 4&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;-&lt;/span&gt;&lt;span&gt; mean score: 83.75&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;-&lt;/span&gt;&lt;span&gt; min score: 75.0&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;-&lt;/span&gt;&lt;span&gt; max score: 92.0&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;验收命令：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;python&lt;/span&gt;&lt;span&gt; src/cli.py summary --input data/scores.csv --output reports/data_summary.md&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;cat&lt;/span&gt;&lt;span&gt; reports/data_summary.md&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;hr /&gt;
&lt;h2&gt;4. Week D：类、迭代器、标准库、浮点数、综合项目&lt;a href=&quot;#4-week-d类迭代器标准库浮点数综合项目&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Week D 的目标是把前面的函数组织成更像真实项目的结构，并理解两个 AI / Quant 特别容易踩坑的东西：迭代器和浮点数。&lt;/p&gt;
&lt;h3&gt;4.1 dataclass：保存配置对象&lt;a href=&quot;#41-dataclass保存配置对象&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;类不是必须一开始学，但你需要读懂这种写法，因为后续 PyTorch、sklearn、Quant 回测都会有配置对象。&lt;/p&gt;
&lt;h4&gt;类怎么用&lt;a href=&quot;#类怎么用&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/h4&gt;
&lt;p&gt;你希望这样创建配置：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;config &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; ExperimentConfig(&lt;/span&gt;&lt;span&gt;input_path&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;&quot;data/scores.csv&quot;&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;output_dir&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;&quot;reports&quot;&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;print&lt;/span&gt;&lt;span&gt;(config.input_path)&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h4&gt;代码逻辑&lt;a href=&quot;#代码逻辑-15&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/h4&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;from&lt;/span&gt;&lt;span&gt; dataclasses &lt;/span&gt;&lt;span&gt;import&lt;/span&gt;&lt;span&gt; dataclass&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;@dataclass&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;frozen&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;True&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;class&lt;/span&gt;&lt;span&gt; ExperimentConfig&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    input_path: &lt;/span&gt;&lt;span&gt;str&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    output_dir: &lt;/span&gt;&lt;span&gt;str&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    seed: &lt;/span&gt;&lt;span&gt;int&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; 42&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;逐行解释：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;@dataclass(frozen=True)       自动生成初始化方法，并禁止修改字段&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;class ExperimentConfig        定义一个配置类型&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;input_path: str               输入路径字段&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;output_dir: str               输出目录字段&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;seed: int = 42                随机种子，默认是 42&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h4&gt;Exercise D-01：实验配置类&lt;a href=&quot;#exercise-d-01实验配置类&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/h4&gt;
&lt;p&gt;文件：&lt;code&gt;src/experiment.py&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;实现 &lt;code&gt;ExperimentConfig&lt;/code&gt;，然后运行：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;config &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; ExperimentConfig(&lt;/span&gt;&lt;span&gt;input_path&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;&quot;data/scores.csv&quot;&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;output_dir&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;&quot;reports&quot;&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;assert&lt;/span&gt;&lt;span&gt; config.seed &lt;/span&gt;&lt;span&gt;==&lt;/span&gt;&lt;span&gt; 42&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;验收：你能解释 &lt;code&gt;config.input_path&lt;/code&gt; 和字典 &lt;code&gt;config[&quot;input_path&quot;]&lt;/code&gt; 的区别。&lt;/p&gt;
&lt;hr /&gt;
&lt;h3&gt;4.2 迭代器和生成器：逐行处理数据&lt;a href=&quot;#42-迭代器和生成器逐行处理数据&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;如果文件很大，不应该一次把所有内容读进内存。生成器可以一行一行地产生数据。&lt;/p&gt;
&lt;h4&gt;函数怎么用&lt;a href=&quot;#函数怎么用-8&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/h4&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;for&lt;/span&gt;&lt;span&gt; line &lt;/span&gt;&lt;span&gt;in&lt;/span&gt;&lt;span&gt; iter_non_empty_lines(Path(&lt;/span&gt;&lt;span&gt;&quot;notes/raw.txt&quot;&lt;/span&gt;&lt;span&gt;)):&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    print&lt;/span&gt;&lt;span&gt;(line)&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h4&gt;代码逻辑&lt;a href=&quot;#代码逻辑-16&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/h4&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;from&lt;/span&gt;&lt;span&gt; pathlib &lt;/span&gt;&lt;span&gt;import&lt;/span&gt;&lt;span&gt; Path&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;def&lt;/span&gt;&lt;span&gt; iter_non_empty_lines&lt;/span&gt;&lt;span&gt;(path: Path):&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    with&lt;/span&gt;&lt;span&gt; path.open(&lt;/span&gt;&lt;span&gt;&quot;r&quot;&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;encoding&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;&quot;utf-8&quot;&lt;/span&gt;&lt;span&gt;) &lt;/span&gt;&lt;span&gt;as&lt;/span&gt;&lt;span&gt; f:&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;        for&lt;/span&gt;&lt;span&gt; line &lt;/span&gt;&lt;span&gt;in&lt;/span&gt;&lt;span&gt; f:&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;            line &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; line.strip()&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;            if&lt;/span&gt;&lt;span&gt; line:&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;                yield&lt;/span&gt;&lt;span&gt; line&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;逐行解释：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;for line in f          文件对象可以逐行迭代&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;line.strip()           去掉换行和两边空格&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;if line                空字符串会被当成 False&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;yield line             产出一行，但函数不一次性结束&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;code&gt;return&lt;/code&gt; 是一次性返回。&lt;code&gt;yield&lt;/code&gt; 是“这次先给你一个值，下次循环再继续”。&lt;/p&gt;
&lt;h4&gt;Exercise D-02：逐行读取&lt;a href=&quot;#exercise-d-02逐行读取&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/h4&gt;
&lt;p&gt;文件：&lt;code&gt;src/data_io.py&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;实现 &lt;code&gt;iter_non_empty_lines&lt;/code&gt;。&lt;/p&gt;
&lt;p&gt;验收：输入文件中有空行时，输出里不能包含空字符串。&lt;/p&gt;
&lt;hr /&gt;
&lt;h3&gt;4.3 浮点数：不要用 &lt;code&gt;==&lt;/code&gt; 比较小数&lt;a href=&quot;#43-浮点数不要用--比较小数&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;Python 官方教程第 15 章非常重要。它解释了为什么很多小数不能被二进制浮点数精确表示。&lt;/p&gt;
&lt;h4&gt;函数怎么用&lt;a href=&quot;#函数怎么用-9&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/h4&gt;
&lt;p&gt;错误比较：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;0.1&lt;/span&gt;&lt;span&gt; +&lt;/span&gt;&lt;span&gt; 0.1&lt;/span&gt;&lt;span&gt; +&lt;/span&gt;&lt;span&gt; 0.1&lt;/span&gt;&lt;span&gt; ==&lt;/span&gt;&lt;span&gt; 0.3&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;推荐比较：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;import&lt;/span&gt;&lt;span&gt; math&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;math.isclose(&lt;/span&gt;&lt;span&gt;0.1&lt;/span&gt;&lt;span&gt; +&lt;/span&gt;&lt;span&gt; 0.1&lt;/span&gt;&lt;span&gt; +&lt;/span&gt;&lt;span&gt; 0.1&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;0.3&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h4&gt;代码逻辑&lt;a href=&quot;#代码逻辑-17&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/h4&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;0.1 在二进制里不能精确表示&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;三个近似值相加，结果仍然是近似值&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;== 要求完全相等，所以可能失败&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;math.isclose 允许非常小的误差&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;这对 Quant 特别重要。收益率、波动率、Sharpe、loss 都是浮点数，不要随便用 &lt;code&gt;==&lt;/code&gt; 判断。&lt;/p&gt;
&lt;h4&gt;Exercise D-03：浮点复盘报告&lt;a href=&quot;#exercise-d-03浮点复盘报告&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/h4&gt;
&lt;p&gt;文件：&lt;code&gt;reports/floating_point_notes.md&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;写清楚：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;为什么 &lt;code&gt;0.1 + 0.2 != 0.3&lt;/code&gt;。&lt;/li&gt;
&lt;li&gt;&lt;code&gt;round&lt;/code&gt; 是显示/近似，不是改变真实存储。&lt;/li&gt;
&lt;li&gt;为什么金融收益率和模型 loss 不能用 &lt;code&gt;==&lt;/code&gt; 比较小数。&lt;/li&gt;
&lt;li&gt;什么时候用 &lt;code&gt;math.isclose&lt;/code&gt;。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;验收：报告里必须有一段你自己的解释，不要只复制代码。&lt;/p&gt;
&lt;hr /&gt;
&lt;h3&gt;4.4 收益率函数：从价格变成收益率&lt;a href=&quot;#44-收益率函数从价格变成收益率&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;Quant 的最小输入通常是价格序列。最小计算是收益率。&lt;/p&gt;
&lt;h4&gt;函数怎么用&lt;a href=&quot;#函数怎么用-10&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/h4&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;returns &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; pct_change([&lt;/span&gt;&lt;span&gt;100&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;101&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;99&lt;/span&gt;&lt;span&gt;])&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;print&lt;/span&gt;&lt;span&gt;(returns)&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;期望：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;[None, 0.010000000000000009, -0.01980198019801982]&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;第一天没有前一天价格，所以收益率是 &lt;code&gt;None&lt;/code&gt;。&lt;/p&gt;
&lt;h4&gt;代码逻辑&lt;a href=&quot;#代码逻辑-18&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/h4&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;def&lt;/span&gt;&lt;span&gt; pct_change&lt;/span&gt;&lt;span&gt;(values: list[&lt;/span&gt;&lt;span&gt;float&lt;/span&gt;&lt;span&gt;]) -&amp;gt; list[&lt;/span&gt;&lt;span&gt;float&lt;/span&gt;&lt;span&gt; |&lt;/span&gt;&lt;span&gt; None&lt;/span&gt;&lt;span&gt;]:&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    returns: list[&lt;/span&gt;&lt;span&gt;float&lt;/span&gt;&lt;span&gt; |&lt;/span&gt;&lt;span&gt; None&lt;/span&gt;&lt;span&gt;] &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; [&lt;/span&gt;&lt;span&gt;None&lt;/span&gt;&lt;span&gt;]&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    for&lt;/span&gt;&lt;span&gt; prev, curr &lt;/span&gt;&lt;span&gt;in&lt;/span&gt;&lt;span&gt; zip&lt;/span&gt;&lt;span&gt;(values, values[&lt;/span&gt;&lt;span&gt;1&lt;/span&gt;&lt;span&gt;:]):&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;        if&lt;/span&gt;&lt;span&gt; prev &lt;/span&gt;&lt;span&gt;==&lt;/span&gt;&lt;span&gt; 0&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;            returns.append(&lt;/span&gt;&lt;span&gt;None&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;        else&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;            returns.append(curr &lt;/span&gt;&lt;span&gt;/&lt;/span&gt;&lt;span&gt; prev &lt;/span&gt;&lt;span&gt;-&lt;/span&gt;&lt;span&gt; 1&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    return&lt;/span&gt;&lt;span&gt; returns&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;逐行解释：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;returns = [None]             第一项没有前值&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;values[1:]                   从第二个价格开始&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;zip(values, values[1:])      组成相邻价格对&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;prev == 0                    防止除以 0&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;curr / prev - 1              收益率公式&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;returns.append(...)          保存每一天收益率&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h4&gt;Exercise D-04：收益率计算&lt;a href=&quot;#exercise-d-04收益率计算&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/h4&gt;
&lt;p&gt;文件：&lt;code&gt;src/toy_backtest.py&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;实现 &lt;code&gt;pct_change&lt;/code&gt;，验收：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;import&lt;/span&gt;&lt;span&gt; math&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;returns &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; pct_change([&lt;/span&gt;&lt;span&gt;100&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;101&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;99&lt;/span&gt;&lt;span&gt;])&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;assert&lt;/span&gt;&lt;span&gt; returns[&lt;/span&gt;&lt;span&gt;0&lt;/span&gt;&lt;span&gt;] &lt;/span&gt;&lt;span&gt;is&lt;/span&gt;&lt;span&gt; None&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;assert&lt;/span&gt;&lt;span&gt; math.isclose(returns[&lt;/span&gt;&lt;span&gt;1&lt;/span&gt;&lt;span&gt;], &lt;/span&gt;&lt;span&gt;0.01&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;assert&lt;/span&gt;&lt;span&gt; math.isclose(returns[&lt;/span&gt;&lt;span&gt;2&lt;/span&gt;&lt;span&gt;], &lt;/span&gt;&lt;span&gt;99&lt;/span&gt;&lt;span&gt; /&lt;/span&gt;&lt;span&gt; 101&lt;/span&gt;&lt;span&gt; -&lt;/span&gt;&lt;span&gt; 1&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;hr /&gt;
&lt;h3&gt;4.5 shift：避免未来函数&lt;a href=&quot;#45-shift避免未来函数&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;回测里最危险的错误之一是未来函数：用今天收盘后的信息，在今天就交易。&lt;/p&gt;
&lt;p&gt;如果你根据今天收益率生成信号，那么真正能执行的仓位应该从下一天开始。&lt;/p&gt;
&lt;h4&gt;函数怎么用&lt;a href=&quot;#函数怎么用-11&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/h4&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;signals &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; [&lt;/span&gt;&lt;span&gt;1&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;0&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;1&lt;/span&gt;&lt;span&gt;]&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;positions &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; shift(signals, &lt;/span&gt;&lt;span&gt;fill&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;0&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;print&lt;/span&gt;&lt;span&gt;(positions)&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;输出：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;[0, 1, 0]&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;含义：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;第 1 天没有前一天信号，所以仓位 0&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;第 2 天使用第 1 天信号&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;第 3 天使用第 2 天信号&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h4&gt;代码逻辑&lt;a href=&quot;#代码逻辑-19&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/h4&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;def&lt;/span&gt;&lt;span&gt; shift&lt;/span&gt;&lt;span&gt;(values: list[&lt;/span&gt;&lt;span&gt;int&lt;/span&gt;&lt;span&gt;], fill: &lt;/span&gt;&lt;span&gt;int&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; 0&lt;/span&gt;&lt;span&gt;) -&amp;gt; list[&lt;/span&gt;&lt;span&gt;int&lt;/span&gt;&lt;span&gt;]:&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    return&lt;/span&gt;&lt;span&gt; [fill] &lt;/span&gt;&lt;span&gt;+&lt;/span&gt;&lt;span&gt; values[:&lt;/span&gt;&lt;span&gt;-&lt;/span&gt;&lt;span&gt;1&lt;/span&gt;&lt;span&gt;]&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;逐行解释：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;values[:-1]       去掉最后一个元素&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;[fill] + ...      在最前面补一个默认值&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h4&gt;Exercise D-05：仓位滞后&lt;a href=&quot;#exercise-d-05仓位滞后&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/h4&gt;
&lt;p&gt;文件：&lt;code&gt;src/toy_backtest.py&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;实现 &lt;code&gt;shift&lt;/code&gt;，验收：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;assert&lt;/span&gt;&lt;span&gt; shift([&lt;/span&gt;&lt;span&gt;1&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;0&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;1&lt;/span&gt;&lt;span&gt;], &lt;/span&gt;&lt;span&gt;fill&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;0&lt;/span&gt;&lt;span&gt;) &lt;/span&gt;&lt;span&gt;==&lt;/span&gt;&lt;span&gt; [&lt;/span&gt;&lt;span&gt;0&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;1&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;0&lt;/span&gt;&lt;span&gt;]&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;assert&lt;/span&gt;&lt;span&gt; shift([&lt;/span&gt;&lt;span&gt;1&lt;/span&gt;&lt;span&gt;], &lt;/span&gt;&lt;span&gt;fill&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;0&lt;/span&gt;&lt;span&gt;) &lt;/span&gt;&lt;span&gt;==&lt;/span&gt;&lt;span&gt; [&lt;/span&gt;&lt;span&gt;0&lt;/span&gt;&lt;span&gt;]&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;你必须能解释：为什么不能直接 &lt;code&gt;position = signal&lt;/code&gt;。&lt;/p&gt;
&lt;hr /&gt;
&lt;h3&gt;4.6 Toy Quant Backtest：综合练习&lt;a href=&quot;#46-toy-quant-backtest综合练习&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;现在把前面学到的东西串起来。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;读 CSV&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  ↓&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;取 close 列&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  ↓&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;计算收益率&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  ↓&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;根据收益率生成 signal&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  ↓&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;shift(signal) 得到 position&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  ↓&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;计算策略收益&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  ↓&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;输出 Markdown 报告&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h4&gt;输入数据&lt;a href=&quot;#输入数据&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/h4&gt;
&lt;p&gt;文件：&lt;code&gt;data/prices.csv&lt;/code&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;date,&lt;/span&gt;&lt;span&gt;close&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;2024-01-01,&lt;/span&gt;&lt;span&gt;100&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;2024-01-02,&lt;/span&gt;&lt;span&gt;101&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;2024-01-03,&lt;/span&gt;&lt;span&gt;99&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;2024-01-04,&lt;/span&gt;&lt;span&gt;102&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h4&gt;关键函数怎么用&lt;a href=&quot;#关键函数怎么用&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/h4&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;rows &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; read_prices(Path(&lt;/span&gt;&lt;span&gt;&quot;data/prices.csv&quot;&lt;/span&gt;&lt;span&gt;))&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;closes &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; [&lt;/span&gt;&lt;span&gt;float&lt;/span&gt;&lt;span&gt;(row[&lt;/span&gt;&lt;span&gt;&quot;close&quot;&lt;/span&gt;&lt;span&gt;]) &lt;/span&gt;&lt;span&gt;for&lt;/span&gt;&lt;span&gt; row &lt;/span&gt;&lt;span&gt;in&lt;/span&gt;&lt;span&gt; rows]&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;returns &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; pct_change(closes)&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;signals &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; [&lt;/span&gt;&lt;span&gt;1&lt;/span&gt;&lt;span&gt; if&lt;/span&gt;&lt;span&gt; r &lt;/span&gt;&lt;span&gt;is&lt;/span&gt;&lt;span&gt; not&lt;/span&gt;&lt;span&gt; None&lt;/span&gt;&lt;span&gt; and&lt;/span&gt;&lt;span&gt; r &lt;/span&gt;&lt;span&gt;&amp;gt;&lt;/span&gt;&lt;span&gt; 0&lt;/span&gt;&lt;span&gt; else&lt;/span&gt;&lt;span&gt; 0&lt;/span&gt;&lt;span&gt; for&lt;/span&gt;&lt;span&gt; r &lt;/span&gt;&lt;span&gt;in&lt;/span&gt;&lt;span&gt; returns]&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;positions &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; shift(signals, &lt;/span&gt;&lt;span&gt;fill&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;0&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h4&gt;代码逻辑&lt;a href=&quot;#代码逻辑-20&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/h4&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;read_prices              从 CSV 读取字典列表&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;float(row[&quot;close&quot;])      把文本价格转成数字&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;pct_change               价格 → 收益率&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;signal                   收益率为正则看多，否则空仓&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;shift                    信号滞后一日，避免未来函数&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;position * return        仓位乘收益率，得到策略收益&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h4&gt;Exercise D-06：完整 toy backtest&lt;a href=&quot;#exercise-d-06完整-toy-backtest&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/h4&gt;
&lt;p&gt;文件：&lt;code&gt;src/toy_backtest.py&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;要求：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;用 &lt;code&gt;csv&lt;/code&gt; 读取数据。&lt;/li&gt;
&lt;li&gt;用 &lt;code&gt;pct_change&lt;/code&gt; 计算收益率。&lt;/li&gt;
&lt;li&gt;用 &lt;code&gt;signal = 1 if return &amp;gt; 0 else 0&lt;/code&gt; 生成简单信号。&lt;/li&gt;
&lt;li&gt;用 &lt;code&gt;position = shift(signal)&lt;/code&gt; 避免未来函数。&lt;/li&gt;
&lt;li&gt;用 &lt;code&gt;math.isclose&lt;/code&gt; 做浮点验收。&lt;/li&gt;
&lt;li&gt;输出 &lt;code&gt;reports/backtest_report.md&lt;/code&gt;。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;报告至少包含：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;# Toy Backtest Report&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;-&lt;/span&gt;&lt;span&gt; rows: 4&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;-&lt;/span&gt;&lt;span&gt; total return: ...&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;-&lt;/span&gt;&lt;span&gt; max daily return: ...&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;-&lt;/span&gt;&lt;span&gt; min daily return: ...&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;-&lt;/span&gt;&lt;span&gt; warning: signal is shifted by one day to avoid look-ahead bias&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;验收命令：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;python&lt;/span&gt;&lt;span&gt; src/toy_backtest.py --input data/prices.csv --output reports/backtest_report.md&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;cat&lt;/span&gt;&lt;span&gt; reports/backtest_report.md&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;hr /&gt;
&lt;h3&gt;4.7 doctest：把示例变成自检&lt;a href=&quot;#47-doctest把示例变成自检&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;小函数可以直接在 docstring 里写示例。&lt;code&gt;doctest&lt;/code&gt; 会执行这些示例。&lt;/p&gt;
&lt;h4&gt;函数怎么用&lt;a href=&quot;#函数怎么用-12&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/h4&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;python&lt;/span&gt;&lt;span&gt; src/stats_utils.py&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;如果 doctest 通过，通常没有输出。如果失败，会显示哪里不一致。&lt;/p&gt;
&lt;h4&gt;代码逻辑&lt;a href=&quot;#代码逻辑-21&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/h4&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;def&lt;/span&gt;&lt;span&gt; mean&lt;/span&gt;&lt;span&gt;(xs: list[&lt;/span&gt;&lt;span&gt;float&lt;/span&gt;&lt;span&gt;]) -&amp;gt; &lt;/span&gt;&lt;span&gt;float&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    &quot;&quot;&quot;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    Return arithmetic mean.&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    &amp;gt;&amp;gt;&amp;gt; &lt;/span&gt;&lt;span&gt;mean([1, 2, 3])&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    2.0&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    &quot;&quot;&quot;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    if&lt;/span&gt;&lt;span&gt; not&lt;/span&gt;&lt;span&gt; xs:&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;        raise&lt;/span&gt;&lt;span&gt; ValueError&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&quot;xs must not be empty&quot;&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    return&lt;/span&gt;&lt;span&gt; sum&lt;/span&gt;&lt;span&gt;(xs) &lt;/span&gt;&lt;span&gt;/&lt;/span&gt;&lt;span&gt; len&lt;/span&gt;&lt;span&gt;(xs)&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;if&lt;/span&gt;&lt;span&gt; __name__&lt;/span&gt;&lt;span&gt; ==&lt;/span&gt;&lt;span&gt; &quot;__main__&quot;&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    import&lt;/span&gt;&lt;span&gt; doctest&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    doctest.testmod()&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;逐行解释：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;&amp;gt;&amp;gt;&amp;gt; mean([1, 2, 3])       docstring 里的交互式示例&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;2.0                       期望输出&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;doctest.testmod()         扫描当前模块里的 docstring 示例并执行&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h4&gt;Exercise D-07：给统计函数加 doctest&lt;a href=&quot;#exercise-d-07给统计函数加-doctest&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/h4&gt;
&lt;p&gt;文件：&lt;code&gt;src/stats_utils.py&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;至少给 &lt;code&gt;mean&lt;/code&gt; 和 &lt;code&gt;variance&lt;/code&gt; 加 doctest。&lt;/p&gt;
&lt;p&gt;验收：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;python&lt;/span&gt;&lt;span&gt; src/stats_utils.py&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;没有失败信息。&lt;/p&gt;
&lt;hr /&gt;
&lt;h2&gt;5. 最终检查清单&lt;a href=&quot;#5-最终检查清单&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;完成后，你应该能运行：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;python&lt;/span&gt;&lt;span&gt; src/basics.py&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;python&lt;/span&gt;&lt;span&gt; src/cli.py summary --input data/scores.csv --output reports/data_summary.md&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;python&lt;/span&gt;&lt;span&gt; src/toy_backtest.py --input data/prices.csv --output reports/backtest_report.md&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;python&lt;/span&gt;&lt;span&gt; src/stats_utils.py&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;最终交付：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;src/basics.py&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;src/stats_utils.py&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;src/metrics.py&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;src/data_io.py&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;src/cli.py&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;src/toy_backtest.py&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;src/experiment.py&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;reports/data_summary.md&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;reports/backtest_report.md&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;reports/floating_point_notes.md&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;notes/official_tutorial_mapping.md&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;hr /&gt;
&lt;h2&gt;6. 评分标准&lt;a href=&quot;#6-评分标准&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;













































&lt;table&gt;&lt;thead&gt;&lt;tr&gt;&lt;th&gt;项目&lt;/th&gt;&lt;th&gt;分值&lt;/th&gt;&lt;th&gt;合格标准&lt;/th&gt;&lt;/tr&gt;&lt;/thead&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td&gt;环境可复现&lt;/td&gt;&lt;td&gt;15&lt;/td&gt;&lt;td&gt;README 写清环境和命令，&lt;code&gt;.venv&lt;/code&gt; 不入 Git&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;基础语法&lt;/td&gt;&lt;td&gt;15&lt;/td&gt;&lt;td&gt;能写数字、字符串、列表、控制流练习&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;函数质量&lt;/td&gt;&lt;td&gt;20&lt;/td&gt;&lt;td&gt;有类型提示、docstring、错误处理、assert/doctest&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;数据结构&lt;/td&gt;&lt;td&gt;10&lt;/td&gt;&lt;td&gt;list/dict/set/tuple 使用合适&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;模块化&lt;/td&gt;&lt;td&gt;15&lt;/td&gt;&lt;td&gt;&lt;code&gt;src/&lt;/code&gt; 拆分清楚，导入关系明确&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;文件/CLI&lt;/td&gt;&lt;td&gt;15&lt;/td&gt;&lt;td&gt;能读 CSV/JSON，能输出报告，CLI 可运行&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;综合项目&lt;/td&gt;&lt;td&gt;10&lt;/td&gt;&lt;td&gt;toy backtest 或数据摘要项目可复现&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;
&lt;p&gt;低于 80 分：不要进入 pandas/sklearn。先补 Python。&lt;/p&gt;
&lt;hr /&gt;
&lt;h2&gt;7. 每日复盘模板&lt;a href=&quot;#7-每日复盘模板&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;# Day X Python Official Practice&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;## 今天读的官方章节&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;## 今天学会的函数或语法&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;## 今天写的代码&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;## 每一行代码的逻辑解释&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;## 今天遇到的错误&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;## 我现在还解释不清的地方&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;## 明天继续&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;最低日任务：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;读官方文档 20 分钟&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;跟着手册敲代码 40 分钟&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;运行和修错 20 分钟&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;写复盘 10 分钟&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;hr /&gt;
&lt;h2&gt;8. 什么时候可以进入 pandas / sklearn / PyTorch&lt;a href=&quot;#8-什么时候可以进入-pandas--sklearn--pytorch&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;不要用“看过 Python 教程”当作标准。用下面这些能力判断：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;能解释 list / dict / set / tuple 的区别&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;能写带参数和返回值的函数&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;能处理空列表、路径不存在、类型转换失败&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;能把函数拆进不同模块&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;能从命令行传 input/output&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;能读 CSV 和 JSON&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;能输出 Markdown 报告&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;能解释为什么回测要 shift signal&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;能解释为什么浮点数不能直接 == 比较&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;这些都能做到，再进入 pandas / sklearn / PyTorch。否则后面遇到的错误看起来像库的问题，其实是 Python 基础问题。&lt;/p&gt;</content:encoded><category>category:工具</category><category>category:Python基础</category><category>tag:Python官方教程</category><category>tag:函数</category><category>tag:CLI</category><category>tag:标准库</category><category>tag:ToyBacktest</category></item><item><title>Week 17：项目工程化、CLI 入口与可复现实验</title><link>https://39miku.space/post/week17-project-engineering</link><guid isPermaLink="false">week17-project-engineering</guid><description>Week 17：项目工程化、CLI 入口与可复现实验。fish-first 终端教学，面向 CachyOS、VS Code、uv 和 Python 学习路线。</description><pubDate>Sun, 26 Jul 2026 09:00:00 GMT</pubDate><content:encoded>
&lt;section&gt;
  &lt;div&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;strong&gt;Oh My Pi&lt;/strong&gt; / weekly tutorial / week17-project-engineering&lt;/span&gt;&lt;/div&gt;
  &lt;div&gt;
    &lt;div&gt;&lt;span&gt;miku@cachyos&lt;/span&gt;&lt;span&gt;:~/Code/python-learning&lt;/span&gt;&lt;span&gt;$&lt;/span&gt; &lt;span&gt;omp teach week17-project-engineering --fish-first --step-by-step&lt;/span&gt;&lt;/div&gt;
    &lt;div&gt;
      &lt;div&gt;&lt;span&gt;source&lt;/span&gt;&lt;strong&gt;591 行教学文档&lt;/strong&gt;&lt;/div&gt;
      &lt;div&gt;&lt;span&gt;week&lt;/span&gt;&lt;strong&gt;Week 17&lt;/strong&gt;&lt;/div&gt;
      &lt;div&gt;&lt;span&gt;shell&lt;/span&gt;&lt;strong&gt;fish-first 命令版&lt;/strong&gt;&lt;/div&gt;
      &lt;div&gt;&lt;span&gt;backlink&lt;/span&gt;&lt;strong&gt;&lt;a href=&quot;/post/ustc-stat-ai-quant-plan/&quot;&gt;20 周计划&lt;/a&gt;&lt;/strong&gt;&lt;/div&gt;
    &lt;/div&gt;
    &lt;article&gt;
&lt;h1&gt;Week 17：项目工程化、CLI 入口与可复现实验&lt;/h1&gt;
&lt;blockquote&gt;
&lt;p&gt;返回总计划：&lt;a href=&quot;/post/ustc-stat-ai-quant-plan/&quot;&gt;USTC 统计 + AI / 量化 20 周成长计划&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Week 17 的目标是把前面做过的项目整理成一个干净、可运行、可复现的仓库。对 USTC 统计学生来说，这一步非常重要：老师、同学、面试官或未来的你，不会只看“我跑过一次”，而会看项目是否能被别人重新运行。&lt;/p&gt;
&lt;p&gt;本文命令默认使用 &lt;strong&gt;fish shell&lt;/strong&gt;。进入项目后激活环境：&lt;/p&gt;
&lt;div&gt;&lt;div&gt;&lt;span&gt;omp&lt;/span&gt;&lt;span&gt;fish&lt;/span&gt;&lt;/div&gt;&lt;pre&gt;&lt;code&gt;source .venv/bin/activate.fish
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;&lt;a&gt;&lt;/a&gt;&lt;/p&gt;
&lt;h2&gt;0. 本周详细教学：语法、规范、验收&lt;/h2&gt;
&lt;blockquote&gt;
&lt;p&gt;本节不是追加在尾部的复习，而是本周正文的入口。先读这里，再做后面的命令和项目。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h3&gt;0.1 本周真正要学会什么&lt;/h3&gt;
&lt;table&gt;
&lt;thead&gt;&lt;tr&gt;&lt;th&gt;维度&lt;/th&gt;&lt;th&gt;要求&lt;/th&gt;&lt;/tr&gt;&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;&lt;td&gt;知识点&lt;/td&gt;&lt;td&gt;README、seed、依赖、运行说明、复现&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;代码语法&lt;/td&gt;&lt;td&gt;能从空文件写出本周核心脚本，而不是只复制运行&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;程序规范&lt;/td&gt;&lt;td&gt;函数拆分、路径清楚、输入输出明确、错误能解释&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;交付物&lt;/td&gt;&lt;td&gt;&lt;code&gt;README.md&lt;/code&gt;&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;验收方式&lt;/td&gt;&lt;td&gt;从 fish 终端运行命令，得到可复查的文件或指标&lt;/td&gt;&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;h3&gt;0.2 代码语法精讲&lt;/h3&gt;
&lt;p&gt;下面的代码不是最终答案，而是本周必须理解的最小骨架：&lt;/p&gt;
&lt;div&gt;&lt;div&gt;&lt;span&gt;omp&lt;/span&gt;&lt;span&gt;python&lt;/span&gt;&lt;/div&gt;&lt;pre&gt;&lt;code&gt;mkdir -p src reports figures outputs
python src/train.py --config configs/baseline.yaml
python src/evaluate.py --model outputs/best.pt
python src/make_report.py --output reports/final_report.md
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;读代码时按四步检查：输入从哪里来；中间变量的类型和 shape 是什么；函数或脚本输出什么；哪些错误应该显式报出来。&lt;/p&gt;
&lt;h3&gt;0.3 本周程序规范&lt;/h3&gt;
&lt;ul&gt;&lt;li&gt;所有路径用相对路径或 `pathlib.Path`，不要写死 `/home/miku/...`。&lt;/li&gt;&lt;li&gt;核心逻辑进 `src/`，notebook 只做探索和解释。&lt;/li&gt;&lt;li&gt;每个脚本能从 fish 终端运行，并在 README 写出命令。&lt;/li&gt;&lt;li&gt;输出必须落盘到 `reports/`、`figures/` 或 `outputs/`，不能只在屏幕上看。&lt;/li&gt;&lt;/ul&gt;
&lt;h3&gt;0.4 本周练习分层&lt;/h3&gt;
&lt;table&gt;
&lt;thead&gt;&lt;tr&gt;&lt;th&gt;层级&lt;/th&gt;&lt;th&gt;任务&lt;/th&gt;&lt;th&gt;不合格表现&lt;/th&gt;&lt;th&gt;合格验收&lt;/th&gt;&lt;/tr&gt;&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;&lt;td&gt;最小练习&lt;/td&gt;&lt;td&gt;手写上面的最小骨架&lt;/td&gt;&lt;td&gt;只在 notebook 里运行&lt;/td&gt;&lt;td&gt;终端运行成功&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;标准练习&lt;/td&gt;&lt;td&gt;把逻辑拆成函数/模块&lt;/td&gt;&lt;td&gt;一个大脚本从头写到尾&lt;/td&gt;&lt;td&gt;至少 2 个函数，职责清楚&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;项目练习&lt;/td&gt;&lt;td&gt;生成本周交付物 &lt;code&gt;README.md&lt;/code&gt;&lt;/td&gt;&lt;td&gt;只有屏幕输出&lt;/td&gt;&lt;td&gt;文件落盘，可复查&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;复盘练习&lt;/td&gt;&lt;td&gt;写 3 个错误和修复&lt;/td&gt;&lt;td&gt;只写“已解决”&lt;/td&gt;&lt;td&gt;写清报错、原因、修复、预防&lt;/td&gt;&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;h3&gt;0.5 本周和主线的连接&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;回到总计划：&lt;a href=&quot;/post/ustc-stat-ai-quant-plan/#week-plan&quot;&gt;USTC AI / Quant 练习手册&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;查详细练习索引：&lt;a href=&quot;/post/ustc-stat-ai-quant-plan/#deep-practice-appendix&quot;&gt;技术练习详解&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;查质量评分：&lt;a href=&quot;/post/ustc-stat-ai-quant-plan/#quality-final-gates&quot;&gt;最终质量门槛&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;1. 本周学习目标&lt;/h2&gt;
&lt;p&gt;完成本周后，你应该能把一个项目整理到以下状态：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;目录结构清楚：代码、数据、报告、图片、配置分离。&lt;/li&gt;
&lt;li&gt;没有临时代码、随手命名的 notebook、无用输出文件。&lt;/li&gt;
&lt;li&gt;使用 &lt;code&gt;uv&lt;/code&gt; 管理依赖，&lt;code&gt;pyproject.toml&lt;/code&gt; 能说明项目需要什么包。&lt;/li&gt;
&lt;li&gt;有 CLI 入口，可以用一条命令运行主流程。&lt;/li&gt;
&lt;li&gt;README 写清楚安装、运行、输出、项目结构和复现步骤。&lt;/li&gt;
&lt;li&gt;随机种子固定，输出结果可复查。&lt;/li&gt;
&lt;li&gt;&lt;code&gt;.gitignore&lt;/code&gt; 不提交 &lt;code&gt;.venv&lt;/code&gt;、缓存、大文件和临时文件。&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;建议最终结构：&lt;/p&gt;
&lt;div&gt;&lt;div&gt;&lt;span&gt;omp&lt;/span&gt;&lt;span&gt;prompt&lt;/span&gt;&lt;/div&gt;&lt;pre&gt;&lt;code&gt;quant-backtest-mini/
├── README.md
├── pyproject.toml
├── uv.lock
├── .gitignore
├── data/
│   ├── README.md
│   └── prices.csv
├── figures/
│   ├── equity_curve.png
│   ├── drawdown.png
│   └── cost_sensitivity.png
├── reports/
│   ├── week16_metrics_by_split.csv
│   ├── week16_cost_sensitivity.csv
│   └── week16_quant_project_report.md
└── src/
    └── quant_backtest_mini/
        ├── __init__.py
        ├── __main__.py
        ├── backtest.py
        ├── cli.py
        ├── metrics.py
        └── report.py
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;注意：这里的 &lt;code&gt;data/README.md&lt;/code&gt; 是项目内部数据说明，不是本博客源文档。Week 17 允许你在自己的量化项目里写 README；但本教程文档本身只放在 &lt;code&gt;docs/week17-project-engineering.md&lt;/code&gt;。&lt;/p&gt;
&lt;h2&gt;2. 前置条件&lt;/h2&gt;
&lt;p&gt;你需要已经有一个待整理项目，例如 Week 15-16 的 &lt;code&gt;quant-backtest-mini&lt;/code&gt;。&lt;/p&gt;
&lt;p&gt;进入项目：&lt;/p&gt;
&lt;div&gt;&lt;div&gt;&lt;span&gt;omp&lt;/span&gt;&lt;span&gt;fish&lt;/span&gt;&lt;/div&gt;&lt;pre&gt;&lt;code&gt;cd ~/Code/ustc-stat-ai-quant/quant-backtest-mini
source .venv/bin/activate.fish
pwd
ls
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;确认你看到：&lt;/p&gt;
&lt;div&gt;&lt;div&gt;&lt;span&gt;omp&lt;/span&gt;&lt;span&gt;prompt&lt;/span&gt;&lt;/div&gt;&lt;pre&gt;&lt;code&gt;pyproject.toml
src
reports
figures
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;如果项目不在这个路径，进入你自己的项目根目录即可。判断标准：当前目录应包含 &lt;code&gt;pyproject.toml&lt;/code&gt;，而不是只在总学习目录。&lt;/p&gt;
&lt;h2&gt;3. 先做清点，不急着重构&lt;/h2&gt;
&lt;p&gt;工程化第一步不是乱改文件，而是弄清楚现在有什么。&lt;/p&gt;
&lt;div&gt;&lt;div&gt;&lt;span&gt;omp&lt;/span&gt;&lt;span&gt;fish&lt;/span&gt;&lt;/div&gt;&lt;pre&gt;&lt;code&gt;find . -maxdepth 3 -type f | sort
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;这条命令用于人工检查项目文件。重点找：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;Untitled.ipynb&lt;/code&gt;、&lt;code&gt;test.py&lt;/code&gt;、&lt;code&gt;tmp.py&lt;/code&gt; 这类临时文件。&lt;/li&gt;
&lt;li&gt;重复保存的结果，例如 &lt;code&gt;result_final_final.csv&lt;/code&gt;。&lt;/li&gt;
&lt;li&gt;不该提交的缓存，例如 &lt;code&gt;__pycache__&lt;/code&gt;。&lt;/li&gt;
&lt;li&gt;是否有数据说明。&lt;/li&gt;
&lt;li&gt;是否有 README。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;如果你不确定某个文件是否还在用，先不要删。可以用 VS Code 搜索文件名或导入名。&lt;/p&gt;
&lt;h2&gt;4. 建立 &lt;code&gt;.gitignore&lt;/code&gt;&lt;/h2&gt;
&lt;p&gt;如果项目还没有 &lt;code&gt;.gitignore&lt;/code&gt;，创建：&lt;/p&gt;
&lt;div&gt;&lt;div&gt;&lt;span&gt;omp&lt;/span&gt;&lt;span&gt;fish&lt;/span&gt;&lt;/div&gt;&lt;pre&gt;&lt;code&gt;code .gitignore
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;写入：&lt;/p&gt;
&lt;div&gt;&lt;div&gt;&lt;span&gt;omp&lt;/span&gt;&lt;span&gt;prompt&lt;/span&gt;&lt;/div&gt;&lt;pre&gt;&lt;code&gt;.venv/
__pycache__/
*.pyc
.ipynb_checkpoints/
.ruff_cache/
.mypy_cache/
.pytest_cache/
.DS_Store
*.log
.env
.env.*
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;解释：&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;条目&lt;/th&gt;
&lt;th&gt;为什么忽略&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;&lt;tr&gt;
&lt;td&gt;&lt;code&gt;.venv/&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;虚拟环境很大，别人应自己用 uv 创建&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;__pycache__/&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Python 运行缓存，不是源码&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;.ipynb_checkpoints/&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Jupyter 自动缓存&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;*.log&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;临时日志通常不可复现&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;.env&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;可能包含密钥或本机路径&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;&lt;/table&gt;
&lt;p&gt;检查 Git 状态：&lt;/p&gt;
&lt;div&gt;&lt;div&gt;&lt;span&gt;omp&lt;/span&gt;&lt;span&gt;fish&lt;/span&gt;&lt;/div&gt;&lt;pre&gt;&lt;code&gt;git status --short
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;如果项目还没有 Git 仓库，可以初始化：&lt;/p&gt;
&lt;div&gt;&lt;div&gt;&lt;span&gt;omp&lt;/span&gt;&lt;span&gt;fish&lt;/span&gt;&lt;/div&gt;&lt;pre&gt;&lt;code&gt;git init
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;不要提交前先确认 &lt;code&gt;.venv/&lt;/code&gt; 没有出现在 &lt;code&gt;git status --short&lt;/code&gt; 里。&lt;/p&gt;
&lt;h2&gt;5. 固定依赖：用 uv 管理环境&lt;/h2&gt;
&lt;p&gt;查看当前依赖：&lt;/p&gt;
&lt;div&gt;&lt;div&gt;&lt;span&gt;omp&lt;/span&gt;&lt;span&gt;fish&lt;/span&gt;&lt;/div&gt;&lt;pre&gt;&lt;code&gt;cat pyproject.toml
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;如果 Week 15-16 用到了 pandas、numpy、matplotlib、scikit-learn，确保它们在依赖中：&lt;/p&gt;
&lt;div&gt;&lt;div&gt;&lt;span&gt;omp&lt;/span&gt;&lt;span&gt;fish&lt;/span&gt;&lt;/div&gt;&lt;pre&gt;&lt;code&gt;uv add pandas numpy matplotlib scikit-learn
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;如果你没有用 &lt;code&gt;scikit-learn&lt;/code&gt;，不要为了好看添加它。依赖越少越容易复现。&lt;/p&gt;
&lt;p&gt;重新同步环境：&lt;/p&gt;
&lt;div&gt;&lt;div&gt;&lt;span&gt;omp&lt;/span&gt;&lt;span&gt;fish&lt;/span&gt;&lt;/div&gt;&lt;pre&gt;&lt;code&gt;uv sync
source .venv/bin/activate.fish
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;解释：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;uv add&lt;/code&gt;：把依赖写进 &lt;code&gt;pyproject.toml&lt;/code&gt;，并更新锁文件。&lt;/li&gt;
&lt;li&gt;&lt;code&gt;uv sync&lt;/code&gt;：根据项目配置创建或同步 &lt;code&gt;.venv&lt;/code&gt;。&lt;/li&gt;
&lt;li&gt;&lt;code&gt;uv.lock&lt;/code&gt;：记录具体版本，帮助别人复现环境。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;如果你需要导出传统 requirements：&lt;/p&gt;
&lt;div&gt;&lt;div&gt;&lt;span&gt;omp&lt;/span&gt;&lt;span&gt;fish&lt;/span&gt;&lt;/div&gt;&lt;pre&gt;&lt;code&gt;uv export --format requirements-txt --output-file requirements.txt
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;但对新项目，优先保留 &lt;code&gt;pyproject.toml&lt;/code&gt; 和 &lt;code&gt;uv.lock&lt;/code&gt;。&lt;/p&gt;
&lt;h2&gt;6. 整理 Python 包结构&lt;/h2&gt;
&lt;p&gt;推荐把业务代码放在 &lt;code&gt;src/quant_backtest_mini/&lt;/code&gt;。&lt;/p&gt;
&lt;p&gt;检查：&lt;/p&gt;
&lt;div&gt;&lt;div&gt;&lt;span&gt;omp&lt;/span&gt;&lt;span&gt;fish&lt;/span&gt;&lt;/div&gt;&lt;pre&gt;&lt;code&gt;ls src/quant_backtest_mini
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;应至少包含：&lt;/p&gt;
&lt;div&gt;&lt;div&gt;&lt;span&gt;omp&lt;/span&gt;&lt;span&gt;prompt&lt;/span&gt;&lt;/div&gt;&lt;pre&gt;&lt;code&gt;__init__.py
backtest.py
metrics.py
report.py
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;如果你现在只有一个 &lt;code&gt;backtest.py&lt;/code&gt; 放在项目根目录，可以移动：&lt;/p&gt;
&lt;div&gt;&lt;div&gt;&lt;span&gt;omp&lt;/span&gt;&lt;span&gt;fish&lt;/span&gt;&lt;/div&gt;&lt;pre&gt;&lt;code&gt;mkdir -p src/quant_backtest_mini
mv backtest.py src/quant_backtest_mini/backtest.py
printf &quot;&quot; &amp;gt; src/quant_backtest_mini/__init__.py
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;移动后，要把导入改成包内导入。例如：&lt;/p&gt;
&lt;div&gt;&lt;div&gt;&lt;span&gt;omp&lt;/span&gt;&lt;span&gt;python&lt;/span&gt;&lt;/div&gt;&lt;pre&gt;&lt;code&gt;from quant_backtest_mini.metrics import summarize
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;而不是依赖当前工作目录碰巧能找到文件。&lt;/p&gt;
&lt;h2&gt;7. 添加 CLI 入口&lt;/h2&gt;
&lt;p&gt;CLI 的作用是让别人不用打开 Python 文件，也能运行项目。&lt;/p&gt;
&lt;p&gt;创建 &lt;code&gt;src/quant_backtest_mini/cli.py&lt;/code&gt;：&lt;/p&gt;
&lt;div&gt;&lt;div&gt;&lt;span&gt;omp&lt;/span&gt;&lt;span&gt;python&lt;/span&gt;&lt;/div&gt;&lt;pre&gt;&lt;code&gt;import argparse
&lt;p&gt;from quant_backtest_mini.backtest import load_prices, plot_results, run_backtest
from quant_backtest_mini.metrics import summarize&lt;/p&gt;
&lt;p&gt;def build_parser() -&amp;gt; argparse.ArgumentParser:
parser = argparse.ArgumentParser(description=“Run a mini momentum backtest.”)
parser.add_argument(“—data”, default=“data/prices.csv”, help=“Path to price CSV.”)
parser.add_argument(“—lookback”, type=int, default=20, help=“Momentum lookback days.”)
parser.add_argument(“—cost-bps”, type=float, default=5.0, help=“Transaction cost in bps.”)
return parser&lt;/p&gt;
&lt;p&gt;def main() -&amp;gt; None:
parser = build_parser()
args = parser.parse_args()&lt;/p&gt;
&lt;p&gt;plain prices = load_prices(args.data)
result = run_backtest(prices, lookback=args.lookback, cost_bps=args.cost_bps)
summary = summarize(result[“net_strategy_return”], result[“turnover”])&lt;/p&gt;
&lt;p&gt;plain for key, value in summary.items():
print(f”{key}: {value:.4f}”)&lt;/p&gt;
&lt;p&gt;plain plot_results(result)
result.to_csv(“reports/backtest_daily_results.csv”)
print(“saved reports/backtest_daily_results.csv and figures”)&lt;/p&gt;
&lt;/code&gt;&lt;p&gt;&lt;code&gt;if &lt;strong&gt;name&lt;/strong&gt; == “&lt;strong&gt;main&lt;/strong&gt;”:
main()
&lt;/code&gt;&lt;/p&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;&lt;/p&gt;
&lt;p&gt;再创建 &lt;code&gt;src/quant_backtest_mini/__main__.py&lt;/code&gt;：&lt;/p&gt;
&lt;div&gt;&lt;div&gt;&lt;span&gt;omp&lt;/span&gt;&lt;span&gt;python&lt;/span&gt;&lt;/div&gt;&lt;pre&gt;&lt;code&gt;from quant_backtest_mini.cli import main
&lt;/code&gt;&lt;p&gt;&lt;code&gt;if &lt;strong&gt;name&lt;/strong&gt; == “&lt;strong&gt;main&lt;/strong&gt;”:
main()
&lt;/code&gt;&lt;/p&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;&lt;/p&gt;
&lt;p&gt;现在可以运行：&lt;/p&gt;
&lt;div&gt;&lt;div&gt;&lt;span&gt;omp&lt;/span&gt;&lt;span&gt;fish&lt;/span&gt;&lt;/div&gt;&lt;pre&gt;&lt;code&gt;python -m quant_backtest_mini --data data/prices.csv --lookback 20 --cost-bps 5
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;如果提示找不到模块，检查是否已用 &lt;code&gt;uv sync&lt;/code&gt; 安装当前项目。也可以在 &lt;code&gt;pyproject.toml&lt;/code&gt; 里确认项目包配置是否正确。&lt;/p&gt;
&lt;h2&gt;8. 在 &lt;code&gt;pyproject.toml&lt;/code&gt; 中添加命令入口&lt;/h2&gt;
&lt;p&gt;打开：&lt;/p&gt;
&lt;div&gt;&lt;div&gt;&lt;span&gt;omp&lt;/span&gt;&lt;span&gt;fish&lt;/span&gt;&lt;/div&gt;&lt;pre&gt;&lt;code&gt;code pyproject.toml
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;在文件中加入脚本入口，具体位置通常在依赖列表之后：&lt;/p&gt;
&lt;div&gt;&lt;div&gt;&lt;span&gt;omp&lt;/span&gt;&lt;span&gt;toml&lt;/span&gt;&lt;/div&gt;&lt;pre&gt;&lt;code&gt;[project.scripts]
quant-backtest-mini = &quot;quant_backtest_mini.cli:main&quot;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;然后同步：&lt;/p&gt;
&lt;div&gt;&lt;div&gt;&lt;span&gt;omp&lt;/span&gt;&lt;span&gt;fish&lt;/span&gt;&lt;/div&gt;&lt;pre&gt;&lt;code&gt;uv sync
source .venv/bin/activate.fish
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;运行：&lt;/p&gt;
&lt;div&gt;&lt;div&gt;&lt;span&gt;omp&lt;/span&gt;&lt;span&gt;fish&lt;/span&gt;&lt;/div&gt;&lt;pre&gt;&lt;code&gt;quant-backtest-mini --data data/prices.csv --lookback 20 --cost-bps 5
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;这就是一个项目级 CLI。以后 README 里可以直接写这条命令。&lt;/p&gt;
&lt;h2&gt;9. 固定随机种子和运行参数&lt;/h2&gt;
&lt;p&gt;如果项目里有任何随机过程，例如模拟数据、随机森林、抽样，都要固定随机种子。&lt;/p&gt;
&lt;p&gt;示例：&lt;/p&gt;
&lt;div&gt;&lt;div&gt;&lt;span&gt;omp&lt;/span&gt;&lt;span&gt;python&lt;/span&gt;&lt;/div&gt;&lt;pre&gt;&lt;code&gt;RANDOM_SEED = 42
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;在 numpy 中：&lt;/p&gt;
&lt;div&gt;&lt;div&gt;&lt;span&gt;omp&lt;/span&gt;&lt;span&gt;python&lt;/span&gt;&lt;/div&gt;&lt;pre&gt;&lt;code&gt;rng = np.random.default_rng(RANDOM_SEED)
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;在 scikit-learn 中：&lt;/p&gt;
&lt;div&gt;&lt;div&gt;&lt;span&gt;omp&lt;/span&gt;&lt;span&gt;python&lt;/span&gt;&lt;/div&gt;&lt;pre&gt;&lt;code&gt;RandomForestClassifier(random_state=RANDOM_SEED)
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;同时，把核心运行参数写进 README：&lt;/p&gt;
&lt;div&gt;&lt;div&gt;&lt;span&gt;omp&lt;/span&gt;&lt;span&gt;prompt&lt;/span&gt;&lt;/div&gt;&lt;pre&gt;&lt;code&gt;默认参数：lookback=20, cost_bps=5
执行假设：t 日收盘生成信号，t+1 日持仓
随机种子：42
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;可复现不是“我电脑上能跑”，而是别人能知道你用了什么参数跑。&lt;/p&gt;
&lt;h2&gt;10. 写 README&lt;/h2&gt;
&lt;p&gt;打开 README：&lt;/p&gt;
&lt;div&gt;&lt;div&gt;&lt;span&gt;omp&lt;/span&gt;&lt;span&gt;fish&lt;/span&gt;&lt;/div&gt;&lt;pre&gt;&lt;code&gt;code README.md
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;推荐结构：&lt;/p&gt;
&lt;div&gt;&lt;div&gt;&lt;span&gt;omp&lt;/span&gt;&lt;span&gt;prompt&lt;/span&gt;&lt;/div&gt;&lt;pre&gt;&lt;code&gt;# Quant Backtest Mini
&lt;p&gt;A minimal momentum backtest project for learning time-series validation and quant metrics.&lt;/p&gt;
&lt;h2&gt;What this project does&lt;a href=&quot;#what-this-project-does&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;Loads daily close prices.&lt;/li&gt;
&lt;li&gt;Computes daily returns.&lt;/li&gt;
&lt;li&gt;Builds a simple N-day momentum signal.&lt;/li&gt;
&lt;li&gt;Applies a one-day signal delay to avoid look-ahead bias.&lt;/li&gt;
&lt;li&gt;Subtracts transaction costs based on turnover.&lt;/li&gt;
&lt;li&gt;Reports annual return, annual volatility, Sharpe, max drawdown, and turnover.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;Project structure&lt;a href=&quot;#project-structure&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;写出 data、src、reports、figures 分别放什么。&lt;/p&gt;
&lt;h2&gt;Setup&lt;a href=&quot;#setup&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;uv sync
source .venv/bin/activate.fish&lt;/p&gt;
&lt;h2&gt;Run&lt;a href=&quot;#run&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;quant-backtest-mini —data data/prices.csv —lookback 20 —cost-bps 5&lt;/p&gt;
&lt;h2&gt;Outputs&lt;a href=&quot;#outputs&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;reports/backtest_daily_results.csv&lt;/li&gt;
&lt;li&gt;figures/equity_curve.png&lt;/li&gt;
&lt;li&gt;figures/drawdown.png&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;Reproducibility&lt;a href=&quot;#reproducibility&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;Python dependencies are managed by uv.&lt;/li&gt;
&lt;li&gt;Lock file: uv.lock&lt;/li&gt;
&lt;li&gt;Random seed: 42 if simulated data is regenerated.&lt;/li&gt;
&lt;li&gt;Default parameters: lookback=20, cost_bps=5.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;Limitations&lt;a href=&quot;#limitations&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;
&lt;/code&gt;&lt;ul&gt;&lt;code&gt;
&lt;li&gt;Educational project only.&lt;/li&gt;
&lt;li&gt;No slippage model.&lt;/li&gt;
&lt;li&gt;No liquidity constraints.&lt;/li&gt;
&lt;li&gt;No live trading.&lt;/li&gt;
&lt;/code&gt;&lt;li&gt;&lt;code&gt;Results do not imply future profitability.
&lt;/code&gt;&lt;/li&gt;&lt;/ul&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;README 要让别人能在 3 分钟内知道：这是什么、怎么装、怎么跑、会生成什么、限制是什么。&lt;/p&gt;
&lt;h2&gt;11. 数据说明&lt;/h2&gt;
&lt;p&gt;如果 &lt;code&gt;data/prices.csv&lt;/code&gt; 是模拟数据或小样本数据，必须说明来源。可以创建：&lt;/p&gt;
&lt;div&gt;&lt;div&gt;&lt;span&gt;omp&lt;/span&gt;&lt;span&gt;fish&lt;/span&gt;&lt;/div&gt;&lt;pre&gt;&lt;code&gt;code data/README.md
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;写入：&lt;/p&gt;
&lt;div&gt;&lt;div&gt;&lt;span&gt;omp&lt;/span&gt;&lt;span&gt;prompt&lt;/span&gt;&lt;/div&gt;&lt;pre&gt;&lt;code&gt;# Data
&lt;p&gt;prices.csv contains daily close prices used for the mini backtest.&lt;/p&gt;
&lt;p&gt;Columns:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;date: trading date&lt;/li&gt;
&lt;li&gt;close: closing price&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Source:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Simulated data generated for learning, or replace this line with the real data source.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Notes:&lt;/p&gt;
&lt;/code&gt;&lt;ul&gt;&lt;code&gt;
&lt;li&gt;This data is for educational backtesting only.&lt;/li&gt;
&lt;/code&gt;&lt;li&gt;&lt;code&gt;Do not treat results as investment advice.
&lt;/code&gt;&lt;/li&gt;&lt;/ul&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;如果真实数据体积很大，不要直接提交大文件。README 中写清楚下载方式，或只提交小样本。&lt;/p&gt;
&lt;h2&gt;12. 可复现运行检查&lt;/h2&gt;
&lt;p&gt;从一个干净终端重新跑：&lt;/p&gt;
&lt;div&gt;&lt;div&gt;&lt;span&gt;omp&lt;/span&gt;&lt;span&gt;fish&lt;/span&gt;&lt;/div&gt;&lt;pre&gt;&lt;code&gt;cd ~/Code/ustc-stat-ai-quant/quant-backtest-mini
uv sync
source .venv/bin/activate.fish
quant-backtest-mini --data data/prices.csv --lookback 20 --cost-bps 5
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;检查输出文件：&lt;/p&gt;
&lt;div&gt;&lt;div&gt;&lt;span&gt;omp&lt;/span&gt;&lt;span&gt;fish&lt;/span&gt;&lt;/div&gt;&lt;pre&gt;&lt;code&gt;ls reports figures
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;用 Python 检查关键文件存在：&lt;/p&gt;
&lt;div&gt;&lt;div&gt;&lt;span&gt;omp&lt;/span&gt;&lt;span&gt;fish&lt;/span&gt;&lt;/div&gt;&lt;pre&gt;&lt;code&gt;python -c &quot;from pathlib import Path; required = [&apos;README.md&apos;, &apos;pyproject.toml&apos;, &apos;uv.lock&apos;, &apos;src/quant_backtest_mini/cli.py&apos;, &apos;src/quant_backtest_mini/__main__.py&apos;, &apos;reports/backtest_daily_results.csv&apos;, &apos;figures/equity_curve.png&apos;, &apos;figures/drawdown.png&apos;]; missing = [p for p in required if not Path(p).exists()]; print(&apos;missing:&apos;, missing); raise SystemExit(1 if missing else 0)&quot;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;这一步比“我觉得整理好了”可靠。&lt;/p&gt;
&lt;h2&gt;13. Git 提交前检查&lt;/h2&gt;
&lt;p&gt;查看状态：&lt;/p&gt;
&lt;div&gt;&lt;div&gt;&lt;span&gt;omp&lt;/span&gt;&lt;span&gt;fish&lt;/span&gt;&lt;/div&gt;&lt;pre&gt;&lt;code&gt;git status --short
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;你希望看到的是源码、配置、报告、图片、小数据说明，而不是虚拟环境和缓存。&lt;/p&gt;
&lt;p&gt;常见不该提交：&lt;/p&gt;
&lt;div&gt;&lt;div&gt;&lt;span&gt;omp&lt;/span&gt;&lt;span&gt;prompt&lt;/span&gt;&lt;/div&gt;&lt;pre&gt;&lt;code&gt;.venv/
__pycache__/
.ipynb_checkpoints/
*.log
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;添加文件：&lt;/p&gt;
&lt;div&gt;&lt;div&gt;&lt;span&gt;omp&lt;/span&gt;&lt;span&gt;fish&lt;/span&gt;&lt;/div&gt;&lt;pre&gt;&lt;code&gt;git add README.md pyproject.toml uv.lock .gitignore data src reports figures
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;提交：&lt;/p&gt;
&lt;div&gt;&lt;div&gt;&lt;span&gt;omp&lt;/span&gt;&lt;span&gt;fish&lt;/span&gt;&lt;/div&gt;&lt;pre&gt;&lt;code&gt;git commit -m &quot;Organize reproducible quant backtest project&quot;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;如果只是课程练习，也可以暂不推送远程仓库。但至少学会让本地历史清楚。&lt;/p&gt;
&lt;h2&gt;14. 清理临时代码&lt;/h2&gt;
&lt;p&gt;删除前先确认临时文件没有用。常见候选：&lt;/p&gt;
&lt;div&gt;&lt;div&gt;&lt;span&gt;omp&lt;/span&gt;&lt;span&gt;prompt&lt;/span&gt;&lt;/div&gt;&lt;pre&gt;&lt;code&gt;tmp.py
scratch.py
Untitled.ipynb
result_old.csv
result_final_final.csv
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;删除单个确认无用的文件：&lt;/p&gt;
&lt;div&gt;&lt;div&gt;&lt;span&gt;omp&lt;/span&gt;&lt;span&gt;fish&lt;/span&gt;&lt;/div&gt;&lt;pre&gt;&lt;code&gt;rm tmp.py
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;不要用危险的大范围删除命令。初学阶段宁愿手动删慢一点，也不要误删报告或数据。&lt;/p&gt;
&lt;h2&gt;15. 文件布局验收&lt;/h2&gt;
&lt;p&gt;执行：&lt;/p&gt;
&lt;div&gt;&lt;div&gt;&lt;span&gt;omp&lt;/span&gt;&lt;span&gt;fish&lt;/span&gt;&lt;/div&gt;&lt;pre&gt;&lt;code&gt;pwd
ls
ls src/quant_backtest_mini reports figures data
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;理想结构：&lt;/p&gt;
&lt;div&gt;&lt;div&gt;&lt;span&gt;omp&lt;/span&gt;&lt;span&gt;prompt&lt;/span&gt;&lt;/div&gt;&lt;pre&gt;&lt;code&gt;README.md
pyproject.toml
uv.lock
.gitignore
data/
figures/
reports/
src/
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;包目录至少包含：&lt;/p&gt;
&lt;div&gt;&lt;div&gt;&lt;span&gt;omp&lt;/span&gt;&lt;span&gt;prompt&lt;/span&gt;&lt;/div&gt;&lt;pre&gt;&lt;code&gt;__init__.py
__main__.py
backtest.py
cli.py
metrics.py
report.py
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;h2&gt;16. 练习&lt;/h2&gt;
&lt;ol&gt;
&lt;li&gt;给 CLI 增加 &lt;code&gt;--output&lt;/code&gt; 参数，让用户指定结果 CSV 保存路径。&lt;/li&gt;
&lt;li&gt;给 CLI 增加 &lt;code&gt;--no-plots&lt;/code&gt; 参数，在只想生成表格时不画图。&lt;/li&gt;
&lt;li&gt;在 README 中加入一段“Methodology”，解释为什么 &lt;code&gt;position = signal.shift(1)&lt;/code&gt;。&lt;/li&gt;
&lt;li&gt;把 Week 16 的报告结论精简成 README 的 “Results summary”。&lt;/li&gt;
&lt;li&gt;新建一个空目录，重新 clone 或复制项目，只按 README 运行，检查是否能复现。&lt;/li&gt;
&lt;li&gt;请同学只看 README，不口头解释，让对方尝试运行；记录对方卡在哪里，再改 README。&lt;/li&gt;
&lt;/ol&gt;
&lt;h2&gt;17. 验收检查&lt;/h2&gt;
&lt;p&gt;在项目根目录执行：&lt;/p&gt;
&lt;div&gt;&lt;div&gt;&lt;span&gt;omp&lt;/span&gt;&lt;span&gt;fish&lt;/span&gt;&lt;/div&gt;&lt;pre&gt;&lt;code&gt;source .venv/bin/activate.fish
uv sync
quant-backtest-mini --data data/prices.csv --lookback 20 --cost-bps 5
python -c &quot;from pathlib import Path; required = [&apos;README.md&apos;, &apos;pyproject.toml&apos;, &apos;uv.lock&apos;, &apos;.gitignore&apos;, &apos;src/quant_backtest_mini/cli.py&apos;, &apos;src/quant_backtest_mini/__main__.py&apos;]; missing = [p for p in required if not Path(p).exists()]; print(&apos;missing:&apos;, missing); raise SystemExit(1 if missing else 0)&quot;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;通过标准：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;README 写清楚 setup、run、outputs、limitations。&lt;/li&gt;
&lt;li&gt;&lt;code&gt;uv sync&lt;/code&gt; 后能重新创建环境。&lt;/li&gt;
&lt;li&gt;fish 激活命令写成 &lt;code&gt;source .venv/bin/activate.fish&lt;/code&gt;。&lt;/li&gt;
&lt;li&gt;CLI 可以一条命令运行主流程。&lt;/li&gt;
&lt;li&gt;结果文件生成位置清楚。&lt;/li&gt;
&lt;li&gt;&lt;code&gt;.venv/&lt;/code&gt;、缓存和日志不会被提交。&lt;/li&gt;
&lt;li&gt;随机种子、默认参数和交易假设写清楚。&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;18. 常见错误&lt;/h2&gt;
&lt;h3&gt;错误 1：README 只有一句话&lt;/h3&gt;
&lt;p&gt;README 不是口号。至少要有项目说明、环境安装、运行命令、输出、限制。&lt;/p&gt;
&lt;h3&gt;错误 2：依赖只存在于本机环境&lt;/h3&gt;
&lt;p&gt;如果你手动 &lt;code&gt;pip install&lt;/code&gt; 过，但没有写进 &lt;code&gt;pyproject.toml&lt;/code&gt;，别人无法复现。用 &lt;code&gt;uv add&lt;/code&gt; 记录依赖。&lt;/p&gt;
&lt;h3&gt;错误 3：CLI 只能在某个目录碰巧运行&lt;/h3&gt;
&lt;p&gt;使用包导入，不要依赖当前工作目录下的相对导入。推荐 &lt;code&gt;python -m quant_backtest_mini&lt;/code&gt; 或项目脚本入口。&lt;/p&gt;
&lt;h3&gt;错误 4：提交 &lt;code&gt;.venv&lt;/code&gt;&lt;/h3&gt;
&lt;p&gt;&lt;code&gt;.venv&lt;/code&gt; 不应提交。别人应该用 &lt;code&gt;uv sync&lt;/code&gt; 创建自己的环境。&lt;/p&gt;
&lt;h3&gt;错误 5：结果不可复现&lt;/h3&gt;
&lt;p&gt;如果数据生成和模型训练有随机性，必须固定随机种子，并在 README 写明。&lt;/p&gt;
&lt;h3&gt;错误 6：工程化时顺手改策略结论&lt;/h3&gt;
&lt;p&gt;Week 17 的重点是整理项目，不是继续调参优化策略。不要为了 README 好看而更改 Week 16 的研究结论。&lt;/p&gt;
&lt;h2&gt;19. 下一步&lt;/h2&gt;
&lt;p&gt;进入 Week 18：把项目经历整理成简历和面试叙述。你现在应该能把这个量化小项目概括成一句专业描述：实现时间序列回测框架，包含 rolling validation、交易成本、最大回撤、Sharpe、换手率等指标，并分析样本外失效风险。&lt;/p&gt;
&lt;p&gt;plain &lt;/p&gt;&lt;/article&gt;&lt;p&gt;&lt;/p&gt;
  &lt;/div&gt;
&lt;/section&gt;</content:encoded><category>category:工具</category><category>category:项目工程</category><category>tag:CLI</category><category>tag:reproducibility</category><category>tag:project-engineering</category><category>tag:config</category></item><item><title>Week 16：量化项目报告、回撤分析与样本外解释</title><link>https://39miku.space/post/week16-quant-project-report</link><guid isPermaLink="false">week16-quant-project-report</guid><description>Week 16：量化项目报告、回撤分析与样本外解释。fish-first 终端教学，面向 CachyOS、VS Code、uv 和 Python 学习路线。</description><pubDate>Sun, 26 Jul 2026 08:00:00 GMT</pubDate><content:encoded>
&lt;section&gt;
  &lt;div&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;strong&gt;Oh My Pi&lt;/strong&gt; / weekly tutorial / week16-quant-project-report&lt;/span&gt;&lt;/div&gt;
  &lt;div&gt;
    &lt;div&gt;&lt;span&gt;miku@cachyos&lt;/span&gt;&lt;span&gt;:~/Code/python-learning&lt;/span&gt;&lt;span&gt;$&lt;/span&gt; &lt;span&gt;omp teach week16-quant-project-report --fish-first --step-by-step&lt;/span&gt;&lt;/div&gt;
    &lt;div&gt;
      &lt;div&gt;&lt;span&gt;source&lt;/span&gt;&lt;strong&gt;487 行教学文档&lt;/strong&gt;&lt;/div&gt;
      &lt;div&gt;&lt;span&gt;week&lt;/span&gt;&lt;strong&gt;Week 16&lt;/strong&gt;&lt;/div&gt;
      &lt;div&gt;&lt;span&gt;shell&lt;/span&gt;&lt;strong&gt;fish-first 命令版&lt;/strong&gt;&lt;/div&gt;
      &lt;div&gt;&lt;span&gt;backlink&lt;/span&gt;&lt;strong&gt;&lt;a href=&quot;/post/ustc-stat-ai-quant-plan/&quot;&gt;20 周计划&lt;/a&gt;&lt;/strong&gt;&lt;/div&gt;
    &lt;/div&gt;
    &lt;article&gt;
&lt;h1&gt;Week 16：量化项目报告、回撤分析与样本外解释&lt;/h1&gt;
&lt;blockquote&gt;
&lt;p&gt;返回总计划：&lt;a href=&quot;/post/ustc-stat-ai-quant-plan/&quot;&gt;USTC 统计 + AI / 量化 20 周成长计划&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Week 16 的目标是把 Week 15 的 mini backtest 写成一份诚实、可复查的量化项目报告。报告不是宣传策略有多赚钱，而是回答：策略规则是什么、数据怎么来、样本怎么切、成本怎么算、风险有多大、样本外是否仍有效、局限性在哪里。&lt;/p&gt;
&lt;p&gt;本文命令默认使用 &lt;strong&gt;fish shell&lt;/strong&gt;。进入项目后激活环境：&lt;/p&gt;
&lt;div&gt;&lt;div&gt;&lt;span&gt;omp&lt;/span&gt;&lt;span&gt;fish&lt;/span&gt;&lt;/div&gt;&lt;pre&gt;&lt;code&gt;source .venv/bin/activate.fish
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;&lt;a&gt;&lt;/a&gt;&lt;/p&gt;
&lt;h2&gt;0. 本周详细教学：语法、规范、验收&lt;/h2&gt;
&lt;blockquote&gt;
&lt;p&gt;本节不是追加在尾部的复习，而是本周正文的入口。先读这里，再做后面的命令和项目。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h3&gt;0.1 本周真正要学会什么&lt;/h3&gt;
&lt;table&gt;
&lt;thead&gt;&lt;tr&gt;&lt;th&gt;维度&lt;/th&gt;&lt;th&gt;要求&lt;/th&gt;&lt;/tr&gt;&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;&lt;td&gt;知识点&lt;/td&gt;&lt;td&gt;annual return、vol、Sharpe、max drawdown、成本敏感性&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;代码语法&lt;/td&gt;&lt;td&gt;能从空文件写出本周核心脚本，而不是只复制运行&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;程序规范&lt;/td&gt;&lt;td&gt;函数拆分、路径清楚、输入输出明确、错误能解释&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;交付物&lt;/td&gt;&lt;td&gt;&lt;code&gt;reports/week16_quant_report.md&lt;/code&gt;&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;验收方式&lt;/td&gt;&lt;td&gt;从 fish 终端运行命令，得到可复查的文件或指标&lt;/td&gt;&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;h3&gt;0.2 代码语法精讲&lt;/h3&gt;
&lt;p&gt;下面的代码不是最终答案，而是本周必须理解的最小骨架：&lt;/p&gt;
&lt;div&gt;&lt;div&gt;&lt;span&gt;omp&lt;/span&gt;&lt;span&gt;python&lt;/span&gt;&lt;/div&gt;&lt;pre&gt;&lt;code&gt;summary = {
    &quot;annual_return&quot;: annual_return(net_ret),
    &quot;annual_volatility&quot;: annual_volatility(net_ret),
    &quot;sharpe&quot;: sharpe_ratio(net_ret),
    &quot;max_drawdown&quot;: max_drawdown(net_ret),
}
print(summary)
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;读代码时按四步检查：输入从哪里来；中间变量的类型和 shape 是什么；函数或脚本输出什么；哪些错误应该显式报出来。&lt;/p&gt;
&lt;h3&gt;0.3 本周程序规范&lt;/h3&gt;
&lt;ul&gt;&lt;li&gt;所有路径用相对路径或 `pathlib.Path`，不要写死 `/home/miku/...`。&lt;/li&gt;&lt;li&gt;核心逻辑进 `src/`，notebook 只做探索和解释。&lt;/li&gt;&lt;li&gt;每个脚本能从 fish 终端运行，并在 README 写出命令。&lt;/li&gt;&lt;li&gt;输出必须落盘到 `reports/`、`figures/` 或 `outputs/`，不能只在屏幕上看。&lt;/li&gt;&lt;/ul&gt;
&lt;h3&gt;0.4 本周练习分层&lt;/h3&gt;
&lt;table&gt;
&lt;thead&gt;&lt;tr&gt;&lt;th&gt;层级&lt;/th&gt;&lt;th&gt;任务&lt;/th&gt;&lt;th&gt;不合格表现&lt;/th&gt;&lt;th&gt;合格验收&lt;/th&gt;&lt;/tr&gt;&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;&lt;td&gt;最小练习&lt;/td&gt;&lt;td&gt;手写上面的最小骨架&lt;/td&gt;&lt;td&gt;只在 notebook 里运行&lt;/td&gt;&lt;td&gt;终端运行成功&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;标准练习&lt;/td&gt;&lt;td&gt;把逻辑拆成函数/模块&lt;/td&gt;&lt;td&gt;一个大脚本从头写到尾&lt;/td&gt;&lt;td&gt;至少 2 个函数，职责清楚&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;项目练习&lt;/td&gt;&lt;td&gt;生成本周交付物 &lt;code&gt;reports/week16_quant_report.md&lt;/code&gt;&lt;/td&gt;&lt;td&gt;只有屏幕输出&lt;/td&gt;&lt;td&gt;文件落盘，可复查&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;复盘练习&lt;/td&gt;&lt;td&gt;写 3 个错误和修复&lt;/td&gt;&lt;td&gt;只写“已解决”&lt;/td&gt;&lt;td&gt;写清报错、原因、修复、预防&lt;/td&gt;&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;h3&gt;0.5 本周和主线的连接&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;回到总计划：&lt;a href=&quot;/post/ustc-stat-ai-quant-plan/#week-plan&quot;&gt;USTC AI / Quant 练习手册&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;查详细练习索引：&lt;a href=&quot;/post/ustc-stat-ai-quant-plan/#deep-practice-appendix&quot;&gt;技术练习详解&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;查质量评分：&lt;a href=&quot;/post/ustc-stat-ai-quant-plan/#quality-final-gates&quot;&gt;最终质量门槛&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;1. 本周学习目标&lt;/h2&gt;
&lt;p&gt;完成本周后，你应该能产出一份 &lt;code&gt;reports/week16_quant_project_report.md&lt;/code&gt;，包含：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;策略规则和交易假设。&lt;/li&gt;
&lt;li&gt;数据来源、时间范围、频率、缺失值处理。&lt;/li&gt;
&lt;li&gt;样本内 / 样本外切分。&lt;/li&gt;
&lt;li&gt;annual return、annual volatility、Sharpe、max drawdown、turnover、交易成本敏感性。&lt;/li&gt;
&lt;li&gt;净值曲线、回撤曲线、月度收益或分段收益。&lt;/li&gt;
&lt;li&gt;对“是否样本外有效”的谨慎判断。&lt;/li&gt;
&lt;li&gt;对过拟合、交易成本、滑点、流动性、实盘约束的限制说明。&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;建议文件布局：&lt;/p&gt;
&lt;div&gt;&lt;div&gt;&lt;span&gt;omp&lt;/span&gt;&lt;span&gt;prompt&lt;/span&gt;&lt;/div&gt;&lt;pre&gt;&lt;code&gt;quant-backtest-mini/
├── data/
│   └── prices.csv
├── figures/
│   ├── equity_curve.png
│   ├── drawdown.png
│   ├── split_equity.png
│   └── cost_sensitivity.png
├── reports/
│   ├── week15_backtest_daily_results.csv
│   ├── week15_backtest_notes.md
│   ├── week16_metrics_by_split.csv
│   ├── week16_cost_sensitivity.csv
│   └── week16_quant_project_report.md
├── src/
│   └── quant_backtest_mini/
│       ├── __init__.py
│       ├── backtest.py
│       ├── metrics.py
│       └── report.py
├── README.md
└── pyproject.toml
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;如果 Week 15 项目还不完整，先回去补齐回测脚本，再开始写报告。&lt;/p&gt;
&lt;h2&gt;2. 前置条件&lt;/h2&gt;
&lt;p&gt;你需要已经完成：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Week 15 的 &lt;code&gt;backtest.py&lt;/code&gt; 和 &lt;code&gt;metrics.py&lt;/code&gt;。&lt;/li&gt;
&lt;li&gt;至少能生成策略日收益、benchmark 日收益、turnover。&lt;/li&gt;
&lt;li&gt;至少能画净值曲线和回撤曲线。&lt;/li&gt;
&lt;li&gt;理解 &lt;code&gt;position.shift(1)&lt;/code&gt; 为什么必要。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;进入 Week 15 项目继续工作：&lt;/p&gt;
&lt;div&gt;&lt;div&gt;&lt;span&gt;omp&lt;/span&gt;&lt;span&gt;fish&lt;/span&gt;&lt;/div&gt;&lt;pre&gt;&lt;code&gt;cd ~/Code/ustc-stat-ai-quant/quant-backtest-mini
source .venv/bin/activate.fish
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;如果你重新打开了终端，必须重新激活虚拟环境。检查依赖：&lt;/p&gt;
&lt;div&gt;&lt;div&gt;&lt;span&gt;omp&lt;/span&gt;&lt;span&gt;fish&lt;/span&gt;&lt;/div&gt;&lt;pre&gt;&lt;code&gt;python -c &quot;import pandas as pd; import numpy as np; import matplotlib; print(&apos;report env ok&apos;)&quot;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;h2&gt;3. 报告不是 README&lt;/h2&gt;
&lt;p&gt;README 负责告诉别人如何运行项目；量化报告负责解释研究结果。&lt;/p&gt;
&lt;p&gt;区别：&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;文件&lt;/th&gt;
&lt;th&gt;读者关心什么&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;&lt;tr&gt;
&lt;td&gt;&lt;code&gt;README.md&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;怎么安装、怎么运行、项目结构是什么&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;reports/week16_quant_project_report.md&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;策略是否可靠、结果是否可解释、风险在哪里&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;&lt;/table&gt;
&lt;p&gt;Week 16 先写报告；Week 17 再把整个项目工程化。&lt;/p&gt;
&lt;h2&gt;4. 创建报告分析脚本&lt;/h2&gt;
&lt;p&gt;创建 &lt;code&gt;src/quant_backtest_mini/report.py&lt;/code&gt;，用于生成分段指标和成本敏感性表。&lt;/p&gt;
&lt;div&gt;&lt;div&gt;&lt;span&gt;omp&lt;/span&gt;&lt;span&gt;python&lt;/span&gt;&lt;/div&gt;&lt;pre&gt;&lt;code&gt;from pathlib import Path
&lt;p&gt;import matplotlib.pyplot as plt
import pandas as pd&lt;/p&gt;
&lt;p&gt;from quant_backtest_mini.backtest import load_prices, run_backtest
from quant_backtest_mini.metrics import drawdown, equity_curve, summarize&lt;/p&gt;
&lt;p&gt;FIGURE_DIR = Path(“figures”)
REPORT_DIR = Path(“reports”)
FIGURE_DIR.mkdir(exist_ok=True)
REPORT_DIR.mkdir(exist_ok=True)&lt;/p&gt;
&lt;p&gt;def split_periods(result: pd.DataFrame) -&amp;gt; dict[str, pd.DataFrame]:
n = len(result)
train_end = int(n * 0.6)
test_start = int(n * 0.8)
return {
“in_sample”: result.iloc[&lt;/p&gt;&lt;div&gt;&lt;/div&gt;],
“validation”: result.iloc[train_end&lt;div&gt;&lt;/div&gt;],
“out_of_sample”: result.iloc[test_start:],
“full_period”: result,
}&lt;p&gt;&lt;/p&gt;
&lt;p&gt;def metrics_by_split(result: pd.DataFrame) -&amp;gt; pd.DataFrame:
rows = []
for name, part in split_periods(result).items():
strategy = summarize(part[“net_strategy_return”], part[“turnover”])
benchmark = summarize(part[“benchmark_return”], pd.Series(0, index=part.index))
rows.append({“period”: name, “portfolio”: “strategy”, **strategy})
rows.append({“period”: name, “portfolio”: “benchmark”, **benchmark})
return pd.DataFrame(rows)&lt;/p&gt;
&lt;p&gt;def cost_sensitivity(prices: pd.DataFrame, lookback: int) -&amp;gt; pd.DataFrame:
rows = []
for cost_bps in [0, 5, 10, 20, 50]:
result = run_backtest(prices, lookback=lookback, cost_bps=cost_bps)
summary = summarize(result[“net_strategy_return”], result[“turnover”])
rows.append({“cost_bps”: cost_bps, **summary})
return pd.DataFrame(rows)&lt;/p&gt;
&lt;p&gt;def plot_split_equity(result: pd.DataFrame) -&amp;gt; None:
fig, ax = plt.subplots(figsize=(10, 5))
for name, part in split_periods(result).items():
if name == “full_period”:
continue
equity_curve(part[“net_strategy_return”]).plot(ax=ax, label=name)
ax.set_title(“Strategy Equity by Split”)
ax.set_ylabel(“growth of 1 within split”)
ax.legend()
fig.tight_layout()
fig.savefig(FIGURE_DIR / “split_equity.png”, dpi=150)
plt.close(fig)&lt;/p&gt;
&lt;p&gt;def plot_cost_sensitivity(table: pd.DataFrame) -&amp;gt; None:
fig, axes = plt.subplots(2, 1, figsize=(10, 7), sharex=True)
axes[0].plot(table[“cost_bps”], table[“annual_return”], marker=“o”)
axes[0].set_title(“Annual Return vs Transaction Cost”)
axes[0].set_ylabel(“annual return”)&lt;/p&gt;
&lt;p&gt;plain axes[1].plot(table[“cost_bps”], table[“sharpe_ratio”], marker=“o”)
axes[1].set_title(“Sharpe vs Transaction Cost”)
axes[1].set_xlabel(“cost bps”)
axes[1].set_ylabel(“sharpe”)&lt;/p&gt;
&lt;p&gt;plain fig.tight_layout()
fig.savefig(FIGURE_DIR / “cost_sensitivity.png”, dpi=150)
plt.close(fig)&lt;/p&gt;
&lt;p&gt;def main() -&amp;gt; None:
prices = load_prices()
result = run_backtest(prices, lookback=20, cost_bps=5)&lt;/p&gt;
&lt;p&gt;plain split_metrics = metrics_by_split(result)
split_metrics.to_csv(REPORT_DIR / “week16_metrics_by_split.csv”, index=False)
print(split_metrics)&lt;/p&gt;
&lt;p&gt;plain cost_table = cost_sensitivity(prices, lookback=20)
cost_table.to_csv(REPORT_DIR / “week16_cost_sensitivity.csv”, index=False)
print(cost_table)&lt;/p&gt;
&lt;p&gt;plain plot_split_equity(result)
plot_cost_sensitivity(cost_table)
print(“saved Week 16 report tables and figures”)&lt;/p&gt;
&lt;/code&gt;&lt;p&gt;&lt;code&gt;if &lt;strong&gt;name&lt;/strong&gt; == “&lt;strong&gt;main&lt;/strong&gt;”:
main()
&lt;/code&gt;&lt;/p&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;&lt;/p&gt;
&lt;p&gt;运行：&lt;/p&gt;
&lt;div&gt;&lt;div&gt;&lt;span&gt;omp&lt;/span&gt;&lt;span&gt;fish&lt;/span&gt;&lt;/div&gt;&lt;pre&gt;&lt;code&gt;python -m quant_backtest_mini.report
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;如果模块运行失败，可以临时使用：&lt;/p&gt;
&lt;div&gt;&lt;div&gt;&lt;span&gt;omp&lt;/span&gt;&lt;span&gt;fish&lt;/span&gt;&lt;/div&gt;&lt;pre&gt;&lt;code&gt;PYTHONPATH=src python src/quant_backtest_mini/report.py
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;但最终报告中建议保留模块运行方式，便于 Week 17 工程化。&lt;/p&gt;
&lt;h2&gt;5. 指标表怎么写进报告&lt;/h2&gt;
&lt;p&gt;打开生成的指标表：&lt;/p&gt;
&lt;div&gt;&lt;div&gt;&lt;span&gt;omp&lt;/span&gt;&lt;span&gt;fish&lt;/span&gt;&lt;/div&gt;&lt;pre&gt;&lt;code&gt;python -c &quot;import pandas as pd; print(pd.read_csv(&apos;reports/week16_metrics_by_split.csv&apos;).round(4))&quot;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;报告中不要只复制一堆数字，要解释数字背后的含义。&lt;/p&gt;
&lt;p&gt;示例解释方式：&lt;/p&gt;
&lt;div&gt;&lt;div&gt;&lt;span&gt;omp&lt;/span&gt;&lt;span&gt;prompt&lt;/span&gt;&lt;/div&gt;&lt;pre&gt;&lt;code&gt;样本内策略 Sharpe 高于 benchmark，但样本外 Sharpe 明显下降，说明策略可能依赖前期市场结构。样本外最大回撤接近 benchmark，没有明显风险优势。因此不能认为该动量规则已经稳定有效。
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;如果策略表现很差，也不要美化。量化报告里诚实说明失败原因，比硬说有效更专业。&lt;/p&gt;
&lt;h2&gt;6. 最大回撤分析&lt;/h2&gt;
&lt;p&gt;最大回撤不是一个抽象数字，它代表真实持有体验。&lt;/p&gt;
&lt;p&gt;要回答：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;最大回撤发生在什么时间段？&lt;/li&gt;
&lt;li&gt;回撤持续多久？&lt;/li&gt;
&lt;li&gt;当时 benchmark 是否也在回撤？&lt;/li&gt;
&lt;li&gt;策略是否比 benchmark 更早恢复？&lt;/li&gt;
&lt;li&gt;如果是自己真实资金，是否能承受？&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;可以加入辅助代码：&lt;/p&gt;
&lt;div&gt;&lt;div&gt;&lt;span&gt;omp&lt;/span&gt;&lt;span&gt;python&lt;/span&gt;&lt;/div&gt;&lt;pre&gt;&lt;code&gt;def drawdown_details(daily_returns: pd.Series) -&amp;gt; dict[str, object]:
    equity = equity_curve(daily_returns)
    dd = drawdown(equity)
    trough = dd.idxmin()
    peak = equity.loc[:trough].idxmax()
    recovery_candidates = equity.loc[trough:][equity.loc[trough:] &amp;gt;= equity.loc[peak]]
    recovery = recovery_candidates.index[0] if not recovery_candidates.empty else None
    return {
        &quot;peak&quot;: peak,
        &quot;trough&quot;: trough,
        &quot;recovery&quot;: recovery,
        &quot;max_drawdown&quot;: float(dd.loc[trough]),
    }
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;在报告中写成自然语言：&lt;/p&gt;
&lt;div&gt;&lt;div&gt;&lt;span&gt;omp&lt;/span&gt;&lt;span&gt;prompt&lt;/span&gt;&lt;/div&gt;&lt;pre&gt;&lt;code&gt;策略最大回撤从 YYYY-MM-DD 的净值高点开始，到 YYYY-MM-DD 达到最低点。样本结束前是否恢复：是/否。这个回撤说明策略在连续震荡或趋势反转时承压明显。
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;h2&gt;7. 样本切分：不要只看全样本&lt;/h2&gt;
&lt;p&gt;报告必须包含三段：&lt;/p&gt;
&lt;div&gt;&lt;div&gt;&lt;span&gt;omp&lt;/span&gt;&lt;span&gt;prompt&lt;/span&gt;&lt;/div&gt;&lt;pre&gt;&lt;code&gt;in_sample：用于观察和开发策略
validation：用于比较参数或模型
out_of_sample：最终检验，不反复调参
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;写报告时避免这种说法：&lt;/p&gt;
&lt;div&gt;&lt;div&gt;&lt;span&gt;omp&lt;/span&gt;&lt;span&gt;prompt&lt;/span&gt;&lt;/div&gt;&lt;pre&gt;&lt;code&gt;全样本收益很好，所以策略有效。
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;更专业的说法：&lt;/p&gt;
&lt;div&gt;&lt;div&gt;&lt;span&gt;omp&lt;/span&gt;&lt;span&gt;prompt&lt;/span&gt;&lt;/div&gt;&lt;pre&gt;&lt;code&gt;全样本表现不能单独证明策略有效。样本外表现更接近未来未知数据，因此本文主要依据 out_of_sample 指标判断策略稳定性。
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;如果样本外变差，可以写：&lt;/p&gt;
&lt;div&gt;&lt;div&gt;&lt;span&gt;omp&lt;/span&gt;&lt;span&gt;prompt&lt;/span&gt;&lt;/div&gt;&lt;pre&gt;&lt;code&gt;样本外年化收益和 Sharpe 均下降，说明策略可能对样本内走势过拟合，或该动量规则在后期市场状态中不适用。
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;h2&gt;8. 成本敏感性&lt;/h2&gt;
&lt;p&gt;查看成本敏感性表：&lt;/p&gt;
&lt;div&gt;&lt;div&gt;&lt;span&gt;omp&lt;/span&gt;&lt;span&gt;fish&lt;/span&gt;&lt;/div&gt;&lt;pre&gt;&lt;code&gt;python -c &quot;import pandas as pd; print(pd.read_csv(&apos;reports/week16_cost_sensitivity.csv&apos;).round(4))&quot;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;打开图：&lt;/p&gt;
&lt;div&gt;&lt;div&gt;&lt;span&gt;omp&lt;/span&gt;&lt;span&gt;fish&lt;/span&gt;&lt;/div&gt;&lt;pre&gt;&lt;code&gt;xdg-open figures/cost_sensitivity.png
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;报告必须回答：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;0 bps 时是否有效？&lt;/li&gt;
&lt;li&gt;5 bps 时是否仍有效？&lt;/li&gt;
&lt;li&gt;20 bps 时是否完全失效？&lt;/li&gt;
&lt;li&gt;策略换手率是否太高？&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;一个常见结论：&lt;/p&gt;
&lt;div&gt;&lt;div&gt;&lt;span&gt;omp&lt;/span&gt;&lt;span&gt;prompt&lt;/span&gt;&lt;/div&gt;&lt;pre&gt;&lt;code&gt;策略在无成本条件下略优于 benchmark，但随着成本从 0 bps 增加到 20 bps，年化收益和 Sharpe 快速下降，说明该策略对交易成本敏感，不适合高频换仓或高摩擦市场。
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;h2&gt;9. 报告正文模板&lt;/h2&gt;
&lt;p&gt;创建报告：&lt;/p&gt;
&lt;div&gt;&lt;div&gt;&lt;span&gt;omp&lt;/span&gt;&lt;span&gt;fish&lt;/span&gt;&lt;/div&gt;&lt;pre&gt;&lt;code&gt;code reports/week16_quant_project_report.md
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;推荐结构：&lt;/p&gt;
&lt;div&gt;&lt;div&gt;&lt;span&gt;omp&lt;/span&gt;&lt;span&gt;prompt&lt;/span&gt;&lt;/div&gt;&lt;pre&gt;&lt;code&gt;# Mini Quant Backtest Report
&lt;h2&gt;1. Research question&lt;a href=&quot;#1-research-question&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;本文研究一个简单 N 日动量规则是否能在样本外获得稳定风险调整收益。&lt;/p&gt;
&lt;h2&gt;2. Data&lt;a href=&quot;#2-data&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;标的：&lt;/li&gt;
&lt;li&gt;数据来源：&lt;/li&gt;
&lt;li&gt;日期范围：&lt;/li&gt;
&lt;li&gt;频率：日频&lt;/li&gt;
&lt;li&gt;缺失值处理：&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;3. Strategy rule&lt;a href=&quot;#3-strategy-rule&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;过去 N 日收益为正：持有&lt;/li&gt;
&lt;li&gt;否则：空仓&lt;/li&gt;
&lt;li&gt;t 日收盘生成信号，t+1 日持仓&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;4. Backtest assumptions&lt;a href=&quot;#4-backtest-assumptions&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;初始资金：1&lt;/li&gt;
&lt;li&gt;交易成本：5 bps&lt;/li&gt;
&lt;li&gt;不考虑滑点&lt;/li&gt;
&lt;li&gt;不考虑停牌、涨跌停、成交量限制&lt;/li&gt;
&lt;li&gt;benchmark：buy and hold&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;5. Sample split&lt;a href=&quot;#5-sample-split&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;in_sample：&lt;/li&gt;
&lt;li&gt;validation：&lt;/li&gt;
&lt;li&gt;out_of_sample：&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;6. Metrics&lt;a href=&quot;#6-metrics&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;插入 metrics_by_split 表格，并解释 annual return、volatility、Sharpe、max drawdown、turnover。&lt;/p&gt;
&lt;h2&gt;7. Equity and drawdown&lt;a href=&quot;#7-equity-and-drawdown&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;引用 equity_curve、drawdown、split_equity 图，并解释最大回撤时间段。&lt;/p&gt;
&lt;h2&gt;8. Cost sensitivity&lt;a href=&quot;#8-cost-sensitivity&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;引用 cost_sensitivity 表格和图片。&lt;/p&gt;
&lt;h2&gt;9. Findings&lt;a href=&quot;#9-findings&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;样本内是否有效？&lt;/li&gt;
&lt;li&gt;样本外是否有效？&lt;/li&gt;
&lt;li&gt;是否优于 benchmark？&lt;/li&gt;
&lt;li&gt;是否对成本敏感？&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;10. Limitations&lt;a href=&quot;#10-limitations&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;数据样本有限&lt;/li&gt;
&lt;li&gt;策略过于简单&lt;/li&gt;
&lt;li&gt;未考虑真实交易滑点和流动性&lt;/li&gt;
&lt;li&gt;未考虑参数挖掘惩罚&lt;/li&gt;
&lt;li&gt;未进行多标的验证&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;11. Conclusion&lt;a href=&quot;#11-conclusion&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;
&lt;/code&gt;&lt;p&gt;&lt;code&gt;用谨慎语言判断：可以继续研究、需要改进，或暂不认为有效。
&lt;/code&gt;&lt;/p&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;&lt;/p&gt;
&lt;h2&gt;10. 如何写限制，而不是找借口&lt;/h2&gt;
&lt;p&gt;限制不是失败借口，而是研究边界。&lt;/p&gt;
&lt;p&gt;好的限制写法：&lt;/p&gt;
&lt;div&gt;&lt;div&gt;&lt;span&gt;omp&lt;/span&gt;&lt;span&gt;prompt&lt;/span&gt;&lt;/div&gt;&lt;pre&gt;&lt;code&gt;本回测只使用单一标的日频数据，无法证明策略在其它市场或更长周期中有效。交易成本仅用固定 bps 近似，没有考虑滑点、成交量、涨跌停和资金容量，因此实盘表现可能显著低于回测。
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;不好的写法：&lt;/p&gt;
&lt;div&gt;&lt;div&gt;&lt;span&gt;omp&lt;/span&gt;&lt;span&gt;prompt&lt;/span&gt;&lt;/div&gt;&lt;pre&gt;&lt;code&gt;如果以后优化一下，策略肯定会更好。
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;量化研究中，未来改进只能说“可能”，不能说“肯定”。&lt;/p&gt;
&lt;h2&gt;11. 图表引用建议&lt;/h2&gt;
&lt;p&gt;在 Markdown 报告中可以用相对路径引用图：&lt;/p&gt;
&lt;div&gt;&lt;div&gt;&lt;span&gt;omp&lt;/span&gt;&lt;span&gt;prompt&lt;/span&gt;&lt;/div&gt;&lt;pre&gt;&lt;code&gt;![Equity Curve](../figures/equity_curve.png)
![Drawdown](../figures/drawdown.png)
![Cost Sensitivity](../figures/cost_sensitivity.png)
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;如果报告渲染工具从项目根目录读取，也可以改成：&lt;/p&gt;
&lt;div&gt;&lt;div&gt;&lt;span&gt;omp&lt;/span&gt;&lt;span&gt;prompt&lt;/span&gt;&lt;/div&gt;&lt;pre&gt;&lt;code&gt;![Equity Curve](figures/equity_curve.png)
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;关键是你自己打开预览检查图片能显示。&lt;/p&gt;
&lt;h2&gt;12. 文件布局检查&lt;/h2&gt;
&lt;p&gt;执行：&lt;/p&gt;
&lt;div&gt;&lt;div&gt;&lt;span&gt;omp&lt;/span&gt;&lt;span&gt;fish&lt;/span&gt;&lt;/div&gt;&lt;pre&gt;&lt;code&gt;pwd
ls reports figures src/quant_backtest_mini
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;至少应有：&lt;/p&gt;
&lt;div&gt;&lt;div&gt;&lt;span&gt;omp&lt;/span&gt;&lt;span&gt;prompt&lt;/span&gt;&lt;/div&gt;&lt;pre&gt;&lt;code&gt;reports/week16_metrics_by_split.csv
reports/week16_cost_sensitivity.csv
reports/week16_quant_project_report.md
figures/split_equity.png
figures/cost_sensitivity.png
src/quant_backtest_mini/report.py
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;h2&gt;13. 练习&lt;/h2&gt;
&lt;ol&gt;
&lt;li&gt;在报告中加入 benchmark 的最大回撤时间段，和策略进行比较。&lt;/li&gt;
&lt;li&gt;把 &lt;code&gt;lookback=20&lt;/code&gt; 改成 &lt;code&gt;lookback=60&lt;/code&gt;，重新生成表格，但不要只因为结果更好就替换主结论。&lt;/li&gt;
&lt;li&gt;增加一张月度收益表，检查收益是否集中在少数月份。&lt;/li&gt;
&lt;li&gt;在成本敏感性中加入 100 bps，观察策略是否完全失效。&lt;/li&gt;
&lt;li&gt;写一段“如果实盘会遇到什么问题”，至少包含滑点、流动性、心理承受、参数失效。&lt;/li&gt;
&lt;li&gt;用三句话向非量化同学解释最大回撤。&lt;/li&gt;
&lt;/ol&gt;
&lt;h2&gt;14. 验收检查&lt;/h2&gt;
&lt;p&gt;在项目根目录执行：&lt;/p&gt;
&lt;div&gt;&lt;div&gt;&lt;span&gt;omp&lt;/span&gt;&lt;span&gt;fish&lt;/span&gt;&lt;/div&gt;&lt;pre&gt;&lt;code&gt;source .venv/bin/activate.fish
python -m quant_backtest_mini.report
python -c &quot;from pathlib import Path; required = [&apos;reports/week16_metrics_by_split.csv&apos;, &apos;reports/week16_cost_sensitivity.csv&apos;, &apos;reports/week16_quant_project_report.md&apos;, &apos;figures/split_equity.png&apos;, &apos;figures/cost_sensitivity.png&apos;]; missing = [p for p in required if not Path(p).exists()]; print(&apos;missing:&apos;, missing); raise SystemExit(1 if missing else 0)&quot;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;通过标准：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;报告明确写出策略规则和执行假设。&lt;/li&gt;
&lt;li&gt;报告包含样本内、验证集、样本外指标。&lt;/li&gt;
&lt;li&gt;报告包含最大回撤解释，而不只是一个数字。&lt;/li&gt;
&lt;li&gt;报告包含交易成本敏感性。&lt;/li&gt;
&lt;li&gt;报告明确写出局限性和是否可能过拟合。&lt;/li&gt;
&lt;li&gt;结论使用谨慎语言，不夸大收益。&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;15. 常见错误&lt;/h2&gt;
&lt;h3&gt;错误 1：报告像广告&lt;/h3&gt;
&lt;p&gt;不要写“策略收益优秀，未来可期”。要写证据、风险、限制。&lt;/p&gt;
&lt;h3&gt;错误 2：只报告全样本&lt;/h3&gt;
&lt;p&gt;全样本结果容易掩盖样本外失效。必须拆分。&lt;/p&gt;
&lt;h3&gt;错误 3：最大回撤只写数字&lt;/h3&gt;
&lt;p&gt;最大回撤应解释发生时间、持续时间、是否恢复，以及投资者是否可能承受。&lt;/p&gt;
&lt;h3&gt;错误 4：忽略交易成本&lt;/h3&gt;
&lt;p&gt;量化策略如果没有成本敏感性分析，报告不完整。&lt;/p&gt;
&lt;h3&gt;错误 5：结论超过证据&lt;/h3&gt;
&lt;p&gt;如果只测了一个模拟数据或一个标的，就不能说“策略有效”。最多说“在当前样本和假设下表现如何”。&lt;/p&gt;
&lt;h2&gt;16. 下一步&lt;/h2&gt;
&lt;p&gt;进入 Week 17：&lt;a href=&quot;./week17-project-engineering.md&quot;&gt;项目工程化、CLI 入口、uv 依赖与可复现运行&lt;/a&gt;。下一周会把 Week 15-16 的回测项目整理成别人可以 clone、安装、运行、复查的干净仓库。&lt;/p&gt;
&lt;p&gt;plain &lt;/p&gt;&lt;/article&gt;&lt;p&gt;&lt;/p&gt;
  &lt;/div&gt;
&lt;/section&gt;</content:encoded><category>category:工具</category><category>category:量化研究</category><category>tag:Quant</category><category>tag:drawdown</category><category>tag:Sharpe</category><category>tag:out-of-sample</category><category>tag:report</category></item><item><title>Week 15：Mini 量化回测、动量规则与交易成本</title><link>https://39miku.space/post/week15-quant-backtest-mini</link><guid isPermaLink="false">week15-quant-backtest-mini</guid><description>Week 15：Mini 量化回测、动量规则与交易成本。fish-first 终端教学，面向 CachyOS、VS Code、uv 和 Python 学习路线。</description><pubDate>Sun, 26 Jul 2026 07:00:00 GMT</pubDate><content:encoded>
&lt;section&gt;
  &lt;div&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;strong&gt;Oh My Pi&lt;/strong&gt; / weekly tutorial / week15-quant-backtest-mini&lt;/span&gt;&lt;/div&gt;
  &lt;div&gt;
    &lt;div&gt;&lt;span&gt;miku@cachyos&lt;/span&gt;&lt;span&gt;:~/Code/python-learning&lt;/span&gt;&lt;span&gt;$&lt;/span&gt; &lt;span&gt;omp teach week15-quant-backtest-mini --fish-first --step-by-step&lt;/span&gt;&lt;/div&gt;
    &lt;div&gt;
      &lt;div&gt;&lt;span&gt;source&lt;/span&gt;&lt;strong&gt;562 行教学文档&lt;/strong&gt;&lt;/div&gt;
      &lt;div&gt;&lt;span&gt;week&lt;/span&gt;&lt;strong&gt;Week 15&lt;/strong&gt;&lt;/div&gt;
      &lt;div&gt;&lt;span&gt;shell&lt;/span&gt;&lt;strong&gt;fish-first 命令版&lt;/strong&gt;&lt;/div&gt;
      &lt;div&gt;&lt;span&gt;backlink&lt;/span&gt;&lt;strong&gt;&lt;a href=&quot;/post/ustc-stat-ai-quant-plan/&quot;&gt;20 周计划&lt;/a&gt;&lt;/strong&gt;&lt;/div&gt;
    &lt;/div&gt;
    &lt;article&gt;
&lt;h1&gt;Week 15：Mini 量化回测、动量规则与交易成本&lt;/h1&gt;
&lt;blockquote&gt;
&lt;p&gt;返回总计划：&lt;a href=&quot;/post/ustc-stat-ai-quant-plan/&quot;&gt;USTC 统计 + AI / 量化 20 周成长计划&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Week 15 的目标是做一个&lt;strong&gt;最小可用回测&lt;/strong&gt;：从价格数据出发，计算收益率，生成交易信号，转成持仓，加入交易成本，最后输出收益、波动、最大回撤、Sharpe、换手率等指标。&lt;/p&gt;
&lt;p&gt;本文命令默认使用 &lt;strong&gt;fish shell&lt;/strong&gt;。激活虚拟环境请使用：&lt;/p&gt;
&lt;div&gt;&lt;div&gt;&lt;span&gt;omp&lt;/span&gt;&lt;span&gt;fish&lt;/span&gt;&lt;/div&gt;&lt;pre&gt;&lt;code&gt;source .venv/bin/activate.fish
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;&lt;a&gt;&lt;/a&gt;&lt;/p&gt;
&lt;h2&gt;0. 本周详细教学：语法、规范、验收&lt;/h2&gt;
&lt;blockquote&gt;
&lt;p&gt;本节不是追加在尾部的复习，而是本周正文的入口。先读这里，再做后面的命令和项目。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h3&gt;0.1 本周真正要学会什么&lt;/h3&gt;
&lt;table&gt;
&lt;thead&gt;&lt;tr&gt;&lt;th&gt;维度&lt;/th&gt;&lt;th&gt;要求&lt;/th&gt;&lt;/tr&gt;&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;&lt;td&gt;知识点&lt;/td&gt;&lt;td&gt;signal、position、shift(1)、成本、turnover&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;代码语法&lt;/td&gt;&lt;td&gt;能从空文件写出本周核心脚本，而不是只复制运行&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;程序规范&lt;/td&gt;&lt;td&gt;函数拆分、路径清楚、输入输出明确、错误能解释&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;交付物&lt;/td&gt;&lt;td&gt;&lt;code&gt;projects/quant-backtest-mini/src/backtest.py&lt;/code&gt;&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;验收方式&lt;/td&gt;&lt;td&gt;从 fish 终端运行命令，得到可复查的文件或指标&lt;/td&gt;&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;h3&gt;0.2 代码语法精讲&lt;/h3&gt;
&lt;p&gt;下面的代码不是最终答案，而是本周必须理解的最小骨架：&lt;/p&gt;
&lt;div&gt;&lt;div&gt;&lt;span&gt;omp&lt;/span&gt;&lt;span&gt;python&lt;/span&gt;&lt;/div&gt;&lt;pre&gt;&lt;code&gt;df[&quot;ret&quot;] = df[&quot;close&quot;].pct_change()
df[&quot;signal&quot;] = (df[&quot;close&quot;].pct_change(20) &amp;gt; 0).astype(float)
df[&quot;position&quot;] = df[&quot;signal&quot;].shift(1).fillna(0.0)
df[&quot;turnover&quot;] = df[&quot;position&quot;].diff().abs().fillna(0.0)
df[&quot;net_ret&quot;] = df[&quot;position&quot;] * df[&quot;ret&quot;] - df[&quot;turnover&quot;] * 0.0005
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;读代码时按四步检查：输入从哪里来；中间变量的类型和 shape 是什么；函数或脚本输出什么；哪些错误应该显式报出来。&lt;/p&gt;
&lt;h3&gt;0.3 本周程序规范&lt;/h3&gt;
&lt;ul&gt;&lt;li&gt;所有路径用相对路径或 `pathlib.Path`，不要写死 `/home/miku/...`。&lt;/li&gt;&lt;li&gt;核心逻辑进 `src/`，notebook 只做探索和解释。&lt;/li&gt;&lt;li&gt;每个脚本能从 fish 终端运行，并在 README 写出命令。&lt;/li&gt;&lt;li&gt;输出必须落盘到 `reports/`、`figures/` 或 `outputs/`，不能只在屏幕上看。&lt;/li&gt;&lt;/ul&gt;
&lt;h3&gt;0.4 本周练习分层&lt;/h3&gt;
&lt;table&gt;
&lt;thead&gt;&lt;tr&gt;&lt;th&gt;层级&lt;/th&gt;&lt;th&gt;任务&lt;/th&gt;&lt;th&gt;不合格表现&lt;/th&gt;&lt;th&gt;合格验收&lt;/th&gt;&lt;/tr&gt;&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;&lt;td&gt;最小练习&lt;/td&gt;&lt;td&gt;手写上面的最小骨架&lt;/td&gt;&lt;td&gt;只在 notebook 里运行&lt;/td&gt;&lt;td&gt;终端运行成功&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;标准练习&lt;/td&gt;&lt;td&gt;把逻辑拆成函数/模块&lt;/td&gt;&lt;td&gt;一个大脚本从头写到尾&lt;/td&gt;&lt;td&gt;至少 2 个函数，职责清楚&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;项目练习&lt;/td&gt;&lt;td&gt;生成本周交付物 &lt;code&gt;projects/quant-backtest-mini/src/backtest.py&lt;/code&gt;&lt;/td&gt;&lt;td&gt;只有屏幕输出&lt;/td&gt;&lt;td&gt;文件落盘，可复查&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;复盘练习&lt;/td&gt;&lt;td&gt;写 3 个错误和修复&lt;/td&gt;&lt;td&gt;只写“已解决”&lt;/td&gt;&lt;td&gt;写清报错、原因、修复、预防&lt;/td&gt;&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;h3&gt;0.5 本周和主线的连接&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;回到总计划：&lt;a href=&quot;/post/ustc-stat-ai-quant-plan/#week-plan&quot;&gt;USTC AI / Quant 练习手册&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;查详细练习索引：&lt;a href=&quot;/post/ustc-stat-ai-quant-plan/#deep-practice-appendix&quot;&gt;技术练习详解&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;查质量评分：&lt;a href=&quot;/post/ustc-stat-ai-quant-plan/#quality-final-gates&quot;&gt;最终质量门槛&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;1. 本周学习目标&lt;/h2&gt;
&lt;p&gt;完成本周后，你应该能做到：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;读取价格数据并计算日收益率。&lt;/li&gt;
&lt;li&gt;实现一个简单动量策略：过去 N 日收益为正则持有，否则空仓。&lt;/li&gt;
&lt;li&gt;正确处理信号和持仓的时间差，避免用当天收盘信号赚当天收益。&lt;/li&gt;
&lt;li&gt;加入按换手计算的交易成本。&lt;/li&gt;
&lt;li&gt;计算策略净值、最大回撤、年化收益、年化波动、Sharpe、turnover。&lt;/li&gt;
&lt;li&gt;把代码拆成 &lt;code&gt;backtest.py&lt;/code&gt; 和 &lt;code&gt;metrics.py&lt;/code&gt;，形成一个可复用的小项目。&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;本周建议文件布局：&lt;/p&gt;
&lt;div&gt;&lt;div&gt;&lt;span&gt;omp&lt;/span&gt;&lt;span&gt;prompt&lt;/span&gt;&lt;/div&gt;&lt;pre&gt;&lt;code&gt;quant-backtest-mini/
├── data/
│   └── prices.csv
├── figures/
│   ├── equity_curve.png
│   └── drawdown.png
├── reports/
│   └── week15_backtest_notes.md
├── src/
│   └── quant_backtest_mini/
│       ├── __init__.py
│       ├── backtest.py
│       └── metrics.py
├── README.md
└── pyproject.toml
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;h2&gt;2. 前置条件&lt;/h2&gt;
&lt;p&gt;你需要理解：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Week 13 的收益率、rolling 指标、画图。&lt;/li&gt;
&lt;li&gt;Week 14 的时间顺序和数据泄露风险。&lt;/li&gt;
&lt;li&gt;&lt;code&gt;pandas.Series.shift()&lt;/code&gt; 的含义。&lt;/li&gt;
&lt;li&gt;年化指标只是约定换算，不代表未来收益保证。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;创建项目：&lt;/p&gt;
&lt;div&gt;&lt;div&gt;&lt;span&gt;omp&lt;/span&gt;&lt;span&gt;fish&lt;/span&gt;&lt;/div&gt;&lt;pre&gt;&lt;code&gt;mkdir -p ~/Code/ustc-stat-ai-quant
cd ~/Code/ustc-stat-ai-quant
mkdir quant-backtest-mini
cd quant-backtest-mini
uv init
uv venv
source .venv/bin/activate.fish
uv add pandas numpy matplotlib
mkdir -p data figures reports src/quant_backtest_mini
printf &quot;&quot; &amp;gt; src/quant_backtest_mini/__init__.py
code .
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;解释：&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;命令&lt;/th&gt;
&lt;th&gt;作用&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;&lt;tr&gt;
&lt;td&gt;&lt;code&gt;quant-backtest-mini&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;本周是一个小项目，不再只叫 week15&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;src/quant_backtest_mini&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;使用包结构，方便 Week 17 工程化&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;printf &quot;&quot; &amp;gt; __init__.py&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;创建空包标记文件&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;uv add pandas numpy matplotlib&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;本周只用基础数据处理和画图依赖&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;&lt;/table&gt;
&lt;h2&gt;3. 准备价格数据&lt;/h2&gt;
&lt;p&gt;如果你有 Week 13 或 Week 14 的数据，可以复制：&lt;/p&gt;
&lt;div&gt;&lt;div&gt;&lt;span&gt;omp&lt;/span&gt;&lt;span&gt;fish&lt;/span&gt;&lt;/div&gt;&lt;pre&gt;&lt;code&gt;cp ../week14-time-series-validation/data/prices.csv data/prices.csv
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;如果没有，创建 &lt;code&gt;src/make_sample_prices.py&lt;/code&gt;：&lt;/p&gt;
&lt;div&gt;&lt;div&gt;&lt;span&gt;omp&lt;/span&gt;&lt;span&gt;python&lt;/span&gt;&lt;/div&gt;&lt;pre&gt;&lt;code&gt;from pathlib import Path
&lt;p&gt;import numpy as np
import pandas as pd&lt;/p&gt;
&lt;p&gt;rng = np.random.default_rng(15)
dates = pd.bdate_range(“2020-01-02”, periods=756)&lt;/p&gt;
&lt;p&gt;trend = np.where(np.arange(len(dates)) &amp;lt; 380, 0.0005, -0.0001)
noise = rng.normal(0, 0.012, size=len(dates))
log_returns = trend + noise
price = 100 * np.exp(np.cumsum(log_returns))&lt;/p&gt;
&lt;/code&gt;&lt;p&gt;&lt;code&gt;Path(“data”).mkdir(exist_ok=True)
pd.DataFrame({“date”: dates, “close”: price}).to_csv(“data/prices.csv”, index=False)
print(“saved data/prices.csv”)
&lt;/code&gt;&lt;/p&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;&lt;/p&gt;
&lt;p&gt;运行：&lt;/p&gt;
&lt;div&gt;&lt;div&gt;&lt;span&gt;omp&lt;/span&gt;&lt;span&gt;fish&lt;/span&gt;&lt;/div&gt;&lt;pre&gt;&lt;code&gt;python src/make_sample_prices.py
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;检查：&lt;/p&gt;
&lt;div&gt;&lt;div&gt;&lt;span&gt;omp&lt;/span&gt;&lt;span&gt;fish&lt;/span&gt;&lt;/div&gt;&lt;pre&gt;&lt;code&gt;python -c &quot;import pandas as pd; df = pd.read_csv(&apos;data/prices.csv&apos;); print(df.head()); print(df.tail()); print(df.shape)&quot;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;真实项目中，数据可以来自指数、ETF、股票或课程提供的数据。本周重点不是数据接口，而是回测逻辑。&lt;/p&gt;
&lt;h2&gt;4. 回测里的时间顺序&lt;/h2&gt;
&lt;p&gt;假设策略规则是：&lt;/p&gt;
&lt;div&gt;&lt;div&gt;&lt;span&gt;omp&lt;/span&gt;&lt;span&gt;prompt&lt;/span&gt;&lt;/div&gt;&lt;pre&gt;&lt;code&gt;如果过去 N 日收益为正，则持有；否则空仓。
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;关键问题：什么时候知道信号，什么时候产生收益？&lt;/p&gt;
&lt;p&gt;本教程采用保守设定：&lt;/p&gt;
&lt;div&gt;&lt;div&gt;&lt;span&gt;omp&lt;/span&gt;&lt;span&gt;prompt&lt;/span&gt;&lt;/div&gt;&lt;pre&gt;&lt;code&gt;t 日收盘后：计算过去 N 日收益，得到 t 日信号
t+1 日：根据信号持仓，获得 t+1 日收益
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;所以代码里必须做：&lt;/p&gt;
&lt;div&gt;&lt;div&gt;&lt;span&gt;omp&lt;/span&gt;&lt;span&gt;python&lt;/span&gt;&lt;/div&gt;&lt;pre&gt;&lt;code&gt;position = signal.shift(1)
strategy_return = position * asset_return
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;如果不 &lt;code&gt;shift(1)&lt;/code&gt;，就相当于用今天收盘后才知道的信号，赚了今天从开盘到收盘的收益，这是未来函数。&lt;/p&gt;
&lt;h2&gt;5. 实现风险指标 &lt;code&gt;metrics.py&lt;/code&gt;&lt;/h2&gt;
&lt;p&gt;创建 &lt;code&gt;src/quant_backtest_mini/metrics.py&lt;/code&gt;：&lt;/p&gt;
&lt;div&gt;&lt;div&gt;&lt;span&gt;omp&lt;/span&gt;&lt;span&gt;python&lt;/span&gt;&lt;/div&gt;&lt;pre&gt;&lt;code&gt;import numpy as np
import pandas as pd
&lt;p&gt;TRADING_DAYS = 252&lt;/p&gt;
&lt;p&gt;def annual_return(daily_returns: pd.Series) -&amp;gt; float:
daily_returns = daily_returns.dropna()
if daily_returns.empty:
return 0.0
total_return = (1 + daily_returns).prod() - 1
years = len(daily_returns) / TRADING_DAYS
if years &amp;lt;= 0:
return 0.0
return float((1 + total_return) ** (1 / years) - 1)&lt;/p&gt;
&lt;p&gt;def annual_volatility(daily_returns: pd.Series) -&amp;gt; float:
daily_returns = daily_returns.dropna()
return float(daily_returns.std(ddof=0) * np.sqrt(TRADING_DAYS))&lt;/p&gt;
&lt;p&gt;def sharpe_ratio(daily_returns: pd.Series, risk_free_rate: float = 0.0) -&amp;gt; float:
daily_returns = daily_returns.dropna()
if daily_returns.empty:
return 0.0
daily_rf = risk_free_rate / TRADING_DAYS
excess = daily_returns - daily_rf
vol = excess.std(ddof=0)
if vol == 0 or np.isnan(vol):
return 0.0
return float(excess.mean() / vol * np.sqrt(TRADING_DAYS))&lt;/p&gt;
&lt;p&gt;def equity_curve(daily_returns: pd.Series) -&amp;gt; pd.Series:
return (1 + daily_returns.fillna(0)).cumprod()&lt;/p&gt;
&lt;p&gt;def drawdown(equity: pd.Series) -&amp;gt; pd.Series:
running_max = equity.cummax()
return equity / running_max - 1&lt;/p&gt;
&lt;p&gt;def max_drawdown(daily_returns: pd.Series) -&amp;gt; float:
equity = equity_curve(daily_returns)
dd = drawdown(equity)
return float(dd.min())&lt;/p&gt;
&lt;/code&gt;&lt;p&gt;&lt;code&gt;def summarize(daily_returns: pd.Series, turnover: pd.Series) -&amp;gt; dict[str, float]:
return {
“annual_return”: annual_return(daily_returns),
“annual_volatility”: annual_volatility(daily_returns),
“sharpe_ratio”: sharpe_ratio(daily_returns),
“max_drawdown”: max_drawdown(daily_returns),
“average_daily_turnover”: float(turnover.fillna(0).mean()),
“total_turnover”: float(turnover.fillna(0).sum()),
}
&lt;/code&gt;&lt;/p&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;&lt;/p&gt;
&lt;p&gt;解释：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;年化收益：把整个期间累计收益换算成年化。&lt;/li&gt;
&lt;li&gt;年化波动：日收益标准差乘以交易日数量平方根。&lt;/li&gt;
&lt;li&gt;Sharpe：单位波动带来的超额收益。&lt;/li&gt;
&lt;li&gt;最大回撤：从历史高点到之后低点的最大跌幅。&lt;/li&gt;
&lt;li&gt;turnover：持仓变化幅度，用来估算交易频率和成本。&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;6. 实现回测 &lt;code&gt;backtest.py&lt;/code&gt;&lt;/h2&gt;
&lt;p&gt;创建 &lt;code&gt;src/quant_backtest_mini/backtest.py&lt;/code&gt;：&lt;/p&gt;
&lt;div&gt;&lt;div&gt;&lt;span&gt;omp&lt;/span&gt;&lt;span&gt;python&lt;/span&gt;&lt;/div&gt;&lt;pre&gt;&lt;code&gt;from pathlib import Path
&lt;p&gt;import matplotlib.pyplot as plt
import pandas as pd&lt;/p&gt;
&lt;p&gt;from quant_backtest_mini.metrics import drawdown, equity_curve, summarize&lt;/p&gt;
&lt;p&gt;FIGURE_DIR = Path(“figures”)
FIGURE_DIR.mkdir(exist_ok=True)&lt;/p&gt;
&lt;p&gt;def load_prices(path: str = “data/prices.csv”) -&amp;gt; pd.DataFrame:
df = pd.read_csv(path, parse_dates=[“date”])
df = df.sort_values(“date”).set_index(“date”)
return df&lt;/p&gt;
&lt;p&gt;def make_momentum_signal(prices: pd.DataFrame, lookback: int) -&amp;gt; pd.Series:
momentum_return = prices[“close”].pct_change(lookback)
signal = (momentum_return &amp;gt; 0).astype(float)
return signal&lt;/p&gt;
&lt;p&gt;def run_backtest(
prices: pd.DataFrame,
lookback: int = 20,
cost_bps: float = 5.0,
) -&amp;gt; pd.DataFrame:
result = prices.copy()
result[“asset_return”] = result[“close”].pct_change()
result[“signal”] = make_momentum_signal(result, lookback=lookback)&lt;/p&gt;
&lt;p&gt;plain # t 日收盘后产生的 signal，只能用于 t+1 日持仓
result[“position”] = result[“signal”].shift(1).fillna(0)&lt;/p&gt;
&lt;p&gt;plain result[“gross_strategy_return”] = result[“position”] * result[“asset_return”]
result[“turnover”] = result[“position”].diff().abs().fillna(result[“position”].abs())&lt;/p&gt;
&lt;p&gt;plain cost_rate = cost_bps / 10_000
result[“transaction_cost”] = result[“turnover”] * cost_rate
result[“net_strategy_return”] = result[“gross_strategy_return”] - result[“transaction_cost”]
result[“benchmark_return”] = result[“asset_return”].fillna(0)&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;return result.dropna(subset=[&amp;amp;quot;asset_return&amp;amp;quot;])&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;def plot_results(result: pd.DataFrame) -&amp;gt; None:
strategy_equity = equity_curve(result[“net_strategy_return”])
benchmark_equity = equity_curve(result[“benchmark_return”])&lt;/p&gt;
&lt;p&gt;plain fig, ax = plt.subplots(figsize=(10, 5))
strategy_equity.plot(ax=ax, label=“momentum strategy net”)
benchmark_equity.plot(ax=ax, label=“buy and hold”)
ax.set_title(“Equity Curve”)
ax.set_ylabel(“growth of 1”)
ax.legend()
fig.tight_layout()
fig.savefig(FIGURE_DIR / “equity_curve.png”, dpi=150)
plt.close(fig)&lt;/p&gt;
&lt;p&gt;plain fig, ax = plt.subplots(figsize=(10, 4))
drawdown(strategy_equity).plot(ax=ax, label=“strategy drawdown”)
drawdown(benchmark_equity).plot(ax=ax, label=“benchmark drawdown”)
ax.set_title(“Drawdown”)
ax.set_ylabel(“drawdown”)
ax.legend()
fig.tight_layout()
fig.savefig(FIGURE_DIR / “drawdown.png”, dpi=150)
plt.close(fig)&lt;/p&gt;
&lt;p&gt;def main() -&amp;gt; None:
prices = load_prices()
result = run_backtest(prices, lookback=20, cost_bps=5.0)&lt;/p&gt;
&lt;p&gt;plain strategy_summary = summarize(result[“net_strategy_return”], result[“turnover”])
benchmark_summary = summarize(result[“benchmark_return”], pd.Series(0, index=result.index))&lt;/p&gt;
&lt;p&gt;plain print(“strategy”)
for key, value in strategy_summary.items():
print(f”{key}: {value:.4f}”)&lt;/p&gt;
&lt;p&gt;plain print(“benchmark”)
for key, value in benchmark_summary.items():
print(f”{key}: {value:.4f}”)&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;plot_results(result)&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;result.to_csv(&amp;amp;quot;reports/week15_backtest_daily_results.csv&amp;amp;quot;)&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;print(&amp;amp;quot;saved figures and reports/week15_backtest_daily_results.csv&amp;amp;quot;)&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;/code&gt;&lt;p&gt;&lt;code&gt;if &lt;strong&gt;name&lt;/strong&gt; == “&lt;strong&gt;main&lt;/strong&gt;”:
main()
&lt;/code&gt;&lt;/p&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;&lt;/p&gt;
&lt;p&gt;运行：&lt;/p&gt;
&lt;div&gt;&lt;div&gt;&lt;span&gt;omp&lt;/span&gt;&lt;span&gt;fish&lt;/span&gt;&lt;/div&gt;&lt;pre&gt;&lt;code&gt;python -m quant_backtest_mini.backtest
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;如果提示找不到包，确认你在项目根目录，并且包在 &lt;code&gt;src/quant_backtest_mini/&lt;/code&gt; 下。&lt;code&gt;uv init&lt;/code&gt; 通常会配置好 &lt;code&gt;src&lt;/code&gt; 布局；如果没有，可以先用下面命令运行：&lt;/p&gt;
&lt;div&gt;&lt;div&gt;&lt;span&gt;omp&lt;/span&gt;&lt;span&gt;fish&lt;/span&gt;&lt;/div&gt;&lt;pre&gt;&lt;code&gt;PYTHONPATH=src python src/quant_backtest_mini/backtest.py
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;推荐最终使用模块方式运行，因为 Week 17 工程化会继续整理 CLI 入口。&lt;/p&gt;
&lt;h2&gt;7. 理解交易成本&lt;/h2&gt;
&lt;p&gt;本教程使用基点 bps：&lt;/p&gt;
&lt;div&gt;&lt;div&gt;&lt;span&gt;omp&lt;/span&gt;&lt;span&gt;prompt&lt;/span&gt;&lt;/div&gt;&lt;pre&gt;&lt;code&gt;1 bps = 0.01%
5 bps = 0.05%
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;交易成本计算：&lt;/p&gt;
&lt;div&gt;&lt;div&gt;&lt;span&gt;omp&lt;/span&gt;&lt;span&gt;python&lt;/span&gt;&lt;/div&gt;&lt;pre&gt;&lt;code&gt;cost_rate = cost_bps / 10_000
transaction_cost = turnover * cost_rate
net_return = gross_return - transaction_cost
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;如果持仓从 0 变成 1，turnover 为 1；如果从 1 变成 0，turnover 也为 1。频繁交易会产生更高成本。&lt;/p&gt;
&lt;p&gt;必须比较：&lt;/p&gt;
&lt;div&gt;&lt;div&gt;&lt;span&gt;omp&lt;/span&gt;&lt;span&gt;prompt&lt;/span&gt;&lt;/div&gt;&lt;pre&gt;&lt;code&gt;gross_strategy_return：未扣成本
net_strategy_return：扣成本后
benchmark_return：买入并持有
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;如果策略只在未扣成本时有效，扣成本后失效，就不能说它有交易价值。&lt;/p&gt;
&lt;h2&gt;8. 风险指标怎么读&lt;/h2&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;指标&lt;/th&gt;
&lt;th&gt;含义&lt;/th&gt;
&lt;th&gt;注意点&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;&lt;tr&gt;
&lt;td&gt;annual return&lt;/td&gt;
&lt;td&gt;年化收益&lt;/td&gt;
&lt;td&gt;样本短时容易夸大&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;annual volatility&lt;/td&gt;
&lt;td&gt;年化波动&lt;/td&gt;
&lt;td&gt;只看波动不看回撤不够&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Sharpe ratio&lt;/td&gt;
&lt;td&gt;单位风险收益&lt;/td&gt;
&lt;td&gt;不稳定，受极端值影响&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;max drawdown&lt;/td&gt;
&lt;td&gt;最大回撤&lt;/td&gt;
&lt;td&gt;很贴近真实心理压力&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;turnover&lt;/td&gt;
&lt;td&gt;换手率&lt;/td&gt;
&lt;td&gt;越高越容易被成本吃掉&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;transaction cost&lt;/td&gt;
&lt;td&gt;交易成本&lt;/td&gt;
&lt;td&gt;回测必须扣除&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;&lt;/table&gt;
&lt;p&gt;对于初学者，最大回撤比年化收益更值得认真看。一个年化很高但回撤很深的策略，实盘中可能根本拿不住。&lt;/p&gt;
&lt;h2&gt;9. 加入样本切分&lt;/h2&gt;
&lt;p&gt;最小回测跑通后，再加入样本内和样本外对比。可以在 &lt;code&gt;backtest.py&lt;/code&gt; 里加入：&lt;/p&gt;
&lt;div&gt;&lt;div&gt;&lt;span&gt;omp&lt;/span&gt;&lt;span&gt;python&lt;/span&gt;&lt;/div&gt;&lt;pre&gt;&lt;code&gt;def split_result(result: pd.DataFrame) -&amp;gt; tuple[pd.DataFrame, pd.DataFrame]:
    split_at = int(len(result) * 0.7)
    in_sample = result.iloc[:split_at]
    out_of_sample = result.iloc[split_at:]
    return in_sample, out_of_sample
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;在 &lt;code&gt;main()&lt;/code&gt; 中输出：&lt;/p&gt;
&lt;div&gt;&lt;div&gt;&lt;span&gt;omp&lt;/span&gt;&lt;span&gt;python&lt;/span&gt;&lt;/div&gt;&lt;pre&gt;&lt;code&gt;in_sample, out_of_sample = split_result(result)
print(&quot;in sample strategy&quot;, summarize(in_sample[&quot;net_strategy_return&quot;], in_sample[&quot;turnover&quot;]))
print(&quot;out of sample strategy&quot;, summarize(out_of_sample[&quot;net_strategy_return&quot;], out_of_sample[&quot;turnover&quot;]))
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;报告中必须写明：样本外表现是否明显变差。如果样本内很好、样本外很差，优先怀疑过拟合。&lt;/p&gt;
&lt;h2&gt;10. 参数敏感性&lt;/h2&gt;
&lt;p&gt;不要只试一个 &lt;code&gt;lookback=20&lt;/code&gt;。至少比较：&lt;/p&gt;
&lt;div&gt;&lt;div&gt;&lt;span&gt;omp&lt;/span&gt;&lt;span&gt;prompt&lt;/span&gt;&lt;/div&gt;&lt;pre&gt;&lt;code&gt;lookback = 5, 10, 20, 60
cost_bps = 0, 5, 10, 20
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;简单做法是在 &lt;code&gt;main()&lt;/code&gt; 中循环：&lt;/p&gt;
&lt;div&gt;&lt;div&gt;&lt;span&gt;omp&lt;/span&gt;&lt;span&gt;python&lt;/span&gt;&lt;/div&gt;&lt;pre&gt;&lt;code&gt;for lookback in [5, 10, 20, 60]:
    for cost_bps in [0, 5, 10, 20]:
        result = run_backtest(prices, lookback=lookback, cost_bps=cost_bps)
        summary = summarize(result[&quot;net_strategy_return&quot;], result[&quot;turnover&quot;])
        print(
            f&quot;lookback={lookback}, cost_bps={cost_bps}, &quot;
            f&quot;annual_return={summary[&apos;annual_return&apos;]:.4f}, &quot;
            f&quot;sharpe={summary[&apos;sharpe_ratio&apos;]:.4f}, &quot;
            f&quot;max_drawdown={summary[&apos;max_drawdown&apos;]:.4f}&quot;
        )
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;如果只有某一个参数组合特别好，其它都很差，要警惕参数挖掘。&lt;/p&gt;
&lt;h2&gt;11. 报告模板&lt;/h2&gt;
&lt;p&gt;创建：&lt;/p&gt;
&lt;div&gt;&lt;div&gt;&lt;span&gt;omp&lt;/span&gt;&lt;span&gt;fish&lt;/span&gt;&lt;/div&gt;&lt;pre&gt;&lt;code&gt;code reports/week15_backtest_notes.md
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;建议结构：&lt;/p&gt;
&lt;div&gt;&lt;div&gt;&lt;span&gt;omp&lt;/span&gt;&lt;span&gt;prompt&lt;/span&gt;&lt;/div&gt;&lt;pre&gt;&lt;code&gt;# Week 15 Backtest Notes
&lt;h2&gt;Strategy&lt;a href=&quot;#strategy&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;策略名称：N 日动量&lt;/li&gt;
&lt;li&gt;规则：过去 N 日收益为正则持有，否则空仓&lt;/li&gt;
&lt;li&gt;执行假设：t 日收盘生成信号，t+1 日持仓&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;Data&lt;a href=&quot;#data&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;数据来源：&lt;/li&gt;
&lt;li&gt;日期范围：&lt;/li&gt;
&lt;li&gt;频率：日频&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;Backtest settings&lt;a href=&quot;#backtest-settings&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;lookback：&lt;/li&gt;
&lt;li&gt;cost_bps：&lt;/li&gt;
&lt;li&gt;benchmark：buy and hold&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;Metrics&lt;a href=&quot;#metrics&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;annual return：&lt;/li&gt;
&lt;li&gt;annual volatility：&lt;/li&gt;
&lt;li&gt;Sharpe：&lt;/li&gt;
&lt;li&gt;max drawdown：&lt;/li&gt;
&lt;li&gt;turnover：&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;Cost sensitivity&lt;a href=&quot;#cost-sensitivity&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;0 bps：&lt;/li&gt;
&lt;li&gt;5 bps：&lt;/li&gt;
&lt;li&gt;10 bps：&lt;/li&gt;
&lt;li&gt;20 bps：&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;Observations&lt;a href=&quot;#observations&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;是否优于 buy and hold？&lt;/li&gt;
&lt;li&gt;收益是否来自少数时间段？&lt;/li&gt;
&lt;li&gt;成本后是否仍有效？&lt;/li&gt;
&lt;li&gt;最大回撤是否能接受？&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;Limitations&lt;a href=&quot;#limitations&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;
&lt;/code&gt;&lt;ul&gt;&lt;code&gt;
&lt;li&gt;模拟数据或单一标的&lt;/li&gt;
&lt;li&gt;没有滑点&lt;/li&gt;
&lt;li&gt;没有停牌、涨跌停、成交量限制&lt;/li&gt;
&lt;/code&gt;&lt;li&gt;&lt;code&gt;没有真实资金容量约束
&lt;/code&gt;&lt;/li&gt;&lt;/ul&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;h2&gt;12. 文件布局检查&lt;/h2&gt;
&lt;p&gt;运行：&lt;/p&gt;
&lt;div&gt;&lt;div&gt;&lt;span&gt;omp&lt;/span&gt;&lt;span&gt;fish&lt;/span&gt;&lt;/div&gt;&lt;pre&gt;&lt;code&gt;pwd
ls
ls data figures reports src/quant_backtest_mini
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;至少应有：&lt;/p&gt;
&lt;div&gt;&lt;div&gt;&lt;span&gt;omp&lt;/span&gt;&lt;span&gt;prompt&lt;/span&gt;&lt;/div&gt;&lt;pre&gt;&lt;code&gt;data/prices.csv
src/quant_backtest_mini/__init__.py
src/quant_backtest_mini/backtest.py
src/quant_backtest_mini/metrics.py
figures/equity_curve.png
figures/drawdown.png
reports/week15_backtest_notes.md
reports/week15_backtest_daily_results.csv
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;h2&gt;13. 练习&lt;/h2&gt;
&lt;ol&gt;
&lt;li&gt;把动量策略改成均值回复：价格低于 20 日均线一定比例则持有，否则空仓。&lt;/li&gt;
&lt;li&gt;比较 &lt;code&gt;lookback=5&lt;/code&gt;、&lt;code&gt;20&lt;/code&gt;、&lt;code&gt;60&lt;/code&gt; 的净值曲线和最大回撤。&lt;/li&gt;
&lt;li&gt;把交易成本从 0 bps 增加到 20 bps，观察 Sharpe 和年化收益变化。&lt;/li&gt;
&lt;li&gt;加入一个简单止损规则，并检查是否只是降低收益、没有真正降低回撤。&lt;/li&gt;
&lt;li&gt;把结果保存为 CSV，并在报告里找出策略收益最高和最低的 5 天。&lt;/li&gt;
&lt;li&gt;把 benchmark 从 buy and hold 改成空仓，解释为什么量化报告通常需要合理 benchmark。&lt;/li&gt;
&lt;/ol&gt;
&lt;h2&gt;14. 验收检查&lt;/h2&gt;
&lt;p&gt;在项目根目录执行：&lt;/p&gt;
&lt;div&gt;&lt;div&gt;&lt;span&gt;omp&lt;/span&gt;&lt;span&gt;fish&lt;/span&gt;&lt;/div&gt;&lt;pre&gt;&lt;code&gt;source .venv/bin/activate.fish
python -m quant_backtest_mini.backtest
python -c &quot;from pathlib import Path; required = [&apos;data/prices.csv&apos;, &apos;src/quant_backtest_mini/backtest.py&apos;, &apos;src/quant_backtest_mini/metrics.py&apos;, &apos;figures/equity_curve.png&apos;, &apos;figures/drawdown.png&apos;, &apos;reports/week15_backtest_daily_results.csv&apos;, &apos;reports/week15_backtest_notes.md&apos;]; missing = [p for p in required if not Path(p).exists()]; print(&apos;missing:&apos;, missing); raise SystemExit(1 if missing else 0)&quot;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;通过标准：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;策略收益使用 &lt;code&gt;position.shift(1)&lt;/code&gt;，没有偷看当天收益。&lt;/li&gt;
&lt;li&gt;回测同时输出扣成本前和扣成本后逻辑，报告中重点解释扣成本后。&lt;/li&gt;
&lt;li&gt;至少计算 annual return、annual volatility、Sharpe、max drawdown、turnover。&lt;/li&gt;
&lt;li&gt;至少有净值曲线和回撤图。&lt;/li&gt;
&lt;li&gt;报告中有成本敏感性和局限性。&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;15. 常见错误&lt;/h2&gt;
&lt;h3&gt;错误 1：忘记 &lt;code&gt;shift(1)&lt;/code&gt;&lt;/h3&gt;
&lt;p&gt;如果直接写：&lt;/p&gt;
&lt;div&gt;&lt;div&gt;&lt;span&gt;omp&lt;/span&gt;&lt;span&gt;python&lt;/span&gt;&lt;/div&gt;&lt;pre&gt;&lt;code&gt;strategy_return = signal * asset_return
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;很可能偷看未来。除非你严格证明信号在收益发生前已知，否则应使用：&lt;/p&gt;
&lt;div&gt;&lt;div&gt;&lt;span&gt;omp&lt;/span&gt;&lt;span&gt;python&lt;/span&gt;&lt;/div&gt;&lt;pre&gt;&lt;code&gt;position = signal.shift(1)
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;h3&gt;错误 2：只看累计收益&lt;/h3&gt;
&lt;p&gt;累计收益高不代表好。必须同时看回撤、波动、Sharpe、交易成本。&lt;/p&gt;
&lt;h3&gt;错误 3：交易成本写成固定每天扣&lt;/h3&gt;
&lt;p&gt;成本应主要和交易有关，而不是每天都扣同样数额。最小版本可以用 &lt;code&gt;turnover * cost_rate&lt;/code&gt;。&lt;/p&gt;
&lt;h3&gt;错误 4：把模拟数据结果当真实策略&lt;/h3&gt;
&lt;p&gt;模拟数据只用于练代码。真实策略必须使用真实数据、严格样本外验证和更完整成本模型。&lt;/p&gt;
&lt;h3&gt;错误 5：参数调到最好就结束&lt;/h3&gt;
&lt;p&gt;如果你试了很多参数，只报告最好的一个，就是参数挖掘。报告中要写参数敏感性。&lt;/p&gt;
&lt;h2&gt;16. 下一步&lt;/h2&gt;
&lt;p&gt;进入 Week 16：&lt;a href=&quot;./week16-quant-project-report.md&quot;&gt;量化项目报告：指标、回撤、样本切分与局限性&lt;/a&gt;。下一周重点是把回测结果写成能被老师、同学或未来自己读懂的报告。&lt;/p&gt;
&lt;p&gt;plain &lt;/p&gt;&lt;/article&gt;&lt;p&gt;&lt;/p&gt;
  &lt;/div&gt;
&lt;/section&gt;</content:encoded><category>category:工具</category><category>category:量化研究</category><category>tag:Quant</category><category>tag:backtest</category><category>tag:momentum</category><category>tag:transaction-cost</category></item><item><title>Week 14：时间序列验证、rolling window 与数据泄露检查</title><link>https://39miku.space/post/week14-time-series-validation</link><guid isPermaLink="false">week14-time-series-validation</guid><description>Week 14：时间序列验证、rolling window 与数据泄露检查。fish-first 终端教学，面向 CachyOS、VS Code、uv 和 Python 学习路线。</description><pubDate>Sun, 26 Jul 2026 06:00:00 GMT</pubDate><content:encoded>
&lt;section&gt;
  &lt;div&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;strong&gt;Oh My Pi&lt;/strong&gt; / weekly tutorial / week14-time-series-validation&lt;/span&gt;&lt;/div&gt;
  &lt;div&gt;
    &lt;div&gt;&lt;span&gt;miku@cachyos&lt;/span&gt;&lt;span&gt;:~/Code/python-learning&lt;/span&gt;&lt;span&gt;$&lt;/span&gt; &lt;span&gt;omp teach week14-time-series-validation --fish-first --step-by-step&lt;/span&gt;&lt;/div&gt;
    &lt;div&gt;
      &lt;div&gt;&lt;span&gt;source&lt;/span&gt;&lt;strong&gt;622 行教学文档&lt;/strong&gt;&lt;/div&gt;
      &lt;div&gt;&lt;span&gt;week&lt;/span&gt;&lt;strong&gt;Week 14&lt;/strong&gt;&lt;/div&gt;
      &lt;div&gt;&lt;span&gt;shell&lt;/span&gt;&lt;strong&gt;fish-first 命令版&lt;/strong&gt;&lt;/div&gt;
      &lt;div&gt;&lt;span&gt;backlink&lt;/span&gt;&lt;strong&gt;&lt;a href=&quot;/post/ustc-stat-ai-quant-plan/&quot;&gt;20 周计划&lt;/a&gt;&lt;/strong&gt;&lt;/div&gt;
    &lt;/div&gt;
    &lt;article&gt;
&lt;h1&gt;Week 14：时间序列验证、rolling window 与数据泄露检查&lt;/h1&gt;
&lt;blockquote&gt;
&lt;p&gt;返回总计划：&lt;a href=&quot;/post/ustc-stat-ai-quant-plan/&quot;&gt;USTC 统计 + AI / 量化 20 周成长计划&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Week 14 的核心目标是：&lt;strong&gt;不要在时间序列任务里偷看未来。&lt;/strong&gt; 预测模型不一定要复杂，但验证方式必须正确。对量化和金融时间序列来说，一个看起来很高的分数，经常只是数据泄露造成的幻觉。&lt;/p&gt;
&lt;p&gt;本文命令默认使用 &lt;strong&gt;fish shell&lt;/strong&gt;，虚拟环境激活命令为：&lt;/p&gt;
&lt;div&gt;&lt;div&gt;&lt;span&gt;omp&lt;/span&gt;&lt;span&gt;fish&lt;/span&gt;&lt;/div&gt;&lt;pre&gt;&lt;code&gt;source .venv/bin/activate.fish
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;&lt;a&gt;&lt;/a&gt;&lt;/p&gt;
&lt;h2&gt;0. 本周详细教学：语法、规范、验收&lt;/h2&gt;
&lt;blockquote&gt;
&lt;p&gt;本节不是追加在尾部的复习，而是本周正文的入口。先读这里，再做后面的命令和项目。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h3&gt;0.1 本周真正要学会什么&lt;/h3&gt;
&lt;table&gt;
&lt;thead&gt;&lt;tr&gt;&lt;th&gt;维度&lt;/th&gt;&lt;th&gt;要求&lt;/th&gt;&lt;/tr&gt;&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;&lt;td&gt;知识点&lt;/td&gt;&lt;td&gt;rolling/expanding validation、naive baseline、防泄漏&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;代码语法&lt;/td&gt;&lt;td&gt;能从空文件写出本周核心脚本，而不是只复制运行&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;程序规范&lt;/td&gt;&lt;td&gt;函数拆分、路径清楚、输入输出明确、错误能解释&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;交付物&lt;/td&gt;&lt;td&gt;&lt;code&gt;src/time_series_validation.py&lt;/code&gt;&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;验收方式&lt;/td&gt;&lt;td&gt;从 fish 终端运行命令，得到可复查的文件或指标&lt;/td&gt;&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;h3&gt;0.2 代码语法精讲&lt;/h3&gt;
&lt;p&gt;下面的代码不是最终答案，而是本周必须理解的最小骨架：&lt;/p&gt;
&lt;div&gt;&lt;div&gt;&lt;span&gt;omp&lt;/span&gt;&lt;span&gt;python&lt;/span&gt;&lt;/div&gt;&lt;pre&gt;&lt;code&gt;def rolling_splits(n, train_size, test_size):
    start = 0
    while start + train_size + test_size &amp;lt;= n:
        train = range(start, start + train_size)
        test = range(start + train_size, start + train_size + test_size)
        yield list(train), list(test)
        start += test_size
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;读代码时按四步检查：输入从哪里来；中间变量的类型和 shape 是什么；函数或脚本输出什么；哪些错误应该显式报出来。&lt;/p&gt;
&lt;h3&gt;0.3 本周程序规范&lt;/h3&gt;
&lt;ul&gt;&lt;li&gt;所有路径用相对路径或 `pathlib.Path`，不要写死 `/home/miku/...`。&lt;/li&gt;&lt;li&gt;核心逻辑进 `src/`，notebook 只做探索和解释。&lt;/li&gt;&lt;li&gt;每个脚本能从 fish 终端运行，并在 README 写出命令。&lt;/li&gt;&lt;li&gt;输出必须落盘到 `reports/`、`figures/` 或 `outputs/`，不能只在屏幕上看。&lt;/li&gt;&lt;/ul&gt;
&lt;h3&gt;0.4 本周练习分层&lt;/h3&gt;
&lt;table&gt;
&lt;thead&gt;&lt;tr&gt;&lt;th&gt;层级&lt;/th&gt;&lt;th&gt;任务&lt;/th&gt;&lt;th&gt;不合格表现&lt;/th&gt;&lt;th&gt;合格验收&lt;/th&gt;&lt;/tr&gt;&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;&lt;td&gt;最小练习&lt;/td&gt;&lt;td&gt;手写上面的最小骨架&lt;/td&gt;&lt;td&gt;只在 notebook 里运行&lt;/td&gt;&lt;td&gt;终端运行成功&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;标准练习&lt;/td&gt;&lt;td&gt;把逻辑拆成函数/模块&lt;/td&gt;&lt;td&gt;一个大脚本从头写到尾&lt;/td&gt;&lt;td&gt;至少 2 个函数，职责清楚&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;项目练习&lt;/td&gt;&lt;td&gt;生成本周交付物 &lt;code&gt;src/time_series_validation.py&lt;/code&gt;&lt;/td&gt;&lt;td&gt;只有屏幕输出&lt;/td&gt;&lt;td&gt;文件落盘，可复查&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;复盘练习&lt;/td&gt;&lt;td&gt;写 3 个错误和修复&lt;/td&gt;&lt;td&gt;只写“已解决”&lt;/td&gt;&lt;td&gt;写清报错、原因、修复、预防&lt;/td&gt;&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;h3&gt;0.5 本周和主线的连接&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;回到总计划：&lt;a href=&quot;/post/ustc-stat-ai-quant-plan/#week-plan&quot;&gt;USTC AI / Quant 练习手册&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;查详细练习索引：&lt;a href=&quot;/post/ustc-stat-ai-quant-plan/#deep-practice-appendix&quot;&gt;技术练习详解&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;查质量评分：&lt;a href=&quot;/post/ustc-stat-ai-quant-plan/#quality-final-gates&quot;&gt;最终质量门槛&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;1. 本周学习目标&lt;/h2&gt;
&lt;p&gt;完成本周后，你应该能做到：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;用时间顺序切分 train / validation / test。&lt;/li&gt;
&lt;li&gt;解释为什么时间序列不能随机打乱。&lt;/li&gt;
&lt;li&gt;实现 naive baseline、rolling window validation、expanding window validation。&lt;/li&gt;
&lt;li&gt;比较线性模型、随机森林和简单基准，而不是只看一个模型。&lt;/li&gt;
&lt;li&gt;检查标准化、rolling 特征、标签构造中的常见泄露。&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;本周建议交付物：&lt;/p&gt;
&lt;div&gt;&lt;div&gt;&lt;span&gt;omp&lt;/span&gt;&lt;span&gt;prompt&lt;/span&gt;&lt;/div&gt;&lt;pre&gt;&lt;code&gt;week14-time-series-validation/
├── data/
│   └── prices.csv
├── figures/
│   ├── split.png
│   └── rolling_validation_scores.png
├── reports/
│   └── week14_forecast_report.md
├── src/
│   └── time_series_validation.py
├── pyproject.toml
└── README.md
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;h2&gt;2. 前置条件&lt;/h2&gt;
&lt;p&gt;你需要已经完成或理解 Week 13：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;价格和收益率的区别。&lt;/li&gt;
&lt;li&gt;&lt;code&gt;pct_change()&lt;/code&gt;、&lt;code&gt;rolling()&lt;/code&gt;、&lt;code&gt;shift()&lt;/code&gt; 的基本用法。&lt;/li&gt;
&lt;li&gt;为什么收益率比价格更适合建模。&lt;/li&gt;
&lt;li&gt;Python 项目的基本结构和 uv 虚拟环境。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;进入学习目录并创建项目：&lt;/p&gt;
&lt;div&gt;&lt;div&gt;&lt;span&gt;omp&lt;/span&gt;&lt;span&gt;fish&lt;/span&gt;&lt;/div&gt;&lt;pre&gt;&lt;code&gt;mkdir -p ~/Code/ustc-stat-ai-quant
cd ~/Code/ustc-stat-ai-quant
mkdir week14-time-series-validation
cd week14-time-series-validation
uv init
uv venv
source .venv/bin/activate.fish
uv add pandas numpy matplotlib scikit-learn
mkdir -p data src reports figures
code .
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;解释：&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;命令&lt;/th&gt;
&lt;th&gt;作用&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;&lt;tr&gt;
&lt;td&gt;&lt;code&gt;uv add scikit-learn&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;本周会用线性模型、随机森林和指标&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;mkdir -p data src reports figures&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;保持数据、代码、报告、图片分离&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;source .venv/bin/activate.fish&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;使用 fish 语法激活环境&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;&lt;/table&gt;
&lt;h2&gt;3. 准备数据&lt;/h2&gt;
&lt;p&gt;如果你已经有 Week 13 的模拟价格数据，可以复制过来：&lt;/p&gt;
&lt;div&gt;&lt;div&gt;&lt;span&gt;omp&lt;/span&gt;&lt;span&gt;fish&lt;/span&gt;&lt;/div&gt;&lt;pre&gt;&lt;code&gt;cp ../week13-time-series-basics/data/prices.csv data/prices.csv
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;如果没有，创建 &lt;code&gt;src/make_sample_prices.py&lt;/code&gt;：&lt;/p&gt;
&lt;div&gt;&lt;div&gt;&lt;span&gt;omp&lt;/span&gt;&lt;span&gt;python&lt;/span&gt;&lt;/div&gt;&lt;pre&gt;&lt;code&gt;from pathlib import Path
&lt;p&gt;import numpy as np
import pandas as pd&lt;/p&gt;
&lt;p&gt;rng = np.random.default_rng(2026)
dates = pd.bdate_range(“2021-01-04”, periods=520)&lt;/p&gt;
&lt;p&gt;market_noise = rng.normal(0.0002, 0.011, size=len(dates))
slow_cycle = 0.002 * np.sin(np.arange(len(dates)) / 30)
log_returns = market_noise + slow_cycle
price = 100 * np.exp(np.cumsum(log_returns))&lt;/p&gt;
&lt;/code&gt;&lt;p&gt;&lt;code&gt;Path(“data”).mkdir(exist_ok=True)
pd.DataFrame({“date”: dates, “close”: price}).to_csv(“data/prices.csv”, index=False)
print(“saved data/prices.csv”)
&lt;/code&gt;&lt;/p&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;&lt;/p&gt;
&lt;p&gt;运行：&lt;/p&gt;
&lt;div&gt;&lt;div&gt;&lt;span&gt;omp&lt;/span&gt;&lt;span&gt;fish&lt;/span&gt;&lt;/div&gt;&lt;pre&gt;&lt;code&gt;python src/make_sample_prices.py
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;检查：&lt;/p&gt;
&lt;div&gt;&lt;div&gt;&lt;span&gt;omp&lt;/span&gt;&lt;span&gt;fish&lt;/span&gt;&lt;/div&gt;&lt;pre&gt;&lt;code&gt;python -c &quot;import pandas as pd; df = pd.read_csv(&apos;data/prices.csv&apos;); print(df.head()); print(df.tail()); print(df.shape)&quot;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;h2&gt;4. 明确预测任务&lt;/h2&gt;
&lt;p&gt;本周不要一上来就“预测价格”。先定义一个清楚、可验证的小任务：&lt;/p&gt;
&lt;div&gt;&lt;div&gt;&lt;span&gt;omp&lt;/span&gt;&lt;span&gt;prompt&lt;/span&gt;&lt;/div&gt;&lt;pre&gt;&lt;code&gt;输入：截至今天已经知道的收益率、滚动均值、滚动波动率
目标：预测明天的日收益率方向
标签：明天收益率是否大于 0
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;这意味着：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;第 &lt;code&gt;t&lt;/code&gt; 天的特征只能使用第 &lt;code&gt;t&lt;/code&gt; 天及以前的数据。&lt;/li&gt;
&lt;li&gt;第 &lt;code&gt;t&lt;/code&gt; 天的标签是第 &lt;code&gt;t+1&lt;/code&gt; 天的方向。&lt;/li&gt;
&lt;li&gt;训练集必须早于验证集，验证集必须早于测试集。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;创建 &lt;code&gt;src/time_series_validation.py&lt;/code&gt;，先写读取和特征构造：&lt;/p&gt;
&lt;div&gt;&lt;div&gt;&lt;span&gt;omp&lt;/span&gt;&lt;span&gt;python&lt;/span&gt;&lt;/div&gt;&lt;pre&gt;&lt;code&gt;from pathlib import Path
&lt;p&gt;import matplotlib.pyplot as plt
import numpy as np
import pandas as pd
from sklearn.ensemble import RandomForestClassifier
from sklearn.linear_model import LogisticRegression
from sklearn.metrics import accuracy_score, precision_score
from sklearn.pipeline import Pipeline
from sklearn.preprocessing import StandardScaler&lt;/p&gt;
&lt;p&gt;FIGURE_DIR = Path(“figures”)
FIGURE_DIR.mkdir(exist_ok=True)&lt;/p&gt;
&lt;p&gt;def load_prices(path: str = “data/prices.csv”) -&amp;gt; pd.DataFrame:
df = pd.read_csv(path, parse_dates=[“date”])
df = df.sort_values(“date”).set_index(“date”)
return df&lt;/p&gt;
&lt;p&gt;def make_features(df: pd.DataFrame) -&amp;gt; pd.DataFrame:
out = df.copy()
out[“return_1d”] = out[“close”].pct_change()
out[“return_5d”] = out[“close”].pct_change(5)
out[“rolling_mean_5”] = out[“return_1d”].rolling(5).mean()
out[“rolling_vol_5”] = out[“return_1d”].rolling(5).std()
out[“rolling_mean_20”] = out[“return_1d”].rolling(20).mean()
out[“rolling_vol_20”] = out[“return_1d”].rolling(20).std()&lt;/p&gt;
&lt;/code&gt;&lt;p&gt;&lt;code&gt;plain # 预测明天方向：明天收益率大于 0 记为 1，否则 0
out[“target_up_next_day”] = (out[“return_1d”].shift(-1) &amp;gt; 0).astype(int)
out = out.dropna()
return out
&lt;/code&gt;&lt;/p&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;&lt;/p&gt;
&lt;p&gt;关键点：&lt;code&gt;shift(-1)&lt;/code&gt; 只用于构造标签，不可以把未来收益率当成特征。&lt;/p&gt;
&lt;h2&gt;5. 不要随机打乱时间序列&lt;/h2&gt;
&lt;p&gt;很多机器学习教程会写：随机划分训练集和测试集。时间序列不能这样做，因为未来样本会混入训练集。&lt;/p&gt;
&lt;p&gt;错误思路：&lt;/p&gt;
&lt;div&gt;&lt;div&gt;&lt;span&gt;omp&lt;/span&gt;&lt;span&gt;prompt&lt;/span&gt;&lt;/div&gt;&lt;pre&gt;&lt;code&gt;随机抽 80% 日期训练，剩下 20% 日期测试
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;这会导致：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;训练集中可能包含 2024 年数据。&lt;/li&gt;
&lt;li&gt;测试集中可能包含 2022 年数据。&lt;/li&gt;
&lt;li&gt;模型相当于在预测过去时已经见过未来市场环境。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;正确思路：&lt;/p&gt;
&lt;div&gt;&lt;div&gt;&lt;span&gt;omp&lt;/span&gt;&lt;span&gt;prompt&lt;/span&gt;&lt;/div&gt;&lt;pre&gt;&lt;code&gt;早期数据：训练
中间数据：验证
后期数据：测试
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;在脚本中加入时间切分函数：&lt;/p&gt;
&lt;div&gt;&lt;div&gt;&lt;span&gt;omp&lt;/span&gt;&lt;span&gt;python&lt;/span&gt;&lt;/div&gt;&lt;pre&gt;&lt;code&gt;def chronological_split(data: pd.DataFrame) -&amp;gt; tuple[pd.DataFrame, pd.DataFrame, pd.DataFrame]:
    n = len(data)
    train_end = int(n * 0.6)
    valid_end = int(n * 0.8)
    train = data.iloc[:train_end]
    valid = data.iloc[train_end:valid_end]
    test = data.iloc[valid_end:]
    return train, valid, test
&lt;/code&gt;&lt;p&gt;&lt;code&gt;def plot_split(train: pd.DataFrame, valid: pd.DataFrame, test: pd.DataFrame) -&amp;gt; None:
fig, ax = plt.subplots(figsize=(10, 4))
train[“close”].plot(ax=ax, label=“train”)
valid[“close”].plot(ax=ax, label=“validation”)
test[“close”].plot(ax=ax, label=“test”)
ax.set_title(“Chronological Split”)
ax.legend()
fig.tight_layout()
fig.savefig(FIGURE_DIR / “split.png”, dpi=150)
plt.close(fig)
&lt;/code&gt;&lt;/p&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;&lt;/p&gt;
&lt;h2&gt;6. 建立 naive baseline&lt;/h2&gt;
&lt;p&gt;在预测方向任务中，最简单的 baseline 可以是：明天方向等于今天方向。&lt;/p&gt;
&lt;div&gt;&lt;div&gt;&lt;span&gt;omp&lt;/span&gt;&lt;span&gt;python&lt;/span&gt;&lt;/div&gt;&lt;pre&gt;&lt;code&gt;def evaluate_naive(data: pd.DataFrame) -&amp;gt; dict[str, float]:
    y_true = data[&quot;target_up_next_day&quot;]
    y_pred = (data[&quot;return_1d&quot;] &amp;gt; 0).astype(int)
    return {
        &quot;accuracy&quot;: accuracy_score(y_true, y_pred),
        &quot;precision&quot;: precision_score(y_true, y_pred, zero_division=0),
    }
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;为什么 baseline 重要？&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;如果复杂模型打不过 naive baseline，说明模型暂时没价值。&lt;/li&gt;
&lt;li&gt;如果只比 50% 高一点点，不一定有交易价值，因为交易成本会吃掉优势。&lt;/li&gt;
&lt;li&gt;量化里“略高于随机”也可能是过拟合，需要滚动验证和样本外验证。&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;7. 用 Pipeline 避免标准化泄露&lt;/h2&gt;
&lt;p&gt;标准化常见泄露：先用全数据计算均值和标准差，再切分训练测试。这样测试集的信息已经进入训练流程。&lt;/p&gt;
&lt;p&gt;正确方式：标准化器只在训练集 &lt;code&gt;fit&lt;/code&gt;，然后对验证集或测试集 &lt;code&gt;transform&lt;/code&gt;。&lt;/p&gt;
&lt;p&gt;&lt;code&gt;scikit-learn&lt;/code&gt; 的 &lt;code&gt;Pipeline&lt;/code&gt; 可以减少误用：&lt;/p&gt;
&lt;div&gt;&lt;div&gt;&lt;span&gt;omp&lt;/span&gt;&lt;span&gt;python&lt;/span&gt;&lt;/div&gt;&lt;pre&gt;&lt;code&gt;FEATURE_COLUMNS = [
    &quot;return_1d&quot;,
    &quot;return_5d&quot;,
    &quot;rolling_mean_5&quot;,
    &quot;rolling_vol_5&quot;,
    &quot;rolling_mean_20&quot;,
    &quot;rolling_vol_20&quot;,
]
TARGET_COLUMN = &quot;target_up_next_day&quot;
&lt;p&gt;def evaluate_model(train: pd.DataFrame, test: pd.DataFrame, model_name: str) -&amp;gt; dict[str, float | str]:
x_train = train[FEATURE_COLUMNS]
y_train = train[TARGET_COLUMN]
x_test = test[FEATURE_COLUMNS]
y_test = test[TARGET_COLUMN]&lt;/p&gt;
&lt;p&gt;plain if model_name == “logistic”:
model = Pipeline(
steps=[
(“scaler”, StandardScaler()),
(“model”, LogisticRegression(max_iter=1000)),
]
)
elif model_name == “random_forest”:
model = RandomForestClassifier(
n_estimators=200,
max_depth=3,
random_state=42,
)
else:
raise ValueError(f”unknown model: {model_name}”)&lt;/p&gt;
&lt;/code&gt;&lt;p&gt;&lt;code&gt;plain model.fit(x_train, y_train)
pred = model.predict(x_test)
return {
“model”: model_name,
“accuracy”: accuracy_score(y_test, pred),
“precision”: precision_score(y_test, pred, zero_division=0),
}
&lt;/code&gt;&lt;/p&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;&lt;/p&gt;
&lt;p&gt;注意：随机森林通常不需要标准化；逻辑回归需要。&lt;/p&gt;
&lt;h2&gt;8. rolling window validation&lt;/h2&gt;
&lt;p&gt;rolling window 的直觉：每次只用最近一段历史训练，然后预测之后一小段。&lt;/p&gt;
&lt;div&gt;&lt;div&gt;&lt;span&gt;omp&lt;/span&gt;&lt;span&gt;prompt&lt;/span&gt;&lt;/div&gt;&lt;pre&gt;&lt;code&gt;第 1 次：用 0-199 天训练，预测 200-219 天
第 2 次：用 20-219 天训练，预测 220-239 天
第 3 次：用 40-239 天训练，预测 240-259 天
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;加入函数：&lt;/p&gt;
&lt;div&gt;&lt;div&gt;&lt;span&gt;omp&lt;/span&gt;&lt;span&gt;python&lt;/span&gt;&lt;/div&gt;&lt;pre&gt;&lt;code&gt;def rolling_window_validation(
    data: pd.DataFrame,
    train_size: int,
    test_size: int,
    step_size: int,
    model_name: str,
) -&amp;gt; pd.DataFrame:
    rows = []
    start = 0
    while start + train_size + test_size &amp;lt;= len(data):
        train = data.iloc[start : start + train_size]
        test = data.iloc[start + train_size : start + train_size + test_size]
        score = evaluate_model(train, test, model_name)
        rows.append(
            {
                &quot;train_start&quot;: train.index[0],
                &quot;train_end&quot;: train.index[-1],
                &quot;test_start&quot;: test.index[0],
                &quot;test_end&quot;: test.index[-1],
                **score,
            }
        )
        start += step_size
    return pd.DataFrame(rows)
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;优点：更接近真实策略的滚动更新。缺点：早期历史会被丢弃，样本利用率较低。&lt;/p&gt;
&lt;h2&gt;9. expanding window validation&lt;/h2&gt;
&lt;p&gt;expanding window 的直觉：训练集从最早日期开始，随着时间不断扩大。&lt;/p&gt;
&lt;div&gt;&lt;div&gt;&lt;span&gt;omp&lt;/span&gt;&lt;span&gt;prompt&lt;/span&gt;&lt;/div&gt;&lt;pre&gt;&lt;code&gt;第 1 次：用 0-199 天训练，预测 200-219 天
第 2 次：用 0-219 天训练，预测 220-239 天
第 3 次：用 0-239 天训练，预测 240-259 天
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;加入函数：&lt;/p&gt;
&lt;div&gt;&lt;div&gt;&lt;span&gt;omp&lt;/span&gt;&lt;span&gt;python&lt;/span&gt;&lt;/div&gt;&lt;pre&gt;&lt;code&gt;def expanding_window_validation(
    data: pd.DataFrame,
    initial_train_size: int,
    test_size: int,
    step_size: int,
    model_name: str,
) -&amp;gt; pd.DataFrame:
    rows = []
    train_end = initial_train_size
    while train_end + test_size &amp;lt;= len(data):
        train = data.iloc[:train_end]
        test = data.iloc[train_end : train_end + test_size]
        score = evaluate_model(train, test, model_name)
        rows.append(
            {
                &quot;train_start&quot;: train.index[0],
                &quot;train_end&quot;: train.index[-1],
                &quot;test_start&quot;: test.index[0],
                &quot;test_end&quot;: test.index[-1],
                **score,
            }
        )
        train_end += step_size
    return pd.DataFrame(rows)
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;优点：历史数据利用更多。缺点：如果市场结构变化很大，太久远的数据可能拖累模型。&lt;/p&gt;
&lt;h2&gt;10. 主程序：比较 baseline 和模型&lt;/h2&gt;
&lt;p&gt;加入主程序：&lt;/p&gt;
&lt;div&gt;&lt;div&gt;&lt;span&gt;omp&lt;/span&gt;&lt;span&gt;python&lt;/span&gt;&lt;/div&gt;&lt;pre&gt;&lt;code&gt;def main() -&amp;gt; None:
    prices = load_prices()
    data = make_features(prices)
    train, valid, test = chronological_split(data)
    plot_split(train, valid, test)
&lt;p&gt;plain print(“naive validation:”, evaluate_naive(valid))
print(“naive test:”, evaluate_naive(test))&lt;/p&gt;
&lt;p&gt;plain for model_name in [“logistic”, “random_forest”]:
print(“single split”, evaluate_model(train, valid, model_name))&lt;/p&gt;
&lt;p&gt;plain rolling_scores = rolling_window_validation(
data=data,
train_size=180,
test_size=20,
step_size=20,
model_name=“logistic”,
)
expanding_scores = expanding_window_validation(
data=data,
initial_train_size=180,
test_size=20,
step_size=20,
model_name=“logistic”,
)&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;print(&amp;amp;quot;rolling scores&amp;amp;quot;)&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;print(rolling_scores)&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;print(&amp;amp;quot;expanding scores&amp;amp;quot;)&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;print(expanding_scores)&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;fig, ax = plt.subplots(figsize=(10, 4))&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;rolling_scores[&amp;amp;quot;accuracy&amp;amp;quot;].plot(ax=ax, marker=&amp;amp;quot;o&amp;amp;quot;, label=&amp;amp;quot;rolling&amp;amp;quot;)&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;expanding_scores[&amp;amp;quot;accuracy&amp;amp;quot;].plot(ax=ax, marker=&amp;amp;quot;o&amp;amp;quot;, label=&amp;amp;quot;expanding&amp;amp;quot;)&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;ax.axhline(0.5, color=&amp;amp;quot;black&amp;amp;quot;, linewidth=1, linestyle=&amp;amp;quot;--&amp;amp;quot;)&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;ax.set_title(&amp;amp;quot;Validation Accuracy by Window&amp;amp;quot;)&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;ax.set_xlabel(&amp;amp;quot;window number&amp;amp;quot;)&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;ax.set_ylabel(&amp;amp;quot;accuracy&amp;amp;quot;)&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;ax.legend()&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;fig.tight_layout()&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;fig.savefig(FIGURE_DIR / &amp;amp;quot;rolling_validation_scores.png&amp;amp;quot;, dpi=150)&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;plt.close(fig)&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;/code&gt;&lt;p&gt;&lt;code&gt;if &lt;strong&gt;name&lt;/strong&gt; == “&lt;strong&gt;main&lt;/strong&gt;”:
main()
&lt;/code&gt;&lt;/p&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;&lt;/p&gt;
&lt;p&gt;运行：&lt;/p&gt;
&lt;div&gt;&lt;div&gt;&lt;span&gt;omp&lt;/span&gt;&lt;span&gt;fish&lt;/span&gt;&lt;/div&gt;&lt;pre&gt;&lt;code&gt;python src/time_series_validation.py
xdg-open figures/split.png
xdg-open figures/rolling_validation_scores.png
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;如果结果不稳定，这是正常现象。金融预测本来就难。你的任务不是强行调参到高分，而是学会诚实验证。&lt;/p&gt;
&lt;h2&gt;11. 数据泄露检查清单&lt;/h2&gt;
&lt;p&gt;写报告前逐项检查。&lt;/p&gt;
&lt;h3&gt;11.1 时间切分&lt;/h3&gt;
&lt;p&gt;错误：随机打乱。&lt;/p&gt;
&lt;p&gt;正确：&lt;/p&gt;
&lt;div&gt;&lt;div&gt;&lt;span&gt;omp&lt;/span&gt;&lt;span&gt;python&lt;/span&gt;&lt;/div&gt;&lt;pre&gt;&lt;code&gt;train = data.iloc[:train_end]
valid = data.iloc[train_end:valid_end]
test = data.iloc[valid_end:]
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;检查问题：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;训练集最后一天是否早于验证集第一天？&lt;/li&gt;
&lt;li&gt;验证集最后一天是否早于测试集第一天？&lt;/li&gt;
&lt;li&gt;图上能否看出三段按时间顺序排列？&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;11.2 标签构造&lt;/h3&gt;
&lt;p&gt;预测明天方向可以使用：&lt;/p&gt;
&lt;div&gt;&lt;div&gt;&lt;span&gt;omp&lt;/span&gt;&lt;span&gt;python&lt;/span&gt;&lt;/div&gt;&lt;pre&gt;&lt;code&gt;out[&quot;target_up_next_day&quot;] = (out[&quot;return_1d&quot;].shift(-1) &amp;gt; 0).astype(int)
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;但不能把 &lt;code&gt;return_1d.shift(-1)&lt;/code&gt; 放进特征列。&lt;/p&gt;
&lt;h3&gt;11.3 rolling 特征&lt;/h3&gt;
&lt;p&gt;当日做决策时，&lt;code&gt;rolling()&lt;/code&gt; 默认包含当前行。是否允许取决于你的交易设定：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;如果你在收盘后计算信号，明天开盘或收盘执行，可以使用当天收盘后才知道的数据。&lt;/li&gt;
&lt;li&gt;如果你在当天收盘前就要交易，就不能使用当天收盘价。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;初学时建议写清楚假设：&lt;/p&gt;
&lt;div&gt;&lt;div&gt;&lt;span&gt;omp&lt;/span&gt;&lt;span&gt;prompt&lt;/span&gt;&lt;/div&gt;&lt;pre&gt;&lt;code&gt;本项目假设在 t 日收盘后计算信号，并在 t+1 日持有，因此 t 日及以前的数据可用。
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;h3&gt;11.4 标准化&lt;/h3&gt;
&lt;p&gt;错误：全数据先标准化，再切分。&lt;/p&gt;
&lt;p&gt;正确：用 &lt;code&gt;Pipeline&lt;/code&gt;，让 scaler 在训练集上 fit。&lt;/p&gt;
&lt;h3&gt;11.5 调参&lt;/h3&gt;
&lt;p&gt;如果你反复看测试集表现再改模型，测试集也被你“污染”了。正确流程：&lt;/p&gt;
&lt;div&gt;&lt;div&gt;&lt;span&gt;omp&lt;/span&gt;&lt;span&gt;prompt&lt;/span&gt;&lt;/div&gt;&lt;pre&gt;&lt;code&gt;train：训练模型
validation：比较模型和调参
test：最后只评估一次
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;h2&gt;12. 报告模板&lt;/h2&gt;
&lt;p&gt;创建报告：&lt;/p&gt;
&lt;div&gt;&lt;div&gt;&lt;span&gt;omp&lt;/span&gt;&lt;span&gt;fish&lt;/span&gt;&lt;/div&gt;&lt;pre&gt;&lt;code&gt;code reports/week14_forecast_report.md
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;建议结构：&lt;/p&gt;
&lt;div&gt;&lt;div&gt;&lt;span&gt;omp&lt;/span&gt;&lt;span&gt;prompt&lt;/span&gt;&lt;/div&gt;&lt;pre&gt;&lt;code&gt;# Week 14 Forecast Report
&lt;h2&gt;Task&lt;a href=&quot;#task&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;预测目标：明天收益率方向&lt;/li&gt;
&lt;li&gt;特征：过去收益率、rolling mean、rolling volatility&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;Data split&lt;a href=&quot;#data-split&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;train 日期范围：&lt;/li&gt;
&lt;li&gt;validation 日期范围：&lt;/li&gt;
&lt;li&gt;test 日期范围：&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;Baseline&lt;a href=&quot;#baseline&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;naive baseline accuracy：&lt;/li&gt;
&lt;li&gt;naive baseline precision：&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;Models&lt;a href=&quot;#models&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;logistic regression：&lt;/li&gt;
&lt;li&gt;random forest：&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;Rolling validation&lt;a href=&quot;#rolling-validation&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;rolling window 设置：&lt;/li&gt;
&lt;li&gt;expanding window 设置：&lt;/li&gt;
&lt;li&gt;哪种结果更稳定？&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;Leakage checks&lt;a href=&quot;#leakage-checks&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;是否随机打乱：否&lt;/li&gt;
&lt;li&gt;标准化是否只在训练集 fit：是&lt;/li&gt;
&lt;li&gt;特征是否使用未来收益率：否&lt;/li&gt;
&lt;li&gt;测试集是否只最终评估：是&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;Conclusion&lt;a href=&quot;#conclusion&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;
&lt;/code&gt;&lt;ul&gt;&lt;code&gt;
&lt;li&gt;模型是否明显优于 baseline？&lt;/li&gt;
&lt;li&gt;结果是否稳定？&lt;/li&gt;
&lt;/code&gt;&lt;li&gt;&lt;code&gt;下一步如果要做回测，还缺什么？
&lt;/code&gt;&lt;/li&gt;&lt;/ul&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;h2&gt;13. 文件布局检查&lt;/h2&gt;
&lt;p&gt;执行：&lt;/p&gt;
&lt;div&gt;&lt;div&gt;&lt;span&gt;omp&lt;/span&gt;&lt;span&gt;fish&lt;/span&gt;&lt;/div&gt;&lt;pre&gt;&lt;code&gt;pwd
ls
ls data src reports figures
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;至少应有：&lt;/p&gt;
&lt;div&gt;&lt;div&gt;&lt;span&gt;omp&lt;/span&gt;&lt;span&gt;prompt&lt;/span&gt;&lt;/div&gt;&lt;pre&gt;&lt;code&gt;data/prices.csv
src/time_series_validation.py
figures/split.png
figures/rolling_validation_scores.png
reports/week14_forecast_report.md
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;h2&gt;14. 练习&lt;/h2&gt;
&lt;ol&gt;
&lt;li&gt;把预测目标改成“未来 5 日收益率是否大于 0”。注意标签和特征时间关系。&lt;/li&gt;
&lt;li&gt;把 &lt;code&gt;train_size&lt;/code&gt; 从 180 改成 120、240，观察 rolling validation 是否变化。&lt;/li&gt;
&lt;li&gt;添加一个新特征：过去 10 日收益率 &lt;code&gt;return_10d&lt;/code&gt;。&lt;/li&gt;
&lt;li&gt;比较 logistic regression 和 random forest，写出哪个更稳定，而不是只写哪个最高。&lt;/li&gt;
&lt;li&gt;故意写一个泄露特征 &lt;code&gt;future_return = return_1d.shift(-1)&lt;/code&gt;，看分数是否异常变高，然后删掉它并在报告中解释为什么不能用。&lt;/li&gt;
&lt;li&gt;把 &lt;code&gt;random_state&lt;/code&gt; 改掉，观察随机森林结果是否明显变化。&lt;/li&gt;
&lt;/ol&gt;
&lt;h2&gt;15. 验收检查&lt;/h2&gt;
&lt;p&gt;在项目根目录执行：&lt;/p&gt;
&lt;div&gt;&lt;div&gt;&lt;span&gt;omp&lt;/span&gt;&lt;span&gt;fish&lt;/span&gt;&lt;/div&gt;&lt;pre&gt;&lt;code&gt;source .venv/bin/activate.fish
python src/time_series_validation.py
python -c &quot;from pathlib import Path; required = [&apos;data/prices.csv&apos;, &apos;src/time_series_validation.py&apos;, &apos;figures/split.png&apos;, &apos;figures/rolling_validation_scores.png&apos;, &apos;reports/week14_forecast_report.md&apos;]; missing = [p for p in required if not Path(p).exists()]; print(&apos;missing:&apos;, missing); raise SystemExit(1 if missing else 0)&quot;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;通过标准：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;不使用随机打乱切分。&lt;/li&gt;
&lt;li&gt;至少实现 naive baseline 和一个机器学习模型。&lt;/li&gt;
&lt;li&gt;至少有 rolling 或 expanding window 的多窗口结果。&lt;/li&gt;
&lt;li&gt;报告中有明确的数据泄露检查。&lt;/li&gt;
&lt;li&gt;能解释为什么测试集不能反复用于调参。&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;16. 常见错误&lt;/h2&gt;
&lt;h3&gt;错误 1：直接使用 &lt;code&gt;train_test_split&lt;/code&gt; 默认随机划分&lt;/h3&gt;
&lt;p&gt;时间序列任务不要默认随机。你可以不用 &lt;code&gt;train_test_split&lt;/code&gt;，直接用 &lt;code&gt;iloc&lt;/code&gt; 按时间切。&lt;/p&gt;
&lt;h3&gt;错误 2：模型分数高得不正常&lt;/h3&gt;
&lt;p&gt;优先检查：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;特征里是否有未来收益率？&lt;/li&gt;
&lt;li&gt;是否全数据标准化后再切分？&lt;/li&gt;
&lt;li&gt;是否把测试集用于多次调参？&lt;/li&gt;
&lt;li&gt;标签是否和特征同一天，导致预测任务变成已知当天涨跌？&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;错误 3：rolling window 前面缺失太多&lt;/h3&gt;
&lt;p&gt;窗口越大，需要的历史越多。可以减少窗口，或接受前几行因信息不足而被删除。&lt;/p&gt;
&lt;h3&gt;错误 4：precision 报 warning&lt;/h3&gt;
&lt;p&gt;如果模型全预测为 0，就没有正类预测。示例代码用 &lt;code&gt;zero_division=0&lt;/code&gt; 避免中断，但报告里要说明模型可能没有学到有效信号。&lt;/p&gt;
&lt;h3&gt;错误 5：只报告最高分&lt;/h3&gt;
&lt;p&gt;只写最高分没有意义。你需要报告窗口间稳定性、是否优于 baseline、是否可能泄露。&lt;/p&gt;
&lt;h2&gt;17. 下一步&lt;/h2&gt;
&lt;p&gt;进入 Week 15：&lt;a href=&quot;./week15-quant-backtest-mini.md&quot;&gt;mini quant backtest：动量规则、交易成本与风险指标&lt;/a&gt;。下一周会把本周的“预测/信号”变成持仓和策略收益，并加入交易成本。&lt;/p&gt;
&lt;p&gt;plain &lt;/p&gt;&lt;/article&gt;&lt;p&gt;&lt;/p&gt;
  &lt;/div&gt;
&lt;/section&gt;</content:encoded><category>category:工具</category><category>category:量化研究</category><category>tag:时间序列</category><category>tag:walk-forward</category><category>tag:leakage</category><category>tag:validation</category></item><item><title>Week 13：时间序列基础、收益率与滚动统计</title><link>https://39miku.space/post/week13-time-series-basics</link><guid isPermaLink="false">week13-time-series-basics</guid><description>Week 13：时间序列基础、收益率与滚动统计。fish-first 终端教学，面向 CachyOS、VS Code、uv 和 Python 学习路线。</description><pubDate>Sun, 26 Jul 2026 05:00:00 GMT</pubDate><content:encoded>
&lt;section&gt;
  &lt;div&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;strong&gt;Oh My Pi&lt;/strong&gt; / weekly tutorial / week13-time-series-basics&lt;/span&gt;&lt;/div&gt;
  &lt;div&gt;
    &lt;div&gt;&lt;span&gt;miku@cachyos&lt;/span&gt;&lt;span&gt;:~/Code/python-learning&lt;/span&gt;&lt;span&gt;$&lt;/span&gt; &lt;span&gt;omp teach week13-time-series-basics --fish-first --step-by-step&lt;/span&gt;&lt;/div&gt;
    &lt;div&gt;
      &lt;div&gt;&lt;span&gt;source&lt;/span&gt;&lt;strong&gt;520 行教学文档&lt;/strong&gt;&lt;/div&gt;
      &lt;div&gt;&lt;span&gt;week&lt;/span&gt;&lt;strong&gt;Week 13&lt;/strong&gt;&lt;/div&gt;
      &lt;div&gt;&lt;span&gt;shell&lt;/span&gt;&lt;strong&gt;fish-first 命令版&lt;/strong&gt;&lt;/div&gt;
      &lt;div&gt;&lt;span&gt;backlink&lt;/span&gt;&lt;strong&gt;&lt;a href=&quot;/post/ustc-stat-ai-quant-plan/&quot;&gt;20 周计划&lt;/a&gt;&lt;/strong&gt;&lt;/div&gt;
    &lt;/div&gt;
    &lt;article&gt;
&lt;h1&gt;Week 13：时间序列基础、收益率与滚动统计&lt;/h1&gt;
&lt;blockquote&gt;
&lt;p&gt;返回总计划：&lt;a href=&quot;/post/ustc-stat-ai-quant-plan/&quot;&gt;USTC 统计 + AI / 量化 20 周成长计划&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;本周目标不是马上预测股价，而是先学会把价格序列变成可以分析的时间序列数据：收益率、滚动均值、滚动波动率、自相关、平稳性直觉，以及最基础的画图检查。&lt;/p&gt;
&lt;p&gt;本文命令默认使用 &lt;strong&gt;fish shell&lt;/strong&gt;。如果你正在 CachyOS 的终端里跟做，虚拟环境激活命令必须使用 fish 版本：&lt;/p&gt;
&lt;div&gt;&lt;div&gt;&lt;span&gt;omp&lt;/span&gt;&lt;span&gt;fish&lt;/span&gt;&lt;/div&gt;&lt;pre&gt;&lt;code&gt;source .venv/bin/activate.fish
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;不要把 Bash / Zsh 的激活脚本直接拿到 fish 里执行。&lt;/p&gt;
&lt;p&gt;&lt;a&gt;&lt;/a&gt;&lt;/p&gt;
&lt;h2&gt;0. 本周详细教学：语法、规范、验收&lt;/h2&gt;
&lt;blockquote&gt;
&lt;p&gt;本节不是追加在尾部的复习，而是本周正文的入口。先读这里，再做后面的命令和项目。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h3&gt;0.1 本周真正要学会什么&lt;/h3&gt;
&lt;table&gt;
&lt;thead&gt;&lt;tr&gt;&lt;th&gt;维度&lt;/th&gt;&lt;th&gt;要求&lt;/th&gt;&lt;/tr&gt;&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;&lt;td&gt;知识点&lt;/td&gt;&lt;td&gt;return、rolling、autocorrelation、时间索引&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;代码语法&lt;/td&gt;&lt;td&gt;能从空文件写出本周核心脚本，而不是只复制运行&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;程序规范&lt;/td&gt;&lt;td&gt;函数拆分、路径清楚、输入输出明确、错误能解释&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;交付物&lt;/td&gt;&lt;td&gt;&lt;code&gt;notebooks/week13_time_series_intro.ipynb&lt;/code&gt;&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;验收方式&lt;/td&gt;&lt;td&gt;从 fish 终端运行命令，得到可复查的文件或指标&lt;/td&gt;&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;h3&gt;0.2 代码语法精讲&lt;/h3&gt;
&lt;p&gt;下面的代码不是最终答案，而是本周必须理解的最小骨架：&lt;/p&gt;
&lt;div&gt;&lt;div&gt;&lt;span&gt;omp&lt;/span&gt;&lt;span&gt;python&lt;/span&gt;&lt;/div&gt;&lt;pre&gt;&lt;code&gt;df = df.sort_values(&quot;date&quot;)
df[&quot;ret&quot;] = df[&quot;close&quot;].pct_change()
df[&quot;log_ret&quot;] = np.log(df[&quot;close&quot;]).diff()
df[&quot;vol_20&quot;] = df[&quot;ret&quot;].rolling(20).std()
df[&quot;mom_20&quot;] = df[&quot;close&quot;].pct_change(20)
print(df.tail())
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;读代码时按四步检查：输入从哪里来；中间变量的类型和 shape 是什么；函数或脚本输出什么；哪些错误应该显式报出来。&lt;/p&gt;
&lt;h3&gt;0.3 本周程序规范&lt;/h3&gt;
&lt;ul&gt;&lt;li&gt;所有路径用相对路径或 `pathlib.Path`，不要写死 `/home/miku/...`。&lt;/li&gt;&lt;li&gt;核心逻辑进 `src/`，notebook 只做探索和解释。&lt;/li&gt;&lt;li&gt;每个脚本能从 fish 终端运行，并在 README 写出命令。&lt;/li&gt;&lt;li&gt;输出必须落盘到 `reports/`、`figures/` 或 `outputs/`，不能只在屏幕上看。&lt;/li&gt;&lt;/ul&gt;
&lt;h3&gt;0.4 本周练习分层&lt;/h3&gt;
&lt;table&gt;
&lt;thead&gt;&lt;tr&gt;&lt;th&gt;层级&lt;/th&gt;&lt;th&gt;任务&lt;/th&gt;&lt;th&gt;不合格表现&lt;/th&gt;&lt;th&gt;合格验收&lt;/th&gt;&lt;/tr&gt;&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;&lt;td&gt;最小练习&lt;/td&gt;&lt;td&gt;手写上面的最小骨架&lt;/td&gt;&lt;td&gt;只在 notebook 里运行&lt;/td&gt;&lt;td&gt;终端运行成功&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;标准练习&lt;/td&gt;&lt;td&gt;把逻辑拆成函数/模块&lt;/td&gt;&lt;td&gt;一个大脚本从头写到尾&lt;/td&gt;&lt;td&gt;至少 2 个函数，职责清楚&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;项目练习&lt;/td&gt;&lt;td&gt;生成本周交付物 &lt;code&gt;notebooks/week13_time_series_intro.ipynb&lt;/code&gt;&lt;/td&gt;&lt;td&gt;只有屏幕输出&lt;/td&gt;&lt;td&gt;文件落盘，可复查&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;复盘练习&lt;/td&gt;&lt;td&gt;写 3 个错误和修复&lt;/td&gt;&lt;td&gt;只写“已解决”&lt;/td&gt;&lt;td&gt;写清报错、原因、修复、预防&lt;/td&gt;&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;h3&gt;0.5 本周和主线的连接&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;回到总计划：&lt;a href=&quot;/post/ustc-stat-ai-quant-plan/#week-plan&quot;&gt;USTC AI / Quant 练习手册&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;查详细练习索引：&lt;a href=&quot;/post/ustc-stat-ai-quant-plan/#deep-practice-appendix&quot;&gt;技术练习详解&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;查质量评分：&lt;a href=&quot;/post/ustc-stat-ai-quant-plan/#quality-final-gates&quot;&gt;最终质量门槛&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;1. 本周学习目标&lt;/h2&gt;
&lt;p&gt;完成本周后，你应该能回答这些问题：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;为什么量化里通常分析收益率，而不是直接分析价格？&lt;/li&gt;
&lt;li&gt;简单收益率和对数收益率有什么区别？&lt;/li&gt;
&lt;li&gt;rolling mean / rolling volatility 在看什么？&lt;/li&gt;
&lt;li&gt;什么叫“时间序列不平稳”，为什么金融价格经常不适合直接建模？&lt;/li&gt;
&lt;li&gt;如何用图和几个简单统计量检查一条序列？&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;本周交付物建议为：&lt;/p&gt;
&lt;div&gt;&lt;div&gt;&lt;span&gt;omp&lt;/span&gt;&lt;span&gt;prompt&lt;/span&gt;&lt;/div&gt;&lt;pre&gt;&lt;code&gt;week13-time-series-basics/
├── data/
│   └── prices.csv
├── figures/
│   ├── price_and_returns.png
│   ├── rolling_stats.png
│   └── autocorrelation.png
├── notebooks/
│   └── week13_time_series_intro.ipynb
├── reports/
│   └── week13_time_series_notes.md
├── src/
│   └── week13_time_series_basics.py
├── pyproject.toml
└── README.md
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;如果你还不熟悉 notebook，可以先用 &lt;code&gt;src/week13_time_series_basics.py&lt;/code&gt; 写脚本；等脚本跑通后，再把关键图和解释整理到 notebook 或报告里。&lt;/p&gt;
&lt;h2&gt;2. 前置条件&lt;/h2&gt;
&lt;p&gt;你需要已经具备：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;CachyOS / Arch 系 Linux 的基本终端操作。&lt;/li&gt;
&lt;li&gt;VS Code 能打开项目目录。&lt;/li&gt;
&lt;li&gt;&lt;code&gt;uv&lt;/code&gt; 已安装。&lt;/li&gt;
&lt;li&gt;Python 虚拟环境能正常创建和激活。&lt;/li&gt;
&lt;li&gt;已经学过 &lt;code&gt;pandas&lt;/code&gt;、&lt;code&gt;numpy&lt;/code&gt;、&lt;code&gt;matplotlib&lt;/code&gt; 的基础用法。&lt;/li&gt;
&lt;li&gt;能理解均值、方差、标准差、相关系数这些统计概念。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;先进入统一学习目录：&lt;/p&gt;
&lt;div&gt;&lt;div&gt;&lt;span&gt;omp&lt;/span&gt;&lt;span&gt;fish&lt;/span&gt;&lt;/div&gt;&lt;pre&gt;&lt;code&gt;mkdir -p ~/Code/ustc-stat-ai-quant
cd ~/Code/ustc-stat-ai-quant
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;解释：&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;命令&lt;/th&gt;
&lt;th&gt;含义&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;&lt;tr&gt;
&lt;td&gt;&lt;code&gt;mkdir -p&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;创建目录；目录已存在时不报错&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;~/Code/ustc-stat-ai-quant&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;建议保存 20 周项目的总目录&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;cd&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;进入该目录，后续命令都在这里执行&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;&lt;/table&gt;
&lt;h2&gt;3. 创建 Week 13 项目&lt;/h2&gt;
&lt;div&gt;&lt;div&gt;&lt;span&gt;omp&lt;/span&gt;&lt;span&gt;fish&lt;/span&gt;&lt;/div&gt;&lt;pre&gt;&lt;code&gt;mkdir week13-time-series-basics
cd week13-time-series-basics
uv init
uv venv
source .venv/bin/activate.fish
uv add pandas numpy matplotlib statsmodels jupyter ipykernel
mkdir -p data src notebooks reports figures
code .
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;逐句解释：&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;命令&lt;/th&gt;
&lt;th&gt;作用&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;&lt;tr&gt;
&lt;td&gt;&lt;code&gt;mkdir week13-time-series-basics&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;为本周单独建项目，不和其它周混在一起&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;uv init&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;生成 &lt;code&gt;pyproject.toml&lt;/code&gt;、&lt;code&gt;README.md&lt;/code&gt; 等项目文件&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;uv venv&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;创建当前项目专用的 &lt;code&gt;.venv&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;source .venv/bin/activate.fish&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;在 fish shell 中激活虚拟环境&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;uv add ...&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;安装数据分析、画图、时间序列相关依赖&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;mkdir -p ...&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;创建数据、代码、报告、图片目录&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;code .&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;用 VS Code 打开当前项目&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;&lt;/table&gt;
&lt;p&gt;检查 Python 是否来自当前项目：&lt;/p&gt;
&lt;div&gt;&lt;div&gt;&lt;span&gt;omp&lt;/span&gt;&lt;span&gt;fish&lt;/span&gt;&lt;/div&gt;&lt;pre&gt;&lt;code&gt;which python
python --version
python -c &quot;import pandas as pd; import numpy as np; import matplotlib; print(&apos;ok&apos;)&quot;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;如果 &lt;code&gt;which python&lt;/code&gt; 的输出包含 &lt;code&gt;.venv/bin/python&lt;/code&gt;，说明环境正确。&lt;/p&gt;
&lt;h2&gt;4. 准备一份练习价格数据&lt;/h2&gt;
&lt;p&gt;真实市场数据可以以后再接入。本周先生成一份模拟价格数据，重点练概念和代码流程。&lt;/p&gt;
&lt;p&gt;在 VS Code 中创建 &lt;code&gt;src/make_sample_prices.py&lt;/code&gt;，写入下面代码：&lt;/p&gt;
&lt;div&gt;&lt;div&gt;&lt;span&gt;omp&lt;/span&gt;&lt;span&gt;python&lt;/span&gt;&lt;/div&gt;&lt;pre&gt;&lt;code&gt;from pathlib import Path
&lt;p&gt;import numpy as np
import pandas as pd&lt;/p&gt;
&lt;p&gt;rng = np.random.default_rng(42)
dates = pd.bdate_range(“2023-01-02”, periods=260)&lt;/p&gt;
&lt;h1&gt;模拟日对数收益：小正漂移 + 随机波动 + 少数冲击日&lt;a href=&quot;#模拟日对数收益小正漂移--随机波动--少数冲击日&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/h1&gt;
&lt;p&gt;log_returns = rng.normal(loc=0.0004, scale=0.012, size=len(dates))
shock_idx = rng.choice(len(dates), size=6, replace=False)
log_returns[shock_idx] += rng.normal(loc=-0.025, scale=0.02, size=len(shock_idx))&lt;/p&gt;
&lt;p&gt;price = 100 * np.exp(np.cumsum(log_returns))&lt;/p&gt;
&lt;/code&gt;&lt;p&gt;&lt;code&gt;out = pd.DataFrame({“date”: dates, “close”: price})
Path(“data”).mkdir(exist_ok=True)
out.to_csv(“data/prices.csv”, index=False)
print(out.head())
print(“saved to data/prices.csv”)
&lt;/code&gt;&lt;/p&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;&lt;/p&gt;
&lt;p&gt;运行：&lt;/p&gt;
&lt;div&gt;&lt;div&gt;&lt;span&gt;omp&lt;/span&gt;&lt;span&gt;fish&lt;/span&gt;&lt;/div&gt;&lt;pre&gt;&lt;code&gt;python src/make_sample_prices.py
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;检查文件：&lt;/p&gt;
&lt;div&gt;&lt;div&gt;&lt;span&gt;omp&lt;/span&gt;&lt;span&gt;fish&lt;/span&gt;&lt;/div&gt;&lt;pre&gt;&lt;code&gt;python -c &quot;import pandas as pd; df = pd.read_csv(&apos;data/prices.csv&apos;); print(df.head()); print(df.tail())&quot;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;这里使用的是模拟数据，所以不能拿结论解释真实市场。它的作用是让你安全地练习时间序列处理。&lt;/p&gt;
&lt;h2&gt;5. 读取价格并设置时间索引&lt;/h2&gt;
&lt;p&gt;创建 &lt;code&gt;src/week13_time_series_basics.py&lt;/code&gt;，先写入读取部分：&lt;/p&gt;
&lt;div&gt;&lt;div&gt;&lt;span&gt;omp&lt;/span&gt;&lt;span&gt;python&lt;/span&gt;&lt;/div&gt;&lt;pre&gt;&lt;code&gt;from pathlib import Path
&lt;p&gt;import matplotlib.pyplot as plt
import numpy as np
import pandas as pd
from statsmodels.graphics.tsaplots import plot_acf&lt;/p&gt;
&lt;p&gt;FIGURE_DIR = Path(“figures”)
FIGURE_DIR.mkdir(exist_ok=True)&lt;/p&gt;
&lt;p&gt;prices = pd.read_csv(“data/prices.csv”, parse_dates=[“date”])
prices = prices.sort_values(“date”).set_index(“date”)&lt;/p&gt;
&lt;/code&gt;&lt;p&gt;&lt;code&gt;print(prices.head())
print(prices.info())
&lt;/code&gt;&lt;/p&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;&lt;/p&gt;
&lt;p&gt;运行：&lt;/p&gt;
&lt;div&gt;&lt;div&gt;&lt;span&gt;omp&lt;/span&gt;&lt;span&gt;fish&lt;/span&gt;&lt;/div&gt;&lt;pre&gt;&lt;code&gt;python src/week13_time_series_basics.py
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;解释：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;parse_dates=[&quot;date&quot;]&lt;/code&gt;：把日期列解析成真正的时间类型。&lt;/li&gt;
&lt;li&gt;&lt;code&gt;sort_values(&quot;date&quot;)&lt;/code&gt;：时间序列必须按时间排序。&lt;/li&gt;
&lt;li&gt;&lt;code&gt;set_index(&quot;date&quot;)&lt;/code&gt;：把日期设为索引，后面 rolling 和画图更自然。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;时间序列第一条规则：&lt;strong&gt;先确认时间顺序，再做任何统计。&lt;/strong&gt;&lt;/p&gt;
&lt;h2&gt;6. 价格、简单收益率、对数收益率&lt;/h2&gt;
&lt;p&gt;在脚本中继续加入：&lt;/p&gt;
&lt;div&gt;&lt;div&gt;&lt;span&gt;omp&lt;/span&gt;&lt;span&gt;python&lt;/span&gt;&lt;/div&gt;&lt;pre&gt;&lt;code&gt;prices[&quot;simple_return&quot;] = prices[&quot;close&quot;].pct_change()
prices[&quot;log_return&quot;] = np.log(prices[&quot;close&quot;]).diff()
&lt;/code&gt;&lt;p&gt;&lt;code&gt;print(prices[[“close”, “simple_return”, “log_return”]].head(10))
print(prices[[“simple_return”, “log_return”]].describe())
&lt;/code&gt;&lt;/p&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;&lt;/p&gt;
&lt;p&gt;运行：&lt;/p&gt;
&lt;div&gt;&lt;div&gt;&lt;span&gt;omp&lt;/span&gt;&lt;span&gt;fish&lt;/span&gt;&lt;/div&gt;&lt;pre&gt;&lt;code&gt;python src/week13_time_series_basics.py
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;概念解释：&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;名称&lt;/th&gt;
&lt;th&gt;公式直觉&lt;/th&gt;
&lt;th&gt;常见用途&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;&lt;tr&gt;
&lt;td&gt;简单收益率&lt;/td&gt;
&lt;td&gt;今天价格相对昨天涨跌百分比&lt;/td&gt;
&lt;td&gt;回测中计算资产组合收益很直观&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;对数收益率&lt;/td&gt;
&lt;td&gt;价格取对数后做差&lt;/td&gt;
&lt;td&gt;数学上可加，时间序列建模时常用&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;&lt;/table&gt;
&lt;p&gt;为什么不用价格直接建模？&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;价格经常有趋势，均值随时间变化。&lt;/li&gt;
&lt;li&gt;价格尺度随时间变大，波动范围也可能变化。&lt;/li&gt;
&lt;li&gt;很多模型默认数据围绕稳定均值波动，价格往往不满足。&lt;/li&gt;
&lt;li&gt;收益率更接近“每天的相对变化”，通常比价格更适合做统计分析。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;注意：第一天没有前一天价格，所以收益率是缺失值。这是正常现象，不要强行填 0。&lt;/p&gt;
&lt;h2&gt;7. 画价格和收益率&lt;/h2&gt;
&lt;p&gt;继续加入画图代码：&lt;/p&gt;
&lt;div&gt;&lt;div&gt;&lt;span&gt;omp&lt;/span&gt;&lt;span&gt;python&lt;/span&gt;&lt;/div&gt;&lt;pre&gt;&lt;code&gt;fig, axes = plt.subplots(2, 1, figsize=(10, 7), sharex=True)
&lt;p&gt;prices[“close”].plot(ax=axes[0], title=“Simulated Close Price”)
axes[0].set_ylabel(“price”)&lt;/p&gt;
&lt;p&gt;prices[“simple_return”].plot(ax=axes[1], title=“Daily Simple Return”)
axes[1].axhline(0, color=“black”, linewidth=1)
axes[1].set_ylabel(“return”)&lt;/p&gt;
&lt;/code&gt;&lt;p&gt;&lt;code&gt;fig.tight_layout()
fig.savefig(FIGURE_DIR / “price_and_returns.png”, dpi=150)
plt.close(fig)
&lt;/code&gt;&lt;/p&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;&lt;/p&gt;
&lt;p&gt;运行并查看图片：&lt;/p&gt;
&lt;div&gt;&lt;div&gt;&lt;span&gt;omp&lt;/span&gt;&lt;span&gt;fish&lt;/span&gt;&lt;/div&gt;&lt;pre&gt;&lt;code&gt;python src/week13_time_series_basics.py
xdg-open figures/price_and_returns.png
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;看图时不要只问“涨了吗”，要问：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;价格是否有明显趋势？&lt;/li&gt;
&lt;li&gt;收益率是否围绕 0 上下波动？&lt;/li&gt;
&lt;li&gt;是否有异常大的正收益或负收益？&lt;/li&gt;
&lt;li&gt;波动是否集中在某些时间段？&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;金融数据经常不是“均匀随机噪声”，而是会出现波动聚集：一段时间很平静，另一段时间连续剧烈波动。&lt;/p&gt;
&lt;h2&gt;8. rolling mean 与 rolling volatility&lt;/h2&gt;
&lt;p&gt;继续加入：&lt;/p&gt;
&lt;div&gt;&lt;div&gt;&lt;span&gt;omp&lt;/span&gt;&lt;span&gt;python&lt;/span&gt;&lt;/div&gt;&lt;pre&gt;&lt;code&gt;window = 20
prices[&quot;rolling_mean_20&quot;] = prices[&quot;simple_return&quot;].rolling(window).mean()
prices[&quot;rolling_vol_20&quot;] = prices[&quot;simple_return&quot;].rolling(window).std()
&lt;p&gt;fig, axes = plt.subplots(2, 1, figsize=(10, 7), sharex=True)&lt;/p&gt;
&lt;p&gt;prices[“rolling_mean_20”].plot(ax=axes[0], title=“20-Day Rolling Mean of Returns”)
axes[0].axhline(0, color=“black”, linewidth=1)
axes[0].set_ylabel(“mean”)&lt;/p&gt;
&lt;p&gt;prices[“rolling_vol_20”].plot(ax=axes[1], title=“20-Day Rolling Volatility of Returns”)
axes[1].set_ylabel(“volatility”)&lt;/p&gt;
&lt;p&gt;fig.tight_layout()
fig.savefig(FIGURE_DIR / “rolling_stats.png”, dpi=150)
plt.close(fig)&lt;/p&gt;
&lt;/code&gt;&lt;p&gt;&lt;code&gt;print(prices[[“simple_return”, “rolling_mean_20”, “rolling_vol_20”]].tail())
&lt;/code&gt;&lt;/p&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;&lt;/p&gt;
&lt;p&gt;运行：&lt;/p&gt;
&lt;div&gt;&lt;div&gt;&lt;span&gt;omp&lt;/span&gt;&lt;span&gt;fish&lt;/span&gt;&lt;/div&gt;&lt;pre&gt;&lt;code&gt;python src/week13_time_series_basics.py
xdg-open figures/rolling_stats.png
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;解释：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;rolling(20).mean()&lt;/code&gt;：每一天都看过去 20 个交易日的平均收益。&lt;/li&gt;
&lt;li&gt;&lt;code&gt;rolling(20).std()&lt;/code&gt;：每一天都看过去 20 个交易日的波动率。&lt;/li&gt;
&lt;li&gt;前 19 天没有足够窗口，所以结果是缺失值，这是合理的。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;量化里 rolling 指标非常重要，因为策略在某一天做决策时，只能使用这一天之前已经发生的数据。以后做回测时，要特别检查 rolling 特征有没有偷看未来。&lt;/p&gt;
&lt;h2&gt;9. 平稳性直觉&lt;/h2&gt;
&lt;p&gt;你不需要本周就精通严谨的单位根检验，但必须先建立直觉。&lt;/p&gt;
&lt;p&gt;粗略地说，一条平稳序列应该满足：&lt;/p&gt;
&lt;div&gt;&lt;div&gt;&lt;span&gt;omp&lt;/span&gt;&lt;span&gt;prompt&lt;/span&gt;&lt;/div&gt;&lt;pre&gt;&lt;code&gt;均值大致稳定
波动范围大致稳定
自相关结构大致稳定
没有明显长期趋势
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;价格通常不像平稳序列：&lt;/p&gt;
&lt;div&gt;&lt;div&gt;&lt;span&gt;omp&lt;/span&gt;&lt;span&gt;prompt&lt;/span&gt;&lt;/div&gt;&lt;pre&gt;&lt;code&gt;价格可能长期上升或下降
价格水平改变后，均值不再稳定
价格的绝对尺度会影响波动大小
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;收益率通常更接近平稳，但也不完美：&lt;/p&gt;
&lt;div&gt;&lt;div&gt;&lt;span&gt;omp&lt;/span&gt;&lt;span&gt;prompt&lt;/span&gt;&lt;/div&gt;&lt;pre&gt;&lt;code&gt;收益率均值更接近稳定
收益率仍可能有波动聚集
极端事件会破坏简单模型假设
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;在脚本里加入一个简单比较：&lt;/p&gt;
&lt;div&gt;&lt;div&gt;&lt;span&gt;omp&lt;/span&gt;&lt;span&gt;python&lt;/span&gt;&lt;/div&gt;&lt;pre&gt;&lt;code&gt;first_half = prices.iloc[: len(prices) // 2]
second_half = prices.iloc[len(prices) // 2 :]
&lt;/code&gt;&lt;p&gt;&lt;code&gt;summary = pd.DataFrame(
{
“price_first_half”: first_half[“close”].describe(),
“price_second_half”: second_half[“close”].describe(),
“return_first_half”: first_half[“simple_return”].describe(),
“return_second_half”: second_half[“simple_return”].describe(),
}
)
print(summary)
&lt;/code&gt;&lt;/p&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;&lt;/p&gt;
&lt;p&gt;运行：&lt;/p&gt;
&lt;div&gt;&lt;div&gt;&lt;span&gt;omp&lt;/span&gt;&lt;span&gt;fish&lt;/span&gt;&lt;/div&gt;&lt;pre&gt;&lt;code&gt;python src/week13_time_series_basics.py
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;观察重点：价格两段的均值可能差很多；收益率两段的均值通常更接近，但标准差仍可能变化。&lt;/p&gt;
&lt;h2&gt;10. 自相关：今天和过去有关吗&lt;/h2&gt;
&lt;p&gt;自相关回答的是：今天的收益率是否和前几天的收益率有关系。&lt;/p&gt;
&lt;p&gt;继续加入：&lt;/p&gt;
&lt;div&gt;&lt;div&gt;&lt;span&gt;omp&lt;/span&gt;&lt;span&gt;python&lt;/span&gt;&lt;/div&gt;&lt;pre&gt;&lt;code&gt;returns = prices[&quot;simple_return&quot;].dropna()
&lt;p&gt;print(“lag 1 autocorrelation:”, returns.autocorr(lag=1))
print(“lag 5 autocorrelation:”, returns.autocorr(lag=5))
print(“lag 20 autocorrelation:”, returns.autocorr(lag=20))&lt;/p&gt;
&lt;/code&gt;&lt;p&gt;&lt;code&gt;fig = plot_acf(returns, lags=30)
fig.set_size_inches(10, 5)
fig.tight_layout()
fig.savefig(FIGURE_DIR / “autocorrelation.png”, dpi=150)
plt.close(fig)
&lt;/code&gt;&lt;/p&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;&lt;/p&gt;
&lt;p&gt;运行：&lt;/p&gt;
&lt;div&gt;&lt;div&gt;&lt;span&gt;omp&lt;/span&gt;&lt;span&gt;fish&lt;/span&gt;&lt;/div&gt;&lt;pre&gt;&lt;code&gt;python src/week13_time_series_basics.py
xdg-open figures/autocorrelation.png
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;解释：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;如果 lag 1 自相关明显为正，可能表示短期动量。&lt;/li&gt;
&lt;li&gt;如果 lag 1 自相关明显为负，可能表示短期反转。&lt;/li&gt;
&lt;li&gt;如果大部分自相关接近 0，说明简单线性关系很弱。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;不要看到一点点相关就兴奋。金融时间序列噪声很大，样本外稳定性比样本内漂亮图更重要。&lt;/p&gt;
&lt;h2&gt;11. 整理报告&lt;/h2&gt;
&lt;p&gt;创建 &lt;code&gt;reports/week13_time_series_notes.md&lt;/code&gt;，建议包含：&lt;/p&gt;
&lt;div&gt;&lt;div&gt;&lt;span&gt;omp&lt;/span&gt;&lt;span&gt;prompt&lt;/span&gt;&lt;/div&gt;&lt;pre&gt;&lt;code&gt;# Week 13 Time Series Notes
&lt;h2&gt;Data&lt;a href=&quot;#data&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;数据来源：模拟价格数据&lt;/li&gt;
&lt;li&gt;时间范围：2023 年 260 个工作日&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;What I computed&lt;a href=&quot;#what-i-computed&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;close price&lt;/li&gt;
&lt;li&gt;simple return&lt;/li&gt;
&lt;li&gt;log return&lt;/li&gt;
&lt;li&gt;20-day rolling mean&lt;/li&gt;
&lt;li&gt;20-day rolling volatility&lt;/li&gt;
&lt;li&gt;autocorrelation&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;Observations&lt;a href=&quot;#observations&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;价格是否有趋势？&lt;/li&gt;
&lt;li&gt;收益率是否围绕 0 波动？&lt;/li&gt;
&lt;li&gt;rolling volatility 是否有明显变化？&lt;/li&gt;
&lt;li&gt;自相关是否明显？&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;Stationarity intuition&lt;a href=&quot;#stationarity-intuition&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;为什么价格更不平稳？&lt;/li&gt;
&lt;li&gt;为什么收益率更适合建模？&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;Questions&lt;a href=&quot;#questions&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;
&lt;/code&gt;&lt;ul&gt;&lt;code&gt;
&lt;/code&gt;&lt;li&gt;&lt;code&gt;如果换成真实股票或指数，结果会怎样？
&lt;/code&gt;&lt;/li&gt;&lt;/ul&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;用 VS Code 创建和编辑报告：&lt;/p&gt;
&lt;div&gt;&lt;div&gt;&lt;span&gt;omp&lt;/span&gt;&lt;span&gt;fish&lt;/span&gt;&lt;/div&gt;&lt;pre&gt;&lt;code&gt;code reports/week13_time_series_notes.md
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;h2&gt;12. 文件布局检查&lt;/h2&gt;
&lt;p&gt;运行：&lt;/p&gt;
&lt;div&gt;&lt;div&gt;&lt;span&gt;omp&lt;/span&gt;&lt;span&gt;fish&lt;/span&gt;&lt;/div&gt;&lt;pre&gt;&lt;code&gt;pwd
ls
ls data src reports figures
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;理想结果类似：&lt;/p&gt;
&lt;div&gt;&lt;div&gt;&lt;span&gt;omp&lt;/span&gt;&lt;span&gt;prompt&lt;/span&gt;&lt;/div&gt;&lt;pre&gt;&lt;code&gt;week13-time-series-basics/
data  figures  notebooks  pyproject.toml  README.md  reports  src
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;你最终至少应有：&lt;/p&gt;
&lt;div&gt;&lt;div&gt;&lt;span&gt;omp&lt;/span&gt;&lt;span&gt;prompt&lt;/span&gt;&lt;/div&gt;&lt;pre&gt;&lt;code&gt;data/prices.csv
src/make_sample_prices.py
src/week13_time_series_basics.py
figures/price_and_returns.png
figures/rolling_stats.png
figures/autocorrelation.png
reports/week13_time_series_notes.md
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;h2&gt;13. 练习&lt;/h2&gt;
&lt;ol&gt;
&lt;li&gt;把 rolling window 从 20 改成 5、60，比较图像差异。&lt;/li&gt;
&lt;li&gt;分别计算简单收益率和对数收益率的均值、标准差、最小值、最大值。&lt;/li&gt;
&lt;li&gt;找出收益率绝对值最大的 5 天，检查这些天在价格图上是否明显。&lt;/li&gt;
&lt;li&gt;把模拟数据的随机种子从 42 改成 7，再生成一次，观察结论是否稳定。&lt;/li&gt;
&lt;li&gt;用 &lt;code&gt;pandas.Series.skew()&lt;/code&gt; 和 &lt;code&gt;pandas.Series.kurt()&lt;/code&gt; 查看收益率的偏度和峰度。&lt;/li&gt;
&lt;li&gt;试着下载一个真实指数或 ETF 的日线数据，再重复同样分析，并在报告中说明数据来源。&lt;/li&gt;
&lt;/ol&gt;
&lt;h2&gt;14. 验收检查&lt;/h2&gt;
&lt;p&gt;在项目根目录执行：&lt;/p&gt;
&lt;div&gt;&lt;div&gt;&lt;span&gt;omp&lt;/span&gt;&lt;span&gt;fish&lt;/span&gt;&lt;/div&gt;&lt;pre&gt;&lt;code&gt;source .venv/bin/activate.fish
python src/make_sample_prices.py
python src/week13_time_series_basics.py
python -c &quot;from pathlib import Path; required = [&apos;data/prices.csv&apos;, &apos;figures/price_and_returns.png&apos;, &apos;figures/rolling_stats.png&apos;, &apos;figures/autocorrelation.png&apos;, &apos;reports/week13_time_series_notes.md&apos;]; missing = [p for p in required if not Path(p).exists()]; print(&apos;missing:&apos;, missing); raise SystemExit(1 if missing else 0)&quot;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;通过标准：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;命令能顺利运行。&lt;/li&gt;
&lt;li&gt;&lt;code&gt;figures/&lt;/code&gt; 下有三张图片。&lt;/li&gt;
&lt;li&gt;报告能解释价格和收益率的差异。&lt;/li&gt;
&lt;li&gt;你能口头说明为什么时间序列不能随便随机打乱。&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;15. 常见错误&lt;/h2&gt;
&lt;h3&gt;错误 1：fish 中激活环境失败&lt;/h3&gt;
&lt;p&gt;错误原因通常是用了 Bash / Zsh 的激活脚本。&lt;/p&gt;
&lt;p&gt;fish 应使用：&lt;/p&gt;
&lt;div&gt;&lt;div&gt;&lt;span&gt;omp&lt;/span&gt;&lt;span&gt;fish&lt;/span&gt;&lt;/div&gt;&lt;pre&gt;&lt;code&gt;source .venv/bin/activate.fish
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;h3&gt;错误 2：收益率第一行是空值&lt;/h3&gt;
&lt;p&gt;这是正常的，因为第一天没有前一天价格。不要为了“看起来整齐”把它随便改成 0。&lt;/p&gt;
&lt;h3&gt;错误 3：rolling 前几行是空值&lt;/h3&gt;
&lt;p&gt;&lt;code&gt;rolling(20)&lt;/code&gt; 需要至少 20 个观测值。前 19 行没有完整窗口，所以是缺失值。&lt;/p&gt;
&lt;h3&gt;错误 4：图保存失败&lt;/h3&gt;
&lt;p&gt;先确认 &lt;code&gt;figures/&lt;/code&gt; 存在：&lt;/p&gt;
&lt;div&gt;&lt;div&gt;&lt;span&gt;omp&lt;/span&gt;&lt;span&gt;fish&lt;/span&gt;&lt;/div&gt;&lt;pre&gt;&lt;code&gt;mkdir -p figures
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;脚本里也建议写：&lt;/p&gt;
&lt;div&gt;&lt;div&gt;&lt;span&gt;omp&lt;/span&gt;&lt;span&gt;python&lt;/span&gt;&lt;/div&gt;&lt;pre&gt;&lt;code&gt;Path(&quot;figures&quot;).mkdir(exist_ok=True)
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;h3&gt;错误 5：把价格图看成策略收益&lt;/h3&gt;
&lt;p&gt;价格上涨不代表策略有效。策略必须明确什么时候买、什么时候卖、交易成本多少、样本外是否有效。这些将在 Week 15 和 Week 16 继续做。&lt;/p&gt;
&lt;h2&gt;16. 下一步&lt;/h2&gt;
&lt;p&gt;进入 Week 14：&lt;a href=&quot;./week14-time-series-validation.md&quot;&gt;时间序列验证、rolling window 与数据泄露检查&lt;/a&gt;。下一周重点不是模型多高级，而是避免把未来信息泄露给过去。&lt;/p&gt;
&lt;p&gt;plain &lt;/p&gt;&lt;/article&gt;&lt;p&gt;&lt;/p&gt;
  &lt;/div&gt;
&lt;/section&gt;</content:encoded><category>category:工具</category><category>category:量化研究</category><category>tag:时间序列</category><category>tag:returns</category><category>tag:rolling</category><category>tag:autocorrelation</category></item><item><title>Week 12：AI 项目复盘报告——实验表、Ablation、README 与 Limitations</title><link>https://39miku.space/post/week12-ai-project-review-report</link><guid isPermaLink="false">week12-ai-project-review-report</guid><description>Week 12：AI 项目复盘报告——实验表、Ablation、README 与 Limitations。fish-first 终端教学，面向 CachyOS、VS Code、uv 和 Python 学习路线。</description><pubDate>Sun, 26 Jul 2026 04:00:00 GMT</pubDate><content:encoded>
&lt;section&gt;
  &lt;div&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;strong&gt;Oh My Pi&lt;/strong&gt; / weekly tutorial / week12-ai-project-review-report&lt;/span&gt;&lt;/div&gt;
  &lt;div&gt;
    &lt;div&gt;&lt;span&gt;miku@cachyos&lt;/span&gt;&lt;span&gt;:~/Code/python-learning&lt;/span&gt;&lt;span&gt;$&lt;/span&gt; &lt;span&gt;omp teach week12-ai-project-review-report --fish-first --step-by-step&lt;/span&gt;&lt;/div&gt;
    &lt;div&gt;
      &lt;div&gt;&lt;span&gt;source&lt;/span&gt;&lt;strong&gt;436 行教学文档&lt;/strong&gt;&lt;/div&gt;
      &lt;div&gt;&lt;span&gt;week&lt;/span&gt;&lt;strong&gt;Week 12&lt;/strong&gt;&lt;/div&gt;
      &lt;div&gt;&lt;span&gt;shell&lt;/span&gt;&lt;strong&gt;fish-first 命令版&lt;/strong&gt;&lt;/div&gt;
      &lt;div&gt;&lt;span&gt;backlink&lt;/span&gt;&lt;strong&gt;&lt;a href=&quot;/post/ustc-stat-ai-quant-plan/&quot;&gt;20 周计划&lt;/a&gt;&lt;/strong&gt;&lt;/div&gt;
    &lt;/div&gt;
    &lt;article&gt;
&lt;h1&gt;Week 12：AI 项目复盘报告——实验表、Ablation、README 与 Limitations&lt;/h1&gt;
&lt;blockquote&gt;
&lt;p&gt;回到总路线：&lt;a href=&quot;/post/ustc-stat-ai-quant-plan/&quot;&gt;USTC 统计 AI / 量化 20 周成长计划&lt;/a&gt;&lt;br /&gt;本周目标对应计划中的 Week 12：让 AI 比赛或深度学习项目能被老师、面试官和未来的自己看懂。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;本教程默认你使用 &lt;strong&gt;CachyOS + fish shell + VS Code + uv + Python + Git&lt;/strong&gt;。所有命令默认是 fish。本周不再只是“跑模型”，而是把 Week 10 / Week 11 的项目整理成一份有证据、有对比、有局限性的复盘报告。&lt;/p&gt;
&lt;p&gt;&lt;a&gt;&lt;/a&gt;&lt;/p&gt;
&lt;h2&gt;0. 本周详细教学：语法、规范、验收&lt;/h2&gt;
&lt;blockquote&gt;
&lt;p&gt;本节不是追加在尾部的复习，而是本周正文的入口。先读这里，再做后面的命令和项目。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h3&gt;0.1 本周真正要学会什么&lt;/h3&gt;
&lt;table&gt;
&lt;thead&gt;&lt;tr&gt;&lt;th&gt;维度&lt;/th&gt;&lt;th&gt;要求&lt;/th&gt;&lt;/tr&gt;&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;&lt;td&gt;知识点&lt;/td&gt;&lt;td&gt;ablation、failure cases、limitations、solution.md&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;代码语法&lt;/td&gt;&lt;td&gt;能从空文件写出本周核心脚本，而不是只复制运行&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;程序规范&lt;/td&gt;&lt;td&gt;函数拆分、路径清楚、输入输出明确、错误能解释&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;交付物&lt;/td&gt;&lt;td&gt;&lt;code&gt;projects/ai-competition-review/solution.md&lt;/code&gt;&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;验收方式&lt;/td&gt;&lt;td&gt;从 fish 终端运行命令，得到可复查的文件或指标&lt;/td&gt;&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;h3&gt;0.2 代码语法精讲&lt;/h3&gt;
&lt;p&gt;下面的代码不是最终答案，而是本周必须理解的最小骨架：&lt;/p&gt;
&lt;div&gt;&lt;div&gt;&lt;span&gt;omp&lt;/span&gt;&lt;span&gt;python&lt;/span&gt;&lt;/div&gt;&lt;pre&gt;&lt;code&gt;rows = [
    {&quot;experiment&quot;: &quot;baseline&quot;, &quot;f1&quot;: 0.71, &quot;note&quot;: &quot;logreg&quot;},
    {&quot;experiment&quot;: &quot;+cleaning&quot;, &quot;f1&quot;: 0.75, &quot;note&quot;: &quot;remove leakage&quot;},
]
for row in rows:
    print(row)
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;读代码时按四步检查：输入从哪里来；中间变量的类型和 shape 是什么；函数或脚本输出什么；哪些错误应该显式报出来。&lt;/p&gt;
&lt;h3&gt;0.3 本周程序规范&lt;/h3&gt;
&lt;ul&gt;&lt;li&gt;所有路径用相对路径或 `pathlib.Path`，不要写死 `/home/miku/...`。&lt;/li&gt;&lt;li&gt;核心逻辑进 `src/`，notebook 只做探索和解释。&lt;/li&gt;&lt;li&gt;每个脚本能从 fish 终端运行，并在 README 写出命令。&lt;/li&gt;&lt;li&gt;输出必须落盘到 `reports/`、`figures/` 或 `outputs/`，不能只在屏幕上看。&lt;/li&gt;&lt;/ul&gt;
&lt;h3&gt;0.4 本周练习分层&lt;/h3&gt;
&lt;table&gt;
&lt;thead&gt;&lt;tr&gt;&lt;th&gt;层级&lt;/th&gt;&lt;th&gt;任务&lt;/th&gt;&lt;th&gt;不合格表现&lt;/th&gt;&lt;th&gt;合格验收&lt;/th&gt;&lt;/tr&gt;&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;&lt;td&gt;最小练习&lt;/td&gt;&lt;td&gt;手写上面的最小骨架&lt;/td&gt;&lt;td&gt;只在 notebook 里运行&lt;/td&gt;&lt;td&gt;终端运行成功&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;标准练习&lt;/td&gt;&lt;td&gt;把逻辑拆成函数/模块&lt;/td&gt;&lt;td&gt;一个大脚本从头写到尾&lt;/td&gt;&lt;td&gt;至少 2 个函数，职责清楚&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;项目练习&lt;/td&gt;&lt;td&gt;生成本周交付物 &lt;code&gt;projects/ai-competition-review/solution.md&lt;/code&gt;&lt;/td&gt;&lt;td&gt;只有屏幕输出&lt;/td&gt;&lt;td&gt;文件落盘，可复查&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;复盘练习&lt;/td&gt;&lt;td&gt;写 3 个错误和修复&lt;/td&gt;&lt;td&gt;只写“已解决”&lt;/td&gt;&lt;td&gt;写清报错、原因、修复、预防&lt;/td&gt;&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;h3&gt;0.5 本周和主线的连接&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;回到总计划：&lt;a href=&quot;/post/ustc-stat-ai-quant-plan/#week-plan&quot;&gt;USTC AI / Quant 练习手册&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;查详细练习索引：&lt;a href=&quot;/post/ustc-stat-ai-quant-plan/#deep-practice-appendix&quot;&gt;技术练习详解&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;查质量评分：&lt;a href=&quot;/post/ustc-stat-ai-quant-plan/#quality-final-gates&quot;&gt;最终质量门槛&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;1. 本周目标&lt;/h2&gt;
&lt;p&gt;完成后，你应该拥有：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;一份 &lt;code&gt;reports/project_review.md&lt;/code&gt;，说明任务、数据、贡献、baseline、改进、实验、结果、失败案例和收获。&lt;/li&gt;
&lt;li&gt;一张实验表，记录每次改动和指标。&lt;/li&gt;
&lt;li&gt;至少一个 ablation，对比“去掉某个改动后结果如何”。&lt;/li&gt;
&lt;li&gt;一个可读的 &lt;code&gt;README.md&lt;/code&gt;，让别人知道如何复现。&lt;/li&gt;
&lt;li&gt;明确的 limitations，不夸大项目结果。&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;本周核心能力是：用结构化证据说明你做了什么，而不是只说“我参加过 AI 比赛”。&lt;/p&gt;
&lt;h2&gt;2. 前置条件&lt;/h2&gt;
&lt;p&gt;你需要已经完成以下二者之一：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Week 10 的 PyTorch 小项目。&lt;/li&gt;
&lt;li&gt;Week 11 的 AI 比赛整理项目。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;你还需要有至少 2 到 3 条实验记录，例如：&lt;/p&gt;
&lt;div&gt;&lt;div&gt;&lt;span&gt;omp&lt;/span&gt;&lt;span&gt;prompt&lt;/span&gt;&lt;/div&gt;&lt;pre&gt;&lt;code&gt;baseline
baseline + feature engineering
baseline + feature engineering + tuned hyperparameters
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;先进入你要复盘的项目。如果还没有项目，可以新建一个复盘练习目录：&lt;/p&gt;
&lt;div&gt;&lt;div&gt;&lt;span&gt;omp&lt;/span&gt;&lt;span&gt;fish&lt;/span&gt;&lt;/div&gt;&lt;pre&gt;&lt;code&gt;mkdir -p ~/Code/ustc-ai/week12-ai-project-review
cd ~/Code/ustc-ai/week12-ai-project-review
uv init --name week12-ai-project-review
uv venv
source .venv/bin/activate.fish
uv add pandas matplotlib
code .
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;h2&gt;3. 文件布局&lt;/h2&gt;
&lt;p&gt;如果你接着 Week 10 / Week 11 项目做，推荐补齐这些文件：&lt;/p&gt;
&lt;div&gt;&lt;div&gt;&lt;span&gt;omp&lt;/span&gt;&lt;span&gt;fish&lt;/span&gt;&lt;/div&gt;&lt;pre&gt;&lt;code&gt;mkdir -p reports figures src
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;目标布局：&lt;/p&gt;
&lt;div&gt;&lt;div&gt;&lt;span&gt;omp&lt;/span&gt;&lt;span&gt;prompt&lt;/span&gt;&lt;/div&gt;&lt;pre&gt;&lt;code&gt;project-root/
├── README.md
├── solution.md
├── reports/
│   ├── experiments.csv
│   ├── experiment_table.md
│   └── project_review.md
├── figures/
│   └── accuracy_by_experiment.png
└── src/
    └── summarize_experiments.py
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;文件职责：&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;文件&lt;/th&gt;
&lt;th&gt;作用&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;&lt;tr&gt;
&lt;td&gt;&lt;code&gt;reports/experiments.csv&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;机器可读实验记录&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;reports/experiment_table.md&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;可粘贴进报告或 README 的 Markdown 表格&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;figures/accuracy_by_experiment.png&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;指标对比图&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;reports/project_review.md&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;本周核心复盘报告&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;README.md&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;项目入口说明&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;solution.md&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;技术方案说明&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;&lt;/table&gt;
&lt;h2&gt;4. 建立实验表&lt;/h2&gt;
&lt;p&gt;创建 &lt;code&gt;reports/experiments.csv&lt;/code&gt;。可以先在 VS Code 里手动写：&lt;/p&gt;
&lt;div&gt;&lt;div&gt;&lt;span&gt;omp&lt;/span&gt;&lt;span&gt;csv&lt;/span&gt;&lt;/div&gt;&lt;pre&gt;&lt;code&gt;id,date,model,change,valid_metric,public_score,notes
exp001,2026-07-26,MLP,baseline,0.921,,first clean baseline
exp002,2026-07-26,MLP,hidden_dim 128 to 256,0.934,,larger model improved validation
exp003,2026-07-26,CNN,small cnn baseline,0.958,,cnn uses image structure
exp004,2026-07-26,CNN,remove second conv layer,0.941,,ablation: second conv helps
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;字段解释：&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;字段&lt;/th&gt;
&lt;th&gt;含义&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;&lt;tr&gt;
&lt;td&gt;&lt;code&gt;id&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;实验编号，永远不要复用&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;date&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;实验日期&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;model&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;模型或方法&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;change&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;本次实验相对上次改了什么&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;valid_metric&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;本地验证集指标&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;public_score&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;如果是比赛，可填 public leaderboard 分数&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;notes&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;观察和结论&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;&lt;/table&gt;
&lt;p&gt;注意：每行最好只对应一个主要变化。否则无法判断改动是否有效。&lt;/p&gt;
&lt;h2&gt;5. 生成 Markdown 表和图&lt;/h2&gt;
&lt;p&gt;创建 &lt;code&gt;src/summarize_experiments.py&lt;/code&gt;：&lt;/p&gt;
&lt;div&gt;&lt;div&gt;&lt;span&gt;omp&lt;/span&gt;&lt;span&gt;python&lt;/span&gt;&lt;/div&gt;&lt;pre&gt;&lt;code&gt;from __future__ import annotations
&lt;p&gt;from pathlib import Path&lt;/p&gt;
&lt;p&gt;import matplotlib.pyplot as plt
import pandas as pd&lt;/p&gt;
&lt;p&gt;EXPERIMENTS_CSV = Path(“reports/experiments.csv”)
TABLE_MD = Path(“reports/experiment_table.md”)
FIGURE_PATH = Path(“figures/accuracy_by_experiment.png”)&lt;/p&gt;
&lt;p&gt;def main() -&amp;gt; None:
experiments = pd.read_csv(EXPERIMENTS_CSV)
required_columns = {“id”, “model”, “change”, “valid_metric”, “notes”}
missing_columns = required_columns - set(experiments.columns)
if missing_columns:
raise ValueError(f”missing columns: {sorted(missing_columns)}”)&lt;/p&gt;
&lt;p&gt;plain TABLE_MD.parent.mkdir(parents=True, exist_ok=True)
FIGURE_PATH.parent.mkdir(parents=True, exist_ok=True)&lt;/p&gt;
&lt;p&gt;plain table = experiments[[“id”, “model”, “change”, “valid_metric”, “notes”]]
TABLE_MD.write_text(table.to_markdown(index=False), encoding=“utf-8”)&lt;/p&gt;
&lt;p&gt;plain plt.figure(figsize=(8, 4))
plt.plot(experiments[“id”], experiments[“valid_metric”], marker=“o”)
plt.xlabel(“experiment”)
plt.ylabel(“validation metric”)
plt.title(“Validation metric by experiment”)
plt.xticks(rotation=30, ha=“right”)
plt.tight_layout()
plt.savefig(FIGURE_PATH, dpi=150)
plt.close()&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;best = experiments.sort_values(&amp;amp;quot;valid_metric&amp;amp;quot;, ascending=False).iloc[0]&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;print(f&amp;amp;quot;best_experiment={best[&amp;amp;#39;id&amp;amp;#39;]} valid_metric={best[&amp;amp;#39;valid_metric&amp;amp;#39;]}&amp;amp;quot;)&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;print(f&amp;amp;quot;wrote {TABLE_MD}&amp;amp;quot;)&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;print(f&amp;amp;quot;wrote {FIGURE_PATH}&amp;amp;quot;)&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;/code&gt;&lt;p&gt;&lt;code&gt;if &lt;strong&gt;name&lt;/strong&gt; == “&lt;strong&gt;main&lt;/strong&gt;”:
main()
&lt;/code&gt;&lt;/p&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;&lt;/p&gt;
&lt;p&gt;运行：&lt;/p&gt;
&lt;div&gt;&lt;div&gt;&lt;span&gt;omp&lt;/span&gt;&lt;span&gt;fish&lt;/span&gt;&lt;/div&gt;&lt;pre&gt;&lt;code&gt;source .venv/bin/activate.fish
python src/summarize_experiments.py
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;如果报 &lt;code&gt;Missing optional dependency &apos;tabulate&apos;&lt;/code&gt;，安装表格依赖：&lt;/p&gt;
&lt;div&gt;&lt;div&gt;&lt;span&gt;omp&lt;/span&gt;&lt;span&gt;fish&lt;/span&gt;&lt;/div&gt;&lt;pre&gt;&lt;code&gt;uv add tabulate
python src/summarize_experiments.py
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;检查产物：&lt;/p&gt;
&lt;div&gt;&lt;div&gt;&lt;span&gt;omp&lt;/span&gt;&lt;span&gt;fish&lt;/span&gt;&lt;/div&gt;&lt;pre&gt;&lt;code&gt;test -f reports/experiment_table.md; and test -f figures/accuracy_by_experiment.png
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;h2&gt;6. 什么是 ablation&lt;/h2&gt;
&lt;p&gt;Ablation 指“去掉某个模块或改动，观察结果变化”。它不是随便多跑几个实验，而是回答：这个改动是否真的有用？&lt;/p&gt;
&lt;p&gt;例子：&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;实验&lt;/th&gt;
&lt;th&gt;改动&lt;/th&gt;
&lt;th&gt;指标&lt;/th&gt;
&lt;th&gt;结论&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;&lt;tr&gt;
&lt;td&gt;exp003&lt;/td&gt;
&lt;td&gt;CNN baseline with two conv layers&lt;/td&gt;
&lt;td&gt;0.958&lt;/td&gt;
&lt;td&gt;当前最好&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;exp004&lt;/td&gt;
&lt;td&gt;remove second conv layer&lt;/td&gt;
&lt;td&gt;0.941&lt;/td&gt;
&lt;td&gt;第二个卷积层有帮助&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;&lt;/table&gt;
&lt;p&gt;写 ablation 时要避免：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;同时改模型、学习率、数据增强，然后说某个改动有效。&lt;/li&gt;
&lt;li&gt;只报告提升，不报告失败。&lt;/li&gt;
&lt;li&gt;没有固定 seed，导致对比不公平。&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;7. 项目复盘报告结构&lt;/h2&gt;
&lt;p&gt;创建 &lt;code&gt;reports/project_review.md&lt;/code&gt;，推荐结构如下：&lt;/p&gt;
&lt;div&gt;&lt;div&gt;&lt;span&gt;omp&lt;/span&gt;&lt;span&gt;markdown&lt;/span&gt;&lt;/div&gt;&lt;pre&gt;&lt;code&gt;# AI Competition / Deep Learning Project Review
&lt;h2&gt;Task&lt;a href=&quot;#task&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;Problem:&lt;/li&gt;
&lt;li&gt;Input:&lt;/li&gt;
&lt;li&gt;Output:&lt;/li&gt;
&lt;li&gt;Metric:&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;Dataset&lt;a href=&quot;#dataset&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;Source:&lt;/li&gt;
&lt;li&gt;Size:&lt;/li&gt;
&lt;li&gt;Features / modalities:&lt;/li&gt;
&lt;li&gt;Train / validation split:&lt;/li&gt;
&lt;li&gt;Important preprocessing:&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;My Contribution&lt;a href=&quot;#my-contribution&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;I implemented:&lt;/li&gt;
&lt;li&gt;I cleaned:&lt;/li&gt;
&lt;li&gt;I trained:&lt;/li&gt;
&lt;li&gt;I analyzed:&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;Baseline&lt;a href=&quot;#baseline&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;Model:&lt;/li&gt;
&lt;li&gt;Why this baseline:&lt;/li&gt;
&lt;li&gt;Baseline result:&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;Improvements&lt;a href=&quot;#improvements&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;Improvement 1:&lt;/li&gt;
&lt;li&gt;Improvement 2:&lt;/li&gt;
&lt;li&gt;Improvement 3:&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;Experiments&lt;a href=&quot;#experiments&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Paste or link &lt;code&gt;reports/experiment_table.md&lt;/code&gt; here.&lt;/p&gt;
&lt;h2&gt;Ablation&lt;a href=&quot;#ablation&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;Removed component:&lt;/li&gt;
&lt;li&gt;Result change:&lt;/li&gt;
&lt;li&gt;Interpretation:&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;Result&lt;a href=&quot;#result&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;Best validation score:&lt;/li&gt;
&lt;li&gt;Public leaderboard score if any:&lt;/li&gt;
&lt;li&gt;Final selected model:&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;Failure Cases&lt;a href=&quot;#failure-cases&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;Case 1:&lt;/li&gt;
&lt;li&gt;Case 2:&lt;/li&gt;
&lt;li&gt;What these failures suggest:&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;Limitations&lt;a href=&quot;#limitations&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;Dataset limitation:&lt;/li&gt;
&lt;li&gt;Evaluation limitation:&lt;/li&gt;
&lt;li&gt;Model limitation:&lt;/li&gt;
&lt;li&gt;Engineering limitation:&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;What I Learned&lt;a href=&quot;#what-i-learned&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;
&lt;/code&gt;&lt;ul&gt;&lt;code&gt;
&lt;li&gt;Technical lesson:&lt;/li&gt;
&lt;li&gt;Statistical lesson:&lt;/li&gt;
&lt;/code&gt;&lt;li&gt;&lt;code&gt;Engineering lesson:
&lt;/code&gt;&lt;/li&gt;&lt;/ul&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;重点不是写得华丽，而是每一段都有证据。&lt;/p&gt;
&lt;h2&gt;8. README 应该怎么写&lt;/h2&gt;
&lt;p&gt;项目根目录 &lt;code&gt;README.md&lt;/code&gt; 是别人打开仓库最先看到的文件。它应该短、清楚、可执行。&lt;/p&gt;
&lt;p&gt;推荐结构：&lt;/p&gt;
&lt;div&gt;&lt;div&gt;&lt;span&gt;omp&lt;/span&gt;&lt;span&gt;markdown&lt;/span&gt;&lt;/div&gt;&lt;pre&gt;&lt;code&gt;# Project Name
&lt;h2&gt;Summary&lt;a href=&quot;#summary&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;One paragraph explaining the task, method, and best result.&lt;/p&gt;
&lt;h2&gt;File Layout&lt;a href=&quot;#file-layout&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;configs/      experiment configs&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;src/          training and inference code&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;reports/      experiment table and review report&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;figures/      generated charts&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;data/raw/     local raw data, not committed&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;Environment&lt;a href=&quot;#environment&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;uv&lt;/span&gt;&lt;span&gt; venv&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;source&lt;/span&gt;&lt;span&gt; .venv/bin/activate.fish&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;uv&lt;/span&gt;&lt;span&gt; sync&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;Train&lt;a href=&quot;#train&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;python&lt;/span&gt;&lt;span&gt; src/train.py --model mlp --epochs 20&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;Evaluate&lt;a href=&quot;#evaluate&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;python&lt;/span&gt;&lt;span&gt; src/evaluate.py --checkpoint checkpoints/best_mlp.pt&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;Results&lt;a href=&quot;#results&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;




















&lt;table&gt;&lt;thead&gt;&lt;tr&gt;&lt;th&gt;Model&lt;/th&gt;&lt;th&gt;Validation Metric&lt;/th&gt;&lt;th&gt;Notes&lt;/th&gt;&lt;/tr&gt;&lt;/thead&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td&gt;baseline&lt;/td&gt;&lt;td&gt;fill&lt;/td&gt;&lt;td&gt;fill&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;best&lt;/td&gt;&lt;td&gt;fill&lt;/td&gt;&lt;td&gt;fill&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;
&lt;h2&gt;Limitations&lt;a href=&quot;#limitations-1&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;
&lt;/code&gt;&lt;ul&gt;&lt;code&gt;
&lt;li&gt;Limitation 1.&lt;/li&gt;
&lt;li&gt;Limitation 2.&lt;/li&gt;
&lt;/code&gt;&lt;li&gt;&lt;code&gt;Limitation 3.
&lt;/code&gt;&lt;/li&gt;&lt;/ul&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;如果你的 Week 11 项目使用 &lt;code&gt;python -m competition_project.train&lt;/code&gt;，README 里的命令就写项目真实命令，不要照抄上面的 MLP 命令。&lt;/p&gt;
&lt;h2&gt;9. 如何写 limitations&lt;/h2&gt;
&lt;p&gt;Limitations 不是自我否定，而是说明你知道项目边界。&lt;/p&gt;
&lt;p&gt;好的 limitations：&lt;/p&gt;
&lt;div&gt;&lt;div&gt;&lt;span&gt;omp&lt;/span&gt;&lt;span&gt;prompt&lt;/span&gt;&lt;/div&gt;&lt;pre&gt;&lt;code&gt;- Validation uses a single random split; cross-validation was not performed.
- The dataset is small and clean, so the result may not transfer to noisy real-world data.
- Hyperparameter search was limited to three manual trials due to CPU-only training.
- Failure cases suggest the model confuses visually similar classes.
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;不好的 limitations：&lt;/p&gt;
&lt;div&gt;&lt;div&gt;&lt;span&gt;omp&lt;/span&gt;&lt;span&gt;prompt&lt;/span&gt;&lt;/div&gt;&lt;pre&gt;&lt;code&gt;- My computer was bad.
- The model could be better.
- Need more time.
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;好的写法要具体、可验证、能引出下一步。&lt;/p&gt;
&lt;h2&gt;10. 如何写给老师或面试官看的贡献&lt;/h2&gt;
&lt;p&gt;不要只写：&lt;/p&gt;
&lt;div&gt;&lt;div&gt;&lt;span&gt;omp&lt;/span&gt;&lt;span&gt;prompt&lt;/span&gt;&lt;/div&gt;&lt;pre&gt;&lt;code&gt;I participated in the competition.
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;改成：&lt;/p&gt;
&lt;div&gt;&lt;div&gt;&lt;span&gt;omp&lt;/span&gt;&lt;span&gt;prompt&lt;/span&gt;&lt;/div&gt;&lt;pre&gt;&lt;code&gt;I cleaned the tabular features, built a RandomForest baseline, fixed the train / inference feature mismatch, and wrote an ablation comparing categorical encoding strategies. The final local macro-F1 improved from 0.742 to 0.781.
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;结构是：&lt;/p&gt;
&lt;div&gt;&lt;div&gt;&lt;span&gt;omp&lt;/span&gt;&lt;span&gt;prompt&lt;/span&gt;&lt;/div&gt;&lt;pre&gt;&lt;code&gt;我做了什么 -&amp;gt; 解决了什么问题 -&amp;gt; 指标或证据是什么
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;h2&gt;11. 练习&lt;/h2&gt;
&lt;ol&gt;
&lt;li&gt;从 &lt;code&gt;reports/experiments.csv&lt;/code&gt; 里选出 best experiment，并在 &lt;code&gt;project_review.md&lt;/code&gt; 中解释为什么选它。&lt;/li&gt;
&lt;li&gt;至少写一个 ablation：去掉一个层、一个特征、一种增强或一个清洗步骤。&lt;/li&gt;
&lt;li&gt;在 &lt;code&gt;Failure Cases&lt;/code&gt; 中放 2 个真实错误例子。如果是图像项目，可以写错分图片编号；如果是表格项目，可以写被误判样本的特征特点。&lt;/li&gt;
&lt;li&gt;把 README 的运行命令复制到一个新终端里执行一遍，确认没有漏步骤。&lt;/li&gt;
&lt;li&gt;给 &lt;code&gt;Limitations&lt;/code&gt; 写 3 条具体限制，每条都要能被事实支持。&lt;/li&gt;
&lt;/ol&gt;
&lt;h2&gt;12. 验收检查&lt;/h2&gt;
&lt;p&gt;在项目根目录执行：&lt;/p&gt;
&lt;div&gt;&lt;div&gt;&lt;span&gt;omp&lt;/span&gt;&lt;span&gt;fish&lt;/span&gt;&lt;/div&gt;&lt;pre&gt;&lt;code&gt;source .venv/bin/activate.fish
test -f reports/experiments.csv; and test -f reports/project_review.md; and test -f README.md
python src/summarize_experiments.py
test -f reports/experiment_table.md; and test -f figures/accuracy_by_experiment.png
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;通过标准：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;reports/project_review.md&lt;/code&gt; 包含 Task、Dataset、My Contribution、Baseline、Improvements、Experiments、Result、Failure Cases、What I Learned。&lt;/li&gt;
&lt;li&gt;实验表中至少有 baseline、best、一个 ablation。&lt;/li&gt;
&lt;li&gt;README 里有环境、训练、评估或推理命令。&lt;/li&gt;
&lt;li&gt;Limitations 至少 3 条，并且不夸大项目结果。&lt;/li&gt;
&lt;li&gt;复盘报告能让没有参加项目的人看懂你做了什么。&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;13. 常见错误&lt;/h2&gt;
&lt;h3&gt;13.1 只写结果，不写过程&lt;/h3&gt;
&lt;p&gt;错误写法：&lt;/p&gt;
&lt;div&gt;&lt;div&gt;&lt;span&gt;omp&lt;/span&gt;&lt;span&gt;prompt&lt;/span&gt;&lt;/div&gt;&lt;pre&gt;&lt;code&gt;We achieved 0.958 accuracy.
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;更好的写法：&lt;/p&gt;
&lt;div&gt;&lt;div&gt;&lt;span&gt;omp&lt;/span&gt;&lt;span&gt;prompt&lt;/span&gt;&lt;/div&gt;&lt;pre&gt;&lt;code&gt;The CNN baseline achieved 0.958 validation accuracy. Compared with the MLP baseline at 0.934, the CNN improved performance by using local image structure.
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;h3&gt;13.2 实验表没有对比意义&lt;/h3&gt;
&lt;p&gt;如果每次实验都同时改很多东西，就无法得出结论。实验表应该支持类似问题：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;CNN 是否优于 MLP？&lt;/li&gt;
&lt;li&gt;第二个卷积层是否有用？&lt;/li&gt;
&lt;li&gt;某个特征工程是否提升指标？&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;13.3 README 命令不能运行&lt;/h3&gt;
&lt;p&gt;每次改 README 后，至少在新终端里检查环境命令。fish 项目应写：&lt;/p&gt;
&lt;div&gt;&lt;div&gt;&lt;span&gt;omp&lt;/span&gt;&lt;span&gt;fish&lt;/span&gt;&lt;/div&gt;&lt;pre&gt;&lt;code&gt;source .venv/bin/activate.fish
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;不要把 Bash / Zsh 的激活命令混进 fish 教程里。&lt;/p&gt;
&lt;h3&gt;13.4 limitations 写得太空&lt;/h3&gt;
&lt;p&gt;“模型还可以优化”不是有效 limitation。你要写清楚受什么限制、可能影响什么、下一步怎么验证。&lt;/p&gt;
&lt;h3&gt;13.5 把复盘报告写成流水账&lt;/h3&gt;
&lt;p&gt;报告不是日记。不要按“第一天我做了什么，第二天我做了什么”写；要按 Task、Dataset、Method、Experiments、Result、Limitations 组织。&lt;/p&gt;
&lt;h2&gt;14. 下一步&lt;/h2&gt;
&lt;p&gt;进入 Week 13：时间序列基础。你会从普通机器学习项目转向有时间顺序的数据，学习 ARIMA、rolling validation，并为后续量化回测打基础。&lt;/p&gt;
&lt;p&gt;plain &lt;/p&gt;&lt;/article&gt;&lt;p&gt;&lt;/p&gt;
  &lt;/div&gt;
&lt;/section&gt;</content:encoded><category>category:工具</category><category>category:AI项目</category><category>tag:AI项目</category><category>tag:ablation</category><category>tag:README</category><category>tag:limitations</category><category>tag:report</category></item><item><title>Week 11：AI 比赛代码整理——从混乱 notebook 到可复现项目</title><link>https://39miku.space/post/week11-ai-competition-code-cleanup</link><guid isPermaLink="false">week11-ai-competition-code-cleanup</guid><description>Week 11：AI 比赛代码整理——从混乱 notebook 到可复现项目。fish-first 终端教学，面向 CachyOS、VS Code、uv 和 Python 学习路线。</description><pubDate>Sun, 26 Jul 2026 03:00:00 GMT</pubDate><content:encoded>
&lt;section&gt;
  &lt;div&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;strong&gt;Oh My Pi&lt;/strong&gt; / weekly tutorial / week11-ai-competition-code-cleanup&lt;/span&gt;&lt;/div&gt;
  &lt;div&gt;
    &lt;div&gt;&lt;span&gt;miku@cachyos&lt;/span&gt;&lt;span&gt;:~/Code/python-learning&lt;/span&gt;&lt;span&gt;$&lt;/span&gt; &lt;span&gt;omp teach week11-ai-competition-code-cleanup --fish-first --step-by-step&lt;/span&gt;&lt;/div&gt;
    &lt;div&gt;
      &lt;div&gt;&lt;span&gt;source&lt;/span&gt;&lt;strong&gt;644 行教学文档&lt;/strong&gt;&lt;/div&gt;
      &lt;div&gt;&lt;span&gt;week&lt;/span&gt;&lt;strong&gt;Week 11&lt;/strong&gt;&lt;/div&gt;
      &lt;div&gt;&lt;span&gt;shell&lt;/span&gt;&lt;strong&gt;fish-first 命令版&lt;/strong&gt;&lt;/div&gt;
      &lt;div&gt;&lt;span&gt;backlink&lt;/span&gt;&lt;strong&gt;&lt;a href=&quot;/post/ustc-stat-ai-quant-plan/&quot;&gt;20 周计划&lt;/a&gt;&lt;/strong&gt;&lt;/div&gt;
    &lt;/div&gt;
    &lt;article&gt;
&lt;h1&gt;Week 11：AI 比赛代码整理——从混乱 notebook 到可复现项目&lt;/h1&gt;
&lt;blockquote&gt;
&lt;p&gt;回到总路线：&lt;a href=&quot;/post/ustc-stat-ai-quant-plan/&quot;&gt;USTC 统计 AI / 量化 20 周成长计划&lt;/a&gt;&lt;br /&gt;本周目标对应计划中的 Week 11：把 AI 比赛或课程项目整理成简历项目，沉淀数据清洗、baseline、特征工程、模型训练、调参、结果分析和文档。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;本教程默认你使用 &lt;strong&gt;CachyOS + fish shell + VS Code + uv + Python + Git&lt;/strong&gt;。所有命令默认是 fish。你可以整理真实比赛项目，也可以用一个课程数据集补做一个模块；重点是让项目结构、配置、随机种子、训练入口和 inference 入口清楚。&lt;/p&gt;
&lt;p&gt;&lt;a&gt;&lt;/a&gt;&lt;/p&gt;
&lt;h2&gt;0. 本周详细教学：语法、规范、验收&lt;/h2&gt;
&lt;blockquote&gt;
&lt;p&gt;本节不是追加在尾部的复习，而是本周正文的入口。先读这里，再做后面的命令和项目。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h3&gt;0.1 本周真正要学会什么&lt;/h3&gt;
&lt;table&gt;
&lt;thead&gt;&lt;tr&gt;&lt;th&gt;维度&lt;/th&gt;&lt;th&gt;要求&lt;/th&gt;&lt;/tr&gt;&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;&lt;td&gt;知识点&lt;/td&gt;&lt;td&gt;notebook 拆分、配置、日志、实验记录&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;代码语法&lt;/td&gt;&lt;td&gt;能从空文件写出本周核心脚本，而不是只复制运行&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;程序规范&lt;/td&gt;&lt;td&gt;函数拆分、路径清楚、输入输出明确、错误能解释&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;交付物&lt;/td&gt;&lt;td&gt;&lt;code&gt;projects/ai-competition-review/&lt;/code&gt;&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;验收方式&lt;/td&gt;&lt;td&gt;从 fish 终端运行命令，得到可复查的文件或指标&lt;/td&gt;&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;h3&gt;0.2 代码语法精讲&lt;/h3&gt;
&lt;p&gt;下面的代码不是最终答案，而是本周必须理解的最小骨架：&lt;/p&gt;
&lt;div&gt;&lt;div&gt;&lt;span&gt;omp&lt;/span&gt;&lt;span&gt;python&lt;/span&gt;&lt;/div&gt;&lt;pre&gt;&lt;code&gt;from dataclasses import dataclass
&lt;p&gt;@dataclass(frozen=True)
class Config:
seed: int = 42
data_path: str = “data/train.csv”
output_dir: str = “outputs”&lt;/p&gt;
&lt;/code&gt;&lt;p&gt;&lt;code&gt;config = Config()
print(config)
&lt;/code&gt;&lt;/p&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;&lt;/p&gt;
&lt;p&gt;读代码时按四步检查：输入从哪里来；中间变量的类型和 shape 是什么；函数或脚本输出什么；哪些错误应该显式报出来。&lt;/p&gt;
&lt;h3&gt;0.3 本周程序规范&lt;/h3&gt;
&lt;ul&gt;&lt;li&gt;所有路径用相对路径或 `pathlib.Path`，不要写死 `/home/miku/...`。&lt;/li&gt;&lt;li&gt;核心逻辑进 `src/`，notebook 只做探索和解释。&lt;/li&gt;&lt;li&gt;每个脚本能从 fish 终端运行，并在 README 写出命令。&lt;/li&gt;&lt;li&gt;输出必须落盘到 `reports/`、`figures/` 或 `outputs/`，不能只在屏幕上看。&lt;/li&gt;&lt;/ul&gt;
&lt;h3&gt;0.4 本周练习分层&lt;/h3&gt;
&lt;table&gt;
&lt;thead&gt;&lt;tr&gt;&lt;th&gt;层级&lt;/th&gt;&lt;th&gt;任务&lt;/th&gt;&lt;th&gt;不合格表现&lt;/th&gt;&lt;th&gt;合格验收&lt;/th&gt;&lt;/tr&gt;&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;&lt;td&gt;最小练习&lt;/td&gt;&lt;td&gt;手写上面的最小骨架&lt;/td&gt;&lt;td&gt;只在 notebook 里运行&lt;/td&gt;&lt;td&gt;终端运行成功&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;标准练习&lt;/td&gt;&lt;td&gt;把逻辑拆成函数/模块&lt;/td&gt;&lt;td&gt;一个大脚本从头写到尾&lt;/td&gt;&lt;td&gt;至少 2 个函数，职责清楚&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;项目练习&lt;/td&gt;&lt;td&gt;生成本周交付物 &lt;code&gt;projects/ai-competition-review/&lt;/code&gt;&lt;/td&gt;&lt;td&gt;只有屏幕输出&lt;/td&gt;&lt;td&gt;文件落盘，可复查&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;复盘练习&lt;/td&gt;&lt;td&gt;写 3 个错误和修复&lt;/td&gt;&lt;td&gt;只写“已解决”&lt;/td&gt;&lt;td&gt;写清报错、原因、修复、预防&lt;/td&gt;&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;h3&gt;0.5 本周和主线的连接&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;回到总计划：&lt;a href=&quot;/post/ustc-stat-ai-quant-plan/#week-plan&quot;&gt;USTC AI / Quant 练习手册&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;查详细练习索引：&lt;a href=&quot;/post/ustc-stat-ai-quant-plan/#deep-practice-appendix&quot;&gt;技术练习详解&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;查质量评分：&lt;a href=&quot;/post/ustc-stat-ai-quant-plan/#quality-final-gates&quot;&gt;最终质量门槛&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;1. 本周目标&lt;/h2&gt;
&lt;p&gt;完成后，你的项目应该从：&lt;/p&gt;
&lt;div&gt;&lt;div&gt;&lt;span&gt;omp&lt;/span&gt;&lt;span&gt;prompt&lt;/span&gt;&lt;/div&gt;&lt;pre&gt;&lt;code&gt;notebook_final.ipynb
notebook_final_v2.ipynb
try_again.py
submit_latest.csv
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;整理成：&lt;/p&gt;
&lt;div&gt;&lt;div&gt;&lt;span&gt;omp&lt;/span&gt;&lt;span&gt;prompt&lt;/span&gt;&lt;/div&gt;&lt;pre&gt;&lt;code&gt;projects/ai-competition-review/
├── README.md
├── solution.md
├── configs/
├── data/
├── src/
├── outputs/
├── submissions/
└── reports/
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;你要交付的不是“更漂亮的文件夹”，而是一个别人能理解、能运行、能复现、能生成预测文件的项目。&lt;/p&gt;
&lt;h2&gt;2. 前置条件&lt;/h2&gt;
&lt;p&gt;你需要已经具备：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;会创建和激活 uv 虚拟环境。&lt;/li&gt;
&lt;li&gt;会运行 Python 脚本。&lt;/li&gt;
&lt;li&gt;理解 train / validation split。&lt;/li&gt;
&lt;li&gt;至少有一个比赛、课程项目或公开数据集项目可以整理。&lt;/li&gt;
&lt;li&gt;知道不要把大数据和私密 token 提交到 Git。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;先确认：&lt;/p&gt;
&lt;div&gt;&lt;div&gt;&lt;span&gt;omp&lt;/span&gt;&lt;span&gt;fish&lt;/span&gt;&lt;/div&gt;&lt;pre&gt;&lt;code&gt;python --version
uv --version
git --version
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;h2&gt;3. 建立整理用项目&lt;/h2&gt;
&lt;p&gt;如果你已有比赛项目，可以在原项目旁边新建一个干净版本；不要直接在混乱目录里硬改。&lt;/p&gt;
&lt;div&gt;&lt;div&gt;&lt;span&gt;omp&lt;/span&gt;&lt;span&gt;fish&lt;/span&gt;&lt;/div&gt;&lt;pre&gt;&lt;code&gt;mkdir -p ~/Code/ustc-ai/week11-ai-competition-review
cd ~/Code/ustc-ai/week11-ai-competition-review
uv init --name week11-ai-competition-review
uv venv
source .venv/bin/activate.fish
uv add pandas numpy scikit-learn joblib
code .
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;命令解释：&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;命令&lt;/th&gt;
&lt;th&gt;作用&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;&lt;tr&gt;
&lt;td&gt;&lt;code&gt;uv init&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;生成项目配置&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;uv venv&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;建立隔离环境&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;source .venv/bin/activate.fish&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;用 fish 激活环境&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;uv add ...&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;安装表格比赛常用依赖&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;code .&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;打开 VS Code 进行整理&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;&lt;/table&gt;
&lt;p&gt;如果你的比赛是图像或 NLP，可以额外安装 &lt;code&gt;torch&lt;/code&gt;、&lt;code&gt;torchvision&lt;/code&gt;、&lt;code&gt;transformers&lt;/code&gt; 等；但本教程用表格分类 baseline 演示，因为它最适合讲清楚工程结构。&lt;/p&gt;
&lt;h2&gt;4. 目标文件布局&lt;/h2&gt;
&lt;p&gt;创建目录：&lt;/p&gt;
&lt;div&gt;&lt;div&gt;&lt;span&gt;omp&lt;/span&gt;&lt;span&gt;fish&lt;/span&gt;&lt;/div&gt;&lt;pre&gt;&lt;code&gt;mkdir -p configs data/raw data/processed src/competition_project outputs/models submissions reports notebooks
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;推荐布局：&lt;/p&gt;
&lt;div&gt;&lt;div&gt;&lt;span&gt;omp&lt;/span&gt;&lt;span&gt;prompt&lt;/span&gt;&lt;/div&gt;&lt;pre&gt;&lt;code&gt;week11-ai-competition-review/
├── configs/
│   └── baseline.toml
├── data/
│   ├── raw/
│   │   ├── train.csv
│   │   └── test.csv
│   └── processed/
├── src/
│   └── competition_project/
│       ├── __init__.py
│       ├── config.py
│       ├── data.py
│       ├── features.py
│       ├── model.py
│       ├── train.py
│       ├── infer.py
│       └── utils.py
├── outputs/
│   └── models/
├── submissions/
├── reports/
│   └── experiments.md
├── notebooks/
├── README.md
└── solution.md
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;每个目录的职责：&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;路径&lt;/th&gt;
&lt;th&gt;放什么&lt;/th&gt;
&lt;th&gt;不放什么&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;&lt;tr&gt;
&lt;td&gt;&lt;code&gt;configs/&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;超参数、路径、随机种子&lt;/td&gt;
&lt;td&gt;临时实验结果&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;data/raw/&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;原始比赛数据&lt;/td&gt;
&lt;td&gt;手工改过的数据&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;data/processed/&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;可重新生成的中间数据&lt;/td&gt;
&lt;td&gt;唯一副本&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;src/competition_project/&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;可复用 Python 代码&lt;/td&gt;
&lt;td&gt;大段实验笔记&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;outputs/models/&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;训练出的模型文件&lt;/td&gt;
&lt;td&gt;源代码&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;submissions/&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;提交文件&lt;/td&gt;
&lt;td&gt;模型权重&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;reports/&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;实验表、错误分析&lt;/td&gt;
&lt;td&gt;原始数据&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;notebooks/&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;探索性分析&lt;/td&gt;
&lt;td&gt;唯一可运行训练逻辑&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;&lt;/table&gt;
&lt;h2&gt;5. 把旧文件迁移进新结构&lt;/h2&gt;
&lt;p&gt;假设旧项目在 &lt;code&gt;~/Downloads/my-competition-messy&lt;/code&gt;，先只复制你需要保留的文件：&lt;/p&gt;
&lt;div&gt;&lt;div&gt;&lt;span&gt;omp&lt;/span&gt;&lt;span&gt;fish&lt;/span&gt;&lt;/div&gt;&lt;pre&gt;&lt;code&gt;cp ~/Downloads/my-competition-messy/train.csv data/raw/train.csv
cp ~/Downloads/my-competition-messy/test.csv data/raw/test.csv
cp ~/Downloads/my-competition-messy/best_notes.ipynb notebooks/eda_original.ipynb
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;如果文件名不同，就按你的实际情况改。原则是：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;原始数据只进 &lt;code&gt;data/raw/&lt;/code&gt;。&lt;/li&gt;
&lt;li&gt;notebook 只作为分析记录，不再作为主训练入口。&lt;/li&gt;
&lt;li&gt;最终训练逻辑要落到 &lt;code&gt;src/competition_project/train.py&lt;/code&gt;。&lt;/li&gt;
&lt;li&gt;最终预测逻辑要落到 &lt;code&gt;src/competition_project/infer.py&lt;/code&gt;。&lt;/li&gt;
&lt;/ol&gt;
&lt;h2&gt;6. 写配置文件&lt;/h2&gt;
&lt;p&gt;创建 &lt;code&gt;configs/baseline.toml&lt;/code&gt;：&lt;/p&gt;
&lt;div&gt;&lt;div&gt;&lt;span&gt;omp&lt;/span&gt;&lt;span&gt;toml&lt;/span&gt;&lt;/div&gt;&lt;pre&gt;&lt;code&gt;[paths]
train_csv = &quot;data/raw/train.csv&quot;
test_csv = &quot;data/raw/test.csv&quot;
model_path = &quot;outputs/models/baseline.joblib&quot;
submission_path = &quot;submissions/baseline_submission.csv&quot;
&lt;p&gt;[target]
column = “target”
id_column = “id”&lt;/p&gt;
&lt;p&gt;[train]
seed = 42
valid_size = 0.2&lt;/p&gt;
&lt;/code&gt;&lt;p&gt;&lt;code&gt;[model]
type = “random_forest”
n_estimators = 300
max_depth = 8
&lt;/code&gt;&lt;/p&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;&lt;/p&gt;
&lt;p&gt;注意：上面假设你的训练集有 &lt;code&gt;target&lt;/code&gt; 列，测试集有 &lt;code&gt;id&lt;/code&gt; 列。真实比赛可能叫 &lt;code&gt;label&lt;/code&gt;、&lt;code&gt;SalePrice&lt;/code&gt;、&lt;code&gt;isDefault&lt;/code&gt; 或其他名字，你需要改成自己的列名。&lt;/p&gt;
&lt;p&gt;如果你复制后发现 TOML 报错，检查 section 名称。正确写法应该是：&lt;/p&gt;
&lt;div&gt;&lt;div&gt;&lt;span&gt;omp&lt;/span&gt;&lt;span&gt;toml&lt;/span&gt;&lt;/div&gt;&lt;pre&gt;&lt;code&gt;[target]
column = &quot;target&quot;
id_column = &quot;id&quot;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;h2&gt;7. 配置读取：&lt;code&gt;config.py&lt;/code&gt;&lt;/h2&gt;
&lt;p&gt;创建 &lt;code&gt;src/competition_project/__init__.py&lt;/code&gt;，可以先留空。&lt;/p&gt;
&lt;p&gt;创建 &lt;code&gt;src/competition_project/config.py&lt;/code&gt;：&lt;/p&gt;
&lt;div&gt;&lt;div&gt;&lt;span&gt;omp&lt;/span&gt;&lt;span&gt;python&lt;/span&gt;&lt;/div&gt;&lt;pre&gt;&lt;code&gt;from __future__ import annotations
&lt;p&gt;import tomllib
from pathlib import Path
from typing import Any&lt;/p&gt;
&lt;/code&gt;&lt;p&gt;&lt;code&gt;def load_config(path: str | Path) -&amp;gt; dict[str, Any]:
config_path = Path(path)
with config_path.open(“rb”) as file:
return tomllib.load(file)
&lt;/code&gt;&lt;/p&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;&lt;/p&gt;
&lt;p&gt;保持简单。Week 11 不需要一上来引入复杂配置框架。&lt;/p&gt;
&lt;h2&gt;8. 可复现工具：&lt;code&gt;utils.py&lt;/code&gt;&lt;/h2&gt;
&lt;p&gt;创建 &lt;code&gt;src/competition_project/utils.py&lt;/code&gt;：&lt;/p&gt;
&lt;div&gt;&lt;div&gt;&lt;span&gt;omp&lt;/span&gt;&lt;span&gt;python&lt;/span&gt;&lt;/div&gt;&lt;pre&gt;&lt;code&gt;from __future__ import annotations
&lt;p&gt;import os
import random
from pathlib import Path&lt;/p&gt;
&lt;p&gt;import numpy as np&lt;/p&gt;
&lt;p&gt;def seed_everything(seed: int) -&amp;gt; None:
os.environ[“PYTHONHASHSEED”] = str(seed)
random.seed(seed)
np.random.seed(seed)&lt;/p&gt;
&lt;/code&gt;&lt;p&gt;&lt;code&gt;def ensure_parent(path: str | Path) -&amp;gt; None:
Path(path).parent.mkdir(parents=True, exist_ok=True)
&lt;/code&gt;&lt;/p&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;&lt;/p&gt;
&lt;p&gt;为什么要固定随机种子：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;train / validation split 会受随机数影响。&lt;/li&gt;
&lt;li&gt;随机森林、神经网络初始化也会受随机数影响。&lt;/li&gt;
&lt;li&gt;复盘报告里需要说明结果来自哪个 seed。&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;9. 数据读取：&lt;code&gt;data.py&lt;/code&gt;&lt;/h2&gt;
&lt;p&gt;创建 &lt;code&gt;src/competition_project/data.py&lt;/code&gt;：&lt;/p&gt;
&lt;div&gt;&lt;div&gt;&lt;span&gt;omp&lt;/span&gt;&lt;span&gt;python&lt;/span&gt;&lt;/div&gt;&lt;pre&gt;&lt;code&gt;from __future__ import annotations
&lt;p&gt;import pandas as pd&lt;/p&gt;
&lt;p&gt;def read_train_test(train_csv: str, test_csv: str) -&amp;gt; tuple[pd.DataFrame, pd.DataFrame]:
train = pd.read_csv(train_csv)
test = pd.read_csv(test_csv)
return train, test&lt;/p&gt;
&lt;/code&gt;&lt;p&gt;&lt;code&gt;def split_features_target(
train: pd.DataFrame,
target_column: str,
) -&amp;gt; tuple[pd.DataFrame, pd.Series]:
y = train[target_column]
x = train.drop(columns=[target_column])
return x, y
&lt;/code&gt;&lt;/p&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;&lt;/p&gt;
&lt;p&gt;原则：读取数据和训练模型分开。不要在 &lt;code&gt;train.py&lt;/code&gt; 里塞满所有细节。&lt;/p&gt;
&lt;h2&gt;10. 特征工程：&lt;code&gt;features.py&lt;/code&gt;&lt;/h2&gt;
&lt;p&gt;创建 &lt;code&gt;src/competition_project/features.py&lt;/code&gt;：&lt;/p&gt;
&lt;div&gt;&lt;div&gt;&lt;span&gt;omp&lt;/span&gt;&lt;span&gt;python&lt;/span&gt;&lt;/div&gt;&lt;pre&gt;&lt;code&gt;from __future__ import annotations
&lt;p&gt;import pandas as pd
from sklearn.compose import ColumnTransformer
from sklearn.impute import SimpleImputer
from sklearn.pipeline import Pipeline
from sklearn.preprocessing import OneHotEncoder, StandardScaler&lt;/p&gt;
&lt;p&gt;def build_preprocessor(features: pd.DataFrame) -&amp;gt; ColumnTransformer:
numeric_columns = features.select_dtypes(include=[“number”, “bool”]).columns.tolist()
categorical_columns = [column for column in features.columns if column not in numeric_columns]&lt;/p&gt;
&lt;p&gt;plain numeric_pipeline = Pipeline(
steps=[
(“imputer”, SimpleImputer(strategy=“median”)),
(“scaler”, StandardScaler()),
]
)
categorical_pipeline = Pipeline(
steps=[
(“imputer”, SimpleImputer(strategy=“most_frequent”)),
(“one_hot”, OneHotEncoder(handle_unknown=“ignore”)),
]
)&lt;/p&gt;
&lt;/code&gt;&lt;p&gt;&lt;code&gt;plain return ColumnTransformer(
transformers=[
(“numeric”, numeric_pipeline, numeric_columns),
(“categorical”, categorical_pipeline, categorical_columns),
]
)
&lt;/code&gt;&lt;/p&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;&lt;/p&gt;
&lt;p&gt;这个版本适合表格分类 baseline：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;数值列：缺失值用中位数填充，然后标准化。&lt;/li&gt;
&lt;li&gt;类别列：缺失值用众数填充，然后 one-hot。&lt;/li&gt;
&lt;li&gt;测试集中出现训练集没见过的类别时，&lt;code&gt;handle_unknown=&quot;ignore&quot;&lt;/code&gt; 不会直接崩。&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;11. 模型定义：&lt;code&gt;model.py&lt;/code&gt;&lt;/h2&gt;
&lt;p&gt;创建 &lt;code&gt;src/competition_project/model.py&lt;/code&gt;：&lt;/p&gt;
&lt;div&gt;&lt;div&gt;&lt;span&gt;omp&lt;/span&gt;&lt;span&gt;python&lt;/span&gt;&lt;/div&gt;&lt;pre&gt;&lt;code&gt;from __future__ import annotations
&lt;p&gt;from sklearn.ensemble import RandomForestClassifier
from sklearn.linear_model import LogisticRegression&lt;/p&gt;
&lt;p&gt;def build_model(config: dict):
model_type = config[“model”][“type”]
seed = config[“train”][“seed”]&lt;/p&gt;
&lt;p&gt;plain if model_type == “random_forest”:
return RandomForestClassifier(
n_estimators=config[“model”][“n_estimators”],
max_depth=config[“model”][“max_depth”],
random_state=seed,
n_jobs=-1,
)&lt;/p&gt;
&lt;p&gt;plain if model_type == “logistic_regression”:
return LogisticRegression(max_iter=1000, random_state=seed)&lt;/p&gt;
&lt;/code&gt;&lt;p&gt;&lt;code&gt;plain raise ValueError(f”unsupported model type: {model_type}”)
&lt;/code&gt;&lt;/p&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;&lt;/p&gt;
&lt;p&gt;这里先支持两个模型，方便你做 baseline 对比。不要在 Week 11 一次性塞入十几个模型；项目清晰比模型数量更重要。&lt;/p&gt;
&lt;h2&gt;12. 训练入口：&lt;code&gt;train.py&lt;/code&gt;&lt;/h2&gt;
&lt;p&gt;创建 &lt;code&gt;src/competition_project/train.py&lt;/code&gt;：&lt;/p&gt;
&lt;div&gt;&lt;div&gt;&lt;span&gt;omp&lt;/span&gt;&lt;span&gt;python&lt;/span&gt;&lt;/div&gt;&lt;pre&gt;&lt;code&gt;from __future__ import annotations
&lt;p&gt;import argparse
from pathlib import Path&lt;/p&gt;
&lt;p&gt;import joblib
import pandas as pd
from sklearn.metrics import accuracy_score, f1_score
from sklearn.model_selection import train_test_split
from sklearn.pipeline import Pipeline&lt;/p&gt;
&lt;p&gt;from competition_project.config import load_config
from competition_project.data import read_train_test, split_features_target
from competition_project.features import build_preprocessor
from competition_project.model import build_model
from competition_project.utils import ensure_parent, seed_everything&lt;/p&gt;
&lt;p&gt;def parse_args() -&amp;gt; argparse.Namespace:
parser = argparse.ArgumentParser()
parser.add_argument(“—config”, type=Path, default=Path(“configs/baseline.toml”))
return parser.parse_args()&lt;/p&gt;
&lt;p&gt;def main() -&amp;gt; None:
args = parse_args()
config = load_config(args.config)
seed_everything(config[“train”][“seed”])&lt;/p&gt;
&lt;p&gt;plain train, _ = read_train_test(config[“paths”][“train_csv”], config[“paths”][“test_csv”])
features, target = split_features_target(train, config[“target”][“column”])&lt;/p&gt;
&lt;p&gt;plain drop_columns = [config[“target”].get(“id_column”)]
drop_columns = [column for column in drop_columns if column and column in features.columns]
features = features.drop(columns=drop_columns)&lt;/p&gt;
&lt;p&gt;plain x_train, x_valid, y_train, y_valid = train_test_split(
features,
target,
test_size=config[“train”][“valid_size”],
stratify=target if target.nunique() &amp;lt; 20 else None,
random_state=config[“train”][“seed”],
)&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;pipeline = Pipeline(&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    steps=[&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;        (&amp;amp;quot;preprocess&amp;amp;quot;, build_preprocessor(x_train)),&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;        (&amp;amp;quot;model&amp;amp;quot;, build_model(config)),&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    ]&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;pipeline.fit(x_train, y_train)&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;predictions = pipeline.predict(x_valid)&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;metrics = {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    &amp;amp;quot;accuracy&amp;amp;quot;: accuracy_score(y_valid, predictions),&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    &amp;amp;quot;f1_macro&amp;amp;quot;: f1_score(y_valid, predictions, average=&amp;amp;quot;macro&amp;amp;quot;),&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;}&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;print(metrics)&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;model_path = Path(config[&amp;amp;quot;paths&amp;amp;quot;][&amp;amp;quot;model_path&amp;amp;quot;])&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;ensure_parent(model_path)&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;joblib.dump({&amp;amp;quot;pipeline&amp;amp;quot;: pipeline, &amp;amp;quot;config&amp;amp;quot;: config, &amp;amp;quot;metrics&amp;amp;quot;: metrics}, model_path)&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;report_path = Path(&amp;amp;quot;reports/baseline_metrics.csv&amp;amp;quot;)&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;ensure_parent(report_path)&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;pd.DataFrame([metrics]).to_csv(report_path, index=False)&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;print(f&amp;amp;quot;model saved to {model_path}&amp;amp;quot;)&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;print(f&amp;amp;quot;metrics saved to {report_path}&amp;amp;quot;)&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;/code&gt;&lt;p&gt;&lt;code&gt;if &lt;strong&gt;name&lt;/strong&gt; == “&lt;strong&gt;main&lt;/strong&gt;”:
main()
&lt;/code&gt;&lt;/p&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;&lt;/p&gt;
&lt;p&gt;运行训练：&lt;/p&gt;
&lt;div&gt;&lt;div&gt;&lt;span&gt;omp&lt;/span&gt;&lt;span&gt;fish&lt;/span&gt;&lt;/div&gt;&lt;pre&gt;&lt;code&gt;source .venv/bin/activate.fish
set -gx PYTHONPATH src
python -m competition_project.train --config configs/baseline.toml
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;&lt;code&gt;set -gx PYTHONPATH src&lt;/code&gt; 的作用是告诉 Python：模块包在 &lt;code&gt;src/&lt;/code&gt; 目录下。以后你也可以改成正式打包方式，但 Week 11 先保持直接。&lt;/p&gt;
&lt;h2&gt;13. 推理入口：&lt;code&gt;infer.py&lt;/code&gt;&lt;/h2&gt;
&lt;p&gt;创建 &lt;code&gt;src/competition_project/infer.py&lt;/code&gt;：&lt;/p&gt;
&lt;div&gt;&lt;div&gt;&lt;span&gt;omp&lt;/span&gt;&lt;span&gt;python&lt;/span&gt;&lt;/div&gt;&lt;pre&gt;&lt;code&gt;from __future__ import annotations
&lt;p&gt;import argparse
from pathlib import Path&lt;/p&gt;
&lt;p&gt;import joblib
import pandas as pd&lt;/p&gt;
&lt;p&gt;from competition_project.utils import ensure_parent&lt;/p&gt;
&lt;p&gt;def parse_args() -&amp;gt; argparse.Namespace:
parser = argparse.ArgumentParser()
parser.add_argument(“—model”, type=Path, default=Path(“outputs/models/baseline.joblib”))
parser.add_argument(“—test”, type=Path, default=Path(“data/raw/test.csv”))
parser.add_argument(“—output”, type=Path, default=Path(“submissions/baseline_submission.csv”))
return parser.parse_args()&lt;/p&gt;
&lt;p&gt;def main() -&amp;gt; None:
args = parse_args()
artifact = joblib.load(args.model)
pipeline = artifact[“pipeline”]
config = artifact[“config”]&lt;/p&gt;
&lt;p&gt;plain test = pd.read_csv(args.test)
id_column = config[“target”].get(“id_column”)
ids = test[id_column] if id_column in test.columns else pd.Series(range(len(test)), name=“id”)
features = test.drop(columns=[id_column]) if id_column in test.columns else test&lt;/p&gt;
&lt;p&gt;plain predictions = pipeline.predict(features)
submission = pd.DataFrame({id_column or “id”: ids, config[“target”][“column”]: predictions})&lt;/p&gt;
&lt;p&gt;plain ensure_parent(args.output)
submission.to_csv(args.output, index=False)
print(f”submission saved to {args.output}”)&lt;/p&gt;
&lt;/code&gt;&lt;p&gt;&lt;code&gt;if &lt;strong&gt;name&lt;/strong&gt; == “&lt;strong&gt;main&lt;/strong&gt;”:
main()
&lt;/code&gt;&lt;/p&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;&lt;/p&gt;
&lt;p&gt;运行推理：&lt;/p&gt;
&lt;div&gt;&lt;div&gt;&lt;span&gt;omp&lt;/span&gt;&lt;span&gt;fish&lt;/span&gt;&lt;/div&gt;&lt;pre&gt;&lt;code&gt;source .venv/bin/activate.fish
set -gx PYTHONPATH src
python -m competition_project.infer --model outputs/models/baseline.joblib --test data/raw/test.csv --output submissions/baseline_submission.csv
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;这一步的意义：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;比赛项目必须能生成提交文件。&lt;/li&gt;
&lt;li&gt;&lt;code&gt;infer.py&lt;/code&gt; 不应该重新训练模型。&lt;/li&gt;
&lt;li&gt;训练和推理使用同一个预处理 pipeline，避免训练时和提交时特征不一致。&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;14. Git 忽略规则&lt;/h2&gt;
&lt;p&gt;创建或更新 &lt;code&gt;.gitignore&lt;/code&gt;：&lt;/p&gt;
&lt;div&gt;&lt;div&gt;&lt;span&gt;omp&lt;/span&gt;&lt;span&gt;prompt&lt;/span&gt;&lt;/div&gt;&lt;pre&gt;&lt;code&gt;.venv/
__pycache__/
*.pyc
.ipynb_checkpoints/
data/raw/
data/processed/
outputs/models/
submissions/*.csv
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;解释：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;虚拟环境不提交。&lt;/li&gt;
&lt;li&gt;原始比赛数据通常不提交，尤其是有比赛协议或体积大时。&lt;/li&gt;
&lt;li&gt;模型文件一般不提交到普通 Git 仓库。&lt;/li&gt;
&lt;li&gt;可以提交 &lt;code&gt;configs/&lt;/code&gt;、&lt;code&gt;src/&lt;/code&gt;、&lt;code&gt;reports/&lt;/code&gt;、&lt;code&gt;README.md&lt;/code&gt;、&lt;code&gt;solution.md&lt;/code&gt;。&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;15. 写 &lt;code&gt;solution.md&lt;/code&gt;&lt;/h2&gt;
&lt;p&gt;&lt;code&gt;solution.md&lt;/code&gt; 不需要很长，但要讲清楚方案：&lt;/p&gt;
&lt;div&gt;&lt;div&gt;&lt;span&gt;omp&lt;/span&gt;&lt;span&gt;markdown&lt;/span&gt;&lt;/div&gt;&lt;pre&gt;&lt;code&gt;# Solution
&lt;h2&gt;Task&lt;a href=&quot;#task&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;Competition / dataset name:&lt;/li&gt;
&lt;li&gt;Prediction target:&lt;/li&gt;
&lt;li&gt;Metric:&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;Data Cleaning&lt;a href=&quot;#data-cleaning&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;Missing values:&lt;/li&gt;
&lt;li&gt;Categorical features:&lt;/li&gt;
&lt;li&gt;Numeric features:&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;Baseline&lt;a href=&quot;#baseline&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;Model:&lt;/li&gt;
&lt;li&gt;Validation split:&lt;/li&gt;
&lt;li&gt;Metric:&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;Improvements&lt;a href=&quot;#improvements&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;Feature engineering tried:&lt;/li&gt;
&lt;li&gt;Model changes tried:&lt;/li&gt;
&lt;li&gt;Hyperparameters tried:&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;Result&lt;a href=&quot;#result&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;Local validation:&lt;/li&gt;
&lt;li&gt;Public leaderboard if available:&lt;/li&gt;
&lt;li&gt;Private leaderboard if available:&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;My Contribution&lt;a href=&quot;#my-contribution&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;
&lt;/code&gt;&lt;ul&gt;&lt;code&gt;
&lt;li&gt;I implemented:&lt;/li&gt;
&lt;li&gt;I debugged:&lt;/li&gt;
&lt;/code&gt;&lt;li&gt;&lt;code&gt;I analyzed:
&lt;/code&gt;&lt;/li&gt;&lt;/ul&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;老师或面试官最关心的是：你做了什么、为什么这样做、结果如何、失败了什么。&lt;/p&gt;
&lt;h2&gt;16. 实验记录：&lt;code&gt;reports/experiments.md&lt;/code&gt;&lt;/h2&gt;
&lt;p&gt;创建 &lt;code&gt;reports/experiments.md&lt;/code&gt;：&lt;/p&gt;
&lt;div&gt;&lt;div&gt;&lt;span&gt;omp&lt;/span&gt;&lt;span&gt;markdown&lt;/span&gt;&lt;/div&gt;&lt;pre&gt;&lt;code&gt;# Experiments





































&lt;table&gt;&lt;thead&gt;&lt;tr&gt;&lt;th&gt;ID&lt;/th&gt;&lt;th&gt;Date&lt;/th&gt;&lt;th&gt;Change&lt;/th&gt;&lt;th&gt;Valid Metric&lt;/th&gt;&lt;th&gt;Submission&lt;/th&gt;&lt;th&gt;Notes&lt;/th&gt;&lt;/tr&gt;&lt;/thead&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td&gt;exp001&lt;/td&gt;&lt;td&gt;2026-07-26&lt;/td&gt;&lt;td&gt;random forest baseline&lt;/td&gt;&lt;td&gt;fill&lt;/td&gt;&lt;td&gt;baseline_submission.csv&lt;/td&gt;&lt;td&gt;first clean run&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;exp002&lt;/td&gt;&lt;td&gt;2026-07-26&lt;/td&gt;&lt;td&gt;add feature group A&lt;/td&gt;&lt;td&gt;fill&lt;/td&gt;&lt;td&gt;&lt;/td&gt;&lt;td&gt;compare with exp001&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;&lt;/td&gt;&lt;td&gt;&lt;/td&gt;&lt;td&gt;&lt;/td&gt;&lt;td&gt;&lt;/td&gt;&lt;td&gt;&lt;/td&gt;&lt;td&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;
&lt;p&gt;每次实验只记录一个主要变化。不要写“改了很多东西然后提升了”，那样无法复盘。&lt;/p&gt;
&lt;h2&gt;17. 练习&lt;/h2&gt;
&lt;ol&gt;
&lt;li&gt;把 &lt;code&gt;random_forest&lt;/code&gt; 改成 &lt;code&gt;logistic_regression&lt;/code&gt;，记录指标差异。&lt;/li&gt;
&lt;li&gt;增加一个简单特征，例如两个数值列的比值，并记录是否提升。&lt;/li&gt;
&lt;li&gt;在 &lt;code&gt;solution.md&lt;/code&gt; 中写清楚你负责的模块。如果原比赛中你只参与了一小部分，本周补做一个完整 baseline。&lt;/li&gt;
&lt;li&gt;在 &lt;code&gt;infer.py&lt;/code&gt; 里确认输出列名符合比赛提交格式。&lt;/li&gt;
&lt;li&gt;把 notebook 中能复用的函数迁移到 &lt;code&gt;src/competition_project/&lt;/code&gt;，notebook 只保留分析图和思路。&lt;/li&gt;
&lt;/ol&gt;
&lt;h2&gt;18. 验收检查&lt;/h2&gt;
&lt;p&gt;在项目根目录执行：&lt;/p&gt;
&lt;div&gt;&lt;div&gt;&lt;span&gt;omp&lt;/span&gt;&lt;span&gt;fish&lt;/span&gt;&lt;/div&gt;&lt;pre&gt;&lt;code&gt;source .venv/bin/activate.fish
set -gx PYTHONPATH src
test -f configs/baseline.toml; and test -f src/competition_project/train.py; and test -f src/competition_project/infer.py
test -f README.md; and test -f solution.md; and test -f reports/experiments.md
python -m competition_project.train --config configs/baseline.toml
python -m competition_project.infer --model outputs/models/baseline.joblib --test data/raw/test.csv --output submissions/baseline_submission.csv
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;通过标准：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;train.py&lt;/code&gt; 可以训练并保存模型。&lt;/li&gt;
&lt;li&gt;&lt;code&gt;infer.py&lt;/code&gt; 可以读取模型并生成提交 CSV。&lt;/li&gt;
&lt;li&gt;配置、随机种子、路径都不硬编码在 notebook 里。&lt;/li&gt;
&lt;li&gt;&lt;code&gt;solution.md&lt;/code&gt; 写清楚任务、方案、结果和你的贡献。&lt;/li&gt;
&lt;li&gt;&lt;code&gt;reports/experiments.md&lt;/code&gt; 至少有一条真实实验记录。&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;19. 常见错误&lt;/h2&gt;
&lt;h3&gt;19.1 fish 环境没有激活&lt;/h3&gt;
&lt;p&gt;打开新终端后先执行：&lt;/p&gt;
&lt;div&gt;&lt;div&gt;&lt;span&gt;omp&lt;/span&gt;&lt;span&gt;fish&lt;/span&gt;&lt;/div&gt;&lt;pre&gt;&lt;code&gt;source .venv/bin/activate.fish
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;否则可能调用系统 Python，导致找不到 pandas 或 sklearn。&lt;/p&gt;
&lt;h3&gt;19.2 忘记设置 &lt;code&gt;PYTHONPATH&lt;/code&gt;&lt;/h3&gt;
&lt;p&gt;如果看到 &lt;code&gt;ModuleNotFoundError: No module named &apos;competition_project&apos;&lt;/code&gt;，执行：&lt;/p&gt;
&lt;div&gt;&lt;div&gt;&lt;span&gt;omp&lt;/span&gt;&lt;span&gt;fish&lt;/span&gt;&lt;/div&gt;&lt;pre&gt;&lt;code&gt;set -gx PYTHONPATH src
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;然后重新运行 &lt;code&gt;python -m competition_project.train&lt;/code&gt;。&lt;/p&gt;
&lt;h3&gt;19.3 配置文件 TOML 写错&lt;/h3&gt;
&lt;p&gt;TOML section 必须写成：&lt;/p&gt;
&lt;div&gt;&lt;div&gt;&lt;span&gt;omp&lt;/span&gt;&lt;span&gt;toml&lt;/span&gt;&lt;/div&gt;&lt;pre&gt;&lt;code&gt;[target]
column = &quot;target&quot;
id_column = &quot;id&quot;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;不要漏掉左方括号。&lt;/p&gt;
&lt;h3&gt;19.4 训练和推理特征不一致&lt;/h3&gt;
&lt;p&gt;不要在 &lt;code&gt;infer.py&lt;/code&gt; 里重新写一套预处理逻辑。正确做法是把预处理器和模型放在同一个 sklearn &lt;code&gt;Pipeline&lt;/code&gt; 里，一起保存。&lt;/p&gt;
&lt;h3&gt;19.5 把数据和模型权重提交到 Git&lt;/h3&gt;
&lt;p&gt;先检查：&lt;/p&gt;
&lt;div&gt;&lt;div&gt;&lt;span&gt;omp&lt;/span&gt;&lt;span&gt;fish&lt;/span&gt;&lt;/div&gt;&lt;pre&gt;&lt;code&gt;git status --short
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;如果看到 &lt;code&gt;data/raw/&lt;/code&gt; 或 &lt;code&gt;outputs/models/&lt;/code&gt; 里的大文件准备提交，说明 &lt;code&gt;.gitignore&lt;/code&gt; 没写好或文件已经被 Git 跟踪，需要先处理。&lt;/p&gt;
&lt;h2&gt;20. 下一步&lt;/h2&gt;
&lt;p&gt;进入 Week 12：把 Week 10 的深度学习项目或 Week 11 的比赛整理项目写成项目复盘报告。你会制作实验表、ablation、README、limitations，并学会用“证据”说明你的改进是否真的有效。&lt;/p&gt;
&lt;p&gt;plain &lt;/p&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/article&gt;&lt;p&gt;&lt;/p&gt;&lt;code&gt;
  &lt;/code&gt;&lt;/div&gt;&lt;code&gt;
&lt;/code&gt;&lt;/section&gt;</content:encoded><category>category:工具</category><category>category:AI项目</category><category>tag:AI比赛</category><category>tag:notebook-cleanup</category><category>tag:reproducibility</category><category>tag:project-structure</category></item><item><title>Week 10：深度学习小项目——MLP / CNN baseline、checkpoint、loss 曲线与评估</title><link>https://39miku.space/post/week10-deep-learning-mini-project</link><guid isPermaLink="false">week10-deep-learning-mini-project</guid><description>Week 10：深度学习小项目——MLP / CNN baseline、checkpoint、loss 曲线与评估。fish-first 终端教学，面向 CachyOS、VS Code、uv 和 Python 学习路线。</description><pubDate>Sun, 26 Jul 2026 02:00:00 GMT</pubDate><content:encoded>
&lt;section&gt;
  &lt;div&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;strong&gt;Oh My Pi&lt;/strong&gt; / weekly tutorial / week10-deep-learning-mini-project&lt;/span&gt;&lt;/div&gt;
  &lt;div&gt;
    &lt;div&gt;&lt;span&gt;miku@cachyos&lt;/span&gt;&lt;span&gt;:~/Code/python-learning&lt;/span&gt;&lt;span&gt;$&lt;/span&gt; &lt;span&gt;omp teach week10-deep-learning-mini-project --fish-first --step-by-step&lt;/span&gt;&lt;/div&gt;
    &lt;div&gt;
      &lt;div&gt;&lt;span&gt;source&lt;/span&gt;&lt;strong&gt;658 行教学文档&lt;/strong&gt;&lt;/div&gt;
      &lt;div&gt;&lt;span&gt;week&lt;/span&gt;&lt;strong&gt;Week 10&lt;/strong&gt;&lt;/div&gt;
      &lt;div&gt;&lt;span&gt;shell&lt;/span&gt;&lt;strong&gt;fish-first 命令版&lt;/strong&gt;&lt;/div&gt;
      &lt;div&gt;&lt;span&gt;backlink&lt;/span&gt;&lt;strong&gt;&lt;a href=&quot;/post/ustc-stat-ai-quant-plan/&quot;&gt;20 周计划&lt;/a&gt;&lt;/strong&gt;&lt;/div&gt;
    &lt;/div&gt;
    &lt;article&gt;
&lt;h1&gt;Week 10：深度学习小项目——MLP / CNN baseline、checkpoint、loss 曲线与评估&lt;/h1&gt;
&lt;blockquote&gt;
&lt;p&gt;回到总路线：&lt;a href=&quot;/post/ustc-stat-ai-quant-plan/&quot;&gt;USTC 统计 AI / 量化 20 周成长计划&lt;/a&gt;&lt;br /&gt;本周目标对应计划中的 Week 10：在 MNIST / CIFAR10 / 表格数据上训练一个小模型，记录 loss 曲线，保存 checkpoint，做验证集评估。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;本教程默认你使用 &lt;strong&gt;CachyOS + fish shell + VS Code + uv + Python + Git&lt;/strong&gt;。所有终端命令默认是 fish。为了 CPU-first、可复现、无需下载大数据，本周使用 &lt;code&gt;scikit-learn&lt;/code&gt; 自带的 digits 数据集：8×8 手写数字图片，适合练 MLP 和 CNN baseline。&lt;/p&gt;
&lt;p&gt;&lt;a&gt;&lt;/a&gt;&lt;/p&gt;
&lt;h2&gt;0. 本周详细教学：语法、规范、验收&lt;/h2&gt;
&lt;blockquote&gt;
&lt;p&gt;本节不是追加在尾部的复习，而是本周正文的入口。先读这里，再做后面的命令和项目。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h3&gt;0.1 本周真正要学会什么&lt;/h3&gt;
&lt;table&gt;
&lt;thead&gt;&lt;tr&gt;&lt;th&gt;维度&lt;/th&gt;&lt;th&gt;要求&lt;/th&gt;&lt;/tr&gt;&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;&lt;td&gt;知识点&lt;/td&gt;&lt;td&gt;MLP/CNN、checkpoint、loss 曲线、evaluate.py&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;代码语法&lt;/td&gt;&lt;td&gt;能从空文件写出本周核心脚本，而不是只复制运行&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;程序规范&lt;/td&gt;&lt;td&gt;函数拆分、路径清楚、输入输出明确、错误能解释&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;交付物&lt;/td&gt;&lt;td&gt;&lt;code&gt;projects/pytorch-baseline/&lt;/code&gt;&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;验收方式&lt;/td&gt;&lt;td&gt;从 fish 终端运行命令，得到可复查的文件或指标&lt;/td&gt;&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;h3&gt;0.2 代码语法精讲&lt;/h3&gt;
&lt;p&gt;下面的代码不是最终答案，而是本周必须理解的最小骨架：&lt;/p&gt;
&lt;div&gt;&lt;div&gt;&lt;span&gt;omp&lt;/span&gt;&lt;span&gt;python&lt;/span&gt;&lt;/div&gt;&lt;pre&gt;&lt;code&gt;checkpoint = {
    &quot;model_state&quot;: model.state_dict(),
    &quot;epoch&quot;: epoch,
    &quot;valid_loss&quot;: valid_loss,
}
torch.save(checkpoint, &quot;outputs/best.pt&quot;)
loaded = torch.load(&quot;outputs/best.pt&quot;, map_location=&quot;cpu&quot;)
model.load_state_dict(loaded[&quot;model_state&quot;])
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;读代码时按四步检查：输入从哪里来；中间变量的类型和 shape 是什么；函数或脚本输出什么；哪些错误应该显式报出来。&lt;/p&gt;
&lt;h3&gt;0.3 本周程序规范&lt;/h3&gt;
&lt;ul&gt;&lt;li&gt;所有路径用相对路径或 `pathlib.Path`，不要写死 `/home/miku/...`。&lt;/li&gt;&lt;li&gt;核心逻辑进 `src/`，notebook 只做探索和解释。&lt;/li&gt;&lt;li&gt;每个脚本能从 fish 终端运行，并在 README 写出命令。&lt;/li&gt;&lt;li&gt;输出必须落盘到 `reports/`、`figures/` 或 `outputs/`，不能只在屏幕上看。&lt;/li&gt;&lt;/ul&gt;
&lt;h3&gt;0.4 本周练习分层&lt;/h3&gt;
&lt;table&gt;
&lt;thead&gt;&lt;tr&gt;&lt;th&gt;层级&lt;/th&gt;&lt;th&gt;任务&lt;/th&gt;&lt;th&gt;不合格表现&lt;/th&gt;&lt;th&gt;合格验收&lt;/th&gt;&lt;/tr&gt;&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;&lt;td&gt;最小练习&lt;/td&gt;&lt;td&gt;手写上面的最小骨架&lt;/td&gt;&lt;td&gt;只在 notebook 里运行&lt;/td&gt;&lt;td&gt;终端运行成功&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;标准练习&lt;/td&gt;&lt;td&gt;把逻辑拆成函数/模块&lt;/td&gt;&lt;td&gt;一个大脚本从头写到尾&lt;/td&gt;&lt;td&gt;至少 2 个函数，职责清楚&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;项目练习&lt;/td&gt;&lt;td&gt;生成本周交付物 &lt;code&gt;projects/pytorch-baseline/&lt;/code&gt;&lt;/td&gt;&lt;td&gt;只有屏幕输出&lt;/td&gt;&lt;td&gt;文件落盘，可复查&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;复盘练习&lt;/td&gt;&lt;td&gt;写 3 个错误和修复&lt;/td&gt;&lt;td&gt;只写“已解决”&lt;/td&gt;&lt;td&gt;写清报错、原因、修复、预防&lt;/td&gt;&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;h3&gt;0.5 本周和主线的连接&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;回到总计划：&lt;a href=&quot;/post/ustc-stat-ai-quant-plan/#week-plan&quot;&gt;USTC AI / Quant 练习手册&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;查详细练习索引：&lt;a href=&quot;/post/ustc-stat-ai-quant-plan/#deep-practice-appendix&quot;&gt;技术练习详解&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;查质量评分：&lt;a href=&quot;/post/ustc-stat-ai-quant-plan/#quality-final-gates&quot;&gt;最终质量门槛&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;1. 本周目标&lt;/h2&gt;
&lt;p&gt;完成后你会得到一个小而完整的深度学习项目：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;code&gt;train.py&lt;/code&gt; 支持 MLP 和 CNN 两种 baseline。&lt;/li&gt;
&lt;li&gt;训练过程保存 best checkpoint。&lt;/li&gt;
&lt;li&gt;训练过程记录 &lt;code&gt;train_loss&lt;/code&gt;、&lt;code&gt;valid_loss&lt;/code&gt;、&lt;code&gt;valid_acc&lt;/code&gt;。&lt;/li&gt;
&lt;li&gt;自动生成 loss 曲线图。&lt;/li&gt;
&lt;li&gt;&lt;code&gt;evaluate.py&lt;/code&gt; 可以从 checkpoint 重新加载模型并评估。&lt;/li&gt;
&lt;li&gt;项目结构接近以后比赛和科研项目的最小形态。&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;本周不追求复杂模型，重点是工程闭环：能训练、能保存、能复评、能解释。&lt;/p&gt;
&lt;h2&gt;2. 前置条件&lt;/h2&gt;
&lt;p&gt;你需要已经完成 Week 09，至少理解：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Tensor 的 shape 和 dtype。&lt;/li&gt;
&lt;li&gt;&lt;code&gt;Dataset&lt;/code&gt; / &lt;code&gt;DataLoader&lt;/code&gt;。&lt;/li&gt;
&lt;li&gt;&lt;code&gt;nn.Module&lt;/code&gt;。&lt;/li&gt;
&lt;li&gt;&lt;code&gt;model.train()&lt;/code&gt; 和 &lt;code&gt;model.eval()&lt;/code&gt; 的区别。&lt;/li&gt;
&lt;li&gt;&lt;code&gt;loss.backward()&lt;/code&gt; 与 &lt;code&gt;optimizer.step()&lt;/code&gt;。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;确认工具：&lt;/p&gt;
&lt;div&gt;&lt;div&gt;&lt;span&gt;omp&lt;/span&gt;&lt;span&gt;fish&lt;/span&gt;&lt;/div&gt;&lt;pre&gt;&lt;code&gt;python --version
uv --version
git --version
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;h2&gt;3. 建立项目&lt;/h2&gt;
&lt;div&gt;&lt;div&gt;&lt;span&gt;omp&lt;/span&gt;&lt;span&gt;fish&lt;/span&gt;&lt;/div&gt;&lt;pre&gt;&lt;code&gt;mkdir -p ~/Code/ustc-ai/week10-deep-learning-mini-project
cd ~/Code/ustc-ai/week10-deep-learning-mini-project
uv init --name week10-deep-learning-mini-project
uv venv
source .venv/bin/activate.fish
uv add torch scikit-learn matplotlib pandas
code .
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;解释：&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;命令&lt;/th&gt;
&lt;th&gt;作用&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;&lt;tr&gt;
&lt;td&gt;&lt;code&gt;uv init&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;初始化 Python 项目&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;uv venv&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;创建隔离虚拟环境&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;source .venv/bin/activate.fish&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;用 fish 激活环境&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;uv add torch ...&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;安装 PyTorch、数据集、画图和表格依赖&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;code .&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;用 VS Code 打开项目&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;&lt;/table&gt;
&lt;h2&gt;4. 文件布局&lt;/h2&gt;
&lt;p&gt;创建目录：&lt;/p&gt;
&lt;div&gt;&lt;div&gt;&lt;span&gt;omp&lt;/span&gt;&lt;span&gt;fish&lt;/span&gt;&lt;/div&gt;&lt;pre&gt;&lt;code&gt;mkdir -p src configs checkpoints reports figures
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;推荐布局：&lt;/p&gt;
&lt;div&gt;&lt;div&gt;&lt;span&gt;omp&lt;/span&gt;&lt;span&gt;prompt&lt;/span&gt;&lt;/div&gt;&lt;pre&gt;&lt;code&gt;week10-deep-learning-mini-project/
├── configs/
│   └── digits.toml
├── src/
│   ├── train.py
│   └── evaluate.py
├── checkpoints/
│   └── best_mlp.pt
├── reports/
│   └── metrics_mlp.csv
├── figures/
│   └── loss_curve_mlp.png
├── pyproject.toml
└── README.md
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;用途说明：&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;路径&lt;/th&gt;
&lt;th&gt;用途&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;&lt;tr&gt;
&lt;td&gt;&lt;code&gt;configs/&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;放实验配置，避免超参数散落在代码里&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;src/train.py&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;训练入口&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;src/evaluate.py&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;独立评估入口&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;checkpoints/&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;保存模型权重和必要元信息&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;reports/&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;保存指标 CSV&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;figures/&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;保存 loss 曲线&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;README.md&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;给别人说明如何复现&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;&lt;/table&gt;
&lt;h2&gt;5. 写配置文件&lt;/h2&gt;
&lt;p&gt;创建 &lt;code&gt;configs/digits.toml&lt;/code&gt;：&lt;/p&gt;
&lt;div&gt;&lt;div&gt;&lt;span&gt;omp&lt;/span&gt;&lt;span&gt;toml&lt;/span&gt;&lt;/div&gt;&lt;pre&gt;&lt;code&gt;[train]
epochs = 20
batch_size = 64
learning_rate = 0.001
seed = 42
valid_size = 0.2
&lt;/code&gt;&lt;p&gt;&lt;code&gt;[model]
hidden_dim = 128
&lt;/code&gt;&lt;/p&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;&lt;/p&gt;
&lt;p&gt;为什么要有配置文件：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;你以后会频繁改 epoch、学习率、batch size。&lt;/li&gt;
&lt;li&gt;配置和代码分开，实验记录更清楚。&lt;/li&gt;
&lt;li&gt;复现实验时，别人不需要读完整代码才能知道你怎么训练的。&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;6. 训练脚本：支持 MLP 和 CNN&lt;/h2&gt;
&lt;p&gt;创建 &lt;code&gt;src/train.py&lt;/code&gt;：&lt;/p&gt;
&lt;div&gt;&lt;div&gt;&lt;span&gt;omp&lt;/span&gt;&lt;span&gt;python&lt;/span&gt;&lt;/div&gt;&lt;pre&gt;&lt;code&gt;from __future__ import annotations
&lt;p&gt;import argparse
import csv
import random
import tomllib
from pathlib import Path&lt;/p&gt;
&lt;p&gt;import matplotlib.pyplot as plt
import numpy as np
import torch
from sklearn.datasets import load_digits
from sklearn.model_selection import train_test_split
from torch import nn
from torch.utils.data import DataLoader, Dataset&lt;/p&gt;
&lt;p&gt;class DigitsDataset(Dataset):
def &lt;strong&gt;init&lt;/strong&gt;(self, images: np.ndarray, labels: np.ndarray, model_type: str) -&amp;gt; None:
images = images.astype(“float32”) / 16.0
if model_type == “mlp”:
images = images.reshape(len(images), -1)
else:
images = images[:, None, :, :]
self.images = torch.tensor(images, dtype=torch.float32)
self.labels = torch.tensor(labels, dtype=torch.long)&lt;/p&gt;
&lt;p&gt;plain def &lt;strong&gt;len&lt;/strong&gt;(self) -&amp;gt; int:
return len(self.labels)&lt;/p&gt;
&lt;p&gt;plain def &lt;strong&gt;getitem&lt;/strong&gt;(self, index: int) -&amp;gt; tuple[torch.Tensor, torch.Tensor]:
return self.images[index], self.labels[index]&lt;/p&gt;
&lt;p&gt;class MLP(nn.Module):
def &lt;strong&gt;init&lt;/strong&gt;(self, hidden_dim: int, num_classes: int = 10) -&amp;gt; None:
super().&lt;strong&gt;init&lt;/strong&gt;()
self.net = nn.Sequential(
nn.Linear(64, hidden_dim),
nn.ReLU(),
nn.Linear(hidden_dim, hidden_dim),
nn.ReLU(),
nn.Linear(hidden_dim, num_classes),
)&lt;/p&gt;
&lt;p&gt;plain def forward(self, x: torch.Tensor) -&amp;gt; torch.Tensor:
return self.net(x)&lt;/p&gt;
&lt;p&gt;class SmallCNN(nn.Module):
def &lt;strong&gt;init&lt;/strong&gt;(self, num_classes: int = 10) -&amp;gt; None:
super().&lt;strong&gt;init&lt;/strong&gt;()
self.features = nn.Sequential(
nn.Conv2d(1, 16, kernel_size=3, padding=1),
nn.ReLU(),
nn.MaxPool2d(2),
nn.Conv2d(16, 32, kernel_size=3, padding=1),
nn.ReLU(),
nn.MaxPool2d(2),
)
self.classifier = nn.Sequential(
nn.Flatten(),
nn.Linear(32 * 2 * 2, 64),
nn.ReLU(),
nn.Linear(64, num_classes),
)&lt;/p&gt;
&lt;p&gt;plain def forward(self, x: torch.Tensor) -&amp;gt; torch.Tensor:
return self.classifier(self.features(x))&lt;/p&gt;
&lt;p&gt;def set_seed(seed: int) -&amp;gt; None:
random.seed(seed)
np.random.seed(seed)
torch.manual_seed(seed)&lt;/p&gt;
&lt;p&gt;def load_config(path: Path) -&amp;gt; dict:
with path.open(“rb”) as file:
return tomllib.load(file)&lt;/p&gt;
&lt;p&gt;def build_loaders(config: dict, model_type: str) -&amp;gt; tuple[DataLoader, DataLoader]:
digits = load_digits()
x_train, x_valid, y_train, y_valid = train_test_split(
digits.images,
digits.target,
test_size=config[“train”][“valid_size”],
stratify=digits.target,
random_state=config[“train”][“seed”],
)
train_dataset = DigitsDataset(x_train, y_train, model_type)
valid_dataset = DigitsDataset(x_valid, y_valid, model_type)
train_loader = DataLoader(
train_dataset,
batch_size=config[“train”][“batch_size”],
shuffle=True,
)
valid_loader = DataLoader(
valid_dataset,
batch_size=config[“train”][“batch_size”],
shuffle=False,
)
return train_loader, valid_loader&lt;/p&gt;
&lt;p&gt;def build_model(config: dict, model_type: str) -&amp;gt; nn.Module:
if model_type == “mlp”:
return MLP(hidden_dim=config[“model”][“hidden_dim”])
if model_type == “cnn”:
return SmallCNN()
raise ValueError(f”unknown model type: {model_type}”)&lt;/p&gt;
&lt;p&gt;def run_epoch(
model: nn.Module,
loader: DataLoader,
criterion: nn.Module,
optimizer: torch.optim.Optimizer | None,
device: torch.device,
) -&amp;gt; tuple[float, float]:
is_train = optimizer is not None
model.train(is_train)
total_loss = 0.0
total_correct = 0
total_examples = 0&lt;/p&gt;
&lt;p&gt;plain context = torch.enable_grad() if is_train else torch.no_grad()
with context:
for images, labels in loader:
images = images.to(device)
labels = labels.to(device)&lt;/p&gt;
&lt;p&gt;plain         logits = model(images)
loss = criterion(logits, labels)&lt;/p&gt;
&lt;p&gt;plain         if is_train:
optimizer.zero_grad()
loss.backward()
optimizer.step()&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;        total_loss += loss.item() * len(labels)&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;        total_correct += (logits.argmax(dim=1) == labels).sum().item()&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;        total_examples += len(labels)&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;return total_loss / total_examples, total_correct / total_examples&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;def save_metrics(path: Path, rows: list[dict[str, float]]) -&amp;gt; None:
path.parent.mkdir(parents=True, exist_ok=True)
with path.open(“w”, newline=&quot;&quot;) as file:
writer = csv.DictWriter(file, fieldnames=list(rows[0].keys()))
writer.writeheader()
writer.writerows(rows)&lt;/p&gt;
&lt;p&gt;def save_loss_curve(path: Path, rows: list[dict[str, float]]) -&amp;gt; None:
path.parent.mkdir(parents=True, exist_ok=True)
epochs = [row[“epoch”] for row in rows]
train_loss = [row[“train_loss”] for row in rows]
valid_loss = [row[“valid_loss”] for row in rows]&lt;/p&gt;
&lt;p&gt;plain plt.figure(figsize=(7, 4))
plt.plot(epochs, train_loss, label=“train_loss”)
plt.plot(epochs, valid_loss, label=“valid_loss”)
plt.xlabel(“epoch”)
plt.ylabel(“loss”)
plt.title(“Digits baseline loss curve”)
plt.legend()
plt.tight_layout()
plt.savefig(path, dpi=150)
plt.close()&lt;/p&gt;
&lt;p&gt;def parse_args() -&amp;gt; argparse.Namespace:
parser = argparse.ArgumentParser()
parser.add_argument(“—config”, type=Path, default=Path(“configs/digits.toml”))
parser.add_argument(“—model”, choices=[“mlp”, “cnn”], default=“mlp”)
parser.add_argument(“—epochs”, type=int, default=None)
return parser.parse_args()&lt;/p&gt;
&lt;p&gt;def main() -&amp;gt; None:
args = parse_args()
config = load_config(args.config)
if args.epochs is not None:
config[“train”][“epochs”] = args.epochs&lt;/p&gt;
&lt;p&gt;plain set_seed(config[“train”][“seed”])
device = torch.device(“cpu”)
train_loader, valid_loader = build_loaders(config, args.model)
model = build_model(config, args.model).to(device)
criterion = nn.CrossEntropyLoss()
optimizer = torch.optim.Adam(model.parameters(), lr=config[“train”][“learning_rate”])&lt;/p&gt;
&lt;p&gt;plain best_acc = 0.0
rows: list[dict[str, float]] = []
checkpoint_path = Path(“checkpoints”) / f”best_{args.model}.pt”&lt;/p&gt;
&lt;p&gt;plain for epoch in range(1, config[“train”][“epochs”] + 1):
train_loss, train_acc = run_epoch(model, train_loader, criterion, optimizer, device)
valid_loss, valid_acc = run_epoch(model, valid_loader, criterion, None, device)
row = {
“epoch”: epoch,
“train_loss”: train_loss,
“train_acc”: train_acc,
“valid_loss”: valid_loss,
“valid_acc”: valid_acc,
}
rows.append(row)
print(
f”epoch={epoch&lt;/p&gt;&lt;div&gt;&lt;/div&gt;} ”
f”train_loss={train_loss:.4f} train_acc={train_acc:.3f} ”
f”valid_loss={valid_loss:.4f} valid_acc={valid_acc:.3f}”
)&lt;p&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;    if valid_acc &amp;amp;gt; best_acc:&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;        best_acc = valid_acc&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;        checkpoint_path.parent.mkdir(parents=True, exist_ok=True)&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;        torch.save(&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;            {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;                &amp;amp;quot;model_type&amp;amp;quot;: args.model,&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;                &amp;amp;quot;model_state_dict&amp;amp;quot;: model.state_dict(),&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;                &amp;amp;quot;config&amp;amp;quot;: config,&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;                &amp;amp;quot;best_valid_acc&amp;amp;quot;: best_acc,&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;            },&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;            checkpoint_path,&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;        )&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;save_metrics(Path(&amp;amp;quot;reports&amp;amp;quot;) / f&amp;amp;quot;metrics_{args.model}.csv&amp;amp;quot;, rows)&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;save_loss_curve(Path(&amp;amp;quot;figures&amp;amp;quot;) / f&amp;amp;quot;loss_curve_{args.model}.png&amp;amp;quot;, rows)&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;print(f&amp;amp;quot;best_valid_acc={best_acc:.3f}&amp;amp;quot;)&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;print(f&amp;amp;quot;checkpoint={checkpoint_path}&amp;amp;quot;)&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;/code&gt;&lt;p&gt;&lt;code&gt;if &lt;strong&gt;name&lt;/strong&gt; == “&lt;strong&gt;main&lt;/strong&gt;”:
main()
&lt;/code&gt;&lt;/p&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;&lt;/p&gt;
&lt;p&gt;运行 MLP baseline：&lt;/p&gt;
&lt;div&gt;&lt;div&gt;&lt;span&gt;omp&lt;/span&gt;&lt;span&gt;fish&lt;/span&gt;&lt;/div&gt;&lt;pre&gt;&lt;code&gt;source .venv/bin/activate.fish
python src/train.py --model mlp --epochs 5
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;再运行 CNN baseline：&lt;/p&gt;
&lt;div&gt;&lt;div&gt;&lt;span&gt;omp&lt;/span&gt;&lt;span&gt;fish&lt;/span&gt;&lt;/div&gt;&lt;pre&gt;&lt;code&gt;source .venv/bin/activate.fish
python src/train.py --model cnn --epochs 5
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;MLP 和 CNN 都可以在 CPU 上很快跑完。digits 数据集很小，所以这是练工程流程的项目，不是刷榜项目。&lt;/p&gt;
&lt;h2&gt;7. 评估脚本：从 checkpoint 复现结果&lt;/h2&gt;
&lt;p&gt;训练脚本能打印验证集指标，但项目里还需要独立评估入口。这样别人拿到你的 checkpoint 后，不需要重新训练。&lt;/p&gt;
&lt;p&gt;创建 &lt;code&gt;src/evaluate.py&lt;/code&gt;：&lt;/p&gt;
&lt;div&gt;&lt;div&gt;&lt;span&gt;omp&lt;/span&gt;&lt;span&gt;python&lt;/span&gt;&lt;/div&gt;&lt;pre&gt;&lt;code&gt;from __future__ import annotations
&lt;p&gt;import argparse
from pathlib import Path&lt;/p&gt;
&lt;p&gt;import numpy as np
import torch
from sklearn.datasets import load_digits
from sklearn.metrics import accuracy_score, classification_report
from sklearn.model_selection import train_test_split
from torch import nn
from torch.utils.data import DataLoader, Dataset&lt;/p&gt;
&lt;p&gt;class DigitsDataset(Dataset):
def &lt;strong&gt;init&lt;/strong&gt;(self, images: np.ndarray, labels: np.ndarray, model_type: str) -&amp;gt; None:
images = images.astype(“float32”) / 16.0
if model_type == “mlp”:
images = images.reshape(len(images), -1)
else:
images = images[:, None, :, :]
self.images = torch.tensor(images, dtype=torch.float32)
self.labels = torch.tensor(labels, dtype=torch.long)&lt;/p&gt;
&lt;p&gt;plain def &lt;strong&gt;len&lt;/strong&gt;(self) -&amp;gt; int:
return len(self.labels)&lt;/p&gt;
&lt;p&gt;plain def &lt;strong&gt;getitem&lt;/strong&gt;(self, index: int) -&amp;gt; tuple[torch.Tensor, torch.Tensor]:
return self.images[index], self.labels[index]&lt;/p&gt;
&lt;p&gt;class MLP(nn.Module):
def &lt;strong&gt;init&lt;/strong&gt;(self, hidden_dim: int, num_classes: int = 10) -&amp;gt; None:
super().&lt;strong&gt;init&lt;/strong&gt;()
self.net = nn.Sequential(
nn.Linear(64, hidden_dim),
nn.ReLU(),
nn.Linear(hidden_dim, hidden_dim),
nn.ReLU(),
nn.Linear(hidden_dim, num_classes),
)&lt;/p&gt;
&lt;p&gt;plain def forward(self, x: torch.Tensor) -&amp;gt; torch.Tensor:
return self.net(x)&lt;/p&gt;
&lt;p&gt;class SmallCNN(nn.Module):
def &lt;strong&gt;init&lt;/strong&gt;(self, num_classes: int = 10) -&amp;gt; None:
super().&lt;strong&gt;init&lt;/strong&gt;()
self.features = nn.Sequential(
nn.Conv2d(1, 16, kernel_size=3, padding=1),
nn.ReLU(),
nn.MaxPool2d(2),
nn.Conv2d(16, 32, kernel_size=3, padding=1),
nn.ReLU(),
nn.MaxPool2d(2),
)
self.classifier = nn.Sequential(
nn.Flatten(),
nn.Linear(32 * 2 * 2, 64),
nn.ReLU(),
nn.Linear(64, num_classes),
)&lt;/p&gt;
&lt;p&gt;plain def forward(self, x: torch.Tensor) -&amp;gt; torch.Tensor:
return self.classifier(self.features(x))&lt;/p&gt;
&lt;p&gt;def build_model(model_type: str, config: dict) -&amp;gt; nn.Module:
if model_type == “mlp”:
return MLP(hidden_dim=config[“model”][“hidden_dim”])
if model_type == “cnn”:
return SmallCNN()
raise ValueError(f”unknown model type: {model_type}”)&lt;/p&gt;
&lt;p&gt;def build_valid_loader(config: dict, model_type: str) -&amp;gt; DataLoader:
digits = load_digits()
_, x_valid, _, y_valid = train_test_split(
digits.images,
digits.target,
test_size=config[“train”][“valid_size”],
stratify=digits.target,
random_state=config[“train”][“seed”],
)
dataset = DigitsDataset(x_valid, y_valid, model_type)
return DataLoader(dataset, batch_size=config[“train”][“batch_size”], shuffle=False)&lt;/p&gt;
&lt;p&gt;def parse_args() -&amp;gt; argparse.Namespace:
parser = argparse.ArgumentParser()
parser.add_argument(“—checkpoint”, type=Path, required=True)
return parser.parse_args()&lt;/p&gt;
&lt;p&gt;def main() -&amp;gt; None:
args = parse_args()
checkpoint = torch.load(args.checkpoint, map_location=“cpu”, weights_only=False)
model_type = checkpoint[“model_type”]
config = checkpoint[“config”]&lt;/p&gt;
&lt;p&gt;plain model = build_model(model_type, config)
model.load_state_dict(checkpoint[“model_state_dict”])
model.eval()&lt;/p&gt;
&lt;p&gt;plain loader = build_valid_loader(config, model_type)
all_predictions: list[int] = []
all_labels: list[int] = []&lt;/p&gt;
&lt;p&gt;plain with torch.no_grad():
for images, labels in loader:
logits = model(images)
all_predictions.extend(logits.argmax(dim=1).tolist())
all_labels.extend(labels.tolist())&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;print(f&amp;amp;quot;checkpoint={args.checkpoint}&amp;amp;quot;)&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;print(f&amp;amp;quot;accuracy={accuracy_score(all_labels, all_predictions):.3f}&amp;amp;quot;)&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;print(classification_report(all_labels, all_predictions, digits=3))&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;/code&gt;&lt;p&gt;&lt;code&gt;if &lt;strong&gt;name&lt;/strong&gt; == “&lt;strong&gt;main&lt;/strong&gt;”:
main()
&lt;/code&gt;&lt;/p&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;&lt;/p&gt;
&lt;p&gt;评估 MLP checkpoint：&lt;/p&gt;
&lt;div&gt;&lt;div&gt;&lt;span&gt;omp&lt;/span&gt;&lt;span&gt;fish&lt;/span&gt;&lt;/div&gt;&lt;pre&gt;&lt;code&gt;source .venv/bin/activate.fish
python src/evaluate.py --checkpoint checkpoints/best_mlp.pt
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;评估 CNN checkpoint：&lt;/p&gt;
&lt;div&gt;&lt;div&gt;&lt;span&gt;omp&lt;/span&gt;&lt;span&gt;fish&lt;/span&gt;&lt;/div&gt;&lt;pre&gt;&lt;code&gt;source .venv/bin/activate.fish
python src/evaluate.py --checkpoint checkpoints/best_cnn.pt
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;h2&gt;8. 看训练产物&lt;/h2&gt;
&lt;p&gt;训练后检查：&lt;/p&gt;
&lt;div&gt;&lt;div&gt;&lt;span&gt;omp&lt;/span&gt;&lt;span&gt;fish&lt;/span&gt;&lt;/div&gt;&lt;pre&gt;&lt;code&gt;ls checkpoints
ls reports
ls figures
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;应该看到类似：&lt;/p&gt;
&lt;div&gt;&lt;div&gt;&lt;span&gt;omp&lt;/span&gt;&lt;span&gt;prompt&lt;/span&gt;&lt;/div&gt;&lt;pre&gt;&lt;code&gt;checkpoints/best_mlp.pt
reports/metrics_mlp.csv
figures/loss_curve_mlp.png
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;用 VS Code 打开 &lt;code&gt;figures/loss_curve_mlp.png&lt;/code&gt;。你要观察：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;train_loss&lt;/code&gt; 是否下降。&lt;/li&gt;
&lt;li&gt;&lt;code&gt;valid_loss&lt;/code&gt; 是否下降后变平。&lt;/li&gt;
&lt;li&gt;如果 &lt;code&gt;train_loss&lt;/code&gt; 下降但 &lt;code&gt;valid_loss&lt;/code&gt; 上升，可能过拟合。&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;9. README 最小内容&lt;/h2&gt;
&lt;p&gt;更新项目根目录的 &lt;code&gt;README.md&lt;/code&gt;，至少包含：&lt;/p&gt;
&lt;div&gt;&lt;div&gt;&lt;span&gt;omp&lt;/span&gt;&lt;span&gt;markdown&lt;/span&gt;&lt;/div&gt;&lt;pre&gt;&lt;code&gt;# Digits Deep Learning Baseline
&lt;h2&gt;Task&lt;a href=&quot;#task&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Classify 8x8 handwritten digit images from sklearn digits.&lt;/p&gt;
&lt;h2&gt;Environment&lt;a href=&quot;#environment&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;CachyOS&lt;/li&gt;
&lt;li&gt;Python managed by uv&lt;/li&gt;
&lt;li&gt;PyTorch CPU training&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;How to Run&lt;a href=&quot;#how-to-run&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;uv&lt;/span&gt;&lt;span&gt; venv&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;source&lt;/span&gt;&lt;span&gt; .venv/bin/activate.fish&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;uv&lt;/span&gt;&lt;span&gt; sync&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;python&lt;/span&gt;&lt;span&gt; src/train.py --model mlp --epochs 20&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;python&lt;/span&gt;&lt;span&gt; src/evaluate.py --checkpoint checkpoints/best_mlp.pt&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;Results&lt;a href=&quot;#results&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;




















&lt;table&gt;&lt;thead&gt;&lt;tr&gt;&lt;th&gt;Model&lt;/th&gt;&lt;th&gt;Best Valid Accuracy&lt;/th&gt;&lt;th&gt;Notes&lt;/th&gt;&lt;/tr&gt;&lt;/thead&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td&gt;MLP&lt;/td&gt;&lt;td&gt;fill after training&lt;/td&gt;&lt;td&gt;CPU baseline&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;CNN&lt;/td&gt;&lt;td&gt;fill after training&lt;/td&gt;&lt;td&gt;small convolution baseline&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;
&lt;h2&gt;Limitations&lt;a href=&quot;#limitations&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;
&lt;/code&gt;&lt;ul&gt;&lt;code&gt;
&lt;li&gt;Dataset is small and clean.&lt;/li&gt;
&lt;li&gt;No external test set.&lt;/li&gt;
&lt;/code&gt;&lt;li&gt;&lt;code&gt;Hyperparameter search is minimal.
&lt;/code&gt;&lt;/li&gt;&lt;/ul&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;如果 Markdown 中嵌套代码块让 VS Code 高亮混乱，可以先只写命令，不必追求复杂排版。&lt;/p&gt;
&lt;h2&gt;10. 练习&lt;/h2&gt;
&lt;ol&gt;
&lt;li&gt;比较 &lt;code&gt;mlp&lt;/code&gt; 和 &lt;code&gt;cnn&lt;/code&gt; 的 best validation accuracy。&lt;/li&gt;
&lt;li&gt;把 &lt;code&gt;configs/digits.toml&lt;/code&gt; 中 &lt;code&gt;learning_rate&lt;/code&gt; 改成 &lt;code&gt;0.01&lt;/code&gt;，观察 loss 是否更不稳定。&lt;/li&gt;
&lt;li&gt;把 &lt;code&gt;hidden_dim&lt;/code&gt; 从 &lt;code&gt;128&lt;/code&gt; 改成 &lt;code&gt;32&lt;/code&gt;，比较 MLP 表现。&lt;/li&gt;
&lt;li&gt;把训练 epoch 从 5 增加到 20，观察是否过拟合。&lt;/li&gt;
&lt;li&gt;在 README 里写 3 句话解释为什么 CNN 可能比 MLP 更适合图像。&lt;/li&gt;
&lt;/ol&gt;
&lt;h2&gt;11. 验收检查&lt;/h2&gt;
&lt;p&gt;在项目根目录执行：&lt;/p&gt;
&lt;div&gt;&lt;div&gt;&lt;span&gt;omp&lt;/span&gt;&lt;span&gt;fish&lt;/span&gt;&lt;/div&gt;&lt;pre&gt;&lt;code&gt;source .venv/bin/activate.fish
test -f src/train.py; and test -f src/evaluate.py; and test -f configs/digits.toml
python src/train.py --model mlp --epochs 3
python src/evaluate.py --checkpoint checkpoints/best_mlp.pt
test -f reports/metrics_mlp.csv; and test -f figures/loss_curve_mlp.png
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;通过标准：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;能训练 MLP baseline。&lt;/li&gt;
&lt;li&gt;训练后生成 checkpoint、CSV 指标、loss 曲线。&lt;/li&gt;
&lt;li&gt;&lt;code&gt;evaluate.py&lt;/code&gt; 能读取 checkpoint 并打印 accuracy。&lt;/li&gt;
&lt;li&gt;README 说明了任务、运行方式、结果和限制。&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;12. 常见错误&lt;/h2&gt;
&lt;h3&gt;12.1 忘记激活 fish 虚拟环境&lt;/h3&gt;
&lt;p&gt;每次打开新终端后，先进入项目目录，再执行：&lt;/p&gt;
&lt;div&gt;&lt;div&gt;&lt;span&gt;omp&lt;/span&gt;&lt;span&gt;fish&lt;/span&gt;&lt;/div&gt;&lt;pre&gt;&lt;code&gt;source .venv/bin/activate.fish
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;h3&gt;12.2 checkpoint 路径写错&lt;/h3&gt;
&lt;p&gt;先检查文件名：&lt;/p&gt;
&lt;div&gt;&lt;div&gt;&lt;span&gt;omp&lt;/span&gt;&lt;span&gt;fish&lt;/span&gt;&lt;/div&gt;&lt;pre&gt;&lt;code&gt;ls checkpoints
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;如果只训练了 MLP，就不会有 &lt;code&gt;best_cnn.pt&lt;/code&gt;。&lt;/p&gt;
&lt;h3&gt;12.3 MLP 和 CNN 输入 shape 混用&lt;/h3&gt;
&lt;p&gt;MLP 输入是 &lt;code&gt;[batch, 64]&lt;/code&gt;，CNN 输入是 &lt;code&gt;[batch, 1, 8, 8]&lt;/code&gt;。如果 checkpoint 是 MLP，就必须用 MLP 结构加载；如果 checkpoint 是 CNN，就必须用 CNN 结构加载。&lt;/p&gt;
&lt;h3&gt;12.4 只保存权重，没有保存配置&lt;/h3&gt;
&lt;p&gt;只保存 &lt;code&gt;state_dict&lt;/code&gt; 不够。你还需要保存 &lt;code&gt;model_type&lt;/code&gt; 和训练配置，否则评估脚本不知道该重建哪种模型。&lt;/p&gt;
&lt;h3&gt;12.5 loss 曲线没有生成&lt;/h3&gt;
&lt;p&gt;确认 &lt;code&gt;matplotlib&lt;/code&gt; 已安装，并且训练脚本真的跑到了最后：&lt;/p&gt;
&lt;div&gt;&lt;div&gt;&lt;span&gt;omp&lt;/span&gt;&lt;span&gt;fish&lt;/span&gt;&lt;/div&gt;&lt;pre&gt;&lt;code&gt;uv add matplotlib
python src/train.py --model mlp --epochs 3
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;h2&gt;13. 下一步&lt;/h2&gt;
&lt;p&gt;进入 Week 11：把一个 AI 比赛或课程项目整理成可以展示的工程项目。你会清理目录、抽出配置、固定随机种子、写训练入口和 inference 脚本，让项目从“能在我电脑上跑”变成“别人也能复现”。&lt;/p&gt;
&lt;p&gt;plain &lt;/p&gt;&lt;/article&gt;&lt;p&gt;&lt;/p&gt;
  &lt;/div&gt;
&lt;/section&gt;</content:encoded><category>category:工具</category><category>category:深度学习</category><category>tag:PyTorch</category><category>tag:MLP</category><category>tag:CNN</category><category>tag:checkpoint</category><category>tag:evaluation</category></item><item><title>Week 09：PyTorch 基础——从 Tensor 到完整训练循环</title><link>https://39miku.space/post/week09-pytorch-basics</link><guid isPermaLink="false">week09-pytorch-basics</guid><description>Week 09：PyTorch 基础——从 Tensor 到完整训练循环。fish-first 终端教学，面向 CachyOS、VS Code、uv 和 Python 学习路线。</description><pubDate>Sun, 26 Jul 2026 01:00:00 GMT</pubDate><content:encoded>
&lt;section&gt;
  &lt;div&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;strong&gt;Oh My Pi&lt;/strong&gt; / weekly tutorial / week09-pytorch-basics&lt;/span&gt;&lt;/div&gt;
  &lt;div&gt;
    &lt;div&gt;&lt;span&gt;miku@cachyos&lt;/span&gt;&lt;span&gt;:~/Code/python-learning&lt;/span&gt;&lt;span&gt;$&lt;/span&gt; &lt;span&gt;omp teach week09-pytorch-basics --fish-first --step-by-step&lt;/span&gt;&lt;/div&gt;
    &lt;div&gt;
      &lt;div&gt;&lt;span&gt;source&lt;/span&gt;&lt;strong&gt;516 行教学文档&lt;/strong&gt;&lt;/div&gt;
      &lt;div&gt;&lt;span&gt;week&lt;/span&gt;&lt;strong&gt;Week 09&lt;/strong&gt;&lt;/div&gt;
      &lt;div&gt;&lt;span&gt;shell&lt;/span&gt;&lt;strong&gt;fish-first 命令版&lt;/strong&gt;&lt;/div&gt;
      &lt;div&gt;&lt;span&gt;backlink&lt;/span&gt;&lt;strong&gt;&lt;a href=&quot;/post/ustc-stat-ai-quant-plan/&quot;&gt;20 周计划&lt;/a&gt;&lt;/strong&gt;&lt;/div&gt;
    &lt;/div&gt;
    &lt;article&gt;
&lt;h1&gt;Week 09：PyTorch 基础——从 Tensor 到完整训练循环&lt;/h1&gt;
&lt;blockquote&gt;
&lt;p&gt;回到总路线：&lt;a href=&quot;/post/ustc-stat-ai-quant-plan/&quot;&gt;USTC 统计 AI / 量化 20 周成长计划&lt;/a&gt;&lt;br /&gt;本周目标对应计划中的 Week 09：Tensor、Dataset / DataLoader、&lt;code&gt;nn.Module&lt;/code&gt;、loss、optimizer、train / eval loop。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;本教程默认你在 &lt;strong&gt;CachyOS + fish shell + VS Code + uv&lt;/strong&gt; 环境下学习。所有命令默认都是 fish；如果你使用 Bash / Zsh，需要自己换成对应激活脚本。&lt;/p&gt;
&lt;p&gt;&lt;a&gt;&lt;/a&gt;&lt;/p&gt;
&lt;h2&gt;0. 本周详细教学：语法、规范、验收&lt;/h2&gt;
&lt;blockquote&gt;
&lt;p&gt;本节不是追加在尾部的复习，而是本周正文的入口。先读这里，再做后面的命令和项目。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h3&gt;0.1 本周真正要学会什么&lt;/h3&gt;
&lt;table&gt;
&lt;thead&gt;&lt;tr&gt;&lt;th&gt;维度&lt;/th&gt;&lt;th&gt;要求&lt;/th&gt;&lt;/tr&gt;&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;&lt;td&gt;知识点&lt;/td&gt;&lt;td&gt;Tensor、Dataset、DataLoader、nn.Module、loss、optimizer&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;代码语法&lt;/td&gt;&lt;td&gt;能从空文件写出本周核心脚本，而不是只复制运行&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;程序规范&lt;/td&gt;&lt;td&gt;函数拆分、路径清楚、输入输出明确、错误能解释&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;交付物&lt;/td&gt;&lt;td&gt;&lt;code&gt;src/pytorch_mlp.py&lt;/code&gt;&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;验收方式&lt;/td&gt;&lt;td&gt;从 fish 终端运行命令，得到可复查的文件或指标&lt;/td&gt;&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;h3&gt;0.2 代码语法精讲&lt;/h3&gt;
&lt;p&gt;下面的代码不是最终答案，而是本周必须理解的最小骨架：&lt;/p&gt;
&lt;div&gt;&lt;div&gt;&lt;span&gt;omp&lt;/span&gt;&lt;span&gt;python&lt;/span&gt;&lt;/div&gt;&lt;pre&gt;&lt;code&gt;for epoch in range(epochs):
    model.train()
    for xb, yb in train_loader:
        logits = model(xb)
        loss = criterion(logits, yb)
        optimizer.zero_grad()
        loss.backward()
        optimizer.step()
&lt;/code&gt;&lt;p&gt;&lt;code&gt;plain model.eval()
with torch.no_grad():
valid_logits = model(X_valid_tensor)
&lt;/code&gt;&lt;/p&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;&lt;/p&gt;
&lt;p&gt;读代码时按四步检查：输入从哪里来；中间变量的类型和 shape 是什么；函数或脚本输出什么；哪些错误应该显式报出来。&lt;/p&gt;
&lt;h3&gt;0.3 本周程序规范&lt;/h3&gt;
&lt;ul&gt;&lt;li&gt;所有路径用相对路径或 `pathlib.Path`，不要写死 `/home/miku/...`。&lt;/li&gt;&lt;li&gt;核心逻辑进 `src/`，notebook 只做探索和解释。&lt;/li&gt;&lt;li&gt;每个脚本能从 fish 终端运行，并在 README 写出命令。&lt;/li&gt;&lt;li&gt;输出必须落盘到 `reports/`、`figures/` 或 `outputs/`，不能只在屏幕上看。&lt;/li&gt;&lt;/ul&gt;
&lt;h3&gt;0.4 本周练习分层&lt;/h3&gt;
&lt;table&gt;
&lt;thead&gt;&lt;tr&gt;&lt;th&gt;层级&lt;/th&gt;&lt;th&gt;任务&lt;/th&gt;&lt;th&gt;不合格表现&lt;/th&gt;&lt;th&gt;合格验收&lt;/th&gt;&lt;/tr&gt;&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;&lt;td&gt;最小练习&lt;/td&gt;&lt;td&gt;手写上面的最小骨架&lt;/td&gt;&lt;td&gt;只在 notebook 里运行&lt;/td&gt;&lt;td&gt;终端运行成功&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;标准练习&lt;/td&gt;&lt;td&gt;把逻辑拆成函数/模块&lt;/td&gt;&lt;td&gt;一个大脚本从头写到尾&lt;/td&gt;&lt;td&gt;至少 2 个函数，职责清楚&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;项目练习&lt;/td&gt;&lt;td&gt;生成本周交付物 &lt;code&gt;src/pytorch_mlp.py&lt;/code&gt;&lt;/td&gt;&lt;td&gt;只有屏幕输出&lt;/td&gt;&lt;td&gt;文件落盘，可复查&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;复盘练习&lt;/td&gt;&lt;td&gt;写 3 个错误和修复&lt;/td&gt;&lt;td&gt;只写“已解决”&lt;/td&gt;&lt;td&gt;写清报错、原因、修复、预防&lt;/td&gt;&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;h3&gt;0.5 本周和主线的连接&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;回到总计划：&lt;a href=&quot;/post/ustc-stat-ai-quant-plan/#week-plan&quot;&gt;USTC AI / Quant 练习手册&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;查详细练习索引：&lt;a href=&quot;/post/ustc-stat-ai-quant-plan/#deep-practice-appendix&quot;&gt;技术练习详解&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;查质量评分：&lt;a href=&quot;/post/ustc-stat-ai-quant-plan/#quality-final-gates&quot;&gt;最终质量门槛&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;1. 本周目标&lt;/h2&gt;
&lt;p&gt;完成后你应该能做到：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;知道 PyTorch 的 Tensor 和 NumPy array 有什么相似与不同。&lt;/li&gt;
&lt;li&gt;会把表格数据包装成 &lt;code&gt;Dataset&lt;/code&gt;，再交给 &lt;code&gt;DataLoader&lt;/code&gt; 分批训练。&lt;/li&gt;
&lt;li&gt;会写一个最小但完整的 &lt;code&gt;nn.Module&lt;/code&gt;。&lt;/li&gt;
&lt;li&gt;会写 CPU-first 的训练循环：&lt;code&gt;model.train()&lt;/code&gt;、前向传播、loss、反向传播、optimizer step。&lt;/li&gt;
&lt;li&gt;会写验证循环：&lt;code&gt;model.eval()&lt;/code&gt;、&lt;code&gt;torch.no_grad()&lt;/code&gt;、accuracy。&lt;/li&gt;
&lt;li&gt;留下可运行的 &lt;code&gt;src/pytorch_mlp.py&lt;/code&gt; 和学习笔记。&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;本周不追求高精度，也不追求 GPU。重点是把深度学习训练的骨架写明白。&lt;/p&gt;
&lt;h2&gt;2. 前置条件&lt;/h2&gt;
&lt;p&gt;你需要已经具备：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;VS Code 能打开项目目录。&lt;/li&gt;
&lt;li&gt;终端默认 shell 是 fish。&lt;/li&gt;
&lt;li&gt;&lt;code&gt;uv&lt;/code&gt; 可用。&lt;/li&gt;
&lt;li&gt;会运行普通 Python 脚本。&lt;/li&gt;
&lt;li&gt;知道监督学习中的 train / validation split。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;先确认工具：&lt;/p&gt;
&lt;div&gt;&lt;div&gt;&lt;span&gt;omp&lt;/span&gt;&lt;span&gt;fish&lt;/span&gt;&lt;/div&gt;&lt;pre&gt;&lt;code&gt;python --version
uv --version
fish --version
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;如果这些命令都能输出版本号，就可以继续。&lt;/p&gt;
&lt;h2&gt;3. 建立 Week 09 项目&lt;/h2&gt;
&lt;p&gt;建议每周一个独立目录，避免依赖混在一起。&lt;/p&gt;
&lt;div&gt;&lt;div&gt;&lt;span&gt;omp&lt;/span&gt;&lt;span&gt;fish&lt;/span&gt;&lt;/div&gt;&lt;pre&gt;&lt;code&gt;mkdir -p ~/Code/ustc-ai/week09-pytorch-basics
cd ~/Code/ustc-ai/week09-pytorch-basics
uv init --name week09-pytorch-basics
uv venv
source .venv/bin/activate.fish
uv add torch scikit-learn matplotlib
code .
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;逐句解释：&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;命令&lt;/th&gt;
&lt;th&gt;作用&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;&lt;tr&gt;
&lt;td&gt;&lt;code&gt;mkdir -p&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;创建项目目录；如果上级目录不存在就一起创建&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;cd&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;进入本周项目目录&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;uv init&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;生成 &lt;code&gt;pyproject.toml&lt;/code&gt; 等项目文件&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;uv venv&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;创建当前项目专用的 &lt;code&gt;.venv&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;source .venv/bin/activate.fish&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;用 fish 方式激活虚拟环境&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;uv add ...&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;安装本周需要的依赖&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;code .&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;用 VS Code 打开当前目录&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;&lt;/table&gt;
&lt;p&gt;检查 PyTorch 是否可用：&lt;/p&gt;
&lt;div&gt;&lt;div&gt;&lt;span&gt;omp&lt;/span&gt;&lt;span&gt;fish&lt;/span&gt;&lt;/div&gt;&lt;pre&gt;&lt;code&gt;python -c &quot;import torch; print(torch.__version__); print(&apos;cuda:&apos;, torch.cuda.is_available())&quot;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;在 CachyOS 笔记本上，&lt;code&gt;cuda: False&lt;/code&gt; 很正常。本教程所有代码都按 CPU 设计。&lt;/p&gt;
&lt;h2&gt;4. 推荐文件布局&lt;/h2&gt;
&lt;p&gt;创建本周目录结构：&lt;/p&gt;
&lt;div&gt;&lt;div&gt;&lt;span&gt;omp&lt;/span&gt;&lt;span&gt;fish&lt;/span&gt;&lt;/div&gt;&lt;pre&gt;&lt;code&gt;mkdir -p src notes outputs
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;目标布局：&lt;/p&gt;
&lt;div&gt;&lt;div&gt;&lt;span&gt;omp&lt;/span&gt;&lt;span&gt;prompt&lt;/span&gt;&lt;/div&gt;&lt;pre&gt;&lt;code&gt;week09-pytorch-basics/
├── .venv/
├── pyproject.toml
├── src/
│   ├── tensor_basics.py
│   └── pytorch_mlp.py
├── notes/
│   └── week09_pytorch.md
└── outputs/
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;文件用途：&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;文件&lt;/th&gt;
&lt;th&gt;用途&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;&lt;tr&gt;
&lt;td&gt;&lt;code&gt;src/tensor_basics.py&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;练习 Tensor、shape、dtype、autograd&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;src/pytorch_mlp.py&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;本周核心：Dataset、DataLoader、MLP、训练循环&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;notes/week09_pytorch.md&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;记录你理解的训练流程和错误&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;outputs/&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;以后放日志、图、模型文件&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;&lt;/table&gt;
&lt;h2&gt;5. Tensor 入门：先理解数据容器&lt;/h2&gt;
&lt;p&gt;在 VS Code 中创建 &lt;code&gt;src/tensor_basics.py&lt;/code&gt;：&lt;/p&gt;
&lt;div&gt;&lt;div&gt;&lt;span&gt;omp&lt;/span&gt;&lt;span&gt;python&lt;/span&gt;&lt;/div&gt;&lt;pre&gt;&lt;code&gt;import torch
&lt;p&gt;x = torch.tensor([[1.0, 2.0], [3.0, 4.0]])
y = torch.ones((2, 2))&lt;/p&gt;
&lt;p&gt;print(“x =”)
print(x)
print(“shape:”, x.shape)
print(“dtype:”, x.dtype)
print(“x + y =”)
print(x + y)
print(“x @ y =”)
print(x @ y)&lt;/p&gt;
&lt;h1&gt;autograd: PyTorch 自动记录计算图，用于反向传播&lt;a href=&quot;#autograd-pytorch-自动记录计算图用于反向传播&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/h1&gt;
&lt;p&gt;w = torch.tensor([2.0], requires_grad=True)
b = torch.tensor([1.0], requires_grad=True)
feature = torch.tensor([3.0])
target = torch.tensor([10.0])&lt;/p&gt;
&lt;p&gt;prediction = w * feature + b
loss = (prediction - target).pow(2).mean()
loss.backward()&lt;/p&gt;
&lt;/code&gt;&lt;p&gt;&lt;code&gt;print(“prediction:”, prediction.item())
print(“loss:”, loss.item())
print(“d loss / d w:”, w.grad.item())
print(“d loss / d b:”, b.grad.item())
&lt;/code&gt;&lt;/p&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;&lt;/p&gt;
&lt;p&gt;运行：&lt;/p&gt;
&lt;div&gt;&lt;div&gt;&lt;span&gt;omp&lt;/span&gt;&lt;span&gt;fish&lt;/span&gt;&lt;/div&gt;&lt;pre&gt;&lt;code&gt;source .venv/bin/activate.fish
python src/tensor_basics.py
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;你需要重点观察：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;shape&lt;/code&gt;：深度学习代码里最常见的错误来源。&lt;/li&gt;
&lt;li&gt;&lt;code&gt;dtype&lt;/code&gt;：模型通常使用 &lt;code&gt;float32&lt;/code&gt;，标签分类通常使用 &lt;code&gt;long&lt;/code&gt;。&lt;/li&gt;
&lt;li&gt;&lt;code&gt;requires_grad=True&lt;/code&gt;：告诉 PyTorch 这个 Tensor 需要梯度。&lt;/li&gt;
&lt;li&gt;&lt;code&gt;loss.backward()&lt;/code&gt;：从 loss 反向计算所有参数的梯度。&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;6. Dataset 和 DataLoader：把数据交给训练循环&lt;/h2&gt;
&lt;p&gt;PyTorch 不希望你在训练循环里手动切片全部数据。更常见的结构是：&lt;/p&gt;
&lt;div&gt;&lt;div&gt;&lt;span&gt;omp&lt;/span&gt;&lt;span&gt;prompt&lt;/span&gt;&lt;/div&gt;&lt;pre&gt;&lt;code&gt;原始数据 -&amp;gt; Dataset -&amp;gt; DataLoader -&amp;gt; 每个 batch 的 x, y -&amp;gt; model
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;概念区分：&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;名称&lt;/th&gt;
&lt;th&gt;作用&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;&lt;tr&gt;
&lt;td&gt;&lt;code&gt;Dataset&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;定义“第 i 条样本怎么取”&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;DataLoader&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;负责 batch、shuffle、迭代&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;batch_size&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;每次给模型多少条样本&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;shuffle=True&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;每个 epoch 打乱训练样本顺序&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;&lt;/table&gt;
&lt;h2&gt;7. 写一个完整的 CPU-first MLP&lt;/h2&gt;
&lt;p&gt;创建 &lt;code&gt;src/pytorch_mlp.py&lt;/code&gt;：&lt;/p&gt;
&lt;div&gt;&lt;div&gt;&lt;span&gt;omp&lt;/span&gt;&lt;span&gt;python&lt;/span&gt;&lt;/div&gt;&lt;pre&gt;&lt;code&gt;from __future__ import annotations
&lt;p&gt;import argparse
import random
from dataclasses import dataclass&lt;/p&gt;
&lt;p&gt;import numpy as np
import torch
from sklearn.datasets import make_classification
from sklearn.model_selection import train_test_split
from sklearn.preprocessing import StandardScaler
from torch import nn
from torch.utils.data import DataLoader, Dataset&lt;/p&gt;
&lt;p&gt;@dataclass(frozen=True)
class TrainConfig:
epochs: int
batch_size: int
lr: float
seed: int&lt;/p&gt;
&lt;p&gt;class TabularClassificationDataset(Dataset):
def &lt;strong&gt;init&lt;/strong&gt;(self, features: np.ndarray, labels: np.ndarray) -&amp;gt; None:
self.features = torch.tensor(features, dtype=torch.float32)
self.labels = torch.tensor(labels, dtype=torch.long)&lt;/p&gt;
&lt;p&gt;plain def &lt;strong&gt;len&lt;/strong&gt;(self) -&amp;gt; int:
return len(self.labels)&lt;/p&gt;
&lt;p&gt;plain def &lt;strong&gt;getitem&lt;/strong&gt;(self, index: int) -&amp;gt; tuple[torch.Tensor, torch.Tensor]:
return self.features[index], self.labels[index]&lt;/p&gt;
&lt;p&gt;class MLP(nn.Module):
def &lt;strong&gt;init&lt;/strong&gt;(self, input_dim: int, hidden_dim: int, num_classes: int) -&amp;gt; None:
super().&lt;strong&gt;init&lt;/strong&gt;()
self.net = nn.Sequential(
nn.Linear(input_dim, hidden_dim),
nn.ReLU(),
nn.Linear(hidden_dim, hidden_dim),
nn.ReLU(),
nn.Linear(hidden_dim, num_classes),
)&lt;/p&gt;
&lt;p&gt;plain def forward(self, x: torch.Tensor) -&amp;gt; torch.Tensor:
return self.net(x)&lt;/p&gt;
&lt;p&gt;def set_seed(seed: int) -&amp;gt; None:
random.seed(seed)
np.random.seed(seed)
torch.manual_seed(seed)&lt;/p&gt;
&lt;p&gt;def build_loaders(config: TrainConfig) -&amp;gt; tuple[DataLoader, DataLoader, int, int]:
x, y = make_classification(
n_samples=1200,
n_features=20,
n_informative=10,
n_redundant=4,
n_classes=2,
random_state=config.seed,
)
x_train, x_valid, y_train, y_valid = train_test_split(
x,
y,
test_size=0.2,
stratify=y,
random_state=config.seed,
)&lt;/p&gt;
&lt;p&gt;plain scaler = StandardScaler()
x_train = scaler.fit_transform(x_train)
x_valid = scaler.transform(x_valid)&lt;/p&gt;
&lt;p&gt;plain train_dataset = TabularClassificationDataset(x_train, y_train)
valid_dataset = TabularClassificationDataset(x_valid, y_valid)&lt;/p&gt;
&lt;p&gt;plain train_loader = DataLoader(
train_dataset,
batch_size=config.batch_size,
shuffle=True,
)
valid_loader = DataLoader(
valid_dataset,
batch_size=config.batch_size,
shuffle=False,
)
return train_loader, valid_loader, x_train.shape[1], len(np.unique(y))&lt;/p&gt;
&lt;p&gt;def train_one_epoch(
model: nn.Module,
loader: DataLoader,
criterion: nn.Module,
optimizer: torch.optim.Optimizer,
device: torch.device,
) -&amp;gt; float:
model.train()
total_loss = 0.0
total_examples = 0&lt;/p&gt;
&lt;p&gt;plain for features, labels in loader:
features = features.to(device)
labels = labels.to(device)&lt;/p&gt;
&lt;p&gt;plain     logits = model(features)
loss = criterion(logits, labels)&lt;/p&gt;
&lt;p&gt;plain     optimizer.zero_grad()
loss.backward()
optimizer.step()&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;    total_loss += loss.item() * len(labels)&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    total_examples += len(labels)&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;return total_loss / total_examples&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;def evaluate(model: nn.Module, loader: DataLoader, criterion: nn.Module, device: torch.device) -&amp;gt; tuple[float, float]:
model.eval()
total_loss = 0.0
total_correct = 0
total_examples = 0&lt;/p&gt;
&lt;p&gt;plain with torch.no_grad():
for features, labels in loader:
features = features.to(device)
labels = labels.to(device)&lt;/p&gt;
&lt;p&gt;plain         logits = model(features)
loss = criterion(logits, labels)
predictions = logits.argmax(dim=1)&lt;/p&gt;
&lt;p&gt;plain         total_loss += loss.item() * len(labels)
total_correct += (predictions == labels).sum().item()
total_examples += len(labels)&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;return total_loss / total_examples, total_correct / total_examples&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;def parse_args() -&amp;gt; TrainConfig:
parser = argparse.ArgumentParser()
parser.add_argument(“—epochs”, type=int, default=20)
parser.add_argument(“—batch-size”, type=int, default=32)
parser.add_argument(“—lr”, type=float, default=0.01)
parser.add_argument(“—seed”, type=int, default=42)
args = parser.parse_args()
return TrainConfig(
epochs=args.epochs,
batch_size=args.batch_size,
lr=args.lr,
seed=args.seed,
)&lt;/p&gt;
&lt;p&gt;def main() -&amp;gt; None:
config = parse_args()
set_seed(config.seed)&lt;/p&gt;
&lt;p&gt;plain device = torch.device(“cpu”)
train_loader, valid_loader, input_dim, num_classes = build_loaders(config)
model = MLP(input_dim=input_dim, hidden_dim=32, num_classes=num_classes).to(device)
criterion = nn.CrossEntropyLoss()
optimizer = torch.optim.Adam(model.parameters(), lr=config.lr)&lt;/p&gt;
&lt;p&gt;plain for epoch in range(1, config.epochs + 1):
train_loss = train_one_epoch(model, train_loader, criterion, optimizer, device)
valid_loss, valid_acc = evaluate(model, valid_loader, criterion, device)
print(
f”epoch={epoch&lt;/p&gt;&lt;div&gt;&lt;/div&gt;} ”
f”train_loss={train_loss:.4f} ”
f”valid_loss={valid_loss:.4f} ”
f”valid_acc={valid_acc:.3f}”
)&lt;p&gt;&lt;/p&gt;
&lt;/code&gt;&lt;p&gt;&lt;code&gt;if &lt;strong&gt;name&lt;/strong&gt; == “&lt;strong&gt;main&lt;/strong&gt;”:
main()
&lt;/code&gt;&lt;/p&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;&lt;/p&gt;
&lt;p&gt;运行一个短训练：&lt;/p&gt;
&lt;div&gt;&lt;div&gt;&lt;span&gt;omp&lt;/span&gt;&lt;span&gt;fish&lt;/span&gt;&lt;/div&gt;&lt;pre&gt;&lt;code&gt;source .venv/bin/activate.fish
python src/pytorch_mlp.py --epochs 5 --batch-size 32 --lr 0.01
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;如果你看到类似下面的输出，说明训练循环已经跑通：&lt;/p&gt;
&lt;div&gt;&lt;div&gt;&lt;span&gt;omp&lt;/span&gt;&lt;span&gt;prompt&lt;/span&gt;&lt;/div&gt;&lt;pre&gt;&lt;code&gt;epoch=01 train_loss=0.6200 valid_loss=0.5100 valid_acc=0.760
epoch=02 train_loss=0.4200 valid_loss=0.3500 valid_acc=0.850
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;数字不需要完全一致，因为初始化和环境可能不同。你要看的是：脚本能运行，loss 大体下降，验证集 accuracy 高于随机猜测。&lt;/p&gt;
&lt;h2&gt;8. 训练循环逐句解释&lt;/h2&gt;
&lt;p&gt;核心结构是：&lt;/p&gt;
&lt;div&gt;&lt;div&gt;&lt;span&gt;omp&lt;/span&gt;&lt;span&gt;python&lt;/span&gt;&lt;/div&gt;&lt;pre&gt;&lt;code&gt;for epoch in range(num_epochs):
    model.train()
    for x, y in train_loader:
        pred = model(x)
        loss = criterion(pred, y)
        optimizer.zero_grad()
        loss.backward()
        optimizer.step()
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;逐句解释：&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;代码&lt;/th&gt;
&lt;th&gt;含义&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;&lt;tr&gt;
&lt;td&gt;&lt;code&gt;model.train()&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;切换到训练模式；以后遇到 Dropout / BatchNorm 时很重要&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;for x, y in train_loader&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;从 DataLoader 取一个 batch&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;pred = model(x)&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;前向传播，得到 logits 或预测值&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;loss = criterion(pred, y)&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;计算当前 batch 的损失&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;optimizer.zero_grad()&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;清空上一轮梯度；否则梯度会累加&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;loss.backward()&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;反向传播，计算每个参数的梯度&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;optimizer.step()&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;根据梯度更新参数&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;&lt;/table&gt;
&lt;p&gt;验证循环必须不同：&lt;/p&gt;
&lt;div&gt;&lt;div&gt;&lt;span&gt;omp&lt;/span&gt;&lt;span&gt;python&lt;/span&gt;&lt;/div&gt;&lt;pre&gt;&lt;code&gt;model.eval()
with torch.no_grad():
    ...
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;原因：验证时不更新参数，也不需要保存计算图。这样更快，也更不容易写错。&lt;/p&gt;
&lt;h2&gt;9. 写学习笔记&lt;/h2&gt;
&lt;p&gt;创建 &lt;code&gt;notes/week09_pytorch.md&lt;/code&gt;，至少回答这些问题：&lt;/p&gt;
&lt;div&gt;&lt;div&gt;&lt;span&gt;omp&lt;/span&gt;&lt;span&gt;prompt&lt;/span&gt;&lt;/div&gt;&lt;pre&gt;&lt;code&gt;# Week 09 PyTorch Notes
&lt;h2&gt;Tensor&lt;a href=&quot;#tensor&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;Tensor 和 NumPy array 的共同点：&lt;/li&gt;
&lt;li&gt;Tensor 多出来的重要能力：&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;Dataset / DataLoader&lt;a href=&quot;#dataset--dataloader&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;Dataset 负责：&lt;/li&gt;
&lt;li&gt;DataLoader 负责：&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;Train Loop&lt;a href=&quot;#train-loop&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;为什么要 zero_grad：&lt;/li&gt;
&lt;li&gt;为什么 train 和 eval 要分开：&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;Errors I Met&lt;a href=&quot;#errors-i-met&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;
&lt;/code&gt;&lt;ul&gt;&lt;code&gt;
&lt;li&gt;错误信息：&lt;/li&gt;
&lt;li&gt;原因：&lt;/li&gt;
&lt;/code&gt;&lt;li&gt;&lt;code&gt;修复方式：
&lt;/code&gt;&lt;/li&gt;&lt;/ul&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;h2&gt;10. 练习&lt;/h2&gt;
&lt;ol&gt;
&lt;li&gt;把 &lt;code&gt;hidden_dim=32&lt;/code&gt; 改成 &lt;code&gt;hidden_dim=64&lt;/code&gt;，观察验证集 accuracy 是否变化。&lt;/li&gt;
&lt;li&gt;把 &lt;code&gt;lr=0.01&lt;/code&gt; 改成 &lt;code&gt;lr=0.001&lt;/code&gt;，比较 loss 下降速度。&lt;/li&gt;
&lt;li&gt;把 &lt;code&gt;batch_size=32&lt;/code&gt; 改成 &lt;code&gt;batch_size=128&lt;/code&gt;，观察每个 epoch 是否更快。&lt;/li&gt;
&lt;li&gt;在 &lt;code&gt;MLP&lt;/code&gt; 里增加一层 &lt;code&gt;nn.Dropout(0.2)&lt;/code&gt;，再比较训练集和验证集表现。&lt;/li&gt;
&lt;li&gt;故意删掉 &lt;code&gt;optimizer.zero_grad()&lt;/code&gt;，观察训练是否变得不稳定，然后恢复。&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;每次只改一个因素，否则你无法判断到底是什么导致结果变化。&lt;/p&gt;
&lt;h2&gt;11. 验收检查&lt;/h2&gt;
&lt;p&gt;在项目根目录执行：&lt;/p&gt;
&lt;div&gt;&lt;div&gt;&lt;span&gt;omp&lt;/span&gt;&lt;span&gt;fish&lt;/span&gt;&lt;/div&gt;&lt;pre&gt;&lt;code&gt;source .venv/bin/activate.fish
test -f src/pytorch_mlp.py; and test -f notes/week09_pytorch.md
python src/tensor_basics.py
python src/pytorch_mlp.py --epochs 3 --batch-size 32 --lr 0.01
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;通过标准：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;tensor_basics.py&lt;/code&gt; 能打印 Tensor、shape、dtype 和梯度。&lt;/li&gt;
&lt;li&gt;&lt;code&gt;pytorch_mlp.py&lt;/code&gt; 能完成至少 3 个 epoch。&lt;/li&gt;
&lt;li&gt;输出中包含 &lt;code&gt;train_loss&lt;/code&gt;、&lt;code&gt;valid_loss&lt;/code&gt;、&lt;code&gt;valid_acc&lt;/code&gt;。&lt;/li&gt;
&lt;li&gt;&lt;code&gt;notes/week09_pytorch.md&lt;/code&gt; 里有你自己的解释，而不是只复制代码。&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;12. 常见错误&lt;/h2&gt;
&lt;h3&gt;12.1 fish 激活脚本用错&lt;/h3&gt;
&lt;p&gt;错误做法是执行 Bash / Zsh 版激活脚本。fish 里应该执行：&lt;/p&gt;
&lt;div&gt;&lt;div&gt;&lt;span&gt;omp&lt;/span&gt;&lt;span&gt;fish&lt;/span&gt;&lt;/div&gt;&lt;pre&gt;&lt;code&gt;source .venv/bin/activate.fish
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;h3&gt;12.2 标签 dtype 不对&lt;/h3&gt;
&lt;p&gt;&lt;code&gt;nn.CrossEntropyLoss()&lt;/code&gt; 要求分类标签是整数类别，并且 dtype 通常是 &lt;code&gt;torch.long&lt;/code&gt;。如果你把标签做成 &lt;code&gt;float32&lt;/code&gt;，可能会报 dtype 错误。&lt;/p&gt;
&lt;h3&gt;12.3 忘记 &lt;code&gt;optimizer.zero_grad()&lt;/code&gt;&lt;/h3&gt;
&lt;p&gt;PyTorch 默认会累加梯度。训练循环里每个 batch 更新前都要清空上一轮梯度。&lt;/p&gt;
&lt;h3&gt;12.4 训练和验证没有分开&lt;/h3&gt;
&lt;p&gt;训练时用：&lt;/p&gt;
&lt;div&gt;&lt;div&gt;&lt;span&gt;omp&lt;/span&gt;&lt;span&gt;python&lt;/span&gt;&lt;/div&gt;&lt;pre&gt;&lt;code&gt;model.train()
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;验证时用：&lt;/p&gt;
&lt;div&gt;&lt;div&gt;&lt;span&gt;omp&lt;/span&gt;&lt;span&gt;python&lt;/span&gt;&lt;/div&gt;&lt;pre&gt;&lt;code&gt;model.eval()
with torch.no_grad():
    ...
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;不要在验证集上调用 &lt;code&gt;optimizer.step()&lt;/code&gt;。&lt;/p&gt;
&lt;h3&gt;12.5 shape 对不上&lt;/h3&gt;
&lt;p&gt;如果看到矩阵乘法 shape 错误，先打印：&lt;/p&gt;
&lt;div&gt;&lt;div&gt;&lt;span&gt;omp&lt;/span&gt;&lt;span&gt;python&lt;/span&gt;&lt;/div&gt;&lt;pre&gt;&lt;code&gt;print(features.shape)
print(logits.shape)
print(labels.shape)
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;分类任务中，&lt;code&gt;logits&lt;/code&gt; 常见 shape 是 &lt;code&gt;[batch_size, num_classes]&lt;/code&gt;，&lt;code&gt;labels&lt;/code&gt; 常见 shape 是 &lt;code&gt;[batch_size]&lt;/code&gt;。&lt;/p&gt;
&lt;h2&gt;13. 下一步&lt;/h2&gt;
&lt;p&gt;进入 Week 10：把这个最小训练脚本升级成一个小型深度学习项目。你会加入 MLP / CNN baseline、checkpoint、loss 曲线、验证集评估和独立的 &lt;code&gt;evaluate.py&lt;/code&gt;。&lt;/p&gt;
&lt;p&gt;plain &lt;/p&gt;&lt;/article&gt;&lt;p&gt;&lt;/p&gt;
  &lt;/div&gt;
&lt;/section&gt;</content:encoded><category>category:工具</category><category>category:深度学习</category><category>tag:PyTorch</category><category>tag:Tensor</category><category>tag:Dataset</category><category>tag:training-loop</category><category>tag:Autograd</category></item><item><title>Week 08：统计机器学习项目收尾、复现与报告打磨</title><link>https://39miku.space/post/week08-stat-ml-project-finish</link><guid isPermaLink="false">week08-stat-ml-project-finish</guid><description>Week 08：统计机器学习项目收尾、复现与报告打磨。fish-first 终端教学，面向 CachyOS、VS Code、uv 和 Python 学习路线。</description><pubDate>Sun, 26 Jul 2026 00:00:00 GMT</pubDate><content:encoded>
&lt;section&gt;
  &lt;div&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;strong&gt;Oh My Pi&lt;/strong&gt; / weekly tutorial / week08-stat-ml-project-finish&lt;/span&gt;&lt;/div&gt;
  &lt;div&gt;
    &lt;div&gt;&lt;span&gt;miku@cachyos&lt;/span&gt;&lt;span&gt;:~/Code/python-learning&lt;/span&gt;&lt;span&gt;$&lt;/span&gt; &lt;span&gt;omp teach week08-stat-ml-project-finish --fish-first --step-by-step&lt;/span&gt;&lt;/div&gt;
    &lt;div&gt;
      &lt;div&gt;&lt;span&gt;source&lt;/span&gt;&lt;strong&gt;610 行教学文档&lt;/strong&gt;&lt;/div&gt;
      &lt;div&gt;&lt;span&gt;week&lt;/span&gt;&lt;strong&gt;Week 08&lt;/strong&gt;&lt;/div&gt;
      &lt;div&gt;&lt;span&gt;shell&lt;/span&gt;&lt;strong&gt;fish-first 命令版&lt;/strong&gt;&lt;/div&gt;
      &lt;div&gt;&lt;span&gt;backlink&lt;/span&gt;&lt;strong&gt;&lt;a href=&quot;/post/ustc-stat-ai-quant-plan/&quot;&gt;20 周计划&lt;/a&gt;&lt;/strong&gt;&lt;/div&gt;
    &lt;/div&gt;
    &lt;article&gt;
&lt;h1&gt;Week 08：统计机器学习项目收尾、复现与报告打磨&lt;/h1&gt;
&lt;blockquote&gt;
&lt;p&gt;返回主线计划：&lt;a href=&quot;/post/ustc-stat-ai-quant-plan/&quot;&gt;USTC 统计学：AI / 量化金融 20 周终端式成长计划&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Week 07 你已经启动了一个统计机器学习项目：有项目骨架、数据卡、baseline 脚本、结果报告和错误分析。Week 08 的任务是把它整理成“可以给老师、导师、实习面试官或未来的自己看的项目”。&lt;/p&gt;
&lt;p&gt;收尾不是美化封面，而是让项目可理解、可复现、可验证、可继续改进。命令默认 fish shell。&lt;/p&gt;
&lt;p&gt;&lt;a&gt;&lt;/a&gt;&lt;/p&gt;
&lt;h2&gt;0. 本周详细教学：语法、规范、验收&lt;/h2&gt;
&lt;blockquote&gt;
&lt;p&gt;本节不是追加在尾部的复习，而是本周正文的入口。先读这里，再做后面的命令和项目。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h3&gt;0.1 本周真正要学会什么&lt;/h3&gt;
&lt;table&gt;
&lt;thead&gt;&lt;tr&gt;&lt;th&gt;维度&lt;/th&gt;&lt;th&gt;要求&lt;/th&gt;&lt;/tr&gt;&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;&lt;td&gt;知识点&lt;/td&gt;&lt;td&gt;实验表、误差分析、局限性、README&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;代码语法&lt;/td&gt;&lt;td&gt;能从空文件写出本周核心脚本，而不是只复制运行&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;程序规范&lt;/td&gt;&lt;td&gt;函数拆分、路径清楚、输入输出明确、错误能解释&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;交付物&lt;/td&gt;&lt;td&gt;&lt;code&gt;reports/error_analysis.md&lt;/code&gt;&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;验收方式&lt;/td&gt;&lt;td&gt;从 fish 终端运行命令，得到可复查的文件或指标&lt;/td&gt;&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;h3&gt;0.2 代码语法精讲&lt;/h3&gt;
&lt;p&gt;下面的代码不是最终答案，而是本周必须理解的最小骨架：&lt;/p&gt;
&lt;div&gt;&lt;div&gt;&lt;span&gt;omp&lt;/span&gt;&lt;span&gt;python&lt;/span&gt;&lt;/div&gt;&lt;pre&gt;&lt;code&gt;errors = valid_df.assign(y_true=y_valid, y_pred=pred)
errors = errors[errors.y_true != errors.y_pred]
print(errors.head(10))
print(errors.groupby(&quot;y_true&quot;).size())
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;读代码时按四步检查：输入从哪里来；中间变量的类型和 shape 是什么；函数或脚本输出什么；哪些错误应该显式报出来。&lt;/p&gt;
&lt;h3&gt;0.3 本周程序规范&lt;/h3&gt;
&lt;ul&gt;&lt;li&gt;所有路径用相对路径或 `pathlib.Path`，不要写死 `/home/miku/...`。&lt;/li&gt;&lt;li&gt;核心逻辑进 `src/`，notebook 只做探索和解释。&lt;/li&gt;&lt;li&gt;每个脚本能从 fish 终端运行，并在 README 写出命令。&lt;/li&gt;&lt;li&gt;输出必须落盘到 `reports/`、`figures/` 或 `outputs/`，不能只在屏幕上看。&lt;/li&gt;&lt;/ul&gt;
&lt;h3&gt;0.4 本周练习分层&lt;/h3&gt;
&lt;table&gt;
&lt;thead&gt;&lt;tr&gt;&lt;th&gt;层级&lt;/th&gt;&lt;th&gt;任务&lt;/th&gt;&lt;th&gt;不合格表现&lt;/th&gt;&lt;th&gt;合格验收&lt;/th&gt;&lt;/tr&gt;&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;&lt;td&gt;最小练习&lt;/td&gt;&lt;td&gt;手写上面的最小骨架&lt;/td&gt;&lt;td&gt;只在 notebook 里运行&lt;/td&gt;&lt;td&gt;终端运行成功&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;标准练习&lt;/td&gt;&lt;td&gt;把逻辑拆成函数/模块&lt;/td&gt;&lt;td&gt;一个大脚本从头写到尾&lt;/td&gt;&lt;td&gt;至少 2 个函数，职责清楚&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;项目练习&lt;/td&gt;&lt;td&gt;生成本周交付物 &lt;code&gt;reports/error_analysis.md&lt;/code&gt;&lt;/td&gt;&lt;td&gt;只有屏幕输出&lt;/td&gt;&lt;td&gt;文件落盘，可复查&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;复盘练习&lt;/td&gt;&lt;td&gt;写 3 个错误和修复&lt;/td&gt;&lt;td&gt;只写“已解决”&lt;/td&gt;&lt;td&gt;写清报错、原因、修复、预防&lt;/td&gt;&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;h3&gt;0.5 本周和主线的连接&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;回到总计划：&lt;a href=&quot;/post/ustc-stat-ai-quant-plan/#week-plan&quot;&gt;USTC AI / Quant 练习手册&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;查详细练习索引：&lt;a href=&quot;/post/ustc-stat-ai-quant-plan/#deep-practice-appendix&quot;&gt;技术练习详解&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;查质量评分：&lt;a href=&quot;/post/ustc-stat-ai-quant-plan/#quality-final-gates&quot;&gt;最终质量门槛&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;1. 本周目标&lt;/h2&gt;
&lt;p&gt;完成一个干净的项目版本：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;README 清楚说明问题、数据、方法、实验、结果、限制和运行方式。&lt;/li&gt;
&lt;li&gt;&lt;code&gt;reports/final_report.md&lt;/code&gt; 形成完整实验报告。&lt;/li&gt;
&lt;li&gt;结果表可复现，指标和 Week 07 报告一致。&lt;/li&gt;
&lt;li&gt;运行命令从空环境开始可执行。&lt;/li&gt;
&lt;li&gt;Git 提交历史清楚，不提交 &lt;code&gt;.venv/&lt;/code&gt;、缓存、大文件或隐私数据。&lt;/li&gt;
&lt;li&gt;写下下一步改进，而不是假装项目完美。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;本周核心标准：&lt;/p&gt;
&lt;div&gt;&lt;div&gt;&lt;span&gt;omp&lt;/span&gt;&lt;span&gt;prompt&lt;/span&gt;&lt;/div&gt;&lt;pre&gt;&lt;code&gt;别人克隆项目后，按 README 执行命令，能得到同类结果。
别人读 final_report 后，能理解你做了什么、为什么做、结果说明什么。
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;h2&gt;2. 前置条件&lt;/h2&gt;
&lt;p&gt;你应该已有 Week 07 项目：&lt;/p&gt;
&lt;div&gt;&lt;div&gt;&lt;span&gt;omp&lt;/span&gt;&lt;span&gt;prompt&lt;/span&gt;&lt;/div&gt;&lt;pre&gt;&lt;code&gt;projects/stat-ml-baseline/
├── README.md
├── docs/data_card.md
├── reports/baseline_results.md
├── reports/error_analysis.md
└── src/train_baseline.py
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;进入项目并激活环境：&lt;/p&gt;
&lt;div&gt;&lt;div&gt;&lt;span&gt;omp&lt;/span&gt;&lt;span&gt;fish&lt;/span&gt;&lt;/div&gt;&lt;pre&gt;&lt;code&gt;cd ~/Code/python-learning/projects/stat-ml-baseline
source .venv/bin/activate.fish
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;检查 baseline 是否还能跑：&lt;/p&gt;
&lt;div&gt;&lt;div&gt;&lt;span&gt;omp&lt;/span&gt;&lt;span&gt;fish&lt;/span&gt;&lt;/div&gt;&lt;pre&gt;&lt;code&gt;python src/train_baseline.py
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;如果这一步失败，不要先写报告。先修复脚本或环境，因为 Week 08 的报告必须建立在可复现实验上。&lt;/p&gt;
&lt;h2&gt;3. 本周文件布局&lt;/h2&gt;
&lt;p&gt;Week 08 结束时建议结构：&lt;/p&gt;
&lt;div&gt;&lt;div&gt;&lt;span&gt;omp&lt;/span&gt;&lt;span&gt;prompt&lt;/span&gt;&lt;/div&gt;&lt;pre&gt;&lt;code&gt;stat-ml-baseline/
├── data/
│   ├── raw/
│   ├── interim/
│   └── processed/
├── docs/
│   └── data_card.md
├── notebooks/
│   └── 01_eda.ipynb
├── reports/
│   ├── baseline_results.md
│   ├── error_analysis.md
│   └── final_report.md
├── src/
│   ├── make_dataset.py
│   └── train_baseline.py
├── .gitignore
├── README.md
├── pyproject.toml
└── uv.lock
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;不要求每个项目都必须有 notebook，但 README 里提到的文件必须真实存在。不要写“见 &lt;code&gt;reports/final_report.md&lt;/code&gt;”，结果文件却不存在。&lt;/p&gt;
&lt;h2&gt;4. 做一次复现前体检&lt;/h2&gt;
&lt;p&gt;先从项目根目录看关键文件：&lt;/p&gt;
&lt;div&gt;&lt;div&gt;&lt;span&gt;omp&lt;/span&gt;&lt;span&gt;fish&lt;/span&gt;&lt;/div&gt;&lt;pre&gt;&lt;code&gt;pwd
ls
ls src
ls reports
ls docs
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;检查依赖文件：&lt;/p&gt;
&lt;div&gt;&lt;div&gt;&lt;span&gt;omp&lt;/span&gt;&lt;span&gt;fish&lt;/span&gt;&lt;/div&gt;&lt;pre&gt;&lt;code&gt;test -f pyproject.toml; and echo &quot;pyproject exists&quot;
test -f uv.lock; and echo &quot;uv lock exists&quot;
test -f src/train_baseline.py; and echo &quot;training script exists&quot;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;检查 Git 状态：&lt;/p&gt;
&lt;div&gt;&lt;div&gt;&lt;span&gt;omp&lt;/span&gt;&lt;span&gt;fish&lt;/span&gt;&lt;/div&gt;&lt;pre&gt;&lt;code&gt;git status --short
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;如果看到 &lt;code&gt;.venv/&lt;/code&gt;、&lt;code&gt;__pycache__/&lt;/code&gt;、&lt;code&gt;.ipynb_checkpoints/&lt;/code&gt;，说明 &lt;code&gt;.gitignore&lt;/code&gt; 需要修正。&lt;/p&gt;
&lt;h2&gt;5. 整理 .gitignore&lt;/h2&gt;
&lt;p&gt;打开或创建 &lt;code&gt;.gitignore&lt;/code&gt;：&lt;/p&gt;
&lt;div&gt;&lt;div&gt;&lt;span&gt;omp&lt;/span&gt;&lt;span&gt;fish&lt;/span&gt;&lt;/div&gt;&lt;pre&gt;&lt;code&gt;code .gitignore
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;建议包含：&lt;/p&gt;
&lt;div&gt;&lt;div&gt;&lt;span&gt;omp&lt;/span&gt;&lt;span&gt;prompt&lt;/span&gt;&lt;/div&gt;&lt;pre&gt;&lt;code&gt;.venv/
__pycache__/
*.pyc
.ipynb_checkpoints/
.DS_Store
.cache/
&lt;h1&gt;Local large or restricted data archives&lt;a href=&quot;#local-large-or-restricted-data-archives&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/h1&gt;
&lt;p&gt;data/raw/&lt;em&gt;.zip
data/raw/&lt;/em&gt;.tar.gz
data/raw/*.7z&lt;/p&gt;
&lt;h1&gt;Optional generated artifacts&lt;a href=&quot;#optional-generated-artifacts&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/h1&gt;
&lt;/code&gt;&lt;p&gt;&lt;code&gt;reports/*.html
&lt;/code&gt;&lt;/p&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;&lt;/p&gt;
&lt;p&gt;是否忽略 CSV 要看数据许可：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;如果数据公开、小、允许再分发，可以提交一份小样本或完整 CSV。&lt;/li&gt;
&lt;li&gt;如果数据大、需要登录下载、包含敏感信息，不要提交原始 CSV。README 必须说明如何取得数据、放到哪里。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;不要把 &lt;code&gt;.venv/&lt;/code&gt; 提交到 Git。虚拟环境是本机生成物，不是项目源代码。&lt;/p&gt;
&lt;h2&gt;6. 确认可复现命令&lt;/h2&gt;
&lt;p&gt;README 的 &lt;code&gt;How to Run&lt;/code&gt; 部分必须从克隆后的项目根目录开始。&lt;/p&gt;
&lt;p&gt;推荐命令：&lt;/p&gt;
&lt;div&gt;&lt;div&gt;&lt;span&gt;omp&lt;/span&gt;&lt;span&gt;fish&lt;/span&gt;&lt;/div&gt;&lt;pre&gt;&lt;code&gt;uv venv
source .venv/bin/activate.fish
uv sync
python src/train_baseline.py
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;逐句解释：&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;命令&lt;/th&gt;
&lt;th&gt;作用&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;&lt;tr&gt;
&lt;td&gt;&lt;code&gt;uv venv&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;创建项目本地 &lt;code&gt;.venv&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;source .venv/bin/activate.fish&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;用 fish 激活环境&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;uv sync&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;根据 &lt;code&gt;pyproject.toml&lt;/code&gt; 和 &lt;code&gt;uv.lock&lt;/code&gt; 安装依赖&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;python src/train_baseline.py&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;重新生成 baseline 报告&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;&lt;/table&gt;
&lt;p&gt;如果数据不能提交，README 在运行命令之前必须加上：&lt;/p&gt;
&lt;div&gt;&lt;div&gt;&lt;span&gt;omp&lt;/span&gt;&lt;span&gt;prompt&lt;/span&gt;&lt;/div&gt;&lt;pre&gt;&lt;code&gt;Download the dataset from ... and place it at data/raw/train.csv.
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;否则别人按照命令运行会直接找不到文件。&lt;/p&gt;
&lt;h2&gt;7. README 最终模板&lt;/h2&gt;
&lt;p&gt;打开 README：&lt;/p&gt;
&lt;div&gt;&lt;div&gt;&lt;span&gt;omp&lt;/span&gt;&lt;span&gt;fish&lt;/span&gt;&lt;/div&gt;&lt;pre&gt;&lt;code&gt;code README.md
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;建议改成下面结构：&lt;/p&gt;
&lt;div&gt;&lt;div&gt;&lt;span&gt;omp&lt;/span&gt;&lt;span&gt;markdown&lt;/span&gt;&lt;/div&gt;&lt;pre&gt;&lt;code&gt;# Statistical Machine Learning Baseline
&lt;h2&gt;Problem&lt;a href=&quot;#problem&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;State the prediction problem in 3-5 sentences.
Explain the unit of observation, target variable, and why this task matters.&lt;/p&gt;
&lt;h2&gt;Data&lt;a href=&quot;#data&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;Source:&lt;/li&gt;
&lt;li&gt;Download date:&lt;/li&gt;
&lt;li&gt;Rows:&lt;/li&gt;
&lt;li&gt;Columns:&lt;/li&gt;
&lt;li&gt;Target:&lt;/li&gt;
&lt;li&gt;License or usage condition:&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;See &lt;code&gt;docs/data_card.md&lt;/code&gt; for field-level details.&lt;/p&gt;
&lt;h2&gt;Method&lt;a href=&quot;#method&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Describe preprocessing, train/test split, cross validation, baseline models, and selection metric.&lt;/p&gt;
&lt;h2&gt;Experiments&lt;a href=&quot;#experiments&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;























&lt;table&gt;&lt;thead&gt;&lt;tr&gt;&lt;th&gt;Model&lt;/th&gt;&lt;th&gt;Validation Metric&lt;/th&gt;&lt;th&gt;Test Metric&lt;/th&gt;&lt;th&gt;Notes&lt;/th&gt;&lt;/tr&gt;&lt;/thead&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td&gt;Logistic Regression&lt;/td&gt;&lt;td&gt;…&lt;/td&gt;&lt;td&gt;…&lt;/td&gt;&lt;td&gt;scaled numeric features&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;Random Forest&lt;/td&gt;&lt;td&gt;…&lt;/td&gt;&lt;td&gt;…&lt;/td&gt;&lt;td&gt;tree baseline&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;
&lt;h2&gt;Results&lt;a href=&quot;#results&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Summarize the chosen model and what the metric means.
Do not exaggerate the result.&lt;/p&gt;
&lt;h2&gt;Error Analysis&lt;a href=&quot;#error-analysis&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Summarize the main error types and link to &lt;code&gt;reports/error_analysis.md&lt;/code&gt;.&lt;/p&gt;
&lt;h2&gt;Limitations&lt;a href=&quot;#limitations&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;List data, modeling, validation, and interpretation limitations.&lt;/p&gt;
&lt;h2&gt;How to Run&lt;a href=&quot;#how-to-run&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;uv&lt;/span&gt;&lt;span&gt; venv&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;source&lt;/span&gt;&lt;span&gt; .venv/bin/activate.fish&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;uv&lt;/span&gt;&lt;span&gt; sync&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;python&lt;/span&gt;&lt;span&gt; src/train_baseline.py&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;&amp;lt;/code&amp;gt;&amp;lt;/pre&amp;gt;&amp;lt;/div&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;&amp;lt;h2&amp;gt;Project&lt;/span&gt;&lt;span&gt; Structure&lt;/span&gt;&lt;span&gt;&amp;lt;/h2&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;&amp;lt;p&amp;gt;Explain&lt;/span&gt;&lt;span&gt; the key directories.&lt;/span&gt;&lt;span&gt;&amp;lt;/p&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;&amp;lt;h2&amp;gt;Next&lt;/span&gt;&lt;span&gt; Steps&lt;/span&gt;&lt;span&gt;&amp;lt;/h2&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;&amp;lt;p&amp;gt;List&lt;/span&gt;&lt;span&gt; realistic improvements.&lt;/span&gt;&lt;span&gt;&amp;lt;/p&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;&amp;lt;div&lt;/span&gt;&lt;span&gt; class=&lt;/span&gt;&lt;span&gt;&quot;omp-code-frame&quot;&lt;/span&gt;&lt;span&gt; data-lang=&lt;/span&gt;&lt;span&gt;&quot;prompt&quot;&lt;/span&gt;&lt;span&gt;&amp;gt;&amp;lt;div&lt;/span&gt;&lt;span&gt; class=&lt;/span&gt;&lt;span&gt;&quot;omp-code-bar&quot;&lt;/span&gt;&lt;span&gt;&amp;gt;&amp;lt;span&amp;gt;omp&amp;lt;/span&amp;gt;&amp;lt;span&amp;gt;prompt&amp;lt;/span&amp;gt;&amp;lt;/div&amp;gt;&amp;lt;pre&amp;gt;&amp;lt;code&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;README&lt;/span&gt;&lt;span&gt; 里不要只写“使用机器学习预测”。要写清楚预测对象和指标选择原因。&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;## 8. 写最终报告&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;创建：&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;```fish&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;code&lt;/span&gt;&lt;span&gt; reports/final_report.md&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;&amp;lt;/code&amp;gt;&amp;lt;/pre&amp;gt;&amp;lt;/div&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;&amp;lt;p&amp;gt;&lt;/span&gt;&lt;span&gt;建议结构：&lt;/span&gt;&lt;span&gt;&amp;lt;/p&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;&amp;lt;div&lt;/span&gt;&lt;span&gt; class=&lt;/span&gt;&lt;span&gt;&quot;omp-code-frame&quot;&lt;/span&gt;&lt;span&gt; data-lang=&lt;/span&gt;&lt;span&gt;&quot;markdown&quot;&lt;/span&gt;&lt;span&gt;&amp;gt;&amp;lt;div&lt;/span&gt;&lt;span&gt; class=&lt;/span&gt;&lt;span&gt;&quot;omp-code-bar&quot;&lt;/span&gt;&lt;span&gt;&amp;gt;&amp;lt;span&amp;gt;omp&amp;lt;/span&amp;gt;&amp;lt;span&amp;gt;markdown&amp;lt;/span&amp;gt;&amp;lt;/div&amp;gt;&amp;lt;pre&amp;gt;&amp;lt;code&lt;/span&gt;&lt;span&gt; class=&lt;/span&gt;&lt;span&gt;&quot;language-markdown&quot;&lt;/span&gt;&lt;span&gt;&amp;gt;&lt;/span&gt;&lt;span&gt;# Final Report: Statistical Machine Learning Baseline&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;## 1. Problem&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;Define&lt;/span&gt;&lt;span&gt; the task.&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;## 2. Data&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;Summarize&lt;/span&gt;&lt;span&gt; source, rows, columns, target, missingness, and limitations.&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;## 3. Experimental Design&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;Explain&lt;/span&gt;&lt;span&gt; train/test split, cross validation, preprocessing pipeline, and metric choice.&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;## 4. Models&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;Describe&lt;/span&gt;&lt;span&gt; naive baseline and sklearn baselines.&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;## 5. Results&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;Include&lt;/span&gt;&lt;span&gt; the result table from `reports/baseline_results.md`.&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;## 6. Error Analysis&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;Summarize&lt;/span&gt;&lt;span&gt; errors from `reports/error_analysis.md`.&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;## 7. Limitations&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;State&lt;/span&gt;&lt;span&gt; what this project cannot prove.&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;## 8. Next Steps&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;List&lt;/span&gt;&lt;span&gt; concrete improvements.&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;&amp;lt;/code&amp;gt;&amp;lt;/pre&amp;gt;&amp;lt;/div&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;&amp;lt;p&amp;gt;&lt;/span&gt;&lt;span&gt;最终报告和 README 的区别：&lt;/span&gt;&lt;span&gt;&amp;lt;/p&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;&amp;lt;table&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;&amp;lt;thead&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;&amp;lt;tr&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;&amp;lt;th&amp;gt;&lt;/span&gt;&lt;span&gt;文件&lt;/span&gt;&lt;span&gt;&amp;lt;/th&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;&amp;lt;th&amp;gt;&lt;/span&gt;&lt;span&gt;读者目的&lt;/span&gt;&lt;span&gt;&amp;lt;/th&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;&amp;lt;/tr&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;&amp;lt;/thead&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;&amp;lt;tbody&amp;gt;&amp;lt;tr&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;&amp;lt;td&amp;gt;README&amp;lt;/td&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;&amp;lt;td&amp;gt;&lt;/span&gt;&lt;span&gt;快速了解项目、运行项目、判断项目结构&lt;/span&gt;&lt;span&gt;&amp;lt;/td&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;&amp;lt;/tr&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;&amp;lt;tr&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;&amp;lt;td&amp;gt;final_report&amp;lt;/td&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;&amp;lt;td&amp;gt;&lt;/span&gt;&lt;span&gt;详细理解实验设计、结果、错误分析、限制&lt;/span&gt;&lt;span&gt;&amp;lt;/td&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;&amp;lt;/tr&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;&amp;lt;/tbody&amp;gt;&amp;lt;/table&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;&amp;lt;p&amp;gt;README&lt;/span&gt;&lt;span&gt; 可以短，报告要完整。&lt;/span&gt;&lt;span&gt;&amp;lt;/p&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;&amp;lt;h2&amp;gt;9.&lt;/span&gt;&lt;span&gt; 把 Week 07 的结果表转成可展示表格&lt;/span&gt;&lt;span&gt;&amp;lt;/h2&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;&amp;lt;p&amp;gt;&lt;/span&gt;&lt;span&gt;打开 Week 07 生成的报告：&lt;/span&gt;&lt;span&gt;&amp;lt;/p&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;&amp;lt;div&lt;/span&gt;&lt;span&gt; class=&lt;/span&gt;&lt;span&gt;&quot;omp-code-frame&quot;&lt;/span&gt;&lt;span&gt; data-lang=&lt;/span&gt;&lt;span&gt;&quot;fish&quot;&lt;/span&gt;&lt;span&gt;&amp;gt;&amp;lt;div&lt;/span&gt;&lt;span&gt; class=&lt;/span&gt;&lt;span&gt;&quot;omp-code-bar&quot;&lt;/span&gt;&lt;span&gt;&amp;gt;&amp;lt;span&amp;gt;omp&amp;lt;/span&amp;gt;&amp;lt;span&amp;gt;fish&amp;lt;/span&amp;gt;&amp;lt;/div&amp;gt;&amp;lt;pre&amp;gt;&amp;lt;code&lt;/span&gt;&lt;span&gt; class=&lt;/span&gt;&lt;span&gt;&quot;language-fish&quot;&lt;/span&gt;&lt;span&gt;&amp;gt;code&lt;/span&gt;&lt;span&gt; reports/baseline_results.md&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;&amp;lt;/code&amp;gt;&amp;lt;/pre&amp;gt;&amp;lt;/div&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;&amp;lt;p&amp;gt;&lt;/span&gt;&lt;span&gt;把关键结果复制到 README 和 &lt;/span&gt;&lt;span&gt;&amp;lt;code&amp;gt;reports/final_report.md&amp;lt;/code&amp;gt;&lt;/span&gt;&lt;span&gt;。表格建议包含：&lt;/span&gt;&lt;span&gt;&amp;lt;/p&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;&amp;lt;div&lt;/span&gt;&lt;span&gt; class=&lt;/span&gt;&lt;span&gt;&quot;omp-code-frame&quot;&lt;/span&gt;&lt;span&gt; data-lang=&lt;/span&gt;&lt;span&gt;&quot;markdown&quot;&lt;/span&gt;&lt;span&gt;&amp;gt;&amp;lt;div&lt;/span&gt;&lt;span&gt; class=&lt;/span&gt;&lt;span&gt;&quot;omp-code-bar&quot;&lt;/span&gt;&lt;span&gt;&amp;gt;&amp;lt;span&amp;gt;omp&amp;lt;/span&amp;gt;&amp;lt;span&amp;gt;markdown&amp;lt;/span&amp;gt;&amp;lt;/div&amp;gt;&amp;lt;pre&amp;gt;&amp;lt;code&lt;/span&gt;&lt;span&gt; class=&lt;/span&gt;&lt;span&gt;&quot;language-markdown&quot;&lt;/span&gt;&lt;span&gt;&amp;gt;|&lt;/span&gt;&lt;span&gt; Model&lt;/span&gt;&lt;span&gt; |&lt;/span&gt;&lt;span&gt; CV&lt;/span&gt;&lt;span&gt; F1 Mean &lt;/span&gt;&lt;span&gt;|&lt;/span&gt;&lt;span&gt; CV&lt;/span&gt;&lt;span&gt; F1 Std &lt;/span&gt;&lt;span&gt;|&lt;/span&gt;&lt;span&gt; Test&lt;/span&gt;&lt;span&gt; F1 &lt;/span&gt;&lt;span&gt;|&lt;/span&gt;&lt;span&gt; Test&lt;/span&gt;&lt;span&gt; ROC AUC &lt;/span&gt;&lt;span&gt;|&lt;/span&gt;&lt;span&gt; Notes&lt;/span&gt;&lt;span&gt; |&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;|&lt;/span&gt;&lt;span&gt;---&lt;/span&gt;&lt;span&gt;|&lt;/span&gt;&lt;span&gt;---&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt;|&lt;/span&gt;&lt;span&gt;---&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt;|&lt;/span&gt;&lt;span&gt;---&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt;|&lt;/span&gt;&lt;span&gt;---&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt;|&lt;/span&gt;&lt;span&gt;---&lt;/span&gt;&lt;span&gt;|&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;|&lt;/span&gt;&lt;span&gt; Naive&lt;/span&gt;&lt;span&gt; baseline &lt;/span&gt;&lt;span&gt;|&lt;/span&gt;&lt;span&gt; ...&lt;/span&gt;&lt;span&gt; |&lt;/span&gt;&lt;span&gt; ...&lt;/span&gt;&lt;span&gt; |&lt;/span&gt;&lt;span&gt; ...&lt;/span&gt;&lt;span&gt; |&lt;/span&gt;&lt;span&gt; ...&lt;/span&gt;&lt;span&gt; |&lt;/span&gt;&lt;span&gt; majority&lt;/span&gt;&lt;span&gt; class |&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;|&lt;/span&gt;&lt;span&gt; Logistic&lt;/span&gt;&lt;span&gt; Regression &lt;/span&gt;&lt;span&gt;|&lt;/span&gt;&lt;span&gt; ...&lt;/span&gt;&lt;span&gt; |&lt;/span&gt;&lt;span&gt; ...&lt;/span&gt;&lt;span&gt; |&lt;/span&gt;&lt;span&gt; ...&lt;/span&gt;&lt;span&gt; |&lt;/span&gt;&lt;span&gt; ...&lt;/span&gt;&lt;span&gt; |&lt;/span&gt;&lt;span&gt; scaled&lt;/span&gt;&lt;span&gt; features |&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;|&lt;/span&gt;&lt;span&gt; Random&lt;/span&gt;&lt;span&gt; Forest &lt;/span&gt;&lt;span&gt;|&lt;/span&gt;&lt;span&gt; ...&lt;/span&gt;&lt;span&gt; |&lt;/span&gt;&lt;span&gt; ...&lt;/span&gt;&lt;span&gt; |&lt;/span&gt;&lt;span&gt; ...&lt;/span&gt;&lt;span&gt; |&lt;/span&gt;&lt;span&gt; ...&lt;/span&gt;&lt;span&gt; |&lt;/span&gt;&lt;span&gt; tree&lt;/span&gt;&lt;span&gt; ensemble |&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;&amp;lt;/code&amp;gt;&amp;lt;/pre&amp;gt;&amp;lt;/div&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;&amp;lt;p&amp;gt;&lt;/span&gt;&lt;span&gt;如果你没有 naive baseline，本周补上。一个没有 naive baseline 的项目，很难说明模型到底是否有价值。&lt;/span&gt;&lt;span&gt;&amp;lt;/p&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;&amp;lt;h2&amp;gt;10.&lt;/span&gt;&lt;span&gt; 检查指标叙述是否诚实&lt;/span&gt;&lt;span&gt;&amp;lt;/h2&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;&amp;lt;p&amp;gt;&lt;/span&gt;&lt;span&gt;不要写：&lt;/span&gt;&lt;span&gt;&amp;lt;/p&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;&amp;lt;div&lt;/span&gt;&lt;span&gt; class=&lt;/span&gt;&lt;span&gt;&quot;omp-code-frame&quot;&lt;/span&gt;&lt;span&gt; data-lang=&lt;/span&gt;&lt;span&gt;&quot;prompt&quot;&lt;/span&gt;&lt;span&gt;&amp;gt;&amp;lt;div&lt;/span&gt;&lt;span&gt; class=&lt;/span&gt;&lt;span&gt;&quot;omp-code-bar&quot;&lt;/span&gt;&lt;span&gt;&amp;gt;&amp;lt;span&amp;gt;omp&amp;lt;/span&amp;gt;&amp;lt;span&amp;gt;prompt&amp;lt;/span&amp;gt;&amp;lt;/div&amp;gt;&amp;lt;pre&amp;gt;&amp;lt;code&lt;/span&gt;&lt;span&gt; class=&lt;/span&gt;&lt;span&gt;&quot;language-text&quot;&lt;/span&gt;&lt;span&gt;&amp;gt;The&lt;/span&gt;&lt;span&gt; model performs very well.&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;&amp;lt;/code&amp;gt;&amp;lt;/pre&amp;gt;&amp;lt;/div&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;&amp;lt;p&amp;gt;&lt;/span&gt;&lt;span&gt;应该写：&lt;/span&gt;&lt;span&gt;&amp;lt;/p&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;&amp;lt;div&lt;/span&gt;&lt;span&gt; class=&lt;/span&gt;&lt;span&gt;&quot;omp-code-frame&quot;&lt;/span&gt;&lt;span&gt; data-lang=&lt;/span&gt;&lt;span&gt;&quot;prompt&quot;&lt;/span&gt;&lt;span&gt;&amp;gt;&amp;lt;div&lt;/span&gt;&lt;span&gt; class=&lt;/span&gt;&lt;span&gt;&quot;omp-code-bar&quot;&lt;/span&gt;&lt;span&gt;&amp;gt;&amp;lt;span&amp;gt;omp&amp;lt;/span&amp;gt;&amp;lt;span&amp;gt;prompt&amp;lt;/span&amp;gt;&amp;lt;/div&amp;gt;&amp;lt;pre&amp;gt;&amp;lt;code&lt;/span&gt;&lt;span&gt; class=&lt;/span&gt;&lt;span&gt;&quot;language-text&quot;&lt;/span&gt;&lt;span&gt;&amp;gt;The&lt;/span&gt;&lt;span&gt; selected model reaches a test F1 of 0.XX on the current holdout split. This suggests it improves over the naive baseline, but the estimate may be optimistic because the dataset is small and only one final holdout split is used.&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;&amp;lt;/code&amp;gt;&amp;lt;/pre&amp;gt;&amp;lt;/div&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;&amp;lt;p&amp;gt;&lt;/span&gt;&lt;span&gt;统计学项目尤其要避免过度宣称。你可以说“在当前数据和划分下表现更好”，不要说“证明该方法一定有效”。&lt;/span&gt;&lt;span&gt;&amp;lt;/p&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;&amp;lt;h2&amp;gt;11.&lt;/span&gt;&lt;span&gt; 复现测试：从干净环境重跑&lt;/span&gt;&lt;span&gt;&amp;lt;/h2&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;&amp;lt;p&amp;gt;&lt;/span&gt;&lt;span&gt;在当前项目里，先确认虚拟环境能用：&lt;/span&gt;&lt;span&gt;&amp;lt;/p&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;&amp;lt;div&lt;/span&gt;&lt;span&gt; class=&lt;/span&gt;&lt;span&gt;&quot;omp-code-frame&quot;&lt;/span&gt;&lt;span&gt; data-lang=&lt;/span&gt;&lt;span&gt;&quot;fish&quot;&lt;/span&gt;&lt;span&gt;&amp;gt;&amp;lt;div&lt;/span&gt;&lt;span&gt; class=&lt;/span&gt;&lt;span&gt;&quot;omp-code-bar&quot;&lt;/span&gt;&lt;span&gt;&amp;gt;&amp;lt;span&amp;gt;omp&amp;lt;/span&amp;gt;&amp;lt;span&amp;gt;fish&amp;lt;/span&amp;gt;&amp;lt;/div&amp;gt;&amp;lt;pre&amp;gt;&amp;lt;code&lt;/span&gt;&lt;span&gt; class=&lt;/span&gt;&lt;span&gt;&quot;language-fish&quot;&lt;/span&gt;&lt;span&gt;&amp;gt;source&lt;/span&gt;&lt;span&gt; .venv/bin/activate.fish&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;uv&lt;/span&gt;&lt;span&gt; sync&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;python&lt;/span&gt;&lt;span&gt; src/train_baseline.py&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;&amp;lt;/code&amp;gt;&amp;lt;/pre&amp;gt;&amp;lt;/div&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;&amp;lt;p&amp;gt;&lt;/span&gt;&lt;span&gt;如果想模拟新机器，不要删除项目文件，只移除本地虚拟环境后重建：&lt;/span&gt;&lt;span&gt;&amp;lt;/p&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;&amp;lt;div&lt;/span&gt;&lt;span&gt; class=&lt;/span&gt;&lt;span&gt;&quot;omp-code-frame&quot;&lt;/span&gt;&lt;span&gt; data-lang=&lt;/span&gt;&lt;span&gt;&quot;fish&quot;&lt;/span&gt;&lt;span&gt;&amp;gt;&amp;lt;div&lt;/span&gt;&lt;span&gt; class=&lt;/span&gt;&lt;span&gt;&quot;omp-code-bar&quot;&lt;/span&gt;&lt;span&gt;&amp;gt;&amp;lt;span&amp;gt;omp&amp;lt;/span&amp;gt;&amp;lt;span&amp;gt;fish&amp;lt;/span&amp;gt;&amp;lt;/div&amp;gt;&amp;lt;pre&amp;gt;&amp;lt;code&lt;/span&gt;&lt;span&gt; class=&lt;/span&gt;&lt;span&gt;&quot;language-fish&quot;&lt;/span&gt;&lt;span&gt;&amp;gt;rm&lt;/span&gt;&lt;span&gt; -rf .venv&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;uv&lt;/span&gt;&lt;span&gt; venv&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;source&lt;/span&gt;&lt;span&gt; .venv/bin/activate.fish&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;uv&lt;/span&gt;&lt;span&gt; sync&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;python&lt;/span&gt;&lt;span&gt; src/train_baseline.py&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;&amp;lt;/code&amp;gt;&amp;lt;/pre&amp;gt;&amp;lt;/div&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;&amp;lt;p&amp;gt;&lt;/span&gt;&lt;span&gt;注意：&lt;/span&gt;&lt;span&gt;&amp;lt;code&amp;gt;rm&lt;/span&gt;&lt;span&gt; -rf .venv&lt;/span&gt;&lt;span&gt;&amp;lt;/code&amp;gt;&lt;/span&gt;&lt;span&gt; 只删除虚拟环境，不能删除 &lt;/span&gt;&lt;span&gt;&amp;lt;code&amp;gt;data/&amp;lt;/code&amp;gt;&lt;/span&gt;&lt;span&gt;、&lt;/span&gt;&lt;span&gt;&amp;lt;code&amp;gt;src/&amp;lt;/code&amp;gt;&lt;/span&gt;&lt;span&gt;、&lt;/span&gt;&lt;span&gt;&amp;lt;code&amp;gt;reports/&amp;lt;/code&amp;gt;&lt;/span&gt;&lt;span&gt;。执行前用 &lt;/span&gt;&lt;span&gt;&amp;lt;code&amp;gt;pwd&amp;lt;/code&amp;gt;&lt;/span&gt;&lt;span&gt; 确认你在项目根目录。&lt;/span&gt;&lt;span&gt;&amp;lt;/p&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;&amp;lt;div&lt;/span&gt;&lt;span&gt; class=&lt;/span&gt;&lt;span&gt;&quot;omp-code-frame&quot;&lt;/span&gt;&lt;span&gt; data-lang=&lt;/span&gt;&lt;span&gt;&quot;fish&quot;&lt;/span&gt;&lt;span&gt;&amp;gt;&amp;lt;div&lt;/span&gt;&lt;span&gt; class=&lt;/span&gt;&lt;span&gt;&quot;omp-code-bar&quot;&lt;/span&gt;&lt;span&gt;&amp;gt;&amp;lt;span&amp;gt;omp&amp;lt;/span&amp;gt;&amp;lt;span&amp;gt;fish&amp;lt;/span&amp;gt;&amp;lt;/div&amp;gt;&amp;lt;pre&amp;gt;&amp;lt;code&lt;/span&gt;&lt;span&gt; class=&lt;/span&gt;&lt;span&gt;&quot;language-fish&quot;&lt;/span&gt;&lt;span&gt;&amp;gt;pwd&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;&amp;lt;/code&amp;gt;&amp;lt;/pre&amp;gt;&amp;lt;/div&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;&amp;lt;p&amp;gt;&lt;/span&gt;&lt;span&gt;运行成功后，报告时间或数值可能重新生成，但主要结果应一致。&lt;/span&gt;&lt;span&gt;&amp;lt;/p&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;&amp;lt;h2&amp;gt;12.&lt;/span&gt;&lt;span&gt; 数据路径检查&lt;/span&gt;&lt;span&gt;&amp;lt;/h2&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;&amp;lt;p&amp;gt;&lt;/span&gt;&lt;span&gt;常见复现失败来自路径写死。脚本里不要写：&lt;/span&gt;&lt;span&gt;&amp;lt;/p&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;&amp;lt;div&lt;/span&gt;&lt;span&gt; class=&lt;/span&gt;&lt;span&gt;&quot;omp-code-frame&quot;&lt;/span&gt;&lt;span&gt; data-lang=&lt;/span&gt;&lt;span&gt;&quot;python&quot;&lt;/span&gt;&lt;span&gt;&amp;gt;&amp;lt;div&lt;/span&gt;&lt;span&gt; class=&lt;/span&gt;&lt;span&gt;&quot;omp-code-bar&quot;&lt;/span&gt;&lt;span&gt;&amp;gt;&amp;lt;span&amp;gt;omp&amp;lt;/span&amp;gt;&amp;lt;span&amp;gt;python&amp;lt;/span&amp;gt;&amp;lt;/div&amp;gt;&amp;lt;pre&amp;gt;&amp;lt;code&lt;/span&gt;&lt;span&gt; class=&lt;/span&gt;&lt;span&gt;&quot;language-python&quot;&lt;/span&gt;&lt;span&gt;&amp;gt;pd.read_csv&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&amp;amp;&lt;/span&gt;&lt;span&gt;quot;/home/miku/Downloads/train.csv&lt;/span&gt;&lt;span&gt;&amp;amp;&lt;/span&gt;&lt;span&gt;quot;)&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;&amp;lt;/code&amp;gt;&amp;lt;/pre&amp;gt;&amp;lt;/div&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;&amp;lt;p&amp;gt;&lt;/span&gt;&lt;span&gt;应该写项目相对路径：&lt;/span&gt;&lt;span&gt;&amp;lt;/p&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;&amp;lt;div&lt;/span&gt;&lt;span&gt; class=&lt;/span&gt;&lt;span&gt;&quot;omp-code-frame&quot;&lt;/span&gt;&lt;span&gt; data-lang=&lt;/span&gt;&lt;span&gt;&quot;python&quot;&lt;/span&gt;&lt;span&gt;&amp;gt;&amp;lt;div&lt;/span&gt;&lt;span&gt; class=&lt;/span&gt;&lt;span&gt;&quot;omp-code-bar&quot;&lt;/span&gt;&lt;span&gt;&amp;gt;&amp;lt;span&amp;gt;omp&amp;lt;/span&amp;gt;&amp;lt;span&amp;gt;python&amp;lt;/span&amp;gt;&amp;lt;/div&amp;gt;&amp;lt;pre&amp;gt;&amp;lt;code&lt;/span&gt;&lt;span&gt; class=&lt;/span&gt;&lt;span&gt;&quot;language-python&quot;&lt;/span&gt;&lt;span&gt;&amp;gt;pd.read_csv&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&amp;amp;&lt;/span&gt;&lt;span&gt;quot;&lt;/span&gt;&lt;span&gt;data&lt;/span&gt;&lt;span&gt;/raw/train.csv&lt;/span&gt;&lt;span&gt;&amp;amp;&lt;/span&gt;&lt;span&gt;quot;)&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;&amp;lt;/code&amp;gt;&amp;lt;/pre&amp;gt;&amp;lt;/div&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;&amp;lt;p&amp;gt;&lt;/span&gt;&lt;span&gt;如果你担心从不同目录运行脚本，可以用文件所在位置推导项目根目录：&lt;/span&gt;&lt;span&gt;&amp;lt;/p&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;&amp;lt;div&lt;/span&gt;&lt;span&gt; class=&lt;/span&gt;&lt;span&gt;&quot;omp-code-frame&quot;&lt;/span&gt;&lt;span&gt; data-lang=&lt;/span&gt;&lt;span&gt;&quot;python&quot;&lt;/span&gt;&lt;span&gt;&amp;gt;&amp;lt;div&lt;/span&gt;&lt;span&gt; class=&lt;/span&gt;&lt;span&gt;&quot;omp-code-bar&quot;&lt;/span&gt;&lt;span&gt;&amp;gt;&amp;lt;span&amp;gt;omp&amp;lt;/span&amp;gt;&amp;lt;span&amp;gt;python&amp;lt;/span&amp;gt;&amp;lt;/div&amp;gt;&amp;lt;pre&amp;gt;&amp;lt;code&lt;/span&gt;&lt;span&gt; class=&lt;/span&gt;&lt;span&gt;&quot;language-python&quot;&lt;/span&gt;&lt;span&gt;&amp;gt;from&lt;/span&gt;&lt;span&gt; pathlib import Path&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;PROJECT_ROOT&lt;/span&gt;&lt;span&gt; = Path(&lt;/span&gt;&lt;span&gt;__file__&lt;/span&gt;&lt;span&gt;).resolve().parents[1]&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;DATA_PATH&lt;/span&gt;&lt;span&gt; = PROJECT_ROOT / &lt;/span&gt;&lt;span&gt;&amp;amp;&lt;/span&gt;&lt;span&gt;quot;&lt;/span&gt;&lt;span&gt;data&lt;/span&gt;&lt;span&gt;&amp;amp;&lt;/span&gt;&lt;span&gt;quot; / &lt;/span&gt;&lt;span&gt;&amp;amp;&lt;/span&gt;&lt;span&gt;quot;&lt;/span&gt;&lt;span&gt;raw&lt;/span&gt;&lt;span&gt;&amp;amp;&lt;/span&gt;&lt;span&gt;quot; / &lt;/span&gt;&lt;span&gt;&amp;amp;&lt;/span&gt;&lt;span&gt;quot;&lt;/span&gt;&lt;span&gt;train.csv&lt;/span&gt;&lt;span&gt;&amp;amp;&lt;/span&gt;&lt;span&gt;quot;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;&amp;lt;/code&amp;gt;&amp;lt;/pre&amp;gt;&amp;lt;/div&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;&amp;lt;p&amp;gt;&lt;/span&gt;&lt;span&gt;这样从项目根目录运行更稳定。&lt;/span&gt;&lt;span&gt;&amp;lt;/p&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;&amp;lt;h2&amp;gt;13.&lt;/span&gt;&lt;span&gt; 报告中的错误分析要具体&lt;/span&gt;&lt;span&gt;&amp;lt;/h2&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;&amp;lt;p&amp;gt;&lt;/span&gt;&lt;span&gt;不要写：&lt;/span&gt;&lt;span&gt;&amp;lt;/p&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;&amp;lt;div&lt;/span&gt;&lt;span&gt; class=&lt;/span&gt;&lt;span&gt;&quot;omp-code-frame&quot;&lt;/span&gt;&lt;span&gt; data-lang=&lt;/span&gt;&lt;span&gt;&quot;prompt&quot;&lt;/span&gt;&lt;span&gt;&amp;gt;&amp;lt;div&lt;/span&gt;&lt;span&gt; class=&lt;/span&gt;&lt;span&gt;&quot;omp-code-bar&quot;&lt;/span&gt;&lt;span&gt;&amp;gt;&amp;lt;span&amp;gt;omp&amp;lt;/span&amp;gt;&amp;lt;span&amp;gt;prompt&amp;lt;/span&amp;gt;&amp;lt;/div&amp;gt;&amp;lt;pre&amp;gt;&amp;lt;code&lt;/span&gt;&lt;span&gt; class=&lt;/span&gt;&lt;span&gt;&quot;language-text&quot;&lt;/span&gt;&lt;span&gt;&amp;gt;Some&lt;/span&gt;&lt;span&gt; samples are predicted incorrectly.&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;&amp;lt;/code&amp;gt;&amp;lt;/pre&amp;gt;&amp;lt;/div&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;&amp;lt;p&amp;gt;&lt;/span&gt;&lt;span&gt;应写：&lt;/span&gt;&lt;span&gt;&amp;lt;/p&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;&amp;lt;div&lt;/span&gt;&lt;span&gt; class=&lt;/span&gt;&lt;span&gt;&quot;omp-code-frame&quot;&lt;/span&gt;&lt;span&gt; data-lang=&lt;/span&gt;&lt;span&gt;&quot;prompt&quot;&lt;/span&gt;&lt;span&gt;&amp;gt;&amp;lt;div&lt;/span&gt;&lt;span&gt; class=&lt;/span&gt;&lt;span&gt;&quot;omp-code-bar&quot;&lt;/span&gt;&lt;span&gt;&amp;gt;&amp;lt;span&amp;gt;omp&amp;lt;/span&amp;gt;&amp;lt;span&amp;gt;prompt&amp;lt;/span&amp;gt;&amp;lt;/div&amp;gt;&amp;lt;pre&amp;gt;&amp;lt;code&lt;/span&gt;&lt;span&gt; class=&lt;/span&gt;&lt;span&gt;&quot;language-text&quot;&lt;/span&gt;&lt;span&gt;&amp;gt;False&lt;/span&gt;&lt;span&gt; negatives are more concerning for this task because missing a positive case has higher cost. In the wrong-prediction preview, several false negatives have feature values near the decision boundary, suggesting that a calibrated probability threshold or additional domain features may be useful.&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;&amp;lt;/code&amp;gt;&amp;lt;/pre&amp;gt;&amp;lt;/div&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;&amp;lt;p&amp;gt;&lt;/span&gt;&lt;span&gt;错误分析至少包含：&lt;/span&gt;&lt;span&gt;&amp;lt;/p&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;&amp;lt;ul&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;&amp;lt;li&amp;gt;&lt;/span&gt;&lt;span&gt;主要错误类型。&lt;/span&gt;&lt;span&gt;&amp;lt;/li&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;&amp;lt;li&amp;gt;&lt;/span&gt;&lt;span&gt;错误的可能原因。&lt;/span&gt;&lt;span&gt;&amp;lt;/li&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;&amp;lt;li&amp;gt;&lt;/span&gt;&lt;span&gt;这些错误对任务有什么影响。&lt;/span&gt;&lt;span&gt;&amp;lt;/li&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;&amp;lt;li&amp;gt;&lt;/span&gt;&lt;span&gt;下一步如何验证改进。&lt;/span&gt;&lt;span&gt;&amp;lt;/li&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;&amp;lt;/ul&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;&amp;lt;h2&amp;gt;14.&lt;/span&gt;&lt;span&gt; 清理 notebook 与临时文件&lt;/span&gt;&lt;span&gt;&amp;lt;/h2&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;&amp;lt;p&amp;gt;Notebook&lt;/span&gt;&lt;span&gt; 可以保留，但不要让它成为唯一入口。检查临时文件：&lt;/span&gt;&lt;span&gt;&amp;lt;/p&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;&amp;lt;div&lt;/span&gt;&lt;span&gt; class=&lt;/span&gt;&lt;span&gt;&quot;omp-code-frame&quot;&lt;/span&gt;&lt;span&gt; data-lang=&lt;/span&gt;&lt;span&gt;&quot;fish&quot;&lt;/span&gt;&lt;span&gt;&amp;gt;&amp;lt;div&lt;/span&gt;&lt;span&gt; class=&lt;/span&gt;&lt;span&gt;&quot;omp-code-bar&quot;&lt;/span&gt;&lt;span&gt;&amp;gt;&amp;lt;span&amp;gt;omp&amp;lt;/span&amp;gt;&amp;lt;span&amp;gt;fish&amp;lt;/span&amp;gt;&amp;lt;/div&amp;gt;&amp;lt;pre&amp;gt;&amp;lt;code&lt;/span&gt;&lt;span&gt; class=&lt;/span&gt;&lt;span&gt;&quot;language-fish&quot;&lt;/span&gt;&lt;span&gt;&amp;gt;find&lt;/span&gt;&lt;span&gt; notebooks -maxdepth 2 -type f&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;find&lt;/span&gt;&lt;span&gt; reports -maxdepth 2 -type f&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;find&lt;/span&gt;&lt;span&gt; src -maxdepth 2 -type f&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;&amp;lt;/code&amp;gt;&amp;lt;/pre&amp;gt;&amp;lt;/div&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;&amp;lt;p&amp;gt;&lt;/span&gt;&lt;span&gt;如果你有很多临时图、草稿 CSV、旧报告，决定：&lt;/span&gt;&lt;span&gt;&amp;lt;/p&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;&amp;lt;table&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;&amp;lt;thead&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;&amp;lt;tr&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;&amp;lt;th&amp;gt;&lt;/span&gt;&lt;span&gt;类型&lt;/span&gt;&lt;span&gt;&amp;lt;/th&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;&amp;lt;th&amp;gt;&lt;/span&gt;&lt;span&gt;处理&lt;/span&gt;&lt;span&gt;&amp;lt;/th&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;&amp;lt;/tr&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;&amp;lt;/thead&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;&amp;lt;tbody&amp;gt;&amp;lt;tr&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;&amp;lt;td&amp;gt;&lt;/span&gt;&lt;span&gt;最终报告需要引用的图&lt;/span&gt;&lt;span&gt;&amp;lt;/td&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;&amp;lt;td&amp;gt;&lt;/span&gt;&lt;span&gt;放在 &lt;/span&gt;&lt;span&gt;&amp;lt;code&amp;gt;reports/figures/&amp;lt;/code&amp;gt;&lt;/span&gt;&lt;span&gt; 并在报告中链接&lt;/span&gt;&lt;span&gt;&amp;lt;/td&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;&amp;lt;/tr&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;&amp;lt;tr&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;&amp;lt;td&amp;gt;notebook&lt;/span&gt;&lt;span&gt; 自动缓存&lt;/span&gt;&lt;span&gt;&amp;lt;/td&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;&amp;lt;td&amp;gt;&lt;/span&gt;&lt;span&gt;加入 &lt;/span&gt;&lt;span&gt;&amp;lt;code&amp;gt;.gitignore&amp;lt;/code&amp;gt;&amp;lt;/td&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;&amp;lt;/tr&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;&amp;lt;tr&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;&amp;lt;td&amp;gt;&lt;/span&gt;&lt;span&gt;临时实验输出&lt;/span&gt;&lt;span&gt;&amp;lt;/td&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;&amp;lt;td&amp;gt;&lt;/span&gt;&lt;span&gt;删除或移到明确命名目录&lt;/span&gt;&lt;span&gt;&amp;lt;/td&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;&amp;lt;/tr&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;&amp;lt;tr&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;&amp;lt;td&amp;gt;&lt;/span&gt;&lt;span&gt;旧版本报告&lt;/span&gt;&lt;span&gt;&amp;lt;/td&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;&amp;lt;td&amp;gt;&lt;/span&gt;&lt;span&gt;如果不再引用，删除，避免读者混淆&lt;/span&gt;&lt;span&gt;&amp;lt;/td&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;&amp;lt;/tr&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;&amp;lt;/tbody&amp;gt;&amp;lt;/table&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;&amp;lt;p&amp;gt;&lt;/span&gt;&lt;span&gt;不要为了“看起来文件多”保留无用文件。项目越清晰越专业。&lt;/span&gt;&lt;span&gt;&amp;lt;/p&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;&amp;lt;h2&amp;gt;15.&lt;/span&gt;&lt;span&gt; Git 提交卫生&lt;/span&gt;&lt;span&gt;&amp;lt;/h2&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;&amp;lt;p&amp;gt;&lt;/span&gt;&lt;span&gt;先看状态：&lt;/span&gt;&lt;span&gt;&amp;lt;/p&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;&amp;lt;div&lt;/span&gt;&lt;span&gt; class=&lt;/span&gt;&lt;span&gt;&quot;omp-code-frame&quot;&lt;/span&gt;&lt;span&gt; data-lang=&lt;/span&gt;&lt;span&gt;&quot;fish&quot;&lt;/span&gt;&lt;span&gt;&amp;gt;&amp;lt;div&lt;/span&gt;&lt;span&gt; class=&lt;/span&gt;&lt;span&gt;&quot;omp-code-bar&quot;&lt;/span&gt;&lt;span&gt;&amp;gt;&amp;lt;span&amp;gt;omp&amp;lt;/span&amp;gt;&amp;lt;span&amp;gt;fish&amp;lt;/span&amp;gt;&amp;lt;/div&amp;gt;&amp;lt;pre&amp;gt;&amp;lt;code&lt;/span&gt;&lt;span&gt; class=&lt;/span&gt;&lt;span&gt;&quot;language-fish&quot;&lt;/span&gt;&lt;span&gt;&amp;gt;git&lt;/span&gt;&lt;span&gt; status --short&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;&amp;lt;/code&amp;gt;&amp;lt;/pre&amp;gt;&amp;lt;/div&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;&amp;lt;p&amp;gt;&lt;/span&gt;&lt;span&gt;查看将要提交的文件：&lt;/span&gt;&lt;span&gt;&amp;lt;/p&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;&amp;lt;div&lt;/span&gt;&lt;span&gt; class=&lt;/span&gt;&lt;span&gt;&quot;omp-code-frame&quot;&lt;/span&gt;&lt;span&gt; data-lang=&lt;/span&gt;&lt;span&gt;&quot;fish&quot;&lt;/span&gt;&lt;span&gt;&amp;gt;&amp;lt;div&lt;/span&gt;&lt;span&gt; class=&lt;/span&gt;&lt;span&gt;&quot;omp-code-bar&quot;&lt;/span&gt;&lt;span&gt;&amp;gt;&amp;lt;span&amp;gt;omp&amp;lt;/span&amp;gt;&amp;lt;span&amp;gt;fish&amp;lt;/span&amp;gt;&amp;lt;/div&amp;gt;&amp;lt;pre&amp;gt;&amp;lt;code&lt;/span&gt;&lt;span&gt; class=&lt;/span&gt;&lt;span&gt;&quot;language-fish&quot;&lt;/span&gt;&lt;span&gt;&amp;gt;git&lt;/span&gt;&lt;span&gt; diff --stat&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;&amp;lt;/code&amp;gt;&amp;lt;/pre&amp;gt;&amp;lt;/div&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;&amp;lt;p&amp;gt;&lt;/span&gt;&lt;span&gt;如果发现 &lt;/span&gt;&lt;span&gt;&amp;lt;code&amp;gt;.venv/&amp;lt;/code&amp;gt;&lt;/span&gt;&lt;span&gt; 或缓存文件，先修 &lt;/span&gt;&lt;span&gt;&amp;lt;code&amp;gt;.gitignore&amp;lt;/code&amp;gt;&lt;/span&gt;&lt;span&gt;。然后提交：&lt;/span&gt;&lt;span&gt;&amp;lt;/p&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;&amp;lt;div&lt;/span&gt;&lt;span&gt; class=&lt;/span&gt;&lt;span&gt;&quot;omp-code-frame&quot;&lt;/span&gt;&lt;span&gt; data-lang=&lt;/span&gt;&lt;span&gt;&quot;fish&quot;&lt;/span&gt;&lt;span&gt;&amp;gt;&amp;lt;div&lt;/span&gt;&lt;span&gt; class=&lt;/span&gt;&lt;span&gt;&quot;omp-code-bar&quot;&lt;/span&gt;&lt;span&gt;&amp;gt;&amp;lt;span&amp;gt;omp&amp;lt;/span&amp;gt;&amp;lt;span&amp;gt;fish&amp;lt;/span&amp;gt;&amp;lt;/div&amp;gt;&amp;lt;pre&amp;gt;&amp;lt;code&lt;/span&gt;&lt;span&gt; class=&lt;/span&gt;&lt;span&gt;&quot;language-fish&quot;&lt;/span&gt;&lt;span&gt;&amp;gt;git&lt;/span&gt;&lt;span&gt; add README.md docs reports src pyproject.toml uv.lock .gitignore&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;git&lt;/span&gt;&lt;span&gt; commit -m &lt;/span&gt;&lt;span&gt;&amp;amp;&lt;/span&gt;&lt;span&gt;quot;&lt;/span&gt;&lt;span&gt;Finish&lt;/span&gt;&lt;span&gt; statistical machine learning baseline project&lt;/span&gt;&lt;span&gt;&amp;amp;&lt;/span&gt;&lt;span&gt;quot;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;&amp;lt;/code&amp;gt;&amp;lt;/pre&amp;gt;&amp;lt;/div&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;&amp;lt;p&amp;gt;&lt;/span&gt;&lt;span&gt;提交信息要说明结果，不要写：&lt;/span&gt;&lt;span&gt;&amp;lt;/p&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;&amp;lt;div&lt;/span&gt;&lt;span&gt; class=&lt;/span&gt;&lt;span&gt;&quot;omp-code-frame&quot;&lt;/span&gt;&lt;span&gt; data-lang=&lt;/span&gt;&lt;span&gt;&quot;prompt&quot;&lt;/span&gt;&lt;span&gt;&amp;gt;&amp;lt;div&lt;/span&gt;&lt;span&gt; class=&lt;/span&gt;&lt;span&gt;&quot;omp-code-bar&quot;&lt;/span&gt;&lt;span&gt;&amp;gt;&amp;lt;span&amp;gt;omp&amp;lt;/span&amp;gt;&amp;lt;span&amp;gt;prompt&amp;lt;/span&amp;gt;&amp;lt;/div&amp;gt;&amp;lt;pre&amp;gt;&amp;lt;code&lt;/span&gt;&lt;span&gt; class=&lt;/span&gt;&lt;span&gt;&quot;language-text&quot;&lt;/span&gt;&lt;span&gt;&amp;gt;update&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;final&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;fix&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;123&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;&amp;lt;/code&amp;gt;&amp;lt;/pre&amp;gt;&amp;lt;/div&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;&amp;lt;p&amp;gt;&lt;/span&gt;&lt;span&gt;更好的提交信息：&lt;/span&gt;&lt;span&gt;&amp;lt;/p&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;&amp;lt;div&lt;/span&gt;&lt;span&gt; class=&lt;/span&gt;&lt;span&gt;&quot;omp-code-frame&quot;&lt;/span&gt;&lt;span&gt; data-lang=&lt;/span&gt;&lt;span&gt;&quot;prompt&quot;&lt;/span&gt;&lt;span&gt;&amp;gt;&amp;lt;div&lt;/span&gt;&lt;span&gt; class=&lt;/span&gt;&lt;span&gt;&quot;omp-code-bar&quot;&lt;/span&gt;&lt;span&gt;&amp;gt;&amp;lt;span&amp;gt;omp&amp;lt;/span&amp;gt;&amp;lt;span&amp;gt;prompt&amp;lt;/span&amp;gt;&amp;lt;/div&amp;gt;&amp;lt;pre&amp;gt;&amp;lt;code&lt;/span&gt;&lt;span&gt; class=&lt;/span&gt;&lt;span&gt;&quot;language-text&quot;&lt;/span&gt;&lt;span&gt;&amp;gt;Add&lt;/span&gt;&lt;span&gt; reproducible baseline report&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;Document&lt;/span&gt;&lt;span&gt; data card and error analysis&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;Finish&lt;/span&gt;&lt;span&gt; statistical ML project README&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;&amp;lt;/code&amp;gt;&amp;lt;/pre&amp;gt;&amp;lt;/div&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;&amp;lt;h2&amp;gt;16.&lt;/span&gt;&lt;span&gt; 如果提交前发现大文件&lt;/span&gt;&lt;span&gt;&amp;lt;/h2&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;&amp;lt;p&amp;gt;&lt;/span&gt;&lt;span&gt;检查 Git 追踪文件大小可以用：&lt;/span&gt;&lt;span&gt;&amp;lt;/p&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;&amp;lt;div&lt;/span&gt;&lt;span&gt; class=&lt;/span&gt;&lt;span&gt;&quot;omp-code-frame&quot;&lt;/span&gt;&lt;span&gt; data-lang=&lt;/span&gt;&lt;span&gt;&quot;fish&quot;&lt;/span&gt;&lt;span&gt;&amp;gt;&amp;lt;div&lt;/span&gt;&lt;span&gt; class=&lt;/span&gt;&lt;span&gt;&quot;omp-code-bar&quot;&lt;/span&gt;&lt;span&gt;&amp;gt;&amp;lt;span&amp;gt;omp&amp;lt;/span&amp;gt;&amp;lt;span&amp;gt;fish&amp;lt;/span&amp;gt;&amp;lt;/div&amp;gt;&amp;lt;pre&amp;gt;&amp;lt;code&lt;/span&gt;&lt;span&gt; class=&lt;/span&gt;&lt;span&gt;&quot;language-fish&quot;&lt;/span&gt;&lt;span&gt;&amp;gt;git&lt;/span&gt;&lt;span&gt; status --short&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;&amp;lt;/code&amp;gt;&amp;lt;/pre&amp;gt;&amp;lt;/div&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;&amp;lt;p&amp;gt;&lt;/span&gt;&lt;span&gt;如果还没提交，但 &lt;/span&gt;&lt;span&gt;&amp;lt;code&amp;gt;git&lt;/span&gt;&lt;span&gt; status&lt;/span&gt;&lt;span&gt;&amp;lt;/code&amp;gt;&lt;/span&gt;&lt;span&gt; 显示大数据文件被加入暂存区，可以取消暂存：&lt;/span&gt;&lt;span&gt;&amp;lt;/p&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;&amp;lt;div&lt;/span&gt;&lt;span&gt; class=&lt;/span&gt;&lt;span&gt;&quot;omp-code-frame&quot;&lt;/span&gt;&lt;span&gt; data-lang=&lt;/span&gt;&lt;span&gt;&quot;fish&quot;&lt;/span&gt;&lt;span&gt;&amp;gt;&amp;lt;div&lt;/span&gt;&lt;span&gt; class=&lt;/span&gt;&lt;span&gt;&quot;omp-code-bar&quot;&lt;/span&gt;&lt;span&gt;&amp;gt;&amp;lt;span&amp;gt;omp&amp;lt;/span&amp;gt;&amp;lt;span&amp;gt;fish&amp;lt;/span&amp;gt;&amp;lt;/div&amp;gt;&amp;lt;pre&amp;gt;&amp;lt;code&lt;/span&gt;&lt;span&gt; class=&lt;/span&gt;&lt;span&gt;&quot;language-fish&quot;&lt;/span&gt;&lt;span&gt;&amp;gt;git&lt;/span&gt;&lt;span&gt; restore --staged data/raw/large_file.csv&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;&amp;lt;/code&amp;gt;&amp;lt;/pre&amp;gt;&amp;lt;/div&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;&amp;lt;p&amp;gt;&lt;/span&gt;&lt;span&gt;然后把它加入 &lt;/span&gt;&lt;span&gt;&amp;lt;code&amp;gt;.gitignore&amp;lt;/code&amp;gt;&lt;/span&gt;&lt;span&gt;，并在 README 写清楚下载方式。&lt;/span&gt;&lt;span&gt;&amp;lt;/p&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;&amp;lt;p&amp;gt;&lt;/span&gt;&lt;span&gt;如果数据已经提交过，处理历史会更复杂。初学阶段先养成提交前检查的习惯。&lt;/span&gt;&lt;span&gt;&amp;lt;/p&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;&amp;lt;h2&amp;gt;17.&lt;/span&gt;&lt;span&gt; 最终验收清单&lt;/span&gt;&lt;span&gt;&amp;lt;/h2&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;&amp;lt;p&amp;gt;&lt;/span&gt;&lt;span&gt;在项目根目录执行：&lt;/span&gt;&lt;span&gt;&amp;lt;/p&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;&amp;lt;div&lt;/span&gt;&lt;span&gt; class=&lt;/span&gt;&lt;span&gt;&quot;omp-code-frame&quot;&lt;/span&gt;&lt;span&gt; data-lang=&lt;/span&gt;&lt;span&gt;&quot;fish&quot;&lt;/span&gt;&lt;span&gt;&amp;gt;&amp;lt;div&lt;/span&gt;&lt;span&gt; class=&lt;/span&gt;&lt;span&gt;&quot;omp-code-bar&quot;&lt;/span&gt;&lt;span&gt;&amp;gt;&amp;lt;span&amp;gt;omp&amp;lt;/span&amp;gt;&amp;lt;span&amp;gt;fish&amp;lt;/span&amp;gt;&amp;lt;/div&amp;gt;&amp;lt;pre&amp;gt;&amp;lt;code&lt;/span&gt;&lt;span&gt; class=&lt;/span&gt;&lt;span&gt;&quot;language-fish&quot;&lt;/span&gt;&lt;span&gt;&amp;gt;source&lt;/span&gt;&lt;span&gt; .venv/bin/activate.fish&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;uv&lt;/span&gt;&lt;span&gt; sync&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;python&lt;/span&gt;&lt;span&gt; src/train_baseline.py&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;test&lt;/span&gt;&lt;span&gt; -f README.md; &lt;/span&gt;&lt;span&gt;and&lt;/span&gt;&lt;span&gt; echo &lt;/span&gt;&lt;span&gt;&amp;amp;&lt;/span&gt;&lt;span&gt;quot;&lt;/span&gt;&lt;span&gt;README&lt;/span&gt;&lt;span&gt; exists&lt;/span&gt;&lt;span&gt;&amp;amp;&lt;/span&gt;&lt;span&gt;quot;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;test&lt;/span&gt;&lt;span&gt; -f docs/data_card.md; &lt;/span&gt;&lt;span&gt;and&lt;/span&gt;&lt;span&gt; echo &lt;/span&gt;&lt;span&gt;&amp;amp;&lt;/span&gt;&lt;span&gt;quot;&lt;/span&gt;&lt;span&gt;data&lt;/span&gt;&lt;span&gt; card exists&lt;/span&gt;&lt;span&gt;&amp;amp;&lt;/span&gt;&lt;span&gt;quot;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;test&lt;/span&gt;&lt;span&gt; -f reports/final_report.md; &lt;/span&gt;&lt;span&gt;and&lt;/span&gt;&lt;span&gt; echo &lt;/span&gt;&lt;span&gt;&amp;amp;&lt;/span&gt;&lt;span&gt;quot;&lt;/span&gt;&lt;span&gt;final&lt;/span&gt;&lt;span&gt; report exists&lt;/span&gt;&lt;span&gt;&amp;amp;&lt;/span&gt;&lt;span&gt;quot;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;test&lt;/span&gt;&lt;span&gt; -f reports/error_analysis.md; &lt;/span&gt;&lt;span&gt;and&lt;/span&gt;&lt;span&gt; echo &lt;/span&gt;&lt;span&gt;&amp;amp;&lt;/span&gt;&lt;span&gt;quot;&lt;/span&gt;&lt;span&gt;error&lt;/span&gt;&lt;span&gt; analysis exists&lt;/span&gt;&lt;span&gt;&amp;amp;&lt;/span&gt;&lt;span&gt;quot;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;&amp;lt;/code&amp;gt;&amp;lt;/pre&amp;gt;&amp;lt;/div&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;&amp;lt;p&amp;gt;&lt;/span&gt;&lt;span&gt;人工检查 README：&lt;/span&gt;&lt;span&gt;&amp;lt;/p&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;&amp;lt;ul&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;&amp;lt;li&amp;gt;Problem&lt;/span&gt;&lt;span&gt; 清楚。&lt;/span&gt;&lt;span&gt;&amp;lt;/li&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;&amp;lt;li&amp;gt;Data&lt;/span&gt;&lt;span&gt; 来源明确。&lt;/span&gt;&lt;span&gt;&amp;lt;/li&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;&amp;lt;li&amp;gt;Method&lt;/span&gt;&lt;span&gt; 说明了预处理、模型、验证方式。&lt;/span&gt;&lt;span&gt;&amp;lt;/li&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;&amp;lt;li&amp;gt;Experiments&lt;/span&gt;&lt;span&gt; 有结果表。&lt;/span&gt;&lt;span&gt;&amp;lt;/li&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;&amp;lt;li&amp;gt;Results&lt;/span&gt;&lt;span&gt; 没有夸大。&lt;/span&gt;&lt;span&gt;&amp;lt;/li&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;&amp;lt;li&amp;gt;Error&lt;/span&gt;&lt;span&gt; Analysis 有具体观察。&lt;/span&gt;&lt;span&gt;&amp;lt;/li&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;&amp;lt;li&amp;gt;Limitations&lt;/span&gt;&lt;span&gt; 诚实。&lt;/span&gt;&lt;span&gt;&amp;lt;/li&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;&amp;lt;li&amp;gt;How&lt;/span&gt;&lt;span&gt; to Run 可复制执行。&lt;/span&gt;&lt;span&gt;&amp;lt;/li&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;&amp;lt;li&amp;gt;Project&lt;/span&gt;&lt;span&gt; Structure 解释主要目录。&lt;/span&gt;&lt;span&gt;&amp;lt;/li&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;&amp;lt;/ul&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;&amp;lt;p&amp;gt;&lt;/span&gt;&lt;span&gt;人工检查报告：&lt;/span&gt;&lt;span&gt;&amp;lt;/p&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;&amp;lt;ul&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;&amp;lt;li&amp;gt;&lt;/span&gt;&lt;span&gt;有数据说明。&lt;/span&gt;&lt;span&gt;&amp;lt;/li&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;&amp;lt;li&amp;gt;&lt;/span&gt;&lt;span&gt;有实验设计。&lt;/span&gt;&lt;span&gt;&amp;lt;/li&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;&amp;lt;li&amp;gt;&lt;/span&gt;&lt;span&gt;有 baseline 和至少一个 sklearn 模型。&lt;/span&gt;&lt;span&gt;&amp;lt;/li&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;&amp;lt;li&amp;gt;&lt;/span&gt;&lt;span&gt;有指标选择理由。&lt;/span&gt;&lt;span&gt;&amp;lt;/li&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;&amp;lt;li&amp;gt;&lt;/span&gt;&lt;span&gt;有结果表。&lt;/span&gt;&lt;span&gt;&amp;lt;/li&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;&amp;lt;li&amp;gt;&lt;/span&gt;&lt;span&gt;有错误分析。&lt;/span&gt;&lt;span&gt;&amp;lt;/li&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;&amp;lt;li&amp;gt;&lt;/span&gt;&lt;span&gt;有限制和下一步。&lt;/span&gt;&lt;span&gt;&amp;lt;/li&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;&amp;lt;/ul&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;&amp;lt;p&amp;gt;&lt;/span&gt;&lt;span&gt;人工检查 Git：&lt;/span&gt;&lt;span&gt;&amp;lt;/p&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;&amp;lt;div&lt;/span&gt;&lt;span&gt; class=&lt;/span&gt;&lt;span&gt;&quot;omp-code-frame&quot;&lt;/span&gt;&lt;span&gt; data-lang=&lt;/span&gt;&lt;span&gt;&quot;fish&quot;&lt;/span&gt;&lt;span&gt;&amp;gt;&amp;lt;div&lt;/span&gt;&lt;span&gt; class=&lt;/span&gt;&lt;span&gt;&quot;omp-code-bar&quot;&lt;/span&gt;&lt;span&gt;&amp;gt;&amp;lt;span&amp;gt;omp&amp;lt;/span&amp;gt;&amp;lt;span&amp;gt;fish&amp;lt;/span&amp;gt;&amp;lt;/div&amp;gt;&amp;lt;pre&amp;gt;&amp;lt;code&lt;/span&gt;&lt;span&gt; class=&lt;/span&gt;&lt;span&gt;&quot;language-fish&quot;&lt;/span&gt;&lt;span&gt;&amp;gt;git&lt;/span&gt;&lt;span&gt; status --short&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;&amp;lt;/code&amp;gt;&amp;lt;/pre&amp;gt;&amp;lt;/div&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;&amp;lt;p&amp;gt;&lt;/span&gt;&lt;span&gt;理想状态是没有未提交变更，或只剩你明确不想提交的数据文件。&lt;/span&gt;&lt;span&gt;&amp;lt;/p&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;&amp;lt;h2&amp;gt;18.&lt;/span&gt;&lt;span&gt; 常见错误&lt;/span&gt;&lt;span&gt;&amp;lt;/h2&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;&amp;lt;h3&amp;gt;&lt;/span&gt;&lt;span&gt;错误 1：README 的运行命令不完整&lt;/span&gt;&lt;span&gt;&amp;lt;/h3&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;&amp;lt;p&amp;gt;&lt;/span&gt;&lt;span&gt;只写 &lt;/span&gt;&lt;span&gt;&amp;lt;code&amp;gt;python&lt;/span&gt;&lt;span&gt; src/train_baseline.py&lt;/span&gt;&lt;span&gt;&amp;lt;/code&amp;gt;&lt;/span&gt;&lt;span&gt; 不够。别人还需要知道如何创建环境、安装依赖、放置数据。&lt;/span&gt;&lt;span&gt;&amp;lt;/p&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;&amp;lt;p&amp;gt;&lt;/span&gt;&lt;span&gt;最低限度写：&lt;/span&gt;&lt;span&gt;&amp;lt;/p&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;&amp;lt;div&lt;/span&gt;&lt;span&gt; class=&lt;/span&gt;&lt;span&gt;&quot;omp-code-frame&quot;&lt;/span&gt;&lt;span&gt; data-lang=&lt;/span&gt;&lt;span&gt;&quot;fish&quot;&lt;/span&gt;&lt;span&gt;&amp;gt;&amp;lt;div&lt;/span&gt;&lt;span&gt; class=&lt;/span&gt;&lt;span&gt;&quot;omp-code-bar&quot;&lt;/span&gt;&lt;span&gt;&amp;gt;&amp;lt;span&amp;gt;omp&amp;lt;/span&amp;gt;&amp;lt;span&amp;gt;fish&amp;lt;/span&amp;gt;&amp;lt;/div&amp;gt;&amp;lt;pre&amp;gt;&amp;lt;code&lt;/span&gt;&lt;span&gt; class=&lt;/span&gt;&lt;span&gt;&quot;language-fish&quot;&lt;/span&gt;&lt;span&gt;&amp;gt;uv&lt;/span&gt;&lt;span&gt; venv&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;source&lt;/span&gt;&lt;span&gt; .venv/bin/activate.fish&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;uv&lt;/span&gt;&lt;span&gt; sync&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;python&lt;/span&gt;&lt;span&gt; src/train_baseline.py&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;&amp;lt;/code&amp;gt;&amp;lt;/pre&amp;gt;&amp;lt;/div&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;&amp;lt;h3&amp;gt;&lt;/span&gt;&lt;span&gt;错误 2：报告和代码结果不一致&lt;/span&gt;&lt;span&gt;&amp;lt;/h3&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;&amp;lt;p&amp;gt;&lt;/span&gt;&lt;span&gt;如果你重新运行脚本后结果变了，README 和 final_report 也要更新。不要让报告写旧结果。&lt;/span&gt;&lt;span&gt;&amp;lt;/p&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;&amp;lt;h3&amp;gt;&lt;/span&gt;&lt;span&gt;错误 3：没有说明数据限制&lt;/span&gt;&lt;span&gt;&amp;lt;/h3&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;&amp;lt;p&amp;gt;&lt;/span&gt;&lt;span&gt;所有数据都有偏差。限制可以包括样本量、时间范围、采样方式、缺失值、标签噪声、不可观测变量。&lt;/span&gt;&lt;span&gt;&amp;lt;/p&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;&amp;lt;h3&amp;gt;&lt;/span&gt;&lt;span&gt;错误 4：把 notebook 当成最终交付&lt;/span&gt;&lt;span&gt;&amp;lt;/h3&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;&amp;lt;p&amp;gt;Notebook&lt;/span&gt;&lt;span&gt; 可以展示探索过程，但最终复现入口应是脚本。面试官更希望看到清楚的项目结构和可运行命令。&lt;/span&gt;&lt;span&gt;&amp;lt;/p&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;&amp;lt;h3&amp;gt;&lt;/span&gt;&lt;span&gt;错误 5：提交了虚拟环境&lt;/span&gt;&lt;span&gt;&amp;lt;/h3&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;&amp;lt;p&amp;gt;&amp;lt;code&amp;gt;.venv/&amp;lt;/code&amp;gt;&lt;/span&gt;&lt;span&gt; 可能包含成千上万个文件，只属于你的机器。提交它会让仓库巨大且不可维护。&lt;/span&gt;&lt;span&gt;&amp;lt;/p&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;&amp;lt;p&amp;gt;&lt;/span&gt;&lt;span&gt;修复：&lt;/span&gt;&lt;span&gt;&amp;lt;/p&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;&amp;lt;div&lt;/span&gt;&lt;span&gt; class=&lt;/span&gt;&lt;span&gt;&quot;omp-code-frame&quot;&lt;/span&gt;&lt;span&gt; data-lang=&lt;/span&gt;&lt;span&gt;&quot;fish&quot;&lt;/span&gt;&lt;span&gt;&amp;gt;&amp;lt;div&lt;/span&gt;&lt;span&gt; class=&lt;/span&gt;&lt;span&gt;&quot;omp-code-bar&quot;&lt;/span&gt;&lt;span&gt;&amp;gt;&amp;lt;span&amp;gt;omp&amp;lt;/span&amp;gt;&amp;lt;span&amp;gt;fish&amp;lt;/span&amp;gt;&amp;lt;/div&amp;gt;&amp;lt;pre&amp;gt;&amp;lt;code&lt;/span&gt;&lt;span&gt; class=&lt;/span&gt;&lt;span&gt;&quot;language-fish&quot;&lt;/span&gt;&lt;span&gt;&amp;gt;code&lt;/span&gt;&lt;span&gt; .gitignore&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;git&lt;/span&gt;&lt;span&gt; status --short&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;&amp;lt;/code&amp;gt;&amp;lt;/pre&amp;gt;&amp;lt;/div&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;&amp;lt;p&amp;gt;&lt;/span&gt;&lt;span&gt;确认 &lt;/span&gt;&lt;span&gt;&amp;lt;code&amp;gt;.gitignore&amp;lt;/code&amp;gt;&lt;/span&gt;&lt;span&gt; 有：&lt;/span&gt;&lt;span&gt;&amp;lt;/p&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;&amp;lt;div&lt;/span&gt;&lt;span&gt; class=&lt;/span&gt;&lt;span&gt;&quot;omp-code-frame&quot;&lt;/span&gt;&lt;span&gt; data-lang=&lt;/span&gt;&lt;span&gt;&quot;prompt&quot;&lt;/span&gt;&lt;span&gt;&amp;gt;&amp;lt;div&lt;/span&gt;&lt;span&gt; class=&lt;/span&gt;&lt;span&gt;&quot;omp-code-bar&quot;&lt;/span&gt;&lt;span&gt;&amp;gt;&amp;lt;span&amp;gt;omp&amp;lt;/span&amp;gt;&amp;lt;span&amp;gt;prompt&amp;lt;/span&amp;gt;&amp;lt;/div&amp;gt;&amp;lt;pre&amp;gt;&amp;lt;code&lt;/span&gt;&lt;span&gt; class=&lt;/span&gt;&lt;span&gt;&quot;language-text&quot;&lt;/span&gt;&lt;span&gt;&amp;gt;.venv/&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;&amp;lt;/code&amp;gt;&amp;lt;/pre&amp;gt;&amp;lt;/div&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;&amp;lt;h3&amp;gt;&lt;/span&gt;&lt;span&gt;错误 6：过度美化结论&lt;/span&gt;&lt;span&gt;&amp;lt;/h3&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;&amp;lt;p&amp;gt;&lt;/span&gt;&lt;span&gt;不要把一次课程项目写成工业级系统。诚实说明“这是 baseline 项目”，反而更专业。&lt;/span&gt;&lt;span&gt;&amp;lt;/p&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;&amp;lt;h2&amp;gt;19.&lt;/span&gt;&lt;span&gt; 面向老师或面试官的项目摘要&lt;/span&gt;&lt;span&gt;&amp;lt;/h2&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;&amp;lt;p&amp;gt;&lt;/span&gt;&lt;span&gt;你可以在 README 顶部或简历中写 3-4 行摘要：&lt;/span&gt;&lt;span&gt;&amp;lt;/p&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;&amp;lt;div&lt;/span&gt;&lt;span&gt; class=&lt;/span&gt;&lt;span&gt;&quot;omp-code-frame&quot;&lt;/span&gt;&lt;span&gt; data-lang=&lt;/span&gt;&lt;span&gt;&quot;prompt&quot;&lt;/span&gt;&lt;span&gt;&amp;gt;&amp;lt;div&lt;/span&gt;&lt;span&gt; class=&lt;/span&gt;&lt;span&gt;&quot;omp-code-bar&quot;&lt;/span&gt;&lt;span&gt;&amp;gt;&amp;lt;span&amp;gt;omp&amp;lt;/span&amp;gt;&amp;lt;span&amp;gt;prompt&amp;lt;/span&amp;gt;&amp;lt;/div&amp;gt;&amp;lt;pre&amp;gt;&amp;lt;code&lt;/span&gt;&lt;span&gt; class=&lt;/span&gt;&lt;span&gt;&quot;language-text&quot;&lt;/span&gt;&lt;span&gt;&amp;gt;Built&lt;/span&gt;&lt;span&gt; a reproducible statistical machine learning baseline for a tabular prediction task using Python, pandas, and scikit-learn. Designed a leakage-safe validation workflow with cross validation, compared interpretable baseline models, and documented error analysis and limitations. Packaged the project with uv environment management, clear README instructions, and Git-tracked reports.&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;&amp;lt;/code&amp;gt;&amp;lt;/pre&amp;gt;&amp;lt;/div&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;&amp;lt;p&amp;gt;&lt;/span&gt;&lt;span&gt;如果是中文场景：&lt;/span&gt;&lt;span&gt;&amp;lt;/p&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;&amp;lt;div&lt;/span&gt;&lt;span&gt; class=&lt;/span&gt;&lt;span&gt;&quot;omp-code-frame&quot;&lt;/span&gt;&lt;span&gt; data-lang=&lt;/span&gt;&lt;span&gt;&quot;prompt&quot;&lt;/span&gt;&lt;span&gt;&amp;gt;&amp;lt;div&lt;/span&gt;&lt;span&gt; class=&lt;/span&gt;&lt;span&gt;&quot;omp-code-bar&quot;&lt;/span&gt;&lt;span&gt;&amp;gt;&amp;lt;span&amp;gt;omp&amp;lt;/span&amp;gt;&amp;lt;span&amp;gt;prompt&amp;lt;/span&amp;gt;&amp;lt;/div&amp;gt;&amp;lt;pre&amp;gt;&amp;lt;code&lt;/span&gt;&lt;span&gt; class=&lt;/span&gt;&lt;span&gt;&quot;language-text&quot;&lt;/span&gt;&lt;span&gt;&amp;gt;&lt;/span&gt;&lt;span&gt;使用 Python、pandas 与 scikit-learn 完成一个可复现的表格数据预测 baseline 项目；设计了防止数据泄漏的验证流程，比较多个经典模型，并整理数据卡、实验结果、错误分析与项目复现说明。&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;&amp;lt;/code&amp;gt;&amp;lt;/pre&amp;gt;&amp;lt;/div&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;&amp;lt;p&amp;gt;&lt;/span&gt;&lt;span&gt;注意：摘要中不要写没有做过的东西，比如“部署”“深度学习”“自动化特征工程”。&lt;/span&gt;&lt;span&gt;&amp;lt;/p&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;&amp;lt;h2&amp;gt;20.&lt;/span&gt;&lt;span&gt; 本周练习&lt;/span&gt;&lt;span&gt;&amp;lt;/h2&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;&amp;lt;ol&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;&amp;lt;li&amp;gt;&lt;/span&gt;&lt;span&gt;从零执行 README 的 How to Run，确认命令完整。&lt;/span&gt;&lt;span&gt;&amp;lt;/li&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;&amp;lt;li&amp;gt;&lt;/span&gt;&lt;span&gt;让同学或未来的自己只看 README，判断能否理解项目。&lt;/span&gt;&lt;span&gt;&amp;lt;/li&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;&amp;lt;li&amp;gt;&lt;/span&gt;&lt;span&gt;给 final_report 增加一个“Metric Choice”小节。&lt;/span&gt;&lt;span&gt;&amp;lt;/li&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;&amp;lt;li&amp;gt;&lt;/span&gt;&lt;span&gt;给 README 增加项目结构树，并解释每个目录。&lt;/span&gt;&lt;span&gt;&amp;lt;/li&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;&amp;lt;li&amp;gt;&lt;/span&gt;&lt;span&gt;检查 Git 状态，确保 &lt;/span&gt;&lt;span&gt;&amp;lt;code&amp;gt;.venv/&amp;lt;/code&amp;gt;&lt;/span&gt;&lt;span&gt; 没被提交。&lt;/span&gt;&lt;span&gt;&amp;lt;/li&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;&amp;lt;li&amp;gt;&lt;/span&gt;&lt;span&gt;写 5 条 next steps，每条都必须可以在未来一周内验证。&lt;/span&gt;&lt;span&gt;&amp;lt;/li&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;&amp;lt;/ol&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;&amp;lt;h2&amp;gt;21.&lt;/span&gt;&lt;span&gt; 下一步&lt;/span&gt;&lt;span&gt;&amp;lt;/h2&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;&amp;lt;p&amp;gt;&lt;/span&gt;&lt;span&gt;回到主线：&lt;/span&gt;&lt;span&gt;&amp;lt;a&lt;/span&gt;&lt;span&gt; href=&lt;/span&gt;&lt;span&gt;&quot;/post/ustc-stat-ai-quant-plan/&quot;&lt;/span&gt;&lt;span&gt;&amp;gt;USTC&lt;/span&gt;&lt;span&gt; 统计学：AI / 量化金融 20 周终端式成长计划&lt;/span&gt;&lt;span&gt;&amp;lt;/a&amp;gt;&lt;/span&gt;&lt;span&gt;。Phase 2 到这里结束。接下来 Week 09-10 会进入 PyTorch：你将从 sklearn 的表格建模，转向张量、Dataset / DataLoader、神经网络训练循环与深度学习 baseline。&lt;/span&gt;&lt;span&gt;&amp;lt;/p&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    &amp;lt;/article&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  &amp;lt;/div&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;&amp;lt;/section&amp;gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/article&gt;&lt;/div&gt;&lt;/section&gt;</content:encoded><category>category:工具</category><category>category:机器学习</category><category>tag:sklearn</category><category>tag:project-report</category><category>tag:reproducibility</category><category>tag:error-analysis</category></item><item><title>Week 07：统计机器学习项目启动、数据卡与 Baseline</title><link>https://39miku.space/post/week07-stat-ml-project-start</link><guid isPermaLink="false">week07-stat-ml-project-start</guid><description>Week 07：统计机器学习项目启动、数据卡与 Baseline。fish-first 终端教学，面向 CachyOS、VS Code、uv 和 Python 学习路线。</description><pubDate>Sat, 25 Jul 2026 23:00:00 GMT</pubDate><content:encoded>
&lt;section&gt;
  &lt;div&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;strong&gt;Oh My Pi&lt;/strong&gt; / weekly tutorial / week07-stat-ml-project-start&lt;/span&gt;&lt;/div&gt;
  &lt;div&gt;
    &lt;div&gt;&lt;span&gt;miku@cachyos&lt;/span&gt;&lt;span&gt;:~/Code/python-learning&lt;/span&gt;&lt;span&gt;$&lt;/span&gt; &lt;span&gt;omp teach week07-stat-ml-project-start --fish-first --step-by-step&lt;/span&gt;&lt;/div&gt;
    &lt;div&gt;
      &lt;div&gt;&lt;span&gt;source&lt;/span&gt;&lt;strong&gt;710 行教学文档&lt;/strong&gt;&lt;/div&gt;
      &lt;div&gt;&lt;span&gt;week&lt;/span&gt;&lt;strong&gt;Week 07&lt;/strong&gt;&lt;/div&gt;
      &lt;div&gt;&lt;span&gt;shell&lt;/span&gt;&lt;strong&gt;fish-first 命令版&lt;/strong&gt;&lt;/div&gt;
      &lt;div&gt;&lt;span&gt;backlink&lt;/span&gt;&lt;strong&gt;&lt;a href=&quot;/post/ustc-stat-ai-quant-plan/&quot;&gt;20 周计划&lt;/a&gt;&lt;/strong&gt;&lt;/div&gt;
    &lt;/div&gt;
    &lt;article&gt;
&lt;h1&gt;Week 07：统计机器学习项目启动、数据卡与 Baseline&lt;/h1&gt;
&lt;blockquote&gt;
&lt;p&gt;返回主线计划：&lt;a href=&quot;/post/ustc-stat-ai-quant-plan/&quot;&gt;USTC 统计学：AI / 量化金融 20 周终端式成长计划&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Week 05-06 你已经能训练 sklearn baseline、做交叉验证和模型选择。Week 07 开始把这些能力组织成一个可以展示的统计机器学习项目。目标不是“写很多模型”，而是让一个陌生人打开你的仓库后能看懂：问题是什么、数据从哪里来、如何运行、baseline 是什么、目前错误在哪里。&lt;/p&gt;
&lt;p&gt;本文默认 CachyOS + fish shell + VS Code + uv + Python + Git。命令按 fish 写。&lt;/p&gt;
&lt;p&gt;&lt;a&gt;&lt;/a&gt;&lt;/p&gt;
&lt;h2&gt;0. 本周详细教学：语法、规范、验收&lt;/h2&gt;
&lt;blockquote&gt;
&lt;p&gt;本节不是追加在尾部的复习，而是本周正文的入口。先读这里，再做后面的命令和项目。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h3&gt;0.1 本周真正要学会什么&lt;/h3&gt;
&lt;table&gt;
&lt;thead&gt;&lt;tr&gt;&lt;th&gt;维度&lt;/th&gt;&lt;th&gt;要求&lt;/th&gt;&lt;/tr&gt;&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;&lt;td&gt;知识点&lt;/td&gt;&lt;td&gt;问题定义、数据卡、baseline、特征工程计划&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;代码语法&lt;/td&gt;&lt;td&gt;能从空文件写出本周核心脚本，而不是只复制运行&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;程序规范&lt;/td&gt;&lt;td&gt;函数拆分、路径清楚、输入输出明确、错误能解释&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;交付物&lt;/td&gt;&lt;td&gt;&lt;code&gt;projects/stat-ml-baseline/README.md&lt;/code&gt;&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;验收方式&lt;/td&gt;&lt;td&gt;从 fish 终端运行命令，得到可复查的文件或指标&lt;/td&gt;&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;h3&gt;0.2 代码语法精讲&lt;/h3&gt;
&lt;p&gt;下面的代码不是最终答案，而是本周必须理解的最小骨架：&lt;/p&gt;
&lt;div&gt;&lt;div&gt;&lt;span&gt;omp&lt;/span&gt;&lt;span&gt;python&lt;/span&gt;&lt;/div&gt;&lt;pre&gt;&lt;code&gt;problem = {
    &quot;target&quot;: &quot;predict whether sample is positive&quot;,
    &quot;unit&quot;: &quot;one row is one observation&quot;,
    &quot;baseline&quot;: &quot;DummyClassifier + LogisticRegression&quot;,
    &quot;risk&quot;: &quot;target leakage from future columns&quot;,
}
for k, v in problem.items():
    print(f&quot;{k}: {v}&quot;)
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;读代码时按四步检查：输入从哪里来；中间变量的类型和 shape 是什么；函数或脚本输出什么；哪些错误应该显式报出来。&lt;/p&gt;
&lt;h3&gt;0.3 本周程序规范&lt;/h3&gt;
&lt;ul&gt;&lt;li&gt;所有路径用相对路径或 `pathlib.Path`，不要写死 `/home/miku/...`。&lt;/li&gt;&lt;li&gt;核心逻辑进 `src/`，notebook 只做探索和解释。&lt;/li&gt;&lt;li&gt;每个脚本能从 fish 终端运行，并在 README 写出命令。&lt;/li&gt;&lt;li&gt;输出必须落盘到 `reports/`、`figures/` 或 `outputs/`，不能只在屏幕上看。&lt;/li&gt;&lt;/ul&gt;
&lt;h3&gt;0.4 本周练习分层&lt;/h3&gt;
&lt;table&gt;
&lt;thead&gt;&lt;tr&gt;&lt;th&gt;层级&lt;/th&gt;&lt;th&gt;任务&lt;/th&gt;&lt;th&gt;不合格表现&lt;/th&gt;&lt;th&gt;合格验收&lt;/th&gt;&lt;/tr&gt;&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;&lt;td&gt;最小练习&lt;/td&gt;&lt;td&gt;手写上面的最小骨架&lt;/td&gt;&lt;td&gt;只在 notebook 里运行&lt;/td&gt;&lt;td&gt;终端运行成功&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;标准练习&lt;/td&gt;&lt;td&gt;把逻辑拆成函数/模块&lt;/td&gt;&lt;td&gt;一个大脚本从头写到尾&lt;/td&gt;&lt;td&gt;至少 2 个函数，职责清楚&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;项目练习&lt;/td&gt;&lt;td&gt;生成本周交付物 &lt;code&gt;projects/stat-ml-baseline/README.md&lt;/code&gt;&lt;/td&gt;&lt;td&gt;只有屏幕输出&lt;/td&gt;&lt;td&gt;文件落盘，可复查&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;复盘练习&lt;/td&gt;&lt;td&gt;写 3 个错误和修复&lt;/td&gt;&lt;td&gt;只写“已解决”&lt;/td&gt;&lt;td&gt;写清报错、原因、修复、预防&lt;/td&gt;&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;h3&gt;0.5 本周和主线的连接&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;回到总计划：&lt;a href=&quot;/post/ustc-stat-ai-quant-plan/#week-plan&quot;&gt;USTC AI / Quant 练习手册&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;查详细练习索引：&lt;a href=&quot;/post/ustc-stat-ai-quant-plan/#deep-practice-appendix&quot;&gt;技术练习详解&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;查质量评分：&lt;a href=&quot;/post/ustc-stat-ai-quant-plan/#quality-final-gates&quot;&gt;最终质量门槛&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;1. 本周目标&lt;/h2&gt;
&lt;p&gt;创建一个完整项目骨架：&lt;/p&gt;
&lt;div&gt;&lt;div&gt;&lt;span&gt;omp&lt;/span&gt;&lt;span&gt;prompt&lt;/span&gt;&lt;/div&gt;&lt;pre&gt;&lt;code&gt;projects/stat-ml-baseline/
├── README.md
├── data/
├── docs/
├── notebooks/
├── reports/
└── src/
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;并完成四个核心产物：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;docs/data_card.md&lt;/code&gt;：说明数据来源、字段、目标变量、限制。&lt;/li&gt;
&lt;li&gt;&lt;code&gt;src/train_baseline.py&lt;/code&gt;：能训练并输出 baseline 结果。&lt;/li&gt;
&lt;li&gt;&lt;code&gt;reports/baseline_results.md&lt;/code&gt;：记录模型、指标、划分方式、初步结论。&lt;/li&gt;
&lt;li&gt;&lt;code&gt;reports/error_analysis.md&lt;/code&gt;：分析错误样本与下一步改进。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;本周你要从“脚本能跑”升级为“项目能被别人理解”。&lt;/p&gt;
&lt;h2&gt;2. 前置条件&lt;/h2&gt;
&lt;p&gt;你应该已经完成：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Week 05：train / validation / test、多个 baseline、分类或回归指标。&lt;/li&gt;
&lt;li&gt;Week 06：KFold / StratifiedKFold、Pipeline、GridSearchCV、防泄漏。&lt;/li&gt;
&lt;li&gt;Week 03-04：pandas 清洗、EDA 图表、报告写作。&lt;/li&gt;
&lt;li&gt;Git 基础：&lt;code&gt;git status&lt;/code&gt;、&lt;code&gt;git add&lt;/code&gt;、&lt;code&gt;git commit&lt;/code&gt;。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;检查工具：&lt;/p&gt;
&lt;div&gt;&lt;div&gt;&lt;span&gt;omp&lt;/span&gt;&lt;span&gt;fish&lt;/span&gt;&lt;/div&gt;&lt;pre&gt;&lt;code&gt;python --version
uv --version
git --version
code --version
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;如果你刚打开新终端，后面每次运行项目前都先激活：&lt;/p&gt;
&lt;div&gt;&lt;div&gt;&lt;span&gt;omp&lt;/span&gt;&lt;span&gt;fish&lt;/span&gt;&lt;/div&gt;&lt;pre&gt;&lt;code&gt;source .venv/bin/activate.fish
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;h2&gt;3. 选题原则&lt;/h2&gt;
&lt;p&gt;推荐题目：&lt;/p&gt;
&lt;div&gt;&lt;div&gt;&lt;span&gt;omp&lt;/span&gt;&lt;span&gt;prompt&lt;/span&gt;&lt;/div&gt;&lt;pre&gt;&lt;code&gt;基于统计学习的表格数据预测与误差分析
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;你可以选择：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Kaggle 入门数据：Titanic、House Prices、Spaceship Titanic。&lt;/li&gt;
&lt;li&gt;UCI 数据集：Adult Income、Wine Quality、Bank Marketing。&lt;/li&gt;
&lt;li&gt;金融或经济公开数据：指数收益、宏观指标、公司财务指标。&lt;/li&gt;
&lt;li&gt;校内课程数据：只要不涉及隐私或不可公开数据。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;选择标准：&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;标准&lt;/th&gt;
&lt;th&gt;建议&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;&lt;tr&gt;
&lt;td&gt;数据规模&lt;/td&gt;
&lt;td&gt;初学项目优先 500 到 100000 行，不要一开始选超大数据&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;数据类型&lt;/td&gt;
&lt;td&gt;表格数据优先，特征包括数值和类别都可以&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;目标变量&lt;/td&gt;
&lt;td&gt;明确：分类或回归，不要模糊&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;可解释性&lt;/td&gt;
&lt;td&gt;能讲清楚每个字段大概含义&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;合法性&lt;/td&gt;
&lt;td&gt;数据来源可引用，不含隐私或违反平台条款&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;&lt;/table&gt;
&lt;p&gt;如果不知道选什么，建议用 UCI Wine Quality 或 Kaggle Titanic。它们简单、可解释、适合展示完整流程。&lt;/p&gt;
&lt;h2&gt;4. 建立项目目录&lt;/h2&gt;
&lt;p&gt;在学习总目录下创建项目：&lt;/p&gt;
&lt;div&gt;&lt;div&gt;&lt;span&gt;omp&lt;/span&gt;&lt;span&gt;fish&lt;/span&gt;&lt;/div&gt;&lt;pre&gt;&lt;code&gt;cd ~/Code/python-learning
mkdir -p projects
cd projects
mkdir -p stat-ml-baseline
cd stat-ml-baseline
pwd
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;初始化 Python 项目：&lt;/p&gt;
&lt;div&gt;&lt;div&gt;&lt;span&gt;omp&lt;/span&gt;&lt;span&gt;fish&lt;/span&gt;&lt;/div&gt;&lt;pre&gt;&lt;code&gt;uv init
uv venv
source .venv/bin/activate.fish
uv add pandas numpy scikit-learn matplotlib seaborn tabulate
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;创建目录：&lt;/p&gt;
&lt;div&gt;&lt;div&gt;&lt;span&gt;omp&lt;/span&gt;&lt;span&gt;fish&lt;/span&gt;&lt;/div&gt;&lt;pre&gt;&lt;code&gt;mkdir -p data/raw data/interim data/processed docs notebooks reports src
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;用 VS Code 打开：&lt;/p&gt;
&lt;div&gt;&lt;div&gt;&lt;span&gt;omp&lt;/span&gt;&lt;span&gt;fish&lt;/span&gt;&lt;/div&gt;&lt;pre&gt;&lt;code&gt;code .
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;h2&gt;5. 文件布局&lt;/h2&gt;
&lt;p&gt;建议结构：&lt;/p&gt;
&lt;div&gt;&lt;div&gt;&lt;span&gt;omp&lt;/span&gt;&lt;span&gt;prompt&lt;/span&gt;&lt;/div&gt;&lt;pre&gt;&lt;code&gt;stat-ml-baseline/
├── .venv/                       # 本地虚拟环境，不提交
├── data/
│   ├── raw/                     # 原始数据，只读
│   ├── interim/                 # 中间处理结果
│   └── processed/               # 建模用数据
├── docs/
│   └── data_card.md             # 数据卡
├── notebooks/
│   └── 01_eda.ipynb             # 可选：探索分析
├── reports/
│   ├── baseline_results.md      # baseline 结果
│   └── error_analysis.md        # 错误分析
├── src/
│   ├── train_baseline.py        # 训练入口
│   └── make_dataset.py          # 可选：数据清洗入口
├── pyproject.toml
├── uv.lock
└── README.md
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;目录职责要清楚：&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;目录&lt;/th&gt;
&lt;th&gt;职责&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;&lt;tr&gt;
&lt;td&gt;&lt;code&gt;data/raw/&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;放原始数据，不在里面手工改值&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;data/interim/&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;放临时清洗结果，可删除重建&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;data/processed/&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;放最终建模输入&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;docs/&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;放数据说明、设计说明&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;notebooks/&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;放探索性分析，不做最终训练入口&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;reports/&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;放实验结果和错误分析&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;src/&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;放可重复运行的 Python 脚本&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;&lt;/table&gt;
&lt;h2&gt;6. 创建 README 初稿&lt;/h2&gt;
&lt;p&gt;打开 &lt;code&gt;README.md&lt;/code&gt;，先写一个能说明项目方向的初稿。不要等项目结束才写 README，否则你会忘记很多设计选择。&lt;/p&gt;
&lt;p&gt;建议结构：&lt;/p&gt;
&lt;div&gt;&lt;div&gt;&lt;span&gt;omp&lt;/span&gt;&lt;span&gt;markdown&lt;/span&gt;&lt;/div&gt;&lt;pre&gt;&lt;code&gt;# Statistical Machine Learning Baseline
&lt;h2&gt;Problem&lt;a href=&quot;#problem&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;This project predicts … using tabular data. The task is classification / regression.&lt;/p&gt;
&lt;h2&gt;Data&lt;a href=&quot;#data&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;Source:&lt;/li&gt;
&lt;li&gt;Rows:&lt;/li&gt;
&lt;li&gt;Columns:&lt;/li&gt;
&lt;li&gt;Target:&lt;/li&gt;
&lt;li&gt;Unit of observation:&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;Method&lt;a href=&quot;#method&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;Baseline models:&lt;/li&gt;
&lt;li&gt;Validation strategy:&lt;/li&gt;
&lt;li&gt;Main metric:&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;Current Status&lt;a href=&quot;#current-status&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Week 07 project start. Baseline and error analysis are in progress.&lt;/p&gt;
&lt;h2&gt;How to Run&lt;a href=&quot;#how-to-run&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;
&lt;/code&gt;&lt;ol&gt;&lt;code&gt;
&lt;li&gt;Create and activate a uv virtual environment.&lt;/li&gt;
&lt;li&gt;Install dependencies.&lt;/li&gt;
&lt;/code&gt;&lt;li&gt;&lt;code&gt;Run the baseline script.
&lt;/code&gt;&lt;/li&gt;&lt;/ol&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;这不是最终版。Week 08 会继续打磨 README。本周的 README 只要能帮助你保持项目方向即可。&lt;/p&gt;
&lt;h2&gt;7. 创建数据卡&lt;/h2&gt;
&lt;p&gt;创建文件：&lt;/p&gt;
&lt;div&gt;&lt;div&gt;&lt;span&gt;omp&lt;/span&gt;&lt;span&gt;prompt&lt;/span&gt;&lt;/div&gt;&lt;pre&gt;&lt;code&gt;docs/data_card.md
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;建议内容：&lt;/p&gt;
&lt;div&gt;&lt;div&gt;&lt;span&gt;omp&lt;/span&gt;&lt;span&gt;markdown&lt;/span&gt;&lt;/div&gt;&lt;pre&gt;&lt;code&gt;# Data Card
&lt;h2&gt;Dataset Name&lt;a href=&quot;#dataset-name&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Write the dataset name here.&lt;/p&gt;
&lt;h2&gt;Source&lt;a href=&quot;#source&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;URL:&lt;/li&gt;
&lt;li&gt;License or access condition:&lt;/li&gt;
&lt;li&gt;Download date:&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;Prediction Target&lt;a href=&quot;#prediction-target&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;Target column:&lt;/li&gt;
&lt;li&gt;Task type: classification / regression&lt;/li&gt;
&lt;li&gt;Positive class definition if classification:&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;Unit of Observation&lt;a href=&quot;#unit-of-observation&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;One row represents …&lt;/p&gt;
&lt;h2&gt;Columns&lt;a href=&quot;#columns&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;

















&lt;table&gt;&lt;thead&gt;&lt;tr&gt;&lt;th&gt;Column&lt;/th&gt;&lt;th&gt;Type&lt;/th&gt;&lt;th&gt;Meaning&lt;/th&gt;&lt;th&gt;Notes&lt;/th&gt;&lt;/tr&gt;&lt;/thead&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td&gt;example_column&lt;/td&gt;&lt;td&gt;numeric&lt;/td&gt;&lt;td&gt;…&lt;/td&gt;&lt;td&gt;…&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;
&lt;h2&gt;Missing Values&lt;a href=&quot;#missing-values&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Describe missing columns and possible reasons.&lt;/p&gt;
&lt;h2&gt;Known Limitations&lt;a href=&quot;#known-limitations&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;Sampling bias:&lt;/li&gt;
&lt;li&gt;Time period limitation:&lt;/li&gt;
&lt;li&gt;Measurement errors:&lt;/li&gt;
&lt;li&gt;Possible leakage columns:&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;Ethical or Privacy Notes&lt;a href=&quot;#ethical-or-privacy-notes&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;
&lt;/code&gt;&lt;p&gt;&lt;code&gt;State whether the dataset contains personal, financial, medical, or sensitive information.
&lt;/code&gt;&lt;/p&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;&lt;/p&gt;
&lt;p&gt;数据卡不是形式主义。它能防止你在 Week 08 写报告时讲不清数据来源。&lt;/p&gt;
&lt;h2&gt;8. 下载或放入数据&lt;/h2&gt;
&lt;p&gt;如果数据已经手动下载，把 CSV 放到：&lt;/p&gt;
&lt;div&gt;&lt;div&gt;&lt;span&gt;omp&lt;/span&gt;&lt;span&gt;prompt&lt;/span&gt;&lt;/div&gt;&lt;pre&gt;&lt;code&gt;data/raw/dataset.csv
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;用 fish 检查文件：&lt;/p&gt;
&lt;div&gt;&lt;div&gt;&lt;span&gt;omp&lt;/span&gt;&lt;span&gt;fish&lt;/span&gt;&lt;/div&gt;&lt;pre&gt;&lt;code&gt;ls data/raw
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;如果你用 Kaggle，不建议本教程里展开认证流程。初学阶段可以手动从网页下载 zip，再解压到 &lt;code&gt;data/raw/&lt;/code&gt;。如果使用 UCI 的公开 CSV，可以用浏览器下载，或者用 Python 下载。&lt;/p&gt;
&lt;p&gt;重要原则：&lt;/p&gt;
&lt;div&gt;&lt;div&gt;&lt;span&gt;omp&lt;/span&gt;&lt;span&gt;prompt&lt;/span&gt;&lt;/div&gt;&lt;pre&gt;&lt;code&gt;原始数据进 data/raw/
脚本生成的数据进 data/processed/
不要在 Excel 里手改原始数据后覆盖 raw 文件
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;h2&gt;9. 如果暂时没有数据：使用 sklearn 数据集占位&lt;/h2&gt;
&lt;p&gt;为了先搭项目骨架，可以用 sklearn 自带数据集跑通流程。等你选定真实数据后再替换 &lt;code&gt;load_data()&lt;/code&gt;。&lt;/p&gt;
&lt;p&gt;本教程下面使用 breast cancer dataset 做可运行示例。它不是你的最终项目题目也没关系；Week 07 重点是项目结构。&lt;/p&gt;
&lt;h2&gt;10. 编写 baseline 训练脚本&lt;/h2&gt;
&lt;p&gt;创建文件：&lt;/p&gt;
&lt;div&gt;&lt;div&gt;&lt;span&gt;omp&lt;/span&gt;&lt;span&gt;prompt&lt;/span&gt;&lt;/div&gt;&lt;pre&gt;&lt;code&gt;src/train_baseline.py
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;粘贴下面代码：&lt;/p&gt;
&lt;div&gt;&lt;div&gt;&lt;span&gt;omp&lt;/span&gt;&lt;span&gt;python&lt;/span&gt;&lt;/div&gt;&lt;pre&gt;&lt;code&gt;from pathlib import Path
&lt;p&gt;import pandas as pd
from sklearn.datasets import load_breast_cancer
from sklearn.ensemble import RandomForestClassifier
from sklearn.linear_model import LogisticRegression
from sklearn.metrics import accuracy_score, confusion_matrix, f1_score, precision_score, recall_score, roc_auc_score
from sklearn.model_selection import StratifiedKFold, cross_validate, train_test_split
from sklearn.pipeline import Pipeline
from sklearn.preprocessing import StandardScaler&lt;/p&gt;
&lt;p&gt;RANDOM_STATE = 42
RESULT_PATH = Path(“reports/baseline_results.md”)
ERROR_PATH = Path(“reports/error_analysis.md”)&lt;/p&gt;
&lt;p&gt;def load_project_data():
dataset = load_breast_cancer(as_frame=True)
X = dataset.data
y = dataset.target
return X, y, list(dataset.target_names)&lt;/p&gt;
&lt;p&gt;def build_models():
return {
“logistic_regression”: Pipeline(
steps=[
(“scaler”, StandardScaler()),
(“model”, LogisticRegression(max_iter=3000, random_state=RANDOM_STATE)),
]
),
“random_forest”: RandomForestClassifier(
n_estimators=200,
max_depth=None,
min_samples_leaf=1,
random_state=RANDOM_STATE,
),
}&lt;/p&gt;
&lt;p&gt;def score_models(models, X_train, y_train):
cv = StratifiedKFold(n_splits=5, shuffle=True, random_state=RANDOM_STATE)
scoring = {
“accuracy”: “accuracy”,
“precision”: “precision”,
“recall”: “recall”,
“f1”: “f1”,
“roc_auc”: “roc_auc”,
}
rows = []
for name, model in models.items():
scores = cross_validate(model, X_train, y_train, cv=cv, scoring=scoring, n_jobs=-1)
row = {“model”: name}
for metric in scoring:
values = scores[f”test_{metric}”]
row[f”{metric}_mean”] = values.mean()
row[f”{metric}_std”] = values.std()
rows.append(row)
return sorted(rows, key=lambda item: item[“f1_mean”], reverse=True)&lt;/p&gt;
&lt;p&gt;def evaluate_final_model(model, X_train, X_test, y_train, y_test):
model.fit(X_train, y_train)
predictions = model.predict(X_test)
probabilities = model.predict_proba(X_test)[:, 1]
metrics = {
“accuracy”: accuracy_score(y_test, predictions),
“precision”: precision_score(y_test, predictions),
“recall”: recall_score(y_test, predictions),
“f1”: f1_score(y_test, predictions),
“roc_auc”: roc_auc_score(y_test, probabilities),
}
matrix = confusion_matrix(y_test, predictions)
errors = X_test.assign(actual=y_test.to_numpy(), predicted=predictions, probability=probabilities)
errors = errors[errors[“actual”] != errors[“predicted”]]
return metrics, matrix, errors&lt;/p&gt;
&lt;p&gt;def write_baseline_report(rows, test_metrics, target_names):
table_rows = []
for row in rows:
table_rows.append(
”| {model} | {f1_mean:.4f} | {f1_std:.4f} | {roc_auc_mean:.4f} | {recall_mean:.4f} |“.format(
**row
)
)&lt;/p&gt;
&lt;p&gt;plain report = f&quot;&quot;&quot;# Baseline Results&lt;/p&gt;
&lt;h2&gt;Dataset&lt;a href=&quot;#dataset&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;Dataset: sklearn breast cancer dataset or project replacement dataset&lt;/li&gt;
&lt;li&gt;Target names:&lt;/li&gt;
&lt;li&gt;Task: binary classification&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;Validation Design&lt;a href=&quot;#validation-design&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;Holdout test size: 20%&lt;/li&gt;
&lt;li&gt;Cross validation: StratifiedKFold with 5 folds on the training portion&lt;/li&gt;
&lt;li&gt;Selection metric: mean CV F1&lt;/li&gt;
&lt;li&gt;Random state:&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;Cross-Validation Results&lt;a href=&quot;#cross-validation-results&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;



















&lt;table&gt;&lt;thead&gt;&lt;tr&gt;&lt;th&gt;Model&lt;/th&gt;&lt;th&gt;Mean F1&lt;/th&gt;&lt;th&gt;Std F1&lt;/th&gt;&lt;th&gt;Mean ROC AUC&lt;/th&gt;&lt;th&gt;Mean Recall&lt;/th&gt;&lt;/tr&gt;&lt;/thead&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td&gt;{chr(10).join(table_rows)}&lt;/td&gt;&lt;td&gt;&lt;/td&gt;&lt;td&gt;&lt;/td&gt;&lt;td&gt;&lt;/td&gt;&lt;td&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;
&lt;h2&gt;Final Test Metrics&lt;a href=&quot;#final-test-metrics&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;





























&lt;table&gt;&lt;thead&gt;&lt;tr&gt;&lt;th&gt;Metric&lt;/th&gt;&lt;th&gt;Value&lt;/th&gt;&lt;/tr&gt;&lt;/thead&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td&gt;Accuracy&lt;/td&gt;&lt;td&gt;{test_metrics[‘accuracy’]:.4f}&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;Precision&lt;/td&gt;&lt;td&gt;{test_metrics[‘precision’]:.4f}&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;Recall&lt;/td&gt;&lt;td&gt;{test_metrics[‘recall’]:.4f}&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;F1&lt;/td&gt;&lt;td&gt;{test_metrics[‘f1’]:.4f}&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;ROC AUC&lt;/td&gt;&lt;td&gt;{test_metrics[‘roc_auc’]:.4f}&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;
&lt;h2&gt;Notes&lt;a href=&quot;#notes&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;Replace this section with your interpretation.&lt;/li&gt;
&lt;li&gt;Explain why the chosen metric matches the project goal.&lt;/li&gt;
&lt;li&gt;Compare the model against a naive baseline if possible.
&quot;&quot;&quot;
RESULT_PATH.parent.mkdir(parents=True, exist_ok=True)
RESULT_PATH.write_text(report, encoding=“utf-8”)&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;def write_error_analysis(matrix, errors):
preview = errors.head(10).round(4).to_markdown(index=False)
report = f&quot;&quot;&quot;# Error Analysis&lt;/p&gt;
&lt;h2&gt;Confusion Matrix&lt;a href=&quot;#confusion-matrix&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Rows are actual labels, columns are predicted labels.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;{matrix}&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;&amp;lt;/code&amp;gt;&amp;lt;/pre&amp;gt;&amp;lt;/div&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;&amp;lt;h2&amp;gt;Wrong Prediction Preview&amp;lt;/h2&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;&amp;lt;p&amp;gt;{preview}&amp;lt;/p&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;&amp;lt;h2&amp;gt;Observations To Fill In&amp;lt;/h2&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;&amp;lt;ul&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;&amp;lt;li&amp;gt;What type of mistake appears most often?&amp;lt;/li&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;&amp;lt;li&amp;gt;Are false positives or false negatives more costly?&amp;lt;/li&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;&amp;lt;li&amp;gt;Do wrong predictions have low confidence or high confidence?&amp;lt;/li&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;&amp;lt;li&amp;gt;Which features look unusual among wrong predictions?&amp;lt;/li&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;&amp;lt;li&amp;gt;What data cleaning or feature engineering should be tried next?&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;&amp;amp;quot;&amp;amp;quot;&amp;amp;quot;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  ERROR_PATH.parent.mkdir(parents=True, exist_ok=True)&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  ERROR_PATH.write_text(report, encoding=&amp;amp;quot;utf-8&amp;amp;quot;)&amp;lt;/li&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;&amp;lt;/ul&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;&amp;lt;p&amp;gt;def main():&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    X, y, target_names = load_project_data()&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    X_train, X_test, y_train, y_test = train_test_split(&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;        X,&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;        y,&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;        test_size=0.2,&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;        random_state=RANDOM_STATE,&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;        stratify=y,&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    )&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    models = build_models()&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    rows = score_models(models, X_train, y_train)&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    best_name = rows[0][&amp;amp;quot;model&amp;amp;quot;]&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    test_metrics, matrix, errors = evaluate_final_model(models[best_name], X_train, X_test, y_train, y_test)&amp;lt;/p&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;&amp;lt;div class=&quot;omp-code-frame&quot; data-lang=&quot;prompt&quot;&amp;gt;&amp;lt;div class=&quot;omp-code-bar&quot;&amp;gt;&amp;lt;span&amp;gt;omp&amp;lt;/span&amp;gt;&amp;lt;span&amp;gt;prompt&amp;lt;/span&amp;gt;&amp;lt;/div&amp;gt;&amp;lt;pre&amp;gt;&amp;lt;code&amp;gt;write_baseline_report(rows, test_metrics, target_names)&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;write_error_analysis(matrix, errors)&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;print(pd.DataFrame(rows).round(4).to_string(index=False))&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;print()&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;print(&amp;amp;quot;Selected model:&amp;amp;quot;, best_name)&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;print(&amp;amp;quot;Reports written:&amp;amp;quot;, RESULT_PATH, ERROR_PATH)&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;&amp;lt;/code&amp;gt;&amp;lt;/pre&amp;gt;&amp;lt;/div&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;&amp;lt;p&amp;gt;if &amp;lt;strong&amp;gt;name&amp;lt;/strong&amp;gt; == &amp;amp;quot;&amp;lt;strong&amp;gt;main&amp;lt;/strong&amp;gt;&amp;amp;quot;:&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    main()&amp;lt;/p&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;&amp;lt;div class=&quot;omp-code-frame&quot; data-lang=&quot;prompt&quot;&amp;gt;&amp;lt;div class=&quot;omp-code-bar&quot;&amp;gt;&amp;lt;span&amp;gt;omp&amp;lt;/span&amp;gt;&amp;lt;span&amp;gt;prompt&amp;lt;/span&amp;gt;&amp;lt;/div&amp;gt;&amp;lt;pre&amp;gt;&amp;lt;code&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;## 11. 运行 baseline&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;```fish&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;source .venv/bin/activate.fish&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;python src/train_baseline.py&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;&amp;lt;/code&amp;gt;&amp;lt;/pre&amp;gt;&amp;lt;/div&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;&amp;lt;p&amp;gt;应该生成：&amp;lt;/p&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;&amp;lt;div class=&quot;omp-code-frame&quot; data-lang=&quot;prompt&quot;&amp;gt;&amp;lt;div class=&quot;omp-code-bar&quot;&amp;gt;&amp;lt;span&amp;gt;omp&amp;lt;/span&amp;gt;&amp;lt;span&amp;gt;prompt&amp;lt;/span&amp;gt;&amp;lt;/div&amp;gt;&amp;lt;pre&amp;gt;&amp;lt;code class=&quot;language-text&quot;&amp;gt;reports/baseline_results.md&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;reports/error_analysis.md&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;&amp;lt;/code&amp;gt;&amp;lt;/pre&amp;gt;&amp;lt;/div&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;&amp;lt;p&amp;gt;检查：&amp;lt;/p&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;&amp;lt;div class=&quot;omp-code-frame&quot; data-lang=&quot;fish&quot;&amp;gt;&amp;lt;div class=&quot;omp-code-bar&quot;&amp;gt;&amp;lt;span&amp;gt;omp&amp;lt;/span&amp;gt;&amp;lt;span&amp;gt;fish&amp;lt;/span&amp;gt;&amp;lt;/div&amp;gt;&amp;lt;pre&amp;gt;&amp;lt;code class=&quot;language-fish&quot;&amp;gt;test -f reports/baseline_results.md; and echo &amp;amp;quot;baseline report exists&amp;amp;quot;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;test -f reports/error_analysis.md; and echo &amp;amp;quot;error analysis exists&amp;amp;quot;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;&amp;lt;/code&amp;gt;&amp;lt;/pre&amp;gt;&amp;lt;/div&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;&amp;lt;p&amp;gt;打开报告：&amp;lt;/p&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;&amp;lt;div class=&quot;omp-code-frame&quot; data-lang=&quot;fish&quot;&amp;gt;&amp;lt;div class=&quot;omp-code-bar&quot;&amp;gt;&amp;lt;span&amp;gt;omp&amp;lt;/span&amp;gt;&amp;lt;span&amp;gt;fish&amp;lt;/span&amp;gt;&amp;lt;/div&amp;gt;&amp;lt;pre&amp;gt;&amp;lt;code class=&quot;language-fish&quot;&amp;gt;code reports/baseline_results.md reports/error_analysis.md&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;&amp;lt;/code&amp;gt;&amp;lt;/pre&amp;gt;&amp;lt;/div&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;&amp;lt;h2&amp;gt;12. 把示例数据替换成真实项目数据&amp;lt;/h2&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;&amp;lt;p&amp;gt;假设真实 CSV 是：&amp;lt;/p&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;&amp;lt;div class=&quot;omp-code-frame&quot; data-lang=&quot;prompt&quot;&amp;gt;&amp;lt;div class=&quot;omp-code-bar&quot;&amp;gt;&amp;lt;span&amp;gt;omp&amp;lt;/span&amp;gt;&amp;lt;span&amp;gt;prompt&amp;lt;/span&amp;gt;&amp;lt;/div&amp;gt;&amp;lt;pre&amp;gt;&amp;lt;code class=&quot;language-text&quot;&amp;gt;data/raw/train.csv&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;&amp;lt;/code&amp;gt;&amp;lt;/pre&amp;gt;&amp;lt;/div&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;&amp;lt;p&amp;gt;目标列叫：&amp;lt;/p&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;&amp;lt;div class=&quot;omp-code-frame&quot; data-lang=&quot;prompt&quot;&amp;gt;&amp;lt;div class=&quot;omp-code-bar&quot;&amp;gt;&amp;lt;span&amp;gt;omp&amp;lt;/span&amp;gt;&amp;lt;span&amp;gt;prompt&amp;lt;/span&amp;gt;&amp;lt;/div&amp;gt;&amp;lt;pre&amp;gt;&amp;lt;code class=&quot;language-text&quot;&amp;gt;target&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;&amp;lt;/code&amp;gt;&amp;lt;/pre&amp;gt;&amp;lt;/div&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;&amp;lt;p&amp;gt;可以把 &amp;lt;code&amp;gt;load_project_data()&amp;lt;/code&amp;gt; 改成：&amp;lt;/p&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;&amp;lt;div class=&quot;omp-code-frame&quot; data-lang=&quot;python&quot;&amp;gt;&amp;lt;div class=&quot;omp-code-bar&quot;&amp;gt;&amp;lt;span&amp;gt;omp&amp;lt;/span&amp;gt;&amp;lt;span&amp;gt;python&amp;lt;/span&amp;gt;&amp;lt;/div&amp;gt;&amp;lt;pre&amp;gt;&amp;lt;code class=&quot;language-python&quot;&amp;gt;def load_project_data():&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    data = pd.read_csv(&amp;amp;quot;data/raw/train.csv&amp;amp;quot;)&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    X = data.drop(columns=[&amp;amp;quot;target&amp;amp;quot;])&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    y = data[&amp;amp;quot;target&amp;amp;quot;]&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    target_names = sorted(y.astype(str).unique().tolist())&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    return X, y, target_names&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;&amp;lt;/code&amp;gt;&amp;lt;/pre&amp;gt;&amp;lt;/div&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;&amp;lt;p&amp;gt;如果有类别变量，你需要在 Week 06 学过的 &amp;lt;code&amp;gt;ColumnTransformer&amp;lt;/code&amp;gt; 中处理。不要用“随便把字符串替换成 0、1、2”的方式编码城市、行业、学校等无序类别。&amp;lt;/p&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;&amp;lt;h2&amp;gt;13. 明确问题定义&amp;lt;/h2&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;&amp;lt;p&amp;gt;在 README 的 &amp;lt;code&amp;gt;Problem&amp;lt;/code&amp;gt; 部分回答：&amp;lt;/p&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;&amp;lt;div class=&quot;omp-code-frame&quot; data-lang=&quot;prompt&quot;&amp;gt;&amp;lt;div class=&quot;omp-code-bar&quot;&amp;gt;&amp;lt;span&amp;gt;omp&amp;lt;/span&amp;gt;&amp;lt;span&amp;gt;prompt&amp;lt;/span&amp;gt;&amp;lt;/div&amp;gt;&amp;lt;pre&amp;gt;&amp;lt;code class=&quot;language-text&quot;&amp;gt;我要预测什么？&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;为什么这个预测有意义？&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;每一行数据代表什么？&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;目标变量是如何定义的？&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;预测提前量是多少？&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;这个项目是分类还是回归？&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;&amp;lt;/code&amp;gt;&amp;lt;/pre&amp;gt;&amp;lt;/div&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;&amp;lt;p&amp;gt;示例：&amp;lt;/p&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;&amp;lt;div class=&quot;omp-code-frame&quot; data-lang=&quot;prompt&quot;&amp;gt;&amp;lt;div class=&quot;omp-code-bar&quot;&amp;gt;&amp;lt;span&amp;gt;omp&amp;lt;/span&amp;gt;&amp;lt;span&amp;gt;prompt&amp;lt;/span&amp;gt;&amp;lt;/div&amp;gt;&amp;lt;pre&amp;gt;&amp;lt;code class=&quot;language-text&quot;&amp;gt;This project predicts whether a passenger survived on the Titanic using passenger-level tabular features. Each row is one passenger. The target is Survived, where 1 means survived and 0 means not survived. This is a binary classification task.&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;&amp;lt;/code&amp;gt;&amp;lt;/pre&amp;gt;&amp;lt;/div&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;&amp;lt;p&amp;gt;统计学学生尤其要注意“单位”。一行是一个人、一家公司、一天、一笔交易，分析意义完全不同。&amp;lt;/p&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;&amp;lt;h2&amp;gt;14. 建立 naive baseline&amp;lt;/h2&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;&amp;lt;p&amp;gt;除了 sklearn 模型，还应该有 naive baseline。&amp;lt;/p&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;&amp;lt;p&amp;gt;分类任务：&amp;lt;/p&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;&amp;lt;div class=&quot;omp-code-frame&quot; data-lang=&quot;prompt&quot;&amp;gt;&amp;lt;div class=&quot;omp-code-bar&quot;&amp;gt;&amp;lt;span&amp;gt;omp&amp;lt;/span&amp;gt;&amp;lt;span&amp;gt;prompt&amp;lt;/span&amp;gt;&amp;lt;/div&amp;gt;&amp;lt;pre&amp;gt;&amp;lt;code class=&quot;language-text&quot;&amp;gt;永远预测训练集中最多的类别&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;&amp;lt;/code&amp;gt;&amp;lt;/pre&amp;gt;&amp;lt;/div&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;&amp;lt;p&amp;gt;回归任务：&amp;lt;/p&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;&amp;lt;div class=&quot;omp-code-frame&quot; data-lang=&quot;prompt&quot;&amp;gt;&amp;lt;div class=&quot;omp-code-bar&quot;&amp;gt;&amp;lt;span&amp;gt;omp&amp;lt;/span&amp;gt;&amp;lt;span&amp;gt;prompt&amp;lt;/span&amp;gt;&amp;lt;/div&amp;gt;&amp;lt;pre&amp;gt;&amp;lt;code class=&quot;language-text&quot;&amp;gt;永远预测训练集目标变量均值或中位数&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;&amp;lt;/code&amp;gt;&amp;lt;/pre&amp;gt;&amp;lt;/div&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;&amp;lt;p&amp;gt;你可以在报告中写：&amp;lt;/p&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;&amp;lt;div class=&quot;omp-code-frame&quot; data-lang=&quot;prompt&quot;&amp;gt;&amp;lt;div class=&quot;omp-code-bar&quot;&amp;gt;&amp;lt;span&amp;gt;omp&amp;lt;/span&amp;gt;&amp;lt;span&amp;gt;prompt&amp;lt;/span&amp;gt;&amp;lt;/div&amp;gt;&amp;lt;pre&amp;gt;&amp;lt;code class=&quot;language-text&quot;&amp;gt;Naive baseline: always predict the majority class.&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;F1 is low because the model never identifies minority-class cases.&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;&amp;lt;/code&amp;gt;&amp;lt;/pre&amp;gt;&amp;lt;/div&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;&amp;lt;p&amp;gt;这样可以证明 sklearn 模型不仅比随机好，也比最简单策略好。&amp;lt;/p&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;&amp;lt;h2&amp;gt;15. 初步错误分析怎么做&amp;lt;/h2&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;&amp;lt;p&amp;gt;不要只看总分。打开 &amp;lt;code&amp;gt;reports/error_analysis.md&amp;lt;/code&amp;gt;，观察错误样本：&amp;lt;/p&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;&amp;lt;div class=&quot;omp-code-frame&quot; data-lang=&quot;fish&quot;&amp;gt;&amp;lt;div class=&quot;omp-code-bar&quot;&amp;gt;&amp;lt;span&amp;gt;omp&amp;lt;/span&amp;gt;&amp;lt;span&amp;gt;fish&amp;lt;/span&amp;gt;&amp;lt;/div&amp;gt;&amp;lt;pre&amp;gt;&amp;lt;code class=&quot;language-fish&quot;&amp;gt;code reports/error_analysis.md&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;&amp;lt;/code&amp;gt;&amp;lt;/pre&amp;gt;&amp;lt;/div&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;&amp;lt;p&amp;gt;写下：&amp;lt;/p&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;&amp;lt;table&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;&amp;lt;thead&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;&amp;lt;tr&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;&amp;lt;th&amp;gt;问题&amp;lt;/th&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;&amp;lt;th&amp;gt;例子&amp;lt;/th&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;&amp;lt;/tr&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;&amp;lt;/thead&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;&amp;lt;tbody&amp;gt;&amp;lt;tr&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;&amp;lt;td&amp;gt;哪类错误更多&amp;lt;/td&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;&amp;lt;td&amp;gt;false positives 多，还是 false negatives 多&amp;lt;/td&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;&amp;lt;/tr&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;&amp;lt;tr&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;&amp;lt;td&amp;gt;错误样本有什么共同点&amp;lt;/td&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;&amp;lt;td&amp;gt;某些特征极端、缺失多、靠近决策边界&amp;lt;/td&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;&amp;lt;/tr&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;&amp;lt;tr&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;&amp;lt;td&amp;gt;指标是否符合目标&amp;lt;/td&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;&amp;lt;td&amp;gt;如果漏判代价高，recall 是否足够&amp;lt;/td&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;&amp;lt;/tr&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;&amp;lt;tr&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;&amp;lt;td&amp;gt;是否需要新特征&amp;lt;/td&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;&amp;lt;td&amp;gt;比如比例、分组统计、时间窗口&amp;lt;/td&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;&amp;lt;/tr&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;&amp;lt;tr&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;&amp;lt;td&amp;gt;是否可能有数据问题&amp;lt;/td&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;&amp;lt;td&amp;gt;标签噪声、重复样本、泄漏列&amp;lt;/td&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;&amp;lt;/tr&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;&amp;lt;/tbody&amp;gt;&amp;lt;/table&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;&amp;lt;p&amp;gt;错误分析的价值在于指导 Week 08 的改进，不是为了装饰报告。&amp;lt;/p&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;&amp;lt;h2&amp;gt;16. README 本周至少要写到什么程度&amp;lt;/h2&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;&amp;lt;p&amp;gt;Week 07 的 README 不要求完美，但至少包含：&amp;lt;/p&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;&amp;lt;div class=&quot;omp-code-frame&quot; data-lang=&quot;prompt&quot;&amp;gt;&amp;lt;div class=&quot;omp-code-bar&quot;&amp;gt;&amp;lt;span&amp;gt;omp&amp;lt;/span&amp;gt;&amp;lt;span&amp;gt;prompt&amp;lt;/span&amp;gt;&amp;lt;/div&amp;gt;&amp;lt;pre&amp;gt;&amp;lt;code class=&quot;language-text&quot;&amp;gt;项目标题&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;问题定义&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;数据来源&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;目标变量&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;运行命令&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;当前 baseline 结果链接&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;下一步计划&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;&amp;lt;/code&amp;gt;&amp;lt;/pre&amp;gt;&amp;lt;/div&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;&amp;lt;p&amp;gt;运行命令可以写成：&amp;lt;/p&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;&amp;lt;div class=&quot;omp-code-frame&quot; data-lang=&quot;fish&quot;&amp;gt;&amp;lt;div class=&quot;omp-code-bar&quot;&amp;gt;&amp;lt;span&amp;gt;omp&amp;lt;/span&amp;gt;&amp;lt;span&amp;gt;fish&amp;lt;/span&amp;gt;&amp;lt;/div&amp;gt;&amp;lt;pre&amp;gt;&amp;lt;code class=&quot;language-fish&quot;&amp;gt;uv venv&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;source .venv/bin/activate.fish&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;uv sync&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;python src/train_baseline.py&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;&amp;lt;/code&amp;gt;&amp;lt;/pre&amp;gt;&amp;lt;/div&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;&amp;lt;p&amp;gt;如果你用了手动下载数据，README 必须说明数据应该放在哪里：&amp;lt;/p&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;&amp;lt;div class=&quot;omp-code-frame&quot; data-lang=&quot;prompt&quot;&amp;gt;&amp;lt;div class=&quot;omp-code-bar&quot;&amp;gt;&amp;lt;span&amp;gt;omp&amp;lt;/span&amp;gt;&amp;lt;span&amp;gt;prompt&amp;lt;/span&amp;gt;&amp;lt;/div&amp;gt;&amp;lt;pre&amp;gt;&amp;lt;code class=&quot;language-text&quot;&amp;gt;Download the dataset and place the CSV at data/raw/train.csv.&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;&amp;lt;/code&amp;gt;&amp;lt;/pre&amp;gt;&amp;lt;/div&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;&amp;lt;p&amp;gt;不要让读者猜文件名。&amp;lt;/p&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;&amp;lt;h2&amp;gt;17. Git 提交&amp;lt;/h2&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;&amp;lt;p&amp;gt;先创建 &amp;lt;code&amp;gt;.gitignore&amp;lt;/code&amp;gt;，内容至少包括：&amp;lt;/p&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;&amp;lt;div class=&quot;omp-code-frame&quot; data-lang=&quot;prompt&quot;&amp;gt;&amp;lt;div class=&quot;omp-code-bar&quot;&amp;gt;&amp;lt;span&amp;gt;omp&amp;lt;/span&amp;gt;&amp;lt;span&amp;gt;prompt&amp;lt;/span&amp;gt;&amp;lt;/div&amp;gt;&amp;lt;pre&amp;gt;&amp;lt;code class=&quot;language-text&quot;&amp;gt;.venv/&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;__pycache__/&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;.ipynb_checkpoints/&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;.DS_Store&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;data/raw/*.zip&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;&amp;lt;/code&amp;gt;&amp;lt;/pre&amp;gt;&amp;lt;/div&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;&amp;lt;p&amp;gt;如果原始 CSV 很大，或数据许可不允许发布，也把它加入 &amp;lt;code&amp;gt;.gitignore&amp;lt;/code&amp;gt;。然后提交：&amp;lt;/p&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;&amp;lt;div class=&quot;omp-code-frame&quot; data-lang=&quot;fish&quot;&amp;gt;&amp;lt;div class=&quot;omp-code-bar&quot;&amp;gt;&amp;lt;span&amp;gt;omp&amp;lt;/span&amp;gt;&amp;lt;span&amp;gt;fish&amp;lt;/span&amp;gt;&amp;lt;/div&amp;gt;&amp;lt;pre&amp;gt;&amp;lt;code class=&quot;language-fish&quot;&amp;gt;git status --short&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;git add README.md docs reports src pyproject.toml uv.lock .gitignore&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;git commit -m &amp;amp;quot;Start statistical machine learning baseline project&amp;amp;quot;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;&amp;lt;/code&amp;gt;&amp;lt;/pre&amp;gt;&amp;lt;/div&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;&amp;lt;p&amp;gt;如果这是你第一次在该目录使用 Git：&amp;lt;/p&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;&amp;lt;div class=&quot;omp-code-frame&quot; data-lang=&quot;fish&quot;&amp;gt;&amp;lt;div class=&quot;omp-code-bar&quot;&amp;gt;&amp;lt;span&amp;gt;omp&amp;lt;/span&amp;gt;&amp;lt;span&amp;gt;fish&amp;lt;/span&amp;gt;&amp;lt;/div&amp;gt;&amp;lt;pre&amp;gt;&amp;lt;code class=&quot;language-fish&quot;&amp;gt;git init&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;&amp;lt;/code&amp;gt;&amp;lt;/pre&amp;gt;&amp;lt;/div&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;&amp;lt;p&amp;gt;再执行上面的 &amp;lt;code&amp;gt;git add&amp;lt;/code&amp;gt; 与 &amp;lt;code&amp;gt;git commit&amp;lt;/code&amp;gt;。&amp;lt;/p&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;&amp;lt;h2&amp;gt;18. 本周练习&amp;lt;/h2&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;&amp;lt;ol&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;&amp;lt;li&amp;gt;在 &amp;lt;code&amp;gt;docs/data_card.md&amp;lt;/code&amp;gt; 中写完至少 10 个字段的说明。&amp;lt;/li&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;&amp;lt;li&amp;gt;在 README 中用 5 句话讲清楚问题定义。&amp;lt;/li&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;&amp;lt;li&amp;gt;加入 naive baseline，并在报告中比较 naive baseline 与 sklearn baseline。&amp;lt;/li&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;&amp;lt;li&amp;gt;把错误样本保存为 &amp;lt;code&amp;gt;reports/wrong_predictions.csv&amp;lt;/code&amp;gt;，用 pandas 查看前 20 行。&amp;lt;/li&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;&amp;lt;li&amp;gt;尝试一个新特征，并说明它为什么可能有用。&amp;lt;/li&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;&amp;lt;li&amp;gt;用 Git 做两次提交：一次提交项目骨架，一次提交 baseline 结果。&amp;lt;/li&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;&amp;lt;/ol&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;&amp;lt;h2&amp;gt;19. 验收检查&amp;lt;/h2&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;&amp;lt;p&amp;gt;在项目根目录执行：&amp;lt;/p&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;&amp;lt;div class=&quot;omp-code-frame&quot; data-lang=&quot;fish&quot;&amp;gt;&amp;lt;div class=&quot;omp-code-bar&quot;&amp;gt;&amp;lt;span&amp;gt;omp&amp;lt;/span&amp;gt;&amp;lt;span&amp;gt;fish&amp;lt;/span&amp;gt;&amp;lt;/div&amp;gt;&amp;lt;pre&amp;gt;&amp;lt;code class=&quot;language-fish&quot;&amp;gt;source .venv/bin/activate.fish&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;python src/train_baseline.py&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;test -f docs/data_card.md; and echo &amp;amp;quot;data card exists&amp;amp;quot;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;test -f reports/baseline_results.md; and echo &amp;amp;quot;baseline report exists&amp;amp;quot;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;test -f reports/error_analysis.md; and echo &amp;amp;quot;error analysis exists&amp;amp;quot;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;&amp;lt;/code&amp;gt;&amp;lt;/pre&amp;gt;&amp;lt;/div&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;&amp;lt;p&amp;gt;人工确认：&amp;lt;/p&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;&amp;lt;ul&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;&amp;lt;li&amp;gt;项目目录结构完整。&amp;lt;/li&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;&amp;lt;li&amp;gt;README 能说明问题、数据、运行方式。&amp;lt;/li&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;&amp;lt;li&amp;gt;&amp;lt;code&amp;gt;docs/data_card.md&amp;lt;/code&amp;gt; 写明数据来源、字段、目标、限制。&amp;lt;/li&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;&amp;lt;li&amp;gt;baseline 脚本能从头运行。&amp;lt;/li&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;&amp;lt;li&amp;gt;报告有模型、指标、验证方式、结果表。&amp;lt;/li&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;&amp;lt;li&amp;gt;错误分析不是空模板，至少有 5 条观察。&amp;lt;/li&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;&amp;lt;li&amp;gt;Git 提交不包含 &amp;lt;code&amp;gt;.venv/&amp;lt;/code&amp;gt;。&amp;lt;/li&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;&amp;lt;/ul&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;&amp;lt;h2&amp;gt;20. 常见错误&amp;lt;/h2&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;&amp;lt;h3&amp;gt;错误 1：项目没有明确目标&amp;lt;/h3&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;&amp;lt;p&amp;gt;“分析 Titanic 数据”不是明确目标。“预测乘客是否生还，并分析哪些群体错误率高”才是明确目标。&amp;lt;/p&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;&amp;lt;h3&amp;gt;错误 2：README 最后才写&amp;lt;/h3&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;&amp;lt;p&amp;gt;README 是项目导航，不是交作业时补的封面。Week 07 就写初稿，Week 08 再打磨。&amp;lt;/p&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;&amp;lt;h3&amp;gt;错误 3：notebook 里能跑，但脚本不能跑&amp;lt;/h3&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;&amp;lt;p&amp;gt;Notebook 适合探索，最终训练入口应该是 &amp;lt;code&amp;gt;src/train_baseline.py&amp;lt;/code&amp;gt;。否则别人无法稳定复现。&amp;lt;/p&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;&amp;lt;h3&amp;gt;错误 4：数据来源不清楚&amp;lt;/h3&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;&amp;lt;p&amp;gt;报告里必须能回答：数据从哪里来、下载日期、许可或引用方式、是否可以公开。&amp;lt;/p&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;&amp;lt;h3&amp;gt;错误 5：只写模型分数，不写错误分析&amp;lt;/h3&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;&amp;lt;p&amp;gt;模型项目的价值不只在分数，还在你能否解释错误、提出下一步改进。&amp;lt;/p&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;&amp;lt;h2&amp;gt;21. 下一步&amp;lt;/h2&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;&amp;lt;p&amp;gt;进入 Week 08：&amp;lt;a href=&quot;/post/ustc-stat-ai-quant-plan/&quot;&amp;gt;统计建模项目收尾&amp;lt;/a&amp;gt;。你会把 Week 07 的项目整理成可以给老师、导师或实习面试官看的版本：README 打磨、报告成稿、复现命令、提交历史清理与成果展示。&amp;lt;/p&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    &amp;lt;/article&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  &amp;lt;/div&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;&amp;lt;/section&amp;gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/article&gt;&lt;/div&gt;&lt;/section&gt;</content:encoded><category>category:工具</category><category>category:机器学习</category><category>tag:sklearn</category><category>tag:ML项目</category><category>tag:baseline</category><category>tag:data-card</category></item><item><title>Week 06：交叉验证、模型选择与防止数据泄漏</title><link>https://39miku.space/post/week06-cross-validation-model-selection</link><guid isPermaLink="false">week06-cross-validation-model-selection</guid><description>Week 06：交叉验证、模型选择与防止数据泄漏。fish-first 终端教学，面向 CachyOS、VS Code、uv 和 Python 学习路线。</description><pubDate>Sat, 25 Jul 2026 22:00:00 GMT</pubDate><content:encoded>
&lt;section&gt;
  &lt;div&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;strong&gt;Oh My Pi&lt;/strong&gt; / weekly tutorial / week06-cross-validation-model-selection&lt;/span&gt;&lt;/div&gt;
  &lt;div&gt;
    &lt;div&gt;&lt;span&gt;miku@cachyos&lt;/span&gt;&lt;span&gt;:~/Code/python-learning&lt;/span&gt;&lt;span&gt;$&lt;/span&gt; &lt;span&gt;omp teach week06-cross-validation-model-selection --fish-first --step-by-step&lt;/span&gt;&lt;/div&gt;
    &lt;div&gt;
      &lt;div&gt;&lt;span&gt;source&lt;/span&gt;&lt;strong&gt;659 行教学文档&lt;/strong&gt;&lt;/div&gt;
      &lt;div&gt;&lt;span&gt;week&lt;/span&gt;&lt;strong&gt;Week 06&lt;/strong&gt;&lt;/div&gt;
      &lt;div&gt;&lt;span&gt;shell&lt;/span&gt;&lt;strong&gt;fish-first 命令版&lt;/strong&gt;&lt;/div&gt;
      &lt;div&gt;&lt;span&gt;backlink&lt;/span&gt;&lt;strong&gt;&lt;a href=&quot;/post/ustc-stat-ai-quant-plan/&quot;&gt;20 周计划&lt;/a&gt;&lt;/strong&gt;&lt;/div&gt;
    &lt;/div&gt;
    &lt;article&gt;
&lt;h1&gt;Week 06：交叉验证、模型选择与防止数据泄漏&lt;/h1&gt;
&lt;blockquote&gt;
&lt;p&gt;返回主线计划：&lt;a href=&quot;/post/ustc-stat-ai-quant-plan/&quot;&gt;USTC 统计学：AI / 量化金融 20 周终端式成长计划&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Week 05 你已经会写一个固定 train / validation / test 的 baseline。Week 06 要解决更真实的问题：一次随机划分可能让模型排序碰运气，特征处理如果放错位置会造成数据泄漏，调参如果没有记录就无法复现。&lt;/p&gt;
&lt;p&gt;本周目标是把“跑一次分数”升级为“可解释、可复现、可比较的模型选择流程”。命令默认 fish shell。&lt;/p&gt;
&lt;p&gt;&lt;a&gt;&lt;/a&gt;&lt;/p&gt;
&lt;h2&gt;0. 本周详细教学：语法、规范、验收&lt;/h2&gt;
&lt;blockquote&gt;
&lt;p&gt;本节不是追加在尾部的复习，而是本周正文的入口。先读这里，再做后面的命令和项目。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h3&gt;0.1 本周真正要学会什么&lt;/h3&gt;
&lt;table&gt;
&lt;thead&gt;&lt;tr&gt;&lt;th&gt;维度&lt;/th&gt;&lt;th&gt;要求&lt;/th&gt;&lt;/tr&gt;&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;&lt;td&gt;知识点&lt;/td&gt;&lt;td&gt;Pipeline、ColumnTransformer、KFold、GridSearch、防泄漏&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;代码语法&lt;/td&gt;&lt;td&gt;能从空文件写出本周核心脚本，而不是只复制运行&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;程序规范&lt;/td&gt;&lt;td&gt;函数拆分、路径清楚、输入输出明确、错误能解释&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;交付物&lt;/td&gt;&lt;td&gt;&lt;code&gt;src/model_selection.py&lt;/code&gt;&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;验收方式&lt;/td&gt;&lt;td&gt;从 fish 终端运行命令，得到可复查的文件或指标&lt;/td&gt;&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;h3&gt;0.2 代码语法精讲&lt;/h3&gt;
&lt;p&gt;下面的代码不是最终答案，而是本周必须理解的最小骨架：&lt;/p&gt;
&lt;div&gt;&lt;div&gt;&lt;span&gt;omp&lt;/span&gt;&lt;span&gt;python&lt;/span&gt;&lt;/div&gt;&lt;pre&gt;&lt;code&gt;from sklearn.pipeline import Pipeline
from sklearn.preprocessing import StandardScaler
from sklearn.linear_model import LogisticRegression
from sklearn.model_selection import cross_validate
&lt;/code&gt;&lt;p&gt;&lt;code&gt;pipe = Pipeline([
(“scaler”, StandardScaler()),
(“model”, LogisticRegression(max_iter=1000)),
])
result = cross_validate(pipe, X, y, cv=5, scoring=[“accuracy”, “f1_macro”])
print(result[“test_f1_macro”].mean())
&lt;/code&gt;&lt;/p&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;&lt;/p&gt;
&lt;p&gt;读代码时按四步检查：输入从哪里来；中间变量的类型和 shape 是什么；函数或脚本输出什么；哪些错误应该显式报出来。&lt;/p&gt;
&lt;h3&gt;0.3 本周程序规范&lt;/h3&gt;
&lt;ul&gt;&lt;li&gt;所有路径用相对路径或 `pathlib.Path`，不要写死 `/home/miku/...`。&lt;/li&gt;&lt;li&gt;核心逻辑进 `src/`，notebook 只做探索和解释。&lt;/li&gt;&lt;li&gt;每个脚本能从 fish 终端运行，并在 README 写出命令。&lt;/li&gt;&lt;li&gt;输出必须落盘到 `reports/`、`figures/` 或 `outputs/`，不能只在屏幕上看。&lt;/li&gt;&lt;/ul&gt;
&lt;h3&gt;0.4 本周练习分层&lt;/h3&gt;
&lt;table&gt;
&lt;thead&gt;&lt;tr&gt;&lt;th&gt;层级&lt;/th&gt;&lt;th&gt;任务&lt;/th&gt;&lt;th&gt;不合格表现&lt;/th&gt;&lt;th&gt;合格验收&lt;/th&gt;&lt;/tr&gt;&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;&lt;td&gt;最小练习&lt;/td&gt;&lt;td&gt;手写上面的最小骨架&lt;/td&gt;&lt;td&gt;只在 notebook 里运行&lt;/td&gt;&lt;td&gt;终端运行成功&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;标准练习&lt;/td&gt;&lt;td&gt;把逻辑拆成函数/模块&lt;/td&gt;&lt;td&gt;一个大脚本从头写到尾&lt;/td&gt;&lt;td&gt;至少 2 个函数，职责清楚&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;项目练习&lt;/td&gt;&lt;td&gt;生成本周交付物 &lt;code&gt;src/model_selection.py&lt;/code&gt;&lt;/td&gt;&lt;td&gt;只有屏幕输出&lt;/td&gt;&lt;td&gt;文件落盘，可复查&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;复盘练习&lt;/td&gt;&lt;td&gt;写 3 个错误和修复&lt;/td&gt;&lt;td&gt;只写“已解决”&lt;/td&gt;&lt;td&gt;写清报错、原因、修复、预防&lt;/td&gt;&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;h3&gt;0.5 本周和主线的连接&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;回到总计划：&lt;a href=&quot;/post/ustc-stat-ai-quant-plan/#week-plan&quot;&gt;USTC AI / Quant 练习手册&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;查详细练习索引：&lt;a href=&quot;/post/ustc-stat-ai-quant-plan/#deep-practice-appendix&quot;&gt;技术练习详解&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;查质量评分：&lt;a href=&quot;/post/ustc-stat-ai-quant-plan/#quality-final-gates&quot;&gt;最终质量门槛&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;1. 本周目标&lt;/h2&gt;
&lt;p&gt;完成 &lt;code&gt;src/model_selection.py&lt;/code&gt; 和 &lt;code&gt;reports/week06_cv_report.md&lt;/code&gt;：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;使用 KFold 或 StratifiedKFold 做交叉验证。&lt;/li&gt;
&lt;li&gt;用 Pipeline 把标准化、编码、模型训练放在同一个流程里。&lt;/li&gt;
&lt;li&gt;用 GridSearchCV 比较参数组合。&lt;/li&gt;
&lt;li&gt;输出 classification 或 regression 指标表。&lt;/li&gt;
&lt;li&gt;记录最佳模型、最佳参数、均值、标准差。&lt;/li&gt;
&lt;li&gt;解释什么是数据泄漏，以及如何避免。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;本周最重要的观念：&lt;/p&gt;
&lt;div&gt;&lt;div&gt;&lt;span&gt;omp&lt;/span&gt;&lt;span&gt;prompt&lt;/span&gt;&lt;/div&gt;&lt;pre&gt;&lt;code&gt;预处理必须只从训练折学习，再应用到验证折。
如果你先对全数据 fit scaler，再交叉验证，验证分数就被污染了。
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;h2&gt;2. 前置条件&lt;/h2&gt;
&lt;p&gt;你应该已经具备：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Week 05 的 &lt;code&gt;src/train_baselines.py&lt;/code&gt; 能正常运行。&lt;/li&gt;
&lt;li&gt;理解 train / validation / test 的角色。&lt;/li&gt;
&lt;li&gt;会看 accuracy、precision、recall、F1、ROC AUC。&lt;/li&gt;
&lt;li&gt;能用 VS Code 修改 Python 文件。&lt;/li&gt;
&lt;li&gt;能用 Git 提交实验结果。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;检查项目环境：&lt;/p&gt;
&lt;div&gt;&lt;div&gt;&lt;span&gt;omp&lt;/span&gt;&lt;span&gt;fish&lt;/span&gt;&lt;/div&gt;&lt;pre&gt;&lt;code&gt;cd ~/Code/python-learning/week05-sklearn-baselines
source .venv/bin/activate.fish
python src/train_baselines.py
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;如果你想把 Week 06 单独放在新目录，也可以按下一节创建新项目。&lt;/p&gt;
&lt;h2&gt;3. 建立 Week 06 项目&lt;/h2&gt;
&lt;div&gt;&lt;div&gt;&lt;span&gt;omp&lt;/span&gt;&lt;span&gt;fish&lt;/span&gt;&lt;/div&gt;&lt;pre&gt;&lt;code&gt;cd ~/Code/python-learning
mkdir -p week06-cross-validation-model-selection
cd week06-cross-validation-model-selection
uv init
uv venv
source .venv/bin/activate.fish
uv add pandas numpy scikit-learn tabulate
mkdir -p src reports data/raw data/processed
code .
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;逐句解释：&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;命令&lt;/th&gt;
&lt;th&gt;作用&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;&lt;tr&gt;
&lt;td&gt;&lt;code&gt;mkdir -p week06-cross-validation-model-selection&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;建立本周项目目录&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;uv init&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;初始化 Python 项目配置&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;uv venv&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;创建隔离环境&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;source .venv/bin/activate.fish&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;激活 fish 专用虚拟环境&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;uv add ...&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;安装 sklearn 建模需要的包&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;mkdir -p src reports ...&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;创建代码、报告、数据目录&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;code .&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;用 VS Code 打开当前项目&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;&lt;/table&gt;
&lt;h2&gt;4. 文件布局&lt;/h2&gt;
&lt;p&gt;建议结构：&lt;/p&gt;
&lt;div&gt;&lt;div&gt;&lt;span&gt;omp&lt;/span&gt;&lt;span&gt;prompt&lt;/span&gt;&lt;/div&gt;&lt;pre&gt;&lt;code&gt;week06-cross-validation-model-selection/
├── data/
│   ├── raw/
│   └── processed/
├── reports/
│   └── week06_cv_report.md
├── src/
│   └── model_selection.py
├── pyproject.toml
├── uv.lock
└── README.md
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;本周的主要产出是 &lt;code&gt;model_selection.py&lt;/code&gt;。它不应该只是把 Week 05 的脚本复制一遍，而要明确体现：&lt;/p&gt;
&lt;div&gt;&lt;div&gt;&lt;span&gt;omp&lt;/span&gt;&lt;span&gt;prompt&lt;/span&gt;&lt;/div&gt;&lt;pre&gt;&lt;code&gt;cross validation + pipeline + grid search + result table
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;h2&gt;5. 为什么一次 validation 不够&lt;/h2&gt;
&lt;p&gt;Week 05 的流程是：&lt;/p&gt;
&lt;div&gt;&lt;div&gt;&lt;span&gt;omp&lt;/span&gt;&lt;span&gt;prompt&lt;/span&gt;&lt;/div&gt;&lt;pre&gt;&lt;code&gt;train -&amp;gt; valid -&amp;gt; choose model -&amp;gt; test
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;问题在于 validation 只有一份。如果某次随机划分刚好让某个模型“运气好”，你会误以为它更强。&lt;/p&gt;
&lt;p&gt;K-fold cross validation 的想法：&lt;/p&gt;
&lt;div&gt;&lt;div&gt;&lt;span&gt;omp&lt;/span&gt;&lt;span&gt;prompt&lt;/span&gt;&lt;/div&gt;&lt;pre&gt;&lt;code&gt;把训练数据切成 K 份
第 1 次：第 1 份做验证，其余做训练
第 2 次：第 2 份做验证，其余做训练
...
第 K 次：第 K 份做验证，其余做训练
最后取 K 次分数的均值和标准差
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;你需要同时看：&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;指标&lt;/th&gt;
&lt;th&gt;含义&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;&lt;tr&gt;
&lt;td&gt;mean score&lt;/td&gt;
&lt;td&gt;平均表现&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;std score&lt;/td&gt;
&lt;td&gt;稳定性，越小越稳定&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;best params&lt;/td&gt;
&lt;td&gt;最优参数组合&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;test score&lt;/td&gt;
&lt;td&gt;最终保留测试集上的一次评估&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;&lt;/table&gt;
&lt;h2&gt;6. KFold 与 StratifiedKFold&lt;/h2&gt;
&lt;h3&gt;6.1 KFold&lt;/h3&gt;
&lt;p&gt;适合回归任务，或分类任务中类别非常均衡的情况。&lt;/p&gt;
&lt;div&gt;&lt;div&gt;&lt;span&gt;omp&lt;/span&gt;&lt;span&gt;python&lt;/span&gt;&lt;/div&gt;&lt;pre&gt;&lt;code&gt;from sklearn.model_selection import KFold
&lt;/code&gt;&lt;p&gt;&lt;code&gt;cv = KFold(n_splits=5, shuffle=True, random_state=42)
&lt;/code&gt;&lt;/p&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;&lt;/p&gt;
&lt;h3&gt;6.2 StratifiedKFold&lt;/h3&gt;
&lt;p&gt;适合分类任务，尤其是类别不平衡时。它会尽量保持每一折的类别比例一致。&lt;/p&gt;
&lt;div&gt;&lt;div&gt;&lt;span&gt;omp&lt;/span&gt;&lt;span&gt;python&lt;/span&gt;&lt;/div&gt;&lt;pre&gt;&lt;code&gt;from sklearn.model_selection import StratifiedKFold
&lt;/code&gt;&lt;p&gt;&lt;code&gt;cv = StratifiedKFold(n_splits=5, shuffle=True, random_state=42)
&lt;/code&gt;&lt;/p&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;&lt;/p&gt;
&lt;p&gt;本周示例是分类任务，所以使用 &lt;code&gt;StratifiedKFold&lt;/code&gt;。&lt;/p&gt;
&lt;h2&gt;7. 数据泄漏是什么&lt;/h2&gt;
&lt;p&gt;数据泄漏不是语法错误，而是实验设计错误。它会让验证分数看起来很好，但真实泛化很差。&lt;/p&gt;
&lt;p&gt;常见泄漏：&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;错误做法&lt;/th&gt;
&lt;th&gt;为什么错&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;&lt;tr&gt;
&lt;td&gt;对全数据 &lt;code&gt;fit_transform&lt;/code&gt; 标准化，再划分训练验证&lt;/td&gt;
&lt;td&gt;验证集均值和方差信息提前进入训练过程&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;用全数据填补缺失值，再交叉验证&lt;/td&gt;
&lt;td&gt;验证折的分布信息泄漏到训练折&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;特征选择时先看全数据与目标的相关性&lt;/td&gt;
&lt;td&gt;目标信息通过特征选择进入验证折&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;调参时反复查看 test 分数&lt;/td&gt;
&lt;td&gt;test 被污染，不能再代表最终泛化&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;&lt;/table&gt;
&lt;p&gt;正确做法：把预处理放进 &lt;code&gt;Pipeline&lt;/code&gt;。交叉验证时，sklearn 会在每个训练折上 &lt;code&gt;fit&lt;/code&gt; 预处理器，再对对应验证折 &lt;code&gt;transform&lt;/code&gt;。&lt;/p&gt;
&lt;h2&gt;8. 编写模型选择脚本&lt;/h2&gt;
&lt;p&gt;在 VS Code 创建：&lt;/p&gt;
&lt;div&gt;&lt;div&gt;&lt;span&gt;omp&lt;/span&gt;&lt;span&gt;prompt&lt;/span&gt;&lt;/div&gt;&lt;pre&gt;&lt;code&gt;src/model_selection.py
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;粘贴下面代码：&lt;/p&gt;
&lt;div&gt;&lt;div&gt;&lt;span&gt;omp&lt;/span&gt;&lt;span&gt;python&lt;/span&gt;&lt;/div&gt;&lt;pre&gt;&lt;code&gt;from pathlib import Path
&lt;p&gt;import pandas as pd
from sklearn.datasets import load_breast_cancer
from sklearn.ensemble import RandomForestClassifier
from sklearn.linear_model import LogisticRegression
from sklearn.metrics import accuracy_score, f1_score, precision_score, recall_score, roc_auc_score
from sklearn.model_selection import GridSearchCV, StratifiedKFold, train_test_split
from sklearn.neighbors import KNeighborsClassifier
from sklearn.pipeline import Pipeline
from sklearn.preprocessing import StandardScaler
from sklearn.tree import DecisionTreeClassifier&lt;/p&gt;
&lt;p&gt;RANDOM_STATE = 42
REPORT_PATH = Path(“reports/week06_cv_report.md”)&lt;/p&gt;
&lt;p&gt;def load_data():
dataset = load_breast_cancer(as_frame=True)
return dataset.data, dataset.target, list(dataset.target_names)&lt;/p&gt;
&lt;p&gt;def train_test_holdout(X, y):
return train_test_split(
X,
y,
test_size=0.2,
random_state=RANDOM_STATE,
stratify=y,
)&lt;/p&gt;
&lt;p&gt;def build_search_spaces():
return {
“logistic_regression”: {
“pipeline”: Pipeline(
steps=[
(“scaler”, StandardScaler()),
(“model”, LogisticRegression(max_iter=3000, random_state=RANDOM_STATE)),
]
),
“params”: {
“model__C”: [0.1, 1.0, 10.0],
“model__class_weight”: [None, “balanced”],
},
},
“knn”: {
“pipeline”: Pipeline(
steps=[
(“scaler”, StandardScaler()),
(“model”, KNeighborsClassifier()),
]
),
“params”: {
“model__n_neighbors”: [3, 5, 9, 15],
“model__weights”: [“uniform”, “distance”],
},
},
“decision_tree”: {
“pipeline”: Pipeline(
steps=[
(“model”, DecisionTreeClassifier(random_state=RANDOM_STATE)),
]
),
“params”: {
“model__max_depth”: [2, 3, 4, 5, None],
“model__min_samples_leaf”: [1, 3, 5],
},
},
“random_forest”: {
“pipeline”: Pipeline(
steps=[
(“model”, RandomForestClassifier(random_state=RANDOM_STATE)),
]
),
“params”: {
“model__n_estimators”: [100, 200],
“model__max_depth”: [3, 5, None],
“model__min_samples_leaf”: [1, 3],
},
},
}&lt;/p&gt;
&lt;p&gt;def evaluate_classifier(model, X_test, y_test):
predictions = model.predict(X_test)
probabilities = model.predict_proba(X_test)[:, 1]
return {
“test_accuracy”: accuracy_score(y_test, predictions),
“test_precision”: precision_score(y_test, predictions),
“test_recall”: recall_score(y_test, predictions),
“test_f1”: f1_score(y_test, predictions),
“test_roc_auc”: roc_auc_score(y_test, probabilities),
}&lt;/p&gt;
&lt;p&gt;def run_model_selection(X_train, y_train):
cv = StratifiedKFold(n_splits=5, shuffle=True, random_state=RANDOM_STATE)
searches = []&lt;/p&gt;
&lt;p&gt;plain for model_name, config in build_search_spaces().items():
search = GridSearchCV(
estimator=config[“pipeline”],
param_grid=config[“params”],
scoring=“f1”,
cv=cv,
n_jobs=-1,
refit=True,
return_train_score=True,
)
search.fit(X_train, y_train)
searches.append(
{
“model”: model_name,
“best_score”: search.best_score_,
“best_params”: search.best_params_,
“best_estimator”: search.best_estimator_,
}
)&lt;/p&gt;
&lt;p&gt;plain return sorted(searches, key=lambda row: row[“best_score”], reverse=True)&lt;/p&gt;
&lt;p&gt;def make_report(results, test_metrics, target_names):
table_rows = []
for row in results:
table_rows.append(
f”| {row[‘model’]} | {row[‘best_score’]:.4f} | &lt;code&gt;{row[&amp;amp;#39;best_params&amp;amp;#39;]}&lt;/code&gt; |”
)&lt;/p&gt;
&lt;p&gt;plain best = results[0]
report = f&quot;&quot;&quot;# Week 06 Cross Validation Report&lt;/p&gt;
&lt;h2&gt;Dataset&lt;a href=&quot;#dataset&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;Source: sklearn breast cancer dataset&lt;/li&gt;
&lt;li&gt;Task: binary classification&lt;/li&gt;
&lt;li&gt;Target names:&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;Validation Design&lt;a href=&quot;#validation-design&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;Holdout test size: 20%&lt;/li&gt;
&lt;li&gt;Cross validation: StratifiedKFold with 5 folds&lt;/li&gt;
&lt;li&gt;Model selection metric: F1&lt;/li&gt;
&lt;li&gt;Leakage prevention: preprocessing is inside sklearn Pipeline&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;Model Selection Results&lt;a href=&quot;#model-selection-results&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;















&lt;table&gt;&lt;thead&gt;&lt;tr&gt;&lt;th&gt;Model&lt;/th&gt;&lt;th&gt;Mean CV F1&lt;/th&gt;&lt;th&gt;Best Params&lt;/th&gt;&lt;/tr&gt;&lt;/thead&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td&gt;{chr(10).join(table_rows)}&lt;/td&gt;&lt;td&gt;&lt;/td&gt;&lt;td&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;
&lt;h2&gt;Best Model&lt;a href=&quot;#best-model&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;Selected model: &lt;code&gt;&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;Selected by: highest mean cross-validation F1 on the training portion&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;Final Holdout Test Metrics&lt;a href=&quot;#final-holdout-test-metrics&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;





























&lt;table&gt;&lt;thead&gt;&lt;tr&gt;&lt;th&gt;Metric&lt;/th&gt;&lt;th&gt;Value&lt;/th&gt;&lt;/tr&gt;&lt;/thead&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td&gt;Accuracy&lt;/td&gt;&lt;td&gt;{test_metrics[‘test_accuracy’]:.4f}&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;Precision&lt;/td&gt;&lt;td&gt;{test_metrics[‘test_precision’]:.4f}&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;Recall&lt;/td&gt;&lt;td&gt;{test_metrics[‘test_recall’]:.4f}&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;F1&lt;/td&gt;&lt;td&gt;{test_metrics[‘test_f1’]:.4f}&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;ROC AUC&lt;/td&gt;&lt;td&gt;{test_metrics[‘test_roc_auc’]:.4f}&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;
&lt;h2&gt;Leakage Notes&lt;a href=&quot;#leakage-notes&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;StandardScaler is inside Pipeline, so it is fit only on each training fold.&lt;/li&gt;
&lt;li&gt;The holdout test set is not used during GridSearchCV.&lt;/li&gt;
&lt;li&gt;The chosen model is evaluated on the test set only after model selection.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;Interpretation&lt;a href=&quot;#interpretation&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Write your own explanation here:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Is the best CV model also clearly better than the others, or only slightly better?&lt;/li&gt;
&lt;li&gt;Does the chosen metric match the business or scientific goal?&lt;/li&gt;
&lt;li&gt;Are the best parameters reasonable, or do they suggest overfitting?&lt;/li&gt;
&lt;li&gt;What should be checked with a larger dataset or repeated CV?
&quot;&quot;&quot;
return report&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;def main():
X, y, target_names = load_data()
X_train, X_test, y_train, y_test = train_test_holdout(X, y)&lt;/p&gt;
&lt;p&gt;plain results = run_model_selection(X_train, y_train)
best_model = results[0][“best_estimator”]
test_metrics = evaluate_classifier(best_model, X_test, y_test)&lt;/p&gt;
&lt;p&gt;plain report = make_report(results, test_metrics, target_names)
REPORT_PATH.parent.mkdir(parents=True, exist_ok=True)
REPORT_PATH.write_text(report, encoding=“utf-8”)&lt;/p&gt;
&lt;p&gt;plain display_rows = [
{
“model”: row[“model”],
“mean_cv_f1”: round(row[“best_score”], 4),
“best_params”: row[“best_params”],
}
for row in results
]
print(pd.DataFrame(display_rows).to_string(index=False))
print()
print(“Best model:”, results[0][“model”])
print(“Test metrics:”, {key: round(value, 4) for key, value in test_metrics.items()})
print(“Report written to”, REPORT_PATH)&lt;/p&gt;
&lt;/code&gt;&lt;p&gt;&lt;code&gt;if &lt;strong&gt;name&lt;/strong&gt; == “&lt;strong&gt;main&lt;/strong&gt;”:
main()
&lt;/code&gt;&lt;/p&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;&lt;/p&gt;
&lt;h2&gt;9. 运行脚本&lt;/h2&gt;
&lt;div&gt;&lt;div&gt;&lt;span&gt;omp&lt;/span&gt;&lt;span&gt;fish&lt;/span&gt;&lt;/div&gt;&lt;pre&gt;&lt;code&gt;source .venv/bin/activate.fish
python src/model_selection.py
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;预期输出结构类似：&lt;/p&gt;
&lt;div&gt;&lt;div&gt;&lt;span&gt;omp&lt;/span&gt;&lt;span&gt;prompt&lt;/span&gt;&lt;/div&gt;&lt;pre&gt;&lt;code&gt;              model  mean_cv_f1                                         best_params
      random_forest      0.9720 {&apos;model__max_depth&apos;: None, &apos;model__min_samples_leaf&apos;: 1, &apos;model__n_estimators&apos;: 200}
logistic_regression      0.9680                     {&apos;model__C&apos;: 1.0, &apos;model__class_weight&apos;: None}
                knn      0.9662                 {&apos;model__n_neighbors&apos;: 9, &apos;model__weights&apos;: &apos;uniform&apos;}
      decision_tree      0.9371                     {&apos;model__max_depth&apos;: 4, &apos;model__min_samples_leaf&apos;: 3}
&lt;/code&gt;&lt;p&gt;&lt;code&gt;Best model: random_forest
Test metrics: {‘test_accuracy’: 0.9649, ‘test_precision’: 0.9589, ‘test_recall’: 0.9859, ‘test_f1’: 0.9722, ‘test_roc_auc’: 0.9951}
Report written to reports/week06_cv_report.md
&lt;/code&gt;&lt;/p&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;&lt;/p&gt;
&lt;p&gt;数值可能不同，但必须具备：模型名、CV 均值、最佳参数、最终 test 指标。&lt;/p&gt;
&lt;h2&gt;10. 结果表怎么读&lt;/h2&gt;
&lt;p&gt;你会得到类似终端风格结果：&lt;/p&gt;
&lt;div&gt;&lt;div&gt;&lt;span&gt;omp&lt;/span&gt;&lt;span&gt;prompt&lt;/span&gt;&lt;/div&gt;&lt;pre&gt;&lt;code&gt;╭─ model selection ─────────────────────╮
│ model                 valid_score      │
├────────────────────────────────────────┤
│ logistic_regression   0.9680           │
│ random_forest         0.9720           │
│ knn                   0.9662           │
│ decision_tree         0.9371           │
╰────────────────────────────────────────╯
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;但是报告里不要只写一个分数。至少解释三件事：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;最佳模型比第二名高多少？如果只高 0.002，可能没有实际差异。&lt;/li&gt;
&lt;li&gt;标准差是否大？如果不同折波动很大，模型不稳定。&lt;/li&gt;
&lt;li&gt;最优参数是否在网格边界？如果 &lt;code&gt;C=10.0&lt;/code&gt; 永远最好，说明还可以继续扩大搜索范围，但不要无止境调参。&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;当前示例脚本为了简洁没有打印每个参数组合的标准差。你可以扩展 &lt;code&gt;search.cv_results_&lt;/code&gt; 生成更完整表格。&lt;/p&gt;
&lt;h2&gt;11. 如何加入标准差&lt;/h2&gt;
&lt;p&gt;在 &lt;code&gt;run_model_selection()&lt;/code&gt; 中，&lt;code&gt;GridSearchCV&lt;/code&gt; 保存了 &lt;code&gt;cv_results_&lt;/code&gt;。可以取最佳行的标准差：&lt;/p&gt;
&lt;div&gt;&lt;div&gt;&lt;span&gt;omp&lt;/span&gt;&lt;span&gt;python&lt;/span&gt;&lt;/div&gt;&lt;pre&gt;&lt;code&gt;best_index = search.best_index_
best_std = search.cv_results_[&quot;std_test_score&quot;][best_index]
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;然后把结果字典改成：&lt;/p&gt;
&lt;div&gt;&lt;div&gt;&lt;span&gt;omp&lt;/span&gt;&lt;span&gt;python&lt;/span&gt;&lt;/div&gt;&lt;pre&gt;&lt;code&gt;{
    &quot;model&quot;: model_name,
    &quot;best_score&quot;: search.best_score_,
    &quot;best_std&quot;: best_std,
    &quot;best_params&quot;: search.best_params_,
    &quot;best_estimator&quot;: search.best_estimator_,
}
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;报告表就能写成：&lt;/p&gt;
&lt;div&gt;&lt;div&gt;&lt;span&gt;omp&lt;/span&gt;&lt;span&gt;prompt&lt;/span&gt;&lt;/div&gt;&lt;pre&gt;&lt;code&gt;| Model | Mean CV F1 | Std | Best Params |
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;这比只看均值更可靠。&lt;/p&gt;
&lt;h2&gt;12. 回归任务怎么改&lt;/h2&gt;
&lt;p&gt;如果目标是连续数值，比如房价、收益率、销量，不要用分类指标。改成：&lt;/p&gt;
&lt;div&gt;&lt;div&gt;&lt;span&gt;omp&lt;/span&gt;&lt;span&gt;python&lt;/span&gt;&lt;/div&gt;&lt;pre&gt;&lt;code&gt;from sklearn.model_selection import KFold
from sklearn.metrics import mean_absolute_error, mean_squared_error, r2_score
&lt;/code&gt;&lt;p&gt;&lt;code&gt;cv = KFold(n_splits=5, shuffle=True, random_state=42)
&lt;/code&gt;&lt;/p&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;&lt;/p&gt;
&lt;p&gt;&lt;code&gt;GridSearchCV&lt;/code&gt; 的 scoring 可以用：&lt;/p&gt;
&lt;div&gt;&lt;div&gt;&lt;span&gt;omp&lt;/span&gt;&lt;span&gt;python&lt;/span&gt;&lt;/div&gt;&lt;pre&gt;&lt;code&gt;scoring=&quot;neg_mean_absolute_error&quot;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;注意 sklearn 的误差类指标常用负号，因为 GridSearchCV 默认“越大越好”。报告里要转回正的 MAE：&lt;/p&gt;
&lt;div&gt;&lt;div&gt;&lt;span&gt;omp&lt;/span&gt;&lt;span&gt;python&lt;/span&gt;&lt;/div&gt;&lt;pre&gt;&lt;code&gt;mean_mae = -search.best_score_
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;回归报告建议包含：&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;指标&lt;/th&gt;
&lt;th&gt;含义&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;&lt;tr&gt;
&lt;td&gt;MAE&lt;/td&gt;
&lt;td&gt;平均绝对误差，单位和目标变量一致，最容易解释&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;MSE&lt;/td&gt;
&lt;td&gt;平方误差，对大误差更敏感&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;RMSE&lt;/td&gt;
&lt;td&gt;MSE 开根号，单位回到目标变量&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;R²&lt;/td&gt;
&lt;td&gt;相对均值模型的解释程度&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;&lt;/table&gt;
&lt;h2&gt;13. 自己数据中的类别特征&lt;/h2&gt;
&lt;p&gt;真实表格常有字符串列，比如：&lt;/p&gt;
&lt;div&gt;&lt;div&gt;&lt;span&gt;omp&lt;/span&gt;&lt;span&gt;prompt&lt;/span&gt;&lt;/div&gt;&lt;pre&gt;&lt;code&gt;city, gender, industry, education
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;不能直接喂给 LogisticRegression。Week 06 的正确方式是 &lt;code&gt;ColumnTransformer&lt;/code&gt;：&lt;/p&gt;
&lt;div&gt;&lt;div&gt;&lt;span&gt;omp&lt;/span&gt;&lt;span&gt;python&lt;/span&gt;&lt;/div&gt;&lt;pre&gt;&lt;code&gt;from sklearn.compose import ColumnTransformer
from sklearn.preprocessing import OneHotEncoder, StandardScaler
&lt;p&gt;numeric_features = [“age”, “income”, “score”]
categorical_features = [“city”, “gender”]&lt;/p&gt;
&lt;/code&gt;&lt;p&gt;&lt;code&gt;preprocess = ColumnTransformer(
transformers=[
(“num”, StandardScaler(), numeric_features),
(“cat”, OneHotEncoder(handle_unknown=“ignore”), categorical_features),
]
)
&lt;/code&gt;&lt;/p&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;&lt;/p&gt;
&lt;p&gt;然后放进 Pipeline：&lt;/p&gt;
&lt;div&gt;&lt;div&gt;&lt;span&gt;omp&lt;/span&gt;&lt;span&gt;python&lt;/span&gt;&lt;/div&gt;&lt;pre&gt;&lt;code&gt;pipeline = Pipeline(
    steps=[
        (&quot;preprocess&quot;, preprocess),
        (&quot;model&quot;, LogisticRegression(max_iter=3000)),
    ]
)
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;这样每一折都会只在训练折上学习均值、标准差和类别编码，验证折不会泄漏信息。&lt;/p&gt;
&lt;h2&gt;14. 报告必须回答的问题&lt;/h2&gt;
&lt;p&gt;打开报告：&lt;/p&gt;
&lt;div&gt;&lt;div&gt;&lt;span&gt;omp&lt;/span&gt;&lt;span&gt;fish&lt;/span&gt;&lt;/div&gt;&lt;pre&gt;&lt;code&gt;code reports/week06_cv_report.md
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;补充下面内容：&lt;/p&gt;
&lt;div&gt;&lt;div&gt;&lt;span&gt;omp&lt;/span&gt;&lt;span&gt;prompt&lt;/span&gt;&lt;/div&gt;&lt;pre&gt;&lt;code&gt;## Model Choice
为什么选择这个模型？它是否明显优于其他模型？
&lt;h2&gt;Metric Choice&lt;a href=&quot;#metric-choice&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;为什么用 F1 / AUC / MAE / R²？这个指标和问题目标是否一致？&lt;/p&gt;
&lt;h2&gt;Leakage Prevention&lt;a href=&quot;#leakage-prevention&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;哪些预处理被放进 Pipeline？test 集有没有参与调参？&lt;/p&gt;
&lt;h2&gt;Limitations&lt;a href=&quot;#limitations&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;
&lt;/code&gt;&lt;p&gt;&lt;code&gt;数据量、特征、划分方式、参数网格有哪些限制？
&lt;/code&gt;&lt;/p&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;&lt;/p&gt;
&lt;p&gt;不要写成“GridSearchCV 得分最高，所以最好”。要说明实验设计。&lt;/p&gt;
&lt;h2&gt;15. Git 保存成果&lt;/h2&gt;
&lt;div&gt;&lt;div&gt;&lt;span&gt;omp&lt;/span&gt;&lt;span&gt;fish&lt;/span&gt;&lt;/div&gt;&lt;pre&gt;&lt;code&gt;git status --short
git add pyproject.toml uv.lock README.md src reports
git commit -m &quot;Add week06 cross validation model selection&quot;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;如果你从 Week 05 复制项目继续做，不要提交 &lt;code&gt;.venv/&lt;/code&gt;。如果 &lt;code&gt;git status --short&lt;/code&gt; 里出现 &lt;code&gt;.venv/&lt;/code&gt;，先确认 &lt;code&gt;.gitignore&lt;/code&gt; 包含：&lt;/p&gt;
&lt;div&gt;&lt;div&gt;&lt;span&gt;omp&lt;/span&gt;&lt;span&gt;prompt&lt;/span&gt;&lt;/div&gt;&lt;pre&gt;&lt;code&gt;.venv/
__pycache__/
.ipynb_checkpoints/
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;h2&gt;16. 本周练习&lt;/h2&gt;
&lt;ol&gt;
&lt;li&gt;把 &lt;code&gt;scoring=&quot;f1&quot;&lt;/code&gt; 改成 &lt;code&gt;scoring=&quot;roc_auc&quot;&lt;/code&gt;，观察模型排序是否改变。&lt;/li&gt;
&lt;li&gt;把 &lt;code&gt;n_splits=5&lt;/code&gt; 改成 3 和 10，比较结果稳定性与运行时间。&lt;/li&gt;
&lt;li&gt;给结果表增加 &lt;code&gt;std_test_score&lt;/code&gt;。&lt;/li&gt;
&lt;li&gt;在报告中解释为什么 &lt;code&gt;StandardScaler&lt;/code&gt; 必须放进 Pipeline。&lt;/li&gt;
&lt;li&gt;用一个回归数据集重写脚本，输出 MAE、RMSE、R²。&lt;/li&gt;
&lt;li&gt;设计一个小实验：故意在全数据上 &lt;code&gt;fit_transform&lt;/code&gt; 标准化，再比较分数，写下为什么这是错误示范。&lt;/li&gt;
&lt;/ol&gt;
&lt;h2&gt;17. 验收检查&lt;/h2&gt;
&lt;p&gt;在项目根目录执行：&lt;/p&gt;
&lt;div&gt;&lt;div&gt;&lt;span&gt;omp&lt;/span&gt;&lt;span&gt;fish&lt;/span&gt;&lt;/div&gt;&lt;pre&gt;&lt;code&gt;source .venv/bin/activate.fish
python src/model_selection.py
test -f reports/week06_cv_report.md; and echo &quot;report exists&quot;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;人工确认：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;src/model_selection.py&lt;/code&gt; 存在。&lt;/li&gt;
&lt;li&gt;使用了 &lt;code&gt;StratifiedKFold&lt;/code&gt; 或 &lt;code&gt;KFold&lt;/code&gt;。&lt;/li&gt;
&lt;li&gt;使用了 &lt;code&gt;Pipeline&lt;/code&gt;。&lt;/li&gt;
&lt;li&gt;至少比较了 3 个模型。&lt;/li&gt;
&lt;li&gt;至少有一个模型使用了参数网格。&lt;/li&gt;
&lt;li&gt;报告里有模型、指标、CV 设计、结果表格、最佳参数。&lt;/li&gt;
&lt;li&gt;报告明确说明如何防止数据泄漏。&lt;/li&gt;
&lt;li&gt;test 集只在最后评估一次。&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;18. 常见错误&lt;/h2&gt;
&lt;h3&gt;错误 1：先标准化全数据再交叉验证&lt;/h3&gt;
&lt;p&gt;错误逻辑：&lt;/p&gt;
&lt;div&gt;&lt;div&gt;&lt;span&gt;omp&lt;/span&gt;&lt;span&gt;prompt&lt;/span&gt;&lt;/div&gt;&lt;pre&gt;&lt;code&gt;scaler.fit_transform(X)
然后再 cross_validate
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;这会泄漏验证折信息。修复：把 scaler 放进 Pipeline。&lt;/p&gt;
&lt;h3&gt;错误 2：分类任务使用普通 KFold 导致类别比例异常&lt;/h3&gt;
&lt;p&gt;如果数据类别不平衡，某一折可能正类很少，指标波动巨大。&lt;/p&gt;
&lt;p&gt;修复：&lt;/p&gt;
&lt;div&gt;&lt;div&gt;&lt;span&gt;omp&lt;/span&gt;&lt;span&gt;python&lt;/span&gt;&lt;/div&gt;&lt;pre&gt;&lt;code&gt;StratifiedKFold(n_splits=5, shuffle=True, random_state=42)
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;h3&gt;错误 3：忘记固定 random_state&lt;/h3&gt;
&lt;p&gt;没有固定随机种子，今天跑和明天跑结果不同，报告无法复现。&lt;/p&gt;
&lt;p&gt;修复：统一设置：&lt;/p&gt;
&lt;div&gt;&lt;div&gt;&lt;span&gt;omp&lt;/span&gt;&lt;span&gt;python&lt;/span&gt;&lt;/div&gt;&lt;pre&gt;&lt;code&gt;RANDOM_STATE = 42
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;h3&gt;错误 4：参数网格过大&lt;/h3&gt;
&lt;p&gt;初学时不要一口气搜索几千种组合。先用小网格理解流程，再针对有希望的模型细化。&lt;/p&gt;
&lt;h3&gt;错误 5：把最高分当作唯一结论&lt;/h3&gt;
&lt;p&gt;如果两个模型均值接近，优先考虑：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;稳定性。&lt;/li&gt;
&lt;li&gt;可解释性。&lt;/li&gt;
&lt;li&gt;训练时间。&lt;/li&gt;
&lt;li&gt;是否容易部署。&lt;/li&gt;
&lt;li&gt;是否和后续项目目标一致。&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;19. 下一步&lt;/h2&gt;
&lt;p&gt;进入 Week 07：&lt;a href=&quot;/post/ustc-stat-ai-quant-plan/&quot;&gt;统计建模项目启动&lt;/a&gt;。你会从练习脚本转向一个可展示的小项目：定义问题、整理数据卡、建立 baseline、记录错误分析，并为 Week 08 的成品报告做准备。&lt;/p&gt;
&lt;p&gt;plain &lt;/p&gt;&lt;/article&gt;&lt;p&gt;&lt;/p&gt;
  &lt;/div&gt;
&lt;/section&gt;</content:encoded><category>category:工具</category><category>category:机器学习</category><category>tag:sklearn</category><category>tag:cross-validation</category><category>tag:model-selection</category><category>tag:leakage</category></item><item><title>Week 05：sklearn Baselines、数据划分与指标</title><link>https://39miku.space/post/week05-sklearn-baselines</link><guid isPermaLink="false">week05-sklearn-baselines</guid><description>Week 05：sklearn Baselines、数据划分与指标。fish-first 终端教学，面向 CachyOS、VS Code、uv 和 Python 学习路线。</description><pubDate>Sat, 25 Jul 2026 21:00:00 GMT</pubDate><content:encoded>
&lt;section&gt;
  &lt;div&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;strong&gt;Oh My Pi&lt;/strong&gt; / weekly tutorial / week05-sklearn-baselines&lt;/span&gt;&lt;/div&gt;
  &lt;div&gt;
    &lt;div&gt;&lt;span&gt;miku@cachyos&lt;/span&gt;&lt;span&gt;:~/Code/python-learning&lt;/span&gt;&lt;span&gt;$&lt;/span&gt; &lt;span&gt;omp teach week05-sklearn-baselines --fish-first --step-by-step&lt;/span&gt;&lt;/div&gt;
    &lt;div&gt;
      &lt;div&gt;&lt;span&gt;source&lt;/span&gt;&lt;strong&gt;638 行教学文档&lt;/strong&gt;&lt;/div&gt;
      &lt;div&gt;&lt;span&gt;week&lt;/span&gt;&lt;strong&gt;Week 05&lt;/strong&gt;&lt;/div&gt;
      &lt;div&gt;&lt;span&gt;shell&lt;/span&gt;&lt;strong&gt;fish-first 命令版&lt;/strong&gt;&lt;/div&gt;
      &lt;div&gt;&lt;span&gt;backlink&lt;/span&gt;&lt;strong&gt;&lt;a href=&quot;/post/ustc-stat-ai-quant-plan/&quot;&gt;20 周计划&lt;/a&gt;&lt;/strong&gt;&lt;/div&gt;
    &lt;/div&gt;
    &lt;article&gt;
&lt;h1&gt;Week 05：sklearn Baselines、数据划分与指标&lt;/h1&gt;
&lt;blockquote&gt;
&lt;p&gt;返回主线计划：&lt;a href=&quot;/post/ustc-stat-ai-quant-plan/&quot;&gt;USTC 统计学：AI / 量化金融 20 周终端式成长计划&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;本周进入 Phase 2：从“会清洗数据”转向“能建立一个可信的机器学习 baseline”。这里的重点不是追求最高分，而是建立一套以后每个项目都会复用的最小训练流程：固定数据划分、训练多个经典模型、记录指标、写下第一轮误差分析。&lt;/p&gt;
&lt;p&gt;本文默认你使用 CachyOS、fish shell、VS Code、uv、Python、Git。命令都按 fish 写；如果不是 fish，需要自己转换激活命令。&lt;/p&gt;
&lt;p&gt;&lt;a&gt;&lt;/a&gt;&lt;/p&gt;
&lt;h2&gt;0. 本周详细教学：语法、规范、验收&lt;/h2&gt;
&lt;blockquote&gt;
&lt;p&gt;本节不是追加在尾部的复习，而是本周正文的入口。先读这里，再做后面的命令和项目。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h3&gt;0.1 本周真正要学会什么&lt;/h3&gt;
&lt;table&gt;
&lt;thead&gt;&lt;tr&gt;&lt;th&gt;维度&lt;/th&gt;&lt;th&gt;要求&lt;/th&gt;&lt;/tr&gt;&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;&lt;td&gt;知识点&lt;/td&gt;&lt;td&gt;train/test、Dummy baseline、LogisticRegression、指标&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;代码语法&lt;/td&gt;&lt;td&gt;能从空文件写出本周核心脚本，而不是只复制运行&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;程序规范&lt;/td&gt;&lt;td&gt;函数拆分、路径清楚、输入输出明确、错误能解释&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;交付物&lt;/td&gt;&lt;td&gt;&lt;code&gt;src/train_baselines.py&lt;/code&gt;&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;验收方式&lt;/td&gt;&lt;td&gt;从 fish 终端运行命令，得到可复查的文件或指标&lt;/td&gt;&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;h3&gt;0.2 代码语法精讲&lt;/h3&gt;
&lt;p&gt;下面的代码不是最终答案，而是本周必须理解的最小骨架：&lt;/p&gt;
&lt;div&gt;&lt;div&gt;&lt;span&gt;omp&lt;/span&gt;&lt;span&gt;python&lt;/span&gt;&lt;/div&gt;&lt;pre&gt;&lt;code&gt;from sklearn.dummy import DummyClassifier
from sklearn.linear_model import LogisticRegression
from sklearn.metrics import classification_report
&lt;/code&gt;&lt;p&gt;&lt;code&gt;models = {
“dummy”: DummyClassifier(strategy=“most_frequent”),
“logreg”: LogisticRegression(max_iter=1000),
}
for name, model in models.items():
model.fit(X_train, y_train)
pred = model.predict(X_valid)
print(name)
print(classification_report(y_valid, pred))
&lt;/code&gt;&lt;/p&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;&lt;/p&gt;
&lt;p&gt;读代码时按四步检查：输入从哪里来；中间变量的类型和 shape 是什么；函数或脚本输出什么；哪些错误应该显式报出来。&lt;/p&gt;
&lt;h3&gt;0.3 本周程序规范&lt;/h3&gt;
&lt;ul&gt;&lt;li&gt;所有路径用相对路径或 `pathlib.Path`，不要写死 `/home/miku/...`。&lt;/li&gt;&lt;li&gt;核心逻辑进 `src/`，notebook 只做探索和解释。&lt;/li&gt;&lt;li&gt;每个脚本能从 fish 终端运行，并在 README 写出命令。&lt;/li&gt;&lt;li&gt;输出必须落盘到 `reports/`、`figures/` 或 `outputs/`，不能只在屏幕上看。&lt;/li&gt;&lt;/ul&gt;
&lt;h3&gt;0.4 本周练习分层&lt;/h3&gt;
&lt;table&gt;
&lt;thead&gt;&lt;tr&gt;&lt;th&gt;层级&lt;/th&gt;&lt;th&gt;任务&lt;/th&gt;&lt;th&gt;不合格表现&lt;/th&gt;&lt;th&gt;合格验收&lt;/th&gt;&lt;/tr&gt;&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;&lt;td&gt;最小练习&lt;/td&gt;&lt;td&gt;手写上面的最小骨架&lt;/td&gt;&lt;td&gt;只在 notebook 里运行&lt;/td&gt;&lt;td&gt;终端运行成功&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;标准练习&lt;/td&gt;&lt;td&gt;把逻辑拆成函数/模块&lt;/td&gt;&lt;td&gt;一个大脚本从头写到尾&lt;/td&gt;&lt;td&gt;至少 2 个函数，职责清楚&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;项目练习&lt;/td&gt;&lt;td&gt;生成本周交付物 &lt;code&gt;src/train_baselines.py&lt;/code&gt;&lt;/td&gt;&lt;td&gt;只有屏幕输出&lt;/td&gt;&lt;td&gt;文件落盘，可复查&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;复盘练习&lt;/td&gt;&lt;td&gt;写 3 个错误和修复&lt;/td&gt;&lt;td&gt;只写“已解决”&lt;/td&gt;&lt;td&gt;写清报错、原因、修复、预防&lt;/td&gt;&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;h3&gt;0.5 本周和主线的连接&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;回到总计划：&lt;a href=&quot;/post/ustc-stat-ai-quant-plan/#week-plan&quot;&gt;USTC AI / Quant 练习手册&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;查详细练习索引：&lt;a href=&quot;/post/ustc-stat-ai-quant-plan/#deep-practice-appendix&quot;&gt;技术练习详解&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;查质量评分：&lt;a href=&quot;/post/ustc-stat-ai-quant-plan/#quality-final-gates&quot;&gt;最终质量门槛&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;1. 本周目标&lt;/h2&gt;
&lt;p&gt;完成一个可以重复运行的 &lt;code&gt;src/train_baselines.py&lt;/code&gt;：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;读取一个表格数据集。&lt;/li&gt;
&lt;li&gt;区分特征 &lt;code&gt;X&lt;/code&gt; 与目标 &lt;code&gt;y&lt;/code&gt;。&lt;/li&gt;
&lt;li&gt;做 &lt;code&gt;train / validation / test&lt;/code&gt; 三份划分。&lt;/li&gt;
&lt;li&gt;训练至少 3 个 baseline 模型。&lt;/li&gt;
&lt;li&gt;对分类或回归任务输出合适指标。&lt;/li&gt;
&lt;li&gt;生成 &lt;code&gt;reports/week05_baseline_results.md&lt;/code&gt;。&lt;/li&gt;
&lt;li&gt;写下初步误差分析，而不是只贴一个分数。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;你最终应理解：&lt;/p&gt;
&lt;div&gt;&lt;div&gt;&lt;span&gt;omp&lt;/span&gt;&lt;span&gt;prompt&lt;/span&gt;&lt;/div&gt;&lt;pre&gt;&lt;code&gt;baseline 不是“低级模型”，而是以后所有改进的比较基准。
没有 baseline，就不知道复杂模型到底有没有带来价值。
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;h2&gt;2. 前置条件&lt;/h2&gt;
&lt;p&gt;你应该已经完成 Week 01-04：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;会在终端进入项目目录。&lt;/li&gt;
&lt;li&gt;会用 &lt;code&gt;uv&lt;/code&gt; 创建虚拟环境。&lt;/li&gt;
&lt;li&gt;会用 VS Code 打开项目。&lt;/li&gt;
&lt;li&gt;会用 pandas 读取 CSV。&lt;/li&gt;
&lt;li&gt;会做基础 EDA：缺失值、分布、相关性、简单图表。&lt;/li&gt;
&lt;li&gt;会用 Git 保存阶段性成果。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;检查工具：&lt;/p&gt;
&lt;div&gt;&lt;div&gt;&lt;span&gt;omp&lt;/span&gt;&lt;span&gt;fish&lt;/span&gt;&lt;/div&gt;&lt;pre&gt;&lt;code&gt;python --version
uv --version
git --version
code --version
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;如果 &lt;code&gt;python&lt;/code&gt; 指向系统 Python，不要急着安装包；先进入项目目录并激活 &lt;code&gt;.venv&lt;/code&gt;。&lt;/p&gt;
&lt;h2&gt;3. 建议项目位置&lt;/h2&gt;
&lt;p&gt;如果你前几周已经有学习仓库，可以继续使用：&lt;/p&gt;
&lt;div&gt;&lt;div&gt;&lt;span&gt;omp&lt;/span&gt;&lt;span&gt;fish&lt;/span&gt;&lt;/div&gt;&lt;pre&gt;&lt;code&gt;cd ~/Code/python-learning
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;为 Week 05 单独建一个小项目：&lt;/p&gt;
&lt;div&gt;&lt;div&gt;&lt;span&gt;omp&lt;/span&gt;&lt;span&gt;fish&lt;/span&gt;&lt;/div&gt;&lt;pre&gt;&lt;code&gt;mkdir -p week05-sklearn-baselines
cd week05-sklearn-baselines
pwd
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;解释：&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;命令&lt;/th&gt;
&lt;th&gt;作用&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;&lt;tr&gt;
&lt;td&gt;&lt;code&gt;mkdir -p&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;创建目录；目录已存在也不报错&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;cd&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;进入项目目录&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;pwd&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;确认当前路径，避免在错误目录里初始化项目&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;&lt;/table&gt;
&lt;p&gt;建议输出类似：&lt;/p&gt;
&lt;div&gt;&lt;div&gt;&lt;span&gt;omp&lt;/span&gt;&lt;span&gt;prompt&lt;/span&gt;&lt;/div&gt;&lt;pre&gt;&lt;code&gt;/home/miku/Code/python-learning/week05-sklearn-baselines
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;h2&gt;4. 初始化 uv 项目&lt;/h2&gt;
&lt;div&gt;&lt;div&gt;&lt;span&gt;omp&lt;/span&gt;&lt;span&gt;fish&lt;/span&gt;&lt;/div&gt;&lt;pre&gt;&lt;code&gt;uv init
uv venv
source .venv/bin/activate.fish
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;逐句解释：&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;命令&lt;/th&gt;
&lt;th&gt;含义&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;&lt;tr&gt;
&lt;td&gt;&lt;code&gt;uv init&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;生成 &lt;code&gt;pyproject.toml&lt;/code&gt;、&lt;code&gt;README.md&lt;/code&gt; 等项目文件&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;uv venv&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;在当前目录创建 &lt;code&gt;.venv/&lt;/code&gt; 虚拟环境&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;source .venv/bin/activate.fish&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;fish shell 专用激活命令&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;&lt;/table&gt;
&lt;p&gt;激活后检查 Python 路径：&lt;/p&gt;
&lt;div&gt;&lt;div&gt;&lt;span&gt;omp&lt;/span&gt;&lt;span&gt;fish&lt;/span&gt;&lt;/div&gt;&lt;pre&gt;&lt;code&gt;command -v python
python --version
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;你希望看到路径里包含当前项目：&lt;/p&gt;
&lt;div&gt;&lt;div&gt;&lt;span&gt;omp&lt;/span&gt;&lt;span&gt;prompt&lt;/span&gt;&lt;/div&gt;&lt;pre&gt;&lt;code&gt;/home/miku/Code/python-learning/week05-sklearn-baselines/.venv/bin/python
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;如果看到 &lt;code&gt;/usr/bin/python&lt;/code&gt;，说明环境没有激活成功。&lt;/p&gt;
&lt;h2&gt;5. 安装依赖&lt;/h2&gt;
&lt;p&gt;Week 05 需要 pandas、numpy、scikit-learn，以及一个简单制表工具。&lt;/p&gt;
&lt;div&gt;&lt;div&gt;&lt;span&gt;omp&lt;/span&gt;&lt;span&gt;fish&lt;/span&gt;&lt;/div&gt;&lt;pre&gt;&lt;code&gt;uv add pandas numpy scikit-learn tabulate
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;检查：&lt;/p&gt;
&lt;div&gt;&lt;div&gt;&lt;span&gt;omp&lt;/span&gt;&lt;span&gt;fish&lt;/span&gt;&lt;/div&gt;&lt;pre&gt;&lt;code&gt;python -c &quot;import pandas, sklearn; print(&apos;pandas&apos;, pandas.__version__); print(&apos;sklearn&apos;, sklearn.__version__)&quot;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;这里用 &lt;code&gt;python -c&lt;/code&gt; 运行一行小检查，不是写项目代码。真正的训练逻辑要放进 &lt;code&gt;src/train_baselines.py&lt;/code&gt;。&lt;/p&gt;
&lt;h2&gt;6. 建立文件布局&lt;/h2&gt;
&lt;p&gt;创建目录：&lt;/p&gt;
&lt;div&gt;&lt;div&gt;&lt;span&gt;omp&lt;/span&gt;&lt;span&gt;fish&lt;/span&gt;&lt;/div&gt;&lt;pre&gt;&lt;code&gt;mkdir -p data/raw data/processed src reports
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;建议布局：&lt;/p&gt;
&lt;div&gt;&lt;div&gt;&lt;span&gt;omp&lt;/span&gt;&lt;span&gt;prompt&lt;/span&gt;&lt;/div&gt;&lt;pre&gt;&lt;code&gt;week05-sklearn-baselines/
├── .venv/                    # 当前项目虚拟环境，不提交到 Git
├── data/
│   ├── raw/                  # 原始数据，只读保存
│   └── processed/            # 处理后的数据，可重复生成
├── reports/
│   └── week05_baseline_results.md
├── src/
│   └── train_baselines.py
├── pyproject.toml
├── uv.lock
└── README.md
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;注意：&lt;code&gt;data/raw/&lt;/code&gt; 里的原始数据不要反复手工修改。你可以复制一份到 &lt;code&gt;data/processed/&lt;/code&gt; 做清洗输出，但原始文件最好保留。&lt;/p&gt;
&lt;h2&gt;7. 选择一个练习数据集&lt;/h2&gt;
&lt;p&gt;为了让脚本可直接运行，本周可以先用 sklearn 自带的 breast cancer 分类数据集。它适合练习：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;二分类。&lt;/li&gt;
&lt;li&gt;数值特征。&lt;/li&gt;
&lt;li&gt;样本量不大，运行很快。&lt;/li&gt;
&lt;li&gt;不需要联网下载。&lt;/li&gt;
&lt;li&gt;可直接比较 Logistic Regression、KNN、Decision Tree、Random Forest。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;真实项目中，你可以换成 Kaggle、UCI 或课程数据。baseline 结构保持不变。&lt;/p&gt;
&lt;h2&gt;8. 理解 train / validation / test&lt;/h2&gt;
&lt;p&gt;不要只划分 train/test。Week 05 应开始养成三份数据意识：&lt;/p&gt;
&lt;div&gt;&lt;div&gt;&lt;span&gt;omp&lt;/span&gt;&lt;span&gt;prompt&lt;/span&gt;&lt;/div&gt;&lt;pre&gt;&lt;code&gt;train      用来拟合模型参数
validation 用来比较模型、调参数、做选择
test       最后只使用一次，用来估计最终泛化表现
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;常见错误是：&lt;/p&gt;
&lt;div&gt;&lt;div&gt;&lt;span&gt;omp&lt;/span&gt;&lt;span&gt;prompt&lt;/span&gt;&lt;/div&gt;&lt;pre&gt;&lt;code&gt;在 test 上反复试模型，哪个分数高就选哪个。
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;这会让 test 变成新的 validation，最终分数偏乐观。正确流程是：&lt;/p&gt;
&lt;div&gt;&lt;div&gt;&lt;span&gt;omp&lt;/span&gt;&lt;span&gt;prompt&lt;/span&gt;&lt;/div&gt;&lt;pre&gt;&lt;code&gt;1. train 上训练多个模型
2. validation 上比较模型
3. 选定一个模型
4. 只在最后对 test 评估一次
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;对于分类任务，划分时使用 &lt;code&gt;stratify=y&lt;/code&gt;，保证每份数据里的类别比例接近整体比例。&lt;/p&gt;
&lt;h2&gt;9. 创建 baseline 脚本&lt;/h2&gt;
&lt;p&gt;在 VS Code 中打开项目：&lt;/p&gt;
&lt;div&gt;&lt;div&gt;&lt;span&gt;omp&lt;/span&gt;&lt;span&gt;fish&lt;/span&gt;&lt;/div&gt;&lt;pre&gt;&lt;code&gt;code .
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;创建文件：&lt;/p&gt;
&lt;div&gt;&lt;div&gt;&lt;span&gt;omp&lt;/span&gt;&lt;span&gt;prompt&lt;/span&gt;&lt;/div&gt;&lt;pre&gt;&lt;code&gt;src/train_baselines.py
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;把下面代码放进去：&lt;/p&gt;
&lt;div&gt;&lt;div&gt;&lt;span&gt;omp&lt;/span&gt;&lt;span&gt;python&lt;/span&gt;&lt;/div&gt;&lt;pre&gt;&lt;code&gt;from pathlib import Path
&lt;p&gt;import pandas as pd
from sklearn.datasets import load_breast_cancer
from sklearn.ensemble import RandomForestClassifier
from sklearn.linear_model import LogisticRegression
from sklearn.metrics import accuracy_score, f1_score, precision_score, recall_score, roc_auc_score
from sklearn.model_selection import train_test_split
from sklearn.neighbors import KNeighborsClassifier
from sklearn.pipeline import Pipeline
from sklearn.preprocessing import StandardScaler
from sklearn.tree import DecisionTreeClassifier&lt;/p&gt;
&lt;p&gt;RANDOM_STATE = 42
REPORT_PATH = Path(“reports/week05_baseline_results.md”)&lt;/p&gt;
&lt;p&gt;def load_data():
dataset = load_breast_cancer(as_frame=True)
X = dataset.data
y = dataset.target
target_names = list(dataset.target_names)
return X, y, target_names&lt;/p&gt;
&lt;p&gt;def split_data(X, y):
X_train_valid, X_test, y_train_valid, y_test = train_test_split(
X,
y,
test_size=0.2,
random_state=RANDOM_STATE,
stratify=y,
)
X_train, X_valid, y_train, y_valid = train_test_split(
X_train_valid,
y_train_valid,
test_size=0.25,
random_state=RANDOM_STATE,
stratify=y_train_valid,
)
return X_train, X_valid, X_test, y_train, y_valid, y_test&lt;/p&gt;
&lt;p&gt;def build_models():
return {
“logistic_regression”: Pipeline(
steps=[
(“scaler”, StandardScaler()),
(“model”, LogisticRegression(max_iter=2000, random_state=RANDOM_STATE)),
]
),
“knn”: Pipeline(
steps=[
(“scaler”, StandardScaler()),
(“model”, KNeighborsClassifier(n_neighbors=5)),
]
),
“decision_tree”: DecisionTreeClassifier(max_depth=4, random_state=RANDOM_STATE),
“random_forest”: RandomForestClassifier(n_estimators=200, random_state=RANDOM_STATE),
}&lt;/p&gt;
&lt;p&gt;def evaluate_classifier(model, X, y):
predictions = model.predict(X)
probabilities = model.predict_proba(X)[:, 1]
return {
“accuracy”: accuracy_score(y, predictions),
“precision”: precision_score(y, predictions),
“recall”: recall_score(y, predictions),
“f1”: f1_score(y, predictions),
“roc_auc”: roc_auc_score(y, probabilities),
}&lt;/p&gt;
&lt;p&gt;def format_metrics(metrics):
return {name: round(value, 4) for name, value in metrics.items()}&lt;/p&gt;
&lt;p&gt;def make_report(results, split_sizes, target_names, best_model_name, test_metrics):
rows = []
for result in results:
rows.append(
”| {model} | {accuracy:.4f} | {precision:.4f} | {recall:.4f} | {f1:.4f} | {roc_auc:.4f} |“.format(
**result
)
)&lt;/p&gt;
&lt;p&gt;plain report = f&quot;&quot;&quot;# Week 05 Baseline Results&lt;/p&gt;
&lt;h2&gt;Dataset&lt;a href=&quot;#dataset&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;Source: sklearn breast cancer dataset&lt;/li&gt;
&lt;li&gt;Task: binary classification&lt;/li&gt;
&lt;li&gt;Target names:&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;Split&lt;a href=&quot;#split&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;





















&lt;table&gt;&lt;thead&gt;&lt;tr&gt;&lt;th&gt;Split&lt;/th&gt;&lt;th&gt;Rows&lt;/th&gt;&lt;/tr&gt;&lt;/thead&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td&gt;Train&lt;/td&gt;&lt;td&gt;{split_sizes[‘train’]}&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;Validation&lt;/td&gt;&lt;td&gt;{split_sizes[‘valid’]}&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;Test&lt;/td&gt;&lt;td&gt;{split_sizes[‘test’]}&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;
&lt;p&gt;The validation set is used for model comparison. The test set is used once after selecting the best validation model.&lt;/p&gt;
&lt;h2&gt;Validation Metrics&lt;a href=&quot;#validation-metrics&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;





















&lt;table&gt;&lt;thead&gt;&lt;tr&gt;&lt;th&gt;Model&lt;/th&gt;&lt;th&gt;Accuracy&lt;/th&gt;&lt;th&gt;Precision&lt;/th&gt;&lt;th&gt;Recall&lt;/th&gt;&lt;th&gt;F1&lt;/th&gt;&lt;th&gt;ROC AUC&lt;/th&gt;&lt;/tr&gt;&lt;/thead&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td&gt;{chr(10).join(rows)}&lt;/td&gt;&lt;td&gt;&lt;/td&gt;&lt;td&gt;&lt;/td&gt;&lt;td&gt;&lt;/td&gt;&lt;td&gt;&lt;/td&gt;&lt;td&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;
&lt;h2&gt;Selected Model&lt;a href=&quot;#selected-model&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;Best validation model by F1: &lt;code&gt;&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;Final Test Metrics&lt;a href=&quot;#final-test-metrics&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;





























&lt;table&gt;&lt;thead&gt;&lt;tr&gt;&lt;th&gt;Metric&lt;/th&gt;&lt;th&gt;Value&lt;/th&gt;&lt;/tr&gt;&lt;/thead&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td&gt;Accuracy&lt;/td&gt;&lt;td&gt;{test_metrics[‘accuracy’]:.4f}&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;Precision&lt;/td&gt;&lt;td&gt;{test_metrics[‘precision’]:.4f}&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;Recall&lt;/td&gt;&lt;td&gt;{test_metrics[‘recall’]:.4f}&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;F1&lt;/td&gt;&lt;td&gt;{test_metrics[‘f1’]:.4f}&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;ROC AUC&lt;/td&gt;&lt;td&gt;{test_metrics[‘roc_auc’]:.4f}&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;
&lt;h2&gt;Initial Error Analysis&lt;a href=&quot;#initial-error-analysis&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Write 5-8 bullet points after reading the confusion matrix or wrong predictions. Suggested questions:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Which class is easier to miss?&lt;/li&gt;
&lt;li&gt;Is high accuracy hiding weak recall?&lt;/li&gt;
&lt;li&gt;Does the model make more false positives or false negatives?&lt;/li&gt;
&lt;li&gt;Which metric should matter more for this problem?&lt;/li&gt;
&lt;li&gt;Is the current split stable enough, or do we need cross validation in Week 06?
&quot;&quot;&quot;
return report&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;def main():
X, y, target_names = load_data()
X_train, X_valid, X_test, y_train, y_valid, y_test = split_data(X, y)
models = build_models()&lt;/p&gt;
&lt;p&gt;plain results = []
fitted_models = {}
for name, model in models.items():
model.fit(X_train, y_train)
fitted_models[name] = model
metrics = evaluate_classifier(model, X_valid, y_valid)
results.append({“model”: name, **metrics})&lt;/p&gt;
&lt;p&gt;plain results = sorted(results, key=lambda item: item[“f1”], reverse=True)
best_model_name = results[0][“model”]
best_model = fitted_models[best_model_name]
test_metrics = evaluate_classifier(best_model, X_test, y_test)&lt;/p&gt;
&lt;p&gt;plain split_sizes = {
“train”: len(X_train),
“valid”: len(X_valid),
“test”: len(X_test),
}
report = make_report(results, split_sizes, target_names, best_model_name, test_metrics)&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;REPORT_PATH.parent.mkdir(parents=True, exist_ok=True)&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;REPORT_PATH.write_text(report, encoding=&amp;amp;quot;utf-8&amp;amp;quot;)&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;print(&amp;amp;quot;Validation results:&amp;amp;quot;)&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;print(pd.DataFrame(results).round(4).to_string(index=False))&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;print()&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;print(&amp;amp;quot;Best model:&amp;amp;quot;, best_model_name)&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;print(&amp;amp;quot;Test metrics:&amp;amp;quot;, format_metrics(test_metrics))&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;print(&amp;amp;quot;Report written to&amp;amp;quot;, REPORT_PATH)&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;/code&gt;&lt;p&gt;&lt;code&gt;if &lt;strong&gt;name&lt;/strong&gt; == “&lt;strong&gt;main&lt;/strong&gt;”:
main()
&lt;/code&gt;&lt;/p&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;&lt;/p&gt;
&lt;h2&gt;10. 运行 baseline&lt;/h2&gt;
&lt;p&gt;确认环境已激活：&lt;/p&gt;
&lt;div&gt;&lt;div&gt;&lt;span&gt;omp&lt;/span&gt;&lt;span&gt;fish&lt;/span&gt;&lt;/div&gt;&lt;pre&gt;&lt;code&gt;source .venv/bin/activate.fish
command -v python
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;运行脚本：&lt;/p&gt;
&lt;div&gt;&lt;div&gt;&lt;span&gt;omp&lt;/span&gt;&lt;span&gt;fish&lt;/span&gt;&lt;/div&gt;&lt;pre&gt;&lt;code&gt;python src/train_baselines.py
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;你应该看到类似输出：&lt;/p&gt;
&lt;div&gt;&lt;div&gt;&lt;span&gt;omp&lt;/span&gt;&lt;span&gt;prompt&lt;/span&gt;&lt;/div&gt;&lt;pre&gt;&lt;code&gt;Validation results:
              model  accuracy  precision  recall     f1  roc_auc
      random_forest    0.9649     0.9714  0.9714 0.9714   0.9921
logistic_regression    0.9561     0.9706  0.9429 0.9565   0.9954
                knn    0.9474     0.9701  0.9286 0.9489   0.9868
      decision_tree    0.9298     0.9444  0.9444 0.9444   0.9355
&lt;/code&gt;&lt;p&gt;&lt;code&gt;Best model: random_forest
Test metrics: {‘accuracy’: 0.9561, ‘precision’: 0.9459, ‘recall’: 0.9859, ‘f1’: 0.9655, ‘roc_auc’: 0.9898}
Report written to reports/week05_baseline_results.md
&lt;/code&gt;&lt;/p&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;&lt;/p&gt;
&lt;p&gt;具体数值可能因 sklearn 版本略有差异。重要的是：脚本能跑通，报告能生成，模型比较逻辑清楚。&lt;/p&gt;
&lt;h2&gt;11. 指标怎么读&lt;/h2&gt;
&lt;h3&gt;11.1 Accuracy&lt;/h3&gt;
&lt;div&gt;&lt;div&gt;&lt;span&gt;omp&lt;/span&gt;&lt;span&gt;prompt&lt;/span&gt;&lt;/div&gt;&lt;pre&gt;&lt;code&gt;accuracy = 预测正确的样本数 / 总样本数
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;优点：直观。缺点：类别不平衡时可能误导。例如 95% 都是负类，模型全预测负类也有 95% accuracy。&lt;/p&gt;
&lt;h3&gt;11.2 Precision&lt;/h3&gt;
&lt;div&gt;&lt;div&gt;&lt;span&gt;omp&lt;/span&gt;&lt;span&gt;prompt&lt;/span&gt;&lt;/div&gt;&lt;pre&gt;&lt;code&gt;precision = 预测为正类且真的为正类 / 所有预测为正类
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;问题意识：模型说“是”的时候，有多大概率真的“是”？&lt;/p&gt;
&lt;h3&gt;11.3 Recall&lt;/h3&gt;
&lt;div&gt;&lt;div&gt;&lt;span&gt;omp&lt;/span&gt;&lt;span&gt;prompt&lt;/span&gt;&lt;/div&gt;&lt;pre&gt;&lt;code&gt;recall = 预测为正类且真的为正类 / 所有真实正类
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;问题意识：真实正类里，模型抓住了多少？如果漏诊代价高，recall 很重要。&lt;/p&gt;
&lt;h3&gt;11.4 F1&lt;/h3&gt;
&lt;div&gt;&lt;div&gt;&lt;span&gt;omp&lt;/span&gt;&lt;span&gt;prompt&lt;/span&gt;&lt;/div&gt;&lt;pre&gt;&lt;code&gt;F1 = precision 和 recall 的调和平均
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;当你同时关心 precision 与 recall，可用 F1 做第一选择指标。&lt;/p&gt;
&lt;h3&gt;11.5 ROC AUC&lt;/h3&gt;
&lt;p&gt;ROC AUC 衡量模型把正类排在负类前面的能力。它不只看一个阈值，而是看不同阈值下的整体区分能力。&lt;/p&gt;
&lt;h2&gt;12. 为什么 Logistic Regression 和 KNN 要标准化&lt;/h2&gt;
&lt;p&gt;&lt;code&gt;StandardScaler()&lt;/code&gt; 会把每列特征变成大致均值 0、标准差 1。&lt;/p&gt;
&lt;p&gt;需要标准化的模型：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Logistic Regression：优化过程受特征尺度影响。&lt;/li&gt;
&lt;li&gt;KNN：距离计算会被大尺度特征支配。&lt;/li&gt;
&lt;li&gt;SVM：同样强烈依赖尺度。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;不一定需要标准化的模型：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Decision Tree。&lt;/li&gt;
&lt;li&gt;Random Forest。&lt;/li&gt;
&lt;li&gt;Gradient Boosting Tree。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;所以脚本里只给 Logistic Regression 和 KNN 放进 &lt;code&gt;Pipeline&lt;/code&gt;。&lt;/p&gt;
&lt;h2&gt;13. 怎样替换成自己的 CSV&lt;/h2&gt;
&lt;p&gt;假设你的数据是：&lt;/p&gt;
&lt;div&gt;&lt;div&gt;&lt;span&gt;omp&lt;/span&gt;&lt;span&gt;prompt&lt;/span&gt;&lt;/div&gt;&lt;pre&gt;&lt;code&gt;data/raw/my_dataset.csv
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;并且目标列叫 &lt;code&gt;target&lt;/code&gt;。可以把 &lt;code&gt;load_data()&lt;/code&gt; 改成：&lt;/p&gt;
&lt;div&gt;&lt;div&gt;&lt;span&gt;omp&lt;/span&gt;&lt;span&gt;python&lt;/span&gt;&lt;/div&gt;&lt;pre&gt;&lt;code&gt;def load_data():
    data = pd.read_csv(&quot;data/raw/my_dataset.csv&quot;)
    X = data.drop(columns=[&quot;target&quot;])
    y = data[&quot;target&quot;]
    target_names = sorted(y.astype(str).unique().tolist())
    return X, y, target_names
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;如果有类别特征，暂时不要手工把字符串硬改成数字。Week 06 会学习 &lt;code&gt;ColumnTransformer&lt;/code&gt; 和防泄漏 pipeline。Week 05 可以先选择全数值数据练手。&lt;/p&gt;
&lt;h2&gt;14. 报告怎么写&lt;/h2&gt;
&lt;p&gt;打开报告：&lt;/p&gt;
&lt;div&gt;&lt;div&gt;&lt;span&gt;omp&lt;/span&gt;&lt;span&gt;fish&lt;/span&gt;&lt;/div&gt;&lt;pre&gt;&lt;code&gt;code reports/week05_baseline_results.md
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;在 &lt;code&gt;Initial Error Analysis&lt;/code&gt; 下补充你的观察。不要只写：&lt;/p&gt;
&lt;div&gt;&lt;div&gt;&lt;span&gt;omp&lt;/span&gt;&lt;span&gt;prompt&lt;/span&gt;&lt;/div&gt;&lt;pre&gt;&lt;code&gt;Random forest is best.
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;应写成类似：&lt;/p&gt;
&lt;div&gt;&lt;div&gt;&lt;span&gt;omp&lt;/span&gt;&lt;span&gt;prompt&lt;/span&gt;&lt;/div&gt;&lt;pre&gt;&lt;code&gt;- Random forest 在 validation F1 上最高，但 Logistic Regression 的 ROC AUC 也很高，说明线性边界已经有不错区分能力。
- 如果任务重视漏检，recall 比 accuracy 更重要。当前最佳模型 test recall 高于 precision，错误更偏向 false positives。
- Validation 集只有一份，结果可能受随机划分影响。Week 06 需要用交叉验证确认排序是否稳定。
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;h2&gt;15. Git 保存成果&lt;/h2&gt;
&lt;p&gt;先看状态：&lt;/p&gt;
&lt;div&gt;&lt;div&gt;&lt;span&gt;omp&lt;/span&gt;&lt;span&gt;fish&lt;/span&gt;&lt;/div&gt;&lt;pre&gt;&lt;code&gt;git status --short
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;如果这是独立学习项目，还没有 Git 仓库：&lt;/p&gt;
&lt;div&gt;&lt;div&gt;&lt;span&gt;omp&lt;/span&gt;&lt;span&gt;fish&lt;/span&gt;&lt;/div&gt;&lt;pre&gt;&lt;code&gt;git init
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;创建 &lt;code&gt;.gitignore&lt;/code&gt; 可以用 VS Code，新建文件 &lt;code&gt;.gitignore&lt;/code&gt;，写入：&lt;/p&gt;
&lt;div&gt;&lt;div&gt;&lt;span&gt;omp&lt;/span&gt;&lt;span&gt;prompt&lt;/span&gt;&lt;/div&gt;&lt;pre&gt;&lt;code&gt;.venv/
__pycache__/
.ipynb_checkpoints/
data/raw/*.zip
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;提交：&lt;/p&gt;
&lt;div&gt;&lt;div&gt;&lt;span&gt;omp&lt;/span&gt;&lt;span&gt;fish&lt;/span&gt;&lt;/div&gt;&lt;pre&gt;&lt;code&gt;git add pyproject.toml uv.lock README.md src reports .gitignore
git commit -m &quot;Add week05 sklearn baseline training&quot;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;如果数据文件来自公开小数据，也可以提交；如果数据很大或有隐私，不要直接提交。&lt;/p&gt;
&lt;h2&gt;16. 本周练习&lt;/h2&gt;
&lt;ol&gt;
&lt;li&gt;把 &lt;code&gt;RANDOM_STATE&lt;/code&gt; 改成 7、21、100，观察 validation 排名是否稳定。&lt;/li&gt;
&lt;li&gt;把 &lt;code&gt;DecisionTreeClassifier(max_depth=4)&lt;/code&gt; 改成 &lt;code&gt;max_depth=2&lt;/code&gt; 和 &lt;code&gt;max_depth=None&lt;/code&gt;，比较过拟合迹象。&lt;/li&gt;
&lt;li&gt;把 KNN 的 &lt;code&gt;n_neighbors&lt;/code&gt; 改成 3、7、11，观察 F1 变化。&lt;/li&gt;
&lt;li&gt;在报告里加一节 &lt;code&gt;Metric Choice&lt;/code&gt;，说明为什么本任务不能只看 accuracy。&lt;/li&gt;
&lt;li&gt;换一个 sklearn 自带数据集或你自己的 CSV，复用同一训练流程。&lt;/li&gt;
&lt;/ol&gt;
&lt;h2&gt;17. 验收检查&lt;/h2&gt;
&lt;p&gt;在项目根目录执行：&lt;/p&gt;
&lt;div&gt;&lt;div&gt;&lt;span&gt;omp&lt;/span&gt;&lt;span&gt;fish&lt;/span&gt;&lt;/div&gt;&lt;pre&gt;&lt;code&gt;source .venv/bin/activate.fish
python src/train_baselines.py
test -f reports/week05_baseline_results.md; and echo &quot;report exists&quot;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;然后人工检查：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;src/train_baselines.py&lt;/code&gt; 存在。&lt;/li&gt;
&lt;li&gt;&lt;code&gt;reports/week05_baseline_results.md&lt;/code&gt; 存在。&lt;/li&gt;
&lt;li&gt;报告里有模型名称、指标、划分方式、结果表格、初步误差分析。&lt;/li&gt;
&lt;li&gt;代码里有固定的 &lt;code&gt;RANDOM_STATE&lt;/code&gt;。&lt;/li&gt;
&lt;li&gt;test set 没有参与模型选择，只在最后评估一次。&lt;/li&gt;
&lt;li&gt;Git 至少有一次提交。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;如果这些都满足，Week 05 合格。&lt;/p&gt;
&lt;h2&gt;18. 常见错误&lt;/h2&gt;
&lt;h3&gt;错误 1：fish 激活命令写错&lt;/h3&gt;
&lt;p&gt;现象：&lt;/p&gt;
&lt;div&gt;&lt;div&gt;&lt;span&gt;omp&lt;/span&gt;&lt;span&gt;prompt&lt;/span&gt;&lt;/div&gt;&lt;pre&gt;&lt;code&gt;Unsupported use of &apos;=&apos;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;原因：在 fish 里用了 Bash / Zsh 的激活脚本。&lt;/p&gt;
&lt;p&gt;修复：&lt;/p&gt;
&lt;div&gt;&lt;div&gt;&lt;span&gt;omp&lt;/span&gt;&lt;span&gt;fish&lt;/span&gt;&lt;/div&gt;&lt;pre&gt;&lt;code&gt;source .venv/bin/activate.fish
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;h3&gt;错误 2：&lt;code&gt;ModuleNotFoundError: No module named &apos;sklearn&apos;&lt;/code&gt;&lt;/h3&gt;
&lt;p&gt;原因通常是没有激活当前项目虚拟环境，或依赖安装在别的环境。&lt;/p&gt;
&lt;p&gt;检查：&lt;/p&gt;
&lt;div&gt;&lt;div&gt;&lt;span&gt;omp&lt;/span&gt;&lt;span&gt;fish&lt;/span&gt;&lt;/div&gt;&lt;pre&gt;&lt;code&gt;command -v python
uv pip list
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;修复：&lt;/p&gt;
&lt;div&gt;&lt;div&gt;&lt;span&gt;omp&lt;/span&gt;&lt;span&gt;fish&lt;/span&gt;&lt;/div&gt;&lt;pre&gt;&lt;code&gt;source .venv/bin/activate.fish
uv add scikit-learn
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;h3&gt;错误 3：忘记 &lt;code&gt;stratify=y&lt;/code&gt;&lt;/h3&gt;
&lt;p&gt;分类任务如果类别不平衡，某一份 split 可能类别比例异常，导致分数不稳定。&lt;/p&gt;
&lt;p&gt;修复：&lt;/p&gt;
&lt;div&gt;&lt;div&gt;&lt;span&gt;omp&lt;/span&gt;&lt;span&gt;python&lt;/span&gt;&lt;/div&gt;&lt;pre&gt;&lt;code&gt;train_test_split(X, y, stratify=y, random_state=RANDOM_STATE)
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;h3&gt;错误 4：在 test 上选模型&lt;/h3&gt;
&lt;p&gt;不要比较所有模型的 test 分数再选最高。这样 test 已经被你用来调参了。&lt;/p&gt;
&lt;p&gt;正确做法：validation 选模型，test 做最终确认。&lt;/p&gt;
&lt;h3&gt;错误 5：把 baseline 当成最终结论&lt;/h3&gt;
&lt;p&gt;Week 05 的 baseline 只是起点。它告诉你：如果后续特征工程、交叉验证、模型选择没有超过 baseline，就说明复杂度没有带来收益。&lt;/p&gt;
&lt;h2&gt;19. 下一步&lt;/h2&gt;
&lt;p&gt;进入 Week 06：&lt;a href=&quot;/post/ustc-stat-ai-quant-plan/&quot;&gt;交叉验证与模型选择&lt;/a&gt;。你会把本周的一次固定划分，升级为 K-fold cross validation、GridSearchCV 和防止数据泄漏的 Pipeline。&lt;/p&gt;
&lt;p&gt;plain &lt;/p&gt;&lt;/article&gt;&lt;p&gt;&lt;/p&gt;
  &lt;/div&gt;
&lt;/section&gt;</content:encoded><category>category:工具</category><category>category:机器学习</category><category>tag:sklearn</category><category>tag:baseline</category><category>tag:train-test-split</category><category>tag:classification-metrics</category></item><item><title>Week 04：可视化 + EDA 报告</title><link>https://39miku.space/post/week04-visualization-eda</link><guid isPermaLink="false">week04-visualization-eda</guid><description>Week 04：可视化 + EDA 报告。fish-first 终端教学，面向 CachyOS、VS Code、uv 和 Python 学习路线。</description><pubDate>Sat, 25 Jul 2026 20:00:00 GMT</pubDate><content:encoded>
&lt;section&gt;
  &lt;div&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;strong&gt;Oh My Pi&lt;/strong&gt; / weekly tutorial / week04-visualization-eda&lt;/span&gt;&lt;/div&gt;
  &lt;div&gt;
    &lt;div&gt;&lt;span&gt;miku@cachyos&lt;/span&gt;&lt;span&gt;:~/Code/python-learning&lt;/span&gt;&lt;span&gt;$&lt;/span&gt; &lt;span&gt;omp teach week04-visualization-eda --fish-first --step-by-step&lt;/span&gt;&lt;/div&gt;
    &lt;div&gt;
      &lt;div&gt;&lt;span&gt;source&lt;/span&gt;&lt;strong&gt;713 行教学文档&lt;/strong&gt;&lt;/div&gt;
      &lt;div&gt;&lt;span&gt;week&lt;/span&gt;&lt;strong&gt;Week 04&lt;/strong&gt;&lt;/div&gt;
      &lt;div&gt;&lt;span&gt;shell&lt;/span&gt;&lt;strong&gt;fish-first 命令版&lt;/strong&gt;&lt;/div&gt;
      &lt;div&gt;&lt;span&gt;backlink&lt;/span&gt;&lt;strong&gt;&lt;a href=&quot;/post/ustc-stat-ai-quant-plan/&quot;&gt;20 周计划&lt;/a&gt;&lt;/strong&gt;&lt;/div&gt;
    &lt;/div&gt;
    &lt;article&gt;
&lt;h1&gt;Week 04：可视化 + EDA 报告&lt;/h1&gt;
&lt;blockquote&gt;
&lt;p&gt;返回主线计划：&lt;a href=&quot;/post/ustc-stat-ai-quant-plan/&quot;&gt;USTC 统计学 AI / 量化 20 周成长计划&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;本文命令默认使用 &lt;strong&gt;fish shell&lt;/strong&gt;。本周目标不是“画很多图”，而是学会用图表支持数据观察，并把观察写成一份结构清楚、可复现、不过度解释的 EDA 报告。&lt;/p&gt;
&lt;p&gt;&lt;a&gt;&lt;/a&gt;&lt;/p&gt;
&lt;h2&gt;0. 本周详细教学：语法、规范、验收&lt;/h2&gt;
&lt;blockquote&gt;
&lt;p&gt;本节不是追加在尾部的复习，而是本周正文的入口。先读这里，再做后面的命令和项目。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h3&gt;0.1 本周真正要学会什么&lt;/h3&gt;
&lt;table&gt;
&lt;thead&gt;&lt;tr&gt;&lt;th&gt;维度&lt;/th&gt;&lt;th&gt;要求&lt;/th&gt;&lt;/tr&gt;&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;&lt;td&gt;知识点&lt;/td&gt;&lt;td&gt;matplotlib、分布、箱线图、相关性、报告叙事&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;代码语法&lt;/td&gt;&lt;td&gt;能从空文件写出本周核心脚本，而不是只复制运行&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;程序规范&lt;/td&gt;&lt;td&gt;函数拆分、路径清楚、输入输出明确、错误能解释&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;交付物&lt;/td&gt;&lt;td&gt;&lt;code&gt;reports/week04_eda_report.md&lt;/code&gt;&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;验收方式&lt;/td&gt;&lt;td&gt;从 fish 终端运行命令，得到可复查的文件或指标&lt;/td&gt;&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;h3&gt;0.2 代码语法精讲&lt;/h3&gt;
&lt;p&gt;下面的代码不是最终答案，而是本周必须理解的最小骨架：&lt;/p&gt;
&lt;div&gt;&lt;div&gt;&lt;span&gt;omp&lt;/span&gt;&lt;span&gt;python&lt;/span&gt;&lt;/div&gt;&lt;pre&gt;&lt;code&gt;import matplotlib.pyplot as plt
&lt;/code&gt;&lt;p&gt;&lt;code&gt;fig, ax = plt.subplots(figsize=(8, 4))
df[“value”].hist(ax=ax, bins=30)
ax.set_title(“Value distribution”)
ax.set_xlabel(“value”)
ax.set_ylabel(“count”)
fig.tight_layout()
fig.savefig(“figures/value_distribution.png”, dpi=160)
&lt;/code&gt;&lt;/p&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;&lt;/p&gt;
&lt;p&gt;读代码时按四步检查：输入从哪里来；中间变量的类型和 shape 是什么；函数或脚本输出什么；哪些错误应该显式报出来。&lt;/p&gt;
&lt;h3&gt;0.3 本周程序规范&lt;/h3&gt;
&lt;ul&gt;&lt;li&gt;所有路径用相对路径或 `pathlib.Path`，不要写死 `/home/miku/...`。&lt;/li&gt;&lt;li&gt;核心逻辑进 `src/`，notebook 只做探索和解释。&lt;/li&gt;&lt;li&gt;每个脚本能从 fish 终端运行，并在 README 写出命令。&lt;/li&gt;&lt;li&gt;输出必须落盘到 `reports/`、`figures/` 或 `outputs/`，不能只在屏幕上看。&lt;/li&gt;&lt;/ul&gt;
&lt;h3&gt;0.4 本周练习分层&lt;/h3&gt;
&lt;table&gt;
&lt;thead&gt;&lt;tr&gt;&lt;th&gt;层级&lt;/th&gt;&lt;th&gt;任务&lt;/th&gt;&lt;th&gt;不合格表现&lt;/th&gt;&lt;th&gt;合格验收&lt;/th&gt;&lt;/tr&gt;&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;&lt;td&gt;最小练习&lt;/td&gt;&lt;td&gt;手写上面的最小骨架&lt;/td&gt;&lt;td&gt;只在 notebook 里运行&lt;/td&gt;&lt;td&gt;终端运行成功&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;标准练习&lt;/td&gt;&lt;td&gt;把逻辑拆成函数/模块&lt;/td&gt;&lt;td&gt;一个大脚本从头写到尾&lt;/td&gt;&lt;td&gt;至少 2 个函数，职责清楚&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;项目练习&lt;/td&gt;&lt;td&gt;生成本周交付物 &lt;code&gt;reports/week04_eda_report.md&lt;/code&gt;&lt;/td&gt;&lt;td&gt;只有屏幕输出&lt;/td&gt;&lt;td&gt;文件落盘，可复查&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;复盘练习&lt;/td&gt;&lt;td&gt;写 3 个错误和修复&lt;/td&gt;&lt;td&gt;只写“已解决”&lt;/td&gt;&lt;td&gt;写清报错、原因、修复、预防&lt;/td&gt;&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;h3&gt;0.5 本周和主线的连接&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;回到总计划：&lt;a href=&quot;/post/ustc-stat-ai-quant-plan/#week-plan&quot;&gt;USTC AI / Quant 练习手册&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;查详细练习索引：&lt;a href=&quot;/post/ustc-stat-ai-quant-plan/#deep-practice-appendix&quot;&gt;技术练习详解&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;查质量评分：&lt;a href=&quot;/post/ustc-stat-ai-quant-plan/#quality-final-gates&quot;&gt;最终质量门槛&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;1. 本周目标&lt;/h2&gt;
&lt;p&gt;完成 Week 04 后，你应该能：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;使用 matplotlib 绘制直方图、散点图、箱线图。&lt;/li&gt;
&lt;li&gt;在安装 seaborn 时使用 seaborn 画更省力的统计图。&lt;/li&gt;
&lt;li&gt;把图保存到 &lt;code&gt;figures/*.png&lt;/code&gt;，而不是只显示在 notebook 输出里。&lt;/li&gt;
&lt;li&gt;建立 EDA 报告结构：数据来源、字段说明、缺失值、主要分布、相关性观察、可能建模目标。&lt;/li&gt;
&lt;li&gt;理解 notebook 和 script 的分工：探索可以用 notebook，最终结果要能用脚本复现。&lt;/li&gt;
&lt;/ol&gt;
&lt;h2&gt;文件布局&lt;/h2&gt;
&lt;p&gt;建议本周交付物：&lt;/p&gt;
&lt;div&gt;&lt;div&gt;&lt;span&gt;omp&lt;/span&gt;&lt;span&gt;prompt&lt;/span&gt;&lt;/div&gt;&lt;pre&gt;&lt;code&gt;week04-visualization-eda/
├── data/
│   ├── raw/
│   │   └── student_scores.csv
│   └── processed/
│       └── student_scores_clean.csv
├── figures/
│   ├── average_score_hist.png
│   ├── study_hours_vs_average_score.png
│   ├── average_score_by_major.png
│   └── correlation_heatmap.png
├── notebooks/
│   └── week04_visualization_eda.ipynb
├── reports/
│   └── week04_eda_report.md
├── src/
│   ├── prepare_data.py
│   └── make_figures.py
├── .venv/
├── pyproject.toml
└── README.md
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;h2&gt;2. 前置要求&lt;/h2&gt;
&lt;p&gt;你需要已经会：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;用 pandas 读取 CSV。&lt;/li&gt;
&lt;li&gt;检查缺失值和描述统计。&lt;/li&gt;
&lt;li&gt;使用 &lt;code&gt;uv run python ...&lt;/code&gt; 运行脚本。&lt;/li&gt;
&lt;li&gt;在 VS Code 中编辑 &lt;code&gt;.py&lt;/code&gt; 和 &lt;code&gt;.md&lt;/code&gt; 文件。&lt;/li&gt;
&lt;li&gt;使用 fish 激活虚拟环境。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;如果 Week 03 的项目还在，也可以直接复制 Week 03 的清洗后数据到 Week 04 项目。为了教程完整，下面从新项目开始。&lt;/p&gt;
&lt;h2&gt;3. 创建 Week 04 项目&lt;/h2&gt;
&lt;div&gt;&lt;div&gt;&lt;span&gt;omp&lt;/span&gt;&lt;span&gt;fish&lt;/span&gt;&lt;/div&gt;&lt;pre&gt;&lt;code&gt;mkdir -p ~/Code/python-learning/week04-visualization-eda
cd ~/Code/python-learning/week04-visualization-eda
mkdir -p src data/raw data/processed figures notebooks reports
uv init
uv venv
source .venv/bin/activate.fish
uv add numpy pandas matplotlib
code .
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;解释：&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;命令&lt;/th&gt;
&lt;th&gt;作用&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;&lt;tr&gt;
&lt;td&gt;&lt;code&gt;mkdir -p ...&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;创建本周项目目录和子目录&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;uv init&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;初始化项目配置&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;uv venv&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;创建 &lt;code&gt;.venv&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;source .venv/bin/activate.fish&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;fish 中激活虚拟环境&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;uv add numpy pandas matplotlib&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;安装数据处理和绘图库&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;code .&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;用 VS Code 打开项目&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;&lt;/table&gt;
&lt;p&gt;检查安装：&lt;/p&gt;
&lt;div&gt;&lt;div&gt;&lt;span&gt;omp&lt;/span&gt;&lt;span&gt;fish&lt;/span&gt;&lt;/div&gt;&lt;pre&gt;&lt;code&gt;uv run python -c &quot;import pandas, matplotlib; print(&apos;ok&apos;)&quot;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;如果你想使用 seaborn，可以额外安装：&lt;/p&gt;
&lt;div&gt;&lt;div&gt;&lt;span&gt;omp&lt;/span&gt;&lt;span&gt;fish&lt;/span&gt;&lt;/div&gt;&lt;pre&gt;&lt;code&gt;uv add seaborn
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;本教程会给出 matplotlib 主线；seaborn 是可选增强。&lt;/p&gt;
&lt;h2&gt;4. 准备数据&lt;/h2&gt;
&lt;p&gt;创建原始数据：&lt;/p&gt;
&lt;div&gt;&lt;div&gt;&lt;span&gt;omp&lt;/span&gt;&lt;span&gt;fish&lt;/span&gt;&lt;/div&gt;&lt;pre&gt;&lt;code&gt;printf &apos;student,major,gender,math,english,python,study_hours\nA,statistics,F,88,79,85,12\nB,statistics,M,92,81,90,14\nC,finance,F,75,86,78,9\nD,finance,M,80,,82,8\nE,statistics,F,95,88,,15\nF,computer,M,70,76,88,10\nG,computer,F,,90,91,11\nH,statistics,M,84,82,86,13\nI,finance,F,78,84,80,9\nJ,computer,M,73,78,84,10\nK,statistics,F,90,85,92,14\nL,finance,M,82,80,79,7\n&apos; &amp;gt; data/raw/student_scores.csv
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;检查：&lt;/p&gt;
&lt;div&gt;&lt;div&gt;&lt;span&gt;omp&lt;/span&gt;&lt;span&gt;fish&lt;/span&gt;&lt;/div&gt;&lt;pre&gt;&lt;code&gt;head data/raw/student_scores.csv
wc -l data/raw/student_scores.csv
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;这里仍然是练习数据，但比 Week 03 多几行，方便画图。&lt;/p&gt;
&lt;h2&gt;5. 写数据准备脚本&lt;/h2&gt;
&lt;p&gt;创建 &lt;code&gt;src/prepare_data.py&lt;/code&gt;：&lt;/p&gt;
&lt;div&gt;&lt;div&gt;&lt;span&gt;omp&lt;/span&gt;&lt;span&gt;python&lt;/span&gt;&lt;/div&gt;&lt;pre&gt;&lt;code&gt;from pathlib import Path
&lt;p&gt;import pandas as pd&lt;/p&gt;
&lt;p&gt;RAW_PATH = Path(“data/raw/student_scores.csv”)
OUTPUT_PATH = Path(“data/processed/student_scores_clean.csv”)
NUMERIC_COLUMNS = [“math”, “english”, “python”, “study_hours”]
SCORE_COLUMNS = [“math”, “english”, “python”]&lt;/p&gt;
&lt;p&gt;def load_data(path: Path) -&amp;gt; pd.DataFrame:
return pd.read_csv(path)&lt;/p&gt;
&lt;p&gt;def clean_data(df: pd.DataFrame) -&amp;gt; pd.DataFrame:
cleaned = df.copy()&lt;/p&gt;
&lt;p&gt;plain for column in NUMERIC_COLUMNS:
cleaned[column] = pd.to_numeric(cleaned[column], errors=“coerce”)
cleaned[column] = cleaned[column].fillna(cleaned[column].median())&lt;/p&gt;
&lt;p&gt;plain cleaned[“average_score”] = cleaned[SCORE_COLUMNS].mean(axis=1)
return cleaned&lt;/p&gt;
&lt;p&gt;def save_data(df: pd.DataFrame, path: Path) -&amp;gt; None:
path.parent.mkdir(parents=True, exist_ok=True)
df.to_csv(path, index=False)&lt;/p&gt;
&lt;p&gt;def main() -&amp;gt; None:
raw = load_data(RAW_PATH)
cleaned = clean_data(raw)
save_data(cleaned, OUTPUT_PATH)
print(f”Rows: {len(cleaned)}”)
print(f”Columns: {len(cleaned.columns)}”)
print(f”Saved: {OUTPUT_PATH}”)&lt;/p&gt;
&lt;/code&gt;&lt;p&gt;&lt;code&gt;if &lt;strong&gt;name&lt;/strong&gt; == “&lt;strong&gt;main&lt;/strong&gt;”:
main()
&lt;/code&gt;&lt;/p&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;&lt;/p&gt;
&lt;p&gt;运行：&lt;/p&gt;
&lt;div&gt;&lt;div&gt;&lt;span&gt;omp&lt;/span&gt;&lt;span&gt;fish&lt;/span&gt;&lt;/div&gt;&lt;pre&gt;&lt;code&gt;uv run python src/prepare_data.py
ls data/processed
head data/processed/student_scores_clean.csv
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;这一步保证后续画图总是使用同一份清洗后数据。&lt;/p&gt;
&lt;h2&gt;6. matplotlib 基础概念&lt;/h2&gt;
&lt;p&gt;matplotlib 的核心是 &lt;code&gt;figure&lt;/code&gt; 和 &lt;code&gt;axes&lt;/code&gt;：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;figure&lt;/code&gt;：整张画布。&lt;/li&gt;
&lt;li&gt;&lt;code&gt;axes&lt;/code&gt;：画布上的一个坐标系，真正画图的位置。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;常用模板：&lt;/p&gt;
&lt;div&gt;&lt;div&gt;&lt;span&gt;omp&lt;/span&gt;&lt;span&gt;python&lt;/span&gt;&lt;/div&gt;&lt;pre&gt;&lt;code&gt;import matplotlib.pyplot as plt
&lt;/code&gt;&lt;p&gt;&lt;code&gt;fig, ax = plt.subplots(figsize=(8, 5))
ax.hist(values, bins=10)
ax.set_title(“Title”)
ax.set_xlabel(“X label”)
ax.set_ylabel(“Y label”)
fig.tight_layout()
fig.savefig(“figures/example.png”, dpi=150)
plt.close(fig)
&lt;/code&gt;&lt;/p&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;&lt;/p&gt;
&lt;p&gt;为什么要 &lt;code&gt;plt.close(fig)&lt;/code&gt;：脚本批量生成多张图时，关闭旧图可以避免内存和状态混乱。&lt;/p&gt;
&lt;h2&gt;7. 写可复现画图脚本&lt;/h2&gt;
&lt;p&gt;创建 &lt;code&gt;src/make_figures.py&lt;/code&gt;：&lt;/p&gt;
&lt;div&gt;&lt;div&gt;&lt;span&gt;omp&lt;/span&gt;&lt;span&gt;python&lt;/span&gt;&lt;/div&gt;&lt;pre&gt;&lt;code&gt;from pathlib import Path
&lt;p&gt;import matplotlib.pyplot as plt
import pandas as pd&lt;/p&gt;
&lt;p&gt;DATA_PATH = Path(“data/processed/student_scores_clean.csv”)
FIGURE_DIR = Path(“figures”)
SCORE_COLUMNS = [“math”, “english”, “python”, “average_score”]&lt;/p&gt;
&lt;p&gt;def load_data(path: Path) -&amp;gt; pd.DataFrame:
return pd.read_csv(path)&lt;/p&gt;
&lt;p&gt;def save_current_figure(fig, filename: str) -&amp;gt; None:
FIGURE_DIR.mkdir(parents=True, exist_ok=True)
output_path = FIGURE_DIR / filename
fig.tight_layout()
fig.savefig(output_path, dpi=150)
plt.close(fig)
print(f”Saved {output_path}”)&lt;/p&gt;
&lt;p&gt;def plot_average_score_hist(df: pd.DataFrame) -&amp;gt; None:
fig, ax = plt.subplots(figsize=(8, 5))
ax.hist(df[“average_score”], bins=8, edgecolor=“black”)
ax.set_title(“Distribution of Average Score”)
ax.set_xlabel(“Average score”)
ax.set_ylabel(“Student count”)
save_current_figure(fig, “average_score_hist.png”)&lt;/p&gt;
&lt;p&gt;def plot_study_hours_scatter(df: pd.DataFrame) -&amp;gt; None:
fig, ax = plt.subplots(figsize=(8, 5))
ax.scatter(df[“study_hours”], df[“average_score”])
ax.set_title(“Study Hours vs Average Score”)
ax.set_xlabel(“Study hours per week”)
ax.set_ylabel(“Average score”)
save_current_figure(fig, “study_hours_vs_average_score.png”)&lt;/p&gt;
&lt;p&gt;def plot_major_boxplot(df: pd.DataFrame) -&amp;gt; None:
majors = sorted(df[“major”].unique())
values = [df.loc[df[“major”] == major, “average_score”] for major in majors]&lt;/p&gt;
&lt;p&gt;plain fig, ax = plt.subplots(figsize=(8, 5))
ax.boxplot(values, labels=majors)
ax.set_title(“Average Score by Major”)
ax.set_xlabel(“Major”)
ax.set_ylabel(“Average score”)
save_current_figure(fig, “average_score_by_major.png”)&lt;/p&gt;
&lt;p&gt;def plot_correlation_heatmap(df: pd.DataFrame) -&amp;gt; None:
corr = df[SCORE_COLUMNS + [“study_hours”]].corr()&lt;/p&gt;
&lt;p&gt;plain fig, ax = plt.subplots(figsize=(7, 6))
image = ax.imshow(corr, cmap=“coolwarm”, vmin=-1, vmax=1)&lt;/p&gt;
&lt;p&gt;plain ax.set_xticks(range(len(corr.columns)))
ax.set_yticks(range(len(corr.index)))
ax.set_xticklabels(corr.columns, rotation=45, ha=“right”)
ax.set_yticklabels(corr.index)
ax.set_title(“Correlation Heatmap”)&lt;/p&gt;
&lt;p&gt;plain for row in range(len(corr.index)):
for col in range(len(corr.columns)):
ax.text(col, row, f”{corr.iloc[row, col]:.2f}”, ha=“center”, va=“center”, color=“black”)&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;fig.colorbar(image, ax=ax)&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;save_current_figure(fig, &amp;amp;quot;correlation_heatmap.png&amp;amp;quot;)&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;def main() -&amp;gt; None:
df = load_data(DATA_PATH)
plot_average_score_hist(df)
plot_study_hours_scatter(df)
plot_major_boxplot(df)
plot_correlation_heatmap(df)&lt;/p&gt;
&lt;/code&gt;&lt;p&gt;&lt;code&gt;if &lt;strong&gt;name&lt;/strong&gt; == “&lt;strong&gt;main&lt;/strong&gt;”:
main()
&lt;/code&gt;&lt;/p&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;&lt;/p&gt;
&lt;p&gt;运行完整流程：&lt;/p&gt;
&lt;div&gt;&lt;div&gt;&lt;span&gt;omp&lt;/span&gt;&lt;span&gt;fish&lt;/span&gt;&lt;/div&gt;&lt;pre&gt;&lt;code&gt;uv run python src/prepare_data.py
uv run python src/make_figures.py
ls figures
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;你应该看到：&lt;/p&gt;
&lt;div&gt;&lt;div&gt;&lt;span&gt;omp&lt;/span&gt;&lt;span&gt;prompt&lt;/span&gt;&lt;/div&gt;&lt;pre&gt;&lt;code&gt;average_score_by_major.png
average_score_hist.png
correlation_heatmap.png
study_hours_vs_average_score.png
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;h2&gt;8. 图表一：直方图看分布&lt;/h2&gt;
&lt;p&gt;直方图回答的问题是：一个数值变量大致集中在哪里？是否偏态？是否有异常值？&lt;/p&gt;
&lt;p&gt;本教程中的直方图：&lt;/p&gt;
&lt;div&gt;&lt;div&gt;&lt;span&gt;omp&lt;/span&gt;&lt;span&gt;python&lt;/span&gt;&lt;/div&gt;&lt;pre&gt;&lt;code&gt;ax.hist(df[&quot;average_score&quot;], bins=8, edgecolor=&quot;black&quot;)
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;解释：&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;参数&lt;/th&gt;
&lt;th&gt;含义&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;&lt;tr&gt;
&lt;td&gt;&lt;code&gt;df[&quot;average_score&quot;]&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;要观察分布的变量&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;bins=8&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;分成 8 个区间&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;edgecolor=&quot;black&quot;&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;给柱子加边框，方便阅读&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;&lt;/table&gt;
&lt;p&gt;报告中不要只写“画了直方图”，要写观察：&lt;/p&gt;
&lt;div&gt;&lt;div&gt;&lt;span&gt;omp&lt;/span&gt;&lt;span&gt;prompt&lt;/span&gt;&lt;/div&gt;&lt;pre&gt;&lt;code&gt;平均分主要集中在 80 到 90 分之间，样本中没有特别低的平均分。由于样本量很小，这个分布只能用于流程练习，不能代表真实学生群体。
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;h2&gt;9. 图表二：散点图看两个变量关系&lt;/h2&gt;
&lt;p&gt;散点图回答的问题是：两个数值变量之间是否有明显关系？&lt;/p&gt;
&lt;p&gt;本教程中：&lt;/p&gt;
&lt;div&gt;&lt;div&gt;&lt;span&gt;omp&lt;/span&gt;&lt;span&gt;python&lt;/span&gt;&lt;/div&gt;&lt;pre&gt;&lt;code&gt;ax.scatter(df[&quot;study_hours&quot;], df[&quot;average_score&quot;])
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;横轴是学习时长，纵轴是平均分。&lt;/p&gt;
&lt;p&gt;报告写法示例：&lt;/p&gt;
&lt;div&gt;&lt;div&gt;&lt;span&gt;omp&lt;/span&gt;&lt;span&gt;prompt&lt;/span&gt;&lt;/div&gt;&lt;pre&gt;&lt;code&gt;散点图显示学习时长和平均分大致同向变化：学习时长较高的学生平均分往往也较高。但样本只有 12 行，不能据此得出稳定因果结论。
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;注意“相关不等于因果”。统计学同学尤其要避免把 EDA 观察写成因果判断。&lt;/p&gt;
&lt;h2&gt;10. 图表三：箱线图看组间差异&lt;/h2&gt;
&lt;p&gt;箱线图适合比较不同组的数值分布。这里按 &lt;code&gt;major&lt;/code&gt; 比较 &lt;code&gt;average_score&lt;/code&gt;。&lt;/p&gt;
&lt;p&gt;代码核心：&lt;/p&gt;
&lt;div&gt;&lt;div&gt;&lt;span&gt;omp&lt;/span&gt;&lt;span&gt;python&lt;/span&gt;&lt;/div&gt;&lt;pre&gt;&lt;code&gt;majors = sorted(df[&quot;major&quot;].unique())
values = [df.loc[df[&quot;major&quot;] == major, &quot;average_score&quot;] for major in majors]
ax.boxplot(values, labels=majors)
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;报告中可以写：&lt;/p&gt;
&lt;div&gt;&lt;div&gt;&lt;span&gt;omp&lt;/span&gt;&lt;span&gt;prompt&lt;/span&gt;&lt;/div&gt;&lt;pre&gt;&lt;code&gt;statistics 组平均分中位数较高，computer 和 finance 组略低。但每组样本数量很少，箱线图主要用于展示方法，不适合做群体差异结论。
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;真实项目中还应补充每组样本数，否则箱线图容易误导。&lt;/p&gt;
&lt;h2&gt;11. 图表四：相关性热力图&lt;/h2&gt;
&lt;p&gt;相关性热力图用来观察多个数值变量之间的线性相关关系。&lt;/p&gt;
&lt;p&gt;核心代码：&lt;/p&gt;
&lt;div&gt;&lt;div&gt;&lt;span&gt;omp&lt;/span&gt;&lt;span&gt;python&lt;/span&gt;&lt;/div&gt;&lt;pre&gt;&lt;code&gt;corr = df[[&quot;math&quot;, &quot;english&quot;, &quot;python&quot;, &quot;average_score&quot;, &quot;study_hours&quot;]].corr()
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;&lt;code&gt;corr()&lt;/code&gt; 默认计算 Pearson 相关系数，范围从 -1 到 1：&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;值&lt;/th&gt;
&lt;th&gt;含义&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;&lt;tr&gt;
&lt;td&gt;接近 1&lt;/td&gt;
&lt;td&gt;强正相关&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;接近 -1&lt;/td&gt;
&lt;td&gt;强负相关&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;接近 0&lt;/td&gt;
&lt;td&gt;线性相关较弱&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;&lt;/table&gt;
&lt;p&gt;注意：&lt;code&gt;average_score&lt;/code&gt; 本来就是由 &lt;code&gt;math&lt;/code&gt;、&lt;code&gt;english&lt;/code&gt;、&lt;code&gt;python&lt;/code&gt; 算出来的，所以它和各科成绩相关很正常。这不是新的发现，而是变量定义带来的结果。&lt;/p&gt;
&lt;h2&gt;12. seaborn 可选增强&lt;/h2&gt;
&lt;p&gt;如果已经安装 seaborn：&lt;/p&gt;
&lt;div&gt;&lt;div&gt;&lt;span&gt;omp&lt;/span&gt;&lt;span&gt;fish&lt;/span&gt;&lt;/div&gt;&lt;pre&gt;&lt;code&gt;uv add seaborn
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;可以用更少代码画热力图：&lt;/p&gt;
&lt;div&gt;&lt;div&gt;&lt;span&gt;omp&lt;/span&gt;&lt;span&gt;python&lt;/span&gt;&lt;/div&gt;&lt;pre&gt;&lt;code&gt;import seaborn as sns
&lt;/code&gt;&lt;p&gt;&lt;code&gt;corr = df[[“math”, “english”, “python”, “average_score”, “study_hours”]].corr()
fig, ax = plt.subplots(figsize=(7, 6))
sns.heatmap(corr, annot=True, cmap=“coolwarm”, vmin=-1, vmax=1, ax=ax)
fig.tight_layout()
fig.savefig(“figures/correlation_heatmap_seaborn.png”, dpi=150)
plt.close(fig)
&lt;/code&gt;&lt;/p&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;&lt;/p&gt;
&lt;p&gt;但本周主线仍应掌握 matplotlib，因为它是 Python 可视化生态的底层基础。seaborn 很好用，但不应成为你完全不理解坐标轴、标题、标签、保存图片的理由。&lt;/p&gt;
&lt;h2&gt;13. 保存图像的规范&lt;/h2&gt;
&lt;p&gt;建议统一把图放在 &lt;code&gt;figures/&lt;/code&gt;：&lt;/p&gt;
&lt;div&gt;&lt;div&gt;&lt;span&gt;omp&lt;/span&gt;&lt;span&gt;fish&lt;/span&gt;&lt;/div&gt;&lt;pre&gt;&lt;code&gt;ls figures
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;命名建议：&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;文件名&lt;/th&gt;
&lt;th&gt;含义&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;&lt;tr&gt;
&lt;td&gt;&lt;code&gt;average_score_hist.png&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;平均分直方图&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;study_hours_vs_average_score.png&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;学习时长与平均分散点图&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;average_score_by_major.png&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;不同专业平均分箱线图&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;correlation_heatmap.png&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;数值变量相关性热力图&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;&lt;/table&gt;
&lt;p&gt;不要用：&lt;/p&gt;
&lt;div&gt;&lt;div&gt;&lt;span&gt;omp&lt;/span&gt;&lt;span&gt;prompt&lt;/span&gt;&lt;/div&gt;&lt;pre&gt;&lt;code&gt;1.png
new.png
final_final.png
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;图片文件名应该让读者不用打开也能猜到内容。&lt;/p&gt;
&lt;h2&gt;14. EDA 报告结构&lt;/h2&gt;
&lt;p&gt;创建 &lt;code&gt;reports/week04_eda_report.md&lt;/code&gt;，建议结构如下：&lt;/p&gt;
&lt;div&gt;&lt;div&gt;&lt;span&gt;omp&lt;/span&gt;&lt;span&gt;markdown&lt;/span&gt;&lt;/div&gt;&lt;pre&gt;&lt;code&gt;# Week 04 EDA 报告
&lt;h2&gt;1. 数据来源&lt;a href=&quot;#1-数据来源&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;说明数据来自本周练习 CSV，包含学生专业、性别、三科成绩和每周学习时长。&lt;/p&gt;
&lt;h2&gt;2. 字段说明&lt;a href=&quot;#2-字段说明&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;


















































&lt;table&gt;&lt;thead&gt;&lt;tr&gt;&lt;th&gt;字段&lt;/th&gt;&lt;th&gt;含义&lt;/th&gt;&lt;th&gt;类型&lt;/th&gt;&lt;/tr&gt;&lt;/thead&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td&gt;student&lt;/td&gt;&lt;td&gt;学生编号&lt;/td&gt;&lt;td&gt;分类&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;major&lt;/td&gt;&lt;td&gt;专业&lt;/td&gt;&lt;td&gt;分类&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;gender&lt;/td&gt;&lt;td&gt;性别&lt;/td&gt;&lt;td&gt;分类&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;math&lt;/td&gt;&lt;td&gt;数学成绩&lt;/td&gt;&lt;td&gt;数值&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;english&lt;/td&gt;&lt;td&gt;英语成绩&lt;/td&gt;&lt;td&gt;数值&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;python&lt;/td&gt;&lt;td&gt;Python 成绩&lt;/td&gt;&lt;td&gt;数值&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;study_hours&lt;/td&gt;&lt;td&gt;每周学习时长&lt;/td&gt;&lt;td&gt;数值&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;average_score&lt;/td&gt;&lt;td&gt;三科平均分&lt;/td&gt;&lt;td&gt;数值&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;
&lt;h2&gt;3. 缺失值情况&lt;a href=&quot;#3-缺失值情况&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;说明原始数据中哪些列有缺失值，以及使用中位数填补数值列。&lt;/p&gt;
&lt;h2&gt;4. 主要分布&lt;a href=&quot;#4-主要分布&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;引用 figures/average_score_hist.png，描述平均分集中区间和异常值情况。&lt;/p&gt;
&lt;h2&gt;5. 分组观察&lt;a href=&quot;#5-分组观察&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;引用 figures/average_score_by_major.png，描述不同专业组的平均分分布，并说明样本量限制。&lt;/p&gt;
&lt;h2&gt;6. 相关性观察&lt;a href=&quot;#6-相关性观察&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;引用 figures/study_hours_vs_average_score.png 和 figures/correlation_heatmap.png，描述学习时长与平均分、各科成绩之间的相关性。&lt;/p&gt;
&lt;h2&gt;7. 可能的建模目标&lt;a href=&quot;#7-可能的建模目标&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;可以提出一个后续建模问题，例如：根据学习时长、专业和前两科成绩预测 Python 成绩，或根据特征判断 average_score 是否高于 85。&lt;/p&gt;
&lt;h2&gt;8. 局限性&lt;a href=&quot;#8-局限性&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;
&lt;/code&gt;&lt;p&gt;&lt;code&gt;说明样本量小、数据为练习数据、不能代表真实 USTC 学生群体。
&lt;/code&gt;&lt;/p&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;&lt;/p&gt;
&lt;p&gt;报告必须用文字解释图，而不是只把图片贴进去。&lt;/p&gt;
&lt;p&gt;Markdown 引用图片写法：&lt;/p&gt;
&lt;div&gt;&lt;div&gt;&lt;span&gt;omp&lt;/span&gt;&lt;span&gt;markdown&lt;/span&gt;&lt;/div&gt;&lt;pre&gt;&lt;code&gt;![平均分直方图](../figures/average_score_hist.png)
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;如果报告和 &lt;code&gt;figures&lt;/code&gt; 都在项目根目录下的不同子目录，上面这种 &lt;code&gt;../figures/...&lt;/code&gt; 路径是合理的：从 &lt;code&gt;reports/&lt;/code&gt; 回到上一级，再进入 &lt;code&gt;figures/&lt;/code&gt;。&lt;/p&gt;
&lt;h2&gt;15. Notebook vs script：本周更要分清&lt;/h2&gt;
&lt;p&gt;Notebook 的优点：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;适合一步步看数据。&lt;/li&gt;
&lt;li&gt;适合临时尝试不同图表。&lt;/li&gt;
&lt;li&gt;适合在同一页面写图和解释。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Script 的优点：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;能一键复现所有图片。&lt;/li&gt;
&lt;li&gt;Git diff 更清楚。&lt;/li&gt;
&lt;li&gt;不依赖手动运行某个单元格。&lt;/li&gt;
&lt;li&gt;适合以后放到作品集。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;推荐流程：&lt;/p&gt;
&lt;div&gt;&lt;div&gt;&lt;span&gt;omp&lt;/span&gt;&lt;span&gt;prompt&lt;/span&gt;&lt;/div&gt;&lt;pre&gt;&lt;code&gt;notebooks/week04_visualization_eda.ipynb：探索
src/prepare_data.py：固定清洗流程
src/make_figures.py：固定画图流程
reports/week04_eda_report.md：写最终解释
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;如果使用 notebook：&lt;/p&gt;
&lt;div&gt;&lt;div&gt;&lt;span&gt;omp&lt;/span&gt;&lt;span&gt;fish&lt;/span&gt;&lt;/div&gt;&lt;pre&gt;&lt;code&gt;uv add ipykernel
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;然后在 VS Code 中选择当前项目 &lt;code&gt;.venv&lt;/code&gt; 作为 kernel。即使使用 notebook，也要保留 &lt;code&gt;src/make_figures.py&lt;/code&gt;，确保图片可重新生成。&lt;/p&gt;
&lt;h2&gt;16. 一键复现本周结果&lt;/h2&gt;
&lt;p&gt;在项目根目录执行：&lt;/p&gt;
&lt;div&gt;&lt;div&gt;&lt;span&gt;omp&lt;/span&gt;&lt;span&gt;fish&lt;/span&gt;&lt;/div&gt;&lt;pre&gt;&lt;code&gt;source .venv/bin/activate.fish
uv run python src/prepare_data.py
uv run python src/make_figures.py
ls figures
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;这组命令应该能从原始 CSV 生成清洗数据，再生成所有图片。&lt;/p&gt;
&lt;p&gt;如果以后换电脑或把项目交给同学，理想流程是：&lt;/p&gt;
&lt;div&gt;&lt;div&gt;&lt;span&gt;omp&lt;/span&gt;&lt;span&gt;fish&lt;/span&gt;&lt;/div&gt;&lt;pre&gt;&lt;code&gt;uv sync
uv run python src/prepare_data.py
uv run python src/make_figures.py
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;这里的关键思想是“可复现”。EDA 不是只在你电脑上偶然跑通，而是别人按说明也能得到同样文件。&lt;/p&gt;
&lt;h2&gt;17. 本周练习&lt;/h2&gt;
&lt;h3&gt;练习 A：增加单科成绩直方图&lt;/h3&gt;
&lt;p&gt;在 &lt;code&gt;src/make_figures.py&lt;/code&gt; 中增加函数：&lt;/p&gt;
&lt;div&gt;&lt;div&gt;&lt;span&gt;omp&lt;/span&gt;&lt;span&gt;python&lt;/span&gt;&lt;/div&gt;&lt;pre&gt;&lt;code&gt;def plot_python_score_hist(df: pd.DataFrame) -&amp;gt; None:
    fig, ax = plt.subplots(figsize=(8, 5))
    ax.hist(df[&quot;python&quot;], bins=8, edgecolor=&quot;black&quot;)
    ax.set_title(&quot;Distribution of Python Score&quot;)
    ax.set_xlabel(&quot;Python score&quot;)
    ax.set_ylabel(&quot;Student count&quot;)
    save_current_figure(fig, &quot;python_score_hist.png&quot;)
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;然后在 &lt;code&gt;main()&lt;/code&gt; 中调用它。&lt;/p&gt;
&lt;p&gt;运行：&lt;/p&gt;
&lt;div&gt;&lt;div&gt;&lt;span&gt;omp&lt;/span&gt;&lt;span&gt;fish&lt;/span&gt;&lt;/div&gt;&lt;pre&gt;&lt;code&gt;uv run python src/make_figures.py
ls figures
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;h3&gt;练习 B：按性别画箱线图&lt;/h3&gt;
&lt;p&gt;仿照 &lt;code&gt;plot_major_boxplot&lt;/code&gt;，按 &lt;code&gt;gender&lt;/code&gt; 比较 &lt;code&gt;average_score&lt;/code&gt;。&lt;/p&gt;
&lt;p&gt;输出文件命名为：&lt;/p&gt;
&lt;div&gt;&lt;div&gt;&lt;span&gt;omp&lt;/span&gt;&lt;span&gt;prompt&lt;/span&gt;&lt;/div&gt;&lt;pre&gt;&lt;code&gt;average_score_by_gender.png
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;报告中必须写样本量限制，不能把练习数据解释成真实群体差异。&lt;/p&gt;
&lt;h3&gt;练习 C：保存描述统计表&lt;/h3&gt;
&lt;p&gt;创建 &lt;code&gt;src/export_summary.py&lt;/code&gt;，把描述统计保存为 CSV：&lt;/p&gt;
&lt;div&gt;&lt;div&gt;&lt;span&gt;omp&lt;/span&gt;&lt;span&gt;python&lt;/span&gt;&lt;/div&gt;&lt;pre&gt;&lt;code&gt;from pathlib import Path
&lt;p&gt;import pandas as pd&lt;/p&gt;
&lt;/code&gt;&lt;p&gt;&lt;code&gt;df = pd.read_csv(Path(“data/processed/student_scores_clean.csv”))
summary = df.describe()
summary.to_csv(Path(“reports/descriptive_statistics.csv”))
&lt;/code&gt;&lt;/p&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;&lt;/p&gt;
&lt;p&gt;运行：&lt;/p&gt;
&lt;div&gt;&lt;div&gt;&lt;span&gt;omp&lt;/span&gt;&lt;span&gt;fish&lt;/span&gt;&lt;/div&gt;&lt;pre&gt;&lt;code&gt;uv run python src/export_summary.py
ls reports
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;h3&gt;练习 D：改进报告&lt;/h3&gt;
&lt;p&gt;在 &lt;code&gt;reports/week04_eda_report.md&lt;/code&gt; 中为每张图写三句话：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;这张图看什么变量？&lt;/li&gt;
&lt;li&gt;从图中观察到什么？&lt;/li&gt;
&lt;li&gt;这个观察有什么限制？&lt;/li&gt;
&lt;/ol&gt;
&lt;h2&gt;18. 验收检查&lt;/h2&gt;
&lt;p&gt;在项目根目录执行：&lt;/p&gt;
&lt;div&gt;&lt;div&gt;&lt;span&gt;omp&lt;/span&gt;&lt;span&gt;fish&lt;/span&gt;&lt;/div&gt;&lt;pre&gt;&lt;code&gt;pwd
ls
source .venv/bin/activate.fish
uv run python src/prepare_data.py
uv run python src/make_figures.py
ls data/processed
ls figures
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;验收标准：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;当前目录是 &lt;code&gt;week04-visualization-eda&lt;/code&gt;。&lt;/li&gt;
&lt;li&gt;&lt;code&gt;source .venv/bin/activate.fish&lt;/code&gt; 不报错。&lt;/li&gt;
&lt;li&gt;&lt;code&gt;data/processed/student_scores_clean.csv&lt;/code&gt; 存在。&lt;/li&gt;
&lt;li&gt;&lt;code&gt;figures/average_score_hist.png&lt;/code&gt; 存在。&lt;/li&gt;
&lt;li&gt;&lt;code&gt;figures/study_hours_vs_average_score.png&lt;/code&gt; 存在。&lt;/li&gt;
&lt;li&gt;&lt;code&gt;figures/average_score_by_major.png&lt;/code&gt; 存在。&lt;/li&gt;
&lt;li&gt;&lt;code&gt;figures/correlation_heatmap.png&lt;/code&gt; 存在。&lt;/li&gt;
&lt;li&gt;&lt;code&gt;reports/week04_eda_report.md&lt;/code&gt; 包含数据来源、字段说明、缺失值情况、主要分布、相关性观察、可能的建模目标。&lt;/li&gt;
&lt;li&gt;报告中的结论使用“观察”“可能”“样本有限”等谨慎表述，而不是过度因果解释。&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;19. 常见错误&lt;/h2&gt;
&lt;h3&gt;错误 1：&lt;code&gt;ModuleNotFoundError: No module named &apos;matplotlib&apos;&lt;/code&gt;&lt;/h3&gt;
&lt;p&gt;说明当前环境没有安装 matplotlib，或没有使用项目环境运行。&lt;/p&gt;
&lt;p&gt;修复：&lt;/p&gt;
&lt;div&gt;&lt;div&gt;&lt;span&gt;omp&lt;/span&gt;&lt;span&gt;fish&lt;/span&gt;&lt;/div&gt;&lt;pre&gt;&lt;code&gt;cd ~/Code/python-learning/week04-visualization-eda
uv add matplotlib
uv run python src/make_figures.py
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;h3&gt;错误 2：图片没有生成&lt;/h3&gt;
&lt;p&gt;先检查脚本是否真的运行到保存函数：&lt;/p&gt;
&lt;div&gt;&lt;div&gt;&lt;span&gt;omp&lt;/span&gt;&lt;span&gt;fish&lt;/span&gt;&lt;/div&gt;&lt;pre&gt;&lt;code&gt;uv run python src/make_figures.py
ls figures
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;如果没有 &lt;code&gt;figures/&lt;/code&gt;，检查是否调用了：&lt;/p&gt;
&lt;div&gt;&lt;div&gt;&lt;span&gt;omp&lt;/span&gt;&lt;span&gt;python&lt;/span&gt;&lt;/div&gt;&lt;pre&gt;&lt;code&gt;fig.savefig(output_path, dpi=150)
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;以及是否创建目录：&lt;/p&gt;
&lt;div&gt;&lt;div&gt;&lt;span&gt;omp&lt;/span&gt;&lt;span&gt;python&lt;/span&gt;&lt;/div&gt;&lt;pre&gt;&lt;code&gt;FIGURE_DIR.mkdir(parents=True, exist_ok=True)
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;h3&gt;错误 3：&lt;code&gt;FileNotFoundError: data/processed/student_scores_clean.csv&lt;/code&gt;&lt;/h3&gt;
&lt;p&gt;原因：还没运行数据准备脚本。&lt;/p&gt;
&lt;p&gt;修复：&lt;/p&gt;
&lt;div&gt;&lt;div&gt;&lt;span&gt;omp&lt;/span&gt;&lt;span&gt;fish&lt;/span&gt;&lt;/div&gt;&lt;pre&gt;&lt;code&gt;uv run python src/prepare_data.py
uv run python src/make_figures.py
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;h3&gt;错误 4：中文标题显示成方块&lt;/h3&gt;
&lt;p&gt;matplotlib 默认字体可能不支持中文。初学阶段最简单的办法是图表标题先使用英文，报告正文使用中文解释。等后续需要正式中文图表时，再单独配置中文字体。&lt;/p&gt;
&lt;h3&gt;错误 5：热力图颜色看起来很夸张&lt;/h3&gt;
&lt;p&gt;如果不固定颜色范围，不同数据集的颜色尺度可能不一致。建议相关性热力图使用：&lt;/p&gt;
&lt;div&gt;&lt;div&gt;&lt;span&gt;omp&lt;/span&gt;&lt;span&gt;python&lt;/span&gt;&lt;/div&gt;&lt;pre&gt;&lt;code&gt;ax.imshow(corr, cmap=&quot;coolwarm&quot;, vmin=-1, vmax=1)
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;这样 -1 到 1 的颜色含义稳定。&lt;/p&gt;
&lt;h3&gt;错误 6：报告只有图片，没有分析&lt;/h3&gt;
&lt;p&gt;EDA 报告不是图库。每张图至少要回答：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;这张图为什么画？&lt;/li&gt;
&lt;li&gt;图中最明显的模式是什么？&lt;/li&gt;
&lt;li&gt;这个结论有什么限制？&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;如果答不出来，说明这张图可能不该放进报告，或者你还没理解变量含义。&lt;/p&gt;
&lt;h2&gt;20. 下一步&lt;/h2&gt;
&lt;p&gt;Phase 1 到这里结束。你已经从 Python 环境、Linux / Git、Pandas 表格处理走到了完整 EDA 报告。Week 05 开始进入机器学习基础：用 scikit-learn 做回归 / 分类、训练集测试集划分、交叉验证和评价指标。Week 04 的清洗数据与 EDA 报告，会成为后续建模项目的输入。&lt;/p&gt;
&lt;p&gt;plain &lt;/p&gt;&lt;/article&gt;&lt;p&gt;&lt;/p&gt;
  &lt;/div&gt;
&lt;/section&gt;</content:encoded><category>category:工具</category><category>category:数据分析</category><category>tag:matplotlib</category><category>tag:seaborn</category><category>tag:数据可视化</category><category>tag:EDA</category></item><item><title>CachyOS + VS Code + uv：Python 学习环境搭建教学</title><link>https://39miku.space/post/python-learning-environment-cachyos</link><guid isPermaLink="false">python-learning-environment-cachyos</guid><description>面向 CachyOS、fish shell 和 VS Code 用户的 Python 学习环境搭建教学，逐句解释目录、uv、虚拟环境、依赖、解释器、notebook 与 Git 初始化。</description><pubDate>Sat, 25 Jul 2026 19:00:00 GMT</pubDate><content:encoded>
&lt;section&gt;
  &lt;div&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;strong&gt;Oh My Pi&lt;/strong&gt; / python environment / fish-first lesson&lt;/span&gt;&lt;/div&gt;
  &lt;div&gt;
    &lt;div&gt;&lt;span&gt;miku@cachyos&lt;/span&gt;&lt;span&gt;:~/Code/python-learning&lt;/span&gt;&lt;span&gt;$&lt;/span&gt; &lt;span&gt;omp teach python-env --fish-first --vscode --uv&lt;/span&gt;&lt;/div&gt;
    &lt;div&gt;
      &lt;div&gt;&lt;span&gt;source&lt;/span&gt;&lt;strong&gt;642 行教学文档&lt;/strong&gt;&lt;/div&gt;
      &lt;div&gt;&lt;span&gt;platform&lt;/span&gt;&lt;strong&gt;CachyOS / Arch 系&lt;/strong&gt;&lt;/div&gt;
      &lt;div&gt;&lt;span&gt;shell&lt;/span&gt;&lt;strong&gt;fish-first 命令版&lt;/strong&gt;&lt;/div&gt;
      &lt;div&gt;&lt;span&gt;env manager&lt;/span&gt;&lt;strong&gt;uv + .venv&lt;/strong&gt;&lt;/div&gt;
    &lt;/div&gt;
    &lt;article&gt;
&lt;h1&gt;CachyOS + VS Code + uv：Python 学习环境搭建教学&lt;/h1&gt;
&lt;blockquote&gt;
&lt;p&gt;适用对象：已经安装 VS Code，正在用 CachyOS / Arch 系 Linux，准备按 &lt;a href=&quot;/post/ustc-stat-ai-quant-plan/&quot;&gt;20 周 AI / 量化成长计划&lt;/a&gt; 开始 Week 01：Python / Linux / Git / 数据分析基础。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;&lt;strong&gt;核心结论&lt;/strong&gt;&lt;br /&gt;不需要把所有事情都放在命令行里做。你可以主要在 VS Code 里写代码、看文件、运行 notebook；但必须掌握少量命令行，因为 Python 环境、包管理、脚本运行、Git、项目复现都依赖这些基础命令。&lt;/p&gt;
&lt;p&gt;本教程采用：&lt;/p&gt;
&lt;div&gt;&lt;div&gt;&lt;span&gt;omp&lt;/span&gt;&lt;span&gt;prompt&lt;/span&gt;&lt;/div&gt;&lt;pre&gt;&lt;code&gt;VS Code 负责：写代码、看文件、运行脚本、调试、使用 notebook
uv 负责：创建虚拟环境、安装依赖、记录项目配置
终端负责：创建目录、激活环境、运行命令、检查结果
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;本文命令行默认按 &lt;strong&gt;fish shell&lt;/strong&gt; 写；如果某一步需要 Bash / Zsh，会单独标注。&lt;/p&gt;
&lt;p&gt;当前机器已经具备：&lt;/p&gt;
&lt;div&gt;&lt;div&gt;&lt;span&gt;omp&lt;/span&gt;&lt;span&gt;prompt&lt;/span&gt;&lt;/div&gt;&lt;pre&gt;&lt;code&gt;Python 3.14.6
uv 0.11.31
VS Code 1.130.0
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;因此不需要从零安装 Python，直接建立学习环境即可。&lt;/p&gt;
&lt;h2&gt;1. 为什么不能完全不用命令行&lt;/h2&gt;
&lt;p&gt;你不需要成为 Linux 高手，但不能完全绕开命令行。&lt;/p&gt;
&lt;p&gt;原因有四个：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;环境隔离需要命令&lt;/strong&gt;：每个项目应有自己的 &lt;code&gt;.venv&lt;/code&gt;，避免不同项目的包互相污染。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;安装包需要命令&lt;/strong&gt;：例如 &lt;code&gt;numpy&lt;/code&gt;、&lt;code&gt;pandas&lt;/code&gt;、&lt;code&gt;matplotlib&lt;/code&gt;、&lt;code&gt;scikit-learn&lt;/code&gt;。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;复现实验需要命令&lt;/strong&gt;：以后别人看你的项目，需要知道如何一键安装依赖并运行。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Git 和部署需要命令&lt;/strong&gt;：提交代码、查看状态、解决冲突，迟早会用到。&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;最低限度只需要会这些：&lt;/p&gt;
&lt;div&gt;&lt;div&gt;&lt;span&gt;omp&lt;/span&gt;&lt;span&gt;fish&lt;/span&gt;&lt;/div&gt;&lt;pre&gt;&lt;code&gt;pwd        # 看当前在哪个目录
ls         # 看当前目录有什么文件
cd         # 进入目录
mkdir      # 创建文件夹
code .     # 用 VS Code 打开当前目录
uv init    # 初始化 Python 项目
uv venv    # 创建虚拟环境
uv add     # 安装依赖
python     # 运行 Python
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;h2&gt;2. 建立统一学习目录&lt;/h2&gt;
&lt;p&gt;先给未来 20 周的学习项目留一个统一位置。&lt;/p&gt;
&lt;p&gt;打开终端，执行：&lt;/p&gt;
&lt;div&gt;&lt;div&gt;&lt;span&gt;omp&lt;/span&gt;&lt;span&gt;fish&lt;/span&gt;&lt;/div&gt;&lt;pre&gt;&lt;code&gt;mkdir -p ~/Code/python-learning
cd ~/Code/python-learning
pwd
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;逐句解释：&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;命令&lt;/th&gt;
&lt;th&gt;含义&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;&lt;tr&gt;
&lt;td&gt;&lt;code&gt;mkdir&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;创建文件夹&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;-p&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;如果上级目录不存在，就一起创建；如果目录已存在，也不要报错&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;~/Code/python-learning&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;你的 Python 学习总目录，&lt;code&gt;~&lt;/code&gt; 表示当前用户主目录&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;cd&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;进入这个目录&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;pwd&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;打印当前所在路径，用来确认你没有走错目录&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;&lt;/table&gt;
&lt;p&gt;如果输出类似：&lt;/p&gt;
&lt;div&gt;&lt;div&gt;&lt;span&gt;omp&lt;/span&gt;&lt;span&gt;prompt&lt;/span&gt;&lt;/div&gt;&lt;pre&gt;&lt;code&gt;/home/miku/Code/python-learning
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;说明位置正确。&lt;/p&gt;
&lt;h2&gt;3. 创建第一个 Week 01 项目&lt;/h2&gt;
&lt;p&gt;每周一个小项目，不要把所有练习都堆在一个文件夹里。&lt;/p&gt;
&lt;div&gt;&lt;div&gt;&lt;span&gt;omp&lt;/span&gt;&lt;span&gt;fish&lt;/span&gt;&lt;/div&gt;&lt;pre&gt;&lt;code&gt;mkdir week01-basics
cd week01-basics
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;建议结构：&lt;/p&gt;
&lt;div&gt;&lt;div&gt;&lt;span&gt;omp&lt;/span&gt;&lt;span&gt;prompt&lt;/span&gt;&lt;/div&gt;&lt;pre&gt;&lt;code&gt;week01-basics/
├── notes/       # 学习笔记
├── src/         # Python 源代码
├── data/        # 小数据文件
├── notebooks/   # Jupyter notebook
└── reports/     # 报告或总结
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;创建这些目录：&lt;/p&gt;
&lt;div&gt;&lt;div&gt;&lt;span&gt;omp&lt;/span&gt;&lt;span&gt;fish&lt;/span&gt;&lt;/div&gt;&lt;pre&gt;&lt;code&gt;mkdir -p notes src data notebooks reports
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;检查：&lt;/p&gt;
&lt;div&gt;&lt;div&gt;&lt;span&gt;omp&lt;/span&gt;&lt;span&gt;fish&lt;/span&gt;&lt;/div&gt;&lt;pre&gt;&lt;code&gt;ls
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;应该看到：&lt;/p&gt;
&lt;div&gt;&lt;div&gt;&lt;span&gt;omp&lt;/span&gt;&lt;span&gt;prompt&lt;/span&gt;&lt;/div&gt;&lt;pre&gt;&lt;code&gt;data  notebooks  notes  reports  src
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;h2&gt;4. 初始化 Python 项目&lt;/h2&gt;
&lt;p&gt;在 &lt;code&gt;week01-basics&lt;/code&gt; 目录里执行：&lt;/p&gt;
&lt;div&gt;&lt;div&gt;&lt;span&gt;omp&lt;/span&gt;&lt;span&gt;fish&lt;/span&gt;&lt;/div&gt;&lt;pre&gt;&lt;code&gt;uv init
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;这一步会生成项目配置文件，通常包括：&lt;/p&gt;
&lt;div&gt;&lt;div&gt;&lt;span&gt;omp&lt;/span&gt;&lt;span&gt;prompt&lt;/span&gt;&lt;/div&gt;&lt;pre&gt;&lt;code&gt;pyproject.toml
README.md
main.py
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;解释：&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;文件&lt;/th&gt;
&lt;th&gt;作用&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;&lt;tr&gt;
&lt;td&gt;&lt;code&gt;pyproject.toml&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;记录项目名称、Python 版本、依赖包&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;README.md&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;项目说明，未来给老师、面试官或自己回看&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;main.py&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;默认入口脚本，可以改也可以删除&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;&lt;/table&gt;
&lt;p&gt;如果 &lt;code&gt;uv init&lt;/code&gt; 提示文件已存在，不用慌，说明当前目录里已经有项目文件。&lt;/p&gt;
&lt;h2&gt;5. 创建虚拟环境 &lt;code&gt;.venv&lt;/code&gt;&lt;/h2&gt;
&lt;p&gt;执行：&lt;/p&gt;
&lt;div&gt;&lt;div&gt;&lt;span&gt;omp&lt;/span&gt;&lt;span&gt;fish&lt;/span&gt;&lt;/div&gt;&lt;pre&gt;&lt;code&gt;uv venv
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;它会创建：&lt;/p&gt;
&lt;div&gt;&lt;div&gt;&lt;span&gt;omp&lt;/span&gt;&lt;span&gt;prompt&lt;/span&gt;&lt;/div&gt;&lt;pre&gt;&lt;code&gt;.venv/
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;&lt;code&gt;.venv&lt;/code&gt; 是这个项目自己的 Python 环境。以后这个项目安装的包都放在里面，不污染系统 Python。&lt;/p&gt;
&lt;p&gt;为什么重要：&lt;/p&gt;
&lt;div&gt;&lt;div&gt;&lt;span&gt;omp&lt;/span&gt;&lt;span&gt;prompt&lt;/span&gt;&lt;/div&gt;&lt;pre&gt;&lt;code&gt;系统 Python：给操作系统和系统工具用
项目 .venv：给当前学习项目用
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;不要把所有包直接装进系统 Python。CachyOS / Arch 是滚动发行版，系统 Python 更应该保持干净。&lt;/p&gt;
&lt;h2&gt;6. 激活虚拟环境&lt;/h2&gt;
&lt;p&gt;先确认你在真正的项目目录里，而不是只在总目录 &lt;code&gt;~/Code/python-learning&lt;/code&gt; 里：&lt;/p&gt;
&lt;div&gt;&lt;div&gt;&lt;span&gt;omp&lt;/span&gt;&lt;span&gt;fish&lt;/span&gt;&lt;/div&gt;&lt;pre&gt;&lt;code&gt;pwd
ls
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;如果你按本文创建的是 &lt;code&gt;week01-basics&lt;/code&gt; 项目，那么应先进入：&lt;/p&gt;
&lt;div&gt;&lt;div&gt;&lt;span&gt;omp&lt;/span&gt;&lt;span&gt;fish&lt;/span&gt;&lt;/div&gt;&lt;pre&gt;&lt;code&gt;cd ~/Code/python-learning/week01-basics
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;判断标准：当前目录里应该能看到 &lt;code&gt;pyproject.toml&lt;/code&gt; 和 &lt;code&gt;.venv/&lt;/code&gt;。如果你在 &lt;code&gt;~/Code/python-learning&lt;/code&gt; 总目录下执行激活命令，而虚拟环境其实建在 &lt;code&gt;week01-basics/.venv/&lt;/code&gt;，就会找错位置。&lt;/p&gt;
&lt;h3&gt;6.1 如果你使用 fish shell&lt;/h3&gt;
&lt;p&gt;你现在遇到的报错就是 fish shell 把 Bash 版激活脚本当成 fish 脚本执行了。&lt;/p&gt;
&lt;p&gt;fish 里应执行：&lt;/p&gt;
&lt;div&gt;&lt;div&gt;&lt;span&gt;omp&lt;/span&gt;&lt;span&gt;fish&lt;/span&gt;&lt;/div&gt;&lt;pre&gt;&lt;code&gt;source .venv/bin/activate.fish
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;不要在 fish 里执行：&lt;/p&gt;
&lt;div&gt;&lt;div&gt;&lt;span&gt;omp&lt;/span&gt;&lt;span&gt;bash/zsh&lt;/span&gt;&lt;/div&gt;&lt;pre&gt;&lt;code&gt;source .venv/bin/activate
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;&lt;code&gt;.venv/bin/activate&lt;/code&gt; 是给 Bash / Zsh 用的，里面有 &lt;code&gt;变量=值&lt;/code&gt; 这种语法；fish 不支持这种写法，所以会报：&lt;/p&gt;
&lt;div&gt;&lt;div&gt;&lt;span&gt;omp&lt;/span&gt;&lt;span&gt;prompt&lt;/span&gt;&lt;/div&gt;&lt;pre&gt;&lt;code&gt;不支持使用 &apos;=&apos;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;h3&gt;6.2 如果你使用 bash 或 zsh&lt;/h3&gt;
&lt;p&gt;Bash / Zsh 才使用：&lt;/p&gt;
&lt;div&gt;&lt;div&gt;&lt;span&gt;omp&lt;/span&gt;&lt;span&gt;bash/zsh&lt;/span&gt;&lt;/div&gt;&lt;pre&gt;&lt;code&gt;source .venv/bin/activate
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;h3&gt;6.3 激活成功后的检查&lt;/h3&gt;
&lt;p&gt;成功后，终端提示符前面通常会出现：&lt;/p&gt;
&lt;div&gt;&lt;div&gt;&lt;span&gt;omp&lt;/span&gt;&lt;span&gt;prompt&lt;/span&gt;&lt;/div&gt;&lt;pre&gt;&lt;code&gt;(.venv)
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;这表示当前终端已经进入项目环境。&lt;/p&gt;
&lt;p&gt;验证 Python 路径：&lt;/p&gt;
&lt;div&gt;&lt;div&gt;&lt;span&gt;omp&lt;/span&gt;&lt;span&gt;fish&lt;/span&gt;&lt;/div&gt;&lt;pre&gt;&lt;code&gt;command -v python
python --version
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;你希望看到的 &lt;code&gt;command -v python&lt;/code&gt; 应该类似：&lt;/p&gt;
&lt;div&gt;&lt;div&gt;&lt;span&gt;omp&lt;/span&gt;&lt;span&gt;prompt&lt;/span&gt;&lt;/div&gt;&lt;pre&gt;&lt;code&gt;/home/miku/Code/python-learning/week01-basics/.venv/bin/python
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;如果看到的是 &lt;code&gt;/usr/bin/python&lt;/code&gt;，说明虚拟环境没有激活成功，或者你不在创建 &lt;code&gt;.venv&lt;/code&gt; 的项目目录里。&lt;/p&gt;
&lt;h2&gt;7. 安装第一批学习包&lt;/h2&gt;
&lt;p&gt;Week 01 到 Week 04 建议安装：&lt;/p&gt;
&lt;div&gt;&lt;div&gt;&lt;span&gt;omp&lt;/span&gt;&lt;span&gt;fish&lt;/span&gt;&lt;/div&gt;&lt;pre&gt;&lt;code&gt;uv add numpy pandas matplotlib jupyter ipykernel
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;逐个解释：&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;包&lt;/th&gt;
&lt;th&gt;用途&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;&lt;tr&gt;
&lt;td&gt;&lt;code&gt;numpy&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;数值计算，数组、矩阵、随机数&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;pandas&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;表格数据处理，CSV、DataFrame&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;matplotlib&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;基础画图&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;jupyter&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;notebook 环境&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;ipykernel&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;让 VS Code / Jupyter 使用当前虚拟环境&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;&lt;/table&gt;
&lt;p&gt;安装完成后，&lt;code&gt;pyproject.toml&lt;/code&gt; 会记录这些依赖。以后别人拿到你的项目，可以根据配置恢复环境。&lt;/p&gt;
&lt;h2&gt;8. 用 VS Code 打开项目&lt;/h2&gt;
&lt;p&gt;确认你还在项目目录：&lt;/p&gt;
&lt;div&gt;&lt;div&gt;&lt;span&gt;omp&lt;/span&gt;&lt;span&gt;fish&lt;/span&gt;&lt;/div&gt;&lt;pre&gt;&lt;code&gt;pwd
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;然后执行：&lt;/p&gt;
&lt;div&gt;&lt;div&gt;&lt;span&gt;omp&lt;/span&gt;&lt;span&gt;fish&lt;/span&gt;&lt;/div&gt;&lt;pre&gt;&lt;code&gt;code .
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;含义：用 VS Code 打开当前目录。&lt;/p&gt;
&lt;p&gt;不要只打开单个 &lt;code&gt;.py&lt;/code&gt; 文件，应该打开整个项目文件夹。这样 VS Code 才能识别 &lt;code&gt;.venv&lt;/code&gt;、&lt;code&gt;pyproject.toml&lt;/code&gt;、目录结构和相对路径。&lt;/p&gt;
&lt;h2&gt;9. 在 VS Code 里选择 Python 解释器&lt;/h2&gt;
&lt;p&gt;打开 VS Code 后：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;按 &lt;code&gt;Ctrl + Shift + P&lt;/code&gt;。&lt;/li&gt;
&lt;li&gt;输入 &lt;code&gt;Python: Select Interpreter&lt;/code&gt;。&lt;/li&gt;
&lt;li&gt;选择当前项目的解释器：&lt;/li&gt;
&lt;/ol&gt;
&lt;div&gt;&lt;div&gt;&lt;span&gt;omp&lt;/span&gt;&lt;span&gt;prompt&lt;/span&gt;&lt;/div&gt;&lt;pre&gt;&lt;code&gt;.venv/bin/python
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;如果列表里没看到，可以手动选择：&lt;/p&gt;
&lt;div&gt;&lt;div&gt;&lt;span&gt;omp&lt;/span&gt;&lt;span&gt;prompt&lt;/span&gt;&lt;/div&gt;&lt;pre&gt;&lt;code&gt;/home/miku/Code/python-learning/week01-basics/.venv/bin/python
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;这一步的意义：告诉 VS Code 使用当前项目环境，而不是系统 Python。&lt;/p&gt;
&lt;h2&gt;10. 写第一个脚本&lt;/h2&gt;
&lt;p&gt;在 &lt;code&gt;src/&lt;/code&gt; 下新建文件：&lt;/p&gt;
&lt;div&gt;&lt;div&gt;&lt;span&gt;omp&lt;/span&gt;&lt;span&gt;prompt&lt;/span&gt;&lt;/div&gt;&lt;pre&gt;&lt;code&gt;src/hello.py
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;内容：&lt;/p&gt;
&lt;div&gt;&lt;div&gt;&lt;span&gt;omp&lt;/span&gt;&lt;span&gt;python&lt;/span&gt;&lt;/div&gt;&lt;pre&gt;&lt;code&gt;print(&quot;Hello, Python&quot;)
&lt;/code&gt;&lt;p&gt;&lt;code&gt;scores = [85, 92, 78]
mean_score = sum(scores) / len(scores)
print(mean_score)
&lt;/code&gt;&lt;/p&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;&lt;/p&gt;
&lt;p&gt;用终端运行：&lt;/p&gt;
&lt;div&gt;&lt;div&gt;&lt;span&gt;omp&lt;/span&gt;&lt;span&gt;fish&lt;/span&gt;&lt;/div&gt;&lt;pre&gt;&lt;code&gt;python src/hello.py
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;应该输出：&lt;/p&gt;
&lt;div&gt;&lt;div&gt;&lt;span&gt;omp&lt;/span&gt;&lt;span&gt;prompt&lt;/span&gt;&lt;/div&gt;&lt;pre&gt;&lt;code&gt;Hello, Python
85.0
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;如果 VS Code 右上角运行按钮也能跑出同样结果，说明解释器配置正确。&lt;/p&gt;
&lt;h2&gt;11. 写第一个 pandas 练习&lt;/h2&gt;
&lt;p&gt;新建：&lt;/p&gt;
&lt;div&gt;&lt;div&gt;&lt;span&gt;omp&lt;/span&gt;&lt;span&gt;prompt&lt;/span&gt;&lt;/div&gt;&lt;pre&gt;&lt;code&gt;src/pandas_demo.py
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;内容：&lt;/p&gt;
&lt;div&gt;&lt;div&gt;&lt;span&gt;omp&lt;/span&gt;&lt;span&gt;python&lt;/span&gt;&lt;/div&gt;&lt;pre&gt;&lt;code&gt;import pandas as pd
&lt;p&gt;data = {
“name”: [“Alice”, “Bob”, “Cindy”],
“score”: [85, 92, 78],
}&lt;/p&gt;
&lt;/code&gt;&lt;p&gt;&lt;code&gt;df = pd.DataFrame(data)
print(df)
print(“mean score:”, df[“score”].mean())
&lt;/code&gt;&lt;/p&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;&lt;/p&gt;
&lt;p&gt;运行：&lt;/p&gt;
&lt;div&gt;&lt;div&gt;&lt;span&gt;omp&lt;/span&gt;&lt;span&gt;fish&lt;/span&gt;&lt;/div&gt;&lt;pre&gt;&lt;code&gt;python src/pandas_demo.py
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;如果能看到表格和平均分，说明：&lt;/p&gt;
&lt;div&gt;&lt;div&gt;&lt;span&gt;omp&lt;/span&gt;&lt;span&gt;prompt&lt;/span&gt;&lt;/div&gt;&lt;pre&gt;&lt;code&gt;Python 能运行
pandas 已安装
VS Code 项目路径正确
虚拟环境可用
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;h2&gt;12. 建立 notebook&lt;/h2&gt;
&lt;p&gt;在 VS Code 里新建：&lt;/p&gt;
&lt;div&gt;&lt;div&gt;&lt;span&gt;omp&lt;/span&gt;&lt;span&gt;prompt&lt;/span&gt;&lt;/div&gt;&lt;pre&gt;&lt;code&gt;notebooks/week01.ipynb
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;第一次打开 notebook 时，右上角选择 Kernel：&lt;/p&gt;
&lt;div&gt;&lt;div&gt;&lt;span&gt;omp&lt;/span&gt;&lt;span&gt;prompt&lt;/span&gt;&lt;/div&gt;&lt;pre&gt;&lt;code&gt;.venv/bin/python
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;新建一个 cell：&lt;/p&gt;
&lt;div&gt;&lt;div&gt;&lt;span&gt;omp&lt;/span&gt;&lt;span&gt;python&lt;/span&gt;&lt;/div&gt;&lt;pre&gt;&lt;code&gt;import pandas as pd
&lt;/code&gt;&lt;p&gt;&lt;code&gt;pd.DataFrame({“x”: [1, 2, 3], “y”: [2, 4, 6]})
&lt;/code&gt;&lt;/p&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;&lt;/p&gt;
&lt;p&gt;如果表格正常显示，notebook 环境配置成功。&lt;/p&gt;
&lt;h2&gt;13. 推荐项目结构&lt;/h2&gt;
&lt;p&gt;Week 01 最终建议变成：&lt;/p&gt;
&lt;div&gt;&lt;div&gt;&lt;span&gt;omp&lt;/span&gt;&lt;span&gt;prompt&lt;/span&gt;&lt;/div&gt;&lt;pre&gt;&lt;code&gt;week01-basics/
├── .venv/
├── data/
│   └── scores.csv
├── notebooks/
│   └── week01.ipynb
├── notes/
│   └── week01-python.md
├── reports/
│   └── week01-summary.md
├── src/
│   ├── hello.py
│   └── pandas_demo.py
├── pyproject.toml
├── README.md
└── uv.lock
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;其中：&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;路径&lt;/th&gt;
&lt;th&gt;说明&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;&lt;tr&gt;
&lt;td&gt;&lt;code&gt;.venv/&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;虚拟环境，不手动编辑，不提交到 Git&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;data/&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;小数据文件&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;notebooks/&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;交互式分析&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;notes/&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;学习笔记&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;reports/&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;阶段总结&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;src/&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;正式 Python 脚本&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;pyproject.toml&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;项目依赖配置&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;uv.lock&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;锁定依赖版本，保证复现&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;&lt;/table&gt;
&lt;h2&gt;14. Git 初始化&lt;/h2&gt;
&lt;p&gt;如果你要把练习当成项目积累，建议从 Week 01 就用 Git。&lt;/p&gt;
&lt;div&gt;&lt;div&gt;&lt;span&gt;omp&lt;/span&gt;&lt;span&gt;fish&lt;/span&gt;&lt;/div&gt;&lt;pre&gt;&lt;code&gt;git init
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;创建 &lt;code&gt;.gitignore&lt;/code&gt;：&lt;/p&gt;
&lt;div&gt;&lt;div&gt;&lt;span&gt;omp&lt;/span&gt;&lt;span&gt;fish&lt;/span&gt;&lt;/div&gt;&lt;pre&gt;&lt;code&gt;printf &quot;%s\n&quot; &quot;.venv/&quot; &quot;__pycache__/&quot; &quot;.ipynb_checkpoints/&quot; &quot;*.pyc&quot; &quot;.env&quot; &amp;gt; .gitignore
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;解释：&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;条目&lt;/th&gt;
&lt;th&gt;为什么忽略&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;&lt;tr&gt;
&lt;td&gt;&lt;code&gt;.venv/&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;虚拟环境很大，可以重建，不应提交&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;__pycache__/&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Python 自动缓存&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;.ipynb_checkpoints/&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Jupyter 自动生成&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;*.pyc&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;编译缓存&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;.env&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;可能包含 API key&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;&lt;/table&gt;
&lt;p&gt;查看状态：&lt;/p&gt;
&lt;div&gt;&lt;div&gt;&lt;span&gt;omp&lt;/span&gt;&lt;span&gt;fish&lt;/span&gt;&lt;/div&gt;&lt;pre&gt;&lt;code&gt;git status
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;提交：&lt;/p&gt;
&lt;div&gt;&lt;div&gt;&lt;span&gt;omp&lt;/span&gt;&lt;span&gt;fish&lt;/span&gt;&lt;/div&gt;&lt;pre&gt;&lt;code&gt;git add README.md pyproject.toml uv.lock src notes notebooks reports data .gitignore
git commit -m &quot;init python learning environment&quot;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;如果你暂时还不熟 Git，可以先不提交，但至少要理解 &lt;code&gt;.venv/&lt;/code&gt; 不该放进仓库。&lt;/p&gt;
&lt;h2&gt;15. 以后每天怎么用&lt;/h2&gt;
&lt;p&gt;每次重新打开项目，如果你使用 fish：&lt;/p&gt;
&lt;div&gt;&lt;div&gt;&lt;span&gt;omp&lt;/span&gt;&lt;span&gt;fish&lt;/span&gt;&lt;/div&gt;&lt;pre&gt;&lt;code&gt;cd ~/Code/python-learning/week01-basics
source .venv/bin/activate.fish
code .
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;如果你使用 bash / zsh：&lt;/p&gt;
&lt;div&gt;&lt;div&gt;&lt;span&gt;omp&lt;/span&gt;&lt;span&gt;bash/zsh&lt;/span&gt;&lt;/div&gt;&lt;pre&gt;&lt;code&gt;cd ~/Code/python-learning/week01-basics
source .venv/bin/activate
code .
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;如果只在 VS Code 里运行，解释器选好后不一定每次都要手动激活；但只要你在终端里运行 &lt;code&gt;python&lt;/code&gt;，建议先激活。&lt;/p&gt;
&lt;h2&gt;16. 常见错误排查&lt;/h2&gt;
&lt;h3&gt;16.1 VS Code 找不到 pandas&lt;/h3&gt;
&lt;p&gt;现象：&lt;/p&gt;
&lt;div&gt;&lt;div&gt;&lt;span&gt;omp&lt;/span&gt;&lt;span&gt;prompt&lt;/span&gt;&lt;/div&gt;&lt;pre&gt;&lt;code&gt;ModuleNotFoundError: No module named &apos;pandas&apos;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;检查：&lt;/p&gt;
&lt;div&gt;&lt;div&gt;&lt;span&gt;omp&lt;/span&gt;&lt;span&gt;fish&lt;/span&gt;&lt;/div&gt;&lt;pre&gt;&lt;code&gt;command -v python
python -c &quot;import pandas as pd; print(pd.__version__)&quot;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;如果终端能导入，但 VS Code 不能，说明 VS Code 解释器没选 &lt;code&gt;.venv/bin/python&lt;/code&gt;。&lt;/p&gt;
&lt;h3&gt;16.2 终端前面没有 &lt;code&gt;(.venv)&lt;/code&gt;&lt;/h3&gt;
&lt;p&gt;先确认当前目录：&lt;/p&gt;
&lt;div&gt;&lt;div&gt;&lt;span&gt;omp&lt;/span&gt;&lt;span&gt;fish&lt;/span&gt;&lt;/div&gt;&lt;pre&gt;&lt;code&gt;pwd
ls
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;如果你在 &lt;code&gt;~/Code/python-learning&lt;/code&gt; 总目录，先进入具体项目：&lt;/p&gt;
&lt;div&gt;&lt;div&gt;&lt;span&gt;omp&lt;/span&gt;&lt;span&gt;fish&lt;/span&gt;&lt;/div&gt;&lt;pre&gt;&lt;code&gt;cd ~/Code/python-learning/week01-basics
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;fish 用户重新激活：&lt;/p&gt;
&lt;div&gt;&lt;div&gt;&lt;span&gt;omp&lt;/span&gt;&lt;span&gt;fish&lt;/span&gt;&lt;/div&gt;&lt;pre&gt;&lt;code&gt;source .venv/bin/activate.fish
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;bash / zsh 用户重新激活：&lt;/p&gt;
&lt;div&gt;&lt;div&gt;&lt;span&gt;omp&lt;/span&gt;&lt;span&gt;bash/zsh&lt;/span&gt;&lt;/div&gt;&lt;pre&gt;&lt;code&gt;source .venv/bin/activate
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;h3&gt;16.3 不知道当前在哪个目录&lt;/h3&gt;
&lt;p&gt;执行：&lt;/p&gt;
&lt;div&gt;&lt;div&gt;&lt;span&gt;omp&lt;/span&gt;&lt;span&gt;fish&lt;/span&gt;&lt;/div&gt;&lt;pre&gt;&lt;code&gt;pwd
ls
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;如果看不到 &lt;code&gt;pyproject.toml&lt;/code&gt;，说明你不在项目根目录。&lt;/p&gt;
&lt;h3&gt;16.4 uv add 失败&lt;/h3&gt;
&lt;p&gt;先检查网络和当前目录：&lt;/p&gt;
&lt;div&gt;&lt;div&gt;&lt;span&gt;omp&lt;/span&gt;&lt;span&gt;fish&lt;/span&gt;&lt;/div&gt;&lt;pre&gt;&lt;code&gt;pwd
uv --version
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;确认在项目目录后再执行：&lt;/p&gt;
&lt;div&gt;&lt;div&gt;&lt;span&gt;omp&lt;/span&gt;&lt;span&gt;fish&lt;/span&gt;&lt;/div&gt;&lt;pre&gt;&lt;code&gt;uv add pandas
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;h2&gt;17. Week 01 验收清单&lt;/h2&gt;
&lt;p&gt;完成后你应该能做到：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt; 能解释 &lt;code&gt;.venv&lt;/code&gt; 是什么。&lt;/li&gt;
&lt;li&gt; 能用 &lt;code&gt;uv add&lt;/code&gt; 安装包。&lt;/li&gt;
&lt;li&gt; 能在 VS Code 里选择 &lt;code&gt;.venv/bin/python&lt;/code&gt;。&lt;/li&gt;
&lt;li&gt; 能运行 &lt;code&gt;python src/hello.py&lt;/code&gt;。&lt;/li&gt;
&lt;li&gt; 能运行 pandas 示例。&lt;/li&gt;
&lt;li&gt; 能创建 notebook 并选择当前 kernel。&lt;/li&gt;
&lt;li&gt; 知道 &lt;code&gt;.venv/&lt;/code&gt; 不应该提交到 Git。&lt;/li&gt;
&lt;li&gt; 知道如何从终端进入项目目录。&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;18. 最小命令速查&lt;/h2&gt;
&lt;h3&gt;18.1 创建项目&lt;/h3&gt;
&lt;div&gt;&lt;div&gt;&lt;span&gt;omp&lt;/span&gt;&lt;span&gt;fish&lt;/span&gt;&lt;/div&gt;&lt;pre&gt;&lt;code&gt;mkdir -p ~/Code/python-learning/week01-basics
cd ~/Code/python-learning/week01-basics
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;h3&gt;18.2 初始化环境&lt;/h3&gt;
&lt;div&gt;&lt;div&gt;&lt;span&gt;omp&lt;/span&gt;&lt;span&gt;fish&lt;/span&gt;&lt;/div&gt;&lt;pre&gt;&lt;code&gt;uv init
uv venv
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;h3&gt;18.3 激活环境&lt;/h3&gt;
&lt;p&gt;fish 用户执行：&lt;/p&gt;
&lt;div&gt;&lt;div&gt;&lt;span&gt;omp&lt;/span&gt;&lt;span&gt;fish&lt;/span&gt;&lt;/div&gt;&lt;pre&gt;&lt;code&gt;source .venv/bin/activate.fish
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;bash / zsh 用户才执行：&lt;/p&gt;
&lt;div&gt;&lt;div&gt;&lt;span&gt;omp&lt;/span&gt;&lt;span&gt;bash/zsh&lt;/span&gt;&lt;/div&gt;&lt;pre&gt;&lt;code&gt;source .venv/bin/activate
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;h3&gt;18.4 安装包&lt;/h3&gt;
&lt;div&gt;&lt;div&gt;&lt;span&gt;omp&lt;/span&gt;&lt;span&gt;fish&lt;/span&gt;&lt;/div&gt;&lt;pre&gt;&lt;code&gt;uv add numpy pandas matplotlib jupyter ipykernel
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;h3&gt;18.5 打开 VS Code&lt;/h3&gt;
&lt;div&gt;&lt;div&gt;&lt;span&gt;omp&lt;/span&gt;&lt;span&gt;fish&lt;/span&gt;&lt;/div&gt;&lt;pre&gt;&lt;code&gt;code .
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;h3&gt;18.6 运行脚本&lt;/h3&gt;
&lt;div&gt;&lt;div&gt;&lt;span&gt;omp&lt;/span&gt;&lt;span&gt;fish&lt;/span&gt;&lt;/div&gt;&lt;pre&gt;&lt;code&gt;python src/hello.py
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;h2&gt;19. 和 20 周计划的关系&lt;/h2&gt;
&lt;p&gt;这篇文档对应 &lt;a href=&quot;/post/ustc-stat-ai-quant-plan/&quot;&gt;20 周 AI / 量化成长计划&lt;/a&gt; 的：&lt;/p&gt;
&lt;div&gt;&lt;div&gt;&lt;span&gt;omp&lt;/span&gt;&lt;span&gt;prompt&lt;/span&gt;&lt;/div&gt;&lt;pre&gt;&lt;code&gt;Phase 1 / Week 01：环境与 Python 基础
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;完成本文后，你就可以进入下一步：&lt;/p&gt;
&lt;div&gt;&lt;div&gt;&lt;span&gt;omp&lt;/span&gt;&lt;span&gt;prompt&lt;/span&gt;&lt;/div&gt;&lt;pre&gt;&lt;code&gt;Week 01：Python 基础语法、函数、列表、字典、字符串、文件读写
Week 02：Python 进阶 + Linux 工作流
Week 03：NumPy / Pandas
Week 04：可视化 + EDA 报告
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;最终目标不是“装好了环境”，而是能持续产出可运行、可复现、可解释的小项目。&lt;/p&gt;
&lt;p&gt;plain &lt;/p&gt;&lt;/article&gt;&lt;p&gt;&lt;/p&gt;
  &lt;/div&gt;
&lt;/section&gt;</content:encoded><category>category:工具</category><category>category:Python基础</category><category>tag:CachyOS</category><category>tag:VS Code</category><category>tag:uv</category><category>tag:fish</category><category>tag:Python环境</category></item><item><title>Week 03：NumPy / Pandas 表格数据处理</title><link>https://39miku.space/post/week03-numpy-pandas</link><guid isPermaLink="false">week03-numpy-pandas</guid><description>Week 03：NumPy / Pandas 表格数据处理。fish-first 终端教学，面向 CachyOS、VS Code、uv 和 Python 学习路线。</description><pubDate>Sat, 25 Jul 2026 19:00:00 GMT</pubDate><content:encoded>
&lt;section&gt;
  &lt;div&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;strong&gt;Oh My Pi&lt;/strong&gt; / weekly tutorial / week03-numpy-pandas&lt;/span&gt;&lt;/div&gt;
  &lt;div&gt;
    &lt;div&gt;&lt;span&gt;miku@cachyos&lt;/span&gt;&lt;span&gt;:~/Code/python-learning&lt;/span&gt;&lt;span&gt;$&lt;/span&gt; &lt;span&gt;omp teach week03-numpy-pandas --fish-first --step-by-step&lt;/span&gt;&lt;/div&gt;
    &lt;div&gt;
      &lt;div&gt;&lt;span&gt;source&lt;/span&gt;&lt;strong&gt;739 行教学文档&lt;/strong&gt;&lt;/div&gt;
      &lt;div&gt;&lt;span&gt;week&lt;/span&gt;&lt;strong&gt;Week 03&lt;/strong&gt;&lt;/div&gt;
      &lt;div&gt;&lt;span&gt;shell&lt;/span&gt;&lt;strong&gt;fish-first 命令版&lt;/strong&gt;&lt;/div&gt;
      &lt;div&gt;&lt;span&gt;backlink&lt;/span&gt;&lt;strong&gt;&lt;a href=&quot;/post/ustc-stat-ai-quant-plan/&quot;&gt;20 周计划&lt;/a&gt;&lt;/strong&gt;&lt;/div&gt;
    &lt;/div&gt;
    &lt;article&gt;
&lt;h1&gt;Week 03：NumPy / Pandas 表格数据处理&lt;/h1&gt;
&lt;blockquote&gt;
&lt;p&gt;返回主线计划：&lt;a href=&quot;/post/ustc-stat-ai-quant-plan/&quot;&gt;USTC 统计学 AI / 量化 20 周成长计划&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;本文命令默认使用 &lt;strong&gt;fish shell&lt;/strong&gt;。本周从“会写 Python 脚本”过渡到“能处理真实表格数据”。对统计学同学来说，NumPy / Pandas 是以后做统计建模、机器学习、时间序列、量化金融的共同地基。&lt;/p&gt;
&lt;p&gt;&lt;a&gt;&lt;/a&gt;&lt;/p&gt;
&lt;h2&gt;0. 本周详细教学：语法、规范、验收&lt;/h2&gt;
&lt;blockquote&gt;
&lt;p&gt;本节不是追加在尾部的复习，而是本周正文的入口。先读这里，再做后面的命令和项目。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h3&gt;0.1 本周真正要学会什么&lt;/h3&gt;
&lt;table&gt;
&lt;thead&gt;&lt;tr&gt;&lt;th&gt;维度&lt;/th&gt;&lt;th&gt;要求&lt;/th&gt;&lt;/tr&gt;&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;&lt;td&gt;知识点&lt;/td&gt;&lt;td&gt;ndarray、DataFrame、缺失值、groupby、merge&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;代码语法&lt;/td&gt;&lt;td&gt;能从空文件写出本周核心脚本，而不是只复制运行&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;程序规范&lt;/td&gt;&lt;td&gt;函数拆分、路径清楚、输入输出明确、错误能解释&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;交付物&lt;/td&gt;&lt;td&gt;&lt;code&gt;notebooks/week03_pandas_eda.ipynb&lt;/code&gt;&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;验收方式&lt;/td&gt;&lt;td&gt;从 fish 终端运行命令，得到可复查的文件或指标&lt;/td&gt;&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;h3&gt;0.2 代码语法精讲&lt;/h3&gt;
&lt;p&gt;下面的代码不是最终答案，而是本周必须理解的最小骨架：&lt;/p&gt;
&lt;div&gt;&lt;div&gt;&lt;span&gt;omp&lt;/span&gt;&lt;span&gt;python&lt;/span&gt;&lt;/div&gt;&lt;pre&gt;&lt;code&gt;import pandas as pd
&lt;/code&gt;&lt;p&gt;&lt;code&gt;df = pd.read_csv(“data/raw.csv”)
df.columns = [c.strip().lower().replace(” ”, ”_”) for c in df.columns]
missing = df.isna().sum().sort_values(ascending=False)
summary = df.groupby(“category”).agg(n=(“value”, “size”), mean_value=(“value”, “mean”))
print(missing)
print(summary)
&lt;/code&gt;&lt;/p&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;&lt;/p&gt;
&lt;p&gt;读代码时按四步检查：输入从哪里来；中间变量的类型和 shape 是什么；函数或脚本输出什么；哪些错误应该显式报出来。&lt;/p&gt;
&lt;h3&gt;0.3 本周程序规范&lt;/h3&gt;
&lt;ul&gt;&lt;li&gt;所有路径用相对路径或 `pathlib.Path`，不要写死 `/home/miku/...`。&lt;/li&gt;&lt;li&gt;核心逻辑进 `src/`，notebook 只做探索和解释。&lt;/li&gt;&lt;li&gt;每个脚本能从 fish 终端运行，并在 README 写出命令。&lt;/li&gt;&lt;li&gt;输出必须落盘到 `reports/`、`figures/` 或 `outputs/`，不能只在屏幕上看。&lt;/li&gt;&lt;/ul&gt;
&lt;h3&gt;0.4 本周练习分层&lt;/h3&gt;
&lt;table&gt;
&lt;thead&gt;&lt;tr&gt;&lt;th&gt;层级&lt;/th&gt;&lt;th&gt;任务&lt;/th&gt;&lt;th&gt;不合格表现&lt;/th&gt;&lt;th&gt;合格验收&lt;/th&gt;&lt;/tr&gt;&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;&lt;td&gt;最小练习&lt;/td&gt;&lt;td&gt;手写上面的最小骨架&lt;/td&gt;&lt;td&gt;只在 notebook 里运行&lt;/td&gt;&lt;td&gt;终端运行成功&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;标准练习&lt;/td&gt;&lt;td&gt;把逻辑拆成函数/模块&lt;/td&gt;&lt;td&gt;一个大脚本从头写到尾&lt;/td&gt;&lt;td&gt;至少 2 个函数，职责清楚&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;项目练习&lt;/td&gt;&lt;td&gt;生成本周交付物 &lt;code&gt;notebooks/week03_pandas_eda.ipynb&lt;/code&gt;&lt;/td&gt;&lt;td&gt;只有屏幕输出&lt;/td&gt;&lt;td&gt;文件落盘，可复查&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;复盘练习&lt;/td&gt;&lt;td&gt;写 3 个错误和修复&lt;/td&gt;&lt;td&gt;只写“已解决”&lt;/td&gt;&lt;td&gt;写清报错、原因、修复、预防&lt;/td&gt;&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;h3&gt;0.5 本周和主线的连接&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;回到总计划：&lt;a href=&quot;/post/ustc-stat-ai-quant-plan/#week-plan&quot;&gt;USTC AI / Quant 练习手册&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;查详细练习索引：&lt;a href=&quot;/post/ustc-stat-ai-quant-plan/#deep-practice-appendix&quot;&gt;技术练习详解&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;查质量评分：&lt;a href=&quot;/post/ustc-stat-ai-quant-plan/#quality-final-gates&quot;&gt;最终质量门槛&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;1. 本周目标&lt;/h2&gt;
&lt;p&gt;完成 Week 03 后，你应该能：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;理解 NumPy array 和 Python list 的区别。&lt;/li&gt;
&lt;li&gt;用 Pandas &lt;code&gt;DataFrame&lt;/code&gt; 表示表格数据。&lt;/li&gt;
&lt;li&gt;读取 CSV、查看数据结构、保存清洗后的 CSV。&lt;/li&gt;
&lt;li&gt;识别和处理缺失值。&lt;/li&gt;
&lt;li&gt;使用 &lt;code&gt;groupby&lt;/code&gt; 做分组统计。&lt;/li&gt;
&lt;li&gt;做一轮简单 EDA：行列数、字段类型、描述统计、缺失值、分组均值。&lt;/li&gt;
&lt;li&gt;输出一个可复现的数据处理脚本和一份简短数据报告。&lt;/li&gt;
&lt;/ol&gt;
&lt;h2&gt;文件布局&lt;/h2&gt;
&lt;p&gt;建议本周交付物：&lt;/p&gt;
&lt;div&gt;&lt;div&gt;&lt;span&gt;omp&lt;/span&gt;&lt;span&gt;prompt&lt;/span&gt;&lt;/div&gt;&lt;pre&gt;&lt;code&gt;week03-numpy-pandas/
├── data/
│   ├── raw/
│   │   └── student_scores.csv
│   └── processed/
│       └── student_scores_clean.csv
├── notebooks/
│   └── week03_pandas_eda.ipynb
├── reports/
│   └── week03_data_report.md
├── src/
│   └── clean_scores.py
├── .venv/
├── pyproject.toml
└── README.md
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Notebook 可以用来探索；脚本用来固定最终流程。以后项目展示时，脚本比“只保存在 notebook 里的零散代码”更容易复现。&lt;/p&gt;
&lt;h2&gt;2. 前置要求&lt;/h2&gt;
&lt;p&gt;你需要已经具备：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;会在 fish 终端里 &lt;code&gt;cd&lt;/code&gt; 到项目目录。&lt;/li&gt;
&lt;li&gt;会使用 &lt;code&gt;uv init&lt;/code&gt;、&lt;code&gt;uv venv&lt;/code&gt;、&lt;code&gt;uv add&lt;/code&gt;。&lt;/li&gt;
&lt;li&gt;知道 list / dict / function 的基本用法。&lt;/li&gt;
&lt;li&gt;能运行 &lt;code&gt;uv run python script.py&lt;/code&gt;。&lt;/li&gt;
&lt;li&gt;VS Code 可以打开项目目录。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;检查基础命令：&lt;/p&gt;
&lt;div&gt;&lt;div&gt;&lt;span&gt;omp&lt;/span&gt;&lt;span&gt;fish&lt;/span&gt;&lt;/div&gt;&lt;pre&gt;&lt;code&gt;python --version
uv --version
pwd
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;如果 Python 或 uv 找不到，先回到 Week 01 环境搭建教程检查。&lt;/p&gt;
&lt;h2&gt;3. 创建 Week 03 项目&lt;/h2&gt;
&lt;div&gt;&lt;div&gt;&lt;span&gt;omp&lt;/span&gt;&lt;span&gt;fish&lt;/span&gt;&lt;/div&gt;&lt;pre&gt;&lt;code&gt;mkdir -p ~/Code/python-learning/week03-numpy-pandas
cd ~/Code/python-learning/week03-numpy-pandas
mkdir -p src data/raw data/processed notebooks reports
uv init
uv venv
source .venv/bin/activate.fish
uv add numpy pandas
code .
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;解释：&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;命令&lt;/th&gt;
&lt;th&gt;作用&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;&lt;tr&gt;
&lt;td&gt;&lt;code&gt;mkdir -p ~/Code/python-learning/week03-numpy-pandas&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;创建本周项目目录&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;mkdir -p src data/raw data/processed notebooks reports&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;区分原始数据、清洗后数据、代码和报告&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;uv init&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;初始化 Python 项目配置&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;uv venv&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;创建 &lt;code&gt;.venv&lt;/code&gt; 虚拟环境&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;source .venv/bin/activate.fish&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;fish 中激活虚拟环境&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;uv add numpy pandas&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;安装 NumPy 和 Pandas&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;code .&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;用 VS Code 打开当前项目&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;&lt;/table&gt;
&lt;p&gt;检查：&lt;/p&gt;
&lt;div&gt;&lt;div&gt;&lt;span&gt;omp&lt;/span&gt;&lt;span&gt;fish&lt;/span&gt;&lt;/div&gt;&lt;pre&gt;&lt;code&gt;which python
uv run python -c &quot;import numpy, pandas; print(numpy.__version__); print(pandas.__version__)&quot;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;如果能打印版本号，环境可用。&lt;/p&gt;
&lt;h2&gt;4. 准备一个练习 CSV&lt;/h2&gt;
&lt;p&gt;先用小数据理解流程。创建 &lt;code&gt;data/raw/student_scores.csv&lt;/code&gt;。&lt;/p&gt;
&lt;p&gt;你可以在 VS Code 手动创建，也可以用 fish：&lt;/p&gt;
&lt;div&gt;&lt;div&gt;&lt;span&gt;omp&lt;/span&gt;&lt;span&gt;fish&lt;/span&gt;&lt;/div&gt;&lt;pre&gt;&lt;code&gt;printf &apos;student,major,gender,math,english,python,study_hours\nA,statistics,F,88,79,85,12\nB,statistics,M,92,81,90,14\nC,finance,F,75,86,78,9\nD,finance,M,80,,82,8\nE,statistics,F,95,88,,15\nF,computer,M,70,76,88,10\nG,computer,F,,90,91,11\nH,statistics,M,84,82,86,13\n&apos; &amp;gt; data/raw/student_scores.csv
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;检查文件：&lt;/p&gt;
&lt;div&gt;&lt;div&gt;&lt;span&gt;omp&lt;/span&gt;&lt;span&gt;fish&lt;/span&gt;&lt;/div&gt;&lt;pre&gt;&lt;code&gt;head data/raw/student_scores.csv
wc -l data/raw/student_scores.csv
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;说明：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;第一行是列名。&lt;/li&gt;
&lt;li&gt;有些成绩故意留空，用来练习缺失值处理。&lt;/li&gt;
&lt;li&gt;&lt;code&gt;major&lt;/code&gt; 和 &lt;code&gt;gender&lt;/code&gt; 是分类变量。&lt;/li&gt;
&lt;li&gt;&lt;code&gt;math&lt;/code&gt;、&lt;code&gt;english&lt;/code&gt;、&lt;code&gt;python&lt;/code&gt;、&lt;code&gt;study_hours&lt;/code&gt; 是数值变量。&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;5. NumPy array 入门&lt;/h2&gt;
&lt;p&gt;NumPy 的核心对象是 array。它适合做数值计算，尤其是向量、矩阵、批量运算。&lt;/p&gt;
&lt;p&gt;创建 &lt;code&gt;src/numpy_intro.py&lt;/code&gt;：&lt;/p&gt;
&lt;div&gt;&lt;div&gt;&lt;span&gt;omp&lt;/span&gt;&lt;span&gt;python&lt;/span&gt;&lt;/div&gt;&lt;pre&gt;&lt;code&gt;import numpy as np
&lt;p&gt;scores = np.array([88, 92, 75, 80, 95])&lt;/p&gt;
&lt;/code&gt;&lt;p&gt;&lt;code&gt;print(scores)
print(scores.mean())
print(scores.std())
print(scores &amp;gt;= 80)
print(scores[scores &amp;gt;= 80])
&lt;/code&gt;&lt;/p&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;&lt;/p&gt;
&lt;p&gt;运行：&lt;/p&gt;
&lt;div&gt;&lt;div&gt;&lt;span&gt;omp&lt;/span&gt;&lt;span&gt;fish&lt;/span&gt;&lt;/div&gt;&lt;pre&gt;&lt;code&gt;uv run python src/numpy_intro.py
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;你会看到：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;scores.mean()&lt;/code&gt; 计算均值。&lt;/li&gt;
&lt;li&gt;&lt;code&gt;scores.std()&lt;/code&gt; 计算标准差。&lt;/li&gt;
&lt;li&gt;&lt;code&gt;scores &amp;gt;= 80&lt;/code&gt; 返回布尔数组。&lt;/li&gt;
&lt;li&gt;&lt;code&gt;scores[scores &amp;gt;= 80]&lt;/code&gt; 用布尔条件筛选数组。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Python list 和 NumPy array 的一个重要区别：&lt;/p&gt;
&lt;div&gt;&lt;div&gt;&lt;span&gt;omp&lt;/span&gt;&lt;span&gt;python&lt;/span&gt;&lt;/div&gt;&lt;pre&gt;&lt;code&gt;python_list = [1, 2, 3]
numpy_array = np.array([1, 2, 3])
&lt;/code&gt;&lt;p&gt;&lt;code&gt;print(python_list * 2)
print(numpy_array * 2)
&lt;/code&gt;&lt;/p&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;&lt;/p&gt;
&lt;p&gt;输出含义：&lt;/p&gt;
&lt;div&gt;&lt;div&gt;&lt;span&gt;omp&lt;/span&gt;&lt;span&gt;prompt&lt;/span&gt;&lt;/div&gt;&lt;pre&gt;&lt;code&gt;[1, 2, 3, 1, 2, 3]  # list 的 * 2 是重复
[2 4 6]             # array 的 * 2 是逐元素乘法
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;这就是为什么数据分析和机器学习大量使用 NumPy。&lt;/p&gt;
&lt;h2&gt;6. Pandas DataFrame 入门&lt;/h2&gt;
&lt;p&gt;Pandas 的核心对象是 &lt;code&gt;DataFrame&lt;/code&gt;，可以理解为“带列名和索引的表格”。&lt;/p&gt;
&lt;p&gt;创建 &lt;code&gt;src/pandas_intro.py&lt;/code&gt;：&lt;/p&gt;
&lt;div&gt;&lt;div&gt;&lt;span&gt;omp&lt;/span&gt;&lt;span&gt;python&lt;/span&gt;&lt;/div&gt;&lt;pre&gt;&lt;code&gt;import pandas as pd
&lt;p&gt;students = pd.DataFrame(
{
“student”: [“A”, “B”, “C”],
“major”: [“statistics”, “statistics”, “finance”],
“math”: [88, 92, 75],
“python”: [85, 90, 78],
}
)&lt;/p&gt;
&lt;/code&gt;&lt;p&gt;&lt;code&gt;print(students)
print(students[“math”])
print(students[[“student”, “python”]])
print(students[students[“math”] &amp;gt;= 80])
&lt;/code&gt;&lt;/p&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;&lt;/p&gt;
&lt;p&gt;运行：&lt;/p&gt;
&lt;div&gt;&lt;div&gt;&lt;span&gt;omp&lt;/span&gt;&lt;span&gt;fish&lt;/span&gt;&lt;/div&gt;&lt;pre&gt;&lt;code&gt;uv run python src/pandas_intro.py
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;常见操作：&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;写法&lt;/th&gt;
&lt;th&gt;作用&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;&lt;tr&gt;
&lt;td&gt;&lt;code&gt;df.head()&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;查看前 5 行&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;df.tail()&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;查看后 5 行&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;df.shape&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;查看行数和列数&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;df.columns&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;查看列名&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;df.dtypes&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;查看每列数据类型&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;df[&quot;math&quot;]&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;取单列，结果是 Series&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;df[[&quot;math&quot;, &quot;python&quot;]]&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;取多列，结果是 DataFrame&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;df[df[&quot;math&quot;] &amp;gt;= 80]&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;按条件筛选行&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;&lt;/table&gt;
&lt;h2&gt;7. 读取 CSV 并快速查看&lt;/h2&gt;
&lt;p&gt;创建 &lt;code&gt;src/read_scores.py&lt;/code&gt;：&lt;/p&gt;
&lt;div&gt;&lt;div&gt;&lt;span&gt;omp&lt;/span&gt;&lt;span&gt;python&lt;/span&gt;&lt;/div&gt;&lt;pre&gt;&lt;code&gt;from pathlib import Path
&lt;p&gt;import pandas as pd&lt;/p&gt;
&lt;p&gt;path = Path(“data/raw/student_scores.csv”)
df = pd.read_csv(path)&lt;/p&gt;
&lt;/code&gt;&lt;p&gt;&lt;code&gt;print(“Shape:”, df.shape)
print(“Columns:”, list(df.columns))
print(“Head:”)
print(df.head())
print(“Data types:”)
print(df.dtypes)
&lt;/code&gt;&lt;/p&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;&lt;/p&gt;
&lt;p&gt;运行：&lt;/p&gt;
&lt;div&gt;&lt;div&gt;&lt;span&gt;omp&lt;/span&gt;&lt;span&gt;fish&lt;/span&gt;&lt;/div&gt;&lt;pre&gt;&lt;code&gt;uv run python src/read_scores.py
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;解释：&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;代码&lt;/th&gt;
&lt;th&gt;作用&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;&lt;tr&gt;
&lt;td&gt;&lt;code&gt;Path(&quot;data/raw/student_scores.csv&quot;)&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;用 pathlib 表示路径&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;pd.read_csv(path)&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;读取 CSV 为 DataFrame&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;df.shape&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;输出 &lt;code&gt;(行数, 列数)&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;df.head()&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;快速看前几行，确认读入正确&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;df.dtypes&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;判断数值列是否真的被识别为数值&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;&lt;/table&gt;
&lt;p&gt;读取真实数据后，第一步永远不是建模，而是看：&lt;/p&gt;
&lt;div&gt;&lt;div&gt;&lt;span&gt;omp&lt;/span&gt;&lt;span&gt;python&lt;/span&gt;&lt;/div&gt;&lt;pre&gt;&lt;code&gt;print(df.head())
print(df.info())
print(df.describe())
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;h2&gt;8. 缺失值检查&lt;/h2&gt;
&lt;p&gt;在数据分析里，缺失值不是小事。它会影响均值、相关性、模型训练和图表解释。&lt;/p&gt;
&lt;p&gt;创建 &lt;code&gt;src/missing_values.py&lt;/code&gt;：&lt;/p&gt;
&lt;div&gt;&lt;div&gt;&lt;span&gt;omp&lt;/span&gt;&lt;span&gt;python&lt;/span&gt;&lt;/div&gt;&lt;pre&gt;&lt;code&gt;from pathlib import Path
&lt;p&gt;import pandas as pd&lt;/p&gt;
&lt;p&gt;path = Path(“data/raw/student_scores.csv”)
df = pd.read_csv(path)&lt;/p&gt;
&lt;p&gt;missing_by_column = df.isna().sum()
missing_total = int(df.isna().sum().sum())
missing_rate = df.isna().mean().sort_values(ascending=False)&lt;/p&gt;
&lt;/code&gt;&lt;p&gt;&lt;code&gt;print(“Missing values by column:”)
print(missing_by_column)
print(“Total missing values:”, missing_total)
print(“Missing rate:”)
print(missing_rate)
&lt;/code&gt;&lt;/p&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;&lt;/p&gt;
&lt;p&gt;运行：&lt;/p&gt;
&lt;div&gt;&lt;div&gt;&lt;span&gt;omp&lt;/span&gt;&lt;span&gt;fish&lt;/span&gt;&lt;/div&gt;&lt;pre&gt;&lt;code&gt;uv run python src/missing_values.py
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;常用方法：&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;写法&lt;/th&gt;
&lt;th&gt;作用&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;&lt;tr&gt;
&lt;td&gt;&lt;code&gt;df.isna()&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;判断每个单元格是否缺失&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;df.isna().sum()&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;每列缺失数量&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;df.isna().mean()&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;每列缺失比例&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;df.dropna()&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;删除包含缺失值的行&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;df.fillna(value)&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;用指定值填补缺失&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;&lt;/table&gt;
&lt;p&gt;初学阶段可以使用简单策略：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;数值成绩列：用该列均值或中位数填补。&lt;/li&gt;
&lt;li&gt;分类列：用众数或 &lt;code&gt;&quot;unknown&quot;&lt;/code&gt; 填补。&lt;/li&gt;
&lt;li&gt;缺失太多的字段：在报告中说明，暂时不用于分析。&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;9. 清洗数据并保存 CSV&lt;/h2&gt;
&lt;p&gt;创建 &lt;code&gt;src/clean_scores.py&lt;/code&gt;：&lt;/p&gt;
&lt;div&gt;&lt;div&gt;&lt;span&gt;omp&lt;/span&gt;&lt;span&gt;python&lt;/span&gt;&lt;/div&gt;&lt;pre&gt;&lt;code&gt;from pathlib import Path
&lt;p&gt;import pandas as pd&lt;/p&gt;
&lt;p&gt;RAW_PATH = Path(“data/raw/student_scores.csv”)
OUTPUT_PATH = Path(“data/processed/student_scores_clean.csv”)
NUMERIC_COLUMNS = [“math”, “english”, “python”, “study_hours”]&lt;/p&gt;
&lt;p&gt;def load_data(path: Path) -&amp;gt; pd.DataFrame:
return pd.read_csv(path)&lt;/p&gt;
&lt;p&gt;def clean_data(df: pd.DataFrame) -&amp;gt; pd.DataFrame:
cleaned = df.copy()&lt;/p&gt;
&lt;p&gt;plain for column in NUMERIC_COLUMNS:
median_value = cleaned[column].median()
cleaned[column] = cleaned[column].fillna(median_value)&lt;/p&gt;
&lt;p&gt;plain cleaned[“average_score”] = cleaned[[“math”, “english”, “python”]].mean(axis=1)
cleaned[“passed_python”] = cleaned[“python”] &amp;gt;= 80&lt;/p&gt;
&lt;p&gt;plain return cleaned&lt;/p&gt;
&lt;p&gt;def save_data(df: pd.DataFrame, path: Path) -&amp;gt; None:
path.parent.mkdir(parents=True, exist_ok=True)
df.to_csv(path, index=False)&lt;/p&gt;
&lt;p&gt;def main() -&amp;gt; None:
df = load_data(RAW_PATH)
cleaned = clean_data(df)
save_data(cleaned, OUTPUT_PATH)
print(f”Saved cleaned data to {OUTPUT_PATH}”)
print(cleaned.head())&lt;/p&gt;
&lt;/code&gt;&lt;p&gt;&lt;code&gt;if &lt;strong&gt;name&lt;/strong&gt; == “&lt;strong&gt;main&lt;/strong&gt;”:
main()
&lt;/code&gt;&lt;/p&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;&lt;/p&gt;
&lt;p&gt;运行：&lt;/p&gt;
&lt;div&gt;&lt;div&gt;&lt;span&gt;omp&lt;/span&gt;&lt;span&gt;fish&lt;/span&gt;&lt;/div&gt;&lt;pre&gt;&lt;code&gt;uv run python src/clean_scores.py
ls data/processed
head data/processed/student_scores_clean.csv
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;重点解释：&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;代码&lt;/th&gt;
&lt;th&gt;作用&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;&lt;tr&gt;
&lt;td&gt;&lt;code&gt;cleaned = df.copy()&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;不直接修改原始 DataFrame，保留原数据&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;median()&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;用中位数作为简单填补值，抗极端值能力比均值稍好&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;fillna(median_value)&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;填补缺失成绩&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;mean(axis=1)&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;对每一行计算平均分&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;to_csv(..., index=False)&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;保存 CSV 时不额外写入索引列&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;&lt;/table&gt;
&lt;h2&gt;10. groupby 分组统计&lt;/h2&gt;
&lt;p&gt;&lt;code&gt;groupby&lt;/code&gt; 是 Pandas 最重要的操作之一。统计学同学可以把它理解为：按某个分类变量分组，然后对每组计算统计量。&lt;/p&gt;
&lt;p&gt;创建 &lt;code&gt;src/groupby_scores.py&lt;/code&gt;：&lt;/p&gt;
&lt;div&gt;&lt;div&gt;&lt;span&gt;omp&lt;/span&gt;&lt;span&gt;python&lt;/span&gt;&lt;/div&gt;&lt;pre&gt;&lt;code&gt;from pathlib import Path
&lt;p&gt;import pandas as pd&lt;/p&gt;
&lt;p&gt;path = Path(“data/processed/student_scores_clean.csv”)
df = pd.read_csv(path)&lt;/p&gt;
&lt;p&gt;major_summary = df.groupby(“major”)[[“math”, “english”, “python”, “average_score”]].mean()
gender_summary = df.groupby(“gender”)[“average_score”].agg([“count”, “mean”, “std”, “min”, “max”])&lt;/p&gt;
&lt;/code&gt;&lt;p&gt;&lt;code&gt;print(“Average scores by major:”)
print(major_summary)
print()
print(“Average score distribution by gender:”)
print(gender_summary)
&lt;/code&gt;&lt;/p&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;&lt;/p&gt;
&lt;p&gt;运行：&lt;/p&gt;
&lt;div&gt;&lt;div&gt;&lt;span&gt;omp&lt;/span&gt;&lt;span&gt;fish&lt;/span&gt;&lt;/div&gt;&lt;pre&gt;&lt;code&gt;uv run python src/groupby_scores.py
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;解释：&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;写法&lt;/th&gt;
&lt;th&gt;含义&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;&lt;tr&gt;
&lt;td&gt;&lt;code&gt;df.groupby(&quot;major&quot;)&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;按专业分组&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;[[&quot;math&quot;, &quot;english&quot;]]&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;选择要统计的列&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;.mean()&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;对每组求均值&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;.agg([&quot;count&quot;, &quot;mean&quot;, &quot;std&quot;])&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;一次计算多个统计量&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;&lt;/table&gt;
&lt;p&gt;注意：分组统计是观察，不是因果结论。比如某专业平均分更高，不能直接说“专业导致成绩更高”，还要考虑样本量、选课背景、数据来源等。&lt;/p&gt;
&lt;h2&gt;11. merge 简介&lt;/h2&gt;
&lt;p&gt;计划里提到 &lt;code&gt;merge&lt;/code&gt;。本周先理解它的用途：把两张表按共同键合并。&lt;/p&gt;
&lt;p&gt;创建 &lt;code&gt;data/raw/major_info.csv&lt;/code&gt;：&lt;/p&gt;
&lt;div&gt;&lt;div&gt;&lt;span&gt;omp&lt;/span&gt;&lt;span&gt;fish&lt;/span&gt;&lt;/div&gt;&lt;pre&gt;&lt;code&gt;printf &apos;major,school\nstatistics,School of Management\nfinance,School of Management\ncomputer,School of Computer Science\n&apos; &amp;gt; data/raw/major_info.csv
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;创建 &lt;code&gt;src/merge_intro.py&lt;/code&gt;：&lt;/p&gt;
&lt;div&gt;&lt;div&gt;&lt;span&gt;omp&lt;/span&gt;&lt;span&gt;python&lt;/span&gt;&lt;/div&gt;&lt;pre&gt;&lt;code&gt;from pathlib import Path
&lt;p&gt;import pandas as pd&lt;/p&gt;
&lt;p&gt;scores = pd.read_csv(Path(“data/processed/student_scores_clean.csv”))
major_info = pd.read_csv(Path(“data/raw/major_info.csv”))&lt;/p&gt;
&lt;/code&gt;&lt;p&gt;&lt;code&gt;merged = scores.merge(major_info, on=“major”, how=“left”)
print(merged[[“student”, “major”, “school”, “average_score”]])
&lt;/code&gt;&lt;/p&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;&lt;/p&gt;
&lt;p&gt;运行：&lt;/p&gt;
&lt;div&gt;&lt;div&gt;&lt;span&gt;omp&lt;/span&gt;&lt;span&gt;fish&lt;/span&gt;&lt;/div&gt;&lt;pre&gt;&lt;code&gt;uv run python src/merge_intro.py
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;常见合并方式：&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;参数&lt;/th&gt;
&lt;th&gt;含义&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;&lt;tr&gt;
&lt;td&gt;&lt;code&gt;on=&quot;major&quot;&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;两张表都用 &lt;code&gt;major&lt;/code&gt; 列作为键&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;how=&quot;left&quot;&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;保留左表所有行，右表匹配不到则为缺失&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;how=&quot;inner&quot;&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;只保留两边都匹配到的行&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;&lt;/table&gt;
&lt;h2&gt;12. 时间索引简介&lt;/h2&gt;
&lt;p&gt;时间序列和量化金融会大量使用时间索引。本周先学会把日期列转成时间类型并设为索引。&lt;/p&gt;
&lt;p&gt;创建 &lt;code&gt;data/raw/daily_price.csv&lt;/code&gt;：&lt;/p&gt;
&lt;div&gt;&lt;div&gt;&lt;span&gt;omp&lt;/span&gt;&lt;span&gt;fish&lt;/span&gt;&lt;/div&gt;&lt;pre&gt;&lt;code&gt;printf &apos;date,close,volume\n2026-07-20,10.2,1200\n2026-07-21,10.5,1500\n2026-07-22,10.1,1100\n2026-07-23,10.8,1800\n&apos; &amp;gt; data/raw/daily_price.csv
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;创建 &lt;code&gt;src/time_index_intro.py&lt;/code&gt;：&lt;/p&gt;
&lt;div&gt;&lt;div&gt;&lt;span&gt;omp&lt;/span&gt;&lt;span&gt;python&lt;/span&gt;&lt;/div&gt;&lt;pre&gt;&lt;code&gt;from pathlib import Path
&lt;p&gt;import pandas as pd&lt;/p&gt;
&lt;p&gt;price = pd.read_csv(Path(“data/raw/daily_price.csv”))
price[“date”] = pd.to_datetime(price[“date”])
price = price.set_index(“date”)
price[“daily_return”] = price[“close”].pct_change()&lt;/p&gt;
&lt;/code&gt;&lt;p&gt;&lt;code&gt;print(price)
print(price.loc[“2026-07-21”:“2026-07-23”])
&lt;/code&gt;&lt;/p&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;&lt;/p&gt;
&lt;p&gt;运行：&lt;/p&gt;
&lt;div&gt;&lt;div&gt;&lt;span&gt;omp&lt;/span&gt;&lt;span&gt;fish&lt;/span&gt;&lt;/div&gt;&lt;pre&gt;&lt;code&gt;uv run python src/time_index_intro.py
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;重点：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;pd.to_datetime&lt;/code&gt; 把字符串日期转成时间类型。&lt;/li&gt;
&lt;li&gt;&lt;code&gt;set_index(&quot;date&quot;)&lt;/code&gt; 把日期作为索引。&lt;/li&gt;
&lt;li&gt;&lt;code&gt;pct_change()&lt;/code&gt; 计算相邻时期的变化率，是量化和时间序列的常见起点。&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;13. 简单 EDA 脚本&lt;/h2&gt;
&lt;p&gt;创建 &lt;code&gt;src/simple_eda.py&lt;/code&gt;，把常用检查固定下来：&lt;/p&gt;
&lt;div&gt;&lt;div&gt;&lt;span&gt;omp&lt;/span&gt;&lt;span&gt;python&lt;/span&gt;&lt;/div&gt;&lt;pre&gt;&lt;code&gt;from pathlib import Path
&lt;p&gt;import pandas as pd&lt;/p&gt;
&lt;p&gt;path = Path(“data/processed/student_scores_clean.csv”)
df = pd.read_csv(path)&lt;/p&gt;
&lt;p&gt;print(“1. Shape”)
print(df.shape)
print()&lt;/p&gt;
&lt;p&gt;print(“2. Columns”)
print(list(df.columns))
print()&lt;/p&gt;
&lt;p&gt;print(“3. Data types”)
print(df.dtypes)
print()&lt;/p&gt;
&lt;p&gt;print(“4. Missing values”)
print(df.isna().sum())
print()&lt;/p&gt;
&lt;p&gt;print(“5. Descriptive statistics”)
print(df.describe())
print()&lt;/p&gt;
&lt;/code&gt;&lt;p&gt;&lt;code&gt;print(“6. Group summary by major”)
print(df.groupby(“major”)[“average_score”].agg([“count”, “mean”, “std”, “min”, “max”]))
&lt;/code&gt;&lt;/p&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;&lt;/p&gt;
&lt;p&gt;运行：&lt;/p&gt;
&lt;div&gt;&lt;div&gt;&lt;span&gt;omp&lt;/span&gt;&lt;span&gt;fish&lt;/span&gt;&lt;/div&gt;&lt;pre&gt;&lt;code&gt;uv run python src/simple_eda.py
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;这份输出可以直接作为 &lt;code&gt;reports/week03_data_report.md&lt;/code&gt; 的素材。&lt;/p&gt;
&lt;h2&gt;14. Notebook vs script：怎么选&lt;/h2&gt;
&lt;p&gt;本周计划的交付物包含 notebook，但你也应该保留脚本。&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;工具&lt;/th&gt;
&lt;th&gt;适合做什么&lt;/th&gt;
&lt;th&gt;不适合做什么&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;&lt;tr&gt;
&lt;td&gt;Notebook&lt;/td&gt;
&lt;td&gt;逐步探索、看表格、尝试图表、写观察&lt;/td&gt;
&lt;td&gt;长期维护的完整清洗流程&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Script&lt;/td&gt;
&lt;td&gt;固化流程、复现结果、命令行运行、Git diff 清楚&lt;/td&gt;
&lt;td&gt;交互式尝试和即时展示&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;&lt;/table&gt;
&lt;p&gt;推荐工作方式：&lt;/p&gt;
&lt;div&gt;&lt;div&gt;&lt;span&gt;omp&lt;/span&gt;&lt;span&gt;prompt&lt;/span&gt;&lt;/div&gt;&lt;pre&gt;&lt;code&gt;1. 在 notebook 中探索：看字段、缺失值、分布、分组统计。
2. 把确认有效的步骤整理到 src/clean_scores.py。
3. 用 src/simple_eda.py 输出稳定统计结果。
4. 在 reports/week03_data_report.md 写结论。
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;如果使用 VS Code Notebook：&lt;/p&gt;
&lt;div&gt;&lt;div&gt;&lt;span&gt;omp&lt;/span&gt;&lt;span&gt;fish&lt;/span&gt;&lt;/div&gt;&lt;pre&gt;&lt;code&gt;uv add ipykernel
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;然后在 VS Code 中新建 &lt;code&gt;notebooks/week03_pandas_eda.ipynb&lt;/code&gt;，选择当前 &lt;code&gt;.venv&lt;/code&gt; 作为 kernel。&lt;/p&gt;
&lt;h2&gt;15. 报告结构 &lt;code&gt;reports/week03_data_report.md&lt;/code&gt;&lt;/h2&gt;
&lt;p&gt;在 VS Code 创建报告文件，建议结构：&lt;/p&gt;
&lt;div&gt;&lt;div&gt;&lt;span&gt;omp&lt;/span&gt;&lt;span&gt;markdown&lt;/span&gt;&lt;/div&gt;&lt;pre&gt;&lt;code&gt;# Week 03 数据处理报告
&lt;h2&gt;1. 数据来源&lt;a href=&quot;#1-数据来源&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;说明数据来自练习 CSV，字段包含学生、专业、性别、数学、英语、Python、学习时长。&lt;/p&gt;
&lt;h2&gt;2. 数据规模&lt;a href=&quot;#2-数据规模&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;原始数据行数：8&lt;/li&gt;
&lt;li&gt;原始数据列数：7&lt;/li&gt;
&lt;li&gt;清洗后数据行数：8&lt;/li&gt;
&lt;li&gt;清洗后数据列数：9&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;3. 缺失值处理&lt;a href=&quot;#3-缺失值处理&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;说明哪些列有缺失值，以及使用中位数填补数值列。&lt;/p&gt;
&lt;h2&gt;4. 描述统计&lt;a href=&quot;#4-描述统计&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;摘录 &lt;code&gt;df.describe()&lt;/code&gt; 中重要结果，例如各科均值、标准差、最小值、最大值。&lt;/p&gt;
&lt;h2&gt;5. 分组统计&lt;a href=&quot;#5-分组统计&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;按 major 比较 average_score，注意样本很小，只能作为练习观察。&lt;/p&gt;
&lt;h2&gt;6. 下一步&lt;a href=&quot;#6-下一步&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;
&lt;/code&gt;&lt;p&gt;&lt;code&gt;Week 04 将对清洗后的数据做可视化，生成直方图、散点图、箱线图和相关性热力图。
&lt;/code&gt;&lt;/p&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;&lt;/p&gt;
&lt;p&gt;报告应该写“观察到了什么”和“不能过度解释什么”。这是统计学训练的一部分。&lt;/p&gt;
&lt;h2&gt;16. 本周练习&lt;/h2&gt;
&lt;h3&gt;练习 A：NumPy 基础&lt;/h3&gt;
&lt;p&gt;创建 &lt;code&gt;src/numpy_exercises.py&lt;/code&gt;：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;创建数组 &lt;code&gt;[1, 2, 3, 4, 5]&lt;/code&gt;。&lt;/li&gt;
&lt;li&gt;计算均值、标准差、最大值、最小值。&lt;/li&gt;
&lt;li&gt;筛选出大于 3 的元素。&lt;/li&gt;
&lt;li&gt;创建一个 2 行 3 列矩阵，并计算每列均值。&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;运行：&lt;/p&gt;
&lt;div&gt;&lt;div&gt;&lt;span&gt;omp&lt;/span&gt;&lt;span&gt;fish&lt;/span&gt;&lt;/div&gt;&lt;pre&gt;&lt;code&gt;uv run python src/numpy_exercises.py
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;h3&gt;练习 B：Pandas 筛选&lt;/h3&gt;
&lt;p&gt;在 &lt;code&gt;student_scores_clean.csv&lt;/code&gt; 中筛选：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;code&gt;major == &quot;statistics&quot;&lt;/code&gt; 的学生。&lt;/li&gt;
&lt;li&gt;&lt;code&gt;python &amp;gt;= 85&lt;/code&gt; 的学生。&lt;/li&gt;
&lt;li&gt;&lt;code&gt;study_hours &amp;gt;= 12&lt;/code&gt; 且 &lt;code&gt;average_score &amp;gt;= 85&lt;/code&gt; 的学生。&lt;/li&gt;
&lt;/ol&gt;
&lt;h3&gt;练习 C：缺失值策略比较&lt;/h3&gt;
&lt;p&gt;分别尝试：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;删除缺失值行。&lt;/li&gt;
&lt;li&gt;用均值填补。&lt;/li&gt;
&lt;li&gt;用中位数填补。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;比较每种策略下 &lt;code&gt;average_score&lt;/code&gt; 的均值是否变化。&lt;/p&gt;
&lt;h3&gt;练习 D：分组统计&lt;/h3&gt;
&lt;p&gt;按 &lt;code&gt;major&lt;/code&gt; 和 &lt;code&gt;gender&lt;/code&gt; 分组，计算：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;样本数。&lt;/li&gt;
&lt;li&gt;&lt;code&gt;average_score&lt;/code&gt; 均值。&lt;/li&gt;
&lt;li&gt;&lt;code&gt;python&lt;/code&gt; 均值。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;提示：可以使用：&lt;/p&gt;
&lt;div&gt;&lt;div&gt;&lt;span&gt;omp&lt;/span&gt;&lt;span&gt;python&lt;/span&gt;&lt;/div&gt;&lt;pre&gt;&lt;code&gt;df.groupby([&quot;major&quot;, &quot;gender&quot;])[[&quot;average_score&quot;, &quot;python&quot;]].mean()
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;h2&gt;17. 验收检查&lt;/h2&gt;
&lt;p&gt;在项目根目录执行：&lt;/p&gt;
&lt;div&gt;&lt;div&gt;&lt;span&gt;omp&lt;/span&gt;&lt;span&gt;fish&lt;/span&gt;&lt;/div&gt;&lt;pre&gt;&lt;code&gt;pwd
ls
source .venv/bin/activate.fish
uv run python -c &quot;import numpy, pandas; print(&apos;ok&apos;)&quot;
uv run python src/clean_scores.py
uv run python src/simple_eda.py
ls data/processed
head data/processed/student_scores_clean.csv
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;验收标准：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;当前目录是 &lt;code&gt;week03-numpy-pandas&lt;/code&gt;。&lt;/li&gt;
&lt;li&gt;&lt;code&gt;source .venv/bin/activate.fish&lt;/code&gt; 不报错。&lt;/li&gt;
&lt;li&gt;NumPy / Pandas 能成功 import。&lt;/li&gt;
&lt;li&gt;&lt;code&gt;src/clean_scores.py&lt;/code&gt; 能生成 &lt;code&gt;data/processed/student_scores_clean.csv&lt;/code&gt;。&lt;/li&gt;
&lt;li&gt;&lt;code&gt;src/simple_eda.py&lt;/code&gt; 能输出 shape、columns、dtypes、missing values、describe、groupby。&lt;/li&gt;
&lt;li&gt;报告 &lt;code&gt;reports/week03_data_report.md&lt;/code&gt; 能解释数据来源、缺失值处理、描述统计和分组统计。&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;18. 常见错误&lt;/h2&gt;
&lt;h3&gt;错误 1：&lt;code&gt;ModuleNotFoundError: No module named &apos;pandas&apos;&lt;/code&gt;&lt;/h3&gt;
&lt;p&gt;说明当前环境没有 pandas，或运行时没有使用 uv 项目环境。&lt;/p&gt;
&lt;p&gt;修复：&lt;/p&gt;
&lt;div&gt;&lt;div&gt;&lt;span&gt;omp&lt;/span&gt;&lt;span&gt;fish&lt;/span&gt;&lt;/div&gt;&lt;pre&gt;&lt;code&gt;cd ~/Code/python-learning/week03-numpy-pandas
uv add pandas numpy
uv run python src/read_scores.py
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;h3&gt;错误 2：&lt;code&gt;FileNotFoundError: data/raw/student_scores.csv&lt;/code&gt;&lt;/h3&gt;
&lt;p&gt;多数情况是当前目录错了。&lt;/p&gt;
&lt;p&gt;检查：&lt;/p&gt;
&lt;div&gt;&lt;div&gt;&lt;span&gt;omp&lt;/span&gt;&lt;span&gt;fish&lt;/span&gt;&lt;/div&gt;&lt;pre&gt;&lt;code&gt;pwd
ls data/raw
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;如果不在项目根目录，执行：&lt;/p&gt;
&lt;div&gt;&lt;div&gt;&lt;span&gt;omp&lt;/span&gt;&lt;span&gt;fish&lt;/span&gt;&lt;/div&gt;&lt;pre&gt;&lt;code&gt;cd ~/Code/python-learning/week03-numpy-pandas
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;h3&gt;错误 3：保存 CSV 后多了一列 &lt;code&gt;Unnamed: 0&lt;/code&gt;&lt;/h3&gt;
&lt;p&gt;原因：保存时把 DataFrame 索引也写进 CSV。&lt;/p&gt;
&lt;p&gt;修复：&lt;/p&gt;
&lt;div&gt;&lt;div&gt;&lt;span&gt;omp&lt;/span&gt;&lt;span&gt;python&lt;/span&gt;&lt;/div&gt;&lt;pre&gt;&lt;code&gt;df.to_csv(path, index=False)
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;h3&gt;错误 4：均值计算报错或结果奇怪&lt;/h3&gt;
&lt;p&gt;原因可能是数字列被读成字符串，或者包含非数字符号。&lt;/p&gt;
&lt;p&gt;检查：&lt;/p&gt;
&lt;div&gt;&lt;div&gt;&lt;span&gt;omp&lt;/span&gt;&lt;span&gt;python&lt;/span&gt;&lt;/div&gt;&lt;pre&gt;&lt;code&gt;print(df.dtypes)
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;必要时转换：&lt;/p&gt;
&lt;div&gt;&lt;div&gt;&lt;span&gt;omp&lt;/span&gt;&lt;span&gt;python&lt;/span&gt;&lt;/div&gt;&lt;pre&gt;&lt;code&gt;df[&quot;math&quot;] = pd.to_numeric(df[&quot;math&quot;], errors=&quot;coerce&quot;)
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;&lt;code&gt;errors=&quot;coerce&quot;&lt;/code&gt; 会把无法转换的值变成缺失值，然后再统一处理。&lt;/p&gt;
&lt;h3&gt;错误 5：Notebook 找不到当前虚拟环境&lt;/h3&gt;
&lt;p&gt;先安装 kernel 支持：&lt;/p&gt;
&lt;div&gt;&lt;div&gt;&lt;span&gt;omp&lt;/span&gt;&lt;span&gt;fish&lt;/span&gt;&lt;/div&gt;&lt;pre&gt;&lt;code&gt;uv add ipykernel
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;然后在 VS Code Notebook 右上角选择当前项目 &lt;code&gt;.venv&lt;/code&gt; 对应的 Python kernel。&lt;/p&gt;
&lt;h2&gt;19. 下一步&lt;/h2&gt;
&lt;p&gt;Week 04 会把本周清洗后的表格数据转成可读图表：直方图看分布，散点图看关系，箱线图看组间差异，相关性热力图看变量关联。最终目标是写出一份结构清楚的 EDA 报告，而不是只生成几张图片。&lt;/p&gt;
&lt;p&gt;plain &lt;/p&gt;&lt;/article&gt;&lt;p&gt;&lt;/p&gt;
  &lt;/div&gt;
&lt;/section&gt;</content:encoded><category>category:工具</category><category>category:数据分析</category><category>tag:NumPy</category><category>tag:pandas</category><category>tag:数据清洗</category><category>tag:EDA</category></item><item><title>Week 02：Python 进阶 + Linux 工作流</title><link>https://39miku.space/post/week02-python-linux-workflow</link><guid isPermaLink="false">week02-python-linux-workflow</guid><description>Week 02：Python 进阶 + Linux 工作流。fish-first 终端教学，面向 CachyOS、VS Code、uv 和 Python 学习路线。</description><pubDate>Sat, 25 Jul 2026 18:00:00 GMT</pubDate><content:encoded>
&lt;section&gt;
  &lt;div&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;strong&gt;Oh My Pi&lt;/strong&gt; / weekly tutorial / week02-python-linux-workflow&lt;/span&gt;&lt;/div&gt;
  &lt;div&gt;
    &lt;div&gt;&lt;span&gt;miku@cachyos&lt;/span&gt;&lt;span&gt;:~/Code/python-learning&lt;/span&gt;&lt;span&gt;$&lt;/span&gt; &lt;span&gt;omp teach week02-python-linux-workflow --fish-first --step-by-step&lt;/span&gt;&lt;/div&gt;
    &lt;div&gt;
      &lt;div&gt;&lt;span&gt;source&lt;/span&gt;&lt;strong&gt;791 行教学文档&lt;/strong&gt;&lt;/div&gt;
      &lt;div&gt;&lt;span&gt;week&lt;/span&gt;&lt;strong&gt;Week 02&lt;/strong&gt;&lt;/div&gt;
      &lt;div&gt;&lt;span&gt;shell&lt;/span&gt;&lt;strong&gt;fish-first 命令版&lt;/strong&gt;&lt;/div&gt;
      &lt;div&gt;&lt;span&gt;backlink&lt;/span&gt;&lt;strong&gt;&lt;a href=&quot;/post/ustc-stat-ai-quant-plan/&quot;&gt;20 周计划&lt;/a&gt;&lt;/strong&gt;&lt;/div&gt;
    &lt;/div&gt;
    &lt;article&gt;
&lt;h1&gt;Week 02：Python 进阶 + Linux 工作流&lt;/h1&gt;
&lt;blockquote&gt;
&lt;p&gt;返回主线计划：&lt;a href=&quot;/post/ustc-stat-ai-quant-plan/&quot;&gt;USTC 统计学 AI / 量化 20 周成长计划&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;本文命令默认使用 &lt;strong&gt;fish shell&lt;/strong&gt;。如果你在 VS Code 终端里看到的不是 fish，可以先执行 &lt;code&gt;fish&lt;/code&gt; 进入 fish；后续不再重复说明。目标不是把 Linux 背熟，而是把“写脚本、运行脚本、查看结果、提交 Git”变成稳定流程。&lt;/p&gt;
&lt;p&gt;&lt;a&gt;&lt;/a&gt;&lt;/p&gt;
&lt;h2&gt;0. 本周详细教学：语法、规范、验收&lt;/h2&gt;
&lt;blockquote&gt;
&lt;p&gt;本节不是追加在尾部的复习，而是本周正文的入口。先读这里，再做后面的命令和项目。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h3&gt;0.1 本周真正要学会什么&lt;/h3&gt;
&lt;table&gt;
&lt;thead&gt;&lt;tr&gt;&lt;th&gt;维度&lt;/th&gt;&lt;th&gt;要求&lt;/th&gt;&lt;/tr&gt;&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;&lt;td&gt;知识点&lt;/td&gt;&lt;td&gt;list/dict、模块、异常、argparse、pathlib、Git commit&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;代码语法&lt;/td&gt;&lt;td&gt;能从空文件写出本周核心脚本，而不是只复制运行&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;程序规范&lt;/td&gt;&lt;td&gt;函数拆分、路径清楚、输入输出明确、错误能解释&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;交付物&lt;/td&gt;&lt;td&gt;&lt;code&gt;src/data_summary.py&lt;/code&gt;&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;验收方式&lt;/td&gt;&lt;td&gt;从 fish 终端运行命令，得到可复查的文件或指标&lt;/td&gt;&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;h3&gt;0.2 代码语法精讲&lt;/h3&gt;
&lt;p&gt;下面的代码不是最终答案，而是本周必须理解的最小骨架：&lt;/p&gt;
&lt;div&gt;&lt;div&gt;&lt;span&gt;omp&lt;/span&gt;&lt;span&gt;python&lt;/span&gt;&lt;/div&gt;&lt;pre&gt;&lt;code&gt;from argparse import ArgumentParser
from pathlib import Path
&lt;p&gt;parser = ArgumentParser()
parser.add_argument(“—input”, required=True)
args = parser.parse_args()
path = Path(args.input)
if not path.exists():
raise SystemExit(f”missing input: {path}”)
print(path.read_text(encoding=“utf-8”)[&lt;/p&gt;&lt;div&gt;&lt;/div&gt;])
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;&lt;/p&gt;
&lt;p&gt;读代码时按四步检查：输入从哪里来；中间变量的类型和 shape 是什么；函数或脚本输出什么；哪些错误应该显式报出来。&lt;/p&gt;
&lt;h3&gt;0.3 本周程序规范&lt;/h3&gt;
&lt;ul&gt;&lt;li&gt;所有路径用相对路径或 `pathlib.Path`，不要写死 `/home/miku/...`。&lt;/li&gt;&lt;li&gt;核心逻辑进 `src/`，notebook 只做探索和解释。&lt;/li&gt;&lt;li&gt;每个脚本能从 fish 终端运行，并在 README 写出命令。&lt;/li&gt;&lt;li&gt;输出必须落盘到 `reports/`、`figures/` 或 `outputs/`，不能只在屏幕上看。&lt;/li&gt;&lt;/ul&gt;
&lt;h3&gt;0.4 本周练习分层&lt;/h3&gt;
&lt;table&gt;
&lt;thead&gt;&lt;tr&gt;&lt;th&gt;层级&lt;/th&gt;&lt;th&gt;任务&lt;/th&gt;&lt;th&gt;不合格表现&lt;/th&gt;&lt;th&gt;合格验收&lt;/th&gt;&lt;/tr&gt;&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;&lt;td&gt;最小练习&lt;/td&gt;&lt;td&gt;手写上面的最小骨架&lt;/td&gt;&lt;td&gt;只在 notebook 里运行&lt;/td&gt;&lt;td&gt;终端运行成功&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;标准练习&lt;/td&gt;&lt;td&gt;把逻辑拆成函数/模块&lt;/td&gt;&lt;td&gt;一个大脚本从头写到尾&lt;/td&gt;&lt;td&gt;至少 2 个函数，职责清楚&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;项目练习&lt;/td&gt;&lt;td&gt;生成本周交付物 &lt;code&gt;src/data_summary.py&lt;/code&gt;&lt;/td&gt;&lt;td&gt;只有屏幕输出&lt;/td&gt;&lt;td&gt;文件落盘，可复查&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;复盘练习&lt;/td&gt;&lt;td&gt;写 3 个错误和修复&lt;/td&gt;&lt;td&gt;只写“已解决”&lt;/td&gt;&lt;td&gt;写清报错、原因、修复、预防&lt;/td&gt;&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;h3&gt;0.5 本周和主线的连接&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;回到总计划：&lt;a href=&quot;/post/ustc-stat-ai-quant-plan/#week-plan&quot;&gt;USTC AI / Quant 练习手册&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;查详细练习索引：&lt;a href=&quot;/post/ustc-stat-ai-quant-plan/#deep-practice-appendix&quot;&gt;技术练习详解&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;查质量评分：&lt;a href=&quot;/post/ustc-stat-ai-quant-plan/#quality-final-gates&quot;&gt;最终质量门槛&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;1. 本周目标&lt;/h2&gt;
&lt;p&gt;完成 Week 02 后，你应该能独立做到：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;用 list comprehension / dict comprehension 写简洁的数据转换代码。&lt;/li&gt;
&lt;li&gt;把重复逻辑封装成函数，而不是把所有代码堆在一个文件里。&lt;/li&gt;
&lt;li&gt;用 &lt;code&gt;pathlib&lt;/code&gt; 处理文件路径，避免手动拼接字符串路径。&lt;/li&gt;
&lt;li&gt;写一个可从命令行运行的 CSV 汇总脚本 &lt;code&gt;src/data_summary.py&lt;/code&gt;。&lt;/li&gt;
&lt;li&gt;在 fish 终端里完成目录导航、文件检查、&lt;code&gt;uv run&lt;/code&gt; 运行、Git status/add/commit。&lt;/li&gt;
&lt;/ol&gt;
&lt;h2&gt;文件布局&lt;/h2&gt;
&lt;p&gt;本周交付物建议放在：&lt;/p&gt;
&lt;div&gt;&lt;div&gt;&lt;span&gt;omp&lt;/span&gt;&lt;span&gt;prompt&lt;/span&gt;&lt;/div&gt;&lt;pre&gt;&lt;code&gt;week02-python-linux-workflow/
├── data/
│   └── scores.csv
├── notes/
│   └── week02-linux-git.md
├── src/
│   └── data_summary.py
├── .venv/
├── pyproject.toml
└── README.md
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;h2&gt;2. 前置要求&lt;/h2&gt;
&lt;p&gt;你需要已经完成 Week 01 的基础环境：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;CachyOS / Arch 系 Linux 可正常打开终端。&lt;/li&gt;
&lt;li&gt;VS Code 已安装，并能用 &lt;code&gt;code .&lt;/code&gt; 打开当前目录。&lt;/li&gt;
&lt;li&gt;&lt;code&gt;uv&lt;/code&gt; 已安装。&lt;/li&gt;
&lt;li&gt;Git 可用：&lt;code&gt;git --version&lt;/code&gt; 能输出版本号。&lt;/li&gt;
&lt;li&gt;知道 Python 基础变量、列表、字典、&lt;code&gt;for&lt;/code&gt; 循环、&lt;code&gt;if&lt;/code&gt; 判断。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;先检查工具：&lt;/p&gt;
&lt;div&gt;&lt;div&gt;&lt;span&gt;omp&lt;/span&gt;&lt;span&gt;fish&lt;/span&gt;&lt;/div&gt;&lt;pre&gt;&lt;code&gt;python --version
uv --version
git --version
pwd
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;解释：&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;命令&lt;/th&gt;
&lt;th&gt;作用&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;&lt;tr&gt;
&lt;td&gt;&lt;code&gt;python --version&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;查看当前终端能找到的 Python 版本&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;uv --version&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;确认 uv 可用&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;git --version&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;确认 Git 可用&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;pwd&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;打印当前位置，避免在错误目录创建项目&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;&lt;/table&gt;
&lt;h2&gt;3. 创建 Week 02 项目目录&lt;/h2&gt;
&lt;p&gt;建议每周一个独立项目，这样 Git 提交、依赖和报告都清楚。&lt;/p&gt;
&lt;div&gt;&lt;div&gt;&lt;span&gt;omp&lt;/span&gt;&lt;span&gt;fish&lt;/span&gt;&lt;/div&gt;&lt;pre&gt;&lt;code&gt;mkdir -p ~/Code/python-learning/week02-python-linux-workflow
cd ~/Code/python-learning/week02-python-linux-workflow
mkdir -p src data notes reports
code .
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;逐句解释：&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;命令&lt;/th&gt;
&lt;th&gt;含义&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;&lt;tr&gt;
&lt;td&gt;&lt;code&gt;mkdir -p ...&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;创建目录；如果上级目录不存在就一起创建&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;cd ...&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;进入 Week 02 项目目录&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;mkdir -p src data notes reports&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;创建源码、数据、笔记、报告目录&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;code .&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;用 VS Code 打开当前项目&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;&lt;/table&gt;
&lt;p&gt;检查当前结构：&lt;/p&gt;
&lt;div&gt;&lt;div&gt;&lt;span&gt;omp&lt;/span&gt;&lt;span&gt;fish&lt;/span&gt;&lt;/div&gt;&lt;pre&gt;&lt;code&gt;pwd
ls
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;你应该看到类似：&lt;/p&gt;
&lt;div&gt;&lt;div&gt;&lt;span&gt;omp&lt;/span&gt;&lt;span&gt;prompt&lt;/span&gt;&lt;/div&gt;&lt;pre&gt;&lt;code&gt;data  notes  reports  src
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;h2&gt;4. 初始化 uv 项目和虚拟环境&lt;/h2&gt;
&lt;p&gt;在项目根目录执行：&lt;/p&gt;
&lt;div&gt;&lt;div&gt;&lt;span&gt;omp&lt;/span&gt;&lt;span&gt;fish&lt;/span&gt;&lt;/div&gt;&lt;pre&gt;&lt;code&gt;uv init
uv venv
source .venv/bin/activate.fish
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;解释：&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;命令&lt;/th&gt;
&lt;th&gt;作用&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;&lt;tr&gt;
&lt;td&gt;&lt;code&gt;uv init&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;生成 &lt;code&gt;pyproject.toml&lt;/code&gt;、&lt;code&gt;README.md&lt;/code&gt;、示例入口文件&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;uv venv&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;创建当前项目专用虚拟环境 &lt;code&gt;.venv/&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;source .venv/bin/activate.fish&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;在 fish shell 中激活虚拟环境&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;&lt;/table&gt;
&lt;p&gt;激活后检查：&lt;/p&gt;
&lt;div&gt;&lt;div&gt;&lt;span&gt;omp&lt;/span&gt;&lt;span&gt;fish&lt;/span&gt;&lt;/div&gt;&lt;pre&gt;&lt;code&gt;which python
python --version
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;如果 &lt;code&gt;which python&lt;/code&gt; 输出路径包含当前项目的 &lt;code&gt;.venv/bin/python&lt;/code&gt;，说明环境正确。&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;fish 注意：不要在 fish 里执行 Bash / Zsh 版本的虚拟环境激活脚本；fish 应使用 &lt;code&gt;source .venv/bin/activate.fish&lt;/code&gt;。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;本周需要 pandas 来读 CSV：&lt;/p&gt;
&lt;div&gt;&lt;div&gt;&lt;span&gt;omp&lt;/span&gt;&lt;span&gt;fish&lt;/span&gt;&lt;/div&gt;&lt;pre&gt;&lt;code&gt;uv add pandas
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;之后运行脚本时优先使用：&lt;/p&gt;
&lt;div&gt;&lt;div&gt;&lt;span&gt;omp&lt;/span&gt;&lt;span&gt;fish&lt;/span&gt;&lt;/div&gt;&lt;pre&gt;&lt;code&gt;uv run python src/data_summary.py --help
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;&lt;code&gt;uv run&lt;/code&gt; 会自动使用项目环境，比“碰运气使用系统 Python”更稳。&lt;/p&gt;
&lt;h2&gt;5. fish 终端导航：从“知道在哪”开始&lt;/h2&gt;
&lt;p&gt;Linux 工作流第一件事不是命令多，而是永远知道自己在什么目录。&lt;/p&gt;
&lt;div&gt;&lt;div&gt;&lt;span&gt;omp&lt;/span&gt;&lt;span&gt;fish&lt;/span&gt;&lt;/div&gt;&lt;pre&gt;&lt;code&gt;pwd
ls
cd src
pwd
cd ..
ls data
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;解释：&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;命令&lt;/th&gt;
&lt;th&gt;作用&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;&lt;tr&gt;
&lt;td&gt;&lt;code&gt;pwd&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;打印当前目录&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;ls&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;列出当前目录文件&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;cd src&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;进入 &lt;code&gt;src&lt;/code&gt; 子目录&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;cd ..&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;回到上一级目录&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;ls data&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;不进入 &lt;code&gt;data&lt;/code&gt;，直接查看它里面有什么&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;&lt;/table&gt;
&lt;p&gt;常用路径写法：&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;写法&lt;/th&gt;
&lt;th&gt;含义&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;&lt;tr&gt;
&lt;td&gt;&lt;code&gt;.&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;当前目录&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;..&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;上一级目录&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;~&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;当前用户主目录，例如 &lt;code&gt;/home/miku&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;~/Code&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;主目录下的 &lt;code&gt;Code&lt;/code&gt; 文件夹&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;src/data_summary.py&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;当前目录下的相对路径&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;&lt;/table&gt;
&lt;p&gt;建议养成习惯：运行任何脚本前先看一眼：&lt;/p&gt;
&lt;div&gt;&lt;div&gt;&lt;span&gt;omp&lt;/span&gt;&lt;span&gt;fish&lt;/span&gt;&lt;/div&gt;&lt;pre&gt;&lt;code&gt;pwd
ls
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;如果当前目录不是项目根目录，先 &lt;code&gt;cd&lt;/code&gt; 回来：&lt;/p&gt;
&lt;div&gt;&lt;div&gt;&lt;span&gt;omp&lt;/span&gt;&lt;span&gt;fish&lt;/span&gt;&lt;/div&gt;&lt;pre&gt;&lt;code&gt;cd ~/Code/python-learning/week02-python-linux-workflow
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;h2&gt;6. 创建练习 CSV 数据&lt;/h2&gt;
&lt;p&gt;可以先用 VS Code 新建 &lt;code&gt;data/scores.csv&lt;/code&gt;，内容如下：&lt;/p&gt;
&lt;div&gt;&lt;div&gt;&lt;span&gt;omp&lt;/span&gt;&lt;span&gt;csv&lt;/span&gt;&lt;/div&gt;&lt;pre&gt;&lt;code&gt;student,major,math,english,python
A,statistics,88,79,85
B,statistics,92,81,90
C,finance,75,86,78
D,finance,80,,82
E,statistics,95,88,
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;如果想用 fish 命令创建，也可以执行：&lt;/p&gt;
&lt;div&gt;&lt;div&gt;&lt;span&gt;omp&lt;/span&gt;&lt;span&gt;fish&lt;/span&gt;&lt;/div&gt;&lt;pre&gt;&lt;code&gt;printf &apos;student,major,math,english,python\nA,statistics,88,79,85\nB,statistics,92,81,90\nC,finance,75,86,78\nD,finance,80,,82\nE,statistics,95,88,\n&apos; &amp;gt; data/scores.csv
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;检查文件：&lt;/p&gt;
&lt;div&gt;&lt;div&gt;&lt;span&gt;omp&lt;/span&gt;&lt;span&gt;fish&lt;/span&gt;&lt;/div&gt;&lt;pre&gt;&lt;code&gt;cat data/scores.csv
wc -l data/scores.csv
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;解释：&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;命令&lt;/th&gt;
&lt;th&gt;作用&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;&lt;tr&gt;
&lt;td&gt;&lt;code&gt;cat&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;把整个小文件打印出来，适合查看短文件&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;wc -l&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;统计行数；CSV 第一行表头也算一行&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;&lt;/table&gt;
&lt;p&gt;真实数据通常不建议用 &lt;code&gt;cat&lt;/code&gt; 一次打印几万行。可以用：&lt;/p&gt;
&lt;div&gt;&lt;div&gt;&lt;span&gt;omp&lt;/span&gt;&lt;span&gt;fish&lt;/span&gt;&lt;/div&gt;&lt;pre&gt;&lt;code&gt;head data/scores.csv
tail data/scores.csv
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;&lt;code&gt;head&lt;/code&gt; 看开头，&lt;code&gt;tail&lt;/code&gt; 看结尾。&lt;/p&gt;
&lt;h2&gt;7. Python 进阶一：list comprehension&lt;/h2&gt;
&lt;p&gt;list comprehension 适合把“循环生成新列表”的代码写得更清楚。&lt;/p&gt;
&lt;p&gt;普通写法：&lt;/p&gt;
&lt;div&gt;&lt;div&gt;&lt;span&gt;omp&lt;/span&gt;&lt;span&gt;python&lt;/span&gt;&lt;/div&gt;&lt;pre&gt;&lt;code&gt;scores = [88, 92, 75, 80, 95]
passed = []
&lt;p&gt;for score in scores:
if score &amp;gt;= 80:
passed.append(score)&lt;/p&gt;
&lt;/code&gt;&lt;p&gt;&lt;code&gt;print(passed)
&lt;/code&gt;&lt;/p&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;&lt;/p&gt;
&lt;p&gt;list comprehension 写法：&lt;/p&gt;
&lt;div&gt;&lt;div&gt;&lt;span&gt;omp&lt;/span&gt;&lt;span&gt;python&lt;/span&gt;&lt;/div&gt;&lt;pre&gt;&lt;code&gt;scores = [88, 92, 75, 80, 95]
passed = [score for score in scores if score &amp;gt;= 80]
print(passed)
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;读法是：&lt;/p&gt;
&lt;div&gt;&lt;div&gt;&lt;span&gt;omp&lt;/span&gt;&lt;span&gt;prompt&lt;/span&gt;&lt;/div&gt;&lt;pre&gt;&lt;code&gt;[要放进新列表的值 for 临时变量 in 原列表 if 条件]
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;常见例子：&lt;/p&gt;
&lt;div&gt;&lt;div&gt;&lt;span&gt;omp&lt;/span&gt;&lt;span&gt;python&lt;/span&gt;&lt;/div&gt;&lt;pre&gt;&lt;code&gt;names = [&quot;alice&quot;, &quot;bob&quot;, &quot;cathy&quot;]
upper_names = [name.upper() for name in names]
&lt;p&gt;scores = [88, 92, 75, 80, 95]
labels = [“pass” if score &amp;gt;= 80 else “review” for score in scores]&lt;/p&gt;
&lt;/code&gt;&lt;p&gt;&lt;code&gt;raw_values = [“88”, “92”, &quot;&quot;, “80”]
clean_values = [int(value) for value in raw_values if value != &quot;&quot;]
&lt;/code&gt;&lt;/p&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;&lt;/p&gt;
&lt;p&gt;注意：不要为了炫技把太复杂的逻辑塞进一行。如果一行里同时有多层循环、多层条件，普通 &lt;code&gt;for&lt;/code&gt; 循环更适合。&lt;/p&gt;
&lt;h2&gt;8. Python 进阶二：dict comprehension&lt;/h2&gt;
&lt;p&gt;dict comprehension 用来从已有数据生成字典。&lt;/p&gt;
&lt;div&gt;&lt;div&gt;&lt;span&gt;omp&lt;/span&gt;&lt;span&gt;python&lt;/span&gt;&lt;/div&gt;&lt;pre&gt;&lt;code&gt;names = [&quot;math&quot;, &quot;english&quot;, &quot;python&quot;]
scores = [88, 79, 85]
score_dict = {name: score for name, score in zip(names, scores)}
print(score_dict)
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;输出：&lt;/p&gt;
&lt;div&gt;&lt;div&gt;&lt;span&gt;omp&lt;/span&gt;&lt;span&gt;prompt&lt;/span&gt;&lt;/div&gt;&lt;pre&gt;&lt;code&gt;{&apos;math&apos;: 88, &apos;english&apos;: 79, &apos;python&apos;: 85}
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;筛选字典：&lt;/p&gt;
&lt;div&gt;&lt;div&gt;&lt;span&gt;omp&lt;/span&gt;&lt;span&gt;python&lt;/span&gt;&lt;/div&gt;&lt;pre&gt;&lt;code&gt;score_dict = {&quot;math&quot;: 88, &quot;english&quot;: 79, &quot;python&quot;: 85}
high_scores = {subject: score for subject, score in score_dict.items() if score &amp;gt;= 80}
print(high_scores)
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;输出：&lt;/p&gt;
&lt;div&gt;&lt;div&gt;&lt;span&gt;omp&lt;/span&gt;&lt;span&gt;prompt&lt;/span&gt;&lt;/div&gt;&lt;pre&gt;&lt;code&gt;{&apos;math&apos;: 88, &apos;python&apos;: 85}
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;在数据分析脚本中，dict comprehension 常用于生成摘要结果：&lt;/p&gt;
&lt;div&gt;&lt;div&gt;&lt;span&gt;omp&lt;/span&gt;&lt;span&gt;python&lt;/span&gt;&lt;/div&gt;&lt;pre&gt;&lt;code&gt;missing_counts = {column: 0 for column in [&quot;math&quot;, &quot;english&quot;, &quot;python&quot;]}
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;h2&gt;9. Python 进阶三：函数&lt;/h2&gt;
&lt;p&gt;函数的作用是把一段有明确职责的逻辑命名，方便重复使用和测试。&lt;/p&gt;
&lt;p&gt;不推荐把所有逻辑写成这样：&lt;/p&gt;
&lt;div&gt;&lt;div&gt;&lt;span&gt;omp&lt;/span&gt;&lt;span&gt;python&lt;/span&gt;&lt;/div&gt;&lt;pre&gt;&lt;code&gt;import pandas as pd
&lt;/code&gt;&lt;p&gt;&lt;code&gt;df = pd.read_csv(“data/scores.csv”)
print(df.shape)
print(df.isna().sum())
print(df.mean(numeric_only=True))
&lt;/code&gt;&lt;/p&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;&lt;/p&gt;
&lt;p&gt;更推荐拆成函数：&lt;/p&gt;
&lt;div&gt;&lt;div&gt;&lt;span&gt;omp&lt;/span&gt;&lt;span&gt;python&lt;/span&gt;&lt;/div&gt;&lt;pre&gt;&lt;code&gt;from pathlib import Path
&lt;p&gt;import pandas as pd&lt;/p&gt;
&lt;p&gt;def load_csv(path: Path) -&amp;gt; pd.DataFrame:
return pd.read_csv(path)&lt;/p&gt;
&lt;/code&gt;&lt;p&gt;&lt;code&gt;def summarize(df: pd.DataFrame) -&amp;gt; dict:
return {
“rows”: len(df),
“columns”: len(df.columns),
“missing_values”: int(df.isna().sum().sum()),
“numeric_means”: df.mean(numeric_only=True).to_dict(),
}
&lt;/code&gt;&lt;/p&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;&lt;/p&gt;
&lt;p&gt;判断函数是否清楚，可以问三个问题：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;函数名是否说明它做什么？&lt;/li&gt;
&lt;li&gt;输入参数是否明确？&lt;/li&gt;
&lt;li&gt;返回值是否明确？&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;对初学者来说，函数不需要一开始写得很抽象。先把“读取数据”“计算摘要”“打印摘要”拆开，就已经很好。&lt;/p&gt;
&lt;h2&gt;10. Python 进阶四：pathlib&lt;/h2&gt;
&lt;p&gt;&lt;code&gt;pathlib&lt;/code&gt; 是 Python 处理路径的现代方式。&lt;/p&gt;
&lt;p&gt;不要这样手动拼路径：&lt;/p&gt;
&lt;div&gt;&lt;div&gt;&lt;span&gt;omp&lt;/span&gt;&lt;span&gt;python&lt;/span&gt;&lt;/div&gt;&lt;pre&gt;&lt;code&gt;path = &quot;data&quot; + &quot;/&quot; + &quot;scores.csv&quot;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;推荐这样：&lt;/p&gt;
&lt;div&gt;&lt;div&gt;&lt;span&gt;omp&lt;/span&gt;&lt;span&gt;python&lt;/span&gt;&lt;/div&gt;&lt;pre&gt;&lt;code&gt;from pathlib import Path
&lt;/code&gt;&lt;p&gt;&lt;code&gt;project_root = Path.cwd()
data_path = project_root / “data” / “scores.csv”
print(data_path)
&lt;/code&gt;&lt;/p&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;&lt;/p&gt;
&lt;p&gt;常用方法：&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;写法&lt;/th&gt;
&lt;th&gt;作用&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;&lt;tr&gt;
&lt;td&gt;&lt;code&gt;Path.cwd()&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;当前运行命令的目录&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;Path(&quot;data/scores.csv&quot;)&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;创建相对路径对象&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;path.exists()&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;判断路径是否存在&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;path.parent&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;获取父目录&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;path.name&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;获取文件名&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;path.suffix&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;获取扩展名，例如 &lt;code&gt;.csv&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;&lt;/table&gt;
&lt;p&gt;在 CLI 脚本里，用户传入的路径建议立刻转成 &lt;code&gt;Path&lt;/code&gt;：&lt;/p&gt;
&lt;div&gt;&lt;div&gt;&lt;span&gt;omp&lt;/span&gt;&lt;span&gt;python&lt;/span&gt;&lt;/div&gt;&lt;pre&gt;&lt;code&gt;csv_path = Path(args.csv)
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;h2&gt;11. 编写 CLI 脚本 &lt;code&gt;src/data_summary.py&lt;/code&gt;&lt;/h2&gt;
&lt;p&gt;在 VS Code 中创建文件：&lt;/p&gt;
&lt;div&gt;&lt;div&gt;&lt;span&gt;omp&lt;/span&gt;&lt;span&gt;prompt&lt;/span&gt;&lt;/div&gt;&lt;pre&gt;&lt;code&gt;src/data_summary.py
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;写入下面代码：&lt;/p&gt;
&lt;div&gt;&lt;div&gt;&lt;span&gt;omp&lt;/span&gt;&lt;span&gt;python&lt;/span&gt;&lt;/div&gt;&lt;pre&gt;&lt;code&gt;from argparse import ArgumentParser
from pathlib import Path
&lt;p&gt;import pandas as pd&lt;/p&gt;
&lt;p&gt;def parse_args():
parser = ArgumentParser(description=“Summarize a CSV file.”)
parser.add_argument(“csv”, help=“Path to the input CSV file”)
return parser.parse_args()&lt;/p&gt;
&lt;p&gt;def load_csv(path: Path) -&amp;gt; pd.DataFrame:
if not path.exists():
raise FileNotFoundError(f”CSV file not found: {path}”)
if path.suffix.lower() != “.csv”:
raise ValueError(f”Expected a .csv file, got: {path}”)
return pd.read_csv(path)&lt;/p&gt;
&lt;p&gt;def build_summary(df: pd.DataFrame) -&amp;gt; dict:
numeric_means = df.mean(numeric_only=True).to_dict()
return {
“rows”: len(df),
“columns”: len(df.columns),
“missing_values”: int(df.isna().sum().sum()),
“numeric_means”: numeric_means,
}&lt;/p&gt;
&lt;p&gt;def print_summary(summary: dict) -&amp;gt; None:
print(“CSV Summary”)
print(”===========”)
print(f”Rows: {summary[‘rows’]}”)
print(f”Columns: {summary[‘columns’]}”)
print(f”Missing values: {summary[‘missing_values’]}”)
print(“Numeric column means:”)&lt;/p&gt;
&lt;p&gt;plain for column, value in summary[“numeric_means”].items():
print(f”- {column}: {value:.2f}”)&lt;/p&gt;
&lt;p&gt;def main() -&amp;gt; None:
args = parse_args()
csv_path = Path(args.csv)
df = load_csv(csv_path)
summary = build_summary(df)
print_summary(summary)&lt;/p&gt;
&lt;/code&gt;&lt;p&gt;&lt;code&gt;if &lt;strong&gt;name&lt;/strong&gt; == “&lt;strong&gt;main&lt;/strong&gt;”:
main()
&lt;/code&gt;&lt;/p&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;&lt;/p&gt;
&lt;p&gt;这段脚本分成四层：&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;函数&lt;/th&gt;
&lt;th&gt;职责&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;&lt;tr&gt;
&lt;td&gt;&lt;code&gt;parse_args&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;读取命令行参数&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;load_csv&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;检查路径并读取 CSV&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;build_summary&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;计算行数、列数、缺失值、均值&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;print_summary&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;把结果打印给用户&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;main&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;串起完整流程&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;&lt;/table&gt;
&lt;h2&gt;12. 运行 CLI 脚本&lt;/h2&gt;
&lt;p&gt;先看帮助：&lt;/p&gt;
&lt;div&gt;&lt;div&gt;&lt;span&gt;omp&lt;/span&gt;&lt;span&gt;fish&lt;/span&gt;&lt;/div&gt;&lt;pre&gt;&lt;code&gt;uv run python src/data_summary.py --help
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;你应该看到脚本说明和参数说明。&lt;/p&gt;
&lt;p&gt;再运行实际数据：&lt;/p&gt;
&lt;div&gt;&lt;div&gt;&lt;span&gt;omp&lt;/span&gt;&lt;span&gt;fish&lt;/span&gt;&lt;/div&gt;&lt;pre&gt;&lt;code&gt;uv run python src/data_summary.py data/scores.csv
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;期望输出类似：&lt;/p&gt;
&lt;div&gt;&lt;div&gt;&lt;span&gt;omp&lt;/span&gt;&lt;span&gt;prompt&lt;/span&gt;&lt;/div&gt;&lt;pre&gt;&lt;code&gt;CSV Summary
===========
Rows: 5
Columns: 5
Missing values: 2
Numeric column means:
- math: 86.00
- english: 83.50
- python: 83.75
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;如果你已经激活虚拟环境，也可以运行：&lt;/p&gt;
&lt;div&gt;&lt;div&gt;&lt;span&gt;omp&lt;/span&gt;&lt;span&gt;fish&lt;/span&gt;&lt;/div&gt;&lt;pre&gt;&lt;code&gt;python src/data_summary.py data/scores.csv
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;但为了可复现，本教程更推荐在说明文档里写 &lt;code&gt;uv run python ...&lt;/code&gt;。&lt;/p&gt;
&lt;h2&gt;13. 常用 Linux 文件检查命令&lt;/h2&gt;
&lt;p&gt;本周不要求成为 Linux 专家，但这些命令会反复出现。&lt;/p&gt;
&lt;div&gt;&lt;div&gt;&lt;span&gt;omp&lt;/span&gt;&lt;span&gt;fish&lt;/span&gt;&lt;/div&gt;&lt;pre&gt;&lt;code&gt;pwd
ls
cat data/scores.csv
head data/scores.csv
tail data/scores.csv
wc -l data/scores.csv
grep statistics data/scores.csv
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;解释：&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;命令&lt;/th&gt;
&lt;th&gt;适合场景&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;&lt;tr&gt;
&lt;td&gt;&lt;code&gt;pwd&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;确认当前目录&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;ls&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;看目录内容&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;cat&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;查看很短的小文件&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;head&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;查看文件开头&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;tail&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;查看文件结尾&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;wc -l&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;统计行数&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;grep statistics ...&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;找包含 &lt;code&gt;statistics&lt;/code&gt; 的行&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;&lt;/table&gt;
&lt;p&gt;复制、移动、删除文件要更谨慎：&lt;/p&gt;
&lt;div&gt;&lt;div&gt;&lt;span&gt;omp&lt;/span&gt;&lt;span&gt;fish&lt;/span&gt;&lt;/div&gt;&lt;pre&gt;&lt;code&gt;cp data/scores.csv data/scores_backup.csv
mv data/scores_backup.csv data/scores_copy.csv
rm data/scores_copy.csv
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;解释：&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;命令&lt;/th&gt;
&lt;th&gt;作用&lt;/th&gt;
&lt;th&gt;风险&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;&lt;tr&gt;
&lt;td&gt;&lt;code&gt;cp&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;复制文件&lt;/td&gt;
&lt;td&gt;可能覆盖同名文件&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;mv&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;移动或重命名文件&lt;/td&gt;
&lt;td&gt;可能把文件移到意外位置&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;rm&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;删除文件&lt;/td&gt;
&lt;td&gt;删除后通常不能轻松恢复&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;&lt;/table&gt;
&lt;p&gt;初学阶段，执行 &lt;code&gt;rm&lt;/code&gt; 前先 &lt;code&gt;ls&lt;/code&gt; 确认目标。&lt;/p&gt;
&lt;h2&gt;14. Git 基础：status / add / commit&lt;/h2&gt;
&lt;p&gt;在项目根目录初始化 Git：&lt;/p&gt;
&lt;div&gt;&lt;div&gt;&lt;span&gt;omp&lt;/span&gt;&lt;span&gt;fish&lt;/span&gt;&lt;/div&gt;&lt;pre&gt;&lt;code&gt;git init
git status
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;&lt;code&gt;git status&lt;/code&gt; 是最重要的 Git 命令之一。任何不确定的时候先运行它。&lt;/p&gt;
&lt;p&gt;建议创建 &lt;code&gt;.gitignore&lt;/code&gt;，避免把虚拟环境提交进去。在 VS Code 新建 &lt;code&gt;.gitignore&lt;/code&gt;，写入：&lt;/p&gt;
&lt;div&gt;&lt;div&gt;&lt;span&gt;omp&lt;/span&gt;&lt;span&gt;prompt&lt;/span&gt;&lt;/div&gt;&lt;pre&gt;&lt;code&gt;.venv/
__pycache__/
.ipynb_checkpoints/
*.pyc
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;再次检查：&lt;/p&gt;
&lt;div&gt;&lt;div&gt;&lt;span&gt;omp&lt;/span&gt;&lt;span&gt;fish&lt;/span&gt;&lt;/div&gt;&lt;pre&gt;&lt;code&gt;git status
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;把本周文件加入暂存区：&lt;/p&gt;
&lt;div&gt;&lt;div&gt;&lt;span&gt;omp&lt;/span&gt;&lt;span&gt;fish&lt;/span&gt;&lt;/div&gt;&lt;pre&gt;&lt;code&gt;git add pyproject.toml uv.lock README.md .gitignore src data notes
git status
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;如果某个文件不存在，Git 会提示 pathspec 错误。可以先用 &lt;code&gt;ls&lt;/code&gt; 看实际文件，再重新 &lt;code&gt;git add&lt;/code&gt; 存在的文件。&lt;/p&gt;
&lt;p&gt;提交：&lt;/p&gt;
&lt;div&gt;&lt;div&gt;&lt;span&gt;omp&lt;/span&gt;&lt;span&gt;fish&lt;/span&gt;&lt;/div&gt;&lt;pre&gt;&lt;code&gt;git commit -m &quot;Add week02 Python Linux workflow exercise&quot;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;提交后检查：&lt;/p&gt;
&lt;div&gt;&lt;div&gt;&lt;span&gt;omp&lt;/span&gt;&lt;span&gt;fish&lt;/span&gt;&lt;/div&gt;&lt;pre&gt;&lt;code&gt;git status
git log --oneline -3
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;期望看到：&lt;/p&gt;
&lt;div&gt;&lt;div&gt;&lt;span&gt;omp&lt;/span&gt;&lt;span&gt;prompt&lt;/span&gt;&lt;/div&gt;&lt;pre&gt;&lt;code&gt;nothing to commit, working tree clean
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;以及最近提交列表中包含你的提交信息。&lt;/p&gt;
&lt;h2&gt;15. 写学习笔记 &lt;code&gt;notes/week02-linux-git.md&lt;/code&gt;&lt;/h2&gt;
&lt;p&gt;在 VS Code 创建：&lt;/p&gt;
&lt;div&gt;&lt;div&gt;&lt;span&gt;omp&lt;/span&gt;&lt;span&gt;prompt&lt;/span&gt;&lt;/div&gt;&lt;pre&gt;&lt;code&gt;notes/week02-linux-git.md
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;建议包含四块：&lt;/p&gt;
&lt;div&gt;&lt;div&gt;&lt;span&gt;omp&lt;/span&gt;&lt;span&gt;markdown&lt;/span&gt;&lt;/div&gt;&lt;pre&gt;&lt;code&gt;# Week 02 Linux + Git 笔记
&lt;h2&gt;今天学会的命令&lt;a href=&quot;#今天学会的命令&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;pwd：查看当前目录&lt;/li&gt;
&lt;li&gt;ls：查看文件&lt;/li&gt;
&lt;li&gt;cd：切换目录&lt;/li&gt;
&lt;li&gt;uv run：用项目环境运行 Python&lt;/li&gt;
&lt;li&gt;git status：查看 Git 状态&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;今天写的脚本&lt;a href=&quot;#今天写的脚本&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;src/data_summary.py：读取 CSV，输出行数、列数、缺失值、数值列均值。&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;遇到的问题&lt;a href=&quot;#遇到的问题&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;问题：fish 中误用了 Bash 激活脚本。&lt;/li&gt;
&lt;li&gt;解决：使用 source .venv/bin/activate.fish。&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;下次要改进&lt;a href=&quot;#下次要改进&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;
&lt;/code&gt;&lt;ul&gt;&lt;code&gt;
&lt;/code&gt;&lt;li&gt;&lt;code&gt;给脚本增加输出到 Markdown 文件的功能。
&lt;/code&gt;&lt;/li&gt;&lt;/ul&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;笔记不是为了凑字数，而是为了让两周后的你能复盘：当时怎么跑、哪里报错、怎么修。&lt;/p&gt;
&lt;h2&gt;16. 本周练习&lt;/h2&gt;
&lt;h3&gt;练习 A：list comprehension&lt;/h3&gt;
&lt;p&gt;创建 &lt;code&gt;src/comprehension_practice.py&lt;/code&gt;，完成：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;给定分数列表 &lt;code&gt;[58, 72, 81, 90, 45, 66]&lt;/code&gt;，生成及格分数列表。&lt;/li&gt;
&lt;li&gt;生成所有分数的平方列表。&lt;/li&gt;
&lt;li&gt;把名字列表 &lt;code&gt;[&apos;alice&apos;, &apos;bob&apos;, &apos;cathy&apos;]&lt;/code&gt; 转成首字母大写。&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;运行：&lt;/p&gt;
&lt;div&gt;&lt;div&gt;&lt;span&gt;omp&lt;/span&gt;&lt;span&gt;fish&lt;/span&gt;&lt;/div&gt;&lt;pre&gt;&lt;code&gt;uv run python src/comprehension_practice.py
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;h3&gt;练习 B：dict comprehension&lt;/h3&gt;
&lt;p&gt;创建科目到分数的字典：&lt;/p&gt;
&lt;div&gt;&lt;div&gt;&lt;span&gt;omp&lt;/span&gt;&lt;span&gt;python&lt;/span&gt;&lt;/div&gt;&lt;pre&gt;&lt;code&gt;subjects = [&quot;math&quot;, &quot;english&quot;, &quot;python&quot;]
scores = [88, 79, 85]
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;要求生成：&lt;/p&gt;
&lt;div&gt;&lt;div&gt;&lt;span&gt;omp&lt;/span&gt;&lt;span&gt;prompt&lt;/span&gt;&lt;/div&gt;&lt;pre&gt;&lt;code&gt;{&apos;math&apos;: 88, &apos;english&apos;: 79, &apos;python&apos;: 85}
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;再筛选出分数大于等于 80 的科目。&lt;/p&gt;
&lt;h3&gt;练习 C：增强 &lt;code&gt;data_summary.py&lt;/code&gt;&lt;/h3&gt;
&lt;p&gt;给脚本增加一个可选参数：&lt;/p&gt;
&lt;div&gt;&lt;div&gt;&lt;span&gt;omp&lt;/span&gt;&lt;span&gt;fish&lt;/span&gt;&lt;/div&gt;&lt;pre&gt;&lt;code&gt;uv run python src/data_summary.py data/scores.csv --show-columns
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;当用户传入 &lt;code&gt;--show-columns&lt;/code&gt; 时，额外打印列名。&lt;/p&gt;
&lt;p&gt;提示：在 &lt;code&gt;parse_args()&lt;/code&gt; 中增加：&lt;/p&gt;
&lt;div&gt;&lt;div&gt;&lt;span&gt;omp&lt;/span&gt;&lt;span&gt;python&lt;/span&gt;&lt;/div&gt;&lt;pre&gt;&lt;code&gt;parser.add_argument(&quot;--show-columns&quot;, action=&quot;store_true&quot;, help=&quot;Show CSV column names&quot;)
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;h3&gt;练习 D：Git 小循环&lt;/h3&gt;
&lt;p&gt;每完成一个小改动，就练习一次：&lt;/p&gt;
&lt;div&gt;&lt;div&gt;&lt;span&gt;omp&lt;/span&gt;&lt;span&gt;fish&lt;/span&gt;&lt;/div&gt;&lt;pre&gt;&lt;code&gt;git status
git add src/data_summary.py
git commit -m &quot;Improve data summary script&quot;
git status
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;重点不是提交很多次，而是理解：工作区、暂存区、提交记录分别是什么。&lt;/p&gt;
&lt;h2&gt;17. 验收检查&lt;/h2&gt;
&lt;p&gt;在 Week 02 项目根目录执行：&lt;/p&gt;
&lt;div&gt;&lt;div&gt;&lt;span&gt;omp&lt;/span&gt;&lt;span&gt;fish&lt;/span&gt;&lt;/div&gt;&lt;pre&gt;&lt;code&gt;pwd
ls
source .venv/bin/activate.fish
uv run python src/data_summary.py --help
uv run python src/data_summary.py data/scores.csv
git status
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;你应能确认：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;pwd&lt;/code&gt; 位于 &lt;code&gt;week02-python-linux-workflow&lt;/code&gt; 项目目录。&lt;/li&gt;
&lt;li&gt;&lt;code&gt;ls&lt;/code&gt; 能看到 &lt;code&gt;src&lt;/code&gt;、&lt;code&gt;data&lt;/code&gt;、&lt;code&gt;notes&lt;/code&gt;、&lt;code&gt;pyproject.toml&lt;/code&gt;。&lt;/li&gt;
&lt;li&gt;&lt;code&gt;source .venv/bin/activate.fish&lt;/code&gt; 不报错。&lt;/li&gt;
&lt;li&gt;&lt;code&gt;uv run python src/data_summary.py --help&lt;/code&gt; 能显示帮助。&lt;/li&gt;
&lt;li&gt;&lt;code&gt;uv run python src/data_summary.py data/scores.csv&lt;/code&gt; 能输出行数、列数、缺失值数量、数值列均值。&lt;/li&gt;
&lt;li&gt;&lt;code&gt;git status&lt;/code&gt; 能解释当前哪些文件未提交，或显示工作区干净。&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;18. 常见错误&lt;/h2&gt;
&lt;h3&gt;错误 1：fish 中激活环境报语法错误&lt;/h3&gt;
&lt;p&gt;错误原因：执行了 Bash / Zsh 激活脚本。&lt;/p&gt;
&lt;p&gt;fish 正确命令：&lt;/p&gt;
&lt;div&gt;&lt;div&gt;&lt;span&gt;omp&lt;/span&gt;&lt;span&gt;fish&lt;/span&gt;&lt;/div&gt;&lt;pre&gt;&lt;code&gt;source .venv/bin/activate.fish
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;h3&gt;错误 2：&lt;code&gt;ModuleNotFoundError: No module named &apos;pandas&apos;&lt;/code&gt;&lt;/h3&gt;
&lt;p&gt;原因：当前环境没有安装 pandas，或没有使用项目环境运行。&lt;/p&gt;
&lt;p&gt;修复：&lt;/p&gt;
&lt;div&gt;&lt;div&gt;&lt;span&gt;omp&lt;/span&gt;&lt;span&gt;fish&lt;/span&gt;&lt;/div&gt;&lt;pre&gt;&lt;code&gt;cd ~/Code/python-learning/week02-python-linux-workflow
uv add pandas
uv run python src/data_summary.py data/scores.csv
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;h3&gt;错误 3：&lt;code&gt;CSV file not found&lt;/code&gt;&lt;/h3&gt;
&lt;p&gt;原因：路径相对于当前目录解释，你可能不在项目根目录。&lt;/p&gt;
&lt;p&gt;检查：&lt;/p&gt;
&lt;div&gt;&lt;div&gt;&lt;span&gt;omp&lt;/span&gt;&lt;span&gt;fish&lt;/span&gt;&lt;/div&gt;&lt;pre&gt;&lt;code&gt;pwd
ls data
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;如果 &lt;code&gt;data/scores.csv&lt;/code&gt; 不存在，就回到正确目录或重新创建文件。&lt;/p&gt;
&lt;h3&gt;错误 4：Git 把 &lt;code&gt;.venv/&lt;/code&gt; 也显示为待提交&lt;/h3&gt;
&lt;p&gt;原因：忘记写 &lt;code&gt;.gitignore&lt;/code&gt;。&lt;/p&gt;
&lt;p&gt;修复：&lt;/p&gt;
&lt;div&gt;&lt;div&gt;&lt;span&gt;omp&lt;/span&gt;&lt;span&gt;fish&lt;/span&gt;&lt;/div&gt;&lt;pre&gt;&lt;code&gt;printf &apos;.venv/\n__pycache__/\n.ipynb_checkpoints/\n*.pyc\n&apos; &amp;gt; .gitignore
git status
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;如果 &lt;code&gt;.venv/&lt;/code&gt; 已经被 &lt;code&gt;git add&lt;/code&gt;，先取消暂存：&lt;/p&gt;
&lt;div&gt;&lt;div&gt;&lt;span&gt;omp&lt;/span&gt;&lt;span&gt;fish&lt;/span&gt;&lt;/div&gt;&lt;pre&gt;&lt;code&gt;git restore --staged .venv
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;h3&gt;错误 5：&lt;code&gt;git commit&lt;/code&gt; 提示身份未知&lt;/h3&gt;
&lt;p&gt;Git 第一次提交可能要求配置姓名和邮箱。用你的真实或常用学习身份配置：&lt;/p&gt;
&lt;div&gt;&lt;div&gt;&lt;span&gt;omp&lt;/span&gt;&lt;span&gt;fish&lt;/span&gt;&lt;/div&gt;&lt;pre&gt;&lt;code&gt;git config --global user.name &quot;Miku&quot;
git config --global user.email &quot;miku@example.com&quot;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;然后重新提交。&lt;/p&gt;
&lt;h2&gt;19. 下一步&lt;/h2&gt;
&lt;p&gt;下一周进入 NumPy / Pandas。你会把本周的“能读 CSV 并打印摘要”升级成真正的数据处理流程：读取真实表格、处理缺失值、做分组统计、保存清洗后的数据，并为 Week 04 的 EDA 报告准备数据基础。&lt;/p&gt;
&lt;p&gt;plain &lt;/p&gt;&lt;/article&gt;&lt;p&gt;&lt;/p&gt;
  &lt;/div&gt;
&lt;/section&gt;</content:encoded><category>category:工具</category><category>category:Python基础</category><category>tag:Linux</category><category>tag:CLI</category><category>tag:Git</category><category>tag:Python函数</category><category>tag:工作流</category></item><item><title>🔒 USTC 统计学：AI / Quant 全栈终端式练习手册</title><link>https://39miku.space/post/ustc-stat-ai-quant-plan</link><guid isPermaLink="false">ustc-stat-ai-quant-plan</guid><description>此文章已加密，请在网页中查看</description><pubDate>Sat, 25 Jul 2026 18:00:00 GMT</pubDate><content:encoded>&lt;p&gt;此文章已加密，请在网页中查看&lt;/p&gt;</content:encoded><category>category:笔记</category><category>category:学习路线</category><category>tag:USTC</category><category>tag:AI路线</category><category>tag:Quant路线</category><category>tag:统计学习</category><category>tag:项目规划</category></item><item><title>Week 01：CachyOS Python 环境与基础语法</title><link>https://39miku.space/post/week01-python-environment-basics</link><guid isPermaLink="false">week01-python-environment-basics</guid><description>Week 01：CachyOS Python 环境与基础语法。fish-first 终端教学，面向 CachyOS、VS Code、uv 和 Python 学习路线。</description><pubDate>Sat, 25 Jul 2026 17:00:00 GMT</pubDate><content:encoded>
&lt;section&gt;
  &lt;div&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;strong&gt;Oh My Pi&lt;/strong&gt; / weekly tutorial / week01-python-environment-basics&lt;/span&gt;&lt;/div&gt;
  &lt;div&gt;
    &lt;div&gt;&lt;span&gt;miku@cachyos&lt;/span&gt;&lt;span&gt;:~/Code/python-learning&lt;/span&gt;&lt;span&gt;$&lt;/span&gt; &lt;span&gt;omp teach week01-python-environment-basics --fish-first --step-by-step&lt;/span&gt;&lt;/div&gt;
    &lt;div&gt;
      &lt;div&gt;&lt;span&gt;source&lt;/span&gt;&lt;strong&gt;780 行教学文档&lt;/strong&gt;&lt;/div&gt;
      &lt;div&gt;&lt;span&gt;week&lt;/span&gt;&lt;strong&gt;Week 01&lt;/strong&gt;&lt;/div&gt;
      &lt;div&gt;&lt;span&gt;shell&lt;/span&gt;&lt;strong&gt;fish-first 命令版&lt;/strong&gt;&lt;/div&gt;
      &lt;div&gt;&lt;span&gt;backlink&lt;/span&gt;&lt;strong&gt;&lt;a href=&quot;/post/ustc-stat-ai-quant-plan/&quot;&gt;20 周计划&lt;/a&gt;&lt;/strong&gt;&lt;/div&gt;
    &lt;/div&gt;
    &lt;article&gt;
&lt;h1&gt;Week 01：CachyOS Python 环境与基础语法&lt;/h1&gt;
&lt;blockquote&gt;
&lt;p&gt;适用对象：已经安装 VS Code，正在用 CachyOS / Arch 系 Linux，准备按 &lt;a href=&quot;/post/ustc-stat-ai-quant-plan/&quot;&gt;20 周 AI / 量化成长计划&lt;/a&gt; 开始 Week 01：环境与 Python 基础。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;&lt;strong&gt;核心结论&lt;/strong&gt;&lt;br /&gt;不需要把所有事情都放在命令行里做。你可以主要在 VS Code 里写代码、看文件、运行 notebook；但必须掌握少量命令行，因为 Python 环境、包管理、脚本运行、Git、项目复现都依赖这些基础命令。&lt;/p&gt;
&lt;p&gt;本教程采用：&lt;/p&gt;
&lt;div&gt;&lt;div&gt;&lt;span&gt;omp&lt;/span&gt;&lt;span&gt;prompt&lt;/span&gt;&lt;/div&gt;&lt;pre&gt;&lt;code&gt;VS Code 负责：写代码、看文件、运行脚本、调试、使用 notebook
uv 负责：创建虚拟环境、安装依赖、记录项目配置
终端负责：创建目录、激活环境、运行命令、检查结果
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;本文命令行默认按 &lt;strong&gt;fish shell&lt;/strong&gt; 写；如果某一步需要 Bash / Zsh，会单独标注。&lt;/p&gt;
&lt;p&gt;当前机器已经具备：&lt;/p&gt;
&lt;div&gt;&lt;div&gt;&lt;span&gt;omp&lt;/span&gt;&lt;span&gt;prompt&lt;/span&gt;&lt;/div&gt;&lt;pre&gt;&lt;code&gt;Python 3.14.6
uv 0.11.31
VS Code 1.130.0
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;因此不需要从零安装 Python，直接建立学习环境即可。&lt;/p&gt;
&lt;p&gt;&lt;a&gt;&lt;/a&gt;&lt;/p&gt;
&lt;h2&gt;0. 本周详细教学：语法、规范、验收&lt;/h2&gt;
&lt;blockquote&gt;
&lt;p&gt;本节不是追加在尾部的复习，而是本周正文的入口。先读这里，再做后面的命令和项目。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h3&gt;0.1 本周真正要学会什么&lt;/h3&gt;
&lt;table&gt;
&lt;thead&gt;&lt;tr&gt;&lt;th&gt;维度&lt;/th&gt;&lt;th&gt;要求&lt;/th&gt;&lt;/tr&gt;&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;&lt;td&gt;知识点&lt;/td&gt;&lt;td&gt;变量、字符串、列表、函数、文件读写&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;代码语法&lt;/td&gt;&lt;td&gt;能从空文件写出本周核心脚本，而不是只复制运行&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;程序规范&lt;/td&gt;&lt;td&gt;函数拆分、路径清楚、输入输出明确、错误能解释&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;交付物&lt;/td&gt;&lt;td&gt;&lt;code&gt;src/week01_basic.py&lt;/code&gt;&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;验收方式&lt;/td&gt;&lt;td&gt;从 fish 终端运行命令，得到可复查的文件或指标&lt;/td&gt;&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;h3&gt;0.2 代码语法精讲&lt;/h3&gt;
&lt;p&gt;下面的代码不是最终答案，而是本周必须理解的最小骨架：&lt;/p&gt;
&lt;div&gt;&lt;div&gt;&lt;span&gt;omp&lt;/span&gt;&lt;span&gt;python&lt;/span&gt;&lt;/div&gt;&lt;pre&gt;&lt;code&gt;def mean(xs):
    if not xs:
        raise ValueError(&quot;xs must not be empty&quot;)
    return sum(xs) / len(xs)
&lt;p&gt;with open(“data/numbers.txt”, “w”, encoding=“utf-8”) as f:
f.write(“1\n2\n3\n”)&lt;/p&gt;
&lt;p&gt;with open(“data/numbers.txt”, encoding=“utf-8”) as f:
nums = [float(line.strip()) for line in f if line.strip()]&lt;/p&gt;
&lt;/code&gt;&lt;p&gt;&lt;code&gt;print(mean(nums))
&lt;/code&gt;&lt;/p&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;&lt;/p&gt;
&lt;p&gt;读代码时按四步检查：输入从哪里来；中间变量的类型和 shape 是什么；函数或脚本输出什么；哪些错误应该显式报出来。&lt;/p&gt;
&lt;h3&gt;0.3 本周程序规范&lt;/h3&gt;
&lt;ul&gt;&lt;li&gt;所有路径用相对路径或 `pathlib.Path`，不要写死 `/home/miku/...`。&lt;/li&gt;&lt;li&gt;核心逻辑进 `src/`，notebook 只做探索和解释。&lt;/li&gt;&lt;li&gt;每个脚本能从 fish 终端运行，并在 README 写出命令。&lt;/li&gt;&lt;li&gt;输出必须落盘到 `reports/`、`figures/` 或 `outputs/`，不能只在屏幕上看。&lt;/li&gt;&lt;/ul&gt;
&lt;h3&gt;0.4 本周练习分层&lt;/h3&gt;
&lt;table&gt;
&lt;thead&gt;&lt;tr&gt;&lt;th&gt;层级&lt;/th&gt;&lt;th&gt;任务&lt;/th&gt;&lt;th&gt;不合格表现&lt;/th&gt;&lt;th&gt;合格验收&lt;/th&gt;&lt;/tr&gt;&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;&lt;td&gt;最小练习&lt;/td&gt;&lt;td&gt;手写上面的最小骨架&lt;/td&gt;&lt;td&gt;只在 notebook 里运行&lt;/td&gt;&lt;td&gt;终端运行成功&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;标准练习&lt;/td&gt;&lt;td&gt;把逻辑拆成函数/模块&lt;/td&gt;&lt;td&gt;一个大脚本从头写到尾&lt;/td&gt;&lt;td&gt;至少 2 个函数，职责清楚&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;项目练习&lt;/td&gt;&lt;td&gt;生成本周交付物 &lt;code&gt;src/week01_basic.py&lt;/code&gt;&lt;/td&gt;&lt;td&gt;只有屏幕输出&lt;/td&gt;&lt;td&gt;文件落盘，可复查&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;复盘练习&lt;/td&gt;&lt;td&gt;写 3 个错误和修复&lt;/td&gt;&lt;td&gt;只写“已解决”&lt;/td&gt;&lt;td&gt;写清报错、原因、修复、预防&lt;/td&gt;&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;h3&gt;0.5 本周和主线的连接&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;回到总计划：&lt;a href=&quot;/post/ustc-stat-ai-quant-plan/#week-plan&quot;&gt;USTC AI / Quant 练习手册&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;查详细练习索引：&lt;a href=&quot;/post/ustc-stat-ai-quant-plan/#deep-practice-appendix&quot;&gt;技术练习详解&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;查质量评分：&lt;a href=&quot;/post/ustc-stat-ai-quant-plan/#quality-final-gates&quot;&gt;最终质量门槛&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;1. 为什么不能完全不用命令行&lt;/h2&gt;
&lt;p&gt;你不需要成为 Linux 高手，但不能完全绕开命令行。&lt;/p&gt;
&lt;p&gt;原因有四个：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;环境隔离需要命令&lt;/strong&gt;：每个项目应有自己的 &lt;code&gt;.venv&lt;/code&gt;，避免不同项目的包互相污染。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;安装包需要命令&lt;/strong&gt;：例如 &lt;code&gt;numpy&lt;/code&gt;、&lt;code&gt;pandas&lt;/code&gt;、&lt;code&gt;matplotlib&lt;/code&gt;、&lt;code&gt;scikit-learn&lt;/code&gt;。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;复现实验需要命令&lt;/strong&gt;：以后别人看你的项目，需要知道如何一键安装依赖并运行。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Git 和部署需要命令&lt;/strong&gt;：提交代码、查看状态、解决冲突，迟早会用到。&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;最低限度只需要会这些：&lt;/p&gt;
&lt;div&gt;&lt;div&gt;&lt;span&gt;omp&lt;/span&gt;&lt;span&gt;fish&lt;/span&gt;&lt;/div&gt;&lt;pre&gt;&lt;code&gt;pwd        # 看当前在哪个目录
ls         # 看当前目录有什么文件
cd         # 进入目录
mkdir      # 创建文件夹
code .     # 用 VS Code 打开当前目录
uv init    # 初始化 Python 项目
uv venv    # 创建虚拟环境
uv add     # 安装依赖
python     # 运行 Python
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;h2&gt;2. 建立统一学习目录&lt;/h2&gt;
&lt;p&gt;先给未来 20 周的学习项目留一个统一位置。&lt;/p&gt;
&lt;p&gt;打开终端，执行：&lt;/p&gt;
&lt;div&gt;&lt;div&gt;&lt;span&gt;omp&lt;/span&gt;&lt;span&gt;fish&lt;/span&gt;&lt;/div&gt;&lt;pre&gt;&lt;code&gt;mkdir -p ~/Code/python-learning
cd ~/Code/python-learning
pwd
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;逐句解释：&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;命令&lt;/th&gt;
&lt;th&gt;含义&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;&lt;tr&gt;
&lt;td&gt;&lt;code&gt;mkdir&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;创建文件夹&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;-p&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;如果上级目录不存在，就一起创建；如果目录已存在，也不要报错&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;~/Code/python-learning&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;你的 Python 学习总目录，&lt;code&gt;~&lt;/code&gt; 表示当前用户主目录&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;cd&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;进入这个目录&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;pwd&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;打印当前所在路径，用来确认你没有走错目录&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;&lt;/table&gt;
&lt;p&gt;如果输出类似：&lt;/p&gt;
&lt;div&gt;&lt;div&gt;&lt;span&gt;omp&lt;/span&gt;&lt;span&gt;prompt&lt;/span&gt;&lt;/div&gt;&lt;pre&gt;&lt;code&gt;/home/miku/Code/python-learning
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;说明位置正确。&lt;/p&gt;
&lt;h2&gt;3. 创建第一个 Week 01 项目&lt;/h2&gt;
&lt;p&gt;每周一个小项目，不要把所有练习都堆在一个文件夹里。&lt;/p&gt;
&lt;div&gt;&lt;div&gt;&lt;span&gt;omp&lt;/span&gt;&lt;span&gt;fish&lt;/span&gt;&lt;/div&gt;&lt;pre&gt;&lt;code&gt;mkdir week01-basics
cd week01-basics
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;建议结构：&lt;/p&gt;
&lt;div&gt;&lt;div&gt;&lt;span&gt;omp&lt;/span&gt;&lt;span&gt;prompt&lt;/span&gt;&lt;/div&gt;&lt;pre&gt;&lt;code&gt;week01-basics/
├── notes/       # 学习笔记
├── src/         # Python 源代码
├── data/        # 小数据文件
├── notebooks/   # Jupyter notebook
└── reports/     # 报告或总结
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;创建这些目录：&lt;/p&gt;
&lt;div&gt;&lt;div&gt;&lt;span&gt;omp&lt;/span&gt;&lt;span&gt;fish&lt;/span&gt;&lt;/div&gt;&lt;pre&gt;&lt;code&gt;mkdir -p notes src data notebooks reports
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;检查：&lt;/p&gt;
&lt;div&gt;&lt;div&gt;&lt;span&gt;omp&lt;/span&gt;&lt;span&gt;fish&lt;/span&gt;&lt;/div&gt;&lt;pre&gt;&lt;code&gt;ls
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;应该看到：&lt;/p&gt;
&lt;div&gt;&lt;div&gt;&lt;span&gt;omp&lt;/span&gt;&lt;span&gt;prompt&lt;/span&gt;&lt;/div&gt;&lt;pre&gt;&lt;code&gt;data  notebooks  notes  reports  src
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;h2&gt;4. 初始化 Python 项目&lt;/h2&gt;
&lt;p&gt;在 &lt;code&gt;week01-basics&lt;/code&gt; 目录里执行：&lt;/p&gt;
&lt;div&gt;&lt;div&gt;&lt;span&gt;omp&lt;/span&gt;&lt;span&gt;fish&lt;/span&gt;&lt;/div&gt;&lt;pre&gt;&lt;code&gt;uv init
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;这一步会生成项目配置文件，通常包括：&lt;/p&gt;
&lt;div&gt;&lt;div&gt;&lt;span&gt;omp&lt;/span&gt;&lt;span&gt;prompt&lt;/span&gt;&lt;/div&gt;&lt;pre&gt;&lt;code&gt;pyproject.toml
README.md
main.py
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;解释：&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;文件&lt;/th&gt;
&lt;th&gt;作用&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;&lt;tr&gt;
&lt;td&gt;&lt;code&gt;pyproject.toml&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;记录项目名称、Python 版本、依赖包&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;README.md&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;项目说明，未来给老师、面试官或自己回看&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;main.py&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;默认入口脚本，可以改也可以删除&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;&lt;/table&gt;
&lt;p&gt;如果 &lt;code&gt;uv init&lt;/code&gt; 提示文件已存在，不用慌，说明当前目录里已经有项目文件。&lt;/p&gt;
&lt;h2&gt;5. 创建虚拟环境 &lt;code&gt;.venv&lt;/code&gt;&lt;/h2&gt;
&lt;p&gt;执行：&lt;/p&gt;
&lt;div&gt;&lt;div&gt;&lt;span&gt;omp&lt;/span&gt;&lt;span&gt;fish&lt;/span&gt;&lt;/div&gt;&lt;pre&gt;&lt;code&gt;uv venv
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;它会创建：&lt;/p&gt;
&lt;div&gt;&lt;div&gt;&lt;span&gt;omp&lt;/span&gt;&lt;span&gt;prompt&lt;/span&gt;&lt;/div&gt;&lt;pre&gt;&lt;code&gt;.venv/
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;&lt;code&gt;.venv&lt;/code&gt; 是这个项目自己的 Python 环境。以后这个项目安装的包都放在里面，不污染系统 Python。&lt;/p&gt;
&lt;p&gt;为什么重要：&lt;/p&gt;
&lt;div&gt;&lt;div&gt;&lt;span&gt;omp&lt;/span&gt;&lt;span&gt;prompt&lt;/span&gt;&lt;/div&gt;&lt;pre&gt;&lt;code&gt;系统 Python：给操作系统和系统工具用
项目 .venv：给当前学习项目用
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;不要把所有包直接装进系统 Python。CachyOS / Arch 是滚动发行版，系统 Python 更应该保持干净。&lt;/p&gt;
&lt;h2&gt;6. 激活虚拟环境&lt;/h2&gt;
&lt;p&gt;先确认你在真正的项目目录里，而不是只在总目录 &lt;code&gt;~/Code/python-learning&lt;/code&gt; 里：&lt;/p&gt;
&lt;div&gt;&lt;div&gt;&lt;span&gt;omp&lt;/span&gt;&lt;span&gt;fish&lt;/span&gt;&lt;/div&gt;&lt;pre&gt;&lt;code&gt;pwd
ls
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;如果你按本文创建的是 &lt;code&gt;week01-basics&lt;/code&gt; 项目，那么应先进入：&lt;/p&gt;
&lt;div&gt;&lt;div&gt;&lt;span&gt;omp&lt;/span&gt;&lt;span&gt;fish&lt;/span&gt;&lt;/div&gt;&lt;pre&gt;&lt;code&gt;cd ~/Code/python-learning/week01-basics
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;判断标准：当前目录里应该能看到 &lt;code&gt;pyproject.toml&lt;/code&gt; 和 &lt;code&gt;.venv/&lt;/code&gt;。如果你在 &lt;code&gt;~/Code/python-learning&lt;/code&gt; 总目录下执行激活命令，而虚拟环境其实建在 &lt;code&gt;week01-basics/.venv/&lt;/code&gt;，就会找错位置。&lt;/p&gt;
&lt;h3&gt;6.1 如果你使用 fish shell&lt;/h3&gt;
&lt;p&gt;你现在遇到的报错就是 fish shell 把 Bash 版激活脚本当成 fish 脚本执行了。&lt;/p&gt;
&lt;p&gt;fish 里应执行：&lt;/p&gt;
&lt;div&gt;&lt;div&gt;&lt;span&gt;omp&lt;/span&gt;&lt;span&gt;fish&lt;/span&gt;&lt;/div&gt;&lt;pre&gt;&lt;code&gt;source .venv/bin/activate.fish
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;不要在 fish 里执行：&lt;/p&gt;
&lt;div&gt;&lt;div&gt;&lt;span&gt;omp&lt;/span&gt;&lt;span&gt;prompt&lt;/span&gt;&lt;/div&gt;&lt;pre&gt;&lt;code&gt;Bash/Zsh activate 脚本
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;&lt;code&gt;.venv/bin/activate&lt;/code&gt; 是给 Bash / Zsh 用的，里面有 &lt;code&gt;变量=值&lt;/code&gt; 这种语法；fish 不支持这种写法，所以会报：&lt;/p&gt;
&lt;div&gt;&lt;div&gt;&lt;span&gt;omp&lt;/span&gt;&lt;span&gt;prompt&lt;/span&gt;&lt;/div&gt;&lt;pre&gt;&lt;code&gt;不支持使用 &apos;=&apos;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;h3&gt;6.2 如果你使用 bash 或 zsh&lt;/h3&gt;
&lt;p&gt;Bash / Zsh 才使用：&lt;/p&gt;
&lt;div&gt;&lt;div&gt;&lt;span&gt;omp&lt;/span&gt;&lt;span&gt;prompt&lt;/span&gt;&lt;/div&gt;&lt;pre&gt;&lt;code&gt;Bash/Zsh activate 脚本
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;h3&gt;6.3 激活成功后的检查&lt;/h3&gt;
&lt;p&gt;成功后，终端提示符前面通常会出现：&lt;/p&gt;
&lt;div&gt;&lt;div&gt;&lt;span&gt;omp&lt;/span&gt;&lt;span&gt;prompt&lt;/span&gt;&lt;/div&gt;&lt;pre&gt;&lt;code&gt;(.venv)
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;这表示当前终端已经进入项目环境。&lt;/p&gt;
&lt;p&gt;验证 Python 路径：&lt;/p&gt;
&lt;div&gt;&lt;div&gt;&lt;span&gt;omp&lt;/span&gt;&lt;span&gt;fish&lt;/span&gt;&lt;/div&gt;&lt;pre&gt;&lt;code&gt;command -v python
python --version
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;你希望看到的 &lt;code&gt;command -v python&lt;/code&gt; 应该类似：&lt;/p&gt;
&lt;div&gt;&lt;div&gt;&lt;span&gt;omp&lt;/span&gt;&lt;span&gt;prompt&lt;/span&gt;&lt;/div&gt;&lt;pre&gt;&lt;code&gt;/home/miku/Code/python-learning/week01-basics/.venv/bin/python
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;如果看到的是 &lt;code&gt;/usr/bin/python&lt;/code&gt;，说明虚拟环境没有激活成功，或者你不在创建 &lt;code&gt;.venv&lt;/code&gt; 的项目目录里。&lt;/p&gt;
&lt;h2&gt;7. 安装第一批学习包&lt;/h2&gt;
&lt;p&gt;Week 01 到 Week 04 建议安装：&lt;/p&gt;
&lt;div&gt;&lt;div&gt;&lt;span&gt;omp&lt;/span&gt;&lt;span&gt;fish&lt;/span&gt;&lt;/div&gt;&lt;pre&gt;&lt;code&gt;uv add numpy pandas matplotlib jupyter ipykernel
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;逐个解释：&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;包&lt;/th&gt;
&lt;th&gt;用途&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;&lt;tr&gt;
&lt;td&gt;&lt;code&gt;numpy&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;数值计算，数组、矩阵、随机数&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;pandas&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;表格数据处理，CSV、DataFrame&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;matplotlib&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;基础画图&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;jupyter&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;notebook 环境&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;ipykernel&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;让 VS Code / Jupyter 使用当前虚拟环境&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;&lt;/table&gt;
&lt;p&gt;安装完成后，&lt;code&gt;pyproject.toml&lt;/code&gt; 会记录这些依赖。以后别人拿到你的项目，可以根据配置恢复环境。&lt;/p&gt;
&lt;h2&gt;8. 用 VS Code 打开项目&lt;/h2&gt;
&lt;p&gt;确认你还在项目目录：&lt;/p&gt;
&lt;div&gt;&lt;div&gt;&lt;span&gt;omp&lt;/span&gt;&lt;span&gt;fish&lt;/span&gt;&lt;/div&gt;&lt;pre&gt;&lt;code&gt;pwd
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;然后执行：&lt;/p&gt;
&lt;div&gt;&lt;div&gt;&lt;span&gt;omp&lt;/span&gt;&lt;span&gt;fish&lt;/span&gt;&lt;/div&gt;&lt;pre&gt;&lt;code&gt;code .
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;含义：用 VS Code 打开当前目录。&lt;/p&gt;
&lt;p&gt;不要只打开单个 &lt;code&gt;.py&lt;/code&gt; 文件，应该打开整个项目文件夹。这样 VS Code 才能识别 &lt;code&gt;.venv&lt;/code&gt;、&lt;code&gt;pyproject.toml&lt;/code&gt;、目录结构和相对路径。&lt;/p&gt;
&lt;h2&gt;9. 在 VS Code 里选择 Python 解释器&lt;/h2&gt;
&lt;p&gt;打开 VS Code 后：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;按 &lt;code&gt;Ctrl + Shift + P&lt;/code&gt;。&lt;/li&gt;
&lt;li&gt;输入 &lt;code&gt;Python: Select Interpreter&lt;/code&gt;。&lt;/li&gt;
&lt;li&gt;选择当前项目的解释器：&lt;/li&gt;
&lt;/ol&gt;
&lt;div&gt;&lt;div&gt;&lt;span&gt;omp&lt;/span&gt;&lt;span&gt;prompt&lt;/span&gt;&lt;/div&gt;&lt;pre&gt;&lt;code&gt;.venv/bin/python
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;如果列表里没看到，可以手动选择：&lt;/p&gt;
&lt;div&gt;&lt;div&gt;&lt;span&gt;omp&lt;/span&gt;&lt;span&gt;prompt&lt;/span&gt;&lt;/div&gt;&lt;pre&gt;&lt;code&gt;/home/miku/Code/python-learning/week01-basics/.venv/bin/python
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;这一步的意义：告诉 VS Code 使用当前项目环境，而不是系统 Python。&lt;/p&gt;
&lt;h2&gt;10. 写第一个脚本&lt;/h2&gt;
&lt;p&gt;在 &lt;code&gt;src/&lt;/code&gt; 下新建文件：&lt;/p&gt;
&lt;div&gt;&lt;div&gt;&lt;span&gt;omp&lt;/span&gt;&lt;span&gt;prompt&lt;/span&gt;&lt;/div&gt;&lt;pre&gt;&lt;code&gt;src/hello.py
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;内容：&lt;/p&gt;
&lt;div&gt;&lt;div&gt;&lt;span&gt;omp&lt;/span&gt;&lt;span&gt;python&lt;/span&gt;&lt;/div&gt;&lt;pre&gt;&lt;code&gt;print(&quot;Hello, Python&quot;)
&lt;/code&gt;&lt;p&gt;&lt;code&gt;scores = [85, 92, 78]
mean_score = sum(scores) / len(scores)
print(mean_score)
&lt;/code&gt;&lt;/p&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;&lt;/p&gt;
&lt;p&gt;用终端运行：&lt;/p&gt;
&lt;div&gt;&lt;div&gt;&lt;span&gt;omp&lt;/span&gt;&lt;span&gt;fish&lt;/span&gt;&lt;/div&gt;&lt;pre&gt;&lt;code&gt;python src/hello.py
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;应该输出：&lt;/p&gt;
&lt;div&gt;&lt;div&gt;&lt;span&gt;omp&lt;/span&gt;&lt;span&gt;prompt&lt;/span&gt;&lt;/div&gt;&lt;pre&gt;&lt;code&gt;Hello, Python
85.0
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;如果 VS Code 右上角运行按钮也能跑出同样结果，说明解释器配置正确。&lt;/p&gt;
&lt;h2&gt;11. 写第一个 pandas 练习&lt;/h2&gt;
&lt;p&gt;新建：&lt;/p&gt;
&lt;div&gt;&lt;div&gt;&lt;span&gt;omp&lt;/span&gt;&lt;span&gt;prompt&lt;/span&gt;&lt;/div&gt;&lt;pre&gt;&lt;code&gt;src/pandas_demo.py
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;内容：&lt;/p&gt;
&lt;div&gt;&lt;div&gt;&lt;span&gt;omp&lt;/span&gt;&lt;span&gt;python&lt;/span&gt;&lt;/div&gt;&lt;pre&gt;&lt;code&gt;import pandas as pd
&lt;p&gt;data = {
“name”: [“Alice”, “Bob”, “Cindy”],
“score”: [85, 92, 78],
}&lt;/p&gt;
&lt;/code&gt;&lt;p&gt;&lt;code&gt;df = pd.DataFrame(data)
print(df)
print(“mean score:”, df[“score”].mean())
&lt;/code&gt;&lt;/p&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;&lt;/p&gt;
&lt;p&gt;运行：&lt;/p&gt;
&lt;div&gt;&lt;div&gt;&lt;span&gt;omp&lt;/span&gt;&lt;span&gt;fish&lt;/span&gt;&lt;/div&gt;&lt;pre&gt;&lt;code&gt;python src/pandas_demo.py
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;如果能看到表格和平均分，说明：&lt;/p&gt;
&lt;div&gt;&lt;div&gt;&lt;span&gt;omp&lt;/span&gt;&lt;span&gt;prompt&lt;/span&gt;&lt;/div&gt;&lt;pre&gt;&lt;code&gt;Python 能运行
pandas 已安装
VS Code 项目路径正确
虚拟环境可用
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;h2&gt;12. 建立 notebook&lt;/h2&gt;
&lt;p&gt;在 VS Code 里新建：&lt;/p&gt;
&lt;div&gt;&lt;div&gt;&lt;span&gt;omp&lt;/span&gt;&lt;span&gt;prompt&lt;/span&gt;&lt;/div&gt;&lt;pre&gt;&lt;code&gt;notebooks/week01.ipynb
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;第一次打开 notebook 时，右上角选择 Kernel：&lt;/p&gt;
&lt;div&gt;&lt;div&gt;&lt;span&gt;omp&lt;/span&gt;&lt;span&gt;prompt&lt;/span&gt;&lt;/div&gt;&lt;pre&gt;&lt;code&gt;.venv/bin/python
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;新建一个 cell：&lt;/p&gt;
&lt;div&gt;&lt;div&gt;&lt;span&gt;omp&lt;/span&gt;&lt;span&gt;python&lt;/span&gt;&lt;/div&gt;&lt;pre&gt;&lt;code&gt;import pandas as pd
&lt;/code&gt;&lt;p&gt;&lt;code&gt;pd.DataFrame({“x”: [1, 2, 3], “y”: [2, 4, 6]})
&lt;/code&gt;&lt;/p&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;&lt;/p&gt;
&lt;p&gt;如果表格正常显示，notebook 环境配置成功。&lt;/p&gt;
&lt;h2&gt;13. 推荐项目结构&lt;/h2&gt;
&lt;p&gt;Week 01 最终建议变成：&lt;/p&gt;
&lt;div&gt;&lt;div&gt;&lt;span&gt;omp&lt;/span&gt;&lt;span&gt;prompt&lt;/span&gt;&lt;/div&gt;&lt;pre&gt;&lt;code&gt;week01-basics/
├── .venv/
├── data/
│   └── scores.csv
├── notebooks/
│   └── week01.ipynb
├── notes/
│   └── week01-python.md
├── reports/
│   └── week01-summary.md
├── src/
│   ├── hello.py
│   └── pandas_demo.py
├── pyproject.toml
├── README.md
└── uv.lock
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;其中：&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;路径&lt;/th&gt;
&lt;th&gt;说明&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;&lt;tr&gt;
&lt;td&gt;&lt;code&gt;.venv/&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;虚拟环境，不手动编辑，不提交到 Git&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;data/&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;小数据文件&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;notebooks/&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;交互式分析&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;notes/&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;学习笔记&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;reports/&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;阶段总结&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;src/&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;正式 Python 脚本&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;pyproject.toml&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;项目依赖配置&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;uv.lock&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;锁定依赖版本，保证复现&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;&lt;/table&gt;
&lt;h2&gt;14. Git 初始化&lt;/h2&gt;
&lt;p&gt;如果你要把练习当成项目积累，建议从 Week 01 就用 Git。&lt;/p&gt;
&lt;div&gt;&lt;div&gt;&lt;span&gt;omp&lt;/span&gt;&lt;span&gt;fish&lt;/span&gt;&lt;/div&gt;&lt;pre&gt;&lt;code&gt;git init
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;创建 &lt;code&gt;.gitignore&lt;/code&gt;：&lt;/p&gt;
&lt;div&gt;&lt;div&gt;&lt;span&gt;omp&lt;/span&gt;&lt;span&gt;fish&lt;/span&gt;&lt;/div&gt;&lt;pre&gt;&lt;code&gt;printf &quot;%s\n&quot; &quot;.venv/&quot; &quot;__pycache__/&quot; &quot;.ipynb_checkpoints/&quot; &quot;*.pyc&quot; &quot;.env&quot; &amp;gt; .gitignore
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;解释：&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;条目&lt;/th&gt;
&lt;th&gt;为什么忽略&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;&lt;tr&gt;
&lt;td&gt;&lt;code&gt;.venv/&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;虚拟环境很大，可以重建，不应提交&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;__pycache__/&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Python 自动缓存&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;.ipynb_checkpoints/&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Jupyter 自动生成&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;*.pyc&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;编译缓存&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;.env&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;可能包含 API key&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;&lt;/table&gt;
&lt;p&gt;查看状态：&lt;/p&gt;
&lt;div&gt;&lt;div&gt;&lt;span&gt;omp&lt;/span&gt;&lt;span&gt;fish&lt;/span&gt;&lt;/div&gt;&lt;pre&gt;&lt;code&gt;git status
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;提交：&lt;/p&gt;
&lt;div&gt;&lt;div&gt;&lt;span&gt;omp&lt;/span&gt;&lt;span&gt;fish&lt;/span&gt;&lt;/div&gt;&lt;pre&gt;&lt;code&gt;git add README.md pyproject.toml uv.lock src notes notebooks reports data .gitignore
git commit -m &quot;init python learning environment&quot;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;如果你暂时还不熟 Git，可以先不提交，但至少要理解 &lt;code&gt;.venv/&lt;/code&gt; 不该放进仓库。&lt;/p&gt;
&lt;h2&gt;15. 以后每天怎么用&lt;/h2&gt;
&lt;p&gt;每次重新打开项目，如果你使用 fish：&lt;/p&gt;
&lt;div&gt;&lt;div&gt;&lt;span&gt;omp&lt;/span&gt;&lt;span&gt;fish&lt;/span&gt;&lt;/div&gt;&lt;pre&gt;&lt;code&gt;cd ~/Code/python-learning/week01-basics
source .venv/bin/activate.fish
code .
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;如果教程不是 fish 版本，可能会写：&lt;/p&gt;
&lt;div&gt;&lt;div&gt;&lt;span&gt;omp&lt;/span&gt;&lt;span&gt;prompt&lt;/span&gt;&lt;/div&gt;&lt;pre&gt;&lt;code&gt;cd ~/Code/python-learning/week01-basics
Bash/Zsh activate 脚本
code .
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;如果只在 VS Code 里运行，解释器选好后不一定每次都要手动激活；但只要你在终端里运行 &lt;code&gt;python&lt;/code&gt;，建议先激活。&lt;/p&gt;
&lt;h2&gt;16. 常见错误排查&lt;/h2&gt;
&lt;h3&gt;16.1 VS Code 找不到 pandas&lt;/h3&gt;
&lt;p&gt;现象：&lt;/p&gt;
&lt;div&gt;&lt;div&gt;&lt;span&gt;omp&lt;/span&gt;&lt;span&gt;prompt&lt;/span&gt;&lt;/div&gt;&lt;pre&gt;&lt;code&gt;ModuleNotFoundError: No module named &apos;pandas&apos;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;检查：&lt;/p&gt;
&lt;div&gt;&lt;div&gt;&lt;span&gt;omp&lt;/span&gt;&lt;span&gt;fish&lt;/span&gt;&lt;/div&gt;&lt;pre&gt;&lt;code&gt;command -v python
python -c &quot;import pandas as pd; print(pd.__version__)&quot;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;如果终端能导入，但 VS Code 不能，说明 VS Code 解释器没选 &lt;code&gt;.venv/bin/python&lt;/code&gt;。&lt;/p&gt;
&lt;h3&gt;16.2 终端前面没有 &lt;code&gt;(.venv)&lt;/code&gt;&lt;/h3&gt;
&lt;p&gt;先确认当前目录：&lt;/p&gt;
&lt;div&gt;&lt;div&gt;&lt;span&gt;omp&lt;/span&gt;&lt;span&gt;fish&lt;/span&gt;&lt;/div&gt;&lt;pre&gt;&lt;code&gt;pwd
ls
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;如果你在 &lt;code&gt;~/Code/python-learning&lt;/code&gt; 总目录，先进入具体项目：&lt;/p&gt;
&lt;div&gt;&lt;div&gt;&lt;span&gt;omp&lt;/span&gt;&lt;span&gt;fish&lt;/span&gt;&lt;/div&gt;&lt;pre&gt;&lt;code&gt;cd ~/Code/python-learning/week01-basics
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;fish 用户重新激活：&lt;/p&gt;
&lt;div&gt;&lt;div&gt;&lt;span&gt;omp&lt;/span&gt;&lt;span&gt;fish&lt;/span&gt;&lt;/div&gt;&lt;pre&gt;&lt;code&gt;source .venv/bin/activate.fish
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;如果你看到别人使用 Bash / Zsh：&lt;/p&gt;
&lt;div&gt;&lt;div&gt;&lt;span&gt;omp&lt;/span&gt;&lt;span&gt;prompt&lt;/span&gt;&lt;/div&gt;&lt;pre&gt;&lt;code&gt;Bash/Zsh activate 脚本
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;h3&gt;16.3 不知道当前在哪个目录&lt;/h3&gt;
&lt;p&gt;执行：&lt;/p&gt;
&lt;div&gt;&lt;div&gt;&lt;span&gt;omp&lt;/span&gt;&lt;span&gt;fish&lt;/span&gt;&lt;/div&gt;&lt;pre&gt;&lt;code&gt;pwd
ls
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;如果看不到 &lt;code&gt;pyproject.toml&lt;/code&gt;，说明你不在项目根目录。&lt;/p&gt;
&lt;h3&gt;16.4 uv add 失败&lt;/h3&gt;
&lt;p&gt;先检查网络和当前目录：&lt;/p&gt;
&lt;div&gt;&lt;div&gt;&lt;span&gt;omp&lt;/span&gt;&lt;span&gt;fish&lt;/span&gt;&lt;/div&gt;&lt;pre&gt;&lt;code&gt;pwd
uv --version
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;确认在项目目录后再执行：&lt;/p&gt;
&lt;div&gt;&lt;div&gt;&lt;span&gt;omp&lt;/span&gt;&lt;span&gt;fish&lt;/span&gt;&lt;/div&gt;&lt;pre&gt;&lt;code&gt;uv add pandas
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;h2&gt;17. Week 01 验收清单&lt;/h2&gt;
&lt;p&gt;完成后你应该能做到：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt; 能解释 &lt;code&gt;.venv&lt;/code&gt; 是什么。&lt;/li&gt;
&lt;li&gt; 能用 &lt;code&gt;uv add&lt;/code&gt; 安装包。&lt;/li&gt;
&lt;li&gt; 能在 VS Code 里选择 &lt;code&gt;.venv/bin/python&lt;/code&gt;。&lt;/li&gt;
&lt;li&gt; 能运行 &lt;code&gt;python src/hello.py&lt;/code&gt;。&lt;/li&gt;
&lt;li&gt; 能运行 pandas 示例。&lt;/li&gt;
&lt;li&gt; 能创建 notebook 并选择当前 kernel。&lt;/li&gt;
&lt;li&gt; 知道 &lt;code&gt;.venv/&lt;/code&gt; 不应该提交到 Git。&lt;/li&gt;
&lt;li&gt; 知道如何从终端进入项目目录。&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;18. 最小命令速查&lt;/h2&gt;
&lt;h3&gt;18.1 创建项目&lt;/h3&gt;
&lt;div&gt;&lt;div&gt;&lt;span&gt;omp&lt;/span&gt;&lt;span&gt;fish&lt;/span&gt;&lt;/div&gt;&lt;pre&gt;&lt;code&gt;mkdir -p ~/Code/python-learning/week01-basics
cd ~/Code/python-learning/week01-basics
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;h3&gt;18.2 初始化环境&lt;/h3&gt;
&lt;div&gt;&lt;div&gt;&lt;span&gt;omp&lt;/span&gt;&lt;span&gt;fish&lt;/span&gt;&lt;/div&gt;&lt;pre&gt;&lt;code&gt;uv init
uv venv
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;h3&gt;18.3 激活环境&lt;/h3&gt;
&lt;p&gt;fish 用户执行：&lt;/p&gt;
&lt;div&gt;&lt;div&gt;&lt;span&gt;omp&lt;/span&gt;&lt;span&gt;fish&lt;/span&gt;&lt;/div&gt;&lt;pre&gt;&lt;code&gt;source .venv/bin/activate.fish
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;如果教程不是 fish 版本，可能会提到：&lt;/p&gt;
&lt;div&gt;&lt;div&gt;&lt;span&gt;omp&lt;/span&gt;&lt;span&gt;prompt&lt;/span&gt;&lt;/div&gt;&lt;pre&gt;&lt;code&gt;Bash/Zsh activate 脚本
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;h3&gt;18.4 安装包&lt;/h3&gt;
&lt;div&gt;&lt;div&gt;&lt;span&gt;omp&lt;/span&gt;&lt;span&gt;fish&lt;/span&gt;&lt;/div&gt;&lt;pre&gt;&lt;code&gt;uv add numpy pandas matplotlib jupyter ipykernel
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;h3&gt;18.5 打开 VS Code&lt;/h3&gt;
&lt;div&gt;&lt;div&gt;&lt;span&gt;omp&lt;/span&gt;&lt;span&gt;fish&lt;/span&gt;&lt;/div&gt;&lt;pre&gt;&lt;code&gt;code .
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;h3&gt;18.6 运行脚本&lt;/h3&gt;
&lt;div&gt;&lt;div&gt;&lt;span&gt;omp&lt;/span&gt;&lt;span&gt;fish&lt;/span&gt;&lt;/div&gt;&lt;pre&gt;&lt;code&gt;python src/hello.py
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;h2&gt;19. 和 20 周计划的关系&lt;/h2&gt;
&lt;p&gt;这篇文档对应 &lt;a href=&quot;/post/ustc-stat-ai-quant-plan/&quot;&gt;20 周 AI / 量化成长计划&lt;/a&gt; 的：&lt;/p&gt;
&lt;div&gt;&lt;div&gt;&lt;span&gt;omp&lt;/span&gt;&lt;span&gt;prompt&lt;/span&gt;&lt;/div&gt;&lt;pre&gt;&lt;code&gt;Phase 1 / Week 01：环境与 Python 基础
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;完成本文后，你就可以进入下一步：&lt;/p&gt;
&lt;div&gt;&lt;div&gt;&lt;span&gt;omp&lt;/span&gt;&lt;span&gt;prompt&lt;/span&gt;&lt;/div&gt;&lt;pre&gt;&lt;code&gt;Week 01：Python 基础语法、函数、列表、字典、字符串、文件读写
Week 02：Python 进阶 + Linux 工作流
Week 03：NumPy / Pandas
Week 04：可视化 + EDA 报告
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;最终目标不是“装好了环境”，而是能持续产出可运行、可复现、可解释的小项目。&lt;/p&gt;
&lt;h2&gt;20. Week 01 Python 基础语法练习&lt;/h2&gt;
&lt;p&gt;前面的步骤解决“环境能不能跑”。Week 01 还要完成一个最小 Python 脚本，证明你能写函数、处理列表、读写文件，并从终端运行。&lt;/p&gt;
&lt;h3&gt;20.1 新建练习脚本&lt;/h3&gt;
&lt;p&gt;确认你在项目目录：&lt;/p&gt;
&lt;div&gt;&lt;div&gt;&lt;span&gt;omp&lt;/span&gt;&lt;span&gt;fish&lt;/span&gt;&lt;/div&gt;&lt;pre&gt;&lt;code&gt;cd ~/Code/python-learning/week01-basics
source .venv/bin/activate.fish
pwd
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;新建脚本：&lt;/p&gt;
&lt;div&gt;&lt;div&gt;&lt;span&gt;omp&lt;/span&gt;&lt;span&gt;fish&lt;/span&gt;&lt;/div&gt;&lt;pre&gt;&lt;code&gt;mkdir -p src data notes
printf &quot;%s\n&quot; &quot;name,score&quot; &quot;Alice,85&quot; &quot;Bob,92&quot; &quot;Cindy,78&quot; &amp;gt; data/scores.csv
code src/week01_basic.py
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;逐句解释：&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;命令&lt;/th&gt;
&lt;th&gt;含义&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;&lt;tr&gt;
&lt;td&gt;&lt;code&gt;mkdir -p src data notes&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;确保代码、数据、笔记目录存在&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;printf ... &amp;gt; data/scores.csv&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;用 fish 兼容方式生成一个小 CSV 文件&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;code src/week01_basic.py&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;用 VS Code 打开脚本文件&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;&lt;/table&gt;
&lt;h3&gt;20.2 写入 Python 代码&lt;/h3&gt;
&lt;p&gt;在 &lt;code&gt;src/week01_basic.py&lt;/code&gt; 里写：&lt;/p&gt;
&lt;div&gt;&lt;div&gt;&lt;span&gt;omp&lt;/span&gt;&lt;span&gt;python&lt;/span&gt;&lt;/div&gt;&lt;pre&gt;&lt;code&gt;from pathlib import Path
&lt;p&gt;def mean(xs: list[float]) -&amp;gt; float:
if not xs:
raise ValueError(“xs must not be empty”)
return sum(xs) / len(xs)&lt;/p&gt;
&lt;p&gt;def read_scores(path: str) -&amp;gt; list[float]:
lines = Path(path).read_text(encoding=“utf-8”).strip().splitlines()
scores: list[float] = []
for line in lines[1:]:
name, score_text = line.split(”,”)
scores.append(float(score_text))
return scores&lt;/p&gt;
&lt;/code&gt;&lt;p&gt;&lt;code&gt;scores = read_scores(“data/scores.csv”)
print(“scores:”, scores)
print(“mean:”, mean(scores))
&lt;/code&gt;&lt;/p&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;&lt;/p&gt;
&lt;p&gt;这段代码练到的能力：&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;代码&lt;/th&gt;
&lt;th&gt;训练点&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;&lt;tr&gt;
&lt;td&gt;&lt;code&gt;def mean(...)&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;函数定义&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;list[float]&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;类型提示&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;if not xs&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;基础条件判断&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;Path(...).read_text(...)&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;文件读取&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;splitlines()&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;字符串处理&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;for line in lines[1:]&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;列表切片和循环&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;float(score_text)&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;类型转换&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;print(...)&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;终端输出&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;&lt;/table&gt;
&lt;h3&gt;20.3 从 fish 终端运行&lt;/h3&gt;
&lt;div&gt;&lt;div&gt;&lt;span&gt;omp&lt;/span&gt;&lt;span&gt;fish&lt;/span&gt;&lt;/div&gt;&lt;pre&gt;&lt;code&gt;python src/week01_basic.py
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;期望输出类似：&lt;/p&gt;
&lt;div&gt;&lt;div&gt;&lt;span&gt;omp&lt;/span&gt;&lt;span&gt;prompt&lt;/span&gt;&lt;/div&gt;&lt;pre&gt;&lt;code&gt;scores: [85.0, 92.0, 78.0]
mean: 85.0
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;如果能跑通，说明你已经完成 Week 01 最小代码闭环：&lt;/p&gt;
&lt;div&gt;&lt;div&gt;&lt;span&gt;omp&lt;/span&gt;&lt;span&gt;prompt&lt;/span&gt;&lt;/div&gt;&lt;pre&gt;&lt;code&gt;创建数据 -&amp;gt; 写 Python 函数 -&amp;gt; 读取文件 -&amp;gt; 终端运行 -&amp;gt; 得到结果
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;h3&gt;20.4 写学习笔记&lt;/h3&gt;
&lt;div&gt;&lt;div&gt;&lt;span&gt;omp&lt;/span&gt;&lt;span&gt;fish&lt;/span&gt;&lt;/div&gt;&lt;pre&gt;&lt;code&gt;code notes/week01-python.md
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;建议写下：&lt;/p&gt;
&lt;div&gt;&lt;div&gt;&lt;span&gt;omp&lt;/span&gt;&lt;span&gt;markdown&lt;/span&gt;&lt;/div&gt;&lt;pre&gt;&lt;code&gt;# Week 01 Python 笔记
&lt;h2&gt;我今天学会了&lt;a href=&quot;#我今天学会了&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;如何用 fish 激活 .venv&lt;/li&gt;
&lt;li&gt;如何用 python 运行脚本&lt;/li&gt;
&lt;li&gt;如何定义函数&lt;/li&gt;
&lt;li&gt;如何读取 CSV 文本&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;我还不熟的地方&lt;a href=&quot;#我还不熟的地方&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;路径和当前目录&lt;/li&gt;
&lt;li&gt;list / dict / for loop&lt;/li&gt;
&lt;li&gt;报错信息怎么读&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;下次要做&lt;a href=&quot;#下次要做&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;
&lt;/code&gt;&lt;ul&gt;&lt;code&gt;
&lt;li&gt;把 read_scores 改得更健壮&lt;/li&gt;
&lt;/code&gt;&lt;li&gt;&lt;code&gt;学 pandas 读取 CSV
&lt;/code&gt;&lt;/li&gt;&lt;/ul&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;h3&gt;20.5 Week 01 最终验收&lt;/h3&gt;
&lt;div&gt;&lt;div&gt;&lt;span&gt;omp&lt;/span&gt;&lt;span&gt;fish&lt;/span&gt;&lt;/div&gt;&lt;pre&gt;&lt;code&gt;cd ~/Code/python-learning/week01-basics
source .venv/bin/activate.fish
python src/week01_basic.py
test -f notes/week01-python.md; and echo &quot;notes exists&quot;
test -f data/scores.csv; and echo &quot;data exists&quot;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;你应该能确认：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;source .venv/bin/activate.fish&lt;/code&gt; 不报错。&lt;/li&gt;
&lt;li&gt;&lt;code&gt;command -v python&lt;/code&gt; 指向 &lt;code&gt;.venv/bin/python&lt;/code&gt;。&lt;/li&gt;
&lt;li&gt;&lt;code&gt;python src/week01_basic.py&lt;/code&gt; 能输出平均分。&lt;/li&gt;
&lt;li&gt;&lt;code&gt;notes/week01-python.md&lt;/code&gt; 存在。&lt;/li&gt;
&lt;li&gt;你能解释 &lt;code&gt;mean(xs)&lt;/code&gt; 为什么要处理空列表。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;完成这些后，再进入 Week 02：&lt;a href=&quot;/post/week02-python-linux-workflow/&quot;&gt;Python 进阶 + Linux 工作流&lt;/a&gt;。&lt;/p&gt;
&lt;p&gt;plain &lt;/p&gt;&lt;/article&gt;&lt;p&gt;&lt;/p&gt;
  &lt;/div&gt;
&lt;/section&gt;</content:encoded><category>category:工具</category><category>category:Python基础</category><category>tag:CachyOS</category><category>tag:fish</category><category>tag:uv</category><category>tag:Python环境</category><category>tag:基础语法</category></item></channel></rss>