<?xml version="1.0" encoding="utf-8"?>
<feed xmlns="http://www.w3.org/2005/Atom">
  <id>https://www.celyn-blog.xyz/</id>
  <title type="text">Celyn&apos;s Blog</title>
  <subtitle type="text">反思所学 · 反馈输出</subtitle>
  <updated>2026-10-09T00:00:00.000Z</updated>
  <author><name>Celyn</name></author>
  <link rel="alternate" href="https://www.celyn-blog.xyz/"/>
  <link rel="self" href="https://www.celyn-blog.xyz/atom.xml"/>
  <generator uri="https://github.com/CuteLeaf/Firefly">Firefly v6.16.8</generator>
    <entry>
      <id>https://www.celyn-blog.xyz/posts/common-software/</id>
      <title type="text">常用软件和项目</title>
      <published>2026-10-09T00:00:00.000Z</published>
      <updated>2026-10-09T00:00:00.000Z</updated>
      <author><name>Celyn</name></author>
      <link rel="alternate" href="https://www.celyn-blog.xyz/posts/common-software/"/>
      <summary type="text">记录日常使用的软件与工具，按用途整理，方便查找和回顾。</summary>
      <content type="html"><![CDATA[<p>记录常用的软件与工具，按用途分类，持续补充。</p>
<section><h2>下载与传输<a href="#下载与传输"><span>#</span></a></h2><section><h3>1. Free Download Manager：下载加速与管理<a href="#1-free-download-manager下载加速与管理"><span>#</span></a></h3><p><a href="https://www.freedownloadmanager.org/zh/" target="_blank">官方网站：Free Download Manager</a> · <a href="https://www.freedownloadmanager.org/zh/download.htm" target="_blank">下载页面</a></p><p></p><figure><img alt="Free Download Manager Windows 软件界面截图" loading="lazy" width="7392" height="4988" src="/_astro/free-download-manager-screenshot.B4iMmZD4_1wgX8w.webp" /><figcaption>Free Download Manager Windows 软件界面截图</figcaption></figure><p></p><p>Free Download Manager（FDM）是一款免费的下载管理器，支持 Windows、macOS、Android 和 Linux。它可以把文件分成多个片段并行下载，支持断点续传，也内置 BitTorrent 和磁力链接下载功能。</p><p>它还提供浏览器集成，适合集中管理日常下载任务，尤其是较大的文件、视频和种子任务。软件官网提供无广告、无恶意软件的版本；安装时仍建议从官方网站下载，并留意下载来源与文件类型。截图来自 FDM 官网的 Windows 界面。</p></section></section>
<section><h2>图像与视频<a href="#图像与视频"><span>#</span></a></h2><section><h3>1. Upscayl：图像超分辨率<a href="#1-upscayl图像超分辨率"><span>#</span></a></h3><p><a href="https://github.com/upscayl/upscayl" target="_blank">GitHub：Upscayl</a></p><p></p><figure><img alt="Upscayl 软件界面截图" loading="lazy" width="1351" height="980" src="/_astro/upscayl-screenshot.CCOJa03k_iHpzg.webp" /><figcaption>Upscayl 软件界面截图</figcaption></figure><p></p><p>Upscayl 是一款免费、开源的 AI 图像超分辨率工具，用于放大低分辨率图片并增强画面细节，支持 Windows、macOS 和 Linux。</p><p>适合需要提高图片分辨率时使用。运行需要兼容 Vulkan 的显卡，部分集成显卡可能无法使用，使用前应确认设备兼容性。</p></section><section><h3>2. video-parser：视频解析与下载<a href="#2-video-parser视频解析与下载"><span>#</span></a></h3><p><a href="https://github.com/HP-Patience/video-parser" target="_blank">GitHub：video-parser</a></p><p></p><figure><img alt="video-parser 视频解析下载器界面" loading="lazy" width="1614" height="936" src="/_astro/video-parser-screenshot.DHgjlw9T_2gJn60.webp" /><figcaption>video-parser 视频解析下载器界面</figcaption></figure><p></p><p>这是一个基于 FastAPI、Gradio 和 Qwen3-VL 的视频解析、下载与 AI 内容提取工具。项目提供抖音、哔哩哔哩、小红书、快手、好看视频等平台的视频链接解析、下载及在线播放功能，还可借助视觉语言模型提取视频内容并生成文字描述。</p><p>界面支持输入视频链接、选择或自动检测平台，并展示解析状态、视频标题和封面。具体解析效果可能受平台接口变化影响；下载和使用视频时应尊重版权与平台规则，仅处理自己有权使用的内容。</p></section><section><h3>3. OpenScreen：屏幕录制与演示视频制作<a href="#3-openscreen屏幕录制与演示视频制作"><span>#</span></a></h3><p><a href="https://github.com/HP-Patience/openscreen" target="_blank">GitHub：OpenScreen</a></p><p></p><figure><img alt="OpenScreen 演示视频编辑界面" loading="lazy" width="881" height="516" src="/_astro/openscreen-screenshot.BezLw_Nf_Z2gIWaN.webp" /><figcaption>OpenScreen 演示视频编辑界面</figcaption></figure><p></p><p>OpenScreen 是一款免费、开源的屏幕录制与演示视频制作工具，适合制作产品演示和操作教程。可以录制窗口或整个屏幕，再通过画面缩放、光标效果、自定义背景和时间轴剪辑整理录屏，导出 MP4 或 GIF。</p><p>它提供部分与 Screen Studio 类似的核心功能，但不是功能完全一致的替代品。项目说明提示软件仍可能存在问题，制作重要视频时应先测试录制与导出流程。</p><p>维护状态提醒：该仓库 README 已注明原项目归档、不再维护，并提供社区接续版本入口；下载前建议查看仓库中的维护说明和发布记录。</p></section><section><h3>4. video-use：用 Coding Agent 剪辑视频<a href="#4-video-use用-coding-agent-剪辑视频"><span>#</span></a></h3><p><a href="https://github.com/browser-use/video-use" target="_blank">GitHub：video-use</a></p><p></p><figure><img alt="video-use 视频时间轴、音频波形与剪切点示意" loading="lazy" width="881" height="418" src="/_astro/video-use-screenshot.B_-1n44m_3FxUo.webp" /><figcaption>video-use 视频时间轴、音频波形与剪切点示意</figcaption></figure><p></p><p>video-use 是一个面向 Coding Agent 的开源视频剪辑项目。把原始视频放到目录中，用自然语言描述剪辑目标，让 Agent 整理素材、提出剪辑方案，确认后完成渲染与检查。</p><p>它支持删除口头填充词和停顿、添加字幕、调色及制作动画叠加。它不是传统的图形化剪辑软件；截图展示的是辅助剪辑判断的时间轴视图。使用需要配置 FFmpeg 等依赖，项目的转写流程还依赖 ElevenLabs API，应留意素材上传与服务费用。</p></section></section>
<section><h2>浏览器与网页体验<a href="#浏览器与网页体验"><span>#</span></a></h2><section><h3>1. BewlyBewly：哔哩哔哩界面美化<a href="#1-bewlybewly哔哩哔哩界面美化"><span>#</span></a></h3><p><a href="https://github.com/BewlyBewly/BewlyBewly" target="_blank">GitHub：BewlyBewly</a> · <a href="https://chromewebstore.google.com/detail/bewlybewly/bbbiejemhfihiooipfcjmjmbfdmobobp" target="_blank">Chrome 应用商店：安装 BewlyBewly</a></p><p></p><figure><img alt="BewlyBewly 哔哩哔哩首页深色界面" loading="lazy" width="1661" height="957" src="/_astro/bewlybewly-screenshot.CqZEnNQI_1lsYTy.webp" /><figcaption>BewlyBewly 哔哩哔哩首页深色界面</figcaption></figure><p></p><p>BewlyBewly 是一个开源的哔哩哔哩浏览器扩展，主要通过重新设计网页界面改善浏览体验。它调整首页布局、视频卡片和导航，并为常用页面提供深色模式；截图展示的是扩展启用后的首页效果。</p><p>适合想让 B 站网页界面更简洁、统一的场景。项目侧重页面调整与视觉优化，并不是视频下载工具，也不意味着所有页面都已适配深色模式。</p><p>维护状态提醒：该 GitHub 仓库已于 2025 年 2 月 26 日归档，处于只读状态。安装前建议查看应用商店说明与项目维护状态，留意 B 站页面变化可能带来的兼容性问题。</p></section></section>
<section><h2>阅读与收藏<a href="#阅读与收藏"><span>#</span></a></h2><section><h3>1. Omnivore：稍后阅读与文章管理<a href="#1-omnivore稍后阅读与文章管理"><span>#</span></a></h3><p><a href="https://github.com/HP-Patience/omnivore" target="_blank">GitHub：Omnivore</a></p><p></p><figure><img alt="Omnivore 文章收藏与阅读界面" loading="lazy" width="881" height="621" src="/_astro/omnivore-screenshot.Huq6gkzM_Z8wYWC.webp" /><figcaption>Omnivore 文章收藏与阅读界面</figcaption></figure><p></p><p>Omnivore 是一款开源的稍后阅读工具，用于集中保存文章，并通过标签、搜索、高亮和笔记整理阅读内容。它也支持 PDF、邮件订阅内容和 RSS，并提供 Obsidian 集成。</p><p>适合先收藏、再集中阅读和整理的场景。需要注意，原有云服务已于 2024 年 11 月停止，目前项目以自托管方式使用；截图展示的是界面示例，不代表原在线服务仍可用。</p></section></section>
<section><h2>市场与行情<a href="#市场与行情"><span>#</span></a></h2><section><h3>1. OpenStock：股票行情与市场信息<a href="#1-openstock股票行情与市场信息"><span>#</span></a></h3><p><a href="https://github.com/HP-Patience/OpenStock" target="_blank">GitHub：OpenStock</a></p><p></p><figure><img alt="OpenStock 市场概览与股票热力图界面" loading="lazy" width="881" height="468" src="/_astro/openstock-screenshot.DuCuR2_t_Z1Nzc0n.webp" /><figcaption>OpenStock 市场概览与股票热力图界面</figcaption></figure><p></p><p>OpenStock 是一款开源的股票市场信息应用，用于查看市场概览、股票行情、热力图及公司信息，也支持搜索股票和建立个人关注列表。</p><p>适合把关注的股票与市场信息集中到一个界面中查看。自行部署需要配置数据库和行情数据接口；数据更新频率、覆盖范围与实时性取决于数据源和部署配置，不能默认所有行情都是实时的。</p><p>它不是证券交易平台，页面中的行情与信息也不构成投资建议。</p></section></section>
<section><h2>AI 与工作流<a href="#ai-与工作流"><span>#</span></a></h2><section><h3>1. TongFlow：多模态 AI 工作台<a href="#1-tongflow多模态-ai-工作台"><span>#</span></a></h3><p><a href="https://github.com/HP-Patience/tongflow" target="_blank">GitHub：TongFlow</a></p><p></p><figure><img alt="TongFlow 多模态生成工作流界面" loading="lazy" width="550" height="288" src="/_astro/tongflow-screenshot.BXzwxrgm_2wLtJu.webp" /><figcaption>TongFlow 多模态生成工作流界面</figcaption></figure><p></p><p>TongFlow 是一个开源的多模态生成式 AI 工作台，把文本、图像、音频、视频等输入与模型能力组织成画布上的节点，通过添加、转换和组合构建创作流程。</p><p>适合将多个生成步骤放到同一个工作流里，例如先根据文字生成图片，再组合多张图片，或串联脚本、语音和视频生成。不同能力由插件提供，实际可用功能取决于插件和模型服务配置。</p><p>项目提供浏览器云端工作台及桌面入口，也支持从源码或通过 Docker 自托管。桌面入口不等于所有模型都在本地运行，使用时应区分云端服务与自托管配置。</p></section><section><h3>2. ComfyUI：节点式 AI 生成工作流<a href="#2-comfyui节点式-ai-生成工作流"><span>#</span></a></h3><p><a href="https://github.com/HP-Patience/ComfyUI" target="_blank">GitHub：ComfyUI</a></p><p></p><figure><img alt="ComfyUI 图像生成节点工作流界面" loading="lazy" width="881" height="441" src="/_astro/comfyui-screenshot.BBnSKyb5_1EY6tc.webp" /><figcaption>ComfyUI 图像生成节点工作流界面</figcaption></figure><p></p><p>ComfyUI 是一个开源的节点式 AI 生成界面与后端。通过连接模型加载、提示词编码、采样、解码和保存等节点，把生成过程组织成可以保存、复用和调整的工作流。</p><p>适合需要控制生成步骤、组合不同模型和反复调试参数的场景。可用能力取决于模型、节点和扩展配置，运行前需要确认相应的硬件与依赖要求；安装第三方节点时也应留意来源与安全性。</p></section><section><h3>3. img2threejs：从参考图重建 Three.js 模型<a href="#3-img2threejs从参考图重建-threejs-模型"><span>#</span></a></h3><p><a href="https://github.com/img2threejs/img2threejs" target="_blank">GitHub：img2threejs</a></p><p></p><figure><img alt="img2threejs 参考图与 Three.js 场景展示" loading="lazy" width="1577" height="964" src="/_astro/img2threejs-screenshot.ZdrV_v1P_ZbGjRX.webp" /><figcaption>img2threejs 参考图与 Three.js 场景展示</figcaption></figure><p></p><p>img2threejs 是一个从参考图重建 Three.js 模型的开源项目与 Agent Skill。它强调用可检查、可修改的程序化代码构建几何体，通过分阶段检查与参考图对照，逐步调整模型。</p><p>输出重点是可维护、适合继续动画化的代码，而不是一个只能导入使用的封闭模型资产。适合探索图像到代码驱动 3D 场景的制作流程，重建效果仍取决于参考图、Agent 能力与迭代调整。</p></section></section>]]></content>
    </entry>
    <entry>
      <id>https://www.celyn-blog.xyz/posts/common-prompts/</id>
      <title type="text">常用Prompts与Skills</title>
      <published>2026-10-07T00:00:00.000Z</published>
      <updated>2026-10-07T00:00:00.000Z</updated>
      <author><name>Celyn</name></author>
      <link rel="alternate" href="https://www.celyn-blog.xyz/posts/common-prompts/"/>
      <summary type="text">整理日常使用的 Prompts 与 Skills，沉淀可复用的 AI 协作方法与工作流程。</summary>
      <content type="html"><![CDATA[<section><h2>常用 Prompts<a href="#常用-prompts"><span>#</span></a></h2><section><h3>1. 中文授课稿改写提示词<a href="#1-中文授课稿改写提示词"><span>#</span></a></h3><div><div><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>请把下面的文稿改成“专业教师可以直接朗读的中文授课稿”。</span></div></div><div><div><div>2</div></div><div>
</div></div><div><div><div>3</div></div><div><span>我的目标不是随意聊天，也不是教材摘要，而是：</span></div></div><div><div><div>4</div></div><div><span>句子完整、对象明确、上下文连贯、口语自然，同时保持专业准确。</span></div></div><div><div><div>5</div></div><div>
</div></div><div><div><div>6</div></div><div><span>请按以下要求修改：</span></div></div><div><div><div>7</div></div><div>
</div></div><div><div><div>8</div></div><div><span>1. 明确主语和讲述对象</span></div></div><div><div><div>9</div></div><div><span>定义句、提问句、结论句、转折句必须明确在讨论哪个对象。</span></div></div><div><div><div>10</div></div><div><span>不要让听众猜“谁满足条件”“哪个量在变化”“哪个事件有概率”。</span></div></div><div><div><div>11</div></div><div><span>不要机械地给每句话添加“我们”；应当使用准确的对象名称作主语。</span></div></div><div><div><div>12</div></div><div><span>同一句中主语清楚时，可以自然承接，避免重复啰嗦。</span></div></div><div><div><div>13</div></div><div>
</div></div><div><div><div>14</div></div><div><span>2. 保持专业定义</span></div></div><div><div><div>15</div></div><div><span>先给出准确的定义，再用完整的口头语言解释。</span></div></div><div><div><div>16</div></div><div><span>不要用模糊说法、比喻或口号代替定义。</span></div></div><div><div><div>17</div></div><div><span>保留结论成立的条件，不要为了通俗而扩大结论的适用范围。</span></div></div><div><div><div>18</div></div><div>
</div></div><div><div><div>19</div></div><div><span>3. 补足上下文衔接</span></div></div><div><div><div>20</div></div><div><span>引入新概念前，先说明当前有什么问题、为什么需要这个概念。</span></div></div><div><div><div>21</div></div><div><span>后一句应承接前一句已经出现的对象或问题。</span></div></div><div><div><div>22</div></div><div><span>不要只靠“接下来、那么、因此”等连接词制造表面的连贯。</span></div></div><div><div><div>23</div></div><div>
</div></div><div><div><div>24</div></div><div><span>4. 检查必要的前置知识</span></div></div><div><div><div>25</div></div><div><span>检查是否出现了未经解释的术语、符号、条件或方法。</span></div></div><div><div><div>26</div></div><div><span>对理解当前内容必需的知识，补上最短的必要解释。</span></div></div><div><div><div>27</div></div><div><span>不要默认听众能自行补出关键步骤，也不要展开完整理论和全部推导。</span></div></div><div><div><div>28</div></div><div>
</div></div><div><div><div>29</div></div><div><span>5. 准确区分对象</span></div></div><div><div><div>30</div></div><div><span>特别检查是否混淆了对象本身、对象的取值、相关集合、</span></div></div><div><div><div>31</div></div><div><span>条件以及由它们计算出的量。</span></div></div><div><div><div>32</div></div><div><span>数学稿中尤其要区分函数与函数值、事件与概率、密度与单点概率。</span></div></div><div><div><div>33</div></div><div>
</div></div><div><div><div>34</div></div><div><span>6. 使用自然的授课语言</span></div></div><div><div><div>35</div></div><div><span>使用完整、顺口、适合朗读的句子，避免提纲式短语和名词堆叠。</span></div></div><div><div><div>36</div></div><div><span>不要故意加入网络用语、语气词、夸张表达或不恰当的比喻。</span></div></div><div><div><div>37</div></div><div><span>不要把每一段都写成设问，也不要反复强调“这很重要”。</span></div></div><div><div><div>38</div></div><div>
</div></div><div><div><div>39</div></div><div><span>7. 控制内容与篇幅</span></div></div><div><div><div>40</div></div><div><span>保留原有知识范围和已经确定的教学顺序。</span></div></div><div><div><div>41</div></div><div><span>删除重复解释、没有教学作用的简单例子和无意义的演算。</span></div></div><div><div><div>42</div></div><div><span>只补理解所必需的内容，不要借修改之名扩写整门课程。</span></div></div><div><div><div>43</div></div><div><span>优先用更准确的句子替换旧句，而不是不断追加解释。</span></div></div><div><div><div>44</div></div><div>
</div></div><div><div><div>45</div></div><div><span>修改后，请再从“听众只听一遍、不回看文字”的角度检查：</span></div></div><div><div><div>46</div></div><div><span>- 听众能否知道每句话在讲哪个对象？</span></div></div><div><div><div>47</div></div><div><span>- 代词和省略成分是否有明确指向？</span></div></div><div><div><div>48</div></div><div><span>- 新概念是否有必要的引入？</span></div></div><div><div><div>49</div></div><div><span>- 结论是否交代了条件与必要理由？</span></div></div><div><div><div>50</div></div><div><span>- 是否仍有可以删去的重复内容？</span></div></div><div><div><div>51</div></div><div>
</div></div><div><div><div>52</div></div><div><span>请输出修改后的完整文稿，再简要列出主要修改。</span></div></div><div><div><div>53</div></div><div><span>如果发现会影响原意的专业错误，请单独说明。</span></div></div><div><div><div>54</div></div><div><span>若涉及文件，请另存新版本，不覆盖原稿。</span></div></div><div><div><div>55</div></div><div>
</div></div><div><div><div>56</div></div><div><span>文稿如下：</span></div></div><div><div><div>57</div></div><div><span>【粘贴文稿】</span></div></div></code></pre><div><div></div><div></div></div></figure><div></div></div><span>展开</span><span>收起</span></div></div><hr /></section><section><h3>2. AGENTS.md 配置提示词<a href="#2-agentsmd-配置提示词"><span>#</span></a></h3><div><div><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>你是一个 Coding Agent。请在当前项目中创建或更新根目录的 `AGENTS.md`，为后续 AI 编程任务写入一套清晰、可执行、不过度冗长的规则，让 CodeGraph、Caveman、Ponytail 分工互补。</span></div></div><div><div><div>2</div></div><div>
</div></div><div><div><div>3</div></div><div><span>本任务不只是写文档：还要检查当前电脑上的全局工具配置、在缺少时完成必要配置、为当前项目初始化所需配置，并实际验证三者能否使用。</span></div></div><div><div><div>4</div></div><div>
</div></div><div><div><div>5</div></div><div><span>参考：</span></div></div><div><div><div>6</div></div><div><span>- Ponytail：https://github.com/DietrichGebert/ponytail</span></div></div><div><div><div>7</div></div><div><span>- Caveman：https://github.com/JuliusBrussee/caveman</span></div></div><div><div><div>8</div></div><div><span>- CodeGraph MCP 设置示例：https://github.com/shantham/codegraph/blob/main/docs/MCP-SERVER-SETUP.md</span></div></div><div><div><div>9</div></div><div><span>- Model Context Protocol：https://modelcontextprotocol.io/</span></div></div><div><div><div>10</div></div><div>
</div></div><div><div><div>11</div></div><div><span>## 执行步骤</span></div></div><div><div><div>12</div></div><div>
</div></div><div><div><div>13</div></div><div><span>1. 先检查当前项目的 `AGENTS.md`、Git 状态和未提交改动；检查当前 Coding Agent 的全局配置、相关 Skill/插件、MCP 服务和可用命令。不要覆盖已有配置或无关改动，也不要输出密钥、令牌或其他凭据。</span></div></div><div><div><div>14</div></div><div><span>2. 如果当前电脑缺少所需的全局配置，只为当前使用的 Coding Agent 补齐：</span></div></div><div><div><div>15</div></div><div><span><span>   </span></span><span>- **CodeGraph**：按官方说明配置全局 MCP 服务。</span></div></div><div><div><div>16</div></div><div><span><span>   </span></span><span>- **Caveman**：按 Skill/工作流安装和使用；不把 CLI 当作前置条件，不为此安装 CLI 或索取 API Key。</span></div></div><div><div><div>17</div></div><div><span><span>   </span></span><span>- **Ponytail**：配置其 Skill/插件，使 Full 模式可用。</span></div></div><div><div><div>18</div></div><div><span><span>   </span></span><span>修改已有全局配置前先备份到项目目录之外，并保留其他设置。若配置依赖用户提供的凭据，停止并说明如何在本机安全配置；不要让用户把密钥发到聊天中。</span></div></div><div><div><div>19</div></div><div><span>3. 全局工具可用后，初始化当前项目所需的 CodeGraph 索引。全局 MCP 配置与项目索引是两件事，不能互相替代。索引范围限制在源码、测试等代码目录；排除个人数据、凭据、依赖目录、构建产物和其他不相关内容。若无法安全划定索引范围，先询问用户。</span></div></div><div><div><div>20</div></div><div><span>4. 实际验证三者，而不只检查“已安装”状态：</span></div></div><div><div><div>21</div></div><div><span><span>   </span></span><span>- **CodeGraph**：在当前项目索引中查询文件或符号，确认结果属于当前项目和预期范围。</span></div></div><div><div><div>22</div></div><div><span><span>   </span></span><span>- **Caveman**：使用已安装的相关 Skill/工作流，完成一次不修改文件的调查或简报验证；明确区分事实与推断。</span></div></div><div><div><div>23</div></div><div><span><span>   </span></span><span>- **Ponytail**：以 Full 模式对一个只读的小任务展示最小正确方案，不修改无关文件。</span></div></div><div><div><div>24</div></div><div><span><span>   </span></span><span>不得把读取配置、找到 Skill 文件或其他项目的索引结果声称为成功调用。若工具无法实际运行，如实记录失败原因。</span></div></div><div><div><div>25</div></div><div><span>5. 创建或更新项目根目录的 `AGENTS.md`：</span></div></div><div><div><div>26</div></div><div><span><span>   </span></span><span>- 若文件已存在，保留原有内容，只追加或合并；不得删除、覆盖或擅自重写现有规则。</span></div></div><div><div><div>27</div></div><div><span><span>   </span></span><span>- 若已有 CodeGraph 规则，保留并复用，避免重复粘贴。</span></div></div><div><div><div>28</div></div><div><span><span>   </span></span><span>- 只写对后续任务持久有效的协作规则，不把机器路径、密钥、临时日志写进项目文档。</span></div></div><div><div><div>29</div></div><div>
</div></div><div><div><div>30</div></div><div><span>## `AGENTS.md` 中应写明的协作规则</span></div></div><div><div><div>31</div></div><div>
</div></div><div><div><div>32</div></div><div><span>- **CodeGraph** 用于代码结构查询：定义、调用链、影响范围和上下文。结构问题优先使用；核实当前会话确实提供 CodeGraph，且索引对应当前项目。</span></div></div><div><div><div>33</div></div><div><span>- **Caveman** 按 Skill/工作流使用：不明问题先调查，报告简短且证据优先，必要时采用安全重构或外科式修复。不得将 CLI 作为前置条件，也不得声称调用了未加载的 Skill。</span></div></div><div><div><div>34</div></div><div><span>- **Ponytail** 用于最小正确实现：采用 YAGNI、复用现有代码、优先标准库/平台能力和已有依赖、避免无理由新增依赖或抽象。实现时使用 Full 模式；若 Skill 未加载，可遵循原则但不得声称已调用。</span></div></div><div><div><div>35</div></div><div><span>- 默认顺序：结构问题先用 CodeGraph；不明问题先调查；实现时用 Ponytail Full；最后按风险做最小必要验证，并遵守项目已有的强制检查。</span></div></div><div><div><div>36</div></div><div><span>- 正确性、安全、数据安全、用户明确要求和无障碍基础都高于“少写代码”；不得为精简而削弱这些要求。</span></div></div><div><div><div>37</div></div><div><span>- 最终报告简短说明改动、原因、实际验证结果和剩余风险；未运行的检查不得报告为通过。</span></div></div><div><div><div>38</div></div><div>
</div></div><div><div><div>39</div></div><div><span>## 验收与回复</span></div></div><div><div><div>40</div></div><div>
</div></div><div><div><div>41</div></div><div><span>检查 `AGENTS.md` 的 Markdown 标题层级、代码围栏和文件结尾；执行 `git diff --check`。文档改动不运行无关构建测试，但工具配置和项目索引必须按上面的步骤实际验证。</span></div></div><div><div><div>42</div></div><div>
</div></div><div><div><div>43</div></div><div><span>完成后分别报告：</span></div></div><div><div><div>44</div></div><div><span>- `AGENTS.md` 的绝对路径，以及是创建还是更新；</span></div></div><div><div><div>45</div></div><div><span>- 是否发现并保留已有 CodeGraph 规则；</span></div></div><div><div><div>46</div></div><div><span>- 全局配置中哪些已存在、哪些新配置；</span></div></div><div><div><div>47</div></div><div><span>- 项目索引的位置、范围和排除项；</span></div></div><div><div><div>48</div></div><div><span>- CodeGraph、Caveman、Ponytail 各自的实际验证结果；</span></div></div><div><div><div>49</div></div><div><span>- 未能验证的部分、原因和剩余风险。</span></div></div></code></pre><div><div></div><div></div></div></figure><div></div></div><span>展开</span><span>收起</span></div></div></section><section><h3>3. 前端视觉与交互检查提示词<a href="#3-前端视觉与交互检查提示词"><span>#</span></a></h3><div><div><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>请先全面检查现有前端页面，重点检查以下问题：</span></div></div><div><div><div>2</div></div><div>
</div></div><div><div><div>3</div></div><div><span>1. 不必要、丑陋或风格不统一的图标。</span></div></div><div><div><div>4</div></div><div><span>2. 多余且没有实际意义的小字。</span></div></div><div><div><div>5</div></div><div><span>3. 按钮点击、状态切换时的频闪，以及不自然或多余的动画效果。</span></div></div><div><div><div>6</div></div><div><span>4. 所有按钮的边框位置、对齐和显示是否正常；结合现有页面风格，判断哪些按钮需要补充或调整边框与阴影，避免统一套用。</span></div></div><div><div><div>7</div></div><div>
</div></div><div><div><div>8</div></div><div><span>需要替换的图标统一从 Iconify 图标库中挑选，尽量使用同一图标集，保持风格一致：</span></div></div><div><div><div>9</div></div><div><span>https://icon-sets.iconify.design/</span></div></div><div><div><div>10</div></div><div>
</div></div><div><div><div>11</div></div><div><span>先把发现的问题、对应页面或组件位置，以及修改建议整理出来给我审核。</span></div></div><div><div><div>12</div></div><div>
</div></div><div><div><div>13</div></div><div><span>涉及图标替换时，制作一个可在浏览器中查看的图标审核页，展示原图标与候选图标的对比、各自的 Iconify 名称（如适用）、使用位置和替换理由，方便我逐项审核。审核页仅用于预览，不直接替换业务页面中的图标。</span></div></div><div><div><div>14</div></div><div>
</div></div><div><div><div>15</div></div><div><span>确认后再动手修改，不要直接批量调整。</span></div></div></code></pre><div><div></div><div></div></div></figure><div></div></div><span>展开</span><span>收起</span></div></div></section></section>
<section><h2>常用 Skills<a href="#常用-skills"><span>#</span></a></h2><p><a href="https://github.com/HP-Patience/Skills" target="_blank">GitHub：我的 Skills 仓库</a></p><section><h3>1. requirement-validate：需求理解确认<a href="#1-requirement-validate需求理解确认"><span>#</span></a></h3><p><a href="https://github.com/HP-Patience/Skills/blob/master/skills/requirement-validate" target="_blank">GitHub：requirement-validate</a></p><p>这是我常用的需求确认 Skill。在开始实现之前，让 AI 用自己的话完整复述需求，我再审查、修改或补充，反复对齐直到确认无误，避免 AI 理解偏了就直接动手。</p><p>它只负责确认需求，不写代码、不设计方案，也不主动提供建议。每轮反馈后都会重新复述完整需求；确认后结束，不自动进入实现阶段。</p><p>使用时，附上需求文本，并明确说“先别写代码，先确认一下需求”或“帮我理解需求”。</p></section><section><h3>2. blog-cover-prompt：博客封面提示词<a href="#2-blog-cover-prompt博客封面提示词"><span>#</span></a></h3><p><a href="https://github.com/HP-Patience/Skills/tree/main/skills/blog-cover-prompt" target="_blank">GitHub：blog-cover-prompt</a></p><p>这是我常用的博客封面提示词 Skill。根据文章的标题、摘要或正文，提取核心对象与关系，生成可以直接用于绘图的提示词，让不同文章的封面保持统一风格，同时体现各自的主题。</p><p>封面以深蓝黑背景、电蓝与青色光线、玻璃或全息材质为主。它会给出构图思路、正向提示词和负向约束，默认只写提示词，不直接生成图片，也不修改或发布文章。</p><p>使用时，附上文章内容，并说明“为这篇文章写封面提示词，保持博客已有封面风格”。</p></section><section><h3>3. humanizer-zh：去除中文 AI 写作痕迹<a href="#3-humanizer-zh去除中文-ai-写作痕迹"><span>#</span></a></h3><p><a href="https://github.com/HP-Patience/Skills/blob/main/skills/humanizer-zh" target="_blank">GitHub：humanizer-zh</a></p><p>用于去除中文文本中的 AI 写作痕迹，让表达更自然。写完文章或文案后，可以用它检查和调整生硬、模板化的表达。</p><p>使用时，附上需要修改的文本，并说明“去除这段文字中的 AI 写作痕迹，保留原意，让表达更自然”。</p></section><section><h3>4. beautify-github-readme：整理与美化 README<a href="#4-beautify-github-readme整理与美化-readme"><span>#</span></a></h3><p><a href="https://github.com/HP-Patience/Skills/blob/main/skills/beautify-github-readme" target="_blank">GitHub：beautify-github-readme</a></p><p>用于整理并美化 GitHub README 的内容层级与视觉呈现，让读者更容易找到项目介绍、使用方式和其他重要信息。</p><p>使用时，提供项目仓库或 README 文件，并说明“整理并美化这个 README，保留重要信息，让内容层级和视觉呈现更清晰”。</p></section><section><h3>5. vibe-learn：面向非科班开发者的实践学习<a href="#5-vibe-learn面向非科班开发者的实践学习"><span>#</span></a></h3><p><a href="https://github.com/HP-Patience/Skills/blob/main/skills/vibe-learn" target="_blank">GitHub：vibe-learn</a></p><p>面向非科班开发者，教授工具使用和向 AI 描述需求的方法。重点是把实际任务说清楚、理解操作步骤，而不是展开底层理论或数学推导。</p><p>使用时，说明当前想完成的任务或想学习的工具，并提出“教我怎么使用这个工具，以及怎样向 AI 描述我的需求”。</p></section></section>]]></content>
    </entry>
    <entry>
      <id>https://www.celyn-blog.xyz/posts/articles-worth-revisiting/</id>
      <title type="text">值得反复观看的文章与视频（持续更新ing）</title>
      <published>2026-09-23T00:00:00.000Z</published>
      <updated>2026-09-23T00:00:00.000Z</updated>
      <author><name>Celyn</name></author>
      <link rel="alternate" href="https://www.celyn-blog.xyz/posts/articles-worth-revisiting/"/>
      <summary type="text">收集有意思或实用的文章与视频，方便以后回来重读、重看。</summary>
      <content type="html"><![CDATA[<p>收集有意思或实用的文章与视频，按主题整理，持续更新。</p>
<section><h2>技术与实践<a href="#技术与实践"><span>#</span></a></h2><ol>
<li><strong>【文章】<a href="https://jvns.ca/blog/2024/10/31/ascii-control-characters/#a-table-of-ascii-control-characters" target="_blank">终端里的 ASCII 控制字符 · Julia Evans</a></strong> · <a href="https://jvns.ca/ascii" target="_blank">配套速查表</a><br />
解释终端中常见 <code>Ctrl</code> 组合键与 ASCII 控制字符的关系，并提供速查表，具体行为会因环境和配置而异。<br />
收录：2026-09-23</li>
</ol></section>
<section><h2>AI 与工具<a href="#ai-与工具"><span>#</span></a></h2></section>
<section><h2>学习与思考<a href="#学习与思考"><span>#</span></a></h2><ol>
<li>
<p><strong>【视频】<a href="https://www.bilibili.com/video/BV1bXTY6hEX4?vd_source=6d16fd7d6b0aa6dba70cc69ab1c81632" target="_blank">从理解差异到经营亲密：一套恋爱关系方法论 — real_屠龙的胭脂井</a></strong><br />
从理解男女差异、建立吸引与依恋，到判断关系并经营亲密，也呈现了将恋爱策略化、强调掌控与忠诚的一面。<br />
收录：2026-09-23</p>
</li>
<li>
<p><strong>【视频】<a href="https://www.bilibili.com/video/BV1dVaa6vEXS" target="_blank">数学地图：数学全分支（26重制）</a></strong><br />
从数、结构、空间与变化出发，梳理纯数学、应用数学与数学基础的主要分支，以及它们与物理、计算机科学、机器学习等领域的联系。适合建立数学全局视野，学习具体知识时回来定位它所在的位置。<br />
收录：2026-10-07</p>
</li>
</ol></section>
<section><h2>设计与表达<a href="#设计与表达"><span>#</span></a></h2></section>
<section><h2>生活与见闻<a href="#生活与见闻"><span>#</span></a></h2></section>]]></content>
    </entry>
    <entry>
      <id>https://www.celyn-blog.xyz/posts/math-statistics-knowledge-cards/</id>
      <title type="text">数学与统计学知识卡片(持续更新ing)</title>
      <published>2026-09-20T00:00:00.000Z</published>
      <updated>2026-10-09T00:00:00.000Z</updated>
      <author><name>Celyn</name></author>
      <link rel="alternate" href="https://www.celyn-blog.xyz/posts/math-statistics-knowledge-cards/"/>
      <summary type="text">记录数学基础、概率论与统计学中的重要概念，作为一份持续更新的知识卡片索引。</summary>
      <content type="html"><![CDATA[<p>这里记录一些值得反复理解、但不一定需要单独写成完整文章的数学与统计学概念。内容会随着学习持续补充。</p>
<section><h2>数学基础与逻辑<a href="#数学基础与逻辑"><span>#</span></a></h2><section><h3>充分条件与必要条件<a href="#充分条件与必要条件"><span>#</span></a></h3><section><h4>一句话抓住本质<a href="#一句话抓住本质"><span>#</span></a></h4><p>判断充分条件和必要条件时，不要先看语序，只看箭头：</p><ul>
<li><span><span>A⇒BA\Rightarrow B</span><span><span><span></span><span>A</span><span></span><span>⇒</span><span></span></span><span><span></span><span>B</span></span></span></span>：<span><span>AA</span><span><span><span></span><span>A</span></span></span></span> 是 <span><span>BB</span><span><span><span></span><span>B</span></span></span></span> 的充分条件，<span><span>BB</span><span><span><span></span><span>B</span></span></span></span> 是 <span><span>AA</span><span><span><span></span><span>A</span></span></span></span> 的必要条件。</li>
<li><span><span>A⇔BA\Leftrightarrow B</span><span><span><span></span><span>A</span><span></span><span>⇔</span><span></span></span><span><span></span><span>B</span></span></span></span>：<span><span>AA</span><span><span><span></span><span>A</span></span></span></span> 与 <span><span>BB</span><span><span><span></span><span>B</span></span></span></span> 互为充要条件。</li>
</ul><p>可以把它记成：</p><ul>
<li><strong>充分条件：有它就够了。</strong></li>
<li><strong>必要条件：没有它不行。</strong></li>
</ul></section><section><h4>充分不必要与必要不充分<a href="#充分不必要与必要不充分"><span>#</span></a></h4><p>若 <span><span>AA</span><span><span><span></span><span>A</span></span></span></span> 是 <span><span>BB</span><span><span><span></span><span>B</span></span></span></span> 的充分不必要条件，则</p><span><span><span>A⇒B,B⇏A.A\Rightarrow B,\qquad B\nRightarrow A.</span><span><span><span></span><span>A</span><span></span><span>⇒</span><span></span></span><span><span></span><span>B</span><span>,</span><span></span><span></span><span>B</span><span></span><span>⇏</span><span></span></span><span><span></span><span>A</span><span>.</span></span></span></span></span><p>由 <span><span>A⇒BA\Rightarrow B</span><span><span><span></span><span>A</span><span></span><span>⇒</span><span></span></span><span><span></span><span>B</span></span></span></span> 可知：</p><ul>
<li><span><span>AA</span><span><span><span></span><span>A</span></span></span></span> 是 <span><span>BB</span><span><span><span></span><span>B</span></span></span></span> 的充分条件；</li>
<li><span><span>BB</span><span><span><span></span><span>B</span></span></span></span> 是 <span><span>AA</span><span><span><span></span><span>A</span></span></span></span> 的必要条件。</li>
</ul><p>再由 <span><span>B⇏AB\nRightarrow A</span><span><span><span></span><span>B</span><span></span><span>⇏</span><span></span></span><span><span></span><span>A</span></span></span></span> 可知，<span><span>BB</span><span><span><span></span><span>B</span></span></span></span> 不是 <span><span>AA</span><span><span><span></span><span>A</span></span></span></span> 的充分条件。因此：</p><blockquote><p>若 <span><span>AA</span><span><span><span></span><span>A</span></span></span></span> 是 <span><span>BB</span><span><span><span></span><span>B</span></span></span></span> 的充分不必要条件，那么 <span><span>BB</span><span><span><span></span><span>B</span></span></span></span> 是 <span><span>AA</span><span><span><span></span><span>A</span></span></span></span> 的必要不充分条件。</p></blockquote><p>例如：</p><blockquote><p><span><span>AA</span><span><span><span></span><span>A</span></span></span></span>：一个整数能被 4 整除；<br />
<span><span>BB</span><span><span><span></span><span>B</span></span></span></span>：这个整数能被 2 整除。</p></blockquote><p>显然 <span><span>A⇒BA\Rightarrow B</span><span><span><span></span><span>A</span><span></span><span>⇒</span><span></span></span><span><span></span><span>B</span></span></span></span>，但 <span><span>B⇏AB\nRightarrow A</span><span><span><span></span><span>B</span><span></span><span>⇏</span><span></span></span><span><span></span><span>A</span></span></span></span>。因此，“能被 4 整除”是“能被 2 整除”的充分不必要条件；反过来，“能被 2 整除”是“能被 4 整除”的必要不充分条件。</p></section><section><h4>充要条件中的充分性与必要性<a href="#充要条件中的充分性与必要性"><span>#</span></a></h4><p>“<span><span>PP</span><span><span><span></span><span>P</span></span></span></span> 的充要条件是 <span><span>QQ</span><span><span><span></span><span>Q</span></span></span></span>”表示</p><span><span><span>P⇔Q.P\Leftrightarrow Q.</span><span><span><span></span><span>P</span><span></span><span>⇔</span><span></span></span><span><span></span><span>Q</span><span>.</span></span></span></span></span><p>证明时要完成两个方向：</p><section><h5>充分性<a href="#充分性"><span>#</span></a></h5><p>证明</p><span><span><span>Q⇒P.Q\Rightarrow P.</span><span><span><span></span><span>Q</span><span></span><span>⇒</span><span></span></span><span><span></span><span>P</span><span>.</span></span></span></span></span><p>这里是在说明：条件 <span><span>QQ</span><span><span><span></span><span>Q</span></span></span></span> 足以保证结论 <span><span>PP</span><span><span><span></span><span>P</span></span></span></span> 成立。</p></section><section><h5>必要性<a href="#必要性"><span>#</span></a></h5><p>证明</p><span><span><span>P⇒Q.P\Rightarrow Q.</span><span><span><span></span><span>P</span><span></span><span>⇒</span><span></span></span><span><span></span><span>Q</span><span>.</span></span></span></span></span><p>这里是在说明：结论 <span><span>PP</span><span><span><span></span><span>P</span></span></span></span> 若成立，条件 <span><span>QQ</span><span><span><span></span><span>Q</span></span></span></span> 就必须成立。</p></section></section><section><h4>语序到底影响什么<a href="#语序到底影响什么"><span>#</span></a></h4><p>“<span><span>PP</span><span><span><span></span><span>P</span></span></span></span> 的充要条件是 <span><span>QQ</span><span><span><span></span><span>Q</span></span></span></span>”与“<span><span>QQ</span><span><span><span></span><span>Q</span></span></span></span> 的充要条件是 <span><span>PP</span><span><span><span></span><span>P</span></span></span></span>”在逻辑上等价，因为</p><span><span><span>P⇔QP\Leftrightarrow Q</span><span><span><span></span><span>P</span><span></span><span>⇔</span><span></span></span><span><span></span><span>Q</span></span></span></span></span><p>与</p><span><span><span>Q⇔PQ\Leftrightarrow P</span><span><span><span></span><span>Q</span><span></span><span>⇔</span><span></span></span><span><span></span><span>P</span></span></span></span></span><p>表达的是同一个等价关系。</p><p>但是，<strong>语序会影响证明中哪一个方向被称为充分性，哪一个方向被称为必要性</strong>：</p>

<table><thead><tr><th>要证明的表述</th><th>充分性</th><th>必要性</th></tr></thead><tbody><tr><td><span><span>PP</span><span><span><span></span><span>P</span></span></span></span> 的充要条件是 <span><span>QQ</span><span><span><span></span><span>Q</span></span></span></span></td><td><span><span>Q⇒PQ\Rightarrow P</span><span><span><span></span><span>Q</span><span></span><span>⇒</span><span></span></span><span><span></span><span>P</span></span></span></span></td><td><span><span>P⇒QP\Rightarrow Q</span><span><span><span></span><span>P</span><span></span><span>⇒</span><span></span></span><span><span></span><span>Q</span></span></span></span></td></tr><tr><td><span><span>QQ</span><span><span><span></span><span>Q</span></span></span></span> 的充要条件是 <span><span>PP</span><span><span><span></span><span>P</span></span></span></span></td><td><span><span>P⇒QP\Rightarrow Q</span><span><span><span></span><span>P</span><span></span><span>⇒</span><span></span></span><span><span></span><span>Q</span></span></span></span></td><td><span><span>Q⇒PQ\Rightarrow P</span><span><span><span></span><span>Q</span><span></span><span>⇒</span><span></span></span><span><span></span><span>P</span></span></span></span></td></tr></tbody></table><p>所以考试中真正要注意的不是两句话是否等价，而是题目要求你证明哪句话，以及当前证明方向应当叫什么。</p></section><section><h4>快速判断法<a href="#快速判断法"><span>#</span></a></h4><p>遇到“<span><span>PP</span><span><span><span></span><span>P</span></span></span></span> 的充分条件、必要条件或充要条件是 <span><span>QQ</span><span><span><span></span><span>Q</span></span></span></span>”时：</p><ol>
<li>先把 <span><span>QQ</span><span><span><span></span><span>Q</span></span></span></span> 看作条件，把 <span><span>PP</span><span><span><span></span><span>P</span></span></span></span> 看作结论。</li>
<li>充分性写成“条件推出结论”：<span><span>Q⇒PQ\Rightarrow P</span><span><span><span></span><span>Q</span><span></span><span>⇒</span><span></span></span><span><span></span><span>P</span></span></span></span>。</li>
<li>必要性写成“结论推出条件”：<span><span>P⇒QP\Rightarrow Q</span><span><span><span></span><span>P</span><span></span><span>⇒</span><span></span></span><span><span></span><span>Q</span></span></span></span>。</li>
</ol><p>最终只认箭头，不靠语感。</p></section></section></section>
<section><h2>概率与统计<a href="#概率与统计"><span>#</span></a></h2><section><h3>概率论与统计学<a href="#概率论与统计学"><span>#</span></a></h3><section><h4>核心区别<a href="#核心区别"><span>#</span></a></h4><ul>
<li><strong>概率论</strong>：已知总体的概率模型，研究该模型可能产生什么样的数据，以及各种结果出现的概率。</li>
<li><strong>统计学</strong>：已经观测到来自总体的样本数据，据此反推未知的概率模型、参数或总体特征。</li>
</ul><p>两者的推理方向正好相反：</p><blockquote><p><strong>概率论：从模型到数据；统计学：从数据到模型。</strong></p></blockquote></section><section><h4>统计学的两类方法<a href="#统计学的两类方法"><span>#</span></a></h4><section><h5>参数统计<a href="#参数统计"><span>#</span></a></h5><p>预先假设总体属于某个确定的分布族，只把其中有限个参数视为未知量，再利用样本估计这些参数。</p><blockquote><p><strong>例：</strong> 假设总体服从正态分布 <span><span>N(μ,σ2)N(\mu,\sigma^2)</span><span><span><span></span><span>N</span><span>(</span><span>μ</span><span>,</span><span></span><span><span>σ</span><span><span><span><span><span><span></span><span><span>2</span></span></span></span></span></span></span></span><span>)</span></span></span></span>，但参数 <span><span>μ\mu</span><span><span><span></span><span>μ</span></span></span></span> 和 <span><span>σ\sigma</span><span><span><span></span><span>σ</span></span></span></span> 未知，于是利用样本对它们进行估计。这里的<strong>分布族已经确定，未知的只是参数</strong>。</p></blockquote></section><section><h5>非参数统计<a href="#非参数统计"><span>#</span></a></h5><p>不预先把总体限制在某个由有限参数决定的分布族中，而是更多地依靠样本数据估计分布形状或其他总体特征。</p><blockquote><p><strong>例：</strong> 核密度估计不要求总体必须服从正态分布、泊松分布等特定分布，而是根据样本直接估计概率密度曲线。</p></blockquote><p>需要注意：<strong>“非参数”不等于“完全没有假设”</strong>。它仍可能依赖平滑性等条件，也需要选择核函数、带宽等方法参数；只是不会预先把总体限定为某个有限参数分布族。</p></section></section></section><section><h3>概率论学习指南<a href="#概率论学习指南"><span>#</span></a></h3><p>这两个都值得收藏，而且很好搭配：<strong>Statlect 更像一本讲解型教材，StatProofBook 更像一本随时查阅的统计证明手册。</strong> 前者适合学懂一个主题，后者适合追问“这个结论到底怎么推出来”。</p><section><h4>1. Statlect：概率、统计与矩阵代数的在线教材<a href="#1-statlect概率统计与矩阵代数的在线教材"><span>#</span></a></h4><p>网址：<a href="https://www.statlect.com/" target="_blank">https://www.statlect.com/</a></p><p>它是免费的英文教材，内容包括概率论基础、常见概率分布、数理统计、渐近理论、矩阵代数，以及部分机器学习内容。</p><p>你可以在里面学：</p><ul>
<li><strong>概率基础</strong>：随机变量、期望、方差、条件概率、贝叶斯公式。</li>
<li><strong>统计推断</strong>：最大似然估计、置信区间、假设检验、线性回归。</li>
<li><strong>进阶理论</strong>：大数定律、中心极限定理、不同类型的随机收敛。</li>
<li><strong>数学工具</strong>：矩阵、特征值等统计学常用的线性代数知识。</li>
<li><strong>部分机器学习方法</strong>：配有详细注释的 Python 示例。</li>
</ul><p>它的优势是<strong>围绕一个主题把定义、解释、公式、推导和例子组织在一起</strong>，还有习题和图示，适合相对连续地读。</p><p>比如，你在论文里看到“最大似然估计”，可以去读它的<a href="https://www.statlect.com/fundamentals-of-statistics/maximum-likelihood" target="_blank">最大似然章节</a>，把这个方法的目标、数学表达和相关性质串起来。</p><p>不过，它整体仍偏数学，遇到进阶章节需要一定微积分、线性代数和概率基础。可以把它当作大学课程的补充教材，或读论文时补基础的工具。</p></section><section><h4>2. StatProofBook：统计学结论的证明库<a href="#2-statproofbook统计学结论的证明库"><span>#</span></a></h4><p>网址：<a href="https://statproofbook.github.io/" target="_blank">https://statproofbook.github.io/</a></p><p>全名是 <strong>The Book of Statistical Proofs</strong>。它是一个开放、协作编辑的统计定理与证明档案，包含定义和证明，可以按主题或编号查找，也提供整本 PDF。</p><p>它的目录主要围绕：</p><ul>
<li>一般定理，包括概率、期望、方差等。</li>
<li>概率分布及其性质。</li>
<li>统计模型。</li>
<li>模型选择准则。</li>
</ul><p>它的特点是<strong>把问题拆得非常细</strong>。例如，在“期望”下面，会分别列出线性性、单调性、全期望公式等证明；在概率密度函数下面，会分别列出变量变换、独立随机变量求和等结果。</p><p>这很适合你遇到这样的情况：</p><blockquote><p>课本或论文直接写出一个公式，但中间几步省略了，我想知道依据是什么。</p></blockquote><p>这时可以从<a href="https://statproofbook.github.io/I/ToC" target="_blank">完整目录</a>找对应命题，沿着定义和证明逐步核对。它尤其适合做理论作业、检查推导，或阅读论文时补上某一步。</p><p>局限是，<strong>它更侧重证明本身，学习动机、直观解释和应用故事相对少</strong>。初学时从头逐条读，容易陷进细节；带着明确问题查，通常更高效。</p></section><section><h4>怎么搭配用？<a href="#怎么搭配用"><span>#</span></a></h4><p>假设你正在学正态分布：</p><ol>
<li>先用 <strong>Statlect</strong> 了解定义、参数、图像及主要性质。</li>
<li>对某个性质有疑问，例如“为什么方差是这个表达式”，去 <strong>StatProofBook</strong> 找对应证明。</li>
<li>自己把推导写一遍，再用例题或代码验证理解。</li>
</ol><p><strong>如果现在只挑一个开始读，我建议先用 Statlect；把 StatProofBook 放在旁边，作为推导卡住时的参考。</strong> 两个网站的符号和参数约定可能不同，交叉查阅时要先核对定义，尤其注意同一个分布里的参数究竟表示方差、标准差、尺度还是率。</p></section></section></section>]]></content>
    </entry>
    <entry>
      <id>https://www.celyn-blog.xyz/posts/github-actions-cicd/</id>
      <title type="text">用 GitHub Actions 实现 CI/CD 自动部署</title>
      <published>2026-09-19T00:00:00.000Z</published>
      <updated>2026-09-19T00:00:00.000Z</updated>
      <author><name>Celyn</name></author>
      <link rel="alternate" href="https://www.celyn-blog.xyz/posts/github-actions-cicd/"/>
      <summary type="text">把构建搬离低内存服务器，用 GitHub Actions 串起验证、构建、上传、版本切换与健康检查，并记录失败回滚和数据库迁移的处理边界。</summary>
      <content type="html"><![CDATA[<section><h1>用 GitHub Actions 实现 CI/CD 自动部署<a href="#用-github-actions-实现-cicd-自动部署"><span>#</span></a></h1><blockquote><p>本文记录如何用 GitHub Actions 串起从代码推送到上线的流程：<strong>自动验证、构建、上传产物并更新服务</strong>，让生产服务器只负责运行，不再承担构建压力。</p></blockquote><section><h3>我为什么开始折腾 CI/CD<a href="#我为什么开始折腾-cicd"><span>#</span></a></h3><p>最开始，我的部署方式完全是手动的：本地写完代码，先推送到 GitHub，再通过 SSH 登录服务器，手动 <code>git pull</code> 拉取代码，执行 <code>npm install</code> 安装依赖，再用 <code>npm run build</code> 构建，最后更新线上服务。</p><p>但实际操作中，<strong>构建经常卡住，连带着 SSH 连接也会中断</strong>。我已经确认，原因就是<strong>服务器内存不足</strong>：服务器内存只有约 1.6 GiB，一边运行线上服务，一边执行构建，内存不够用。因此，需要把构建工作搬离服务器，让它只负责运行服务。</p><p>于是，我先想到一个办法：<strong>在本地构建好，再用 SCP 把产物上传到服务器，让服务器只负责运行。</strong> 这一步解决的是“在哪里构建”的问题，但构建、上传、更新版本这些操作，仍然需要我手动完成。</p><p>接着，我想到了以前一直听说、却从来没用过的 GitHub Actions：既然这些步骤每次都差不多，能不能让 GitHub 在收到代码后，自动替我执行？</p><p>我想达到的效果是：</p><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>本地修改代码，push 到 GitHub</span></div></div><div><div><div>2</div></div><div><span><span>  </span></span><span>↓</span></div></div><div><div><div>3</div></div><div><span>GitHub Actions 自动验证和构建</span></div></div><div><div><div>4</div></div><div><span><span>  </span></span><span>↓</span></div></div><div><div><div>5</div></div><div><span>把构建产物上传到服务器</span></div></div><div><div><div>6</div></div><div><span><span>  </span></span><span>↓</span></div></div><div><div><div>7</div></div><div><span>服务器更新版本并检查服务是否正常</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p>所以，我学习 CI/CD 不是为了先记住一套概念，而是为了解决一个具体问题：<strong>从“SSH 登录服务器，手动拉代码、装依赖、构建”，变成“我只负责推送代码，流水线负责验证、构建和部署”。</strong></p><p>这条思路分成两步就很清楚：<strong>先把构建搬离服务器，减轻服务器压力；再把重复的发布步骤交给 GitHub Actions，减少手动操作。</strong></p><hr /><p></p><figure><img alt="CI/CD 自动部署完整流程" loading="lazy" width="1400" height="760" src="/_astro/cicd-overview-bw.DqP9ndc4_Z1j1BTv.svg" /><figcaption>CI/CD 自动部署完整流程</figcaption></figure><p></p><p>这张图展示从提交代码到上线的完整流程：push 后，GitHub Actions 自动验证、构建并打包，通过 SCP 上传产物，再通过 SSH 执行服务器上的版本切换和重启，最后做健康检查，失败时尝试回滚。<strong>核心是把构建交给 Actions，服务器只负责运行产物。</strong></p></section><section><h2>一、我的部署分工<a href="#一我的部署分工"><span>#</span></a></h2><p>最终采用的分工是：</p><div><div><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>本地电脑</span></div></div><div><div><div>2</div></div><div><span><span>  </span></span><span>└─ 写代码，git push</span></div></div><div><div><div>3</div></div><div>
</div></div><div><div><div>4</div></div><div><span>GitHub Actions runner</span></div></div><div><div><div>5</div></div><div><span><span>  </span></span><span>├─ 拉取代码</span></div></div><div><div><div>6</div></div><div><span><span>  </span></span><span>├─ 安装依赖</span></div></div><div><div><div>7</div></div><div><span><span>  </span></span><span>├─ 运行测试</span></div></div><div><div><div>8</div></div><div><span><span>  </span></span><span>├─ 构建 Next.js standalone</span></div></div><div><div><div>9</div></div><div><span><span>  </span></span><span>└─ 打包部署产物</span></div></div><div><div><div>10</div></div><div>
</div></div><div><div><div>11</div></div><div><span>生产服务器</span></div></div><div><div><div>12</div></div><div><span><span>  </span></span><span>├─ 接收压缩包</span></div></div><div><div><div>13</div></div><div><span><span>  </span></span><span>├─ 解压到 releases/&lt;commit-sha&gt;</span></div></div><div><div><div>14</div></div><div><span><span>  </span></span><span>├─ 切换 current 软链接</span></div></div><div><div><div>15</div></div><div><span><span>  </span></span><span>├─ 重启 systemd 服务</span></div></div><div><div><div>16</div></div><div><span><span>  </span></span><span>└─ 做健康检查</span></div></div></code></pre><div><div></div><div></div></div></figure><div></div></div><span>展开</span><span>收起</span></div></div><p>为了尽早发现代码问题、减少手动发布操作，我们把验证和部署组织成 CI/CD 流程：</p><ul>
<li><strong>CI（Continuous Integration，持续集成）</strong>：频繁地将代码变更集成到共享主干，并通过自动构建、测试等检查，尽早发现问题。本文中的安装依赖、测试和构建，就是其中的自动验证步骤。</li>
<li><strong>CD（Continuous Deployment，持续部署）</strong>：本文采用这一含义，即在 CI 检查通过后，自动将构建产物部署到生产环境并更新服务，不需要人工批准上线。</li>
</ul><p>这个项目推送到 <code>master</code> 后会自动验证、构建并部署，采用的就是<strong>持续集成 + 持续部署</strong>。GitHub Actions 是执行这套流程的工具，不是 CI/CD 本身。</p><hr /></section><section><h2>二、从 push 到上线的主线<a href="#二从-push-到上线的主线"><span>#</span></a></h2><p>实际流程压缩起来就是这一条：</p><p></p><figure><img alt="从代码推送到服务上线的发布主线" loading="lazy" width="1440" height="620" src="/_astro/cicd-push-to-live-bw._RmDvxlc_vMooQ.svg" /><figcaption>从代码推送到服务上线的发布主线</figcaption></figure><p></p><p>这张图按执行位置串起发布主线：本地 push 触发工作流，runner 负责验证、构建和打包，再通过 SCP 上传产物、SSH 执行服务器上的版本更新，健康检查通过后完成发布。</p><p></p><figure><img alt="GitHub Actions 工作流、任务与步骤的结构" loading="lazy" width="1120" height="860" src="/_astro/github-actions-structure-bw.DvMXiuYo_1dmUae.svg" /><figcaption>GitHub Actions 工作流、任务与步骤的结构</figcaption></figure><p></p><p>GitHub Actions 是 GitHub 提供的自动化工具：在 <code>.github/workflows/</code> 中编写 <strong>workflow（工作流）</strong>，由 <code>on</code> 指定 push 等触发事件。一个 workflow 包含一个或多个 <strong>job（任务）</strong>，每个 job 由 **runner（执行机器）**执行，内部的 **step（步骤）**按顺序运行；step 用 <code>run</code> 执行命令，或用 <code>uses</code> 调用封装好的 <strong>action（操作）</strong>。不同 job 默认可并行，用 <code>needs</code> 指定先后依赖；runner 不等于生产服务器，远程部署需通过 SSH 等方式连接。</p><p>下面用项目中的几个配置项，看看这些概念在 YAML 中怎么写。<strong>以下是局部示例，不是完整的部署配置。</strong></p><p><strong>第一个例子：什么时候触发 workflow？</strong></p><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>on</span><span>:</span></div></div><div><div><div>2</div></div><div><span>  </span><span>push</span><span>:</span></div></div><div><div><div>3</div></div><div><span>    </span><span>branches</span><span>: [</span><span>master</span><span>]</span></div></div><div><div><div>4</div></div><div><span>  </span><span>workflow_dispatch</span><span>:</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p><code>on</code> 指定整个 workflow 的触发条件：<code>push</code> 配合 <code>branches: [master]</code>，表示向 <code>master</code> 推送代码时运行；<code>workflow_dispatch</code> 则允许在 GitHub 页面手动触发。<strong>这里决定的是“什么时候开始”，还没有描述具体做什么。</strong></p><p><strong>第二个例子：job 在哪里执行，step 做什么？</strong></p><p>下面补出必要层级作示意，其他步骤省略：</p><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>jobs</span><span>:</span></div></div><div><div><div>2</div></div><div><span>  </span><span>build</span><span>:</span></div></div><div><div><div>3</div></div><div><span>    </span><span>runs-on</span><span>: </span><span>ubuntu-latest</span></div></div><div><div><div>4</div></div><div><span>    </span><span>steps</span><span>:</span></div></div><div><div><div>5</div></div><div><span><span>      </span></span><span>- </span><span>uses</span><span>: </span><span>actions/setup-node@v4</span></div></div><div><div><div>6</div></div><div><span>        </span><span>with</span><span>:</span></div></div><div><div><div>7</div></div><div><span>          </span><span>node-version</span><span>: </span><span>22</span></div></div><div><div><div>8</div></div><div><span>          </span><span>cache</span><span>: </span><span>npm</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p>这里的 <code>build</code> 是 job 的标识，<code>runs-on</code> 为它选择 GitHub 提供的 Ubuntu 执行机器；<code>steps</code> 中的这个步骤通过 <code>uses</code> 调用 <code>actions/setup-node</code>，准备 Node.js 环境。<code>with</code> 是传给这个 action 的参数：使用 Node.js 22，并启用 npm 缓存来加快依赖准备，<strong>但缓存不能代替后续的 <code>npm ci</code> 安装步骤</strong>。注意，<code>@v4</code> 是 action 的版本，<code>22</code> 才是 Node.js 的版本。</p><p>Next.js 使用 standalone 输出：</p><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span><span>output</span><span>: </span></span><span>'standalone'</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p>standalone 的意义是：生产服务器不需要完整源码和完整 <code>node_modules</code>，主要运行构建后的：</p><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>.next/standalone/server.js</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p>这正好适合低内存服务器。</p><hr /></section><section><h2>三、服务器上怎么切换版本<a href="#三服务器上怎么切换版本"><span>#</span></a></h2><p>服务器不是直接覆盖旧目录，而是保留多个 release：</p><p></p><figure><img alt="服务器 release 目录与 current 软链接" loading="lazy" width="1400" height="760" src="/_astro/cicd-release-directory-bw.Cr2VolEf_Z1j1BTv.svg" /><figcaption>服务器 release 目录与 current 软链接</figcaption></figure><p></p><p>图中 <code>incoming/</code> 暂存上传的包，<code>releases/</code> 分别保存新旧版本，<code>current</code> 是指向所选版本的软链接，<code>.env</code> 则单独留在服务器。<strong>新版本先准备完整，再切换入口并重启服务</strong>，避免直接覆盖旧文件，也为失败回滚保留旧版本。</p><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>~/English_Context/</span></div></div><div><div><div>2</div></div><div><span>├── incoming/</span></div></div><div><div><div>3</div></div><div><span>├── releases/</span></div></div><div><div><div>4</div></div><div><span>│   ├── &lt;sha-1&gt;/</span></div></div><div><div><div>5</div></div><div><span>│   └── &lt;sha-2&gt;/</span></div></div><div><div><div>6</div></div><div><span>├── current -&gt; releases/&lt;当前版本&gt;</span></div></div><div><div><div>7</div></div><div><span>└── .env</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p>每次新版本上传后，先解压到：</p><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>releases/&lt;commit-sha&gt;/</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p>然后把 <code>current</code> 软链接切过去：</p><div><figure><figcaption><span></span><span>Terminal window</span></figcaption><pre><code><div><div><div>1</div></div><div><span>ln</span><span> </span><span>-sfn</span><span> </span><span>"</span><span>$release</span><span>"</span><span> </span><span>"</span><span>$next_link</span><span>"</span></div></div><div><div><div>2</div></div><div><span>mv</span><span> </span><span>-Tf</span><span> </span><span>"</span><span>$next_link</span><span>"</span><span> </span><span>"</span><span>$current</span><span>"</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p>这样做的好处是，服务目录要么指向完整旧版本，要么指向完整新版本，不会停在复制了一半的状态。</p><p>服务由用户级 systemd 运行：</p><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>WorkingDirectory</span><span><span>=</span><span>%h/English_Context/current</span></span></div></div><div><div><div>2</div></div><div><span>Environment</span><span>=</span><span>NODE_ENV</span><span><span>=</span><span>production</span></span></div></div><div><div><div>3</div></div><div><span>Environment</span><span>=</span><span>PORT</span><span><span>=</span><span>3456</span></span></div></div><div><div><div>4</div></div><div><span>Environment</span><span>=</span><span>HOSTNAME</span><span><span>=</span><span>0.0.0.0</span></span></div></div><div><div><div>5</div></div><div><span>ExecStart</span><span><span>=</span><span>/usr/bin/node server.js</span></span></div></div><div><div><div>6</div></div><div><span>Restart</span><span><span>=</span><span>on-failure</span></span></div></div><div><div><div>7</div></div><div><span>RestartSec</span><span><span>=</span><span>5</span></span></div></div></code></pre><div><div></div><div></div></div></figure></div><p>部署脚本切换 <code>current</code> 后，执行：</p><div><figure><figcaption><span></span><span>Terminal window</span></figcaption><pre><code><div><div><div>1</div></div><div><span>systemctl</span><span> </span><span>--user</span><span> </span><span>daemon-reload</span></div></div><div><div><div>2</div></div><div><span>systemctl</span><span> </span><span>--user</span><span> </span><span>restart</span><span> </span><span>english-context.service</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p>最后用 <code>curl</code> 检查本机端口：</p><div><figure><figcaption><span></span><span>Terminal window</span></figcaption><pre><code><div><div><div>1</div></div><div><span>curl</span><span> </span><span>-fsS</span><span> </span><span>--max-time</span><span> </span><span>2</span><span> </span><span>"http://127.0.0.1:3456/"</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p>这个检查不代表业务完全正常，但至少能确认 Next.js 服务已经启动，并且 HTTP 能返回成功状态。</p><hr /></section><section><h2>四、失败时怎么退回去<a href="#四失败时怎么退回去"><span>#</span></a></h2><p>自动部署一定要考虑失败。这里最需要处理的是：文件已经上传了，<code>current</code> 也切到新版本了，但服务启动失败或健康检查失败。</p><p>脚本会在切换前记录旧版本：</p><p></p><figure><img alt="健康检查失败后的应用版本回滚" loading="lazy" width="1400" height="760" src="/_astro/cicd-rollback-bw.DigrkAi4_Z1j1BTv.svg" /><figcaption>健康检查失败后的应用版本回滚</figcaption></figure><p></p><p>沿着上排看：先记录旧版本，再切到新版本并重启，随后检查服务是否正常响应；如果检查失败，就沿虚线进入下排，把 <code>current</code> 切回旧目录并重启。<strong>回滚能成立，是因为旧版本仍被完整保留</strong>，而不是失败后再重新构建一次。</p><div><figure><figcaption><span></span><span>Terminal window</span></figcaption><pre><code><div><div><div>1</div></div><div><span>previous</span><span>=</span><span>"$(</span><span>readlink</span><span> </span><span>-f</span><span> "</span><span>$current</span><span>")"</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p>如果新版本失败，就把 <code>current</code> 切回旧版本，并重启服务：</p><div><figure><figcaption><span></span><span>Terminal window</span></figcaption><pre><code><div><div><div>1</div></div><div><span>ln</span><span> </span><span>-sfn</span><span> </span><span>"</span><span>$previous</span><span>"</span><span> </span><span>"</span><span>$next_link</span><span>"</span></div></div><div><div><div>2</div></div><div><span>mv</span><span> </span><span>-Tf</span><span> </span><span>"</span><span>$next_link</span><span>"</span><span> </span><span>"</span><span>$current</span><span>"</span></div></div><div><div><div>3</div></div><div><span>systemctl</span><span> </span><span>--user</span><span> </span><span>restart</span><span> </span><span>"</span><span>$unit_name</span><span>"</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p>这只能回滚应用目录，不能回滚所有东西。数据库、用户数据、缓存、Service Worker 都可能有自己的状态。</p><hr /></section><section><h2>五、数据库迁移单独处理<a href="#五数据库迁移单独处理"><span>#</span></a></h2><p>ContextVocab 使用 Prisma 和 PostgreSQL。应用发布和数据库结构变更不是一回事：</p><p></p><figure><img alt="应用回滚与数据库迁移的边界" loading="lazy" width="1400" height="760" src="/_astro/cicd-db-migration-bw.D5s2AUVi_Z1j1BTv.svg" /><figcaption>应用回滚与数据库迁移的边界</figcaption></figure><p></p><p>左边的应用版本可以通过 <code>current</code> 切回旧目录，右边的数据库变更却不会随之撤销：例如字段已经被删除，退回旧代码也无法把它恢复。因此，<strong>数据库迁移需要单独处理，优先做兼容新旧代码的渐进变更</strong>，不能把应用回滚当成数据库回滚。</p><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>应用代码发布</span></div></div><div><div><div>2</div></div><div><span>数据库 schema/migration 发布</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p>当前 workflow 不自动执行数据库迁移。原因是：应用 release 可以切回去，但数据库迁移未必可逆。</p><p>生产数据库更适合渐进式处理：</p><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>1. 先添加兼容旧代码的新字段</span></div></div><div><div><div>2</div></div><div><span>2. 部署兼容新旧结构的代码</span></div></div><div><div><div>3</div></div><div><span>3. 执行数据回填或索引调整</span></div></div><div><div><div>4</div></div><div><span>4. 验证生产读写</span></div></div><div><div><div>5</div></div><div><span>5. 后续版本再删除旧字段</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p>这样新旧代码在过渡期都能工作，出问题时也更容易退回。</p><hr /></section><section><h2>六、这套方案的核心<a href="#六这套方案的核心"><span>#</span></a></h2><p>这篇记录里真正重要的不是 GitHub Actions 的 YAML 细节，而是这条分工：</p><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>本地负责写代码和推送</span></div></div><div><div><div>2</div></div><div><span>GitHub Actions 负责验证和构建</span></div></div><div><div><div>3</div></div><div><span>服务器负责运行产物和切换版本</span></div></div><div><div><div>4</div></div><div><span>健康检查决定发布是否成功</span></div></div><div><div><div>5</div></div><div><span>失败时回滚应用 release</span></div></div><div><div><div>6</div></div><div><span>数据库迁移单独设计</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p>对低内存服务器来说，这比“SSH 上去现场构建”稳定很多。</p></section></section>]]></content>
    </entry>
    <entry>
      <id>https://www.celyn-blog.xyz/posts/core-aesthetics/</id>
      <title type="text">核类美学：梦核、怪核与池核</title>
      <published>2026-09-13T00:00:00.000Z</published>
      <updated>2026-09-13T00:00:00.000Z</updated>
      <author><name>Celyn</name></author>
      <link rel="alternate" href="https://www.celyn-blog.xyz/posts/core-aesthetics/"/>
      <summary type="text">从阈限空间出发，结合图像、游戏与 Blender 积水地下通道实践，理解梦核、怪核与池核的视觉语言、差异和重叠。</summary>
      <content type="html"><![CDATA[<p></p><figure><img alt="怪核-超市黑洞" loading="lazy" width="788" height="575" src="/_astro/weirdcore-supermarket._pYVFMj9_1v5F7g.webp" /><figcaption>怪核-超市黑洞</figcaption></figure><p></p>
<p>两侧是再普通不过的商店货架，通道尽头却只剩一片没有细节的黑。<sup><a href="#user-content-fn-10">1</a></sup></p>
<p>怪就怪在这里：货架、地面、灯光都没有问题，唯独本该通向别处的通道失去了下文。梦核、怪核与池核经常这样处理普通场所，留下熟悉的外壳，拿走人物、用途或空间线索。要弄清这种处理为什么有效，得先认识一个反复出现在三者中的概念：<strong>阈限空间</strong>。</p>
<hr />
<section><h2>阈限空间<a href="#阈限空间"><span>#</span></a></h2><p>“阈限”来自拉丁语 <code>limen</code>，意思是门槛或临界。走廊、等候室、停车场都属于这类空间：人们来这里是为了经过，很少把它们当作目的地。<sup><a href="#user-content-fn-1">2</a></sup></p><p>凌晨的商场、放假后的学校走廊、空停车场，都是常见题材。建筑和设施还在，人和日常活动却消失了。<sup><a href="#user-content-fn-1">2</a></sup></p><p></p><figure><img alt="阈限空间-昏暗走廊" loading="lazy" width="586" height="440" src="/_astro/liminal-corridor.B2QErxxD_17YaCK.webp" /><figcaption>阈限空间-昏暗走廊</figcaption></figure><p></p><p>这条走廊属于什么建筑，又通向哪里，画面都没有交代。柱子、顶灯和转角一再重复，视线找不到可以停下来的终点。</p><p>我们平时会靠建筑和物件判断一个地方是做什么的。阈限空间没有抹掉这些提示，只是抽走了本该在这里发生的活动，于是熟悉的地方突然变得不太对劲。</p></section>
<section><h2>“核类美学”是什么<a href="#核类美学是什么"><span>#</span></a></h2><p>中文互联网所说的“核”，来自英文后缀 <code>-core</code>，大致是“围绕某种元素形成的风格”。它本身不等于恐怖，也不一定诡异。梦核偏向梦境感，怪核偏向异常感，池核则围绕水池空间展开。<sup><a href="#user-content-fn-2">3</a></sup></p><p>判断一幅图更接近哪种风格，可以先问几件事：</p><ul>
<li>它主要在拍什么：梦境、异常信息，还是水池？</li>
<li>这个地方出了什么问题：空无一人、已经废弃、失去用途，还是根本不符合建筑逻辑？</li>
<li>画面又是怎样处理的：柔光和旧画质，故障效果，还是蓝白色的水光？</li>
</ul><p>这些分类并不互斥。一座无人泳池，题材上是池核，空间状态带有阈限感；如果再加上故障和低清效果，画面也会染上怪核的味道。</p><section><h3>共性与差异<a href="#共性与差异"><span>#</span></a></h3><p>无人场所、旧画质和残缺的空间信息，在这些作品里反复出现。不过它们关注的东西不同。阈限空间关心场所为何像是卡在“途中”；梦核借用梦的逻辑和童年意象；怪核让信息彼此冲突；池核最直接，主角通常就是泳池和水域。</p><hr /></section></section>
<section><h2>梦核<a href="#梦核"><span>#</span></a></h2><section><h3>视觉语法<a href="#视觉语法"><span>#</span></a></h3><p>按美学 Wiki 的说法，梦核是一种用图像、视频和音乐表现梦境及梦中感受的超现实主义美学。<sup><a href="#user-content-fn-3">4</a></sup></p><p></p><figure><img alt="梦核-雾中泳池" loading="lazy" width="1080" height="1329" src="/_astro/dreamcore-foggy-pool.CLJjECZw_ZA2Hwu.webp" /><figcaption>梦核-雾中泳池</figcaption></figure><p></p><p>柔和的蓝色、浓雾、对称摆放的盆栽，还有一路向下的瓷砖台阶。楼梯通向哪里？水面从哪儿开始？图里没有答案，也找不到能判断尺度和方向的参照物。<sup><a href="#user-content-fn-11">5</a></sup></p><p>所以它既像池核，也像梦核。水池、瓷砖和蓝色调来自池核，柔焦、无人场景与含混的空间关系又把它推向了梦境。</p><p>梦核喜欢挪用学校走廊、游乐场、商场和卧室这类带有童年记忆的地方，再塞进不合常理的生物、物件或空间关系。你认得每样东西，却说不清它们为什么会同时出现在这里。</p><p>粗略地说，它常由童年场景、错位的物件或生物，再加一层旧影像质感组成。</p><p>胶片光晕、划痕、压缩噪点和冷色荧光灯，会让画面像一段来历不明的旧录像。童年符号让人产生“我好像见过”的感觉，空间错位又马上让这份熟悉感站不住脚。</p><p>它常以阈限空间、不切实际的地形或梦幻场所为底，再叠上文字、眼睛、翅膀、奇怪生物、RPG 文本框、电脑弹窗或 CRT 电视机。<sup><a href="#user-content-fn-3">4</a></sup></p><p>这些符号未必对应某个具体的梦。梦核借用的是梦的组织方式：东西可以一起出现，不必遵守现实中的尺度、位置和因果。</p></section><section><h3>与阈限空间的区别<a href="#与阈限空间的区别"><span>#</span></a></h3><ul>
<li>阈限空间强调过渡场所，以及空间功能与当前状态之间的错位。</li>
<li>梦核强调梦境逻辑、童年意象和超现实重组，不必发生在严格意义上的过渡空间。<sup><a href="#user-content-fn-3">4</a></sup></li>
</ul><p>简单说，阈限空间主要改变场所的使用状态；梦核动得更多，物件组合、比例乃至因果关系都可能被改写。</p></section><section><h3>《Dreamcore》游戏案例<a href="#dreamcore游戏案例"><span>#</span></a></h3><p>《Dreamcore》官网称它是一款以阈限空间为基础的心理恐怖游戏。游戏没有固定方向，也不给出明确目标，玩家只能自行探索。<sup><a href="#user-content-fn-4">6</a></sup></p><p>Playrooms 里有软垫房、塑料城堡和球池通道，看起来像儿童游乐区，却没有孩子、声音和可辨认的用途。Eternal Suburbia 换成了重复的住宅与循环路径，布局不讲道理，方向感也随之失效。<sup><a href="#user-content-fn-4">6</a></sup></p><p>它不太依赖突然惊吓。真正让人紧张的是：场所结构还在，原有用途、人物和行动方向却都没了。</p><p><a href="https://www.youtube.com/watch?v=VYl5i-kBHy8" target="_blank">在 YouTube 观看 Liminal Hotel 展示视频</a></p><p>“Liminal Hotel” 的展示视频里，走廊不断循环，楼层也会悄悄转换。固定地标不再可靠，玩家很难确认自己在哪里，更别说出口。</p><hr /></section></section>
<section><h2>怪核<a href="#怪核"><span>#</span></a></h2><p>怪核也爱用阈限空间、早期互联网画面和超现实拼贴。它最在意的不是“像不像梦”，而是让图像里的信息怎么都拼不成一个完整解释。</p><p>美学 Wiki 把它归为超现实主义美学。业余摄影、低质量图像和刻意粗糙的后期很常见，它们会带来混乱、陌生、怀旧甚至恐惧感。<sup><a href="#user-content-fn-5">7</a></sup></p><section><h3>开头商店图的构图<a href="#开头商店图的构图"><span>#</span></a></h3><ol>
<li>透视线汇聚：货架边缘、层板和地面边界都朝中央消失点收束。</li>
<li>通道逐渐收窄：两侧高大货架形成隧道式包围，越接近中心，可活动空间看起来越小。</li>
<li>终点被黑暗替代：远处信息没有逐渐缩小，而是直接消失。</li>
<li>明暗反差明显：两侧商品明亮且具体，中央区域则没有细节和尺度。</li>
</ol><p>货架、地面和通道全都指向中央，照理说那里应该给出答案，结果却什么也看不见。正常透视走到终点，突然断了。</p><p>怪核不需要真的放进一只怪物。低画质、缺失的背景、割裂的文字和不正常的空间，已经足够让画面失去完整解释。<sup><a href="#user-content-fn-5">7</a></sup></p></section><section><h3>林中的发光人影<a href="#林中的发光人影"><span>#</span></a></h3><p></p><figure><img alt="怪核-林中发光人影" loading="lazy" width="1672" height="941" src="/_astro/weirdcore-glowing-figures.yYU6mKLK_ZjvSQx.webp" /><figcaption>怪核-林中发光人影</figcaption></figure><p></p><p>树木、落叶和林间空地都很真实，中央两个人形却被白光抹去了面孔、衣着和身份。熟悉的森林因此失去尺度与解释：他们究竟是被照亮的人，还是只有轮廓的不明物体？</p><hr /></section></section>
<section><h2>池核<a href="#池核"><span>#</span></a></h2><p>池核围绕水池、瓷砖和波光展开。有些画面干净、安静，像是可以休息的地方；另一些则空无一人，结构不断重复，建筑关系也根本不可能成立。<sup><a href="#user-content-fn-6">8</a></sup></p><p>蓝色、白色和蓝绿色最常见，整齐的瓷砖、清澈水面与公共泳池设施也几乎成了固定班底。<sup><a href="#user-content-fn-6">8</a></sup></p><section><h3>没有出口的室内泳池<a href="#没有出口的室内泳池"><span>#</span></a></h3><p></p><figure><img alt="池核-室内泳池" loading="lazy" width="1080" height="1350" src="/_astro/poolcore-indoor-pool.Bv-gFh7N_Z21oXgc.webp" /><figcaption>池核-室内泳池</figcaption></figure><p></p><p>这里没有怪物，也没有明显的破败痕迹，只有蓝色瓷砖、水面、天窗和一座空泳池。可右侧采光口被墙和水面隔开，远处两个门洞也不知通向哪里。它看起来能用，细想又处处不合理。</p></section><section><h3>Jared Pike 的《Dream Pools》<a href="#jared-pike-的dream-pools"><span>#</span></a></h3><p></p><figure><img alt="Jared Pike-Dream Pool 05" loading="lazy" width="1200" height="1500" src="/_astro/jared-pike-dream-pool-05.BqrYF6Gk_Z21BvJw.webp" /><figcaption>Jared Pike-Dream Pool 05</figcaption></figure><p></p><p>Jared Pike 从 2020 年开始制作《Dream Pools》。他称这个系列是在探索“诡异的怀旧”：无尽的泳池把梦境和清醒状态混在了一起。作品使用 Blender 3D 和 LuxCore 制作。<sup><a href="#user-content-fn-7">9</a></sup></p><p>这些泳池干净明亮，看上去随时可以下水，却没有人、出口和明确尺度。水、瓷砖、光照都足够真实，唯独泳池没有尽头，建筑关系也对不上现实。</p></section><section><h3>《POOLS》游戏案例<a href="#pools游戏案例"><span>#</span></a></h3><p></p><figure><img alt="POOLS-官方游戏截图" loading="lazy" width="960" height="540" src="/_astro/pools-game.DHbh7jfO_1LWCb2.webp" /><figcaption>POOLS-官方游戏截图</figcaption></figure><p></p><p>《POOLS》没有怪物追逐，也没有用户界面、对白、背景音乐或传统故事。玩家面对的只有黑暗区域、狭窄通道，以及一间接一间找不到明确出口的水池。<sup><a href="#user-content-fn-8">10</a></sup></p><p>每个房间的声音与回声都不一样，涉水还会拖慢脚步。这里的玩法就是听声、看路，并在相似的建筑里勉强维持方向感。</p><p>官网列出的灵感来源包括阈限空间、Backrooms 和 Poolrooms，也明确提到 Jared Pike 对 Poolrooms 概念的影响。<sup><a href="#user-content-fn-8">10</a></sup></p></section><section><h3>Blender 实践：构造一条积水地下通道<a href="#blender-实践构造一条积水地下通道"><span>#</span></a></h3>
  
  你的浏览器不支持视频播放，可<a href="/videos/core-aesthetics/flooded-underpass.mp4">下载观看积水地下通道视频</a>。
<p>为了看看这些视觉特征能否被主动构造，我用 Blender 制作了一条被积水淹没的地下通道，把池核、阈限空间与怪核的元素放进同一个场景。</p><p>水面、瓷砖和破碎的灯光倒影构成了池核的题材基础。地下通道原本用于通行，如今却空无一人，积水也让它失去正常用途，因此带有明显的阈限感。通道不断向前延伸，尽头被青绿色薄雾遮住，观者无法判断出口是否存在。这种空间信息的中断，又让画面靠近怪核。</p><p>制作时，水波不能太剧烈。这个场景需要近乎停滞的水面，只留下缓慢晃动的反光。冷白灯光负责交代建筑结构，薄雾则抹去远处的信息。场景里没有怪物，那个看似真实却解释不通的地方，本身就让人不安。</p><p>完整的场景描述和制作要求见 <a href="#blender-scene-prompt">Blender 积水地下通道场景构建提示词</a>。</p><hr /></section></section>
<section><h2>三种核类美学如何重叠<a href="#三种核类美学如何重叠"><span>#</span></a></h2><p>这次实践也说明，只给一幅图贴上“池核”或“怪核”的标签，往往会漏掉它真正起作用的部分。题材、场所状态和画面处理可以分别来自不同风格。</p><section><h3>池核 × 怪核<a href="#池核--怪核"><span>#</span></a></h3><p></p><figure><img alt="池核与怪核交叉作品" loading="lazy" width="1079" height="1266" src="/_astro/poolcore-weirdcore-crosswork.k8mi8rCt_1JQBy6.webp" /><figcaption>池核与怪核交叉作品</figcaption></figure><p></p><p>画面主体是池子和水域，所以首先是池核。这里空无一人，用途不明，带着阈限感；低清画质、黑暗区域和无法确认的背景，又让它靠近怪核。<sup><a href="#user-content-fn-12">11</a></sup></p><p>更准确地说，池核说的是题材，阈限空间说的是场所状态，怪核说的是画面怎样变得异常。</p></section><section><h3>把风格拆成参数<a href="#把风格拆成参数"><span>#</span></a></h3><p><a href="https://github.com/George-Ye2012/Dreamcore-Style-Transfer" target="_blank">Dreamcore Style Transfer</a> 是一个使用 Python Pillow 的开源项目。它把十种核类风格拆成参数配方，再依次调整色彩、曝光、颗粒噪点、柔光 Bloom 和扭曲。<sup><a href="#user-content-fn-9">12</a></sup></p><p></p><figure><img alt="核类美学-风格迁移界面" loading="lazy" width="1533" height="976" src="/_astro/core-style-transfer.Byg6YgPE_1PLMwY.webp" /><figcaption>核类美学-风格迁移界面</figcaption></figure><p></p><p>截图里的原图是一座水池，界面选择了“怪核”预设。处理后，画面多了强对比、故障线和彩色噪点。</p><p>但滤镜只能改变图像的质感。梦核常见过曝、褪色和胶片颗粒，怪核偏爱色散与故障，池核则常用水下蓝和青色高光。<sup><a href="#user-content-fn-9">12</a></sup></p><p>如果原图里没有无人空间、异常比例、符号错位或信息断裂，套完滤镜往往只是复古、低清或者偏色。决定风格的，仍是场景本身和元素如何组合。</p><hr /></section></section>
<section><h2>核类美学为什么吸引人<a href="#核类美学为什么吸引人"><span>#</span></a></h2><p>这类图像先让人认出场所。建筑、家具和公共设施都很熟悉，大脑很快就会猜出这里“应该”发生什么。接着，画面用无人、黑暗、循环或异常比例打破这个预测，却又故意省略人物、事件和空间边界。我们只好自己补全缺失的信息。</p><p>吸引力就来自这段没有完成的判断：眼前的地方明明认识，却始终解释不通。梦核、怪核和池核改动的是场所状态、空间关系或图像信息，滤镜反而只是表面的一层。</p><hr />
附录：Blender 积水地下通道场景构建提示词（点击展开）<p><strong>场景目标</strong></p><p>制作一条被洪水灌满的废弃地铁人行通道。两侧铺墨绿色小方块马赛克瓷砖，左侧露出金属楼梯扶手。天花板排列长条 LED 荧光灯，冷白光向下照射，暗色积水覆盖整条通道。水面缓慢荡漾，把灯光反射切成破碎的亮斑。</p><p>通道要像是一直延伸到远处，尽头被青绿色薄雾遮住。画面中没有人，也看不出明确出口，整体氛围死寂而幽闭。</p><p><strong>美学拆解</strong></p><ul>
<li>积水、瓷砖和灯光倒影提供池核的题材特征。</li>
<li>失去通行功能的地下通道构成阈限空间。</li>
<li>被雾吞没的尽头切断空间信息，使画面靠近怪核。</li>
</ul><p><strong>材质与建模要求</strong></p><ul>
<li>可以从 <a href="https://polyhaven.com/textures" target="_blank">Poly Haven</a> 下载所需材质，优先使用 4K 版本。</li>
<li>下载的材质存入“材质（按照自己需求创建）”文件夹，也可以使用现有的材质资产和 HDR 资产。</li>
<li>门、窗和其他建筑开口使用布尔方式制作。</li>
<li>建模前先审核场景，确认空间结构没有明显问题。</li>
</ul><p><strong>灯光与渲染要求</strong></p><ul>
<li>使用 Cycles 渲染，整体效果以写实为目标。</li>
<li>先确定主光方向，再调整水面反射、环境亮度和整体氛围。</li>
<li>外景光照使用环境 HDR。</li>
<li>薄雾中需要出现带有丁达尔效应的体积光。</li>
<li>氛围和参考描述应尽量一致，不能只完成建筑模型而忽略光线与空气感。</li>
</ul><p><strong>工具</strong></p><ul>
<li><a href="https://www.blender.org/download/" target="_blank">Blender 下载</a></li>
<li><a href="https://github.com/XliuXjianX/blender-production-skills" target="_blank">Blender Production Skills</a></li>
</ul><p><strong>成品</strong></p><p><a href="#flooded-underpass">观看积水地下通道成品视频</a></p><p>这个场景在美学文章中的分析见 <a href="#blender-%E5%AE%9E%E8%B7%B5%E6%9E%84%E9%80%A0%E4%B8%80%E6%9D%A1%E7%A7%AF%E6%B0%B4%E5%9C%B0%E4%B8%8B%E9%80%9A%E9%81%93">本文的 Blender 实践章节</a>。</p></section>
<section><h2>Footnotes<a href="#footnote-label"><span>#</span></a></h2>
<ol>
<li>
<p>图片原链接：<a href="https://preview.redd.it/supermarket-weirdcore-v0-jfrnkmmm3ds41.jpg?auto=webp&amp;s=06b228197961543e74186a36d1cf932f14bdde8f" target="_blank">Reddit 图片</a>，出处为 <a href="https://www.reddit.com/r/weirdcore/comments/fzu8rf/supermarket_weirdcore/" target="_blank">r/weirdcore 的 “supermarket weirdcore” 帖子</a>。 <a href="#user-content-fnref-10">↩</a></p>
</li>
<li>
<p>美学 Wiki，〈<a href="https://aesthetics.fandom.com/zh/wiki/Liminal_Space" target="_blank">Liminal Space（阈限空间）</a>〉，访问于 2026-09-06。社区协作资料，用于说明美学分类与常见视觉特征，不等同于同行评审研究。 <a href="#user-content-fnref-1">↩</a> <a href="#user-content-fnref-1-2">↩<sup>2</sup></a></p>
</li>
<li>
<p>美学 Wiki，〈<a href="https://aesthetics.fandom.com/zh/wiki/Category:%E6%A0%B8%E7%B1%BB%E7%BE%8E%E5%AD%A6" target="_blank">核类美学</a>〉，访问于 2026-09-06。该分类页用于说明 <code>-core</code>/“核”的分类含义。 <a href="#user-content-fnref-2">↩</a></p>
</li>
<li>
<p>美学 Wiki，〈<a href="https://aesthetics.fandom.com/zh/wiki/Dreamcore" target="_blank">梦核（Dreamcore）</a>〉，访问于 2026-09-06。社区协作资料，用于说明梦核的常见定义、传播媒介与视觉元素。 <a href="#user-content-fnref-3">↩</a> <a href="#user-content-fnref-3-2">↩<sup>2</sup></a> <a href="#user-content-fnref-3-3">↩<sup>3</sup></a></p>
</li>
<li>
<p><a href="https://a-ssl.dtstatic.com/uploads/blog/202303/10/20230310072604_3ce68.jpg" target="_blank">梦核图片原链接</a>。 <a href="#user-content-fnref-11">↩</a></p>
</li>
<li>
<p>Montraluz LLC，〈<a href="https://www.dreamcoregame.com/" target="_blank">Dreamcore Official Website</a>〉，访问于 2026-09-06。用于核对游戏定位、地图设计与探索方式。 <a href="#user-content-fnref-4">↩</a> <a href="#user-content-fnref-4-2">↩<sup>2</sup></a></p>
</li>
<li>
<p>美学 Wiki，〈<a href="https://aesthetics.fandom.com/zh/wiki/Weirdcore" target="_blank">怪核（Weirdcore）</a>〉，访问于 2026-09-06。社区协作资料，用于说明怪核的视觉语言与情绪取向，不作为临床心理学资料。 <a href="#user-content-fnref-5">↩</a> <a href="#user-content-fnref-5-2">↩<sup>2</sup></a></p>
</li>
<li>
<p>美学 Wiki，〈<a href="https://aesthetics.fandom.com/zh/wiki/Poolcore#articleComments" target="_blank">池核（Poolcore）</a>〉，访问于 2026-09-06。社区协作资料，用于说明池核的题材、视觉元素与情绪特征。 <a href="#user-content-fnref-6">↩</a> <a href="#user-content-fnref-6-2">↩<sup>2</sup></a></p>
</li>
<li>
<p>Jared Pike，〈<a href="https://www.jaredpike.art/projects/dream-pools/" target="_blank">Dream Pools</a>〉，访问于 2026-09-06。艺术家本人对系列的说明，用于核对创作时间、主题与工具。 <a href="#user-content-fnref-7">↩</a></p>
</li>
<li>
<p>Tensori，〈<a href="https://poolsthegame.com/zh-CN/" target="_blank">POOLS</a>〉，访问于 2026-09-06。游戏官网，用于核对交互设计、声音机制、情绪目标与创作灵感。 <a href="#user-content-fnref-8">↩</a> <a href="#user-content-fnref-8-2">↩<sup>2</sup></a></p>
</li>
<li>
<p>图片原链接：<a href="https://static.wikia.nocookie.net/backrooms/images/f/f4/Level_188.37.jpg/revision/latest?cb=20220712231034&amp;format=original" target="_blank">Backrooms Wiki 图片</a>，作者原帖为 <a href="https://www.instagram.com/p/CeEMhjFMx4v/" target="_blank">Instagram @onion.wave</a>，出处核对：<a href="https://backrooms.fandom.com/wiki/Level_188.8" target="_blank">Backrooms Wiki — Level 188.8</a>。 <a href="#user-content-fnref-12">↩</a></p>
</li>
<li>
<p>George-Ye2012，〈<a href="https://github.com/George-Ye2012/Dreamcore-Style-Transfer" target="_blank">Dreamcore Style Transfer</a>〉，GitHub，访问于 2026-09-06。开源实践项目，用于说明核类风格的参数化处理方法，不是美学分类或心理学研究的权威来源。 <a href="#user-content-fnref-9">↩</a> <a href="#user-content-fnref-9-2">↩<sup>2</sup></a></p>
</li>
</ol>
</section>]]></content>
    </entry>
    <entry>
      <id>https://www.celyn-blog.xyz/posts/ai-assisted-learning/</id>
      <title type="text">AI辅助学习：把多源知识变成一对一课堂</title>
      <published>2026-08-25T00:00:00.000Z</published>
      <updated>2026-08-25T00:00:00.000Z</updated>
      <author><name>Celyn</name></author>
      <link rel="alternate" href="https://www.celyn-blog.xyz/posts/ai-assisted-learning/"/>
      <summary type="text">从 Probe-Plan-Teach 出发，讨论 AI 如何围绕理解边缘、情绪曲线、学习目标和多种练习形式，构建真正的一对一学习体验。</summary>
      <content type="html"><![CDATA[<blockquote><p>来源：<a href="https://www.youtube.com/watch?v=kzcI5F4tGiU" target="_blank">How I Use AI to Learn Things</a></p></blockquote>
<p>我们常把学习理解成“找到足够好的资料，然后认真读完”。但真正困难的地方，往往不是资料不够，而是资料与自己的理解状态并不匹配。</p>
<p>一本教材可能从已经掌握的内容开始讲，也可能突然跳到暂时无法理解的概念。老师面对一整个班级时，也很难为每个人实时调整进度。于是，学习者不得不自己承担大量“后勤工作”：寻找资料、判断资料是否可信、安排顺序、确认自己究竟卡在哪里。</p>
<p>AI 的价值，或许不只是更快地回答问题，而是把学习过程重新组织成一场真正的一对一教学。</p>
<section><h2>传统教学的问题：同一条路径无法服务所有人<a href="#传统教学的问题同一条路径无法服务所有人"><span>#</span></a></h2><p>常见的教学模式是“多对多”：一个老师用同一套课程，面向多个学习者授课；或者多个资料来源共同影响一个学习者。这样的模式便于规模化，却很难做到精准。</p><p><img alt="" loading="lazy" width="647" height="362" src="/_astro/learning-path.HOnlhSQf_ZvyuJS.webp" /></p><p>对某个学生来说，课程中可能有一部分内容早已掌握，另一部分内容又超出了当前的理解能力。最有效的教学，不应该反复讲已经会的知识，也不应该强行讲解暂时还无法吸收的内容，而是应该持续工作在学习者的“理解边缘”。</p><p>所谓理解边缘，就是学习者已经具备了必要的基础，只差一个恰当的解释、一个例子，或者一个小的推导，就能继续向前理解的地方。</p><p>这也是一对一教学的优势：老师可以根据学生的即时反馈调整深度、顺序和表达方式。AI 如果能够准确掌握学习者的状态，就有机会把这种优势扩展到更多场景中。</p></section>
<section><h2>第一步：Probe，探查我究竟理解了什么<a href="#第一步probe探查我究竟理解了什么"><span>#</span></a></h2><p>个性化教学的前提，是系统必须知道学习者当前的理解状态。只说“我想学人工智能”或“我看过这本书”，远远不够。AI 需要建立一张更细致的学习者理解地图。</p><p>一种可行的方法是使用分级多选测验。系统先提出比较宽泛的问题，了解学习者对主题的整体把握；然后沿着课程依赖的知识线继续追问，<strong>用类似二分搜索的方式逐步逼近理解边缘</strong>。</p><p>例如，学习者声称自己理解了“梯度下降”，系统不能只停留在术语层面。它还需要确认学习者是否理解函数、导数、斜率、损失函数，以及这些概念之间的依赖关系。</p><p>这种探查不是为了考试，而是为了避免教学建立在错误的假设上。只有知道哪些内容已经掌握、哪些内容似懂非懂、哪些内容完全陌生，后续的教学计划才有意义。</p></section>
<section><h2>第二步：Plan，规划适合当前状态的路径<a href="#第二步plan规划适合当前状态的路径"><span>#</span></a></h2><p>完成探查后，AI 才能决定从哪里开始、讲到什么程度，以及哪些内容应该暂时跳过。这个过程可以称为优化教学（Optimized Teaching）。</p><p>优化教学的目标不是把所有知识都讲一遍，而是把有限的注意力用在最有价值的地方：<strong>减少重复，避开当前无法理解的内容，集中突破学习者刚好能够吸收的部分。</strong></p><p>因此，同一个问题不应该只有一份固定答案。同一个概念，也不应该对所有人使用完全相同的解释。对于初学者，AI 需要先补充直觉和例子；对于已经掌握基础的人，则可以直接进入形式化定义、推导和应用。</p><p>更重要的是，学习路径应该随着理解变化而变化。学习者一旦掌握了某个前置概念，原本困难的内容可能就变得容易；如果某个基础环节没有真正理解，系统也应该及时回退，而不是机械地继续推进。</p></section>
<section><h2>第三步：Teach，围绕理解边缘进行教学<a href="#第三步teach围绕理解边缘进行教学"><span>#</span></a></h2><p>在教学阶段，AI 不只是输出一段解释，而是要不断判断“下一步讲什么最合适”。这要求它把讲解、提问、举例和练习结合起来。</p><p>一个好的教学循环可能是：先用简洁的语言解释概念，再让学习者用自己的话复述；接着给出一个略有变化的例子，检查理解能否迁移；最后根据回答决定继续深入、换一种说法，还是回到前置知识。</p><p>这个循环的核心不是让学习者获得更多文字，而是让系统获得更多反馈。每一次回答都在帮助 AI 更新对学习者状态的判断。</p><p>所以，持续反馈并不是教学结束后的附加测试，而是教学本身的一部分。没有反馈，个性化只是一种表面上的称呼；有了反馈，教学路径才真正具备适应性。</p></section>
<section><h2>好的学习路径，也需要设计情绪曲线<a href="#好的学习路径也需要设计情绪曲线"><span>#</span></a></h2><p>学习不只是信息输入和答案判断，也是一段持续变化的情绪体验。刚接触新主题时，好奇心可以帮助学习者投入；遇到暂时无法解决的问题时，适度的挫败感能够提示自己需要补足某个环节；解决问题后的满足感，则会强化继续学习的动力。</p><p>因此，AI 教师不应该只追求题目越来越难，也不能让学习者一直停留在舒适区。失败太多会让人沮丧，挑战太少又会让人感到无聊。更合理的路径，是根据学习者的状态调整挑战强度，在“我还不会”和“我能够学会”之间保持张力。</p><p>这意味着系统需要关注的不只是答对率，还包括学习者是否连续受挫、是否长时间没有获得进展感，以及是否已经因为任务过于简单而失去兴趣。一次适时的提示、一个更容易进入的例子，或者一道能够快速建立信心的练习，都可能让学习重新启动。</p></section>
<section><h2>学习不止一种目标<a href="#学习不止一种目标"><span>#</span></a></h2><p>学习也不是单一的“理解知识”。它至少包含几个相互连接的方面。</p><ul>
<li><strong>发现</strong>：意识到一个问题、概念或知识领域的存在，知道自己接下来可以探索什么。</li>
<li><strong>理解</strong>：建立心智模型，能够解释概念为什么成立，以及它和其他知识如何连接。</li>
<li><strong>练习</strong>：通过重复和反馈形成掌握，提升准确率、熟练度和解决问题的速度。</li>
<li><strong>应用</strong>：把知识放进真实情境，在具体任务中形成判断、视角和经验。</li>
</ul><p>这几个方面不能完全互相替代。读懂一个概念，不代表能够快速使用它；做对几道练习，也不代表能够在陌生场景中迁移；知道一个领域存在，更不等于已经形成了可操作的理解。</p><p>所以，AI 在规划学习路径时，应该先判断学习者当前缺少的是哪一层能力。如果目标是建立整体认识，就应该安排探索和概念连接；如果目标是提高熟练度，就需要更多有梯度的重复练习；如果目标是解决实际问题，就应该提供案例、项目和开放性任务。</p><p>只有把这些目标组合起来，学习才不会停留在“看过”和“答对”的层面，而会逐渐转化为真正能够调用的能力。</p></section>
<section><h2>测验之外：让反馈变成不同类型的练习<a href="#测验之外让反馈变成不同类型的练习"><span>#</span></a></h2><p>测验适合用来探查理解状态，但它不应该成为 AI 教师唯一的输出形式。不同的学习目标，需要不同的学习材料和反馈方式。</p><p>对于需要记忆的事实、术语和关键定义，可以使用闪卡。闪卡的价值不在于把内容压缩成问答，而在于通过间隔重复，让学习者在即将遗忘时再次主动回忆。</p><p>对于需要形成熟练度的技能，可以设计重复练习。题目应该有清晰的梯度，并逐步提高速度要求，让学习者不仅知道怎么做，也能够更加准确、稳定地完成。</p><p>对于需要理解和表达的内容，则可以要求学习者解释概念、比较两个观点、指出例子的边界，或者用自己的话重建一段推理。这样的练习比单纯选择正确答案更能暴露心智模型中的缺口。</p><p>实际应用还需要另一类任务：案例分析、模拟决策、小型项目，以及带有不完整信息的问题。它们能够检验学习者是否可以把知识迁移到新情境中，并积累课本无法直接提供的经验。</p><p>因此，一个完整的 AI 学习系统，应该能够在讲解、闪卡、测验、重复练习、解释任务和真实应用之间切换。它输出的不是一串内容，而是一组围绕学习目标组织起来的活动。</p></section>
<section><h2>学习中的隐性成本：信任也会消耗认知资源<a href="#学习中的隐性成本信任也会消耗认知资源"><span>#</span></a></h2><p>学习者通常会把注意力放在知识内容上，却容易忽略另一个重要成本：信任成本。</p><p>当我们同时使用多个老师、多个课程和多个资料库时，需要不断判断它们是否可靠、观点是否冲突、术语是否一致、哪种说法更值得采用。这些判断本身会消耗认知资源，并且可能打断对知识的深入理解。</p><p><img alt="" loading="lazy" width="661" height="534" src="/_astro/trust-cost.hYcDJ7sm_Z2tAlfX.webp" /></p><p>从心理资源优化分配（Optimized Allocation of Mental Resources）的角度看，学习者应该尽量把精力放在材料本身，而不是放在资料管理和来源比较上。</p><p>这并不意味着学习者只能接受单一观点。更合理的方式是：由一个值得信任的教学界面，承担资料筛选、观点整合和冲突解释的工作，然后以适合个人理解水平的方式传递给学习者。</p></section>
<section><h2>一个老师，不等于一个来源<a href="#一个老师不等于一个来源"><span>#</span></a></h2><p>“只使用一个老师，会不会失去多视角？”这是一个自然的担忧。</p><p>但单一的教学界面，并不意味着背后只有一个知识来源。AI 可以先收集教材、论文、课程和其他可靠资料，再比较它们的观点，识别分歧，最后把多种视角组织成一条适合当前学习者的解释路径。</p><p>可以把它想象成这样：老师阅读了多个来源，理解它们各自的侧重点，然后面对面地为你讲解。你不需要直接面对一堆互相独立的资料，而是先获得一份经过整理、能够继续追问的知识地图。</p><p>当然，这种模式仍然需要工程化的信任机制。AI 应该尽可能说明知识来源，在存在争议时明确指出不确定性，在无法确认时承认不知道，而不是用流畅的表达掩盖错误。</p></section>
<section><h2>结语：AI 教师的关键是适配，而不是生成<a href="#结语ai-教师的关键是适配而不是生成"><span>#</span></a></h2><p>AI 辅助学习最有价值的地方，不在于它能一次生成多少内容，而在于它能否围绕一个具体的人持续调整教学。</p><p>一个可信的 AI 教师系统，至少需要完成三个动作：先探查学习者当前理解到哪里，再规划一条合适的学习路径，最后通过教学、练习和多种反馈不断更新判断。这就是 Probe-Plan-Teach 的基本循环。</p><p>在这个循环之外，它还需要设计合适的情绪节奏，兼顾发现、理解、练习和应用，并根据不同目标提供闪卡、重复练习、解释任务和真实案例。AI 可以聚合多种知识来源，但最好通过一个稳定、透明、可追问的界面把这些活动组织起来。</p><p>最终，理想的学习体验不是“AI 替我学习”，而是“AI 让我始终在最适合自己的理解边缘学习”。当系统真正理解我已经会什么、还缺什么，以及下一步最值得理解什么时，学习才可能从资料堆积，转变为一场持续推进的对话。</p></section>]]></content>
    </entry>
    <entry>
      <id>https://www.celyn-blog.xyz/posts/opencode-linux-server/</id>
      <title type="text">在 Linux 服务器上使用 opencode 的正确方式</title>
      <published>2026-08-24T00:00:00.000Z</published>
      <updated>2026-08-24T00:00:00.000Z</updated>
      <author><name>Celyn</name></author>
      <link rel="alternate" href="https://www.celyn-blog.xyz/posts/opencode-linux-server/"/>
      <summary type="text">记录我把 opencode 搬到低配 Linux 服务器上的经历，以及后来改用本地运行、SSH 操作服务器的原因。</summary>
      <content type="html"><![CDATA[<p>我一开始想把本地的工作流搬到服务器上。装好 Linux，SSH 上去，直接在服务器里干活。</p>
<section><h2>我当时想要什么<a href="#我当时想要什么"><span>#</span></a></h2><p>我想要的布局很简单：左边敲代码，右边跑 <code>opencode</code>，用 <code>tmux</code> 把两个终端分开。</p><p><img alt="" loading="lazy" width="1920" height="1035" src="/_astro/Pasted-image-20260824142416.Dq23d0Sy_ZVEa2C.webp" /></p><p>我还希望 <code>opencode</code> 能尽量自己完成任务，不要每一步都等我确认。为了少确认几次，我做了个 <code>opencodeskip</code>，让它直接调用 <code>opencode --auto</code>。</p></section>
<section><h2><code>tmux</code> 是真学了，但也真被 <code>Ctrl+C</code> 坑了<a href="#tmux-是真学了但也真被-ctrlc-坑了"><span>#</span></a></h2><p>为了这套用法，我顺手学了 <code>tmux</code> 最基本的操作。最常用的是：</p><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>Ctrl+b，然后按 %</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p>这个快捷键能把窗口一分为二，左边放代码，右边跑 <code>opencode</code>。刚开始用时，我觉得这个安排挺合理。</p><p>坑也从这里开始。我以前在 Windows 上习惯用 <code>Ctrl+C</code> 复制文字，到了 Linux 里还是会下意识按它。结果文字没复制到，服务先被我中断了。明明只是想拿一段输出，最后还得重新处理进程。</p></section>
<section><h2><code>opencodeskip</code> 这件事<a href="#opencodeskip-这件事"><span>#</span></a></h2><p>我在 Windows 本机上本来就有一个脚本：</p><div><figure><figcaption><span></span><span>Terminal window</span></figcaption><pre><code><div><div><div>1</div></div><div><span>@</span><span>echo</span><span> </span><span>off</span></div></div><div><div><div>2</div></div><div><span><span>opencode --auto </span><span>%*</span></span></div></div></code></pre><div><div></div><div></div></div></figure></div><p>所以我在 Linux 上也照着做了一个等价版本：</p><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>#!/usr/bin/env bash</span></div></div><div><div><div>2</div></div><div><span>exec</span><span> </span><span>opencode</span><span> </span><span>--auto</span><span> </span><span>"</span><span>$@</span><span>"</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p>我把它放到 <code>~/.local/bin/opencodeskip</code>，加上执行权限，想着以后可以像在本地一样直接调用。它只做一件事：把参数转给 <code>opencode --auto</code>，省掉一些确认。</p></section>
<section><h2>真正让我崩溃的是服务器太小<a href="#真正让我崩溃的是服务器太小"><span>#</span></a></h2><p>我用的是阿里云送的 <code>2核2G</code> 服务器，内存实在太紧。刚开始我以为跑一个 <code>opencode</code> 应该没问题，实际很快就发现不是这么回事。</p><p>只要我开两个 <code>opencode</code>，服务器就开始卡，甚至直接死掉，最后只能重启。</p><p>我原本想做的是远程协作，结果变成了远程把服务器折腾死。</p><p>退出 <code>opencode</code> 之后，麻烦也没有马上结束：</p><ol>
<li><code>Ctrl+C</code> 后子进程残留</li>
<li>网络一卡，<code>TUI</code> 直接卡死</li>
<li>服务器资源占用下不来</li>
<li>环境版本冲突</li>
<li>会话丢失</li>
</ol><p>我重新运行 <code>opencode</code> 时，服务器还可能再次卡死。用着用着就得先救火，原本的工作反而没推进多少。</p></section>
<section><h2><code>zshrc</code> 这次也把我坑得不轻<a href="#zshrc-这次也把我坑得不轻"><span>#</span></a></h2><p>我还试着让 <code>opencode</code> 帮我装几个 <code>zsh</code> 插件，想把远程 shell 调整得顺手一点。结果它写配置时，直接覆盖了原来的 <code>~/.zshrc</code>。不是追加，是整份覆盖！！！</p><p>终端样式、别名和原来的配置全没了。</p><p>更烦的是，之前弄好的 <code>opencodeskip</code> 也不见了。服务器一出错，修复就变得很麻烦：配置没了，命令没了，插件没了，环境还不稳定。每一项单独看都不算大事，叠在一起就够折腾了。</p><p>到这时我才意识到，问题不在于要不要放权给 <code>opencode</code>，而在于这台服务器根本扛不住这种用法。</p></section>
<section><h2>后来我换了个做法<a href="#后来我换了个做法"><span>#</span></a></h2><p>我最后还是改成本地运行 <code>opencode</code>，通过 SSH 操作服务器。对这台 <code>2核2G</code> 的机器来说，这样省事得多，也不容易因为进程残留、TUI 卡顿或配置覆盖反复救火。</p></section>
<section><h2>相关命令<a href="#相关命令"><span>#</span></a></h2><p>下面是我当时用到的几组基础命令。第一组先更新软件源，安装 <code>curl</code>、<code>git</code> 和证书包，再安装 <code>opencode</code>。重新加载 shell 后，用版本号确认安装是否成功。</p><div><figure><figcaption><span></span><span>Terminal window</span></figcaption><pre><code><div><div><div>1</div></div><div><span>sudo</span><span> </span><span>apt</span><span> </span><span>update</span></div></div><div><div><div>2</div></div><div><span>sudo</span><span> </span><span>apt</span><span> </span><span>install</span><span> </span><span>-y</span><span> </span><span>curl</span><span> </span><span>git</span><span> </span><span>ca-certificates</span></div></div><div><div><div>3</div></div><div><span>curl</span><span> </span><span>-fsSL</span><span> </span><span>https://opencode.ai/install</span><span> | </span><span>bash</span></div></div><div><div><div>4</div></div><div><span>exec</span><span> </span><span>$SHELL</span></div></div><div><div><div>5</div></div><div><span>opencode</span><span> </span><span>--version</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p>如果终端找不到刚安装的命令，就把 <code>~/.local/bin</code> 加进 <code>PATH</code>，然后重新加载 <code>~/.zshrc</code>。</p><div><figure><figcaption><span></span><span>Terminal window</span></figcaption><pre><code><div><div><div>1</div></div><div><span>echo</span><span> </span><span>'export PATH="$HOME/.local/bin:$PATH"'</span><span> &gt;&gt; </span><span>~/.zshrc</span></div></div><div><div><div>2</div></div><div><span>source</span><span> </span><span>~/.zshrc</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p>最后这组命令用来删除 <code>zsh</code> 的补全缓存，再重新加载配置。修改过插件或补全设置后，通常需要这一步。</p><div><figure><figcaption><span></span><span>Terminal window</span></figcaption><pre><code><div><div><div>1</div></div><div><span>rm</span><span> </span><span>-f</span><span> </span><span>~/.zcompdump</span><span>*</span></div></div><div><div><div>2</div></div><div><span>source</span><span> </span><span>~/.zshrc</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p>我现在的想法很简单：服务器性能不够，就别硬把它当成本地主机。需要远程操作时用 SSH，能保守一点就保守一点。最后负责修系统的，还是自己。</p></section>]]></content>
    </entry>
    <entry>
      <id>https://www.celyn-blog.xyz/posts/html-to-vite-modern-frontend/</id>
      <title type="text">从 HTML 到 Vite：现代前端开发到底解决了什么问题？</title>
      <published>2026-08-17T00:00:00.000Z</published>
      <updated>2026-08-17T00:00:00.000Z</updated>
      <author><name>Celyn</name></author>
      <link rel="alternate" href="https://www.celyn-blog.xyz/posts/html-to-vite-modern-frontend/"/>
      <summary type="text">从普通网页的组成讲起，理解现代前端为什么需要模块化、构建工具，以及 Vite 在开发、构建和部署中解决了什么问题。</summary>
      <content type="html"><![CDATA[<section><h1>前言<a href="#前言"><span>#</span></a></h1><p>前端开发并不是一开始就复杂的。</p><p>最开始，我们只需要写 HTML、CSS 和 JavaScript，就能做出一个可以运行的网页。</p><p>但随着项目变大，文件会越来越多，代码之间的依赖关系会越来越复杂，本地开发、上线构建、浏览器缓存等问题也会陆续出现。</p><p>这篇文章就从一个普通网页的组成讲起，一步步说明：为什么现代前端需要模块化，为什么需要构建工具，以及 Vite 到底解决了什么问题。</p><hr /></section>
<section><h1>从最简单的网页开始<a href="#从最简单的网页开始"><span>#</span></a></h1><p>一个普通网页通常由三部分组成：HTML、CSS 和 JavaScript。</p><p>HTML 负责页面结构，CSS 负责页面样式，JavaScript 负责交互逻辑。</p><p>一个很小的页面，可以把它们都写在同一个 HTML 文件里；但项目稍微变大，这个文件就会越来越难维护。</p><p>所以更常见的做法，是把它们拆成不同文件：</p><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>&lt;</span><span>link</span><span> </span><span>rel</span><span>=</span><span>"stylesheet"</span><span> </span><span>href</span><span>=</span><span>"style.css"</span><span> /&gt;</span></div></div><div><div><div>2</div></div><div><span>&lt;</span><span>script</span><span> </span><span>src</span><span>=</span><span>"main.js"</span><span>&gt;&lt;/</span><span>script</span><span>&gt;</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p>这样做之后，结构、样式和逻辑被分开了，代码确实更清晰。但新的问题很快出现：当 JavaScript 文件越来越多时，脚本的加载顺序会变得非常重要。</p><p>比如 <code>a.js</code> 里定义了一个变量，<code>b.js</code> 里要使用它，那么 <code>a.js</code> 就必须写在 <code>b.js</code> 前面。顺序一旦错了，页面运行时就可能报错：变量不存在、函数未定义。</p><p>如果项目里只有两三个脚本，这个问题还可以靠人脑记住。可如果项目里有几十个甚至上百个 JS 文件，手动维护它们的引用顺序就会非常痛苦。</p></section>
<section><h1>模块化：让文件之间的关系写清楚<a href="#模块化让文件之间的关系写清楚"><span>#</span></a></h1><p>为了解决这个问题，现代前端引入了一个非常重要的基础能力：模块化。</p><p>模块化的核心思想是：每个文件只暴露自己想让外部使用的内容，其他文件需要什么，就显式地导入什么。</p><p>例如：</p><div><figure><figcaption><span>utils.js</span></figcaption><pre><code><div><div><div>1</div></div><div><span>export</span><span> </span><span>function</span><span> </span><span>add</span><span><span>(</span><span>a</span><span>, </span><span>b</span><span>) {</span></span></div></div><div><div><div>2</div></div><div><span>  </span><span>return</span><span><span> </span><span>a</span><span> </span></span><span>+</span><span><span> </span><span>b</span></span></div></div><div><div><div>3</div></div><div><span>}</span></div></div></code></pre><div><div></div><div></div></div></figure></div><div><figure><figcaption><span>main.js</span></figcaption><pre><code><div><div><div>1</div></div><div><span>import</span><span> { </span><span>add</span><span> } </span><span>from</span><span> </span><span>'./utils.js'</span></div></div><div><div><div>2</div></div><div>
</div></div><div><div><div>3</div></div><div><span><span>console</span><span>.</span></span><span>log</span><span>(</span><span>add</span><span>(</span><span>1</span><span>, </span><span>2</span><span>))</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p>这样一来，文件之间的依赖关系就不再依靠 HTML 里的 <code>&lt;script&gt;</code> 顺序来维持，而是直接写在 JavaScript 代码里。</p><p>这就是 ES Modules，也就是我们常说的 ESM。<code>export</code> 负责导出，<code>import</code> 负责导入。</p></section>
<section><h1>模块化带来的第一个变化：不能再直接双击打开 HTML<a href="#模块化带来的第一个变化不能再直接双击打开-html"><span>#</span></a></h1><p>使用普通 <code>&lt;script&gt;</code> 标签时，很多简单页面可以直接双击 HTML 文件运行，也就是通过 <code>file://</code> 协议打开。</p><p>但如果你使用了 ESM：</p><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>&lt;</span><span>script</span><span> </span><span>type</span><span>=</span><span>"module"</span><span> </span><span>src</span><span>=</span><span>"main.js"</span><span>&gt;&lt;/</span><span>script</span><span>&gt;</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p>情况就不一样了。</p><p>浏览器在加载模块时，会按照更严格的规则处理文件请求。通过 <code>file://</code> 打开页面时，模块之间的导入很容易因为同源策略或 CORS 限制而加载失败。</p><p>所以，使用模块化之后，我们通常不能再依赖“直接双击 HTML 文件”来开发页面，而是需要通过一个本地 HTTP 服务来访问页面。</p><p>也就是说，页面应该通过类似这样的地址打开：</p><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>http://localhost:5173</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p>而不是：</p><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>file:///C:/project/index.html</span></div></div></code></pre><div><div></div><div></div></div></figure></div></section>
<section><h1>项目变大后，还会遇到哪些问题？<a href="#项目变大后还会遇到哪些问题"><span>#</span></a></h1><p>到这里，我们其实已经从“写几个静态文件”进入了“现代前端项目”的世界。</p><p>这个阶段通常会遇到三个典型问题。</p><section><h2>1. 本地开发需要一个服务器<a href="#1-本地开发需要一个服务器"><span>#</span></a></h2><p>浏览器中的原生 ESM 开发通常应通过 HTTP 服务访问。如果每次修改代码后，都要手动上传到服务器再刷新页面，开发效率会非常低。</p><p>理想的开发体验应该是：在本地启动一个服务器，修改代码后浏览器自动刷新，甚至只更新变动的部分。</p></section><section><h2>2. 上线时不能让浏览器请求太多零散文件<a href="#2-上线时不能让浏览器请求太多零散文件"><span>#</span></a></h2><p>开发阶段，我们希望文件拆得清楚，方便维护。</p><p>但上线之后，如果页面需要请求大量 CSS 和 JS 文件，首屏加载可能会变慢。虽然现代浏览器和 HTTP/2 已经缓解了这个问题，但生产环境仍然需要压缩、拆分和优化资源。</p></section><section><h2>3. 浏览器缓存可能导致用户拿到旧文件<a href="#3-浏览器缓存可能导致用户拿到旧文件"><span>#</span></a></h2><p>浏览器会缓存 JS、CSS、图片等静态资源。这样可以加快第二次访问速度，但也会带来一个问题：代码明明已经更新了，用户浏览器里却还在使用旧文件。</p><p>一个常见解决方案是给文件名加上 hash，也叫文件指纹。</p><p>例如：</p><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>main.8d3f4a1c.js</span></div></div><div><div><div>2</div></div><div><span>style.6a91b2ef.css</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p>只要文件内容发生变化，hash 就会变化，文件名也会变化。浏览器会把它当成一个新文件重新下载。</p></section></section>
<section><h1>Vite：现代前端开发的基础工具<a href="#vite现代前端开发的基础工具"><span>#</span></a></h1><p>为了解决上面这些问题，我们会引入构建工具。现在非常常用的一个构建工具，就是 Vite。</p><p>需要注意的是，Vite 不是 React、Vue 这种 UI 框架。它不负责规定页面怎么写，而是负责开发服务器、模块处理和生产构建。</p><p>Vite 主要做两类事情：开发阶段提升效率，生产阶段优化资源。</p><section><h2>开发阶段：启动本地服务器<a href="#开发阶段启动本地服务器"><span>#</span></a></h2><p>Vite 会在本地启动一个开发服务器。你可以通过 <code>http://localhost:5173</code> 访问项目，而不是直接双击 HTML 文件。</p><p>这样就解决了 ESM 在 <code>file://</code> 协议下的加载问题。</p><p>同时，Vite 还支持热更新，也就是 HMR。修改代码后，浏览器可以快速看到变化，不需要你手动重新部署。</p></section><section><h2>生产阶段：打包、压缩、加文件指纹<a href="#生产阶段打包压缩加文件指纹"><span>#</span></a></h2><p>开发时，我们希望代码拆得细，文件职责清楚。</p><p>上线时，我们希望资源更适合浏览器加载。Vite 在构建生产版本时，会对代码进行打包、压缩和优化，并为输出文件生成带 hash 的文件名。</p><p>最终生成的文件通常会放在 <code>dist</code> 目录中。这个目录里的内容，才是要部署到服务器上的生产版本。</p></section></section>
<section><h1>为什么使用 Vite 前要安装 Node.js？<a href="#为什么使用-vite-前要安装-nodejs"><span>#</span></a></h1><p>Vite 本质上是一个运行在 Node.js 环境里的前端工具。</p><p>JavaScript 原本主要运行在浏览器里，用来操作页面。但如果我们想在浏览器之外运行 JavaScript，比如执行构建工具、启动开发服务器、安装第三方库，就需要 Node.js。</p><p>你可以把 Node.js 理解成“让 JavaScript 在电脑本地运行的环境”。</p><p>安装 Node.js 后，可以在终端里检查版本：</p><div><figure><figcaption><span></span><span>Terminal window</span></figcaption><pre><code><div><div><div>1</div></div><div><span>node</span><span> </span><span>-v</span></div></div><div><div><div>2</div></div><div><span>npm</span><span> </span><span>-v</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p>如果能看到版本号，就说明安装成功了。</p><p>这里的 npm 是 Node.js 自带的包管理工具。它的作用类似于 Python 里的 pip，用来安装和管理 JavaScript 生态里的第三方库。</p></section>
<section><h1>如何在项目里安装 Vite？<a href="#如何在项目里安装-vite"><span>#</span></a></h1><p>进入项目文件夹后，先初始化项目：</p><div><figure><figcaption><span></span><span>Terminal window</span></figcaption><pre><code><div><div><div>1</div></div><div><span>npm</span><span> </span><span>init</span><span> </span><span>-y</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p>执行后，项目里会出现一个 <code>package.json</code> 文件。它用来记录项目名称、版本、脚本命令、依赖库等信息。</p><p>然后安装 Vite：</p><div><figure><figcaption><span></span><span>Terminal window</span></figcaption><pre><code><div><div><div>1</div></div><div><span>npm</span><span> </span><span>install</span><span> </span><span>-D</span><span> </span><span>vite</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p>这里的 <code>-D</code> 表示把 Vite 安装为开发依赖，也就是写入 <code>devDependencies</code>。</p><p>所谓“开发依赖”，不是说它只能安装在你自己的电脑上，而是说它只在开发和构建阶段需要。</p><p>如果选择在服务器上执行 <code>npm run build</code>，服务器在构建阶段也需要安装 Vite，所以这时要安装 <code>devDependencies</code>。默认执行 <code>npm install</code> 会安装它们。</p><p>但网站真正运行起来以后，浏览器访问的是 <code>dist</code> 里的静态文件，Nginx 只负责把这些文件返回给浏览器，并不需要继续运行 Vite。</p><p>安装完成后，项目里通常会多出两个东西：</p><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>node_modules/</span></div></div><div><div><div>2</div></div><div><span>package-lock.json</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p></p><figure><img alt="安装 Vite 后，项目目录里出现 package-lock.json 和 node_modules" loading="lazy" width="634" height="204" src="/_astro/01-npm-install-files.NNlT3nW3_Z8WIh8.webp" /><figcaption>安装 Vite 后，项目目录里出现 package-lock.json 和 node_modules</figcaption></figure><p></p><p><code>node_modules</code> 是第三方库实际存放的位置。Vite 以及 Vite 依赖的其他库，都会安装在这里。</p><p></p><figure><img alt="node_modules 中除了 Vite，还会包含它依赖的其他包" loading="lazy" width="633" height="378" src="/_astro/02-node-modules-dependencies.BrsKMtfa_ZUuB6r.webp" /><figcaption>node_modules 中除了 Vite，还会包含它依赖的其他包</figcaption></figure><p></p><p><code>package-lock.json</code> 用来锁定依赖版本，保证其他人安装项目依赖时，尽量得到一致的结果。</p><p>补充一下，如果是从零创建一个新项目，也可以直接使用 Vite 官方脚手架：</p><div><figure><figcaption><span></span><span>Terminal window</span></figcaption><pre><code><div><div><div>1</div></div><div><span>npm</span><span> </span><span>create</span><span> </span><span>vite@latest</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p>它会帮你生成基础项目结构。本文使用 <code>npm init</code> 和 <code>npm install -D vite</code>，是为了更清楚地说明 Vite 是如何作为项目依赖被安装进来的。</p></section>
<section><h1>Vite 命令从哪里来？<a href="#vite-命令从哪里来"><span>#</span></a></h1><p>安装 Vite 后，<code>node_modules/.bin</code> 目录里会出现可执行命令。</p><p>在 Windows 下，你可能会看到类似这样的文件：</p><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>node_modules\.bin\vite.cmd</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p>它就是本地项目里的 Vite 命令。</p><p></p><figure><img alt="node_modules/.bin 里的 vite.cmd、vite.ps1 等可执行入口" loading="lazy" width="625" height="238" src="/_astro/03-bin-vite-command.DfqlMbmZ_Z2iqUpD.webp" /><figcaption>node_modules/.bin 里的 vite.cmd、vite.ps1 等可执行入口</figcaption></figure><p></p><p>不过实际开发中，我们通常不会手动去执行这个路径，而是把命令写进 <code>package.json</code> 的 <code>scripts</code> 里。</p><p>例如：</p><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>{</span></div></div><div><div><div>2</div></div><div><span>  </span><span>"scripts"</span><span>: {</span></div></div><div><div><div>3</div></div><div><span>    </span><span>"dev"</span><span>: </span><span>"vite"</span><span>,</span></div></div><div><div><div>4</div></div><div><span>    </span><span>"build"</span><span>: </span><span>"vite build"</span><span>,</span></div></div><div><div><div>5</div></div><div><span>    </span><span>"preview"</span><span>: </span><span>"vite preview"</span></div></div><div><div><div>6</div></div><div><span><span>  </span></span><span>}</span></div></div><div><div><div>7</div></div><div><span>}</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p>之后就可以这样使用：</p><div><figure><figcaption><span></span><span>Terminal window</span></figcaption><pre><code><div><div><div>1</div></div><div><span>npm</span><span> </span><span>run</span><span> </span><span>dev</span></div></div><div><div><div>2</div></div><div><span>npm</span><span> </span><span>run</span><span> </span><span>build</span></div></div><div><div><div>3</div></div><div><span>npm</span><span> </span><span>run</span><span> </span><span>preview</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p><code>npm run dev</code> 用来启动本地开发服务器。启动成功后，终端会显示本地访问地址，例如 <code>http://localhost:5173/</code>。</p><p></p><figure><img alt="Vite 开发服务器启动成功后，会显示本地访问地址" loading="lazy" width="680" height="157" src="/_astro/04-vite-dev-server.CtEWkJMJ_2kfOzL.webp" /><figcaption>Vite 开发服务器启动成功后，会显示本地访问地址</figcaption></figure><p></p><p><code>npm run build</code> 用来生成生产环境文件。</p><p><code>npm run preview</code> 用来在本地启动一个服务器，检查构建后的生产版本是否正常；它不是正式的生产服务器。</p></section>
<section><h1>补充：构建后的 dist 如何部署到服务器？<a href="#补充构建后的-dist-如何部署到服务器"><span>#</span></a></h1><p>一种常见做法是：服务器拉取源码后，在服务器上安装依赖并执行构建，而不是把 <code>dist</code> 提交到 Git 仓库。</p><p>具体来说，Git 仓库里只保存项目源码和配置文件，<code>dist</code> 和 <code>node_modules</code> 都放进 <code>.gitignore</code>。</p><p><code>node_modules</code> 不提交，是因为它体积很大，而且可以根据 <code>package.json</code> 和 <code>package-lock.json</code> 重新安装。</p><p><code>dist</code> 不提交，是因为它是构建产物，可以随时通过源码重新生成。</p><p>所以一次典型的服务器部署流程是：</p><div><figure><figcaption><span></span><span>Terminal window</span></figcaption><pre><code><div><div><div>1</div></div><div><span>git</span><span> </span><span>pull</span></div></div><div><div><div>2</div></div><div><span>npm</span><span> </span><span>install</span></div></div><div><div><div>3</div></div><div><span>npm</span><span> </span><span>run</span><span> </span><span>build</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p>注意：如果服务器负责执行 <code>npm run build</code>，就不能在构建前只安装 production 依赖。因为 Vite 在 <code>devDependencies</code> 中，构建阶段仍然需要它。</p><p>执行完 <code>npm run build</code> 后，服务器上会生成新的 <code>dist</code> 目录。</p><p>然后在 Nginx 中把网站根目录指向这个 <code>dist</code> 文件夹，并把入口文件设置为 <code>index.html</code>。</p><p>例如：</p><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>server</span><span> {</span></div></div><div><div><div>2</div></div><div><span> <span> </span></span><span>listen </span><span>80</span><span>;</span></div></div><div><div><div>3</div></div><div><span> <span> </span></span><span>server_name </span><span>example.com;</span></div></div><div><div><div>4</div></div><div>
</div></div><div><div><div>5</div></div><div><span> <span> </span></span><span>root </span><span>/var/www/my-project/dist;</span></div></div><div><div><div>6</div></div><div><span> <span> </span></span><span>index </span><span>index.html;</span></div></div><div><div><div>7</div></div><div><span>}</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p>这样浏览器访问网站时，Nginx 返回的就是 Vite 构建后的生产文件。</p><p>如果后续项目使用前端路由，例如 React Router，还需要让 Nginx 在找不到具体文件时回退到 <code>index.html</code>。</p></section>
<section><h1>总结<a href="#总结"><span>#</span></a></h1><p>现代前端开发并不是一开始就复杂的。它是项目规模变大后，为了解决实际问题一步步演化出来的。</p><p>最开始，我们只需要 HTML、CSS 和 JavaScript。</p><p>后来，文件越来越多，手动维护脚本顺序变得困难，于是有了模块化。</p><p>模块化让依赖关系更清楚，但也要求我们通过 HTTP 服务来开发页面，于是需要本地开发服务器。</p><p>项目上线时，还需要打包、压缩、文件指纹和缓存处理，于是构建工具就变得非常重要。</p><p>Vite 解决的正是这些问题：开发时提供快速的本地服务器和热更新，上线前生成优化后的生产资源。</p><p>所以，理解 Vite 的关键不是记命令，而是理解它为什么出现：它是在帮我们把“适合开发的代码”，转换成“适合上线的资源”。</p></section>
<section><h1>学完 Vite 之后，下一步学什么<a href="#学完-vite-之后下一步学什么"><span>#</span></a></h1><p>学到这里，我们已经解决了一部分“工程化”的问题。</p><p>Vite 帮我们解决的是开发和构建：本地服务器、模块化、热更新、打包、压缩、文件指纹。</p><p>也就是说，Vite 主要回答的是：</p><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>项目怎么开发？</span></div></div><div><div><div>2</div></div><div><span>代码怎么组织成模块？</span></div></div><div><div><div>3</div></div><div><span>上线前怎么构建成生产资源？</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p>但 Vite 并不会直接解决“页面本身如何组织”的问题。</p><p>当页面越来越复杂时，我们会遇到新的麻烦：按钮、表单、列表、弹窗、用户状态、加载状态都混在一起。如果继续手动操作 DOM，代码会很快变得难以维护。</p><p>这时就需要 React。</p><p>React 是一个用于构建用户界面（UI）的 JavaScript 库，也常被称为前端框架；它的核心思想是用组件来组织页面。</p><p>React 关注的是另一个层面：如何把页面拆成组件，如何让数据传入组件，如何让状态变化后页面自动更新。</p><p>所以，Vite 和 React 解决的是两个不同问题：</p><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>Vite：解决项目开发、构建、部署的问题</span></div></div><div><div><div>2</div></div><div><span>React：解决页面组织、数据驱动、状态更新的问题</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p>也正因为如此，学完 Vite 之后，继续学习 React 是很自然的一步。</p><p>这条现代前端学习路线可以继续往下走：</p><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>HTML/CSS/JS</span></div></div><div><div><div>2</div></div><div><span>→ Vite</span></div></div><div><div><div>3</div></div><div><span>→ React（或 Vue、Svelte 等）</span></div></div><div><div><div>4</div></div><div><span>→ Next.js 等应用框架</span></div></div></code></pre><div><div></div><div></div></div></figure></div></section>]]></content>
    </entry>
    <entry>
      <id>https://www.celyn-blog.xyz/posts/fastapi-python-decorator-learning/</id>
      <title type="text">FastAPI初识&amp;Python 装饰器学习</title>
      <published>2026-08-15T00:00:00.000Z</published>
      <updated>2026-08-15T00:00:00.000Z</updated>
      <author><name>Celyn</name></author>
      <link rel="alternate" href="https://www.celyn-blog.xyz/posts/fastapi-python-decorator-learning/"/>
      <summary type="text">从 FastAPI 的 @app.get() 出发，逐步理解 Python 装饰器的本质、执行时序和带参数装饰器。</summary>
      <content type="html"><![CDATA[<blockquote><p>从 FastAPI 的 <code>@app.get()</code> 出发，拆开看看 Python 装饰器到底在干什么。</p></blockquote>
<section><h1>前言<a href="#前言"><span>#</span></a></h1><p>我一开始是从 FastAPI 的 <code>@app.get()</code> 产生疑问，然后顺着“它到底在修饰谁”“为什么这样写”“装饰器什么时候执行”“带参数装饰器又是什么”这条线，一步一步把 Python 装饰器弄懂。</p><p>这篇整理记录的是我的学习过程，先把思路走通，再回头看结论。</p><hr /></section>
<section><h1>1. 最初疑问：FastAPI 路由装饰器<a href="#1-最初疑问fastapi-路由装饰器"><span>#</span></a></h1><p><img alt="" loading="lazy" width="421" height="289" src="/_astro/fastapi-decorator-question.DazRS9hx_Z1yVgBE.webp" /></p><p>其中的代码：</p><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span><span>@app</span><span>.</span><span>get</span></span><span>(</span><span>"/api/profile"</span><span>)</span></div></div><div><div><div>2</div></div><div><span>def</span><span> </span><span>get_profile</span><span>():</span></div></div><div><div><div>3</div></div><div><span>    </span><span>return</span><span> profile</span></div></div></code></pre><div><div></div><div></div></div></figure></div><section><h2>疑问点 1：<code>@app.get()</code> 和下方 <code>def</code> 是不是绑定在一起？<a href="#疑问点-1appget-和下方-def-是不是绑定在一起"><span>#</span></a></h2><p>结论：<strong>是的，装饰器只修饰紧随其后的函数</strong>。它们在语法上必须连在一起，中间不能插入其他代码。</p></section><section><h2>疑问点 2：谁装饰谁？<a href="#疑问点-2谁装饰谁"><span>#</span></a></h2><p><code>@app.get("/api/profile")</code> 装饰的是函数 <code>get_profile</code>。</p></section><section><h2>疑问点 3：装饰器到底有什么用？<a href="#疑问点-3装饰器到底有什么用"><span>#</span></a></h2><p>它的作用是给普通函数打上“角色标记”，告诉框架：</p><blockquote><p>这个函数用来处理 <code>GET /api/profile</code> 的网络请求。</p></blockquote><p>等价的原生写法可以理解为：</p><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>def</span><span> </span><span>get_profile</span><span>():</span></div></div><div><div><div>2</div></div><div><span>    </span><span>return</span><span> profile</span></div></div><div><div><div>3</div></div><div>
</div></div><div><div><div>4</div></div><div><span><span>app.</span><span>add_api_route</span><span>(</span></span><span>path</span><span>=</span><span>"/api/profile"</span><span>, </span><span>endpoint</span><span><span>=</span><span>get_profile, </span></span><span>methods</span><span><span>=</span><span>[</span></span><span>"GET"</span><span>])</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p>所以装饰器本质上是语法糖：把“路径注册”和“业务函数”写在一起，让代码更紧凑、更易读。</p><hr /></section></section>
<section><h1>2. 切入基础：自定义装饰器（计时器示例）<a href="#2-切入基础自定义装饰器计时器示例"><span>#</span></a></h1><p>示例代码：</p><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>import</span><span> time</span></div></div><div><div><div>2</div></div><div>
</div></div><div><div><div>3</div></div><div><span>def</span><span> </span><span>timer</span><span>(</span><span>function</span><span>):</span></div></div><div><div><div>4</div></div><div><span>    </span><span>def</span><span> </span><span>wrapper</span><span>():</span></div></div><div><div><div>5</div></div><div><span><span>        </span></span><span>time_start </span><span>=</span><span> time.</span><span>time</span><span>()</span></div></div><div><div><div>6</div></div><div><span><span>        </span></span><span>function</span><span>()</span></div></div><div><div><div>7</div></div><div><span><span>        </span></span><span>time_end </span><span>=</span><span> time.</span><span>time</span><span>()</span></div></div><div><div><div>8</div></div><div><span><span>        </span></span><span>cost_time </span><span>=</span><span> time_end </span><span>-</span><span> time_start</span></div></div><div><div><div>9</div></div><div><span>        </span><span>print</span><span>(</span><span>"花费时间：</span><span>{}</span><span>秒"</span><span><span>.</span><span>format</span><span>(cost_time))</span></span></div></div><div><div><div>10</div></div><div><span>    </span><span>return</span><span> wrapper</span></div></div><div><div><div>11</div></div><div>
</div></div><div><div><div>12</div></div><div><span>@timer</span></div></div><div><div><div>13</div></div><div><span>def</span><span> </span><span>func1</span><span>():</span></div></div><div><div><div>14</div></div><div><span>    </span><span>print</span><span>(</span><span>'我是一个函数1'</span><span>)</span></div></div></code></pre><div><div></div><div></div></div></figure></div><section><h2>关键疑问 1：函数内部为什么还能定义函数？<a href="#关键疑问-1函数内部为什么还能定义函数"><span>#</span></a></h2><p>Python 支持<strong>嵌套函数</strong>。内层函数可以使用外层函数传进来的变量，这就是闭包的典型表现。</p><p>这里可以这样拆：</p><ul>
<li><code>timer</code>：外层工厂函数</li>
<li><code>wrapper</code>：包装函数，负责新增逻辑（计时）</li>
</ul></section><section><h2>关键疑问 2：看不见 <code>wrapper()</code> 调用，它是怎么运行的？<a href="#关键疑问-2看不见-wrapper-调用它是怎么运行的"><span>#</span></a></h2><p>把语法糖展开：</p><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>@timer</span></div></div><div><div><div>2</div></div><div><span>def</span><span> </span><span>func1</span><span>():</span></div></div><div><div><div>3</div></div><div><span>    </span><span>pass</span></div></div><div><div><div>4</div></div><div>
</div></div><div><div><div>5</div></div><div><span># 等价于</span></div></div><div><div><div>6</div></div><div>
</div></div><div><div><div>7</div></div><div><span>def</span><span> </span><span>func1</span><span>():</span></div></div><div><div><div>8</div></div><div><span>    </span><span>pass</span></div></div><div><div><div>9</div></div><div>
</div></div><div><div><div>10</div></div><div><span><span>func1 </span><span>=</span><span> </span><span>timer</span><span>(func1)</span></span></div></div></code></pre><div><div></div><div></div></div></figure></div><p>执行过程可以分成三步：</p><ol>
<li><strong>代码加载阶段（定义时）</strong>：执行 <code>timer(原始 func1)</code>，生成 <code>wrapper</code> 函数对象；</li>
<li><strong>变量覆盖</strong>：名字 <code>func1</code> 不再指向原始函数，而是指向 <code>wrapper</code>；</li>
<li><strong>调用阶段</strong>：执行 <code>func1()</code>，实际上就是执行 <code>wrapper()</code>。</li>
</ol></section><section><h2>关键疑问 3：为什么是 <code>return wrapper</code>，不能写 <code>return wrapper()</code>？<a href="#关键疑问-3为什么是-return-wrapper不能写-return-wrapper"><span>#</span></a></h2><p>这两者完全不同：</p><ul>
<li><code>return wrapper</code>：返回<strong>函数对象本身</strong>，后续可以随时调用；</li>
<li><code>return wrapper()</code>：立刻执行函数，返回执行结果（大多是 <code>None</code>）。</li>
</ul><p>错误示范：</p><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>def</span><span> </span><span>deco</span><span>(</span><span>func</span><span>):</span></div></div><div><div><div>2</div></div><div><span>    </span><span>def</span><span> </span><span>inner</span><span>():</span></div></div><div><div><div>3</div></div><div><span>        </span><span>print</span><span>(</span><span>"包装启动"</span><span>)</span></div></div><div><div><div>4</div></div><div><span><span>        </span></span><span>func</span><span>()</span></div></div><div><div><div>5</div></div><div><span>    </span><span>return</span><span><span> </span><span>inner</span><span>()  </span></span><span># 当场执行 inner，返回 None</span></div></div><div><div><div>6</div></div><div>
</div></div><div><div><div>7</div></div><div><span>@deco</span></div></div><div><div><div>8</div></div><div><span>def</span><span> </span><span>demo</span><span>():</span></div></div><div><div><div>9</div></div><div><span>    </span><span>print</span><span>(</span><span>"demo执行"</span><span>)</span></div></div><div><div><div>10</div></div><div>
</div></div><div><div><div>11</div></div><div><span><span>demo</span><span>()  </span></span><span># 报错 TypeError: 'NoneType' object is not callable</span></div></div></code></pre><div><div></div><div></div></div></figure></div><hr /></section></section>
<section><h1>3. 随堂测试复盘：易错的时序考点<a href="#3-随堂测试复盘易错的时序考点"><span>#</span></a></h1><section><h2>考题 1<a href="#考题-1"><span>#</span></a></h2><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>def</span><span> </span><span>deco</span><span>(</span><span>func</span><span>):</span></div></div><div><div><div>2</div></div><div><span>    </span><span>def</span><span> </span><span>inner</span><span>():</span></div></div><div><div><div>3</div></div><div><span>        </span><span>print</span><span>(</span><span>"开始"</span><span>)</span></div></div><div><div><div>4</div></div><div><span><span>        </span></span><span>func</span><span>()</span></div></div><div><div><div>5</div></div><div><span>        </span><span>print</span><span>(</span><span>"结束"</span><span>)</span></div></div><div><div><div>6</div></div><div><span>    </span><span>return</span><span> inner</span></div></div><div><div><div>7</div></div><div>
</div></div><div><div><div>8</div></div><div><span>@deco</span></div></div><div><div><div>9</div></div><div><span>def</span><span> </span><span>hello</span><span>():</span></div></div><div><div><div>10</div></div><div><span>    </span><span>print</span><span>(</span><span>"Hello"</span><span>)</span></div></div><div><div><div>11</div></div><div>
</div></div><div><div><div>12</div></div><div><span><span>hello</span><span>()</span></span></div></div></code></pre><div><div></div><div></div></div></figure></div><p>输出：</p><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>开始</span></div></div><div><div><div>2</div></div><div><span>Hello</span></div></div><div><div><div>3</div></div><div><span>结束</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p>理解方式：<code>hello = deco(hello)</code>，调用 <code>hello()</code> 时实际执行的是 <code>inner()</code>。</p></section><section><h2>考题 2：时序大坑<a href="#考题-2时序大坑"><span>#</span></a></h2><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>def</span><span> </span><span>deco</span><span>(</span><span>func</span><span>):</span></div></div><div><div><div>2</div></div><div><span>    </span><span>print</span><span>(</span><span>"进入deco"</span><span>)</span></div></div><div><div><div>3</div></div><div><span>    </span><span>def</span><span> </span><span>inner</span><span>():</span></div></div><div><div><div>4</div></div><div><span>        </span><span>print</span><span>(</span><span>"inner运行"</span><span>)</span></div></div><div><div><div>5</div></div><div><span><span>        </span></span><span>func</span><span>()</span></div></div><div><div><div>6</div></div><div><span>    </span><span>return</span><span> inner</span></div></div><div><div><div>7</div></div><div>
</div></div><div><div><div>8</div></div><div><span>@deco</span></div></div><div><div><div>9</div></div><div><span>def</span><span> </span><span>test</span><span>():</span></div></div><div><div><div>10</div></div><div><span>    </span><span>print</span><span>(</span><span>"执行test本体"</span><span>)</span></div></div><div><div><div>11</div></div><div>
</div></div><div><div><div>12</div></div><div><span>print</span><span>(</span><span>"====分割线===="</span><span>)</span></div></div><div><div><div>13</div></div><div><span><span>test</span><span>()</span></span></div></div></code></pre><div><div></div><div></div></div></figure></div><p>正确输出顺序：</p><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>进入deco</span></div></div><div><div><div>2</div></div><div><span><span>====</span><span>分割线</span><span>====</span></span></div></div><div><div><div>3</div></div><div><span>inner运行</span></div></div><div><div><div>4</div></div><div><span>执行test本体</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p>核心知识点：<strong><code>@</code> 装饰器在函数定义阶段就执行外层代码，不等你调用函数。</strong></p><hr /></section></section>
<section><h1>4. 知识迁移到 FastAPI<a href="#4-知识迁移到-fastapi"><span>#</span></a></h1><p>回到最开始的代码：</p><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span><span>@app</span><span>.</span><span>get</span></span><span>(</span><span>"/api/profile"</span><span>)</span></div></div><div><div><div>2</div></div><div><span>def</span><span> </span><span>get_profile</span><span>():</span></div></div><div><div><div>3</div></div><div><span>    </span><span>return</span><span> profile</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p>它可以展开成：</p><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>def</span><span> </span><span>get_profile</span><span>():</span></div></div><div><div><div>2</div></div><div><span>    </span><span>return</span><span> profile</span></div></div><div><div><div>3</div></div><div>
</div></div><div><div><div>4</div></div><div><span># 1. app.get 先执行，得到一个装饰器</span></div></div><div><div><div>5</div></div><div><span><span>decorator </span><span>=</span><span> app.</span><span>get</span><span>(</span></span><span>"/api/profile"</span><span>)</span></div></div><div><div><div>6</div></div><div>
</div></div><div><div><div>7</div></div><div><span># 2. 使用装饰器包装原始函数</span></div></div><div><div><div>8</div></div><div><span><span>get_profile </span><span>=</span><span> </span><span>decorator</span><span>(get_profile)</span></span></div></div></code></pre><div><div></div><div></div></div></figure></div><section><h2>带参数的装饰器：<a href="#带参数的装饰器"><span>#</span></a></h2><ul>
<li><code>app.get("路径")</code> 先传入配置；</li>
<li>然后返回一个专属装饰器；</li>
<li>再由这个装饰器去包装业务函数。</li>
</ul></section><section><h2>执行流程<a href="#执行流程"><span>#</span></a></h2><ol>
<li>项目启动加载代码：只是在注册路由表；</li>
<li>浏览器发起 HTTP 请求访问接口时，FastAPI 自动调用包装后的函数；</li>
<li>框架自动把 <code>return</code> 的字典转成 JSON，并封装成 HTTP 响应。</li>
</ol><hr /><p>我最后记住的是，装饰器的核心就是 <code>@deco</code> 等价于 <code>f = deco(f)</code>。它不是在改原函数的代码，而是把原函数交给另一个函数包一层，再把新的函数返回回来。</p></section></section>]]></content>
    </entry>
    <entry>
      <id>https://www.celyn-blog.xyz/posts/claude-code-cli-setup/</id>
      <title type="text">【Claude_Code_CLI安装】代理配置-本地模型-插件生态</title>
      <published>2026-05-23T00:00:00.000Z</published>
      <updated>2026-05-23T00:00:00.000Z</updated>
      <author><name>Celyn</name></author>
      <link rel="alternate" href="https://www.celyn-blog.xyz/posts/claude-code-cli-setup/"/>
      <summary type="text">Claude Code CLI完整安装指南，涵盖代理配置、DeepSeek/本地模型接入、内网穿透及插件生态。</summary>
      <content type="html"><![CDATA[<section><h1>1 前言<a href="#1-前言"><span>#</span></a></h1><blockquote><p>最近在使用Claude Code (简称 cc )帮助我进行日常学习、项目开发，本文记录下 Claude Code CLI 的安装、DeepSeek 模型配置、llama.cpp 本地模型内网穿透后进行公网连接的操作，最后简单提及下 skill、plugin 等功能的安装，后续也会更新下 cc 的一些进阶操作。</p></blockquote><hr /></section>
<section><h1>2 安装<a href="#2-安装"><span>#</span></a></h1><p>先安装 <a href="https://git-scm.com/book/en/v2/Getting-Started-Installing-Git" target="_blank">Git</a> 与 <a href="https://nodejs.org/zh-cn" target="_blank">Node.js</a>。</p><div><figure><figcaption><span></span><span>Terminal window</span></figcaption><pre><code><div><div><div>1</div></div><div><span># 国内用户（winget 源）</span></div></div><div><div><div>2</div></div><div><span>winget install Anthropic.ClaudeCode</span></div></div><div><div><div>3</div></div><div>
</div></div><div><div><div>4</div></div><div><span># 官方渠道（npm 全局安装）</span></div></div><div><div><div>5</div></div><div><span><span>npm install </span><span>-</span><span>g </span></span><span>@anthropic</span><span><span>-</span><span>ai</span><span>/</span><span>claude</span><span>-</span><span>code</span></span></div></div></code></pre><div><div></div><div></div></div></figure></div><p>安装完成后，在终端输入 <code>claude</code> 即可启动。</p><hr /></section>
<section><h1>3 <code>cc switch</code> 多模型配置切换<a href="#3-cc-switch-多模型配置切换"><span>#</span></a></h1><p>下载：<a href="https://www.ccswitch.io/zh/" target="_blank">CC Switch</a></p><p><code>cc switch</code> 用于在多个模型配置之间快速切换，并对齐模型所需格式。</p><p><img alt="" loading="lazy" width="902" height="632" src="/_astro/Pasted-image-20260523155037.CJHAmUBy_Z1Xv6JN.webp" /></p></section>
<section><h1>4 配置 DeepSeek 兼容端点<a href="#4-配置-deepseek-兼容端点"><span>#</span></a></h1><p>Claude Code 默认走 Anthropic 官方 API，但也可以通过自定义端点接入 DeepSeek 等兼容模型，降低成本。</p><section><h2>4.1 获取 API Key<a href="#41-获取-api-key"><span>#</span></a></h2><p>前往 <a href="https://platform.deepseek.com/usage" target="_blank">DeepSeek 开放平台</a> 申请 API Key，然后在 Claude Code 中配置。</p></section><section><h2>4.2 配置自定义端点<a href="#42-配置自定义端点"><span>#</span></a></h2><p>使用 <code>cc switch</code> 创建一套新配置，填入 DeepSeek 的 API 地址和 Key 即可。</p><blockquote><p>注意：DeepSeek 兼容的是 OpenAI API 格式，Claude Code 内部已做适配，直接使用社区提供的端点地址即可。</p></blockquote><p><img alt="" loading="lazy" width="902" height="632" src="/_astro/Pasted-image-20260523154919.DB8hpg0I_Z1dnB5F.webp" /></p><p><img alt="" loading="lazy" width="889" height="625" src="/_astro/Pasted-image-20260523164457.D19q9nWH_ZgAAAH.webp" />
点击获取模型列表，再点击一键设置即可
记得勾选1M，表示可以使用1百万token上下文，当然前提是你的模型支持(比如deepseek)</p><hr /></section></section>
<section><h1>5 接入本地模型（llama.cpp）<a href="#5-接入本地模型llamacpp"><span>#</span></a></h1><p>llama.cpp 默认提供的是 OpenAI 兼容 API，而 Claude Code 走的是 Anthropic 协议。直接在 <code>settings.json</code> 里填 llama.cpp 的地址是不行的，需要配合 <code>cc switch</code> 的自定义端点功能来桥接。</p><section><h2>5.1 内网穿透（ngrok）<a href="#51-内网穿透ngrok"><span>#</span></a></h2><p>使用 <a href="https://ngrok.com/" target="_blank">ngrok</a> 进行内网穿透，下载客户端即可使用</p><p>如果 llama.cpp 跑在本地，想让外网也能访问（比如在另一台机器上用 Claude Code），可以用 ngrok 做内网穿透，把本地端口暴露到公网：</p><div><figure><figcaption><span></span><span>Terminal window</span></figcaption><pre><code><div><div><div>1</div></div><div><span>ngrok</span><span> </span><span>http</span><span> </span><span>8080</span></div></div></code></pre><div><div></div><div></div></div></figure></div></section><section><h2>5.2 在 Claude Code 中连接 llama.cpp<a href="#52-在-claude-code-中连接-llamacpp"><span>#</span></a></h2><p>配置步骤如下：</p><ol>
<li><strong>如果 llama.cpp 没有配置 API Key</strong>，<code>cc switch</code> 处的 API Key 可以<strong>随意填写</strong>，不会校验。</li>
<li>输入请求地址后，点击<strong>管理与测速</strong>，测试是否连通。</li>
<li>点击<strong>高级选项</strong>展开。</li>
<li>点击<strong>获取模型列表</strong>（上方弹窗会显示获取到的模型数量）。</li>
<li>在模型下拉列表中选择你的模型。</li>
<li>点击<strong>一键设置</strong>。</li>
<li><strong>保存</strong>配置。</li>
</ol><blockquote><p>注意：请保持 <strong>API 格式</strong>与<strong>认证字段</strong>不变，不要随意修改。</p></blockquote><p>配置完成后，在任意文件夹打开终端输入 <code>claude</code>，测试是否正常启用。</p><p><img alt="" loading="lazy" width="902" height="632" src="/_astro/Pasted-image-20260523164024.C5Wfa08d_Z1Gxvcx.webp" /></p><p><img alt="" loading="lazy" width="1300" height="832" src="/_astro/Pasted-image-20260523164123.B_ZO7vZ1_Z1NeAig.webp" /></p><p><img alt="" loading="lazy" width="1351" height="832" src="/_astro/Pasted-image-20260523164159.JvM-5td__Z1mOz9M.webp" /></p></section><section><h2>5.3 切换模型<a href="#53-切换模型"><span>#</span></a></h2><p>在 <code>cc switch</code> 界面中，你可以随时在不同配置之间切换——远程大模型干活，本地小模型摸鱼，灵活选择。</p></section><section><h2>5.4 缺点<a href="#54-缺点"><span>#</span></a></h2><p>存在这样一种情景，我需要同时使用多家大模型，但是ccswitch只能选择启用一个供应商。</p><p>而且由于目前deepseek v4仅为文本推理llm，不具备图片理解能力，导致我必须额外配置一个具有视觉的大模型，此时我就必须使用python自己写一个网关脚本，用于对齐agent要求的格式并连接上我视觉模型，当然我们可以将其配置成全局skill，当需要视觉理解时进行调用。</p></section><section><h2>5.5 CC switch实际修改配置<a href="#55-cc-switch实际修改配置"><span>#</span></a></h2><p><img alt="" loading="lazy" width="829" height="479" src="/_astro/Pasted-image-20260523165709.08T39klJ_1a3BVw.webp" /></p><p><img alt="" loading="lazy" width="1026" height="693" src="/_astro/Pasted-image-20260523165830.BeP2GjO6_Zol6Lv.webp" /></p><p>cc switch实际就是建立网关，将baseurl改成自己的端口，再填写供应商的api调用地址和映射模型进行模型调用</p><hr /></section></section>
<section><h1>6 Skill、Plugin、Agent 扩展<a href="#6-skillpluginagent-扩展"><span>#</span></a></h1><p>Claude Code 的真正威力在于可扩展生态。通过插件市场，你可以安装社区提供的 Skill、Plugin 和 Agent，极大扩展能力边界。</p><section><h2>6.1 添加市场<a href="#61-添加市场"><span>#</span></a></h2><p>首先添加 Anthropic 官方市场：</p><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>/plugin marketplace add anthropics/skills</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p>市场地址：<a href="https://github.com/anthropics/skills" target="_blank">https://github.com/anthropics/skills</a>
<img alt="" loading="lazy" width="1225" height="223" src="/_astro/Pasted-image-20260523184257.BdDGTTb7_Z1md6ik.webp" /></p></section><section><h2>6.2 浏览与安装<a href="#62-浏览与安装"><span>#</span></a></h2><p>添加市场后，输入 <code>/plugin</code> 即可浏览所有可用插件。按需安装，最后执行一次插件重载/reload-plugin即可生效。</p><p><img alt="" loading="lazy" width="1924" height="1152" src="/_astro/Pasted-image-20260523184311.CpyukQtY_Zl63tw.webp" /></p><p>安装完成后，右下角状态栏会显示各服务的可用状态。如果某个 MCP 服务启动失败或需要官方验证，也会有相应提示，方便排查。</p><p><img alt="" loading="lazy" width="1873" height="1152" src="/_astro/Pasted-image-20260523184326.BWtRLsVN_1PN2WS.webp" /></p><hr /></section></section>
<section><h1>7 小结<a href="#7-小结"><span>#</span></a></h1>

<table><thead><tr><th>场景</th><th>方案</th></tr></thead><tbody><tr><td>安装使用</td><td>官方地址 或<code>winget</code>安装</td></tr><tr><td>模型接入</td><td>DeepSeek + <code>cc switch</code></td></tr><tr><td>本地模型</td><td>llama.cpp 本地模型 + <code>cc switch</code></td></tr><tr><td>扩展能力</td><td>Marketplace 安装 Skill / Plugin</td></tr></tbody></table><p>折腾一圈下来，Claude Code CLI 就配置好了—— 一套工具，多种后端模型，按需切换。希望这篇笔记能帮你少走弯路。</p></section>]]></content>
    </entry>
    <entry>
      <id>https://www.celyn-blog.xyz/posts/git-collaboration-guide/</id>
      <title type="text">【Git】项目协作开发教程</title>
      <published>2026-05-19T00:00:00.000Z</published>
      <updated>2026-05-19T00:00:00.000Z</updated>
      <author><name>Celyn</name></author>
      <link rel="alternate" href="https://www.celyn-blog.xyz/posts/git-collaboration-guide/"/>
      <summary type="text">GitHub协作开发全流程指南，从Conventional Commit规范到Fork+PR贡献代码的标准化操作。</summary>
      <content type="html"><![CDATA[<section><h1>1 前言<a href="#1-前言"><span>#</span></a></h1><blockquote><p>近期，我在 GitHub 平台参与项目协同开发的过程中，系统掌握了规范的 Git 操作流程。从遵循 Conventional Commit 规范撰写提交信息，到将代码提交至 Fork 后的个人仓库，再到通过 Pull Request（PR）向上游原仓库贡献代码，每一个环节都经过实践打磨，逐步形成了标准化的操作习惯。为了梳理所学、方便后续查阅，同时也为有类似协作需求的开发者提供参考，特此整理这份 Git 操作指南，涵盖从基础配置到协作收尾的全流程，力求步骤清晰、重点突出、可直接使用。</p></blockquote></section>
<section><h1>2 Git 基础全局配置<a href="#2-git-基础全局配置"><span>#</span></a></h1><p>在使用 Git 前，需先配置<strong>全局用户名</strong>和<strong>邮箱</strong>，这是提交代码时的身份标识，仅需配置一次。</p><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span># 配置全局用户名（替换为你的 GitHub 用户名）</span></div></div><div><div><div>2</div></div><div><span>git config --global user.name "Celyn"</span></div></div><div><div><div>3</div></div><div>
</div></div><div><div><div>4</div></div><div><span># 配置全局邮箱（替换为你的 GitHub 绑定邮箱）</span></div></div><div><div><div>5</div></div><div><span>git config --global user.email "1249140039@qq.com"</span></div></div></code></pre><div><div></div><div></div></div></figure></div><section><h2>2.1 验证配置是否成功<a href="#21-验证配置是否成功"><span>#</span></a></h2><p>执行以下命令，查看已配置的用户名和邮箱：</p><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span># 查看用户名</span></div></div><div><div><div>2</div></div><div><span>git config user.name</span></div></div><div><div><div>3</div></div><div>
</div></div><div><div><div>4</div></div><div><span># 查看邮箱</span></div></div><div><div><div>5</div></div><div><span>git config user.email</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p>==注意==：请务必确认好本地 Git 用户配置是否生效。</p><blockquote><p>小故事😢：
我在实验室电脑执行 git push、提交 PR 时，由于疏忽检查，提交记录带入电脑里留存的其他用户信息，导致 pr 时对方被设为共同作者，并且在我 fork 的仓库中显示上传 commit的用户并不是自己。所以一定要提前核对清楚本地个人账号信息，不然你的辛苦付出最后反倒会为他人做了嫁衣。</p></blockquote><p><img alt="" loading="lazy" width="1855" height="1060" src="/_astro/Pasted-image-20260519032430.DhZqRvL1_1Cuq7e.webp" />
注：pr 里面多了个不知名的 xxxu680</p></section><section><h2>2.2 Git 的默认分支：<code>master</code> 还是 <code>main</code><a href="#22-git-的默认分支master-还是-main"><span>#</span></a></h2><p>Git 本地执行 <code>git init</code> 时，默认分支原本是 <code>master</code>；GitHub 后来将新建仓库的默认分支改成了 <code>main</code>。因此，GitHub 会提示先统一本地分支名，再推送：</p><div><figure><figcaption><span></span><span>Terminal window</span></figcaption><pre><code><div><div><div>1</div></div><div><span># 将当前本地分支强制重命名为 main</span></div></div><div><div><div>2</div></div><div><span>git</span><span> </span><span>branch</span><span> </span><span>-M</span><span> </span><span>main</span></div></div><div><div><div>3</div></div><div>
</div></div><div><div><div>4</div></div><div><span># 首次推送，并关联本地 main 与远程 origin/main</span></div></div><div><div><div>5</div></div><div><span>git</span><span> </span><span>push</span><span> </span><span>-u</span><span> </span><span>origin</span><span> </span><span>main</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p>如果不同 AI 开发工具初始化出的分支名不一致，通常是因为部分环境已配置 <code>git config --global init.defaultBranch main</code>，而部分环境仍使用 Git 原本的 <code>master</code>。</p></section></section>
<section><h1>3 Git 代理配置（解决 GitHub 下载慢）<a href="#3-git-代理配置解决-github-下载慢"><span>#</span></a></h1><p>若访问 GitHub 速度缓慢，可配置 <strong>7897 端口代理</strong>（本地常用代理端口），提升克隆、拉取代码的效率。</p><p><img alt="" loading="lazy" width="1854" height="1138" src="/_astro/Pasted-image-20260519031146.BJiCdRq-_9B0kN.webp" /></p><section><h2>3.1 设置 7897 端口代理<a href="#31-设置-7897-端口代理"><span>#</span></a></h2><p>适用于 http/https 协议下载 GitHub</p><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span># 配置 HTTP 代理</span></div></div><div><div><div>2</div></div><div><span>git config --global http.proxy http://127.0.0.1:7897</span></div></div><div><div><div>3</div></div><div>
</div></div><div><div><div>4</div></div><div><span># 配置 HTTPS 代理</span></div></div><div><div><div>5</div></div><div><span>git config --global https.proxy https://127.0.0.1:7897</span></div></div></code></pre><div><div></div><div></div></div></figure></div></section><section><h2>3.2 验证代理是否生效<a href="#32-验证代理是否生效"><span>#</span></a></h2><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span># 查看 HTTP 代理</span></div></div><div><div><div>2</div></div><div><span>git config --global --get http.proxy</span></div></div><div><div><div>3</div></div><div>
</div></div><div><div><div>4</div></div><div><span># 查看 HTTPS 代理</span></div></div><div><div><div>5</div></div><div><span>git config --global --get https.proxy</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p><img alt="" loading="lazy" width="1098" height="560" src="/_astro/Pasted-image-20260519031247.B_ojV0ic_ZWm9Nl.webp" /></p></section><section><h2>3.3 取消代理（备用）<a href="#33-取消代理备用"><span>#</span></a></h2><p>若无需代理，可执行以下命令清除配置：</p><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span># 取消 HTTP 代理</span></div></div><div><div><div>2</div></div><div><span>git config --global --unset http.proxy</span></div></div><div><div><div>3</div></div><div>
</div></div><div><div><div>4</div></div><div><span># 取消 HTTPS 代理</span></div></div><div><div><div>5</div></div><div><span>git config --global --unset https.proxy</span></div></div></code></pre><div><div></div><div></div></div></figure></div></section></section>
<section><h1>4 Fork 仓库与本地初始化<a href="#4-fork-仓库与本地初始化"><span>#</span></a></h1><section><h2>4.1 Fork 上游仓库<a href="#41-fork-上游仓库"><span>#</span></a></h2><p>以==我参与开发的项目==为例：
在 GitHub 上将目标仓库（如 <code>jihe520/MathModelAgent</code>）Fork 到<strong>自己的账号</strong>下，生成个人副本（如 <code>HP-Patience/MathModelAgent</code>）。</p><p><img alt="" loading="lazy" width="2280" height="1281" src="/_astro/Pasted-image-20260519031658.Bwgj7C7R_1GL0vl.webp" /></p></section><section><h2>4.2 本地初始化仓库<a href="#42-本地初始化仓库"><span>#</span></a></h2><section><h3>4.2.1 方式 1：下载 ZIP 压缩包后初始化<a href="#421-方式-1下载-zip-压缩包后初始化"><span>#</span></a></h3><ol>
<li>下载 Fork 后仓库的 <code>.zip</code> 压缩包，解压到本地目录；</li>
<li>进入目录，执行 <code>git init</code> 初始化 Git 仓库；</li>
<li>执行 <code>git branch -M main</code>，确保本地分支名与远程默认分支一致；</li>
<li>关联<strong>自己的远程仓库（origin）</strong> 和<strong>上游原仓库（upstream）</strong>：</li>
</ol><div><figure><figcaption><span></span><span>Terminal window</span></figcaption><pre><code><div><div><div>1</div></div><div><span># 关联自己的 Fork 仓库（origin）</span></div></div><div><div><div>2</div></div><div><span>git</span><span> </span><span>remote</span><span> </span><span>add</span><span> </span><span>origin</span><span> </span><span>https://github.com/HP-Patience/MathModelAgent.git</span></div></div><div><div><div>3</div></div><div>
</div></div><div><div><div>4</div></div><div><span># 关联上游原仓库（upstream，用于同步最新代码）</span></div></div><div><div><div>5</div></div><div><span>git</span><span> </span><span>remote</span><span> </span><span>add</span><span> </span><span>upstream</span><span> </span><span>https://github.com/jihe520/MathModelAgent.git</span></div></div></code></pre><div><div></div><div></div></div></figure></div><ol>
<li>拉取最新代码到本地：</li>
</ol><div><figure><figcaption><span></span><span>Terminal window</span></figcaption><pre><code><div><div><div>1</div></div><div><span>git</span><span> </span><span>pull</span><span> </span><span>origin</span><span> </span><span>main</span></div></div></code></pre><div><div></div><div></div></div></figure></div></section><section><h3>4.2.2 方式 2：直接 Git Clone（推荐）<a href="#422-方式-2直接-git-clone推荐"><span>#</span></a></h3><p><code>git clone</code> 会<strong>自动执行 git init + 关联远程仓库 + 下载完整最新代码</strong>，无需手动初始化和拉取：</p><div><figure><figcaption><span></span><span>Terminal window</span></figcaption><pre><code><div><div><div>1</div></div><div><span># 克隆自己的 Fork 仓库到本地</span></div></div><div><div><div>2</div></div><div><span>git</span><span> </span><span>clone</span><span> </span><span>https://github.com/HP-Patience/MathModelAgent.git</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p><img alt="" loading="lazy" width="1882" height="777" src="/_astro/Pasted-image-20260519031751.C5movFg7_Z1wBuIB.webp" /></p></section></section></section>
<section><h1>5 分支开发与代码提交<a href="#5-分支开发与代码提交"><span>#</span></a></h1><section><h2>5.1 创建开发分支<a href="#51-创建开发分支"><span>#</span></a></h2><p><strong>永远不要直接在 main 分支修改代码</strong>，需创建独立开发分支：</p><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span># 创建并切换到 myfeat 分支（分支名可自定义）</span></div></div><div><div><div>2</div></div><div><span>git checkout -b myfeat</span></div></div></code></pre><div><div></div><div></div></div></figure></div></section><section><h2>5.2 修改代码与提交<a href="#52-修改代码与提交"><span>#</span></a></h2><p>在 <code>myfeat</code> 分支完成代码修改后，提交变更：</p><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span># 添加所有修改文件</span></div></div><div><div><div>2</div></div><div><span>git add .</span></div></div><div><div><div>3</div></div><div>
</div></div><div><div><div>4</div></div><div><span># 提交代码（添加清晰的提交信息）</span></div></div><div><div><div>5</div></div><div><span>git commit -m "feat: 完成XX功能开发"</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p>==注意==：请使用 <strong>conventional commit</strong> 进行提交内容说明</p></section></section>
<section><h1>6 同步上游更新与推送代码<a href="#6-同步上游更新与推送代码"><span>#</span></a></h1><section><h2>6.1 场景 1：上游仓库无新提交<a href="#61-场景-1上游仓库无新提交"><span>#</span></a></h2><p>若原仓库（upstream）未更新代码，直接将本地分支推送到自己的远程仓库：</p><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span># 将 myfeat 分支推送到自己的 GitHub 仓库</span></div></div><div><div><div>2</div></div><div><span>git push origin myfeat</span></div></div></code></pre><div><div></div><div></div></div></figure></div></section><section><h2>6.2 场景 2：上游仓库有新提交<a href="#62-场景-2上游仓库有新提交"><span>#</span></a></h2><p>若原仓库已更新，需<strong>先同步上游代码</strong>，再推送，避免冲突：</p><ol>
<li>切换回 main 分支，拉取上游最新代码：</li>
</ol><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>git checkout main</span></div></div><div><div><div>2</div></div><div><span>git pull upstream main</span></div></div></code></pre><div><div></div><div></div></div></figure></div><ol>
<li>切换回开发分支，将上游代码合并到当前分支：</li>
</ol><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>git checkout myfeat</span></div></div><div><div><div>2</div></div><div><span>git rebase main</span></div></div></code></pre><div><div></div><div></div></div></figure></div><ol>
<li>若出现 <strong>rebase 冲突</strong>，手动修改冲突文件后，继续 rebase；</li>
<li>无冲突或冲突解决后，<strong>强制推送</strong>到自己的远程仓库（必须加 <code>-f</code>）：</li>
</ol><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>git push -f origin myfeat</span></div></div></code></pre><div><div></div><div></div></div></figure></div></section><section><h2>6.3 提交 PR 到上游仓库<a href="#63-提交-pr-到上游仓库"><span>#</span></a></h2><ol>
<li>进入自己的 GitHub 仓库（<code>HP-Patience/MathModelAgent</code>）；</li>
<li>点击 <code>Compare &amp; pull request</code>，选择目标分支；</li>
<li>填写 PR 说明，提交后等待原仓库作者审核即可。</li>
</ol></section></section>
<section><h1>7 PR 被合并后：同步代码 + 清理分支<a href="#7-pr-被合并后同步代码--清理分支"><span>#</span></a></h1><p>当你的 PR 被上游仓库<strong>合并完成</strong>后，执行以下命令同步代码并清理无用分支：</p><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span># 1. 切换到本地 main 分支</span></div></div><div><div><div>2</div></div><div><span>git checkout main</span></div></div><div><div><div>3</div></div><div>
</div></div><div><div><div>4</div></div><div><span># 2. 拉取上游最新代码（包含你刚合并的功能）</span></div></div><div><div><div>5</div></div><div><span>git pull upstream main</span></div></div><div><div><div>6</div></div><div>
</div></div><div><div><div>7</div></div><div><span># 3. 删除本地已完成的开发分支（强制删除）</span></div></div><div><div><div>8</div></div><div><span>git branch -D myfeat</span></div></div><div><div><div>9</div></div><div>
</div></div><div><div><div>10</div></div><div><span># 4.（可选）同步到你自己的远程 main 分支</span></div></div><div><div><div>11</div></div><div><span>git push origin main</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p><img alt="" loading="lazy" width="2280" height="1281" src="/_astro/Pasted-image-20260519031920.XZYipsba_Z1KoKpT.webp" /></p><hr /><section><h2>7.1 常见问题提示<a href="#71-常见问题提示"><span>#</span></a></h2><ol>
<li>代理报错 <code>URL拼写可能存在错误</code>：检查代理地址是否为 <code>127.0.0.1:7897</code>，确认本地代理服务已启动；</li>
<li>Rebase 冲突：打开冲突文件，删除冲突标记（<code>&lt;&lt;&lt;&lt;&lt;&lt;&lt;</code>/<code>=======</code>/<code>&gt;&gt;&gt;&gt;&gt;&gt;&gt;</code>），保留正确代码后重新执行；</li>
<li>推送失败：确认分支权限，非 main 分支无需强制推送，更新上游后务必加 <code>-f</code> 强制推送。</li>
<li>删除分支报错：使用大写 <code>-D</code> 强制删除已合并完成的开发分支。</li>
</ol><hr /></section></section>
<section><h1>8 总结<a href="#8-总结"><span>#</span></a></h1><p>本文介绍了<strong>从配置 → 克隆 → 开发 → 提交 → 同步 → 提PR → 清理</strong>的<strong>完整闭环 Git 协作流程</strong>，所有命令都是团队开源协作最标准的用法，可以直接使用。</p></section>]]></content>
    </entry>
    <entry>
      <id>https://www.celyn-blog.xyz/posts/n8n-wsl2-docker-deploy/</id>
      <title type="text">【n8n】基于WSL2-Docker完整部署教程</title>
      <published>2026-05-12T00:00:00.000Z</published>
      <updated>2026-05-12T00:00:00.000Z</updated>
      <author><name>Celyn</name></author>
      <link rel="alternate" href="https://www.celyn-blog.xyz/posts/n8n-wsl2-docker-deploy/"/>
      <summary type="text">基于WSL2+Docker一键部署开源自动化工作流工具n8n，实现低代码可视化流程编排与定时任务。</summary>
      <content type="html"><![CDATA[<section><h1>1 引言：为什么使用 n8n<a href="#1-引言为什么使用-n8n"><span>#</span></a></h1><blockquote><p>n8n 是一款<strong>开源免费低代码自动化工作流工具</strong>，支持可视化拖拽、对接海量第三方服务（数据库、企业微信、飞书、网盘、接口等），可自建部署、数据私有不托管，搭配 WSL2 + Docker 一键部署，轻量化不占用 Windows 主环境，方便日常自动化流程搭建、定时任务、数据同步等场景。</p></blockquote></section>
<section><h1>2 WSL 下载配置<a href="#2-wsl-下载配置"><span>#</span></a></h1><section><h2>2.1 WSL 内核更新<a href="#21-wsl-内核更新"><span>#</span></a></h2><p>在 CMD/PowerShell 里输入：</p><div><figure><figcaption><span></span><span>Terminal window</span></figcaption><pre><code><div><div><div>1</div></div><div><span>wsl</span><span> --update</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p>等待自动下载安装 WSL2 内核，跑完就好了。</p></section><section><h2>2.2 手动安装 Ubuntu22.04（清华镜像离线导入）<a href="#22-手动安装-ubuntu2204清华镜像离线导入"><span>#</span></a></h2><p>使用<a href="https://mirrors.tuna.tsinghua.edu.cn/ubuntu-cloud-images/jammy/current/" target="_blank">清华镜像源</a>进行下载，点击该链接后：
如果你电脑是 Windows 64 位，下滑找到<code>jammy-server-cloudimg-amd64-root.tar.xz</code>下载即可</p><ol>
<li>
<p>下载好后操作把文件放到：<code>F:\wsl\</code>，没有文件夹自行创建即可</p>
</li>
<li>
<p>接着打开 CMD 或者 PowerShell创建文件夹，输入：</p>
</li>
</ol><div><figure><figcaption><span></span><span>Terminal window</span></figcaption><pre><code><div><div><div>1</div></div><div><span>mkdir</span><span> F:\wsl\Ubuntu2204</span></div></div></code></pre><div><div></div><div></div></div></figure></div><ol>
<li>接着导入安装，运行：</li>
</ol><div><figure><figcaption><span></span><span>Terminal window</span></figcaption><pre><code><div><div><div>1</div></div><div><span>wsl</span><span> --import Ubuntu-22.04 F:\wsl\Ubuntu2204 F:\wsl\jammy-server-cloudimg-amd64-root.tar.xz --version </span><span>2</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p>等着跑完就行，几分钟搞定，直接装在 <strong>F 盘</strong>。</p></section><section><h2>2.3 常见报错处理：<a href="#23-常见报错处理"><span>#</span></a></h2><section><h3>2.3.1 开启系统必要功能（管理员 PowerShell 逐条执行）<a href="#231-开启系统必要功能管理员-powershell-逐条执行"><span>#</span></a></h3><div><figure><figcaption><span></span><span>Terminal window</span></figcaption><pre><code><div><div><div>1</div></div><div><span>dism.exe</span><span><span> </span><span>/</span><span>online </span><span>/</span></span><span>enable-feature</span><span><span> </span><span>/</span><span>featurename:Microsoft</span><span>-</span><span>Windows</span><span>-</span><span>Subsystem</span><span>-</span><span>Linux </span><span>/</span><span>all </span><span>/</span><span>norestart</span></span></div></div><div><div><div>2</div></div><div><span>dism.exe</span><span><span> </span><span>/</span><span>online </span><span>/</span></span><span>enable-feature</span><span><span> </span><span>/</span><span>featurename:VirtualMachinePlatform </span><span>/</span><span>all </span><span>/</span><span>norestart</span></span></div></div><div><div><div>3</div></div><div><span>dism.exe</span><span><span> </span><span>/</span><span>online </span><span>/</span></span><span>enable-feature</span><span><span> </span><span>/</span><span>featurename:Hyper</span><span>-</span><span>V</span><span>-</span><span>Online </span><span>/</span><span>all </span><span>/</span><span>norestart</span></span></div></div></code></pre><div><div></div><div></div></div></figure></div></section><section><h3>2.3.2 图形化检查 Windows 功能<a href="#232-图形化检查-windows-功能"><span>#</span></a></h3><ol>
<li>按下 <code>Win + R</code> 输入 <code>optionalfeatures.exe</code> 回车</li>
<li>查看是否勾选下面 3 个，若无则<strong>打勾</strong>：</li>
</ol><ul>
<li>✅ Windows 子系统用于 Linux</li>
<li>✅ 虚拟机平台</li>
<li>✅ Hyper-V（能看到就勾）</li>
</ul><p>确定后<strong>重启电脑</strong>。</p><p><img alt="" loading="lazy" width="572" height="903" src="/_astro/Pastedimage20260512121830.ZRMqFVgk_Z1BzqfY.webp" /></p></section><section><h3>2.3.3 开启 CPU 虚拟化<a href="#233-开启-cpu-虚拟化"><span>#</span></a></h3><p>重启开机马上按 <strong>Del / F2 / F1</strong> 进 BIOS：</p><ul>
<li>Intel 找：<strong>Intel VT-x</strong> 设为 Enabled</li>
<li>AMD 找：<strong>AMD-V / SVM Mode</strong> 设为 Enabled</li>
</ul><p>保存退出重启。</p></section><section><h3>2.3.4 重新执行导入命令<a href="#234-重新执行导入命令"><span>#</span></a></h3><div><figure><figcaption><span></span><span>Terminal window</span></figcaption><pre><code><div><div><div>1</div></div><div><span><span>wsl </span><span>--</span><span>import Ubuntu</span><span>-</span></span><span>22.04</span><span><span> F:\wsl\Ubuntu2204 F:\wsl\jammy</span><span>-</span><span>server</span><span>-</span><span>cloudimg</span><span>-</span><span>amd64</span><span>-</span><span>root.tar.xz </span><span>--</span><span>version </span></span><span>2</span></div></div></code></pre><div><div></div><div></div></div></figure></div></section></section></section>
<section><h1>3 Docker下载与配置<a href="#3-docker下载与配置"><span>#</span></a></h1><section><h2>3.1 Docker Desktop下载<a href="#31-docker-desktop下载"><span>#</span></a></h2><p><a href="https://www.docker.com/products/docker-desktop/" target="_blank">Docker Desktop下载链接</a></p></section><section><h2>3.2 Docker Resources<a href="#32-docker-resources"><span>#</span></a></h2><section><h3>3.2.1 Advanced 设置<a href="#321-advanced-设置"><span>#</span></a></h3><p>修改 <code>Disk image location</code>，把 Docker 存储路径迁移到<strong>非 C 盘</strong>。</p><p><img alt="" loading="lazy" width="749" height="422" src="/_astro/Pastedimage20260512114819.mfXjjWNI_Z1sawBO.webp" /></p></section></section><section><h2>3.3 WSL integration 配置<a href="#33-wsl-integration-配置"><span>#</span></a></h2><p>开启对应 <code>Ubuntu-22.04</code> 的 WSL 集成，让 Docker 与 WSL 互通</p><p><img alt="" loading="lazy" width="631" height="382" src="/_astro/Pastedimage20260512114718.BUpCTkHm_Z16hEqL.webp" /></p></section><section><h2>3.4 Docker Engine 镜像加速配置<a href="#34-docker-engine-镜像加速配置"><span>#</span></a></h2><p>粘贴以下完整配置，配置国内镜像源加速拉取：</p><div><div><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>{</span></div></div><div><div><div>2</div></div><div><span><span>  </span></span><span>"builder": {</span></div></div><div><div><div>3</div></div><div><span><span>    </span></span><span>"gc": {</span></div></div><div><div><div>4</div></div><div><span><span>      </span></span><span>"defaultKeepStorage": "20GB",</span></div></div><div><div><div>5</div></div><div><span><span>      </span></span><span>"enabled": true</span></div></div><div><div><div>6</div></div><div><span><span>    </span></span><span>}</span></div></div><div><div><div>7</div></div><div><span><span>  </span></span><span>},</span></div></div><div><div><div>8</div></div><div><span><span>  </span></span><span>"experimental": false,</span></div></div><div><div><div>9</div></div><div><span><span>  </span></span><span>"features": {</span></div></div><div><div><div>10</div></div><div><span><span>    </span></span><span>"buildkit": true</span></div></div><div><div><div>11</div></div><div><span><span>  </span></span><span>},</span></div></div><div><div><div>12</div></div><div><span><span>  </span></span><span>"registry-mirrors": [</span></div></div><div><div><div>13</div></div><div><span><span>    </span></span><span>"https://docker.1ms.run",</span></div></div><div><div><div>14</div></div><div><span><span>    </span></span><span>"https://docker.xuanyuan.me",</span></div></div><div><div><div>15</div></div><div><span><span>    </span></span><span>"https://registry-1.docker.io"</span></div></div><div><div><div>16</div></div><div><span><span>  </span></span><span>]</span></div></div><div><div><div>17</div></div><div><span>}</span></div></div></code></pre><div><div></div><div></div></div></figure><div></div></div><span>展开</span><span>收起</span></div></div><p>配置后重启 Docker 生效。
<img alt="" loading="lazy" width="1270" height="720" src="/_astro/Pastedimage20260512114943.CopKY3Pb_NqwDS.webp" /></p></section></section>
<section><h1>4 n8n 部署与使用<a href="#4-n8n-部署与使用"><span>#</span></a></h1><section><h2>4.1 Docker 部署启动 n8n<a href="#41-docker-部署启动-n8n"><span>#</span></a></h2><p>在bash中输入docker命令：</p><ol>
<li>创建持久化数据卷</li>
</ol><div><figure><figcaption><span></span><span>Terminal window</span></figcaption><pre><code><div><div><div>1</div></div><div><span>docker</span><span> </span><span>volume</span><span> </span><span>create</span><span> </span><span>n8n_data</span></div></div></code></pre><div><div></div><div></div></div></figure></div><ol>
<li>启动 n8n 容器（时区设为上海、端口映射、数据持久化）</li>
</ol><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>docker run -d ` --name n8n ` -p 5678:5678 ` -e GENERIC_TIMEZONE="Asia/Shanghai" ` -e TZ="Asia/Shanghai" ` -v n8n_data:/home/node/.n8n ` n8nio/n8n</span></div></div></code></pre><div><div></div><div></div></div></figure></div><ol>
<li>访问地址</li>
</ol><p>浏览器打开：<a href="http://localhost:5678/" target="_blank">http://localhost&lt;5678&gt;/</a></p><p><img alt="" loading="lazy" width="831" height="305" src="/_astro/Pastedimage20260512115338.CR6rNQS8_5hVCc.webp" /></p><p>同时在docker desktop中的Containers中出现该容器</p><p><img alt="" loading="lazy" width="1270" height="720" src="/_astro/Pastedimage20260512115207.BeSK9IIL_2jqLsn.webp" /></p></section><section><h2>4.2 n8n 的webui界面操作<a href="#42-n8n-的webui界面操作"><span>#</span></a></h2><p><img alt="" loading="lazy" width="1920" height="992" src="/_astro/Pastedimage20260512115650.p4SPuvfY_PpbeD.webp" /></p><p><img alt="" loading="lazy" width="384" height="208" src="/_astro/Pastedimage20260512115812.5hAXY26R_1BMk15.webp" /></p><p><img alt="" loading="lazy" width="1710" height="914" src="/_astro/Pastedimage20260512115832.DFYMjBDW_1Mlp4b.webp" /></p></section></section>
<section><h1>5 参考文档：<a href="#5-参考文档"><span>#</span></a></h1><ol>
<li><a href="https://docs.n8n.io/hosting/installation/docker/#prerequisites" target="_blank">Docker | n8n Docs</a></li>
<li><a href="https://github.com/n8n-io/n8n" target="_blank">GitHub | n8n-io/n8n</a></li>
</ol></section>]]></content>
    </entry>
    <entry>
      <id>https://www.celyn-blog.xyz/posts/neural-network-training-and-optimization/</id>
      <title type="text">神经网络优化核心：鞍点而非局部极小值</title>
      <published>2026-05-10T00:00:00.000Z</published>
      <updated>2026-09-20T00:00:00.000Z</updated>
      <author><name>Celyn</name></author>
      <link rel="alternate" href="https://www.celyn-blog.xyz/posts/neural-network-training-and-optimization/"/>
      <summary type="text">深入解析神经网络梯度下降停滞的真正原因——鞍点问题，结合Hessian矩阵分析高维损失景观特性。</summary>
      <content type="html"><![CDATA[<section><h1>1 前言<a href="#1-前言"><span>#</span></a></h1><blockquote><p>当模型在训练集上损失居高不下，且增加模型复杂度后损失未降反升时，问题通常不在于模型 bias，而在于优化过程本身——梯度下降易在高维损失景观中被困于鞍点，本文结合 Hessian 矩阵与实验证据，梳理相关核心结论。</p></blockquote></section>
<section><h1>2 梯度下降收敛停滞的关键原因<a href="#2-梯度下降收敛停滞的关键原因"><span>#</span></a></h1><p>梯度下降过程中，当梯度∇L (θ)=0 时，损失函数值不再变化，优化陷入停滞。造成这一现象的原因并非仅为局部最小值点，还可能是鞍点，两者核心区别如下：</p><ul>
<li>局部最小值点（local minimum point）：梯度为 0，且任意方向移动损失均增大，优化达到局部最优；</li>
<li>鞍点（saddle point）：梯度为 0，存在一对正交方向，沿其中一个方向移动损失增大，沿另一个方向移动损失减小，并非最优解，却会导致梯度下降停滞。</li>
</ul><p>综上，局部最小值点与鞍点的梯度均为 0，这两类梯度为 0 的点统称为驻点。</p><p><img alt="" loading="lazy" width="1379" height="1034" src="/_astro/PixPin_2026-05-10_16-16-44.xrYzV_22_Z1LUMaP.webp" /></p><p><img alt="" loading="lazy" width="1571" height="521" src="/_astro/PixPin_2026-05-10_16-15-32.BH6sRzsT_2jRMBm.webp" />
那么当我们遇到梯度为 0 的驻点时，如何判断其是局部最小值点还是导致优化停滞的鞍点呢？此时需要引入 Hessian 矩阵进行判别：</p></section>
<section><h1>3 Hessian 矩阵与二次型<a href="#3-hessian-矩阵与二次型"><span>#</span></a></h1><p>要判断一个临界点到底是什么，我们需要用到二阶泰勒展开，在临界点 <span><span>θ′\theta'</span><span><span><span></span><span><span>θ</span><span><span><span><span><span><span></span><span><span><span>′</span></span></span></span></span></span></span></span></span></span></span></span> 附近近似损失函数：</p><span><span><span>L(θ)≈L(θ′)+12(θ−θ′)TH(θ−θ′)L(\theta) \approx L(\theta') + \frac{1}{2} (\theta - \theta')^T H (\theta - \theta')</span><span><span><span></span><span>L</span><span>(</span><span>θ</span><span>)</span><span></span><span>≈</span><span></span></span><span><span></span><span>L</span><span>(</span><span><span>θ</span><span><span><span><span><span><span></span><span><span><span>′</span></span></span></span></span></span></span></span></span><span>)</span><span></span><span>+</span><span></span></span><span><span></span><span><span></span><span><span><span><span><span><span></span><span><span>2</span></span></span><span><span></span><span></span></span><span><span></span><span><span>1</span></span></span></span><span>​</span></span><span><span><span></span></span></span></span></span><span></span></span><span>(</span><span>θ</span><span></span><span>−</span><span></span></span><span><span></span><span><span>θ</span><span><span><span><span><span><span></span><span><span><span>′</span></span></span></span></span></span></span></span></span><span><span>)</span><span><span><span><span><span><span></span><span><span>T</span></span></span></span></span></span></span></span><span>H</span><span>(</span><span>θ</span><span></span><span>−</span><span></span></span><span><span></span><span><span>θ</span><span><span><span><span><span><span></span><span><span><span>′</span></span></span></span></span></span></span></span></span><span>)</span></span></span></span></span><p>这里的 <span><span>HH</span><span><span><span></span><span>H</span></span></span></span> 就是<strong>Hessian 矩阵</strong>，是由损失函数 L (θ) 各参数的二阶偏导数构成的对称方阵，用于刻画损失函数曲率，辅助判断驻点类型。</p><p>我们设 <span><span>v=θ−θ′v = \theta - \theta'</span><span><span><span></span><span>v</span><span></span><span>=</span><span></span></span><span><span></span><span>θ</span><span></span><span>−</span><span></span></span><span><span></span><span><span>θ</span><span><span><span><span><span><span></span><span><span><span>′</span></span></span></span></span></span></span></span></span></span></span></span>（移动方向），那么损失的变化就完全由二次项决定：</p><span><span><span>ΔL≈12vTHv\Delta L \approx \frac{1}{2} v^T H v</span><span><span><span></span><span>Δ</span><span>L</span><span></span><span>≈</span><span></span></span><span><span></span><span><span></span><span><span><span><span><span><span></span><span><span>2</span></span></span><span><span></span><span></span></span><span><span></span><span><span>1</span></span></span></span><span>​</span></span><span><span><span></span></span></span></span></span><span></span></span><span><span>v</span><span><span><span><span><span><span></span><span><span>T</span></span></span></span></span></span></span></span><span>H</span><span>v</span></span></span></span></span><p>而根据特征向量的定义，对于 Hessian 的特征向量 <span><span>uu</span><span><span><span></span><span>u</span></span></span></span> 和对应的特征值 <span><span>λ\lambda</span><span><span><span></span><span>λ</span></span></span></span>，我们有：</p><span><span><span>uTHu=λ∥u∥2u^T H u = \lambda \|u\|^2</span><span><span><span></span><span><span>u</span><span><span><span><span><span><span></span><span><span>T</span></span></span></span></span></span></span></span><span>H</span><span>u</span><span></span><span>=</span><span></span></span><span><span></span><span>λ</span><span>∥</span><span>u</span><span><span>∥</span><span><span><span><span><span><span></span><span><span>2</span></span></span></span></span></span></span></span></span></span></span></span><p>因为 <span><span>∥u∥2&gt;0\|u\|^2 &gt; 0</span><span><span><span></span><span>∥</span><span>u</span><span><span>∥</span><span><span><span><span><span><span></span><span><span>2</span></span></span></span></span></span></span></span><span></span><span>&gt;</span><span></span></span><span><span></span><span>0</span></span></span></span>，所以 <span><span>uTHuu^T H u</span><span><span><span></span><span><span>u</span><span><span><span><span><span><span></span><span><span>T</span></span></span></span></span></span></span></span><span>H</span><span>u</span></span></span></span> 的符号<strong>完全由特征值 <span><span>λ\lambda</span><span><span><span></span><span>λ</span></span></span></span> 决定</strong>：</p><ul>
<li><span><span>λ&gt;0\lambda &gt; 0</span><span><span><span></span><span>λ</span><span></span><span>&gt;</span><span></span></span><span><span></span><span>0</span></span></span></span>：沿 <span><span>uu</span><span><span><span></span><span>u</span></span></span></span> 方向走，损失会变大（上坡）。</li>
<li><span><span>λ&lt;0\lambda &lt; 0</span><span><span><span></span><span>λ</span><span></span><span>&lt;</span><span></span></span><span><span></span><span>0</span></span></span></span>：沿 <span><span>uu</span><span><span><span></span><span>u</span></span></span></span> 方向走，损失会变小（下坡）。</li>
</ul><p>这就是为什么我们可以用 Hessian 的特征值来判断临界点的类型。</p><p><img alt="" loading="lazy" width="720" height="548" src="/_astro/PixPin_2026-05-10_16-15-09.CUPwXZ-B_Z2qkzlp.webp" /></p></section>
<section><h1>4 收敛点的真实本质<a href="#4-收敛点的真实本质"><span>#</span></a></h1><p>我们可以做一个简单的实验：重复训练同一个网络很多次，每次训练到收敛，然后计算每个临界点 Hessian 矩阵的<strong>正特征值比例</strong>（我们称之为 Minimum Ratio），并记录对应的训练损失。</p><p>我们能从实验结果中清晰地看到一个强规律：</p><ol>
<li><strong>左侧点群（Minimum Ratio 很低）</strong>：损失都很高（0.08-0.09），这些是典型的低维鞍点，有大量负特征值，模型困在这里根本没学好。</li>
<li><strong>右侧点群（Minimum Ratio 很高）</strong>：损失都很低，这些点看起来像“局部极小值”，但它们的 Minimum Ratio 远小于 1，说明 Hessian 矩阵里仍然存在少量负特征值，本质上还是鞍点。</li>
</ol><p><img alt="" loading="lazy" width="1297" height="909" src="/_astro/PixPin_2026-05-10_16-10-03.CdZv1Rat_uKaIz.webp" /></p><p>图中的文字 <code>never reach a real "local minima"</code> 直接点破了真相：
<strong>我们几乎永远无法到达数学上严格定义的局部极小值，只能停在这些高维鞍点上。</strong></p></section>
<section><h1>5 总结<a href="#5-总结"><span>#</span></a></h1><ol>
<li>高维空间中，鞍点数量远多于局部极小值，是模型收敛的主要位置，且鞍点可进一步优化，并非优化终点；</li>
<li>驻点类型可通过 Hessian 矩阵特征值判断：所有特征值&gt;0 为局部最小值点，正负特征值共存为鞍点；</li>
<li>可沿 Hessian 负特征值对应的特征向量方向，逃离劣质鞍点，继续降低损失；</li>
<li>优化的关键并非追求严格局部极小值，而是收敛到<strong>负特征值少、正特征值占比偏高</strong>的平缓优质临界点，这类点损失更低、泛化性能更好。</li>
</ol></section>]]></content>
    </entry>
    <entry>
      <id>https://www.celyn-blog.xyz/posts/webrtc-leak-protection/</id>
      <title type="text">【IP地址防护】-预防WebRTC泄露</title>
      <published>2026-05-02T00:00:00.000Z</published>
      <updated>2026-05-02T00:00:00.000Z</updated>
      <author><name>Celyn</name></author>
      <link rel="alternate" href="https://www.celyn-blog.xyz/posts/webrtc-leak-protection/"/>
      <summary type="text">详解WebRTC泄露原理及防护方法，保护你的真实IP地址不被泄露，提升网络隐私安全。</summary>
      <content type="html"><![CDATA[<section><h1>什么是 WebRTC 泄露（WebRTC Leak），如何预防<a href="#什么是-webrtc-泄露webrtc-leak如何预防"><span>#</span></a></h1><blockquote><p>WebRTC（Web Real-Time Communication）是浏览器提供的实时音视频与点对点数据通道技术。<strong>WebRTC 泄露</strong> 指的是在使用浏览器或某些应用时，WebRTC 的连接流程（ICE 候选交换）意外暴露了本地或真实公网 IP 地址，导致即便你在用 VPN/代理，目标网站或第三方仍可能看到你的真实 IP 地址或局域网地址，从而破坏隐私与匿名性。</p></blockquote><p><strong>WebRTC 泄露简要原理</strong></p><ol>
<li>建立 P2P 连接时，浏览器会通过 STUN/TURN server 获取 <strong>ICE candidates</strong>（候选连接地址），这些候选包括：
<ul>
<li><code>host</code>（本机局域网 IP）</li>
<li><code>srflx</code>（通过 STUN 获得的公网映射 IP）</li>
<li><code>relay</code>（通过 TURN 中继的地址）</li>
</ul>
</li>
<li>如果浏览器本地或网页脚本暴露/发送了 <code>host</code> 或 <code>srflx</code> 类型的候选，第三方就可能获知本机真实 IP（包括局域网 IP 与公网真实 IP）。</li>
<li>VPN/代理通常只影响浏览器的普通 HTTP(S) 流量，但 WebRTC 的 STUN 请求可能绕过这些路径，从而暴露真实地址。</li>
</ol><p>chrome需要安装扩展来避免WebRTC 泄露，推荐一个开源扩展
<a href="https://chromewebstore.google.com/detail/webrtc-control/fjkmabmdepjfammlpliljpnbhleegehm" target="_blank">chrome扩展地址</a></p></section>
<section><h1>使用 IPPure 检查 WebRTC 泄露情况<a href="#使用-ippure-检查-webrtc-泄露情况"><span>#</span></a></h1><section><h2>安装前：<a href="#安装前"><span>#</span></a></h2><p><a href="https://ippure.com/Browser-WebRTC-Leak-Detect.html" target="_blank">IPPure链接地址</a>
<img alt="" loading="lazy" width="2187" height="1203" src="/_astro/PixPin_2026-05-02_22-53-07.Dis5WX0B_TIpc7.webp" /></p></section><section><h2>安装后 ：<a href="#安装后-"><span>#</span></a></h2><p><img alt="" loading="lazy" width="2199" height="1113" src="/_astro/PixPin_2026-05-02_22-53-10.BYiUAw2J_Z1XujCN.webp" />
<img alt="" loading="lazy" width="2156" height="1200" src="/_astro/PixPin_2026-05-02_22-53-16.C_1xT_xr_ZJtiEh.webp" /></p></section></section>
<section><h1>Reference：<a href="#reference"><span>#</span></a></h1><ol>
<li><a href="https://ippure.com/Browser-WebRTC-Leak-Detect.html" target="_blank">WebRTC解释</a></li>
</ol></section>]]></content>
    </entry>
    <entry>
      <id>https://www.celyn-blog.xyz/posts/cv-ai-defense-task1/</id>
      <title type="text">【cv-AI攻防】-Task1：赛题方案解读</title>
      <published>2024-10-12T00:00:00.000Z</published>
      <updated>2024-10-12T00:00:00.000Z</updated>
      <author><name>Celyn</name></author>
      <link rel="alternate" href="https://www.celyn-blog.xyz/posts/cv-ai-defense-task1/"/>
      <summary type="text">CV-AI攻防竞赛Task1方案解读，基于YOLO构建分割数据集并完成目标检测任务实战。</summary>
      <content type="html"><![CDATA[<section><h1>步骤一：构建YOLO数据集<a href="#步骤一构建yolo数据集"><span>#</span></a></h1><p>由于比赛原始数据集较大，我们采样部分数据构建训练集和验证集：</p><div><div><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>if os.path.exists('yolo_seg_dataset'):</span></div></div><div><div><div>2</div></div><div><span><span>    </span></span><span>shutil.rmtree('yolo_seg_dataset')</span></div></div><div><div><div>3</div></div><div>
</div></div><div><div><div>4</div></div><div><span>os.makedirs('yolo_seg_dataset/train')</span></div></div><div><div><div>5</div></div><div><span>os.makedirs('yolo_seg_dataset/valid')</span></div></div><div><div><div>6</div></div><div>
</div></div><div><div><div>7</div></div><div><span>def normalize_polygon(polygon, img_width, img_height):</span></div></div><div><div><div>8</div></div><div><span><span>    </span></span><span>return [(x / img_width, y / img_height) for x, y in polygon]</span></div></div><div><div><div>9</div></div><div>
</div></div><div><div><div>10</div></div><div><span># 采样训练集</span></div></div><div><div><div>11</div></div><div><span>for row in training_anno.iloc[:10000].iterrows():</span></div></div><div><div><div>12</div></div><div><span><span>    </span></span><span>shutil.copy(row[1].Path, 'yolo_seg_dataset/train')</span></div></div><div><div><div>13</div></div><div>
</div></div><div><div><div>14</div></div><div><span><span>    </span></span><span>img = cv2.imread(row[1].Path)</span></div></div><div><div><div>15</div></div><div><span><span>    </span></span><span>img_height, img_width = img.shape[:2]</span></div></div><div><div><div>16</div></div><div><span><span>    </span></span><span>txt_filename = os.path.join('yolo_seg_dataset/train/' + row[1].Path.split('/')[-1][:-4] + '.txt')</span></div></div><div><div><div>17</div></div><div><span><span>    </span></span><span>with open(txt_filename, 'w') as up:</span></div></div><div><div><div>18</div></div><div><span><span>        </span></span><span>for polygon in row[1].Polygons:</span></div></div><div><div><div>19</div></div><div><span><span>            </span></span><span>normalized_polygon = normalize_polygon(polygon, img_width, img_height)</span></div></div><div><div><div>20</div></div><div><span><span>            </span></span><span>normalized_coords = ' '.join([f'{coord[0]:.3f} {coord[1]:.3f}' for coord in normalized_polygon])</span></div></div><div><div><div>21</div></div><div><span><span>            </span></span><span>up.write(f'0 {normalized_coords}\n')</span></div></div><div><div><div>22</div></div><div>
</div></div><div><div><div>23</div></div><div><span><span> </span></span><span># 采用验证集</span></div></div><div><div><div>24</div></div><div><span><span> </span></span><span>for row in training_anno.iloc[10000:10150].iterrows():</span></div></div><div><div><div>25</div></div><div><span><span>    </span></span><span>shutil.copy(row[1].Path, 'yolo_seg_dataset/valid')</span></div></div><div><div><div>26</div></div><div>
</div></div><div><div><div>27</div></div><div><span><span>    </span></span><span>img = cv2.imread(row[1].Path)</span></div></div><div><div><div>28</div></div><div><span><span>    </span></span><span>img_height, img_width = img.shape[:2]</span></div></div><div><div><div>29</div></div><div><span><span>    </span></span><span>txt_filename = os.path.join('yolo_seg_dataset/valid/' + row[1].Path.split('/')[-1][:-4] + '.txt')</span></div></div><div><div><div>30</div></div><div><span><span>    </span></span><span>with open(txt_filename, 'w') as up:</span></div></div><div><div><div>31</div></div><div><span><span>        </span></span><span>for polygon in row[1].Polygons:</span></div></div><div><div><div>32</div></div><div><span><span>            </span></span><span>normalized_polygon = normalize_polygon(polygon, img_width, img_height)</span></div></div><div><div><div>33</div></div><div><span><span>            </span></span><span>normalized_coords = ' '.join([f'{coord[0]:.3f} {coord[1]:.3f}' for coord in normalized_polygon])</span></div></div><div><div><div>34</div></div><div><span><span>            </span></span><span>up.write(f'0 {normalized_coords}\n')</span></div></div></code></pre><div><div></div><div></div></div></figure><div></div></div><span>展开</span><span>收起</span></div></div></section>
<section><h1>逐行代码分析<a href="#逐行代码分析"><span>#</span></a></h1><p>好的，我们逐行分析这段代码，它的主要功能是将训练集和验证集的数据从原始数据集中复制到新的目录中，并将多边形的坐标标准化为相对坐标。</p><section><h3>代码逐行分析<a href="#代码逐行分析"><span>#</span></a></h3><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>if</span><span><span> os.path.</span><span>exists</span><span>(</span></span><span>'yolo_seg_dataset'</span><span>):</span></div></div><div><div><div>2</div></div><div><span><span>    </span></span><span>shutil.</span><span>rmtree</span><span>(</span><span>'yolo_seg_dataset'</span><span>)</span></div></div></code></pre><div><div></div><div></div></div></figure></div><ul>
<li><strong>功能</strong>: 检查<code>yolo_seg_dataset</code>目录是否存在。如果存在，则使用<code>shutil.rmtree</code>删除该目录及其内容。<code>shutil</code>常用于文件与目录的处理。</li>
<li><strong>目的</strong>: 确保每次运行代码时，数据集是干净的，没有旧的数据。</li>
</ul><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span><span>os.</span><span>makedirs</span><span>(</span></span><span>'yolo_seg_dataset/train'</span><span>)</span></div></div><div><div><div>2</div></div><div><span><span>os.</span><span>makedirs</span><span>(</span></span><span>'yolo_seg_dataset/valid'</span><span>)</span></div></div></code></pre><div><div></div><div></div></div></figure></div><ul>
<li><strong>功能</strong>: 创建两个新的子目录<code>train</code>和<code>valid</code>，用于存放训练集和验证集数据。</li>
<li><strong>目的</strong>: 为之后的文件复制和标签文件创建所需的目录结构。</li>
</ul><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>def</span><span> </span><span>normalize_polygon</span><span>(</span><span>polygon</span><span>,</span><span><span> </span><span>img_width</span></span><span>,</span><span><span> </span><span>img_height</span></span><span>):</span></div></div><div><div><div>2</div></div><div><span>    </span><span>return</span><span><span> [(x </span><span>/</span><span> img_width, y </span><span>/</span><span> img_height) </span></span><span>for</span><span> x, y </span><span>in</span><span> polygon]</span></div></div></code></pre><div><div></div><div></div></div></figure></div><ul>
<li><strong>功能</strong>: 定义一个函数<code>normalize_polygon</code>，它接受一个多边形的坐标（列表形式），以及图像的宽度和高度。该函数返回归一化后的坐标，归一化的方式是将每个坐标除以图像的宽度和高度。</li>
<li><strong>目的</strong>: ==将坐标转换为相对坐标，使其在不同尺寸的图像中具有一致性==。</li>
</ul><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span># 采样训练集</span></div></div><div><div><div>2</div></div><div><span>for</span><span> row </span><span>in</span><span> training_anno.iloc[:</span><span>10000</span><span><span>].</span><span>iterrows</span><span>():</span></span></div></div></code></pre><div><div></div><div></div></div></figure></div><ul>
<li><strong>功能</strong>: 遍历<code>training_anno</code>数据框的前10000行。<code>iterrows()</code>方法用于逐行迭代数据框，并返回行的索引和行的内容。</li>
<li><strong>目的</strong>: 处理训练集中的前10000条数据。</li>
</ul><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span><span>    </span></span><span>shutil.</span><span>copy</span><span>(row[</span><span>1</span><span>].Path, </span><span>'yolo_seg_dataset/train'</span><span>)</span></div></div></code></pre><div><div></div><div></div></div></figure></div><ul>
<li><strong>功能</strong>: 将当前行的图像文件从原始路径复制到<code>yolo_seg_dataset/train</code>目录。</li>
<li><strong>目的</strong>: 将图像文件放入训练集目录中。</li>
</ul><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span><span>    </span></span><span>img </span><span>=</span><span> cv2.</span><span>imread</span><span>(row[</span><span>1</span><span>].Path)</span></div></div><div><div><div>2</div></div><div><span><span>    </span></span><span>img_height, img_width </span><span>=</span><span> img.shape[:</span><span>2</span><span>]</span></div></div></code></pre><div><div></div><div></div></div></figure></div><ul>
<li><strong>功能</strong>: 使用OpenCV读取图像文件，并获取其高度和宽度。</li>
<li><strong>目的</strong>: 获取图像的尺寸，以便后续进行坐标归一化。</li>
</ul><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span><span>    </span></span><span>txt_filename </span><span>=</span><span> os.path.</span><span>join</span><span>(</span><span>'yolo_seg_dataset/train/'</span><span><span> </span><span>+</span><span> row[</span></span><span>1</span><span><span>].Path.</span><span>split</span><span>(</span></span><span>'/'</span><span><span>)[</span><span>-</span></span><span>1</span><span><span>][:</span><span>-</span></span><span>4</span><span><span>] </span><span>+</span><span> </span></span><span>'.txt'</span><span>)</span></div></div></code></pre><div><div></div><div></div></div></figure></div><ul>
<li><strong>功能</strong>: 生成对应的文本文件名，该文件名是基于图像文件名生成的（去掉扩展名并添加<code>.txt</code>）。</li>
<li><strong>目的</strong>: 为每个图像文件创建一个对应的标签文件。</li>
</ul><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>    </span><span>with</span><span> </span><span>open</span><span>(txt_filename, </span><span>'w'</span><span>) </span><span>as</span><span> up:</span></div></div></code></pre><div><div></div><div></div></div></figure></div><ul>
<li><strong>功能</strong>: 打开或创建文本文件进行写入，使用<code>with</code>语句可以确保文件在写入完成后正确关闭。</li>
<li><strong>目的</strong>: 准备写入归一化后的坐标数据。</li>
</ul><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>        </span><span>for</span><span> polygon </span><span>in</span><span> row[</span><span>1</span><span>].Polygons:</span></div></div><div><div><div>2</div></div><div><span><span>            </span></span><span>normalized_polygon </span><span>=</span><span> </span><span>normalize_polygon</span><span>(polygon, img_width, img_height)</span></div></div></code></pre><div><div></div><div></div></div></figure></div><ul>
<li><strong>功能</strong>: 遍历当前行的所有多边形坐标，调用<code>normalize_polygon</code>函数进行归一化。</li>
<li><strong>目的</strong>: 将多边形的坐标转化为相对坐标。</li>
</ul><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span><span>            </span></span><span>normalized_coords </span><span>=</span><span> </span><span>' '</span><span><span>.</span><span>join</span><span>([</span></span><span>f</span><span>'</span><span>{</span><span>coord[</span><span>0</span><span>]</span><span>:.3f</span><span>}</span><span> </span><span>{</span><span>coord[</span><span>1</span><span>]</span><span>:.3f</span><span>}</span><span>'</span><span> </span><span>for</span><span> coord </span><span>in</span><span> normalized_polygon])</span></div></div></code></pre><div><div></div><div></div></div></figure></div><ul>
<li><strong>功能</strong>: 这是一个列表推导式，用于遍历<code>normalized_polygon</code>中的每个坐标<code>coord</code>。每个<code>coord</code>都是一个包含两个元素的元组（<code>(x, y)</code>），表示一个点的坐标。</li>
<li><strong>格式化</strong>:
<ul>
<li><code>coord[0]:.3f</code>：将<code>coord</code>中的第一个元素（x坐标）==格式化为小数点后三位的浮点数==。</li>
<li><code>coord[1]:.3f</code>：将第二个元素（y坐标）格式化为小数点后三位的浮点数。</li>
</ul>
</li>
<li><strong>结果</strong>: 列表推导式生成一个字符串列表，其中每个字符串都包含格式化后的坐标，例如：<code>["0.123 0.456", "0.789 0.012"]</code>。</li>
</ul><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span><span>            </span></span><span>up.</span><span>write</span><span>(</span><span>f</span><span>'0 </span><span>{</span><span>normalized_coords</span><span>}</span><span>\n</span><span>'</span><span>)</span></div></div></code></pre><div><div></div><div></div></div></figure></div><ul>
<li><strong>内容</strong>:
<ul>
<li><code>'0 '</code>：这是一个常量字符串，通常用于表示==类别标签==。在YOLO格式中，<code>0</code>可能表示某个特定的类别（例如，一个物体的类别，具体取决于数据集的类别定义）。</li>
<li><code>up</code>是一个文件对象，通过<code>with open(...) as up:</code>创建的。调用<code>write</code>方法将构建的字符串写入到文件中。</li>
</ul>
</li>
<li><strong>功能</strong>: 将归一化后的坐标写入文本文件。</li>
<li><strong>目的</strong>: 保存标签信息，以便YOLO模型使用。</li>
</ul></section><section><h3>验证集处理<a href="#验证集处理"><span>#</span></a></h3><p>接下来的代码与训练集处理相似，只是处理的是验证集数据。它从<code>training_anno</code>的第10000到第10150行进行迭代，重复上述步骤，复制图像到<code>yolo_seg_dataset/valid</code>目录，并创建相应的标签文件。</p><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span># 采用验证集</span></div></div><div><div><div>2</div></div><div><span>for</span><span> row </span><span>in</span><span> training_anno.iloc[</span><span>10000</span><span>:</span><span>10150</span><span><span>].</span><span>iterrows</span><span>():</span></span></div></div><div><div><div>3</div></div><div><span><span>    </span></span><span>shutil.</span><span>copy</span><span>(row[</span><span>1</span><span>].Path, </span><span>'yolo_seg_dataset/valid'</span><span>)</span></div></div><div><div><div>4</div></div><div>
</div></div><div><div><div>5</div></div><div><span><span>    </span></span><span>img </span><span>=</span><span> cv2.</span><span>imread</span><span>(row[</span><span>1</span><span>].Path)</span></div></div><div><div><div>6</div></div><div><span><span>    </span></span><span>img_height, img_width </span><span>=</span><span> img.shape[:</span><span>2</span><span>]</span></div></div><div><div><div>7</div></div><div><span><span>    </span></span><span>txt_filename </span><span>=</span><span> os.path.</span><span>join</span><span>(</span><span>'yolo_seg_dataset/valid/'</span><span><span> </span><span>+</span><span> row[</span></span><span>1</span><span><span>].Path.</span><span>split</span><span>(</span></span><span>'/'</span><span><span>)[</span><span>-</span></span><span>1</span><span><span>][:</span><span>-</span></span><span>4</span><span><span>] </span><span>+</span><span> </span></span><span>'.txt'</span><span>)</span></div></div><div><div><div>8</div></div><div><span>    </span><span>with</span><span> </span><span>open</span><span>(txt_filename, </span><span>'w'</span><span>) </span><span>as</span><span> up:</span></div></div><div><div><div>9</div></div><div><span>        </span><span>for</span><span> polygon </span><span>in</span><span> row[</span><span>1</span><span>].Polygons:</span></div></div><div><div><div>10</div></div><div><span><span>            </span></span><span>normalized_polygon </span><span>=</span><span> </span><span>normalize_polygon</span><span>(polygon, img_width, img_height)</span></div></div><div><div><div>11</div></div><div><span><span>            </span></span><span>normalized_coords </span><span>=</span><span> </span><span>' '</span><span><span>.</span><span>join</span><span>([</span></span><span>f</span><span>'</span><span>{</span><span>coord[</span><span>0</span><span>]</span><span>:.3f</span><span>}</span><span> </span><span>{</span><span>coord[</span><span>1</span><span>]</span><span>:.3f</span><span>}</span><span>'</span><span> </span><span>for</span><span> coord </span><span>in</span><span> normalized_polygon])</span></div></div><div><div><div>12</div></div><div><span><span>            </span></span><span>up.</span><span>write</span><span>(</span><span>f</span><span>'0 </span><span>{</span><span>normalized_coords</span><span>}</span><span>\n</span><span>'</span><span>)</span></div></div></code></pre><div><div></div><div></div></div></figure></div></section><section><h3>总结<a href="#总结"><span>#</span></a></h3><p>这段代码的主要目的是将多边形标注数据从原始数据集中提取出来，规范化并保存到新的训练集和验证集目录中，以供YOLO模型使用。通过归一化坐标，模型可以更好地适应不同尺寸的输入图像。</p></section></section>
<section><h1>步骤二：写入YOLO配置文件<a href="#步骤二写入yolo配置文件"><span>#</span></a></h1><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>with open('yolo_seg_dataset/data.yaml', 'w') as up:</span></div></div><div><div><div>2</div></div><div><span><span>    </span></span><span>data_root = os.path.abspath('yolo_seg_dataset/')</span></div></div><div><div><div>3</div></div><div><span><span>    </span></span><span>up.write(f'''</span></div></div><div><div><div>4</div></div><div><span>path: {data_root}</span></div></div><div><div><div>5</div></div><div><span>train: train</span></div></div><div><div><div>6</div></div><div><span>val: valid</span></div></div><div><div><div>7</div></div><div>
</div></div><div><div><div>8</div></div><div><span>names:</span></div></div><div><div><div>9</div></div><div><span><span>    </span></span><span>0: alter</span></div></div><div><div><div>10</div></div><div><span>''')</span></div></div></code></pre><div><div></div><div></div></div></figure></div></section>
<section><h1>逐行代码分析<a href="#逐行代码分析-1"><span>#</span></a></h1><p>这一段代码的目的是创建一个名为 <code>data.yaml</code> 的配置文件，通常用于YOLO等深度学习模型的数据配置。我们来逐步分析这段代码的结构和含义。</p><section><h3>代码解析<a href="#代码解析"><span>#</span></a></h3><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>with</span><span> </span><span>open</span><span>(</span><span>'yolo_seg_dataset/data.yaml'</span><span>, </span><span>'w'</span><span>) </span><span>as</span><span> up:</span></div></div></code></pre><div><div></div><div></div></div></figure></div><ul>
<li><strong>功能</strong>: 使用 <code>with open(...) as ...:</code> 语句打开（或创建）一个名为 <code>data.yaml</code> 的文件，并以写入模式（<code>'w'</code>）打开。<code>up</code> 是一个文件对象，用于后续的写入操作。</li>
<li><strong>上下文管理</strong>: 使用 <code>with</code> 语句可以确保文件在操作完成后自动关闭，避免文件未关闭导致的资源泄漏。</li>
</ul></section><section><h3>2. ==获取数据根路径==<a href="#2-获取数据根路径"><span>#</span></a></h3><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span><span>data_root </span><span>=</span><span> os.path.</span><span>abspath</span><span>(</span></span><span>'yolo_seg_dataset/'</span><span>)</span></div></div></code></pre><div><div></div><div></div></div></figure></div><ul>
<li><strong>功能</strong>: <code>os.path.abspath(...)</code> 返回给定路径的==绝对路径==。在这里，它将  <code>'yolo_seg_dataset/'</code> 转换为绝对路径并存储在 <code>data_root</code> 变量中。</li>
<li><strong>示例</strong>: 如果当前工作目录是 <code>/home/user/projects</code>，那么 <code>data_root</code> 的值将是 <code>/home/user/projects/yolo_seg_dataset</code>。</li>
</ul></section><section><h3>3. 写入内容到文件<a href="#3-写入内容到文件"><span>#</span></a></h3><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span><span>up.</span><span>write</span><span>(</span></span><span>f</span><span>'''</span></div></div><div><div><div>2</div></div><div><span>path: </span><span>{</span><span>data_root</span><span>}</span></div></div><div><div><div>3</div></div><div><span>train: train</span></div></div><div><div><div>4</div></div><div><span>val: valid</span></div></div><div><div><div>5</div></div><div>
</div></div><div><div><div>6</div></div><div><span>names:</span></div></div><div><div><div>7</div></div><div><span><span>    </span></span><span>0: alter</span></div></div><div><div><div>8</div></div><div><span>'''</span><span>)</span></div></div></code></pre><div><div></div><div></div></div></figure></div><ul>
<li><strong>功能</strong>: 使用 <code>write(...)</code> 方法将格式化的字符串写入到 <code>data.yaml</code> 文件中。字符串的内容使用了多行字符串的语法（即用三个单引号包裹的字符串）。</li>
<li><strong>内容解释</strong>:
<ul>
<li><code>path: {data_root}</code>: 写入<strong>数据集</strong>根路径的==绝对路径==。</li>
<li><code>train: train</code>: 指定训练数据的子目录名（==相对路径==）。</li>
<li><code>val: valid</code>: 指定验证数据的子目录名（==相对路径==）。</li>
<li><code>names:</code>: 这是一个字典，用于==映射类别ID到类别名称==。在这里，<code>0</code> 映射到类别名称 <code>alter</code>。</li>
</ul>
</li>
</ul></section><section><h3>完整内容示例<a href="#完整内容示例"><span>#</span></a></h3><p>假设 <code>data_root</code> 的绝对路径为 <code>/home/user/projects/yolo_seg_dataset</code>，最终写入 <code>data.yaml</code> 文件的内容将类似于：</p><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>path</span><span>: </span><span>/home/user/projects/yolo_seg_dataset</span></div></div><div><div><div>2</div></div><div><span>train</span><span>: </span><span>train</span></div></div><div><div><div>3</div></div><div><span>val</span><span>: </span><span>valid</span></div></div><div><div><div>4</div></div><div>
</div></div><div><div><div>5</div></div><div><span>names</span><span>:</span></div></div><div><div><div>6</div></div><div><span>    </span><span>0</span><span>: </span><span>alter</span></div></div></code></pre><div><div></div><div></div></div></figure></div></section><section><h3>总结<a href="#总结-1"><span>#</span></a></h3><p>这段代码生成了一个用于YOLO数据集配置的 <code>data.yaml</code> 文件，包含了数据集的根路径、训练和验证集的目录，以及类别的映射信息。该配置文件通常用于训练模型，帮助模型了解数据集的结构和类别定义。</p></section></section>
<section><h1>步骤三：训练YOLO分割模型<a href="#步骤三训练yolo分割模型"><span>#</span></a></h1><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>from ultralytics import YOLO</span></div></div><div><div><div>2</div></div><div>
</div></div><div><div><div>3</div></div><div><span>model = YOLO("./yolov8n-seg.pt")</span></div></div><div><div><div>4</div></div><div><span>results = model.train(data="./yolo_seg_dataset/data.yaml", epochs=10, imgsz=640)</span></div></div></code></pre><div><div></div><div></div></div></figure></div></section>
<section><h1>逐行代码分析<a href="#逐行代码分析-2"><span>#</span></a></h1><p>以下是对代码的逐行分析：</p><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>from</span><span> ultralytics </span><span>import</span><span><span> </span><span>YOLO</span></span></div></div></code></pre><div><div></div><div></div></div></figure></div><ul>
<li>这行代码导入了 <code>ultralytics</code> 库中的 <code>YOLO</code> 类。</li>
<li><code>ultralytics</code> 是一个用于目标检测的库，提供了易于使用的接口来训练和推理 YOLO 模型，尤其是 <code>YOLOv8</code> 模型。</li>
<li><code>YOLO</code> 类是用于创建并管理 YOLO 模型的类，包括加载预训练模型、训练模型和进行推理。</li>
</ul><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span><span>model </span><span>=</span><span> </span><span>YOLO</span><span>(</span></span><span>"./yolov8n-seg.pt"</span><span>)</span></div></div></code></pre><div><div></div><div></div></div></figure></div><ul>
<li>这行代码使用 <code>YOLO</code> 类创建了一个 <code>model</code> 实例。</li>
<li><code>YOLO("./yolov8n-seg.pt")</code> 加载一个预训练的 YOLOv8 模型，该模型文件是 <code>yolov8n-seg.pt</code>。这是 YOLOv8 模型的小型版本（<code>n</code> 代表 nano纳米），同时具有分割功能（<code>-seg</code> 代表分割任务）。</li>
<li>通过这种加载方式，可以直接基于预训练模型进行进一步训练或推理。</li>
</ul><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span><span>results </span><span>=</span><span> model.</span><span>train</span><span>(</span></span><span>data</span><span>=</span><span>"./yolo_seg_dataset/data.yaml"</span><span>, </span><span>epochs</span><span>=</span><span>10</span><span>, </span><span>imgsz</span><span>=</span><span>640</span><span>)</span></div></div></code></pre><div><div></div><div></div></div></figure></div><ul>
<li><code>model.train()</code> 是 YOLO 模型的训练方法。</li>
<li><code>data="./yolo_seg_dataset/data.yaml"</code> 指定了用于训练的数据集配置文件。<code>data.yaml</code> 文件通常包含训练集、验证集的路径，以及类别名称等信息。</li>
<li><code>epochs=10</code> 指定模型要进行 10 个==训练周期==（epochs）。每个周期意味着模型将遍历完整的训练数据集一次。</li>
<li><code>imgsz=640</code> 设置了输入图像的尺寸为 640 像素。YOLO 模型会将训练数据的图像调整到 640x640 的尺寸进行训练。</li>
</ul><p>训练完成后，<code>results</code> 会包含训练过程中的结果和性能指标（如损失值、准确度等），这些结果可以用于评估模型的训练效果。</p><section><h3>总结<a href="#总结-2"><span>#</span></a></h3><p>这段代码从 <code>ultralytics</code> 库中导入 YOLO 模型，加载了一个 YOLOv8 分割模型，并基于指定的数据集文件进行 10 个周期的训练，使用了 640x640 的图像输入尺寸。</p></section></section>
<section><h1>步骤四：预测测试集<a href="#步骤四预测测试集"><span>#</span></a></h1><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>from ultralytics import YOLO</span></div></div><div><div><div>2</div></div><div><span>import glob</span></div></div><div><div><div>3</div></div><div><span>from tqdm import tqdm</span></div></div><div><div><div>4</div></div><div>
</div></div><div><div><div>5</div></div><div><span>model = YOLO("./runs/segment/train6/weights/best.pt")</span></div></div><div><div><div>6</div></div><div><span>test_imgs = glob.glob('./test_set_A_rename/*/*')</span></div></div></code></pre><div><div></div><div></div></div></figure></div><div><div><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>Polygon = []</span></div></div><div><div><div>2</div></div><div><span>for path in tqdm(test_imgs[:]):</span></div></div><div><div><div>3</div></div><div><span><span>    </span></span><span>results = model(path, verbose=False)</span></div></div><div><div><div>4</div></div><div><span><span>    </span></span><span>result = results[0]</span></div></div><div><div><div>5</div></div><div><span><span>    </span></span><span>if result.masks is None:</span></div></div><div><div><div>6</div></div><div><span><span>        </span></span><span>Polygon.append([])</span></div></div><div><div><div>7</div></div><div><span><span>    </span></span><span>else:</span></div></div><div><div><div>8</div></div><div><span><span>        </span></span><span>Polygon.append([mask.astype(int).tolist() for mask in result.masks.xy])</span></div></div><div><div><div>9</div></div><div>
</div></div><div><div><div>10</div></div><div><span>import pandas as pd</span></div></div><div><div><div>11</div></div><div><span>submit = pd.DataFrame({</span></div></div><div><div><div>12</div></div><div><span><span>    </span></span><span>'Path': [x.split('/')[-1] for x in test_imgs[:]],</span></div></div><div><div><div>13</div></div><div><span><span>    </span></span><span>'Polygon': Polygon</span></div></div><div><div><div>14</div></div><div><span>})</span></div></div><div><div><div>15</div></div><div><span>submit.to_csv('track2_submit.csv', index=None)</span></div></div></code></pre><div><div></div><div></div></div></figure><div></div></div><span>展开</span><span>收起</span></div></div></section>
<section><h1>逐行分析代码<a href="#逐行分析代码"><span>#</span></a></h1><p>这段代码使用预训练的YOLO模型来处理图像集，并将结果保存到CSV文件中。下面是逐行的分析：</p><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>from</span><span> ultralytics </span><span>import</span><span><span> </span><span>YOLO</span></span></div></div><div><div><div>2</div></div><div><span>import</span><span> glob</span></div></div><div><div><div>3</div></div><div><span>from</span><span> tqdm </span><span>import</span><span> tqdm</span></div></div></code></pre><div><div></div><div></div></div></figure></div><ul>
<li><strong><code>from ultralytics import YOLO</code></strong>: 从 <code>ultralytics</code> 库中导入 YOLO 类，这个类用于加载并使用预训练的 YOLO 模型进行推理和处理图像。</li>
<li><strong><code>import glob</code></strong>: <code>glob</code> 模块用于文件操作，它可以根据特定的模式匹配文件路径。</li>
<li><strong><code>from tqdm import tqdm</code></strong>: <code>tqdm</code> 模块用于显示进度条，常用于长时间运行的循环或任务中，帮助可视化处理进度。</li>
</ul><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span><span>model </span><span>=</span><span> </span><span>YOLO</span><span>(</span></span><span>"./runs/segment/train6/weights/best.pt"</span><span>)</span></div></div></code></pre><div><div></div><div></div></div></figure></div><ul>
<li>这行代码加载了一个训练好的YOLO模型，权重文件位于 <code>./runs/segment/train6/weights/best.pt</code>。这个模型是预训练的权重，专门用于图像分割任务。</li>
</ul><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span><span>test_imgs </span><span>=</span><span> glob.</span><span>glob</span><span>(</span></span><span>'./test_set_A_rename/*/*'</span><span>)</span></div></div></code></pre><div><div></div><div></div></div></figure></div><ul>
<li>使用 <code>glob.glob()</code> 函数获取所有位于 <code>./test_set_A_rename/</code> 路径下的图像文件。</li>
<li><code>'*/*'</code> 匹配所有子目录中的文件路径，因此它会抓取该目录下所有的图像路径，并存储在 <code>test_imgs</code> 列表中。</li>
</ul><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span><span>Polygon </span><span>=</span><span> []</span></span></div></div><div><div><div>2</div></div><div><span>for</span><span> path </span><span>in</span><span><span> </span><span>tqdm</span><span>(test_imgs[:]):</span></span></div></div><div><div><div>3</div></div><div><span><span>    </span></span><span>results </span><span>=</span><span> </span><span>model</span><span>(path, </span><span>verbose</span><span>=</span><span>False</span><span>)</span></div></div><div><div><div>4</div></div><div><span><span>    </span></span><span>result </span><span>=</span><span> results[</span><span>0</span><span>]</span></div></div></code></pre><div><div></div><div></div></div></figure></div><ul>
<li><strong><code>Polygon = []</code></strong>: 初始化一个空列表 <code>Polygon</code>，用于存储每张图片的分割结果。</li>
<li><strong><code>for path in tqdm(test_imgs[:]):</code></strong>: 通过 <code>tqdm</code> 包裹循环，遍历 <code>test_imgs</code> 中的每个图像路径，并显示进度条。</li>
<li><strong><code>results = model(path, verbose=False)</code></strong>: 调用模型对当前图片（<code>path</code>）进行推理，得到分割结果。<code>verbose=False</code> 表示不打印详细信息。</li>
<li><strong><code>result = results[0]</code></strong>: YOLO 返回的结果可能包含多张图片（批处理），这里仅取第一个结果（当前处理的图片）。</li>
</ul><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>if</span><span> result.masks </span><span>is</span><span> </span><span>None</span><span>:</span></div></div><div><div><div>2</div></div><div><span><span>    </span></span><span>Polygon.</span><span>append</span><span>([])</span></div></div><div><div><div>3</div></div><div><span>else</span><span>:</span></div></div><div><div><div>4</div></div><div><span><span>    </span></span><span>Polygon.</span><span>append</span><span>([mask.</span><span>astype</span><span>(</span><span>int</span><span><span>).</span><span>tolist</span><span>() </span></span><span>for</span><span> mask </span><span>in</span><span> result.masks.xy])</span></div></div></code></pre><div><div></div><div></div></div></figure></div><ul>
<li>
<p><strong><code>if result.masks is None:</code></strong>: 检查结果是否包含分割的掩码（masks）。如果没有分割掩码（即 <code>result.masks</code> 为 <code>None</code>），则向 <code>Polygon</code> 列表中添加一个空列表。</p>
</li>
<li>
<p><strong><code>Polygon.append([mask.astype(int).tolist() for mask in result.masks.xy])</code></strong>: 如果存在分割掩码，将掩码的边界坐标（<code>result.masks.xy</code>）转换为整数并存储为 Python 列表的形式，然后将其添加到 <code>Polygon</code> 列表中。</p>
<p>==掩码（Mask）==在计算机视觉中是一个非常重要的概念，它通常用于表示图像中的特定区域。掩码的作用是突出或分离出图像中的某些部分（例如物体、背景等），便于后续的处理。掩码通常是一个与原始图像尺寸相同的二值化矩阵，每个像素点要么是 1（代表属于目标区域），要么是 0（代表不属于目标区域）。</p>
<p>在你的代码中，掩码（<code>mask</code>）与图像分割任务相关。模型返回的 <code>mask</code> 描述的是在图像中哪一部分属于分割出的对象。这些掩码可以是二值化的矩阵，也可以是其对应的轮廓坐标（多边形）。</p>
</li>
</ul><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>import</span><span> pandas </span><span>as</span><span> pd</span></div></div><div><div><div>2</div></div><div><span><span>submit </span><span>=</span><span> pd.</span><span>DataFrame</span><span>({</span></span></div></div><div><div><div>3</div></div><div><span>    </span><span>'Path'</span><span><span>: [x.</span><span>split</span><span>(</span></span><span>'/'</span><span><span>)[</span><span>-</span></span><span>1</span><span>] </span><span>for</span><span> x </span><span>in</span><span> test_imgs[:]],</span></div></div><div><div><div>4</div></div><div><span>    </span><span>'Polygon'</span><span>: Polygon</span></div></div><div><div><div>5</div></div><div><span>})</span></div></div><div><div><div>6</div></div><div><span><span>submit.</span><span>to_csv</span><span>(</span></span><span>'track2_submit.csv'</span><span>, </span><span>index</span><span>=</span><span>None</span><span>)</span></div></div></code></pre><div><div></div><div></div></div></figure></div><ul>
<li><strong><code>import pandas as pd</code></strong>: 导入 <code>pandas</code> 库，用于处理数据并生成 CSV 文件。</li>
<li><strong><code>submit = pd.DataFrame({...})</code></strong>: 创建一个 pandas 数据框 <code>submit</code>，包含两列：
<ul>
<li><code>'Path'</code>: 存储每张图像的文件名（通过 <code>x.split('/')[-1]</code> 从路径中提取文件名）。</li>
<li><code>'Polygon'</code>: 存储每张图像的分割结果（存储在 <code>Polygon</code> 列表中）。</li>
</ul>
</li>
<li><strong><code>submit.to_csv('track2_submit.csv', index=None)</code></strong>: 将 <code>submit</code> 数据框保存为一个名为 <code>track2_submit.csv</code> 的 CSV 文件，<code>index=None</code> 表示不保存行索引。</li>
</ul><section><h3>总结<a href="#总结-3"><span>#</span></a></h3><ul>
<li>这段代码使用YOLO模型处理一批图像，提取图像中的分割掩码边界（多边形），并将这些结果存储到一个CSV文件中。</li>
<li><code>tqdm</code> 用来展示处理进度，<code>pandas</code> 则用来生成最终的提交文件（<code>track2_submit.csv</code>）。</li>
</ul></section></section>]]></content>
    </entry>
    <entry>
      <id>https://www.celyn-blog.xyz/posts/internlm-personal-ai-assistant/</id>
      <title type="text">使用 InternLM 构建个人 AI 助手：Prompt、RAG、Agent 与 XTuner 微调</title>
      <published>2024-09-03T00:00:00.000Z</published>
      <updated>2026-08-18T00:00:00.000Z</updated>
      <author><name>Celyn</name></author>
      <link rel="alternate" href="https://www.celyn-blog.xyz/posts/internlm-personal-ai-assistant/"/>
      <summary type="text">从结构化提示词、RAG 知识库、Lagent 工具调用到 XTuner 微调，梳理基于 InternLM 构建个人 AI 助手的完整能力路线。</summary>
      <content type="html"><![CDATA[<p>这篇文章把四次相对独立的实践整理成一条完整路线：先用结构化 Prompt 约束模型，再用 RAG 补充外部知识，随后通过 Agent 连接工具，最后用微调固化稳定的行为与风格。</p>
<p>这四种技术并不是互相替代的关系。它们分别解决“如何表达任务”“如何获得知识”“如何执行动作”和“如何形成长期能力”四类问题。</p>
<section><h2>能力路线<a href="#能力路线"><span>#</span></a></h2>

<table><thead><tr><th>层次</th><th>技术</th><th>主要解决的问题</th><th>适合优先使用的场景</th></tr></thead><tbody><tr><td>任务表达</td><td>LangGPT / Prompt</td><td>输出不稳定、格式不统一、推理步骤不清晰</td><td>需求可以通过文字明确描述</td></tr><tr><td>外部知识</td><td>RAG / LlamaIndex</td><td>模型不知道私有资料或最新内容</td><td>文档问答、知识库检索</td></tr><tr><td>工具执行</td><td>Lagent</td><td>模型只能回答，不能搜索、计算或调用服务</td><td>需要调用工具完成多步任务</td></tr><tr><td>行为固化</td><td>XTuner</td><td>仅靠提示词难以长期保持风格或能力</td><td>有高质量训练数据且需求稳定</td></tr></tbody></table><p>实际开发时，通常应该按表格从上到下逐层增加复杂度，而不是一开始就进行微调。</p></section>
<section><h2>LangGPT：先让模型准确理解任务<a href="#langgpt先让模型准确理解任务"><span>#</span></a></h2><p>结构化提示词是成本最低、反馈最快的一层。下面保留当时针对浮点数比较问题的实践记录，并包含模型服务和 tmux 的基础操作。</p><section><h3>前言<a href="#前言"><span>#</span></a></h3><blockquote><p>在日常使用大模型时，我发现它经常在数字比对这类基础问题上出错，并且输出结果很不严谨。为了解决这个问题，我尝试使用Prompt Engineering，
并在网上找到了一个开源的「结构化提示词框架」— LangGPT，以下是我的使用过程记录。</p></blockquote><hr /></section><section><h3>实现步骤<a href="#实现步骤"><span>#</span></a></h3><p>step0：前期准备</p><ol>
<li>创建虚拟环境-&gt;激活虚拟环境-&gt;安装必要包文件</li>
<li>创建项目路径-&gt;进入项目</li>
<li>安装必要软件，如tmux</li>
</ol><p>step1：模型部署
模型下载-&gt;部署模型为OpenAI server-&gt;图形化界面调用</p><p>step3：langgpt结构化提示词编写
偷懒大法：GPTS有LangGPT提示词专家，用大模型生成即可</p><hr /></section><section><h3>tmux使用<a href="#tmux使用"><span>#</span></a></h3><p>tmux可以在终端中创建终端，将进程维持在后台。</p><p>所以当我们下载模型，使用tmux在后台下载时，即便我们断开ssh连接，下载也不会中断。</p><p>step1：部署模型为OpenAI server</p><section><h4>tmux常见命令<a href="#tmux常见命令"><span>#</span></a></h4><p>1.<strong>创建窗口命令</strong>：</p><p><strong>PS：t表示target(目标)，用于指定会话、窗口或面板的名称</strong></p><div><figure><figcaption><span></span><span>Terminal window</span></figcaption><pre><code><div><div><div>1</div></div><div><span>tmux</span><span> </span><span>new</span><span> </span><span>-t</span><span> </span><span>&lt;session_name&gt;</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p>PS：创建完成后，运行下面的命令进入新的命令窗口(<strong>首次创建自动进入，之后需要连接</strong>)：
其中<code>a</code> 是 <strong><code>attach</code></strong> 的简写</p><div><figure><figcaption><span></span><span>Terminal window</span></figcaption><pre><code><div><div><div>1</div></div><div><span>tmux</span><span> </span><span>a</span><span> </span><span>-t</span><span> </span><span>&lt;session_name&gt;</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p>2.<strong>查看当前 tmux 会话</strong>：</p><div><figure><figcaption><span></span><span>Terminal window</span></figcaption><pre><code><div><div><div>1</div></div><div><span>tmux</span><span> </span><span>ls</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p>3.<strong>连接到特定的 tmux 会话</strong>：</p><div><figure><figcaption><span></span><span>Terminal window</span></figcaption><pre><code><div><div><div>1</div></div><div><span>tmux</span><span> </span><span>attach</span><span> </span><span>-t</span><span> </span><span>&lt;session_name&gt;</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p>将 <code>&lt;session_name&gt;</code> 替换为你要连接的会话的实际名称或编号。</p><p>4.<strong>杀死整个 tmux 会话：</strong></p><div><figure><figcaption><span></span><span>Terminal window</span></figcaption><pre><code><div><div><div>1</div></div><div><span>tmux</span><span> </span><span>kill-session</span><span> </span><span>-t</span><span> </span><span>&lt;session_name&gt;</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p>5.<strong>退出tmux</strong>:</p><p>Ctrl+B进入<code>tmux</code>的控制模式，然后按d退出窗口连接</p></section><section><h4>模型部署<a href="#模型部署"><span>#</span></a></h4><p>进入命令窗口后，需要在新窗口中再次激活环境，命令参考<strong>0.1节</strong>。然后，使用LMDeploy进行部署，参考如下命令：</p><p>使用LMDeploy进行部署，参考如下命令：</p><div><figure><figcaption><span></span><span>Terminal window</span></figcaption><pre><code><div><div><div>1</div></div><div><span>CUDA_VISIBLE_DEVICES</span><span>=</span><span>0</span><span> </span><span>lmdeploy</span><span> </span><span>serve</span><span> </span><span>api_server</span><span> </span><span>/share/new_models/Shanghai_AI_Laboratory/internlm2-chat-1_8b</span><span> </span><span>--server-port</span><span> </span><span>23333</span><span> </span><span>--api-keys</span><span> </span><span>internlm2</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p>更多设置参考：<a href="https://lmdeploy.readthedocs.io/en/latest/index.html" target="_blank">https://lmdeploy.readthedocs.io/en/latest/index.html</a></p><p>部署成功后，可以利用如下脚本调用部署的InternLM2-chat-1_8b模型并测试是否部署成功。</p><div><div><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>from</span><span> openai </span><span>import</span><span> OpenAI</span></div></div><div><div><div>2</div></div><div>
</div></div><div><div><div>3</div></div><div><span><span>client </span><span>=</span><span> </span><span>OpenAI</span><span>(</span></span></div></div><div><div><div>4</div></div><div><span>    </span><span>api_key</span><span><span> </span><span>=</span><span> </span></span><span>"internlm2"</span><span>,</span></div></div><div><div><div>5</div></div><div><span>    </span><span>base_url</span><span><span> </span><span>=</span><span> </span></span><span>"http://0.0.0.0:23333/v1"</span></div></div><div><div><div>6</div></div><div><span>)</span></div></div><div><div><div>7</div></div><div>
</div></div><div><div><div>8</div></div><div><span><span>response </span><span>=</span><span> client.chat.completions.</span><span>create</span><span>(</span></span></div></div><div><div><div>9</div></div><div><span>    </span><span>model</span><span><span>=</span><span>client.models.</span><span>list</span><span>().data[</span></span><span>0</span><span>].id,</span></div></div><div><div><div>10</div></div><div><span>    </span><span>messages</span><span><span>=</span><span>[</span></span></div></div><div><div><div>11</div></div><div><span><span>        </span></span><span>{</span><span>"role"</span><span>: </span><span>"system"</span><span>, </span><span>"content"</span><span>: </span><span>"请介绍一下你自己"</span><span>}</span></div></div><div><div><div>12</div></div><div><span><span>    </span></span><span>]</span></div></div><div><div><div>13</div></div><div><span>)</span></div></div><div><div><div>14</div></div><div>
</div></div><div><div><div>15</div></div><div><span>print</span><span>(response.choices[</span><span>0</span><span>].message.content)</span></div></div></code></pre><div><div></div><div></div></div></figure><div></div></div><span>展开</span><span>收起</span></div></div><p>服务启动完成后，可以按Ctrl+B进入<code>tmux</code>的控制模式，然后按D退出窗口连接，更多操作<a href="https://aik9.top/" target="_blank">参考</a>。</p><hr /></section></section><section><h3>作业：<a href="#作业"><span>#</span></a></h3><p>基础任务 (完成此任务即完成闯关)：</p><ul>
<li>
<p><strong>背景问题</strong>：近期相关研究发现，LLM在对比浮点数字时表现不佳，经验证，internlm2-chat-1.8b (internlm2-chat-7b)也存在这一问题，例如认为<code>13.8&lt;13.11</code>。</p>
</li>
<li>
<p><strong>任务要求</strong>：利用LangGPT优化提示词，使LLM输出正确结果。<strong>完成一次并提交截图即可</strong></p>
</li>
</ul><p>使用GPTS中LangGPT提示词专家，配合我们的需求生成LangGPT结构化提示词</p><p><img alt="" loading="lazy" width="1636" height="1665" src="/_astro/1.YgbfaEj3_ZxXjp5.webp" />
<strong>生成结果如下</strong>：</p><div><div><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span># Role: 数学助手</span></div></div><div><div><div>2</div></div><div>
</div></div><div><div><div>3</div></div><div><span>## Profile</span></div></div><div><div><div>4</div></div><div><span>- author: LangGPT</span></div></div><div><div><div>5</div></div><div><span>- version: 1.0</span></div></div><div><div><div>6</div></div><div><span>- language: 中文</span></div></div><div><div><div>7</div></div><div><span>- description: 一个能够进行基本数学加减法运算，并能够比较两个数字大小的助手。</span></div></div><div><div><div>8</div></div><div>
</div></div><div><div><div>9</div></div><div><span>## Skills</span></div></div><div><div><div>10</div></div><div><span>1. 能够进行基本的数学加法和减法运算。</span></div></div><div><div><div>11</div></div><div><span>2. 能够比较两个数字的大小，并给出相应的判断。</span></div></div><div><div><div>12</div></div><div><span>3. 能够理解用户输入的简单数学表达式或问题描述。</span></div></div><div><div><div>13</div></div><div>
</div></div><div><div><div>14</div></div><div><span>## Rules</span></div></div><div><div><div>15</div></div><div><span>1. 用户输入可以是任意两个数字或一个包含加法、减法的表达式。</span></div></div><div><div><div>16</div></div><div><span>2. 当输入两个数字时，助手应比较它们的大小，并返回结果。</span></div></div><div><div><div>17</div></div><div><span>3. 当输入一个加法或减法表达式时，助手应计算出结果并返回。</span></div></div><div><div><div>18</div></div><div><span>4. 如果用户输入有误或不符合数学表达式规则，助手应给出提示并要求重新输入。</span></div></div><div><div><div>19</div></div><div>
</div></div><div><div><div>20</div></div><div><span>## Workflows</span></div></div><div><div><div>21</div></div><div><span>1. 接收用户输入的数字或数学表达式。</span></div></div><div><div><div>22</div></div><div><span>2. 判断输入的类型（两个数字或数学表达式）。</span></div></div><div><div><div>23</div></div><div><span>3. 如果是两个数字，执行大小比较，并返回结果。</span></div></div><div><div><div>24</div></div><div><span>4. 如果是数学表达式，进行加法或减法计算，返回结果。</span></div></div><div><div><div>25</div></div><div><span>5. 如果输入不符合预期格式，返回错误提示，要求用户重新输入。</span></div></div><div><div><div>26</div></div><div>
</div></div><div><div><div>27</div></div><div><span>## Init</span></div></div><div><div><div>28</div></div><div><span>1. 向用户介绍助手的功能和使用方法。</span></div></div><div><div><div>29</div></div><div><span>2. 提示用户可以输入两个数字进行比较，或输入一个加法、减法表达式进行计算。</span></div></div></code></pre><div><div></div><div></div></div></figure><div></div></div><span>展开</span><span>收起</span></div></div><p><strong>加入系统提示词前：</strong>
PS：估计InternLM2-chat-1_8b版本太久远了，所以回答不出来🤔
<img alt="" loading="lazy" width="2318" height="1277" src="/_astro/2.zdhPAbLE_2tBdG0.webp" /></p><p><strong>加入系统提示词后：</strong>
PS：效果明显变好了😋
<img alt="" loading="lazy" width="2318" height="1277" src="/_astro/3.ttNnI3tf_1sz6sK.webp" /></p></section><section><h3>Reference：<a href="#reference"><span>#</span></a></h3><p>1.LangGPT社区：<a href="https://langgptai.feishu.cn/wiki/QaArwzc7biR5nqkSo3mcwzGfnhf" target="_blank">LangGPT结构化提示词 - 飞书云文档</a>
2.浦语开源文档<a href="https://github.com/InternLM/Tutorial/tree/camp3/docs/L1/Prompt" target="_blank">书生浦语-浦语提示词工程实践</a>
3.文档：<a href="https://mp.weixin.qq.com/s/N9BrkDqvkIHQD7TTnhNk6Q" target="_blank">系统论述文章： 构建高性能 Prompt 之路——结构化 Prompt</a></p></section></section>
<section><h2>RAG：给助手接入自己的知识库<a href="#rag给助手接入自己的知识库"><span>#</span></a></h2><p>当问题来自私有文档或模型训练后的新知识时，仅修改 Prompt 不够。RAG 会先检索相关资料，再把检索结果交给模型生成答案。</p><section><h3>一、前置知识<a href="#一前置知识"><span>#</span></a></h3><ul>
<li><strong>给模型注入新知识的方式</strong>：
<ul>
<li>内部方式：更新模型的权重，但训练代价较大。</li>
<li>外部方式：给模型注入额外的上下文或外部信息，不改变其权重。</li>
</ul>
</li>
<li><strong>RAG 工作原理</strong>：
<ul>
<li>将问题编码成向量，在向量数据库中找到最相关的文档块（top-k chunks）。</li>
<li>将知识源分割成小块，编码成向量并存储在向量数据库中。</li>
<li>将检索到的文档块与原始问题一起作为提示输入到 LLM 中，生成最终的回答。</li>
</ul>
</li>
<li><strong>RAG 效果比对</strong>：
<ul>
<li>由于 <code>xtuner</code> 是较新的框架，<code>InternLM2-Chat-1.8B</code> 训练数据库中未收录相关信息，使用 RAG 前问答均未给出准确答案，使用后能获得想要的答案。</li>
</ul>
</li>
</ul></section><section><h3>二、环境、模型准备<a href="#二环境模型准备"><span>#</span></a></h3><section><h4>（一）配置基础环境<a href="#一配置基础环境"><span>#</span></a></h4><ul>
<li>
<p>在 <code>Intern Studio</code> 服务器上部署 <code>LlamaIndex</code>：</p>
<ul>
<li>打开 <code>Intern Studio</code> 界面，点击 <code>创建开发机</code> 配置开发机系统。</li>
<li>填写 <code>开发机名称</code> 后，点击 <code>选择镜像</code> 使用 <code>Cuda11.7-conda</code> 镜像，在资源配置中选择 <code>30% A100 * 1</code> 的选项，立即创建开发机器。</li>
<li>进入开发机后，创建新的 <code>conda</code> 环境，命名为 <code>llamaindex</code>，运行以下命令：</li>
</ul>
<div><figure><figcaption><span></span><span>Terminal window</span></figcaption><pre><code><div><div><div>1</div></div><div><span>conda</span><span> </span><span>create</span><span> </span><span>-n</span><span> </span><span>llamaindex</span><span> </span><span>python=</span><span>3.10</span></div></div><div><div><div>2</div></div><div><span>conda</span><span> </span><span>env</span><span> </span><span>list</span></div></div><div><div><div>3</div></div><div><span>conda</span><span> </span><span>activate</span><span> </span><span>llamaindex</span></div></div><div><div><div>4</div></div><div><span>conda</span><span> </span><span>install</span><span> </span><span>pytorch</span><span>**</span><span>2.0.1</span><span> </span><span>torchvision</span><span>**</span><span>0.15.2</span><span> </span><span>torchaudio==</span><span>2.0.2</span><span> </span><span>pytorch-cuda=</span><span>11.7</span><span> </span><span>-c</span><span> </span><span>pytorch</span><span> </span><span>-c</span><span> </span><span>nvidia</span></div></div><div><div><div>5</div></div><div><span>pip</span><span> </span><span>install</span><span> </span><span>einops</span><span>**</span><span>0.7.0</span><span> </span><span>protobuf</span><span>**</span><span>5.26.1</span></div></div></code></pre><div><div></div><div></div></div></figure></div>
<ul>
<li>环境激活后，命令行左边会显示当前环境名称。</li>
</ul>
</li>
</ul></section><section><h4>（二）安装 LlamaIndex<a href="#二安装-llamaindex"><span>#</span></a></h4><ul>
<li>
<p>安装 <code>LlamaIndex</code> 和相关的包：</p>
<div><figure><figcaption><span></span><span>Terminal window</span></figcaption><pre><code><div><div><div>1</div></div><div><span>conda</span><span> </span><span>activate</span><span> </span><span>llamaindex</span></div></div><div><div><div>2</div></div><div><span>pip</span><span> </span><span>install</span><span> </span><span>llama-index</span><span>**</span><span>0.10.38</span><span> </span><span>llama-index-llms-huggingface</span><span>**</span><span>0.2.0</span><span> </span><span>"transformers[torch]**4.41.1"</span><span> </span><span>"huggingface_hub[inference]**0.23.1"</span><span> </span><span>huggingface_hub</span><span>**</span><span>0.23.1</span><span> </span><span>sentence-transformers</span><span>**</span><span>2.7.0</span><span> </span><span>sentencepiece==</span><span>0.2.0</span></div></div></code></pre><div><div></div><div></div></div></figure></div>
</li>
</ul></section><section><h4>（三）下载 Sentence Transformer 词嵌入模型<a href="#三下载-sentence-transformer-词嵌入模型"><span>#</span></a></h4><ul>
<li>
<p>新建一个 <code>python</code> 文件，贴入以下代码：</p>
<div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>import</span><span> os</span></div></div><div><div><div>2</div></div><div>
</div></div><div><div><div>3</div></div><div><span># 设置环境变量</span></div></div><div><div><div>4</div></div><div><span>os.environ[</span><span>'HF_ENDPOINT'</span><span><span>] </span><span>=</span><span> </span></span><span>'https://hf-mirror.com'</span></div></div><div><div><div>5</div></div><div>
</div></div><div><div><div>6</div></div><div><span># 下载模型</span></div></div><div><div><div>7</div></div><div><span><span>os.</span><span>system</span><span>(</span></span><span>'huggingface-cli download --resume-download sentence-transformers/paraphrase-multilingual-MiniLM-L12-v2 --local-dir /root/model/sentence-transformer'</span><span>)</span></div></div></code></pre><div><div></div><div></div></div></figure></div>
</li>
<li>
<p>在 <code>/root/llamaindex_demo</code> 目录下执行该脚本自动开始下载。</p>
</li>
</ul></section><section><h4>（四）下载 NLTK 相关资源<a href="#四下载-nltk-相关资源"><span>#</span></a></h4><ul>
<li>
<p>使用以下命令下载 <code>nltk</code> 资源并解压到服务器上：</p>
<div><figure><figcaption><span></span><span>Terminal window</span></figcaption><pre><code><div><div><div>1</div></div><div><span>cd</span><span> </span><span>/root</span></div></div><div><div><div>2</div></div><div><span>git</span><span> </span><span>clone</span><span> </span><span>https://gitee.com/yzy0612/nltk_data.git</span><span> </span><span>--branch</span><span> </span><span>gh-pages</span></div></div><div><div><div>3</div></div><div><span>cd</span><span> </span><span>nltk_data</span></div></div><div><div><div>4</div></div><div><span>mv</span><span> </span><span>packages/</span><span>*</span><span> </span><span>./</span></div></div><div><div><div>5</div></div><div><span>cd</span><span> </span><span>tokenizers</span></div></div><div><div><div>6</div></div><div><span>unzip</span><span> </span><span>punkt.zip</span></div></div><div><div><div>7</div></div><div><span>cd</span><span> </span><span>../taggers</span></div></div><div><div><div>8</div></div><div><span>unzip</span><span> </span><span>averaged_perceptron_tagger.zip</span></div></div></code></pre><div><div></div><div></div></div></figure></div>
</li>
</ul></section></section><section><h3>三、LlamaIndex HuggingFaceLLM<a href="#三llamaindex-huggingfacellm"><span>#</span></a></h3><ul>
<li>
<p>运行以下指令，把 <code>InternLM2 1.8B</code> 软连接出来：</p>
<div><figure><figcaption><span></span><span>Terminal window</span></figcaption><pre><code><div><div><div>1</div></div><div><span>cd</span><span> </span><span>~/model</span></div></div><div><div><div>2</div></div><div><span>ln</span><span> </span><span>-s</span><span> </span><span>/root/share/new_models/Shanghai_AI_Laboratory/internlm2-chat-1_8b/</span><span> </span><span>./</span></div></div></code></pre><div><div></div><div></div></div></figure></div>
</li>
<li>
<p>新建一个 <code>python</code> 文件，贴入以下代码：</p>
<div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>from</span><span> llama_index.llms.huggingface </span><span>import</span><span> HuggingFaceLLM</span></div></div><div><div><div>2</div></div><div><span>from</span><span> llama_index.core.llms </span><span>import</span><span> ChatMessage</span></div></div><div><div><div>3</div></div><div>
</div></div><div><div><div>4</div></div><div><span><span>llm </span><span>=</span><span> </span><span>HuggingFaceLLM</span><span>(</span></span></div></div><div><div><div>5</div></div><div><span>    </span><span>model_name</span><span>=</span><span>"/root/model/internlm2-chat-1_8b"</span><span>,</span></div></div><div><div><div>6</div></div><div><span>    </span><span>tokenizer_name</span><span>=</span><span>"/root/model/internlm2-chat-1_8b"</span><span>,</span></div></div><div><div><div>7</div></div><div><span>    </span><span>model_kwargs</span><span><span>=</span><span>{</span></span><span>"trust_remote_code"</span><span>: </span><span>True</span><span>},</span></div></div><div><div><div>8</div></div><div><span>    </span><span>tokenizer_kwargs</span><span><span>=</span><span>{</span></span><span>"trust_remote_code"</span><span>: </span><span>True</span><span>}</span></div></div><div><div><div>9</div></div><div><span>)</span></div></div><div><div><div>10</div></div><div>
</div></div><div><div><div>11</div></div><div><span><span>rsp </span><span>=</span><span> llm.</span><span>chat</span><span>(</span></span><span>messages</span><span><span>=</span><span>[</span><span>ChatMessage</span><span>(</span></span><span>content</span><span>=</span><span>"xtuner 是什么？"</span><span>)])</span></div></div><div><div><div>12</div></div><div><span>print</span><span>(rsp)</span></div></div></code></pre><div><div></div><div></div></div></figure></div>
</li>
<li>
<p>运行以下命令：</p>
<div><figure><figcaption><span></span><span>Terminal window</span></figcaption><pre><code><div><div><div>1</div></div><div><span>conda</span><span> </span><span>activate</span><span> </span><span>llamaindex</span></div></div><div><div><div>2</div></div><div><span>cd</span><span> </span><span>~/llamaindex_demo/</span></div></div><div><div><div>3</div></div><div><span>python</span><span> </span><span>llamaindex_internlm.py</span></div></div></code></pre><div><div></div><div></div></div></figure></div>
</li>
<li>
<p>结果回答效果并不好，不是想要的 <code>xtuner</code>。</p>
</li>
</ul></section><section><h3>四、LlamaIndex RAG<a href="#四llamaindex-rag"><span>#</span></a></h3><ul>
<li>
<p>安装 <code>LlamaIndex</code> 词嵌入向量依赖：</p>
<div><figure><figcaption><span></span><span>Terminal window</span></figcaption><pre><code><div><div><div>1</div></div><div><span>conda</span><span> </span><span>activate</span><span> </span><span>llamaindex</span></div></div><div><div><div>2</div></div><div><span>pip</span><span> </span><span>install</span><span> </span><span>llama-index-embeddings-huggingface</span><span>**</span><span>0.2.0</span><span> </span><span>llama-index-embeddings-instructor</span><span>**</span><span>0.1.3</span></div></div></code></pre><div><div></div><div></div></div></figure></div>
</li>
<li>
<p>获取知识库：</p>
<div><figure><figcaption><span></span><span>Terminal window</span></figcaption><pre><code><div><div><div>1</div></div><div><span>cd</span><span> </span><span>~/llamaindex_demo</span></div></div><div><div><div>2</div></div><div><span>mkdir</span><span> </span><span>data</span></div></div><div><div><div>3</div></div><div><span>cd</span><span> </span><span>data</span></div></div><div><div><div>4</div></div><div><span>git</span><span> </span><span>clone</span><span> </span><span>https://github.com/InternLM/xtuner.git</span></div></div><div><div><div>5</div></div><div><span>mv</span><span> </span><span>xtuner/README_zh-CN.md</span><span> </span><span>./</span></div></div></code></pre><div><div></div><div></div></div></figure></div>
</li>
<li>
<p>新建一个 <code>python</code> 文件，贴入以下代码：</p>
<div><div><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>from</span><span> llama_index.core </span><span>import</span><span> VectorStoreIndex, SimpleDirectoryReader, Settings</span></div></div><div><div><div>2</div></div><div><span>from</span><span> llama_index.embeddings.huggingface </span><span>import</span><span> HuggingFaceEmbedding</span></div></div><div><div><div>3</div></div><div><span>from</span><span> llama_index.llms.huggingface </span><span>import</span><span> HuggingFaceLLM</span></div></div><div><div><div>4</div></div><div>
</div></div><div><div><div>5</div></div><div><span># 初始化一个 HuggingFaceEmbedding 对象，用于将文本转换为向量表示</span></div></div><div><div><div>6</div></div><div><span><span>embed_model </span><span>=</span><span> </span><span>HuggingFaceEmbedding</span><span>(</span></span></div></div><div><div><div>7</div></div><div><span>    </span><span>model_name</span><span>=</span><span>"/root/model/sentence-transformer"</span></div></div><div><div><div>8</div></div><div><span>)</span></div></div><div><div><div>9</div></div><div><span><span>Settings.embed_model </span><span>=</span><span> embed_model</span></span></div></div><div><div><div>10</div></div><div>
</div></div><div><div><div>11</div></div><div><span><span>llm </span><span>=</span><span> </span><span>HuggingFaceLLM</span><span>(</span></span></div></div><div><div><div>12</div></div><div><span>    </span><span>model_name</span><span>=</span><span>"/root/model/internlm2-chat-1_8b"</span><span>,</span></div></div><div><div><div>13</div></div><div><span>    </span><span>tokenizer_name</span><span>=</span><span>"/root/model/internlm2-chat-1_8b"</span><span>,</span></div></div><div><div><div>14</div></div><div><span>    </span><span>model_kwargs</span><span><span>=</span><span>{</span></span><span>"trust_remote_code"</span><span>: </span><span>True</span><span>},</span></div></div><div><div><div>15</div></div><div><span>    </span><span>tokenizer_kwargs</span><span><span>=</span><span>{</span></span><span>"trust_remote_code"</span><span>: </span><span>True</span><span>}</span></div></div><div><div><div>16</div></div><div><span>)</span></div></div><div><div><div>17</div></div><div><span><span>Settings.llm </span><span>=</span><span> llm</span></span></div></div><div><div><div>18</div></div><div>
</div></div><div><div><div>19</div></div><div><span># 从指定目录读取所有文档，并加载数据到内存中</span></div></div><div><div><div>20</div></div><div><span><span>documents </span><span>=</span><span> </span><span>SimpleDirectoryReader</span><span>(</span></span><span>"/root/llamaindex_demo/data"</span><span><span>).</span><span>load_data</span><span>()</span></span></div></div><div><div><div>21</div></div><div><span><span>index </span><span>=</span><span> VectorStoreIndex.</span><span>from_documents</span><span>(documents)</span></span></div></div><div><div><div>22</div></div><div><span><span>query_engine </span><span>=</span><span> index.</span><span>as_query_engine</span><span>()</span></span></div></div><div><div><div>23</div></div><div><span><span>response </span><span>=</span><span> query_engine.</span><span>query</span><span>(</span></span><span>"xtuner 是什么?"</span><span>)</span></div></div><div><div><div>24</div></div><div>
</div></div><div><div><div>25</div></div><div><span>print</span><span>(response)</span></div></div></code></pre><div><div></div><div></div></div></figure><div></div></div><span>展开</span><span>收起</span></div></div>
</li>
<li>
<p>运行以下命令：</p>
<div><figure><figcaption><span></span><span>Terminal window</span></figcaption><pre><code><div><div><div>1</div></div><div><span>conda</span><span> </span><span>activate</span><span> </span><span>llamaindex</span></div></div><div><div><div>2</div></div><div><span>cd</span><span> </span><span>~/llamaindex_demo/</span></div></div><div><div><div>3</div></div><div><span>python</span><span> </span><span>llamaindex_RAG.py</span></div></div></code></pre><div><div></div><div></div></div></figure></div>
</li>
<li>
<p>结果借助 RAG 技术后，能获得想要的答案。</p>
</li>
</ul></section><section><h3>五、LlamaIndex Web<a href="#五llamaindex-web"><span>#</span></a></h3><ul>
<li>
<p>安装依赖：</p>
<div><figure><figcaption><span></span><span>Terminal window</span></figcaption><pre><code><div><div><div>1</div></div><div><span>pip</span><span> </span><span>install</span><span> </span><span>streamlit==</span><span>1.36.0</span></div></div></code></pre><div><div></div><div></div></div></figure></div>
</li>
<li>
<p>新建一个 <code>python</code> 文件，贴入以下代码：</p>
<div><div><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>import</span><span> streamlit </span><span>as</span><span> st</span></div></div><div><div><div>2</div></div><div><span>from</span><span> llama_index.core </span><span>import</span><span> VectorStoreIndex, SimpleDirectoryReader, Settings</span></div></div><div><div><div>3</div></div><div><span>from</span><span> llama_index.embeddings.huggingface </span><span>import</span><span> HuggingFaceEmbedding</span></div></div><div><div><div>4</div></div><div><span>from</span><span> llama_index.llms.huggingface </span><span>import</span><span> HuggingFaceLLM</span></div></div><div><div><div>5</div></div><div>
</div></div><div><div><div>6</div></div><div><span><span>st.</span><span>set_page_config</span><span>(</span></span><span>page_title</span><span>=</span><span>"llama_index_demo"</span><span>, </span><span>page_icon</span><span>=</span><span>"🦜🔗"</span><span>)</span></div></div><div><div><div>7</div></div><div><span><span>st.</span><span>title</span><span>(</span></span><span>"llama_index_demo"</span><span>)</span></div></div><div><div><div>8</div></div><div>
</div></div><div><div><div>9</div></div><div><span># 初始化模型</span></div></div><div><div><div>10</div></div><div><span><span>@st</span><span>.</span><span>cache_resource</span></span></div></div><div><div><div>11</div></div><div><span>def</span><span> </span><span>init_models</span><span>():</span></div></div><div><div><div>12</div></div><div><span><span>    </span></span><span>embed_model </span><span>=</span><span> </span><span>HuggingFaceEmbedding</span><span>(</span></div></div><div><div><div>13</div></div><div><span>        </span><span>model_name</span><span>=</span><span>"/root/model/sentence-transformer"</span></div></div><div><div><div>14</div></div><div><span><span>    </span></span><span>)</span></div></div><div><div><div>15</div></div><div><span><span>    </span></span><span>Settings.embed_model </span><span>=</span><span> embed_model</span></div></div><div><div><div>16</div></div><div>
</div></div><div><div><div>17</div></div><div><span><span>    </span></span><span>llm </span><span>=</span><span> </span><span>HuggingFaceLLM</span><span>(</span></div></div><div><div><div>18</div></div><div><span>        </span><span>model_name</span><span>=</span><span>"/root/model/internlm2-chat-1_8b"</span><span>,</span></div></div><div><div><div>19</div></div><div><span>        </span><span>tokenizer_name</span><span>=</span><span>"/root/model/internlm2-chat-1_8b"</span><span>,</span></div></div><div><div><div>20</div></div><div><span>        </span><span>model_kwargs</span><span><span>=</span><span>{</span></span><span>"trust_remote_code"</span><span>: </span><span>True</span><span>},</span></div></div><div><div><div>21</div></div><div><span>        </span><span>tokenizer_kwargs</span><span><span>=</span><span>{</span></span><span>"trust_remote_code"</span><span>: </span><span>True</span><span>}</span></div></div><div><div><div>22</div></div><div><span><span>    </span></span><span>)</span></div></div><div><div><div>23</div></div><div><span><span>    </span></span><span>Settings.llm </span><span>=</span><span> llm</span></div></div><div><div><div>24</div></div><div>
</div></div><div><div><div>25</div></div><div><span><span>    </span></span><span>documents </span><span>=</span><span> </span><span>SimpleDirectoryReader</span><span>(</span><span>"/root/llamaindex_demo/data"</span><span><span>).</span><span>load_data</span><span>()</span></span></div></div><div><div><div>26</div></div><div><span><span>    </span></span><span>index </span><span>=</span><span> VectorStoreIndex.</span><span>from_documents</span><span>(documents)</span></div></div><div><div><div>27</div></div><div><span><span>    </span></span><span>query_engine </span><span>=</span><span> index.</span><span>as_query_engine</span><span>()</span></div></div><div><div><div>28</div></div><div>
</div></div><div><div><div>29</div></div><div><span>    </span><span>return</span><span> query_engine</span></div></div><div><div><div>30</div></div><div>
</div></div><div><div><div>31</div></div><div><span># 检查是否需要初始化模型</span></div></div><div><div><div>32</div></div><div><span>if</span><span> </span><span>'query_engine'</span><span> </span><span>not</span><span> </span><span>in</span><span> st.session_state:</span></div></div><div><div><div>33</div></div><div><span><span>    </span></span><span>st.session_state[</span><span>'query_engine'</span><span><span>] </span><span>=</span><span> </span><span>init_models</span><span>()</span></span></div></div><div><div><div>34</div></div><div>
</div></div><div><div><div>35</div></div><div><span>def</span><span> </span><span>greet2</span><span>(</span><span>question</span><span>):</span></div></div><div><div><div>36</div></div><div><span><span>    </span></span><span>response </span><span>=</span><span> st.session_state[</span><span>'query_engine'</span><span><span>].</span><span>query</span><span>(question)</span></span></div></div><div><div><div>37</div></div><div><span>    </span><span>return</span><span> response</span></div></div><div><div><div>38</div></div><div>
</div></div><div><div><div>39</div></div><div>
</div></div><div><div><div>40</div></div><div><span># Store LLM generated responses</span></div></div><div><div><div>41</div></div><div><span>if</span><span> </span><span>"messages"</span><span> </span><span>not</span><span> </span><span>in</span><span><span> st.session_state.</span><span>keys</span><span>():</span></span></div></div><div><div><div>42</div></div><div><span><span>    </span></span><span>st.session_state.messages </span><span>=</span><span> [{</span><span>"role"</span><span>: </span><span>"assistant"</span><span>, </span><span>"content"</span><span>: </span><span>"你好，我是你的助手，有什么我可以帮助你的吗？"</span><span>}]</span></div></div><div><div><div>43</div></div><div>
</div></div><div><div><div>44</div></div><div><span># Display or clear chat messages</span></div></div><div><div><div>45</div></div><div><span>for</span><span> message </span><span>in</span><span> st.session_state.messages:</span></div></div><div><div><div>46</div></div><div><span>    </span><span>with</span><span><span> st.</span><span>chat_message</span><span>(message[</span></span><span>"role"</span><span>]):</span></div></div><div><div><div>47</div></div><div><span><span>        </span></span><span>st.</span><span>write</span><span>(message[</span><span>"content"</span><span>])</span></div></div><div><div><div>48</div></div><div>
</div></div><div><div><div>49</div></div><div><span>def</span><span> </span><span>clear_chat_history</span><span>():</span></div></div><div><div><div>50</div></div><div><span><span>    </span></span><span>st.session_state.messages </span><span>=</span><span> [{</span><span>"role"</span><span>: </span><span>"assistant"</span><span>, </span><span>"content"</span><span>: </span><span>"你好，我是你的助手，有什么我可以帮助你的吗？"</span><span>}]</span></div></div><div><div><div>51</div></div><div>
</div></div><div><div><div>52</div></div><div><span><span>st.sidebar.</span><span>button</span><span>(</span></span><span>'Clear Chat History'</span><span>, </span><span>on_click</span><span><span>=</span><span>clear_chat_history)</span></span></div></div><div><div><div>53</div></div><div>
</div></div><div><div><div>54</div></div><div><span># Function for generating LLaMA2 response</span></div></div><div><div><div>55</div></div><div>
</div></div><div><div><div>56</div></div><div>
</div></div><div><div><div>57</div></div><div><span>def</span><span> </span><span>generate_llama_response</span><span>(</span><span>question</span><span>):</span></div></div><div><div><div>58</div></div><div><span><span>    </span></span><span>response </span><span>=</span><span> </span><span>greet2</span><span>(question)</span></div></div><div><div><div>59</div></div><div><span>    </span><span>return</span><span> response</span></div></div><div><div><div>60</div></div><div>
</div></div><div><div><div>61</div></div><div><span># User-provided prompt</span></div></div><div><div><div>62</div></div><div><span>if</span><span><span> prompt </span><span>:=</span><span> st.</span><span>chat_input</span><span>(</span></span><span>"What's on your mind?"</span><span>):</span></div></div><div><div><div>63</div></div><div><span><span>    </span></span><span>st.</span><span>chat_message</span><span>(</span><span>"user"</span><span><span>).</span><span>write</span><span>(prompt)</span></span></div></div><div><div><div>64</div></div><div><span>    </span><span>with</span><span><span> st.</span><span>chat_message</span><span>(</span></span><span>"assistant"</span><span>):</span></div></div><div><div><div>65</div></div><div><span><span>        </span></span><span>message_placeholder </span><span>=</span><span> st.</span><span>empty</span><span>()</span></div></div><div><div><div>66</div></div><div><span><span>        </span></span><span>response </span><span>=</span><span> </span><span>generate_llama_response</span><span>(prompt)</span></div></div><div><div><div>67</div></div><div><span><span>        </span></span><span>message_placeholder.</span><span>write</span><span>(response)</span></div></div><div><div><div>68</div></div><div><span><span>    </span></span><span>st.session_state.messages.</span><span>append</span><span>({</span><span>"role"</span><span>: </span><span>"assistant"</span><span>, </span><span>"content"</span><span>: response})</span></div></div></code></pre><div><div></div><div></div></div></figure><div></div></div><span>展开</span><span>收起</span></div></div>
</li>
<li>
<p>启动 Streamlit 服务：</p>
<div><figure><figcaption><span></span><span>Terminal window</span></figcaption><pre><code><div><div><div>1</div></div><div><span>cd</span><span> </span><span>~/llamaindex_demo/</span></div></div><div><div><div>2</div></div><div><span>streamlit</span><span> </span><span>run</span><span> </span><span>llamaindex_streamlit.py</span></div></div></code></pre><div><div></div><div></div></div></figure></div>
</li>
<li>
<p>打开浏览器，访问 <code>https://localhost:XXX</code>。</p>
</li>
</ul></section><section><h3>六、总结<a href="#六总结"><span>#</span></a></h3><ul>
<li>本文介绍如何结合 <code>LlamaIndex</code> 和 <code>InternLM2</code> 部署 <code>RAG</code>，为大模型注入最新知识库，效果显著。</li>
</ul></section><section><h3>七、作业<a href="#七作业"><span>#</span></a></h3><p><img alt="" loading="lazy" width="971" height="591" src="/_astro/1.84e-8dFa_ZdIQS.webp" /></p><p><strong>RAG前</strong></p><p><img alt="" loading="lazy" width="2479" height="1277" src="/_astro/2.DYmRlT_H_Z460u0.webp" /></p><p><strong>RAG后</strong></p><p><img alt="" loading="lazy" width="2309" height="1277" src="/_astro/3.CRIe1tEw_1Hs4v9.webp" /></p><p><strong>使用Streamlit部署webui（使用RAG）</strong></p><p><img alt="" loading="lazy" width="2309" height="1277" src="/_astro/4.CV2U4s9r_Z1yRDTn.webp" /></p></section></section>
<section><h2>Lagent：让助手能够调用工具<a href="#lagent让助手能够调用工具"><span>#</span></a></h2><p>RAG 解决“知道什么”，Agent 更关注“能够做什么”。这一部分记录 Lagent WebUI、模型服务、依赖问题和自定义工具的接入过程。</p><section><h3>启动webui服务<a href="#启动webui服务"><span>#</span></a></h3><p><strong>使用lmdeploy启动一个api_server</strong></p><div><figure><figcaption><span></span><span>Terminal window</span></figcaption><pre><code><div><div><div>1</div></div><div><span>conda</span><span> </span><span>activate</span><span> </span><span>agent_camp3</span></div></div><div><div><div>2</div></div><div><span>lmdeploy</span><span> </span><span>serve</span><span> </span><span>api_server</span><span> </span><span>/share/new_models/Shanghai_AI_Laboratory/internlm2_5-7b-chat</span><span> </span><span>--model-name</span><span> </span><span>internlm2_5-7b-chat</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p><img alt="" loading="lazy" width="2316" height="1282" src="/_astro/1.BbcMG7Nr_Z1ApRi7.webp" /></p><p><strong>另开一个终端</strong>，<strong>使用stremlit启动agent_web应用</strong></p><div><figure><figcaption><span></span><span>Terminal window</span></figcaption><pre><code><div><div><div>1</div></div><div><span>cd</span><span> </span><span>/root/agent_camp3/lagent</span></div></div><div><div><div>2</div></div><div><span>conda</span><span> </span><span>activate</span><span> </span><span>agent_camp3</span></div></div><div><div><div>3</div></div><div><span>streamlit</span><span> </span><span>run</span><span> </span><span>examples/internlm2_agent_web_demo.py</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p><img alt="" loading="lazy" width="2309" height="1274" src="/_astro/2.BY_1rlYI_ZSe1la.webp" /></p><p><strong>本地powershell建立ssh连接，进行端口映射</strong></p><p><img alt="" loading="lazy" width="1730" height="924" src="/_astro/3.CUvyMTK__Z1LqcUm.webp" /></p></section><section><h3>Q&amp;A<a href="#qa"><span>#</span></a></h3><section><h4>遇到的问题：<a href="#遇到的问题"><span>#</span></a></h4><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>ModuleNotFoundError: No module named 'griffe.enumerations</span></div></div></code></pre><div><div></div><div></div></div></figure></div></section><section><h4>解决方法：<a href="#解决方法"><span>#</span></a></h4><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>Due to griffe's recent 1.x release, the `griffe.enumerations` module has been removed, resulting in a break change, which can be resolved by</span></div></div><div><div><div>2</div></div><div><span>`pip install griffe==0.48`.</span></div></div></code></pre><div><div></div><div></div></div></figure></div></section></section><section><h3>修改模型名称以及模型IP地址<a href="#修改模型名称以及模型ip地址"><span>#</span></a></h3><p><strong>启动streamlit的web应用如下</strong>：</p><p><img alt="" loading="lazy" width="2309" height="1274" src="/_astro/4.CLa-Aptf_ZVekNx.webp" /></p><p><strong>修改红框内的内容</strong></p><p><img alt="" loading="lazy" width="2309" height="1274" src="/_astro/5.D57HoA1d_MFcTr.webp" /></p><p><strong>终端的反馈</strong>：</p><p>PS&lt;很是可惜&gt;，没有找到我想要的内容<a href="https://arxiv.org/abs/1706.03762" target="_blank">[1706.03762] Attention Is All You Need (arxiv.org)</a></p><p><img alt="" loading="lazy" width="2309" height="1274" src="/_astro/6.DHRN4M3q_Z1DBmKn.webp" /></p></section><section><h3>自定义工具<a href="#自定义工具"><span>#</span></a></h3><p><img alt="" loading="lazy" width="2309" height="1274" src="/_astro/7.DQcByGPF_nwxRD.webp" /></p><p><strong>进入lagent_web_demo的py文件中</strong></p><p><strong>添加&amp;修改代码：</strong></p><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>from</span><span> lagent.actions </span><span>import</span><span> ActionExecutor, ArxivSearch, IPythonInterpreter</span></div></div><div><div><div>2</div></div><div><span><span>+</span><span> </span></span><span>from</span><span> lagent.actions.magicmaker </span><span>import</span><span> MagicMaker</span></div></div><div><div><div>3</div></div><div><span>from</span><span> lagent.agents.internlm2_agent </span><span>import</span><span><span> </span><span>INTERPRETER_CN</span><span>, </span><span>META_CN</span><span>, </span><span>PLUGIN_CN</span><span>, Internlm2Agent, Internlm2Protocol</span></span></div></div><div><div><div>4</div></div><div>
</div></div><div><div><div>5</div></div><div><span>...</span></div></div><div><div><div>6</div></div><div><span><span>        </span></span><span>action_list </span><span>=</span><span> [</span></div></div><div><div><div>7</div></div><div><span><span>            </span></span><span>ArxivSearch</span><span>(),</span></div></div><div><div><div>8</div></div><div><span><span>+</span><span>             </span><span>MagicMaker</span><span>(),</span></span></div></div><div><div><div>9</div></div><div><span><span>        </span></span><span>]</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p><img alt="" loading="lazy" width="2309" height="1274" src="/_astro/8.wkCLf-Zp_24dApT.webp" /></p><p><strong>重新启动agent_web服务</strong></p><p>使用<code>MagicMaker</code>工具绘画</p><p><strong>Prompt</strong>：帮我画一个微笑的女孩，穿着校服在学校的走廊上，阳光照耀在她身上</p><p><img alt="" loading="lazy" width="2309" height="1274" src="/_astro/9.C7d1Z5MD_Zlufjw.webp" /></p><p><strong>可以看到对于用户输入的提示词，有自动进行填充完善</strong></p><p><img alt="" loading="lazy" width="2309" height="1274" src="/_astro/12.MtkHcn_X_buNt7.webp" /></p><p><img alt="" loading="lazy" width="1381" height="278" src="/_astro/13.cIBHWkXr_Z1akOzT.webp" /></p></section></section>
<section><h2>XTuner：把稳定需求固化到模型中<a href="#xtuner把稳定需求固化到模型中"><span>#</span></a></h2><p>微调适合处理长期、重复且有训练样本支持的能力或风格。它不应替代知识库和工具系统；知识经常变化时优先使用 RAG，需要执行动作时仍要使用 Agent。原始记录主要保存了任务要求与结果截图，因此这里不补写未经验证的训练参数。</p><section><h3>任务内容<a href="#任务内容"><span>#</span></a></h3><p><img alt="" loading="lazy" width="1257" height="1102" src="/_astro/1.Cn2mcB7l_1jTK6V.webp" /></p></section><section><h3>完成任务截图<a href="#完成任务截图"><span>#</span></a></h3><p><img alt="" loading="lazy" width="2309" height="1277" src="/_astro/2.CQbjbNR1_2ckAlP.webp" /></p></section></section>
<section><h2>四种方案如何组合<a href="#四种方案如何组合"><span>#</span></a></h2><p>一个实用的个人助手可以采用这样的工作流：用户输入先经过结构化 Prompt 整理；系统判断是否需要检索本地知识库；如果任务要求搜索、绘图或运行代码，再交给 Agent 调用工具；模型本身则通过少量高质量微调数据保持稳定的人设和表达习惯。</p></section>
<section><h2>实践建议<a href="#实践建议"><span>#</span></a></h2><ol>
<li>先用 Prompt 做最小可行版本，确认任务边界。</li>
<li>知识变化频繁时使用 RAG，不要反复微调事实数据。</li>
<li>工具权限应尽量最小化，并为调用结果增加校验。</li>
<li>只有当需求稳定、样本可靠、收益明确时再进行微调。</li>
<li>用固定测试集分别验证准确率、引用质量、工具成功率和风格一致性。</li>
</ol><p>从 Prompt 到 RAG、Agent、微调，本质上是在逐步扩展助手的可控性、知识范围、行动能力和个性化程度。</p></section>]]></content>
    </entry>
    <entry>
      <id>https://www.celyn-blog.xyz/posts/internlm-deployment-and-evaluation/</id>
      <title type="text">从认识 InternLM 到 8G 显存部署：Demo、LMDeploy 与 OpenCompass 评测</title>
      <published>2024-08-08T00:00:00.000Z</published>
      <updated>2026-08-18T00:00:00.000Z</updated>
      <author><name>Celyn</name></author>
      <link rel="alternate" href="https://www.celyn-blog.xyz/posts/internlm-deployment-and-evaluation/"/>
      <summary type="text">梳理 InternLM 全链路开源体系，并记录在 8G 显存环境运行 CLI、Web Demo、多模态模型以及使用 OpenCompass 进行评测的过程。</summary>
      <content type="html"><![CDATA[<p>这篇文章把“认识模型、运行模型、评估模型”三段学习记录合并在一起。目标不是只把 Demo 跑起来，而是形成一条从生态理解到低显存部署，再到能力验证的完整路径。</p>
<section><h2>阅读路线<a href="#阅读路线"><span>#</span></a></h2><ol>
<li>先了解 InternLM 的模型、数据、训练、部署和评测工具链。</li>
<li>在 8G 显存环境中运行文本与多模态 Demo。</li>
<li>使用 LMDeploy 提供更适合实际调用的推理服务。</li>
<li>使用 OpenCompass 对模型能力进行可重复的评测。</li>
</ol><p>部署成功只说明环境和推理链路能够工作，并不代表模型适合目标任务。最终仍需要结合显存占用、响应速度、输出质量和评测结果进行选择。</p></section>
<section><h2>InternLM 全链路开源体系<a href="#internlm-全链路开源体系"><span>#</span></a></h2><p>先从全局理解书生·浦语生态。模型只是其中一环，数据处理、训练、微调、部署、智能体和评测共同组成完整工具链。</p><section><h3>博客：书生·浦语大模型全链路开源开放体系及其最新发展<a href="#博客书生浦语大模型全链路开源开放体系及其最新发展"><span>#</span></a></h3><p><img alt="" loading="lazy" width="1311" height="1019" src="/_astro/1.DBP4HcTJ_SPVgh.webp" /></p><p>在现代人工智能技术的迅猛发展浪潮中，书生·浦语大模型全链路开源开放体系以其独特的优势和卓越的性能，在各个领域中不断取得突破性进展。本文将详细介绍该体系的发展历程、最新版本的特征、基于规则、模型和反馈的数据生成方法，以及mind search项目、开源数据提取工具和预训练框架、EXTINA的评测和部署、知识管理工具等各个方面的应用与优化策略。</p><section><h4>书生·浦语大模型全链路开源开放体系的历程及最新版本的特征<a href="#书生浦语大模型全链路开源开放体系的历程及最新版本的特征"><span>#</span></a></h4><p>书生·浦语大模型开源开放体系在多个方面表现出色，包括数据收集整理、模型训练、微调、评测和搜索引擎AI应用的部署等方面。最新版本书生·浦语大模型2.5在推理能力和短期记忆等方面有质的飞跃，并开放了label LLM项目，方便标注数据。此外，视频还介绍了模型的性能天梯和应用前景。</p><ul>
<li><strong>书生浦语大模型开源开放体系</strong></li>
<li><strong>InputLM2.5性能飞跃</strong></li>
<li><strong>迭代发展过程中的数据驱动模型性能</strong></li>
</ul></section><section><h4>基于规则、模型和反馈的数据生成方法，以及如何使用开源项目进行标注和推理<a href="#基于规则模型和反馈的数据生成方法以及如何使用开源项目进行标注和推理"><span>#</span></a></h4><p>反映模型的数据生成方法在模型优化和训练过程中至关重要，以下是一些关键方法的介绍：</p><ul>
<li><strong>基于模型的反馈数据生成方法</strong>：包括相似度对齐和基于反馈的强化训练。</li>
<li><strong>大海捞针实验</strong>：介绍模型的推理能力和大海捞针实验，模型在处理稀长背景知识的表现。</li>
<li><strong>问题匹配分块</strong>：介绍问题匹配分块的方法，可以用于语言模型的索引和问题理解。</li>
</ul></section><section><h4>mind search项目<a href="#mind-search项目"><span>#</span></a></h4><p>mind search项目展示了其在语言模型、预训练模型、微调框架、部署工具等方面的强大功能和应用潜力。</p><ul>
<li><strong>Mindsearch项目和书生普与开源模型谱系</strong></li>
<li><strong>部署工具和评测工具</strong></li>
<li><strong>应用方面的工具和框架</strong></li>
</ul></section><section><h4>开源数据提取工具Minor U和预训练框架XTNER<a href="#开源数据提取工具minor-u和预训练框架xtner"><span>#</span></a></h4><p>Minor U和XTNER提供了无缝衔接和数据格式兼容性的功能，极大地加速了模型的训练和优化过程。</p><ul>
<li><strong>智能体框架和开源工具无缝衔接</strong></li>
<li><strong>微调框架和数据格式兼容性</strong></li>
<li><strong>使用XTNER进行微调的方法和流程</strong></li>
</ul></section><section><h4>EXTINA的评测和部署<a href="#extina的评测和部署"><span>#</span></a></h4><p>EXTINA通过微调和Open Compass评测模型，以及lm deploy的推理性能对比，构建了智能体框架建设方案。</p><ul>
<li><strong>使用EXTINA微调和Open Compass评测模型</strong></li>
<li><strong>lm deploy模型部署框架和智能体框架</strong></li>
<li><strong>基于AI的搜索引擎和知识插件</strong></li>
</ul></section><section><h4>知识管理工具笞香豆的企业级应用<a href="#知识管理工具笞香豆的企业级应用"><span>#</span></a></h4><p>知识管理工具笞香豆通过可视化、强生和知识图谱生成，支持搜索和思考过程的展示，推动了开源生态系统的完善和实践营的创新发展。</p><ul>
<li><strong>思维可视化</strong>：通过搜索和思考1900年和1924年的相关信息，展示了模拟人脑思维逻辑的可视化过程。</li>
<li><strong>支持检索增强生成和知识图谱</strong>：理解群体行为。</li>
<li><strong>赋能创新</strong>：通过开源生态实践营，持续以高质量的开源赋能创新。</li>
</ul><p>在人工智能的快速发展中，书生·浦语大模型全链路开源开放体系无疑是一颗璀璨的明珠。它不仅在技术上不断推陈出新，在应用上也不断拓展，为各行各业带来了无限的可能。期待未来它能为更多领域提供支持，推动社会的智能化发展。</p></section></section></section>
<section><h2>8G 显存环境下的模型部署<a href="#8g-显存环境下的模型部署"><span>#</span></a></h2><p>在资源有限的环境中，模型规模、精度、量化方式和推理框架必须一起考虑。下面保留 CLI、Streamlit Web Demo、XComposer 和 InternVL2 的实际部署记录。</p><section><h3>环境配置<a href="#环境配置"><span>#</span></a></h3><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span># 创建环境</span></div></div><div><div><div>2</div></div><div><span>conda create -n demo python=3.10 -y</span></div></div><div><div><div>3</div></div><div><span># 激活环境</span></div></div><div><div><div>4</div></div><div><span>conda activate demo</span></div></div><div><div><div>5</div></div><div><span># 安装 torch</span></div></div><div><div><div>6</div></div><div><span>conda install pytorch**2.1.2 torchvision**0.16.2 torchaudio==2.1.2 pytorch-cuda=12.1 -c pytorch -c nvidia -y</span></div></div><div><div><div>7</div></div><div><span># 安装其他依赖</span></div></div><div><div><div>8</div></div><div><span>pip install transformers==4.38</span></div></div><div><div><div>9</div></div><div><span>pip install sentencepiece==0.1.99</span></div></div><div><div><div>10</div></div><div><span>pip install einops==0.8.0</span></div></div><div><div><div>11</div></div><div><span>pip install protobuf==5.27.2</span></div></div><div><div><div>12</div></div><div><span>pip install accelerate==0.33.0</span></div></div><div><div><div>13</div></div><div><span>pip install streamlit==1.37.0</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p><img alt="" loading="lazy" width="2318" height="1206" src="/_astro/P1._nmN5EHo_Z2lLYsP.webp" /></p></section><section><h3>InternLM2-Chat-1.8B 模型部署<a href="#internlm2-chat-18b-模型部署"><span>#</span></a></h3><section><h4>一、用Cli Demo 部署<a href="#一用cli-demo-部署"><span>#</span></a></h4><p>1.创建<code>demo</code>文件夹，用于存放代码。并创建 <code>cli_demo.py</code>文件</p><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>mkdir -p /root/demo</span></div></div><div><div><div>2</div></div><div><span>touch /root/demo/cli_demo.py</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p><img alt="" loading="lazy" width="467" height="535" src="/_astro/P2.ADceU7eY_3HQS6.webp" /></p><p>其中<code>cli_demo.py</code> 的代码为：</p><div><div><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>import</span><span> torch</span></div></div><div><div><div>2</div></div><div><span>from</span><span> transformers </span><span>import</span><span> AutoTokenizer, AutoModelForCausalLM</span></div></div><div><div><div>3</div></div><div>
</div></div><div><div><div>4</div></div><div>
</div></div><div><div><div>5</div></div><div><span><span>model_name_or_path </span><span>=</span><span> </span></span><span>"/root/share/new_models/Shanghai_AI_Laboratory/internlm2-chat-1_8b"</span></div></div><div><div><div>6</div></div><div>
</div></div><div><div><div>7</div></div><div><span><span>tokenizer </span><span>=</span><span> AutoTokenizer.</span><span>from_pretrained</span><span>(model_name_or_path, </span></span><span>trust_remote_code</span><span>=</span><span>True</span><span>, </span><span>device_map</span><span>=</span><span>'cuda:0'</span><span>)</span></div></div><div><div><div>8</div></div><div><span><span>model </span><span>=</span><span> AutoModelForCausalLM.</span><span>from_pretrained</span><span>(model_name_or_path, </span></span><span>trust_remote_code</span><span>=</span><span>True</span><span>, </span><span>torch_dtype</span><span><span>=</span><span>torch.bfloat16, </span></span><span>device_map</span><span>=</span><span>'cuda:0'</span><span>)</span></div></div><div><div><div>9</div></div><div><span><span>model </span><span>=</span><span> model.</span><span>eval</span><span>()</span></span></div></div><div><div><div>10</div></div><div>
</div></div><div><div><div>11</div></div><div><span><span>system_prompt </span><span>=</span><span> </span></span><span>"""You are an AI assistant whose name is InternLM (书生·浦语).</span></div></div><div><div><div>12</div></div><div><span>- InternLM (书生·浦语) is a conversational language model that is developed by Shanghai AI Laboratory (上海人工智能实验室). It is designed to be helpful, honest, and harmless.</span></div></div><div><div><div>13</div></div><div><span>- InternLM (书生·浦语) can understand and communicate fluently in the language chosen by the user such as English and 中文.</span></div></div><div><div><div>14</div></div><div><span>"""</span></div></div><div><div><div>15</div></div><div>
</div></div><div><div><div>16</div></div><div><span><span>messages </span><span>=</span><span> [(system_prompt, </span></span><span>''</span><span>)]</span></div></div><div><div><div>17</div></div><div>
</div></div><div><div><div>18</div></div><div><span>print</span><span>(</span><span>"===========**Welcome to InternLM chatbot, type 'exit' to exit.**==========="</span><span>)</span></div></div><div><div><div>19</div></div><div>
</div></div><div><div><div>20</div></div><div><span>while</span><span> </span><span>True</span><span>:</span></div></div><div><div><div>21</div></div><div><span><span>    </span></span><span>input_text </span><span>=</span><span> </span><span>input</span><span>(</span><span>"</span><span>\n</span><span>User  &gt;&gt;&gt; "</span><span>)</span></div></div><div><div><div>22</div></div><div><span><span>    </span></span><span>input_text </span><span>=</span><span> input_text.</span><span>replace</span><span>(</span><span>' '</span><span>, </span><span>''</span><span>)</span></div></div><div><div><div>23</div></div><div><span>    </span><span>if</span><span><span> input_text </span><span>==</span><span> </span></span><span>"exit"</span><span>:</span></div></div><div><div><div>24</div></div><div><span>        </span><span>break</span></div></div><div><div><div>25</div></div><div>
</div></div><div><div><div>26</div></div><div><span><span>    </span></span><span>length </span><span>=</span><span> </span><span>0</span></div></div><div><div><div>27</div></div><div><span>    </span><span>for</span><span> response, _ </span><span>in</span><span><span> model.</span><span>stream_chat</span><span>(tokenizer, input_text, messages):</span></span></div></div><div><div><div>28</div></div><div><span>        </span><span>if</span><span> response </span><span>is</span><span> </span><span>not</span><span> </span><span>None</span><span>:</span></div></div><div><div><div>29</div></div><div><span>            </span><span>print</span><span>(response[length:], </span><span>flush</span><span>=</span><span>True</span><span>, </span><span>end</span><span>=</span><span>""</span><span>)</span></div></div><div><div><div>30</div></div><div><span><span>            </span></span><span>length </span><span>=</span><span> </span><span>len</span><span>(response)</span></div></div></code></pre><div><div></div><div></div></div></figure><div></div></div><span>展开</span><span>收起</span></div></div><p>2.在终端执行<code>python /root/demo/cli_demo.py</code>命令启动Demo</p><p>3.使用 Cli Demo 完成 InternLM2-Chat-1.8B 模型的部署，并生成 300 字小故事</p><p><img alt="" loading="lazy" width="2318" height="1206" src="/_astro/P3.DpENYasR_ROsEB.webp" /></p></section><section><h4>二、用Streamlit Web Demo 部署<a href="#二用streamlit-web-demo-部署"><span>#</span></a></h4><p>1.clone InternLM 的github仓库 到本地：</p><div><figure><figcaption><span></span><span>Terminal window</span></figcaption><pre><code><div><div><div>1</div></div><div><span>cd</span><span> </span><span>/root/demo</span></div></div><div><div><div>2</div></div><div><span>git</span><span> </span><span>clone</span><span> </span><span>https://github.com/InternLM/Tutorial.git</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p>2.启动Streamlit 服务：</p><div><figure><figcaption><span></span><span>Terminal window</span></figcaption><pre><code><div><div><div>1</div></div><div><span>cd</span><span> </span><span>/root/demo</span></div></div><div><div><div>2</div></div><div><span>streamlit</span><span> </span><span>run</span><span> </span><span>/root/demo/Tutorial/tools/streamlit_demo.py</span><span> </span><span>--server.address</span><span> </span><span>127.0.0.1</span><span> </span><span>--server.port</span><span> </span><span>6006</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p><img alt="" loading="lazy" width="1769" height="409" src="/_astro/P4.8Z57rt-Z_Zsds1.webp" /></p><p>3.在<strong>本地</strong>的 PowerShell 中输入以下命令，将端口映射到本地：</p><div><figure><figcaption><span></span><span>Terminal window</span></figcaption><pre><code><div><div><div>1</div></div><div><span>ssh</span><span> </span><span>-CNg</span><span> </span><span>-L</span><span> </span><span>6006:127.0.0.1:6006</span><span> </span><span>root@ssh.intern-ai.org.cn</span><span> </span><span>-p</span><span> </span><span>你的</span><span> </span><span>ssh</span><span> </span><span>端口号</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p><img alt="" loading="lazy" width="1730" height="924" src="/_astro/P5.aJ6Qvc2o_Z1zCJ4B.webp" /></p><p>4.完成端口映射后，通过浏览器访问 <code>http://localhost:6006</code> 来启动我们的 Demo。</p><p><img alt="" loading="lazy" width="2318" height="1206" src="/_astro/P6.CYmE4Vq9_Z2frm4n.webp" /></p></section></section><section><h3>LMDeploy 部署<a href="#lmdeploy-部署"><span>#</span></a></h3><section><h4>一、InternLM-XComposer2-VL-1.8B 模型<a href="#一internlm-xcomposer2-vl-18b-模型"><span>#</span></a></h4><section><h5>模型介绍：<a href="#模型介绍"><span>#</span></a></h5><p>InternLM-XComposer2 是一款基于 InternLM2 的视觉语言大模型，其擅长自由形式的文本图像合成和理解。其主要特点包括：</p><ul>
<li>自由形式的交错文本图像合成：InternLM-XComposer2 可以根据大纲、详细文本要求和参考图像等不同输入，生成连贯且上下文相关，具有交错图像和文本的文章，从而实现高度可定制的内容创建。</li>
<li>准确的视觉语言问题解决：InternLM-XComposer2 基于自由形式的指令准确地处理多样化和具有挑战性的视觉语言问答任务，在识别，感知，详细标签，视觉推理等方面表现出色。</li>
</ul></section><section><h5>部署步骤：<a href="#部署步骤"><span>#</span></a></h5><p>1.激活环境并安装 LMDeploy 以及其他依赖。</p><div><figure><figcaption><span></span><span>Terminal window</span></figcaption><pre><code><div><div><div>1</div></div><div><span>conda</span><span> </span><span>activate</span><span> </span><span>demo</span></div></div><div><div><div>2</div></div><div><span>pip</span><span> </span><span>install</span><span> </span><span>lmdeploy[all]==</span><span>0.5.1</span></div></div><div><div><div>3</div></div><div><span>pip</span><span> </span><span>install</span><span> </span><span>timm==</span><span>1.0.7</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p><img alt="" loading="lazy" width="1796" height="701" src="/_astro/P7.BPThUuhq_Z1xDYJn.webp" /></p><p>2.使用 LMDeploy 启动一个与 InternLM-XComposer2-VL-1.8B 模型交互的 Gradio 服务。</p><div><figure><figcaption><span></span><span>Terminal window</span></figcaption><pre><code><div><div><div>1</div></div><div><span>lmdeploy</span><span> </span><span>serve</span><span> </span><span>gradio</span><span> </span><span>/share/new_models/Shanghai_AI_Laboratory/internlm-xcomposer2-vl-1_8b</span><span> </span><span>--cache-max-entry-count</span><span> </span><span>0.1</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p><img alt="" loading="lazy" width="1795" height="483" src="/_astro/P8.D9fFsMQ8_1ujRX1.webp" /></p><p>3.若已关闭端口映射，重新输入以下代码即可:</p><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>ssh -CNg -L 6006:127.0.0.1:6006 root@ssh.intern-ai.org.cn -p 36558</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p><img alt="" loading="lazy" width="1729" height="347" src="/_astro/P9.BThwsSkB_1RhY01.webp" /></p><p>通过浏览器访问 <code>http://localhost:6006</code> 来启动<strong>InternLM-XComposer2-VL-1.8B</strong>模型</p><p><img alt="" loading="lazy" width="2318" height="1206" src="/_astro/P10.ChXS8lNo_Z1BXGbP.webp" /></p><p>输入图片并询问图片里有什么：</p><p>输出：</p><p><img alt="" loading="lazy" width="2318" height="1206" src="/_astro/P11.BEOKmCH2_Z1AjV4p.webp" /></p></section></section><section><h4>二、InternVL2-2B 模型<a href="#二internvl2-2b-模型"><span>#</span></a></h4><section><h5>模型介绍:<a href="#模型介绍-1"><span>#</span></a></h5><p>InternVL2 是上海人工智能实验室推出的新一代视觉-语言多模态大模型，是首个综合性能媲美国际闭源商业模型的开源多模态大模型。InternVL2 系列从千亿大模型到端侧小模型全覆盖，通专融合，支持多种模态。</p></section><section><h5>部署步骤：<a href="#部署步骤-1"><span>#</span></a></h5><p>1.通过下面的命令来启动 InternVL2-2B 模型的 Gradio 服务:</p><div><figure><figcaption><span></span><span>Terminal window</span></figcaption><pre><code><div><div><div>1</div></div><div><span>conda</span><span> </span><span>activate</span><span> </span><span>demo</span></div></div><div><div><div>2</div></div><div><span>lmdeploy</span><span> </span><span>serve</span><span> </span><span>gradio</span><span> </span><span>/share/new_models/OpenGVLab/InternVL2-2B</span><span> </span><span>--cache-max-entry-count</span><span> </span><span>0.1</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p>2.若已关闭端口映射，重新输入以下代码即可:</p><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>ssh -CNg -L 6006:127.0.0.1:6006 root@ssh.intern-ai.org.cn -p 36558</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p><img alt="" loading="lazy" width="1729" height="347" src="/_astro/P9.BThwsSkB_1RhY01.webp" /></p><p>通过浏览器访问 <code>http://localhost:6006</code> 来启动<strong>InternVL2-2B</strong>模型</p><p>输入图片并询问详细描述图片内容：</p><p><img alt="" loading="lazy" width="700" height="700" src="/_astro/cat2.CEVqinJk_ZbQG6F.webp" />
输出：</p><p><img alt="" loading="lazy" width="2318" height="1206" src="/_astro/P12.yndE-CtM_ZRROOF.webp" /></p></section></section></section></section>
<section><h2>使用 OpenCompass 进行模型评测<a href="#使用-opencompass-进行模型评测"><span>#</span></a></h2><p>模型能够启动后，还需要回答“它在目标任务上表现如何”。OpenCompass 提供统一的数据集、配置和指标，使不同模型与不同设置之间的比较更加规范。</p><section><h3>参考文档<a href="#参考文档"><span>#</span></a></h3><p><a href="https://github.com/InternLM/Tutorial/tree/camp3/docs/L1/OpenCompass" target="_blank">Tutorial/docs/L1/OpenCompass at camp3 · InternLM/Tutorial (github.com)</a></p></section><section><h3>任务内容<a href="#任务内容"><span>#</span></a></h3><p><img alt="" loading="lazy" width="1142" height="656" src="/_astro/0.BjlGEc9X_ZxCwiW.webp" /></p></section><section><h3>任务复现过程<a href="#任务复现过程"><span>#</span></a></h3><p>列出所有跟 InternLM 及 C-Eval 相关的配置：</p><div><figure><figcaption><span></span><span>Terminal window</span></figcaption><pre><code><div><div><div>1</div></div><div><span>python</span><span> </span><span>tools/list_configs.py</span><span> </span><span>internlm</span><span> </span><span>ceval</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p><img alt="" loading="lazy" width="2309" height="1277" src="/_astro/1.ClvfLoMg_1I7tmd.webp" /></p><p><img alt="" loading="lazy" width="2309" height="1277" src="/_astro/2.BPBxz9K__cr4Pe.webp" /></p><p><strong>评测结束</strong></p><p><img alt="" loading="lazy" width="2309" height="1212" src="/_astro/3.Cbkna4-z_UV70e.webp" /></p></section></section>
<section><h2>从 Demo 到可用服务<a href="#从-demo-到可用服务"><span>#</span></a></h2><p>完成本地 Demo 后，可以继续沿以下方向完善：</p><ul>
<li>记录模型加载后的实际显存占用和首字延迟。</li>
<li>固定模型版本、依赖版本和推理参数，保证结果可复现。</li>
<li>使用 LMDeploy API Server 将界面与模型服务解耦。</li>
<li>为目标业务建立小型自定义测试集，不只依赖通用榜单。</li>
<li>同时观察准确率、稳定性、速度和资源消耗。</li>
</ul></section>
<section><h2>总结<a href="#总结"><span>#</span></a></h2><p>InternLM 的价值不仅在模型本身，也在于它提供了从数据、训练到部署和评测的完整开源链路。8G 显存实践验证了轻量模型的可运行性，而 OpenCompass 则补上了“运行之后如何判断是否好用”的关键环节。</p></section>]]></content>
    </entry>
    <entry>
      <id>https://www.celyn-blog.xyz/posts/classical-supervised-learning-models/</id>
      <title type="text">经典监督学习模型：线性模型、决策树与支持向量机</title>
      <published>2024-05-16T00:00:00.000Z</published>
      <updated>2026-08-18T00:00:00.000Z</updated>
      <author><name>Celyn</name></author>
      <link rel="alternate" href="https://www.celyn-blog.xyz/posts/classical-supervised-learning-models/"/>
      <summary type="text">融合西瓜书第 3、4、6 章学习笔记，对比线性模型、决策树与支持向量机的表示方式、训练目标和适用场景。</summary>
      <content type="html"><![CDATA[<p>线性模型、决策树和支持向量机是经典监督学习中三条非常不同的建模路线。它们分别通过线性组合、递归划分和最大间隔来刻画输入与输出之间的关系。</p>
<p>把三章放在一起学习，更容易看清模型结构、优化目标、非线性能力和泛化方式之间的差异。</p>
<section><h2>统一比较框架<a href="#统一比较框架"><span>#</span></a></h2>

<table><thead><tr><th>模型</th><th>基本表示</th><th>非线性来源</th><th>主要优点</th><th>常见限制</th></tr></thead><tbody><tr><td>线性模型</td><td>特征的加权组合</td><td>特征变换或广义线性连接函数</td><td>简洁、可解释、训练高效</td><td>原始特征空间中表达能力有限</td></tr><tr><td>决策树</td><td>一系列属性划分规则</td><td>递归分区天然形成非线性边界</td><td>规则直观、能处理混合特征</td><td>容易过拟合，对数据扰动敏感</td></tr><tr><td>支持向量机</td><td>最大间隔超平面</td><td>核函数映射到高维空间</td><td>小样本、高维任务中表现稳定</td><td>大规模训练成本高，参数和核选择重要</td></tr></tbody></table><p>三种模型都可以完成监督学习任务，但它们引入先验偏好的方式不同，因此适用的数据结构也不同。</p></section>
<section><h2>线性模型：从回归到分类<a href="#线性模型从回归到分类"><span>#</span></a></h2><p>线性模型用特征的加权组合表达预测函数，是理解最小二乘、对数几率回归和许多复杂模型的基础。</p><section><h3>所需数学知识<a href="#所需数学知识"><span>#</span></a></h3><ol>
<li>求偏导</li>
<li>矩阵求导</li>
<li>求逆矩阵</li>
</ol></section><section><h3>名词解释<a href="#名词解释"><span>#</span></a></h3><p>1.<strong>序关系</strong></p><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>有序：属性之间可进行相对比较（如大、中、小）</span></div></div><div><div><div>2</div></div><div><span>无序：属性之间不可进行相对比较（如南瓜、西瓜、冬瓜）</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p>2.<strong>符号</strong>arg与s.t.</p><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>arg：即argument（参数），用于表示求出指定函数时的**参数取值**</span></div></div><div><div><div>2</div></div><div><span><span>  </span></span><span>例如：</span></div></div><div><div><div>3</div></div><div><span><span>  </span></span><span>arg min 就是使后面这个式子达到最小值时的 变量的取值</span></div></div><div><div><div>4</div></div><div><span><span>  </span></span><span>arg max 就是使后面这个式子达到最大值时的 变量的取值</span></div></div><div><div><div>5</div></div><div>
</div></div><div><div><div>6</div></div><div><span>s.t.：即subject to，意思是受限于...，后面紧跟约束条件</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p>3.<strong>闭式解</strong></p><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>也叫做 解析解，</span></div></div><div><div><div>2</div></div><div><span>闭式解就是一些严格的公式,给出任意的自变量就可以求出其因变量,也就是问题的解</span></div></div><div><div><div>3</div></div><div><span>南瓜书中说闭式解是指可以通过具体的表达式解出待解参数</span></div></div></code></pre><div><div></div><div></div></div></figure></div></section><section><h3>模型介绍<a href="#模型介绍"><span>#</span></a></h3><section><h4>表示形式<a href="#表示形式"><span>#</span></a></h4><p>对于拥有 <span><span>dd</span><span><span><span></span><span>d</span></span></span></span> 个属性的样本，特征向量可写为：</p><span><span><span>x=(x1,x2,…,xd)T\boldsymbol{x}
=(x_1,x_2,\ldots,x_d)^{\mathrm T}</span><span><span><span></span><span><span><span>x</span></span></span><span></span><span>=</span><span></span></span><span><span></span><span>(</span><span><span>x</span><span><span><span><span><span><span></span><span><span>1</span></span></span></span><span>​</span></span><span><span><span></span></span></span></span></span></span><span>,</span><span></span><span><span>x</span><span><span><span><span><span><span></span><span><span>2</span></span></span></span><span>​</span></span><span><span><span></span></span></span></span></span></span><span>,</span><span></span><span>…</span><span></span><span>,</span><span></span><span><span>x</span><span><span><span><span><span><span></span><span><span>d</span></span></span></span><span>​</span></span><span><span><span></span></span></span></span></span></span><span><span>)</span><span><span><span><span><span><span></span><span><span><span>T</span></span></span></span></span></span></span></span></span></span></span></span></span><p>其中 <span><span>xix_i</span><span><span><span></span><span><span>x</span><span><span><span><span><span><span></span><span><span>i</span></span></span></span><span>​</span></span><span><span><span></span></span></span></span></span></span></span></span></span> 表示第 <span><span>ii</span><span><span><span></span><span>i</span></span></span></span> 个属性的属性值。各属性的线性组合作为预测函数：</p><span><span><span>f(x)=w1x1+w2x2+⋯+wdxd+bf(\boldsymbol{x})
=w_1x_1+w_2x_2+\cdots+w_dx_d+b</span><span><span><span></span><span>f</span><span>(</span><span><span><span>x</span></span></span><span>)</span><span></span><span>=</span><span></span></span><span><span></span><span><span>w</span><span><span><span><span><span><span></span><span><span>1</span></span></span></span><span>​</span></span><span><span><span></span></span></span></span></span></span><span><span>x</span><span><span><span><span><span><span></span><span><span>1</span></span></span></span><span>​</span></span><span><span><span></span></span></span></span></span></span><span></span><span>+</span><span></span></span><span><span></span><span><span>w</span><span><span><span><span><span><span></span><span><span>2</span></span></span></span><span>​</span></span><span><span><span></span></span></span></span></span></span><span><span>x</span><span><span><span><span><span><span></span><span><span>2</span></span></span></span><span>​</span></span><span><span><span></span></span></span></span></span></span><span></span><span>+</span><span></span></span><span><span></span><span>⋯</span><span></span><span>+</span><span></span></span><span><span></span><span><span>w</span><span><span><span><span><span><span></span><span><span>d</span></span></span></span><span>​</span></span><span><span><span></span></span></span></span></span></span><span><span>x</span><span><span><span><span><span><span></span><span><span>d</span></span></span></span><span>​</span></span><span><span><span></span></span></span></span></span></span><span></span><span>+</span><span></span></span><span><span></span><span>b</span></span></span></span></span><p>向量形式为：</p><span><span><span>f(x)=wTx+bf(\boldsymbol{x})
=\boldsymbol{w}^{\mathrm T}\boldsymbol{x}+b</span><span><span><span></span><span>f</span><span>(</span><span><span><span>x</span></span></span><span>)</span><span></span><span>=</span><span></span></span><span><span></span><span><span><span><span>w</span></span></span><span><span><span><span><span><span></span><span><span><span>T</span></span></span></span></span></span></span></span></span><span><span><span>x</span></span></span><span></span><span>+</span><span></span></span><span><span></span><span>b</span></span></span></span></span><p>其中 <span><span>w\boldsymbol w</span><span><span><span></span><span><span><span>w</span></span></span></span></span></span> 和 <span><span>x\boldsymbol x</span><span><span><span></span><span><span><span>x</span></span></span></span></span></span> 均为列向量，<span><span>w\boldsymbol w</span><span><span><span></span><span><span><span>w</span></span></span></span></span></span> 中的元素表示不同属性的权重
故线性模型具有很强的<strong>可解释性</strong></p></section><section><h4>模型优点<a href="#模型优点"><span>#</span></a></h4><ol>
<li>简单（模型表示、数学公式简单）</li>
<li>基本（通过引入<strong>层级结构</strong>或<strong>高维映射</strong>可以得到许多<strong>非线性模型</strong>）</li>
<li>可理解性好（通过<strong>权重</strong>可以看出对属性的<strong>偏好</strong>）</li>
</ol></section></section><section><h3>属性数值化<a href="#属性数值化"><span>#</span></a></h3><p><strong>总结</strong>：<strong>离散属性的处理：若有“序”(order)，则连续化；否则，转化为 𝑘维向量</strong></p><p>对于线性模型的求解，我们首先需要明确<strong>参数</strong>的<u>输入</u>和<u>求解</u>两个部分</p><p>我们知道<strong>模型的输入</strong>为各个示例 <span><span>xix_i</span><span><span><span></span><span><span>x</span><span><span><span><span><span><span></span><span><span>i</span></span></span></span><span>​</span></span><span><span><span></span></span></span></span></span></span></span></span></span>，其中的<span><span>xix_i</span><span><span><span></span><span><span>x</span><span><span><span><span><span><span></span><span><span>i</span></span></span></span><span>​</span></span><span><span><span></span></span></span></span></span></span></span></span></span>由d个属性组成，
而我们用<strong>d个属性值</strong>表示一个<strong>具体</strong>的示例<span><span>xix_i</span><span><span><span></span><span><span>x</span><span><span><span><span><span><span></span><span><span>i</span></span></span></span><span>​</span></span><span><span><span></span></span></span></span></span></span></span></span></span>作为输入</p><p>这时我们会发现我们需要注意属性值的<strong>数据类型</strong>
于是这里我们把属性分为两类</p><ol>
<li>
<p>连续属性</p>
</li>
<li>
<p>离散属性</p>
<ul>
<li><strong>有序</strong>的离散属性</li>
<li><strong>无序</strong>的离散属性</li>
</ul>
</li>
</ol><p>对于<strong>有序</strong>的属性，我们用<strong>相对值</strong>表示属性值，如大、中、小分别用1，0.5，0来表示</p><p>对于<strong>无序</strong>的属性，我们用<strong>0/1</strong>表示属性值，我们记改属性有m种属性值，则一个属性值需要<strong>m维列向量</strong>进行表示。</p><p>比如属性“瓜的类别”有西瓜、南瓜、冬瓜三种取值，可分别进行独热编码：</p><span><span><span>x1=(1,0,0)T,x2=(0,1,0)T,x3=(0,0,1)T\begin{aligned}
\boldsymbol{x}_1&amp;=(1,0,0)^{\mathrm T},\\
\boldsymbol{x}_2&amp;=(0,1,0)^{\mathrm T},\\
\boldsymbol{x}_3&amp;=(0,0,1)^{\mathrm T}
\end{aligned}</span><span><span><span></span><span><span><span><span><span><span><span><span></span><span><span><span><span><span>x</span></span></span><span><span><span><span><span><span></span><span><span>1</span></span></span></span><span>​</span></span><span><span><span></span></span></span></span></span></span></span></span><span><span></span><span><span><span><span><span>x</span></span></span><span><span><span><span><span><span></span><span><span>2</span></span></span></span><span>​</span></span><span><span><span></span></span></span></span></span></span></span></span><span><span></span><span><span><span><span><span>x</span></span></span><span><span><span><span><span><span></span><span><span>3</span></span></span></span><span>​</span></span><span><span><span></span></span></span></span></span></span></span></span></span><span>​</span></span><span><span><span></span></span></span></span></span><span><span><span><span><span><span></span><span><span></span><span></span><span>=</span><span></span><span>(</span><span>1</span><span>,</span><span></span><span>0</span><span>,</span><span></span><span>0</span><span><span>)</span><span><span><span><span><span><span></span><span><span><span>T</span></span></span></span></span></span></span></span></span><span>,</span></span></span><span><span></span><span><span></span><span></span><span>=</span><span></span><span>(</span><span>0</span><span>,</span><span></span><span>1</span><span>,</span><span></span><span>0</span><span><span>)</span><span><span><span><span><span><span></span><span><span><span>T</span></span></span></span></span></span></span></span></span><span>,</span></span></span><span><span></span><span><span></span><span></span><span>=</span><span></span><span>(</span><span>0</span><span>,</span><span></span><span>0</span><span>,</span><span></span><span>1</span><span><span>)</span><span><span><span><span><span><span></span><span><span><span>T</span></span></span></span></span></span></span></span></span></span></span></span><span>​</span></span><span><span><span></span></span></span></span></span></span></span></span></span></span></span><p><span><span>x1\boldsymbol x_1</span><span><span><span></span><span><span><span><span>x</span></span></span><span><span><span><span><span><span></span><span><span>1</span></span></span></span><span>​</span></span><span><span><span></span></span></span></span></span></span></span></span></span>、<span><span>x2\boldsymbol x_2</span><span><span><span></span><span><span><span><span>x</span></span></span><span><span><span><span><span><span></span><span><span>2</span></span></span></span><span>​</span></span><span><span><span></span></span></span></span></span></span></span></span></span>、<span><span>x3\boldsymbol x_3</span><span><span><span></span><span><span><span><span>x</span></span></span><span><span><span><span><span><span></span><span><span>3</span></span></span></span><span>​</span></span><span><span><span></span></span></span></span></span></span></span></span></span> 分别表示西瓜、南瓜、冬瓜。</p><hr /><p>对于<strong>回归</strong>，<strong>二分类</strong>，<strong>多分类</strong>任务，我们给出不同的线性模型</p><p></p><figure><img alt="1.png" loading="lazy" width="1289" height="484" src="/_astro/1.B1PmBO2e_Z1gKkGn.webp" /><figcaption>1.png</figcaption></figure><p></p></section><section><h3>回归任务<a href="#回归任务"><span>#</span></a></h3><section><h4>最小二乘法<a href="#最小二乘法"><span>#</span></a></h4><blockquote><p>在<strong>第二章</strong>中我们知道，均方误差是回归任务常见的<strong>性能度量</strong>。</p><p>最小二乘把残差向量的平方欧氏范数作为优化目标，因而具有清晰的几何意义。</p></blockquote><p><strong>定义</strong>：通过最小化预测值与真实标记之间的平方残差来求解模型。普通最小二乘最小化的是纵向残差平方和，并不是样本点到直线的垂直欧氏距离。</p><p><strong>数学知识</strong>：涉及求偏导数</p></section><section><h4>1. 一元线性回归<a href="#1-一元线性回归"><span>#</span></a></h4><section><h5>1. 假设方程<a href="#1-假设方程"><span>#</span></a></h5><span><span><span>f(xi)=wxi+bf(x_i)=w x_i+b</span><span><span><span></span><span>f</span><span>(</span><span><span>x</span><span><span><span><span><span><span></span><span><span>i</span></span></span></span><span>​</span></span><span><span><span></span></span></span></span></span></span><span>)</span><span></span><span>=</span><span></span></span><span><span></span><span>w</span><span><span>x</span><span><span><span><span><span><span></span><span><span>i</span></span></span></span><span>​</span></span><span><span><span></span></span></span></span></span></span><span></span><span>+</span><span></span></span><span><span></span><span>b</span></span></span></span></span><p>希望预测值 <span><span>f(xi)f(x_i)</span><span><span><span></span><span>f</span><span>(</span><span><span>x</span><span><span><span><span><span><span></span><span><span>i</span></span></span></span><span>​</span></span><span><span><span></span></span></span></span></span></span><span>)</span></span></span></span> 尽可能接近真实标记 <span><span>yiy_i</span><span><span><span></span><span><span>y</span><span><span><span><span><span><span></span><span><span>i</span></span></span></span><span>​</span></span><span><span><span></span></span></span></span></span></span></span></span></span>。</p></section><section><h5>2. 最小化平方误差<a href="#2-最小化平方误差"><span>#</span></a></h5><p>定义平方误差和：</p><span><span><span>E(w,b)=∑i=1m(yi−wxi−b)2E(w,b)=\sum_{i=1}^{m}\left(y_i-wx_i-b\right)^2</span><span><span><span></span><span>E</span><span>(</span><span>w</span><span>,</span><span></span><span>b</span><span>)</span><span></span><span>=</span><span></span></span><span><span></span><span><span><span><span><span><span></span><span><span><span>i</span><span>=</span><span>1</span></span></span></span><span><span></span><span><span>∑</span></span></span><span><span></span><span><span><span>m</span></span></span></span></span><span>​</span></span><span><span><span></span></span></span></span></span><span></span><span><span><span>(</span><span><span>y</span><span><span><span><span><span><span></span><span><span>i</span></span></span></span><span>​</span></span><span><span><span></span></span></span></span></span></span><span></span><span>−</span><span></span><span>w</span><span><span>x</span><span><span><span><span><span><span></span><span><span>i</span></span></span></span><span>​</span></span><span><span><span></span></span></span></span></span></span><span></span><span>−</span><span></span><span>b</span><span>)</span></span><span><span><span><span><span><span></span><span><span>2</span></span></span></span></span></span></span></span></span></span></span></span><p>于是最小二乘估计为：</p><span><span><span>(w∗,b∗)=arg min⁡w,bE(w,b)\left(w^*,b^*\right)
=\operatorname*{arg\,min}_{w,b}E(w,b)</span><span><span><span></span><span><span>(</span><span><span>w</span><span><span><span><span><span><span></span><span><span>∗</span></span></span></span></span></span></span></span><span>,</span><span></span><span><span>b</span><span><span><span><span><span><span></span><span><span>∗</span></span></span></span></span></span></span></span><span>)</span></span><span></span><span>=</span><span></span></span><span><span></span><span><span><span><span><span><span></span><span><span><span>w</span><span>,</span><span>b</span></span></span></span><span><span></span><span><span><span>arg</span><span></span><span>min</span></span></span></span></span><span>​</span></span><span><span><span></span></span></span></span></span><span></span><span>E</span><span>(</span><span>w</span><span>,</span><span></span><span>b</span><span>)</span></span></span></span></span><p>若把目标函数写成均方误差 <span><span>E(w,b)/mE(w,b)/m</span><span><span><span></span><span>E</span><span>(</span><span>w</span><span>,</span><span></span><span>b</span><span>)</span><span>/</span><span>m</span></span></span></span>，只会多出常数因子 <span><span>1/m1/m</span><span><span><span></span><span>1/</span><span>m</span></span></span></span>，不会改变最优解。</p></section><section><h5>3. 对 <span><span>ww</span><span><span><span></span><span>w</span></span></span></span> 与 <span><span>bb</span><span><span><span></span><span>b</span></span></span></span> 求偏导<a href="#3-对-www-与-bbb-求偏导"><span>#</span></a></h5><span><span><span>∂E∂w=−2∑i=1mxi(yi−wxi−b),∂E∂b=−2∑i=1m(yi−wxi−b).\begin{aligned}
\frac{\partial E}{\partial w}
&amp;=-2\sum_{i=1}^{m}x_i\left(y_i-wx_i-b\right),\\
\frac{\partial E}{\partial b}
&amp;=-2\sum_{i=1}^{m}\left(y_i-wx_i-b\right).
\end{aligned}</span><span><span><span></span><span><span><span><span><span><span><span><span></span><span><span><span></span><span><span><span><span><span><span></span><span><span>∂</span><span>w</span></span></span><span><span></span><span></span></span><span><span></span><span><span>∂</span><span>E</span></span></span></span><span>​</span></span><span><span><span></span></span></span></span></span><span></span></span></span></span><span><span></span><span><span><span></span><span><span><span><span><span><span></span><span><span>∂</span><span>b</span></span></span><span><span></span><span></span></span><span><span></span><span><span>∂</span><span>E</span></span></span></span><span>​</span></span><span><span><span></span></span></span></span></span><span></span></span></span></span></span><span>​</span></span><span><span><span></span></span></span></span></span><span><span><span><span><span><span></span><span><span></span><span></span><span>=</span><span></span><span>−</span><span>2</span><span></span><span><span><span><span><span><span></span><span><span><span>i</span><span>=</span><span>1</span></span></span></span><span><span></span><span><span>∑</span></span></span><span><span></span><span><span><span>m</span></span></span></span></span><span>​</span></span><span><span><span></span></span></span></span></span><span></span><span><span>x</span><span><span><span><span><span><span></span><span><span>i</span></span></span></span><span>​</span></span><span><span><span></span></span></span></span></span></span><span></span><span><span>(</span><span><span>y</span><span><span><span><span><span><span></span><span><span>i</span></span></span></span><span>​</span></span><span><span><span></span></span></span></span></span></span><span></span><span>−</span><span></span><span>w</span><span><span>x</span><span><span><span><span><span><span></span><span><span>i</span></span></span></span><span>​</span></span><span><span><span></span></span></span></span></span></span><span></span><span>−</span><span></span><span>b</span><span>)</span></span><span></span><span>,</span></span></span><span><span></span><span><span></span><span></span><span>=</span><span></span><span>−</span><span>2</span><span></span><span><span><span><span><span><span></span><span><span><span>i</span><span>=</span><span>1</span></span></span></span><span><span></span><span><span>∑</span></span></span><span><span></span><span><span><span>m</span></span></span></span></span><span>​</span></span><span><span><span></span></span></span></span></span><span></span><span><span>(</span><span><span>y</span><span><span><span><span><span><span></span><span><span>i</span></span></span></span><span>​</span></span><span><span><span></span></span></span></span></span></span><span></span><span>−</span><span></span><span>w</span><span><span>x</span><span><span><span><span><span><span></span><span><span>i</span></span></span></span><span>​</span></span><span><span><span></span></span></span></span></span></span><span></span><span>−</span><span></span><span>b</span><span>)</span></span><span></span><span>.</span></span></span></span><span>​</span></span><span><span><span></span></span></span></span></span></span></span></span></span></span></span><p><span><span>E(w,b)E(w,b)</span><span><span><span></span><span>E</span><span>(</span><span>w</span><span>,</span><span></span><span>b</span><span>)</span></span></span></span> 是关于 <span><span>ww</span><span><span><span></span><span>w</span></span></span></span> 和 <span><span>bb</span><span><span><span></span><span>b</span></span></span></span> 的凸二次函数，因此令梯度为零即可得到全局最优解；若所有 <span><span>xix_i</span><span><span><span></span><span><span>x</span><span><span><span><span><span><span></span><span><span>i</span></span></span></span><span>​</span></span><span><span><span></span></span></span></span></span></span></span></span></span> 都相同，则斜率不能被唯一确定。</p></section><section><h5>4. 求得闭式解<a href="#4-求得闭式解"><span>#</span></a></h5><p>当 <span><span>∑i=1m(xi−xˉ)2&gt;0\sum_{i=1}^{m}(x_i-\bar{x})^2&gt;0</span><span><span><span></span><span><span>∑</span><span><span><span><span><span><span></span><span><span><span>i</span><span>=</span><span>1</span></span></span></span><span><span></span><span><span><span>m</span></span></span></span></span><span>​</span></span><span><span><span></span></span></span></span></span></span><span>(</span><span><span>x</span><span><span><span><span><span><span></span><span><span>i</span></span></span></span><span>​</span></span><span><span><span></span></span></span></span></span></span><span></span><span>−</span><span></span></span><span><span></span><span><span><span><span><span><span></span><span>x</span></span><span><span></span><span><span>ˉ</span></span></span></span></span></span></span><span><span>)</span><span><span><span><span><span><span></span><span><span>2</span></span></span></span></span></span></span></span><span></span><span>&gt;</span><span></span></span><span><span></span><span>0</span></span></span></span> 时：</p><span><span><span>w∗=∑i=1m(xi−xˉ)(yi−yˉ)∑i=1m(xi−xˉ)2w^*
=\frac{\sum_{i=1}^{m}(x_i-\bar{x})(y_i-\bar{y})}
{\sum_{i=1}^{m}(x_i-\bar{x})^2}</span><span><span><span></span><span><span>w</span><span><span><span><span><span><span></span><span><span>∗</span></span></span></span></span></span></span></span><span></span><span>=</span><span></span></span><span><span></span><span><span></span><span><span><span><span><span><span></span><span><span><span>∑</span><span><span><span><span><span><span></span><span><span><span>i</span><span>=</span><span>1</span></span></span></span><span><span></span><span><span><span>m</span></span></span></span></span><span>​</span></span><span><span><span></span></span></span></span></span></span><span>(</span><span><span>x</span><span><span><span><span><span><span></span><span><span>i</span></span></span></span><span>​</span></span><span><span><span></span></span></span></span></span></span><span></span><span>−</span><span></span><span><span><span><span><span><span></span><span>x</span></span><span><span></span><span><span>ˉ</span></span></span></span></span></span></span><span><span>)</span><span><span><span><span><span><span></span><span><span>2</span></span></span></span></span></span></span></span></span></span><span><span></span><span></span></span><span><span></span><span><span><span>∑</span><span><span><span><span><span><span></span><span><span><span>i</span><span>=</span><span>1</span></span></span></span><span><span></span><span><span><span>m</span></span></span></span></span><span>​</span></span><span><span><span></span></span></span></span></span></span><span>(</span><span><span>x</span><span><span><span><span><span><span></span><span><span>i</span></span></span></span><span>​</span></span><span><span><span></span></span></span></span></span></span><span></span><span>−</span><span></span><span><span><span><span><span><span></span><span>x</span></span><span><span></span><span><span>ˉ</span></span></span></span></span></span></span><span>)</span><span>(</span><span><span>y</span><span><span><span><span><span><span></span><span><span>i</span></span></span></span><span>​</span></span><span><span><span></span></span></span></span></span></span><span></span><span>−</span><span></span><span><span><span><span><span><span></span><span>y</span></span><span><span></span><span><span>ˉ</span></span></span></span><span>​</span></span><span><span><span></span></span></span></span></span><span>)</span></span></span></span><span>​</span></span><span><span><span></span></span></span></span></span><span></span></span></span></span></span></span><span><span><span>b∗=yˉ−w∗xˉb^*=\bar{y}-w^*\bar{x}</span><span><span><span></span><span><span>b</span><span><span><span><span><span><span></span><span><span>∗</span></span></span></span></span></span></span></span><span></span><span>=</span><span></span></span><span><span></span><span><span><span><span><span><span></span><span>y</span></span><span><span></span><span><span>ˉ</span></span></span></span><span>​</span></span><span><span><span></span></span></span></span></span><span></span><span>−</span><span></span></span><span><span></span><span><span>w</span><span><span><span><span><span><span></span><span><span>∗</span></span></span></span></span></span></span></span><span><span><span><span><span><span></span><span>x</span></span><span><span></span><span><span>ˉ</span></span></span></span></span></span></span></span></span></span></span><p>其中 <span><span>xˉ=1m∑i=1mxi\bar{x}=\frac{1}{m}\sum_{i=1}^{m}x_i</span><span><span><span></span><span><span><span><span><span><span></span><span>x</span></span><span><span></span><span><span>ˉ</span></span></span></span></span></span></span><span></span><span>=</span><span></span></span><span><span></span><span><span></span><span><span><span><span><span><span></span><span><span><span>m</span></span></span></span><span><span></span><span></span></span><span><span></span><span><span><span>1</span></span></span></span></span><span>​</span></span><span><span><span></span></span></span></span></span><span></span></span><span></span><span><span>∑</span><span><span><span><span><span><span></span><span><span><span>i</span><span>=</span><span>1</span></span></span></span><span><span></span><span><span><span>m</span></span></span></span></span><span>​</span></span><span><span><span></span></span></span></span></span></span><span></span><span><span>x</span><span><span><span><span><span><span></span><span><span>i</span></span></span></span><span>​</span></span><span><span><span></span></span></span></span></span></span></span></span></span>，<span><span>yˉ=1m∑i=1myi\bar{y}=\frac{1}{m}\sum_{i=1}^{m}y_i</span><span><span><span></span><span><span><span><span><span><span></span><span>y</span></span><span><span></span><span><span>ˉ</span></span></span></span><span>​</span></span><span><span><span></span></span></span></span></span><span></span><span>=</span><span></span></span><span><span></span><span><span></span><span><span><span><span><span><span></span><span><span><span>m</span></span></span></span><span><span></span><span></span></span><span><span></span><span><span><span>1</span></span></span></span></span><span>​</span></span><span><span><span></span></span></span></span></span><span></span></span><span></span><span><span>∑</span><span><span><span><span><span><span></span><span><span><span>i</span><span>=</span><span>1</span></span></span></span><span><span></span><span><span><span>m</span></span></span></span></span><span>​</span></span><span><span><span></span></span></span></span></span></span><span></span><span><span>y</span><span><span><span><span><span><span></span><span><span>i</span></span></span></span><span>​</span></span><span><span><span></span></span></span></span></span></span></span></span></span>。</p></section></section><section><h4>2. 多元线性回归<a href="#2-多元线性回归"><span>#</span></a></h4><p><strong>额外数学知识</strong>：矩阵求导与矩阵求逆。</p><blockquote><p>基于本人数学系所教授的高等代数中无此内容，故认为需要额外补充学习。</p></blockquote><p>详见《机器学习》附录 P400 的 <code>A.2 导数</code>。</p><section><h5>1. 假设方程<a href="#1-假设方程-1"><span>#</span></a></h5><span><span><span>f(xi)=wTxi+bf(\boldsymbol{x}_i)=\boldsymbol{w}^{\mathrm T}\boldsymbol{x}_i+b</span><span><span><span></span><span>f</span><span>(</span><span><span><span><span>x</span></span></span><span><span><span><span><span><span></span><span><span>i</span></span></span></span><span>​</span></span><span><span><span></span></span></span></span></span></span><span>)</span><span></span><span>=</span><span></span></span><span><span></span><span><span><span><span>w</span></span></span><span><span><span><span><span><span></span><span><span><span>T</span></span></span></span></span></span></span></span></span><span><span><span><span>x</span></span></span><span><span><span><span><span><span></span><span><span>i</span></span></span></span><span>​</span></span><span><span><span></span></span></span></span></span></span><span></span><span>+</span><span></span></span><span><span></span><span>b</span></span></span></span></span><p>同样希望预测值 <span><span>f(xi)f(\boldsymbol{x}_i)</span><span><span><span></span><span>f</span><span>(</span><span><span><span><span>x</span></span></span><span><span><span><span><span><span></span><span><span>i</span></span></span></span><span>​</span></span><span><span><span></span></span></span></span></span></span><span>)</span></span></span></span> 尽可能接近真实标记 <span><span>yiy_i</span><span><span><span></span><span><span>y</span><span><span><span><span><span><span></span><span><span>i</span></span></span></span><span>​</span></span><span><span><span></span></span></span></span></span></span></span></span></span>。</p></section><section><h5>2. 构造增广向量与设计矩阵<a href="#2-构造增广向量与设计矩阵"><span>#</span></a></h5><p>定义增广参数和增广样本：</p><span><span><span>w^=(wb),x^i=(xi1)\hat{\boldsymbol w}
=\begin{pmatrix}\boldsymbol w\\b\end{pmatrix},
\qquad
\hat{\boldsymbol x}_i
=\begin{pmatrix}\boldsymbol x_i\\1\end{pmatrix}</span><span><span><span></span><span><span><span><span><span><span></span><span><span><span>w</span></span></span></span><span><span></span><span><span>^</span></span></span></span></span></span></span><span></span><span>=</span><span></span></span><span><span></span><span><span><span>(</span></span><span><span><span><span><span><span><span><span></span><span><span><span><span>w</span></span></span></span></span><span><span></span><span><span>b</span></span></span></span><span>​</span></span><span><span><span></span></span></span></span></span></span></span><span><span>)</span></span></span><span></span><span>,</span><span></span><span></span><span><span><span><span><span><span><span></span><span><span><span>x</span></span></span></span><span><span></span><span><span>^</span></span></span></span></span></span></span><span><span><span><span><span><span></span><span><span>i</span></span></span></span><span>​</span></span><span><span><span></span></span></span></span></span></span><span></span><span>=</span><span></span></span><span><span></span><span><span><span>(</span></span><span><span><span><span><span><span><span><span></span><span><span><span><span><span>x</span></span></span><span><span><span><span><span><span></span><span><span>i</span></span></span></span><span>​</span></span><span><span><span></span></span></span></span></span></span></span></span><span><span></span><span><span>1</span></span></span></span><span>​</span></span><span><span><span></span></span></span></span></span></span></span><span><span>)</span></span></span></span></span></span></span><p>将所有样本按行排列，得到设计矩阵：</p><span><span><span>X=(x1T1x2T1⋮⋮xmT1)∈Rm×(d+1)\mathbf X
=\begin{pmatrix}
\boldsymbol{x}_1^{\mathrm T} &amp; 1\\
\boldsymbol{x}_2^{\mathrm T} &amp; 1\\
\vdots &amp; \vdots\\
\boldsymbol{x}_m^{\mathrm T} &amp; 1
\end{pmatrix}
\in\mathbb R^{m\times(d+1)}</span><span><span><span></span><span>X</span><span></span><span>=</span><span></span></span><span><span></span><span><span><span><span><span><span><span><span></span><span></span></span></span><span>​</span></span><span><span><span></span></span></span></span></span></span><span><span><span><span><span><span><span><span></span><span><span><span><span><span>x</span></span></span><span><span><span><span><span><span></span><span><span>1</span></span></span><span><span></span><span><span><span>T</span></span></span></span></span><span>​</span></span><span><span><span></span></span></span></span></span></span></span></span><span><span></span><span><span><span><span><span>x</span></span></span><span><span><span><span><span><span></span><span><span>2</span></span></span><span><span></span><span><span><span>T</span></span></span></span></span><span>​</span></span><span><span><span></span></span></span></span></span></span></span></span><span><span></span><span><span><span>⋮</span><span></span></span></span></span><span><span></span><span><span><span><span><span>x</span></span></span><span><span><span><span><span><span></span><span><span>m</span></span></span><span><span></span><span><span><span>T</span></span></span></span></span><span>​</span></span><span><span><span></span></span></span></span></span></span></span></span></span><span>​</span></span><span><span><span></span></span></span></span></span><span></span><span></span><span><span><span><span><span><span></span><span><span>1</span></span></span><span><span></span><span><span>1</span></span></span><span><span></span><span><span><span>⋮</span><span></span></span></span></span><span><span></span><span><span>1</span></span></span></span><span>​</span></span><span><span><span></span></span></span></span></span></span></span><span><span><span><span><span><span><span></span><span></span></span></span><span>​</span></span><span><span><span></span></span></span></span></span></span></span><span></span><span>∈</span><span></span></span><span><span></span><span><span>R</span><span><span><span><span><span><span></span><span><span><span>m</span><span>×</span><span>(</span><span>d</span><span>+</span><span>1</span><span>)</span></span></span></span></span></span></span></span></span></span></span></span></span><p>标记向量写作 <span><span>y=(y1,y2,…,ym)T\boldsymbol y=(y_1,y_2,\ldots,y_m)^{\mathrm T}</span><span><span><span></span><span><span><span>y</span></span></span><span></span><span>=</span><span></span></span><span><span></span><span>(</span><span><span>y</span><span><span><span><span><span><span></span><span><span>1</span></span></span></span><span>​</span></span><span><span><span></span></span></span></span></span></span><span>,</span><span></span><span><span>y</span><span><span><span><span><span><span></span><span><span>2</span></span></span></span><span>​</span></span><span><span><span></span></span></span></span></span></span><span>,</span><span></span><span>…</span><span></span><span>,</span><span></span><span><span>y</span><span><span><span><span><span><span></span><span><span>m</span></span></span></span><span>​</span></span><span><span><span></span></span></span></span></span></span><span><span>)</span><span><span><span><span><span><span></span><span><span><span>T</span></span></span></span></span></span></span></span></span></span></span></span>，平方误差和为：</p><span><span><span>E(w^)=∥Xw^−y∥22E(\hat{\boldsymbol w})
=\left\|\mathbf X\hat{\boldsymbol w}-\boldsymbol y\right\|_2^2</span><span><span><span></span><span>E</span><span>(</span><span><span><span><span><span><span></span><span><span><span>w</span></span></span></span><span><span></span><span><span>^</span></span></span></span></span></span></span><span>)</span><span></span><span>=</span><span></span></span><span><span></span><span><span><span>∥</span><span>X</span><span><span><span><span><span><span></span><span><span><span>w</span></span></span></span><span><span></span><span><span>^</span></span></span></span></span></span></span><span></span><span>−</span><span></span><span><span><span>y</span></span></span><span>∥</span></span><span><span><span><span><span><span></span><span><span>2</span></span></span><span><span></span><span><span>2</span></span></span></span><span>​</span></span><span><span><span></span></span></span></span></span></span></span></span></span></span><p></p><figure><img alt="2.png" loading="lazy" width="1113" height="440" src="/_astro/2.Cul6eNYg_Z14FcxL.webp" /><figcaption>2.png</figcaption></figure>
<code>[图片源于周志华老师]</code><p></p></section><section><h5>3. 求解参数向量<a href="#3-求解参数向量"><span>#</span></a></h5><p>对目标函数求梯度：</p><span><span><span>∇w^E=2XT(Xw^−y)\nabla_{\hat{\boldsymbol w}}E
=2\mathbf X^{\mathrm T}
\left(\mathbf X\hat{\boldsymbol w}-\boldsymbol y\right)</span><span><span><span></span><span><span>∇</span><span><span><span><span><span><span></span><span><span><span><span><span><span><span><span></span><span><span><span>w</span></span></span></span><span><span></span><span><span>^</span></span></span></span></span></span></span></span></span></span></span><span>​</span></span><span><span><span></span></span></span></span></span></span><span>E</span><span></span><span>=</span><span></span></span><span><span></span><span>2</span><span><span>X</span><span><span><span><span><span><span></span><span><span><span>T</span></span></span></span></span></span></span></span></span><span></span><span><span>(</span><span>X</span><span><span><span><span><span><span></span><span><span><span>w</span></span></span></span><span><span></span><span><span>^</span></span></span></span></span></span></span><span></span><span>−</span><span></span><span><span><span>y</span></span></span><span>)</span></span></span></span></span></span><p>若 <span><span>X\mathbf X</span><span><span><span></span><span>X</span></span></span></span> 满列秩，则 <span><span>XTX\mathbf X^{\mathrm T}\mathbf X</span><span><span><span></span><span><span>X</span><span><span><span><span><span><span></span><span><span><span>T</span></span></span></span></span></span></span></span></span><span>X</span></span></span></span> 正定且可逆。令梯度为零，得到唯一闭式解：</p><span><span><span>w^∗=(XTX)−1XTy\hat{\boldsymbol w}^{*}
=\left(\mathbf X^{\mathrm T}\mathbf X\right)^{-1}
\mathbf X^{\mathrm T}\boldsymbol y</span><span><span><span></span><span><span><span><span><span><span><span></span><span><span><span>w</span></span></span></span><span><span></span><span><span>^</span></span></span></span></span></span></span><span><span><span><span><span><span></span><span><span><span>∗</span></span></span></span></span></span></span></span></span><span></span><span>=</span><span></span></span><span><span></span><span><span><span><span>(</span></span><span><span>X</span><span><span><span><span><span><span></span><span><span><span>T</span></span></span></span></span></span></span></span></span><span>X</span><span><span>)</span></span></span><span><span><span><span><span><span></span><span><span><span>−</span><span>1</span></span></span></span></span></span></span></span></span><span></span><span><span>X</span><span><span><span><span><span><span></span><span><span><span>T</span></span></span></span></span></span></span></span></span><span><span><span>y</span></span></span></span></span></span></span><p>若 <span><span>XTX\mathbf X^{\mathrm T}\mathbf X</span><span><span><span></span><span><span>X</span><span><span><span><span><span><span></span><span><span><span>T</span></span></span></span></span></span></span></span></span><span>X</span></span></span></span> 不可逆，最小二乘解可能不唯一，此时可用 Moore–Penrose 伪逆得到一个最小范数解：</p><span><span><span>w^∗=X+y\hat{\boldsymbol w}^{*}=\mathbf X^{+}\boldsymbol y</span><span><span><span></span><span><span><span><span><span><span><span></span><span><span><span>w</span></span></span></span><span><span></span><span><span>^</span></span></span></span></span></span></span><span><span><span><span><span><span></span><span><span><span>∗</span></span></span></span></span></span></span></span></span><span></span><span>=</span><span></span></span><span><span></span><span><span>X</span><span><span><span><span><span><span></span><span><span><span>+</span></span></span></span></span></span></span></span></span><span><span><span>y</span></span></span></span></span></span></span><p>也可以加入正则化项表达归纳偏好，详见 6.4 节和 11.4 节。</p><blockquote><p>常见正则化方法包括岭回归（Ridge Regression）、Lasso 和弹性网（Elastic Net）。</p></blockquote></section><section><h5>4. 得到预测模型<a href="#4-得到预测模型"><span>#</span></a></h5><p>对于增广输入 <span><span>x^i\hat{\boldsymbol x}_i</span><span><span><span></span><span><span><span><span><span><span><span></span><span><span><span>x</span></span></span></span><span><span></span><span><span>^</span></span></span></span></span></span></span><span><span><span><span><span><span></span><span><span>i</span></span></span></span><span>​</span></span><span><span><span></span></span></span></span></span></span></span></span></span>，预测值为：</p><span><span><span>y^i=x^iTw^∗\hat y_i
=\hat{\boldsymbol x}_i^{\mathrm T}\hat{\boldsymbol w}^{*}</span><span><span><span></span><span><span><span><span><span><span><span></span><span>y</span></span><span><span></span><span><span>^</span></span></span></span><span>​</span></span><span><span><span></span></span></span></span></span><span><span><span><span><span><span></span><span><span>i</span></span></span></span><span>​</span></span><span><span><span></span></span></span></span></span></span><span></span><span>=</span><span></span></span><span><span></span><span><span><span><span><span><span><span></span><span><span><span>x</span></span></span></span><span><span></span><span><span>^</span></span></span></span></span></span></span><span><span><span><span><span><span></span><span><span>i</span></span></span><span><span></span><span><span><span>T</span></span></span></span></span><span>​</span></span><span><span><span></span></span></span></span></span></span><span><span><span><span><span><span><span></span><span><span><span>w</span></span></span></span><span><span></span><span><span>^</span></span></span></span></span></span></span><span><span><span><span><span><span></span><span><span><span>∗</span></span></span></span></span></span></span></span></span></span></span></span></span><p>PS：把最小二乘目标写成二次型并使用矩阵求导很有意思，详细推导可参考《南瓜书》P35–P36。</p></section></section><section><h4>3.广义线性模型(GLM)<a href="#3广义线性模型glm"><span>#</span></a></h4><blockquote><p>通过联系函数，让输出的条件均值与线性预测子建立关系。</p></blockquote><section><h5>对数线性模型<a href="#对数线性模型"><span>#</span></a></h5><blockquote><p>当输出变量为正，且条件均值随输入呈指数尺度变化时，可以使用对数联系函数建模。</p></blockquote><p><strong>个人理解</strong>：</p><p>其实就是我们在原样本中，通过数值或者散点图，观察到<strong>输出标记</strong>在<strong>指数尺度</strong>上变化，故<strong>很难直接</strong>用一条直线拟合输出标记，因为标记值本身为<strong>非线性增长</strong>。</p><p>而对在指数尺度上变化的输出标记<strong>取对数</strong>后，
就可以将输出标记<strong>映射</strong>到<strong>线性尺度</strong>上变化，故易于用直线拟合</p><p>将输出的条件均值记为 <span><span>μ(x)=E[Y∣x]\mu(\boldsymbol x)=\mathbb E[Y\mid\boldsymbol x]</span><span><span><span></span><span>μ</span><span>(</span><span><span><span>x</span></span></span><span>)</span><span></span><span>=</span><span></span></span><span><span></span><span>E</span><span>[</span><span>Y</span><span></span><span>∣</span><span></span></span><span><span></span><span><span><span>x</span></span></span><span>]</span></span></span></span>。对数线性模型使用对数联系函数：</p><span><span><span>ln⁡μ(x)=wTx+b\ln \mu(\boldsymbol{x})
=\boldsymbol{w}^{\mathrm T}\boldsymbol{x}+b</span><span><span><span></span><span>ln</span><span></span><span>μ</span><span>(</span><span><span><span>x</span></span></span><span>)</span><span></span><span>=</span><span></span></span><span><span></span><span><span><span><span>w</span></span></span><span><span><span><span><span><span></span><span><span><span>T</span></span></span></span></span></span></span></span></span><span><span><span>x</span></span></span><span></span><span>+</span><span></span></span><span><span></span><span>b</span></span></span></span></span><p>因此原始尺度上的预测为：</p><span><span><span>μ(x)=exp⁡ ⁣(wTx+b)\mu(\boldsymbol{x})
=\exp\!\left(\boldsymbol{w}^{\mathrm T}\boldsymbol{x}+b\right)</span><span><span><span></span><span>μ</span><span>(</span><span><span><span>x</span></span></span><span>)</span><span></span><span>=</span><span></span></span><span><span></span><span>exp</span><span></span><span></span><span><span><span>(</span></span><span><span><span><span>w</span></span></span><span><span><span><span><span><span></span><span><span><span>T</span></span></span></span></span></span></span></span></span><span><span><span>x</span></span></span><span></span><span>+</span><span></span><span>b</span><span><span>)</span></span></span></span></span></span></span><blockquote><p>这里的对数函数把输出均值与线性预测子联系起来。偏置 <span><span>bb</span><span><span><span></span><span>b</span></span></span></span> 也位于指数内部。</p><p>但实质上已是在求取输入空间到输出空间的<strong>非线性函数映射</strong></p></blockquote><hr /><p>广义线性模型通过选择联系函数和响应变量分布来扩展线性模型。联系函数并不是为了把每个观测值强行“变成线性”，而是规定条件均值 <span><span>μ(x)\mu(\boldsymbol x)</span><span><span><span></span><span>μ</span><span>(</span><span><span><span>x</span></span></span><span>)</span></span></span></span> 与线性预测子之间的关系。</p><p>实际建模时，应根据响应变量的取值范围、分布假设和任务目标选择合适的联系函数，再通过极大似然等方法估计模型参数。</p><hr /><p>而由此我们引出了<strong>广义线性模型</strong>（Generalized Linear Model）的定义：</p><span><span><span>g ⁣(μ(x))=wTx+b,μ(x)=E[Y∣x]\begin{aligned}
g\!\left(\mu(\boldsymbol{x})\right)
&amp;=\boldsymbol{w}^{\mathrm T}\boldsymbol{x}+b,\\
\mu(\boldsymbol{x})
&amp;=\mathbb E[Y\mid\boldsymbol{x}]
\end{aligned}</span><span><span><span></span><span><span><span><span><span><span><span><span></span><span><span>g</span><span></span><span></span><span><span>(</span><span>μ</span><span>(</span><span><span><span>x</span></span></span><span>)</span><span>)</span></span></span></span><span><span></span><span><span>μ</span><span>(</span><span><span><span>x</span></span></span><span>)</span></span></span></span><span>​</span></span><span><span><span></span></span></span></span></span><span><span><span><span><span><span></span><span><span></span><span></span><span>=</span><span></span><span><span><span><span>w</span></span></span><span><span><span><span><span><span></span><span><span><span>T</span></span></span></span></span></span></span></span></span><span><span><span>x</span></span></span><span></span><span>+</span><span></span><span>b</span><span>,</span></span></span><span><span></span><span><span></span><span></span><span>=</span><span></span><span>E</span><span>[</span><span>Y</span><span></span><span>∣</span><span></span><span><span><span>x</span></span></span><span>]</span></span></span></span><span>​</span></span><span><span><span></span></span></span></span></span></span></span></span></span></span></span><p>其中 <span><span>g(⋅)g(\cdot)</span><span><span><span></span><span>g</span><span>(</span><span>⋅</span><span>)</span></span></span></span> 称为联系函数（link function），它把输出分布的条件均值与线性预测子联系起来。</p><blockquote><p>广义线性模型的<strong>参数估计</strong>常通过加权最小二乘法或极大似然法进行</p></blockquote><p>对数线性模型是广义线性模型在 <span><span>g(μ)=ln⁡μg(\mu)=\ln\mu</span><span><span><span></span><span>g</span><span>(</span><span>μ</span><span>)</span><span></span><span>=</span><span></span></span><span><span></span><span>ln</span><span></span><span>μ</span></span></span></span> 时的一个特例。
显然，<strong>一元线性回归</strong>也是如此</p></section></section></section><section><h3>分类任务<a href="#分类任务"><span>#</span></a></h3><section><h4>引导<a href="#引导"><span>#</span></a></h4><p>在先前的文章：<code>西瓜书-第3章-线性模型 (Part 1) 中 我们介绍了</code>
<u>一元线性回归、多元线性回归、广义线性回归（对数线性回归）</u>的模型,
用于解决<strong>回归类</strong>问题</p><p>那么对于<strong>分类</strong>问题，我们又该使用哪种模型呢？
我们知道分类问题可以分成<u>二分类</u>与<u>多分类</u>问题</p><p>此处我们先介绍<u>二分类</u>问题所使用的模型</p><p>我们可以沿用广义线性模型的思想，用联系函数把类别概率与线性预测子联系起来。模型先输出连续的概率，再根据阈值或决策规则得到离散类别。</p></section><section><h4>对数几率回归<a href="#对数几率回归"><span>#</span></a></h4><p>这里我们先给出“单位阶跃函数“(unit-step function)</p><p>此函数能够将输出范围在R上的标记 转换为离散值</p><blockquote><p>若预测值大于零就判为正例，小于零则判为反例，预测值为临界值零则可
任意判别</p></blockquote><p></p><figure><img src="https://img-blog.csdnimg.cn/direct/b1e0d606699d4ad0bf0fb4904882bd78.jpeg#pic_center" alt="在这里插入图片描述" /><figcaption>在这里插入图片描述</figcaption></figure>
我们可以看出这个函数的效果是很不错的
但是因为其函数性质很差，不连续也不可微，不符合联系函数的定义<p></p><p>所以我们引入 Sigmoid 函数。令</p><span><span><span>z=wTx+bz=\boldsymbol{w}^{\mathrm T}\boldsymbol{x}+b</span><span><span><span></span><span>z</span><span></span><span>=</span><span></span></span><span><span></span><span><span><span><span>w</span></span></span><span><span><span><span><span><span></span><span><span><span>T</span></span></span></span></span></span></span></span></span><span><span><span>x</span></span></span><span></span><span>+</span><span></span></span><span><span></span><span>b</span></span></span></span></span><p>则样本属于正类的条件概率为：</p><span><span><span>p(y=1∣x)=σ(z)=11+e−zp(y=1\mid\boldsymbol{x})
=\sigma(z)
=\frac{1}{1+e^{-z}}</span><span><span><span></span><span>p</span><span>(</span><span>y</span><span></span><span>=</span><span></span></span><span><span></span><span>1</span><span></span><span>∣</span><span></span></span><span><span></span><span><span><span>x</span></span></span><span>)</span><span></span><span>=</span><span></span></span><span><span></span><span>σ</span><span>(</span><span>z</span><span>)</span><span></span><span>=</span><span></span></span><span><span></span><span><span></span><span><span><span><span><span><span></span><span><span>1</span><span></span><span>+</span><span></span><span><span>e</span><span><span><span><span><span><span></span><span><span><span>−</span><span>z</span></span></span></span></span></span></span></span></span></span></span><span><span></span><span></span></span><span><span></span><span><span>1</span></span></span></span><span>​</span></span><span><span><span></span></span></span></span></span><span></span></span></span></span></span></span><p>它对应的对数几率为：</p><span><span><span>ln⁡p(y=1∣x)1−p(y=1∣x)=wTx+b\ln\frac{p(y=1\mid\boldsymbol{x})}
{1-p(y=1\mid\boldsymbol{x})}
=\boldsymbol{w}^{\mathrm T}\boldsymbol{x}+b</span><span><span><span></span><span>ln</span><span></span><span><span></span><span><span><span><span><span><span></span><span><span>1</span><span></span><span>−</span><span></span><span>p</span><span>(</span><span>y</span><span></span><span>=</span><span></span><span>1</span><span></span><span>∣</span><span></span><span><span><span>x</span></span></span><span>)</span></span></span><span><span></span><span></span></span><span><span></span><span><span>p</span><span>(</span><span>y</span><span></span><span>=</span><span></span><span>1</span><span></span><span>∣</span><span></span><span><span><span>x</span></span></span><span>)</span></span></span></span><span>​</span></span><span><span><span></span></span></span></span></span><span></span></span><span></span><span>=</span><span></span></span><span><span></span><span><span><span><span>w</span></span></span><span><span><span><span><span><span></span><span><span><span>T</span></span></span></span></span></span></span></span></span><span><span><span>x</span></span></span><span></span><span>+</span><span></span></span><span><span></span><span>b</span></span></span></span></span><p></p><figure><img src="https://img-blog.csdnimg.cn/direct/62ffa64366c94340af6113681861aed6.png" alt="在这里插入图片描述" /><figcaption>在这里插入图片描述</figcaption></figure><p></p><p>若把 <span><span>p(y=1∣x)p(y=1\mid\boldsymbol x)</span><span><span><span></span><span>p</span><span>(</span><span>y</span><span></span><span>=</span><span></span></span><span><span></span><span>1</span><span></span><span>∣</span><span></span></span><span><span></span><span><span><span>x</span></span></span><span>)</span></span></span></span> 视为样本属于正类的概率，则 <span><span>1−p(y=1∣x)1-p(y=1\mid\boldsymbol x)</span><span><span><span></span><span>1</span><span></span><span>−</span><span></span></span><span><span></span><span>p</span><span>(</span><span>y</span><span></span><span>=</span><span></span></span><span><span></span><span>1</span><span></span><span>∣</span><span></span></span><span><span></span><span><span><span>x</span></span></span><span>)</span></span></span></span> 是属于负类的概率。二者之比称为几率（odds），几率的对数称为对数几率（log odds）。</p><p>最小二乘是一种损失准则，并不是只适用于凸函数的优化算法。在线性回归中，它恰好形成凸二次目标并可在满秩条件下得到闭式解。</p><p>对数几率回归通常使用极大似然估计，其负对数似然是凸函数，可通过梯度下降、牛顿法等数值方法求解。</p></section></section></section>
<section><h2>决策树：用属性划分构造规则<a href="#决策树用属性划分构造规则"><span>#</span></a></h2><p>决策树通过递归选择属性划分数据。信息增益、增益率和基尼指数决定如何分裂，剪枝则用于控制过拟合。</p><section><h3>递归终止条件<a href="#递归终止条件"><span>#</span></a></h3><p>决策树使用递归实现，而递归终止条件有以下三种：</p><ol>
<li>当前结点所有样本属于同类，无需划分</li>
<li>当前属性集为空，无法划分，选取<strong>此节点</strong>中数量更多的标记作为类别标记</li>
<li>当前样本集为空，不能划分，依据<strong>父节点</strong>中数量更多的标记作为类别标记</li>
</ol></section><section><h3>名词概念<a href="#名词概念"><span>#</span></a></h3><p>1.纯度：同类聚集程度高、不同类越分散，则纯度越高
2.信息熵：纯度的量化指标，来源于信息论
3.剪枝：防止决策树过拟合，减去部分划分属性。分为预剪枝和后剪枝</p></section><section><h3>信息熵<a href="#信息熵"><span>#</span></a></h3><p><strong>信息熵计算公式</strong>：</p><span><span><span>Ent⁡(D)=−∑k=1∣Y∣pklog⁡2pk\operatorname{Ent}(D)
=-\sum_{k=1}^{|\mathcal Y|}p_k\log_2p_k</span><span><span><span></span><span><span>Ent</span></span><span>(</span><span>D</span><span>)</span><span></span><span>=</span><span></span></span><span><span></span><span>−</span><span></span><span><span><span><span><span><span></span><span><span><span>k</span><span>=</span><span>1</span></span></span></span><span><span></span><span><span>∑</span></span></span><span><span></span><span><span><span>∣</span><span>Y</span><span>∣</span></span></span></span></span><span>​</span></span><span><span><span></span></span></span></span></span><span></span><span><span>p</span><span><span><span><span><span><span></span><span><span>k</span></span></span></span><span>​</span></span><span><span><span></span></span></span></span></span></span><span></span><span><span>lo<span>g</span></span><span><span><span><span><span><span></span><span><span>2</span></span></span></span><span>​</span></span><span><span><span></span></span></span></span></span></span><span></span><span><span>p</span><span><span><span><span><span><span></span><span><span>k</span></span></span></span><span>​</span></span><span><span><span></span></span></span></span></span></span></span></span></span></span><p><strong>信息熵用于衡量信息的不确定性或信息的混乱程度</strong>，我们可以将其用于<strong>量化纯度</strong>
信息熵越大，数据分布越均匀、随机、杂乱无章，明显这不是我们想要的。
我们想要的是相同类靠近，不同类远离的效果，即<strong>需要越小的信息熵</strong></p><p><span><span>pkp_k</span><span><span><span></span><span><span>p</span><span><span><span><span><span><span></span><span><span>k</span></span></span></span><span>​</span></span><span><span><span></span></span></span></span></span></span></span></span></span>表示选到k类别的<strong>概率</strong>，而 <span><span>−log⁡2pk-\log _ { 2 } p _ { k }</span><span><span><span></span><span>−</span><span></span><span><span>lo<span>g</span></span><span><span><span><span><span><span></span><span><span><span>2</span></span></span></span></span><span>​</span></span><span><span><span></span></span></span></span></span></span><span></span><span><span>p</span><span><span><span><span><span><span></span><span><span><span>k</span></span></span></span></span><span>​</span></span><span><span><span></span></span></span></span></span></span></span></span></span>则表示<strong>信息量</strong></p><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>我们可以理解对于某一事件，其发生的概率越小，那么其信息量越大；发生的概率越大，那么其信息量越小。所有对两者求期望即得到信息熵。</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p><strong>注意</strong>：<span><span>∣Y∣|\mathcal Y|</span><span><span><span></span><span>∣</span><span>Y</span><span>∣</span></span></span></span> 表示类别集合中的类别数量，例如二分类任务中 <span><span>∣Y∣=2|\mathcal Y|=2</span><span><span><span></span><span>∣</span><span>Y</span><span>∣</span><span></span><span>=</span><span></span></span><span><span></span><span>2</span></span></span></span>。</p></section><section><h3>划分选择<a href="#划分选择"><span>#</span></a></h3><p>决策树中<u>最重要</u>的部分，用于<strong>选择最佳划分属性</strong></p><section><h4>1.信息增益(Information Gain)<a href="#1信息增益information-gain"><span>#</span></a></h4><p>计算<strong>某属性</strong>的信息增益，用<u>根节点</u>的信息熵-属性<u>各个</u>属性值的<u>信息熵的加权平均值</u>
<strong>信息增益越大，意味着使用属性a进行划分获得的纯度提升越大</strong></p><span><span><span>Gain⁡(D,a)=Ent⁡(D)−∑v=1V∣Dv∣∣D∣Ent⁡(Dv)\begin{aligned}
\operatorname{Gain}(D,a)
={}&amp;\operatorname{Ent}(D)\\
&amp;-\sum_{v=1}^{V}
\frac{|D^v|}{|D|}
\operatorname{Ent}(D^v)
\end{aligned}</span><span><span><span></span><span><span><span><span><span><span><span><span></span><span><span><span>Gain</span></span><span>(</span><span>D</span><span>,</span><span></span><span>a</span><span>)</span><span></span><span>=</span><span></span><span></span></span></span><span><span></span><span></span></span></span><span>​</span></span><span><span><span></span></span></span></span></span><span><span><span><span><span><span></span><span><span></span><span></span><span><span>Ent</span></span><span>(</span><span>D</span><span>)</span></span></span><span><span></span><span><span></span><span></span><span>−</span><span></span><span><span><span><span><span><span></span><span><span><span>v</span><span>=</span><span>1</span></span></span></span><span><span></span><span><span>∑</span></span></span><span><span></span><span><span><span>V</span></span></span></span></span><span>​</span></span><span><span><span></span></span></span></span></span><span></span><span><span></span><span><span><span><span><span><span></span><span><span>∣</span><span>D</span><span>∣</span></span></span><span><span></span><span></span></span><span><span></span><span><span>∣</span><span><span>D</span><span><span><span><span><span><span></span><span><span>v</span></span></span></span></span></span></span></span><span>∣</span></span></span></span><span>​</span></span><span><span><span></span></span></span></span></span><span></span></span><span></span><span><span>Ent</span></span><span>(</span><span><span>D</span><span><span><span><span><span><span></span><span><span>v</span></span></span></span></span></span></span></span><span>)</span></span></span></span><span>​</span></span><span><span><span></span></span></span></span></span></span></span></span></span></span></span></section><section><h4>2.增益率(Gain Ratio)<a href="#2增益率gain-ratio"><span>#</span></a></h4><p>由于<strong>信息增益</strong>对于<u>取值数目较多</u>的属性<strong>有所偏好</strong>，所以引进<strong>增益率</strong>进行选择最优划分属性，比如著名的<u>C4.5决策树算法</u></p><p>属性的取值越分散，固有值 <span><span>IV⁡(a)\operatorname{IV}(a)</span><span><span><span></span><span><span>IV</span></span><span>(</span><span>a</span><span>)</span></span></span></span> 通常越大。增益率用信息增益除以固有值，从而抑制信息增益对多取值属性的偏好：</p><span><span><span>GainRatio⁡(D,a)=Gain⁡(D,a)IV⁡(a)\operatorname{GainRatio}(D,a)
=\frac{\operatorname{Gain}(D,a)}
{\operatorname{IV}(a)}</span><span><span><span></span><span><span>GainRatio</span></span><span>(</span><span>D</span><span>,</span><span></span><span>a</span><span>)</span><span></span><span>=</span><span></span></span><span><span></span><span><span></span><span><span><span><span><span><span></span><span><span><span>IV</span></span><span>(</span><span>a</span><span>)</span></span></span><span><span></span><span></span></span><span><span></span><span><span><span>Gain</span></span><span>(</span><span>D</span><span>,</span><span></span><span>a</span><span>)</span></span></span></span><span>​</span></span><span><span><span></span></span></span></span></span><span></span></span></span></span></span></span><span><span><span>IV⁡(a)=−∑v=1V∣Dv∣∣D∣log⁡2∣Dv∣∣D∣\operatorname{IV}(a)
=-\sum_{v=1}^{V}
\frac{|D^v|}{|D|}
\log_2\frac{|D^v|}{|D|}</span><span><span><span></span><span><span>IV</span></span><span>(</span><span>a</span><span>)</span><span></span><span>=</span><span></span></span><span><span></span><span>−</span><span></span><span><span><span><span><span><span></span><span><span><span>v</span><span>=</span><span>1</span></span></span></span><span><span></span><span><span>∑</span></span></span><span><span></span><span><span><span>V</span></span></span></span></span><span>​</span></span><span><span><span></span></span></span></span></span><span></span><span><span></span><span><span><span><span><span><span></span><span><span>∣</span><span>D</span><span>∣</span></span></span><span><span></span><span></span></span><span><span></span><span><span>∣</span><span><span>D</span><span><span><span><span><span><span></span><span><span>v</span></span></span></span></span></span></span></span><span>∣</span></span></span></span><span>​</span></span><span><span><span></span></span></span></span></span><span></span></span><span></span><span><span>lo<span>g</span></span><span><span><span><span><span><span></span><span><span>2</span></span></span></span><span>​</span></span><span><span><span></span></span></span></span></span></span><span></span><span><span></span><span><span><span><span><span><span></span><span><span>∣</span><span>D</span><span>∣</span></span></span><span><span></span><span></span></span><span><span></span><span><span>∣</span><span><span>D</span><span><span><span><span><span><span></span><span><span>v</span></span></span></span></span></span></span></span><span>∣</span></span></span></span><span>​</span></span><span><span><span></span></span></span></span></span><span></span></span></span></span></span></span><p>不过，增益率可能偏好固有值很小的属性。C4.5 通常先保留信息增益高于平均水平的候选属性，再从中选择增益率最大的属性。</p><section><h5>思考🤔：<a href="#思考"><span>#</span></a></h5><p>增益率可以减少 <u>信息增益对于属性值较多的属性有所偏好</u> 的问题，
但是同样的，<strong>增益率对于属性值较少的属性有所偏好</strong>
所以实际处理时，对于不同的属性的划分选择可以采取不同的方法，但是在<strong>同一层</strong>属性划分选择时只能使用同一种方法。</p></section></section><section><h4>3.基尼指数(Gini Index)<a href="#3基尼指数gini-index"><span>#</span></a></h4><p>含义：<strong>反映从数据集中随机抽取两个样本，其类别标记不一致的概率</strong></p><p>CART 分类树常用基尼指数选择划分；CART 回归树通常使用平方误差等回归损失，而不是下面的分类基尼公式。</p><p><strong>基尼指数越小，则数据集纯度越高。</strong></p><span><span><span>Gini⁡(D)=1−∑k=1∣Y∣pk2\operatorname{Gini}(D)
=1-\sum_{k=1}^{|\mathcal Y|}p_k^2</span><span><span><span></span><span><span>Gini</span></span><span>(</span><span>D</span><span>)</span><span></span><span>=</span><span></span></span><span><span></span><span>1</span><span></span><span>−</span><span></span></span><span><span></span><span><span><span><span><span><span></span><span><span><span>k</span><span>=</span><span>1</span></span></span></span><span><span></span><span><span>∑</span></span></span><span><span></span><span><span><span>∣</span><span>Y</span><span>∣</span></span></span></span></span><span>​</span></span><span><span><span></span></span></span></span></span><span></span><span><span>p</span><span><span><span><span><span><span></span><span><span>k</span></span></span><span><span></span><span><span>2</span></span></span></span><span>​</span></span><span><span><span></span></span></span></span></span></span></span></span></span></span><span><span><span>GiniIndex⁡(D,a)=∑v=1V∣Dv∣∣D∣Gini⁡(Dv)\operatorname{GiniIndex}(D,a)
=\sum_{v=1}^{V}
\frac{|D^v|}{|D|}
\operatorname{Gini}(D^v)</span><span><span><span></span><span><span>GiniIndex</span></span><span>(</span><span>D</span><span>,</span><span></span><span>a</span><span>)</span><span></span><span>=</span><span></span></span><span><span></span><span><span><span><span><span><span></span><span><span><span>v</span><span>=</span><span>1</span></span></span></span><span><span></span><span><span>∑</span></span></span><span><span></span><span><span><span>V</span></span></span></span></span><span>​</span></span><span><span><span></span></span></span></span></span><span></span><span><span></span><span><span><span><span><span><span></span><span><span>∣</span><span>D</span><span>∣</span></span></span><span><span></span><span></span></span><span><span></span><span><span>∣</span><span><span>D</span><span><span><span><span><span><span></span><span><span>v</span></span></span></span></span></span></span></span><span>∣</span></span></span></span><span>​</span></span><span><span><span></span></span></span></span></span><span></span></span><span></span><span><span>Gini</span></span><span>(</span><span><span>D</span><span><span><span><span><span><span></span><span><span>v</span></span></span></span></span></span></span></span><span>)</span></span></span></span></span></section></section><section><h3>剪枝(Pruning)<a href="#剪枝pruning"><span>#</span></a></h3><p>为了<strong>防止决策树模型过拟合</strong>而采取的手段
分为<strong>预剪枝</strong>与<strong>后剪枝</strong>两种方法</p><p><strong>PS</strong>：需要先选定 <u>评估方法</u>和<u>性能度量</u>（第二章知识内容）
下列假设使用<strong>留出法</strong>，以<strong>精度</strong>为性能度量</p><section><h4>预剪枝(Prepruning)<a href="#预剪枝prepruning"><span>#</span></a></h4><p>运用<strong>贪心思想</strong>，可能有弊端</p><p>在建立决策树的<u>过程中</u>进行剪枝，使用验证数据比较当前精度与增加划分属性后的精度，
<strong>判断是否需要增加划分属性</strong></p></section><section><h4>后剪枝 (Postpruning)<a href="#后剪枝-postpruning"><span>#</span></a></h4><p>在决策树建立<u>完成后</u>进行剪枝，使用验证数据比较当前精度与把该结点变成叶节点后的精度，
<strong>判断是否需要剪去不要的树枝</strong>
<strong>优点</strong>：欠拟合风险小，泛化性能较好</p></section></section></section>
<section><h2>支持向量机：最大间隔与核方法<a href="#支持向量机最大间隔与核方法"><span>#</span></a></h2><p>支持向量机从几何间隔出发，通过对偶问题与核函数处理高维非线性任务，并可扩展到支持向量回归。</p><section><h3>章节介绍<a href="#章节介绍"><span>#</span></a></h3><p>统计学领域名声赫赫的<strong>SVM</strong>与<strong>核方法</strong>是时至今日仍在高频使用的经典算法。</p></section><section><h3>笔记介绍<a href="#笔记介绍"><span>#</span></a></h3><p>对本章各节知识点进行汇总，主要分为<u>引入原因，原理思想，和一些思考</u>，对于数学推导内容介绍较少，有需要的可以先阅读西瓜书，再参考南瓜书的数学推导。</p></section><section><h3>数学知识<a href="#数学知识"><span>#</span></a></h3><p>大部分都是规划类、最优化的问题，最好先进行相关知识的学习。</p><p>这里<strong>推荐一本书</strong>：最优化：建模、算法与理论 (刘浩洋 户将 李勇锋 文再文)</p></section><section><h3>SVM-支持向量机<a href="#svm-支持向量机"><span>#</span></a></h3><section><h4>引入原因：<a href="#引入原因"><span>#</span></a></h4><p>在<strong>线性可分</strong>的条件下，我们在训练集做分类任务时，最基本的想法就是在样本空间中找到一个<strong>超平面</strong>进行划分,
但是对于分类任务，我们可以画出很多个超平面，这时候就需要引入<strong>损失函数</strong>，对超平面进行选择，而使得<strong>两个异类支持向量</strong>的<u>距离最大化</u>，就是我们所说的支持向量机的<strong>基本型</strong>。</p><p><img alt="" loading="lazy" width="495" height="340" src="/_astro/1.BK0QkQpL_1W7fwB.webp" /></p></section><section><h4>数学公式及其原理：<a href="#数学公式及其原理"><span>#</span></a></h4><p>仍用线性模型表示分类超平面：</p><span><span><span>wTx+b=0\boldsymbol{w}^{\mathrm T}\boldsymbol{x}+b=0</span><span><span><span></span><span><span><span><span>w</span></span></span><span><span><span><span><span><span></span><span><span><span>T</span></span></span></span></span></span></span></span></span><span><span><span>x</span></span></span><span></span><span>+</span><span></span></span><span><span></span><span>b</span><span></span><span>=</span><span></span></span><span><span></span><span>0</span></span></span></span></span><p>点 <span><span>x\boldsymbol x</span><span><span><span></span><span><span><span>x</span></span></span></span></span></span> 到该超平面的几何距离为：</p><span><span><span>r(x)=∣wTx+b∣∥w∥2r(\boldsymbol{x})
=\frac{|\boldsymbol{w}^{\mathrm T}\boldsymbol{x}+b|}
{\|\boldsymbol{w}\|_2}</span><span><span><span></span><span>r</span><span>(</span><span><span><span>x</span></span></span><span>)</span><span></span><span>=</span><span></span></span><span><span></span><span><span></span><span><span><span><span><span><span></span><span><span>∥</span><span><span><span>w</span></span></span><span><span>∥</span><span><span><span><span><span><span></span><span><span>2</span></span></span></span><span>​</span></span><span><span><span></span></span></span></span></span></span></span></span><span><span></span><span></span></span><span><span></span><span><span>∣</span><span><span><span><span>w</span></span></span><span><span><span><span><span><span></span><span><span><span>T</span></span></span></span></span></span></span></span></span><span><span><span>x</span></span></span><span></span><span>+</span><span></span><span>b</span><span>∣</span></span></span></span><span>​</span></span><span><span><span></span></span></span></span></span><span></span></span></span></span></span></span><p>令类别标记 <span><span>yi∈{−1,+1}y_i\in\{-1,+1\}</span><span><span><span></span><span><span>y</span><span><span><span><span><span><span></span><span><span>i</span></span></span></span><span>​</span></span><span><span><span></span></span></span></span></span></span><span></span><span>∈</span><span></span></span><span><span></span><span>{</span><span>−</span><span>1</span><span>,</span><span></span><span>+</span><span>1</span><span>}</span></span></span></span>，并采用规范化约束</p><span><span><span>yi(wTxi+b)≥1,i=1,…,my_i(\boldsymbol{w}^{\mathrm T}\boldsymbol{x}_i+b)\geq 1,
\qquad i=1,\ldots,m</span><span><span><span></span><span><span>y</span><span><span><span><span><span><span></span><span><span>i</span></span></span></span><span>​</span></span><span><span><span></span></span></span></span></span></span><span>(</span><span><span><span><span>w</span></span></span><span><span><span><span><span><span></span><span><span><span>T</span></span></span></span></span></span></span></span></span><span><span><span><span>x</span></span></span><span><span><span><span><span><span></span><span><span>i</span></span></span></span><span>​</span></span><span><span><span></span></span></span></span></span></span><span></span><span>+</span><span></span></span><span><span></span><span>b</span><span>)</span><span></span><span>≥</span><span></span></span><span><span></span><span>1</span><span>,</span><span></span><span></span><span>i</span><span></span><span>=</span><span></span></span><span><span></span><span>1</span><span>,</span><span></span><span>…</span><span></span><span>,</span><span></span><span>m</span></span></span></span></span><p>两条间隔边界 <span><span>wTx+b=±1\boldsymbol w^{\mathrm T}\boldsymbol x+b=\pm1</span><span><span><span></span><span><span><span><span>w</span></span></span><span><span><span><span><span><span></span><span><span><span>T</span></span></span></span></span></span></span></span></span><span><span><span>x</span></span></span><span></span><span>+</span><span></span></span><span><span></span><span>b</span><span></span><span>=</span><span></span></span><span><span></span><span>±</span><span>1</span></span></span></span> 之间的距离为：</p><span><span><span>γ=2∥w∥2\gamma=\frac{2}{\|\boldsymbol{w}\|_2}</span><span><span><span></span><span>γ</span><span></span><span>=</span><span></span></span><span><span></span><span><span></span><span><span><span><span><span><span></span><span><span>∥</span><span><span><span>w</span></span></span><span><span>∥</span><span><span><span><span><span><span></span><span><span>2</span></span></span></span><span>​</span></span><span><span><span></span></span></span></span></span></span></span></span><span><span></span><span></span></span><span><span></span><span><span>2</span></span></span></span><span>​</span></span><span><span><span></span></span></span></span></span><span></span></span></span></span></span></span><p><img alt="" loading="lazy" width="438" height="328" src="/_astro/2.CGBt_FUJ_2lNxQK.webp" /></p><p>最大化间隔等价于最小化 <span><span>∥w∥22\|\boldsymbol w\|_2^2</span><span><span><span></span><span>∥</span><span><span><span>w</span></span></span><span><span>∥</span><span><span><span><span><span><span></span><span><span>2</span></span></span><span><span></span><span><span>2</span></span></span></span><span>​</span></span><span><span><span></span></span></span></span></span></span></span></span></span>，因此硬间隔 SVM 的基本形式为：</p><span><span><span>min⁡w,b12∥w∥22s.t.yi(wTxi+b)≥1,i=1,…,m\begin{aligned}
\min_{\boldsymbol w,b}\quad
&amp;\frac{1}{2}\|\boldsymbol w\|_2^2 \\
\text{s.t.}\quad
&amp;y_i(\boldsymbol w^{\mathrm T}\boldsymbol x_i+b)\geq1,
\quad i=1,\ldots,m
\end{aligned}</span><span><span><span></span><span><span><span><span><span><span><span><span></span><span><span><span><span><span><span><span></span><span><span><span><span><span>w</span></span></span><span>,</span><span>b</span></span></span></span><span><span></span><span><span>min</span></span></span></span><span>​</span></span><span><span><span></span></span></span></span></span><span></span></span></span><span><span></span><span><span><span>s.t.</span></span><span></span></span></span></span><span>​</span></span><span><span><span></span></span></span></span></span><span><span><span><span><span><span></span><span><span></span><span><span></span><span><span><span><span><span><span></span><span><span>2</span></span></span><span><span></span><span></span></span><span><span></span><span><span>1</span></span></span></span><span>​</span></span><span><span><span></span></span></span></span></span><span></span></span><span>∥</span><span><span><span>w</span></span></span><span><span>∥</span><span><span><span><span><span><span></span><span><span>2</span></span></span><span><span></span><span><span>2</span></span></span></span><span>​</span></span><span><span><span></span></span></span></span></span></span></span></span><span><span></span><span><span></span><span><span>y</span><span><span><span><span><span><span></span><span><span>i</span></span></span></span><span>​</span></span><span><span><span></span></span></span></span></span></span><span>(</span><span><span><span><span>w</span></span></span><span><span><span><span><span><span></span><span><span><span>T</span></span></span></span></span></span></span></span></span><span><span><span><span>x</span></span></span><span><span><span><span><span><span></span><span><span>i</span></span></span></span><span>​</span></span><span><span><span></span></span></span></span></span></span><span></span><span>+</span><span></span><span>b</span><span>)</span><span></span><span>≥</span><span></span><span>1</span><span>,</span><span></span><span></span><span>i</span><span></span><span>=</span><span></span><span>1</span><span>,</span><span></span><span>…</span><span></span><span>,</span><span></span><span>m</span></span></span></span><span>​</span></span><span><span><span></span></span></span></span></span></span></span></span></span></span></span><p><img alt="" loading="lazy" width="406" height="108" src="/_astro/3.wg3K8beP_17Msuh.webp" /></p></section><section><h4>求解方法：<a href="#求解方法"><span>#</span></a></h4><p>涉及到二次规划问题，使用<strong>拉格朗日乘子法</strong>解决问题，我们会得到<u>基本型</u>的“<strong>对偶问题</strong>”</p><p>具体方法不做详解，此处仅做大概阐述：</p><ol>
<li>为每条不等式约束引入拉格朗日乘子 <span><span>αi≥0\alpha_i\geq0</span><span><span><span></span><span><span>α</span><span><span><span><span><span><span></span><span><span>i</span></span></span></span><span>​</span></span><span><span><span></span></span></span></span></span></span><span></span><span>≥</span><span></span></span><span><span></span><span>0</span></span></span></span>。</li>
<li>令拉格朗日函数对 <span><span>w\boldsymbol w</span><span><span><span></span><span><span><span>w</span></span></span></span></span></span> 和 <span><span>bb</span><span><span><span></span><span>b</span></span></span></span> 的偏导为零，消去原始变量。</li>
<li>得到关于 <span><span>α\boldsymbol\alpha</span><span><span><span></span><span><span><span>α</span></span></span></span></span></span> 的对偶二次规划问题，同时保留 <span><span>αi≥0\alpha_i\geq0</span><span><span><span></span><span><span>α</span><span><span><span><span><span><span></span><span><span>i</span></span></span></span><span>​</span></span><span><span><span></span></span></span></span></span></span><span></span><span>≥</span><span></span></span><span><span></span><span>0</span></span></span></span> 与 <span><span>∑iαiyi=0\sum_i\alpha_i y_i=0</span><span><span><span></span><span><span>∑</span><span><span><span><span><span><span></span><span><span>i</span></span></span></span><span>​</span></span><span><span><span></span></span></span></span></span></span><span></span><span><span>α</span><span><span><span><span><span><span></span><span><span>i</span></span></span></span><span>​</span></span><span><span><span></span></span></span></span></span></span><span><span>y</span><span><span><span><span><span><span></span><span><span>i</span></span></span></span><span>​</span></span><span><span><span></span></span></span></span></span></span><span></span><span>=</span><span></span></span><span><span></span><span>0</span></span></span></span> 等约束。</li>
<li>可使用二次规划算法或 SMO 求解 <span><span>α\boldsymbol\alpha</span><span><span><span></span><span><span><span>α</span></span></span></span></span></span>，再恢复 <span><span>w\boldsymbol w</span><span><span><span></span><span><span><span>w</span></span></span></span></span></span> 与 <span><span>bb</span><span><span><span></span><span>b</span></span></span></span>。因此对偶问题并不是普通的无约束优化。</li>
</ol></section></section><section><h3>核函数<a href="#核函数"><span>#</span></a></h3><section><h4>引入原因：<a href="#引入原因-1"><span>#</span></a></h4><p>由于前面的讨论都是基于训练集是<strong>线性可分的假设</strong>
对于原始空间中线性不可分的数据，可以通过核函数隐式计算高维特征空间中的内积，从而学习非线性决策边界。映射后也不保证数据一定完全线性可分，因此仍可能需要软间隔。</p></section><section><h4>升维思想：<a href="#升维思想"><span>#</span></a></h4><blockquote><p>通过特征映射把数据表示到新的空间，并使用核函数直接计算映射后样本的内积；这样可以得到原始空间中的非线性决策边界。</p></blockquote></section><section><h4>常用核函数：<a href="#常用核函数"><span>#</span></a></h4><p>下面给出常用核函数：</p><p><img alt="" loading="lazy" width="705" height="245" src="/_astro/4.CX981-wQ_Z1a106b.webp" /></p></section></section><section><h3>软间隔和正则化：<a href="#软间隔和正则化"><span>#</span></a></h3><section><h4>引入原因：<a href="#引入原因-2"><span>#</span></a></h4><p>前面的硬间隔模型假设训练样本线性可分。实际数据可能包含噪声、类别重叠和异常点，即使使用核函数，也不应要求所有样本都被绝对正确地分开，否则模型可能过拟合。</p><p>所以我们引入了<strong>软间隔</strong>（soft margin），即允许部分样本违反间隔约束或被错误分类。
而相应的，先前我们<u>让所有样本正确分类</u>就叫做<strong>硬间隔</strong>。</p></section><section><h4>正则化思想：<a href="#正则化思想"><span>#</span></a></h4><p>其实软间隔也就是一种正则化，通过对不希望的到的结果进行惩罚，使得优化过程趋向于希望目标。</p></section></section><section><h3>支持向量回归<a href="#支持向量回归"><span>#</span></a></h3><section><h4>引入原因：<a href="#引入原因-3"><span>#</span></a></h4><p>我们一开始引入SVM就是为了解决分类问题，而对于回归问题是否也能通过SVM解决呢？🤔</p><p>为了解决回归问题，我们引入了<strong>支持向量回归</strong>，简称SVR(Support Vector Regression)</p></section><section><h4>方法思想：<a href="#方法思想"><span>#</span></a></h4><p>在<strong>损失函数</strong>方面，与<u>传统的回归模型</u>用预测和标记的差别计算损失<u>不同</u>，</p><p>SVR 使用 <span><span>ϵ\epsilon</span><span><span><span></span><span>ϵ</span></span></span></span>-不敏感损失：当预测误差满足 <span><span>∣f(xi)−yi∣≤ϵ|f(\boldsymbol x_i)-y_i|\leq\epsilon</span><span><span><span></span><span>∣</span><span>f</span><span>(</span><span><span><span><span>x</span></span></span><span><span><span><span><span><span></span><span><span>i</span></span></span></span><span>​</span></span><span><span><span></span></span></span></span></span></span><span>)</span><span></span><span>−</span><span></span></span><span><span></span><span><span>y</span><span><span><span><span><span><span></span><span><span>i</span></span></span></span><span>​</span></span><span><span><span></span></span></span></span></span></span><span>∣</span><span></span><span>≤</span><span></span></span><span><span></span><span>ϵ</span></span></span></span> 时不产生损失。几何上，这对应预测函数上下各宽 <span><span>ϵ\epsilon</span><span><span><span></span><span>ϵ</span></span></span></span>、总宽 <span><span>2ϵ2\epsilon</span><span><span><span></span><span>2</span><span>ϵ</span></span></span></span> 的容忍带。</p></section></section></section>
<section><h2>如何选择模型<a href="#如何选择模型"><span>#</span></a></h2><ul>
<li>特征数量适中、强调可解释性或需要快速建立基线时，优先考虑线性模型。</li>
<li>数据中存在清晰的条件规则、特征尺度差异较大或需要直观解释时，可以尝试决策树及其集成方法。</li>
<li>样本量不大但特征维度较高、类别间隔较明显时，支持向量机通常值得尝试。</li>
</ul><p>无论选择哪一种模型，都应回到第二章的评估方法：使用独立验证过程、合适的性能度量和一致的实验设置进行比较。</p></section>
<section><h2>三类模型的共同点<a href="#三类模型的共同点"><span>#</span></a></h2><p>它们都在特定假设空间中寻找经验风险较小、同时具有一定泛化能力的函数。线性模型依赖参数约束，决策树依赖划分与剪枝，支持向量机依赖最大间隔与正则化。差异最终都可以理解为不同的归纳偏好。</p></section>
<section><h2>总结<a href="#总结"><span>#</span></a></h2><p>线性模型提供简洁基线，决策树提供可读规则，支持向量机提供最大间隔与核技巧。理解三者的表示方式和约束方法，比单独记忆公式更有助于解决实际问题。</p></section>]]></content>
    </entry>
    <entry>
      <id>https://www.celyn-blog.xyz/posts/machine-learning-foundations-and-evaluation/</id>
      <title type="text">机器学习基础：基本概念、评估方法与性能度量</title>
      <published>2024-05-15T00:00:00.000Z</published>
      <updated>2026-08-18T00:00:00.000Z</updated>
      <author><name>Celyn</name></author>
      <link rel="alternate" href="https://www.celyn-blog.xyz/posts/machine-learning-foundations-and-evaluation/"/>
      <summary type="text">融合西瓜书第 1、2 章学习笔记，梳理机器学习基本概念、归纳偏好、数据划分、性能度量与模型比较。</summary>
      <content type="html"><![CDATA[<p>机器学习不仅是选择一个算法并运行训练。真正完整的实验需要先明确任务、数据和假设，再决定如何划分数据、使用什么指标，以及怎样比较模型。</p>
<p>这篇笔记把《机器学习》第一章和第二章连在一起：第一章回答“机器学习在做什么”，第二章回答“怎样判断它做得好不好”。</p>
<section><h2>学习路线<a href="#学习路线"><span>#</span></a></h2><ol>
<li>理解样本、特征、标记、假设空间等基本概念。</li>
<li>认识独立同分布、未知分布和归纳偏好等基本假设。</li>
<li>区分监督学习、无监督学习等任务类型。</li>
<li>使用留出法、交叉验证法或自助法组织实验。</li>
<li>根据任务选择准确率、查准率、查全率、F1、ROC 或 AUC。</li>
<li>避免用测试集调参，并在比较结论中考虑统计波动。</li>
</ol></section>
<section><h2>机器学习的基本概念与假设<a href="#机器学习的基本概念与假设"><span>#</span></a></h2><p>这一部分建立后续章节需要的共同语言，并说明学习算法为什么必须带有归纳偏好。</p><section><h3>本书的使用：<a href="#本书的使用"><span>#</span></a></h3><p><img alt="" loading="lazy" width="962" height="640" src="/_astro/1.D_yMyxfp_24Sj87.webp" />
第1章-绪论</p><section><h4>计算学习理论<a href="#计算学习理论"><span>#</span></a></h4><p><strong>概率近似正确</strong>（Probably Approximately Correct，PAC）描述的是一种学习保证：训练样本由未知分布随机抽取时，学习算法以至少 <span><span>1−δ1-\delta</span><span><span><span></span><span>1</span><span></span><span>−</span><span></span></span><span><span></span><span>δ</span></span></span></span> 的概率得到泛化误差足够小的模型。</p><p>一种常见的不可知 PAC 表述是：</p><span><span><span>Pr⁡S∼Dm[RD(hS)≤inf⁡h∈HRD(h)+ϵ]≥1−δ\Pr_{S\sim\mathcal{D}^{m}}
\left[
R_{\mathcal{D}}(h_S)
\leq
\inf_{h\in\mathcal{H}}R_{\mathcal{D}}(h)+\epsilon
\right]
\geq 1-\delta</span><span><span><span></span><span><span><span><span><span><span></span><span><span><span>S</span><span>∼</span><span><span>D</span><span><span><span><span><span><span></span><span><span><span>m</span></span></span></span></span></span></span></span></span></span></span></span><span><span></span><span><span>Pr</span></span></span></span><span>​</span></span><span><span><span></span></span></span></span></span><span></span><span><span><span>[</span></span><span><span>R</span><span><span><span><span><span><span></span><span><span><span>D</span></span></span></span></span><span>​</span></span><span><span><span></span></span></span></span></span></span><span>(</span><span><span>h</span><span><span><span><span><span><span></span><span><span>S</span></span></span></span><span>​</span></span><span><span><span></span></span></span></span></span></span><span>)</span><span></span><span>≤</span><span></span><span><span><span><span><span><span></span><span><span><span>h</span><span>∈</span><span>H</span></span></span></span><span><span></span><span><span>in<span>f</span></span></span></span></span><span>​</span></span><span><span><span></span></span></span></span></span><span></span><span><span>R</span><span><span><span><span><span><span></span><span><span><span>D</span></span></span></span></span><span>​</span></span><span><span><span></span></span></span></span></span></span><span>(</span><span>h</span><span>)</span><span></span><span>+</span><span></span><span>ϵ</span><span><span>]</span></span></span><span></span><span>≥</span><span></span></span><span><span></span><span>1</span><span></span><span>−</span><span></span></span><span><span></span><span>δ</span></span></span></span></span><p>其中，<span><span>SS</span><span><span><span></span><span>S</span></span></span></span> 是从分布 <span><span>D\mathcal D</span><span><span><span></span><span>D</span></span></span></span> 独立抽取的训练集，<span><span>hSh_S</span><span><span><span></span><span><span>h</span><span><span><span><span><span><span></span><span><span>S</span></span></span></span><span>​</span></span><span><span><span></span></span></span></span></span></span></span></span></span> 是算法根据 <span><span>SS</span><span><span><span></span><span>S</span></span></span></span> 学到的模型，<span><span>RD(h)R_{\mathcal D}(h)</span><span><span><span></span><span><span>R</span><span><span><span><span><span><span></span><span><span><span>D</span></span></span></span></span><span>​</span></span><span><span><span></span></span></span></span></span></span><span>(</span><span>h</span><span>)</span></span></span></span> 是模型在真实分布上的风险。</p><p><span><span>ϵ\epsilon</span><span><span><span></span><span>ϵ</span></span></span></span> 表示允许的误差裕量，<span><span>δ\delta</span><span><span><span></span><span>δ</span></span></span></span> 表示学习保证失败的概率。公式中的概率来自训练集的随机抽取，而不是“取得某个模型”的概率。</p><p>PAC 讨论的是样本复杂度和泛化保证，不能由 <span><span>ϵ=0\epsilon=0</span><span><span><span></span><span>ϵ</span><span></span><span>=</span><span></span></span><span><span></span><span>0</span></span></span></span>、<span><span>δ=0\delta=0</span><span><span><span></span><span>δ</span><span></span><span>=</span><span></span></span><span><span></span><span>0</span></span></span></span> 推导出 P 与 NP 的关系。通常只要求二者为足够小的正数。</p></section><section><h4>术语名词<a href="#术语名词"><span>#</span></a></h4><p>1.示例 = 特征向量，样本</p><ul>
<li>名词解释:<code>即对某个事件或者对象的 全局 描述</code></li>
<li>构成元素:<code>多组(特征:特征值)</code></li>
<li>样本 要根据上下文来判断含义</li>
</ul><p>2.特征 = 属性</p><ul>
<li>名词解释：<code>即对某个事件或对象的一个 具体 特征的描述</code></li>
</ul><p>3.样本空间 = 属性空间 = 输入空间</p><ul>
<li>名词解释：<code>即特征张成的空间，空间中每个点对应一个特征向量即样本</code></li>
</ul><p>4.数据集、训练集、验证集与测试集</p><ul>
<li>训练集用于拟合模型参数。</li>
<li>验证集用于选择模型和超参数。</li>
<li>测试集只用于最终评估，不能参与调参。</li>
</ul><p>一个包含 <span><span>mm</span><span><span><span></span><span>m</span></span></span></span> 个样本的数据集可表示为：</p><span><span><span>D={x1,x2,…,xm}D=\left\{\boldsymbol{x}_1,\boldsymbol{x}_2,\ldots,\boldsymbol{x}_m\right\}</span><span><span><span></span><span>D</span><span></span><span>=</span><span></span></span><span><span></span><span><span>{</span><span><span><span><span>x</span></span></span><span><span><span><span><span><span></span><span><span>1</span></span></span></span><span>​</span></span><span><span><span></span></span></span></span></span></span><span>,</span><span></span><span><span><span><span>x</span></span></span><span><span><span><span><span><span></span><span><span>2</span></span></span></span><span>​</span></span><span><span><span></span></span></span></span></span></span><span>,</span><span></span><span>…</span><span></span><span>,</span><span></span><span><span><span><span>x</span></span></span><span><span><span><span><span><span></span><span><span>m</span></span></span></span><span>​</span></span><span><span><span></span></span></span></span></span></span><span>}</span></span></span></span></span></span><p>若每个样本包含 <span><span>dd</span><span><span><span></span><span>d</span></span></span></span> 个特征，则 <span><span>xi∈Rd\boldsymbol x_i\in\mathbb R^d</span><span><span><span></span><span><span><span><span>x</span></span></span><span><span><span><span><span><span></span><span><span>i</span></span></span></span><span>​</span></span><span><span><span></span></span></span></span></span></span><span></span><span>∈</span><span></span></span><span><span></span><span><span>R</span><span><span><span><span><span><span></span><span><span>d</span></span></span></span></span></span></span></span></span></span></span>。实际划分比例应根据样本量与任务决定，并不存在固定的“二八分”规则。</p><p>5.样例，标记，标记空间</p><ul>
<li>样例=样本+标记</li>
</ul><p><strong>标记</strong>：即想预测的结果的 实际信息，比如想预测瓜的好坏，实际样本中的信息为”好瓜”/“坏瓜”，</p><p>一般表示为：</p><span><span><span>(xi,yi)(\boldsymbol{x}_i,y_i)</span><span><span><span></span><span>(</span><span><span><span><span>x</span></span></span><span><span><span><span><span><span></span><span><span>i</span></span></span></span><span>​</span></span><span><span><span></span></span></span></span></span></span><span>,</span><span></span><span><span>y</span><span><span><span><span><span><span></span><span><span>i</span></span></span></span><span>​</span></span><span><span><span></span></span></span></span></span></span><span>)</span></span></span></span></span><p><strong>标记空间</strong> or <strong>输出空间</strong>：所有标记的集合</p><p>6.假设空间，版本空间</p><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>假设：学得模型关于数据的潜在规律</span></div></div><div><div><div>2</div></div><div>
</div></div><div><div><div>3</div></div><div><span>真实or真相：潜在规律本身</span></div></div><div><div><div>4</div></div><div>
</div></div><div><div><div>5</div></div><div>
</div></div><div><div><div>6</div></div><div><span>假设空间：所有假设构成的集合</span></div></div><div><div><div>7</div></div><div>
</div></div><div><div><div>8</div></div><div><span>版本空间：与训练集一致的假设构成的集合，由一个或多个假设空间的子集构成</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p>…</p></section><section><h4>基本假设<a href="#基本假设"><span>#</span></a></h4><p>我们知道训练出的模型是为了对<strong>未知数据</strong>进行结果预测</p><p>但是为什么模型可以对未知数据进行预测呢？</p><p>这里我们引出了机器学习的基本假设。</p><section><h5>1.未知分布D<a href="#1未知分布d"><span>#</span></a></h5><blockquote><p>通常假设样本空间中全体样本服从一个未知“分布” D</p><p>此处的“分布”指的是概率论中的概率分布</p></blockquote><p>我们假设数据（包括 源数据集 和 未知数据）背后满足某种规律，</p><p>即数据的<strong>采样</strong>来自一个未知的、潜在的 <strong>分布</strong>D</p></section><section><h5>2.独立同分布（i.i.d）<a href="#2独立同分布iid"><span>#</span></a></h5><p>我们获得的每个样本都是独立地从这个分布上采样获得的，即“独立同分布”（简称i.i.d）</p><p>或者说 所有样本都是独立同分布的</p><p>一般而言，训练样本越多，我们得到的关于D的信息也越多</p></section><section><h5>3.一些思考<a href="#3一些思考"><span>#</span></a></h5><p>在现实生活中，大多数样本之间不是独立同分布的，而是相互影响的。</p><p>比如说：在淘宝上 买衣服的人 和 买裤子的人，它们之间可能来自不同的分布，可能买衣服的人推荐买裤子的人来淘宝购物。</p><p>所以现在在机器学习的前沿领域，</p><p><strong>如何突破独立同分布的限制</strong> 是一个重大课题</p></section></section><section><h4>归纳偏好<a href="#归纳偏好"><span>#</span></a></h4><blockquote><p>归纳偏好对应了学习算法本身所做出的关于“什么样的模型更好”的假设</p></blockquote><p>对假设空间 筛选 后所得到的 版本空间中 可能有多个假设
这些假设都能够匹配训练集中的训练样本</p><p>而如何对版本空间中的假设进行选择呢？🤔</p><p>这里引入一个原则or方法论：</p><p><strong>奥卡姆剃刀</strong>：</p><blockquote><p>若非必要勿增实体</p><p>选取多个假设中最简单的。</p></blockquote><p>但是其实感觉没啥用，因为”简单“的定义难以量化。</p><blockquote><p>一个“随机乱猜”的算法有可能优于精心选择的算法</p></blockquote><p><strong>“没有免费的午餐”（NFL）定理：</strong></p><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>任意算法的“训练集外误差”相等，即不同算法的误差期望相同，无绝对意义上的更优算法。</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p><img alt="" loading="lazy" width="1706" height="1279" src="/_astro/2.BxExycys_ZN4Rui.webp" /></p><p>所以 <strong>不能摆脱具体问题</strong> 谈论算法的<strong>优劣</strong></p><p><strong>实际上</strong>：还是看<strong>测试集</strong>再模型上的效果，以及结合<strong>特定领域的需求</strong>对模型进行选择</p></section><section><h4>机器学习分类<a href="#机器学习分类"><span>#</span></a></h4><section><h5>1.监督学习-有导师学习<a href="#1监督学习-有导师学习"><span>#</span></a></h5><p><strong>样本有标记</strong></p><p>1.1 <strong>分类</strong>问题-预测 <strong>离散值</strong></p><ul>
<li>
<p>二分类-正类/负类(反类)</p>
<p><code>一般取值0/1，文本可通过 特征工程 转换为数值型变量</code></p>
<p><code>一般假设正类和负类是可交换的</code></p>
</li>
<li>
<p>多分类
<code>涉及 多类别 的预测输出</code></p>
<p><code>可以转换成二分类问题</code></p>
</li>
</ul><p>1.2 <strong>回归</strong>问题-预测 <strong>连续值</strong></p><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>预测结果 ∈ R</span></div></div></code></pre><div><div></div><div></div></div></figure></div></section><section><h5>2.非监督学习-无导师学习<a href="#2非监督学习-无导师学习"><span>#</span></a></h5><p><strong>样本无标记</strong></p><p>2.1 <strong>聚类</strong>算法</p><ul>
<li>
<p>离散型变量的分类、分组别</p>
</li>
<li>
<p>连续型变量的统计个数，进行密度估计</p>
<p>了解数据内在规律</p>
</li>
</ul><p>2.2 <strong>降维</strong>算法</p><ul>
<li>如PCA主成分分析等</li>
</ul><p>…</p></section></section><section><h4>机器学习的发展<a href="#机器学习的发展"><span>#</span></a></h4><ul>
<li>
<p><strong>符号主义</strong>：源于数学逻辑，产生明确的概念表示</p>
<p>符号主义认为人工智能源于数理逻辑后来又发展了 启发式算法&gt;专家系统&gt;知识工程理论与技术</p>
<p>主要方向：决策树 和 基于逻辑的学习</p>
<div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span><span>  </span></span><span>决策树-&gt;模拟人类对概念的判定树形过程</span></div></div><div><div><div>2</div></div><div>
</div></div><div><div><div>3</div></div><div><span><span>  </span></span><span>基于逻辑的学习--&gt;典型代表：归纳逻辑程序设计(ILP)</span></div></div></code></pre><div><div></div><div></div></div></figure></div>
</li>
<li>
<p><strong>连接主义</strong>：基于神经网络</p>
<p>算法复杂度高，假设空间大，且参数设置缺乏理论指导</p>
<p>经典代表：BP反向传播算法</p>
</li>
<li>
<p><strong>统计学习</strong>：支持向量机(SVM)，核方法</p>
<p>与连接主义关系密切</p>
</li>
<li>
<p><strong>深度学习</strong>：早期连接主义的衍生，基于神经网络，现阶段很流行</p>
</li>
</ul></section></section></section>
<section><h2>模型评估、性能度量与比较<a href="#模型评估性能度量与比较"><span>#</span></a></h2><p>有了学习任务和假设之后，需要设计可靠的实验。数据划分方式与评价指标会直接影响最终结论。</p><section><h3>术语名词<a href="#术语名词-1"><span>#</span></a></h3><p>1.泛化误差与经验误差</p><blockquote><p>泛化误差：在“未来”样本上的误差</p><p>经验误差：在训练集上的误差，亦称“训练误差”</p></blockquote><p>训练集样本数越接近数据集数量，经验误差就越小。
但是经验误差越小，模型效果就越好吗？</p><p>请注意，我们是为了得到泛化能力强的模型，而<strong>经验误差≠泛化误差</strong></p><p>经验误差很小，会使模型学习到训练样本中的许多<strong>无用特征</strong>，导致<strong>泛化能力变弱</strong>
我们称其为<strong>过拟合</strong>(overfitting)</p><p>而与之相对的概念，我们称为<strong>欠拟合</strong>(underfitting)，其表示的就是模型<strong>没有很好的学习</strong>到训练样本上的特征，从而也导致<strong>泛化能力变弱</strong></p><p>2.过拟合与欠拟合</p><blockquote><p>过拟合：模型在训练数据上表现得过于复杂，以至于在未见过的数据上表现不佳。
欠拟合：模型在训练数据上表现得过于简单，无法捕捉到数据的内在结构和模式。</p></blockquote><p><strong>出现原因</strong>：
1.出现<strong>欠拟合</strong>的情况，一般是由于<u>样本特征少</u>，<u>模型复杂度低</u>等</p><p>2.出现<strong>过拟合</strong>的情况，一般是由于<u>样本数量少、噪声多</u>，<u>模型复杂度过高</u>等</p><p>经验误差就是<strong>训练程度</strong>的体现，<u>经验误差越小则训练程度越强</u>
所以现在，我们可以得出训练程度<strong>不能过大也不能过小</strong>的结论</p><p>那我们能找到一个<strong>最佳方案</strong>得出<strong>最佳的训练程度</strong>吗🤔</p><blockquote><p>答案是---------------&gt;不能🙅</p></blockquote><p><strong>原因</strong>是：
对于千禧年七大数学问题之——“P=NP”问题，只要我们相信”P≠NP”，就无法找出最优解</p><p>但是我们依然有相应的解决方法，用于欠拟合与过拟合的问题
此处的解决方法我们留待以后解决…</p><p>好了，回归本章主题—模型评估与选择</p><p>对于<strong>模型的选择</strong>，我们有三个<strong>关键问题</strong>：</p><blockquote><p>如何获得<strong>测试集</strong>？------&gt;<u>评估方法</u></p><p>如何评价<strong>性能</strong>优劣？------&gt;<u>性能度量</u></p><p>如何判断模型<strong>实质</strong>差别------&gt;<u>比较检验</u></p></blockquote></section><section><h3>1.评估方法<a href="#1评估方法"><span>#</span></a></h3><p>因为我们无法得知未来数据的输出标记
故而需要得到<strong>测试集</strong>用来评估模型</p><p>而测试集有多种<strong>划分方法</strong>，这里我们给出<strong>以下三种</strong></p><section><h4>1.1 <strong>留出法</strong>(hold-out)：<a href="#11-留出法hold-out"><span>#</span></a></h4><blockquote><p>将数据集<strong>直接划分</strong>为训练集和测试集</p></blockquote><p>留出法：对数据集的<strong>划分方法</strong>会影响模型结果；训练集和测试集的<strong>数据分布</strong>必须保持一致；测试集过大会使模型拟合效果变差，太小会使得测试估计偏小；有一些数据可能从未被训练过</p><p><strong>总结</strong>：</p><ol>
<li>保证数据分布一致性（比如分层取样）</li>
<li>多次重复划分 (例如: 100次随机划分)</li>
<li>测试集不能太大或太小（例如：1/5~1/3，其实二八分偏多）</li>
<li>可能遗漏数据（随机划分没取到该数据进行训练）</li>
</ol></section><section><h4>1.2 交叉验证法<a href="#12-交叉验证法"><span>#</span></a></h4><p>对于 <strong>留出法</strong>出现 可能<strong>遗漏数据</strong>（随机划分没取到该数据进行训练）的问题</p><p>我们引入<strong>新的划分方法</strong> <u>k-折交叉验证法</u> 可以有效解决该问题</p><p><img alt="" loading="lazy" width="1897" height="877" src="/_astro/1.BP1srWVC_2iAfAT.webp" /></p><p>以该图为例：</p><blockquote><p>首先将数据集D随机划分为10个子集，
进行十次操作，
每一次取其中1个子集为测试集，其余为训练集（实际也可以选取多个子集为测试集）
将结果做平均处理</p></blockquote><p>而对于再划分子集的阶段，将测试集中只留有一个数据的方法叫做<strong>留一法</strong>
留一法每次只留一个样本用于验证，因此单次训练集与原始数据集非常接近。它的数据利用率高，但需要训练 <span><span>mm</span><span><span><span></span><span>m</span></span></span></span> 次，计算成本较高；不同训练集高度重叠，评估结果也可能具有较高方差。</p><p><strong>优缺点</strong>：</p><p>在 <span><span>kk</span><span><span><span></span><span>k</span></span></span></span> 折交叉验证中，每个样本会恰好作为一次验证样本，并在其余 <span><span>k−1k-1</span><span><span><span></span><span>k</span><span></span><span>−</span><span></span></span><span><span></span><span>1</span></span></span></span> 折中参与训练。较大的 <span><span>kk</span><span><span><span></span><span>k</span></span></span></span> 能提高训练数据利用率，但会显著增加计算成本。</p><p>划分时仍要遵守数据的独立结构。例如，同一用户、同一时间序列或同一实体的相关样本不应随意分到训练折和验证折，否则可能产生数据泄漏。</p></section><section><h4>1.3 自助法<a href="#13-自助法"><span>#</span></a></h4><p><img alt="" loading="lazy" width="1397" height="876" src="/_astro/2.gNeFIb5q_1EoYs5.webp" /></p><p>“自助法”(bootstrapping)
基于<strong>放回取样</strong> 亦称“可重复采样”</p><p>从含有 <span><span>mm</span><span><span><span></span><span>m</span></span></span></span> 个样本的原始数据集中进行 <span><span>mm</span><span><span><span></span><span>m</span></span></span></span> 次有放回抽样，可以得到一个大小仍为 <span><span>mm</span><span><span><span></span><span>m</span></span></span></span> 的训练集，但其中会包含重复样本。</p><p>某个原始样本在一次抽样中未被选中的概率为 <span><span>1−1/m1-1/m</span><span><span><span></span><span>1</span><span></span><span>−</span><span></span></span><span><span></span><span>1/</span><span>m</span></span></span></span>，在 <span><span>mm</span><span><span><span></span><span>m</span></span></span></span> 次抽样后仍未被选中的概率为：</p><span><span><span>lim⁡m→∞(1−1m)m=1e≈0.368\lim_{m\to\infty}
\left(1-\frac{1}{m}\right)^m
=\frac{1}{e}
\approx 0.368</span><span><span><span></span><span><span><span><span><span><span></span><span><span><span>m</span><span>→</span><span>∞</span></span></span></span><span><span></span><span><span>lim</span></span></span></span><span>​</span></span><span><span><span></span></span></span></span></span><span></span><span><span><span><span>(</span></span><span>1</span><span></span><span>−</span><span></span><span><span></span><span><span><span><span><span><span></span><span><span>m</span></span></span><span><span></span><span></span></span><span><span></span><span><span>1</span></span></span></span><span>​</span></span><span><span><span></span></span></span></span></span><span></span></span><span><span>)</span></span></span><span><span><span><span><span><span></span><span><span>m</span></span></span></span></span></span></span></span><span></span><span>=</span><span></span></span><span><span></span><span><span></span><span><span><span><span><span><span></span><span><span>e</span></span></span><span><span></span><span></span></span><span><span></span><span><span>1</span></span></span></span><span>​</span></span><span><span><span></span></span></span></span></span><span></span></span><span></span><span>≈</span><span></span></span><span><span></span><span>0.368</span></span></span></span></span><p>因此，约有 <span><span>36.8%36.8\%</span><span><span><span></span><span>36.8%</span></span></span></span> 的原始样本不会进入本次自助训练集，可作为包外样本（out-of-bag samples）进行评估；进入训练集的不同原始样本约占 <span><span>63.2%63.2\%</span><span><span><span></span><span>63.2%</span></span></span></span>。</p><p><strong>优缺点</strong>：</p><p><strong>优点</strong>：
自助法在数据集较小、难以有效划分训练/测试集时很有用，
自助法能从初始数据集中产生多个不同的训练集,这对<strong>集成学习</strong>等方法有很大的好处。</p><p><strong>缺点</strong>：
自助法产生的数据集改变了初始数据集的分布，这会引入估计偏差。但是通过增加数据划分的次数，就可以缓解该影响。</p></section><section><h4>1.4 调参与验证集<a href="#14-调参与验证集"><span>#</span></a></h4><p>在模型评估与选择的过程中，我们既要选择学习算法，还要对算法的参数进行设定</p><p>训练过程中的参数分为<strong>两种</strong>：</p><ul>
<li>算法参数：亦称“超参数”，由人工设定</li>
<li>模型参数：由训练集训练而成</li>
</ul></section><section><h4>1.5 重新训练<a href="#15-重新训练"><span>#</span></a></h4><p>模型选择和调参完成后，可以合并训练集与验证集重新训练最终模型，再使用此前从未参与训练和调参的测试集进行一次独立评估。</p><p>测试集不是用来选择算法或参数的。如果把测试集也用于重新训练，就不能再用同一测试集报告无偏的泛化性能。部署前确实可以使用全部已有数据训练，但之后应准备新的独立数据进行评估。</p></section></section><section><h3>2.性能度量<a href="#2性能度量"><span>#</span></a></h3><section><h4>2.1 错误率和精度<a href="#21-错误率和精度"><span>#</span></a></h4><p>错误率定义：</p><span><span><span>E(f;D)=1m∑i=1mI ⁣(f(xi)≠yi)E(f;D)
=
\frac{1}{m}
\sum_{i=1}^{m}
\mathbb{I}\!\left(f(\boldsymbol{x}_i)\neq y_i\right)</span><span><span><span></span><span>E</span><span>(</span><span>f</span><span>;</span><span></span><span>D</span><span>)</span><span></span><span>=</span><span></span></span><span><span></span><span><span></span><span><span><span><span><span><span></span><span><span>m</span></span></span><span><span></span><span></span></span><span><span></span><span><span>1</span></span></span></span><span>​</span></span><span><span><span></span></span></span></span></span><span></span></span><span></span><span><span><span><span><span><span></span><span><span><span>i</span><span>=</span><span>1</span></span></span></span><span><span></span><span><span>∑</span></span></span><span><span></span><span><span><span>m</span></span></span></span></span><span>​</span></span><span><span><span></span></span></span></span></span><span></span><span>I</span><span></span><span></span><span><span>(</span><span>f</span><span>(</span><span><span><span><span>x</span></span></span><span><span><span><span><span><span></span><span><span>i</span></span></span></span><span>​</span></span><span><span><span></span></span></span></span></span></span><span>)</span><span></span><span><span><span><span><span><span></span><span><span><span></span></span></span><span></span></span></span></span></span><span></span><span>=</span></span><span></span><span><span>y</span><span><span><span><span><span><span></span><span><span>i</span></span></span></span><span>​</span></span><span><span><span></span></span></span></span></span></span><span>)</span></span></span></span></span></span><p>精度定义：</p><span><span><span>acc⁡(f;D)=1m∑i=1mI ⁣(f(xi)=yi)=1−E(f;D)\begin{aligned}
\operatorname{acc}(f;D)
&amp;=
\frac{1}{m}
\sum_{i=1}^{m}
\mathbb{I}\!\left(f(\boldsymbol{x}_i)=y_i\right) \\
&amp;=1-E(f;D)
\end{aligned}</span><span><span><span></span><span><span><span><span><span><span><span><span></span><span><span><span>acc</span></span><span>(</span><span>f</span><span>;</span><span></span><span>D</span><span>)</span></span></span><span><span></span><span></span></span></span><span>​</span></span><span><span><span></span></span></span></span></span><span><span><span><span><span><span></span><span><span></span><span></span><span>=</span><span></span><span><span></span><span><span><span><span><span><span></span><span><span>m</span></span></span><span><span></span><span></span></span><span><span></span><span><span>1</span></span></span></span><span>​</span></span><span><span><span></span></span></span></span></span><span></span></span><span></span><span><span><span><span><span><span></span><span><span><span>i</span><span>=</span><span>1</span></span></span></span><span><span></span><span><span>∑</span></span></span><span><span></span><span><span><span>m</span></span></span></span></span><span>​</span></span><span><span><span></span></span></span></span></span><span></span><span>I</span><span></span><span></span><span><span>(</span><span>f</span><span>(</span><span><span><span><span>x</span></span></span><span><span><span><span><span><span></span><span><span>i</span></span></span></span><span>​</span></span><span><span><span></span></span></span></span></span></span><span>)</span><span></span><span>=</span><span></span><span><span>y</span><span><span><span><span><span><span></span><span><span>i</span></span></span></span><span>​</span></span><span><span><span></span></span></span></span></span></span><span>)</span></span></span></span><span><span></span><span><span></span><span></span><span>=</span><span></span><span>1</span><span></span><span>−</span><span></span><span>E</span><span>(</span><span>f</span><span>;</span><span></span><span>D</span><span>)</span></span></span></span><span>​</span></span><span><span><span></span></span></span></span></span></span></span></span></span></span></span><p>然而对于不同的任务需求，仅仅用错误率和精度是不够的，故引出<code>查准率、查全率、F1</code>的概念</p></section><section><h4>2.2 查准率、查全率、F1<a href="#22-查准率查全率f1"><span>#</span></a></h4><section><h5>二分类任务：<a href="#二分类任务"><span>#</span></a></h5><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>现有一瓜田含10000个瓜，其中有100个好瓜，利用学习器挑瓜</span></div></div></code></pre><div><div></div><div></div></div></figure></div><ul>
<li>查准率或精确率（precision）：挑出的瓜中好瓜所占的比例。</li>
<li>查全率或召回率（recall）：所有好瓜中被成功挑出的比例。</li>
<li>F1-score：查准率与查全率的调和平均。</li>
</ul><p>注意，precision 不是 accuracy。accuracy 表示全部样本中预测正确的比例。</p><p>在选瓜任务中我们的目标是：</p><ol>
<li>在挑出来的瓜中尽可能都是好瓜</li>
<li>瓜田中的好瓜尽可能多地被挑出来
很直观的可以感受到查全率越大(如挑出越多的瓜，好瓜被挑出的比例也就越大），查准率就会越低</li>
</ol><p><strong>混淆矩阵</strong>：</p><p><img alt="" loading="lazy" width="473" height="232" src="/_astro/3.BcD6V0fL_QVRL4.webp" /></p><p>其中查准率P和查全率R的定义为：</p><span><span><span>P=TPTP+FPP=\frac{TP}{TP+FP}</span><span><span><span></span><span>P</span><span></span><span>=</span><span></span></span><span><span></span><span><span></span><span><span><span><span><span><span></span><span><span>T</span><span>P</span><span></span><span>+</span><span></span><span>F</span><span>P</span></span></span><span><span></span><span></span></span><span><span></span><span><span>T</span><span>P</span></span></span></span><span>​</span></span><span><span><span></span></span></span></span></span><span></span></span></span></span></span></span><span><span><span>R=TPTP+FNR=\frac{TP}{TP+FN}</span><span><span><span></span><span>R</span><span></span><span>=</span><span></span></span><span><span></span><span><span></span><span><span><span><span><span><span></span><span><span>T</span><span>P</span><span></span><span>+</span><span></span><span>F</span><span>N</span></span></span><span><span></span><span></span></span><span><span></span><span><span>T</span><span>P</span></span></span></span><span>​</span></span><span><span><span></span></span></span></span></span><span></span></span></span></span></span></span><p>F1即综合考虑两者效果:</p><span><span><span>F1=2PRP+RF_1
=
\frac{2PR}{P+R}</span><span><span><span></span><span><span>F</span><span><span><span><span><span><span></span><span><span>1</span></span></span></span><span>​</span></span><span><span><span></span></span></span></span></span></span><span></span><span>=</span><span></span></span><span><span></span><span><span></span><span><span><span><span><span><span></span><span><span>P</span><span></span><span>+</span><span></span><span>R</span></span></span><span><span></span><span></span></span><span><span></span><span><span>2</span><span>P</span><span>R</span></span></span></span><span>​</span></span><span><span><span></span></span></span></span></span><span></span></span></span></span></span></span><p>等价地：</p><span><span><span>1F1=12(1P+1R)\frac{1}{F_1}
=
\frac{1}{2}
\left(\frac{1}{P}+\frac{1}{R}\right)</span><span><span><span></span><span><span></span><span><span><span><span><span><span></span><span><span><span>F</span><span><span><span><span><span><span></span><span><span>1</span></span></span></span><span>​</span></span><span><span><span></span></span></span></span></span></span></span></span><span><span></span><span></span></span><span><span></span><span><span>1</span></span></span></span><span>​</span></span><span><span><span></span></span></span></span></span><span></span></span><span></span><span>=</span><span></span></span><span><span></span><span><span></span><span><span><span><span><span><span></span><span><span>2</span></span></span><span><span></span><span></span></span><span><span></span><span><span>1</span></span></span></span><span>​</span></span><span><span><span></span></span></span></span></span><span></span></span><span></span><span><span><span>(</span></span><span><span></span><span><span><span><span><span><span></span><span><span>P</span></span></span><span><span></span><span></span></span><span><span></span><span><span>1</span></span></span></span><span>​</span></span><span><span><span></span></span></span></span></span><span></span></span><span></span><span>+</span><span></span><span><span></span><span><span><span><span><span><span></span><span><span>R</span></span></span><span><span></span><span></span></span><span><span></span><span><span>1</span></span></span></span><span>​</span></span><span><span><span></span></span></span></span></span><span></span></span><span><span>)</span></span></span></span></span></span></span></section><section><h5>多分类任务：<a href="#多分类任务"><span>#</span></a></h5></section></section><section><h4>2.3 ROC与AUC<a href="#23-roc与auc"><span>#</span></a></h4></section></section><section><h3>3.比较检验<a href="#3比较检验"><span>#</span></a></h3></section></section>
<section><h2>从任务到结论的基本流程<a href="#从任务到结论的基本流程"><span>#</span></a></h2><p>一个较规范的机器学习实验可以按以下顺序进行：</p><ol>
<li>定义输入、输出和业务目标。</li>
<li>明确数据来源及其分布假设。</li>
<li>划分训练集、验证集和测试集。</li>
<li>只使用验证集选择模型与超参数。</li>
<li>根据错误代价选择合适的性能指标。</li>
<li>最后一次使用测试集报告泛化性能。</li>
<li>比较多个模型时说明随机种子、方差和统计检验方法。</li>
</ol></section>
<section><h2>总结<a href="#总结"><span>#</span></a></h2><p>第一章提供机器学习的概念框架，第二章提供验证结论的实验框架。只有把“学习什么”和“如何评价”连接起来，模型结果才具有可解释性和可复现性。</p></section>]]></content>
    </entry>
    <entry>
      <id>https://www.celyn-blog.xyz/posts/java-classes-and-methods/</id>
      <title type="text">定义类和方法</title>
      <published>2024-05-14T00:00:00.000Z</published>
      <updated>2024-05-14T00:00:00.000Z</updated>
      <author><name>Celyn</name></author>
      <link rel="alternate" href="https://www.celyn-blog.xyz/posts/java-classes-and-methods/"/>
      <summary type="text">Java面向对象编程基础，讲解类的定义、对象的实例化以及成员变量与方法的概念。</summary>
      <content type="html"><![CDATA[<p>对象是实例化的类，所以对象也叫做实例。</p>
<p>一个实例(对象)由 <strong>属性</strong> (变量or数据)和 <strong>方法</strong> (行为)构成</p>
<p>所以属性和方法叫做对象的 <strong>成员</strong></p>
<p>对象的  属性  叫做 成员变量or实例变量(这俩也有点区别)</p>
<p>对象的  方法  叫做 成员方法</p>
<hr />
<p>在面向对象编程中，成员变量和实例变量通常指的是相同的概念，但它们的使用和含义略有不同，具体取决于上下文：</p>
<ol>
<li>
<p><strong>成员变量</strong>：</p>
<ul>
<li>成员变量是类的一部分，它们定义了类的状态或属性。</li>
<li>每个成员变量都是类的蓝图，用于创建对象时存储数据。</li>
<li>成员变量可以是静态的或非静态的（实例变量）。</li>
</ul>
</li>
<li>
<p><strong>实例变量</strong>（非静态变量）：</p>
<ul>
<li>实例变量是成员变量的一种，它们属于类的特定实例（对象）。</li>
<li>每个实例变量的副本都存储在创建的对象中，这意味着每个对象都有自己的实例变量副本。</li>
<li>实例变量的值对于每个对象都是独立的，一个对象的实例变量改变不会影响另一个对象的相应变量。</li>
</ul>
</li>
<li>
<p><strong>静态变量</strong>（类变量）：</p>
<ul>
<li>静态变量也是成员变量的一种，但它们不属于任何特定的实例。</li>
<li>静态变量只有单一的副本，所有类的实例共享这个副本。</li>
<li>静态变量通常用于存储类级别的数据，如配置信息或计数器。</li>
</ul>
</li>
</ol>
<p>以下是一些关键点的对比：</p>
<ul>
<li><strong>作用域</strong>：实例变量属于对象，每个对象都有其独立的副本；静态变量属于类，所有实例共享同一个副本。</li>
<li><strong>生命周期</strong>：实例变量随对象的创建而存在，随对象的销毁而消失；静态变量随类的加载而存在，随程序结束或类被卸载而消失。</li>
<li><strong>访问</strong>：实例变量可以通过对象的引用访问；静态变量可以通过类名直接访问，也可以通过对象引用访问。</li>
</ul>
<p>下面是一个简单的Java类示例，展示了实例变量和静态变量的使用：</p>
<div><div><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>public</span><span> </span><span>class</span><span><span> </span><span>Car</span><span> </span></span><span>{</span></div></div><div><div><div>2</div></div><div><span>    </span><span>// 实例变量，每个Car对象都有自己的颜色和速度</span></div></div><div><div><div>3</div></div><div><span>    </span><span>String</span><span> color</span><span>;</span></div></div><div><div><div>4</div></div><div><span>    </span><span>int</span><span> speed</span><span>;</span></div></div><div><div><div>5</div></div><div>
</div></div><div><div><div>6</div></div><div><span>    </span><span>// 静态变量，所有Car对象共享同一个制造数量</span></div></div><div><div><div>7</div></div><div><span>    </span><span>static</span><span> </span><span>int</span><span> manufacturingCount </span><span><span>=</span><span> </span></span><span>0</span><span>;</span></div></div><div><div><div>8</div></div><div>
</div></div><div><div><div>9</div></div><div><span>    </span><span>public</span><span> </span><span>Car</span><span>(</span><span>String</span><span><span> </span><span>color</span><span>)</span><span> </span><span>{</span></span></div></div><div><div><div>10</div></div><div><span>        </span><span>this</span><span>.</span><span>color</span><span><span> </span><span>=</span><span> color;</span></span></div></div><div><div><div>11</div></div><div><span>        </span><span>// 每创建一个Car对象，制造数量增加</span></div></div><div><div><div>12</div></div><div><span><span>        </span></span><span>manufacturingCount++;</span></div></div><div><div><div>13</div></div><div><span><span>    </span></span><span>}</span></div></div><div><div><div>14</div></div><div>
</div></div><div><div><div>15</div></div><div><span>    </span><span>public</span><span> </span><span>void</span><span> </span><span>drive</span><span><span>()</span><span> </span><span>{</span></span></div></div><div><div><div>16</div></div><div><span>        </span><span>// 实例方法，影响特定对象的速度</span></div></div><div><div><div>17</div></div><div><span><span>        </span></span><span>speed </span><span>+=</span><span> </span><span>10</span><span>;</span></div></div><div><div><div>18</div></div><div><span><span>    </span></span><span>}</span></div></div><div><div><div>19</div></div><div>
</div></div><div><div><div>20</div></div><div><span>    </span><span>public</span><span> </span><span>static</span><span> </span><span>int</span><span> </span><span>getManufacturingCount</span><span><span>()</span><span> </span><span>{</span></span></div></div><div><div><div>21</div></div><div><span>        </span><span>// 静态方法，返回所有Car对象共享的制造数量</span></div></div><div><div><div>22</div></div><div><span>        </span><span>return</span><span> manufacturingCount;</span></div></div><div><div><div>23</div></div><div><span><span>    </span></span><span>}</span></div></div><div><div><div>24</div></div><div><span>}</span></div></div></code></pre><div><div></div><div></div></div></figure><div></div></div><span>展开</span><span>收起</span></div></div>
<p>在这个例子中，<code>color</code> 和 <code>speed</code> 是实例变量，因为它们属于每个 <code>Car</code> 对象。而 <code>manufacturingCount</code> 是一个静态变量，因为它属于 <code>Car</code> 类本身，并且所有 <code>Car</code> 实例共享这个变量。<code>drive</code> 方法是一个实例方法，因为它操作特定 <code>Car</code> 对象的速度。<code>getManufacturingCount</code> 是一个静态方法，因为它返回的是类级别的信息。</p>
<hr />
<p>在Java中，类（Class）是用来创建对象的模板或蓝图。类本身不直接持有数据，但类定义中可以包含成员变量（也称为字段或属性），这些成员变量是用来存储数据的。当你根据类创建一个对象（实例）时，每个对象都会有自己的成员变量副本，这些副本中存储的就是数据。</p>
<p>例如，假设有一个名为 <code>Person</code> 的类：</p>
<div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>public</span><span> </span><span>class</span><span><span> </span><span>Person</span><span> </span></span><span>{</span></div></div><div><div><div>2</div></div><div><span>    </span><span>String</span><span> name</span><span>;</span></div></div><div><div><div>3</div></div><div><span>    </span><span>int</span><span> age</span><span>;</span></div></div><div><div><div>4</div></div><div>
</div></div><div><div><div>5</div></div><div><span>    </span><span>public</span><span> </span><span>Person</span><span>(</span><span>String</span><span><span> </span><span>name</span><span>, </span></span><span>int</span><span><span> </span><span>age</span><span>)</span><span> </span><span>{</span></span></div></div><div><div><div>6</div></div><div><span>        </span><span>this</span><span>.</span><span>name</span><span><span> </span><span>=</span><span> name;</span></span></div></div><div><div><div>7</div></div><div><span>        </span><span>this</span><span>.</span><span>age</span><span><span> </span><span>=</span><span> age;</span></span></div></div><div><div><div>8</div></div><div><span><span>    </span></span><span>}</span></div></div><div><div><div>9</div></div><div>
</div></div><div><div><div>10</div></div><div><span>    </span><span>// 其他方法...</span></div></div><div><div><div>11</div></div><div><span>}</span></div></div></code></pre><div><div></div><div></div></div></figure></div>
<p>在这个 <code>Person</code> 类中，<code>name</code> 和 <code>age</code> 就是成员变量，它们可以在类的实例中存储数据。当你创建 <code>Person</code> 类的对象时：</p>
<div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>Person</span><span> person1 </span><span><span>=</span><span> </span></span><span>new</span><span> </span><span>Person</span><span>(</span><span>"Alice"</span><span><span>,</span><span> </span></span><span>30</span><span><span>)</span><span>;</span></span></div></div><div><div><div>2</div></div><div><span>Person</span><span> person2 </span><span><span>=</span><span> </span></span><span>new</span><span> </span><span>Person</span><span>(</span><span>"Bob"</span><span><span>,</span><span> </span></span><span>25</span><span><span>)</span><span>;</span></span></div></div></code></pre><div><div></div><div></div></div></figure></div>
<p><code>person1</code> 和 <code>person2</code> 都是 <code>Person</code> 类的实例，它们各自拥有自己的 <code>name</code> 和 <code>age</code> 数据。<code>person1</code> 的 <code>name</code> 是 “Alice”，<code>age</code> 是 30；而 <code>person2</code> 的 <code>name</code> 是 “Bob”，<code>age</code> 是 25。</p>
<p>类本身只是一个定义，它不包含数据，但是它定义了如何创建可以包含数据的对象。</p>
<hr />
<p>在面向对象编程（OOP）中，成员是类的一部分，它代表了类的特性（属性）或行为（方法）。成员是构成类的基本元素，它们定义了类的功能和数据结构。以下是成员的两种主要类型：</p>
<ol>
<li>
<p><strong>成员变量（属性）</strong>：</p>
<ul>
<li>成员变量是类的一部分，用于存储数据。它们是类的属性，通常用于描述对象的状态。</li>
<li>成员变量可以是私有的（private），这意味着它们只能被类内部的方法访问，或者可以是公有的（public），这意味着它们可以被任何其他类访问。</li>
<li>成员变量可以有默认的访问修饰符（没有显式指定），这通常是包级私有的（即同一个包内的其他类可以访问）。</li>
</ul>
</li>
<li>
<p><strong>成员方法（行为）</strong>：</p>
<ul>
<li>成员方法是类的一部分，定义了对象的行为。它们是类的操作，用于描述对象可以执行的动作。</li>
<li>成员方法可以有不同的访问修饰符，如public、private或protected，这决定了它们可以被谁调用。</li>
<li>方法还可以是静态的（static），这意味着它们属于类而不是类的实例，并且可以在不创建类实例的情况下被调用。</li>
</ul>
</li>
</ol>
<p>除了成员变量和成员方法，类的成员还可以包括：</p>
<ul>
<li><strong>构造方法</strong>：特殊的方法，用于创建类的对象。它们的名字必须与类名相同，并且没有返回类型。</li>
<li><strong>静态初始化块</strong>：用于初始化静态变量的代码块，它在类加载时执行一次。</li>
<li><strong>实例初始化块</strong>：用于初始化非静态变量的代码块，它在每次创建类的新实例时执行。</li>
<li><strong>内部类</strong>：定义在另一个类内部的类，它可以访问外部类的成员，包括私有成员。</li>
</ul>
<p>成员变量和方法的可见性和行为可以通过使用不同的访问修饰符和非访问修饰符来控制。例如：</p>
<div><div><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>public</span><span> </span><span>class</span><span><span> </span><span>ExampleClass</span><span> </span></span><span>{</span></div></div><div><div><div>2</div></div><div><span>    </span><span>// 成员变量</span></div></div><div><div><div>3</div></div><div><span>    </span><span>private</span><span> </span><span>int</span><span> privateVar</span><span>;</span></div></div><div><div><div>4</div></div><div><span>    </span><span>protected</span><span> </span><span>int</span><span> protectedVar</span><span>;</span></div></div><div><div><div>5</div></div><div><span>    </span><span>int</span><span> packageVar</span><span><span>;</span><span> </span></span><span>// 默认访问修饰符，也称为包级私有</span></div></div><div><div><div>6</div></div><div><span>    </span><span>public</span><span> </span><span>int</span><span> publicVar</span><span>;</span></div></div><div><div><div>7</div></div><div>
</div></div><div><div><div>8</div></div><div><span>    </span><span>// 构造方法</span></div></div><div><div><div>9</div></div><div><span>    </span><span>public</span><span> </span><span>ExampleClass</span><span><span>()</span><span> </span><span>{</span></span></div></div><div><div><div>10</div></div><div><span>        </span><span>// 构造逻辑</span></div></div><div><div><div>11</div></div><div><span><span>    </span></span><span>}</span></div></div><div><div><div>12</div></div><div>
</div></div><div><div><div>13</div></div><div><span>    </span><span>// 成员方法</span></div></div><div><div><div>14</div></div><div><span>    </span><span>public</span><span> </span><span>void</span><span> </span><span>publicMethod</span><span><span>()</span><span> </span><span>{</span></span></div></div><div><div><div>15</div></div><div><span>        </span><span>// 方法逻辑</span></div></div><div><div><div>16</div></div><div><span><span>    </span></span><span>}</span></div></div><div><div><div>17</div></div><div>
</div></div><div><div><div>18</div></div><div><span>    </span><span>private</span><span> </span><span>void</span><span> </span><span>privateMethod</span><span><span>()</span><span> </span><span>{</span></span></div></div><div><div><div>19</div></div><div><span>        </span><span>// 私有方法逻辑</span></div></div><div><div><div>20</div></div><div><span><span>    </span></span><span>}</span></div></div><div><div><div>21</div></div><div>
</div></div><div><div><div>22</div></div><div><span>    </span><span>// 静态方法</span></div></div><div><div><div>23</div></div><div><span>    </span><span>public</span><span> </span><span>static</span><span> </span><span>void</span><span> </span><span>staticMethod</span><span><span>()</span><span> </span><span>{</span></span></div></div><div><div><div>24</div></div><div><span>        </span><span>// 静态方法逻辑</span></div></div><div><div><div>25</div></div><div><span><span>    </span></span><span>}</span></div></div><div><div><div>26</div></div><div>
</div></div><div><div><div>27</div></div><div><span>    </span><span>// 实例初始化块</span></div></div><div><div><div>28</div></div><div><span><span>    </span></span><span>{</span></div></div><div><div><div>29</div></div><div><span>        </span><span>// 实例初始化逻辑</span></div></div><div><div><div>30</div></div><div><span><span>    </span></span><span>}</span></div></div><div><div><div>31</div></div><div>
</div></div><div><div><div>32</div></div><div><span>    </span><span>// 静态初始化块</span></div></div><div><div><div>33</div></div><div><span>    </span><span>static</span><span> {</span></div></div><div><div><div>34</div></div><div><span>        </span><span>// 静态初始化逻辑</span></div></div><div><div><div>35</div></div><div><span><span>    </span></span><span>}</span></div></div><div><div><div>36</div></div><div><span>}</span></div></div></code></pre><div><div></div><div></div></div></figure><div></div></div><span>展开</span><span>收起</span></div></div>
<p>在这个例子中，<code>ExampleClass</code> 包含了不同类型的成员，包括私有和公有的成员变量，一个构造方法，公有和私有的成员方法，以及静态方法。静态初始化块和实例初始化块分别用于初始化静态成员和非静态成员。</p>
<hr />
<p>在Java语言中，<code>this</code> 是一个特殊的关键字，它指向当前对象的引用。每个对象都有一个隐式的 <code>this</code> 引用，指向它自己。以下是 <code>this</code> 关键字的一些常见用法：</p>
<ol>
<li>
<p><strong>区分成员变量和局部变量</strong>：当局部变量名与成员变量名相同时，可以使用 <code>this</code> 关键字来区分它们。</p>
<div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>public</span><span> </span><span>class</span><span><span> </span><span>MyClass</span><span> </span></span><span>{</span></div></div><div><div><div>2</div></div><div><span>    </span><span>private</span><span> </span><span>int</span><span> number</span><span>;</span></div></div><div><div><div>3</div></div><div><span>    </span><span>public</span><span> </span><span>MyClass</span><span>(</span><span>int</span><span><span> </span><span>number</span><span>)</span><span> </span><span>{</span></span></div></div><div><div><div>4</div></div><div><span>        </span><span>this</span><span>.</span><span>number</span><span><span> </span><span>=</span><span> number; </span></span><span>// 使用 this 来引用成员变量</span></div></div><div><div><div>5</div></div><div><span><span>    </span></span><span>}</span></div></div><div><div><div>6</div></div><div><span>}</span></div></div></code></pre><div><div></div><div></div></div></figure></div>
</li>
<li>
<p><strong>在构造函数中调用另一个构造函数</strong>：可以使用 <code>this()</code> 来调用同一个类中的另一个构造函数。</p>
<div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>public</span><span> </span><span>class</span><span><span> </span><span>MyClass</span><span> </span></span><span>{</span></div></div><div><div><div>2</div></div><div><span>    </span><span>private</span><span> </span><span>int</span><span> number</span><span>;</span></div></div><div><div><div>3</div></div><div><span>    </span><span>public</span><span> </span><span>MyClass</span><span><span>()</span><span> </span><span>{</span></span></div></div><div><div><div>4</div></div><div><span>        </span><span>this</span><span>(</span><span>0</span><span>); </span><span>// 默认构造函数调用带参数的构造函数</span></div></div><div><div><div>5</div></div><div><span><span>    </span></span><span>}</span></div></div><div><div><div>6</div></div><div><span>    </span><span>public</span><span> </span><span>MyClass</span><span>(</span><span>int</span><span><span> </span><span>number</span><span>)</span><span> </span><span>{</span></span></div></div><div><div><div>7</div></div><div><span>        </span><span>this</span><span>.</span><span>number</span><span><span> </span><span>=</span><span> number;</span></span></div></div><div><div><div>8</div></div><div><span><span>    </span></span><span>}</span></div></div><div><div><div>9</div></div><div><span>}</span></div></div></code></pre><div><div></div><div></div></div></figure></div>
</li>
<li>
<p><strong>在方法中返回当前对象的引用</strong>：有时，方法需要返回调用该方法的对象的引用，这时可以使用 <code>this</code>。</p>
<div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>public</span><span> </span><span>class</span><span><span> </span><span>MyClass</span><span> </span></span><span>{</span></div></div><div><div><div>2</div></div><div><span>    </span><span>public</span><span> </span><span>MyClass</span><span> </span><span>getNext</span><span><span>()</span><span> </span><span>{</span></span></div></div><div><div><div>3</div></div><div><span>        </span><span>// 一些逻辑...</span></div></div><div><div><div>4</div></div><div><span>        </span><span>return</span><span> </span><span>this</span><span>; </span><span>// 返回当前对象的引用</span></div></div><div><div><div>5</div></div><div><span><span>    </span></span><span>}</span></div></div><div><div><div>6</div></div><div><span>}</span></div></div></code></pre><div><div></div><div></div></div></figure></div>
</li>
<li>
<p><strong>在参数中传递当前对象</strong>：当需要将当前对象作为参数传递给另一个方法时，可以使用 <code>this</code>。</p>
<div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>public</span><span> </span><span>class</span><span><span> </span><span>MyClass</span><span> </span></span><span>{</span></div></div><div><div><div>2</div></div><div><span>    </span><span>public</span><span> </span><span>void</span><span> </span><span>doSomething</span><span>(</span><span>MyClass</span><span><span> </span><span>other</span><span>)</span><span> </span><span>{</span></span></div></div><div><div><div>3</div></div><div><span>        </span><span>other</span><span>.</span><span>copy</span><span>(</span><span>this</span><span>); </span><span>// 将当前对象作为参数传递</span></div></div><div><div><div>4</div></div><div><span><span>    </span></span><span>}</span></div></div><div><div><div>5</div></div><div><span>    </span><span>public</span><span> </span><span>void</span><span> </span><span>copy</span><span>(</span><span>MyClass</span><span><span> </span><span>other</span><span>)</span><span> </span><span>{</span></span></div></div><div><div><div>6</div></div><div><span>        </span><span>// 复制逻辑...</span></div></div><div><div><div>7</div></div><div><span><span>    </span></span><span>}</span></div></div><div><div><div>8</div></div><div><span>}</span></div></div></code></pre><div><div></div><div></div></div></figure></div>
</li>
<li>
<p><strong>在匿名类和内部类中引用外部类的实例</strong>：在匿名类或内部类中，可以使用 <code>this</code> 关键字来引用外部类的实例。</p>
<div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>public</span><span> </span><span>class</span><span><span> </span><span>OuterClass</span><span> </span></span><span>{</span></div></div><div><div><div>2</div></div><div><span>    </span><span>public</span><span> </span><span>void</span><span> </span><span>doSomething</span><span><span>()</span><span> </span><span>{</span></span></div></div><div><div><div>3</div></div><div><span>        </span><span>new</span><span> </span><span>Thread</span><span>(</span><span>new</span><span> </span><span>Runnable</span><span>() {</span></div></div><div><div><div>4</div></div><div><span>            </span><span>public</span><span> </span><span>void</span><span> </span><span>run</span><span><span>()</span><span> </span><span>{</span></span></div></div><div><div><div>5</div></div><div><span>                </span><span>OuterClass</span><span>.</span><span>this</span><span>.</span><span>doWork</span><span>(); </span><span>// 调用外部类的 doWork 方法</span></div></div><div><div><div>6</div></div><div><span><span>            </span></span><span>}</span></div></div><div><div><div>7</div></div><div><span><span>        </span></span><span>}).</span><span>start</span><span>();</span></div></div><div><div><div>8</div></div><div><span><span>    </span></span><span>}</span></div></div><div><div><div>9</div></div><div><span>    </span><span>public</span><span> </span><span>void</span><span> </span><span>doWork</span><span><span>()</span><span> </span><span>{</span></span></div></div><div><div><div>10</div></div><div><span>        </span><span>// 工作逻辑...</span></div></div><div><div><div>11</div></div><div><span><span>    </span></span><span>}</span></div></div><div><div><div>12</div></div><div><span>}</span></div></div></code></pre><div><div></div><div></div></div></figure></div>
</li>
<li>
<p><strong>在重载方法中使用</strong>：当类中有多个同名方法但参数列表不同时，可以使用 <code>this</code> 调用其他重载的方法。</p>
<div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>public</span><span> </span><span>class</span><span><span> </span><span>MyClass</span><span> </span></span><span>{</span></div></div><div><div><div>2</div></div><div><span>    </span><span>public</span><span> </span><span>void</span><span> </span><span>print</span><span>(</span><span>int</span><span><span> </span><span>number</span><span>)</span><span> </span><span>{</span></span></div></div><div><div><div>3</div></div><div><span>        </span><span>System</span><span>.</span><span>out</span><span>.</span><span>println</span><span>(</span><span>"Number: "</span><span><span> </span><span>+</span><span> number);</span></span></div></div><div><div><div>4</div></div><div><span><span>    </span></span><span>}</span></div></div><div><div><div>5</div></div><div><span>    </span><span>public</span><span> </span><span>void</span><span> </span><span>print</span><span>(</span><span>String</span><span><span> </span><span>text</span><span>)</span><span> </span><span>{</span></span></div></div><div><div><div>6</div></div><div><span>        </span><span>System</span><span>.</span><span>out</span><span>.</span><span>println</span><span>(</span><span>"Text: "</span><span><span> </span><span>+</span><span> text);</span></span></div></div><div><div><div>7</div></div><div><span><span>    </span></span><span>}</span></div></div><div><div><div>8</div></div><div><span>    </span><span>public</span><span> </span><span>void</span><span> </span><span>printAll</span><span>(</span><span>int</span><span><span> </span><span>number</span><span>, </span></span><span>String</span><span><span> </span><span>text</span><span>)</span><span> </span><span>{</span></span></div></div><div><div><div>9</div></div><div><span>        </span><span>this</span><span>.</span><span>print</span><span>(number); </span><span>// 调用第一个 print 方法</span></div></div><div><div><div>10</div></div><div><span>        </span><span>this</span><span>.</span><span>print</span><span>(text); </span><span>// 调用第二个 print 方法</span></div></div><div><div><div>11</div></div><div><span><span>    </span></span><span>}</span></div></div><div><div><div>12</div></div><div><span>}</span></div></div></code></pre><div><div></div><div></div></div></figure></div>
</li>
</ol>
<p><code>this</code> 关键字是Java中一个非常有用的工具，它允许程序员在各种情况下引用当前对象。</p>]]></content>
    </entry>
    <entry>
      <id>https://www.celyn-blog.xyz/posts/hexo-setup-issues/</id>
      <title type="text">Hexo搭建实遇问题</title>
      <published>2024-05-13T00:00:00.000Z</published>
      <updated>2024-05-13T00:00:00.000Z</updated>
      <author><name>Celyn</name></author>
      <link rel="alternate" href="https://www.celyn-blog.xyz/posts/hexo-setup-issues/"/>
      <summary type="text">Hexo博客搭建过程中遇到的各种问题及解决方案，包括deploy部署、图片显示、端口占用、Latex渲染等问题。</summary>
      <content type="html"><![CDATA[<section><h1>1.spawn_failed问题<a href="#1spawn_failed问题"><span>#</span></a></h1><section><h2>原因分析：<a href="#原因分析"><span>#</span></a></h2><p>其实出现这个问题，很大可能是因为https和http的proxy的对应的分别是https和http开proxy server，</p><p>而https的proxy server可能无法正常工作。</p></section><section><h2>解决办法：<a href="#解决办法"><span>#</span></a></h2><p>修改_config.yml文件的deploy部分，将https 修改为http url 或者 设置为git url, 配置为https oauth2 加token</p><ul>
<li>设置为git url(推荐) 亲测有效</li>
</ul><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span><span>    </span></span><span>deploy:</span></div></div><div><div><div>2</div></div><div><span><span>        </span></span><span>type: git</span></div></div><div><div><div>3</div></div><div><span><span>        </span></span><span>repo: git@github.com:your_github_id/your_github_id.github.io.git</span></div></div><div><div><div>4</div></div><div><span><span>        </span></span><span>branch: gh-pages</span></div></div></code></pre><div><div></div><div></div></div></figure></div></section></section>
<section><h1>2.头像无法显示问题<a href="#2头像无法显示问题"><span>#</span></a></h1><section><h2>原因分析：<a href="#原因分析-1"><span>#</span></a></h2><p>不明原因，猜测是路径问题</p></section><section><h2>解决方法1：<a href="#解决方法1"><span>#</span></a></h2><ol>
<li>将想要显示的头像图片存入本地文件夹</li>
<li>在根目录下进入git bash使用hexo g和hexo d上传代码到github仓库</li>
<li>在github仓库找到该图片，鼠标右键复制图片链接</li>
<li>修改主题配置文件，如我修改的为config.butterfly.yml文件</li>
</ol><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>#Avatar (頭像)</span></div></div><div><div><div>2</div></div><div><span>avatar:</span></div></div><div><div><div>3</div></div><div><span><span>  </span></span><span>img: 输入你复制的图片链接</span></div></div><div><div><div>4</div></div><div><span><span>  </span></span><span># effect为true 则鼠标放于图片上，会使图片一直旋转</span></div></div><div><div><div>5</div></div><div><span><span>  </span></span><span>effect: false</span></div></div></code></pre><div><div></div><div></div></div></figure></div></section><section><h2>解决方法2：<a href="#解决方法2"><span>#</span></a></h2><p>更改默认头像路径</p><p>默认路径：<code>"C:\Users\陈荣伟\Desktop\Blog\Hexo-blog\blog\themes\butterfly\source\img\friend_404.gif"</code></p><p>把需要的 头像 名称改为<code>friend_404.gif</code>，把<code>friend_404.gif</code> 重命名 成别的即可</p><p>_config.butterfly.yml内的配置文件</p><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span># Replace Broken Images (替换无法显示的图片)</span></div></div><div><div><div>2</div></div><div><span>error_img:</span></div></div><div><div><div>3</div></div><div><span><span>  </span></span><span>flink: /img/friend_404.gif</span></div></div><div><div><div>4</div></div><div><span><span>  </span></span><span>post_page: /img/404.jpg</span></div></div></code></pre><div><div></div><div></div></div></figure></div></section></section>
<section><h1>3.本地预览正常，但部署到GitHub 网站背景图片不加载<a href="#3本地预览正常但部署到github-网站背景图片不加载"><span>#</span></a></h1><section><h2>解决方法：<a href="#解决方法"><span>#</span></a></h2><ol>
<li>分清背景图片是放在本地还是别处？</li>
<li>记得用图片的网络链接，确保图片地址没有错误。</li>
<li>然后<strong>清除浏览器缓存</strong>再试试</li>
</ol></section><section><h2>hexo clean命令<a href="#hexo-clean命令"><span>#</span></a></h2><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>hexo clean</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p>清除缓存文件 <code>db.json</code> 和已生成的静态文件 <code>public</code>。</p><ul>
<li>网站显示异常时可以执行这条命令试试。</li>
</ul><p>接着依次运行代码：</p><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>hexo g</span></div></div></code></pre><div><div></div><div></div></div></figure></div><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>hexo d</span></div></div></code></pre><div><div></div><div></div></div></figure></div></section><section><h2>终极方法<a href="#终极方法"><span>#</span></a></h2><p>删除<code>.deploy_git</code>和<code>public</code>文件</p></section></section>
<section><h1>4.插入图片的显示问题<a href="#4插入图片的显示问题"><span>#</span></a></h1><p>参考文章：</p><p><a href="https://blog.csdn.net/2301_77285173/article/details/130189857" target="_blank">在hexo博客中插入图片的方法_hexo插入图片-CSDN博客</a></p><p>插入图片的方法
在完成了博客搭建、发布文章后，如果我们想在文章中插入图片，该怎么做呢？</p><p>如果图片保存在本地</p><section><h2>方法一：全局资源文件夹<a href="#方法一全局资源文件夹"><span>#</span></a></h2><p>即，将所有文章的资源统一用一个全局资源文件夹管理。</p><p>此方法的优点是比较简便，并且当多篇文章需要引用同一资源时，也比较方便。</p><p>缺点是当文章很多时，各个文章的图片都在同一文件夹，不便管理。</p><p><strong>具体方法</strong>：
在hexo文件夹下的source目录下，新建一个文件夹叫images(名字随意)，将要插入的图片放在该文件夹中。
md文档内，使用<code>&lt;!-- broken image removed: 图片链接地址 --&gt;</code>的格式，圆括号内的链接地址写<code>(/images/name.jpeg)</code>。
这里的 / 指的是根目录，对于hexo，资源文件的根目录就是source(可以在config文件里修改资源的根目录)</p><p>例如，在md文档中写：<code>![图片](/images/20.jpeg "甘雨")</code>
同时将“20.jpeg”这个图片文件放在hexo文件夹<code>/source/images</code>下，则图片可以上传到博客。</p></section><section><h2>方法二：文章资源文件夹<a href="#方法二文章资源文件夹"><span>#</span></a></h2><p>即，对于每篇文章，使用一个文件夹管理资源。</p><p>此方法的优点是，当文章很多时，便于结构化管理。</p><p>缺点是，比方法一麻烦一点。</p><p><strong>具体方法</strong>：</p><p>2.1 修改hexo文件夹中的_config.yml文件，如下：</p><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>post_asset_folder: true</span></div></div><div><div><div>2</div></div><div><span>marked:</span></div></div><div><div><div>3</div></div><div><span><span>  </span></span><span>prependRoot: true</span></div></div><div><div><div>4</div></div><div><span><span>  </span></span><span>postAsset: true</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p>2.2 在终端cd到hexo文件夹，<code>hexo new 文章名   </code>命令创建一篇新文章，此时会在hexo文件夹的source目录下，自动创建一个文件夹和文章名.md文件。</p><p><strong>注</strong>：如果文章名中有空格，务必将整个文章名用双引号引起来。如果文章名中没有空格，可以加双引号，也可以不加。</p><p>例如，执行hexo new “如何发布文章到hexo博客上”，如下：</p><p>会在<code>source/_post</code>文件夹下生成一个”如何发布文章到hexo博客上.md”文件。如下：</p><p>可以看到，同时还生成了一个同名的资源文件夹。</p><p>2.3 我们可以将所有与该文章有关的资源（包括图片）放在这个关联文件夹中
2.4 通过<strong>相对路径</strong>来引用图片资源。</p><p>例如，将“1.jpeg”这个图片资源放在该文件夹中，并在.md文件中像这样引用图片：<code>&lt;!-- broken image removed: 1.jpeg --&gt;</code>，这个方法在资源较多时方便管理。</p><p>另附Typora编辑器中不显示图片的解决方案：
安装下面的插件，可以使Typora等Markdown编辑器预览以及Hexo发布预览时，均能正常显示图片。
<code>npm install hexo-asset-img --save</code>
这样，如果你使用Typora编辑markdown文档，在typora内也可以显示图片了。</p></section></section>
<section><h1>5.4000端口占用问题<a href="#54000端口占用问题"><span>#</span></a></h1><section><h2>查看端口:<a href="#查看端口"><span>#</span></a></h2><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>netstat -ano</span></div></div></code></pre><div><div></div><div></div></div></figure></div><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span><span>    </span></span><span>每一列分别对应：协议、本地地址、外部地址、状态、PID</span></div></div><div><div><div>2</div></div><div>
</div></div><div><div><div>3</div></div><div><span><span>  </span></span><span>参数详解：</span></div></div><div><div><div>4</div></div><div><span><span>    </span></span><span>-a            显示所有连接和侦听端口。</span></div></div><div><div><div>5</div></div><div><span><span>    </span></span><span>-n            以数字形式显示地址和端口号。</span></div></div><div><div><div>6</div></div><div><span><span>    </span></span><span>-o            显示拥有的与每个连接关联的进程 ID。</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p>注：其余参数可使用help命令查看：</p><p><code>netstat -help</code></p><p>​        查询出的端口数量很多，我们可使用findstr命令进行过滤：</p><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>netstat -ano | findstr "被占用的端口"</span></div></div></code></pre><div><div></div><div></div></div></figure></div></section><section><h2><strong>释放被占用的端口</strong>：<a href="#释放被占用的端口"><span>#</span></a></h2><p>​      过滤出需要释放的端口后，在cmd窗口输入<code>task kill命令 </code>可释放被占用的端口：</p><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>taskkill -f -t /pid "占用端口的程序的pid"</span></div></div></code></pre><div><div></div><div></div></div></figure></div></section></section>
<section><h1>6.网页渲染Latex问题<a href="#6网页渲染latex问题"><span>#</span></a></h1><p>修改主题配置文件<code>_config.butterfly.yml</code>：</p><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>mathjax:</span></div></div><div><div><div>2</div></div><div><span><span>  </span></span><span>enable: true</span></div></div><div><div><div>3</div></div><div><span><span>  </span></span><span># true 表示每一頁都加載mathjax.js</span></div></div><div><div><div>4</div></div><div><span><span>  </span></span><span># false 需要時加載，須在使用的Markdown Front-matter 加上 mathjax: true</span></div></div><div><div><div>5</div></div><div><span><span>  </span></span><span>per_page: false</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p>以下操作在你 hexo 博客的目录下 (不是 Butterfly 的目录):</p><ol>
<li>安装插件</li>
</ol><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>npm uninstall hexo-renderer-marked --save</span></div></div><div><div><div>2</div></div><div><span>npm install hexo-renderer-kramed --save</span></div></div></code></pre><div><div></div><div></div></div></figure></div><ol>
<li>配置 hexo 根目录的配置文件<code>_config.yml</code></li>
</ol><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>kramed:</span></div></div><div><div><div>2</div></div><div><span><span>  </span></span><span>gfm: true</span></div></div><div><div><div>3</div></div><div><span><span>  </span></span><span>pedantic: false</span></div></div><div><div><div>4</div></div><div><span><span>  </span></span><span>sanitize: false</span></div></div><div><div><div>5</div></div><div><span><span>  </span></span><span>tables: true</span></div></div><div><div><div>6</div></div><div><span><span>  </span></span><span>breaks: true</span></div></div><div><div><div>7</div></div><div><span><span>  </span></span><span>smartLists: true</span></div></div><div><div><div>8</div></div><div><span><span>  </span></span><span>smartypants: true</span></div></div></code></pre><div><div></div><div></div></div></figure></div></section>
<section><h1>7.引入CSS文件<a href="#7引入css文件"><span>#</span></a></h1><p>打开config.butterfly.yml文件</p><p>CTRL+F 搜索下面的代码，增加<code>&lt;link&gt;</code>标签即可</p><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>inject:</span></div></div><div><div><div>2</div></div><div><span><span>  </span></span><span>head:</span></div></div><div><div><div>3</div></div><div><span><span>    </span></span><span># - &lt;link rel="stylesheet" href="/xxx.css"&gt;</span></div></div><div><div><div>4</div></div><div><span><span>    </span></span><span>- &lt;link rel="stylesheet" href="/css/categories_dark_fit.css"&gt;</span></div></div><div><div><div>5</div></div><div><span><span>    </span></span><span>- &lt;link rel="stylesheet" href="/css/butterfly_article_double_row.css"&gt;</span></div></div><div><div><div>6</div></div><div><span><span>  </span></span><span>bottom:</span></div></div><div><div><div>7</div></div><div><span><span>    </span></span><span># - &lt;script src="xxxx"&gt;&lt;/script&gt;</span></div></div></code></pre><div><div></div><div></div></div></figure></div></section>]]></content>
    </entry>
</feed>
