<?xml version="1.0" encoding="UTF-8"?>
<rss xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:content="http://purl.org/rss/1.0/modules/content/"
    xmlns:atom="http://www.w3.org/2005/Atom" xmlns:media="http://search.yahoo.com/mrss/" version="2.0">
    <channel>
        
        <title>
            <![CDATA[ AI - freeCodeCamp.org ]]>
        </title>
        <description>
            <![CDATA[ freeCodeCamp 是一个免费学习编程的开发者社区，涵盖 Python、HTML、CSS、React、Vue、BootStrap、JSON 教程等，还有活跃的技术论坛和丰富的社区活动，在你学习编程和找工作时为你提供建议和帮助。 ]]>
        </description>
        <link>https://www.freecodecamp.org/chinese/news/</link>
        <image>
            <url>https://cdn.freecodecamp.org/universal/favicons/favicon.png</url>
            <title>
                <![CDATA[ AI - freeCodeCamp.org ]]>
            </title>
            <link>https://www.freecodecamp.org/chinese/news/</link>
        </image>
        <generator>Eleventy</generator>
        <lastBuildDate>Wed, 03 Jun 2026 17:04:23 +0000</lastBuildDate>
        <atom:link href="https://www.freecodecamp.org/chinese/news/tag/ai/rss.xml" rel="self" type="application/rss+xml" />
        <ttl>60</ttl>
        
            <item>
                <title>
                    <![CDATA[ 如何成为 AI 辅助编程专家 - 开发者手册 ]]>
                </title>
                <description>
                    <![CDATA[ 在过去的七年里，我一直负责 freeCodeCamp 的基础设施。现在我确信，经验丰富的开发者可以在保持代码质量的同时提升 3-4 倍的编码速度。这就是 AI 辅助开发能够提供的优势。简单来说，使用像 GitHub Copilot 这样的 AI 工具作为你的编码伙伴可以提高效率。它们可以建议代码、帮助你调试以及加快处理重复性任务。 为什么这很重要 传统编码时，你需要自己编写每一行代码、查找文档、弄清语法。有了 AI，你可以：  * 专注于解决问题，而不是记住语法          * 通过实时看到优秀的代码示例更快地学习          * 快速构建项目而不牺牲质量         对于有经验的开发者来说，在 AI ]]>
                </description>
                <link>https://www.freecodecamp.org/chinese/news/how-to-become-an-expert-in-ai-assisted-coding-a-handbook-for-developers/</link>
                <guid isPermaLink="false">6953dfe138384c0457be01cb</guid>
                
                    <category>
                        <![CDATA[ AI ]]>
                    </category>
                
                <dc:creator>
                    <![CDATA[ Miya Liu ]]>
                </dc:creator>
                <pubDate>Fri, 09 Jan 2026 11:16:00 +0000</pubDate>
                <media:content url="https://chinese.freecodecamp.org/news/content/images/2026/01/----_20260110202949_190_113.png" medium="image" />
                <content:encoded>
                    <![CDATA[ <p data-test-label="translation-intro">
        <strong>原文：</strong> <a href="https://www.freecodecamp.org/news/how-to-become-an-expert-in-ai-assisted-coding-a-handbook-for-developers/" target="_blank" rel="noopener noreferrer" data-test-label="original-article-link">How to Become an Expert in AI-Assisted Coding – A Handbook for Developers</a>
      </p><!--kg-card-begin: markdown--><p>在过去的七年里，我一直负责 freeCodeCamp 的基础设施。现在我确信，经验丰富的开发者可以在保持代码质量的同时提升 3-4 倍的编码速度。这就是 AI 辅助开发能够提供的优势。简单来说，使用像 GitHub Copilot 这样的 AI 工具作为你的编码伙伴可以提高效率。它们可以建议代码、帮助你调试以及加快处理重复性任务。</p>
<h3 id="">为什么这很重要</h3>
<p>传统编码时，你需要自己编写每一行代码、查找文档、弄清语法。有了 AI，你可以：</p>
<ul>
<li>
<p>专注于解决问题，而不是记住语法</p>
</li>
<li>
<p>通过实时看到优秀的代码示例更快地学习</p>
</li>
<li>
<p>快速构建项目而不牺牲质量</p>
</li>
</ul>
<p>对于有经验的开发者来说，在 AI 帮助下可以更快地完成任务。但关键是：<strong>你需要了解如何有效使用这些工具</strong>。你也需要有编程背景来做到这一点。</p>
<p>感兴趣吗？让我们深入了解这些已经风靡全球的 AI 编码工具。</p>
<h2 id="">目录</h2>
<ul>
<li>
<p><a href="#essential-ai-terminology">基础 AI 术语</a></p>
</li>
<li>
<p><a href="#when-to-use-ai-vs-when-to-code-yourself">何时使用 AI 以及何时自己编码</a></p>
</li>
<li>
<p><a href="#prerequisites">前提条件</a></p>
</li>
<li>
<p><a href="#your-complete-learning-journey">你的完整学习旅程</a></p>
</li>
<li>
<p><a href="#how-to-generate-your-first-ai-assisted-code">如何生成你的第一个 AI 辅助代码（快速入门）</a></p>
</li>
<li>
<p><a href="#stage-1">阶段 1：基础 – 开始 AI 编码</a></p>
</li>
<li>
<p><a href="#stage-2">阶段 2：高级 GitHub Copilot 功能</a></p>
</li>
<li>
<p><a href="#stage-3">阶段 3：基于 CLI 的 AI 代理（Claude Code &amp; Gemini）</a></p>
</li>
<li>
<p><a href="#stage-4">阶段 4：掌握 – 结合工具和高级工作流程</a></p>
</li>
<li>
<p><a href="#common-ai-issues">常见 AI 问题</a></p>
</li>
<li>
<p><a href="#whats-next-after-completing-all-stages">完成所有阶段后接下来做什么？</a></p>
</li>
<li>
<p><a href="#conclusion">结语</a></p>
</li>
</ul>
<h2 id="essential-ai-terminology">基础 AI 术语</h2>
<p>在开始之前，确保你了解以下关键术语：</p>
<ul>
<li>
<p><strong>Tokens（令牌）：</strong> 把令牌想象成“词片”——AI 阅读你的代码和文本的方式。每个字符、单词或符号都会使用令牌。免费版本会限制你可以使用的令牌数量。</p>
</li>
<li>
<p><strong>Context Window（上下文窗口）：</strong> AI 能一次性“记住”多少代码/对话。就像短期记忆，更大的窗口意味着对项目的更好理解。</p>
</li>
<li>
<p><strong>Hallucinations（幻觉）：</strong> AI 自信地建议错误信息——例如，捏造不存在的函数。务必验证 AI 的建议！</p>
</li>
<li>
<p><strong>Prompt（提示）：</strong> 你给 AI 的指令——评论、问题或请求，用以指导其生成的代码。</p>
</li>
</ul>
<h2 id="when-to-use-ai-vs-when-to-code-yourself">何时使用 AI 以及何时自己编码</h2>
<p><strong>使用 AI 的场合：</strong></p>
<ul>
<li>
<p>编写模板代码（getters、setters、基本的 CRUD）</p>
</li>
<li>
<p>学习新的框架或语法</p>
</li>
<li>
<p>编写测试和文档</p>
</li>
<li>
<p>重构重复模式</p>
</li>
<li>
<p>在语法错误上解开困境</p>
</li>
</ul>
<p><strong>自己编码的场合：</strong></p>
<ul>
<li>
<p>设计系统架构</p>
</li>
<li>
<p>做出关键的安全决策</p>
</li>
<li>
<p>编写复杂的业务逻辑</p>
</li>
<li>
<p>学习新概念（首次）</p>
</li>
<li>
<p>进行关键的性能优化</p>
</li>
</ul>
<p><strong>黄金法则：</strong> 使用 AI 加速实施，但自己做架构决策。AI 擅长“如何”，但你决定“什么”和“为什么”。</p>
<h2 id="prerequisites">前提条件</h2>
<p>在开始本教程之前，你应该具备：</p>
<ul>
<li>
<p><strong>基本编程经验</strong> – 你可以用某种语言编写简单程序</p>
</li>
<li>
<p><strong>安装有代码编辑器</strong> – 推荐使用 VS Code（从 <a href="http://code.visualstudio.com/">code.visualstudio.com</a> 免费获取）</p>
</li>
<li>
<p><strong>基本 Git 知识</strong> – 你知道如何提交和推送代码</p>
</li>
<li>
<p><strong>免费起步</strong> – 许多工具现在都有慷慨的免费版本，付费计划最低大约每月 10-20 美元</p>
</li>
</ul>
<h2 id="your-complete-learning-journey">你的完整学习旅程</h2>
<p>这篇全面的教程提供了一个循序渐进的计划，旨在助你成为 AI 辅助开发的专家：</p>
<p>注意：为了保持教程的易用性，我们将只关注一些核心工具。但你应该研究和探索更多适合你特定需求的工具，超出我们在此使用的工具。</p>
<h3 id="">学习路径</h3>
<p>你将经过 4 个阶段：掌握 GitHub Copilot 基础、解锁高级功能如聊天模式和代理、探索 CLI 工具（Claude Code 和 Gemini），最终战略性地结合多个工具实现完整的项目工作流程。</p>
<p>首先，让我们快速看看如何生成你的第一个 AI 代码片段。</p>
<h2 id="how-to-generate-your-first-ai-assisted-code">如何生成你的第一个 AI 辅助代码（快速入门）</h2>
<p>让我们从最基础的开始。别担心选择“完美”的工具——你总可以后续切换。以下是开始的方法：</p>
<h3 id="githubcopilot">GitHub Copilot（推荐初学者使用）</h3>
<p>你可以通过以下步骤安装 GitHub Copilot：</p>
<ol>
<li>
<p>打开 VS Code</p>
</li>
<li>
<p>点击扩展图标（或按 Ctrl+Shift+X）</p>
</li>
<li>
<p>搜索“GitHub Copilot”</p>
</li>
<li>
<p>点击“安装”</p>
</li>
<li>
<p>使用你的 GitHub 账户登录</p>
</li>
</ol>
<p><strong>提示：</strong> 学生、教师和开源软件维护者<a href="https://docs.github.com/en/copilot/how-tos/manage-your-account/getting-free-access-to-copilot-pro-as-a-student-teacher-or-maintainer">可以免费获得 Pro 计划</a>，这可以提供无限制的使用，而不是免费的使用限制。</p>
<h3 id="ai">你的第一个 AI 建议</h3>
<p>安装后，创建一个名为 <code>test.js</code> 的新文件，并输入：</p>
<pre><code>// function to calculate the area of a circle
</code></pre>
<p>按下 Enter 键等待。你会看到灰色文本出现——这是您的 AI 建议！按 Tab 键接受它。</p>
<p>就这样！您刚刚获得了第一个 AI 建议！是不是很酷？</p>
<h2 id="stage-1">阶段 1：基础 – 开始使用 AI 编码</h2>
<h3 id="1">第 1 步：了解您的选项</h3>
<p>将 AI 编码助手想象成不同类型的有帮助的朋友和同事。让我们来浏览几个：</p>
<p><strong>基于 IDE 的：</strong> 一些工具设计为可以与熟悉的代码编辑器一起工作，或作为编辑器如 VS Code 的独立分支。例如：</p>
<ul>
<li>
<p><strong>GitHub Copilot (VS Code 扩展)</strong> – 来自 GitHub 的 AI 编码助手，直接在 VS Code 中工作，提供 Tab 补全和聊天功能</p>
</li>
<li>
<p><strong>Cursor (独立)</strong> – VS Code 分支，具有增强的代理模式、更快的自主编码和更好的大代码库重构处理</p>
</li>
<li>
<p><strong>Windsurf (独立或 VS Code 扩展)</strong> – 专注于实时建议和团队功能的协作 AI 开发</p>
</li>
<li>
<p><strong>Zed</strong> – 高性能编辑器，内置 AI 辅助功能和快速渲染</p>
</li>
</ul>
<p><strong>基于 CLI 的：</strong> 一些工具是基于 CLI 的，您可以在终端应用中启动：</p>
<ul>
<li>
<p><strong>Claude Code</strong> – Anthropic 的终端 AI，用于自主开发会话和复杂推理</p>
</li>
<li>
<p><strong>Gemini</strong> – Google 的 CLI 工具，具有大上下文窗口和多模态功能（图像、文档）</p>
</li>
<li>
<p><strong>OpenCode</strong> – 开源替代方案，具有可自定义模型和本地处理选项</p>
</li>
<li>
<p><strong>Cursor CLI</strong> – Cursor 的终端版本，用于命令行 AI 辅助</p>
</li>
</ul>
<p><strong>基于 UI 和后台代理的工具：</strong> 除此之外，还有一些后台代理和工具可以完全在后台运行，例如执行拉取请求审查等。</p>
<p>例如，如果您设置了它们，ChatGPT 和 Claude 的桌面应用程序都可以编辑本地文件系统上的文件。同样，一些基于云的代理可以“在后台运行”以完成您的指令。我们将在本指南范围内排除这些。</p>
<h3 id="2tab">第 2 步：做出选择并学习自动建议（Tab 补全）</h3>
<p>对于您的第一阶段，我建议从 GitHub Copilot 开始。您可以在学习完基础知识后随时切换到适合您需求的工具。</p>
<h3 id="3">第 3 步：逐步设置</h3>
<h4 id="githubcopilot">如何设置 GitHub Copilot（如果您之前已经遵循过快速入门，可以跳过此步骤）</h4>
<ol>
<li>
<p><strong>打开 VS Code。</strong> 如果您没有，请从 <a href="https://code.visualstudio.com/">code.visualstudio.com</a> 下载。</p>
</li>
<li>
<p><strong>安装扩展</strong></p>
<ul>
<li>
<p>按 <code>Ctrl+Shift+X</code>（Windows/Linux）或 <code>Cmd+Shift+X</code>（Mac）</p>
</li>
<li>
<p>在搜索框中输入“GitHub Copilot”</p>
</li>
<li>
<p>点击蓝色的“安装”按钮</p>
</li>
<li>
<p>您会看到一个弹出窗口要求您登录</p>
</li>
</ul>
</li>
<li>
<p><strong>登录</strong></p>
<ul>
<li>
<p>点击“登录 GitHub”</p>
</li>
<li>
<p>您的浏览器会打开</p>
</li>
<li>
<p>使用您的 GitHub 帐户登录（如果需要，可以在 <a href="http://github.com/">github.com</a> 免费创建一个）</p>
</li>
<li>
<p>点击“授权 GitHub Copilot”</p>
</li>
</ul>
</li>
<li>
<p><strong>开始使用 Copilot</strong></p>
<ul>
<li>返回 VS Code，您会看到“GitHub Copilot 已准备好使用”</li>
</ul>
</li>
</ol>
<h3 id="4tab">第 4 步：掌握 Tab 补全</h3>
<p>让我们确保它正常工作。创建一个新文件：<code>hello.py</code>。输入此注释并按 Enter：</p>
<pre><code># function to greet a user by name
</code></pre>
<p>等待 1-2 秒。您应该会看到灰色文本出现。只需按 <code>Tab</code> 接受建议。</p>
<p><strong>您应该看到的内容：</strong></p>
<pre><code># function to greet a user by name
def greet_user(name):
    return f"Hello, {name}!"
</code></pre>
<p>如果您看到了这个，恭喜您！您现在正在使用 AI 帮助您编写代码。</p>
<p>如果您遇到设置问题，可以查看 <a href="http://localhost:3333/#troubleshooting-quick-reference">疑难解答快速参考</a> 寻求解决方案。</p>
<h3 id="5">第 5 步：基本键盘快捷键和第一次练习</h3>
<p>以下是您第一周需要的唯一快捷键：</p>
<p><strong>基础：</strong></p>
<ul>
<li>
<p><code>Tab</code> – 接受 AI 建议（这个用得最多！）</p>
</li>
<li>
<p><code>Esc</code> – 拒绝建议（当您不需要时）</p>
</li>
</ul>
<p>当您准备好了解更多时，尝试这些：</p>
<p><strong>Windows/Linux：</strong></p>
<ul>
<li>
<p><code>Alt+]</code> – 查看下一个建议</p>
</li>
<li>
<p><code>Alt+[</code> – 查看上一个建议</p>
</li>
<li>
<p><code>Ctrl+Enter</code> – 在面板中查看所有建议</p>
</li>
</ul>
<p><strong>macOS：</strong></p>
<ul>
<li>
<p><code>Option+]</code> （或 <code>Alt+]</code>） – 查看下一个建议</p>
</li>
<li>
<p><code>Option+[</code> （或 <code>Alt+[</code>) – 查看上一个建议</p>
</li>
<li>
<p><code>Ctrl+Enter</code> – 在面板中查看所有建议</p>
</li>
</ul>
<h3 id="1">阶段 1 实践练习</h3>
<h4 id="">练习：构建一个简单的待办事项应用</h4>
<ol>
<li>
<p>创建一个名为 <code>todo.js</code> 的新文件</p>
</li>
<li>
<p>以此注释开始：<code>// TODO app with add, remove, and list functions</code></p>
</li>
<li>
<p>添加此注释并等待 AI 建议：<code>// function to add a new todo item</code></p>
</li>
<li>
<p>如果建议看起来不错，按 Tab 键接受</p>
</li>
<li>
<p>继续为移除和列出函数添加注释</p>
</li>
<li>
<p>测试您的函数以确保它们工作正常</p>
</li>
</ol>
<p><strong>目标：</strong> 学习通过清晰的注释与 AI “对话”，并建立接受/拒绝建议的信心。</p>
<h3 id="">准备好进入下一阶段了吗？在继续之前，请确保您能够：</h3>
<pre><code>- [ ] 通过输入注释获取 AI 建议
- [ ] 使用 Tab 接受建议，使用 Esc 拒绝建议
- [ ] 使用 Alt+] 和 Alt+[ 查看不同的建议
- [ ] 在 AI 帮助下编写基本函数
</code></pre>
<p>如果您对这些基本内容感到满意，就可以学习更强大的 Copilot 功能了。</p>
<h2 id="stage-2">阶段 2：高级 GitHub Copilot 功能</h2>
<h3 id="6ai">第 6 步：获取更好的 AI 建议</h3>
<p>现在您已经了解了基础知识，让我们学习如何从您的 AI 那里得到_更好_的建议。诀窍在于了解您的 AI 能看到什么。</p>
<h4 id="ai">您的 AI 助手能看到什么</h4>
<p>想象您的 AI 助手就像一个站在你肩头、随时提供帮助的朋友。它能看到：</p>
<ol>
<li>
<p><strong>您当前正在输入的内容</strong>——您的当前文件</p>
</li>
<li>
<p><strong>其他打开的标签页</strong>——您打开的文件（这很重要！）</p>
</li>
<li>
<p><strong>您的项目结构</strong>——文件夹和文件名</p>
</li>
<li>
<p><strong>您的注释</strong>——这是您与 AI “对话” 的方式</p>
</li>
</ol>
<h4 id="">“相邻标签页”技巧</h4>
<p>这是一个能为您节省数小时的专业提示：<strong>在标签页中打开相关文件</strong>。</p>
<p><strong>示例：</strong> 如果您在编写一个 React 组件：</p>
<ul>
<li>
<p>打开您的组件文件 (<code>Button.jsx</code>)</p>
</li>
<li>
<p>还要打开 CSS 文件 (<code>Button.css</code>)</p>
</li>
<li>
<p>同时也让测试文件可见 (<code>Button.test.js</code>)</p>
</li>
</ul>
<p>然后，您可以通过几种方式与 AI 共享这些额外文件的上下文：</p>
<ul>
<li>
<p><strong>@提及文件：</strong> 在聊天中键入 <code>@filename.js</code> 以引用特定文件</p>
</li>
<li>
<p><strong>使用 @workspace：</strong> 这个聊天参与者可以看到您项目中的所有文件</p>
</li>
<li>
<p><strong>拖放：</strong> 只需将文件从资源管理器窗口拖动到聊天窗口</p>
</li>
<li>
<p><strong>选择代码：</strong> 高亮代码并右键单击 “Ask Copilot” 以将其包括在上下文中</p>
</li>
</ul>
<p>AI 使用这些打开的文件来理解您的项目结构，并建议更相关的代码以匹配您现有的模式。</p>
<h3 id="7">第 7 步：质量控制与最佳实践</h3>
<h4 id="ai">了解 AI 的局限性</h4>
<p>AI 功能强大，但并不完美。以下是需要注意的关键事项。</p>
<p><strong>常见的 AI 错误：</strong></p>
<ol>
<li>
<p>虚构的函数：例如，<code>const result = array.superSort();</code> 并不存在！</p>
</li>
<li>
<p>错误的参数：例如，当函数期望 <code>greetUser(name)</code> 时，<code>greetUser("John", "Doe");</code></p>
</li>
<li>
<p>过于复杂的解决方案：例如，<code>const isEven = (num) =&gt; num.toString(2).slice(-1) === "0";</code> - 只需使用 <code>num % 2 === 0</code></p>
</li>
</ol>
<p>快速质量检查清单：</p>
<pre><code>- [ ] 测试代码 - 它真的有效吗？
- [ ] 阅读 - 逻辑上是否合理？
- [ ] 检查基础 - 所有函数/变量是否已定义？
- [ ] 信任直觉 - 如果感觉不对，进行调查
</code></pre>
<h4 id="">安全要点</h4>
<p>在接受 AI 建议之前，请确保检查这些安全问题：</p>
<pre><code>- [ ] 没有硬编码的密码或 API 密钥
- [ ] 用户输入已验证
- [ ] 没有对用户数据使用 eval()
- [ ] 错误消息不暴露敏感信息
</code></pre>
<h4 id="">更好的提示写作</h4>
<p>这里有一个编写可靠提示的公式：是什么 + 如何 + 返回类型。</p>
<pre><code>// ❌ 含糊： "make function"
// ✅ 清晰： "function to validate email format using regex, returns boolean"
</code></pre>
<h4 id="copilot">使用 Copilot 指令进行仓库级自定义</h4>
<p>GitHub Copilot 现在支持通过 <code>.github/copilot-instructions.md</code> 文件进行仓库级别的自定义。此功能帮助 Copilot 理解您项目的特定模式和约定。</p>
<p>以下是设置 Copilot 指令的方法：</p>
<pre><code># 如果不存在，创建 GitHub 目录
mkdir -p .github
touch .github/copilot-instructions.md
</code></pre>
<p>示例 <a href="http://copilot-instructions.md/">copilot-instructions.md</a> 文件：</p>
<pre><code># Copilot 指令

## 代码风格

- 使用具有 hooks 的 React 函数组件
- 为新文件更偏好使用 TypeScript 而不是 JavaScript
- 使用 Tailwind CSS 进行样式设计
- 遵循 `/src/components` 中的现有文件结构

## 测试

- 使用 React Testing Library 编写测试
- 将测试文件放在 `__tests__` 目录中
- 使用描述性测试名称来解释行为

## API 模式

- 使用自定义钩子进行 API 调用
- 一致地处理加载和错误状态
- 使用 React Query 进行数据提取

## 命名约定

- 组件：PascalCase（例如，`UserProfile.tsx`）
- 钩子：以“use”开头的 camelCase（例如，`useUserData.ts`）
- 工具：camelCase（例如，`formatDate.ts`）
</code></pre>
<p><strong>这项功能实现了：</strong></p>
<ul>
<li>
<p>Copilot 建议符合您项目模式的代码</p>
</li>
<li>
<p>自动遵循您的命名约定</p>
</li>
<li>
<p>建议合适的测试方法</p>
</li>
<li>
<p>理解您偏好的库和框架</p>
</li>
</ul>
<p><strong>最佳实践：</strong></p>
<ul>
<li>
<p>保持指令清晰和具体</p>
</li>
<li>
<p>随着项目标准的发展及时更新</p>
</li>
<li>
<p>包括首选模式的示例</p>
</li>
<li>
<p>提到您使用的库和框架</p>
</li>
</ul>
<h3 id="8copilot">第 8 步：解锁高级 Copilot 功能</h3>
<h4 id="">了解您的选择</h4>
<p>GitHub Copilot 提供多种方式来获得 AI 帮助：</p>
<ol>
<li>
<p><strong>Tab 补全</strong>（您一直在使用的）——在输入时提供建议</p>
</li>
<li>
<p><strong>聊天模式</strong>——与 AI 就您的代码进行对话</p>
</li>
<li>
<p><strong>编辑模式</strong>——请求 AI 对现有代码进行修改</p>
</li>
<li>
<p><strong>代理模式</strong>——让 AI 自主完成大型任务</p>
</li>
</ol>
<h4 id="">模型选择</h4>
<p>Copilot 现在提供不同的 AI 模型以满足不同需求：</p>
<p>订阅免费：</p>
<ul>
<li>
<p><strong>GPT-4.1</strong> – 默认模型，综合性能强大</p>
</li>
<li>
<p><strong>GPT-4</strong> – 对大多数编码任务来说可靠</p>
</li>
</ul>
<p>高级模型（有限的每月使用量）：</p>
<ul>
<li>
<p><strong>Claude 3.5 Sonnet</strong> – 适合复杂逻辑</p>
</li>
<li>
<p><strong>GPT-5</strong> – 最新且最强大</p>
</li>
<li>
<p><strong>Gemini 2.0 Flash</strong> – 响应非常快速</p>
</li>
</ul>
<p><strong>如何切换模型：</strong> 点击聊天视图中的模型下拉菜单</p>
<p><strong>提示：</strong> 学习时从免费模型（GPT-4.1）开始，对于复杂问题保留高级模型使用。</p>
<h4 id="githubcopilot">GitHub Copilot 的局限性</h4>
<p>以下是在使用 AI 来帮助您进行编码时需要考虑的一些重要事项：</p>
<ul>
<li>
<p><strong>互联网依赖性</strong> – 需要稳定的连接以获得建议</p>
</li>
<li>
<p><strong>上下文限制</strong> – 只能看到打开的文件，而不是整个项目结构</p>
</li>
<li>
<p><strong>免费账号限制</strong> – 每月 2,000 次补全和 50 次聊天请求</p>
</li>
<li>
<p><strong>代码质量参差不齐</strong> – 始终审查建议，特别是涉及安全性敏感的代码时</p>
</li>
<li>
<p><strong>学习曲线</strong> – 编写有效的提示需要时间，特别是对于复杂任务</p>
</li>
<li>
<p><strong>隐私考虑</strong> – 您的代码会被发送到 GitHub 的服务器（请检查您的组织的政策）</p>
</li>
</ul>
<h4 id="">基础聊天与建议</h4>
<p>您可能想知道 - 什么时候应该使用 Tab 补全，什么时候应该使用聊天？最好是用 Tab 补全来编写新函数、快速的语法帮助和模式补全。可以使用聊天来解释现有代码、获取错误帮助和规划解决问题的方法。</p>
<p><strong>尝试一下：</strong> 打开聊天（Ctrl+Shift+I）并询问：“这个函数有什么作用？”同时选择代码。</p>
<h3 id="9">第9步：掌握聊天和代理模式</h3>
<h4 id="">三种聊天模式</h4>
<ol>
<li><strong>询问模式（默认）</strong> – 用于问题和解释：</li>
</ol>
<pre><code>“这个函数有什么作用？”
“如何优化这段代码？”
“解释此错误消息”
</code></pre>
<ol start="2">
<li><strong>编辑模式</strong> – 用于对现有代码进行更改：</li>
</ol>
<pre><code>“重构这段代码以使用 async/await”
“为所有 API 调用添加错误处理”
“将其转换为 TypeScript”
</code></pre>
<ul>
<li>
<p>显示内联差异后应用更改</p>
</li>
<li>
<p>可跨多个文件工作</p>
</li>
<li>
<p>适合系统化重构</p>
</li>
</ul>
<ol start="3">
<li><strong>代理模式</strong> – 用于自主开发：</li>
</ol>
<pre><code>“创建一个带身份验证的 REST API”
“使用React和测试构建一个待办应用”
“将此代码库从Vue 2迁移到Vue 3”
</code></pre>
<ul>
<li>
<p>按 <code>Cmd+Shift+I</code> （Mac）或 <code>Ctrl+Shift+Alt+I</code>（Linux）或 <code>Ctrl+Shift+I</code>（Windows）</p>
</li>
<li>
<p>可独立工作数小时</p>
</li>
<li>
<p>自动安装包、创建文件、运行测试</p>
</li>
</ul>
<h4 id="">何时使用各模式</h4>
<p>每种模式都有特定的使用场景。当您学习新概念时，希望理解现有代码、获取解释和去计划方法时，使用询问模式。</p>
<p>当您正在重构现有代码、应用一致的更改、为现有功能添加特性或进行样式/模式更新时，使用编辑模式。</p>
<p>代理模式在构建完整特性（30分钟以上的工作）、设置新项目、大规模重构时有用，并且在您希望在 AI 编码时处理其他事情时使用。</p>
<h4 id="">代理模式示例</h4>
<p>小型代理任务（15分钟）：</p>
<pre><code>“为我的 Express 应用添加用户身份验证”
</code></pre>
<p>代理生成的内容：</p>
<pre><code>// middleware/auth.js
const jwt = require('jsonwebtoken');

const authenticateToken = (req, res, next) =&gt; {
  const authHeader = req.headers['authorization'];
  const token = authHeader &amp;&amp; authHeader.split(' ')[1];

  if (!token) return res.sendStatus(401);

  jwt.verify(token, process.env.ACCESS_TOKEN_SECRET, (err, user) =&gt; {
    if (err) return res.sendStatus(403);
    req.user = user;
    next();
  });
};

// routes/auth.js
router.post('/login', async (req, res) =&gt; {
  // 使用 bcrypt 进行身份验证的逻辑
  const accessToken = jwt.sign({username: user.username}, process.env.ACCESS_TOKEN_SECRET);
  res.json({accessToken: accessToken});
});
</code></pre>
<p><strong>我发现的关键问题：</strong> 代理最初忘记对密码进行哈希处理，并且没有包括刷新令牌。这需要一次迭代来修复安全漏洞并添加适当的错误处理。</p>
<p>大型代理任务（4小时以上）：</p>
<pre><code>“将此基于类的 React 应用更新为使用 TypeScript 的 hooks”
</code></pre>
<p>代理生成的内容：</p>
<pre><code>// 之前（类组件）
class UserProfile extends React.Component {
  constructor(props) {
    this.state = { user: null, loading: true };
  }
  // ... 生命周期方法
}

// 之后（Hooks + TypeScript）
interface User {
  id: number;
  name: string;
  email: string;
}

const UserProfile: React.FC = () =&gt; {
  const [user, setUser] = useState&lt;User | null&gt;(null);
  const [loading, setLoading] = useState(true);

  useEffect(() =&gt; {
    fetchUser().then(setUser).finally(() =&gt; setLoading(false));
  }, []);

  return &lt;div&gt;{loading ? 'Loading...' : user?.name}&lt;/div&gt;;
};
</code></pre>
<p>**我发现的关键问题：**代理成功更新了47个文件，但最初在事件处理程序中存在类型问题，并且需要改进泛型类型。自动化测试还需要人工检查以确保适当的 TypeScript 覆盖。</p>
<p>Chat 参与者是专门的 AI 助手，可以访问您的开发环境中的特定部分。可以将它们视为不同领域的专家，能够帮助完成有针对性的任务。</p>
<p>它们基本上是以 <code>@</code> 作为前缀的 AI 助手，具有特殊的知识和能力：</p>
<ul>
<li>
<p><strong>@workspace</strong> 可以访问您整个项目的结构，可以搜索文件并理解组件之间的关系。当您需要项目范围的分析时，请使用 <code>@workspace</code>："找出此项目中的所有 API 端点" 或 "显示用户身份验证是在哪里实现的。"</p>
</li>
<li>
<p><strong>@terminal</strong> 了解命令行操作，可以建议 shell 命令并解释终端输出。对于命令行帮助，请使用 <code>@terminal</code>："哪个命令可以运行测试？" 或 "如何为生产构建此项目？"</p>
</li>
<li>
<p><strong>@vscode</strong> 是 VS Code 功能方面的专家，可以帮助进行设置、调试和编辑器配置。对于编辑器帮助，请使用 <code>@vscode</code>："为 Node.js 设置调试" 或 "为此项目配置自动格式化。"</p>
</li>
</ul>
<p><strong>示例用法:</strong></p>
<pre><code>@workspace 你能找到这个项目中的所有数据库模型吗？
@terminal 安装依赖项并启动开发服务器的命令是什么？
@vscode 如何设置断点以调试这个 Express 应用？
</code></pre>
<h3 id="10">步骤 10：高级用户功能和高级工作流程</h3>
<p>除了您已经了解的 Copilot 核心功能外，还有一些专门的工具和命令可以极大地提高您的生产力。这些功能超越基本的聊天模式和模型选择，专注于复杂的多文件操作和高级自动化。</p>
<h4 id="">高级斜线命令</h4>
<pre><code>/doc - 生成文档
/explain - 详细的代码说明
/fix - 修复选定代码中的错误
/tests - 生成单元测试
/new - 创建新的项目结构
</code></pre>
<h4 id="">多文件操作</h4>
<p><strong>使用 # 引用：</strong></p>
<p><code>#</code> 符号创建特定的引用，告诉 Copilot 精确关注哪里。这些引用就像是指向项目不同部分的精准指针：</p>
<ul>
<li>
<p><strong>#file:filename</strong>: 引用特定文件：<code>#file:UserModel.js</code></p>
</li>
<li>
<p><strong>#codebase</strong>: 引用整个项目代码库以进行搜索</p>
</li>
<li>
<p><strong>#selection</strong>: 引用当前选定的代码</p>
</li>
<li>
<p><strong>#editor</strong>: 引用当前活动文件</p>
</li>
</ul>
<pre><code>"更新 #file:UserModel.js 以包含时间戳"
"在 #codebase 中搜索所有数据库查询"  
"将 #selection 重构为现代 JavaScript 语法"
"为所有 API 调用添加错误处理到 #editor"
</code></pre>
<p>这些引用帮助 Copilot 准确理解要查看和更改的位置，使多文件操作更加精确。</p>
<p><strong>拖放操作：</strong></p>
<p>拖放是提供给 Copilot 的最直观的上下文方式之一。您可以将文件从 VS Code 浏览器中直接拖入聊天窗口，Copilot 会立即了解其内容和结构。</p>
<p>当您处理相关组件并需要 AI 理解不同文件如何相互连接时，此功能特别有用。Copilot 会在整个对话中记住这些文件关系，因此在继续相同的讨论时无需再次上传文件。</p>
<p>这种上下文保留在多个聊天会话中有效，使您可以轻松在复杂的多文件项目中继续之前的工作。</p>
<h3 id="2">阶段 2 实践练习</h3>
<h4 id="1">练习 1：聊天模式实践</h4>
<ol>
<li>
<p>使用提问模式理解一个复杂的函数</p>
</li>
<li>
<p>切换到编辑模式进行重构</p>
</li>
<li>
<p>比较不同的方法</p>
</li>
</ol>
<h4 id="2">练习 2：代理模式项目</h4>
<ol>
<li>
<p>启动代理模式 (<code>Shift+Cmd+I</code>)</p>
</li>
<li>
<p>请求："创建一个带测试的简单待办应用"</p>
</li>
<li>
<p>观察自主开发过程</p>
</li>
<li>
<p>查看生成的代码</p>
</li>
</ol>
<h4 id="3">练习 3：高级功能</h4>
<ol>
<li>
<p>使用 @ 参与者进行项目问题</p>
</li>
<li>
<p>试验斜线命令</p>
</li>
<li>
<p>练习多文件操作</p>
</li>
</ol>
<h3 id="cli">准备好使用 CLI 工具了吗？</h3>
<p>您现在已经学习了在 VS Code 中使用 GitHub Copilot 的基础知识！像 Claude Code 和 Gemini 这样的 CLI 工具为基于终端的开发提供了更强大的功能。</p>
<p>如果你对终端 AI 感兴趣，可以继续学习下方的第三阶段。如果你更愿意继续使用 VS Code，请跳到第四阶段以获取高级工作流程。</p>
<h2 id="stage-3">阶段 3：基于 CLI 的 AI 代理（Claude Code &amp; Gemini）</h2>
<h3 id="11claudecodeai">步骤 11：认识 Claude Code——您的终端 AI 助手</h3>
<h4 id="claudecode">什么是 Claude Code？</h4>
<p>还记得 GitHub Copilot 如何在 VS Code 中帮助你吗？Claude Code 在终端中为你提供相同的帮助。</p>
<p>不用在 VS Code 中键入和获取建议，你可以在终端中键入并与 AI 对话。这就像在命令行上有一个编程伙伴。</p>
<h4 id="">简单例子：</h4>
<p>在 VS Code 中使用 Copilot：</p>
<pre><code>// 创建一个函数来验证电子邮件
[AI 提供建议代码]
</code></pre>
<p>在终端中使用 Claude Code：</p>
<pre><code>claude
&gt; 创造一个函数来验证电子邮件地址
[AI 为你编写代码]
</code></pre>
<p>那么，什么时候应该使用 VS Code/Copilot，什么时候应该使用 Claude Code 呢？</p>
<p><strong>如果你：</strong></p>
<ul>
<li>
<p>喜欢在终端工作</p>
</li>
<li>
<p>想要与 AI 进行关于代码的对话</p>
</li>
<li>
<p>需要有关命令行任务的帮助</p>
</li>
<li>
<p>想要更多控制 AI 互动</p>
</li>
</ul>
<p>Claude Code 非常适合你。</p>
<ul>
<li>
<p>偏好可视化编辑器</p>
</li>
<li>
<p>对当前的工作流程感到满意</p>
</li>
<li>
<p>不花太多时间在终端</p>
</li>
</ul>
<h4 id="">定价</h4>
<p>Claude Code 需要 Claude Pro（每月 20 美元）或 ClaudeMax（每月 100 美元）订阅，或使用 API 积分按使用量付费。</p>
<h4 id="claudecode">Claude Code 限制</h4>
<p>如果您计划使用 Claude Code，请考虑以下重要事项：</p>
<ul>
<li>
<p><strong>仅付费</strong> – 没有免费版本，需要 Claude Pro 订阅或 API 积分</p>
</li>
<li>
<p><strong>基于终端</strong> – 不如 IDE 集成工具可视化</p>
</li>
<li>
<p><strong>学习曲线</strong> – 需要熟悉命令行界面</p>
</li>
<li>
<p><strong>上下文管理</strong> – 需要手动管理对话上下文</p>
</li>
<li>
<p><strong>依赖互联网</strong> – 所有操作都需要稳定的连接</p>
</li>
<li>
<p><strong>会话限制</strong> – 长时间的自主会话会消耗大量 API 积分</p>
</li>
</ul>
<h4 id="">安装</h4>
<p>推荐（所有平台）：</p>
<pre><code>npm install -g @anthropic-ai/claude-code
</code></pre>
<p>其他安装方式：</p>
<ul>
<li>
<p><strong>macOS/Linux</strong>: <code>curl -fsSL https://claude.ai/install.sh | bash</code></p>
</li>
<li>
<p><strong>Windows</strong>: <code>irm https://claude.ai/install.ps1 | iex</code></p>
</li>
</ul>
<h4 id="">基本用法</h4>
<p><strong>交互模式（推荐）：</strong></p>
<p>交互模式是 Claude Code 的主要界面，您可以与 AI 实时对话。不同于一次性命令，交互模式创建了一个持久的会话，您可以提出后续问题、迭代解决方案，并随着时间的推移构建复杂的项目。</p>
<p>推荐使用交互模式，因为：</p>
<ul>
<li>
<p><strong>上下文持久性</strong>：Claude 会记住整个对话和项目上下文</p>
</li>
<li>
<p><strong>迭代开发</strong>：您可以优化请求，并基于先前的回应进行改进</p>
</li>
<li>
<p><strong>实时协作</strong>：在工作时提问、获取解释并修改方法</p>
</li>
<li>
<p><strong>会话恢复</strong>：使用 <code>claude --resume</code> 继续之前的对话</p>
</li>
</ul>
<p><strong>其他可用模式：</strong></p>
<ul>
<li>
<p><strong>一次性模式</strong>：单个命令执行（下文说明）</p>
</li>
<li>
<p><strong>代理模式</strong>：可独立工作数小时的自主开发会话</p>
</li>
</ul>
<ol>
<li>导航到您的项目：</li>
</ol>
<pre><code>cd your-project
claude
</code></pre>
<ol start="2">
<li>自然地开始对话：</li>
</ol>
<pre><code>Claude Code &gt; 分析这个代码库并提出改进建议

Claude Code &gt; 现在帮我重构用户认证

Claude Code &gt; 为支付模块添加单元测试
</code></pre>
<ol start="3">
<li>继续之前的会话：</li>
</ol>
<pre><code>claude --resume
</code></pre>
<p><strong>一次性命令（用于快速任务）：</strong></p>
<p>一次性命令是执行特定任务后即退出的单次执行命令。与交互模式不同，这些命令不保留对话上下文 —— 非常适合快速、独立的任务。</p>
<p><strong>什么是一次性命令？</strong></p>
<p>这些是在您的终端中直接运行带有特定指令的命令，而不进入交互会话。Claude 将执行请求并立即提供结果。</p>
<p><strong>何时使用一次性命令：</strong></p>
<ul>
<li>
<p>快速分析或代码审查</p>
</li>
<li>
<p>简单文件修改</p>
</li>
<li>
<p>自动化脚本和 CI/CD 集成</p>
</li>
<li>
<p>需要单一特定答案时</p>
</li>
</ul>
<p><strong>示例：</strong></p>
<pre><code>claude "分析这个代码库并提出改进建议"
claude "修复 src/ 中的所有 TypeScript 错误"
claude "为 utils.js 生成单元测试"
claude "解释这个函数的作用" --file src/auth.js
</code></pre>
<p>关键区别在于一次性命令在运行之间不记住上下文，而交互模式保持完整的对话历史记录和项目理解。</p>
<p><strong>交互式 vs 自主会话：</strong></p>
<p>在交互模式内，您可以选择协作和自主的方法：</p>
<p><strong>交互式会话（协作）：</strong></p>
<pre><code>Claude Code &gt; 我正在构建用户认证。我们应该采用什么方法？

你：使用 JWT 令牌和刷新令牌轮换

Claude Code &gt; 实现带刷新令牌的 JWT 认证
[逐步向您展示实现方案]

Claude Code &gt; 我还需要添加密码重置功能吗？

你：是的，使用基于邮件的重置
</code></pre>
<p><strong>自主会话（免动手开发）：</strong></p>
<pre><code>Claude Code &gt; 构建一个完整的用户管理系统，包括认证、个人资料、偏好设置和管理功能。使用安全和测试的最佳实践。

[Claude 自主工作数小时，定期提供更新]
[最终结果：完整的用户管理系统，准备投入生产]
</code></pre>
<p><strong>何时使用每种模式：</strong> 在学习时或希望对决策进行控制时使用交互式会话。对于定义明确的任务，在您信任 Claude 能够独立做出良好选择时使用自主会话。</p>
<h4 id="">关键特性</h4>
<p><strong>思维模式（在交互式会话中使用）：</strong></p>
<p>思维模式是特定的命令，可以告诉 Claude 在响应前要进行多深的分析。您可以根据问题的复杂程度手动选择这些模式。</p>
<p><strong>何时使用每种模式：</strong></p>
<ul>
<li>
<p><code>think</code> – 对简单任务进行快速分析："think: review this function for bugs"</p>
</li>
<li>
<p><code>think hard</code> – 对复杂逻辑进行深入推理："think hard: optimize this algorithm"</p>
</li>
<li>
<p><code>think harder</code> – 复杂问题解决，考虑多重因素："think harder: design a scalable database schema"</p>
</li>
<li>
<p><code>ultrathink</code> – 对架构决策进行最大深度分析："ultrathink: evaluate microservices vs monolith for this project"</p>
</li>
</ul>
<p>Claude展示其通过较长的思考模式进行推理的过程。您将在得出最终答案之前看到逐步分析。更高的思考模式花费更多时间，但会提供更全面的解决方案。</p>
<p><strong>选择正确的模式：</strong></p>
<p>使用<code>think</code>进行快速代码审查，使用<code>think hard</code>调试复杂问题，使用<code>think harder</code>处理系统设计问题，使用<code>ultrathink</code>进行影响整个项目的重大架构决策。</p>
<h4 id="claudemd">使用Claude.md进行项目级定制</h4>
<p>Claude Code最强大的功能之一是使用<code>.claude/CLAUDE.md</code>文件进行项目级定制。这让你可以为Claude提供有关特定项目、编码标准和偏好的上下文。</p>
<p>像这样设置CLAUDE.md：</p>
<pre><code># 创建项目级配置
mkdir -p .claude
touch .claude/CLAUDE.md
</code></pre>
<p>以下是一个CLAUDE.md文件的示例：</p>
<pre><code># 项目上下文

这是一个使用Express和PostgreSQL的Node.js REST API。

## 编码标准

- 使用async/await，绝不使用回调
- 所有数据库查询使用Prisma ORM
- 为所有新功能使用Jest编写测试
- 遵循RESTful约定

## 项目结构

- `/src/routes` - API端点
- `/src/models` - 数据库模型
- `/src/middleware` - Express中间件
- `/tests` - 单元和集成测试

## 偏好

- 为所有新文件使用TypeScript
- 更喜欢函数式编程模式
- 为所有函数包含JSDoc注释
</code></pre>
<p>这有助于Claude自动理解您的项目结构。还帮助Claude遵循您的特定编码标准，为您的技术栈建议合适的模式，并在多次会话中记住您的偏好。</p>
<p><strong>专业提示：</strong> 随着项目的发展更新你的CLAUDE.md，以保持Claude的建议相关和准确。</p>
<h4 id="claudecode">Claude Code自主开发会话</h4>
<p>这是一个多小时的自主开发会话示例：</p>
<pre><code>cd your-project
claude
</code></pre>
<p><strong>你的提示：</strong>“构建一个可扩展的具有实时订阅、身份验证、速率限制和全面测试的GraphQL API”</p>
<p>Claude的6小时自主过程：</p>
<pre><code>Claude Code &gt; ultrathink: 分析需求并设计架构

[创建详细的技术规格和开发计划]

Claude Code &gt; 实现核心GraphQL服务器与Apollo
[运行：npm install apollo-server-express graphql]
[创建：server.js, schema/typeDefs.js, resolvers/index.js]

Claude Code &gt; 添加身份验证和授权中间件
[创建：middleware/auth.js, utils/jwt.js, models/User.js]
[实现具有适当错误处理的JWT策略]

Claude Code &gt; 设置具有Redis的实时订阅
[运行：npm install graphql-redis-subscriptions]
[配置WebSocket连接，创建订阅解析器]

Claude Code &gt; 实施速率限制和安全措施
[添加helmet, express-rate-limit, 查询复杂度分析]

Claude Code &gt; 编写全面的测试套件
[创建单元测试、集成测试、负载测试]
[运行：npm test - 看到8个失败的测试]

Claude Code &gt; 自动修复测试失败
[识别异步时间问题，修复解析器逻辑]
[反复重新运行测试直到94/94个测试通过]

Claude Code &gt; 性能优化和缓存
[实现Redis缓存，优化数据库查询]
[添加性能监控和日志记录]

Claude Code &gt; 生成全面的文档
[创建带示例的API文档，README，部署指南]

Claude Code &gt; 最终验证和清理
[运行完整的测试套件，检查代码覆盖率：96%]
[创建生产构建和部署脚本]
</code></pre>
<p>这代表了6小时的自主工作（在此期间你可以处理其他项目）。结果是一个生产就绪的GraphQL API，具有身份验证、实时功能和全面的测试。</p>
<p>为什么这有效：</p>
<ul>
<li>
<p><strong>自主反馈循环：</strong> Claude运行测试，看到失败，自动修复</p>
</li>
<li>
<p><strong>上下文感知：</strong> 保持对整个项目结构的理解</p>
</li>
<li>
<p><strong>自我纠正：</strong> 反复迭代解决方案直至正确工作</p>
</li>
<li>
<p><strong>工具集成：</strong> 无缝使用git, npm, 测试框架</p>
</li>
</ul>
<p><strong>网络搜索集成：</strong></p>
<p>Claude Code可以搜索网络以获取当前信息，这对于AI训练数据有截断日期特别有用。该功能帮助您保持与最新文档、最佳实践和解决方案的同步。</p>
<pre><code>Claude Code &gt; 搜索最新的React 19功能并更新我的组件

[Claude搜索网络，然后以发现继续对话]

Claude Code &gt; 现在将这些新功能应用于UserProfile组件
</code></pre>
<p><strong>网络搜索何时有帮助：</strong></p>
<ul>
<li>
<p>获取新库版本的当前文档</p>
</li>
<li>
<p>找到最近错误消息或漏洞的解决方案</p>
</li>
<li>
<p>研究最新的最佳实践和模式</p>
</li>
<li>
<p>比较当前问题的解决方案</p>
</li>
</ul>
<p>当Claude检测到需要当前信息时，网络搜索会自动发生，或者你可以通过在提示中提到“搜索”或“最新”来显式请求。</p>
<h4 id="claudecode">Claude Code键盘快捷键</h4>
<p>你可以使用这些键盘快捷键提高工作效率：</p>
<ul>
<li>
<p><code>Ctrl+C</code> – 取消当前输入或生成</p>
</li>
<li>
<p><code>Ctrl+D</code> – 退出 Claude Code 会话</p>
</li>
<li>
<p><code>Ctrl+L</code> – 清除终端屏幕</p>
</li>
<li>
<p><code>Up/Down arrows</code> – 导航命令历史</p>
</li>
<li>
<p><code>Esc</code> + <code>Esc</code> – 编辑上一条消息</p>
</li>
</ul>
<p><strong>多行输入：</strong></p>
<ul>
<li>
<p><code>\</code> + <code>Enter</code> – 快速转义以创建新行（适用于所有终端）</p>
</li>
<li>
<p><code>Option+Enter</code> (Mac) / <code>Shift+Enter</code> (已配置) – 插入新行</p>
</li>
</ul>
<h3 id="12googlegemini">步骤12：Google Gemini 命令行界面</h3>
<h4 id="geminiclaudecode">何时使用 Gemini 与 Claude Code：</h4>
<p>Gemini 是另一种基于命令行界面的 AI 工具，它与 Claude Code 相辅相成，而不是竞争。尽管 Claude Code 擅长深度推理和复杂的开发任务，但 Gemini 提供了独特的优势：大规模上下文窗口（超过100万个标记）、慷慨的免费限制以及强大的多模态能力。</p>
<p><strong>使用 Gemini，当您：</strong></p>
<ul>
<li>
<p>需要一次分析整个大型代码库</p>
</li>
<li>
<p>希望处理图像、图表或草图</p>
</li>
<li>
<p>在预算限制内工作（慷慨的免费套餐）</p>
</li>
<li>
<p>需要极大的上下文窗口来处理复杂项目</p>
</li>
</ul>
<p><strong>使用 Claude Code，当您：</strong></p>
<ul>
<li>
<p>需要复杂的推理和问题解决</p>
</li>
<li>
<p>希望进行自主开发会话</p>
</li>
<li>
<p>偏爱用于复杂分析的高级思维模式</p>
</li>
<li>
<p>正在构建需要详细规划的生产系统</p>
</li>
</ul>
<p><strong>最佳方法：</strong> 许多开发人员策略性地使用这两种工具——Gemini用于分析和视觉输入，Claude Code用于复杂的开发任务。</p>
<p>Gemini 将 Google 的 AI 带到您的终端，并提供慷慨的免费限制。</p>
<h4 id="">安装</h4>
<p>使用 npx (推荐试用)：</p>
<pre><code>npx @google/gemini-cli
</code></pre>
<p>全局安装：</p>
<pre><code>npm install -g @google/gemini-cli
gemini  # 开始交互式会话
</code></pre>
<h4 id="">认证</h4>
<ol>
<li>使用 Google 登录：</li>
</ol>
<pre><code>gemini auth login
</code></pre>
<ol start="2">
<li>检查状态：</li>
</ol>
<pre><code>gemini auth status
</code></pre>
<p>免费限制：</p>
<ul>
<li>
<p>60 次请求/分钟</p>
</li>
<li>
<p>使用 Google 帐户每日 1,000 次请求</p>
</li>
</ul>
<p>内置工具：</p>
<ul>
<li>
<p><code>/memory</code> – 管理对话记忆</p>
</li>
<li>
<p><code>/stats</code> – 查看使用统计</p>
</li>
<li>
<p><code>/tools</code> – 列出可用工具</p>
</li>
<li>
<p><code>/mcp</code> – 配置模型上下文协议服务器</p>
</li>
</ul>
<h4 id="geminicli">Gemini CLI 的限制</h4>
<p>如果您计划使用 Gemini，请考虑以下重要事项：</p>
<ul>
<li>
<p><strong>速率限制</strong> – 免费套餐 60 次请求/分钟，1,000次/天</p>
</li>
<li>
<p><strong>Google 依赖性</strong> – 需要 Google 帐户和互联网连接</p>
</li>
<li>
<p><strong>较新的工具</strong> – 相较于 GitHub Copilot，社区较小，资源较少</p>
</li>
<li>
<p><strong>面向终端</strong> – 与流行的 IDE 整合较少</p>
</li>
<li>
<p><strong>多模态处理</strong> – 图像上传有大小限制 (20MB)</p>
</li>
<li>
<p><strong>测试版功能</strong> – 某些高级功能可能不稳定</p>
</li>
</ul>
<h4 id="gemini">独特的 Gemini 特性</h4>
<p><strong>庞大的上下文窗口：</strong><br>
Gemini 可以在单个会话中处理超过 100 万个标记，这意味着它可以同时分析整个大型代码库。这对于理解复杂系统架构和多个文件之间的关系特别有用。</p>
<p><strong>多模态能力：</strong><br>
Gemini 能够处理和理解各种类型的视觉内容以及代码，使其在设计到代码的工作流程和视觉调试中具有独特的优势。</p>
<h4 id="">将您的草图转化为代码</h4>
<p>这真的很酷：您可以在纸上绘制一些东西，然后 Gemini 将其转化为可工作的代码！</p>
<p>操作方法如下：</p>
<ol>
<li>
<p><strong>创建您的草图：</strong> 在纸上、白板或数位板上绘制您的想法</p>
</li>
<li>
<p><strong>拍照或截图：</strong> 使用手机拍摄或截图，将草图数字化</p>
</li>
<li>
<p><strong>保存图像：</strong> 保存为 JPG、PNG 或 WebP 格式（小于 20MB）</p>
</li>
<li>
<p><strong>通过命令行将其展示给 Gemini：</strong></p>
</li>
</ol>
<pre><code>gemini -p "将此草图转化为具有良好样式的 React 组件" sketch.jpg
</code></pre>
<p><strong>替代方法：</strong></p>
<pre><code># 如果您处于交互会话中，您可以引用文件：
gemini
&gt; 分析此UI草图并创建HTML/CSS：@sketch.jpg

# 或在支持的终端中拖放
gemini
&gt; 将此设计实现为 Vue 组件
[将 sketch.jpg 拖入终端]
</code></pre>
<p>然后，Gemini 会查看您的图纸并生成：</p>
<ul>
<li>
<p>与您的草图匹配的工作 React 组件</p>
</li>
<li>
<p>使其看起来不错的精美 CSS 样式</p>
</li>
<li>
<p>如果您绘制了表单则提供表单验证</p>
</li>
<li>
<p>使其工作的所有代码</p>
</li>
</ul>
<p>这就像拥有一个能读懂您心思的设计师和开发人员！</p>
<h4 id="gemini">通过展示图像来修复 Gemini 中的错误</h4>
<p>UI 出现了 Bug？您可以向 Gemini 展示视觉信息以帮助调试：</p>
<pre><code>gemini -p "该 UI 看起来有问题。有什么问题，我该如何解决？" image.png
</code></pre>
<p>Gemini 可以分析视觉信息并告诉您：</p>
<ul>
<li>
<p>问题的原因是什么</p>
</li>
<li>
<p>需要修改哪些代码</p>
</li>
<li>
<p>有时还有更好的解决方法</p>
</li>
</ul>
<h4 id="">将架构图转化为代码</h4>
<p>画出系统架构图，Gemini 可以构建它：</p>
<pre><code>gemini -p "使用 Docker 和数据库构建此系统架构" diagram.jpg
</code></pre>
<p>Gemini 将：</p>
<ul>
<li>
<p>理解您的图示</p>
</li>
<li>
<p>创建您所需的所有 Docker 文件</p>
</li>
<li>
<p>设置数据库和连接</p>
</li>
<li>
<p>根据您的设计提供一个可工作的系统</p>
</li>
</ul>
<p>将设计翻译成代码不再需要耗费数小时，您可以：</p>
<ol>
<li>
<p>向 Gemini 展示您的草图或设计</p>
</li>
<li>
<p>让 Gemini 构建它</p>
</li>
<li>
<p>在几分钟内获得工作代码，而不是几个小时，只需根据需要进行细化</p>
</li>
</ol>
<p>大多数情况下，Gemini 初次尝试就能很接近您的期望。即使它并不完美，它也为您提供了一个很好的起点，节省了大量时间。</p>
<h3 id="13cli">第13步：比较 CLI 工具</h3>
<p>这里有一个简易表格来帮助您比较 Claude Code 和 Gemini CLI 的功能：</p>
<table>
<thead>
<tr>
<th><strong>功能</strong></th>
<th><strong>Claude Code</strong></th>
<th><strong>Gemini CLI</strong></th>
</tr>
</thead>
<tbody>
<tr>
<td><strong>上下文窗口</strong></td>
<td>大</td>
<td>1M+ 令牌</td>
</tr>
<tr>
<td><strong>网络搜索</strong></td>
<td>内置</td>
<td>谷歌搜索集成</td>
</tr>
<tr>
<td><strong>文件编辑</strong></td>
<td>直接编辑</td>
<td>基于差异</td>
</tr>
<tr>
<td><strong>思维模式</strong></td>
<td>4 个等级</td>
<td>ReAct 循环</td>
</tr>
<tr>
<td><strong>IDE 集成</strong></td>
<td>VS Code 快捷键</td>
<td>终端优先</td>
</tr>
<tr>
<td><strong>免费层</strong></td>
<td>有限</td>
<td>慷慨 (1000/天)</td>
</tr>
<tr>
<td><strong>开源</strong></td>
<td>否</td>
<td>是</td>
</tr>
<tr>
<td><strong>多模态</strong></td>
<td>否</td>
<td>是 (图像, PDF)</td>
</tr>
</tbody>
</table>
<h3 id="14cli">第14步：高级 CLI 工作流程</h3>
<h4 id="1claudecode">工作流程1：Claude Code 的互动代码审查</h4>
<pre><code>Claude Code &gt; review my recent git changes

[Claude 分析差异]

Claude Code &gt; fix the security issue you found in the login function

Claude Code &gt; now create a pull request with a good description
</code></pre>
<h4 id="2gemini">工作流程2：Gemini 的对话式架构分析</h4>
<pre><code>Gemini &gt; analyze this codebase architecture and identify technical debt

[Gemini 提供全面的分析]

Gemini &gt; create a migration plan for the database issues you found

Gemini &gt; generate API documentation for the endpoints
</code></pre>
<h4 id="3">工作流程3：互动测试驱动开发</h4>
<pre><code>Claude Code &gt; I need to add payment processing. Start by writing comprehensive tests

[Claude 创建测试套件]

Claude Code &gt; now implement the payment service to pass these tests

Claude Code &gt; add error handling and edge cases
</code></pre>
<h3 id="vscodecli">结合 VS Code 和 CLI 工具</h3>
<h4 id="">混合工作流程的力量：</h4>
<p>最具生产力的开发者通常不会只选择一个 AI 工具——他们策略性地将 VS Code 扩展与 CLI 工具结合起来，以最大化效率。每个工具都有独特的优势，结合它们会创造出一个大于其各部分总和的工作流程。</p>
<p><strong>结合工具的好处：</strong></p>
<ul>
<li>
<p><strong>无缝的上下文切换：</strong> 从 Copilot 开始快速开发，然后无缝移动到 Claude Code 进行复杂的分析，而不失去动力</p>
</li>
<li>
<p><strong>互补的优势：</strong> 利用每个工具的最佳功能，如 Copilot 的实时建议 + Claude 的深入推理 + Gemini 的视觉处理</p>
</li>
<li>
<p><strong>持续的工作流：</strong> 无需在工具之间复制/粘贴代码—在项目中直接工作，并根据需要获得不同的 AI 支持</p>
</li>
<li>
<p><strong>减少心理负担：</strong> 工具处理不同的认知任务，让您专注于创造性的问题解决</p>
</li>
</ul>
<h4 id="">如何实际组合工具：</h4>
<p>示例工作流程——构建用户仪表板：</p>
<ol>
<li>
<p><strong>在 VS Code 中使用 Copilot 开始：</strong> 使用制表符补全快速构建基本组件结构</p>
</li>
<li>
<p><strong>保持 VS Code 打开，启动 Claude Code：</strong> 获取架构建议和重构建议，同时保持编辑器上下文</p>
</li>
<li>
<p><strong>切换到 Gemini 以获取视觉元素：</strong> 上传 UI 模型以生成匹配的样式</p>
</li>
<li>
<p><strong>返回到 VS Code：</strong> 应用所有建议，同时有 Copilot 协助实现细节</p>
</li>
</ol>
<p><strong>关键集成点：</strong></p>
<ul>
<li>
<p><strong>共享项目上下文：</strong> 所有工具在同一个目录中工作，了解您的项目结构</p>
</li>
<li>
<p><strong>文件系统协调：</strong> CLI 工具进行的更改会立即在 VS Code 中可见</p>
</li>
<li>
<p><strong>版本控制集成：</strong> 使用 CLI 工具进行 git 操作，而 VS Code 显示视觉差异</p>
</li>
</ul>
<h3 id="">快速切换设置</h3>
<h4 id="">什么是快速切换？</h4>
<p>快速切换设置指的是配置你的开发环境，让你可以快速在不同的 AI 工具之间切换，而不产生摩擦。通过创建快捷方式，而不是输入长命令或通过多个设置步骤进行导航，您可以立即访问当前任务所需的 AI 工具。</p>
<p>在您的 shell 配置文件中添加（<code>.zshrc</code> 或 <code>.bashrc</code>）：</p>
<pre><code># 快速 AI 命令用于交互模式
alias cc="claude"
alias gc="gemini"

# 在需要时进行快速单次命令
alias think="claude 'think hard:'"
alias analyze="gemini -p 'analyze:'"
</code></pre>
<h3 id="3">阶段3实践练习</h3>
<h4 id="1claudecode">练习1：互动 Claude Code 项目设置</h4>
<ol>
<li>
<p>创建一个新项目目录</p>
</li>
<li>
<p>启动：<code>claude</code></p>
</li>
<li>
<p>开始对话："set up a Node.js Express API with PostgreSQL"</p>
</li>
<li>
<p>继续聊天："add authentication middleware"</p>
</li>
<li>
<p>继续："now add comprehensive error handling"</p>
</li>
<li>
<p>查看生成的代码并提问</p>
</li>
</ol>
<h4 id="2gemini">练习2：互动 Gemini 代码分析</h4>
<ol>
<li>
<p>导航到现有项目</p>
</li>
<li>
<p>启动：<code>gemini</code></p>
</li>
<li>
<p>从以下内容开始："analyze this codebase and identify potential security vulnerabilities"</p>
</li>
<li>
<p>后续："explain the most critical issue in detail"</p>
</li>
<li>
<p>继续："create a fix for the authentication vulnerability"</p>
</li>
<li>
<p>询问："what other improvements should I prioritize?"</p>
</li>
<li>
<p>在 VS Code 中使用 Copilot 开始初始开发</p>
</li>
<li>
<p>切换到交互式 Claude Code 会话进行复杂的代码重构</p>
</li>
<li>
<p>使用交互式 Gemini 会话进行代码库分析和文档撰写</p>
</li>
<li>
<p>熟练地在工具之间无缝切换</p>
</li>
</ol>
<p>需要 CLI 工具的帮助吗？请参阅<a href="http://localhost:3333/#troubleshooting-quick-reference">故障排除快速参考</a>以了解设置和常见问题。</p>
<h2 id="stage-4">阶段 4：掌握 - 工具与高级工作流程的结合</h2>
<h3 id="15">第 15 步：工具选择策略</h3>
<h4 id="">何时使用每种工具</h4>
<p>那么，在你的工作流程中，什么时候应该使用每个工具呢？</p>
<p>当速度至关重要时，你可以将 GitHub Copilot 作为一名在线的结对程序员使用。它可以帮助你快速编写新函数、在输入时获得实时建议，并即时掌握不熟悉的 API 或框架。它也方便于在不中断你的工作流程的情况下快速查找文档。</p>
<p>然后，你可以转而使用 Claude Code 来处理更大、更复杂的任务：复杂的多文件重构、起草全面的测试以及“大声思考”关于架构和权衡的问题。在这里，它还可以帮助完成 Git 任务，比如指导你进行操作和组装拉取请求。</p>
<p>最后，当需要端到端分析大型代码库或将视觉输入（如截图/图表）整合到工作流程中时，可以从终端调用 Gemini CLI。由于可以免费使用，所以适合大量运行的场景，并且它适用于需要可定制、脚本友好的设置的场合。</p>
<h3 id="16mcpai">第 16 步：理解 MCP —— 让 AI 工具协同工作</h3>
<h4 id="mcp">什么是 MCP？</h4>
<p>MCP（模型上下文协议）是一种让你的 AI 工具增强能力的简单方法。可以把它想象成给你的手机增添应用程序——每个 MCP 服务器都会为你的 AI 增加新的功能。</p>
<h4 id="mcp">为什么初学者需要关注 MCP？</h4>
<p>没有 MCP 会遇到的问题是：你的 AI 只能处理它所知道的和你告诉它的内容。它无法：</p>
<ul>
<li>
<p>搜索网络上的当前信息</p>
</li>
<li>
<p>自动测试你的网站</p>
</li>
<li>
<p>在会话之间记住你的项目细节</p>
</li>
<li>
<p>连接到你的数据库或 API</p>
</li>
</ul>
<p>但有了 MCP 服务器，你的 AI 就能突然：</p>
<ul>
<li>
<p><strong>获取当前信息</strong> —— 从 Google 搜索最新的文档和解决方案</p>
</li>
<li>
<p><strong>测试你的代码</strong> —— 自动检查你的网站是否正常工作</p>
</li>
<li>
<p><strong>记住你的项目</strong> —— 跟踪你的架构和决策</p>
</li>
<li>
<p><strong>连接到工具</strong> —— 使用 GitHub、数据库等</p>
</li>
</ul>
<p>所以，相比于手动重复劳动，你的 AI 可以自动处理这些任务。这意味着你将花更少的时间去谷歌搜索错误消息、手动测试代码，以及在每个会话中向 AI 解释你的项目，而是花更多时间实际构建东西。</p>
<h4 id="mcp">初学者简单的 MCP 示例</h4>
<p>以下是 MCP 能为你做的一些初学者友好的示例：</p>
<p><strong>示例 1：无需谷歌搜索的帮助</strong></p>
<pre><code>你：“这个 CSS 不工作。找出原因并修复它”

没有 MCP：你需要谷歌搜索错误，阅读文档，尝试解决方案
有了 MCP：AI 搜索当前的 CSS 文档，找出问题，并自动修复
</code></pre>
<p><strong>示例 2：自动测试你的网站</strong></p>
<pre><code>你：“检查我的联系表单是否正常工作”

没有 MCP：你需要手动填写表单，检查邮件，测试边缘案例
有了 MCP：AI 填写表单，确认邮件已发送，测试不同的输入
</code></pre>
<p><strong>示例 3：AI 记住你的项目</strong></p>
<pre><code>你：“为我的待办事项应用程序添加一项新功能”

没有 MCP：你需要解释数据库结构、API 路线、前端框架
有了 MCP：AI 已记住一切并直接构建功能
</code></pre>
<h4 id="mcp">准备尝试 MCP 吗？</h4>
<p>如果这看起来很复杂，不用担心！你可以从一个简单的 MCP 服务开始，在适应后再添加更多。</p>
<h4 id="mcp">初学者简单的 MCP 设置</h4>
<p>我们将从 VS Code 开始（因为这是最简单的选项）：</p>
<ol>
<li>
<p>打开 VS Code</p>
</li>
<li>
<p>转到扩展（Ctrl+Shift+X）</p>
</li>
<li>
<p>搜索“GitHub Copilot MCP”或类似的 MCP 扩展</p>
</li>
<li>
<p>点击“安装”</p>
</li>
</ol>
<p>这样就完成了！扩展会自动处理一切。</p>
<p>通过这样做，你可以为你的 AI 获取网络搜索功能、基本项目记忆力和简单的自动化功能。</p>
<p>要测试它，试着让你的 AI：“搜索最新的 React 最佳实践并给我一个示例”。 如果它能搜索并返回当前的信息，MCP 就正常工作了！</p>
<h4 id="mcp">想要更多的 MCP 功能吗？</h4>
<p>一旦你适应了基本的 MCP，可以探索下面更高级的设置：</p>
<ul>
<li>
<p>自定义 MCP 服务器安装</p>
</li>
<li>
<p>高级配置选项</p>
</li>
<li>
<p>构建自己的 MCP 集成</p>
</li>
</ul>
<p>目前，上述的 VS Code 扩展方法将为你提供充足的 AI 超能力以便入门！</p>
<p>**这就是 MCP 的精要！**从上面的简单 VS Code 扩展方法开始，你将迅速看到你的 AI 变得多么强大。</p>
<h4 id="">下一步</h4>
<ul>
<li>
<p>尝试基础的 VS Code MCP 扩展</p>
</li>
<li>
<p>使用简单的请求进行测试，比如“搜索 X 并实现它”</p>
</li>
<li>
<p>熟悉后，探索更多的第 4 阶段 MCP 服务器</p>
</li>
</ul>
<p>MCP 将你的 AI 从代码建议工具转变为真正的开发伙伴。最好的部分是？一旦你用一个工具设置好，它就会与所有工具协同工作！</p>
<p>如果 AI 提示无法搜索网络，可以尝试以下几种方法。</p>
<p>首先，检查 MCP 扩展是否确实安装在 VS Code 中。然后尝试重启 VS Code。最后，要确保你的提问方式是 AI 能理解的，比如：“搜索 X 并展示 Y”。</p>
<p>如果 VS Code 扩展无法安装，尝试检查你的网络连接或将 VS Code 更新到最新版本。你也可以尝试使用不同名称搜索“MCP”或“Model Context Protocol”扩展。</p>
<p>如果仍然遇到问题，我们将在下文中介绍高级故障排除方法。或者你也可以询问你的 AI：“帮助我排查 MCP 设置故障”。</p>
<h3 id="mcp">高级 MCP 设置与集成</h3>
<h4 id="mcp">手动 MCP 服务器安装</h4>
<p>对于希望完全控制其 MCP 设置的高级用户：</p>
<p><strong>步骤 1：安装 MCP 服务器</strong></p>
<p>大多数 MCP 服务器可以通过 npm 安装：</p>
<pre><code># 用于网络自动化和测试
npm install -g @modelcontextprotocol/server-puppeteer

# 用于无需 API 密钥的网络搜索
npm install -g @mcp-servers/duckduckgo

# 用于数据库访问
npm install -g @modelcontextprotocol/server-postgres
</code></pre>
<p>某些服务器（如 GitHub）则使用 Docker：</p>
<pre><code>docker pull ghcr.io/github/github-mcp-server
</code></pre>
<p><strong>步骤 2：配置你的工具</strong></p>
<p><strong>理解层次化配置：</strong></p>
<p>每个 AI 工具在多个位置检查 MCP 配置，更具体的设置优先级高于一般设置。这意味着你可以有全局默认值，但可以针对特定项目进行覆盖。这就像 CSS——更具体的规则覆盖一般规则。</p>
<p><strong>Claude Code 具有最灵活的设置：</strong></p>
<p>Claude Code 配置层次结构（按顺序检查）：</p>
<ol>
<li>
<p><strong>项目级别</strong>：<code>.claude/mcp.json</code>（最高优先级）</p>
</li>
<li>
<p><strong>本地设置</strong>：<code>.claude/settings.local.json</code></p>
</li>
<li>
<p><strong>全局配置</strong>：<code>~/.claude/mcp.json</code>（后备）</p>
</li>
</ol>
<p>其他工具：</p>
<ul>
<li>
<p><strong>VS Code</strong>：<code>.vscode/mcp.json</code>（仅项目级别）</p>
</li>
<li>
<p><strong>Cursor</strong>：<code>.cursor/mcp.json</code>（仅项目级别）</p>
</li>
<li>
<p><strong>Windsurf</strong>：使用 VS Code 的配置格式</p>
</li>
</ul>
<p>以下是一个示例配置（适用于任何工具，只需调整文件位置）：</p>
<pre><code>{
  "mcpServers": {
    "puppeteer": {
      "command": "npx",
      "args": ["@modelcontextprotocol/server-puppeteer"]
    },
    "duckduckgo": {
      "command": "npx",
      "args": ["@mcp-servers/duckduckgo"]
    },
    "github": {
      "command": "docker",
      "args": [
        "run",
        "-i",
        "--rm",
        "-e",
        "GITHUB_PERSONAL_ACCESS_TOKEN",
        "ghcr.io/github/github-mcp-server"
      ],
      "env": {
        "GITHUB_PERSONAL_ACCESS_TOKEN": "your_token_here"
      }
    }
  }
}
</code></pre>
<h4 id="mcp">生产环境 MCP 服务器</h4>
<p><strong>1. 颠覆性认知工具：</strong></p>
<p><strong>序列思维服务器：</strong><br>
该服务器通过将复杂问题分解为逻辑步骤来改变 AI 处理复杂问题的方式。当你要求实现一个大型功能时，AI 不会直接跳到代码实施，而是首先创建一个详细的计划，其中包含阶段、依赖关系和决策点。</p>
<p>这对于重构遗留系统或构建新功能来说是无价的，尤其是在操作顺序很重要的情况下。服务器在整个开发会话期间保持这种规划上下文，确保决策的一致性。</p>
<p><strong>记忆库服务器：</strong><br>
消除了每次会话都需要重新解释项目结构的烦恼。该服务器会创建有关你的架构选择、编码标准、团队偏好和项目目标的持久性记忆。当你几天后返回工作时，AI 可以立即知道你的数据库架构、API 模式，甚至为什么做出某些决策。它就像一个随着你的开发工作完美同步的项目文档系统。</p>
<p><strong>知识图谱服务器：</strong><br>
创建代码库关系的动态地图——不仅仅是文件依赖，还有功能、共享工具和架构模式之间的概念连接。当你修改一个组件时，AI 可以立即识别出可能需要更新的所有相关区域。这可以防止由于遗漏相关更改而导致的错误，并有助于重构期间的影响分析。</p>
<p><strong>2. 网页自动化与测试服务器：</strong></p>
<p><strong>Puppeteer Server：</strong><br>
提供无头浏览器的控制，用于综合测试工作流。AI 可以自动浏览你的 Web 应用程序，填写表单、点击按钮，并验证预期行为。</p>
<p>这在回归测试中特别强大——AI 可以重播用户工作流并在部署前捕获更改。此外，它还启用基于截图的测试和性能监控自动化。</p>
<p><strong>Playwright Server：</strong><br>
同时扩展 Chrome、Firefox 和 Safari 浏览器的自动化功能。该服务器对于跨浏览器兼容性测试至关重要，并允许 AI 在开发初期捕获特定浏览器的问题。</p>
<p>与手动测试不同，AI 可以在所有浏览器上并行运行相同的测试场景，生成关于功能和性能差异的比较报告。</p>
<p><strong>3. 开发集成服务器：</strong></p>
<p><strong>GitHub Server：</strong><br>
将终端转变为具有 AI 智能的完整 GitHub 界面。AI 可以自动创建分支、管理拉取请求、分析代码审查评论，甚至根据代码更改生成 PR 描述。它还可以根据内容分析分配标签，并通过理解问题与实际代码更改之间的关系来维护项目板。</p>
<p><strong>PostgreSQL 服务器：</strong><br>
支持直接的数据库分析和优化。人工智能可以检查查询性能、建议索引优化、分析数据模式，甚至生成迁移脚本。此服务器在调试生产问题时特别有价值，因为人工智能需要理解实际的数据分布和查询执行模式，而不仅仅是理论上的数据库设计。</p>
<p><strong>4. 辅助工具：</strong></p>
<p><strong>MCP Compass</strong><br>
帮助您为任何任务找到合适的 MCP 服务器。</p>
<p>这些服务器将您的人工智能从代码建议者转变为能够测试、搜索、记忆和自动化的真正开发合作伙伴！</p>
<h3 id="17">步骤 17：高级提示工程</h3>
<h4 id="">上下文提示</h4>
<p>提供示例：</p>
<pre><code>// 替代用语："创建一个验证函数"
// 使用："创建一个类似这样的验证函数但用于电子邮件：
// function validatePhone(phone) { return /^\d{10}$/.test(phone); }"
</code></pre>
<p>指定约束条件：</p>
<pre><code>claude "将此代码重构为使用函数式编程，不使用循环，使用 map/filter/reduce"
</code></pre>
<p>包括边缘案例：</p>
<pre><code>gemini -p "实现用户认证，处理以下情况：过期令牌、并发登录、速率限制"
</code></pre>
<h3 id="18ai">步骤 18：构建 AI 辅助开发流水线</h3>
<h4 id="">自动化代码审查流水线</h4>
<ol>
<li>使用 Copilot 进行预提交检查：</li>
</ol>
<pre><code>// .copilot-instructions
"审核所有更改以发现：安全问题、性能问题、代码风格"
</code></pre>
<ol start="2">
<li>使用 Claude 进行 PR 审查：</li>
</ol>
<pre><code>claude "审查此 PR：git diff main..feature-branch"
</code></pre>
<ol start="3">
<li>使用 Gemini 完成文档：</li>
</ol>
<pre><code>gemini -p "为这些更改生成更新日志并更新 README"
</code></pre>
<h4 id="ai">测试驱动的 AI 开发</h4>
<ol>
<li>编写测试规范：</li>
</ol>
<pre><code>claude "为支付处理系统编写全面的测试规范"
</code></pre>
<ol start="2">
<li>生成测试代码：</li>
</ol>
<pre><code>gemini -p "使用 Jest 实现这些测试规范"
</code></pre>
<ol start="3">
<li>
<p>使用 Copilot 实现：</p>
<ul>
<li>
<p>使用代理模式实现功能</p>
</li>
<li>
<p>测试指导实现过程</p>
</li>
</ul>
</li>
</ol>
<h3 id="19ai">步骤 19：创建您的个人 AI 工作流</h3>
<h4 id="">设置您的环境</h4>
<p>1. VS Code 设置（<code>settings.json</code>）：</p>
<pre><code>{
  "github.copilot.enable": {
    "*": true
  },
  "github.copilot.advanced": {
    "inlineCompletions.enable": true,
    "chat.enabled": true
  }
}
</code></pre>
<p>2. Claude 代码配置（<code>~/.claude/settings.json</code>）：</p>
<pre><code>{
  "cleanupPeriodDays": 7,
  "permissions": {
    "allow": [
      "Bash(fd:*)",
      "Bash(rg:*)",
      "Bash(ls:*)",
      "WebFetch(domain:github.com)",
      "WebFetch(domain:stackoverflow.com)"
    ],
    "deny": ["WebFetch(domain:medium.com)"]
  }
}
</code></pre>
<p>3. Gemini 设置（<code>~/.gemini/config.json</code>）：</p>
<pre><code>{
  "defaultModel": "gemini-2.5-pro",
  "contextWindow": "large",
  "safetyMode": "interactive"
}
</code></pre>
<h4 id="">自定义命令和别名</h4>
<p>为常见任务设置 Shell 别名：</p>
<pre><code># 启动互动会话
alias cc='claude'
alias gc='gemini'

# 快速一次性命令（在需要时）
alias aicommit='claude "创建一个带有描述性信息的 git 提交"'
alias aireview='claude "审查我的未提交更改"'
alias complexity='gemini -p "分析代码复杂性并建议简化"'
alias security='claude "更深入思考：检查安全漏洞"'
alias aidocs='gemini -p "生成全面的文档"'
</code></pre>
<h3 id="ai">最终项目：构建一个完整的应用程序与 AI</h3>
<h4 id="">项目要求</h4>
<p>构建一个任务管理 API，具备：</p>
<ul>
<li>
<p>用户认证</p>
</li>
<li>
<p>CRUD 操作</p>
</li>
<li>
<p>实时更新</p>
</li>
<li>
<p>测试套件</p>
</li>
<li>
<p>文档</p>
</li>
</ul>
<h4 id="">建议工作流程</h4>
<p>阶段 1：互动规划</p>
<pre><code># 启动 Claude 代码会话
claude

Claude Code &gt; 超级思考：设计一个可扩展的任务管理 API 架构

[Claude 提供详细分析]

Claude Code &gt; 现在将其分解为实施阶段

# 切换到 Gemini 进行规格说明
gemini

Gemini &gt; 为此任务管理 API 创建详细的技术规格

Gemini &gt; 包括数据库架构和 API 端点规格
</code></pre>
<p>阶段 2：互动实现</p>
<ol>
<li>
<p>使用 Copilot 代理模式进行初始设置</p>
</li>
<li>
<p>使用内联 Copilot 实现功能</p>
</li>
<li>
<p>切换到互动 Claude 代码会话处理复杂逻辑：</p>
</li>
</ol>
<pre><code>Claude Code &gt; 实施我们计划的用户认证系统

Claude Code &gt; 现在添加任务 CRUD 操作

Claude Code &gt; 用 WebSockets 集成实时更新
</code></pre>
<p>阶段 3：互动测试与文档</p>
<pre><code># Claude 代码会话用于测试
claude

Claude Code &gt; 为所有 API 端点编写全面测试

Claude Code &gt; 为身份验证流程添加集成测试

Claude Code &gt; 为高负载场景创建性能测试

# Gemini 生成文档
gemini

Gemini &gt; 生成包含示例的全面 API 文档

Gemini &gt; 创建开发者入职指南
</code></pre>
<p>阶段 4：互动优化</p>
<pre><code># Claude 代码进行性能优化
claude

Claude Code &gt; 分析并优化我们的数据库查询

Claude Code &gt; 为经常访问的数据实现缓存

Claude Code &gt; 添加监控和日志

# Gemini 进行最终审查
gemini

Gemini &gt; 审查整个代码库以进行改进
</code></pre>
<h3 id="">衡量你的进展</h3>
<h4 id="1">阶段 1 里程碑</h4>
<ul>
<li>
<p>熟悉标签补全</p>
</li>
<li>
<p>可以编写有效的提示语</p>
</li>
<li>
<p>理解 AI 的局限性</p>
</li>
</ul>
<h4 id="2">阶段 2 里程碑</h4>
<ul>
<li>
<p>有效使用多个模型</p>
</li>
<li>
<p>掌握聊天模式和代理</p>
</li>
<li>
<p>使用高级聊天功能</p>
</li>
</ul>
<h4 id="3">阶段 3 里程碑</h4>
<ul>
<li>
<p>熟练使用 CLI 工具</p>
</li>
<li>
<p>可以将 VS Code 和终端工作流结合起来</p>
</li>
<li>
<p>了解工具的优势</p>
</li>
</ul>
<h4 id="4">阶段 4 里程碑</h4>
<ul>
<li>
<p>创建自定义 AI 工作流</p>
</li>
<li>
<p>使用 AI 构建完整应用程序</p>
</li>
<li>
<p>可以教别人 AI 辅助开发</p>
</li>
</ul>
<h3 id="4">阶段 4 练习</h3>
<h4 id="1">练习 1：掌握工具选择</h4>
<ol>
<li>
<p>选择一个中等复杂的编码任务（例如，“构建一个 URL 缩短服务 API”）</p>
</li>
<li>
<p>计划每个阶段使用的工具（设计、编码、测试、部署）</p>
</li>
<li>
<p>按照你选择的工作流程执行</p>
</li>
<li>
<p>记录哪些工作顺利，哪些需要改进</p>
</li>
</ol>
<h4 id="2">练习 2：创建自定义工作流</h4>
<ol>
<li>
<p>识别工作中的重复性开发任务</p>
</li>
<li>
<p>设计一个使用多个工具的 AI 辅助工作流</p>
</li>
<li>
<p>测试并改进该工作流</p>
</li>
<li>
<p>为团队同事创建相关文档</p>
</li>
</ol>
<h4 id="3">练习 3：完整项目构建</h4>
<ol>
<li>
<p>使用 AI 辅助构建一个小型但完整的应用程序</p>
</li>
<li>
<p>战略性地使用至少两种不同 AI 工具</p>
</li>
<li>
<p>包括测试、文档和部署</p>
</li>
<li>
<p>反思比起传统开发的效率提升</p>
</li>
</ol>
<h3 id="">继续你的旅程</h3>
<h4 id="">持续更新</h4>
<ul>
<li>
<p>关注工具的更新说明</p>
</li>
<li>
<p>加入 AI 编码社区</p>
</li>
<li>
<p>试验新功能</p>
</li>
</ul>
<h4 id="">探索高级主题</h4>
<ul>
<li>
<p>自定义 MCP 服务器开发</p>
</li>
<li>
<p>AI 模型微调</p>
</li>
<li>
<p>企业部署策略</p>
</li>
<li>
<p>团队协作模式</p>
</li>
</ul>
<h4 id="">持续学习资源</h4>
<ul>
<li>
<p>每个工具的官方文档</p>
</li>
<li>
<p>社区论坛和 Discord 服务器</p>
</li>
<li>
<p>开源 AI 编码项目</p>
</li>
<li>
<p>会议演讲和教程</p>
</li>
</ul>
<h2 id="common-ai-issues">常见 AI 问题</h2>
<p>即便使用最好的 AI 工具，你仍会遇到挑战。一旦你了解模式，这些问题是正常且可管理的。以下是开发者面临的最常见问题以及实际可行的解决方案。</p>
<h3 id="ai">“我的 AI 建议很糟！”</h3>
<p><strong>问题：</strong> AI 给出的建议无关或错误</p>
<p><strong>解决方案：</strong></p>
<ul>
<li>
<p>写更清晰的注释</p>
</li>
<li>
<p>打开相关文件以提供上下文</p>
</li>
<li>
<p>从简单任务开始</p>
</li>
<li>
<p>确保你在正确的文件类型中</p>
</li>
</ul>
<p><strong>示例修正：</strong></p>
<pre><code>// 原先: "make function"
// 尝试: "create function to validate US phone number format (xxx) xxx-xxxx"
</code></pre>
<h3 id="ai">“AI 太慢”</h3>
<p><strong>问题：</strong> 等待建议时间过长</p>
<p><strong>解决方案：</strong></p>
<ul>
<li>
<p>检查你的网络连接</p>
</li>
<li>
<p>关闭不必要的程序</p>
</li>
<li>
<p>尝试较轻量级的 AI 工具</p>
</li>
<li>
<p>要有耐心 —— 复杂建议需要时间</p>
</li>
</ul>
<h3 id="ai">“我害怕过于依赖 AI”</h3>
<p><strong>问题：</strong> 担心丧失编码能力</p>
<p><strong>解决方案：</strong></p>
<ul>
<li>
<p>将 AI 作为学习工具，而非依赖的工具</p>
</li>
<li>
<p>在接受之前务必理解代码</p>
</li>
<li>
<p>定期练习不使用 AI 编码</p>
</li>
<li>
<p>专注于解决问题，而非语法</p>
</li>
</ul>
<h3 id="">“它建议使用过时代码”</h3>
<p><strong>问题：</strong> AI 建议的模式老旧或使用了废弃的方法</p>
<p><strong>解决方案：</strong></p>
<ul>
<li>
<p>在注释中指定版本</p>
</li>
<li>
<p>保持工具的更新</p>
</li>
<li>
<p>学会识别过时的模式</p>
</li>
</ul>
<p><strong>示例：</strong></p>
<pre><code>// create React functional component using hooks (not class component)
</code></pre>
<h3 id="">故障排除快速参考</h3>
<h4 id="">常见问题（所有工具）</h4>
<table>
<thead>
<tr>
<th><strong>问题</strong></th>
<th><strong>快捷修复</strong></th>
</tr>
</thead>
<tbody>
<tr>
<td>没有 AI 建议</td>
<td>检查网络连接，重启编辑器，验证登录</td>
</tr>
<tr>
<td>“需要付款”消息</td>
<td>检查免费层限制，验证账户状态</td>
</tr>
<tr>
<td>建议效果差</td>
<td>使用更清晰的注释，打开相关文件提供上下文</td>
</tr>
<tr>
<td>工具无法安装</td>
<td>更新编辑器，检查网络，尝试不同安装方法</td>
</tr>
</tbody>
</table>
<h4 id="githubcopilot">GitHub Copilot 问题</h4>
<table>
<thead>
<tr>
<th><strong>问题</strong></th>
<th><strong>解决方案</strong></th>
</tr>
</thead>
<tbody>
<tr>
<td>VS Code 无建议</td>
<td>检查右下角“GitHub Copilot”状态</td>
</tr>
<tr>
<td>免费层过期</td>
<td>查看 <a href="https://docs.github.com/en/copilot/how-tos/manage-your-account/getting-free-access-to-copilot-pro-as-a-student-teacher-or-maintainer">学生/维护者免费访问</a></td>
</tr>
<tr>
<td>代理模式不起作用</td>
<td>尝试 <code>Shift+Cmd+I</code>（Mac）或 <code>Ctrl+Shift+I</code>（Windows/Linux）</td>
</tr>
<tr>
<td>聊天无响应</td>
<td>尝试重启 VS Code，检查网络连接</td>
</tr>
</tbody>
</table>
<h4 id="claudecode">Claude Code 问题</h4>
<table>
<thead>
<tr>
<th><strong>问题</strong></th>
<th><strong>解决方案</strong></th>
</tr>
</thead>
<tbody>
<tr>
<td>“命令未找到”</td>
<td>重新安装：<code>npm uninstall -g @anthropic-ai/claude-code &amp;&amp; npm install -g @anthropic-ai/claude-code</code></td>
</tr>
<tr>
<td>认证失败</td>
<td>运行 <code>claude auth login</code>，检查剩余 API 信用额度</td>
</tr>
<tr>
<td>响应缓慢</td>
<td>网络检查：<code>ping api.anthropic.com</code>，尝试轻量模型：<code>--model claude-3-haiku</code></td>
</tr>
<tr>
<td>MCP 服务器不起作用</td>
<td>检查 <code>~/.claude/mcp.json</code> 语法，测试服务器：<code>npx @mcp/server-github --help</code></td>
</tr>
<tr>
<td>命令挂起/卡住</td>
<td>按 <code>Ctrl+C</code> 取消，重启终端，检查后台进程</td>
</tr>
</tbody>
</table>
<h4 id="geminicli">Gemini CLI 问题</h4>
<table>
<thead>
<tr>
<th><strong>问题</strong></th>
<th><strong>解决方案</strong></th>
</tr>
</thead>
<tbody>
<tr>
<td>需要认证</td>
<td>运行 <code>gemini auth login</code>，检查 Google 账户权限</td>
</tr>
<tr>
<td>超过速率限制</td>
<td>检查使用情况：<code>gemini /stats</code>，等待 1 分钟或升级计划</td>
</tr>
<tr>
<td>无法安装</td>
<td>尝试 <code>npx @google/gemini-cli</code>，检查 Node.js 16+</td>
</tr>
<tr>
<td>图片上传失败</td>
<td>检查格式（JPG/PNG/WebP），大小小于 20MB，验证文件路径</td>
</tr>
<tr>
<td>上下文窗口错误</td>
<td>将大请求分割成小部分，清除历史记录</td>
</tr>
</tbody>
</table>
<p>如果一切无效，请按顺序尝试以下方法：</p>
<ol>
<li>
<p>重启你的编辑器/终端</p>
</li>
<li>
<p>检查互联网连接</p>
</li>
<li>
<p>确认你已登录到正确的账户</p>
</li>
<li>
<p>更新工具至最新版本</p>
</li>
<li>
<p>尝试不同的工具（如果一个失败了，通常其他的会有效）</p>
</li>
<li>
<p>询问 AI 自己：“帮助我排查 troubleshoottool_tool_setup”</p>
</li>
</ol>
<h2 id="whats-next-after-completing-all-stages">完成所有阶段后有哪些后续步骤？</h2>
<p>一旦你掌握了基础知识，这里有一些简单的后续步骤：</p>
<h3 id="">与团队合作</h3>
<h4 id="ai">团队 AI 工作流基础</h4>
<p><strong>共享提示库：</strong></p>
<p>构建团队提示库可以改变你整个团队使用 AI 的方式。首先创建一个共享的仓库，让开发者记录适用于你特定领域和代码库的有效提示。</p>
<p>例如，如果你正在构建电子商务软件，为常见任务创建标准化提示，如“生成符合我们 REST 约定的产品目录 API 端点”或“使用我们的标准模式创建支付处理错误处理”。</p>
<p>记录成功的代理模式工作流供团队成员重复使用。某位开发者可能会发现 Claude Code 在给定有关模式演化实践的特定上下文时，特别适合数据库迁移。通过共享这些工作流，你可以避免每个团队成员独立地发现有效的方法。</p>
<p><strong>工具标准化：</strong></p>
<p>当每个人都使用兼容的 AI 工具时，团队生产力会倍增。根据团队的需要确定主要工具——例如，所有开发者都使用 GitHub Copilot，以确保一致的内联支持，再加上 Claude Code 以处理需要深入推理的复杂架构任务。制定清晰的指南，规定何时使用自主的代理模式与协作会话，以防止冲突并确保代码质量。</p>
<p>配置共享的 MCP 服务器设置，以便所有团队成员都能访问相同的增强型 AI 功能。这可能包括内部 API 的团队专用服务器、共享数据库访问或了解你的部署管道的定制工具。当每个人都拥有相同的 AI 功能时，协作就会变得顺畅无比。</p>
<p><strong>AI 生成的代码审查：</strong></p>
<p>将你的代码审查过程转变为与 AI 生成的代码有效协作。为在拉取请求中标记 AI 生成的部分建立惯例——这有助于审查者将注意力集中在适当的地方。审查者可以专注于架构决策、业务逻辑正确性和需要人工判断的集成模式，而不是挑剔 AI 通常处理得很好的语法。</p>
<p>对 AI 生成的代码实施严格的测试，因为自动化测试比手动审查更可靠地捕捉 AI 错误。为测试 AI 输出创建团队标准，包括 AI 可能遗漏的边缘案例和集成场景。这样你就能在享受 AI 带来的速度的同时，通过系统的验证保持质量。</p>
<p><strong>在提交信息中记录 AI 工具决策。</strong></p>
<h4 id="">简单的团队设置</h4>
<p>从小处着手，逐步建立：</p>
<ul>
<li>
<p>首先让每个人使用相同的 AI 工具</p>
</li>
<li>
<p>创建一个适合你项目的提示共享文档</p>
</li>
<li>
<p>找出团队何时应该使用代理模式与常规协助模式</p>
</li>
<li>
<p>为你最重要的团队工具设置 MCP 服务器</p>
</li>
</ul>
<h3 id="">对于更大的项目</h3>
<p>随着项目的发展，你可能会想要：</p>
<ul>
<li>
<p>为不同的任务尝试不同的 AI 模型（简单代码用快速的，复杂问题用强大的）</p>
</li>
<li>
<p>为你经常执行的任务创建快捷方式</p>
</li>
<li>
<p>将 AI 工具与现有开发工作流连接</p>
</li>
</ul>
<h3 id="">持续学习</h3>
<p>AI 编码工具每个月都在进步！通过以下方式保持最新：</p>
<ul>
<li>
<p>关注工具的发行说明（他们会发送更新邮件）</p>
</li>
<li>
<p>加入 AI 编码的 Discord 社区</p>
</li>
<li>
<p>尝试新功能，因为它们不断出现</p>
</li>
</ul>
<h2 id="conclusion">结语</h2>
<p>恭喜你！你现在已经拥有了开启 AI 辅助编码之旅的所有必需品。记住，每个专家都曾是初学者，而有 AI 作为你的编码伙伴，你可以比以往更快地学习和成长。</p>
<p><strong>记住：</strong></p>
<ul>
<li>
<p>AI 不会取代你的创造力——它会放大创造力</p>
</li>
<li>
<p>每个建议都是一次学习的机会</p>
</li>
<li>
<p>错误是旅程的一部分</p>
</li>
<li>
<p>社区在这里帮助你</p>
</li>
</ul>
<p>你不仅仅是在学习用 AI 编码——你是在了解软件开发的未来。几个月后，你会想知道自己过去是如何在没有它的情况下编码的。今天拥抱 AI 辅助的开发者将成为明日的领袖。</p>
<p>Happy coding! 🚀</p>
<!--kg-card-end: markdown--> ]]>
                </content:encoded>
            </item>
        
            <item>
                <title>
                    <![CDATA[ 为什么 Vibe Coding 不会摧毁软件工程 ]]>
                </title>
                <description>
                    <![CDATA[ AI 正在以史无前例的速度颠覆所有行业。 曾经由一两家公司主导或非常“以人为本”的技术和行业正面临威胁。 Google 正在被 AI 搜索抢占市场 [https://www.smoothseo.co/blog/misc/what-the-numbers-say-about-ais-growing-role-in-search/] ，卡车司机 [https://www.axios.com/2022/03/28/automation-long-haul-truckers-jobs] 可能很快就会成为历史，低技能文书工作每天都在流失 [https://news.sky.com/story/ai-risks-up-to-eight-million-uk-job-losses-with-low-skilled-worst-hit-report-warns-13102214] 。 这种颠覆会摧毁软件工程行业吗？我认为不会，我来告诉你为什么。 我们将讨论的内容  1. “Vibe Coding” 现象            2. AI 如何改变软件开发            3. 生产力悖论 ]]>
                </description>
                <link>https://www.freecodecamp.org/chinese/news/why-vibe-coding-wont-destroy-software-engineering/</link>
                <guid isPermaLink="false">686e358e032b52046653aa8d</guid>
                
                    <category>
                        <![CDATA[ AI ]]>
                    </category>
                
                <dc:creator>
                    <![CDATA[ YiWei ]]>
                </dc:creator>
                <pubDate>Wed, 09 Jul 2025 09:37:28 +0000</pubDate>
                <media:content url="https://chinese.freecodecamp.org/news/content/images/2025/07/----_20250709173551.png" medium="image" />
                <content:encoded>
                    <![CDATA[ <p data-test-label="translation-intro">
        <strong>原文：</strong> <a href="https://www.freecodecamp.org/news/why-vibe-coding-wont-destroy-software-engineering/" target="_blank" rel="noopener noreferrer" data-test-label="original-article-link">Why Vibe Coding Won't Destroy Software Engineering</a>
      </p><!--kg-card-begin: markdown--><p>AI 正在以史无前例的速度颠覆所有行业。</p>
<p>曾经由一两家公司主导或非常“以人为本”的技术和行业正面临威胁。</p>
<p><a href="https://www.smoothseo.co/blog/misc/what-the-numbers-say-about-ais-growing-role-in-search/">Google 正在被 AI 搜索抢占市场</a>，<a href="https://www.axios.com/2022/03/28/automation-long-haul-truckers-jobs">卡车司机</a>可能很快就会成为历史，低技能文书<a href="https://news.sky.com/story/ai-risks-up-to-eight-million-uk-job-losses-with-low-skilled-worst-hit-report-warns-13102214">工作每天都在流失</a>。</p>
<p>这种颠覆会摧毁软件工程行业吗？我认为不会，我来告诉你为什么。</p>
<h3 id="">我们将讨论的内容</h3>
<ol>
<li>
<p>“Vibe Coding” 现象</p>
</li>
<li>
<p>AI 如何改变软件开发</p>
</li>
<li>
<p>生产力悖论</p>
</li>
<li>
<p>为什么人类工程师仍然至关重要</p>
</li>
<li>
<p>AI 作为“能力倍增器”</p>
</li>
<li>
<p>AI 时代的关键技能</p>
</li>
<li>
<p>前进之路</p>
</li>
</ol>
<h2 id="vibecoding"><strong>“Vibe Coding” 现象</strong></h2>
<p>如果你关注技术讨论，你可能已经看到过 “Vibe Coding” 这个术语——通过试验和错误、直觉和 AI 生成的代码片段来构建软件，而不需要深入的技术知识。</p>
<p>现代 AI 助手，如 GitHub Copilot 和 ChatGPT，可以根据简单的描述生成完整的函数、修复 bug 和创建组件。“Vibe Coders” 声称人类程序员很快就会变得过时。</p>
<p>从我的角度来看，这些 AI 工具更像是技能倍增器，而不是替代品。</p>
<p>它们帮助有才华的开发者工作得更快，同时暴露了技能不足的程序员的知识缺口。那些缺乏技术基础的程序员会遇到他们无法解决的问题，但将 AI 助手与实用的专业知识相结合的工程师将能够非常高效地工作。</p>
<h2 id="ai"><strong>AI 如何改变软件开发</strong></h2>
<p>软件行业正在快速普及基于大语言模型的 AI 编程工具，这些工具分析代码仓库以预测和建议下一步行动。</p>
<p>这些工具通过以下方式改变了日常编程工作：</p>
<ul>
<li>
<p>根据你输入的内容建议完整的函数</p>
</li>
<li>
<p>根据普通语言描述创建 API 端点</p>
</li>
<li>
<p>消除了花在标准代码模式上的时间</p>
</li>
<li>
<p>自动化文档任务</p>
</li>
<li>
<p>快速处理重复逻辑</p>
</li>
</ul>
<p>这种向 “vibe coding” 的转变加快了功能交付速度。程序员现在可以在不掌握每个技术细节的情况下构建软件——他们描述需求，获取 AI 建议，然后不断调整直至代码正常运行。</p>
<p><strong>风险在于，开发者经常推送他们无法解释的代码</strong>。他们在构建过程中进展很快，但在系统崩溃或需要更改时却会遇到困难。</p>
<p>还有一种令人担忧的趋势是非程序员在销售完全通过 AI 构建的应用程序。最近，一位没有编程背景的人通过 AI 提示推出了一个付费服务，只是在几天后就面临了数据泄露，因为黑客利用了基本的安全漏洞。这很危险。它浪费了人们的钱，并暴露了他们的数据。想象一下，如果这种情况由于 “Vibe Coders” 的崛起而变得普遍？</p>
<p>对于任何考虑构建软件但不是软件工程师的人来说，有一些基本的安全级别需要考虑：</p>
<ul>
<li>
<p>向 API 端点添加身份验证：人们可以扫描整个互联网上的开放端口和端点。如果他们可以在没有身份验证的情况下调用你的 API 端点，那么这可能会导致各种问题</p>
</li>
<li>
<p>不要以明文存储密码。这是一个大禁忌。如果你这样做并且你的数据库被泄露，那么这些密码将被所有人看到。而且，现实中人们会重复使用密码，所以这些密码将是他们在其他网站上的密码。</p>
</li>
<li>
<p>SSL：确保你的网站安全并且具有最新的 SSL 证书。在明文中传输数据很危险。</p>
</li>
<li>
<p>锁定未使用的端口：如果你托管后端服务，请确保未使用的端口被锁定，人们无法连接到它们。</p>
</li>
<li>
<p>如果你有允许上传文件的区域，请限制上传的文件类型。</p>
</li>
</ul>
<p>以上仅是网站或产品安全防护的部分注意事项，实际需要考量的因素远不止于此。</p>
<h2 id=""><strong>生产力悖论</strong></h2>
<p>AI 助手显著提高了代码输出量——但数量并不等于软件工程中的价值。</p>
<p>这些工具在语法方面表现良好，但对系统架构、可扩展性问题和维护需求没有任何了解。就像打字速度不会创造出更好的小说一样，代码生成速度也不会产生更好的软件系统。</p>
<p>AI 适用于单个函数，但在架构决策、安全规划和长期支持需求方面却很难。没有适当的审查和理解，AI 生成的代码通常会成为明天的技术债务和维护负担。</p>
<p>想象一下这样的场景：开发者实现了一个 AI 创建的身份验证系统，它在隔离环境中有效，但在用户注册产品时会导致微妙的故障。找到并修复这些集成问题可能需要经验丰富的员工几天的时间——这抵消了最初节省的时间。这种做法很快就会导致资金损失和信任危机。</p>
<h2 id=""><strong>为什么人类工程师仍然至关重要</strong></h2>
<p>虽然 AI 工具在语法方面表现良好，但它们无法：</p>
<ol>
<li>
<p>规划随着用户需求而增长的系统</p>
</li>
<li>
<p>创建可靠的部署和测试管道</p>
</li>
<li>
<p>预测不寻常但至关重要的故障情况</p>
</li>
<li>
<p>在性能和成本之间做出明智的权衡</p>
</li>
<li>
<p>找到不明显的安全漏洞</p>
</li>
</ol>
<p>优秀的工程师不仅仅是编写代码。他们开发有助于整个团队的模式，选择合适的技术，并为成功和失败场景做计划。</p>
<p>软件创建涉及复杂的权衡：我们是否优先考虑速度或稳定性？灵活性或简单性？这些决策需要技术专业知识和商业知识。</p>
<p>我合作的最有价值的工程师花更多的时间思考而不是输入代码。他们考虑：需求如何演变？哪些压力点可能出现？系统如何从故障中恢复？</p>
<p>随着基础代码生成变得广泛可用，你的价值来自于理解系统交互。竞争优势将属于那些知道为什么某些方法会成功、它们可能在哪里失败以及如何构建弹性解决方案的人。</p>
<p>正如上所述，有一些事情 AI 无法做到。</p>
<p>AI 可以在简单的提示中创建解决方案来解决众所周知的问题。如果你使用此提示，例如：</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1747771377197/8aae1332-f852-435a-a724-a1770a4163bf.png" alt="Prompt: &quot;Create a Python module that will tell you if some input JSON is valid&quot;" width="1024" height="130" loading="lazy"></p>
<p>这里是你可能得到的代码：</p>
<pre><code class="language-plain">import json

def is_valid_json(input_string: str) -&gt; bool:
    """
    Checks if the input string is valid JSON.

    Args:
        input_string (str): The JSON string to validate.

    Returns:
        bool: True if valid, False otherwise.
    """
    try:
        json.loads(input_string)
        return True
    except json.JSONDecodeError:
        return False

def main():
    user_input = input("Enter a JSON string to validate: ")
    if is_valid_json(user_input):
        print("✅ The input is valid JSON.")
    else:
        print("❌ The input is NOT valid JSON.")

if __name__ == "__main__":
    main()
</code></pre>
<p>非常基本。</p>
<p>我不会要求 AI 为我构建一个复杂的外汇交易平台。</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1747771546630/d212d814-7785-4391-8e01-79e80c05da44.png" alt="d212d814-7785-4391-8e01-79e80c05da44" width="600" height="400" loading="lazy"></p>
<p>我就不赘述全部回复内容了，简单来说就是它无法完成这个操作。</p>
<p>我想将其分解为一个稍微较小的金融交易平台子集：</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1747771773603/b65e1a13-97aa-4276-81c7-55303e5a04c6.png" alt="Prompt: &quot;Create me an endpoint for a FIX message for an FX Financial security. Make it in Python and I want to be able to accept J messages. Security is key here, so I need this to be as secure as possible.&quot;" width="1326" height="224" loading="lazy"></p>
<p>现在，我会承认，这更接近了。这里是结果：</p>
<pre><code class="language-plain">from fastapi import FastAPI, Request, HTTPException, Header
from pydantic import BaseModel, Field
from typing import Optional
import hmac
import hashlib
import logging

app = FastAPI()

# === CONFIGURATION ===
SHARED_SECRET = b"super_secure_shared_secret"  # Keep this secure

# === LOGGING SETUP ===
logging.basicConfig(level=logging.INFO)
logger = logging.getLogger("ems.fix")

# === Pydantic Model for Execution Report (simplified) ===
class ExecutionReport(BaseModel):
    msg_type: str = Field(default="J", const=True)
    cl_ord_id: str  # Tag 11
    order_id: str   # Tag 37
    exec_id: str    # Tag 17
    exec_type: str  # Tag 150
    ord_status: str # Tag 39
    symbol: str     # Tag 55 (e.g., EUR/USD)
    side: str       # Tag 54 (1=Buy, 2=Sell)
    leaves_qty: float  # Tag 151
    cum_qty: float     # Tag 14
    avg_px: float      # Tag 6
    transact_time: str # Tag 60 (ISO8601 string)

# === Helper: HMAC Validator ===
def verify_signature(payload: bytes, signature: str) -&gt; bool:
    computed = hmac.new(SHARED_SECRET, payload, hashlib.sha256).hexdigest()
    return hmac.compare_digest(computed, signature)

# === Endpoint ===
@app.post("/fix/execution-report")
async def receive_execution_report(
    request: Request,
    x_signature: Optional[str] = Header(None)
):
    body = await request.body()

    if not x_signature or not verify_signature(body, x_signature):
        logger.warning("Invalid or missing signature.")
        raise HTTPException(status_code=403, detail="Unauthorized")

    try:
        report_data = ExecutionReport.parse_raw(body)
        logger.info(f"Execution Report received: {report_data.dict()}")
        # ✅ Here, you'd handle order updates, booking, etc.
        return {"status": "ok", "message": "Execution Report received"}
    except Exception as e:
        logger.exception("Invalid payload format.")
        raise HTTPException(status_code=400, detail=f"Invalid FIX message format: {str(e)}")
</code></pre>
<p>但这仍然无法接近最终版本。即使你可以使用此代码作为交易系统的一部分，但除非你知道如何构建和架构整个软件系统，否则你将从哪里开始？你可以告诉客户他们可以发送“ExecutionReport”，但接下来呢？</p>
<p>关于“只需更擅长提示工程就能构建更庞大系统”的说法，我早已耳闻目见。但真正的效益从何而来？金融交易系统的复杂度远超人类理解范畴。要让提示工程足够完善，以便构建一个功能齐全、可扩展、安全且可扩展的系统（更别说调试能力）本身就是一项巨大的任务。所谓节省时间从何体现？这真的可行吗</p>
<p>我还没有看到任何证据表明有人在没有人类监督的情况下构建了这样一个复杂的系统，我不相信我们会在近期看到。</p>
<h2 id="ai"><strong>AI 作为“能力倍增器”</strong></h2>
<p>这些 AI 工具帮助放大现有的能力，而不是取代它们。有才华的开发者变得更加高效，而技能较弱的开发者则更快地生成问题。</p>
<p>优秀工程师使用 AI 来：</p>
<ul>
<li>
<p>处理基本的实现任务</p>
</li>
<li>
<p>创建初始项目框架</p>
</li>
<li>
<p>比较不同的解决方案方法</p>
</li>
<li>
<p>突破具有挑战性的问题</p>
</li>
</ul>
<p>同时，能力较弱的开发者使用 AI 来掩盖技能差距，实现他们不理解也不知道如何修改的解决方案。当这些实现失败时，他们缺乏独立修复它们的知识。</p>
<p>这扩大了技能差距。顶级工程师利用 AI 处理机械任务，同时专注于更高价值的思考。那些将 AI 用作学习替代品的人会在超出 AI 知识边界时遇到限制。</p>
<p>一个很好的例子是 AI 完美适合的东西是翻译逻辑：</p>
<p>假设我有一个 Python Dataclass 表示一个“内部用户”。我还有一个 Django ORM 表示相同的实体。如果我想将一个转换为另一个，我可以简单地将两个表示形式粘贴到 ChatGPT 中，并让它为我创建一个转换函数。请注意，转换函数还考虑到字段名称不完全匹配：</p>
<pre><code class="language-plain">@dataclass
class InternalUser:
    id: str
    email: str
    hashed_password: str
    full_name: str
    role: UserRole
    status: AccountStatus
    created_at: datetime
    updated_at: datetime
    address: Optional[Address] = None
    preferences: Preferences = field(default_factory=Preferences)
    login_activity: LoginActivity = field(default_factory=LoginActivity)
    tags: List[str] = field(default_factory=list)
    notes: Optional[str] = None
</code></pre>
<pre><code class="language-plain">class InternalUser(models.Model):
    id = models.CharField(primary_key=True, max_length=100)  # Use UUIDField if preferred
    email = models.EmailField(unique=True)
    hashedPassword = models.CharField(max_length=256)
    fullName = models.CharField(max_length=255)
    role = models.CharField(max_length=20, choices=UserRole.choices, default=UserRole.USER)
    status = models.CharField(max_length=20, choices=AccountStatus.choices, default=AccountStatus.PENDING)
    createdAt = models.DateTimeField(default=timezone.now)
    updatedAt = models.DateTimeField(auto_now=True)

    address = models.OneToOneField(Address, null=True, blank=True, on_delete=models.SET_NULL)
    preferences = models.OneToOneField(Preferences, on_delete=models.CASCADE)
    loginActivity = models.OneToOneField(LoginActivity, on_delete=models.CASCADE)

    tags = models.JSONField(default=list, blank=True)  # List of strings
    notes = models.TextField(null=True, blank=True)

    def __str__(self):
        return f"{self.fullName} ({self.email})"
</code></pre>
<pre><code class="language-plain">from typing import Optional
from myapp.models import InternalUser as ORMInternalUser, Address as ORMAddress, Preferences as ORMPreferences, LoginActivity as ORMLoginActivity
from mydataclasses import InternalUser as DCInternalUser  # assuming your dataclass lives here


def convert_internal_user_to_model(dc_user: DCInternalUser) -&gt; ORMInternalUser:
    # Create or update Address if provided
    orm_address: Optional[ORMAddress] = None
    if dc_user.address:
        orm_address = ORMAddress.objects.create(
            street=dc_user.address.street,
            city=dc_user.address.city,
            state=dc_user.address.state,
            postalCode=dc_user.address.postal_code,
            country=dc_user.address.country
        )

    # Create Preferences
    orm_preferences = ORMPreferences.objects.create(
        newsletterOptIn=dc_user.preferences.newsletter_opt_in,
        darkModeEnabled=dc_user.preferences.dark_mode_enabled,
        language=dc_user.preferences.language
    )

    # Create LoginActivity
    orm_login_activity = ORMLoginActivity.objects.create(
        lastLogin=dc_user.login_activity.last_login,
        loginAttempts=dc_user.login_activity.login_attempts,
        lastIpAddress=dc_user.login_activity.last_ip_address
    )

    # Create InternalUser
    orm_user = ORMInternalUser.objects.create(
        id=dc_user.id,
        email=dc_user.email,
        hashedPassword=dc_user.hashed_password,
        fullName=dc_user.full_name,
        role=dc_user.role.value,
        status=dc_user.status.value,
        createdAt=dc_user.created_at,
        updatedAt=dc_user.updated_at,
        address=orm_address,
        preferences=orm_preferences,
        loginActivity=orm_login_activity,
        tags=dc_user.tags,
        notes=dc_user.notes
    )

    return orm_user
</code></pre>
<p>这是一个基本的例子，但如果你将其扩展到整个内部数据模型，那么你将节省数小时的代码。</p>
<p>你甚至可以要求它为你创建测试函数的存根：</p>
<pre><code class="language-plain">import pytest
from datetime import datetime
from yourmodule.dataclasses import InternalUser, Address, Preferences, LoginActivity, UserRole, AccountStatus
from yourmodule.conversion import dataclass_to_django_internal_user


@pytest.mark.django_db
def test_internal_user_conversion_basic_fields():
    # 测试基本字段（电子邮件、姓名等）是否正确复制
    pass


@pytest.mark.django_db
def test_internal_user_conversion_with_address():
    # 测试地址字段是否正确映射到 ORM 模型
    pass


@pytest.mark.django_db
def test_internal_user_conversion_with_preferences():
    # 测试偏好设置，如暗黑模式、新闻通讯订阅和语言
    pass


@pytest.mark.django_db
def test_internal_user_conversion_with_login_activity():
    # 测试登录尝试、最后一次登录和最后一次 IP 地址
    pass


@pytest.mark.django_db
def test_internal_user_conversion_with_tags_and_notes():
    # 测试标签列表和可选备注字段
    pass


@pytest.mark.django_db
def test_internal_user_conversion_with_missing_optional_fields():
    # 确保缺失的可选字段（如地址或最后一次登录）不会破坏转换
    pass


@pytest.mark.django_db
def test_internal_user_conversion_saves_correctly():
    # 保存所有相关模型和主 InternalUser 模型，并检查数据库
    pass
</code></pre>
<p>我并非建议你直接照搬这些测试用例而不加以思考，但这是一个很好的开始。</p>
<p>这些“苦差事”从来不是我们高薪聘请顶尖工程师的原因。它们只是完成项目的必要工序。人们不喜欢这些任务。它们无法带给人成就感。</p>
<h2 id="ai"><strong>AI 时代的关键技能</strong></h2>
<p>随着 AI 处理更多的编码任务，成功的工程师必须在人类判断仍然至关重要的领域发展自己的优势：</p>
<p>系统思维成为主要技能——了解组件交互，识别潜在故障，并为未来的增长设计。这项能力来自经验，而不是提示。</p>
<p>你应该在基础设施和部署流程方面建立专业知识。开发环境中有效但生产环境中失败的软件不会创造任何价值。因此，学习<a href="https://www.freecodecamp.org/news/learn-continuous-integration-delivery-and-deployment/">持续集成</a>、<a href="https://www.freecodecamp.org/news/how-to-set-up-monitoring-for-nodejs-applications-using-elastic/">监控</a>系统和<a href="https://www.freecodecamp.org/news/beginners-guide-to-cloud-computing-with-aws/">云平台功能</a>。</p>
<p>你还应该掌握[ API 设计][<a href="https://www.freecodecamp.org/chinese/news/rest-api-design-best-practices-build-a-rest-api/">https://www.freecodecamp.org/chinese/news/rest-api-design-best-practices-build-a-rest-api/</a>]——系统之间的接口。<a href="https://www.freecodecamp.org/news/design-an-api-application-program-interface/">良好设计的 API </a>使团队独立。糟糕的接口会造成瓶颈，影响每个人。</p>
<p>另一个关键技能是将安全性集成到整个开发过程中。单一疏忽可能会导致泄露，损害客户信任和商业声誉。</p>
<p>确保你发展与技术和非技术受众交流的能力。你将需要清晰地解释复杂的决策，跨越不同的利益相关者群体。</p>
<p>并研究 AI 工具的工作原理，以了解它们的局限性和优势，使你能够更有效地使用它们。</p>
<p>对于高级开发人员，指导变得越来越重要。新工程师需要获得“如何负责任地使用 AI” 这方面的指导——知道何时接受建议、何时质疑它们。</p>
<h2 id=""><strong>前进之路</strong></h2>
<p>软件领域正在经历一次重大的转变。AI 将生成更多代码，改变开发实践。这一转变带来了机遇和挑战。</p>
<p>最有价值的职位将属于擅长机器无法处理的任务的工程师。这些工程师将决定要构建什么、如何设计，以及如何平衡技术约束与商业目标。</p>
<p>“Vibe Coding”是一种有用的技术，适用于特定的需求——例如快速构建标准组件。但是，它并不能提供复杂系统开发的全面策略。</p>
<p>有才华的工程师将通过将常规工作委派给 AI，同时解决更具挑战性的问题来推进。技能较弱的工程师将在基本知识缺口变得明显时遇到困难。</p>
<p>关于如何有效运用 AI 技术，在采纳网络建议时务必保持审慎判断。这仍然是一个相对较新的领域，并且不断变化。</p>
<p>人们在线提供“免费提示”来生成代码。这些提示可能很好，也可能有问题。提示可能在他们使用时很好，但 AI 模型可能已经改变，也可能会产生不同的结果。要谨慎并使用你的最佳判断。</p>
<p>未来属于那些将 AI 视为协作工具而不是替代品的人。软件开发仍然是人类驱动的，现在得到了越来越强大的支持。</p>
<p>在他的空闲时间里，Ben 写他的技术博客 <a href="https://justanothertechlead.com/"><em>Just Another Tech Lead</em></a> 和运营一个关于 SEO 的网站 <a href="https://www.smoothseo.co"><em>SmoothSEO</em></a>。</p>
<!--kg-card-end: markdown--> ]]>
                </content:encoded>
            </item>
        
            <item>
                <title>
                    <![CDATA[ Python 中的聚类——机器学习工程手册 ]]>
                </title>
                <description>
                    <![CDATA[ 您是否想学习如何发现与分析数据中隐藏的模式呢？聚类，作为一项基本的无监督机器学习技术，能够助力您揭示极具价值的洞察，进而从根本上转变您对复杂数据集的认知。 在这本全面的手册中，我们将深入探讨一些必须掌握的聚类算法和技术，并辅以相关理论作为支撑。随后，您将通过大量实例、Python 实现方法和其可视化效果来知晓其工作原理。 无论您是刚入门的初学者，或者是富有经验的数据科学家，这本手册都是熟悉掌握聚类技术的宝贵资源。您也能够在此下载这本手册 [https://join.lunartech.ai/clustering-in-python]。 倘若您同样喜欢通过听来学习，这里还有个 15 分钟的播客，在其中我们更详细地讨论了聚类。在这一期里，我们探索了聚类的基本理念，从而帮助您更深入地了解如何将这些技术应用于现实世界的数据。 以下是我们将要涵盖的内容：  1.  无监督学习导论              2.  监督学习与无监督学习的区别              3.  核心术语解析      ]]>
                </description>
                <link>https://www.freecodecamp.org/chinese/news/clustering-in-python-a-machine-learning-handbook/</link>
                <guid isPermaLink="false">67ced2e93d55950475baa158</guid>
                
                    <category>
                        <![CDATA[ AI ]]>
                    </category>
                
                <dc:creator>
                    <![CDATA[ Qingfeng Huang ]]>
                </dc:creator>
                <pubDate>Mon, 10 Mar 2025 12:08:03 +0000</pubDate>
                <media:content url="https://chinese.freecodecamp.org/news/content/images/2025/03/0f8cd7d3-54d4-49a3-b864-e3e477446089.png" medium="image" />
                <content:encoded>
                    <![CDATA[ <p data-test-label="translation-intro">
        <strong>原文：</strong> <a href="https://www.freecodecamp.org/news/clustering-in-python-a-machine-learning-handbook/" target="_blank" rel="noopener noreferrer" data-test-label="original-article-link">Learn Clustering in Python – A Machine Learning Engineering Handbook</a>
      </p><!--kg-card-begin: markdown--><p>您是否想学习如何发现与分析数据中隐藏的模式呢？聚类，作为一项基本的无监督机器学习技术，能够助力您揭示极具价值的洞察，进而从根本上转变您对复杂数据集的认知。</p>
<p>在这本全面的手册中，我们将深入探讨一些必须掌握的聚类算法和技术，并辅以相关理论作为支撑。随后，您将通过大量实例、Python 实现方法和其可视化效果来知晓其工作原理。</p>
<p>无论您是刚入门的初学者，或者是富有经验的数据科学家，这本手册都是熟悉掌握聚类技术的宝贵资源。您也能够<a href="https://join.lunartech.ai/clustering-in-python">在此下载这本手册</a>。</p>
<p>倘若您同样喜欢通过听来学习，这里还有个 15 分钟的播客，在其中我们更详细地讨论了聚类。在这一期里，我们探索了聚类的基本理念，从而帮助您更深入地了解如何将这些技术应用于现实世界的数据。</p>
<iframe width="100%" height="152" src="https://open.spotify.com/embed/episode/2O3KSW25GbqCJXl6LfUmyw" title="Spotify embed" allow="autoplay; clipboard-write; encrypted-media; fullscreen; picture-in-picture" allowfullscreen="" loading="lazy"></iframe>
<h3 id="">以下是我们将要涵盖的内容：</h3>
<ol>
<li>
<p><a href="#heading-introduction-to-unsupervised-learning">无监督学习导论</a></p>
</li>
<li>
<p><a href="#heading-supervised-vs-unsupervised-learning">监督学习与无监督学习的区别</a></p>
</li>
<li>
<p><a href="#heading-important-terminology">核心术语解析</a></p>
</li>
<li>
<p><a href="#heading-how-to-prepare-data-for-unsupervised-learning">无监督学习的数据预处理方法</a></p>
</li>
<li>
<p><a href="#heading-clustering-explained">聚类概念解析</a></p>
</li>
<li>
<p><a href="#heading-k-means-clustering">K-均值聚类</a></p>
<ul>
<li>
<p><a href="#heading-k-means-clustering-python-implementation">K-均值聚类：Python 的实现方法</a></p>
</li>
<li>
<p><a href="#heading-k-means-clustering-visualization">K 均值聚类：可视化的实现方法</a></p>
</li>
</ul>
</li>
<li>
<p><a href="#heading-elbow-method-for-optimal-number-of-clusters-k">肘部法则：选择最佳聚类数(K)</a></p>
</li>
<li>
<p><a href="#heading-hierarchical-clustering">层次聚类</a></p>
<ul>
<li>
<p><a href="#heading-hierarchical-clustering-python-implementation">层次聚类：Python 的实现方法</a></p>
</li>
<li>
<p><a href="#heading-hierarchical-clustering-visualization">层次聚类: 可视化的实现方法</a></p>
</li>
</ul>
</li>
<li>
<p><a href="#heading-dbscan-clustering">DBSCAN 聚类</a></p>
<ul>
<li>
<p><a href="#heading-dbscan-clustering-python-implementation">DBSCAN 聚类: Python 的实现方法</a></p>
</li>
<li>
<p><a href="#heading-dbscan-clustering-visualization">DBSCAN 聚类: 可视化的实现方法</a></p>
</li>
</ul>
</li>
<li>
<p><a href="#heading-how-to-use-t-sne-for-visualizing-clusters-with-python">如何使用 t-SNE 在 Python 中可视化聚类</a></p>
</li>
<li>
<p><a href="#heading-more-unsupervised-learning-techniques">探索更多无监督学习技巧方法</a></p>
</li>
</ol>
<h3 id=""><strong>在通过本手册的学习探索后，你将具备以下技能：</strong></h3>
<ol>
<li>
<p><strong>理解无监督学习基础原理</strong> – 您将掌握监督学习与无监督学习的关键差异，并知晓聚类怎样融入更广泛的机器学习范畴.</p>
</li>
<li>
<p><strong>精通聚类相关的重要术语</strong> – 您将熟悉数据点、簇中心、距离度量和聚类评估方法等关键概念.</p>
</li>
<li>
<p><strong>数据预处理以适用于聚类</strong> – 您将会学习怎样处理缺失值、对数据集进行规范化、去除异常值，以及应用像 PCA 和 t-SNE 这类降维技术。</p>
</li>
<li>
<p><strong>深入领会聚类技术</strong> – 您将探索各种各样的聚类方法, 包括 K 均值算法、层次聚类和 DBSCAN，并了解何时使用每种方法。</p>
</li>
<li>
<p><strong>在 Python 中实现 K-均值聚类</strong> – 您将学习如何使用 Python 应用 K-均值算法，同时使用肘部法则优化聚类数量，并有效地可视化聚类结果。</p>
</li>
<li>
<p><strong>应用层次聚类</strong> – 您将理解聚合聚类和分裂聚类，学习如何构建树状图，并使用 Python 实现层次聚类。</p>
</li>
<li>
<p><strong>使用 DBSCAN 进行基于密度的聚类</strong> – 您将掌握 DBSCAN 的聚类方法，包括其识别噪声点和任意形状聚类的能力。</p>
</li>
<li>
<p><strong>可视化聚类结果</strong> – 您将能够使用 Matplotlib、Seaborn 和 t-SNE 等库为聚类结果生成有意义的可视化效果，从而有效地分析和解释数据。</p>
</li>
<li>
<p><strong>评估聚类性能</strong> – 您将学习如何使用轮廓分数、戴维森堡丁指数和方差比准则等技术评估聚类质量。</p>
</li>
<li>
<p><strong>使用真实世界的数据集来进行工作</strong> – 您将获得在真实世界的数据集上应用聚类技术的实践经验，包括客户细分、异常检测和模式识别。</p>
</li>
<li>
<p><strong>扩展您在聚类之外的知识</strong> – 您将接触到其他无监督学习技术，例如混合模型和主题建模，从而拓宽您在机器学习方面的专业知识。</p>
</li>
</ol>
<p>通过学习本手册，您将在聚类和无监督学习方面打下坚实的基础，这将使您能够自信地分析复杂数据集并发现其隐藏的模式！</p>
<h3 id=""><strong>前提条件</strong></h3>
<p>在深入研读这本关于聚类和无监督学习的手册之前，您应当对机器学习概念、数据预处理技术以及基础的 Python 编程技能有扎实的了解。这些前提条件将有助于您理解本书中涵盖的理论基础和实际应用。</p>
<p>首先也是最重要的是，要熟悉 <em><strong>机器学习的基础知识</strong></em>。您应当了解有监督学习和无监督学习之间的区别，以及聚类技术背后的核心原理。</p>
<p>诸如数据点、特征、距离度量（欧几里得距离、曼哈顿距离）， 以及相似性度量等概念在聚类算法中的重要作用. 而掌握概率、统计学和线性代数的基础知识也将对帮助您理解有所帮助，因为这些数学概念构成了许多机器学习模型的基础。</p>
<p>接下来，<strong>数据预处理技术</strong> 对于处理真实世界的数据集起到至关重要的作用。由于聚类算法严重依赖于结构良好的数据，您需要了解如何处理缺失值、对数值特征进行归一化或标准化以及去除可能扭曲聚类结果的异常值。</p>
<p>诸如特征缩放（最小 - 最大归一化、标准化）和降维（PCA, t-SNE）之类的技术能够提高聚类的准确性和效率，让您更易于解读结果。</p>
<p>最后，要跟上本手册中的实践操作，需要具备 <strong>熟练的Python 编程和掌握数据科学库能力</strong>。您应当能够熟悉使用诸如 NumPy 和 Pandas 等库来进行数据处理，使用 Matplotlib 和 Seaborn 进行数据可视化，以及使用 Scikit-learn 实现机器学习算法。</p>
<p>由于您将应用诸如 K-均值算法、层次聚类和 DBSCAN 等聚类技术，熟悉使用 Jupyter Notebooks 编写和执行 Python 脚本以及解读聚类结果，将有助于提升您的学习体验</p>
<p>在这些领域打下坚实的基础后，您将能够充分发挥聚类的优势，从数据中获取更深入的洞察。</p>
<h2 id=""><strong>无监督学习导论</strong></h2>
<p>无监督学习是机器学习中的一项强大技术，它能够在没有预定义标签或目标变量的情况下揭示数据中的隐藏模式和结构。不同于依赖标签数据进行训练的监督学习，无监督学习使我们能够探索和理解无标签数据集的内在结构。</p>
<p>无监督学习的一个重要应用是聚类。聚类是一种根据数据点的内在特征和相似性将其分组的过程。通过识别数据集中的模式和关系，聚类能够帮助我们提取有价值的见解，并理解复杂数据的结构。</p>
<p>聚类在多个领域具有重要应用，包括客户分群、异常检测、图像识别和推荐系统。它能够识别数据中的不同群组，将数据分类为有意义的类别，并揭示数据集背后的潜在趋势。</p>
<p>在后续章节里，我们将深入探讨不同的聚类算法，包括 K-Means、层次聚类 和 DBSCAN, 分析其理论基础、实现方法与可视化呈现。在本手册结束时，您将全面掌握无监督学习的相关知识，能够熟练运用各类聚类技术到自己的数据分析工作中，具备相应的知识与技能。</p>
<p>请记住，聚类只是无监督学习的一个方面，它还包含许多其他技术和应用。让我们深入探索无监督学习的精彩世界，挖掘它在从无标签数据中提取洞察方面的强大能力！</p>
<p><a href="https://www.lunartech.ai/bootcamp/ai-engineering-bootcamp"><img src="https://dataexpertise.in/wp-content/uploads/2023/12/Supervised-vs.-Unsupervised-Learning-1.jpg" alt="监督学习与无监督学习之间的区别 " width="600" height="400" loading="lazy"></a></p>
<h2 id="vs">监督学习 vs. 无监督学习</h2>
<p>谈到机器学习，主要有两种方法：有监督学习和无监督学习。理解这两种方法之间的差异对于根据您的数据分析需求选择合适的技术至关重要。</p>
<p>监督学习，顾名思义，是指在有标签的数据上训练机器学习模型。在这种方法中，输入数据包含特征（也称为属性或变量）以及相应的目标值或标签。模型从这些有标签的数据中学习，并根据新的、未见过的数据进行预测或分类。</p>
<p>而另一方面，无监督学习完全是关于探索未标记的数据。在无监督学习中，数据没有预定义的标签或目标值。相反，算法自行在数据中寻找模式、结构和关系。其目标是发现隐藏的见解，并更深入地了解数据的潜在结构。</p>
<p>值得一提的是，无监督学习的一个关键优势是能够发现此前未知的模式和关系。因为由于不受标签数据的限制，无监督算法可以揭示其他分析方法可能难以察觉的有价值信息。这使得无监督学习在探索性数据分析、异常检测和聚类等领域尤为有用。</p>
<p>在监督学习中，目标变量起到引导作用，使模型能够进行准确的预测或分类。然而，这种对标签数据的依赖也会限制模型的能力，因为它可能难以处理训练数据中未出现的全新模式或未被充分表示的数据。</p>
<p>相比之下，无监督学习提供了一种更灵活和自适应的方法。即使没有明确的标签，它仍能捕捉数据的内在结构和关系。通过利用聚类算法和降维技术，无监督学习为解析复杂数据集提供了强大的工具。</p>
<p>总而言之，监督学习适用于有标签数据的任务，目标是进行精准的预测或分类。而无监督学习在探索数据中的隐藏模式和关系时尤为重要，特别是在标签数据稀缺或不存在的情况下。</p>
<p>通过理解这两种方法之间的差异，您可以有效地选择正确的技术，从而充分发挥数据分析工作的潜力。</p>
<h2 id=""><strong>关键术语</strong></h2>
<p>想要全面理解无监督学习和聚类，熟悉与这些概念相关的关键术语至关重要。以下是一些您应该了解的重要术语：</p>
<p><strong>1. 数据点</strong></p>
<p>数据点指的是数据集中单个的观测值或实例。每个数据点包含描述特定对象或事件的各种特征或属性。</p>
<p><strong>2. 聚类数量</strong></p>
<p>聚类的数量代表了在聚类过程中，数据将被划分成不同组别的期望数量或估计数量。这是一个关键参数，它决定了最终聚类的结构。</p>
<p><strong>3. 无监督算法</strong></p>
<p>无监督算法是一种数学方法，用于在没有标记或预分类示例的情况下识别数据中的模式或关系。这些算法能够探索数据集的内在结构，挖掘隐藏的规律。</p>
<p>理解并运用这些术语，将为您的无监督学习和聚类之旅奠定坚实的基础。而在接下来的部分中，我们将更深入地探讨如何在 Python 中实现聚类技术的实际应用。</p>
<p><a href="https://www.lunartech.ai/bootcamp/ai-engineering-bootcamp"><img src="https://cdn.letterdrop.co/pictures/fe3db832-862f-4a35-be7c-37231ad814bb.png" alt="这张图展示了从数据收集到清理、转换、降维和拆分的准备过程。出自机器学习的数据准备: 终极指南| Pecan AI" width="1024" height="576" loading="lazy"></a></p>
<h2 id=""><strong>如何为无监督学习做数据预处理</strong></h2>
<p>在实施无监督学习算法之前，确保数据已经过适当的预处理至关重要。这包括采取某些步骤来优化输入数据，使其适合使用聚类技术进行分析。以下是为无监督学习准备数据时的重要考虑因素：</p>
<h3 id=""><strong>数据归一化</strong></h3>
<p>数据准备的一个关键方面是归一化，即把所有特征都缩放到一致的范围。这是必要的，因为数据集中的变量可能具有不同的单位或量级。</p>
<p>归一化有助于在聚类过程中避免对任何特定特征产生偏向。常见的归一化方法包括最小 - 最大缩放和标准化。</p>
<h3 id=""><strong>处理缺失值</strong></h3>
<p>处理缺失值是数据预处理中的关键步骤。在应用聚类算法之前，必须识别并解决数据集中存在的缺失值。</p>
<p>处理缺失值的方法多种多样，其中一种常见技术是插值，即使用统计方法或算法估算缺失值并进行填充。</p>
<h3 id=""><strong>异常值检测与处理</strong></h3>
<p>异常值可能会显著影响聚类结果，因为它们可能会干扰簇边界的确定。因此，及时检测和处理异常值至关重要。常见的方法包括 Z-score 分析和四分位距（IQR）分析，用于识别并处理异常值。</p>
<h3 id=""><strong>降维</strong></h3>
<p>在某些情况下，数据集可能具有很高的维度，这意味着它包含大量的特征。高维数据可能难以有效地可视化和分析。可以采用诸如主成分分析（PCA）之类的降维技术来减少特征的数量，同时保留数据中最具信息量的部分。</p>
<p>通过仔细准备数据、标准化变量、处理缺失值、解决异常值以及在必要时降低维度，您可以优化无监督学习算法的输入数据质量。这能确保聚类结果准确且有意义，从而在数据中发现有价值的见解和模式。</p>
<p>请记住，数据准备是无监督学习过程中至关重要的步骤，它为成功的聚类分析奠定基础。</p>
<p><a href="https://www.lunartech.ai/bootcamp/ai-engineering-bootcamp"><img src="https://cdn.analyticsvidhya.com/wp-content/uploads/2019/08/An-Introduction-to-K-Means-Clustering-.webp" alt="K 均值聚类的可视化图，彩色数据点在坐标平面上按聚类排列。周围有图表和数学公式，说明了聚类分配和质心。- Analytics Vidhya" width="872" height="473" loading="lazy"></a></p>
<h2 id=""><strong>聚类解析</strong></h2>
<p>聚类是无监督学习中的一项基本技术，在揭示数据中的隐藏模式方面发挥着关键作用。它通过基于数据点的相似性对其进行分组，使我们能够识别数据集中的不同子集或簇。通过分析这些簇的结构，我们可以获得有价值的见解并做出基于数据的决策。</p>
<h3 id=""><strong>聚类的概念</strong></h3>
<p>从本质上讲，聚类旨在找出数据点之间的相似性或关系，而无需任何预定义的标签或目标变量。其目标是在每个聚类内部最大化相似性，同时在不同聚类之间最大化差异性。这一过程使我们能够识别数据中的模式和内在结构。</p>
<p>聚类可以通过多种因素来定义，例如距离、连通性或密度。聚类中的每个数据点与其他同一聚类中的点的相似度高于与其他聚类中的点的相似度。这种分组方式使我们能够对数据进行细分，这在诸如客户细分、异常检测和图像识别等各种领域中都具有极大的用处。</p>
<h3 id=""><strong>聚类算法的类型</strong></h3>
<p>有几种聚类算法可供选择，每种算法都有其自身将数据划分成簇的方法。一些流行的算法包括 K 均值聚类、层次聚类和 DBSCAN（基于密度的空间聚类算法，用于处理噪声和离群点）。</p>
<h4 id="1k"><strong>1. K-均值聚类</strong></h4>
<p>K 均值聚类是一种被广泛使用的算法，旨在将数据划分成 K 个不同的簇。它通过迭代地将每个数据点分配给最近的簇中心，然后重新计算簇中心来实现这一目标。这个过程会一直持续到收敛，从而形成定义明确的簇。</p>
<h4 id="2"><strong>2. 层次聚类</strong></h4>
<p>层次聚类通过基于特定标准递归地划分或合并聚类来创建聚类的层次结构。这种方法可以用树状图来表示，树状图能为聚类的层次结构以及聚类之间的关系提供有价值的见解。</p>
<h4 id="3dbscan"><strong>3. DBSCAN 聚类</strong></h4>
<p>DBSCAN 是一种基于密度的算法，它根据数据点的密度和连通性对其进行分组。该算法特别适用于识别任意形状的簇以及处理噪声数据。</p>
<p>这些只是聚类算法的几个例子，每种算法都有其自身的优势和适用于特定场景的情况。根据数据特征和问题领域选择最合适的算法是很重要的。</p>
<p>在接下来的部分中，我们将更深入地探讨这些聚类算法的理论、实现和可视化，以便为您提供对其工作原理以及何时使用它们的全面理解。</p>
<p>需要谨记的是，聚类是一种强大的技术，它能让我们挖掘出数据中隐藏的结构，从而获得宝贵的见解并做出明智的决策。所以就让我们一起走进聚类的世界，探索它所蕴含的潜力。</p>
<p><a href="https://www.lunartech.ai/bootcamp/ai-engineering-bootcamp"><img src="https://images.squarespace-cdn.com/content/v1/5acbdd3a25bf024c12f4c8b4/1608407348392-22767PJ7RQ85BD5RLSLZ/k-means-clustering.png" alt="K-均值聚类 — 机器学习与人工智能的科学" width="1127" height="867" loading="lazy"></a></p>
<h2 id="k"><strong>K-均值聚类</strong></h2>
<p>K 均值聚类是一种常见的无监督学习算法，基于数据点之间的相似性将其划分到不同的簇中。在本节中，我们将深入探讨 K 均值聚类的理论原理，并使用 scikit-learn 库在 Python 中实现该算法。</p>
<p>在数据科学和数据分析领域，我们常常希望将观察结果归类到一系列的 <strong>分段</strong> 或者 <strong>簇</strong> 以满足不同的分析需求。例如，一家公司可能希望根据客户的交易历史或购买频率，将客户分为 3 到 5 组。这通常是一种 <strong>无监督</strong> <strong>学习</strong> 方法，因为这些标签(组别/分段/簇）在分析前是未知的。</p>
<p>将观测值聚类分组的最流行方法之一是无监督聚类算法<strong>K-均值</strong>. 以下是 K 均值聚类的条件：</p>
<ul>
<li>
<p>需要提前指定聚类的数量：K</p>
</li>
<li>
<p>每个观察结果都需要至少属于一个类别</p>
</li>
<li>
<p>每个观测值都必须只属于一个类别（类别之间不能有重叠）</p>
</li>
<li>
<p>任何观测值都不应属于超过一个类别。</p>
</li>
</ul>
<p>K 均值算法背后的理念在于 <strong>最小化簇内方差，并最大化簇间方差</strong> 因此，K 均值算法将观测值划分成 K 个簇，使得所有 K 个簇的总簇内方差尽可能小。</p>
<p>其背后的动机是将观测值进行聚类，使得聚类到同一组的观测值尽可能相似，而来自不同组的观测值尽可能不同。</p>
<p>从数学角度而言，簇内变异的定义取决于您自行选择的距离度量方式。例如，您可以选用欧几里得距离、曼哈顿距离等作为距离度量方式。</p>
<p>K 均值聚类在簇内变异最小的情况下是最优的 簇 C_k 的簇内差异量度 W(C_k) 反映了同一簇内观测点之间的差异程度。因此，需要解决以下优化问题：</p>
<p>$$\min_{C_1, \dots, C_K} \sum_{k=1}^{K} W(C_k)$$</p>
<p>其中，基于欧几里得距离的簇内差异可以表示如下：</p>
<p>$$W(C_k) = \frac{1}{|C_k|} \sum_{i,i' \in C_k} \sum_{j=1}^{p} (x_{ij} - x_{i'j})^2$$</p>
<p>第 k 个聚类中的观测值数量用 |C_k | 表示。因此，K 均值的优化问题可以描述如下：</p>
<p>$$\min_{C_1, \dots, C_K} \left\{ \sum_{k=1}^{K} \frac{1}{|C_k|} \sum_{i,i' \in C_k} \sum_{j=1}^{p} (x_{ij} - x_{i'j})^2 \right\}$$</p>
<h3 id="k"><strong>K-均值算法</strong></h3>
<p>K 均值算法的伪代码可描述如下：</p>
<p><a href="https://www.lunartech.ai/bootcamp/ai-engineering-bootcamp"><img src="https://miro.medium.com/v2/resize:fit:1400/1*0DjFFWY4tY74Z8EMXggEMA.png" alt="替换文本: 该图展示了 K 均值算法的伪代码，包含两个主要步骤。步骤 1：在初始条件下，将每个数据点随机分配到一个簇中。步骤 2：当簇发生变化时，更新簇的质心并重新分配数据点，直至收敛。" width="1400" height="718" loading="lazy"></a></p>
<p>K 均值算法是一种非确定性方法，其随机性体现在第一步，即所有观测值都被随机分配到 K 个类别中的一个。</p>
<p>在第二步中，对于每个聚类，通过计算该聚类中所有数据点的平均值来计算聚类质心。第 <em>Kth</em> 个聚类的质心是一个长度为 <em>p</em> 的向量，其中包含第  <em>kth</em> 个聚类中所有观测值的变量均值，而 <em>p</em> 为变量的数量。</p>
<p>然后，在下一步中，对观测值的簇进行更新，使得每个观测值都被分配到其质心最近的簇中，通过迭代最小化 <strong>总簇内平方和</strong> 来实现。也就是说，我们反复执行步骤 2 和 3，直到簇的质心不再变化或者达到最大迭代次数为止。</p>
<h3 id="kpython"><strong>K 均值聚类：Python 的实现方法</strong></h3>
<p>让我们来看一个将观测值分类为 4 类的例子。原始数据如下所示：</p>
<p><a href="https://www.lunartech.ai/bootcamp/ai-engineering-bootcamp"><img src="https://miro.medium.com/v2/resize:fit:1200/1*QRRqHu4MATa7piwcPHmsSA.png" alt="标题为 &quot;原始数据可视化,&quot; 的散点图，显示绿色点分布在 X 值从 0 到 3 的列中，Y 值范围为 0 到 10。" width="600" height="600" loading="lazy"></a></p>
<pre><code># 导入项目所需要的库
# KMeans 是 scikit-learn 提供的聚类算法
from sklearn.cluster import KMeans  
# Metrics 模块用于评估聚类性能
from sklearn import metrics  
# 用于数值计算和数组操作
import numpy as np  
# Pandas 用于以结构化 DataFrame 格式处理数据
import pandas as pd  

# 生成用于 K-Means 聚类的合成数据
# 创建一个 100×2 的数组，元素为 0 到 9 之间的随机整数
df = np.random.randint(0, 10, size=[100, 2])  
# 生成一个 300×1 的数组，元素为 0 到 3 之间的随机整数
X1 = np.random.randint(0, 4, size=[300, 1])  
# 生成一个 300×1 的数组，元素为 0 到 10 之间的随机浮点数
X2 = np.random.uniform(0, 10, size=[300, 1])  
# 沿第二个轴（列方向）合并 X1 和 X2，形成具有两个特征的数据集
df = np.append(X1, X2, axis=1)  

# 在生成的数据集上应用 K-Means 聚类算法
# 调用 KMeans_Algorithm 函数，并设置簇数 K=4
Clustered_df = KMeans_Algorithm(df=df, K=4)  
# 将聚类后的数据转换为 Pandas DataFrame
df = pd.DataFrame(Clustered_df)  


# 执行 K-Means 聚类的函数
def KMeans_Algorithm(df, K):
    """
    在给定数据集上执行 K-Means 聚类。

    参数:
    df (array-like): 待聚类的输入数据集。
    K (int): 聚类的簇数.

    返回:
    df (DataFrame): 原始数据集，新增一列用于存储聚类标签。
    """

    # 使用指定参数初始化 K-Means 模型
    # 将聚类数量设置为 K
    # 使用 k-means++ 初始化以提高收敛速度
    # 将最大迭代次数设置为 300
    # 设置固定的随机种子以确保结果可复现
    KMeans_model = KMeans(
        n_clusters=K,  
        init='k-means++',  
        max_iter=300,  
        random_state=2021  
    )

    # 在数据集上拟合 K-Means 模型
    KMeans_model.fit(df)

    # 提取聚类质心（每个簇的中心点）。
    centroids = KMeans_model.cluster_centers_

    # 将质心转换为 DataFrame，并设置列名为 "X" 和 "Y"
    centroids_df = pd.DataFrame(centroids, columns=["X", "Y"])

    # 获取分配给每个数据点的聚类标签
    labels = KMeans_model.labels_

    # 将输入数据转换为 Pandas DataFrame（如果尚未转换）
    df = pd.DataFrame(df)

    # 添加新列以存储分配的聚类标签
    df["labels"] = labels

    # 返回包含聚类标签的更新后的 DataFrame
    return d
</code></pre>
<p><a href="https://lunartech.ai"><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1738528849086/9891484a-a8b0-45eb-a8e3-f1a76c038b73.png" alt="这是 Python 中 K-Means 聚类的代码截图。其中包括导入 scikit-learn、numpy 和 pandas 等库，生成合成数据，以及定义一个带有参数和 K-Means 模型初始化的聚类函数。该代码处理数据集，并返回带有聚类标签的 DataFrame。- lunartech.ai" width="1750" height="3052" loading="lazy"></a></p>
<p>此脚本旨在生成合成数据，应用 K-Means 聚类，并为每个数据点分配聚类标签。K-Means 聚类算法是一种无监督机器学习方法，它根据特征空间中数据点的接近程度将相似的数据点分组到不同的簇中。以下是该脚本的工作原理的逐步分解。</p>
<p>第一步是导入必要的库。 该脚本使用<code>sklearn.cluster</code> 中的 <code>KMeans</code> 来实现 K-均值聚类算法。虽然  <code>metrics</code> 模块在 <code>sklearn</code> 已经存在, 但是在本脚本中未被使用, 不过它对于评估聚类质量非常有用. <code>NumPy</code> 负责数值计算和数组操作，而 <code>Pandas</code>用于将数据组织成 DataFrame，以便更方便地操作和处理。</p>
<p>接下来，脚本生成合成数值数据。创建一个 NumPy 数组 <code>df</code>，其维度为 100×2，包含 0 到 9 之间的随机整数。此外，还分别生成两个额外的数组 <code>X1</code> and <code>X2</code>。其中 <code>X1</code> 为 300×1 的数组，包含 0 到 3 之间的随机整数，而 <code>X2</code> 为 300×1 的数组，包含 0 到 10 之间的随机浮点数。随后，这两个数组沿第二个轴（列方向）合并，形成一个具有两个特征的数据集，使其可以用于聚类分析。</p>
<p>在合成数据准备完成后，脚本应用 K-Means 聚类算法。调用 <code>KMeans_Algorithm</code> 函数，并设置 <code>K=4</code>, 即算法将数据分为四个簇。该函数返回聚类后的数据集，并将其转换为 Pandas DataFrame 进行进一步处理。</p>
<p><code>KMeans_Algorithm</code> 函数接受两个参数：数据集 <code>df</code> 和簇的数量 <code>K</code>. 在该函数内部，使用 <code>KMeans()</code> 初始化 K-均值模型. 簇的数量设为 <code>K</code>, 同时 <code>init='k-means++'</code> 参数用于优化初始化，以加快收敛速度。<code>max_iter=300</code> 限制最大迭代次数，以防止计算时间过长，而 <code>random_state=2021</code> 用于确保结果的可复现性。</p>
<p>初始化后，使用<code>KMeans_</code><a href="http://model.fit"><code>model.fit</code></a><code>(df)</code> 在数据集上拟合 K-均值模型。这一步骤处理数据集，识别聚类中心，并将数据点归入相应的簇。训练完成后，通过 <code>KMeans_model.cluster_centers_</code>提取聚类质心，并将其存储在一个 Pandas DataFrame 中，列名设为 "X" and "Y"，以便更直观地解析聚类结果。</p>
<p>每个数据点都会被分配一个聚类标签，该标签可通过 <code>KMeans_model.labels_</code>获取。 脚本随后确保数据集被存储为 Pandas DataFrame（如果尚未转换），并添加一个新的 <code>"labels"</code>列来存储分配的聚类标签。最终，返回包含原始特征及聚类结果的更新数据集。</p>
<p>该脚本的输出是一个 Pandas DataFrame，包含三列：两列数值特征列表示生成的数据点，另一列 <code>"labels"</code> 列指示每个数据点所属的聚类。 例如，简化的输出可能如下所示：一个数据点 <code>[2.0, 7.4]</code> 被分配到簇 <code>0</code>，而另一个数据点 <code>[1.0, 3.2]</code> 归属于簇 <code>1</code>.</p>
<p>该脚本成功创建了一个结构化数据集，将数据划分为四个不同的簇，并为每个数据点分配了相应的聚类标签。 结果可以通过散点图等可视化技术进一步分析，以更直观地理解聚类分布。未来的优化方向可能包括使用 轮廓系数评估聚类质量，或尝试不同的簇数量，以找到最优的聚类方案。</p>
<h3 id="k"><strong>K 均值聚类：可视化的实现方法</strong></h3>
<p>K-均值的关键优势之一在于其处理大型数据集时的简单性和高效性。它是一种在众多领域广泛使用的聚类算法，包括客户细分、图像压缩、异常检测和模式识别。</p>
<p>尽管K-均值算法简单，但它在发现数据中固有的群组结构方面非常有效，使其成为无监督学习中的重要工具。但和任何算法一样，它也有局限性，比如对初始质心选择的敏感性以及难以检测非球形簇。了解这些优缺点有助于在将K-均值应用于实际数据集时做出明智的决策。</p>
<p>在本节中，我们将探讨如何在 Python 中实现 K-均值聚类并可视化结果。通过逐步的代码实现，您将看到数据点是如何被分组到各个簇中的，以及算法是如何迭代地优化其簇分配的。我们还将讨论选择最优簇数的最佳实践以及如何评估聚类质量。</p>
<h3 id="k">洞悉K-均值算法</h3>
<p>在深入探讨实现方法之前，让我们先简要了解一下K-均值算法的工作原理。该算法遵循以下步骤：</p>
<ol>
<li>
<p><strong>步骤 1：初始化</strong> – 随机选择 K 个质心，其中 K 表示期望的聚类数量。</p>
</li>
<li>
<p><strong>步骤 2：分配任务</strong> – 根据欧几里得距离，将每个数据点分配给最近的质心。</p>
</li>
<li>
<p><strong>步骤 3: 更新</strong> – 通过取分配给每个聚类的所有数据点的平均值来重新计算质心</p>
</li>
<li>
<p><strong>步骤 4: 重复</strong> – 重复步骤 2 和 3，直至满足收敛标准（例如，质心移动极小）。</p>
</li>
</ol>
<pre><code>fig, ax = plt.subplots(figsize=(6, 6))

# 针对具有不同标签的观测数据，使用第 1 列和第 2 列进行可视化。
plt.scatter(df[df["labels"] == 0][0], df[df["labels"] == 0][1],
c='black', label='cluster 1')
plt.scatter(df[df["labels"] == 1][0], df[df["labels"] == 1][1],
c='green', label='cluster 2')
plt.scatter(df[df["labels"] == 2][0], df[df["labels"] == 2][1],
c='red', label='cluster 3')
plt.scatter(df[df["labels"] == 3][0], df[df["labels"] == 3][1],
c='y', label='cluster 4')
plt.scatter(centroids[:, 0], centroids[:, 1], marker='*', s=300, c='black', label='centroid')
plt.legend()
plt.xlim([-2, 6])
plt.ylim([0, 10])
plt.xlabel('X')
plt.ylabel('Y')
plt.title('聚类数据的可视化')
ax.set_aspect('equal')
plt.show()
</code></pre>
<p><a href="https://lunartech.ai"><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1738529023579/d49a6f1c-93fa-42ab-ac99-4e168d30c44c.png" alt="一张使用 Matplotlib 库的 Python 脚本的截图，该脚本用于可视化聚类数据。它在一个散点图中用不同的颜色和标签绘制了聚类，并包含一个黑色的质心标记。- lunartech.ai" width="1868" height="1116" loading="lazy"></a></p>
<p><a href="https://www.lunartech.ai/bootcamp/ai-engineering-bootcamp"><img src="https://miro.medium.com/v2/resize:fit:1400/1*Isl-76ShvTNwa35Xu50yHA.png" alt="标题为“聚类数据可视化”的散点图，用四种不同颜色表示四个聚类：黑色代表聚类 1，绿色代表聚类 2，红色代表聚类 3，黄色代表聚类 4。黑色星号标记网格上的质心，X 轴和 Y 轴分别标注为 -2 至 6 和 0 至 10。图中还包含图例。" width="969" height="705" loading="lazy"></a></p>
<p>在上图中，K 均值算法已将这些观测值聚类为 4 组。从可视化效果来看，这些观测值的聚类方式甚至从图表上看也显得很自然，而且合乎情理。</p>
<h3 id="k"><strong>肘部法则确定最优聚类数（K）</strong></h3>
<p>使用 K 均值算法时面临的最大挑战之一是聚类数目的选择。有时这是业务决策，但大多数时候我们希望选择一个最优且合理的 K 值（即聚类数量）。确定这个最优 K 值（或聚类数量）最常用的方法之一是 <strong>肘部法则</strong>。</p>
<p>要使用这种方法，您需要了解什么是 <strong>惯性</strong>。惯性是样本到其最近聚类中心的平方距离之和。因此，惯性或 <strong>聚类内平方和</strong> 值可以表明不同聚类的连贯性或纯度如何。惯性可以这样描述：</p>
<p>$$\sum_{i=1}^{N} (x_i - C_k)^2$$</p>
<p>其中 N 是数据集中的样本数量，C 是一个聚类的中心，k 是聚类的索引。因此，惯性只是计算每个聚类中样本到其聚类中心的平方距离，并将它们相加。</p>
<p>然后我们可以计算不同聚类数量 K 的惯性。我们可以像下面的图一样绘制出来，其中我们考虑 K = 1, 2, ..., 10。然后从图中我们可以选择肘部出现时对应的 K 值。在这种情况下，肘部出现在 K = 3 时。</p>
<p><a href="https://www.lunartech.ai/bootcamp/ai-engineering-bootcamp"><img src="https://miro.medium.com/v2/resize:fit:1400/1*S9wmsHzA4nVnZ7zSi9WfLA.png" alt="折线图展示了 K 均值肘部方法，其中 x 轴表示从 1 到 9 的聚类数量，y 轴表示惯性。该图表明在大约 3 个聚类时惯性急剧下降。" width="1400" height="667" loading="lazy"></a></p>
<pre><code>def Elbow_Method(df):
    inertia = []
    # 考虑 K = 1, 2,..., 10 作为 K
    K = range(1, 10)
    for k in K:
        KMeans_Model = KMeans(n_clusters=k, random_state = 2022)
        KMeans_Model.fit(df)
        inertia.append(KMeans_Model.inertia_)
    return(inertia)

K = range(1, 10)
inertia = Elbow_Method(df)
plt.figure(figsize = (17,8))
plt.plot(K, inertia, 'bx-')
plt.xlabel("K: 聚类数量")
plt.ylabel("惯性")
plt.title("K 均值：肘部法则")
plt.show()
</code></pre>
<p><a href="https://lunartech.ai"><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1738529158688/f8c4892b-962b-416d-9795-c442b149deee.png" alt="这段代码片段展示了 Python 中 K-Means 聚类的肘部法则。该函数计算了从 1 到 10 个聚类的惯性，并使用 Matplotlib 绘制结果，以确定最佳聚类数量。- lunartech.ai" width="1380" height="1042" loading="lazy"></a></p>
<p>K 均值算法是一种非确定性方法，其随机性体现在第一步，即所有观测值都被随机分配到 K 个类别中的一个。</p>
<p>正如您所见，K 均值聚类提供了一种基于相似性对数据点进行分组的高效且有效的方法。通过在 Python 中实现 K 均值算法，您可以轻松地将此技术应用于自己的数据集，并从数据中获得有价值的见解。</p>
<p>Python 提供了强大的工具来实现和可视化 K-Means 聚类。借助 scikit-learn 库和 matplotlib，您可以轻松地将 K-Means 应用于您的数据集，并从生成的聚类中获得很多有用的信息。</p>
<p><a href="https://www.lunartech.ai/bootcamp/ai-engineering-bootcamp"><img src="https://media.geeksforgeeks.org/wp-content/uploads/20230427165259/Distance-Matrix-in-Hierarchical--Clustering.webp" alt="图示展示了层次聚类中的距离矩阵比较。图中说明了四种方法：最小法、最大法、组平均法和沃德法，每种方法都用圆圈和标有数字的点来表示数据簇。" width="1000" height="500" loading="lazy"></a></p>
<h2 id=""><strong>层次聚类理论</strong></h2>
<p>另一种常见的聚类技术是层次聚类。 这是一种无监督学习方法，可用于将观测数据聚类成不同的分组。但与 K-Means 不同，层次聚类最初将每个观测数据视为一个独立的簇。</p>
<h3 id="vs"><strong>凝聚层次聚类 vs. 分裂层次聚类</strong></h3>
<p>层次聚类主要有两种类型：凝聚层次聚类和分裂层次聚类。</p>
<p>凝聚聚类首先将每个数据点分配到其自身的簇中。然后，它基于选定的距离度量标准，迭代地合并最相似的簇，直到形成一个包含所有数据点的单个簇。</p>
<p>这种自下而上的方法会生成一种类似二叉树的结构，也称为树状图，其中每个节点的高度代表正在合并的簇之间的差异程度。</p>
<p>另一方面，分裂聚类从包含所有数据点的一个单一簇开始。然后，它递归地将簇划分为更小的子簇，直到每个数据点都在自己的簇中。这种自上而下的方法生成了一个树状图，提供了有关簇层次结构的见解。</p>
<h3 id=""><strong>层次聚类的距离度量</strong></h3>
<p>要确定聚类或数据点之间的相似性，您可以使用多种距离度量方法。常用的度量方法包括欧几里得距离、曼哈顿距离和余弦相似度。这些度量方法量化了成对数据点之间的差异或相似性，并指导聚类过程。</p>
<p>在这种技术中，最初每个数据点都被视为一个单独的簇。在每次迭代中，最相似或差异最小的簇合并为一个簇，此过程一直持续到只剩下一个簇为止。因此，该算法反复执行以下步骤：</p>
<ul>
<li>
<p>1: 找出距离最近的两个聚类。</p>
</li>
<li>
<p>2: 合并两个最相似的簇。</p>
</li>
<li>
<p>然后它继续这种迭代过程，直到所有的簇都合并在一起。</p>
</li>
</ul>
<p>两个簇之间的相异性或相似性计算取决于所选择的链接（Linkage）方式。常见的五种链接方法包括：</p>
<ul>
<li>
<p><strong>完全链接:</strong> 计算两个簇（K1 和 K2）之间的最大簇间差异性。具体而言，需要计算 K1 中所有观测点与 K2 中所有观测点之间的成对差异性，并选择其中最大的值作为簇间距离。</p>
</li>
<li>
<p><strong>单链接:</strong> 计算两个簇（K1 和 K2）之间的最小簇间差异性。具体而言，需要计算 K1 中所有观测点与 K2 中所有观测点之间的成对差异性，并选择其中最小的值作为簇间距离。</p>
</li>
<li>
<p><strong>平均链接:</strong> 计算两个簇（K1 和 K2）之间的平均簇间差异性。具体而言，需要计算 K1 中所有观测点与 K2 中所有观测点之间的成对差异性，并取这些差异值的平均值作为簇间距离。</p>
</li>
<li>
<p><strong>质心链接:</strong> 计算簇 K1 的质心与簇 K2 的质心之间的差异性。（这种链接方式通常较少被使用，因为它可能导致较多的簇重叠。）</p>
</li>
<li>
<p><strong>沃德法:</strong> 通过最小化每个观测点与簇内平均观测点之间的平方和距离，来决定如何合并簇。该方法倾向于生成大小相近的簇，并在层次聚类中常用于优化聚类结果。</p>
</li>
</ul>
<h3 id="python"><strong>层次聚类：Python 的实现方法</strong></h3>
<p>层次聚类是一种强大的无监督学习技术，可根据数据点之间的相似性将其分组为不同的簇。 在本节中，我们将探讨如何在 Python 中实现层次聚类。</p>
<p>以下是在 Python 中实现层次聚类的示例：</p>
<pre><code>import scipy.cluster.hierarchy as HieraarchicalClustering
from sklearn.cluster import AgglomerativeClustering
import numpy as np
import pandas as pd

# 生成层次聚类的数据
df = np.random.randint(0,10,size = [100,2])
X1 = np.random.randint(0,4,size = [300,1])
X2 = np.random.uniform(0,10,size = [300,1])
df = np.append(X1,X2,axis = 1)
hierCl = HieraarchicalClustering.linkage(df, method='ward')

Hcl= AgglomerativeClustering(n_clusters = 7, affinity = 'euclidean', linkage ='ward')
Hcl_fitted = Hcl.fit_predict(df)
df = pd.DataFrame(df)
df["labels"] = Hcl_fitted
</code></pre>
<p><a href="https://lunartech.ai"><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1738529216677/9b71d1c5-4847-4cc3-b847-0620409119d6.png" alt="使用 scipy、sklearn、numpy 和 pandas 等库进行层次聚类的 Python 代码截图。该代码生成随机数据，使用函数执行聚类，并输出带有标签的 DataFrame。- lunartech.ai " width="1734" height="968" loading="lazy"></a></p>
<p>这段代码使用了 Scipy 的层次聚类模块和 Scikit-learn 的凝聚聚类算法来实现层次聚类。该脚本的目的是生成一个合成数据集，应用层次聚类，并为数据点分配聚类标签。</p>
<p>脚本的第一部分导入了所需的库。导入了 Scipy 的层次聚类模块 (<code>scipy.cluster.hierarchy</code>) 并将其命名为 <code>HieraarchicalClustering</code>,用于执行基于链接的聚类。还导入了 Scikit-learn 的 <code>AgglomerativeClustering</code> 类来实现一种特定类型的层次聚类。此外，NumPy 用于数值运算和生成随机数据，而 Pandas 则用于将数据结构化为 DataFrame。</p>
<p>接下来，脚本生成合成的数值数据。创建一个 100×2 的矩阵 (<code>df</code>)，其中包含 0 到 9 之间的随机整数。然后分别创建两个额外的数据集 <code>X1</code> and <code>X2</code>。<code>X1</code> 包含 300 个 0 到 3 之间的随机整数，而<code>X2</code> 包含 300 个 0 到 10 之间的随机浮点数。然后使用 <code>np.append()</code>沿着第二轴将这两个数据集合并，形成一个具有两个特征的数据集，该数据集将用于聚类。</p>
<p>一旦数据集准备就绪，便使用 Ward 连接法进行层次聚类，该方法可使合并后的聚类之间的方差最小化。通过 <code>HieraarchicalClustering.linkage(df, method='ward')</code> 创建链接矩阵 <code>hierCl</code>，该函数用于计算层次聚类的解决方案。</p>
<p>生成层次聚类链接矩阵后，应用凝聚聚类将数据分为七个簇（<code>n_clusters=7</code>）。 <code>affinity='euclidean'</code> 参数指定使用欧几里得距离作为度量点之间相似性的距离度量。<code>linkage='ward'</code> 参数确保使用 Ward 方法合并簇，以最小化方差。然后使用 <a href="http://Hcl.fit"><code>Hcl.fit</code></a><code>_predict(df)</code> 将模型拟合到数据集，为每个数据点分配一个簇标签。</p>
<p>最后，数据集被转换为一个 Pandas 数据框，并添加了一个新列“labels”来存储分配的聚类标签。生成的数据框现在既包含原始数据点，也包含其对应的聚类分配，从而便于进一步分析或可视化。</p>
<p>总之，此脚本生成随机数据，使用 Scipy 的 linkage 方法和 Scikit-learn 的 Agglomerative Clustering 进行层次聚类，并为每个数据点分配聚类标签。最终的数据集可用于分析聚类结构、可视化结果或验证聚类效果。</p>
<h3 id=""><strong>层次聚类: 可视化的实现方法</strong></h3>
<p>层次聚类的一个关键优势在于其能够创建聚类的层次结构，这能够为数据点之间的关系提供有价值的见解。</p>
<p>在 Python 中可视化层次聚类，我们可以使用诸如 Scikit-learn、SciPy 和 Matplotlib 等多种库。这些库提供了易于使用的函数和工具，从而简化了可视化过程。</p>
<p>因此，在执行层次聚类之后，通常很有帮助的是将聚类结果可视化。我们可以使用各种可视化技术，例如树状图或热图。</p>
<p>正如我们上面所讨论的，树状图是一种树形图，用于展示聚类之间的层次关系。它可以通过 Python 中的 Scipy 库生成。</p>
<p>以下是一个在 Python 中可视化树状图和聚类点的示例：</p>
<pre><code># 生成一个树状图以帮助确定最佳聚类数量
# 树状图展示了层次聚类如何逐步合并数据点。
dendrogram = HieraarchicalClustering.dendrogram(hierCl)

# 设置树状图的标题
plt.title('树状图')

# 在 x 轴上标注以表明观测值（数据点）
plt.xlabel("观察结果")

# 在 y 轴上标注出各聚类之间的欧几里得距离
plt.ylabel('欧几里得距离')

# 显示树状图
plt.show()


# 使用散点图可视化聚类数据
# 每种颜色代表一个不同的类别。

# 将属于聚类 1 的所有点用黑色绘制出来
plt.scatter(df[df["labels"] == 0][0], df[df["labels"] == 0][1], 
            c='black', label='cluster 1')

# 将属于第 2 类的所有点都用绿色标出。
plt.scatter(df[df["labels"] == 1][0], df[df["labels"] == 1][1], 
            c='green', label='cluster 2')

# 将属于第 3 类的所有点都用红色标出。
plt.scatter(df[df["labels"] == 2][0], df[df["labels"] == 2][1], 
            c='red', label='cluster 3')

# 将属于第 4 类的所有点用洋红色绘制出来
plt.scatter(df[df["labels"] == 3][0], df[df["labels"] == 3][1], 
            c='magenta', label='cluster 4')

# 将属于第 5 类的所有点都用紫色标出。
plt.scatter(df[df["labels"] == 4][0], df[df["labels"] == 4][1], 
            c='purple', label='cluster 5')

# 将属于第 6 类的所有点用黄色标出
plt.scatter(df[df["labels"] == 5][0], df[df["labels"] == 5][1], 
            c='y', label='cluster 6')

# 将属于第 7 类的所有点用黑色标出
plt.scatter(df[df["labels"] == 6][0], df[df["labels"] == 6][1], 
            c='black', label='cluster 7')

# 在图中显示图例以标注每个聚类
plt.legend()

# 将代表特征 1（第一维度）的 x 轴标注出来
plt.xlabel('X')

# 将代表特征 2（第二维度）的 y 轴标注出来
plt.ylabel('Y')

# 设置散点图的标题
plt.title('层级聚类')

# 显示聚类散点图
plt.show()
</code></pre>
<p><a href="https://lunartech.ai"><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1738529338003/d04605b0-8c9e-46d9-8aac-0f62dc0a67d3.png" alt="一段用于在 Python 中可视化层次聚类的代码片段。它包括生成树状图以及创建散点图来表示聚类，每个聚类用不同的颜色表示。X 轴和 Y 轴都有标签，并且设置了图的标题以增强清晰度。该代码使用了 Matplotlib 库中的 、 、 和 等函数。 - lunartech.ai" width="1682" height="2680" loading="lazy"></a></p>
<p>以下是在 Python 中可视化层次聚类的分步指南：</p>
<p><strong>步骤 1：预处理数据</strong></p>
<p>在进行层次聚类的可视化之前，对数据进行预处理（如缩放或标准化）是很重要的。这能确保所有特征具有相似的范围，并防止对特定特征产生任何偏见。</p>
<p><strong>步骤 2: 执行层次聚类</strong></p>
<p>接下来，我们使用选定的算法，例如 Scikit-learn 中的 AgglomerativeClustering 执行层次聚类。该算法计算数据点之间的相似度，并根据特定的链接准则将它们合并为簇。</p>
<p><strong>步骤 3: 创建一个树状图</strong></p>
<p>我们可以使用 SciPy 库中的树状图函数来创建这种可视化效果。树状图能够让我们直观地看到各个聚类之间的距离和关系。</p>
<p><strong>步骤 4: 绘制聚类图</strong></p>
<p>最后，我们可以使用散点图或其他合适的可视化技术来绘制聚类。这有助于我们直观地看到每个聚类中的数据点，并深入了解每个聚类的特征。</p>
<p><a href="https://www.lunartech.ai/bootcamp/ai-engineering-bootcamp"><img src="https://miro.medium.com/v2/resize:fit:1400/1*wIrFoLxUBv-8Y_cuskgukQ.png" alt="一张展示观测值层次聚类的树状图，采用欧几里得距离。图表用蓝色、绿色和橙色标注了聚类编号和分支。 - lunartech.ai" width="839" height="684" loading="lazy"></a></p>
<p>此树状图随后可帮助我们确定更优的聚类数量。如您所见，在这种情况下，我们似乎应该使用 7 个聚类。</p>
<p><a href="https://www.lunartech.ai/bootcamp/ai-engineering-bootcamp"><img src="https://miro.medium.com/v2/resize:fit:1280/1*WBByBnOzYgVVhTvTc-d7PA.png" alt="标题为“层次聚类”的散点图，沿 X 轴显示七个垂直的聚类。每个聚类都有不同的颜色，并配有图例，标明聚类编号及对应颜色。- lunartech.ai" width="640" height="480" loading="lazy"></a></p>
<p>通过在 Python 中对层次聚类进行可视化，我们可以更好地理解数据内部的结构和关系。这种可视化技术在处理复杂数据集时特别有用，并且有助于决策过程和模式发现。</p>
<p>请记得根据您的数据集和目标调整具体的参数和设置。尝试不同的可视化方法和技术能够让您对数据有更深入的了解。</p>
<h2 id="dbscan"><strong>DBSCAN 聚类理论</strong></h2>
<p>DBSCAN (基于密度的空间聚类算法，用于处理噪声数据)是一种用于聚类分析的无监督学习算法。它特别擅长识别任意形状的聚类，并能处理噪声数据。</p>
<p>与 K-均值 或层次聚类不同，DBSCAN 不需要预先指定聚类的数量。相反，它根据数据中的密度和连通性来定义聚类。</p>
<h3 id="dbscan"><strong>DBSCAN 工作原理:</strong></h3>
<p><strong>基于密度的聚类</strong>: DBSCAN 会将彼此距离较近且具有足够数量近邻的数据点归为一组。它将数据点密集的区域识别为聚类，并将稀疏区域视为噪声。</p>
<p><strong>核心点、边界点和噪声点</strong>: DBSCAN 将数据点分为三种类型：核心点、边界点和噪声点。</p>
<ul>
<li>
<p>核心要点: 在指定距离（由 <code>eps</code> 参数定义）内具有最少数量邻近点（由 <code>min_samples</code> 参数定义）的数据点。</p>
</li>
<li>
<p>边界点: 位于核心点的 <code>eps</code> 距离内但相邻点数量不足，而不能被视为核心点的数据点。</p>
</li>
<li>
<p>噪声点：既非核心点也非边界点的数据点。</p>
</li>
</ul>
<p><strong>可达性与连通性</strong>:  DBSCAN 通过可达性和连通性的概念来定义簇。如果一个数据点可以通过一条由核心点（Core Points）连接的路径到达另一个数据点，则认为该数据点是可达的（reachable）。如果两个数据点是可达的，它们属于同一个簇。</p>
<p><strong>簇扩展</strong>: DBSCAN 从一个任意数据点开始，通过检查其邻居及其邻居的邻居，不断扩展簇，从而形成一个连通的数据点群。</p>
<h3 id="dbscan">** DBSCAN 聚类的核心优势:**</h3>
<ul>
<li>
<p><strong>检测复杂结构的能力</strong>: DBSCAN 能够发现各种形状和大小的簇，使其非常适合处理具有非线性关系或不规则模式的数据集。</p>
</li>
<li>
<p><strong>抗噪声能力强</strong>: DBSCAN 能够有效地处理含噪声的数据，通过将噪声点与聚类点区分开来。</p>
</li>
<li>
<p><strong>自动确定聚类数量</strong>: DBSCAN 算法无需预先指定聚类的数量，这使其更方便且能更好地适应不同的数据集。</p>
</li>
<li>
<p><strong>适用于大规模数据集</strong>: 与某些其他聚类算法相比，DBSCAN 的时间复杂度相对较低，这使其能够很好地扩展到大规模数据集。</p>
</li>
</ul>
<p>在下一节中，我们将深入探讨如何在 Python 中实现 DBSCAN 算法，并提供分步指导和示例。</p>
<h3 id="dbscanpython"><strong>DBSCAN 聚类: Python 的实现方法</strong></h3>
<p>在本节中，我将指导您如何使用 Python 实现 DBSCAN。</p>
<h4 id="dbscan">DBSCAN 聚类的关键步骤</h4>
<ol>
<li>
<p><strong>数据预处理:</strong> 在应用 DBSCAN 之前，对数据进行预处理非常重要。这包括处理缺失值、对特征进行标准化以及选择合适的距离度量。</p>
</li>
<li>
<p><strong>定义参数:</strong> DBSCAN 需要两个主要参数 epsilon (eps) 和最小点数 (MinPts). Epsilon 决定了将两点视为邻点的最大距离，而 MinPts 则指定了形成密集区域所需的最少点数。</p>
</li>
<li>
<p><strong>执行基于密度的聚类:</strong> DBSCAN 算法首先随机选取一个数据点，并找出其在指定的 epsilon 距离内的邻点。如果邻点数量超过 MinPts 阈值，则形成一个新的聚类。该算法通过迭代添加新点来扩展这个聚类，直到无法再添加新的点为止。</p>
</li>
<li>
<p><strong>执行噪声检测:</strong> 不属于任何聚类的点被视为噪声或异常值。这些点未被分配到任何聚类中，对于识别数据中的异常情况至关重要。</p>
</li>
</ol>
<p>要在 Python 中执行 DBSCAN 聚类，我们可以使用 scikit-learn 库。首先，需要导入所需的库并加载要聚类的数据集。然后，可以创建 DBSCAN 类的一个实例，并设置 epsilon (eps) 和最小样本数 (min_samples) 参数。</p>
<p>以下是一个示例代码片段，可帮助您入门：</p>
<pre><code>import numpy as np
import matplotlib.pyplot as plt
from sklearn.datasets import make_moons
from sklearn.cluster import DBSCAN

# 生成示例数据
X, _ = make_moons(n_samples=500, noise=0.05, random_state=0)

# 应用 DBSCAN 聚类
db = DBSCAN(eps=0.3, min_samples=5, metric='euclidean')
y_db = db.fit_predict(X)
</code></pre>
<p><a href="https://lunartech.ai"><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1738529451227/4b01ac7c-a9f9-4666-8fe5-e457a18ad160.png" alt="紫色背景上的代码片段展示了使用 DBSCAN 聚类算法的过程。代码导入了 NumPy 和 Matplotlib 等库，生成示例数据，并使用指定参数应用 DBSCAN 进行聚类。- lunartech.ai" width="1312" height="782" loading="lazy"></a></p>
<p>请记得将 <code>X</code> 替换为您的实际数据集。您可以调整 <code>eps</code> 和 <code>min_samples</code> 参数以获得不同的聚类结果。<code>eps</code> 参数是将一个样本视为另一个样本邻域内的最大距离。<code>min_samples</code> 是将一个点视为核心点的邻域中的样本数或总权重。</p>
<p>DBSCAN 相较于其他聚类算法具有诸多优势，比如无需预先设定聚类的数量。这使得它适用于聚类数量未知的数据集。DBSCAN 还能够识别出形状和大小各异的聚类，使其在捕捉复杂结构方面更具灵活性。</p>
<p>但是 DBSCAN 在处理数据集中的不同密度时可能会遇到困难，并且对 epsilon (eps)和最小点数参数的选择较为敏感。要获得最佳的聚类结果，必须对这些参数进行精细调整。</p>
<p>通过在 Python 中实现 DBSCAN，您可以利用这一强大的聚类算法来发现数据中具有意义的模式和结构。</p>
<p>在我们探讨 DBSCAN 与其他聚类技术之间的差异之前，让我们更仔细地研究一下影响 DBSCAN 性能和结果的关键参数。</p>
<h3 id="dbscan">理解 DBSCAN 的关键参数</h3>
<p><strong>eps</strong> (epsilon) 参数定义了两个数据点之间的最大距离，使其中一个点被视为另一个点的邻居。这意味着，在核心点 eps 半径范围内的所有点都属于同一个簇。选择合适的 eps 值至关重要，因为 eps 过小可能会导致簇数量过多且规模较小，而 eps 过大可能会将原本独立的簇合并为一个簇。</p>
<p><strong>min_samples</strong> 参数决定形成高密度区域所需的最小数据点数. 如果一个点在 eps  半径范围内至少有 min_samples 个邻居，则被分类为 <strong>核心点</strong> 。如果一个点落在某个核心点的 eps 半径内，但自身不满足 min_samples 要求，则被分类为 <strong>边界点</strong>. 任何既不是核心点也不是边界点的数据点都会被标记为噪声或异常值。</p>
<h3 id="dbscan">DBSCAN 如何对数据点进行分组</h3>
<p>DBSCAN 通过识别核心点并围绕它们扩展簇来运行。它根据密度将紧密分布的点（即簇）归为一组，并将低密度点标记为异常值（或噪声）。其过程如下：</p>
<ol>
<li>
<p><strong>选择一个未访问的数据点</strong> 并检查其 <code>eps</code> 半径范围内是否至少有 <code>min_samples</code> 个邻居。</p>
</li>
<li>
<p>如果满足条件，该点被标记为核心点，并以其为中心形成一个新的簇。</p>
</li>
<li>
<p><strong>扩展簇</strong> 将所有<code>eps</code>范围内直接可达的点加入簇中。如果这些点也是核心点，则将其邻居点一并加入。</p>
</li>
<li>
<p><strong>继续扩展</strong> 直到没有更多数据点满足密度准则。</p>
</li>
<li>
<p><strong>移动到下一个未访问的数据点</strong> ，并重复该过程。</p>
</li>
<li>
<p><strong>对剩余数据点进行分类。</strong> 将剩余数据点分类为边界点（属于某个簇但不是核心点）或噪声点（不属于任何簇的异常值）。</p>
</li>
</ol>
<h3 id="dbscan">DBSCAN 示例实现</h3>
<p>在该实现中:</p>
<ul>
<li>
<p><code>eps=0.3</code>: 定义数据点之间的最大距离，决定它们是否被视为邻居。</p>
</li>
<li>
<p><code>min_samples=5</code>: 设置形成高密度区域所需的最小数据点数</p>
</li>
<li>
<p><code>fit_predict(X)</code>: 为每个数据点分配一个聚类标签。</p>
</li>
</ul>
<p>应用 DBSCAN 算法后，数据点会被分配标签。如果两个点属于同一个聚类，它们在 <code>y_db</code>中将具有相同的标签。 被识别为离群点的数据点将被标记为<code>-1</code> 并且不被聚类。</p>
<p>生成的散点图直观地展示了 DBSCAN 如何识别出两个新月形的簇。与假定簇为球形的 K-均值不同，DBSCAN 能够有效地检测出任意形状的簇。</p>
<pre><code>plt.scatter(X[y_db == 0, 0], X[y_db == 0, 1],
            c='lightblue', marker='o', s=40,
            edgecolor='black', 
            label='cluster 1')
plt.scatter(X[y_db == 1, 0], X[y_db == 1, 1],
            c='red', marker='s', s=40,
            edgecolor='black', 
            label='cluster 2')
plt.legend()
plt.show()
</code></pre>
<p><a href="https://lunartech.ai"><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1738529515628/a5c2861e-1263-4cad-84f2-9e026261942f.png" alt="这是使用 Matplotlib 绘制散点图的 Python 代码截图。该代码定义了两个具有不同颜色和标记的簇，添加了图例，并显示了图形。- lunartech.ai" width="1058" height="744" loading="lazy"></a></p>
<p><a href="https://www.lunartech.ai/bootcamp/ai-engineering-bootcamp"><img src="https://miro.medium.com/v2/resize:fit:1400/1*ymoTCnR3H-WBs8ShoTrYNg.png" alt="散点图显示两个聚类：簇 1 绿色圆点在上方形成一条曲线, 簇 2 红色方块在下方形成一条曲线。图片来源: The Author" width="1054" height="637" loading="lazy"></a></p>
<p>生成的图表将显示两个呈月牙状的簇，分别以绿色和红色呈现，这表明 DBSCAN 成功地识别并分离了这两个相互交织的半圆。</p>
<p><a href="https://www.lunartech.ai/bootcamp/ai-engineering-bootcamp"><img src="https://substackcdn.com/image/fetch/f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F9a97d1f6-3c00-4493-b430-1d8e3cb8d270_3327x1350.png" alt="该插图展示了未标注的数据点在两个不同图表中被聚类成两组。其中一个简笔人物问道 &quot;没有标签，如何评估这些呢?&quot; - lunartech.ai" width="3327" height="1350" loading="lazy"></a></p>
<h2 id=""><strong>如何评估聚类算法的性能</strong></h2>
<p>评估聚类模型的性能可能颇具挑战性，因为在无监督学习中没有可用的真实标签。不过，有几种评估指标可以为聚类结果的质量提供见解。</p>
<ul>
<li>
<p><strong>轮廓系数</strong>: 衡量每个数据点与其所属聚类的契合程度，同时与其他聚类进行比较。轮廓系数越高，表明聚类效果越好。</p>
</li>
<li>
<p><strong>戴维森堡丁指数:</strong> 该指标衡量每个聚类与其最相似聚类之间的平均相似度，同时考虑聚类之间的分离度。值越低表示聚类效果越好。</p>
</li>
<li>
<p><strong>方差比准则:</strong> 评估簇间离散度与簇内离散度的比值，较高的值表示聚类效果更清晰。</p>
</li>
<li>
<p><strong>可视化评估</strong>: 检查聚类结果的可视化表示，例如散点图或树状图，也能为聚类的质量和意义提供有价值的见解。</p>
</li>
</ul>
<p>我建议您结合使用评估指标和视觉评估，以全面评估聚类模型的性能。</p>
<h2 id="kdbscan"><strong>K 均值聚类、层次聚类和 DBSCAN 之间的区别</strong></h2>
<p>K 均值、层次聚类和 DBSCAN 是三种广泛使用的聚类算法，每种算法都有其独特的数据分组方法。理解它们之间的差异对于根据数据特征和分析目标选择最合适的算法至关重要。</p>
<h3 id="k"><strong>K-均值聚类</strong></h3>
<p>K 均值聚类是一种基于质心的算法，它根据相似性将数据划分为 K 个簇。该算法首先随机初始化 K 个质心，然后迭代地将每个数据点分配给最近的质心。一旦所有数据点都被分配，就根据每个簇内点的均值重新计算质心。这个过程会一直持续到达到收敛为止。</p>
<h4 id="k"><strong>K-均值聚类的核心优势:</strong></h4>
<ul>
<li>
<p>对于大型数据集而言，高效且可扩展。</p>
</li>
<li>
<p>当聚类呈球形且分布均匀时效果良好。</p>
</li>
<li>
<p>与层次聚类相比，计算速度更快。</p>
</li>
<li>
<p>易于实施和理解。</p>
</li>
</ul>
<h4 id="k"><strong>K-均值聚类的局限性:</strong></h4>
<ul>
<li>
<p>需要提前指定聚类（K）的数量。</p>
</li>
<li>
<p>对初始质心位置敏感，导致结果不同。</p>
</li>
<li>
<p>假定各聚类大小相等且呈球形，但实际情况并非总是如此。</p>
</li>
<li>
<p>在处理异常值和非线性形状的聚类时遇到的难题。</p>
</li>
</ul>
<h3 id=""><strong>层次聚类</strong></h3>
<p>层次聚类创建了一个嵌套的聚类层次结构，无需预先定义聚类的数量。它从将每个数据点视为单独的聚类开始，然后根据相似性逐步合并或拆分聚类。其结果通常用树状图来可视化，这有助于确定最佳的聚类数量。</p>
<h4 id=""><strong>层次聚类的核心优势:</strong></h4>
<ul>
<li>
<p><strong>无需</strong> 预先指定聚类数量。</p>
</li>
<li>
<p>捕捉集群之间的层级关系。</p>
</li>
<li>
<p>能够处理不同类型的数据，包括数值型和分类型。</p>
</li>
<li>
<p>对于探索性分析很有用，带有树状图以提高可解释性。</p>
</li>
</ul>
<h4 id=""><strong>层次聚类的局限性:</strong></h4>
<ul>
<li>
<p>计算成本高，在大规模数据集上效率低（O(n²)时间复杂度）。</p>
</li>
<li>
<p>由于内存限制，难以扩展到大规模数据集。</p>
</li>
<li>
<p>确定树状图的合适截断点较具挑战性。</p>
</li>
<li>
<p>对噪声和异常值敏感，可能导致层次结构失真。</p>
</li>
</ul>
<h3 id="dbscan"><strong>DBSCAN（基于密度的应用空间聚类算法）</strong></h3>
<p>DBSCAN 是一种基于密度的聚类算法，它根据数据点之间的接近程度和密度来对数据点进行分组，而不是基于预先定义的聚类。与 K-Means 和层次聚类不同，DBSCAN 不需要指定聚类的数量。相反，它使用两个关键参数：eps (两个点被视为邻居的最大距离) 和 min_samples (形成高密度簇所需的最小点数)。不满足这些条件的点会被标记为噪声。</p>
<h4 id="dbscan"><strong>DBSCAN 的核心优势:</strong></h4>
<ul>
<li>
<p>无需预先指定聚类的数量。</p>
</li>
<li>
<p>能够检测任意形状的簇，这与假定簇为球形的 K-均值 算法不同。</p>
</li>
<li>
<p>能够有效地处理异常值，将其标记为噪声，而不是强行将其归入某个聚类。</p>
</li>
<li>
<p>适用于密度不均或具有非线性结构的数据集，在复杂数据分布下表现良好。</p>
</li>
</ul>
<h4 id="dbscan"><strong>DBSCAN 的局限性:</strong></h4>
<ul>
<li>
<p>难以处理不同密度的聚类，因为单一的 eps 值可能无法适用于所有簇。</p>
</li>
<li>
<p>对参数调整敏感 (eps 和 min_samples)，参数选择会影响聚类性能。</p>
</li>
<li>
<p>不适用于高维数据，因为在高维空间中，欧几里得距离的区分度会降低。</p>
</li>
<li>
<p>在超大规模数据集上可能表现欠佳，尽管其可扩展性优于层次聚类。</p>
</li>
</ul>
<h3 id=""><strong>选择合适的聚类算法</strong></h3>
<table>
<thead>
<tr>
<th>特征</th>
<th>K-均值</th>
<th>层次聚类</th>
<th>DBSCAN</th>
</tr>
</thead>
<tbody>
<tr>
<td><strong>簇形</strong></td>
<td>假设聚类为球形</td>
<td>适用于层次结构</td>
<td>适用于任意形状的聚类</td>
</tr>
<tr>
<td><strong>可扩展性</strong></td>
<td>高度可扩展（适用于大规模数据集，计算速度快）</td>
<td>不具备良好扩展性 (时间复杂度 O(n²))</td>
<td>度可扩展（处理超大规模数据集可能存在挑战）</td>
</tr>
<tr>
<td><strong>簇的数量</strong></td>
<td>必须预定义</td>
<td>无需指定</td>
<td>无需指定</td>
</tr>
<tr>
<td><strong>处理异常值</strong></td>
<td>较差</td>
<td>对噪声敏感</td>
<td>良好，可检测异常值作为噪声</td>
</tr>
<tr>
<td><strong>计算复杂度</strong></td>
<td>O(n) 至 O(n log n)</td>
<td>O(n²)</td>
<td>O(n log n)</td>
</tr>
<tr>
<td><strong>可解释性</strong></td>
<td>结果输出易于理解</td>
<td>树状图提供良好的可视化解析</td>
<td>直观性较低，需要调整参数</td>
</tr>
</tbody>
</table>
<p>每种聚类算法都有其优缺点。 <strong>K-均值</strong> 在处理大型数据集以及簇呈球形且彼此分离时效果最佳。<strong>层次聚类</strong> 在存在层次关系或簇的数量未知时很有用。<strong>DBSCAN</strong> 在检测任意形状的簇和处理噪声方面表现出色，但需要仔细调整参数。</p>
<p>通过了解每种算法的特点，您可以做出明智的决定，选择最适合您数据分析需求的聚类方法。</p>
<p><a href="https://www.lunartech.ai/bootcamp/ai-engineering-bootcamp"><img src="https://miro.medium.com/v2/resize:fit:1400/1*HpMauXQZe0ByFFSHs4wNLw.png" alt="-SNE 可视化图，困惑度为 50，展示了数据点的聚类情况。标注的聚类突出了不同的年份、评分以及电影类型，如爱情、惊悚、动作和冒险。- lunartech.ai" width="1400" height="1103" loading="lazy"></a></p>
<h2 id="tsnepython"><strong>如何使用 t-SNE 在 Python 中可视化聚类</strong></h2>
<p>在应用 K-Means、层次聚类和 DBSCAN 等聚类算法后，通常需要对聚类结果进行可视化，以更好地理解数据的内在结构。</p>
<p>而对于二维或三维数据集，散点图可以很好地展示聚类结果。然而，在实际应用中，数据通常具有高维特征，直接可视化是较为困难的。</p>
<p>为应对这一挑战，您可以使用诸如<strong>t-SNE</strong> (t-Distributed Stochastic Neighbor Embedding) 之类的降维技术将高维数据投影到低维空间，同时保留其结构。这使您能够更有效地可视化聚类，并识别出在原始数据中可能不那么明显的隐藏模式。</p>
<p>在本节中，我们将探讨 t-SNE 的理论基础及其在 Python 中的实现。</p>
<h3 id="tsne"><strong>深入解析 t-SNE</strong></h3>
<p>t-SNE 由 Laurens van der Maaten 和 Geoffrey Hinton 于 2008 年提出，作为一种用于可视化复杂数据结构的方法。其目标是在低维空间中表示高维数据点，同时保留数据点之间的局部结构和成对相似性。</p>
<p>t-SNE 通过在高维空间和低维空间中建模数据点之间的相似性来实现这一目标。</p>
<h3 id="tsne"><strong>t-SNE 算法</strong></h3>
<p>t-SNE 算法的执行步骤如下：</p>
<ol>
<li>
<p>计算高维空间中数据点的两两相似度。通常使用高斯核函数（Gaussian kernel），基于欧几里得距离（ Euclidean distances）来衡量数据点之间的相似性。</p>
</li>
<li>
<p>随机初始化低维嵌入。</p>
</li>
<li>
<p>定义一个成本函数，用于衡量高维空间与低维空间中数据点相似性的匹配程度。</p>
</li>
<li>
<p>使用梯度下降优化成本函数，以最小化高维空间与低维空间相似性之间的差异。</p>
</li>
<li>
<p>重复执行步骤 3 和 4，直至成本函数收敛。</p>
</li>
</ol>
<p>借助 scikit-learn 等库，用 Python 实现 t-SNE 相对简单。scikit-learn 库为将 t-SNE 应用于您的数据提供了用户友好的 API。通过遵循 scikit-learn 的文档和示例，您可以轻松地将 t-SNE 集成到您的机器学习流程中。</p>
<h3 id="tsne"><strong>二维 t-SNE 可视化</strong></h3>
<pre><code>import matplotlib.pyplot as plt
from sklearn import datasets
from sklearn.manifold import TSNE

# 加载数据集
digits = datasets.load_digits()
X, y = digits.data, digits.target

# 应用 t-SNE 算法
tsne = TSNE(n_components=2, random_state=0)
X_tsne = tsne.fit_transform(X)

# 在二维平面上可视化结果
plt.figure(figsize=(10, 6))
scatter = plt.scatter(X_tsne[:, 0], X_tsne[:, 1], c=y, edgecolor='none', alpha=0.7, cmap=plt.cm.get_cmap('jet', 10))
plt.colorbar(scatter)
plt.title("数字数据集的 t-SNE 可视化")
plt.show()
</code></pre>
<p><a href="https://lunartech.ai"><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1738529609503/e4a5dac2-0c31-4e9c-b8cd-9d243736ee67.png" alt="使用 Matplotlib 和 scikit-learn 可视化数字数据集 t-SNE 转换的 Python 代码片段。该代码加载数据集，应用 t-SNE，并在二维平面上绘制结果。- lunartech.ai" width="2048" height="1080" loading="lazy"></a></p>
<p><a href="https://www.lunartech.ai/bootcamp/ai-engineering-bootcamp"><img src="https://miro.medium.com/v2/resize:fit:1400/1*vFccfsJFgXl3rulHs93MKA.png" alt="这个散点图展示了 Digits 数据集的 t-SNE 可视化结果。不同颜色的点群代表不同的数字，颜色从深红色到浅蓝色渐变，分别对应数字 0 到 9。右侧的颜色条显示了每种颜色所代表的数字。- lunartech.ai" width="1000" height="600" loading="lazy"></a></p>
<p>在这个例子中：</p>
<ol>
<li>
<p>我们将加载 <code>digits</code> 数据集.</p>
</li>
<li>
<p>我们应用 t-SNE 将数据从 64 维（因为每张图像为 8×8）降维至 2 维。</p>
</li>
<li>
<p>然后我们绘制转换后的数据，根据每个点的真实数字标签为其着色。</p>
</li>
</ol>
<p>生成的可视化结果将展示出若干簇，每个簇对应一个数字（从 0 到 9）。这有助于了解不同数字在原始高维空间中的分离程度。</p>
<h3 id=""><strong>高维数据可视化</strong></h3>
<p>t-SNE 的主要优势之一在于其能够将高维数据在低维空间中进行可视化。通过降低数据的维度，t-SNE 使我们能够识别出在原始高维空间中可能不明显的聚类和模式。由此产生的可视化结果能够为数据的结构提供有价值的见解，从而有助于人们的决策过程。</p>
<pre><code>import matplotlib.pyplot as plt
from sklearn import datasets
from sklearn.manifold import TSNE
from mpl_toolkits.mplot3d import Axes3D

# 加载数据集
digits = datasets.load_digits()
X, y = digits.data, digits.target

# 应用 t-SNE 算法
tsne = TSNE(n_components=3, random_state=0)
X_tsne = tsne.fit_transform(X)

# 在三维平面上实现可视化结果
fig = plt.figure(figsize=(10, 8))
ax = fig.add_subplot(111, projection='3d')
scatter = ax.scatter(X_tsne[:, 0], X_tsne[:, 1], X_tsne[:, 2], c=y, edgecolor='none', alpha=0.7, cmap=plt.cm.get_cmap('jet', 10))
plt.colorbar(scatter)
plt.title("数字数据集的三维 t-SNE 分析图")
plt.show()
</code></pre>
<p><a href="https://lunartech.ai"><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1738529676545/772f6b94-655b-4ae3-bdb5-a5334442c970.png" alt="这是一个使用 Python 编写的代码片段，其中用到了 matplotlib、sklearn 和 mpl_toolkits.mplot3d 等库。它加载了数字数据集，应用 t-SNE 进行降维，并在三维平面上实现可视化结果。 - lunartech.ai" width="2048" height="1154" loading="lazy"></a></p>
<p>在这段修订后的代码中：</p>
<ol>
<li>
<p>我们将 t-SNE 的<code>n_components=3</code> 设置为 3，以获得三维变换。</p>
</li>
<li>
<p>我们使用 <code>mpl_toolkits.mplot3d.Axes3D</code> 来创建一个三维散点图。</p>
</li>
</ol>
<p>执行此代码后，您将看到一个 3D 散点图，其中的点根据其 t-SNE 坐标进行定位，并根据其真实的数字标签进行着色。</p>
<p>而旋转三维可视化图像，会有助于我们更好地理解数据点的空间分布情况。</p>
<p><a href="https://www.lunartech.ai/bootcamp/ai-engineering-bootcamp"><img src="https://miro.medium.com/v2/resize:fit:1400/1*aw8wAIvC2CXwXO7Ixjy1JQ.png" alt="用于数字数据集的 t-SNE 投影的 3D 散点图。数据点呈簇状分布，不同颜色代表不同的数字。右侧的颜色条表示从 0 到 9 的数值。" width="844" height="692" loading="lazy"></a></p>
<p>t-SNE 是一种强大的降维和高维数据可视化工具。通过利用其功能，您可以更深入地了解复杂的数据集，并发现那些并非一目了然的隐藏模式。而凭借其 Python 实现方法和易用性，t-SNE 对于任何数据科学家或机器学习从业者来说都是一项宝贵的资源。</p>
<figure class="kg-card kg-card-image kg-card-hascaption">
    <img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1741790800643/da4e7d4f-4030-4b8a-9dc1-d8cb669a4bbb.gif" alt="散点矩阵图展示了鸢尾花属植物中的三种物种（山鸢尾（蓝色）、变色鸢尾（红色）和维吉尼亚鸢尾（绿色））的花萼宽度、花萼长度、花瓣宽度和花瓣长度之间的关系。- lunartech.ai" class="kg-image" width="1014" height="596" loading="lazy">
    <figcaption></figcaption>
</figure>
<h2 id=""><strong>有关更多的无监督学习技术</strong></h2>
<p>除了我们在此讨论的聚类技术之外，还有一些重要的无监督学习技术值得探索。虽然我们在此不会进行详细的探讨，但让我们简要提及其中两种技术：混合模型和主题建模。</p>
<h3 id=""><strong>混合模型</strong></h3>
<p>混合模型是一类概率模型，用于建模复杂的数据分布。它假设整体数据集可以表示为多个潜在子群或成分的组合，每个成分由其自身的概率分布描述。</p>
<p>混合模型在数据点不属于明确的独立聚类且可能存在重叠特征的情况下特别有用。</p>
<h3 id=""><strong>主题建模</strong></h3>
<p>主题建模是一种用于从文档集合中提取潜在主题的方法，它可以帮助探索和发现文本数据中的隐含语义模式。</p>
<p>通过分析单词在不同文档中的共现情况并识别常见主题，主题建模能够实现大规模文本数据的自动分类和摘要生成。该技术广泛应用于自然语言处理(NLP)、信息检索和内容推荐系统等领域。</p>
<p>尽管这些技术的深入研究超出了本手册的范围，但是值得进一步探索，因为它们是很有价值的工具，从而有助于挖掘和洞察隐藏的模式从您的数据中，从而帮助您获得见解。</p>
<p>请一定要牢记，掌握无监督学习是需要持续的学习和实践。在熟悉上述不同技术后，您将能够更好地应对各种数据分析挑战，并在多个领域内灵活运用这些方法。</p>
<h2 id=""><strong>常见问题解答</strong></h2>
<h3 id=""><strong>小问答: 请问监督学习和无监督学习之间的区别是什么？</strong></h3>
<p>首先，监督学习会是在带有标签的数据上训练模型，其中输入与对应的输出配对，目标是预测新数据的输出结果。</p>
<p>而相比之下，无监督学习处理的是无标签数据，旨在发现数据中的模式、结构或聚类，而无需来预定义其输出结果。</p>
<p>所以在本质上，监督学习的目标是学习一个映射函数，而无监督学习则侧重于发现数据中的隐藏关系或分组结构。</p>
<h3 id=""><strong>小问答: 如何知道哪种聚类算法最适合我的数据类型?</strong></h3>
<p>聚类算法的适用性取决于多种因素，例如数据的性质、期望的聚类数量以及您试图解决的具体问题。</p>
<p>在本手册中，我们将讨论三种常用的聚类算法:</p>
<ul>
<li>
<p><strong>K-均值</strong> 是一种常用的聚类算法，旨在将数据划分为 K 个簇，并将每个数据点分配给最临近的质心。该算法适用于分布均匀、接近球形的聚类，但需要预先指定簇的数量。</p>
</li>
<li>
<p><strong>层次聚类</strong> 通过迭代合并或拆分数据点来构建聚类层次结构。它提供树状图来可视化聚类过程，并能够处理不同形状和规模的聚类。</p>
</li>
<li>
<p><strong>DBSCAN</strong> 是一种基于密度的聚类算法，它将相互接近的数据点归为一类，并识别并分离异常值。该算法能够发现任意形状的聚类，并且无需预先指定聚类数量。</p>
</li>
</ul>
<p>要确定最适合您用例的算法，我建议您尝试不同的技术，并根据诸如聚类质量、计算效率和可解释性等指标来评估其性能。</p>
<h3 id=""><strong>小问答: 请问无监督学习可以用于预测分析吗?</strong></h3>
<p>尽管无监督学习主要关注在没有特定输出标签的情况下发现数据中的模式和关系，但它也可以间接支持预测分析。通过来揭示数据中的隐藏结构和聚类，无监督学习能够提供有价值的洞察，从而优化特征工程、异常检测或数据分群，这些都能进一步提升预测模型的性能。</p>
<p>最明显的一个例子是，聚类等无监督学习技术可以帮助识别数据中的不同群组或模式，这些信息可作为预测模型的输入特征，或者用于生成新的预测变量。因此，无监督学习在预测分析中发挥着重要作用，它能够加深对数据的理解，并提升预测模型的准确性和有效性。</p>
<h2 id=""><strong>数据科学与人工智能资源</strong></h2>
<p>想要更详细的了解有关数据科学、机器学习和人工智能方面的职业，或者学习如何获得一份数据科学的工作吗？您可以下载这本 <a href="https://downloads.tatevaslanyan.com/six-figure-data-science-ebook">免费的数据科学与人工智能职业手册</a>.</p>
<p>想从零开始学习机器学习，或者想巩固已有知识吗? 下载这本 <a href="https://www.freecodecamp.org/news/machine-learning-handbook/">免费的机器学习基础手册</a> 一次性获取所有与 Python 示例相结合的机器学习基础知识。</p>
<h2 id=""><strong>关于作者</strong></h2>
<p><a href="https://www.linkedin.com/in/tatev-karen-aslanyan/"><strong>Tatev Aslanyan</strong></a> 是一位高级机器学习和人工智能工程师，以及 <a href="https://www.lunartech.ai/"><strong>LunarTech</strong></a>的首席执行官兼联合创始人。其领导的 LunarTech 是一家致力于让数据科学和人工智能在全球普及的深度技术创新初创公司。Tatev 在人工智能工程和数据科学领域拥有超过 6 年的工作经验，曾在美国、英国、加拿大和荷兰工作过，她将自己的专业知识应用于推进不同行业的人工智能解决方案。</p>
<p>Tatev 本人不仅拥有<a href="https://www.lunartech.ai/">顶尖荷兰大学</a>的[硕士]计量经济学和[本科]运筹学学位<a href="https://www.linkedin.com/in/tatev-karen-aslanyan/">51</a>,还曾发表了多篇科学学术文章有关于自然语言处理（NLP）、机器学习和推荐系统在美国权威的学术期刊上。</p>
<p>作为顶级开源项目的贡献者, Tatev 共同参与撰写了多门课程和书籍, 其中包括 <strong>2024 年的 freeCodeCamp</strong> 项目, 并且还在<a href="https://www.lunartech.ai/bootcamp/ai-engineering-bootcamp"><strong>LunarTech</strong> 项目</a>中发挥了关键作用，帮助了<strong>来自于 144 个国家的 30,000 多名学习者</strong>。</p>
<p><a href="https://www.lunartech.ai/">LunarTech</a> 作为一家深度技术创新公司，其不断研发人工智能驱动的产品，并同时提供教育工具来帮助企业和个人推动创新，从而实现让其能降低运营成本并提升盈利能力。</p>
<h2 id=""><strong>联系我们</strong></h2>
<ul>
<li>
<p><a href="https://www.linkedin.com/in/tatev-karen-aslanyan/">关注我的 LinkeIn 账户</a></p>
</li>
<li>
<p><a href="https://www.youtube.com/@LunarTech_ai">访问 YouTube 频道</a></p>
</li>
<li>
<p>订阅 <a href="https://substack.com/@lunartech"><strong>LunarTech Newsletter</strong></a> 或者 <a href="https://lens.lunartech.ai/"><strong>LENS</strong></a> - 我们的资讯频道。</p>
</li>
</ul>
<p>想要更全面了解数据科学、机器学习和人工智能的职业发展，并学习如何成功获得数据科学岗位吗？那就立即下载这本免费的数据科学与人工智能职业手册。</p>
<p>感谢您选择本手册作为您的学习伙伴。在您继续探索广阔的人工智能领域时，我希望您能够保持自信、精准思考，并怀揣创新精神！</p>
<h2 id="lunartech"><strong>由 LunarTech 打造的人工智能工程师训练营</strong></h2>
<p>如果您坚定地想成为一名人工智能工程师，并寻找一门既涵盖深层理论又注重实践的全方位训练营, 那么一定要看看专注于生成式人工智能的<a href="https://www.lunartech.ai/bootcamp/ai-engineering-bootcamp"><strong>LunarTech 人工智能工程师训练营</strong></a>. 这虽然不是在有关人工智能工程上最全面和最高级的项目, 但是该训练营将为您提供在最具竞争力的人工智能领域和行业中脱颖而出的全部技能和知识。</p>
<figure class="kg-card kg-embed-card" data-test-label="fitted">
        <div class="fluid-width-video-container">
          <div style="padding-top: 56.25%;" class="fluid-width-video-wrapper">
            <iframe width="560" height="315" src="https://www.youtube.com/embed/g6KQHEeZVQY" style="aspect-ratio: 16 / 9; width: 100%; height: auto;" title="YouTube video player" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share" referrerpolicy="strict-origin-when-cross-origin" allowfullscreen="" loading="lazy" name="fitvid0"></iframe>
          </div>
        </div>
      </figure>
<p>在 3 至 6 个月内，你可以选择自定进度或参与小组学习，掌握生成式人工智能及其基础模型，如 变分自编码器、生成式对抗网络、Transformers 和大型语言模型。深入学习数学、统计学、模型架构，以及使用 PyTorch 和 TensorFlow 等行业标准框架训练这些模型的技术细节。</p>
<p>该课程涵盖大型模型的预训练、微调、提示工程、量化和大模型优化，以及诸如检索增强生成（RAGs）等前沿技术。</p>
<p>本次训练营将帮助您弥合研究与实际应用之间的差距，赋能您能够设计出有影响力的解决方案, 并同时打造出包含前沿项目的优秀作品集。</p>
<p>该课程还同时考虑人工智能伦理，帮助您构建可持续并且合乎道德规范的人工智能模型，与负责任的人工智能原则相契合。这不单单是一门课程 —— 它还将是一段全面的旅程，旨在让您成为人工智能革命中的领军人物 <a href="https://www.lunartech.ai/bootcamp/ai-engineering-bootcamp">立即浏览课程详情</a>。</p>
<p>由于名额有限，我们对于人工智能工程师的需求比以往任何时候都更高。所以请不要犹豫，您的人工智能工程师之路，将从现在开始！您可以<a href="https://forms.fillout.com/t/frSHf9HUZCus">报名参加</a>。</p>
<blockquote>
<p><em>“让我们一起共创未来!“ - Tatev Aslanyan, LunarTech 的首席执行官兼联合创始人</em></p>
</blockquote>
<h2 id="tatevkarensubstack"><a href="https://tatevaslanyan.substack.com/?source=post_page-----f9fb36a94a05--------------------------------"><strong>数据科学与人工智能电子期刊 | Tatev Karen | Substack</strong></a></h2>
<p>如果想从零开始学习机器学习，或者想要巩固已有知识? 可下载<a href="https://join.lunartech.ai/machine-learning-fundamentals--3f64f"><strong>免费的机器学习基础手册</strong></a></p>
<p>如果还想全面了解数据科学、机器学习和人工智能行业，并学习如何进入数据科学领域？可下载<a href="https://downloads.tatevaslanyan.com/six-figure-data-science-ebook"><strong>免费的数据科学与人工智能职业手册</strong></a>。</p>
<p>感谢您选择本手册作为您的学习伙伴。在您继续探索广阔的机器学习领域时，希望您充满信心、严谨细致，并秉持创新精神。祝您未来一切顺利！</p>
<!--kg-card-end: markdown--> ]]>
                </content:encoded>
            </item>
        
            <item>
                <title>
                    <![CDATA[ 播客 Ep. 17 余琦：保持好奇心，在技术领域不断“刷油漆” ]]>
                </title>
                <description>
                    <![CDATA[ 我们的生活中可能都会有这样的朋友：在各种活动中他不是最引人关注的那一个，合影也很少站 C 位，喜欢自己探索，偶然发一个作品出来，令人感觉惊艳。 这期节目的嘉宾余琦给我的印象就是这样一位朋友。 余琦 他很早就对电子产品感兴趣，在中学时开始学习编程，大学时是电子竞技校队的成员。大学毕业后，他本来有一份在很多人眼里稳定的工作，因为对软件开发更感兴趣，他在学习和职业发展路上做更多探索，例如在 freeCodeCamp 学习前端开发、加入 K12 编程教育创业公司。现在他是 ThoughtWorks 的专家级咨询师，最近在研究 AI 新技术。 在节目中，他分享了自己的这些经历，以及对他很有启发的“油漆滴”模型。我们还聊到他组织文言文编程工作坊的时候，写的那篇让我觉得惊艳的文章。 如果他保持好奇心、透明做自己的状态也能给你一些启发和动力，请把这期节目分享给更多朋友。也欢迎你发邮件分享自己的故事，也许我们会邀请你作为 freeCodeCamp 播客的嘉宾。你可以在这篇文章 [https://www.freecodecamp.org/chinese/news/freecodecamp-podc ]]>
                </description>
                <link>https://www.freecodecamp.org/chinese/news/interview-yu-qi-stay-curious-and-keep-painting-in-the-tech-field/</link>
                <guid isPermaLink="false">654d083ae0668103ebf81a78</guid>
                
                    <category>
                        <![CDATA[ 播客 ]]>
                    </category>
                
                    <category>
                        <![CDATA[ AI ]]>
                    </category>
                
                    <category>
                        <![CDATA[ 前端开发 ]]>
                    </category>
                
                    <category>
                        <![CDATA[ 人工智能 ]]>
                    </category>
                
                <dc:creator>
                    <![CDATA[ Miya Liu ]]>
                </dc:creator>
                <pubDate>Thu, 09 Nov 2023 13:00:00 +0000</pubDate>
                <media:content url="https://chinese.freecodecamp.org/news/content/images/2023/11/Chinese-1.png" medium="image" />
                <content:encoded>
                    <![CDATA[ <p>我们的生活中可能都会有这样的朋友：在各种活动中他不是最引人关注的那一个，合影也很少站 C 位，喜欢自己探索，偶然发一个作品出来，令人感觉惊艳。</p><p>这期节目的嘉宾余琦给我的印象就是这样一位朋友。</p><figure class="kg-card kg-image-card kg-card-hascaption"><img src="https://chinese.freecodecamp.org/news/content/images/2023/11/yuqi.jpeg" class="kg-image" alt="yuqi" srcset="https://chinese.freecodecamp.org/news/content/images/size/w600/2023/11/yuqi.jpeg 600w, https://chinese.freecodecamp.org/news/content/images/size/w1000/2023/11/yuqi.jpeg 1000w, https://chinese.freecodecamp.org/news/content/images/size/w1600/2023/11/yuqi.jpeg 1600w, https://chinese.freecodecamp.org/news/content/images/2023/11/yuqi.jpeg 1776w" sizes="(min-width: 720px) 720px" width="1776" height="1184" loading="lazy"><figcaption>余琦</figcaption></figure><!--kg-card-begin: html--><iframe width="100%" height="180" frameborder="no" scrolling="no" seamless="" src="https://share.transistor.fm/e/200b830c" title="嵌入内容" loading="lazy"></iframe><!--kg-card-end: html--><p>他很早就对电子产品感兴趣，在中学时开始学习编程，大学时是电子竞技校队的成员。大学毕业后，他本来有一份在很多人眼里稳定的工作，因为对软件开发更感兴趣，他在学习和职业发展路上做更多探索，例如在 freeCodeCamp 学习前端开发、加入 K12 编程教育创业公司。现在他是 ThoughtWorks 的专家级咨询师，最近在研究 AI 新技术。</p><p>在节目中，他分享了自己的这些经历，以及对他很有启发的“油漆滴”模型。我们还聊到他组织文言文编程工作坊的时候，写的那篇让我觉得惊艳的文章。</p><p>如果他保持好奇心、透明做自己的状态也能给你一些启发和动力，请把这期节目分享给更多朋友。也欢迎你发邮件分享自己的故事，也许我们会邀请你作为 freeCodeCamp 播客的嘉宾。你可以在<a href="https://www.freecodecamp.org/chinese/news/freecodecamp-podcast-in-chinese/">这篇文章</a>中找到邮箱地址。</p><p>欢迎在 <a href="https://chinese.freecodecamp.org/">https://chinese.freecodecamp.org/</a> 查看更多免费的编程学习资源。</p><p>注：29:16 DEI 是 Diversity、Equity、Inclusion 三个词的缩写。</p><h2 id="-"><strong><strong>主要话题</strong></strong></h2><ul><li>03:19 编程和游戏</li><li>11:26 成为前端开发者</li><li>17:52 青少年编程教育</li><li>28:25 ThoughtWorks 企业文化</li><li>38:00 在 ThoughtWorks 的工作内容</li><li>49:41 开发者如何提升综合能力</li><li>50:58 文言文编程</li><li>56:20 参与帮助孕妇的项目</li><li>59:16 在 TEDxChengdu 协作</li><li>1:03:53 对 AI 新技术的观察和思考</li><li>1:10:24 保持好奇心</li></ul><h2 id="--1"><strong><strong>提到的资源</strong></strong></h2><ul><li><a href="https://chinese.freecodecamp.org/learn">freeCodeCamp 课程</a></li><li><a href="https://ed.ted.com/ted_ed_collections/think-like-a-coder">TEDEd《像程序员一样思考》</a></li><li><a href="https://code.org/">编程一小时</a></li><li><a href="https://www.codingame.com/start/">CodinGame</a></li><li><a href="https://zhuanlan.zhihu.com/p/102594855">文言文编程工作坊</a></li><li><a href="https://www.notion.so/6865246e82984627938cff4256507692?pvs=21">油漆滴模型</a></li></ul> ]]>
                </content:encoded>
            </item>
        
            <item>
                <title>
                    <![CDATA[ LangChain 教程——如何构建自定义知识聊天机器人 ]]>
                </title>
                <description>
                    <![CDATA[ 你可能已经了解到过去几个月中发布的大量人工智能应用程序。你甚至可能已经开始使用其中的一些。 ChatPDF [https://www.chatpdf.com/] 和 CustomGPT AI [https://customgpt.ai/use-cases/] 等人工智能工具对人们非常有用，这是有道理的。你需要翻阅长达 50 页的文档才能找到一个简单答案的时代已经一去不复返了。取而代之的是，你可以依靠人工智能来完成繁重的工作。 但是，这些开发人员究竟是如何创建和使用这些工具的呢？他们中的许多人都在使用一个名为 LangChain 的开源框架。 在本文中，我将向你介绍 LangChain，并向你展示如何将其与 OpenAI 的 API 结合使用，以创建这些改变游戏规则的工具。希望我的介绍能激发你们的灵感，创造出属于自己的工具。那么，让我们开始吧！ 什么是 LangChain LangChain [https://github.com/hwchase17/langchain/]是一个开源框架，允许人工智能开发人员将 GPT-4 等大型语言模型（LLM）与外部数据相结合。它提供 P ]]>
                </description>
                <link>https://www.freecodecamp.org/chinese/news/langchain-how-to-create-custom-knowledge-chatbots/</link>
                <guid isPermaLink="false">652ce3d97452bd03fdf58d3c</guid>
                
                    <category>
                        <![CDATA[ 人工智能 ]]>
                    </category>
                
                    <category>
                        <![CDATA[ AI ]]>
                    </category>
                
                <dc:creator>
                    <![CDATA[ luojiyin ]]>
                </dc:creator>
                <pubDate>Mon, 16 Oct 2023 07:25:04 +0000</pubDate>
                <media:content url="https://chinese.freecodecamp.org/news/content/images/2023/10/ThumbnailArticle--1-.png" medium="image" />
                <content:encoded>
                    <![CDATA[ <p data-test-label="translation-intro">
        <strong>原文：</strong> <a href="https://www.freecodecamp.org/news/langchain-how-to-create-custom-knowledge-chatbots/" target="_blank" rel="noopener noreferrer" data-test-label="original-article-link">LangChain Tutorial – How to Build a Custom-Knowledge Chatbot</a>
      </p><!--kg-card-begin: markdown--><p>你可能已经了解到过去几个月中发布的大量人工智能应用程序。你甚至可能已经开始使用其中的一些。</p>
<p><a href="https://www.chatpdf.com/">ChatPDF</a> 和 <a href="https://customgpt.ai/use-cases/">CustomGPT AI</a> 等人工智能工具对人们非常有用，这是有道理的。你需要翻阅长达 50 页的文档才能找到一个简单答案的时代已经一去不复返了。取而代之的是，你可以依靠人工智能来完成繁重的工作。</p>
<p>但是，这些开发人员究竟是如何创建和使用这些工具的呢？他们中的许多人都在使用一个名为 LangChain 的开源框架。</p>
<p>在本文中，我将向你介绍 LangChain，并向你展示如何将其与 OpenAI 的 API 结合使用，以创建这些改变游戏规则的工具。希望我的介绍能激发你们的灵感，创造出属于自己的工具。那么，让我们开始吧！</p>
<h2 id="langchain">什么是 LangChain</h2>
<p><img src="https://www.freecodecamp.org/news/content/images/2023/05/Screenshot-2023-05-29-at-5.40.38-PM.png" alt="Screenshot-2023-05-29-at-5.40.38-PM" width="600" height="400" loading="lazy"></p>
<p><a href="https://github.com/hwchase17/langchain/">LangChain</a>是一个开源框架，允许人工智能开发人员将 GPT-4 等大型语言模型（LLM）与外部数据相结合。它提供 Python 或 JavaScript（TypeScript）包。</p>
<p>大家可能知道，GPT 模型是在 2021 年之前的数据上训练出来的，这可能是一个很大的局限。虽然这些模型的常识很不错，但如果能将它们与自定义数据和计算连接起来，就会打开很多大门。这正是 LangChain 所要做的。</p>
<p>从本质上讲，它可以让你的 LLM 在得出答案时参考整个数据库。因此，你现在可以让你的 GPT 模型访问报告、文档和网站信息等形式的最新数据。</p>
<p>最近，LangChain 的受欢迎程度大幅上升，尤其是在三月份推出 GPT-4 之后。这要归功于它的多功能性，以及与功能强大的 LLM 配合后所带来的多种可能性。</p>
<h2 id="langchain">LangChain 是怎样工作的</h2>
<p>可能会觉得 LangChain 听起来很复杂，但实际上它非常容易上手。</p>
<p>简而言之，LangChain 就是将大量数据组成一个 LLM 可以轻松引用的数据链，而且计算能力越低越好。它的工作原理是将大量数据（例如 50 页的 PDF 文件）分解成 <code>块(chunks)</code>，然后将这些 <code>块(chunks)</code> 嵌入到 向量存储(Vector Store) 中。</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2023/05/------LangChain.png" alt="------LangChain" width="600" height="400" loading="lazy"></p>
<p>以上是创建向量存储(Vector Store) 的简单示意图。</p>
<p>现在，我们已经有了大型文档的向量化表示，可以将其与 LLM 结合使用，在创建 提示-完成对<br>
(prompt-completion pair) 时，只检索我们需要引用的信息。</p>
<p>当我们在新聊天机器人中插入 提示(prompt) 时，LangChain 会查询 向量存储库(Vector Store) 以获取相关信息。把它想象成你文档的迷你谷歌。一旦检索到相关信息，我们就会将其与 提示(prompt ) 信息一起输入 LLM，生成我们的答案。</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2023/05/ByteSizedThumbnail--1200---800-px---10-.png" alt="ByteSizedThumbnail--1200---800-px---10-" width="600" height="400" loading="lazy"></p>
<p>以上展示了 LangChain 如何与 OpenAI 的 LLM 协同工作。</p>
<p>LangChain 还允许你创建可以执行操作的应用程序，例如上网、发送电子邮件和完成其他与 API 相关的任务。请查看 <a href="https://Agentsgpt.reworkd.ai/">AgentsGPT</a>，这就是一个很好的例子。</p>
<p>这有很多可能的用例，以下是我想到的几个：</p>
<ul>
<li>个人 AI 电子邮件助理</li>
<li>人工智能学习伙伴</li>
<li>人工智能数据分析</li>
<li>定制公司客户服务聊天机器人</li>
<li>社交媒体内容创建助手</li>
</ul>
<p>还有更多。我将在今后的文章中介绍正确的构建教程，敬请期待。</p>
<h2 id="langchain">怎样开始使用 LangChain</h2>
<p>LangChain 应用程序由 5 个主要部分组成：</p>
<ol>
<li>Models (LLM Wrappers)</li>
<li>Prompts</li>
<li>Chains</li>
<li>Embeddings and Vector Stores</li>
<li>Agents</li>
</ol>
<p>我将为你逐一介绍，以便你对 LangChain 的工作原理有一个高层次的了解。接下来，你应该能够应用这些概念，开始制作自己的用例并创建自己的应用程序。</p>
<p>我将用 Rabbitmetrics 的简短代码片段（<a href="https://github.com/rabbitmetrics/langchain-13-min/blob/main/notebooks/langchain-13-min.ipynb">Github</a>）来解释一切。他就这一主题提供了很好的教程。通过这些代码片段，你可以完成所有设置并准备使用 LangChain。</p>
<p>首先，我们来设置环境。你可以用 pip 安装 3 个需要的库：</p>
<pre><code class="language-shell">pip install -r requirements.txt
</code></pre>
<pre><code class="language-requirements.txt">python-dotenv==1.0.0
langchain==0.0.137
pinecone-client==2.2.1
</code></pre>
<p><a href="https://www.pinecone.io/">Pinecone</a>是我们将与 LangChain 结合使用的向量存储。使用这些工具时，请确保将 OpenAI、Pinecone Environment 和 Pinecone API 的 API 密钥存储到环境文件中。你可以在它们各自的网站上找到这些信息。然后，我们只需在环境文件中加载以下内容即可：</p>
<pre><code class="language-Python"># Load environment variables

from dotenv import load_dotenv,find_dotenv
load_dotenv(find_dotenv())
</code></pre>
<p>现在，我们可以开始了！</p>
<h3 id="modelsllmwrappers">Models（LLM Wrappers）</h3>
<p>为了与我们的 LLM 进行交互，我们将为 OpenAI 的 GPT 模型实例化一个封装器。在本例中，我们将使用 OpenAI 的 GPT-3.5-turbo，因为它最具性价比。但如果你有授权，也可以使用功能更强大的 GPT4。</p>
<pre><code class="language-Python"># import schema for chat messages and ChatOpenAI in order to query chatmodels GPT-3.5-turbo or GPT-4

from langchain.schema import (
    AIMessage,
    HumanMessage,
    SystemMessage
)
from langchain.chat_models import ChatOpenAI
     

chat = ChatOpenAI(model_name="gpt-3.5-turbo",temperature=0.3)
messages = [
    SystemMessage(content="You are an expert data scientist"),
    HumanMessage(content="Write a Python script that trains a neural network on simulated data ")
]
response=chat(messages)

print(response.content,end='\n')
     
</code></pre>
<p>本质上，<code>SystemMessage</code> 为 GPT-3.5-turbo 模块提供了上下文，它将为每个提示-完成对引用该模块。 HumanMessage 指的是你在 ChatGPT 界面中输入的内容(你的提示)。</p>
<p>但对于定制知识聊天机器人，我们经常抽象出 提示（prompt）中的重复组件。例如，如果我正在创建一个推文生成器应用程序，我不想继续输入<code>给我写一条关于...的推文</code>。其实，<a href="https://ilampadman.com/best-ai-writer-best-ai-copywriter">AI 写作工具</a> 就是这么简单开发出来的！</p>
<p>那么让我们看看如何使用 提示模板(prompt templates) 将其抽象出来。</p>
<h3 id="prompts">Prompts（提示）</h3>
<p>LangChain 提供 PromptTemplates，允许你根据用户输入动态更改提示，类似于使用正则表达式。</p>
<pre><code class="language-Python"># Import prompt and define PromptTemplate

from langchain import PromptTemplate

template = """
You are an expert data scientist with an expertise in building deep learning models. 
Explain the concept of {concept} in a couple of lines
"""

prompt = PromptTemplate(
    input_variables=["concept"],
    template=template,
)
     

# Run LLM with PromptTemplate

llm(prompt.format(concept="autoencoder"))
llm(prompt.format(concept="regularization"))
</code></pre>
<p>你可以通过不同的方式改变它们以适合你的用例。 如果你熟悉使用 ChatGPT，这对你来说应该很舒服。</p>
<h3 id="chains">Chains（链）</h3>
<p>链（Chains）允许你采用简单的 PromptTemplates 并在它们之上构建功能。本质上，链就像 <a href="https://www.freecodecamp.org/news/function-composition-in-javascript/">复合函数(composite functions)</a>，允许你将 PromptTemplates 和 LLM 集成在一起。</p>
<p>使用之前的装饰器(wrappers) 和 PromptTemplates，我们可以使用单个链运行相同的提示，该链采用 PromptTemplate 并将其与 LLM 组合：</p>
<pre><code class="language-Python"># Import LLMChain and define chain with language model and prompt as arguments.

from langchain.chains import LLMChain
chain = LLMChain(llm=llm, prompt=prompt)

# Run the chain only specifying the input variable.
print(chain.run("autoencoder"))
     
</code></pre>
<p>最重要的是，顾名思义，我们可以将它们链接在一起以创建更大的作品。</p>
<p>例如，我可以从一个链中获取结果并将其传递到另一个链中。 在此片段中，Rabbitmetrics 从第一个链中获取补全内容，并将其传递到第二个链中，以便向 5 岁的孩子解释。</p>
<p>然后，你可以将这些链组合成一个更大的链并运行它。</p>
<pre><code class="language-Python"># Define a second prompt 

second_prompt = PromptTemplate(
    input_variables=["ml_concept"],
    template="Turn the concept description of {ml_concept} and explain it to me like I'm five in 500 words",
)
chain_two = LLMChain(llm=llm, prompt=second_prompt)

# Define a sequential chain using the two chains above: the second chain takes the output of the first chain as input

from langchain.chains import SimpleSequentialChain
overall_chain = SimpleSequentialChain(chains=[chain, chain_two], verbose=True)

# Run the chain specifying only the input variable for the first chain.
explanation = overall_chain.run("autoencoder")
print(explanation)
</code></pre>
<p>通过链，你可以创建大量的功能，这就是 LangChain 如此多功能的原因。 但它真正的亮点在于将其与前面讨论的向量存储结合使用。 我们来介绍一下这个组件。</p>
<h3 id="embeddingsvectorstores">Embeddings（嵌入）和 Vector Stores（向量存储）</h3>
<p>这就是我们整合 LangChain 的自定义数据方面的地方。 如前所述，嵌入(embeddings) 和向量存储背后的想法是将大数据分成块并在相关时存储要查询的数据。</p>
<p>LangChain 有一个文本分割器函数(text splitter function) 可以做到这一点：</p>
<pre><code class="language-Python"># Import utility for splitting up texts and split up the explanation given above into document chunks

from langchain.text_splitter import RecursiveCharacterTextSplitter

text_splitter = RecursiveCharacterTextSplitter(
    chunk_size = 100,
    chunk_overlap  = 0,
)

texts = text_splitter.create_documents([explanation])
</code></pre>
<p>分割文本需要两个参数：块有多大 (chunk_size) 以及每个块重叠的程度 (chunk_overlap)。 每个块之间的重叠对于帮助识别相关的相邻块非常重要。</p>
<p>每个块都可以这样检索：</p>
<pre><code class="language-Python">texts[0].page_content
</code></pre>
<p>获得这些块后，我们需要将它们转化为嵌入(embeddings)。这允许向量存储在查询时查找并返回每个块。我们将使用 OpenAI 的 嵌入模型(embedding model) 来完成此操作。</p>
<pre><code class="language-Python"># Import and instantiate OpenAI embeddings

from langchain.embeddings import OpenAIEmbeddings

embeddings = OpenAIEmbeddings(model_name="ada")
     

# Turn the first text chunk into a vector with the embedding

query_result = embeddings.embed_query(texts[0].page_content)
print(query_result)
</code></pre>
<p>最后，我们需要有一个地方来存储这些 矢量化嵌入(vectorized embeddings)。如前所述，我们将为此使用 Pinecone。使用之前环境文件中的 API key，我们可以初始化 Pinecone 来存储我们的嵌入。</p>
<pre><code class="language-Python"># Import and initialize Pinecone client

import os
import pinecone
from langchain.vectorstores import Pinecone


pinecone.init(
    api_key=os.getenv('PINECONE_API_KEY'),  
    environment=os.getenv('PINECONE_ENV')  
)
     

# Upload vectors to Pinecone

index_name = "langchain-quickstart"
search = Pinecone.from_documents(texts, embeddings, index_name=index_name)
     

# Do a simple vector similarity search

query = "What is magical about an autoencoder?"
result = search.similarity_search(query)

print(result)
</code></pre>
<p>现在，我们能够从 Pinecone 向量存储中查询相关信息了！剩下要做的就是把我们学到的知识结合起来，创建我们的特定用例。这就是我们的专业人工智能 <code>Agents</code>。</p>
<h3 id="agents">Agents</h3>
<p>Agents 本质上是一种自主的人工智能，它接收输入并按顺序完成这些任务，直至达到最终目标。这就需要我们的人工智能使用其他应用程序接口，从而完成发送电子邮件或做数学题等任务。结合我们的 LLM + 提示链(prompt chains)，我们可以串联起一个合适的人工智能应用程序。</p>
<p>现在，要解释这部分内容会很费劲，因此这里有一个简单的例子，说明如何在 LangChain 中使用 Python Agents 来解决一个简单的数学问题。本例中的 Agents 通过连接我们的 LLM 来运行 Python 代码，并使用 NumPy 求根来解决问题：</p>
<pre><code class="language-Python"># Import Python REPL tool and instantiate Python Agents

from langchain.Agents.Agents_toolkits import create_python_Agents
from langchain.tools.python.tool import PythonREPLTool
from langchain.python import PythonREPL
from langchain.llms.openai import OpenAI

Agents_executor = create_python_Agents(
    llm=OpenAI(temperature=0, max_tokens=1000),
    tool=PythonREPLTool(),
    verbose=True
)
     

# Execute the Python Agents

Agents_executor.run("Find the roots (zeros) if the quadratic function 3 * x**2 + 2*x -1")
</code></pre>
<p>定制知识聊天机器人本质上是一个 Agents，它可以将查询向量化存储的 提示(prompts) 和操作串联起来，获取结果，并将其与原始问题串联起来！</p>
<p>如果你想了解更多关于人工智能 Agents 的信息，<a href="https://lablab.ai/t/ai-Agents-tutorial-how-to-use-and-create-them">这个</a> 是一个很好的资源。</p>
<h2 id="">别的因素</h2>
<p>即使你刚刚对 LangChain 的功能有了基本的了解，我相信你此时也会有很多想法。</p>
<p>但到目前为止，我们只研究了一个 OpenAI 模型，那就是基于文本的 GPT-3.5-turbo。OpenAI 有一系列模型可供 LangChain 使用，包括使用 Dall-E 生成图像。应用我们讨论过的相同概念，我们可以创建 <a href="https://julianlankstead.com/ai-nft-art-generator">人工智能艺术生成器</a> 代理、网站生成器代理等。</p>
<p>花点时间探索一下人工智能领域，我相信你会有越来越多的想法。</p>
<h2 id="">总结</h2>
<p>我希望你已经对所有这些新人工智能工具的幕后工作有了更多了解。作为一名程序员，了解 LangChain 的工作原理是一项宝贵的技能，它可以为你的人工智能开发带来无限可能。</p>
<p>如果你喜欢这篇文章，并想了解更多有关人工智能创造者们正在构建的新工具的信息，你可以通过我的 <a href="https://bytesizedai.beehiiv.com/subscribe">Byte-Sized AI Newsletter</a> 随时了解最新信息。我希望你能加入我们的社区。</p>
<p>你也可以在<a href="https://twitter.com/Shuggggan">Twitter</a> 上关注我，我们也可以在那里保持联系。</p>
<p>除此之外，请开始尝试使用 LangChain 并创建一些有趣的人工智能项目。</p>
<!--kg-card-end: markdown--> ]]>
                </content:encoded>
            </item>
        
            <item>
                <title>
                    <![CDATA[ 如何使用 LangChain 创建人工智能推文生成器 ]]>
                </title>
                <description>
                    <![CDATA[ 今天我给大家带来了一个有趣的教程。如果你读过我上一篇关于使用 LangChain 构建自定义知识聊天机器人 [/news/langchain-how-to-create-custom-knowledge-chatbots/] 的文章，那么你一定对自己可以创建的伟大项目充满了想法。 好的，我想鼓励你的创造力，并举例说明使用 LangChain 和大型语言模型（LLM）可以创建什么。虽然听起来有点吓人，但实际上实现起来非常简单。 今天，我们将使用 LangChain 和 OpenAI 的 LLM 创建一个 AI Tweet 生成器。这是一个简单的项目，它接收一个 Tweet 主题并输出相关的 Tweet。 这有什么特别的？有趣的是，我们将使用 LangChain 通过维基百科上的最新信息。这让我们克服了 ChatGPT 的训练数据限制，因为它只能在 2021 年之前的数据上进行训练。 这就是我们要做的： 请看下面我们的推特所引用的信息。它使用了维基百科关于微软在 2023 年对 OpenAI 进行投资的信息。因此，现在你不必担心你的人工智能所引用的数据会过时了。 如果你 ]]>
                </description>
                <link>https://www.freecodecamp.org/chinese/news/create-an-ai-tweet-generator-openai-langchain/</link>
                <guid isPermaLink="false">6525fbf3c8bb7703fa0287e1</guid>
                
                    <category>
                        <![CDATA[ 人工智能 ]]>
                    </category>
                
                    <category>
                        <![CDATA[ AI ]]>
                    </category>
                
                <dc:creator>
                    <![CDATA[ luojiyin ]]>
                </dc:creator>
                <pubDate>Tue, 10 Oct 2023 01:43:00 +0000</pubDate>
                <media:content url="https://chinese.freecodecamp.org/news/content/images/2023/10/cover.png" medium="image" />
                <content:encoded>
                    <![CDATA[ <p data-test-label="translation-intro">
        <strong>原文：</strong> <a href="https://www.freecodecamp.org/news/create-an-ai-tweet-generator-openai-langchain/" target="_blank" rel="noopener noreferrer" data-test-label="original-article-link">How to Create an AI Tweet Generator Using LangChain</a>
      </p><!--kg-card-begin: markdown--><p>今天我给大家带来了一个有趣的教程。如果你读过我上一篇关于使用 LangChain 构建<a href="https://chinese.freecodecamp.org/news/langchain-how-to-create-custom-knowledge-chatbots/">自定义知识聊天机器人</a>的文章，那么你一定对自己可以创建的伟大项目充满了想法。</p>
<p>好的，我想鼓励你的创造力，并举例说明使用 LangChain 和大型语言模型（LLM）可以创建什么。虽然听起来有点吓人，但实际上实现起来非常简单。</p>
<p>今天，我们将使用 LangChain 和 OpenAI 的 LLM 创建一个 AI Tweet 生成器。这是一个简单的项目，它接收一个 Tweet 主题并输出相关的 Tweet。</p>
<p>这有什么特别的？有趣的是，我们将使用 LangChain 通过维基百科上的最新信息。这让我们克服了 ChatGPT 的训练数据限制，因为它只能在 2021 年之前的数据上进行训练。</p>
<p>这就是我们要做的：</p>
<p><img src="https://lh4.googleusercontent.com/B-AqnuHPFtkT010tllL0VZlbZRK-wasEjUwl8a5yzDRCuG3VYRt8hz1QPC3tz1F_vnDSXwHM8gJNIbM9jFcGbnz1uu4OSQB-hTVSuDYULlfVRWlQfewvFpS4-XF8pkMn37Gu5Au4liSxujehfV7uCWg" alt="B-AqnuHPFtkT010tllL0VZlbZRK-wasEjUwl8a5yzDRCuG3VYRt8hz1QPC3tz1F_vnDSXwHM8gJNIbM9jFcGbnz1uu4OSQB-hTVSuDYULlfVRWlQfewvFpS4-XF8pkMn37Gu5Au4liSxujehfV7uCWg" width="780" height="574" loading="lazy"></p>
<p>请看下面我们的推特所引用的信息。它使用了维基百科关于微软在 <strong>2023 年</strong>对 OpenAI 进行投资的信息。因此，现在你不必担心你的人工智能所引用的数据会过时了。</p>
<p><img src="https://lh6.googleusercontent.com/r5CxHduLOViifkCaI2R84nl-n26rGVHnJCOa3Rgpt_WqXlyL9O7Hnar52p0yGLhKNhe3F5F3X6CNM98-0oJBeBXQ8IvQvNgTZirblgs5lSU4j8G9X_X1ROgoPd06vIGhLd_mdmWyEZzAtrC5ESSXvZA" alt="r5CxHduLOViifkCaI2R84nl-n26rGVHnJCOa3Rgpt_WqXlyL9O7Hnar52p0yGLhKNhe3F5F3X6CNM98-0oJBeBXQ8IvQvNgTZirblgs5lSU4j8G9X_X1ROgoPd06vIGhLd_mdmWyEZzAtrC5ESSXvZA" width="748" height="662" loading="lazy"></p>
<p>如果你觉得听起来不错，那我们就开始吧。</p>
<h2 id="">如何设置项目</h2>
<p>虽然这个项目需要多个组件，但实际上所有内容都可以很好地整合到一个 app.py 文件中，该文件只需将多个 API 整合在一起即可。</p>
<p>在结构上，我们只需创建 app.py 文件和 apikey.py 文件来存储 API key（主要用于 <a href="https://openai.com/blog/openai-api">OpenAI</a>）。</p>
<p>此外，我们还要安装的库。以下是我们将在本项目中使用的库列表：</p>
<ul>
<li><strong>Streamlit</strong> – Used to build the app（用于构建应用程序）</li>
<li><strong>LangChain</strong> – Used to build LLM workflow（用于建立 LLM 工作流程）</li>
<li><strong>OpenAI</strong> – For using OpenAI GPT（调用 OpenAI GPT 接口）</li>
<li><strong>Wikipedia</strong> – Used to connect GPT to WIKIPEDIA（用于连接 GPT 和 WIKIPEDIA）</li>
<li><strong>ChromaDB</strong> – Vector storage（向量存储)）</li>
<li><strong>Tiktoken</strong> – Backend tokenizer for OpenAI（OpenAI 的后台令牌生成器）</li>
</ul>
<p>要安装这些软件，请在终端运行以下命令：</p>
<pre><code class="language-shell">pip install streamlit langchain openai wikipedia chromadb tiktoken
</code></pre>
<p>如果你的系统已经包含其中一些服务，则可以逐一安装。此外，我们还将为该环境配置 API Key 变量。将这些内容导入 app.py 文件后，我们就可以开始了。</p>
<pre><code class="language-Python">import os 
from apikey import apikey 

import streamlit as st 
from langchain.llms import OpenAI
from langchain.prompts import PromptTemplate
from langchain.chains import LLMChain, SequentialChain 
from langchain.memory import ConversationBufferMemory
from langchain.utilities import WikipediaAPIWrapper

os.environ['OPENAI_API_KEY'] = apikey
</code></pre>
<h2 id="">如何实现用户界面</h2>
<p>现在，我们要创建的应用程序相当简单。因此，我决定尽可能简化用户界面，只使用一个标题和文本输入框。对于这个 Tweet 生成器来说，这已经达到了目的。</p>
<pre><code class="language-Python"># Creating the title and input field
st.title('🦜🔗 Tweet Generator')
prompt = st.text_input('Tweet topic: ')
</code></pre>
<p>稍后，我们将添加功能来显示我们的话题历史、推特历史，以及最重要的，我们引用的维基百科数据。现在，这就是我们要使用的用户界面：</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2023/06/Screenshot-2023-06-21-at-4.38.00-PM.png" alt="Screenshot-2023-06-21-at-4.38.00-PM" width="600" height="400" loading="lazy"></p>
<h2 id="">如何包含提示模板</h2>
<p>现在，我们将进入 LangChain 领域。在这一点上，如果你对 LangChain 不熟悉，也没有阅读过我关于 LangChain 的 <a href="https://www.freecodecamp.org/news/langchain-how-to-create-custom-knowledge-chatbots/">上一篇文章</a>，我强烈建议你阅读一下，以便更好地了解接下来的步骤。</p>
<p>我们要做的第一件事就是介绍 PromptTemplates。概括地说，PromptTemplates 是提示语的封装器，可以通过多种操作将它们串联起来，这也是 LangChain 的基础。</p>
<p>此外，我们还将为维基百科应用程序接口（Wikipedia API）提供一个封装，以便在链式执行中包含数据。</p>
<pre><code class="language-Python"># template for the title
title_template = PromptTemplate(
    input_variables = ['topic'], 
    template='write me a tweet about {topic}'
)

# template for the tweet
tweet_template = PromptTemplate(
    input_variables = ['title', 'wikipedia_research'], 
    template='write me a tweet on this title TITLE: {title} while leveraging this wikipedia reserch:{wikipedia_research} '
)

# wrapper for Wikipedia data
wiki = WikipediaAPIWrapper()
</code></pre>
<p>在这个例子中，我还创建了一个 标题提示（title prompt），为我们的推文主题提供一个总标题。至于实际的提示，如果你一直在使用 ChatGPT，它的概念基本相同，只是现在我们引入了变量（推特话题）。</p>
<p>这样我们就可以避免每次输入时都输入 <code>给我写一条关于...的推文</code>。相反，我们只需插入话题即可。说完这些，让我们开始介绍实际的 OpenAI LLM。</p>
<h2 id="introducingopenaisllms">Introducing OpenAI's LLMs</h2>
<p>有几种方法可以做到这一点，你可以选择自己认为合适的模式。在上一篇文章中，我使用了 GPT-3.5-turbo <a href="https://python.langchain.com/docs/modules/model_io/models/chat/integrations/openai">chat model</a>，代码如下：</p>
<pre><code class="language-Python">chat = ChatOpenAI(model_name="gpt-3.5-turbo",temperature=0.3)
messages = [
    SystemMessage(content="You are an expert data scientist"),
    HumanMessage(content="Write a Python script that trains a neural network on simulated data ")
]
</code></pre>
<p>不过，你可以根据自己的 API 密钥决定使用哪个模块，然后按照 <a href="https://python.langchain.com/docs/modules/model_io/models/llms/integrations/openai">LangChain 文档</a> 进行设置即可。</p>
<p>今天，我们将使用 "text-davinci-003 "模型，它与 ChatGPT 早期的 GPT-3 模型基本相同。</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2023/06/Screenshot-2023-06-22-at-12.17.17-PM.png" alt="Screenshot-2023-06-22-at-12.17.17-PM" width="600" height="400" loading="lazy"></p>
<p>上图是 OpenAI 的各种模型（见其<a href="https://platform.openai.com/docs/models/overview">网站</a>）。</p>
<p>你可以随意尝试使用这些模型，看看哪种推文能产生最好的结果。你甚至可以尝试功能更强大（也更昂贵）的 GPT-4，但对于像推文生成器这样简单的提示完成案例，可能就没有必要了。</p>
<pre><code class="language-Python">llm = OpenAI(model_name="text-davinci-003", temperature=0.9) 
title_chain = LLMChain(llm=llm, prompt=title_template, verbose=True, output_key='title', memory=title_memory)
script_chain = LLMChain(llm=llm, prompt=script_template, verbose=True, output_key='script', memory=script_memory)
</code></pre>
<p>我还决定将 <code>temperature</code> 设定为 0.9，以便让模型发表更有创意的推文。<code>temperature</code> 可以衡量模型回复的随机性和创造性，0 代表简单明了，1 代表随机性极强。如果你希望你的回应更符合事实和确定性，只需调低 <code>temperature</code> 即可。</p>
<p><code>temperature</code> 将是我们进行这项工作所需的唯一变量。如果你想了解其他字段，请花些时间阅读文档，了解它们的含义。</p>
<p>例如，我们可以指定令牌限制(token limits)，以确保不会得到冗长的响应，但使用我们当前的 tweet 提示模板应该不成问题。</p>
<h2 id="tweet">如何跟踪你的 Tweet 生成历史</h2>
<p>这是一个可选部分，但可为应用程序提供额外功能。如果你想跟踪应用程序活动的历史记录，包括以前的标题或推文等信息，则只需包含以下步骤即可：</p>
<pre><code class="language-Python"># Memory 
title_memory = ConversationBufferMemory(input_key='topic', memory_key='chat_history')
script_memory = ConversationBufferMemory(input_key='title', memory_key='chat_history')
</code></pre>
<p>在上面的代码中，我们创建了两个不同的内存变量：<code>title_memory</code> 和 <code>script_memory</code>。<code>title_memory</code> 保存推文主题的历史记录，<code>script_memory</code> 保存推文的历史记录。</p>
<p><a href="https://python.langchain.com/docs/modules/memory/how_to/buffer">ConversationBufferMemory</a> 是 LangChain 的一项功能，它允许你跟踪迄今为止的输入和输出（在本例中，这只是我们之前生成的话题和推文）。</p>
<h2 id="">如何将组件串联起来</h2>
<p>现在，我们已经对应用程序的所有组件（用户界面、提示模板和维基百科封装器）进行了分类，可以将它们组合在一起执行了。这正是 LangChain 的价值所在。</p>
<p>标准程序中的 复合函数（composite functions）就是一个很好的类比。只不过，这里的函数是 PromptTemplates、LLMs 和 Wikipedia 数据。使用之前的封装器，我们只需决定执行顺序（就像一个链条），就能获得我们想要的输出。</p>
<p>在本例中，首先从我们的主题中获取标题，然后使用维基百科对该主题的相关研究创建 Tweet，最后使用 Streamlit 显示这些内容。</p>
<pre><code class="language-Python">if prompt: 
    title = title_chain.run(prompt)  # Running the title prompt
    wiki_research = wiki.run(prompt)  # Performing wikipedia research
    script = script_chain.run(title=title, wikipedia_research=wiki_research) # Creating the tweet

    st.write(title)  # Show the topic/title of the tweet
    st.write(script)  # Show the generated tweet

    with st.expander('Title History'): 
        st.info(title_memory.buffer)   # Storing the topic of the tweet to the history

    with st.expander('Tweet History'): 
        st.info(script_memory.buffer)  # Storing the tweet to the history

    with st.expander('Wikipedia Research'): 
        st.info(wiki_research)  # Storing the Wikipedia Research on the topic to the history
</code></pre>
<p>当我们运行 这些链（those chain）时，基本上是从用户界面获取话题，然后将其插入到与 LLM 相连的 PromptTemplate 中。推文 PromptTemplate 也会从维基百科中获取数据，并输入到 LLM 中。</p>
<p>最后，是时候检查我们的应用程序了。使用以下命令运行它：</p>
<pre><code class="language-shell">streamlit run app.py
</code></pre>
<h2 id="">最终代码和下一步行动</h2>
<p>其结果是能够克服 ChatGPT 过时的信息限制，并创建相关推文。如果你觉得难以理解，下面是我们目前掌握的情况：</p>
<pre><code class="language-Python"># Importing necessary packages, files and services
import os 
from apikey import apikey 

import streamlit as st 
from langchain.llms import OpenAI
from langchain.prompts import PromptTemplate
from langchain.chains import LLMChain, SequentialChain 
from langchain.memory import ConversationBufferMemory
from langchain.utilities import WikipediaAPIWrapper

os.environ['OPENAI_API_KEY'] = apikey

# App UI framework
st.title('🦜🔗 Tweet Generator')
prompt = st.text_input('Tweet topic: ') 

# Prompt templates
title_template = PromptTemplate(
    input_variables = ['topic'], 
    template='write me a tweet about {topic}'
)

tweet_template = PromptTemplate(
    input_variables = ['title', 'wikipedia_research'], 
    template='write me a tweet on this title TITLE: {title} while leveraging this wikipedia reserch:{wikipedia_research} '
)

# Wikipedia data
wiki = WikipediaAPIWrapper()

# Memory 
title_memory = ConversationBufferMemory(input_key='topic', memory_key='chat_history')
tweet_memory = ConversationBufferMemory(input_key='title', memory_key='chat_history')


# Llms
llm = OpenAI(model_name="text-davinci-003", temperature=0.9) 
title_chain = LLMChain(llm=llm, prompt=title_template, verbose=True, output_key='title', memory=title_memory)
tweet_chain = LLMChain(llm=llm, prompt=tweet_template, verbose=True, output_key='script', memory=tweet_memory)

# Chaining the components and displaying outputs
if prompt: 
    title = title_chain.run(prompt)
    wiki_research = wiki.run(prompt) 
    tweet = tweet_chain.run(title=title, wikipedia_research=wiki_research)

    st.write(title) 
    st.write(tweet) 

    with st.expander('Title History'): 
        st.info(title_memory.buffer)

    with st.expander('Tweet History'): 
        st.info(tweet_memory.buffer)

    with st.expander('Wikipedia Research'): 
        st.info(wiki_research)
</code></pre>
<p>当然，Tweet 生成器只是 LangChain 和 LLMs 的一个简单示例。例如，你还可以应用相同的流程创建 YouTube 脚本生成器或社交媒体内容日历助手，无限可能。</p>
<h2 id="">总结</h2>
<p>希望你喜欢这个有趣的教程！LangChain 最近非常流行，这是有原因的--它的用途非常广泛。我强烈推荐你去看看。</p>
<p>如果你喜欢这篇文章，并想了解更多关于人工智能创造者正在开发的新工具的信息，你可以通过我的<a href="https://bytesizedai.beehiiv.com/subscribe"><strong>字节大小的人工智能通讯</strong></a>了解最新信息。这里有大量关于人工智能领域的精彩故事，我希望你能加入我们的社区。</p>
<p>我也会定期在 <a href="https://www.linkedin.com/in/shanepduggan/">Linkedin</a> 上发帖，我很乐意与你联系！除此之外，祝大家享受创建项目的过程，我很期待看到你们分享的项目。</p>
<!--kg-card-end: markdown--> ]]>
                </content:encoded>
            </item>
        
            <item>
                <title>
                    <![CDATA[ 如何为双人回合制游戏创建 AI ]]>
                </title>
                <description>
                    <![CDATA[ 双人回合制游戏是指两个玩家对弈，多个回合，直到其中一人获胜。这类游戏的例子有井字游戏、西洋双陆棋、曼卡拉、国际象棋和 Connect 4 四子棋。 在本教程中，我们将学习 Minimax  算法。它是一种用于决策和博弈论的回溯算法。它为棋手找到最优棋步，假设他们的对手也以最优方式下棋。它被广泛用于双人回合制游戏中。 你将学会如何创建自己的人工智能，玩上述任何游戏或其他类似游戏。另外，为了尽可能使其容易理解，我将把该算法应用于 Tic-Tac-Toe 井字游戏。 我们将不涉及创建游戏的整个过程，而只涉及与人工智能有关的部分，因为这是我们的主题。如果你对游戏创建过程感兴趣，你可以在 GitHub [https://github.com/HousseinBadra/BadraAI.github.io.git] 上查看这个使用人工智能的井字游戏 [https://housseinbadra.github.io/BadraAI.github.io/] 及其源代码。这是我很久以前做的一个项目，它仍然是我的最爱之一。 目录  * Minimax 算法是如何运行的  * Minimax 算法的 ]]>
                </description>
                <link>https://www.freecodecamp.org/chinese/news/build-an-ai-for-two-player-turn-based-games/</link>
                <guid isPermaLink="false">63b7b39381727e0763145bf6</guid>
                
                    <category>
                        <![CDATA[ 算法 ]]>
                    </category>
                
                    <category>
                        <![CDATA[ 人工智能 ]]>
                    </category>
                
                    <category>
                        <![CDATA[ AI ]]>
                    </category>
                
                <dc:creator>
                    <![CDATA[ Chengjun.L ]]>
                </dc:creator>
                <pubDate>Fri, 06 Jan 2023 04:29:00 +0000</pubDate>
                <media:content url="https://chinese.freecodecamp.org/news/content/images/2023/01/2825132-637490944552534550-16x9-1.jpeg" medium="image" />
                <content:encoded>
                    <![CDATA[ <p data-test-label="translation-intro">
        <strong>原文：</strong> <a href="https://www.freecodecamp.org/news/build-an-ai-for-two-player-turn-based-games/" target="_blank" rel="noopener noreferrer" data-test-label="original-article-link">How to Build an AI for Two-Player Turn-based Games</a>
      </p><p>双人回合制游戏是指两个玩家对弈，多个回合，直到其中一人获胜。这类游戏的例子有井字游戏、西洋双陆棋、曼卡拉、国际象棋和 Connect 4 四子棋。</p><p>在本教程中，我们将学习 <strong>Minimax</strong> 算法。它是一种用于决策和博弈论的回溯算法。它为棋手找到最优棋步，假设他们的对手也以最优方式下棋。它被广泛用于双人回合制游戏中。</p><p>你将学会如何创建自己的人工智能，玩上述任何游戏或其他类似游戏。另外，为了尽可能使其容易理解，我将把该算法应用于 <strong>Tic-Tac-Toe 井字游戏</strong>。</p><p>我们将不涉及创建游戏的整个过程，而只涉及与人工智能有关的部分，因为这是我们的主题。如果你对游戏创建过程感兴趣，你可以在 <a href="https://github.com/HousseinBadra/BadraAI.github.io.git"><strong><strong>GitHub</strong></strong></a> 上查看这个<strong><a href="https://housseinbadra.github.io/BadraAI.github.io/">使用人工智能的井字游戏</a></strong>及其源代码。这是我很久以前做的一个项目，它仍然是我的最爱之一。</p><h3 id="-"><strong>目录</strong></h3><ul><li>Minimax 算法是如何运行的</li><li>Minimax 算法的局限性</li><li>如何提高该算法的时间复杂度</li><li>井字游戏 AI 代码</li><li>总结</li></ul><h2 id="minimax-">Minimax 算法是如何运行的</h2><p>Minimax 算法的方法非常简单。首先，它从一个给定的位置检查所有可能的组合。然后，假设双方都发挥得很好，它就会选择使获胜机会最大化的最佳棋步。</p><p>为了说明这一点，让我们考虑一个井字游戏，使之更有说服力。正如你可能知道的，在这个游戏中，有 2 个玩家和 9 个空位。所以我们可以用一个长度为 9 的数组来表示一个游戏。</p><p>现在让我们以这个棋盘为例：正如你所看到的，一个游戏棋盘是一个长度为 9 的数组，其值可以是 <strong>X</strong>、<strong>O</strong> 或空字符串。一个空字符串意味着这个位置仍然可用。</p><figure class="kg-card kg-image-card kg-card-hascaption"><img src="https://www.freecodecamp.org/news/content/images/2022/12/new1.jpg" class="kg-image" alt="new1" width="600" height="400" loading="lazy"><figcaption>棋盘数组到游戏棋盘</figcaption></figure><p>现在轮到 <strong>X</strong> 了。Minimax 算法将从这个位置开始尝试所有的游戏组合。然后，它将从所产生的子位置尝试所有的游戏组合，直到达到一个位置，游戏结束时，要么是 X 赢，要么是 O 赢，要么是平局（当棋盘满了，没有人赢）。</p><p>这张图说明了这是如何运行的：</p><figure class="kg-card kg-image-card kg-width-wide kg-card-hascaption"><img src="https://www.freecodecamp.org/news/content/images/2022/12/new2.jpg" class="kg-image" alt="new2" width="600" height="400" loading="lazy"><figcaption>所有游戏组合的演示</figcaption></figure><p>我们可以使用递归来实现这一点。我将在最后展示代码，但现在，让我们专注于理解如何处理游戏组合以获得最佳棋步。我们将考虑这些情况：</p><ul><li>棋盘上有一个 X 的获胜位置，等于 <strong>1 分</strong></li><li>棋盘上有一个 O 的获胜位置，等于 <strong>-1 分</strong></li><li>棋盘上有一个平局的位置，等于 <strong>0 分</strong></li></ul><p>如果我们要为 X 找到最佳棋步，我们应该找到分数最多的棋盘。但是如果一个棋盘还没有完成呢？那么我们应该根据它的子棋盘来选择棋盘——但我们该选择哪一个呢？</p><p>我需要你专注于这部分，因为这是最重要的部分。当我介绍这个算法时，我说它能找到所有的游戏组合，假设双方都在采取最优的棋步。</p><p>在第一代子棋盘之后，就会轮到 O 了。假设 O 是在采取最优棋步，我们应该选择一个 O 做得最好的棋盘，也就是分数最少的棋盘之一（因为当 O 赢的时候，该棋盘会返回 -1）。</p><p>为什么我们要这样选择呢？想象一下，如果我们在轮到 O 的时候选择最大值，那么我们就会让 X 获胜。这使得该算法毫无用处，因为我们需要假设 O 以最佳棋步下棋。</p><p>对于第三代，玩家又是 X，我们将再一次选择点数最多的棋盘。</p><p>这种交替选择最大值和最小值的方法就是这个算法被称为极小化极大值算法的原因。请看这个可视化图，以进一步了解：</p><figure class="kg-card kg-image-card kg-card-hascaption"><img src="https://chinese.freecodecamp.org/news/content/images/2023/01/image-7.png" class="kg-image" alt="image-7" width="1366" height="768" loading="lazy"><figcaption>Minimax 机制</figcaption></figure><p>这与上面的例子相同。底部的 2 个棋盘对 X 来说是赢的，所以每个棋盘都会返回 1。这里，轮到 X 了，所以我们会选择最佳值——恰好这两个棋盘的值都是 1。</p><p>正如我之前所说，如果一个棋盘不满足获胜或平局条件，我们将查看其子棋盘。这就是为什么数值为 1 的棋盘的父棋盘会有 1 的数值。</p><figure class="kg-card kg-image-card kg-width-wide kg-card-hascaption"><img src="https://www.freecodecamp.org/news/content/images/2022/12/new4.jpg" class="kg-image" alt="new4" width="600" height="400" loading="lazy"><figcaption>Minimax 算法机制</figcaption></figure><p>这里轮到 O，所以我们将选择可能的最低值，恰好是 1。我选择这个特定的例子是为了让事情变得简单，但这在所有棋盘上都适用。</p><figure class="kg-card kg-image-card kg-width-wide kg-card-hascaption"><img src="https://www.freecodecamp.org/news/content/images/2022/12/new5.jpg" class="kg-image" alt="new5" width="600" height="400" loading="lazy"><figcaption>Minimax 机制</figcaption></figure><p>最后又轮到了 X，我们将最大化所选棋盘的值。这就是为什么我们可以选择左边的子棋盘或右边的子棋盘或中间的子棋盘——这并不重要，因为它们的值是一样的。</p><p>最后，X 的最佳棋步是在第 7、8 或 9 位，以最大限度地提高其获胜的机会。如果你仍然不相信，可以取任何棋盘组合并画出组合树，你会得到一个满意的结果——我强烈建议在纸上画出来。</p><h2 id="minimax--1"><strong>Minimax </strong>算法的局限性</h2><p>正如你所看到的，该算法是递归的，执行次数可能会变得很大。</p><p>例如，对于井字游戏，执行次数大约为 “9！”（9 的阶乘）。原因是第一步棋有 9 种可能性，然后每一步棋有 8 种可能性，以此类推。</p><p>这对井字棋来说不是问题，但考虑一下国际象棋游戏。如果我们要写出组合的数量，整个宇宙都不够用了。所以，最小值经常被用作引擎的一部分，但它不足以满足我们的需要。</p><h2 id="--1">如何提高算法的时间复杂度</h2><p>你可能已经注意到，使用这种方法可能会导致一些重复的棋盘，我们需要多次计算它们的值。那么，为什么不在计算时存储每个棋盘的值呢？</p><p>所以现在，对于每一次迭代，我们将检查一个位置是否已经发生过。如果是的话，我们将使用它的存储值。否则，我们可以计算出位置的值，然后将其存储。</p><p>对于存储值，我们将使用一个字典，允许在 O(1) 中进行搜索。使用这种方法，我们可以降低时间复杂度——但在某些情况下，它仍然是无效的。</p><p>我曾经用这种算法做了一个四子棋游戏，它的运行时间很糟糕。因此，我没有寻找所有的组合，而是在一定的深度上停下来，这生成了一个可以提前看到 n 步的 AI。</p><p>如果你有兴趣，可以看看这个 <a href="https://github.com/HousseinBadra/badraconnect4AI.github.io.git">GitHub 仓库</a>里的四子游戏代码。我是很久以前写的，但仍然值得一看。</p><h2 id="-ai-">井字游戏 AI 代码</h2><p>现在我们先来实现一些辅助函数。我们将首先检查棋盘数组中是否有 3 个水平、垂直或对角线的非空字符串值。</p><pre><code class="language-javascript">// 棋盘数组
let xo=['','','',
        '','','',
        '','','']

// 编写这个函数时，我们需要确保相等的值不是空字符串

// 在这之前，我将写一个辅助函数来确保 3 个索引没有空字符串

function helper(index1,index2,index3){
  return xo[index1] !='' &amp;&amp; xo[index2] !='' &amp;&amp; xo[index3]!=''
}



function checkwin(){
  
   return (xo[0]==xo[1] &amp;&amp; xo[1] ==xo[2] &amp;&amp; helper(0,1,2)) ||
          (xo[3]==xo[4] &amp;&amp; xo[4] ==xo[5] &amp;&amp; helper(3,4,5)) ||
          (xo[6]==xo[7] &amp;&amp; xo[7] ==xo[8] &amp;&amp; helper(6,7,8)) ||
          (xo[0]==xo[3] &amp;&amp; xo[3] ==xo[6] &amp;&amp; helper(0,3,6)) ||
          (xo[1]==xo[4] &amp;&amp; xo[4] ==xo[7] &amp;&amp; helper(1,4,7)) || 
          (xo[2]==xo[5] &amp;&amp; xo[5] ==xo[8] &amp;&amp; helper(2,5,8)) ||
          (xo[0]==xo[4] &amp;&amp; xo[4] ==xo[8] &amp;&amp; helper(0,4,8)) ||
          (xo[2]==xo[4] &amp;&amp; xo[4] ==xo[6] &amp;&amp; helper(2,4,6))
   
}

// 最后是一个检查是否有平局的函数，它将检查所有的棋盘值是否为空字符串。这个函数只有在首先检查了没有获胜条件之后才会起作用。

function boardfull(){
  return xo.every((elem)=&gt;{
   return elem !=''
  })
}</code></pre><p>现在我们有了检查是否存在获胜状态的函数，我们最终可以像这样写出 Minimax（我在代码中添加了注释以帮助解释）。</p><pre><code class="language-javascript">// 正如我之前所说的，算法将通过 board 和 ismax 参数来检查我们是否要最大化或最小化某一个回合

function minimax(board,depth,ismax){
    
    // 这是一个递归函数，所以我们应该首先设置基本情况，这将是获得平局、胜利或达到深度。
    
    // 正如你在可视化中所看到的，当出现平局或获胜条件时，不会再产生子棋盘。因此，如果在轮到 X 的时候发生了获胜的条件。赢的是 X，这就是我返回 1 的原因，同样的逻辑也适用于 Y 赢时，我在最小化时返回 -1。
    
    if (checkwin()) return ismax ? 1 : -1
    if (boardfull()) return 0
      
    if(ismax){
        
        // 当我们进行最大化时，我们将设置一个计数器为 -Infinity，只要我们遇到一个高于计数器的棋盘值，我们就将其视为最佳棋步。
        
     let best=-Infinity
     board.forEach((elem,index)=&gt;{
         
         // 现在，为了检查所有产生的位置，我们将在棋盘上进行迭代，只要有一个可用的，我们就将其设置为 X，并在这个位置上运行 minimax。
         
     if(elem ==''){
       board[index]='X'
       let  localscores=minimax(test,depth+1,false)
       
       // 我在这里把棋盘重新设置到相同的位置
       
       board[index]=''
       best=max(best,localscores)
     }})
     return best
     }
    
 // 这里的 else 意味着我们正在最小化
    
 else{
 
     // 在这里，我们将设置我们的计数器为 + Infinity，因为我们想找到可能的最低值
     
    let best=Infinity
    board.forEach((elem,index)=&gt;{
    if(elem ==''){
     board[index]=humanicon
     let localscores=minimax(test,depth+1,true)
     board[index]=''
     best=min(best,localscores)
    }})
    return best
  }
}</code></pre><p>我们完成了！</p><h2 id="--2">总结</h2><p>在这篇文章中，我们了解了 Minimax 算法、它的机制、它的局限性，以及如何改进它。现在你可以去定制 Minimax 算法，使其在各种游戏中运行，创造一些很酷的游戏机器人。</p><p>最后，我希望你从这篇文章中学到一些新东西。</p><p>如果你觉得这篇文章很有用，并想获得更多精彩的内容，请在 LinkedIn 上关注我。这将会有很大的帮助。</p> ]]>
                </content:encoded>
            </item>
        
            <item>
                <title>
                    <![CDATA[ 用 Azure AI 解决数独问题 ]]>
                </title>
                <description>
                    <![CDATA[ 这篇文章将带你使用 Azure 表单识别器创建一个数独解题器，Azure 表单识别器是一个由 AI 驱动的文档提取服务。 我们的程序将首先让用户上传一张数独表的图片，接下来我们将从图像中提取数据，然后基于此数据实现数独解题算法。 我们将在后端使用 .NET，在前端使用 Angular，并用 Angular material 来设计程序的 UI 风格。 下面是此软件的一个演示。 准备  * 从 https://nodejs.org/zh-cn/download/  下载并安装最新的 LTS 版本的 Node.js  * 从 https://cli.angular.io/  下载并安装 Angular CLI  * 一个 Azure 订阅账号，你可以在 https://azure.microsoft.com/zh-cn/free/  创建一个免费的 Azure 账号  * 从 https://dotnet.microsoft.com/zh-cn/download/dotnet/5.0  下载并安装 ]]>
                </description>
                <link>https://www.freecodecamp.org/chinese/news/solve-sudoku-using-azure-ai/</link>
                <guid isPermaLink="false">6300954060480505ded7a836</guid>
                
                    <category>
                        <![CDATA[ Azure ]]>
                    </category>
                
                    <category>
                        <![CDATA[ AI ]]>
                    </category>
                
                    <category>
                        <![CDATA[ 数独 ]]>
                    </category>
                
                <dc:creator>
                    <![CDATA[ HeZean ]]>
                </dc:creator>
                <pubDate>Fri, 19 Aug 2022 08:00:00 +0000</pubDate>
                <media:content url="https://chinese.freecodecamp.org/news/content/images/2022/08/6065634f9618b008528ab1f6.jpeg" medium="image" />
                <content:encoded>
                    <![CDATA[ <p data-test-label="translation-intro">
        <strong>原文：</strong> <a href="https://www.freecodecamp.org/news/solve-sudoku-using-azure-ai/" target="_blank" rel="noopener noreferrer" data-test-label="original-article-link">How to Solve a Sudoku Puzzle Using Azure AI</a>
      </p><!--kg-card-begin: markdown--><p>这篇文章将带你使用 Azure 表单识别器创建一个数独解题器，Azure 表单识别器是一个由 AI 驱动的文档提取服务。</p>
<p>我们的程序将首先让用户上传一张数独表的图片，接下来我们将从图像中提取数据，然后基于此数据实现数独解题算法。</p>
<p>我们将在后端使用 .NET，在前端使用 Angular，并用 Angular material 来设计程序的 UI 风格。</p>
<p>下面是此软件的一个演示。</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2021/04/SudokuSolver.gif" alt="SudokuSolver" width="600" height="400" loading="lazy"></p>
<h2 id="">准备</h2>
<ul>
<li>从 <a href="https://nodejs.org/zh-cn/download/">https://nodejs.org/zh-cn/download/</a> 下载并安装最新的 LTS 版本的 Node.js</li>
<li>从 <a href="https://cli.angular.io/">https://cli.angular.io/</a> 下载并安装 Angular CLI</li>
<li>一个 Azure 订阅账号，你可以在 <a href="https://azure.microsoft.com/zh-cn/free/">https://azure.microsoft.com/zh-cn/free/</a> 创建一个免费的 Azure 账号</li>
<li>从 <a href="https://dotnet.microsoft.com/zh-cn/download/dotnet/5.0">https://dotnet.microsoft.com/zh-cn/download/dotnet/5.0</a> 下载并安装 .NET Core 5.0 SDK</li>
<li>从 <a href="https://visualstudio.microsoft.com/downloads/">https://visualstudio.microsoft.com/downloads/</a> 下载并安装最新版本的 Visual Studio 2019（译者注：已推出了更新版本的 Visual Studio 2022）</li>
</ul>
<h2 id="">源代码</h2>
<p>你可以在 <a href="https://github.com/AnkitSharma-007/Azure-AI-Sudoku-solver">GitHub</a> 上获取源代码。</p>
<h2 id="azure">什么是 Azure 表单识别器认知服务</h2>
<p>得益于 <a href="https://azure.microsoft.com/zh-cn/services/form-recognizer/">Azure 表单识别器</a>认知服务，我们可以使用机器学习技术构建自动数据处理软件。它能从文档中提取文本、键-值对、选择标记、表格和结构。</p>
<p>在 REST API 或客户端库 SDK 的帮助下，我们可以轻松地调用表单识别器模型。</p>
<p>表单识别器认知服务提供了以下功能：</p>
<ul>
<li><strong>预构建模型</strong>：我们可以使用预先建立的模型，从独特的文件类型中提取数据，比如发票、收据、身份证和名片。</li>
<li><strong>自定义模型</strong>：我们可以使用自定义模型从表单中提取文本、键-值对、选择标记和表格数据。但我们需要使用自己的数据来训练自定义模型，使其适合我们的自定义需求。</li>
<li><strong>布局 API</strong>：它允许我们从文件中提取文本、选择标记和表格结构。</li>
</ul>
<p>在这篇文章中，我们将使用布局 API 从用户上传的数独表图片中提取内容。</p>
<h2 id="azure">如何创建 Azure 表单识别器认知服务资源</h2>
<p>登录 Azure 门户，在搜索栏中搜索“认知服务”并点击结果。在下一个屏幕上，点击“创建”按钮。这将打开认知服务市场的页面。在搜索栏中搜索“表单识别器”，并点击搜索结果中的“表单识别器”选项。</p>
<p>这将打开表单识别器 API 页面。点击“创建”按钮来新建一个表单识别器资源。如下图所示。</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2021/04/CreateFR.png" alt="CreateFR" width="600" height="400" loading="lazy"></p>
<p>在创建表格识别器的页面中，按照下面的指引填写详细信息。</p>
<ul>
<li><strong>订阅 / Subscription</strong>：从下拉菜单中选择订阅类型。</li>
<li><strong>资源组 / Resource group</strong>：选择一个现有的资源组或新建一个资源组。</li>
<li><strong>地区 / Region</strong>：选择你所在的地区。</li>
<li><strong>名称 / Name</strong>：为你的资源起一个独特的名字。</li>
<li><strong>定价 / Pricing tier</strong>：选择你想要的定价级别。</li>
</ul>
<p>点击“检查并创建”按钮。如下图所示。</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2021/04/ConfigureFR.png" alt="ConfigureFR" width="600" height="400" loading="lazy"></p>
<p>在下一个页面，勾选确认使用条款，检查核对你所提供的信息，然后点击“创建”按钮。</p>
<p>在你的资源被成功部署后，点击“转到资源”按钮。点击左边菜单上的“密钥和端点”链接。如下图所示。</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2021/04/FRKeys.png" alt="FRKeys" width="600" height="400" loading="lazy"></p>
<p>记下端点和页面上提供的任意一个密钥。我们将在本文的后半部分使用这些来从 .NET 代码中调用表单识别器服务的布局 API。</p>
<h2 id="aspnetcore">如何创建 ASP.NET Core 程序</h2>
<p>打开 Visual Studio 2019（或更新版本），点击“创建一个新项目”，这将打开一个“创建新项目”对话框。选择“ASP.NET Core with Angular”并点击“下一步”。如下图所示。</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2021/04/CreateProj.png" alt="CreateProj" width="600" height="400" loading="lazy"></p>
<p>现在你将进入“配置新项目”界面。为该应用程序设置一个名称，比如 <code>ngSudokuSolver</code>，并点击“下一步”。如下图所示。</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2021/04/CreateProj_1.png" alt="CreateProj_1" width="600" height="400" loading="lazy"></p>
<p>在附加信息页面，选择目标框架为 .NET 5.0，并将认证类型设置为“无”，如下图所示。</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2021/04/CreateProj_2.png" alt="CreateProj_2" width="600" height="400" loading="lazy"></p>
<p>现在，我们创建了一个工程。这个程序的文件夹结构应与下图类似：</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2021/04/Sol_Exp.png" alt="Sol_Exp" width="600" height="400" loading="lazy"></p>
<p><code>ClientApp</code> 文件夹中包含我们程序的 Angular 代码。<code>Controllers</code> 文件夹将包含 API 控制器。Angular 组件则位于 <code>ClientApp/src/app</code> 文件夹中。</p>
<p>默认模板包含了一些 Angular 组件。这些组件不会影响我们的应用程序，但为了简单起见，我们将从 <code>ClientApp/src/app</code> 文件夹中删除 <code>fetchdata</code> 和 <code>counter</code> 文件夹。同时，从 <code>app.module.ts</code> 文件中删除对这两个组件的引用。</p>
<h2 id="nuget">如何安装所需的 NuGet 包</h2>
<p>要安装 NuGet 包，请打开 工具 &gt;&gt; NuGet 包管理器 &gt;&gt; 包管理器控制台。这将在 Visual Studio 内打开包管理器控制台。</p>
<p>运行以下命令来安装 <a href="https://www.nuget.org/packages/Polly">Polly</a>。这个库能让你以流畅和线程安全的方式表达弹性和瞬时故障处理策略，如重试、断路、超时、隔板隔离和回退。</p>
<p><code>Install-Package Polly -Version 7.2.1</code></p>
<p>运行以下命令来安装 <a href="https://www.nuget.org/packages/Newtonsoft.Json/">Newtonsoft.Json</a>。</p>
<p><code>Install-Package Newtonsoft.Json -Version 13.0.1</code></p>
<h2 id="retrymessagehandler">如何创建 RetryMessage Handler</h2>
<p>右键单击 <code>ngSudokuSolver</code> 项目，选择 添加 &gt;&gt; 新文件夹。将该文件夹命名为 Models。</p>
<p>接下来，右键单击 Models 文件夹并选择 添加 &gt;&gt; 类 来添加一个新的类文件。把这个类的名字定为 <code>HttpRetryMessageHandler.cs</code>，然后点击“添加”。</p>
<p>将下面的代码放入这个类里面。</p>
<pre><code class="language-C#">using Newtonsoft.Json;
using Polly;
using System;
using System.Net.Http;
using System.Threading;
using System.Threading.Tasks;

namespace ngSudokuSolver.Models
{
    public class HttpRetryMessageHandler : DelegatingHandler
    {
        public HttpRetryMessageHandler(HttpClientHandler handler) : base(handler) { }

        protected override Task&lt;HttpResponseMessage&gt; SendAsync(
            HttpRequestMessage request,
            CancellationToken cancellationToken) =&gt;
            Policy
                .Handle&lt;HttpRequestException&gt;()
                .Or&lt;TaskCanceledException&gt;()
                .OrResult&lt;HttpResponseMessage&gt;(x =&gt;
                {
                    string result = x.Content.ReadAsStringAsync().GetAwaiter().GetResult();
                    dynamic array = JsonConvert.DeserializeObject(result);

                    if (array["status"] == "running")
                    {
                        return true;
                    }
                    else
                    {
                        return false;
                    }
                })
                .WaitAndRetryAsync(7, retryAttempt =&gt; TimeSpan.FromSeconds(Math.Pow(2, retryAttempt)))
                .ExecuteAsync(() =&gt; base.SendAsync(request, cancellationToken));
    }
}
</code></pre>
<p>我们将使用 RetryMessageHandler 来重试对 <code>sendAsync</code> 的调用。如果 HttpResponseMessage 的状态是“运行中”，我们将重试 HTTP 调用。</p>
<p>我们把最大重试次数设置为 7 次。每次重试时，都会增加等待时间，将其翻倍。如果所有重试次数已经用完，而 HttpResponseMessage 还没有成功，我们将返回 false。</p>
<h2 id="formrecognizer">如何加入 FormRecognizer 控制器</h2>
<p>我们现在在程序里加入一个新的控制器。</p>
<p>右键单击 Controllers 文件夹，选择 添加 &gt;&gt; 新项目。这将打开一个“添加新项目”的对话框。</p>
<p>从左侧栏选择 “Visual C#”，然后在模版栏中选择 “API Controller-Empty”，并将其命名为 <code>FormRecognizerController.cs</code>，点击“添加”。如下图所示。</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2021/04/AddController.png" alt="AddController" width="600" height="400" loading="lazy"></p>
<p>将以下代码放入该类。</p>
<pre><code class="language-C#">using Microsoft.AspNetCore.Mvc;
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
using ngSudokuSolver.Models;
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Net.Http;
using System.Net.Http.Headers;
using System.Threading.Tasks;

namespace ngSudokuSolver.Controllers
{
    [Produces("application/json")]
    [Route("api/[controller]")]
    public class FormRecognizerController : ControllerBase
    {
        static string endpoint;
        static string apiKey;

        public FormRecognizerController()
        {
            endpoint = "https://sudokusolver.cognitiveservices.azure.com/";
            apiKey = "a9f75796b3ba49bdade48eb3b905cb0e";
        }

        [HttpPost, DisableRequestSizeLimit]
        public async Task&lt;string[][]&gt; Post()
        {
            try
            {
                string[][] sudokuArray = GetNewSudokuArray();

                if (Request.Form.Files.Count &gt; 0)
                {
                    var file = Request.Form.Files[Request.Form.Files.Count - 1];

                    if (file.Length &gt; 0)
                    {
                        var memoryStream = new MemoryStream();
                        file.CopyTo(memoryStream);
                        byte[] imageFileBytes = memoryStream.ToArray();
                        memoryStream.Flush();

                        string SudokuLayoutJSON = await GetSudokuBoardLayout(imageFileBytes);
                        if (SudokuLayoutJSON.Length &gt; 0)
                        {
                            sudokuArray = GetSudokuBoardItems(SudokuLayoutJSON);
                        }
                    }
                }

                return sudokuArray;
            }
            catch
            {
                throw;
            }
        }

        static async Task&lt;string&gt; GetSudokuBoardLayout(byte[] byteData)
        {
            HttpClient client = new();
            client.DefaultRequestHeaders.Add("Ocp-Apim-Subscription-Key", apiKey);
            string uri = endpoint + "formrecognizer/v2.1-preview.3/layout/analyze";
            string LayoutJSON = string.Empty;

            using (ByteArrayContent content = new(byteData))
            {
                HttpResponseMessage response;
                content.Headers.ContentType = new MediaTypeHeaderValue("image/png");
                response = await client.PostAsync(uri, content);

                if (response.IsSuccessStatusCode)
                {
                    HttpHeaders headers = response.Headers;

                    if (headers.TryGetValues("Operation-Location", out IEnumerable&lt;string&gt; values))
                    {
                        string OperationLocation = values.First();
                        LayoutJSON = await GetJSON(OperationLocation);
                    }
                }
            }
            return LayoutJSON;
        }

        static async Task&lt;string&gt; GetJSON(string endpoint)
        {
            using var client = new HttpClient(new HttpRetryMessageHandler(new HttpClientHandler()));
            var request = new HttpRequestMessage();
            request.Method = HttpMethod.Get;
            request.RequestUri = new Uri(endpoint);

            client.DefaultRequestHeaders.Add("Ocp-Apim-Subscription-Key", apiKey);

            var response = await client.SendAsync(request);
            var result = response.Content.ReadAsStringAsync().GetAwaiter().GetResult();

            return result;
        }

        static string[][] GetSudokuBoardItems(string LayoutData)
        {
            string[][] sudokuArray = GetNewSudokuArray();
            dynamic array = JsonConvert.DeserializeObject(LayoutData);
            int countOfCells = ((JArray)array?.analyzeResult?.pageResults[0]?.tables[0]?.cells).Count;

            for (int i = 0; i &lt; countOfCells; i++)
            {
                int rowIndex = array.analyzeResult.pageResults[0].tables[0].cells[i].rowIndex;
                int columnIndex = array.analyzeResult.pageResults[0].tables[0].cells[i].columnIndex;

                sudokuArray[rowIndex][columnIndex] = array.analyzeResult.pageResults[0].tables[0].cells[i]?.text;
            }
            return sudokuArray;
        }

        static string[][] GetNewSudokuArray()
        {
            string[][] sudokuArray = new string[9][];

            for (int i = 0; i &lt; 9; i++)
            {
                sudokuArray[i] = new string[9];
            }

            return sudokuArray;
        }
    }
}
</code></pre>
<p>在 FormRecognizerController 类的构造函数中，我们初始化了表单识别器 API 的密钥和端点 URL。</p>
<p>上面的 Post 方法将接收请求体中作为文件集合的图像数据，并返回一个二维数组。我们将把图像数据转换为字节数组并调用 <code>GetSudokuBoardLayout</code> 方法。如果我们得到一个成功的响应并且 JSON 结果非空，我们将调用 <code>GetSudokuBoardItems</code> 方法。</p>
<p>在 <code>GetSudokuBoardLayout</code> 方法中，我们将实例化一个新的 HttpClient。我们会在请求头中传递订阅密钥。</p>
<p>当我们调用表单识别器 API 时，Azure 服务将返回状态码 202。这表明该服务已经接受了请求，并将在稍后开始处理。</p>
<p>响应包括一个“Operation-Location”首部。“Operation-Location”字段里的数据就我们将用来获取表单识别结果的 URL，这个 URL 会在 48 小时后过期。</p>
<p>在获取表单识别结果前需要等待一段时间，等待时间的长短与文本的长度相关。</p>
<p>这里就要用到了我们前面配置的 RetryMessageHandler。我们将从“Operation-Location”首部获取 URL，并调用 <code>GetJSON</code> 方法来获取 JSON 结果。</p>
<p>在 <code>GetJSON</code> 方法中，我们创建了一个 HttpClient，并用自定义的 <code>HttpRetryMessageHandler</code> 来初始化它。这个方法将以字符串的形式返回 JSON 响应。</p>
<p><code>GetSudokuBoardItems</code> 方法接受 JSON 字符串。然后它将遍历 JSON 字符串中的 table 属性，以准备二维的 <code>sudokuArray</code>。</p>
<h2 id="">现在，让我们开始实现程序的客户端部分</h2>
<p>客户端的代码位于 ClientApp 文件夹中。我们将使用 Angular CLI 来处理客户端的代码。</p>
<blockquote>
<p>Angular CLI 并非你的唯一选择。本文中使用 Angular CLI 是因为它对用户友好且直接。如果你不想使用 CLI，你可以手动创建组件和服务的文件。</p>
</blockquote>
<p>进入 <code>ngSudokuSolver/ClientApp</code> 文件夹并在此打开一个命令窗口。我们将在这个窗口中执行我们所有的 Angular CLI 命令。</p>
<h2 id="angularmaterial">如何安装 Angular Material</h2>
<p>运行以下命令以将 Angular Material 添加到项目中。</p>
<p><code>ng add @angular/material</code></p>
<p>该命令将为你的项目安装 Angular Material，它会询问以下问题，以确定要包括哪些功能：</p>
<ul>
<li>Choose a prebuilt theme name, or "custom" for a custom theme: <strong>请选择 Indigo/Pink 主题</strong></li>
<li>Set up global Angular Material typography styles? (Y/n): <strong>Y</strong></li>
<li>Set up browser animations for Angular Material? (Y/n): <strong>Y</strong></li>
</ul>
<p>如下图所示：</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2021/04/ngMaterial.png" alt="ngMaterial" width="600" height="400" loading="lazy"></p>
<h2 id="angularmaterial">如何为 Angular Material 添加模块</h2>
<p>运行以下命令来创建一个新的模块。</p>
<p><code>ng g m ng-material</code></p>
<p>将下面的代码放入 <code>src\app\ng-material\ng-material.module.ts</code> 文件中。</p>
<pre><code class="language-typescript">import { NgModule } from '@angular/core';
import { CommonModule } from '@angular/common';
import { MatButtonModule } from '@angular/material/button';
import { MatCardModule } from '@angular/material/card';
import { MatInputModule } from '@angular/material/input';
import { MatToolbarModule } from '@angular/material/toolbar';
import { MatDividerModule } from '@angular/material/divider';
import { MatIconModule } from '@angular/material/icon';

const materialModules = [
  MatButtonModule,
  MatCardModule,
  MatInputModule,
  MatToolbarModule,
  MatDividerModule,
  MatIconModule,
];

@NgModule({
  declarations: [],
  imports: [CommonModule, ...materialModules],
  exports: [...materialModules],
})
export class NgMaterialModule {}
</code></pre>
<p>我们正在导入我们将在这个应用程序中使用的 Angular material 组件的所有必要模块。独立的 Angular material 模块将使该应用易于维护。</p>
<p>在 <code>app.module.ts</code> 文件中导入 <code>NgMaterialModule</code>，如下所示：</p>
<pre><code class="language-typescript">import { NgMaterialModule } from './ng-material/ng-material.module';

@NgModule({
	...
	imports: [
		...
		NgMaterialModule,
	],
})
</code></pre>
<h2 id="">如何配置程序的导航栏</h2>
<p>打开 <code>nav-menu.component.html</code>，在里面放入以下代码。</p>
<pre><code class="language-html">&lt;mat-toolbar color="primary" class="mat-elevation-z2"&gt;
  &lt;mat-toolbar-row&gt;
    &lt;div&gt;
      &lt;button mat-button [routerLink]='["/"]'&gt;
        &lt;mat-icon&gt;book&lt;/mat-icon&gt; Sudoku Solver
      &lt;/button&gt;
    &lt;/div&gt;
  &lt;/mat-toolbar-row&gt;
&lt;/mat-toolbar&gt;
</code></pre>
<p>现在，我们成功地在程序中加入了 Angular material 工具栏，和一个链接到程序根路由的按钮。</p>
<h2 id="">如何创建表单识别器服务</h2>
<p>我们将创建一个 Angular 服务，它将调用 Web API 端点并将响应传递给我们的组件。请运行下面的命令。</p>
<p><code>ng g s services\form-recognizer</code></p>
<p>这个命令将创建一个名为 services 的文件夹，然后在里面创建以下两个文件：</p>
<ul>
<li>form-recognizer.service.ts - 服务类文件</li>
<li>form-recognizer.service.spec.ts - 服务的单元测试文件</li>
</ul>
<p>将下面的代码放入 <code>form-recognizer.service.ts</code> 文件中。</p>
<pre><code class="language-typescript">import { Injectable } from '@angular/core';
import { HttpClient } from '@angular/common/http';

@Injectable({
  providedIn: 'root',
})
export class FormRecognizerService {
  baseURL: string;

  constructor(private http: HttpClient) {
    this.baseURL = '/api/FormRecognizer';
  }

  getSudokuTableFromImage(image: FormData) {
    return this.http.post(this.baseURL, image);
  }
}
</code></pre>
<p>我们定义了一个存有 API 的端点 URL 的变量 baseURL。我们会在构造器中初始化 baseURL，并将其赋值为 <code>FormRecognizerController</code> 的端点。</p>
<p><code>getSudokuTableFromImage</code> 方法将向 <code>FormRecognizerController</code> 发送一个 Post 请求并提供 FormData 类型的参数。它将获取一个表示数独表的二维数组。</p>
<h2 id="">如何更新主页组件</h2>
<p>把下面的代码放入 <code>home.component.html</code> 中。</p>
<pre><code class="language-html">&lt;div class="container"&gt;
  &lt;h1 class="display-4"&gt; 用 Azure AI 解决数独问题 &lt;/h1&gt;
  &lt;mat-divider&gt;&lt;/mat-divider&gt;
  &lt;div class="row mt-3"&gt;
    &lt;div class="col-md-6"&gt;
      &lt;mat-card class="mat-elevation-z4"&gt;
        &lt;mat-card-content&gt;
          &lt;table&gt;
            &lt;tr *ngFor="let row of gameBoard"&gt;
              &lt;td *ngFor="let col of gameBoard"&gt;
                {{game[row][col]}}
              &lt;/td&gt;
            &lt;/tr&gt;
          &lt;/table&gt;
        &lt;/mat-card-content&gt;
        &lt;mat-card-actions&gt;
          &lt;button type="button" mat-raised-button color="primary" (click)="SolveSudoku()"&gt; 解数独 &lt;/button&gt;
        &lt;/mat-card-actions&gt;
      &lt;/mat-card&gt;
    &lt;/div&gt;
    &lt;div class="col-md-6"&gt;
      &lt;div class="image-container"&gt;
        &lt;img class="preview-image" src={{imagePreview}}&gt;
      &lt;/div&gt;
      &lt;input type="file" (change)="uploadImage($event)" /&gt;
      &lt;hr /&gt;
      &lt;button mat-raised-button color="accent" (click)="GetSudokuTable()"&gt;
        &lt;span *ngIf="loading" class="spinner-border spinner-border-sm mr-1"&gt;&lt;/span&gt; 提取数独表
      &lt;/button&gt;
    &lt;/div&gt;
  &lt;/div&gt;
&lt;/div&gt;
</code></pre>
<p>我们创建了一个 9x9 的数独表格。我们定义了一个支持上传图片的文件上传控件。上传图片后，<code>&lt;img&gt;</code> 元素中将显示图片的预览。</p>
<p>点击“提取数独表”按钮将从图像中获取数独的内容，并将这些数字填入表格中。点击“解数独”将解决数独，并将结果更新到表格中。</p>
<p>把以下代码放入 <code>home.component.ts</code> 中。</p>
<pre><code class="language-typescript">import { Component, OnDestroy, OnInit } from '@angular/core';
import { Subject } from 'rxjs';
import { FormRecognizerService } from '../services/form-recognizer.service';
import { takeUntil } from 'rxjs/operators';

@Component({
  selector: 'app-home',
  templateUrl: './home.component.html',
  styleUrls: ['./home.component.scss'],
})
export class HomeComponent implements OnDestroy {
  gameBoard = [0, 1, 2, 3, 4, 5, 6, 7, 8];
  loading = false;
  imageFile;
  imagePreview;
  maxFileSize: number;
  isValidFile = true;
  status: string;
  DefaultStatus: string;
  imageData = new FormData();
  game = new Array(9);
  private unsubscribe$ = new Subject();

  constructor(private formRecognizerService: FormRecognizerService) {
    this.DefaultStatus = '允许上传的最大图像大小为 4 MB';
    this.status = this.DefaultStatus;
    this.maxFileSize = 4 * 1024 * 1024; // 4MB

    for (var i = 0; i &lt; this.game.length; i++) {
      this.game[i] = new Array(9);
    }
  }

  uploadImage(event) {
    this.imageFile = event.target.files[0];
    if (this.imageFile.size &gt; this.maxFileSize) {
      this.status = `文件大小为 ${this.imageFile.size} 比特，超出所允许的大小上限 ${this.maxFileSize} 比特。`;
      this.isValidFile = false;
    } else if (this.imageFile.type.indexOf('image') == -1) {
      this.status = '请上传有效的图片文件';
      this.isValidFile = false;
    } else {
      const reader = new FileReader();
      reader.readAsDataURL(event.target.files[0]);
      reader.onload = () =&gt; {
        this.imagePreview = reader.result;
      };
      this.status = this.DefaultStatus;
      this.isValidFile = true;
    }
  }

  GetSudokuTable() {
    if (this.isValidFile) {
      this.loading = true;
      this.imageData.append('imageFile', this.imageFile);

      this.formRecognizerService
        .getSudokuTableFromImage(this.imageData)
        .pipe(takeUntil(this.unsubscribe$))
        .subscribe(
          (result: any) =&gt; {
            this.game = result;
            this.loading = false;
          },
          () =&gt; {
            console.error();
            this.loading = false;
          }
        );
    }
  }

  SolveSudoku() {
    this.sudokuSolver(this.game);
  }

  ngOnDestroy() {
    this.unsubscribe$.next();
    this.unsubscribe$.complete();
  }

  private sudokuSolver(data) {
    for (let i = 0; i &lt; 9; i++) {
      for (let j = 0; j &lt; 9; j++) {
        if (data[i][j] == '') {
          for (let k = 1; k &lt;= 9; k++) {
            if (this.isSudokuValid(data, i, j, k)) {
              data[i][j] = `${k}`;
              if (this.sudokuSolver(data)) {
                return true;
              } else {
                data[i][j] = '';
              }
            }
          }
          return false;
        }
      }
    }
    return true;
  }

  private isSudokuValid(board, row, col, k) {
    for (let i = 0; i &lt; 9; i++) {
      const m = 3 * Math.floor(row / 3) + Math.floor(i / 3);
      const n = 3 * Math.floor(col / 3) + (i % 3);
      if (board[row][i] == k || board[i][col] == k || board[m][n] == k) {
        return false;
      }
    }
    return true;
  }
}
</code></pre>
<p>我们在 <code>HomeComponent</code> 的构造器中注入 formRecognizerService，并设置提示信息和允许的图像的最大尺寸。我们还将初始化一个二维数组来保存数独的值。</p>
<p>上传图片时将调用 <code>uploadImage</code> 方法。我们将检查上传的文件是否是一个有效的图像，并且在允许的大小限制之内。我们将使用一个 FileReader 对象来处理图像数据。readAsDataURL 方法将读取上传文件的内容。</p>
<p>当读取操作成功完成后，reader.onload 事件将会被触发。imagePreview 的值将被设置为 fileReader 对象的 ArrayBuffer 类型的返回值。</p>
<p>在 <code>GetSudokuTable</code> 方法中，我们将把图像文件附加到一个 FormData 类型的变量中。我们将调用服务的 <code>getSudokuTableFromImage</code>，并将结果绑定到游戏数组中。</p>
<p><code>sudokuSolver</code> 方法将接受数独表作为入参。然后我们使用回溯算法来解出这道数独。</p>
<h2 id="">运行演示程序</h2>
<p>按 F5 键来启动该程序。上传一张数独表的图片。点击“提取数独表”按钮，你会看到左边的表格中出现了从图像中提取出来的内容。如下图所示。</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2021/04/ExecDemo1.png" alt="ExecDemo1" width="600" height="400" loading="lazy"></p>
<p>点击“解数独”按钮。你可以在界面上看到数独的答案。如下图所示。</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2021/04/ExecDemo2.png" alt="ExecDemo2" width="600" height="400" loading="lazy"></p>
<h2 id=""><strong>总结</strong></h2>
<p>我们用 Angular 和 Azure 表单识别器服务创建了一个数独解题器。这个程序可以从用户上传的数独表的图片中提取数据。我们接着用回溯法解决数独问题。另外，我们使用了 Angular material 来设计程序的 UI 风格。</p>
<p>你可以从 <a href="https://github.com/AnkitSharma-007/Azure-AI-Sudoku-solver">GitHub</a> 上获取源代码，随便看看或把玩一下以加深你的理解。</p>
<h2 id="">拓展阅读</h2>
<ul>
<li><a href="https://ankitsharmablogs.com/optical-character-reader-using-angular-and-azure-computer-vision/">Optical Character Reader Using Angular And Azure Computer Vision（使用 Angular 和 Azure 计算机视觉的光学字符阅读器）</a></li>
<li><a href="https://ankitsharmablogs.com/multi-language-translator-using-blazor-and-azure-cognitive-services/">Multi-Language Translator Using Blazor And Azure Cognitive Services（使用 Blazor 和 Azure 认知服务的多语言翻译器）</a></li>
<li><a href="https://ankitsharmablogs.com/facebook-authentication-and-authorization-in-server-side-blazor-app/">Facebook Authentication And Authorization In Server-Side Blazor App（在服务器端 Blazor 应用中进行 Facebook 认证和授权）</a></li>
<li><a href="https://ankitsharmablogs.com/continuous-deployment-for-angular-app-using-heroku-and-github/">Continuous Deployment For Angular App Using Heroku And GitHub（使用 Heroku 和 GitHub 为 Angular 应用程序进行持续部署）</a></li>
<li><a href="https://ankitsharmablogs.com/going-serverless-with-blazor/">Going Serverless With Blazor（利用 Blazor 实现无服务器化）</a></li>
</ul>
<p>如果你喜欢这篇文章，请把它分享给你的朋友。你也可以在<a href="https://twitter.com/ankitsharma_007">推特</a>和<a href="https://www.linkedin.com/in/ankitsharma-007/">领英</a>上与我联系。</p>
<p>原文发表于 <a href="https://ankitsharmablogs.com/">https://ankitsharmablogs.com/</a>。</p>
<!--kg-card-end: markdown--> ]]>
                </content:encoded>
            </item>
        
            <item>
                <title>
                    <![CDATA[ 如何使用 React、Firebase 和 Alan AI 构建语音控制的 Todo 应用 ]]>
                </title>
                <description>
                    <![CDATA[ React Todo 应用程序通常是非常基础的——事实上，如果你是 React 初学者，并且想要提升你的技能，构建这类项目是一个很好的练习方式。 但是，你是否曾经构建过一个 Todo 应用程序，用户可以在其中使用语音命令添加待办事项。这使它变得更加复杂和令人兴奋。 这就是我们在本教程中要做的。为了构建这个基于语音的 Todo 应用程序，我们将使用三个主要工具：  * React - 用于用户界面  * Firebase – 用于数据库  * Alan AI – 用于执行语音命令 那么，让我们开始吧。 如何使用 React 创建 Todo 应用 UI 让我们首先创建一个 React 应用程序。只需输入以下命令： npx create-react-app react-todo-alan-firebase 它将像这样初始化并创建我们的 React 应用程序。然后，我们将导航到该文件夹，并使用 npm start 启动应用程序。 现在让我们创建一个名为 components 的文件夹。它将包含我们的主要组件 Todo.js。 ]]>
                </description>
                <link>https://www.freecodecamp.org/chinese/news/build-a-voice-based-weather-application-using-react-and-alan-ai/</link>
                <guid isPermaLink="false">619df7330a5a9f063820dc16</guid>
                
                    <category>
                        <![CDATA[ React ]]>
                    </category>
                
                    <category>
                        <![CDATA[ Firebase ]]>
                    </category>
                
                    <category>
                        <![CDATA[ AI ]]>
                    </category>
                
                <dc:creator>
                    <![CDATA[ Chengjun.L ]]>
                </dc:creator>
                <pubDate>Tue, 23 Nov 2021 01:30:00 +0000</pubDate>
                <media:content url="https://chinese.freecodecamp.org/news/content/images/2021/11/How-to-Build-a-Weather-Application-using-React--36-.png" medium="image" />
                <content:encoded>
                    <![CDATA[ <p>React Todo 应用程序通常是非常基础的——事实上，如果你是 React 初学者，并且想要提升你的技能，构建这类项目是一个很好的练习方式。</p><p>但是，你是否曾经构建过一个 Todo 应用程序，用户可以在其中使用语音命令添加待办事项。这使它变得更加复杂和令人兴奋。</p><p>这就是我们在本教程中要做的。为了构建这个基于语音的 Todo 应用程序，我们将使用三个主要工具：</p><ul><li>React - 用于用户界面</li><li>Firebase – 用于数据库</li><li>Alan AI – 用于执行语音命令</li></ul><p>那么，让我们开始吧。</p><h2 id="-react-todo-ui">如何使用 React 创建 Todo 应用 UI</h2><p>让我们首先创建一个 React 应用程序。只需输入以下命令：</p><pre><code>npx create-react-app react-todo-alan-firebase</code></pre><p>它将像这样初始化并创建我们的 React 应用程序。然后，我们将导航到该文件夹，并使用 npm start 启动应用程序。</p><p>现在让我们创建一个名为 components 的文件夹。它将包含我们的主要组件 Todo.js。 如下创建一个 Todo.js 文件。</p><figure class="kg-card kg-code-card"><pre><code>import React from 'react'

export default function Todo() {
    return (
        &lt;div&gt;
            
        &lt;/div&gt;
    )
}
</code></pre><figcaption>Todo.js</figcaption></figure><p>为应用程序提供一个标题（或名称），例如基于语音的 Todo 应用程序或你选择的任何内容。</p><pre><code>import React from 'react'

export default function Todo() {
    return (
        &lt;div&gt;
            &lt;h2&gt;Voice-based Todo Application&lt;/h2&gt;
        &lt;/div&gt;
    )
}
</code></pre><p>然后，将此组件导入你的 App.js 文件。</p><figure class="kg-card kg-code-card"><pre><code>import './App.css';
import Todo from './components/Todo';

function App() {
  return (
    &lt;div&gt;
      &lt;Todo /&gt;
    &lt;/div&gt;
  );
}

export default App;
</code></pre><figcaption>App.js</figcaption></figure><p>让我们把标题居中。因此，在 Todo.js 组件中为 <code>h2</code> 指定一个 header 的 classname。</p><pre><code>import React from 'react'

export default function Todo() {
    return (
        &lt;div&gt;
            &lt;h2 className="header"&gt;Voice-based Todo Application&lt;/h2&gt;
        &lt;/div&gt;
    )
}
</code></pre><p>然后我们将在 App.css 文件中添加一些样式，将标题居中。</p><pre><code>.header{
  text-align: center;
}</code></pre><figure class="kg-card kg-image-card"><img src="https://www.freecodecamp.org/news/content/images/2021/11/Screenshot-2021-11-15-215217.png" class="kg-image" alt="Screenshot-2021-11-15-215217" width="600" height="400" loading="lazy"></figure><p>你将在屏幕上看到上面的输出，标题居中。</p><p>现在，让我们创建一张包含待办事项的卡片。</p><pre><code>import React from 'react'

export default function Todo() {
    return (
        &lt;div className="todo-main"&gt;
            &lt;h2 className="header"&gt;Voice-based Todo Application&lt;/h2&gt;

            &lt;div className="todo-card"&gt;

            &lt;/div&gt;
        &lt;/div&gt;
    )
}
</code></pre><p>创建一个 div 并使 <code>className</code> 为 <code>todo-card</code>。你将看到最上级 div 的 <code>className</code> 为 <code>todo-main</code>。这是因为我们需要一切内容都居中。</p><pre><code>.todo-main {
  display: flex;
  justify-content: center;
  align-items: center;
  flex-direction: column;
}

.header {
  text-align: center;
}

.todo-card {
  border: 1px dashed #1f133d;
  height: 50vh;
  width: 50vh;
  border-radius: 20px;
}
</code></pre><p>将上述样式添加到 App.css 中。现在看起来像这样：</p><figure class="kg-card kg-image-card kg-width-wide"><img src="https://www.freecodecamp.org/news/content/images/2021/11/Screenshot-2021-11-15-220125.png" class="kg-image" alt="Screenshot-2021-11-15-220125" width="600" height="400" loading="lazy"></figure><p>现在让我们添加列表。</p><pre><code>import React from 'react'

export default function Todo() {
    return (
        &lt;div className="todo-main"&gt;
            &lt;h2 className="header"&gt;Voice-based Todo Application&lt;/h2&gt;

            &lt;div className="todo-card"&gt;
                &lt;div className="todo-list"&gt;
                    &lt;h3&gt;
                        Wash the Clothes
                    &lt;/h3&gt;
                &lt;/div&gt;
                &lt;div className="todo-list"&gt;
                    &lt;h3&gt;
                        Cook the Dinner
                    &lt;/h3&gt;
                &lt;/div&gt;
                &lt;div className="todo-list"&gt;
                    &lt;h3&gt;
                        Code some software
                    &lt;/h3&gt;
                &lt;/div&gt;
            &lt;/div&gt;
        &lt;/div&gt;
    )
}
</code></pre><p>所以，我创建了一个 div，它包含 <code>h3</code> 标签中的项目。这些文本目前是静态的，但我们很快也会创建来自 Firebase 数据库的动态文本。</p><p>这是我们更新的样式：</p><pre><code>.todo-main {
  display: flex;
  justify-content: center;
  align-items: center;
  flex-direction: column;
}

.header {
  text-align: center;
}

.todo-card {
  border: 1px dashed #1f133d;
  height: 50vh;
  width: 50vh;
  border-radius: 20px;
}

.todo-list{
  text-align: center;
}</code></pre><figure class="kg-card kg-image-card kg-width-wide"><img src="https://www.freecodecamp.org/news/content/images/2021/11/Screenshot-2021-11-15-220710.png" class="kg-image" alt="Screenshot-2021-11-15-220710" width="600" height="400" loading="lazy"></figure><p>这就是现在的输出，我们的列表中有三个项目。</p><p>现在，让我们添加一个关闭图标，它会在我们完成后删除每个项目。</p><p>为了添加图标，我们需要一个图标包。因此，让我们使用以下命令安装 React 图标：</p><pre><code>npm install react-icons --save</code></pre><p>安装后，从左侧边栏中选择任何图标包。</p><figure class="kg-card kg-image-card kg-width-wide"><img src="https://www.freecodecamp.org/news/content/images/2021/11/Screenshot-2021-11-15-220805.png" class="kg-image" alt="Screenshot-2021-11-15-220805" width="600" height="400" loading="lazy"></figure><p>我正在使用 Feather Icons，所以我将导入它。</p><p>首先，让我们使用以下命令导入该包：</p><pre><code>import { FiX } from "react-icons/fi";</code></pre><p>然后，在 h3 标签后调用它。</p><pre><code>import React from 'react'
import { FiX } from "react-icons/fi";
export default function Todo() {
    return (
        &lt;div className="todo-main"&gt;
            &lt;h2 className="header"&gt;Voice-based Todo Application&lt;/h2&gt;

            &lt;div className="todo-card"&gt;
                &lt;div className="todo-list"&gt;
                    &lt;h3&gt;
                        Wash the Clothes
                    &lt;/h3&gt;
                    &lt;FiX /&gt;
                &lt;/div&gt;
                &lt;div className="todo-list"&gt;
                    &lt;h3&gt;
                        Cook the Dinner
                    &lt;/h3&gt;
                    &lt;FiX /&gt;
                &lt;/div&gt;
                &lt;div className="todo-list"&gt;
                    &lt;h3&gt;
                        Code some software
                    &lt;/h3&gt;
                    &lt;FiX /&gt;
                &lt;/div&gt;
            &lt;/div&gt;
        &lt;/div&gt;
    )
}
</code></pre><figure class="kg-card kg-image-card kg-width-wide"><img src="https://www.freecodecamp.org/news/content/images/2021/11/Screenshot-2021-11-15-221145.png" class="kg-image" alt="Screenshot-2021-11-15-221145" width="600" height="400" loading="lazy"></figure><p>上面，这就是我们的应用程序现在的样子。但是我们希望关闭图标和待办事项在同一行。</p><p>为 Icon 指定一个 className 为 <code>close-icon</code>。</p><pre><code> &lt;FiX className="close-icon" /&gt;</code></pre><p>并在 App.css 中，添加以下样式：</p><pre><code>.todo-list {
  display: flex;
  align-items: center;
  justify-content: center;
}

.close-icon {
  margin-left: 10px;
}
</code></pre><p>到目前为止，我们的 Todo.js 组件将具有以下最终代码：</p><pre><code>import React from 'react'
import { FiX } from "react-icons/fi";
export default function Todo() {
    return (
        &lt;div className="todo-main"&gt;
            &lt;h2 className="header"&gt;Voice-based Todo Application&lt;/h2&gt;

            &lt;div className="todo-card"&gt;
                &lt;div className="todo-list"&gt;
                    &lt;h3&gt;
                        Wash the Clothes
                    &lt;/h3&gt;
                    &lt;FiX className="close-icon" /&gt;
                &lt;/div&gt;
                &lt;div className="todo-list"&gt;
                    &lt;h3&gt;
                        Cook the Dinner
                    &lt;/h3&gt;
                    &lt;FiX className="close-icon" /&gt;
                &lt;/div&gt;
                &lt;div className="todo-list"&gt;
                    &lt;h3&gt;
                        Code some software
                    &lt;/h3&gt;
                    &lt;FiX className="close-icon" /&gt;
                &lt;/div&gt;
            &lt;/div&gt;
        &lt;/div&gt;
    )
}
</code></pre><p>App.css 是这样的：</p><pre><code>.todo-main {
  display: flex;
  justify-content: center;
  align-items: center;
  flex-direction: column;
}

.header {
  text-align: center;
}

.todo-card {
  border: 1px dashed #1f133d;
  height: 50vh;
  width: 50vh;
  border-radius: 20px;
}

.todo-list {
  display: flex;
  align-items: center;
  justify-content: center;
}

.close-icon {
  margin-left: 10px;
}
</code></pre><p>UI 看起来是这样：</p><figure class="kg-card kg-image-card kg-width-wide"><img src="https://www.freecodecamp.org/news/content/images/2021/11/Screenshot-2021-11-15-221826.png" class="kg-image" alt="Screenshot-2021-11-15-221826" width="600" height="400" loading="lazy"></figure><h2 id="-alan-ai-react-"><strong>如何把 Alan AI 添加到我们的 React 项目</strong></h2><p>访问 <a href="https://alan.app/">https://alan.app/</a>，创建你的账号。</p><p>登录后，你可以创建一个项目。只需单击加号按钮。</p><figure class="kg-card kg-image-card kg-width-full"><img src="https://www.freecodecamp.org/news/content/images/2021/11/Screenshot-2021-11-15-222106.png" class="kg-image" alt="Screenshot-2021-11-15-222106" width="600" height="400" loading="lazy"></figure><p>但是在使用之前，我们需要先安装 Alan AI 包。因此，请访问 <a href="https://alan.app/docs/client-api/web/react">https://alan.app/docs/client-api/web/react</a>，获取 React 文档。</p><p>使用以下命令安装 Alan Al：</p><pre><code>npm install @alan-ai/alan-sdk-web --save</code></pre><p>现在，让我们在主 App.js 文件中导入 Alan。</p><pre><code>import alanBtn from "@alan-ai/alan-sdk-web";</code></pre><p>然后，我们需要创建一个 useEffect Hook。每当我们的组件被挂载或加载时，它就会启动我们的 Alan 服务。</p><pre><code>useEffect(() =&gt; {
    alanBtn({
        key: 'YOUR_KEY_FROM_ALAN_STUDIO_HERE',
        onCommand: (commandData) =&gt; {
            if (commandData.command === 'go:back') {
                // Call the client code that will react to the received command
            }
        }
    });
}, []);</code></pre><p>这个 <code>alanBtn</code> 需要一个我们需要得到的密钥。因此，在你在 Alan 中创建的项目中，你应该会在顶部栏中看到一个 “Integrations” 按钮。 单击该按钮。</p><figure class="kg-card kg-image-card kg-width-full"><img src="https://www.freecodecamp.org/news/content/images/2021/11/Screenshot-2021-11-15-222645.png" class="kg-image" alt="Screenshot-2021-11-15-222645" width="600" height="400" loading="lazy"></figure><p>在那里你会得到你的密钥。</p><p>将该密钥粘贴到 React 应用程序中的 <code>alanBtn</code> 中，如下所示：</p><pre><code>import './App.css';
import Todo from './components/Todo';
import alanBtn from "@alan-ai/alan-sdk-web";
import { useEffect } from 'react'
function App() {
  useEffect(() =&gt; {
    alanBtn({
      key: '86e866fbe49666abd385ee5c9f9cbf5c2e956eca572e1d8b807a3e2338fdd0dc/stage',
      onCommand: (commandData) =&gt; {

      }
    });
  }, []);
  return (
    &lt;div&gt;
      &lt;Todo /&gt;
    &lt;/div&gt;
  );
}

export default App;
</code></pre><p>现在，检查输出，你将看到一个麦克风按钮。</p><figure class="kg-card kg-image-card kg-width-wide"><img src="https://www.freecodecamp.org/news/content/images/2021/11/Screenshot-2021-11-15-222913.png" class="kg-image" alt="Screenshot-2021-11-15-222913" width="600" height="400" loading="lazy"></figure><p>现在，我们需要在我们的 Alan 应用程序中创建一个 Intent。它将以添加命令开始，例如添加洗涤衣服、添加编写一些代码等。因此，让我们为此编写代码：</p><pre><code>intent('Add $(item* (.*))', (p) =&gt; {
    if(p.item.value){
        p.play({ command: 'todoApp', data: p.item.value });
        p.play(`${p.item.value} added`);
    }
    else{
        p.play(`Cannot add Empty Item`);
    }
})</code></pre><p>它还会将项目返回给我们，我们可以在 React 应用程序中看到。在这里，我们还有一个阻止我们添加任何空项目的检查。如果我们尝试，它会回复“无法添加空项目”。</p><p>现在，我们希望将语音项接收回我们的 React 应用程序。</p><pre><code>import './App.css';
import Todo from './components/Todo';
import alanBtn from "@alan-ai/alan-sdk-web";
import { useEffect } from 'react'
function App() {
  useEffect(() =&gt; {
    alanBtn({
      key: '86e866fbe49666abd385ee5c9f9cbf5c2e956eca572e1d8b807a3e2338fdd0dc/stage',
      onCommand: (commandData) =&gt; {
        console.log(commandData)
      }
    });
  }, []);
  return (
    &lt;div&gt;
      &lt;Todo /&gt;
    &lt;/div&gt;
  );
}

export default App;
</code></pre><p>只需 console.log commandData，你将得到以下结果。不要忘记单击麦克风按钮并说点什么。你将在控制台中看到你所说的内容。</p><figure class="kg-card kg-image-card kg-width-wide"><img src="https://www.freecodecamp.org/news/content/images/2021/11/Screenshot-2021-11-15-223428.png" class="kg-image" alt="Screenshot-2021-11-15-223428" width="600" height="400" loading="lazy"></figure><p>好了，我们的 Alan AI 现已准备就绪。</p><h2 id="-firebase-firestore-">如何使用 Firebase 将数据发送到 Firestore 数据库</h2><p>我们现在将这些数据发送到 Firebase。</p><p>但首先，我们需要安装它。访问 <a href="https://firebase.google.com/">https://firebase.google.com/</a>，并在那里创建一个项目。</p><p>要安装 Firebase，只需键入 <code>npm install firebase</code>。</p><p>然后，在项目中创建一个应用程序，如下所示：</p><figure class="kg-card kg-image-card kg-width-wide"><img src="https://www.freecodecamp.org/news/content/images/2021/11/Screenshot-2021-11-15-223909.png" class="kg-image" alt="Screenshot-2021-11-15-223909" width="600" height="400" loading="lazy"></figure><p>它会给我们一些配置数据。只需在 src 文件夹中创建一个文件，将其命名为 <code>firebase-config.js</code>，然后添加这些配置数据。</p><pre><code>// Import the functions you need from the SDKs you need
import { initializeApp } from "firebase/app";

// Your web app's Firebase configuration
const firebaseConfig = {
    apiKey: "AIzaSyCP8qL8z9BorGF3NZJsGb4vSaWHYyCVfc8",
    authDomain: "todo-firebase-alan.firebaseapp.com",
    projectId: "todo-firebase-alan",
    storageBucket: "todo-firebase-alan.appspot.com",
    messagingSenderId: "892581913000",
    appId: "1:892581913000:web:dbe08ac753c3adaab87d9d"
};

// Initialize Firebase
export const app = initializeApp(firebaseConfig);</code></pre><p>不要忘记导出 const 应用程序。</p><p>接下来，我们还需要访问 Firestore。所以，让我们将它导入到我们的 firebase-config.js 文件中。</p><pre><code>import { getFirestore } from 'firebase/firestore'</code></pre><pre><code>export const database = getFirestore(app);</code></pre><p>然后，在底部导出它。</p><pre><code>// Import the functions you need from the SDKs you need
import { initializeApp } from "firebase/app";
import { getFirestore } from 'firebase/firestore'

// Your web app's Firebase configuration
const firebaseConfig = {
    apiKey: "AIzaSyCP8qL8z9BorGF3NZJsGb4vSaWHYyCVfc8",
    authDomain: "todo-firebase-alan.firebaseapp.com",
    projectId: "todo-firebase-alan",
    storageBucket: "todo-firebase-alan.appspot.com",
    messagingSenderId: "892581913000",
    appId: "1:892581913000:web:dbe08ac753c3adaab87d9d"
};

// Initialize Firebase
export const app = initializeApp(firebaseConfig);
export const database = getFirestore(app);</code></pre><p>这是整个 firebase-config 代码。</p><p>现在，在 App.js 中，我们需要导入这个应用程序和数据库。</p><pre><code>import { app, database } from './firebase-config';</code></pre><p>接下来，我们需要创建与 Firebase Firestore 的连接。为此，我们需要 Firebase Firestore 的集合属性。此外，我们将导入 addDoc，我们将使用它向 Firestore 添加数据。</p><pre><code>import { collection, addDoc } from 'firebase/firestore';</code></pre><p>现在，让我们创建与数据库的连接。</p><p>创建一个变量，并将我们从 firebase-config 文件导入的数据库以及我们想要给集合的名称放入数据库中。由于我们希望集合是待办事项列表，所以可以添加以下内容：</p><pre><code>const databaseRef = collection(database, 'todo-list');</code></pre><p>要添加数据，我们需要 addDoc 属性。</p><p>addDoc 属性将采用两个参数。第一个是我们创建的连接，databaseRef。第二个是我们要添加的数据，作为一个对象。</p><p>将 addDoc 放入 useEffect Hook 中，如下所示：</p><pre><code>useEffect(() =&gt; {
    alanBtn({
      key: '86e866fbe49666abd385ee5c9f9cbf5c2e956eca572e1d8b807a3e2338fdd0dc/stage',
      onCommand: (commandData) =&gt; {
        addDoc(databaseRef, { item: commandData.data })
      }
    });
  }, []);</code></pre><p>目前，我们的 Firestore 是空的。</p><figure class="kg-card kg-image-card kg-width-wide"><img src="https://www.freecodecamp.org/news/content/images/2021/11/Screenshot-2021-11-15-225805.png" class="kg-image" alt="Screenshot-2021-11-15-225805" width="600" height="400" loading="lazy"></figure><p>现在，让我们试试这个。从 add 命令开始对着麦克风说话，它会在 Firebase Firestore 中可见。</p><figure class="kg-card kg-image-card kg-width-wide"><img src="https://www.freecodecamp.org/news/content/images/2021/11/Screenshot-2021-11-15-225922.png" class="kg-image" alt="Screenshot-2021-11-15-225922" width="600" height="400" loading="lazy"></figure><p>可以看到，我们所说的现在已添加到 Firebase 中。</p><p>现在，让我们尝试读取并显示这些数据。</p><p>将 <code>databaseRef</code> 作为 prop 传递给 Todo 组件。</p><pre><code>&lt;Todo databaseRef={databaseRef}/&gt;</code></pre><p>然后在 Todo 组件中接收它。</p><pre><code>export default function Todo({databaseRef})</code></pre><p>在 Todo.js 组件中创建一个 useEffect 钩子，并在 useEffect 钩子中创建函数 <code>getData</code>。</p><pre><code>useEffect(() =&gt; {
        const getData = async () =&gt; {
            
        }
        getData()
    }, [])</code></pre><p>我们将使用 getDocs 属性从 Firebase Firestore 读取数据。我们还需要连接 databaseRef，之前将它作为 prop 传递。</p><pre><code>let data = await getDocs(databaseRef);</code></pre><pre><code>const getData = async () =&gt; {
  let data = await getDocs(databaseRef);
  console.log(data.docs.map((item) =&gt; ({ ...item.data(), id: item.id })));
}</code></pre><p>我们映射传入的数据以使其更具可读性，还添加了项目的唯一 ID，应用程序稍后将用它删除该项目。</p><p>现在让我们检查控制台：</p><figure class="kg-card kg-image-card kg-width-wide"><img src="https://www.freecodecamp.org/news/content/images/2021/11/Screenshot-2021-11-15-231500.png" class="kg-image" alt="Screenshot-2021-11-15-231500" width="600" height="400" loading="lazy"></figure><p>我们就要完成项目啦。</p><p>现在，让我们将这些数据存储在 state 中，在 React 应用程序中显示。</p><p>导入 useState Hook，然后像这样创建一个数组状态：</p><pre><code> const [todoList, setTodoList] = useState([]);</code></pre><p>并使用 <code>setTodoList</code> 函数设置数据：</p><pre><code>setTodoList(data.docs.map((item) =&gt; ({ ...item.data(), id: item.id })));</code></pre><p>现在，让我们映射 todoList 数组。</p><pre><code>&lt;div className="todo-main"&gt;
            &lt;h2 className="header"&gt;Voice-based Todo Application&lt;/h2&gt;

            &lt;div className="todo-card"&gt;
                {todoList.map((todo) =&gt; {
                    return (
                        &lt;div className="todo-list"&gt;
                            &lt;h3&gt;
                                {todo.item}
                            &lt;/h3&gt;
                            &lt;FiX className="close-icon" /&gt;
                        &lt;/div&gt;
                    )
                })}
            &lt;/div&gt;
        &lt;/div&gt;</code></pre><p>我们将在 React UI 中看到我们的数据，它看起来像这样：</p><figure class="kg-card kg-image-card kg-width-wide"><img src="https://www.freecodecamp.org/news/content/images/2021/11/Screenshot-2021-11-15-231945.png" class="kg-image" alt="Screenshot-2021-11-15-231945" width="600" height="400" loading="lazy"></figure><p>现在，让我们更新它，使每个 Todo 都以大写字母开头。</p><p>设置 h3 标签的 <code>className</code> 为 <code>todo-items</code>。</p><pre><code>&lt;h3 className="todo-item"&gt;
 {todo.item}
&lt;/h3&gt;</code></pre><p>在 App.css 中，添加以下样式：</p><pre><code>.todo-item{
  text-transform: capitalize;
}</code></pre><p>你会看到每个 Todo 现在都大写了。</p><figure class="kg-card kg-image-card kg-width-wide"><img src="https://www.freecodecamp.org/news/content/images/2021/11/Screenshot-2021-11-15-232149.png" class="kg-image" alt="Screenshot-2021-11-15-232149" width="600" height="400" loading="lazy"></figure><p>现在，如果我们通过语音命令添加任何内容，在 React 中的列表应该更新。因此，让我们将 useEffect 配置为每次我们对应用程序说话时运行。</p><p>在 App.js 文件中，创建一个状态。它将是一个布尔值，初始状态为 false。</p><pre><code>const [update, setUpdate] = useState(false)</code></pre><p>当我们说些什么，或者 App.js 中的 useEffect 运行时，这个状态会变成 true。</p><pre><code>useEffect(() =&gt; {
    alanBtn({
      key: '86e866fbe49666abd385ee5c9f9cbf5c2e956eca572e1d8b807a3e2338fdd0dc/stage',
      onCommand: (commandData) =&gt; {
        addDoc(databaseRef, { item: commandData.data })
        .then(() =&gt; {
          setUpdate(true);
        })
      }
    });
  }, []);</code></pre><p>然后，我们将在 Todo.js 中传递更新状态和更新状态的函数。</p><pre><code>&lt;Todo databaseRef={databaseRef} update={update} setUpdate={setUpdate}/&gt;</code></pre><p>并在 Todo 组件中接收这两个。</p><pre><code>export default function Todo({ databaseRef, update, setUpdate })</code></pre><p>然后，在 Todo.js 的 useEffect 中，一旦它从 Firebase Firestore 获取我们的数据，就使用 setUpdate 函数将更新设置为 false，然后将更新状态放入依赖数组中。</p><pre><code>useEffect(() =&gt; {
        const getData = async () =&gt; {
            let data = await getDocs(databaseRef);
            setTodoList(data.docs.map((item) =&gt; ({ ...item.data(), id: item.id })));
        }
        getData()
        setUpdate(false)
    }, [update])</code></pre><p>这可能有点令人困惑，让我来解释一下。</p><p>当我们说话时，更新状态从 false 变为 true。然后，当完成从 Firestore 获取数据时，它会从 true 更改为 false。这样，状态就会不断变化。 所以每次更新状态改变时 useEffect 都会更新。</p><p>让我们现在试试看。说点什么，列表会动态更新。</p><figure class="kg-card kg-image-card kg-width-wide"><img src="https://www.freecodecamp.org/news/content/images/2021/11/Screenshot-2021-11-15-233617.png" class="kg-image" alt="Screenshot-2021-11-15-233617" width="600" height="400" loading="lazy"></figure><p>现在，让我们添加删除函数以从 Firebase Firestore 和我们的 React 应用程序中删除项目。</p><p>创建一个名为 <code>deleteItems</code> 的函数。</p><pre><code>const deleteItems = () =&gt; {
        
}</code></pre><p>并将此函数绑定到 <code>close</code> 图标，如下所示：</p><pre><code>&lt;FiX className="close-icon" onClick={() =&gt; deleteItems()}/&gt;</code></pre><p>当我们点击一个特定的关闭图标时，我们需要将该项目的 id 传递给函数，以删除该项目。</p><pre><code>&lt;FiX className="close-icon" onClick={() =&gt; deleteItems(todo.id)}/&gt;</code></pre><p>并在函数中接收它。</p><p>让我们尝试 console.log 我们的 id：</p><pre><code>const deleteItems = (id) =&gt; {
  console.log(id)
}</code></pre><figure class="kg-card kg-image-card kg-width-wide"><img src="https://www.freecodecamp.org/news/content/images/2021/11/Screenshot-2021-11-15-234631.png" class="kg-image" alt="Screenshot-2021-11-15-234631" width="600" height="400" loading="lazy"></figure><p>我们将在控制台中获得该特定 id。</p><p>现在，在删除任何待办事项之前，我们需要指定要删除的待办事项。因此，我们将使用此 id 创建一个引用。我们将使用 Firestore 的 doc 属性。</p><p>因此，首先使用以下命令从 Firestore 导入文档：</p><pre><code>import { getDocs, doc } from 'firebase/firestore';</code></pre><p>然后，在函数 1deleteItems1 中，添加以下代码：</p><pre><code>const data = doc(database, 'todo-list', id)</code></pre><p>该文档具有三个参数——数据库、集合名称和 id。我们有这三样东西。</p><p>数据库已从 firebase-config 导入。待办事项列表是 Firestore 集合的名称，以及我们从关闭按钮点击中获得的 id。</p><p>要删除一个项目，我们需要来自 Firestore 的另一个名为 deleteDoc 的属性。</p><pre><code>import { getDocs, doc, deleteDoc } from 'firebase/firestore';</code></pre><p>然后，只需添加以下内容：</p><pre><code>const deleteItems = (id) =&gt; {
   const data = doc(database, 'todo-list', id);
   deleteDoc(data)
}</code></pre><p>立即尝试——单击关闭图标，然后检查 Firestore。该项目将被删除。</p><p>但是我们在添加和读取操作期间遇到了同样的问题：在我们删除一个项目后，React 应用程序没有得到更新。</p><p>因此，首先要做的是将 getData 函数移到 useEffect Hook 之外。别担心，它仍然有效。</p><pre><code>const getData = async () =&gt; {
   let data = await getDocs(databaseRef);
   setTodoList(data.docs.map((item) =&gt; ({ ...item.data(), id: item.id })));
}

useEffect(() =&gt; {   
   getData()
   setUpdate(false)
}, [update])</code></pre><p>而在 deleteDoc 函数中，我们需要再次调用 getData 函数，在用户删除一个 item 后获取更新的数据。</p><pre><code>const deleteItems = (id) =&gt; {
    const data = doc(database, 'todo-list', id);
    deleteDoc(data)
    .then(() =&gt; {
       getData()
    })
}</code></pre><p>这是 Todo.js 的全部代码：</p><pre><code>import React, { useEffect, useState } from 'react'
import { FiX } from "react-icons/fi";
import { database } from '../firebase-config';
import { getDocs, doc, deleteDoc } from 'firebase/firestore';
export default function Todo({ databaseRef, update, setUpdate }) {
    const [todoList, setTodoList] = useState([]);
    const getData = async () =&gt; {
        let data = await getDocs(databaseRef);
        setTodoList(data.docs.map((item) =&gt; ({ ...item.data(), id: item.id })));
    }
    useEffect(() =&gt; {
        getData()
        setUpdate(false)
    }, [update])

    const deleteItems = (id) =&gt; {
        const data = doc(database, 'todo-list', id);
        deleteDoc(data)
            .then(() =&gt; {
                getData()
            })
    }

    return (
        &lt;div className="todo-main"&gt;
            &lt;h2 className="header"&gt;Voice-based Todo Application&lt;/h2&gt;

            &lt;div className="todo-card"&gt;
                {todoList.map((todo) =&gt; {
                    return (
                        &lt;div className="todo-list"&gt;
                            &lt;h3 className="todo-item"&gt;
                                {todo.item}
                            &lt;/h3&gt;
                            &lt;FiX className="close-icon" onClick={() =&gt; deleteItems(todo.id)} /&gt;
                        &lt;/div&gt;
                    )
                })}
            &lt;/div&gt;
        &lt;/div&gt;
    )
}
</code></pre><p>这是 App.js 的代码：</p><pre><code>import './App.css';
import Todo from './components/Todo';
import alanBtn from "@alan-ai/alan-sdk-web";
import { useEffect, useState } from 'react';
import { app, database } from './firebase-config';
import { addDoc, collection } from '@firebase/firestore';
function App() {
  const databaseRef = collection(database, 'todo-list');
  const [update, setUpdate] = useState(false)
  useEffect(() =&gt; {
    alanBtn({
      key: '86e866fbe49666abd385ee5c9f9cbf5c2e956eca572e1d8b807a3e2338fdd0dc/stage',
      onCommand: (commandData) =&gt; {
        addDoc(databaseRef, { item: commandData.data })
          .then(() =&gt; {
            setUpdate(true);
          })
      }
    });
  }, []);
  return (
    &lt;div&gt;
      &lt;Todo databaseRef={databaseRef} update={update} setUpdate={setUpdate} /&gt;
    &lt;/div&gt;
  );
}

export default App;
</code></pre><p>现在，我们可以使用语音命令添加项目，并将其存储在我们的 Firebase 数据库中。它也会出现在我们的 React 应用程序中。</p><p>还有最后一件事要做。在我们的 App.css 文件中，我们需要将卡片的高度设置为 auto，以防止文本溢出。</p><pre><code>.todo-card {
  border: 1px dashed #1f133d;
  height: auto;
  width: 50vh;
  border-radius: 20px;
}</code></pre><p>这只是一个简单的 UI 设计。如果需要，你可以设计自己喜欢的。来吧，构建这个很棒的应用程序。</p><p>你可以在我的 YouTube 频道查看视频 <a href="https://youtu.be/BzHbI2AAXGs">Let's build a Voice-Based Todo Application using React, Firebase, and Alan AI</a>。</p><p>谢谢阅读，祝你学习愉快！</p><p>原文：<a href="https://www.freecodecamp.org/news/build-a-voice-based-weather-application-using-react-and-alan-ai/">How to Build a Voice-Based Todo App using React, Firebase, and Alan AI</a>，作者：<a href="https://www.freecodecamp.org/news/author/nishant-kumar/">Nishant Kumar</a></p> ]]>
                </content:encoded>
            </item>
        
            <item>
                <title>
                    <![CDATA[ 通过 AI 将原型图转换为 HTML 和 CSS ]]>
                </title>
                <description>
                    <![CDATA[ 在未来几年，深度学习会改变前端开发，它会提高基于原型的开发速度，降低开发软件的成本。 随着 Tony Beltramelli 发起的 pix2code [https://arxiv.org/abs/1705.07962] 和 Airbnb 发起的  sketch2code [https://airbnb.design/sketching-interfaces/] 项目的发展，这一领域正在飞速发展。 目前，前端开发智能化的最大阻碍是计算能力。不管怎样，我们可以使用当下流行的深度学习算法，以及现成的训练数据，来探索一下机器开发前端。 1）把设计图片输入给训练过的神经网络 2）神经网络把图片转换成 HTML 代码 3）渲染输出 我们将会分三步构建神经网络。 首先，我们将会实现一个基础功能版本熟悉一下流程。第二个是 HTML 版本，会将所有的操作自动化，这里会介绍神经网络层。在最终的 Booststrap 版本里，我们将创建一个通用的 LSTM 层模型。 代码托管在 GitHub [https://github.com/emilwallner/Screenshot-to-code-i ]]>
                </description>
                <link>https://www.freecodecamp.org/chinese/news/how-you-can-train-an-ai-to-convert-your-design-mockups-into-html-and-css/</link>
                <guid isPermaLink="false">5d2453effbfdee429dc5ec47</guid>
                
                    <category>
                        <![CDATA[ AI ]]>
                    </category>
                
                    <category>
                        <![CDATA[ HTML ]]>
                    </category>
                
                    <category>
                        <![CDATA[ CSS ]]>
                    </category>
                
                <dc:creator>
                    <![CDATA[ ZhichengChen ]]>
                </dc:creator>
                <pubDate>Wed, 20 Jan 2021 09:20:00 +0000</pubDate>
                <media:content url="https://chinese.freecodecamp.org/news/content/images/2019/07/1-4.jpeg" medium="image" />
                <content:encoded>
                    <![CDATA[ <p>在未来几年，深度学习会改变前端开发，它会提高基于原型的开发速度，降低开发软件的成本。</p><p>随着 Tony Beltramelli 发起的 <a href="https://arxiv.org/abs/1705.07962" rel="nofollow noopener">pix2code</a> 和 Airbnb 发起的 <a href="https://airbnb.design/sketching-interfaces/" rel="nofollow noopener">sketch2code</a> 项目的发展，这一领域正在飞速发展。</p><p>目前，前端开发智能化的最大阻碍是计算能力。不管怎样，我们可以使用当下流行的深度学习算法，以及现成的训练数据，来探索一下机器开发前端。</p><h3 id="1-">1）把设计图片输入给训练过的神经网络</h3><figure class="kg-card kg-image-card"><img src="https://chinese.freecodecamp.org/forum/uploads/default/optimized/1X/c165ee08bdff0d8db233418923c4e6102cca42c5_2_690x299.png" class="kg-image" alt="1_M1S0BwZOTZWgIXYSpUY0NA" width="600" height="400" loading="lazy"></figure><h3 id="2-html-">2）神经网络把图片转换成 HTML 代码</h3><figure class="kg-card kg-image-card"><img src="https://chinese.freecodecamp.org/news/content/images/2019/08/image-55.png" class="kg-image" alt="image-55" width="800" height="563" loading="lazy"></figure><h3 id="3-">3）渲染输出</h3><figure class="kg-card kg-image-card"><img src="https://chinese.freecodecamp.org/forum/uploads/default/optimized/1X/3f98e394c79dd51a928e408b7b4c3431c08b9f0d_2_690x442.png" class="kg-image" alt="1_TJ335ei7RVxx3XhWrMp6rg%20(1)" width="600" height="400" loading="lazy"></figure><p>我们将会分三步构建神经网络。</p><p>首先，我们将会实现一个基础功能版本熟悉一下流程。第二个是 HTML 版本，会将所有的操作自动化，这里会介绍神经网络层。在最终的 Booststrap 版本里，我们将创建一个通用的 LSTM 层模型。</p><p>代码托管在 <a href="https://github.com/emilwallner/Screenshot-to-code-in-Keras/blob/master/README.md" rel="nofollow noopener">GitHub</a> 和 <a href="https://www.floydhub.com/emilwallner/projects/picturetocode" rel="nofollow noopener">FloyHub</a> Jupyter 的 notebooks 上。所有的 FloydHub notebooks 在 <code>floydhub</code> 目录下，本地文件在 <code>local</code> 目录下。</p><p>模型基于 Beltramelli 的 <a href="https://arxiv.org/abs/1705.07962" rel="nofollow noopener">pix2code paper</a> 以及 Jason Brownlee 的<a href="https://machinelearningmastery.com/blog/page/2/" rel="nofollow noopener">图片识别教程</a>。代码是 Python 和 Keras 写的，Keras 是一个基于 TensorFlow 的框架。</p><p>如果你是深度学习的新手，建议你从 Python 、backpropagation 和卷积神经网络开始入门。我在 FloyHub 的三篇早期博客（英文）可以作为参考：</p><ul><li><a href="https://blog.floydhub.com/my-first-weekend-of-deep-learning/" rel="nofollow noopener">我学习深度学习的第一个周末</a></li><li><a href="https://blog.floydhub.com/coding-the-history-of-deep-learning/" rel="nofollow noopener">深度学习的代码记录</a></li><li><a href="https://blog.floydhub.com/coding-the-history-of-deep-learning/" rel="nofollow noopener">用神经网络给 B&amp;W 照片上色</a></li></ul><h3 id="-">核心概念</h3><p>让我们来看一下我们的目标。我们想要构建一个神经网络，它可以根据网页截图生成相应的 HTML/CSS 代码。</p><p>通过和截图匹配的 HTML 代码来训练神经网络。</p><p>机器通过把 HTML 代码标签和相应的图片对比来学习。它分析截图以找到合适的 HTML 标签。</p><p>这里是 Google Sheet 上的一组<a href="https://docs.google.com/spreadsheets/d/1xXwarcQZAHluorveZsACtXRdmNFbwGtN3WMNhcTdEyQ/edit?usp=sharing" rel="nofollow noopener">简单的训练数据例子</a>。</p><p>创建逐字预测的模型是普遍做法。当然还有<a href="https://machinelearningmastery.com/deep-learning-caption-generation-models/" rel="nofollow noopener">其它的方法</a>，为了便于理解，我们在教程里采用普遍做法。</p><p>注意每次预测都是读取同一个图片。所以如果预测 20 个单词，会读取 20 次原型图。目前为止，不用太纠结神经网络是如何工作的，了解神经网络的输入和输出就好了。</p><figure class="kg-card kg-image-card"><img src="https://chinese.freecodecamp.org/forum/uploads/default/optimized/1X/4d15a86f4c125a5fb382c4d056af732841d0e09b_2_690x173.png" class="kg-image" alt="1_gr6gkpnyQq1dbhI2uWYexg" width="600" height="400" loading="lazy"></figure><p>先从上一个标签开始处理。训练机器来识别句子 “I can code” 。当输入 “I”，它会预测出 “can”。下一次输入变成 “I can” 然后预测出 “code”。输入一个单词就能预测出下一个单词。</p><figure class="kg-card kg-image-card"><img src="https://chinese.freecodecamp.org/forum/uploads/default/optimized/1X/3456d26e7803a4e7941c079b7372c2c1fb504ca0_2_690x207.png" class="kg-image" alt="1_VV97BMdaICBB8KRDivXNUQ" width="600" height="400" loading="lazy"></figure><p>神经网络从数据里寻找特征，然后通过特征建立输入和输出的联系。它会创建画像来了解每一个截图是什么，然后预测 HTML 语句。从而构建出知识体系来预测标签。</p><p>把训练好的模型应用于真实世界，和模型训练的原理是相同的。文字也是根据截图逐个生成。区别是它根据已经学习过的标签来预测代码，而不是给定相应的 HTML 代码来学习。接着，预测下一个代码标签。预测从 “开始标签” 预测，在 “结束标签” 或者达到最大限制截止。这里是在 <a href="https://docs.google.com/spreadsheets/d/1yneocsAb_w3-ZUdhwJ1odfsxR2kr-4e_c5FabQbNJrs/edit?usp=sharing" rel="nofollow noopener">Google Sheet</a> 上的另一个例子。</p><figure class="kg-card kg-image-card"><img src="https://chinese.freecodecamp.org/forum/uploads/default/optimized/1X/bc312e47885caf82fd0664d875ca1f37f9fefd23_2_690x173.png" class="kg-image" alt="1_gBrXrx0iBSdrpk_cbXx-yA" width="600" height="400" loading="lazy"></figure><h3 id="-hello-world-">“hello world” 版本</h3><p>让我们先来构建一个 “hello world” 版本。先让神经网络学习一个显示 “Hello World” 的网站截图，教他生成相应标签。</p><figure class="kg-card kg-image-card"><img src="https://chinese.freecodecamp.org/news/content/images/2019/08/image-56.png" class="kg-image" alt="image-56" width="800" height="458" loading="lazy"></figure><p>首先，神经网络把设计原型图转换为像素值列表。值范围是 0 - 255，包含红蓝绿三个通道。</p><figure class="kg-card kg-image-card"><img src="https://chinese.freecodecamp.org/forum/uploads/default/optimized/1X/aa2f0bae5da7ec706099a89c237c53766ac1f018_2_690x253.png" class="kg-image" alt="1_bxEFeIDqkah93Ah8J9Bjpw" width="600" height="400" loading="lazy"></figure><p>这里使用了 <a href="https://machinelearningmastery.com/how-to-one-hot-encode-sequence-data-in-python/" rel="nofollow noopener">one hot encoding</a> 把标签转换成神经网络可以理解的方式。例如，句子 “I can code” 可以转换成下面的样子。</p><figure class="kg-card kg-image-card"><img src="https://chinese.freecodecamp.org/forum/uploads/default/optimized/1X/8adfa6a693e8ecd330f82a45d219a621cdd20fcf_2_690x213.png" class="kg-image" alt="1_KnLSt5Sybm5MTMnF-L1GSQ" width="600" height="400" loading="lazy"></figure><p>在上面的图片里，还包含了开始和结束标记。这些标记代表了机器什么时候开始识别什么时候终止识别。</p><p>我们使用句子作为数据输入，从第一个单词开始，接着一个单词一个单词的添加。输出数据也是单词。</p><p>句子的逻辑和单词是一样的，也同样需要输入长度。句子受最大句子长度约束，而不是受最低词汇量限制。如果句子长度比最大长度短，会使用 0 字符填充。</p><figure class="kg-card kg-image-card"><img src="https://chinese.freecodecamp.org/forum/uploads/default/optimized/1X/f52b36f2436c44ae2f20b5dc065c8751ea7a5451_2_690x213.png" class="kg-image" alt="1_azyTDIp4CLkoY5IByseOvg" width="600" height="400" loading="lazy"></figure><p>如你所见，单词从右到左打印。每轮训练单词的位置都不固定。这样模型就可以学习句子而不是简单记住每个单词的位置。</p><p>在下面的图片里有四个预测。每一行是一个预测。左边的图片表示三个颜色通道： 红、绿、蓝，以及当前的单词。括号外面代表每次的预测，红色的方块代表结束。</p><figure class="kg-card kg-image-card"><img src="https://chinese.freecodecamp.org/forum/uploads/default/optimized/1X/48d9e7cb988310d4addb34a7a6964a3ff2eed9b2_2_690x110.png" class="kg-image" alt="1_BowYDCuj79u1QZPyGl1dfQ" width="600" height="400" loading="lazy"></figure><p><em>green blocks = start tokens | red block = end token</em></p><pre><code class="language-python">#Length of longest sentence
max_caption_len = 3
#Size of vocabulary 
vocab_size = 3
# Load one screenshot for each word and turn them into digits 
images = []
for i in range(2):
    images.append(img_to_array(load_img('screenshot.jpg', target_size=(224, 224))))
images = np.array(images, dtype=float)
# Preprocess input for the VGG16 model
images = preprocess_input(images)
#Turn start tokens into one-hot encoding
html_input = np.array(
            [[[0., 0., 0.], #start
             [0., 0., 0.],
             [1., 0., 0.]],
             [[0., 0., 0.], #start &lt;HTML&gt;Hello World!&lt;/HTML&gt;
             [1., 0., 0.],
             [0., 1., 0.]]])
#Turn next word into one-hot encoding
next_words = np.array(
            [[0., 1., 0.], # &lt;HTML&gt;Hello World!&lt;/HTML&gt;
             [0., 0., 1.]]) # end
# Load the VGG16 model trained on imagenet and output the classification feature
VGG = VGG16(weights='imagenet', include_top=True)
# Extract the features from the image
features = VGG.predict(images)
#Load the feature to the network, apply a dense layer, and repeat the vector
vgg_feature = Input(shape=(1000,))
vgg_feature_dense = Dense(5)(vgg_feature)
vgg_feature_repeat = RepeatVector(max_caption_len)(vgg_feature_dense)
# Extract information from the input seqence 
language_input = Input(shape=(vocab_size, vocab_size))
language_model = LSTM(5, return_sequences=True)(language_input)
# Concatenate the information from the image and the input
decoder = concatenate([vgg_feature_repeat, language_model])
# Extract information from the concatenated output
decoder = LSTM(5, return_sequences=False)(decoder)
# Predict which word comes next
decoder_output = Dense(vocab_size, activation='softmax')(decoder)
# Compile and run the neural network
model = Model(inputs=[vgg_feature, language_input], outputs=decoder_output)
model.compile(loss='categorical_crossentropy', optimizer='rmsprop')
# Train the neural network
model.fit([features, html_input], next_words, batch_size=2, shuffle=False, epochs=1000)
</code></pre><p>在 hello world 版本里，用到了 3 个 tokens： <code>start</code>、<code>&lt;HTML&gt;&lt;center&gt;&lt;H1&gt;Hello World!&lt;/H1&gt;&lt;/center&gt;&lt;/HTML&gt;</code> 和 <code>end</code>。token 可以是任何东西。可以是字符、单词或者句子。字符版本需要较少的词汇量，但是受限于神经网络。单词版本则性更优。</p><p>下面是做出的预测：</p><pre><code class="language-python"># Create an empty sentence and insert the start token
sentence = np.zeros((1, 3, 3)) # [[0,0,0], [0,0,0], [0,0,0]]
start_token = [1., 0., 0.] # start
sentence[0][2] = start_token # place start in empty sentence
    
# Making the first prediction with the start token
second_word = model.predict([np.array([features[1]]), sentence])
    
# Put the second word in the sentence and make the final prediction
sentence[0][1] = start_token
sentence[0][2] = np.round(second_word)
third_word = model.predict([np.array([features[1]]), sentence])
    
# Place the start token and our two predictions in the sentence 
sentence[0][0] = start_token
sentence[0][1] = np.round(second_word)
sentence[0][2] = np.round(third_word)
    
# Transform our one-hot predictions into the final tokens
vocabulary = ["start", "&lt;HTML&gt;&lt;center&gt;&lt;H1&gt;Hello World!&lt;/H1&gt;&lt;/center&gt;&lt;/HTML&gt;", "end"]
for i in sentence[0]:
print(vocabulary[np.argmax(i)], end=' ')
</code></pre><h3 id="--1">输出</h3><ul><li>10 epochs: <code>start start start</code></li><li>100 epochs: <code>start &lt;HTML&gt;&lt;center&gt;&lt;H1&gt;Hello World!&lt;/H1&gt;&lt;/center&gt;&lt;/HTML&gt; &lt;HTML&gt;&lt;center&gt;&lt;H1&gt;Hello World!&lt;/h1&gt;&lt;/center&gt;&lt;/HTML&gt;</code></li><li>300 epochs: <code>start &lt;HTML&gt;&lt;center&gt;&lt;H1&gt;Hello World!&lt;/H1&gt;&lt;/center&gt;&lt;/HTML&gt; end</code></li></ul><h3 id="--2">我犯的错误</h3><ul><li><strong><strong>构建前先收集数据。</strong></strong> 在这个项目初期，我设法获取了 Geocities 托管网站的一个旧的副本。上面有三千八百万个网站。由于过于盲目，我忽略了分析 10 万词汇量所需的巨大工作。</li><li><strong><strong>处理 TB 级别的数据需要一个好的设备以及很大的耐心。</strong></strong> 在 mac 运行力不从心后，我开始使用更强大的远程服务器。租用的 8 核 CPU 1G 带宽让我有了一个像样的调试环境。</li><li><strong><strong>在我熟悉数据输入输出之前一切都没用。</strong></strong> 输入 X，是一个截图以及前一个标签代码。输出，Y，是下一个标签代码。当我理清这些时，事情变得简单多了。切换不同的架构也变得很方便。</li><li><strong><strong>当心未知世界。</strong></strong> 由于这个项目涉及到深度学习的很多领域，在学习过程中我在好多不必要的地方浪费了大量时间。我花了一周时间从零开始学习 RNNs，研究了一段时间嵌入向量空间，又了解了各种非主流实现。</li><li><strong><strong>机器图片转代码是一种图片识别模型。</strong></strong> 当我了解到这一点时，我仍然忽略了很多图片识别资料，因为它们太枯燥了。一旦我意识到这一点，我的进步飞快。</li></ul><h3 id="-floydhub-">在 FloydHub 上运行代码</h3><p>FloydHub 是一个深度学习训练平台。我第一次接触深度学习的时候就开始用它，现在我用它来处理机器学习并记录我的学习过程。你可以点击下面的按钮在 30s 内快速上手：</p><figure class="kg-card kg-image-card"><img src="https://chinese.freecodecamp.org/forum/uploads/default/original/1X/c49109f315289606927262a366b3dd4a4162d3db.png" class="kg-image" alt="1_x3hl-4aDKNicJWPgcBOy4A" width="600" height="400" loading="lazy"></figure><p>这个链接在 <a href="https://www.floydhub.com/?utm_medium=readme&amp;utm_source=pix2code&amp;utm_campaign=aug_2018" rel="nofollow noopener">FloydHub</a> 上打开了一个<a href="https://blog.floydhub.com/workspaces/" rel="nofollow noopener">工作空间</a>，里面包含了 <em>Bootstrap 版本</em>所使用的环境和数据集。以及测试用的训练好的模型。</p><p>或者你可以通过以下这两步手动安装: <a href="https://www.floydhub.com/" rel="nofollow noopener">2 分钟安装</a> 、<a href="https://www.youtube.com/watch?v=byLQ9kgjTdQ&amp;t=21s" rel="nofollow noopener">5 分钟上手教程</a>。</p><h4 id="--3">克隆仓库</h4><pre><code>git clone https://github.com/emilwallner/Screenshot-to-code-in-Keras.git
</code></pre><h4 id="-floydhub--1">登录并初始化 FloydHub 命令行工具</h4><pre><code>cd Screenshot-to-code-in-Keras
floyd login
floyd init s2c
</code></pre><h4 id="-floydhub-gpu-jupyter-">在FloydHub 云 GPU 机器上运行 Jupyter 笔记本</h4><pre><code>floyd run --gpu --env tensorflow-1.4 --data emilwallner/datasets/imagetocode/2:data --mode jupyter
</code></pre><p>所有的 notebooks 都在 FloydHub 路径下。local 代表本地。一旦运行，你可以在这里找到第一个 notebook：floydub/Helloworld/helloworld.ipynd。</p><p>如果你想要更详细的介绍，这里有<a href="https://blog.floydhub.com/colorizing-b&amp;w-photos-with-neural-networks/" rel="nofollow noopener">一篇我早期的文章</a>。</p><h3 id="html-">HTML 版本</h3><p>在这个版本里，我们在 Hello World 模型基础上做了很多自动化工作。这节将会专注于用神经网络创建一个可扩展、可移植的实现。</p><p>这个版本还不能从一个随机的截图来生成 HTML，但却是对动态解决问题的更深探索。</p><figure class="kg-card kg-image-card"><img src="http://www.chenzhicheng.com/content/images/2019/04/1_NXZX3G_IrOyQyoHZfucfDQ.gif" class="kg-image" alt="1_NXZX3G_IrOyQyoHZfucfDQ" width="600" height="400" loading="lazy"></figure><h3 id="--4">概述</h3><p>如果我们展开前一个图片的组件，看起来如下：<br></p><figure class="kg-card kg-image-card"><img src="https://chinese.freecodecamp.org/forum/uploads/default/optimized/1X/e91d5ab57145bb6f9a58a8946054795bc6aeadac_2_690x240.png" class="kg-image" alt="1_aNs78m9t8gX_5MVBrOM6kg" width="600" height="400" loading="lazy"></figure><p>有两个主要的部分。首先，encoder。这是我们处理图片特征（Image features）和前一个标签特征（Previous markup features）的地方。特征就是机器创建的把设计原型图和代码联系起来的语句块。在 encoder 的末尾，我们把图片特征和前一个标签的每个单词连在一起。</p><p>decoder 接收设计和标签特征合体然后创建了下一个标签特性。这个特性通过完全连接的神经网络运行，以预测下一个标签。</p><h3 id="--5">原型图特性</h3><p>由于我们需要给每一个单词插入一个截图，这在我们训练机器的时候成为了瓶颈 (<a href="https://docs.google.com/spreadsheets/d/1xXwarcQZAHluorveZsACtXRdmNFbwGtN3WMNhcTdEyQ/edit#gid=0" rel="nofollow noopener">例子</a>)。所以我们直接提取代码所需要的信息，而不是使用图片。</p><p>信息编码成图片特性。这里用到了一个预训练的卷积神经网络 (CNN)。模型已经在 Imagenet 上面训练过了。</p><p>我们在最终分类前在图层上提取了特征。</p><figure class="kg-card kg-image-card"><img src="https://chinese.freecodecamp.org/forum/uploads/default/optimized/1X/8c3c404f4f195282f137dbeaf04db67dde768afa_2_690x104.png" class="kg-image" alt="1_VFWgUWKErANkgkF7VqV9SQ" width="600" height="400" loading="lazy"></figure><p>我们最终获得了 1536 个 8*8 像素的图片做为已知特征。虽然它们对于人脑来说很难理解，神经网络却可以从这些特征里提取所需的对象以及元素的位置信息。</p><h3 id="markup-">Markup 特性</h3><p>在 hello world 版本里，我们使用 one-hot encoding 来描述代码。在这个版本里，我们使用嵌入单词做为输入，使用 one-hot encoding 做为输出。</p><p>我们划分每个句子的方式还是相同，但是 token 的映射改变了。One-hot encoding 把每个单词当成了一个独立的部分。我们把输入数据中的每个单词转换成数字列表。列表代表了代码标签间的关系。</p><figure class="kg-card kg-image-card"><img src="https://chinese.freecodecamp.org/forum/uploads/default/optimized/1X/f18dc0d1c60e6010a3b987a0a7b183bf38c7a2d5_2_690x133.png" class="kg-image" alt="1_IUbBUf1Nv13M5iV0RJMaeQ" width="600" height="400" loading="lazy"></figure><p>嵌入单词的参数是 8 ，但是经常在 50-500 之间浮动，和词汇的大小有关。</p><p>单词的 8 类似于 BP 神经网络里的权重。它们需要动态调整，代表了单词之前的相关性 (<a href="https://arxiv.org/abs/1301.3781" rel="nofollow noopener">Mikolov et al., 2013</a>)。</p><p>这就是开发代码特性的过程。特性通过神经网络把输入数据和输出数据连接起来。目前为止，不用在意它们是什么，我们将要在下一节里讨论它们。</p><h3 id="--6">编码</h3><p>我们输入嵌入单词然后运行在 LSTM 上并返回一系列代码特性。它们运行在 Time 分发密集层—把它想成一个有多个输入和输出的密集层。</p><figure class="kg-card kg-image-card"><img src="https://chinese.freecodecamp.org/forum/uploads/default/optimized/1X/be930bff9f83631000218301ade291374d192028_2_690x204.png" class="kg-image" alt="1_eEmEB_OT6DEoq-1e7IbdTA" width="600" height="400" loading="lazy"></figure><p>平行的，图片特性在第一层。忽略数字结构，它们把图片转换成了一个大的数字列表。然后我们在这层上又应用了一个密集层组成了一个高阶特性，把这些图像特性和标签特性组合起来。</p><p>这可能很难理解–让我们分解它。</p><h3 id="--7">代码特性</h3><p>这里我们通过 LSTM 层运行嵌入单词。在这个图片里，所有的句子都被填充以达到三个 token 的最大大小。</p><figure class="kg-card kg-image-card"><img src="https://chinese.freecodecamp.org/forum/uploads/default/optimized/1X/9d25963c204e08fb2aabcb7b13217f0322e98ac6_2_690x156.png" class="kg-image" alt="1_1Odkg1J_79EHQsDdwc0yJw" width="600" height="400" loading="lazy"></figure><p>为了混合信号以及找到更高级的模式，我们给代码特性应用了一个 TimeDistributed 密集层。TimeDistributed 密集和普通密集层相同，只不过有多个输入和输出。</p><h3 id="--8">图片特性</h3><p>平行的，我们准备了图片。我们获取到所有的小图片特征然后把它们转换成一个长列表。信息没有改变，只是被重新整理。</p><figure class="kg-card kg-image-card"><img src="https://chinese.freecodecamp.org/forum/uploads/default/optimized/1X/003448acd7b468baef03d9aa923f7b6d143efb91_2_690x153.png" class="kg-image" alt="1_d6HPOe0lHUJDuQMl5g-99Q" width="600" height="400" loading="lazy"></figure><p>再次，为了混合信号以及抽出更高的概念，我们应用了一个密集层。由于我们只处理了一路输入值，所以我们可以使用一个普通的密集层。我们复制了图片特性，以便把图片特性和标记特性组合起来。</p><h3 id="--9">将图片和标签特性联系起来</h3><p>所有的句子都被扩充以创造三个代码特性。由于已经准备好了图片特征，现在能给每个代码特性添加一个图片特征。</p><figure class="kg-card kg-image-card"><img src="https://chinese.freecodecamp.org/forum/uploads/default/optimized/1X/5dbd39655295b1197f40ec448b52c4ef036c85f0_2_690x78.png" class="kg-image" alt="1_dHwH2MYqLgJiEtIIYFFBiA" width="600" height="400" loading="lazy"></figure><p>在把一个图片特征粘贴到每个代码标签后，我们最终得到三个图片代码特性。这就是我们提供给解码器的输入。</p><h3 id="--10">解码</h3><p>在这里我们使用组合图片代码特性来预测下一个标签。</p><figure class="kg-card kg-image-card"><img src="https://chinese.freecodecamp.org/forum/uploads/default/optimized/1X/ceb70e8b9cf75a4fbe6fc33b1daa36f552eff3e9_2_690x139.png" class="kg-image" alt="1_FZKu2rmpXgelEyNKoljXdw" width="600" height="400" loading="lazy"></figure><p>在下面的例子里，我们使用三个图片代码特性配对，然后输出给下一个标签特性。</p><p>请注意 LSTM 图层序列设置为 false。它只预测一个特性，而不是返回输入序列的长度。在我们的用例里，是下一个标签的特性。它包含了最终预测的信息。</p><figure class="kg-card kg-image-card"><img src="https://chinese.freecodecamp.org/forum/uploads/default/optimized/1X/593e34dd9f38ecbb9542c2a69b7ea6c9aef9f267_2_690x120.png" class="kg-image" alt="1_6zk2iz9GaRTJOD1TJnWmIg" width="600" height="400" loading="lazy"></figure><h3 id="--11">最终预测</h3><p>密集层的原理有点像前馈神经网络。它在含有 4 个最终预测的下一个标签里连接了 512 个数字。在我们的词汇里有四个单词：start、hello、world 和 end。</p><p>预测的词汇应该是 [0.1, 0.1, 0.1, 0.7]。在密集层的 softmax 激活分配了值为从 0-1 之间的概率，预测值的总和是 1。在这里，它在下一个标签里预测了第四个单词。然后把 one-hot encoding [0, 0, 0, 1] 翻译成映射的值，也就是 “end”。</p><pre><code class="language-python"># Load the images and preprocess them for inception-resnet
images = []
all_filenames = listdir('images/')
all_filenames.sort()
for filename in all_filenames:
    images.append(img_to_array(load_img('images/'+filename, target_size=(299, 299))))
images = np.array(images, dtype=float)
images = preprocess_input(images)
# Run the images through inception-resnet and extract the features without the classification layer
IR2 = InceptionResNetV2(weights='imagenet', include_top=False)
features = IR2.predict(images)
# We will cap each input sequence to 100 tokens
max_caption_len = 100
# Initialize the function that will create our vocabulary 
tokenizer = Tokenizer(filters='', split=" ", lower=False)
# Read a document and return a string
def load_doc(filename):
    file = open(filename, 'r')
    text = file.read()
    file.close()
    return text
# Load all the HTML files
X = []
all_filenames = listdir('html/')
all_filenames.sort()
for filename in all_filenames:
    X.append(load_doc('html/'+filename))
# Create the vocabulary from the html files
tokenizer.fit_on_texts(X)
# Add +1 to leave space for empty words
vocab_size = len(tokenizer.word_index) + 1
# Translate each word in text file to the matching vocabulary index
sequences = tokenizer.texts_to_sequences(X)
# The longest HTML file
max_length = max(len(s) for s in sequences)
# Intialize our final input to the model
X, y, image_data = list(), list(), list()
for img_no, seq in enumerate(sequences):
    for i in range(1, len(seq)):
        # Add the entire sequence to the input and only keep the next word for the output
        in_seq, out_seq = seq[:i], seq[i]
        # If the sentence is shorter than max_length, fill it up with empty words
        in_seq = pad_sequences([in_seq], maxlen=max_length)[0]
        # Map the output to one-hot encoding
        out_seq = to_categorical([out_seq], num_classes=vocab_size)[0]
        # Add and image corresponding to the HTML file
        image_data.append(features[img_no])
        # Cut the input sentence to 100 tokens, and add it to the input data
        X.append(in_seq[-100:])
        y.append(out_seq)
X, y, image_data = np.array(X), np.array(y), np.array(image_data)
# Create the encoder
image_features = Input(shape=(8, 8, 1536,))
image_flat = Flatten()(image_features)
image_flat = Dense(128, activation='relu')(image_flat)
ir2_out = RepeatVector(max_caption_len)(image_flat)
language_input = Input(shape=(max_caption_len,))
language_model = Embedding(vocab_size, 200, input_length=max_caption_len)(language_input)
language_model = LSTM(256, return_sequences=True)(language_model)
language_model = LSTM(256, return_sequences=True)(language_model)
language_model = TimeDistributed(Dense(128, activation='relu'))(language_model)
# Create the decoder
decoder = concatenate([ir2_out, language_model])
decoder = LSTM(512, return_sequences=False)(decoder)
decoder_output = Dense(vocab_size, activation='softmax')(decoder)
# Compile the model
model = Model(inputs=[image_features, language_input], outputs=decoder_output)
model.compile(loss='categorical_crossentropy', optimizer='rmsprop')
# Train the neural network
model.fit([image_data, X], y, batch_size=64, shuffle=False, epochs=2)
# map an integer to a word
def word_for_id(integer, tokenizer):
    for word, index in tokenizer.word_index.items():
        if index == integer:
            return word
    return None
# generate a description for an image
def generate_desc(model, tokenizer, photo, max_length):
    # seed the generation process
    in_text = 'START'
    # iterate over the whole length of the sequence
    for i in range(900):
        # integer encode input sequence
        sequence = tokenizer.texts_to_sequences([in_text])[0][-100:]
        # pad input
        sequence = pad_sequences([sequence], maxlen=max_length)
        # predict next word
        yhat = model.predict([photo,sequence], verbose=0)
        # convert probability to integer
        yhat = np.argmax(yhat)
        # map integer to word
        word = word_for_id(yhat, tokenizer)
        # stop if we cannot map the word
        if word is None:
            break
        # append as input for generating the next word
        in_text += ' ' + word
        # Print the prediction
        print(' ' + word, end='')
        # stop if we predict the end of the sequence
        if word == 'END':
            break
    return
# Load and image, preprocess it for IR2, extract features and generate the HTML
test_image = img_to_array(load_img('images/87.jpg', target_size=(299, 299)))
test_image = np.array(test_image, dtype=float)
test_image = preprocess_input(test_image)
test_features = IR2.predict(np.array([test_image]))
generate_desc(model, tokenizer, np.array(test_features), 100)
</code></pre><h3 id="--12">输出</h3><figure class="kg-card kg-image-card"><img src="https://chinese.freecodecamp.org/forum/uploads/default/optimized/1X/f268c3be368e5370b8347afed2dfe363a64927f4_2_690x179.png" class="kg-image" alt="1_unVxRV7QIJ8tmLLNH5S53w" width="600" height="400" loading="lazy"></figure><h3 id="--13">生成好的页面</h3><ul><li><a href="https://emilwallner.github.io/html/250_epochs/" rel="nofollow noopener">250 epochs</a></li><li><a href="https://emilwallner.github.io/html/350_epochs/" rel="nofollow noopener">350 epochs</a></li><li><a href="https://emilwallner.github.io/html/450_epochs/" rel="nofollow noopener">450 epochs</a></li><li><a href="https://emilwallner.github.io/html/550_epochs/" rel="nofollow noopener">550 epochs</a></li></ul><p>如果你点击上面的链接后看不到内容，那么需要右击选择 “显示网页源代码”。这是对应的 <a href="https://emilwallner.github.io/html/Original/" rel="nofollow noopener">原始网站</a>。</p><h3 id="--14">我犯的错误</h3><ul><li><strong><strong>在我的认知里 LSTMs 比 CNNs 更重。</strong></strong> 当我展开所有的 LSTMs，它们变得更容易理解。<a href="http://course.fast.ai/lessons/lesson6.html" rel="nofollow noopener">Fast.ai’s video on RNNs</a> 里写的很棒。另外，不要尝试去了解它们怎样工作，专注于输入和输出功能就好了。</li><li><strong><strong>着手构建一个词汇表比弄一个巨大的词汇库简单多了。</strong></strong> 这也适用于字体、div 尺寸、hex colors 、变量名和普通的单词。</li><li><strong><strong>大部分库都是用来解析文本文档而非代码的。</strong></strong> 在文档里，一切东西都以空格分隔，但是在代码里，你需要定制解析。</li><li><strong><strong>可以使用在 Imagenet 训练好的模型来提取特性。</strong></strong> 由于 Imagenet 里只有少量的 web 图片所以这可能有悖于常理。虽然和由 scratch 训练的 pix2code 模型相比错误率高达 30% ，但使用基于 web 截图的由 inception-resnet 训练的模型是一件很有趣的事。</li></ul><h3 id="bootstrap-">Bootstrap 版本</h3><p>在最终版本里，使用 <a href="https://arxiv.org/abs/1705.07962" rel="nofollow noopener">pix2code paper</a> 上面生成好的 bootstrap 网站数据集。通过引入 Twitter 的 <a href="https://getbootstrap.com/" rel="nofollow noopener">bootstrap</a>， 可以合并 HTML 和 CSS，减少词汇的体积。</p><p>我们将要为之前没有见过的截图生成标记。我们还会深入关于怎样将截图转换成代码的知识。</p><p>我们将使用 17 个简化的 tokens 来转换成 HTML 和 CSS，而不是在 bootstrap 标签上训练。<a href="https://github.com/tonybeltramelli/pix2code/tree/master/datasets" rel="nofollow noopener">数据集</a>包含了1500 个测试截图和 250 个符合要求的图片。每个截图平均有 65 个 tokens，共有 96925 个训练例子。</p><p>通过在 pix2code paper 里定制模型，可以预测 97% 的 web 组件 (使用 BLEU 4-ngram 贪婪搜索模式后，还会更高)。</p><figure class="kg-card kg-image-card"><img src="http://www.chenzhicheng.com/content/images/2019/04/1_mefes8A2q21d7wqzPy5NaQ.gif" class="kg-image" alt="1_mefes8A2q21d7wqzPy5NaQ" width="600" height="400" loading="lazy"></figure><h3 id="--15">端到端接近</h3><p>在图片识别模型抽出的预训练模型效果很不错。但是经过一些尝试后，我意识到 pix2code 的端到端方式在这个问题能上表现的更出色。训练好的模型还没有在 web 数据上训练，它们需要手动来分类。</p><p>在这个模型里，我们把预训练图片特性替换成了一个轻量的卷积神经网络。我们使用了 strides 而不是 max-pooling 来提高信息密度。这样就可以获得前端元素的位置和颜色。</p><figure class="kg-card kg-image-card"><img src="https://chinese.freecodecamp.org/forum/uploads/default/optimized/1X/7143cf11b3160c644d06070d3dbdd969b9b0ec5e_2_690x240.png" class="kg-image" alt="1_v7-Xpexl_yVfNajjzSBp4A" width="600" height="400" loading="lazy"></figure><p>有两个核心模型可以做到这点：卷积神经网络 (CNN）和递归神经网络 (RNN)。最常见的递归神经网络就是 long-short term memory (LSTM)，这也是我更倾向的模型。</p><p>有很多的出色的 CNN 教程，我在<a href="https://blog.floydhub.com/colorizing-b&amp;w-photos-with-neural-networks/" rel="nofollow noopener">之前的文章</a>里也有提到过。在这里，我将会专注于 LSTMs。</p><h3 id="-lstms-timesteps">了解 LSTMs 里面的 timesteps</h3><p>LSTMs 里面最难理解的就是 timesteps。BP 神经网络可以看成是两个 timesteps。 如果给出 "Hello, " 它会预测出 “World.”。但是它很难预测更多的 timesteps。在下面的例子里，输入有 4 个timesteps，每一个对应一个单词。</p><p>LSTMs 由包含 timesteps 的输入组成。它是为有序信息定制的神经网络。如果展开模型看起来像下面这样。对于每个向下的 step，具有同样的权重。用一组权重来做为前一个输出，另一组做为新的输入。</p><figure class="kg-card kg-image-card"><img src="https://chinese.freecodecamp.org/forum/uploads/default/optimized/1X/2368bc789ada2db3bfa21785d19835df16639878_2_690x458.png" class="kg-image" alt="1_Ubbuzpxear4swWsNysbrUw" width="600" height="400" loading="lazy"></figure><p>加权的输入和输出被合并添加到 activation 里。这是这个 timestep 的输出。由于我们复用了权重，它们从多个输入里重组了信息构建了新的知识系列。</p><p>下面是在 LSTM 里处理每个 timestep 的过程的简单描述：</p><figure class="kg-card kg-image-card"><img src="https://chinese.freecodecamp.org/forum/uploads/default/optimized/1X/41870e22289751d2a62a4418ccd5724df460d79d_2_690x149.png" class="kg-image" alt="1_OFyhhDvz2I6T7sMpT8uxPA" width="600" height="400" loading="lazy"></figure><p>如果想更好的理解这一逻辑，我建议通过 Andrew Trask 的<a href="https://iamtrask.github.io/2015/11/15/anyone-can-code-lstm/" rel="nofollow noopener">教程</a> 来亲自用 scratch 构建一个 RNN。</p><h3 id="-lstm-">了解 LSTM 层里面的单元</h3><p>每个 LSTM 层里面的单元数量决定了存储的能力。这也反应了每个输出特性的大小。再次，特性是一个用来在不同层之间传输信息的长列表的数字。</p><p>LSTM 层里的单元跟踪学习不同的语句。下面是一个单元跟踪 row div 信息的可视化表述。这是一个简化过了的训练 bootstrap 模型的代码。</p><figure class="kg-card kg-image-card"><img src="https://chinese.freecodecamp.org/forum/uploads/default/optimized/1X/dd6531b003b57d22caefdb5bd84bed5e680d9840_2_690x91.png" class="kg-image" alt="1_GhhCtZXqY4QHpzieJqppmw" width="600" height="400" loading="lazy"></figure><p>每个 LSTM 单元维护了一个 cell 的状态。可以把 cell 状态理解为内存。权重和 activations 用不同的方式修改状态。这可以让 LSTM 层在信息保持和丢弃的时候更好的契合。</p><p>除了给每个输入传入一个输出特性，还可以转发 cell 状态，cell 状态就是 LSTM 里每个单元的一个值。如果需要了解更多 LSTM 组件的交互，建议参考 <a href="https://colah.github.io/posts/2015-08-Understanding-LSTMs/" rel="nofollow noopener">Golah 的教程</a>，Jayasiri 的 <a href="https://colah.github.io/posts/2015-08-Understanding-LSTMs/" rel="nofollow noopener">Nummpy 实现</a>，以及 [Karphay 的讲座](<a href="https://www.youtube.com/watch?v=yCC09vCHzF8" rel="nofollow noopener">Karphay’s lecture</a>)和<a href="https://karpathy.github.io/2015/05/21/rnn-effectiveness/" rel="nofollow noopener">资料</a>。</p><pre><code class="language-python">dir_name = 'resources/eval_light/'
# Read a file and return a string
def load_doc(filename):
    file = open(filename, 'r')
    text = file.read()
    file.close()
    return text
def load_data(data_dir):
    text = []
    images = []
    # Load all the files and order them
    all_filenames = listdir(data_dir)
    all_filenames.sort()
    for filename in (all_filenames):
        if filename[-3:] == "npz":
            # Load the images already prepared in arrays
            image = np.load(data_dir+filename)
            images.append(image['features'])
        else:
            # Load the boostrap tokens and rap them in a start and end tag
            syntax = '&lt;START&gt; ' + load_doc(data_dir+filename) + ' &lt;END&gt;'
            # Seperate all the words with a single space
            syntax = ' '.join(syntax.split())
            # Add a space after each comma
            syntax = syntax.replace(',', ' ,')
            text.append(syntax)
    images = np.array(images, dtype=float)
    return images, text
train_features, texts = load_data(dir_name)
# Initialize the function to create the vocabulary 
tokenizer = Tokenizer(filters='', split=" ", lower=False)
# Create the vocabulary 
tokenizer.fit_on_texts([load_doc('bootstrap.vocab')])
# Add one spot for the empty word in the vocabulary 
vocab_size = len(tokenizer.word_index) + 1
# Map the input sentences into the vocabulary indexes
train_sequences = tokenizer.texts_to_sequences(texts)
# The longest set of boostrap tokens
max_sequence = max(len(s) for s in train_sequences)
# Specify how many tokens to have in each input sentence
max_length = 48
def preprocess_data(sequences, features):
    X, y, image_data = list(), list(), list()
    for img_no, seq in enumerate(sequences):
        for i in range(1, len(seq)):
            # Add the sentence until the current count(i) and add the current count to the output
            in_seq, out_seq = seq[:i], seq[i]
            # Pad all the input token sentences to max_sequence
            in_seq = pad_sequences([in_seq], maxlen=max_sequence)[0]
            # Turn the output into one-hot encoding
            out_seq = to_categorical([out_seq], num_classes=vocab_size)[0]
            # Add the corresponding image to the boostrap token file
            image_data.append(features[img_no])
            # Cap the input sentence to 48 tokens and add it
            X.append(in_seq[-48:])
            y.append(out_seq)
    return np.array(X), np.array(y), np.array(image_data)
X, y, image_data = preprocess_data(train_sequences, train_features)
#Create the encoder
image_model = Sequential()
image_model.add(Conv2D(16, (3, 3), padding='valid', activation='relu', input_shape=(256, 256, 3,)))
image_model.add(Conv2D(16, (3,3), activation='relu', padding='same', strides=2))
image_model.add(Conv2D(32, (3,3), activation='relu', padding='same'))
image_model.add(Conv2D(32, (3,3), activation='relu', padding='same', strides=2))
image_model.add(Conv2D(64, (3,3), activation='relu', padding='same'))
image_model.add(Conv2D(64, (3,3), activation='relu', padding='same', strides=2))
image_model.add(Conv2D(128, (3,3), activation='relu', padding='same'))
image_model.add(Flatten())
image_model.add(Dense(1024, activation='relu'))
image_model.add(Dropout(0.3))
image_model.add(Dense(1024, activation='relu'))
image_model.add(Dropout(0.3))
image_model.add(RepeatVector(max_length))
visual_input = Input(shape=(256, 256, 3,))
encoded_image = image_model(visual_input)
language_input = Input(shape=(max_length,))
language_model = Embedding(vocab_size, 50, input_length=max_length, mask_zero=True)(language_input)
language_model = LSTM(128, return_sequences=True)(language_model)
language_model = LSTM(128, return_sequences=True)(language_model)
#Create the decoder
decoder = concatenate([encoded_image, language_model])
decoder = LSTM(512, return_sequences=True)(decoder)
decoder = LSTM(512, return_sequences=False)(decoder)
decoder = Dense(vocab_size, activation='softmax')(decoder)
# Compile the model
model = Model(inputs=[visual_input, language_input], outputs=decoder)
optimizer = RMSprop(lr=0.0001, clipvalue=1.0)
model.compile(loss='categorical_crossentropy', optimizer=optimizer)
#Save the model for every 2nd epoch
filepath="org-weights-epoch-{epoch:04d}--val_loss-{val_loss:.4f}--loss-{loss:.4f}.hdf5"
checkpoint = ModelCheckpoint(filepath, monitor='val_loss', verbose=1, save_weights_only=True, period=2)
callbacks_list = [checkpoint]
# Train the model
model.fit([image_data, X], y, batch_size=64, shuffle=False, validation_split=0.1, callbacks=callbacks_list, verbose=1, epochs=50)
</code></pre><h3 id="--16">精确测试</h3><p>找到合适的方式测量准确性很难。你可能会说可以一个单词一个单词对比呀。如果一个单词一个单词对比，可能会是 0% 的准确率。但是如果移动一个单词，可能准确率就变成了 99%。</p><p>我使用的是 BLEU 记分制，它是机器翻译和图片识别领域的最佳方案。它把句子拆成 4 个 n-gram，从 1-4 的单词系列。在下面预测的 “cat” 应该是 “code”。</p><figure class="kg-card kg-image-card"><img src="https://chinese.freecodecamp.org/forum/uploads/default/optimized/1X/8b707fb803e12bd476ca24192874f011aeee305e_2_690x91.png" class="kg-image" alt="1_dBzW7ebTLfHeVKbY3UHFMA" width="600" height="400" loading="lazy"></figure><p>为了获得最终分数，把每个分数乘以 25%，(4/5) * 0.25 + (2/4) * 0.25 + (1/3) * 0.25 + (0/2) * 0.25 = 0.2 + 0.125 + 0.083 + 0 = 0.408 。总分在乘以句子长度误差。由于在我们的例子里长度是正确的，可以忽略。</p><p>还可以增大 n-grams 的数目来让它更难。四个 n-gram 模型是人工翻译最好的测试模型。我建议用下面的代码多运行一些测试或者读一读它的<a href="https://en.wikipedia.org/wiki/BLEU" rel="nofollow noopener">文档</a>。</p><pre><code class="language-python">#Create a function to read a file and return its content
def load_doc(filename):
    file = open(filename, 'r')
    text = file.read()
    file.close()
    return text
def load_data(data_dir):
    text = []
    images = []
    files_in_folder = os.listdir(data_dir)
    files_in_folder.sort()
    for filename in tqdm(files_in_folder):
        #Add an image
        if filename[-3:] == "npz":
            image = np.load(data_dir+filename)
            images.append(image['features'])
        else:
        # Add text and wrap it in a start and end tag
            syntax = '&lt;START&gt; ' + load_doc(data_dir+filename) + ' &lt;END&gt;'
            #Seperate each word with a space
            syntax = ' '.join(syntax.split())
            #Add a space between each comma
            syntax = syntax.replace(',', ' ,')
            text.append(syntax)
    images = np.array(images, dtype=float)
    return images, text
#Intialize the function to create the vocabulary
tokenizer = Tokenizer(filters='', split=" ", lower=False)
#Create the vocabulary in a specific order
tokenizer.fit_on_texts([load_doc('bootstrap.vocab')])
dir_name = '../../../../eval/'
train_features, texts = load_data(dir_name)
#load model and weights 
json_file = open('../../../../model.json', 'r')
loaded_model_json = json_file.read()
json_file.close()
loaded_model = model_from_json(loaded_model_json)
# load weights into new model
loaded_model.load_weights("../../../../weights.hdf5")
print("Loaded model from disk")
# map an integer to a word
def word_for_id(integer, tokenizer):
    for word, index in tokenizer.word_index.items():
        if index == integer:
            return word
    return None
print(word_for_id(17, tokenizer))
# generate a description for an image
def generate_desc(model, tokenizer, photo, max_length):
    photo = np.array([photo])
    # seed the generation process
    in_text = '&lt;START&gt; '
    # iterate over the whole length of the sequence
    print('\nPrediction----&gt;\n\n&lt;START&gt; ', end='')
    for i in range(150):
        # integer encode input sequence
        sequence = tokenizer.texts_to_sequences([in_text])[0]
        # pad input
        sequence = pad_sequences([sequence], maxlen=max_length)
        # predict next word
        yhat = loaded_model.predict([photo, sequence], verbose=0)
        # convert probability to integer
        yhat = argmax(yhat)
        # map integer to word
        word = word_for_id(yhat, tokenizer)
        # stop if we cannot map the word
        if word is None:
            break
        # append as input for generating the next word
        in_text += word + ' '
        # stop if we predict the end of the sequence
        print(word + ' ', end='')
        if word == '&lt;END&gt;':
            break
    return in_text
max_length = 48
# evaluate the skill of the model
def evaluate_model(model, descriptions, photos, tokenizer, max_length):
    actual, predicted = list(), list()
    # step over the whole set
    for i in range(len(texts)):
        yhat = generate_desc(model, tokenizer, photos[i], max_length)
        # store actual and predicted
        print('\n\nReal----&gt;\n\n' + texts[i])
        actual.append([texts[i].split()])
        predicted.append(yhat.split())
    # calculate BLEU score
    bleu = corpus_bleu(actual, predicted)
    return bleu, actual, predicted
bleu, actual, predicted = evaluate_model(loaded_model, texts, train_features, tokenizer, max_length)
#Compile the tokens into HTML and css
dsl_path = "compiler/assets/web-dsl-mapping.json"
compiler = Compiler(dsl_path)
compiled_website = compiler.compile(predicted[0], 'index.html')
print(compiled_website )
print(bleu)
</code></pre><h3 id="--17">输出</h3><figure class="kg-card kg-image-card"><img src="https://chinese.freecodecamp.org/forum/uploads/default/optimized/1X/8df864a07daedb317d7c1d4de0f9006f8c8a9d5e_2_690x246.png" class="kg-image" alt="1_ihsDbdxDbiBFO3wpYrD6hQ" width="600" height="400" loading="lazy"></figure><p>输出结果的例子的链接</p><ul><li><a href="https://emilwallner.github.io/bootstrap/pred_1/" rel="nofollow noopener">生成的网站 1</a>—<a href="https://emilwallner.github.io/bootstrap/real_1/" rel="nofollow noopener">原始1</a></li><li><a href="https://emilwallner.github.io/bootstrap/pred_2/" rel="nofollow noopener">生成的网站 2</a>—<a href="https://emilwallner.github.io/bootstrap/real_2/" rel="nofollow noopener">原始2</a></li><li><a href="https://emilwallner.github.io/bootstrap/pred_3/" rel="nofollow noopener">生成的网站 3</a>—<a href="https://emilwallner.github.io/bootstrap/real_3/" rel="nofollow noopener">原始3</a></li><li><a href="https://emilwallner.github.io/bootstrap/pred_4/" rel="nofollow noopener">生成的网站 4</a>—<a href="https://emilwallner.github.io/bootstrap/real_4/" rel="nofollow noopener">原始4</a></li><li><a href="https://emilwallner.github.io/bootstrap/pred_5/" rel="nofollow noopener">生成的网站 5</a>—<a href="https://emilwallner.github.io/bootstrap/real_5/" rel="nofollow noopener">原始5</a></li></ul><h3 id="--18">我犯的错误</h3><ul><li><strong><strong>理解模型的弱点而不是测试随机的模型。</strong></strong> 最初我使用了一个随机的模型类似于 batch normalization 和 bidirectional networks，尝试花更多精力在上面。后来查看了测试数据发现它无法准确预测颜色和位置等信息，我意识到了 CNN 在某些放面有缺陷。所以我把 mapooling 替换成了更新的 strides。validation 损失从 0.12 降到了 0.02，BLEU 分数从 85% 增加到了 97%。</li><li><strong><strong>如果有现成的就使用预训练的数据。</strong></strong> 用一个小数据集的图片预训练模型会提高性能。根据我的经验，端到端模型训练起来很慢并且需要更多的内存，只提高了 30% 的准确性。</li><li><strong><strong>如果把你的模型运行在远程服务器上的话需要注意一些细节。</strong></strong> 在我的 mac 上，文件按字母顺序读取。可是在服务器上读取顺序是随机的。这样截图和代码之间就不能匹配了。虽然可能有相交，但是数据比我修复这一问题后糟糕了 50%。</li><li><strong><strong>确保你了解了库的功能。</strong></strong> 比如在你的词汇里面的空格产生的空 token。当我添加时，它并没有添加这些 tokens。在调试之后我只注意到了最终输出了多次，并没有预测出 “single” token。在排查后，我意识它甚至到不在词汇表中。因此，最好按和词汇表里相同的顺序训练测试。</li><li><strong><strong>体验的时候尽量使用轻量级的库。</strong></strong> 使用 GRUs 而不是 LSTMs，这样每个 epoch 能减少 30% ，同时对性能又不会有太大的影响。</li></ul><h3 id="--19">下一步</h3><p>前端开发是深度学习的一个重要的应用场景。生成数据很容易，当前的深度学习算法能覆盖到大部分逻辑。</p><p>另一个更令人振奋的领域是 <a href="https://arxiv.org/pdf/1502.03044.pdf" rel="nofollow noopener">在 LSTMs 里使用 attention</a>。这不仅能提升准确度，还能可视化。CNN 则更专注于生成代码。</p><p>Attention 是代码标签、样式、脚本和后端间沟通的桥梁。Attention 层可以打通变量，能够在不同编程语言间通讯。</p><p>在最终版本里，需要考虑下怎样用一个可扩展的方式来生成数据。接下来就可以添加字体、颜色、单词甚至是动画。</p><p>目前为止，大部分的流程都是通过 sketches 设计然后把它们转换成模板 app。在未来两年时间里，我们在纸上画一个 APP，前端会在 1 秒内得到页面。现在 <a href="https://airbnb.design/sketching-interfaces/" rel="nofollow noopener">Airbnb 的设计团队</a>和 <a href="https://www.uizard.io/" rel="nofollow noopener">Uizard</a> 已经有了两个原型。</p><p>这里是更多的玩法。</p><h3 id="--20">玩法</h3><p><strong><strong>起步</strong></strong></p><ul><li>运行所有的方法</li><li>尝试不同的 hyper 参数</li><li>测试不同的 LSTM 模型</li><li>使用不同的<a href="http://lstm.seas.harvard.edu/latex/" rel="nofollow noopener">数据集</a>实现模型。(可以通过 <code>--data emilwallner/datasets/100k-html:data</code> flag 方便的在 FloydHub 上挂载这一工具集)</li></ul><p><strong><strong>更多玩法</strong></strong></p><ul><li>创建一个固定的随机的 app/web 自动代码生成器。</li><li>sketch 的 app model 数据。自动转换 app/web 截图到 sketches，使用 GAN 来创建分类。</li><li>应用 attention 层来可视化每个预测焦的预测，类似于<a href="https://arxiv.org/abs/1502.03044" rel="nofollow noopener">这个模型</a>。</li><li>创建一个模块化功能的框架。已经有了字体、颜色、布局的编码模块，把它们组合成一个解码器。建议从实现图像特性开始。</li><li>为机器提供简单的 HTML 组件，教会它使用 CSS 生成动画。采用 attention 方法专注于两个输入源一定很有趣。</li></ul><p>原文链接：<a href="https://www.freecodecamp.org/news/how-you-can-train-an-ai-to-convert-your-design-mockups-into-html-and-css-cc7afd82fed4/">How you can train an AI to convert your design mockups into HTML and CSS</a>，作者：Emil Wallner</p> ]]>
                </content:encoded>
            </item>
        
    </channel>
</rss>
