<?xml version="1.0" encoding="utf-8"?>
<feed xmlns="http://www.w3.org/2005/Atom">
  <author>
    <name>yohoten</name>
  </author>
  <generator uri="https://hexo.io/">Hexo</generator>
  <icon>https://yohoten.github.io/images/favicon.svg</icon>
  <id>https://yohoten.github.io/</id>
  <link href="https://yohoten.github.io/" rel="alternate"/>
  <link href="https://yohoten.github.io/atom.xml" rel="self"/>
  <rights>All rights reserved 2026, yohoten</rights>
  <subtitle>记录技术 · 分享生活 · 保持热爱</subtitle>
  <title>Yohoten's Blog</title>
  <updated>2026-07-21T14:00:00.000Z</updated>
  <entry>
    <author>
      <name>yohoten</name>
    </author>
    <category term="开发" scheme="https://yohoten.github.io/categories/%E5%BC%80%E5%8F%91/"/>
    <category term="工具" scheme="https://yohoten.github.io/tags/%E5%B7%A5%E5%85%B7/"/>
    <category term="Python" scheme="https://yohoten.github.io/tags/Python/"/>
    <category term="开发" scheme="https://yohoten.github.io/tags/%E5%BC%80%E5%8F%91/"/>
    <category term="AI" scheme="https://yohoten.github.io/tags/AI/"/>
    <category term="TypeScript" scheme="https://yohoten.github.io/tags/TypeScript/"/>
    <content>
      <![CDATA[<h2 id="写在前面"><a href="#写在前面" class="headerlink" title="写在前面"></a>写在前面</h2><p>你有没有想过，给自己的电脑装一个「像素小人」陪你聊天？</p><p>前一阵子我一直在捣鼓一个叫 <strong>ChenXiaoxue</strong> 的项目——一个像素复古风 + AI 情感互动的桌面/Web 助手。做它的初衷其实很朴素：一个是想要一个能记住上下文、带点情绪反馈的 AI 陪伴，另一个就是想做个足够「有意思」的个人项目，把自己学过的前端、后端、桌面三块东西串起来练练手。</p><p>今天想跟你聊聊它从「单机像素小玩具」长成「三层全栈现代架构」的过程，中间踩了不少坑，也攒了不少经验。文章尽量写得实在，希望对你有点启发。</p><h2 id="一、第一代：PyQt5-单机像素应用"><a href="#一、第一代：PyQt5-单机像素应用" class="headerlink" title="一、第一代：PyQt5 单机像素应用"></a>一、第一代：PyQt5 单机像素应用</h2><p>故事的开头是 <strong>chenxiaoxue-orgin</strong>——一个 Python 3.7+ / PyQt5 的单机桌面应用。那时候的想法很简单：一个窗口，一个像素角色，一个输入框，连上 DeepSeek 的 API 就能对话。</p><p><img src="https://i.imgs.ovh/2026/08/18/6c64e76a6c8072a423eaec6febd397d0.webp"></p><p>它虽然「轻」，但五脏俱全：</p><ul><li><strong>像素主题</strong>：深蓝 <code>#1a1a2e</code> + 橙 <code>#ff9d00</code> 的配色，搭配 Press Start 2P / Zpix 像素字体，一眼就是复古游戏的味道。</li><li><strong>EnhancedResponseSystem 完整链路</strong>：分词 → 意图 → 情感 → API → 响应 → 上下文，一条链路走下来，还带缓存和 3 次重试。</li><li><strong>情感驱动的表情与音效</strong>：聊到高兴时，小人的表情和音效会跟着变。</li><li><strong>角色眨眼动画、打字机输出、心电图状态栏</strong>：这些小细节让「陪伴感」一下子就出来了。</li><li><strong>梦境符号解析</strong>：飞翔、坠落、水、火、蛇、追逐、考试、牙齿……它会帮你解析梦境里的意象。</li><li><strong>插件系统</strong>：基于 PluginBase + plugins.json，可插拔。</li><li><strong>消息导出</strong>：支持 JSON / TXT / CSV，方便把对话存档。</li><li><strong>中英日 i18n</strong>、<strong>「灵动 / 迟缓」两种输出模式</strong>。</li></ul><p>第一代用的技术栈：PyQt5 / requests / jieba / snownlp / lxml，SQLite 存对话历史。说实话，单模型（DeepSeek）对话 + 本地情感分析，已经能跑出一个很完整的「像素陪伴」体验了。</p><p>仓库就在：<a href="https://gitee.com/yohotens/chenxiaoxue-orgin">https://gitee.com/yohotens/chenxiaoxue-orgin</a></p><h2 id="二、第二代：全面重写，重写不丢初心"><a href="#二、第二代：全面重写，重写不丢初心" class="headerlink" title="二、第二代：全面重写，重写不丢初心"></a>二、第二代：全面重写，重写不丢初心</h2><p>单机版玩熟了之后，我开始不满足于「只能在自己电脑上跑」这个限制。2025.08 我启动了迁移，把它重写成了 <strong>chenxiaoxue-reborn-reborn</strong>——一套前后端分离 + 桌面壳的现代架构：</p><ul><li><strong>后端</strong>：FastAPI 0.115 + Uvicorn + SQLAlchemy 2.0 + aiosqlite + JWT(bcrypt) + slowapi 限流 + loguru + alembic</li><li><strong>前端</strong>：React 18 + TypeScript + Vite 4 + Three.js</li><li><strong>桌面壳</strong>：Tauri 2（Rust）</li></ul><p><img src="https://i.imgs.ovh/2026/08/18/e02a0ed3fccb401e44c3289b15fbebb7.webp"></p><p>三层技术栈（Python / TypeScript / Rust），一套代码同时覆盖 Web 和桌面。整体代码量大概 <strong>~11000 行</strong>：后端 Python ~5000+、前端 TS/React ~3000+、Rust ~500+、测试 ~500+、文档 ~2000+，26+ 个 API 端点，12+ 个功能模块。</p><p><img src="https://i.imgs.ovh/2026/08/18/10ef35f617d9e16022187d03917e16f0.webp"></p><p>关键是——<strong>重写不丢初心</strong>。像素主题一脉相承：GIF 背景、打字机输出、ASCII 艺术、CRT 扫描线、毛玻璃效果，全都保留并且升级了。并且注册购买了域名：<strong>chenxiaoxue.space</strong>，未来会运载在服务器中并挂载在此网址。</p><p>仓库在这里：<a href="https://gitee.com/yohoten_space/chenxiaoxue-reborn">https://gitee.com/yohoten_space/chenxiaoxue-reborn</a></p><h2 id="三、核心功能亮点"><a href="#三、核心功能亮点" class="headerlink" title="三、核心功能亮点"></a>三、核心功能亮点</h2><p>重写版功能一下子丰富了很多，挑几个我觉得最有意思的展开说说：</p><h3 id="1-多模型智能路由"><a href="#1-多模型智能路由" class="headerlink" title="1. 多模型智能路由"></a>1. 多模型智能路由</h3><p><code>ai_router.py</code> 是后端最核心的模块之一。它集成了 <strong>DeepSeek / OpenAI / 通义 / 豆包 / Kimi / Claude</strong> 等多家供应商，会根据「任务类型 + 复杂度 + 成本」自动选型，支持流式 SSE，还有故障转移——某个供应商挂了，自动切到下一个可用的。</p><p>成本控制靠的是 <code>model_pricing.json</code> 这份计价表，比如 <code>deepseek-chat</code> 约 $0.008/1k tokens，<code>gpt-4o</code> 约 $2.5/1k tokens。路由时把价格拉进来算一笔账，简单的日常聊天就不会动不动砸到贵模型上。</p><h3 id="2-模型协作-collaborative-chat"><a href="#2-模型协作-collaborative-chat" class="headerlink" title="2. 模型协作 collaborative_chat"></a>2. 模型协作 collaborative_chat</h3><p>单模型不够，那就多模型协作：<strong>任务分解 → 多模型并行 → 结果聚合</strong>。一个复杂问题拆成几块，分别交给不同的模型处理，最后汇总成一份答案。这也是现代 Agent 玩法里很经典的一种思路。</p><h3 id="3-MCP-协议"><a href="#3-MCP-协议" class="headerlink" title="3. MCP 协议"></a>3. MCP 协议</h3><p>重写版接入了 MCP（Model Context Protocol），暴露了 <code>chat_completion</code>、<code>analyze_emotion</code>、<code>translate</code>、<code>get_models</code>、<code>collaborative_chat</code> 等一批工具，并且支持接入外部 MCP 服务器。这意味着chenxiaoxue的能力可以像插头一样往外扩展，生态一下就打开了。</p><p><img src="https://i.imgs.ovh/2026/08/18/28f5d475add37ff159c4502393fc42f6.webp"></p><h3 id="4-情感分析：SnowNLP-AI-双模式"><a href="#4-情感分析：SnowNLP-AI-双模式" class="headerlink" title="4. 情感分析：SnowNLP + AI 双模式"></a>4. 情感分析：SnowNLP + AI 双模式</h3><p><code>emotion_service</code> 实现了混合情感分析，有 <code>auto / rule / ai</code> 三种模式：<strong>少于 20 个字走规则，否则交给 AI；AI 失败时自动降级回规则</strong>。底层还用 SnowNLP 做了阈值分段：</p><ul><li><code>&gt; 0.8</code> → ecstatic（狂喜）</li><li><code>&gt; 0.6</code> → happy（开心）</li><li><code>&gt; 0.5</code> → content（满足）</li><li><code>≥ 0.4</code> → neutral（平静）</li><li><code>≥ 0.2</code> → sad（低落）</li><li>否则 → sad</li></ul><p>一共 9 种情感，每种都有对应的 Emoji 映射（比如 ecstatic 对应 😊✨🎉，angry 对应 😠）。情感分析结果直接驱动小人表情、音效和 ECG 心电图，让「陪伴」不是冷冰冰的文字。</p><h3 id="5-像素主题-UI"><a href="#5-像素主题-UI" class="headerlink" title="5. 像素主题 UI"></a>5. 像素主题 UI</h3><p>这是我最舍不得的部分。GIF 动态背景、打字机逐字输出、ASCII 艺术、CRT 扫描线、毛玻璃卡片——一套下来，整个界面就像一台复古掌机。前端是 React 18 + TS + Vite 4 搭的，主题完全围绕「像素 + 复古」这一个核心设计语言展开。</p><p><img src="https://i.imgs.ovh/2026/08/18/0f5696bdaf045e003a62c5a706bdda45.webp"></p><h3 id="6-Humanizer-AI-检测器"><a href="#6-Humanizer-AI-检测器" class="headerlink" title="6. Humanizer AI 检测器"></a>6. Humanizer AI 检测器</h3><p>一个很有意思的小工具：内置 <strong>24 种 AI 写作模式</strong>，能对一段文字打分（0-100%），判断它「像不像 AI 写的」。写东西多了之后，真的很需要这样一面镜子来提醒自己「这段是不是又 AI 味太重了」。</p><p><img src="https://i.imgs.ovh/2026/08/18/cb6998c23b0470d18bfe8f02b6c9abb0.webp"></p><h3 id="7-ECG-情感心电图"><a href="#7-ECG-情感心电图" class="headerlink" title="7. ECG 情感心电图"></a>7. ECG 情感心电图</h3><p>Canvas 画出来的心电图，<strong>P/QRS/T 波</strong>齐全，颜色和心率会跟着情感状态变化。开心的时候心电图「扑通扑通」跳得欢快，低落的时候慢慢悠悠——把这个做成状态栏，我觉得是整台机器最有灵性的地方。</p><p><img src="https://i.imgs.ovh/2026/08/18/1270c6795143643074377adc04e90aac.webp"></p><h3 id="8-三级自我进化记忆"><a href="#8-三级自我进化记忆" class="headerlink" title="8. 三级自我进化记忆"></a>8. 三级自我进化记忆</h3><p><code>self_improving</code> 模块做了一套分层的长期记忆：</p><ul><li><strong>HOT</strong>：<code>memory.md</code>，≤100 行，记最近的对话状态；</li><li><strong>WARM</strong>：<code>projects</code> / <code>domains</code>，≤200 行，记项目相关的长期信息；</li><li><strong>COLD</strong>：归档，存更久远的内容。</li></ul><p>它会识别「纠正」和「偏好」信号，自动沉淀成 <code>CONTEXT / REFLECTION / LESSON</code> 三种记忆条目。用着用着，它真的会越来越「懂你」。</p><blockquote><p>顺带一提，还有 <strong>梦境解析 + Three.js 星图</strong>、<strong>JWT 多用户（Access 7 天 / Refresh 30 天）</strong>、<strong>插件系统</strong> 等一票功能，限于篇幅就不一一展开了。</p></blockquote><h2 id="四、技术与工程细节"><a href="#四、技术与工程细节" class="headerlink" title="四、技术与工程细节"></a>四、技术与工程细节</h2><p>作为工程人，再补几个「幕后」的细节：</p><ul><li><strong>launcher.bat v2.2</strong>：<code>setup / dev / backend / frontend / tauri / test / prod</code> 七种模式一个脚本全搞定。自己一个人开发前后端 + 桌面，启动器好不好用直接决定幸福指数。</li><li><strong>JWT 双 Token</strong>：Access 7 天 + Refresh 30 天，登录态既安全又省心。</li><li><strong>限流 + 日志</strong>：slowapi 做接口限流，loguru 管日志，alembic 管数据库迁移——该有的基建一个没落下。</li><li><strong>多语言前端</strong>：重写版继续保留了多语言支持，国际化一直是这个项目的保留节目。</li></ul><h2 id="五、踩过的坑与感悟"><a href="#五、踩过的坑与感悟" class="headerlink" title="五、踩过的坑与感悟"></a>五、踩过的坑与感悟</h2><p>写代码嘛，哪有不踩坑的。几个真实教训分享给你：</p><ol><li><strong>API Key 明文暴露过。</strong> 第一代项目的 <code>config.ini</code> 和 README 里，曾直接把 DeepSeek API Key 明文写了出来。虽然是个人项目，但这也是非常不安全的坏习惯。重写版全部改成了 <strong>.env 环境变量</strong>，密钥绝不进仓库。<strong>血的教训：密钥永远不要硬编码进代码或文档。</strong></li><li><strong>单文件过大。</strong> 第一代 <code>chat_window.py</code> 有 <strong>2539 行</strong>，重写版的 <code>ai_router.py</code> 也有 <strong>1240 行</strong>。功能堆着堆着文件就失控了。现在回头看，早期就应该更早拆模块、抽服务。</li><li><strong>命名不一致。</strong> 项目名一会儿「陈筱雪」一会儿「Chen筱雪」一会儿「ChenXiaoxue」，甚至仓库名 <code>chenxiaoxue-orgin</code> 里的 <code>orgin</code> 还是个拼写笔误（应该是 origin）。<strong>正式项目开工前，先定好统一的命名规范，包括仓库名，不然后面到处都是坑。</strong></li><li><strong>重写要有理由，也要有底线。</strong> 全面重写容易上头，但一定要想清楚「保留什么、抛弃什么」。我这次坚持把「像素主题」这条灵魂主线一路带下来，重写才没有变成「重做」。</li></ol><p>对了，我的本机开发路径是 <code>d:/pixel_ai_assistant-250907/Chen Xiaoxue/</code>，项目一路从像素小玩具长到现在，也算有点感情了（笑）。</p><h2 id="六、结尾"><a href="#六、结尾" class="headerlink" title="六、结尾"></a>六、结尾</h2><p>chenxiaoxue这个项目，是我把前端、后端、桌面三块能力真正「合体」的一次尝试。第一代让我理解了「一个完整的 AI 应用」长什么样，第二代让我学会了「如何把一套个人玩具做成正经的多层架构」。如果你也喜欢像素风，或者对「AI 情感陪伴」「全栈 + Tauri 桌面」感兴趣，欢迎来仓库逛逛、提提 issue。</p><p><strong>项目仓库：</strong></p><ul><li>第一代：<a href="https://github.com/yohoten/chenxiaoxue-orgin">https://github.com/yohoten/chenxiaoxue-orgin</a> / <a href="https://gitee.com/yohotens/chenxiaoxue-orgin">https://gitee.com/yohotens/chenxiaoxue-orgin</a></li><li>第二代：<a href="https://github.com/yohoten/chenxiaoxue-reborn">https://github.com/yohoten/chenxiaoxue-reborn</a> / <a href="https://gitee.com/yohoten_space/chenxiaoxue-reborn">https://gitee.com/yohoten_space/chenxiaoxue-reborn</a></li></ul><p><strong>相关阅读：</strong></p><ul><li>知乎问答：<a href="https://www.zhihu.com/question/15175936725/answer/1944403822710596747">https://www.zhihu.com/question/15175936725/answer/1944403822710596747</a></li><li>知乎专栏：<a href="https://zhuanlan.zhihu.com/p/1961952610673226156">https://zhuanlan.zhihu.com/p/1961952610673226156</a></li></ul><p>未来我还想给它加更多的东西：更聪明的记忆策略、更丰富的像素场景、更顺滑的桌面体验……不过这些都慢慢来。如果你也有类似的「像素 + AI」想法，欢迎来找我交流～</p><p>路还长，我们继续写下去。</p>]]>
    </content>
    <id>https://yohoten.github.io/2026/07/21/ChenXiaoxue-%E5%83%8F%E7%B4%A0%E9%A3%8E%E6%83%85%E6%84%9F%E4%BA%92%E5%8A%A8%E7%A8%8B%E5%BA%8F/</id>
    <link href="https://yohoten.github.io/2026/07/21/ChenXiaoxue-%E5%83%8F%E7%B4%A0%E9%A3%8E%E6%83%85%E6%84%9F%E4%BA%92%E5%8A%A8%E7%A8%8B%E5%BA%8F/"/>
    <published>2026-07-21T14:00:00.000Z</published>
    <summary>分享我开发的像素复古风 AI 情感互动助手「ChenXiaoxue」——从第一代 PyQt5 单机像素应用，到第二代 FastAPI + React + Tauri 全面重写，重写不丢初心的技术笔记与踩坑实录。</summary>
    <title>ChenXiaoxue 像素风情感互动程序：从 PyQt5 到全栈重写的心路历程</title>
    <updated>2026-07-21T14:00:00.000Z</updated>
  </entry>
  <entry>
    <author>
      <name>yohoten</name>
    </author>
    <category term="开发" scheme="https://yohoten.github.io/categories/%E5%BC%80%E5%8F%91/"/>
    <category term="工具" scheme="https://yohoten.github.io/tags/%E5%B7%A5%E5%85%B7/"/>
    <category term="Python" scheme="https://yohoten.github.io/tags/Python/"/>
    <content>
      <![CDATA[<h2 id="📖-简介"><a href="#📖-简介" class="headerlink" title="📖 简介"></a>📖 简介</h2><p>ACRPA 是一款轻量级桌面自动化工具，专为普通用户设计。它采用 Python 开发，基于 <code>tkinter</code> 构建图形界面，底层操作由 <code>pyautogui</code> 实现。用户无需编写代码，只需通过 Excel 脚本文件（<code>.xls</code>）和json文本文件（<code>.json</code>）编排自动化流程，借助图像识别定位技术，即可让程序自动处理重复性的桌面任务。项目代码托管于 GitHub 和 Gitee，由开发者 <strong>yohoten</strong> 本人维护。</p><blockquote><p>🌐 <strong>网页直达</strong>：<a href="https://yohoten.github.io/acrpa/">https://yohoten.github.io/acrpa/</a> —— 图文并茂的产品主页，含功能演示与使用说明和下载直链，推荐先从这里了解。</p></blockquote><p>🔗 <strong>仓库地址</strong>：</p><ul><li><a href="https://github.com/yohoten/acrpa">https://github.com/yohoten/acrpa</a></li><li><a href="https://gitee.com/yohoten/acrpa">https://gitee.com/yohoten/acrpa</a>（推荐）</li></ul><hr><h2 id="✨-核心特点"><a href="#✨-核心特点" class="headerlink" title="✨ 核心特点"></a>✨ 核心特点</h2><ul><li><strong>轻量冷启动</strong>：资源占用小，启动迅速</li><li><strong>Excel 脚本驱动</strong>：使用常见表格文件编排流程，无需编程</li><li><strong>图像识别定位</strong>：精准点击，支持区域搜索</li><li><strong>录制回放</strong>：含拖拽录制，一键生成脚本</li><li><strong>窗口管理</strong>：直接操作 Windows 窗口，无需图像识别，支持窗口相对坐标</li><li><strong>工作流编排</strong>：多脚本串联 + 可视化流程图（节点/连线/拖拽）</li><li><strong>变量系统</strong>：支持复杂数据处理和数学运算</li><li><strong>AI 增强</strong>：视觉定位、智能重试、异常检测、自然语言调试</li><li><strong>调试器</strong>：断点/条件断点（标记旁显示表达式）/单步/变量监视/调用栈</li><li><strong>深浅主题切换</strong>、错误重试、日志导出、Py 脚本置入</li></ul><hr><h2 id="🧩-自动化命令支持"><a href="#🧩-自动化命令支持" class="headerlink" title="🧩 自动化命令支持"></a>🧩 自动化命令支持</h2><p>工具支持 <strong>30 种以上</strong> 的操作类型，涵盖桌面自动化常见需求：</p><h3 id="基础命令"><a href="#基础命令" class="headerlink" title="基础命令"></a>基础命令</h3><table><thead><tr><th>命令</th><th>参数示例</th><th>说明</th></tr></thead><tbody><tr><td>找图</td><td>button.png, 0.9</td><td>全屏查找图片</td></tr><tr><td>点图</td><td>submit.png, 0.9</td><td>找到并点击</td></tr><tr><td>区域找图</td><td>icon.png, 0.8, 100,100,500,500</td><td>限定区域查找</td></tr><tr><td>按键</td><td>enter, 1</td><td>按键（可指定次数）</td></tr><tr><td>热键</td><td>ctrl, c</td><td>组合键</td></tr><tr><td>输入</td><td>Hello World</td><td>粘贴输入（Ctrl+V）</td></tr><tr><td>写入</td><td>Hello,0.05,auto</td><td>逐字输入（支持 DD 驱动增强）</td></tr><tr><td>等待</td><td>2.5</td><td>延时（秒，支持随机范围如 1.0-3.0）</td></tr><tr><td>坐标</td><td>500, 300</td><td>点击屏幕坐标</td></tr><tr><td>滚轮</td><td>-3</td><td>滚动行数</td></tr></tbody></table><h3 id="窗口管理"><a href="#窗口管理" class="headerlink" title="窗口管理"></a>窗口管理</h3><table><thead><tr><th>命令</th><th>参数示例</th><th>说明</th></tr></thead><tbody><tr><td>激活窗口</td><td>记事本</td><td>激活指定窗口（支持模糊匹配）</td></tr><tr><td>关闭窗口</td><td>无标题 - 记事本</td><td>关闭指定窗口</td></tr><tr><td>最小化窗口</td><td>计算器</td><td>最小化窗口到任务栏</td></tr><tr><td>最大化窗口</td><td>资源管理器</td><td>最大化窗口</td></tr><tr><td>获取窗口位置</td><td>记事本,x,y,w,h</td><td>获取窗口坐标和大小</td></tr><tr><td>等待窗口</td><td>Chrome,10,存在</td><td>等待窗口出现/消失</td></tr><tr><td>窗口坐标</td><td>记事本,100,50</td><td>相对于窗口左上角点击</td></tr></tbody></table><h3 id="变量增强"><a href="#变量增强" class="headerlink" title="变量增强"></a>变量增强</h3><table><thead><tr><th>命令</th><th>参数示例</th><th>说明</th></tr></thead><tbody><tr><td>设置变量</td><td>count,100</td><td>设置变量值</td></tr><tr><td>读取剪贴板</td><td>clip_text</td><td>读取剪贴板到变量</td></tr><tr><td>字符串处理</td><td>text,截取,0:5,result</td><td>字符串操作</td></tr><tr><td>数学运算</td><td>x+<em>x</em> +{y},sum</td><td>数学计算</td></tr></tbody></table><h3 id="工作流"><a href="#工作流" class="headerlink" title="工作流"></a>工作流</h3><table><thead><tr><th>命令</th><th>参数示例</th><th>说明</th></tr></thead><tbody><tr><td>运行工作流</td><td>workflow.json</td><td>调用工作流文件执行多脚本编排</td></tr><tr><td>工作流变量</td><td>var_name,value</td><td>设置工作流级共享变量</td></tr></tbody></table><h3 id="AI-增强"><a href="#AI-增强" class="headerlink" title="AI 增强"></a>AI 增强</h3><table><thead><tr><th>命令</th><th>参数示例</th><th>说明</th></tr></thead><tbody><tr><td>AI找图</td><td>登录按钮,0.8,click</td><td>AI 分析截图，自然语言描述找元素</td></tr><tr><td>AI识别界面</td><td>ui</td><td>AI 列出所有 UI 元素存入变量</td></tr><tr><td>AI优化建议</td><td>log</td><td>AI 分析执行数据给出优化建议</td></tr><tr><td>[AI] 调试</td><td>点击 [AI] 调试按钮</td><td>自然语言提问，AI 分析日志回答</td></tr></tbody></table><blockquote><p>💡 <strong>变量引用语法</strong>：使用 <code>$&#123;variable_name&#125;</code> 引用变量</p></blockquote><hr><h2 id="📁-示例脚本库"><a href="#📁-示例脚本库" class="headerlink" title="📁 示例脚本库"></a>📁 示例脚本库</h2><p><strong>全新推出！</strong> 提供 11 个即用型模板，帮助你快速上手 ACRPA 自动化。</p>]]>
    </content>
    <id>https://yohoten.github.io/2026/05/24/ACRPA-%E6%A1%8C%E9%9D%A2%E8%87%AA%E5%8A%A8%E5%8C%96%E5%B7%A5%E4%BD%9C%E6%B5%81%E5%B7%A5%E5%85%B7/</id>
    <link href="https://yohoten.github.io/2026/05/24/ACRPA-%E6%A1%8C%E9%9D%A2%E8%87%AA%E5%8A%A8%E5%8C%96%E5%B7%A5%E4%BD%9C%E6%B5%81%E5%B7%A5%E5%85%B7/"/>
    <published>2026-05-24T14:07:00.000Z</published>
    <summary>分享一个个人开发的桌面自动化脚本工具。</summary>
    <title>ACRPA-轻量级桌面自动化工作流工具</title>
    <updated>2026-05-24T14:07:00.000Z</updated>
  </entry>
  <entry>
    <author>
      <name>yohoten</name>
    </author>
    <category term="学习笔记" scheme="https://yohoten.github.io/categories/%E5%AD%A6%E4%B9%A0%E7%AC%94%E8%AE%B0/"/>
    <category term="深度学习" scheme="https://yohoten.github.io/tags/%E6%B7%B1%E5%BA%A6%E5%AD%A6%E4%B9%A0/"/>
    <category term="PyTorch" scheme="https://yohoten.github.io/tags/PyTorch/"/>
    <category term="学习笔记" scheme="https://yohoten.github.io/tags/%E5%AD%A6%E4%B9%A0%E7%AC%94%E8%AE%B0/"/>
    <category term="优化算法" scheme="https://yohoten.github.io/tags/%E4%BC%98%E5%8C%96%E7%AE%97%E6%B3%95/"/>
    <content>
      <![CDATA[<p>反向传播是一个计算梯度的过程，它通过链式法则将损失函数关于网络参数的梯度从输出层向输入层传播。梯度下降则是一种基于梯度信息的优化算法，它通过沿着梯度的反方向调整参数值，以减小损失函数的值。</p><p>这里列出常见的几个梯度下降算法，按算法的复杂度排列为：</p><p>SGD &lt; SGDM &lt; NAG &lt; AdaGrad &lt; RMSProp &lt; AdaDelta &lt; Adam &lt; Nadam</p><p>目前最常用的是 Adam。</p><h2 id="目录"><a href="#目录" class="headerlink" title="目录"></a>目录</h2><ol><li>SGD</li><li>SGDM</li><li>NAG</li><li>AdaGrad</li><li>RMSProp</li><li>AdaDelta</li><li>Adam</li><li>Nadam</li></ol><h2 id="一、SGD"><a href="#一、SGD" class="headerlink" title="一、SGD"></a>一、SGD</h2><p>随机梯度下降 SGD（Stochastic Gradient Descent）为基本的梯度下降算法，每次迭代使用单个样本的梯度来更新参数。</p><p>PyTorch 实现：<code>torch.optim.SGD(params)</code></p><p>PyTorch 文档：<a href="https://pytorch.org/docs/stable/generated/torch.optim.SGD.html">https://pytorch.org/docs/stable/generated/torch.optim.SGD.html</a></p><h2 id="二、SGDM"><a href="#二、SGDM" class="headerlink" title="二、SGDM"></a>二、SGDM</h2><p>带动量的随机梯度下降 SGDM（SGD with Momentum）。在 SGD 的基础上增加了一个动量项，该项考虑了上次的梯度更新的方向和速度，这样可以帮助加速收敛，并且有助于跳出局部最优解。直观的描述是：类似于物理学上的小球滚落过程，有一个动量或者惯性。</p><p>PyTorch 实现：<code>torch.optim.SGD(params, momentum)</code></p><p>PyTorch 文档：同上。</p><h2 id="三、NAG"><a href="#三、NAG" class="headerlink" title="三、NAG"></a>三、NAG</h2><p>NAG（Nesterov Accelerated Gradient）是一种改进的梯度下降算法。主要思想是在更新参数之前，首先利用动量来预测下一步的位置，然后在这个预测位置处计算梯度，这种做法可以减少动量带来的过度振荡，更有效地朝着最优解方向移动，从而加速收敛。直观的描述是：在小球滚落的过程中，可以提前预知前方情况，这样如果遇到了上升坡面，小球可以在之前提前减速。</p><p>PyTorch 实现：<code>torch.optim.SGD(params, momentum, nesterov=True)</code></p><p>PyTorch 文档：同上。</p><h2 id="四、AdaGrad"><a href="#四、AdaGrad" class="headerlink" title="四、AdaGrad"></a>四、AdaGrad</h2><p>AdaGrad（Adaptive Gradient）为自适应学习率的算法，根据每个参数的历史梯度调整学习率。随着训练的进行，学习率会逐渐变小，可能导致学习速度减慢。</p><p>PyTorch 实现：<code>torch.optim.Adagrad(params)</code></p><p>PyTorch 文档：<a href="https://pytorch.org/docs/stable/generated/torch.optim.Adagrad.html">https://pytorch.org/docs/stable/generated/torch.optim.Adagrad.html</a></p><h2 id="五、RMSProp"><a href="#五、RMSProp" class="headerlink" title="五、RMSProp"></a>五、RMSProp</h2><p>RMSProp（Root Mean Square Propagation）也是一种自适应学习率算法，但相比 AdaGrad，RMSProp 使用了一个衰减系数来限制历史梯度的影响，修正了 AdaGrad 学习率下降过快的问题。</p><p>PyTorch 实现：<code>torch.optim.RMSprop(params)</code></p><p>PyTorch 文档：<a href="https://pytorch.org/docs/stable/generated/torch.optim.RMSprop.html">https://pytorch.org/docs/stable/generated/torch.optim.RMSprop.html</a></p><h2 id="六、AdaDelta"><a href="#六、AdaDelta" class="headerlink" title="六、AdaDelta"></a>六、AdaDelta</h2><p>AdaDelta（Adaptive Delta）是 RMSProp 的一种改进版本，主要解决了 RMSProp 中需要手动设置初始学习率的问题。RMSProp 需要手动设置一个初始学习率，而 AdaDelta 完全去掉了学习率的概念，它使用了一个类似于 RMSProp 的梯度平方的指数加权平均来调整学习率，但不需要设置初始学习率。</p><p>PyTorch 实现：<code>torch.optim.Adadelta(params)</code></p><p>PyTorch 文档：<a href="https://pytorch.org/docs/stable/generated/torch.optim.Adadelta.html">https://pytorch.org/docs/stable/generated/torch.optim.Adadelta.html</a></p><h2 id="七、Adam"><a href="#七、Adam" class="headerlink" title="七、Adam"></a>七、Adam</h2><p>Adam（Adaptive Moment Estimation）结合了以上的动量和自适应学习率的优势，是一种流行的优化算法，通常表现出很好的性能，对于大多数问题都是一个良好的默认选择。</p><p>PyTorch 实现：<code>torch.optim.Adam(params)</code></p><p>PyTorch 文档：<a href="https://pytorch.org/docs/stable/generated/torch.optim.Adam.html">https://pytorch.org/docs/stable/generated/torch.optim.Adam.html</a></p><h2 id="八、Nadam"><a href="#八、Nadam" class="headerlink" title="八、Nadam"></a>八、Nadam</h2><p>Nadam 是一种优化算法，是 Nesterov Accelerated Gradient（NAG）和 Adam 的结合体。</p><p>PyTorch 实现：<code>torch.optim.NAdam(params)</code></p><p>PyTorch 文档：<a href="https://pytorch.org/docs/stable/generated/torch.optim.NAdam.html">https://pytorch.org/docs/stable/generated/torch.optim.NAdam.html</a></p><h2 id="附：两张动图"><a href="#附：两张动图" class="headerlink" title="附：两张动图"></a>附：两张动图</h2><p><img src="/images/gradient-descent-3d-path.gif" alt="三维损失曲面上各算法的下降轨迹"></p><p><img src="/images/gradient-descent-contour.gif" alt="等高线视角下各算法的下降轨迹"></p><hr><p>参考资料：<a href="https://www.guanjihuan.com/archives/38807">https://www.guanjihuan.com/archives/38807</a></p>]]>
    </content>
    <id>https://yohoten.github.io/2025/03/12/%E5%B8%B8%E8%A7%81%E7%9A%84%E6%A2%AF%E5%BA%A6%E4%B8%8B%E9%99%8D%E7%AE%97%E6%B3%95/</id>
    <link href="https://yohoten.github.io/2025/03/12/%E5%B8%B8%E8%A7%81%E7%9A%84%E6%A2%AF%E5%BA%A6%E4%B8%8B%E9%99%8D%E7%AE%97%E6%B3%95/"/>
    <published>2025-03-12T04:00:00.000Z</published>
    <summary>按复杂度梳理八种常见梯度下降算法——SGD、SGDM、NAG、AdaGrad、RMSProp、AdaDelta、Adam、Nadam，说明各自的核心思想、优缺点与 PyTorch 实现方式。</summary>
    <title>常见的梯度下降算法</title>
    <updated>2025-03-12T04:00:00.000Z</updated>
  </entry>
  <entry>
    <author>
      <name>yohoten</name>
    </author>
    <category term="日常" scheme="https://yohoten.github.io/categories/%E6%97%A5%E5%B8%B8/"/>
    <category term="随笔" scheme="https://yohoten.github.io/tags/%E9%9A%8F%E7%AC%94/"/>
    <category term="学习" scheme="https://yohoten.github.io/tags/%E5%AD%A6%E4%B9%A0/"/>
    <content>
      <![CDATA[<h2 id="写在前面"><a href="#写在前面" class="headerlink" title="写在前面"></a>写在前面</h2><p>经常有人问我：技术到底应该怎么学？其实每个人情况不同，没有标准答案。这里分享我自己的学习路线和一些心得，希望能给你一些启发。</p><h2 id="一、我的技术路线"><a href="#一、我的技术路线" class="headerlink" title="一、我的技术路线"></a>一、我的技术路线</h2><h3 id="1-打好基础"><a href="#1-打好基础" class="headerlink" title="1. 打好基础"></a>1. 打好基础</h3><p>无论前端还是后端，数据结构与算法、计算机网络、操作系统这些基础都绕不开。基础扎实了，学新框架会快很多。</p><h3 id="2-主攻一个方向"><a href="#2-主攻一个方向" class="headerlink" title="2. 主攻一个方向"></a>2. 主攻一个方向</h3><p>建议先选一个方向深入，不要什么都浅尝辄止：</p><ul><li><strong>前端</strong>：JavaScript/TypeScript → Vue/React → 工程化</li><li><strong>后端</strong>：一门语言（Java/Python/Node.js）→ 数据库 → Web 框架</li></ul><h3 id="3-用项目驱动学习"><a href="#3-用项目驱动学习" class="headerlink" title="3. 用项目驱动学习"></a>3. 用项目驱动学习</h3><p>光看不练假把式。每学完一个知识点，就做一个小的 Demo 或项目，把知识用起来。</p><h2 id="二、我的学习心得"><a href="#二、我的学习心得" class="headerlink" title="二、我的学习心得"></a>二、我的学习心得</h2><h3 id="1-输出是最好的学习"><a href="#1-输出是最好的学习" class="headerlink" title="1. 输出是最好的学习"></a>1. 输出是最好的学习</h3><p>写博客、做分享、回答别人的问题，都是极好的学习方式。这也是我搭建这个博客的初衷——<strong>通过写作倒逼自己把知识讲清楚</strong>。</p><h3 id="2-学会搜索与阅读源码"><a href="#2-学会搜索与阅读源码" class="headerlink" title="2. 学会搜索与阅读源码"></a>2. 学会搜索与阅读源码</h3><p>遇到问题先自己搜（Google、Stack Overflow、官方文档），别急着问。看源码是最快理解一个框架的方式。</p><h3 id="3-保持好奇心与耐心"><a href="#3-保持好奇心与耐心" class="headerlink" title="3. 保持好奇心与耐心"></a>3. 保持好奇心与耐心</h3><p>技术迭代很快，但底层原理变化很慢。保持好奇心，遇到不懂的先记下来，慢慢补。</p><h2 id="三、给新手的建议"><a href="#三、给新手的建议" class="headerlink" title="三、给新手的建议"></a>三、给新手的建议</h2><ol><li>不要囤积教程，选一个系列跟到底</li><li>每周末复盘一次本周所学</li><li>尽早建立自己的知识库（博客、笔记皆可）</li></ol><h2 id="结语"><a href="#结语" class="headerlink" title="结语"></a>结语</h2><p>路虽远，行则将至。愿我们在技术的路上，一直保持热爱。</p><p>如果你也有好的学习方法，欢迎在评论区或友链里和我交流～</p>]]>
    </content>
    <id>https://yohoten.github.io/2025/01/04/%E6%88%91%E7%9A%84%E6%8A%80%E6%9C%AF%E5%AD%A6%E4%B9%A0%E8%B7%AF%E7%BA%BF%E4%B8%8E%E5%BF%83%E5%BE%97/</id>
    <link href="https://yohoten.github.io/2025/01/04/%E6%88%91%E7%9A%84%E6%8A%80%E6%9C%AF%E5%AD%A6%E4%B9%A0%E8%B7%AF%E7%BA%BF%E4%B8%8E%E5%BF%83%E5%BE%97/"/>
    <published>2025-01-04T04:00:00.000Z</published>
    <summary>分享我的技术学习路线与踩坑心得，与大家共勉。</summary>
    <title>我的技术学习路线与心得</title>
    <updated>2025-01-04T04:00:00.000Z</updated>
  </entry>
  <entry>
    <author>
      <name>yohoten</name>
    </author>
    <category term="技术教程" scheme="https://yohoten.github.io/categories/%E6%8A%80%E6%9C%AF%E6%95%99%E7%A8%8B/"/>
    <category term="教程" scheme="https://yohoten.github.io/tags/%E6%95%99%E7%A8%8B/"/>
    <category term="Hexo" scheme="https://yohoten.github.io/tags/Hexo/"/>
    <content>
      <![CDATA[<h2 id="前言"><a href="#前言" class="headerlink" title="前言"></a>前言</h2><p>建好博客之后，怎么高效地写文章、预览、并发布到线上，是每个博主最关心的日常操作。本文基于本博客（Hexo + Butterfly）整理了一份完整的使用指南。</p><h2 id="一、写作"><a href="#一、写作" class="headerlink" title="一、写作"></a>一、写作</h2><h3 id="1-新建文章"><a href="#1-新建文章" class="headerlink" title="1. 新建文章"></a>1. 新建文章</h3><p>在博客根目录执行：</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">hexo new 文章标题</span><br></pre></td></tr></table></figure><p>执行后会在 <code>source/_posts/</code> 目录下生成一个 Markdown 文件，并自动带上模板（Front Matter）。</p><h3 id="2-认识-Front-Matter"><a href="#2-认识-Front-Matter" class="headerlink" title="2. 认识 Front Matter"></a>2. 认识 Front Matter</h3><p>文章开头的 <code>---</code> 之间就是 Front Matter，用于声明文章的元信息：</p><figure class="highlight yaml"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">---</span></span><br><span class="line"><span class="attr">title:</span> <span class="string">文章标题</span></span><br><span class="line"><span class="attr">date:</span> <span class="number">2025-01-03 12:00:00</span></span><br><span class="line"><span class="attr">tags:</span></span><br><span class="line">  <span class="bullet">-</span> <span class="string">Hexo</span></span><br><span class="line"><span class="attr">categories:</span></span><br><span class="line">  <span class="bullet">-</span> <span class="string">技术教程</span></span><br><span class="line"><span class="attr">description:</span> <span class="string">文章摘要，会显示在首页卡片上</span></span><br><span class="line"><span class="attr">cover:</span> <span class="string">/images/cover2.svg</span></span><br><span class="line"><span class="meta">---</span></span><br></pre></td></tr></table></figure><p>常用字段说明：</p><table><thead><tr><th>字段</th><th>作用</th></tr></thead><tbody><tr><td><code>title</code></td><td>文章标题</td></tr><tr><td><code>date</code></td><td>发布时间</td></tr><tr><td><code>tags</code></td><td>标签，可多个</td></tr><tr><td><code>categories</code></td><td>分类（多个分类会形成层级）</td></tr><tr><td><code>description</code></td><td>首页摘要，优先于正文截取显示</td></tr><tr><td><code>cover</code></td><td>文章封面图</td></tr><tr><td><code>top</code></td><td>置顶，数值越大越靠前</td></tr><tr><td><code>comments</code></td><td>是否开启评论（true/false）</td></tr></tbody></table><h3 id="3-使用-Markdown-写作"><a href="#3-使用-Markdown-写作" class="headerlink" title="3. 使用 Markdown 写作"></a>3. 使用 Markdown 写作</h3><p>正文使用标准 Markdown 语法即可，代码块可以指定语言获得高亮：</p><figure class="highlight markdown"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line"><span class="code">```javascript</span></span><br><span class="line"><span class="code">const greeting = &#x27;Hello, Yohoten!&#x27;</span></span><br><span class="line"><span class="code">console.log(greeting)</span></span><br><span class="line"><span class="code">```</span></span><br></pre></td></tr></table></figure><h2 id="二、本地预览"><a href="#二、本地预览" class="headerlink" title="二、本地预览"></a>二、本地预览</h2><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">hexo clean <span class="comment"># 清除缓存</span></span><br><span class="line">hexo server <span class="comment"># 启动本地服务，默认 http://localhost:4000</span></span><br></pre></td></tr></table></figure><p>浏览器打开 <code>http://localhost:4000</code> 即可实时预览，修改文章后刷新即可看到效果。</p><h2 id="三、生成静态文件"><a href="#三、生成静态文件" class="headerlink" title="三、生成静态文件"></a>三、生成静态文件</h2><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">hexo generate <span class="comment"># 生成静态文件到 public 目录</span></span><br></pre></td></tr></table></figure><p>生成后可以本地 <code>hexo server</code> 预览，也可以把 <code>public/</code> 目录部署到任意静态托管平台。</p><h2 id="四、一键部署到-GitHub-Pages"><a href="#四、一键部署到-GitHub-Pages" class="headerlink" title="四、一键部署到 GitHub Pages"></a>四、一键部署到 GitHub Pages</h2><p>本博客已配置好 <code>hexo-deployer-git</code> 插件，只需：</p><ol><li>在 GitHub 上创建仓库 <code>你的用户名.github.io</code></li><li>修改根目录 <code>_config.yml</code> 中的 <code>deploy.repo</code> 为你自己的仓库地址</li><li>执行：</li></ol><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">hexo clean &amp;&amp; hexo generate &amp;&amp; hexo deploy</span><br></pre></td></tr></table></figure><p>稍等片刻，访问 <code>https://你的用户名.github.io</code> 即可看到线上博客。</p><blockquote><p>提示：Windows 的 cmd 不支持 <code>&amp;&amp;</code> 链式命令，请分步执行：</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line">hexo clean</span><br><span class="line">hexo generate</span><br><span class="line">hexo deploy</span><br></pre></td></tr></table></figure></blockquote><h2 id="五、常用命令速查"><a href="#五、常用命令速查" class="headerlink" title="五、常用命令速查"></a>五、常用命令速查</h2><table><thead><tr><th>命令</th><th>说明</th></tr></thead><tbody><tr><td><code>hexo new 标题</code></td><td>新建文章</td></tr><tr><td><code>hexo new page 名称</code></td><td>新建页面（如 tags/categories）</td></tr><tr><td><code>hexo clean</code></td><td>清除缓存</td></tr><tr><td><code>hexo generate</code> / <code>hexo g</code></td><td>生成静态文件</td></tr><tr><td><code>hexo server</code> / <code>hexo s</code></td><td>本地预览</td></tr><tr><td><code>hexo deploy</code> / <code>hexo d</code></td><td>部署上线</td></tr></tbody></table><h2 id="六、常见问题"><a href="#六、常见问题" class="headerlink" title="六、常见问题"></a>六、常见问题</h2><ul><li><strong>修改主题配置不生效</strong>：运行 <code>hexo clean</code> 后再 <code>hexo server</code>。</li><li><strong>图片放在哪里</strong>：放在 <code>source/images/</code> 目录，Markdown 中写 <code>/images/xxx.png</code>。</li><li><strong>文章排序不对</strong>：检查 Front Matter 中的 <code>date</code> 是否写错。</li></ul><p>祝大家写作愉快！</p>]]>
    </content>
    <id>https://yohoten.github.io/2025/01/03/Hexo%E5%8D%9A%E5%AE%A2%E4%BD%BF%E7%94%A8%E6%8C%87%E5%8D%97-%E4%BB%8E%E5%86%99%E4%BD%9C%E5%88%B0%E5%8F%91%E5%B8%83/</id>
    <link href="https://yohoten.github.io/2025/01/03/Hexo%E5%8D%9A%E5%AE%A2%E4%BD%BF%E7%94%A8%E6%8C%87%E5%8D%97-%E4%BB%8E%E5%86%99%E4%BD%9C%E5%88%B0%E5%8F%91%E5%B8%83/"/>
    <published>2025-01-03T04:00:00.000Z</published>
    <summary>建好博客只是开始，本文分享 Hexo 的日常写作、本地预览与一键部署的完整流程。</summary>
    <title>Hexo 博客使用指南：从写作到发布</title>
    <updated>2025-01-03T04:00:00.000Z</updated>
  </entry>
  <entry>
    <author>
      <name>yohoten</name>
    </author>
    <category term="学习笔记" scheme="https://yohoten.github.io/categories/%E5%AD%A6%E4%B9%A0%E7%AC%94%E8%AE%B0/"/>
    <category term="深度学习" scheme="https://yohoten.github.io/tags/%E6%B7%B1%E5%BA%A6%E5%AD%A6%E4%B9%A0/"/>
    <category term="PyTorch" scheme="https://yohoten.github.io/tags/PyTorch/"/>
    <category term="MNIST" scheme="https://yohoten.github.io/tags/MNIST/"/>
    <category term="神经网络" scheme="https://yohoten.github.io/tags/%E7%A5%9E%E7%BB%8F%E7%BD%91%E7%BB%9C/"/>
    <category term="学习笔记" scheme="https://yohoten.github.io/tags/%E5%AD%A6%E4%B9%A0%E7%AC%94%E8%AE%B0/"/>
    <content>
      <![CDATA[<h2 id="一、MNIST-数据集"><a href="#一、MNIST-数据集" class="headerlink" title="一、MNIST 数据集"></a>一、MNIST 数据集</h2><p>MNIST 数据集包含 70000 张手写数字的灰度图像，分为以下两部分：</p><ul><li>训练集：60,000 张图像</li><li>测试集：10,000 张图像</li></ul><p>其中，每张图像是 28×28 像素，表示一个手写的数字（0 到 9），即每张图像有 784（28×28）个像素点，每个像素的值是一个从 0 到 255 的灰度值。</p><p>MNIST 数据集有四个文件（下载时是 .gz 压缩包格式，约 11M，会自动解压缩）：</p><ul><li>train-images-idx3-ubyte（训练集图像）</li><li>train-labels-idx1-ubyte（训练集标签）</li><li>t10k-images-idx3-ubyte（测试集图像）</li><li>t10k-labels-idx1-ubyte（测试集标签）</li></ul><h2 id="二、下载数据集与查看图片"><a href="#二、下载数据集与查看图片" class="headerlink" title="二、下载数据集与查看图片"></a>二、下载数据集与查看图片</h2><p>下载 MNIST 数据集和查看图片的 Python 代码如下：</p><figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br></pre></td><td class="code"><pre><span class="line"><span class="string">&quot;&quot;&quot;</span></span><br><span class="line"><span class="string">This code is supported by the website: https://www.guanjihuan.com</span></span><br><span class="line"><span class="string">The newest version of this code is on the web page: https://www.guanjihuan.com/archives/43720</span></span><br><span class="line"><span class="string">&quot;&quot;&quot;</span></span><br><span class="line"></span><br><span class="line"><span class="keyword">from</span> torchvision <span class="keyword">import</span> datasets, transforms</span><br><span class="line"></span><br><span class="line">transform = transforms.Compose([transforms.ToTensor()]) <span class="comment"># 定义数据预处理步骤（转换为Tensor）</span></span><br><span class="line">train_dataset = datasets.MNIST(root=<span class="string">&#x27;./data&#x27;</span>, train=<span class="literal">True</span>, download=<span class="literal">True</span>, transform=transform) <span class="comment"># 加载 MNIST 数据集，训练集</span></span><br><span class="line"><span class="built_in">print</span>(<span class="built_in">type</span>(train_dataset))</span><br><span class="line">size_of_train_dataset = <span class="built_in">len</span>(train_dataset)</span><br><span class="line"><span class="built_in">print</span>(size_of_train_dataset)</span><br><span class="line">test_dataset = datasets.MNIST(root=<span class="string">&#x27;./data&#x27;</span>, train=<span class="literal">False</span>, download=<span class="literal">True</span>, transform=transform) <span class="comment"># 加载 MNIST 数据集，测试集</span></span><br><span class="line"><span class="built_in">print</span>(<span class="built_in">type</span>(test_dataset))</span><br><span class="line">size_of_test_dataset = <span class="built_in">len</span>(test_dataset)</span><br><span class="line"><span class="built_in">print</span>(size_of_test_dataset)</span><br><span class="line"></span><br><span class="line"><span class="keyword">import</span> random</span><br><span class="line">rand_number = random.randint(<span class="number">0</span>, size_of_train_dataset-<span class="number">1</span>)</span><br><span class="line">image, label = train_dataset[rand_number] <span class="comment"># 获取一张图像和标签</span></span><br><span class="line"><span class="built_in">print</span>(<span class="built_in">type</span>(image))</span><br><span class="line"><span class="built_in">print</span>(image.shape)</span><br><span class="line">image = image.squeeze(<span class="number">0</span>)  <span class="comment"># 去掉单通道的维度 (1, 28, 28) -&gt; (28, 28)</span></span><br><span class="line"><span class="built_in">print</span>(<span class="built_in">type</span>(image))</span><br><span class="line"><span class="built_in">print</span>(image.shape)</span><br><span class="line"></span><br><span class="line"><span class="keyword">import</span> matplotlib.pyplot <span class="keyword">as</span> plt</span><br><span class="line"><span class="comment"># import os</span></span><br><span class="line"><span class="comment"># os.environ[&quot;KMP_DUPLICATE_LIB_OK&quot;] = &quot;TRUE&quot; # 解决可能的多个 OpenMP 库版本冲突的问题。如果有 OMP 报错，可以试着使用这个解决。</span></span><br><span class="line">plt.imshow(image, cmap=<span class="string">&#x27;gray&#x27;</span>) <span class="comment"># 显示图像</span></span><br><span class="line">plt.title(<span class="string">f&quot;Label: <span class="subst">&#123;label&#125;</span>&quot;</span>)  <span class="comment"># 标签值（理论值）</span></span><br><span class="line">plt.axis(<span class="string">&#x27;off&#x27;</span>)  <span class="comment"># 不显示坐标轴</span></span><br><span class="line">plt.show()</span><br></pre></td></tr></table></figure><p>某次的运行结果为：</p><p><img src="/images/mnist-sample-image.png" alt="用 plt.imshow 显示的一张 MNIST 样本图像，标题为 Label: 5"></p><h2 id="三、用神经网络训练"><a href="#三、用神经网络训练" class="headerlink" title="三、用神经网络训练"></a>三、用神经网络训练</h2><p>以下是使用神经网络进行训练的 Python 代码。这里对模型进行了包装，需要用到软件包 <a href="https://py.guanjihuan.com/">https://py.guanjihuan.com</a>（<code>pip install --upgrade guan</code>）。</p><figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br><span class="line">60</span><br><span class="line">61</span><br><span class="line">62</span><br><span class="line">63</span><br><span class="line">64</span><br><span class="line">65</span><br><span class="line">66</span><br><span class="line">67</span><br><span class="line">68</span><br><span class="line">69</span><br><span class="line">70</span><br><span class="line">71</span><br><span class="line">72</span><br><span class="line">73</span><br><span class="line">74</span><br><span class="line">75</span><br><span class="line">76</span><br><span class="line">77</span><br><span class="line">78</span><br><span class="line">79</span><br><span class="line">80</span><br><span class="line">81</span><br><span class="line">82</span><br><span class="line">83</span><br><span class="line">84</span><br><span class="line">85</span><br><span class="line">86</span><br><span class="line">87</span><br><span class="line">88</span><br><span class="line">89</span><br><span class="line">90</span><br><span class="line">91</span><br><span class="line">92</span><br><span class="line">93</span><br><span class="line">94</span><br><span class="line">95</span><br><span class="line">96</span><br><span class="line">97</span><br><span class="line">98</span><br><span class="line">99</span><br><span class="line">100</span><br></pre></td><td class="code"><pre><span class="line"><span class="string">&quot;&quot;&quot;</span></span><br><span class="line"><span class="string">This code is supported by the website: https://www.guanjihuan.com</span></span><br><span class="line"><span class="string">The newest version of this code is on the web page: https://www.guanjihuan.com/archives/43720</span></span><br><span class="line"><span class="string">&quot;&quot;&quot;</span></span><br><span class="line"></span><br><span class="line"><span class="keyword">import</span> torch</span><br><span class="line"><span class="keyword">import</span> torch.nn <span class="keyword">as</span> nn</span><br><span class="line"><span class="keyword">import</span> torch.optim <span class="keyword">as</span> optim</span><br><span class="line"><span class="keyword">from</span> torch.utils.data <span class="keyword">import</span> DataLoader, TensorDataset</span><br><span class="line"><span class="keyword">from</span> torchvision <span class="keyword">import</span> datasets, transforms</span><br><span class="line"></span><br><span class="line">transform = transforms.Compose([transforms.ToTensor(),transforms.Normalize((<span class="number">0.5</span>,), (<span class="number">0.5</span>,))])  <span class="comment"># 数据转换（将图片转换为 Tensor 并进行归一化处理，均值和标准差为 0.5）</span></span><br><span class="line">train_dataset = datasets.MNIST(root=<span class="string">&#x27;./data&#x27;</span>, train=<span class="literal">True</span>, download=<span class="literal">True</span>, transform=transform) <span class="comment"># 下载训练数据集</span></span><br><span class="line">test_dataset = datasets.MNIST(root=<span class="string">&#x27;./data&#x27;</span>, train=<span class="literal">False</span>, download=<span class="literal">True</span>, transform=transform) <span class="comment"># 下载测试数据集</span></span><br><span class="line"></span><br><span class="line"><span class="comment"># 训练函数</span></span><br><span class="line"><span class="keyword">def</span> <span class="title function_">train</span>(<span class="params">model, train_loader, criterion, optimizer, num_epochs=<span class="number">5</span></span>):</span><br><span class="line">    <span class="keyword">for</span> epoch <span class="keyword">in</span> <span class="built_in">range</span>(num_epochs):</span><br><span class="line">        model.train()</span><br><span class="line">        running_loss = <span class="number">0.0</span></span><br><span class="line">        correct = <span class="number">0</span></span><br><span class="line">        total = <span class="number">0</span></span><br><span class="line">        <span class="keyword">for</span> images, labels <span class="keyword">in</span> train_loader:</span><br><span class="line">            <span class="comment"># print(images.shape)</span></span><br><span class="line">            optimizer.zero_grad()  <span class="comment"># 清除以前的梯度</span></span><br><span class="line">            outputs = model(images) <span class="comment"># 前向传播</span></span><br><span class="line">            loss = criterion(outputs, labels)</span><br><span class="line">            loss.backward() <span class="comment"># 反向传播和优化</span></span><br><span class="line">            optimizer.step()</span><br><span class="line">            running_loss += loss.item()</span><br><span class="line">            _, predicted = torch.<span class="built_in">max</span>(outputs, <span class="number">1</span>) <span class="comment"># 计算准确率</span></span><br><span class="line">            total += labels.size(<span class="number">0</span>)</span><br><span class="line">            correct += (predicted == labels).<span class="built_in">sum</span>().item()</span><br><span class="line">        avg_loss = running_loss / <span class="built_in">len</span>(train_loader)</span><br><span class="line">        accuracy = <span class="number">100</span> * correct / total</span><br><span class="line">        <span class="built_in">print</span>(<span class="string">f&#x27;Epoch [<span class="subst">&#123;epoch+<span class="number">1</span>&#125;</span>/<span class="subst">&#123;num_epochs&#125;</span>], Loss: <span class="subst">&#123;avg_loss:<span class="number">.4</span>f&#125;</span>, Accuracy: <span class="subst">&#123;accuracy:<span class="number">.2</span>f&#125;</span>%&#x27;</span>)</span><br><span class="line"></span><br><span class="line"><span class="comment"># 测试函数</span></span><br><span class="line"><span class="keyword">def</span> <span class="title function_">test</span>(<span class="params">model, test_loader</span>):</span><br><span class="line">    model.<span class="built_in">eval</span>()  <span class="comment"># 设置为评估模式</span></span><br><span class="line">    correct = <span class="number">0</span></span><br><span class="line">    total = <span class="number">0</span></span><br><span class="line">    <span class="keyword">with</span> torch.no_grad():  <span class="comment"># 禁用梯度计算</span></span><br><span class="line">        <span class="keyword">for</span> images, labels <span class="keyword">in</span> test_loader:</span><br><span class="line">            outputs = model(images)</span><br><span class="line">            _, predicted = torch.<span class="built_in">max</span>(outputs, <span class="number">1</span>)</span><br><span class="line">            total += labels.size(<span class="number">0</span>)</span><br><span class="line">            correct += (predicted == labels).<span class="built_in">sum</span>().item()</span><br><span class="line">    accuracy = <span class="number">100</span> * correct / total</span><br><span class="line">    <span class="built_in">print</span>(<span class="string">f&#x27;Test Accuracy: <span class="subst">&#123;accuracy:<span class="number">.2</span>f&#125;</span>%&#x27;</span>)</span><br><span class="line"></span><br><span class="line"><span class="comment"># 训练和测试</span></span><br><span class="line"><span class="keyword">def</span> <span class="title function_">train_and_test</span>(<span class="params">model, train_loader, test_loader</span>):</span><br><span class="line">    criterion = nn.CrossEntropyLoss()  <span class="comment"># 交叉熵损失</span></span><br><span class="line">    optimizer = optim.Adam(model.parameters(), lr=<span class="number">0.001</span>)</span><br><span class="line">    train(model, train_loader, criterion, optimizer, num_epochs=<span class="number">10</span>)</span><br><span class="line">    test(model, test_loader)</span><br><span class="line"></span><br><span class="line"><span class="comment"># 扁平化数据，并重建 DataLoader（用于全连接神经网络输入端的数据处理）</span></span><br><span class="line"><span class="keyword">def</span> <span class="title function_">flatten_data</span>(<span class="params">data_loader</span>):</span><br><span class="line">    images_array = []</span><br><span class="line">    labels_array = []</span><br><span class="line">    <span class="keyword">for</span> images, labels <span class="keyword">in</span> data_loader:</span><br><span class="line">        images = torch.flatten(images, start_dim=<span class="number">1</span>) <span class="comment"># 除去batch维度后，其他维度展平</span></span><br><span class="line">        images_array.append(images)</span><br><span class="line">        labels_array.append(labels)</span><br><span class="line">    images_array = torch.cat(images_array, dim=<span class="number">0</span>)</span><br><span class="line">    labels_array = torch.cat(labels_array, dim=<span class="number">0</span>)</span><br><span class="line">    dataset_new = TensorDataset(images_array, labels_array)</span><br><span class="line">    loader_new = DataLoader(dataset_new, batch_size=<span class="number">64</span>, shuffle=<span class="literal">True</span>)</span><br><span class="line">    <span class="keyword">return</span> loader_new</span><br><span class="line"></span><br><span class="line"><span class="comment"># 数据加载器</span></span><br><span class="line">train_loader = DataLoader(train_dataset, batch_size=<span class="number">64</span>, shuffle=<span class="literal">True</span>)</span><br><span class="line">test_loader = DataLoader(test_dataset, batch_size=<span class="number">64</span>, shuffle=<span class="literal">False</span>)</span><br><span class="line"></span><br><span class="line"><span class="comment"># 扁平化数据</span></span><br><span class="line">train_loader_new = flatten_data(train_loader)</span><br><span class="line">test_loader_new = flatten_data(test_loader)</span><br><span class="line"></span><br><span class="line"><span class="comment"># 安装软件包：pip install --upgrade guan</span></span><br><span class="line"><span class="keyword">import</span> guan</span><br><span class="line"></span><br><span class="line">hidden_size = <span class="number">64</span></span><br><span class="line"></span><br><span class="line"><span class="built_in">print</span>(<span class="string">&#x27;---全连接神经网络模型（包含一个隐藏层）---&#x27;</span>)</span><br><span class="line">model = guan.fully_connected_neural_network_with_one_hidden_layer(input_size=<span class="number">28</span>*<span class="number">28</span>, hidden_size=hidden_size, output_size=<span class="number">10</span>, activation=<span class="string">&#x27;relu&#x27;</span>)</span><br><span class="line">train_and_test(model, train_loader_new, test_loader_new)</span><br><span class="line"></span><br><span class="line"><span class="built_in">print</span>(<span class="string">&#x27;---全连接神经网络模型（包含两个隐藏层）---&#x27;</span>)</span><br><span class="line">model = guan.fully_connected_neural_network_with_two_hidden_layers(input_size=<span class="number">28</span>*<span class="number">28</span>, hidden_size_1=hidden_size, hidden_size_2=hidden_size, output_size=<span class="number">10</span>, activation_1=<span class="string">&#x27;relu&#x27;</span>, activation_2=<span class="string">&#x27;relu&#x27;</span>)</span><br><span class="line">train_and_test(model, train_loader_new, test_loader_new)</span><br><span class="line"></span><br><span class="line"><span class="built_in">print</span>(<span class="string">&#x27;---全连接神经网络模型（包含三个隐藏层）---&#x27;</span>)</span><br><span class="line">model = guan.fully_connected_neural_network_with_three_hidden_layers(input_size=<span class="number">28</span>*<span class="number">28</span>, hidden_size_1=hidden_size, hidden_size_2=hidden_size, hidden_size_3=hidden_size, output_size=<span class="number">10</span>, activation_1=<span class="string">&#x27;relu&#x27;</span>, activation_2=<span class="string">&#x27;relu&#x27;</span>, activation_3=<span class="string">&#x27;relu&#x27;</span>)</span><br><span class="line">train_and_test(model, train_loader_new, test_loader_new)</span><br><span class="line"></span><br><span class="line"><span class="built_in">print</span>(<span class="string">&#x27;---卷积神经网络模型（包含两个卷积层和两个全连接层）---&#x27;</span>)</span><br><span class="line">model = guan.convolutional_neural_network_with_two_convolutional_layers_and_two_fully_connected_layers(in_channels=<span class="number">1</span>, out_channels_1=<span class="number">32</span>, out_channels_2=<span class="number">64</span>, kernel_size_1=<span class="number">3</span>, kernel_size_2=<span class="number">3</span>, stride_1=<span class="number">1</span>, stride_2=<span class="number">1</span>, padding_1=<span class="number">1</span>, padding_2=<span class="number">1</span>, pooling=<span class="number">1</span>, pooling_kernel_size=<span class="number">2</span>, pooling_stride=<span class="number">2</span>, input_size=<span class="number">7</span>*<span class="number">7</span>*<span class="number">64</span>, hidden_size_1=hidden_size, hidden_size_2=hidden_size, output_size=<span class="number">10</span>)</span><br><span class="line">train_and_test(model, train_loader, test_loader)</span><br></pre></td></tr></table></figure><p>运行结果：</p><figure class="highlight text"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br></pre></td><td class="code"><pre><span class="line">---全连接神经网络模型（包含一个隐藏层）---</span><br><span class="line">Epoch [1/10], Loss: 0.4376, Accuracy: 87.51%</span><br><span class="line">Epoch [2/10], Loss: 0.2560, Accuracy: 92.49%</span><br><span class="line">Epoch [3/10], Loss: 0.1936, Accuracy: 94.25%</span><br><span class="line">Epoch [4/10], Loss: 0.1555, Accuracy: 95.38%</span><br><span class="line">Epoch [5/10], Loss: 0.1328, Accuracy: 95.98%</span><br><span class="line">Epoch [6/10], Loss: 0.1193, Accuracy: 96.41%</span><br><span class="line">Epoch [7/10], Loss: 0.1087, Accuracy: 96.68%</span><br><span class="line">Epoch [8/10], Loss: 0.0996, Accuracy: 96.97%</span><br><span class="line">Epoch [9/10], Loss: 0.0945, Accuracy: 97.03%</span><br><span class="line">Epoch [10/10], Loss: 0.0872, Accuracy: 97.30%</span><br><span class="line">Test Accuracy: 96.75%</span><br><span class="line">---全连接神经网络模型（包含两个隐藏层）---</span><br><span class="line">Epoch [1/10], Loss: 0.4167, Accuracy: 87.95%</span><br><span class="line">Epoch [2/10], Loss: 0.2170, Accuracy: 93.50%</span><br><span class="line">Epoch [3/10], Loss: 0.1611, Accuracy: 95.21%</span><br><span class="line">Epoch [4/10], Loss: 0.1338, Accuracy: 95.92%</span><br><span class="line">Epoch [5/10], Loss: 0.1114, Accuracy: 96.56%</span><br><span class="line">Epoch [6/10], Loss: 0.1002, Accuracy: 96.88%</span><br><span class="line">Epoch [7/10], Loss: 0.0903, Accuracy: 97.16%</span><br><span class="line">Epoch [8/10], Loss: 0.0802, Accuracy: 97.48%</span><br><span class="line">Epoch [9/10], Loss: 0.0759, Accuracy: 97.59%</span><br><span class="line">Epoch [10/10], Loss: 0.0699, Accuracy: 97.80%</span><br><span class="line">Test Accuracy: 96.71%</span><br><span class="line">---全连接神经网络模型（包含三个隐藏层）---</span><br><span class="line">Epoch [1/10], Loss: 0.4567, Accuracy: 86.16%</span><br><span class="line">Epoch [2/10], Loss: 0.2217, Accuracy: 93.27%</span><br><span class="line">Epoch [3/10], Loss: 0.1686, Accuracy: 94.78%</span><br><span class="line">Epoch [4/10], Loss: 0.1442, Accuracy: 95.54%</span><br><span class="line">Epoch [5/10], Loss: 0.1276, Accuracy: 96.07%</span><br><span class="line">Epoch [6/10], Loss: 0.1132, Accuracy: 96.55%</span><br><span class="line">Epoch [7/10], Loss: 0.1007, Accuracy: 96.86%</span><br><span class="line">Epoch [8/10], Loss: 0.0944, Accuracy: 96.96%</span><br><span class="line">Epoch [9/10], Loss: 0.0863, Accuracy: 97.27%</span><br><span class="line">Epoch [10/10], Loss: 0.0816, Accuracy: 97.39%</span><br><span class="line">Test Accuracy: 96.74%</span><br><span class="line">---卷积神经网络模型（包含两个卷积层和两个全连接层）---</span><br><span class="line">Epoch [1/10], Loss: 0.1923, Accuracy: 93.92%</span><br><span class="line">Epoch [2/10], Loss: 0.0522, Accuracy: 98.41%</span><br><span class="line">Epoch [3/10], Loss: 0.0349, Accuracy: 98.92%</span><br><span class="line">Epoch [4/10], Loss: 0.0278, Accuracy: 99.11%</span><br><span class="line">Epoch [5/10], Loss: 0.0228, Accuracy: 99.28%</span><br><span class="line">Epoch [6/10], Loss: 0.0177, Accuracy: 99.41%</span><br><span class="line">Epoch [7/10], Loss: 0.0148, Accuracy: 99.51%</span><br><span class="line">Epoch [8/10], Loss: 0.0127, Accuracy: 99.58%</span><br><span class="line">Epoch [9/10], Loss: 0.0118, Accuracy: 99.64%</span><br><span class="line">Epoch [10/10], Loss: 0.0080, Accuracy: 99.75%</span><br><span class="line">Test Accuracy: 99.14%</span><br></pre></td></tr></table></figure><h2 id="四、附：手写数字识别动图"><a href="#四、附：手写数字识别动图" class="headerlink" title="四、附：手写数字识别动图"></a>四、附：手写数字识别动图</h2><p><img src="/images/mnist-handwriting-demo.gif" alt="手写数字识别演示：左侧手写输入，右侧网络输出 0 到 9 的判别结果"></p><hr><p>参考资料：<a href="https://www.guanjihuan.com/archives/43720">https://www.guanjihuan.com/archives/43720</a></p>]]>
    </content>
    <id>https://yohoten.github.io/2024/12/02/MNIST%E6%95%B0%E6%8D%AE%E9%9B%86%E5%92%8C%E6%89%8B%E5%86%99%E6%95%B0%E5%AD%97%E7%9A%84%E6%A8%A1%E5%9E%8B%E8%AE%AD%E7%BB%83/</id>
    <link href="https://yohoten.github.io/2024/12/02/MNIST%E6%95%B0%E6%8D%AE%E9%9B%86%E5%92%8C%E6%89%8B%E5%86%99%E6%95%B0%E5%AD%97%E7%9A%84%E6%A8%A1%E5%9E%8B%E8%AE%AD%E7%BB%83/"/>
    <published>2024-12-02T04:00:00.000Z</published>
    <summary>介绍 MNIST 手写数字数据集的结构与下载方式，并用 PyTorch 分别训练含一个、两个、三个隐藏层的全连接网络和一个两层卷积网络，对比它们在测试集上的准确率。</summary>
    <title>MNIST 数据集和手写数字的模型训练</title>
    <updated>2024-12-02T04:00:00.000Z</updated>
  </entry>
  <entry>
    <author>
      <name>yohoten</name>
    </author>
    <category term="技术教程" scheme="https://yohoten.github.io/categories/%E6%8A%80%E6%9C%AF%E6%95%99%E7%A8%8B/"/>
    <category term="Python" scheme="https://yohoten.github.io/tags/Python/"/>
    <category term="学习笔记" scheme="https://yohoten.github.io/tags/%E5%AD%A6%E4%B9%A0%E7%AC%94%E8%AE%B0/"/>
    <category term="SQL" scheme="https://yohoten.github.io/tags/SQL/"/>
    <category term="数据库" scheme="https://yohoten.github.io/tags/%E6%95%B0%E6%8D%AE%E5%BA%93/"/>
    <category term="SQLite" scheme="https://yohoten.github.io/tags/SQLite/"/>
    <content>
      <![CDATA[<p>通过实操学会 SQL。</p><h2 id="目录"><a href="#目录" class="headerlink" title="目录"></a>目录</h2><ol><li>引入</li><li>SQLite 的安装</li><li>创建一个表<ul><li>查看表</li><li>退出/打开数据库</li></ul></li><li>基本操作<ul><li>查询 / DISTINCT / COUNT / AS / WHERE / AND 和 OR</li></ul></li><li>条件与分组<ul><li>GROUP BY / ORDER BY / LIMIT / LIKE</li></ul></li><li>插入、更新与删除<ul><li>INSERT / UPDATE / DELETE</li></ul></li><li>主键与外键<ul><li>准备 / 主键和外键 / JOIN</li></ul></li><li>建立索引</li><li>事务<ul><li>“事务”的介绍 / 四大特性</li></ul></li><li>在 Python 中使用 SQL</li><li>附：关系型数据库和非关系型数据库</li></ol><h2 id="1-引入"><a href="#1-引入" class="headerlink" title="1. 引入"></a>1. 引入</h2><p>现代的程序，无论是网站、手机app等等，往往都要处理大量的数据，这些数据需要通过 <strong>数据库（Database）</strong> 来管理。为了便于程序与数据库之间的交互，我们引入一门语言，<strong>SQL（Structured Query Language，结构化查询语言）</strong>。使用SQL可以方便地对 <em><strong>关系型数据库（Relational Database）</strong></em> 进行增、查、改、删等操作。（关于什么是关系型数据库可以见文末）</p><h2 id="2-SQLite的安装"><a href="#2-SQLite的安装" class="headerlink" title="2. SQLite的安装"></a>2. SQLite的安装</h2><p>为了学习SQL，我们先来安装一个轻量级的关系型数据库 <strong>SQLite</strong> ，请与SQL区分，SQL是一种查询语言，SQLiete是一种数据库。</p><p>因为我们只是用来学习SQL语言，所以比起Oracle这样的庞然大物，使用轻量级的SQLite是更好的选择。当然，你学会了SQL之后，可以自由选择符合你需求的数据库，关系型数据库都使用统一的SQL语言，无需再次学习。</p><p>安装SQLite非常简单，访问<a href="https://sqlite.org/download.html">SQLite官网的下载页面</a>，下载你的系统对应的带有“Precompiled”前缀的压缩包。以windows x64系统为例，下载这两个文件，并把他们解压到相同的文件夹下面，例如“C:\sqlite”。</p><p><img src="https://codecyrus.com/_astro/SQLite-install-1.a7ekan9j_Z166gXF.webp" alt="windows x64系统请下载这两个文件"></p><p><img src="https://codecyrus.com/_astro/SQLite-install-2.BX5U9Nd__Z1rs0kY.webp" alt="解压后放到同一个文件夹下将是这样的"></p><p>此时双击sqlite3.exe，看到以下界面就成功了，连安装都不需要，只要解压，够轻量级吧。</p><p><img src="https://codecyrus.com/_astro/SQLite-install-3.D7Z6OXj-_Zd7yLX.webp" alt="成功界面"></p><p>但是当你在命令行输入<code>sqlite3</code>命令时会发现，系统找不到这个命令，这是因为没有在系统的环境变量path里加上sqlite3.exe所在的路径（例如C:\sqlite）。假如你不知道环境变量path是什么，可以看看我的<a href="https://codecyrus.com/zh/blog/environment-variable-path/">这篇文章</a>。</p><p>添加了环境变量后，在cmd/powershell/bash或者你喜欢的任何shell里输入<code>sqlite3</code>，你应该能看到和上面双击exe一样的输出。</p><p>在命令行里键入以下命令：</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">sqlite3</span><br></pre></td></tr></table></figure><p>如果你配置好了环境，应该可以看到以下输出。</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line"># SQLite version 3.43.1 2023-09-11 12:01:27</span><br><span class="line"># Enter &quot;.help&quot; for usage hints.</span><br><span class="line"># sqlite&gt;</span><br></pre></td></tr></table></figure><p>看到输入提示符变成“sqlite&gt;”，我们就进入了sqlite的命令行。</p><h2 id="3-创建一个表"><a href="#3-创建一个表" class="headerlink" title="3. 创建一个表"></a>3. 创建一个表</h2><p>使用以下命令来创建一个表，注意所有的SQL语句末尾都要有分号。</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">CREATE TABLE 表名 (&#x27;列名&#x27; 数据类型, ...);</span><br></pre></td></tr></table></figure><p><em>不同的数据库可能对相同的类型有不同的名称、不同的长度、数据范围</em> 以sqlite为例，常用的数据类型有以下几种：</p><table><thead><tr><th align="left">数据类型</th><th align="left">描述</th></tr></thead><tbody><tr><td align="left">NULL</td><td align="left">空值NULL</td></tr><tr><td align="left">INTEGER</td><td align="left">整数，具体位数取决于插入数据的大小</td></tr><tr><td align="left">REAL</td><td align="left">8位IEEE浮点数</td></tr><tr><td align="left">TEXT</td><td align="left">字符串</td></tr><tr><td align="left">BLOB</td><td align="left">二进制大对象（Binary Large Object）</td></tr></tbody></table><p>另外，SQLite还提供了两个修饰符：<code>NOT NULL</code>和<code>UNIQUE</code>，分别表示这个字段不能为空，以及这个字段必须唯一。</p><p>根据我们的需求，创建一个这样的表：</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">CREATE TABLE books (&#x27;title&#x27; TEXT NOT NULL, &#x27;price&#x27; REAL, &#x27;sales&#x27; INTEGER, &#x27;category&#x27; TEXT);</span><br></pre></td></tr></table></figure><p>我们先把一些数据拷贝到books.csv中（csv是Comma-Separated Values，逗号分隔的值的缩写）</p><p><em>数据是ChatGPT生成的，所以不要在意一些不合理的小细节</em></p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br></pre></td><td class="code"><pre><span class="line">The Great Gatsby,50.99,4,Fiction</span><br><span class="line">To Kill a Mockingbird,25.99,3,Fiction</span><br><span class="line">Pride and Prejudice,40.99,2,Fiction</span><br><span class="line">1984,35.99,1,Science Fiction</span><br><span class="line">The Catcher in the Rye,60.99,5,Fiction</span><br><span class="line">Animal Farm,30.5,2,Fiction</span><br><span class="line">The Hobbit,45,4,Fantasy</span><br><span class="line">The Lord of the Rings,55.99,3,Fantasy</span><br><span class="line">Harry Potter and the Sorcerer&#x27;s Stone,20,1,Fantasy</span><br><span class="line">The Chronicles of Narnia,15.98,5,Fantasy</span><br><span class="line">Moby Dick,45.99,3,Fiction</span><br><span class="line">The Odyssey,30.99,2,Classic</span><br><span class="line">To the Lighthouse,40,4,Classic</span><br><span class="line">The Adventures of Tom Sawyer,25.1,2,Classic</span><br><span class="line">War and Peace,50.99,4,Classic</span><br><span class="line">Crime and Punishment,35.99,1,Classic</span><br><span class="line">The Alchemist,55.99,2,Fiction</span><br><span class="line">Fahrenheit 451,20.99,4,Science Fiction</span><br><span class="line">The Picture of Dorian Gray,15.98,5,Classic</span><br><span class="line">Brave New World,60.3,3,Science Fiction</span><br></pre></td></tr></table></figure><p>将sqlite切换为csv模式并导入这个.csv文件。</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">.mode csv</span><br><span class="line">.import books.csv books</span><br></pre></td></tr></table></figure><p><em>所有以逗号开头的命令都不属于SQL，而是SQLite的语法</em></p><h3 id="查看表"><a href="#查看表" class="headerlink" title="查看表"></a>查看表</h3><p>这样可以查看有哪些表，每个表有哪些字段：</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">.schema</span><br></pre></td></tr></table></figure><p>这样查看表的内容：</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">SELECT 列名,... FROM 表名;</span><br></pre></td></tr></table></figure><p>切换到box模式，这样的输出比较好看，然后我们用<code>SELECT</code>命令查看一下导入的数据。</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">.mode box</span><br><span class="line">SELECT * FROM books;</span><br></pre></td></tr></table></figure><p>“*”表示通配符，即选择所有的列，在这里相当于<code>SELECT name, price, sales FROM books;</code></p><p>可以看到输出了刚刚导入的表格</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br></pre></td><td class="code"><pre><span class="line">┌───────────────────────────────────────┬───────┬───────┬─────────────────┐</span><br><span class="line">│                 title                 │ price │ sales │    category     │</span><br><span class="line">├───────────────────────────────────────┼───────┼───────┼─────────────────┤</span><br><span class="line">│ The Great Gatsby                      │ 50.99 │ 4     │ Fiction         │</span><br><span class="line">│ To Kill a Mockingbird                 │ 25.99 │ 3     │ Fiction         │</span><br><span class="line">│ Pride and Prejudice                   │ 40.99 │ 2     │ Fiction         │</span><br><span class="line">│ 1984                                  │ 35.99 │ 1     │ Science Fiction │</span><br><span class="line">│ The Catcher in the Rye                │ 60.99 │ 5     │ Fiction         │</span><br><span class="line">│ Animal Farm                           │ 30.5  │ 2     │ Fiction         │</span><br><span class="line">│ The Hobbit                            │ 45.0  │ 4     │ Fantasy         │</span><br><span class="line">│ The Lord of the Rings                 │ 55.99 │ 3     │ Fantasy         │</span><br><span class="line">│ Harry Potter and the Sorcerer&#x27;s Stone │ 20.0  │ 1     │ Fantasy         │</span><br><span class="line">│ The Chronicles of Narnia              │ 15.98 │ 5     │ Fantasy         │</span><br><span class="line">│ Moby Dick                             │ 45.99 │ 3     │ Fiction         │</span><br><span class="line">│ The Odyssey                           │ 30.99 │ 2     │ Classic         │</span><br><span class="line">│ To the Lighthouse                     │ 40.0  │ 4     │ Classic         │</span><br><span class="line">│ The Adventures of Tom Sawyer          │ 25.1  │ 2     │ Classic         │</span><br><span class="line">│ War and Peace                         │ 50.99 │ 4     │ Classic         │</span><br><span class="line">│ Crime and Punishment                  │ 35.99 │ 1     │ Classic         │</span><br><span class="line">│ The Alchemist                         │ 55.99 │ 2     │ Fiction         │</span><br><span class="line">│ Fahrenheit 451                        │ 20.99 │ 4     │ Science Fiction │</span><br><span class="line">│ The Picture of Dorian Gray            │ 15.98 │ 5     │ Classic         │</span><br><span class="line">│ Brave New World                       │ 60.3  │ 3     │ Science Fiction │</span><br><span class="line">└───────────────────────────────────────┴───────┴───────┴─────────────────┘</span><br></pre></td></tr></table></figure><p>当然，不一定要选择所有的列，如果输入<code>SELECT name FROM books;</code>就只会显示所有的书名。试一试吧！</p><h3 id="退出-打开数据库"><a href="#退出-打开数据库" class="headerlink" title="退出/打开数据库"></a>退出/打开数据库</h3><p>输入<code>.quit</code>退出SQLite。</p><p>你会发现，SQLite为你在目录下自动创建了一个名为“books.db”的文件。</p><p>下一次打开数据库只需要输入<code>sqlite3 books.db</code>即可恢复原来的数据。</p><h2 id="4-基本操作"><a href="#4-基本操作" class="headerlink" title="4. 基本操作"></a>4. 基本操作</h2><p>数据库有四个基本操作CRUD，即Create增、Read查、Update改、Delete删，在SQL中分别对应以下语句。</p><ul><li>增：CREATE, INSERT</li><li>查：SELECT</li><li>改：UPDATE</li><li>删：DELETE, DROP</li></ul><h3 id="查询"><a href="#查询" class="headerlink" title="查询"></a>查询</h3><p>我们已经学会了使用<code>SELECT</code>来查询数据，现在我们来学习一些常用的函数。</p><p>SQL中自带了很多函数，常用的有这几种：</p><table><thead><tr><th align="left">函数</th><th align="left">描述</th></tr></thead><tbody><tr><td align="left">AVG</td><td align="left">求平均数</td></tr><tr><td align="left">COUNT</td><td align="left">计数</td></tr><tr><td align="left">DISTINCT</td><td align="left">去重</td></tr><tr><td align="left">LOWER</td><td align="left">小写</td></tr><tr><td align="left">UPPER</td><td align="left">大写</td></tr><tr><td align="left">MAX</td><td align="left">取最大值</td></tr><tr><td align="left">MIN</td><td align="left">取最小值</td></tr></tbody></table><h3 id="DISTINCT"><a href="#DISTINCT" class="headerlink" title="DISTINCT"></a>DISTINCT</h3><p>假如我们想要查看所有的分类，可以这样子写:</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">SELECT DISTINCT(category) FROM books;</span><br></pre></td></tr></table></figure><p>输出:</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line">┌─────────────────┐</span><br><span class="line">│    category     │</span><br><span class="line">├─────────────────┤</span><br><span class="line">│ Fiction         │</span><br><span class="line">│ Science Fiction │</span><br><span class="line">│ Fantasy         │</span><br><span class="line">│ Classic         │</span><br><span class="line">└─────────────────┘</span><br></pre></td></tr></table></figure><h3 id="COUNT"><a href="#COUNT" class="headerlink" title="COUNT"></a>COUNT</h3><p>查看有多少种分类：</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">SELECT COUNT(DISTINCT(category)) FROM books; </span><br></pre></td></tr></table></figure><p>输出是这样的</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line">┌───────────────────────────┐</span><br><span class="line">│ COUNT(DISTINCT(category)) │</span><br><span class="line">├───────────────────────────┤</span><br><span class="line">│ 4                         │</span><br><span class="line">└───────────────────────────┘</span><br></pre></td></tr></table></figure><h3 id="AS"><a href="#AS" class="headerlink" title="AS"></a>AS</h3><p>这个“COUNT(DISTINCT(category))”有点太长了，我们可以给它起个别名，比如说叫“n”。</p><p>SQL中有一个关键字<code>AS</code>可以实现这个功能。</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">SELECT COUNT(DISTINCT(category)) AS n FROM books; </span><br></pre></td></tr></table></figure><p>输出：</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line">┌───┐</span><br><span class="line">│ n │</span><br><span class="line">├───┤</span><br><span class="line">│ 4 │</span><br><span class="line">└───┘</span><br></pre></td></tr></table></figure><h3 id="WHERE"><a href="#WHERE" class="headerlink" title="WHERE"></a>WHERE</h3><p><code>WHERE</code>可以用来指定选择的条件，如</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">SELECT COUNT(*) FROM books WHERE category = &#x27;Fiction&#x27;;</span><br></pre></td></tr></table></figure><p>结果显示一共有7本小说</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line">┌──────────┐</span><br><span class="line">│ COUNT(*) │</span><br><span class="line">├──────────┤</span><br><span class="line">│ 7        │</span><br><span class="line">└──────────┘</span><br></pre></td></tr></table></figure><h3 id="AND-和-OR"><a href="#AND-和-OR" class="headerlink" title="AND 和 OR"></a>AND 和 OR</h3><p>也可以使用<code>AND</code>或者<code>OR</code>来连接多个条件，比如我想知道有哪些经典小说的价格在30或以上。</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">SELECT * FROM books WHERE category = &#x27;Classic&#x27; AND price &gt;= 30;</span><br></pre></td></tr></table></figure><p>非常简单对吧？</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line">┌──────────────────────┬───────┬───────┬──────────┐</span><br><span class="line">│        title         │ price │ sales │ category │</span><br><span class="line">├──────────────────────┼───────┼───────┼──────────┤</span><br><span class="line">│ The Odyssey          │ 30.99 │ 2     │ Classic  │</span><br><span class="line">│ To the Lighthouse    │ 40.0  │ 4     │ Classic  │</span><br><span class="line">│ War and Peace        │ 50.99 │ 4     │ Classic  │</span><br><span class="line">│ Crime and Punishment │ 35.99 │ 1     │ Classic  │</span><br><span class="line">└──────────────────────┴───────┴───────┴──────────┘</span><br></pre></td></tr></table></figure><p>也可以自己试试把<code>AND</code>换成<code>OR</code>。</p><p>容易注意到，上面写的SQL语句的关键字都是使用全大写的，其实小写的命令也能被识别，大写的风格是一种传统的风格。</p><h2 id="5-条件与分组"><a href="#5-条件与分组" class="headerlink" title="5. 条件与分组"></a>5. 条件与分组</h2><p>上一节里我们使用了<code>WHERE</code>关键字进行了条件的筛选，实际上这样的关键字还有很多，以下是常用的一些。</p><h3 id="GROUP-BY"><a href="#GROUP-BY" class="headerlink" title="GROUP BY"></a>GROUP BY</h3><p>将数据按某个字段分组</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">SELECT category, COUNT(*) FROM books GROUP BY category;</span><br></pre></td></tr></table></figure><p>输出：</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line">┌─────────────────┬──────────┐</span><br><span class="line">│    category     │ COUNT(*) │</span><br><span class="line">├─────────────────┼──────────┤</span><br><span class="line">│ Classic         │ 6        │</span><br><span class="line">│ Fantasy         │ 4        │</span><br><span class="line">│ Fiction         │ 7        │</span><br><span class="line">│ Science Fiction │ 3        │</span><br><span class="line">└─────────────────┴──────────┘</span><br></pre></td></tr></table></figure><h3 id="ORDER-BY"><a href="#ORDER-BY" class="headerlink" title="ORDER BY"></a>ORDER BY</h3><p>按某个字段排序，例如按每种分类的个数排序。</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">SELECT category, COUNT(*) FROM books GROUP BY category ORDER BY COUNT(*);</span><br></pre></td></tr></table></figure><p>输出：</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line">┌─────────────────┬──────────┐</span><br><span class="line">│    category     │ COUNT(*) │</span><br><span class="line">├─────────────────┼──────────┤</span><br><span class="line">│ Science Fiction │ 3        │</span><br><span class="line">│ Fantasy         │ 4        │</span><br><span class="line">│ Classic         │ 6        │</span><br><span class="line">│ Fiction         │ 7        │</span><br><span class="line">└─────────────────┴──────────┘</span><br></pre></td></tr></table></figure><p>默认是升序排列的，如果我们想要降序排列，需要在后面加上<code>DESC</code>（Descending，降序）。默认情况和后面加上<code>ASC</code>是一样的（Ascending，升序）</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">SELECT category, COUNT(*) FROM books GROUP BY category ORDER BY COUNT(*) DESC;</span><br></pre></td></tr></table></figure><p>输出</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line">┌─────────────────┬──────────┐</span><br><span class="line">│    category     │ COUNT(*) │</span><br><span class="line">├─────────────────┼──────────┤</span><br><span class="line">│ Fiction         │ 7        │</span><br><span class="line">│ Classic         │ 6        │</span><br><span class="line">│ Fantasy         │ 4        │</span><br><span class="line">│ Science Fiction │ 3        │</span><br><span class="line">└─────────────────┴──────────┘</span><br></pre></td></tr></table></figure><h3 id="LIMIT"><a href="#LIMIT" class="headerlink" title="LIMIT"></a>LIMIT</h3><p><code>LIMIT</code>可以限制最多显示的数据条数，让我们看看最贵的5本书：</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">SELECT title, price FROM books ORDER BY price DESC LIMIT 5;</span><br></pre></td></tr></table></figure><p>输出：</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><span class="line">┌────────────────────────┬───────┐</span><br><span class="line">│         title          │ price │</span><br><span class="line">├────────────────────────┼───────┤</span><br><span class="line">│ The Catcher in the Rye │ 60.99 │</span><br><span class="line">│ Brave New World        │ 60.3  │</span><br><span class="line">│ The Lord of the Rings  │ 55.99 │</span><br><span class="line">│ The Alchemist          │ 55.99 │</span><br><span class="line">│ The Great Gatsby       │ 50.99 │</span><br><span class="line">└────────────────────────┴───────┘</span><br></pre></td></tr></table></figure><h3 id="LIKE"><a href="#LIKE" class="headerlink" title="LIKE"></a>LIKE</h3><p><code>LIKE</code>字句允许你使用通配符进行模糊匹配。</p><ol><li>“%” 表示0或任意个字符（包括数字）</li><li>“_” 表示1个字符（包括数字）</li></ol><p>我想找出所有以“The”开头的书，可以这样：</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">SELECT title FROM books WHERE title LIKE &#x27;The%&#x27;; </span><br></pre></td></tr></table></figure><p>输出：</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br></pre></td><td class="code"><pre><span class="line">┌──────────────────────────────┐</span><br><span class="line">│            title             │</span><br><span class="line">├──────────────────────────────┤</span><br><span class="line">│ The Great Gatsby             │</span><br><span class="line">│ The Catcher in the Rye       │</span><br><span class="line">│ The Hobbit                   │</span><br><span class="line">│ The Lord of the Rings        │</span><br><span class="line">│ The Chronicles of Narnia     │</span><br><span class="line">│ The Odyssey                  │</span><br><span class="line">│ The Adventures of Tom Sawyer │</span><br><span class="line">│ The Alchemist                │</span><br><span class="line">│ The Picture of Dorian Gray   │</span><br><span class="line">└──────────────────────────────┘</span><br></pre></td></tr></table></figure><h2 id="6-插入、更新与删除"><a href="#6-插入、更新与删除" class="headerlink" title="6. 插入、更新与删除"></a>6. 插入、更新与删除</h2><h3 id="INSERT"><a href="#INSERT" class="headerlink" title="INSERT"></a>INSERT</h3><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">INSERT INTO 表名 (列名, ...) VALUES (值, ...);</span><br></pre></td></tr></table></figure><p>这样可以插入新的数据，例如插入一本新书“A Wrinkle in Time”。</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">INSERT INTO books (title, price, sales, category) VALUES (&#x27;A Wrinkle in Time&#x27;,23.99,1,&#x27;Fantasy&#x27;);</span><br></pre></td></tr></table></figure><p>键入<code>SELECT * FROM books;</code>可以看到已经成功地插入了新的书。</p><h3 id="UPDATE"><a href="#UPDATE" class="headerlink" title="UPDATE"></a>UPDATE</h3><p>用<code>UPDATE</code>将所有“Science Fiction”分类改名为“Sci-fi”可以这样写：</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">UPDATE books SET category = &#x27;Sci-fi&#x27; WHERE category = &#x27;Science Fiction&#x27;;</span><br></pre></td></tr></table></figure><p>这是我们接触到的第一个“破坏性”的命令，所以键入命令时请double check一下，确认这就是你想要的，<strong>避免造成严重的后果</strong> 。实际的项目中一般有权限控制、备份、生产环境隔离等方法来避免这种悲剧的发生，但是养成这个好习惯也很重要。</p><h3 id="DELETE"><a href="#DELETE" class="headerlink" title="DELETE"></a>DELETE</h3><p>这个命令比<code>UPDATE</code>更加危险，因此请确保你所写下的命令，就是你想要的，不要漏掉任何条件语句如<code>WHERE</code>。</p><p>下面的命令可以删除所有销量不高于2的书：</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">DELETE FROM books WHERE sales &lt;= 2;</span><br></pre></td></tr></table></figure><p>现在的数据是这样的：</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br></pre></td><td class="code"><pre><span class="line">┌────────────────────────────┬───────┬───────┬──────────┐</span><br><span class="line">│           title            │ price │ sales │ category │</span><br><span class="line">├────────────────────────────┼───────┼───────┼──────────┤</span><br><span class="line">│ The Great Gatsby           │ 50.99 │ 4     │ Fiction  │</span><br><span class="line">│ To Kill a Mockingbird      │ 25.99 │ 3     │ Fiction  │</span><br><span class="line">│ The Catcher in the Rye     │ 60.99 │ 5     │ Fiction  │</span><br><span class="line">│ The Hobbit                 │ 45.0  │ 4     │ Fantasy  │</span><br><span class="line">│ The Lord of the Rings      │ 55.99 │ 3     │ Fantasy  │</span><br><span class="line">│ The Chronicles of Narnia   │ 15.98 │ 5     │ Fantasy  │</span><br><span class="line">│ Moby Dick                  │ 45.99 │ 3     │ Fiction  │</span><br><span class="line">│ To the Lighthouse          │ 40.0  │ 4     │ Classic  │</span><br><span class="line">│ War and Peace              │ 50.99 │ 4     │ Classic  │</span><br><span class="line">│ Fahrenheit 451             │ 20.99 │ 4     │ Sci-fi   │</span><br><span class="line">│ The Picture of Dorian Gray │ 15.98 │ 5     │ Classic  │</span><br><span class="line">│ Brave New World            │ 60.3  │ 3     │ Sci-fi   │</span><br><span class="line">└────────────────────────────┴───────┴───────┴──────────┘</span><br></pre></td></tr></table></figure><h2 id="7-主键与外键"><a href="#7-主键与外键" class="headerlink" title="7. 主键与外键"></a>7. 主键与外键</h2><h3 id="准备"><a href="#准备" class="headerlink" title="准备"></a>准备</h3><p>为了介绍 <strong>主键（Primary Keys）</strong> 与外键 <strong>（Foreign Keys）</strong> ，我们再新建一个数据库。</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line">sqlite3 movies.db</span><br><span class="line"># SQLite version 3.43.1 2023-09-11 12:01:27</span><br><span class="line"># Enter &quot;.help&quot; for usage hints.</span><br><span class="line"># sqlite&gt;</span><br></pre></td></tr></table></figure><p>假如movies.db不存在，SQLite会自动创建它。</p><p>我用ChatGPT又生成了一些数据（不确定有没有错误）。</p><p>movies.csv:（两列分别代表id和title）</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line">1,The Dark Knight</span><br><span class="line">2,Inception</span><br><span class="line">3,The Shawshank Redemption</span><br><span class="line">4,The Godfather</span><br><span class="line">5,The Godfather: Part II</span><br><span class="line"></span><br><span class="line">......</span><br></pre></td></tr></table></figure><p>stars.csv:（两列分别代表id和name）</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line">1,Christian Bale</span><br><span class="line">2,Heath Ledger</span><br><span class="line">3,Aaron Eckhart</span><br><span class="line">4,Michael Caine</span><br><span class="line">5,Gary Oldman</span><br><span class="line"></span><br><span class="line">......</span><br></pre></td></tr></table></figure><p>movies_stars.csv:（两列分别代表movie_id和star_id）</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line">1,1</span><br><span class="line">1,2</span><br><span class="line">1,3</span><br><span class="line">1,4</span><br><span class="line">1,5</span><br><span class="line"></span><br><span class="line">......</span><br></pre></td></tr></table></figure><p><a href="https://codecyrus.com/files/SQLdata.zip">下载这三个文件</a></p><p>创建三个相应的表格，并载入数据：</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br></pre></td><td class="code"><pre><span class="line">CREATE TABLE movies (</span><br><span class="line">  &#x27;id&#x27; INTEGER PRIMARY KEY,</span><br><span class="line">  &#x27;title&#x27; TEXT</span><br><span class="line">);</span><br><span class="line">CREATE TABLE stars (</span><br><span class="line">  &#x27;id&#x27; INTEGER PRIMARY KEY,</span><br><span class="line">  &#x27;name&#x27; TEXT</span><br><span class="line">);</span><br><span class="line">CREATE TABLE movies_stars (</span><br><span class="line">  &#x27;movie_id&#x27; INTEGER,</span><br><span class="line">  &#x27;star_id&#x27; INTEGER,</span><br><span class="line">  FOREIGN KEY(&#x27;movie_id&#x27;) REFERENCES movies(&#x27;id&#x27;),</span><br><span class="line">  FOREIGN KEY(&#x27;star_id&#x27;) REFERENCES stars(&#x27;id&#x27;)</span><br><span class="line">);</span><br><span class="line">.mode csv</span><br><span class="line">.import movies.csv movies</span><br><span class="line">.import stars.csv stars</span><br><span class="line">.import movies_stars.csv movies_stars</span><br><span class="line">.mode box</span><br></pre></td></tr></table></figure><h3 id="主键和外键"><a href="#主键和外键" class="headerlink" title="主键和外键"></a>主键和外键</h3><p>我们这次的数据里，每个电影都对应了多名主演，而每个演员也可能出演多部电影，所以我们使用一张movies_stars表格来记录电影和主演之间的关系，而不是直接放在同一个表格的另一列中。</p><p>主键是一种约束，一个表只能有一列主键，每个主键唯一，且不可以为空。<code>&#39;id&#39; INTEGER PRIMARY KEY</code>会将id设置为主键。</p><p>外键可以连接其他的表的主键，设置外键的约束可以防止插入外部表中不存在的主键。按以下格式：</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line">CREATE TABLE movies_stars (</span><br><span class="line">  ......,</span><br><span class="line">  FOREIGN KEY(外键) REFERENCES 外部表(外部表的主键)</span><br><span class="line">);</span><br></pre></td></tr></table></figure><h3 id="JOIN"><a href="#JOIN" class="headerlink" title="JOIN"></a>JOIN</h3><p>查看movies_stars，发现它实际上只有两列数字id，如果我们想看看这些数字id代表那哪电影或哪名演员，就要使用<code>JOIN</code>合并两张表。</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">SELECT 列名, ... FROM 表1 JOIN 表2 ON 条件;</span><br></pre></td></tr></table></figure><p>按照上面的格式，有：</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">SELECT * FROM movies_stars JOIN stars ON movies_stars.star_id = stars.id LIMIT 5;</span><br></pre></td></tr></table></figure><p>输出如下结果：</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><span class="line">┌──────────┬─────────┬────┬────────────────┐</span><br><span class="line">│ movie_id │ star_id │ id │      name      │</span><br><span class="line">├──────────┼─────────┼────┼────────────────┤</span><br><span class="line">│ 1        │ 1       │ 1  │ Christian Bale │</span><br><span class="line">│ 1        │ 2       │ 2  │ Heath Ledger   │</span><br><span class="line">│ 1        │ 3       │ 3  │ Aaron Eckhart  │</span><br><span class="line">│ 1        │ 4       │ 4  │ Michael Caine  │</span><br><span class="line">│ 1        │ 5       │ 5  │ Gary Oldman    │</span><br><span class="line">└──────────┴─────────┴────┴────────────────┘</span><br></pre></td></tr></table></figure><p>比如我们想知道Morgan Freeman和Tom Hardy主演了哪些电影，我们可以使用两次<code>JOIN</code>:</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line">SELECT title, name FROM movies_stars</span><br><span class="line">JOIN stars ON movies_stars.star_id = stars.id</span><br><span class="line">JOIN movies ON movies_stars.movie_id = movies.id</span><br><span class="line">WHERE name = &#x27;Morgan Freeman&#x27;</span><br><span class="line">OR name = &#x27;Tom Hardy&#x27;</span><br><span class="line">ORDER BY name;</span><br></pre></td></tr></table></figure><p>得到以下结果：</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><span class="line">┌──────────────────────────┬────────────────┐</span><br><span class="line">│          title           │      name      │</span><br><span class="line">├──────────────────────────┼────────────────┤</span><br><span class="line">│ The Shawshank Redemption │ Morgan Freeman │</span><br><span class="line">│ Se7en                    │ Morgan Freeman │</span><br><span class="line">│ The Dark Knight Rises    │ Morgan Freeman │</span><br><span class="line">│ Inception                │ Tom Hardy      │</span><br><span class="line">│ The Dark Knight Rises    │ Tom Hardy      │</span><br><span class="line">└──────────────────────────┴────────────────┘</span><br></pre></td></tr></table></figure><p>还有一种写法，效果是一样的：</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line">SELECT title, name FROM movies_stars, stars, movies</span><br><span class="line">WHERE movies_stars.star_id = stars.id</span><br><span class="line">AND movies_stars.movie_id = movies.id</span><br><span class="line">AND name IN (&#x27;Morgan Freeman&#x27;, &#x27;Tom Hardy&#x27;)</span><br><span class="line">ORDER BY name;</span><br></pre></td></tr></table></figure><p>请注意<code>FROM</code>后面写出了多个表格。</p><h2 id="8-建立索引"><a href="#8-建立索引" class="headerlink" title="8. 建立索引"></a>8. 建立索引</h2><p>当数据量小的时候，查找操作几乎是瞬间完成的，但是当数据库的规模很大的时候，对数据库的搜索操作会变得很慢，因为默认情况下数据库是使用低效的线性查找，这时候就需要我们建立 <strong>索引</strong> 。</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">CREATE INDEX 索引名 ON 表名 (列名, ...);</span><br><span class="line">CREATE INDEX title_index ON movies (title);</span><br></pre></td></tr></table></figure><p>当执行这个语句之后，数据库会为这几列创建一种名为 <strong>B树（B-Tree）</strong> 的数据结构，将搜索加速到 O(log⁡n)<em>O</em>(log<em>n</em>) ，但同时使用的储存空间也会更多，所以推荐只在需要经常查找的字段上建立索引。</p><p><em>另外，<code>.timer on</code>可以打开计时器比较执行速度，但是在我使用的这么小的数据里是看不出效果的。</em></p><h2 id="9-事务"><a href="#9-事务" class="headerlink" title="9. 事务"></a>9. 事务</h2><h3 id="“事务”的介绍"><a href="#“事务”的介绍" class="headerlink" title="“事务”的介绍"></a>“事务”的介绍</h3><p><strong>事务（Transaction）</strong> 可以将一系列数据库操作绑定在一起执行，要么一起成功，要么都不执行。</p><p>设想假如我去银行取钱，我的账户余额扣除了1000元，但是这个时候ATM机坏了，什么都没有吐出来，此时我的钱就凭空消失了。如果把“从账户里扣除1000元”和“ATM吐出1000元”绑定成一个 <strong>事务</strong> ，就可以避免这种情况的发生，如果ATM吐钱失败了，我的账户也不会扣钱。</p><p>还有一种情况，是David教授在CS50的课上举的例子，假如宿舍里有一个小冰箱，一天我打开冰箱发现牛奶喝完了，于是我去买牛奶，这时我的室友回来了，他也看到冰箱里没牛奶了，于是也去买。结果两个人都买了牛奶，冰箱放不下了。</p><p>买牛奶这个问题的关键在于，我的室友并不知道冰箱有没有牛奶的状态正在从“没有”更新到“有”，他只能看到冰箱里“没有”牛奶，而不知道我已经去买牛奶了。具体到数据库上，我们需要一种方法，把数据库“锁住”，让其他读取操作等待，直到数据更新完毕后再执行。</p><p>“取钱”和“买牛奶”这两个问题都可以通过SQL中的“事务”来解决，你只需要把一系列的命令用<code>BEGIN TRANSACTION</code>和<code>COMMIT</code>包裹起来就可以了。</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line">BEGIN TRANSACTION</span><br><span class="line">......</span><br><span class="line">COMMIT</span><br></pre></td></tr></table></figure><h3 id="四大特性"><a href="#四大特性" class="headerlink" title="四大特性"></a>四大特性</h3><p>事务具有ACID四大特性：</p><ol><li>原子性（Atomicity）：事务中的操作不可再分，要么全部成功，要么全部取消。</li><li>一致性（Consistency）：事务开始前与结束后的数据都是合法的，例如甲给乙转账，转账前后他们俩的钱加起来应该不变。</li><li>隔离性（Isolation）：事务的运行互不干扰。</li><li>持久性（Durability）：事务完成后，它对数据库的修改就永久提交了，不再回滚。</li></ol><h2 id="10-在Python中使用SQL"><a href="#10-在Python中使用SQL" class="headerlink" title="10. 在Python中使用SQL"></a>10. 在Python中使用SQL</h2><p>SQL很少被单独使用，通常SQL会与其他的一些语言结合使用，比如Python、Java、 C#等等。这篇文章里，我们关注如何在Python中使用SQL操作数据库。</p><p>Python中内置了SQLite3，你只需要执行</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">import sqlite3</span><br></pre></td></tr></table></figure><p>就可以开始使用了，不需要安装任何第三方库。</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br></pre></td><td class="code"><pre><span class="line">db = sqlite3.connect(&quot;database.db&quot;) </span><br><span class="line"># 连接到一个现有的数据库，如果没有这个文件就创建它</span><br><span class="line"></span><br><span class="line">cur = db.cursor() </span><br><span class="line"># 创建一个cursor，才能执行SQL语句</span><br><span class="line"></span><br><span class="line"># cur.execute(你想执行的SQL语句)</span><br><span class="line">cur.execute(&quot;CREATE TABLE books (&#x27;title&#x27; TEXT NOT NULL, &#x27;price&#x27; REAL, &#x27;sales&#x27; INTEGER, &#x27;category&#x27; TEXT);&quot;)  </span><br><span class="line"># 创建一个表</span><br><span class="line"></span><br><span class="line">res = cur.execute(&quot;SELECT name FROM sqlite_master&quot;)</span><br><span class="line"># SQLite内置了一个名叫“sqlite_master”的表，储存了所有已创建的表</span><br><span class="line"></span><br><span class="line">print(res.fetchone())</span><br><span class="line"># 输出(&#x27;books&#x27;,)</span><br><span class="line"></span><br><span class="line">cur.execute(&quot;&quot;&quot;</span><br><span class="line">    INSERT INTO books VALUES</span><br><span class="line">        (&#x27;The Great Gatsby&#x27;, 50.99, 4, &#x27;Fiction&#x27;),</span><br><span class="line">        (&#x27;The Lord of the Rings&#x27;, 55.99, 3, &#x27;Fantasy&#x27;)</span><br><span class="line">&quot;&quot;&quot;)</span><br><span class="line">db.commit()</span><br><span class="line"># 插入两名学生</span><br><span class="line"># 会自动地创建事务，无需手动加上“BEGIN TRANSACTION”</span><br><span class="line"># 需要手动db.commit()</span><br><span class="line"></span><br><span class="line"></span><br><span class="line">category = input(&quot;category:&quot;)</span><br><span class="line">res = cur.execute(&quot;SELECT title FROM books WHERE category = ?&quot;, (category, ))</span><br><span class="line"># 注意传入的第二个参数是一个元组，不能直接传category</span><br><span class="line"># 请使用“?”占位符，不要使用 f&quot;SELECT title FROM books WHERE category = &#123;category&#125;&quot;或者其他任何方式</span><br><span class="line"># 否则可能会受到SQL注入攻击（SQL injection attacks）</span><br><span class="line"># “?”占位符会自动处理传入的字符串，防止SQL注入</span><br><span class="line"></span><br><span class="line"></span><br><span class="line"># cur.executemany可以批量执行SQL语句，推荐使用executemany而不是for循环嵌套execute</span><br><span class="line">cur.executemany(&quot;INSERT INTO books VALUES(?, ?, ?, ?);&quot;, data)</span><br><span class="line">db.commit()</span><br><span class="line"></span><br><span class="line">res = cur.execute(&#x27;SELECT title, price FROM books&#x27;)</span><br><span class="line">for row in res:</span><br><span class="line">    print(row)</span><br><span class="line">&#x27;&#x27;&#x27;</span><br><span class="line">输出</span><br><span class="line">(&#x27;The Great Gatsby&#x27;, 50.99)</span><br><span class="line">(&#x27;The Lord of the Rings&#x27;, 55.99)</span><br><span class="line">(&#x27;To Kill a Mockingbird&#x27;, 25.99)</span><br><span class="line">(&#x27;Pride and Prejudice&#x27;, 40.99)</span><br><span class="line">(&#x27;To Kill a Mockingbird&#x27;, 25.99)</span><br><span class="line">(&#x27;Pride and Prejudice&#x27;, 40.99)</span><br><span class="line">(&#x27;1984&#x27;, 35.99)</span><br><span class="line">(&#x27;The Catcher in the Rye&#x27;, 60.99)</span><br><span class="line">(&#x27;Animal Farm&#x27;, 30.5)</span><br><span class="line">(&#x27;The Hobbit&#x27;, 45.0)</span><br><span class="line">&#x27;&#x27;&#x27;</span><br><span class="line"></span><br><span class="line">db.close()</span><br><span class="line"># 关闭数据库连接，现在可以连接别的数据库</span><br></pre></td></tr></table></figure><h2 id="附：关系型数据库和非关系型数据库"><a href="#附：关系型数据库和非关系型数据库" class="headerlink" title="附：关系型数据库和非关系型数据库"></a>附：关系型数据库和非关系型数据库</h2><p>我们上面讨论的 SQL 语言主要适用于关系型数据库，那么什么是关系型数据库和非关系性数据库呢？</p><p>关系型数据库（Relational Database）与非关系型数据库（NoSQL Database）有什么区别呢？请看下面的表格。</p><table><thead><tr><th align="left"></th><th align="left">关系型数据库</th><th align="left">非关系型数据库</th></tr></thead><tbody><tr><td align="left">数据结构</td><td align="left">数据库表</td><td align="left">不固定，如键值对（Key-Value）等</td></tr><tr><td align="left">可扩展性</td><td align="left">横向扩展较为困难，需要增加外部关联数据表</td><td align="left">具有高度可扩展性</td></tr><tr><td align="left">查询语言</td><td align="left">SQL</td><td align="left">通常具有自己的查询语言，没有SQL那样标准化</td></tr><tr><td align="left"><em>ACID</em></td><td align="left">支持恢复、回滚、并发控制等</td><td align="left">难以保证数据的完整性和安全性</td></tr></tbody></table><p><em>ACID: 原子性（Atomicity）、一致性（Consistency）、隔离性（Isolation）、持久性（Durability）</em></p><p>简单来说，关系型数据库就像Excel表格，非关系型数据库就像.json文件（当使用键值对数据结构时）。</p><p>关系型数据库更成熟稳定，非关系型数据库更灵活易扩展。</p>]]>
    </content>
    <id>https://yohoten.github.io/2024/11/10/SQL%E5%AD%A6%E4%B9%A0%E7%AC%94%E8%AE%B0-%E4%BB%8E%E5%85%A5%E9%97%A8%E5%88%B0%E5%AE%9E%E6%93%8D/</id>
    <link href="https://yohoten.github.io/2024/11/10/SQL%E5%AD%A6%E4%B9%A0%E7%AC%94%E8%AE%B0-%E4%BB%8E%E5%85%A5%E9%97%A8%E5%88%B0%E5%AE%9E%E6%93%8D/"/>
    <published>2024-11-10T04:00:00.000Z</published>
    <summary>通过实操学会 SQL：从 SQLite 安装、建表、增删改查，到主键外键、JOIN、索引、事务与 Python 中使用 SQL 的完整学习笔记。</summary>
    <title>SQL 学习笔记：从入门到实操</title>
    <updated>2024-11-10T04:00:00.000Z</updated>
  </entry>
  <entry>
    <author>
      <name>yohoten</name>
    </author>
    <category term="学习笔记" scheme="https://yohoten.github.io/categories/%E5%AD%A6%E4%B9%A0%E7%AC%94%E8%AE%B0/"/>
    <category term="学习笔记" scheme="https://yohoten.github.io/tags/%E5%AD%A6%E4%B9%A0%E7%AC%94%E8%AE%B0/"/>
    <category term="数学" scheme="https://yohoten.github.io/tags/%E6%95%B0%E5%AD%A6/"/>
    <category term="线性代数" scheme="https://yohoten.github.io/tags/%E7%BA%BF%E6%80%A7%E4%BB%A3%E6%95%B0/"/>
    <category term="抽象代数" scheme="https://yohoten.github.io/tags/%E6%8A%BD%E8%B1%A1%E4%BB%A3%E6%95%B0/"/>
    <category term="群论" scheme="https://yohoten.github.io/tags/%E7%BE%A4%E8%AE%BA/"/>
    <content>
      <![CDATA[<h2 id="目录"><a href="#目录" class="headerlink" title="目录"></a>目录</h2><ol><li>线性空间到底是什么？</li><li>群、环、域、模<ul><li>群<ul><li>比群更简单的结构</li></ul></li><li>环<ul><li>除环</li><li>域</li></ul></li></ul></li><li>模</li><li>线性空间的定义</li><li>为什么线性空间要定义在域上？</li><li>证明线性空间一定有基<ul><li>Hamel 基<ul><li>存在性证明</li></ul></li></ul></li><li>模不一定有基</li><li>基在线性空间中的作用</li><li>参考链接</li></ol><hr><h2 id="线性空间到底是什么？"><a href="#线性空间到底是什么？" class="headerlink" title="线性空间到底是什么？"></a>线性空间到底是什么？</h2><p><img src="https://codecyrus.com/_astro/linear-space._-qCp9EG_ZgOtel.svg" alt="线性空间"></p><p>我们在学习线性代数的时候，通常见到的线性空间是由以下八个条件共同定义的：</p><p>设 $K$ 是一个数域，$V$ 是一个非空集合，同时还定义了：</p><ul><li>向量加法：$+:V\times V\to V$</li><li>标量乘法：$\cdot:K\times V\to V$（$\cdot$ 也可省略）</li></ul><p>且两种运算满足以下八条，则称 $V$ 是定义在 $K$ 上的<strong>线性空间</strong>，又称向量空间。</p><ol><li>向量加法单位元存在：存在 $\theta\in V$ 使得 $\alpha+\theta=\alpha,\ \alpha\in V$，称 $\theta$ 为 $V$ 的零元素，即向量加法的单位元。</li><li>向量加法逆元存在：$\forall\alpha\in V,\ \exists\beta\in V$ 使得 $\alpha+\beta=\theta$，称 $\beta$ 为 $V$ 的负元素，即向量加法的逆元。</li><li>向量加法结合律：$(\alpha+\beta)+\gamma=\alpha+(\beta+\gamma),\ \alpha,\beta,\gamma\in V$</li><li>向量加法交换律：$\alpha+\beta=\beta+\alpha,\ \alpha,\beta\in V$</li><li>标量乘法单位元存在：存在 $1\in K$ 使得 $1\cdot\alpha=\alpha,\ \alpha\in V$，称 $1$ 为 $K$ 的单位元，即标量乘法的单位元。</li><li>标量乘法对向量加法的分配律：$c\cdot(\alpha+\beta)=c\cdot\alpha+c\cdot\beta,\ c\in K,\ \alpha,\beta\in V$</li><li>标量乘法对域加法的分配律：$(c_1+c_2)\cdot\alpha=c_1\cdot\alpha+c_2\cdot\alpha,\ c_1,c_2\in K,\ \alpha\in V$</li><li>标量乘法与域乘法的结合律：$(c_1c_2)\cdot\alpha=c_1(c_2\cdot\alpha),\ c_1,c_2\in K,\ \alpha\in V$</li></ol><p>整整八条，实在劝退，有没有更简单的方式来表达这些呢？</p><p>维基百科上对线性空间的概括定义是：</p><blockquote><p>$(K,+,\times)$ 是一个域，且 $V$ 是一个 $K-$ 模。</p></blockquote><p>本文将从八个条件出发，根据抽象代数中域和模的定义，来解释这一句话。前半部分亦可作为抽象代数中基本概念的速览。</p><hr><h2 id="群、环、域、模"><a href="#群、环、域、模" class="headerlink" title="群、环、域、模"></a>群、环、域、模</h2><h3 id="群"><a href="#群" class="headerlink" title="群"></a>群</h3><p>群是指一个集合 $G$，和一个运算 $\circ:G\times G\to G$（封闭性），满足以下条件：</p><ol><li>结合律：$\forall a,b,c\in G,\ (a\circ b)\circ c=a\circ(b\circ c)$</li><li>单位元：存在 $e\in G$，使得 $\forall a\in G,\ e\circ a=a$，称 $e$ 为群的<strong>单位元（Identity）</strong>，又称<strong>幺元</strong>。</li><li>逆元：对任意 $a\in G$，存在 $a^{-1}\in G$，使得 $a^{-1}\circ a=e$，称 $a^{-1}$ 为 $a$ 的<strong>逆元（Inverse）</strong>。</li></ol><p>就称 $(G,\circ)$ 为一个<strong>群（Group）</strong>。需要注意的是，群不一定满足交换律；在群的定义的基础上，若还满足交换律 $a\circ b=b\circ a$，则称该群为<strong>交换群</strong>，又叫 <strong>Abel 群</strong>。</p><p>群的例子：</p><ol><li>$(\mathbb{Z},+)$：整数关于加法满足结合律、单位元 $0$ 存在、逆元 $-a$ 存在，构成一个群，不仅如此，这还是一个交换群。</li><li>$(\mathbb{R}\setminus{0},\times)$：去掉零的实数关于乘法满足结合律、单位元 $1$ 存在、逆元 $a^{-1}$ 存在，构成一个群，也是一个交换群。</li><li>非交换群的一个例子：<a href="https://zh.wikipedia.org/wiki/%E4%BA%8C%E9%9D%A2%E9%AB%94%E7%BE%A4">六阶二面体群</a></li></ol><p>我们可以看出，线性空间定义的前 4 条，现在可以用一句话概括：</p><blockquote><p>$(V,+)$ 是一个交换群。</p></blockquote><h4 id="比群更简单的结构"><a href="#比群更简单的结构" class="headerlink" title="比群更简单的结构"></a>比群更简单的结构</h4><p>上面群的定义可以概括成封闭性、结合律、单位元、逆元四个条件，可以进一步分解为：</p><ul><li>满足封闭性就可以称为<strong>原群（Magma）</strong></li><li>满足封闭性、结合律就可以称为<strong>半群（Semigroup）</strong></li><li>满足封闭性、结合律、单位元就可以称为<strong>幺半群（Monoid）</strong></li></ul><h3 id="环"><a href="#环" class="headerlink" title="环"></a>环</h3><p>不同地方对环的定义存在分歧，最常见的定义是以下这种：</p><p>若 $(R,+,\times)$ 满足：</p><ol><li>$(R,+)$ 是一个交换群</li><li>$(R,\times)$ 是一个幺半群</li><li>左分配律：$a(b+c)=ab+ac$</li><li>右分配律：$(b+c)a=ba+ca$</li></ol><p>则称 $(R,+,\times)$ 为一个<strong>环（Ring）</strong>。</p><p>环不一定满足乘法交换律，满足乘法交换律的环叫做<strong>交换环</strong>。</p><p>环的例子：</p><ol><li>$(\mathbb{Z},+,\times)$，是环。</li><li>实系数矩阵 $M_n(\mathbb{R})$ 是环。</li><li>有理系数多项式 $\mathbb{Q}[x]$ 是环。</li></ol><h4 id="除环"><a href="#除环" class="headerlink" title="除环"></a>除环</h4><p>如果环中的任何非零元都存在乘法逆元，即：</p><p>$$\forall a\in R\setminus{0},\ \exists a^{-1}\in R\setminus{0},\ \text{使得 } a^{-1}\cdot a=1$$</p><p>则称 $(R,+,\times)$ 为一个<strong>除环</strong>。</p><h4 id="域"><a href="#域" class="headerlink" title="域"></a>域</h4><p>交换的除环就是<strong>域（Field）</strong>。</p><p>$\mathbb{Q}$、$\mathbb{R}$、$\mathbb{C}$ 都是域。但是 $\mathbb{Z}$ 不是域，因为 $\mathbb{Z}$ 中只有 $1,-1$ 两个元素存在乘法逆元。</p><hr><h2 id="模"><a href="#模" class="headerlink" title="模"></a>模</h2><p>若 $R$ 是一个环，$1_R$ 是环的幺元，$(M,+)$ 构成一个交换群，且有一个运算 $\cdot:R\times M\to M$，称为标量乘法。</p><p>若这个标量乘法对所有 $r,s\in R$，$x,y\in M$，满足：</p><ol><li>$(r\cdot s)\cdot x=r\cdot(s\cdot x)$</li><li>$r\cdot(x+y)=r\cdot x+r\cdot y$</li><li>$(r+s)\cdot x=r\cdot x+s\cdot x$</li><li>$1_R\cdot x=x$</li></ol><p>则称 $M$ 是一个左 $R-$ 模。</p><p>若基于上面的定义，但交换标量乘法的运算顺序，则称 $M$ 是一个右 $R-$ 模。</p><hr><h2 id="线性空间的定义"><a href="#线性空间的定义" class="headerlink" title="线性空间的定义"></a>线性空间的定义</h2><p>到模这一步就已经很抽象了，恭喜你坚持到了这里，现在我们终于可以定义线性空间了：</p><blockquote><p>$(K,+,\times)$ 是一个域，且 $V$ 是一个 $K-$ 模。</p></blockquote><p>线性空间的八条定义里，前四条实际上规定了 $(V,+)$ 是一个交换群，而后四条正好就是模的定义中的四条。因此说 $V$ 是一个 $K-$ 模，这就是定义的后半句话。</p><p>那么，这个定义中的前半句话可不可以去掉呢？模的定义里已经要求 $K$ 是一个环了，但是线性空间这里，把环的这个条件加强到了域，这有必要吗？</p><hr><h2 id="为什么线性空间要定义在域上？"><a href="#为什么线性空间要定义在域上？" class="headerlink" title="为什么线性空间要定义在域上？"></a>为什么线性空间要定义在域上？</h2><p>我们这里不去讨论这样定义的历史原因，而是去讨论其合理性。</p><p>这非常有必要，因为这个条件保证了线性空间存在<strong>基</strong>。熟悉线性代数的朋友都应该非常清楚，基是线性代数中非常重要的概念，没有了基，线性代数中很多定理都无法成立，线性空间优良的性质也不存在了。</p><p>要理解这个定义，我们首先要证明这个定义下，线性空间一定有基，而且这个证明需要用到 $K$ 是一个域的这个条件。</p><hr><h2 id="证明线性空间一定有基"><a href="#证明线性空间一定有基" class="headerlink" title="证明线性空间一定有基"></a>证明线性空间一定有基</h2><p>我们学习线性代数的时候，通常讨论的是有限维线性空间，证明有限维线性空间存在基比较简单，并且不需要用到 Zorn 引理；此处为了证明的泛用性，我们的证明将涵盖无穷维线性空间。</p><h3 id="Hamel-基"><a href="#Hamel-基" class="headerlink" title="Hamel 基"></a>Hamel 基</h3><p>基有多种不同的定义，此处指的是最常用的 Hamel 基。Hamel 基的定义如下：</p><p>设 $V$ 为一向量空间，Hamel 基是指一组向量组 $H\subset V$，满足：</p><ol><li>$H$ 线性无关，即：任取 $H$ 中<strong>有限个</strong>向量 $\alpha_1,\alpha_2,\cdots,\alpha_n\in H$，则 $\displaystyle\sum_{i=1}^{n}c_i\alpha_i=0$ 当且仅当 $c_1=c_2=\cdots=c_n=0$。（注意此处加粗的”有限个”很重要）</li><li>$H$ 可以张成整个 $V$，即 $\text{span }H=V$：</li></ol><p>$$V=\left{\sum c_i\alpha_i : c_i\in K,\ \alpha_i\in H\right}$$</p><h4 id="存在性证明"><a href="#存在性证明" class="headerlink" title="存在性证明"></a>存在性证明</h4><blockquote><p><strong>定理：任何线性空间都有 Hamel 基。</strong></p></blockquote><p>或者更进一步的，任何线性无关向量组都可以扩展出一组基：</p><blockquote><p><strong>定理：设 $S$ 是线性空间 $V$ 中的线性无关向量组，则存在 Hamel 基 $H\supset S$。</strong></p></blockquote><p>证明需要用到 Zorn 引理：</p><blockquote><p><strong>Zorn 引理：设 $\mathcal{S}$ 为一非空偏序集，若 $\mathcal{S}$ 中任意全序集都在 $\mathcal{S}$ 中存在上界，则 $\mathcal{S}$ 中必有极大元。</strong></p></blockquote><p>设 $\mathcal{S}$ 为 $V$ 中所有包含 $S$ 的线性无关向量组的集合，$(\mathcal{S},\subset)$ 构成一偏序集，显然 $\mathcal{S}$ 非空。设 $S_0\in\mathcal{S}$ 为一全序子集，则令 $X=\bigcup_{S_i\in S_0}S_i$，有：</p><ol><li>$X$ 是 $S_0$ 的上界：显然。</li><li>$X\in\mathcal{S}$，只需证明 $X$ 线性无关。</li></ol><p>下证 $X$ 线性无关：</p><p>由 $S_i$ 构成全序集，知可以令 $S_1\subset S_2\subset S_3\subset\cdots$。</p><p>任取<strong>有限</strong>个向量 $\alpha_1,\alpha_2,\cdots,\alpha_n\in X$，则由于 $X=\bigcup_{S_i\in S_0}S_i$，对任意 $\alpha_i$，存在 $j_i$，$\alpha_i\in S_{j_i}$。令 $j=\max{j_1,j_2,\cdots,j_n}$，则 $\forall i,\ \alpha_i\in S_j$。又因为 $S_j\in\mathcal{S}$，所以 $\alpha_i$ 构成的向量组线性无关。$\alpha_i$ 可以任取，由线性无关定义，$X$ 线性无关，且 $X\in\mathcal{S}$。</p><p>由 $X$ 是 $S_0$ 的上界且 $X\in\mathcal{S}$，应用 Zorn 引理，$\mathcal{S}$ 中必有极大元，设 $H$ 为一极大元，则 $H\supset S$ 且 $H$ 线性无关。</p><p>考察 Hamel 基的定义，线性无关已满足，还需证明 $H$ 可以张成 $V$。任取 $x\in V$，则 $H\cup{x}$ 一定线性相关。因为若线性无关，设 $H’=H\cup{x}$，则 $H’\supset H$ 且 $H’\in\mathcal{S}$，这与 $H$ 是极大元矛盾。</p><p>由 $H$ 线性无关、$H’$ 线性相关，知存在不全为零的 $c_1,c_2,\cdots,c_n$ 和 $c_{n+1}\neq 0$，使得</p><p>$$c_1\alpha_1+c_2\alpha_2+\cdots+c_n\alpha_n+c_{n+1}x=0$$</p><p>则</p><p>$$x=c_{n+1}^{-1}(c_1\alpha_1+c_2\alpha_2+\cdots+c_n\alpha_n)$$</p><p>即任意 $x\in V$ 均可被 $H$ 线性表示，$\text{span }H=V$。</p><p>综上得证。</p><hr><h2 id="模不一定有基"><a href="#模不一定有基" class="headerlink" title="模不一定有基"></a>模不一定有基</h2><p>回到我们原来的问题来，为什么线性空间一定要定义在域上？这是因为一般的模不一定存在基，存在基的模叫做自由模（Free Module），我们有：</p><blockquote><p><strong>除环上的模必为自由模。</strong></p></blockquote><p>考察我们上面的证明，最后一步中用到了 $c_{n+1}^{-1}$，这一步就要求了 $K$ 中存在乘法逆元，而模并不能保证这一点。</p><p>为了方便理解，下面举一个不存在基的模的例子。这个模就是 $\mathbb{Z}-$ 模 $\mathbb{Q}$。</p><p>证明 $\mathbb{Z}-$ 模 $\mathbb{Q}$ 不存在基：</p><p>设这个模为 $M$。首先，我们有任意两个非零元素都线性相关：$\forall p,q\in\mathbb{Q}\setminus{0}$，存在 $x,y\in\mathbb{Z}$，使得 $xp+yq=0$，这是显然的。</p><p>因此如果 $M$ 有基 $B$，一定有 $|B|=1$，设一个基为 $B={f}$，$f\in\mathbb{Q}\setminus{0}$，则 $\text{span }B={0,\pm f,\pm 2f,\cdots}$。显然 $f/2\in M$，$f/2\notin\text{span }B$。故这个基不能张成 $M$，矛盾。</p><hr><h2 id="基在线性空间中的作用"><a href="#基在线性空间中的作用" class="headerlink" title="基在线性空间中的作用"></a>基在线性空间中的作用</h2><p>想到哪写到哪，早就偏题了，思绪比较混乱，望谅解。</p><p>为什么基这么重要？因为对于任何一个数学结构，只要能够证明它是一个线性空间，那么就可以取一组基，然后你就获得了整个线性代数，可以使用线性代数中熟悉的概念。而在群/环/模论中，不同的群/环/模可能有截然不同的性质，没办法这样通用地去研究。</p><p>例如在 Hilbert 空间里，傅里叶变换就是将函数变换到一组特殊的正交基上。</p><p>定义内积：</p><p>$$\langle\alpha,\beta\rangle=\lim_{T\to\infty}\frac{1}{T}\int_{-T/2}^{+T/2}\alpha(t)\overline{\beta(t)},dt$$</p><p>选取正交基 $e^{i2\pi ft}$。</p><p>不难发现这组基是正交的：</p><p>$$\langle e^{i2\pi f_1 t},e^{i2\pi f_2 t}\rangle=\begin{cases}1 &amp; f_1=f_2\ 0 &amp; f_1\neq f_2\end{cases}$$</p><p>在此基础上我们就可以推出傅里叶变换：</p><p>把信号 $x(t)$ 用正交基 $e^{i2\pi f_n t}$ 表示为：</p><p>$$x(t)=\lim_{T\to\infty}\sum_{n=-\infty}^{\infty}\langle x,e^{i2\pi f_n t}\rangle e^{i2\pi f_n t}=\lim_{T\to\infty}\sum_{n=-\infty}^{\infty}\frac{1}{T}\int_{-T/2}^{+T/2}x(t)e^{-i2\pi f_n t},dt\cdot e^{i2\pi f_n t}$$</p><p>其中 $f_n=\dfrac{n}{T}$。$\Delta f=\dfrac{1}{T}$，所以上式可以写成：</p><p>$$x(t)=\lim_{T\to\infty}\sum_{n=-\infty}^{\infty}\Delta f\int_{-T/2}^{+T/2}x(t)e^{-i2\pi f_n t},dt\cdot e^{i2\pi f_n t}=\int_{-\infty}^{\infty}\int_{-\infty}^{\infty}x(t)e^{-i2\pi ft},dt\cdot e^{i2\pi ft},df$$</p><p>由积分的定义可以将求和化为积分。</p><p>这个二重积分有着美妙的对称性，而且这里面就蕴含了傅里叶变换：</p><p>$$F(x)(f)=\hat{x}(f)=\int_{-\infty}^{\infty}x(t)e^{-i2\pi ft},dt$$</p><p>及其逆变换：</p><p>$$F^{-1}(\hat{x})(t)=x(t)=\int_{-\infty}^{\infty}\hat{x}(f)e^{i2\pi ft},df$$</p><p>这种定义的傅里叶变换 $F$ 可以看作一个线性变换，$F$ 和 $F^{-1}$ 互为逆变换，$F^{-1}\circ F=\mathrm{id}$。</p><p>而我们熟悉的 Parseval 定理，其实也只是勾股定理的推广，这里说的是在不同的正交基下，向量的模长都等于坐标的平方和：</p><p>$$\int_{-\infty}^{\infty}|x(t)|^2,dt=\int_{-\infty}^{\infty}|\hat{x}(f)|^2,df$$</p><hr><h2 id="参考链接"><a href="#参考链接" class="headerlink" title="参考链接"></a>参考链接</h2><ul><li><a href="https://zh.wikipedia.org/wiki/%E7%BE%A4">维基百科：群</a></li><li><a href="https://zh.wikipedia.org/wiki/%E7%8E%AF_(%E4%BB%A3%E6%95%B0)">维基百科：环（代数）</a></li><li><a href="https://zh.wikipedia.org/wiki/%E6%A8%A1">维基百科：模</a></li><li><a href="https://www.bananaspace.org/wiki/%E8%AE%B2%E4%B9%89:%E7%BA%BF%E6%80%A7%E4%BB%A3%E6%95%B0%EF%BC%88infi%EF%BC%89/%E4%BB%BB%E6%84%8F%E7%8E%AF%E5%92%8C%E5%9F%9F%E4%B8%8A%E7%9A%84%E7%BA%BF%E6%80%A7%E4%BB%A3%E6%95%B0/%E7%8E%AF%E3%80%81%E5%9F%9F%E3%80%81%E6%A8%A1%E3%80%81%E5%90%91%E9%87%8F%E7%A9%BA%E9%97%B4%E5%92%8C%E4%BB%A3%E6%95%B0">香蕉空间讲义：线性代数（infi）/ 任意环和域上的线性代数 / 环、域、模、向量空间和代数</a></li><li><a href="https://zhuanlan.zhihu.com/p/331823781">知乎专栏：P/331823781</a></li><li><a href="https://brezezee.github.io/2019/09/02/ML/Fourier%20transform/">brezezee.github.io：Fourier transform</a></li><li><a href="https://zh.wikipedia.org/wiki/%E5%82%85%E9%87%8C%E5%8F%B6%E5%8F%98%E6%8D%A2">维基百科：傅里叶变换</a></li></ul>]]>
    </content>
    <id>https://yohoten.github.io/2024/06/30/%E7%BA%BF%E6%80%A7%E7%A9%BA%E9%97%B4%E5%88%B0%E5%BA%95%E6%98%AF%E4%BB%80%E4%B9%88/</id>
    <link href="https://yohoten.github.io/2024/06/30/%E7%BA%BF%E6%80%A7%E7%A9%BA%E9%97%B4%E5%88%B0%E5%BA%95%E6%98%AF%E4%BB%80%E4%B9%88/"/>
    <published>2024-06-30T04:00:00.000Z</published>
    <summary>从八个公理出发，借助抽象代数中群、环、域、模的概念，把线性空间浓缩成一句话「域上的模」，并证明线性空间一定存在 Hamel 基，进而解释为什么线性空间必须定义在域上。</summary>
    <title>线性空间到底是什么？能不能用一句话定义线性空间？为什么线性空间一定有基？</title>
    <updated>2024-06-30T04:00:00.000Z</updated>
  </entry>
  <entry>
    <author>
      <name>yohoten</name>
    </author>
    <category term="日常" scheme="https://yohoten.github.io/categories/%E6%97%A5%E5%B8%B8/"/>
    <category term="随笔" scheme="https://yohoten.github.io/tags/%E9%9A%8F%E7%AC%94/"/>
    <content>
      <![CDATA[<h2 id="Hello-World"><a href="#Hello-World" class="headerlink" title="Hello World"></a>Hello World</h2><p>欢迎来到我的博客！这是我搭建博客后的第一篇文章。</p><p>博客搭建于24年10月，直到2025年01月才开始使用，哈哈哈。之前都是活跃在CSDN、知乎、编程中国等博客，我一直希望能有一个属于自己的独立博客。今天，这个愿望终于实现了！</p><p>在接下来的日子里，我会在这里分享我的技术经验、学习心得和生活感悟。</p><p>敬请期待更多内容！</p>]]>
    </content>
    <id>https://yohoten.github.io/2024/01/01/%E6%88%91%E7%9A%84%E7%AC%AC%E4%B8%80%E7%AF%87%E5%8D%9A%E5%AE%A2/</id>
    <link href="https://yohoten.github.io/2024/01/01/%E6%88%91%E7%9A%84%E7%AC%AC%E4%B8%80%E7%AF%87%E5%8D%9A%E5%AE%A2/"/>
    <published>2024-01-01T04:00:00.000Z</published>
    <summary>这是我在自己独立博客上的第一篇文章，记录下建站这一刻的心情与期待。</summary>
    <title>我的第一篇博客</title>
    <updated>2024-01-01T04:00:00.000Z</updated>
  </entry>
</feed>
