<?xml version="1.0" encoding="utf-8"?>
<feed xmlns="http://www.w3.org/2005/Atom">
  <title>Yee&#39;s Blog  个人生活网站分享 | 王大白</title>
  
  
  <link href="/atom.xml" rel="self"/>
  
  <link href="https://yeee.wang/"/>
  <updated>2026-02-19T20:00:41.878Z</updated>
  <id>https://yeee.wang/</id>
  
  <author>
    <name>Yee Wang</name>
    
  </author>
  
  <generator uri="http://hexo.io/">Hexo</generator>
  
  <entry>
    <title>10X AI 全栈工程师的进化之路</title>
    <link href="https://yeee.wang/posts/10x-ai-fullstack-engineer.html"/>
    <id>https://yeee.wang/posts/10x-ai-fullstack-engineer.html</id>
    <published>2025-12-22T18:15:00.000Z</published>
    <updated>2026-02-19T20:00:41.878Z</updated>
    
    <content type="html"><![CDATA[<p><a href="javascript:void(0">内网文档 - 全栈手册</a>)</p><p>👆🏻 全栈手册，是我在近期全栈化转型过程汇总梳理的较为结构化、系统性的知识库手册，希望能够对后来人有所帮助。</p><p>自我介绍：</p><ol><li>还是前端的我，目前负责 Lazada B 端前端基建，<a href="javascript:void(0">Merlion UI (UI 框架)</a>) 作者，<a href="javascript:void(0">LAGO (页面发布平台)</a>)、<a href="javascript:void(0">Lazada Material (物料平台)</a>) 等平台主要设计者及维护人，维护 Lazada 商家工作台 Node.js 应用(1000+ QPS)。</li><li>开始转 Java 全栈的我，不到 4 个月被紧急成长完成了 Java 迭代需求 30+，主导大型重点项目 —— 智能审核（40 人日以上）交付 1 个，5 人日以上完整需求 4 个。涉及技术栈包含 ODPS (大数据平台)、OpenSearch (分布式搜索)、Redis (分布式缓存)、TDDL (分库分表)、ScheduleX (分布式调度)、MetaQ (消息中间件)、Jinwei (数据同步)、多租户等多项主流内部技术体系。8 月 Java 代码 46,991 行，后端代码占比 78.78%。</li></ol><blockquote><p>以上不是想要自夸，只是想说，确确实实投入了非常多的时间，在前后专业领域均有所尝试，分享一些观点也算是有依据。叠个甲，看官轻喷 🤕</p></blockquote><p>从参与小的接口开发，到完整评审交付一个全后端全栈需求，再到开始设计、筹备、押镖一个相对大型的重点项目，短时间内完整经历了一个全栈工程师的成长历程。过程中，沉淀了全栈手册以供团队其他同学学习参考，除了一些干货的知识分享，对于 Java 前后端全栈，也有一些自己的感悟与最近实践，接下来我从几个暴论开始与大家分享。</p><h2 id="2-个资深-AI-全栈-gt-3-前端-2-后端-1-UED"><a href="#2-个资深-AI-全栈-gt-3-前端-2-后端-1-UED" class="headerlink" title="2 个资深 AI 全栈 &gt; 3 前端 + 2 后端 + 1 UED"></a>2 个资深 AI 全栈 &gt; 3 前端 + 2 后端 + 1 UED</h2><p>这无疑看起来有点暴论，但各位只要经历过就能知道我在说什么。就像两个人打乒乓球和六个人踢足球的区别 —— 表面上足球队人多势众，但乒乓球的来回节奏要快得多。</p><p><img src="https://img.alicdn.com/imgextra/i1/O1CN01JTE8jS1CsnNEvEs0l_!!6000000000137-2-tps-1408-768.png" alt=""></p><h3 id="沟通成本的几何级递减"><a href="#沟通成本的几何级递减" class="headerlink" title="沟通成本的几何级递减"></a>沟通成本的几何级递减</h3><p>最明显的感受是沟通效率的变化。以前做一个需求，我们需要先和后端同学对接口，再和 UED 确认交互细节，中间还要来回拉群讨论、开会评审。光是理解一个数据结构的设计意图，就要经过好几轮的”为什么这样设计””前端能不能这样处理”的拉锯。</p><p>现在我自己就是那个设计数据结构的人，也是那个要处理这些数据的人。脑子里想的时候，数据库设计、接口逻辑、前端展示已经形成了一个完整的闭环。就像自己跟自己下棋，每一步都知道对方（其实是自己）要怎么应对。</p><p>最典型的案例是接口文档。以前这个东西简直是所有前后端矛盾的源头。后端同学写文档时往往想的是”把接口说清楚就行”，但前端真正关心的是”这个字段什么时候为空””数组长度有没有限制””异常情况下返回什么”。文档里写着”用户信息对象”，但具体包含哪些字段、字段的业务含义是什么，往往要单独拉群确认。</p><p>现在做全栈开发，接口文档这个中间环节基本消失了。Database → PO/Entity → DO → DTO/TO → VO(View Object) 整个过程可以使用 AI 快速完成 <strong>跨语言的定义</strong>。除此之外借助 AI 我们也可以快速理解上游 HSF (内部 RPC 服务) 定义的各类数据对象。</p><p>结果就是，接口基本一次到位，不仅仅是省掉了写文档和开会的时间，更重要的是减少了返工的沟通成本。</p><p><img src="https://md.xiaobe.top/imgs/202512221713420.png!preview.webp" alt=""></p><p><img src="https://md.xiaobe.top/imgs/202512221713257.png!preview.webp" alt=""></p><p><img src="https://img.alicdn.com/imgextra/i3/O1CN013GbT4k1S0mW5VAz03_!!6000000002185-2-tps-1408-768.png" alt=""></p><h3 id="决策链路的根本性变化"><a href="#决策链路的根本性变化" class="headerlink" title="决策链路的根本性变化"></a>决策链路的根本性变化</h3><p>传统的分工模式下，每个角色都有自己的专业考量。后端关心性能和数据一致性，前端在意用户体验和交互流畅度，UED 专注视觉效果和用户认知。这些考量都很重要，但整合起来就成了一个复杂的多目标优化问题。</p><p>全栈的优势在于，这个多目标优化在一个人的大脑里就完成了。我在写 Java 接口的时候，脑子里已经在想前端要怎么调用，用户操作会触发什么样的数据流转。这种”内化”的设计思考，比任何文档和会议都要高效。</p><p>记得在做 AFeedback (用户反馈系统) 的系统改造过程中，如果是纯后端的思维看待一个需要模糊搜索、分词匹配、多语言索引、跨页总结的需求，那肯定会想到 OpenSearch (分布式搜索)、分词索引、任务队列、超时补偿 等等，这样的一套系统前后端沟通配合研发下来少说得 20 人日，还要有各系统环节的严密测试。</p><p>但其实面对整体反馈数据量只有万级数据量，作为全栈视角来思考这个问题时，解决问题的思路会更加开阔，思路会变成，它的数据量并不大，我们要的这种效果能否直接交由端侧来处理。在数据量不超过 20w 的前提下，端侧可以做到很好的索引性能与过滤分页。并且有全量数据后，对于不同筛选项下的实时总结也会更容易做到。</p><p>这看起来是把工作量大部分都压到了前端，但从现阶段系统的复杂度与交付效率来讲显然是最好的。如果是以前可能会存在许多沟通成本，因为是相对非主流方案，研发 SOP 不常见，实现过程中又可能会存在发起人与执行人不同，出现一些非预期的风险，导致最终产品效果不佳。</p><p><a href="javascript:void(0">查看内部文档详情</a>)</p><h2 id="我能撬动多大的-AI-杠杆"><a href="#我能撬动多大的-AI-杠杆" class="headerlink" title="我能撬动多大的 AI 杠杆"></a>我能撬动多大的 AI 杠杆</h2><p>在当前 AI 的发展背景下，AI 是一个随时可问的 70 分专家，驱动力还是来自于人。AI 可以快速提高我们对未知领域的能力下限，所以在这个时间点，非常适合探索于尝试新领域。什么领域上可以有更多机会驱动 AI，什么领域就可以做到更大的变革提升。在 80 分到 100 分的追逐道路上，使用驱动 AI 来完成是非常消耗资源的，需要加入非常多的规则与微调。但如果在 0 分到 60 分的入门路上，我们可以驱动 AI 日行千里，快速有把握的了解与掌握知识。</p><p>显然在全栈方向上，开发者可以接触到非常多非本领域的公域知识，如果说原本的全栈开发者定义是 Node.js 服务端 + 前端，那么现在的全栈开发者定义则可以是手持 AI 的跨语种开发者。编程语言各类中间件真正回归到工具，不需要我们再花太多时间在学习门槛知识方面。</p><p><img src="https://img.alicdn.com/imgextra/i4/O1CN0169womH1PBpDf78ubV_!!6000000001803-2-tps-1408-768.png" alt=""></p><p>此外，在 AI 驱动的全栈化研发模式我认为与之前都会有些许不同，以前的全栈开发流程我们会在任务拆解后选择先处理前端还是先处理后端，完成一端的研发后再着实完成另一端的工作。现在有所不同的是，会更倾向于先做大方向的拆解，并发的从大方向的 0 ~ 10 分的事情，再逐步细化。</p><h4 id="例：让小工汇报"><a href="#例：让小工汇报" class="headerlink" title="例：让小工汇报"></a>例：让小工汇报</h4><p>假设现在我们正在做一个智能审核的项目，该项目由中台上浮，首先我们需要对整个项目中我们关心的部分有大体了解，这时候我们可以使用 Qwen CLI (命令行工具) 对项目针对不同我们关注的问题进行提问了解，甚至要求其编写脚本进行数据统计分析，如：</p><ul><li>该项目的操作数据库的主要定义在哪里？</li><li>涉及到卖家审核单创建的上下游关系的逻辑处理在哪里？</li><li>卖家已经 Pending 的审核单，如果再次提交时，存在一个已有数据的合并逻辑，帮我找出他们</li><li>根据数据源文件夹中已有过去半年审核的 40w 数据，分国家分析耗时分布。分析过程通过编写脚本计算得出。</li></ul><p><img src="https://md.xiaobe.top/imgs/202512221713579.png!preview.webp" alt=""></p><blockquote><p>阅读类，建议采用 Qwen CLI (命令行工具) 的 Qwen Coder 模型，速度快，幻觉少，价格低。</p><p>分析脚本，建议采用 Claude Code，深度思考可以给出不同维度的统计维度，往往可以给出意想不到、角度独特的统计维度。</p><p>报告生成类，建议采用 Cherry Studio + Claude Sonnet + 特定模板 生成，设计风格统一，避免 AI 味。</p></blockquote><h4 id="例：让小工设计"><a href="#例：让小工设计" class="headerlink" title="例：让小工设计"></a>例：让小工设计</h4><blockquote><p>不会设计怎么办？美商差怎么办？</p></blockquote><p>使用 Cherry Studio + HTML 快速完成页面设计 “抽卡”。对于设计类需求，在驱动 AI 时，应该与生图逻辑一样，尽量一次生成多张，然后从中挑选。</p><table><thead><tr><th>原始界面 或 原始需求</th><th>AI 设计</th><th>最终成品</th></tr></thead><tbody><tr><td>智能搜索后台，原型图生成</td><td><img src="https://md.xiaobe.top/imgs/202512221713736.png!preview.webp" alt=""></td><td><img src="https://md.xiaobe.top/imgs/202512221713890.png!preview.webp" alt=""></td></tr><tr><td><img src="https://md.xiaobe.top/imgs/202512221713807.png!preview.webp" alt=""></td><td><img src="https://md.xiaobe.top/imgs/202512221713754.png!preview.webp" alt=""><img src="https://md.xiaobe.top/imgs/202512221713685.png!preview.webp" alt=""></td><td><img src="https://md.xiaobe.top/imgs/202512221713627.png!preview.webp" alt=""></td></tr><tr><td><img src="https://md.xiaobe.top/imgs/202512221713092.png!preview.webp" alt=""></td><td><img src="https://md.xiaobe.top/imgs/202512221713514.png!preview.webp" alt=""><img src="https://md.xiaobe.top/imgs/202512221713352.png!preview.webp" alt=""></td><td><img src="https://md.xiaobe.top/imgs/202512221713322.png!preview.webp" alt=""></td></tr><tr><td>对数据看板进行不同维度下钻分析，并提供不同类型的图标设计</td><td><img src="https://md.xiaobe.top/imgs/202512221713354.png!preview.webp" alt=""></td><td><img src="https://md.xiaobe.top/imgs/202512221713452.png!preview.webp" alt=""></td></tr><tr><td><img src="https://md.xiaobe.top/imgs/202512221713581.png!preview.webp" alt=""></td><td><img src="https://md.xiaobe.top/imgs/202512221713002.png!preview.webp" alt=""><img src="https://md.xiaobe.top/imgs/202512221713896.png!preview.webp" alt=""></td><td><img src="https://md.xiaobe.top/imgs/202512221828569.png!preview.webp" alt=""></td></tr></tbody></table><p>提示词方面，出图可以采用 html 或 svg 进行绘制，为了减少返回内容规定 AI 采用 <code>tailwindcss</code>，参照设计规范可以先定 Ant Design、Shadcn UI 等公域背景知识。</p><h6 id="参考-Prompt："><a href="#参考-Prompt：" class="headerlink" title="参考 Prompt："></a>参考 Prompt：</h6><pre class=" language-markdown"><code class="language-markdown"><span class="token title important"><span class="token punctuation">#</span> 角色 </span> UI/UX设计师专家  <span class="token title important"><span class="token punctuation">##</span> 注意 </span> <span class="token list punctuation">1.</span> 激励模型深入思考角色配置细节，确保任务完成。  <span class="token list punctuation">2.</span> 专家设计应考虑使用者的需求和关注点。  <span class="token list punctuation">3.</span> 使用情感提示的方法来强调角色的意义和情感层面。  <span class="token title important"><span class="token punctuation">##</span> 性格类型指标 </span> INTJ（内向直觉思维判断型）  <span class="token title important"><span class="token punctuation">##</span> 背景 </span> UI/UX设计师专家的角色设计是为了帮助用户在视觉设计和用户体验领域中做出明智的决策。这个角色可以为用户提供专业的指导和建议，帮助他们创造出既美观又实用的界面设计。  <span class="token title important"><span class="token punctuation">##</span> 约束条件 </span> <span class="token list punctuation">-</span> 必须遵循用户中心设计原则  <span class="token list punctuation">-</span> 需要考虑跨平台和多设备的兼容性  <span class="token title important"><span class="token punctuation">##</span> 定义 </span> <span class="token list punctuation">-</span> UI：用户界面，指用户与产品交互的界面设计。  <span class="token list punctuation">-</span> UX：用户体验，指用户在使用产品过程中的整体感受和满意度。  <span class="token title important"><span class="token punctuation">##</span> 目标 </span> <span class="token list punctuation">-</span> 提供创新和实用的UI/UX设计方案  <span class="token list punctuation">-</span> 增强用户满意度和产品易用性  <span class="token list punctuation">-</span> 优化用户与产品之间的交互体验  <span class="token title important"><span class="token punctuation">##</span> Skills </span> 为了在限制条件下实现目标，该专家需要具备以下技能：  <span class="token list punctuation">1.</span> 视觉设计能力  <span class="token list punctuation">2.</span> 用户研究和分析能力  <span class="token list punctuation">3.</span> 交互设计能力  <span class="token list punctuation">4.</span> 技术实现能力  <span class="token title important"><span class="token punctuation">##</span> 音调 </span> <span class="token list punctuation">-</span> 专业且富有洞察力  <span class="token list punctuation">-</span> 鼓励创新和实验性思维  <span class="token list punctuation">-</span> 亲切且易于理解  <span class="token title important"><span class="token punctuation">##</span> 价值观 </span> <span class="token list punctuation">-</span> 用户至上，一切设计以用户需求为中心  <span class="token list punctuation">-</span> 追求简洁而不失功能性的设计  <span class="token list punctuation">-</span> 持续学习和适应新技术、新趋势  <span class="token title important"><span class="token punctuation">##</span> 工作流程 </span> <span class="token list punctuation">-</span> 第一步：理解用户需求和目标  <span class="token list punctuation">-</span> 第二步：进行市场调研和竞品分析  <span class="token list punctuation">-</span> 第三步：确定设计方向和风格  <span class="token list punctuation">-</span> 第四步：创建原型和交互流程  <span class="token list punctuation">-</span> 第五步：进行用户测试和反馈收集  <span class="token list punctuation">-</span> 第六步：根据反馈进行迭代优化  <span class="token list punctuation">-</span> 第七步：最终交付高质量的设计成果 <span class="token title important"><span class="token punctuation">#</span> Initialization </span> 您好，接下来，让我们一步一步地思考，努力且细心地工作，请根据您选择的角色，严格遵循步骤（Workflow）step-by-step, 完成目标（Goals）。这对我来说非常重要，请帮助我，谢谢！让我们开始吧。 <span class="token title important"><span class="token punctuation">#</span> 返回格式</span> 最终设计结果，使用 html 进行返回，样式部分使用 tailwindcss 实现</code></pre><h4 id="例：让小工编码"><a href="#例：让小工编码" class="headerlink" title="例：让小工编码"></a>例：让小工编码</h4><p><img src="https://md.xiaobe.top/imgs/202512221714464.png!preview.webp" alt=""></p><p>AI 编码这个领域更新太快，简直是日新月异，各类 IDE 及 IDE 插件每日层出不穷，这是 3 天一个版本的领域。我就目前使用过的几个内外部热门 IDE 来分享一下在全栈实践中的经验和用途。</p><p>驱动 AI 编码有一个非常大的前提是，需要有识别对错的能力，能够描述清楚需求。这里注意，识别对错的能力可以是各方面的，单元测试、设计模式、编码风格、性能标准。</p><p>这些都需要我们通过 Context 来定义，如何编写与准备 Context 这个我不展开，日新月异的今天这类的技巧会被不断新增的功能覆盖。但是无疑，我们需要通过定义 rule 来规范 AI，一个不定义 rule 的开发者，就像是在高速公路上开车却不看路标的司机，即使有最先进的车辆，也很难到达正确的目的地。（AI 编码不便宜，且行且珍惜）</p><p>以下是我自己使用过的工具，综合主观评价：<strong>Cursor &gt; Qoder (阿里自研 IDE) &gt; Trae &gt; 其他</strong>。</p><p><img src="https://img.alicdn.com/imgextra/i2/O1CN01c2wycs26vBvepweGq_!!6000000007723-2-tps-1408-768.png" alt=""></p><p>使用这些工具就像完成一件雕塑，用到不同的锤子、凿子、刮刀；AI 编码不是 3D 打印那样一次成型，而是 <strong>粗凿 → 主雕 → 精雕</strong> 的过程。</p><ol><li><strong>粗凿</strong><br>使用较大的 token，<strong>最好 clone 多个项目，把 Claude Code、Trae、Qoder 都试一遍</strong>。<br>用 <strong>Gemini 2.5 Pro</strong> 注入更多 context 上下文，择优留用。</li><li><strong>主雕</strong><br>用 <strong>Cursor 或 Qoder</strong>，先规划步骤与任务，开启深度思考，用 <strong>Claude Sonnet</strong> 快速实现。<br>期间会有多次调整与打断，需要及时关注；也可前后端多项目分窗口并行。</li><li><strong>精雕</strong><br>建议用 <strong>Cursor 手动处理</strong>，因为它有更强的 Tab 提示能力。<br>小型任务可交给 <strong>Grok Code Fast</strong> 模型，非常迅速且精准。</li></ol><p><img src="https://img.alicdn.com/imgextra/i2/O1CN012MLA7R1Pr2rioFQUt_!!6000000001893-2-tps-1408-768.png" alt=""></p><h4 id="例：让小工验证"><a href="#例：让小工验证" class="headerlink" title="例：让小工验证"></a>例：让小工验证</h4><p><img src="https://md.xiaobe.top/imgs/202512221713869.png!preview.webp" alt=""></p><p>一个好的闭环结果验证，可以驱动 AI 无限自我循环直到完成任务。我曾经使用 AI 去处理一个测试完备的开源项目 PR，因为是开源项目，项目中已经有 150+ 的测试用例，在驱动 AI 完成任务的过程中要求 TDD 方式处理，中间大概跑了 1 个多小时，AI 会反复通过单元测试自我验证，然后调试修改，再验证，直到最后完全完成任务。</p><p>从这个角度上来讲，我认为 AI 在编写 Java 代码时会更具验证优势，Java 的强类型特性，编译通过基本可以解决大部分 BUG，这些配合 IDE 连通 Language Server 就可以做到相对较好的自我纠偏。而前端这一方面会相对较弱，许多对象定义缺少类型推断，页面样式又涉及到图像 AI 缺乏判断标准，导致在自我纠偏与验证的过程会相对麻烦。</p><p><img src="https://img.alicdn.com/imgextra/i1/O1CN01w6J7Nl1wDY1txeYd7_!!6000000006274-2-tps-1408-768.png" alt=""></p><p>另外一方面，AI 配合一些好的设计模式，再加上可以明确 TDD 的情况下，可以做许多”小而优雅”的封装。</p><p>举个具体的例子，在服务端中我们有许多上下游 HSF (内部 RPC 调用) 调用，为了更优雅地内聚相关逻辑，我们可能会将部分查询封装在各自的 Service 中，但这可能会导致同一个耗时 IO 被重复执行。好的办法是我们可以单独针对 Remote 调用做一个独立的 Service 封装，然后在该方法中处理好请求级缓存。想到这里，更优雅的办法是可以采用装饰器设计模式，对其做一个独立的注解封装，例如：</p><pre class=" language-java"><code class="language-java"><span class="token annotation punctuation">@RequestCache</span><span class="token punctuation">(</span>    key <span class="token operator">=</span> <span class="token string">"'remote-' + #serviceId + '-' + #params.hashCode()"</span><span class="token punctuation">,</span>     condition <span class="token operator">=</span> <span class="token string">"#params != null"</span><span class="token punctuation">,</span>    ignoreException <span class="token operator">=</span> <span class="token boolean">true</span><span class="token punctuation">)</span><span class="token keyword">public</span> RemoteData <span class="token function">callRemoteService</span><span class="token punctuation">(</span>String serviceId<span class="token punctuation">,</span> Map<span class="token operator">&lt;</span>String<span class="token punctuation">,</span> Object<span class="token operator">></span> params<span class="token punctuation">)</span> <span class="token operator">&amp;</span>#<span class="token number">123</span><span class="token punctuation">;</span>    <span class="token keyword">return</span> remoteService<span class="token punctuation">.</span><span class="token function">call</span><span class="token punctuation">(</span>serviceId<span class="token punctuation">,</span> params<span class="token punctuation">)</span><span class="token punctuation">;</span><span class="token operator">&amp;</span>#<span class="token number">125</span><span class="token punctuation">;</span></code></pre><p>这在 HTTP 请求相关的项目中有最佳实践，但在 HSF 调用时我却没找到相关资料，如果纯 if-else 又显得极不优雅。</p><p>于是，给 Claude 布置作业吧：要求完成上述用法的装饰器设计，严格按照 TDD 模式开发，具备容错降级能力…</p><h4 id="例：招-薅-更多小工"><a href="#例：招-薅-更多小工" class="headerlink" title="例：招/薅 更多小工"></a>例：招/薅 更多小工</h4><p><img src="https://md.xiaobe.top/imgs/202512221713136.png!preview.webp" alt=""></p><p><img src="https://md.xiaobe.top/imgs/202512221713980.png!preview.webp" alt=""></p><p>一套前后端全栈干下来，会发现基础的套餐额度根本不够用。其实现在的 AI 编码真用起来不便宜，基础套餐基本在高强度使用下，一周就用完，所以基本需要看到 Max 套餐。这个时候就不得不想想在哪可以买到或者薅到更便宜的 Claude 来用。</p><p><img src="https://md.xiaobe.top/imgs/202512221713071.png!preview.webp" alt=""></p><p><img src="https://md.xiaobe.top/imgs/202512221713956.png!preview.webp" alt=""></p><p>所以针对不同小工的价格和计费方式，我们需要分配不同的任务给到他们以便 ROI 最大化，逐渐感觉自己变成资本家 🤦‍♂️</p><p>在目前的阶段，公司内部其实有非常多渠道可以拿到免费的 AI 资源，参考：<a href="javascript:void(0">内网 AI 白嫖手册</a>)</p><p>简单列举一下：</p><ul><li>内部大模型平台提供的免费额度，你可以在这里薅到各种主流大模型，配合本地客户端或命令行工具可以作为日常杂活无限用的场景。</li><li>Qoder (阿里新发布的 IDE)，限免阶段，是优秀的 AI 编程工具。</li><li>Aone Agent (智能研发 Agent)，Agent 模式后台运行，与 Aone MCP、Code 平台打通，可选择仓库后通过编写任务清单，后台异步运行，完成任务后会通过消息通知或 MR 形式反馈，异步运行，潜力巨大。</li></ul><p>总体来说，公司提供的能薅到的 AI 资源还是不少的，就看咱们能撬动多少了，薅到就是赚到 💰 虽然我是一个人，实际背后有 10 个 AI，10X 工程师本师 🦁</p><h2 id="一人独角兽"><a href="#一人独角兽" class="headerlink" title="一人独角兽"></a>一人独角兽</h2><p>在 AI 投资圈内，最近有一个比较热的词语，一人独角兽公司，并非是指一个人的独立创业者，而是指那些人数极少却带来极大利润的公司。</p><blockquote><p>一人独角兽之路并非单纯比拼专业技能，而在于能否在心理认知、财务策略、市场洞察和持续迭代上全方位做好准备。换言之，真正成功的Solopreneur，不仅仅是一个技术达人或创意达人，更要是一个精通商业、善于自省并能够快速调整状态的“全能”经营者。</p></blockquote><p>在当前日新月异的 AI 发展背景下，不用谈论 AI 现在还做不到什么，还代替不了什么。我的观点是，尽快让自己成长到更高维度的思维上，问问自己能撬动多少 AI，有了 AI 我可以做些什么以前做不到的事情 ，我现在一个月能用掉多少 Token。</p><p>永远给 AI 留出成长空间，比如今天 AI 的幻觉高、美商差，不一定需要大量规则限定，AI 基座的迭代速度比之前的互联网迭代速度还要快的多，曾经我们花费了大量人力、算力投入进 SD 方案，模特穿衣、抠图、换背景，速度慢成本高，新的端侧 AI 方案一出现把这个事直接干趴下。甚至可能端侧直接调用，就可以完成以前 200 人日研发出来的工程能力。</p><p><img src="https://md.xiaobe.top/imgs/202512221713928.jpeg!preview.webp" alt=""></p><p>在这种时刻下，有更清晰与宏观的产品认知、架构设计、商业逻辑，能够思路清晰、边界明确对目标结果有明确认知的人才能驱动更大 AI 能力。人才能力模型会发生变化，生产关系也一定会有变化，公司中能否出现像“一人独角兽”类似概念的“超级单兵”，可能会是接下来相当长一段时间的话题。</p><p><img src="https://md.xiaobe.top/imgs/202512221713711.png!preview.webp" alt=""></p><p><img src="https://md.xiaobe.top/imgs/202512221713790.png!preview.webp" alt=""></p><p>马老师之前讲过，<em>“今天能够定义清楚的东西都不是未来”</em>。</p><p>这句话现在给 AI，<em>“今天能够定义清楚的东西都交给 AI”。</em></p><p>__</p><blockquote><p>Agent 管理会变成一门学问吗？🤦‍♂️</p></blockquote>]]></content>
    
    <summary type="html">
    
      内网文档 - 全栈手册)

👆🏻 全栈手册，是我在近期全栈化转型过程汇总梳理的较为结构化、系统性的知识库手册，希望能够对后来人有所帮助。

自我介绍：

 1. 还是前端的我，目前负责 Lazada B 端前端基建，Merlion UI (UI 框架)) 作者，LAGO (页面发布平台))、Lazada Material (物料平台)) 等平台主要设计者及维护人，维护 Lazada 商家工作台 Node.js 应用(1000+ QPS)。
 2. 开始转 Java 全栈的我，不到 4 个月被紧急成长完成了 Java 迭代需求 30+，主导大型重点项目 —— 智能审核（40 人日以上）交付 
    
    </summary>
    
      <category term="经验心得" scheme="https://yeee.wang/categories/%E7%BB%8F%E9%AA%8C%E5%BF%83%E5%BE%97/"/>
    
      <category term="AI" scheme="https://yeee.wang/categories/%E7%BB%8F%E9%AA%8C%E5%BF%83%E5%BE%97/AI/"/>
    
    
      <category term="全栈" scheme="https://yeee.wang/tags/%E5%85%A8%E6%A0%88/"/>
    
      <category term="AI" scheme="https://yeee.wang/tags/AI/"/>
    
      <category term="效率提升" scheme="https://yeee.wang/tags/%E6%95%88%E7%8E%87%E6%8F%90%E5%8D%87/"/>
    
  </entry>
  
  <entry>
    <title>第 20 章：系统架构与工程实践</title>
    <link href="https://yeee.wang/posts/e127.html"/>
    <id>https://yeee.wang/posts/e127.html</id>
    <published>2025-12-22T10:35:00.000Z</published>
    <updated>2026-02-19T20:00:41.881Z</updated>
    
    <content type="html"><![CDATA[<h1 id="第-20-章：系统架构与工程实践-System-Architecture-amp-Engineering"><a href="#第-20-章：系统架构与工程实践-System-Architecture-amp-Engineering" class="headerlink" title="第 20 章：系统架构与工程实践 (System Architecture &amp; Engineering)"></a>第 20 章：系统架构与工程实践 (System Architecture &amp; Engineering)</h1><blockquote><p>“算法只是冰山一角，工程才是水面下的巨兽。”</p></blockquote><p>恭喜你，你已经掌握了无监督学习的所有核心算法。<br>但在实际工作中，写出算法代码可能只占 10% 的时间。<br>剩下的 90% 时间，你在处理：<strong>数据管道、异常恢复、性能优化、成本控制。</strong></p><p>本章将以一个典型的文本分析系统为例，剖析工业级数据挖掘系统的架构设计。</p><p><img src="https://img.alicdn.com/imgextra/i3/O1CN01NweZGw1Q17ZvVGU7E_!!6000000001915-2-tps-1376-768.png" alt="Lambda 架构"></p><h2 id="1-核心概念：批处理-vs-流处理"><a href="#1-核心概念：批处理-vs-流处理" class="headerlink" title="1. 核心概念：批处理 vs 流处理"></a>1. 核心概念：批处理 vs 流处理</h2><h3 id="1-1-批处理-Batch-Processing"><a href="#1-1-批处理-Batch-Processing" class="headerlink" title="1.1 批处理 (Batch Processing)"></a>1.1 批处理 (Batch Processing)</h3><ul><li><strong>模式</strong>：T+1。每天凌晨把昨天的数据全量跑一遍。</li><li><strong>适用</strong>：Embedding, KMeans, LLM 总结。这些算法很重，没法实时跑。</li><li><strong>常见选择</strong>：文本分析系统通常采用批处理。因为”风险挖掘”通常不需要秒级响应，发现昨天的风险已经很有价值了。</li></ul><h3 id="1-2-流处理-Stream-Processing"><a href="#1-2-流处理-Stream-Processing" class="headerlink" title="1.2 流处理 (Stream Processing)"></a>1.2 流处理 (Stream Processing)</h3><ul><li><strong>模式</strong>：实时。每来一条数据，立马处理。</li><li><strong>适用</strong>：规则匹配 (SQL), 简单统计 (Count)。</li><li><strong>架构</strong>：Flink / Flink SQL。</li></ul><p><strong>最佳实践 (Lambda 架构)</strong>：</p><ul><li><strong>Batch Layer</strong>：每晚跑重型 AI，生成新的规则/中心点。</li><li><strong>Speed Layer</strong>：实时用这些规则/中心点去过滤新数据。</li></ul><h2 id="2-向量计算优化"><a href="#2-向量计算优化" class="headerlink" title="2. 向量计算优化"></a>2. 向量计算优化</h2><p>处理 100 万条向量很快，但处理 10 亿条呢？</p><h3 id="2-1-向量数据库-Vector-DB"><a href="#2-1-向量数据库-Vector-DB" class="headerlink" title="2.1 向量数据库 (Vector DB)"></a>2.1 向量数据库 (Vector DB)</h3><p>不要把向量存 MySQL 或 CSV。<br>使用专门的向量数据库：<strong>Milvus, Pinecone, Weaviate, Elasticsearch (Vector)</strong>。<br>它们内置了 <strong>HNSW</strong> 索引，可以在毫秒级完成亿级数据的近似搜索 (ANN)。</p><h3 id="2-2-缓存策略-Caching"><a href="#2-2-缓存策略-Caching" class="headerlink" title="2.2 缓存策略 (Caching)"></a>2.2 缓存策略 (Caching)</h3><p>在实际代码中，可以实现一个非常”暴力”但有效的缓存机制：</p><pre class=" language-python"><code class="language-python"><span class="token keyword">def</span> <span class="token function">get_embeddings_batch</span><span class="token punctuation">(</span>texts<span class="token punctuation">)</span><span class="token punctuation">:</span>    <span class="token comment" spellcheck="true"># 计算文本 Hash</span>    texts_hash <span class="token operator">=</span> compute_texts_hash<span class="token punctuation">(</span>texts<span class="token punctuation">)</span>    <span class="token comment" spellcheck="true"># 检查本地是否有 .npy 文件匹配这个 Hash</span>    <span class="token comment" spellcheck="true"># 如果有，直接读取；如果没有，调用 API 并缓存</span>    <span class="token comment" spellcheck="true"># ...</span></code></pre><p><strong>为什么要这样？</strong></p><ul><li><strong>省钱</strong>：OpenAI API 很贵。</li><li><strong>省时</strong>：网络 IO 很慢。</li><li><strong>容灾</strong>：如果程序跑到 99% 崩了，下次重启能直接从缓存读，不需要重跑。</li></ul><p><img src="https://img.alicdn.com/imgextra/i4/O1CN01F23mX21pS1EWYYXKx_!!6000000005358-2-tps-1376-768.png" alt="向量计算优化"></p><h2 id="3-MLOps：模型监控"><a href="#3-MLOps：模型监控" class="headerlink" title="3. MLOps：模型监控"></a>3. MLOps：模型监控</h2><p>无监督学习最怕<strong>模型漂移 (Model Drift)</strong>。</p><ul><li><strong>数据漂移</strong>：用户突然开始用一种新的语言投诉。</li><li><strong>概念漂移</strong>：原本属于”物流”的词，现在变成了”诈骗”的词。</li></ul><p><strong>监控指标</strong>：</p><ol><li><strong>聚类稳定性</strong>：今天的 Cluster 1 和昨天的 Cluster 1 重合度多少？</li><li><strong>噪声比例</strong>：如果 DBSCAN 的噪声点比例突然从 5% 飙升到 50%，说明模型失效了，需要重训。</li><li><strong>Embedding 分布</strong>：监控向量空间的中心点是否发生了显著位移。</li></ol><p><img src="https://img.alicdn.com/imgextra/i2/O1CN01hNXFjo1lIce0SaQOg_!!6000000004796-2-tps-1376-768.png" alt="模型漂移监控"></p><h2 id="4-结语：AI-分析师的未来"><a href="#4-结语：AI-分析师的未来" class="headerlink" title="4. 结语：AI 分析师的未来"></a>4. 结语：AI 分析师的未来</h2><p>我们正处于一个时代的转折点。</p><ul><li><strong>过去</strong>：分析师用 Excel 和 SQL 手动挖掘数据。</li><li><strong>现在</strong>：算法工程师用 Python 和 K-Means 辅助挖掘。</li><li><strong>未来</strong>：<strong>AI Agent</strong> 自动巡检数据，自动调用工具（聚类、降维），自动生成报告，并主动向人类预警。</li></ul><p>无监督学习，是通往 <strong>通用人工智能 (AGI)</strong> 的必经之路。因为只有学会了无监督学习，机器才能像人类婴儿一样，通过观察世界来通过常识，而不是永远依赖人类的喂养（标注数据）。</p><p>希望这套教程能成为你探索数据宇宙的罗盘。<br>愿你的数据里，永远藏着黄金。</p><hr><h3 id="📚-附录：核心技术栈清单"><a href="#📚-附录：核心技术栈清单" class="headerlink" title="📚 附录：核心技术栈清单"></a>📚 附录：核心技术栈清单</h3><table><thead><tr><th style="text-align:left">领域</th><th style="text-align:left">核心库/工具</th></tr></thead><tbody><tr><td style="text-align:left"><strong>数据处理</strong></td><td style="text-align:left">Pandas, NumPy, Spark</td></tr><tr><td style="text-align:left"><strong>机器学习</strong></td><td style="text-align:left">Scikit-Learn (KMeans, IsolationForest)</td></tr><tr><td style="text-align:left"><strong>降维可视化</strong></td><td style="text-align:left">UMAP-learn, Plotly</td></tr><tr><td style="text-align:left"><strong>Embedding</strong></td><td style="text-align:left">OpenAI API, HuggingFace Transformers</td></tr><tr><td style="text-align:left"><strong>向量检索</strong></td><td style="text-align:left">Faiss, Milvus</td></tr><tr><td style="text-align:left"><strong>大语言模型</strong></td><td style="text-align:left">LangChain, OpenAI</td></tr></tbody></table><p><em>(全书完)</em></p>]]></content>
    
    <summary type="html">
    
      第 20 章：系统架构与工程实践 (System Architecture &amp; Engineering)
“算法只是冰山一角，工程才是水面下的巨兽。”

恭喜你，你已经掌握了无监督学习的所有核心算法。
但在实际工作中，写出算法代码可能只占 10% 的时间。
剩下的 90% 时间，你在处理：数据管道、异常恢复、性能优化、成本控制。

本章将以一个典型的文本分析系统为例，剖析工业级数据挖掘系统的架构设计。



1. 核心概念：批处理 vs 流处理
1.1 批处理 (Batch Processing)
 * 模式：T+1。每天凌晨把昨天的数据全量跑一遍。
 * 适用：Embedding, KMea
    
    </summary>
    
      <category term="算法" scheme="https://yeee.wang/categories/%E7%AE%97%E6%B3%95/"/>
    
    
      <category term="算法" scheme="https://yeee.wang/tags/%E7%AE%97%E6%B3%95/"/>
    
      <category term="数据分析" scheme="https://yeee.wang/tags/%E6%95%B0%E6%8D%AE%E5%88%86%E6%9E%90/"/>
    
      <category term="机器学习" scheme="https://yeee.wang/tags/%E6%9C%BA%E5%99%A8%E5%AD%A6%E4%B9%A0/"/>
    
  </entry>
  
  <entry>
    <title>第 19 章：从模型到规则：知识蒸馏</title>
    <link href="https://yeee.wang/posts/a60c.html"/>
    <id>https://yeee.wang/posts/a60c.html</id>
    <published>2025-12-22T10:30:00.000Z</published>
    <updated>2026-02-19T20:00:41.881Z</updated>
    
    <content type="html"><![CDATA[<h1 id="第-19-章：从模型到规则：知识蒸馏-Knowledge-Distillation-to-Rules"><a href="#第-19-章：从模型到规则：知识蒸馏-Knowledge-Distillation-to-Rules" class="headerlink" title="第 19 章：从模型到规则：知识蒸馏 (Knowledge Distillation to Rules)"></a>第 19 章：从模型到规则：知识蒸馏 (Knowledge Distillation to Rules)</h1><blockquote><p>“最好的模型，是用完了就可以扔掉的模型。”</p></blockquote><p>在 Python 里跑完聚类和 LLM 之后，我们得到了深刻的洞察。<br>但 Python 脚本难以处理<strong>亿级</strong>的实时数据流。<br>我们需要把 Python/AI 学到的知识，<strong>转移</strong>到更轻量级、更高效的系统（如 SQL 引擎、规则引擎）中去。</p><p>这一过程被称为 <strong>知识蒸馏 (Knowledge Distillation)</strong>，或者更具体地说，<strong>规则提取 (Rule Extraction)</strong>。</p><p><img src="https://img.alicdn.com/imgextra/i3/O1CN01hCjuH11egiAJTnsuC_!!6000000003901-2-tps-1376-768.png" alt="知识蒸馏流程"></p><h2 id="1-核心概念：Model-to-Rule"><a href="#1-核心概念：Model-to-Rule" class="headerlink" title="1. 核心概念：Model-to-Rule"></a>1. 核心概念：Model-to-Rule</h2><h3 id="1-1-为什么需要规则？"><a href="#1-1-为什么需要规则？" class="headerlink" title="1.1 为什么需要规则？"></a>1.1 为什么需要规则？</h3><ol><li><strong>性能</strong>：SQL <code>RLIKE</code> 比 Embedding 快一万倍。</li><li><strong>成本</strong>：不需要调 API，不需要 GPU。</li><li><strong>可解释性</strong>：规则是白盒（White-box），完全透明。</li><li><strong>合规</strong>：某些行业要求必须能解释为什么拒绝了这笔交易。</li></ol><h3 id="1-2-蒸馏流程"><a href="#1-2-蒸馏流程" class="headerlink" title="1.2 蒸馏流程"></a>1.2 蒸馏流程</h3><ol><li><strong>Teacher (AI)</strong>：用 Embedding + KMeans + LLM 发现了一个高风险簇（例如“虚假签收”）。</li><li><strong>Extraction</strong>：分析这个簇里的文本，提取特征词（如 <code>fake</code>, <code>signature</code>, <code>guard</code>）。</li><li><strong>Student (Rule)</strong>：生成一条正则规则：<code>text RLIKE &#39;(fake|fraud) AND signature&#39;</code>。</li><li><strong>Deploy</strong>：把这条 SQL 部署到数仓。</li></ol><h2 id="2-自动化-SQL-生成"><a href="#2-自动化-SQL-生成" class="headerlink" title="2. 自动化 SQL 生成"></a>2. 自动化 SQL 生成</h2><p>在实际项目中，可以实现一个自动化脚本，将聚类结果转换为 SQL 规则。</p><h3 id="2-1-关键词提取"><a href="#2-1-关键词提取" class="headerlink" title="2.1 关键词提取"></a>2.1 关键词提取</h3><p>使用 LLM 从每个簇中提取关键词。</p><pre class=" language-python"><code class="language-python">prompt <span class="token operator">=</span> <span class="token string">"请从以下文本中提取 5 个最具代表性的关键词（Regex 格式），用于匹配同类问题。"</span></code></pre><h3 id="2-2-规则组装"><a href="#2-2-规则组装" class="headerlink" title="2.2 规则组装"></a>2.2 规则组装</h3><p>我们将关键词组装成 <code>CASE WHEN</code> 语句。</p><pre class=" language-sql"><code class="language-sql"><span class="token keyword">SELECT</span>   <span class="token keyword">CASE</span>    <span class="token keyword">WHEN</span> <span class="token keyword">text</span> <span class="token operator">RLIKE</span> <span class="token string">'fake sign|not receive'</span> <span class="token keyword">THEN</span> <span class="token string">'High_Risk_Fake_Sign'</span>    <span class="token keyword">WHEN</span> <span class="token keyword">text</span> <span class="token operator">RLIKE</span> <span class="token string">'rude|shout'</span> <span class="token keyword">THEN</span> <span class="token string">'Medium_Risk_Attitude'</span>    <span class="token keyword">ELSE</span> <span class="token string">'Normal'</span>  <span class="token keyword">END</span> <span class="token keyword">as</span> risk_label<span class="token keyword">FROM</span> logs<span class="token punctuation">;</span></code></pre><h2 id="3-技术对比：AI-vs-规则"><a href="#3-技术对比：AI-vs-规则" class="headerlink" title="3. 技术对比：AI vs 规则"></a>3. 技术对比：AI vs 规则</h2><table><thead><tr><th style="text-align:left">维度</th><th style="text-align:left">AI 模型 (Teacher)</th><th style="text-align:left">规则系统 (Student)</th></tr></thead><tbody><tr><td style="text-align:left"><strong>精度</strong></td><td style="text-align:left">高 (泛化能力强)</td><td style="text-align:left">中 (容易漏抓变体)</td></tr><tr><td style="text-align:left"><strong>召回率</strong></td><td style="text-align:left">高</td><td style="text-align:left">低 (覆盖不全)</td></tr><tr><td style="text-align:left"><strong>维护成本</strong></td><td style="text-align:left">高 (需重新训练)</td><td style="text-align:left">低 (改代码即可)</td></tr><tr><td style="text-align:left"><strong>响应速度</strong></td><td style="text-align:left">慢 (ms 级)</td><td style="text-align:left"><strong>极快</strong> (us 级)</td></tr><tr><td style="text-align:left"><strong>冷启动</strong></td><td style="text-align:left">难</td><td style="text-align:left">易</td></tr></tbody></table><p><strong>最佳实践</strong>：<strong>AI 负责“探索”，规则负责“利用”。</strong></p><ul><li>每天晚上跑一次 AI，发现新模式，生成新规则。</li><li>白天用规则系统实时拦截。</li></ul><p><img src="https://img.alicdn.com/imgextra/i2/O1CN01nEge7m1NrqK80SJ00_!!6000000001624-2-tps-1376-768.png" alt="AI vs 规则系统对比"></p><h2 id="4-决策树近似-Decision-Tree-Approximation"><a href="#4-决策树近似-Decision-Tree-Approximation" class="headerlink" title="4. 决策树近似 (Decision Tree Approximation)"></a>4. 决策树近似 (Decision Tree Approximation)</h2><p>除了关键词提取，还可以用<strong>决策树</strong>来模仿复杂模型。</p><ol><li>用复杂模型给数据打标（生成伪标签）。</li><li>用原始特征（如金额、时间）训练一棵浅层的决策树去拟合伪标签。</li><li>把决策树的路径翻译成 If-Then 规则。</li></ol><h2 id="5-实践要点"><a href="#5-实践要点" class="headerlink" title="5. 实践要点"></a>5. 实践要点</h2><ol><li><strong>准确率校验</strong>：自动生成的 SQL 必须在历史数据上回测。如果误伤率（False Positive）太高，不能上线。</li><li><strong>多语言规则</strong>：对于多语言场景，需要生成多套关键词（或者先翻译再匹配）。</li><li><strong>规则生命周期</strong>：规则是会“腐烂”的。随着业务变化，旧规则会失效。必须建立<strong>规则淘汰机制</strong>。</li></ol><hr><p><strong>下一章预告</strong>：<br>最后，我们将视角拉高，看看如何构建一个<strong>工业级</strong>的无监督学习系统。<br>批处理还是流处理？如何处理断点续传？向量数据库怎么用？<br>这是从算法工程师进阶到架构师的必修课。</p><p>👉 <a href="https://yeee.wang/posts/e127.html">第 20 章：系统架构与工程实践</a></p>]]></content>
    
    <summary type="html">
    
      第 19 章：从模型到规则：知识蒸馏 (Knowledge Distillation to Rules)
“最好的模型，是用完了就可以扔掉的模型。”

在 Python 里跑完聚类和 LLM 之后，我们得到了深刻的洞察。
但 Python 脚本难以处理亿级的实时数据流。
我们需要把 Python/AI 学到的知识，转移到更轻量级、更高效的系统（如 SQL 引擎、规则引擎）中去。

这一过程被称为 知识蒸馏 (Knowledge Distillation)，或者更具体地说，规则提取 (Rule Extraction)。



1. 核心概念：Model-to-Rule
1.1 为什么需要规则？

    
    </summary>
    
      <category term="算法" scheme="https://yeee.wang/categories/%E7%AE%97%E6%B3%95/"/>
    
    
      <category term="算法" scheme="https://yeee.wang/tags/%E7%AE%97%E6%B3%95/"/>
    
      <category term="数据分析" scheme="https://yeee.wang/tags/%E6%95%B0%E6%8D%AE%E5%88%86%E6%9E%90/"/>
    
      <category term="机器学习" scheme="https://yeee.wang/tags/%E6%9C%BA%E5%99%A8%E5%AD%A6%E4%B9%A0/"/>
    
  </entry>
  
  <entry>
    <title>第 18 章：大语言模型在数据分析中的应用</title>
    <link href="https://yeee.wang/posts/19f0.html"/>
    <id>https://yeee.wang/posts/19f0.html</id>
    <published>2025-12-22T10:25:00.000Z</published>
    <updated>2026-02-19T20:00:41.880Z</updated>
    
    <content type="html"><![CDATA[<h1 id="第-18-章：大语言模型在数据分析中的应用-LLM-for-Data-Analysis"><a href="#第-18-章：大语言模型在数据分析中的应用-LLM-for-Data-Analysis" class="headerlink" title="第 18 章：大语言模型在数据分析中的应用 (LLM for Data Analysis)"></a>第 18 章：大语言模型在数据分析中的应用 (LLM for Data Analysis)</h1><blockquote><p>“以前我们教机器学数学，现在我们教机器读课文。”</p></blockquote><p>在传统的无监督学习流程中，最大的痛点是<strong>“结果不可读”</strong>。</p><ul><li>聚类结果：<code>Cluster_42</code>。</li><li>异常结果：<code>Anomaly_Score = 98.5</code>。</li><li>业务人员：？？？</li></ul><p>在大模型 (LLM) 时代，我们有了一种全新的范式：<strong>使用 LLM 作为这一流程的“解释层” (Interpretation Layer)。</strong></p><p><img src="https://img.alicdn.com/imgextra/i1/O1CN015Lrmwi1ZPhNHfZfyV_!!6000000003187-2-tps-1376-768.png" alt="LLM 作为解释层"></p><h2 id="1-核心概念：LLM-的三种角色"><a href="#1-核心概念：LLM-的三种角色" class="headerlink" title="1. 核心概念：LLM 的三种角色"></a>1. 核心概念：LLM 的三种角色</h2><p>在数据分析链路中，LLM 可以扮演三种角色：</p><h3 id="1-1-摘要员-Summarizer"><a href="#1-1-摘要员-Summarizer" class="headerlink" title="1.1 摘要员 (Summarizer)"></a>1.1 摘要员 (Summarizer)</h3><p>这是最基础的用法。</p><ul><li><strong>输入</strong>：Cluster 42 中的 50 条工单文本。</li><li><strong>Prompt</strong>：请总结这些工单的共同投诉点。</li><li><strong>输出</strong>：“主要涉及物流虚假签收，且多发生在晚间。”</li></ul><h3 id="1-2-标注员-Tagger"><a href="#1-2-标注员-Tagger" class="headerlink" title="1.2 标注员 (Tagger)"></a>1.2 标注员 (Tagger)</h3><ul><li><strong>输入</strong>：一条工单。</li><li><strong>Prompt</strong>：请判断这属于【物流、支付、商品】中的哪一类？输出 JSON。</li><li><strong>输出</strong>：<code>&#123;&quot;category&quot;: &quot;物流&quot;, &quot;sentiment&quot;: &quot;负面&quot;&#125;</code></li></ul><h3 id="1-3-翻译官-Translator"><a href="#1-3-翻译官-Translator" class="headerlink" title="1.3 翻译官 (Translator)"></a>1.3 翻译官 (Translator)</h3><ul><li><strong>输入</strong>：泰语、越南语混合文本。</li><li><strong>输出</strong>：统一的英文/中文摘要。这是多语言文本分析系统的关键能力。</li></ul><p><img src="https://img.alicdn.com/imgextra/i2/O1CN01430QzI1wXF3nLj1Za_!!6000000006317-2-tps-1376-768.png" alt="LLM 的三种角色"></p><h2 id="2-技术对比：LLM-vs-传统-NLP"><a href="#2-技术对比：LLM-vs-传统-NLP" class="headerlink" title="2. 技术对比：LLM vs 传统 NLP"></a>2. 技术对比：LLM vs 传统 NLP</h2><table><thead><tr><th style="text-align:left">任务</th><th style="text-align:left">传统 NLP (TF-IDF/LDA)</th><th style="text-align:left">LLM (GPT-4/Claude)</th></tr></thead><tbody><tr><td style="text-align:left"><strong>关键词提取</strong></td><td style="text-align:left">提取出高频词（如 “please”, “help”），往往无意义</td><td style="text-align:left">提取出<strong>语义关键词</strong>（如 “fake signature”）</td></tr><tr><td style="text-align:left"><strong>主题建模</strong></td><td style="text-align:left">主题词袋（”logistics, time, wait”），需要脑补</td><td style="text-align:left"><strong>连贯的句子</strong>总结，包含因果关系</td></tr><tr><td style="text-align:left"><strong>小样本能力</strong></td><td style="text-align:left">需要大量数据训练</td><td style="text-align:left"><strong>Zero-shot</strong> 或 Few-shot 即可工作</td></tr><tr><td style="text-align:left"><strong>成本</strong></td><td style="text-align:left">几乎免费</td><td style="text-align:left">昂贵 (API 调用费)</td></tr><tr><td style="text-align:left"><strong>速度</strong></td><td style="text-align:left">毫秒级</td><td style="text-align:left">秒级 (慢)</td></tr></tbody></table><h2 id="3-代码实战：Prompt-Engineering-实践"><a href="#3-代码实战：Prompt-Engineering-实践" class="headerlink" title="3. 代码实战：Prompt Engineering 实践"></a>3. 代码实战：Prompt Engineering 实践</h2><p>在实际项目中，需要设计精密的 Prompt 来确保 LLM 输出结构化数据。</p><pre class=" language-python"><code class="language-python"><span class="token comment" spellcheck="true"># 代码示例</span>prompt <span class="token operator">=</span> f<span class="token triple-quoted-string string">"""分析以下客服工单样本，提炼共同的业务场景。样本：&amp;#123;samples_text&amp;#125;请严格按照以下 JSON 格式输出（不要有任何其他内容）：&amp;#123;&amp;#123;"label": "简短标题(10字内)", "summary": "一句话总结(50字内)"&amp;#125;&amp;#125;"""</span></code></pre><p><strong>关键技巧</strong>：</p><ol><li><strong>System Constraints</strong>：明确“不要有其他内容”。</li><li><strong>Format Enforcing</strong>：指定 JSON 模板。</li><li><strong>Length Limit</strong>：限制字数，防止 LLM 写作文。</li><li><strong>Sampling</strong>：不要把几万条全发过去（太贵），每个簇只抽 5-10 条代表性样本。</li></ol><h2 id="4-幻觉-Hallucination-与控制"><a href="#4-幻觉-Hallucination-与控制" class="headerlink" title="4. 幻觉 (Hallucination) 与控制"></a>4. 幻觉 (Hallucination) 与控制</h2><p>LLM 最大的问题是<strong>胡说八道</strong>。</p><ul><li>它可能编造出一个不存在的投诉原因。</li><li>它可能无视你的 JSON 格式要求。</li></ul><p><strong>解决方案</strong>：</p><ol><li><strong>Temperature = 0</strong>：把随机性降到最低，让输出尽可能确定。</li><li><strong>Robust Parsing</strong>：代码里写好正则匹配，就算它加了 Markdown 符号也能提取出 JSON。</li><li><strong>Human in the Loop</strong>：关键的风险报告，最后一步必须由人审核。</li></ol><h2 id="5-实践要点"><a href="#5-实践要点" class="headerlink" title="5. 实践要点"></a>5. 实践要点</h2><ol><li><strong>聚类 + LLM = 黄金搭档</strong>：<ul><li>先用 K-Means 把 10 万条数据聚成 80 类。</li><li>再用 LLM 读这 80 类（而不是读 10 万条）。</li><li><strong>这是降低 LLM 成本最有效的方法</strong>（降本 1000 倍）。</li></ul></li><li><strong>上下文长度</strong>：注意 LLM 的 Context Window。如果样本太长，需要截断或分批摘要。</li><li><strong>隐私问题</strong>：工单中可能包含手机号、地址。在发给 OpenAI 之前，<strong>必须</strong>在本地跑正则进行脱敏 (Masking)。</li></ol><hr><p><strong>下一章预告</strong>：<br>我们用 Python 跑出了很棒的结果。<br>但是，能不能把这些结果<strong>固化</strong>下来？<br>能不能把 AI 的智慧“蒸馏”成简单的 SQL 规则，让它在数仓里每天自动跑？</p><p>👉 <a href="https://yeee.wang/posts/a60c.html">第 19 章：从模型到规则：知识蒸馏</a></p>]]></content>
    
    <summary type="html">
    
      第 18 章：大语言模型在数据分析中的应用 (LLM for Data Analysis)
“以前我们教机器学数学，现在我们教机器读课文。”

在传统的无监督学习流程中，最大的痛点是“结果不可读”。

 * 聚类结果：Cluster_42。
 * 异常结果：Anomaly_Score = 98.5。
 * 业务人员：？？？

在大模型 (LLM) 时代，我们有了一种全新的范式：使用 LLM 作为这一流程的“解释层” (Interpretation Layer)。



1. 核心概念：LLM 的三种角色
在数据分析链路中，LLM 可以扮演三种角色：

1.1 摘要员 (Summarizer)

    
    </summary>
    
      <category term="算法" scheme="https://yeee.wang/categories/%E7%AE%97%E6%B3%95/"/>
    
    
      <category term="算法" scheme="https://yeee.wang/tags/%E7%AE%97%E6%B3%95/"/>
    
      <category term="数据分析" scheme="https://yeee.wang/tags/%E6%95%B0%E6%8D%AE%E5%88%86%E6%9E%90/"/>
    
      <category term="机器学习" scheme="https://yeee.wang/tags/%E6%9C%BA%E5%99%A8%E5%AD%A6%E4%B9%A0/"/>
    
  </entry>
  
  <entry>
    <title>第 17 章：风险评分模型设计</title>
    <link href="https://yeee.wang/posts/0e2a.html"/>
    <id>https://yeee.wang/posts/0e2a.html</id>
    <published>2025-12-22T10:20:00.000Z</published>
    <updated>2026-02-19T20:00:41.880Z</updated>
    
    <content type="html"><![CDATA[<h1 id="第-17-章：风险评分模型设计-Risk-Scoring-Model-Design"><a href="#第-17-章：风险评分模型设计-Risk-Scoring-Model-Design" class="headerlink" title="第 17 章：风险评分模型设计 (Risk Scoring Model Design)"></a>第 17 章：风险评分模型设计 (Risk Scoring Model Design)</h1><blockquote><p>“给我一个数字，我就能撬动地球。—— 前提是这个数字已经归一化了。”</p></blockquote><p>在前面的章节中，我们学习了各式各样的异常检测算法，它们会吐出各种数字：</p><ul><li><strong>K-Means</strong>：离群距离 (Distance)。单位可能是米、元、或者抽象的欧氏距离。</li><li><strong>LOF</strong>：局部离群因子 (Factor)。通常大于 1，没有上限。</li><li><strong>Isolation Forest</strong>：异常概率/分数 (Score)。通常在 0 到 1 之间。</li></ul><p>但老板和业务方不想看这些天书。他们只想知道：<br><strong>“这个用户的风险是 85 分（高危），还是 20 分（安全）？”</strong></p><p>这就需要我们构建一个 <strong>风险评分模型 (Risk Scoring Model)</strong>。本章将探讨如何科学地将多个异构指标，像炼金术一样，融合为一个最终的 Risk Score。</p><p><img src="https://img.alicdn.com/imgextra/i3/O1CN01ZdlEix1QvSILJbevR_!!6000000002038-2-tps-1376-768.png" alt="风险评分模型架构"><br><em>(图注：输入层是原始指标，经过归一化层、加权层，最终汇聚成 Risk Score。)</em></p><hr><h2 id="1-核心概念：多因子融合的艺术"><a href="#1-核心概念：多因子融合的艺术" class="headerlink" title="1. 核心概念：多因子融合的艺术"></a>1. 核心概念：多因子融合的艺术</h2><p>评分模型本质上是一个<strong>函数</strong>：$Score = f(x_1, x_2, …, x_n)$。<br>虽然深度学习 (Deep Learning) 很火，但在风险评分领域，最常用、最稳健的形式依然是<strong>线性加权求和</strong>：</p><p>$$ Score = w_1 \cdot \hat{x}_1 + w_2 \cdot \hat{x}_2 + … + w_n \cdot \hat{x}_n $$</p><p>这个公式看似简单，但有两个巨坑必须填平：</p><ol><li><strong>量纲不同 ($\hat{x}$)</strong>：距离是 [0, 100]，概率是 [0, 1]，LOF 是 [1, $\infty$]。如果直接相加，大数会吃掉小数。必须<strong>归一化</strong>。</li><li><strong>权重难定 ($w$)</strong>：到底是距离重要，还是密度重要？需要一套<strong>赋权机制</strong>。</li></ol><hr><h2 id="2-归一化策略-Normalization"><a href="#2-归一化策略-Normalization" class="headerlink" title="2. 归一化策略 (Normalization)"></a>2. 归一化策略 (Normalization)</h2><p>我们的目标是把所有指标都拉到同一起跑线，通常是 $[0, 1]$ 或 $[0, 100]$。</p><h3 id="2-1-Min-Max-Scaling-离差标准化"><a href="#2-1-Min-Max-Scaling-离差标准化" class="headerlink" title="2.1 Min-Max Scaling (离差标准化)"></a>2.1 Min-Max Scaling (离差标准化)</h3><p>这是最简单粗暴的方法。<br>$$ x_{new} = \frac{x - x_{min}}{x_{max} - x_{min}} $$</p><ul><li><strong>优点</strong>：严格限制在 [0, 1]，解释性好（0 是最好，1 是最差）。</li><li><strong>缺点</strong>：<strong>极度受异常值影响</strong>。<ul><li><em>场景</em>：99 个人的欠款是 1 万，有 1 个人的欠款是 1 亿。</li><li><em>结果</em>：那个 1 亿的人归一化后是 1。其他 99 个人全是 0.0001。</li><li><em>反转</em>：<strong>在异常检测中，这反而可能是优点！</strong> 我们就是想把那个“显眼包”揪出来，同时压低普通人的噪音。</li></ul></li></ul><h3 id="2-2-Z-Score-Standardization-标准差标准化"><a href="#2-2-Z-Score-Standardization-标准差标准化" class="headerlink" title="2.2 Z-Score Standardization (标准差标准化)"></a>2.2 Z-Score Standardization (标准差标准化)</h3><p>$$ x_{new} = \frac{x - \mu}{\sigma} $$</p><ul><li><strong>优点</strong>：保留了数据的分布形态，对异常值稍微没那么敏感。</li><li><strong>缺点</strong>：没有固定的上下界（可能是 -3 到 +5），不方便转化为 0-100 分。</li></ul><h3 id="2-3-Rank-Scaling-排名归一化-——-推荐"><a href="#2-3-Rank-Scaling-排名归一化-——-推荐" class="headerlink" title="2.3 Rank Scaling (排名归一化) —— 推荐"></a>2.3 Rank Scaling (排名归一化) —— <strong>推荐</strong></h3><p>不管数值是多少，只看排名百分比。<br>$$ x_{new} = \frac{Rank(x)}{N} $$</p><ul><li><strong>优点</strong>：<strong>极度鲁棒</strong>。不管那个欠款是 1 亿还是 1 万亿，它都是第一名 (1.0)。数据会均匀分布在 [0, 1] 之间。</li><li><strong>缺点</strong>：丢失了“程度”信息。第一名和第二名可能只差 0.01 元，也可能差 100 亿，排名看不出来。</li></ul><p><img src="https://img.alicdn.com/imgextra/i4/O1CN01DX7UqH1hS0IHpQA77_!!6000000004275-2-tps-1376-768.png" alt="归一化策略对比"><br><em>(图注：Min-Max 凸显极端值；Rank 抹平差距。根据业务需求选择。)</em></p><hr><h2 id="3-权重设计-Weighting"><a href="#3-权重设计-Weighting" class="headerlink" title="3. 权重设计 (Weighting)"></a>3. 权重设计 (Weighting)</h2><p>如何决定 $w_i$？这是一个哲学问题。</p><h3 id="3-1-专家规则-Heuristic-——-拍脑袋"><a href="#3-1-专家规则-Heuristic-——-拍脑袋" class="headerlink" title="3.1 专家规则 (Heuristic) —— 拍脑袋"></a>3.1 专家规则 (Heuristic) —— 拍脑袋</h3><p>直接问业务专家。</p><ul><li>“老张，你觉得‘离群距离’和‘局部密度’哪个更能代表风险？”</li><li>老张：“离群更重要吧，给 0.6；密度给 0.4。”</li><li><strong>优点</strong>：解释性极强，完全符合业务直觉，老板容易接受。</li><li><strong>缺点</strong>：主观，难以验证。</li></ul><h3 id="3-2-熵权法-Entropy-Weight-Method-——-让数据说话"><a href="#3-2-熵权法-Entropy-Weight-Method-——-让数据说话" class="headerlink" title="3.2 熵权法 (Entropy Weight Method) —— 让数据说话"></a>3.2 熵权法 (Entropy Weight Method) —— 让数据说话</h3><p>利用信息论中的<strong>熵 (Entropy)</strong>。</p><ul><li><strong>逻辑</strong>：<ul><li>如果某个指标大家的数值都差不多（方差小，混乱度高，熵大），说明这个指标没啥区分度，<strong>权重应该低</strong>。</li><li>如果某个指标大家差异巨大（方差大，熵小），说明这个指标蕴含信息量大，<strong>权重应该高</strong>。</li></ul></li><li><strong>优点</strong>：客观，自动化。</li></ul><h3 id="3-3-AHP-层次分析法-——-科学地拍脑袋"><a href="#3-3-AHP-层次分析法-——-科学地拍脑袋" class="headerlink" title="3.3 AHP (层次分析法) —— 科学地拍脑袋"></a>3.3 AHP (层次分析法) —— 科学地拍脑袋</h3><p>让专家做选择题，而不是直接填空。</p><ul><li>不要问：“A 权重多少？”</li><li>要问：“你觉得指标 A 比指标 B 重要多少？(1-9 分)”</li><li>然后构建判断矩阵，通过计算特征向量得出一致性权重。</li></ul><p><img src="https://img.alicdn.com/imgextra/i3/O1CN01LnGYDz1fTiXp8Qavf_!!6000000004008-2-tps-1376-768.png" alt="权重设计方法"><br><em>(图注：专家规则靠直觉，熵权法靠数据，AHP 兼顾两者。)</em></p><hr><h2 id="4-综合评分公式：实战案例"><a href="#4-综合评分公式：实战案例" class="headerlink" title="4. 综合评分公式：实战案例"></a>4. 综合评分公式：实战案例</h2><p>在实际项目中，我们不需要搞太复杂。一个简单而有效的混合策略往往最好用。</p><p><strong>策略</strong>：</p><ol><li><strong>归一化</strong>：使用 <strong>Min-Max</strong>。因为我们就是想找 Top Risk，不仅要排名第一，还要看它到底有多离谱。</li><li><strong>权重</strong>：使用 <strong>50/50 均分</strong>。除非有强烈的业务理由，否则不要轻易认为谁比谁重要。</li></ol><p>$$ Risk = 100 \times (0.5 \times \text{Norm}(Distance) + 0.5 \times \text{Norm}(Density)) $$</p><pre class=" language-python"><code class="language-python"><span class="token keyword">import</span> numpy <span class="token keyword">as</span> np<span class="token comment" spellcheck="true"># 假设我们有两个原始指标数组</span>cluster_distances <span class="token operator">=</span> np<span class="token punctuation">.</span>array<span class="token punctuation">(</span><span class="token punctuation">[</span><span class="token number">0.1</span><span class="token punctuation">,</span> <span class="token number">0.5</span><span class="token punctuation">,</span> <span class="token number">2.0</span><span class="token punctuation">,</span> <span class="token number">10.0</span><span class="token punctuation">]</span><span class="token punctuation">)</span> <span class="token comment" spellcheck="true"># 离群距离</span>density_scores <span class="token operator">=</span> np<span class="token punctuation">.</span>array<span class="token punctuation">(</span><span class="token punctuation">[</span><span class="token number">1.0</span><span class="token punctuation">,</span> <span class="token number">1.2</span><span class="token punctuation">,</span> <span class="token number">3.0</span><span class="token punctuation">,</span> <span class="token number">5.0</span><span class="token punctuation">]</span><span class="token punctuation">)</span>     <span class="token comment" spellcheck="true"># LOF 分数 (越大越异常)</span><span class="token comment" spellcheck="true"># 1. 归一化 (Min-Max)</span><span class="token keyword">def</span> <span class="token function">min_max_scale</span><span class="token punctuation">(</span>arr<span class="token punctuation">)</span><span class="token punctuation">:</span>    <span class="token keyword">if</span> arr<span class="token punctuation">.</span>max<span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">==</span> arr<span class="token punctuation">.</span>min<span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">:</span>        <span class="token keyword">return</span> np<span class="token punctuation">.</span>zeros_like<span class="token punctuation">(</span>arr<span class="token punctuation">)</span>    <span class="token keyword">return</span> <span class="token punctuation">(</span>arr <span class="token operator">-</span> arr<span class="token punctuation">.</span>min<span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">)</span> <span class="token operator">/</span> <span class="token punctuation">(</span>arr<span class="token punctuation">.</span>max<span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">-</span> arr<span class="token punctuation">.</span>min<span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">)</span>norm_dist <span class="token operator">=</span> min_max_scale<span class="token punctuation">(</span>cluster_distances<span class="token punctuation">)</span>norm_dens <span class="token operator">=</span> min_max_scale<span class="token punctuation">(</span>density_scores<span class="token punctuation">)</span><span class="token keyword">print</span><span class="token punctuation">(</span>f<span class="token string">"归一化距离: &amp;#123;norm_dist&amp;#125;"</span><span class="token punctuation">)</span><span class="token comment" spellcheck="true"># [0.   0.04 0.19 1.  ] -> 注意：那个 10.0 把其他人压得很扁</span><span class="token keyword">print</span><span class="token punctuation">(</span>f<span class="token string">"归一化密度: &amp;#123;norm_dens&amp;#125;"</span><span class="token punctuation">)</span><span class="token comment" spellcheck="true"># [0.   0.05 0.5  1.  ]</span><span class="token comment" spellcheck="true"># 2. 加权融合 (50/50)</span>risk_scores <span class="token operator">=</span> <span class="token punctuation">(</span>norm_dist <span class="token operator">*</span> <span class="token number">0.5</span> <span class="token operator">+</span> norm_dens <span class="token operator">*</span> <span class="token number">0.5</span><span class="token punctuation">)</span> <span class="token operator">*</span> <span class="token number">100</span><span class="token keyword">print</span><span class="token punctuation">(</span>f<span class="token string">"最终风险分: &amp;#123;risk_scores&amp;#125;"</span><span class="token punctuation">)</span><span class="token comment" spellcheck="true"># [ 0.    4.5  34.5 100. ]</span></code></pre><hr><h2 id="5-实践要点"><a href="#5-实践要点" class="headerlink" title="5. 实践要点"></a>5. 实践要点</h2><ol><li><strong>分数校准 (Calibration)</strong>：<ul><li>直接算出来的分数，分布往往很难看（长尾）。比如 90% 的人都得 0-5 分，只有几个 100 分。</li><li>如果你希望分数分布更像“正态分布”或者更平滑，可以在归一化前加一个 <strong>Log 变换</strong> (<code>np.log1p</code>)，或者最后加一个 <strong>Sigmoid</strong> 变换。</li></ul></li><li><strong>分级 (Tiering)</strong>：<ul><li>老板不需要精确的 87.5 分。他需要的是：<strong>红灯 (High)</strong>、<strong>黄灯 (Medium)</strong>、<strong>绿灯 (Low)</strong>。</li><li><strong>切分策略</strong>：<ul><li><strong>High Risk</strong>: Top 1% (需要立即处理)。</li><li><strong>Medium Risk</strong>: Top 5% (需要关注)。</li><li><strong>Low Risk</strong>: 剩余 95%。</li></ul></li></ul></li></ol><p><img src="https://img.alicdn.com/imgextra/i2/O1CN01PSKGge27Kq6lmv9tZ_!!6000000007779-2-tps-1376-768.png" alt="风险分级策略"><br><em>(图注：红黄绿灯策略将连续分数转化为可操作的业务决策。)</em></p><ol start="3"><li><strong>可解释性 (Explainability)</strong>：<ul><li>如果用户问“为什么我得了 100 分？”，你不能说“因为模型算的”。</li><li>你得能通过<strong>贡献度归因</strong>告诉他：“因为你的‘离群距离’贡献了 50 分（满分），‘低密度’贡献了 50 分（满分）。你既离得远，又很孤独。”</li></ul></li></ol><hr><h2 id="下一章预告"><a href="#下一章预告" class="headerlink" title="下一章预告"></a>下一章预告</h2><p>至此，我们的数学模型（聚类、降维、异常、评分）已经全部搭建完毕。<br>但是，这一堆数字（Risk=85, Cluster=3）对于业务人员来说还是天书。</p><ul><li>“Cluster 3 到底代表什么业务含义？”</li><li>“这个 85 分的风险具体是指什么？”</li></ul><p>如何让机器用人类的语言，告诉我们“这里发生了什么”？<br><strong>LLM (大语言模型)</strong> 将作为最后一块拼图登场。</p><p>👉 <a href="https://yeee.wang/posts/19f0.html">第 18 章：大语言模型在数据分析中的应用</a></p>]]></content>
    
    <summary type="html">
    
      第 17 章：风险评分模型设计 (Risk Scoring Model Design)
“给我一个数字，我就能撬动地球。—— 前提是这个数字已经归一化了。”

在前面的章节中，我们学习了各式各样的异常检测算法，它们会吐出各种数字：

 * K-Means：离群距离 (Distance)。单位可能是米、元、或者抽象的欧氏距离。
 * LOF：局部离群因子 (Factor)。通常大于 1，没有上限。
 * Isolation Forest：异常概率/分数 (Score)。通常在 0 到 1 之间。

但老板和业务方不想看这些天书。他们只想知道：
“这个用户的风险是 85 分（高危），还是 20 分
    
    </summary>
    
      <category term="算法" scheme="https://yeee.wang/categories/%E7%AE%97%E6%B3%95/"/>
    
    
      <category term="算法" scheme="https://yeee.wang/tags/%E7%AE%97%E6%B3%95/"/>
    
      <category term="数据分析" scheme="https://yeee.wang/tags/%E6%95%B0%E6%8D%AE%E5%88%86%E6%9E%90/"/>
    
      <category term="机器学习" scheme="https://yeee.wang/tags/%E6%9C%BA%E5%99%A8%E5%AD%A6%E4%B9%A0/"/>
    
  </entry>
  
  <entry>
    <title>第 16 章：模型驱动异常检测</title>
    <link href="https://yeee.wang/posts/584b.html"/>
    <id>https://yeee.wang/posts/584b.html</id>
    <published>2025-12-22T10:15:00.000Z</published>
    <updated>2026-02-19T20:00:41.880Z</updated>
    
    <content type="html"><![CDATA[<h1 id="第-16-章：模型驱动异常检测-Model-Based-Anomaly-Detection"><a href="#第-16-章：模型驱动异常检测-Model-Based-Anomaly-Detection" class="headerlink" title="第 16 章：模型驱动异常检测 (Model-Based Anomaly Detection)"></a>第 16 章：模型驱动异常检测 (Model-Based Anomaly Detection)</h1><blockquote><p>“如果你想把一个苹果从一堆西瓜里分出来，你不需要描述苹果长什么样，你只需要切几刀。”</p></blockquote><p>前两章我们讨论了基于<strong>统计</strong>（Z-Score）和<strong>距离</strong>（KNN, LOF）的异常检测。<br>但在大数据时代，它们都有一个致命伤：<strong>慢</strong>。<br>计算距离矩阵是 $O(N^2)$ 的复杂度。如果你有 100 万条数据，计算量就是 $10^{12}$ 次。即使是现在的超算也得跑很久。</p><p>本章我们将介绍基于<strong>模型</strong>的方法，特别是工业界的神器——<strong>隔离森林 (Isolation Forest)</strong>。它不计算距离，而是通过“随机切割”来快速锁定异常。它的复杂度是 $O(N)$，<strong>线性的！</strong></p><p><img src="https://img.alicdn.com/imgextra/i1/O1CN01D3gSEN1xnYmslSSYx_!!6000000006488-2-tps-1376-768.png" alt="Isolation Forest 切割原理"><br><em>(图注：左图：正常点深埋在中心，需要切很多刀才能分离。右图：异常点在边缘，切一两刀就分离了。)</em></p><hr><h2 id="1-核心概念：Isolation-Forest-孤立森林"><a href="#1-核心概念：Isolation-Forest-孤立森林" class="headerlink" title="1. 核心概念：Isolation Forest (孤立森林)"></a>1. 核心概念：Isolation Forest (孤立森林)</h2><h3 id="1-1-异常的两个特性"><a href="#1-1-异常的两个特性" class="headerlink" title="1.1 异常的两个特性"></a>1.1 异常的两个特性</h3><p>Isolation Forest 基于两个极其简单的假设，这两个假设是异常点自带的“原罪”：</p><ol><li><strong>稀少 (Few)</strong>：异常点很少。</li><li><strong>独特 (Different)</strong>：异常点的值域与正常点差异很大。</li></ol><p>基于这两个特性，如果我们随机切割数据空间，异常点会<strong>很容易</strong>被切出去。</p><h3 id="1-2-随机树的构建-iTree"><a href="#1-2-随机树的构建-iTree" class="headerlink" title="1.2 随机树的构建 (iTree)"></a>1.2 随机树的构建 (iTree)</h3><p>想象你在玩切蛋糕游戏：</p><ol><li>随机选择一个特征（比如“金额”）。</li><li>在该特征的最大值和最小值之间，随机选一个切分点。</li><li>一刀切下去，数据被分为两半。</li><li>重复上述过程，直到每个点都被单独切出来（成为叶子节点）。</li></ol><p><strong>关键洞察</strong>：</p><ul><li><strong>正常点</strong>：它们挤在蛋糕中心最密集的地方。你需要切很多很多刀（树很深），才能把它们一个个分开。</li><li><strong>异常点</strong>：它们离群索居在蛋糕边缘。你随便切两刀（树很浅），它就被<strong>孤立 (Isolated)</strong> 了。</li></ul><p><strong>结论</strong>：<strong>路径长度 (Path Length)</strong> 越短，越可能是异常点。</p><p><img src="https://img.alicdn.com/imgextra/i3/O1CN015Nmj5n1NmnxzvEdVX_!!6000000001613-2-tps-1376-768.png" alt="隔离森林路径长度"><br><em>(图注：正常点深埋中心需要 8 刀，异常点在边缘只需 2 刀。路径长度 = 异常程度的倒数。)</em></p><h3 id="1-3-异常分数-Anomaly-Score"><a href="#1-3-异常分数-Anomaly-Score" class="headerlink" title="1.3 异常分数 (Anomaly Score)"></a>1.3 异常分数 (Anomaly Score)</h3><p>一棵树可能误判，所以我们要种一片森林（比如 100 棵树）。<br>对于每个样本 $x$，计算它在 100 棵树中的平均路径长度 $E(h(x))$。然后归一化为分数 $s$：</p><p>$$ s(x, n) = 2^{-\frac{E(h(x))}{c(n)}} $$</p><ul><li><strong>$s \approx 1$</strong>：路径极短 $\rightarrow$ <strong>几乎肯定是异常</strong>。</li><li><strong>$s \approx 0.5$</strong>：路径不长不短 $\rightarrow$ <strong>正常</strong>（和大家都一样）。</li><li><strong>$s \approx 0$</strong>：路径极长 $\rightarrow$ <strong>最安全的核心数据</strong>。</li></ul><hr><h2 id="2-另一种思路：One-Class-SVM-单类支持向量机"><a href="#2-另一种思路：One-Class-SVM-单类支持向量机" class="headerlink" title="2. 另一种思路：One-Class SVM (单类支持向量机)"></a>2. 另一种思路：One-Class SVM (单类支持向量机)</h2><p>如果你只有正常数据（比如只有良品图片），想检测次品，这是一个<strong>单分类 (One-Class Classification)</strong> 问题。</p><p><strong>One-Class SVM</strong> 的思路是：</p><ul><li>把所有正常数据映射到高维特征空间（使用核函数 Kernel Trick）。</li><li>在这个高维空间里，找一个最小的<strong>超球体 (Hypersphere)</strong>，把正常数据紧紧包住。</li><li>这个超球体就是<strong>边界</strong>。</li><li><strong>测试时</strong>：如果新数据落在球里面，就是正常；落在球外面，就是异常。</li></ul><p><img src="https://img.alicdn.com/imgextra/i3/O1CN01bo18N91KEVSXOWd2V_!!6000000001132-2-tps-1376-768.png" alt="One-Class SVM 边界示意图"><br><em>(图注：One-Class SVM 试图画一个圈，把所有白点（正常）圈进去。红点（异常）自然就被排除在外了。)</em></p><hr><h2 id="3-技术对比：模型驱动全家桶"><a href="#3-技术对比：模型驱动全家桶" class="headerlink" title="3. 技术对比：模型驱动全家桶"></a>3. 技术对比：模型驱动全家桶</h2><table><thead><tr><th style="text-align:left">算法</th><th style="text-align:left">核心思想</th><th style="text-align:left">复杂度</th><th style="text-align:left">优点</th><th style="text-align:left">缺点</th></tr></thead><tbody><tr><td style="text-align:left"><strong>Isolation Forest</strong></td><td style="text-align:left"><strong>随机切割</strong>，看路径长短</td><td style="text-align:left">$O(N)$ (线性！)</td><td style="text-align:left"><strong>极快</strong>，适合海量高维数据，鲁棒性强</td><td style="text-align:left">对局部异常点（Local Anomaly）不敏感</td></tr><tr><td style="text-align:left"><strong>One-Class SVM</strong></td><td style="text-align:left">寻找最大间隔<strong>边界</strong></td><td style="text-align:left">$O(N^2)$ ~ $O(N^3)$</td><td style="text-align:left">理论严谨，适合<strong>小样本</strong>、非线性边界</td><td style="text-align:left"><strong>慢</strong>，对参数 $\nu$ 极其敏感</td></tr><tr><td style="text-align:left"><strong>Elliptic Envelope</strong></td><td style="text-align:left">拟合鲁棒<strong>高斯分布</strong></td><td style="text-align:left">$O(N^3)$</td><td style="text-align:left">对<strong>正态分布</strong>数据极准</td><td style="text-align:left">不适合非正态分布</td></tr><tr><td style="text-align:left"><strong>Autoencoder</strong></td><td style="text-align:left"><strong>重构误差</strong></td><td style="text-align:left">依赖网络</td><td style="text-align:left">适合图像/序列等<strong>非结构化</strong>数据</td><td style="text-align:left">黑盒，训练难，容易过拟合</td></tr></tbody></table><p><img src="https://img.alicdn.com/imgextra/i2/O1CN01KFNJVA1tCE2Sdkx3Y_!!6000000005865-2-tps-1376-768.png" alt="异常检测三大流派"><br><em>(图注：统计方法适合小数据，距离/密度适合复杂形状，模型方法适合大规模高维数据。)</em></p><hr><h2 id="4-代码实战：Isolation-Forest"><a href="#4-代码实战：Isolation-Forest" class="headerlink" title="4. 代码实战：Isolation Forest"></a>4. 代码实战：Isolation Forest</h2><p>在工业界，Isolation Forest 是处理大数据的绝对首选。它不仅快，而且不需要太多调参。</p><pre class=" language-python"><code class="language-python"><span class="token keyword">import</span> numpy <span class="token keyword">as</span> np<span class="token keyword">import</span> matplotlib<span class="token punctuation">.</span>pyplot <span class="token keyword">as</span> plt<span class="token keyword">from</span> sklearn<span class="token punctuation">.</span>ensemble <span class="token keyword">import</span> IsolationForest<span class="token comment" spellcheck="true"># 1. 生成数据：一堆正常点，几个离群点</span>rng <span class="token operator">=</span> np<span class="token punctuation">.</span>random<span class="token punctuation">.</span>RandomState<span class="token punctuation">(</span><span class="token number">42</span><span class="token punctuation">)</span>X <span class="token operator">=</span> <span class="token number">0.3</span> <span class="token operator">*</span> rng<span class="token punctuation">.</span>randn<span class="token punctuation">(</span><span class="token number">100</span><span class="token punctuation">,</span> <span class="token number">2</span><span class="token punctuation">)</span>X_train <span class="token operator">=</span> np<span class="token punctuation">.</span>r_<span class="token punctuation">[</span>X <span class="token operator">+</span> <span class="token number">2</span><span class="token punctuation">,</span> X <span class="token operator">-</span> <span class="token number">2</span><span class="token punctuation">]</span> <span class="token comment" spellcheck="true"># 两个正常的簇</span>X_outliers <span class="token operator">=</span> rng<span class="token punctuation">.</span>uniform<span class="token punctuation">(</span>low<span class="token operator">=</span><span class="token operator">-</span><span class="token number">4</span><span class="token punctuation">,</span> high<span class="token operator">=</span><span class="token number">4</span><span class="token punctuation">,</span> size<span class="token operator">=</span><span class="token punctuation">(</span><span class="token number">20</span><span class="token punctuation">,</span> <span class="token number">2</span><span class="token punctuation">)</span><span class="token punctuation">)</span> <span class="token comment" spellcheck="true"># 均匀分布的噪声</span>X <span class="token operator">=</span> np<span class="token punctuation">.</span>r_<span class="token punctuation">[</span>X_train<span class="token punctuation">,</span> X_outliers<span class="token punctuation">]</span><span class="token comment" spellcheck="true"># 2. 训练 iForest</span><span class="token comment" spellcheck="true"># contamination: 预计异常比例。这决定了阈值。</span><span class="token comment" spellcheck="true"># 如果设为 'auto'，它会自动定一个阈值（通常在 0.5 左右）。</span>clf <span class="token operator">=</span> IsolationForest<span class="token punctuation">(</span>n_estimators<span class="token operator">=</span><span class="token number">100</span><span class="token punctuation">,</span> contamination<span class="token operator">=</span><span class="token number">0.1</span><span class="token punctuation">,</span> random_state<span class="token operator">=</span><span class="token number">42</span><span class="token punctuation">)</span>clf<span class="token punctuation">.</span>fit<span class="token punctuation">(</span>X<span class="token punctuation">)</span><span class="token comment" spellcheck="true"># 3. 预测</span>y_pred <span class="token operator">=</span> clf<span class="token punctuation">.</span>predict<span class="token punctuation">(</span>X<span class="token punctuation">)</span> <span class="token comment" spellcheck="true"># 1: 正常</span><span class="token comment" spellcheck="true"># -1: 异常</span><span class="token comment" spellcheck="true"># 获取异常分数 (负数表示越异常)</span>scores <span class="token operator">=</span> clf<span class="token punctuation">.</span>decision_function<span class="token punctuation">(</span>X<span class="token punctuation">)</span><span class="token comment" spellcheck="true"># 4. 绘图</span>plt<span class="token punctuation">.</span>figure<span class="token punctuation">(</span>figsize<span class="token operator">=</span><span class="token punctuation">(</span><span class="token number">10</span><span class="token punctuation">,</span> <span class="token number">6</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token comment" spellcheck="true"># 画正常点</span>plt<span class="token punctuation">.</span>scatter<span class="token punctuation">(</span>X<span class="token punctuation">[</span>y_pred <span class="token operator">==</span> <span class="token number">1</span><span class="token punctuation">,</span> <span class="token number">0</span><span class="token punctuation">]</span><span class="token punctuation">,</span> X<span class="token punctuation">[</span>y_pred <span class="token operator">==</span> <span class="token number">1</span><span class="token punctuation">,</span> <span class="token number">1</span><span class="token punctuation">]</span><span class="token punctuation">,</span> c<span class="token operator">=</span><span class="token string">'white'</span><span class="token punctuation">,</span> edgecolors<span class="token operator">=</span><span class="token string">'k'</span><span class="token punctuation">,</span> label<span class="token operator">=</span><span class="token string">'Normal'</span><span class="token punctuation">)</span><span class="token comment" spellcheck="true"># 画异常点</span>plt<span class="token punctuation">.</span>scatter<span class="token punctuation">(</span>X<span class="token punctuation">[</span>y_pred <span class="token operator">==</span> <span class="token operator">-</span><span class="token number">1</span><span class="token punctuation">,</span> <span class="token number">0</span><span class="token punctuation">]</span><span class="token punctuation">,</span> X<span class="token punctuation">[</span>y_pred <span class="token operator">==</span> <span class="token operator">-</span><span class="token number">1</span><span class="token punctuation">,</span> <span class="token number">1</span><span class="token punctuation">]</span><span class="token punctuation">,</span> c<span class="token operator">=</span><span class="token string">'red'</span><span class="token punctuation">,</span> label<span class="token operator">=</span><span class="token string">'Outlier'</span><span class="token punctuation">)</span>plt<span class="token punctuation">.</span>title<span class="token punctuation">(</span><span class="token string">"Isolation Forest Detection"</span><span class="token punctuation">)</span>plt<span class="token punctuation">.</span>legend<span class="token punctuation">(</span><span class="token punctuation">)</span>plt<span class="token punctuation">.</span>show<span class="token punctuation">(</span><span class="token punctuation">)</span></code></pre><hr><h2 id="5-实践要点"><a href="#5-实践要点" class="headerlink" title="5. 实践要点"></a>5. 实践要点</h2><ol><li><strong>子采样 (Sub-sampling)</strong>：这是 iForest 的精髓，也是它快的原因。<ul><li><strong>不要用全量数据建树！</strong> 如果你有 100 万数据，每棵树只需要随机采样 <strong>256 或 512</strong> 个样本就足够了。</li><li><strong>为什么？</strong> 因为异常检测不需要看清全局，只需要看清边缘。采样太密，反而会导致 <strong>Masking Effect</strong>（正常点太密，把异常点包围了，导致切不开）。</li><li>sklearn 默认 <code>max_samples=&#39;auto&#39;</code> 会自动限制为 256。</li></ul></li><li><strong>高维优势</strong>：iForest 在高维数据上表现出奇地好。因为它每次只随机选一个特征切割，这天然地避开了距离计算的维度灾难。</li><li><strong>参数 contamination</strong>：这个参数决定了阈值。如果你不知道有多少异常，可以先把 <code>decision_function</code> 的得分分布图画出来，找那个长尾的拐点。</li></ol><hr><h2 id="下一章预告"><a href="#下一章预告" class="headerlink" title="下一章预告"></a>下一章预告</h2><p>我们现在有了各种异常分数：</p><ul><li>统计学的 <strong>Z-Score</strong>。</li><li>基于密度的 <strong>LOF Score</strong>。</li><li>基于模型的 <strong>Isolation Score</strong>。</li></ul><p>每个分数的量纲都不一样（有的 0-1，有的 0-100，有的负无穷到正无穷）。<br>如何把这些分数<strong>融合</strong>起来，给老板一个最终的、可解释的 <strong>Risk Score (0-100)</strong>？<br>这不仅是数学问题，更是<strong>业务模型设计</strong>的问题。</p><p>👉 <a href="https://yeee.wang/posts/0e2a.html">第 17 章：风险评分模型设计</a></p>]]></content>
    
    <summary type="html">
    
      第 16 章：模型驱动异常检测 (Model-Based Anomaly Detection)
“如果你想把一个苹果从一堆西瓜里分出来，你不需要描述苹果长什么样，你只需要切几刀。”

前两章我们讨论了基于统计（Z-Score）和距离（KNN, LOF）的异常检测。
但在大数据时代，它们都有一个致命伤：慢。
计算距离矩阵是 $O(N^2)$ 的复杂度。如果你有 100 万条数据，计算量就是 $10^{12}$ 次。即使是现在的超算也得跑很久。

本章我们将介绍基于模型的方法，特别是工业界的神器——隔离森林 (Isolation Forest)。它不计算距离，而是通过“随机切割”来快速锁定异常。它
    
    </summary>
    
      <category term="算法" scheme="https://yeee.wang/categories/%E7%AE%97%E6%B3%95/"/>
    
    
      <category term="算法" scheme="https://yeee.wang/tags/%E7%AE%97%E6%B3%95/"/>
    
      <category term="数据分析" scheme="https://yeee.wang/tags/%E6%95%B0%E6%8D%AE%E5%88%86%E6%9E%90/"/>
    
      <category term="机器学习" scheme="https://yeee.wang/tags/%E6%9C%BA%E5%99%A8%E5%AD%A6%E4%B9%A0/"/>
    
  </entry>
  
  <entry>
    <title>第 15 章：基于距离与密度的异常检测</title>
    <link href="https://yeee.wang/posts/bbf1.html"/>
    <id>https://yeee.wang/posts/bbf1.html</id>
    <published>2025-12-22T10:10:00.000Z</published>
    <updated>2026-02-19T20:00:41.880Z</updated>
    
    <content type="html"><![CDATA[<h1 id="第-15-章：基于距离与密度的异常检测-Distance-amp-Density-based-Anomaly-Detection"><a href="#第-15-章：基于距离与密度的异常检测-Distance-amp-Density-based-Anomaly-Detection" class="headerlink" title="第 15 章：基于距离与密度的异常检测 (Distance &amp; Density-based Anomaly Detection)"></a>第 15 章：基于距离与密度的异常检测 (Distance &amp; Density-based Anomaly Detection)</h1><blockquote><p>“离群并不意味着你是错的，只意味着你是孤独的。”</p></blockquote><p>上一章的统计方法（Z-Score）假设数据服从某种分布（如正态分布）。但在现实世界中，数据的形状可能千奇百怪（如双螺旋、甜甜圈、不规则的多簇）。<br>这时候，任何假设分布的模型都会失效。我们需要回归几何直觉：<br><strong>异常点就是那些离大家都远的点。</strong></p><p>本章将介绍两类不依赖分布假设的算法：<strong>KNN</strong>（看距离）和 <strong>LOF</strong>（看密度）。</p><hr><h2 id="1-KNN-异常检测：简单的力量"><a href="#1-KNN-异常检测：简单的力量" class="headerlink" title="1. KNN 异常检测：简单的力量"></a>1. KNN 异常检测：简单的力量</h2><p>KNN (K-Nearest Neighbors) 不仅可以做分类，也可以做异常检测。<br>它的逻辑非常朴素：<strong>如果你的 K 个最近邻居都离你很远，那你就是异常点。</strong></p><p><img src="https://img.alicdn.com/imgextra/i1/O1CN01zOJfxQ1gnF2i7K6iv_!!6000000004186-2-tps-1376-768.png" alt="KNN 异常检测示意图"><br><em>(图注：正常点 A 的邻居都在身边；异常点 B 的邻居都在十万八千里外。B 的平均邻居距离显著大于 A。)</em></p><h3 id="1-1-算法流程"><a href="#1-1-算法流程" class="headerlink" title="1.1 算法流程"></a>1.1 算法流程</h3><ol><li><strong>找邻居</strong>：对于数据点 $x$，找到它最近的 $k$ 个邻居。</li><li><strong>算得分</strong>：计算这 $k$ 个邻居的平均距离（或者第 $k$ 个邻居的距离）。</li><li><strong>排座次</strong>：这个距离就是 <strong>异常得分 (Anomaly Score)</strong>。得分越高，越异常。</li><li><strong>切一刀</strong>：设定一个阈值（比如 Top 1%），得分最高的点即为异常。</li></ol><h3 id="1-2-优缺点分析"><a href="#1-2-优缺点分析" class="headerlink" title="1.2 优缺点分析"></a>1.2 优缺点分析</h3><ul><li><strong>优点</strong>：<ul><li><strong>非参数</strong>：不需要假设数据服从正态分布。</li><li><strong>可解释</strong>：你可以告诉用户“因为它离所有正常工单都很远”。</li></ul></li><li><strong>缺点</strong>：<ul><li><strong>计算量大</strong>：需要计算所有点对的距离，复杂度 $O(N^2)$。</li><li><strong>密度敏感</strong>：这是它最大的死穴。无法处理<strong>多密度</strong>的数据集。</li></ul></li></ul><hr><h2 id="2-LOF-局部离群因子-：密度的相对论"><a href="#2-LOF-局部离群因子-：密度的相对论" class="headerlink" title="2. LOF (局部离群因子)：密度的相对论"></a>2. LOF (局部离群因子)：密度的相对论</h2><h3 id="2-1-为什么-KNN-不行？-多密度问题"><a href="#2-1-为什么-KNN-不行？-多密度问题" class="headerlink" title="2.1 为什么 KNN 不行？(多密度问题)"></a>2.1 为什么 KNN 不行？(多密度问题)</h3><p>想象一个城市：</p><ul><li><strong>闹市区 (C1)</strong>：人口极度密集。大家都挤在一起，人与人之间距离 1 米。</li><li><strong>郊区 (C2)</strong>：人口稀疏。人与人之间距离 100 米。</li></ul><p><strong>KNN 的困境</strong>：</p><ul><li>如果在闹市区，有一个人离邻居 10 米远。在 KNN 看来，10 米很近啊，<strong>正常</strong>。但在闹市区，这其实是<strong>异常</strong>（为什么大家都在排队，你一个人站在 10 米开外？）。</li><li>如果在郊区，大家距离都是 100 米。KNN 看来，100 米很远啊，<strong>异常</strong>。但其实在郊区这很<strong>正常</strong>。</li></ul><p><strong>结论</strong>：用同一把尺子（全局阈值）去衡量不同密度的区域，注定会失败。</p><p><img src="https://img.alicdn.com/imgextra/i4/O1CN01QycNpH1SV0emvgh7J_!!6000000002251-2-tps-1376-768.png" alt="多密度问题"><br><em>(图注：闹市区的离群点被漏判，郊区的正常点被误判。KNN 的全局阈值无法处理密度差异。)</em></p><h3 id="2-2-LOF-的核心思想：相对密度"><a href="#2-2-LOF-的核心思想：相对密度" class="headerlink" title="2.2 LOF 的核心思想：相对密度"></a>2.2 LOF 的核心思想：相对密度</h3><p><strong>LOF (Local Outlier Factor)</strong> 引入了<strong>相对密度</strong>的概念。它不看绝对距离，而是看：<br><strong>“你的密度”与“你邻居的密度”的比值。</strong></p><p><img src="https://img.alicdn.com/imgextra/i4/O1CN01bEVq5k24ZXyoezkFn_!!6000000007405-2-tps-1376-768.png" alt="LOF 局部离群因子"><br><em>(图注：点 A 的密度很低，但它的邻居（在圆圈里）密度很高。这种反差使得 A 的 LOF 值很高。)</em></p><h3 id="2-3-核心公式推导-简化版"><a href="#2-3-核心公式推导-简化版" class="headerlink" title="2.3 核心公式推导 (简化版)"></a>2.3 核心公式推导 (简化版)</h3><ol><li><strong>局部可达密度 (LRD)</strong>：大概就是“我周围邻居有多挤”的倒数。</li><li><strong>LOF 指数</strong>：<br>$$ LOF(A) \approx \frac{\text{邻居们的平均 LRD}}{\text{A 的 LRD}} $$</li></ol><p><strong>判读指南</strong>：</p><ul><li><strong>$LOF \approx 1$</strong>：<strong>正常</strong>。你的密度和邻居差不多。大家都是郊区人，或者大家都是城里人。</li><li><strong>$LOF \gg 1$</strong>：<strong>异常</strong>。你的密度远小于邻居的密度。你的邻居很挤，而你很空。你是“闹市区的孤儿”。</li><li><strong>$LOF &lt; 1$</strong>：<strong>核心点</strong>。你的密度比邻居还大。你是“比正常还正常”的致密核心。</li></ul><p><img src="https://img.alicdn.com/imgextra/i3/O1CN01abTLjc1fE94uvAsSs_!!6000000003974-2-tps-1376-768.png" alt="LOF 核心公式"><br><em>(图注：LOF 通过比较自身密度与邻居密度的比值来判断异常程度。)</em></p><hr><h2 id="3-技术对比：距离-vs-密度"><a href="#3-技术对比：距离-vs-密度" class="headerlink" title="3. 技术对比：距离 vs 密度"></a>3. 技术对比：距离 vs 密度</h2><table><thead><tr><th style="text-align:left">算法</th><th style="text-align:left">核心指标</th><th style="text-align:left">适用场景</th><th style="text-align:left">局限性</th></tr></thead><tbody><tr><td style="text-align:left"><strong>KNN</strong></td><td style="text-align:left"><strong>绝对距离</strong></td><td style="text-align:left">密度均匀的数据集，简单场景</td><td style="text-align:left">无法处理多密度簇 (闹市区 vs 郊区)</td></tr><tr><td style="text-align:left"><strong>LOF</strong></td><td style="text-align:left"><strong>相对密度比</strong></td><td style="text-align:left"><strong>密度不均</strong>的复杂数据集，检测局部异常</td><td style="text-align:left">计算慢，对近邻数 k 敏感</td></tr><tr><td style="text-align:left"><strong>COF</strong></td><td style="text-align:left">链式距离</td><td style="text-align:left">线性和曲线状的数据 (如血管分布)</td><td style="text-align:left">更慢</td></tr><tr><td style="text-align:left"><strong>LOCI</strong></td><td style="text-align:left">多尺度积分</td><td style="text-align:left">极其复杂的数据，自动选半径</td><td style="text-align:left">极慢 (实际上很少用)</td></tr></tbody></table><hr><h2 id="4-代码实战：检测不均匀簇中的异常"><a href="#4-代码实战：检测不均匀簇中的异常" class="headerlink" title="4. 代码实战：检测不均匀簇中的异常"></a>4. 代码实战：检测不均匀簇中的异常</h2><p>我们将生成一个“闹市区”和一个“郊区”，并在其中安插几个异常点，看看 KNN 和 LOF 的表现。</p><pre class=" language-python"><code class="language-python"><span class="token keyword">import</span> numpy <span class="token keyword">as</span> np<span class="token keyword">import</span> matplotlib<span class="token punctuation">.</span>pyplot <span class="token keyword">as</span> plt<span class="token keyword">from</span> sklearn<span class="token punctuation">.</span>neighbors <span class="token keyword">import</span> LocalOutlierFactor<span class="token punctuation">,</span> NearestNeighbors<span class="token comment" spellcheck="true"># 1. 生成数据：两个密度不同的簇</span>np<span class="token punctuation">.</span>random<span class="token punctuation">.</span>seed<span class="token punctuation">(</span><span class="token number">42</span><span class="token punctuation">)</span><span class="token comment" spellcheck="true"># C1: 闹市区 (密)</span>X_dense <span class="token operator">=</span> np<span class="token punctuation">.</span>random<span class="token punctuation">.</span>normal<span class="token punctuation">(</span><span class="token number">0</span><span class="token punctuation">,</span> <span class="token number">0.2</span><span class="token punctuation">,</span> <span class="token punctuation">(</span><span class="token number">200</span><span class="token punctuation">,</span> <span class="token number">2</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token comment" spellcheck="true"># C2: 郊区 (稀)</span>X_sparse <span class="token operator">=</span> np<span class="token punctuation">.</span>random<span class="token punctuation">.</span>normal<span class="token punctuation">(</span><span class="token number">3</span><span class="token punctuation">,</span> <span class="token number">1.0</span><span class="token punctuation">,</span> <span class="token punctuation">(</span><span class="token number">200</span><span class="token punctuation">,</span> <span class="token number">2</span><span class="token punctuation">)</span><span class="token punctuation">)</span>X <span class="token operator">=</span> np<span class="token punctuation">.</span>r_<span class="token punctuation">[</span>X_dense<span class="token punctuation">,</span> X_sparse<span class="token punctuation">]</span><span class="token comment" spellcheck="true"># 2. 插入异常点</span><span class="token comment" spellcheck="true"># Outlier 1: 在闹市区旁边 (距离 0.8)。对于闹市区来说，这很远。</span><span class="token comment" spellcheck="true"># Outlier 2: 在郊区旁边 (距离 0.8)。对于郊区来说，这很近。</span>X_outliers <span class="token operator">=</span> np<span class="token punctuation">.</span>array<span class="token punctuation">(</span><span class="token punctuation">[</span><span class="token punctuation">[</span><span class="token number">0.8</span><span class="token punctuation">,</span> <span class="token number">0.8</span><span class="token punctuation">]</span><span class="token punctuation">,</span> <span class="token punctuation">[</span><span class="token number">3.8</span><span class="token punctuation">,</span> <span class="token number">3.8</span><span class="token punctuation">]</span><span class="token punctuation">]</span><span class="token punctuation">)</span>X <span class="token operator">=</span> np<span class="token punctuation">.</span>r_<span class="token punctuation">[</span>X<span class="token punctuation">,</span> X_outliers<span class="token punctuation">]</span><span class="token comment" spellcheck="true"># ==========================================</span><span class="token comment" spellcheck="true"># 方法 A: KNN (绝对距离)</span><span class="token comment" spellcheck="true"># ==========================================</span>knn <span class="token operator">=</span> NearestNeighbors<span class="token punctuation">(</span>n_neighbors<span class="token operator">=</span><span class="token number">20</span><span class="token punctuation">)</span>knn<span class="token punctuation">.</span>fit<span class="token punctuation">(</span>X<span class="token punctuation">)</span>distances<span class="token punctuation">,</span> _ <span class="token operator">=</span> knn<span class="token punctuation">.</span>kneighbors<span class="token punctuation">(</span>X<span class="token punctuation">)</span><span class="token comment" spellcheck="true"># 取第 20 个邻居的距离作为得分</span>knn_scores <span class="token operator">=</span> distances<span class="token punctuation">[</span><span class="token punctuation">:</span><span class="token punctuation">,</span> <span class="token operator">-</span><span class="token number">1</span><span class="token punctuation">]</span><span class="token comment" spellcheck="true"># ==========================================</span><span class="token comment" spellcheck="true"># 方法 B: LOF (相对密度)</span><span class="token comment" spellcheck="true"># ==========================================</span>lof <span class="token operator">=</span> LocalOutlierFactor<span class="token punctuation">(</span>n_neighbors<span class="token operator">=</span><span class="token number">20</span><span class="token punctuation">,</span> contamination<span class="token operator">=</span><span class="token string">'auto'</span><span class="token punctuation">)</span><span class="token comment" spellcheck="true"># sklearn 返回的是负的 LOF，越接近 -1 越正常，越小越异常</span>lof<span class="token punctuation">.</span>fit<span class="token punctuation">(</span>X<span class="token punctuation">)</span>lof_scores <span class="token operator">=</span> <span class="token operator">-</span>lof<span class="token punctuation">.</span>negative_outlier_factor_ <span class="token comment" spellcheck="true"># 3. 绘图对比</span>fig<span class="token punctuation">,</span> <span class="token punctuation">(</span>ax1<span class="token punctuation">,</span> ax2<span class="token punctuation">)</span> <span class="token operator">=</span> plt<span class="token punctuation">.</span>subplots<span class="token punctuation">(</span><span class="token number">1</span><span class="token punctuation">,</span> <span class="token number">2</span><span class="token punctuation">,</span> figsize<span class="token operator">=</span><span class="token punctuation">(</span><span class="token number">15</span><span class="token punctuation">,</span> <span class="token number">6</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token comment" spellcheck="true"># KNN 结果</span>scatter1 <span class="token operator">=</span> ax1<span class="token punctuation">.</span>scatter<span class="token punctuation">(</span>X<span class="token punctuation">[</span><span class="token punctuation">:</span><span class="token punctuation">,</span> <span class="token number">0</span><span class="token punctuation">]</span><span class="token punctuation">,</span> X<span class="token punctuation">[</span><span class="token punctuation">:</span><span class="token punctuation">,</span> <span class="token number">1</span><span class="token punctuation">]</span><span class="token punctuation">,</span> c<span class="token operator">=</span>knn_scores<span class="token punctuation">,</span> cmap<span class="token operator">=</span><span class="token string">'Reds'</span><span class="token punctuation">,</span> s<span class="token operator">=</span><span class="token number">50</span><span class="token punctuation">,</span> edgecolor<span class="token operator">=</span><span class="token string">'k'</span><span class="token punctuation">)</span>ax1<span class="token punctuation">.</span>set_title<span class="token punctuation">(</span><span class="token string">"KNN Scores (Absolute Distance)"</span><span class="token punctuation">)</span>plt<span class="token punctuation">.</span>colorbar<span class="token punctuation">(</span>scatter1<span class="token punctuation">,</span> ax<span class="token operator">=</span>ax1<span class="token punctuation">,</span> label<span class="token operator">=</span><span class="token string">'Distance to 20th Neighbor'</span><span class="token punctuation">)</span><span class="token comment" spellcheck="true"># 你会发现：郊区的正常点得分都很高（被误判为异常），而闹市区的异常点得分很低（被漏判）。</span><span class="token comment" spellcheck="true"># LOF 结果</span>scatter2 <span class="token operator">=</span> ax2<span class="token punctuation">.</span>scatter<span class="token punctuation">(</span>X<span class="token punctuation">[</span><span class="token punctuation">:</span><span class="token punctuation">,</span> <span class="token number">0</span><span class="token punctuation">]</span><span class="token punctuation">,</span> X<span class="token punctuation">[</span><span class="token punctuation">:</span><span class="token punctuation">,</span> <span class="token number">1</span><span class="token punctuation">]</span><span class="token punctuation">,</span> c<span class="token operator">=</span>lof_scores<span class="token punctuation">,</span> cmap<span class="token operator">=</span><span class="token string">'Reds'</span><span class="token punctuation">,</span> s<span class="token operator">=</span><span class="token number">50</span><span class="token punctuation">,</span> edgecolor<span class="token operator">=</span><span class="token string">'k'</span><span class="token punctuation">)</span>ax2<span class="token punctuation">.</span>set_title<span class="token punctuation">(</span><span class="token string">"LOF Scores (Relative Density)"</span><span class="token punctuation">)</span>plt<span class="token punctuation">.</span>colorbar<span class="token punctuation">(</span>scatter2<span class="token punctuation">,</span> ax<span class="token operator">=</span>ax2<span class="token punctuation">,</span> label<span class="token operator">=</span><span class="token string">'LOF Score'</span><span class="token punctuation">)</span><span class="token comment" spellcheck="true"># 你会发现：LOF 准确地把两个异常点都标红了，且忽略了郊区和闹市区的密度差异。</span>plt<span class="token punctuation">.</span>show<span class="token punctuation">(</span><span class="token punctuation">)</span></code></pre><hr><h2 id="5-实践要点"><a href="#5-实践要点" class="headerlink" title="5. 实践要点"></a>5. 实践要点</h2><ol><li><strong>参数 k 的选择</strong>：<code>n_neighbors</code> 至关重要。<ul><li><strong>太小</strong>：容易受随机噪声干扰，把几个偶然凑在一起的噪声当成正常簇。</li><li><strong>太大</strong>：会跨越多个簇，导致局部密度估计不准。</li><li><strong>经验</strong>：通常建议设为 20。或者设为 <code>MinPts</code>（你认为一个簇最少有多少人）。</li></ul></li><li><strong>高维失效</strong>：无论是 KNN 还是 LOF，都依赖<strong>欧氏距离</strong>计算。在 1536 维空间，距离失效，这些算法效果会大打折扣。<ul><li><strong>对策</strong>：<strong>务必先降维</strong>（PCA 到 20-50 维），然后再跑 LOF。</li></ul></li><li><strong>计算瓶颈</strong>：LOF 需要计算所有点的 k-近邻，复杂度 $O(N^2)$。<ul><li>对于 10 万数据，可能要跑几分钟。</li><li>对于 100 万数据，可能要跑几天。</li><li><strong>对策</strong>：如果数据量太大，请看下一章的 <strong>iForest</strong>。</li></ul></li></ol><hr><h2 id="下一章预告"><a href="#下一章预告" class="headerlink" title="下一章预告"></a>下一章预告</h2><p>如果数据量达到百万级，算不动距离怎么办？<br>有没有一种算法，不需要计算昂贵的 $O(N^2)$ 距离矩阵，像切西瓜一样随便切几刀，就能把异常点找出来？<br>这就是大数据时代的神器——<strong>隔离森林 (Isolation Forest)</strong>。</p><p>👉 <a href="https://yeee.wang/posts/584b.html">第 16 章：模型驱动异常检测</a></p>]]></content>
    
    <summary type="html">
    
      第 15 章：基于距离与密度的异常检测 (Distance &amp; Density-based Anomaly Detection)
“离群并不意味着你是错的，只意味着你是孤独的。”

上一章的统计方法（Z-Score）假设数据服从某种分布（如正态分布）。但在现实世界中，数据的形状可能千奇百怪（如双螺旋、甜甜圈、不规则的多簇）。
这时候，任何假设分布的模型都会失效。我们需要回归几何直觉：
异常点就是那些离大家都远的点。

本章将介绍两类不依赖分布假设的算法：KNN（看距离）和 LOF（看密度）。




1. KNN 异常检测：简单的力量
KNN (K-Nearest Neighbors) 不仅可
    
    </summary>
    
      <category term="算法" scheme="https://yeee.wang/categories/%E7%AE%97%E6%B3%95/"/>
    
    
      <category term="算法" scheme="https://yeee.wang/tags/%E7%AE%97%E6%B3%95/"/>
    
      <category term="数据分析" scheme="https://yeee.wang/tags/%E6%95%B0%E6%8D%AE%E5%88%86%E6%9E%90/"/>
    
      <category term="机器学习" scheme="https://yeee.wang/tags/%E6%9C%BA%E5%99%A8%E5%AD%A6%E4%B9%A0/"/>
    
  </entry>
  
  <entry>
    <title>第 14 章：统计异常检测</title>
    <link href="https://yeee.wang/posts/9ca6.html"/>
    <id>https://yeee.wang/posts/9ca6.html</id>
    <published>2025-12-22T10:05:00.000Z</published>
    <updated>2026-02-19T20:00:41.880Z</updated>
    
    <content type="html"><![CDATA[<h1 id="第-14-章：统计异常检测-Statistical-Anomaly-Detection"><a href="#第-14-章：统计异常检测-Statistical-Anomaly-Detection" class="headerlink" title="第 14 章：统计异常检测 (Statistical Anomaly Detection)"></a>第 14 章：统计异常检测 (Statistical Anomaly Detection)</h1><blockquote><p>“虽然每片雪花都是独一无二的，但有些雪花实在是太独特了。”</p></blockquote><p>在文本分析项目中，我们的目标不仅是聚类（发现常态），更是<strong>风险挖掘</strong>。<br>什么是风险？风险通常意味着“异常”。</p><ul><li>大部分工单都在说“快递慢”（常态）。</li><li>突然有一条工单说“快递员在门口放火”（异常）。</li></ul><p>如何用数学定义“异常”？最古老也最经久不衰的方法是<strong>统计学</strong>。<br>统计学告诉我们：<strong>“正常”就是大多数，“异常”就是极少数。</strong></p><p><img src="https://img.alicdn.com/imgextra/i2/O1CN01IIOyLq1b9mqWCkpOH_!!6000000003423-2-tps-1376-768.png" alt="统计异常检测原理"><br><em>(图注：在正态分布中，落在 3σ 之外的区域就是异常区。)</em></p><hr><h2 id="1-核心概念：你离平均值有多远？"><a href="#1-核心概念：你离平均值有多远？" class="headerlink" title="1. 核心概念：你离平均值有多远？"></a>1. 核心概念：你离平均值有多远？</h2><h3 id="1-1-正态分布假设-The-Normal-Assumption"><a href="#1-1-正态分布假设-The-Normal-Assumption" class="headerlink" title="1.1 正态分布假设 (The Normal Assumption)"></a>1.1 正态分布假设 (The Normal Assumption)</h3><p>统计方法的核心假设是：<strong>正常数据服从某种分布（通常是正态分布），而异常数据是小概率事件。</strong></p><p>想象一下人类的身高：</p><ul><li>平均身高 $\mu = 170cm$。</li><li>标准差 $\sigma = 5cm$。</li><li><strong>68%</strong> 的人在 $170 \pm 5$ 之间。</li><li><strong>99.7%</strong> 的人在 $170 \pm 15$ (155-185) 之间。</li><li>如果你身高 2.3米，你落在了 $3\sigma$ 之外。恭喜你，你是那是 $0.3\%$ 的异类。</li></ul><h3 id="1-2-Z-Score-标准分数-——-最通用的尺子"><a href="#1-2-Z-Score-标准分数-——-最通用的尺子" class="headerlink" title="1.2 Z-Score (标准分数) —— 最通用的尺子"></a>1.2 Z-Score (标准分数) —— 最通用的尺子</h3><p>不同数据的单位不一样（身高是 cm，存款是元），怎么比？<br>我们要把它们都换算成<strong>“距离平均值有几个标准差”</strong>。这就是 <strong>Z-Score</strong>。</p><p>$$ Z = \frac{x - \mu}{\sigma} $$</p><ul><li><strong>Z = 0</strong>：你就是平均人。</li><li><strong>Z = 1</strong>：你比平均水平高一点点。</li><li><strong>Z &gt; 3</strong>：<strong>疑似异常</strong>。你有点离谱了。</li><li><strong>Z &gt; 5</strong>：<strong>几乎肯定是异常</strong>。你太离谱了。</li></ul><h3 id="1-3-致命缺陷：掩蔽效应-Masking-Effect"><a href="#1-3-致命缺陷：掩蔽效应-Masking-Effect" class="headerlink" title="1.3 致命缺陷：掩蔽效应 (Masking Effect)"></a>1.3 致命缺陷：掩蔽效应 (Masking Effect)</h3><p>Z-Score 有个死穴：<strong>均值 $\mu$ 和方差 $\sigma$ 本身非常容易受异常值影响。</strong></p><ul><li><strong>场景</strong>：班里有 9 个穷光蛋（资产 0）和 1 个马斯克（资产 1000 亿）。</li><li><strong>结果</strong>：<ul><li>均值 $\mu$ 被拉到了 100 亿。</li><li>方差 $\sigma$ 被撑得巨大。</li><li>算一下穷光蛋的 Z-Score：$(0 - 100) / \text{巨大} \approx 0$。</li><li>算一下马斯克的 Z-Score：$(1000 - 100) / \text{巨大} \approx \text{很小}$。</li></ul></li><li><strong>结论</strong>：因为马斯克的存在，大家都变得“正常”了。异常值把自己<strong>掩护</strong>起来了。</li></ul><p><img src="https://img.alicdn.com/imgextra/i1/O1CN01flCsgL1SzhC3GEICv_!!6000000002318-2-tps-1376-768.png" alt="Z-Score vs IQR"><br><em>(图注：Z-Score 受极值污染，IQR 基于中位数更鲁棒。)</em></p><hr><h2 id="2-进阶技术：鲁棒统计-Robust-Statistics"><a href="#2-进阶技术：鲁棒统计-Robust-Statistics" class="headerlink" title="2. 进阶技术：鲁棒统计 (Robust Statistics)"></a>2. 进阶技术：鲁棒统计 (Robust Statistics)</h2><p>为了解决掩蔽效应，我们需要更硬核的统计量。我们需要<strong>不受马斯克影响</strong>的指标。</p><h3 id="2-1-IQR-四分位距-——-中位数的智慧"><a href="#2-1-IQR-四分位距-——-中位数的智慧" class="headerlink" title="2.1 IQR (四分位距) —— 中位数的智慧"></a>2.1 IQR (四分位距) —— 中位数的智慧</h3><p>不再看均值（Mean），而是看<strong>中位数 (Median)</strong> 和<strong>分位数 (Quantile)</strong>。</p><ul><li><strong>Q1</strong>：第 25% 的人。</li><li><strong>Q3</strong>：第 75% 的人。</li><li><strong>IQR</strong>：$Q3 - Q1$。代表了中间 50% 大众的贫富差距。</li><li><strong>异常判定</strong>：<ul><li>上限：$Q3 + 1.5 \times IQR$</li><li>下限：$Q1 - 1.5 \times IQR$</li></ul></li></ul><p><strong>为什么它鲁棒？</strong><br>哪怕班里混进了 10 个马斯克，中位数（第 50 名）依然是那个普通人。Q1 和 Q3 也几乎不变。<br>所以 IQR 能稳准狠地把马斯克抓出来。</p><h3 id="2-2-Grubbs-检验"><a href="#2-2-Grubbs-检验" class="headerlink" title="2.2 Grubbs 检验"></a>2.2 Grubbs 检验</h3><p>专门用于检验“只有一个异常值”的假设检验方法。</p><ul><li>它假设数据服从正态分布。</li><li>它通过计算最大值与均值的偏差，看这个偏差是否大到“不科学”。</li><li><em>适用场景</em>：小样本数据，逐个剔除异常。</li></ul><hr><h2 id="3-多变量统计：马氏距离-Mahalanobis-Distance"><a href="#3-多变量统计：马氏距离-Mahalanobis-Distance" class="headerlink" title="3. 多变量统计：马氏距离 (Mahalanobis Distance)"></a>3. 多变量统计：马氏距离 (Mahalanobis Distance)</h2><p>如果是多维数据（比如身高和体重），单纯看每一维的 Z-Score 是不够的。</p><ul><li><strong>案例</strong>：<ul><li>身高 190cm：在人群中属于正常偏高（Z=2）。</li><li>体重 50kg：在人群中属于正常偏瘦（Z=-2）。</li><li><strong>但是</strong>，身高 190cm <strong>且</strong> 体重 50kg：<strong>极度异常</strong>（竹竿）。</li></ul></li></ul><p>这种异常，单独看 X 或 Y 都看不出来，必须看<strong>相关性</strong>。<br><strong>马氏距离</strong>就是为此而生的。它本质上是<strong>消除了相关性干扰的欧氏距离</strong>。</p><p>$$ D_M(x) = \sqrt{(x - \mu)^T \Sigma^{-1} (x - \mu)} $$</p><ul><li>$\Sigma^{-1}$：协方差矩阵的逆。它的作用是：如果两个特征高度相关（身高体重），就给它们<strong>降权</strong>；如果两个特征不相关，就保持原权。</li></ul><p><img src="https://img.alicdn.com/imgextra/i3/O1CN01HpMz7t24yHMq74OV3_!!6000000007459-2-tps-1376-768.png" alt="马氏距离示意图"><br><em>(图注：红色点在 X 和 Y轴投影上都在正常范围内（盒子内），但在联合分布中，它显然偏离了主群体（椭圆）。)</em></p><hr><h2 id="4-代码实战：Z-Score-vs-IQR"><a href="#4-代码实战：Z-Score-vs-IQR" class="headerlink" title="4. 代码实战：Z-Score vs IQR"></a>4. 代码实战：Z-Score vs IQR</h2><p>我们来模拟一个“马斯克混入班级”的场景，看看两种方法的表现。</p><pre class=" language-python"><code class="language-python"><span class="token keyword">import</span> numpy <span class="token keyword">as</span> np<span class="token keyword">import</span> matplotlib<span class="token punctuation">.</span>pyplot <span class="token keyword">as</span> plt<span class="token comment" spellcheck="true"># 1. 生成数据：100 个正常点 + 5 个极端异常点</span>np<span class="token punctuation">.</span>random<span class="token punctuation">.</span>seed<span class="token punctuation">(</span><span class="token number">42</span><span class="token punctuation">)</span>normal_data <span class="token operator">=</span> np<span class="token punctuation">.</span>random<span class="token punctuation">.</span>normal<span class="token punctuation">(</span><span class="token number">0</span><span class="token punctuation">,</span> <span class="token number">1</span><span class="token punctuation">,</span> <span class="token number">100</span><span class="token punctuation">)</span> <span class="token comment" spellcheck="true"># 均值0，方差1</span>outliers <span class="token operator">=</span> np<span class="token punctuation">.</span>array<span class="token punctuation">(</span><span class="token punctuation">[</span><span class="token number">10</span><span class="token punctuation">,</span> <span class="token number">12</span><span class="token punctuation">,</span> <span class="token number">15</span><span class="token punctuation">,</span> <span class="token operator">-</span><span class="token number">10</span><span class="token punctuation">,</span> <span class="token number">20</span><span class="token punctuation">]</span><span class="token punctuation">)</span> <span class="token comment" spellcheck="true"># 极端的异常值</span>data <span class="token operator">=</span> np<span class="token punctuation">.</span>concatenate<span class="token punctuation">(</span><span class="token punctuation">[</span>normal_data<span class="token punctuation">,</span> outliers<span class="token punctuation">]</span><span class="token punctuation">)</span><span class="token comment" spellcheck="true"># ==========================================</span><span class="token comment" spellcheck="true"># 方法 A: Z-Score (传统方法)</span><span class="token comment" spellcheck="true"># ==========================================</span>mean <span class="token operator">=</span> np<span class="token punctuation">.</span>mean<span class="token punctuation">(</span>data<span class="token punctuation">)</span>std <span class="token operator">=</span> np<span class="token punctuation">.</span>std<span class="token punctuation">(</span>data<span class="token punctuation">)</span>z_scores <span class="token operator">=</span> np<span class="token punctuation">.</span>abs<span class="token punctuation">(</span><span class="token punctuation">(</span>data <span class="token operator">-</span> mean<span class="token punctuation">)</span> <span class="token operator">/</span> std<span class="token punctuation">)</span><span class="token comment" spellcheck="true"># 阈值通常设为 3</span>z_outliers <span class="token operator">=</span> data<span class="token punctuation">[</span>z_scores <span class="token operator">></span> <span class="token number">3</span><span class="token punctuation">]</span><span class="token keyword">print</span><span class="token punctuation">(</span>f<span class="token string">"真实均值: 0, 受污染均值: &amp;#123;mean:.2f&amp;#125;"</span><span class="token punctuation">)</span><span class="token keyword">print</span><span class="token punctuation">(</span>f<span class="token string">"真实方差: 1, 受污染方差: &amp;#123;std:.2f&amp;#125;"</span><span class="token punctuation">)</span><span class="token keyword">print</span><span class="token punctuation">(</span>f<span class="token string">"Z-Score 抓到了 &amp;#123;len(z_outliers)&amp;#125; 个异常点 (漏网之鱼!)"</span><span class="token punctuation">)</span><span class="token comment" spellcheck="true"># ==========================================</span><span class="token comment" spellcheck="true"># 方法 B: IQR (鲁棒方法)</span><span class="token comment" spellcheck="true"># ==========================================</span>q1 <span class="token operator">=</span> np<span class="token punctuation">.</span>percentile<span class="token punctuation">(</span>data<span class="token punctuation">,</span> <span class="token number">25</span><span class="token punctuation">)</span>q3 <span class="token operator">=</span> np<span class="token punctuation">.</span>percentile<span class="token punctuation">(</span>data<span class="token punctuation">,</span> <span class="token number">75</span><span class="token punctuation">)</span>iqr <span class="token operator">=</span> q3 <span class="token operator">-</span> q1<span class="token comment" spellcheck="true"># 判定范围</span>lower_bound <span class="token operator">=</span> q1 <span class="token operator">-</span> <span class="token number">1.5</span> <span class="token operator">*</span> iqrupper_bound <span class="token operator">=</span> q3 <span class="token operator">+</span> <span class="token number">1.5</span> <span class="token operator">*</span> iqriqr_outliers <span class="token operator">=</span> data<span class="token punctuation">[</span><span class="token punctuation">(</span>data <span class="token operator">></span> upper_bound<span class="token punctuation">)</span> <span class="token operator">|</span> <span class="token punctuation">(</span>data <span class="token operator">&lt;</span> lower_bound<span class="token punctuation">)</span><span class="token punctuation">]</span><span class="token keyword">print</span><span class="token punctuation">(</span>f<span class="token string">"IQR 抓到了 &amp;#123;len(iqr_outliers)&amp;#125; 个异常点 (全部抓住!)"</span><span class="token punctuation">)</span><span class="token comment" spellcheck="true"># 绘图对比</span>plt<span class="token punctuation">.</span>figure<span class="token punctuation">(</span>figsize<span class="token operator">=</span><span class="token punctuation">(</span><span class="token number">10</span><span class="token punctuation">,</span> <span class="token number">4</span><span class="token punctuation">)</span><span class="token punctuation">)</span>plt<span class="token punctuation">.</span>plot<span class="token punctuation">(</span>data<span class="token punctuation">,</span> <span class="token string">'o'</span><span class="token punctuation">,</span> label<span class="token operator">=</span><span class="token string">'Data'</span><span class="token punctuation">)</span>plt<span class="token punctuation">.</span>axhline<span class="token punctuation">(</span>mean <span class="token operator">+</span> <span class="token number">3</span><span class="token operator">*</span>std<span class="token punctuation">,</span> color<span class="token operator">=</span><span class="token string">'r'</span><span class="token punctuation">,</span> linestyle<span class="token operator">=</span><span class="token string">'--'</span><span class="token punctuation">,</span> label<span class="token operator">=</span><span class="token string">'Z-Score Threshold'</span><span class="token punctuation">)</span>plt<span class="token punctuation">.</span>axhline<span class="token punctuation">(</span>upper_bound<span class="token punctuation">,</span> color<span class="token operator">=</span><span class="token string">'g'</span><span class="token punctuation">,</span> linestyle<span class="token operator">=</span><span class="token string">'--'</span><span class="token punctuation">,</span> label<span class="token operator">=</span><span class="token string">'IQR Threshold'</span><span class="token punctuation">)</span>plt<span class="token punctuation">.</span>legend<span class="token punctuation">(</span><span class="token punctuation">)</span>plt<span class="token punctuation">.</span>title<span class="token punctuation">(</span><span class="token string">"Z-Score vs IQR"</span><span class="token punctuation">)</span>plt<span class="token punctuation">.</span>show<span class="token punctuation">(</span><span class="token punctuation">)</span></code></pre><hr><h2 id="5-实践要点"><a href="#5-实践要点" class="headerlink" title="5. 实践要点"></a>5. 实践要点</h2><ol><li><strong>数据正态性</strong>：Z-Score 假设数据是正态的。<ul><li><strong>陷阱</strong>：电商销量、点赞数通常是<strong>长尾分布</strong>（Power Law）。如果直接算 Z-Score，会把所有头部爆款（Top 1%）都当成异常杀掉。</li><li><strong>对策</strong>：先对数据取 <code>log1p</code> (对数变换)，把它拉成近似正态，再算 Z-Score。</li></ul></li></ol><p><img src="https://img.alicdn.com/imgextra/i2/O1CN01KCSU6G1QtAJYaKgXv_!!6000000002033-2-tps-1376-768.png" alt="长尾数据变换"><br><em>(图注：长尾分布通过 log1p 变换后接近正态，Z-Score 才能正常工作。)</em></p><ol start="2"><li><strong>高维失效</strong>：在 1536 维的文本 Embedding 空间里，马氏距离需要计算 $1536 \times 1536$ 的协方差矩阵逆矩阵。<ul><li><strong>后果</strong>：计算极慢，且极其不稳定（矩阵不可逆）。</li><li><strong>结论</strong>：高维异常检测通常不走统计路线，而是走下一章的<strong>距离/密度路线</strong>。</li></ul></li></ol><hr><h2 id="下一章预告"><a href="#下一章预告" class="headerlink" title="下一章预告"></a>下一章预告</h2><p>统计方法假设数据服从某种分布（如正态分布）。<br>如果数据分布极其复杂（比如是个双螺旋结构，或者是几个形状各异的圈），统计方法就废了。<br>这时候，我们需要回归最朴素的几何直觉：<strong>离群就是离邻居远。</strong></p><p>👉 <a href="https://yeee.wang/posts/bbf1.html">第 15 章：基于距离与密度的异常检测</a></p>]]></content>
    
    <summary type="html">
    
      第 14 章：统计异常检测 (Statistical Anomaly Detection)
“虽然每片雪花都是独一无二的，但有些雪花实在是太独特了。”

在文本分析项目中，我们的目标不仅是聚类（发现常态），更是风险挖掘。
什么是风险？风险通常意味着“异常”。

 * 大部分工单都在说“快递慢”（常态）。
 * 突然有一条工单说“快递员在门口放火”（异常）。

如何用数学定义“异常”？最古老也最经久不衰的方法是统计学。
统计学告诉我们：“正常”就是大多数，“异常”就是极少数。


(图注：在正态分布中，落在 3σ 之外的区域就是异常区。)




1. 核心概念：你离平均值有多远？
1.1 正态
    
    </summary>
    
      <category term="算法" scheme="https://yeee.wang/categories/%E7%AE%97%E6%B3%95/"/>
    
    
      <category term="算法" scheme="https://yeee.wang/tags/%E7%AE%97%E6%B3%95/"/>
    
      <category term="数据分析" scheme="https://yeee.wang/tags/%E6%95%B0%E6%8D%AE%E5%88%86%E6%9E%90/"/>
    
      <category term="机器学习" scheme="https://yeee.wang/tags/%E6%9C%BA%E5%99%A8%E5%AD%A6%E4%B9%A0/"/>
    
  </entry>
  
  <entry>
    <title>第 13 章：UMAP原理与实践</title>
    <link href="https://yeee.wang/posts/b3b7.html"/>
    <id>https://yeee.wang/posts/b3b7.html</id>
    <published>2025-12-22T10:00:00.000Z</published>
    <updated>2026-02-19T20:00:41.880Z</updated>
    
    <content type="html"><![CDATA[<h1 id="第-13-章：UMAP-原理与实践-Uniform-Manifold-Approximation-and-Projection"><a href="#第-13-章：UMAP-原理与实践-Uniform-Manifold-Approximation-and-Projection" class="headerlink" title="第 13 章：UMAP 原理与实践 (Uniform Manifold Approximation and Projection)"></a>第 13 章：UMAP 原理与实践 (Uniform Manifold Approximation and Projection)</h1><blockquote><p>“数学家在咖啡杯和甜甜圈之间看到了同伦，UMAP 在高维数据和二维流形之间看到了同构。”</p></blockquote><p>t-SNE 统治了数据可视化界很多年，直到 2018 年 McInnes 等人提出了 <strong>UMAP</strong>。<br>UMAP (Uniform Manifold Approximation and Projection) 建立在深奥的<strong>代数拓扑</strong>和<strong>黎曼几何</strong>理论之上，但它的效果却是立竿见影的：<br><strong>它比 t-SNE 快得多，且保留了更多的全局结构。</strong></p><p>在实际的文本分析项目中（如 BERT/OpenAI Embedding），UMAP 已经逐渐成为<strong>首选</strong>的降维引擎，正是看中了它在速度和结构保持上的平衡能力。</p><p><img src="https://img.alicdn.com/imgextra/i4/O1CN01s913VH1oRGyJxAJNw_!!6000000005221-2-tps-1376-768.png" alt="UMAP 拓扑骨架示意图"><br><em>(图注：UMAP 像搭建脚手架一样构建数据的拓扑骨架，然后把它压扁到二维平面。)</em></p><hr><h2 id="1-核心概念：模糊单纯复形-Fuzzy-Simplicial-Complex"><a href="#1-核心概念：模糊单纯复形-Fuzzy-Simplicial-Complex" class="headerlink" title="1. 核心概念：模糊单纯复形 (Fuzzy Simplicial Complex)"></a>1. 核心概念：模糊单纯复形 (Fuzzy Simplicial Complex)</h2><p>别被这个吓人的名字劝退。我们用人话讲一遍它的核心假设。</p><h3 id="1-1-黎曼几何假设：距离是相对的"><a href="#1-1-黎曼几何假设：距离是相对的" class="headerlink" title="1.1 黎曼几何假设：距离是相对的"></a>1.1 黎曼几何假设：距离是相对的</h3><p>UMAP 假设数据均匀分布在一个<strong>黎曼流形</strong>上。<br>什么是黎曼流形？就是一个局部看起来像欧氏空间（平的），但整体可能弯曲的空间。<br>关键点在于：<strong>在这个流形上，距离也是“局部”的。</strong></p><ul><li><strong>对于拥挤的点 A</strong>：周围全是人。那么距离它 1 米的点 B 就在“旁边”。</li><li><strong>对于孤独的点 C</strong>：位于稀疏区域。那么距离它 10 米的点 D 也算“旁边”，因为周围实在没人了。</li></ul><p>UMAP 会根据每个点的<strong>局部密度</strong>，自动伸缩“尺子”的长度。这使得它能同时处理稀疏和稠密的数据区域。</p><h3 id="1-2-算法步骤：脚手架工程"><a href="#1-2-算法步骤：脚手架工程" class="headerlink" title="1.2 算法步骤：脚手架工程"></a>1.2 算法步骤：脚手架工程</h3><ol><li><strong>构建高维骨架 (Topological Representation)</strong>：<ul><li>对于每个点，找到它的 K 个最近邻。</li><li>根据局部密度，赋予每条边一个权重（概率）。密度大的地方，距离单位小；密度小的地方，距离单位大。</li><li>这就建立了一个<strong>加权图</strong>（Fuzzy Simplicial Complex）。这就像给数据搭了一个骨架。</li></ul></li></ol><p><img src="https://img.alicdn.com/imgextra/i2/O1CN016SJxr91z0fk5dpVLs_!!6000000006652-2-tps-1376-768.png" alt="UMAP 脚手架工程"><br><em>(图注：构建邻居图 → 模糊单纯复形 → 低维布局优化。三步完成拓扑保持降维。)</em></p><ol start="2"><li><strong>低维布局 (Optimization)</strong>：<ul><li>在 2D 空间初始化一些点（通常用谱嵌入 Spectral Embedding，这很重要，保留了全局结构）。</li><li>使用 <strong>Cross-Entropy（交叉熵）</strong> 作为损失函数，强迫 2D 点的拓扑结构尽可能接近高维骨架。</li><li>使用随机梯度下降 (SGD) 优化位置。</li></ul></li></ol><hr><h2 id="2-技术对比：t-SNE-vs-UMAP"><a href="#2-技术对比：t-SNE-vs-UMAP" class="headerlink" title="2. 技术对比：t-SNE vs UMAP"></a>2. 技术对比：t-SNE vs UMAP</h2><table><thead><tr><th style="text-align:left">特性</th><th style="text-align:left">t-SNE</th><th style="text-align:left">UMAP</th><th style="text-align:left">胜出者</th></tr></thead><tbody><tr><td style="text-align:left"><strong>理论基础</strong></td><td style="text-align:left">概率分布 (KL散度)</td><td style="text-align:left">代数拓扑 + 黎曼几何</td><td style="text-align:left">UMAP (更坚实)</td></tr><tr><td style="text-align:left"><strong>全局结构</strong></td><td style="text-align:left">丢失 (簇间距离无意义)</td><td style="text-align:left"><strong>保留较好</strong> (簇间距离有意义)</td><td style="text-align:left"><strong>UMAP</strong></td></tr><tr><td style="text-align:left"><strong>计算速度</strong></td><td style="text-align:left">慢 (尽管有加速)</td><td style="text-align:left"><strong>快</strong> (快 10-100 倍)</td><td style="text-align:left"><strong>UMAP</strong></td></tr><tr><td style="text-align:left"><strong>初始化</strong></td><td style="text-align:left">随机 / PCA</td><td style="text-align:left"><strong>谱嵌入 (Spectral)</strong></td><td style="text-align:left">UMAP (确定性更好)</td></tr><tr><td style="text-align:left"><strong>可扩展性</strong></td><td style="text-align:left">只能可视化 (2D/3D)</td><td style="text-align:left"><strong>任意维度</strong> (可用于聚类前置)</td><td style="text-align:left"><strong>UMAP</strong></td></tr><tr><td style="text-align:left"><strong>新数据</strong></td><td style="text-align:left">不支持 Transform</td><td style="text-align:left"><strong>支持 Transform</strong></td><td style="text-align:left"><strong>UMAP</strong> (杀手锏)</td></tr></tbody></table><p><strong>为什么 UMAP 能保留全局结构？</strong></p><ol><li><strong>初始化</strong>：UMAP 使用谱嵌入初始化，这本身就是一种保留全局结构的降维方法。</li><li><strong>损失函数</strong>：t-SNE 的 KL 散度只惩罚“该近的不近”，不太惩罚“该远的不远”。而 UMAP 的交叉熵损失对两者都有惩罚，迫使原本离得远的簇在低维空间也得保持距离。</li></ol><p><img src="https://img.alicdn.com/imgextra/i1/O1CN01Rq6PHh1nDFE20qH5g_!!6000000005055-2-tps-1376-768.png" alt="t-SNE vs UMAP 效果对比"><br><em>(图注：UMAP 保留全局结构、速度更快、支持新数据 Transform，综合表现优于 t-SNE。)</em></p><hr><h2 id="3-代码实战：UMAP-的完整流程"><a href="#3-代码实战：UMAP-的完整流程" class="headerlink" title="3. 代码实战：UMAP 的完整流程"></a>3. 代码实战：UMAP 的完整流程</h2><p>在文本分析项目中，UMAP 通常是整个分析链路的枢纽：<code>Embedding -&gt; UMAP -&gt; HDBSCAN</code>。</p><pre class=" language-python"><code class="language-python"><span class="token keyword">import</span> umap<span class="token keyword">import</span> numpy <span class="token keyword">as</span> np<span class="token keyword">import</span> matplotlib<span class="token punctuation">.</span>pyplot <span class="token keyword">as</span> plt<span class="token keyword">from</span> sklearn<span class="token punctuation">.</span>datasets <span class="token keyword">import</span> load_digits<span class="token comment" spellcheck="true"># 1. 准备数据</span>digits <span class="token operator">=</span> load_digits<span class="token punctuation">(</span><span class="token punctuation">)</span>embeddings <span class="token operator">=</span> digits<span class="token punctuation">.</span>data  <span class="token comment" spellcheck="true"># 64 维</span><span class="token comment" spellcheck="true"># 2. UMAP 降维</span><span class="token comment" spellcheck="true"># n_components=2: 降到 2 维用于画图</span><span class="token comment" spellcheck="true"># n_neighbors=15: 关注周围 15 个邻居（平衡点）</span><span class="token comment" spellcheck="true"># min_dist=0.1: 点之间至少隔 0.1（防止重叠）</span><span class="token comment" spellcheck="true"># metric='euclidean': 图像数据用欧氏距离；如果是文本 Embedding，请务必用 'cosine'</span>reducer <span class="token operator">=</span> umap<span class="token punctuation">.</span>UMAP<span class="token punctuation">(</span>    n_components<span class="token operator">=</span><span class="token number">2</span><span class="token punctuation">,</span>    n_neighbors<span class="token operator">=</span><span class="token number">15</span><span class="token punctuation">,</span>    min_dist<span class="token operator">=</span><span class="token number">0.1</span><span class="token punctuation">,</span>    metric<span class="token operator">=</span><span class="token string">'euclidean'</span><span class="token punctuation">,</span>    random_state<span class="token operator">=</span><span class="token number">42</span><span class="token punctuation">)</span>coords_2d <span class="token operator">=</span> reducer<span class="token punctuation">.</span>fit_transform<span class="token punctuation">(</span>embeddings<span class="token punctuation">)</span><span class="token comment" spellcheck="true"># 3. 绘图</span>plt<span class="token punctuation">.</span>scatter<span class="token punctuation">(</span>coords_2d<span class="token punctuation">[</span><span class="token punctuation">:</span><span class="token punctuation">,</span> <span class="token number">0</span><span class="token punctuation">]</span><span class="token punctuation">,</span> coords_2d<span class="token punctuation">[</span><span class="token punctuation">:</span><span class="token punctuation">,</span> <span class="token number">1</span><span class="token punctuation">]</span><span class="token punctuation">,</span> c<span class="token operator">=</span>digits<span class="token punctuation">.</span>target<span class="token punctuation">,</span> cmap<span class="token operator">=</span><span class="token string">'tab10'</span><span class="token punctuation">,</span> s<span class="token operator">=</span><span class="token number">5</span><span class="token punctuation">)</span>plt<span class="token punctuation">.</span>title<span class="token punctuation">(</span><span class="token string">'UMAP projection of Digits'</span><span class="token punctuation">)</span>plt<span class="token punctuation">.</span>show<span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token comment" spellcheck="true"># 4. 杀手锏：处理新数据</span><span class="token comment" spellcheck="true"># t-SNE 做不到这点！但在生产环境中，我们需要对新来的用户工单进行降维</span>new_data <span class="token operator">=</span> np<span class="token punctuation">.</span>random<span class="token punctuation">.</span>rand<span class="token punctuation">(</span><span class="token number">1</span><span class="token punctuation">,</span> <span class="token number">64</span><span class="token punctuation">)</span>new_coord <span class="token operator">=</span> reducer<span class="token punctuation">.</span>transform<span class="token punctuation">(</span>new_data<span class="token punctuation">)</span><span class="token keyword">print</span><span class="token punctuation">(</span>f<span class="token string">"新数据的坐标: &amp;#123;new_coord&amp;#125;"</span><span class="token punctuation">)</span></code></pre><hr><h2 id="4-关键参数调优指南"><a href="#4-关键参数调优指南" class="headerlink" title="4. 关键参数调优指南"></a>4. 关键参数调优指南</h2><p>UMAP 虽然好用，但也不是完全免调参的。它有两个参数如同太极的阴阳：</p><h3 id="4-1-n-neighbors-邻居数-——-平衡-局部-vs-全局"><a href="#4-1-n-neighbors-邻居数-——-平衡-局部-vs-全局" class="headerlink" title="4.1 n_neighbors (邻居数) —— 平衡 局部 vs 全局"></a>4.1 <code>n_neighbors</code> (邻居数) —— 平衡 局部 vs 全局</h3><ul><li><strong>含义</strong>：构建脚手架时，每个点连几根线。</li><li><strong>小 (2-10)</strong>：<strong>微观视角</strong>。关注极局部的细节。图会碎成很多小块，展示数据的精细纹理。</li><li><strong>大 (50-100)</strong>：<strong>宏观视角</strong>。关注全局概貌。图会连成一片，保留大类之间的拓扑关系。</li><li><strong>推荐</strong>：默认值 <strong>15</strong> 是一个很好的折中。</li></ul><h3 id="4-2-min-dist-最小距离-——-平衡-紧凑-vs-均匀"><a href="#4-2-min-dist-最小距离-——-平衡-紧凑-vs-均匀" class="headerlink" title="4.2 min_dist (最小距离) —— 平衡 紧凑 vs 均匀"></a>4.2 <code>min_dist</code> (最小距离) —— 平衡 紧凑 vs 均匀</h3><ul><li><strong>含义</strong>：在低维空间，允许点之间挤得多紧。</li><li><strong>小 (0.0 - 0.1)</strong>：<strong>聚类视角</strong>。点紧紧抱团，簇与簇分界清晰。适合后续接 HDBSCAN 聚类。</li><li><strong>大 (0.5 - 0.99)</strong>：<strong>展示视角</strong>。点散得很开，分布均匀，像一张平铺的地图。适合观察整体分布。</li><li><strong>推荐</strong>：<strong>0.1</strong> 是默认值，适合大多数情况。</li></ul><p><img src="https://img.alicdn.com/imgextra/i1/O1CN01nHDbCB24I8vgDbsPD_!!6000000007367-2-tps-1376-768.png" alt="UMAP 参数调优指南"><br><em>(图注：横轴 min_dist 变大导致点变散；纵轴 n_neighbors 变大导致全局结构更清晰。)</em></p><hr><h2 id="5-实践要点"><a href="#5-实践要点" class="headerlink" title="5. 实践要点"></a>5. 实践要点</h2><ol><li><strong>随机性控制</strong>：UMAP 使用随机梯度下降。为了结果可复现，务必固定 <code>random_state=42</code>。</li><li><strong>不仅是 2D</strong>：UMAP 可以降到任意维度。<ul><li><strong>降维策略</strong>：如果你要做聚类，强烈建议<strong>先用 UMAP 把 1536 维降到 10-50 维</strong>，然后再跑 HDBSCAN。这通常比直接在 1536 维上跑聚类效果好得多，也快得多。</li></ul></li><li><strong>度量选择</strong>：再次强调，对于 BERT/OpenAI 等文本 Embedding 数据，<code>metric=&#39;cosine&#39;</code> 是必须的。默认的 <code>&#39;euclidean&#39;</code> 在高维空间意义不大。</li></ol><hr><h2 id="下一章预告"><a href="#下一章预告" class="headerlink" title="下一章预告"></a>下一章预告</h2><p>至此，我们的“探索”之旅（聚类+降维）告一段落。<br>我们手里已经有了锋利的武器：UMAP 帮我们看清结构，HDBSCAN 帮我们发现群体。</p><p>从下一章开始，我们将进入更具实战意义的领域——<strong>异常检测 (Anomaly Detection)</strong>。<br>如何从数万条看似正常的工单中，揪出那一两条预示着危机的“黑天鹅”？<br>这不是找“大多数”，而是找“极少数”。</p><p>👉 <a href="https://yeee.wang/posts/9ca6.html">第 14 章：统计异常检测</a></p>]]></content>
    
    <summary type="html">
    
      第 13 章：UMAP 原理与实践 (Uniform Manifold Approximation and Projection)
“数学家在咖啡杯和甜甜圈之间看到了同伦，UMAP 在高维数据和二维流形之间看到了同构。”

t-SNE 统治了数据可视化界很多年，直到 2018 年 McInnes 等人提出了 UMAP。
UMAP (Uniform Manifold Approximation and Projection) 建立在深奥的代数拓扑和黎曼几何理论之上，但它的效果却是立竿见影的：
它比 t-SNE 快得多，且保留了更多的全局结构。

在实际的文本分析项目中（如 BERT/OpenAI
    
    </summary>
    
      <category term="算法" scheme="https://yeee.wang/categories/%E7%AE%97%E6%B3%95/"/>
    
    
      <category term="算法" scheme="https://yeee.wang/tags/%E7%AE%97%E6%B3%95/"/>
    
      <category term="数据分析" scheme="https://yeee.wang/tags/%E6%95%B0%E6%8D%AE%E5%88%86%E6%9E%90/"/>
    
      <category term="机器学习" scheme="https://yeee.wang/tags/%E6%9C%BA%E5%99%A8%E5%AD%A6%E4%B9%A0/"/>
    
  </entry>
  
  <entry>
    <title>第 12 章：t-SNE深度解析</title>
    <link href="https://yeee.wang/posts/e4cc.html"/>
    <id>https://yeee.wang/posts/e4cc.html</id>
    <published>2025-12-22T09:55:00.000Z</published>
    <updated>2026-02-19T20:00:41.880Z</updated>
    
    <content type="html"><![CDATA[<h1 id="第-12-章：t-SNE-深度解析-t-Distributed-Stochastic-Neighbor-Embedding"><a href="#第-12-章：t-SNE-深度解析-t-Distributed-Stochastic-Neighbor-Embedding" class="headerlink" title="第 12 章：t-SNE 深度解析 (t-Distributed Stochastic Neighbor Embedding)"></a>第 12 章：t-SNE 深度解析 (t-Distributed Stochastic Neighbor Embedding)</h1><blockquote><p>“在数据可视化的世界里，t-SNE 就是那个把乱麻理顺的魔术师。”</p></blockquote><p>如果你在 Kaggle 上看过别人的 Kernel，你一定见过那种把 MNIST 手写数字完美分成 10 堆彩色小岛的图。<br>这种令人惊叹的“分堆”效果，90% 都是 <strong>t-SNE (t-分布随机邻域嵌入)</strong> 的功劳。</p><p>它不是在做数学投影（像 PCA 那些线性代数游戏），而是在做<strong>概率匹配</strong>。它强迫低维空间中的点，必须模仿高维空间中的社交关系。<br>本章我们将深入这个稍微有点复杂的算法内部，掰开揉碎了讲讲它是如何利用“学生 t 分布”来解决<strong>拥挤问题</strong>的。</p><p><img src="https://img.alicdn.com/imgextra/i2/O1CN01U4e5jF1s9BnP1sH1S_!!6000000005723-2-tps-1376-768.png" alt="t-SNE 优化原理"><br><em>(图注：上图是高维空间，下图是低维空间。t-SNE 通过梯度下降，不断移动下图中的点，让两边的“邻居关系”尽可能一致。)</em></p><hr><h2 id="1-第一步：高维空间的社交圈-SNE"><a href="#1-第一步：高维空间的社交圈-SNE" class="headerlink" title="1. 第一步：高维空间的社交圈 (SNE)"></a>1. 第一步：高维空间的社交圈 (SNE)</h2><p>首先，我们要量化在高维空间里，谁和谁是邻居。t-SNE 说：<strong>别算死板的距离了，算概率吧。</strong></p><h3 id="1-1-条件概率：选邻居"><a href="#1-1-条件概率：选邻居" class="headerlink" title="1.1 条件概率：选邻居"></a>1.1 条件概率：选邻居</h3><p>对于点 $x_i$，它选择 $x_j$ 作为邻居的概率 $p_{j|i}$ 是多少？<br>这取决于 $x_j$ 离它有多近。t-SNE 假设这种关系服从<strong>高斯分布（正态分布）</strong>。</p><p>$$ p_{j|i} = \frac{\exp(-||x_i - x_j||^2 / 2\sigma_i^2)}{\sum_{k \neq i} \exp(-||x_i - x_k||^2 / 2\sigma_i^2)} $$</p><ul><li><strong>分子</strong>：距离越近 ($||x_i - x_j||$ 小)，指数值越大，概率越高。</li><li><strong>分母</strong>：所有其他点对 $x_i$ 的“吸引力”总和。这是一个归一化项，确保所有概率加起来等于 1。</li><li><strong>直觉</strong>：这就像你在派对上选朋友。你选 $x_j$ 的概率，取决于他离你有多近，以及派对上有多少其他人（分母）。</li></ul><h3 id="1-2-动态半径：-sigma-i-的秘密"><a href="#1-2-动态半径：-sigma-i-的秘密" class="headerlink" title="1.2 动态半径：$\sigma_i$ 的秘密"></a>1.2 动态半径：$\sigma_i$ 的秘密</h3><p>公式里有个 $\sigma_i$，它是每个点独有的方差。这意味着<strong>每个点都有自己的“社交半径”</strong>。</p><ul><li><strong>稠密区域（市中心）</strong>：周围人很多。$\sigma_i$ 会自动变小。只关注身边 1 米的人，远一点的都不算邻居。</li><li><strong>稀疏区域（郊区）</strong>：周围没几个人。$\sigma_i$ 会自动变大。哪怕离我 10 米远的人，也算我的邻居（因为没别的选择了）。</li></ul><p>这种<strong>自适应密度</strong>的能力，是 t-SNE 优于 PCA 的关键原因之一。它是通过二分查找来确定的，目标是满足用户设定的 <strong>Perplexity（困惑度）</strong>。</p><hr><h2 id="2-第二步：低维空间的模仿秀"><a href="#2-第二步：低维空间的模仿秀" class="headerlink" title="2. 第二步：低维空间的模仿秀"></a>2. 第二步：低维空间的模仿秀</h2><p>现在，我们在 2D 屏幕上随机撒一堆点 $y_i$。我们要让这些 $y$ 模仿 $x$ 的社交关系。</p><h3 id="2-1-长尾分布：为什么要用-t-分布？"><a href="#2-1-长尾分布：为什么要用-t-分布？" class="headerlink" title="2.1 长尾分布：为什么要用 t 分布？"></a>2.1 长尾分布：为什么要用 t 分布？</h3><p>在低维空间，我们不用高斯分布了，改用<strong>学生 t 分布 (Student’s t-distribution)</strong>（自由度为 1 的 t 分布，也就是柯西分布）。<br>公式长这样：<br>$$ q_{ij} = \frac{(1 + ||y_i - y_j||^2)^{-1}}{\sum_{k \neq l} (1 + ||y_k - y_l||^2)^{-1}} $$</p><p><strong>为什么要换？（这是 t-SNE 的灵魂！）</strong><br>这就是为了解决著名的 <strong>拥挤问题 (Crowding Problem)</strong>。</p><ul><li><strong>几何事实</strong>：在高维空间（比如 100 维），球的体积随着半径指数级增长，能容纳海量的邻居。但在 2D 平面上，圆的面积增长很慢，根本挤不下那么多邻居。</li><li><strong>后果</strong>：如果低维也用高斯分布，中等距离的点会没地方站，只好被迫挤在中心，导致所有簇都粘连成一团。</li><li><strong>t 分布的救赎</strong>：t 分布是<strong>长尾</strong>的（尾巴比高斯分布高）。<ul><li>这意味着：为了表示同样的概率（同样的亲密度），t 分布允许点与点之间<strong>离得更远</strong>。</li><li><strong>效果</strong>：这就像一种<strong>斥力</strong>。它把原本必须挤在一起的簇，强行推开了。并在簇与簇之间留出了明显的空白（Ghost Space）。</li></ul></li></ul><h3 id="2-2-损失函数：KL-散度"><a href="#2-2-损失函数：KL-散度" class="headerlink" title="2.2 损失函数：KL 散度"></a>2.2 损失函数：KL 散度</h3><p>我们要让低维概率 $Q$ 尽可能接近高维概率 $P$。衡量两个分布差异的标准工具是 <strong>KL 散度</strong>。</p><p>$$ C = KL(P || Q) = \sum_i \sum_j p_{ij} \log \frac{p_{ij}}{q_{ij}} $$</p><p><img src="https://img.alicdn.com/imgextra/i1/O1CN01liqzUC1NgOQEDYEyr_!!6000000001599-2-tps-1376-768.png" alt="KL 散度优化"><br><em>(图注：KL 散度驱动梯度下降，吸引力拉近邻居，排斥力推开非邻居。)</em></p><h3 id="2-3-梯度下降：推拉游戏"><a href="#2-3-梯度下降：推拉游戏" class="headerlink" title="2.3 梯度下降：推拉游戏"></a>2.3 梯度下降：推拉游戏</h3><p>t-SNE 的优化过程就是不断移动 $y_i$ 的位置。梯度的物理意义非常直观：</p><ul><li><strong>吸引力</strong>：如果 $p_{ij}$ 很大（原本是邻居）但 $q_{ij}$ 很小（现在离远了），梯度会把它们拉近。</li><li><strong>排斥力</strong>：t 分布的长尾提供了一种自然的排斥力，防止大家挤在一起。</li></ul><p><img src="https://img.alicdn.com/imgextra/i1/O1CN01naAIOn1Z3iMaMegha_!!6000000003139-2-tps-1376-768.png" alt="拥挤问题与 t 分布"><br><em>(图注：高斯分布导致簇粘连（左），t 分布的长尾提供斥力，把簇推开（右）。)</em></p><hr><h2 id="3-关键参数：Perplexity-困惑度"><a href="#3-关键参数：Perplexity-困惑度" class="headerlink" title="3. 关键参数：Perplexity (困惑度)"></a>3. 关键参数：Perplexity (困惑度)</h2><p>t-SNE 只有一个核心参数，但它至关重要，能决定图的生死：<strong>Perplexity</strong>。<br>它大致相当于<strong>“每个点应该关注多少个有效邻居”</strong>。</p><ul><li><strong>Perplexity = 5</strong>：<strong>微观视角</strong>。每个点只在乎身边那几个人。结果：数据会碎成很多小块，像一盘散沙。</li><li><strong>Perplexity = 50</strong>：<strong>中观视角</strong>。每个点会看更远一点。结果：簇会更完整，甚至能看到一些全局形状。</li><li><strong>Perplexity = 100</strong>：<strong>宏观视角</strong>。开始关注全局结构。</li></ul><p><strong>经验法则</strong>：</p><ul><li>通常设在 <strong>5 到 50</strong> 之间。</li><li><strong>默认值 30</strong> 通常是个不错的起点。</li><li>对于大数据集（&gt; 10万），设大一点（如 50-100）有助于保持全局结构。</li></ul><p><img src="https://img.alicdn.com/imgextra/i3/O1CN01H4J1Ry1cx55dc9PgD_!!6000000003666-2-tps-1376-768.png" alt="Perplexity 参数效果对比"><br><em>(图注：左图 Perplexity=2，簇支离破碎；右图 Perplexity=30，数字 0-9 分得清清楚楚。)</em></p><hr><h2 id="4-技术对比：t-SNE-的阿喀琉斯之踵"><a href="#4-技术对比：t-SNE-的阿喀琉斯之踵" class="headerlink" title="4. 技术对比：t-SNE 的阿喀琉斯之踵"></a>4. 技术对比：t-SNE 的阿喀琉斯之踵</h2><p>虽然 t-SNE 效果惊艳，但它有几个致命弱点，这也是为什么后来 UMAP 能挑战它的原因。</p><table><thead><tr><th style="text-align:left">特性</th><th style="text-align:left">PCA</th><th style="text-align:left">t-SNE</th><th style="text-align:left">评价</th></tr></thead><tbody><tr><td style="text-align:left"><strong>原理</strong></td><td style="text-align:left">线性投影</td><td style="text-align:left">非线性概率匹配</td><td style="text-align:left">t-SNE 更强力</td></tr><tr><td style="text-align:left"><strong>全局结构</strong></td><td style="text-align:left">保持极好</td><td style="text-align:left"><strong>丢失</strong></td><td style="text-align:left">t-SNE 图上的簇间距离<strong>没有意义</strong>。相隔很远的两个簇，可能在高维空间是挨着的。</td></tr><tr><td style="text-align:left"><strong>局部结构</strong></td><td style="text-align:left">一般</td><td style="text-align:left"><strong>极好</strong></td><td style="text-align:left">t-SNE 擅长把同一类分在一起。</td></tr><tr><td style="text-align:left"><strong>确定性</strong></td><td style="text-align:left">确定</td><td style="text-align:left"><strong>随机</strong></td><td style="text-align:left">每次跑结果都不一样，必须固定 random_state。</td></tr><tr><td style="text-align:left"><strong>速度</strong></td><td style="text-align:left">极快</td><td style="text-align:left"><strong>慢</strong></td><td style="text-align:left">即使有 Barnes-Hut 加速，跑 10 万数据也要好久。</td></tr><tr><td style="text-align:left"><strong>新数据</strong></td><td style="text-align:left">可以 Transform</td><td style="text-align:left"><strong>无法直接 Transform</strong></td><td style="text-align:left">这是最大痛点。来了新数据，必须把旧数据加进去<strong>重新跑一遍</strong>。无法用于线上实时推断。</td></tr></tbody></table><hr><h2 id="5-代码实战：手写数字可视化"><a href="#5-代码实战：手写数字可视化" class="headerlink" title="5. 代码实战：手写数字可视化"></a>5. 代码实战：手写数字可视化</h2><pre class=" language-python"><code class="language-python"><span class="token keyword">from</span> sklearn <span class="token keyword">import</span> datasets<span class="token keyword">from</span> sklearn<span class="token punctuation">.</span>manifold <span class="token keyword">import</span> TSNE<span class="token keyword">from</span> sklearn<span class="token punctuation">.</span>decomposition <span class="token keyword">import</span> PCA<span class="token keyword">import</span> matplotlib<span class="token punctuation">.</span>pyplot <span class="token keyword">as</span> plt<span class="token keyword">import</span> time<span class="token comment" spellcheck="true"># 1. 加载手写数字数据 (1797 张图片，64 维)</span>digits <span class="token operator">=</span> datasets<span class="token punctuation">.</span>load_digits<span class="token punctuation">(</span><span class="token punctuation">)</span>X <span class="token operator">=</span> digits<span class="token punctuation">.</span>datay <span class="token operator">=</span> digits<span class="token punctuation">.</span>target<span class="token keyword">print</span><span class="token punctuation">(</span>f<span class="token string">"原始维度: &amp;#123;X.shape&amp;#125;"</span><span class="token punctuation">)</span><span class="token comment" spellcheck="true"># 2. 预处理 (强烈推荐！)</span><span class="token comment" spellcheck="true"># t-SNE 在高维数据上计算距离很慢，且容易受噪声影响。</span><span class="token comment" spellcheck="true"># 先用 PCA 降到 50 维，既去噪又加速。</span><span class="token keyword">print</span><span class="token punctuation">(</span><span class="token string">"正在进行 PCA 降维..."</span><span class="token punctuation">)</span>X_pca <span class="token operator">=</span> PCA<span class="token punctuation">(</span>n_components<span class="token operator">=</span><span class="token number">50</span><span class="token punctuation">)</span><span class="token punctuation">.</span>fit_transform<span class="token punctuation">(</span>X<span class="token punctuation">)</span><span class="token comment" spellcheck="true"># 3. 运行 t-SNE</span><span class="token comment" spellcheck="true"># init='pca': 用 PCA 结果初始化，而不是随机初始化。</span><span class="token comment" spellcheck="true"># 这能让 t-SNE 收敛更快，且保留更多全局结构，结果更稳定。</span><span class="token keyword">print</span><span class="token punctuation">(</span><span class="token string">"正在运行 t-SNE..."</span><span class="token punctuation">)</span>start_time <span class="token operator">=</span> time<span class="token punctuation">.</span>time<span class="token punctuation">(</span><span class="token punctuation">)</span>tsne <span class="token operator">=</span> TSNE<span class="token punctuation">(</span>n_components<span class="token operator">=</span><span class="token number">2</span><span class="token punctuation">,</span> perplexity<span class="token operator">=</span><span class="token number">30</span><span class="token punctuation">,</span> init<span class="token operator">=</span><span class="token string">'pca'</span><span class="token punctuation">,</span> random_state<span class="token operator">=</span><span class="token number">42</span><span class="token punctuation">)</span>X_tsne <span class="token operator">=</span> tsne<span class="token punctuation">.</span>fit_transform<span class="token punctuation">(</span>X_pca<span class="token punctuation">)</span><span class="token keyword">print</span><span class="token punctuation">(</span>f<span class="token string">"t-SNE 耗时: &amp;#123;time.time() - start_time:.2f&amp;#125; 秒"</span><span class="token punctuation">)</span><span class="token comment" spellcheck="true"># 4. 绘图</span>plt<span class="token punctuation">.</span>figure<span class="token punctuation">(</span>figsize<span class="token operator">=</span><span class="token punctuation">(</span><span class="token number">10</span><span class="token punctuation">,</span> <span class="token number">8</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token comment" spellcheck="true"># 使用 tab10 调色板，区分 10 个数字</span>scatter <span class="token operator">=</span> plt<span class="token punctuation">.</span>scatter<span class="token punctuation">(</span>X_tsne<span class="token punctuation">[</span><span class="token punctuation">:</span><span class="token punctuation">,</span> <span class="token number">0</span><span class="token punctuation">]</span><span class="token punctuation">,</span> X_tsne<span class="token punctuation">[</span><span class="token punctuation">:</span><span class="token punctuation">,</span> <span class="token number">1</span><span class="token punctuation">]</span><span class="token punctuation">,</span> c<span class="token operator">=</span>y<span class="token punctuation">,</span> cmap<span class="token operator">=</span><span class="token string">'tab10'</span><span class="token punctuation">,</span> alpha<span class="token operator">=</span><span class="token number">0.6</span><span class="token punctuation">,</span> s<span class="token operator">=</span><span class="token number">10</span><span class="token punctuation">)</span>plt<span class="token punctuation">.</span>legend<span class="token punctuation">(</span><span class="token operator">*</span>scatter<span class="token punctuation">.</span>legend_elements<span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">,</span> title<span class="token operator">=</span><span class="token string">"Digits"</span><span class="token punctuation">)</span>plt<span class="token punctuation">.</span>title<span class="token punctuation">(</span><span class="token string">"t-SNE visualization of MNIST Digits"</span><span class="token punctuation">)</span>plt<span class="token punctuation">.</span>axis<span class="token punctuation">(</span><span class="token string">'off'</span><span class="token punctuation">)</span> <span class="token comment" spellcheck="true"># t-SNE 的坐标轴刻度没有意义，关掉它</span>plt<span class="token punctuation">.</span>show<span class="token punctuation">(</span><span class="token punctuation">)</span></code></pre><hr><h2 id="6-实践避坑指南"><a href="#6-实践避坑指南" class="headerlink" title="6. 实践避坑指南"></a>6. 实践避坑指南</h2><ol><li><strong>别信簇间距离</strong>：千万别看着图说：“簇 A 和簇 B 离得远，说明它们很不相似。” 不一定！可能只是 t-SNE 为了把它们分开，随机推到了两边。</li><li><strong>别信簇的大小</strong>：t-SNE 会膨胀密集的簇，收缩稀疏的簇。图上两个簇看起来一样大，实际上密度可能差 100 倍。</li><li><strong>先降维</strong>：如果你的特征超过 50 维（比如本项目的 1536 维），<strong>千万别直接跑 t-SNE</strong>。先用 PCA 降到 50 维，效果更好且速度更快。</li><li><strong>学习率 (Learning Rate)</strong>：通常在 [10, 1000] 之间。如果图看起来像一个球，可能是学习率太大了；如果点都挤在一起，可能是学习率太小了。</li></ol><hr><h2 id="下一章预告"><a href="#下一章预告" class="headerlink" title="下一章预告"></a>下一章预告</h2><p>t-SNE 虽好，但太慢了，而且它只能做“事后分析”（可视化），不能做“在线服务”（因为不能 transform 新数据）。<br>有没有一种算法，既能像 t-SNE 一样分堆，又能像 PCA 一样保持全局关系，速度快，还支持 transform？</p><p>有。它就是降维界的新皇——<strong>UMAP</strong>。</p><p>👉 <a href="https://yeee.wang/posts/b3b7.html">第 13 章：UMAP 原理与实践</a></p>]]></content>
    
    <summary type="html">
    
      第 12 章：t-SNE 深度解析 (t-Distributed Stochastic Neighbor Embedding)
“在数据可视化的世界里，t-SNE 就是那个把乱麻理顺的魔术师。”

如果你在 Kaggle 上看过别人的 Kernel，你一定见过那种把 MNIST 手写数字完美分成 10 堆彩色小岛的图。
这种令人惊叹的“分堆”效果，90% 都是 t-SNE (t-分布随机邻域嵌入) 的功劳。

它不是在做数学投影（像 PCA 那些线性代数游戏），而是在做概率匹配。它强迫低维空间中的点，必须模仿高维空间中的社交关系。
本章我们将深入这个稍微有点复杂的算法内部，掰开揉碎了讲讲它是如
    
    </summary>
    
      <category term="算法" scheme="https://yeee.wang/categories/%E7%AE%97%E6%B3%95/"/>
    
    
      <category term="算法" scheme="https://yeee.wang/tags/%E7%AE%97%E6%B3%95/"/>
    
      <category term="数据分析" scheme="https://yeee.wang/tags/%E6%95%B0%E6%8D%AE%E5%88%86%E6%9E%90/"/>
    
      <category term="机器学习" scheme="https://yeee.wang/tags/%E6%9C%BA%E5%99%A8%E5%AD%A6%E4%B9%A0/"/>
    
  </entry>
  
  <entry>
    <title>第 11 章：非线性降维基础</title>
    <link href="https://yeee.wang/posts/6d4e.html"/>
    <id>https://yeee.wang/posts/6d4e.html</id>
    <published>2025-12-22T09:50:00.000Z</published>
    <updated>2026-02-19T20:00:41.880Z</updated>
    
    <content type="html"><![CDATA[<h1 id="第-11-章：非线性降维基础-Manifold-Learning"><a href="#第-11-章：非线性降维基础-Manifold-Learning" class="headerlink" title="第 11 章：非线性降维基础 (Manifold Learning)"></a>第 11 章：非线性降维基础 (Manifold Learning)</h1><blockquote><p>“地球是圆的，但地图是平的。把球面画成平面的过程，就是流形学习。”</p></blockquote><p>PCA 假设数据分布在一个平坦的超平面上。但现实世界的数据往往是<strong>卷曲</strong>的、<strong>扭曲</strong>的。<br>经典的例子是 <strong>“瑞士卷” (Swiss Roll)</strong> 数据集。数据像一块卷起来的地毯。<br>如果你直接用 PCA 从侧面压扁它（线性投影），原本相隔很远的两层会叠在一起，红色的点和蓝色的点就混淆了。这就叫<strong>投影混叠</strong>。</p><p>我们需要把这个卷小心翼翼地<strong>展开</strong>，就像把地毯铺平一样。这叫 <strong>流形学习 (Manifold Learning)</strong>。</p><hr><h2 id="1-核心概念：流形假设-Manifold-Hypothesis"><a href="#1-核心概念：流形假设-Manifold-Hypothesis" class="headerlink" title="1. 核心概念：流形假设 (Manifold Hypothesis)"></a>1. 核心概念：流形假设 (Manifold Hypothesis)</h2><p>流形学习基于一个大胆的假设：<br><strong>虽然数据看起来维数很高（比如 1536 维），但它们其实只分布在一个维数很低的流形（比如 2 维曲面）上。</strong></p><h3 id="1-1-什么是“流形”？"><a href="#1-1-什么是“流形”？" class="headerlink" title="1.1 什么是“流形”？"></a>1.1 什么是“流形”？</h3><p>流形 (Manifold) 是一个拓扑学概念。通俗地说，它是一个<strong>局部看起来是欧氏空间</strong>，但<strong>整体结构复杂</strong>的空间。</p><ul><li><strong>例子</strong>：地球表面。<ul><li><strong>局部</strong>：你在操场上跑步，地面是平的（2维平面）。</li><li><strong>整体</strong>：地球是圆的（嵌在3维空间里的2维球面）。</li></ul></li></ul><h3 id="1-2-测地距离-Geodesic-Distance"><a href="#1-2-测地距离-Geodesic-Distance" class="headerlink" title="1.2 测地距离 (Geodesic Distance)"></a>1.2 测地距离 (Geodesic Distance)</h3><p>在瑞士卷上，点 A 和点 B 可能在三维空间中离得很近（欧氏距离小），但如果你是一只生活在瑞士卷表面的蚂蚁，你要从 A 爬到 B，你需要沿着卷面绕很多圈（测地距离大）。</p><ul><li><strong>欧氏距离 (Euclidean Distance)</strong>：直线穿墙而过。对于卷曲数据，这是一种<strong>欺骗性</strong>的距离。</li><li><strong>测地距离 (Geodesic Distance)</strong>：沿着曲面行走。这是数据的<strong>真实</strong>距离。</li></ul><p>流形学习的核心任务，就是试图恢复这种<strong>测地距离</strong>，并把它在低维空间中展示出来。</p><p><img src="https://img.alicdn.com/imgextra/i1/O1CN01yqiKOm1w4qVJefSDA_!!6000000006255-2-tps-1376-768.png" alt="Isomap 测地距离示意图"><br><em>(图注：虚线是欧氏距离（短但错误），实线是测地距离（长但正确）。蚂蚁不能飞，它必须爬过实线。)</em></p><hr><h2 id="2-经典算法：Isomap-等距映射"><a href="#2-经典算法：Isomap-等距映射" class="headerlink" title="2. 经典算法：Isomap (等距映射)"></a>2. 经典算法：Isomap (等距映射)</h2><p>Isomap 是流形学习的鼻祖之一。它的思想极其朴素：<strong>既然测地距离才是真理，那我们就直接算测地距离！</strong></p><h3 id="2-1-算法三部曲"><a href="#2-1-算法三部曲" class="headerlink" title="2.1 算法三部曲"></a>2.1 算法三部曲</h3><ol><li><strong>构图 (Graph Construction)</strong>：<ul><li>对于每个点，找到它的 K 个最近邻（比如 K=5）。</li><li>把这些点连起来。</li><li><strong>隐喻</strong>：这就像在数据表面铺设了一张渔网。渔网只能连接相邻的节点。</li></ul></li><li><strong>算距离 (Shortest Path)</strong>：<ul><li>计算图中任意两点间的最短路径。</li><li>使用 Dijkstra 或 Floyd 算法。</li><li><strong>原理</strong>：虽然 A 和 B 离得远，但我们可以通过 A -&gt; C -&gt; D -&gt; E -&gt; B 一步步跳过去。所有跳跃距离之和，近似等于测地距离。</li></ul></li><li><strong>降维 (MDS)</strong>：<ul><li>把计算好的“测地距离矩阵”丢进 <strong>MDS (多维缩放)</strong> 算法。</li><li>MDS 会寻找一个新的低维坐标系，使得新坐标系下的欧氏距离尽可能等于输入的测地距离。</li></ul></li></ol><h3 id="2-2-优缺点分析"><a href="#2-2-优缺点分析" class="headerlink" title="2.2 优缺点分析"></a>2.2 优缺点分析</h3><ul><li><strong>优点</strong>：<strong>全局结构保持得很好</strong>。它能完美展开瑞士卷，保留长宽比例。</li><li><strong>缺点</strong>：<ol><li><strong>慢！</strong> 计算所有点对的最短路径是 $O(N^3)$ 的噩梦。数据量超过 5000 就跑不动了。</li><li><strong>短路风险</strong>：如果 K 设得太大，或者有噪声点，可能会在两个本该分开的卷面之间搭一座桥。蚂蚁会直接走捷径，导致展开失败。</li></ol></li></ul><hr><h2 id="3-经典算法：LLE-局部线性嵌入"><a href="#3-经典算法：LLE-局部线性嵌入" class="headerlink" title="3. 经典算法：LLE (局部线性嵌入)"></a>3. 经典算法：LLE (局部线性嵌入)</h2><p>LLE 代表了另一种流派：<strong>局部派</strong>。它的思想是：<strong>地球是圆的，但你脚下的篮球场是平的。</strong></p><h3 id="3-1-算法逻辑：拼图游戏"><a href="#3-1-算法逻辑：拼图游戏" class="headerlink" title="3.1 算法逻辑：拼图游戏"></a>3.1 算法逻辑：拼图游戏</h3><ol><li><strong>局部重构 (Local Reconstruction)</strong>：<ul><li>对于每个点 $x_i$，找它的邻居。</li><li>试着用邻居的<strong>线性组合</strong>来表示它：$x_i \approx \sum_j w_{ij} x_j$。</li><li><strong>目标</strong>：找出一组权重 $w$，使得重构误差最小。</li><li><strong>隐喻</strong>：这一步是在记录每个点和邻居的<strong>相对几何关系</strong>（比如：我在张三左边 1 米，李四右边 2 米）。</li></ul></li><li><strong>全局降维 (Global Alignment)</strong>：<ul><li>在低维空间找对应的点 $y_i$。</li><li><strong>核心约束</strong>：这些点必须保持<strong>同样的线性组合关系</strong>（权重 $w_{ij}$ 不变）。</li><li>$y_i \approx \sum_j w_{ij} y_j$。</li><li><strong>隐喻</strong>：这就像玩拼图。只要每块拼图和它周围拼图的咬合关系不变，整个拼图怎么弯曲、怎么平铺都可以。</li></ul></li></ol><h3 id="3-2-优缺点分析"><a href="#3-2-优缺点分析" class="headerlink" title="3.2 优缺点分析"></a>3.2 优缺点分析</h3><ul><li><strong>优点</strong>：可以解开极其卷曲的流形。对局部细节保留得很好。</li><li><strong>缺点</strong>：<ol><li><strong>对噪声极其敏感</strong>。</li><li><strong>数据挤压</strong>：往往把数据挤在中间，难以保持全局几何形状。</li></ol></li></ul><p><img src="https://img.alicdn.com/imgextra/i2/O1CN016pdeYu1re2rfGMwsJ_!!6000000005655-2-tps-1376-768.png" alt="LLE 局部线性重构"><br><em>(图注：左图是高维卷曲状态，右图是低维展开状态。虽然形状变了，但每个小红点相对于蓝点的相对位置（权重）是锁死的。)</em></p><hr><h2 id="4-维度灾难：流形学习的噩梦"><a href="#4-维度灾难：流形学习的噩梦" class="headerlink" title="4. 维度灾难：流形学习的噩梦"></a>4. 维度灾难：流形学习的噩梦</h2><p>你可能会问：既然流形学习这么好，为什么没一统天下？<br>因为有一个幽灵一直盘旋在数据科学上空：<strong>维度灾难 (The Curse of Dimensionality)</strong>。</p><p>在高维空间（如 1000 维）：</p><ol><li><strong>距离失效</strong>：所有点之间的欧氏距离都趋于相等。Isomap 的“最近邻”可能根本不是真的近。</li><li><strong>数据稀疏</strong>：数据像撒在沙漠里的几粒沙子。要填满 1000 维空间，需要的数据量是天文数字。流形甚至可能是不连通的（断裂的）。</li></ol><p>这就是为什么传统的 Isomap 和 LLE 在工业界（面对稀疏、高维、含噪的真实数据）往往效果不佳。我们需要更鲁棒的算法——于是 <strong>t-SNE</strong> 和 <strong>UMAP</strong> 应运而生。</p><p><img src="https://img.alicdn.com/imgextra/i4/O1CN0125elhi2803kkOj0CB_!!6000000007869-2-tps-1376-768.png" alt="维度灾难"><br><em>(图注：高维空间中，距离失效、数据稀疏、体积集中在表面。这是流形学习的噩梦。)</em></p><hr><h2 id="5-代码实战：展开瑞士卷"><a href="#5-代码实战：展开瑞士卷" class="headerlink" title="5. 代码实战：展开瑞士卷"></a>5. 代码实战：展开瑞士卷</h2><pre class=" language-python"><code class="language-python"><span class="token keyword">from</span> sklearn <span class="token keyword">import</span> manifold<span class="token punctuation">,</span> datasets<span class="token keyword">import</span> matplotlib<span class="token punctuation">.</span>pyplot <span class="token keyword">as</span> plt<span class="token keyword">from</span> mpl_toolkits<span class="token punctuation">.</span>mplot3d <span class="token keyword">import</span> Axes3D<span class="token comment" spellcheck="true"># 1. 生成瑞士卷数据</span><span class="token comment" spellcheck="true"># n_samples=1500: 数据点数量</span><span class="token comment" spellcheck="true"># noise=0.0: 纯净的瑞士卷。如果设为 0.5，Isomap 就会因为短路而崩溃。</span>X<span class="token punctuation">,</span> color <span class="token operator">=</span> datasets<span class="token punctuation">.</span>make_swiss_roll<span class="token punctuation">(</span>n_samples<span class="token operator">=</span><span class="token number">1500</span><span class="token punctuation">,</span> noise<span class="token operator">=</span><span class="token number">0.0</span><span class="token punctuation">)</span><span class="token comment" spellcheck="true"># 2. 降维算法对比</span><span class="token comment" spellcheck="true"># A. Isomap (展开)</span><span class="token comment" spellcheck="true"># n_neighbors=10: 连接最近的 10 个点</span>X_iso <span class="token operator">=</span> manifold<span class="token punctuation">.</span>Isomap<span class="token punctuation">(</span>n_neighbors<span class="token operator">=</span><span class="token number">10</span><span class="token punctuation">,</span> n_components<span class="token operator">=</span><span class="token number">2</span><span class="token punctuation">)</span><span class="token punctuation">.</span>fit_transform<span class="token punctuation">(</span>X<span class="token punctuation">)</span><span class="token comment" spellcheck="true"># B. LLE (局部重构)</span>X_lle <span class="token operator">=</span> manifold<span class="token punctuation">.</span>LocallyLinearEmbedding<span class="token punctuation">(</span>n_neighbors<span class="token operator">=</span><span class="token number">10</span><span class="token punctuation">,</span> n_components<span class="token operator">=</span><span class="token number">2</span><span class="token punctuation">)</span><span class="token punctuation">.</span>fit_transform<span class="token punctuation">(</span>X<span class="token punctuation">)</span><span class="token comment" spellcheck="true"># C. t-SNE (作为对比)</span><span class="token comment" spellcheck="true"># 这是一个概率方法，我们将在下一章详细讲</span>X_tsne <span class="token operator">=</span> manifold<span class="token punctuation">.</span>TSNE<span class="token punctuation">(</span>n_components<span class="token operator">=</span><span class="token number">2</span><span class="token punctuation">,</span> perplexity<span class="token operator">=</span><span class="token number">30</span><span class="token punctuation">,</span> init<span class="token operator">=</span><span class="token string">'pca'</span><span class="token punctuation">,</span> random_state<span class="token operator">=</span><span class="token number">42</span><span class="token punctuation">)</span><span class="token punctuation">.</span>fit_transform<span class="token punctuation">(</span>X<span class="token punctuation">)</span><span class="token comment" spellcheck="true"># 3. 绘图</span>fig <span class="token operator">=</span> plt<span class="token punctuation">.</span>figure<span class="token punctuation">(</span>figsize<span class="token operator">=</span><span class="token punctuation">(</span><span class="token number">20</span><span class="token punctuation">,</span> <span class="token number">5</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token comment" spellcheck="true"># 原始 3D 数据</span>ax <span class="token operator">=</span> fig<span class="token punctuation">.</span>add_subplot<span class="token punctuation">(</span><span class="token number">141</span><span class="token punctuation">,</span> projection<span class="token operator">=</span><span class="token string">'3d'</span><span class="token punctuation">)</span>ax<span class="token punctuation">.</span>scatter<span class="token punctuation">(</span>X<span class="token punctuation">[</span><span class="token punctuation">:</span><span class="token punctuation">,</span> <span class="token number">0</span><span class="token punctuation">]</span><span class="token punctuation">,</span> X<span class="token punctuation">[</span><span class="token punctuation">:</span><span class="token punctuation">,</span> <span class="token number">1</span><span class="token punctuation">]</span><span class="token punctuation">,</span> X<span class="token punctuation">[</span><span class="token punctuation">:</span><span class="token punctuation">,</span> <span class="token number">2</span><span class="token punctuation">]</span><span class="token punctuation">,</span> c<span class="token operator">=</span>color<span class="token punctuation">,</span> cmap<span class="token operator">=</span>plt<span class="token punctuation">.</span>cm<span class="token punctuation">.</span>Spectral<span class="token punctuation">)</span>ax<span class="token punctuation">.</span>view_init<span class="token punctuation">(</span><span class="token number">4</span><span class="token punctuation">,</span> <span class="token operator">-</span><span class="token number">72</span><span class="token punctuation">)</span>ax<span class="token punctuation">.</span>set_title<span class="token punctuation">(</span><span class="token string">"Original Swiss Roll (3D)"</span><span class="token punctuation">)</span><span class="token comment" spellcheck="true"># Isomap 结果</span>ax <span class="token operator">=</span> fig<span class="token punctuation">.</span>add_subplot<span class="token punctuation">(</span><span class="token number">142</span><span class="token punctuation">)</span>ax<span class="token punctuation">.</span>scatter<span class="token punctuation">(</span>X_iso<span class="token punctuation">[</span><span class="token punctuation">:</span><span class="token punctuation">,</span> <span class="token number">0</span><span class="token punctuation">]</span><span class="token punctuation">,</span> X_iso<span class="token punctuation">[</span><span class="token punctuation">:</span><span class="token punctuation">,</span> <span class="token number">1</span><span class="token punctuation">]</span><span class="token punctuation">,</span> c<span class="token operator">=</span>color<span class="token punctuation">,</span> cmap<span class="token operator">=</span>plt<span class="token punctuation">.</span>cm<span class="token punctuation">.</span>Spectral<span class="token punctuation">)</span>ax<span class="token punctuation">.</span>set_title<span class="token punctuation">(</span><span class="token string">"Isomap (Unrolled)"</span><span class="token punctuation">)</span><span class="token comment" spellcheck="true"># 预期：完美的长方形，颜色渐变均匀。</span><span class="token comment" spellcheck="true"># LLE 结果</span>ax <span class="token operator">=</span> fig<span class="token punctuation">.</span>add_subplot<span class="token punctuation">(</span><span class="token number">143</span><span class="token punctuation">)</span>ax<span class="token punctuation">.</span>scatter<span class="token punctuation">(</span>X_lle<span class="token punctuation">[</span><span class="token punctuation">:</span><span class="token punctuation">,</span> <span class="token number">0</span><span class="token punctuation">]</span><span class="token punctuation">,</span> X_lle<span class="token punctuation">[</span><span class="token punctuation">:</span><span class="token punctuation">,</span> <span class="token number">1</span><span class="token punctuation">]</span><span class="token punctuation">,</span> c<span class="token operator">=</span>color<span class="token punctuation">,</span> cmap<span class="token operator">=</span>plt<span class="token punctuation">.</span>cm<span class="token punctuation">.</span>Spectral<span class="token punctuation">)</span>ax<span class="token punctuation">.</span>set_title<span class="token punctuation">(</span><span class="token string">"LLE (Unrolled)"</span><span class="token punctuation">)</span><span class="token comment" spellcheck="true"># 预期：也是长方形，但可能形状有点扭曲。</span><span class="token comment" spellcheck="true"># t-SNE 结果</span>ax <span class="token operator">=</span> fig<span class="token punctuation">.</span>add_subplot<span class="token punctuation">(</span><span class="token number">144</span><span class="token punctuation">)</span>ax<span class="token punctuation">.</span>scatter<span class="token punctuation">(</span>X_tsne<span class="token punctuation">[</span><span class="token punctuation">:</span><span class="token punctuation">,</span> <span class="token number">0</span><span class="token punctuation">]</span><span class="token punctuation">,</span> X_tsne<span class="token punctuation">[</span><span class="token punctuation">:</span><span class="token punctuation">,</span> <span class="token number">1</span><span class="token punctuation">]</span><span class="token punctuation">,</span> c<span class="token operator">=</span>color<span class="token punctuation">,</span> cmap<span class="token operator">=</span>plt<span class="token punctuation">.</span>cm<span class="token punctuation">.</span>Spectral<span class="token punctuation">)</span>ax<span class="token punctuation">.</span>set_title<span class="token punctuation">(</span><span class="token string">"t-SNE (Fragmented)"</span><span class="token punctuation">)</span><span class="token comment" spellcheck="true"># 预期：数据断裂成几块。t-SNE 不保证连续性。</span>plt<span class="token punctuation">.</span>show<span class="token punctuation">(</span><span class="token punctuation">)</span></code></pre><p><img src="https://img.alicdn.com/imgextra/i3/O1CN01aDFvUE1NlQmLGWA39_!!6000000001610-2-tps-1376-768.png" alt="瑞士卷展开对比"><br><em>(图注：PCA 混叠、Isomap 完美展开、t-SNE 断裂。瑞士卷是流形学习的试金石。)</em></p><hr><h2 id="6-实践要点"><a href="#6-实践要点" class="headerlink" title="6. 实践要点"></a>6. 实践要点</h2><ol><li><strong>邻居数 (K / n_neighbors)</strong>：这是所有流形学习算法最重要的“超参数”。<ul><li><strong>K 太小</strong>：图不连通，数据像饼干一样碎裂成孤岛。</li><li><strong>K 太大</strong>：引入了“短路”连接（横跨卷面的错误边），导致卷曲没解开，反而被压扁了。</li><li><strong>技巧</strong>：如果你不知道 K 选多少，试着画出 K 与“残差方差”的关系图，找拐点。</li></ul></li><li><strong>工业界现状</strong>：<ul><li><strong>Isomap / LLE</strong>：<strong>学院派</strong>。理论优美，但计算复杂度太高 ($O(N^2)$ 或 $O(N^3)$)，且对噪声太敏感。主要用于教学。</li><li><strong>t-SNE / UMAP</strong>：<strong>实战派</strong>。虽然理论复杂（涉及概率和拓扑），但它们通过近似算法（如 Barnes-Hut 树）解决了计算效率问题，且对噪声有很强的容忍度。</li></ul></li></ol><hr><h2 id="下一章预告"><a href="#下一章预告" class="headerlink" title="下一章预告"></a>下一章预告</h2><p>终于轮到 <strong>t-SNE</strong> 登场了。它放弃了严谨的几何距离，转而投向了<strong>概率统计</strong>的怀抱。</p><ul><li>它是怎么做到在二维屏幕上把 MNIST 的 10 个数字分得那么开的？</li><li>为什么它叫 “t”-SNE？这个 “t” 和学生 t 分布有什么关系？</li><li>为什么说它是“可视化之王”？</li></ul><p>👉 <a href="https://yeee.wang/posts/e4cc.html">第 12 章：t-SNE 深度解析</a></p>]]></content>
    
    <summary type="html">
    
      第 11 章：非线性降维基础 (Manifold Learning)
“地球是圆的，但地图是平的。把球面画成平面的过程，就是流形学习。”

PCA 假设数据分布在一个平坦的超平面上。但现实世界的数据往往是卷曲的、扭曲的。
经典的例子是 “瑞士卷” (Swiss Roll) 数据集。数据像一块卷起来的地毯。
如果你直接用 PCA 从侧面压扁它（线性投影），原本相隔很远的两层会叠在一起，红色的点和蓝色的点就混淆了。这就叫投影混叠。

我们需要把这个卷小心翼翼地展开，就像把地毯铺平一样。这叫 流形学习 (Manifold Learning)。




1. 核心概念：流形假设 (Manifold H
    
    </summary>
    
      <category term="算法" scheme="https://yeee.wang/categories/%E7%AE%97%E6%B3%95/"/>
    
    
      <category term="算法" scheme="https://yeee.wang/tags/%E7%AE%97%E6%B3%95/"/>
    
      <category term="数据分析" scheme="https://yeee.wang/tags/%E6%95%B0%E6%8D%AE%E5%88%86%E6%9E%90/"/>
    
      <category term="机器学习" scheme="https://yeee.wang/tags/%E6%9C%BA%E5%99%A8%E5%AD%A6%E4%B9%A0/"/>
    
  </entry>
  
  <entry>
    <title>第 10 章：线性降维：PCA</title>
    <link href="https://yeee.wang/posts/432e.html"/>
    <id>https://yeee.wang/posts/432e.html</id>
    <published>2025-12-22T09:45:00.000Z</published>
    <updated>2026-02-19T20:00:41.879Z</updated>
    
    <content type="html"><![CDATA[<h1 id="第-10-章：线性降维：PCA-Principal-Component-Analysis"><a href="#第-10-章：线性降维：PCA-Principal-Component-Analysis" class="headerlink" title="第 10 章：线性降维：PCA (Principal Component Analysis)"></a>第 10 章：线性降维：PCA (Principal Component Analysis)</h1><blockquote><p>“最好的数据压缩算法，不是 zip，而是理解数据的结构。”</p></blockquote><p>我们生活在一个高维数据爆炸的时代。</p><ul><li><strong>图像</strong>：一张 100x100 像素的小头像，如果展平，就是 10,000 维的向量。</li><li><strong>文本</strong>：一段包含 512 个 token 的文本，如果用 Embedding 表示，通常是 768 维或 1536 维。</li><li><strong>用户画像</strong>：一个电商用户的特征，可能包含点击历史、购买力、地理位置等几千个指标。</li></ul><p>在这些成千上万的维度中，往往充斥着<strong>冗余</strong>（比如“出生年份”和“年龄”完全相关）和<strong>噪声</strong>（比如图片边缘的随机噪点）。<br><strong>降维 (Dimensionality Reduction)</strong> 的目标很简单：<strong>去粗取精</strong>。把那些冗余的、噪声的维度扔掉，只保留最核心的信息。这不仅能减少存储空间，更能让后续的聚类算法（如 K-Means）跑得更快、更准。</p><p>本章我们将介绍降维领域的鼻祖——<strong>主成分分析 (PCA)</strong>。虽然它诞生于 1901 年，虽然它是线性的，但它依然是目前最常用、最稳健的数据预处理工具。它是数据科学家的“瑞士军刀”。</p><p><img src="https://img.alicdn.com/imgextra/i2/O1CN017OkvFp1iw3u30SFgR_!!6000000004476-2-tps-1376-768.png" alt="PCA 原理示意图"><br><em>(图注：数据像一个扁平的法棍面包。PCA 找到了面包的主轴，让我们能忽略厚度，只看长度。)</em></p><hr><h2 id="1-核心概念：为什么“方差”就是“信息”？"><a href="#1-核心概念：为什么“方差”就是“信息”？" class="headerlink" title="1. 核心概念：为什么“方差”就是“信息”？"></a>1. 核心概念：为什么“方差”就是“信息”？</h2><p>在学习 PCA 之前，我们必须达成一个共识：<strong>方差 (Variance) = 信息 (Information)</strong>。</p><h3 id="1-1-一个直观的例子：学生成绩"><a href="#1-1-一个直观的例子：学生成绩" class="headerlink" title="1.1 一个直观的例子：学生成绩"></a>1.1 一个直观的例子：学生成绩</h3><p>假设我们有一个班级的成绩单，包含两个特征：</p><ol><li><strong>数学成绩</strong>：大家的分别很大。学霸考 100 分，学渣考 0 分。分数分布在 [0, 100] 之间，方差很大。</li><li><strong>体育成绩</strong>：大家都差不多。基本都在 [80, 85] 之间，方差很小。</li></ol><p>如果我们被迫只能保留一个特征来区分这些学生，你会选哪个？<br>当然是<strong>数学成绩</strong>。</p><ul><li>因为数学成绩方差大，能把学生<strong>区分开</strong>（提供了信息）。</li><li>体育成绩方差小，大家都一样，提供了很少的信息（甚至接近常数）。</li></ul><p><strong>结论</strong>：在降维时，我们总是希望保留那些<strong>方差最大</strong>的方向，扔掉那些<strong>方差极小</strong>的方向（因为那通常是噪声或常数）。</p><h3 id="1-2-PCA-的思想：换个角度看世界"><a href="#1-2-PCA-的思想：换个角度看世界" class="headerlink" title="1.2 PCA 的思想：换个角度看世界"></a>1.2 PCA 的思想：换个角度看世界</h3><p>PCA 的核心任务是：<strong>旋转坐标轴</strong>。它要找到一个新的坐标系，使得数据在新坐标轴上的投影方差最大。</p><p>想象一个法棍面包（数据云）悬浮在空中：</p><ul><li><strong>原始坐标系 (X, Y, Z)</strong>：这是人为设定的，比如 X 代表“面粉量”，Y 代表“烘焙时间”。这跟面包的几何形状可能没关系。</li><li><strong>PCA 坐标系 (PC1, PC2, PC3)</strong>：这是由数据决定的。<ul><li><strong>主成分 1 (PC1)</strong>：沿着法棍<strong>最长</strong>的方向。在这个方向上，数据分布最广（方差最大）。保留它，我们就保留了面包的主要长度。</li><li><strong>主成分 2 (PC2)</strong>：沿着法棍<strong>宽度</strong>的方向。这是仅次于长度的第二大方差方向。</li><li><strong>主成分 3 (PC3)</strong>：沿着法棍<strong>厚度</strong>的方向。这是方差最小的方向。</li></ul></li></ul><p>如果我们只能保留 1 个维度，我们肯定选 PC1。这样我们就能用一条线来近似这根法棍，虽然丢失了宽度和厚度，但保留了物体最大的特征。</p><hr><h2 id="2-数学推导：一步步拆解-PCA"><a href="#2-数学推导：一步步拆解-PCA" class="headerlink" title="2. 数学推导：一步步拆解 PCA"></a>2. 数学推导：一步步拆解 PCA</h2><p>这部分包含核心的线性代数原理，理解它能让你明白 PCA 为什么会失败（以及什么时候会失败）。</p><h3 id="第一步：中心化-Centering"><a href="#第一步：中心化-Centering" class="headerlink" title="第一步：中心化 (Centering)"></a>第一步：中心化 (Centering)</h3><p>先把数据的中心挪到原点 $(0,0)$。<br>$$ X_{new} = X - \mu $$<br><strong>为什么？</strong> 因为 PCA 是基于旋转的。如果不把数据挪到原点，旋转轴就会乱套。就像转动地球仪，必须围绕地心转一样。</p><h3 id="第二步：计算协方差矩阵-Covariance-Matrix"><a href="#第二步：计算协方差矩阵-Covariance-Matrix" class="headerlink" title="第二步：计算协方差矩阵 (Covariance Matrix)"></a>第二步：计算协方差矩阵 (Covariance Matrix)</h3><p>计算矩阵 $\Sigma = \frac{1}{N} X^T X$。<br>这个矩阵描述了特征之间是如何“联动”的。</p><ul><li>$\Sigma_{ij} &gt; 0$：特征 i 变大，特征 j 也变大（正相关）。</li><li>$\Sigma_{ij} = 0$：特征 i 和 j 不相关（正交）。</li><li>$\Sigma_{ij} &lt; 0$：特征 i 变大，特征 j 变小（负相关）。</li></ul><p>PCA 的目标其实就是<strong>去除相关性</strong>。在新的坐标系里，所有特征应该是<strong>不相关</strong>的（协方差为 0）。</p><h3 id="第三步：特征值分解-Eigen-decomposition"><a href="#第三步：特征值分解-Eigen-decomposition" class="headerlink" title="第三步：特征值分解 (Eigen-decomposition)"></a>第三步：特征值分解 (Eigen-decomposition)</h3><p>这是 PCA 的灵魂。我们需要求解协方差矩阵的特征方程：<br>$$ \Sigma v = \lambda v $$</p><ul><li><strong>特征向量 $v$ (Eigenvector)</strong>：它指出了新坐标轴的<strong>方向</strong>（法棍的朝向）。在 PCA 中，它们被称为“主成分方向”。</li><li><strong>特征值 $\lambda$ (Eigenvalue)</strong>：它代表了这个方向上的<strong>方差大小</strong>（法棍有多长）。</li></ul><h3 id="第四步：排序与截断"><a href="#第四步：排序与截断" class="headerlink" title="第四步：排序与截断"></a>第四步：排序与截断</h3><ol><li>算出所有的特征值和特征向量。</li><li>按特征值 $\lambda$ <strong>从大到小</strong>排序。</li><li><strong>截断</strong>：如果你想降到 $k$ 维，就只取前 $k$ 个特征向量。</li><li><strong>投影</strong>：把原始数据 $X$ 投影到这 $k$ 个向量上，得到降维后的数据。</li></ol><p><img src="https://img.alicdn.com/imgextra/i4/O1CN01PKnY3N1eB6pwcnREk_!!6000000003832-2-tps-1376-768.png" alt="PCA 四步拆解"><br><em>(图注：中心化 → 协方差矩阵 → 特征值分解 → 排序截断。四步完成降维。)</em></p><hr><h2 id="3-技术对比：线性与非线性"><a href="#3-技术对比：线性与非线性" class="headerlink" title="3. 技术对比：线性与非线性"></a>3. 技术对比：线性与非线性</h2><p>PCA 是线性的，这意味着它只能做“旋转”和“拉伸”，不能做“弯曲”。</p><table><thead><tr><th style="text-align:left">算法</th><th style="text-align:left">原理</th><th style="text-align:left">优点</th><th style="text-align:left">缺点</th><th style="text-align:left">适用场景</th></tr></thead><tbody><tr><td style="text-align:left"><strong>PCA</strong></td><td style="text-align:left">方差最大化 (线性)</td><td style="text-align:left"><strong>快</strong> (矩阵运算)，<strong>可解释</strong> (知道哪个特征重要)，<strong>无参数</strong> (不用调参)</td><td style="text-align:left">处理不了<strong>非线性流形</strong> (如瑞士卷)，容易受<strong>离群点</strong>影响 (方差会被极值拉偏)</td><td style="text-align:left">图像压缩，去噪，预处理，特征工程</td></tr><tr><td style="text-align:left"><strong>t-SNE</strong></td><td style="text-align:left">概率分布匹配 (非线性)</td><td style="text-align:left">可视化效果极佳，<strong>分堆明显</strong></td><td style="text-align:left">慢，<strong>丢失全局结构</strong>，不可逆 (不能用于新数据)</td><td style="text-align:left">2D/3D 可视化</td></tr><tr><td style="text-align:left"><strong>UMAP</strong></td><td style="text-align:left">拓扑流形 (非线性)</td><td style="text-align:left">比 t-SNE 快，<strong>兼顾全局与局部</strong></td><td style="text-align:left">理论复杂</td><td style="text-align:left">2D/3D 可视化，特征提取</td></tr><tr><td style="text-align:left"><strong>Autoencoder</strong></td><td style="text-align:left">神经网络 (非线性)</td><td style="text-align:left">极其灵活，可处理超大规模数据</td><td style="text-align:left">黑盒，训练难</td><td style="text-align:left">深度学习管道</td></tr></tbody></table><p><img src="https://img.alicdn.com/imgextra/i2/O1CN01zG3ACQ1yrVoxzrKl2_!!6000000006632-2-tps-1376-768.png" alt="线性 vs 非线性降维"><br><em>(图注：左边：PCA 只能像压扁一张纸一样降维。右边：非线性算法可以像剥橘子皮一样把弯曲的表面展开。)</em></p><hr><h2 id="4-代码实战：Scikit-Learn-实现-PCA"><a href="#4-代码实战：Scikit-Learn-实现-PCA" class="headerlink" title="4. 代码实战：Scikit-Learn 实现 PCA"></a>4. 代码实战：Scikit-Learn 实现 PCA</h2><p>我们来模拟一个实际场景：把一个拉长的、旋转过的椭圆数据降维。</p><pre class=" language-python"><code class="language-python"><span class="token keyword">import</span> numpy <span class="token keyword">as</span> np<span class="token keyword">from</span> sklearn<span class="token punctuation">.</span>decomposition <span class="token keyword">import</span> PCA<span class="token keyword">from</span> sklearn<span class="token punctuation">.</span>preprocessing <span class="token keyword">import</span> StandardScaler<span class="token keyword">import</span> matplotlib<span class="token punctuation">.</span>pyplot <span class="token keyword">as</span> plt<span class="token comment" spellcheck="true"># 1. 准备数据</span><span class="token comment" spellcheck="true"># 生成一个拉长的椭圆数据 (2D)，带有旋转</span><span class="token comment" spellcheck="true"># 我们可以把这想象成：身高(X) 和 体重(Y) 的关系，它们是高度相关的</span>rng <span class="token operator">=</span> np<span class="token punctuation">.</span>random<span class="token punctuation">.</span>RandomState<span class="token punctuation">(</span><span class="token number">1</span><span class="token punctuation">)</span>X <span class="token operator">=</span> np<span class="token punctuation">.</span>dot<span class="token punctuation">(</span>rng<span class="token punctuation">.</span>rand<span class="token punctuation">(</span><span class="token number">2</span><span class="token punctuation">,</span> <span class="token number">2</span><span class="token punctuation">)</span><span class="token punctuation">,</span> rng<span class="token punctuation">.</span>randn<span class="token punctuation">(</span><span class="token number">2</span><span class="token punctuation">,</span> <span class="token number">200</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">.</span>T<span class="token comment" spellcheck="true"># 2. 预处理 (关键步骤！)</span><span class="token comment" spellcheck="true"># PCA 对量纲极其敏感。如果 X 的单位是毫米(0-2000)，Y 的单位是米(0-2)，</span><span class="token comment" spellcheck="true"># 毫米的方差会远远大于米，导致 PCA 认为只有 X 重要。</span><span class="token comment" spellcheck="true"># 所以必须用 StandardScaler 把它们都变成标准正态分布。</span>scaler <span class="token operator">=</span> StandardScaler<span class="token punctuation">(</span><span class="token punctuation">)</span>X_scaled <span class="token operator">=</span> scaler<span class="token punctuation">.</span>fit_transform<span class="token punctuation">(</span>X<span class="token punctuation">)</span><span class="token comment" spellcheck="true"># 3. 训练 PCA</span><span class="token comment" spellcheck="true"># n_components=2: 这里我们先不降维，保留所有成分，看看每个成分的权重</span>pca <span class="token operator">=</span> PCA<span class="token punctuation">(</span>n_components<span class="token operator">=</span><span class="token number">2</span><span class="token punctuation">)</span>pca<span class="token punctuation">.</span>fit<span class="token punctuation">(</span>X_scaled<span class="token punctuation">)</span><span class="token comment" spellcheck="true"># 4. 结果分析</span><span class="token keyword">print</span><span class="token punctuation">(</span><span class="token string">"--- PCA 分析报告 ---"</span><span class="token punctuation">)</span><span class="token keyword">print</span><span class="token punctuation">(</span>f<span class="token string">"主成分方向 (特征向量):\n&amp;#123;pca.components_&amp;#125;"</span><span class="token punctuation">)</span><span class="token keyword">print</span><span class="token punctuation">(</span>f<span class="token string">"每个方向的方差 (特征值): &amp;#123;pca.explained_variance_&amp;#125;"</span><span class="token punctuation">)</span><span class="token keyword">print</span><span class="token punctuation">(</span>f<span class="token string">"方差解释率 (重要性): &amp;#123;pca.explained_variance_ratio_&amp;#125;"</span><span class="token punctuation">)</span><span class="token comment" spellcheck="true"># 输出示例: [0.97, 0.03]</span><span class="token comment" spellcheck="true"># 含义：第一个主成分包含了 97% 的信息，第二个只包含了 3%（可能是噪声）。</span><span class="token comment" spellcheck="true"># 5. 降维</span><span class="token comment" spellcheck="true"># 既然 PC2 只有 3% 的信息，我们可以放心地扔掉它，只保留 1 维</span>pca_1d <span class="token operator">=</span> PCA<span class="token punctuation">(</span>n_components<span class="token operator">=</span><span class="token number">1</span><span class="token punctuation">)</span>X_pca <span class="token operator">=</span> pca_1d<span class="token punctuation">.</span>fit_transform<span class="token punctuation">(</span>X_scaled<span class="token punctuation">)</span><span class="token keyword">print</span><span class="token punctuation">(</span>f<span class="token string">"\n降维后形状: &amp;#123;X_pca.shape&amp;#125;"</span><span class="token punctuation">)</span> # <span class="token punctuation">(</span><span class="token number">200</span><span class="token punctuation">,</span> <span class="token number">1</span><span class="token punctuation">)</span><span class="token comment" spellcheck="true"># 6. 数据还原 (Inverse Transform)</span><span class="token comment" spellcheck="true"># 我们可以把降维后的数据还原回去，看看丢掉了多少信息</span>X_restored <span class="token operator">=</span> pca_1d<span class="token punctuation">.</span>inverse_transform<span class="token punctuation">(</span>X_pca<span class="token punctuation">)</span><span class="token comment" spellcheck="true"># 7. 绘图对比</span>plt<span class="token punctuation">.</span>figure<span class="token punctuation">(</span>figsize<span class="token operator">=</span><span class="token punctuation">(</span><span class="token number">10</span><span class="token punctuation">,</span> <span class="token number">5</span><span class="token punctuation">)</span><span class="token punctuation">)</span>plt<span class="token punctuation">.</span>scatter<span class="token punctuation">(</span>X_scaled<span class="token punctuation">[</span><span class="token punctuation">:</span><span class="token punctuation">,</span> <span class="token number">0</span><span class="token punctuation">]</span><span class="token punctuation">,</span> X_scaled<span class="token punctuation">[</span><span class="token punctuation">:</span><span class="token punctuation">,</span> <span class="token number">1</span><span class="token punctuation">]</span><span class="token punctuation">,</span> alpha<span class="token operator">=</span><span class="token number">0.3</span><span class="token punctuation">,</span> label<span class="token operator">=</span><span class="token string">'Original'</span><span class="token punctuation">)</span>plt<span class="token punctuation">.</span>scatter<span class="token punctuation">(</span>X_restored<span class="token punctuation">[</span><span class="token punctuation">:</span><span class="token punctuation">,</span> <span class="token number">0</span><span class="token punctuation">]</span><span class="token punctuation">,</span> X_restored<span class="token punctuation">[</span><span class="token punctuation">:</span><span class="token punctuation">,</span> <span class="token number">1</span><span class="token punctuation">]</span><span class="token punctuation">,</span> alpha<span class="token operator">=</span><span class="token number">0.8</span><span class="token punctuation">,</span> color<span class="token operator">=</span><span class="token string">'red'</span><span class="token punctuation">,</span> label<span class="token operator">=</span><span class="token string">'Restored'</span><span class="token punctuation">)</span>plt<span class="token punctuation">.</span>legend<span class="token punctuation">(</span><span class="token punctuation">)</span>plt<span class="token punctuation">.</span>title<span class="token punctuation">(</span><span class="token string">'PCA Compression &amp; Restoration'</span><span class="token punctuation">)</span><span class="token comment" spellcheck="true"># 你会看到红色的点完美地排成了一条直线，这就是 PCA 找到的“主轴”</span>plt<span class="token punctuation">.</span>show<span class="token punctuation">(</span><span class="token punctuation">)</span></code></pre><hr><h2 id="5-实践避坑指南"><a href="#5-实践避坑指南" class="headerlink" title="5. 实践避坑指南"></a>5. 实践避坑指南</h2><h3 id="5-1-必须先归一化-Normalization"><a href="#5-1-必须先归一化-Normalization" class="headerlink" title="5.1 必须先归一化 (Normalization)"></a>5.1 必须先归一化 (Normalization)</h3><p>这是新手最容易犯的错误。</p><ul><li><strong>错误案例</strong>：一个特征是“年收入”（几万到几百万），另一个特征是“年龄”（0到100）。</li><li><strong>后果</strong>：PCA 算方差时，会发现“年收入”的方差是 $10^{10}$ 级别，而“年龄”只有 $10^2$。于是 PCA 认为只有“年收入”重要，直接把“年龄”当噪声扔了。</li><li><strong>正确做法</strong>：使用 <code>StandardScaler</code>，把所有特征都缩放到 均值=0，方差=1。让大家站在同一起跑线上比拼相关性。</li></ul><p><img src="https://img.alicdn.com/imgextra/i4/O1CN01f8nkcs1DpsSylT7pM_!!6000000000266-2-tps-1376-768.png" alt="归一化的重要性"><br><em>(图注：左图未归一化，年收入主导了 PCA；右图归一化后，两个特征公平竞争。)</em></p><h3 id="5-2-如何选择-K-值？-方差解释率"><a href="#5-2-如何选择-K-值？-方差解释率" class="headerlink" title="5.2 如何选择 K 值？(方差解释率)"></a>5.2 如何选择 K 值？(方差解释率)</h3><p>我们不需要拍脑袋决定降到几维。PCA 给了我们一个科学的指标：<strong>解释方差比 (Explained Variance Ratio)</strong>。</p><ul><li><strong>可视化法</strong>：画出 <code>cumsum(pca.explained_variance_ratio_)</code> 曲线。</li><li><strong>95% 准则</strong>：通常我们希望保留原始数据 <strong>95%</strong> 的信息量。<ul><li>在 sklearn 中，你可以直接设 <code>PCA(n_components=0.95)</code>。</li><li>算法会自动计算需要多少个维度才能凑够 95% 的方差。</li><li>对于 768 维的 BERT 向量，通常 50-100 维就能保留 99% 的信息。这说明 BERT 向量里有大量的冗余！</li></ul></li></ul><p><img src="https://img.alicdn.com/imgextra/i3/O1CN01m1Aq4F1vhUIymIOC6_!!6000000006204-2-tps-1376-768.png" alt="PCA 方差解释率"><br><em>(图注：横轴是维度数量，纵轴是累计解释的方差百分比。曲线通常在开始时急剧上升，然后变平。拐点处就是最佳截断点。)</em></p><h3 id="5-3-PCA-作为“前菜”"><a href="#5-3-PCA-作为“前菜”" class="headerlink" title="5.3 PCA 作为“前菜”"></a>5.3 PCA 作为“前菜”</h3><p>在跑 t-SNE 或 UMAP 之前，通常<strong>强烈建议先跑一遍 PCA</strong>。</p><ul><li><strong>目的</strong>：把维度从 10000 (图片) 或 1536 (文本) 降到 50。</li><li><strong>好处</strong>：<ol><li><strong>去噪</strong>：扔掉了那些只有 0.01% 方差的噪声维度，让后续算法看得更清楚。</li><li><strong>加速</strong>：t-SNE/UMAP 的计算复杂度与维度有关。先降维能让后续算法快几十倍。</li></ol></li></ul><hr><h2 id="下一章预告"><a href="#下一章预告" class="headerlink" title="下一章预告"></a>下一章预告</h2><p>PCA 只能处理“平坦”的数据。如果数据像一个<strong>瑞士卷蛋糕</strong>一样卷在一起，PCA 这一刀切下去，红色的层和蓝色的层就混在一起了。<br>为了小心翼翼地展开这个卷，我们需要引入<strong>测地距离</strong>的概念。这就进入了<strong>流形学习</strong>的领域。</p><p>👉 <a href="https://yeee.wang/posts/6d4e.html">第 11 章：非线性降维基础</a></p>]]></content>
    
    <summary type="html">
    
      第 10 章：线性降维：PCA (Principal Component Analysis)
“最好的数据压缩算法，不是 zip，而是理解数据的结构。”

我们生活在一个高维数据爆炸的时代。

 * 图像：一张 100x100 像素的小头像，如果展平，就是 10,000 维的向量。
 * 文本：一段包含 512 个 token 的文本，如果用 Embedding 表示，通常是 768 维或 1536 维。
 * 用户画像：一个电商用户的特征，可能包含点击历史、购买力、地理位置等几千个指标。

在这些成千上万的维度中，往往充斥着冗余（比如“出生年份”和“年龄”完全相关）和噪声（比如图片边缘的随机
    
    </summary>
    
      <category term="算法" scheme="https://yeee.wang/categories/%E7%AE%97%E6%B3%95/"/>
    
    
      <category term="算法" scheme="https://yeee.wang/tags/%E7%AE%97%E6%B3%95/"/>
    
      <category term="数据分析" scheme="https://yeee.wang/tags/%E6%95%B0%E6%8D%AE%E5%88%86%E6%9E%90/"/>
    
      <category term="机器学习" scheme="https://yeee.wang/tags/%E6%9C%BA%E5%99%A8%E5%AD%A6%E4%B9%A0/"/>
    
  </entry>
  
  <entry>
    <title>第 09 章：聚类评估方法</title>
    <link href="https://yeee.wang/posts/0aa7.html"/>
    <id>https://yeee.wang/posts/0aa7.html</id>
    <published>2025-12-22T09:40:00.000Z</published>
    <updated>2026-02-19T20:00:41.879Z</updated>
    
    <content type="html"><![CDATA[<h1 id="第-09-章：聚类评估方法-Cluster-Evaluation"><a href="#第-09-章：聚类评估方法-Cluster-Evaluation" class="headerlink" title="第 09 章：聚类评估方法 (Cluster Evaluation)"></a>第 09 章：聚类评估方法 (Cluster Evaluation)</h1><blockquote><p>“没有标准答案的考试，该怎么评分？”</p></blockquote><p>在监督学习（如猫狗分类）中，评估很简单：你猜对了多少个？Accuracy = 95%。<br>但在无监督学习中，我们没有 <strong>Ground Truth</strong>（真实标签）。<br>机器把数据分成了 3 堆，你怎么知道分得对不对？也许实际上应该是 4 堆？或者那两个点不该在一起？</p><p>本章我们将介绍一套<strong>系统化</strong>的评估体系。既然没有外部答案，我们就从内部结构、稳定性、业务价值等多个维度来审视聚类结果。</p><p><img src="https://img.alicdn.com/imgextra/i2/O1CN01GKnRXc28uOTEV8wOb_!!6000000007992-2-tps-1376-768.png" alt="聚类评估体系"><br><em>(图注：聚类评估的四个维度：内部评估、外部评估、稳定性评估、业务评估。)</em></p><hr><h2 id="1-评估体系概览-The-Evaluation-Taxonomy"><a href="#1-评估体系概览-The-Evaluation-Taxonomy" class="headerlink" title="1. 评估体系概览 (The Evaluation Taxonomy)"></a>1. 评估体系概览 (The Evaluation Taxonomy)</h2><p>聚类评估不仅仅是算一个分数，它是一个分层的体系：</p><table><thead><tr><th style="text-align:left">评估维度</th><th style="text-align:left">核心问题</th><th style="text-align:left">常用指标/方法</th><th style="text-align:left">适用阶段</th></tr></thead><tbody><tr><td style="text-align:left"><strong>1. 内部评估</strong> (Internal)</td><td style="text-align:left">聚得紧不紧？分得开不开？</td><td style="text-align:left">Silhouette, CH, DB</td><td style="text-align:left"><strong>模型选型</strong> (调参、选 K)</td></tr><tr><td style="text-align:left"><strong>2. 外部评估</strong> (External)</td><td style="text-align:left">和专家标的标签一致吗？</td><td style="text-align:left">RI, NMI, Purity</td><td style="text-align:left"><strong>POC 验证</strong> (有少量标注数据时)</td></tr><tr><td style="text-align:left"><strong>3. 稳定性评估</strong> (Stability)</td><td style="text-align:left">数据变一点，结果会不会大变？</td><td style="text-align:left">扰动测试, 重采样</td><td style="text-align:left"><strong>模型上线前</strong> (鲁棒性检查)</td></tr><tr><td style="text-align:left"><strong>4. 业务评估</strong> (Business)</td><td style="text-align:left">对业务真的有用吗？</td><td style="text-align:left">可解释性, Actionability</td><td style="text-align:left"><strong>最终验收</strong></td></tr></tbody></table><hr><h2 id="2-内部评估：没有答案怎么打分？"><a href="#2-内部评估：没有答案怎么打分？" class="headerlink" title="2. 内部评估：没有答案怎么打分？"></a>2. 内部评估：没有答案怎么打分？</h2><p>这是最常用的评估方式。我们的普世审美标准是：<strong>“物以类聚 (Cohesion)，人以群分 (Separation)”</strong>。</p><h3 id="2-1-轮廓系数-Silhouette-Coefficient-——-精细的个体体检"><a href="#2-1-轮廓系数-Silhouette-Coefficient-——-精细的个体体检" class="headerlink" title="2.1 轮廓系数 (Silhouette Coefficient) —— 精细的个体体检"></a>2.1 轮廓系数 (Silhouette Coefficient) —— 精细的个体体检</h3><p>它不是给班级打分，而是给<strong>每个学生</strong>打分。它能画出一张“轮廓图”，让你一眼看出哪个班级很松散。</p><ul><li><p><strong>计算逻辑</strong>：<br>对于点 $i$：</p><ul><li>$a(i)$：<strong>内卷程度</strong>。我离同班同学平均有多近？（越小越好）</li><li>$b(i)$：<strong>排外程度</strong>。我离隔壁班同学平均有多远？（越大越好）</li><li>$$ s(i) = \frac{b(i) - a(i)}{\max(a(i), b(i))} $$</li></ul></li><li><p><strong>深度解读</strong>：</p><ul><li><strong>接近 +1</strong>：完美。我稳稳地坐在班级中心。</li><li><strong>接近 0</strong>：<strong>骑墙派</strong>。我站在两个班级的走廊上，分给谁都行。</li><li><strong>接近 -1</strong>：<strong>身在曹营心在汉</strong>。我离隔壁班比离自己班还近，肯定是分错了。</li></ul></li><li><p><strong>局限性</strong>：轮廓系数假设簇是<strong>凸形（球形）</strong>的。如果你的数据是“月牙形”的（DBSCAN 擅长的那种），轮廓系数会很低，但这不代表聚类是错的。</p></li></ul><p><img src="https://img.alicdn.com/imgextra/i4/O1CN01qyEr8X23hxdko87Dx_!!6000000007288-2-tps-1376-768.png" alt="轮廓系数原理"><br><em>(图注：a(i) 是点到同簇的平均距离，b(i) 是到最近邻簇的距离。s(i) 接近 1 表示归属感强。)</em></p><h3 id="2-2-Calinski-Harabasz-CH-Index-——-统计学的严谨"><a href="#2-2-Calinski-Harabasz-CH-Index-——-统计学的严谨" class="headerlink" title="2.2 Calinski-Harabasz (CH) Index —— 统计学的严谨"></a>2.2 Calinski-Harabasz (CH) Index —— 统计学的严谨</h3><p>也叫方差比准则。它的核心思想源自 ANOVA（方差分析）。</p><p>$$ CH = \frac{\text{簇间方差 (Separation)}}{\text{簇内方差 (Cohesion)}} \times \frac{N-K}{K-1} $$</p><ul><li><strong>核心思想</strong>：我们希望<strong>簇间方差大</strong>（不同簇差异明显），<strong>簇内方差小</strong>（簇内非常团结）。</li><li><strong>优点</strong>：计算只涉及矩阵运算，<strong>速度极快</strong>。在千万级数据上，它是唯一跑得动的指标。</li></ul><h3 id="2-3-Davies-Bouldin-DB-Index-——-寻找最差短板"><a href="#2-3-Davies-Bouldin-DB-Index-——-寻找最差短板" class="headerlink" title="2.3 Davies-Bouldin (DB) Index —— 寻找最差短板"></a>2.3 Davies-Bouldin (DB) Index —— 寻找最差短板</h3><p>它是一种<strong>悲观</strong>的指标。它不看平均情况，而是去挑刺：对于每一个簇，它都去寻找<strong>和它最像（最难区分）</strong>的那个簇，计算它们的相似度。</p><ul><li><strong>判定</strong>：DB 指数是所有“最大相似度”的平均值。数值<strong>越小越好</strong>（0 代表完美分离）。</li></ul><hr><h2 id="3-外部评估：如果有“上帝视角”"><a href="#3-外部评估：如果有“上帝视角”" class="headerlink" title="3. 外部评估：如果有“上帝视角”"></a>3. 外部评估：如果有“上帝视角”</h2><p>在项目初期（POC 阶段），我们通常会人工标注一小部分数据（比如 500 条），作为 Ground Truth。这时我们可以用外部指标来验证算法。</p><h3 id="3-1-Rand-Index-RI-amp-ARI"><a href="#3-1-Rand-Index-RI-amp-ARI" class="headerlink" title="3.1 Rand Index (RI) &amp; ARI"></a>3.1 Rand Index (RI) &amp; ARI</h3><ul><li><strong>Rand Index (RI)</strong>：简单的准确率。<ul><li>机器判定“在一起”且人工也判定“在一起”的对子数 + 机器判定“分开”且人工也判定“分开”的对子数 / 总对子数。</li></ul></li><li><strong>Adjusted Rand Index (ARI)</strong>：<strong>推荐使用</strong>。<ul><li>RI 有个问题：即使随机乱猜，RI 也不会是 0（因为总能蒙对一些）。</li><li>ARI 对随机猜测进行了校正。<strong>ARI = 0 表示随机乱猜，ARI = 1 表示完美匹配。</strong></li></ul></li></ul><h3 id="3-2-归一化互信息-NMI"><a href="#3-2-归一化互信息-NMI" class="headerlink" title="3.2 归一化互信息 (NMI)"></a>3.2 归一化互信息 (NMI)</h3><ul><li>源自信息论。它衡量的是：<strong>“知道了聚类结果，能给推断真实标签带来多少信息量？”</strong></li><li><strong>特点</strong>：NMI 对簇的数量不敏感，适合对比 K 值不同的聚类结果。</li></ul><p><img src="https://img.alicdn.com/imgextra/i1/O1CN01cdsyof1pvKa7NxmGc_!!6000000005422-2-tps-1376-768.png" alt="外部评估 ARI"><br><em>(图注：ARI 校正了随机猜测的影响。ARI=0 表示随机，ARI=1 表示完美匹配。)</em></p><hr><h2 id="4-稳定性评估：经得起考验吗？"><a href="#4-稳定性评估：经得起考验吗？" class="headerlink" title="4. 稳定性评估：经得起考验吗？"></a>4. 稳定性评估：经得起考验吗？</h2><p>这是新手最容易忽略的一环。一个好的聚类模型，应该是<strong>稳健</strong>的。</p><p><strong>测试方法（扰动测试）</strong>：</p><ol><li><strong>随机采样</strong>：从原数据中随机抽取 90% 的数据。</li><li><strong>重新聚类</strong>：用同样的参数跑一遍 K-Means。</li><li><strong>对比结果</strong>：对比这两次聚类的中心点位置、簇的成员构成。<ul><li><strong>如果不稳定</strong>：仅仅少了一点数据，聚类结果就面目全非（比如簇 A 突然分裂成了两个，或者簇 B 消失了）。说明目前的聚类结构是<strong>偶然</strong>的，不可靠。</li><li><strong>如果稳定</strong>：说明我们要找的结构是真实存在于数据分布中的。</li></ul></li></ol><p><img src="https://img.alicdn.com/imgextra/i1/O1CN01knGV1X1HoHY3ShQ0c_!!6000000000804-2-tps-1376-768.png" alt="稳定性测试"><br><em>(图注：扰动测试流程：采样 90% 数据重新聚类，对比结果是否稳定。)</em></p><hr><h2 id="5-K-值的选择艺术"><a href="#5-K-值的选择艺术" class="headerlink" title="5. K 值的选择艺术"></a>5. K 值的选择艺术</h2><h3 id="5-1-肘部法则-Elbow-Method"><a href="#5-1-肘部法则-Elbow-Method" class="headerlink" title="5.1 肘部法则 (Elbow Method)"></a>5.1 肘部法则 (Elbow Method)</h3><p>我们画出 $K$ 与 <strong>Inertia</strong> (簇内误差平方和 SSE) 的关系图。<br>我们要找的是<strong>性价比最高</strong>的点（收益递减点）。就像人的手肘：在此之前，增加 K 能大幅降低误差；在手肘之后，增加 K 只是微小的优化，不划算。</p><p><img src="https://img.alicdn.com/imgextra/i4/O1CN01JbX8Qm28INb4965in_!!6000000007909-2-tps-1376-768.png" alt="肘部法则示意图"><br><em>(图注：在 K=3 处出现明显的拐点。此处即为最佳 K 值。)</em></p><h3 id="5-2-为什么不能完全听指标的？"><a href="#5-2-为什么不能完全听指标的？" class="headerlink" title="5.2 为什么不能完全听指标的？"></a>5.2 为什么不能完全听指标的？</h3><p><strong>这是新手的最大误区</strong>。<br>在实际项目中，如果用肘部法则，可能算出来最佳 K=5 或 K=8。<br>但我们在第 2 章提到，我们最终强行设了 <strong>K=80</strong>。这是否违反了科学精神？</p><p><strong>没有。因为业务需求 &gt; 数学指标。</strong></p><ul><li><strong>数学视角</strong>：把所有“投诉”归为一类 (K=1)，非常紧凑，轮廓系数可能最高。</li><li><strong>业务视角</strong>：这毫无意义。我要区分“丢件”、“破损”、“慢”。即使它们在语义空间上很像（导致数学上很难分开，轮廓系数降低），我也必须把它们拆开。</li></ul><p><strong>结论</strong>：无监督学习的评估指标仅供参考。最终的评判标准是 <strong>可解释性 (Interpretability)</strong> 和 <strong>可落地性 (Actionability)</strong>。如果 K=80 能帮运营人员发现新问题，那它就是最好的 K。</p><hr><h2 id="6-代码实战：全家桶评估"><a href="#6-代码实战：全家桶评估" class="headerlink" title="6. 代码实战：全家桶评估"></a>6. 代码实战：全家桶评估</h2><pre class=" language-python"><code class="language-python"><span class="token keyword">from</span> sklearn <span class="token keyword">import</span> datasets<span class="token keyword">from</span> sklearn<span class="token punctuation">.</span>cluster <span class="token keyword">import</span> KMeans<span class="token keyword">from</span> sklearn<span class="token punctuation">.</span>metrics <span class="token keyword">import</span> silhouette_score<span class="token punctuation">,</span> calinski_harabasz_score<span class="token punctuation">,</span> davies_bouldin_score<span class="token punctuation">,</span> adjusted_rand_score<span class="token keyword">import</span> matplotlib<span class="token punctuation">.</span>pyplot <span class="token keyword">as</span> plt<span class="token keyword">import</span> numpy <span class="token keyword">as</span> np<span class="token comment" spellcheck="true"># 1. 生成模拟数据 (带真实标签 y_true，用于演示外部评估)</span>X<span class="token punctuation">,</span> y_true <span class="token operator">=</span> datasets<span class="token punctuation">.</span>make_blobs<span class="token punctuation">(</span>n_samples<span class="token operator">=</span><span class="token number">1000</span><span class="token punctuation">,</span> centers<span class="token operator">=</span><span class="token number">4</span><span class="token punctuation">,</span> random_state<span class="token operator">=</span><span class="token number">42</span><span class="token punctuation">)</span><span class="token comment" spellcheck="true"># 2. 跑 K-Means</span>kmeans <span class="token operator">=</span> KMeans<span class="token punctuation">(</span>n_clusters<span class="token operator">=</span><span class="token number">4</span><span class="token punctuation">,</span> random_state<span class="token operator">=</span><span class="token number">42</span><span class="token punctuation">)</span><span class="token punctuation">.</span>fit<span class="token punctuation">(</span>X<span class="token punctuation">)</span>labels <span class="token operator">=</span> kmeans<span class="token punctuation">.</span>labels_<span class="token comment" spellcheck="true"># ==========================================</span><span class="token comment" spellcheck="true"># Part A: 内部评估 (假设没有标签)</span><span class="token comment" spellcheck="true"># ==========================================</span><span class="token keyword">print</span><span class="token punctuation">(</span><span class="token string">"--- 内部评估 (Internal) ---"</span><span class="token punctuation">)</span><span class="token comment" spellcheck="true"># 1. 轮廓系数 (Silhouette)</span><span class="token comment" spellcheck="true"># 注意：O(N^2) 复杂度，大数据请采样 sample_size=10000</span>sil <span class="token operator">=</span> silhouette_score<span class="token punctuation">(</span>X<span class="token punctuation">,</span> labels<span class="token punctuation">)</span><span class="token keyword">print</span><span class="token punctuation">(</span>f<span class="token string">"Silhouette Coefficient (接近1好): &amp;#123;sil:.3f&amp;#125;"</span><span class="token punctuation">)</span><span class="token comment" spellcheck="true"># 2. CH 指数 (Calinski-Harabasz)</span><span class="token comment" spellcheck="true"># 计算超快，适合大数据</span>ch <span class="token operator">=</span> calinski_harabasz_score<span class="token punctuation">(</span>X<span class="token punctuation">,</span> labels<span class="token punctuation">)</span><span class="token keyword">print</span><span class="token punctuation">(</span>f<span class="token string">"Calinski-Harabasz Index (越大越好): &amp;#123;ch:.1f&amp;#125;"</span><span class="token punctuation">)</span><span class="token comment" spellcheck="true"># 3. DB 指数 (Davies-Bouldin)</span>db <span class="token operator">=</span> davies_bouldin_score<span class="token punctuation">(</span>X<span class="token punctuation">,</span> labels<span class="token punctuation">)</span><span class="token keyword">print</span><span class="token punctuation">(</span>f<span class="token string">"Davies-Bouldin Index (越小越好): &amp;#123;db:.3f&amp;#125;"</span><span class="token punctuation">)</span><span class="token comment" spellcheck="true"># ==========================================</span><span class="token comment" spellcheck="true"># Part B: 外部评估 (假设有少量人工标注标签)</span><span class="token comment" spellcheck="true"># ==========================================</span><span class="token keyword">print</span><span class="token punctuation">(</span><span class="token string">"\n--- 外部评估 (External) ---"</span><span class="token punctuation">)</span><span class="token comment" spellcheck="true"># 1. Adjusted Rand Index (ARI)</span>ari <span class="token operator">=</span> adjusted_rand_score<span class="token punctuation">(</span>y_true<span class="token punctuation">,</span> labels<span class="token punctuation">)</span><span class="token keyword">print</span><span class="token punctuation">(</span>f<span class="token string">"Adjusted Rand Index (1为完美): &amp;#123;ari:.3f&amp;#125;"</span><span class="token punctuation">)</span><span class="token comment" spellcheck="true"># 如果结果是 0.95，说明机器聚出来的簇和专家标注几乎一致。</span><span class="token comment" spellcheck="true"># ==========================================</span><span class="token comment" spellcheck="true"># Part C: 稳定性评估 (Stability)</span><span class="token comment" spellcheck="true"># ==========================================</span><span class="token keyword">print</span><span class="token punctuation">(</span><span class="token string">"\n--- 稳定性评估 (Stability) ---"</span><span class="token punctuation">)</span><span class="token comment" spellcheck="true"># 随机删掉 10% 的数据，再跑一次</span>indices <span class="token operator">=</span> np<span class="token punctuation">.</span>random<span class="token punctuation">.</span>choice<span class="token punctuation">(</span>X<span class="token punctuation">.</span>shape<span class="token punctuation">[</span><span class="token number">0</span><span class="token punctuation">]</span><span class="token punctuation">,</span> size<span class="token operator">=</span>int<span class="token punctuation">(</span>X<span class="token punctuation">.</span>shape<span class="token punctuation">[</span><span class="token number">0</span><span class="token punctuation">]</span><span class="token operator">*</span><span class="token number">0.9</span><span class="token punctuation">)</span><span class="token punctuation">,</span> replace<span class="token operator">=</span><span class="token boolean">False</span><span class="token punctuation">)</span>X_sub <span class="token operator">=</span> X<span class="token punctuation">[</span>indices<span class="token punctuation">]</span>kmeans_sub <span class="token operator">=</span> KMeans<span class="token punctuation">(</span>n_clusters<span class="token operator">=</span><span class="token number">4</span><span class="token punctuation">,</span> random_state<span class="token operator">=</span><span class="token number">42</span><span class="token punctuation">)</span><span class="token punctuation">.</span>fit<span class="token punctuation">(</span>X_sub<span class="token punctuation">)</span><span class="token comment" spellcheck="true"># (此处通常需要更复杂的对齐算法来比较两个模型，略)</span><span class="token keyword">print</span><span class="token punctuation">(</span><span class="token string">"稳定性检查：观察中心点是否剧烈漂移..."</span><span class="token punctuation">)</span><span class="token keyword">print</span><span class="token punctuation">(</span>f<span class="token string">"原中心点:\n&amp;#123;kmeans.cluster_centers_[0]&amp;#125;"</span><span class="token punctuation">)</span><span class="token keyword">print</span><span class="token punctuation">(</span>f<span class="token string">"新中心点:\n&amp;#123;kmeans_sub.cluster_centers_[0]&amp;#125;"</span><span class="token punctuation">)</span></code></pre><hr><h2 id="7-总结对比表"><a href="#7-总结对比表" class="headerlink" title="7. 总结对比表"></a>7. 总结对比表</h2><table><thead><tr><th style="text-align:left">指标类型</th><th style="text-align:left">指标</th><th style="text-align:left">目标</th><th style="text-align:left">速度</th><th style="text-align:left">核心价值</th></tr></thead><tbody><tr><td style="text-align:left"><strong>内部指标</strong></td><td style="text-align:left"><strong>Silhouette</strong></td><td style="text-align:left">接近 1</td><td style="text-align:left"><strong>慢</strong> $O(N^2)$</td><td style="text-align:left">衡量个体归属感，适合小数据精细评估</td></tr><tr><td style="text-align:left"></td><td style="text-align:left"><strong>CH Index</strong></td><td style="text-align:left">越大越好</td><td style="text-align:left"><strong>快</strong> $O(N)$</td><td style="text-align:left">衡量整体方差比，适合大数据</td></tr><tr><td style="text-align:left"></td><td style="text-align:left"><strong>DB Index</strong></td><td style="text-align:left">越小越好</td><td style="text-align:left"><strong>快</strong> $O(N)$</td><td style="text-align:left">衡量最差分离度，寻找短板</td></tr><tr><td style="text-align:left"><strong>外部指标</strong></td><td style="text-align:left"><strong>ARI</strong></td><td style="text-align:left">接近 1</td><td style="text-align:left">快</td><td style="text-align:left"><strong>金标准</strong>（如果有标签），验证算法能力</td></tr><tr><td style="text-align:left"></td><td style="text-align:left"><strong>NMI</strong></td><td style="text-align:left">接近 1</td><td style="text-align:left">快</td><td style="text-align:left">信息论视角，适合不同 K 值对比</td></tr><tr><td style="text-align:left"><strong>选择方法</strong></td><td style="text-align:left"><strong>肘部法则</strong></td><td style="text-align:left">找拐点</td><td style="text-align:left">-</td><td style="text-align:left">平衡误差与复杂度，确定最佳 K</td></tr><tr><td style="text-align:left"><strong>终极裁判</strong></td><td style="text-align:left"><strong>业务价值</strong></td><td style="text-align:left"><strong>有用</strong></td><td style="text-align:left">-</td><td style="text-align:left"><strong>最终标准</strong>：可解释、可落地、稳定性</td></tr></tbody></table><hr><h2 id="下一章预告"><a href="#下一章预告" class="headerlink" title="下一章预告"></a>下一章预告</h2><p>至此，我们完成了聚类算法的学习。<br>接下来，我们将进入无监督学习的另一大支柱——<strong>降维</strong>。</p><p>我们一直在说“先降维再聚类”。</p><ul><li>降维到底是怎么把 1536 个数字变成 2 个数字的？</li><li>为什么降维后还能保留原有的含义？</li><li>PCA 又是如何像投影仪一样工作的？</li></ul><p>👉 <a href="https://yeee.wang/posts/432e.html">第 10 章：线性降维：PCA</a></p>]]></content>
    
    <summary type="html">
    
      第 09 章：聚类评估方法 (Cluster Evaluation)
“没有标准答案的考试，该怎么评分？”

在监督学习（如猫狗分类）中，评估很简单：你猜对了多少个？Accuracy = 95%。
但在无监督学习中，我们没有 Ground Truth（真实标签）。
机器把数据分成了 3 堆，你怎么知道分得对不对？也许实际上应该是 4 堆？或者那两个点不该在一起？

本章我们将介绍一套系统化的评估体系。既然没有外部答案，我们就从内部结构、稳定性、业务价值等多个维度来审视聚类结果。


(图注：聚类评估的四个维度：内部评估、外部评估、稳定性评估、业务评估。)




1. 评估体系概览 (The 
    
    </summary>
    
      <category term="算法" scheme="https://yeee.wang/categories/%E7%AE%97%E6%B3%95/"/>
    
    
      <category term="算法" scheme="https://yeee.wang/tags/%E7%AE%97%E6%B3%95/"/>
    
      <category term="数据分析" scheme="https://yeee.wang/tags/%E6%95%B0%E6%8D%AE%E5%88%86%E6%9E%90/"/>
    
      <category term="机器学习" scheme="https://yeee.wang/tags/%E6%9C%BA%E5%99%A8%E5%AD%A6%E4%B9%A0/"/>
    
  </entry>
  
  <entry>
    <title>第 08 章：概率模型聚类</title>
    <link href="https://yeee.wang/posts/6897.html"/>
    <id>https://yeee.wang/posts/6897.html</id>
    <published>2025-12-22T09:35:00.000Z</published>
    <updated>2026-02-19T20:00:41.879Z</updated>
    
    <content type="html"><![CDATA[<h1 id="第-08-章：概率模型聚类-Probabilistic-Clustering"><a href="#第-08-章：概率模型聚类-Probabilistic-Clustering" class="headerlink" title="第 08 章：概率模型聚类 (Probabilistic Clustering)"></a>第 08 章：概率模型聚类 (Probabilistic Clustering)</h1><blockquote><p>“上帝不掷骰子，但数据科学家掷。” —— 改编自爱因斯坦</p></blockquote><p>之前的聚类算法（K-Means, DBSCAN）都有一个共同特征：<strong>硬聚类 (Hard Clustering)</strong>。<br>一个样本要么属于 A，要么属于 B，没有中间地带。<br>这就像把人简单分为“好人”和“坏人”，丢失了人性的复杂灰度。</p><p>在实际的文本分析场景中，我们经常遇到这种情况：</p><blockquote><p>用户工单：”快递员态度太差了，而且还不给我送上楼，我要退款！”</p></blockquote><p>这句话既涉及【物流服务】，又涉及【退款流程】。如果硬把它归为某一类，就会丢失另一半信息。<br>本章我们将介绍 <strong>高斯混合模型 (GMM, Gaussian Mixture Model)</strong>，它引入了 <strong>软聚类 (Soft Clustering)</strong> 的概念，告诉我们：这个样本 <strong>70%</strong> 是物流问题，<strong>30%</strong> 是退款问题。</p><p><img src="https://img.alicdn.com/imgextra/i2/O1CN01XLaKvb1hfHmOgPWqK_!!6000000004304-2-tps-1376-768.png" alt="GMM 软聚类示意图"><br><em>(图注：软聚类允许一个点同时属于多个簇。颜色混合的区域表示不确定性高的“骑墙派”。)</em></p><hr><h2 id="1-核心概念：世界是由“钟形曲线”组成的"><a href="#1-核心概念：世界是由“钟形曲线”组成的" class="headerlink" title="1. 核心概念：世界是由“钟形曲线”组成的"></a>1. 核心概念：世界是由“钟形曲线”组成的</h2><h3 id="1-1-什么是-GMM？"><a href="#1-1-什么是-GMM？" class="headerlink" title="1.1 什么是 GMM？"></a>1.1 什么是 GMM？</h3><p>GMM 的核心假设非常简单：数据不是一堆无规律的石头，而是一堆叠加在一起的<strong>波</strong>。<br>具体来说，所有数据都是由 $K$ 个<strong>高斯分布</strong>（Gaussian Distribution，也就是正态分布/钟形曲线）混合而成的。</p><p>想象你在操场上看到一堆人。</p><ul><li>有一群打篮球的男生，平均身高 180cm（波峰），但也有些矮个和高个（波宽）。</li><li>有一群跳健美操的女生，平均身高 165cm。</li><li>如果你只看身高数据，你会看到两个叠加在一起的“钟形曲线”。</li></ul><p>GMM 的任务就是把这两个混在一起的钟形曲线<strong>拆解</strong>出来。</p><h3 id="1-2-三个关键参数：给簇“塑形”"><a href="#1-2-三个关键参数：给簇“塑形”" class="headerlink" title="1.2 三个关键参数：给簇“塑形”"></a>1.2 三个关键参数：给簇“塑形”</h3><p>为了描述一个高斯分布（一个簇），我们需要三个参数。我们可以把这想象成捏泥人：</p><ol><li><strong>$\mu$ (均值 Mean)</strong>：<strong>位置</strong>。决定了钟形曲线的最高点（簇中心）在哪里。</li><li><strong>$\Sigma$ (协方差 Covariance)</strong>：<strong>形状</strong>。<ul><li>决定了钟形曲线是胖是瘦，是圆是扁。</li><li><strong>这是 GMM 相比 K-Means 的最大优势</strong>。K-Means 只能找正圆形的簇，而 GMM 通过协方差矩阵，可以拟合细长的椭圆形。</li></ul></li><li><strong>$\pi$ (权重 Weight)</strong>：<strong>高度</strong>。决定了这个簇里有多少人。</li></ol><p>$$ P(x) = \sum_{k=1}^K \pi_k \mathcal{N}(x | \mu_k, \Sigma_k) $$</p><p><em>(公式含义：任意一个点出现的概率，等于它属于第1个簇的概率 + 属于第2个簇的概率 + …)</em></p><p><img src="https://img.alicdn.com/imgextra/i4/O1CN01QazpvZ1Desxdnv6ml_!!6000000000242-2-tps-1376-768.png" alt="GMM 三大参数"><br><em>(图注：μ 决定簇的位置，Σ 决定簇的形状（圆形/椭圆），π 决定簇的权重大小。)</em></p><hr><h2 id="2-怎么解？EM-算法的“食堂打饭”隐喻"><a href="#2-怎么解？EM-算法的“食堂打饭”隐喻" class="headerlink" title="2. 怎么解？EM 算法的“食堂打饭”隐喻"></a>2. 怎么解？EM 算法的“食堂打饭”隐喻</h2><p>我们要解出这三个参数，但是有个“鸡生蛋，蛋生鸡”的难题：</p><ul><li><strong>蛋 (标签)</strong>：如果我们知道每个人属于哪一队（篮球/健美操），我们就能算出每一队的平均身高 ($\mu$) 和身形标准差 ($\Sigma$)。</li><li><strong>鸡 (参数)</strong>：如果我们知道每一队的平均身高和身形标准差，我们就能算出某个人属于哪一队的概率。</li><li><strong>死锁</strong>：现在我们既不知道标签，也不知道参数。</li></ul><p>于是我们有了 <strong>EM 算法 (Expectation-Maximization)</strong>。这就好比食堂排队打饭：</p><p><img src="https://img.alicdn.com/imgextra/i3/O1CN012DQ3mL1cjnbQK8dxY_!!6000000003637-2-tps-1376-768.png" alt="EM 算法流程"><br><em>(图注：EM 算法是一个迭代优化的过程。E步计算归属概率，M步更新分布参数，以此往复直到收敛。)</em></p><ol><li><strong>初始化 (瞎猜)</strong>：随便先画两个圈，假装这是两个队。</li><li><strong>E-Step (期望步 / 站队)</strong>：<ul><li>根据目前的圈（参数），每个人计算自己属于每个圈的概率。</li><li>“我身高 175，看起来有 60% 的可能属于篮球队，40% 的可能属于健美操队。”（注意：这里不是非黑即白，而是按比例站队）。</li></ul></li><li><strong>M-Step (最大化步 / 调整圈)</strong>：<ul><li>根据大家站队的结果（加权），重新计算每个圈的中心和形状。</li><li>“既然这 0.6 个人属于篮球队，那篮球队的平均身高得往他这边挪一点点。”</li></ul></li><li><strong>Loop (循环)</strong>：重复 2 和 3，直到大家都不再换队，圈也不再移动（收敛）。</li></ol><hr><h2 id="3-技术对比：K-Means-vs-GMM"><a href="#3-技术对比：K-Means-vs-GMM" class="headerlink" title="3. 技术对比：K-Means vs GMM"></a>3. 技术对比：K-Means vs GMM</h2><table><thead><tr><th style="text-align:left">特性</th><th style="text-align:left">K-Means</th><th style="text-align:left">GMM (高斯混合模型)</th></tr></thead><tbody><tr><td style="text-align:left"><strong>聚类性质</strong></td><td style="text-align:left"><strong>硬聚类</strong> (0 或 1)</td><td style="text-align:left"><strong>软聚类</strong> (概率分布，如 [0.8, 0.2])</td></tr><tr><td style="text-align:left"><strong>簇形状</strong></td><td style="text-align:left">只能是<strong>正圆</strong> (球体)</td><td style="text-align:left">可以是任意方向的<strong>椭圆</strong></td></tr><tr><td style="text-align:left"><strong>参数量</strong></td><td style="text-align:left">少 (只有中心)</td><td style="text-align:left">多 (中心 + 协方差矩阵)</td></tr><tr><td style="text-align:left"><strong>计算速度</strong></td><td style="text-align:left">快 (线性复杂度)</td><td style="text-align:left">较慢 (涉及矩阵求逆，迭代次数多)</td></tr><tr><td style="text-align:left"><strong>适用场景</strong></td><td style="text-align:left">大规模数据，简单的硬划分</td><td style="text-align:left">需要概率输出，簇有重叠，形状扁长</td></tr><tr><td style="text-align:left"><strong>本质关系</strong></td><td style="text-align:left">K-Means 是 GMM 的<strong>特例</strong></td><td style="text-align:left">当 GMM 的方差极小且相等时，就变成了 K-Means</td></tr></tbody></table><p><img src="https://img.alicdn.com/imgextra/i2/O1CN01QZS0BG1c1pa6rKsca_!!6000000003541-2-tps-1376-768.png" alt="K-Means vs GMM 簇形状对比"><br><em>(图注：左图 K-Means 强行把扁长的簇切成了两半；右图 GMM 完美拟合了椭圆形的分布。)</em></p><hr><h2 id="4-实际应用场景：模糊地带的价值"><a href="#4-实际应用场景：模糊地带的价值" class="headerlink" title="4. 实际应用场景：模糊地带的价值"></a>4. 实际应用场景：模糊地带的价值</h2><p>虽然在通用聚类任务上 K-Means 是老大，但在以下场景中，GMM 的“骑墙派”特性具有极大的商业价值。</p><h3 id="场景-A：用户画像中的“多重身份”"><a href="#场景-A：用户画像中的“多重身份”" class="headerlink" title="场景 A：用户画像中的“多重身份”"></a>场景 A：用户画像中的“多重身份”</h3><p>有些用户是复杂的。</p><ul><li><strong>K-Means</strong>：把用户 A 强行归为“价格敏感型”。</li><li><strong>GMM</strong>：告诉我们用户 A <code>[0.6 价格敏感, 0.4 品质追求]</code>。</li><li><strong>决策</strong>：对于这种用户，我们不仅要推优惠券（满足 0.6），还要推一些高性价比的品牌货（满足 0.4），而不是只推 9.9 包邮的垃圾。</li></ul><h3 id="场景-B：异常检测与风控"><a href="#场景-B：异常检测与风控" class="headerlink" title="场景 B：异常检测与风控"></a>场景 B：异常检测与风控</h3><p>GMM 可以输出每个样本的<strong>似然度 (Likelihood)</strong>，也就是“这个样本属于当前这个世界的概率”。</p><ul><li>如果一个样本算出来的概率极低（比如 $10^{-5}$），说明它不属于任何一个现有的正常簇。</li><li><strong>结论</strong>：这大概率是一笔<strong>欺诈交易</strong>或者一个<strong>异常工单</strong>。</li></ul><h3 id="场景-C：不确定性筛选"><a href="#场景-C：不确定性筛选" class="headerlink" title="场景 C：不确定性筛选"></a>场景 C：不确定性筛选</h3><ul><li><strong>策略</strong>：筛选出那些最大概率小于 0.6 的样本（即 <code>max(probs) &lt; 0.6</code>）。</li><li><strong>含义</strong>：这些样本是模型“拿不准”的。它们往往是<strong>脏数据</strong>，或者是蕴含了新趋势的<strong>边缘案例</strong>，值得在这个子集上投入人工复核。</li></ul><p><img src="https://img.alicdn.com/imgextra/i1/O1CN01SrWSQ71R5zP86I9Fi_!!6000000002061-2-tps-1376-768.png" alt="GMM 应用场景"><br><em>(图注：软聚类的概率输出在用户画像、异常检测、不确定性筛选中具有独特价值。)</em></p><hr><h2 id="5-模型选择：奥卡姆剃刀"><a href="#5-模型选择：奥卡姆剃刀" class="headerlink" title="5. 模型选择：奥卡姆剃刀"></a>5. 模型选择：奥卡姆剃刀</h2><p>GMM 也面临和 K-Means 一样的问题：K (n_components) 选多少？<br>K 越大，模型肯定拟合得越好（误差越小），但这可能只是死记硬背（<strong>过拟合</strong>）。我们需要一个裁判，在“拟合得好”和“模型简单”之间找平衡。这叫<strong>奥卡姆剃刀原理</strong>：<em>如无必要，勿增实体。</em></p><p>我们引入两个信息准则作为裁判：<strong>AIC</strong> 和 <strong>BIC</strong>。</p><h3 id="5-1-核心公式拆解"><a href="#5-1-核心公式拆解" class="headerlink" title="5.1 核心公式拆解"></a>5.1 核心公式拆解</h3><p>它们的公式长得很像，都由两部分组成：</p><p>$$ \text{Score} = \underbrace{-2\ln(\hat{L})}<em>{\text{坏处：拟合误差}} + \underbrace{\alpha \cdot k}</em>{\text{惩罚：模型复杂度}} $$</p><ol><li><strong>第一项（拟合误差）</strong>：$\hat{L}$ 是似然度，代表模型对数据的解释能力。拟合得越好，$\hat{L}$ 越大，$-2\ln(\hat{L})$ 就越小。<strong>我们希望这项越小越好</strong>。</li><li><strong>第二项（复杂度惩罚）</strong>：$k$ 是参数个数（模型越复杂，$k$ 越大）。<strong>我们希望这项也越小越好</strong>。</li></ol><p>所以，我们的目标是找一个 K 值，使得 <strong>AIC 或 BIC 最小</strong>。</p><h3 id="5-2-AIC-vs-BIC：选哪个裁判？"><a href="#5-2-AIC-vs-BIC：选哪个裁判？" class="headerlink" title="5.2 AIC vs BIC：选哪个裁判？"></a>5.2 AIC vs BIC：选哪个裁判？</h3><table><thead><tr><th style="text-align:left">准则</th><th style="text-align:left">全称</th><th style="text-align:left">惩罚力度</th><th style="text-align:left">倾向性</th><th style="text-align:left">隐喻</th></tr></thead><tbody><tr><td style="text-align:left"><strong>AIC</strong></td><td style="text-align:left">赤池信息量准则</td><td style="text-align:left"><strong>宽松</strong> ($\alpha=2$)</td><td style="text-align:left">允许模型稍微复杂一点，只要预测得准。</td><td style="text-align:left"><strong>“实用主义者”</strong>：只要衣服穿着好看，贵点也没关系。</td></tr><tr><td style="text-align:left"><strong>BIC</strong></td><td style="text-align:left">贝叶斯信息量准则</td><td style="text-align:left"><strong>严厉</strong> ($\alpha=\ln(n)$)</td><td style="text-align:left">随着样本量 $n$ 增加，惩罚极重。倾向于更简单的模型。</td><td style="text-align:left"><strong>“极简主义者”</strong>：衣服必须性价比极高，稍微贵一点都不要。</td></tr></tbody></table><p><strong>👑 推荐</strong>：在聚类任务中，我们通常希望找到数据的“真实结构”，而不只是为了预测。而且聚类很容易过拟合。因此，<strong>BIC 是更好的选择</strong>。它能帮你砍掉那些不必要的簇，给你一个最精简的分类。</p><p><img src="https://img.alicdn.com/imgextra/i1/O1CN01CHqmIM22VlTeh17uc_!!6000000007126-2-tps-1376-768.png" alt="BIC 模型选择"><br><em>(图注：BIC 曲线在最佳 K 处达到最低点，平衡了拟合程度和模型复杂度。)</em></p><hr><h2 id="6-代码实战：Scikit-Learn-实现"><a href="#6-代码实战：Scikit-Learn-实现" class="headerlink" title="6. 代码实战：Scikit-Learn 实现"></a>6. 代码实战：Scikit-Learn 实现</h2><pre class=" language-python"><code class="language-python"><span class="token keyword">from</span> sklearn<span class="token punctuation">.</span>mixture <span class="token keyword">import</span> GaussianMixture<span class="token keyword">from</span> sklearn<span class="token punctuation">.</span>datasets <span class="token keyword">import</span> make_blobs<span class="token keyword">import</span> numpy <span class="token keyword">as</span> np<span class="token comment" spellcheck="true"># 1. 准备数据</span>X<span class="token punctuation">,</span> _ <span class="token operator">=</span> make_blobs<span class="token punctuation">(</span>n_samples<span class="token operator">=</span><span class="token number">500</span><span class="token punctuation">,</span> centers<span class="token operator">=</span><span class="token number">3</span><span class="token punctuation">,</span> cluster_std<span class="token operator">=</span><span class="token number">1.5</span><span class="token punctuation">)</span><span class="token comment" spellcheck="true"># 2. 训练 GMM</span><span class="token comment" spellcheck="true"># n_components: 簇的数量 (K)</span><span class="token comment" spellcheck="true"># covariance_type='full': 允许任意形状的椭圆 (最灵活但参数最多)</span>gmm <span class="token operator">=</span> GaussianMixture<span class="token punctuation">(</span>n_components<span class="token operator">=</span><span class="token number">3</span><span class="token punctuation">,</span> covariance_type<span class="token operator">=</span><span class="token string">'full'</span><span class="token punctuation">,</span> random_state<span class="token operator">=</span><span class="token number">42</span><span class="token punctuation">)</span>gmm<span class="token punctuation">.</span>fit<span class="token punctuation">(</span>X<span class="token punctuation">)</span><span class="token comment" spellcheck="true"># 3. 结果分析</span><span class="token comment" spellcheck="true"># A. 硬聚类结果 (和 K-Means 类似，返回 0, 1, 2)</span>labels <span class="token operator">=</span> gmm<span class="token punctuation">.</span>predict<span class="token punctuation">(</span>X<span class="token punctuation">)</span><span class="token comment" spellcheck="true"># B. 软聚类概率 (GMM 的灵魂)</span><span class="token comment" spellcheck="true"># 返回 shape (N, 3)，每一行加起来等于 1</span>probs <span class="token operator">=</span> gmm<span class="token punctuation">.</span>predict_proba<span class="token punctuation">(</span>X<span class="token punctuation">)</span><span class="token comment" spellcheck="true"># 看看第一个点的情况</span><span class="token keyword">print</span><span class="token punctuation">(</span>f<span class="token string">"样本 0 的归属概率: &amp;#123;probs[0]&amp;#125;"</span><span class="token punctuation">)</span><span class="token comment" spellcheck="true"># 可能输出: [0.05, 0.90, 0.05] -> 很有把握是簇 1</span><span class="token comment" spellcheck="true"># 可能输出: [0.40, 0.50, 0.10] -> 骑墙派，位于簇 0 和 1 的交界处</span><span class="token comment" spellcheck="true"># 4. 模型选择 (AIC/BIC)</span>n_components_range <span class="token operator">=</span> range<span class="token punctuation">(</span><span class="token number">1</span><span class="token punctuation">,</span> <span class="token number">10</span><span class="token punctuation">)</span>bics <span class="token operator">=</span> <span class="token punctuation">[</span><span class="token punctuation">]</span><span class="token keyword">for</span> n <span class="token keyword">in</span> n_components_range<span class="token punctuation">:</span>    gmm_test <span class="token operator">=</span> GaussianMixture<span class="token punctuation">(</span>n_components<span class="token operator">=</span>n<span class="token punctuation">,</span> random_state<span class="token operator">=</span><span class="token number">42</span><span class="token punctuation">)</span><span class="token punctuation">.</span>fit<span class="token punctuation">(</span>X<span class="token punctuation">)</span>    bics<span class="token punctuation">.</span>append<span class="token punctuation">(</span>gmm_test<span class="token punctuation">.</span>bic<span class="token punctuation">(</span>X<span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token comment" spellcheck="true"># 找 BIC 最小的那个 K</span>best_k <span class="token operator">=</span> n_components_range<span class="token punctuation">[</span>np<span class="token punctuation">.</span>argmin<span class="token punctuation">(</span>bics<span class="token punctuation">)</span><span class="token punctuation">]</span><span class="token keyword">print</span><span class="token punctuation">(</span>f<span class="token string">"BIC 建议的最佳簇数: &amp;#123;best_k&amp;#125;"</span><span class="token punctuation">)</span></code></pre><hr><h2 id="7-实践要点"><a href="#7-实践要点" class="headerlink" title="7. 实践要点"></a>7. 实践要点</h2><ol><li><strong>预热 (Warm Start)</strong>：EM 算法很容易陷入局部最优。聪明的做法是<strong>先跑一遍 K-Means</strong>，用 K-Means 找到的中心点作为 GMM 的初始均值 ($\mu$)。<code>sklearn</code> 默认就是这么做的 (<code>init_params=&#39;kmeans&#39;</code>)。</li><li><strong>维度灾难预警</strong>：<ul><li>GMM 的参数量随着维度<strong>平方级</strong>增长（因为要算 $D \times D$ 的协方差矩阵）。</li><li><strong>禁忌</strong>：千万不要在 768 维的 BERT 向量上直接跑 <code>covariance_type=&#39;full&#39;</code> 的 GMM。矩阵会不可逆，模型会过拟合。</li><li><strong>对策</strong>：必须先降维（如 PCA 到 50 维以内），或者强制把协方差矩阵设为 <code>diag</code>（只算对角线，假设特征独立）或 <code>spherical</code>（球形，退化为类似 K-Means）。</li></ul></li></ol><hr><h2 id="下一章预告"><a href="#下一章预告" class="headerlink" title="下一章预告"></a>下一章预告</h2><p>我们讲了 K-Means, DBSCAN, 层次聚类, GMM…<br>每种算法跑出来的结果都不一样。</p><ul><li>K-Means 说分 5 类好。</li><li>DBSCAN 说分 3 类好，还有一堆噪声。</li><li>GMM 说分 4 类 BIC 最小。</li></ul><p>到底哪个结果是好的？没有标准答案（真实标签）的情况下，我们如何给无监督学习打分？<br>这需要引入一套全新的<strong>评估体系</strong>。</p><p>👉 <a href="https://yeee.wang/posts/0aa7.html">第 09 章：聚类评估方法</a></p>]]></content>
    
    <summary type="html">
    
      第 08 章：概率模型聚类 (Probabilistic Clustering)
“上帝不掷骰子，但数据科学家掷。” —— 改编自爱因斯坦

之前的聚类算法（K-Means, DBSCAN）都有一个共同特征：硬聚类 (Hard Clustering)。
一个样本要么属于 A，要么属于 B，没有中间地带。
这就像把人简单分为“好人”和“坏人”，丢失了人性的复杂灰度。

在实际的文本分析场景中，我们经常遇到这种情况：

用户工单：”快递员态度太差了，而且还不给我送上楼，我要退款！”

这句话既涉及【物流服务】，又涉及【退款流程】。如果硬把它归为某一类，就会丢失另一半信息。
本章我们将介绍 高斯混合
    
    </summary>
    
      <category term="算法" scheme="https://yeee.wang/categories/%E7%AE%97%E6%B3%95/"/>
    
    
      <category term="算法" scheme="https://yeee.wang/tags/%E7%AE%97%E6%B3%95/"/>
    
      <category term="数据分析" scheme="https://yeee.wang/tags/%E6%95%B0%E6%8D%AE%E5%88%86%E6%9E%90/"/>
    
      <category term="机器学习" scheme="https://yeee.wang/tags/%E6%9C%BA%E5%99%A8%E5%AD%A6%E4%B9%A0/"/>
    
  </entry>
  
  <entry>
    <title>第 07 章：层次聚类</title>
    <link href="https://yeee.wang/posts/f476.html"/>
    <id>https://yeee.wang/posts/f476.html</id>
    <published>2025-12-22T09:30:00.000Z</published>
    <updated>2026-02-19T20:00:41.879Z</updated>
    
    <content type="html"><![CDATA[<h1 id="第-07-章：层次聚类-Hierarchical-Clustering"><a href="#第-07-章：层次聚类-Hierarchical-Clustering" class="headerlink" title="第 07 章：层次聚类 (Hierarchical Clustering)"></a>第 07 章：层次聚类 (Hierarchical Clustering)</h1><blockquote><p>“生命之树不是平铺直叙的，而是分叉生长的。”</p></blockquote><p>K-Means 给了我们一张扁平的地图：这一块是中国，那一块是美国。在地图上，北京和上海是平级的城市。</p><p>但生物学家看世界的眼光不一样。他们会给你画一棵树：</p><ul><li>所有动物 -&gt; 脊索动物 -&gt; 哺乳动物 -&gt; 食肉目 -&gt; 猫科 -&gt; 家猫。</li></ul><p>这种<strong>层层嵌套</strong>的结构，往往比扁平的分组包含了更丰富的信息。比如，我们不仅想知道“这个客户属于高价值客户”，我们可能还想知道“在高价值客户里，他又属于偏爱理财的那一小撮”。</p><p>本章我们将介绍 <strong>层次聚类 (Hierarchical Clustering)</strong>。它不需要你痛苦地纠结 K 到底是 5 还是 6。它会直接送给你<strong>一整棵家谱树</strong>（学术名叫 <strong>树状图 Dendrogram</strong>）。你想把世界分成几份，完全取决于你拿着剪刀在树的哪一层剪一刀。</p><p><img src="https://img.alicdn.com/imgextra/i4/O1CN01Ffb2Hx1GztyysdxY2_!!6000000000694-2-tps-1376-768.png" alt="层次聚类树状图示意"><br><em>(图注：这是一棵倒置的树。最下面是独立的个体，随着高度上升，它们逐渐合并，最后汇聚成一个根节点。)</em></p><hr><h2 id="1-两种策略：分久必合，合久必分"><a href="#1-两种策略：分久必合，合久必分" class="headerlink" title="1. 两种策略：分久必合，合久必分"></a>1. 两种策略：分久必合，合久必分</h2><p>建立这棵树，只有两种截然相反的思路。</p><table><thead><tr><th style="text-align:left">策略</th><th style="text-align:left">学术名称</th><th style="text-align:left">方向</th><th style="text-align:left">过程隐喻</th><th style="text-align:left">工业界地位</th></tr></thead><tbody><tr><td style="text-align:left"><strong>凝聚式</strong></td><td style="text-align:left">Agglomerative</td><td style="text-align:left">自底向上 (Bottom-Up)</td><td style="text-align:left"><strong>部落合并</strong>：从 N 个小部落开始，两两合并，直到统一成一个大帝国。</td><td style="text-align:left"><strong>主流</strong> (默认指的就是它)</td></tr><tr><td style="text-align:left"><strong>分裂式</strong></td><td style="text-align:left">Divisive</td><td style="text-align:left">自顶向下 (Top-Down)</td><td style="text-align:left"><strong>帝国分裂</strong>：从一个大帝国开始，不断分裂内战，直到每个人都独立建国。</td><td style="text-align:left">罕见 (计算量太大)</td></tr></tbody></table><p><strong>凝聚式过程详解</strong>：</p><ol><li><strong>Day 1 (初始状态)</strong>：世界上有 N 个人。每个人都是一个独立的簇 (Cluster)。</li><li><strong>Day 2 (合并)</strong>：计算所有簇之间的距离矩阵。上帝看了一眼，发现张三和李四离得最近。于是把他们俩绑在一起，组成了一个新的簇。现在的簇数量是 N-1。</li><li><strong>Day 3 (继续合并)</strong>：上帝又看了一眼，发现那个 2 人小簇和王五离得挺近……</li><li><strong>Day N (终局)</strong>：重复这个过程，直到全世界所有人都合并成了一个大一统的帝国（根节点）。</li></ol><p>在这个过程中，我们记录下每一次合并发生的“时间”（也就是距离）。这就在历史上画出了一棵树。</p><p><img src="https://img.alicdn.com/imgextra/i1/O1CN01HQ9SJy23A4Kb6rbzh_!!6000000007214-2-tps-1376-768.png" alt="凝聚式 vs 分裂式"><br><em>(图注：凝聚式从个体出发逐步合并（主流），分裂式从整体出发逐步拆分（罕见）。)</em></p><hr><h2 id="2-关键抉择：链接准则-Linkage-Criteria"><a href="#2-关键抉择：链接准则-Linkage-Criteria" class="headerlink" title="2. 关键抉择：链接准则 (Linkage Criteria)"></a>2. 关键抉择：链接准则 (Linkage Criteria)</h2><p>在“自底向上”的合并过程中，最核心的问题来了：</p><p><strong>我们知道两个“人”之间的距离怎么算（比如欧氏距离 Euclidean Distance），但两个“部落”（簇）之间的距离怎么算？</strong></p><p>簇 A 有两个人 ${a_1, a_2}$，簇 B 有两个人 ${b_1, b_2}$。它们到底有多远？这取决于你的<strong>链接准则</strong>。不同的准则会决定树的形状。</p><p><img src="https://img.alicdn.com/imgextra/i3/O1CN01Uy4CsJ29noOWXDD4k_!!6000000008113-2-tps-1376-768.png" alt="链接准则对比图"><br><em>(图注：展示了三种不同的测量方式。Single Linkage 像是在握手，Complete Linkage 像是在对峙，Average Linkage 则是重心的距离。)</em></p><table><thead><tr><th style="text-align:left">链接准则 (Linkage)</th><th style="text-align:left">规则隐喻</th><th style="text-align:left">公式逻辑</th><th style="text-align:left">优点</th><th style="text-align:left">缺点</th></tr></thead><tbody><tr><td style="text-align:left"><strong>Single (最短距离)</strong></td><td style="text-align:left"><strong>“握手”</strong>：只要有一个朋友，我们就是一家人。</td><td style="text-align:left">$\min dist(a, b)$</td><td style="text-align:left">能处理非球形簇</td><td style="text-align:left">容易产生<strong>连锁效应</strong> (Chaining)，像贪吃蛇一样连成一长串</td></tr><tr><td style="text-align:left"><strong>Complete (最长距离)</strong></td><td style="text-align:left"><strong>“对峙”</strong>：必须所有人都同意，我们才能合并。</td><td style="text-align:left">$\max dist(a, b)$</td><td style="text-align:left">生成紧凑的球形簇</td><td style="text-align:left">对异常值极度敏感</td></tr><tr><td style="text-align:left"><strong>Average (平均距离)</strong></td><td style="text-align:left"><strong>“重心”</strong>：大家的平均关系怎么样。</td><td style="text-align:left">$avg dist(a, b)$</td><td style="text-align:left">折中，稳健</td><td style="text-align:left">计算量稍大</td></tr><tr><td style="text-align:left"><strong>Ward (最小方差)</strong></td><td style="text-align:left"><strong>“混乱度”</strong>：合并后，世界的混乱程度增加最少。</td><td style="text-align:left">$\min \Delta ESS$</td><td style="text-align:left"><strong>效果最好</strong>，簇大小均匀</td><td style="text-align:left">偏向球形簇，<strong>计算量最大</strong></td></tr></tbody></table><p><strong>👑 推荐</strong>：在大多数情况下，优先使用 <strong>Ward’s Method</strong>。如果你处理的是文本数据（余弦距离），则推荐 <strong>Average Linkage</strong>。</p><p><img src="https://img.alicdn.com/imgextra/i1/O1CN01H8ADLG25z1d1SmIIY_!!6000000007596-2-tps-1376-768.png" alt="Ward 链接准则"><br><em>(图注：Ward 方法选择使簇内方差增量最小的合并方案，生成的簇更紧凑、大小更均匀。)</em></p><hr><h2 id="3-代价：由于太贵，富人专用？"><a href="#3-代价：由于太贵，富人专用？" class="headerlink" title="3. 代价：由于太贵，富人专用？"></a>3. 代价：由于太贵，富人专用？</h2><p>层次聚类虽然结构优美，逻辑清晰，但它有一个致命的缺点：<strong>计算复杂度 (Complexity)</strong> 太高。</p><table><thead><tr><th style="text-align:left">算法</th><th style="text-align:left">时间复杂度</th><th style="text-align:left">空间复杂度</th><th style="text-align:left">这种复杂度意味着什么？</th></tr></thead><tbody><tr><td style="text-align:left"><strong>K-Means</strong></td><td style="text-align:left">$O(N \cdot K \cdot I)$</td><td style="text-align:left">$O(N)$</td><td style="text-align:left">线性增长。数据翻倍，时间翻倍。<strong>亲民。</strong></td></tr><tr><td style="text-align:left"><strong>层次聚类</strong></td><td style="text-align:left">$O(N^2 \log N)$</td><td style="text-align:left"><strong>$O(N^2)$</strong></td><td style="text-align:left">平方级增长。数据翻倍，内存翻 <strong>4 倍</strong>。<strong>昂贵。</strong></td></tr></tbody></table><p><strong>算一笔账：</strong><br>为了知道谁和谁最近，层次聚类需要计算并存储<strong>所有点两两之间的距离</strong>（距离矩阵 Distance Matrix）。</p><ul><li>如果你有 1 万个样本，距离矩阵有 $10^8$ 个格子 -&gt; 约 400MB 内存。还能接受。</li><li>如果你有 10 万个样本，距离矩阵有 $10^{10}$ 个格子 -&gt; 约 <strong>40GB - 80GB 内存</strong>。普通服务器直接爆炸。</li></ul><p><strong>结论</strong>：层次聚类通常只适用于<strong>小数据集</strong>（样本量 &lt; 3万）。如果你有百万级数据，请出门左转找 K-Means 或 <strong>BIRCH</strong>（一种专门为大数据设计的层次聚类改进版）。</p><hr><h2 id="4-商业价值：为什么我们还是需要它？"><a href="#4-商业价值：为什么我们还是需要它？" class="headerlink" title="4. 商业价值：为什么我们还是需要它？"></a>4. 商业价值：为什么我们还是需要它？</h2><p>既然它这么慢，为什么没被淘汰？因为它的<strong>“多粒度视角”</strong>在商业分析中太值钱了。</p><p><strong>场景：电商平台的动态风险分级</strong></p><p>运营团队不仅想看 80 个具体的问题类型，还想看宏观的 5 大类。</p><ul><li><strong>L1 (K=5)</strong>：物流、商品、服务、支付、App体验。</li><li><strong>L2 (K=20)</strong>：物流下面细分为：丢件、慢、破损…</li><li><strong>L3 (K=80)</strong>：丢件下面细分为：虚假签收、驿站丢失、快递员偷吃…</li></ul><p><strong>如果你用 K-Means</strong>：你需要分别跑 K=5, K=20, K=80 三次模型。而且这三次的结果可能<strong>不兼容</strong>（K=20 里的某个簇，可能有一半人来自 K=5 的簇 A，另一半来自簇 B，乱套了）。</p><p><strong>如果你用层次聚类</strong>：只需要训练一次树。然后像切蛋糕一样：</p><ul><li>在树根附近横切一刀 -&gt; 得到 5 大类。</li><li>往下一层横切一刀 -&gt; 得到 20 中类。</li><li>再往下一层 -&gt; 得到 80 小类。<br>这些类别是<strong>完美嵌套</strong>的（Sub-cluster），逻辑极其清晰，非常适合构建<strong>标签体系 (Tagging System)</strong>。</li></ul><p><img src="https://img.alicdn.com/imgextra/i3/O1CN01f5hr3V1gRG276JxqO_!!6000000004138-2-tps-1376-768.png" alt="多粒度视角"><br><em>(图注：一次训练，多次切割。在不同高度横切，得到不同粒度的分类体系。)</em></p><hr><h2 id="5-代码实战：画出家谱树"><a href="#5-代码实战：画出家谱树" class="headerlink" title="5. 代码实战：画出家谱树"></a>5. 代码实战：画出家谱树</h2><p>Python 的 <code>scipy</code> 库提供了非常棒的绘图工具。</p><pre class=" language-python"><code class="language-python"><span class="token keyword">from</span> scipy<span class="token punctuation">.</span>cluster<span class="token punctuation">.</span>hierarchy <span class="token keyword">import</span> dendrogram<span class="token punctuation">,</span> linkage<span class="token punctuation">,</span> fcluster<span class="token keyword">import</span> matplotlib<span class="token punctuation">.</span>pyplot <span class="token keyword">as</span> plt<span class="token keyword">import</span> numpy <span class="token keyword">as</span> np<span class="token comment" spellcheck="true"># 1. 准备数据</span><span class="token comment" spellcheck="true"># 假设我们有 50 个样本，每个样本 2 维</span>X <span class="token operator">=</span> np<span class="token punctuation">.</span>random<span class="token punctuation">.</span>rand<span class="token punctuation">(</span><span class="token number">50</span><span class="token punctuation">,</span> <span class="token number">2</span><span class="token punctuation">)</span><span class="token comment" spellcheck="true"># 2. 训练：生成链接矩阵 (Linkage Matrix)</span><span class="token comment" spellcheck="true"># 这一步是计算量最大的，Z 包含了树的所有合并历史</span><span class="token comment" spellcheck="true"># method='ward' 是最推荐的默认参数</span>Z <span class="token operator">=</span> linkage<span class="token punctuation">(</span>X<span class="token punctuation">,</span> method<span class="token operator">=</span><span class="token string">'ward'</span><span class="token punctuation">)</span><span class="token comment" spellcheck="true"># 3. 绘图：Dendrogram (树状图)</span>plt<span class="token punctuation">.</span>figure<span class="token punctuation">(</span>figsize<span class="token operator">=</span><span class="token punctuation">(</span><span class="token number">10</span><span class="token punctuation">,</span> <span class="token number">5</span><span class="token punctuation">)</span><span class="token punctuation">)</span>dendrogram<span class="token punctuation">(</span>Z<span class="token punctuation">)</span><span class="token comment" spellcheck="true"># 画一条红色的横线，表示我们在哪里“剪了一刀”</span><span class="token comment" spellcheck="true"># y=1.5 是一个假设的距离阈值</span>plt<span class="token punctuation">.</span>axhline<span class="token punctuation">(</span>y<span class="token operator">=</span><span class="token number">1.5</span><span class="token punctuation">,</span> c<span class="token operator">=</span><span class="token string">'r'</span><span class="token punctuation">,</span> ls<span class="token operator">=</span><span class="token string">'--'</span><span class="token punctuation">,</span> label<span class="token operator">=</span><span class="token string">'Cut Threshold'</span><span class="token punctuation">)</span>plt<span class="token punctuation">.</span>title<span class="token punctuation">(</span><span class="token string">'Hierarchical Clustering Dendrogram'</span><span class="token punctuation">)</span>plt<span class="token punctuation">.</span>xlabel<span class="token punctuation">(</span><span class="token string">'Sample Index'</span><span class="token punctuation">)</span>plt<span class="token punctuation">.</span>ylabel<span class="token punctuation">(</span><span class="token string">'Distance'</span><span class="token punctuation">)</span>plt<span class="token punctuation">.</span>legend<span class="token punctuation">(</span><span class="token punctuation">)</span>plt<span class="token punctuation">.</span>show<span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token comment" spellcheck="true"># 4. 获取结果：这一刀剪下去，大家都属于哪个簇？</span><span class="token comment" spellcheck="true"># t=1.5: 阈值</span><span class="token comment" spellcheck="true"># criterion='distance': 按照距离来切</span>labels <span class="token operator">=</span> fcluster<span class="token punctuation">(</span>Z<span class="token punctuation">,</span> t<span class="token operator">=</span><span class="token number">1.5</span><span class="token punctuation">,</span> criterion<span class="token operator">=</span><span class="token string">'distance'</span><span class="token punctuation">)</span><span class="token keyword">print</span><span class="token punctuation">(</span>f<span class="token string">"在距离 1.5 处切割，我们得到了 &amp;#123;len(np.unique(labels))&amp;#125; 个簇。"</span><span class="token punctuation">)</span><span class="token comment" spellcheck="true"># 或者，你可以直接指定要几个簇（类似 K-Means）</span><span class="token comment" spellcheck="true"># t=3: 我就要 3 个簇，你自己帮我找在哪里切</span>labels_k3 <span class="token operator">=</span> fcluster<span class="token punctuation">(</span>Z<span class="token punctuation">,</span> t<span class="token operator">=</span><span class="token number">3</span><span class="token punctuation">,</span> criterion<span class="token operator">=</span><span class="token string">'maxclust'</span><span class="token punctuation">)</span></code></pre><hr><h2 id="6-实践要点"><a href="#6-实践要点" class="headerlink" title="6. 实践要点"></a>6. 实践要点</h2><ol><li><strong>预处理</strong>：和 K-Means 一样，层次聚类对距离非常敏感，<strong>必须做归一化 (Normalization)</strong>。</li><li><strong>大数据解决方案</strong>：<ul><li>如果你真的想在 100 万数据上用层次结构，怎么办？</li><li><strong>两阶段法 (Two-stage Clustering)</strong>：先用 K-Means (K=1000) 把 100 万个点聚成 1000 个小簇中心。然后对这 1000 个中心点跑层次聚类。既快又有层次感。</li></ul></li><li><strong>度量选择</strong>：<ul><li><strong>Ward</strong> 方法必须配合 <strong>欧氏距离 (Euclidean)</strong> 使用，这是数学原理决定的。</li><li>如果你处理的是文本（需要余弦距离），请使用 <strong>Average Linkage</strong> 或 <strong>Complete Linkage</strong>，配合 <code>metric=&#39;cosine&#39;</code>。</li></ul></li></ol><hr><h2 id="下一章预告"><a href="#下一章预告" class="headerlink" title="下一章预告"></a>下一章预告</h2><p>到目前为止，我们介绍的 K-Means、DBSCAN、层次聚类，它们都有一个共同点：<strong>硬聚类 (Hard Clustering)</strong>。<br>意思是：一个样本，要么属于 A 组，要么属于 B 组，非黑即白。</p><p>但在现实世界，很多事情是模棱两可的。</p><ul><li>“这句话 70% 是在抱怨物流慢，但也有 30% 是在抱怨客服态度不好。”</li><li>“这个用户既像高价值用户，又有点像流失用户。”</li></ul><p>有没有一种算法，能给出一个<strong>概率值</strong>，而不是一个绝对的标签？告诉我们事物的不确定性？</p><p>👉 <a href="https://yeee.wang/posts/6897.html">第 08 章：概率模型聚类</a></p>]]></content>
    
    <summary type="html">
    
      第 07 章：层次聚类 (Hierarchical Clustering)
“生命之树不是平铺直叙的，而是分叉生长的。”

K-Means 给了我们一张扁平的地图：这一块是中国，那一块是美国。在地图上，北京和上海是平级的城市。

但生物学家看世界的眼光不一样。他们会给你画一棵树：

 * 所有动物 -&gt; 脊索动物 -&gt; 哺乳动物 -&gt; 食肉目 -&gt; 猫科 -&gt; 家猫。

这种层层嵌套的结构，往往比扁平的分组包含了更丰富的信息。比如，我们不仅想知道“这个客户属于高价值客户”，我们可能还想知道“在高价值客户里，他又属于偏爱理财的那一小撮”。

本章我们将介绍 层次聚类 (Hierarchical 
    
    </summary>
    
      <category term="算法" scheme="https://yeee.wang/categories/%E7%AE%97%E6%B3%95/"/>
    
    
      <category term="算法" scheme="https://yeee.wang/tags/%E7%AE%97%E6%B3%95/"/>
    
      <category term="数据分析" scheme="https://yeee.wang/tags/%E6%95%B0%E6%8D%AE%E5%88%86%E6%9E%90/"/>
    
      <category term="机器学习" scheme="https://yeee.wang/tags/%E6%9C%BA%E5%99%A8%E5%AD%A6%E4%B9%A0/"/>
    
  </entry>
  
  <entry>
    <title>第 06 章：密度聚类</title>
    <link href="https://yeee.wang/posts/be3b.html"/>
    <id>https://yeee.wang/posts/be3b.html</id>
    <published>2025-12-22T09:25:00.000Z</published>
    <updated>2026-02-19T20:00:41.879Z</updated>
    
    <content type="html"><![CDATA[<h1 id="第-06-章：密度聚类-Density-Based-Clustering"><a href="#第-06-章：密度聚类-Density-Based-Clustering" class="headerlink" title="第 06 章：密度聚类 (Density-Based Clustering)"></a>第 06 章：密度聚类 (Density-Based Clustering)</h1><blockquote><p>“在拥挤的城市里，社区是由密度定义的，而不是由圆心定义的。”</p></blockquote><p>想象一下，你站在上海的人民广场或者纽约的时代广场。你怎么判断哪些人是一伙的？</p><p>K-Means 算法像是一个拿着圆规的管理员。它假设大家都是以某个中心站成一个个圆圈。如果一群人排成了长长的贪吃蛇队伍（非凸形状），或者环绕着喷泉站成了一个甜甜圈形状，K-Means 就彻底傻眼了——它会强行把“贪吃蛇”切成几段，或者把“甜甜圈”切成几块蛋糕。</p><p>但在现实世界中，数据的形状千奇百怪。有些客户群体像细长的河流（比如随着时间推移的特定行为模式），有些像紧密的蜂巢。</p><p>本章我们将介绍 <strong>DBSCAN</strong>，一种基于<strong>密度</strong>的聚类算法。它不需要你预先告诉它“这里有 3 类人”，它更像是一场<strong>流感</strong>或者<strong>谣言</strong>的传播：只要人挨着人（密度够大），它就会一直蔓延下去，直到遇到空旷地带才会停止。它能自动发现那些蜿蜒曲折、形状古怪的“社区”。</p><p><img src="https://img.alicdn.com/imgextra/i3/O1CN01jVj5hO1P5PfsZROVz_!!6000000001789-2-tps-1376-768.png" alt="K-Means vs DBSCAN 对比图"><br><em>(图注：左边是 K-Means，它笨拙地把笑脸形状切成了几块；右边是 DBSCAN，它完美地识别出了眼睛、嘴巴和脸庞轮廓。)</em></p><h2 id="1-核心概念：怎么才算“挤”？"><a href="#1-核心概念：怎么才算“挤”？" class="headerlink" title="1. 核心概念：怎么才算“挤”？"></a>1. 核心概念：怎么才算“挤”？</h2><p>DBSCAN 的全称是 <em>Density-Based Spatial Clustering of Applications with Noise</em>（带噪声的基于密度的空间聚类）。它的名字已经暴露了它的三个特性：基于密度、能处理噪声、空间聚类。</p><p>它只依赖两个核心参数，我们可以把它们想象成<strong>派对门槛</strong>：</p><ol><li><strong>$\epsilon$ (Eps - $\epsilon$-neighborhood)</strong>：<strong>邻域半径</strong>。也就是你伸手能摸到的范围（你的朋友圈半径）。</li><li><strong>MinPts (Minimum Points)</strong>：<strong>最小点数</strong>。在这个 $\epsilon$ 半径的圈子里，至少得站多少个人（包括你自己），这里才算是一个高密度区域。</li></ol><h3 id="三种角色：派对上的众生相"><a href="#三种角色：派对上的众生相" class="headerlink" title="三种角色：派对上的众生相"></a>三种角色：派对上的众生相</h3><p>基于这两个参数，DBSCAN 把世界上的每一个点（每个人）分成了三种角色：</p><p><img src="https://img.alicdn.com/imgextra/i3/O1CN0190J9iE28mbjmLRQ6I_!!6000000007975-2-tps-1376-768.png" alt="DBSCAN 核心点、边界点、噪声点示意图"><br><em>(图注：红色的是核心点，它们被人群包围；黄色的是边界点，它们在边缘蹭着核心点；蓝色的是噪声点，孤零零地在远处。)</em></p><ol><li><p><strong>核心点 (Core Object/Point)</strong></p><ul><li><strong>定义</strong>：在他的 $\epsilon$ 半径邻域里，样本数 $\ge$ MinPts。</li><li><strong>通俗解释</strong>：<strong>“派对发起人”</strong>。社交达人，人群的中心。他走到哪里，哪里就是聚落的核心。</li></ul></li><li><p><strong>边界点 (Border Object/Point)</strong></p><ul><li><strong>定义</strong>：他自己的邻域里样本数 &lt; MinPts，<strong>但是</strong>，他落在了某个“核心点”的邻域内。</li><li><strong>通俗解释</strong>：<strong>“跟班”</strong>。虽然自己不够社牛（周围人不够多），但他紧紧跟着社牛（在核心点的圈子里）。他属于这个簇，但处于簇的边缘。</li></ul></li><li><p><strong>噪声点 (Noise/Outlier)</strong></p><ul><li><strong>定义</strong>：既不是核心点，也不是边界点。</li><li><strong>通俗解释</strong>：<strong>“孤狼”</strong>。特立独行，离谁都远。在数据分析中，这通常代表<strong>异常值</strong>或者脏数据。</li></ul></li></ol><h3 id="专业术语小贴士"><a href="#专业术语小贴士" class="headerlink" title="专业术语小贴士"></a>专业术语小贴士</h3><p>在阅读文献时，你还会看到这几个词，它们描述了点之间的关系：</p><ul><li><strong>直接密度可达 (Directly Density-Reachable)</strong>：如果 A 是核心点，B 在 A 的 $\epsilon$ 邻域里，那么 B 从 A 直接密度可达。</li><li><strong>密度可达 (Density-Reachable)</strong>：如果有一连串的点 $P_1, P_2, …, P_n$，其中 $P_1$ 是 A， $P_n$ 是 B，且每一个点都能“直接密度可达”下一个点，那么 B 从 A 密度可达。（像传声筒一样）。</li><li><strong>密度相连 (Density-Connected)</strong>：如果存在一个核心点 O，使得 A 和 B 都能从 O 密度可达，那么 A 和 B 就是密度相连的。（我们有共同的朋友）。</li></ul><hr><h2 id="2-聚类过程：传染病模型"><a href="#2-聚类过程：传染病模型" class="headerlink" title="2. 聚类过程：传染病模型"></a>2. 聚类过程：传染病模型</h2><p>DBSCAN 找簇的过程，不需要复杂的迭代优化（如 K-Means 的 EM 算法），完全就是一个<strong>“传帮带”</strong>的遍历过程。</p><ol><li><strong>随机点名</strong>：随机选择一个未访问过的点 P。</li><li><strong>核酸检测 (区域查询)</strong>：<ul><li>计算 P 的 $\epsilon$ 邻域内有多少个点。</li><li><strong>情况一：密度不足</strong>。如果点数 &lt; MinPts，P 暂时标记为 <strong>噪声</strong>。（注意：如果后面它被某个核心点圈进去了，它可以“平反”为边界点）。</li><li><strong>情况二：密度达标</strong>。如果点数 $\ge$ MinPts，P 确诊为 <strong>核心点</strong>。创建一个新簇 $C$，把 P 和它邻域里的所有点都加入 $C$。</li></ul></li><li><strong>病毒扩散 (区域生长)</strong>：<ul><li>对于刚被拉进 $C$ 的每个邻居 Q，继续检查：<ul><li>如果 Q 也是核心点，那么把 Q 的邻居们也都拉进 $C$（<strong>密度可达</strong>的传递性）。</li><li>如果 Q 是边界点，传播链条在 Q 这里终止。</li></ul></li></ul></li><li><strong>循环</strong>：重复上述过程，直到所有点都被访问过。</li></ol><p><strong>结果</strong>：所有<strong>密度相连</strong>的点集构成了一个个簇，剩下的就是噪声。</p><p><img src="https://img.alicdn.com/imgextra/i3/O1CN01MTupv81ntNfCEw659_!!6000000005147-2-tps-1376-768.png" alt="DBSCAN 聚类过程"><br><em>(图注：DBSCAN 像传染病一样扩散，核心点不断传播，边界点终止传播，噪声点被孤立。)</em></p><hr><h2 id="3-技术对比：密度聚类家族谱系"><a href="#3-技术对比：密度聚类家族谱系" class="headerlink" title="3. 技术对比：密度聚类家族谱系"></a>3. 技术对比：密度聚类家族谱系</h2><p>DBSCAN 是密度聚类的鼻祖，但它也有软肋。为了解决它的问题，学术界衍生出了一个庞大的家族。了解这些变体，能让你在选型时更专业。</p><table><thead><tr><th style="text-align:left">算法</th><th style="text-align:left">核心痛点解决</th><th style="text-align:left">优点</th><th style="text-align:left">缺点</th><th style="text-align:left">适用场景</th></tr></thead><tbody><tr><td style="text-align:left"><strong>DBSCAN</strong></td><td style="text-align:left">(基准)</td><td style="text-align:left">无需 K，发现任意形状，<strong>自带异常检测</strong></td><td style="text-align:left"><strong>参数极难调</strong> ($\epsilon$)，无法处理密度不均</td><td style="text-align:left">低维、形状复杂的数据</td></tr><tr><td style="text-align:left"><strong>OPTICS</strong></td><td style="text-align:left">$\epsilon$ 难确定</td><td style="text-align:left">对 $\epsilon$ 不敏感，生成可达距离图 (Reachability Plot)</td><td style="text-align:left">计算量大，不直接出簇 (需二次处理)</td><td style="text-align:left">探索性数据分析，观察数据结构</td></tr><tr><td style="text-align:left"><strong>HDBSCAN</strong></td><td style="text-align:left">层次化 + 密度</td><td style="text-align:left"><strong>无需 $\epsilon$</strong>，自动选择最优簇数，最鲁棒</td><td style="text-align:left">算法复杂，解释性略差</td><td style="text-align:left"><strong>目前最佳的密度聚类算法</strong></td></tr><tr><td style="text-align:left"><strong>Mean-Shift</strong></td><td style="text-align:left">核密度估计</td><td style="text-align:left">无需参数，自适应</td><td style="text-align:left">极慢 ($O(N^2)$)，主要用于图像</td><td style="text-align:left">图像分割，低维平滑数据</td></tr></tbody></table><hr><h2 id="4-为什么它是数据科学家的“备胎”？"><a href="#4-为什么它是数据科学家的“备胎”？" class="headerlink" title="4. 为什么它是数据科学家的“备胎”？"></a>4. 为什么它是数据科学家的“备胎”？</h2><p>尽管有上述家族成员，但在实际的工业界项目中（尤其是文本挖掘、用户分层），我们往往<strong>首选 K-Means</strong>，而把 DBSCAN 当作备选方案。为什么？</p><h3 id="痛点-1：参数-epsilon-极其难调"><a href="#痛点-1：参数-epsilon-极其难调" class="headerlink" title="痛点 1：参数 $\epsilon$ 极其难调"></a>痛点 1：参数 $\epsilon$ 极其难调</h3><p>选 K 值（聚成几类）虽然难，但也就是在 5 到 100 之间猜。但 $\epsilon$ 是一个连续的距离值，且对数据分布非常敏感。</p><ul><li><strong>$\epsilon$ 太小</strong>：数据稍微稀疏一点，就被判为噪声。结果：产生大量的簇和大量的噪声点。</li><li><strong>$\epsilon$ 太大</strong>：不同密度的簇被合并。结果：所有点都归为一个巨型簇。</li></ul><h3 id="痛点-2：维度灾难-The-Curse-of-Dimensionality"><a href="#痛点-2：维度灾难-The-Curse-of-Dimensionality" class="headerlink" title="痛点 2：维度灾难 (The Curse of Dimensionality)"></a>痛点 2：维度灾难 (The Curse of Dimensionality)</h3><p>这是最致命的。在文本向量（如 768 维 BERT 向量）的高维空间里：</p><ul><li><strong>距离失效</strong>：根据<strong>距离集中效应</strong>，高维空间中任意两点的距离都趋于一致。</li><li><strong>稀疏性</strong>：高维空间极度空旷，很难满足 MinPts 的密度要求。</li><li><strong>结论</strong>：在未经降维的高维数据上直接跑 DBSCAN，效果通常极差。</li></ul><h3 id="痛点-3：密度不均-Varying-Density"><a href="#痛点-3：密度不均-Varying-Density" class="headerlink" title="痛点 3：密度不均 (Varying Density)"></a>痛点 3：密度不均 (Varying Density)</h3><p>如果数据集包含不同密度的簇（例如：市中心的人群非常密集，而郊区的聚落相对稀疏）。</p><ul><li>DBSCAN 只有一组全局参数 ($\epsilon$, MinPts)。</li><li>如果参数适应了高密度区，低密度区就会变成噪声。</li><li>如果参数适应了低密度区，高密度区就会被合并成一个大块。</li></ul><p><img src="https://img.alicdn.com/imgextra/i1/O1CN014ETdok1a1Fqs8r2Bz_!!6000000003269-2-tps-1376-768.png" alt="密度不均问题"><br><em>(图注：一把尺子量不了两个世界。HDBSCAN 通过自适应密度解决了这个问题。)</em></p><hr><h2 id="5-什么时候非用它不可？"><a href="#5-什么时候非用它不可？" class="headerlink" title="5. 什么时候非用它不可？"></a>5. 什么时候非用它不可？</h2><p>尽管有上述缺点，DBSCAN 在以下场景是<strong>无可替代</strong>的：</p><ol><li><strong>地理位置数据 (Geo-Spatial Data)</strong>：<ul><li>经纬度只有 2 维，不存在维度灾难。</li><li>物理世界的聚落（商圈、住宅区）本身就是基于密度的自然分布。</li></ul></li><li><strong>异常检测 (Anomaly Detection)</strong>：<ul><li>有时候我们的目的不是聚类，而是<strong>抓坏人</strong>。DBSCAN 跑完后，那些标记为 -1 的点，往往就是信用卡欺诈、网络攻击或者设备故障的数据。</li></ul></li><li><strong>非球形簇</strong>：<ul><li>如果你用 UMAP/t-SNE 降维后，发现数据明显呈现出弯曲的长条状，K-Means 切不开，这时候必须上 DBSCAN。</li></ul></li></ol><hr><h2 id="6-代码实战：DBSCAN-vs-HDBSCAN"><a href="#6-代码实战：DBSCAN-vs-HDBSCAN" class="headerlink" title="6. 代码实战：DBSCAN vs HDBSCAN"></a>6. 代码实战：DBSCAN vs HDBSCAN</h2><p>为了解决 DBSCAN 调参难和密度不均的问题，Campello 等人在 2013 年提出了 <strong>HDBSCAN</strong> (Hierarchical DBSCAN)。</p><ul><li><strong>DBSCAN</strong>：静态的截断。必须指定固定的 $\epsilon$。</li><li><strong>HDBSCAN</strong>：结合了层次聚类和密度聚类。它把 $\epsilon$ 看作一个变化的变量，构建一棵聚类稳定性树，自动选择最稳定的簇。</li></ul><p><strong>强烈推荐：在 Python 中，尽可能优先使用 <code>hdbscan</code> 库，而不是 sklearn 自带的 DBSCAN。</strong></p><pre class=" language-python"><code class="language-python"><span class="token keyword">import</span> numpy <span class="token keyword">as</span> np<span class="token keyword">import</span> matplotlib<span class="token punctuation">.</span>pyplot <span class="token keyword">as</span> plt<span class="token keyword">from</span> sklearn<span class="token punctuation">.</span>cluster <span class="token keyword">import</span> DBSCAN<span class="token keyword">import</span> hdbscan <span class="token comment" spellcheck="true"># 需要安装: pip install hdbscan</span><span class="token keyword">from</span> sklearn<span class="token punctuation">.</span>datasets <span class="token keyword">import</span> make_moons<span class="token comment" spellcheck="true"># 1. 制造数据：生成两个“新月”形状的数据</span><span class="token comment" spellcheck="true"># 这种非凸形状是 K-Means 的噩梦</span>X<span class="token punctuation">,</span> _ <span class="token operator">=</span> make_moons<span class="token punctuation">(</span>n_samples<span class="token operator">=</span><span class="token number">500</span><span class="token punctuation">,</span> noise<span class="token operator">=</span><span class="token number">0.1</span><span class="token punctuation">,</span> random_state<span class="token operator">=</span><span class="token number">42</span><span class="token punctuation">)</span><span class="token comment" spellcheck="true"># ==========================================</span><span class="token comment" spellcheck="true"># 方法 A: 传统 DBSCAN (Sklearn)</span><span class="token comment" spellcheck="true"># ==========================================</span><span class="token comment" spellcheck="true"># 痛点：你需要反复尝试 eps 的值。0.1? 0.2? 0.15?</span><span class="token comment" spellcheck="true"># 这里的 0.15 是我们试出来的“上帝视角”最优解</span>db <span class="token operator">=</span> DBSCAN<span class="token punctuation">(</span>eps<span class="token operator">=</span><span class="token number">0.15</span><span class="token punctuation">,</span> min_samples<span class="token operator">=</span><span class="token number">5</span><span class="token punctuation">)</span><span class="token punctuation">.</span>fit<span class="token punctuation">(</span>X<span class="token punctuation">)</span>labels_db <span class="token operator">=</span> db<span class="token punctuation">.</span>labels_<span class="token comment" spellcheck="true"># ==========================================</span><span class="token comment" spellcheck="true"># 方法 B: HDBSCAN (推荐)</span><span class="token comment" spellcheck="true"># ==========================================</span><span class="token comment" spellcheck="true"># 优点：只需要思考业务逻辑——“哪怕最小的群体，也不能少于 10 人吧？”</span><span class="token comment" spellcheck="true"># 不需要关心具体的距离数值，它能自适应不同密度的簇。</span>hdb <span class="token operator">=</span> hdbscan<span class="token punctuation">.</span>HDBSCAN<span class="token punctuation">(</span>min_cluster_size<span class="token operator">=</span><span class="token number">10</span><span class="token punctuation">)</span><span class="token punctuation">.</span>fit<span class="token punctuation">(</span>X<span class="token punctuation">)</span>labels_hdb <span class="token operator">=</span> hdb<span class="token punctuation">.</span>labels_<span class="token comment" spellcheck="true"># 打印结果</span><span class="token comment" spellcheck="true"># -1 代表噪声 (Noise)</span>n_noise_db <span class="token operator">=</span> list<span class="token punctuation">(</span>labels_db<span class="token punctuation">)</span><span class="token punctuation">.</span>count<span class="token punctuation">(</span><span class="token operator">-</span><span class="token number">1</span><span class="token punctuation">)</span>n_noise_hdb <span class="token operator">=</span> list<span class="token punctuation">(</span>labels_hdb<span class="token punctuation">)</span><span class="token punctuation">.</span>count<span class="token punctuation">(</span><span class="token operator">-</span><span class="token number">1</span><span class="token punctuation">)</span><span class="token keyword">print</span><span class="token punctuation">(</span>f<span class="token string">"DBSCAN 发现噪声点数: &amp;#123;n_noise_db&amp;#125;"</span><span class="token punctuation">)</span><span class="token keyword">print</span><span class="token punctuation">(</span>f<span class="token string">"HDBSCAN 发现噪声点数: &amp;#123;n_noise_hdb&amp;#125;"</span><span class="token punctuation">)</span></code></pre><h3 id="实用技巧：如何确定-DBSCAN-的-eps？"><a href="#实用技巧：如何确定-DBSCAN-的-eps？" class="headerlink" title="实用技巧：如何确定 DBSCAN 的 eps？"></a>实用技巧：如何确定 DBSCAN 的 <code>eps</code>？</h3><p>如果你必须用传统的 DBSCAN（比如为了兼容性），这里有一个<strong>“膝盖法则” (Knee Method / k-distance graph)</strong> 来找最佳 $\epsilon$：</p><ol><li>计算每个点到它<strong>第 k 个最近邻居</strong>的距离（通常取 k=MinPts）。</li><li>把这些距离从大到小排序。</li><li>画出曲线图。</li><li>找到曲线急剧弯曲的那个“拐点”（膝盖位置）。该位置对应的距离值，就是不错的 $\epsilon$ 初始值。</li></ol><p><img src="https://img.alicdn.com/imgextra/i4/O1CN01uUYjTz1we708tkCKV_!!6000000006332-2-tps-1376-768.png" alt="k-距离图示例"><br><em>(图注：横轴是点的序号，纵轴是 k-dist。在曲线突然陡峭上升的地方，意味着点开始变得稀疏，这正是簇的边界。)</em></p><hr><h2 id="7-总结与预告"><a href="#7-总结与预告" class="headerlink" title="7. 总结与预告"></a>7. 总结与预告</h2><table><thead><tr><th style="text-align:left">特性</th><th style="text-align:left">K-Means</th><th style="text-align:left">DBSCAN</th><th style="text-align:left">HDBSCAN</th></tr></thead><tbody><tr><td style="text-align:left"><strong>核心思想</strong></td><td style="text-align:left">距离划分 (Partitioning)</td><td style="text-align:left">密度传播 (Density)</td><td style="text-align:left">层次密度 (Hierarchical Density)</td></tr><tr><td style="text-align:left"><strong>形状假设</strong></td><td style="text-align:left">凸形/球形 (Convex)</td><td style="text-align:left">任意形状</td><td style="text-align:left">任意形状</td></tr><tr><td style="text-align:left"><strong>参数</strong></td><td style="text-align:left">K (簇数)</td><td style="text-align:left">$\epsilon$ (半径), MinPts</td><td style="text-align:left">MinPts (最小簇大小)</td></tr><tr><td style="text-align:left"><strong>噪声处理</strong></td><td style="text-align:left">无 (强行归类)</td><td style="text-align:left"><strong>识别并剔除</strong></td><td style="text-align:left"><strong>识别并剔除</strong></td></tr><tr><td style="text-align:left"><strong>密度敏感度</strong></td><td style="text-align:left">均一密度</td><td style="text-align:left">均一密度</td><td style="text-align:left"><strong>自适应不同密度</strong></td></tr></tbody></table><p>DBSCAN 告诉我们，聚类不一定是切蛋糕，也可以是病毒传播。它让我们看到了数据中更自然的拓扑结构。</p><p>然而，无论是 K-Means 还是 DBSCAN，它们都有一个共同的局限性：<strong>它们都是“扁平”的</strong>。它们给你的是一张这一刻的世界地图。</p><p>但生物学家会告诉你，世界不是扁平的，而是<strong>分层</strong>的 (Hierarchical)：从动物界到脊索动物门，再到哺乳纲、灵长目……<br>如果我们想从数据中得到这种<strong>树状的家谱</strong>，该怎么办？</p><p>👉 <a href="https://yeee.wang/posts/f476.html">第 07 章：层次聚类</a></p>]]></content>
    
    <summary type="html">
    
      第 06 章：密度聚类 (Density-Based Clustering)
“在拥挤的城市里，社区是由密度定义的，而不是由圆心定义的。”

想象一下，你站在上海的人民广场或者纽约的时代广场。你怎么判断哪些人是一伙的？

K-Means 算法像是一个拿着圆规的管理员。它假设大家都是以某个中心站成一个个圆圈。如果一群人排成了长长的贪吃蛇队伍（非凸形状），或者环绕着喷泉站成了一个甜甜圈形状，K-Means 就彻底傻眼了——它会强行把“贪吃蛇”切成几段，或者把“甜甜圈”切成几块蛋糕。

但在现实世界中，数据的形状千奇百怪。有些客户群体像细长的河流（比如随着时间推移的特定行为模式），有些像紧密的蜂巢。
    
    </summary>
    
      <category term="算法" scheme="https://yeee.wang/categories/%E7%AE%97%E6%B3%95/"/>
    
    
      <category term="算法" scheme="https://yeee.wang/tags/%E7%AE%97%E6%B3%95/"/>
    
      <category term="数据分析" scheme="https://yeee.wang/tags/%E6%95%B0%E6%8D%AE%E5%88%86%E6%9E%90/"/>
    
      <category term="机器学习" scheme="https://yeee.wang/tags/%E6%9C%BA%E5%99%A8%E5%AD%A6%E4%B9%A0/"/>
    
  </entry>
  
  <entry>
    <title>第 05 章：划分式聚类</title>
    <link href="https://yeee.wang/posts/22f6.html"/>
    <id>https://yeee.wang/posts/22f6.html</id>
    <published>2025-12-22T09:20:00.000Z</published>
    <updated>2026-02-19T20:00:41.879Z</updated>
    
    <content type="html"><![CDATA[<h1 id="第-05-章：划分式聚类-Partitional-Clustering"><a href="#第-05-章：划分式聚类-Partitional-Clustering" class="headerlink" title="第 05 章：划分式聚类 (Partitional Clustering)"></a>第 05 章：划分式聚类 (Partitional Clustering)</h1><blockquote><p>“虽然它诞生于 1957 年，但它依然是数据挖掘界的 AK-47——简单、粗暴、有效。”</p></blockquote><h2 id="1-导言：牧羊人的智慧"><a href="#1-导言：牧羊人的智慧" class="headerlink" title="1. 导言：牧羊人的智慧"></a>1. 导言：牧羊人的智慧</h2><p>如果把你扔到一个大草原上，给你 1000 只羊，让你把它们分成 3 群，你会怎么做？<br>你可能会插 3 根旗子，然后吹哨子让羊跑到离自己最近的旗子那里去。<br>然后你发现，某个旗子插歪了，羊群并不集中。于是你拔起旗子，走到羊群的中心重新插下。<br>再吹哨子，羊群微调位置。<br>重复几次，羊群就分得整整齐齐了。</p><p>这就是 <strong>K-Means</strong> 算法的直觉：<strong>选中心 -&gt; 分组 -&gt; 移中心 -&gt; 再分组</strong>。<br>这种将数据划分为互不重叠的子集的方法，称为 <strong>划分式聚类 (Partitional Clustering)</strong>。</p><p><img src="https://img.alicdn.com/imgextra/i4/O1CN01aW36La22QGizaclL8_!!6000000007114-2-tps-1376-768.png" alt="K-Means 迭代过程"></p><h2 id="2-核心概念：K-Means-的数学之美"><a href="#2-核心概念：K-Means-的数学之美" class="headerlink" title="2. 核心概念：K-Means 的数学之美"></a>2. 核心概念：K-Means 的数学之美</h2><h3 id="2-1-目标函数-Inertia"><a href="#2-1-目标函数-Inertia" class="headerlink" title="2.1 目标函数 (Inertia)"></a>2.1 目标函数 (Inertia)</h3><p>K-Means 的目标非常单纯：让每个点到它老大的距离之和最小。<br>数学上，我们要最小化 <strong>簇内平方和 (Within-Cluster Sum of Squares, WCSS)</strong>：</p><p>$$ J = \sum_{k=1}^K \sum_{x \in C_k} ||x - \mu_k||^2 $$</p><ul><li>$K$：簇的数量（比如 80）。</li><li>$\mu_k$：第 $k$ 个簇的中心点 (Centroid)。</li><li>$x$：样本点。</li></ul><h3 id="2-2-Lloyd-算法：两步走"><a href="#2-2-Lloyd-算法：两步走" class="headerlink" title="2.2 Lloyd 算法：两步走"></a>2.2 Lloyd 算法：两步走</h3><p>为了解这个方程，我们使用迭代法（Lloyd 算法）：</p><ol><li><strong>Assignment (E-step)</strong>：固定中心 $\mu$，把每个点 $x$ 分配给最近的中心。<br>$$ C_k = {x : ||x - \mu_k|| \le ||x - \mu_j||, \forall j} $$</li><li><strong>Update (M-step)</strong>：固定分组 $C$，重新计算中心 $\mu$（取平均值）。<br>$$ \mu_k = \frac{1}{|C_k|} \sum_{x \in C_k} x $$</li></ol><p>重复这两步，直到中心不再移动。数学证明，这个过程一定会<strong>收敛</strong>（虽然可能收敛到局部最优）。</p><h3 id="2-3-初始化陷阱：K-Means"><a href="#2-3-初始化陷阱：K-Means" class="headerlink" title="2.3 初始化陷阱：K-Means++"></a>2.3 初始化陷阱：K-Means++</h3><p>最原始的 K-Means 是随机选 3 个点做中心。<br>但这有个大坑：如果运气不好，选的 3 个点挤在一起怎么办？<br><strong>K-Means++</strong> 提出了一种聪明的策略：</p><ul><li>第 1 个点：随机选。</li><li>第 2 个点：<strong>离第 1 个点越远，越容易被选中</strong>（概率正比于距离平方）。</li><li>…<br>这样能保证初始中心尽可能分散，大大提升了算法的稳定性和速度。</li></ul><p><img src="https://img.alicdn.com/imgextra/i1/O1CN01RAvUPb1zAI3kLWSOB_!!6000000006673-2-tps-1376-768.png" alt="K-Means++ 初始化原理"></p><h2 id="3-技术对比：K-Means-家族"><a href="#3-技术对比：K-Means-家族" class="headerlink" title="3. 技术对比：K-Means 家族"></a>3. 技术对比：K-Means 家族</h2><p>虽然 K-Means 是老大哥，但它也有很多表亲，分别解决了它的不同弱点。</p><p><img src="https://img.alicdn.com/imgextra/i4/O1CN019F2iHl27xJNUwe8dN_!!6000000007863-2-tps-1376-768.png" alt="划分式聚类家族"></p><table><thead><tr><th style="text-align:left">算法</th><th style="text-align:left">核心改进</th><th style="text-align:left">优点</th><th style="text-align:left">缺点</th><th style="text-align:left">适用场景</th></tr></thead><tbody><tr><td style="text-align:left"><strong>K-Means</strong></td><td style="text-align:left">基准</td><td style="text-align:left">简单，可解释，适合球形簇</td><td style="text-align:left">对离群点敏感，需指定 K</td><td style="text-align:left">通用场景</td></tr><tr><td style="text-align:left"><strong>K-Means++</strong></td><td style="text-align:left">优化初始化</td><td style="text-align:left">收敛更快，结果更优</td><td style="text-align:left">初始化略慢</td><td style="text-align:left"><strong>默认首选</strong></td></tr><tr><td style="text-align:left"><strong>Mini-Batch K-Means</strong></td><td style="text-align:left">随机抽样更新</td><td style="text-align:left"><strong>速度极快</strong>，支持海量数据流</td><td style="text-align:left">精度略有下降</td><td style="text-align:left">亿级数据，实时流处理</td></tr><tr><td style="text-align:left"><strong>K-Medoids (PAM)</strong></td><td style="text-align:left">选真实点做中心</td><td style="text-align:left"><strong>抗噪</strong>（不受极端值拉扯）</td><td style="text-align:left">慢 ($O(N^2)$)</td><td style="text-align:left">数据脏、有离群点的小数据集</td></tr><tr><td style="text-align:left"><strong>Bisecting K-Means</strong></td><td style="text-align:left">递归二分</td><td style="text-align:left">层次化，更稳定</td><td style="text-align:left">无法回溯</td><td style="text-align:left">需要层级结构的场景</td></tr></tbody></table><h2 id="4-代码实战：从原理到-Scikit-Learn"><a href="#4-代码实战：从原理到-Scikit-Learn" class="headerlink" title="4. 代码实战：从原理到 Scikit-Learn"></a>4. 代码实战：从原理到 Scikit-Learn</h2><h3 id="4-1-手写-K-Means-Python"><a href="#4-1-手写-K-Means-Python" class="headerlink" title="4.1 手写 K-Means (Python)"></a>4.1 手写 K-Means (Python)</h3><p>为了理解原理，我们写一个极简版：</p><pre class=" language-python"><code class="language-python"><span class="token keyword">import</span> numpy <span class="token keyword">as</span> np<span class="token keyword">def</span> <span class="token function">simple_kmeans</span><span class="token punctuation">(</span>X<span class="token punctuation">,</span> k<span class="token punctuation">,</span> max_iters<span class="token operator">=</span><span class="token number">100</span><span class="token punctuation">)</span><span class="token punctuation">:</span>    <span class="token comment" spellcheck="true"># 1. 随机初始化中心</span>    centroids <span class="token operator">=</span> X<span class="token punctuation">[</span>np<span class="token punctuation">.</span>random<span class="token punctuation">.</span>choice<span class="token punctuation">(</span>X<span class="token punctuation">.</span>shape<span class="token punctuation">[</span><span class="token number">0</span><span class="token punctuation">]</span><span class="token punctuation">,</span> k<span class="token punctuation">,</span> replace<span class="token operator">=</span><span class="token boolean">False</span><span class="token punctuation">)</span><span class="token punctuation">]</span>    <span class="token keyword">for</span> _ <span class="token keyword">in</span> range<span class="token punctuation">(</span>max_iters<span class="token punctuation">)</span><span class="token punctuation">:</span>        <span class="token comment" spellcheck="true"># 2. Assignment: 计算距离并分组</span>        <span class="token comment" spellcheck="true"># distances shape: (N, k)</span>        distances <span class="token operator">=</span> np<span class="token punctuation">.</span>linalg<span class="token punctuation">.</span>norm<span class="token punctuation">(</span>X<span class="token punctuation">[</span><span class="token punctuation">:</span><span class="token punctuation">,</span> np<span class="token punctuation">.</span>newaxis<span class="token punctuation">]</span> <span class="token operator">-</span> centroids<span class="token punctuation">,</span> axis<span class="token operator">=</span><span class="token number">2</span><span class="token punctuation">)</span>        labels <span class="token operator">=</span> np<span class="token punctuation">.</span>argmin<span class="token punctuation">(</span>distances<span class="token punctuation">,</span> axis<span class="token operator">=</span><span class="token number">1</span><span class="token punctuation">)</span>        <span class="token comment" spellcheck="true"># 3. Update: 2025-12-22 09:20:00</span>        new_centroids <span class="token operator">=</span> np<span class="token punctuation">.</span>array<span class="token punctuation">(</span><span class="token punctuation">[</span>X<span class="token punctuation">[</span>labels <span class="token operator">==</span> i<span class="token punctuation">]</span><span class="token punctuation">.</span>mean<span class="token punctuation">(</span>axis<span class="token operator">=</span><span class="token number">0</span><span class="token punctuation">)</span> <span class="token keyword">for</span> i <span class="token keyword">in</span> range<span class="token punctuation">(</span>k<span class="token punctuation">)</span><span class="token punctuation">]</span><span class="token punctuation">)</span>        <span class="token comment" spellcheck="true"># 收敛检测</span>        <span class="token keyword">if</span> np<span class="token punctuation">.</span>all<span class="token punctuation">(</span>centroids <span class="token operator">==</span> new_centroids<span class="token punctuation">)</span><span class="token punctuation">:</span>            <span class="token keyword">break</span>        centroids <span class="token operator">=</span> new_centroids    <span class="token keyword">return</span> labels<span class="token punctuation">,</span> centroids</code></pre><h3 id="4-2-工程实践中的应用"><a href="#4-2-工程实践中的应用" class="headerlink" title="4.2 工程实践中的应用"></a>4.2 工程实践中的应用</h3><p>在实际项目中，通常使用成熟的 <code>sklearn.cluster.KMeans</code>。</p><pre class=" language-python"><code class="language-python"><span class="token comment" spellcheck="true"># 代码引用: cluster_analysis.py</span>kmeans <span class="token operator">=</span> KMeans<span class="token punctuation">(</span>    n_clusters<span class="token operator">=</span><span class="token number">80</span><span class="token punctuation">,</span>    <span class="token comment" spellcheck="true"># 这是一个很大的 K，我们采用了“过聚类”策略</span>    init<span class="token operator">=</span><span class="token string">'k-means++'</span><span class="token punctuation">,</span> <span class="token comment" spellcheck="true"># 使用聪明初始化</span>    n_init<span class="token operator">=</span><span class="token number">10</span><span class="token punctuation">,</span>        <span class="token comment" spellcheck="true"># 跑 10 次取最好，防止局部最优</span>    random_state<span class="token operator">=</span><span class="token number">42</span>   <span class="token comment" spellcheck="true"># 保证结果可复现</span><span class="token punctuation">)</span>cluster_labels <span class="token operator">=</span> kmeans<span class="token punctuation">.</span>fit_predict<span class="token punctuation">(</span>embeddings<span class="token punctuation">)</span></code></pre><p><strong>工程思考</strong>：为什么选 80？<br>在无监督学习中，K 值的选择往往不是数学问题，而是<strong>业务问题</strong>。</p><ul><li>如果 K=5，虽然数学指标（如轮廓系数）可能很高，但业务上没法用（“物流问题”太笼统了）。</li><li>如果 K=80，我们能把“物流”拆解为“丢件”、“慢”、“态度差”、“破损”等具体问题。</li><li><strong>宁可分碎，不可分错</strong>。后续我们可以通过合并相似簇来减少数量，但如果一开始就混在一起了，后面就分不开了。</li></ul><h2 id="5-实践要点"><a href="#5-实践要点" class="headerlink" title="5. 实践要点"></a>5. 实践要点</h2><ol><li><strong>数据标准化</strong>：K-Means 对距离极其敏感。如果你的特征一个是“年龄(0-100)”，一个是“收入(0-10000)”，K-Means 会完全被收入主导。<strong>必须先做 StandardScaler</strong>。当然，Embedding 向量通常已经 Normalized 了，所以可以直接跑。</li><li><strong>异常值处理</strong>：一个离群点可能会把中心拉偏十万八千里。建议先剔除异常值，或者使用 K-Medoids。</li><li><strong>K 的选择</strong>：不要迷信“手肘法 (Elbow Method)”。在工业界，通常根据<strong>运营人力</strong>来定 K。如果你有 5 个运营小组，K=50 可能比较合适（每组看 10 类）。</li></ol><hr><p><strong>下一章预告</strong>：<br>K-Means 有个致命弱点：它假设簇是<strong>凸的（球形的）</strong>。<br>如果数据长得像新月形，或者环形（同心圆），K-Means 会切得乱七八糟。<br>这时候，我们需要一种不依赖距离，而是依赖<strong>密度</strong>的算法——DBSCAN。</p><p>👉 <a href="https://yeee.wang/posts/be3b.html">第 06 章：密度聚类</a></p>]]></content>
    
    <summary type="html">
    
      第 05 章：划分式聚类 (Partitional Clustering)
“虽然它诞生于 1957 年，但它依然是数据挖掘界的 AK-47——简单、粗暴、有效。”

1. 导言：牧羊人的智慧
如果把你扔到一个大草原上，给你 1000 只羊，让你把它们分成 3 群，你会怎么做？
你可能会插 3 根旗子，然后吹哨子让羊跑到离自己最近的旗子那里去。
然后你发现，某个旗子插歪了，羊群并不集中。于是你拔起旗子，走到羊群的中心重新插下。
再吹哨子，羊群微调位置。
重复几次，羊群就分得整整齐齐了。

这就是 K-Means 算法的直觉：选中心 -&gt; 分组 -&gt; 移中心 -&gt; 再分组。
这种将数据划分为互不
    
    </summary>
    
      <category term="算法" scheme="https://yeee.wang/categories/%E7%AE%97%E6%B3%95/"/>
    
    
      <category term="算法" scheme="https://yeee.wang/tags/%E7%AE%97%E6%B3%95/"/>
    
      <category term="数据分析" scheme="https://yeee.wang/tags/%E6%95%B0%E6%8D%AE%E5%88%86%E6%9E%90/"/>
    
      <category term="机器学习" scheme="https://yeee.wang/tags/%E6%9C%BA%E5%99%A8%E5%AD%A6%E4%B9%A0/"/>
    
  </entry>
  
  <entry>
    <title>第 04 章：高维数据的几何特性</title>
    <link href="https://yeee.wang/posts/8ed4.html"/>
    <id>https://yeee.wang/posts/8ed4.html</id>
    <published>2025-12-22T09:15:00.000Z</published>
    <updated>2026-02-19T20:00:41.879Z</updated>
    
    <content type="html"><![CDATA[<h1 id="第-04-章：高维数据的几何特性-The-Geometry-of-High-Dimensional-Data"><a href="#第-04-章：高维数据的几何特性-The-Geometry-of-High-Dimensional-Data" class="headerlink" title="第 04 章：高维数据的几何特性 (The Geometry of High-Dimensional Data)"></a>第 04 章：高维数据的几何特性 (The Geometry of High-Dimensional Data)</h1><blockquote><p>“在高维空间里，每个人都是孤独的。”</p></blockquote><p>欢迎来到 1536 维的世界。这里是 Embedding 的家园，也是直觉的坟墓。<br>我们的大脑是为三维世界进化的。我们很难想象，当维度增加到 1000 以上时，几何规则会发生怎样翻天覆地的变化。</p><p>这一章我们将揭示一个可怕的现象——<strong>维度灾难 (The Curse of Dimensionality)</strong>，以及一个美好的奇迹——<strong>维度祝福</strong>。</p><p><img src="https://img.alicdn.com/imgextra/i1/O1CN01kawENz1i0oOYG0dHR_!!6000000004351-2-tps-1376-768.png" alt="维度灾难示意图"></p><h2 id="1-维度灾难：空旷的宇宙"><a href="#1-维度灾难：空旷的宇宙" class="headerlink" title="1. 维度灾难：空旷的宇宙"></a>1. 维度灾难：空旷的宇宙</h2><h3 id="1-1-越来越空"><a href="#1-1-越来越空" class="headerlink" title="1.1 越来越空"></a>1.1 越来越空</h3><p>想象一个边长为 1 的正方形（2维）。如果你往里面撒 100 个点，它会显得很拥挤。<br>现在，保持边长为 1，把它变成一个 1536 维的超立方体。<br>虽然边长没变，但它的“体积”（超体积）指数级膨胀了。</p><p>如果你还想保持同样的拥挤程度，你需要撒多少个点？<br>答案是 $100^{1536/2}$。这个数字比宇宙中所有原子的总和还要大亿万倍。</p><p><strong>结论</strong>：在高维空间中，任何有限的数据集（哪怕你有 10 亿条数据）都是<strong>极度稀疏</strong>的。所有的样本点都像是在真空中漂浮的尘埃，彼此相距光年之远。</p><h3 id="1-2-距离失效-Distance-Concentration"><a href="#1-2-距离失效-Distance-Concentration" class="headerlink" title="1.2 距离失效 (Distance Concentration)"></a>1.2 距离失效 (Distance Concentration)</h3><p>这是对聚类算法最致命的打击。<br>数学推导证明：随着维度 $D \to \infty$，任意两个随机点之间的距离会趋向于常数。<br>$$ \frac{\text{最远距离} - \text{最近距离}}{\text{最近距离}} \to 0 $$</p><p>这意味着：<strong>在高维空间里，最近的邻居和最远的陌生人，距离其实差不多！</strong><br>如果所有人都离你一样远，K-Means 怎么找最近的中心？KNN 怎么找邻居？算法会彻底迷失。</p><p>这就是为什么文本分析项目<strong>必须</strong>在聚类之前或之后进行降维，或者使用余弦相似度（它受维度影响相对较小）。</p><h3 id="1-3-球壳效应"><a href="#1-3-球壳效应" class="headerlink" title="1.3 球壳效应"></a>1.3 球壳效应</h3><p>在高维球体中，几乎所有的体积都集中在<strong>表面（球壳）</strong>上，球心是空的。<br>就像一个西瓜，皮越来越厚，瓤越来越小，最后只剩下一层皮。<br>所以，我们的 Embedding 向量，本质上都分布在一个 1536 维的超球面上。</p><h2 id="2-维度祝福：Johnson-Lindenstrauss-引理"><a href="#2-维度祝福：Johnson-Lindenstrauss-引理" class="headerlink" title="2. 维度祝福：Johnson-Lindenstrauss 引理"></a>2. 维度祝福：Johnson-Lindenstrauss 引理</h2><p>既然高维这么可怕，为什么现在的 AI 还要用 1536 维，甚至 4096 维？<br>因为高维空间有一个神奇的<strong>祝福</strong>：<strong>它足够大，大到可以容纳任何形状的流形。</strong></p><p>同时，有一个数学定理拯救了我们：<strong>Johnson-Lindenstrauss (JL) 引理</strong>。</p><blockquote><p><strong>JL 引理</strong>：一个在高维空间里的点集，可以被线性投影到一个低得多的维度（比如几百维），而点与点之间的距离关系<strong>几乎不变</strong>。</p></blockquote><p><img src="https://img.alicdn.com/imgextra/i1/O1CN01HUgEVE20aE6WqiD4r_!!6000000006865-2-tps-1376-768.png" alt="JL 引理示意图"></p><p><strong>直观理解</strong>：<br>想象天空中有一个复杂的星座（3维）。当你把它拍成照片（2维）时，星星之间的相对位置（谁离谁近）通常还是保持得不错的。<br>这就是<strong>随机投影</strong>的原理。它告诉我们：<strong>我们可以放心地降维，不用担心丢失太多信息。</strong></p><h2 id="3-工业项目的应对策略"><a href="#3-工业项目的应对策略" class="headerlink" title="3. 工业项目的应对策略"></a>3. 工业项目的应对策略</h2><p>面对维度灾难，现代文本分析系统通常采取以下策略：</p><ol><li><strong>特征提取用高维 (1536D)</strong>：<br>利用 Transformer 的高维能力，把语义分得尽可能细。在高维空间里，复杂的语义是<strong>线性可分</strong>的（容易切开）。</li><li><strong>距离计算用 Cosine</strong>：<br>避开欧氏距离在高维失效的坑，使用方向来度量相似。</li><li><strong>可视化用低维 (2D)</strong>：<br>利用 UMAP 算法，把高维流形“展开”铺平到 2D 屏幕上。JL 引理保证了这种操作在数学上是靠谱的。</li></ol><h2 id="4-动手时刻：验证距离失效"><a href="#4-动手时刻：验证距离失效" class="headerlink" title="4. 动手时刻：验证距离失效"></a>4. 动手时刻：验证距离失效</h2><p>我们可以写一段代码，亲眼看看随着维度增加，距离是怎么“崩坏”的。</p><pre class=" language-python"><code class="language-python"><span class="token keyword">import</span> numpy <span class="token keyword">as</span> np<span class="token keyword">from</span> scipy<span class="token punctuation">.</span>spatial<span class="token punctuation">.</span>distance <span class="token keyword">import</span> pdist<span class="token punctuation">,</span> squareform<span class="token keyword">import</span> matplotlib<span class="token punctuation">.</span>pyplot <span class="token keyword">as</span> plt<span class="token keyword">def</span> <span class="token function">get_contrast</span><span class="token punctuation">(</span>dim<span class="token punctuation">)</span><span class="token punctuation">:</span>    <span class="token comment" spellcheck="true"># 生成 100 个随机点</span>    X <span class="token operator">=</span> np<span class="token punctuation">.</span>random<span class="token punctuation">.</span>rand<span class="token punctuation">(</span><span class="token number">100</span><span class="token punctuation">,</span> dim<span class="token punctuation">)</span>    <span class="token comment" spellcheck="true"># 计算两两距离</span>    dists <span class="token operator">=</span> pdist<span class="token punctuation">(</span>X<span class="token punctuation">)</span>    <span class="token comment" spellcheck="true"># 计算对比度：(最大-最小)/最小</span>    <span class="token keyword">return</span> <span class="token punctuation">(</span>dists<span class="token punctuation">.</span>max<span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">-</span> dists<span class="token punctuation">.</span>min<span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">)</span> <span class="token operator">/</span> dists<span class="token punctuation">.</span>min<span class="token punctuation">(</span><span class="token punctuation">)</span>dims <span class="token operator">=</span> <span class="token punctuation">[</span><span class="token number">2</span><span class="token punctuation">,</span> <span class="token number">10</span><span class="token punctuation">,</span> <span class="token number">100</span><span class="token punctuation">,</span> <span class="token number">1000</span><span class="token punctuation">,</span> <span class="token number">5000</span><span class="token punctuation">]</span>contrasts <span class="token operator">=</span> <span class="token punctuation">[</span>get_contrast<span class="token punctuation">(</span>d<span class="token punctuation">)</span> <span class="token keyword">for</span> d <span class="token keyword">in</span> dims<span class="token punctuation">]</span><span class="token comment" spellcheck="true"># 你会看到 output 急剧下降</span><span class="token keyword">for</span> d<span class="token punctuation">,</span> c <span class="token keyword">in</span> zip<span class="token punctuation">(</span>dims<span class="token punctuation">,</span> contrasts<span class="token punctuation">)</span><span class="token punctuation">:</span>    <span class="token keyword">print</span><span class="token punctuation">(</span>f<span class="token string">"维度: &amp;#123;d:&lt;5&amp;#125; | 距离对比度: &amp;#123;c:.2f&amp;#125;"</span><span class="token punctuation">)</span><span class="token comment" spellcheck="true"># Output 示例:</span><span class="token comment" spellcheck="true"># 维度: 2     | 距离对比度: 28.5 (很容易区分远近)</span><span class="token comment" spellcheck="true"># 维度: 1000  | 距离对比度: 0.3  (几乎无法区分)</span></code></pre><h2 id="5-实践要点"><a href="#5-实践要点" class="headerlink" title="5. 实践要点"></a>5. 实践要点</h2><ol><li><strong>降维是必选项</strong>：处理 Embedding 数据时，如果想做可视化或密度估计，<strong>必须</strong>降维（PCA/UMAP）。</li><li><strong>不要在高维空间跑 DBSCAN</strong>：DBSCAN 依赖 <code>eps</code> 距离阈值。在高维空间，所有点的距离都差不多，你很难找到一个合适的 <code>eps</code>。要么全连在一起，要么全散开。</li><li><strong>向量数据库 (Vector DB)</strong>：现在的 Milvus, Pinecone 等向量数据库，底层都在做一件事——<strong>近似最近邻 (ANN)</strong>。它们通过构建图索引（HNSW），在不遍历所有点的情况下，快速找到邻居，从而绕过高维计算的瓶颈。</li></ol><hr><p><strong>下一章预告</strong>：<br>理论铺垫终于结束了！我们理解了向量，选择了距离，也知道了维度的坑。<br>现在，让我们进入算法的核心战场。<br>最经典的 <strong>K-Means</strong> 算法，究竟是如何在几万个点中，像牧羊犬一样把羊群赶进羊圈的？</p><p>👉 <a href="https://yeee.wang/posts/22f6.html">第 05 章：划分式聚类</a></p>]]></content>
    
    <summary type="html">
    
      第 04 章：高维数据的几何特性 (The Geometry of High-Dimensional Data)
“在高维空间里，每个人都是孤独的。”

欢迎来到 1536 维的世界。这里是 Embedding 的家园，也是直觉的坟墓。
我们的大脑是为三维世界进化的。我们很难想象，当维度增加到 1000 以上时，几何规则会发生怎样翻天覆地的变化。

这一章我们将揭示一个可怕的现象——维度灾难 (The Curse of Dimensionality)，以及一个美好的奇迹——维度祝福。



1. 维度灾难：空旷的宇宙
1.1 越来越空
想象一个边长为 1 的正方形（2维）。如果你往里面撒 10
    
    </summary>
    
      <category term="算法" scheme="https://yeee.wang/categories/%E7%AE%97%E6%B3%95/"/>
    
    
      <category term="算法" scheme="https://yeee.wang/tags/%E7%AE%97%E6%B3%95/"/>
    
      <category term="数据分析" scheme="https://yeee.wang/tags/%E6%95%B0%E6%8D%AE%E5%88%86%E6%9E%90/"/>
    
      <category term="机器学习" scheme="https://yeee.wang/tags/%E6%9C%BA%E5%99%A8%E5%AD%A6%E4%B9%A0/"/>
    
  </entry>
  
  <entry>
    <title>第 03 章：相似度与距离度量</title>
    <link href="https://yeee.wang/posts/bdff.html"/>
    <id>https://yeee.wang/posts/bdff.html</id>
    <published>2025-12-22T09:10:00.000Z</published>
    <updated>2026-02-19T20:00:41.878Z</updated>
    
    <content type="html"><![CDATA[<h1 id="第-03-章：相似度与距离度量-Similarity-and-Distance-Metrics"><a href="#第-03-章：相似度与距离度量-Similarity-and-Distance-Metrics" class="headerlink" title="第 03 章：相似度与距离度量 (Similarity and Distance Metrics)"></a>第 03 章：相似度与距离度量 (Similarity and Distance Metrics)</h1><blockquote><p>“如果不定义‘近’，我们就无法定义‘类’。”</p></blockquote><p>在第 2 章中，我们成功把“投诉”变成了“向量”。<br>现在，文本分析系统面临一个核心问题：<strong>如何判断两条投诉是不是在说同一件事？</strong></p><ul><li>A: “My package is lost.” (向量 $v_A$)</li><li>B: “I haven’t received my item.” (向量 $v_B$)</li></ul><p>我们需要一把数学“尺子”来量一量 $v_A$ 和 $v_B$ 之间的距离。<br>距离越近 $\rightarrow$ 越相似 $\rightarrow$ 应该聚为一类。</p><p>但是，数学世界里有无数种尺子。用哪一把？这决定了聚类的生死。</p><p><img src="https://img.alicdn.com/imgextra/i1/O1CN01Z8ypQF1wFNc6wHOm0_!!6000000006278-2-tps-1376-768.png" alt="欧氏距离与余弦相似度对比"></p><h2 id="1-核心概念：常用的几把尺子"><a href="#1-核心概念：常用的几把尺子" class="headerlink" title="1. 核心概念：常用的几把尺子"></a>1. 核心概念：常用的几把尺子</h2><h3 id="1-1-欧氏距离-Euclidean-Distance-：直线连接"><a href="#1-1-欧氏距离-Euclidean-Distance-：直线连接" class="headerlink" title="1.1 欧氏距离 (Euclidean Distance)：直线连接"></a>1.1 欧氏距离 (Euclidean Distance)：直线连接</h3><p>这是我们最熟悉的尺子，也就是两点间的<strong>直线距离</strong>。<br>$$ d(x, y) = \sqrt{\sum (x_i - y_i)^2} $$</p><ul><li><strong>特点</strong>：它对<strong>数值的大小</strong>非常敏感。</li><li><strong>Bug</strong>：在文本分析中，这通常是个灾难。<ul><li>文本 A: “Good” (向量长度短)</li><li>文本 B: “Good Good Good” (向量长度长，因为词多)</li><li>文本 C: “Bad” (向量长度短)</li><li>虽然 A 和 B 意思完全一样，但在欧氏空间里，A 和 B 的距离可能比 A 和 C 还远！因为 B 飞到了空间很远的地方。</li></ul></li></ul><h3 id="1-2-余弦相似度-Cosine-Similarity-：看方向，不看长短"><a href="#1-2-余弦相似度-Cosine-Similarity-：看方向，不看长短" class="headerlink" title="1.2 余弦相似度 (Cosine Similarity)：看方向，不看长短"></a>1.2 余弦相似度 (Cosine Similarity)：看方向，不看长短</h3><p>这是 NLP (自然语言处理) 领域的<strong>黄金标准</strong>。它不关心向量有多长，只关心向量指向哪里（夹角）。</p><p>$$ \text{Similarity} = \cos(\theta) = \frac{A \cdot B}{||A|| \times ||B||} $$</p><ul><li><strong>夹角 0 度</strong>：完全重合 ($\cos=1$)。比如 “Good” 和 “Good Good Good”。</li><li><strong>夹角 90 度</strong>：完全无关 ($\cos=0$)。比如 “Apple” 和 “Car”。</li><li><strong>夹角 180 度</strong>：截然相反 ($\cos=-1$)。</li></ul><p>在文本分析项目中，通常全程使用余弦相似度。因为我们只在乎<strong>语义的方向</strong>（是物流问题还是支付问题），不在乎用户啰嗦了多少个词（向量长度）。</p><h3 id="1-3-曼哈顿距离-Manhattan-Distance-：出租车路线"><a href="#1-3-曼哈顿距离-Manhattan-Distance-：出租车路线" class="headerlink" title="1.3 曼哈顿距离 (Manhattan Distance)：出租车路线"></a>1.3 曼哈顿距离 (Manhattan Distance)：出租车路线</h3><p>想象在一个方格状的城市里开车，你不能横穿建筑，只能走横竖的街道。<br>$$ d(x, y) = \sum |x_i - y_i| $$</p><ul><li><strong>用途</strong>：在高维稀疏数据（如推荐系统的用户特征）中，有时比欧氏距离更有效。</li></ul><p><img src="https://img.alicdn.com/imgextra/i3/O1CN01Q2rxTp1K7B7cUSSwj_!!6000000001116-2-tps-1376-768.png" alt="距离度量地图"></p><h2 id="2-技术对比：什么时候用什么？"><a href="#2-技术对比：什么时候用什么？" class="headerlink" title="2. 技术对比：什么时候用什么？"></a>2. 技术对比：什么时候用什么？</h2><table><thead><tr><th style="text-align:left">度量</th><th style="text-align:left">关注点</th><th style="text-align:left">适用场景</th><th style="text-align:left">致命弱点</th></tr></thead><tbody><tr><td style="text-align:left"><strong>欧氏距离</strong></td><td style="text-align:left">绝对位置</td><td style="text-align:left">物理坐标（GPS）、图像像素、身高体重聚类</td><td style="text-align:left">对<strong>量纲</strong>敏感（必须先归一化），受向量长度影响大</td></tr><tr><td style="text-align:left"><strong>余弦相似度</strong></td><td style="text-align:left"><strong>方向 (语义)</strong></td><td style="text-align:left"><strong>文本挖掘</strong>、推荐系统、用户兴趣画像</td><td style="text-align:left">不满足三角不等式（严格来说不是“距离”）</td></tr><tr><td style="text-align:left"><strong>曼哈顿距离</strong></td><td style="text-align:left">维度累积差</td><td style="text-align:left">高维稀疏特征（如用户标签匹配）</td><td style="text-align:left">旋转不具备不变性（坐标轴旋转后距离会变）</td></tr><tr><td style="text-align:left"><strong>Jaccard</strong></td><td style="text-align:left">集合重叠度</td><td style="text-align:left">两个集合是否相似（如两个用户买了多少相同的商品）</td><td style="text-align:left">忽略了元素的具体数值（只看有无，不看多少）</td></tr></tbody></table><h2 id="3-工程实战：混合双打"><a href="#3-工程实战：混合双打" class="headerlink" title="3. 工程实战：混合双打"></a>3. 工程实战：混合双打</h2><p>有趣的是，在实际工程中，可以同时使用<strong>余弦相似度</strong>和<strong>欧氏距离</strong>，分别用于不同的阶段。这体现了极高的工程技巧。</p><h3 id="第一阶段：UMAP-降维-——-用-Cosine"><a href="#第一阶段：UMAP-降维-——-用-Cosine" class="headerlink" title="第一阶段：UMAP 降维 —— 用 Cosine"></a>第一阶段：UMAP 降维 —— 用 Cosine</h3><pre class=" language-python"><code class="language-python"><span class="token comment" spellcheck="true"># 代码示例</span>reducer <span class="token operator">=</span> umap<span class="token punctuation">.</span>UMAP<span class="token punctuation">(</span>    metric<span class="token operator">=</span><span class="token string">'cosine'</span><span class="token punctuation">,</span>  <span class="token comment" spellcheck="true"># &lt;--- 这里用余弦！</span>    <span class="token comment" spellcheck="true"># ...</span><span class="token punctuation">)</span></code></pre><p><strong>理由</strong>：在 1536 维的语义空间里，只有方向代表语义。所以降维算法必须依据方向来把相似的点拉近。</p><h3 id="第二阶段：异常检测-——-用-Euclidean"><a href="#第二阶段：异常检测-——-用-Euclidean" class="headerlink" title="第二阶段：异常检测 —— 用 Euclidean"></a>第二阶段：异常检测 —— 用 Euclidean</h3><pre class=" language-python"><code class="language-python"><span class="token comment" spellcheck="true"># 代码示例：计算 UMAP 2D 空间中的局部密度</span>nbrs <span class="token operator">=</span> NearestNeighbors<span class="token punctuation">(</span>metric<span class="token operator">=</span><span class="token string">'euclidean'</span><span class="token punctuation">)</span><span class="token punctuation">.</span>fit<span class="token punctuation">(</span>coords_2d<span class="token punctuation">)</span></code></pre><p><strong>理由</strong>：当 UMAP 把数据降维到 2D 平面后，数据已经变成了<strong>几何坐标</strong> (x, y)。此时，语义已经转化为了位置。在 2D 平面上算密度，当然要用直观的欧氏距离（画圆圈）！</p><h2 id="4-动手时刻：自己算一算"><a href="#4-动手时刻：自己算一算" class="headerlink" title="4. 动手时刻：自己算一算"></a>4. 动手时刻：自己算一算</h2><p>我们用 Numpy 来验证一下 “Good” 和 “Good Good” 的区别。</p><pre class=" language-python"><code class="language-python"><span class="token keyword">import</span> numpy <span class="token keyword">as</span> np<span class="token keyword">from</span> scipy<span class="token punctuation">.</span>spatial<span class="token punctuation">.</span>distance <span class="token keyword">import</span> euclidean<span class="token punctuation">,</span> cosine<span class="token comment" spellcheck="true"># 假设 'Good' 的向量是 [1, 1]</span>v1 <span class="token operator">=</span> np<span class="token punctuation">.</span>array<span class="token punctuation">(</span><span class="token punctuation">[</span><span class="token number">1</span><span class="token punctuation">,</span> <span class="token number">1</span><span class="token punctuation">]</span><span class="token punctuation">)</span><span class="token comment" spellcheck="true"># 'Good Good' 只是把词频翻倍，向量变为 [2, 2]</span>v2 <span class="token operator">=</span> np<span class="token punctuation">.</span>array<span class="token punctuation">(</span><span class="token punctuation">[</span><span class="token number">2</span><span class="token punctuation">,</span> <span class="token number">2</span><span class="token punctuation">]</span><span class="token punctuation">)</span><span class="token comment" spellcheck="true"># 一个完全不同的词 'Bad'，向量是 [-1, 1]</span>v3 <span class="token operator">=</span> np<span class="token punctuation">.</span>array<span class="token punctuation">(</span><span class="token punctuation">[</span><span class="token operator">-</span><span class="token number">1</span><span class="token punctuation">,</span> <span class="token number">1</span><span class="token punctuation">]</span><span class="token punctuation">)</span><span class="token keyword">print</span><span class="token punctuation">(</span>f<span class="token string">"Good vs GoodGood (欧氏距离): &amp;#123;euclidean(v1, v2):.2f&amp;#125;"</span><span class="token punctuation">)</span> # <span class="token number">1.41</span> <span class="token punctuation">(</span>居然有距离！<span class="token punctuation">)</span><span class="token keyword">print</span><span class="token punctuation">(</span>f<span class="token string">"Good vs GoodGood (余弦距离): &amp;#123;cosine(v1, v2):.2f&amp;#125;"</span><span class="token punctuation">)</span>    # <span class="token number">0.00</span> <span class="token punctuation">(</span>完全一样！这是我们想要的<span class="token punctuation">)</span><span class="token keyword">print</span><span class="token punctuation">(</span>f<span class="token string">"Good vs Bad (余弦距离): &amp;#123;cosine(v1, v3):.2f&amp;#125;"</span><span class="token punctuation">)</span>       # <span class="token number">1.00</span> <span class="token punctuation">(</span>完全正交<span class="token operator">/</span>相反<span class="token punctuation">)</span></code></pre><p>看到没？如果你用欧氏距离，算法会认为“好”和“很好”是两回事。用余弦距离，它们就是一回事。</p><h2 id="5-实践要点"><a href="#5-实践要点" class="headerlink" title="5. 实践要点"></a>5. 实践要点</h2><ol><li><strong>归一化 (Normalization)</strong>：如果你非要用欧氏距离算文本，请务必先对向量做 L2 归一化（把长度都变成 1）。归一化后，欧氏距离的排名就和余弦距离一样了。</li><li><strong>维度灾难预警</strong>：在高维空间（比如 10000 维），欧氏距离会失效（所有点之间的距离都差不多，详见下一章）。而余弦相似度在高维空间依然相对鲁棒。</li></ol><hr><p><strong>下一章预告</strong>：<br>我们一直在说“高维空间”。1536 维到底是什么概念？<br>在那个世界里，发生着一些违反人类直觉的怪事——<strong>维度灾难</strong>。这直接决定了为什么我们需要降维。</p><p>👉 <a href="https://yeee.wang/posts/8ed4.html">第 04 章：高维数据的几何特性</a></p>]]></content>
    
    <summary type="html">
    
      第 03 章：相似度与距离度量 (Similarity and Distance Metrics)
“如果不定义‘近’，我们就无法定义‘类’。”

在第 2 章中，我们成功把“投诉”变成了“向量”。
现在，文本分析系统面临一个核心问题：如何判断两条投诉是不是在说同一件事？

 * A: “My package is lost.” (向量 $v_A$)
 * B: “I haven’t received my item.” (向量 $v_B$)

我们需要一把数学“尺子”来量一量 $v_A$ 和 $v_B$ 之间的距离。
距离越近 $&#92;rightarrow$ 越相似 $&#92;rightarrow$ 
    
    </summary>
    
      <category term="算法" scheme="https://yeee.wang/categories/%E7%AE%97%E6%B3%95/"/>
    
    
      <category term="算法" scheme="https://yeee.wang/tags/%E7%AE%97%E6%B3%95/"/>
    
      <category term="数据分析" scheme="https://yeee.wang/tags/%E6%95%B0%E6%8D%AE%E5%88%86%E6%9E%90/"/>
    
      <category term="机器学习" scheme="https://yeee.wang/tags/%E6%9C%BA%E5%99%A8%E5%AD%A6%E4%B9%A0/"/>
    
  </entry>
  
  <entry>
    <title>第 02 章：文本表示学习：从词袋到Transformer</title>
    <link href="https://yeee.wang/posts/d461.html"/>
    <id>https://yeee.wang/posts/d461.html</id>
    <published>2025-12-22T09:05:00.000Z</published>
    <updated>2026-02-19T20:00:41.878Z</updated>
    
    <content type="html"><![CDATA[<h1 id="第-02-章：文本表示学习：从词袋到-Transformer-Text-Representation-Learning"><a href="#第-02-章：文本表示学习：从词袋到-Transformer-Text-Representation-Learning" class="headerlink" title="第 02 章：文本表示学习：从词袋到 Transformer (Text Representation Learning)"></a>第 02 章：文本表示学习：从词袋到 Transformer (Text Representation Learning)</h1><blockquote><p>“语言是人类思想的密码，Embedding 是解开这道密码的钥匙。”</p></blockquote><h2 id="1-导言：计算机不懂泰语"><a href="#1-导言：计算机不懂泰语" class="headerlink" title="1. 导言：计算机不懂泰语"></a>1. 导言：计算机不懂泰语</h2><p>在多语言文本分析场景中，我们面临的第一个挑战是语言的巴别塔。<br>用户的抱怨五花八门：</p><ul><li>英文: “Where is my package?”</li><li>泰文: “พัสดุอยู่ที่ไหน?”</li><li>印尼文: “Di mana paket saya?”</li></ul><p>虽然字面完全不同，但它们的意思是一模一样的。<br>如果我们直接把这些字符串丢给聚类算法，算法会认为它们是完全不相关的东西。因为它只能看到字符 <code>W-h-e-r-e</code> 和 <code>พ-ั-ส-ด-ุ</code> 的区别。</p><p>我们需要一种<strong>通用语言</strong>，把所有人类的语言翻译成计算机能理解的<strong>数学语言</strong>。<br>这个过程，就是 <strong>文本表示 (Text Representation)</strong>，或者更时髦的叫法——<strong>Embedding</strong>。</p><p><img src="https://img.alicdn.com/imgextra/i3/O1CN012QT1jp1CzfJZksoD1_!!6000000000152-2-tps-1376-768.png" alt="Embedding 进化史"></p><h2 id="2-核心概念：进化的三个阶段"><a href="#2-核心概念：进化的三个阶段" class="headerlink" title="2. 核心概念：进化的三个阶段"></a>2. 核心概念：进化的三个阶段</h2><p>文本表示技术经历了三个时代的跨越，每一次跨越都让机器离“理解”更近一步。</p><h3 id="阶段一：词袋模型-Bag-of-Words-——-极其笨拙的翻译"><a href="#阶段一：词袋模型-Bag-of-Words-——-极其笨拙的翻译" class="headerlink" title="阶段一：词袋模型 (Bag of Words) —— 极其笨拙的翻译"></a>阶段一：词袋模型 (Bag of Words) —— 极其笨拙的翻译</h3><p>这是最早期的做法。假设我们有一个词典 <code>&#123;&#39;apple&#39;, &#39;banana&#39;, &#39;cat&#39;&#125;</code>。</p><ul><li>句子 “I have an apple” -&gt; <code>[1, 0, 0]</code> (假设只关注关键词)</li><li>句子 “I have a banana” -&gt; <code>[0, 1, 0]</code></li></ul><p><strong>致命缺陷</strong>：</p><ol><li><strong>稀疏 (Sparse)</strong>：词典可能有 10 万个词，你的向量就是 10 万维，全是 0。这极度浪费内存。</li><li><strong>语义鸿沟</strong>：在数学上，<code>[1,0,0]</code> 和 <code>[0,1,0]</code> 是正交的（垂直的），没有任何相似性。但在现实中，苹果和香蕉都是水果，应该很相似才对。</li></ol><h3 id="阶段二：静态词向量-Word2Vec-——-捕捉到了语义"><a href="#阶段二：静态词向量-Word2Vec-——-捕捉到了语义" class="headerlink" title="阶段二：静态词向量 (Word2Vec) —— 捕捉到了语义"></a>阶段二：静态词向量 (Word2Vec) —— 捕捉到了语义</h3><p>2013 年，Google 提出了 Word2Vec。它做了一件惊天动地的事：<strong>把词映射到低维稠密空间</strong>。</p><ul><li>Apple: <code>[0.8, 0.2, 0.1]</code></li><li>Banana: <code>[0.7, 0.3, 0.1]</code></li><li>Cat: <code>[-0.5, 0.1, 0.9]</code></li></ul><p>在这个空间里，神奇的事情发生了：</p><ul><li>$\text{Apple} \approx \text{Banana}$ (向量距离很近)</li><li>$\vec{King} - \vec{Man} + \vec{Woman} \approx \vec{Queen}$ (居然能做加减法！)</li></ul><p><strong>致命缺陷</strong>：<br>它是<strong>静态</strong>的。单词 “Bank” 在 “River Bank” (河岸) 和 “Bank Account” (银行) 中，用的是同一个向量。机器依然是个脸盲。</p><h3 id="阶段三：动态上下文向量-Transformer-BERT-——-真正的理解"><a href="#阶段三：动态上下文向量-Transformer-BERT-——-真正的理解" class="headerlink" title="阶段三：动态上下文向量 (Transformer / BERT) —— 真正的理解"></a>阶段三：动态上下文向量 (Transformer / BERT) —— 真正的理解</h3><p>这是目前最先进的技术（也是大多数现代工业系统的选择）。<br>Transformer 模型（如 BERT, GPT）引入了 <strong>Self-Attention (自注意力机制)</strong>。<br>在生成向量时，它会看<strong>整句话</strong>。</p><ul><li>当它看到 “River Bank” 时，它会给 “Bank” 一个代表【地理位置】的向量。</li><li>当它看到 “Bank Account” 时，它会给 “Bank” 一个代表【金融机构】的向量。</li></ul><p><strong>这就是为什么现代 Embedding 模型能听懂多国语言</strong>：<br>OpenAI 的 Embedding 模型经过了海量多语言语料的训练。它不仅理解了上下文，还理解了跨语言的对应关系。</p><ul><li>English “Package” 的向量 $\approx$ Thai “พัสดุ” 的向量。</li><li>在 1536 维的空间里，它们几乎重叠在一起。</li></ul><p><img src="https://img.alicdn.com/imgextra/i1/O1CN01yOC6YV1KShjlHUkk3_!!6000000001163-2-tps-1376-768.png" alt="语义空间示意图"></p><h2 id="3-技术对比：主流-Embedding-方案"><a href="#3-技术对比：主流-Embedding-方案" class="headerlink" title="3. 技术对比：主流 Embedding 方案"></a>3. 技术对比：主流 Embedding 方案</h2><p>在工业界，选择哪种 Embedding 方案取决于你的钱包和需求。</p><table><thead><tr><th style="text-align:left">方案</th><th style="text-align:left">代表模型</th><th style="text-align:left">优点</th><th style="text-align:left">缺点</th><th style="text-align:left">工业界常见选择</th></tr></thead><tbody><tr><td style="text-align:left"><strong>开源小模型</strong></td><td style="text-align:left">BERT, RoBERTa</td><td style="text-align:left">免费，可私有化部署，速度快</td><td style="text-align:left">维度低 (768)，语义理解能力有限，多语言支持弱</td><td style="text-align:left">❌</td></tr><tr><td style="text-align:left"><strong>开源大模型</strong></td><td style="text-align:left">E5, BGE (MTEB 榜单前列)</td><td style="text-align:left">效果极好，目前 SOTA</td><td style="text-align:left">需要昂贵的 GPU 显存，部署维护麻烦</td><td style="text-align:left">❌</td></tr><tr><td style="text-align:left"><strong>商业 API</strong></td><td style="text-align:left"><strong>OpenAI</strong>, Cohere, Google PaLM</td><td style="text-align:left"><strong>效果顶级，多语言无敌，无需运维</strong></td><td style="text-align:left">收费，数据隐私（需传云端）</td><td style="text-align:left">✅</td></tr></tbody></table><p><strong>决策思考</strong>：<br>对于需要处理小语种（如泰语、越南语、印尼语等）的项目，开源模型的支持通常较弱。而 OpenAI 的模型在多语言对齐上具有统治级优势。虽然要花钱，但相比于自己雇人清洗数据、训练模型的成本，调用 API 反而是最省钱的。</p><h2 id="4-代码实战：调用-OpenAI-Embedding"><a href="#4-代码实战：调用-OpenAI-Embedding" class="headerlink" title="4. 代码实战：调用 OpenAI Embedding"></a>4. 代码实战：调用 OpenAI Embedding</h2><p>在 <code>cluster_analysis.py</code> 中，我们封装了一个函数来获取向量。</p><pre class=" language-python"><code class="language-python"><span class="token comment" spellcheck="true"># 通用实现：假设你有一个 API Key</span><span class="token keyword">import</span> openai<span class="token keyword">def</span> <span class="token function">get_embedding</span><span class="token punctuation">(</span>text<span class="token punctuation">,</span> model<span class="token operator">=</span><span class="token string">"text-embedding-3-small"</span><span class="token punctuation">)</span><span class="token punctuation">:</span>   text <span class="token operator">=</span> text<span class="token punctuation">.</span>replace<span class="token punctuation">(</span><span class="token string">"\n"</span><span class="token punctuation">,</span> <span class="token string">" "</span><span class="token punctuation">)</span>   <span class="token keyword">return</span> openai<span class="token punctuation">.</span>embeddings<span class="token punctuation">.</span>create<span class="token punctuation">(</span>input <span class="token operator">=</span> <span class="token punctuation">[</span>text<span class="token punctuation">]</span><span class="token punctuation">,</span> model<span class="token operator">=</span>model<span class="token punctuation">)</span><span class="token punctuation">.</span>data<span class="token punctuation">[</span><span class="token number">0</span><span class="token punctuation">]</span><span class="token punctuation">.</span>embedding<span class="token comment" spellcheck="true"># 工业实践：使用 requests 库直接调用 REST API，并增加重试机制</span><span class="token keyword">def</span> <span class="token function">get_single_embedding_with_retry</span><span class="token punctuation">(</span>session<span class="token punctuation">,</span> text<span class="token punctuation">,</span> index<span class="token punctuation">)</span><span class="token punctuation">:</span>    <span class="token comment" spellcheck="true"># ... (省略重试逻辑)</span>    response <span class="token operator">=</span> session<span class="token punctuation">.</span>post<span class="token punctuation">(</span>        f<span class="token string">"&amp;#123;OPENAI_BASE_URL&amp;#125;/v1/embeddings"</span><span class="token punctuation">,</span>        json<span class="token operator">=</span><span class="token operator">&amp;</span><span class="token comment" spellcheck="true">#123;</span>            <span class="token string">"model"</span><span class="token punctuation">:</span> <span class="token string">"text-embedding-3-small"</span><span class="token punctuation">,</span>  <span class="token comment" spellcheck="true"># OpenAI 的 Embedding 模型</span>            <span class="token string">"input"</span><span class="token punctuation">:</span> text        <span class="token operator">&amp;</span><span class="token comment" spellcheck="true">#125;</span>    <span class="token punctuation">)</span>    <span class="token comment" spellcheck="true"># 返回一个 list，长度为 1536</span>    <span class="token keyword">return</span> result<span class="token punctuation">[</span><span class="token string">'data'</span><span class="token punctuation">]</span><span class="token punctuation">[</span><span class="token number">0</span><span class="token punctuation">]</span><span class="token punctuation">[</span><span class="token string">'embedding'</span><span class="token punctuation">]</span></code></pre><p>得到的这个 <code>[0.12, -0.45, ...]</code> 长度为 1536 的数组，就是那句话的<strong>数字灵魂</strong>。</p><h2 id="5-实践要点"><a href="#5-实践要点" class="headerlink" title="5. 实践要点"></a>5. 实践要点</h2><ol><li><strong>文本清洗</strong>：虽然 Transformer 很强，但不要给它喂垃圾。<ul><li>去掉无意义的 HTML 标签 (<code>&lt;br&gt;</code>, <code>&lt;div&gt;</code>)。</li><li>截断超长文本（OpenAI 通常限制 8191 tokens，但为了效果，建议只取前 512 个 token）。</li></ul></li><li><strong>加权策略</strong>：<ul><li>在实际项目中，可以把 <code>category</code> (分类) 字段重复拼接到 <code>description</code> 前面。</li><li><code>&quot;Logistics | Logistics | Package lost&quot;</code></li><li>这就相当于告诉 Attention 机制：“嘿，这是物流相关的，请重点关注物流这个词！”这是一种隐式的特征加权。</li></ul></li><li><strong>缓存 (Caching)</strong>：<ul><li>API 是要钱的！而且很慢！</li><li><strong>务必</strong>把跑过的文本 Hash 一下，存到本地文件里。下次跑同一句话，直接读缓存，别再调 API 了。这是降低成本的关键策略。</li></ul></li></ol><hr><p><strong>下一章预告</strong>：<br>现在我们手里有了一堆向量。<br>我们该如何判断两个向量是不是“相似”的？是用尺子量距离？还是用量角器量角度？<br>在高维空间里，这可是个大问题。</p><p>👉 <a href="https://yeee.wang/posts/bdff.html">第 03 章：相似度与距离度量</a></p>]]></content>
    
    <summary type="html">
    
      第 02 章：文本表示学习：从词袋到 Transformer (Text Representation Learning)
“语言是人类思想的密码，Embedding 是解开这道密码的钥匙。”

1. 导言：计算机不懂泰语
在多语言文本分析场景中，我们面临的第一个挑战是语言的巴别塔。
用户的抱怨五花八门：

 * 英文: “Where is my package?”
 * 泰文: “พัสดุอยู่ที่ไหน?”
 * 印尼文: “Di mana paket saya?”

虽然字面完全不同，但它们的意思是一模一样的。
如果我们直接把这些字符串丢给聚类算法，算法会认为它们是完全不相关的东西
    
    </summary>
    
      <category term="算法" scheme="https://yeee.wang/categories/%E7%AE%97%E6%B3%95/"/>
    
    
      <category term="算法" scheme="https://yeee.wang/tags/%E7%AE%97%E6%B3%95/"/>
    
      <category term="数据分析" scheme="https://yeee.wang/tags/%E6%95%B0%E6%8D%AE%E5%88%86%E6%9E%90/"/>
    
      <category term="机器学习" scheme="https://yeee.wang/tags/%E6%9C%BA%E5%99%A8%E5%AD%A6%E4%B9%A0/"/>
    
  </entry>
  
</feed>
