[{"categories":"旅游","collections":["超超的奇妙冒险"],"content":"第 1 节 东坡园\r步入东坡园，首先映入眼帘的是苏东坡的雕像，他拄着拐杖、左手轻抚胡须，那「竹杖芒鞋轻胜马」的洒脱姿态仿佛穿越千年扑面而来。 移步向前，石墙上镌刻着苏东坡的诗词名篇，字里行间流淌着千古文人的豪迈与旷达。 抬眼望去，一条清流蜿蜒而过，对岸白墙黑瓦的苏式建筑静静伫立，与园中的诗意交相辉映。 顺着视线远眺，一座气势恢宏的建筑拔地而起，檐角飞扬，令人顿生仰慕之情。 走近细看，墙上「坡仙遗范」四个大字赫然在目，乃康熙皇帝御笔亲书，笔力遒劲，尽显帝王气象与对东坡的敬意。 沿着曲径通幽的小路继续深入，脚下斑驳的石块仿佛在诉说着岁月的沧桑与故事。 再往前行，一面刻满诗文的「苏子墙」映入眼帘，字字珠玑，宛如一座露天诗碑。 旁边矗立着「楚颂亭」，飞檐翘角，掩映在葱茏绿意之中，自有一番古朴雅致的韵味。 穿过一道拱门，一座唯美的庭院悄然呈现，院中的洗砚池静静卧于角落，仿佛还能嗅到当年苏子研磨挥毫的墨香。 不远处一座拱形石桥横跨于碧水之上，弧线优美而充满张力，为园景平添了几分灵动的美感。 踏上阶梯，登上高处，四周植被茂盛繁密，正值花季的流苏树盛开着洁白的花朵，如雪似絮，故而被称作「四月雪」。 最后来到一片池塘前，池中央三只石蟾蜍正源源不断地喷吐着清泉，水花四溅，为这座诗意的园林画上了一个灵动活泼的句号。 ","date":"2026-05-05","objectID":"/posts/%E6%B1%9F%E5%8D%97%E4%B8%89%E9%9F%B5%E4%B8%9C%E5%9D%A1%E9%81%97%E9%A3%8E%E7%BA%A2%E6%A2%85%E6%98%A5%E8%89%B2%E4%B8%8E%E5%A4%A9%E5%AE%81%E7%A6%85%E6%84%8F/:1:0","tags":["旅游"],"title":"江南三韵：东坡遗风、红梅春色与天宁禅意","uri":"/posts/%E6%B1%9F%E5%8D%97%E4%B8%89%E9%9F%B5%E4%B8%9C%E5%9D%A1%E9%81%97%E9%A3%8E%E7%BA%A2%E6%A2%85%E6%98%A5%E8%89%B2%E4%B8%8E%E5%A4%A9%E5%AE%81%E7%A6%85%E6%84%8F/#东坡园"},{"categories":"旅游","collections":["超超的奇妙冒险"],"content":"第 2 节 红梅公园\r走进红梅公园，首先映入眼帘的是一座古塔，塔身上缠绕着标志性的红梅，朵朵梅花拼凑成粉色巨龙的身躯——那正是常州的吉祥物「常宝」，栩栩如生，仿佛随时要腾空而起。 绕到塔后，眼前出现一堆乱石堆砌而成的洞穴，石块千疮百孔、形态各异，下方恰可乘凉避暑，别有一番野趣。 再往前走，一座造型独特的建筑静立眼前，透过它圆形的门洞向外望去，外面花草林立、绿意盎然，真可谓别有洞天，仿佛误入了桃花源中。 继续往里走，透过一扇圆形窗棂向外眺望，窗外一片清澈的水池，池中央点缀着一座黑瓦小房，倒映在水中，静谧如画。 最后来到一面繁花似锦的花墙前，爬藤月季与矮牵牛顺着墙体攀援而上，枝叶被精心修剪得整整齐齐，为这趟游园之旅画上了一个芬芳悠长的句号。 ","date":"2026-05-05","objectID":"/posts/%E6%B1%9F%E5%8D%97%E4%B8%89%E9%9F%B5%E4%B8%9C%E5%9D%A1%E9%81%97%E9%A3%8E%E7%BA%A2%E6%A2%85%E6%98%A5%E8%89%B2%E4%B8%8E%E5%A4%A9%E5%AE%81%E7%A6%85%E6%84%8F/:2:0","tags":["旅游"],"title":"江南三韵：东坡遗风、红梅春色与天宁禅意","uri":"/posts/%E6%B1%9F%E5%8D%97%E4%B8%89%E9%9F%B5%E4%B8%9C%E5%9D%A1%E9%81%97%E9%A3%8E%E7%BA%A2%E6%A2%85%E6%98%A5%E8%89%B2%E4%B8%8E%E5%A4%A9%E5%AE%81%E7%A6%85%E6%84%8F/#红梅公园"},{"categories":"旅游","collections":["超超的奇妙冒险"],"content":"第 3 节 天宁宝塔\r走进天宁禅寺，首先映入眼帘的便是正门牌匾上苍劲有力的\"天宁福地\"四个大字，仿佛在向每一位到访者传递着吉祥与祝福。 继续往深处走，寺院墙体上赫然写着\"东南第一丛林\"，彰显着这座古刹在江南佛教丛林中的崇高地位。 再往前行，便来到护法财神殿前，殿宇肃穆庄重，香火缭绕之间寄托着信众们对美好生活的虔诚祈愿。 抬头望去，殿宇的挑檐呈现出方正的四角造型，线条简洁有力，尽显传统古建筑的端庄之美。 接着步入大雄宝殿，殿内气势恢宏、庄严肃穆，巨大的佛像端坐其间，令人心生敬畏、不由得屏息凝神。 最后来到天宁宝塔之下，宝塔高耸入云、直插天际，走近细看，塔身上镌刻着\"大智大慧\"四字，让人顿悟佛法之广大深远。 ","date":"2026-05-05","objectID":"/posts/%E6%B1%9F%E5%8D%97%E4%B8%89%E9%9F%B5%E4%B8%9C%E5%9D%A1%E9%81%97%E9%A3%8E%E7%BA%A2%E6%A2%85%E6%98%A5%E8%89%B2%E4%B8%8E%E5%A4%A9%E5%AE%81%E7%A6%85%E6%84%8F/:3:0","tags":["旅游"],"title":"江南三韵：东坡遗风、红梅春色与天宁禅意","uri":"/posts/%E6%B1%9F%E5%8D%97%E4%B8%89%E9%9F%B5%E4%B8%9C%E5%9D%A1%E9%81%97%E9%A3%8E%E7%BA%A2%E6%A2%85%E6%98%A5%E8%89%B2%E4%B8%8E%E5%A4%A9%E5%AE%81%E7%A6%85%E6%84%8F/#天宁宝塔"},{"categories":"旅游","collections":["超超的奇妙冒险"],"content":"第 1 节 初见南浔\r刚踏入南浔古镇，一面古朴的白墙便映入眼帘，墙面上「南浔古镇」四个大字格外醒目，其中「南浔」二字竟由一朵朵粉红的玫瑰精心拼成，散发着浪漫与雅致的气息。 墙下四只大象造型的小喷泉一字排开，象鼻正汩汩地喷着清澈的水流，与粉色的花墙相映成趣，这里早已成为游客们争相打卡的网红地标。 ","date":"2026-05-03","objectID":"/posts/%E5%8D%97%E6%B5%94%E7%8E%AB%E7%91%B0%E5%A2%99%E4%B8%8B%E7%9A%84%E6%B0%B4%E4%B9%A1%E6%BC%AB%E6%AD%A5/:1:0","tags":["旅游"],"title":"南浔：玫瑰墙下的水乡漫步","uri":"/posts/%E5%8D%97%E6%B5%94%E7%8E%AB%E7%91%B0%E5%A2%99%E4%B8%8B%E7%9A%84%E6%B0%B4%E4%B9%A1%E6%BC%AB%E6%AD%A5/#初见南浔"},{"categories":"旅游","collections":["超超的奇妙冒险"],"content":"第 2 节 水巷与旧宅之间\r走进南浔古镇，眼前便是一条蜿蜒的河道，清澈的河水静静流淌，岸边垂柳依依，柔美的倒影在碧波中轻轻摇曳，宛如一幅恬淡的水墨画卷。 沿着河岸继续前行，对岸一排排黑瓦老屋下人头攒动，原来是热闹的小吃街，各种江南特色的香气随风飘散而来，为这片静谧的水乡增添了几分烟火气息。 再往前走，河面上缓缓驶来几艘古朴的游船，船夫摇着橹，船身在水面划开一道道涟漪，游客们坐在船上悠然欣赏着两岸的风景，好不惬意。 登上石桥向下望去，整条水巷尽收眼底，两岸错落有致的黑瓦屋檐层层叠叠，与碧绿的河水相映成趣，古色古香的建筑韵味十足，令人流连忘返。 ","date":"2026-05-03","objectID":"/posts/%E5%8D%97%E6%B5%94%E7%8E%AB%E7%91%B0%E5%A2%99%E4%B8%8B%E7%9A%84%E6%B0%B4%E4%B9%A1%E6%BC%AB%E6%AD%A5/:2:0","tags":["旅游"],"title":"南浔：玫瑰墙下的水乡漫步","uri":"/posts/%E5%8D%97%E6%B5%94%E7%8E%AB%E7%91%B0%E5%A2%99%E4%B8%8B%E7%9A%84%E6%B0%B4%E4%B9%A1%E6%BC%AB%E6%AD%A5/#水巷与旧宅之间"},{"categories":"旅游","collections":["超超的奇妙冒险"],"content":"第 3 节 白墙黑瓦里的江南\r走进湖州南浔古镇，首先映入眼帘的是河对岸的一排古老房屋，白墙黑瓦在江南水乡的薄雾中静静伫立，古朴而安详。 再往前看，红棕色的木质栏杆沿河而立，与斑驳的墙面形成鲜明对比——破损处露出的红砖，仿佛在无声诉说着岁月沉淀下来的故事。 继续沿着河边漫步，墙皮发白发黑、层层剥落，历史的痕迹就这样自然地刻写在每一寸砖瓦之上，让人不禁驻足凝望。 不经意间回头，左边停放的一排现代车辆与右边古色古香的建筑群交相辉映，新与旧在这一刻悄然交织，构成了南浔独有的时光韵味。 ","date":"2026-05-03","objectID":"/posts/%E5%8D%97%E6%B5%94%E7%8E%AB%E7%91%B0%E5%A2%99%E4%B8%8B%E7%9A%84%E6%B0%B4%E4%B9%A1%E6%BC%AB%E6%AD%A5/:3:0","tags":["旅游"],"title":"南浔：玫瑰墙下的水乡漫步","uri":"/posts/%E5%8D%97%E6%B5%94%E7%8E%AB%E7%91%B0%E5%A2%99%E4%B8%8B%E7%9A%84%E6%B0%B4%E4%B9%A1%E6%BC%AB%E6%AD%A5/#白墙黑瓦里的江南"},{"categories":"旅游","collections":["超超的奇妙冒险"],"content":"第 4 节 香火与旧时光\r在香火缭绕间便瞧见一座古朴的道馆，抬头望去，牌匾上赫然写着「黄大仙殿」四个大字。 步入殿内，只见黄大仙雕像端坐于香案之后，慈眉善目间透着几分仙风道骨，令人顿生虔敬之心。 ","date":"2026-05-03","objectID":"/posts/%E5%8D%97%E6%B5%94%E7%8E%AB%E7%91%B0%E5%A2%99%E4%B8%8B%E7%9A%84%E6%B0%B4%E4%B9%A1%E6%BC%AB%E6%AD%A5/:4:0","tags":["旅游"],"title":"南浔：玫瑰墙下的水乡漫步","uri":"/posts/%E5%8D%97%E6%B5%94%E7%8E%AB%E7%91%B0%E5%A2%99%E4%B8%8B%E7%9A%84%E6%B0%B4%E4%B9%A1%E6%BC%AB%E6%AD%A5/#香火与旧时光"},{"categories":"旅游","collections":["超超的奇妙冒险"],"content":"第 5 节 沿河烟火气\r继续漫步，一条热闹的小吃街迎面铺展开来，两旁店铺林立，摆满了湖州当地的各式特产与小吃，烟火气息扑面而来，令人目不暇接。 我在街边的小店点了一碗招牌白汤鱼，鱼肉鲜嫩爽滑，汤汁醇厚清甜，一口下去满是水乡的鲜美滋味，实在令人回味无穷。 ","date":"2026-05-03","objectID":"/posts/%E5%8D%97%E6%B5%94%E7%8E%AB%E7%91%B0%E5%A2%99%E4%B8%8B%E7%9A%84%E6%B0%B4%E4%B9%A1%E6%BC%AB%E6%AD%A5/:5:0","tags":["旅游"],"title":"南浔：玫瑰墙下的水乡漫步","uri":"/posts/%E5%8D%97%E6%B5%94%E7%8E%AB%E7%91%B0%E5%A2%99%E4%B8%8B%E7%9A%84%E6%B0%B4%E4%B9%A1%E6%BC%AB%E6%AD%A5/#沿河烟火气"},{"categories":["网页开发"],"collections":["前端"],"content":"第 13 节 概述\r在复杂的前端项目中，会有多个页面和模块，使用 Vue 3 构建复杂项目时，为了方便实现页面的模块化解耦以及统一管理，需要使用组件和路由。 ","date":"2026-01-13","objectID":"/posts/vue-3-%E7%BB%84%E4%BB%B6%E5%8F%8A%E8%B7%AF%E7%94%B1/:1:0","tags":null,"title":"Vue 3 组件及路由","uri":"/posts/vue-3-%E7%BB%84%E4%BB%B6%E5%8F%8A%E8%B7%AF%E7%94%B1/#概述"},{"categories":["网页开发"],"collections":["前端"],"content":"第 14 节 目录结构\r一个复杂的 Vue 3 项目会由多个组件构成，这些组件组成了如下的目录结构： ","date":"2026-01-13","objectID":"/posts/vue-3-%E7%BB%84%E4%BB%B6%E5%8F%8A%E8%B7%AF%E7%94%B1/:2:0","tags":null,"title":"Vue 3 组件及路由","uri":"/posts/vue-3-%E7%BB%84%E4%BB%B6%E5%8F%8A%E8%B7%AF%E7%94%B1/#目录结构"},{"categories":["网页开发"],"collections":["前端"],"content":"14.1 配置 Tailwind CSS\r在项目目录下使用命令进行安装： npm install tailwindcss @tailwindcss/vite 在 vite.config.ts 中增加以下内容： import tailwindcss from '@tailwindcss/vite' export default defineConfig({ plugins: [tailwindcss()] }) 在 /src/styles/index.css 中引入 Tailwind CSS： @import \"tailwindcss\"; 在 /src/main.ts 中增加以下内容： import './styles/index.css' ","date":"2026-01-13","objectID":"/posts/vue-3-%E7%BB%84%E4%BB%B6%E5%8F%8A%E8%B7%AF%E7%94%B1/:2:1","tags":null,"title":"Vue 3 组件及路由","uri":"/posts/vue-3-%E7%BB%84%E4%BB%B6%E5%8F%8A%E8%B7%AF%E7%94%B1/#配置-tailwind-css"},{"categories":["网页开发"],"collections":["前端"],"content":"14.2 配置 TypeScript\rsrc/ ├─ types/ │ ├─ xxx.ts │ ├─ xxx.ts │ └─ xxx.ts TypeScript 的类型定义放在 /src/types 目录下，需要新增类型时，创建 xxx.ts 文件，比如创建一个 user.ts 文件： export type gender = '男' | '女' export interface User { id: number name: string gender: gender } 在 xxx.vue 中这样使用，使用 @ 符号表示项目的 src 目录： \u003cscript lang=\"ts\" setup\u003e import { RouterLink, RouterView } from 'vue-router' import type { User } from '@/types/user' const person: User = { id: 1, name: '张三', gender: '男', } \u003c/script\u003e @ 符号的定义在 vite.config.ts 中, 使用 npm create vue@latest 的时候会自动创建好： export default defineConfig({ resolve: { alias: { '@': fileURLToPath(new URL('./src', import.meta.url)), }, }, }) ","date":"2026-01-13","objectID":"/posts/vue-3-%E7%BB%84%E4%BB%B6%E5%8F%8A%E8%B7%AF%E7%94%B1/:2:2","tags":null,"title":"Vue 3 组件及路由","uri":"/posts/vue-3-%E7%BB%84%E4%BB%B6%E5%8F%8A%E8%B7%AF%E7%94%B1/#配置-typescript"},{"categories":["网页开发"],"collections":["前端"],"content":"14.3 配置组件\rsrc/ ├─ components/ │ ├─ xxx.vue │ ├─ xxx.vue │ └─ xxx.vue 通用的小组件放在 /scr/components 目录下，文件名是 xxx.vue，Vue 3 要求组件名首字母大写，并且至少要有两个单词组成，比如，创建 SubmitButton.vue 文件： \u003ctemplate\u003e \u003cbutton class=\"px-4 py-2 rounded bg-blue-500 text-white text-sm hover:bg-blue-600 transition\" @click=\"handleClick\" \u003e 提交 \u003c/button\u003e \u003c/template\u003e \u003cscript setup lang=\"ts\"\u003e function handleClick() { console.log('SubmitButton 被点击了') } \u003c/script\u003e 在 xxx.vue 文件中，我想要使用这个组件，可以这样写： \u003ctemplate\u003e \u003cSubmitButton\u003e\u003c/SubmitButton\u003e \u003c/template\u003e \u003cscript lang=\"ts\" setup\u003e import SubmitButton from '@/components/SubmitButton.vue' \u003c/script\u003e ","date":"2026-01-13","objectID":"/posts/vue-3-%E7%BB%84%E4%BB%B6%E5%8F%8A%E8%B7%AF%E7%94%B1/:2:3","tags":null,"title":"Vue 3 组件及路由","uri":"/posts/vue-3-%E7%BB%84%E4%BB%B6%E5%8F%8A%E8%B7%AF%E7%94%B1/#配置组件"},{"categories":["网页开发"],"collections":["前端"],"content":"14.4 配置路由\rsrc/ ├─ pages/ │ ├─ xxx.vue │ ├─ xxx.vue │ └─ xxx.vue ├─ router/ │ └─ index.ts 页面组件需要配置单独的 URL，路由（Router）就是管理页面跳转和 URL 映射的机制，所以我们需要通过路由来配置这些组件。 使用 npm create vue@latest 的时候可以选择是否配置路由，Vue 3 会自动为我们配置路由。 Vue 3 会为我们安装 vue-router，在项目目录下执行命令： npm install vue-router Vue 3 会创建 /src/router/index.ts 文件，并且进行初始化，导出一个 router 对象： import { createRouter, createWebHistory } from 'vue-router' const router = createRouter({ history: createWebHistory(import.meta.env.BASE_URL), routes: [], }) export default router Vue 3 会在 /src/main.ts 中导入 router 并使用： import router from './router' const app = createApp(App) app.use(router) app.mount('#app') Vue 3 的页面组件需要放到 /src/pages 文件夹中。 vue-router 的具体使用方法在后面的章节讲述。 ","date":"2026-01-13","objectID":"/posts/vue-3-%E7%BB%84%E4%BB%B6%E5%8F%8A%E8%B7%AF%E7%94%B1/:2:4","tags":null,"title":"Vue 3 组件及路由","uri":"/posts/vue-3-%E7%BB%84%E4%BB%B6%E5%8F%8A%E8%B7%AF%E7%94%B1/#配置路由"},{"categories":["网页开发"],"collections":["前端"],"content":"14.5 配置状态管理\rVue 3 中有多种组件之间的通信方式，使用 Pinia 是其中一种方法，Pinia 是一个状态管理库，可以实现多个组件共享数据，当其状态改变时，相关组件自动更新。 使用 npm create vue@latest 的时候可以选择是否配置状态管理，Vue 3 会自动为我们配置状态管理。 Vue 3 会为我们安装 pinia，在项目目录下执行命令： npm install pinia Vue 3 会在 /src/main.ts 中导入 pinia 并使用： import { createPinia } from 'pinia' const app = createApp(App) app.use(createPinia()) app.mount('#app') pinia 的具体使用方法在后面的章节讲述。 ","date":"2026-01-13","objectID":"/posts/vue-3-%E7%BB%84%E4%BB%B6%E5%8F%8A%E8%B7%AF%E7%94%B1/:2:5","tags":null,"title":"Vue 3 组件及路由","uri":"/posts/vue-3-%E7%BB%84%E4%BB%B6%E5%8F%8A%E8%B7%AF%E7%94%B1/#配置状态管理"},{"categories":["网页开发"],"collections":["前端"],"content":"第 15 节 组件通信\r通常，父组件和子组件之间存在数据交互，Vue 3 中实现了这一交互，可以很方便实现组件之间的通信。 ","date":"2026-01-13","objectID":"/posts/vue-3-%E7%BB%84%E4%BB%B6%E5%8F%8A%E8%B7%AF%E7%94%B1/:3:0","tags":null,"title":"Vue 3 组件及路由","uri":"/posts/vue-3-%E7%BB%84%E4%BB%B6%E5%8F%8A%E8%B7%AF%E7%94%B1/#组件通信"},{"categories":["网页开发"],"collections":["前端"],"content":"15.1 props 属性\r","date":"2026-01-13","objectID":"/posts/vue-3-%E7%BB%84%E4%BB%B6%E5%8F%8A%E8%B7%AF%E7%94%B1/:3:1","tags":null,"title":"Vue 3 组件及路由","uri":"/posts/vue-3-%E7%BB%84%E4%BB%B6%E5%8F%8A%E8%B7%AF%E7%94%B1/#props-属性"},{"categories":["网页开发"],"collections":["前端"],"content":"第 16 节 路由\r以一个简单的新闻网页为例说明如何构建路由，网页有导航栏和内容区域两部分，导航栏有主页、新闻和关于三个标签，点击导航栏的标签，内容区域加载对应的页面。 ","date":"2026-01-13","objectID":"/posts/vue-3-%E7%BB%84%E4%BB%B6%E5%8F%8A%E8%B7%AF%E7%94%B1/:4:0","tags":null,"title":"Vue 3 组件及路由","uri":"/posts/vue-3-%E7%BB%84%E4%BB%B6%E5%8F%8A%E8%B7%AF%E7%94%B1/#路由"},{"categories":["网页开发"],"collections":["前端"],"content":"16.1 页面跳转\r在 /src/pages 目录下创建 Home.vue、News.vue 和 About.vue 三个组件 \u003ctemplate\u003e \u003cdiv\u003e \u003ch1 class=\"text-2xl font-bold\"\u003e首页\u003c/h1\u003e \u003cp\u003e欢迎来到首页\u003c/p\u003e \u003c/div\u003e \u003c/template\u003e \u003ctemplate\u003e \u003cdiv\u003e \u003ch1 class=\"text-2xl font-bold\"\u003e新闻\u003c/h1\u003e \u003cp\u003e这里是新闻页面\u003c/p\u003e \u003c/div\u003e \u003c/template\u003e \u003ctemplate\u003e \u003cdiv\u003e \u003ch1 class=\"text-2xl font-bold\"\u003e关于\u003c/h1\u003e \u003cp\u003e这里是关于页面\u003c/p\u003e \u003c/div\u003e \u003c/template\u003e 在 /src/router/index.ts 中写入如下内容： import { createRouter, createWebHistory } from 'vue-router' import Home from '@/pages/Home.vue' import News from '@/pages/News.vue' import About from '@/pages/About.vue' const routes = [ { path: '/', redirect: '/home', }, { path: '/home', component: Home, }, { path: '/news', component: News, }, { path: '/about', component: About, }, ] const router = createRouter({ history: createWebHistory(), routes, }) export default router 然后在 xxx.vue 中，需要引入 RouterLink 和 RouterView： \u003ctemplate\u003e \u003cdiv\u003e \u003cnav class=\"p-4 bg-gray-200 flex gap-10 justify-center\"\u003e \u003crouter-link class=\"hover:text-blue-500\" to=\"/\"\u003e首页\u003c/router-link\u003e \u003crouter-link class=\"hover:text-blue-500\" to=\"/news\"\u003e新闻\u003c/router-link\u003e \u003crouter-link class=\"hover:text-blue-500\" to=\"/about\"\u003e关于\u003c/router-link\u003e \u003c/nav\u003e \u003cdiv class=\"p-4\"\u003e \u003crouter-view /\u003e \u003c/div\u003e \u003c/div\u003e \u003c/template\u003e \u003cscript lang=\"ts\" setup\u003e import { RouterLink, RouterView } from 'vue-router' \u003c/script\u003e router-link 标签中需要指定属性 to 为路由的 path，router-view 标签为加载页面内容的位置。 ","date":"2026-01-13","objectID":"/posts/vue-3-%E7%BB%84%E4%BB%B6%E5%8F%8A%E8%B7%AF%E7%94%B1/:4:1","tags":null,"title":"Vue 3 组件及路由","uri":"/posts/vue-3-%E7%BB%84%E4%BB%B6%E5%8F%8A%E8%B7%AF%E7%94%B1/#页面跳转"},{"categories":["网页开发"],"collections":["前端"],"content":"16.2 路由命名\r在 /src/router/index.ts 中，路由可以指定 name 属性： const routes = [ { path: '/', redirect: '/home', }, { path: '/home', name: 'home', component: Home, }, { path: '/news', name: 'news', component: News, }, { path: '/about', name: 'about', component: About, }, ] 这样在 xxx.vue 中就可以使用 :to，通过 name 来查找 path，当 path 比较长时，这种方式会更加简便： \u003ctemplate\u003e \u003cdiv\u003e \u003cnav class=\"p-4 bg-gray-200 flex gap-10 justify-center\"\u003e \u003crouter-link :to=\"{ name: 'home' }\" class=\"hover:text-blue-500\"\u003e首页\u003c/router-link\u003e \u003crouter-link :to=\"{ name: 'news' }\" class=\"hover:text-blue-500\"\u003e新闻\u003c/router-link\u003e \u003crouter-link :to=\"{ name: 'about' }\" class=\"hover:text-blue-500\"\u003e关于\u003c/router-link\u003e \u003c/nav\u003e \u003cdiv class=\"p-4\"\u003e \u003crouter-view /\u003e \u003c/div\u003e \u003c/div\u003e \u003c/template\u003e ","date":"2026-01-13","objectID":"/posts/vue-3-%E7%BB%84%E4%BB%B6%E5%8F%8A%E8%B7%AF%E7%94%B1/:4:2","tags":null,"title":"Vue 3 组件及路由","uri":"/posts/vue-3-%E7%BB%84%E4%BB%B6%E5%8F%8A%E8%B7%AF%E7%94%B1/#路由命名"},{"categories":["网页开发"],"collections":["前端"],"content":"16.3 嵌套路由\r路由可以嵌套为多级，比如新闻页面下可以有新闻的详情页面。 修改之前的 News.vue： \u003ctemplate\u003e \u003cdiv class=\"flex gap-4\"\u003e \u003cdiv class=\"w-1/4 bg-gray-100 p-4 rounded\"\u003e \u003ch2 class=\"text-xl font-bold mb-2\"\u003e新闻列表\u003c/h2\u003e \u003cul class=\"space-y-2\"\u003e \u003cli v-for=\"item in newsList\" :key=\"item.id\"\u003e \u003crouter-link :to=\"{ name: 'news-detail', params: { id: item.id } }\" class=\"block text-blue-500 hover:underline\" \u003e {{ item.title }} \u003c/router-link\u003e \u003c/li\u003e \u003c/ul\u003e \u003c/div\u003e \u003cdiv class=\"flex-1 bg-white p-4 rounded shadow\"\u003e \u003crouter-view /\u003e \u003c!-- 如果没有选择新闻，显示默认提示 --\u003e \u003ctemplate\u003e \u003cp class=\"text-gray-400\"\u003e请选择一条新闻查看详情\u003c/p\u003e \u003c/template\u003e \u003c/div\u003e \u003c/div\u003e \u003c/template\u003e \u003cscript setup lang=\"ts\"\u003e const newsList = [ { id: 1, title: '新闻 1' }, { id: 2, title: '新闻 2' }, { id: 3, title: '新闻 3' }, ] \u003c/script\u003e 在 /src/pages 目录下创建 NewsDetail.vue： \u003ctemplate\u003e \u003cdiv\u003e \u003ch2 class=\"text-xl font-bold\"\u003e新闻详情页\u003c/h2\u003e \u003cp\u003e新闻 ID: {{ newsId }}\u003c/p\u003e \u003c/div\u003e \u003c/template\u003e \u003cscript setup lang=\"ts\"\u003e import { useRoute } from 'vue-router' import { computed } from 'vue' const route = useRoute() const newsId = computed(() =\u003e route.params.id) \u003c/script\u003e 修改/src/router/index.ts： const routes = [ { path: '/', redirect: { name: 'home' }, }, { path: '/home', name: 'home', component: Home, }, { path: '/news', name: 'news', component: News, children: [ { path: ':id', name: 'news-detail', component: NewsDetail, }, ], }, { path: '/about', name: 'about', component: About, }, ] 在 /news 下新增 children 属性，并定义 path 为 :id, 这样新闻详情的 path 就会是多级嵌套的叠加，变成 /news/:id，这里的 :id 为动态参数，在 news.vue 中传入。 ","date":"2026-01-13","objectID":"/posts/vue-3-%E7%BB%84%E4%BB%B6%E5%8F%8A%E8%B7%AF%E7%94%B1/:4:3","tags":null,"title":"Vue 3 组件及路由","uri":"/posts/vue-3-%E7%BB%84%E4%BB%B6%E5%8F%8A%E8%B7%AF%E7%94%B1/#嵌套路由"},{"categories":["网页开发"],"collections":["前端"],"content":"16.4 路由传参\r路由的传参方式有两种，一种是使用 query 传参，一种是使用 params 传参，上面的例子中使用的就是 params 传参，稍作修改就可以变成使用 query 传参。 在/src/pages/NewsDetail.vue中稍作修改： \u003crouter-link :to=\"{ name: 'news-detail', query: { id: item.id } }\" class=\"block text-blue-500 hover:underline\" \u003e {{ item.title }} \u003c/router-link\u003e 然后修改/src/router/index.ts： const routes = [ { path: '/news', name: 'news', component: News, children: [ { path: '', name: 'news-detail', component: NewsDetail, }, ], } ] 使用query传参时，path形式类似/news?id=1\u0026x=x\u0026y=y，使用params传参时path形式类似/news/1/x/y，可以看到两种传参方式差别不是很大。 ","date":"2026-01-13","objectID":"/posts/vue-3-%E7%BB%84%E4%BB%B6%E5%8F%8A%E8%B7%AF%E7%94%B1/:4:4","tags":null,"title":"Vue 3 组件及路由","uri":"/posts/vue-3-%E7%BB%84%E4%BB%B6%E5%8F%8A%E8%B7%AF%E7%94%B1/#路由传参"},{"categories":["网页开发"],"collections":["前端"],"content":"第 7 节 概述\rVue 是一个渐进式的前端框架，所谓渐进式就是“可以被逐步集成”的意思，按照需求可以构建简单的静态页面，也可以构建复杂的终端应用： 无需构建步骤，渐进式增强静态的 HTML 在任何页面中作为 Web Components 嵌入 单页应用 (SPA) 全栈 / 服务端渲染 (SSR) Jamstack / 静态站点生成 (SSG) 开发桌面端、移动端、WebGL，甚至是命令行终端中的界面 ","date":"2026-01-08","objectID":"/posts/vue-3-%E5%93%8D%E5%BA%94%E5%BC%8F%E7%B3%BB%E7%BB%9F/:1:0","tags":null,"title":"Vue 3 响应式系统","uri":"/posts/vue-3-%E5%93%8D%E5%BA%94%E5%BC%8F%E7%B3%BB%E7%BB%9F/#概述"},{"categories":["网页开发"],"collections":["前端"],"content":"7.1 创建项目\r使用以下命令创建一个 Vue 项目 npm create vue@latest 根据脚手架可以一步步生成项目： # 请输入项目名称: project-name # 请选择要包含的功能： TypeScript JSX 支持 Router (单页面应用开发) Pinia (状态管理) Vitest (单元测试) 端到端测试 ESLint (错误预防) Prettier (代码格式化) ","date":"2026-01-08","objectID":"/posts/vue-3-%E5%93%8D%E5%BA%94%E5%BC%8F%E7%B3%BB%E7%BB%9F/:1:1","tags":null,"title":"Vue 3 响应式系统","uri":"/posts/vue-3-%E5%93%8D%E5%BA%94%E5%BC%8F%E7%B3%BB%E7%BB%9F/#创建项目"},{"categories":["网页开发"],"collections":["前端"],"content":"7.2 项目组成\r项目创建时会生成 index.html，这便是网页的入口，在 index.html 中会创建一个 id 为 app 的 div，并通过 /src/main.ts 向 div 中添加内容。 \u003c!DOCTYPE html\u003e \u003chtml lang=\"\"\u003e \u003chead\u003e \u003cmeta charset=\"UTF-8\"\u003e \u003clink rel=\"icon\" href=\"/favicon.ico\"\u003e \u003cmeta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\"\u003e \u003ctitle\u003eVite App\u003c/title\u003e \u003c/head\u003e \u003cbody\u003e \u003cdiv id=\"app\"\u003e\u003c/div\u003e \u003cscript type=\"module\" src=\"/src/main.ts\"\u003e\u003c/script\u003e \u003c/body\u003e \u003c/html\u003e 在 main. ts 中会创建如下内容，从 vue.js 中加载 createApp 函数，调用该函数，传入 ./App.vue 文件，并将加载的内容挂载到 id 为 app 的 div 中： import { createApp } from 'vue' import App from './App.vue' const app = createApp(App) app.mount('#app') .vue 文件称为组件，在 Vue 3 项目中有大量这样的组件，Vue 3 使用组合式 API，单独定义每个组件中的模板、事件和样式，并将它们组合起来，形成完整的项目。 在单个 .vue 文件中，一般需要定义有如下三个 html 块，在 template 块中定义该组件的模板，使用 html 语言来书写，在 script 块中定义该组件的事件，可以使用 js 或者 ts 来书写，在 style 块中定义该组件的样式，使用 css 样式来书写： \u003ctemplate\u003e\u003c/template\u003e \u003cscript setup lang=\"ts\"\u003e\u003c/script\u003e \u003cstyle scoped\u003e\u003c/style\u003e ","date":"2026-01-08","objectID":"/posts/vue-3-%E5%93%8D%E5%BA%94%E5%BC%8F%E7%B3%BB%E7%BB%9F/:1:2","tags":null,"title":"Vue 3 响应式系统","uri":"/posts/vue-3-%E5%93%8D%E5%BA%94%E5%BC%8F%E7%B3%BB%E7%BB%9F/#项目组成"},{"categories":["网页开发"],"collections":["前端"],"content":"7.3 生命周期\rVue 3 的组件从创建到渲染到移除有如下的生命周期，经历 setup、create、mount、update 和 unmount 几个步骤： setup 是最开始的步骤，对应 \u003cscript setup lang=\"ts\"\u003e\u003c/script\u003e 中的 setup，在这里会初始化组合式 API。 create 对应 const app = createApp(App)，在这里会初始化选项式 API，选项式 API 多在 Vue 2 项目中使用，通过兼容选项式 API，Vue 3 项目实现了对 Vue 2 项目的支持。 mount 对应 app.mount('#app')，实现了页面的渲染，也就是创建和插入 DOM 节点的过程。 update 在数据变化时触发，重新渲染页面。在 setup 和 create 步骤中初始化的 API，对数据进行包装并加入钩子函数，实现数据变化的监听。 unmount 用于在取消挂载后移除页面的渲染。 ","date":"2026-01-08","objectID":"/posts/vue-3-%E5%93%8D%E5%BA%94%E5%BC%8F%E7%B3%BB%E7%BB%9F/:1:3","tags":null,"title":"Vue 3 响应式系统","uri":"/posts/vue-3-%E5%93%8D%E5%BA%94%E5%BC%8F%E7%B3%BB%E7%BB%9F/#生命周期"},{"categories":["网页开发"],"collections":["前端"],"content":"第 8 节 响应式系统\r","date":"2026-01-08","objectID":"/posts/vue-3-%E5%93%8D%E5%BA%94%E5%BC%8F%E7%B3%BB%E7%BB%9F/:2:0","tags":null,"title":"Vue 3 响应式系统","uri":"/posts/vue-3-%E5%93%8D%E5%BA%94%E5%BC%8F%E7%B3%BB%E7%BB%9F/#响应式系统"},{"categories":["网页开发"],"collections":["前端"],"content":"8.1 setup 函数\r在 Vue 3 中，最重要的就是其相应式系统，它实现了数据的双向绑定，在上面的 .vue 文件中，定义了 \u003cscript setup lang=\"ts\"\u003e\u003c/script\u003e，这里的写法是一种“语法糖”，完整的写法如下： \u003ctemplate\u003e {{ count }} \u003c/template\u003e \u003cscript lang=\"ts\"\u003e export default { setup() { const count = 0 return { count } }, } \u003c/script\u003e setup 函数通过 export 导出，便可以通过 import App from './App.vue' 获取，Vue 3 框架调用方法便可以获取返回值 count，并在渲染模板时将 {{ count }} 替换为 count 变量的值。 通过语法糖，上面的代码可以简写为： \u003ctemplate\u003e {{ count }} \u003c/template\u003e \u003cscript setup lang=\"ts\"\u003e import { ref } from 'vue' const count = ref(0) \u003c/script\u003e ","date":"2026-01-08","objectID":"/posts/vue-3-%E5%93%8D%E5%BA%94%E5%BC%8F%E7%B3%BB%E7%BB%9F/:2:1","tags":null,"title":"Vue 3 响应式系统","uri":"/posts/vue-3-%E5%93%8D%E5%BA%94%E5%BC%8F%E7%B3%BB%E7%BB%9F/#setup-函数"},{"categories":["网页开发"],"collections":["前端"],"content":"8.2 ref 函数\r上面的代码中用到了 ref 函数，ref 函数对其包裹的变量进行包装，函数会创建一个名为 RefImpl 的对象，对象的 value 就是包裹的变量。 Vue 3 通过 RefImpl 的 getter 和 setter 函数，实现数据的双向绑定，监听到数据变化时，重新渲染页面的 DOM 节点；DOM 节点变化时，调用 setter 函数修改 RefImpl 的值。 \u003ctemplate\u003e {{ count }} \u003c/template\u003e \u003cscript setup lang=\"ts\"\u003e import { ref } from 'vue' const count = ref(0) console.log(count.value) \u003c/script\u003e 在 templtae 中使用 RefImpl 对象时，不需要输入 .value，Vue 3 会对 RefImpl 对象自动解包，而在 script 中，获取 RefImpl 的值需要 .value，不会自动解包。 ","date":"2026-01-08","objectID":"/posts/vue-3-%E5%93%8D%E5%BA%94%E5%BC%8F%E7%B3%BB%E7%BB%9F/:2:2","tags":null,"title":"Vue 3 响应式系统","uri":"/posts/vue-3-%E5%93%8D%E5%BA%94%E5%BC%8F%E7%B3%BB%E7%BB%9F/#ref-函数"},{"categories":["网页开发"],"collections":["前端"],"content":"8.3 reactive 函数\r对对象类型数据进行双向绑定时，除了可以使用 ref 函数外还可以使用 reactive 函数： \u003ctemplate\u003e \u003cdiv\u003e姓名:{{ person.name }}\u003c/div\u003e \u003cdiv\u003e智力:{{ person.ability.intelligence }}\u003c/div\u003e \u003cdiv\u003e武器:{{ person.equipments[0] }}\u003c/div\u003e \u003c/template\u003e \u003cscript setup lang=\"ts\"\u003e import { reactive } from 'vue' const person = reactive({ name: '张三', ability: { strength: 80, intelligence: 90, }, equipments: ['拳套', '轻甲', '帽子'], }) console.log(person.name) console.log(person.ability.strength) console.log(person.equipments[0]) \u003c/script\u003e reactive 函数会创建 Proxy 对象，通过 Proxy 对象实现数据的双向绑定。在 script 中获取 Proxy 的值不需要 .value。 ref 函数也可以用于对象类型数据，这种情况下 RefImpl 的 value 就是 Proxy 对象，使用 ref 函数创建对象类型数据的包装类时，在 script 中获取 RefImpl 的值仍然是需要 .value 的。 ","date":"2026-01-08","objectID":"/posts/vue-3-%E5%93%8D%E5%BA%94%E5%BC%8F%E7%B3%BB%E7%BB%9F/:2:3","tags":null,"title":"Vue 3 响应式系统","uri":"/posts/vue-3-%E5%93%8D%E5%BA%94%E5%BC%8F%E7%B3%BB%E7%BB%9F/#reactive-函数"},{"categories":["网页开发"],"collections":["前端"],"content":"8.4 toRefs 与 toRef 函数\r有时候我们需要对 RefImpl 或者 Proxy 进行解包，取出其中的每一个元素，并进行修改，如果我们这样写： const person = reactive({ name: '张三', ability: { strength: 80, intelligence: 90, }, equipments: ['拳套', '轻甲', '帽子'], }) let { name, ability } = person console.log(name) console.log(ability) name = '李四' ability.intelligence = 70 console.log(person.name) console.log(person.ability.intelligence) 就会发现基本类型数据解包后失去了与原 Proxy 的关联，而对象类型数据还保留了关联。 toRefs 函数就是用来解决这一问题的： const {name, ability} = toRefs(person) console.log(name) console.log(ability) 可以看到，不管是基本类型数据还是对象类型数据，都被转成了 ObjectRefImpl 对象，和原 Proxy 保留了关联。 如果 person 是 RefImpl 类型，需要这样写： const { name, ability } = toRefs(person.value) console.log(name) console.log(ability) 返回值同样是 ObjectRefImpl 对象。 信息\r这里需要注意的是，如果使用 ref 函数包裹深层对象，那么使用 toRefs 进行解包后每一层的对象依然是 RefImpl 类型，在语法上具有一致性，对象不会中途变成 Proxy 类型。那么如果总是使用 ref 函数，那么 Proxy 类型就不会出现。所以不使用 reactive 函数，而是全部用 ref 来替代，就是一种常见的偷懒手段。 toRef 函数使用方式类似，可以解包某一特定的元素： const name = toRef(person.value, 'name') console.log(name) ","date":"2026-01-08","objectID":"/posts/vue-3-%E5%93%8D%E5%BA%94%E5%BC%8F%E7%B3%BB%E7%BB%9F/:2:4","tags":null,"title":"Vue 3 响应式系统","uri":"/posts/vue-3-%E5%93%8D%E5%BA%94%E5%BC%8F%E7%B3%BB%E7%BB%9F/#torefs-与-toref-函数"},{"categories":["网页开发"],"collections":["前端"],"content":"8.5 计算属性\r如果一个属性不是从页面或者后端获取到，而是通过其他属性的值计算出来的，就可以使用计算属性来定义一个全新的属性： \u003ctemplate\u003e {{ fullName }}\u003cbr /\u003e {{ fullName }}\u003cbr /\u003e {{ fullName }}\u003cbr /\u003e \u003c/template\u003e \u003cscript setup lang=\"ts\"\u003e import { ref, computed } from 'vue' const firstName = ref('zhang') const lastName = ref('san') const fullName = computed(() =\u003e { console.log(1) return `${firstName.value} ${lastName.value}` }) \u003c/script\u003e computed 有 getter 和 setter 方法，上面代码中直接用箭头函数定义计算方法，属于是重写了 getter 方法，computed 函数相比于普通函数的优势在于，其能监听它自身 getter 方法内部其他的 getter 方法的调用（比如 firstName. value），当 firstName 和 lastName 没有变化时，即便调用多次 {{ fullName }}，computed 的 getter 方法只会返回上一次的结果，而不会重新计算。也就是，上面的 console.log(1) 只会输出一次。 getter 方法中可以获取上一次的值，方法是在箭头函数中增加对应的参数名： const fullName = computed((previous) =\u003e {}) computed 还可以设置 setter 方法，当 setter 方法调用时，可以修改其他属性的值。 const fullName = computed({ get() { return `${firstName.value} ${lastName.value}` }, set(newValue: string) { const [first = '', last = ''] = newValue.split(' ') firstName.value = first lastName.value = last }, }) ","date":"2026-01-08","objectID":"/posts/vue-3-%E5%93%8D%E5%BA%94%E5%BC%8F%E7%B3%BB%E7%BB%9F/:2:5","tags":null,"title":"Vue 3 响应式系统","uri":"/posts/vue-3-%E5%93%8D%E5%BA%94%E5%BC%8F%E7%B3%BB%E7%BB%9F/#计算属性"},{"categories":["网页开发"],"collections":["前端"],"content":"8.6 属性绑定\rtemplate 中 DOM 节点的属性可以绑定 script 中的变量 ID 绑定\r使用 v-bind:id 来进行 id 的绑定，这样 div 的 id 就设置成了 container： \u003ctemplate\u003e \u003cdiv v-bind:id=\"id\"\u003e\u003c/div\u003e \u003c/template\u003e \u003cscript setup lang=\"ts\"\u003e import { ref } from 'vue' const id = ref('container') \u003c/script\u003e 因为 v-bind 非常常用，Vue 3 对 v-bind 提供了简写语法： \u003ctemplate\u003e \u003cdiv :id=\"id\"\u003e\u003c/div\u003e \u003c/template\u003e 这里非常特殊，属性名称和 script 中的变量名相同，Vue 3 提供了进一步的简化语法： \u003ctemplate\u003e \u003cdiv :id\u003e\u003c/div\u003e \u003c/template\u003e class 绑定\r:class 和 class 可以同时存在，Vue 3 会自动将它们合并，大多数的布局、外观的定义可以放到 class 中，少数状态可以放到 :class 中。语法上 :class 可以传入的数据非常灵活，可以是字符串、对象、数组，也可以是它们的组合： \u003ctemplate\u003e \u003cdiv :class=\"div_class\" class=\"text-white bg-red-500\"\u003e\u003c/div\u003e \u003c/template\u003e \u003cscript setup lang=\"ts\"\u003e import { ref } from 'vue' const div_class = ref([ { active: true, }, 'opacity-50', ['px-2', 'py-2'], ]) \u003c/script\u003e style 绑定\r:style 和 style 也可以同时存在，也会自动合并，语法上，:style 可以传入的数据同样非常灵活： \u003ctemplate\u003e \u003cdiv :style=\"style\"\u003e\u003c/div\u003e \u003c/template\u003e \u003cscript setup lang=\"ts\"\u003e import { ref } from 'vue' const style = ref([ { color: 'red', fontSize: '30px', }, ]) \u003c/script\u003e 信息\r其他的 HTML 标签的属性，都可以通过前面加 : 的方式转变为 Vue 3 中可绑定的属性，两者的区别在于，不加 : 的属性是 HTML 标签的属性，会当成普通字符串进行解析，而加了 : 的属性会作为变量或者表达式进行解析，Vue 3 会获取其值并渲染到 DOM 元素中。 绑定多个属性\r使用 v-bind 可以一次绑定多个属性： \u003ctemplate\u003e \u003cdiv v-bind=\"attr\"\u003e\u003c/div\u003e \u003c/template\u003e \u003cscript setup lang=\"ts\"\u003e import { ref } from 'vue' const attr = ref({ id: 'container', class: 'wrapper', style: 'background-color:green', }) \u003c/script\u003e ","date":"2026-01-08","objectID":"/posts/vue-3-%E5%93%8D%E5%BA%94%E5%BC%8F%E7%B3%BB%E7%BB%9F/:2:6","tags":null,"title":"Vue 3 响应式系统","uri":"/posts/vue-3-%E5%93%8D%E5%BA%94%E5%BC%8F%E7%B3%BB%E7%BB%9F/#属性绑定"},{"categories":["网页开发"],"collections":["前端"],"content":"8.6 属性绑定\rtemplate 中 DOM 节点的属性可以绑定 script 中的变量 ID 绑定\r使用 v-bind:id 来进行 id 的绑定，这样 div 的 id 就设置成了 container： 因为 v-bind 非常常用，Vue 3 对 v-bind 提供了简写语法： 这里非常特殊，属性名称和 script 中的变量名相同，Vue 3 提供了进一步的简化语法： class 绑定\r:class 和 class 可以同时存在，Vue 3 会自动将它们合并，大多数的布局、外观的定义可以放到 class 中，少数状态可以放到 :class 中。语法上 :class 可以传入的数据非常灵活，可以是字符串、对象、数组，也可以是它们的组合： style 绑定\r:style 和 style 也可以同时存在，也会自动合并，语法上，:style 可以传入的数据同样非常灵活： 信息\r其他的 HTML 标签的属性，都可以通过前面加 : 的方式转变为 Vue 3 中可绑定的属性，两者的区别在于，不加 : 的属性是 HTML 标签的属性，会当成普通字符串进行解析，而加了 : 的属性会作为变量或者表达式进行解析，Vue 3 会获取其值并渲染到 DOM 元素中。 绑定多个属性\r使用 v-bind 可以一次绑定多个属性： ","date":"2026-01-08","objectID":"/posts/vue-3-%E5%93%8D%E5%BA%94%E5%BC%8F%E7%B3%BB%E7%BB%9F/:2:6","tags":null,"title":"Vue 3 响应式系统","uri":"/posts/vue-3-%E5%93%8D%E5%BA%94%E5%BC%8F%E7%B3%BB%E7%BB%9F/#id-绑定"},{"categories":["网页开发"],"collections":["前端"],"content":"8.6 属性绑定\rtemplate 中 DOM 节点的属性可以绑定 script 中的变量 ID 绑定\r使用 v-bind:id 来进行 id 的绑定，这样 div 的 id 就设置成了 container： 因为 v-bind 非常常用，Vue 3 对 v-bind 提供了简写语法： 这里非常特殊，属性名称和 script 中的变量名相同，Vue 3 提供了进一步的简化语法： class 绑定\r:class 和 class 可以同时存在，Vue 3 会自动将它们合并，大多数的布局、外观的定义可以放到 class 中，少数状态可以放到 :class 中。语法上 :class 可以传入的数据非常灵活，可以是字符串、对象、数组，也可以是它们的组合： style 绑定\r:style 和 style 也可以同时存在，也会自动合并，语法上，:style 可以传入的数据同样非常灵活： 信息\r其他的 HTML 标签的属性，都可以通过前面加 : 的方式转变为 Vue 3 中可绑定的属性，两者的区别在于，不加 : 的属性是 HTML 标签的属性，会当成普通字符串进行解析，而加了 : 的属性会作为变量或者表达式进行解析，Vue 3 会获取其值并渲染到 DOM 元素中。 绑定多个属性\r使用 v-bind 可以一次绑定多个属性： ","date":"2026-01-08","objectID":"/posts/vue-3-%E5%93%8D%E5%BA%94%E5%BC%8F%E7%B3%BB%E7%BB%9F/:2:6","tags":null,"title":"Vue 3 响应式系统","uri":"/posts/vue-3-%E5%93%8D%E5%BA%94%E5%BC%8F%E7%B3%BB%E7%BB%9F/#class-绑定"},{"categories":["网页开发"],"collections":["前端"],"content":"8.6 属性绑定\rtemplate 中 DOM 节点的属性可以绑定 script 中的变量 ID 绑定\r使用 v-bind:id 来进行 id 的绑定，这样 div 的 id 就设置成了 container： 因为 v-bind 非常常用，Vue 3 对 v-bind 提供了简写语法： 这里非常特殊，属性名称和 script 中的变量名相同，Vue 3 提供了进一步的简化语法： class 绑定\r:class 和 class 可以同时存在，Vue 3 会自动将它们合并，大多数的布局、外观的定义可以放到 class 中，少数状态可以放到 :class 中。语法上 :class 可以传入的数据非常灵活，可以是字符串、对象、数组，也可以是它们的组合： style 绑定\r:style 和 style 也可以同时存在，也会自动合并，语法上，:style 可以传入的数据同样非常灵活： 信息\r其他的 HTML 标签的属性，都可以通过前面加 : 的方式转变为 Vue 3 中可绑定的属性，两者的区别在于，不加 : 的属性是 HTML 标签的属性，会当成普通字符串进行解析，而加了 : 的属性会作为变量或者表达式进行解析，Vue 3 会获取其值并渲染到 DOM 元素中。 绑定多个属性\r使用 v-bind 可以一次绑定多个属性： ","date":"2026-01-08","objectID":"/posts/vue-3-%E5%93%8D%E5%BA%94%E5%BC%8F%E7%B3%BB%E7%BB%9F/:2:6","tags":null,"title":"Vue 3 响应式系统","uri":"/posts/vue-3-%E5%93%8D%E5%BA%94%E5%BC%8F%E7%B3%BB%E7%BB%9F/#style-绑定"},{"categories":["网页开发"],"collections":["前端"],"content":"8.6 属性绑定\rtemplate 中 DOM 节点的属性可以绑定 script 中的变量 ID 绑定\r使用 v-bind:id 来进行 id 的绑定，这样 div 的 id 就设置成了 container： 因为 v-bind 非常常用，Vue 3 对 v-bind 提供了简写语法： 这里非常特殊，属性名称和 script 中的变量名相同，Vue 3 提供了进一步的简化语法： class 绑定\r:class 和 class 可以同时存在，Vue 3 会自动将它们合并，大多数的布局、外观的定义可以放到 class 中，少数状态可以放到 :class 中。语法上 :class 可以传入的数据非常灵活，可以是字符串、对象、数组，也可以是它们的组合： style 绑定\r:style 和 style 也可以同时存在，也会自动合并，语法上，:style 可以传入的数据同样非常灵活： 信息\r其他的 HTML 标签的属性，都可以通过前面加 : 的方式转变为 Vue 3 中可绑定的属性，两者的区别在于，不加 : 的属性是 HTML 标签的属性，会当成普通字符串进行解析，而加了 : 的属性会作为变量或者表达式进行解析，Vue 3 会获取其值并渲染到 DOM 元素中。 绑定多个属性\r使用 v-bind 可以一次绑定多个属性： ","date":"2026-01-08","objectID":"/posts/vue-3-%E5%93%8D%E5%BA%94%E5%BC%8F%E7%B3%BB%E7%BB%9F/:2:6","tags":null,"title":"Vue 3 响应式系统","uri":"/posts/vue-3-%E5%93%8D%E5%BA%94%E5%BC%8F%E7%B3%BB%E7%BB%9F/#绑定多个属性"},{"categories":["网页开发"],"collections":["前端"],"content":"8.7 条件渲染\rv-if、v-else-if、v-else 指令用于条件渲染，只有当对应的表达式为真时，对应的 DOM 节点才会渲染，否则不会出现在页面中。 \u003ctemplate\u003e \u003cdiv v-if=\"flag1\"\u003e1\u003c/div\u003e \u003cdiv v-else-if=\"flag2\"\u003e2\u003c/div\u003e \u003cdiv v-else\u003e3\u003c/div\u003e \u003cdiv v-show=\"flag1\"\u003e2\u003c/div\u003e \u003c/template\u003e \u003cscript setup lang=\"ts\"\u003e import { ref } from 'vue' const flag1 = ref(false) const flag2 = ref(true) \u003c/script\u003e v-show 功能类似，区别在于 v-if、v-else-if、v-else 在条件为 false 的时候不会渲染，只有当条件为 true 时才会渲染，而 v-show 无论条件是否为 true 都会渲染，但是条件为 false 时，display: none。 ","date":"2026-01-08","objectID":"/posts/vue-3-%E5%93%8D%E5%BA%94%E5%BC%8F%E7%B3%BB%E7%BB%9F/:2:7","tags":null,"title":"Vue 3 响应式系统","uri":"/posts/vue-3-%E5%93%8D%E5%BA%94%E5%BC%8F%E7%B3%BB%E7%BB%9F/#条件渲染"},{"categories":["网页开发"],"collections":["前端"],"content":"8.8 列表渲染\r对于可遍历的对象，可以使用 v-for 方法获取其每一个元素： \u003ctemplate\u003e \u003cli v-for=\"(value, key) in items\" :key=\"key\"\u003e{{ key }}:{{ value }}\u003c/li\u003e \u003c/template\u003e \u003cscript setup lang=\"ts\"\u003e import { ref } from 'vue' const items = ref({ 姓名: '张三', 年龄: '18', 性别: '男', }) \u003c/script\u003e ","date":"2026-01-08","objectID":"/posts/vue-3-%E5%93%8D%E5%BA%94%E5%BC%8F%E7%B3%BB%E7%BB%9F/:2:8","tags":null,"title":"Vue 3 响应式系统","uri":"/posts/vue-3-%E5%93%8D%E5%BA%94%E5%BC%8F%E7%B3%BB%E7%BB%9F/#列表渲染"},{"categories":["网页开发"],"collections":["前端"],"content":"8.9 事件处理\r可以使用 v-on 指令来监听 DOM 事件，并在事件触发时执行对应的 JavaScript。 \u003ctemplate\u003e \u003cdiv\u003e当前点击次数: {{ count }}\u003c/div\u003e \u003cbutton v-on:click=\"handleClick\"\u003e点击\u003c/button\u003e \u003c/template\u003e \u003cscript setup lang=\"ts\"\u003e import { ref } from 'vue' const count = ref(0) const handleClick = () =\u003e { count.value++ } \u003c/script\u003e v-on 指令可以简写为 @： \u003ctemplate\u003e \u003cdiv\u003e当前点击次数: {{ count }}\u003c/div\u003e \u003cbutton @click=\"handleClick\"\u003e点击\u003c/button\u003e \u003c/template\u003e \u003cscript setup lang=\"ts\"\u003e import { ref } from 'vue' const count = ref(0) const handleClick = () =\u003e { count.value++ } \u003c/script\u003e 事件修饰符\r一般 DOM 事件会经历 3 个阶段： 捕获阶段：window / document -\u003e ... -\u003e target.parent 目标阶段：event.target 冒泡阶段：parent -\u003e ... -\u003e document / window 为了简化处理 DOM 事件的细节，Vue 3 提供了事件修饰符： 修饰符 作用 常见用法 .stop 阻止冒泡 内部按钮，不想触发父级点击 .prevent 阻止默认行为 阻止表单刷新，阻止 \u003ca\u003e 跳转 .self 只响应自身 避免子元素误触 .capture 捕获阶段 全局拦截 .once 只触发一次 一次性操作，防止重复提交 .passive 性能优化 不会调用 preventDefault()，不能和 .prevent 一起用 事件修饰符的使用方法如下： \u003ctemplate\u003e \u003cdiv @click=\"parentClick\"\u003e \u003cbutton @click.stop=\"childClick\"\u003e点我\u003c/button\u003e \u003c/div\u003e \u003c/template\u003e \u003cscript setup lang=\"ts\"\u003e const parentClick = () =\u003e { console.log('parentClick') } const childClick = () =\u003e { console.log('childClick') } \u003c/script\u003e 按键修饰符\r按键修饰符包括鼠标按键和键盘按键： 鼠标按键修饰符有 .left 、.right 和 middle 键盘按键修饰符比较多，比如有修饰键 .ctrl、.alt、.shift 和 .meta，以及一些没有实体字符的按键 .esc 、.tab 、.space、delete (捕获 Delete 和 Backspace 两个按键)、.up、.down、.left、.right 比如可以有如下用法： \u003cinput @keyup.enter=\"submit\" /\u003e \u003ctextarea @keyup.ctrl.enter=\"send\" /\u003e \u003cdiv @click.right.prevent=\"openMenu\" /\u003e \u003cinput @keydown.alt.a.exact=\"onAltA\" /\u003e 如果需要精确控制触发事件的修饰键，可以使用 .exact： \u003c!--当按下Ctrl时,即使同时按下Alt或Shift也会触发--\u003e \u003cbutton @click.ctrl=\"onClick\"\u003eA\u003c/button\u003e \u003c!--仅当按下Ctrl且未按任何其他键时才会触发--\u003e \u003cbutton @click.ctrl.exact=\"onCtrlClick\"\u003eA\u003c/button\u003e \u003c!--仅当没有按下任何系统按键时触发--\u003e \u003cbutton @click.exact=\"onClick\"\u003eA\u003c/button\u003e ","date":"2026-01-08","objectID":"/posts/vue-3-%E5%93%8D%E5%BA%94%E5%BC%8F%E7%B3%BB%E7%BB%9F/:2:9","tags":null,"title":"Vue 3 响应式系统","uri":"/posts/vue-3-%E5%93%8D%E5%BA%94%E5%BC%8F%E7%B3%BB%E7%BB%9F/#事件处理"},{"categories":["网页开发"],"collections":["前端"],"content":"8.9 事件处理\r可以使用 v-on 指令来监听 DOM 事件，并在事件触发时执行对应的 JavaScript。 当前点击次数: {{ count }} 点击 v-on 指令可以简写为 @： 当前点击次数: {{ count }} 点击 事件修饰符\r一般 DOM 事件会经历 3 个阶段： 捕获阶段：window / document -\u003e ... -\u003e target.parent 目标阶段：event.target 冒泡阶段：parent -\u003e ... -\u003e document / window 为了简化处理 DOM 事件的细节，Vue 3 提供了事件修饰符： 修饰符 作用 常见用法 .stop 阻止冒泡 内部按钮，不想触发父级点击 .prevent 阻止默认行为 阻止表单刷新，阻止 跳转 .self 只响应自身 避免子元素误触 .capture 捕获阶段 全局拦截 .once 只触发一次 一次性操作，防止重复提交 .passive 性能优化 不会调用 preventDefault()，不能和 .prevent 一起用 事件修饰符的使用方法如下： 点我 按键修饰符\r按键修饰符包括鼠标按键和键盘按键： 鼠标按键修饰符有 .left 、.right 和 middle 键盘按键修饰符比较多，比如有修饰键 .ctrl、.alt、.shift 和 .meta，以及一些没有实体字符的按键 .esc 、.tab 、.space、delete (捕获 Delete 和 Backspace 两个按键)、.up、.down、.left、.right 比如可以有如下用法： \u003cdiv @click.right.prevent=\"openMenu\" /\u003e \u003cinput @keydown.alt.a.exact=\"onAltA\" /\u003e 如果需要精确控制触发事件的修饰键，可以使用 .exact： \u003c!--当按下Ctrl时,即使同时按下Alt或Shift也会触发--\u003e \u003cbutton @click.ctrl=\"onClick\"\u003eA\u003c/button\u003e \u003c!--仅当按下Ctrl且未按任何其他键时才会触发--\u003e \u003cbutton @click.ctrl.exact=\"onCtrlClick\"\u003eA\u003c/button\u003e \u003c!--仅当没有按下任何系统按键时触发--\u003e \u003cbutton @click.exact=\"onClick\"\u003eA\u003c/button\u003e ","date":"2026-01-08","objectID":"/posts/vue-3-%E5%93%8D%E5%BA%94%E5%BC%8F%E7%B3%BB%E7%BB%9F/:2:9","tags":null,"title":"Vue 3 响应式系统","uri":"/posts/vue-3-%E5%93%8D%E5%BA%94%E5%BC%8F%E7%B3%BB%E7%BB%9F/#事件修饰符"},{"categories":["网页开发"],"collections":["前端"],"content":"8.9 事件处理\r可以使用 v-on 指令来监听 DOM 事件，并在事件触发时执行对应的 JavaScript。 当前点击次数: {{ count }} 点击 v-on 指令可以简写为 @： 当前点击次数: {{ count }} 点击 事件修饰符\r一般 DOM 事件会经历 3 个阶段： 捕获阶段：window / document -\u003e ... -\u003e target.parent 目标阶段：event.target 冒泡阶段：parent -\u003e ... -\u003e document / window 为了简化处理 DOM 事件的细节，Vue 3 提供了事件修饰符： 修饰符 作用 常见用法 .stop 阻止冒泡 内部按钮，不想触发父级点击 .prevent 阻止默认行为 阻止表单刷新，阻止 跳转 .self 只响应自身 避免子元素误触 .capture 捕获阶段 全局拦截 .once 只触发一次 一次性操作，防止重复提交 .passive 性能优化 不会调用 preventDefault()，不能和 .prevent 一起用 事件修饰符的使用方法如下： 点我 按键修饰符\r按键修饰符包括鼠标按键和键盘按键： 鼠标按键修饰符有 .left 、.right 和 middle 键盘按键修饰符比较多，比如有修饰键 .ctrl、.alt、.shift 和 .meta，以及一些没有实体字符的按键 .esc 、.tab 、.space、delete (捕获 Delete 和 Backspace 两个按键)、.up、.down、.left、.right 比如可以有如下用法： 如果需要精确控制触发事件的修饰键，可以使用 .exact： A A A ","date":"2026-01-08","objectID":"/posts/vue-3-%E5%93%8D%E5%BA%94%E5%BC%8F%E7%B3%BB%E7%BB%9F/:2:9","tags":null,"title":"Vue 3 响应式系统","uri":"/posts/vue-3-%E5%93%8D%E5%BA%94%E5%BC%8F%E7%B3%BB%E7%BB%9F/#按键修饰符"},{"categories":["网页开发"],"collections":["前端"],"content":"8.10 表单绑定\r要实现表单的双向绑定，原本需要进行绑定值并设定监听事件： \u003cinput :value=\"text\" @input=\"event =\u003e text = event.target.value\"\u003e 在 Vue 3 中可以使用 v-model 来简化这一步骤： \u003cinput v-model=\"text\"\u003e 比如说，单行文本框可以如下： \u003cp\u003eMessage is: {{ message }}\u003c/p\u003e \u003cinput v-model=\"message\" placeholder=\"edit me\" /\u003e 信息\r其他类型的表单基本相似，在通常的 HTML 表单基础上增加 v-model 属性，即可实现数据的双向绑定。 表单有几个特殊的修饰符： .number 可以将用户输入自动转换为数字 \u003cinput v-model.number=\"age\" /\u003e .trim 可以自动去除用户输入内容中两端的空格 \u003cinput v-model.trim=\"msg\" /\u003e ","date":"2026-01-08","objectID":"/posts/vue-3-%E5%93%8D%E5%BA%94%E5%BC%8F%E7%B3%BB%E7%BB%9F/:2:10","tags":null,"title":"Vue 3 响应式系统","uri":"/posts/vue-3-%E5%93%8D%E5%BA%94%E5%BC%8F%E7%B3%BB%E7%BB%9F/#表单绑定"},{"categories":["网页开发"],"collections":["前端"],"content":"8.11 侦听器\r侦听器可以监测某一个 script 变量的变化情况，并且当变量值发生改变时，调用回调函数，在下面的例子中，watch 接收 3 个参数，第一个是需要侦听的变量或者变量的 getter 函数，第二个是回调函数，第三个是可选参数。 \u003ctemplate\u003e \u003cdiv\u003e当前点击次数: {{ count }}\u003c/div\u003e \u003cbutton v-on:click=\"handleClick\"\u003e点击\u003c/button\u003e \u003c/template\u003e \u003cscript setup lang=\"ts\"\u003e import { ref, watch } from 'vue' const count = ref(0) const handleClick = () =\u003e { count.value++ } watch( count, (newValue, oldValue) =\u003e { console.log('count变化了', newValue, oldValue) }, { immediate: true }, ) \u003c/script\u003e 如果需要侦听的变量是 RefImpl 类型的对象类型数据（使用 ref 函数创建），那么其 value 为 Proxy ，当数据变化时，Proxy 不会变化（除非整个替换 person.value = { name: '李四', age: 20 }），此时就需要使用深层侦听器，方法是在可选参数中增加 { deep: true }： const person = ref({ name: '张三', age: 18, }) const handleClick = () =\u003e { person.value.name = '李四' person.value.age = 20 } watch( person, (value) =\u003e { console.log('person变化了', value) }, { deep: true }, ) 对象类型数据为 Proxy 时（使用 reactive 函数创建），无需使用深层侦听器。 通常 watch 默认是懒执行的，仅当数据源变化时，才会执行回调。如果我们需要在创建侦听器时，立即执行一遍回调，需要在可选参数中增加 { immediate: true }。 如果希望回调只在源变化时触发一次，可以在可选参数中增加 { once: true }。 如果我们想要侦听对象类型数据中的某一个特定的值，可以使用如下方式： watch( () =\u003e person.value.name, (value) =\u003e { console.log('name变化了', value) }, ) 原理是传入一个 getter 函数，这样做有两个原因： 一是如果 name 是基本类型数据，watch 需要侦听其数值的变化（需要传入的参数有 getter 函数），上述方式可以构建出 getter 函数。 二是，如果 name 是对象类型数据，虽然 RefImpl 和 Proxy 类型本身有 getter 函数，但是可能被整个替换，导致 person.value.name 的地址指向新的 RefImpl 或者 Proxy 对象，如果直接传入 name，则可能依旧侦听原来的地址。 此外，Vue 3 提供了函数 watchEffect，可以简化 watch 的使用。watchEffect 的作用是立即运行一个函数，同时响应式地追踪其依赖，并在依赖更改时重新执行该函数。比如下面这个函数，watchEffect 可以自动追踪 name 和 age 的变化。 watchEffect(() =\u003e { person.value.name = person.value.age + '岁的' + person.value.name }) ","date":"2026-01-08","objectID":"/posts/vue-3-%E5%93%8D%E5%BA%94%E5%BC%8F%E7%B3%BB%E7%BB%9F/:2:11","tags":null,"title":"Vue 3 响应式系统","uri":"/posts/vue-3-%E5%93%8D%E5%BA%94%E5%BC%8F%E7%B3%BB%E7%BB%9F/#侦听器"},{"categories":["网页开发"],"collections":["前端"],"content":"第 19 节 概述\rTypeScript 由微软开发，是基于 JavaScript 的一个扩展语言。TypeScript 中增加了静态类型检查、接口、泛型等现代开发特性，更适合大型项目的开发。 TypeScript 无法直接在浏览器中运行，需要先编译为 JavaScript，编译前需要安装环境： # 全局安装 typescript npm install typescript -g 可以对单个文件进行编译： tsc index.ts 也可以设置为自动化编译： # 创建编译控制⽂件 tsconfig.json tsc --init # 监视⽬录中的 .ts ⽂件变化 tsc --watch 一般来说，现代的前端框架会自动对 TypeScript 进行编译。我们无需自己编译 TypeScript 文件。 ","date":"2026-01-05","objectID":"/posts/typescript-%E7%B1%BB%E5%9E%8B/:1:0","tags":null,"title":"TypeScript 基础知识","uri":"/posts/typescript-%E7%B1%BB%E5%9E%8B/#概述"},{"categories":["网页开发"],"collections":["前端"],"content":"第 20 节 基础类型\r最简单的类型，通常作为更复杂类型的组成部分，分为字符串、数值和布尔值三类。 let name: string = \"Tom\" let age: number = 18 let isLogin: boolean = true 变量的取值严格限制为某一个确定值时，可以使用字面量类型，让“值本身”成为类型的一部分。 let direction: \"left\" = \"left\" ","date":"2026-01-05","objectID":"/posts/typescript-%E7%B1%BB%E5%9E%8B/:2:0","tags":null,"title":"TypeScript 基础知识","uri":"/posts/typescript-%E7%B1%BB%E5%9E%8B/#基础类型"},{"categories":["网页开发"],"collections":["前端"],"content":"第 21 节 拼接类型\r拼接类型是由多个类型拼接而成的复杂类型。 ","date":"2026-01-05","objectID":"/posts/typescript-%E7%B1%BB%E5%9E%8B/:3:0","tags":null,"title":"TypeScript 基础知识","uri":"/posts/typescript-%E7%B1%BB%E5%9E%8B/#拼接类型"},{"categories":["网页开发"],"collections":["前端"],"content":"21.1 类型推断\rTypeScript 会对变量的类型进行推断，即便我们不写变量类型，也会进行静态类型检查。 // TypeScript 会推断出变量 a 的类型是数值 let a = 10 在我们写拼接类型时，可以依据类型推断，不影响语义的同时，简化变量的申明步骤。 ","date":"2026-01-05","objectID":"/posts/typescript-%E7%B1%BB%E5%9E%8B/:3:1","tags":null,"title":"TypeScript 基础知识","uri":"/posts/typescript-%E7%B1%BB%E5%9E%8B/#类型推断"},{"categories":["网页开发"],"collections":["前端"],"content":"21.2 函数类型\r// 可以在申明时指定数据类型 let count: (a: number, b: number) =\u003e number count = function (x, y) { return x + y } // 也可以在创建时指定数据类型 let count = function (x: number, y: number): number { return x + y } ","date":"2026-01-05","objectID":"/posts/typescript-%E7%B1%BB%E5%9E%8B/:3:2","tags":null,"title":"TypeScript 基础知识","uri":"/posts/typescript-%E7%B1%BB%E5%9E%8B/#函数类型"},{"categories":["网页开发"],"collections":["前端"],"content":"21.3 对象类型\rtype 可以为任意类型创建别名，可以方便地进行类型复用和扩展。适用于定义需要多次使用的类型。 // 必须有 name 属性,age 为可选属性 type Person = { name: string, age?: number } let person: Person = {name: '张三'} ","date":"2026-01-05","objectID":"/posts/typescript-%E7%B1%BB%E5%9E%8B/:3:3","tags":null,"title":"TypeScript 基础知识","uri":"/posts/typescript-%E7%B1%BB%E5%9E%8B/#对象类型"},{"categories":["网页开发"],"collections":["前端"],"content":"21.4 联合类型\r表示允许取几种类型中的任意一种，比如“字面量”就是一种特殊的类型： // 性别这个类型就比较常用，定义为男或女 type Gender = '男' | '女'; let gender: Gender = '男' ","date":"2026-01-05","objectID":"/posts/typescript-%E7%B1%BB%E5%9E%8B/:3:4","tags":null,"title":"TypeScript 基础知识","uri":"/posts/typescript-%E7%B1%BB%E5%9E%8B/#联合类型"},{"categories":["网页开发"],"collections":["前端"],"content":"21.5 交叉类型\r表示要满足全部的要求。 //⾯积 type Area = { height: number; //⾼ width: number; //宽 }; //地址 type Address = { num: number; //楼号 cell: number; //单元号 room: string; //房间号 }; // 定义类型 House ,且 House 是 Area 和 Address 组成的交叉类型 type House = Area \u0026 Address; const house: House = { height: 180, width: 75, num: 6, cell: 3, room: '702' }; ","date":"2026-01-05","objectID":"/posts/typescript-%E7%B1%BB%E5%9E%8B/:3:5","tags":null,"title":"TypeScript 基础知识","uri":"/posts/typescript-%E7%B1%BB%E5%9E%8B/#交叉类型"},{"categories":["网页开发"],"collections":["前端"],"content":"21.6 枚举类型\r枚举类型用来表示有限的、固定的类型。 enum Direction { Up, Down, Left, Right, } function walk(n: Direction) { if (n === Direction.Up) { console.log(\"向上⾛\"); } else if (n === Direction.Down) { console.log(\"向下⾛\"); } else if (n === Direction.Left) { console.log(\"向左⾛\"); } else if (n === Direction.Right) { console.log(\"向右⾛\"); } else { console.log(\"未知⽅向\"); } } walk(Direction.Up) let x = Direction.Down ","date":"2026-01-05","objectID":"/posts/typescript-%E7%B1%BB%E5%9E%8B/:3:6","tags":null,"title":"TypeScript 基础知识","uri":"/posts/typescript-%E7%B1%BB%E5%9E%8B/#枚举类型"},{"categories":["网页开发"],"collections":["前端"],"content":"21.7 数组类型\r数组可以直接定义，也可以通过泛型方式进行定义： let arr1: string[] let arr2: Array\u003cstring\u003e arr1 = ['a','b','c'] arr2 = ['hello','world'] ","date":"2026-01-05","objectID":"/posts/typescript-%E7%B1%BB%E5%9E%8B/:3:7","tags":null,"title":"TypeScript 基础知识","uri":"/posts/typescript-%E7%B1%BB%E5%9E%8B/#数组类型"},{"categories":["网页开发"],"collections":["前端"],"content":"21.8 元组类型\r当一个变量本质上是数组，但每个位置都有明确含义时，使用元组会比普通数组更清晰。 //// 第⼆个元素可选,如果存在,必须是 boolean 类型。 type Count = [number, boolean?] let count: Count = [10, true] ","date":"2026-01-05","objectID":"/posts/typescript-%E7%B1%BB%E5%9E%8B/:3:8","tags":null,"title":"TypeScript 基础知识","uri":"/posts/typescript-%E7%B1%BB%E5%9E%8B/#元组类型"},{"categories":["网页开发"],"collections":["前端"],"content":"第 22 节 输入/输出\r","date":"2026-01-05","objectID":"/posts/typescript-%E7%B1%BB%E5%9E%8B/:4:0","tags":null,"title":"TypeScript 基础知识","uri":"/posts/typescript-%E7%B1%BB%E5%9E%8B/#输入输出"},{"categories":["网页开发"],"collections":["前端"],"content":"22.1 无返回值\rvoid 表示函数无返回值。 function log(msg: string): void { console.log(msg) } ","date":"2026-01-05","objectID":"/posts/typescript-%E7%B1%BB%E5%9E%8B/:4:1","tags":null,"title":"TypeScript 基础知识","uri":"/posts/typescript-%E7%B1%BB%E5%9E%8B/#无返回值"},{"categories":["网页开发"],"collections":["前端"],"content":"22.2 不可信输入\r在处理外部输入时，不清楚其类型，可以从 unknow 开始，先验证再使用。 function handle(input: unknown) { if (typeof input === \"string\") { input.toUpperCase() } } ","date":"2026-01-05","objectID":"/posts/typescript-%E7%B1%BB%E5%9E%8B/:4:2","tags":null,"title":"TypeScript 基础知识","uri":"/posts/typescript-%E7%B1%BB%E5%9E%8B/#不可信输入"},{"categories":["网页开发"],"collections":["前端"],"content":"22.3 穷尽检查\rnever 表示当所有可能情况都被处理完之后，仍然不应该存在的分支，一般由 TypeScript 推断出来，可以借助 never 发现程序中无法执行或者不会有任何返回值（比如抛出异常）的分支。 type Action = \"add\" | \"remove\" function handleAction(a: Action) { switch (a) { case \"add\": return case \"remove\": return default: const _never: never = a } } ","date":"2026-01-05","objectID":"/posts/typescript-%E7%B1%BB%E5%9E%8B/:4:3","tags":null,"title":"TypeScript 基础知识","uri":"/posts/typescript-%E7%B1%BB%E5%9E%8B/#穷尽检查"},{"categories":["网页开发"],"collections":["前端"],"content":"第 23 节 类\r","date":"2026-01-05","objectID":"/posts/typescript-%E7%B1%BB%E5%9E%8B/:5:0","tags":null,"title":"TypeScript 基础知识","uri":"/posts/typescript-%E7%B1%BB%E5%9E%8B/#类"},{"categories":["网页开发"],"collections":["前端"],"content":"23.1 属性修饰符\r修饰符 含义 具体规则 public 公开的 可被类内部、子类、类外部访问。 protected 受保护的 可被类内部、子类访问。 private 私有的 可被类内部访问。 readonly 只读属性 属性无法修改。 ","date":"2026-01-05","objectID":"/posts/typescript-%E7%B1%BB%E5%9E%8B/:5:1","tags":null,"title":"TypeScript 基础知识","uri":"/posts/typescript-%E7%B1%BB%E5%9E%8B/#属性修饰符"},{"categories":["网页开发"],"collections":["前端"],"content":"23.2 类\r在 JavaScript 中类的属性需要在构造函数外部申明，在 TypeScript 中可以只在构造函数中申明一次。 class Person { constructor( // name 为 readonly 属性，无法修改 public readonly name: string, // age 为 protected 属性，只能在类内部和子类中使用 protected age: number, // IDCard 为 private 属性，只能在类内部使用 private IDCard: string ) { } private getPrivateInfo() { // 类内部可以访问 private 属性 return `身份证号码为: ${this.IDCard}` } getInfo() { return `我叫: ${this.name}，今年${this.age}岁`; } } ","date":"2026-01-05","objectID":"/posts/typescript-%E7%B1%BB%E5%9E%8B/:5:2","tags":null,"title":"TypeScript 基础知识","uri":"/posts/typescript-%E7%B1%BB%E5%9E%8B/#类-1"},{"categories":["网页开发"],"collections":["前端"],"content":"23.3 接口\rinterface 为类、对象、函数等定义格式，不能包含任何实现。interface 和 type 的功能十分接近，很多场景下可以互换，区别在于： interface 更专注于定义对象和类的结构，支持继承、合并。 type可以定义类型别名、联合类型、交叉类型，但不支持继承和自动合并。 // 使⽤ interface 定义 Person 对象 interface PersonInterface { name: string; age: number; speak(): void; } // 使⽤ type 定义 Person 对象 type PersonType = { name: string; age: number; speak(): void; }; 接口可以实现继承、合并： interface PersonInterface { name: string; // 姓名 age: number; // 年龄 } ​ interface PersonInterface { speak: () =\u003e void; } ​ interface StudentInterface extends PersonInterface { grade: string; // 年级 } ​ const student: StudentInterface = { name: '李四', age: 18, grade: '高二', speak() { console.log(this.name, this.age, this.grade); } } type 也可以通过交叉类型实现： // 使⽤ type 定义 Person 类型,并通过交叉类型实现属性的合并 type PersonType = { name: string; // 姓名 age: number; // 年龄 } \u0026 { speak: () =\u003e void; }; // 使⽤ type 定义 Student 类型,并通过交叉类型继承 PersonType type StudentType = PersonType \u0026 { grade: string; // 年级 }; const student: StudentType = { name: '李四', age: 18, grade: '⾼⼆', speak() { console.log(this.name, this.age, this.grade); } }; ","date":"2026-01-05","objectID":"/posts/typescript-%E7%B1%BB%E5%9E%8B/:5:3","tags":null,"title":"TypeScript 基础知识","uri":"/posts/typescript-%E7%B1%BB%E5%9E%8B/#接口"},{"categories":["网页开发"],"collections":["前端"],"content":"23.4 抽象类\r如果某一类对象不仅有结构要求，还有一部分共同行为，同时又希望子类必须补充关键逻辑，就可以使用抽象类。抽象类和接口的区别在于： 接口：只能描述结构，不能有任何实现代码，一个类可以实现多个接口。 抽象类：既可以包含抽象方法，也可以包含具体方法，一个类只能继承一个抽象类。 abstract class Animal { move(): void { console.log(\"moving\") } abstract speak(): void } class Dog extends Animal { speak() { console.log(\"wang\") } } ","date":"2026-01-05","objectID":"/posts/typescript-%E7%B1%BB%E5%9E%8B/:5:4","tags":null,"title":"TypeScript 基础知识","uri":"/posts/typescript-%E7%B1%BB%E5%9E%8B/#抽象类"},{"categories":["网页开发"],"collections":["前端"],"content":"第 24 节 泛型\r泛型允许我们在定义函数、类或接口时，使用类型参数来表示未指定的类型，这些参数在具体使用时，才被指定具体的类型。泛型能让同一段代码适用于多种类型。同时仍然保持类型的安全性。 function logData\u003cT, U\u003e(data1: T, data2: U): void { console.log(data1, data2) } logData\u003cnumber, string\u003e(100, 'hello') 可以为泛型设置一定的约束条件： // 泛型约束 TypeScript interface LengthInterface { length: number } // 传入的类型 T 必须具有 length 属性 function logPerson\u003cT extends LengthInterface\u003e(data: T): void { console.log(data.length) } ","date":"2026-01-05","objectID":"/posts/typescript-%E7%B1%BB%E5%9E%8B/:6:0","tags":null,"title":"TypeScript 基础知识","uri":"/posts/typescript-%E7%B1%BB%E5%9E%8B/#泛型"},{"categories":["深度学习"],"collections":["pytorch"],"content":"第 25 节 概述\r在本文节中，使用卷积神经网络，对上一篇的 Fashion-Minist 数据集再次进行分类。 ","date":"2026-01-04","objectID":"/posts/pytorch-%E5%8D%B7%E7%A7%AF%E7%A5%9E%E7%BB%8F%E7%BD%91%E7%BB%9C/:1:0","tags":null,"title":"Pytorch 卷积神经网络","uri":"/posts/pytorch-%E5%8D%B7%E7%A7%AF%E7%A5%9E%E7%BB%8F%E7%BD%91%E7%BB%9C/#概述"},{"categories":["深度学习"],"collections":["pytorch"],"content":"第 26 节 分类\r","date":"2026-01-04","objectID":"/posts/pytorch-%E5%8D%B7%E7%A7%AF%E7%A5%9E%E7%BB%8F%E7%BD%91%E7%BB%9C/:2:0","tags":null,"title":"Pytorch 卷积神经网络","uri":"/posts/pytorch-%E5%8D%B7%E7%A7%AF%E7%A5%9E%E7%BB%8F%E7%BD%91%E7%BB%9C/#分类"},{"categories":["深度学习"],"collections":["pytorch"],"content":"26.1 数据预处理 \u0026 构建数据集\r编写数据预处理函数： ToTensor: 将图像的颜色从 0~255 变为 0~1，并转为张量 [N,C,H,W] Normalize: 对图像进行标准化，颜色从 0~1 变为 -1~1 数据集划分为训练集、验证集和测试集。 import torchvision from torchvision.transforms import Compose, ToTensor, Normalize from torch.utils.data import random_split,DataLoader def load_data_fashion_mnist(batch_size, val_ratio=0.2): trans = Compose([ToTensor(),Normalize((0.5,), (0.5,))]) total_dataset = torchvision.datasets.FashionMNIST( root=\"../data\", train=True, transform=trans, download=True) test_dataset = torchvision.datasets.FashionMNIST( root=\"../data\", train=False, transform=trans, download=True) total_size = len(total_dataset) val_size = int(total_size * val_ratio) train_size = total_size - val_size train_dataset, val_dataset = random_split( total_dataset, [train_size, val_size] ) train_loader = DataLoader( train_dataset, batch_size=batch_size, shuffle=True, num_workers=4, pin_memory=True ) val_loader = DataLoader( val_dataset, batch_size=batch_size, shuffle=False, num_workers=4, pin_memory=True ) test_loader = DataLoader( test_dataset, batch_size=batch_size, shuffle=False, num_workers=4, pin_memory=True ) return (train_loader,val_loader,test_loader) batch_size = 256 train_loader, val_loader, test_loader = load_data_fashion_mnist(batch_size) ","date":"2026-01-04","objectID":"/posts/pytorch-%E5%8D%B7%E7%A7%AF%E7%A5%9E%E7%BB%8F%E7%BD%91%E7%BB%9C/:2:1","tags":null,"title":"Pytorch 卷积神经网络","uri":"/posts/pytorch-%E5%8D%B7%E7%A7%AF%E7%A5%9E%E7%BB%8F%E7%BD%91%E7%BB%9C/#数据预处理--构建数据集"},{"categories":["深度学习"],"collections":["pytorch"],"content":"26.2 构建网络\r使用卷积神经网络构建： 首先使用大小为 3×3 的卷积核，填充 1 像素，卷积后尺寸为 32×28×28，经过最大池化后为 32×14×14 然后使用大小为 3×3 的卷积核，填充 1 像素，卷积后尺寸为 64×14×14，经过最大池化后为 64×7×7 经过展平、两次全连接和 relu，最终变为 10 构建卷积神经网络的模型时，没有使用 nn.Sequential 进行构建，通过继承 nn.Module 构建模型的方式具有一般性。 网络中的一些层使用了 nn 进行定义，而另外一些层使用了 F 进行定义，区别在于： 使用 nn 进行定义的层，需要注册到 model.parameters() 中，在反向传播的过程中计算梯度并更新参数。或者像是 dropout，在训练和测试时会分别启用和自动关闭。 使用 F 进行定义的层，其作为单独的函数无需计算梯度，也不会进行更新，使用 F 定义更加简洁。 import torch import torch.nn as nn import torch.nn.functional as F device = torch.device('cuda' if torch.cuda.is_available() else 'cpu') class FashionCNN(nn.Module): def __init__(self): super().__init__() self.conv1 = nn.Conv2d(1, 32, 3, padding=1) self.conv2 = nn.Conv2d(32, 64, 3, padding=1) self.fc1 = nn.Linear(64*7*7, 128) self.dropout = nn.Dropout(0.5) self.fc2 = nn.Linear(128, 10) def forward(self, x): x = F.relu(self.conv1(x)) x = F.max_pool2d(x, 2) x = F.relu(self.conv2(x)) x = F.max_pool2d(x, 2) x = x.view(x.size(0), -1) x = F.relu(self.fc1(x)) x = self.dropout(x) x = self.fc2(x) return x def init_weights(m): if isinstance(m, nn.Conv2d) or isinstance(m, nn.Linear): nn.init.kaiming_normal_(m.weight, mode='fan_out', nonlinearity='relu') if m.bias is not None: nn.init.constant_(m.bias, 0) model = FashionCNN() model.apply(init_weights) model.to(device) FashionCNN(\r(conv1): Conv2d(1, 32, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1))\r(conv2): Conv2d(32, 64, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1))\r(fc1): Linear(in_features=3136, out_features=128, bias=True)\r(dropout): Dropout(p=0.5, inplace=False)\r(fc2): Linear(in_features=128, out_features=10, bias=True)\r)\rsum(p.numel() for p in model.parameters()) 421642\rfrom torchviz import make_dot x = torch.randn(1, 1, 28, 28).to(device, non_blocking=True) y = model(x) dot = make_dot(y, params=dict(model.named_parameters())) dot ","date":"2026-01-04","objectID":"/posts/pytorch-%E5%8D%B7%E7%A7%AF%E7%A5%9E%E7%BB%8F%E7%BD%91%E7%BB%9C/:2:2","tags":null,"title":"Pytorch 卷积神经网络","uri":"/posts/pytorch-%E5%8D%B7%E7%A7%AF%E7%A5%9E%E7%BB%8F%E7%BD%91%E7%BB%9C/#构建网络"},{"categories":["深度学习"],"collections":["pytorch"],"content":"26.3 设置优化器和损失函数\r优化器使用 Adam，学习率为 10^-3，并设置 L2 正则防止过拟合，损失函数用均方误差损失。 import torch.optim as optim optimizer = optim.Adam(model.parameters(), lr=1e-3, weight_decay=1e-4) criterion = nn.CrossEntropyLoss() ","date":"2026-01-04","objectID":"/posts/pytorch-%E5%8D%B7%E7%A7%AF%E7%A5%9E%E7%BB%8F%E7%BD%91%E7%BB%9C/:2:3","tags":null,"title":"Pytorch 卷积神经网络","uri":"/posts/pytorch-%E5%8D%B7%E7%A7%AF%E7%A5%9E%E7%BB%8F%E7%BD%91%E7%BB%9C/#设置优化器和损失函数"},{"categories":["深度学习"],"collections":["pytorch"],"content":"26.4 编写训练代码\r设置最大 epoch 为 50，设置\"早停\"，10 个 epoch 验证集没有优化就停止训练。 max_epochs = 50 # 早停 patience_counter = 0 patience = 10 best_loss = float('inf') for epoch in range(max_epochs): model.train() train_samples = 0 train_loss = 0 train_acc = 0 for x, y in train_loader: x, y = x.to(device, non_blocking=True), y.to(device, non_blocking=True) y_hat = model(x) loss = criterion(y_hat, y) optimizer.zero_grad() loss.backward() optimizer.step() train_samples += len(x) train_loss += loss.item() * len(x) pred = torch.argmax(y_hat, axis=1) train_acc += (pred == y).sum().item() train_loss = train_loss / train_samples train_acc = train_acc / train_samples model.eval() val_samples = 0 val_loss = 0 val_acc = 0 with torch.no_grad(): for x,y in val_loader: x, y = x.to(device, non_blocking=True), y.to(device, non_blocking=True) y_hat = model(x) loss = criterion(y_hat, y) val_samples += len(x) val_loss += loss.item() * len(x) pred = torch.argmax(y_hat, axis=1) val_acc += (pred == y).sum().item() val_loss = val_loss / val_samples val_acc = val_acc / val_samples if val_loss \u003c best_loss: best_loss = val_loss patience_counter = 0 torch.save(model.state_dict(), 'best_model.pth') else: patience_counter += 1 if patience_counter \u003e= patience: print(f\"Early stopping at epoch {epoch + 1}\") break if (epoch + 1) % 5 == 0: print(f\"epoch {epoch + 1}: train_loss: {train_loss}, train_acc: {train_acc}, val_loss: {val_loss}, val_acc: {val_acc}\") test_samples = 0 test_loss = 0 test_acc = 0 for x,y in test_loader: x, y = x.to(device, non_blocking=True), y.to(device, non_blocking=True) y_hat = model(x) loss = criterion(y_hat,y) test_samples += len(x) test_loss += loss.item() * len(x) pred = torch.argmax(y_hat, axis=1) test_acc += (pred == y).sum().item() test_loss = test_loss / test_samples test_acc = test_acc / test_samples print(f\"epoch {epoch + 1}: train_loss: {train_loss}, train_acc: {train_acc}, val_loss: {val_loss}, val_acc: {val_acc}, test_loss: {test_loss}, test_acc: {test_acc}\") epoch 5: train_loss: 0.3323359337647756, train_acc: 0.882625, val_loss: 0.2947879132429759, val_acc: 0.8954166666666666\repoch 10: train_loss: 0.24026671481132508, train_acc: 0.9115833333333333, val_loss: 0.25261858995755515, val_acc: 0.9113333333333333\repoch 15: train_loss: 0.1967949519753456, train_acc: 0.9285625, val_loss: 0.23852672167619068, val_acc: 0.9178333333333333\repoch 20: train_loss: 0.16271487843990326, train_acc: 0.9406458333333333, val_loss: 0.22613940691947937, val_acc: 0.9230833333333334\repoch 25: train_loss: 0.1353553236722946, train_acc: 0.9498958333333334, val_loss: 0.23934940230846405, val_acc: 0.9220833333333334\rEarly stopping at epoch 30\repoch 30: train_loss: 0.11594114247957865, train_acc: 0.9563125, val_loss: 0.24821518286069233, val_acc: 0.9226666666666666, test_loss: 0.24525352787971497, test_acc: 0.923\r","date":"2026-01-04","objectID":"/posts/pytorch-%E5%8D%B7%E7%A7%AF%E7%A5%9E%E7%BB%8F%E7%BD%91%E7%BB%9C/:2:4","tags":null,"title":"Pytorch 卷积神经网络","uri":"/posts/pytorch-%E5%8D%B7%E7%A7%AF%E7%A5%9E%E7%BB%8F%E7%BD%91%E7%BB%9C/#编写训练代码"},{"categories":["深度学习"],"collections":["pytorch"],"content":"第 27 节 LeNet-5\r第一个真正成功的卷积神经网络，用于手写数字识别，奠定了\"卷积 + 池化 + 全连接\"的范式。 model = nn.Sequential( nn.Conv2d(1, 6, kernel_size=5, padding=2), nn.Tanh(), nn.AvgPool2d(kernel_size=2, stride=2), nn.Conv2d(6, 16, kernel_size=5), nn.Tanh(), nn.AvgPool2d(kernel_size=2, stride=2), nn.Flatten(), nn.Linear(16 * 5 * 5, 120), nn.Tanh(), nn.Linear(120, 84), nn.Tanh(), nn.Linear(84, 10)) x = torch.rand(1, 1, 28, 28) for layer in model: if isinstance(layer,nn.Conv2d) or isinstance(layer,nn.Linear): print('-'*50) x = layer(x) print(layer.__class__.__name__,'output shape: \\t',x.shape) --------------------------------------------------\rConv2d output shape: torch.Size([1, 6, 28, 28])\rTanh output shape: torch.Size([1, 6, 28, 28])\rAvgPool2d output shape: torch.Size([1, 6, 14, 14])\r--------------------------------------------------\rConv2d output shape: torch.Size([1, 16, 10, 10])\rTanh output shape: torch.Size([1, 16, 10, 10])\rAvgPool2d output shape: torch.Size([1, 16, 5, 5])\rFlatten output shape: torch.Size([1, 400])\r--------------------------------------------------\rLinear output shape: torch.Size([1, 120])\rTanh output shape: torch.Size([1, 120])\r--------------------------------------------------\rLinear output shape: torch.Size([1, 84])\rTanh output shape: torch.Size([1, 84])\r--------------------------------------------------\rLinear output shape: torch.Size([1, 10])\rsum(p.numel() for p in model.parameters()) 61706\r","date":"2026-01-04","objectID":"/posts/pytorch-%E5%8D%B7%E7%A7%AF%E7%A5%9E%E7%BB%8F%E7%BD%91%E7%BB%9C/:3:0","tags":null,"title":"Pytorch 卷积神经网络","uri":"/posts/pytorch-%E5%8D%B7%E7%A7%AF%E7%A5%9E%E7%BB%8F%E7%BD%91%E7%BB%9C/#lenet-5"},{"categories":["深度学习"],"collections":["pytorch"],"content":"第 28 节 AlexNet\r使用 ReLU 作为激活函数，在全连接层使用 Dropout，有效抑制过拟合，使用了随机裁剪、水平翻转、颜色扰动等数据增强，使用了局部响应归一化 (LRN)。 model = nn.Sequential( # Conv1 nn.Conv2d(3, 96, kernel_size=11, stride=4, padding=1), nn.ReLU(), nn.MaxPool2d(kernel_size=3, stride=2), # Conv2 nn.Conv2d(96, 256, kernel_size=5, padding=2), nn.ReLU(), nn.MaxPool2d(kernel_size=3, stride=2), # Conv3 nn.Conv2d(256, 384, kernel_size=3, padding=1), nn.ReLU(), # Conv4 nn.Conv2d(384, 384, kernel_size=3, padding=1), nn.ReLU(), # Conv5 nn.Conv2d(384, 256, kernel_size=3, padding=1), nn.ReLU(), nn.MaxPool2d(kernel_size=3, stride=2), nn.Flatten(), # FC6 nn.Linear(6400, 4096), nn.ReLU(), nn.Dropout(p=0.5), # FC7 nn.Linear(4096, 4096), nn.ReLU(), nn.Dropout(p=0.5), # FC8 nn.Linear(4096, 1000)) x = torch.randn(1, 3, 224, 224) for layer in model: if isinstance(layer,nn.Conv2d) or isinstance(layer,nn.Linear): print('-'*50) x=layer(x) print(layer.__class__.__name__,'output shape:\\t',x.shape) --------------------------------------------------\rConv2d output shape: torch.Size([1, 96, 54, 54])\rReLU output shape: torch.Size([1, 96, 54, 54])\rMaxPool2d output shape: torch.Size([1, 96, 26, 26])\r--------------------------------------------------\rConv2d output shape: torch.Size([1, 256, 26, 26])\rReLU output shape: torch.Size([1, 256, 26, 26])\rMaxPool2d output shape: torch.Size([1, 256, 12, 12])\r--------------------------------------------------\rConv2d output shape: torch.Size([1, 384, 12, 12])\rReLU output shape: torch.Size([1, 384, 12, 12])\r--------------------------------------------------\rConv2d output shape: torch.Size([1, 384, 12, 12])\rReLU output shape: torch.Size([1, 384, 12, 12])\r--------------------------------------------------\rConv2d output shape: torch.Size([1, 256, 12, 12])\rReLU output shape: torch.Size([1, 256, 12, 12])\rMaxPool2d output shape: torch.Size([1, 256, 5, 5])\rFlatten output shape: torch.Size([1, 6400])\r--------------------------------------------------\rLinear output shape: torch.Size([1, 4096])\rReLU output shape: torch.Size([1, 4096])\rDropout output shape: torch.Size([1, 4096])\r--------------------------------------------------\rLinear output shape: torch.Size([1, 4096])\rReLU output shape: torch.Size([1, 4096])\rDropout output shape: torch.Size([1, 4096])\r--------------------------------------------------\rLinear output shape: torch.Size([1, 1000])\rsum(p.numel() for p in model.parameters()) 50844008\r","date":"2026-01-04","objectID":"/posts/pytorch-%E5%8D%B7%E7%A7%AF%E7%A5%9E%E7%BB%8F%E7%BD%91%E7%BB%9C/:4:0","tags":null,"title":"Pytorch 卷积神经网络","uri":"/posts/pytorch-%E5%8D%B7%E7%A7%AF%E7%A5%9E%E7%BB%8F%E7%BD%91%E7%BB%9C/#alexnet"},{"categories":["深度学习"],"collections":["pytorch"],"content":"第 29 节 VGG\rVGG（Visual Geometry Group）网络是牛津大学 VGG 组在 2014 年提出的经典卷积神经网络，在 ImageNet 竞赛中表现非常突出。VGG 的核心思想就是用大量连续的 3×3 小卷积核堆叠，构建深层 CNN。 def vgg_block(num_convs, in_channels, out_channels): layers = [] for _ in range(num_convs): layers.append(nn.Conv2d(in_channels, out_channels, kernel_size=3, padding=1)) layers.append(nn.ReLU()) in_channels = out_channels layers.append(nn.MaxPool2d(kernel_size=2,stride=2)) return nn.Sequential(*layers) def vgg16(): conv_arch = ((2, 64), (2, 128), (3, 256), (3, 512), (3, 512)) conv_blks = [] in_channels = 3 for (num_convs, out_channels) in conv_arch: conv_blks.append(vgg_block(num_convs, in_channels, out_channels)) in_channels = out_channels return nn.Sequential( *conv_blks, nn.Flatten(), nn.Linear(out_channels * 7 * 7, 4096), nn.ReLU(), nn.Dropout(0.5), nn.Linear(4096, 4096), nn.ReLU(), nn.Dropout(0.5), nn.Linear(4096, 1000)) model = vgg16() x = torch.randn(size=(1, 3, 224, 224)) for block in model: x = block(x) print(block.__class__.__name__,'output shape:\\t',x.shape) Sequential output shape: torch.Size([1, 64, 112, 112])\rSequential output shape: torch.Size([1, 128, 56, 56])\rSequential output shape: torch.Size([1, 256, 28, 28])\rSequential output shape: torch.Size([1, 512, 14, 14])\rSequential output shape: torch.Size([1, 512, 7, 7])\rFlatten output shape: torch.Size([1, 25088])\rLinear output shape: torch.Size([1, 4096])\rReLU output shape: torch.Size([1, 4096])\rDropout output shape: torch.Size([1, 4096])\rLinear output shape: torch.Size([1, 4096])\rReLU output shape: torch.Size([1, 4096])\rDropout output shape: torch.Size([1, 4096])\rLinear output shape: torch.Size([1, 1000])\rsum(p.numel() for p in model.parameters()) 138357544\r","date":"2026-01-04","objectID":"/posts/pytorch-%E5%8D%B7%E7%A7%AF%E7%A5%9E%E7%BB%8F%E7%BD%91%E7%BB%9C/:5:0","tags":null,"title":"Pytorch 卷积神经网络","uri":"/posts/pytorch-%E5%8D%B7%E7%A7%AF%E7%A5%9E%E7%BB%8F%E7%BD%91%E7%BB%9C/#vgg"},{"categories":["深度学习"],"collections":["pytorch"],"content":"第 30 节 GoogleNet\r在 GoogLeNet 中，基本的卷积块被称为Inception 块（Inception block）。Inception 块由四条并行路径组成。 前三条路径使用窗口大小为 $1\\times 1$、$3\\times 3$ 和 $5\\times 5$ 的卷积层，从不同空间大小中提取信息。 中间的两条路径在输入上执行 $1\\times 1$ 卷积，以减少通道数，从而降低模型的复杂性。 第四条路径使用 $3\\times 3$ 最大汇聚层，然后使用 $1\\times 1$ 卷积层来改变通道数。 这四条路径都使用合适的填充来使输入与输出的高和宽一致，最后我们将每条线路的输出在通道维度上连结，并构成 Inception 块的输出。 class Inception(nn.Module): def __init__(self, in_channels, c1, c2, c3, c4, **kwargs): super(Inception, self).__init__(**kwargs) # 线路1，单1x1卷积层 self.p1_1 = nn.Conv2d(in_channels, c1, kernel_size=1) # 线路2，1x1卷积层后接3x3卷积层 self.p2_1 = nn.Conv2d(in_channels, c2[0], kernel_size=1) self.p2_2 = nn.Conv2d(c2[0], c2[1], kernel_size=3, padding=1) # 线路3，1x1卷积层后接5x5卷积层 self.p3_1 = nn.Conv2d(in_channels, c3[0], kernel_size=1) self.p3_2 = nn.Conv2d(c3[0], c3[1], kernel_size=5, padding=2) # 线路4，3x3最大汇聚层后接1x1卷积层 self.p4_1 = nn.MaxPool2d(kernel_size=3, stride=1, padding=1) self.p4_2 = nn.Conv2d(in_channels, c4, kernel_size=1) def forward(self, x): p1 = F.relu(self.p1_1(x)) p2 = F.relu(self.p2_2(F.relu(self.p2_1(x)))) p3 = F.relu(self.p3_2(F.relu(self.p3_1(x)))) p4 = F.relu(self.p4_2(self.p4_1(x))) # 在通道维度上连结输出 return torch.cat((p1, p2, p3, p4), dim=1) 第一个模块使用 64 个通道、$7\\times 7$ 卷积层。 b1 = nn.Sequential(nn.Conv2d(3, 64, kernel_size=7, stride=2, padding=3), nn.ReLU(), nn.MaxPool2d(kernel_size=3, stride=2, padding=1)) 第二个模块使用两个卷积层：第一个卷积层是 64 个通道、$1\\times 1$ 卷积层；第二个卷积层使用将通道数量增加三倍的 $3\\times 3$ 卷积层。 这对应于 Inception 块中的第二条路径。 b2 = nn.Sequential(nn.Conv2d(64, 64, kernel_size=1), nn.ReLU(), nn.Conv2d(64, 192, kernel_size=3, padding=1), nn.ReLU(), nn.MaxPool2d(kernel_size=3, stride=2, padding=1)) 第三个模块串联两个完整的 Inception 块。 第一个 Inception 块的输出通道数为 $64+128+32+32=256$，四个路径之间的输出通道数量比为 $64:128:32:32=2:4:1:1$。第二个和第三个路径首先将输入通道的数量分别减少到 $96/192=1/2$ 和 $16/192=1/12$，然后连接第二个卷积层。 第二个 Inception 块的输出通道数增加到 $128+192+96+64=480$，四个路径之间的输出通道数量比为 $128:192:96:64 = 4:6:3:2$。第二条和第三条路径首先将输入通道的数量分别减少到 $128/256=1/2$ 和 $32/256=1/8$。 b3 = nn.Sequential(Inception(192, 64, (96, 128), (16, 32), 32), Inception(256, 128, (128, 192), (32, 96), 64), nn.MaxPool2d(kernel_size=3, stride=2, padding=1)) 第四模块更加复杂，它串联了 5 个 Inception 块，其输出通道数分别是 $192+208+48+64=512$、$160+224+64+64=512$、$128+256+64+64=512$、$112+288+64+64=528$ 和 $256+320+128+128=832$。 这些路径的通道数分配和第三模块中的类似，首先是含 $3×3$ 卷积层的第二条路径输出最多通道，其次是仅含 $1×1$ 卷积层的第一条路径，之后是含 $5×5$ 卷积层的第三条路径和含 $3×3$ 最大汇聚层的第四条路径。其中第二、第三条路径都会先按比例减小通道数。 这些比例在各个 Inception 块中都略有不同。 b4 = nn.Sequential(Inception(480, 192, (96, 208), (16, 48), 64), Inception(512, 160, (112, 224), (24, 64), 64), Inception(512, 128, (128, 256), (24, 64), 64), Inception(512, 112, (144, 288), (32, 64), 64), Inception(528, 256, (160, 320), (32, 128), 128), nn.MaxPool2d(kernel_size=3, stride=2, padding=1)) 第五模块包含输出通道数为 $256+320+128+128=832$ 和 $384+384+128+128=1024$ 的两个 Inception 块。其中每条路径通道数的分配思路和第三、第四模块中的一致，只是在具体数值上有所不同。 需要注意的是，第五模块的后面紧跟输出层，该模块使用全局平均汇聚层，将每个通道的高和宽变成 1。最后我们将输出变成二维数组，再接上一个输出个数为标签类别数的全连接层。 b5 = nn.Sequential(Inception(832, 256, (160, 320), (32, 128), 128), Inception(832, 384, (192, 384), (48, 128), 128), nn.AdaptiveAvgPool2d((1,1)), nn.Flatten()) model = nn.Sequential(b1, b2, b3, b4, b5, nn.Linear(1024, 1000)) x = torch.rand(size=(1, 3, 224, 224)) for layer in model: x = layer(x) print(layer.__class__.__name__,'output shape:\\t', x.shape) Sequential output shape: torch.Size([1, 64, 56, 56])\rSequential output shape: torch.Size([1, 192, 28, 28])\rSequential output shape: torch.Size([1, 480, 14, 14])\rSequential output shape: torch.Size([1, 832, 7, 7])\rSequential output shape: torch.Size([1, 1024])\rLinear output shape: torch.Size([1, 1000])\rsum(p.numel() for p in model.parameters()) 6998552\r","date":"2026-01-04","objectID":"/posts/pytorch-%E5%8D%B7%E7%A7%AF%E7%A5%9E%E7%BB%8F%E7%BD%91%E7%BB%9C/:6:0","tags":null,"title":"Pytorch 卷积神经网络","uri":"/posts/pytorch-%E5%8D%B7%E7%A7%AF%E7%A5%9E%E7%BB%8F%E7%BD%91%E7%BB%9C/#googlenet"},{"categories":["深度学习"],"collections":["pytorch"],"content":"第 31 节 批量归一化\r在训练深层网络时，中间层的数值分布会不断变化、有时变化幅度还很大，这会让训练变得困难、收敛变慢；批量归一化就是为了解决这种\"分布不稳定\"的问题。 用 $\\mathbf{x} \\in \\mathcal{B}$ 表示一个来自小批量 $\\mathcal{B}$ 的输入，批量规范化 $\\mathrm{BN}$： $$\\mathrm{BN}(\\mathbf{x}) = \\boldsymbol{\\gamma} \\odot \\frac{\\mathbf{x} - \\hat{\\boldsymbol{\\mu}}\\mathcal{B}}{\\hat{\\boldsymbol{\\sigma}}\\mathcal{B}} + \\boldsymbol{\\beta}.$$ $\\hat{\\boldsymbol{\\mu}}\\mathcal{B}$ 是小批量 $\\mathcal{B}$ 的样本均值，$\\hat{\\boldsymbol{\\sigma}}\\mathcal{B}$ 是小批量 $\\mathcal{B}$ 的样本标准差。 标准化后，生成的小批量的平均值为 0 和单位方差为 1。 拉伸参数（scale）$\\boldsymbol{\\gamma}$ 和偏移参数（shift）$\\boldsymbol{\\beta}$，它们的形状与 $\\mathbf{x}$ 相同，参与反向传播，用于让网络\"有权决定要不要标准化，以及标准化到什么程度\"。 def batch_norm(X, gamma, beta, moving_mean, moving_var, eps, momentum): # 通过is_grad_enabled来判断当前模式是训练模式还是预测模式 if not torch.is_grad_enabled(): # 如果是在预测模式下，直接使用传入的移动平均所得的均值和方差 X_hat = (X - moving_mean) / torch.sqrt(moving_var + eps) else: assert len(X.shape) in (2, 4) if len(X.shape) == 2: # 使用全连接层的情况，计算特征维上的均值和方差 mean = X.mean(dim=0) var = ((X - mean) ** 2).mean(dim=0) else: # 使用二维卷积层的情况，计算通道维上（axis=1）的均值和方差。 # 这里我们需要保持X的形状以便后面可以做广播运算 mean = X.mean(dim=(0, 2, 3), keepdim=True) var = ((X - mean) ** 2).mean(dim=(0, 2, 3), keepdim=True) # 训练模式下，用当前的均值和方差做标准化 X_hat = (X - mean) / torch.sqrt(var + eps) # 更新移动平均的均值和方差 moving_mean = momentum * moving_mean + (1.0 - momentum) * mean moving_var = momentum * moving_var + (1.0 - momentum) * var Y = gamma * X_hat + beta # 缩放和移位 return Y, moving_mean.data, moving_var.data class BatchNorm(nn.Module): # num_features：完全连接层的输出数量或卷积层的输出通道数。 # num_dims：2表示完全连接层，4表示卷积层 def __init__(self, num_features, num_dims): super().__init__() if num_dims == 2: shape = (1, num_features) else: shape = (1, num_features, 1, 1) # 参与求梯度和迭代的拉伸和偏移参数，分别初始化成1和0 self.gamma = nn.Parameter(torch.ones(shape)) self.beta = nn.Parameter(torch.zeros(shape)) # 非模型参数的变量初始化为0和1 self.moving_mean = torch.zeros(shape) self.moving_var = torch.ones(shape) def forward(self, X): # 如果X不在内存上，将moving_mean和moving_var # 复制到X所在显存上 if self.moving_mean.device != X.device: self.moving_mean = self.moving_mean.to(X.device) self.moving_var = self.moving_var.to(X.device) # 保存更新过的moving_mean和moving_var Y, self.moving_mean, self.moving_var = batch_norm( X, self.gamma, self.beta, self.moving_mean, self.moving_var, eps=1e-5, momentum=0.9) return Y ","date":"2026-01-04","objectID":"/posts/pytorch-%E5%8D%B7%E7%A7%AF%E7%A5%9E%E7%BB%8F%E7%BD%91%E7%BB%9C/:7:0","tags":null,"title":"Pytorch 卷积神经网络","uri":"/posts/pytorch-%E5%8D%B7%E7%A7%AF%E7%A5%9E%E7%BB%8F%E7%BD%91%E7%BB%9C/#批量归一化"},{"categories":["深度学习"],"collections":["pytorch"],"content":"第 32 节 Resnet\rclass Residual(nn.Module): def __init__(self, input_channels, num_channels, use_1x1conv=False, strides=1): super().__init__() self.conv1 = nn.Conv2d(input_channels, num_channels, kernel_size=3, padding=1, stride=strides) self.conv2 = nn.Conv2d(num_channels, num_channels, kernel_size=3, padding=1) if use_1x1conv: self.conv3 = nn.Conv2d(input_channels, num_channels, kernel_size=1, stride=strides) else: self.conv3 = None self.bn1 = nn.BatchNorm2d(num_channels) self.bn2 = nn.BatchNorm2d(num_channels) def forward(self, X): Y = F.relu(self.bn1(self.conv1(X))) Y = self.bn2(self.conv2(Y)) if self.conv3: X = self.conv3(X) Y += X return F.relu(Y) ResNet 的前两层跟之前介绍的 GoogLeNet 中的一样： 在输出通道数为 64、步幅为 2 的 $7 \\times 7$ 卷积层后，接步幅为 2 的 $3 \\times 3$ 的最大汇聚层。 不同之处在于 ResNet 每个卷积层后增加了批量规范化层。 b1 = nn.Sequential(nn.Conv2d(3, 64, kernel_size=7, stride=2, padding=3), nn.BatchNorm2d(64), nn.ReLU(), nn.MaxPool2d(kernel_size=3, stride=2, padding=1)) GoogLeNet 在后面接了 4 个由 Inception 块组成的模块。 ResNet 则使用 4 个由残差块组成的模块，每个模块使用若干个同样输出通道数的残差块。 第一个模块的通道数同输入通道数一致。 由于之前已经使用了步幅为 2 的最大汇聚层，所以无须减小高和宽。 之后的每个模块在第一个残差块里将上一个模块的通道数翻倍，并将高和宽减半。 下面我们来实现这个模块。注意，我们对第一个模块做了特别处理。 def resnet_block(input_channels, num_channels, num_residuals, first_block=False): blk = [] for i in range(num_residuals): if i == 0 and not first_block: blk.append(Residual(input_channels, num_channels, use_1x1conv=True, strides=2)) else: blk.append(Residual(num_channels, num_channels)) return blk b2 = nn.Sequential(*resnet_block(64, 64, 2, first_block=True)) b3 = nn.Sequential(*resnet_block(64, 128, 2)) b4 = nn.Sequential(*resnet_block(128, 256, 2)) b5 = nn.Sequential(*resnet_block(256, 512, 2)) model = nn.Sequential(b1, b2, b3, b4, b5, nn.AdaptiveAvgPool2d((1,1)), nn.Flatten(), nn.Linear(512, 1000)) x = torch.rand(size=(1, 3, 224, 224)) for layer in model: x = layer(x) print(layer.__class__.__name__,'output shape:\\t', x.shape) Sequential output shape: torch.Size([1, 64, 56, 56])\rSequential output shape: torch.Size([1, 64, 56, 56])\rSequential output shape: torch.Size([1, 128, 28, 28])\rSequential output shape: torch.Size([1, 256, 14, 14])\rSequential output shape: torch.Size([1, 512, 7, 7])\rAdaptiveAvgPool2d output shape: torch.Size([1, 512, 1, 1])\rFlatten output shape: torch.Size([1, 512])\rLinear output shape: torch.Size([1, 1000])\rsum(p.numel() for p in model.parameters()) 11692520\r","date":"2026-01-04","objectID":"/posts/pytorch-%E5%8D%B7%E7%A7%AF%E7%A5%9E%E7%BB%8F%E7%BD%91%E7%BB%9C/:8:0","tags":null,"title":"Pytorch 卷积神经网络","uri":"/posts/pytorch-%E5%8D%B7%E7%A7%AF%E7%A5%9E%E7%BB%8F%E7%BD%91%E7%BB%9C/#resnet"},{"categories":["深度学习"],"collections":["pytorch"],"content":"第 10 节 概述\r在本文中，使用全连接神经网络进行简单的分类和回归。 ","date":"2026-01-03","objectID":"/posts/pytorch-%E5%85%A8%E8%BF%9E%E6%8E%A5%E7%A5%9E%E7%BB%8F%E7%BD%91%E7%BB%9C/:1:0","tags":null,"title":"Pytorch 全连接神经网络","uri":"/posts/pytorch-%E5%85%A8%E8%BF%9E%E6%8E%A5%E7%A5%9E%E7%BB%8F%E7%BD%91%E7%BB%9C/#概述"},{"categories":["深度学习"],"collections":["pytorch"],"content":"第 11 节 回归\r以 scikit-learn 上的加州房价数据集为例说明，加州房价数据集是一个用于回归计算的数据集： 输入特征一共 8 条 输出为加州房价 from sklearn.datasets import fetch_california_housing X, y = fetch_california_housing(return_X_y=True) X.shape, y.shape ((20640, 8), (20640,))\r","date":"2026-01-03","objectID":"/posts/pytorch-%E5%85%A8%E8%BF%9E%E6%8E%A5%E7%A5%9E%E7%BB%8F%E7%BD%91%E7%BB%9C/:2:0","tags":null,"title":"Pytorch 全连接神经网络","uri":"/posts/pytorch-%E5%85%A8%E8%BF%9E%E6%8E%A5%E7%A5%9E%E7%BB%8F%E7%BD%91%E7%BB%9C/#回归"},{"categories":["深度学习"],"collections":["pytorch"],"content":"11.1 数据预处理\r首先进行数据集划分，80% 数据作为训练集，20% 数据作为测试集。 from sklearn.model_selection import train_test_split X_train,X_test,y_train,y_test = train_test_split(X,y,test_size=0.2,shuffle=True) X_train.shape, X_test.shape, y_train.shape, y_test.shape ((16512, 8), (4128, 8), (16512,), (4128,))\r对训练数据和测试数据都进行标准化处理，统一量纲。 因为训练数据是已知的，可以获取数据分布，而测试数据是未知的，不能获取数据分布，所以： 对训练数据做 fit_transform 操作 对测试数据做 transform 操作 from sklearn.preprocessing import StandardScaler x_scaler = StandardScaler() X_train_scaled = x_scaler.fit_transform(X_train) X_test_scaled = x_scaler.transform(X_test) y_scaler = StandardScaler() y_train_scaled = y_scaler.fit_transform(y_train.reshape(-1,1)) y_test_scaled = y_scaler.transform(y_test.reshape(-1,1)) ","date":"2026-01-03","objectID":"/posts/pytorch-%E5%85%A8%E8%BF%9E%E6%8E%A5%E7%A5%9E%E7%BB%8F%E7%BD%91%E7%BB%9C/:2:1","tags":null,"title":"Pytorch 全连接神经网络","uri":"/posts/pytorch-%E5%85%A8%E8%BF%9E%E6%8E%A5%E7%A5%9E%E7%BB%8F%E7%BD%91%E7%BB%9C/#数据预处理"},{"categories":["深度学习"],"collections":["pytorch"],"content":"11.2 机器学习对照\r传统机器学习中，随机森林比较适合用来解决加州房价的回归问题，用来和全连接神经网络做对照。 from sklearn.ensemble import RandomForestRegressor from sklearn.metrics import mean_squared_error regressor = RandomForestRegressor() regressor.fit(X_train_scaled,y_train) y_pred = regressor.predict(X_test_scaled) loss = mean_squared_error(y_pred,y_test) loss 0.26758457404360897\r","date":"2026-01-03","objectID":"/posts/pytorch-%E5%85%A8%E8%BF%9E%E6%8E%A5%E7%A5%9E%E7%BB%8F%E7%BD%91%E7%BB%9C/:2:2","tags":null,"title":"Pytorch 全连接神经网络","uri":"/posts/pytorch-%E5%85%A8%E8%BF%9E%E6%8E%A5%E7%A5%9E%E7%BB%8F%E7%BD%91%E7%BB%9C/#机器学习对照"},{"categories":["深度学习"],"collections":["pytorch"],"content":"11.3 构建数据集\r在使用全连接神经网络进行训练时，为了防止过拟合，使用了\"早停\"的策略。 这里对训练数据再次划分，80% 为训练集，20% 为验证集。 import torch X_train_scaled,X_val_scaled,y_train_scaled,y_val_scaled = train_test_split(X_train_scaled,y_train_scaled,test_size=0.2,shuffle=True) X_train_scaled,X_val_scaled, X_test_scaled, y_train_scaled,y_val_scaled, y_test_scaled = torch.tensor(X_train_scaled,dtype=torch.float32),torch.tensor(X_val_scaled,dtype=torch.float32),torch.tensor(X_test_scaled,dtype=torch.float32), torch.tensor(y_train_scaled,dtype=torch.float32), torch.tensor(y_val_scaled,dtype=torch.float32),torch.tensor(y_test_scaled,dtype=torch.float32) 创建 DataLoader 实现按批量自动加载数据集。 batch_size 设置为 256。 训练集和验证集 shuffle 设置为 True，每个 epoch 会自动重排，测试集无需重排。 由于数据都存储在内存中，num_workers 设置为 0。 设置 pin_memory 使用页锁定内存，内存不会被 OS swap，GPU 可以直接 DMA 读取，拷贝速度更快。 from torch.utils.data import TensorDataset, DataLoader train_dataset = TensorDataset(X_train_scaled,y_train_scaled) train_loader = DataLoader(train_dataset,batch_size=256,shuffle=True,num_workers=0,pin_memory=True) val_dataset = TensorDataset(X_val_scaled,y_val_scaled) val_loader = DataLoader(val_dataset,batch_size=256,shuffle=True,num_workers=0,pin_memory=True) test_dataset = TensorDataset(X_test_scaled,y_test_scaled) test_loader = DataLoader(test_dataset,batch_size=256,shuffle=False,num_workers=0,pin_memory=True) ","date":"2026-01-03","objectID":"/posts/pytorch-%E5%85%A8%E8%BF%9E%E6%8E%A5%E7%A5%9E%E7%BB%8F%E7%BD%91%E7%BB%9C/:2:3","tags":null,"title":"Pytorch 全连接神经网络","uri":"/posts/pytorch-%E5%85%A8%E8%BF%9E%E6%8E%A5%E7%A5%9E%E7%BB%8F%E7%BD%91%E7%BB%9C/#构建数据集"},{"categories":["深度学习"],"collections":["pytorch"],"content":"11.4 构建网络\r创建全连接神经网络，并且把模型放到 GPU 上。 from torch import nn import torch.nn.init as init device = 'cuda' if torch.cuda.is_available() else 'cpu' model = nn.Sequential( nn.Linear(8,64), nn.ReLU(), nn.Linear(64,32), nn.ReLU(), nn.Linear(32,1)) for layer in model: if isinstance(layer, nn.Linear): init.kaiming_uniform_(layer.weight, nonlinearity='relu') if layer.bias is not None: nn.init.zeros_(layer.bias) model.to(device) Sequential(\r(0): Linear(in_features=8, out_features=64, bias=True)\r(1): ReLU()\r(2): Linear(in_features=64, out_features=32, bias=True)\r(3): ReLU()\r(4): Linear(in_features=32, out_features=1, bias=True)\r)\r常见的初始化方式有两种，一种是恺明初始化，另一种是 xavier 初始化。 恺明初始化：适用于激活函数为 relu xavier 初始化：适用于激活函数为 tanh / sigmoid init.kaiming_uniform_(layer.weight, nonlinearity='relu') # 可取 relu、leaky_relu、selu 等 init.zeros_(layer.bias) init.xavier_uniform_(layer.weight) init.zeros_(layer.bias) Parameter containing:\rtensor([0.], device='cuda:0', requires_grad=True)\r","date":"2026-01-03","objectID":"/posts/pytorch-%E5%85%A8%E8%BF%9E%E6%8E%A5%E7%A5%9E%E7%BB%8F%E7%BD%91%E7%BB%9C/:2:4","tags":null,"title":"Pytorch 全连接神经网络","uri":"/posts/pytorch-%E5%85%A8%E8%BF%9E%E6%8E%A5%E7%A5%9E%E7%BB%8F%E7%BD%91%E7%BB%9C/#构建网络"},{"categories":["深度学习"],"collections":["pytorch"],"content":"11.5 设置优化器和损失函数\r优化器使用 Adam，学习率为 10^-3，并设置 L2 正则防止过拟合，损失函数用均方误差损失。 import torch.optim as optim optimizer = optim.Adam(model.parameters(), lr=1e-3, weight_decay=1e-4) criterion = nn.MSELoss() ","date":"2026-01-03","objectID":"/posts/pytorch-%E5%85%A8%E8%BF%9E%E6%8E%A5%E7%A5%9E%E7%BB%8F%E7%BD%91%E7%BB%9C/:2:5","tags":null,"title":"Pytorch 全连接神经网络","uri":"/posts/pytorch-%E5%85%A8%E8%BF%9E%E6%8E%A5%E7%A5%9E%E7%BB%8F%E7%BD%91%E7%BB%9C/#设置优化器和损失函数"},{"categories":["深度学习"],"collections":["pytorch"],"content":"11.6 编写训练代码\r设置最大 epoch 为 300，设置\"早停\"，20 个 epoch 验证集没有优化就停止训练。 non_blocking 是异步拷贝，在数据从 CPU 传输到 GPU 的过程中，GPU 可以并行训练。需要在 DataLoader 开启 pin_memory=True max_epochs = 300 # 早停 patience_counter = 0 patience = 20 best_loss = float('inf') for epoch in range(max_epochs): model.train() train_loss = 0 for x, y in train_loader: x, y = x.to(device, non_blocking=True), y.to(device, non_blocking=True) optimizer.zero_grad() y_hat = model(x) loss = criterion(y_hat, y) train_loss += loss.item() loss.backward() optimizer.step() train_loss = train_loss / len(train_loader) model.eval() val_loss = 0 with torch.no_grad(): for x,y in val_loader: x, y = x.to(device, non_blocking=True), y.to(device, non_blocking=True) y_hat = model(x) loss = criterion(y_hat,y) val_loss += loss.item() val_loss /= len(val_loader) if val_loss \u003c best_loss: best_loss = val_loss patience_counter = 0 torch.save(model.state_dict(), 'best_model.pth') else: patience_counter += 1 if patience_counter \u003e= patience: print(f\"Early stopping at epoch {epoch + 1}\") break if (epoch + 1) % 10 == 0: print(f\"epoch {epoch + 1}: train_loss: {train_loss},val_loss: {val_loss}\") test_loss = 0 for x,y in test_loader: x, y = x.to(device, non_blocking=True), y.to(device, non_blocking=True) y_hat = model(x) loss = criterion(y_hat,y) test_loss += loss.item() test_loss /= len(test_loader) print(f\"epoch {epoch + 1}: train_loss: {train_loss},val_loss: {val_loss},test_loss:{test_loss}\") epoch 10: train_loss: 0.29649700740208995,val_loss: 0.28805418656422543\repoch 20: train_loss: 0.2750496683785549,val_loss: 0.26191359758377075\repoch 30: train_loss: 0.2403819847565431,val_loss: 0.2530410255377109\repoch 40: train_loss: 0.2293749749660492,val_loss: 0.23420465336396143\repoch 50: train_loss: 0.2189344371167513,val_loss: 0.2368529702608402\repoch 60: train_loss: 0.21395021619705054,val_loss: 0.22983338511907137\repoch 70: train_loss: 0.21009109541773796,val_loss: 0.22714151327426618\repoch 80: train_loss: 0.20461482526018068,val_loss: 0.2212988195511011\repoch 90: train_loss: 0.2007397161080287,val_loss: 0.2203361988067627\repoch 100: train_loss: 0.20478695802963698,val_loss: 0.22049094621951765\repoch 110: train_loss: 0.19856098007697326,val_loss: 0.22147137041275317\repoch 120: train_loss: 0.1933052376485788,val_loss: 0.2151113244203421\repoch 130: train_loss: 0.19220517102915508,val_loss: 0.2174885834638889\repoch 140: train_loss: 0.1903463828449066,val_loss: 0.21797557977529672\repoch 150: train_loss: 0.18772307095619348,val_loss: 0.21591514692856714\repoch 160: train_loss: 0.18299155925902036,val_loss: 0.21724496896450335\repoch 170: train_loss: 0.19431308141121498,val_loss: 0.22148504165502694\rEarly stopping at epoch 175\repoch 175: train_loss: 0.18683575093746185,val_loss: 0.21517686889721796,test_loss:0.20920205905156977\r","date":"2026-01-03","objectID":"/posts/pytorch-%E5%85%A8%E8%BF%9E%E6%8E%A5%E7%A5%9E%E7%BB%8F%E7%BD%91%E7%BB%9C/:2:6","tags":null,"title":"Pytorch 全连接神经网络","uri":"/posts/pytorch-%E5%85%A8%E8%BF%9E%E6%8E%A5%E7%A5%9E%E7%BB%8F%E7%BD%91%E7%BB%9C/#编写训练代码"},{"categories":["深度学习"],"collections":["pytorch"],"content":"第 12 节 分类\r以 Fashion-Minist 数据集为例说明： Fashion-Minist 中包含的 10 个类别，分别为 t-shirt（T 恤）、trouser（裤子）、pullover（套衫）、dress（连衣裙）、coat（外套）、sandal（凉鞋）、shirt（衬衫）、sneaker（运动鞋）、bag（包）和 ankle boot（短靴）。 import torchvision from torchvision.transforms import Compose, ToTensor, Normalize trans = ToTensor() mnist_train = torchvision.datasets.FashionMNIST(root=\"../data\",transform=trans,train=True,download=True) mnist_test = torchvision.datasets.FashionMNIST(root=\"../data\",transform=trans,train=False, download=True) 训练数据集中有的 6000 张图像，测试数据集中有 10000 张图像。 len(mnist_train), len(mnist_test) (60000, 10000)\r每张图像的高度和宽度均为 28 像素，数据集由灰度图像组成，其通道数为 1。 mnist_train[0][0].shape torch.Size([1, 28, 28])\r","date":"2026-01-03","objectID":"/posts/pytorch-%E5%85%A8%E8%BF%9E%E6%8E%A5%E7%A5%9E%E7%BB%8F%E7%BD%91%E7%BB%9C/:3:0","tags":null,"title":"Pytorch 全连接神经网络","uri":"/posts/pytorch-%E5%85%A8%E8%BF%9E%E6%8E%A5%E7%A5%9E%E7%BB%8F%E7%BD%91%E7%BB%9C/#分类"},{"categories":["深度学习"],"collections":["pytorch"],"content":"12.1 数据预处理 \u0026 构建数据集\r编写数据预处理函数： ToTensor: 将图像的颜色从 0~255 变为 0~1，并转为张量 [N,C,H,W] Normalize: 对图像进行标准化，颜色从 0~1 变为 -1~1 数据集划分为训练集、验证集和测试集。 from torch.utils.data import random_split def load_data_fashion_mnist(batch_size, val_ratio=0.2): trans = Compose([ToTensor(),Normalize((0.5,), (0.5,))]) total_dataset = torchvision.datasets.FashionMNIST( root=\"../data\", train=True, transform=trans, download=True) test_dataset = torchvision.datasets.FashionMNIST( root=\"../data\", train=False, transform=trans, download=True) total_size = len(total_dataset) val_size = int(total_size * val_ratio) train_size = total_size - val_size train_dataset, val_dataset = random_split( total_dataset, [train_size, val_size] ) train_loader = DataLoader( train_dataset, batch_size=batch_size, shuffle=True, num_workers=4, pin_memory=True ) val_loader = DataLoader( val_dataset, batch_size=batch_size, shuffle=False, num_workers=4, pin_memory=True ) test_loader = DataLoader( test_dataset, batch_size=batch_size, shuffle=False, num_workers=4, pin_memory=True ) return (train_loader,val_loader,test_loader) batch_size = 256 train_loader, val_loader, test_loader = load_data_fashion_mnist(batch_size) ","date":"2026-01-03","objectID":"/posts/pytorch-%E5%85%A8%E8%BF%9E%E6%8E%A5%E7%A5%9E%E7%BB%8F%E7%BD%91%E7%BB%9C/:3:1","tags":null,"title":"Pytorch 全连接神经网络","uri":"/posts/pytorch-%E5%85%A8%E8%BF%9E%E6%8E%A5%E7%A5%9E%E7%BB%8F%E7%BD%91%E7%BB%9C/#数据预处理--构建数据集"},{"categories":["深度学习"],"collections":["pytorch"],"content":"12.2 构建网络\r使用全连接神经网络构建： 首先使用 Flatten 将 28*28 的图像转为 784 的一维向量 使用 Dropout 防止过拟合 最后，网络有 10 个输出值，其中最大的就是其类别 device = torch.device('cuda' if torch.cuda.is_available() else 'cpu') model = nn.Sequential( nn.Flatten(), nn.Linear(784, 512), nn.ReLU(), nn.Dropout(0.2), nn.Linear(512, 256), nn.ReLU(), nn.Dropout(0.2), nn.Linear(256, 10)) for layer in model: if isinstance(layer, nn.Linear): nn.init.kaiming_uniform_(layer.weight, nonlinearity='relu') nn.init.zeros_(layer.bias) model.to(device) Sequential(\r(0): Flatten(start_dim=1, end_dim=-1)\r(1): Linear(in_features=784, out_features=512, bias=True)\r(2): ReLU()\r(3): Dropout(p=0.2, inplace=False)\r(4): Linear(in_features=512, out_features=256, bias=True)\r(5): ReLU()\r(6): Dropout(p=0.2, inplace=False)\r(7): Linear(in_features=256, out_features=10, bias=True)\r)\r","date":"2026-01-03","objectID":"/posts/pytorch-%E5%85%A8%E8%BF%9E%E6%8E%A5%E7%A5%9E%E7%BB%8F%E7%BD%91%E7%BB%9C/:3:2","tags":null,"title":"Pytorch 全连接神经网络","uri":"/posts/pytorch-%E5%85%A8%E8%BF%9E%E6%8E%A5%E7%A5%9E%E7%BB%8F%E7%BD%91%E7%BB%9C/#构建网络-1"},{"categories":["深度学习"],"collections":["pytorch"],"content":"12.3 设置优化器和损失函数\r优化器使用 Adam，学习率为 10^-3，并设置 L2 正则防止过拟合，损失函数用均方误差损失。 optimizer = optim.Adam(model.parameters(), lr=1e-3, weight_decay=1e-4) criterion = nn.CrossEntropyLoss() ","date":"2026-01-03","objectID":"/posts/pytorch-%E5%85%A8%E8%BF%9E%E6%8E%A5%E7%A5%9E%E7%BB%8F%E7%BD%91%E7%BB%9C/:3:3","tags":null,"title":"Pytorch 全连接神经网络","uri":"/posts/pytorch-%E5%85%A8%E8%BF%9E%E6%8E%A5%E7%A5%9E%E7%BB%8F%E7%BD%91%E7%BB%9C/#设置优化器和损失函数-1"},{"categories":["深度学习"],"collections":["pytorch"],"content":"12.4 编写训练代码\r设置最大 epoch 为 50，设置\"早停\"，10 个 epoch 验证集没有优化就停止训练。 max_epochs = 50 # 早停 patience_counter = 0 patience = 10 best_loss = float('inf') for epoch in range(max_epochs): model.train() train_samples = 0 train_loss = 0 train_acc = 0 for x, y in train_loader: x, y = x.to(device, non_blocking=True), y.to(device, non_blocking=True) y_hat = model(x) loss = criterion(y_hat, y) optimizer.zero_grad() loss.backward() optimizer.step() train_samples += len(x) train_loss += loss.item() * len(x) pred = torch.argmax(y_hat, axis=1) train_acc += (pred == y).sum().item() train_loss = train_loss / train_samples train_acc = train_acc / train_samples model.eval() val_samples = 0 val_loss = 0 val_acc = 0 with torch.no_grad(): for x,y in val_loader: x, y = x.to(device, non_blocking=True), y.to(device, non_blocking=True) y_hat = model(x) loss = criterion(y_hat, y) val_samples += len(x) val_loss += loss.item() * len(x) pred = torch.argmax(y_hat, axis=1) val_acc += (pred == y).sum().item() val_loss = val_loss / val_samples val_acc = val_acc / val_samples if val_loss \u003c best_loss: best_loss = val_loss patience_counter = 0 torch.save(model.state_dict(), 'best_model.pth') else: patience_counter += 1 if patience_counter \u003e= patience: print(f\"Early stopping at epoch {epoch + 1}\") break if (epoch + 1) % 5 == 0: print(f\"epoch {epoch + 1}: train_loss: {train_loss}, train_acc: {train_acc}, val_loss: {val_loss}, val_acc: {val_acc}\") test_samples = 0 test_loss = 0 test_acc = 0 for x,y in test_loader: x, y = x.to(device, non_blocking=True), y.to(device, non_blocking=True) y_hat = model(x) loss = criterion(y_hat,y) test_samples += len(x) test_loss += loss.item() * len(x) pred = torch.argmax(y_hat, axis=1) test_acc += (pred == y).sum().item() test_loss = test_loss / test_samples test_acc = test_acc / test_samples print(f\"epoch {epoch + 1}: train_loss: {train_loss}, train_acc: {train_acc}, val_loss: {val_loss}, val_acc: {val_acc}, test_loss: {test_loss}, test_acc: {test_acc}\") epoch 5: train_loss: 0.3285662808418274, train_acc: 0.878875, val_loss: 0.3343446226119995, val_acc: 0.8786666666666667\repoch 10: train_loss: 0.2763984892368317, train_acc: 0.8970625, val_loss: 0.3130743578275045, val_acc: 0.8841666666666667\repoch 15: train_loss: 0.24112992405891417, train_acc: 0.9097916666666667, val_loss: 0.31883913882573445, val_acc: 0.8863333333333333\repoch 20: train_loss: 0.22001066426436106, train_acc: 0.9165833333333333, val_loss: 0.3008507702350616, val_acc: 0.8915833333333333\repoch 25: train_loss: 0.19760130242506663, train_acc: 0.9248958333333334, val_loss: 0.31735219049453733, val_acc: 0.8919166666666667\repoch 30: train_loss: 0.1833056865533193, train_acc: 0.9304791666666666, val_loss: 0.2940208122730255, val_acc: 0.8993333333333333\repoch 35: train_loss: 0.17255648855368297, train_acc: 0.93425, val_loss: 0.30067479848861695, val_acc: 0.8950833333333333\rEarly stopping at epoch 40\repoch 40: train_loss: 0.15898222970962525, train_acc: 0.9389166666666666, val_loss: 0.3018113072713216, val_acc: 0.9005833333333333, test_loss: 0.3316642808914185, test_acc: 0.8966\r","date":"2026-01-03","objectID":"/posts/pytorch-%E5%85%A8%E8%BF%9E%E6%8E%A5%E7%A5%9E%E7%BB%8F%E7%BD%91%E7%BB%9C/:3:4","tags":null,"title":"Pytorch 全连接神经网络","uri":"/posts/pytorch-%E5%85%A8%E8%BF%9E%E6%8E%A5%E7%A5%9E%E7%BB%8F%E7%BD%91%E7%BB%9C/#编写训练代码-1"},{"categories":["深度学习"],"collections":["pytorch"],"content":"第 10 节 概述\r本文的内容主要来自《动手学深度学习》和《深入浅出 Pytorch》, 在本系列中，不会涉及过多的深度学习相关知识，主要聚焦于如何使用 Pytorch 进行深度学习。 ","date":"2025-12-30","objectID":"/posts/pytorch-%E5%BC%A0%E9%87%8F/:1:0","tags":null,"title":"Pytorch 张量","uri":"/posts/pytorch-%E5%BC%A0%E9%87%8F/#概述"},{"categories":["深度学习"],"collections":["pytorch"],"content":"第 11 节 创建张量\r张量表示一个由数值组成的数组，这个数组可能有多个维度。具有一个轴的张量对应数学上的向量（vector）；具有两个轴的张量对应数学上的矩阵（matrix）；具有两个轴以上的张量没有特殊的数学名称。 在 Pytorch 中，张量有如下的构造函数： class Tensor(torch._C.TensorBase): ... class DoubleTensor(Tensor): ... class FloatTensor(Tensor): ... class BFloat16Tensor(Tensor): ... class LongTensor(Tensor): ... class IntTensor(Tensor): ... class ShortTensor(Tensor): ... class HalfTensor(Tensor): ... class CharTensor(Tensor): ... class ByteTensor(Tensor): ... class BoolTensor(Tensor): ... 通过构造函数可以直接创建张量，不过 Pytorch 实现了许多工厂函数，可以实现定制化的 Tensor 生成，一般， Pytorch 推荐我们使用各类工厂函数生成张量，如下： ","date":"2025-12-30","objectID":"/posts/pytorch-%E5%BC%A0%E9%87%8F/:2:0","tags":null,"title":"Pytorch 张量","uri":"/posts/pytorch-%E5%BC%A0%E9%87%8F/#创建张量"},{"categories":["深度学习"],"collections":["pytorch"],"content":"11.1 从已有数据创建\r可以从 list 和 np.ndarray 创建张量。 使用 torh.tensor 创建张量时，总是新建一个 tensor，和原 numpy 数组不共享内存`。 import torch import numpy as np torch.tensor([[1, 2], [3, 4]], dtype = torch.float32) tensor([[1., 2.],\r[3., 4.]])\r使用 torch.as_tensor 创建张量时，如果指定了 dtype 或者 device 使用了 GPU 就会创建新的张量，否则共享内存。 torch.as_tensor([5, 6, 7]) tensor([5, 6, 7])\r使用 torch.from_numpy 创建张量时，一定会共享内存，创建时不可以指定 dtype 和 device。 np_arr = np.array([1, 2, 3], dtype = np.float32) torch.from_numpy(np_arr) tensor([1., 2., 3.])\r常数张量\r可以创建全部为常数的张量。 torch.zeros((2, 3)) tensor([[0., 0., 0.],\r[0., 0., 0.]])\rtorch.ones((3, 2), dtype = torch.int64) tensor([[1, 1],\r[1, 1],\r[1, 1]])\rtorch.full((2, 2), 3.14) tensor([[3.1400, 3.1400],\r[3.1400, 3.1400]])\re = torch.eye(3, 4) e tensor([[1., 0., 0., 0.],\r[0., 1., 0., 0.],\r[0., 0., 1., 0.]])\rtorch.zeros_like(e) tensor([[0., 0., 0., 0.],\r[0., 0., 0., 0.],\r[0., 0., 0., 0.]])\rtorch.ones_like(e) tensor([[1., 1., 1., 1.],\r[1., 1., 1., 1.],\r[1., 1., 1., 1.]])\rtorch.full_like(e, -1.0) tensor([[-1., -1., -1., -1.],\r[-1., -1., -1., -1.],\r[-1., -1., -1., -1.]])\r未初始化的张量\r未初始化的张量只分配内存，不初始化数值，因而存储的数值是随机的。 torch.empty((3, 3)) tensor([[0., 0., 0.],\r[0., 0., 0.],\r[0., 0., 0.]])\rtorch.empty_like(e) tensor([[-3.9431e-34, 1.8133e-42, 0.0000e+00, 0.0000e+00],\r[ 0.0000e+00, 0.0000e+00, 0.0000e+00, 0.0000e+00],\r[ 0.0000e+00, 0.0000e+00, 0.0000e+00, 0.0000e+00]])\r序列张量\r可以按照特定的序列生成张量。 # 等差数列：从 1.0 开始到 5.0 结束（不包含 5.0），步长为 0.5 torch.arange(1.0, 5.0, 0.5) tensor([1.0000, 1.5000, 2.0000, 2.5000, 3.0000, 3.5000, 4.0000, 4.5000])\r# 线性等分：从 0 到 1，均匀分成 5 个点（包含终点 1） torch.linspace(0, 1, steps=5) tensor([0.0000, 0.2500, 0.5000, 0.7500, 1.0000])\r# 对数等分：生成 10^0 到 10^3 之间的 4 个点 torch.logspace(0, 3, steps=4) tensor([ 1., 10., 100., 1000.])\r随机张量\r可以按照特定的分布随机生成张量。 # 均匀分布：从 [0, 1) 的均匀分布中采样，形状为 (2, 2) torch.rand((2, 2)) tensor([[0.9802, 0.4825],\r[0.1893, 0.8265]])\r# 正态分布：从正态分布 N(mean=0.0, std=0.5) 中采样，生成一维 Tensor torch.normal(mean=0.0, std=0.5, size=(3,)) tensor([ 0.7416, -1.2993, -0.4609])\r# 标准正态分布：从标准正态分布 N(0, 1) 中采样，形状为 (2, 2) torch.randn((2, 2)) tensor([[ 0.2501, -1.0411],\r[-0.4427, 1.6018]])\r# 生成 [0, 10) 范围内的随机整数，形状为 (3, 3) torch.randint(0, 10, (3, 3)) tensor([[7, 4, 5],\r[5, 5, 9],\r[0, 2, 2]])\r# 生成 0 到 9 的随机排列（常用于打乱索引） torch.randperm(10) tensor([1, 8, 3, 4, 2, 0, 7, 6, 9, 5])\r","date":"2025-12-30","objectID":"/posts/pytorch-%E5%BC%A0%E9%87%8F/:2:1","tags":null,"title":"Pytorch 张量","uri":"/posts/pytorch-%E5%BC%A0%E9%87%8F/#从已有数据创建"},{"categories":["深度学习"],"collections":["pytorch"],"content":"11.1 从已有数据创建\r可以从 list 和 np.ndarray 创建张量。 使用 torh.tensor 创建张量时，总是新建一个 tensor，和原 numpy 数组不共享内存`。 import torch import numpy as np torch.tensor([[1, 2], [3, 4]], dtype = torch.float32) tensor([[1., 2.],\r[3., 4.]])\r使用 torch.as_tensor 创建张量时，如果指定了 dtype 或者 device 使用了 GPU 就会创建新的张量，否则共享内存。 torch.as_tensor([5, 6, 7]) tensor([5, 6, 7])\r使用 torch.from_numpy 创建张量时，一定会共享内存，创建时不可以指定 dtype 和 device。 np_arr = np.array([1, 2, 3], dtype = np.float32) torch.from_numpy(np_arr) tensor([1., 2., 3.])\r常数张量\r可以创建全部为常数的张量。 torch.zeros((2, 3)) tensor([[0., 0., 0.],\r[0., 0., 0.]])\rtorch.ones((3, 2), dtype = torch.int64) tensor([[1, 1],\r[1, 1],\r[1, 1]])\rtorch.full((2, 2), 3.14) tensor([[3.1400, 3.1400],\r[3.1400, 3.1400]])\re = torch.eye(3, 4) e tensor([[1., 0., 0., 0.],\r[0., 1., 0., 0.],\r[0., 0., 1., 0.]])\rtorch.zeros_like(e) tensor([[0., 0., 0., 0.],\r[0., 0., 0., 0.],\r[0., 0., 0., 0.]])\rtorch.ones_like(e) tensor([[1., 1., 1., 1.],\r[1., 1., 1., 1.],\r[1., 1., 1., 1.]])\rtorch.full_like(e, -1.0) tensor([[-1., -1., -1., -1.],\r[-1., -1., -1., -1.],\r[-1., -1., -1., -1.]])\r未初始化的张量\r未初始化的张量只分配内存，不初始化数值，因而存储的数值是随机的。 torch.empty((3, 3)) tensor([[0., 0., 0.],\r[0., 0., 0.],\r[0., 0., 0.]])\rtorch.empty_like(e) tensor([[-3.9431e-34, 1.8133e-42, 0.0000e+00, 0.0000e+00],\r[ 0.0000e+00, 0.0000e+00, 0.0000e+00, 0.0000e+00],\r[ 0.0000e+00, 0.0000e+00, 0.0000e+00, 0.0000e+00]])\r序列张量\r可以按照特定的序列生成张量。 # 等差数列：从 1.0 开始到 5.0 结束（不包含 5.0），步长为 0.5 torch.arange(1.0, 5.0, 0.5) tensor([1.0000, 1.5000, 2.0000, 2.5000, 3.0000, 3.5000, 4.0000, 4.5000])\r# 线性等分：从 0 到 1，均匀分成 5 个点（包含终点 1） torch.linspace(0, 1, steps=5) tensor([0.0000, 0.2500, 0.5000, 0.7500, 1.0000])\r# 对数等分：生成 10^0 到 10^3 之间的 4 个点 torch.logspace(0, 3, steps=4) tensor([ 1., 10., 100., 1000.])\r随机张量\r可以按照特定的分布随机生成张量。 # 均匀分布：从 [0, 1) 的均匀分布中采样，形状为 (2, 2) torch.rand((2, 2)) tensor([[0.9802, 0.4825],\r[0.1893, 0.8265]])\r# 正态分布：从正态分布 N(mean=0.0, std=0.5) 中采样，生成一维 Tensor torch.normal(mean=0.0, std=0.5, size=(3,)) tensor([ 0.7416, -1.2993, -0.4609])\r# 标准正态分布：从标准正态分布 N(0, 1) 中采样，形状为 (2, 2) torch.randn((2, 2)) tensor([[ 0.2501, -1.0411],\r[-0.4427, 1.6018]])\r# 生成 [0, 10) 范围内的随机整数，形状为 (3, 3) torch.randint(0, 10, (3, 3)) tensor([[7, 4, 5],\r[5, 5, 9],\r[0, 2, 2]])\r# 生成 0 到 9 的随机排列（常用于打乱索引） torch.randperm(10) tensor([1, 8, 3, 4, 2, 0, 7, 6, 9, 5])\r","date":"2025-12-30","objectID":"/posts/pytorch-%E5%BC%A0%E9%87%8F/:2:1","tags":null,"title":"Pytorch 张量","uri":"/posts/pytorch-%E5%BC%A0%E9%87%8F/#常数张量"},{"categories":["深度学习"],"collections":["pytorch"],"content":"11.1 从已有数据创建\r可以从 list 和 np.ndarray 创建张量。 使用 torh.tensor 创建张量时，总是新建一个 tensor，和原 numpy 数组不共享内存`。 import torch import numpy as np torch.tensor([[1, 2], [3, 4]], dtype = torch.float32) tensor([[1., 2.],\r[3., 4.]])\r使用 torch.as_tensor 创建张量时，如果指定了 dtype 或者 device 使用了 GPU 就会创建新的张量，否则共享内存。 torch.as_tensor([5, 6, 7]) tensor([5, 6, 7])\r使用 torch.from_numpy 创建张量时，一定会共享内存，创建时不可以指定 dtype 和 device。 np_arr = np.array([1, 2, 3], dtype = np.float32) torch.from_numpy(np_arr) tensor([1., 2., 3.])\r常数张量\r可以创建全部为常数的张量。 torch.zeros((2, 3)) tensor([[0., 0., 0.],\r[0., 0., 0.]])\rtorch.ones((3, 2), dtype = torch.int64) tensor([[1, 1],\r[1, 1],\r[1, 1]])\rtorch.full((2, 2), 3.14) tensor([[3.1400, 3.1400],\r[3.1400, 3.1400]])\re = torch.eye(3, 4) e tensor([[1., 0., 0., 0.],\r[0., 1., 0., 0.],\r[0., 0., 1., 0.]])\rtorch.zeros_like(e) tensor([[0., 0., 0., 0.],\r[0., 0., 0., 0.],\r[0., 0., 0., 0.]])\rtorch.ones_like(e) tensor([[1., 1., 1., 1.],\r[1., 1., 1., 1.],\r[1., 1., 1., 1.]])\rtorch.full_like(e, -1.0) tensor([[-1., -1., -1., -1.],\r[-1., -1., -1., -1.],\r[-1., -1., -1., -1.]])\r未初始化的张量\r未初始化的张量只分配内存，不初始化数值，因而存储的数值是随机的。 torch.empty((3, 3)) tensor([[0., 0., 0.],\r[0., 0., 0.],\r[0., 0., 0.]])\rtorch.empty_like(e) tensor([[-3.9431e-34, 1.8133e-42, 0.0000e+00, 0.0000e+00],\r[ 0.0000e+00, 0.0000e+00, 0.0000e+00, 0.0000e+00],\r[ 0.0000e+00, 0.0000e+00, 0.0000e+00, 0.0000e+00]])\r序列张量\r可以按照特定的序列生成张量。 # 等差数列：从 1.0 开始到 5.0 结束（不包含 5.0），步长为 0.5 torch.arange(1.0, 5.0, 0.5) tensor([1.0000, 1.5000, 2.0000, 2.5000, 3.0000, 3.5000, 4.0000, 4.5000])\r# 线性等分：从 0 到 1，均匀分成 5 个点（包含终点 1） torch.linspace(0, 1, steps=5) tensor([0.0000, 0.2500, 0.5000, 0.7500, 1.0000])\r# 对数等分：生成 10^0 到 10^3 之间的 4 个点 torch.logspace(0, 3, steps=4) tensor([ 1., 10., 100., 1000.])\r随机张量\r可以按照特定的分布随机生成张量。 # 均匀分布：从 [0, 1) 的均匀分布中采样，形状为 (2, 2) torch.rand((2, 2)) tensor([[0.9802, 0.4825],\r[0.1893, 0.8265]])\r# 正态分布：从正态分布 N(mean=0.0, std=0.5) 中采样，生成一维 Tensor torch.normal(mean=0.0, std=0.5, size=(3,)) tensor([ 0.7416, -1.2993, -0.4609])\r# 标准正态分布：从标准正态分布 N(0, 1) 中采样，形状为 (2, 2) torch.randn((2, 2)) tensor([[ 0.2501, -1.0411],\r[-0.4427, 1.6018]])\r# 生成 [0, 10) 范围内的随机整数，形状为 (3, 3) torch.randint(0, 10, (3, 3)) tensor([[7, 4, 5],\r[5, 5, 9],\r[0, 2, 2]])\r# 生成 0 到 9 的随机排列（常用于打乱索引） torch.randperm(10) tensor([1, 8, 3, 4, 2, 0, 7, 6, 9, 5])\r","date":"2025-12-30","objectID":"/posts/pytorch-%E5%BC%A0%E9%87%8F/:2:1","tags":null,"title":"Pytorch 张量","uri":"/posts/pytorch-%E5%BC%A0%E9%87%8F/#未初始化的张量"},{"categories":["深度学习"],"collections":["pytorch"],"content":"11.1 从已有数据创建\r可以从 list 和 np.ndarray 创建张量。 使用 torh.tensor 创建张量时，总是新建一个 tensor，和原 numpy 数组不共享内存`。 import torch import numpy as np torch.tensor([[1, 2], [3, 4]], dtype = torch.float32) tensor([[1., 2.],\r[3., 4.]])\r使用 torch.as_tensor 创建张量时，如果指定了 dtype 或者 device 使用了 GPU 就会创建新的张量，否则共享内存。 torch.as_tensor([5, 6, 7]) tensor([5, 6, 7])\r使用 torch.from_numpy 创建张量时，一定会共享内存，创建时不可以指定 dtype 和 device。 np_arr = np.array([1, 2, 3], dtype = np.float32) torch.from_numpy(np_arr) tensor([1., 2., 3.])\r常数张量\r可以创建全部为常数的张量。 torch.zeros((2, 3)) tensor([[0., 0., 0.],\r[0., 0., 0.]])\rtorch.ones((3, 2), dtype = torch.int64) tensor([[1, 1],\r[1, 1],\r[1, 1]])\rtorch.full((2, 2), 3.14) tensor([[3.1400, 3.1400],\r[3.1400, 3.1400]])\re = torch.eye(3, 4) e tensor([[1., 0., 0., 0.],\r[0., 1., 0., 0.],\r[0., 0., 1., 0.]])\rtorch.zeros_like(e) tensor([[0., 0., 0., 0.],\r[0., 0., 0., 0.],\r[0., 0., 0., 0.]])\rtorch.ones_like(e) tensor([[1., 1., 1., 1.],\r[1., 1., 1., 1.],\r[1., 1., 1., 1.]])\rtorch.full_like(e, -1.0) tensor([[-1., -1., -1., -1.],\r[-1., -1., -1., -1.],\r[-1., -1., -1., -1.]])\r未初始化的张量\r未初始化的张量只分配内存，不初始化数值，因而存储的数值是随机的。 torch.empty((3, 3)) tensor([[0., 0., 0.],\r[0., 0., 0.],\r[0., 0., 0.]])\rtorch.empty_like(e) tensor([[-3.9431e-34, 1.8133e-42, 0.0000e+00, 0.0000e+00],\r[ 0.0000e+00, 0.0000e+00, 0.0000e+00, 0.0000e+00],\r[ 0.0000e+00, 0.0000e+00, 0.0000e+00, 0.0000e+00]])\r序列张量\r可以按照特定的序列生成张量。 # 等差数列：从 1.0 开始到 5.0 结束（不包含 5.0），步长为 0.5 torch.arange(1.0, 5.0, 0.5) tensor([1.0000, 1.5000, 2.0000, 2.5000, 3.0000, 3.5000, 4.0000, 4.5000])\r# 线性等分：从 0 到 1，均匀分成 5 个点（包含终点 1） torch.linspace(0, 1, steps=5) tensor([0.0000, 0.2500, 0.5000, 0.7500, 1.0000])\r# 对数等分：生成 10^0 到 10^3 之间的 4 个点 torch.logspace(0, 3, steps=4) tensor([ 1., 10., 100., 1000.])\r随机张量\r可以按照特定的分布随机生成张量。 # 均匀分布：从 [0, 1) 的均匀分布中采样，形状为 (2, 2) torch.rand((2, 2)) tensor([[0.9802, 0.4825],\r[0.1893, 0.8265]])\r# 正态分布：从正态分布 N(mean=0.0, std=0.5) 中采样，生成一维 Tensor torch.normal(mean=0.0, std=0.5, size=(3,)) tensor([ 0.7416, -1.2993, -0.4609])\r# 标准正态分布：从标准正态分布 N(0, 1) 中采样，形状为 (2, 2) torch.randn((2, 2)) tensor([[ 0.2501, -1.0411],\r[-0.4427, 1.6018]])\r# 生成 [0, 10) 范围内的随机整数，形状为 (3, 3) torch.randint(0, 10, (3, 3)) tensor([[7, 4, 5],\r[5, 5, 9],\r[0, 2, 2]])\r# 生成 0 到 9 的随机排列（常用于打乱索引） torch.randperm(10) tensor([1, 8, 3, 4, 2, 0, 7, 6, 9, 5])\r","date":"2025-12-30","objectID":"/posts/pytorch-%E5%BC%A0%E9%87%8F/:2:1","tags":null,"title":"Pytorch 张量","uri":"/posts/pytorch-%E5%BC%A0%E9%87%8F/#序列张量"},{"categories":["深度学习"],"collections":["pytorch"],"content":"11.1 从已有数据创建\r可以从 list 和 np.ndarray 创建张量。 使用 torh.tensor 创建张量时，总是新建一个 tensor，和原 numpy 数组不共享内存`。 import torch import numpy as np torch.tensor([[1, 2], [3, 4]], dtype = torch.float32) tensor([[1., 2.],\r[3., 4.]])\r使用 torch.as_tensor 创建张量时，如果指定了 dtype 或者 device 使用了 GPU 就会创建新的张量，否则共享内存。 torch.as_tensor([5, 6, 7]) tensor([5, 6, 7])\r使用 torch.from_numpy 创建张量时，一定会共享内存，创建时不可以指定 dtype 和 device。 np_arr = np.array([1, 2, 3], dtype = np.float32) torch.from_numpy(np_arr) tensor([1., 2., 3.])\r常数张量\r可以创建全部为常数的张量。 torch.zeros((2, 3)) tensor([[0., 0., 0.],\r[0., 0., 0.]])\rtorch.ones((3, 2), dtype = torch.int64) tensor([[1, 1],\r[1, 1],\r[1, 1]])\rtorch.full((2, 2), 3.14) tensor([[3.1400, 3.1400],\r[3.1400, 3.1400]])\re = torch.eye(3, 4) e tensor([[1., 0., 0., 0.],\r[0., 1., 0., 0.],\r[0., 0., 1., 0.]])\rtorch.zeros_like(e) tensor([[0., 0., 0., 0.],\r[0., 0., 0., 0.],\r[0., 0., 0., 0.]])\rtorch.ones_like(e) tensor([[1., 1., 1., 1.],\r[1., 1., 1., 1.],\r[1., 1., 1., 1.]])\rtorch.full_like(e, -1.0) tensor([[-1., -1., -1., -1.],\r[-1., -1., -1., -1.],\r[-1., -1., -1., -1.]])\r未初始化的张量\r未初始化的张量只分配内存，不初始化数值，因而存储的数值是随机的。 torch.empty((3, 3)) tensor([[0., 0., 0.],\r[0., 0., 0.],\r[0., 0., 0.]])\rtorch.empty_like(e) tensor([[-3.9431e-34, 1.8133e-42, 0.0000e+00, 0.0000e+00],\r[ 0.0000e+00, 0.0000e+00, 0.0000e+00, 0.0000e+00],\r[ 0.0000e+00, 0.0000e+00, 0.0000e+00, 0.0000e+00]])\r序列张量\r可以按照特定的序列生成张量。 # 等差数列：从 1.0 开始到 5.0 结束（不包含 5.0），步长为 0.5 torch.arange(1.0, 5.0, 0.5) tensor([1.0000, 1.5000, 2.0000, 2.5000, 3.0000, 3.5000, 4.0000, 4.5000])\r# 线性等分：从 0 到 1，均匀分成 5 个点（包含终点 1） torch.linspace(0, 1, steps=5) tensor([0.0000, 0.2500, 0.5000, 0.7500, 1.0000])\r# 对数等分：生成 10^0 到 10^3 之间的 4 个点 torch.logspace(0, 3, steps=4) tensor([ 1., 10., 100., 1000.])\r随机张量\r可以按照特定的分布随机生成张量。 # 均匀分布：从 [0, 1) 的均匀分布中采样，形状为 (2, 2) torch.rand((2, 2)) tensor([[0.9802, 0.4825],\r[0.1893, 0.8265]])\r# 正态分布：从正态分布 N(mean=0.0, std=0.5) 中采样，生成一维 Tensor torch.normal(mean=0.0, std=0.5, size=(3,)) tensor([ 0.7416, -1.2993, -0.4609])\r# 标准正态分布：从标准正态分布 N(0, 1) 中采样，形状为 (2, 2) torch.randn((2, 2)) tensor([[ 0.2501, -1.0411],\r[-0.4427, 1.6018]])\r# 生成 [0, 10) 范围内的随机整数，形状为 (3, 3) torch.randint(0, 10, (3, 3)) tensor([[7, 4, 5],\r[5, 5, 9],\r[0, 2, 2]])\r# 生成 0 到 9 的随机排列（常用于打乱索引） torch.randperm(10) tensor([1, 8, 3, 4, 2, 0, 7, 6, 9, 5])\r","date":"2025-12-30","objectID":"/posts/pytorch-%E5%BC%A0%E9%87%8F/:2:1","tags":null,"title":"Pytorch 张量","uri":"/posts/pytorch-%E5%BC%A0%E9%87%8F/#随机张量"},{"categories":["深度学习"],"collections":["pytorch"],"content":"第 12 节 张量运算\r","date":"2025-12-30","objectID":"/posts/pytorch-%E5%BC%A0%E9%87%8F/:3:0","tags":null,"title":"Pytorch 张量","uri":"/posts/pytorch-%E5%BC%A0%E9%87%8F/#张量运算"},{"categories":["深度学习"],"collections":["pytorch"],"content":"12.1 基础属性\r张量有以下常见的基础属性： import torch a = torch.ones(4,3) a tensor([[1., 1., 1.],\r[1., 1., 1.],\r[1., 1., 1.],\r[1., 1., 1.]])\r# 张量的维度数 a.ndim 2\r# 同 ndim a.dim() 2\r# 返回张量每个维度的大小 a.shape torch.Size([4, 3])\r# 和 shape 一样，返回尺寸 a.size(0) 4\r# 返回第 0 维大小 len(a) 4\r# 返回元素总数 a.numel() 12\r# 张量中元素的数据类型 a.dtype torch.float32\r# 张量所在设备（CPU / GPU） a.device device(type='cpu')\rprint(torch.cuda.is_available()) True\ra.to('cuda'), a.cpu(), a.cuda() (tensor([[1., 1., 1.],\r[1., 1., 1.],\r[1., 1., 1.],\r[1., 1., 1.]], device='cuda:0'),\rtensor([[1., 1., 1.],\r[1., 1., 1.],\r[1., 1., 1.],\r[1., 1., 1.]]),\rtensor([[1., 1., 1.],\r[1., 1., 1.],\r[1., 1., 1.],\r[1., 1., 1.]], device='cuda:0'))\r# 是否参与反向传播 a.requires_grad False\r# 反向传播后保存的梯度 a.grad # 切断梯度但共享数据 a.detach() tensor([[1., 1., 1.],\r[1., 1., 1.],\r[1., 1., 1.],\r[1., 1., 1.]])\r","date":"2025-12-30","objectID":"/posts/pytorch-%E5%BC%A0%E9%87%8F/:3:1","tags":null,"title":"Pytorch 张量","uri":"/posts/pytorch-%E5%BC%A0%E9%87%8F/#基础属性"},{"categories":["深度学习"],"collections":["pytorch"],"content":"12.2 尺寸变换\r张量可以改变其形状： # 改变形状，内存不连续则拷贝 a.reshape([2, -1]) tensor([[1., 1., 1., 1., 1., 1.],\r[1., 1., 1., 1., 1., 1.]])\r# 改变形状，不拷贝，要求内存连续 a.view([3, -1]) tensor([[1., 1., 1., 1.],\r[1., 1., 1., 1.],\r[1., 1., 1., 1.]])\r# 改变形状，拷贝 a.view([3, -1]).clone() tensor([[1., 1., 1., 1.],\r[1., 1., 1., 1.],\r[1., 1., 1., 1.]])\r# 增加一个维度 a.unsqueeze(0).shape, a.unsqueeze(1).shape, a.unsqueeze(2).shape (torch.Size([1, 4, 3]), torch.Size([4, 1, 3]), torch.Size([4, 3, 1]))\r# 去掉大小为 1 的维度 b = a.unsqueeze(0).clone() b.shape, b.squeeze().shape (torch.Size([1, 4, 3]), torch.Size([4, 3]))\r# 交换两个维度 b.shape, b.transpose(0,1).shape, b.transpose(1,2).shape (torch.Size([1, 4, 3]), torch.Size([4, 1, 3]), torch.Size([1, 3, 4]))\r# 任意重排维度 b.shape, b.permute(2,0,1).shape (torch.Size([1, 4, 3]), torch.Size([3, 1, 4]))\r# 转置矩阵 a.T tensor([[1., 1., 1., 1.],\r[1., 1., 1., 1.],\r[1., 1., 1., 1.]])\r# 拉平成 1D a.flatten() tensor([1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1.])\r","date":"2025-12-30","objectID":"/posts/pytorch-%E5%BC%A0%E9%87%8F/:3:2","tags":null,"title":"Pytorch 张量","uri":"/posts/pytorch-%E5%BC%A0%E9%87%8F/#尺寸变换"},{"categories":["深度学习"],"collections":["pytorch"],"content":"12.3 类型变换\r张量可以改变其类型： type(a), type(a.numpy()), type(a.tolist()) (torch.Tensor, numpy.ndarray, list)\ra.double().dtype, a.float().dtype, a.long().dtype, a.int().dtype, a.half().dtype (torch.float64, torch.float32, torch.int64, torch.int32, torch.float16)\r对于标量，有如下的特殊运算： a = torch.tensor([3.5]) a, a.item(), float(a), int(a) (tensor([3.5000]), 3.5, 3.5, 3)\r","date":"2025-12-30","objectID":"/posts/pytorch-%E5%BC%A0%E9%87%8F/:3:3","tags":null,"title":"Pytorch 张量","uri":"/posts/pytorch-%E5%BC%A0%E9%87%8F/#类型变换"},{"categories":["深度学习"],"collections":["pytorch"],"content":"12.4 运算符\r张量有如下的运算符，常见的运算符经过了运算符重载，可以直接用符号表示： x = torch.Tensor([1.0,2,3,4,5,6]).view(2,3) y = torch.Tensor([1,3,5,7,9,11]).view(2,3) x + y, x - y, x * y, x / y, x ** y, x @ y.T (tensor([[ 2., 5., 8.],\r[11., 14., 17.]]),\rtensor([[ 0., -1., -2.],\r[-3., -4., -5.]]),\rtensor([[ 1., 6., 15.],\r[28., 45., 66.]]),\rtensor([[1.0000, 0.6667, 0.6000],\r[0.5714, 0.5556, 0.5455]]),\rtensor([[1.0000e+00, 8.0000e+00, 2.4300e+02],\r[1.6384e+04, 1.9531e+06, 3.6280e+08]]),\rtensor([[ 22., 58.],\r[ 49., 139.]]))\r# e^x x.exp() tensor([[ 2.7183, 7.3891, 20.0855],\r[ 54.5981, 148.4132, 403.4288]])\r# 平均值 x.mean(), x.mean(axis = 0), x.mean(axis = 1) (tensor(3.5000), tensor([2.5000, 3.5000, 4.5000]), tensor([2., 5.]))\r# 方差 x.var(), x.var(axis = 0), x.var(axis = 1) (tensor(3.5000), tensor([4.5000, 4.5000, 4.5000]), tensor([1., 1.]))\r# 标准差 x.std(), x.std(axis = 0), x.std(axis = 1) (tensor(1.8708), tensor([2.1213, 2.1213, 2.1213]), tensor([1., 1.]))\r# L1 范数 x.abs().sum() tensor(21.)\r# L2 范数 x.norm() tensor(9.5394)\r# 求和 x.sum(), x.sum(axis = 0), x.sum(axis = 1) (tensor(21.), tensor([5., 7., 9.]), tensor([ 6., 15.]))\r# 求累计和 x.cumsum(axis = 0), x.cumsum(axis = 1) (tensor([[1., 2., 3.],\r[5., 7., 9.]]),\rtensor([[ 1., 3., 6.],\r[ 4., 9., 15.]]))\r# 求最大 x.max(), x.max(axis = 0), x.max(axis = 1) (tensor(6.),\rtorch.return_types.max(\rvalues=tensor([4., 5., 6.]),\rindices=tensor([1, 1, 1])),\rtorch.return_types.max(\rvalues=tensor([3., 6.]),\rindices=tensor([2, 2])))\r# 求最小 x.min(), x.min(axis = 0), x.min(axis = 1) (tensor(1.),\rtorch.return_types.min(\rvalues=tensor([1., 2., 3.]),\rindices=tensor([0, 0, 0])),\rtorch.return_types.min(\rvalues=tensor([1., 4.]),\rindices=tensor([0, 0])))\r# 最大值索引 x.argmax(), x.argmax(axis = 0), x.argmax(axis = 1) (tensor(5), tensor([1, 1, 1]), tensor([2, 2]))\r# 最小值索引 x.argmin(), x.argmin(axis = 0), x.argmin(axis = 1) (tensor(0), tensor([0, 0, 0]), tensor([0, 0]))\r# 是否存在 True x.any(), x.any(axis = 0), x.any(axis = 1) (tensor(True), tensor([True, True, True]), tensor([True, True]))\r# 是否全 True x.all(), x.all(axis = 0), x.all(axis = 1) (tensor(True), tensor([True, True, True]), tensor([True, True]))\r","date":"2025-12-30","objectID":"/posts/pytorch-%E5%BC%A0%E9%87%8F/:3:4","tags":null,"title":"Pytorch 张量","uri":"/posts/pytorch-%E5%BC%A0%E9%87%8F/#运算符"},{"categories":["深度学习"],"collections":["pytorch"],"content":"12.5 拼接\r张量可以进行拼接，两个拼接的张量要求在其他维度上的 shape 是一样的。 torch.cat((x, y), axis = 0), torch.cat((x, y), axis = 1) (tensor([[ 1., 2., 3.],\r[ 4., 5., 6.],\r[ 1., 3., 5.],\r[ 7., 9., 11.]]),\rtensor([[ 1., 2., 3., 1., 3., 5.],\r[ 4., 5., 6., 7., 9., 11.]]))\r","date":"2025-12-30","objectID":"/posts/pytorch-%E5%BC%A0%E9%87%8F/:3:5","tags":null,"title":"Pytorch 张量","uri":"/posts/pytorch-%E5%BC%A0%E9%87%8F/#拼接"},{"categories":["深度学习"],"collections":["pytorch"],"content":"12.6 广播\r广播从最后一维开始对齐，两个维度必须相等或者广播前的维度为 1。 a = torch.arange(3).reshape((3, 1)) b = torch.arange(2).reshape((1, 1, 2)) a, b (tensor([[0],\r[1],\r[2]]),\rtensor([[[0, 1]]]))\ra + b, (a + b).shape (tensor([[[0, 1],\r[1, 2],\r[2, 3]]]),\rtorch.Size([1, 3, 2]))\r","date":"2025-12-30","objectID":"/posts/pytorch-%E5%BC%A0%E9%87%8F/:3:6","tags":null,"title":"Pytorch 张量","uri":"/posts/pytorch-%E5%BC%A0%E9%87%8F/#广播"},{"categories":["深度学习"],"collections":["pytorch"],"content":"12.7 索引和切片\rpytorch 的索引和切片与 numpy 相同，索引和切片与原变量共享相同的内存空间。 X = torch.arange(12, dtype = torch.float32).view((3,4)) X tensor([[ 0., 1., 2., 3.],\r[ 4., 5., 6., 7.],\r[ 8., 9., 10., 11.]])\rx[-1], x[1:3] (tensor([4., 5., 6.]), tensor([[4., 5., 6.]]))\rX[1, 2] = 9 X tensor([[ 0., 1., 2., 3.],\r[ 4., 5., 9., 7.],\r[ 8., 9., 10., 11.]])\rX[0:2, :] = 12 X tensor([[12., 12., 12., 12.],\r[12., 12., 12., 12.],\r[ 8., 9., 10., 11.]])\r","date":"2025-12-30","objectID":"/posts/pytorch-%E5%BC%A0%E9%87%8F/:3:7","tags":null,"title":"Pytorch 张量","uri":"/posts/pytorch-%E5%BC%A0%E9%87%8F/#索引和切片"},{"categories":[],"collections":["深度学习"],"content":"第 46 节 概述\r当我们学习深度学习并接触到 Pytorch 之类的框架时，我们会思考，框架语法为什么应该那样写？ 实现了前向传播的过程，给了训练数据，为什么模型就可以自动训练了？反向传播是如何实现的？ 当我们把一部分官方实现的方法替换成我们自己的方法时，模型还能不能正确计算？什么情况下会报错？ 其他人将 numpy、scikit-learn 和 pytorch 混合使用，什么样的混合使用是允许的，我们去使用会不会出错？ 其他人在训练时手动控制 cuda 的计算，自己定义一些算子，我们想要修改应该如何？ 本文的内容来自于《深度学习入门 2：自制框架》，这本书搭建了一个简易的深度学习框架 DeZero，语法类似 pytorch，通过一步步深入浅出的教导，我们知晓了 pytorch 之类的框架是如何从代码层面实现的，理解各种语法为什么要那样写，后面修改自己的模型时也会更加底气十足。 本人学习深度学习相关知识已经很久，理论知识掌握了许多，但是涉及到代码层面，总是会陷入去记忆别人写的代码这种怪圈，说到底其实本人并不懂这个框架，只是会用而已，只是一种别人代码这么用了所以我也可以这么用。 这本书一共 500 多页，如果有基础的话，全部翻看一遍其实并不会太久，几天时间就可以了，看完会有一种醍醐灌顶的感觉，赞叹 pytorch 之类的深度学习框架居然可以如此精妙，赞叹作者居然可以如此恰到好处的描绘出来。这本书从做工来看是非常精致的，内容的衔接也是做足了功夫，可见作者其实是倾注了很多心血的，作者花费的时间是远比本人看书的这几天时间要多的多的，不过因为作者的付出，世界上成千上万个像本人这样的学习者才能快速掌握相关的知识。 ","date":"2025-12-27","objectID":"/posts/%E6%B7%B1%E5%BA%A6%E5%AD%A6%E4%B9%A0%E5%9F%BA%E7%A1%80~%E8%87%AA%E5%88%B6%E6%A1%86%E6%9E%B6/:1:0","tags":null,"title":"深度学习基础~自制框架","uri":"/posts/%E6%B7%B1%E5%BA%A6%E5%AD%A6%E4%B9%A0%E5%9F%BA%E7%A1%80~%E8%87%AA%E5%88%B6%E6%A1%86%E6%9E%B6/#概述"},{"categories":[],"collections":["深度学习"],"content":"第 47 节 基础搭建\r首先，需要构建名为 Variable 的类，对标的是 pytorch 中的 Tensor class Variable: def __init__(self, data): self.data = data 然后，需要构建名为 Function 的类： class Function: def __call__(self, input): x = input.data y = self.forward(x) output = Variable(y) return output def forward(self, x): raise NotImplementedError() ","date":"2025-12-27","objectID":"/posts/%E6%B7%B1%E5%BA%A6%E5%AD%A6%E4%B9%A0%E5%9F%BA%E7%A1%80~%E8%87%AA%E5%88%B6%E6%A1%86%E6%9E%B6/:2:0","tags":null,"title":"深度学习基础~自制框架","uri":"/posts/%E6%B7%B1%E5%BA%A6%E5%AD%A6%E4%B9%A0%E5%9F%BA%E7%A1%80~%E8%87%AA%E5%88%B6%E6%A1%86%E6%9E%B6/#基础搭建"},{"categories":[],"collections":["深度学习"],"content":"第 48 节 反向传播\r","date":"2025-12-27","objectID":"/posts/%E6%B7%B1%E5%BA%A6%E5%AD%A6%E4%B9%A0%E5%9F%BA%E7%A1%80~%E8%87%AA%E5%88%B6%E6%A1%86%E6%9E%B6/:3:0","tags":null,"title":"深度学习基础~自制框架","uri":"/posts/%E6%B7%B1%E5%BA%A6%E5%AD%A6%E4%B9%A0%E5%9F%BA%E7%A1%80~%E8%87%AA%E5%88%B6%E6%A1%86%E6%9E%B6/#反向传播"},{"categories":[],"collections":["深度学习"],"content":"48.1 求导方法\r计算机程序求导的方法主要有 3 种： 数值微分：就是在下图中，让 $h$ 取一个极小的值计算出来的微分值的方法。使用计算机浮点数计算，存在精度丢失的缺点；神经网络参数众多，存在计算成本高的缺点。 符号微分：使用导数公式求导的方法，输入是式子，输出也是式子，被用在 Mathematica 和 MATLAB 等软件中。式子会变的臃肿，神经网络参数众多，计算成本高。 自动微分：采用链式法则求导的方法。自动微分可以大体分为两种：前向模式的自动微分和反向模式的自动微分。反向传播相当于反向模式的自动微分。 ","date":"2025-12-27","objectID":"/posts/%E6%B7%B1%E5%BA%A6%E5%AD%A6%E4%B9%A0%E5%9F%BA%E7%A1%80~%E8%87%AA%E5%88%B6%E6%A1%86%E6%9E%B6/:3:1","tags":null,"title":"深度学习基础~自制框架","uri":"/posts/%E6%B7%B1%E5%BA%A6%E5%AD%A6%E4%B9%A0%E5%9F%BA%E7%A1%80~%E8%87%AA%E5%88%B6%E6%A1%86%E6%9E%B6/#求导方法"},{"categories":[],"collections":["深度学习"],"content":"48.2 链式法则\r假设有一个函数 $y = F(x)$ ，这个函数 $F$ 由 3 个函数组成：$a=A(x)$ 、 $b=B(a)$ 和 $y=C(b)$ $y$ 对 $x$ 的导数可以表示为： $$ \\frac{\\mathrm{d} y}{\\mathrm{~d} x}=\\frac{\\mathrm{d} y}{\\mathrm{~d} y} \\frac{\\mathrm{~d} y}{\\mathrm{~d} b} \\frac{\\mathrm{~d} b}{\\mathrm{~d} a} \\frac{\\mathrm{~d} a}{\\mathrm{~d} x} $$ 可以按照下图的顺序依次计算： 于是可以构建出下面这张计算图： 从 $\\frac{\\mathrm{d} y}{\\mathrm{~d} y}(=1)$ 开始，计算它与 $\\frac{\\mathrm{d} y}{\\mathrm{~d} b}$ 的乘积。这里的 $\\frac{\\mathrm{d} y}{\\mathrm{~d} b}$ 是函数 $y=C(b)$ 的导数。 因此，如果用 $C^{\\prime}$ 表示函数 $C$ 的导函数，我们就可以把式子写成 $\\frac{\\mathrm{d} y}{\\mathrm{~d} b}=C^{\\prime}(b)$ 。 同样，有 $\\frac{\\mathrm{d} b}{\\mathrm{~d} a}=B^{\\prime}(a), \\frac{d a}{d x}=A^{\\prime}(x)$ ，于是计算图可以简化成下图： 信息\r有以下两点需要注意： 反向传播中， $y$ 对各变量的导数从右向左传播，$y$ 是“重要人物” 进行反向传播时需要用到正向传播中使用的数据 框架搭建\r增加反向传播的内容，需要在 Variable 中增加梯度： class Variable: def __init__(self, data): self.data = data self.grad = None # 增加梯度 Function 中需要保存输入的变量，增加反向传播方法： class Function: def __call__(self, input): x = input.data y = self.forward(x) output = Variable(y) self.input = input # 保存输入的变量 return output def forward(self, x): raise NotImplementedError() def backward(self, gy): # 新增反向传播方法 raise NotImplementedError() 示例\r","date":"2025-12-27","objectID":"/posts/%E6%B7%B1%E5%BA%A6%E5%AD%A6%E4%B9%A0%E5%9F%BA%E7%A1%80~%E8%87%AA%E5%88%B6%E6%A1%86%E6%9E%B6/:3:2","tags":null,"title":"深度学习基础~自制框架","uri":"/posts/%E6%B7%B1%E5%BA%A6%E5%AD%A6%E4%B9%A0%E5%9F%BA%E7%A1%80~%E8%87%AA%E5%88%B6%E6%A1%86%E6%9E%B6/#链式法则"},{"categories":[],"collections":["深度学习"],"content":"48.2 链式法则\r假设有一个函数 $y = F(x)$ ，这个函数 $F$ 由 3 个函数组成：$a=A(x)$ 、 $b=B(a)$ 和 $y=C(b)$ $y$ 对 $x$ 的导数可以表示为： $$ \\frac{\\mathrm{d} y}{\\mathrm{~d} x}=\\frac{\\mathrm{d} y}{\\mathrm{~d} y} \\frac{\\mathrm{~d} y}{\\mathrm{~d} b} \\frac{\\mathrm{~d} b}{\\mathrm{~d} a} \\frac{\\mathrm{~d} a}{\\mathrm{~d} x} $$ 可以按照下图的顺序依次计算： 于是可以构建出下面这张计算图： 从 $\\frac{\\mathrm{d} y}{\\mathrm{~d} y}(=1)$ 开始，计算它与 $\\frac{\\mathrm{d} y}{\\mathrm{~d} b}$ 的乘积。这里的 $\\frac{\\mathrm{d} y}{\\mathrm{~d} b}$ 是函数 $y=C(b)$ 的导数。 因此，如果用 $C^{\\prime}$ 表示函数 $C$ 的导函数，我们就可以把式子写成 $\\frac{\\mathrm{d} y}{\\mathrm{~d} b}=C^{\\prime}(b)$ 。 同样，有 $\\frac{\\mathrm{d} b}{\\mathrm{~d} a}=B^{\\prime}(a), \\frac{d a}{d x}=A^{\\prime}(x)$ ，于是计算图可以简化成下图： 信息\r有以下两点需要注意： 反向传播中， $y$ 对各变量的导数从右向左传播，$y$ 是“重要人物” 进行反向传播时需要用到正向传播中使用的数据 框架搭建\r增加反向传播的内容，需要在 Variable 中增加梯度： class Variable: def __init__(self, data): self.data = data self.grad = None # 增加梯度 Function 中需要保存输入的变量，增加反向传播方法： class Function: def __call__(self, input): x = input.data y = self.forward(x) output = Variable(y) self.input = input # 保存输入的变量 return output def forward(self, x): raise NotImplementedError() def backward(self, gy): # 新增反向传播方法 raise NotImplementedError() 示例\r","date":"2025-12-27","objectID":"/posts/%E6%B7%B1%E5%BA%A6%E5%AD%A6%E4%B9%A0%E5%9F%BA%E7%A1%80~%E8%87%AA%E5%88%B6%E6%A1%86%E6%9E%B6/:3:2","tags":null,"title":"深度学习基础~自制框架","uri":"/posts/%E6%B7%B1%E5%BA%A6%E5%AD%A6%E4%B9%A0%E5%9F%BA%E7%A1%80~%E8%87%AA%E5%88%B6%E6%A1%86%E6%9E%B6/#框架搭建"},{"categories":[],"collections":["深度学习"],"content":"48.2 链式法则\r假设有一个函数 $y = F(x)$ ，这个函数 $F$ 由 3 个函数组成：$a=A(x)$ 、 $b=B(a)$ 和 $y=C(b)$ $y$ 对 $x$ 的导数可以表示为： $$ \\frac{\\mathrm{d} y}{\\mathrm{~d} x}=\\frac{\\mathrm{d} y}{\\mathrm{~d} y} \\frac{\\mathrm{~d} y}{\\mathrm{~d} b} \\frac{\\mathrm{~d} b}{\\mathrm{~d} a} \\frac{\\mathrm{~d} a}{\\mathrm{~d} x} $$ 可以按照下图的顺序依次计算： 于是可以构建出下面这张计算图： 从 $\\frac{\\mathrm{d} y}{\\mathrm{~d} y}(=1)$ 开始，计算它与 $\\frac{\\mathrm{d} y}{\\mathrm{~d} b}$ 的乘积。这里的 $\\frac{\\mathrm{d} y}{\\mathrm{~d} b}$ 是函数 $y=C(b)$ 的导数。 因此，如果用 $C^{\\prime}$ 表示函数 $C$ 的导函数，我们就可以把式子写成 $\\frac{\\mathrm{d} y}{\\mathrm{~d} b}=C^{\\prime}(b)$ 。 同样，有 $\\frac{\\mathrm{d} b}{\\mathrm{~d} a}=B^{\\prime}(a), \\frac{d a}{d x}=A^{\\prime}(x)$ ，于是计算图可以简化成下图： 信息\r有以下两点需要注意： 反向传播中， $y$ 对各变量的导数从右向左传播，$y$ 是“重要人物” 进行反向传播时需要用到正向传播中使用的数据 框架搭建\r增加反向传播的内容，需要在 Variable 中增加梯度： class Variable: def __init__(self, data): self.data = data self.grad = None # 增加梯度 Function 中需要保存输入的变量，增加反向传播方法： class Function: def __call__(self, input): x = input.data y = self.forward(x) output = Variable(y) self.input = input # 保存输入的变量 return output def forward(self, x): raise NotImplementedError() def backward(self, gy): # 新增反向传播方法 raise NotImplementedError() 示例\r","date":"2025-12-27","objectID":"/posts/%E6%B7%B1%E5%BA%A6%E5%AD%A6%E4%B9%A0%E5%9F%BA%E7%A1%80~%E8%87%AA%E5%88%B6%E6%A1%86%E6%9E%B6/:3:2","tags":null,"title":"深度学习基础~自制框架","uri":"/posts/%E6%B7%B1%E5%BA%A6%E5%AD%A6%E4%B9%A0%E5%9F%BA%E7%A1%80~%E8%87%AA%E5%88%B6%E6%A1%86%E6%9E%B6/#示例"},{"categories":[],"collections":["深度学习"],"content":"48.3 动态计算图\r从函数的角度来看，变量是以输入和输出的形式存在的，函数的变量包括“输入变量”（input）和“输出变量”（output）。从变量的角度来看，变量是由函数“创造”的。也就是说，函数是变量的“父母”，是creator（创造者）。 计算图由函数和变量之间的“连接”构成，这个“连接”是在计算实际发生的时候形成的，称为动态计算图（Define-by-Run）。 信息\r因为形成了上面的连接，我们就可以使用计算机中的递归思想，自动构建反向传播的过程，无需每次手动反向传播。 框架搭建\r变量类进行如下修改：： class Variable: def __init__(self, data): self.data = data self.grad = None self.creator = None def set_creator(self, func): self.creator = func def backward(self): funcs = [self.creator] while funcs: f = funcs.pop() # 获取函数 x, y = f.input, f.output # 获取函数的输入 x.grad = f.backward(y.grad) # backward调用backward方法 if x.creator is not None: funcs.append(x.creator) # 将前一个函数添加到列表中 函数类进行如下修改： class Function: def __call__(self, input): x = input.data y = self.forward(x) output = Variable(y) output.set_creator(self) # 让输出变量保存创造者信息 self.input = input self.output = output # 也保存输出变量 return output def forward(self, x): raise NotImplementedError() def backward(self, gy): raise NotImplementedError() 示例\r","date":"2025-12-27","objectID":"/posts/%E6%B7%B1%E5%BA%A6%E5%AD%A6%E4%B9%A0%E5%9F%BA%E7%A1%80~%E8%87%AA%E5%88%B6%E6%A1%86%E6%9E%B6/:3:3","tags":null,"title":"深度学习基础~自制框架","uri":"/posts/%E6%B7%B1%E5%BA%A6%E5%AD%A6%E4%B9%A0%E5%9F%BA%E7%A1%80~%E8%87%AA%E5%88%B6%E6%A1%86%E6%9E%B6/#动态计算图"},{"categories":[],"collections":["深度学习"],"content":"48.3 动态计算图\r从函数的角度来看，变量是以输入和输出的形式存在的，函数的变量包括“输入变量”（input）和“输出变量”（output）。从变量的角度来看，变量是由函数“创造”的。也就是说，函数是变量的“父母”，是creator（创造者）。 计算图由函数和变量之间的“连接”构成，这个“连接”是在计算实际发生的时候形成的，称为动态计算图（Define-by-Run）。 信息\r因为形成了上面的连接，我们就可以使用计算机中的递归思想，自动构建反向传播的过程，无需每次手动反向传播。 框架搭建\r变量类进行如下修改：： class Variable: def __init__(self, data): self.data = data self.grad = None self.creator = None def set_creator(self, func): self.creator = func def backward(self): funcs = [self.creator] while funcs: f = funcs.pop() # 获取函数 x, y = f.input, f.output # 获取函数的输入 x.grad = f.backward(y.grad) # backward调用backward方法 if x.creator is not None: funcs.append(x.creator) # 将前一个函数添加到列表中 函数类进行如下修改： class Function: def __call__(self, input): x = input.data y = self.forward(x) output = Variable(y) output.set_creator(self) # 让输出变量保存创造者信息 self.input = input self.output = output # 也保存输出变量 return output def forward(self, x): raise NotImplementedError() def backward(self, gy): raise NotImplementedError() 示例\r","date":"2025-12-27","objectID":"/posts/%E6%B7%B1%E5%BA%A6%E5%AD%A6%E4%B9%A0%E5%9F%BA%E7%A1%80~%E8%87%AA%E5%88%B6%E6%A1%86%E6%9E%B6/:3:3","tags":null,"title":"深度学习基础~自制框架","uri":"/posts/%E6%B7%B1%E5%BA%A6%E5%AD%A6%E4%B9%A0%E5%9F%BA%E7%A1%80~%E8%87%AA%E5%88%B6%E6%A1%86%E6%9E%B6/#框架搭建-1"},{"categories":[],"collections":["深度学习"],"content":"48.3 动态计算图\r从函数的角度来看，变量是以输入和输出的形式存在的，函数的变量包括“输入变量”（input）和“输出变量”（output）。从变量的角度来看，变量是由函数“创造”的。也就是说，函数是变量的“父母”，是creator（创造者）。 计算图由函数和变量之间的“连接”构成，这个“连接”是在计算实际发生的时候形成的，称为动态计算图（Define-by-Run）。 信息\r因为形成了上面的连接，我们就可以使用计算机中的递归思想，自动构建反向传播的过程，无需每次手动反向传播。 框架搭建\r变量类进行如下修改：： class Variable: def __init__(self, data): self.data = data self.grad = None self.creator = None def set_creator(self, func): self.creator = func def backward(self): funcs = [self.creator] while funcs: f = funcs.pop() # 获取函数 x, y = f.input, f.output # 获取函数的输入 x.grad = f.backward(y.grad) # backward调用backward方法 if x.creator is not None: funcs.append(x.creator) # 将前一个函数添加到列表中 函数类进行如下修改： class Function: def __call__(self, input): x = input.data y = self.forward(x) output = Variable(y) output.set_creator(self) # 让输出变量保存创造者信息 self.input = input self.output = output # 也保存输出变量 return output def forward(self, x): raise NotImplementedError() def backward(self, gy): raise NotImplementedError() 示例\r","date":"2025-12-27","objectID":"/posts/%E6%B7%B1%E5%BA%A6%E5%AD%A6%E4%B9%A0%E5%9F%BA%E7%A1%80~%E8%87%AA%E5%88%B6%E6%A1%86%E6%9E%B6/:3:3","tags":null,"title":"深度学习基础~自制框架","uri":"/posts/%E6%B7%B1%E5%BA%A6%E5%AD%A6%E4%B9%A0%E5%9F%BA%E7%A1%80~%E8%87%AA%E5%88%B6%E6%A1%86%E6%9E%B6/#示例-1"},{"categories":[],"collections":["深度学习"],"content":"第 49 节 框架优化\r","date":"2025-12-27","objectID":"/posts/%E6%B7%B1%E5%BA%A6%E5%AD%A6%E4%B9%A0%E5%9F%BA%E7%A1%80~%E8%87%AA%E5%88%B6%E6%A1%86%E6%9E%B6/:4:0","tags":null,"title":"深度学习基础~自制框架","uri":"/posts/%E6%B7%B1%E5%BA%A6%E5%AD%A6%E4%B9%A0%E5%9F%BA%E7%A1%80~%E8%87%AA%E5%88%B6%E6%A1%86%E6%9E%B6/#框架优化"},{"categories":[],"collections":["深度学习"],"content":"49.1 函数类包装\r构建函数 function 对函数类 Function 进行包装： 框架搭建\rdef square(x): return Square()(x) def exp(x): return Exp()(x) 示例\r","date":"2025-12-27","objectID":"/posts/%E6%B7%B1%E5%BA%A6%E5%AD%A6%E4%B9%A0%E5%9F%BA%E7%A1%80~%E8%87%AA%E5%88%B6%E6%A1%86%E6%9E%B6/:4:1","tags":null,"title":"深度学习基础~自制框架","uri":"/posts/%E6%B7%B1%E5%BA%A6%E5%AD%A6%E4%B9%A0%E5%9F%BA%E7%A1%80~%E8%87%AA%E5%88%B6%E6%A1%86%E6%9E%B6/#函数类包装"},{"categories":[],"collections":["深度学习"],"content":"49.1 函数类包装\r构建函数 function 对函数类 Function 进行包装： 框架搭建\rdef square(x): return Square()(x) def exp(x): return Exp()(x) 示例\r","date":"2025-12-27","objectID":"/posts/%E6%B7%B1%E5%BA%A6%E5%AD%A6%E4%B9%A0%E5%9F%BA%E7%A1%80~%E8%87%AA%E5%88%B6%E6%A1%86%E6%9E%B6/:4:1","tags":null,"title":"深度学习基础~自制框架","uri":"/posts/%E6%B7%B1%E5%BA%A6%E5%AD%A6%E4%B9%A0%E5%9F%BA%E7%A1%80~%E8%87%AA%E5%88%B6%E6%A1%86%E6%9E%B6/#框架搭建-2"},{"categories":[],"collections":["深度学习"],"content":"49.1 函数类包装\r构建函数 function 对函数类 Function 进行包装： 框架搭建\rdef square(x): return Square()(x) def exp(x): return Exp()(x) 示例\r","date":"2025-12-27","objectID":"/posts/%E6%B7%B1%E5%BA%A6%E5%AD%A6%E4%B9%A0%E5%9F%BA%E7%A1%80~%E8%87%AA%E5%88%B6%E6%A1%86%E6%9E%B6/:4:1","tags":null,"title":"深度学习基础~自制框架","uri":"/posts/%E6%B7%B1%E5%BA%A6%E5%AD%A6%E4%B9%A0%E5%9F%BA%E7%A1%80~%E8%87%AA%E5%88%B6%E6%A1%86%E6%9E%B6/#示例-2"},{"categories":[],"collections":["深度学习"],"content":"49.2 简化 backward 方法\r省略掉 y.grad = np.array(1.0) 框架搭建\r变量类进行如下修改： class Variable: def __init__(self, data): self.data = data self.grad = None self.creator = None def set_creator(self, func): self.creator = func def backward(self): if self.grad is None: # 自动初始化 self.grid self.grad = np.ones_like(self.data) funcs = [self.creator] while funcs: f = funcs.pop() x, y = f.input, f.output x.grad = f.backward(y.grad) if x.creator is not None: funcs.append(x.creator) 示例\r","date":"2025-12-27","objectID":"/posts/%E6%B7%B1%E5%BA%A6%E5%AD%A6%E4%B9%A0%E5%9F%BA%E7%A1%80~%E8%87%AA%E5%88%B6%E6%A1%86%E6%9E%B6/:4:2","tags":null,"title":"深度学习基础~自制框架","uri":"/posts/%E6%B7%B1%E5%BA%A6%E5%AD%A6%E4%B9%A0%E5%9F%BA%E7%A1%80~%E8%87%AA%E5%88%B6%E6%A1%86%E6%9E%B6/#简化-backward-方法"},{"categories":[],"collections":["深度学习"],"content":"49.2 简化 backward 方法\r省略掉 y.grad = np.array(1.0) 框架搭建\r变量类进行如下修改： class Variable: def __init__(self, data): self.data = data self.grad = None self.creator = None def set_creator(self, func): self.creator = func def backward(self): if self.grad is None: # 自动初始化 self.grid self.grad = np.ones_like(self.data) funcs = [self.creator] while funcs: f = funcs.pop() x, y = f.input, f.output x.grad = f.backward(y.grad) if x.creator is not None: funcs.append(x.creator) 示例\r","date":"2025-12-27","objectID":"/posts/%E6%B7%B1%E5%BA%A6%E5%AD%A6%E4%B9%A0%E5%9F%BA%E7%A1%80~%E8%87%AA%E5%88%B6%E6%A1%86%E6%9E%B6/:4:2","tags":null,"title":"深度学习基础~自制框架","uri":"/posts/%E6%B7%B1%E5%BA%A6%E5%AD%A6%E4%B9%A0%E5%9F%BA%E7%A1%80~%E8%87%AA%E5%88%B6%E6%A1%86%E6%9E%B6/#框架搭建-3"},{"categories":[],"collections":["深度学习"],"content":"49.2 简化 backward 方法\r省略掉 y.grad = np.array(1.0) 框架搭建\r变量类进行如下修改： class Variable: def __init__(self, data): self.data = data self.grad = None self.creator = None def set_creator(self, func): self.creator = func def backward(self): if self.grad is None: # 自动初始化 self.grid self.grad = np.ones_like(self.data) funcs = [self.creator] while funcs: f = funcs.pop() x, y = f.input, f.output x.grad = f.backward(y.grad) if x.creator is not None: funcs.append(x.creator) 示例\r","date":"2025-12-27","objectID":"/posts/%E6%B7%B1%E5%BA%A6%E5%AD%A6%E4%B9%A0%E5%9F%BA%E7%A1%80~%E8%87%AA%E5%88%B6%E6%A1%86%E6%9E%B6/:4:2","tags":null,"title":"深度学习基础~自制框架","uri":"/posts/%E6%B7%B1%E5%BA%A6%E5%AD%A6%E4%B9%A0%E5%9F%BA%E7%A1%80~%E8%87%AA%E5%88%B6%E6%A1%86%E6%9E%B6/#示例-3"},{"categories":[],"collections":["深度学习"],"content":"49.3 只支持 ndarray\r限制 Variable 的输入只能是 ndarray 类型 框架搭建\r变量类进行如下修改： class Variable: def __init__(self, data): if data is not None: if not isinstance(data, np.ndarray): # 只支持 ndarray raise TypeError('{} is not supported'.format(type(data))) self.data = data self.grad = None self.creator = None def set_creator(self, func): self.creator = func def backward(self): if self.grad is None: self.grad = np.ones_like(self.data) funcs = [self.creator] while funcs: f = funcs.pop() x, y = f.input, f.output x.grad = f.backward(y.grad) if x.creator is not None: funcs.append(x.creator) 函数类进行如下修改： def as_array(x): if np.isscalar(x): return np.array(x) return x class Function: def __call__(self, input): x = input.data y = self.forward(x) output = Variable(as_array(y)) output.set_creator(self) self.input = input self.output = output return output def forward(self, x): raise NotImplementedError() def backward(self, gy): raise NotImplementedError() 信息\r如果输入 x 是 0 维的 ndarray，输出 y 会变成非 ndarray，比如 np. float64 ","date":"2025-12-27","objectID":"/posts/%E6%B7%B1%E5%BA%A6%E5%AD%A6%E4%B9%A0%E5%9F%BA%E7%A1%80~%E8%87%AA%E5%88%B6%E6%A1%86%E6%9E%B6/:4:3","tags":null,"title":"深度学习基础~自制框架","uri":"/posts/%E6%B7%B1%E5%BA%A6%E5%AD%A6%E4%B9%A0%E5%9F%BA%E7%A1%80~%E8%87%AA%E5%88%B6%E6%A1%86%E6%9E%B6/#只支持-ndarray"},{"categories":[],"collections":["深度学习"],"content":"49.3 只支持 ndarray\r限制 Variable 的输入只能是 ndarray 类型 框架搭建\r变量类进行如下修改： class Variable: def __init__(self, data): if data is not None: if not isinstance(data, np.ndarray): # 只支持 ndarray raise TypeError('{} is not supported'.format(type(data))) self.data = data self.grad = None self.creator = None def set_creator(self, func): self.creator = func def backward(self): if self.grad is None: self.grad = np.ones_like(self.data) funcs = [self.creator] while funcs: f = funcs.pop() x, y = f.input, f.output x.grad = f.backward(y.grad) if x.creator is not None: funcs.append(x.creator) 函数类进行如下修改： def as_array(x): if np.isscalar(x): return np.array(x) return x class Function: def __call__(self, input): x = input.data y = self.forward(x) output = Variable(as_array(y)) output.set_creator(self) self.input = input self.output = output return output def forward(self, x): raise NotImplementedError() def backward(self, gy): raise NotImplementedError() 信息\r如果输入 x 是 0 维的 ndarray，输出 y 会变成非 ndarray，比如 np. float64 ","date":"2025-12-27","objectID":"/posts/%E6%B7%B1%E5%BA%A6%E5%AD%A6%E4%B9%A0%E5%9F%BA%E7%A1%80~%E8%87%AA%E5%88%B6%E6%A1%86%E6%9E%B6/:4:3","tags":null,"title":"深度学习基础~自制框架","uri":"/posts/%E6%B7%B1%E5%BA%A6%E5%AD%A6%E4%B9%A0%E5%9F%BA%E7%A1%80~%E8%87%AA%E5%88%B6%E6%A1%86%E6%9E%B6/#框架搭建-4"},{"categories":[],"collections":["深度学习"],"content":"第 50 节 多参数反向传播\r输入、输出都可以是多个。 ","date":"2025-12-27","objectID":"/posts/%E6%B7%B1%E5%BA%A6%E5%AD%A6%E4%B9%A0%E5%9F%BA%E7%A1%80~%E8%87%AA%E5%88%B6%E6%A1%86%E6%9E%B6/:5:0","tags":null,"title":"深度学习基础~自制框架","uri":"/posts/%E6%B7%B1%E5%BA%A6%E5%AD%A6%E4%B9%A0%E5%9F%BA%E7%A1%80~%E8%87%AA%E5%88%B6%E6%A1%86%E6%9E%B6/#多参数反向传播"},{"categories":[],"collections":["深度学习"],"content":"50.1 修改类\r框架搭建\r变量类进行如下修改： class Variable: def __init__(self, data): if data is not None: if not isinstance(data, np.ndarray): raise TypeError('{} is not supported'.format(type(data))) self.data = data self.grad = None self.creator = None def set_creator(self, func): self.creator = func def backward(self): if self.grad is None: self.grad = np.ones_like(self.data) funcs = [self.creator] while funcs: f = funcs.pop() gys = [output.grad for output in f.outputs] # 将输出变量 outputs 的导数汇总在列表中 gxs = f.backward(*gys) # 调用了函数 f 的反向传播 if not isinstance(gxs, tuple): # 当 gxs 不是元组时，将其转换为元组 gxs = (gxs,) for x, gx in zip(f.inputs, gxs): # 将反向传播中传播的导数设置为 Variable 的实例变量 grad x.grad = gx if x.creator is not None: funcs.append(x.creator) 函数类进行如下修改： class Function: def __call__(self, *inputs): # 添加星号 xs = [x.data for x in inputs] ys = self.forward(*xs) # 使用星号解包 if not isinstance(ys, tuple): # 对非元组情况的额外处理 ys = (ys,) outputs = [Variable(as_array(y)) for y in ys] for output in outputs: output.set_creator(self) self.inputs = inputs self.outputs = outputs # 如果列表中只有一个元素，则返回第1 个元素 return outputs if len(outputs) \u003e 1 else outputs[0] def forward(self, *xs): raise NotImplementedError() def backward(self, *gys): raise NotImplementedError() 示例\r","date":"2025-12-27","objectID":"/posts/%E6%B7%B1%E5%BA%A6%E5%AD%A6%E4%B9%A0%E5%9F%BA%E7%A1%80~%E8%87%AA%E5%88%B6%E6%A1%86%E6%9E%B6/:5:1","tags":null,"title":"深度学习基础~自制框架","uri":"/posts/%E6%B7%B1%E5%BA%A6%E5%AD%A6%E4%B9%A0%E5%9F%BA%E7%A1%80~%E8%87%AA%E5%88%B6%E6%A1%86%E6%9E%B6/#修改类"},{"categories":[],"collections":["深度学习"],"content":"50.1 修改类\r框架搭建\r变量类进行如下修改： class Variable: def __init__(self, data): if data is not None: if not isinstance(data, np.ndarray): raise TypeError('{} is not supported'.format(type(data))) self.data = data self.grad = None self.creator = None def set_creator(self, func): self.creator = func def backward(self): if self.grad is None: self.grad = np.ones_like(self.data) funcs = [self.creator] while funcs: f = funcs.pop() gys = [output.grad for output in f.outputs] # 将输出变量 outputs 的导数汇总在列表中 gxs = f.backward(*gys) # 调用了函数 f 的反向传播 if not isinstance(gxs, tuple): # 当 gxs 不是元组时，将其转换为元组 gxs = (gxs,) for x, gx in zip(f.inputs, gxs): # 将反向传播中传播的导数设置为 Variable 的实例变量 grad x.grad = gx if x.creator is not None: funcs.append(x.creator) 函数类进行如下修改： class Function: def __call__(self, *inputs): # 添加星号 xs = [x.data for x in inputs] ys = self.forward(*xs) # 使用星号解包 if not isinstance(ys, tuple): # 对非元组情况的额外处理 ys = (ys,) outputs = [Variable(as_array(y)) for y in ys] for output in outputs: output.set_creator(self) self.inputs = inputs self.outputs = outputs # 如果列表中只有一个元素，则返回第1 个元素 return outputs if len(outputs) \u003e 1 else outputs[0] def forward(self, *xs): raise NotImplementedError() def backward(self, *gys): raise NotImplementedError() 示例\r","date":"2025-12-27","objectID":"/posts/%E6%B7%B1%E5%BA%A6%E5%AD%A6%E4%B9%A0%E5%9F%BA%E7%A1%80~%E8%87%AA%E5%88%B6%E6%A1%86%E6%9E%B6/:5:1","tags":null,"title":"深度学习基础~自制框架","uri":"/posts/%E6%B7%B1%E5%BA%A6%E5%AD%A6%E4%B9%A0%E5%9F%BA%E7%A1%80~%E8%87%AA%E5%88%B6%E6%A1%86%E6%9E%B6/#框架搭建-5"},{"categories":[],"collections":["深度学习"],"content":"50.1 修改类\r框架搭建\r变量类进行如下修改： class Variable: def __init__(self, data): if data is not None: if not isinstance(data, np.ndarray): raise TypeError('{} is not supported'.format(type(data))) self.data = data self.grad = None self.creator = None def set_creator(self, func): self.creator = func def backward(self): if self.grad is None: self.grad = np.ones_like(self.data) funcs = [self.creator] while funcs: f = funcs.pop() gys = [output.grad for output in f.outputs] # 将输出变量 outputs 的导数汇总在列表中 gxs = f.backward(*gys) # 调用了函数 f 的反向传播 if not isinstance(gxs, tuple): # 当 gxs 不是元组时，将其转换为元组 gxs = (gxs,) for x, gx in zip(f.inputs, gxs): # 将反向传播中传播的导数设置为 Variable 的实例变量 grad x.grad = gx if x.creator is not None: funcs.append(x.creator) 函数类进行如下修改： class Function: def __call__(self, *inputs): # 添加星号 xs = [x.data for x in inputs] ys = self.forward(*xs) # 使用星号解包 if not isinstance(ys, tuple): # 对非元组情况的额外处理 ys = (ys,) outputs = [Variable(as_array(y)) for y in ys] for output in outputs: output.set_creator(self) self.inputs = inputs self.outputs = outputs # 如果列表中只有一个元素，则返回第1 个元素 return outputs if len(outputs) \u003e 1 else outputs[0] def forward(self, *xs): raise NotImplementedError() def backward(self, *gys): raise NotImplementedError() 示例\r","date":"2025-12-27","objectID":"/posts/%E6%B7%B1%E5%BA%A6%E5%AD%A6%E4%B9%A0%E5%9F%BA%E7%A1%80~%E8%87%AA%E5%88%B6%E6%A1%86%E6%9E%B6/:5:1","tags":null,"title":"深度学习基础~自制框架","uri":"/posts/%E6%B7%B1%E5%BA%A6%E5%AD%A6%E4%B9%A0%E5%9F%BA%E7%A1%80~%E8%87%AA%E5%88%B6%E6%A1%86%E6%9E%B6/#示例-4"},{"categories":[],"collections":["深度学习"],"content":"50.2 重复使用同一个变量\r重复使用统一个变量时，梯度值应该累加，而不是覆盖。 框架搭建\r变量类进行如下修改： class Variable: def __init__(self, data): if data is not None: if not isinstance(data, np.ndarray): raise TypeError('{} is not supported'.format(type(data))) self.data = data self.grad = None self.creator = None def set_creator(self, func): self.creator = func def backward(self): if self.grad is None: self.grad = np.ones_like(self.data) funcs = [self.creator] while funcs: f = funcs.pop() gys = [output.grad for output in f.outputs] gxs = f.backward(*gys) if not isinstance(gxs, tuple): gxs = (gxs,) for x, gx in zip(f.inputs, gxs): if x.grad is None: # 梯度值累加 x.grad = gx else: x.grad = x.grad + gx if x.creator is not None: funcs.append(x.creator) ","date":"2025-12-27","objectID":"/posts/%E6%B7%B1%E5%BA%A6%E5%AD%A6%E4%B9%A0%E5%9F%BA%E7%A1%80~%E8%87%AA%E5%88%B6%E6%A1%86%E6%9E%B6/:5:2","tags":null,"title":"深度学习基础~自制框架","uri":"/posts/%E6%B7%B1%E5%BA%A6%E5%AD%A6%E4%B9%A0%E5%9F%BA%E7%A1%80~%E8%87%AA%E5%88%B6%E6%A1%86%E6%9E%B6/#重复使用同一个变量"},{"categories":[],"collections":["深度学习"],"content":"50.2 重复使用同一个变量\r重复使用统一个变量时，梯度值应该累加，而不是覆盖。 框架搭建\r变量类进行如下修改： class Variable: def __init__(self, data): if data is not None: if not isinstance(data, np.ndarray): raise TypeError('{} is not supported'.format(type(data))) self.data = data self.grad = None self.creator = None def set_creator(self, func): self.creator = func def backward(self): if self.grad is None: self.grad = np.ones_like(self.data) funcs = [self.creator] while funcs: f = funcs.pop() gys = [output.grad for output in f.outputs] gxs = f.backward(*gys) if not isinstance(gxs, tuple): gxs = (gxs,) for x, gx in zip(f.inputs, gxs): if x.grad is None: # 梯度值累加 x.grad = gx else: x.grad = x.grad + gx if x.creator is not None: funcs.append(x.creator) ","date":"2025-12-27","objectID":"/posts/%E6%B7%B1%E5%BA%A6%E5%AD%A6%E4%B9%A0%E5%9F%BA%E7%A1%80~%E8%87%AA%E5%88%B6%E6%A1%86%E6%9E%B6/:5:2","tags":null,"title":"深度学习基础~自制框架","uri":"/posts/%E6%B7%B1%E5%BA%A6%E5%AD%A6%E4%B9%A0%E5%9F%BA%E7%A1%80~%E8%87%AA%E5%88%B6%E6%A1%86%E6%9E%B6/#框架搭建-6"},{"categories":[],"collections":["深度学习"],"content":"50.3 重置梯度\r进行多次反向传播时，之前修改梯度值为累加，所以梯度值不会自动清除，需要添加清除函数。 框架搭建\r变量类进行如下修改： class Variable: def __init__(self, data): if data is not None: if not isinstance(data, np.ndarray): raise TypeError('{} is not supported'.format(type(data))) self.data = data self.grad = None self.creator = None def set_creator(self, func): # 清除梯度函数 self.creator = func def cleargrad(self): self.grad = None def backward(self): if self.grad is None: self.grad = np.ones_like(self.data) funcs = [self.creator] while funcs: f = funcs.pop() gys = [output.grad for output in f.outputs] gxs = f.backward(*gys) if not isinstance(gxs, tuple): gxs = (gxs,) for x, gx in zip(f.inputs, gxs): if x.grad is None: x.grad = gx else: x.grad = x.grad + gx if x.creator is not None: funcs.append(x.creator) 示例\r","date":"2025-12-27","objectID":"/posts/%E6%B7%B1%E5%BA%A6%E5%AD%A6%E4%B9%A0%E5%9F%BA%E7%A1%80~%E8%87%AA%E5%88%B6%E6%A1%86%E6%9E%B6/:5:3","tags":null,"title":"深度学习基础~自制框架","uri":"/posts/%E6%B7%B1%E5%BA%A6%E5%AD%A6%E4%B9%A0%E5%9F%BA%E7%A1%80~%E8%87%AA%E5%88%B6%E6%A1%86%E6%9E%B6/#重置梯度"},{"categories":[],"collections":["深度学习"],"content":"50.3 重置梯度\r进行多次反向传播时，之前修改梯度值为累加，所以梯度值不会自动清除，需要添加清除函数。 框架搭建\r变量类进行如下修改： class Variable: def __init__(self, data): if data is not None: if not isinstance(data, np.ndarray): raise TypeError('{} is not supported'.format(type(data))) self.data = data self.grad = None self.creator = None def set_creator(self, func): # 清除梯度函数 self.creator = func def cleargrad(self): self.grad = None def backward(self): if self.grad is None: self.grad = np.ones_like(self.data) funcs = [self.creator] while funcs: f = funcs.pop() gys = [output.grad for output in f.outputs] gxs = f.backward(*gys) if not isinstance(gxs, tuple): gxs = (gxs,) for x, gx in zip(f.inputs, gxs): if x.grad is None: x.grad = gx else: x.grad = x.grad + gx if x.creator is not None: funcs.append(x.creator) 示例\r","date":"2025-12-27","objectID":"/posts/%E6%B7%B1%E5%BA%A6%E5%AD%A6%E4%B9%A0%E5%9F%BA%E7%A1%80~%E8%87%AA%E5%88%B6%E6%A1%86%E6%9E%B6/:5:3","tags":null,"title":"深度学习基础~自制框架","uri":"/posts/%E6%B7%B1%E5%BA%A6%E5%AD%A6%E4%B9%A0%E5%9F%BA%E7%A1%80~%E8%87%AA%E5%88%B6%E6%A1%86%E6%9E%B6/#框架搭建-7"},{"categories":[],"collections":["深度学习"],"content":"50.3 重置梯度\r进行多次反向传播时，之前修改梯度值为累加，所以梯度值不会自动清除，需要添加清除函数。 框架搭建\r变量类进行如下修改： class Variable: def __init__(self, data): if data is not None: if not isinstance(data, np.ndarray): raise TypeError('{} is not supported'.format(type(data))) self.data = data self.grad = None self.creator = None def set_creator(self, func): # 清除梯度函数 self.creator = func def cleargrad(self): self.grad = None def backward(self): if self.grad is None: self.grad = np.ones_like(self.data) funcs = [self.creator] while funcs: f = funcs.pop() gys = [output.grad for output in f.outputs] gxs = f.backward(*gys) if not isinstance(gxs, tuple): gxs = (gxs,) for x, gx in zip(f.inputs, gxs): if x.grad is None: x.grad = gx else: x.grad = x.grad + gx if x.creator is not None: funcs.append(x.creator) 示例\r","date":"2025-12-27","objectID":"/posts/%E6%B7%B1%E5%BA%A6%E5%AD%A6%E4%B9%A0%E5%9F%BA%E7%A1%80~%E8%87%AA%E5%88%B6%E6%A1%86%E6%9E%B6/:5:3","tags":null,"title":"深度学习基础~自制框架","uri":"/posts/%E6%B7%B1%E5%BA%A6%E5%AD%A6%E4%B9%A0%E5%9F%BA%E7%A1%80~%E8%87%AA%E5%88%B6%E6%A1%86%E6%9E%B6/#示例-5"},{"categories":[],"collections":["深度学习"],"content":"第 51 节 复杂计算图\r对于复杂的计算图，比如下面这张计算图： 正确的反向传播顺序如下： 但是按照我们之前创建的程序，反向传播的顺序如下： 按照程序默认的顺序进行反向传播无法得出正确的结果。 ","date":"2025-12-27","objectID":"/posts/%E6%B7%B1%E5%BA%A6%E5%AD%A6%E4%B9%A0%E5%9F%BA%E7%A1%80~%E8%87%AA%E5%88%B6%E6%A1%86%E6%9E%B6/:6:0","tags":null,"title":"深度学习基础~自制框架","uri":"/posts/%E6%B7%B1%E5%BA%A6%E5%AD%A6%E4%B9%A0%E5%9F%BA%E7%A1%80~%E8%87%AA%E5%88%B6%E6%A1%86%E6%9E%B6/#复杂计算图"},{"categories":[],"collections":["深度学习"],"content":"51.1 函数优先级\r在反向传播时，如果按照从后代到先代的顺序处理，就可以保证“子辈”在“父辈”之前被取出，如下图： 增加辈分变量就可以解决，如下图： 框架搭建\r变量类进行如下修改： class Variable: def __init__(self, data): if data is not None: if not isinstance(data, np.ndarray): raise TypeError('{} is not supported'.format(type(data))) self.data = data self.grad = None self.creator = None self.generation = 0 # 增加辈分变量 def set_creator(self, func): self.creator = func self.generation = func.generation + 1 # 辈分增加 def cleargrad(self): self.grad = None def backward(self): if self.grad is None: self.grad = np.ones_like(self.data) funcs = [] seen_set = set() # 按照辈分对 funcs 进行排序 def add_func(f): if f not in seen_set: funcs.append(f) seen_set.add(f) funcs.sort(key=lambda x: x.generation) add_func(self.creator) while funcs: f = funcs.pop() gys = [output.grad for output in f.outputs] gxs = f.backward(*gys) if not isinstance(gxs, tuple): gxs = (gxs,) for x, gx in zip(f.inputs, gxs): if x.grad is None: x.grad = gx else: x.grad = x.grad + gx if x.creator is not None: add_func(x.creator) 函数类进行如下修改： class Function: def __call__(self, *inputs): xs = [x.data for x in inputs] ys = self.forward(*xs) if not isinstance(ys, tuple): ys = (ys,) outputs = [Variable(as_array(y)) for y in ys] self.generation = max([x.generation for x in inputs]) # 取最大的辈分 for output in outputs: output.set_creator(self) self.inputs = inputs self.outputs = outputs return outputs if len(outputs) \u003e 1 else outputs[0] def forward(self, *xs): raise NotImplementedError() def backward(self, *gys): raise NotImplementedError() 示例\r","date":"2025-12-27","objectID":"/posts/%E6%B7%B1%E5%BA%A6%E5%AD%A6%E4%B9%A0%E5%9F%BA%E7%A1%80~%E8%87%AA%E5%88%B6%E6%A1%86%E6%9E%B6/:6:1","tags":null,"title":"深度学习基础~自制框架","uri":"/posts/%E6%B7%B1%E5%BA%A6%E5%AD%A6%E4%B9%A0%E5%9F%BA%E7%A1%80~%E8%87%AA%E5%88%B6%E6%A1%86%E6%9E%B6/#函数优先级"},{"categories":[],"collections":["深度学习"],"content":"51.1 函数优先级\r在反向传播时，如果按照从后代到先代的顺序处理，就可以保证“子辈”在“父辈”之前被取出，如下图： 增加辈分变量就可以解决，如下图： 框架搭建\r变量类进行如下修改： class Variable: def __init__(self, data): if data is not None: if not isinstance(data, np.ndarray): raise TypeError('{} is not supported'.format(type(data))) self.data = data self.grad = None self.creator = None self.generation = 0 # 增加辈分变量 def set_creator(self, func): self.creator = func self.generation = func.generation + 1 # 辈分增加 def cleargrad(self): self.grad = None def backward(self): if self.grad is None: self.grad = np.ones_like(self.data) funcs = [] seen_set = set() # 按照辈分对 funcs 进行排序 def add_func(f): if f not in seen_set: funcs.append(f) seen_set.add(f) funcs.sort(key=lambda x: x.generation) add_func(self.creator) while funcs: f = funcs.pop() gys = [output.grad for output in f.outputs] gxs = f.backward(*gys) if not isinstance(gxs, tuple): gxs = (gxs,) for x, gx in zip(f.inputs, gxs): if x.grad is None: x.grad = gx else: x.grad = x.grad + gx if x.creator is not None: add_func(x.creator) 函数类进行如下修改： class Function: def __call__(self, *inputs): xs = [x.data for x in inputs] ys = self.forward(*xs) if not isinstance(ys, tuple): ys = (ys,) outputs = [Variable(as_array(y)) for y in ys] self.generation = max([x.generation for x in inputs]) # 取最大的辈分 for output in outputs: output.set_creator(self) self.inputs = inputs self.outputs = outputs return outputs if len(outputs) \u003e 1 else outputs[0] def forward(self, *xs): raise NotImplementedError() def backward(self, *gys): raise NotImplementedError() 示例\r","date":"2025-12-27","objectID":"/posts/%E6%B7%B1%E5%BA%A6%E5%AD%A6%E4%B9%A0%E5%9F%BA%E7%A1%80~%E8%87%AA%E5%88%B6%E6%A1%86%E6%9E%B6/:6:1","tags":null,"title":"深度学习基础~自制框架","uri":"/posts/%E6%B7%B1%E5%BA%A6%E5%AD%A6%E4%B9%A0%E5%9F%BA%E7%A1%80~%E8%87%AA%E5%88%B6%E6%A1%86%E6%9E%B6/#框架搭建-8"},{"categories":[],"collections":["深度学习"],"content":"51.1 函数优先级\r在反向传播时，如果按照从后代到先代的顺序处理，就可以保证“子辈”在“父辈”之前被取出，如下图： 增加辈分变量就可以解决，如下图： 框架搭建\r变量类进行如下修改： class Variable: def __init__(self, data): if data is not None: if not isinstance(data, np.ndarray): raise TypeError('{} is not supported'.format(type(data))) self.data = data self.grad = None self.creator = None self.generation = 0 # 增加辈分变量 def set_creator(self, func): self.creator = func self.generation = func.generation + 1 # 辈分增加 def cleargrad(self): self.grad = None def backward(self): if self.grad is None: self.grad = np.ones_like(self.data) funcs = [] seen_set = set() # 按照辈分对 funcs 进行排序 def add_func(f): if f not in seen_set: funcs.append(f) seen_set.add(f) funcs.sort(key=lambda x: x.generation) add_func(self.creator) while funcs: f = funcs.pop() gys = [output.grad for output in f.outputs] gxs = f.backward(*gys) if not isinstance(gxs, tuple): gxs = (gxs,) for x, gx in zip(f.inputs, gxs): if x.grad is None: x.grad = gx else: x.grad = x.grad + gx if x.creator is not None: add_func(x.creator) 函数类进行如下修改： class Function: def __call__(self, *inputs): xs = [x.data for x in inputs] ys = self.forward(*xs) if not isinstance(ys, tuple): ys = (ys,) outputs = [Variable(as_array(y)) for y in ys] self.generation = max([x.generation for x in inputs]) # 取最大的辈分 for output in outputs: output.set_creator(self) self.inputs = inputs self.outputs = outputs return outputs if len(outputs) \u003e 1 else outputs[0] def forward(self, *xs): raise NotImplementedError() def backward(self, *gys): raise NotImplementedError() 示例\r","date":"2025-12-27","objectID":"/posts/%E6%B7%B1%E5%BA%A6%E5%AD%A6%E4%B9%A0%E5%9F%BA%E7%A1%80~%E8%87%AA%E5%88%B6%E6%A1%86%E6%9E%B6/:6:1","tags":null,"title":"深度学习基础~自制框架","uri":"/posts/%E6%B7%B1%E5%BA%A6%E5%AD%A6%E4%B9%A0%E5%9F%BA%E7%A1%80~%E8%87%AA%E5%88%B6%E6%A1%86%E6%9E%B6/#示例-6"},{"categories":[],"collections":["深度学习"],"content":"第 52 节 框架优化\r","date":"2025-12-27","objectID":"/posts/%E6%B7%B1%E5%BA%A6%E5%AD%A6%E4%B9%A0%E5%9F%BA%E7%A1%80~%E8%87%AA%E5%88%B6%E6%A1%86%E6%9E%B6/:7:0","tags":null,"title":"深度学习基础~自制框架","uri":"/posts/%E6%B7%B1%E5%BA%A6%E5%AD%A6%E4%B9%A0%E5%9F%BA%E7%A1%80~%E8%87%AA%E5%88%B6%E6%A1%86%E6%9E%B6/#框架优化-1"},{"categories":[],"collections":["深度学习"],"content":"52.1 循环引用\rPython 会自动从内存中删除不再需要的对象，但是，如果代码写得不好，就可能出现内存泄漏或内存不足等情况。 Python 使用两种方式管理内存：一种是引用计数，另一种是分代垃圾回收。这里我们把后者称为 GC（Garbage Collection，垃圾回收）。 引用计数的机制很简单，每个对象在被创建时的引用计数为 0，当它被另一个对象引用时，引用计数加 1，当引用停止时，引用计数减 1。最终，当引用计数变为 0 时，Python 解释器会回收该对象。 右图中的 a、b、c 的引用计数均为 1。这时用户已无法访问这 3 个对象，如果只设置了 a = b = c =None，那么此时因为循环引用，引用计数不会为 0，对象也不会从内存中释放出来。这时就需要使用 GC 了。 GC 能够正确处理循环引用。因此在使用 Python 编程时，我们通常不需要关心循环引用。不过，使用 GC 推迟内存释放会导致程序整体的内存使用量增加。内存是机器学习，尤其是神经网络运算时的重要资源，因此建议避免循环引用。 Function 实例引用了输入和输出的 Variable 实例，同时， Variable 实例也引用了作为创建者的 Function 实例。这时，Function 实例和 variable 实例之间就存在循环引用关系。我们可以使用 Python 标准模块 weakref 来避免循环引用。 框架搭建\r变量类进行如下修改： class Variable: def __init__(self, data): if data is not None: if not isinstance(data, np.ndarray): raise TypeError('{} is not supported'.format(type(data))) self.data = data self.grad = None self.creator = None self.generation = 0 def set_creator(self, func): self.creator = func self.generation = func.generation + 1 def cleargrad(self): self.grad = None def backward(self): if self.grad is None: self.grad = np.ones_like(self.data) funcs = [] seen_set = set() def add_func(f): if f not in seen_set: funcs.append(f) seen_set.add(f) funcs.sort(key=lambda x: x.generation) add_func(self.creator) while funcs: f = funcs.pop() # gys = [output.grad for output in f.outputs] gys = [output().grad for output in f.outputs] gxs = f.backward(*gys) if not isinstance(gxs, tuple): gxs = (gxs,) for x, gx in zip(f.inputs, gxs): if x.grad is None: x.grad = gx else: x.grad = x.grad + gx if x.creator is not None: add_func(x.creator) 函数类进行如下修改： class Function: def __call__(self, *inputs): xs = [x.data for x in inputs] ys = self.forward(*xs) if not isinstance(ys, tuple): ys = (ys,) outputs = [Variable(as_array(y)) for y in ys] self.generation = max([x.generation for x in inputs]) for output in outputs: output.set_creator(self) self.inputs = inputs self.outputs = [weakref.ref(output) for output in outputs] # 增加弱引用 return outputs if len(outputs) \u003e 1 else outputs[0] def forward(self, *xs): raise NotImplementedError() def backward(self, *gys): raise NotImplementedError() ","date":"2025-12-27","objectID":"/posts/%E6%B7%B1%E5%BA%A6%E5%AD%A6%E4%B9%A0%E5%9F%BA%E7%A1%80~%E8%87%AA%E5%88%B6%E6%A1%86%E6%9E%B6/:7:1","tags":null,"title":"深度学习基础~自制框架","uri":"/posts/%E6%B7%B1%E5%BA%A6%E5%AD%A6%E4%B9%A0%E5%9F%BA%E7%A1%80~%E8%87%AA%E5%88%B6%E6%A1%86%E6%9E%B6/#循环引用"},{"categories":[],"collections":["深度学习"],"content":"52.1 循环引用\rPython 会自动从内存中删除不再需要的对象，但是，如果代码写得不好，就可能出现内存泄漏或内存不足等情况。 Python 使用两种方式管理内存：一种是引用计数，另一种是分代垃圾回收。这里我们把后者称为 GC（Garbage Collection，垃圾回收）。 引用计数的机制很简单，每个对象在被创建时的引用计数为 0，当它被另一个对象引用时，引用计数加 1，当引用停止时，引用计数减 1。最终，当引用计数变为 0 时，Python 解释器会回收该对象。 右图中的 a、b、c 的引用计数均为 1。这时用户已无法访问这 3 个对象，如果只设置了 a = b = c =None，那么此时因为循环引用，引用计数不会为 0，对象也不会从内存中释放出来。这时就需要使用 GC 了。 GC 能够正确处理循环引用。因此在使用 Python 编程时，我们通常不需要关心循环引用。不过，使用 GC 推迟内存释放会导致程序整体的内存使用量增加。内存是机器学习，尤其是神经网络运算时的重要资源，因此建议避免循环引用。 Function 实例引用了输入和输出的 Variable 实例，同时， Variable 实例也引用了作为创建者的 Function 实例。这时，Function 实例和 variable 实例之间就存在循环引用关系。我们可以使用 Python 标准模块 weakref 来避免循环引用。 框架搭建\r变量类进行如下修改： class Variable: def __init__(self, data): if data is not None: if not isinstance(data, np.ndarray): raise TypeError('{} is not supported'.format(type(data))) self.data = data self.grad = None self.creator = None self.generation = 0 def set_creator(self, func): self.creator = func self.generation = func.generation + 1 def cleargrad(self): self.grad = None def backward(self): if self.grad is None: self.grad = np.ones_like(self.data) funcs = [] seen_set = set() def add_func(f): if f not in seen_set: funcs.append(f) seen_set.add(f) funcs.sort(key=lambda x: x.generation) add_func(self.creator) while funcs: f = funcs.pop() # gys = [output.grad for output in f.outputs] gys = [output().grad for output in f.outputs] gxs = f.backward(*gys) if not isinstance(gxs, tuple): gxs = (gxs,) for x, gx in zip(f.inputs, gxs): if x.grad is None: x.grad = gx else: x.grad = x.grad + gx if x.creator is not None: add_func(x.creator) 函数类进行如下修改： class Function: def __call__(self, *inputs): xs = [x.data for x in inputs] ys = self.forward(*xs) if not isinstance(ys, tuple): ys = (ys,) outputs = [Variable(as_array(y)) for y in ys] self.generation = max([x.generation for x in inputs]) for output in outputs: output.set_creator(self) self.inputs = inputs self.outputs = [weakref.ref(output) for output in outputs] # 增加弱引用 return outputs if len(outputs) \u003e 1 else outputs[0] def forward(self, *xs): raise NotImplementedError() def backward(self, *gys): raise NotImplementedError() ","date":"2025-12-27","objectID":"/posts/%E6%B7%B1%E5%BA%A6%E5%AD%A6%E4%B9%A0%E5%9F%BA%E7%A1%80~%E8%87%AA%E5%88%B6%E6%A1%86%E6%9E%B6/:7:1","tags":null,"title":"深度学习基础~自制框架","uri":"/posts/%E6%B7%B1%E5%BA%A6%E5%AD%A6%E4%B9%A0%E5%9F%BA%E7%A1%80~%E8%87%AA%E5%88%B6%E6%A1%86%E6%9E%B6/#框架搭建-9"},{"categories":[],"collections":["深度学习"],"content":"52.2 是否保留梯度\r在反向传播时，只有输入变量的梯度才需要保留。 框架搭建\r变量类进行如下修改： class Variable: def __init__(self, data): if data is not None: if not isinstance(data, np.ndarray): raise TypeError('{} is not supported'.format(type(data))) self.data = data self.grad = None self.creator = None self.generation = 0 def set_creator(self, func): self.creator = func self.generation = func.generation + 1 def cleargrad(self): self.grad = None def backward(self, retain_grad=False): # 增加保留梯度参数 if self.grad is None: self.grad = np.ones_like(self.data) funcs = [] seen_set = set() def add_func(f): if f not in seen_set: funcs.append(f) seen_set.add(f) funcs.sort(key=lambda x: x.generation) add_func(self.creator) while funcs: f = funcs.pop() gys = [output().grad for output in f.outputs] gxs = f.backward(*gys) if not isinstance(gxs, tuple): gxs = (gxs,) for x, gx in zip(f.inputs, gxs): if x.grad is None: x.grad = gx else: x.grad = x.grad + gx if x.creator is not None: add_func(x.creator) if not retain_grad: # 保留梯度 for y in f.outputs: y().grad = None # y 是弱引用 示例\r","date":"2025-12-27","objectID":"/posts/%E6%B7%B1%E5%BA%A6%E5%AD%A6%E4%B9%A0%E5%9F%BA%E7%A1%80~%E8%87%AA%E5%88%B6%E6%A1%86%E6%9E%B6/:7:2","tags":null,"title":"深度学习基础~自制框架","uri":"/posts/%E6%B7%B1%E5%BA%A6%E5%AD%A6%E4%B9%A0%E5%9F%BA%E7%A1%80~%E8%87%AA%E5%88%B6%E6%A1%86%E6%9E%B6/#是否保留梯度"},{"categories":[],"collections":["深度学习"],"content":"52.2 是否保留梯度\r在反向传播时，只有输入变量的梯度才需要保留。 框架搭建\r变量类进行如下修改： class Variable: def __init__(self, data): if data is not None: if not isinstance(data, np.ndarray): raise TypeError('{} is not supported'.format(type(data))) self.data = data self.grad = None self.creator = None self.generation = 0 def set_creator(self, func): self.creator = func self.generation = func.generation + 1 def cleargrad(self): self.grad = None def backward(self, retain_grad=False): # 增加保留梯度参数 if self.grad is None: self.grad = np.ones_like(self.data) funcs = [] seen_set = set() def add_func(f): if f not in seen_set: funcs.append(f) seen_set.add(f) funcs.sort(key=lambda x: x.generation) add_func(self.creator) while funcs: f = funcs.pop() gys = [output().grad for output in f.outputs] gxs = f.backward(*gys) if not isinstance(gxs, tuple): gxs = (gxs,) for x, gx in zip(f.inputs, gxs): if x.grad is None: x.grad = gx else: x.grad = x.grad + gx if x.creator is not None: add_func(x.creator) if not retain_grad: # 保留梯度 for y in f.outputs: y().grad = None # y 是弱引用 示例\r","date":"2025-12-27","objectID":"/posts/%E6%B7%B1%E5%BA%A6%E5%AD%A6%E4%B9%A0%E5%9F%BA%E7%A1%80~%E8%87%AA%E5%88%B6%E6%A1%86%E6%9E%B6/:7:2","tags":null,"title":"深度学习基础~自制框架","uri":"/posts/%E6%B7%B1%E5%BA%A6%E5%AD%A6%E4%B9%A0%E5%9F%BA%E7%A1%80~%E8%87%AA%E5%88%B6%E6%A1%86%E6%9E%B6/#框架搭建-10"},{"categories":[],"collections":["深度学习"],"content":"52.2 是否保留梯度\r在反向传播时，只有输入变量的梯度才需要保留。 框架搭建\r变量类进行如下修改： class Variable: def __init__(self, data): if data is not None: if not isinstance(data, np.ndarray): raise TypeError('{} is not supported'.format(type(data))) self.data = data self.grad = None self.creator = None self.generation = 0 def set_creator(self, func): self.creator = func self.generation = func.generation + 1 def cleargrad(self): self.grad = None def backward(self, retain_grad=False): # 增加保留梯度参数 if self.grad is None: self.grad = np.ones_like(self.data) funcs = [] seen_set = set() def add_func(f): if f not in seen_set: funcs.append(f) seen_set.add(f) funcs.sort(key=lambda x: x.generation) add_func(self.creator) while funcs: f = funcs.pop() gys = [output().grad for output in f.outputs] gxs = f.backward(*gys) if not isinstance(gxs, tuple): gxs = (gxs,) for x, gx in zip(f.inputs, gxs): if x.grad is None: x.grad = gx else: x.grad = x.grad + gx if x.creator is not None: add_func(x.creator) if not retain_grad: # 保留梯度 for y in f.outputs: y().grad = None # y 是弱引用 示例\r","date":"2025-12-27","objectID":"/posts/%E6%B7%B1%E5%BA%A6%E5%AD%A6%E4%B9%A0%E5%9F%BA%E7%A1%80~%E8%87%AA%E5%88%B6%E6%A1%86%E6%9E%B6/:7:2","tags":null,"title":"深度学习基础~自制框架","uri":"/posts/%E6%B7%B1%E5%BA%A6%E5%AD%A6%E4%B9%A0%E5%9F%BA%E7%A1%80~%E8%87%AA%E5%88%B6%E6%A1%86%E6%9E%B6/#示例-7"},{"categories":[],"collections":["深度学习"],"content":"52.3 是否反向传播\r只有训练的时候需要反向传播，验证和测试的时候不需要。 框架搭建\r创建 Config 类 import contextlib class Config: # 创建Config 类，定义是否启用反向传播 enable_backprop = True # 创建一个 With 上下文环境，在该上下文中，对 Config 进行修改 @contextlib.contextmanager def using_config(name, value): old_value = getattr(Config, name) setattr(Config, name, value) try: yield finally: setattr(Config, name, old_value) # 创建 no_grad 函数，返回 enable_backprop 为 false 的上下文环境 def no_grad(): return using_config('enable_backprop', False) 函数类进行如下修改： class Function: def __call__(self, *inputs): xs = [x.data for x in inputs] ys = self.forward(*xs) if not isinstance(ys, tuple): ys = (ys,) outputs = [Variable(as_array(y)) for y in ys] if Config.enable_backprop: # 是否启用反向传播 self.generation = max([x.generation for x in inputs]) for output in outputs: output.set_creator(self) self.inputs = inputs self.outputs = [weakref.ref(output) for output in outputs] return outputs if len(outputs) \u003e 1 else outputs[0] def forward(self, *xs): raise NotImplementedError() def backward(self, *gys): raise NotImplementedError() 示例\r","date":"2025-12-27","objectID":"/posts/%E6%B7%B1%E5%BA%A6%E5%AD%A6%E4%B9%A0%E5%9F%BA%E7%A1%80~%E8%87%AA%E5%88%B6%E6%A1%86%E6%9E%B6/:7:3","tags":null,"title":"深度学习基础~自制框架","uri":"/posts/%E6%B7%B1%E5%BA%A6%E5%AD%A6%E4%B9%A0%E5%9F%BA%E7%A1%80~%E8%87%AA%E5%88%B6%E6%A1%86%E6%9E%B6/#是否反向传播"},{"categories":[],"collections":["深度学习"],"content":"52.3 是否反向传播\r只有训练的时候需要反向传播，验证和测试的时候不需要。 框架搭建\r创建 Config 类 import contextlib class Config: # 创建Config 类，定义是否启用反向传播 enable_backprop = True # 创建一个 With 上下文环境，在该上下文中，对 Config 进行修改 @contextlib.contextmanager def using_config(name, value): old_value = getattr(Config, name) setattr(Config, name, value) try: yield finally: setattr(Config, name, old_value) # 创建 no_grad 函数，返回 enable_backprop 为 false 的上下文环境 def no_grad(): return using_config('enable_backprop', False) 函数类进行如下修改： class Function: def __call__(self, *inputs): xs = [x.data for x in inputs] ys = self.forward(*xs) if not isinstance(ys, tuple): ys = (ys,) outputs = [Variable(as_array(y)) for y in ys] if Config.enable_backprop: # 是否启用反向传播 self.generation = max([x.generation for x in inputs]) for output in outputs: output.set_creator(self) self.inputs = inputs self.outputs = [weakref.ref(output) for output in outputs] return outputs if len(outputs) \u003e 1 else outputs[0] def forward(self, *xs): raise NotImplementedError() def backward(self, *gys): raise NotImplementedError() 示例\r","date":"2025-12-27","objectID":"/posts/%E6%B7%B1%E5%BA%A6%E5%AD%A6%E4%B9%A0%E5%9F%BA%E7%A1%80~%E8%87%AA%E5%88%B6%E6%A1%86%E6%9E%B6/:7:3","tags":null,"title":"深度学习基础~自制框架","uri":"/posts/%E6%B7%B1%E5%BA%A6%E5%AD%A6%E4%B9%A0%E5%9F%BA%E7%A1%80~%E8%87%AA%E5%88%B6%E6%A1%86%E6%9E%B6/#框架搭建-11"},{"categories":[],"collections":["深度学习"],"content":"52.3 是否反向传播\r只有训练的时候需要反向传播，验证和测试的时候不需要。 框架搭建\r创建 Config 类 import contextlib class Config: # 创建Config 类，定义是否启用反向传播 enable_backprop = True # 创建一个 With 上下文环境，在该上下文中，对 Config 进行修改 @contextlib.contextmanager def using_config(name, value): old_value = getattr(Config, name) setattr(Config, name, value) try: yield finally: setattr(Config, name, old_value) # 创建 no_grad 函数，返回 enable_backprop 为 false 的上下文环境 def no_grad(): return using_config('enable_backprop', False) 函数类进行如下修改： class Function: def __call__(self, *inputs): xs = [x.data for x in inputs] ys = self.forward(*xs) if not isinstance(ys, tuple): ys = (ys,) outputs = [Variable(as_array(y)) for y in ys] if Config.enable_backprop: # 是否启用反向传播 self.generation = max([x.generation for x in inputs]) for output in outputs: output.set_creator(self) self.inputs = inputs self.outputs = [weakref.ref(output) for output in outputs] return outputs if len(outputs) \u003e 1 else outputs[0] def forward(self, *xs): raise NotImplementedError() def backward(self, *gys): raise NotImplementedError() 示例\r","date":"2025-12-27","objectID":"/posts/%E6%B7%B1%E5%BA%A6%E5%AD%A6%E4%B9%A0%E5%9F%BA%E7%A1%80~%E8%87%AA%E5%88%B6%E6%A1%86%E6%9E%B6/:7:3","tags":null,"title":"深度学习基础~自制框架","uri":"/posts/%E6%B7%B1%E5%BA%A6%E5%AD%A6%E4%B9%A0%E5%9F%BA%E7%A1%80~%E8%87%AA%E5%88%B6%E6%A1%86%E6%9E%B6/#示例-8"},{"categories":[],"collections":["深度学习"],"content":"52.4 变量类属性方法\r给变量类增加 name 、shape、ndim、size、dtype 属性， len、print 方法 框架搭建\rclass Variable: def __init__(self, data, name=None): if data is not None: if not isinstance(data, np.ndarray): raise TypeError('{} is not supported'.format(type(data))) self.data = data self.name = name # 增加 name 属性 self.grad = None self.creator = None self.generation = 0 @property def shape(self): # 增加 shape 属性 return self.data.shape @property def ndim(self): # 增加 ndim 属性 return self.data.ndim @property def size(self): # 增加 size 属性 return self.data.size @property def dtype(self): # 增加 dtype 属性 return self.data.dtype def __len__(self): # 增加 len 方法 return len(self.data) def __repr__(self): # 增加 print 方法 if self.data is None: return 'variable(None)' p = str(self.data).replace('\\n', '\\n' + ' ' * 9) return 'variable(' + p + ')' def set_creator(self, func): self.creator = func self.generation = func.generation + 1 def cleargrad(self): self.grad = None def backward(self, retain_grad=False): # 增加梯度保留参数 if self.grad is None: self.grad = np.ones_like(self.data) funcs = [] seen_set = set() def add_func(f): if f not in seen_set: funcs.append(f) seen_set.add(f) funcs.sort(key=lambda x: x.generation) add_func(self.creator) while funcs: f = funcs.pop() gys = [output().grad for output in f.outputs] gxs = f.backward(*gys) if not isinstance(gxs, tuple): gxs = (gxs,) for x, gx in zip(f.inputs, gxs): if x.grad is None: x.grad = gx else: x.grad = x.grad + gx if x.creator is not None: add_func(x.creator) if not retain_grad: for y in f.outputs: y().grad = None 示例\r","date":"2025-12-27","objectID":"/posts/%E6%B7%B1%E5%BA%A6%E5%AD%A6%E4%B9%A0%E5%9F%BA%E7%A1%80~%E8%87%AA%E5%88%B6%E6%A1%86%E6%9E%B6/:7:4","tags":null,"title":"深度学习基础~自制框架","uri":"/posts/%E6%B7%B1%E5%BA%A6%E5%AD%A6%E4%B9%A0%E5%9F%BA%E7%A1%80~%E8%87%AA%E5%88%B6%E6%A1%86%E6%9E%B6/#变量类属性方法"},{"categories":[],"collections":["深度学习"],"content":"52.4 变量类属性方法\r给变量类增加 name 、shape、ndim、size、dtype 属性， len、print 方法 框架搭建\rclass Variable: def __init__(self, data, name=None): if data is not None: if not isinstance(data, np.ndarray): raise TypeError('{} is not supported'.format(type(data))) self.data = data self.name = name # 增加 name 属性 self.grad = None self.creator = None self.generation = 0 @property def shape(self): # 增加 shape 属性 return self.data.shape @property def ndim(self): # 增加 ndim 属性 return self.data.ndim @property def size(self): # 增加 size 属性 return self.data.size @property def dtype(self): # 增加 dtype 属性 return self.data.dtype def __len__(self): # 增加 len 方法 return len(self.data) def __repr__(self): # 增加 print 方法 if self.data is None: return 'variable(None)' p = str(self.data).replace('\\n', '\\n' + ' ' * 9) return 'variable(' + p + ')' def set_creator(self, func): self.creator = func self.generation = func.generation + 1 def cleargrad(self): self.grad = None def backward(self, retain_grad=False): # 增加梯度保留参数 if self.grad is None: self.grad = np.ones_like(self.data) funcs = [] seen_set = set() def add_func(f): if f not in seen_set: funcs.append(f) seen_set.add(f) funcs.sort(key=lambda x: x.generation) add_func(self.creator) while funcs: f = funcs.pop() gys = [output().grad for output in f.outputs] gxs = f.backward(*gys) if not isinstance(gxs, tuple): gxs = (gxs,) for x, gx in zip(f.inputs, gxs): if x.grad is None: x.grad = gx else: x.grad = x.grad + gx if x.creator is not None: add_func(x.creator) if not retain_grad: for y in f.outputs: y().grad = None 示例\r","date":"2025-12-27","objectID":"/posts/%E6%B7%B1%E5%BA%A6%E5%AD%A6%E4%B9%A0%E5%9F%BA%E7%A1%80~%E8%87%AA%E5%88%B6%E6%A1%86%E6%9E%B6/:7:4","tags":null,"title":"深度学习基础~自制框架","uri":"/posts/%E6%B7%B1%E5%BA%A6%E5%AD%A6%E4%B9%A0%E5%9F%BA%E7%A1%80~%E8%87%AA%E5%88%B6%E6%A1%86%E6%9E%B6/#框架搭建-12"},{"categories":[],"collections":["深度学习"],"content":"52.4 变量类属性方法\r给变量类增加 name 、shape、ndim、size、dtype 属性， len、print 方法 框架搭建\rclass Variable: def __init__(self, data, name=None): if data is not None: if not isinstance(data, np.ndarray): raise TypeError('{} is not supported'.format(type(data))) self.data = data self.name = name # 增加 name 属性 self.grad = None self.creator = None self.generation = 0 @property def shape(self): # 增加 shape 属性 return self.data.shape @property def ndim(self): # 增加 ndim 属性 return self.data.ndim @property def size(self): # 增加 size 属性 return self.data.size @property def dtype(self): # 增加 dtype 属性 return self.data.dtype def __len__(self): # 增加 len 方法 return len(self.data) def __repr__(self): # 增加 print 方法 if self.data is None: return 'variable(None)' p = str(self.data).replace('\\n', '\\n' + ' ' * 9) return 'variable(' + p + ')' def set_creator(self, func): self.creator = func self.generation = func.generation + 1 def cleargrad(self): self.grad = None def backward(self, retain_grad=False): # 增加梯度保留参数 if self.grad is None: self.grad = np.ones_like(self.data) funcs = [] seen_set = set() def add_func(f): if f not in seen_set: funcs.append(f) seen_set.add(f) funcs.sort(key=lambda x: x.generation) add_func(self.creator) while funcs: f = funcs.pop() gys = [output().grad for output in f.outputs] gxs = f.backward(*gys) if not isinstance(gxs, tuple): gxs = (gxs,) for x, gx in zip(f.inputs, gxs): if x.grad is None: x.grad = gx else: x.grad = x.grad + gx if x.creator is not None: add_func(x.creator) if not retain_grad: for y in f.outputs: y().grad = None 示例\r","date":"2025-12-27","objectID":"/posts/%E6%B7%B1%E5%BA%A6%E5%AD%A6%E4%B9%A0%E5%9F%BA%E7%A1%80~%E8%87%AA%E5%88%B6%E6%A1%86%E6%9E%B6/:7:4","tags":null,"title":"深度学习基础~自制框架","uri":"/posts/%E6%B7%B1%E5%BA%A6%E5%AD%A6%E4%B9%A0%E5%9F%BA%E7%A1%80~%E8%87%AA%E5%88%B6%E6%A1%86%E6%9E%B6/#示例-9"},{"categories":[],"collections":["深度学习"],"content":"52.5 运算符重载\r重载运算符，实现类似 $a * b + c$ 的效果，并且左右两侧有一边是 int、float 或者 ndarray 时都支持运算。 框架搭建\r创建运算符 Function： class Mul(Function): def forward(self, x0, x1): y = x0 * x1 return y def backward(self, gy): x0, x1 = self.inputs[0].data, self.inputs[1].data return gy * x1, gy * x0 class Neg(Function): def forward(self, x): return -x def backward(self, gy): return -gy class Sub(Function): def forward(self, x0, x1): y = x0 - x1 return y def backward(self, gy): return gy, -gy class Div(Function): def forward(self, x0, x1): y = x0 / x1 return y def backward(self, gy): x0, x1 = self.inputs[0].data, self.inputs[1].data gx0 = gy / x1 gx1 = gy * (-x0 / x1 ** 2) return gx0, gx1 class Pow(Function): def __init__(self, c): self.c = c def forward(self, x): y = x ** self.c return y def backward(self, gy): x = self.inputs[0].data c = self.c gx = c * x ** (c - 1) * gy return gx def add(x0, x1): x0 = as_array(x0) x1 = as_array(x1) return Add()(x0, x1) def mul(x0, x1): x0 = as_array(x0) x1 = as_array(x1) return Mul()(x0, x1) def neg(x): return Neg()(x) def sub(x0, x1): x1 = as_array(x1) return Sub()(x0, x1) def rsub(x0, x1): x1 = as_array(x1) return Sub()(x1, x0) def div(x0, x1): x1 = as_array(x1) return Div()(x0, x1) def rdiv(x0, x1): x1 = as_array(x1) return Div()(x1, x0) def pow(x, c): return Pow(c)(x) def setup_variable(): Variable.__add__ = add Variable.__radd__ = add Variable.__mul__ = mul Variable.__rmul__ = mul Variable.__neg__ = neg Variable.__sub__ = sub Variable.__rsub__ = rsub Variable.__truediv__ = div Variable.__rtruediv__ = rdiv Variable.__pow__ = pow setup_variable() 变量类进行如下修改： class Variable: __array_priority__ = 200 # 优先级 200,Variable 右侧运算的优先级要高于 ndarray 左侧运算的优先级 def __init__(self, data, name=None): if data is not None: if not isinstance(data, np.ndarray): raise TypeError('{} is not supported'.format(type(data))) self.data = data self.name = name self.grad = None self.creator = None self.generation = 0 @property def shape(self): return self.data.shape @property def ndim(self): return self.data.ndim @property def size(self): return self.data.size @property def dtype(self): return self.data.dtype def __len__(self): return len(self.data) def __repr__(self): if self.data is None: return 'variable(None)' p = str(self.data).replace('\\n', '\\n' + ' ' * 9) return 'variable(' + p + ')' def set_creator(self, func): self.creator = func self.generation = func.generation + 1 def cleargrad(self): self.grad = None def backward(self, retain_grad=False): if self.grad is None: self.grad = np.ones_like(self.data) funcs = [] seen_set = set() def add_func(f): if f not in seen_set: funcs.append(f) seen_set.add(f) funcs.sort(key=lambda x: x.generation) add_func(self.creator) while funcs: f = funcs.pop() gys = [output().grad for output in f.outputs] gxs = f.backward(*gys) if not isinstance(gxs, tuple): gxs = (gxs,) for x, gx in zip(f.inputs, gxs): if x.grad is None: x.grad = gx else: x.grad = x.grad + gx if x.creator is not None: add_func(x.creator) if not retain_grad: for y in f.outputs: y().grad = None 定义函数 as_variable，当运算符一侧不是 Variable 时转换为 Variable： def as_variable(obj): if isinstance(obj, Variable): return obj return Variable(obj) 函数类进行如下修改： class Function: def __call__(self, *inputs): inputs = [as_variable(x) for x in inputs] # 转成 Variable xs = [x.data for x in inputs] ys = self.forward(*xs) if not isinstance(ys, tuple): ys = (ys,) outputs = [Variable(as_array(y)) for y in ys] if Config.enable_backprop: self.generation = max([x.generation for x in inputs]) for output in outputs: output.set_creator(self) self.inputs = inputs self.outputs = [weakref.ref(output) for output in outputs] return outputs if len(outputs) \u003e 1 else outputs[0] def forward(self, *xs): raise NotImplementedError() def backward(self, *gys): raise NotImplementedError() 示例\r","date":"2025-12-27","objectID":"/posts/%E6%B7%B1%E5%BA%A6%E5%AD%A6%E4%B9%A0%E5%9F%BA%E7%A1%80~%E8%87%AA%E5%88%B6%E6%A1%86%E6%9E%B6/:7:5","tags":null,"title":"深度学习基础~自制框架","uri":"/posts/%E6%B7%B1%E5%BA%A6%E5%AD%A6%E4%B9%A0%E5%9F%BA%E7%A1%80~%E8%87%AA%E5%88%B6%E6%A1%86%E6%9E%B6/#运算符重载"},{"categories":[],"collections":["深度学习"],"content":"52.5 运算符重载\r重载运算符，实现类似 $a * b + c$ 的效果，并且左右两侧有一边是 int、float 或者 ndarray 时都支持运算。 框架搭建\r创建运算符 Function： class Mul(Function): def forward(self, x0, x1): y = x0 * x1 return y def backward(self, gy): x0, x1 = self.inputs[0].data, self.inputs[1].data return gy * x1, gy * x0 class Neg(Function): def forward(self, x): return -x def backward(self, gy): return -gy class Sub(Function): def forward(self, x0, x1): y = x0 - x1 return y def backward(self, gy): return gy, -gy class Div(Function): def forward(self, x0, x1): y = x0 / x1 return y def backward(self, gy): x0, x1 = self.inputs[0].data, self.inputs[1].data gx0 = gy / x1 gx1 = gy * (-x0 / x1 ** 2) return gx0, gx1 class Pow(Function): def __init__(self, c): self.c = c def forward(self, x): y = x ** self.c return y def backward(self, gy): x = self.inputs[0].data c = self.c gx = c * x ** (c - 1) * gy return gx def add(x0, x1): x0 = as_array(x0) x1 = as_array(x1) return Add()(x0, x1) def mul(x0, x1): x0 = as_array(x0) x1 = as_array(x1) return Mul()(x0, x1) def neg(x): return Neg()(x) def sub(x0, x1): x1 = as_array(x1) return Sub()(x0, x1) def rsub(x0, x1): x1 = as_array(x1) return Sub()(x1, x0) def div(x0, x1): x1 = as_array(x1) return Div()(x0, x1) def rdiv(x0, x1): x1 = as_array(x1) return Div()(x1, x0) def pow(x, c): return Pow(c)(x) def setup_variable(): Variable.__add__ = add Variable.__radd__ = add Variable.__mul__ = mul Variable.__rmul__ = mul Variable.__neg__ = neg Variable.__sub__ = sub Variable.__rsub__ = rsub Variable.__truediv__ = div Variable.__rtruediv__ = rdiv Variable.__pow__ = pow setup_variable() 变量类进行如下修改： class Variable: __array_priority__ = 200 # 优先级 200,Variable 右侧运算的优先级要高于 ndarray 左侧运算的优先级 def __init__(self, data, name=None): if data is not None: if not isinstance(data, np.ndarray): raise TypeError('{} is not supported'.format(type(data))) self.data = data self.name = name self.grad = None self.creator = None self.generation = 0 @property def shape(self): return self.data.shape @property def ndim(self): return self.data.ndim @property def size(self): return self.data.size @property def dtype(self): return self.data.dtype def __len__(self): return len(self.data) def __repr__(self): if self.data is None: return 'variable(None)' p = str(self.data).replace('\\n', '\\n' + ' ' * 9) return 'variable(' + p + ')' def set_creator(self, func): self.creator = func self.generation = func.generation + 1 def cleargrad(self): self.grad = None def backward(self, retain_grad=False): if self.grad is None: self.grad = np.ones_like(self.data) funcs = [] seen_set = set() def add_func(f): if f not in seen_set: funcs.append(f) seen_set.add(f) funcs.sort(key=lambda x: x.generation) add_func(self.creator) while funcs: f = funcs.pop() gys = [output().grad for output in f.outputs] gxs = f.backward(*gys) if not isinstance(gxs, tuple): gxs = (gxs,) for x, gx in zip(f.inputs, gxs): if x.grad is None: x.grad = gx else: x.grad = x.grad + gx if x.creator is not None: add_func(x.creator) if not retain_grad: for y in f.outputs: y().grad = None 定义函数 as_variable，当运算符一侧不是 Variable 时转换为 Variable： def as_variable(obj): if isinstance(obj, Variable): return obj return Variable(obj) 函数类进行如下修改： class Function: def __call__(self, *inputs): inputs = [as_variable(x) for x in inputs] # 转成 Variable xs = [x.data for x in inputs] ys = self.forward(*xs) if not isinstance(ys, tuple): ys = (ys,) outputs = [Variable(as_array(y)) for y in ys] if Config.enable_backprop: self.generation = max([x.generation for x in inputs]) for output in outputs: output.set_creator(self) self.inputs = inputs self.outputs = [weakref.ref(output) for output in outputs] return outputs if len(outputs) \u003e 1 else outputs[0] def forward(self, *xs): raise NotImplementedError() def backward(self, *gys): raise NotImplementedError() 示例\r","date":"2025-12-27","objectID":"/posts/%E6%B7%B1%E5%BA%A6%E5%AD%A6%E4%B9%A0%E5%9F%BA%E7%A1%80~%E8%87%AA%E5%88%B6%E6%A1%86%E6%9E%B6/:7:5","tags":null,"title":"深度学习基础~自制框架","uri":"/posts/%E6%B7%B1%E5%BA%A6%E5%AD%A6%E4%B9%A0%E5%9F%BA%E7%A1%80~%E8%87%AA%E5%88%B6%E6%A1%86%E6%9E%B6/#框架搭建-13"},{"categories":[],"collections":["深度学习"],"content":"52.5 运算符重载\r重载运算符，实现类似 $a * b + c$ 的效果，并且左右两侧有一边是 int、float 或者 ndarray 时都支持运算。 框架搭建\r创建运算符 Function： class Mul(Function): def forward(self, x0, x1): y = x0 * x1 return y def backward(self, gy): x0, x1 = self.inputs[0].data, self.inputs[1].data return gy * x1, gy * x0 class Neg(Function): def forward(self, x): return -x def backward(self, gy): return -gy class Sub(Function): def forward(self, x0, x1): y = x0 - x1 return y def backward(self, gy): return gy, -gy class Div(Function): def forward(self, x0, x1): y = x0 / x1 return y def backward(self, gy): x0, x1 = self.inputs[0].data, self.inputs[1].data gx0 = gy / x1 gx1 = gy * (-x0 / x1 ** 2) return gx0, gx1 class Pow(Function): def __init__(self, c): self.c = c def forward(self, x): y = x ** self.c return y def backward(self, gy): x = self.inputs[0].data c = self.c gx = c * x ** (c - 1) * gy return gx def add(x0, x1): x0 = as_array(x0) x1 = as_array(x1) return Add()(x0, x1) def mul(x0, x1): x0 = as_array(x0) x1 = as_array(x1) return Mul()(x0, x1) def neg(x): return Neg()(x) def sub(x0, x1): x1 = as_array(x1) return Sub()(x0, x1) def rsub(x0, x1): x1 = as_array(x1) return Sub()(x1, x0) def div(x0, x1): x1 = as_array(x1) return Div()(x0, x1) def rdiv(x0, x1): x1 = as_array(x1) return Div()(x1, x0) def pow(x, c): return Pow(c)(x) def setup_variable(): Variable.__add__ = add Variable.__radd__ = add Variable.__mul__ = mul Variable.__rmul__ = mul Variable.__neg__ = neg Variable.__sub__ = sub Variable.__rsub__ = rsub Variable.__truediv__ = div Variable.__rtruediv__ = rdiv Variable.__pow__ = pow setup_variable() 变量类进行如下修改： class Variable: __array_priority__ = 200 # 优先级 200,Variable 右侧运算的优先级要高于 ndarray 左侧运算的优先级 def __init__(self, data, name=None): if data is not None: if not isinstance(data, np.ndarray): raise TypeError('{} is not supported'.format(type(data))) self.data = data self.name = name self.grad = None self.creator = None self.generation = 0 @property def shape(self): return self.data.shape @property def ndim(self): return self.data.ndim @property def size(self): return self.data.size @property def dtype(self): return self.data.dtype def __len__(self): return len(self.data) def __repr__(self): if self.data is None: return 'variable(None)' p = str(self.data).replace('\\n', '\\n' + ' ' * 9) return 'variable(' + p + ')' def set_creator(self, func): self.creator = func self.generation = func.generation + 1 def cleargrad(self): self.grad = None def backward(self, retain_grad=False): if self.grad is None: self.grad = np.ones_like(self.data) funcs = [] seen_set = set() def add_func(f): if f not in seen_set: funcs.append(f) seen_set.add(f) funcs.sort(key=lambda x: x.generation) add_func(self.creator) while funcs: f = funcs.pop() gys = [output().grad for output in f.outputs] gxs = f.backward(*gys) if not isinstance(gxs, tuple): gxs = (gxs,) for x, gx in zip(f.inputs, gxs): if x.grad is None: x.grad = gx else: x.grad = x.grad + gx if x.creator is not None: add_func(x.creator) if not retain_grad: for y in f.outputs: y().grad = None 定义函数 as_variable，当运算符一侧不是 Variable 时转换为 Variable： def as_variable(obj): if isinstance(obj, Variable): return obj return Variable(obj) 函数类进行如下修改： class Function: def __call__(self, *inputs): inputs = [as_variable(x) for x in inputs] # 转成 Variable xs = [x.data for x in inputs] ys = self.forward(*xs) if not isinstance(ys, tuple): ys = (ys,) outputs = [Variable(as_array(y)) for y in ys] if Config.enable_backprop: self.generation = max([x.generation for x in inputs]) for output in outputs: output.set_creator(self) self.inputs = inputs self.outputs = [weakref.ref(output) for output in outputs] return outputs if len(outputs) \u003e 1 else outputs[0] def forward(self, *xs): raise NotImplementedError() def backward(self, *gys): raise NotImplementedError() 示例\r","date":"2025-12-27","objectID":"/posts/%E6%B7%B1%E5%BA%A6%E5%AD%A6%E4%B9%A0%E5%9F%BA%E7%A1%80~%E8%87%AA%E5%88%B6%E6%A1%86%E6%9E%B6/:7:5","tags":null,"title":"深度学习基础~自制框架","uri":"/posts/%E6%B7%B1%E5%BA%A6%E5%AD%A6%E4%B9%A0%E5%9F%BA%E7%A1%80~%E8%87%AA%E5%88%B6%E6%A1%86%E6%9E%B6/#示例-10"},{"categories":[],"collections":["深度学习"],"content":"52.6 框架打包\r框架搭建\r将之前的代码打包到 dezero/core_simple.py 文件，并创建 dezero/__init__.py 文件： is_simple_core = True if is_simple_core: from dezero.core_simple import Variable from dezero.core_simple import Function from dezero.core_simple import using_config from dezero.core_simple import no_grad from dezero.core_simple import as_array from dezero.core_simple import as_variable from dezero.core_simple import setup_variable setup_variable() 示例\r信息\r到这儿就有一些 pytorch 的雏形了。 ","date":"2025-12-27","objectID":"/posts/%E6%B7%B1%E5%BA%A6%E5%AD%A6%E4%B9%A0%E5%9F%BA%E7%A1%80~%E8%87%AA%E5%88%B6%E6%A1%86%E6%9E%B6/:7:6","tags":null,"title":"深度学习基础~自制框架","uri":"/posts/%E6%B7%B1%E5%BA%A6%E5%AD%A6%E4%B9%A0%E5%9F%BA%E7%A1%80~%E8%87%AA%E5%88%B6%E6%A1%86%E6%9E%B6/#框架打包"},{"categories":[],"collections":["深度学习"],"content":"52.6 框架打包\r框架搭建\r将之前的代码打包到 dezero/core_simple.py 文件，并创建 dezero/__init__.py 文件： is_simple_core = True if is_simple_core: from dezero.core_simple import Variable from dezero.core_simple import Function from dezero.core_simple import using_config from dezero.core_simple import no_grad from dezero.core_simple import as_array from dezero.core_simple import as_variable from dezero.core_simple import setup_variable setup_variable() 示例\r信息\r到这儿就有一些 pytorch 的雏形了。 ","date":"2025-12-27","objectID":"/posts/%E6%B7%B1%E5%BA%A6%E5%AD%A6%E4%B9%A0%E5%9F%BA%E7%A1%80~%E8%87%AA%E5%88%B6%E6%A1%86%E6%9E%B6/:7:6","tags":null,"title":"深度学习基础~自制框架","uri":"/posts/%E6%B7%B1%E5%BA%A6%E5%AD%A6%E4%B9%A0%E5%9F%BA%E7%A1%80~%E8%87%AA%E5%88%B6%E6%A1%86%E6%9E%B6/#框架搭建-14"},{"categories":[],"collections":["深度学习"],"content":"52.6 框架打包\r框架搭建\r将之前的代码打包到 dezero/core_simple.py 文件，并创建 dezero/__init__.py 文件： is_simple_core = True if is_simple_core: from dezero.core_simple import Variable from dezero.core_simple import Function from dezero.core_simple import using_config from dezero.core_simple import no_grad from dezero.core_simple import as_array from dezero.core_simple import as_variable from dezero.core_simple import setup_variable setup_variable() 示例\r信息\r到这儿就有一些 pytorch 的雏形了。 ","date":"2025-12-27","objectID":"/posts/%E6%B7%B1%E5%BA%A6%E5%AD%A6%E4%B9%A0%E5%9F%BA%E7%A1%80~%E8%87%AA%E5%88%B6%E6%A1%86%E6%9E%B6/:7:6","tags":null,"title":"深度学习基础~自制框架","uri":"/posts/%E6%B7%B1%E5%BA%A6%E5%AD%A6%E4%B9%A0%E5%9F%BA%E7%A1%80~%E8%87%AA%E5%88%B6%E6%A1%86%E6%9E%B6/#示例-11"},{"categories":[],"collections":["深度学习"],"content":"52.7 计算图可视化\r使用 Graphviz 将计算图可视化 brew install graphviz Graphviz 可以将 DOT 语言翻译成图像，将下面的文字存储成 xxx.dot 文件： digraph g { 1 [label=\"x\", color=orange, style=filled] 2 [label=\"y\", color=orange, style=filled] 3 [label=\"Exp\", color=lightblue, style=filled, shape=box] 1 -\u003e 3 3 -\u003e 2 } 使用方法是： dot xxx.dot -T png -o xxx.png 其中 -T 选项后指定要输出的文件扩展名，扩展名可以指定为pdf、svg 等。 架构搭建\r在 dezero/utils.py 中实现 get_dot_graph 函数： import os import subprocess def _dot_var(v, verbose=False): name = '' if v.name is None else v.name if verbose and v.data is not None: if v.name is not None: name += ': ' name += f'{v.shape} {v.dtype}' dot_var = f'{id(v)} [label=\"{name}\", color=orange, style=filled]\\n' return dot_var.format(id(v), name) def _dot_func(f): txt = f'{id(f)} [label=\"{f.__class__.__name__}\", color=lightblue, style=filled,shape=box]\\n' for x in f.inputs: txt += f'{id(x)} -\u003e {id(f)}\\n' for y in f.outputs: txt += f'{id(f)} -\u003e {id(y())}\\n' # y是weakref return txt def get_dot_graph(output, verbose=True): txt = '' funcs = [] seen_set = set() def add_func(f): if f not in seen_set: funcs.append(f) seen_set.add(f) add_func(output.creator) txt += _dot_var(output, verbose) while funcs: func = funcs.pop() txt += _dot_func(func) for x in func.inputs: txt += _dot_var(x, verbose) if x.creator is not None: add_func(x.creator) return 'digraph g {\\n' + txt + '}' def plot_dot_graph(output, verbose=True, to_file='graph.png'): dot_graph = get_dot_graph(output, verbose) graph_path = './sample.dot' with open(graph_path, 'w') as f: f.write(dot_graph) extension = os.path.splitext(to_file)[1][1:] cmd = f'dot {graph_path} -T {extension} -o {to_file}' subprocess.run(cmd, shell=True) 示例\r","date":"2025-12-27","objectID":"/posts/%E6%B7%B1%E5%BA%A6%E5%AD%A6%E4%B9%A0%E5%9F%BA%E7%A1%80~%E8%87%AA%E5%88%B6%E6%A1%86%E6%9E%B6/:7:7","tags":null,"title":"深度学习基础~自制框架","uri":"/posts/%E6%B7%B1%E5%BA%A6%E5%AD%A6%E4%B9%A0%E5%9F%BA%E7%A1%80~%E8%87%AA%E5%88%B6%E6%A1%86%E6%9E%B6/#计算图可视化"},{"categories":[],"collections":["深度学习"],"content":"52.7 计算图可视化\r使用 Graphviz 将计算图可视化 brew install graphviz Graphviz 可以将 DOT 语言翻译成图像，将下面的文字存储成 xxx.dot 文件： digraph g { 1 [label=\"x\", color=orange, style=filled] 2 [label=\"y\", color=orange, style=filled] 3 [label=\"Exp\", color=lightblue, style=filled, shape=box] 1 -\u003e 3 3 -\u003e 2 } 使用方法是： dot xxx.dot -T png -o xxx.png 其中 -T 选项后指定要输出的文件扩展名，扩展名可以指定为pdf、svg 等。 架构搭建\r在 dezero/utils.py 中实现 get_dot_graph 函数： import os import subprocess def _dot_var(v, verbose=False): name = '' if v.name is None else v.name if verbose and v.data is not None: if v.name is not None: name += ': ' name += f'{v.shape} {v.dtype}' dot_var = f'{id(v)} [label=\"{name}\", color=orange, style=filled]\\n' return dot_var.format(id(v), name) def _dot_func(f): txt = f'{id(f)} [label=\"{f.__class__.__name__}\", color=lightblue, style=filled,shape=box]\\n' for x in f.inputs: txt += f'{id(x)} -\u003e {id(f)}\\n' for y in f.outputs: txt += f'{id(f)} -\u003e {id(y())}\\n' # y是weakref return txt def get_dot_graph(output, verbose=True): txt = '' funcs = [] seen_set = set() def add_func(f): if f not in seen_set: funcs.append(f) seen_set.add(f) add_func(output.creator) txt += _dot_var(output, verbose) while funcs: func = funcs.pop() txt += _dot_func(func) for x in func.inputs: txt += _dot_var(x, verbose) if x.creator is not None: add_func(x.creator) return 'digraph g {\\n' + txt + '}' def plot_dot_graph(output, verbose=True, to_file='graph.png'): dot_graph = get_dot_graph(output, verbose) graph_path = './sample.dot' with open(graph_path, 'w') as f: f.write(dot_graph) extension = os.path.splitext(to_file)[1][1:] cmd = f'dot {graph_path} -T {extension} -o {to_file}' subprocess.run(cmd, shell=True) 示例\r","date":"2025-12-27","objectID":"/posts/%E6%B7%B1%E5%BA%A6%E5%AD%A6%E4%B9%A0%E5%9F%BA%E7%A1%80~%E8%87%AA%E5%88%B6%E6%A1%86%E6%9E%B6/:7:7","tags":null,"title":"深度学习基础~自制框架","uri":"/posts/%E6%B7%B1%E5%BA%A6%E5%AD%A6%E4%B9%A0%E5%9F%BA%E7%A1%80~%E8%87%AA%E5%88%B6%E6%A1%86%E6%9E%B6/#架构搭建"},{"categories":[],"collections":["深度学习"],"content":"52.7 计算图可视化\r使用 Graphviz 将计算图可视化 brew install graphviz Graphviz 可以将 DOT 语言翻译成图像，将下面的文字存储成 xxx.dot 文件： digraph g { 1 [label=\"x\", color=orange, style=filled] 2 [label=\"y\", color=orange, style=filled] 3 [label=\"Exp\", color=lightblue, style=filled, shape=box] 1 -\u003e 3 3 -\u003e 2 } 使用方法是： dot xxx.dot -T png -o xxx.png 其中 -T 选项后指定要输出的文件扩展名，扩展名可以指定为pdf、svg 等。 架构搭建\r在 dezero/utils.py 中实现 get_dot_graph 函数： import os import subprocess def _dot_var(v, verbose=False): name = '' if v.name is None else v.name if verbose and v.data is not None: if v.name is not None: name += ': ' name += f'{v.shape} {v.dtype}' dot_var = f'{id(v)} [label=\"{name}\", color=orange, style=filled]\\n' return dot_var.format(id(v), name) def _dot_func(f): txt = f'{id(f)} [label=\"{f.__class__.__name__}\", color=lightblue, style=filled,shape=box]\\n' for x in f.inputs: txt += f'{id(x)} -\u003e {id(f)}\\n' for y in f.outputs: txt += f'{id(f)} -\u003e {id(y())}\\n' # y是weakref return txt def get_dot_graph(output, verbose=True): txt = '' funcs = [] seen_set = set() def add_func(f): if f not in seen_set: funcs.append(f) seen_set.add(f) add_func(output.creator) txt += _dot_var(output, verbose) while funcs: func = funcs.pop() txt += _dot_func(func) for x in func.inputs: txt += _dot_var(x, verbose) if x.creator is not None: add_func(x.creator) return 'digraph g {\\n' + txt + '}' def plot_dot_graph(output, verbose=True, to_file='graph.png'): dot_graph = get_dot_graph(output, verbose) graph_path = './sample.dot' with open(graph_path, 'w') as f: f.write(dot_graph) extension = os.path.splitext(to_file)[1][1:] cmd = f'dot {graph_path} -T {extension} -o {to_file}' subprocess.run(cmd, shell=True) 示例\r","date":"2025-12-27","objectID":"/posts/%E6%B7%B1%E5%BA%A6%E5%AD%A6%E4%B9%A0%E5%9F%BA%E7%A1%80~%E8%87%AA%E5%88%B6%E6%A1%86%E6%9E%B6/:7:7","tags":null,"title":"深度学习基础~自制框架","uri":"/posts/%E6%B7%B1%E5%BA%A6%E5%AD%A6%E4%B9%A0%E5%9F%BA%E7%A1%80~%E8%87%AA%E5%88%B6%E6%A1%86%E6%9E%B6/#示例-12"},{"categories":[],"collections":["深度学习"],"content":"第 53 节 高阶导数\r","date":"2025-12-27","objectID":"/posts/%E6%B7%B1%E5%BA%A6%E5%AD%A6%E4%B9%A0%E5%9F%BA%E7%A1%80~%E8%87%AA%E5%88%B6%E6%A1%86%E6%9E%B6/:8:0","tags":null,"title":"深度学习基础~自制框架","uri":"/posts/%E6%B7%B1%E5%BA%A6%E5%AD%A6%E4%B9%A0%E5%9F%BA%E7%A1%80~%E8%87%AA%E5%88%B6%E6%A1%86%E6%9E%B6/#高阶导数"},{"categories":[],"collections":["深度学习"],"content":"53.1 梯度下降法\r求解 Rosenbrock 函数的最小值所在位置，其表达式为： $$ y=100\\left(x_1-x_0^2\\right)^2+\\left(x_0-1\\right)^2 $$ 框架搭建\rimport numpy as np from dezero import Variable def rosenbrock(x0, x1): y = 100 * (x1 - x0 ** 2) ** 2 + (x0 - 1) ** 2 return y x0 = Variable(np.array(0.0)) x1 = Variable(np.array(2.0)) lr = 0.001 # 学习率 iters = 1000 # 迭代次数 for i in range(iters): if not (i+1) % 100: print(x0, x1) y = rosenbrock(x0, x1) x0.cleargrad() x1.cleargrad() y.backward() x0.data -= lr * x0.grad x1.data -= lr * x1.grad 示例\r","date":"2025-12-27","objectID":"/posts/%E6%B7%B1%E5%BA%A6%E5%AD%A6%E4%B9%A0%E5%9F%BA%E7%A1%80~%E8%87%AA%E5%88%B6%E6%A1%86%E6%9E%B6/:8:1","tags":null,"title":"深度学习基础~自制框架","uri":"/posts/%E6%B7%B1%E5%BA%A6%E5%AD%A6%E4%B9%A0%E5%9F%BA%E7%A1%80~%E8%87%AA%E5%88%B6%E6%A1%86%E6%9E%B6/#梯度下降法"},{"categories":[],"collections":["深度学习"],"content":"53.1 梯度下降法\r求解 Rosenbrock 函数的最小值所在位置，其表达式为： $$ y=100\\left(x_1-x_0^2\\right)^2+\\left(x_0-1\\right)^2 $$ 框架搭建\rimport numpy as np from dezero import Variable def rosenbrock(x0, x1): y = 100 * (x1 - x0 ** 2) ** 2 + (x0 - 1) ** 2 return y x0 = Variable(np.array(0.0)) x1 = Variable(np.array(2.0)) lr = 0.001 # 学习率 iters = 1000 # 迭代次数 for i in range(iters): if not (i+1) % 100: print(x0, x1) y = rosenbrock(x0, x1) x0.cleargrad() x1.cleargrad() y.backward() x0.data -= lr * x0.grad x1.data -= lr * x1.grad 示例\r","date":"2025-12-27","objectID":"/posts/%E6%B7%B1%E5%BA%A6%E5%AD%A6%E4%B9%A0%E5%9F%BA%E7%A1%80~%E8%87%AA%E5%88%B6%E6%A1%86%E6%9E%B6/:8:1","tags":null,"title":"深度学习基础~自制框架","uri":"/posts/%E6%B7%B1%E5%BA%A6%E5%AD%A6%E4%B9%A0%E5%9F%BA%E7%A1%80~%E8%87%AA%E5%88%B6%E6%A1%86%E6%9E%B6/#框架搭建-15"},{"categories":[],"collections":["深度学习"],"content":"53.1 梯度下降法\r求解 Rosenbrock 函数的最小值所在位置，其表达式为： $$ y=100\\left(x_1-x_0^2\\right)^2+\\left(x_0-1\\right)^2 $$ 框架搭建\rimport numpy as np from dezero import Variable def rosenbrock(x0, x1): y = 100 * (x1 - x0 ** 2) ** 2 + (x0 - 1) ** 2 return y x0 = Variable(np.array(0.0)) x1 = Variable(np.array(2.0)) lr = 0.001 # 学习率 iters = 1000 # 迭代次数 for i in range(iters): if not (i+1) % 100: print(x0, x1) y = rosenbrock(x0, x1) x0.cleargrad() x1.cleargrad() y.backward() x0.data -= lr * x0.grad x1.data -= lr * x1.grad 示例\r","date":"2025-12-27","objectID":"/posts/%E6%B7%B1%E5%BA%A6%E5%AD%A6%E4%B9%A0%E5%9F%BA%E7%A1%80~%E8%87%AA%E5%88%B6%E6%A1%86%E6%9E%B6/:8:1","tags":null,"title":"深度学习基础~自制框架","uri":"/posts/%E6%B7%B1%E5%BA%A6%E5%AD%A6%E4%B9%A0%E5%9F%BA%E7%A1%80~%E8%87%AA%E5%88%B6%E6%A1%86%E6%9E%B6/#示例-13"},{"categories":[],"collections":["深度学习"],"content":"53.2 高阶导数\r对于 $y=sin(x)$ 有如下计算图 x -\u003e y： 在反向传播的过程中，会调用 y.backward()，对于 $y=sin(x)$，就是计算 gx = gy * np.cos(x) 的过程。所以说， gx 是 x 的函数，上述过程也可以构建出一张计算图，如下： 在这张 x -\u003e gx 的计算图中，调用 gx.backward()，就可以计算 x 的二阶导数。 不过，通常计算图只在正向传播的过程中建立，在反向传播时不会被创建。如果我们在反向传播的过程中也建立计算图，就可以求高阶导数。 框架搭建\r变量类进行如下修改： class Variable: def backward(self, retain_grad=False, create_graph=False): if self.grad is None: # self.grad = np.ones_like(self.data) self.grad = Variable(np.ones_like(self.data)) funcs = [] seen_set = set() def add_func(f): if f not in seen_set: funcs.append(f) seen_set.add(f) funcs.sort(key=lambda x: x.generation) add_func(self.creator) while funcs: f = funcs.pop() gys = [output().grad for output in f.outputs] with using_config('enable_backprop', create_graph): gxs = f.backward(*gys) # 主要的backward处理 if not isinstance(gxs, tuple): gxs = (gxs,) for x, gx in zip(f.inputs, gxs): if x.grad is None: x.grad = gx else: x.grad = x.grad + gx # 这个计算也是对象 if x.creator is not None: add_func(x.creator) if not retain_grad: for y in f.outputs: y().grad = None 信息\r将梯度修改成 Variable 变量类，设置 enable_backprop 是否反向传播，可以控制梯度是否建立计算图。 运算符进行如下修改： class Mul(Function): def forward(self, x0, x1): y = x0 * x1 return y def backward(self, gy): x0, x1 = self.inputs return gy * x1, gy * x0 class Div(Function): def forward(self, x0, x1): y = x0 / x1 return y def backward(self, gy): x0, x1 = self.inputs gx0 = gy / x1 gx1 = gy * (-x0 / x1 ** 2) return gx0, gx1 class Pow(Function): def __init__(self, c): self.c = c def forward(self, x): y = x ** self.c return y def backward(self, gy): x, = self.inputs c = self.c gx = c * x ** (c - 1) * gy return gx 信息\rVariable 变量类的运算符已经重载过，修改反向传播函数即可适配 Variable 变量类。 示例\r","date":"2025-12-27","objectID":"/posts/%E6%B7%B1%E5%BA%A6%E5%AD%A6%E4%B9%A0%E5%9F%BA%E7%A1%80~%E8%87%AA%E5%88%B6%E6%A1%86%E6%9E%B6/:8:2","tags":null,"title":"深度学习基础~自制框架","uri":"/posts/%E6%B7%B1%E5%BA%A6%E5%AD%A6%E4%B9%A0%E5%9F%BA%E7%A1%80~%E8%87%AA%E5%88%B6%E6%A1%86%E6%9E%B6/#高阶导数-1"},{"categories":[],"collections":["深度学习"],"content":"53.2 高阶导数\r对于 $y=sin(x)$ 有如下计算图 x -\u003e y： 在反向传播的过程中，会调用 y.backward()，对于 $y=sin(x)$，就是计算 gx = gy * np.cos(x) 的过程。所以说， gx 是 x 的函数，上述过程也可以构建出一张计算图，如下： 在这张 x -\u003e gx 的计算图中，调用 gx.backward()，就可以计算 x 的二阶导数。 不过，通常计算图只在正向传播的过程中建立，在反向传播时不会被创建。如果我们在反向传播的过程中也建立计算图，就可以求高阶导数。 框架搭建\r变量类进行如下修改： class Variable: def backward(self, retain_grad=False, create_graph=False): if self.grad is None: # self.grad = np.ones_like(self.data) self.grad = Variable(np.ones_like(self.data)) funcs = [] seen_set = set() def add_func(f): if f not in seen_set: funcs.append(f) seen_set.add(f) funcs.sort(key=lambda x: x.generation) add_func(self.creator) while funcs: f = funcs.pop() gys = [output().grad for output in f.outputs] with using_config('enable_backprop', create_graph): gxs = f.backward(*gys) # 主要的backward处理 if not isinstance(gxs, tuple): gxs = (gxs,) for x, gx in zip(f.inputs, gxs): if x.grad is None: x.grad = gx else: x.grad = x.grad + gx # 这个计算也是对象 if x.creator is not None: add_func(x.creator) if not retain_grad: for y in f.outputs: y().grad = None 信息\r将梯度修改成 Variable 变量类，设置 enable_backprop 是否反向传播，可以控制梯度是否建立计算图。 运算符进行如下修改： class Mul(Function): def forward(self, x0, x1): y = x0 * x1 return y def backward(self, gy): x0, x1 = self.inputs return gy * x1, gy * x0 class Div(Function): def forward(self, x0, x1): y = x0 / x1 return y def backward(self, gy): x0, x1 = self.inputs gx0 = gy / x1 gx1 = gy * (-x0 / x1 ** 2) return gx0, gx1 class Pow(Function): def __init__(self, c): self.c = c def forward(self, x): y = x ** self.c return y def backward(self, gy): x, = self.inputs c = self.c gx = c * x ** (c - 1) * gy return gx 信息\rVariable 变量类的运算符已经重载过，修改反向传播函数即可适配 Variable 变量类。 示例\r","date":"2025-12-27","objectID":"/posts/%E6%B7%B1%E5%BA%A6%E5%AD%A6%E4%B9%A0%E5%9F%BA%E7%A1%80~%E8%87%AA%E5%88%B6%E6%A1%86%E6%9E%B6/:8:2","tags":null,"title":"深度学习基础~自制框架","uri":"/posts/%E6%B7%B1%E5%BA%A6%E5%AD%A6%E4%B9%A0%E5%9F%BA%E7%A1%80~%E8%87%AA%E5%88%B6%E6%A1%86%E6%9E%B6/#框架搭建-16"},{"categories":[],"collections":["深度学习"],"content":"53.2 高阶导数\r对于 $y=sin(x)$ 有如下计算图 x -\u003e y： 在反向传播的过程中，会调用 y.backward()，对于 $y=sin(x)$，就是计算 gx = gy * np.cos(x) 的过程。所以说， gx 是 x 的函数，上述过程也可以构建出一张计算图，如下： 在这张 x -\u003e gx 的计算图中，调用 gx.backward()，就可以计算 x 的二阶导数。 不过，通常计算图只在正向传播的过程中建立，在反向传播时不会被创建。如果我们在反向传播的过程中也建立计算图，就可以求高阶导数。 框架搭建\r变量类进行如下修改： class Variable: def backward(self, retain_grad=False, create_graph=False): if self.grad is None: # self.grad = np.ones_like(self.data) self.grad = Variable(np.ones_like(self.data)) funcs = [] seen_set = set() def add_func(f): if f not in seen_set: funcs.append(f) seen_set.add(f) funcs.sort(key=lambda x: x.generation) add_func(self.creator) while funcs: f = funcs.pop() gys = [output().grad for output in f.outputs] with using_config('enable_backprop', create_graph): gxs = f.backward(*gys) # 主要的backward处理 if not isinstance(gxs, tuple): gxs = (gxs,) for x, gx in zip(f.inputs, gxs): if x.grad is None: x.grad = gx else: x.grad = x.grad + gx # 这个计算也是对象 if x.creator is not None: add_func(x.creator) if not retain_grad: for y in f.outputs: y().grad = None 信息\r将梯度修改成 Variable 变量类，设置 enable_backprop 是否反向传播，可以控制梯度是否建立计算图。 运算符进行如下修改： class Mul(Function): def forward(self, x0, x1): y = x0 * x1 return y def backward(self, gy): x0, x1 = self.inputs return gy * x1, gy * x0 class Div(Function): def forward(self, x0, x1): y = x0 / x1 return y def backward(self, gy): x0, x1 = self.inputs gx0 = gy / x1 gx1 = gy * (-x0 / x1 ** 2) return gx0, gx1 class Pow(Function): def __init__(self, c): self.c = c def forward(self, x): y = x ** self.c return y def backward(self, gy): x, = self.inputs c = self.c gx = c * x ** (c - 1) * gy return gx 信息\rVariable 变量类的运算符已经重载过，修改反向传播函数即可适配 Variable 变量类。 示例\r","date":"2025-12-27","objectID":"/posts/%E6%B7%B1%E5%BA%A6%E5%AD%A6%E4%B9%A0%E5%9F%BA%E7%A1%80~%E8%87%AA%E5%88%B6%E6%A1%86%E6%9E%B6/:8:2","tags":null,"title":"深度学习基础~自制框架","uri":"/posts/%E6%B7%B1%E5%BA%A6%E5%AD%A6%E4%B9%A0%E5%9F%BA%E7%A1%80~%E8%87%AA%E5%88%B6%E6%A1%86%E6%9E%B6/#示例-14"},{"categories":[],"collections":["深度学习"],"content":"第 54 节 框架优化\r","date":"2025-12-27","objectID":"/posts/%E6%B7%B1%E5%BA%A6%E5%AD%A6%E4%B9%A0%E5%9F%BA%E7%A1%80~%E8%87%AA%E5%88%B6%E6%A1%86%E6%9E%B6/:9:0","tags":null,"title":"深度学习基础~自制框架","uri":"/posts/%E6%B7%B1%E5%BA%A6%E5%AD%A6%E4%B9%A0%E5%9F%BA%E7%A1%80~%E8%87%AA%E5%88%B6%E6%A1%86%E6%9E%B6/#框架优化-2"},{"categories":[],"collections":["深度学习"],"content":"54.1 高级函数\r在 dezero/functions.py 中创建高级函数： import numpy as np from dezero.core import Function class Sin(Function): def forward(self, x): y = np.sin(x) return y def backward(self, gy): x, = self.inputs gx = gy * cos(x) return gx def sin(x): return Sin()(x) class Cos(Function): def forward(self, x): y = np.cos(x) return y def backward(self, gy): x, = self.inputs gx = gy * -sin(x) return gx def cos(x): return Cos()(x) class Tanh(Function): def forward(self, x): y = np.tanh(x) return y def backward(self, gy): y = self.outputs[0]() gx = gy * (1 - y * y) return gx def tanh(x): return Tanh()(x) ","date":"2025-12-27","objectID":"/posts/%E6%B7%B1%E5%BA%A6%E5%AD%A6%E4%B9%A0%E5%9F%BA%E7%A1%80~%E8%87%AA%E5%88%B6%E6%A1%86%E6%9E%B6/:9:1","tags":null,"title":"深度学习基础~自制框架","uri":"/posts/%E6%B7%B1%E5%BA%A6%E5%AD%A6%E4%B9%A0%E5%9F%BA%E7%A1%80~%E8%87%AA%E5%88%B6%E6%A1%86%E6%9E%B6/#高级函数"},{"categories":[],"collections":["深度学习"],"content":"54.2 reshape\r框架搭建\r在 dezero/function.py 中进行如下修改： class Reshape(Function): def __init__(self, shape): self.shape = shape def forward(self, x): self.x_shape = x.shape y = x.reshape(self.shape) return y def backward(self, gy): return reshape(gy, self.x_shape) def reshape(x, shape): if x.shape == shape: return as_variable(x) return Reshape(shape)(x) 在 dezero/core.py 中进行如下修改： def reshape(self, *shape): if len(shape) == 1 and isinstance(shape[0], (tuple, list)): shape = shape[0] return dezero.functions.reshape(self, shape) ","date":"2025-12-27","objectID":"/posts/%E6%B7%B1%E5%BA%A6%E5%AD%A6%E4%B9%A0%E5%9F%BA%E7%A1%80~%E8%87%AA%E5%88%B6%E6%A1%86%E6%9E%B6/:9:2","tags":null,"title":"深度学习基础~自制框架","uri":"/posts/%E6%B7%B1%E5%BA%A6%E5%AD%A6%E4%B9%A0%E5%9F%BA%E7%A1%80~%E8%87%AA%E5%88%B6%E6%A1%86%E6%9E%B6/#reshape"},{"categories":[],"collections":["深度学习"],"content":"54.2 reshape\r框架搭建\r在 dezero/function.py 中进行如下修改： class Reshape(Function): def __init__(self, shape): self.shape = shape def forward(self, x): self.x_shape = x.shape y = x.reshape(self.shape) return y def backward(self, gy): return reshape(gy, self.x_shape) def reshape(x, shape): if x.shape == shape: return as_variable(x) return Reshape(shape)(x) 在 dezero/core.py 中进行如下修改： def reshape(self, *shape): if len(shape) == 1 and isinstance(shape[0], (tuple, list)): shape = shape[0] return dezero.functions.reshape(self, shape) ","date":"2025-12-27","objectID":"/posts/%E6%B7%B1%E5%BA%A6%E5%AD%A6%E4%B9%A0%E5%9F%BA%E7%A1%80~%E8%87%AA%E5%88%B6%E6%A1%86%E6%9E%B6/:9:2","tags":null,"title":"深度学习基础~自制框架","uri":"/posts/%E6%B7%B1%E5%BA%A6%E5%AD%A6%E4%B9%A0%E5%9F%BA%E7%A1%80~%E8%87%AA%E5%88%B6%E6%A1%86%E6%9E%B6/#框架搭建-17"},{"categories":[],"collections":["深度学习"],"content":"54.3 transpose\r框架搭建\r在 dezero/function.py 中进行如下修改： class Transpose(Function): def __init__(self, axes=None): self.axes = axes def forward(self, x): y = x.transpose(self.axes) return y def backward(self, gy): if self.axes is None: return transpose(gy) axes_len = len(self.axes) inv_axes = tuple(np.argsort([ax % axes_len for ax in self.axes])) return transpose(gy, inv_axes) def transpose(x, axes=None): return Transpose(axes)(x) 信息\r对 axes 进行 np.argsort 操作，正好可以得到 inv_axes，transpose 两次正好可以还原回去，% axes_len 是为了保证 ax 为正，因为 ax 可以传入 -1 之类的复数。 在 dezero/core.py 中进行如下修改： import dezero class Variable: def transpose(self, *axes): if len(axes) == 0: axes = None elif len(axes) == 1: if isinstance(axes[0], (tuple, list)) or axes[0] is None: axes = axes[0] return dezero.functions.transpose(self, axes) @property def T(self): return dezero.functions.transpose(self) ","date":"2025-12-27","objectID":"/posts/%E6%B7%B1%E5%BA%A6%E5%AD%A6%E4%B9%A0%E5%9F%BA%E7%A1%80~%E8%87%AA%E5%88%B6%E6%A1%86%E6%9E%B6/:9:3","tags":null,"title":"深度学习基础~自制框架","uri":"/posts/%E6%B7%B1%E5%BA%A6%E5%AD%A6%E4%B9%A0%E5%9F%BA%E7%A1%80~%E8%87%AA%E5%88%B6%E6%A1%86%E6%9E%B6/#transpose"},{"categories":[],"collections":["深度学习"],"content":"54.3 transpose\r框架搭建\r在 dezero/function.py 中进行如下修改： class Transpose(Function): def __init__(self, axes=None): self.axes = axes def forward(self, x): y = x.transpose(self.axes) return y def backward(self, gy): if self.axes is None: return transpose(gy) axes_len = len(self.axes) inv_axes = tuple(np.argsort([ax % axes_len for ax in self.axes])) return transpose(gy, inv_axes) def transpose(x, axes=None): return Transpose(axes)(x) 信息\r对 axes 进行 np.argsort 操作，正好可以得到 inv_axes，transpose 两次正好可以还原回去，% axes_len 是为了保证 ax 为正，因为 ax 可以传入 -1 之类的复数。 在 dezero/core.py 中进行如下修改： import dezero class Variable: def transpose(self, *axes): if len(axes) == 0: axes = None elif len(axes) == 1: if isinstance(axes[0], (tuple, list)) or axes[0] is None: axes = axes[0] return dezero.functions.transpose(self, axes) @property def T(self): return dezero.functions.transpose(self) ","date":"2025-12-27","objectID":"/posts/%E6%B7%B1%E5%BA%A6%E5%AD%A6%E4%B9%A0%E5%9F%BA%E7%A1%80~%E8%87%AA%E5%88%B6%E6%A1%86%E6%9E%B6/:9:3","tags":null,"title":"深度学习基础~自制框架","uri":"/posts/%E6%B7%B1%E5%BA%A6%E5%AD%A6%E4%B9%A0%E5%9F%BA%E7%A1%80~%E8%87%AA%E5%88%B6%E6%A1%86%E6%9E%B6/#框架搭建-18"},{"categories":[],"collections":["深度学习"],"content":"54.4 sum \u0026 boardcast\r在求和 sum 函数中， axis 用于指定求和时的轴，如下： keepdims 用于指定输入和输出是否应具有相同维度（轴的数量）。 广播 boardcast 函数，复制输入的元素，并将其形状变为 shape 的形状： 信息\r广播从最后一维开始对齐，两个维度必须相等或者广播前的维度为 1。 广播 boardcast 的反向传播，需要实现名为 sum_to 的函数，其求和输入的元素，将其形状变为 shape 的形状： 求和 sum_to 的反向传播，就是 boardcast_to： 同样，求和 sum 的反向传播，也是 boardcast_to。 框架搭建\r在 dezero/function.py 中进行如下修改： from dezero import utils class Sum(Function): def __init__(self, axis, keepdims): self.axis = axis self.keepdims = keepdims def forward(self, x): self.x_shape = x.shape y = x.sum(axis=self.axis, keepdims=self.keepdims) return y def backward(self, gy): gy = utils.reshape_sum_backward(gy, self.x_shape, self.axis, self.keepdims) gx = broadcast_to(gy, self.x_shape) return gx def sum(x, axis=None, keepdims=False): return Sum(axis, keepdims)(x) class SumTo(Function): def __init__(self, shape): self.shape = shape def forward(self, x): self.x_shape = x.shape y = utils.sum_to(x, self.shape) return y def backward(self, gy): gx = broadcast_to(gy, self.x_shape) return gx def sum_to(x, shape): if x.shape == shape: return as_variable(x) return SumTo(shape)(x) class BroadcastTo(Function): def __init__(self, shape): self.shape = shape def forward(self, x): self.x_shape = x.shape y = np.broadcast_to(x, self.shape) return y def backward(self, gy): gx = sum_to(gy, self.x_shape) return gx def broadcast_to(x, shape): if x.shape == shape: return as_variable(x) return BroadcastTo(shape)(x) 需要在 dezero/utils.py 中实现 reshape_sum_backward 方法和 sum_to 方法： def sum_to(x, shape): ndim = len(shape) lead = x.ndim - ndim # 前导维度：broadcast过程中自动补的维度 lead_axis = tuple(range(lead)) # 前导维度所在轴 # 维度为 1 的轴 axis = tuple([i + lead for i, sx in enumerate(shape) if sx == 1]) # 对指定的轴（lead_axis 和 axis）进行求和 y = x.sum(lead_axis + axis, keepdims=True) if lead \u003e 0: y = y.squeeze(lead_axis) # 去掉前导维度 return y def reshape_sum_backward(gy, x_shape, axis, keepdims): ndim = len(x_shape) tupled_axis = axis if axis is None: tupled_axis = None elif not isinstance(axis, tuple): tupled_axis = (axis,) if not (ndim == 0 or tupled_axis is None or keepdims): # sum 过程中消失的轴 actual_axis = [a % ndim for a in tupled_axis] shape = list(gy.shape) for a in sorted(actual_axis): shape.insert(a, 1) # 消失的轴填充 1,用来广播 else: shape = gy.shape 在变量类 Variable 中添加 sum 方法： class Variable: def sum(self, axis=None, keepdims=False): return dezero.functions.sum(self, axis, keepdims) ","date":"2025-12-27","objectID":"/posts/%E6%B7%B1%E5%BA%A6%E5%AD%A6%E4%B9%A0%E5%9F%BA%E7%A1%80~%E8%87%AA%E5%88%B6%E6%A1%86%E6%9E%B6/:9:4","tags":null,"title":"深度学习基础~自制框架","uri":"/posts/%E6%B7%B1%E5%BA%A6%E5%AD%A6%E4%B9%A0%E5%9F%BA%E7%A1%80~%E8%87%AA%E5%88%B6%E6%A1%86%E6%9E%B6/#sum--boardcast"},{"categories":[],"collections":["深度学习"],"content":"54.4 sum \u0026 boardcast\r在求和 sum 函数中， axis 用于指定求和时的轴，如下： keepdims 用于指定输入和输出是否应具有相同维度（轴的数量）。 广播 boardcast 函数，复制输入的元素，并将其形状变为 shape 的形状： 信息\r广播从最后一维开始对齐，两个维度必须相等或者广播前的维度为 1。 广播 boardcast 的反向传播，需要实现名为 sum_to 的函数，其求和输入的元素，将其形状变为 shape 的形状： 求和 sum_to 的反向传播，就是 boardcast_to： 同样，求和 sum 的反向传播，也是 boardcast_to。 框架搭建\r在 dezero/function.py 中进行如下修改： from dezero import utils class Sum(Function): def __init__(self, axis, keepdims): self.axis = axis self.keepdims = keepdims def forward(self, x): self.x_shape = x.shape y = x.sum(axis=self.axis, keepdims=self.keepdims) return y def backward(self, gy): gy = utils.reshape_sum_backward(gy, self.x_shape, self.axis, self.keepdims) gx = broadcast_to(gy, self.x_shape) return gx def sum(x, axis=None, keepdims=False): return Sum(axis, keepdims)(x) class SumTo(Function): def __init__(self, shape): self.shape = shape def forward(self, x): self.x_shape = x.shape y = utils.sum_to(x, self.shape) return y def backward(self, gy): gx = broadcast_to(gy, self.x_shape) return gx def sum_to(x, shape): if x.shape == shape: return as_variable(x) return SumTo(shape)(x) class BroadcastTo(Function): def __init__(self, shape): self.shape = shape def forward(self, x): self.x_shape = x.shape y = np.broadcast_to(x, self.shape) return y def backward(self, gy): gx = sum_to(gy, self.x_shape) return gx def broadcast_to(x, shape): if x.shape == shape: return as_variable(x) return BroadcastTo(shape)(x) 需要在 dezero/utils.py 中实现 reshape_sum_backward 方法和 sum_to 方法： def sum_to(x, shape): ndim = len(shape) lead = x.ndim - ndim # 前导维度：broadcast过程中自动补的维度 lead_axis = tuple(range(lead)) # 前导维度所在轴 # 维度为 1 的轴 axis = tuple([i + lead for i, sx in enumerate(shape) if sx == 1]) # 对指定的轴（lead_axis 和 axis）进行求和 y = x.sum(lead_axis + axis, keepdims=True) if lead \u003e 0: y = y.squeeze(lead_axis) # 去掉前导维度 return y def reshape_sum_backward(gy, x_shape, axis, keepdims): ndim = len(x_shape) tupled_axis = axis if axis is None: tupled_axis = None elif not isinstance(axis, tuple): tupled_axis = (axis,) if not (ndim == 0 or tupled_axis is None or keepdims): # sum 过程中消失的轴 actual_axis = [a % ndim for a in tupled_axis] shape = list(gy.shape) for a in sorted(actual_axis): shape.insert(a, 1) # 消失的轴填充 1,用来广播 else: shape = gy.shape 在变量类 Variable 中添加 sum 方法： class Variable: def sum(self, axis=None, keepdims=False): return dezero.functions.sum(self, axis, keepdims) ","date":"2025-12-27","objectID":"/posts/%E6%B7%B1%E5%BA%A6%E5%AD%A6%E4%B9%A0%E5%9F%BA%E7%A1%80~%E8%87%AA%E5%88%B6%E6%A1%86%E6%9E%B6/:9:4","tags":null,"title":"深度学习基础~自制框架","uri":"/posts/%E6%B7%B1%E5%BA%A6%E5%AD%A6%E4%B9%A0%E5%9F%BA%E7%A1%80~%E8%87%AA%E5%88%B6%E6%A1%86%E6%9E%B6/#框架搭建-19"},{"categories":[],"collections":["深度学习"],"content":"第 55 节 张量\r","date":"2025-12-27","objectID":"/posts/%E6%B7%B1%E5%BA%A6%E5%AD%A6%E4%B9%A0%E5%9F%BA%E7%A1%80~%E8%87%AA%E5%88%B6%E6%A1%86%E6%9E%B6/:10:0","tags":null,"title":"深度学习基础~自制框架","uri":"/posts/%E6%B7%B1%E5%BA%A6%E5%AD%A6%E4%B9%A0%E5%9F%BA%E7%A1%80~%E8%87%AA%E5%88%B6%E6%A1%86%E6%9E%B6/#张量"},{"categories":[],"collections":["深度学习"],"content":"55.1 链式法则\r对于函数 $\\boldsymbol{y}=F(\\boldsymbol{x})$ ，其中 $\\boldsymbol{x}$ 和 $\\boldsymbol{y}$ 是向量，假设这两个向量的元素数都是 $n$ 。 $\\boldsymbol{y}$ 对 $\\boldsymbol{x}$ 的导数可通过以下式子定义： $$ \\begin{aligned} \\frac{\\partial y}{\\partial x} \u0026= \\left( \\begin{array}{cccc} \\frac{\\partial y_1}{\\partial x_1} \u0026 \\frac{\\partial y_1}{\\partial x_2} \u0026 \\cdots \u0026 \\frac{\\partial y_1}{\\partial x_n} \\ \\frac{\\partial y_2}{\\partial x_1} \u0026 \\frac{\\partial y_2}{\\partial x_2} \u0026 \\cdots \u0026 \\frac{\\partial y_2}{\\partial x_n} \\ \\vdots \u0026 \\vdots \u0026 \\ddots \u0026 \\vdots \\ \\frac{\\partial y_n}{\\partial x_1} \u0026 \\frac{\\partial y_n}{\\partial x_2} \u0026 \\cdots \u0026 \\frac{\\partial y_n}{\\partial x_n} \\end{array} \\right) \\end{aligned} $$ 这个矩阵称为雅可比矩阵。 如果 $\\boldsymbol{y}$ 不是向量而是标量，那么 $\\boldsymbol{y}$ 对 $\\boldsymbol{x}$ 的导数就是下面这样。 $$ \\frac{\\partial y}{\\partial x}=\\left(\\begin{array}{llll} \\frac{\\partial y}{\\partial x_1} \u0026 \\frac{\\partial y}{\\partial x_2} \u0026 \\cdots \u0026 \\frac{\\partial y}{\\partial x_n} \\end{array}\\right) $$ 这是一个 $1 \\times n$ 的雅可比矩阵，可以将它看作一个行向量。 接下来思考复合函数。假设有复合函数 $\\boldsymbol{y}=F(\\boldsymbol{x})$ ，它由 3 个函数复合而成，分别是 $\\boldsymbol{a}=A(\\boldsymbol{x}), \\boldsymbol{b}=B(\\boldsymbol{a}), y=C(\\boldsymbol{b})$ 。假设变量 $\\boldsymbol{x}$ 、$\\boldsymbol{a}$ 、 $\\boldsymbol{b}$ 都是向量，它们的元素数为 $n$ ，只有最终的输出 $y$ 是标量。那么，基于链式法则，$y$ 对 $x$ 的导数可以表示如下。 $$ \\frac{\\partial y}{\\partial x}=\\frac{\\partial y}{\\partial b} \\frac{\\partial b}{\\partial a} \\frac{\\partial a}{\\partial x} $$ ","date":"2025-12-27","objectID":"/posts/%E6%B7%B1%E5%BA%A6%E5%AD%A6%E4%B9%A0%E5%9F%BA%E7%A1%80~%E8%87%AA%E5%88%B6%E6%A1%86%E6%9E%B6/:10:1","tags":null,"title":"深度学习基础~自制框架","uri":"/posts/%E6%B7%B1%E5%BA%A6%E5%AD%A6%E4%B9%A0%E5%9F%BA%E7%A1%80~%E8%87%AA%E5%88%B6%E6%A1%86%E6%9E%B6/#链式法则-1"},{"categories":[],"collections":["深度学习"],"content":"55.2 矩阵乘积\r假设有向量 $\\boldsymbol{a}=\\left(a_1, \\cdots, a_n\\right)$ 和向量 $\\boldsymbol{b}=\\left(b_1, \\cdots, b_n\\right)$ 。向量的内积可以定义为： $$ \\boldsymbol{a} \\boldsymbol{b}=a_1 b_1+a_2 b_2+\\cdots+a_n b_n $$ 矩阵乘积的计算方法是先分别求出左侧矩阵水平方向的向量和右侧矩阵垂直方向的向量的内积，然后将结果存储在新矩阵的相应元素中。 下面以 $\\boldsymbol{y}=\\boldsymbol{x} \\boldsymbol{W}$ 为例介绍矩阵乘积的反向传播。在该计算中， $\\boldsymbol{x}$ 、 $\\boldsymbol{W}$ 和 $\\boldsymbol{y}$ 的形状分别为 $1 \\times D$ 、 $D \\times H$ 和 $1 \\times H$ ： 假定计算最终输出的标量是 $L$（通过反向传播求 $L$ 对每个变量的导数），此时，$L$ 对 $\\boldsymbol{x}$ 的第 $i$ 个元素的导数 $\\frac{\\partial L}{\\partial x_i}$ 的式子如下所示： $$ \\frac{\\partial L}{\\partial x_i}=\\sum_j \\frac{\\partial L}{\\partial y_j} \\frac{\\partial y_j}{\\partial x_i} $$ $\\frac{\\partial L}{\\partial x_i}$ 表示当 $x_i$ 发生微小的变化时 $L$ 的变化程度。当 $x_i$ 发生变化时，向量 $\\boldsymbol{y}$ 的所有元素也会发生改变， $\\boldsymbol{y}$ 的每个元素的改变也会使 $L$ 最终发生变化。因此，从 $x_i$ 到 $L$ 有多条链式法则的路径，其总和为 $\\frac{\\partial L}{\\partial x_i}$ 。 展开 $\\boldsymbol{y}$ 的第 $j$ 个元素，有 $$ y_j=x_1 W_{1 j}+x_2 W_{2 j}+\\cdots+x_i W_{i j}+\\cdots+x_H W_{H j} $$ 由此可知，$\\frac{\\partial y_j}{\\partial x_i}=W_{i j}$ ，于是： $$ \\frac{\\partial L}{\\partial x_i}=\\sum_j \\frac{\\partial L}{\\partial y_j} \\frac{\\partial y_j}{\\partial x_i}=\\sum_j \\frac{\\partial L}{\\partial y_j} W_{i j} $$ 也就是说， $\\frac{\\partial L}{\\partial x_i}$ 可通过向量 $\\frac{\\partial L}{\\partial \\boldsymbol{y}}$ 和 $\\boldsymbol{W}$ 的第 $i$ 行向量的内积求出，由此我们可以推导出以下式子： $$ \\frac{\\partial L}{\\partial \\boldsymbol{x}}=\\frac{\\partial L}{\\partial \\boldsymbol{y}} \\boldsymbol{W}^{\\mathrm{T}} $$ 再次思考 $\\boldsymbol{y}=\\boldsymbol{x} \\boldsymbol{W}$ 这个矩阵乘积的计算，这次 $\\boldsymbol{x}$ 、 $\\boldsymbol{W}$ 和 $\\boldsymbol{y}$ 的形状分别为 $N \\times D$ 、 $D \\times H$ 和 $N \\times H$ ，此时反向传播的计算图如下： 通过矩阵的形状，我们可以推导出如下的式子： 同样，上面的式子也可以通过计算每个矩阵的元素并比较两边的结果推导出来，这里不再进行推导。 框架搭建\r在 dezero/functions.py 中实现矩阵乘积如下： class MatMul(Function): def forward(self, x, W): y = x.dot(W) return y def backward(self, gy): x, W = self.inputs gx = matmul(gy, W.T) gW = matmul(x.T, gy) return gx, gW def matmul(x, W): return MatMul()(x, W) 示例\r","date":"2025-12-27","objectID":"/posts/%E6%B7%B1%E5%BA%A6%E5%AD%A6%E4%B9%A0%E5%9F%BA%E7%A1%80~%E8%87%AA%E5%88%B6%E6%A1%86%E6%9E%B6/:10:2","tags":null,"title":"深度学习基础~自制框架","uri":"/posts/%E6%B7%B1%E5%BA%A6%E5%AD%A6%E4%B9%A0%E5%9F%BA%E7%A1%80~%E8%87%AA%E5%88%B6%E6%A1%86%E6%9E%B6/#矩阵乘积"},{"categories":[],"collections":["深度学习"],"content":"55.2 矩阵乘积\r假设有向量 $\\boldsymbol{a}=\\left(a_1, \\cdots, a_n\\right)$ 和向量 $\\boldsymbol{b}=\\left(b_1, \\cdots, b_n\\right)$ 。向量的内积可以定义为： $$ \\boldsymbol{a} \\boldsymbol{b}=a_1 b_1+a_2 b_2+\\cdots+a_n b_n $$ 矩阵乘积的计算方法是先分别求出左侧矩阵水平方向的向量和右侧矩阵垂直方向的向量的内积，然后将结果存储在新矩阵的相应元素中。 下面以 $\\boldsymbol{y}=\\boldsymbol{x} \\boldsymbol{W}$ 为例介绍矩阵乘积的反向传播。在该计算中， $\\boldsymbol{x}$ 、 $\\boldsymbol{W}$ 和 $\\boldsymbol{y}$ 的形状分别为 $1 \\times D$ 、 $D \\times H$ 和 $1 \\times H$ ： 假定计算最终输出的标量是 $L$（通过反向传播求 $L$ 对每个变量的导数），此时，$L$ 对 $\\boldsymbol{x}$ 的第 $i$ 个元素的导数 $\\frac{\\partial L}{\\partial x_i}$ 的式子如下所示： $$ \\frac{\\partial L}{\\partial x_i}=\\sum_j \\frac{\\partial L}{\\partial y_j} \\frac{\\partial y_j}{\\partial x_i} $$ $\\frac{\\partial L}{\\partial x_i}$ 表示当 $x_i$ 发生微小的变化时 $L$ 的变化程度。当 $x_i$ 发生变化时，向量 $\\boldsymbol{y}$ 的所有元素也会发生改变， $\\boldsymbol{y}$ 的每个元素的改变也会使 $L$ 最终发生变化。因此，从 $x_i$ 到 $L$ 有多条链式法则的路径，其总和为 $\\frac{\\partial L}{\\partial x_i}$ 。 展开 $\\boldsymbol{y}$ 的第 $j$ 个元素，有 $$ y_j=x_1 W_{1 j}+x_2 W_{2 j}+\\cdots+x_i W_{i j}+\\cdots+x_H W_{H j} $$ 由此可知，$\\frac{\\partial y_j}{\\partial x_i}=W_{i j}$ ，于是： $$ \\frac{\\partial L}{\\partial x_i}=\\sum_j \\frac{\\partial L}{\\partial y_j} \\frac{\\partial y_j}{\\partial x_i}=\\sum_j \\frac{\\partial L}{\\partial y_j} W_{i j} $$ 也就是说， $\\frac{\\partial L}{\\partial x_i}$ 可通过向量 $\\frac{\\partial L}{\\partial \\boldsymbol{y}}$ 和 $\\boldsymbol{W}$ 的第 $i$ 行向量的内积求出，由此我们可以推导出以下式子： $$ \\frac{\\partial L}{\\partial \\boldsymbol{x}}=\\frac{\\partial L}{\\partial \\boldsymbol{y}} \\boldsymbol{W}^{\\mathrm{T}} $$ 再次思考 $\\boldsymbol{y}=\\boldsymbol{x} \\boldsymbol{W}$ 这个矩阵乘积的计算，这次 $\\boldsymbol{x}$ 、 $\\boldsymbol{W}$ 和 $\\boldsymbol{y}$ 的形状分别为 $N \\times D$ 、 $D \\times H$ 和 $N \\times H$ ，此时反向传播的计算图如下： 通过矩阵的形状，我们可以推导出如下的式子： 同样，上面的式子也可以通过计算每个矩阵的元素并比较两边的结果推导出来，这里不再进行推导。 框架搭建\r在 dezero/functions.py 中实现矩阵乘积如下： class MatMul(Function): def forward(self, x, W): y = x.dot(W) return y def backward(self, gy): x, W = self.inputs gx = matmul(gy, W.T) gW = matmul(x.T, gy) return gx, gW def matmul(x, W): return MatMul()(x, W) 示例\r","date":"2025-12-27","objectID":"/posts/%E6%B7%B1%E5%BA%A6%E5%AD%A6%E4%B9%A0%E5%9F%BA%E7%A1%80~%E8%87%AA%E5%88%B6%E6%A1%86%E6%9E%B6/:10:2","tags":null,"title":"深度学习基础~自制框架","uri":"/posts/%E6%B7%B1%E5%BA%A6%E5%AD%A6%E4%B9%A0%E5%9F%BA%E7%A1%80~%E8%87%AA%E5%88%B6%E6%A1%86%E6%9E%B6/#框架搭建-20"},{"categories":[],"collections":["深度学习"],"content":"55.2 矩阵乘积\r假设有向量 $\\boldsymbol{a}=\\left(a_1, \\cdots, a_n\\right)$ 和向量 $\\boldsymbol{b}=\\left(b_1, \\cdots, b_n\\right)$ 。向量的内积可以定义为： $$ \\boldsymbol{a} \\boldsymbol{b}=a_1 b_1+a_2 b_2+\\cdots+a_n b_n $$ 矩阵乘积的计算方法是先分别求出左侧矩阵水平方向的向量和右侧矩阵垂直方向的向量的内积，然后将结果存储在新矩阵的相应元素中。 下面以 $\\boldsymbol{y}=\\boldsymbol{x} \\boldsymbol{W}$ 为例介绍矩阵乘积的反向传播。在该计算中， $\\boldsymbol{x}$ 、 $\\boldsymbol{W}$ 和 $\\boldsymbol{y}$ 的形状分别为 $1 \\times D$ 、 $D \\times H$ 和 $1 \\times H$ ： 假定计算最终输出的标量是 $L$（通过反向传播求 $L$ 对每个变量的导数），此时，$L$ 对 $\\boldsymbol{x}$ 的第 $i$ 个元素的导数 $\\frac{\\partial L}{\\partial x_i}$ 的式子如下所示： $$ \\frac{\\partial L}{\\partial x_i}=\\sum_j \\frac{\\partial L}{\\partial y_j} \\frac{\\partial y_j}{\\partial x_i} $$ $\\frac{\\partial L}{\\partial x_i}$ 表示当 $x_i$ 发生微小的变化时 $L$ 的变化程度。当 $x_i$ 发生变化时，向量 $\\boldsymbol{y}$ 的所有元素也会发生改变， $\\boldsymbol{y}$ 的每个元素的改变也会使 $L$ 最终发生变化。因此，从 $x_i$ 到 $L$ 有多条链式法则的路径，其总和为 $\\frac{\\partial L}{\\partial x_i}$ 。 展开 $\\boldsymbol{y}$ 的第 $j$ 个元素，有 $$ y_j=x_1 W_{1 j}+x_2 W_{2 j}+\\cdots+x_i W_{i j}+\\cdots+x_H W_{H j} $$ 由此可知，$\\frac{\\partial y_j}{\\partial x_i}=W_{i j}$ ，于是： $$ \\frac{\\partial L}{\\partial x_i}=\\sum_j \\frac{\\partial L}{\\partial y_j} \\frac{\\partial y_j}{\\partial x_i}=\\sum_j \\frac{\\partial L}{\\partial y_j} W_{i j} $$ 也就是说， $\\frac{\\partial L}{\\partial x_i}$ 可通过向量 $\\frac{\\partial L}{\\partial \\boldsymbol{y}}$ 和 $\\boldsymbol{W}$ 的第 $i$ 行向量的内积求出，由此我们可以推导出以下式子： $$ \\frac{\\partial L}{\\partial \\boldsymbol{x}}=\\frac{\\partial L}{\\partial \\boldsymbol{y}} \\boldsymbol{W}^{\\mathrm{T}} $$ 再次思考 $\\boldsymbol{y}=\\boldsymbol{x} \\boldsymbol{W}$ 这个矩阵乘积的计算，这次 $\\boldsymbol{x}$ 、 $\\boldsymbol{W}$ 和 $\\boldsymbol{y}$ 的形状分别为 $N \\times D$ 、 $D \\times H$ 和 $N \\times H$ ，此时反向传播的计算图如下： 通过矩阵的形状，我们可以推导出如下的式子： 同样，上面的式子也可以通过计算每个矩阵的元素并比较两边的结果推导出来，这里不再进行推导。 框架搭建\r在 dezero/functions.py 中实现矩阵乘积如下： class MatMul(Function): def forward(self, x, W): y = x.dot(W) return y def backward(self, gy): x, W = self.inputs gx = matmul(gy, W.T) gW = matmul(x.T, gy) return gx, gW def matmul(x, W): return MatMul()(x, W) 示例\r","date":"2025-12-27","objectID":"/posts/%E6%B7%B1%E5%BA%A6%E5%AD%A6%E4%B9%A0%E5%9F%BA%E7%A1%80~%E8%87%AA%E5%88%B6%E6%A1%86%E6%9E%B6/:10:2","tags":null,"title":"深度学习基础~自制框架","uri":"/posts/%E6%B7%B1%E5%BA%A6%E5%AD%A6%E4%B9%A0%E5%9F%BA%E7%A1%80~%E8%87%AA%E5%88%B6%E6%A1%86%E6%9E%B6/#示例-15"},{"categories":[],"collections":["深度学习"],"content":"55.3 线性回归\r构建简单的数据集，进行线性回归。 框架搭建\r在 dezero/funtion.py 中实现均方误差如下： class MeanSquaredError(Function): def forward(self, x0, x1): diff = x0 - x1 y = (diff ** 2).sum() / len(diff) return y def backward(self, gy): x0, x1 = self.inputs diff = x0 - x1 gx0 = gy * diff * (2. / len(diff)) gx1 = -gx0 return gx0, gx1 def mean_squared_error(x0, x1): return MeanSquaredError()(x0, x1) 示例\r","date":"2025-12-27","objectID":"/posts/%E6%B7%B1%E5%BA%A6%E5%AD%A6%E4%B9%A0%E5%9F%BA%E7%A1%80~%E8%87%AA%E5%88%B6%E6%A1%86%E6%9E%B6/:10:3","tags":null,"title":"深度学习基础~自制框架","uri":"/posts/%E6%B7%B1%E5%BA%A6%E5%AD%A6%E4%B9%A0%E5%9F%BA%E7%A1%80~%E8%87%AA%E5%88%B6%E6%A1%86%E6%9E%B6/#线性回归"},{"categories":[],"collections":["深度学习"],"content":"55.3 线性回归\r构建简单的数据集，进行线性回归。 框架搭建\r在 dezero/funtion.py 中实现均方误差如下： class MeanSquaredError(Function): def forward(self, x0, x1): diff = x0 - x1 y = (diff ** 2).sum() / len(diff) return y def backward(self, gy): x0, x1 = self.inputs diff = x0 - x1 gx0 = gy * diff * (2. / len(diff)) gx1 = -gx0 return gx0, gx1 def mean_squared_error(x0, x1): return MeanSquaredError()(x0, x1) 示例\r","date":"2025-12-27","objectID":"/posts/%E6%B7%B1%E5%BA%A6%E5%AD%A6%E4%B9%A0%E5%9F%BA%E7%A1%80~%E8%87%AA%E5%88%B6%E6%A1%86%E6%9E%B6/:10:3","tags":null,"title":"深度学习基础~自制框架","uri":"/posts/%E6%B7%B1%E5%BA%A6%E5%AD%A6%E4%B9%A0%E5%9F%BA%E7%A1%80~%E8%87%AA%E5%88%B6%E6%A1%86%E6%9E%B6/#框架搭建-21"},{"categories":[],"collections":["深度学习"],"content":"55.3 线性回归\r构建简单的数据集，进行线性回归。 框架搭建\r在 dezero/funtion.py 中实现均方误差如下： class MeanSquaredError(Function): def forward(self, x0, x1): diff = x0 - x1 y = (diff ** 2).sum() / len(diff) return y def backward(self, gy): x0, x1 = self.inputs diff = x0 - x1 gx0 = gy * diff * (2. / len(diff)) gx1 = -gx0 return gx0, gx1 def mean_squared_error(x0, x1): return MeanSquaredError()(x0, x1) 示例\r","date":"2025-12-27","objectID":"/posts/%E6%B7%B1%E5%BA%A6%E5%AD%A6%E4%B9%A0%E5%9F%BA%E7%A1%80~%E8%87%AA%E5%88%B6%E6%A1%86%E6%9E%B6/:10:3","tags":null,"title":"深度学习基础~自制框架","uri":"/posts/%E6%B7%B1%E5%BA%A6%E5%AD%A6%E4%B9%A0%E5%9F%BA%E7%A1%80~%E8%87%AA%E5%88%B6%E6%A1%86%E6%9E%B6/#示例-16"},{"categories":[],"collections":["深度学习"],"content":"第 56 节 模型\r","date":"2025-12-27","objectID":"/posts/%E6%B7%B1%E5%BA%A6%E5%AD%A6%E4%B9%A0%E5%9F%BA%E7%A1%80~%E8%87%AA%E5%88%B6%E6%A1%86%E6%9E%B6/:11:0","tags":null,"title":"深度学习基础~自制框架","uri":"/posts/%E6%B7%B1%E5%BA%A6%E5%AD%A6%E4%B9%A0%E5%9F%BA%E7%A1%80~%E8%87%AA%E5%88%B6%E6%A1%86%E6%9E%B6/#模型"},{"categories":[],"collections":["深度学习"],"content":"56.1 线性变换 Linear\r输入 $\\boldsymbol{x}$ 和参数 $\\boldsymbol{W}$ 之间的矩阵乘积，然后加上 $\\boldsymbol{b}$ 的结果，称为叫作线性变换（linear transformation）或仿射变换（affine transformation ）。 左图的实现方式使用了 DeZero 的 matmul 函数和 add 函数，matmul 函数的输出作为 Variable 实例记录在计算图中。 右图的实现方式是继承 Function 类后实现 Linear 类。在使用这种方式下，中间结果没有作为 Variable 实例存储在内存中，所以正向传播中使用的数据在正向传播完成后会立即被删除。因此从内存效率的角度考虑，需要使用第二种实现方式。 框架搭建\r在 dezero/function.py 中实现线性变换： class Linear(Function): def forward(self, x, W, b): y = x.dot(W) if b is not None: y += b return y def backward(self, gy): x, W, b = self.inputs gb = None if b.data is None else sum_to(gy, b.shape) gx = matmul(gy, W.T) gW = matmul(x.T, gy) return gx, gW, gb def linear(x, W, b=None): return Linear()(x, W, b) ","date":"2025-12-27","objectID":"/posts/%E6%B7%B1%E5%BA%A6%E5%AD%A6%E4%B9%A0%E5%9F%BA%E7%A1%80~%E8%87%AA%E5%88%B6%E6%A1%86%E6%9E%B6/:11:1","tags":null,"title":"深度学习基础~自制框架","uri":"/posts/%E6%B7%B1%E5%BA%A6%E5%AD%A6%E4%B9%A0%E5%9F%BA%E7%A1%80~%E8%87%AA%E5%88%B6%E6%A1%86%E6%9E%B6/#线性变换-linear"},{"categories":[],"collections":["深度学习"],"content":"56.1 线性变换 Linear\r输入 $\\boldsymbol{x}$ 和参数 $\\boldsymbol{W}$ 之间的矩阵乘积，然后加上 $\\boldsymbol{b}$ 的结果，称为叫作线性变换（linear transformation）或仿射变换（affine transformation ）。 左图的实现方式使用了 DeZero 的 matmul 函数和 add 函数，matmul 函数的输出作为 Variable 实例记录在计算图中。 右图的实现方式是继承 Function 类后实现 Linear 类。在使用这种方式下，中间结果没有作为 Variable 实例存储在内存中，所以正向传播中使用的数据在正向传播完成后会立即被删除。因此从内存效率的角度考虑，需要使用第二种实现方式。 框架搭建\r在 dezero/function.py 中实现线性变换： class Linear(Function): def forward(self, x, W, b): y = x.dot(W) if b is not None: y += b return y def backward(self, gy): x, W, b = self.inputs gb = None if b.data is None else sum_to(gy, b.shape) gx = matmul(gy, W.T) gW = matmul(x.T, gy) return gx, gW, gb def linear(x, W, b=None): return Linear()(x, W, b) ","date":"2025-12-27","objectID":"/posts/%E6%B7%B1%E5%BA%A6%E5%AD%A6%E4%B9%A0%E5%9F%BA%E7%A1%80~%E8%87%AA%E5%88%B6%E6%A1%86%E6%9E%B6/:11:1","tags":null,"title":"深度学习基础~自制框架","uri":"/posts/%E6%B7%B1%E5%BA%A6%E5%AD%A6%E4%B9%A0%E5%9F%BA%E7%A1%80~%E8%87%AA%E5%88%B6%E6%A1%86%E6%9E%B6/#框架搭建-22"},{"categories":[],"collections":["深度学习"],"content":"56.2 激活函数\r线性变换指对输入数据进行线性的变换，而神经网络则对线性变换的输出进行非线性的变换。这种非线性变换叫作激活函数，典型的激活函数有 ReLU 和 sigmoid 函数。 框架搭建\r在 dezero/function.py 中实现激活函数： class Sigmoid(Function): def forward(self, x): y = np.tanh(x * 0.5) * 0.5 + 0.5 return y def backward(self, gy): y = self.outputs[0]() gx = gy * y * (1 - y) return gx def sigmoid(x): return Sigmoid()(x) class ReLU(Function): def forward(self, x): y = np.maximum(x, 0.0) return y def backward(self, gy): x, = self.inputs mask = x.data \u003e 0 gx = gy * mask return gx def relu(x): return ReLU()(x) ","date":"2025-12-27","objectID":"/posts/%E6%B7%B1%E5%BA%A6%E5%AD%A6%E4%B9%A0%E5%9F%BA%E7%A1%80~%E8%87%AA%E5%88%B6%E6%A1%86%E6%9E%B6/:11:2","tags":null,"title":"深度学习基础~自制框架","uri":"/posts/%E6%B7%B1%E5%BA%A6%E5%AD%A6%E4%B9%A0%E5%9F%BA%E7%A1%80~%E8%87%AA%E5%88%B6%E6%A1%86%E6%9E%B6/#激活函数"},{"categories":[],"collections":["深度学习"],"content":"56.2 激活函数\r线性变换指对输入数据进行线性的变换，而神经网络则对线性变换的输出进行非线性的变换。这种非线性变换叫作激活函数，典型的激活函数有 ReLU 和 sigmoid 函数。 框架搭建\r在 dezero/function.py 中实现激活函数： class Sigmoid(Function): def forward(self, x): y = np.tanh(x * 0.5) * 0.5 + 0.5 return y def backward(self, gy): y = self.outputs[0]() gx = gy * y * (1 - y) return gx def sigmoid(x): return Sigmoid()(x) class ReLU(Function): def forward(self, x): y = np.maximum(x, 0.0) return y def backward(self, gy): x, = self.inputs mask = x.data \u003e 0 gx = gy * mask return gx def relu(x): return ReLU()(x) ","date":"2025-12-27","objectID":"/posts/%E6%B7%B1%E5%BA%A6%E5%AD%A6%E4%B9%A0%E5%9F%BA%E7%A1%80~%E8%87%AA%E5%88%B6%E6%A1%86%E6%9E%B6/:11:2","tags":null,"title":"深度学习基础~自制框架","uri":"/posts/%E6%B7%B1%E5%BA%A6%E5%AD%A6%E4%B9%A0%E5%9F%BA%E7%A1%80~%E8%87%AA%E5%88%B6%E6%A1%86%E6%9E%B6/#框架搭建-23"},{"categories":[],"collections":["深度学习"],"content":"56.3 层 Layer\r在实现结构更加复杂的网络时，参数处理将更加复杂。通过构建 Layer，可以实现参数的自动处理。 框架搭建\r在 dezero/core.py 中新建 Parameter 类如下： class Parameter(Variable): pass 在 dezero/layers.py 中新建 Layer 类如下： import weakref from dezero.core import Parameter class Layer: def __init__(self): self._params = set() def __setattr__(self, name, value): if isinstance(value, Parameter): self._params.add(name) super().__setattr__(name, value) def __call__(self, *inputs): outputs = self.forward(*inputs) if not isinstance(outputs, tuple): outputs = (outputs,) self.inputs = [weakref.ref(x) for x in inputs] self.outputs = [weakref.ref(y) for y in outputs] return outputs if len(outputs) \u003e 1 else outputs[0] def forward(self, inputs): raise NotImplementedError() def params(self): for name in self._params: # 所有的实例变量都以字典的形式存储在实例变量__dict__中 yield self.__dict__[name] def cleargrads(self): for param in self.params(): param.cleargrad() 在 dezero/layers.py 中新建 Linear 类如下： class Linear(Layer): def __init__(self, out_size, nobias=False, dtype=np.float32, in_size=None): super().__init__() self.in_size = in_size self.out_size = out_size self.dtype = dtype self.W = Parameter(None, name='W') if self.in_size is not None: # 如果没有指定 in_size，则延后处理 self._init_W() if nobias: self.b = None else: # 偏置初始化为 0 self.b = Parameter(np.zeros(out_size, dtype=dtype), name='b') def _init_W(self): I, O = self.in_size, self.out_size # 权重 Xavier 初始化 W_data = np.random.randn(I, O).astype(self.dtype) * np.sqrt(1 / I) self.W.data = W_data def forward(self, x): if self.W.data is None: self.in_size = x.shape[1] self._init_W() y = F.linear(x, self.W, self.b) return y 示例\r","date":"2025-12-27","objectID":"/posts/%E6%B7%B1%E5%BA%A6%E5%AD%A6%E4%B9%A0%E5%9F%BA%E7%A1%80~%E8%87%AA%E5%88%B6%E6%A1%86%E6%9E%B6/:11:3","tags":null,"title":"深度学习基础~自制框架","uri":"/posts/%E6%B7%B1%E5%BA%A6%E5%AD%A6%E4%B9%A0%E5%9F%BA%E7%A1%80~%E8%87%AA%E5%88%B6%E6%A1%86%E6%9E%B6/#层-layer"},{"categories":[],"collections":["深度学习"],"content":"56.3 层 Layer\r在实现结构更加复杂的网络时，参数处理将更加复杂。通过构建 Layer，可以实现参数的自动处理。 框架搭建\r在 dezero/core.py 中新建 Parameter 类如下： class Parameter(Variable): pass 在 dezero/layers.py 中新建 Layer 类如下： import weakref from dezero.core import Parameter class Layer: def __init__(self): self._params = set() def __setattr__(self, name, value): if isinstance(value, Parameter): self._params.add(name) super().__setattr__(name, value) def __call__(self, *inputs): outputs = self.forward(*inputs) if not isinstance(outputs, tuple): outputs = (outputs,) self.inputs = [weakref.ref(x) for x in inputs] self.outputs = [weakref.ref(y) for y in outputs] return outputs if len(outputs) \u003e 1 else outputs[0] def forward(self, inputs): raise NotImplementedError() def params(self): for name in self._params: # 所有的实例变量都以字典的形式存储在实例变量__dict__中 yield self.__dict__[name] def cleargrads(self): for param in self.params(): param.cleargrad() 在 dezero/layers.py 中新建 Linear 类如下： class Linear(Layer): def __init__(self, out_size, nobias=False, dtype=np.float32, in_size=None): super().__init__() self.in_size = in_size self.out_size = out_size self.dtype = dtype self.W = Parameter(None, name='W') if self.in_size is not None: # 如果没有指定 in_size，则延后处理 self._init_W() if nobias: self.b = None else: # 偏置初始化为 0 self.b = Parameter(np.zeros(out_size, dtype=dtype), name='b') def _init_W(self): I, O = self.in_size, self.out_size # 权重 Xavier 初始化 W_data = np.random.randn(I, O).astype(self.dtype) * np.sqrt(1 / I) self.W.data = W_data def forward(self, x): if self.W.data is None: self.in_size = x.shape[1] self._init_W() y = F.linear(x, self.W, self.b) return y 示例\r","date":"2025-12-27","objectID":"/posts/%E6%B7%B1%E5%BA%A6%E5%AD%A6%E4%B9%A0%E5%9F%BA%E7%A1%80~%E8%87%AA%E5%88%B6%E6%A1%86%E6%9E%B6/:11:3","tags":null,"title":"深度学习基础~自制框架","uri":"/posts/%E6%B7%B1%E5%BA%A6%E5%AD%A6%E4%B9%A0%E5%9F%BA%E7%A1%80~%E8%87%AA%E5%88%B6%E6%A1%86%E6%9E%B6/#框架搭建-24"},{"categories":[],"collections":["深度学习"],"content":"56.3 层 Layer\r在实现结构更加复杂的网络时，参数处理将更加复杂。通过构建 Layer，可以实现参数的自动处理。 框架搭建\r在 dezero/core.py 中新建 Parameter 类如下： class Parameter(Variable): pass 在 dezero/layers.py 中新建 Layer 类如下： import weakref from dezero.core import Parameter class Layer: def __init__(self): self._params = set() def __setattr__(self, name, value): if isinstance(value, Parameter): self._params.add(name) super().__setattr__(name, value) def __call__(self, *inputs): outputs = self.forward(*inputs) if not isinstance(outputs, tuple): outputs = (outputs,) self.inputs = [weakref.ref(x) for x in inputs] self.outputs = [weakref.ref(y) for y in outputs] return outputs if len(outputs) \u003e 1 else outputs[0] def forward(self, inputs): raise NotImplementedError() def params(self): for name in self._params: # 所有的实例变量都以字典的形式存储在实例变量__dict__中 yield self.__dict__[name] def cleargrads(self): for param in self.params(): param.cleargrad() 在 dezero/layers.py 中新建 Linear 类如下： class Linear(Layer): def __init__(self, out_size, nobias=False, dtype=np.float32, in_size=None): super().__init__() self.in_size = in_size self.out_size = out_size self.dtype = dtype self.W = Parameter(None, name='W') if self.in_size is not None: # 如果没有指定 in_size，则延后处理 self._init_W() if nobias: self.b = None else: # 偏置初始化为 0 self.b = Parameter(np.zeros(out_size, dtype=dtype), name='b') def _init_W(self): I, O = self.in_size, self.out_size # 权重 Xavier 初始化 W_data = np.random.randn(I, O).astype(self.dtype) * np.sqrt(1 / I) self.W.data = W_data def forward(self, x): if self.W.data is None: self.in_size = x.shape[1] self._init_W() y = F.linear(x, self.W, self.b) return y 示例\r","date":"2025-12-27","objectID":"/posts/%E6%B7%B1%E5%BA%A6%E5%AD%A6%E4%B9%A0%E5%9F%BA%E7%A1%80~%E8%87%AA%E5%88%B6%E6%A1%86%E6%9E%B6/:11:3","tags":null,"title":"深度学习基础~自制框架","uri":"/posts/%E6%B7%B1%E5%BA%A6%E5%AD%A6%E4%B9%A0%E5%9F%BA%E7%A1%80~%E8%87%AA%E5%88%B6%E6%A1%86%E6%9E%B6/#示例-17"},{"categories":[],"collections":["深度学习"],"content":"56.4 模型 Model\r将多层的 Layer 合并就可以得到一个 Model。 框架搭建\r修改 dezero/layer.py 中的层 Layer 类： class Layer: def __init__(self): self._params = set() def __setattr__(self, name, value): if isinstance(value, (Parameter, Layer)): # 再增加 Layer self._params.add(name) super().__setattr__(name, value) def __call__(self, *inputs): outputs = self.forward(*inputs) if not isinstance(outputs, tuple): outputs = (outputs,) self.inputs = [weakref.ref(x) for x in inputs] self.outputs = [weakref.ref(y) for y in outputs] return outputs if len(outputs) \u003e 1 else outputs[0] def forward(self, inputs): raise NotImplementedError() def params(self): for name in self._params: obj = self.__dict__[name] if isinstance(obj, Layer): # 从 Layer 取出参数 yield from obj.params() else: yield obj def cleargrads(self): for param in self.params(): param.cleargrad() 在 dezero/models.py 中新建 Model 类： from dezero import Layer from dezero import utils class Model(Layer): def plot(self, *inputs, to_file='model.png'): y = self.forward(*inputs) return utils.plot_dot_graph(y, verbose=True, to_file=to_file) 在 dezero/__init__.py 中新增： from dezero.layers import Layer from dezero.models import Model 示例\r搭建的两层模型如图： ","date":"2025-12-27","objectID":"/posts/%E6%B7%B1%E5%BA%A6%E5%AD%A6%E4%B9%A0%E5%9F%BA%E7%A1%80~%E8%87%AA%E5%88%B6%E6%A1%86%E6%9E%B6/:11:4","tags":null,"title":"深度学习基础~自制框架","uri":"/posts/%E6%B7%B1%E5%BA%A6%E5%AD%A6%E4%B9%A0%E5%9F%BA%E7%A1%80~%E8%87%AA%E5%88%B6%E6%A1%86%E6%9E%B6/#模型-model"},{"categories":[],"collections":["深度学习"],"content":"56.4 模型 Model\r将多层的 Layer 合并就可以得到一个 Model。 框架搭建\r修改 dezero/layer.py 中的层 Layer 类： class Layer: def __init__(self): self._params = set() def __setattr__(self, name, value): if isinstance(value, (Parameter, Layer)): # 再增加 Layer self._params.add(name) super().__setattr__(name, value) def __call__(self, *inputs): outputs = self.forward(*inputs) if not isinstance(outputs, tuple): outputs = (outputs,) self.inputs = [weakref.ref(x) for x in inputs] self.outputs = [weakref.ref(y) for y in outputs] return outputs if len(outputs) \u003e 1 else outputs[0] def forward(self, inputs): raise NotImplementedError() def params(self): for name in self._params: obj = self.__dict__[name] if isinstance(obj, Layer): # 从 Layer 取出参数 yield from obj.params() else: yield obj def cleargrads(self): for param in self.params(): param.cleargrad() 在 dezero/models.py 中新建 Model 类： from dezero import Layer from dezero import utils class Model(Layer): def plot(self, *inputs, to_file='model.png'): y = self.forward(*inputs) return utils.plot_dot_graph(y, verbose=True, to_file=to_file) 在 dezero/__init__.py 中新增： from dezero.layers import Layer from dezero.models import Model 示例\r搭建的两层模型如图： ","date":"2025-12-27","objectID":"/posts/%E6%B7%B1%E5%BA%A6%E5%AD%A6%E4%B9%A0%E5%9F%BA%E7%A1%80~%E8%87%AA%E5%88%B6%E6%A1%86%E6%9E%B6/:11:4","tags":null,"title":"深度学习基础~自制框架","uri":"/posts/%E6%B7%B1%E5%BA%A6%E5%AD%A6%E4%B9%A0%E5%9F%BA%E7%A1%80~%E8%87%AA%E5%88%B6%E6%A1%86%E6%9E%B6/#框架搭建-25"},{"categories":[],"collections":["深度学习"],"content":"56.4 模型 Model\r将多层的 Layer 合并就可以得到一个 Model。 框架搭建\r修改 dezero/layer.py 中的层 Layer 类： class Layer: def __init__(self): self._params = set() def __setattr__(self, name, value): if isinstance(value, (Parameter, Layer)): # 再增加 Layer self._params.add(name) super().__setattr__(name, value) def __call__(self, *inputs): outputs = self.forward(*inputs) if not isinstance(outputs, tuple): outputs = (outputs,) self.inputs = [weakref.ref(x) for x in inputs] self.outputs = [weakref.ref(y) for y in outputs] return outputs if len(outputs) \u003e 1 else outputs[0] def forward(self, inputs): raise NotImplementedError() def params(self): for name in self._params: obj = self.__dict__[name] if isinstance(obj, Layer): # 从 Layer 取出参数 yield from obj.params() else: yield obj def cleargrads(self): for param in self.params(): param.cleargrad() 在 dezero/models.py 中新建 Model 类： from dezero import Layer from dezero import utils class Model(Layer): def plot(self, *inputs, to_file='model.png'): y = self.forward(*inputs) return utils.plot_dot_graph(y, verbose=True, to_file=to_file) 在 dezero/__init__.py 中新增： from dezero.layers import Layer from dezero.models import Model 示例\r搭建的两层模型如图： ","date":"2025-12-27","objectID":"/posts/%E6%B7%B1%E5%BA%A6%E5%AD%A6%E4%B9%A0%E5%9F%BA%E7%A1%80~%E8%87%AA%E5%88%B6%E6%A1%86%E6%9E%B6/:11:4","tags":null,"title":"深度学习基础~自制框架","uri":"/posts/%E6%B7%B1%E5%BA%A6%E5%AD%A6%E4%B9%A0%E5%9F%BA%E7%A1%80~%E8%87%AA%E5%88%B6%E6%A1%86%E6%9E%B6/#示例-18"},{"categories":[],"collections":["深度学习"],"content":"56.5 全连接网络 MLP\r实现一个更通用的全连接层的网络。 框架搭建\rclass MLP(Model): def __init__(self, fc_output_sizes, activation=F.sigmoid): super().__init__() self.activation = activation self.layers = [] for i, out_size in enumerate(fc_output_sizes): layer = L.Linear(out_size) setattr(self, 'l' + str(i), layer) self.layers.append(layer) def forward(self, x): for l in self.layers[:-1]: x = self.activation(l(x)) return self.layers[-1](x) ","date":"2025-12-27","objectID":"/posts/%E6%B7%B1%E5%BA%A6%E5%AD%A6%E4%B9%A0%E5%9F%BA%E7%A1%80~%E8%87%AA%E5%88%B6%E6%A1%86%E6%9E%B6/:11:5","tags":null,"title":"深度学习基础~自制框架","uri":"/posts/%E6%B7%B1%E5%BA%A6%E5%AD%A6%E4%B9%A0%E5%9F%BA%E7%A1%80~%E8%87%AA%E5%88%B6%E6%A1%86%E6%9E%B6/#全连接网络-mlp"},{"categories":[],"collections":["深度学习"],"content":"56.5 全连接网络 MLP\r实现一个更通用的全连接层的网络。 框架搭建\rclass MLP(Model): def __init__(self, fc_output_sizes, activation=F.sigmoid): super().__init__() self.activation = activation self.layers = [] for i, out_size in enumerate(fc_output_sizes): layer = L.Linear(out_size) setattr(self, 'l' + str(i), layer) self.layers.append(layer) def forward(self, x): for l in self.layers[:-1]: x = self.activation(l(x)) return self.layers[-1](x) ","date":"2025-12-27","objectID":"/posts/%E6%B7%B1%E5%BA%A6%E5%AD%A6%E4%B9%A0%E5%9F%BA%E7%A1%80~%E8%87%AA%E5%88%B6%E6%A1%86%E6%9E%B6/:11:5","tags":null,"title":"深度学习基础~自制框架","uri":"/posts/%E6%B7%B1%E5%BA%A6%E5%AD%A6%E4%B9%A0%E5%9F%BA%E7%A1%80~%E8%87%AA%E5%88%B6%E6%A1%86%E6%9E%B6/#框架搭建-26"},{"categories":[],"collections":["深度学习"],"content":"第 57 节 优化器\r上面框架中在反向传播时，还需要手动使用梯度下降法更新参数，这里使用优化器自动对参数进行更新。 框架搭建\rdezero/optimizers.py 中创建优化器类： class Optimizer: def __init__(self): self.target = None self.hooks = [] def setup(self, target): self.target = target return self def update(self): params = [p for p in self.target.params() if p.grad is not None] # 预处理（可选） for f in self.hooks: f(params) # 更新参数 for param in params: self.update_one(param) def update_one(self, param): raise NotImplementedError() def add_hook(self, f): self.hooks.append(f) 创建梯度下降法： class SGD(Optimizer): def __init__(self, lr=0.01): super().__init__() self.lr = lr def update_one(self, param): param.data -= self.lr * param.grad.data 创建动量法： $$ \\boldsymbol{v} \\leftarrow \\alpha \\boldsymbol{v}-\\eta \\frac{\\partial L}{\\partial \\boldsymbol{W}} $$ $$ \\boldsymbol{W} \\leftarrow \\boldsymbol{W}+\\boldsymbol{v} $$ class MomentumSGD(Optimizer): def __init__(self, lr=0.01, momentum=0.9): super().__init__() self.lr = lr self.momentum = momentum self.vs = {} def update_one(self, param): v_key = id(param) if v_key not in self.vs: self.vs[v_key] = np.zeros_like(param.data) v = self.vs[v_key] v *= self.momentum v -= self.lr * param.grad.data param.data += v 示例\r信息\r可以看到，到这里已经比较接近 pytorch 的代码体验了。 ","date":"2025-12-27","objectID":"/posts/%E6%B7%B1%E5%BA%A6%E5%AD%A6%E4%B9%A0%E5%9F%BA%E7%A1%80~%E8%87%AA%E5%88%B6%E6%A1%86%E6%9E%B6/:12:0","tags":null,"title":"深度学习基础~自制框架","uri":"/posts/%E6%B7%B1%E5%BA%A6%E5%AD%A6%E4%B9%A0%E5%9F%BA%E7%A1%80~%E8%87%AA%E5%88%B6%E6%A1%86%E6%9E%B6/#优化器"},{"categories":[],"collections":["深度学习"],"content":"第 57 节 优化器\r上面框架中在反向传播时，还需要手动使用梯度下降法更新参数，这里使用优化器自动对参数进行更新。 框架搭建\rdezero/optimizers.py 中创建优化器类： class Optimizer: def __init__(self): self.target = None self.hooks = [] def setup(self, target): self.target = target return self def update(self): params = [p for p in self.target.params() if p.grad is not None] # 预处理（可选） for f in self.hooks: f(params) # 更新参数 for param in params: self.update_one(param) def update_one(self, param): raise NotImplementedError() def add_hook(self, f): self.hooks.append(f) 创建梯度下降法： class SGD(Optimizer): def __init__(self, lr=0.01): super().__init__() self.lr = lr def update_one(self, param): param.data -= self.lr * param.grad.data 创建动量法： $$ \\boldsymbol{v} \\leftarrow \\alpha \\boldsymbol{v}-\\eta \\frac{\\partial L}{\\partial \\boldsymbol{W}} $$ $$ \\boldsymbol{W} \\leftarrow \\boldsymbol{W}+\\boldsymbol{v} $$ class MomentumSGD(Optimizer): def __init__(self, lr=0.01, momentum=0.9): super().__init__() self.lr = lr self.momentum = momentum self.vs = {} def update_one(self, param): v_key = id(param) if v_key not in self.vs: self.vs[v_key] = np.zeros_like(param.data) v = self.vs[v_key] v *= self.momentum v -= self.lr * param.grad.data param.data += v 示例\r信息\r可以看到，到这里已经比较接近 pytorch 的代码体验了。 ","date":"2025-12-27","objectID":"/posts/%E6%B7%B1%E5%BA%A6%E5%AD%A6%E4%B9%A0%E5%9F%BA%E7%A1%80~%E8%87%AA%E5%88%B6%E6%A1%86%E6%9E%B6/:12:0","tags":null,"title":"深度学习基础~自制框架","uri":"/posts/%E6%B7%B1%E5%BA%A6%E5%AD%A6%E4%B9%A0%E5%9F%BA%E7%A1%80~%E8%87%AA%E5%88%B6%E6%A1%86%E6%9E%B6/#框架搭建-27"},{"categories":[],"collections":["深度学习"],"content":"第 57 节 优化器\r上面框架中在反向传播时，还需要手动使用梯度下降法更新参数，这里使用优化器自动对参数进行更新。 框架搭建\rdezero/optimizers.py 中创建优化器类： class Optimizer: def __init__(self): self.target = None self.hooks = [] def setup(self, target): self.target = target return self def update(self): params = [p for p in self.target.params() if p.grad is not None] # 预处理（可选） for f in self.hooks: f(params) # 更新参数 for param in params: self.update_one(param) def update_one(self, param): raise NotImplementedError() def add_hook(self, f): self.hooks.append(f) 创建梯度下降法： class SGD(Optimizer): def __init__(self, lr=0.01): super().__init__() self.lr = lr def update_one(self, param): param.data -= self.lr * param.grad.data 创建动量法： $$ \\boldsymbol{v} \\leftarrow \\alpha \\boldsymbol{v}-\\eta \\frac{\\partial L}{\\partial \\boldsymbol{W}} $$ $$ \\boldsymbol{W} \\leftarrow \\boldsymbol{W}+\\boldsymbol{v} $$ class MomentumSGD(Optimizer): def __init__(self, lr=0.01, momentum=0.9): super().__init__() self.lr = lr self.momentum = momentum self.vs = {} def update_one(self, param): v_key = id(param) if v_key not in self.vs: self.vs[v_key] = np.zeros_like(param.data) v = self.vs[v_key] v *= self.momentum v -= self.lr * param.grad.data param.data += v 示例\r信息\r可以看到，到这里已经比较接近 pytorch 的代码体验了。 ","date":"2025-12-27","objectID":"/posts/%E6%B7%B1%E5%BA%A6%E5%AD%A6%E4%B9%A0%E5%9F%BA%E7%A1%80~%E8%87%AA%E5%88%B6%E6%A1%86%E6%9E%B6/:12:0","tags":null,"title":"深度学习基础~自制框架","uri":"/posts/%E6%B7%B1%E5%BA%A6%E5%AD%A6%E4%B9%A0%E5%9F%BA%E7%A1%80~%E8%87%AA%E5%88%B6%E6%A1%86%E6%9E%B6/#示例-19"},{"categories":[],"collections":["深度学习"],"content":"第 58 节 多分类\r","date":"2025-12-27","objectID":"/posts/%E6%B7%B1%E5%BA%A6%E5%AD%A6%E4%B9%A0%E5%9F%BA%E7%A1%80~%E8%87%AA%E5%88%B6%E6%A1%86%E6%9E%B6/:13:0","tags":null,"title":"深度学习基础~自制框架","uri":"/posts/%E6%B7%B1%E5%BA%A6%E5%AD%A6%E4%B9%A0%E5%9F%BA%E7%A1%80~%E8%87%AA%E5%88%B6%E6%A1%86%E6%9E%B6/#多分类"},{"categories":[],"collections":["深度学习"],"content":"58.1 切片\r增加切片函数，将多维数组中的一些数据原封不动地传递出去，反向传播只在被提取的部分设置梯度，其余部分梯度为 0。 框架搭建\r在 dezero/functions.py 增加 GetItem 类： class GetItem(Function): def __init__(self, slices): self.slices = slices def forward(self, x): y = x[self.slices] return y def backward(self, gy): x, = self.inputs f = GetItemGrad(self.slices, x.shape) return f(gy) class GetItemGrad(Function): def __init__(self, slices, in_shape): self.slices = slices self.in_shape = in_shape def forward(self, gy): gx = np.zeros(self.in_shape, dtype=gy.dtype) np.add.at(gx, self.slices, gy) # 只在被切出来的位置累加梯度，其余位置为 0 return gx def backward(self, ggx): return get_item(ggx, self.slices) def get_item(x, slices): f = GetItem(slices) return f(x) ","date":"2025-12-27","objectID":"/posts/%E6%B7%B1%E5%BA%A6%E5%AD%A6%E4%B9%A0%E5%9F%BA%E7%A1%80~%E8%87%AA%E5%88%B6%E6%A1%86%E6%9E%B6/:13:1","tags":null,"title":"深度学习基础~自制框架","uri":"/posts/%E6%B7%B1%E5%BA%A6%E5%AD%A6%E4%B9%A0%E5%9F%BA%E7%A1%80~%E8%87%AA%E5%88%B6%E6%A1%86%E6%9E%B6/#切片"},{"categories":[],"collections":["深度学习"],"content":"58.1 切片\r增加切片函数，将多维数组中的一些数据原封不动地传递出去，反向传播只在被提取的部分设置梯度，其余部分梯度为 0。 框架搭建\r在 dezero/functions.py 增加 GetItem 类： class GetItem(Function): def __init__(self, slices): self.slices = slices def forward(self, x): y = x[self.slices] return y def backward(self, gy): x, = self.inputs f = GetItemGrad(self.slices, x.shape) return f(gy) class GetItemGrad(Function): def __init__(self, slices, in_shape): self.slices = slices self.in_shape = in_shape def forward(self, gy): gx = np.zeros(self.in_shape, dtype=gy.dtype) np.add.at(gx, self.slices, gy) # 只在被切出来的位置累加梯度，其余位置为 0 return gx def backward(self, ggx): return get_item(ggx, self.slices) def get_item(x, slices): f = GetItem(slices) return f(x) ","date":"2025-12-27","objectID":"/posts/%E6%B7%B1%E5%BA%A6%E5%AD%A6%E4%B9%A0%E5%9F%BA%E7%A1%80~%E8%87%AA%E5%88%B6%E6%A1%86%E6%9E%B6/:13:1","tags":null,"title":"深度学习基础~自制框架","uri":"/posts/%E6%B7%B1%E5%BA%A6%E5%AD%A6%E4%B9%A0%E5%9F%BA%E7%A1%80~%E8%87%AA%E5%88%B6%E6%A1%86%E6%9E%B6/#框架搭建-28"},{"categories":[],"collections":["深度学习"],"content":"58.2 Softmax\rSoftmax 将神经网络输出的数值转换为概率。 $$ p_k=\\frac{\\exp \\left(y_k\\right)}{\\sum_{i=1}^n \\exp \\left(y_i\\right)} $$ Softmax 有平移不变性： $$ \\mathrm{softmax}(x)=\\mathrm{softmax}(x−c) $$ 取 $c$ 为 $\\max (x)$ 可以防止 $\\exp$ 溢出。 框架搭建\r在 dezero/functions.py 增加 Softmax 类： class Softmax(Function): def __init__(self, axis=1): self.axis = axis def forward(self, x): y = x - x.max(axis=self.axis, keepdims=True) y = np.exp(y) y /= y.sum(axis=self.axis, keepdims=True) return y def backward(self, gy): y = self.outputs[0]() gx = y * gy sumdx = gx.sum(axis=self.axis, keepdims=True) gx -= y * sumdx return gx def softmax(x, axis=1): return Softmax(axis)(x) ","date":"2025-12-27","objectID":"/posts/%E6%B7%B1%E5%BA%A6%E5%AD%A6%E4%B9%A0%E5%9F%BA%E7%A1%80~%E8%87%AA%E5%88%B6%E6%A1%86%E6%9E%B6/:13:2","tags":null,"title":"深度学习基础~自制框架","uri":"/posts/%E6%B7%B1%E5%BA%A6%E5%AD%A6%E4%B9%A0%E5%9F%BA%E7%A1%80~%E8%87%AA%E5%88%B6%E6%A1%86%E6%9E%B6/#softmax"},{"categories":[],"collections":["深度学习"],"content":"58.2 Softmax\rSoftmax 将神经网络输出的数值转换为概率。 $$ p_k=\\frac{\\exp \\left(y_k\\right)}{\\sum_{i=1}^n \\exp \\left(y_i\\right)} $$ Softmax 有平移不变性： $$ \\mathrm{softmax}(x)=\\mathrm{softmax}(x−c) $$ 取 $c$ 为 $\\max (x)$ 可以防止 $\\exp$ 溢出。 框架搭建\r在 dezero/functions.py 增加 Softmax 类： class Softmax(Function): def __init__(self, axis=1): self.axis = axis def forward(self, x): y = x - x.max(axis=self.axis, keepdims=True) y = np.exp(y) y /= y.sum(axis=self.axis, keepdims=True) return y def backward(self, gy): y = self.outputs[0]() gx = y * gy sumdx = gx.sum(axis=self.axis, keepdims=True) gx -= y * sumdx return gx def softmax(x, axis=1): return Softmax(axis)(x) ","date":"2025-12-27","objectID":"/posts/%E6%B7%B1%E5%BA%A6%E5%AD%A6%E4%B9%A0%E5%9F%BA%E7%A1%80~%E8%87%AA%E5%88%B6%E6%A1%86%E6%9E%B6/:13:2","tags":null,"title":"深度学习基础~自制框架","uri":"/posts/%E6%B7%B1%E5%BA%A6%E5%AD%A6%E4%B9%A0%E5%9F%BA%E7%A1%80~%E8%87%AA%E5%88%B6%E6%A1%86%E6%9E%B6/#框架搭建-29"},{"categories":[],"collections":["深度学习"],"content":"58.3 交叉熵\r在线性回归中，我们使用均方误差作为损失函数，但在进行多分类时，需要使用专用的损失函数。最常用的是交叉熵误差（cross entropy error），对于单个样本，有： $$ L=-\\sum_i^C y_{i} \\log p_i $$ 其中 $C$ 为类别数量，样本的真实类别为 $t$， $y_i= \\begin{cases}1 \u0026 i=t \\ 0 \u0026 i \\neq t\\end{cases}$ 在多分类问题中，交叉熵误差的 $p_k$ 使用 Softmax 函数的输出，可以将 Softmax 函数和交叉熵误差合二为一来实现，合并后的函数复杂度会更低，计算更加稳定。 $$ L=-\\sum_{i=1}C y_i \\log \\left(\\frac{e{x_i}}{\\sum_k e^{x_k}}\\right) $$ 所以: $$ L=-\\sum_i y_i x_i+\\sum_i y_i \\log \\sum_k e^{x_k} $$ 因为 $y$ 是 one-hot，$\\sum_i y_i=1$， $\\sum_i y_ix_i=x_t$，所以： $$ L=-x_t+\\log \\sum_{k=1}C e{x_k} $$ 扩展到多样本： $$ L=\\frac{1}{N} \\sum_{i=1}N\\left(-x_{i, t_i}+\\log \\sum_k e{x_{i k}}\\right) $$ 对第 $i$ 个样本求梯度： $$ L_i=-x_{i, t_i}+\\log \\sum_{k=1}C e{x_{i k}} $$ 对于第一项： $$ \\frac{\\partial\\left(-x_{i, t_i}\\right)}{\\partial x_{i j}}=\\left{\\begin{array}{ll} -1 \u0026 j=t_i \\ 0 \u0026 j \\neq t_i \\end{array}=-\\left(t_{-} \\text {onehot }\\right)_{i j}\\right. $$ 对于第二项： $$ \\frac{\\partial}{\\partial x_{i j}} \\log \\sum_k e^{x_{i k}}=\\frac{e^{x_{i j}}}{\\sum_k e^{x_{i k}}}=\\left(\\operatorname{softmax}\\left(x_i\\right)\\right)_j $$ 于是： $$ \\frac{\\partial L_i}{\\partial x_{i j}}=\\left(\\operatorname{softmax}\\left(x_i\\right)\\right)j-\\left(t{-} o n e h o t\\right)_{i j} $$ 所以对整体求梯度： $$ \\frac{\\partial L}{\\partial x_{i j}}=\\frac{1}{N}\\left[\\left(\\operatorname{softmax}\\left(x_i\\right)\\right)j-\\left(t{-} o n e h o t\\right)_{i j}\\right] $$ 框架搭建\r在 dezero/utils.py 增加 logsumexp 类： def logsumexp(x, axis=1): m = x.max(axis=axis, keepdims=True) y = x - m np.exp(y, out=y) s = y.sum(axis=axis, keepdims=True) np.log(s, out=s) m += s return m 信息\r通常维度 0 表示不同的样本，维度 1 表示不同的类别，因而默认的轴为 1。 在 dezero/functions.py 增加 SoftmaxCrossEntropy 类： class SoftmaxCrossEntropy(Function): def forward(self, x, t): N = x.shape[0] log_z = utils.logsumexp(x, axis=1) log_p = x - log_z # np.arange(N) 从 0 到 N-1 # t.ravel() 把真实标签索引展平成一维向量 # 从log_p中取出每一个log_p[x,y]对应的元素 log_p = log_p[np.arange(N), t.ravel()] y = -log_p.sum() / np.float32(N) return y def backward(self, gy): x, t = self.inputs N, CLS_NUM = x.shape gy *= 1 / N y = softmax(x) # 通过 t.data 获取真实标签的索引，从单位矩阵中取出对应的行 t_onehot = np.eye(CLS_NUM, dtype=t.dtype)[t.data] y = (y - t_onehot) * gy return y def softmax_cross_entropy(x, t): return SoftmaxCrossEntropy()(x, t) ","date":"2025-12-27","objectID":"/posts/%E6%B7%B1%E5%BA%A6%E5%AD%A6%E4%B9%A0%E5%9F%BA%E7%A1%80~%E8%87%AA%E5%88%B6%E6%A1%86%E6%9E%B6/:13:3","tags":null,"title":"深度学习基础~自制框架","uri":"/posts/%E6%B7%B1%E5%BA%A6%E5%AD%A6%E4%B9%A0%E5%9F%BA%E7%A1%80~%E8%87%AA%E5%88%B6%E6%A1%86%E6%9E%B6/#交叉熵"},{"categories":[],"collections":["深度学习"],"content":"58.3 交叉熵\r在线性回归中，我们使用均方误差作为损失函数，但在进行多分类时，需要使用专用的损失函数。最常用的是交叉熵误差（cross entropy error），对于单个样本，有： $$ L=-\\sum_i^C y_{i} \\log p_i $$ 其中 $C$ 为类别数量，样本的真实类别为 $t$， $y_i= \\begin{cases}1 \u0026 i=t \\ 0 \u0026 i \\neq t\\end{cases}$ 在多分类问题中，交叉熵误差的 $p_k$ 使用 Softmax 函数的输出，可以将 Softmax 函数和交叉熵误差合二为一来实现，合并后的函数复杂度会更低，计算更加稳定。 $$ L=-\\sum_{i=1}C y_i \\log \\left(\\frac{e{x_i}}{\\sum_k e^{x_k}}\\right) $$ 所以: $$ L=-\\sum_i y_i x_i+\\sum_i y_i \\log \\sum_k e^{x_k} $$ 因为 $y$ 是 one-hot，$\\sum_i y_i=1$， $\\sum_i y_ix_i=x_t$，所以： $$ L=-x_t+\\log \\sum_{k=1}C e{x_k} $$ 扩展到多样本： $$ L=\\frac{1}{N} \\sum_{i=1}N\\left(-x_{i, t_i}+\\log \\sum_k e{x_{i k}}\\right) $$ 对第 $i$ 个样本求梯度： $$ L_i=-x_{i, t_i}+\\log \\sum_{k=1}C e{x_{i k}} $$ 对于第一项： $$ \\frac{\\partial\\left(-x_{i, t_i}\\right)}{\\partial x_{i j}}=\\left{\\begin{array}{ll} -1 \u0026 j=t_i \\ 0 \u0026 j \\neq t_i \\end{array}=-\\left(t_{-} \\text {onehot }\\right)_{i j}\\right. $$ 对于第二项： $$ \\frac{\\partial}{\\partial x_{i j}} \\log \\sum_k e^{x_{i k}}=\\frac{e^{x_{i j}}}{\\sum_k e^{x_{i k}}}=\\left(\\operatorname{softmax}\\left(x_i\\right)\\right)_j $$ 于是： $$ \\frac{\\partial L_i}{\\partial x_{i j}}=\\left(\\operatorname{softmax}\\left(x_i\\right)\\right)j-\\left(t{-} o n e h o t\\right)_{i j} $$ 所以对整体求梯度： $$ \\frac{\\partial L}{\\partial x_{i j}}=\\frac{1}{N}\\left[\\left(\\operatorname{softmax}\\left(x_i\\right)\\right)j-\\left(t{-} o n e h o t\\right)_{i j}\\right] $$ 框架搭建\r在 dezero/utils.py 增加 logsumexp 类： def logsumexp(x, axis=1): m = x.max(axis=axis, keepdims=True) y = x - m np.exp(y, out=y) s = y.sum(axis=axis, keepdims=True) np.log(s, out=s) m += s return m 信息\r通常维度 0 表示不同的样本，维度 1 表示不同的类别，因而默认的轴为 1。 在 dezero/functions.py 增加 SoftmaxCrossEntropy 类： class SoftmaxCrossEntropy(Function): def forward(self, x, t): N = x.shape[0] log_z = utils.logsumexp(x, axis=1) log_p = x - log_z # np.arange(N) 从 0 到 N-1 # t.ravel() 把真实标签索引展平成一维向量 # 从log_p中取出每一个log_p[x,y]对应的元素 log_p = log_p[np.arange(N), t.ravel()] y = -log_p.sum() / np.float32(N) return y def backward(self, gy): x, t = self.inputs N, CLS_NUM = x.shape gy *= 1 / N y = softmax(x) # 通过 t.data 获取真实标签的索引，从单位矩阵中取出对应的行 t_onehot = np.eye(CLS_NUM, dtype=t.dtype)[t.data] y = (y - t_onehot) * gy return y def softmax_cross_entropy(x, t): return SoftmaxCrossEntropy()(x, t) ","date":"2025-12-27","objectID":"/posts/%E6%B7%B1%E5%BA%A6%E5%AD%A6%E4%B9%A0%E5%9F%BA%E7%A1%80~%E8%87%AA%E5%88%B6%E6%A1%86%E6%9E%B6/:13:3","tags":null,"title":"深度学习基础~自制框架","uri":"/posts/%E6%B7%B1%E5%BA%A6%E5%AD%A6%E4%B9%A0%E5%9F%BA%E7%A1%80~%E8%87%AA%E5%88%B6%E6%A1%86%E6%9E%B6/#框架搭建-30"},{"categories":[],"collections":["深度学习"],"content":"第 59 节 数据集\r","date":"2025-12-27","objectID":"/posts/%E6%B7%B1%E5%BA%A6%E5%AD%A6%E4%B9%A0%E5%9F%BA%E7%A1%80~%E8%87%AA%E5%88%B6%E6%A1%86%E6%9E%B6/:14:0","tags":null,"title":"深度学习基础~自制框架","uri":"/posts/%E6%B7%B1%E5%BA%A6%E5%AD%A6%E4%B9%A0%E5%9F%BA%E7%A1%80~%E8%87%AA%E5%88%B6%E6%A1%86%E6%9E%B6/#数据集"},{"categories":[],"collections":["深度学习"],"content":"59.1 Dataset 类\rDataset 类是作为基类实现的。我们让用户实际使用的数据集类继承 Dataset 类。 框架搭建\r在 dezero/dataset.py 中创建 Dataset 类： class Dataset: def __init__(self, train=True, transform=None, target_transform=None): self.train = train self.transform = transform self.target_transform = target_transform if self.transform is None: self.transform = lambda x: x if self.target_transform is None: self.target_transform = lambda x: x self.data = None self.label = None self.prepare() def __getitem__(self, index): assert np.isscalar(index) if self.label is None: return self.transform(self.data[index]), None else: return self.transform(self.data[index]), self.target_transform(self.label[index]) def __len__(self): return len(self.data) def prepare(self): pass ","date":"2025-12-27","objectID":"/posts/%E6%B7%B1%E5%BA%A6%E5%AD%A6%E4%B9%A0%E5%9F%BA%E7%A1%80~%E8%87%AA%E5%88%B6%E6%A1%86%E6%9E%B6/:14:1","tags":null,"title":"深度学习基础~自制框架","uri":"/posts/%E6%B7%B1%E5%BA%A6%E5%AD%A6%E4%B9%A0%E5%9F%BA%E7%A1%80~%E8%87%AA%E5%88%B6%E6%A1%86%E6%9E%B6/#dataset-类"},{"categories":[],"collections":["深度学习"],"content":"59.1 Dataset 类\rDataset 类是作为基类实现的。我们让用户实际使用的数据集类继承 Dataset 类。 框架搭建\r在 dezero/dataset.py 中创建 Dataset 类： class Dataset: def __init__(self, train=True, transform=None, target_transform=None): self.train = train self.transform = transform self.target_transform = target_transform if self.transform is None: self.transform = lambda x: x if self.target_transform is None: self.target_transform = lambda x: x self.data = None self.label = None self.prepare() def __getitem__(self, index): assert np.isscalar(index) if self.label is None: return self.transform(self.data[index]), None else: return self.transform(self.data[index]), self.target_transform(self.label[index]) def __len__(self): return len(self.data) def prepare(self): pass ","date":"2025-12-27","objectID":"/posts/%E6%B7%B1%E5%BA%A6%E5%AD%A6%E4%B9%A0%E5%9F%BA%E7%A1%80~%E8%87%AA%E5%88%B6%E6%A1%86%E6%9E%B6/:14:1","tags":null,"title":"深度学习基础~自制框架","uri":"/posts/%E6%B7%B1%E5%BA%A6%E5%AD%A6%E4%B9%A0%E5%9F%BA%E7%A1%80~%E8%87%AA%E5%88%B6%E6%A1%86%E6%9E%B6/#框架搭建-31"},{"categories":[],"collections":["深度学习"],"content":"59.2 transform 函数\rNormalize\rNormalize 对数据进行正则化处理。 class Normalize: def __init__(self, mean=0, std=1): self.mean = mean self.std = std def __call__(self, array): mean, std = self.mean, self.std # 正则化图像的处理步骤 if not np.isscalar(mean): # 构造全 1 的 mshape，长度等于数组维度 mshape = [1] * array.ndim # 这里考虑的数组是 [C H W]，维度0是通道 mshape[0] = len(array) if len(self.mean) == 1 else len(self.mean) mean = np.array(self.mean, dtype=array.dtype).reshape(*mshape) if not np.isscalar(std): # 构造全 1 的 mshape，长度等于数组维度 rshape = [1] * array.ndim # 这里考虑的数组是 [C H W]，维度0是通道 rshape[0] = len(array) if len(self.std) == 1 else len(self.std) std = np.array(self.std, dtype=array.dtype).reshape(*rshape) return (array - mean) / std 信息\r由于框架的 transform 函数是在 __getitem__ 的时候执行的，所以样本数都是 1，因而输入数据不是 [N C H W] 这种结构。 Flatten\rFlatten 将数据展平成一维。 class Flatten: def __call__(self, array): return array.flatten() ToFloat\rToFloat 将数据的类型转成 np.float32 class AsType: def __init__(self, dtype=np.float32): self.dtype = dtype def __call__(self, array): return array.astype(self.dtype) ToFloat = AsType Compose\rCompose 类按顺序从头开始连续进行多个转换。 class Compose: def __init__(self, transforms=[]): self.transforms = transforms def __call__(self, img): if not self.transforms: return img for t in self.transforms: img = t(img) return img ","date":"2025-12-27","objectID":"/posts/%E6%B7%B1%E5%BA%A6%E5%AD%A6%E4%B9%A0%E5%9F%BA%E7%A1%80~%E8%87%AA%E5%88%B6%E6%A1%86%E6%9E%B6/:14:2","tags":null,"title":"深度学习基础~自制框架","uri":"/posts/%E6%B7%B1%E5%BA%A6%E5%AD%A6%E4%B9%A0%E5%9F%BA%E7%A1%80~%E8%87%AA%E5%88%B6%E6%A1%86%E6%9E%B6/#transform-函数"},{"categories":[],"collections":["深度学习"],"content":"59.2 transform 函数\rNormalize\rNormalize 对数据进行正则化处理。 class Normalize: def __init__(self, mean=0, std=1): self.mean = mean self.std = std def __call__(self, array): mean, std = self.mean, self.std # 正则化图像的处理步骤 if not np.isscalar(mean): # 构造全 1 的 mshape，长度等于数组维度 mshape = [1] * array.ndim # 这里考虑的数组是 [C H W]，维度0是通道 mshape[0] = len(array) if len(self.mean) == 1 else len(self.mean) mean = np.array(self.mean, dtype=array.dtype).reshape(*mshape) if not np.isscalar(std): # 构造全 1 的 mshape，长度等于数组维度 rshape = [1] * array.ndim # 这里考虑的数组是 [C H W]，维度0是通道 rshape[0] = len(array) if len(self.std) == 1 else len(self.std) std = np.array(self.std, dtype=array.dtype).reshape(*rshape) return (array - mean) / std 信息\r由于框架的 transform 函数是在 __getitem__ 的时候执行的，所以样本数都是 1，因而输入数据不是 [N C H W] 这种结构。 Flatten\rFlatten 将数据展平成一维。 class Flatten: def __call__(self, array): return array.flatten() ToFloat\rToFloat 将数据的类型转成 np.float32 class AsType: def __init__(self, dtype=np.float32): self.dtype = dtype def __call__(self, array): return array.astype(self.dtype) ToFloat = AsType Compose\rCompose 类按顺序从头开始连续进行多个转换。 class Compose: def __init__(self, transforms=[]): self.transforms = transforms def __call__(self, img): if not self.transforms: return img for t in self.transforms: img = t(img) return img ","date":"2025-12-27","objectID":"/posts/%E6%B7%B1%E5%BA%A6%E5%AD%A6%E4%B9%A0%E5%9F%BA%E7%A1%80~%E8%87%AA%E5%88%B6%E6%A1%86%E6%9E%B6/:14:2","tags":null,"title":"深度学习基础~自制框架","uri":"/posts/%E6%B7%B1%E5%BA%A6%E5%AD%A6%E4%B9%A0%E5%9F%BA%E7%A1%80~%E8%87%AA%E5%88%B6%E6%A1%86%E6%9E%B6/#normalize"},{"categories":[],"collections":["深度学习"],"content":"59.2 transform 函数\rNormalize\rNormalize 对数据进行正则化处理。 class Normalize: def __init__(self, mean=0, std=1): self.mean = mean self.std = std def __call__(self, array): mean, std = self.mean, self.std # 正则化图像的处理步骤 if not np.isscalar(mean): # 构造全 1 的 mshape，长度等于数组维度 mshape = [1] * array.ndim # 这里考虑的数组是 [C H W]，维度0是通道 mshape[0] = len(array) if len(self.mean) == 1 else len(self.mean) mean = np.array(self.mean, dtype=array.dtype).reshape(*mshape) if not np.isscalar(std): # 构造全 1 的 mshape，长度等于数组维度 rshape = [1] * array.ndim # 这里考虑的数组是 [C H W]，维度0是通道 rshape[0] = len(array) if len(self.std) == 1 else len(self.std) std = np.array(self.std, dtype=array.dtype).reshape(*rshape) return (array - mean) / std 信息\r由于框架的 transform 函数是在 __getitem__ 的时候执行的，所以样本数都是 1，因而输入数据不是 [N C H W] 这种结构。 Flatten\rFlatten 将数据展平成一维。 class Flatten: def __call__(self, array): return array.flatten() ToFloat\rToFloat 将数据的类型转成 np.float32 class AsType: def __init__(self, dtype=np.float32): self.dtype = dtype def __call__(self, array): return array.astype(self.dtype) ToFloat = AsType Compose\rCompose 类按顺序从头开始连续进行多个转换。 class Compose: def __init__(self, transforms=[]): self.transforms = transforms def __call__(self, img): if not self.transforms: return img for t in self.transforms: img = t(img) return img ","date":"2025-12-27","objectID":"/posts/%E6%B7%B1%E5%BA%A6%E5%AD%A6%E4%B9%A0%E5%9F%BA%E7%A1%80~%E8%87%AA%E5%88%B6%E6%A1%86%E6%9E%B6/:14:2","tags":null,"title":"深度学习基础~自制框架","uri":"/posts/%E6%B7%B1%E5%BA%A6%E5%AD%A6%E4%B9%A0%E5%9F%BA%E7%A1%80~%E8%87%AA%E5%88%B6%E6%A1%86%E6%9E%B6/#flatten"},{"categories":[],"collections":["深度学习"],"content":"59.2 transform 函数\rNormalize\rNormalize 对数据进行正则化处理。 class Normalize: def __init__(self, mean=0, std=1): self.mean = mean self.std = std def __call__(self, array): mean, std = self.mean, self.std # 正则化图像的处理步骤 if not np.isscalar(mean): # 构造全 1 的 mshape，长度等于数组维度 mshape = [1] * array.ndim # 这里考虑的数组是 [C H W]，维度0是通道 mshape[0] = len(array) if len(self.mean) == 1 else len(self.mean) mean = np.array(self.mean, dtype=array.dtype).reshape(*mshape) if not np.isscalar(std): # 构造全 1 的 mshape，长度等于数组维度 rshape = [1] * array.ndim # 这里考虑的数组是 [C H W]，维度0是通道 rshape[0] = len(array) if len(self.std) == 1 else len(self.std) std = np.array(self.std, dtype=array.dtype).reshape(*rshape) return (array - mean) / std 信息\r由于框架的 transform 函数是在 __getitem__ 的时候执行的，所以样本数都是 1，因而输入数据不是 [N C H W] 这种结构。 Flatten\rFlatten 将数据展平成一维。 class Flatten: def __call__(self, array): return array.flatten() ToFloat\rToFloat 将数据的类型转成 np.float32 class AsType: def __init__(self, dtype=np.float32): self.dtype = dtype def __call__(self, array): return array.astype(self.dtype) ToFloat = AsType Compose\rCompose 类按顺序从头开始连续进行多个转换。 class Compose: def __init__(self, transforms=[]): self.transforms = transforms def __call__(self, img): if not self.transforms: return img for t in self.transforms: img = t(img) return img ","date":"2025-12-27","objectID":"/posts/%E6%B7%B1%E5%BA%A6%E5%AD%A6%E4%B9%A0%E5%9F%BA%E7%A1%80~%E8%87%AA%E5%88%B6%E6%A1%86%E6%9E%B6/:14:2","tags":null,"title":"深度学习基础~自制框架","uri":"/posts/%E6%B7%B1%E5%BA%A6%E5%AD%A6%E4%B9%A0%E5%9F%BA%E7%A1%80~%E8%87%AA%E5%88%B6%E6%A1%86%E6%9E%B6/#tofloat"},{"categories":[],"collections":["深度学习"],"content":"59.2 transform 函数\rNormalize\rNormalize 对数据进行正则化处理。 class Normalize: def __init__(self, mean=0, std=1): self.mean = mean self.std = std def __call__(self, array): mean, std = self.mean, self.std # 正则化图像的处理步骤 if not np.isscalar(mean): # 构造全 1 的 mshape，长度等于数组维度 mshape = [1] * array.ndim # 这里考虑的数组是 [C H W]，维度0是通道 mshape[0] = len(array) if len(self.mean) == 1 else len(self.mean) mean = np.array(self.mean, dtype=array.dtype).reshape(*mshape) if not np.isscalar(std): # 构造全 1 的 mshape，长度等于数组维度 rshape = [1] * array.ndim # 这里考虑的数组是 [C H W]，维度0是通道 rshape[0] = len(array) if len(self.std) == 1 else len(self.std) std = np.array(self.std, dtype=array.dtype).reshape(*rshape) return (array - mean) / std 信息\r由于框架的 transform 函数是在 __getitem__ 的时候执行的，所以样本数都是 1，因而输入数据不是 [N C H W] 这种结构。 Flatten\rFlatten 将数据展平成一维。 class Flatten: def __call__(self, array): return array.flatten() ToFloat\rToFloat 将数据的类型转成 np.float32 class AsType: def __init__(self, dtype=np.float32): self.dtype = dtype def __call__(self, array): return array.astype(self.dtype) ToFloat = AsType Compose\rCompose 类按顺序从头开始连续进行多个转换。 class Compose: def __init__(self, transforms=[]): self.transforms = transforms def __call__(self, img): if not self.transforms: return img for t in self.transforms: img = t(img) return img ","date":"2025-12-27","objectID":"/posts/%E6%B7%B1%E5%BA%A6%E5%AD%A6%E4%B9%A0%E5%9F%BA%E7%A1%80~%E8%87%AA%E5%88%B6%E6%A1%86%E6%9E%B6/:14:2","tags":null,"title":"深度学习基础~自制框架","uri":"/posts/%E6%B7%B1%E5%BA%A6%E5%AD%A6%E4%B9%A0%E5%9F%BA%E7%A1%80~%E8%87%AA%E5%88%B6%E6%A1%86%E6%9E%B6/#compose"},{"categories":[],"collections":["深度学习"],"content":"59.3 DataLoader 类\rDataLoader 从 Dataset 中创建小批量数据，实现数据集重排等工作。 框架搭建\rimport math import numpy as np class DataLoader: def __init__(self, dataset, batch_size, shuffle=True): self.dataset = dataset self.batch_size = batch_size self.shuffle = shuffle self.data_size = len(dataset) self.max_iter = math.ceil(self.data_size / batch_size) self.reset() def reset(self): self.iteration = 0 if self.shuffle: self.index = np.random.permutation(len(self.dataset)) else: self.index = np.arange(len(self.dataset)) def __iter__(self): return self def __next__(self): if self.iteration \u003e= self.max_iter: self.reset() raise StopIteration i, batch_size = self.iteration, self.batch_size batch_index = self.index[i * batch_size:(i + 1) * batch_size] batch = [self.dataset[i] for i in batch_index] x = np.array([example[0] for example in batch]) t = np.array([example[1] for example in batch]) self.iteration += 1 return x, t def next(self): return self.__next__() ","date":"2025-12-27","objectID":"/posts/%E6%B7%B1%E5%BA%A6%E5%AD%A6%E4%B9%A0%E5%9F%BA%E7%A1%80~%E8%87%AA%E5%88%B6%E6%A1%86%E6%9E%B6/:14:3","tags":null,"title":"深度学习基础~自制框架","uri":"/posts/%E6%B7%B1%E5%BA%A6%E5%AD%A6%E4%B9%A0%E5%9F%BA%E7%A1%80~%E8%87%AA%E5%88%B6%E6%A1%86%E6%9E%B6/#dataloader-类"},{"categories":[],"collections":["深度学习"],"content":"59.3 DataLoader 类\rDataLoader 从 Dataset 中创建小批量数据，实现数据集重排等工作。 框架搭建\rimport math import numpy as np class DataLoader: def __init__(self, dataset, batch_size, shuffle=True): self.dataset = dataset self.batch_size = batch_size self.shuffle = shuffle self.data_size = len(dataset) self.max_iter = math.ceil(self.data_size / batch_size) self.reset() def reset(self): self.iteration = 0 if self.shuffle: self.index = np.random.permutation(len(self.dataset)) else: self.index = np.arange(len(self.dataset)) def __iter__(self): return self def __next__(self): if self.iteration \u003e= self.max_iter: self.reset() raise StopIteration i, batch_size = self.iteration, self.batch_size batch_index = self.index[i * batch_size:(i + 1) * batch_size] batch = [self.dataset[i] for i in batch_index] x = np.array([example[0] for example in batch]) t = np.array([example[1] for example in batch]) self.iteration += 1 return x, t def next(self): return self.__next__() ","date":"2025-12-27","objectID":"/posts/%E6%B7%B1%E5%BA%A6%E5%AD%A6%E4%B9%A0%E5%9F%BA%E7%A1%80~%E8%87%AA%E5%88%B6%E6%A1%86%E6%9E%B6/:14:3","tags":null,"title":"深度学习基础~自制框架","uri":"/posts/%E6%B7%B1%E5%BA%A6%E5%AD%A6%E4%B9%A0%E5%9F%BA%E7%A1%80~%E8%87%AA%E5%88%B6%E6%A1%86%E6%9E%B6/#框架搭建-32"},{"categories":[],"collections":["深度学习"],"content":"59.4 准确率\r添加一个用于评估识别精度的函数 accuracy。 框架搭建\r在 dezero/functions.py 中添加 accuracy 函数 def accuracy(y, t): y, t = as_variable(y), as_variable(t) pred = y.data.argmax(axis=1).reshape(t.shape) result = (pred == t.data) acc = result.mean() return Variable(as_array(acc)) ","date":"2025-12-27","objectID":"/posts/%E6%B7%B1%E5%BA%A6%E5%AD%A6%E4%B9%A0%E5%9F%BA%E7%A1%80~%E8%87%AA%E5%88%B6%E6%A1%86%E6%9E%B6/:14:4","tags":null,"title":"深度学习基础~自制框架","uri":"/posts/%E6%B7%B1%E5%BA%A6%E5%AD%A6%E4%B9%A0%E5%9F%BA%E7%A1%80~%E8%87%AA%E5%88%B6%E6%A1%86%E6%9E%B6/#准确率"},{"categories":[],"collections":["深度学习"],"content":"59.4 准确率\r添加一个用于评估识别精度的函数 accuracy。 框架搭建\r在 dezero/functions.py 中添加 accuracy 函数 def accuracy(y, t): y, t = as_variable(y), as_variable(t) pred = y.data.argmax(axis=1).reshape(t.shape) result = (pred == t.data) acc = result.mean() return Variable(as_array(acc)) ","date":"2025-12-27","objectID":"/posts/%E6%B7%B1%E5%BA%A6%E5%AD%A6%E4%B9%A0%E5%9F%BA%E7%A1%80~%E8%87%AA%E5%88%B6%E6%A1%86%E6%9E%B6/:14:4","tags":null,"title":"深度学习基础~自制框架","uri":"/posts/%E6%B7%B1%E5%BA%A6%E5%AD%A6%E4%B9%A0%E5%9F%BA%E7%A1%80~%E8%87%AA%E5%88%B6%E6%A1%86%E6%9E%B6/#框架搭建-33"},{"categories":[],"collections":["深度学习"],"content":"59.5 文件下载\r添加一个下载文件的工具函数。 框架搭建\r在 dezero/utils.py 中添加 show_progress 函数和 get_file 函数。 def show_progress(block_num, block_size, total_size): bar_template = \"\\r[{}] {:.2f}%\" downloaded = block_num * block_size p = downloaded / total_size * 100 i = int(downloaded / total_size * 30) if p \u003e= 100.0: p = 100.0 if i \u003e= 30: i = 30 bar = \"#\" * i + \".\" * (30 - i) print(bar_template.format(bar, p), end='') def get_file(url, file_name=None): if file_name is None: file_name = url[url.rfind('/') + 1:] file_path = f'./{file_name}' if os.path.exists(file_path): return file_path print(\"Downloading: \" + file_name) try: urllib.request.urlretrieve(url, file_path, show_progress) except (Exception, KeyboardInterrupt) as e: if os.path.exists(file_path): os.remove(file_path) raise print(\" Done\") return file_path ","date":"2025-12-27","objectID":"/posts/%E6%B7%B1%E5%BA%A6%E5%AD%A6%E4%B9%A0%E5%9F%BA%E7%A1%80~%E8%87%AA%E5%88%B6%E6%A1%86%E6%9E%B6/:14:5","tags":null,"title":"深度学习基础~自制框架","uri":"/posts/%E6%B7%B1%E5%BA%A6%E5%AD%A6%E4%B9%A0%E5%9F%BA%E7%A1%80~%E8%87%AA%E5%88%B6%E6%A1%86%E6%9E%B6/#文件下载"},{"categories":[],"collections":["深度学习"],"content":"59.5 文件下载\r添加一个下载文件的工具函数。 框架搭建\r在 dezero/utils.py 中添加 show_progress 函数和 get_file 函数。 def show_progress(block_num, block_size, total_size): bar_template = \"\\r[{}] {:.2f}%\" downloaded = block_num * block_size p = downloaded / total_size * 100 i = int(downloaded / total_size * 30) if p \u003e= 100.0: p = 100.0 if i \u003e= 30: i = 30 bar = \"#\" * i + \".\" * (30 - i) print(bar_template.format(bar, p), end='') def get_file(url, file_name=None): if file_name is None: file_name = url[url.rfind('/') + 1:] file_path = f'./{file_name}' if os.path.exists(file_path): return file_path print(\"Downloading: \" + file_name) try: urllib.request.urlretrieve(url, file_path, show_progress) except (Exception, KeyboardInterrupt) as e: if os.path.exists(file_path): os.remove(file_path) raise print(\" Done\") return file_path ","date":"2025-12-27","objectID":"/posts/%E6%B7%B1%E5%BA%A6%E5%AD%A6%E4%B9%A0%E5%9F%BA%E7%A1%80~%E8%87%AA%E5%88%B6%E6%A1%86%E6%9E%B6/:14:5","tags":null,"title":"深度学习基础~自制框架","uri":"/posts/%E6%B7%B1%E5%BA%A6%E5%AD%A6%E4%B9%A0%E5%9F%BA%E7%A1%80~%E8%87%AA%E5%88%B6%E6%A1%86%E6%9E%B6/#框架搭建-34"},{"categories":[],"collections":["深度学习"],"content":"59.6 MNIST 数据集\r使用 Dataset 类构建一个 MNIST 数据集。 框架搭建\rclass MNIST(Dataset): def __init__(self, train=True, transform=Compose([Flatten(), ToFloat(), Normalize(0., 255.)]), target_transform=None): super().__init__(train, transform, target_transform) def prepare(self): url = 'https://ossci-datasets.s3.amazonaws.com/mnist/' # mirror site train_files = {'target': 'train-images-idx3-ubyte.gz', 'label': 'train-labels-idx1-ubyte.gz'} test_files = {'target': 't10k-images-idx3-ubyte.gz', 'label': 't10k-labels-idx1-ubyte.gz'} files = train_files if self.train else test_files data_path = get_file(url + files['target']) label_path = get_file(url + files['label']) self.data = self._load_data(data_path) self.label = self._load_label(label_path) def _load_label(self, filepath): with gzip.open(filepath, 'rb') as f: labels = np.frombuffer(f.read(), np.uint8, offset=8) return labels def _load_data(self, filepath): with gzip.open(filepath, 'rb') as f: data = np.frombuffer(f.read(), np.uint8, offset=16) data = data.reshape(-1, 1, 28, 28) return data def show(self, row=10, col=10): H, W = 28, 28 img = np.zeros((H * row, W * col)) for r in range(row): for c in range(col): img[r * H:(r + 1) * H, c * W:(c + 1) * W] = self.data[ np.random.randint(0, len(self.data) - 1)].reshape(H, W) plt.imshow(img, cmap='gray', interpolation='nearest') plt.axis('off') plt.show() @staticmethod def labels(): return {0: '0', 1: '1', 2: '2', 3: '3', 4: '4', 5: '5', 6: '6', 7: '7', 8: '8', 9: '9'} 示例\r信息\r到这里已经和 pytorch 的使用体验基本一样了。 ","date":"2025-12-27","objectID":"/posts/%E6%B7%B1%E5%BA%A6%E5%AD%A6%E4%B9%A0%E5%9F%BA%E7%A1%80~%E8%87%AA%E5%88%B6%E6%A1%86%E6%9E%B6/:14:6","tags":null,"title":"深度学习基础~自制框架","uri":"/posts/%E6%B7%B1%E5%BA%A6%E5%AD%A6%E4%B9%A0%E5%9F%BA%E7%A1%80~%E8%87%AA%E5%88%B6%E6%A1%86%E6%9E%B6/#mnist-数据集"},{"categories":[],"collections":["深度学习"],"content":"59.6 MNIST 数据集\r使用 Dataset 类构建一个 MNIST 数据集。 框架搭建\rclass MNIST(Dataset): def __init__(self, train=True, transform=Compose([Flatten(), ToFloat(), Normalize(0., 255.)]), target_transform=None): super().__init__(train, transform, target_transform) def prepare(self): url = 'https://ossci-datasets.s3.amazonaws.com/mnist/' # mirror site train_files = {'target': 'train-images-idx3-ubyte.gz', 'label': 'train-labels-idx1-ubyte.gz'} test_files = {'target': 't10k-images-idx3-ubyte.gz', 'label': 't10k-labels-idx1-ubyte.gz'} files = train_files if self.train else test_files data_path = get_file(url + files['target']) label_path = get_file(url + files['label']) self.data = self._load_data(data_path) self.label = self._load_label(label_path) def _load_label(self, filepath): with gzip.open(filepath, 'rb') as f: labels = np.frombuffer(f.read(), np.uint8, offset=8) return labels def _load_data(self, filepath): with gzip.open(filepath, 'rb') as f: data = np.frombuffer(f.read(), np.uint8, offset=16) data = data.reshape(-1, 1, 28, 28) return data def show(self, row=10, col=10): H, W = 28, 28 img = np.zeros((H * row, W * col)) for r in range(row): for c in range(col): img[r * H:(r + 1) * H, c * W:(c + 1) * W] = self.data[ np.random.randint(0, len(self.data) - 1)].reshape(H, W) plt.imshow(img, cmap='gray', interpolation='nearest') plt.axis('off') plt.show() @staticmethod def labels(): return {0: '0', 1: '1', 2: '2', 3: '3', 4: '4', 5: '5', 6: '6', 7: '7', 8: '8', 9: '9'} 示例\r信息\r到这里已经和 pytorch 的使用体验基本一样了。 ","date":"2025-12-27","objectID":"/posts/%E6%B7%B1%E5%BA%A6%E5%AD%A6%E4%B9%A0%E5%9F%BA%E7%A1%80~%E8%87%AA%E5%88%B6%E6%A1%86%E6%9E%B6/:14:6","tags":null,"title":"深度学习基础~自制框架","uri":"/posts/%E6%B7%B1%E5%BA%A6%E5%AD%A6%E4%B9%A0%E5%9F%BA%E7%A1%80~%E8%87%AA%E5%88%B6%E6%A1%86%E6%9E%B6/#框架搭建-35"},{"categories":[],"collections":["深度学习"],"content":"59.6 MNIST 数据集\r使用 Dataset 类构建一个 MNIST 数据集。 框架搭建\rclass MNIST(Dataset): def __init__(self, train=True, transform=Compose([Flatten(), ToFloat(), Normalize(0., 255.)]), target_transform=None): super().__init__(train, transform, target_transform) def prepare(self): url = 'https://ossci-datasets.s3.amazonaws.com/mnist/' # mirror site train_files = {'target': 'train-images-idx3-ubyte.gz', 'label': 'train-labels-idx1-ubyte.gz'} test_files = {'target': 't10k-images-idx3-ubyte.gz', 'label': 't10k-labels-idx1-ubyte.gz'} files = train_files if self.train else test_files data_path = get_file(url + files['target']) label_path = get_file(url + files['label']) self.data = self._load_data(data_path) self.label = self._load_label(label_path) def _load_label(self, filepath): with gzip.open(filepath, 'rb') as f: labels = np.frombuffer(f.read(), np.uint8, offset=8) return labels def _load_data(self, filepath): with gzip.open(filepath, 'rb') as f: data = np.frombuffer(f.read(), np.uint8, offset=16) data = data.reshape(-1, 1, 28, 28) return data def show(self, row=10, col=10): H, W = 28, 28 img = np.zeros((H * row, W * col)) for r in range(row): for c in range(col): img[r * H:(r + 1) * H, c * W:(c + 1) * W] = self.data[ np.random.randint(0, len(self.data) - 1)].reshape(H, W) plt.imshow(img, cmap='gray', interpolation='nearest') plt.axis('off') plt.show() @staticmethod def labels(): return {0: '0', 1: '1', 2: '2', 3: '3', 4: '4', 5: '5', 6: '6', 7: '7', 8: '8', 9: '9'} 示例\r信息\r到这里已经和 pytorch 的使用体验基本一样了。 ","date":"2025-12-27","objectID":"/posts/%E6%B7%B1%E5%BA%A6%E5%AD%A6%E4%B9%A0%E5%9F%BA%E7%A1%80~%E8%87%AA%E5%88%B6%E6%A1%86%E6%9E%B6/:14:6","tags":null,"title":"深度学习基础~自制框架","uri":"/posts/%E6%B7%B1%E5%BA%A6%E5%AD%A6%E4%B9%A0%E5%9F%BA%E7%A1%80~%E8%87%AA%E5%88%B6%E6%A1%86%E6%9E%B6/#示例-20"},{"categories":[],"collections":["深度学习"],"content":"第 60 节 支持 GPU\r","date":"2025-12-27","objectID":"/posts/%E6%B7%B1%E5%BA%A6%E5%AD%A6%E4%B9%A0%E5%9F%BA%E7%A1%80~%E8%87%AA%E5%88%B6%E6%A1%86%E6%9E%B6/:15:0","tags":null,"title":"深度学习基础~自制框架","uri":"/posts/%E6%B7%B1%E5%BA%A6%E5%AD%A6%E4%B9%A0%E5%9F%BA%E7%A1%80~%E8%87%AA%E5%88%B6%E6%A1%86%E6%9E%B6/#支持-gpu"},{"categories":["艺术品鉴"],"collections":["游戏作品"],"content":"第 1 节 概述\r在本人玩过的全部游戏中，很少有游戏能能像「异度神剑 3」 这般拥有如此深刻的主题。正因如此，本人决定写一篇博客来讲述这个故事。 故事发生的大陆有两大军事国家—「科维斯」与「安格努斯」，这里生活的人们为了活下去需要名为“命火”的东西。所谓“命火”便是生命之火，夺取他人的生命便能变作自己的生命，而失去所有的“命火”，便会化作遗骸。 在这里，人的寿命是 10 年，从出生起以一期、二期区分，最后到十期。在 10 年中，要不断战斗。严格来说，很少有人能历经 10 年战斗，绝大部分人都在迎来第 10 年前，殒命于战场之中。而平安迎来生命终结之人，将会被授予一项名誉，那就是成人仪式（由女王施展）。人们认为生命自女王处诞生，而最终回归女王的怀抱，便是圆满。为此，所有人都为了接受成人仪式而活，为了圆满结束这 10 年而战，这一切是理所当然的。 而事实上，人们从「摇篮」（成长模组）中诞生，10 年寿命大致相当于人类的 10 岁到 20 岁的范围，因而在这个世界中便没有性别、结婚、生育这些概念，所有人都是作为士兵接受训练并成为战士。 因为一些机缘巧合，来自两大国家的 6 名主角，获得了「衔尾石」，成为了「衔尾蛇」，来到了「都市」，了解到“命火”其实是「梅比乌斯」的食粮，生命消逝瞬间的求生欲望，对于梅比乌斯而言，简直是无上的美味。世界停留在了「永远的当下」，而当主角们击败了这一切的始作俑者后，时间开始流动。 以上大致就是整个游戏所讲述的剧情，游戏的剧情其实远不止上面所说的这些，但其他的一些事项与本篇博客关联性并不大，所以便不再讲述。 ","date":"2025-12-07","objectID":"/posts/%E5%BC%82%E5%BA%A6%E7%A5%9E%E5%89%913%E5%91%BD%E7%81%AB%E4%B9%8B%E5%A4%96%E6%88%91%E4%BB%AC%E4%B8%BA%E4%BD%95%E8%80%8C%E6%B4%BB/:1:0","tags":null,"title":"异度神剑3：命火之外，我们为何而活？","uri":"/posts/%E5%BC%82%E5%BA%A6%E7%A5%9E%E5%89%913%E5%91%BD%E7%81%AB%E4%B9%8B%E5%A4%96%E6%88%91%E4%BB%AC%E4%B8%BA%E4%BD%95%E8%80%8C%E6%B4%BB/#概述"},{"categories":["艺术品鉴"],"collections":["游戏作品"],"content":"第 2 节 被设计的人生，被定义的人生价值\r在「异度神剑 3」的世界中，科维斯与安格努斯的士兵们从出生那刻起就明白自己的任务，那便是战斗到生命最后一刻，但是这一切是被设计好的，因为他们坚信： “命火”是维持生命的唯一来源 只要不断杀戮，就能让本国的“命火之钟”积累能量 能活到“成人仪式”是一种至高荣耀 引用\r诺亚：不是因为期望战斗而选择战斗，而是因为明天的性命近在眼前，所以不得不战斗。无论失去，抑或是被剥夺，一切都只是结果而已，我们只能以战求生。如果你知道是谁决定了这一切，请告诉我吧，告诉我那是谁，告诉我此人身在何处。 但是这一切是被「梅比乌斯」设计好的，因为梅比乌斯们为了获得永恒的生命，需要“命火”作为食粮。 引用\r莫妮卡：你们作为士兵接受训练并杀害彼此世界的士兵，夺取他们的生命，被夺走的生命注入火之钟，而那些生命，就会成为梅比乌斯的食粮。为了维系自己的存在，梅比乌斯对生命的需求片刻不止，他们将那些生命再生，新创造出来的就是这个。你们的生命只是为了梅比乌斯的生存，就算死亡后成为遗骸，也会经历成千上万次再生，这就是枷锁。士兵们被枷锁囚禁，其命运就是不断厮杀。被杀之人在死亡瞬间就会变成新的自己，失去以前的记忆，从主堡的摇篮中诞生。 而正是因为人生被设计成只能战斗，所以衡量人的价值的标准就是战斗能力的高低，除此以外的任何的事情都会被定义成「不务正业」。 约兰是「科维斯」那边主角团幼年的朋友，因为不擅长战斗所以一直充当治疗辅助的职业，在幼年时的一次遭遇中，他牺牲了自己救下了自己的朋友（也就是主角团的一员）。约兰很擅长做手工雕刻，可以说在艺术方面有着绝佳的天赋，但是因为只有一种衡量标准，他的这项天赋并不被广泛认可。 引用\r优妮：那是什么，兰兹？ 约兰：这是优妮，还有这是诺亚。 诺亚：做得真好啊, 好厉害啊约兰。 诺亚：我们每天不是挥剑就是开枪对吧？可是你真的好厉害啊。 兰兹：喂这是谁啊？有长成这样的人吗？特别帅呢。 约兰：那是—是—是我。 优妮\u0026兰兹：这是你吗？完全不像嘛。 约兰：我以后就想成为这样的人。 事实上，为了更好的收获“命火”，人们的寿命被精细地计算过。在「摇篮」中诞生的人们，寿命从人类的 10 岁到 20 岁，没有性别、结婚、生育的概念。年轻的生命正是生产“命火”的最好工具，生来便参与战斗，直到 20 岁奉献完一生后便会被“成人仪式”。 梅比乌斯们便是社会上的「食利阶层」，人类社会从古至今皆是如此，变化的只有社会的生产力。 在古代，社会生产力地下，梅比乌斯们便是皇权和士大夫，他们享用并支配资源。芸芸众生遵循着“士农工商”的阶级划分，从来也不是因为梅比乌斯们认为「农」这个阶层高贵，而是因为社会生产力地下，农民提供粮食，这便是梅比乌斯们赖以生存的“命火”。 在现代，工业得到了发展，打工人便成了“命火 ”的提供者，打工人将“时间”和“健康”转变成“命火”，承受高压与长时间的工作、高昂的房价与生活成本，仅有的闲暇也会被各种“奶头乐”吸走。而梅比乌斯们呢，手上的资源可以升值、工作可以雇人完成，有时间健身、有营养团队和私人医生保养、有资源延长寿命。 而在未来，也许情况会更加残酷。AI 会替代掉绝大多数“可量化的劳动”而社会上会出现大量的“无用之人”，而梅比乌斯们手中的资源却能保值。这意味着普通人的价值会快速缩水，而食利阶层为了延长寿命、体验人生，一定会推进医疗技术和服务业的发展。那么显然，社会上的无用之人可能会被迫参与医疗试验，或者跻身服务业，承担食利者不愿做的杂事。 那么既然未来如此残酷，那么各位，我们便会面临与游戏主角相同的抉择，如果时间可以在这个瞬间静止，你会选择「永远的当下」还是选择「崭新的未来」，这一选择其实并不简单。 ","date":"2025-12-07","objectID":"/posts/%E5%BC%82%E5%BA%A6%E7%A5%9E%E5%89%913%E5%91%BD%E7%81%AB%E4%B9%8B%E5%A4%96%E6%88%91%E4%BB%AC%E4%B8%BA%E4%BD%95%E8%80%8C%E6%B4%BB/:2:0","tags":null,"title":"异度神剑3：命火之外，我们为何而活？","uri":"/posts/%E5%BC%82%E5%BA%A6%E7%A5%9E%E5%89%913%E5%91%BD%E7%81%AB%E4%B9%8B%E5%A4%96%E6%88%91%E4%BB%AC%E4%B8%BA%E4%BD%95%E8%80%8C%E6%B4%BB/#被设计的人生被定义的人生价值"},{"categories":["艺术品鉴"],"collections":["游戏作品"],"content":"第 3 节 因为命中注定？\r为什么游戏世界中千千万万的人，最终只有主角六人能成为反抗的核心？是因为命中注定吗？ ","date":"2025-12-07","objectID":"/posts/%E5%BC%82%E5%BA%A6%E7%A5%9E%E5%89%913%E5%91%BD%E7%81%AB%E4%B9%8B%E5%A4%96%E6%88%91%E4%BB%AC%E4%B8%BA%E4%BD%95%E8%80%8C%E6%B4%BB/:3:0","tags":null,"title":"异度神剑3：命火之外，我们为何而活？","uri":"/posts/%E5%BC%82%E5%BA%A6%E7%A5%9E%E5%89%913%E5%91%BD%E7%81%AB%E4%B9%8B%E5%A4%96%E6%88%91%E4%BB%AC%E4%B8%BA%E4%BD%95%E8%80%8C%E6%B4%BB/#因为命中注定"},{"categories":["艺术品鉴"],"collections":["游戏作品"],"content":"3.1 工具\r引用\r衔尾蛇替补 A：最多也就能再活个一两年吧哪有未来可言。 衔尾蛇替补 B：为什么范戴姆先生要把衔尾蛇之力交给这种人。 衔尾蛇替补 C：夺走我们的衔尾蛇满足了吗？ 同一时代最多出现 6 位衔尾蛇，也就是说，当范戴姆先生把衔尾蛇之力交给主角 6 人时，原本在都市中的预备衔尾蛇成员们便失去了资格，这便是主角 6 人的机遇。 在游戏世界中，能抗衡梅比乌斯的只有衔尾蛇，这是唯一能够改变现状的“工具”，主角团能成功，是因为他们恰好拥有了时代最关键的工具。 同样，人类历史上的各种反抗，也都需要工具，没有工具，便没有与系统对抗的能力。 在古代，工具可以是冷兵器 在工业时代，工具可以是蒸汽机 在互联网时代，工具可以是信息扩散 而在现在，普通人最重要的“衔尾蛇之石”，就是 AI 芸芸众生一定是忙碌的，一定没有时间也没有健康，仅有的闲暇也会成迷于奶头乐，只有这样他们才会没有时间质疑、反抗或思考。 只有借助 AI 才能在极短时间内学会一个新的知识点，快速处理掉简单的工作，这样我们才会有闲暇去做真正想做的事情，才能形成自己的资本原始积累。 ","date":"2025-12-07","objectID":"/posts/%E5%BC%82%E5%BA%A6%E7%A5%9E%E5%89%913%E5%91%BD%E7%81%AB%E4%B9%8B%E5%A4%96%E6%88%91%E4%BB%AC%E4%B8%BA%E4%BD%95%E8%80%8C%E6%B4%BB/:3:1","tags":null,"title":"异度神剑3：命火之外，我们为何而活？","uri":"/posts/%E5%BC%82%E5%BA%A6%E7%A5%9E%E5%89%913%E5%91%BD%E7%81%AB%E4%B9%8B%E5%A4%96%E6%88%91%E4%BB%AC%E4%B8%BA%E4%BD%95%E8%80%8C%E6%B4%BB/#工具"},{"categories":["艺术品鉴"],"collections":["游戏作品"],"content":"3.2 同理心\r引用\r诺亚：我很害怕，这个必须战斗才能存活的世界让我很害怕，只能活在其中的我们让我害怕。 里克：不愧是里克看中的男子汉，这把刀的确拥有极其强大的力量，只消一念之差就足以夺走万千性命，所以只有知晓了它的恐怖才有资格拥有。 引用\r弥央：漫无目的还怎么给生命送行啊！再多活一天，再多活一分钟，这一路上我送行了不知多少抱着这种愿望消失的同伴，这就是我们的日常。因为我们只能这样做，所以我们坚信这就是真相，每天—每天—我们费尽千辛万苦到达都市，见到了完全不一样的世界。我真的好羡慕，只要将我们的 10 年翻上几倍竟然就会有如此大的变化。我们还看到了新生命的诞生—握住这根手指的小手。我明白你们想要守护这些的心情，如果这就是你说的“当下” 我并不打算去毁灭，但是也请让我们一同去守护它，守护同伴们的生命，守护那可能延续的未来。 主角团中的诺亚和弥央是两名“送行者”，他们亲眼见证一波又一波同伴离去，更重要的是他们的好友为了让他们活下去都选择了自我牺牲，这些经历让他们成为剧中最具“人味”的角色，他们懂得生命的重量、能感受痛苦与失去、富有同理心因而得以避免走向 N 与 M 的结局。 引用\r莫妮卡：但是都市里也有许多不欢迎这一情况发生的人，活在当下就好，就算世界的某个角落发生了战争，只要藏起来挺过去就好。 兰兹：他们觉得我们的生命无所谓吗？ 莫妮卡：只要没有瓜葛只要不曾相遇，那就和不存在没什么两样。 诺亚：无论世界的某处发生什么。 莫妮卡：抱着这种想法的人有很多。 弥央：那不就和梅比乌斯一样吗？两者之间的区别，只在于是否直接干涉而已。 这世上多的是事不关己、岁月静好之人，能对他人所受苦难感同身受、倾囊相助的“抱薪者”终究是少数。独善其身之人众多，而又有几人发达之后能够兼济天下的呢。但是，能推动时代前进的往往是那些真正经历过痛苦、理解他人处境的人。他们能看见别人看不见的东西，也能承受别人承受不来的压力。 ","date":"2025-12-07","objectID":"/posts/%E5%BC%82%E5%BA%A6%E7%A5%9E%E5%89%913%E5%91%BD%E7%81%AB%E4%B9%8B%E5%A4%96%E6%88%91%E4%BB%AC%E4%B8%BA%E4%BD%95%E8%80%8C%E6%B4%BB/:3:2","tags":null,"title":"异度神剑3：命火之外，我们为何而活？","uri":"/posts/%E5%BC%82%E5%BA%A6%E7%A5%9E%E5%89%913%E5%91%BD%E7%81%AB%E4%B9%8B%E5%A4%96%E6%88%91%E4%BB%AC%E4%B8%BA%E4%BD%95%E8%80%8C%E6%B4%BB/#同理心"},{"categories":["艺术品鉴"],"collections":["游戏作品"],"content":"3.3 际遇\r引用\rZ：人生在世前路总有分歧，是右—是左—如此选择有何意义？是希望—是绝望—周而复始历经无数抉择，造就人—造就你。满足于结果自是最好，但若是胸怀不忿又将如何抒发？是一蹶不振徒留悲叹—或怒火中烧心生愤恨—会否也曾期许时光不再流动？只愿“当下永驻”，跨越成人—跨越此世定理仍绽放光芒。 主角团并不是第一次反抗，在无尽的轮回中，他们已经失败了无数次，只是这一次，因为命运的偶然与奇迹，他们成功了。 引用\r诺亚：运气，比较好吧。 N：运气？ 诺亚：也可以叫它际遇，我啊，遇见了各种各样的人—兰兹、优妮、泰恩、圣奈、里克、玛娜娜，身边有那么多的同伴，而且，我还遇见了弥央。其实只差那么一点啊，我跟你之间。如果我站在你的位置，一定也会选择相同的道路。 N：相同的道路？ 诺亚：没错—但是现在不一样了。弥央和同伴们—不仅如此，还有另一个弥央和另一个我—是我遇见的所有人将我塑造成现在的模样，是大家让我迈向未来。 同时，主角团们从来也没有觉得自己是“天选之人”，与梅比乌斯们的对抗从来就不是一代人能够完成的，每一代人都有自己的梅比乌斯们。同样，衔尾蛇也不会止步，在关键时刻，主角们真正做到了不迷恋衔尾蛇之力，勇于舍弃，为他人开拓未来。 引用\r冈度：你们几个要怎么办啊？！ 诺亚：应该会找个时机投降吧。勇于舍弃，你不是说过吗？ 冈度：那—那是指— 诺亚：现在就是时候了。衔尾蛇不会在我们这代止步，你不是候补吗？ 冈度：诺亚—你— 诺亚：所以你要继承下去。 有些人反对特权，不是因为讨厌特权，而是因为特权不在自己手里。一旦轮到他们掌握特权，他们便会比任何人都紧抓不放。能够激流勇退、深藏功名之人又有多少呢。 ","date":"2025-12-07","objectID":"/posts/%E5%BC%82%E5%BA%A6%E7%A5%9E%E5%89%913%E5%91%BD%E7%81%AB%E4%B9%8B%E5%A4%96%E6%88%91%E4%BB%AC%E4%B8%BA%E4%BD%95%E8%80%8C%E6%B4%BB/:3:3","tags":null,"title":"异度神剑3：命火之外，我们为何而活？","uri":"/posts/%E5%BC%82%E5%BA%A6%E7%A5%9E%E5%89%913%E5%91%BD%E7%81%AB%E4%B9%8B%E5%A4%96%E6%88%91%E4%BB%AC%E4%B8%BA%E4%BD%95%E8%80%8C%E6%B4%BB/#际遇"},{"categories":["艺术品鉴"],"collections":["游戏作品"],"content":"第 4 节 什么是崭新的未来？\r引用\r约兰：我想要双翼啊，我想要乘着赞美的劲风能在蓝天上自由翱翔的双翼。鸟儿怎么可能理解蚯蚓的心境呢！ 诺亚：也许我们确实是鸟儿，但这并不是因为我们强大，只是因为我们无法选择。因为我们身处在这样的世界，而世界只允许我们拥有一个姿态。我们必须改变它，为了弱小的我们。 引用\r克里斯：你们想要前往的，是只有强者能抵达的地平线，你们的话语是专属于强者的—胜利者理论。那么弱小之人又该怎么办？准备把他们置之脑后吗？还是要审判他们的一切，终结他们的生命呢？你们真的相信，解放始源就能令世界重生吗？女王大人应该没有说谎，可同时也没有确凿的证据。如果—如果世界并未像女王所说的那样再生，而是直接被消灭了呢？如果女王所说的只是幻想该怎么办？为了这种甜言蜜语就要赌上性命吗？这份责任由谁来承担？ 克里斯是一个看清艾欧尼翁送行仪式是“虚假死亡”的人，他很有智慧，也是一个真正的强者，他洞悉了这世界的虚妄。但他又很慈悲，他知道弱者需要这种自我欺骗的“永恒”，因为改变停滞的现在需要智慧、勇气和力量，这是强者才能拥有的东西。 引用\r诺亚：你当时问我“还要继续前进吗”，现在我能作出回答了。为了把最后的瞬间珍藏于心，我会不断前进，直到天涯海角。当最后一刻来临时，回顾人生能露出笑容。正因为大家想在生命的最后露出笑容，所以才会在各自的人生中做出选择，选择未来的自己。但是—人也会因为无法做出选择而逐渐崩溃、挣扎、痛苦，在得不出答案的迷茫中摸索。这得不出答案的世界毫无疑问是错误的。我们能做的事十分有限，但相信这一丝可能的思念确确实实地存在。我相信—这份真实的思念。 那么，回到开头，如果在未来，AI 会替代掉绝大多数“可量化的劳动”而社会上会出现大量的“无用之人”，被迫参与医疗试验，或者跻身服务业，承担食利者不愿做的杂事，我们还应该继续前进吗？是选择「永远的当下」还是「崭新的未来」呢？抵制时代的发展是一种仁慈吗？ 引用\r克里斯：梅比乌斯，只能存在于这个世界。强大—但同样脆弱，正因为脆弱才会寻求“当下”，会对迈向未来感到恐惧。纵是记忆消逝，仍会留下思念，将思念寄托于旋律，便能传入心间。反映出这一点的，正是都市。诞生于都市的人们，他们的生命并不存在于始源之中，而他们的存在昭示着，终将诞生的未来。他们—就是希望。 梅比乌斯们同样也会对迈向未来感到恐惧，恐惧 AI 的恰恰是这个时代的“老钱”们，所以他们才会给予其巨额的投资，以期望在未来能分一杯羹，不被“新钱”们取代。而生于未来的新生代们，他们也会找到属于自己的新的出路，生生不息才是人类的未来。 引用\r莫妮卡：这是遗骸上的东西吗？你可不在那边哦？你所拥有的是“现在”和“未来”，过去的你并不是你。现在的你并不是“被创造出来的”，但未来的你，可以由你自己来创造。只要你想你可以成为任何你所期望的人，你拥有这样的力量—去成为你所期望的自己。 从古至今，人类总体上是越来越自由的，可支配的时间也是越来越多的，人生体验也是越来越丰富的。在这个 AI 时代，普通人第一次拥有了“改变自己命运”的工具。用它学习、用它成长、用它提升自己，然后将释放出来的时间投资在真正重要的事情上。 引用\r克里斯：诺亚，你—还要往前去吗？我觉得这里就好。 诺亚：克里斯？这里有什么好的？ 克里斯：谢谢你 “命火”是幻象，“永恒 ”是牢笼，“安稳”是虚假，而未来不是等来的，而是争取来的。 勇于舍弃，你不是说过吗？现在就是时候了。衔尾蛇不会在我们这代止步。 ","date":"2025-12-07","objectID":"/posts/%E5%BC%82%E5%BA%A6%E7%A5%9E%E5%89%913%E5%91%BD%E7%81%AB%E4%B9%8B%E5%A4%96%E6%88%91%E4%BB%AC%E4%B8%BA%E4%BD%95%E8%80%8C%E6%B4%BB/:4:0","tags":null,"title":"异度神剑3：命火之外，我们为何而活？","uri":"/posts/%E5%BC%82%E5%BA%A6%E7%A5%9E%E5%89%913%E5%91%BD%E7%81%AB%E4%B9%8B%E5%A4%96%E6%88%91%E4%BB%AC%E4%B8%BA%E4%BD%95%E8%80%8C%E6%B4%BB/#什么是崭新的未来"},{"categories":["玩转NAS"],"collections":["玩转NAS"],"content":"第 25 节 概述\r在项目建设中，为了及时掌握项目推进情况，项目承建人员需要定期汇报工作进展，而项目管理员需要将这些信息整合成日报、周报和月报。 项目数量较少时，联系各承建人员、收集并整理进展信息还不复杂。然而，当项目数量增多、规模扩大时，往往需要安排多人分级负责、逐层上报汇总。这种方式不仅会降低灵活性，还易导致信息传递滞后。因此，我们不禁思考：是否有一种项目进度管理工具，能够帮助我们更高效、更灵活地管理项目进展？ 因为工作上的原因，本人要参与几十个项目的管理工作，为了实现上述的效果，本人尝试部署了多款项目管理工具，并最终选择了 KanBoard。主要原因是： Github 上的各种开源项目管理工具都是英文为第一语言的，翻译这块做的不是很好，包括 KanBoard，交给承建人员使用时就需要考虑他们的适应性。 大部分的开源项目管理工具不支持 Webhook 和 API，为了自动化生成报表和消息提醒，这个功能是必备的。 KanBoard 本身的功能比较完善，可定制的内容比其他项目要多，并且支持插件市场，可以增加很多插件来完善其功能。此外，程序的 Bug 也比较少，自己魔改的成本比较低。 ","date":"2025-11-25","objectID":"/posts/%E4%B8%80%E4%B8%AA%E7%AE%80%E6%B4%81%E4%BD%86%E5%8A%9F%E8%83%BD%E5%85%A8%E9%9D%A2%E7%9A%84%E9%A1%B9%E7%9B%AE%E8%BF%9B%E5%BA%A6%E7%AE%A1%E7%90%86%E5%B7%A5%E5%85%B7-kanboard/:1:0","tags":null,"title":"一个简洁但功能全面的项目进度管理工具 KanBoard","uri":"/posts/%E4%B8%80%E4%B8%AA%E7%AE%80%E6%B4%81%E4%BD%86%E5%8A%9F%E8%83%BD%E5%85%A8%E9%9D%A2%E7%9A%84%E9%A1%B9%E7%9B%AE%E8%BF%9B%E5%BA%A6%E7%AE%A1%E7%90%86%E5%B7%A5%E5%85%B7-kanboard/#概述"},{"categories":["玩转NAS"],"collections":["玩转NAS"],"content":"第 26 节 部署\r这里同样使用 Docker 进行部署： docker run -itd --name KanBorad --restart always \\ -p 「port」:80 \\ -e DATABASE_URL=\"postgres://「user」:「password」@「host」:「port」/「db」\" \\ -e PLUGIN_INSTALLER=true \\ -v /「path」/data:/var/www/app/data \\ -v /「path」/plugins:/var/www/app/plugins \\ -v /「path」/Locale:/var/www/app/app/Locale:ro \\ -v /share/Web/KanBoard/Model:/var/www/app/app/Model:ro \\ -v /「path」/Template:/var/www/app/app/Template:ro \\ -v /share/Web/KanBoard/Controller:/var/www/app/app/Controller:ro \\ -v /「path」/css:/var/www/app/assets/css:ro \\ -v /「path」/js:/var/www/app/assets/js:ro \\ kanboard/kanboard:latest 其中： DATABASE_URL：可以选择PostgreSQL或者MySQL(MariaDB)，不过我实测下来，我使用的MariaDB 在容器初始化的时候会有些问题，导致无法正常部署。 PLUGIN_INSTALLER：表示可以安装插件，如果想要更好的体验，插件还是需要的。 data和plugins目录时官方推荐的挂载目录，Locale、Model、Template、Controller、css和js目录是我个人的挂载目录。KanBoard 是 PHP 项目： Locale和项目汉化相关，将原项目中的英文翻译成中文，因为原先的中文汉化有种机翻的味道，有些内容明明是中文却看不懂，所以需要重新翻译。 Model和Controller定义了数据和控制器，可以在修改过程中参考。 Template是项目页面的模板，修改模板可以控制页面上显示的内容，可以把一些不常见的或者不想让用户使用的功能删除。 css和js设计页面的样式和部分逻辑，本人用到的不是很多，主要是因为安装了插件以后，在plugins里头也可以修改样式，并且优先级会高一些。 警告\rLocale、Template、css和js目录需要先建立容器将里面的内容手动复制出来才行，不管有没有ro，只要映射了目录，目录内就不会生成文件了。 ","date":"2025-11-25","objectID":"/posts/%E4%B8%80%E4%B8%AA%E7%AE%80%E6%B4%81%E4%BD%86%E5%8A%9F%E8%83%BD%E5%85%A8%E9%9D%A2%E7%9A%84%E9%A1%B9%E7%9B%AE%E8%BF%9B%E5%BA%A6%E7%AE%A1%E7%90%86%E5%B7%A5%E5%85%B7-kanboard/:2:0","tags":null,"title":"一个简洁但功能全面的项目进度管理工具 KanBoard","uri":"/posts/%E4%B8%80%E4%B8%AA%E7%AE%80%E6%B4%81%E4%BD%86%E5%8A%9F%E8%83%BD%E5%85%A8%E9%9D%A2%E7%9A%84%E9%A1%B9%E7%9B%AE%E8%BF%9B%E5%BA%A6%E7%AE%A1%E7%90%86%E5%B7%A5%E5%85%B7-kanboard/#部署"},{"categories":["玩转NAS"],"collections":["玩转NAS"],"content":"第 27 节 主要功能\r","date":"2025-11-25","objectID":"/posts/%E4%B8%80%E4%B8%AA%E7%AE%80%E6%B4%81%E4%BD%86%E5%8A%9F%E8%83%BD%E5%85%A8%E9%9D%A2%E7%9A%84%E9%A1%B9%E7%9B%AE%E8%BF%9B%E5%BA%A6%E7%AE%A1%E7%90%86%E5%B7%A5%E5%85%B7-kanboard/:3:0","tags":null,"title":"一个简洁但功能全面的项目进度管理工具 KanBoard","uri":"/posts/%E4%B8%80%E4%B8%AA%E7%AE%80%E6%B4%81%E4%BD%86%E5%8A%9F%E8%83%BD%E5%85%A8%E9%9D%A2%E7%9A%84%E9%A1%B9%E7%9B%AE%E8%BF%9B%E5%BA%A6%E7%AE%A1%E7%90%86%E5%B7%A5%E5%85%B7-kanboard/#主要功能"},{"categories":["玩转NAS"],"collections":["玩转NAS"],"content":"27.1 权限\rKanBoard 在权限这块基本做的比较完善： 应用默认有三种角色：超级管理员、管理员和用户。 超级管理员：可以理解成是后台人员，可以对应用的设置进行修改。 管理员：可以理解成是项目管理人员，可以对「项目」以及项目下的「任务」进行修改。 用户：可以理解成是项目承建人员，可以对「任务」进行修改。 ","date":"2025-11-25","objectID":"/posts/%E4%B8%80%E4%B8%AA%E7%AE%80%E6%B4%81%E4%BD%86%E5%8A%9F%E8%83%BD%E5%85%A8%E9%9D%A2%E7%9A%84%E9%A1%B9%E7%9B%AE%E8%BF%9B%E5%BA%A6%E7%AE%A1%E7%90%86%E5%B7%A5%E5%85%B7-kanboard/:3:1","tags":null,"title":"一个简洁但功能全面的项目进度管理工具 KanBoard","uri":"/posts/%E4%B8%80%E4%B8%AA%E7%AE%80%E6%B4%81%E4%BD%86%E5%8A%9F%E8%83%BD%E5%85%A8%E9%9D%A2%E7%9A%84%E9%A1%B9%E7%9B%AE%E8%BF%9B%E5%BA%A6%E7%AE%A1%E7%90%86%E5%B7%A5%E5%85%B7-kanboard/#权限"},{"categories":["玩转NAS"],"collections":["玩转NAS"],"content":"27.2 常用插件\r使用超级管理员账户可以配置项目的插件，我安装的插件如下： Customizer主要用来设置主题，安装以后可以在plugins里头直接修改样式，里头的主题大多都一般，名为Breathe的主题看着还可以，可以以这个主题为模板，魔改自己的主题，我最终使用的就是这款主题。 警告\r我使用的这个版本的 Customizer 有一个 Bug，因为它覆盖了原来项目中的logout也就是退出登录的.php文件，而它新写的代码中有些问题，导致无法正常退出登录，需要手动修复后才可以。 Calendar主要用来生成项目日历，可以清晰展示每一天的任务。 metaMagik主要用来增加自定义字段，比如原来任务中不含有「任务进度」这个字段，新增这个字段以后，就可以在面板上显示这个字段了。 警告\r要注意的是，这个插件对于中文的支持不是很好，录入的时候，如果将名称（比如任务进度）以中文输入，将不会正常录入到数据库中，我采用的方法是先以英文名称的方式录入，再通过连接数据库找到记录手动修改为中文。表名是metadata_types，修改的列名是human_name和beauty_name。 里程碑：用来生成里程碑。 Gantt：用来生成甘特图。 Bigboard：将勾选的多个项目聚合到一块，生成项目总览。 EnableAttachmentRenaming可以对上传的附件进行重命名。 信息\r上面这些插件对于中文的支持都不是很好，如果想要愉快的使用，都需要提前进行一定的汉化工作。 ","date":"2025-11-25","objectID":"/posts/%E4%B8%80%E4%B8%AA%E7%AE%80%E6%B4%81%E4%BD%86%E5%8A%9F%E8%83%BD%E5%85%A8%E9%9D%A2%E7%9A%84%E9%A1%B9%E7%9B%AE%E8%BF%9B%E5%BA%A6%E7%AE%A1%E7%90%86%E5%B7%A5%E5%85%B7-kanboard/:3:2","tags":null,"title":"一个简洁但功能全面的项目进度管理工具 KanBoard","uri":"/posts/%E4%B8%80%E4%B8%AA%E7%AE%80%E6%B4%81%E4%BD%86%E5%8A%9F%E8%83%BD%E5%85%A8%E9%9D%A2%E7%9A%84%E9%A1%B9%E7%9B%AE%E8%BF%9B%E5%BA%A6%E7%AE%A1%E7%90%86%E5%B7%A5%E5%85%B7-kanboard/#常用插件"},{"categories":["玩转NAS"],"collections":["玩转NAS"],"content":"第 28 节 项目\r信息\r从下面开始，展示的内容就是我魔改以后的 KanBoard 页面了，很多功能都经过了修改，不常用的功能也都删除了，所以和正常部署还是有很大的差别的，仅供参考。 项目管理人员（超级管理员或管理员权限）可以对项目进行编辑： 项目承建人员（用户权限）可以对项目进行浏览，但是不可以进行编辑： ","date":"2025-11-25","objectID":"/posts/%E4%B8%80%E4%B8%AA%E7%AE%80%E6%B4%81%E4%BD%86%E5%8A%9F%E8%83%BD%E5%85%A8%E9%9D%A2%E7%9A%84%E9%A1%B9%E7%9B%AE%E8%BF%9B%E5%BA%A6%E7%AE%A1%E7%90%86%E5%B7%A5%E5%85%B7-kanboard/:4:0","tags":null,"title":"一个简洁但功能全面的项目进度管理工具 KanBoard","uri":"/posts/%E4%B8%80%E4%B8%AA%E7%AE%80%E6%B4%81%E4%BD%86%E5%8A%9F%E8%83%BD%E5%85%A8%E9%9D%A2%E7%9A%84%E9%A1%B9%E7%9B%AE%E8%BF%9B%E5%BA%A6%E7%AE%A1%E7%90%86%E5%B7%A5%E5%85%B7-kanboard/#项目"},{"categories":["玩转NAS"],"collections":["玩转NAS"],"content":"28.1 新建项目\r新建项目的时候可以以其他项目作为模板快速搭建一个新的项目，或者建立一个全新的项目。 以其他项目为模板时，「项目配置」会自动配置成和模板项目一致，会生成一个空白的、没有任何任务的项目。 ","date":"2025-11-25","objectID":"/posts/%E4%B8%80%E4%B8%AA%E7%AE%80%E6%B4%81%E4%BD%86%E5%8A%9F%E8%83%BD%E5%85%A8%E9%9D%A2%E7%9A%84%E9%A1%B9%E7%9B%AE%E8%BF%9B%E5%BA%A6%E7%AE%A1%E7%90%86%E5%B7%A5%E5%85%B7-kanboard/:4:1","tags":null,"title":"一个简洁但功能全面的项目进度管理工具 KanBoard","uri":"/posts/%E4%B8%80%E4%B8%AA%E7%AE%80%E6%B4%81%E4%BD%86%E5%8A%9F%E8%83%BD%E5%85%A8%E9%9D%A2%E7%9A%84%E9%A1%B9%E7%9B%AE%E8%BF%9B%E5%BA%A6%E7%AE%A1%E7%90%86%E5%B7%A5%E5%85%B7-kanboard/#新建项目"},{"categories":["玩转NAS"],"collections":["玩转NAS"],"content":"28.2 项目配置\r创建好项目以后可以点击「项目配置」对项目进行更加细致的修改： 可编辑内容包括「修改项目」、「进度」、「阶段」、「权限」这几个部分，其中：进度、阶段和权限可以通过模板进行继承，只要设置好模板然后引用即可。 修改项目\r「修改项目」可以设置项目的「描述」、「项目负责人」和项目的「启动时间」和「结束时间」： 阶段和进度\r「阶段」是项目从立项到验收的一段段时间区间，比如可以设置成如下内容： 「进度」是某个任务从发布到完成的过程，比如可以设置成如下内容： 这样项目内的任务面板上就可以看到如下内容： 权限\r权限用于添加项目的参与者，包括「项目管理员」、「项目成员」和「项目观察者」，这些人员都需要提前注册账号，项目只对项目的参与者可见。 权限通过模板继承，所以选择模板时，应当尽可能选择承建单位相同的项目。 ","date":"2025-11-25","objectID":"/posts/%E4%B8%80%E4%B8%AA%E7%AE%80%E6%B4%81%E4%BD%86%E5%8A%9F%E8%83%BD%E5%85%A8%E9%9D%A2%E7%9A%84%E9%A1%B9%E7%9B%AE%E8%BF%9B%E5%BA%A6%E7%AE%A1%E7%90%86%E5%B7%A5%E5%85%B7-kanboard/:4:2","tags":null,"title":"一个简洁但功能全面的项目进度管理工具 KanBoard","uri":"/posts/%E4%B8%80%E4%B8%AA%E7%AE%80%E6%B4%81%E4%BD%86%E5%8A%9F%E8%83%BD%E5%85%A8%E9%9D%A2%E7%9A%84%E9%A1%B9%E7%9B%AE%E8%BF%9B%E5%BA%A6%E7%AE%A1%E7%90%86%E5%B7%A5%E5%85%B7-kanboard/#项目配置"},{"categories":["玩转NAS"],"collections":["玩转NAS"],"content":"28.2 项目配置\r创建好项目以后可以点击「项目配置」对项目进行更加细致的修改： 可编辑内容包括「修改项目」、「进度」、「阶段」、「权限」这几个部分，其中：进度、阶段和权限可以通过模板进行继承，只要设置好模板然后引用即可。 修改项目\r「修改项目」可以设置项目的「描述」、「项目负责人」和项目的「启动时间」和「结束时间」： 阶段和进度\r「阶段」是项目从立项到验收的一段段时间区间，比如可以设置成如下内容： 「进度」是某个任务从发布到完成的过程，比如可以设置成如下内容： 这样项目内的任务面板上就可以看到如下内容： 权限\r权限用于添加项目的参与者，包括「项目管理员」、「项目成员」和「项目观察者」，这些人员都需要提前注册账号，项目只对项目的参与者可见。 权限通过模板继承，所以选择模板时，应当尽可能选择承建单位相同的项目。 ","date":"2025-11-25","objectID":"/posts/%E4%B8%80%E4%B8%AA%E7%AE%80%E6%B4%81%E4%BD%86%E5%8A%9F%E8%83%BD%E5%85%A8%E9%9D%A2%E7%9A%84%E9%A1%B9%E7%9B%AE%E8%BF%9B%E5%BA%A6%E7%AE%A1%E7%90%86%E5%B7%A5%E5%85%B7-kanboard/:4:2","tags":null,"title":"一个简洁但功能全面的项目进度管理工具 KanBoard","uri":"/posts/%E4%B8%80%E4%B8%AA%E7%AE%80%E6%B4%81%E4%BD%86%E5%8A%9F%E8%83%BD%E5%85%A8%E9%9D%A2%E7%9A%84%E9%A1%B9%E7%9B%AE%E8%BF%9B%E5%BA%A6%E7%AE%A1%E7%90%86%E5%B7%A5%E5%85%B7-kanboard/#修改项目"},{"categories":["玩转NAS"],"collections":["玩转NAS"],"content":"28.2 项目配置\r创建好项目以后可以点击「项目配置」对项目进行更加细致的修改： 可编辑内容包括「修改项目」、「进度」、「阶段」、「权限」这几个部分，其中：进度、阶段和权限可以通过模板进行继承，只要设置好模板然后引用即可。 修改项目\r「修改项目」可以设置项目的「描述」、「项目负责人」和项目的「启动时间」和「结束时间」： 阶段和进度\r「阶段」是项目从立项到验收的一段段时间区间，比如可以设置成如下内容： 「进度」是某个任务从发布到完成的过程，比如可以设置成如下内容： 这样项目内的任务面板上就可以看到如下内容： 权限\r权限用于添加项目的参与者，包括「项目管理员」、「项目成员」和「项目观察者」，这些人员都需要提前注册账号，项目只对项目的参与者可见。 权限通过模板继承，所以选择模板时，应当尽可能选择承建单位相同的项目。 ","date":"2025-11-25","objectID":"/posts/%E4%B8%80%E4%B8%AA%E7%AE%80%E6%B4%81%E4%BD%86%E5%8A%9F%E8%83%BD%E5%85%A8%E9%9D%A2%E7%9A%84%E9%A1%B9%E7%9B%AE%E8%BF%9B%E5%BA%A6%E7%AE%A1%E7%90%86%E5%B7%A5%E5%85%B7-kanboard/:4:2","tags":null,"title":"一个简洁但功能全面的项目进度管理工具 KanBoard","uri":"/posts/%E4%B8%80%E4%B8%AA%E7%AE%80%E6%B4%81%E4%BD%86%E5%8A%9F%E8%83%BD%E5%85%A8%E9%9D%A2%E7%9A%84%E9%A1%B9%E7%9B%AE%E8%BF%9B%E5%BA%A6%E7%AE%A1%E7%90%86%E5%B7%A5%E5%85%B7-kanboard/#阶段和进度"},{"categories":["玩转NAS"],"collections":["玩转NAS"],"content":"28.2 项目配置\r创建好项目以后可以点击「项目配置」对项目进行更加细致的修改： 可编辑内容包括「修改项目」、「进度」、「阶段」、「权限」这几个部分，其中：进度、阶段和权限可以通过模板进行继承，只要设置好模板然后引用即可。 修改项目\r「修改项目」可以设置项目的「描述」、「项目负责人」和项目的「启动时间」和「结束时间」： 阶段和进度\r「阶段」是项目从立项到验收的一段段时间区间，比如可以设置成如下内容： 「进度」是某个任务从发布到完成的过程，比如可以设置成如下内容： 这样项目内的任务面板上就可以看到如下内容： 权限\r权限用于添加项目的参与者，包括「项目管理员」、「项目成员」和「项目观察者」，这些人员都需要提前注册账号，项目只对项目的参与者可见。 权限通过模板继承，所以选择模板时，应当尽可能选择承建单位相同的项目。 ","date":"2025-11-25","objectID":"/posts/%E4%B8%80%E4%B8%AA%E7%AE%80%E6%B4%81%E4%BD%86%E5%8A%9F%E8%83%BD%E5%85%A8%E9%9D%A2%E7%9A%84%E9%A1%B9%E7%9B%AE%E8%BF%9B%E5%BA%A6%E7%AE%A1%E7%90%86%E5%B7%A5%E5%85%B7-kanboard/:4:2","tags":null,"title":"一个简洁但功能全面的项目进度管理工具 KanBoard","uri":"/posts/%E4%B8%80%E4%B8%AA%E7%AE%80%E6%B4%81%E4%BD%86%E5%8A%9F%E8%83%BD%E5%85%A8%E9%9D%A2%E7%9A%84%E9%A1%B9%E7%9B%AE%E8%BF%9B%E5%BA%A6%E7%AE%A1%E7%90%86%E5%B7%A5%E5%85%B7-kanboard/#权限-1"},{"categories":["玩转NAS"],"collections":["玩转NAS"],"content":"第 29 节 任务\r项目管理人员（超级管理员或管理员）和项目承建人员（用户）可以对项目任务进行编辑： ","date":"2025-11-25","objectID":"/posts/%E4%B8%80%E4%B8%AA%E7%AE%80%E6%B4%81%E4%BD%86%E5%8A%9F%E8%83%BD%E5%85%A8%E9%9D%A2%E7%9A%84%E9%A1%B9%E7%9B%AE%E8%BF%9B%E5%BA%A6%E7%AE%A1%E7%90%86%E5%B7%A5%E5%85%B7-kanboard/:5:0","tags":null,"title":"一个简洁但功能全面的项目进度管理工具 KanBoard","uri":"/posts/%E4%B8%80%E4%B8%AA%E7%AE%80%E6%B4%81%E4%BD%86%E5%8A%9F%E8%83%BD%E5%85%A8%E9%9D%A2%E7%9A%84%E9%A1%B9%E7%9B%AE%E8%BF%9B%E5%BA%A6%E7%AE%A1%E7%90%86%E5%B7%A5%E5%85%B7-kanboard/#任务"},{"categories":["玩转NAS"],"collections":["玩转NAS"],"content":"29.1 新增任务\r点击任务面板中对应「阶段」和「进度」前面的+号，可以新增任务： 新增的任务对全部的项目参与者都是可见的，可以通过设置过滤器只显示指派给自己的项目： ","date":"2025-11-25","objectID":"/posts/%E4%B8%80%E4%B8%AA%E7%AE%80%E6%B4%81%E4%BD%86%E5%8A%9F%E8%83%BD%E5%85%A8%E9%9D%A2%E7%9A%84%E9%A1%B9%E7%9B%AE%E8%BF%9B%E5%BA%A6%E7%AE%A1%E7%90%86%E5%B7%A5%E5%85%B7-kanboard/:5:1","tags":null,"title":"一个简洁但功能全面的项目进度管理工具 KanBoard","uri":"/posts/%E4%B8%80%E4%B8%AA%E7%AE%80%E6%B4%81%E4%BD%86%E5%8A%9F%E8%83%BD%E5%85%A8%E9%9D%A2%E7%9A%84%E9%A1%B9%E7%9B%AE%E8%BF%9B%E5%BA%A6%E7%AE%A1%E7%90%86%E5%B7%A5%E5%85%B7-kanboard/#新增任务"},{"categories":["玩转NAS"],"collections":["玩转NAS"],"content":"29.2 任务配置\r点击上图框内部分可进行「快捷操作」，点击框外部分可以进入「任务详情」页面。 快捷操作\r点击卡片上的「下拉」按钮有以下快捷操作，点击「编辑」按钮可以快速「修改任务」： 任务详情\r点击其余部分进入「任务详情」页面，除了「快捷操作」以外的一些内容在这里使用： ","date":"2025-11-25","objectID":"/posts/%E4%B8%80%E4%B8%AA%E7%AE%80%E6%B4%81%E4%BD%86%E5%8A%9F%E8%83%BD%E5%85%A8%E9%9D%A2%E7%9A%84%E9%A1%B9%E7%9B%AE%E8%BF%9B%E5%BA%A6%E7%AE%A1%E7%90%86%E5%B7%A5%E5%85%B7-kanboard/:5:2","tags":null,"title":"一个简洁但功能全面的项目进度管理工具 KanBoard","uri":"/posts/%E4%B8%80%E4%B8%AA%E7%AE%80%E6%B4%81%E4%BD%86%E5%8A%9F%E8%83%BD%E5%85%A8%E9%9D%A2%E7%9A%84%E9%A1%B9%E7%9B%AE%E8%BF%9B%E5%BA%A6%E7%AE%A1%E7%90%86%E5%B7%A5%E5%85%B7-kanboard/#任务配置"},{"categories":["玩转NAS"],"collections":["玩转NAS"],"content":"29.2 任务配置\r点击上图框内部分可进行「快捷操作」，点击框外部分可以进入「任务详情」页面。 快捷操作\r点击卡片上的「下拉」按钮有以下快捷操作，点击「编辑」按钮可以快速「修改任务」： 任务详情\r点击其余部分进入「任务详情」页面，除了「快捷操作」以外的一些内容在这里使用： ","date":"2025-11-25","objectID":"/posts/%E4%B8%80%E4%B8%AA%E7%AE%80%E6%B4%81%E4%BD%86%E5%8A%9F%E8%83%BD%E5%85%A8%E9%9D%A2%E7%9A%84%E9%A1%B9%E7%9B%AE%E8%BF%9B%E5%BA%A6%E7%AE%A1%E7%90%86%E5%B7%A5%E5%85%B7-kanboard/:5:2","tags":null,"title":"一个简洁但功能全面的项目进度管理工具 KanBoard","uri":"/posts/%E4%B8%80%E4%B8%AA%E7%AE%80%E6%B4%81%E4%BD%86%E5%8A%9F%E8%83%BD%E5%85%A8%E9%9D%A2%E7%9A%84%E9%A1%B9%E7%9B%AE%E8%BF%9B%E5%BA%A6%E7%AE%A1%E7%90%86%E5%B7%A5%E5%85%B7-kanboard/#快捷操作"},{"categories":["玩转NAS"],"collections":["玩转NAS"],"content":"29.2 任务配置\r点击上图框内部分可进行「快捷操作」，点击框外部分可以进入「任务详情」页面。 快捷操作\r点击卡片上的「下拉」按钮有以下快捷操作，点击「编辑」按钮可以快速「修改任务」： 任务详情\r点击其余部分进入「任务详情」页面，除了「快捷操作」以外的一些内容在这里使用： ","date":"2025-11-25","objectID":"/posts/%E4%B8%80%E4%B8%AA%E7%AE%80%E6%B4%81%E4%BD%86%E5%8A%9F%E8%83%BD%E5%85%A8%E9%9D%A2%E7%9A%84%E9%A1%B9%E7%9B%AE%E8%BF%9B%E5%BA%A6%E7%AE%A1%E7%90%86%E5%B7%A5%E5%85%B7-kanboard/:5:2","tags":null,"title":"一个简洁但功能全面的项目进度管理工具 KanBoard","uri":"/posts/%E4%B8%80%E4%B8%AA%E7%AE%80%E6%B4%81%E4%BD%86%E5%8A%9F%E8%83%BD%E5%85%A8%E9%9D%A2%E7%9A%84%E9%A1%B9%E7%9B%AE%E8%BF%9B%E5%BA%A6%E7%AE%A1%E7%90%86%E5%B7%A5%E5%85%B7-kanboard/#任务详情"},{"categories":["玩转NAS"],"collections":["玩转NAS"],"content":"第 30 节 API\rKanBoard 的 API 可以通过如下网址访问。 API 有两种，一种是应用程序 API，一种是用户 API。应用程序 API 能够获取全部的信息，但是只有超级管理员可见。用户 API 能够访问用户自身授权的信息，使用用户自己的用户名和密码登录即可。 因为应用程序 API 功能更加全面一些，所以就直接使用应用程序 API了。 ","date":"2025-11-25","objectID":"/posts/%E4%B8%80%E4%B8%AA%E7%AE%80%E6%B4%81%E4%BD%86%E5%8A%9F%E8%83%BD%E5%85%A8%E9%9D%A2%E7%9A%84%E9%A1%B9%E7%9B%AE%E8%BF%9B%E5%BA%A6%E7%AE%A1%E7%90%86%E5%B7%A5%E5%85%B7-kanboard/:6:0","tags":null,"title":"一个简洁但功能全面的项目进度管理工具 KanBoard","uri":"/posts/%E4%B8%80%E4%B8%AA%E7%AE%80%E6%B4%81%E4%BD%86%E5%8A%9F%E8%83%BD%E5%85%A8%E9%9D%A2%E7%9A%84%E9%A1%B9%E7%9B%AE%E8%BF%9B%E5%BA%A6%E7%AE%A1%E7%90%86%E5%B7%A5%E5%85%B7-kanboard/#api"},{"categories":["玩转NAS"],"collections":["玩转NAS"],"content":"30.1 通用部分\r这里使用python调用 API 接口，通用的使用方法如下： import requests url = 'https://host:port/jsonrpc.php' username = \"jsonrpc\" password = \"xxxxx\" payload = { \"jsonrpc\": \"2.0\", \"method\": \"xxx\", \"id\": 1 \"params\":{} } response = requests.post( url, auth=(username, password), json=payload ) data = response.json() 不同的 API 接口差别在于payload不同。Kanboard 使用 JSON-RPC 协议，和其他使用 JSON-RPC 协议的应用程序使用方法相同。payload里的id由自己设定，主要用于异步通信的时候确定response和requests的对应关系。 ","date":"2025-11-25","objectID":"/posts/%E4%B8%80%E4%B8%AA%E7%AE%80%E6%B4%81%E4%BD%86%E5%8A%9F%E8%83%BD%E5%85%A8%E9%9D%A2%E7%9A%84%E9%A1%B9%E7%9B%AE%E8%BF%9B%E5%BA%A6%E7%AE%A1%E7%90%86%E5%B7%A5%E5%85%B7-kanboard/:6:1","tags":null,"title":"一个简洁但功能全面的项目进度管理工具 KanBoard","uri":"/posts/%E4%B8%80%E4%B8%AA%E7%AE%80%E6%B4%81%E4%BD%86%E5%8A%9F%E8%83%BD%E5%85%A8%E9%9D%A2%E7%9A%84%E9%A1%B9%E7%9B%AE%E8%BF%9B%E5%BA%A6%E7%AE%A1%E7%90%86%E5%B7%A5%E5%85%B7-kanboard/#通用部分"},{"categories":["玩转NAS"],"collections":["玩转NAS"],"content":"30.2 获取全部项目\rpayload = { \"jsonrpc\": \"2.0\", \"method\": \"getAllProjects\", \"id\": 1 } ","date":"2025-11-25","objectID":"/posts/%E4%B8%80%E4%B8%AA%E7%AE%80%E6%B4%81%E4%BD%86%E5%8A%9F%E8%83%BD%E5%85%A8%E9%9D%A2%E7%9A%84%E9%A1%B9%E7%9B%AE%E8%BF%9B%E5%BA%A6%E7%AE%A1%E7%90%86%E5%B7%A5%E5%85%B7-kanboard/:6:2","tags":null,"title":"一个简洁但功能全面的项目进度管理工具 KanBoard","uri":"/posts/%E4%B8%80%E4%B8%AA%E7%AE%80%E6%B4%81%E4%BD%86%E5%8A%9F%E8%83%BD%E5%85%A8%E9%9D%A2%E7%9A%84%E9%A1%B9%E7%9B%AE%E8%BF%9B%E5%BA%A6%E7%AE%A1%E7%90%86%E5%B7%A5%E5%85%B7-kanboard/#获取全部项目"},{"categories":["玩转NAS"],"collections":["玩转NAS"],"content":"30.3 设置项目的 MetaData\rpayload = { \"jsonrpc\": \"2.0\", \"method\": \"saveProjectMetadata\", \"id\": 1, \"params\": { \"project_id\": 1, \"values\": { key: value } } } ","date":"2025-11-25","objectID":"/posts/%E4%B8%80%E4%B8%AA%E7%AE%80%E6%B4%81%E4%BD%86%E5%8A%9F%E8%83%BD%E5%85%A8%E9%9D%A2%E7%9A%84%E9%A1%B9%E7%9B%AE%E8%BF%9B%E5%BA%A6%E7%AE%A1%E7%90%86%E5%B7%A5%E5%85%B7-kanboard/:6:3","tags":null,"title":"一个简洁但功能全面的项目进度管理工具 KanBoard","uri":"/posts/%E4%B8%80%E4%B8%AA%E7%AE%80%E6%B4%81%E4%BD%86%E5%8A%9F%E8%83%BD%E5%85%A8%E9%9D%A2%E7%9A%84%E9%A1%B9%E7%9B%AE%E8%BF%9B%E5%BA%A6%E7%AE%A1%E7%90%86%E5%B7%A5%E5%85%B7-kanboard/#设置项目的-metadata"},{"categories":["玩转NAS"],"collections":["玩转NAS"],"content":"30.4 获取项目的 MetaData\r可以一次获取全部的 MetaData： payload = { \"jsonrpc\": \"2.0\", \"method\": \"getProjectMetadata\", \"id\": 1, \"params\": { \"project_id\": project_id } } 或者获取指定名称的 MetaData： payload = { \"jsonrpc\": \"2.0\", \"method\": \"getProjectMetadataByName\", \"id\": 1, \"params\": { \"project_id\": 1, \"name\": key } } ","date":"2025-11-25","objectID":"/posts/%E4%B8%80%E4%B8%AA%E7%AE%80%E6%B4%81%E4%BD%86%E5%8A%9F%E8%83%BD%E5%85%A8%E9%9D%A2%E7%9A%84%E9%A1%B9%E7%9B%AE%E8%BF%9B%E5%BA%A6%E7%AE%A1%E7%90%86%E5%B7%A5%E5%85%B7-kanboard/:6:4","tags":null,"title":"一个简洁但功能全面的项目进度管理工具 KanBoard","uri":"/posts/%E4%B8%80%E4%B8%AA%E7%AE%80%E6%B4%81%E4%BD%86%E5%8A%9F%E8%83%BD%E5%85%A8%E9%9D%A2%E7%9A%84%E9%A1%B9%E7%9B%AE%E8%BF%9B%E5%BA%A6%E7%AE%A1%E7%90%86%E5%B7%A5%E5%85%B7-kanboard/#获取项目的-metadata"},{"categories":["玩转NAS"],"collections":["玩转NAS"],"content":"30.5 获取项目活动\rpayload = { \"jsonrpc\": \"2.0\", \"method\": \"getProjectActivity\", \"id\": 1, \"params\": { \"project_id\": project_id } } ","date":"2025-11-25","objectID":"/posts/%E4%B8%80%E4%B8%AA%E7%AE%80%E6%B4%81%E4%BD%86%E5%8A%9F%E8%83%BD%E5%85%A8%E9%9D%A2%E7%9A%84%E9%A1%B9%E7%9B%AE%E8%BF%9B%E5%BA%A6%E7%AE%A1%E7%90%86%E5%B7%A5%E5%85%B7-kanboard/:6:5","tags":null,"title":"一个简洁但功能全面的项目进度管理工具 KanBoard","uri":"/posts/%E4%B8%80%E4%B8%AA%E7%AE%80%E6%B4%81%E4%BD%86%E5%8A%9F%E8%83%BD%E5%85%A8%E9%9D%A2%E7%9A%84%E9%A1%B9%E7%9B%AE%E8%BF%9B%E5%BA%A6%E7%AE%A1%E7%90%86%E5%B7%A5%E5%85%B7-kanboard/#获取项目活动"},{"categories":["玩转NAS"],"collections":["玩转NAS"],"content":"30.6 获取任务\rpayload = { \"jsonrpc\": \"2.0\", \"method\": \"getTask\", \"id\": 1, \"params\": { \"task_id\": task_id } } ","date":"2025-11-25","objectID":"/posts/%E4%B8%80%E4%B8%AA%E7%AE%80%E6%B4%81%E4%BD%86%E5%8A%9F%E8%83%BD%E5%85%A8%E9%9D%A2%E7%9A%84%E9%A1%B9%E7%9B%AE%E8%BF%9B%E5%BA%A6%E7%AE%A1%E7%90%86%E5%B7%A5%E5%85%B7-kanboard/:6:6","tags":null,"title":"一个简洁但功能全面的项目进度管理工具 KanBoard","uri":"/posts/%E4%B8%80%E4%B8%AA%E7%AE%80%E6%B4%81%E4%BD%86%E5%8A%9F%E8%83%BD%E5%85%A8%E9%9D%A2%E7%9A%84%E9%A1%B9%E7%9B%AE%E8%BF%9B%E5%BA%A6%E7%AE%A1%E7%90%86%E5%B7%A5%E5%85%B7-kanboard/#获取任务"},{"categories":["玩转NAS"],"collections":["玩转NAS"],"content":"30.7 更新任务\rpayload = { \"jsonrpc\": \"2.0\", \"method\": \"updateTask\", \"id\": 1, \"params\": { \"id\": task_id, key: value, key2: value2 } } 可选的字段包括： title：标题 description：描述 date_started：开始日期，格式是YYYY-MM-DD HH:MM date_due：到期日期，格式是YYYY-MM-DD HH:MM owner_id：任务的指派人 ","date":"2025-11-25","objectID":"/posts/%E4%B8%80%E4%B8%AA%E7%AE%80%E6%B4%81%E4%BD%86%E5%8A%9F%E8%83%BD%E5%85%A8%E9%9D%A2%E7%9A%84%E9%A1%B9%E7%9B%AE%E8%BF%9B%E5%BA%A6%E7%AE%A1%E7%90%86%E5%B7%A5%E5%85%B7-kanboard/:6:7","tags":null,"title":"一个简洁但功能全面的项目进度管理工具 KanBoard","uri":"/posts/%E4%B8%80%E4%B8%AA%E7%AE%80%E6%B4%81%E4%BD%86%E5%8A%9F%E8%83%BD%E5%85%A8%E9%9D%A2%E7%9A%84%E9%A1%B9%E7%9B%AE%E8%BF%9B%E5%BA%A6%E7%AE%A1%E7%90%86%E5%B7%A5%E5%85%B7-kanboard/#更新任务"},{"categories":["玩转NAS"],"collections":["玩转NAS"],"content":"30.8 设置任务的MetaData\rpayload = { \"jsonrpc\": \"2.0\", \"method\": \"saveTaskMetadata\", \"id\": 1, \"params\": [ task_id, { key: value } ] } ","date":"2025-11-25","objectID":"/posts/%E4%B8%80%E4%B8%AA%E7%AE%80%E6%B4%81%E4%BD%86%E5%8A%9F%E8%83%BD%E5%85%A8%E9%9D%A2%E7%9A%84%E9%A1%B9%E7%9B%AE%E8%BF%9B%E5%BA%A6%E7%AE%A1%E7%90%86%E5%B7%A5%E5%85%B7-kanboard/:6:8","tags":null,"title":"一个简洁但功能全面的项目进度管理工具 KanBoard","uri":"/posts/%E4%B8%80%E4%B8%AA%E7%AE%80%E6%B4%81%E4%BD%86%E5%8A%9F%E8%83%BD%E5%85%A8%E9%9D%A2%E7%9A%84%E9%A1%B9%E7%9B%AE%E8%BF%9B%E5%BA%A6%E7%AE%A1%E7%90%86%E5%B7%A5%E5%85%B7-kanboard/#设置任务的metadata"},{"categories":["玩转NAS"],"collections":["玩转NAS"],"content":"30.9 获取任务的MetaData\r可以一次获取全部的 MetaData： payload = { \"jsonrpc\": \"2.0\", \"method\": \"getTaskMetadata\", \"id\": 1, \"params\": [ task_id ] } 或者获取指定名称的 MetaData： payload = { \"jsonrpc\": \"2.0\", \"method\": \"getTaskMetadataByName\", \"id\": 133280317, \"params\": [ task_id, \"key\" ] } ","date":"2025-11-25","objectID":"/posts/%E4%B8%80%E4%B8%AA%E7%AE%80%E6%B4%81%E4%BD%86%E5%8A%9F%E8%83%BD%E5%85%A8%E9%9D%A2%E7%9A%84%E9%A1%B9%E7%9B%AE%E8%BF%9B%E5%BA%A6%E7%AE%A1%E7%90%86%E5%B7%A5%E5%85%B7-kanboard/:6:9","tags":null,"title":"一个简洁但功能全面的项目进度管理工具 KanBoard","uri":"/posts/%E4%B8%80%E4%B8%AA%E7%AE%80%E6%B4%81%E4%BD%86%E5%8A%9F%E8%83%BD%E5%85%A8%E9%9D%A2%E7%9A%84%E9%A1%B9%E7%9B%AE%E8%BF%9B%E5%BA%A6%E7%AE%A1%E7%90%86%E5%B7%A5%E5%85%B7-kanboard/#获取任务的metadata"},{"categories":["玩转NAS"],"collections":["玩转NAS"],"content":"30.10 获取评论\r获取任务下的全部评论： payload = { \"jsonrpc\": \"2.0\", \"method\": \"getAllComments\", \"id\": 1, \"params\": { \"task_id\": task_id } } 信息\r不过说起来我能直接访问数据库，所以用 API 反而更加复杂一些，原来以为会用不上的，直到我需要使用 WebHook 时，这时需要修改一些数据，所以用 API 会更加保险一些。 ","date":"2025-11-25","objectID":"/posts/%E4%B8%80%E4%B8%AA%E7%AE%80%E6%B4%81%E4%BD%86%E5%8A%9F%E8%83%BD%E5%85%A8%E9%9D%A2%E7%9A%84%E9%A1%B9%E7%9B%AE%E8%BF%9B%E5%BA%A6%E7%AE%A1%E7%90%86%E5%B7%A5%E5%85%B7-kanboard/:6:10","tags":null,"title":"一个简洁但功能全面的项目进度管理工具 KanBoard","uri":"/posts/%E4%B8%80%E4%B8%AA%E7%AE%80%E6%B4%81%E4%BD%86%E5%8A%9F%E8%83%BD%E5%85%A8%E9%9D%A2%E7%9A%84%E9%A1%B9%E7%9B%AE%E8%BF%9B%E5%BA%A6%E7%AE%A1%E7%90%86%E5%B7%A5%E5%85%B7-kanboard/#获取评论"},{"categories":["玩转NAS"],"collections":["玩转NAS"],"content":"第 31 节 WebHook\r发送过来的 WebHook 主要由以下数据组成： 以task.move.column为例，就是移动进度（待办、进行中、已完成），传过来的json中有三个字段event_name、event_data和event_author。 event_data中包含了大部分信息，这样我们收到了 WebHook 以后就可以得知项目的承建人员进行了哪些活动，主要有两方面的作用： 一是当项目进展有变动时，可以推送消息给我们，让我们在第一时间知道项目的变化。 二是项目承建人员在填写任务时，可能有一些字段没有填入，我们可以进行推断，使用 API 给这些任务填入默认值。 信息\rKanBoard 的项目中，很多地方都留有了 Hook Refer，在触发事件时让用户执行自己的操作，只需要自己写plugin定义 Hook 即可。不过 Hook Refer 的位置不是很全面，有时候需要自己去定义一些 Hook Refer。出于省事的考虑，本人就直接用 WebHook 来处理了。 ","date":"2025-11-25","objectID":"/posts/%E4%B8%80%E4%B8%AA%E7%AE%80%E6%B4%81%E4%BD%86%E5%8A%9F%E8%83%BD%E5%85%A8%E9%9D%A2%E7%9A%84%E9%A1%B9%E7%9B%AE%E8%BF%9B%E5%BA%A6%E7%AE%A1%E7%90%86%E5%B7%A5%E5%85%B7-kanboard/:7:0","tags":null,"title":"一个简洁但功能全面的项目进度管理工具 KanBoard","uri":"/posts/%E4%B8%80%E4%B8%AA%E7%AE%80%E6%B4%81%E4%BD%86%E5%8A%9F%E8%83%BD%E5%85%A8%E9%9D%A2%E7%9A%84%E9%A1%B9%E7%9B%AE%E8%BF%9B%E5%BA%A6%E7%AE%A1%E7%90%86%E5%B7%A5%E5%85%B7-kanboard/#webhook"},{"categories":["玩转NAS"],"collections":["玩转NAS"],"content":"第 32 节 与其他项目的联动\r为了方便 KanBoard 的使用体验，本人结合之前布置的其他项目，与 KanBoard 进行了一些联动操作，介绍如下： Chanify：当新建任务、任务状态变动和任务评论时，会推送消息到手机 n8n：结合 AI Agent 生成日报和周报，调用 FastAPI 处理结果并根据模板调用 docxtpl 生成 Word 文档 MetaBase：生成周工作的 BI 图表 Habitica：在 KanBoard 中建立个人工作记录，个人工作创建和完成同步到 Habitica 的待办事项并自动记分 因为上述内容讲述起来比较繁琐，并且本身实现起来并不困难，所以不再继续阐述。 ","date":"2025-11-25","objectID":"/posts/%E4%B8%80%E4%B8%AA%E7%AE%80%E6%B4%81%E4%BD%86%E5%8A%9F%E8%83%BD%E5%85%A8%E9%9D%A2%E7%9A%84%E9%A1%B9%E7%9B%AE%E8%BF%9B%E5%BA%A6%E7%AE%A1%E7%90%86%E5%B7%A5%E5%85%B7-kanboard/:8:0","tags":null,"title":"一个简洁但功能全面的项目进度管理工具 KanBoard","uri":"/posts/%E4%B8%80%E4%B8%AA%E7%AE%80%E6%B4%81%E4%BD%86%E5%8A%9F%E8%83%BD%E5%85%A8%E9%9D%A2%E7%9A%84%E9%A1%B9%E7%9B%AE%E8%BF%9B%E5%BA%A6%E7%AE%A1%E7%90%86%E5%B7%A5%E5%85%B7-kanboard/#与其他项目的联动"},{"categories":["玩转NAS"],"collections":["玩转NAS"],"content":"第 13 节 概述\rHabitica 是一款角色扮演游戏（RPG），玩家可以收集金币、物品、装备、宠物等，并通过升级提升实力，升级所需的经验来自完成现实生活中的目标（习惯、日常任务和待办事项）。 习惯可以设置为“积极的”、“消极的”或两者兼有： 完成积极的习惯，比如“1 小时高效工作”，就可以获得经验值和金币 完成消极的习惯，比如“吃垃圾食品”，生命值就会下降 为什么要用 Habitica 来养成习惯？\r生活中的目标（习惯、日常任务和待办事项）通常会有很多，平时大家都比较忙碌，会容易忘记，定期看 Habitica 可以提醒我们去完成这些目标。 完成 Habitica 上设定的目标以后，会有正向的回馈（经验值和金币），可以提供足够的情绪价值，这样我们更容易坚持下去。 ","date":"2025-11-15","objectID":"/posts/%E4%BD%BF%E7%94%A8-habitica-%E5%85%BB%E6%88%90%E5%A5%BD%E4%B9%A0%E6%83%AF/:1:0","tags":null,"title":"使用 Habitica 养成好习惯","uri":"/posts/%E4%BD%BF%E7%94%A8-habitica-%E5%85%BB%E6%88%90%E5%A5%BD%E4%B9%A0%E6%83%AF/#概述"},{"categories":["玩转NAS"],"collections":["玩转NAS"],"content":"第 14 节 好习惯\r什么样的习惯是一个好的习惯呢？不妨来思考： 每天去采购新鲜的食材是否是一个好的习惯 晚上自己做饭是否是一个好的习惯 尽可能让自己吃上新鲜的饭菜是否是一个好的习惯 饭后洗碗是否是一个好的习惯 饭后抽空出去跑步是否是一个好的习惯 毫无疑问，这些都可以被称之为好习惯，但是当它们合到一起呢？ 就本人而言： 大概平常晚上 5 点钟可以下班，到家一般 6 点左右，如果有加班或者路上堵车可能还不止，而当我买菜、做饭、吃饭、洗碗之后，通常已经接近晚上 9 点了。 不管是做菜还是做饭，都会有一个最少的量，比这个最少的量更少时，既不方便买菜，也不方便下锅，而一个人吃这个最少的量时，通常要吃上好几顿。为了保证食材的新鲜，本人一般在三顿内就要吃完。 本人一直以来身材的塑造都是通过晚上跑步，跑了就会保持好的身材，没跑就会发胖。 那么，结果就很明显了，虽然这样看着非常上进，但是每天会没有任何的空闲时间，并且因为要按时吃完食物，每一天的热量都是超标的，晚上的跑步只不过是消耗了这一部分多出的热量，对于瘦身而言也没有半点作用。 所以绕了一圈这些都成了无用功，并且因为透支了每天的精力和时间，没有时间的我失去了个人成长的机会，仅剩的时间变会沉迷于“奶头乐”。 但是，人是不可以没有空闲时间的。如果一个人的收入只能够刚好维系日常生活，那么这个人便不会有积蓄；如果一个人的空闲时间只能够刚好维系日常生活，那么这个人便不会有任何的成长。 有人有过类似的观点，引用如下： 引用\r全世界的资本家都有一个心照不宣的共识，那就是要用一切的手段避免普通人完成原始资本的积累。因为一旦所有人都拥有了被动收入，那便没有人愿意去努力工作，创造价值了。 什么叫原始资本的积累？就是这个资本我们积累好了以后，也就不用再参与任何的社会劳动了，不需要再靠出卖时间和体力来获取报酬，只需要靠资本本身产生的收益就可以完全的养活自己，这就叫完成原始资本的积累。 一个年轻人如果有个三年、五年属于自己的时间，学习培养自己的爱好，用心挖掘自己的优势和潜力，专心学习研究自己喜欢的、有价值、有意义、有更大回报的事情，找到一个真正属于自己的、能发挥自己价值的道场，那你未来的可能性是极大的。为什么？因为三年、五年之后，他的劳动效率会得到大幅的提升。一天的时间创造出来的价值相当于之前10天、30天甚至一年的价值，达到这样的专业程度，你才有完成原始资本加累的可能。 读上面这段文字，并非所有的人都会有感触，也会有人嗤之以鼻，但是这些文字却真实的诉说了我过去的经历。 在人生的大事上，大的消费上，本人向来都是比较保守的，会去学习时政财经知识，会去分析租售比，质疑高昂的房价，这也使得我手头上有比同龄人更多的现金，能够去投资纳斯达克。 工作上一直也比较轻松，让我能够有时间持续学习、提升自己，刚毕业那会儿其实也算编程小白，会的其实并不多，但是这么多年下来也是逐渐有了自己的方向。写程序这个行为，终究成了能够提高效率、弯道超车的利器。 蛮好的，正是因为有了上面的这些真正的好习惯，本人才能够坐在电脑前写下这篇博客，要不然我连博客都不会搭建，说不定早就在日复一日的重复工作中变得麻木了。有句话就说的很好，“仓岭实而知礼节，衣食足而知荣辱。”，所有的堕落归根结底可能都是“不曾拥有过”。 ","date":"2025-11-15","objectID":"/posts/%E4%BD%BF%E7%94%A8-habitica-%E5%85%BB%E6%88%90%E5%A5%BD%E4%B9%A0%E6%83%AF/:2:0","tags":null,"title":"使用 Habitica 养成好习惯","uri":"/posts/%E4%BD%BF%E7%94%A8-habitica-%E5%85%BB%E6%88%90%E5%A5%BD%E4%B9%A0%E6%83%AF/#好习惯"},{"categories":["玩转NAS"],"collections":["玩转NAS"],"content":"第 15 节 本地部署\r很巧的是 Habitica 也可以本地部署，因为数据是非结构化的，所以用的数据库是 MongoDB。 不过因为原本就是设计给多人共同使用的，所以这里的 MongoDB 要以集群的模式建立。 ","date":"2025-11-15","objectID":"/posts/%E4%BD%BF%E7%94%A8-habitica-%E5%85%BB%E6%88%90%E5%A5%BD%E4%B9%A0%E6%83%AF/:3:0","tags":null,"title":"使用 Habitica 养成好习惯","uri":"/posts/%E4%BD%BF%E7%94%A8-habitica-%E5%85%BB%E6%88%90%E5%A5%BD%E4%B9%A0%E6%83%AF/#本地部署"},{"categories":["玩转NAS"],"collections":["玩转NAS"],"content":"15.1 部署 MongoDB 集群\r因为本人 NAS 的 CPU 不支持某个特性，只能使用 5.0 以下版本的 MongoDB，此时 5.0 以下的最新版本是 4.4.29，因而以这个版本为例。 docker run -itd --name MongoDB --restart=always \\ -p 27017:27017 \\ -e MONGO_INITDB_ROOT_USERNAME=xxx \\ -e MONGO_INITDB_ROOT_PASSWORD=xxx \\ -v /xxx/MongoDB/config:/data/configdb \\ -v /xxx/MongoDB/data:/data/db \\ -v /xxx/MongoDB/log:/data/log \\ mongo:4.4.29 \\ --replSet rs \\ --bind_ip_all \\ --keyFile /data/configdb/mongo.key \\ --auth 要准备一个mongo.key文件，方法如下： sudo openssl rand -base64 756 \u003e mongo.key sudo chmod 400 mongo.key 建好容器以后，需要初始化集群，方法如下： mongo -u xxx -p xxx --authenticationDatabase admin rs.initiate({ _id: \"rs\", members: [{ _id: 0, host: \"HOST:PORT\" }] }) 需要注意的是，这里的 host 和 port 需要是容器外访问的地址，不能是容器内的localhost ","date":"2025-11-15","objectID":"/posts/%E4%BD%BF%E7%94%A8-habitica-%E5%85%BB%E6%88%90%E5%A5%BD%E4%B9%A0%E6%83%AF/:3:1","tags":null,"title":"使用 Habitica 养成好习惯","uri":"/posts/%E4%BD%BF%E7%94%A8-habitica-%E5%85%BB%E6%88%90%E5%A5%BD%E4%B9%A0%E6%83%AF/#部署-mongodb-集群"},{"categories":["玩转NAS"],"collections":["玩转NAS"],"content":"15.2 部署 Habitica\rHabitica 是没有官方的 Docker 镜像的，使用 Docker 部署时，需要将官方的项目下载到本地进行部署，不过因为有第三方帮我们打包好了，所以可以直接用第三方的镜像。 这个人还有一个名为 Habitica Client 的项目，官方的 Docker Compose 里头也是同时有 Server 和 Client，不过本人试下来只部署一个 Server 就可以正常使用了，因而就不需要部署 Client 了。 docker run -itd --name Habitica --restart always \\ -e NODE_DB_URI=\"mongodb://USERNAME:PASSWORD@HOST:PORT/Habitica?replicaSet=rs\u0026directConnection=false\u0026authSource=admin\" \\ -e BASE_URL=http://HOST:PORT \\ -e INVITE_ONLY=false \\ -e EMAIL_SERVER_URL=mail.example.com \\ -e EMAIL_SERVER_PORT=587 \\ -e EMAIL_SERVER_AUTH_USER=MAIL_USER \\ -e EMAIL_SERVER_AUTH_PASSWORD=MAIL_PASSWORD \\ -p PORT:3000 \\ awinterstein/habitica-server:latest 这里和EMAIL_SERVER相关的内容是用来发邮件的，不过本人目前用下来未发现需要发邮件的场景，不清楚有何具体作用。国内邮箱一般都有 SMTP 功能，申请一个填入即可。 ","date":"2025-11-15","objectID":"/posts/%E4%BD%BF%E7%94%A8-habitica-%E5%85%BB%E6%88%90%E5%A5%BD%E4%B9%A0%E6%83%AF/:3:2","tags":null,"title":"使用 Habitica 养成好习惯","uri":"/posts/%E4%BD%BF%E7%94%A8-habitica-%E5%85%BB%E6%88%90%E5%A5%BD%E4%B9%A0%E6%83%AF/#部署-habitica"},{"categories":["玩转NAS"],"collections":["玩转NAS"],"content":"第 16 节 使用\r部署好之后很轻松就能注册一个账号： 第一个注册用户自动获得管理员权限。 登录以后进入主界面，可以添加的有习惯、日常任务、待办事项和奖励： 习惯可以设置为正向或反向，适用于你想要每天多次完成、或者不设时间安排的任务。完成正向的习惯会带来奖励，比如金币和经验值（Exp），而反向的习惯则会扣除你的生命值（HP）。 日常任务适用于重复性的、有相对固定的时间安排的任务。比如每天一次、每周三次，或每月四次。未完成的日常任务会扣除生命值，但设置的难度越高，获得的奖励也越丰厚！ 待办事项是一次性任务，完成后可获得奖励。待办事项可以设置截止日期，但错过截止日期不会损失生命值。 比如，可以添加一个如下的习惯： 这样，我们在写完一篇博客以后就可以得到金币和经验值了，正如本人此时此刻这般。 除此以外，我们可以去做一些真正有意义的事情： 指引自己做一些家务： 甚至多喝水也是可以的： 真棒，今天我也喝了足够的水呢。 ","date":"2025-11-15","objectID":"/posts/%E4%BD%BF%E7%94%A8-habitica-%E5%85%BB%E6%88%90%E5%A5%BD%E4%B9%A0%E6%83%AF/:4:0","tags":null,"title":"使用 Habitica 养成好习惯","uri":"/posts/%E4%BD%BF%E7%94%A8-habitica-%E5%85%BB%E6%88%90%E5%A5%BD%E4%B9%A0%E6%83%AF/#使用"},{"categories":[],"collections":["仿真"],"content":"第 19 节 概述\r由于工作上的一些需要，需要将 Simulink 部署到服务器上，通过 Restful API 调用，传入参数进行仿真。 Simulink 是 Matlab 的一个模块，属于公司 MathWorks，是一款商业软件。服务的部署有多种方式可选，但是选择的方法不恰当，可能会面临授权和收费的问题。MathWorks 的收费向来很多，显然，通过付费来解决问题并不是我们想要的。 ","date":"2025-09-24","objectID":"/posts/%E4%BD%BF%E7%94%A8-python-%E8%B0%83%E7%94%A8-simulink-%E8%BF%9B%E8%A1%8C%E4%BB%BF%E7%9C%9F/:1:0","tags":["Simulink"],"title":"使用 Python 调用 Simulink 进行仿真","uri":"/posts/%E4%BD%BF%E7%94%A8-python-%E8%B0%83%E7%94%A8-simulink-%E8%BF%9B%E8%A1%8C%E4%BB%BF%E7%9C%9F/#概述"},{"categories":[],"collections":["仿真"],"content":"第 20 节 部署方式\rMatlab 主程序提供了一些部署方法，这些部署方法更加适合对.m文件进行部署。 「代码生成」中的 Matlab Coder 可以将 Matlab 代码编译成原生的 C 语言程序，从而不依赖 Matlab 环境运行，但是其不支持 Simulink 的编译 「应用程序部署」那一类，都需要 Matlab 运行环境，其实现的是使用对应编程语言调用 Matlab 运行环境的接口。 Simulink 子程序中也提供了一些部署方法，这些部署方法是专门给 Simulink 适配的。 其中的 Simulink Coder 可以将 Simulink 模块编译成原生的 C语言程序，从而不依赖 Matlab 环境运行。 ","date":"2025-09-24","objectID":"/posts/%E4%BD%BF%E7%94%A8-python-%E8%B0%83%E7%94%A8-simulink-%E8%BF%9B%E8%A1%8C%E4%BB%BF%E7%9C%9F/:2:0","tags":["Simulink"],"title":"使用 Python 调用 Simulink 进行仿真","uri":"/posts/%E4%BD%BF%E7%94%A8-python-%E8%B0%83%E7%94%A8-simulink-%E8%BF%9B%E8%A1%8C%E4%BB%BF%E7%9C%9F/#部署方式"},{"categories":[],"collections":["仿真"],"content":"20.1 Matlab Production/Web App Server\r首先，MathWorks 公司肯定是料想到了它的客户需要将 Matlab 相关服务部署到服务器上运行，于是，它推出了两款产品。 分别是：Matlab Production Server 和 Matlab Web App Server，这两款产品的区别在于，Matlab Production Server 提供的是 Restful API，而 Matlab Web App Server 可以通过可视化的方式在浏览器上运行 Matlab 应用程序。 信息\r这两款软件都是商业软件，所以都是要收费的。 编译器的位置如下： Matlab Production Server\rMatlab Production Server 可以直接将 .m文件及其他关联附属文件打包。 打包的过程中使用依赖关系分析器，并将依赖关系用图列举出来： 最终，相关的依赖模块会以各种Addon的形式体现在buildresult.json文件中： 于是在执行相应函数时，只会加载对应的 Addon。 Matlab Web App Server\rMatlab Web App Server 不同于 Matlab Production Server，它需要先创建一个 Matlab 的 App 应用，并在其中设计好相应的前端 UI 并绑定函数功能，如下： 由于本人并不打算使用这种方式构建，因而不再进行进一步的功能探索。 ","date":"2025-09-24","objectID":"/posts/%E4%BD%BF%E7%94%A8-python-%E8%B0%83%E7%94%A8-simulink-%E8%BF%9B%E8%A1%8C%E4%BB%BF%E7%9C%9F/:2:1","tags":["Simulink"],"title":"使用 Python 调用 Simulink 进行仿真","uri":"/posts/%E4%BD%BF%E7%94%A8-python-%E8%B0%83%E7%94%A8-simulink-%E8%BF%9B%E8%A1%8C%E4%BB%BF%E7%9C%9F/#matlab-productionweb-app-server"},{"categories":[],"collections":["仿真"],"content":"20.1 Matlab Production/Web App Server\r首先，MathWorks 公司肯定是料想到了它的客户需要将 Matlab 相关服务部署到服务器上运行，于是，它推出了两款产品。 分别是：Matlab Production Server 和 Matlab Web App Server，这两款产品的区别在于，Matlab Production Server 提供的是 Restful API，而 Matlab Web App Server 可以通过可视化的方式在浏览器上运行 Matlab 应用程序。 信息\r这两款软件都是商业软件，所以都是要收费的。 编译器的位置如下： Matlab Production Server\rMatlab Production Server 可以直接将 .m文件及其他关联附属文件打包。 打包的过程中使用依赖关系分析器，并将依赖关系用图列举出来： 最终，相关的依赖模块会以各种Addon的形式体现在buildresult.json文件中： 于是在执行相应函数时，只会加载对应的 Addon。 Matlab Web App Server\rMatlab Web App Server 不同于 Matlab Production Server，它需要先创建一个 Matlab 的 App 应用，并在其中设计好相应的前端 UI 并绑定函数功能，如下： 由于本人并不打算使用这种方式构建，因而不再进行进一步的功能探索。 ","date":"2025-09-24","objectID":"/posts/%E4%BD%BF%E7%94%A8-python-%E8%B0%83%E7%94%A8-simulink-%E8%BF%9B%E8%A1%8C%E4%BB%BF%E7%9C%9F/:2:1","tags":["Simulink"],"title":"使用 Python 调用 Simulink 进行仿真","uri":"/posts/%E4%BD%BF%E7%94%A8-python-%E8%B0%83%E7%94%A8-simulink-%E8%BF%9B%E8%A1%8C%E4%BB%BF%E7%9C%9F/#matlab-production-server"},{"categories":[],"collections":["仿真"],"content":"20.1 Matlab Production/Web App Server\r首先，MathWorks 公司肯定是料想到了它的客户需要将 Matlab 相关服务部署到服务器上运行，于是，它推出了两款产品。 分别是：Matlab Production Server 和 Matlab Web App Server，这两款产品的区别在于，Matlab Production Server 提供的是 Restful API，而 Matlab Web App Server 可以通过可视化的方式在浏览器上运行 Matlab 应用程序。 信息\r这两款软件都是商业软件，所以都是要收费的。 编译器的位置如下： Matlab Production Server\rMatlab Production Server 可以直接将 .m文件及其他关联附属文件打包。 打包的过程中使用依赖关系分析器，并将依赖关系用图列举出来： 最终，相关的依赖模块会以各种Addon的形式体现在buildresult.json文件中： 于是在执行相应函数时，只会加载对应的 Addon。 Matlab Web App Server\rMatlab Web App Server 不同于 Matlab Production Server，它需要先创建一个 Matlab 的 App 应用，并在其中设计好相应的前端 UI 并绑定函数功能，如下： 由于本人并不打算使用这种方式构建，因而不再进行进一步的功能探索。 ","date":"2025-09-24","objectID":"/posts/%E4%BD%BF%E7%94%A8-python-%E8%B0%83%E7%94%A8-simulink-%E8%BF%9B%E8%A1%8C%E4%BB%BF%E7%9C%9F/:2:1","tags":["Simulink"],"title":"使用 Python 调用 Simulink 进行仿真","uri":"/posts/%E4%BD%BF%E7%94%A8-python-%E8%B0%83%E7%94%A8-simulink-%E8%BF%9B%E8%A1%8C%E4%BB%BF%E7%9C%9F/#matlab-web-app-server"},{"categories":[],"collections":["仿真"],"content":"20.2 MATLAB Engine API\rMATLAB Engine API 是 MathWorks 提供的官方工具，允许在 Python 中调用 MATLAB 函数、脚本和数据。 找到如下路径：[MATLAB安装路径]/extern/engines/python 使用命令安装： python setup.py install MATLAB Engine API 就相当于在后台打开了一个 matlab 程序，使用 python 执行的命令会原封不动的转变成执行 matlab 命令： # 导入库 import matlab.engine # 在后台打开 matlab 程序 eng = matlab.engine.start_matlab() # 添加 matlab 项目的目录 eng.addpath('path') # 执行目录下的 xxx.m 文件定义的函数 result = eng.xxx(1,2) 这种方式只是在 matlab 外套了一层 python 的包装，怎样使用 matlab 就怎样使用 python，不会出现任何的意料之外的报错。 也正因为这样，这种方式需要在服务器上安装完整的 matlab 程序并授权，并且每次服务启动前需要先启动 matlab，执行 simulink 仿真前也会加载相应模块，每次运行前会有好几秒的加载时间，需要进行优化并预先加载资源，所以这种方式不适合部署在服务器上。 ","date":"2025-09-24","objectID":"/posts/%E4%BD%BF%E7%94%A8-python-%E8%B0%83%E7%94%A8-simulink-%E8%BF%9B%E8%A1%8C%E4%BB%BF%E7%9C%9F/:2:2","tags":["Simulink"],"title":"使用 Python 调用 Simulink 进行仿真","uri":"/posts/%E4%BD%BF%E7%94%A8-python-%E8%B0%83%E7%94%A8-simulink-%E8%BF%9B%E8%A1%8C%E4%BB%BF%E7%9C%9F/#matlab-engine-api"},{"categories":[],"collections":["仿真"],"content":"20.3 Python 包编译器\rPython 包编译器可以直接将 .m文件及其他关联附属文件打包，如下： 安装一下即可： python setup.py install # 导入包 import RPC_ABSmin # 初始化包 handle = RPC_ABSmin.initialize() # 执行包中定义的函数 result = handle.run_RPC_ABSmin(1,2) 这种方式不需要完整安装 matlab 应用，只需要安装 matlab 运行环境，执行函数时只加载依赖的模块，所以速度上是要快上不少的，并且 matlab 的运行环境是不收费且无需授权的，所以这是一种可以考虑的部署方式。 警告\r不过，本人在使用这种方式运行 Simulink 仿真的时候遇到了未知的错误，执行sim函数时会报错，因而最终也没有采用这种方式。 ","date":"2025-09-24","objectID":"/posts/%E4%BD%BF%E7%94%A8-python-%E8%B0%83%E7%94%A8-simulink-%E8%BF%9B%E8%A1%8C%E4%BB%BF%E7%9C%9F/:2:3","tags":["Simulink"],"title":"使用 Python 调用 Simulink 进行仿真","uri":"/posts/%E4%BD%BF%E7%94%A8-python-%E8%B0%83%E7%94%A8-simulink-%E8%BF%9B%E8%A1%8C%E4%BB%BF%E7%9C%9F/#python-包编译器"},{"categories":[],"collections":["仿真"],"content":"20.4 Simulink Coder\r使用 Simulink Coder 前需要安装一款合适的 C 编译器，如下： 在「APP」中选择 Simulink Coder，然后在弹出的 C 代码标签页上点击「编译」即可： 这样，便会生成如下的 C Make 项目： 打开文件夹cmake-build-debug，可以看到，matlab 已经为我们编译好了可执行文件。 信息\r这种方式不依赖于 Matlab 运行环境，并且因为是原生的 C 语言环境，程序的运行速度也很快，所以最后本人便采用这种方式来编译 Simulink。 ","date":"2025-09-24","objectID":"/posts/%E4%BD%BF%E7%94%A8-python-%E8%B0%83%E7%94%A8-simulink-%E8%BF%9B%E8%A1%8C%E4%BB%BF%E7%9C%9F/:2:4","tags":["Simulink"],"title":"使用 Python 调用 Simulink 进行仿真","uri":"/posts/%E4%BD%BF%E7%94%A8-python-%E8%B0%83%E7%94%A8-simulink-%E8%BF%9B%E8%A1%8C%E4%BB%BF%E7%9C%9F/#simulink-coder"},{"categories":[],"collections":["仿真"],"content":"第 21 节 编译共享链接库\r使用 Simulink Coder 编译时，默认生成的是可执行文件，这有一定的局限性。 一方面，执行可执行文件时，不好指定输入，输出也不好获取，对于 Simulink 这样依赖输入输出的仿真应用，非常不方便。 另一方面，可执行文件的中间运算过程是固定的，不方便在仿真的中途修改数据。 所以，需要修改C Make的编译方式，生成共享链接库（Windows 中是 dll 文件，Mac 中是dylib 文件，linux 中是 so 文件）。 找到CMakeLists.txt文件，其中有一行用于生成可执行文件： add_executable(「xxx」 ${MATLAB_ROOT}/rtw/c/src/common/rt_main.c) 将其替换成以下内容： # 将原来的add_executable改为add_library并指定为SHARED库 add_library(「xxx」 SHARED ${MATLAB_ROOT}/rtw/c/src/common/rt_main.c) # 设置库的属性 set_target_properties(「xxx」 PROPERTIES PREFIX \"\" BUILD_WITH_INSTALL_RPATH TRUE INSTALL_RPATH_USE_LINK_PATH TRUE SOVERSION 1) # 添加版本号 # 修改安装规则，将RUNTIME改为LIBRARY install(TARGETS 「xxx」 LIBRARY DESTINATION \"lib\" # 修改为LIBRARY并指定安装目录 ARCHIVE DESTINATION \"lib\" # 添加静态库安装目录 RUNTIME DESTINATION \"bin\" # 可执行文件目录(如果有的话) OPTIONAL) 重新编译即可获得共享链接库。 ","date":"2025-09-24","objectID":"/posts/%E4%BD%BF%E7%94%A8-python-%E8%B0%83%E7%94%A8-simulink-%E8%BF%9B%E8%A1%8C%E4%BB%BF%E7%9C%9F/:3:0","tags":["Simulink"],"title":"使用 Python 调用 Simulink 进行仿真","uri":"/posts/%E4%BD%BF%E7%94%A8-python-%E8%B0%83%E7%94%A8-simulink-%E8%BF%9B%E8%A1%8C%E4%BB%BF%E7%9C%9F/#编译共享链接库"},{"categories":[],"collections":["仿真"],"content":"第 22 节 查看 C 代码\r因为后续使用 Python 调用共享链接库时，需要执行库中的 C 函数，因而需要了解源码中 C 函数的变量名。 在xxx.h里面，定义了模型的输入和输出，如下： typedef struct { real_T CV11; /* '\u003cRoot\u003e/CV11' */ real_T CV12; /* '\u003cRoot\u003e/CV12' */ real_T CV21; /* '\u003cRoot\u003e/CV21' */ real_T CV22; /* '\u003cRoot\u003e/CV22' */ boolean_T Reduce1; /* '\u003cRoot\u003e/Reduce1' */ boolean_T Reduce2; /* '\u003cRoot\u003e/Reduce2' */ real_T Power; /* '\u003cRoot\u003e/Power' */ } ExtU_RPC_ABSmin_T; typedef struct { real_T Number; /* '\u003cRoot\u003e/Number' */ } ExtY_RPC_ABSmin_T; 定义了两个结构体，ExtU_xxx_T是输入，ExtY_xxx_T是输出。 在xxx.c里面，定义了模型的运算过程，如下： void RPC_ABSmin_initialize(void) {...} void RPC_ABSmin_step(void) {...} void RPC_ABSmin_terminate(void) {...} RPC_ABSmin_initialize是构造函数，RPC_ABSmin_step是迭代函数，而RPC_ABSmin_terminate是析构函数。 如果在模型中指定了仿真的步长和时间，就会在构造函数中看到如下内容： # 步长 0.01 s RPC_ABSmin_M-\u003eTiming.stepSize0 = 0.01; # 时间 1s rtmSetTFinal(RPC_ABSmin_M, 1.0); 迭代函数里面是每个步长中需要执行的运算。 信息\r可见 Simulink 就是定义了初始状态和每一步的运算，然后反复迭代的过程。 ","date":"2025-09-24","objectID":"/posts/%E4%BD%BF%E7%94%A8-python-%E8%B0%83%E7%94%A8-simulink-%E8%BF%9B%E8%A1%8C%E4%BB%BF%E7%9C%9F/:4:0","tags":["Simulink"],"title":"使用 Python 调用 Simulink 进行仿真","uri":"/posts/%E4%BD%BF%E7%94%A8-python-%E8%B0%83%E7%94%A8-simulink-%E8%BF%9B%E8%A1%8C%E4%BB%BF%E7%9C%9F/#查看-c-代码"},{"categories":[],"collections":["仿真"],"content":"第 23 节 编写 Python 代码\r有了上面 C 代码的铺垫，可以编写 Python 代码如下： 首先，定义模型的输入输出形式，对应.h文件： import ctypes class ExtU_RPC_ABSmin_T(ctypes.Structure): _fields_ = [ (\"CV11\", ctypes.c_double), (\"CV12\", ctypes.c_double), (\"CV21\", ctypes.c_double), (\"CV22\", ctypes.c_double), (\"Reduce1\", ctypes.c_uint8), (\"Reduce2\", ctypes.c_uint8), (\"Power\", ctypes.c_double) ] class ExtY_RPC_ABSmin_T(ctypes.Structure): _fields_ = [ (\"Number\", ctypes.c_double) ] 然后，读取共享链接库，并绑定数据： lib = ctypes.CDLL(\"RPC_ABSmin.dylib\") RPC_ABSmin_U = ExtU_RPC_ABSmin_T.in_dll(lib, \"RPC_ABSmin_U\") RPC_ABSmin_Y = ExtY_RPC_ABSmin_T.in_dll(lib, \"RPC_ABSmin_Y\") 最后，执行仿真，对应.c文件： lib.RPC_ABSmin_initialize() RPC_ABSmin_U.CV11 = 1.0 RPC_ABSmin_U.CV12 = 1.0 RPC_ABSmin_U.CV21 = 1.0 RPC_ABSmin_U.CV22 = 1.0 RPC_ABSmin_U.Reduce1 = 0 RPC_ABSmin_U.Reduce2 = 0 RPC_ABSmin_U.Power = 2000.0 for i in range(100): lib.RPC_ABSmin_step() print(\"模型输出 Number =\", RPC_ABSmin_Y.Number) lib.RPC_ABSmin_terminate() ","date":"2025-09-24","objectID":"/posts/%E4%BD%BF%E7%94%A8-python-%E8%B0%83%E7%94%A8-simulink-%E8%BF%9B%E8%A1%8C%E4%BB%BF%E7%9C%9F/:5:0","tags":["Simulink"],"title":"使用 Python 调用 Simulink 进行仿真","uri":"/posts/%E4%BD%BF%E7%94%A8-python-%E8%B0%83%E7%94%A8-simulink-%E8%BF%9B%E8%A1%8C%E4%BB%BF%E7%9C%9F/#编写-python-代码"},{"categories":[],"collections":["仿真"],"content":"第 24 节 移植到其他平台\r在完成上述工作以后，需要将项目移植到其他平台，常见的服务器有 Windows 服务器和 Linux 服务器，而架构有 X86 架构和 ARM 架构，需要按照服务器的系统和架构编译对应版本的共享链接库。 由于 CMake 可以跨平台交叉编译，所以只需要进行一定的设置，就可以一次生成全平台的编译结果，方便我们进行移植。 ","date":"2025-09-24","objectID":"/posts/%E4%BD%BF%E7%94%A8-python-%E8%B0%83%E7%94%A8-simulink-%E8%BF%9B%E8%A1%8C%E4%BB%BF%E7%9C%9F/:6:0","tags":["Simulink"],"title":"使用 Python 调用 Simulink 进行仿真","uri":"/posts/%E4%BD%BF%E7%94%A8-python-%E8%B0%83%E7%94%A8-simulink-%E8%BF%9B%E8%A1%8C%E4%BB%BF%E7%9C%9F/#移植到其他平台"},{"categories":[],"collections":["仿真"],"content":"24.1 安装编译器\r本人安装过程中主要使用homebrew进行安装。 Windows X86_64 的编译器可以通过homebrew直接安装，方法如下： brew install mingw-w64 Windows ARM64 的编译器无法直接通过homebrew安装，可以在 Github 上下载llvm-mingw的最新版本，然后解压到任意指定位置即可。 可以看到压缩包也包含了Windows X86_64 的编译器，所以也可以直接用这个，homebrew会同时配置好环境变量，更加方便一些。 Linux 的编译器可以通过homebrew直接安装，方法如下： brew install FiloSottile/musl-cross/musl-cross ","date":"2025-09-24","objectID":"/posts/%E4%BD%BF%E7%94%A8-python-%E8%B0%83%E7%94%A8-simulink-%E8%BF%9B%E8%A1%8C%E4%BB%BF%E7%9C%9F/:6:1","tags":["Simulink"],"title":"使用 Python 调用 Simulink 进行仿真","uri":"/posts/%E4%BD%BF%E7%94%A8-python-%E8%B0%83%E7%94%A8-simulink-%E8%BF%9B%E8%A1%8C%E4%BB%BF%E7%9C%9F/#安装编译器"},{"categories":[],"collections":["仿真"],"content":"24.2 编写 CMakePresets.json\r这个文件中定义了多种编译方式，以及各种编译方式的编译器和相关设置所在的位置： 以上是本人使用的编译方式，从上到下定义了 MacOS、Linux 和 Windows 的多种编译： MacOS的编译：使用的是本地的编译器，位置在/usr/bin/clang和/usr/bin/clang++，可能会因为安装方式的不同而发生变化，本人的操作系统在如下位置。 Linux和Windows的编译：这里使用了工具链（ToolChain）进行编译，可以看到工具链放在了${sourceDir}/cmake/toolchains/目录下，也就是在项目的根目录下有如下内容： linux-x86_64.cmake\rset(CMAKE_SYSTEM_NAME Linux) set(CMAKE_SYSTEM_PROCESSOR x86_64) set(CMAKE_C_COMPILER /opt/homebrew/bin/x86_64-linux-musl-gcc) set(CMAKE_CXX_COMPILER /opt/homebrew/bin/x86_64-linux-musl-g++) set(CMAKE_SYSROOT /opt/homebrew) set(CMAKE_FIND_ROOT_PATH_MODE_PROGRAM NEVER) set(CMAKE_FIND_ROOT_PATH_MODE_LIBRARY ONLY) set(CMAKE_FIND_ROOT_PATH_MODE_INCLUDE ONLY) linux-arm64.cmake\rset(CMAKE_SYSTEM_NAME Linux) set(CMAKE_SYSTEM_PROCESSOR aarch64) set(CMAKE_C_COMPILER /opt/homebrew/bin/aarch64-linux-musl-gcc) set(CMAKE_CXX_COMPILER /opt/homebrew/bin/aarch64-linux-musl-g++) set(CMAKE_SYSROOT /opt/homebrew) set(CMAKE_FIND_ROOT_PATH_MODE_PROGRAM NEVER) set(CMAKE_FIND_ROOT_PATH_MODE_LIBRARY ONLY) set(CMAKE_FIND_ROOT_PATH_MODE_INCLUDE ONLY) windows-x86_64.cmake\rset(CMAKE_SYSTEM_NAME Windows) set(CMAKE_SYSTEM_PROCESSOR x86_64) set(CMAKE_C_COMPILER /opt/homebrew/bin/x86_64-w64-mingw32-gcc) set(CMAKE_CXX_COMPILER /opt/homebrew/bin/x86_64-w64-mingw32-g++) set(CMAKE_SYSROOT /opt/homebrew) set(CMAKE_FIND_ROOT_PATH_MODE_PROGRAM NEVER) set(CMAKE_FIND_ROOT_PATH_MODE_LIBRARY ONLY) set(CMAKE_FIND_ROOT_PATH_MODE_INCLUDE ONLY) windows-arm64.cmake\rset(CMAKE_SYSTEM_NAME Windows) set(CMAKE_SYSTEM_PROCESSOR ARM64) set(CMAKE_C_COMPILER /xxx/llvm-mingw/bin/aarch64-w64-mingw32-clang) set(CMAKE_CXX_COMPILER /xxx/llvm-mingw/bin/aarch64-w64-mingw32-clang++) set(CMAKE_SYSROOT /xxx/llvm-mingw/aarch64-w64-mingw32) set(CMAKE_FIND_ROOT_PATH_MODE_PROGRAM NEVER) set(CMAKE_FIND_ROOT_PATH_MODE_LIBRARY ONLY) set(CMAKE_FIND_ROOT_PATH_MODE_INCLUDE ONLY) 这里llvm-mingw的位置按照安装位置设定。 ","date":"2025-09-24","objectID":"/posts/%E4%BD%BF%E7%94%A8-python-%E8%B0%83%E7%94%A8-simulink-%E8%BF%9B%E8%A1%8C%E4%BB%BF%E7%9C%9F/:6:2","tags":["Simulink"],"title":"使用 Python 调用 Simulink 进行仿真","uri":"/posts/%E4%BD%BF%E7%94%A8-python-%E8%B0%83%E7%94%A8-simulink-%E8%BF%9B%E8%A1%8C%E4%BB%BF%E7%9C%9F/#编写-cmakepresetsjson"},{"categories":[],"collections":["仿真"],"content":"24.2 编写 CMakePresets.json\r这个文件中定义了多种编译方式，以及各种编译方式的编译器和相关设置所在的位置： 以上是本人使用的编译方式，从上到下定义了 MacOS、Linux 和 Windows 的多种编译： MacOS的编译：使用的是本地的编译器，位置在/usr/bin/clang和/usr/bin/clang++，可能会因为安装方式的不同而发生变化，本人的操作系统在如下位置。 Linux和Windows的编译：这里使用了工具链（ToolChain）进行编译，可以看到工具链放在了${sourceDir}/cmake/toolchains/目录下，也就是在项目的根目录下有如下内容： linux-x86_64.cmake\rset(CMAKE_SYSTEM_NAME Linux) set(CMAKE_SYSTEM_PROCESSOR x86_64) set(CMAKE_C_COMPILER /opt/homebrew/bin/x86_64-linux-musl-gcc) set(CMAKE_CXX_COMPILER /opt/homebrew/bin/x86_64-linux-musl-g++) set(CMAKE_SYSROOT /opt/homebrew) set(CMAKE_FIND_ROOT_PATH_MODE_PROGRAM NEVER) set(CMAKE_FIND_ROOT_PATH_MODE_LIBRARY ONLY) set(CMAKE_FIND_ROOT_PATH_MODE_INCLUDE ONLY) linux-arm64.cmake\rset(CMAKE_SYSTEM_NAME Linux) set(CMAKE_SYSTEM_PROCESSOR aarch64) set(CMAKE_C_COMPILER /opt/homebrew/bin/aarch64-linux-musl-gcc) set(CMAKE_CXX_COMPILER /opt/homebrew/bin/aarch64-linux-musl-g++) set(CMAKE_SYSROOT /opt/homebrew) set(CMAKE_FIND_ROOT_PATH_MODE_PROGRAM NEVER) set(CMAKE_FIND_ROOT_PATH_MODE_LIBRARY ONLY) set(CMAKE_FIND_ROOT_PATH_MODE_INCLUDE ONLY) windows-x86_64.cmake\rset(CMAKE_SYSTEM_NAME Windows) set(CMAKE_SYSTEM_PROCESSOR x86_64) set(CMAKE_C_COMPILER /opt/homebrew/bin/x86_64-w64-mingw32-gcc) set(CMAKE_CXX_COMPILER /opt/homebrew/bin/x86_64-w64-mingw32-g++) set(CMAKE_SYSROOT /opt/homebrew) set(CMAKE_FIND_ROOT_PATH_MODE_PROGRAM NEVER) set(CMAKE_FIND_ROOT_PATH_MODE_LIBRARY ONLY) set(CMAKE_FIND_ROOT_PATH_MODE_INCLUDE ONLY) windows-arm64.cmake\rset(CMAKE_SYSTEM_NAME Windows) set(CMAKE_SYSTEM_PROCESSOR ARM64) set(CMAKE_C_COMPILER /xxx/llvm-mingw/bin/aarch64-w64-mingw32-clang) set(CMAKE_CXX_COMPILER /xxx/llvm-mingw/bin/aarch64-w64-mingw32-clang++) set(CMAKE_SYSROOT /xxx/llvm-mingw/aarch64-w64-mingw32) set(CMAKE_FIND_ROOT_PATH_MODE_PROGRAM NEVER) set(CMAKE_FIND_ROOT_PATH_MODE_LIBRARY ONLY) set(CMAKE_FIND_ROOT_PATH_MODE_INCLUDE ONLY) 这里llvm-mingw的位置按照安装位置设定。 ","date":"2025-09-24","objectID":"/posts/%E4%BD%BF%E7%94%A8-python-%E8%B0%83%E7%94%A8-simulink-%E8%BF%9B%E8%A1%8C%E4%BB%BF%E7%9C%9F/:6:2","tags":["Simulink"],"title":"使用 Python 调用 Simulink 进行仿真","uri":"/posts/%E4%BD%BF%E7%94%A8-python-%E8%B0%83%E7%94%A8-simulink-%E8%BF%9B%E8%A1%8C%E4%BB%BF%E7%9C%9F/#linux-x86_64cmake"},{"categories":[],"collections":["仿真"],"content":"24.2 编写 CMakePresets.json\r这个文件中定义了多种编译方式，以及各种编译方式的编译器和相关设置所在的位置： 以上是本人使用的编译方式，从上到下定义了 MacOS、Linux 和 Windows 的多种编译： MacOS的编译：使用的是本地的编译器，位置在/usr/bin/clang和/usr/bin/clang++，可能会因为安装方式的不同而发生变化，本人的操作系统在如下位置。 Linux和Windows的编译：这里使用了工具链（ToolChain）进行编译，可以看到工具链放在了${sourceDir}/cmake/toolchains/目录下，也就是在项目的根目录下有如下内容： linux-x86_64.cmake\rset(CMAKE_SYSTEM_NAME Linux) set(CMAKE_SYSTEM_PROCESSOR x86_64) set(CMAKE_C_COMPILER /opt/homebrew/bin/x86_64-linux-musl-gcc) set(CMAKE_CXX_COMPILER /opt/homebrew/bin/x86_64-linux-musl-g++) set(CMAKE_SYSROOT /opt/homebrew) set(CMAKE_FIND_ROOT_PATH_MODE_PROGRAM NEVER) set(CMAKE_FIND_ROOT_PATH_MODE_LIBRARY ONLY) set(CMAKE_FIND_ROOT_PATH_MODE_INCLUDE ONLY) linux-arm64.cmake\rset(CMAKE_SYSTEM_NAME Linux) set(CMAKE_SYSTEM_PROCESSOR aarch64) set(CMAKE_C_COMPILER /opt/homebrew/bin/aarch64-linux-musl-gcc) set(CMAKE_CXX_COMPILER /opt/homebrew/bin/aarch64-linux-musl-g++) set(CMAKE_SYSROOT /opt/homebrew) set(CMAKE_FIND_ROOT_PATH_MODE_PROGRAM NEVER) set(CMAKE_FIND_ROOT_PATH_MODE_LIBRARY ONLY) set(CMAKE_FIND_ROOT_PATH_MODE_INCLUDE ONLY) windows-x86_64.cmake\rset(CMAKE_SYSTEM_NAME Windows) set(CMAKE_SYSTEM_PROCESSOR x86_64) set(CMAKE_C_COMPILER /opt/homebrew/bin/x86_64-w64-mingw32-gcc) set(CMAKE_CXX_COMPILER /opt/homebrew/bin/x86_64-w64-mingw32-g++) set(CMAKE_SYSROOT /opt/homebrew) set(CMAKE_FIND_ROOT_PATH_MODE_PROGRAM NEVER) set(CMAKE_FIND_ROOT_PATH_MODE_LIBRARY ONLY) set(CMAKE_FIND_ROOT_PATH_MODE_INCLUDE ONLY) windows-arm64.cmake\rset(CMAKE_SYSTEM_NAME Windows) set(CMAKE_SYSTEM_PROCESSOR ARM64) set(CMAKE_C_COMPILER /xxx/llvm-mingw/bin/aarch64-w64-mingw32-clang) set(CMAKE_CXX_COMPILER /xxx/llvm-mingw/bin/aarch64-w64-mingw32-clang++) set(CMAKE_SYSROOT /xxx/llvm-mingw/aarch64-w64-mingw32) set(CMAKE_FIND_ROOT_PATH_MODE_PROGRAM NEVER) set(CMAKE_FIND_ROOT_PATH_MODE_LIBRARY ONLY) set(CMAKE_FIND_ROOT_PATH_MODE_INCLUDE ONLY) 这里llvm-mingw的位置按照安装位置设定。 ","date":"2025-09-24","objectID":"/posts/%E4%BD%BF%E7%94%A8-python-%E8%B0%83%E7%94%A8-simulink-%E8%BF%9B%E8%A1%8C%E4%BB%BF%E7%9C%9F/:6:2","tags":["Simulink"],"title":"使用 Python 调用 Simulink 进行仿真","uri":"/posts/%E4%BD%BF%E7%94%A8-python-%E8%B0%83%E7%94%A8-simulink-%E8%BF%9B%E8%A1%8C%E4%BB%BF%E7%9C%9F/#linux-arm64cmake"},{"categories":[],"collections":["仿真"],"content":"24.2 编写 CMakePresets.json\r这个文件中定义了多种编译方式，以及各种编译方式的编译器和相关设置所在的位置： 以上是本人使用的编译方式，从上到下定义了 MacOS、Linux 和 Windows 的多种编译： MacOS的编译：使用的是本地的编译器，位置在/usr/bin/clang和/usr/bin/clang++，可能会因为安装方式的不同而发生变化，本人的操作系统在如下位置。 Linux和Windows的编译：这里使用了工具链（ToolChain）进行编译，可以看到工具链放在了${sourceDir}/cmake/toolchains/目录下，也就是在项目的根目录下有如下内容： linux-x86_64.cmake\rset(CMAKE_SYSTEM_NAME Linux) set(CMAKE_SYSTEM_PROCESSOR x86_64) set(CMAKE_C_COMPILER /opt/homebrew/bin/x86_64-linux-musl-gcc) set(CMAKE_CXX_COMPILER /opt/homebrew/bin/x86_64-linux-musl-g++) set(CMAKE_SYSROOT /opt/homebrew) set(CMAKE_FIND_ROOT_PATH_MODE_PROGRAM NEVER) set(CMAKE_FIND_ROOT_PATH_MODE_LIBRARY ONLY) set(CMAKE_FIND_ROOT_PATH_MODE_INCLUDE ONLY) linux-arm64.cmake\rset(CMAKE_SYSTEM_NAME Linux) set(CMAKE_SYSTEM_PROCESSOR aarch64) set(CMAKE_C_COMPILER /opt/homebrew/bin/aarch64-linux-musl-gcc) set(CMAKE_CXX_COMPILER /opt/homebrew/bin/aarch64-linux-musl-g++) set(CMAKE_SYSROOT /opt/homebrew) set(CMAKE_FIND_ROOT_PATH_MODE_PROGRAM NEVER) set(CMAKE_FIND_ROOT_PATH_MODE_LIBRARY ONLY) set(CMAKE_FIND_ROOT_PATH_MODE_INCLUDE ONLY) windows-x86_64.cmake\rset(CMAKE_SYSTEM_NAME Windows) set(CMAKE_SYSTEM_PROCESSOR x86_64) set(CMAKE_C_COMPILER /opt/homebrew/bin/x86_64-w64-mingw32-gcc) set(CMAKE_CXX_COMPILER /opt/homebrew/bin/x86_64-w64-mingw32-g++) set(CMAKE_SYSROOT /opt/homebrew) set(CMAKE_FIND_ROOT_PATH_MODE_PROGRAM NEVER) set(CMAKE_FIND_ROOT_PATH_MODE_LIBRARY ONLY) set(CMAKE_FIND_ROOT_PATH_MODE_INCLUDE ONLY) windows-arm64.cmake\rset(CMAKE_SYSTEM_NAME Windows) set(CMAKE_SYSTEM_PROCESSOR ARM64) set(CMAKE_C_COMPILER /xxx/llvm-mingw/bin/aarch64-w64-mingw32-clang) set(CMAKE_CXX_COMPILER /xxx/llvm-mingw/bin/aarch64-w64-mingw32-clang++) set(CMAKE_SYSROOT /xxx/llvm-mingw/aarch64-w64-mingw32) set(CMAKE_FIND_ROOT_PATH_MODE_PROGRAM NEVER) set(CMAKE_FIND_ROOT_PATH_MODE_LIBRARY ONLY) set(CMAKE_FIND_ROOT_PATH_MODE_INCLUDE ONLY) 这里llvm-mingw的位置按照安装位置设定。 ","date":"2025-09-24","objectID":"/posts/%E4%BD%BF%E7%94%A8-python-%E8%B0%83%E7%94%A8-simulink-%E8%BF%9B%E8%A1%8C%E4%BB%BF%E7%9C%9F/:6:2","tags":["Simulink"],"title":"使用 Python 调用 Simulink 进行仿真","uri":"/posts/%E4%BD%BF%E7%94%A8-python-%E8%B0%83%E7%94%A8-simulink-%E8%BF%9B%E8%A1%8C%E4%BB%BF%E7%9C%9F/#windows-x86_64cmake"},{"categories":[],"collections":["仿真"],"content":"24.2 编写 CMakePresets.json\r这个文件中定义了多种编译方式，以及各种编译方式的编译器和相关设置所在的位置： 以上是本人使用的编译方式，从上到下定义了 MacOS、Linux 和 Windows 的多种编译： MacOS的编译：使用的是本地的编译器，位置在/usr/bin/clang和/usr/bin/clang++，可能会因为安装方式的不同而发生变化，本人的操作系统在如下位置。 Linux和Windows的编译：这里使用了工具链（ToolChain）进行编译，可以看到工具链放在了${sourceDir}/cmake/toolchains/目录下，也就是在项目的根目录下有如下内容： linux-x86_64.cmake\rset(CMAKE_SYSTEM_NAME Linux) set(CMAKE_SYSTEM_PROCESSOR x86_64) set(CMAKE_C_COMPILER /opt/homebrew/bin/x86_64-linux-musl-gcc) set(CMAKE_CXX_COMPILER /opt/homebrew/bin/x86_64-linux-musl-g++) set(CMAKE_SYSROOT /opt/homebrew) set(CMAKE_FIND_ROOT_PATH_MODE_PROGRAM NEVER) set(CMAKE_FIND_ROOT_PATH_MODE_LIBRARY ONLY) set(CMAKE_FIND_ROOT_PATH_MODE_INCLUDE ONLY) linux-arm64.cmake\rset(CMAKE_SYSTEM_NAME Linux) set(CMAKE_SYSTEM_PROCESSOR aarch64) set(CMAKE_C_COMPILER /opt/homebrew/bin/aarch64-linux-musl-gcc) set(CMAKE_CXX_COMPILER /opt/homebrew/bin/aarch64-linux-musl-g++) set(CMAKE_SYSROOT /opt/homebrew) set(CMAKE_FIND_ROOT_PATH_MODE_PROGRAM NEVER) set(CMAKE_FIND_ROOT_PATH_MODE_LIBRARY ONLY) set(CMAKE_FIND_ROOT_PATH_MODE_INCLUDE ONLY) windows-x86_64.cmake\rset(CMAKE_SYSTEM_NAME Windows) set(CMAKE_SYSTEM_PROCESSOR x86_64) set(CMAKE_C_COMPILER /opt/homebrew/bin/x86_64-w64-mingw32-gcc) set(CMAKE_CXX_COMPILER /opt/homebrew/bin/x86_64-w64-mingw32-g++) set(CMAKE_SYSROOT /opt/homebrew) set(CMAKE_FIND_ROOT_PATH_MODE_PROGRAM NEVER) set(CMAKE_FIND_ROOT_PATH_MODE_LIBRARY ONLY) set(CMAKE_FIND_ROOT_PATH_MODE_INCLUDE ONLY) windows-arm64.cmake\rset(CMAKE_SYSTEM_NAME Windows) set(CMAKE_SYSTEM_PROCESSOR ARM64) set(CMAKE_C_COMPILER /xxx/llvm-mingw/bin/aarch64-w64-mingw32-clang) set(CMAKE_CXX_COMPILER /xxx/llvm-mingw/bin/aarch64-w64-mingw32-clang++) set(CMAKE_SYSROOT /xxx/llvm-mingw/aarch64-w64-mingw32) set(CMAKE_FIND_ROOT_PATH_MODE_PROGRAM NEVER) set(CMAKE_FIND_ROOT_PATH_MODE_LIBRARY ONLY) set(CMAKE_FIND_ROOT_PATH_MODE_INCLUDE ONLY) 这里llvm-mingw的位置按照安装位置设定。 ","date":"2025-09-24","objectID":"/posts/%E4%BD%BF%E7%94%A8-python-%E8%B0%83%E7%94%A8-simulink-%E8%BF%9B%E8%A1%8C%E4%BB%BF%E7%9C%9F/:6:2","tags":["Simulink"],"title":"使用 Python 调用 Simulink 进行仿真","uri":"/posts/%E4%BD%BF%E7%94%A8-python-%E8%B0%83%E7%94%A8-simulink-%E8%BF%9B%E8%A1%8C%E4%BB%BF%E7%9C%9F/#windows-arm64cmake"},{"categories":[],"collections":["仿真"],"content":"24.3 执行编译\r执行编译的步骤如下： # MacOS-x86_64 cmake --preset macos-x86_64 cmake --build build/macos-x86_64 # MacOS-arm64 cmake --preset macos-arm64 cmake --build build/macos-arm64 # Windows-x86_64 cmake --preset windows-x86_64 cmake --build build/windows-x86_64 # Windows-arm64 cmake --preset windows-arm64 cmake --build build/windows-arm64 # Linux-x86_64 cmake --preset linux-x86_64 cmake --build build/linux-x86_64 # Linux-arm64 cmake --preset linux-arm64 cmake --build build/linux-arm64 ","date":"2025-09-24","objectID":"/posts/%E4%BD%BF%E7%94%A8-python-%E8%B0%83%E7%94%A8-simulink-%E8%BF%9B%E8%A1%8C%E4%BB%BF%E7%9C%9F/:6:3","tags":["Simulink"],"title":"使用 Python 调用 Simulink 进行仿真","uri":"/posts/%E4%BD%BF%E7%94%A8-python-%E8%B0%83%E7%94%A8-simulink-%E8%BF%9B%E8%A1%8C%E4%BB%BF%E7%9C%9F/#执行编译"},{"categories":[],"collections":["大模型基础"],"content":"第 1 节 概述\r随着模型训练数据规模和参数数量的持续增长，大语言模型突破了泛化瓶颈，并涌现出了强大的指令跟随能力。泛化能力的增强使得模型能够处理和理解多种未知任务，而指令跟随能力的提升则确保了模型能够准确响应人类的指令。 ","date":"2025-08-26","objectID":"/posts/%E5%A4%A7%E6%A8%A1%E5%9E%8B%E5%9F%BA%E7%A1%80~03.prompt-%E5%B7%A5%E7%A8%8B/:1:0","tags":["大模型"],"title":"大模型基础~03.Prompt 工程","uri":"/posts/%E5%A4%A7%E6%A8%A1%E5%9E%8B%E5%9F%BA%E7%A1%80~03.prompt-%E5%B7%A5%E7%A8%8B/#概述"},{"categories":[],"collections":["大模型基础"],"content":"1.1 Prompt 与 Prompt 工程\r「Prompt」：是指用于指导生成式人工智能模型执行特定任务的输入指令，这些指令通常以自然语言文本的形式出现。 「Prompt 工程」：又称提示工程，是指设计和优化用于与生成式人工智能模型交互的 Prompt 的过程。这种技术的核心在于，将新任务通过 Prompt 构建为模型在预训练阶段已经熟悉的形式，利用模型固有的泛化能力来执行新的任务，而无需在额外的特定任务上进行训练。 经过良好设计的Prompt 通常由任务说明、上下文、问题、输出格式四个基本元素组成： 「任务说明」：向模型明确提出具体的任务要求。任务说明应当清晰、直接，并尽可能详细地描述期望模型完成的任务。 「上下文」：向模型提供的任务相关背景信息，用以增强其对任务的理解以及提供解决任务的思路。上下文可以包括特定的知识前提、目标受众的背景、相关任务的示例，或任何有助于模型更好地理解任务的信息。 「问题」：向模型描述用户的具体问题或需要处理的信息。这部分应直接涉及用户的查询或任务，为模型提供一个明确的起点。问题可以是显式的提问，也可以是隐式的陈述句，用以表达用户的潜在疑问。 「输出格式」：期望模型给出的回答的展示形式。这包括输出的格式，以及任何特定的细节要求，如简洁性或详细程度。例如，可以指示模型以JSON 格式输出结果。 在此基础上，Prompt 工程包括多种技巧和技术，如上下文学习（In-Context Learning）和思维链（Chain of Thought）等。 ","date":"2025-08-26","objectID":"/posts/%E5%A4%A7%E6%A8%A1%E5%9E%8B%E5%9F%BA%E7%A1%80~03.prompt-%E5%B7%A5%E7%A8%8B/:1:1","tags":["大模型"],"title":"大模型基础~03.Prompt 工程","uri":"/posts/%E5%A4%A7%E6%A8%A1%E5%9E%8B%E5%9F%BA%E7%A1%80~03.prompt-%E5%B7%A5%E7%A8%8B/#prompt-与-prompt-工程"},{"categories":[],"collections":["大模型基础"],"content":"1.2 Prompt 分词向量化\r语言模型无法直接理解文本，在 Prompt 进入大模型之前，需要将它拆分成 Token 序列。 在构建大语言模型的词表时，分词器依赖于分词算法，如 BBPE 、BPE 和WordPiece 等，这些算法通过分析语料库中的词频等信息来划分 Token。BBPE 算法的流程主要包括以下四个步骤： 「初始化词表」：首先，将所有字符按照其底层编码拆分为若干字节，并将这些单字节编码作为初始词表的 Token。 「统计词频」：接下来，统计词表中所有 Token 对（即相邻 Token 的组合）的出现频率。在初始阶段，Token 对即为相邻字节的组合。 「合并高频 Token 对」：然后，选择出现频率最高的Token 对，将其合并成一个新的Token 并加入词表。 「迭代合并」：重复步骤 2 和步骤 3，不断迭代合并，直至达到预设的词表大小或达到指定的合并次数。 词表中收录了语料库中高频出现的词语或短语，形成独立的 Token。 示例\r「干脆」一词在词表中以一个 Token 来表示。 为了优化 Token 空间并压缩词表大小，构建词表时会包含了一些特殊的 Token，这些 Token 既能单独表示语义，也能通过两两组合，表示语料库中低频出现的生僻字。 示例\r「浣」字使用两个 Token，$æ\\mu$ 和 $£$ 来表示。 在完成分词之后，这些 Token 随后会经过模型的嵌入矩阵（Embedding Matrix）处理，转化为固定大小的表征向量。 在模型生成阶段，模型会根据输入的向量序列计算出词表中每个词的概率分布。模型从这些概率分布中选择并输出对应的 Token，这些 Token 再被转换为相应的文本内容。 ","date":"2025-08-26","objectID":"/posts/%E5%A4%A7%E6%A8%A1%E5%9E%8B%E5%9F%BA%E7%A1%80~03.prompt-%E5%B7%A5%E7%A8%8B/:1:2","tags":["大模型"],"title":"大模型基础~03.Prompt 工程","uri":"/posts/%E5%A4%A7%E6%A8%A1%E5%9E%8B%E5%9F%BA%E7%A1%80~03.prompt-%E5%B7%A5%E7%A8%8B/#prompt-分词向量化"},{"categories":[],"collections":["大模型基础"],"content":"第 2 节 上下文学习\r上下文学习（In-Context Learning, ICL）是一种通过构造特定的 Prompt，来使得语言模型理解并学习下游任务的范式，这些特定的 Prompt 中可以包含演示示例，任务说明等元素。 按照示例数量的不同，上下文学习可以呈现出多种形式：零样本（Zero-shot）、单样本（One-shot）和少样本（Few-shot）。 「零样本上下文学习」：在此种学习方式下，仅需向模型提供任务说明，而无需提供任何示例。其具有强大的场景泛化能力。但零样本学习的性能完全依赖于大语言模型的能力，并且在处理任务时可能表现欠佳。 「单样本上下文学习」：这种方式仅需为模型提供一个示例，贴合人类“举一反三”的学习模式。不过，单样本学习的效果强依赖于示例相对于任务的代表性。 「少样本上下文学习」：这种方法通过为模型提供少量的示例（通常为几个至十几个），显著提升模型在特定任务上的表现。但在，示例的增加会显著增加大语言模型推理时的计算成本。示例的代表性和多样性也将影响其生成效果。 上下文学习为何奏效？\r在大语言模型的预训练阶段，模型从大量文本中学习潜在的概念。当运用上下文学习进行推理时，大语言模型借助演示示例来“锚定”其在预训练期间所习得的相关概念，从而进行上下文学习，并对问题进行预测。 示例\r模型之所以能给出正确答案，是因为模型在预训练时已经学习到与情感相关的概念，比如积极情感的表现形式（满意的笑容，手舞足蹈…..）、句法结构和句法关系等等，当模型在推理时，借助「浣熊吃了一包麻辣味的干脆面，辣得肚子疼。\\n 消极」等示例“锚定”到情感等相关概念，并基于这些概念，给出问题答案。 ","date":"2025-08-26","objectID":"/posts/%E5%A4%A7%E6%A8%A1%E5%9E%8B%E5%9F%BA%E7%A1%80~03.prompt-%E5%B7%A5%E7%A8%8B/:2:0","tags":["大模型"],"title":"大模型基础~03.Prompt 工程","uri":"/posts/%E5%A4%A7%E6%A8%A1%E5%9E%8B%E5%9F%BA%E7%A1%80~03.prompt-%E5%B7%A5%E7%A8%8B/#上下文学习"},{"categories":[],"collections":["大模型基础"],"content":"2.1 演示示例选择\r演示示例在引导大语言模型理解任务中扮演着重要作用，其内容和质量直接影响着学习效果，演示示例选择主要依靠相似性和多样性： 「相似性」：精心挑选出与待解决问题最为相近的示例。相似性可以从多个层面进行度量，如语言层面的相似性（包括关键字匹配或语义相似度匹配）、结构相似性等等。通过选取相似的示例，能够为模型提供与待解决问题接近的参照，使大语言模型更易理解该问题。 「多样性」：所选的示例涵盖尽量广的内容，扩大演示示例对待解决问题的覆盖范围。多样化的示例能够帮助模型从不同的角度去理解任务，增强其应对各种问题的能力。 直接检索\r给定一组候选示例，直接检索的方法依据候选示例与待解决问题间的相似性对候选示例进行排序，然后选取排名靠前的 K 个示例。 直接检索的代表性方法是 KATE： 利用 RoBERTa 对待解决问题和候选示例（移除标签）进行编码。 然后通过计算解决问题编码和候选示例编码间的向量余弦相似度对二者的相似度进行评分。 基于此评分，选择评分最高的 K 个示例作为上下文学习的演示示例。 信息\r直接检索的方法简单易操作，是目前应用广泛的示例选择策略之一。但是，其未对示例的多样性进行考虑，选择出的示例可能趋向同质化。 聚类检索\r聚类检索方法采用先聚类后检索的方法来保证检索结果的多样性。其先把所有候选示例划分为K 个簇，然后从每个簇中选取最为相似的一个示例。 Self-Prompting 是其中的代表性方法： 首先将候选示例和待解决问题编码成向量形式。 接着运用K-Means 算法把示例集合聚为 K 个簇。 依照问题与示例之间的余弦相似度，从每个簇中选取与问题最相似的示例，由此得到 K 个示例。 信息\r虽然聚类检索方法提高了示例的多样性，但因为有些簇与问题可能并不相似，导致选择的示例的相似性可能不够高。 迭代检索\r为了兼顾相似性和多样性，迭代检索策略应运而生。迭代检索首先挑选与问题高度相似的示例，随后在迭代过程中，结合当前问题和已选示例，动态选择下一个示例，从而确保所选示例的相似性和多样性。 RetICL 是迭代检索的代表性方法： 根据当前问题初始化基于 LSTM 的检索器内部状态，并选择一个示例。 接着根据当前问题和所选示例集更新检索器内部状态，并选择下一个示例。 这一过程不断迭代，直到得到 k 个示例。 信息\r尽管迭代检索在计算上相对复杂，但其能够生成更优的示例集，在复杂任务中展现出更好的适应性和灵活性。 ","date":"2025-08-26","objectID":"/posts/%E5%A4%A7%E6%A8%A1%E5%9E%8B%E5%9F%BA%E7%A1%80~03.prompt-%E5%B7%A5%E7%A8%8B/:2:1","tags":["大模型"],"title":"大模型基础~03.Prompt 工程","uri":"/posts/%E5%A4%A7%E6%A8%A1%E5%9E%8B%E5%9F%BA%E7%A1%80~03.prompt-%E5%B7%A5%E7%A8%8B/#演示示例选择"},{"categories":[],"collections":["大模型基础"],"content":"2.1 演示示例选择\r演示示例在引导大语言模型理解任务中扮演着重要作用，其内容和质量直接影响着学习效果，演示示例选择主要依靠相似性和多样性： 「相似性」：精心挑选出与待解决问题最为相近的示例。相似性可以从多个层面进行度量，如语言层面的相似性（包括关键字匹配或语义相似度匹配）、结构相似性等等。通过选取相似的示例，能够为模型提供与待解决问题接近的参照，使大语言模型更易理解该问题。 「多样性」：所选的示例涵盖尽量广的内容，扩大演示示例对待解决问题的覆盖范围。多样化的示例能够帮助模型从不同的角度去理解任务，增强其应对各种问题的能力。 直接检索\r给定一组候选示例，直接检索的方法依据候选示例与待解决问题间的相似性对候选示例进行排序，然后选取排名靠前的 K 个示例。 直接检索的代表性方法是 KATE： 利用 RoBERTa 对待解决问题和候选示例（移除标签）进行编码。 然后通过计算解决问题编码和候选示例编码间的向量余弦相似度对二者的相似度进行评分。 基于此评分，选择评分最高的 K 个示例作为上下文学习的演示示例。 信息\r直接检索的方法简单易操作，是目前应用广泛的示例选择策略之一。但是，其未对示例的多样性进行考虑，选择出的示例可能趋向同质化。 聚类检索\r聚类检索方法采用先聚类后检索的方法来保证检索结果的多样性。其先把所有候选示例划分为K 个簇，然后从每个簇中选取最为相似的一个示例。 Self-Prompting 是其中的代表性方法： 首先将候选示例和待解决问题编码成向量形式。 接着运用K-Means 算法把示例集合聚为 K 个簇。 依照问题与示例之间的余弦相似度，从每个簇中选取与问题最相似的示例，由此得到 K 个示例。 信息\r虽然聚类检索方法提高了示例的多样性，但因为有些簇与问题可能并不相似，导致选择的示例的相似性可能不够高。 迭代检索\r为了兼顾相似性和多样性，迭代检索策略应运而生。迭代检索首先挑选与问题高度相似的示例，随后在迭代过程中，结合当前问题和已选示例，动态选择下一个示例，从而确保所选示例的相似性和多样性。 RetICL 是迭代检索的代表性方法： 根据当前问题初始化基于 LSTM 的检索器内部状态，并选择一个示例。 接着根据当前问题和所选示例集更新检索器内部状态，并选择下一个示例。 这一过程不断迭代，直到得到 k 个示例。 信息\r尽管迭代检索在计算上相对复杂，但其能够生成更优的示例集，在复杂任务中展现出更好的适应性和灵活性。 ","date":"2025-08-26","objectID":"/posts/%E5%A4%A7%E6%A8%A1%E5%9E%8B%E5%9F%BA%E7%A1%80~03.prompt-%E5%B7%A5%E7%A8%8B/:2:1","tags":["大模型"],"title":"大模型基础~03.Prompt 工程","uri":"/posts/%E5%A4%A7%E6%A8%A1%E5%9E%8B%E5%9F%BA%E7%A1%80~03.prompt-%E5%B7%A5%E7%A8%8B/#直接检索"},{"categories":[],"collections":["大模型基础"],"content":"2.1 演示示例选择\r演示示例在引导大语言模型理解任务中扮演着重要作用，其内容和质量直接影响着学习效果，演示示例选择主要依靠相似性和多样性： 「相似性」：精心挑选出与待解决问题最为相近的示例。相似性可以从多个层面进行度量，如语言层面的相似性（包括关键字匹配或语义相似度匹配）、结构相似性等等。通过选取相似的示例，能够为模型提供与待解决问题接近的参照，使大语言模型更易理解该问题。 「多样性」：所选的示例涵盖尽量广的内容，扩大演示示例对待解决问题的覆盖范围。多样化的示例能够帮助模型从不同的角度去理解任务，增强其应对各种问题的能力。 直接检索\r给定一组候选示例，直接检索的方法依据候选示例与待解决问题间的相似性对候选示例进行排序，然后选取排名靠前的 K 个示例。 直接检索的代表性方法是 KATE： 利用 RoBERTa 对待解决问题和候选示例（移除标签）进行编码。 然后通过计算解决问题编码和候选示例编码间的向量余弦相似度对二者的相似度进行评分。 基于此评分，选择评分最高的 K 个示例作为上下文学习的演示示例。 信息\r直接检索的方法简单易操作，是目前应用广泛的示例选择策略之一。但是，其未对示例的多样性进行考虑，选择出的示例可能趋向同质化。 聚类检索\r聚类检索方法采用先聚类后检索的方法来保证检索结果的多样性。其先把所有候选示例划分为K 个簇，然后从每个簇中选取最为相似的一个示例。 Self-Prompting 是其中的代表性方法： 首先将候选示例和待解决问题编码成向量形式。 接着运用K-Means 算法把示例集合聚为 K 个簇。 依照问题与示例之间的余弦相似度，从每个簇中选取与问题最相似的示例，由此得到 K 个示例。 信息\r虽然聚类检索方法提高了示例的多样性，但因为有些簇与问题可能并不相似，导致选择的示例的相似性可能不够高。 迭代检索\r为了兼顾相似性和多样性，迭代检索策略应运而生。迭代检索首先挑选与问题高度相似的示例，随后在迭代过程中，结合当前问题和已选示例，动态选择下一个示例，从而确保所选示例的相似性和多样性。 RetICL 是迭代检索的代表性方法： 根据当前问题初始化基于 LSTM 的检索器内部状态，并选择一个示例。 接着根据当前问题和所选示例集更新检索器内部状态，并选择下一个示例。 这一过程不断迭代，直到得到 k 个示例。 信息\r尽管迭代检索在计算上相对复杂，但其能够生成更优的示例集，在复杂任务中展现出更好的适应性和灵活性。 ","date":"2025-08-26","objectID":"/posts/%E5%A4%A7%E6%A8%A1%E5%9E%8B%E5%9F%BA%E7%A1%80~03.prompt-%E5%B7%A5%E7%A8%8B/:2:1","tags":["大模型"],"title":"大模型基础~03.Prompt 工程","uri":"/posts/%E5%A4%A7%E6%A8%A1%E5%9E%8B%E5%9F%BA%E7%A1%80~03.prompt-%E5%B7%A5%E7%A8%8B/#聚类检索"},{"categories":[],"collections":["大模型基础"],"content":"2.1 演示示例选择\r演示示例在引导大语言模型理解任务中扮演着重要作用，其内容和质量直接影响着学习效果，演示示例选择主要依靠相似性和多样性： 「相似性」：精心挑选出与待解决问题最为相近的示例。相似性可以从多个层面进行度量，如语言层面的相似性（包括关键字匹配或语义相似度匹配）、结构相似性等等。通过选取相似的示例，能够为模型提供与待解决问题接近的参照，使大语言模型更易理解该问题。 「多样性」：所选的示例涵盖尽量广的内容，扩大演示示例对待解决问题的覆盖范围。多样化的示例能够帮助模型从不同的角度去理解任务，增强其应对各种问题的能力。 直接检索\r给定一组候选示例，直接检索的方法依据候选示例与待解决问题间的相似性对候选示例进行排序，然后选取排名靠前的 K 个示例。 直接检索的代表性方法是 KATE： 利用 RoBERTa 对待解决问题和候选示例（移除标签）进行编码。 然后通过计算解决问题编码和候选示例编码间的向量余弦相似度对二者的相似度进行评分。 基于此评分，选择评分最高的 K 个示例作为上下文学习的演示示例。 信息\r直接检索的方法简单易操作，是目前应用广泛的示例选择策略之一。但是，其未对示例的多样性进行考虑，选择出的示例可能趋向同质化。 聚类检索\r聚类检索方法采用先聚类后检索的方法来保证检索结果的多样性。其先把所有候选示例划分为K 个簇，然后从每个簇中选取最为相似的一个示例。 Self-Prompting 是其中的代表性方法： 首先将候选示例和待解决问题编码成向量形式。 接着运用K-Means 算法把示例集合聚为 K 个簇。 依照问题与示例之间的余弦相似度，从每个簇中选取与问题最相似的示例，由此得到 K 个示例。 信息\r虽然聚类检索方法提高了示例的多样性，但因为有些簇与问题可能并不相似，导致选择的示例的相似性可能不够高。 迭代检索\r为了兼顾相似性和多样性，迭代检索策略应运而生。迭代检索首先挑选与问题高度相似的示例，随后在迭代过程中，结合当前问题和已选示例，动态选择下一个示例，从而确保所选示例的相似性和多样性。 RetICL 是迭代检索的代表性方法： 根据当前问题初始化基于 LSTM 的检索器内部状态，并选择一个示例。 接着根据当前问题和所选示例集更新检索器内部状态，并选择下一个示例。 这一过程不断迭代，直到得到 k 个示例。 信息\r尽管迭代检索在计算上相对复杂，但其能够生成更优的示例集，在复杂任务中展现出更好的适应性和灵活性。 ","date":"2025-08-26","objectID":"/posts/%E5%A4%A7%E6%A8%A1%E5%9E%8B%E5%9F%BA%E7%A1%80~03.prompt-%E5%B7%A5%E7%A8%8B/:2:1","tags":["大模型"],"title":"大模型基础~03.Prompt 工程","uri":"/posts/%E5%A4%A7%E6%A8%A1%E5%9E%8B%E5%9F%BA%E7%A1%80~03.prompt-%E5%B7%A5%E7%A8%8B/#迭代检索"},{"categories":[],"collections":["大模型基础"],"content":"2.2 演示示例的影响\r我们已经讨论了演示示例选择对上下文学习的重要性。接下来，我们将继续探讨演示示例的格式、输入-标签映射、演示示例数量及顺序对上下文学习的影响。 演示示例的格式\r不同的任务对于示例格式的要求不同： 对于简单的任务，我们只需要在演示示例中给出输入以及输出即可。 但是，一些复杂推理任务，例如算术、代码等，仅仅给出输入和输出不足以让模型掌握其中的推理过程。在这种情况下，以思维链的形式构造示例，即在输入和输出之间添加中间推理步骤，能帮助模型逐步推理并学习到更复杂的映射关系。 输入-输出映射\r对于一个包含输入、输出的示例，大语言模型旨在从中学习到输入-输出映射，以完成目标任务。如果给定示例中的输入-输出映射是错误的，势必会对上下文学习的效果产生影响。 信息\r对输入-输出映射错误的敏感性与模型规模大小有关。 较大的模型对输入-输出映射的正确性表现出更高的敏感性。 较小的模型对输入-输出映射错误的敏感性较弱。 演示示例的数量和顺序\r增加演示示例的数量通常能够提升上下文学习性能，但随着示例数量的增多，性能提升的速率会逐渐减缓。相较于分类任务，生成任务更能从增加的示例数量中获益。 演示示例的顺序同样是影响上下文学习性能的关键因素，不同示例顺序下的模型表现存在显著差异。 最优的示例顺序具有模型依赖性，即对某一模型有效的示例顺序未必适用于其他模型。 示例顺序的选择也受到所使用数据集的特定特性影响 ","date":"2025-08-26","objectID":"/posts/%E5%A4%A7%E6%A8%A1%E5%9E%8B%E5%9F%BA%E7%A1%80~03.prompt-%E5%B7%A5%E7%A8%8B/:2:2","tags":["大模型"],"title":"大模型基础~03.Prompt 工程","uri":"/posts/%E5%A4%A7%E6%A8%A1%E5%9E%8B%E5%9F%BA%E7%A1%80~03.prompt-%E5%B7%A5%E7%A8%8B/#演示示例的影响"},{"categories":[],"collections":["大模型基础"],"content":"2.2 演示示例的影响\r我们已经讨论了演示示例选择对上下文学习的重要性。接下来，我们将继续探讨演示示例的格式、输入-标签映射、演示示例数量及顺序对上下文学习的影响。 演示示例的格式\r不同的任务对于示例格式的要求不同： 对于简单的任务，我们只需要在演示示例中给出输入以及输出即可。 但是，一些复杂推理任务，例如算术、代码等，仅仅给出输入和输出不足以让模型掌握其中的推理过程。在这种情况下，以思维链的形式构造示例，即在输入和输出之间添加中间推理步骤，能帮助模型逐步推理并学习到更复杂的映射关系。 输入-输出映射\r对于一个包含输入、输出的示例，大语言模型旨在从中学习到输入-输出映射，以完成目标任务。如果给定示例中的输入-输出映射是错误的，势必会对上下文学习的效果产生影响。 信息\r对输入-输出映射错误的敏感性与模型规模大小有关。 较大的模型对输入-输出映射的正确性表现出更高的敏感性。 较小的模型对输入-输出映射错误的敏感性较弱。 演示示例的数量和顺序\r增加演示示例的数量通常能够提升上下文学习性能，但随着示例数量的增多，性能提升的速率会逐渐减缓。相较于分类任务，生成任务更能从增加的示例数量中获益。 演示示例的顺序同样是影响上下文学习性能的关键因素，不同示例顺序下的模型表现存在显著差异。 最优的示例顺序具有模型依赖性，即对某一模型有效的示例顺序未必适用于其他模型。 示例顺序的选择也受到所使用数据集的特定特性影响 ","date":"2025-08-26","objectID":"/posts/%E5%A4%A7%E6%A8%A1%E5%9E%8B%E5%9F%BA%E7%A1%80~03.prompt-%E5%B7%A5%E7%A8%8B/:2:2","tags":["大模型"],"title":"大模型基础~03.Prompt 工程","uri":"/posts/%E5%A4%A7%E6%A8%A1%E5%9E%8B%E5%9F%BA%E7%A1%80~03.prompt-%E5%B7%A5%E7%A8%8B/#演示示例的格式"},{"categories":[],"collections":["大模型基础"],"content":"2.2 演示示例的影响\r我们已经讨论了演示示例选择对上下文学习的重要性。接下来，我们将继续探讨演示示例的格式、输入-标签映射、演示示例数量及顺序对上下文学习的影响。 演示示例的格式\r不同的任务对于示例格式的要求不同： 对于简单的任务，我们只需要在演示示例中给出输入以及输出即可。 但是，一些复杂推理任务，例如算术、代码等，仅仅给出输入和输出不足以让模型掌握其中的推理过程。在这种情况下，以思维链的形式构造示例，即在输入和输出之间添加中间推理步骤，能帮助模型逐步推理并学习到更复杂的映射关系。 输入-输出映射\r对于一个包含输入、输出的示例，大语言模型旨在从中学习到输入-输出映射，以完成目标任务。如果给定示例中的输入-输出映射是错误的，势必会对上下文学习的效果产生影响。 信息\r对输入-输出映射错误的敏感性与模型规模大小有关。 较大的模型对输入-输出映射的正确性表现出更高的敏感性。 较小的模型对输入-输出映射错误的敏感性较弱。 演示示例的数量和顺序\r增加演示示例的数量通常能够提升上下文学习性能，但随着示例数量的增多，性能提升的速率会逐渐减缓。相较于分类任务，生成任务更能从增加的示例数量中获益。 演示示例的顺序同样是影响上下文学习性能的关键因素，不同示例顺序下的模型表现存在显著差异。 最优的示例顺序具有模型依赖性，即对某一模型有效的示例顺序未必适用于其他模型。 示例顺序的选择也受到所使用数据集的特定特性影响 ","date":"2025-08-26","objectID":"/posts/%E5%A4%A7%E6%A8%A1%E5%9E%8B%E5%9F%BA%E7%A1%80~03.prompt-%E5%B7%A5%E7%A8%8B/:2:2","tags":["大模型"],"title":"大模型基础~03.Prompt 工程","uri":"/posts/%E5%A4%A7%E6%A8%A1%E5%9E%8B%E5%9F%BA%E7%A1%80~03.prompt-%E5%B7%A5%E7%A8%8B/#输入-输出映射"},{"categories":[],"collections":["大模型基础"],"content":"2.2 演示示例的影响\r我们已经讨论了演示示例选择对上下文学习的重要性。接下来，我们将继续探讨演示示例的格式、输入-标签映射、演示示例数量及顺序对上下文学习的影响。 演示示例的格式\r不同的任务对于示例格式的要求不同： 对于简单的任务，我们只需要在演示示例中给出输入以及输出即可。 但是，一些复杂推理任务，例如算术、代码等，仅仅给出输入和输出不足以让模型掌握其中的推理过程。在这种情况下，以思维链的形式构造示例，即在输入和输出之间添加中间推理步骤，能帮助模型逐步推理并学习到更复杂的映射关系。 输入-输出映射\r对于一个包含输入、输出的示例，大语言模型旨在从中学习到输入-输出映射，以完成目标任务。如果给定示例中的输入-输出映射是错误的，势必会对上下文学习的效果产生影响。 信息\r对输入-输出映射错误的敏感性与模型规模大小有关。 较大的模型对输入-输出映射的正确性表现出更高的敏感性。 较小的模型对输入-输出映射错误的敏感性较弱。 演示示例的数量和顺序\r增加演示示例的数量通常能够提升上下文学习性能，但随着示例数量的增多，性能提升的速率会逐渐减缓。相较于分类任务，生成任务更能从增加的示例数量中获益。 演示示例的顺序同样是影响上下文学习性能的关键因素，不同示例顺序下的模型表现存在显著差异。 最优的示例顺序具有模型依赖性，即对某一模型有效的示例顺序未必适用于其他模型。 示例顺序的选择也受到所使用数据集的特定特性影响 ","date":"2025-08-26","objectID":"/posts/%E5%A4%A7%E6%A8%A1%E5%9E%8B%E5%9F%BA%E7%A1%80~03.prompt-%E5%B7%A5%E7%A8%8B/:2:2","tags":["大模型"],"title":"大模型基础~03.Prompt 工程","uri":"/posts/%E5%A4%A7%E6%A8%A1%E5%9E%8B%E5%9F%BA%E7%A1%80~03.prompt-%E5%B7%A5%E7%A8%8B/#演示示例的数量和顺序"},{"categories":[],"collections":["大模型基础"],"content":"2.3 预训练数据的影响\r预训练数据是上下文学习能力的来源，深刻影响着上下文学习的性能。对预训练数据而言，以下三方面因素是影响上下文学习性能的关键： 「领域丰富度」：预训练数据的所覆盖的领域的丰富度直接影响模型的领域泛化能力。在丰富的跨领域语料库进行预训练的模型具备更稳定的上下文学习能力。而单一领域的语料库可能限制模型的适应性，即使该领域与目标任务高度相关，也无法保证最佳的上下文学习性。 「任务多样性」：预训练数据中的任务多样性是提升上下文学习性能的重要因素。多样化的任务类型有助于模型学习到更广泛的知识和技能，增强其任务泛化能力，从而在面对新任务时表现也更为出。 「训练数据的分布特性」：训练数据中存在突发性分布和罕见类别时，能够增强模型的上下文学习能力。突发性分布使得“文本-标签映射”的数据模式在整个训练过程中得以反复出现，罕见类别增强模型处理少见或新颖输入的能力，从而提升模型上下文学习的表现。 ","date":"2025-08-26","objectID":"/posts/%E5%A4%A7%E6%A8%A1%E5%9E%8B%E5%9F%BA%E7%A1%80~03.prompt-%E5%B7%A5%E7%A8%8B/:2:3","tags":["大模型"],"title":"大模型基础~03.Prompt 工程","uri":"/posts/%E5%A4%A7%E6%A8%A1%E5%9E%8B%E5%9F%BA%E7%A1%80~03.prompt-%E5%B7%A5%E7%A8%8B/#预训练数据的影响"},{"categories":[],"collections":["大模型基础"],"content":"2.4 预训练模型的影响\r预训练模型对上下文学习的性能影响主要体现在模型参数规模上。当模型参数达到一定的规模时，上下文学习才能得以涌现。 示例\r通常，模型的参数数量需达到亿级别及以上。常见的拥有上下文学习能力的模型中，规模最小的是来自阿里巴巴通义实验室的 Qwen2-0.5B 模型，具有 5 亿参数。 ","date":"2025-08-26","objectID":"/posts/%E5%A4%A7%E6%A8%A1%E5%9E%8B%E5%9F%BA%E7%A1%80~03.prompt-%E5%B7%A5%E7%A8%8B/:2:4","tags":["大模型"],"title":"大模型基础~03.Prompt 工程","uri":"/posts/%E5%A4%A7%E6%A8%A1%E5%9E%8B%E5%9F%BA%E7%A1%80~03.prompt-%E5%B7%A5%E7%A8%8B/#预训练模型的影响"},{"categories":[],"collections":["大模型基础"],"content":"第 3 节 思维链\r在面对算术求解、常识判断和符号推理等需要复杂推理能力的任务时，模型参数规模的增长并未带来预期的性能突破，这种现象被称作“Flat Scaling Curves”。 思维链提示（Chain-of-Thought，CoT）通过模拟人类解决复杂问题时的思考过程，引导大语言模型在生成答案的过程中引入一系列的中间推理步骤。这种方法不仅能够显著提升模型在推理任务上的表现，而且还能够揭示模型在处理复杂问题时的内部逻辑和推理路径。 CoT 方法的核心是构造合适的 Prompt 以触发大语言模型一步一步生成推理路径，并生成最终答案。早期方法在构造 Prompt 时，加入少量包含推理过程的样本示例（Few-Shot Demonstrations），来引导模型一步一步生成答案。 在 CoT 核心思想的指引下，衍生出了一系列的扩展的方法。这些扩展的方法按照其推理方式的不同，可以归纳为三种模式：按部就班、三思后行和集思广益。 「按部就班」：模型一步接着一步地进行推理，推理路径形成了一条逻辑连贯的链条。在这种模式下，模型像是在遵循一条预设的逻辑路径，“按部就班”的一步步向前。这种模式以CoT、Zero-Shot CoT、Auto-CoT 等方法为代表。 「三思后行」：模型每一步都停下来评估当前的情况，然后从多个推理方向中选择出下一步的行进方向。在这种模式下，模型像是在探索一片未知的森林，模型在每一步都会停下来评估周围的环境，“三思后行”以找出最佳推理路径。这种模式以ToT、GoT 等方法为代表。 「集思广益」：模型同时生成多条推理路径并得到多个结果，然后整合这些结果，得到一个更为全面和准确的答案。在这种模式下，模型像是在召开一场智者的会议，每个智者都带来了自己的见解，最终通过讨论和整合，“集思广益”得出一个更优的结论。这一类模式以Self-Consistency 等方法为代表。 ","date":"2025-08-26","objectID":"/posts/%E5%A4%A7%E6%A8%A1%E5%9E%8B%E5%9F%BA%E7%A1%80~03.prompt-%E5%B7%A5%E7%A8%8B/:3:0","tags":["大模型"],"title":"大模型基础~03.Prompt 工程","uri":"/posts/%E5%A4%A7%E6%A8%A1%E5%9E%8B%E5%9F%BA%E7%A1%80~03.prompt-%E5%B7%A5%E7%A8%8B/#思维链"},{"categories":[],"collections":["大模型基础"],"content":"3.1 按部就班\r原始的少样本思维链（CoT）方法就采用了按部就班模式。其通过手工构造几个一步一步推理回答问题的例子作为示例放入 Prompt 中，来引导模型一步一步生成推理步骤，并生成最终的答案。 这种方法在提升模型推理能力方面取得了一定的成功，但是需要费时费力地手工编写大量 CoT 示例，并且过度依赖于CoT 的编写质量。 Zero-Shot CoT\rZero-Shot CoT 通过简单的提示，如“Let’s think step by step”，引导模型自行生成一条推理链。其无需手工标注的 CoT 示例，减少了对人工示例的依赖，多个推理任务上展现出了与原始少样本CoT 相媲美甚至更优的性能。 Zero-Shot CoT 使用两阶段方法来回答问题： 首先，在第一阶段，在问题后面跟上一句“让我们一步一步思考”或者“Let’s think step by step”来作为 CoT 的提示触发词，来指示大语言模型先生成中间推理步骤，再生成最后的答案。 在第二阶段，把原始的问题以及第一阶段生成的推理步骤拼接在一起，在末尾加上一句“Therefore, the answer is”或者“因此，最终答案为”，把这些内容输给大语言模型，让他输出最终的答案。 Auto CoT\rAuto-CoT 引入与待解决问题相关的问题及其推理链作为示例，以继续提升CoT 的效果。相关示例的生成过程是由大语言模型自动完成的，无需手工标注。 Auto-CoT 的流程如下： 利用聚类技术从问题库中筛选出与用户提问位于一个簇中的问题。 然后，借助 Zero-Shot CoT 的方式，为筛选出的问题生成推理链，形成示例。这些示例包含了不同问题及其对应的推理内容，可为模型提供不同解题思路，辅助模型做出更为审慎的推理。 在这些示例的基础上，Auto-CoT 以“让我们一步一步思考” 引导大语言模型生成针对用户问题的推理链和答案。 ","date":"2025-08-26","objectID":"/posts/%E5%A4%A7%E6%A8%A1%E5%9E%8B%E5%9F%BA%E7%A1%80~03.prompt-%E5%B7%A5%E7%A8%8B/:3:1","tags":["大模型"],"title":"大模型基础~03.Prompt 工程","uri":"/posts/%E5%A4%A7%E6%A8%A1%E5%9E%8B%E5%9F%BA%E7%A1%80~03.prompt-%E5%B7%A5%E7%A8%8B/#按部就班"},{"categories":[],"collections":["大模型基础"],"content":"3.1 按部就班\r原始的少样本思维链（CoT）方法就采用了按部就班模式。其通过手工构造几个一步一步推理回答问题的例子作为示例放入 Prompt 中，来引导模型一步一步生成推理步骤，并生成最终的答案。 这种方法在提升模型推理能力方面取得了一定的成功，但是需要费时费力地手工编写大量 CoT 示例，并且过度依赖于CoT 的编写质量。 Zero-Shot CoT\rZero-Shot CoT 通过简单的提示，如“Let’s think step by step”，引导模型自行生成一条推理链。其无需手工标注的 CoT 示例，减少了对人工示例的依赖，多个推理任务上展现出了与原始少样本CoT 相媲美甚至更优的性能。 Zero-Shot CoT 使用两阶段方法来回答问题： 首先，在第一阶段，在问题后面跟上一句“让我们一步一步思考”或者“Let’s think step by step”来作为 CoT 的提示触发词，来指示大语言模型先生成中间推理步骤，再生成最后的答案。 在第二阶段，把原始的问题以及第一阶段生成的推理步骤拼接在一起，在末尾加上一句“Therefore, the answer is”或者“因此，最终答案为”，把这些内容输给大语言模型，让他输出最终的答案。 Auto CoT\rAuto-CoT 引入与待解决问题相关的问题及其推理链作为示例，以继续提升CoT 的效果。相关示例的生成过程是由大语言模型自动完成的，无需手工标注。 Auto-CoT 的流程如下： 利用聚类技术从问题库中筛选出与用户提问位于一个簇中的问题。 然后，借助 Zero-Shot CoT 的方式，为筛选出的问题生成推理链，形成示例。这些示例包含了不同问题及其对应的推理内容，可为模型提供不同解题思路，辅助模型做出更为审慎的推理。 在这些示例的基础上，Auto-CoT 以“让我们一步一步思考” 引导大语言模型生成针对用户问题的推理链和答案。 ","date":"2025-08-26","objectID":"/posts/%E5%A4%A7%E6%A8%A1%E5%9E%8B%E5%9F%BA%E7%A1%80~03.prompt-%E5%B7%A5%E7%A8%8B/:3:1","tags":["大模型"],"title":"大模型基础~03.Prompt 工程","uri":"/posts/%E5%A4%A7%E6%A8%A1%E5%9E%8B%E5%9F%BA%E7%A1%80~03.prompt-%E5%B7%A5%E7%A8%8B/#zero-shot-cot"},{"categories":[],"collections":["大模型基础"],"content":"3.1 按部就班\r原始的少样本思维链（CoT）方法就采用了按部就班模式。其通过手工构造几个一步一步推理回答问题的例子作为示例放入 Prompt 中，来引导模型一步一步生成推理步骤，并生成最终的答案。 这种方法在提升模型推理能力方面取得了一定的成功，但是需要费时费力地手工编写大量 CoT 示例，并且过度依赖于CoT 的编写质量。 Zero-Shot CoT\rZero-Shot CoT 通过简单的提示，如“Let’s think step by step”，引导模型自行生成一条推理链。其无需手工标注的 CoT 示例，减少了对人工示例的依赖，多个推理任务上展现出了与原始少样本CoT 相媲美甚至更优的性能。 Zero-Shot CoT 使用两阶段方法来回答问题： 首先，在第一阶段，在问题后面跟上一句“让我们一步一步思考”或者“Let’s think step by step”来作为 CoT 的提示触发词，来指示大语言模型先生成中间推理步骤，再生成最后的答案。 在第二阶段，把原始的问题以及第一阶段生成的推理步骤拼接在一起，在末尾加上一句“Therefore, the answer is”或者“因此，最终答案为”，把这些内容输给大语言模型，让他输出最终的答案。 Auto CoT\rAuto-CoT 引入与待解决问题相关的问题及其推理链作为示例，以继续提升CoT 的效果。相关示例的生成过程是由大语言模型自动完成的，无需手工标注。 Auto-CoT 的流程如下： 利用聚类技术从问题库中筛选出与用户提问位于一个簇中的问题。 然后，借助 Zero-Shot CoT 的方式，为筛选出的问题生成推理链，形成示例。这些示例包含了不同问题及其对应的推理内容，可为模型提供不同解题思路，辅助模型做出更为审慎的推理。 在这些示例的基础上，Auto-CoT 以“让我们一步一步思考” 引导大语言模型生成针对用户问题的推理链和答案。 ","date":"2025-08-26","objectID":"/posts/%E5%A4%A7%E6%A8%A1%E5%9E%8B%E5%9F%BA%E7%A1%80~03.prompt-%E5%B7%A5%E7%A8%8B/:3:1","tags":["大模型"],"title":"大模型基础~03.Prompt 工程","uri":"/posts/%E5%A4%A7%E6%A8%A1%E5%9E%8B%E5%9F%BA%E7%A1%80~03.prompt-%E5%B7%A5%E7%A8%8B/#auto-cot"},{"categories":[],"collections":["大模型基础"],"content":"3.2 三思后行\r三思后行模式强调的是在决策过程中的融入审慎和灵活性。在这种模式下，模型在每一步都会停下来评估当前的情况，判断是否需要调整推理方向。这种模式的核心在于允许模型在遇到困难或不确定性时进行回溯和重新选择，确保决策过程的稳健性和适应性。 思维树（Tree of Thoughts, ToT）\rToT 将推理过程构造为一棵思维树，其从以下四个角度对思维树进行构造： 「拆解」：将复杂问题拆分成多个简单子问题，每个子问题的解答过程对应一个思维过程。思维过程拆解的形式和粒度依任务类别而定，例如在数学推理任务上以一行等式作为一个思维过程，在创意写作任务上以内容提纲作为思维过程。 「衍生」：模型需要根据当前子问题生成可能的下一步推理方向。衍生有两种模式：样本启发和命令提示。样本启发以多个独立的示例作为上下文，增大衍生空间，适合于创意写作等思维空间宽泛的任务；命令提示在Prompt 中指明规则和要求，限制衍生空间，适用于24 点游戏等思维空间受限的任务。 「评估」：利用模型评估推理节点合理性。根据任务是否便于量化评分，选择投票或打分模式。投票模式中，模型在多节点中选择，依据票数决定保留哪些节点；打分模式中，模型对节点进行评分，依据评分结果决定节点的保留。 「搜索」：从一个或多个当前状态出发，搜索通往问题解决方案的路径。依据任务特点选择不同搜索算法。可以使用深度优先搜索、广度优先搜索等经典搜索算法，也可以使用A* 搜索、蒙特卡洛树搜索等启发式搜索算法。 思维图（Graph of Thoughts, GoT）\rGoT 将树扩展为有向图，以提供了每个思维自我评估修正以及思维聚合的操作。 顶点代表某个问题（初始问题、中间问题、最终问题）的一个解决方案 有向边代表使用“出节点”作为直接输入，构造出思维“入节点”的过程。 信息\rGoT 相比于ToT 的核心优势是其思维自我反思，以及思维聚合的能力，能够将来自不同思维路径的知识和信息进行集成，形成综合的解决方案。 ","date":"2025-08-26","objectID":"/posts/%E5%A4%A7%E6%A8%A1%E5%9E%8B%E5%9F%BA%E7%A1%80~03.prompt-%E5%B7%A5%E7%A8%8B/:3:2","tags":["大模型"],"title":"大模型基础~03.Prompt 工程","uri":"/posts/%E5%A4%A7%E6%A8%A1%E5%9E%8B%E5%9F%BA%E7%A1%80~03.prompt-%E5%B7%A5%E7%A8%8B/#三思后行"},{"categories":[],"collections":["大模型基础"],"content":"3.2 三思后行\r三思后行模式强调的是在决策过程中的融入审慎和灵活性。在这种模式下，模型在每一步都会停下来评估当前的情况，判断是否需要调整推理方向。这种模式的核心在于允许模型在遇到困难或不确定性时进行回溯和重新选择，确保决策过程的稳健性和适应性。 思维树（Tree of Thoughts, ToT）\rToT 将推理过程构造为一棵思维树，其从以下四个角度对思维树进行构造： 「拆解」：将复杂问题拆分成多个简单子问题，每个子问题的解答过程对应一个思维过程。思维过程拆解的形式和粒度依任务类别而定，例如在数学推理任务上以一行等式作为一个思维过程，在创意写作任务上以内容提纲作为思维过程。 「衍生」：模型需要根据当前子问题生成可能的下一步推理方向。衍生有两种模式：样本启发和命令提示。样本启发以多个独立的示例作为上下文，增大衍生空间，适合于创意写作等思维空间宽泛的任务；命令提示在Prompt 中指明规则和要求，限制衍生空间，适用于24 点游戏等思维空间受限的任务。 「评估」：利用模型评估推理节点合理性。根据任务是否便于量化评分，选择投票或打分模式。投票模式中，模型在多节点中选择，依据票数决定保留哪些节点；打分模式中，模型对节点进行评分，依据评分结果决定节点的保留。 「搜索」：从一个或多个当前状态出发，搜索通往问题解决方案的路径。依据任务特点选择不同搜索算法。可以使用深度优先搜索、广度优先搜索等经典搜索算法，也可以使用A* 搜索、蒙特卡洛树搜索等启发式搜索算法。 思维图（Graph of Thoughts, GoT）\rGoT 将树扩展为有向图，以提供了每个思维自我评估修正以及思维聚合的操作。 顶点代表某个问题（初始问题、中间问题、最终问题）的一个解决方案 有向边代表使用“出节点”作为直接输入，构造出思维“入节点”的过程。 信息\rGoT 相比于ToT 的核心优势是其思维自我反思，以及思维聚合的能力，能够将来自不同思维路径的知识和信息进行集成，形成综合的解决方案。 ","date":"2025-08-26","objectID":"/posts/%E5%A4%A7%E6%A8%A1%E5%9E%8B%E5%9F%BA%E7%A1%80~03.prompt-%E5%B7%A5%E7%A8%8B/:3:2","tags":["大模型"],"title":"大模型基础~03.Prompt 工程","uri":"/posts/%E5%A4%A7%E6%A8%A1%E5%9E%8B%E5%9F%BA%E7%A1%80~03.prompt-%E5%B7%A5%E7%A8%8B/#思维树tree-of-thoughts-tot"},{"categories":[],"collections":["大模型基础"],"content":"3.2 三思后行\r三思后行模式强调的是在决策过程中的融入审慎和灵活性。在这种模式下，模型在每一步都会停下来评估当前的情况，判断是否需要调整推理方向。这种模式的核心在于允许模型在遇到困难或不确定性时进行回溯和重新选择，确保决策过程的稳健性和适应性。 思维树（Tree of Thoughts, ToT）\rToT 将推理过程构造为一棵思维树，其从以下四个角度对思维树进行构造： 「拆解」：将复杂问题拆分成多个简单子问题，每个子问题的解答过程对应一个思维过程。思维过程拆解的形式和粒度依任务类别而定，例如在数学推理任务上以一行等式作为一个思维过程，在创意写作任务上以内容提纲作为思维过程。 「衍生」：模型需要根据当前子问题生成可能的下一步推理方向。衍生有两种模式：样本启发和命令提示。样本启发以多个独立的示例作为上下文，增大衍生空间，适合于创意写作等思维空间宽泛的任务；命令提示在Prompt 中指明规则和要求，限制衍生空间，适用于24 点游戏等思维空间受限的任务。 「评估」：利用模型评估推理节点合理性。根据任务是否便于量化评分，选择投票或打分模式。投票模式中，模型在多节点中选择，依据票数决定保留哪些节点；打分模式中，模型对节点进行评分，依据评分结果决定节点的保留。 「搜索」：从一个或多个当前状态出发，搜索通往问题解决方案的路径。依据任务特点选择不同搜索算法。可以使用深度优先搜索、广度优先搜索等经典搜索算法，也可以使用A* 搜索、蒙特卡洛树搜索等启发式搜索算法。 思维图（Graph of Thoughts, GoT）\rGoT 将树扩展为有向图，以提供了每个思维自我评估修正以及思维聚合的操作。 顶点代表某个问题（初始问题、中间问题、最终问题）的一个解决方案 有向边代表使用“出节点”作为直接输入，构造出思维“入节点”的过程。 信息\rGoT 相比于ToT 的核心优势是其思维自我反思，以及思维聚合的能力，能够将来自不同思维路径的知识和信息进行集成，形成综合的解决方案。 ","date":"2025-08-26","objectID":"/posts/%E5%A4%A7%E6%A8%A1%E5%9E%8B%E5%9F%BA%E7%A1%80~03.prompt-%E5%B7%A5%E7%A8%8B/:3:2","tags":["大模型"],"title":"大模型基础~03.Prompt 工程","uri":"/posts/%E5%A4%A7%E6%A8%A1%E5%9E%8B%E5%9F%BA%E7%A1%80~03.prompt-%E5%B7%A5%E7%A8%8B/#思维图graph-of-thoughts-got"},{"categories":[],"collections":["大模型基础"],"content":"3.3 集思广益\r集思广益模式强调的是通过汇集多种不同的观点和方法来优化决策过程。在这种模式下，模型不仅仅依赖于单一的推理路径，而是通过探索多种可能的解决方案，从中选择最优的答案。这种方法借鉴了集体智慧的概念，即通过整合多个独立的思考结果，可以得到更全面、更准确的结论。 Self-Consistency 实现过程可以分为三个步骤： 在随机采样策略下，使用 CoT 或 Zero-Shot CoT 的方式来引导大语言模型针对待解决问题生成一组多样化的推理路径。 针对大语言模型生成的每个推理内容，收集其最终的答案，并统计每个答案在所有推理路径中出现的频率。 选择出现频率最高的答案作为最终的、最一致的答案。 ","date":"2025-08-26","objectID":"/posts/%E5%A4%A7%E6%A8%A1%E5%9E%8B%E5%9F%BA%E7%A1%80~03.prompt-%E5%B7%A5%E7%A8%8B/:3:3","tags":["大模型"],"title":"大模型基础~03.Prompt 工程","uri":"/posts/%E5%A4%A7%E6%A8%A1%E5%9E%8B%E5%9F%BA%E7%A1%80~03.prompt-%E5%B7%A5%E7%A8%8B/#集思广益"},{"categories":[],"collections":["大模型基础"],"content":"第 4 节 Prompt 技巧\r经典的Prompt通常由任务说明，上下文，问题，输出格式等部分中的一个或几个组成： ","date":"2025-08-26","objectID":"/posts/%E5%A4%A7%E6%A8%A1%E5%9E%8B%E5%9F%BA%E7%A1%80~03.prompt-%E5%B7%A5%E7%A8%8B/:4:0","tags":["大模型"],"title":"大模型基础~03.Prompt 工程","uri":"/posts/%E5%A4%A7%E6%A8%A1%E5%9E%8B%E5%9F%BA%E7%A1%80~03.prompt-%E5%B7%A5%E7%A8%8B/#prompt-技巧"},{"categories":[],"collections":["大模型基础"],"content":"4.1 规范 Prompt 编写\r任务说明要明确\r-「 明确的动词」：选择能够清晰表达动作的动词，如“判断”、“分类”、“生成”等，避免使用模糊的动词如“处理”或“操作”。 「具体的名词」：使用具体的名词来定义任务的输出或目标，例如“积极”和“消极”在情感分类任务中提供了明确的分类标准。 「简洁明了」：任务说明应简洁且直接，避免冗长或复杂的句子结构，使模型能够快速抓住任务的核心要求。 「结构化布局」：在较长的Prompt 中，将任务说明放置在开头和结尾，因为模型通常更关注这些部分的信息。这种布局有助于确保模型首先和最后接触到的是最关键的任务信息。 上下文丰富且清晰\r一个丰富且清晰的上下文能够显著提升模型的理解和回答准确率。 上下文的丰富性体现在其内容的多样性和相关性。 上下文的清晰性则要求上下文信息必须与问题紧密相关，避免包含冗余或不必要的信息。 输出格式要规范\r为了确保输出格式的规范性，可以采取以下措施： 「明确指定输出格式」：在Prompt 中明确指出希望模型使用的输出格式，如“请以 JSON 格式返回结果”，并且选择广泛接受和易于处理的输出格式，如JSON、CSV 等，易于解析和数据交换。 「提供输出格式的示例」：在 Prompt 中提供一个输出格式的具体示例，比如在 JSON 中明确指出关键字，帮助模型理解预期的输出结构。 排版要清晰\r为了确保 Prompt 的排版清晰，可以采取以下措施： 「使用一致的分隔符」：选择并坚持使用一种或几种分隔符（如#、###、—等），以区分不同的 Prompt 部分。 「合理使用空白和缩进」：通过增加空白行和适当的缩进，增强 Prompt 的可读性，帮助模型区分不同的内容块。 「清晰的标题和子标题」：为每个部分提供清晰的标题或子标题，使模型能够快速识别每个部分的主题。 ","date":"2025-08-26","objectID":"/posts/%E5%A4%A7%E6%A8%A1%E5%9E%8B%E5%9F%BA%E7%A1%80~03.prompt-%E5%B7%A5%E7%A8%8B/:4:1","tags":["大模型"],"title":"大模型基础~03.Prompt 工程","uri":"/posts/%E5%A4%A7%E6%A8%A1%E5%9E%8B%E5%9F%BA%E7%A1%80~03.prompt-%E5%B7%A5%E7%A8%8B/#规范-prompt-编写"},{"categories":[],"collections":["大模型基础"],"content":"4.1 规范 Prompt 编写\r任务说明要明确\r-「 明确的动词」：选择能够清晰表达动作的动词，如“判断”、“分类”、“生成”等，避免使用模糊的动词如“处理”或“操作”。 「具体的名词」：使用具体的名词来定义任务的输出或目标，例如“积极”和“消极”在情感分类任务中提供了明确的分类标准。 「简洁明了」：任务说明应简洁且直接，避免冗长或复杂的句子结构，使模型能够快速抓住任务的核心要求。 「结构化布局」：在较长的Prompt 中，将任务说明放置在开头和结尾，因为模型通常更关注这些部分的信息。这种布局有助于确保模型首先和最后接触到的是最关键的任务信息。 上下文丰富且清晰\r一个丰富且清晰的上下文能够显著提升模型的理解和回答准确率。 上下文的丰富性体现在其内容的多样性和相关性。 上下文的清晰性则要求上下文信息必须与问题紧密相关，避免包含冗余或不必要的信息。 输出格式要规范\r为了确保输出格式的规范性，可以采取以下措施： 「明确指定输出格式」：在Prompt 中明确指出希望模型使用的输出格式，如“请以 JSON 格式返回结果”，并且选择广泛接受和易于处理的输出格式，如JSON、CSV 等，易于解析和数据交换。 「提供输出格式的示例」：在 Prompt 中提供一个输出格式的具体示例，比如在 JSON 中明确指出关键字，帮助模型理解预期的输出结构。 排版要清晰\r为了确保 Prompt 的排版清晰，可以采取以下措施： 「使用一致的分隔符」：选择并坚持使用一种或几种分隔符（如#、###、—等），以区分不同的 Prompt 部分。 「合理使用空白和缩进」：通过增加空白行和适当的缩进，增强 Prompt 的可读性，帮助模型区分不同的内容块。 「清晰的标题和子标题」：为每个部分提供清晰的标题或子标题，使模型能够快速识别每个部分的主题。 ","date":"2025-08-26","objectID":"/posts/%E5%A4%A7%E6%A8%A1%E5%9E%8B%E5%9F%BA%E7%A1%80~03.prompt-%E5%B7%A5%E7%A8%8B/:4:1","tags":["大模型"],"title":"大模型基础~03.Prompt 工程","uri":"/posts/%E5%A4%A7%E6%A8%A1%E5%9E%8B%E5%9F%BA%E7%A1%80~03.prompt-%E5%B7%A5%E7%A8%8B/#任务说明要明确"},{"categories":[],"collections":["大模型基础"],"content":"4.1 规范 Prompt 编写\r任务说明要明确\r-「 明确的动词」：选择能够清晰表达动作的动词，如“判断”、“分类”、“生成”等，避免使用模糊的动词如“处理”或“操作”。 「具体的名词」：使用具体的名词来定义任务的输出或目标，例如“积极”和“消极”在情感分类任务中提供了明确的分类标准。 「简洁明了」：任务说明应简洁且直接，避免冗长或复杂的句子结构，使模型能够快速抓住任务的核心要求。 「结构化布局」：在较长的Prompt 中，将任务说明放置在开头和结尾，因为模型通常更关注这些部分的信息。这种布局有助于确保模型首先和最后接触到的是最关键的任务信息。 上下文丰富且清晰\r一个丰富且清晰的上下文能够显著提升模型的理解和回答准确率。 上下文的丰富性体现在其内容的多样性和相关性。 上下文的清晰性则要求上下文信息必须与问题紧密相关，避免包含冗余或不必要的信息。 输出格式要规范\r为了确保输出格式的规范性，可以采取以下措施： 「明确指定输出格式」：在Prompt 中明确指出希望模型使用的输出格式，如“请以 JSON 格式返回结果”，并且选择广泛接受和易于处理的输出格式，如JSON、CSV 等，易于解析和数据交换。 「提供输出格式的示例」：在 Prompt 中提供一个输出格式的具体示例，比如在 JSON 中明确指出关键字，帮助模型理解预期的输出结构。 排版要清晰\r为了确保 Prompt 的排版清晰，可以采取以下措施： 「使用一致的分隔符」：选择并坚持使用一种或几种分隔符（如#、###、—等），以区分不同的 Prompt 部分。 「合理使用空白和缩进」：通过增加空白行和适当的缩进，增强 Prompt 的可读性，帮助模型区分不同的内容块。 「清晰的标题和子标题」：为每个部分提供清晰的标题或子标题，使模型能够快速识别每个部分的主题。 ","date":"2025-08-26","objectID":"/posts/%E5%A4%A7%E6%A8%A1%E5%9E%8B%E5%9F%BA%E7%A1%80~03.prompt-%E5%B7%A5%E7%A8%8B/:4:1","tags":["大模型"],"title":"大模型基础~03.Prompt 工程","uri":"/posts/%E5%A4%A7%E6%A8%A1%E5%9E%8B%E5%9F%BA%E7%A1%80~03.prompt-%E5%B7%A5%E7%A8%8B/#上下文丰富且清晰"},{"categories":[],"collections":["大模型基础"],"content":"4.1 规范 Prompt 编写\r任务说明要明确\r-「 明确的动词」：选择能够清晰表达动作的动词，如“判断”、“分类”、“生成”等，避免使用模糊的动词如“处理”或“操作”。 「具体的名词」：使用具体的名词来定义任务的输出或目标，例如“积极”和“消极”在情感分类任务中提供了明确的分类标准。 「简洁明了」：任务说明应简洁且直接，避免冗长或复杂的句子结构，使模型能够快速抓住任务的核心要求。 「结构化布局」：在较长的Prompt 中，将任务说明放置在开头和结尾，因为模型通常更关注这些部分的信息。这种布局有助于确保模型首先和最后接触到的是最关键的任务信息。 上下文丰富且清晰\r一个丰富且清晰的上下文能够显著提升模型的理解和回答准确率。 上下文的丰富性体现在其内容的多样性和相关性。 上下文的清晰性则要求上下文信息必须与问题紧密相关，避免包含冗余或不必要的信息。 输出格式要规范\r为了确保输出格式的规范性，可以采取以下措施： 「明确指定输出格式」：在Prompt 中明确指出希望模型使用的输出格式，如“请以 JSON 格式返回结果”，并且选择广泛接受和易于处理的输出格式，如JSON、CSV 等，易于解析和数据交换。 「提供输出格式的示例」：在 Prompt 中提供一个输出格式的具体示例，比如在 JSON 中明确指出关键字，帮助模型理解预期的输出结构。 排版要清晰\r为了确保 Prompt 的排版清晰，可以采取以下措施： 「使用一致的分隔符」：选择并坚持使用一种或几种分隔符（如#、###、—等），以区分不同的 Prompt 部分。 「合理使用空白和缩进」：通过增加空白行和适当的缩进，增强 Prompt 的可读性，帮助模型区分不同的内容块。 「清晰的标题和子标题」：为每个部分提供清晰的标题或子标题，使模型能够快速识别每个部分的主题。 ","date":"2025-08-26","objectID":"/posts/%E5%A4%A7%E6%A8%A1%E5%9E%8B%E5%9F%BA%E7%A1%80~03.prompt-%E5%B7%A5%E7%A8%8B/:4:1","tags":["大模型"],"title":"大模型基础~03.Prompt 工程","uri":"/posts/%E5%A4%A7%E6%A8%A1%E5%9E%8B%E5%9F%BA%E7%A1%80~03.prompt-%E5%B7%A5%E7%A8%8B/#输出格式要规范"},{"categories":[],"collections":["大模型基础"],"content":"4.1 规范 Prompt 编写\r任务说明要明确\r-「 明确的动词」：选择能够清晰表达动作的动词，如“判断”、“分类”、“生成”等，避免使用模糊的动词如“处理”或“操作”。 「具体的名词」：使用具体的名词来定义任务的输出或目标，例如“积极”和“消极”在情感分类任务中提供了明确的分类标准。 「简洁明了」：任务说明应简洁且直接，避免冗长或复杂的句子结构，使模型能够快速抓住任务的核心要求。 「结构化布局」：在较长的Prompt 中，将任务说明放置在开头和结尾，因为模型通常更关注这些部分的信息。这种布局有助于确保模型首先和最后接触到的是最关键的任务信息。 上下文丰富且清晰\r一个丰富且清晰的上下文能够显著提升模型的理解和回答准确率。 上下文的丰富性体现在其内容的多样性和相关性。 上下文的清晰性则要求上下文信息必须与问题紧密相关，避免包含冗余或不必要的信息。 输出格式要规范\r为了确保输出格式的规范性，可以采取以下措施： 「明确指定输出格式」：在Prompt 中明确指出希望模型使用的输出格式，如“请以 JSON 格式返回结果”，并且选择广泛接受和易于处理的输出格式，如JSON、CSV 等，易于解析和数据交换。 「提供输出格式的示例」：在 Prompt 中提供一个输出格式的具体示例，比如在 JSON 中明确指出关键字，帮助模型理解预期的输出结构。 排版要清晰\r为了确保 Prompt 的排版清晰，可以采取以下措施： 「使用一致的分隔符」：选择并坚持使用一种或几种分隔符（如#、###、—等），以区分不同的 Prompt 部分。 「合理使用空白和缩进」：通过增加空白行和适当的缩进，增强 Prompt 的可读性，帮助模型区分不同的内容块。 「清晰的标题和子标题」：为每个部分提供清晰的标题或子标题，使模型能够快速识别每个部分的主题。 ","date":"2025-08-26","objectID":"/posts/%E5%A4%A7%E6%A8%A1%E5%9E%8B%E5%9F%BA%E7%A1%80~03.prompt-%E5%B7%A5%E7%A8%8B/:4:1","tags":["大模型"],"title":"大模型基础~03.Prompt 工程","uri":"/posts/%E5%A4%A7%E6%A8%A1%E5%9E%8B%E5%9F%BA%E7%A1%80~03.prompt-%E5%B7%A5%E7%A8%8B/#排版要清晰"},{"categories":[],"collections":["大模型基础"],"content":"4.2 合理归纳提问\r复杂问题拆解\r在处理复杂问题时，我们可以将问题分解为更小、更易于理解的子问题，并逐一解决。在计算机算法设计中，这种策略被称为“分而治之”，其基本理念是通过逐一解决子问题，最终达成解决整个问题的目标。 这一过程包括两个关键步骤：分步引导和归纳总结。 在分步引导阶段，我们需将复杂问题细化为多个子问题，并引导模型针对每个子问题进行深入分析和回答。这一步骤旨在确保每个子问题都能得到详尽的解答，从而为后续的归纳总结奠定坚实基础。 在归纳总结阶段，我们将各个子问题的答案进行汇总，并综合形成最终的全面回答。这一步骤不仅有助于我们全面把握问题的各个方面，还能确保最终答案的准确性和完整性。 追问\r通过在对话中进行追问，用户可以引导大语言模型的输出更贴合心意的内容。这种对话形式的交互不仅可以促进更深层次的理解和更为丰富的讨论，而且有助于更精确地表达用户的真实想法，从而更好地指导模型的思考，使其输出更加贴合用户需求。 深入追问\r深入追问的形式是指用户可以根据大语言模型的输出继续发问来深入挖掘特定话题的深层信息。这种追问适用于需要对某个概念、现象或过程有详尽解释的场景。 扩展追问\r扩展追问是一种在大语言模型给出回答的基础上，进一步要求模型提供更多相关信息或例子的提问方式，其目的在于拓宽讨论的广度，收集更多数据、例证或选项，帮助用户获得更广泛的视角，增加对话题的理解。 反馈追问\r反馈追问的形式是在大语言模型的输出不符合预期或存在错误时，提供反馈，指出问题所在，并请求模型进行更正或澄清。其目的在于通过反馈机制提升模型的准确性，确保信息的正确性。 ","date":"2025-08-26","objectID":"/posts/%E5%A4%A7%E6%A8%A1%E5%9E%8B%E5%9F%BA%E7%A1%80~03.prompt-%E5%B7%A5%E7%A8%8B/:4:2","tags":["大模型"],"title":"大模型基础~03.Prompt 工程","uri":"/posts/%E5%A4%A7%E6%A8%A1%E5%9E%8B%E5%9F%BA%E7%A1%80~03.prompt-%E5%B7%A5%E7%A8%8B/#合理归纳提问"},{"categories":[],"collections":["大模型基础"],"content":"4.2 合理归纳提问\r复杂问题拆解\r在处理复杂问题时，我们可以将问题分解为更小、更易于理解的子问题，并逐一解决。在计算机算法设计中，这种策略被称为“分而治之”，其基本理念是通过逐一解决子问题，最终达成解决整个问题的目标。 这一过程包括两个关键步骤：分步引导和归纳总结。 在分步引导阶段，我们需将复杂问题细化为多个子问题，并引导模型针对每个子问题进行深入分析和回答。这一步骤旨在确保每个子问题都能得到详尽的解答，从而为后续的归纳总结奠定坚实基础。 在归纳总结阶段，我们将各个子问题的答案进行汇总，并综合形成最终的全面回答。这一步骤不仅有助于我们全面把握问题的各个方面，还能确保最终答案的准确性和完整性。 追问\r通过在对话中进行追问，用户可以引导大语言模型的输出更贴合心意的内容。这种对话形式的交互不仅可以促进更深层次的理解和更为丰富的讨论，而且有助于更精确地表达用户的真实想法，从而更好地指导模型的思考，使其输出更加贴合用户需求。 深入追问\r深入追问的形式是指用户可以根据大语言模型的输出继续发问来深入挖掘特定话题的深层信息。这种追问适用于需要对某个概念、现象或过程有详尽解释的场景。 扩展追问\r扩展追问是一种在大语言模型给出回答的基础上，进一步要求模型提供更多相关信息或例子的提问方式，其目的在于拓宽讨论的广度，收集更多数据、例证或选项，帮助用户获得更广泛的视角，增加对话题的理解。 反馈追问\r反馈追问的形式是在大语言模型的输出不符合预期或存在错误时，提供反馈，指出问题所在，并请求模型进行更正或澄清。其目的在于通过反馈机制提升模型的准确性，确保信息的正确性。 ","date":"2025-08-26","objectID":"/posts/%E5%A4%A7%E6%A8%A1%E5%9E%8B%E5%9F%BA%E7%A1%80~03.prompt-%E5%B7%A5%E7%A8%8B/:4:2","tags":["大模型"],"title":"大模型基础~03.Prompt 工程","uri":"/posts/%E5%A4%A7%E6%A8%A1%E5%9E%8B%E5%9F%BA%E7%A1%80~03.prompt-%E5%B7%A5%E7%A8%8B/#复杂问题拆解"},{"categories":[],"collections":["大模型基础"],"content":"4.2 合理归纳提问\r复杂问题拆解\r在处理复杂问题时，我们可以将问题分解为更小、更易于理解的子问题，并逐一解决。在计算机算法设计中，这种策略被称为“分而治之”，其基本理念是通过逐一解决子问题，最终达成解决整个问题的目标。 这一过程包括两个关键步骤：分步引导和归纳总结。 在分步引导阶段，我们需将复杂问题细化为多个子问题，并引导模型针对每个子问题进行深入分析和回答。这一步骤旨在确保每个子问题都能得到详尽的解答，从而为后续的归纳总结奠定坚实基础。 在归纳总结阶段，我们将各个子问题的答案进行汇总，并综合形成最终的全面回答。这一步骤不仅有助于我们全面把握问题的各个方面，还能确保最终答案的准确性和完整性。 追问\r通过在对话中进行追问，用户可以引导大语言模型的输出更贴合心意的内容。这种对话形式的交互不仅可以促进更深层次的理解和更为丰富的讨论，而且有助于更精确地表达用户的真实想法，从而更好地指导模型的思考，使其输出更加贴合用户需求。 深入追问\r深入追问的形式是指用户可以根据大语言模型的输出继续发问来深入挖掘特定话题的深层信息。这种追问适用于需要对某个概念、现象或过程有详尽解释的场景。 扩展追问\r扩展追问是一种在大语言模型给出回答的基础上，进一步要求模型提供更多相关信息或例子的提问方式，其目的在于拓宽讨论的广度，收集更多数据、例证或选项，帮助用户获得更广泛的视角，增加对话题的理解。 反馈追问\r反馈追问的形式是在大语言模型的输出不符合预期或存在错误时，提供反馈，指出问题所在，并请求模型进行更正或澄清。其目的在于通过反馈机制提升模型的准确性，确保信息的正确性。 ","date":"2025-08-26","objectID":"/posts/%E5%A4%A7%E6%A8%A1%E5%9E%8B%E5%9F%BA%E7%A1%80~03.prompt-%E5%B7%A5%E7%A8%8B/:4:2","tags":["大模型"],"title":"大模型基础~03.Prompt 工程","uri":"/posts/%E5%A4%A7%E6%A8%A1%E5%9E%8B%E5%9F%BA%E7%A1%80~03.prompt-%E5%B7%A5%E7%A8%8B/#追问"},{"categories":[],"collections":["大模型基础"],"content":"4.2 合理归纳提问\r复杂问题拆解\r在处理复杂问题时，我们可以将问题分解为更小、更易于理解的子问题，并逐一解决。在计算机算法设计中，这种策略被称为“分而治之”，其基本理念是通过逐一解决子问题，最终达成解决整个问题的目标。 这一过程包括两个关键步骤：分步引导和归纳总结。 在分步引导阶段，我们需将复杂问题细化为多个子问题，并引导模型针对每个子问题进行深入分析和回答。这一步骤旨在确保每个子问题都能得到详尽的解答，从而为后续的归纳总结奠定坚实基础。 在归纳总结阶段，我们将各个子问题的答案进行汇总，并综合形成最终的全面回答。这一步骤不仅有助于我们全面把握问题的各个方面，还能确保最终答案的准确性和完整性。 追问\r通过在对话中进行追问，用户可以引导大语言模型的输出更贴合心意的内容。这种对话形式的交互不仅可以促进更深层次的理解和更为丰富的讨论，而且有助于更精确地表达用户的真实想法，从而更好地指导模型的思考，使其输出更加贴合用户需求。 深入追问\r深入追问的形式是指用户可以根据大语言模型的输出继续发问来深入挖掘特定话题的深层信息。这种追问适用于需要对某个概念、现象或过程有详尽解释的场景。 扩展追问\r扩展追问是一种在大语言模型给出回答的基础上，进一步要求模型提供更多相关信息或例子的提问方式，其目的在于拓宽讨论的广度，收集更多数据、例证或选项，帮助用户获得更广泛的视角，增加对话题的理解。 反馈追问\r反馈追问的形式是在大语言模型的输出不符合预期或存在错误时，提供反馈，指出问题所在，并请求模型进行更正或澄清。其目的在于通过反馈机制提升模型的准确性，确保信息的正确性。 ","date":"2025-08-26","objectID":"/posts/%E5%A4%A7%E6%A8%A1%E5%9E%8B%E5%9F%BA%E7%A1%80~03.prompt-%E5%B7%A5%E7%A8%8B/:4:2","tags":["大模型"],"title":"大模型基础~03.Prompt 工程","uri":"/posts/%E5%A4%A7%E6%A8%A1%E5%9E%8B%E5%9F%BA%E7%A1%80~03.prompt-%E5%B7%A5%E7%A8%8B/#深入追问"},{"categories":[],"collections":["大模型基础"],"content":"4.2 合理归纳提问\r复杂问题拆解\r在处理复杂问题时，我们可以将问题分解为更小、更易于理解的子问题，并逐一解决。在计算机算法设计中，这种策略被称为“分而治之”，其基本理念是通过逐一解决子问题，最终达成解决整个问题的目标。 这一过程包括两个关键步骤：分步引导和归纳总结。 在分步引导阶段，我们需将复杂问题细化为多个子问题，并引导模型针对每个子问题进行深入分析和回答。这一步骤旨在确保每个子问题都能得到详尽的解答，从而为后续的归纳总结奠定坚实基础。 在归纳总结阶段，我们将各个子问题的答案进行汇总，并综合形成最终的全面回答。这一步骤不仅有助于我们全面把握问题的各个方面，还能确保最终答案的准确性和完整性。 追问\r通过在对话中进行追问，用户可以引导大语言模型的输出更贴合心意的内容。这种对话形式的交互不仅可以促进更深层次的理解和更为丰富的讨论，而且有助于更精确地表达用户的真实想法，从而更好地指导模型的思考，使其输出更加贴合用户需求。 深入追问\r深入追问的形式是指用户可以根据大语言模型的输出继续发问来深入挖掘特定话题的深层信息。这种追问适用于需要对某个概念、现象或过程有详尽解释的场景。 扩展追问\r扩展追问是一种在大语言模型给出回答的基础上，进一步要求模型提供更多相关信息或例子的提问方式，其目的在于拓宽讨论的广度，收集更多数据、例证或选项，帮助用户获得更广泛的视角，增加对话题的理解。 反馈追问\r反馈追问的形式是在大语言模型的输出不符合预期或存在错误时，提供反馈，指出问题所在，并请求模型进行更正或澄清。其目的在于通过反馈机制提升模型的准确性，确保信息的正确性。 ","date":"2025-08-26","objectID":"/posts/%E5%A4%A7%E6%A8%A1%E5%9E%8B%E5%9F%BA%E7%A1%80~03.prompt-%E5%B7%A5%E7%A8%8B/:4:2","tags":["大模型"],"title":"大模型基础~03.Prompt 工程","uri":"/posts/%E5%A4%A7%E6%A8%A1%E5%9E%8B%E5%9F%BA%E7%A1%80~03.prompt-%E5%B7%A5%E7%A8%8B/#扩展追问"},{"categories":[],"collections":["大模型基础"],"content":"4.2 合理归纳提问\r复杂问题拆解\r在处理复杂问题时，我们可以将问题分解为更小、更易于理解的子问题，并逐一解决。在计算机算法设计中，这种策略被称为“分而治之”，其基本理念是通过逐一解决子问题，最终达成解决整个问题的目标。 这一过程包括两个关键步骤：分步引导和归纳总结。 在分步引导阶段，我们需将复杂问题细化为多个子问题，并引导模型针对每个子问题进行深入分析和回答。这一步骤旨在确保每个子问题都能得到详尽的解答，从而为后续的归纳总结奠定坚实基础。 在归纳总结阶段，我们将各个子问题的答案进行汇总，并综合形成最终的全面回答。这一步骤不仅有助于我们全面把握问题的各个方面，还能确保最终答案的准确性和完整性。 追问\r通过在对话中进行追问，用户可以引导大语言模型的输出更贴合心意的内容。这种对话形式的交互不仅可以促进更深层次的理解和更为丰富的讨论，而且有助于更精确地表达用户的真实想法，从而更好地指导模型的思考，使其输出更加贴合用户需求。 深入追问\r深入追问的形式是指用户可以根据大语言模型的输出继续发问来深入挖掘特定话题的深层信息。这种追问适用于需要对某个概念、现象或过程有详尽解释的场景。 扩展追问\r扩展追问是一种在大语言模型给出回答的基础上，进一步要求模型提供更多相关信息或例子的提问方式，其目的在于拓宽讨论的广度，收集更多数据、例证或选项，帮助用户获得更广泛的视角，增加对话题的理解。 反馈追问\r反馈追问的形式是在大语言模型的输出不符合预期或存在错误时，提供反馈，指出问题所在，并请求模型进行更正或澄清。其目的在于通过反馈机制提升模型的准确性，确保信息的正确性。 ","date":"2025-08-26","objectID":"/posts/%E5%A4%A7%E6%A8%A1%E5%9E%8B%E5%9F%BA%E7%A1%80~03.prompt-%E5%B7%A5%E7%A8%8B/:4:2","tags":["大模型"],"title":"大模型基础~03.Prompt 工程","uri":"/posts/%E5%A4%A7%E6%A8%A1%E5%9E%8B%E5%9F%BA%E7%A1%80~03.prompt-%E5%B7%A5%E7%A8%8B/#反馈追问"},{"categories":[],"collections":["大模型基础"],"content":"4.3 适时使用 CoT\r何时使用CoT\r在决定何时使用CoT 时，需要对任务类别、模型规模以及模型能力三方面因素进行考虑。 在任务类别方面，CoT 技术特别适用于需要复杂推理的任务，如算术、常识和符号推理。对于简单问题，标准的 Prompt 方法已足够有效，使用 CoT 可能难以提升效果，反而可能引入不必要的复杂性。 在模型规模方面，CoT 技术应用于参数量超过千亿的巨型模型时，能够显著提升其性能。在规模较小的模型上应用 CoT 技术可能会遭遇挑战，如生成逻辑不连贯的思维链，或导致最终结果的准确性不如直接的标准提示方法。 在模型能力方面，CoT 是否起效与模型在预训练阶段是否进行过推理方面的指令微调有关。对于那些未经推理方面的指令微调的大语言模型，适当的 CoT 提示能够激发其卓越的CoT 推理能力。对于已经历过推理方面的指令微调的大语言模型，即便在没有 CoT 指令的情况下，也能自发生成条理清晰的中间推理步骤。在许多情况下，这些模型在没有 CoT 指令的条件下反而展现出更佳的性能。 灵活使用CoT\r调整 CoT 的详细程度\r我们可以指定 CoT 输出的详细程度，以适应不同的用户需求。 对于简单的计算问题，在用户不需要推理的中间过程时，我们可以直接给出最终乘法和加法的结果。 对于复杂的计算、推理问题，或者用户需要理解中间推理过程时，我们需要通过样例进行引导，以使其展示完整的推理步骤。 使用不同的 CoT 形式\r我们可以根据不同任务场景，选择不同的CoT 形式。 在不需要特定领域知识，仅需对问题进行逻辑推理和逐步分析时，可以使用 Zero-Shot CoT 或者Auto CoT 的方式，通过“让我们一步一步思考”这种 CoT 提示触发词，来引导模型以CoT 的形式回答内容。 在处理需要高准确度和可靠性的任务时，可要求模型生成多个回答并提出最终结果，进而运用 Self-Consistency 方法筛选出一致性最强的答案。 ","date":"2025-08-26","objectID":"/posts/%E5%A4%A7%E6%A8%A1%E5%9E%8B%E5%9F%BA%E7%A1%80~03.prompt-%E5%B7%A5%E7%A8%8B/:4:3","tags":["大模型"],"title":"大模型基础~03.Prompt 工程","uri":"/posts/%E5%A4%A7%E6%A8%A1%E5%9E%8B%E5%9F%BA%E7%A1%80~03.prompt-%E5%B7%A5%E7%A8%8B/#适时使用-cot"},{"categories":[],"collections":["大模型基础"],"content":"4.3 适时使用 CoT\r何时使用CoT\r在决定何时使用CoT 时，需要对任务类别、模型规模以及模型能力三方面因素进行考虑。 在任务类别方面，CoT 技术特别适用于需要复杂推理的任务，如算术、常识和符号推理。对于简单问题，标准的 Prompt 方法已足够有效，使用 CoT 可能难以提升效果，反而可能引入不必要的复杂性。 在模型规模方面，CoT 技术应用于参数量超过千亿的巨型模型时，能够显著提升其性能。在规模较小的模型上应用 CoT 技术可能会遭遇挑战，如生成逻辑不连贯的思维链，或导致最终结果的准确性不如直接的标准提示方法。 在模型能力方面，CoT 是否起效与模型在预训练阶段是否进行过推理方面的指令微调有关。对于那些未经推理方面的指令微调的大语言模型，适当的 CoT 提示能够激发其卓越的CoT 推理能力。对于已经历过推理方面的指令微调的大语言模型，即便在没有 CoT 指令的情况下，也能自发生成条理清晰的中间推理步骤。在许多情况下，这些模型在没有 CoT 指令的条件下反而展现出更佳的性能。 灵活使用CoT\r调整 CoT 的详细程度\r我们可以指定 CoT 输出的详细程度，以适应不同的用户需求。 对于简单的计算问题，在用户不需要推理的中间过程时，我们可以直接给出最终乘法和加法的结果。 对于复杂的计算、推理问题，或者用户需要理解中间推理过程时，我们需要通过样例进行引导，以使其展示完整的推理步骤。 使用不同的 CoT 形式\r我们可以根据不同任务场景，选择不同的CoT 形式。 在不需要特定领域知识，仅需对问题进行逻辑推理和逐步分析时，可以使用 Zero-Shot CoT 或者Auto CoT 的方式，通过“让我们一步一步思考”这种 CoT 提示触发词，来引导模型以CoT 的形式回答内容。 在处理需要高准确度和可靠性的任务时，可要求模型生成多个回答并提出最终结果，进而运用 Self-Consistency 方法筛选出一致性最强的答案。 ","date":"2025-08-26","objectID":"/posts/%E5%A4%A7%E6%A8%A1%E5%9E%8B%E5%9F%BA%E7%A1%80~03.prompt-%E5%B7%A5%E7%A8%8B/:4:3","tags":["大模型"],"title":"大模型基础~03.Prompt 工程","uri":"/posts/%E5%A4%A7%E6%A8%A1%E5%9E%8B%E5%9F%BA%E7%A1%80~03.prompt-%E5%B7%A5%E7%A8%8B/#何时使用cot"},{"categories":[],"collections":["大模型基础"],"content":"4.3 适时使用 CoT\r何时使用CoT\r在决定何时使用CoT 时，需要对任务类别、模型规模以及模型能力三方面因素进行考虑。 在任务类别方面，CoT 技术特别适用于需要复杂推理的任务，如算术、常识和符号推理。对于简单问题，标准的 Prompt 方法已足够有效，使用 CoT 可能难以提升效果，反而可能引入不必要的复杂性。 在模型规模方面，CoT 技术应用于参数量超过千亿的巨型模型时，能够显著提升其性能。在规模较小的模型上应用 CoT 技术可能会遭遇挑战，如生成逻辑不连贯的思维链，或导致最终结果的准确性不如直接的标准提示方法。 在模型能力方面，CoT 是否起效与模型在预训练阶段是否进行过推理方面的指令微调有关。对于那些未经推理方面的指令微调的大语言模型，适当的 CoT 提示能够激发其卓越的CoT 推理能力。对于已经历过推理方面的指令微调的大语言模型，即便在没有 CoT 指令的情况下，也能自发生成条理清晰的中间推理步骤。在许多情况下，这些模型在没有 CoT 指令的条件下反而展现出更佳的性能。 灵活使用CoT\r调整 CoT 的详细程度\r我们可以指定 CoT 输出的详细程度，以适应不同的用户需求。 对于简单的计算问题，在用户不需要推理的中间过程时，我们可以直接给出最终乘法和加法的结果。 对于复杂的计算、推理问题，或者用户需要理解中间推理过程时，我们需要通过样例进行引导，以使其展示完整的推理步骤。 使用不同的 CoT 形式\r我们可以根据不同任务场景，选择不同的CoT 形式。 在不需要特定领域知识，仅需对问题进行逻辑推理和逐步分析时，可以使用 Zero-Shot CoT 或者Auto CoT 的方式，通过“让我们一步一步思考”这种 CoT 提示触发词，来引导模型以CoT 的形式回答内容。 在处理需要高准确度和可靠性的任务时，可要求模型生成多个回答并提出最终结果，进而运用 Self-Consistency 方法筛选出一致性最强的答案。 ","date":"2025-08-26","objectID":"/posts/%E5%A4%A7%E6%A8%A1%E5%9E%8B%E5%9F%BA%E7%A1%80~03.prompt-%E5%B7%A5%E7%A8%8B/:4:3","tags":["大模型"],"title":"大模型基础~03.Prompt 工程","uri":"/posts/%E5%A4%A7%E6%A8%A1%E5%9E%8B%E5%9F%BA%E7%A1%80~03.prompt-%E5%B7%A5%E7%A8%8B/#灵活使用cot"},{"categories":[],"collections":["大模型基础"],"content":"4.3 适时使用 CoT\r何时使用CoT\r在决定何时使用CoT 时，需要对任务类别、模型规模以及模型能力三方面因素进行考虑。 在任务类别方面，CoT 技术特别适用于需要复杂推理的任务，如算术、常识和符号推理。对于简单问题，标准的 Prompt 方法已足够有效，使用 CoT 可能难以提升效果，反而可能引入不必要的复杂性。 在模型规模方面，CoT 技术应用于参数量超过千亿的巨型模型时，能够显著提升其性能。在规模较小的模型上应用 CoT 技术可能会遭遇挑战，如生成逻辑不连贯的思维链，或导致最终结果的准确性不如直接的标准提示方法。 在模型能力方面，CoT 是否起效与模型在预训练阶段是否进行过推理方面的指令微调有关。对于那些未经推理方面的指令微调的大语言模型，适当的 CoT 提示能够激发其卓越的CoT 推理能力。对于已经历过推理方面的指令微调的大语言模型，即便在没有 CoT 指令的情况下，也能自发生成条理清晰的中间推理步骤。在许多情况下，这些模型在没有 CoT 指令的条件下反而展现出更佳的性能。 灵活使用CoT\r调整 CoT 的详细程度\r我们可以指定 CoT 输出的详细程度，以适应不同的用户需求。 对于简单的计算问题，在用户不需要推理的中间过程时，我们可以直接给出最终乘法和加法的结果。 对于复杂的计算、推理问题，或者用户需要理解中间推理过程时，我们需要通过样例进行引导，以使其展示完整的推理步骤。 使用不同的 CoT 形式\r我们可以根据不同任务场景，选择不同的CoT 形式。 在不需要特定领域知识，仅需对问题进行逻辑推理和逐步分析时，可以使用 Zero-Shot CoT 或者Auto CoT 的方式，通过“让我们一步一步思考”这种 CoT 提示触发词，来引导模型以CoT 的形式回答内容。 在处理需要高准确度和可靠性的任务时，可要求模型生成多个回答并提出最终结果，进而运用 Self-Consistency 方法筛选出一致性最强的答案。 ","date":"2025-08-26","objectID":"/posts/%E5%A4%A7%E6%A8%A1%E5%9E%8B%E5%9F%BA%E7%A1%80~03.prompt-%E5%B7%A5%E7%A8%8B/:4:3","tags":["大模型"],"title":"大模型基础~03.Prompt 工程","uri":"/posts/%E5%A4%A7%E6%A8%A1%E5%9E%8B%E5%9F%BA%E7%A1%80~03.prompt-%E5%B7%A5%E7%A8%8B/#调整-cot-的详细程度"},{"categories":[],"collections":["大模型基础"],"content":"4.3 适时使用 CoT\r何时使用CoT\r在决定何时使用CoT 时，需要对任务类别、模型规模以及模型能力三方面因素进行考虑。 在任务类别方面，CoT 技术特别适用于需要复杂推理的任务，如算术、常识和符号推理。对于简单问题，标准的 Prompt 方法已足够有效，使用 CoT 可能难以提升效果，反而可能引入不必要的复杂性。 在模型规模方面，CoT 技术应用于参数量超过千亿的巨型模型时，能够显著提升其性能。在规模较小的模型上应用 CoT 技术可能会遭遇挑战，如生成逻辑不连贯的思维链，或导致最终结果的准确性不如直接的标准提示方法。 在模型能力方面，CoT 是否起效与模型在预训练阶段是否进行过推理方面的指令微调有关。对于那些未经推理方面的指令微调的大语言模型，适当的 CoT 提示能够激发其卓越的CoT 推理能力。对于已经历过推理方面的指令微调的大语言模型，即便在没有 CoT 指令的情况下，也能自发生成条理清晰的中间推理步骤。在许多情况下，这些模型在没有 CoT 指令的条件下反而展现出更佳的性能。 灵活使用CoT\r调整 CoT 的详细程度\r我们可以指定 CoT 输出的详细程度，以适应不同的用户需求。 对于简单的计算问题，在用户不需要推理的中间过程时，我们可以直接给出最终乘法和加法的结果。 对于复杂的计算、推理问题，或者用户需要理解中间推理过程时，我们需要通过样例进行引导，以使其展示完整的推理步骤。 使用不同的 CoT 形式\r我们可以根据不同任务场景，选择不同的CoT 形式。 在不需要特定领域知识，仅需对问题进行逻辑推理和逐步分析时，可以使用 Zero-Shot CoT 或者Auto CoT 的方式，通过“让我们一步一步思考”这种 CoT 提示触发词，来引导模型以CoT 的形式回答内容。 在处理需要高准确度和可靠性的任务时，可要求模型生成多个回答并提出最终结果，进而运用 Self-Consistency 方法筛选出一致性最强的答案。 ","date":"2025-08-26","objectID":"/posts/%E5%A4%A7%E6%A8%A1%E5%9E%8B%E5%9F%BA%E7%A1%80~03.prompt-%E5%B7%A5%E7%A8%8B/:4:3","tags":["大模型"],"title":"大模型基础~03.Prompt 工程","uri":"/posts/%E5%A4%A7%E6%A8%A1%E5%9E%8B%E5%9F%BA%E7%A1%80~03.prompt-%E5%B7%A5%E7%A8%8B/#使用不同的-cot-形式"},{"categories":[],"collections":["大模型基础"],"content":"4.4 善用心理暗示\r在硅谷，流传着一句创业金句，“Fake it till you make it”（假装它直到你成功）。这句话具体含义为，先吹嘘你的想法，进而吸引资本和人才，最终在实践中努力追赶并实现既定目标。这句话源自一种积极的心理暗示方法：通过模仿自信和乐观的心态，一个人可以在他们的现实生活中实现这些品质。 角色扮演\r通过 Prompt 指导大语言模型扮演特定角色能够显著改善其与角色相关的技能。这种技术被称为角色扮演（Role-Playing），它可使大语言模型能够生成更为准确、角色相关的内容。为了构建一个有效的角色，需要在指令中包含具体属性、职责、知识和技能。 情景代入\r通过将模型置于特定的“情景”或“环境”中，可以影响其生成的文本内容和风格。情景代入指的是将特定情境下所需的专业知识、历史背景等信息嵌入到模型的响应中。 ","date":"2025-08-26","objectID":"/posts/%E5%A4%A7%E6%A8%A1%E5%9E%8B%E5%9F%BA%E7%A1%80~03.prompt-%E5%B7%A5%E7%A8%8B/:4:4","tags":["大模型"],"title":"大模型基础~03.Prompt 工程","uri":"/posts/%E5%A4%A7%E6%A8%A1%E5%9E%8B%E5%9F%BA%E7%A1%80~03.prompt-%E5%B7%A5%E7%A8%8B/#善用心理暗示"},{"categories":[],"collections":["大模型基础"],"content":"4.4 善用心理暗示\r在硅谷，流传着一句创业金句，“Fake it till you make it”（假装它直到你成功）。这句话具体含义为，先吹嘘你的想法，进而吸引资本和人才，最终在实践中努力追赶并实现既定目标。这句话源自一种积极的心理暗示方法：通过模仿自信和乐观的心态，一个人可以在他们的现实生活中实现这些品质。 角色扮演\r通过 Prompt 指导大语言模型扮演特定角色能够显著改善其与角色相关的技能。这种技术被称为角色扮演（Role-Playing），它可使大语言模型能够生成更为准确、角色相关的内容。为了构建一个有效的角色，需要在指令中包含具体属性、职责、知识和技能。 情景代入\r通过将模型置于特定的“情景”或“环境”中，可以影响其生成的文本内容和风格。情景代入指的是将特定情境下所需的专业知识、历史背景等信息嵌入到模型的响应中。 ","date":"2025-08-26","objectID":"/posts/%E5%A4%A7%E6%A8%A1%E5%9E%8B%E5%9F%BA%E7%A1%80~03.prompt-%E5%B7%A5%E7%A8%8B/:4:4","tags":["大模型"],"title":"大模型基础~03.Prompt 工程","uri":"/posts/%E5%A4%A7%E6%A8%A1%E5%9E%8B%E5%9F%BA%E7%A1%80~03.prompt-%E5%B7%A5%E7%A8%8B/#角色扮演"},{"categories":[],"collections":["大模型基础"],"content":"4.4 善用心理暗示\r在硅谷，流传着一句创业金句，“Fake it till you make it”（假装它直到你成功）。这句话具体含义为，先吹嘘你的想法，进而吸引资本和人才，最终在实践中努力追赶并实现既定目标。这句话源自一种积极的心理暗示方法：通过模仿自信和乐观的心态，一个人可以在他们的现实生活中实现这些品质。 角色扮演\r通过 Prompt 指导大语言模型扮演特定角色能够显著改善其与角色相关的技能。这种技术被称为角色扮演（Role-Playing），它可使大语言模型能够生成更为准确、角色相关的内容。为了构建一个有效的角色，需要在指令中包含具体属性、职责、知识和技能。 情景代入\r通过将模型置于特定的“情景”或“环境”中，可以影响其生成的文本内容和风格。情景代入指的是将特定情境下所需的专业知识、历史背景等信息嵌入到模型的响应中。 ","date":"2025-08-26","objectID":"/posts/%E5%A4%A7%E6%A8%A1%E5%9E%8B%E5%9F%BA%E7%A1%80~03.prompt-%E5%B7%A5%E7%A8%8B/:4:4","tags":["大模型"],"title":"大模型基础~03.Prompt 工程","uri":"/posts/%E5%A4%A7%E6%A8%A1%E5%9E%8B%E5%9F%BA%E7%A1%80~03.prompt-%E5%B7%A5%E7%A8%8B/#情景代入"},{"categories":[],"collections":["大模型基础"],"content":"第 5 节 相关应用\r","date":"2025-08-26","objectID":"/posts/%E5%A4%A7%E6%A8%A1%E5%9E%8B%E5%9F%BA%E7%A1%80~03.prompt-%E5%B7%A5%E7%A8%8B/:5:0","tags":["大模型"],"title":"大模型基础~03.Prompt 工程","uri":"/posts/%E5%A4%A7%E6%A8%A1%E5%9E%8B%E5%9F%BA%E7%A1%80~03.prompt-%E5%B7%A5%E7%A8%8B/#相关应用"},{"categories":[],"collections":["大模型基础"],"content":"5.1 基于大语言模型的Agent\r智能体（Agent）是一种能够自主感知环境并采取行动以实现特定目标的实体。 基于大语言模型的 Agent 系统中，大语言模型作为核心控制器，能够完成规划、决策、行动等操作，这些操作很多都依赖 Prompt 完成。 该框架主要由四大部分组成：配置模块（Profile）、记忆模块（Memory）、计划模块（Planning）和行动模块（Action）。 「配置模块」利用Prompt 工程中的角色扮演技术，来定义Agent 的角色。设定Agent 的背景、技能、职责等信息，这些角色设定信息以上下文的形式嵌入到 Agent 每一次交互的 Prompt 中。 「记忆模块」是 Agent 的知识与交互记忆的存储中心。记忆模块通过检索增强等技术获取记忆，这一过程涉及到使用Prompt 工程中的上下文学习技术来构造和优化查询，从而帮助更加精准检索到相关记忆。在获取记忆之后，将这些记忆将被添加到交互的Prompt 中，帮助Agent 利用这些记忆知识，实现更为准确高效的决策与行动。 「计划模块」则扮演着任务分解者的角色，它将复杂的任务细化为一系列更为简单、易于管理的子任务。在这一过程中，通过 Prompt 工程中的思维链技术，让大语言模型分解任务并进行规划，按照链式顺序输出子任务；同时还利用了上下文学习技术，构造少样本示例来调控分解出的子任务的粒度，确保整个任务流程的顺畅与高效。 「行动模块」负责将计划模块生成的计划转化为具体的行动步骤，并借助外部工具执行这些步骤以实现 Agent 的目标。通常会为 Agent 提供工具 API 的接口，把调用 API 接口的示例作为上下文，让大语言模型生成调用 API 的代码，之后执行这些代码，从而得到执行步骤的结果。 ","date":"2025-08-26","objectID":"/posts/%E5%A4%A7%E6%A8%A1%E5%9E%8B%E5%9F%BA%E7%A1%80~03.prompt-%E5%B7%A5%E7%A8%8B/:5:1","tags":["大模型"],"title":"大模型基础~03.Prompt 工程","uri":"/posts/%E5%A4%A7%E6%A8%A1%E5%9E%8B%E5%9F%BA%E7%A1%80~03.prompt-%E5%B7%A5%E7%A8%8B/#基于大语言模型的agent"},{"categories":[],"collections":["大模型基础"],"content":"5.2 数据合成\r公共领域的高质量语言数据，如书籍、新闻、科学论文和维基百科，预计将在2026 年左右耗尽。特定领域的垂直数据因隐私保护和标注难度高等问题，难以大量提供高质量数据，限制了模型的进一步发展。 利用大语言模型强大的思维能力、指令跟随能力，来合成高质量数据是一种补充或替代真实数据的有效手段。Self-Instruct 通过Prompt 工程技术构建 Prompt，通过多步骤调用大语言模型，并依据已有的少量指令数据，合成大量丰富且多样化的指令数据。 Self-Instruct 包含构建任务池、指令生成、指令分类、数据生成、数据过滤五个步骤。 「构建任务池」人工手工设计了 175 个指令数据集，作为初始任务池，后续模型会不断参考任务池的示例，来生成指令数据，并将生成的指令数据加入任务池中。 「指令生成」从任务池中随机抽取 8 个现有指令作为演示示例组成 Prompt中的上下文，以少样本学习的方式构造 Prompt 让模型生成指令。 「指令分类」编写若干条“指令-分类任务/生成任务”样例对，作为上下文构造 Prompt，让模型判断该指令对应的任务是分类任务还是生成任务。 「数据生成」对分类任务和生成任务使用 Prompt 工程中的上下文学习技术，构造不同的Prompt 来生成指令数据中的输入部分和回答部分。 「数据过滤」通过设置各种启发式方法过滤低质量或者高重复度的指令数据，然后将剩余的有效任务添加到任务池中。 ","date":"2025-08-26","objectID":"/posts/%E5%A4%A7%E6%A8%A1%E5%9E%8B%E5%9F%BA%E7%A1%80~03.prompt-%E5%B7%A5%E7%A8%8B/:5:2","tags":["大模型"],"title":"大模型基础~03.Prompt 工程","uri":"/posts/%E5%A4%A7%E6%A8%A1%E5%9E%8B%E5%9F%BA%E7%A1%80~03.prompt-%E5%B7%A5%E7%A8%8B/#数据合成"},{"categories":[],"collections":["大模型基础"],"content":"5.3 Text-to-SQL\rText-to-SQL 技术可以将自然语言查询翻译成可以在数据库中执行的 SQL 语句，是实现零代码或低代码数据查询的有效途径。 ","date":"2025-08-26","objectID":"/posts/%E5%A4%A7%E6%A8%A1%E5%9E%8B%E5%9F%BA%E7%A1%80~03.prompt-%E5%B7%A5%E7%A8%8B/:5:3","tags":["大模型"],"title":"大模型基础~03.Prompt 工程","uri":"/posts/%E5%A4%A7%E6%A8%A1%E5%9E%8B%E5%9F%BA%E7%A1%80~03.prompt-%E5%B7%A5%E7%A8%8B/#text-to-sql"},{"categories":[],"collections":["大模型基础"],"content":"5.4 GPTS\rGPTs 是 OpenAI 推出的支持用户自定义的 GPT 应用，允许用户通过编写 Prompt，添加工具等方式创建定制版的 GPT 应用，也可以使用别人分享的 GPTs 模型。 ","date":"2025-08-26","objectID":"/posts/%E5%A4%A7%E6%A8%A1%E5%9E%8B%E5%9F%BA%E7%A1%80~03.prompt-%E5%B7%A5%E7%A8%8B/:5:4","tags":["大模型"],"title":"大模型基础~03.Prompt 工程","uri":"/posts/%E5%A4%A7%E6%A8%A1%E5%9E%8B%E5%9F%BA%E7%A1%80~03.prompt-%E5%B7%A5%E7%A8%8B/#gpts"},{"categories":null,"collections":["大模型基础"],"content":"第 1 节 概述\r随着数据资源和计算能力的爆发式增长，语言模型的参数规模和性能表现实现了质的飞跃，迈入了大语言模型（Large Language Model, LLM）的新时代。 大语言模型不仅仅是模型规模的庞大，也涵盖了训练数据规模的庞大，以及由此衍生出的模型能力的强大。 ","date":"2025-08-22","objectID":"/posts/%E5%A4%A7%E6%A8%A1%E5%9E%8B%E5%9F%BA%E7%A1%80~02.%E5%A4%A7%E8%AF%AD%E8%A8%80%E6%A8%A1%E5%9E%8B%E6%9E%B6%E6%9E%84/:1:0","tags":["大模型"],"title":"大模型大模型基础~02.大语言模型架构","uri":"/posts/%E5%A4%A7%E6%A8%A1%E5%9E%8B%E5%9F%BA%E7%A1%80~02.%E5%A4%A7%E8%AF%AD%E8%A8%80%E6%A8%A1%E5%9E%8B%E6%9E%B6%E6%9E%84/#概述"},{"categories":null,"collections":["大模型基础"],"content":"1.1 平衡点\r模型规模和数据规模的增长并非没有代价，它们带来了更高的计算和存储需求，这要求我们在模型设计时必须在资源消耗和性能提升之间找到一个恰当的平衡点。 于是有团队提出了大语言模型的扩展法则（Scaling Laws），揭示了模型的能力随模型和数据规模的变化关系，为大语言模型的设计和优化提供了宝贵的指导和参考。 Kaplan-McCandlish 扩展法则\r这个法则由 OpenAI 团队提出，他们通过对不同的数据规模和模型规模进行实验，拟合出了模型性能与两者的关系： $$ L(D) = {(\\frac{D}{D_c})}^{\\alpha_D} $$ $$ \\alpha_{D} \\sim -0.095,\\ D_{c} \\sim 5.4 \\times {10}^{13} $$ $$ L(N)={(\\frac{N}{N_{c}})}^{\\alpha_{N}} $$ $$ \\alpha_{N} \\sim -0.076,\\ N_{c} \\sim 8.8 \\times {10}^{13} $$ $L(D)$ 表示在模型规模固定时，不同数据规模下的交叉熵损失函数；$L(N)$ 表示在数据规模固定时，不同模型规模下的交叉熵损失函数。 损失函数越小说明模型越精确，性能越好。于是，模型的性能与模型规模和数据规模高度正相关。 OpenAI 在进一步研究计算预算的最优分配时发现，总计算量 $C$ 与数据量 $D$ 和模型规模 $N$ 的乘积近似成正比，即 $C \\approx 6ND$。 结合上述内容进行优化，两者的最优配置比例应当为：$N_{o p t} \\propto C^{0.73}, D_{o p t} \\propto C^{0.27}$ 信息\r这意味着，如果总计算预算增加了10 倍，模型规模应扩大约5.37 倍，而数据规模应扩大约1.86 倍，以实现模型的最佳性能。 Chinchilla 扩展法则\r谷歌的 DeepMind 团队同样进行了研究，并据此提出了Chinchilla 扩展法则： $$ L(N, D)=E+\\frac{A}{N^\\alpha}+\\frac{B}{D^\\beta} $$ $$ E=1.69,\\ A=406.4,\\ B=410.7,\\ \\alpha=0.34,\\ \\beta=0.28 $$ 于是两者的最优配置比例应当为：$N_{o p t} \\propto C^{0.46}, D_{o p t} \\propto C^{0.54}$ 信息\r这意味着，数据集量 $D$ 与模型规模 $N$ 几乎同等重要，如果总计算预算增加了10 倍，那么模型规模以及数据规模都应当扩大约3.16 倍。 此外，Chinchilla 扩展法则进一步提出，理想的数据集大小应当是模型规模的 20 倍。例如，对于一个 7B（70 亿参数）的模型，最理想的训练数据集大小应为 140B（1400 亿）个Token。 ","date":"2025-08-22","objectID":"/posts/%E5%A4%A7%E6%A8%A1%E5%9E%8B%E5%9F%BA%E7%A1%80~02.%E5%A4%A7%E8%AF%AD%E8%A8%80%E6%A8%A1%E5%9E%8B%E6%9E%B6%E6%9E%84/:1:1","tags":["大模型"],"title":"大模型大模型基础~02.大语言模型架构","uri":"/posts/%E5%A4%A7%E6%A8%A1%E5%9E%8B%E5%9F%BA%E7%A1%80~02.%E5%A4%A7%E8%AF%AD%E8%A8%80%E6%A8%A1%E5%9E%8B%E6%9E%B6%E6%9E%84/#平衡点"},{"categories":null,"collections":["大模型基础"],"content":"1.1 平衡点\r模型规模和数据规模的增长并非没有代价，它们带来了更高的计算和存储需求，这要求我们在模型设计时必须在资源消耗和性能提升之间找到一个恰当的平衡点。 于是有团队提出了大语言模型的扩展法则（Scaling Laws），揭示了模型的能力随模型和数据规模的变化关系，为大语言模型的设计和优化提供了宝贵的指导和参考。 Kaplan-McCandlish 扩展法则\r这个法则由 OpenAI 团队提出，他们通过对不同的数据规模和模型规模进行实验，拟合出了模型性能与两者的关系： $$ L(D) = {(\\frac{D}{D_c})}^{\\alpha_D} $$ $$ \\alpha_{D} \\sim -0.095,\\ D_{c} \\sim 5.4 \\times {10}^{13} $$ $$ L(N)={(\\frac{N}{N_{c}})}^{\\alpha_{N}} $$ $$ \\alpha_{N} \\sim -0.076,\\ N_{c} \\sim 8.8 \\times {10}^{13} $$ $L(D)$ 表示在模型规模固定时，不同数据规模下的交叉熵损失函数；$L(N)$ 表示在数据规模固定时，不同模型规模下的交叉熵损失函数。 损失函数越小说明模型越精确，性能越好。于是，模型的性能与模型规模和数据规模高度正相关。 OpenAI 在进一步研究计算预算的最优分配时发现，总计算量 $C$ 与数据量 $D$ 和模型规模 $N$ 的乘积近似成正比，即 $C \\approx 6ND$。 结合上述内容进行优化，两者的最优配置比例应当为：$N_{o p t} \\propto C^{0.73}, D_{o p t} \\propto C^{0.27}$ 信息\r这意味着，如果总计算预算增加了10 倍，模型规模应扩大约5.37 倍，而数据规模应扩大约1.86 倍，以实现模型的最佳性能。 Chinchilla 扩展法则\r谷歌的 DeepMind 团队同样进行了研究，并据此提出了Chinchilla 扩展法则： $$ L(N, D)=E+\\frac{A}{N^\\alpha}+\\frac{B}{D^\\beta} $$ $$ E=1.69,\\ A=406.4,\\ B=410.7,\\ \\alpha=0.34,\\ \\beta=0.28 $$ 于是两者的最优配置比例应当为：$N_{o p t} \\propto C^{0.46}, D_{o p t} \\propto C^{0.54}$ 信息\r这意味着，数据集量 $D$ 与模型规模 $N$ 几乎同等重要，如果总计算预算增加了10 倍，那么模型规模以及数据规模都应当扩大约3.16 倍。 此外，Chinchilla 扩展法则进一步提出，理想的数据集大小应当是模型规模的 20 倍。例如，对于一个 7B（70 亿参数）的模型，最理想的训练数据集大小应为 140B（1400 亿）个Token。 ","date":"2025-08-22","objectID":"/posts/%E5%A4%A7%E6%A8%A1%E5%9E%8B%E5%9F%BA%E7%A1%80~02.%E5%A4%A7%E8%AF%AD%E8%A8%80%E6%A8%A1%E5%9E%8B%E6%9E%B6%E6%9E%84/:1:1","tags":["大模型"],"title":"大模型大模型基础~02.大语言模型架构","uri":"/posts/%E5%A4%A7%E6%A8%A1%E5%9E%8B%E5%9F%BA%E7%A1%80~02.%E5%A4%A7%E8%AF%AD%E8%A8%80%E6%A8%A1%E5%9E%8B%E6%9E%B6%E6%9E%84/#kaplan-mccandlish-扩展法则"},{"categories":null,"collections":["大模型基础"],"content":"1.1 平衡点\r模型规模和数据规模的增长并非没有代价，它们带来了更高的计算和存储需求，这要求我们在模型设计时必须在资源消耗和性能提升之间找到一个恰当的平衡点。 于是有团队提出了大语言模型的扩展法则（Scaling Laws），揭示了模型的能力随模型和数据规模的变化关系，为大语言模型的设计和优化提供了宝贵的指导和参考。 Kaplan-McCandlish 扩展法则\r这个法则由 OpenAI 团队提出，他们通过对不同的数据规模和模型规模进行实验，拟合出了模型性能与两者的关系： $$ L(D) = {(\\frac{D}{D_c})}^{\\alpha_D} $$ $$ \\alpha_{D} \\sim -0.095,\\ D_{c} \\sim 5.4 \\times {10}^{13} $$ $$ L(N)={(\\frac{N}{N_{c}})}^{\\alpha_{N}} $$ $$ \\alpha_{N} \\sim -0.076,\\ N_{c} \\sim 8.8 \\times {10}^{13} $$ $L(D)$ 表示在模型规模固定时，不同数据规模下的交叉熵损失函数；$L(N)$ 表示在数据规模固定时，不同模型规模下的交叉熵损失函数。 损失函数越小说明模型越精确，性能越好。于是，模型的性能与模型规模和数据规模高度正相关。 OpenAI 在进一步研究计算预算的最优分配时发现，总计算量 $C$ 与数据量 $D$ 和模型规模 $N$ 的乘积近似成正比，即 $C \\approx 6ND$。 结合上述内容进行优化，两者的最优配置比例应当为：$N_{o p t} \\propto C^{0.73}, D_{o p t} \\propto C^{0.27}$ 信息\r这意味着，如果总计算预算增加了10 倍，模型规模应扩大约5.37 倍，而数据规模应扩大约1.86 倍，以实现模型的最佳性能。 Chinchilla 扩展法则\r谷歌的 DeepMind 团队同样进行了研究，并据此提出了Chinchilla 扩展法则： $$ L(N, D)=E+\\frac{A}{N^\\alpha}+\\frac{B}{D^\\beta} $$ $$ E=1.69,\\ A=406.4,\\ B=410.7,\\ \\alpha=0.34,\\ \\beta=0.28 $$ 于是两者的最优配置比例应当为：$N_{o p t} \\propto C^{0.46}, D_{o p t} \\propto C^{0.54}$ 信息\r这意味着，数据集量 $D$ 与模型规模 $N$ 几乎同等重要，如果总计算预算增加了10 倍，那么模型规模以及数据规模都应当扩大约3.16 倍。 此外，Chinchilla 扩展法则进一步提出，理想的数据集大小应当是模型规模的 20 倍。例如，对于一个 7B（70 亿参数）的模型，最理想的训练数据集大小应为 140B（1400 亿）个Token。 ","date":"2025-08-22","objectID":"/posts/%E5%A4%A7%E6%A8%A1%E5%9E%8B%E5%9F%BA%E7%A1%80~02.%E5%A4%A7%E8%AF%AD%E8%A8%80%E6%A8%A1%E5%9E%8B%E6%9E%B6%E6%9E%84/:1:1","tags":["大模型"],"title":"大模型大模型基础~02.大语言模型架构","uri":"/posts/%E5%A4%A7%E6%A8%A1%E5%9E%8B%E5%9F%BA%E7%A1%80~02.%E5%A4%A7%E8%AF%AD%E8%A8%80%E6%A8%A1%E5%9E%8B%E6%9E%B6%E6%9E%84/#chinchilla-扩展法则"},{"categories":null,"collections":["大模型基础"],"content":"1.2 涌现\r随着模型训练数据规模以及参数数量的不断提升，大模型“解锁”了例如上下文学习能力、常识推理能力、数学运算能力、代码生成能力等新的能力，这些能力并非通过在特定下游任务上通过训练获得，而是随着模型复杂度的提升凭空自然涌现的。 「上下文学习」：大语言模型在推理过程中，能够利用输入文本的上下文信息来执行特定任务的能力。 「代码生成」：大语言模型基于自然语言描述自动生成编程代码。 「逻辑推理」：大语言模型能够基于给定信息和规则进行合乎逻辑的推断和结论。 ","date":"2025-08-22","objectID":"/posts/%E5%A4%A7%E6%A8%A1%E5%9E%8B%E5%9F%BA%E7%A1%80~02.%E5%A4%A7%E8%AF%AD%E8%A8%80%E6%A8%A1%E5%9E%8B%E6%9E%B6%E6%9E%84/:1:2","tags":["大模型"],"title":"大模型大模型基础~02.大语言模型架构","uri":"/posts/%E5%A4%A7%E6%A8%A1%E5%9E%8B%E5%9F%BA%E7%A1%80~02.%E5%A4%A7%E8%AF%AD%E8%A8%80%E6%A8%A1%E5%9E%8B%E6%9E%B6%E6%9E%84/#涌现"},{"categories":null,"collections":["大模型基础"],"content":"第 2 节 架构\r绝大多数大语言模型均以 Transformer 框架为核心，并进一步演化出了三种经典架构，分别是Encoder-only 架构，Decoder-only 架构以及 Encoder-Decoder 架构。 ","date":"2025-08-22","objectID":"/posts/%E5%A4%A7%E6%A8%A1%E5%9E%8B%E5%9F%BA%E7%A1%80~02.%E5%A4%A7%E8%AF%AD%E8%A8%80%E6%A8%A1%E5%9E%8B%E6%9E%B6%E6%9E%84/:2:0","tags":["大模型"],"title":"大模型大模型基础~02.大语言模型架构","uri":"/posts/%E5%A4%A7%E6%A8%A1%E5%9E%8B%E5%9F%BA%E7%A1%80~02.%E5%A4%A7%E8%AF%AD%E8%A8%80%E6%A8%A1%E5%9E%8B%E6%9E%B6%E6%9E%84/#架构"},{"categories":null,"collections":["大模型基础"],"content":"2.1 Encoder-only 架构\rEncoder-only 架构仅选取了Transformer 中的编码器（Encoder）部分，用于接收输入文本并生成与上下文相关的特征。 具体来说，Encoder-only 架构包含三个部分，分别是输入编码部分，特征编码部分以及任务处理部分。 「输入编码」：包含分词、向量化以及添加位置编码三个过程。原始输入文本会被分词器（Tokenizer） 拆解为 Token 序列，随后通过词表和词嵌入（Embedding） 矩阵映射为向量序列。接着为了保留文本中单词的顺序信息，每个向量序列会被赋予位置编码（Positional Encoding） 。 「特征编码」：由多个相同的编码模块（Encoder Block）堆叠而成，其中每个编码模块包含自注意力模块（Self-Attention）和全连接前馈模块。这些模块通过自注意力机制和前馈网络进一步提取和深化文本特征。 「任务处理」：针对任务需求专门设计的模块，其可以由用户针对任务需求自行设计。 ","date":"2025-08-22","objectID":"/posts/%E5%A4%A7%E6%A8%A1%E5%9E%8B%E5%9F%BA%E7%A1%80~02.%E5%A4%A7%E8%AF%AD%E8%A8%80%E6%A8%A1%E5%9E%8B%E6%9E%B6%E6%9E%84/:2:1","tags":["大模型"],"title":"大模型大模型基础~02.大语言模型架构","uri":"/posts/%E5%A4%A7%E6%A8%A1%E5%9E%8B%E5%9F%BA%E7%A1%80~02.%E5%A4%A7%E8%AF%AD%E8%A8%80%E6%A8%A1%E5%9E%8B%E6%9E%B6%E6%9E%84/#encoder-only-架构"},{"categories":null,"collections":["大模型基础"],"content":"2.2 Encoder-Decoder 架构\r为了弥补 Encoder-only 架构在文本生成任务上的短板，Encoder-Decoder 架构在其基础上引入了一个解码器（Decoder），并采用交叉注意力机制来实现编码器与解码器之间的有效交互。 解码器包含了输出编码、特征解码以及输出生成三个部分： 「输出编码」：与编码器中的输入编码结构相同，包含分词、向量化以及添加位置编码三个过程，将原始输入文本转换化为带有位置信息的向量序列。 「特征解码」：与编码器中的特征编码部分在网络结构上高度相似，包括掩码自注意力（Masked Self-Attention）模块，交叉注意力模块和全连接前馈模块。 「输出生成」：由一个线性层以及一个Softmax 层组成，负责将特征解码后的向量转换为词表上的概率分布，并从这个分布中采样得到最合适的 Token 作为输出。 警告\r输出编码中的输出文本和分词器只在训练阶段存在，红色虚线表示“自回归”只在推理阶段存在。 具体工作流程如下： 在训练阶段，样本中同时包含了输入和真实（Ground Truth）输出文本。 输入文本首先被输入编码部分转化为向量序列，接着在特征编码模块中被多个堆叠起来的编码模块进一步处理，从而被转化为上下文表示。 输出文本之前会被添加特殊的开始标记[START]，然后在输出编码部分被分词、词嵌入和位置编码处理后，并行输入到特征解码模块中。 解码模块使用 Teacher Forcing 技术，在每轮预测时，使用真实输出文本中的已知部分作为输入，并结合从最后一个编码块得到的上下文信息，来预测下一个 Token，计算预测的Token 和真实 Token 之间的损失，通过反向传播更新模型参数。 信息\r最后一个编码器的特征编码块包含了全部的上下文信息 在推理阶段，缺少真实的输出文本. 输出序列原始状态只有开始标记[START]，也不再需要分词器。 模型通过自回归的方式，在每轮采样生成 Token 后，会将其拼接到输出序列中，用于下一轮预测。 这个过程循环进行，直到生成特定的结束标记[end] 或达到模型设定的最大输出长度。 在这一过程中，由于每轮的输入依赖于上一轮的采样结果，因此只能一步步地串行输出。 ","date":"2025-08-22","objectID":"/posts/%E5%A4%A7%E6%A8%A1%E5%9E%8B%E5%9F%BA%E7%A1%80~02.%E5%A4%A7%E8%AF%AD%E8%A8%80%E6%A8%A1%E5%9E%8B%E6%9E%B6%E6%9E%84/:2:2","tags":["大模型"],"title":"大模型大模型基础~02.大语言模型架构","uri":"/posts/%E5%A4%A7%E6%A8%A1%E5%9E%8B%E5%9F%BA%E7%A1%80~02.%E5%A4%A7%E8%AF%AD%E8%A8%80%E6%A8%A1%E5%9E%8B%E6%9E%B6%E6%9E%84/#encoder-decoder-架构"},{"categories":null,"collections":["大模型基础"],"content":"2.3 Decoder-only 架构\r为了有效缩减模型的规模以及降低整体的计算复杂度，Decoder-only 架构摒弃了 Encoder-Decoder 架构中的编码器部分以及与编码器交互的交叉注意力模块。在这种架构下，模型仅使用解码器来构建语言模型。 Decoder-only 架构同样包含了三个部分，分别是输入编码部分、特征解码部分以及输出生成部分。 ","date":"2025-08-22","objectID":"/posts/%E5%A4%A7%E6%A8%A1%E5%9E%8B%E5%9F%BA%E7%A1%80~02.%E5%A4%A7%E8%AF%AD%E8%A8%80%E6%A8%A1%E5%9E%8B%E6%9E%B6%E6%9E%84/:2:3","tags":["大模型"],"title":"大模型大模型基础~02.大语言模型架构","uri":"/posts/%E5%A4%A7%E6%A8%A1%E5%9E%8B%E5%9F%BA%E7%A1%80~02.%E5%A4%A7%E8%AF%AD%E8%A8%80%E6%A8%A1%E5%9E%8B%E6%9E%B6%E6%9E%84/#decoder-only-架构"},{"categories":null,"collections":["大模型基础"],"content":"2.4 功能对比\r注意力矩阵\rEncoder-only 架构\r注意力矩阵来自于自注意力模块，用于捕捉输入序列中各个Token 之间的关系。Encoder-only 架构的注意力矩阵呈现出“完全”的注意力，即对于每个 Token 的理解都依赖于整个输入序列中的所有 Token。 示例\r在将输入Token $x_i$ 转换为上下文向量 $y_i$ 的过程中，模型能够综合利用从$x_1 \\sim x_n$ 的所有输入信息，这就是所谓的双向注意力机制。在这种双向注意力机制的作用下，模型能够同时利用前后文信息，深入理解复杂的语义联系和上下文依赖。 Encoder-Decoder 架构\r注意力矩阵较为复杂，它结合了编码器的自注意力、解码器的掩码自注意力以及交叉注意力三种机制。 编码器的自注意力矩阵与Encoder-only 架构类似，用于生成输入序列的全面上下文表示，呈现“完全”的注意力。 解码器的掩码自注意力矩阵则呈现出“下三角”的注意力，防止未来信息的“泄露”，确保解码过程的自回归特性。 交叉注意力机制允许解码器始终能够动态地参考编码器生成的完整上下文表示，确保输出与输入序列高度相关且连贯。 示例\r编码器将输入 $x_i$ 转化为上下文向量时，可以利用从 $x_1 \\sim x_n$ 的 所有输入信息（自注意力）。 解码器在生成 Token $y_i$ 的时候，可以参考由 $x_1 \\sim x_n$ 转化得到的 上下文向量（交叉注意力）以及先前生成的Token 序列 $y_1 \\sim y_{i-1}$ 的相关信息（掩码自注意力）。 Decoder-only 架构\r注意力矩阵来自于掩码自注意力模块，其特点是呈现出“下三角”的注意力模式。这意味着在预测当前 Token 时，模型只能依赖于已经生成的历史 Token 信息，体现了单向注意力机制。 示例\r在生成Token $y_i$ 的时候，模型只能考虑先前 $y_1 \\sim y_{i-1}$ 的信息，这样的设计确保了生成过程的顺序性和文本的连贯性。 适用任务\rEncoder-only 架构\r双向注意力机制允许模型在预测每个Token 时都充分考虑序列中的前后文信息，捕捉丰富的语义和依赖关系。模型特别适合于自然语言理解（Natural Language Understanding, NLU）任务，如情感分析或文本分类等判别任务。 由于缺少解码器组件，模型无法直接生成所需目标序列，因此在自然语言生成任务（Natural LanguageGeneration, NLG）上可能表现不如专门设计的生成模型。 Encoder-Decode 架构\r编码器和解码器的结合，使得模型可以有效地处理复杂的输入条件，并生成相关且连贯的高质量内容。模型非常适合于处理各种复杂的有条件生成任务，例如机器翻译、文本摘要和问答系统等需要同时理解输入并生成相应输出的场景。 新添加解码器也同样带来了模型规模以及计算量庞大的问题。 Decoder-only 架构\r大规模预训练数据的加持使得 Decoder-only 架构的模型能够生成高质量、连贯的文本，在自动故事生成、新闻文章生成此类不依赖于特定的输入文本的无条件文本生成任务中表现出色。 然而，在模型规模有限的情况下（例如GPT-1 以及GPT-2 等模型），由于缺乏编码器提供的双向上下文信息，Decoder-only 架构的模型在理解复杂输入数据时存在一定局限性，表现可能不如Encoder-Decoder 架构。 ","date":"2025-08-22","objectID":"/posts/%E5%A4%A7%E6%A8%A1%E5%9E%8B%E5%9F%BA%E7%A1%80~02.%E5%A4%A7%E8%AF%AD%E8%A8%80%E6%A8%A1%E5%9E%8B%E6%9E%B6%E6%9E%84/:2:4","tags":["大模型"],"title":"大模型大模型基础~02.大语言模型架构","uri":"/posts/%E5%A4%A7%E6%A8%A1%E5%9E%8B%E5%9F%BA%E7%A1%80~02.%E5%A4%A7%E8%AF%AD%E8%A8%80%E6%A8%A1%E5%9E%8B%E6%9E%B6%E6%9E%84/#功能对比"},{"categories":null,"collections":["大模型基础"],"content":"2.4 功能对比\r注意力矩阵\rEncoder-only 架构\r注意力矩阵来自于自注意力模块，用于捕捉输入序列中各个Token 之间的关系。Encoder-only 架构的注意力矩阵呈现出“完全”的注意力，即对于每个 Token 的理解都依赖于整个输入序列中的所有 Token。 示例\r在将输入Token $x_i$ 转换为上下文向量 $y_i$ 的过程中，模型能够综合利用从$x_1 \\sim x_n$ 的所有输入信息，这就是所谓的双向注意力机制。在这种双向注意力机制的作用下，模型能够同时利用前后文信息，深入理解复杂的语义联系和上下文依赖。 Encoder-Decoder 架构\r注意力矩阵较为复杂，它结合了编码器的自注意力、解码器的掩码自注意力以及交叉注意力三种机制。 编码器的自注意力矩阵与Encoder-only 架构类似，用于生成输入序列的全面上下文表示，呈现“完全”的注意力。 解码器的掩码自注意力矩阵则呈现出“下三角”的注意力，防止未来信息的“泄露”，确保解码过程的自回归特性。 交叉注意力机制允许解码器始终能够动态地参考编码器生成的完整上下文表示，确保输出与输入序列高度相关且连贯。 示例\r编码器将输入 $x_i$ 转化为上下文向量时，可以利用从 $x_1 \\sim x_n$ 的 所有输入信息（自注意力）。 解码器在生成 Token $y_i$ 的时候，可以参考由 $x_1 \\sim x_n$ 转化得到的 上下文向量（交叉注意力）以及先前生成的Token 序列 $y_1 \\sim y_{i-1}$ 的相关信息（掩码自注意力）。 Decoder-only 架构\r注意力矩阵来自于掩码自注意力模块，其特点是呈现出“下三角”的注意力模式。这意味着在预测当前 Token 时，模型只能依赖于已经生成的历史 Token 信息，体现了单向注意力机制。 示例\r在生成Token $y_i$ 的时候，模型只能考虑先前 $y_1 \\sim y_{i-1}$ 的信息，这样的设计确保了生成过程的顺序性和文本的连贯性。 适用任务\rEncoder-only 架构\r双向注意力机制允许模型在预测每个Token 时都充分考虑序列中的前后文信息，捕捉丰富的语义和依赖关系。模型特别适合于自然语言理解（Natural Language Understanding, NLU）任务，如情感分析或文本分类等判别任务。 由于缺少解码器组件，模型无法直接生成所需目标序列，因此在自然语言生成任务（Natural LanguageGeneration, NLG）上可能表现不如专门设计的生成模型。 Encoder-Decode 架构\r编码器和解码器的结合，使得模型可以有效地处理复杂的输入条件，并生成相关且连贯的高质量内容。模型非常适合于处理各种复杂的有条件生成任务，例如机器翻译、文本摘要和问答系统等需要同时理解输入并生成相应输出的场景。 新添加解码器也同样带来了模型规模以及计算量庞大的问题。 Decoder-only 架构\r大规模预训练数据的加持使得 Decoder-only 架构的模型能够生成高质量、连贯的文本，在自动故事生成、新闻文章生成此类不依赖于特定的输入文本的无条件文本生成任务中表现出色。 然而，在模型规模有限的情况下（例如GPT-1 以及GPT-2 等模型），由于缺乏编码器提供的双向上下文信息，Decoder-only 架构的模型在理解复杂输入数据时存在一定局限性，表现可能不如Encoder-Decoder 架构。 ","date":"2025-08-22","objectID":"/posts/%E5%A4%A7%E6%A8%A1%E5%9E%8B%E5%9F%BA%E7%A1%80~02.%E5%A4%A7%E8%AF%AD%E8%A8%80%E6%A8%A1%E5%9E%8B%E6%9E%B6%E6%9E%84/:2:4","tags":["大模型"],"title":"大模型大模型基础~02.大语言模型架构","uri":"/posts/%E5%A4%A7%E6%A8%A1%E5%9E%8B%E5%9F%BA%E7%A1%80~02.%E5%A4%A7%E8%AF%AD%E8%A8%80%E6%A8%A1%E5%9E%8B%E6%9E%B6%E6%9E%84/#注意力矩阵"},{"categories":null,"collections":["大模型基础"],"content":"2.4 功能对比\r注意力矩阵\rEncoder-only 架构\r注意力矩阵来自于自注意力模块，用于捕捉输入序列中各个Token 之间的关系。Encoder-only 架构的注意力矩阵呈现出“完全”的注意力，即对于每个 Token 的理解都依赖于整个输入序列中的所有 Token。 示例\r在将输入Token $x_i$ 转换为上下文向量 $y_i$ 的过程中，模型能够综合利用从$x_1 \\sim x_n$ 的所有输入信息，这就是所谓的双向注意力机制。在这种双向注意力机制的作用下，模型能够同时利用前后文信息，深入理解复杂的语义联系和上下文依赖。 Encoder-Decoder 架构\r注意力矩阵较为复杂，它结合了编码器的自注意力、解码器的掩码自注意力以及交叉注意力三种机制。 编码器的自注意力矩阵与Encoder-only 架构类似，用于生成输入序列的全面上下文表示，呈现“完全”的注意力。 解码器的掩码自注意力矩阵则呈现出“下三角”的注意力，防止未来信息的“泄露”，确保解码过程的自回归特性。 交叉注意力机制允许解码器始终能够动态地参考编码器生成的完整上下文表示，确保输出与输入序列高度相关且连贯。 示例\r编码器将输入 $x_i$ 转化为上下文向量时，可以利用从 $x_1 \\sim x_n$ 的 所有输入信息（自注意力）。 解码器在生成 Token $y_i$ 的时候，可以参考由 $x_1 \\sim x_n$ 转化得到的 上下文向量（交叉注意力）以及先前生成的Token 序列 $y_1 \\sim y_{i-1}$ 的相关信息（掩码自注意力）。 Decoder-only 架构\r注意力矩阵来自于掩码自注意力模块，其特点是呈现出“下三角”的注意力模式。这意味着在预测当前 Token 时，模型只能依赖于已经生成的历史 Token 信息，体现了单向注意力机制。 示例\r在生成Token $y_i$ 的时候，模型只能考虑先前 $y_1 \\sim y_{i-1}$ 的信息，这样的设计确保了生成过程的顺序性和文本的连贯性。 适用任务\rEncoder-only 架构\r双向注意力机制允许模型在预测每个Token 时都充分考虑序列中的前后文信息，捕捉丰富的语义和依赖关系。模型特别适合于自然语言理解（Natural Language Understanding, NLU）任务，如情感分析或文本分类等判别任务。 由于缺少解码器组件，模型无法直接生成所需目标序列，因此在自然语言生成任务（Natural LanguageGeneration, NLG）上可能表现不如专门设计的生成模型。 Encoder-Decode 架构\r编码器和解码器的结合，使得模型可以有效地处理复杂的输入条件，并生成相关且连贯的高质量内容。模型非常适合于处理各种复杂的有条件生成任务，例如机器翻译、文本摘要和问答系统等需要同时理解输入并生成相应输出的场景。 新添加解码器也同样带来了模型规模以及计算量庞大的问题。 Decoder-only 架构\r大规模预训练数据的加持使得 Decoder-only 架构的模型能够生成高质量、连贯的文本，在自动故事生成、新闻文章生成此类不依赖于特定的输入文本的无条件文本生成任务中表现出色。 然而，在模型规模有限的情况下（例如GPT-1 以及GPT-2 等模型），由于缺乏编码器提供的双向上下文信息，Decoder-only 架构的模型在理解复杂输入数据时存在一定局限性，表现可能不如Encoder-Decoder 架构。 ","date":"2025-08-22","objectID":"/posts/%E5%A4%A7%E6%A8%A1%E5%9E%8B%E5%9F%BA%E7%A1%80~02.%E5%A4%A7%E8%AF%AD%E8%A8%80%E6%A8%A1%E5%9E%8B%E6%9E%B6%E6%9E%84/:2:4","tags":["大模型"],"title":"大模型大模型基础~02.大语言模型架构","uri":"/posts/%E5%A4%A7%E6%A8%A1%E5%9E%8B%E5%9F%BA%E7%A1%80~02.%E5%A4%A7%E8%AF%AD%E8%A8%80%E6%A8%A1%E5%9E%8B%E6%9E%B6%E6%9E%84/#encoder-only-架构-1"},{"categories":null,"collections":["大模型基础"],"content":"2.4 功能对比\r注意力矩阵\rEncoder-only 架构\r注意力矩阵来自于自注意力模块，用于捕捉输入序列中各个Token 之间的关系。Encoder-only 架构的注意力矩阵呈现出“完全”的注意力，即对于每个 Token 的理解都依赖于整个输入序列中的所有 Token。 示例\r在将输入Token $x_i$ 转换为上下文向量 $y_i$ 的过程中，模型能够综合利用从$x_1 \\sim x_n$ 的所有输入信息，这就是所谓的双向注意力机制。在这种双向注意力机制的作用下，模型能够同时利用前后文信息，深入理解复杂的语义联系和上下文依赖。 Encoder-Decoder 架构\r注意力矩阵较为复杂，它结合了编码器的自注意力、解码器的掩码自注意力以及交叉注意力三种机制。 编码器的自注意力矩阵与Encoder-only 架构类似，用于生成输入序列的全面上下文表示，呈现“完全”的注意力。 解码器的掩码自注意力矩阵则呈现出“下三角”的注意力，防止未来信息的“泄露”，确保解码过程的自回归特性。 交叉注意力机制允许解码器始终能够动态地参考编码器生成的完整上下文表示，确保输出与输入序列高度相关且连贯。 示例\r编码器将输入 $x_i$ 转化为上下文向量时，可以利用从 $x_1 \\sim x_n$ 的 所有输入信息（自注意力）。 解码器在生成 Token $y_i$ 的时候，可以参考由 $x_1 \\sim x_n$ 转化得到的 上下文向量（交叉注意力）以及先前生成的Token 序列 $y_1 \\sim y_{i-1}$ 的相关信息（掩码自注意力）。 Decoder-only 架构\r注意力矩阵来自于掩码自注意力模块，其特点是呈现出“下三角”的注意力模式。这意味着在预测当前 Token 时，模型只能依赖于已经生成的历史 Token 信息，体现了单向注意力机制。 示例\r在生成Token $y_i$ 的时候，模型只能考虑先前 $y_1 \\sim y_{i-1}$ 的信息，这样的设计确保了生成过程的顺序性和文本的连贯性。 适用任务\rEncoder-only 架构\r双向注意力机制允许模型在预测每个Token 时都充分考虑序列中的前后文信息，捕捉丰富的语义和依赖关系。模型特别适合于自然语言理解（Natural Language Understanding, NLU）任务，如情感分析或文本分类等判别任务。 由于缺少解码器组件，模型无法直接生成所需目标序列，因此在自然语言生成任务（Natural LanguageGeneration, NLG）上可能表现不如专门设计的生成模型。 Encoder-Decode 架构\r编码器和解码器的结合，使得模型可以有效地处理复杂的输入条件，并生成相关且连贯的高质量内容。模型非常适合于处理各种复杂的有条件生成任务，例如机器翻译、文本摘要和问答系统等需要同时理解输入并生成相应输出的场景。 新添加解码器也同样带来了模型规模以及计算量庞大的问题。 Decoder-only 架构\r大规模预训练数据的加持使得 Decoder-only 架构的模型能够生成高质量、连贯的文本，在自动故事生成、新闻文章生成此类不依赖于特定的输入文本的无条件文本生成任务中表现出色。 然而，在模型规模有限的情况下（例如GPT-1 以及GPT-2 等模型），由于缺乏编码器提供的双向上下文信息，Decoder-only 架构的模型在理解复杂输入数据时存在一定局限性，表现可能不如Encoder-Decoder 架构。 ","date":"2025-08-22","objectID":"/posts/%E5%A4%A7%E6%A8%A1%E5%9E%8B%E5%9F%BA%E7%A1%80~02.%E5%A4%A7%E8%AF%AD%E8%A8%80%E6%A8%A1%E5%9E%8B%E6%9E%B6%E6%9E%84/:2:4","tags":["大模型"],"title":"大模型大模型基础~02.大语言模型架构","uri":"/posts/%E5%A4%A7%E6%A8%A1%E5%9E%8B%E5%9F%BA%E7%A1%80~02.%E5%A4%A7%E8%AF%AD%E8%A8%80%E6%A8%A1%E5%9E%8B%E6%9E%B6%E6%9E%84/#encoder-decoder-架构-1"},{"categories":null,"collections":["大模型基础"],"content":"2.4 功能对比\r注意力矩阵\rEncoder-only 架构\r注意力矩阵来自于自注意力模块，用于捕捉输入序列中各个Token 之间的关系。Encoder-only 架构的注意力矩阵呈现出“完全”的注意力，即对于每个 Token 的理解都依赖于整个输入序列中的所有 Token。 示例\r在将输入Token $x_i$ 转换为上下文向量 $y_i$ 的过程中，模型能够综合利用从$x_1 \\sim x_n$ 的所有输入信息，这就是所谓的双向注意力机制。在这种双向注意力机制的作用下，模型能够同时利用前后文信息，深入理解复杂的语义联系和上下文依赖。 Encoder-Decoder 架构\r注意力矩阵较为复杂，它结合了编码器的自注意力、解码器的掩码自注意力以及交叉注意力三种机制。 编码器的自注意力矩阵与Encoder-only 架构类似，用于生成输入序列的全面上下文表示，呈现“完全”的注意力。 解码器的掩码自注意力矩阵则呈现出“下三角”的注意力，防止未来信息的“泄露”，确保解码过程的自回归特性。 交叉注意力机制允许解码器始终能够动态地参考编码器生成的完整上下文表示，确保输出与输入序列高度相关且连贯。 示例\r编码器将输入 $x_i$ 转化为上下文向量时，可以利用从 $x_1 \\sim x_n$ 的 所有输入信息（自注意力）。 解码器在生成 Token $y_i$ 的时候，可以参考由 $x_1 \\sim x_n$ 转化得到的 上下文向量（交叉注意力）以及先前生成的Token 序列 $y_1 \\sim y_{i-1}$ 的相关信息（掩码自注意力）。 Decoder-only 架构\r注意力矩阵来自于掩码自注意力模块，其特点是呈现出“下三角”的注意力模式。这意味着在预测当前 Token 时，模型只能依赖于已经生成的历史 Token 信息，体现了单向注意力机制。 示例\r在生成Token $y_i$ 的时候，模型只能考虑先前 $y_1 \\sim y_{i-1}$ 的信息，这样的设计确保了生成过程的顺序性和文本的连贯性。 适用任务\rEncoder-only 架构\r双向注意力机制允许模型在预测每个Token 时都充分考虑序列中的前后文信息，捕捉丰富的语义和依赖关系。模型特别适合于自然语言理解（Natural Language Understanding, NLU）任务，如情感分析或文本分类等判别任务。 由于缺少解码器组件，模型无法直接生成所需目标序列，因此在自然语言生成任务（Natural LanguageGeneration, NLG）上可能表现不如专门设计的生成模型。 Encoder-Decode 架构\r编码器和解码器的结合，使得模型可以有效地处理复杂的输入条件，并生成相关且连贯的高质量内容。模型非常适合于处理各种复杂的有条件生成任务，例如机器翻译、文本摘要和问答系统等需要同时理解输入并生成相应输出的场景。 新添加解码器也同样带来了模型规模以及计算量庞大的问题。 Decoder-only 架构\r大规模预训练数据的加持使得 Decoder-only 架构的模型能够生成高质量、连贯的文本，在自动故事生成、新闻文章生成此类不依赖于特定的输入文本的无条件文本生成任务中表现出色。 然而，在模型规模有限的情况下（例如GPT-1 以及GPT-2 等模型），由于缺乏编码器提供的双向上下文信息，Decoder-only 架构的模型在理解复杂输入数据时存在一定局限性，表现可能不如Encoder-Decoder 架构。 ","date":"2025-08-22","objectID":"/posts/%E5%A4%A7%E6%A8%A1%E5%9E%8B%E5%9F%BA%E7%A1%80~02.%E5%A4%A7%E8%AF%AD%E8%A8%80%E6%A8%A1%E5%9E%8B%E6%9E%B6%E6%9E%84/:2:4","tags":["大模型"],"title":"大模型大模型基础~02.大语言模型架构","uri":"/posts/%E5%A4%A7%E6%A8%A1%E5%9E%8B%E5%9F%BA%E7%A1%80~02.%E5%A4%A7%E8%AF%AD%E8%A8%80%E6%A8%A1%E5%9E%8B%E6%9E%B6%E6%9E%84/#decoder-only-架构-1"},{"categories":null,"collections":["大模型基础"],"content":"2.4 功能对比\r注意力矩阵\rEncoder-only 架构\r注意力矩阵来自于自注意力模块，用于捕捉输入序列中各个Token 之间的关系。Encoder-only 架构的注意力矩阵呈现出“完全”的注意力，即对于每个 Token 的理解都依赖于整个输入序列中的所有 Token。 示例\r在将输入Token $x_i$ 转换为上下文向量 $y_i$ 的过程中，模型能够综合利用从$x_1 \\sim x_n$ 的所有输入信息，这就是所谓的双向注意力机制。在这种双向注意力机制的作用下，模型能够同时利用前后文信息，深入理解复杂的语义联系和上下文依赖。 Encoder-Decoder 架构\r注意力矩阵较为复杂，它结合了编码器的自注意力、解码器的掩码自注意力以及交叉注意力三种机制。 编码器的自注意力矩阵与Encoder-only 架构类似，用于生成输入序列的全面上下文表示，呈现“完全”的注意力。 解码器的掩码自注意力矩阵则呈现出“下三角”的注意力，防止未来信息的“泄露”，确保解码过程的自回归特性。 交叉注意力机制允许解码器始终能够动态地参考编码器生成的完整上下文表示，确保输出与输入序列高度相关且连贯。 示例\r编码器将输入 $x_i$ 转化为上下文向量时，可以利用从 $x_1 \\sim x_n$ 的 所有输入信息（自注意力）。 解码器在生成 Token $y_i$ 的时候，可以参考由 $x_1 \\sim x_n$ 转化得到的 上下文向量（交叉注意力）以及先前生成的Token 序列 $y_1 \\sim y_{i-1}$ 的相关信息（掩码自注意力）。 Decoder-only 架构\r注意力矩阵来自于掩码自注意力模块，其特点是呈现出“下三角”的注意力模式。这意味着在预测当前 Token 时，模型只能依赖于已经生成的历史 Token 信息，体现了单向注意力机制。 示例\r在生成Token $y_i$ 的时候，模型只能考虑先前 $y_1 \\sim y_{i-1}$ 的信息，这样的设计确保了生成过程的顺序性和文本的连贯性。 适用任务\rEncoder-only 架构\r双向注意力机制允许模型在预测每个Token 时都充分考虑序列中的前后文信息，捕捉丰富的语义和依赖关系。模型特别适合于自然语言理解（Natural Language Understanding, NLU）任务，如情感分析或文本分类等判别任务。 由于缺少解码器组件，模型无法直接生成所需目标序列，因此在自然语言生成任务（Natural LanguageGeneration, NLG）上可能表现不如专门设计的生成模型。 Encoder-Decode 架构\r编码器和解码器的结合，使得模型可以有效地处理复杂的输入条件，并生成相关且连贯的高质量内容。模型非常适合于处理各种复杂的有条件生成任务，例如机器翻译、文本摘要和问答系统等需要同时理解输入并生成相应输出的场景。 新添加解码器也同样带来了模型规模以及计算量庞大的问题。 Decoder-only 架构\r大规模预训练数据的加持使得 Decoder-only 架构的模型能够生成高质量、连贯的文本，在自动故事生成、新闻文章生成此类不依赖于特定的输入文本的无条件文本生成任务中表现出色。 然而，在模型规模有限的情况下（例如GPT-1 以及GPT-2 等模型），由于缺乏编码器提供的双向上下文信息，Decoder-only 架构的模型在理解复杂输入数据时存在一定局限性，表现可能不如Encoder-Decoder 架构。 ","date":"2025-08-22","objectID":"/posts/%E5%A4%A7%E6%A8%A1%E5%9E%8B%E5%9F%BA%E7%A1%80~02.%E5%A4%A7%E8%AF%AD%E8%A8%80%E6%A8%A1%E5%9E%8B%E6%9E%B6%E6%9E%84/:2:4","tags":["大模型"],"title":"大模型大模型基础~02.大语言模型架构","uri":"/posts/%E5%A4%A7%E6%A8%A1%E5%9E%8B%E5%9F%BA%E7%A1%80~02.%E5%A4%A7%E8%AF%AD%E8%A8%80%E6%A8%A1%E5%9E%8B%E6%9E%B6%E6%9E%84/#适用任务"},{"categories":null,"collections":["大模型基础"],"content":"2.4 功能对比\r注意力矩阵\rEncoder-only 架构\r注意力矩阵来自于自注意力模块，用于捕捉输入序列中各个Token 之间的关系。Encoder-only 架构的注意力矩阵呈现出“完全”的注意力，即对于每个 Token 的理解都依赖于整个输入序列中的所有 Token。 示例\r在将输入Token $x_i$ 转换为上下文向量 $y_i$ 的过程中，模型能够综合利用从$x_1 \\sim x_n$ 的所有输入信息，这就是所谓的双向注意力机制。在这种双向注意力机制的作用下，模型能够同时利用前后文信息，深入理解复杂的语义联系和上下文依赖。 Encoder-Decoder 架构\r注意力矩阵较为复杂，它结合了编码器的自注意力、解码器的掩码自注意力以及交叉注意力三种机制。 编码器的自注意力矩阵与Encoder-only 架构类似，用于生成输入序列的全面上下文表示，呈现“完全”的注意力。 解码器的掩码自注意力矩阵则呈现出“下三角”的注意力，防止未来信息的“泄露”，确保解码过程的自回归特性。 交叉注意力机制允许解码器始终能够动态地参考编码器生成的完整上下文表示，确保输出与输入序列高度相关且连贯。 示例\r编码器将输入 $x_i$ 转化为上下文向量时，可以利用从 $x_1 \\sim x_n$ 的 所有输入信息（自注意力）。 解码器在生成 Token $y_i$ 的时候，可以参考由 $x_1 \\sim x_n$ 转化得到的 上下文向量（交叉注意力）以及先前生成的Token 序列 $y_1 \\sim y_{i-1}$ 的相关信息（掩码自注意力）。 Decoder-only 架构\r注意力矩阵来自于掩码自注意力模块，其特点是呈现出“下三角”的注意力模式。这意味着在预测当前 Token 时，模型只能依赖于已经生成的历史 Token 信息，体现了单向注意力机制。 示例\r在生成Token $y_i$ 的时候，模型只能考虑先前 $y_1 \\sim y_{i-1}$ 的信息，这样的设计确保了生成过程的顺序性和文本的连贯性。 适用任务\rEncoder-only 架构\r双向注意力机制允许模型在预测每个Token 时都充分考虑序列中的前后文信息，捕捉丰富的语义和依赖关系。模型特别适合于自然语言理解（Natural Language Understanding, NLU）任务，如情感分析或文本分类等判别任务。 由于缺少解码器组件，模型无法直接生成所需目标序列，因此在自然语言生成任务（Natural LanguageGeneration, NLG）上可能表现不如专门设计的生成模型。 Encoder-Decode 架构\r编码器和解码器的结合，使得模型可以有效地处理复杂的输入条件，并生成相关且连贯的高质量内容。模型非常适合于处理各种复杂的有条件生成任务，例如机器翻译、文本摘要和问答系统等需要同时理解输入并生成相应输出的场景。 新添加解码器也同样带来了模型规模以及计算量庞大的问题。 Decoder-only 架构\r大规模预训练数据的加持使得 Decoder-only 架构的模型能够生成高质量、连贯的文本，在自动故事生成、新闻文章生成此类不依赖于特定的输入文本的无条件文本生成任务中表现出色。 然而，在模型规模有限的情况下（例如GPT-1 以及GPT-2 等模型），由于缺乏编码器提供的双向上下文信息，Decoder-only 架构的模型在理解复杂输入数据时存在一定局限性，表现可能不如Encoder-Decoder 架构。 ","date":"2025-08-22","objectID":"/posts/%E5%A4%A7%E6%A8%A1%E5%9E%8B%E5%9F%BA%E7%A1%80~02.%E5%A4%A7%E8%AF%AD%E8%A8%80%E6%A8%A1%E5%9E%8B%E6%9E%B6%E6%9E%84/:2:4","tags":["大模型"],"title":"大模型大模型基础~02.大语言模型架构","uri":"/posts/%E5%A4%A7%E6%A8%A1%E5%9E%8B%E5%9F%BA%E7%A1%80~02.%E5%A4%A7%E8%AF%AD%E8%A8%80%E6%A8%A1%E5%9E%8B%E6%9E%B6%E6%9E%84/#encoder-only-架构-2"},{"categories":null,"collections":["大模型基础"],"content":"2.4 功能对比\r注意力矩阵\rEncoder-only 架构\r注意力矩阵来自于自注意力模块，用于捕捉输入序列中各个Token 之间的关系。Encoder-only 架构的注意力矩阵呈现出“完全”的注意力，即对于每个 Token 的理解都依赖于整个输入序列中的所有 Token。 示例\r在将输入Token $x_i$ 转换为上下文向量 $y_i$ 的过程中，模型能够综合利用从$x_1 \\sim x_n$ 的所有输入信息，这就是所谓的双向注意力机制。在这种双向注意力机制的作用下，模型能够同时利用前后文信息，深入理解复杂的语义联系和上下文依赖。 Encoder-Decoder 架构\r注意力矩阵较为复杂，它结合了编码器的自注意力、解码器的掩码自注意力以及交叉注意力三种机制。 编码器的自注意力矩阵与Encoder-only 架构类似，用于生成输入序列的全面上下文表示，呈现“完全”的注意力。 解码器的掩码自注意力矩阵则呈现出“下三角”的注意力，防止未来信息的“泄露”，确保解码过程的自回归特性。 交叉注意力机制允许解码器始终能够动态地参考编码器生成的完整上下文表示，确保输出与输入序列高度相关且连贯。 示例\r编码器将输入 $x_i$ 转化为上下文向量时，可以利用从 $x_1 \\sim x_n$ 的 所有输入信息（自注意力）。 解码器在生成 Token $y_i$ 的时候，可以参考由 $x_1 \\sim x_n$ 转化得到的 上下文向量（交叉注意力）以及先前生成的Token 序列 $y_1 \\sim y_{i-1}$ 的相关信息（掩码自注意力）。 Decoder-only 架构\r注意力矩阵来自于掩码自注意力模块，其特点是呈现出“下三角”的注意力模式。这意味着在预测当前 Token 时，模型只能依赖于已经生成的历史 Token 信息，体现了单向注意力机制。 示例\r在生成Token $y_i$ 的时候，模型只能考虑先前 $y_1 \\sim y_{i-1}$ 的信息，这样的设计确保了生成过程的顺序性和文本的连贯性。 适用任务\rEncoder-only 架构\r双向注意力机制允许模型在预测每个Token 时都充分考虑序列中的前后文信息，捕捉丰富的语义和依赖关系。模型特别适合于自然语言理解（Natural Language Understanding, NLU）任务，如情感分析或文本分类等判别任务。 由于缺少解码器组件，模型无法直接生成所需目标序列，因此在自然语言生成任务（Natural LanguageGeneration, NLG）上可能表现不如专门设计的生成模型。 Encoder-Decode 架构\r编码器和解码器的结合，使得模型可以有效地处理复杂的输入条件，并生成相关且连贯的高质量内容。模型非常适合于处理各种复杂的有条件生成任务，例如机器翻译、文本摘要和问答系统等需要同时理解输入并生成相应输出的场景。 新添加解码器也同样带来了模型规模以及计算量庞大的问题。 Decoder-only 架构\r大规模预训练数据的加持使得 Decoder-only 架构的模型能够生成高质量、连贯的文本，在自动故事生成、新闻文章生成此类不依赖于特定的输入文本的无条件文本生成任务中表现出色。 然而，在模型规模有限的情况下（例如GPT-1 以及GPT-2 等模型），由于缺乏编码器提供的双向上下文信息，Decoder-only 架构的模型在理解复杂输入数据时存在一定局限性，表现可能不如Encoder-Decoder 架构。 ","date":"2025-08-22","objectID":"/posts/%E5%A4%A7%E6%A8%A1%E5%9E%8B%E5%9F%BA%E7%A1%80~02.%E5%A4%A7%E8%AF%AD%E8%A8%80%E6%A8%A1%E5%9E%8B%E6%9E%B6%E6%9E%84/:2:4","tags":["大模型"],"title":"大模型大模型基础~02.大语言模型架构","uri":"/posts/%E5%A4%A7%E6%A8%A1%E5%9E%8B%E5%9F%BA%E7%A1%80~02.%E5%A4%A7%E8%AF%AD%E8%A8%80%E6%A8%A1%E5%9E%8B%E6%9E%B6%E6%9E%84/#encoder-decode-架构"},{"categories":null,"collections":["大模型基础"],"content":"2.4 功能对比\r注意力矩阵\rEncoder-only 架构\r注意力矩阵来自于自注意力模块，用于捕捉输入序列中各个Token 之间的关系。Encoder-only 架构的注意力矩阵呈现出“完全”的注意力，即对于每个 Token 的理解都依赖于整个输入序列中的所有 Token。 示例\r在将输入Token $x_i$ 转换为上下文向量 $y_i$ 的过程中，模型能够综合利用从$x_1 \\sim x_n$ 的所有输入信息，这就是所谓的双向注意力机制。在这种双向注意力机制的作用下，模型能够同时利用前后文信息，深入理解复杂的语义联系和上下文依赖。 Encoder-Decoder 架构\r注意力矩阵较为复杂，它结合了编码器的自注意力、解码器的掩码自注意力以及交叉注意力三种机制。 编码器的自注意力矩阵与Encoder-only 架构类似，用于生成输入序列的全面上下文表示，呈现“完全”的注意力。 解码器的掩码自注意力矩阵则呈现出“下三角”的注意力，防止未来信息的“泄露”，确保解码过程的自回归特性。 交叉注意力机制允许解码器始终能够动态地参考编码器生成的完整上下文表示，确保输出与输入序列高度相关且连贯。 示例\r编码器将输入 $x_i$ 转化为上下文向量时，可以利用从 $x_1 \\sim x_n$ 的 所有输入信息（自注意力）。 解码器在生成 Token $y_i$ 的时候，可以参考由 $x_1 \\sim x_n$ 转化得到的 上下文向量（交叉注意力）以及先前生成的Token 序列 $y_1 \\sim y_{i-1}$ 的相关信息（掩码自注意力）。 Decoder-only 架构\r注意力矩阵来自于掩码自注意力模块，其特点是呈现出“下三角”的注意力模式。这意味着在预测当前 Token 时，模型只能依赖于已经生成的历史 Token 信息，体现了单向注意力机制。 示例\r在生成Token $y_i$ 的时候，模型只能考虑先前 $y_1 \\sim y_{i-1}$ 的信息，这样的设计确保了生成过程的顺序性和文本的连贯性。 适用任务\rEncoder-only 架构\r双向注意力机制允许模型在预测每个Token 时都充分考虑序列中的前后文信息，捕捉丰富的语义和依赖关系。模型特别适合于自然语言理解（Natural Language Understanding, NLU）任务，如情感分析或文本分类等判别任务。 由于缺少解码器组件，模型无法直接生成所需目标序列，因此在自然语言生成任务（Natural LanguageGeneration, NLG）上可能表现不如专门设计的生成模型。 Encoder-Decode 架构\r编码器和解码器的结合，使得模型可以有效地处理复杂的输入条件，并生成相关且连贯的高质量内容。模型非常适合于处理各种复杂的有条件生成任务，例如机器翻译、文本摘要和问答系统等需要同时理解输入并生成相应输出的场景。 新添加解码器也同样带来了模型规模以及计算量庞大的问题。 Decoder-only 架构\r大规模预训练数据的加持使得 Decoder-only 架构的模型能够生成高质量、连贯的文本，在自动故事生成、新闻文章生成此类不依赖于特定的输入文本的无条件文本生成任务中表现出色。 然而，在模型规模有限的情况下（例如GPT-1 以及GPT-2 等模型），由于缺乏编码器提供的双向上下文信息，Decoder-only 架构的模型在理解复杂输入数据时存在一定局限性，表现可能不如Encoder-Decoder 架构。 ","date":"2025-08-22","objectID":"/posts/%E5%A4%A7%E6%A8%A1%E5%9E%8B%E5%9F%BA%E7%A1%80~02.%E5%A4%A7%E8%AF%AD%E8%A8%80%E6%A8%A1%E5%9E%8B%E6%9E%B6%E6%9E%84/:2:4","tags":["大模型"],"title":"大模型大模型基础~02.大语言模型架构","uri":"/posts/%E5%A4%A7%E6%A8%A1%E5%9E%8B%E5%9F%BA%E7%A1%80~02.%E5%A4%A7%E8%AF%AD%E8%A8%80%E6%A8%A1%E5%9E%8B%E6%9E%B6%E6%9E%84/#decoder-only-架构-2"},{"categories":null,"collections":["大模型基础"],"content":"第 3 节 Encoder-only 模型\r","date":"2025-08-22","objectID":"/posts/%E5%A4%A7%E6%A8%A1%E5%9E%8B%E5%9F%BA%E7%A1%80~02.%E5%A4%A7%E8%AF%AD%E8%A8%80%E6%A8%A1%E5%9E%8B%E6%9E%B6%E6%9E%84/:3:0","tags":["大模型"],"title":"大模型大模型基础~02.大语言模型架构","uri":"/posts/%E5%A4%A7%E6%A8%A1%E5%9E%8B%E5%9F%BA%E7%A1%80~02.%E5%A4%A7%E8%AF%AD%E8%A8%80%E6%A8%A1%E5%9E%8B%E6%9E%B6%E6%9E%84/#encoder-only-模型"},{"categories":null,"collections":["大模型基础"],"content":"3.1 BERT 语言模型\rBERT（Bidirectional Encoder Representations from Transformers）是一种基于 Encoder-only 架构的预训练语言模型，由Google AI 团队于2018 年10 月提出。作为早期Encoder-only 架构的代表，BERT 在自然语言处理领域带来了突破性的改进。 其核心创新在于通过双向编码模型深入挖掘文本的上下文信息，从而为各种下游任务提供优秀的上下文嵌入。 模型结构\rBERT 模型的结构与 Transformer 中的编码器几乎一致，都是由多个编码模块堆叠而成，每个编码模块包含一个多头自注意力模块和一个全连接前馈模块。 根据参数量的不同，BERT 模型共有 BERT-Base 和 BERT-Large 两个版本。 BERT-Base 由 12 个编码模块堆叠而成，隐藏层维度为 768，自注意力头的数量为 12，总参数数量为 1.1 亿； BERT-Large 由 24 个编码模块堆叠而成，隐藏层维度为 1024，自注意力头的数量为 16，总参数数量约为 3.4 亿。 预训练方式\r数据集\rBERT 使用小说数据集 BookCorpus（包含约8 亿个Token）和英语维基百科数据集（包含约25 亿个Token）进行预训练，总计约33 亿个Token，总数据量达到了15GB 左右。 预训练任务\r在预训练任务上，BERT 开创性地提出了掩码语言建模（Masked Language Model, MLM）和下文预测（Next Sentence Prediction, NSP）两种任务来学习生成上下文嵌入。 具体步骤如下： 基于给定的原始文本构造多个样本序列，每个样本序列由原始文本中的两个句子组成，这两个句子有 50% 的概率是来自原文的连续句子，另外 50% 的概率是随机挑选的两个句子。 对构造出来的样本序列进行分词，并在序列的开头添加特殊标签 [CLS]，在每个句子的结尾添加特殊标签 [SEP]。其中 [CLS] 标签用于聚合整个序列的信息，而 [SEP] 标签则明确句子之间的界限。 利用处理后的序列进行下文预测任务，利用模型判断样本序列中的两个句子是否为连续的。 随机选择样本序列中大约15% 的 Token 进行遮掩，将其替换为特殊标签 [MASK] 或者随机单词。模型需要预测这些被替换的Token 的原始内容。 通过这两种预训练任务的结合，使 BERT 在理解语言的深度和广度上都有显著提升。BERT 不仅能够捕捉到 Token 的细粒度特征（掩码语言建模），还能够把握长距离的依赖关系和句子间的复杂联系（下文预测），为各种下游任务提供了坚实的语言理解基础。 下游任务\r由于 BERT 的输出是输入中所有Token 的向量表示，因此总长度不固定，无法直接应用于各类下游任务。为了解决这一问题，BERT 设计了 [CLS] 标签（Classification Token）来提取整个输入序列的聚合表示。 [CLS] 标签汇总整个输入序列的信息，生成一个固定长度的向量表示，从而实现对所有Token 序列信息的概括，便于处理各种下游任务。 在文本分类任务中，可以将输出中 [CLS] 标签对应的向量提取出来，传递给一个全连接层，从而用于分类，例如判断整个句子的情绪是积极、消极或是中立的。 在问答系统任务中，需要输入问题以及一段相关的文本，即“[CLS] 问题[SEP] 文本[SEP]”。最终同样提取出 [CLS] 标签的对应向量，并传递给两个全连接层，用于判断答案是否存在于相关文本中。如果存在，这两个全连接层分别用于输出答案的起始和结束位置。 在语义相似度任务中，需要计算两段或者多段文本之间的语义相似度。在这一任务中，可以通过构造“[CLS] 文本1[SEP] 文本2 [SEP]”的方式，结合一个线性层来直接输出两个文本之间的相似度；也可以不添加额外的组件，直接提取 [CLS] 标签对应的向量，再利用额外的相似度度量方法（例如余弦相似度）来计算多段文本之间的相似度。 ","date":"2025-08-22","objectID":"/posts/%E5%A4%A7%E6%A8%A1%E5%9E%8B%E5%9F%BA%E7%A1%80~02.%E5%A4%A7%E8%AF%AD%E8%A8%80%E6%A8%A1%E5%9E%8B%E6%9E%B6%E6%9E%84/:3:1","tags":["大模型"],"title":"大模型大模型基础~02.大语言模型架构","uri":"/posts/%E5%A4%A7%E6%A8%A1%E5%9E%8B%E5%9F%BA%E7%A1%80~02.%E5%A4%A7%E8%AF%AD%E8%A8%80%E6%A8%A1%E5%9E%8B%E6%9E%B6%E6%9E%84/#bert-语言模型"},{"categories":null,"collections":["大模型基础"],"content":"3.1 BERT 语言模型\rBERT（Bidirectional Encoder Representations from Transformers）是一种基于 Encoder-only 架构的预训练语言模型，由Google AI 团队于2018 年10 月提出。作为早期Encoder-only 架构的代表，BERT 在自然语言处理领域带来了突破性的改进。 其核心创新在于通过双向编码模型深入挖掘文本的上下文信息，从而为各种下游任务提供优秀的上下文嵌入。 模型结构\rBERT 模型的结构与 Transformer 中的编码器几乎一致，都是由多个编码模块堆叠而成，每个编码模块包含一个多头自注意力模块和一个全连接前馈模块。 根据参数量的不同，BERT 模型共有 BERT-Base 和 BERT-Large 两个版本。 BERT-Base 由 12 个编码模块堆叠而成，隐藏层维度为 768，自注意力头的数量为 12，总参数数量为 1.1 亿； BERT-Large 由 24 个编码模块堆叠而成，隐藏层维度为 1024，自注意力头的数量为 16，总参数数量约为 3.4 亿。 预训练方式\r数据集\rBERT 使用小说数据集 BookCorpus（包含约8 亿个Token）和英语维基百科数据集（包含约25 亿个Token）进行预训练，总计约33 亿个Token，总数据量达到了15GB 左右。 预训练任务\r在预训练任务上，BERT 开创性地提出了掩码语言建模（Masked Language Model, MLM）和下文预测（Next Sentence Prediction, NSP）两种任务来学习生成上下文嵌入。 具体步骤如下： 基于给定的原始文本构造多个样本序列，每个样本序列由原始文本中的两个句子组成，这两个句子有 50% 的概率是来自原文的连续句子，另外 50% 的概率是随机挑选的两个句子。 对构造出来的样本序列进行分词，并在序列的开头添加特殊标签 [CLS]，在每个句子的结尾添加特殊标签 [SEP]。其中 [CLS] 标签用于聚合整个序列的信息，而 [SEP] 标签则明确句子之间的界限。 利用处理后的序列进行下文预测任务，利用模型判断样本序列中的两个句子是否为连续的。 随机选择样本序列中大约15% 的 Token 进行遮掩，将其替换为特殊标签 [MASK] 或者随机单词。模型需要预测这些被替换的Token 的原始内容。 通过这两种预训练任务的结合，使 BERT 在理解语言的深度和广度上都有显著提升。BERT 不仅能够捕捉到 Token 的细粒度特征（掩码语言建模），还能够把握长距离的依赖关系和句子间的复杂联系（下文预测），为各种下游任务提供了坚实的语言理解基础。 下游任务\r由于 BERT 的输出是输入中所有Token 的向量表示，因此总长度不固定，无法直接应用于各类下游任务。为了解决这一问题，BERT 设计了 [CLS] 标签（Classification Token）来提取整个输入序列的聚合表示。 [CLS] 标签汇总整个输入序列的信息，生成一个固定长度的向量表示，从而实现对所有Token 序列信息的概括，便于处理各种下游任务。 在文本分类任务中，可以将输出中 [CLS] 标签对应的向量提取出来，传递给一个全连接层，从而用于分类，例如判断整个句子的情绪是积极、消极或是中立的。 在问答系统任务中，需要输入问题以及一段相关的文本，即“[CLS] 问题[SEP] 文本[SEP]”。最终同样提取出 [CLS] 标签的对应向量，并传递给两个全连接层，用于判断答案是否存在于相关文本中。如果存在，这两个全连接层分别用于输出答案的起始和结束位置。 在语义相似度任务中，需要计算两段或者多段文本之间的语义相似度。在这一任务中，可以通过构造“[CLS] 文本1[SEP] 文本2 [SEP]”的方式，结合一个线性层来直接输出两个文本之间的相似度；也可以不添加额外的组件，直接提取 [CLS] 标签对应的向量，再利用额外的相似度度量方法（例如余弦相似度）来计算多段文本之间的相似度。 ","date":"2025-08-22","objectID":"/posts/%E5%A4%A7%E6%A8%A1%E5%9E%8B%E5%9F%BA%E7%A1%80~02.%E5%A4%A7%E8%AF%AD%E8%A8%80%E6%A8%A1%E5%9E%8B%E6%9E%B6%E6%9E%84/:3:1","tags":["大模型"],"title":"大模型大模型基础~02.大语言模型架构","uri":"/posts/%E5%A4%A7%E6%A8%A1%E5%9E%8B%E5%9F%BA%E7%A1%80~02.%E5%A4%A7%E8%AF%AD%E8%A8%80%E6%A8%A1%E5%9E%8B%E6%9E%B6%E6%9E%84/#模型结构"},{"categories":null,"collections":["大模型基础"],"content":"3.1 BERT 语言模型\rBERT（Bidirectional Encoder Representations from Transformers）是一种基于 Encoder-only 架构的预训练语言模型，由Google AI 团队于2018 年10 月提出。作为早期Encoder-only 架构的代表，BERT 在自然语言处理领域带来了突破性的改进。 其核心创新在于通过双向编码模型深入挖掘文本的上下文信息，从而为各种下游任务提供优秀的上下文嵌入。 模型结构\rBERT 模型的结构与 Transformer 中的编码器几乎一致，都是由多个编码模块堆叠而成，每个编码模块包含一个多头自注意力模块和一个全连接前馈模块。 根据参数量的不同，BERT 模型共有 BERT-Base 和 BERT-Large 两个版本。 BERT-Base 由 12 个编码模块堆叠而成，隐藏层维度为 768，自注意力头的数量为 12，总参数数量为 1.1 亿； BERT-Large 由 24 个编码模块堆叠而成，隐藏层维度为 1024，自注意力头的数量为 16，总参数数量约为 3.4 亿。 预训练方式\r数据集\rBERT 使用小说数据集 BookCorpus（包含约8 亿个Token）和英语维基百科数据集（包含约25 亿个Token）进行预训练，总计约33 亿个Token，总数据量达到了15GB 左右。 预训练任务\r在预训练任务上，BERT 开创性地提出了掩码语言建模（Masked Language Model, MLM）和下文预测（Next Sentence Prediction, NSP）两种任务来学习生成上下文嵌入。 具体步骤如下： 基于给定的原始文本构造多个样本序列，每个样本序列由原始文本中的两个句子组成，这两个句子有 50% 的概率是来自原文的连续句子，另外 50% 的概率是随机挑选的两个句子。 对构造出来的样本序列进行分词，并在序列的开头添加特殊标签 [CLS]，在每个句子的结尾添加特殊标签 [SEP]。其中 [CLS] 标签用于聚合整个序列的信息，而 [SEP] 标签则明确句子之间的界限。 利用处理后的序列进行下文预测任务，利用模型判断样本序列中的两个句子是否为连续的。 随机选择样本序列中大约15% 的 Token 进行遮掩，将其替换为特殊标签 [MASK] 或者随机单词。模型需要预测这些被替换的Token 的原始内容。 通过这两种预训练任务的结合，使 BERT 在理解语言的深度和广度上都有显著提升。BERT 不仅能够捕捉到 Token 的细粒度特征（掩码语言建模），还能够把握长距离的依赖关系和句子间的复杂联系（下文预测），为各种下游任务提供了坚实的语言理解基础。 下游任务\r由于 BERT 的输出是输入中所有Token 的向量表示，因此总长度不固定，无法直接应用于各类下游任务。为了解决这一问题，BERT 设计了 [CLS] 标签（Classification Token）来提取整个输入序列的聚合表示。 [CLS] 标签汇总整个输入序列的信息，生成一个固定长度的向量表示，从而实现对所有Token 序列信息的概括，便于处理各种下游任务。 在文本分类任务中，可以将输出中 [CLS] 标签对应的向量提取出来，传递给一个全连接层，从而用于分类，例如判断整个句子的情绪是积极、消极或是中立的。 在问答系统任务中，需要输入问题以及一段相关的文本，即“[CLS] 问题[SEP] 文本[SEP]”。最终同样提取出 [CLS] 标签的对应向量，并传递给两个全连接层，用于判断答案是否存在于相关文本中。如果存在，这两个全连接层分别用于输出答案的起始和结束位置。 在语义相似度任务中，需要计算两段或者多段文本之间的语义相似度。在这一任务中，可以通过构造“[CLS] 文本1[SEP] 文本2 [SEP]”的方式，结合一个线性层来直接输出两个文本之间的相似度；也可以不添加额外的组件，直接提取 [CLS] 标签对应的向量，再利用额外的相似度度量方法（例如余弦相似度）来计算多段文本之间的相似度。 ","date":"2025-08-22","objectID":"/posts/%E5%A4%A7%E6%A8%A1%E5%9E%8B%E5%9F%BA%E7%A1%80~02.%E5%A4%A7%E8%AF%AD%E8%A8%80%E6%A8%A1%E5%9E%8B%E6%9E%B6%E6%9E%84/:3:1","tags":["大模型"],"title":"大模型大模型基础~02.大语言模型架构","uri":"/posts/%E5%A4%A7%E6%A8%A1%E5%9E%8B%E5%9F%BA%E7%A1%80~02.%E5%A4%A7%E8%AF%AD%E8%A8%80%E6%A8%A1%E5%9E%8B%E6%9E%B6%E6%9E%84/#预训练方式"},{"categories":null,"collections":["大模型基础"],"content":"3.1 BERT 语言模型\rBERT（Bidirectional Encoder Representations from Transformers）是一种基于 Encoder-only 架构的预训练语言模型，由Google AI 团队于2018 年10 月提出。作为早期Encoder-only 架构的代表，BERT 在自然语言处理领域带来了突破性的改进。 其核心创新在于通过双向编码模型深入挖掘文本的上下文信息，从而为各种下游任务提供优秀的上下文嵌入。 模型结构\rBERT 模型的结构与 Transformer 中的编码器几乎一致，都是由多个编码模块堆叠而成，每个编码模块包含一个多头自注意力模块和一个全连接前馈模块。 根据参数量的不同，BERT 模型共有 BERT-Base 和 BERT-Large 两个版本。 BERT-Base 由 12 个编码模块堆叠而成，隐藏层维度为 768，自注意力头的数量为 12，总参数数量为 1.1 亿； BERT-Large 由 24 个编码模块堆叠而成，隐藏层维度为 1024，自注意力头的数量为 16，总参数数量约为 3.4 亿。 预训练方式\r数据集\rBERT 使用小说数据集 BookCorpus（包含约8 亿个Token）和英语维基百科数据集（包含约25 亿个Token）进行预训练，总计约33 亿个Token，总数据量达到了15GB 左右。 预训练任务\r在预训练任务上，BERT 开创性地提出了掩码语言建模（Masked Language Model, MLM）和下文预测（Next Sentence Prediction, NSP）两种任务来学习生成上下文嵌入。 具体步骤如下： 基于给定的原始文本构造多个样本序列，每个样本序列由原始文本中的两个句子组成，这两个句子有 50% 的概率是来自原文的连续句子，另外 50% 的概率是随机挑选的两个句子。 对构造出来的样本序列进行分词，并在序列的开头添加特殊标签 [CLS]，在每个句子的结尾添加特殊标签 [SEP]。其中 [CLS] 标签用于聚合整个序列的信息，而 [SEP] 标签则明确句子之间的界限。 利用处理后的序列进行下文预测任务，利用模型判断样本序列中的两个句子是否为连续的。 随机选择样本序列中大约15% 的 Token 进行遮掩，将其替换为特殊标签 [MASK] 或者随机单词。模型需要预测这些被替换的Token 的原始内容。 通过这两种预训练任务的结合，使 BERT 在理解语言的深度和广度上都有显著提升。BERT 不仅能够捕捉到 Token 的细粒度特征（掩码语言建模），还能够把握长距离的依赖关系和句子间的复杂联系（下文预测），为各种下游任务提供了坚实的语言理解基础。 下游任务\r由于 BERT 的输出是输入中所有Token 的向量表示，因此总长度不固定，无法直接应用于各类下游任务。为了解决这一问题，BERT 设计了 [CLS] 标签（Classification Token）来提取整个输入序列的聚合表示。 [CLS] 标签汇总整个输入序列的信息，生成一个固定长度的向量表示，从而实现对所有Token 序列信息的概括，便于处理各种下游任务。 在文本分类任务中，可以将输出中 [CLS] 标签对应的向量提取出来，传递给一个全连接层，从而用于分类，例如判断整个句子的情绪是积极、消极或是中立的。 在问答系统任务中，需要输入问题以及一段相关的文本，即“[CLS] 问题[SEP] 文本[SEP]”。最终同样提取出 [CLS] 标签的对应向量，并传递给两个全连接层，用于判断答案是否存在于相关文本中。如果存在，这两个全连接层分别用于输出答案的起始和结束位置。 在语义相似度任务中，需要计算两段或者多段文本之间的语义相似度。在这一任务中，可以通过构造“[CLS] 文本1[SEP] 文本2 [SEP]”的方式，结合一个线性层来直接输出两个文本之间的相似度；也可以不添加额外的组件，直接提取 [CLS] 标签对应的向量，再利用额外的相似度度量方法（例如余弦相似度）来计算多段文本之间的相似度。 ","date":"2025-08-22","objectID":"/posts/%E5%A4%A7%E6%A8%A1%E5%9E%8B%E5%9F%BA%E7%A1%80~02.%E5%A4%A7%E8%AF%AD%E8%A8%80%E6%A8%A1%E5%9E%8B%E6%9E%B6%E6%9E%84/:3:1","tags":["大模型"],"title":"大模型大模型基础~02.大语言模型架构","uri":"/posts/%E5%A4%A7%E6%A8%A1%E5%9E%8B%E5%9F%BA%E7%A1%80~02.%E5%A4%A7%E8%AF%AD%E8%A8%80%E6%A8%A1%E5%9E%8B%E6%9E%B6%E6%9E%84/#数据集"},{"categories":null,"collections":["大模型基础"],"content":"3.1 BERT 语言模型\rBERT（Bidirectional Encoder Representations from Transformers）是一种基于 Encoder-only 架构的预训练语言模型，由Google AI 团队于2018 年10 月提出。作为早期Encoder-only 架构的代表，BERT 在自然语言处理领域带来了突破性的改进。 其核心创新在于通过双向编码模型深入挖掘文本的上下文信息，从而为各种下游任务提供优秀的上下文嵌入。 模型结构\rBERT 模型的结构与 Transformer 中的编码器几乎一致，都是由多个编码模块堆叠而成，每个编码模块包含一个多头自注意力模块和一个全连接前馈模块。 根据参数量的不同，BERT 模型共有 BERT-Base 和 BERT-Large 两个版本。 BERT-Base 由 12 个编码模块堆叠而成，隐藏层维度为 768，自注意力头的数量为 12，总参数数量为 1.1 亿； BERT-Large 由 24 个编码模块堆叠而成，隐藏层维度为 1024，自注意力头的数量为 16，总参数数量约为 3.4 亿。 预训练方式\r数据集\rBERT 使用小说数据集 BookCorpus（包含约8 亿个Token）和英语维基百科数据集（包含约25 亿个Token）进行预训练，总计约33 亿个Token，总数据量达到了15GB 左右。 预训练任务\r在预训练任务上，BERT 开创性地提出了掩码语言建模（Masked Language Model, MLM）和下文预测（Next Sentence Prediction, NSP）两种任务来学习生成上下文嵌入。 具体步骤如下： 基于给定的原始文本构造多个样本序列，每个样本序列由原始文本中的两个句子组成，这两个句子有 50% 的概率是来自原文的连续句子，另外 50% 的概率是随机挑选的两个句子。 对构造出来的样本序列进行分词，并在序列的开头添加特殊标签 [CLS]，在每个句子的结尾添加特殊标签 [SEP]。其中 [CLS] 标签用于聚合整个序列的信息，而 [SEP] 标签则明确句子之间的界限。 利用处理后的序列进行下文预测任务，利用模型判断样本序列中的两个句子是否为连续的。 随机选择样本序列中大约15% 的 Token 进行遮掩，将其替换为特殊标签 [MASK] 或者随机单词。模型需要预测这些被替换的Token 的原始内容。 通过这两种预训练任务的结合，使 BERT 在理解语言的深度和广度上都有显著提升。BERT 不仅能够捕捉到 Token 的细粒度特征（掩码语言建模），还能够把握长距离的依赖关系和句子间的复杂联系（下文预测），为各种下游任务提供了坚实的语言理解基础。 下游任务\r由于 BERT 的输出是输入中所有Token 的向量表示，因此总长度不固定，无法直接应用于各类下游任务。为了解决这一问题，BERT 设计了 [CLS] 标签（Classification Token）来提取整个输入序列的聚合表示。 [CLS] 标签汇总整个输入序列的信息，生成一个固定长度的向量表示，从而实现对所有Token 序列信息的概括，便于处理各种下游任务。 在文本分类任务中，可以将输出中 [CLS] 标签对应的向量提取出来，传递给一个全连接层，从而用于分类，例如判断整个句子的情绪是积极、消极或是中立的。 在问答系统任务中，需要输入问题以及一段相关的文本，即“[CLS] 问题[SEP] 文本[SEP]”。最终同样提取出 [CLS] 标签的对应向量，并传递给两个全连接层，用于判断答案是否存在于相关文本中。如果存在，这两个全连接层分别用于输出答案的起始和结束位置。 在语义相似度任务中，需要计算两段或者多段文本之间的语义相似度。在这一任务中，可以通过构造“[CLS] 文本1[SEP] 文本2 [SEP]”的方式，结合一个线性层来直接输出两个文本之间的相似度；也可以不添加额外的组件，直接提取 [CLS] 标签对应的向量，再利用额外的相似度度量方法（例如余弦相似度）来计算多段文本之间的相似度。 ","date":"2025-08-22","objectID":"/posts/%E5%A4%A7%E6%A8%A1%E5%9E%8B%E5%9F%BA%E7%A1%80~02.%E5%A4%A7%E8%AF%AD%E8%A8%80%E6%A8%A1%E5%9E%8B%E6%9E%B6%E6%9E%84/:3:1","tags":["大模型"],"title":"大模型大模型基础~02.大语言模型架构","uri":"/posts/%E5%A4%A7%E6%A8%A1%E5%9E%8B%E5%9F%BA%E7%A1%80~02.%E5%A4%A7%E8%AF%AD%E8%A8%80%E6%A8%A1%E5%9E%8B%E6%9E%B6%E6%9E%84/#预训练任务"},{"categories":null,"collections":["大模型基础"],"content":"3.1 BERT 语言模型\rBERT（Bidirectional Encoder Representations from Transformers）是一种基于 Encoder-only 架构的预训练语言模型，由Google AI 团队于2018 年10 月提出。作为早期Encoder-only 架构的代表，BERT 在自然语言处理领域带来了突破性的改进。 其核心创新在于通过双向编码模型深入挖掘文本的上下文信息，从而为各种下游任务提供优秀的上下文嵌入。 模型结构\rBERT 模型的结构与 Transformer 中的编码器几乎一致，都是由多个编码模块堆叠而成，每个编码模块包含一个多头自注意力模块和一个全连接前馈模块。 根据参数量的不同，BERT 模型共有 BERT-Base 和 BERT-Large 两个版本。 BERT-Base 由 12 个编码模块堆叠而成，隐藏层维度为 768，自注意力头的数量为 12，总参数数量为 1.1 亿； BERT-Large 由 24 个编码模块堆叠而成，隐藏层维度为 1024，自注意力头的数量为 16，总参数数量约为 3.4 亿。 预训练方式\r数据集\rBERT 使用小说数据集 BookCorpus（包含约8 亿个Token）和英语维基百科数据集（包含约25 亿个Token）进行预训练，总计约33 亿个Token，总数据量达到了15GB 左右。 预训练任务\r在预训练任务上，BERT 开创性地提出了掩码语言建模（Masked Language Model, MLM）和下文预测（Next Sentence Prediction, NSP）两种任务来学习生成上下文嵌入。 具体步骤如下： 基于给定的原始文本构造多个样本序列，每个样本序列由原始文本中的两个句子组成，这两个句子有 50% 的概率是来自原文的连续句子，另外 50% 的概率是随机挑选的两个句子。 对构造出来的样本序列进行分词，并在序列的开头添加特殊标签 [CLS]，在每个句子的结尾添加特殊标签 [SEP]。其中 [CLS] 标签用于聚合整个序列的信息，而 [SEP] 标签则明确句子之间的界限。 利用处理后的序列进行下文预测任务，利用模型判断样本序列中的两个句子是否为连续的。 随机选择样本序列中大约15% 的 Token 进行遮掩，将其替换为特殊标签 [MASK] 或者随机单词。模型需要预测这些被替换的Token 的原始内容。 通过这两种预训练任务的结合，使 BERT 在理解语言的深度和广度上都有显著提升。BERT 不仅能够捕捉到 Token 的细粒度特征（掩码语言建模），还能够把握长距离的依赖关系和句子间的复杂联系（下文预测），为各种下游任务提供了坚实的语言理解基础。 下游任务\r由于 BERT 的输出是输入中所有Token 的向量表示，因此总长度不固定，无法直接应用于各类下游任务。为了解决这一问题，BERT 设计了 [CLS] 标签（Classification Token）来提取整个输入序列的聚合表示。 [CLS] 标签汇总整个输入序列的信息，生成一个固定长度的向量表示，从而实现对所有Token 序列信息的概括，便于处理各种下游任务。 在文本分类任务中，可以将输出中 [CLS] 标签对应的向量提取出来，传递给一个全连接层，从而用于分类，例如判断整个句子的情绪是积极、消极或是中立的。 在问答系统任务中，需要输入问题以及一段相关的文本，即“[CLS] 问题[SEP] 文本[SEP]”。最终同样提取出 [CLS] 标签的对应向量，并传递给两个全连接层，用于判断答案是否存在于相关文本中。如果存在，这两个全连接层分别用于输出答案的起始和结束位置。 在语义相似度任务中，需要计算两段或者多段文本之间的语义相似度。在这一任务中，可以通过构造“[CLS] 文本1[SEP] 文本2 [SEP]”的方式，结合一个线性层来直接输出两个文本之间的相似度；也可以不添加额外的组件，直接提取 [CLS] 标签对应的向量，再利用额外的相似度度量方法（例如余弦相似度）来计算多段文本之间的相似度。 ","date":"2025-08-22","objectID":"/posts/%E5%A4%A7%E6%A8%A1%E5%9E%8B%E5%9F%BA%E7%A1%80~02.%E5%A4%A7%E8%AF%AD%E8%A8%80%E6%A8%A1%E5%9E%8B%E6%9E%B6%E6%9E%84/:3:1","tags":["大模型"],"title":"大模型大模型基础~02.大语言模型架构","uri":"/posts/%E5%A4%A7%E6%A8%A1%E5%9E%8B%E5%9F%BA%E7%A1%80~02.%E5%A4%A7%E8%AF%AD%E8%A8%80%E6%A8%A1%E5%9E%8B%E6%9E%B6%E6%9E%84/#下游任务"},{"categories":null,"collections":["大模型基础"],"content":"3.2 RoBERTa 语言模型\rRoBERTa（Robustly Optimized BERT Pretraining Approach）由Facebook AI（现更名Meta）研究院于2019 年7 月提出，旨在解决BERT 在训练不充分这一问题，以提升预训练语言模型的性能。 RoBERTa 在 BERT 的基础上采用了更大的数据集（包括更多的英文书籍、维基百科和其他网页数据）、更长的训练时间（包括更大的批次大小和更多的训练步数）以及更细致的超参数调整（包括学习率、训练步数等的设置）来优化预训练的过程，从而提高模型在各种自然语言处理任务上的性能和鲁棒性。 预训练方式\r数据集\rRoBERTa 在BERT 原有的小说数据集 BookCorpus（包含约 8 亿个Token）以及英语维基百科数据集（包含约 25 亿个Token）的基础上，添加了新闻数据集 CC-News （包含约 76 GB 的新闻文章）、网页开放数据集 OpenWebText（包含约38GB 的网页文本内容）以及故事数据集 Stories（包含约31GB 的故事文本），总数据量达到约 160 GB。 预训练任务\rRoBERTa 移除了BERT 中的下文预测任务，并将BERT 原生的静态掩码语言建模任务更改为动态掩码语言建模。 BERT 在数据预处理期间对句子进行掩码，随后在每个训练 epoch 中，掩码位置不再变化。 RoBERTa 则将训练数据复制成10 个副本，分别进行掩码。 示例\r训练 40 个 epoch： BERT 在其静态掩码后的文本上训练了40 次 RoBERTa 将 10 个不同掩码后的副本分别训练了4 次，从而增加模型训练的多样性，有助于模型学习到更丰富的上下文信息。 ","date":"2025-08-22","objectID":"/posts/%E5%A4%A7%E6%A8%A1%E5%9E%8B%E5%9F%BA%E7%A1%80~02.%E5%A4%A7%E8%AF%AD%E8%A8%80%E6%A8%A1%E5%9E%8B%E6%9E%B6%E6%9E%84/:3:2","tags":["大模型"],"title":"大模型大模型基础~02.大语言模型架构","uri":"/posts/%E5%A4%A7%E6%A8%A1%E5%9E%8B%E5%9F%BA%E7%A1%80~02.%E5%A4%A7%E8%AF%AD%E8%A8%80%E6%A8%A1%E5%9E%8B%E6%9E%B6%E6%9E%84/#roberta-语言模型"},{"categories":null,"collections":["大模型基础"],"content":"3.2 RoBERTa 语言模型\rRoBERTa（Robustly Optimized BERT Pretraining Approach）由Facebook AI（现更名Meta）研究院于2019 年7 月提出，旨在解决BERT 在训练不充分这一问题，以提升预训练语言模型的性能。 RoBERTa 在 BERT 的基础上采用了更大的数据集（包括更多的英文书籍、维基百科和其他网页数据）、更长的训练时间（包括更大的批次大小和更多的训练步数）以及更细致的超参数调整（包括学习率、训练步数等的设置）来优化预训练的过程，从而提高模型在各种自然语言处理任务上的性能和鲁棒性。 预训练方式\r数据集\rRoBERTa 在BERT 原有的小说数据集 BookCorpus（包含约 8 亿个Token）以及英语维基百科数据集（包含约 25 亿个Token）的基础上，添加了新闻数据集 CC-News （包含约 76 GB 的新闻文章）、网页开放数据集 OpenWebText（包含约38GB 的网页文本内容）以及故事数据集 Stories（包含约31GB 的故事文本），总数据量达到约 160 GB。 预训练任务\rRoBERTa 移除了BERT 中的下文预测任务，并将BERT 原生的静态掩码语言建模任务更改为动态掩码语言建模。 BERT 在数据预处理期间对句子进行掩码，随后在每个训练 epoch 中，掩码位置不再变化。 RoBERTa 则将训练数据复制成10 个副本，分别进行掩码。 示例\r训练 40 个 epoch： BERT 在其静态掩码后的文本上训练了40 次 RoBERTa 将 10 个不同掩码后的副本分别训练了4 次，从而增加模型训练的多样性，有助于模型学习到更丰富的上下文信息。 ","date":"2025-08-22","objectID":"/posts/%E5%A4%A7%E6%A8%A1%E5%9E%8B%E5%9F%BA%E7%A1%80~02.%E5%A4%A7%E8%AF%AD%E8%A8%80%E6%A8%A1%E5%9E%8B%E6%9E%B6%E6%9E%84/:3:2","tags":["大模型"],"title":"大模型大模型基础~02.大语言模型架构","uri":"/posts/%E5%A4%A7%E6%A8%A1%E5%9E%8B%E5%9F%BA%E7%A1%80~02.%E5%A4%A7%E8%AF%AD%E8%A8%80%E6%A8%A1%E5%9E%8B%E6%9E%B6%E6%9E%84/#预训练方式-1"},{"categories":null,"collections":["大模型基础"],"content":"3.2 RoBERTa 语言模型\rRoBERTa（Robustly Optimized BERT Pretraining Approach）由Facebook AI（现更名Meta）研究院于2019 年7 月提出，旨在解决BERT 在训练不充分这一问题，以提升预训练语言模型的性能。 RoBERTa 在 BERT 的基础上采用了更大的数据集（包括更多的英文书籍、维基百科和其他网页数据）、更长的训练时间（包括更大的批次大小和更多的训练步数）以及更细致的超参数调整（包括学习率、训练步数等的设置）来优化预训练的过程，从而提高模型在各种自然语言处理任务上的性能和鲁棒性。 预训练方式\r数据集\rRoBERTa 在BERT 原有的小说数据集 BookCorpus（包含约 8 亿个Token）以及英语维基百科数据集（包含约 25 亿个Token）的基础上，添加了新闻数据集 CC-News （包含约 76 GB 的新闻文章）、网页开放数据集 OpenWebText（包含约38GB 的网页文本内容）以及故事数据集 Stories（包含约31GB 的故事文本），总数据量达到约 160 GB。 预训练任务\rRoBERTa 移除了BERT 中的下文预测任务，并将BERT 原生的静态掩码语言建模任务更改为动态掩码语言建模。 BERT 在数据预处理期间对句子进行掩码，随后在每个训练 epoch 中，掩码位置不再变化。 RoBERTa 则将训练数据复制成10 个副本，分别进行掩码。 示例\r训练 40 个 epoch： BERT 在其静态掩码后的文本上训练了40 次 RoBERTa 将 10 个不同掩码后的副本分别训练了4 次，从而增加模型训练的多样性，有助于模型学习到更丰富的上下文信息。 ","date":"2025-08-22","objectID":"/posts/%E5%A4%A7%E6%A8%A1%E5%9E%8B%E5%9F%BA%E7%A1%80~02.%E5%A4%A7%E8%AF%AD%E8%A8%80%E6%A8%A1%E5%9E%8B%E6%9E%B6%E6%9E%84/:3:2","tags":["大模型"],"title":"大模型大模型基础~02.大语言模型架构","uri":"/posts/%E5%A4%A7%E6%A8%A1%E5%9E%8B%E5%9F%BA%E7%A1%80~02.%E5%A4%A7%E8%AF%AD%E8%A8%80%E6%A8%A1%E5%9E%8B%E6%9E%B6%E6%9E%84/#数据集-1"},{"categories":null,"collections":["大模型基础"],"content":"3.2 RoBERTa 语言模型\rRoBERTa（Robustly Optimized BERT Pretraining Approach）由Facebook AI（现更名Meta）研究院于2019 年7 月提出，旨在解决BERT 在训练不充分这一问题，以提升预训练语言模型的性能。 RoBERTa 在 BERT 的基础上采用了更大的数据集（包括更多的英文书籍、维基百科和其他网页数据）、更长的训练时间（包括更大的批次大小和更多的训练步数）以及更细致的超参数调整（包括学习率、训练步数等的设置）来优化预训练的过程，从而提高模型在各种自然语言处理任务上的性能和鲁棒性。 预训练方式\r数据集\rRoBERTa 在BERT 原有的小说数据集 BookCorpus（包含约 8 亿个Token）以及英语维基百科数据集（包含约 25 亿个Token）的基础上，添加了新闻数据集 CC-News （包含约 76 GB 的新闻文章）、网页开放数据集 OpenWebText（包含约38GB 的网页文本内容）以及故事数据集 Stories（包含约31GB 的故事文本），总数据量达到约 160 GB。 预训练任务\rRoBERTa 移除了BERT 中的下文预测任务，并将BERT 原生的静态掩码语言建模任务更改为动态掩码语言建模。 BERT 在数据预处理期间对句子进行掩码，随后在每个训练 epoch 中，掩码位置不再变化。 RoBERTa 则将训练数据复制成10 个副本，分别进行掩码。 示例\r训练 40 个 epoch： BERT 在其静态掩码后的文本上训练了40 次 RoBERTa 将 10 个不同掩码后的副本分别训练了4 次，从而增加模型训练的多样性，有助于模型学习到更丰富的上下文信息。 ","date":"2025-08-22","objectID":"/posts/%E5%A4%A7%E6%A8%A1%E5%9E%8B%E5%9F%BA%E7%A1%80~02.%E5%A4%A7%E8%AF%AD%E8%A8%80%E6%A8%A1%E5%9E%8B%E6%9E%B6%E6%9E%84/:3:2","tags":["大模型"],"title":"大模型大模型基础~02.大语言模型架构","uri":"/posts/%E5%A4%A7%E6%A8%A1%E5%9E%8B%E5%9F%BA%E7%A1%80~02.%E5%A4%A7%E8%AF%AD%E8%A8%80%E6%A8%A1%E5%9E%8B%E6%9E%B6%E6%9E%84/#预训练任务-1"},{"categories":null,"collections":["大模型基础"],"content":"3.3 ALBERT 语言模型\rALBERT（A Lite BERT）是由 Google Research 团队于2019 年9 月提出的轻量级 BERT 模型，通过参数因子分解技术和跨层参数共享技术显著减少了参数的数量，从而提高训练和推理效率。 模型结构\r参数因子分解\r在BERT 中，Embedding 层的输出向量维度 $E$ 与隐藏层的向量维 $H$ 是一致的，这意味着Embedding 层的输出直接用作后续编码模块的输入。ALBERT 将 Embedding 层的矩阵先进行分解，将词表对应的独热编码向量通过一个低维的投影层下投影至维度 $E$，再将其上投影回隐藏状态的维度 $H$。 示例\rBERT-Base 模型对应的词表大小$V$ 为 30000 左右，并且其隐藏层的向量维度 $H$ 设置为 768。因此，BERT 的 Embedding 层需要的参数数量是 $V \\times H$ ，大约为 2304 万。 ALBERT 选择了一个较小的 Embedding 层维度，例如 128，并将参数数量拆解为 $V \\times E + E \\times H$ 。按照这个设计，ALBERT 的 Embedding 层大约需要 394 万个参数，大约是 BERT 参数数量的六分之一。 跨层参数共享\r以经典的 BERT-Base 模型为例，模型中共有12 层相同架构的编码模块，所有 Transformer 块的参数都是独立训练的。 ALBERT 为了降低模型的参数量，提出了跨层参数共享机制，只学习第一层编码模块的参数，并将其直接共享给其他所有层。该机制在一定程度上牺牲了模型性能，但显著提升了参数存储空间的压缩比，从而实现了更高效的资源利用。 预训练方式\r预训练任务\r在预训练任务的选择上，ALBERT 保留了BERT 中的掩码语言建模任务，并将下文预测任务替换为句序预测（Sentence Order Prediction, SOP）。 ALBERT 从文本中选择连续的两个句子，将这两个句子直接拼接起来，或是先将这两个句子的顺序翻转后再进行拼接，并将拼接后的内容作为输入样本，而模型需要预测该样本中的两个句子是正序还是反序。 ","date":"2025-08-22","objectID":"/posts/%E5%A4%A7%E6%A8%A1%E5%9E%8B%E5%9F%BA%E7%A1%80~02.%E5%A4%A7%E8%AF%AD%E8%A8%80%E6%A8%A1%E5%9E%8B%E6%9E%B6%E6%9E%84/:3:3","tags":["大模型"],"title":"大模型大模型基础~02.大语言模型架构","uri":"/posts/%E5%A4%A7%E6%A8%A1%E5%9E%8B%E5%9F%BA%E7%A1%80~02.%E5%A4%A7%E8%AF%AD%E8%A8%80%E6%A8%A1%E5%9E%8B%E6%9E%B6%E6%9E%84/#albert-语言模型"},{"categories":null,"collections":["大模型基础"],"content":"3.3 ALBERT 语言模型\rALBERT（A Lite BERT）是由 Google Research 团队于2019 年9 月提出的轻量级 BERT 模型，通过参数因子分解技术和跨层参数共享技术显著减少了参数的数量，从而提高训练和推理效率。 模型结构\r参数因子分解\r在BERT 中，Embedding 层的输出向量维度 $E$ 与隐藏层的向量维 $H$ 是一致的，这意味着Embedding 层的输出直接用作后续编码模块的输入。ALBERT 将 Embedding 层的矩阵先进行分解，将词表对应的独热编码向量通过一个低维的投影层下投影至维度 $E$，再将其上投影回隐藏状态的维度 $H$。 示例\rBERT-Base 模型对应的词表大小$V$ 为 30000 左右，并且其隐藏层的向量维度 $H$ 设置为 768。因此，BERT 的 Embedding 层需要的参数数量是 $V \\times H$ ，大约为 2304 万。 ALBERT 选择了一个较小的 Embedding 层维度，例如 128，并将参数数量拆解为 $V \\times E + E \\times H$ 。按照这个设计，ALBERT 的 Embedding 层大约需要 394 万个参数，大约是 BERT 参数数量的六分之一。 跨层参数共享\r以经典的 BERT-Base 模型为例，模型中共有12 层相同架构的编码模块，所有 Transformer 块的参数都是独立训练的。 ALBERT 为了降低模型的参数量，提出了跨层参数共享机制，只学习第一层编码模块的参数，并将其直接共享给其他所有层。该机制在一定程度上牺牲了模型性能，但显著提升了参数存储空间的压缩比，从而实现了更高效的资源利用。 预训练方式\r预训练任务\r在预训练任务的选择上，ALBERT 保留了BERT 中的掩码语言建模任务，并将下文预测任务替换为句序预测（Sentence Order Prediction, SOP）。 ALBERT 从文本中选择连续的两个句子，将这两个句子直接拼接起来，或是先将这两个句子的顺序翻转后再进行拼接，并将拼接后的内容作为输入样本，而模型需要预测该样本中的两个句子是正序还是反序。 ","date":"2025-08-22","objectID":"/posts/%E5%A4%A7%E6%A8%A1%E5%9E%8B%E5%9F%BA%E7%A1%80~02.%E5%A4%A7%E8%AF%AD%E8%A8%80%E6%A8%A1%E5%9E%8B%E6%9E%B6%E6%9E%84/:3:3","tags":["大模型"],"title":"大模型大模型基础~02.大语言模型架构","uri":"/posts/%E5%A4%A7%E6%A8%A1%E5%9E%8B%E5%9F%BA%E7%A1%80~02.%E5%A4%A7%E8%AF%AD%E8%A8%80%E6%A8%A1%E5%9E%8B%E6%9E%B6%E6%9E%84/#模型结构-1"},{"categories":null,"collections":["大模型基础"],"content":"3.3 ALBERT 语言模型\rALBERT（A Lite BERT）是由 Google Research 团队于2019 年9 月提出的轻量级 BERT 模型，通过参数因子分解技术和跨层参数共享技术显著减少了参数的数量，从而提高训练和推理效率。 模型结构\r参数因子分解\r在BERT 中，Embedding 层的输出向量维度 $E$ 与隐藏层的向量维 $H$ 是一致的，这意味着Embedding 层的输出直接用作后续编码模块的输入。ALBERT 将 Embedding 层的矩阵先进行分解，将词表对应的独热编码向量通过一个低维的投影层下投影至维度 $E$，再将其上投影回隐藏状态的维度 $H$。 示例\rBERT-Base 模型对应的词表大小$V$ 为 30000 左右，并且其隐藏层的向量维度 $H$ 设置为 768。因此，BERT 的 Embedding 层需要的参数数量是 $V \\times H$ ，大约为 2304 万。 ALBERT 选择了一个较小的 Embedding 层维度，例如 128，并将参数数量拆解为 $V \\times E + E \\times H$ 。按照这个设计，ALBERT 的 Embedding 层大约需要 394 万个参数，大约是 BERT 参数数量的六分之一。 跨层参数共享\r以经典的 BERT-Base 模型为例，模型中共有12 层相同架构的编码模块，所有 Transformer 块的参数都是独立训练的。 ALBERT 为了降低模型的参数量，提出了跨层参数共享机制，只学习第一层编码模块的参数，并将其直接共享给其他所有层。该机制在一定程度上牺牲了模型性能，但显著提升了参数存储空间的压缩比，从而实现了更高效的资源利用。 预训练方式\r预训练任务\r在预训练任务的选择上，ALBERT 保留了BERT 中的掩码语言建模任务，并将下文预测任务替换为句序预测（Sentence Order Prediction, SOP）。 ALBERT 从文本中选择连续的两个句子，将这两个句子直接拼接起来，或是先将这两个句子的顺序翻转后再进行拼接，并将拼接后的内容作为输入样本，而模型需要预测该样本中的两个句子是正序还是反序。 ","date":"2025-08-22","objectID":"/posts/%E5%A4%A7%E6%A8%A1%E5%9E%8B%E5%9F%BA%E7%A1%80~02.%E5%A4%A7%E8%AF%AD%E8%A8%80%E6%A8%A1%E5%9E%8B%E6%9E%B6%E6%9E%84/:3:3","tags":["大模型"],"title":"大模型大模型基础~02.大语言模型架构","uri":"/posts/%E5%A4%A7%E6%A8%A1%E5%9E%8B%E5%9F%BA%E7%A1%80~02.%E5%A4%A7%E8%AF%AD%E8%A8%80%E6%A8%A1%E5%9E%8B%E6%9E%B6%E6%9E%84/#参数因子分解"},{"categories":null,"collections":["大模型基础"],"content":"3.3 ALBERT 语言模型\rALBERT（A Lite BERT）是由 Google Research 团队于2019 年9 月提出的轻量级 BERT 模型，通过参数因子分解技术和跨层参数共享技术显著减少了参数的数量，从而提高训练和推理效率。 模型结构\r参数因子分解\r在BERT 中，Embedding 层的输出向量维度 $E$ 与隐藏层的向量维 $H$ 是一致的，这意味着Embedding 层的输出直接用作后续编码模块的输入。ALBERT 将 Embedding 层的矩阵先进行分解，将词表对应的独热编码向量通过一个低维的投影层下投影至维度 $E$，再将其上投影回隐藏状态的维度 $H$。 示例\rBERT-Base 模型对应的词表大小$V$ 为 30000 左右，并且其隐藏层的向量维度 $H$ 设置为 768。因此，BERT 的 Embedding 层需要的参数数量是 $V \\times H$ ，大约为 2304 万。 ALBERT 选择了一个较小的 Embedding 层维度，例如 128，并将参数数量拆解为 $V \\times E + E \\times H$ 。按照这个设计，ALBERT 的 Embedding 层大约需要 394 万个参数，大约是 BERT 参数数量的六分之一。 跨层参数共享\r以经典的 BERT-Base 模型为例，模型中共有12 层相同架构的编码模块，所有 Transformer 块的参数都是独立训练的。 ALBERT 为了降低模型的参数量，提出了跨层参数共享机制，只学习第一层编码模块的参数，并将其直接共享给其他所有层。该机制在一定程度上牺牲了模型性能，但显著提升了参数存储空间的压缩比，从而实现了更高效的资源利用。 预训练方式\r预训练任务\r在预训练任务的选择上，ALBERT 保留了BERT 中的掩码语言建模任务，并将下文预测任务替换为句序预测（Sentence Order Prediction, SOP）。 ALBERT 从文本中选择连续的两个句子，将这两个句子直接拼接起来，或是先将这两个句子的顺序翻转后再进行拼接，并将拼接后的内容作为输入样本，而模型需要预测该样本中的两个句子是正序还是反序。 ","date":"2025-08-22","objectID":"/posts/%E5%A4%A7%E6%A8%A1%E5%9E%8B%E5%9F%BA%E7%A1%80~02.%E5%A4%A7%E8%AF%AD%E8%A8%80%E6%A8%A1%E5%9E%8B%E6%9E%B6%E6%9E%84/:3:3","tags":["大模型"],"title":"大模型大模型基础~02.大语言模型架构","uri":"/posts/%E5%A4%A7%E6%A8%A1%E5%9E%8B%E5%9F%BA%E7%A1%80~02.%E5%A4%A7%E8%AF%AD%E8%A8%80%E6%A8%A1%E5%9E%8B%E6%9E%B6%E6%9E%84/#跨层参数共享"},{"categories":null,"collections":["大模型基础"],"content":"3.3 ALBERT 语言模型\rALBERT（A Lite BERT）是由 Google Research 团队于2019 年9 月提出的轻量级 BERT 模型，通过参数因子分解技术和跨层参数共享技术显著减少了参数的数量，从而提高训练和推理效率。 模型结构\r参数因子分解\r在BERT 中，Embedding 层的输出向量维度 $E$ 与隐藏层的向量维 $H$ 是一致的，这意味着Embedding 层的输出直接用作后续编码模块的输入。ALBERT 将 Embedding 层的矩阵先进行分解，将词表对应的独热编码向量通过一个低维的投影层下投影至维度 $E$，再将其上投影回隐藏状态的维度 $H$。 示例\rBERT-Base 模型对应的词表大小$V$ 为 30000 左右，并且其隐藏层的向量维度 $H$ 设置为 768。因此，BERT 的 Embedding 层需要的参数数量是 $V \\times H$ ，大约为 2304 万。 ALBERT 选择了一个较小的 Embedding 层维度，例如 128，并将参数数量拆解为 $V \\times E + E \\times H$ 。按照这个设计，ALBERT 的 Embedding 层大约需要 394 万个参数，大约是 BERT 参数数量的六分之一。 跨层参数共享\r以经典的 BERT-Base 模型为例，模型中共有12 层相同架构的编码模块，所有 Transformer 块的参数都是独立训练的。 ALBERT 为了降低模型的参数量，提出了跨层参数共享机制，只学习第一层编码模块的参数，并将其直接共享给其他所有层。该机制在一定程度上牺牲了模型性能，但显著提升了参数存储空间的压缩比，从而实现了更高效的资源利用。 预训练方式\r预训练任务\r在预训练任务的选择上，ALBERT 保留了BERT 中的掩码语言建模任务，并将下文预测任务替换为句序预测（Sentence Order Prediction, SOP）。 ALBERT 从文本中选择连续的两个句子，将这两个句子直接拼接起来，或是先将这两个句子的顺序翻转后再进行拼接，并将拼接后的内容作为输入样本，而模型需要预测该样本中的两个句子是正序还是反序。 ","date":"2025-08-22","objectID":"/posts/%E5%A4%A7%E6%A8%A1%E5%9E%8B%E5%9F%BA%E7%A1%80~02.%E5%A4%A7%E8%AF%AD%E8%A8%80%E6%A8%A1%E5%9E%8B%E6%9E%B6%E6%9E%84/:3:3","tags":["大模型"],"title":"大模型大模型基础~02.大语言模型架构","uri":"/posts/%E5%A4%A7%E6%A8%A1%E5%9E%8B%E5%9F%BA%E7%A1%80~02.%E5%A4%A7%E8%AF%AD%E8%A8%80%E6%A8%A1%E5%9E%8B%E6%9E%B6%E6%9E%84/#预训练方式-2"},{"categories":null,"collections":["大模型基础"],"content":"3.3 ALBERT 语言模型\rALBERT（A Lite BERT）是由 Google Research 团队于2019 年9 月提出的轻量级 BERT 模型，通过参数因子分解技术和跨层参数共享技术显著减少了参数的数量，从而提高训练和推理效率。 模型结构\r参数因子分解\r在BERT 中，Embedding 层的输出向量维度 $E$ 与隐藏层的向量维 $H$ 是一致的，这意味着Embedding 层的输出直接用作后续编码模块的输入。ALBERT 将 Embedding 层的矩阵先进行分解，将词表对应的独热编码向量通过一个低维的投影层下投影至维度 $E$，再将其上投影回隐藏状态的维度 $H$。 示例\rBERT-Base 模型对应的词表大小$V$ 为 30000 左右，并且其隐藏层的向量维度 $H$ 设置为 768。因此，BERT 的 Embedding 层需要的参数数量是 $V \\times H$ ，大约为 2304 万。 ALBERT 选择了一个较小的 Embedding 层维度，例如 128，并将参数数量拆解为 $V \\times E + E \\times H$ 。按照这个设计，ALBERT 的 Embedding 层大约需要 394 万个参数，大约是 BERT 参数数量的六分之一。 跨层参数共享\r以经典的 BERT-Base 模型为例，模型中共有12 层相同架构的编码模块，所有 Transformer 块的参数都是独立训练的。 ALBERT 为了降低模型的参数量，提出了跨层参数共享机制，只学习第一层编码模块的参数，并将其直接共享给其他所有层。该机制在一定程度上牺牲了模型性能，但显著提升了参数存储空间的压缩比，从而实现了更高效的资源利用。 预训练方式\r预训练任务\r在预训练任务的选择上，ALBERT 保留了BERT 中的掩码语言建模任务，并将下文预测任务替换为句序预测（Sentence Order Prediction, SOP）。 ALBERT 从文本中选择连续的两个句子，将这两个句子直接拼接起来，或是先将这两个句子的顺序翻转后再进行拼接，并将拼接后的内容作为输入样本，而模型需要预测该样本中的两个句子是正序还是反序。 ","date":"2025-08-22","objectID":"/posts/%E5%A4%A7%E6%A8%A1%E5%9E%8B%E5%9F%BA%E7%A1%80~02.%E5%A4%A7%E8%AF%AD%E8%A8%80%E6%A8%A1%E5%9E%8B%E6%9E%B6%E6%9E%84/:3:3","tags":["大模型"],"title":"大模型大模型基础~02.大语言模型架构","uri":"/posts/%E5%A4%A7%E6%A8%A1%E5%9E%8B%E5%9F%BA%E7%A1%80~02.%E5%A4%A7%E8%AF%AD%E8%A8%80%E6%A8%A1%E5%9E%8B%E6%9E%B6%E6%9E%84/#预训练任务-2"},{"categories":null,"collections":["大模型基础"],"content":"3.4 ELECTRA 语言模型\rELECTRA（Efficiently Learning an Encoder that Classifies Token Replacements Accurately）是由Google Brain 和斯坦福大学的研究人员于2020 年3 月提出的另一种 BERT 变体，旨在解决大规模预训练语言模型中的效率和可扩展性问题。通过使用生成器-判别器架构，ELECTRA 能够更高效地利用预训练数据，提高了模型在下游任务中的表现。 预训练方式\r结合了生成对抗网络（Generative Adversarial Network, GAN）的思想，模型包含一个生成器和一个判别器： 生成器（Generator）是一个能进行掩码预测的模型（例如 BERT 模型），负责将掩码后的文本恢复原状。 判别器（Discriminator）执行替换词检测（Replaced Token Detection, RTD）任务，负责检测生成器输出的内容中的每个Token 是否是原文中的内容。 信息\r在BERT 中，只有 15% 的固定比例 Token 被掩码，模型训练的内容仅限于这15% 的Token。但是在 ELECTRA 中，判别器会判断生成器输出的所有 Token 是否被替换过，因此能够更好地学习文本的上下文嵌入。 ","date":"2025-08-22","objectID":"/posts/%E5%A4%A7%E6%A8%A1%E5%9E%8B%E5%9F%BA%E7%A1%80~02.%E5%A4%A7%E8%AF%AD%E8%A8%80%E6%A8%A1%E5%9E%8B%E6%9E%B6%E6%9E%84/:3:4","tags":["大模型"],"title":"大模型大模型基础~02.大语言模型架构","uri":"/posts/%E5%A4%A7%E6%A8%A1%E5%9E%8B%E5%9F%BA%E7%A1%80~02.%E5%A4%A7%E8%AF%AD%E8%A8%80%E6%A8%A1%E5%9E%8B%E6%9E%B6%E6%9E%84/#electra-语言模型"},{"categories":null,"collections":["大模型基础"],"content":"3.4 ELECTRA 语言模型\rELECTRA（Efficiently Learning an Encoder that Classifies Token Replacements Accurately）是由Google Brain 和斯坦福大学的研究人员于2020 年3 月提出的另一种 BERT 变体，旨在解决大规模预训练语言模型中的效率和可扩展性问题。通过使用生成器-判别器架构，ELECTRA 能够更高效地利用预训练数据，提高了模型在下游任务中的表现。 预训练方式\r结合了生成对抗网络（Generative Adversarial Network, GAN）的思想，模型包含一个生成器和一个判别器： 生成器（Generator）是一个能进行掩码预测的模型（例如 BERT 模型），负责将掩码后的文本恢复原状。 判别器（Discriminator）执行替换词检测（Replaced Token Detection, RTD）任务，负责检测生成器输出的内容中的每个Token 是否是原文中的内容。 信息\r在BERT 中，只有 15% 的固定比例 Token 被掩码，模型训练的内容仅限于这15% 的Token。但是在 ELECTRA 中，判别器会判断生成器输出的所有 Token 是否被替换过，因此能够更好地学习文本的上下文嵌入。 ","date":"2025-08-22","objectID":"/posts/%E5%A4%A7%E6%A8%A1%E5%9E%8B%E5%9F%BA%E7%A1%80~02.%E5%A4%A7%E8%AF%AD%E8%A8%80%E6%A8%A1%E5%9E%8B%E6%9E%B6%E6%9E%84/:3:4","tags":["大模型"],"title":"大模型大模型基础~02.大语言模型架构","uri":"/posts/%E5%A4%A7%E6%A8%A1%E5%9E%8B%E5%9F%BA%E7%A1%80~02.%E5%A4%A7%E8%AF%AD%E8%A8%80%E6%A8%A1%E5%9E%8B%E6%9E%B6%E6%9E%84/#预训练方式-3"},{"categories":null,"collections":["大模型基础"],"content":"第 4 节 Encoder-Decoder 模型\r","date":"2025-08-22","objectID":"/posts/%E5%A4%A7%E6%A8%A1%E5%9E%8B%E5%9F%BA%E7%A1%80~02.%E5%A4%A7%E8%AF%AD%E8%A8%80%E6%A8%A1%E5%9E%8B%E6%9E%B6%E6%9E%84/:4:0","tags":["大模型"],"title":"大模型大模型基础~02.大语言模型架构","uri":"/posts/%E5%A4%A7%E6%A8%A1%E5%9E%8B%E5%9F%BA%E7%A1%80~02.%E5%A4%A7%E8%AF%AD%E8%A8%80%E6%A8%A1%E5%9E%8B%E6%9E%B6%E6%9E%84/#encoder-decoder-模型"},{"categories":null,"collections":["大模型基础"],"content":"4.1 T5 语言模型\rGoogle Research 团队在2019 年10 月提出了一种基于 Encoder-Decoder 架构的大型预训练语言模型T5（Text-to-Text Transfer Transformer），其采用了统一的文本到文本的转换范式来处理多种任务。 模型结构\rT5 模型的核心思想是将多种 NLP 任务统一到一个文本转文本的生成式框架中。 T5 通过不同的输入前缀来指示模型执行不同任务，然后生成相应的任务输出。可以视为早期的提示（Prompt） 技术的运用，通过构造合理的输入前缀，T5 模型能够引导自身针对特定任务进行优化，而无需对模型架构进行根本性的改变。 预训练方式\rT5 提出了名为 Span Corruption 的预训练任务： 从原始输入中选择 15% 的 Token 进行破坏，每次都选择连续三个 Token 作为一个小段（span） 整体被掩码成 [MASK]。 与 BERT 模型中采用的单个 Token 预测不同，T5 模型需要对整个被遮挡的连续文本片段进行预测。这些片段可能包括连续的短语或子句，它们在自然语言中构成了具有完整意义的语义单元。这一设计要求模型不仅等理解局部词汇的表面形式，还要可以捕捉更深层次的句子结构和上下文之间的复杂依赖关系。 下游任务\rT5 模型可以在零样本（Zero-Shot） 的情况下，利用 Prompt 工程技术直接适配到多种下游任务。 同时，T5 模型也可以通过微调（Fine-Tuning） 来适配到特定的任务。但是，微调过程需要针对下游任务收集带标签训练数据，同时也需要更多的计算资源和训练时间，因此通常只被应用于那些对精度要求极高且任务本身较为复杂的应用场景。 ","date":"2025-08-22","objectID":"/posts/%E5%A4%A7%E6%A8%A1%E5%9E%8B%E5%9F%BA%E7%A1%80~02.%E5%A4%A7%E8%AF%AD%E8%A8%80%E6%A8%A1%E5%9E%8B%E6%9E%B6%E6%9E%84/:4:1","tags":["大模型"],"title":"大模型大模型基础~02.大语言模型架构","uri":"/posts/%E5%A4%A7%E6%A8%A1%E5%9E%8B%E5%9F%BA%E7%A1%80~02.%E5%A4%A7%E8%AF%AD%E8%A8%80%E6%A8%A1%E5%9E%8B%E6%9E%B6%E6%9E%84/#t5-语言模型"},{"categories":null,"collections":["大模型基础"],"content":"4.1 T5 语言模型\rGoogle Research 团队在2019 年10 月提出了一种基于 Encoder-Decoder 架构的大型预训练语言模型T5（Text-to-Text Transfer Transformer），其采用了统一的文本到文本的转换范式来处理多种任务。 模型结构\rT5 模型的核心思想是将多种 NLP 任务统一到一个文本转文本的生成式框架中。 T5 通过不同的输入前缀来指示模型执行不同任务，然后生成相应的任务输出。可以视为早期的提示（Prompt） 技术的运用，通过构造合理的输入前缀，T5 模型能够引导自身针对特定任务进行优化，而无需对模型架构进行根本性的改变。 预训练方式\rT5 提出了名为 Span Corruption 的预训练任务： 从原始输入中选择 15% 的 Token 进行破坏，每次都选择连续三个 Token 作为一个小段（span） 整体被掩码成 [MASK]。 与 BERT 模型中采用的单个 Token 预测不同，T5 模型需要对整个被遮挡的连续文本片段进行预测。这些片段可能包括连续的短语或子句，它们在自然语言中构成了具有完整意义的语义单元。这一设计要求模型不仅等理解局部词汇的表面形式，还要可以捕捉更深层次的句子结构和上下文之间的复杂依赖关系。 下游任务\rT5 模型可以在零样本（Zero-Shot） 的情况下，利用 Prompt 工程技术直接适配到多种下游任务。 同时，T5 模型也可以通过微调（Fine-Tuning） 来适配到特定的任务。但是，微调过程需要针对下游任务收集带标签训练数据，同时也需要更多的计算资源和训练时间，因此通常只被应用于那些对精度要求极高且任务本身较为复杂的应用场景。 ","date":"2025-08-22","objectID":"/posts/%E5%A4%A7%E6%A8%A1%E5%9E%8B%E5%9F%BA%E7%A1%80~02.%E5%A4%A7%E8%AF%AD%E8%A8%80%E6%A8%A1%E5%9E%8B%E6%9E%B6%E6%9E%84/:4:1","tags":["大模型"],"title":"大模型大模型基础~02.大语言模型架构","uri":"/posts/%E5%A4%A7%E6%A8%A1%E5%9E%8B%E5%9F%BA%E7%A1%80~02.%E5%A4%A7%E8%AF%AD%E8%A8%80%E6%A8%A1%E5%9E%8B%E6%9E%B6%E6%9E%84/#模型结构-2"},{"categories":null,"collections":["大模型基础"],"content":"4.1 T5 语言模型\rGoogle Research 团队在2019 年10 月提出了一种基于 Encoder-Decoder 架构的大型预训练语言模型T5（Text-to-Text Transfer Transformer），其采用了统一的文本到文本的转换范式来处理多种任务。 模型结构\rT5 模型的核心思想是将多种 NLP 任务统一到一个文本转文本的生成式框架中。 T5 通过不同的输入前缀来指示模型执行不同任务，然后生成相应的任务输出。可以视为早期的提示（Prompt） 技术的运用，通过构造合理的输入前缀，T5 模型能够引导自身针对特定任务进行优化，而无需对模型架构进行根本性的改变。 预训练方式\rT5 提出了名为 Span Corruption 的预训练任务： 从原始输入中选择 15% 的 Token 进行破坏，每次都选择连续三个 Token 作为一个小段（span） 整体被掩码成 [MASK]。 与 BERT 模型中采用的单个 Token 预测不同，T5 模型需要对整个被遮挡的连续文本片段进行预测。这些片段可能包括连续的短语或子句，它们在自然语言中构成了具有完整意义的语义单元。这一设计要求模型不仅等理解局部词汇的表面形式，还要可以捕捉更深层次的句子结构和上下文之间的复杂依赖关系。 下游任务\rT5 模型可以在零样本（Zero-Shot） 的情况下，利用 Prompt 工程技术直接适配到多种下游任务。 同时，T5 模型也可以通过微调（Fine-Tuning） 来适配到特定的任务。但是，微调过程需要针对下游任务收集带标签训练数据，同时也需要更多的计算资源和训练时间，因此通常只被应用于那些对精度要求极高且任务本身较为复杂的应用场景。 ","date":"2025-08-22","objectID":"/posts/%E5%A4%A7%E6%A8%A1%E5%9E%8B%E5%9F%BA%E7%A1%80~02.%E5%A4%A7%E8%AF%AD%E8%A8%80%E6%A8%A1%E5%9E%8B%E6%9E%B6%E6%9E%84/:4:1","tags":["大模型"],"title":"大模型大模型基础~02.大语言模型架构","uri":"/posts/%E5%A4%A7%E6%A8%A1%E5%9E%8B%E5%9F%BA%E7%A1%80~02.%E5%A4%A7%E8%AF%AD%E8%A8%80%E6%A8%A1%E5%9E%8B%E6%9E%B6%E6%9E%84/#预训练方式-4"},{"categories":null,"collections":["大模型基础"],"content":"4.1 T5 语言模型\rGoogle Research 团队在2019 年10 月提出了一种基于 Encoder-Decoder 架构的大型预训练语言模型T5（Text-to-Text Transfer Transformer），其采用了统一的文本到文本的转换范式来处理多种任务。 模型结构\rT5 模型的核心思想是将多种 NLP 任务统一到一个文本转文本的生成式框架中。 T5 通过不同的输入前缀来指示模型执行不同任务，然后生成相应的任务输出。可以视为早期的提示（Prompt） 技术的运用，通过构造合理的输入前缀，T5 模型能够引导自身针对特定任务进行优化，而无需对模型架构进行根本性的改变。 预训练方式\rT5 提出了名为 Span Corruption 的预训练任务： 从原始输入中选择 15% 的 Token 进行破坏，每次都选择连续三个 Token 作为一个小段（span） 整体被掩码成 [MASK]。 与 BERT 模型中采用的单个 Token 预测不同，T5 模型需要对整个被遮挡的连续文本片段进行预测。这些片段可能包括连续的短语或子句，它们在自然语言中构成了具有完整意义的语义单元。这一设计要求模型不仅等理解局部词汇的表面形式，还要可以捕捉更深层次的句子结构和上下文之间的复杂依赖关系。 下游任务\rT5 模型可以在零样本（Zero-Shot） 的情况下，利用 Prompt 工程技术直接适配到多种下游任务。 同时，T5 模型也可以通过微调（Fine-Tuning） 来适配到特定的任务。但是，微调过程需要针对下游任务收集带标签训练数据，同时也需要更多的计算资源和训练时间，因此通常只被应用于那些对精度要求极高且任务本身较为复杂的应用场景。 ","date":"2025-08-22","objectID":"/posts/%E5%A4%A7%E6%A8%A1%E5%9E%8B%E5%9F%BA%E7%A1%80~02.%E5%A4%A7%E8%AF%AD%E8%A8%80%E6%A8%A1%E5%9E%8B%E6%9E%B6%E6%9E%84/:4:1","tags":["大模型"],"title":"大模型大模型基础~02.大语言模型架构","uri":"/posts/%E5%A4%A7%E6%A8%A1%E5%9E%8B%E5%9F%BA%E7%A1%80~02.%E5%A4%A7%E8%AF%AD%E8%A8%80%E6%A8%A1%E5%9E%8B%E6%9E%B6%E6%9E%84/#下游任务-1"},{"categories":null,"collections":["大模型基础"],"content":"4.2 BART 语言模型\rBART（Bidirectional and Auto-Regressive Transformers）是由 Meta AI 研究院同样于 2019 年10 月提出的一个 Encoder-Decoder 架构模型。不同于 T5 将多种 NLP 任务集成到一个统一的框架，BART 旨在通过多样化的预训练任务来提升模型在文本生成任务和文本理解任务上的表现。 预训练方式\rBART 以重建被破坏的文本为目标，通过多种任务来破坏文本，然后训练模型对原始文本进行恢复： Token 遮挡任务（Token Masking）：类似于 BERT 中的 MLM 任务，在原始文本中随机采样一部分Token 并将其替换为 [MASK]，从而训练模型推断被删除的 Token 内容的能力。 Token 删除任务（Token Deletion） ：在原始文本中随机删除一部分Token，从而训练模型推断被删除的Token 位置以及内容的能力。 连续文本填空任务（Text Infilling）：类似于 T5 的预训练任务，在原始文本中选择几段连续的 Token（每段作为一个 span），整体替换为 [MASK]。其中 span 的长度服从$\\lambda = 3$ 的泊松分布，如果长度为 0 则直接插入一个 [MASK]。这一任务旨在训练模型推断一段 span 及其长度的能力。 句子打乱任务（Sentence Permutation）：将给定文本拆分为多个句子，并随机打乱句子的顺序。旨在训练模型推理前后句关系的能力。 文档旋转任务（Document Rotation）：从给定文本中随机选取一个Token，作为文本新的开头进行旋转。旨在训练模型找到文本合理起始点的能力。 ","date":"2025-08-22","objectID":"/posts/%E5%A4%A7%E6%A8%A1%E5%9E%8B%E5%9F%BA%E7%A1%80~02.%E5%A4%A7%E8%AF%AD%E8%A8%80%E6%A8%A1%E5%9E%8B%E6%9E%B6%E6%9E%84/:4:2","tags":["大模型"],"title":"大模型大模型基础~02.大语言模型架构","uri":"/posts/%E5%A4%A7%E6%A8%A1%E5%9E%8B%E5%9F%BA%E7%A1%80~02.%E5%A4%A7%E8%AF%AD%E8%A8%80%E6%A8%A1%E5%9E%8B%E6%9E%B6%E6%9E%84/#bart-语言模型"},{"categories":null,"collections":["大模型基础"],"content":"4.2 BART 语言模型\rBART（Bidirectional and Auto-Regressive Transformers）是由 Meta AI 研究院同样于 2019 年10 月提出的一个 Encoder-Decoder 架构模型。不同于 T5 将多种 NLP 任务集成到一个统一的框架，BART 旨在通过多样化的预训练任务来提升模型在文本生成任务和文本理解任务上的表现。 预训练方式\rBART 以重建被破坏的文本为目标，通过多种任务来破坏文本，然后训练模型对原始文本进行恢复： Token 遮挡任务（Token Masking）：类似于 BERT 中的 MLM 任务，在原始文本中随机采样一部分Token 并将其替换为 [MASK]，从而训练模型推断被删除的 Token 内容的能力。 Token 删除任务（Token Deletion） ：在原始文本中随机删除一部分Token，从而训练模型推断被删除的Token 位置以及内容的能力。 连续文本填空任务（Text Infilling）：类似于 T5 的预训练任务，在原始文本中选择几段连续的 Token（每段作为一个 span），整体替换为 [MASK]。其中 span 的长度服从$\\lambda = 3$ 的泊松分布，如果长度为 0 则直接插入一个 [MASK]。这一任务旨在训练模型推断一段 span 及其长度的能力。 句子打乱任务（Sentence Permutation）：将给定文本拆分为多个句子，并随机打乱句子的顺序。旨在训练模型推理前后句关系的能力。 文档旋转任务（Document Rotation）：从给定文本中随机选取一个Token，作为文本新的开头进行旋转。旨在训练模型找到文本合理起始点的能力。 ","date":"2025-08-22","objectID":"/posts/%E5%A4%A7%E6%A8%A1%E5%9E%8B%E5%9F%BA%E7%A1%80~02.%E5%A4%A7%E8%AF%AD%E8%A8%80%E6%A8%A1%E5%9E%8B%E6%9E%B6%E6%9E%84/:4:2","tags":["大模型"],"title":"大模型大模型基础~02.大语言模型架构","uri":"/posts/%E5%A4%A7%E6%A8%A1%E5%9E%8B%E5%9F%BA%E7%A1%80~02.%E5%A4%A7%E8%AF%AD%E8%A8%80%E6%A8%A1%E5%9E%8B%E6%9E%B6%E6%9E%84/#预训练方式-5"},{"categories":null,"collections":["大模型基础"],"content":"第 5 节 Decoder-only 模型\r","date":"2025-08-22","objectID":"/posts/%E5%A4%A7%E6%A8%A1%E5%9E%8B%E5%9F%BA%E7%A1%80~02.%E5%A4%A7%E8%AF%AD%E8%A8%80%E6%A8%A1%E5%9E%8B%E6%9E%B6%E6%9E%84/:5:0","tags":["大模型"],"title":"大模型大模型基础~02.大语言模型架构","uri":"/posts/%E5%A4%A7%E6%A8%A1%E5%9E%8B%E5%9F%BA%E7%A1%80~02.%E5%A4%A7%E8%AF%AD%E8%A8%80%E6%A8%A1%E5%9E%8B%E6%9E%B6%E6%9E%84/#decoder-only-模型"},{"categories":null,"collections":["大模型基础"],"content":"5.1 GPT 系列语言模型\rGPT-1、GPT-2、GPT-3\r模型结构\rGPT-1 在结构上与 BERT-Base 高度类似，两者都包含 12 个编码或解码模块，每个模块也同样由一个自注意力模块和一个全连接前馈模块组成。 两者的本质区别在于 BERT-Base 中的自注意力模块是双向的自注意力机制，而 GPT-1 中的自注意力模块则是带有掩码的单向自注意力机制。 GPT-2 和 GPT-3 使用了更大规模的模型，如下： 预训练方法\r数据集\rGPT-1 使用小说数据集 BookCorpus 来进行预训练，该数据集包含约8 亿个Token，总数据量接近5GB。 GPT-2 采用了全新的 WebText 数据集，该数据集由 40GB 经过精心筛选和清洗的网文本组成。 GPT-3 使用了更大规模和更多样化的互联网文本数据集，数据量接近1TB，涵盖了Common Craw、WebText、BookCorpus、Wikipedia等多个来源，包括书籍、网站、论坛帖子等各类文本形式。 预训练任务\r采用下一词预测任务，即基于给定的上文预测下一个可能出现的Token。 InstructGPT\rInstructGPT 是 ChatGPT 的前身。它通过引入了人类反馈强化学习（Reinforcement Learning from Human Feedback, RLHF），显著提升了模型对用户指令的响应能力。 人类评估者首先提供关于模型输出质量的反馈，然后使用这些反馈来微调模型。 有监督微调：收集大量“问题-人类回答”对作为训练样本，对大语言模型进行微调。 训练奖励模型：针对每个输入，让模型生成多个候选输出，并由人工对其进行质量评估和排名，构成偏好数据集。用此偏好数据集训练一个奖励模型，使其可以对输出是否符合人类偏好进行打分。 强化学习微调：基于上一步中得到的奖励模型，使用强化学习方法优化第一步中的语言模型。即在语言模型生成输出后，奖励模型对其进行评分，强化学习算法根据这些评分调整模型参数，以提升高质量输出的概率。 ChatGPT、GPT-4\rOpenAI 于2022 年11 月推出了聊天机器人（ChatGPT, Chat Generative Pretraine Transformer）。ChatGPT“一鸣惊人”，以强大的对话能力展示出令人惊讶的智能，一度燃起了ChatGPT 是否可以通过“图灵测试”的讨论。 GPT-4 在理解复杂语境、捕捉语言细微差别、生成连贯文本等任务上进一步提升，并且能够更有效地处理数学问题、编程挑战等高级认知任务。此外，GPT-4 还引入了对图文双模态的支持，扩展了其在图像描述和视觉问题解答等应用领域的可能性。 GPT-4o 模型在前代GPT-4 的基础上，大幅提升了响应速度，显著降低了延迟，并且还增强了多模态处理能力以及多语言支持能力。 ","date":"2025-08-22","objectID":"/posts/%E5%A4%A7%E6%A8%A1%E5%9E%8B%E5%9F%BA%E7%A1%80~02.%E5%A4%A7%E8%AF%AD%E8%A8%80%E6%A8%A1%E5%9E%8B%E6%9E%B6%E6%9E%84/:5:1","tags":["大模型"],"title":"大模型大模型基础~02.大语言模型架构","uri":"/posts/%E5%A4%A7%E6%A8%A1%E5%9E%8B%E5%9F%BA%E7%A1%80~02.%E5%A4%A7%E8%AF%AD%E8%A8%80%E6%A8%A1%E5%9E%8B%E6%9E%B6%E6%9E%84/#gpt-系列语言模型"},{"categories":null,"collections":["大模型基础"],"content":"5.1 GPT 系列语言模型\rGPT-1、GPT-2、GPT-3\r模型结构\rGPT-1 在结构上与 BERT-Base 高度类似，两者都包含 12 个编码或解码模块，每个模块也同样由一个自注意力模块和一个全连接前馈模块组成。 两者的本质区别在于 BERT-Base 中的自注意力模块是双向的自注意力机制，而 GPT-1 中的自注意力模块则是带有掩码的单向自注意力机制。 GPT-2 和 GPT-3 使用了更大规模的模型，如下： 预训练方法\r数据集\rGPT-1 使用小说数据集 BookCorpus 来进行预训练，该数据集包含约8 亿个Token，总数据量接近5GB。 GPT-2 采用了全新的 WebText 数据集，该数据集由 40GB 经过精心筛选和清洗的网文本组成。 GPT-3 使用了更大规模和更多样化的互联网文本数据集，数据量接近1TB，涵盖了Common Craw、WebText、BookCorpus、Wikipedia等多个来源，包括书籍、网站、论坛帖子等各类文本形式。 预训练任务\r采用下一词预测任务，即基于给定的上文预测下一个可能出现的Token。 InstructGPT\rInstructGPT 是 ChatGPT 的前身。它通过引入了人类反馈强化学习（Reinforcement Learning from Human Feedback, RLHF），显著提升了模型对用户指令的响应能力。 人类评估者首先提供关于模型输出质量的反馈，然后使用这些反馈来微调模型。 有监督微调：收集大量“问题-人类回答”对作为训练样本，对大语言模型进行微调。 训练奖励模型：针对每个输入，让模型生成多个候选输出，并由人工对其进行质量评估和排名，构成偏好数据集。用此偏好数据集训练一个奖励模型，使其可以对输出是否符合人类偏好进行打分。 强化学习微调：基于上一步中得到的奖励模型，使用强化学习方法优化第一步中的语言模型。即在语言模型生成输出后，奖励模型对其进行评分，强化学习算法根据这些评分调整模型参数，以提升高质量输出的概率。 ChatGPT、GPT-4\rOpenAI 于2022 年11 月推出了聊天机器人（ChatGPT, Chat Generative Pretraine Transformer）。ChatGPT“一鸣惊人”，以强大的对话能力展示出令人惊讶的智能，一度燃起了ChatGPT 是否可以通过“图灵测试”的讨论。 GPT-4 在理解复杂语境、捕捉语言细微差别、生成连贯文本等任务上进一步提升，并且能够更有效地处理数学问题、编程挑战等高级认知任务。此外，GPT-4 还引入了对图文双模态的支持，扩展了其在图像描述和视觉问题解答等应用领域的可能性。 GPT-4o 模型在前代GPT-4 的基础上，大幅提升了响应速度，显著降低了延迟，并且还增强了多模态处理能力以及多语言支持能力。 ","date":"2025-08-22","objectID":"/posts/%E5%A4%A7%E6%A8%A1%E5%9E%8B%E5%9F%BA%E7%A1%80~02.%E5%A4%A7%E8%AF%AD%E8%A8%80%E6%A8%A1%E5%9E%8B%E6%9E%B6%E6%9E%84/:5:1","tags":["大模型"],"title":"大模型大模型基础~02.大语言模型架构","uri":"/posts/%E5%A4%A7%E6%A8%A1%E5%9E%8B%E5%9F%BA%E7%A1%80~02.%E5%A4%A7%E8%AF%AD%E8%A8%80%E6%A8%A1%E5%9E%8B%E6%9E%B6%E6%9E%84/#gpt-1gpt-2gpt-3"},{"categories":null,"collections":["大模型基础"],"content":"5.1 GPT 系列语言模型\rGPT-1、GPT-2、GPT-3\r模型结构\rGPT-1 在结构上与 BERT-Base 高度类似，两者都包含 12 个编码或解码模块，每个模块也同样由一个自注意力模块和一个全连接前馈模块组成。 两者的本质区别在于 BERT-Base 中的自注意力模块是双向的自注意力机制，而 GPT-1 中的自注意力模块则是带有掩码的单向自注意力机制。 GPT-2 和 GPT-3 使用了更大规模的模型，如下： 预训练方法\r数据集\rGPT-1 使用小说数据集 BookCorpus 来进行预训练，该数据集包含约8 亿个Token，总数据量接近5GB。 GPT-2 采用了全新的 WebText 数据集，该数据集由 40GB 经过精心筛选和清洗的网文本组成。 GPT-3 使用了更大规模和更多样化的互联网文本数据集，数据量接近1TB，涵盖了Common Craw、WebText、BookCorpus、Wikipedia等多个来源，包括书籍、网站、论坛帖子等各类文本形式。 预训练任务\r采用下一词预测任务，即基于给定的上文预测下一个可能出现的Token。 InstructGPT\rInstructGPT 是 ChatGPT 的前身。它通过引入了人类反馈强化学习（Reinforcement Learning from Human Feedback, RLHF），显著提升了模型对用户指令的响应能力。 人类评估者首先提供关于模型输出质量的反馈，然后使用这些反馈来微调模型。 有监督微调：收集大量“问题-人类回答”对作为训练样本，对大语言模型进行微调。 训练奖励模型：针对每个输入，让模型生成多个候选输出，并由人工对其进行质量评估和排名，构成偏好数据集。用此偏好数据集训练一个奖励模型，使其可以对输出是否符合人类偏好进行打分。 强化学习微调：基于上一步中得到的奖励模型，使用强化学习方法优化第一步中的语言模型。即在语言模型生成输出后，奖励模型对其进行评分，强化学习算法根据这些评分调整模型参数，以提升高质量输出的概率。 ChatGPT、GPT-4\rOpenAI 于2022 年11 月推出了聊天机器人（ChatGPT, Chat Generative Pretraine Transformer）。ChatGPT“一鸣惊人”，以强大的对话能力展示出令人惊讶的智能，一度燃起了ChatGPT 是否可以通过“图灵测试”的讨论。 GPT-4 在理解复杂语境、捕捉语言细微差别、生成连贯文本等任务上进一步提升，并且能够更有效地处理数学问题、编程挑战等高级认知任务。此外，GPT-4 还引入了对图文双模态的支持，扩展了其在图像描述和视觉问题解答等应用领域的可能性。 GPT-4o 模型在前代GPT-4 的基础上，大幅提升了响应速度，显著降低了延迟，并且还增强了多模态处理能力以及多语言支持能力。 ","date":"2025-08-22","objectID":"/posts/%E5%A4%A7%E6%A8%A1%E5%9E%8B%E5%9F%BA%E7%A1%80~02.%E5%A4%A7%E8%AF%AD%E8%A8%80%E6%A8%A1%E5%9E%8B%E6%9E%B6%E6%9E%84/:5:1","tags":["大模型"],"title":"大模型大模型基础~02.大语言模型架构","uri":"/posts/%E5%A4%A7%E6%A8%A1%E5%9E%8B%E5%9F%BA%E7%A1%80~02.%E5%A4%A7%E8%AF%AD%E8%A8%80%E6%A8%A1%E5%9E%8B%E6%9E%B6%E6%9E%84/#模型结构-3"},{"categories":null,"collections":["大模型基础"],"content":"5.1 GPT 系列语言模型\rGPT-1、GPT-2、GPT-3\r模型结构\rGPT-1 在结构上与 BERT-Base 高度类似，两者都包含 12 个编码或解码模块，每个模块也同样由一个自注意力模块和一个全连接前馈模块组成。 两者的本质区别在于 BERT-Base 中的自注意力模块是双向的自注意力机制，而 GPT-1 中的自注意力模块则是带有掩码的单向自注意力机制。 GPT-2 和 GPT-3 使用了更大规模的模型，如下： 预训练方法\r数据集\rGPT-1 使用小说数据集 BookCorpus 来进行预训练，该数据集包含约8 亿个Token，总数据量接近5GB。 GPT-2 采用了全新的 WebText 数据集，该数据集由 40GB 经过精心筛选和清洗的网文本组成。 GPT-3 使用了更大规模和更多样化的互联网文本数据集，数据量接近1TB，涵盖了Common Craw、WebText、BookCorpus、Wikipedia等多个来源，包括书籍、网站、论坛帖子等各类文本形式。 预训练任务\r采用下一词预测任务，即基于给定的上文预测下一个可能出现的Token。 InstructGPT\rInstructGPT 是 ChatGPT 的前身。它通过引入了人类反馈强化学习（Reinforcement Learning from Human Feedback, RLHF），显著提升了模型对用户指令的响应能力。 人类评估者首先提供关于模型输出质量的反馈，然后使用这些反馈来微调模型。 有监督微调：收集大量“问题-人类回答”对作为训练样本，对大语言模型进行微调。 训练奖励模型：针对每个输入，让模型生成多个候选输出，并由人工对其进行质量评估和排名，构成偏好数据集。用此偏好数据集训练一个奖励模型，使其可以对输出是否符合人类偏好进行打分。 强化学习微调：基于上一步中得到的奖励模型，使用强化学习方法优化第一步中的语言模型。即在语言模型生成输出后，奖励模型对其进行评分，强化学习算法根据这些评分调整模型参数，以提升高质量输出的概率。 ChatGPT、GPT-4\rOpenAI 于2022 年11 月推出了聊天机器人（ChatGPT, Chat Generative Pretraine Transformer）。ChatGPT“一鸣惊人”，以强大的对话能力展示出令人惊讶的智能，一度燃起了ChatGPT 是否可以通过“图灵测试”的讨论。 GPT-4 在理解复杂语境、捕捉语言细微差别、生成连贯文本等任务上进一步提升，并且能够更有效地处理数学问题、编程挑战等高级认知任务。此外，GPT-4 还引入了对图文双模态的支持，扩展了其在图像描述和视觉问题解答等应用领域的可能性。 GPT-4o 模型在前代GPT-4 的基础上，大幅提升了响应速度，显著降低了延迟，并且还增强了多模态处理能力以及多语言支持能力。 ","date":"2025-08-22","objectID":"/posts/%E5%A4%A7%E6%A8%A1%E5%9E%8B%E5%9F%BA%E7%A1%80~02.%E5%A4%A7%E8%AF%AD%E8%A8%80%E6%A8%A1%E5%9E%8B%E6%9E%B6%E6%9E%84/:5:1","tags":["大模型"],"title":"大模型大模型基础~02.大语言模型架构","uri":"/posts/%E5%A4%A7%E6%A8%A1%E5%9E%8B%E5%9F%BA%E7%A1%80~02.%E5%A4%A7%E8%AF%AD%E8%A8%80%E6%A8%A1%E5%9E%8B%E6%9E%B6%E6%9E%84/#预训练方法"},{"categories":null,"collections":["大模型基础"],"content":"5.1 GPT 系列语言模型\rGPT-1、GPT-2、GPT-3\r模型结构\rGPT-1 在结构上与 BERT-Base 高度类似，两者都包含 12 个编码或解码模块，每个模块也同样由一个自注意力模块和一个全连接前馈模块组成。 两者的本质区别在于 BERT-Base 中的自注意力模块是双向的自注意力机制，而 GPT-1 中的自注意力模块则是带有掩码的单向自注意力机制。 GPT-2 和 GPT-3 使用了更大规模的模型，如下： 预训练方法\r数据集\rGPT-1 使用小说数据集 BookCorpus 来进行预训练，该数据集包含约8 亿个Token，总数据量接近5GB。 GPT-2 采用了全新的 WebText 数据集，该数据集由 40GB 经过精心筛选和清洗的网文本组成。 GPT-3 使用了更大规模和更多样化的互联网文本数据集，数据量接近1TB，涵盖了Common Craw、WebText、BookCorpus、Wikipedia等多个来源，包括书籍、网站、论坛帖子等各类文本形式。 预训练任务\r采用下一词预测任务，即基于给定的上文预测下一个可能出现的Token。 InstructGPT\rInstructGPT 是 ChatGPT 的前身。它通过引入了人类反馈强化学习（Reinforcement Learning from Human Feedback, RLHF），显著提升了模型对用户指令的响应能力。 人类评估者首先提供关于模型输出质量的反馈，然后使用这些反馈来微调模型。 有监督微调：收集大量“问题-人类回答”对作为训练样本，对大语言模型进行微调。 训练奖励模型：针对每个输入，让模型生成多个候选输出，并由人工对其进行质量评估和排名，构成偏好数据集。用此偏好数据集训练一个奖励模型，使其可以对输出是否符合人类偏好进行打分。 强化学习微调：基于上一步中得到的奖励模型，使用强化学习方法优化第一步中的语言模型。即在语言模型生成输出后，奖励模型对其进行评分，强化学习算法根据这些评分调整模型参数，以提升高质量输出的概率。 ChatGPT、GPT-4\rOpenAI 于2022 年11 月推出了聊天机器人（ChatGPT, Chat Generative Pretraine Transformer）。ChatGPT“一鸣惊人”，以强大的对话能力展示出令人惊讶的智能，一度燃起了ChatGPT 是否可以通过“图灵测试”的讨论。 GPT-4 在理解复杂语境、捕捉语言细微差别、生成连贯文本等任务上进一步提升，并且能够更有效地处理数学问题、编程挑战等高级认知任务。此外，GPT-4 还引入了对图文双模态的支持，扩展了其在图像描述和视觉问题解答等应用领域的可能性。 GPT-4o 模型在前代GPT-4 的基础上，大幅提升了响应速度，显著降低了延迟，并且还增强了多模态处理能力以及多语言支持能力。 ","date":"2025-08-22","objectID":"/posts/%E5%A4%A7%E6%A8%A1%E5%9E%8B%E5%9F%BA%E7%A1%80~02.%E5%A4%A7%E8%AF%AD%E8%A8%80%E6%A8%A1%E5%9E%8B%E6%9E%B6%E6%9E%84/:5:1","tags":["大模型"],"title":"大模型大模型基础~02.大语言模型架构","uri":"/posts/%E5%A4%A7%E6%A8%A1%E5%9E%8B%E5%9F%BA%E7%A1%80~02.%E5%A4%A7%E8%AF%AD%E8%A8%80%E6%A8%A1%E5%9E%8B%E6%9E%B6%E6%9E%84/#数据集-2"},{"categories":null,"collections":["大模型基础"],"content":"5.1 GPT 系列语言模型\rGPT-1、GPT-2、GPT-3\r模型结构\rGPT-1 在结构上与 BERT-Base 高度类似，两者都包含 12 个编码或解码模块，每个模块也同样由一个自注意力模块和一个全连接前馈模块组成。 两者的本质区别在于 BERT-Base 中的自注意力模块是双向的自注意力机制，而 GPT-1 中的自注意力模块则是带有掩码的单向自注意力机制。 GPT-2 和 GPT-3 使用了更大规模的模型，如下： 预训练方法\r数据集\rGPT-1 使用小说数据集 BookCorpus 来进行预训练，该数据集包含约8 亿个Token，总数据量接近5GB。 GPT-2 采用了全新的 WebText 数据集，该数据集由 40GB 经过精心筛选和清洗的网文本组成。 GPT-3 使用了更大规模和更多样化的互联网文本数据集，数据量接近1TB，涵盖了Common Craw、WebText、BookCorpus、Wikipedia等多个来源，包括书籍、网站、论坛帖子等各类文本形式。 预训练任务\r采用下一词预测任务，即基于给定的上文预测下一个可能出现的Token。 InstructGPT\rInstructGPT 是 ChatGPT 的前身。它通过引入了人类反馈强化学习（Reinforcement Learning from Human Feedback, RLHF），显著提升了模型对用户指令的响应能力。 人类评估者首先提供关于模型输出质量的反馈，然后使用这些反馈来微调模型。 有监督微调：收集大量“问题-人类回答”对作为训练样本，对大语言模型进行微调。 训练奖励模型：针对每个输入，让模型生成多个候选输出，并由人工对其进行质量评估和排名，构成偏好数据集。用此偏好数据集训练一个奖励模型，使其可以对输出是否符合人类偏好进行打分。 强化学习微调：基于上一步中得到的奖励模型，使用强化学习方法优化第一步中的语言模型。即在语言模型生成输出后，奖励模型对其进行评分，强化学习算法根据这些评分调整模型参数，以提升高质量输出的概率。 ChatGPT、GPT-4\rOpenAI 于2022 年11 月推出了聊天机器人（ChatGPT, Chat Generative Pretraine Transformer）。ChatGPT“一鸣惊人”，以强大的对话能力展示出令人惊讶的智能，一度燃起了ChatGPT 是否可以通过“图灵测试”的讨论。 GPT-4 在理解复杂语境、捕捉语言细微差别、生成连贯文本等任务上进一步提升，并且能够更有效地处理数学问题、编程挑战等高级认知任务。此外，GPT-4 还引入了对图文双模态的支持，扩展了其在图像描述和视觉问题解答等应用领域的可能性。 GPT-4o 模型在前代GPT-4 的基础上，大幅提升了响应速度，显著降低了延迟，并且还增强了多模态处理能力以及多语言支持能力。 ","date":"2025-08-22","objectID":"/posts/%E5%A4%A7%E6%A8%A1%E5%9E%8B%E5%9F%BA%E7%A1%80~02.%E5%A4%A7%E8%AF%AD%E8%A8%80%E6%A8%A1%E5%9E%8B%E6%9E%B6%E6%9E%84/:5:1","tags":["大模型"],"title":"大模型大模型基础~02.大语言模型架构","uri":"/posts/%E5%A4%A7%E6%A8%A1%E5%9E%8B%E5%9F%BA%E7%A1%80~02.%E5%A4%A7%E8%AF%AD%E8%A8%80%E6%A8%A1%E5%9E%8B%E6%9E%B6%E6%9E%84/#预训练任务-3"},{"categories":null,"collections":["大模型基础"],"content":"5.1 GPT 系列语言模型\rGPT-1、GPT-2、GPT-3\r模型结构\rGPT-1 在结构上与 BERT-Base 高度类似，两者都包含 12 个编码或解码模块，每个模块也同样由一个自注意力模块和一个全连接前馈模块组成。 两者的本质区别在于 BERT-Base 中的自注意力模块是双向的自注意力机制，而 GPT-1 中的自注意力模块则是带有掩码的单向自注意力机制。 GPT-2 和 GPT-3 使用了更大规模的模型，如下： 预训练方法\r数据集\rGPT-1 使用小说数据集 BookCorpus 来进行预训练，该数据集包含约8 亿个Token，总数据量接近5GB。 GPT-2 采用了全新的 WebText 数据集，该数据集由 40GB 经过精心筛选和清洗的网文本组成。 GPT-3 使用了更大规模和更多样化的互联网文本数据集，数据量接近1TB，涵盖了Common Craw、WebText、BookCorpus、Wikipedia等多个来源，包括书籍、网站、论坛帖子等各类文本形式。 预训练任务\r采用下一词预测任务，即基于给定的上文预测下一个可能出现的Token。 InstructGPT\rInstructGPT 是 ChatGPT 的前身。它通过引入了人类反馈强化学习（Reinforcement Learning from Human Feedback, RLHF），显著提升了模型对用户指令的响应能力。 人类评估者首先提供关于模型输出质量的反馈，然后使用这些反馈来微调模型。 有监督微调：收集大量“问题-人类回答”对作为训练样本，对大语言模型进行微调。 训练奖励模型：针对每个输入，让模型生成多个候选输出，并由人工对其进行质量评估和排名，构成偏好数据集。用此偏好数据集训练一个奖励模型，使其可以对输出是否符合人类偏好进行打分。 强化学习微调：基于上一步中得到的奖励模型，使用强化学习方法优化第一步中的语言模型。即在语言模型生成输出后，奖励模型对其进行评分，强化学习算法根据这些评分调整模型参数，以提升高质量输出的概率。 ChatGPT、GPT-4\rOpenAI 于2022 年11 月推出了聊天机器人（ChatGPT, Chat Generative Pretraine Transformer）。ChatGPT“一鸣惊人”，以强大的对话能力展示出令人惊讶的智能，一度燃起了ChatGPT 是否可以通过“图灵测试”的讨论。 GPT-4 在理解复杂语境、捕捉语言细微差别、生成连贯文本等任务上进一步提升，并且能够更有效地处理数学问题、编程挑战等高级认知任务。此外，GPT-4 还引入了对图文双模态的支持，扩展了其在图像描述和视觉问题解答等应用领域的可能性。 GPT-4o 模型在前代GPT-4 的基础上，大幅提升了响应速度，显著降低了延迟，并且还增强了多模态处理能力以及多语言支持能力。 ","date":"2025-08-22","objectID":"/posts/%E5%A4%A7%E6%A8%A1%E5%9E%8B%E5%9F%BA%E7%A1%80~02.%E5%A4%A7%E8%AF%AD%E8%A8%80%E6%A8%A1%E5%9E%8B%E6%9E%B6%E6%9E%84/:5:1","tags":["大模型"],"title":"大模型大模型基础~02.大语言模型架构","uri":"/posts/%E5%A4%A7%E6%A8%A1%E5%9E%8B%E5%9F%BA%E7%A1%80~02.%E5%A4%A7%E8%AF%AD%E8%A8%80%E6%A8%A1%E5%9E%8B%E6%9E%B6%E6%9E%84/#instructgpt"},{"categories":null,"collections":["大模型基础"],"content":"5.1 GPT 系列语言模型\rGPT-1、GPT-2、GPT-3\r模型结构\rGPT-1 在结构上与 BERT-Base 高度类似，两者都包含 12 个编码或解码模块，每个模块也同样由一个自注意力模块和一个全连接前馈模块组成。 两者的本质区别在于 BERT-Base 中的自注意力模块是双向的自注意力机制，而 GPT-1 中的自注意力模块则是带有掩码的单向自注意力机制。 GPT-2 和 GPT-3 使用了更大规模的模型，如下： 预训练方法\r数据集\rGPT-1 使用小说数据集 BookCorpus 来进行预训练，该数据集包含约8 亿个Token，总数据量接近5GB。 GPT-2 采用了全新的 WebText 数据集，该数据集由 40GB 经过精心筛选和清洗的网文本组成。 GPT-3 使用了更大规模和更多样化的互联网文本数据集，数据量接近1TB，涵盖了Common Craw、WebText、BookCorpus、Wikipedia等多个来源，包括书籍、网站、论坛帖子等各类文本形式。 预训练任务\r采用下一词预测任务，即基于给定的上文预测下一个可能出现的Token。 InstructGPT\rInstructGPT 是 ChatGPT 的前身。它通过引入了人类反馈强化学习（Reinforcement Learning from Human Feedback, RLHF），显著提升了模型对用户指令的响应能力。 人类评估者首先提供关于模型输出质量的反馈，然后使用这些反馈来微调模型。 有监督微调：收集大量“问题-人类回答”对作为训练样本，对大语言模型进行微调。 训练奖励模型：针对每个输入，让模型生成多个候选输出，并由人工对其进行质量评估和排名，构成偏好数据集。用此偏好数据集训练一个奖励模型，使其可以对输出是否符合人类偏好进行打分。 强化学习微调：基于上一步中得到的奖励模型，使用强化学习方法优化第一步中的语言模型。即在语言模型生成输出后，奖励模型对其进行评分，强化学习算法根据这些评分调整模型参数，以提升高质量输出的概率。 ChatGPT、GPT-4\rOpenAI 于2022 年11 月推出了聊天机器人（ChatGPT, Chat Generative Pretraine Transformer）。ChatGPT“一鸣惊人”，以强大的对话能力展示出令人惊讶的智能，一度燃起了ChatGPT 是否可以通过“图灵测试”的讨论。 GPT-4 在理解复杂语境、捕捉语言细微差别、生成连贯文本等任务上进一步提升，并且能够更有效地处理数学问题、编程挑战等高级认知任务。此外，GPT-4 还引入了对图文双模态的支持，扩展了其在图像描述和视觉问题解答等应用领域的可能性。 GPT-4o 模型在前代GPT-4 的基础上，大幅提升了响应速度，显著降低了延迟，并且还增强了多模态处理能力以及多语言支持能力。 ","date":"2025-08-22","objectID":"/posts/%E5%A4%A7%E6%A8%A1%E5%9E%8B%E5%9F%BA%E7%A1%80~02.%E5%A4%A7%E8%AF%AD%E8%A8%80%E6%A8%A1%E5%9E%8B%E6%9E%B6%E6%9E%84/:5:1","tags":["大模型"],"title":"大模型大模型基础~02.大语言模型架构","uri":"/posts/%E5%A4%A7%E6%A8%A1%E5%9E%8B%E5%9F%BA%E7%A1%80~02.%E5%A4%A7%E8%AF%AD%E8%A8%80%E6%A8%A1%E5%9E%8B%E6%9E%B6%E6%9E%84/#chatgptgpt-4"},{"categories":null,"collections":["大模型基础"],"content":"5.2 LLAMA 系列语言模型\rLLaMA（Large Language Model Meta AI）是由Meta AI 开发的一系列大语言模型，其模型权重在非商业许可证下向学术界开。 LLaMA 与GPT 系列的主要区别在于：GPT 系列的升级主线聚焦于模型规模与预训练语料的同步提升，而LLaMA 则在模型规模上保持相对稳定，更专注于提升预训练数据的规模。 LLaMA1 模型\r在模型架构方面，LLaMA1 采用了与GPT 系列同样的网络架构。但是，其在 Transformer 原始词嵌入模块、注意力模块和全连接前馈模块上进行了优化。 在词嵌入模块上，为了提高词嵌入质量，LLaMA1 参考了GPTNeo 的做法，使用旋转位置编码（Rotary Positional Embeddings, RoPE）替代了原有的绝对位置编码，从而增强位置编码的表达能力，增强了模型对序列顺序的理解。 在注意力模块上，LLaMA1 参考了 PaLM 的做法，将 Transformer 中的 RELU 激活函数改为 SwiGLU 激活函数。并且，LLaMA1 在进行自注意力操作之前对查询（query）以及键（key）添加旋转位置编码。 在全连接前馈模块上，LLaMA1 借鉴了 GPT-3 中的 Pre-Norm 层正则化策略，将正则化应用于自注意力和前馈网络的输入。 LLaMA2 模型\rLLaMA2 采纳了人类反馈强化学习的方法，进一步提升了模型的性能。 首先，其使用了大规模且公开的指令微调数据集对模型进行有监督的微调。 然后，LLaMA2 还训练了RLHF 奖励模型，并基于近似策略优化（Proximal Policy Optimization, PPO）以及拒绝采样（Rejection Sampling）进行强化学习对模型进行更新。 LLaMA3 模型\r在模型架构上，LLaMA3 与前一代 LLaMA2 几乎完全相同，只是在分词（tokenizer） 阶段，将字典长度扩大了三倍，极大提升了推理效率。 这一改进减少了中文字符等语言元素被拆分为多个 Token 的情况，有效降低了总体Token 数量，从而提高了模型处理语言的连贯性和准确性。 另一方面，扩大的字典有助于减少对具有完整意义的语义单元进行分割，使模型在处理文本时可以更准确的捕捉词义和上下文，提高生成文本的流畅性和连贯性。 ","date":"2025-08-22","objectID":"/posts/%E5%A4%A7%E6%A8%A1%E5%9E%8B%E5%9F%BA%E7%A1%80~02.%E5%A4%A7%E8%AF%AD%E8%A8%80%E6%A8%A1%E5%9E%8B%E6%9E%B6%E6%9E%84/:5:2","tags":["大模型"],"title":"大模型大模型基础~02.大语言模型架构","uri":"/posts/%E5%A4%A7%E6%A8%A1%E5%9E%8B%E5%9F%BA%E7%A1%80~02.%E5%A4%A7%E8%AF%AD%E8%A8%80%E6%A8%A1%E5%9E%8B%E6%9E%B6%E6%9E%84/#llama-系列语言模型"},{"categories":null,"collections":["大模型基础"],"content":"5.2 LLAMA 系列语言模型\rLLaMA（Large Language Model Meta AI）是由Meta AI 开发的一系列大语言模型，其模型权重在非商业许可证下向学术界开。 LLaMA 与GPT 系列的主要区别在于：GPT 系列的升级主线聚焦于模型规模与预训练语料的同步提升，而LLaMA 则在模型规模上保持相对稳定，更专注于提升预训练数据的规模。 LLaMA1 模型\r在模型架构方面，LLaMA1 采用了与GPT 系列同样的网络架构。但是，其在 Transformer 原始词嵌入模块、注意力模块和全连接前馈模块上进行了优化。 在词嵌入模块上，为了提高词嵌入质量，LLaMA1 参考了GPTNeo 的做法，使用旋转位置编码（Rotary Positional Embeddings, RoPE）替代了原有的绝对位置编码，从而增强位置编码的表达能力，增强了模型对序列顺序的理解。 在注意力模块上，LLaMA1 参考了 PaLM 的做法，将 Transformer 中的 RELU 激活函数改为 SwiGLU 激活函数。并且，LLaMA1 在进行自注意力操作之前对查询（query）以及键（key）添加旋转位置编码。 在全连接前馈模块上，LLaMA1 借鉴了 GPT-3 中的 Pre-Norm 层正则化策略，将正则化应用于自注意力和前馈网络的输入。 LLaMA2 模型\rLLaMA2 采纳了人类反馈强化学习的方法，进一步提升了模型的性能。 首先，其使用了大规模且公开的指令微调数据集对模型进行有监督的微调。 然后，LLaMA2 还训练了RLHF 奖励模型，并基于近似策略优化（Proximal Policy Optimization, PPO）以及拒绝采样（Rejection Sampling）进行强化学习对模型进行更新。 LLaMA3 模型\r在模型架构上，LLaMA3 与前一代 LLaMA2 几乎完全相同，只是在分词（tokenizer） 阶段，将字典长度扩大了三倍，极大提升了推理效率。 这一改进减少了中文字符等语言元素被拆分为多个 Token 的情况，有效降低了总体Token 数量，从而提高了模型处理语言的连贯性和准确性。 另一方面，扩大的字典有助于减少对具有完整意义的语义单元进行分割，使模型在处理文本时可以更准确的捕捉词义和上下文，提高生成文本的流畅性和连贯性。 ","date":"2025-08-22","objectID":"/posts/%E5%A4%A7%E6%A8%A1%E5%9E%8B%E5%9F%BA%E7%A1%80~02.%E5%A4%A7%E8%AF%AD%E8%A8%80%E6%A8%A1%E5%9E%8B%E6%9E%B6%E6%9E%84/:5:2","tags":["大模型"],"title":"大模型大模型基础~02.大语言模型架构","uri":"/posts/%E5%A4%A7%E6%A8%A1%E5%9E%8B%E5%9F%BA%E7%A1%80~02.%E5%A4%A7%E8%AF%AD%E8%A8%80%E6%A8%A1%E5%9E%8B%E6%9E%B6%E6%9E%84/#llama1-模型"},{"categories":null,"collections":["大模型基础"],"content":"5.2 LLAMA 系列语言模型\rLLaMA（Large Language Model Meta AI）是由Meta AI 开发的一系列大语言模型，其模型权重在非商业许可证下向学术界开。 LLaMA 与GPT 系列的主要区别在于：GPT 系列的升级主线聚焦于模型规模与预训练语料的同步提升，而LLaMA 则在模型规模上保持相对稳定，更专注于提升预训练数据的规模。 LLaMA1 模型\r在模型架构方面，LLaMA1 采用了与GPT 系列同样的网络架构。但是，其在 Transformer 原始词嵌入模块、注意力模块和全连接前馈模块上进行了优化。 在词嵌入模块上，为了提高词嵌入质量，LLaMA1 参考了GPTNeo 的做法，使用旋转位置编码（Rotary Positional Embeddings, RoPE）替代了原有的绝对位置编码，从而增强位置编码的表达能力，增强了模型对序列顺序的理解。 在注意力模块上，LLaMA1 参考了 PaLM 的做法，将 Transformer 中的 RELU 激活函数改为 SwiGLU 激活函数。并且，LLaMA1 在进行自注意力操作之前对查询（query）以及键（key）添加旋转位置编码。 在全连接前馈模块上，LLaMA1 借鉴了 GPT-3 中的 Pre-Norm 层正则化策略，将正则化应用于自注意力和前馈网络的输入。 LLaMA2 模型\rLLaMA2 采纳了人类反馈强化学习的方法，进一步提升了模型的性能。 首先，其使用了大规模且公开的指令微调数据集对模型进行有监督的微调。 然后，LLaMA2 还训练了RLHF 奖励模型，并基于近似策略优化（Proximal Policy Optimization, PPO）以及拒绝采样（Rejection Sampling）进行强化学习对模型进行更新。 LLaMA3 模型\r在模型架构上，LLaMA3 与前一代 LLaMA2 几乎完全相同，只是在分词（tokenizer） 阶段，将字典长度扩大了三倍，极大提升了推理效率。 这一改进减少了中文字符等语言元素被拆分为多个 Token 的情况，有效降低了总体Token 数量，从而提高了模型处理语言的连贯性和准确性。 另一方面，扩大的字典有助于减少对具有完整意义的语义单元进行分割，使模型在处理文本时可以更准确的捕捉词义和上下文，提高生成文本的流畅性和连贯性。 ","date":"2025-08-22","objectID":"/posts/%E5%A4%A7%E6%A8%A1%E5%9E%8B%E5%9F%BA%E7%A1%80~02.%E5%A4%A7%E8%AF%AD%E8%A8%80%E6%A8%A1%E5%9E%8B%E6%9E%B6%E6%9E%84/:5:2","tags":["大模型"],"title":"大模型大模型基础~02.大语言模型架构","uri":"/posts/%E5%A4%A7%E6%A8%A1%E5%9E%8B%E5%9F%BA%E7%A1%80~02.%E5%A4%A7%E8%AF%AD%E8%A8%80%E6%A8%A1%E5%9E%8B%E6%9E%B6%E6%9E%84/#llama2-模型"},{"categories":null,"collections":["大模型基础"],"content":"5.2 LLAMA 系列语言模型\rLLaMA（Large Language Model Meta AI）是由Meta AI 开发的一系列大语言模型，其模型权重在非商业许可证下向学术界开。 LLaMA 与GPT 系列的主要区别在于：GPT 系列的升级主线聚焦于模型规模与预训练语料的同步提升，而LLaMA 则在模型规模上保持相对稳定，更专注于提升预训练数据的规模。 LLaMA1 模型\r在模型架构方面，LLaMA1 采用了与GPT 系列同样的网络架构。但是，其在 Transformer 原始词嵌入模块、注意力模块和全连接前馈模块上进行了优化。 在词嵌入模块上，为了提高词嵌入质量，LLaMA1 参考了GPTNeo 的做法，使用旋转位置编码（Rotary Positional Embeddings, RoPE）替代了原有的绝对位置编码，从而增强位置编码的表达能力，增强了模型对序列顺序的理解。 在注意力模块上，LLaMA1 参考了 PaLM 的做法，将 Transformer 中的 RELU 激活函数改为 SwiGLU 激活函数。并且，LLaMA1 在进行自注意力操作之前对查询（query）以及键（key）添加旋转位置编码。 在全连接前馈模块上，LLaMA1 借鉴了 GPT-3 中的 Pre-Norm 层正则化策略，将正则化应用于自注意力和前馈网络的输入。 LLaMA2 模型\rLLaMA2 采纳了人类反馈强化学习的方法，进一步提升了模型的性能。 首先，其使用了大规模且公开的指令微调数据集对模型进行有监督的微调。 然后，LLaMA2 还训练了RLHF 奖励模型，并基于近似策略优化（Proximal Policy Optimization, PPO）以及拒绝采样（Rejection Sampling）进行强化学习对模型进行更新。 LLaMA3 模型\r在模型架构上，LLaMA3 与前一代 LLaMA2 几乎完全相同，只是在分词（tokenizer） 阶段，将字典长度扩大了三倍，极大提升了推理效率。 这一改进减少了中文字符等语言元素被拆分为多个 Token 的情况，有效降低了总体Token 数量，从而提高了模型处理语言的连贯性和准确性。 另一方面，扩大的字典有助于减少对具有完整意义的语义单元进行分割，使模型在处理文本时可以更准确的捕捉词义和上下文，提高生成文本的流畅性和连贯性。 ","date":"2025-08-22","objectID":"/posts/%E5%A4%A7%E6%A8%A1%E5%9E%8B%E5%9F%BA%E7%A1%80~02.%E5%A4%A7%E8%AF%AD%E8%A8%80%E6%A8%A1%E5%9E%8B%E6%9E%B6%E6%9E%84/:5:2","tags":["大模型"],"title":"大模型大模型基础~02.大语言模型架构","uri":"/posts/%E5%A4%A7%E6%A8%A1%E5%9E%8B%E5%9F%BA%E7%A1%80~02.%E5%A4%A7%E8%AF%AD%E8%A8%80%E6%A8%A1%E5%9E%8B%E6%9E%B6%E6%9E%84/#llama3-模型"},{"categories":null,"collections":["大模型基础"],"content":"第 6 节 非Transformer 模型\rTransformer 并非完美，其并行输入的机制会导致模型规模随输入序列长度平方增长，导致其在处理长序列时面临计算瓶颈。 RNN 在生成输出时，只考虑之前的隐藏状态和当前输入，理论上可以处理无限长的序列。传统的RNN 模型（如GRU、LSTM 等）在处理长序列时可能难以捕捉到长期依赖关系，且面临着梯度消失或爆炸问题。 为了克服这些问题，近年来，研究者提出了两类现代 RNN 变体，分别为状态空间模型（State Space Model，SSM）和测试时训练（Test-Time Training，TTT）。 ","date":"2025-08-22","objectID":"/posts/%E5%A4%A7%E6%A8%A1%E5%9E%8B%E5%9F%BA%E7%A1%80~02.%E5%A4%A7%E8%AF%AD%E8%A8%80%E6%A8%A1%E5%9E%8B%E6%9E%B6%E6%9E%84/:6:0","tags":["大模型"],"title":"大模型大模型基础~02.大语言模型架构","uri":"/posts/%E5%A4%A7%E6%A8%A1%E5%9E%8B%E5%9F%BA%E7%A1%80~02.%E5%A4%A7%E8%AF%AD%E8%A8%80%E6%A8%A1%E5%9E%8B%E6%9E%B6%E6%9E%84/#非transformer-模型"},{"categories":null,"collections":["大模型基础"],"content":"6.1 状态空间模型SSM\r思想源自于控制理论中的动力系统： 其中，$u(t)$表示状态输入，$x(t)$表示状态变量，$y(t)$表示输出，$A$是状态矩阵，$B$是控制矩阵，$C$是输出矩阵，$D$是命令矩阵，有如下的系统方程： $$ \\begin{aligned} x^{\\prime}(t) \u0026 =\\mathbf{A} x(t)+\\mathbf{B} u(t) \\ y(t) \u0026 =\\mathbf{C} x(t)+\\mathbf{D} u(t) \\end{aligned} $$ 这个系统方程有三种形式： 连续形式：就是上面这种包含导数的形式，求解需要用到积分。适用于对连续数据（例如音频信号、时间序列）的处理，计算的结果准确，但是在训练和推理都非常慢。 递归形式：使用梯形法代替连续形式中的积分操作，可以得出离散化后递归形式下的系统方程，递归形式的SSM 类似于RNN，能够实现与序列长度呈线性复杂度的高效推理，但是无法并行训练，有梯度消失或爆炸问题。 $$ \\begin{aligned} x_k \u0026 =\\overline{\\mathbf{A}} x_{k-1}+\\overline{\\mathbf{B}} u_k \\ y_k \u0026 =\\overline{\\mathbf{C}} x_k \\end{aligned} $$ 卷积形式：将系统方程的递归形式进行迭代，可以得到卷积形式。卷积核是由SSM 中的矩阵参数决定的，由于这些参数在整个序列的处理过程中是固定的，被称为时不变性。时不变性使得SSM 能够一致地处理不同时间步长的数据，进行高效的并行化训练。但由于上下文长度固定，卷积形式的 SSM 在进行自回归任务时延迟长且计算消耗大。 $$ y_k=\\overline{\\mathbf{K}}_k * u_k $$ 结合离散化后 SSM 的递归形式和卷积形式的优缺点，可以选择在训练时使用卷积形式，推理时使用递归形式。 SSM 通过将上下文信息压缩到固定长度的隐藏状态中，成功将计算复杂度降低至线性级别，有效扩展了模型处理长上下文的能力。然而，随着上下文长度的持续增长，基于SSM 范式的模型可能会过早出现性能饱和。 ","date":"2025-08-22","objectID":"/posts/%E5%A4%A7%E6%A8%A1%E5%9E%8B%E5%9F%BA%E7%A1%80~02.%E5%A4%A7%E8%AF%AD%E8%A8%80%E6%A8%A1%E5%9E%8B%E6%9E%B6%E6%9E%84/:6:1","tags":["大模型"],"title":"大模型大模型基础~02.大语言模型架构","uri":"/posts/%E5%A4%A7%E6%A8%A1%E5%9E%8B%E5%9F%BA%E7%A1%80~02.%E5%A4%A7%E8%AF%AD%E8%A8%80%E6%A8%A1%E5%9E%8B%E6%9E%B6%E6%9E%84/#状态空间模型ssm"},{"categories":null,"collections":["大模型基础"],"content":"6.2 训练时更新TTT\rTTT 利用模型本身的参数来存储隐藏状态、记忆上文，在每一步推理中，对模型参数进行梯度更新，已实现上文的不断循环流入。 在预训练阶段，训练过程包含内部循环以及外部循环两个部分。 外部循环遵循传统的下词预测任务，通过自回归方式优化模型全局权重参数。 内部循环则是基于自监督的方式来优化隐藏状态。具体来说，模型需要在每个时间步动态地更新隐藏状态，使其能够不断适应新的输入数据。 ","date":"2025-08-22","objectID":"/posts/%E5%A4%A7%E6%A8%A1%E5%9E%8B%E5%9F%BA%E7%A1%80~02.%E5%A4%A7%E8%AF%AD%E8%A8%80%E6%A8%A1%E5%9E%8B%E6%9E%B6%E6%9E%84/:6:2","tags":["大模型"],"title":"大模型大模型基础~02.大语言模型架构","uri":"/posts/%E5%A4%A7%E6%A8%A1%E5%9E%8B%E5%9F%BA%E7%A1%80~02.%E5%A4%A7%E8%AF%AD%E8%A8%80%E6%A8%A1%E5%9E%8B%E6%9E%B6%E6%9E%84/#训练时更新ttt"},{"categories":[],"collections":["大模型基础"],"content":"第 1 节 概述\r本合集是关于大数据基础这本书的，这本书是一个 Github 项目 ，访问该项目即可获取其最新版的电子书。 本人在 F5AI 上也上传了这本书的，通过链接可以访问。 本人阅读这本书的过程中，发现这本书写的非常不错： 首先，大模型是一个比较新的知识，所以一些成体系介绍大模型的书还比较少。这本书讲述的内容比较前沿、成体系并且有一定的深度，是当下比较难得的。 然后，这本书讲述的内容非常的浅显易懂，即便没有什么基础的人也能看下去。但这本书讲的知识其实是有一定的难度的，这本书的作者们很认真的调整了书本的框架结构和行文逻辑，并且配了许多高质量的图片，让复杂的知识读起来没有什么瓶颈，能够很顺畅的越读下去。 最后，这本书讲的非常的详细，阅读的过程中，能感受到书本上的很多内容有一定的重复性，但是这些重复是非常重要的，这些重复可以将跨度比较大的知识点连接起来，自然而然的让读者在阅读中形成了比较深度的思考，并且每个章节都会在行文中自然带出一些需要用到的前置知识点，所以即便单独学习其中的某一个章节，也能很顺畅的进行，不会感觉无从下手。 信息\r读这本书的时候，本人体会到了一读就停不下来的感觉，知识点衔接非常的顺滑，一章接着一章毫无瓶颈。如果国内其他教科书的编写都能有这样的认真程度就好了，也不会被称作是“反自学”教材了。 后面的章节是本人阅读这本书后做的笔记，受限于篇幅只提取了原书中一些比较关键的内容，若是要对大模型有更加全面的认识，还是阅读原书会更好一些。 ","date":"2025-08-22","objectID":"/posts/%E5%A4%A7%E6%A8%A1%E5%9E%8B%E5%9F%BA%E7%A1%80~01.%E6%A6%82%E8%BF%B0/:1:0","tags":["大模型"],"title":"大模型大模型基础~01.概述","uri":"/posts/%E5%A4%A7%E6%A8%A1%E5%9E%8B%E5%9F%BA%E7%A1%80~01.%E6%A6%82%E8%BF%B0/#概述"},{"categories":"玩转NAS","collections":["玩转NAS"],"content":"第 13 节 概述\r由于公网 IP 的获取比较困难，本人的 NAS 上的各种 Docker 应用都是通过路由器的公网 IPV6 地址进行代理的，使用的是在 OpenWrt 上部署到一款名为 Lucky 的应用，Github项目地址， 这款应用聚合了端口转发、动态域名、自动证书等功能，一个应用就能解决将服务部署到公网的全部流程。 因为 Lucky 集成了 socat 的功能，可以进行 IPV6 到 IPV4 的端口转发，所以通常 NAS 上的应用只要能通过 IPV4 地址访问到就可以部署到公网。 但是这样会存在一个问题： 信息\rDocker 默认情况下不会配置 IPV6 功能，因而在 Docker 容器内无法通过域名（IPV6）去访问别的容器，只能使用本地的 IPV4 地址来访问。这样不方便，并且有的应用需要SSL或者设置了BaseURL，直接用 IPV4 地址访问可能会有问题。 不过，一直以来，本人并不知道 Docker 默认没有 IPV6 ，主要原因如下： 我在 OpenWrt 上安装了Clash，而 Clash 为了保证访问的私密性，配置了 FakeIP 功能。 我没有设置 FakeIP Filter，所以即便是本地网络之间的访问，地址也会被转成 FakeIP。 访问的 IPV6 地址也会被转成 FakeIP，并且 FakeIP 是保留 IP 段的 IPV4 地址。 路由器负责解析 FakeIP，并且 Lucky 将解析后的结果代理到了对应的 IPV4 地址。 所以，阴差阳错之下，本人一直在容器内正常使用着 IPV6 功能。 不过，也许是因为 Clash 时常可能访问不了，或者别的原因，容器内访问 IPV6 时常也会访问不了。并且用这种方式会增加通信之间的消耗，增加网络的复杂性，容易出现各种未知的问题，所以只好想办法来解决这一问题。 查看 OpenWrt 上的路由器网络，WAN6 IP 地址的掩码是 64 位的，这说明运营商只给我分配了这一个 IPV6 公网地址，我不可以通过前缀委派（Prefix Delegation,PD）给其他内网设备分配公网 IPV6 地址，所以从路由器开始，我的后续所有的设备的 IPV6 地址都只能通过网络地址转换（Network Address Translation,NAT）来分配。 本人的 Docker 容器基本全都是桥接的，于是解决这个问题有 3 个步骤： Nas 本身要配置 IPV6 Docker 的虚拟网口和 NAS 之间要配置 NAT Docker 容器内要配置 IPV6 并且和 Docker 网口之间配置 NAT 在下文中，本人将依次讲述这些如何配置。 ","date":"2025-08-08","objectID":"/posts/%E5%9C%A8-docker-%E4%B8%AD%E9%85%8D%E7%BD%AE-ipv6-nat/:1:0","tags":["网络"],"title":"在 Docker 中配置 IPV6 NAT","uri":"/posts/%E5%9C%A8-docker-%E4%B8%AD%E9%85%8D%E7%BD%AE-ipv6-nat/#概述"},{"categories":"玩转NAS","collections":["玩转NAS"],"content":"第 14 节 路由器网络\r本人使用的是威联通的 NAS，登录 NAS 系统，来到控制台-\u003e网络与虚拟交换机-\u003e网络适配器，配置 IPV6 如下： ","date":"2025-08-08","objectID":"/posts/%E5%9C%A8-docker-%E4%B8%AD%E9%85%8D%E7%BD%AE-ipv6-nat/:2:0","tags":["网络"],"title":"在 Docker 中配置 IPV6 NAT","uri":"/posts/%E5%9C%A8-docker-%E4%B8%AD%E9%85%8D%E7%BD%AE-ipv6-nat/#路由器网络"},{"categories":"玩转NAS","collections":["玩转NAS"],"content":"第 15 节 NAS 网络\r通过 ssh 连接到 NAS，通过测试，威联通的 NAS 安装了iptables和ip6talbes，但是没有安装ip6tables_nat，所以不可以进行 IPV6 的 NAT。 Github 上有人编译了威联通的 ip6tables_nat 包，项目地址。去 release 里头下载modules.zip并解压得到ip6table_nat.ko即可。 通过 ssh 连接到 NAS，执行以下命令，将对应目录挂载到/tmp/config，具体参照网址： sudo -i mount $(/sbin/hal_app --get_boot_pd port_id=0)6 /tmp/config 在目录下新建文件autorun.sh，填入以下内容 /sbin/modprobe ip6_tables /sbin/modprobe nf_nat /sbin/modprobe xt_MASQUERADE insmod /「PATH」/ip6table_nat.ko 将文件修改为可执行，并解除挂载： chmod +x /tmp/config/autorun.sh umount /tmp/config 登录 NAS 系统，来到控制台-\u003e硬件-\u003e常规，对应选项上打勾： 重启 NAS 或者手动执行autorun.sh中的内容。 在 Docker 中创建支持 IPV6 的网络： docker network create \\ --ipv6 \\ --subnet=xxxx:xxxx:xxxx::/64 \\ --gateway=xxxx:xxxx:xxxx::1 \\ --subnet=xx.xx.xx.xx/24 \\ --gateway=xx.xx.xx.1 \\ --opt com.docker.network.bridge.name=ipv6 \\ ipv6 NAT分为 SNAT 和 DNAT 两种： DNAT 修改目标 IP 地址，主要用来进行端口转发，将公网IP端口映射到内网IP端口 SNAT 修改源 IP 地址，用来解决 IP 地址不足的问题，将内网IP端口修改为公网IP端口 这里要实现的目标不是通过内网的 IPV6 地址访问内网的服务，因为 Docker 原本就有 IPV4 的端口转发，更加简单好记。 所以，只考虑配置 SNAT，使得容器内访问 IPV6 地址，修改 IP 端口，变为 NAS 访问 IPV6 地址，进而交由路由器来处理，方法如下： sudo ip6tables -A FORWARD -i ipv6 -o eth0 -j ACCEPT sudo ip6tables -A FORWARD -i eth0 -o ipv6 -j ACCEPT sudo ip6tables -t nat -A POSTROUTING -o eth0 -j MASQUERADE ","date":"2025-08-08","objectID":"/posts/%E5%9C%A8-docker-%E4%B8%AD%E9%85%8D%E7%BD%AE-ipv6-nat/:3:0","tags":["网络"],"title":"在 Docker 中配置 IPV6 NAT","uri":"/posts/%E5%9C%A8-docker-%E4%B8%AD%E9%85%8D%E7%BD%AE-ipv6-nat/#nas-网络"},{"categories":"玩转NAS","collections":["玩转NAS"],"content":"第 16 节 容器内部\rDocker 在创建容器时，一般就自动创建好了容器内的防火墙和NAT规则，规则也比较简单，通常不需要进行任何设置就可以访问 NAS。 一个比较特殊的容器是 WireGuard，其内部可能存在多个网口，比如wg0、wg1、wg2等，需要配置相关的 NAT 规则，这种方式的目的是隔离多个网口，并且每个网口分别做防火墙，提供给不同的人使用。 Docker 部署方式如下： docker run -itd --name=WireGuard --restart always \\ --network ipv6 \\ --cap-add=NET_ADMIN \\ -e PUID=「PUID」 \\ -e PGID=「PGID」 \\ -e TZ=Asia/Shanghai \\ -e SERVERURL=「URL」 \\ -e SERVERPORT=「POST」 \\ -e PEERS=3 \\ -e PEERDNS=「DNS」 \\ -e INTERNAL_SUBNET=xx.xx.xx.0 \\ -e INTERNAL_SUBNET6=xxxx:xxxx:xxxx:: \\ -e ALLOWEDIPS=0.0.0.0/0,::/0 \\ -e PERSISTENTKEEPALIVE_PEERS=25 \\ -e LOG_CONFS=true \\ -p 51820:51820/udp \\ -p 51821:51821/udp \\ -p 51822:51822/udp \\ -v /「PATH」/config:/config \\ --sysctl=\"net.ipv4.conf.all.src_valid_mark=1\" \\ --sysctl=\"net.ipv6.conf.all.disable_ipv6=0\" \\ --sysctl=\"net.ipv6.conf.all.forwarding=1\" \\ --sysctl=\"net.ipv6.conf.default.forwarding=1\" \\ linuxserver/wireguard:latest 需要在容器内使用 IPV6 时，需要添加的有： --sysctl=\"net.ipv6.conf.all.disable_ipv6=0\" \\ --sysctl=\"net.ipv6.conf.all.forwarding=1\" \\ --sysctl=\"net.ipv6.conf.default.forwarding=1\" \\ 其他项不是必备的配置，可以创建以后手动进行修改。 服务器端的配置如下： [Interface] Address = xx.xx.xx.1/24, xxxx:xxxx:xxxx::1/64 ListenPort = 51820 PrivateKey = xxxxx PostUp = iptables -A FORWARD -i %i -j ACCEPT; iptables -A FORWARD -o %i -j ACCEPT; iptables -t nat -A POSTROUTING -o eth0 -j MASQUERADE; ip6tables -A FORWARD -i %i -j ACCEPT; ip6tables -A FORWARD -o %i -j ACCEPT; ip6tables -t nat -A POSTROUTING -o eth0 -j MASQUERADE PostDown = iptables -D FORWARD -i %i -j ACCEPT; iptables -D FORWARD -o %i -j ACCEPT; iptables -t nat -D POSTROUTING -o eth0 -j MASQUERADE; ip6tables -D FORWARD -i %i -j ACCEPT; ip6tables -D FORWARD -o %i -j ACCEPT; ip6tables -t nat -D POSTROUTING -o eth0 -j MASQUERADE [Peer] PublicKey = xxxxx PresharedKey = xxxxx AllowedIPs = xx.xx.xx.2/32, xxxx:xxxx:xxxx::2/128 ","date":"2025-08-08","objectID":"/posts/%E5%9C%A8-docker-%E4%B8%AD%E9%85%8D%E7%BD%AE-ipv6-nat/:4:0","tags":["网络"],"title":"在 Docker 中配置 IPV6 NAT","uri":"/posts/%E5%9C%A8-docker-%E4%B8%AD%E9%85%8D%E7%BD%AE-ipv6-nat/#容器内部"},{"categories":"玩转NAS","collections":["玩转NAS"],"content":"第 13 节 概述\r现在，大家家中或多或少都会有一些有「智能家居」功能的电器，这些电器能够连网，并且配置好之后，可以使用专用的 App 远程控制。 示例\r比如，在回家的路上，大约还有 10 多分钟到家，就可以远程控制家中的空调，提前打开。 当这些各式各样的电器多了以后，难免会遇到一些问题。主要的困扰在于，需要安装各式各样的 App ，这一过程十分繁杂。为了解决这一困扰，市场上出现了一些智能家居产品的集成商，比较知名的就是米家、天猫精灵和小度，只要购买与他们自家生产或者有合作关系的第一方产品，或者适配了他们平台的第三方产品，就可以使用他们提供的 App ，统一适配各种产品。 但是，这需要我们在购买之初就做好一些功课，选择可以适配的产品，这样会减少我们可以选择的余地。并且，可能我们已经购买了多个品牌的产品，并且近期并不需要更换，所以还是被迫要安装很多的 App。或者，也可能像本人这样，房子是租的，能用什么样的智能家居产品，取决于房东的选择。 那么，如果我们手头上已经有了一堆各式各样的产品，我们想要使用一个 App 去集中控制它们，并且我们希望各个产品之间可以形成联动，可以按照设定的逻辑自动开启、关闭和执行任务，那么配置大名鼎鼎的 Home Assistant 就是一个不错的选择。 ","date":"2025-08-06","objectID":"/posts/homeassistant%E6%89%93%E9%80%A0%E6%99%BA%E8%83%BD%E5%AE%B6%E5%B1%85/:1:0","tags":["可视化"],"title":"HomeAssistant，打造智能家居","uri":"/posts/homeassistant%E6%89%93%E9%80%A0%E6%99%BA%E8%83%BD%E5%AE%B6%E5%B1%85/#概述"},{"categories":"玩转NAS","collections":["玩转NAS"],"content":"第 14 节 安装\r","date":"2025-08-06","objectID":"/posts/homeassistant%E6%89%93%E9%80%A0%E6%99%BA%E8%83%BD%E5%AE%B6%E5%B1%85/:2:0","tags":["可视化"],"title":"HomeAssistant，打造智能家居","uri":"/posts/homeassistant%E6%89%93%E9%80%A0%E6%99%BA%E8%83%BD%E5%AE%B6%E5%B1%85/#安装"},{"categories":"玩转NAS","collections":["玩转NAS"],"content":"14.1 安装 Home Assistant\r使用 Docker 进行安装，方法如下： docker run -itd --name HomeAssistant --restart=always \\ --privileged \\ -e TZ=Asia/Shanghai \\ -v /「PATH」:/config \\ -p 8123:8123 \\ ghcr.io/home-assistant/home-assistant:latest 信息\r官网是推荐使用 Host 模式的，这种方式安装使用会比较顺利。我这里使用的桥接，感觉影响不大。 ","date":"2025-08-06","objectID":"/posts/homeassistant%E6%89%93%E9%80%A0%E6%99%BA%E8%83%BD%E5%AE%B6%E5%B1%85/:2:1","tags":["可视化"],"title":"HomeAssistant，打造智能家居","uri":"/posts/homeassistant%E6%89%93%E9%80%A0%E6%99%BA%E8%83%BD%E5%AE%B6%E5%B1%85/#安装-home-assistant"},{"categories":"玩转NAS","collections":["玩转NAS"],"content":"14.2 安装 HACS\rHACS 全称是 Home Assistant Community Store，也就是 Home Assistant 的社区商店，安装之后安装其他插件就会轻松许多。 安装方式也很简单，首先进入 Home Assistant 的容器中： docker exec -it xxxxx sh 然后执行下面这行命令： wget -O - https://get.hacs.xyz | bash - 命令执行完毕，就会自动将文件下载并解压到对应位置。 ","date":"2025-08-06","objectID":"/posts/homeassistant%E6%89%93%E9%80%A0%E6%99%BA%E8%83%BD%E5%AE%B6%E5%B1%85/:2:2","tags":["可视化"],"title":"HomeAssistant，打造智能家居","uri":"/posts/homeassistant%E6%89%93%E9%80%A0%E6%99%BA%E8%83%BD%E5%AE%B6%E5%B1%85/#安装-hacs"},{"categories":"玩转NAS","collections":["玩转NAS"],"content":"14.3 启用 HACS\r首先，先重启一下容器，会重新加载一遍新增的内容。 点击设置-\u003e设备与服务-\u003e添加集成-\u003e输入HACS-\u003e点击结果 进行相应的配置后就启用了 HACS ","date":"2025-08-06","objectID":"/posts/homeassistant%E6%89%93%E9%80%A0%E6%99%BA%E8%83%BD%E5%AE%B6%E5%B1%85/:2:3","tags":["可视化"],"title":"HomeAssistant，打造智能家居","uri":"/posts/homeassistant%E6%89%93%E9%80%A0%E6%99%BA%E8%83%BD%E5%AE%B6%E5%B1%85/#启用-hacs"},{"categories":"玩转NAS","collections":["玩转NAS"],"content":"第 15 节 看板配置\r简单来说，我们使用 Home Assistant 所要实现的效果，就是制作一定数量的看板，并且按照一定的排版展示出来。 这些看板有一些用来展示家中各类电器的状态，一些用来控制家中各类电器的启停。比如下面这个就是一个看板，用来展示家中 Nas 设备的使用详情信息。 ","date":"2025-08-06","objectID":"/posts/homeassistant%E6%89%93%E9%80%A0%E6%99%BA%E8%83%BD%E5%AE%B6%E5%B1%85/:3:0","tags":["可视化"],"title":"HomeAssistant，打造智能家居","uri":"/posts/homeassistant%E6%89%93%E9%80%A0%E6%99%BA%E8%83%BD%E5%AE%B6%E5%B1%85/#看板配置"},{"categories":"玩转NAS","collections":["玩转NAS"],"content":"15.1 配置文件目录\r为了讲清楚看板如何制作，先讲一下 Home Assistant 配置文件的目录详情： 我们使用 Docker 安装了 Home Assistant，并且将/config目录映射到了外部。Home Assistant 的主要配置文件都在这个/config目录下，有「三个目录」和「一个文件」比较重要： custom_components：集成组件，之前安装的 HACS 就在其中，可以理解成是「后端」，用来向前端页面提供数据，执行命令。起到适配器作用，负责和电器通信，控制电器。 themes：主题，可以理解成是「前端样式」，配置好以后，看板的风格就确定下来了。 www：卡片，可以理解成是「前端结构」，配置好以后，看板的布局就确定下来了。 configuration.yaml：主要的配置文件。 ","date":"2025-08-06","objectID":"/posts/homeassistant%E6%89%93%E9%80%A0%E6%99%BA%E8%83%BD%E5%AE%B6%E5%B1%85/:3:1","tags":["可视化"],"title":"HomeAssistant，打造智能家居","uri":"/posts/homeassistant%E6%89%93%E9%80%A0%E6%99%BA%E8%83%BD%E5%AE%B6%E5%B1%85/#配置文件目录"},{"categories":"玩转NAS","collections":["玩转NAS"],"content":"15.2 常见组件\r配置集成组件后就可以连接到对应的电器了，在 HACS 中下载： Xiaomi Home：连接支持米家的设备，可以把米家中的设备导入到 Home Assistant 中。 Midea AC Lan：连接美的设备。 天气预报：可以获取最近一段时间某个地区的天气情况。 ","date":"2025-08-06","objectID":"/posts/homeassistant%E6%89%93%E9%80%A0%E6%99%BA%E8%83%BD%E5%AE%B6%E5%B1%85/:3:2","tags":["可视化"],"title":"HomeAssistant，打造智能家居","uri":"/posts/homeassistant%E6%89%93%E9%80%A0%E6%99%BA%E8%83%BD%E5%AE%B6%E5%B1%85/#常见组件"},{"categories":"玩转NAS","collections":["玩转NAS"],"content":"15.3 常见卡片\r在 HACS 中搜索对应卡片下载即可： Mushroom：内置了很多美观的卡片，基本上是人手必备的卡片。 apexcharts-card：绘图卡片，将数据展示为图表。 mini-graph-card：另外一个绘图卡片，将数据展示为图表。 Tabbed Card：很方便生成 Tab 页，一块空间中可以很方便堆放多个相似的卡片。 Colorfulclouds Weather Card：彩云卡片，专门用来展示天气情况的，和天气预报配合使用。 ","date":"2025-08-06","objectID":"/posts/homeassistant%E6%89%93%E9%80%A0%E6%99%BA%E8%83%BD%E5%AE%B6%E5%B1%85/:3:3","tags":["可视化"],"title":"HomeAssistant，打造智能家居","uri":"/posts/homeassistant%E6%89%93%E9%80%A0%E6%99%BA%E8%83%BD%E5%AE%B6%E5%B1%85/#常见卡片"},{"categories":"玩转NAS","collections":["玩转NAS"],"content":"15.4 主题\r我用的这款主题叫做 visionOS Theme，在 HACS 中搜索对应卡片下载即可。 ","date":"2025-08-06","objectID":"/posts/homeassistant%E6%89%93%E9%80%A0%E6%99%BA%E8%83%BD%E5%AE%B6%E5%B1%85/:3:4","tags":["可视化"],"title":"HomeAssistant，打造智能家居","uri":"/posts/homeassistant%E6%89%93%E9%80%A0%E6%99%BA%E8%83%BD%E5%AE%B6%E5%B1%85/#主题"},{"categories":"玩转NAS","collections":["玩转NAS"],"content":"15.5 仪表盘\r新增仪表盘\r点击设置-\u003e仪表板-\u003e添加仪表板-\u003e从新建仪表盘开始 其他几个选项用到的不多，默认选择新建仪表盘就可以了。 创建视图\r一个仪表盘可以创建多个视图，如下： 点击「箭头」可以左右移动视图的位置，点击「加号」新增视图，「铅笔」图标可以编辑视图。 编辑视图\r点击「编辑视图」，如下： 布局可以选择四种，部件、瀑布流、侧边栏、面板，选择前两种就可以。 如果需要显示的内容比较多，需要分列显示，就选择「部件」 如果需要显示的内容较少，一列就可以了，就选择「瀑布流」 其他的配置很多都可以使用默认的值，稍微修改并对比一下，很快就能理解。 新增卡片\r「部件」布局的时候，点击页面上的「加号」来新增卡片 「瀑布流」布局的时候，点击右下角的「添加卡片」来新增卡片 大部分的卡片可以通过图形化的界面进行配置，卡片的配置过程就是将后端数据和前端模板连接到过程。配好以后，卡片中原本设定要展示数据的区域就会展示你指定的数据。 比如这个空调卡片： 在本人指定好「实体」（组件连接到电器以后，就会生成一个或者多个实体）以后，就基本配置完成了。 点击左下角的「显示代码编辑器」，可以看到如下的yaml格式的文字： type: thermostat entity: climate.xxxxx name: 卧室空调 features: - type: climate-hvac-modes 有一些复杂一些的界面，只能使用yaml进行配置，不提供图形化界面。 比如之前展示的 Nas 详情信息： type: custom:tabbed-card styles: \"--mdc-theme-primary\": white \"--mdc-tab-text-label-color-default\": orange options: null tabs: - card: type: custom:apexcharts-card chart_type: radialBar header: show: true show_states: true colorize_states: true all_series_config: min: 0 max: 100 series: - entity: sensor.cpu_usage name: CPU 使用率 - entity: sensor.memory_percent name: 内存使用率 - entity: sensor.disk_percent name: 硬盘使用率 attributes: label: 使用详情 - card: type: custom:apexcharts-card chart_type: donut header: show: true show_states: true colorize_states: true series: - entity: sensor.memory_used name: 已用内存 unit: GB - entity: sensor.memory_available name: 可用内存 unit: GB attributes: label: 内存详情 - card: type: custom:apexcharts-card chart_type: donut header: show: true show_states: true colorize_states: true series: - entity: sensor.disk_used name: 已用存储 unit: TB - entity: sensor.disk_available name: 可用存储 unit: TB attributes: label: 硬盘详情 ","date":"2025-08-06","objectID":"/posts/homeassistant%E6%89%93%E9%80%A0%E6%99%BA%E8%83%BD%E5%AE%B6%E5%B1%85/:3:5","tags":["可视化"],"title":"HomeAssistant，打造智能家居","uri":"/posts/homeassistant%E6%89%93%E9%80%A0%E6%99%BA%E8%83%BD%E5%AE%B6%E5%B1%85/#仪表盘"},{"categories":"玩转NAS","collections":["玩转NAS"],"content":"15.5 仪表盘\r新增仪表盘\r点击设置-\u003e仪表板-\u003e添加仪表板-\u003e从新建仪表盘开始 其他几个选项用到的不多，默认选择新建仪表盘就可以了。 创建视图\r一个仪表盘可以创建多个视图，如下： 点击「箭头」可以左右移动视图的位置，点击「加号」新增视图，「铅笔」图标可以编辑视图。 编辑视图\r点击「编辑视图」，如下： 布局可以选择四种，部件、瀑布流、侧边栏、面板，选择前两种就可以。 如果需要显示的内容比较多，需要分列显示，就选择「部件」 如果需要显示的内容较少，一列就可以了，就选择「瀑布流」 其他的配置很多都可以使用默认的值，稍微修改并对比一下，很快就能理解。 新增卡片\r「部件」布局的时候，点击页面上的「加号」来新增卡片 「瀑布流」布局的时候，点击右下角的「添加卡片」来新增卡片 大部分的卡片可以通过图形化的界面进行配置，卡片的配置过程就是将后端数据和前端模板连接到过程。配好以后，卡片中原本设定要展示数据的区域就会展示你指定的数据。 比如这个空调卡片： 在本人指定好「实体」（组件连接到电器以后，就会生成一个或者多个实体）以后，就基本配置完成了。 点击左下角的「显示代码编辑器」，可以看到如下的yaml格式的文字： type: thermostat entity: climate.xxxxx name: 卧室空调 features: - type: climate-hvac-modes 有一些复杂一些的界面，只能使用yaml进行配置，不提供图形化界面。 比如之前展示的 Nas 详情信息： type: custom:tabbed-card styles: \"--mdc-theme-primary\": white \"--mdc-tab-text-label-color-default\": orange options: null tabs: - card: type: custom:apexcharts-card chart_type: radialBar header: show: true show_states: true colorize_states: true all_series_config: min: 0 max: 100 series: - entity: sensor.cpu_usage name: CPU 使用率 - entity: sensor.memory_percent name: 内存使用率 - entity: sensor.disk_percent name: 硬盘使用率 attributes: label: 使用详情 - card: type: custom:apexcharts-card chart_type: donut header: show: true show_states: true colorize_states: true series: - entity: sensor.memory_used name: 已用内存 unit: GB - entity: sensor.memory_available name: 可用内存 unit: GB attributes: label: 内存详情 - card: type: custom:apexcharts-card chart_type: donut header: show: true show_states: true colorize_states: true series: - entity: sensor.disk_used name: 已用存储 unit: TB - entity: sensor.disk_available name: 可用存储 unit: TB attributes: label: 硬盘详情 ","date":"2025-08-06","objectID":"/posts/homeassistant%E6%89%93%E9%80%A0%E6%99%BA%E8%83%BD%E5%AE%B6%E5%B1%85/:3:5","tags":["可视化"],"title":"HomeAssistant，打造智能家居","uri":"/posts/homeassistant%E6%89%93%E9%80%A0%E6%99%BA%E8%83%BD%E5%AE%B6%E5%B1%85/#新增仪表盘"},{"categories":"玩转NAS","collections":["玩转NAS"],"content":"15.5 仪表盘\r新增仪表盘\r点击设置-\u003e仪表板-\u003e添加仪表板-\u003e从新建仪表盘开始 其他几个选项用到的不多，默认选择新建仪表盘就可以了。 创建视图\r一个仪表盘可以创建多个视图，如下： 点击「箭头」可以左右移动视图的位置，点击「加号」新增视图，「铅笔」图标可以编辑视图。 编辑视图\r点击「编辑视图」，如下： 布局可以选择四种，部件、瀑布流、侧边栏、面板，选择前两种就可以。 如果需要显示的内容比较多，需要分列显示，就选择「部件」 如果需要显示的内容较少，一列就可以了，就选择「瀑布流」 其他的配置很多都可以使用默认的值，稍微修改并对比一下，很快就能理解。 新增卡片\r「部件」布局的时候，点击页面上的「加号」来新增卡片 「瀑布流」布局的时候，点击右下角的「添加卡片」来新增卡片 大部分的卡片可以通过图形化的界面进行配置，卡片的配置过程就是将后端数据和前端模板连接到过程。配好以后，卡片中原本设定要展示数据的区域就会展示你指定的数据。 比如这个空调卡片： 在本人指定好「实体」（组件连接到电器以后，就会生成一个或者多个实体）以后，就基本配置完成了。 点击左下角的「显示代码编辑器」，可以看到如下的yaml格式的文字： type: thermostat entity: climate.xxxxx name: 卧室空调 features: - type: climate-hvac-modes 有一些复杂一些的界面，只能使用yaml进行配置，不提供图形化界面。 比如之前展示的 Nas 详情信息： type: custom:tabbed-card styles: \"--mdc-theme-primary\": white \"--mdc-tab-text-label-color-default\": orange options: null tabs: - card: type: custom:apexcharts-card chart_type: radialBar header: show: true show_states: true colorize_states: true all_series_config: min: 0 max: 100 series: - entity: sensor.cpu_usage name: CPU 使用率 - entity: sensor.memory_percent name: 内存使用率 - entity: sensor.disk_percent name: 硬盘使用率 attributes: label: 使用详情 - card: type: custom:apexcharts-card chart_type: donut header: show: true show_states: true colorize_states: true series: - entity: sensor.memory_used name: 已用内存 unit: GB - entity: sensor.memory_available name: 可用内存 unit: GB attributes: label: 内存详情 - card: type: custom:apexcharts-card chart_type: donut header: show: true show_states: true colorize_states: true series: - entity: sensor.disk_used name: 已用存储 unit: TB - entity: sensor.disk_available name: 可用存储 unit: TB attributes: label: 硬盘详情 ","date":"2025-08-06","objectID":"/posts/homeassistant%E6%89%93%E9%80%A0%E6%99%BA%E8%83%BD%E5%AE%B6%E5%B1%85/:3:5","tags":["可视化"],"title":"HomeAssistant，打造智能家居","uri":"/posts/homeassistant%E6%89%93%E9%80%A0%E6%99%BA%E8%83%BD%E5%AE%B6%E5%B1%85/#创建视图"},{"categories":"玩转NAS","collections":["玩转NAS"],"content":"15.5 仪表盘\r新增仪表盘\r点击设置-\u003e仪表板-\u003e添加仪表板-\u003e从新建仪表盘开始 其他几个选项用到的不多，默认选择新建仪表盘就可以了。 创建视图\r一个仪表盘可以创建多个视图，如下： 点击「箭头」可以左右移动视图的位置，点击「加号」新增视图，「铅笔」图标可以编辑视图。 编辑视图\r点击「编辑视图」，如下： 布局可以选择四种，部件、瀑布流、侧边栏、面板，选择前两种就可以。 如果需要显示的内容比较多，需要分列显示，就选择「部件」 如果需要显示的内容较少，一列就可以了，就选择「瀑布流」 其他的配置很多都可以使用默认的值，稍微修改并对比一下，很快就能理解。 新增卡片\r「部件」布局的时候，点击页面上的「加号」来新增卡片 「瀑布流」布局的时候，点击右下角的「添加卡片」来新增卡片 大部分的卡片可以通过图形化的界面进行配置，卡片的配置过程就是将后端数据和前端模板连接到过程。配好以后，卡片中原本设定要展示数据的区域就会展示你指定的数据。 比如这个空调卡片： 在本人指定好「实体」（组件连接到电器以后，就会生成一个或者多个实体）以后，就基本配置完成了。 点击左下角的「显示代码编辑器」，可以看到如下的yaml格式的文字： type: thermostat entity: climate.xxxxx name: 卧室空调 features: - type: climate-hvac-modes 有一些复杂一些的界面，只能使用yaml进行配置，不提供图形化界面。 比如之前展示的 Nas 详情信息： type: custom:tabbed-card styles: \"--mdc-theme-primary\": white \"--mdc-tab-text-label-color-default\": orange options: null tabs: - card: type: custom:apexcharts-card chart_type: radialBar header: show: true show_states: true colorize_states: true all_series_config: min: 0 max: 100 series: - entity: sensor.cpu_usage name: CPU 使用率 - entity: sensor.memory_percent name: 内存使用率 - entity: sensor.disk_percent name: 硬盘使用率 attributes: label: 使用详情 - card: type: custom:apexcharts-card chart_type: donut header: show: true show_states: true colorize_states: true series: - entity: sensor.memory_used name: 已用内存 unit: GB - entity: sensor.memory_available name: 可用内存 unit: GB attributes: label: 内存详情 - card: type: custom:apexcharts-card chart_type: donut header: show: true show_states: true colorize_states: true series: - entity: sensor.disk_used name: 已用存储 unit: TB - entity: sensor.disk_available name: 可用存储 unit: TB attributes: label: 硬盘详情 ","date":"2025-08-06","objectID":"/posts/homeassistant%E6%89%93%E9%80%A0%E6%99%BA%E8%83%BD%E5%AE%B6%E5%B1%85/:3:5","tags":["可视化"],"title":"HomeAssistant，打造智能家居","uri":"/posts/homeassistant%E6%89%93%E9%80%A0%E6%99%BA%E8%83%BD%E5%AE%B6%E5%B1%85/#编辑视图"},{"categories":"玩转NAS","collections":["玩转NAS"],"content":"15.5 仪表盘\r新增仪表盘\r点击设置-\u003e仪表板-\u003e添加仪表板-\u003e从新建仪表盘开始 其他几个选项用到的不多，默认选择新建仪表盘就可以了。 创建视图\r一个仪表盘可以创建多个视图，如下： 点击「箭头」可以左右移动视图的位置，点击「加号」新增视图，「铅笔」图标可以编辑视图。 编辑视图\r点击「编辑视图」，如下： 布局可以选择四种，部件、瀑布流、侧边栏、面板，选择前两种就可以。 如果需要显示的内容比较多，需要分列显示，就选择「部件」 如果需要显示的内容较少，一列就可以了，就选择「瀑布流」 其他的配置很多都可以使用默认的值，稍微修改并对比一下，很快就能理解。 新增卡片\r「部件」布局的时候，点击页面上的「加号」来新增卡片 「瀑布流」布局的时候，点击右下角的「添加卡片」来新增卡片 大部分的卡片可以通过图形化的界面进行配置，卡片的配置过程就是将后端数据和前端模板连接到过程。配好以后，卡片中原本设定要展示数据的区域就会展示你指定的数据。 比如这个空调卡片： 在本人指定好「实体」（组件连接到电器以后，就会生成一个或者多个实体）以后，就基本配置完成了。 点击左下角的「显示代码编辑器」，可以看到如下的yaml格式的文字： type: thermostat entity: climate.xxxxx name: 卧室空调 features: - type: climate-hvac-modes 有一些复杂一些的界面，只能使用yaml进行配置，不提供图形化界面。 比如之前展示的 Nas 详情信息： type: custom:tabbed-card styles: \"--mdc-theme-primary\": white \"--mdc-tab-text-label-color-default\": orange options: null tabs: - card: type: custom:apexcharts-card chart_type: radialBar header: show: true show_states: true colorize_states: true all_series_config: min: 0 max: 100 series: - entity: sensor.cpu_usage name: CPU 使用率 - entity: sensor.memory_percent name: 内存使用率 - entity: sensor.disk_percent name: 硬盘使用率 attributes: label: 使用详情 - card: type: custom:apexcharts-card chart_type: donut header: show: true show_states: true colorize_states: true series: - entity: sensor.memory_used name: 已用内存 unit: GB - entity: sensor.memory_available name: 可用内存 unit: GB attributes: label: 内存详情 - card: type: custom:apexcharts-card chart_type: donut header: show: true show_states: true colorize_states: true series: - entity: sensor.disk_used name: 已用存储 unit: TB - entity: sensor.disk_available name: 可用存储 unit: TB attributes: label: 硬盘详情 ","date":"2025-08-06","objectID":"/posts/homeassistant%E6%89%93%E9%80%A0%E6%99%BA%E8%83%BD%E5%AE%B6%E5%B1%85/:3:5","tags":["可视化"],"title":"HomeAssistant，打造智能家居","uri":"/posts/homeassistant%E6%89%93%E9%80%A0%E6%99%BA%E8%83%BD%E5%AE%B6%E5%B1%85/#新增卡片"},{"categories":"玩转NAS","collections":["玩转NAS"],"content":"第 16 节 自定义实体\r一般来说，组件连接到电器，就能够生成符合我们要求的实体。但是，如果我们想要在卡片上展示一部分我们自己获取到数据，就需要自己去编写相应的 API。 我使用的方法非常的简单，并且可以绕过 Home Assistant 自身的一些生态，非常直接的实现想要的效果。 ","date":"2025-08-06","objectID":"/posts/homeassistant%E6%89%93%E9%80%A0%E6%99%BA%E8%83%BD%E5%AE%B6%E5%B1%85/:4:0","tags":["可视化"],"title":"HomeAssistant，打造智能家居","uri":"/posts/homeassistant%E6%89%93%E9%80%A0%E6%99%BA%E8%83%BD%E5%AE%B6%E5%B1%85/#自定义实体"},{"categories":"玩转NAS","collections":["玩转NAS"],"content":"16.1 rest 传感器\rHome Assistant 提供了名为 Rest 的传感器，这种传感器的数据通过定期调用 Restful API 来获取。 在configuration.yml中添加如下内容： sensor: !include configuration/sensor.yaml 然后创建configuration/sensor.yaml文件，写入如下内容： - platform: rest name: nas_info resource: \"http://192.168.0.103:8000/nas_info/\" scan_interval: 60 json_attributes: - cpu - memory - disk 然后使用fastapi写一个 API 如下： from fastapi import APIRouter import psutil from typing import Dict, Any router = APIRouter(prefix=\"/nas_info\", tags=[\"nas_info\"], responses={}) @router.get(\"/\", response_model=Dict[str, Any]) async def nas_info(): cpu_info = {\"percent\": psutil.cpu_percent(interval=1)} mem = psutil.virtual_memory() memory_info = {\"percent\": mem.percent, \"available\": f\"{mem.available / (1024 ** 3):.2f}\", \"used\": f\"{mem.used / (1024 ** 3):.2f}\"} usage = psutil.disk_usage('/') disk_info = {\"percent\": usage.percent, \"used\": f\"{usage.used / (1024 ** 4):.2f}\", \"available\": f\"{usage.free / (1024 ** 4):.2f}\"} return {\"cpu\": cpu_info, \"memory\": memory_info, \"disk\": disk_info} 这样，每隔60s，传感器sensor.nas_info就会调用一次 API，获取到返回的json数据。 ","date":"2025-08-06","objectID":"/posts/homeassistant%E6%89%93%E9%80%A0%E6%99%BA%E8%83%BD%E5%AE%B6%E5%B1%85/:4:1","tags":["可视化"],"title":"HomeAssistant，打造智能家居","uri":"/posts/homeassistant%E6%89%93%E9%80%A0%E6%99%BA%E8%83%BD%E5%AE%B6%E5%B1%85/#rest-传感器"},{"categories":"玩转NAS","collections":["玩转NAS"],"content":"16.2 template 模板\rtemplate 模板负责进行数据的格式转换，可以拆分复杂的数据格式，在configuration.yml中添加如下内容： template: !include configuration/template.yaml 然后创建configuration/template.yaml文件，写入如下内容： sensors: cpu_usage: friendly_name: \"BlackHole CPU 使用率\" unit_of_measurement: \"%\" value_template: \"{{ state_attr('sensor.nas_info', 'cpu').percent}}\" memory_percent: friendly_name: \"BlackHole 内存使用率\" unit_of_measurement: \"%\" value_template: \"{{ state_attr('sensor.nas_info', 'memory').percent}}\" memory_used: friendly_name: \"BlackHole 已用内存\" value_template: \"{{ state_attr('sensor.nas_info', 'memory').used }}\" memory_available: friendly_name: \"BlackHole 可用内存\" value_template: \"{{ state_attr('sensor.nas_info', 'memory').available }}\" 因为 Home Assistant 也是用 Python 写的，所以里头的模板语言用的是jinja 信息\rHome Assistant 的大部分卡片，只支持特定数据类型的实体，比如要求实体是数值型的、要求是百分比、要求是空调、扫地机器人等电器，通常json格式的数据无法直接放到卡片中，所以要用 template 模板转换一下。 ","date":"2025-08-06","objectID":"/posts/homeassistant%E6%89%93%E9%80%A0%E6%99%BA%E8%83%BD%E5%AE%B6%E5%B1%85/:4:2","tags":["可视化"],"title":"HomeAssistant，打造智能家居","uri":"/posts/homeassistant%E6%89%93%E9%80%A0%E6%99%BA%E8%83%BD%E5%AE%B6%E5%B1%85/#template-模板"},{"categories":"玩转NAS","collections":["玩转NAS"],"content":"16.3 实体信息获取\r有时候，我们需要获取实体的详细信息。比如，我们需要从空调实体中获取当前房间的温湿度信息。可以采用如下方式： api/ha_entity.py\rfrom fastapi import APIRouter from typing import Dict, Any from core.homeassistant import get_entity router = APIRouter(prefix=\"/ha_entity\", tags=[\"ha_entity\"], responses={}) @router.get(\"/{entity_id:str}\", response_model=Dict[str, Any]) async def air_conditioner_info(entity_id: str): info = get_entity(entity_id) return info core/homeassistant.py\rimport requests home_assistant_url = \"http://HOST:8123\" api_token = \"xxxxx\" # 获取实体状态 def get_entity(entity_id): url = f\"{home_assistant_url}/api/states/{entity_id}\" headers = {\"Authorization\": f\"Bearer {api_token}\", \"Content-Type\": \"application/json\"} response = requests.get(url, headers=headers) if response.status_code == 200: data = response.json() return data 信息\r官方有一个库，叫做homeassistant_api，可以使用pip进行安装，可以更简单实现这个效果。本人这里使用 HTTP 请求进行获取是因为本人的一个设备返回的json数据太长被截断了，使用官方的库会报错。 使用这个 API 访问一下本人的空调，得到一个类似如下的json数据： 于是就可以获取到当前房间的温度，创建一个 template 实体： current_temperature: friendly_name: \"卧室室内温度\" value_template: \"{{ state_attr('climate.xxxxx', 'current_temperature') }}\" 其他设备的信息获取可以参照这个进行。 ","date":"2025-08-06","objectID":"/posts/homeassistant%E6%89%93%E9%80%A0%E6%99%BA%E8%83%BD%E5%AE%B6%E5%B1%85/:4:3","tags":["可视化"],"title":"HomeAssistant，打造智能家居","uri":"/posts/homeassistant%E6%89%93%E9%80%A0%E6%99%BA%E8%83%BD%E5%AE%B6%E5%B1%85/#实体信息获取"},{"categories":"玩转NAS","collections":["玩转NAS"],"content":"16.3 实体信息获取\r有时候，我们需要获取实体的详细信息。比如，我们需要从空调实体中获取当前房间的温湿度信息。可以采用如下方式： api/ha_entity.py\rfrom fastapi import APIRouter from typing import Dict, Any from core.homeassistant import get_entity router = APIRouter(prefix=\"/ha_entity\", tags=[\"ha_entity\"], responses={}) @router.get(\"/{entity_id:str}\", response_model=Dict[str, Any]) async def air_conditioner_info(entity_id: str): info = get_entity(entity_id) return info core/homeassistant.py\rimport requests home_assistant_url = \"http://HOST:8123\" api_token = \"xxxxx\" # 获取实体状态 def get_entity(entity_id): url = f\"{home_assistant_url}/api/states/{entity_id}\" headers = {\"Authorization\": f\"Bearer {api_token}\", \"Content-Type\": \"application/json\"} response = requests.get(url, headers=headers) if response.status_code == 200: data = response.json() return data 信息\r官方有一个库，叫做homeassistant_api，可以使用pip进行安装，可以更简单实现这个效果。本人这里使用 HTTP 请求进行获取是因为本人的一个设备返回的json数据太长被截断了，使用官方的库会报错。 使用这个 API 访问一下本人的空调，得到一个类似如下的json数据： 于是就可以获取到当前房间的温度，创建一个 template 实体： current_temperature: friendly_name: \"卧室室内温度\" value_template: \"{{ state_attr('climate.xxxxx', 'current_temperature') }}\" 其他设备的信息获取可以参照这个进行。 ","date":"2025-08-06","objectID":"/posts/homeassistant%E6%89%93%E9%80%A0%E6%99%BA%E8%83%BD%E5%AE%B6%E5%B1%85/:4:3","tags":["可视化"],"title":"HomeAssistant，打造智能家居","uri":"/posts/homeassistant%E6%89%93%E9%80%A0%E6%99%BA%E8%83%BD%E5%AE%B6%E5%B1%85/#apiha_entitypy"},{"categories":"玩转NAS","collections":["玩转NAS"],"content":"16.3 实体信息获取\r有时候，我们需要获取实体的详细信息。比如，我们需要从空调实体中获取当前房间的温湿度信息。可以采用如下方式： api/ha_entity.py\rfrom fastapi import APIRouter from typing import Dict, Any from core.homeassistant import get_entity router = APIRouter(prefix=\"/ha_entity\", tags=[\"ha_entity\"], responses={}) @router.get(\"/{entity_id:str}\", response_model=Dict[str, Any]) async def air_conditioner_info(entity_id: str): info = get_entity(entity_id) return info core/homeassistant.py\rimport requests home_assistant_url = \"http://HOST:8123\" api_token = \"xxxxx\" # 获取实体状态 def get_entity(entity_id): url = f\"{home_assistant_url}/api/states/{entity_id}\" headers = {\"Authorization\": f\"Bearer {api_token}\", \"Content-Type\": \"application/json\"} response = requests.get(url, headers=headers) if response.status_code == 200: data = response.json() return data 信息\r官方有一个库，叫做homeassistant_api，可以使用pip进行安装，可以更简单实现这个效果。本人这里使用 HTTP 请求进行获取是因为本人的一个设备返回的json数据太长被截断了，使用官方的库会报错。 使用这个 API 访问一下本人的空调，得到一个类似如下的json数据： 于是就可以获取到当前房间的温度，创建一个 template 实体： current_temperature: friendly_name: \"卧室室内温度\" value_template: \"{{ state_attr('climate.xxxxx', 'current_temperature') }}\" 其他设备的信息获取可以参照这个进行。 ","date":"2025-08-06","objectID":"/posts/homeassistant%E6%89%93%E9%80%A0%E6%99%BA%E8%83%BD%E5%AE%B6%E5%B1%85/:4:3","tags":["可视化"],"title":"HomeAssistant，打造智能家居","uri":"/posts/homeassistant%E6%89%93%E9%80%A0%E6%99%BA%E8%83%BD%E5%AE%B6%E5%B1%85/#corehomeassistantpy"},{"categories":"玩转NAS","collections":["玩转NAS"],"content":"16.4 rest_command 动作\rHome Assistant 有一个叫做「自动化」的功能。比如我离开家的时候，我想把空调关了，比如我快到家的时候，我想把空调打开。 自动化需要触发条件和动作两项。触发条件比较好设置，而动作通常会比较复杂，如果使用自带的图形化界面会比较繁琐，可以创建一个 rest_command 动作，触发条件以后，调用 API 执行一系列的动作。 在configuration.yaml中进行如下配置： rest_command: !include configuration/rest_command.yaml 然后创建configuration/rest_command.yaml文件，写入如下内容： leave_home: url: \"http://192.168.0.103:8000/leave/\" method: POST payload: '{\"place\": \"home\"}' headers: Content-Type: \"application/json\" 这样，触发离家这个条件的时候，就会使用 POST 请求调用http://192.168.0.103:8000/leave/，并且传入参数{\"place\": \"home\"} 这样，只要我们写一个 API来处理后续事件即可。 信息\r使用 rest_command 动作还有一个好处，就是可以在中途调用n8n和mcp服务，实现更加复杂的控制效果，比如调用高德地图的mcp，推算出到家的时间。 ","date":"2025-08-06","objectID":"/posts/homeassistant%E6%89%93%E9%80%A0%E6%99%BA%E8%83%BD%E5%AE%B6%E5%B1%85/:4:4","tags":["可视化"],"title":"HomeAssistant，打造智能家居","uri":"/posts/homeassistant%E6%89%93%E9%80%A0%E6%99%BA%E8%83%BD%E5%AE%B6%E5%B1%85/#rest_command-动作"},{"categories":"旅游","collections":["超超的奇妙冒险"],"content":"第 1 节 启程\r从泰山脚下出发，远远便见一片片红灯笼高高挂起，上书「红门里」三字，浓浓的古风韵味扑面而来，仿佛在向每一位来客招手致意。 红门外头是一条热闹的集市，店铺鳞次栉比，登山所需的拐杖、水壶、祈福带等一应俱全，行人穿梭其间，为即将开始的登山之旅做着最后的准备。 穿过集市，一座古朴的石门巍然矗立，牌匾上刻着「天阶」二字，右侧书「人间灵应无双」，左侧题「天下巍岩第一」，还未登山，便已感受到泰山的气势与庄严。 继续往里走，眼前出现一座红色的拱形门——红门，朱红色的门框在青山绿树的映衬下格外醒目，跨过此门，便算正式踏上了登临泰山的征程。 ","date":"2025-07-11","objectID":"/posts/%E6%B3%B0%E5%B1%B1%E4%B8%80%E6%97%A5%E7%BA%A2%E9%97%A8%E5%8F%A4%E9%9F%B5%E7%8E%89%E7%9A%87%E6%99%A8%E6%9B%A6/:1:0","tags":["旅游"],"title":"泰山一日：红门古韵，玉皇晨曦","uri":"/posts/%E6%B3%B0%E5%B1%B1%E4%B8%80%E6%97%A5%E7%BA%A2%E9%97%A8%E5%8F%A4%E9%9F%B5%E7%8E%89%E7%9A%87%E6%99%A8%E6%9B%A6/#启程"},{"categories":"旅游","collections":["超超的奇妙冒险"],"content":"第 2 节 上山\r来到中天门，只见人潮涌动、摩肩接踵，一块巨石赫然矗立，上书\"天下名山第一\"六个大字，气势非凡。 继续前行，穿过热闹的人群便来到了升仙坊，从这里开始逐步进入上山的核心阶段，石阶愈发陡峭。 沿着蜿蜒的山路再往上走，抬头望去，南天门已在云雾中若隐若现，意味着行程已到达中后段。 穿过南天门继续攀登，眼前豁然开朗，天街横亘在群山之间，进入了登顶前的最后冲刺阶段。 沿着天街奋力向上，终于来到了泰山之巅——玉皇顶，登顶成功的那一刻，心中豪情油然而生。 站在玉皇顶上俯瞰山下，连绵的山脉在暮色中勾勒出壮阔的轮廓，令人心旷神怡。 夜幕降临，山下的城市亮起点点灯火，万家灯火通明璀璨，与天上的星光交相辉映，为这次登山之旅画上了完美的句号。 ","date":"2025-07-11","objectID":"/posts/%E6%B3%B0%E5%B1%B1%E4%B8%80%E6%97%A5%E7%BA%A2%E9%97%A8%E5%8F%A4%E9%9F%B5%E7%8E%89%E7%9A%87%E6%99%A8%E6%9B%A6/:2:0","tags":["旅游"],"title":"泰山一日：红门古韵，玉皇晨曦","uri":"/posts/%E6%B3%B0%E5%B1%B1%E4%B8%80%E6%97%A5%E7%BA%A2%E9%97%A8%E5%8F%A4%E9%9F%B5%E7%8E%89%E7%9A%87%E6%99%A8%E6%9B%A6/#上山"},{"categories":"旅游","collections":["超超的奇妙冒险"],"content":"第 3 节 日出\r晨曦微露，天边刚泛起鱼肚白，玉皇顶上早已聚满了等待日出的游人，大家迎着寒风翘首以盼，期待着太阳跃出地平线的那一刻。 没过多久，天边出现了一抹耀眼的金光，太阳缓缓探出头来，光芒洒在云海之上，人群中顿时爆发出阵阵欢呼与惊叹。 太阳终于完全升起，金光洒满整个泰山之巅，这一刻，旅途的所有疲惫都化为值得，怀揣着满满的感动与震撼，我们心满意足地踏上了返程的路。 ","date":"2025-07-11","objectID":"/posts/%E6%B3%B0%E5%B1%B1%E4%B8%80%E6%97%A5%E7%BA%A2%E9%97%A8%E5%8F%A4%E9%9F%B5%E7%8E%89%E7%9A%87%E6%99%A8%E6%9B%A6/:3:0","tags":["旅游"],"title":"泰山一日：红门古韵，玉皇晨曦","uri":"/posts/%E6%B3%B0%E5%B1%B1%E4%B8%80%E6%97%A5%E7%BA%A2%E9%97%A8%E5%8F%A4%E9%9F%B5%E7%8E%89%E7%9A%87%E6%99%A8%E6%9B%A6/#日出"},{"categories":"旅游","collections":["超超的奇妙冒险"],"content":"第 4 节 下山\r下山前先登顶玉皇顶最高峰，俯瞰众山，只见红墙黑瓦挑檐的建筑群错落有致，气势恢宏，真正体会到了一览众山小的豪迈。 随后走进玉皇庙，庙内有一堆石块，中央摆放着一尊刻有龙腾图案的铜鼎，庄重而古朴。 只见人们纷纷往石块中间丢掷硬币祈福，清脆的碰撞声此起彼伏，寄托着无数美好的心愿。 石块边上立着一块石碑，上书「泰山极顶 1545米」，彰显着这征服巅峰的荣耀时刻。 接着来到五岳独尊石前，虽然游人如织有些遮挡，但依旧能感受到这块名石的独特气韵。 重新回到天街，便正式开启了漫长的下山之旅。 沿着山路下行，只见山体上覆盖着茂密的翠叶，生机盎然。 继续前行，远处浓雾弥漫，山峦若隐若现，宛如梦幻仙境。 途中经过缆车附近，排队的人群一眼望不到头，而一旁陡峭的山峰更是险峻无比。 最后看到一块形状奇特的巨石，上面刻着「霖雨苍生」四字，经水流常年冲刷后形成了独特的流线型褪色纹路，十分奇特。 ","date":"2025-07-11","objectID":"/posts/%E6%B3%B0%E5%B1%B1%E4%B8%80%E6%97%A5%E7%BA%A2%E9%97%A8%E5%8F%A4%E9%9F%B5%E7%8E%89%E7%9A%87%E6%99%A8%E6%9B%A6/:4:0","tags":["旅游"],"title":"泰山一日：红门古韵，玉皇晨曦","uri":"/posts/%E6%B3%B0%E5%B1%B1%E4%B8%80%E6%97%A5%E7%BA%A2%E9%97%A8%E5%8F%A4%E9%9F%B5%E7%8E%89%E7%9A%87%E6%99%A8%E6%9B%A6/#下山"},{"categories":"旅游","collections":["超超的奇妙冒险"],"content":"第 1 节 大明湖\r刚走进大明湖，首先映入眼帘的便是那尊栩栩如生的夏雨荷雕像，材质似布料般轻盈，透光之中眼睛竟仿佛会动，惟妙惟肖令人称奇。 抬头望去，上书「大明湖」三个大字的牌匾宽阔庄重，气势恢宏，仿佛在向每位到访者诉说这片水域的千年故事。 放眼湖面，对岸的灯火璀璨夺目，倒映在泛起层层涟漪的水面上，光影摇曳，如梦如幻。 继续沿湖畔漫步，柳枝低垂间，一艘小船缓缓驶来，透着温暖的黄色灯光，在夜色中显得格外美轮美奂。 湖中央的景致更是迷人，两岸柳树依依，中间湖水静静承托着两岸灯火的倒影，流光溢彩，令人沉醉。 再往前行，一座拱桥横跨湖面，桥洞下灯火通明，桥上人头攒动，人们或凭栏远眺，或驻足拍照，将这幅夜色画卷点缀得更加生动热闹。 ","date":"2025-07-06","objectID":"/posts/%E6%B8%85%E6%B3%89%E7%BB%95%E6%8C%87%E5%B0%96%E7%83%9F%E7%81%AB%E6%BB%A1%E6%B3%89%E5%9F%8E/:1:0","tags":["旅游"],"title":"清泉绕指尖，烟火满泉城","uri":"/posts/%E6%B8%85%E6%B3%89%E7%BB%95%E6%8C%87%E5%B0%96%E7%83%9F%E7%81%AB%E6%BB%A1%E6%B3%89%E5%9F%8E/#大明湖"},{"categories":"旅游","collections":["超超的奇妙冒险"],"content":"第 2 节 超然楼\r继续向前，远远便望见一座巍峨壮观的楼阁矗立在湖畔，这便是闻名遐迩的超然楼。从侧面看去，楼体飞檐翘角、雕梁画栋，透着一股不凡的皇家气韵，在蓝天映衬下格外醒目。 随后绕到正面，更觉其气势恢宏，七层高的楼阁层层叠叠、规制严整，朱红立柱与金色匾额相得益彰，颇有几分皇宫般的庄重与精致。 再往前走近些仰望，超然楼的巍峨身姿愈发震撼人心，整座楼直插云霄，令人不由心生赞叹。待到夜幕降临，华灯齐放时，这里必将化作一片璀璨夺目的光影盛景。 ","date":"2025-07-06","objectID":"/posts/%E6%B8%85%E6%B3%89%E7%BB%95%E6%8C%87%E5%B0%96%E7%83%9F%E7%81%AB%E6%BB%A1%E6%B3%89%E5%9F%8E/:2:0","tags":["旅游"],"title":"清泉绕指尖，烟火满泉城","uri":"/posts/%E6%B8%85%E6%B3%89%E7%BB%95%E6%8C%87%E5%B0%96%E7%83%9F%E7%81%AB%E6%BB%A1%E6%B3%89%E5%9F%8E/#超然楼"},{"categories":"旅游","collections":["超超的奇妙冒险"],"content":"第 3 节 曲水亭街\r漫步在济南曲水亭街，河对岸灯火璀璨，斑斓的光影倒映在缓缓流淌的河水中，波光粼粼，如梦似幻。 继续往前走，一条热闹的商业街映入眼帘，街上人潮涌动，两旁林立着各式特色店铺，新奇有趣，让人目不暇接。 再往里探去，发现这里主要售卖各种文玩古物，充满了浓厚的人文气息，虽说吃饭的地方不多，但逛逛这些小店已足够让人流连忘返。 ","date":"2025-07-06","objectID":"/posts/%E6%B8%85%E6%B3%89%E7%BB%95%E6%8C%87%E5%B0%96%E7%83%9F%E7%81%AB%E6%BB%A1%E6%B3%89%E5%9F%8E/:3:0","tags":["旅游"],"title":"清泉绕指尖，烟火满泉城","uri":"/posts/%E6%B8%85%E6%B3%89%E7%BB%95%E6%8C%87%E5%B0%96%E7%83%9F%E7%81%AB%E6%BB%A1%E6%B3%89%E5%9F%8E/#曲水亭街"},{"categories":"旅游","collections":["超超的奇妙冒险"],"content":"第 4 节 芙蓉街/宽厚里\r夜幕降临，我来到芙蓉街，抬头便是那块古色古香的牌匾，整条街灯火通明，人声鼎沸。 穿过熙攘的人群，再往前走不远就来到了宽厚里，相比之下这里多了几分古朴与开阔。 这两条小吃街相距甚近，汇集了天南地北的各色美食，虽然山东本地小吃不算多，但连本地人也常来闲逛，可见济南的夜生活相当热闹。 继续往里逛去，街边各式小吃摊位一字排开，蒸腾的热气和诱人的香味交织在一起，让人忍不住想逐一品尝。 ","date":"2025-07-06","objectID":"/posts/%E6%B8%85%E6%B3%89%E7%BB%95%E6%8C%87%E5%B0%96%E7%83%9F%E7%81%AB%E6%BB%A1%E6%B3%89%E5%9F%8E/:4:0","tags":["旅游"],"title":"清泉绕指尖，烟火满泉城","uri":"/posts/%E6%B8%85%E6%B3%89%E7%BB%95%E6%8C%87%E5%B0%96%E7%83%9F%E7%81%AB%E6%BB%A1%E6%B3%89%E5%9F%8E/#芙蓉街宽厚里"},{"categories":"旅游","collections":["超超的奇妙冒险"],"content":"第 5 节 趵突泉\r","date":"2025-07-06","objectID":"/posts/%E6%B8%85%E6%B3%89%E7%BB%95%E6%8C%87%E5%B0%96%E7%83%9F%E7%81%AB%E6%BB%A1%E6%B3%89%E5%9F%8E/:5:0","tags":["旅游"],"title":"清泉绕指尖，烟火满泉城","uri":"/posts/%E6%B8%85%E6%B3%89%E7%BB%95%E6%8C%87%E5%B0%96%E7%83%9F%E7%81%AB%E6%BB%A1%E6%B3%89%E5%9F%8E/#趵突泉"},{"categories":"旅游","collections":["超超的奇妙冒险"],"content":"5.1 湛露泉\r踏入趵突泉公园，首先映入眼帘的是湛露泉，它与我脑海中泉涌喷薄的想象相去甚远——并没有太多波澜，倒是池塘中聚集的锦鲤正争相抢食，热闹非凡。 再看时，锦鲤们渐渐分散开来，在水中悠然游弋，红色的身影若隐若现，为这方静水平添了几分灵动。 我蹲下身子，从贴近水面的视角望出去，水下的世界显得更加宁静而深邃，鱼儿的身影也变得格外清晰。 ","date":"2025-07-06","objectID":"/posts/%E6%B8%85%E6%B3%89%E7%BB%95%E6%8C%87%E5%B0%96%E7%83%9F%E7%81%AB%E6%BB%A1%E6%B3%89%E5%9F%8E/:5:1","tags":["旅游"],"title":"清泉绕指尖，烟火满泉城","uri":"/posts/%E6%B8%85%E6%B3%89%E7%BB%95%E6%8C%87%E5%B0%96%E7%83%9F%E7%81%AB%E6%BB%A1%E6%B3%89%E5%9F%8E/#湛露泉"},{"categories":"旅游","collections":["超超的奇妙冒险"],"content":"5.2 无忧泉\r继续往里走，便来到了无忧泉，果然如其名一般，水面波澜不惊，一片安然静谧的气息扑面而来。 几条黑色的鱼儿缓缓游过，在水底无声穿行，仿佛也不忍打破这份宁静。 凑近细看，那些鱼在水中姿态轻盈自在，这一刻我似乎也感受到了无忧二字的真意。 ","date":"2025-07-06","objectID":"/posts/%E6%B8%85%E6%B3%89%E7%BB%95%E6%8C%87%E5%B0%96%E7%83%9F%E7%81%AB%E6%BB%A1%E6%B3%89%E5%9F%8E/:5:2","tags":["旅游"],"title":"清泉绕指尖，烟火满泉城","uri":"/posts/%E6%B8%85%E6%B3%89%E7%BB%95%E6%8C%87%E5%B0%96%E7%83%9F%E7%81%AB%E6%BB%A1%E6%B3%89%E5%9F%8E/#无忧泉"},{"categories":"旅游","collections":["超超的奇妙冒险"],"content":"5.3 趵突泉\r再往前，正对着趵突泉望去——池水中不断冒出气泡，隐约有水流喷涌而出，这才是名副其实的天下第一泉。 ","date":"2025-07-06","objectID":"/posts/%E6%B8%85%E6%B3%89%E7%BB%95%E6%8C%87%E5%B0%96%E7%83%9F%E7%81%AB%E6%BB%A1%E6%B3%89%E5%9F%8E/:5:3","tags":["旅游"],"title":"清泉绕指尖，烟火满泉城","uri":"/posts/%E6%B8%85%E6%B3%89%E7%BB%95%E6%8C%87%E5%B0%96%E7%83%9F%E7%81%AB%E6%BB%A1%E6%B3%89%E5%9F%8E/#趵突泉-1"},{"categories":"旅游","collections":["超超的奇妙冒险"],"content":"5.4 杜康泉\r转而来到杜康泉，看着这个并不大的池塘，我忽然明白了无忧泉为何能如此无忧无虑——有杜康美酒相伴，何愁不能忘忧呢？ 离开泉眼区域，园中的河流蜿蜒流淌，两侧砖石整齐堆砌，水流安静而平缓。 不远处有一片方正的池塘，池边一棵树木横着生长，姿态奇特，别有一番趣味。 水面的尽头处，一道小小的瀑布轻泻而下，流水潺潺，给这片静谧奏响了轻快的乐章。 ","date":"2025-07-06","objectID":"/posts/%E6%B8%85%E6%B3%89%E7%BB%95%E6%8C%87%E5%B0%96%E7%83%9F%E7%81%AB%E6%BB%A1%E6%B3%89%E5%9F%8E/:5:4","tags":["旅游"],"title":"清泉绕指尖，烟火满泉城","uri":"/posts/%E6%B8%85%E6%B3%89%E7%BB%95%E6%8C%87%E5%B0%96%E7%83%9F%E7%81%AB%E6%BB%A1%E6%B3%89%E5%9F%8E/#杜康泉"},{"categories":"旅游","collections":["超超的奇妙冒险"],"content":"5.5 植被\r园中的植被也颇值得细赏，凌霄花爬满了整座花架，橙红色的花朵如火焰般热烈绽放。 在绿叶间层层叠叠，红橙交织，格外夺目。 两侧的竹林笔直挺立，竹影婆娑，透着文人雅士的清高气节。 穿行在竹林之间，翠竹掩映，风过处沙沙作响，令人心旷神怡。 柏树苍劲挺拔，寓意着坚韧与长青，与这千年泉水的意境十分契合。 最后，紫薇花在园中灿烂绽放，花色柔美淡雅，与整个泉群的主题相得益彰，为这次游览画上了一个圆满的句号。 ","date":"2025-07-06","objectID":"/posts/%E6%B8%85%E6%B3%89%E7%BB%95%E6%8C%87%E5%B0%96%E7%83%9F%E7%81%AB%E6%BB%A1%E6%B3%89%E5%9F%8E/:5:5","tags":["旅游"],"title":"清泉绕指尖，烟火满泉城","uri":"/posts/%E6%B8%85%E6%B3%89%E7%BB%95%E6%8C%87%E5%B0%96%E7%83%9F%E7%81%AB%E6%BB%A1%E6%B3%89%E5%9F%8E/#植被"},{"categories":"玩转NAS","collections":["玩转NAS"],"content":"第 7 节 概述\rMCP(模型上下文协议，Model Context Protocol)是一套用于 AI 模型的接口协议，使得模型不仅能处理文本，还能连接数据库、本地文件、API等。 MCP 服务器 ：是一个轻量级程序，提供特定 MCP 服务。它能够安全访问本地数据源和远程服务，进行一定的程序处理，并通过标准化的模型上下文协议公开，供 MCP 客户端进行访问。 官方和社区开发了许多 MCP Server，如下网址列举出了一些 MCP Server： 官方列表 awesome-mcp-servers mcp.so MCP Market smithery.ai 关于 MCP 的介绍，可以查看下面的这些视频进行学习： 基础篇 进阶篇 番外篇 视频中有这样一副图： 当 MCP 服务器启动以后，会和客户端进行一次握手，然后 MCP 服务器会告诉客户端自己能够提供哪些工具（Tools）以及这些工具的作用，如下： 信息\r因为 MCP 服务器告诉了客户端自己可以执行的函数、函数的作用以及需要传入的参数，并且现在的大模型一般都有函数调用（Function Calling）能力，所以大模型结合这些信息可以自行决定调用函数的时机和顺序。 信息\rMCP 服务其实就是一段普通的程序，不仅是大模型可以调用，其他的符合其调用方式的函数也可以对其进行调用，它与其他函数最大的不同在于提供了list_tools这一方法，使得大模型也可以理解如何调用它。 MCP 服务器有两种主要的通信协议： 本地通信：通过 stdio 传输数据，适用于在同一台机器上运行的客户端和服务器之间的通信。 远程通信：利用 SSE（服务器发送事件，Server-Sent Events）与 HTTP 结合，实现跨网络的实时数据传输，适用于需要访问远程资源或分布式部署的场景。 ","date":"2025-07-04","objectID":"/posts/%E4%BD%BF%E7%94%A8mcp%E6%9C%8D%E5%8A%A1%E6%89%A9%E5%B1%95%E6%A8%A1%E5%9E%8B%E5%8A%9F%E8%83%BD/:1:0","tags":["大模型"],"title":"使用MCP服务扩展模型功能","uri":"/posts/%E4%BD%BF%E7%94%A8mcp%E6%9C%8D%E5%8A%A1%E6%89%A9%E5%B1%95%E6%A8%A1%E5%9E%8B%E5%8A%9F%E8%83%BD/#概述"},{"categories":"玩转NAS","collections":["玩转NAS"],"content":"第 8 节 安装部署\r","date":"2025-07-04","objectID":"/posts/%E4%BD%BF%E7%94%A8mcp%E6%9C%8D%E5%8A%A1%E6%89%A9%E5%B1%95%E6%A8%A1%E5%9E%8B%E5%8A%9F%E8%83%BD/:2:0","tags":["大模型"],"title":"使用MCP服务扩展模型功能","uri":"/posts/%E4%BD%BF%E7%94%A8mcp%E6%9C%8D%E5%8A%A1%E6%89%A9%E5%B1%95%E6%A8%A1%E5%9E%8B%E5%8A%9F%E8%83%BD/#安装部署"},{"categories":"玩转NAS","collections":["玩转NAS"],"content":"8.1 n8n 上安装 MCP 插件\r从上一节中可以知道，MCP 服务器使用前是需要启动，并且和客户端进行握手的，可以本地通信也可以远程通信。 在n8n上默认的 MCP 工具只能接在 AI 上来使用，为了获得更多的灵活性，需要安装一个插件n8n-nodes-mcp，在社区节点里面安装一下即可。 安装完成后就可以使用 stdio 协议的服务器了，比如我们使用@modelcontextprotocol/server-filesystem这款服务器来读取本地文件信息，可以通过Github获取，安装方式如下： 将信息填入凭证： 警告\r不知道是不是为了解决资源占用问题，n8n 的这个插件以stdio方式运行时，每次执行前会创建 MCP 服务器并进行握手，执行结束后会释放这个服务器资源，所以会导致每次执行前都会有较长时间的加载。 为了解决这个问题，我们需要部署下一个软件。 ","date":"2025-07-04","objectID":"/posts/%E4%BD%BF%E7%94%A8mcp%E6%9C%8D%E5%8A%A1%E6%89%A9%E5%B1%95%E6%A8%A1%E5%9E%8B%E5%8A%9F%E8%83%BD/:2:1","tags":["大模型"],"title":"使用MCP服务扩展模型功能","uri":"/posts/%E4%BD%BF%E7%94%A8mcp%E6%9C%8D%E5%8A%A1%E6%89%A9%E5%B1%95%E6%A8%A1%E5%9E%8B%E5%8A%9F%E8%83%BD/#n8n-上安装-mcp-插件"},{"categories":"玩转NAS","collections":["玩转NAS"],"content":"8.2 聚合器 MetaMCP\r聚合器是一类 MCP 服务器，这类服务器对其他的 MCP 服务进行代理，于是可以通过单个 MCP服务器访问多个应用程序和工具，方便对多个 MCP 服务器进行集中管理。 ┌─────────┐ ┌─────────┐ ┌─────────┐ │ Client │────▶│ MetaMCP │────▶│MCP │ │ │ │ │ │Server 1 │ └─────────┘ └─────────┘ └─────────┘ │ │ ┌─────────┐ └─────────▶│MCP │ │Server 2 │ └─────────┘ 因为这个 MCP 服务器是单独部署的，里面包含的子 MCP 服务器也是一直启动的，每次调用前无需启动，因而速度更快。 通过 Docker 容器进行部署，可以访问Github，参考其docker-compose.yml进行部署： docker run -itd --name MetaMCP --restart always \\ -u root \\ -e POSTGRES_HOST=「HOST」 \\ -e POSTGRES_PORT=「PORT」 \\ -e POSTGRES_USER=「USER」 \\ -e POSTGRES_PASSWORD=「PASSWORD」 \\ -e POSTGRES_DB=metamcp \\ -e DATABASE_URL=postgresql://「USER」:「PASSWORD」@「HOST」:「PORT」/metamcp \\ -e BETTER_AUTH_SECRET=「SECRET」 \\ -e APP_URL=http://「HOST2」:「PORT2」 \\ -e NEXT_PUBLIC_APP_URL=「HOST2」:「PORT2」 \\ -p 「PORT2」:12008 \\ -v /「DATA」:/「DATA」 \\ ghcr.io/metatool-ai/metamcp:latest 信息\r这里的APP_URL和NEXT_PUBLIC_APP_URL因为要提供 URL 给容器外的应用调用，所以填容器外的访问地址。 信息\r这里使用root身份访问容器是因为默认容器不是以root身份运行的，而我希望在里面部署@modelcontextprotocol/server-filesystem进行文件的访问，文件在容器外也会使用到，无法随意修改读写权限和所有者信息，不使用root身份的话会有权限问题。 部署完成之后进入管理页面，依次设置MCP Server、Namespace和Endpoints，并且生成一条API Key，如下： 返回n8n进行如下修改： 可以看到访问速度比之前会快上许多。 ","date":"2025-07-04","objectID":"/posts/%E4%BD%BF%E7%94%A8mcp%E6%9C%8D%E5%8A%A1%E6%89%A9%E5%B1%95%E6%A8%A1%E5%9E%8B%E5%8A%9F%E8%83%BD/:2:2","tags":["大模型"],"title":"使用MCP服务扩展模型功能","uri":"/posts/%E4%BD%BF%E7%94%A8mcp%E6%9C%8D%E5%8A%A1%E6%89%A9%E5%B1%95%E6%A8%A1%E5%9E%8B%E5%8A%9F%E8%83%BD/#聚合器-metamcp"},{"categories":"玩转NAS","collections":["玩转NAS"],"content":"第 13 节 概述\rn8n 是一个非常强大的开源 AI 工作流自动化平台，能够通过拖拽的方式，让各个 AI 工具相互配合，构建复杂的自动化流程，高效处理我们日常遇到的问题。 与 n8n 相关的网址包括：n8n 的官网 、官方文档、 Github 页面。 ","date":"2025-07-01","objectID":"/posts/n8nai%E5%B7%A5%E4%BD%9C%E6%B5%81%E8%87%AA%E5%8A%A8%E5%8C%96%E5%B9%B3%E5%8F%B0/:1:0","tags":["大模型"],"title":"n8n！AI工作流自动化平台","uri":"/posts/n8nai%E5%B7%A5%E4%BD%9C%E6%B5%81%E8%87%AA%E5%8A%A8%E5%8C%96%E5%B9%B3%E5%8F%B0/#概述"},{"categories":"玩转NAS","collections":["玩转NAS"],"content":"第 14 节 软件部署\r部署方式包括 Node 部署和 Docker 部署两种，因为 n8n 主要通过调用 AI 工具的 API 来工作（也可以调用本地的 AI 模型），所以本身对于机器性能的要求并不高，即便是 NAS 这种性能比较一般的机器也可以轻松部署。 n8n 默认情况下使用SQLite作为数据库，也可以使用PostgreSQL替换默认的SQLite数据库，本文中使用PostgreSQL数据库来构建，构建前需要自行创建一个名为n8n的数据库。 docker run -itd --name n8n --restart always \\ -e GENERIC_TIMEZONE=\"Asia/Shanghai\" \\ -e TZ=\"Asia/Shanghai\" \\ -e N8N_SECURE_COOKIE=false \\ -e DB_TYPE=postgresdb \\ -e DB_POSTGRESDB_DATABASE=n8n \\ -e DB_POSTGRESDB_HOST=「HOST」 \\ -e DB_POSTGRESDB_PORT=「PORT」 \\ -e DB_POSTGRESDB_USER=「USER」 \\ -e DB_POSTGRESDB_PASSWORD=「PASSWORD」 \\ -p 5678:5678 \\ -v 「PATH_N8N」:/home/node/.n8n \\ n8nio/n8n:latest 信息\r容器内的/home/node/.n8n保存了配置文件，但是镜像没有并非以root用户登录，因为权限问题无法将文件直接写入，导致加上-v 「PATH_N8N」:/home/node/.n8n 后会无法正常运行，可以先构建容器，将里面的文件复制出来，在加上这句话就可以正常运行了。 信息\r默认 n8n 使用http协议访问时只能运行在localhost，而使用https协议时不受限，通过设置N8N_SECURE_COOKIE=false可以不遵守这一限制。n8n 放在内网使用时，可以避免在内网进行反向代理，比较方便，也相对安全。而在公网使用时，这样设置容易导致密码和 API Key 的泄露，还是应当申请证书进行反向代理。 ","date":"2025-07-01","objectID":"/posts/n8nai%E5%B7%A5%E4%BD%9C%E6%B5%81%E8%87%AA%E5%8A%A8%E5%8C%96%E5%B9%B3%E5%8F%B0/:2:0","tags":["大模型"],"title":"n8n！AI工作流自动化平台","uri":"/posts/n8nai%E5%B7%A5%E4%BD%9C%E6%B5%81%E8%87%AA%E5%8A%A8%E5%8C%96%E5%B9%B3%E5%8F%B0/#软件部署"},{"categories":"玩转NAS","collections":["玩转NAS"],"content":"第 15 节 工作流节点\r工作流由一个个节点组成，前一个工作流输出的结果传到下一个工作流中作为输入，构成了复杂的自动化流程。 ","date":"2025-07-01","objectID":"/posts/n8nai%E5%B7%A5%E4%BD%9C%E6%B5%81%E8%87%AA%E5%8A%A8%E5%8C%96%E5%B9%B3%E5%8F%B0/:3:0","tags":["大模型"],"title":"n8n！AI工作流自动化平台","uri":"/posts/n8nai%E5%B7%A5%E4%BD%9C%E6%B5%81%E8%87%AA%E5%8A%A8%E5%8C%96%E5%B9%B3%E5%8F%B0/#工作流节点"},{"categories":"玩转NAS","collections":["玩转NAS"],"content":"15.1 触发器节点\r工作流的第一个节点是触发器节点，触发器节点定义了工作流在何种情况下被触发，主要触发方式有以下几种： 触发方式大致上就是手动触发、定时触发、事件触发这三种。 ","date":"2025-07-01","objectID":"/posts/n8nai%E5%B7%A5%E4%BD%9C%E6%B5%81%E8%87%AA%E5%8A%A8%E5%8C%96%E5%B9%B3%E5%8F%B0/:3:1","tags":["大模型"],"title":"n8n！AI工作流自动化平台","uri":"/posts/n8nai%E5%B7%A5%E4%BD%9C%E6%B5%81%E8%87%AA%E5%8A%A8%E5%8C%96%E5%B9%B3%E5%8F%B0/#触发器节点"},{"categories":"玩转NAS","collections":["玩转NAS"],"content":"15.2 工作节点\r工作节点主要是一些获取数据和对数据进行处理的节点。 HTTP 节点\rHTTP 节点可以进行 HTTP 请求，可以将前一个节点的输出作为Header或者params中的一部分，自身的response也可以作为下一个节点的输入来使用。 比如，在这样一个逻辑链条中： 上一个节点的输入是JSON数据： 我们可以在请求中将其作为参数传入，比如放在body中： Field 节点\r可以从杂乱无章的JSON数据中提取出我们想要的数据，比如，在这样一个逻辑链条中： 输入数据的格式比较凌乱： 我们可以对数据进行处理： 得到简洁的输出数据： Merge 节点\rMerge节点将左侧的输入按照顺序放到一个列表中，传到右侧，如图： 两个输入，经过Merge以后，变成了单输出，输出上写着2个项目。 信息\r2个项目的意思就是，Merge节点右侧接有运算时，左侧之前传入的参数会分2次分别传入到右侧，在右侧进行2次计算，并得到2个结果。 Aggregate 节点\r上面提到，Merge节点右侧接有运算时会计算多次，有时候当我们想要将其作为一个整体传到右侧进行计算时，就需要用到Aggregate节点。 信息\r输入是json数据，当最外层是列表时，列表的每个元素会计算一次，Aggregate节点就是在列表外套一个对象，将列表中的所有元素放到一个对象中，这样就只计算一次。 Split 节点\rSplit 节点和上面讲的正好相反，比如当我们左侧的输入是一个当做整体的列表时，右侧有一个「新增单位」的运算，如图： 我们的想法是，列表中的每一个值都要新增，这时候我们就需要用到Split节点。Split节点就是把对象展开成列表。 Code 节点\rCode节点上可以运行JS程序或者python程序，因为python程序还在测试阶段，使用体验不是很好，本人建议还是直接写JS程序。 Code节点有2种Mode，分别是Run Once for All Items和Run Once for Each Item，前面那种模式，可以省略掉一个Aggregate节点，因为不管前面有几个项目，都会被当做一个整体一次处理，而后面那种模式会将每个项目单独处理。 两种模式下的语法也有些微不同。 AI 节点\rAI 节点是一个很伟大的东西，它提供了一个非常强大的功能，把非结构化、结构未知的数据转成结构化的数据。 左侧传入的是一段食谱文本和一些类别信息： 我们在 AI 节点上写上一些提示词： 阅读以下食谱文本 {{ $json.data[0].body.text }} 需要对这一食谱进行分类，类别可以有一个或者多个，优先从以下类别中选择 {{ $json.data[1].category }} 如果上述类别不足以描述该食谱，可以使用新的类别，返回一个严格的 JSON 数组。 连接DeepSeek模型和Structured Output Parser，指定输出格式如下： 这样 AI 模型经过运算，就会以我们想要的格式将结果输出。 ","date":"2025-07-01","objectID":"/posts/n8nai%E5%B7%A5%E4%BD%9C%E6%B5%81%E8%87%AA%E5%8A%A8%E5%8C%96%E5%B9%B3%E5%8F%B0/:3:2","tags":["大模型"],"title":"n8n！AI工作流自动化平台","uri":"/posts/n8nai%E5%B7%A5%E4%BD%9C%E6%B5%81%E8%87%AA%E5%8A%A8%E5%8C%96%E5%B9%B3%E5%8F%B0/#工作节点"},{"categories":"玩转NAS","collections":["玩转NAS"],"content":"15.2 工作节点\r工作节点主要是一些获取数据和对数据进行处理的节点。 HTTP 节点\rHTTP 节点可以进行 HTTP 请求，可以将前一个节点的输出作为Header或者params中的一部分，自身的response也可以作为下一个节点的输入来使用。 比如，在这样一个逻辑链条中： 上一个节点的输入是JSON数据： 我们可以在请求中将其作为参数传入，比如放在body中： Field 节点\r可以从杂乱无章的JSON数据中提取出我们想要的数据，比如，在这样一个逻辑链条中： 输入数据的格式比较凌乱： 我们可以对数据进行处理： 得到简洁的输出数据： Merge 节点\rMerge节点将左侧的输入按照顺序放到一个列表中，传到右侧，如图： 两个输入，经过Merge以后，变成了单输出，输出上写着2个项目。 信息\r2个项目的意思就是，Merge节点右侧接有运算时，左侧之前传入的参数会分2次分别传入到右侧，在右侧进行2次计算，并得到2个结果。 Aggregate 节点\r上面提到，Merge节点右侧接有运算时会计算多次，有时候当我们想要将其作为一个整体传到右侧进行计算时，就需要用到Aggregate节点。 信息\r输入是json数据，当最外层是列表时，列表的每个元素会计算一次，Aggregate节点就是在列表外套一个对象，将列表中的所有元素放到一个对象中，这样就只计算一次。 Split 节点\rSplit 节点和上面讲的正好相反，比如当我们左侧的输入是一个当做整体的列表时，右侧有一个「新增单位」的运算，如图： 我们的想法是，列表中的每一个值都要新增，这时候我们就需要用到Split节点。Split节点就是把对象展开成列表。 Code 节点\rCode节点上可以运行JS程序或者python程序，因为python程序还在测试阶段，使用体验不是很好，本人建议还是直接写JS程序。 Code节点有2种Mode，分别是Run Once for All Items和Run Once for Each Item，前面那种模式，可以省略掉一个Aggregate节点，因为不管前面有几个项目，都会被当做一个整体一次处理，而后面那种模式会将每个项目单独处理。 两种模式下的语法也有些微不同。 AI 节点\rAI 节点是一个很伟大的东西，它提供了一个非常强大的功能，把非结构化、结构未知的数据转成结构化的数据。 左侧传入的是一段食谱文本和一些类别信息： 我们在 AI 节点上写上一些提示词： 阅读以下食谱文本 {{ $json.data[0].body.text }} 需要对这一食谱进行分类，类别可以有一个或者多个，优先从以下类别中选择 {{ $json.data[1].category }} 如果上述类别不足以描述该食谱，可以使用新的类别，返回一个严格的 JSON 数组。 连接DeepSeek模型和Structured Output Parser，指定输出格式如下： 这样 AI 模型经过运算，就会以我们想要的格式将结果输出。 ","date":"2025-07-01","objectID":"/posts/n8nai%E5%B7%A5%E4%BD%9C%E6%B5%81%E8%87%AA%E5%8A%A8%E5%8C%96%E5%B9%B3%E5%8F%B0/:3:2","tags":["大模型"],"title":"n8n！AI工作流自动化平台","uri":"/posts/n8nai%E5%B7%A5%E4%BD%9C%E6%B5%81%E8%87%AA%E5%8A%A8%E5%8C%96%E5%B9%B3%E5%8F%B0/#http-节点"},{"categories":"玩转NAS","collections":["玩转NAS"],"content":"15.2 工作节点\r工作节点主要是一些获取数据和对数据进行处理的节点。 HTTP 节点\rHTTP 节点可以进行 HTTP 请求，可以将前一个节点的输出作为Header或者params中的一部分，自身的response也可以作为下一个节点的输入来使用。 比如，在这样一个逻辑链条中： 上一个节点的输入是JSON数据： 我们可以在请求中将其作为参数传入，比如放在body中： Field 节点\r可以从杂乱无章的JSON数据中提取出我们想要的数据，比如，在这样一个逻辑链条中： 输入数据的格式比较凌乱： 我们可以对数据进行处理： 得到简洁的输出数据： Merge 节点\rMerge节点将左侧的输入按照顺序放到一个列表中，传到右侧，如图： 两个输入，经过Merge以后，变成了单输出，输出上写着2个项目。 信息\r2个项目的意思就是，Merge节点右侧接有运算时，左侧之前传入的参数会分2次分别传入到右侧，在右侧进行2次计算，并得到2个结果。 Aggregate 节点\r上面提到，Merge节点右侧接有运算时会计算多次，有时候当我们想要将其作为一个整体传到右侧进行计算时，就需要用到Aggregate节点。 信息\r输入是json数据，当最外层是列表时，列表的每个元素会计算一次，Aggregate节点就是在列表外套一个对象，将列表中的所有元素放到一个对象中，这样就只计算一次。 Split 节点\rSplit 节点和上面讲的正好相反，比如当我们左侧的输入是一个当做整体的列表时，右侧有一个「新增单位」的运算，如图： 我们的想法是，列表中的每一个值都要新增，这时候我们就需要用到Split节点。Split节点就是把对象展开成列表。 Code 节点\rCode节点上可以运行JS程序或者python程序，因为python程序还在测试阶段，使用体验不是很好，本人建议还是直接写JS程序。 Code节点有2种Mode，分别是Run Once for All Items和Run Once for Each Item，前面那种模式，可以省略掉一个Aggregate节点，因为不管前面有几个项目，都会被当做一个整体一次处理，而后面那种模式会将每个项目单独处理。 两种模式下的语法也有些微不同。 AI 节点\rAI 节点是一个很伟大的东西，它提供了一个非常强大的功能，把非结构化、结构未知的数据转成结构化的数据。 左侧传入的是一段食谱文本和一些类别信息： 我们在 AI 节点上写上一些提示词： 阅读以下食谱文本 {{ $json.data[0].body.text }} 需要对这一食谱进行分类，类别可以有一个或者多个，优先从以下类别中选择 {{ $json.data[1].category }} 如果上述类别不足以描述该食谱，可以使用新的类别，返回一个严格的 JSON 数组。 连接DeepSeek模型和Structured Output Parser，指定输出格式如下： 这样 AI 模型经过运算，就会以我们想要的格式将结果输出。 ","date":"2025-07-01","objectID":"/posts/n8nai%E5%B7%A5%E4%BD%9C%E6%B5%81%E8%87%AA%E5%8A%A8%E5%8C%96%E5%B9%B3%E5%8F%B0/:3:2","tags":["大模型"],"title":"n8n！AI工作流自动化平台","uri":"/posts/n8nai%E5%B7%A5%E4%BD%9C%E6%B5%81%E8%87%AA%E5%8A%A8%E5%8C%96%E5%B9%B3%E5%8F%B0/#field-节点"},{"categories":"玩转NAS","collections":["玩转NAS"],"content":"15.2 工作节点\r工作节点主要是一些获取数据和对数据进行处理的节点。 HTTP 节点\rHTTP 节点可以进行 HTTP 请求，可以将前一个节点的输出作为Header或者params中的一部分，自身的response也可以作为下一个节点的输入来使用。 比如，在这样一个逻辑链条中： 上一个节点的输入是JSON数据： 我们可以在请求中将其作为参数传入，比如放在body中： Field 节点\r可以从杂乱无章的JSON数据中提取出我们想要的数据，比如，在这样一个逻辑链条中： 输入数据的格式比较凌乱： 我们可以对数据进行处理： 得到简洁的输出数据： Merge 节点\rMerge节点将左侧的输入按照顺序放到一个列表中，传到右侧，如图： 两个输入，经过Merge以后，变成了单输出，输出上写着2个项目。 信息\r2个项目的意思就是，Merge节点右侧接有运算时，左侧之前传入的参数会分2次分别传入到右侧，在右侧进行2次计算，并得到2个结果。 Aggregate 节点\r上面提到，Merge节点右侧接有运算时会计算多次，有时候当我们想要将其作为一个整体传到右侧进行计算时，就需要用到Aggregate节点。 信息\r输入是json数据，当最外层是列表时，列表的每个元素会计算一次，Aggregate节点就是在列表外套一个对象，将列表中的所有元素放到一个对象中，这样就只计算一次。 Split 节点\rSplit 节点和上面讲的正好相反，比如当我们左侧的输入是一个当做整体的列表时，右侧有一个「新增单位」的运算，如图： 我们的想法是，列表中的每一个值都要新增，这时候我们就需要用到Split节点。Split节点就是把对象展开成列表。 Code 节点\rCode节点上可以运行JS程序或者python程序，因为python程序还在测试阶段，使用体验不是很好，本人建议还是直接写JS程序。 Code节点有2种Mode，分别是Run Once for All Items和Run Once for Each Item，前面那种模式，可以省略掉一个Aggregate节点，因为不管前面有几个项目，都会被当做一个整体一次处理，而后面那种模式会将每个项目单独处理。 两种模式下的语法也有些微不同。 AI 节点\rAI 节点是一个很伟大的东西，它提供了一个非常强大的功能，把非结构化、结构未知的数据转成结构化的数据。 左侧传入的是一段食谱文本和一些类别信息： 我们在 AI 节点上写上一些提示词： 阅读以下食谱文本 {{ $json.data[0].body.text }} 需要对这一食谱进行分类，类别可以有一个或者多个，优先从以下类别中选择 {{ $json.data[1].category }} 如果上述类别不足以描述该食谱，可以使用新的类别，返回一个严格的 JSON 数组。 连接DeepSeek模型和Structured Output Parser，指定输出格式如下： 这样 AI 模型经过运算，就会以我们想要的格式将结果输出。 ","date":"2025-07-01","objectID":"/posts/n8nai%E5%B7%A5%E4%BD%9C%E6%B5%81%E8%87%AA%E5%8A%A8%E5%8C%96%E5%B9%B3%E5%8F%B0/:3:2","tags":["大模型"],"title":"n8n！AI工作流自动化平台","uri":"/posts/n8nai%E5%B7%A5%E4%BD%9C%E6%B5%81%E8%87%AA%E5%8A%A8%E5%8C%96%E5%B9%B3%E5%8F%B0/#merge-节点"},{"categories":"玩转NAS","collections":["玩转NAS"],"content":"15.2 工作节点\r工作节点主要是一些获取数据和对数据进行处理的节点。 HTTP 节点\rHTTP 节点可以进行 HTTP 请求，可以将前一个节点的输出作为Header或者params中的一部分，自身的response也可以作为下一个节点的输入来使用。 比如，在这样一个逻辑链条中： 上一个节点的输入是JSON数据： 我们可以在请求中将其作为参数传入，比如放在body中： Field 节点\r可以从杂乱无章的JSON数据中提取出我们想要的数据，比如，在这样一个逻辑链条中： 输入数据的格式比较凌乱： 我们可以对数据进行处理： 得到简洁的输出数据： Merge 节点\rMerge节点将左侧的输入按照顺序放到一个列表中，传到右侧，如图： 两个输入，经过Merge以后，变成了单输出，输出上写着2个项目。 信息\r2个项目的意思就是，Merge节点右侧接有运算时，左侧之前传入的参数会分2次分别传入到右侧，在右侧进行2次计算，并得到2个结果。 Aggregate 节点\r上面提到，Merge节点右侧接有运算时会计算多次，有时候当我们想要将其作为一个整体传到右侧进行计算时，就需要用到Aggregate节点。 信息\r输入是json数据，当最外层是列表时，列表的每个元素会计算一次，Aggregate节点就是在列表外套一个对象，将列表中的所有元素放到一个对象中，这样就只计算一次。 Split 节点\rSplit 节点和上面讲的正好相反，比如当我们左侧的输入是一个当做整体的列表时，右侧有一个「新增单位」的运算，如图： 我们的想法是，列表中的每一个值都要新增，这时候我们就需要用到Split节点。Split节点就是把对象展开成列表。 Code 节点\rCode节点上可以运行JS程序或者python程序，因为python程序还在测试阶段，使用体验不是很好，本人建议还是直接写JS程序。 Code节点有2种Mode，分别是Run Once for All Items和Run Once for Each Item，前面那种模式，可以省略掉一个Aggregate节点，因为不管前面有几个项目，都会被当做一个整体一次处理，而后面那种模式会将每个项目单独处理。 两种模式下的语法也有些微不同。 AI 节点\rAI 节点是一个很伟大的东西，它提供了一个非常强大的功能，把非结构化、结构未知的数据转成结构化的数据。 左侧传入的是一段食谱文本和一些类别信息： 我们在 AI 节点上写上一些提示词： 阅读以下食谱文本 {{ $json.data[0].body.text }} 需要对这一食谱进行分类，类别可以有一个或者多个，优先从以下类别中选择 {{ $json.data[1].category }} 如果上述类别不足以描述该食谱，可以使用新的类别，返回一个严格的 JSON 数组。 连接DeepSeek模型和Structured Output Parser，指定输出格式如下： 这样 AI 模型经过运算，就会以我们想要的格式将结果输出。 ","date":"2025-07-01","objectID":"/posts/n8nai%E5%B7%A5%E4%BD%9C%E6%B5%81%E8%87%AA%E5%8A%A8%E5%8C%96%E5%B9%B3%E5%8F%B0/:3:2","tags":["大模型"],"title":"n8n！AI工作流自动化平台","uri":"/posts/n8nai%E5%B7%A5%E4%BD%9C%E6%B5%81%E8%87%AA%E5%8A%A8%E5%8C%96%E5%B9%B3%E5%8F%B0/#aggregate-节点"},{"categories":"玩转NAS","collections":["玩转NAS"],"content":"15.2 工作节点\r工作节点主要是一些获取数据和对数据进行处理的节点。 HTTP 节点\rHTTP 节点可以进行 HTTP 请求，可以将前一个节点的输出作为Header或者params中的一部分，自身的response也可以作为下一个节点的输入来使用。 比如，在这样一个逻辑链条中： 上一个节点的输入是JSON数据： 我们可以在请求中将其作为参数传入，比如放在body中： Field 节点\r可以从杂乱无章的JSON数据中提取出我们想要的数据，比如，在这样一个逻辑链条中： 输入数据的格式比较凌乱： 我们可以对数据进行处理： 得到简洁的输出数据： Merge 节点\rMerge节点将左侧的输入按照顺序放到一个列表中，传到右侧，如图： 两个输入，经过Merge以后，变成了单输出，输出上写着2个项目。 信息\r2个项目的意思就是，Merge节点右侧接有运算时，左侧之前传入的参数会分2次分别传入到右侧，在右侧进行2次计算，并得到2个结果。 Aggregate 节点\r上面提到，Merge节点右侧接有运算时会计算多次，有时候当我们想要将其作为一个整体传到右侧进行计算时，就需要用到Aggregate节点。 信息\r输入是json数据，当最外层是列表时，列表的每个元素会计算一次，Aggregate节点就是在列表外套一个对象，将列表中的所有元素放到一个对象中，这样就只计算一次。 Split 节点\rSplit 节点和上面讲的正好相反，比如当我们左侧的输入是一个当做整体的列表时，右侧有一个「新增单位」的运算，如图： 我们的想法是，列表中的每一个值都要新增，这时候我们就需要用到Split节点。Split节点就是把对象展开成列表。 Code 节点\rCode节点上可以运行JS程序或者python程序，因为python程序还在测试阶段，使用体验不是很好，本人建议还是直接写JS程序。 Code节点有2种Mode，分别是Run Once for All Items和Run Once for Each Item，前面那种模式，可以省略掉一个Aggregate节点，因为不管前面有几个项目，都会被当做一个整体一次处理，而后面那种模式会将每个项目单独处理。 两种模式下的语法也有些微不同。 AI 节点\rAI 节点是一个很伟大的东西，它提供了一个非常强大的功能，把非结构化、结构未知的数据转成结构化的数据。 左侧传入的是一段食谱文本和一些类别信息： 我们在 AI 节点上写上一些提示词： 阅读以下食谱文本 {{ $json.data[0].body.text }} 需要对这一食谱进行分类，类别可以有一个或者多个，优先从以下类别中选择 {{ $json.data[1].category }} 如果上述类别不足以描述该食谱，可以使用新的类别，返回一个严格的 JSON 数组。 连接DeepSeek模型和Structured Output Parser，指定输出格式如下： 这样 AI 模型经过运算，就会以我们想要的格式将结果输出。 ","date":"2025-07-01","objectID":"/posts/n8nai%E5%B7%A5%E4%BD%9C%E6%B5%81%E8%87%AA%E5%8A%A8%E5%8C%96%E5%B9%B3%E5%8F%B0/:3:2","tags":["大模型"],"title":"n8n！AI工作流自动化平台","uri":"/posts/n8nai%E5%B7%A5%E4%BD%9C%E6%B5%81%E8%87%AA%E5%8A%A8%E5%8C%96%E5%B9%B3%E5%8F%B0/#split-节点"},{"categories":"玩转NAS","collections":["玩转NAS"],"content":"15.2 工作节点\r工作节点主要是一些获取数据和对数据进行处理的节点。 HTTP 节点\rHTTP 节点可以进行 HTTP 请求，可以将前一个节点的输出作为Header或者params中的一部分，自身的response也可以作为下一个节点的输入来使用。 比如，在这样一个逻辑链条中： 上一个节点的输入是JSON数据： 我们可以在请求中将其作为参数传入，比如放在body中： Field 节点\r可以从杂乱无章的JSON数据中提取出我们想要的数据，比如，在这样一个逻辑链条中： 输入数据的格式比较凌乱： 我们可以对数据进行处理： 得到简洁的输出数据： Merge 节点\rMerge节点将左侧的输入按照顺序放到一个列表中，传到右侧，如图： 两个输入，经过Merge以后，变成了单输出，输出上写着2个项目。 信息\r2个项目的意思就是，Merge节点右侧接有运算时，左侧之前传入的参数会分2次分别传入到右侧，在右侧进行2次计算，并得到2个结果。 Aggregate 节点\r上面提到，Merge节点右侧接有运算时会计算多次，有时候当我们想要将其作为一个整体传到右侧进行计算时，就需要用到Aggregate节点。 信息\r输入是json数据，当最外层是列表时，列表的每个元素会计算一次，Aggregate节点就是在列表外套一个对象，将列表中的所有元素放到一个对象中，这样就只计算一次。 Split 节点\rSplit 节点和上面讲的正好相反，比如当我们左侧的输入是一个当做整体的列表时，右侧有一个「新增单位」的运算，如图： 我们的想法是，列表中的每一个值都要新增，这时候我们就需要用到Split节点。Split节点就是把对象展开成列表。 Code 节点\rCode节点上可以运行JS程序或者python程序，因为python程序还在测试阶段，使用体验不是很好，本人建议还是直接写JS程序。 Code节点有2种Mode，分别是Run Once for All Items和Run Once for Each Item，前面那种模式，可以省略掉一个Aggregate节点，因为不管前面有几个项目，都会被当做一个整体一次处理，而后面那种模式会将每个项目单独处理。 两种模式下的语法也有些微不同。 AI 节点\rAI 节点是一个很伟大的东西，它提供了一个非常强大的功能，把非结构化、结构未知的数据转成结构化的数据。 左侧传入的是一段食谱文本和一些类别信息： 我们在 AI 节点上写上一些提示词： 阅读以下食谱文本 {{ $json.data[0].body.text }} 需要对这一食谱进行分类，类别可以有一个或者多个，优先从以下类别中选择 {{ $json.data[1].category }} 如果上述类别不足以描述该食谱，可以使用新的类别，返回一个严格的 JSON 数组。 连接DeepSeek模型和Structured Output Parser，指定输出格式如下： 这样 AI 模型经过运算，就会以我们想要的格式将结果输出。 ","date":"2025-07-01","objectID":"/posts/n8nai%E5%B7%A5%E4%BD%9C%E6%B5%81%E8%87%AA%E5%8A%A8%E5%8C%96%E5%B9%B3%E5%8F%B0/:3:2","tags":["大模型"],"title":"n8n！AI工作流自动化平台","uri":"/posts/n8nai%E5%B7%A5%E4%BD%9C%E6%B5%81%E8%87%AA%E5%8A%A8%E5%8C%96%E5%B9%B3%E5%8F%B0/#code-节点"},{"categories":"玩转NAS","collections":["玩转NAS"],"content":"15.2 工作节点\r工作节点主要是一些获取数据和对数据进行处理的节点。 HTTP 节点\rHTTP 节点可以进行 HTTP 请求，可以将前一个节点的输出作为Header或者params中的一部分，自身的response也可以作为下一个节点的输入来使用。 比如，在这样一个逻辑链条中： 上一个节点的输入是JSON数据： 我们可以在请求中将其作为参数传入，比如放在body中： Field 节点\r可以从杂乱无章的JSON数据中提取出我们想要的数据，比如，在这样一个逻辑链条中： 输入数据的格式比较凌乱： 我们可以对数据进行处理： 得到简洁的输出数据： Merge 节点\rMerge节点将左侧的输入按照顺序放到一个列表中，传到右侧，如图： 两个输入，经过Merge以后，变成了单输出，输出上写着2个项目。 信息\r2个项目的意思就是，Merge节点右侧接有运算时，左侧之前传入的参数会分2次分别传入到右侧，在右侧进行2次计算，并得到2个结果。 Aggregate 节点\r上面提到，Merge节点右侧接有运算时会计算多次，有时候当我们想要将其作为一个整体传到右侧进行计算时，就需要用到Aggregate节点。 信息\r输入是json数据，当最外层是列表时，列表的每个元素会计算一次，Aggregate节点就是在列表外套一个对象，将列表中的所有元素放到一个对象中，这样就只计算一次。 Split 节点\rSplit 节点和上面讲的正好相反，比如当我们左侧的输入是一个当做整体的列表时，右侧有一个「新增单位」的运算，如图： 我们的想法是，列表中的每一个值都要新增，这时候我们就需要用到Split节点。Split节点就是把对象展开成列表。 Code 节点\rCode节点上可以运行JS程序或者python程序，因为python程序还在测试阶段，使用体验不是很好，本人建议还是直接写JS程序。 Code节点有2种Mode，分别是Run Once for All Items和Run Once for Each Item，前面那种模式，可以省略掉一个Aggregate节点，因为不管前面有几个项目，都会被当做一个整体一次处理，而后面那种模式会将每个项目单独处理。 两种模式下的语法也有些微不同。 AI 节点\rAI 节点是一个很伟大的东西，它提供了一个非常强大的功能，把非结构化、结构未知的数据转成结构化的数据。 左侧传入的是一段食谱文本和一些类别信息： 我们在 AI 节点上写上一些提示词： 阅读以下食谱文本 {{ $json.data[0].body.text }} 需要对这一食谱进行分类，类别可以有一个或者多个，优先从以下类别中选择 {{ $json.data[1].category }} 如果上述类别不足以描述该食谱，可以使用新的类别，返回一个严格的 JSON 数组。 连接DeepSeek模型和Structured Output Parser，指定输出格式如下： 这样 AI 模型经过运算，就会以我们想要的格式将结果输出。 ","date":"2025-07-01","objectID":"/posts/n8nai%E5%B7%A5%E4%BD%9C%E6%B5%81%E8%87%AA%E5%8A%A8%E5%8C%96%E5%B9%B3%E5%8F%B0/:3:2","tags":["大模型"],"title":"n8n！AI工作流自动化平台","uri":"/posts/n8nai%E5%B7%A5%E4%BD%9C%E6%B5%81%E8%87%AA%E5%8A%A8%E5%8C%96%E5%B9%B3%E5%8F%B0/#ai-节点"},{"categories":"玩转NAS","collections":["玩转NAS"],"content":"第 16 节 实例\r举一个实例来说明，在之前的文章中，我们知晓了Mealie是一款功能强大的食谱管理系统，但是维护其繁杂的数据库是一件辛苦的事情，实在是非常的劝退。 所以，很容易想到，如果使用n8n这样一个自动化平台，若是只需要将食谱的文字部分一股脑的传入进去，平台会调用AI功能自动为我们将文字解析成结构化的数据，并调用 API 接口传入 Mealie，那岂不是非常令人振奋吗！ 经过我的不懈努力，最终也是成功实现了这一功能！ ","date":"2025-07-01","objectID":"/posts/n8nai%E5%B7%A5%E4%BD%9C%E6%B5%81%E8%87%AA%E5%8A%A8%E5%8C%96%E5%B9%B3%E5%8F%B0/:4:0","tags":["大模型"],"title":"n8n！AI工作流自动化平台","uri":"/posts/n8nai%E5%B7%A5%E4%BD%9C%E6%B5%81%E8%87%AA%E5%8A%A8%E5%8C%96%E5%B9%B3%E5%8F%B0/#实例"},{"categories":"玩转NAS","collections":["玩转NAS"],"content":"16.1 常规参数\r一些常规数据，类型为字符串或者数字，使用DeepSeek直接识别。 解析以下食谱文本并提取关键信息： {{ $json.body.text }} 请以JSON格式返回以下字段： 1. name：菜名 2. recipe_servings：几人份（纯数字） 3. recipe_yield_quantity：几人份（纯数字） 4. prep_time：准备时间（如1 小时，数字和中文之间有1个空格） 5. perform_time：烹饪时间（如30 分钟，数字和中文之间有1个空格） 6. total_time：总时间（如30 分钟，数字和中文之间有1个空格） 7. description：菜品描述（1-2句话） ","date":"2025-07-01","objectID":"/posts/n8nai%E5%B7%A5%E4%BD%9C%E6%B5%81%E8%87%AA%E5%8A%A8%E5%8C%96%E5%B9%B3%E5%8F%B0/:4:1","tags":["大模型"],"title":"n8n！AI工作流自动化平台","uri":"/posts/n8nai%E5%B7%A5%E4%BD%9C%E6%B5%81%E8%87%AA%E5%8A%A8%E5%8C%96%E5%B9%B3%E5%8F%B0/#常规参数"},{"categories":"玩转NAS","collections":["玩转NAS"],"content":"16.2 营养参数\r营养参数让DeepSeek根据食谱的配料自己估算。 分析以下食谱文本并精确估算营养数据： {{ $json.body.text }} 要求： 1. 输出标准JSON格式的营养信息 2. 所有数值必须为具体数字（整数或小数），禁止使用范围 3. 基于常见食材营养数据估算，保持合理比例 4. 单位严格按以下要求： - 能量：千卡(kcal) - 重量：克(g) - 钠/胆固醇：毫克(mg) 注意： 1. 所有字段必须存在，无数据时填0 2. 数值需符合膳食常识（如蛋白质≈总热量15-20%） 3. 脂肪类总和不超过fat_content 4. 优先考虑主要食材的营养贡献 ","date":"2025-07-01","objectID":"/posts/n8nai%E5%B7%A5%E4%BD%9C%E6%B5%81%E8%87%AA%E5%8A%A8%E5%8C%96%E5%B9%B3%E5%8F%B0/:4:2","tags":["大模型"],"title":"n8n！AI工作流自动化平台","uri":"/posts/n8nai%E5%B7%A5%E4%BD%9C%E6%B5%81%E8%87%AA%E5%8A%A8%E5%8C%96%E5%B9%B3%E5%8F%B0/#营养参数"},{"categories":"玩转NAS","collections":["玩转NAS"],"content":"16.3 烹制方法\r让 AI 自动分析文本，总结归纳。 阅读并分析以下食谱文本： {{ $json.body.text }} 提取烹饪步骤并以JSON格式输出，要求： 1. 使用数组表示步骤，每个步骤包含： - text：步骤详细说明 - summary：步骤简要概括 - title：仅当几个相似步骤的第一个显示概括标题，其余留空 - ingredient_references：保持空数组 2. 对连续相似步骤进行分组，只在第一个步骤设置title 3. 保持输出简洁规范 ","date":"2025-07-01","objectID":"/posts/n8nai%E5%B7%A5%E4%BD%9C%E6%B5%81%E8%87%AA%E5%8A%A8%E5%8C%96%E5%B9%B3%E5%8F%B0/:4:3","tags":["大模型"],"title":"n8n！AI工作流自动化平台","uri":"/posts/n8nai%E5%B7%A5%E4%BD%9C%E6%B5%81%E8%87%AA%E5%8A%A8%E5%8C%96%E5%B9%B3%E5%8F%B0/#烹制方法"},{"categories":"玩转NAS","collections":["玩转NAS"],"content":"16.4 效果\r其他部分涉及到了许多的逻辑运算和JS脚本，处理方式相对比较传统，按照调用各种 API 的逻辑顺序依次调用，即可实现。 ","date":"2025-07-01","objectID":"/posts/n8nai%E5%B7%A5%E4%BD%9C%E6%B5%81%E8%87%AA%E5%8A%A8%E5%8C%96%E5%B9%B3%E5%8F%B0/:4:4","tags":["大模型"],"title":"n8n！AI工作流自动化平台","uri":"/posts/n8nai%E5%B7%A5%E4%BD%9C%E6%B5%81%E8%87%AA%E5%8A%A8%E5%8C%96%E5%B9%B3%E5%8F%B0/#效果"},{"categories":"玩转NAS","collections":["玩转NAS"],"content":"第 16 节 概述\r想必各位都纠结过每天应该吃什么，使用家中现有的食材可以做出哪些之前未尝试制作过的美食。如果有一个食谱管理系统，可以在各个维度上对食谱进行检索，对检索结果提供所需的食材、制作过程等细节，那么无疑会方便许多。 Mealie 是一款非常详尽的食谱管理系统，提供了非常细致的记录和存放食谱的数据库。官网地址是这个，Github 页面的地址是这个，主要功能如下： 「食谱管理」：支持手动创建食谱和导入食谱，提供了食品、单位、标注、分类、标签、用具等多种相关数据的管理，方便整理大量食谱。 「食材与购物清单」：根据食谱自动生成食材清单，并可整合为购物清单，支持多用户协作编辑。 「膳食计划」：创建每日/每周的饮食计划，关联食谱并自动生成所需食材汇总。 「多平台访问」：适配电脑、平板或手机，随时随地查看食谱。 信息\r可以看到 Mealie 的功能是非常全面的，然而，也正是因为全面的功能，导致维护 Mealie 的数据库工作量会比较大，本文也在着力于解决这一困难。 ","date":"2025-06-30","objectID":"/posts/mealie%E6%9E%84%E5%BB%BA%E4%BA%8B%E6%97%A0%E5%B7%A8%E7%BB%86%E7%9A%84%E9%A3%9F%E8%B0%B1%E7%AE%A1%E7%90%86%E7%B3%BB%E7%BB%9F/:1:0","tags":null,"title":"Mealie！构建事无巨细的食谱管理系统","uri":"/posts/mealie%E6%9E%84%E5%BB%BA%E4%BA%8B%E6%97%A0%E5%B7%A8%E7%BB%86%E7%9A%84%E9%A3%9F%E8%B0%B1%E7%AE%A1%E7%90%86%E7%B3%BB%E7%BB%9F/#概述"},{"categories":"玩转NAS","collections":["玩转NAS"],"content":"第 17 节 安装部署\r软件可以通过 Docker 进行安装部署，命令如下： docker run -itd --name Mealie --restart always\\ -p 9925:9000 \\ -e ALLOW_SIGNUP=false \\ -e PUID=「PUID」 \\ -e PGID=「PGID」 \\ -e TZ=Asia/Shanghai \\ -e BASE_URL=「URL」 \\ -e DB_ENGINE=postgres \\ -e POSTGRES_USER=「USER」 \\ -e POSTGRES_PASSWORD=「PASSWORD」 \\ -e POSTGRES_SERVER=「HOST」 \\ -e POSTGRES_PORT=「PORT」 \\ -e POSTGRES_DB=「DATABASE」 \\ -v 「MEALIE_PATH」:/app/data \\ hkotel/mealie:latest 信息\r这里使用了PostgreSQL数据库，如果没有指定数据库，则会使用默认的SQLite数据库 ","date":"2025-06-30","objectID":"/posts/mealie%E6%9E%84%E5%BB%BA%E4%BA%8B%E6%97%A0%E5%B7%A8%E7%BB%86%E7%9A%84%E9%A3%9F%E8%B0%B1%E7%AE%A1%E7%90%86%E7%B3%BB%E7%BB%9F/:2:0","tags":null,"title":"Mealie！构建事无巨细的食谱管理系统","uri":"/posts/mealie%E6%9E%84%E5%BB%BA%E4%BA%8B%E6%97%A0%E5%B7%A8%E7%BB%86%E7%9A%84%E9%A3%9F%E8%B0%B1%E7%AE%A1%E7%90%86%E7%B3%BB%E7%BB%9F/#安装部署"},{"categories":"玩转NAS","collections":["玩转NAS"],"content":"第 18 节 枚举值维护\r安装完成之后，还需要对于枚举值进行一定的维护，这样后续的使用过程中会更加得心应手。 点击左上角的「用户头像」可以进入「用户设置」页面，这里有一个非常重要的功能「数据库维护」。 进入后可以对数据库中常用的枚举值进行维护： ","date":"2025-06-30","objectID":"/posts/mealie%E6%9E%84%E5%BB%BA%E4%BA%8B%E6%97%A0%E5%B7%A8%E7%BB%86%E7%9A%84%E9%A3%9F%E8%B0%B1%E7%AE%A1%E7%90%86%E7%B3%BB%E7%BB%9F/:3:0","tags":null,"title":"Mealie！构建事无巨细的食谱管理系统","uri":"/posts/mealie%E6%9E%84%E5%BB%BA%E4%BA%8B%E6%97%A0%E5%B7%A8%E7%BB%86%E7%9A%84%E9%A3%9F%E8%B0%B1%E7%AE%A1%E7%90%86%E7%B3%BB%E7%BB%9F/#枚举值维护"},{"categories":"玩转NAS","collections":["玩转NAS"],"content":"18.1 食材数据\r标注、食品、单位是对于食材本身枚举参数的维护。 标注\r标注就是单个食材的类型，比如可以设置如下： 食品\r在完成上一步标注的设置之后，可以对食品进行简单的维护，因为后面可以通过 API 来维护食品，所以可以先简单构建即可： 单位\r单位表示单个食材的计量单位，本身没有太多的可选项，简单维护即可： ","date":"2025-06-30","objectID":"/posts/mealie%E6%9E%84%E5%BB%BA%E4%BA%8B%E6%97%A0%E5%B7%A8%E7%BB%86%E7%9A%84%E9%A3%9F%E8%B0%B1%E7%AE%A1%E7%90%86%E7%B3%BB%E7%BB%9F/:3:1","tags":null,"title":"Mealie！构建事无巨细的食谱管理系统","uri":"/posts/mealie%E6%9E%84%E5%BB%BA%E4%BA%8B%E6%97%A0%E5%B7%A8%E7%BB%86%E7%9A%84%E9%A3%9F%E8%B0%B1%E7%AE%A1%E7%90%86%E7%B3%BB%E7%BB%9F/#食材数据"},{"categories":"玩转NAS","collections":["玩转NAS"],"content":"18.1 食材数据\r标注、食品、单位是对于食材本身枚举参数的维护。 标注\r标注就是单个食材的类型，比如可以设置如下： 食品\r在完成上一步标注的设置之后，可以对食品进行简单的维护，因为后面可以通过 API 来维护食品，所以可以先简单构建即可： 单位\r单位表示单个食材的计量单位，本身没有太多的可选项，简单维护即可： ","date":"2025-06-30","objectID":"/posts/mealie%E6%9E%84%E5%BB%BA%E4%BA%8B%E6%97%A0%E5%B7%A8%E7%BB%86%E7%9A%84%E9%A3%9F%E8%B0%B1%E7%AE%A1%E7%90%86%E7%B3%BB%E7%BB%9F/:3:1","tags":null,"title":"Mealie！构建事无巨细的食谱管理系统","uri":"/posts/mealie%E6%9E%84%E5%BB%BA%E4%BA%8B%E6%97%A0%E5%B7%A8%E7%BB%86%E7%9A%84%E9%A3%9F%E8%B0%B1%E7%AE%A1%E7%90%86%E7%B3%BB%E7%BB%9F/#标注"},{"categories":"玩转NAS","collections":["玩转NAS"],"content":"18.1 食材数据\r标注、食品、单位是对于食材本身枚举参数的维护。 标注\r标注就是单个食材的类型，比如可以设置如下： 食品\r在完成上一步标注的设置之后，可以对食品进行简单的维护，因为后面可以通过 API 来维护食品，所以可以先简单构建即可： 单位\r单位表示单个食材的计量单位，本身没有太多的可选项，简单维护即可： ","date":"2025-06-30","objectID":"/posts/mealie%E6%9E%84%E5%BB%BA%E4%BA%8B%E6%97%A0%E5%B7%A8%E7%BB%86%E7%9A%84%E9%A3%9F%E8%B0%B1%E7%AE%A1%E7%90%86%E7%B3%BB%E7%BB%9F/:3:1","tags":null,"title":"Mealie！构建事无巨细的食谱管理系统","uri":"/posts/mealie%E6%9E%84%E5%BB%BA%E4%BA%8B%E6%97%A0%E5%B7%A8%E7%BB%86%E7%9A%84%E9%A3%9F%E8%B0%B1%E7%AE%A1%E7%90%86%E7%B3%BB%E7%BB%9F/#食品"},{"categories":"玩转NAS","collections":["玩转NAS"],"content":"18.1 食材数据\r标注、食品、单位是对于食材本身枚举参数的维护。 标注\r标注就是单个食材的类型，比如可以设置如下： 食品\r在完成上一步标注的设置之后，可以对食品进行简单的维护，因为后面可以通过 API 来维护食品，所以可以先简单构建即可： 单位\r单位表示单个食材的计量单位，本身没有太多的可选项，简单维护即可： ","date":"2025-06-30","objectID":"/posts/mealie%E6%9E%84%E5%BB%BA%E4%BA%8B%E6%97%A0%E5%B7%A8%E7%BB%86%E7%9A%84%E9%A3%9F%E8%B0%B1%E7%AE%A1%E7%90%86%E7%B3%BB%E7%BB%9F/:3:1","tags":null,"title":"Mealie！构建事无巨细的食谱管理系统","uri":"/posts/mealie%E6%9E%84%E5%BB%BA%E4%BA%8B%E6%97%A0%E5%B7%A8%E7%BB%86%E7%9A%84%E9%A3%9F%E8%B0%B1%E7%AE%A1%E7%90%86%E7%B3%BB%E7%BB%9F/#单位"},{"categories":"玩转NAS","collections":["玩转NAS"],"content":"18.2 食谱数据\r食谱数据是对每个菜品的标签数据的维护。 分类\r按照我的个人理解进行了简单的分类，类别可以同时选择多个： 标签\r按照我的理解按照难易度和口味设置了标签： 用具\r稍微设置一下，后期也可以进行添加： ","date":"2025-06-30","objectID":"/posts/mealie%E6%9E%84%E5%BB%BA%E4%BA%8B%E6%97%A0%E5%B7%A8%E7%BB%86%E7%9A%84%E9%A3%9F%E8%B0%B1%E7%AE%A1%E7%90%86%E7%B3%BB%E7%BB%9F/:3:2","tags":null,"title":"Mealie！构建事无巨细的食谱管理系统","uri":"/posts/mealie%E6%9E%84%E5%BB%BA%E4%BA%8B%E6%97%A0%E5%B7%A8%E7%BB%86%E7%9A%84%E9%A3%9F%E8%B0%B1%E7%AE%A1%E7%90%86%E7%B3%BB%E7%BB%9F/#食谱数据"},{"categories":"玩转NAS","collections":["玩转NAS"],"content":"18.2 食谱数据\r食谱数据是对每个菜品的标签数据的维护。 分类\r按照我的个人理解进行了简单的分类，类别可以同时选择多个： 标签\r按照我的理解按照难易度和口味设置了标签： 用具\r稍微设置一下，后期也可以进行添加： ","date":"2025-06-30","objectID":"/posts/mealie%E6%9E%84%E5%BB%BA%E4%BA%8B%E6%97%A0%E5%B7%A8%E7%BB%86%E7%9A%84%E9%A3%9F%E8%B0%B1%E7%AE%A1%E7%90%86%E7%B3%BB%E7%BB%9F/:3:2","tags":null,"title":"Mealie！构建事无巨细的食谱管理系统","uri":"/posts/mealie%E6%9E%84%E5%BB%BA%E4%BA%8B%E6%97%A0%E5%B7%A8%E7%BB%86%E7%9A%84%E9%A3%9F%E8%B0%B1%E7%AE%A1%E7%90%86%E7%B3%BB%E7%BB%9F/#分类"},{"categories":"玩转NAS","collections":["玩转NAS"],"content":"18.2 食谱数据\r食谱数据是对每个菜品的标签数据的维护。 分类\r按照我的个人理解进行了简单的分类，类别可以同时选择多个： 标签\r按照我的理解按照难易度和口味设置了标签： 用具\r稍微设置一下，后期也可以进行添加： ","date":"2025-06-30","objectID":"/posts/mealie%E6%9E%84%E5%BB%BA%E4%BA%8B%E6%97%A0%E5%B7%A8%E7%BB%86%E7%9A%84%E9%A3%9F%E8%B0%B1%E7%AE%A1%E7%90%86%E7%B3%BB%E7%BB%9F/:3:2","tags":null,"title":"Mealie！构建事无巨细的食谱管理系统","uri":"/posts/mealie%E6%9E%84%E5%BB%BA%E4%BA%8B%E6%97%A0%E5%B7%A8%E7%BB%86%E7%9A%84%E9%A3%9F%E8%B0%B1%E7%AE%A1%E7%90%86%E7%B3%BB%E7%BB%9F/#标签"},{"categories":"玩转NAS","collections":["玩转NAS"],"content":"18.2 食谱数据\r食谱数据是对每个菜品的标签数据的维护。 分类\r按照我的个人理解进行了简单的分类，类别可以同时选择多个： 标签\r按照我的理解按照难易度和口味设置了标签： 用具\r稍微设置一下，后期也可以进行添加： ","date":"2025-06-30","objectID":"/posts/mealie%E6%9E%84%E5%BB%BA%E4%BA%8B%E6%97%A0%E5%B7%A8%E7%BB%86%E7%9A%84%E9%A3%9F%E8%B0%B1%E7%AE%A1%E7%90%86%E7%B3%BB%E7%BB%9F/:3:2","tags":null,"title":"Mealie！构建事无巨细的食谱管理系统","uri":"/posts/mealie%E6%9E%84%E5%BB%BA%E4%BA%8B%E6%97%A0%E5%B7%A8%E7%BB%86%E7%9A%84%E9%A3%9F%E8%B0%B1%E7%AE%A1%E7%90%86%E7%B3%BB%E7%BB%9F/#用具"},{"categories":"玩转NAS","collections":["玩转NAS"],"content":"第 19 节 添加食谱\r信息\r设置完枚举值之后，可以开始尝试添加食谱，这里本人建议仅作为练习，尝试一下各种添加方式。对于少量的食谱，手动创建还比较方便，但如果食谱很多的情况下，就应当优先考虑使用流程化的程序进行添加。 添加食谱分为「手动创建」和「导入食谱」两种，「手动创建」可选择的字段更加多一些，「导入食谱」功能我个人使用下来感觉不是很完善，很多「手动创建」可以选择的字段，在「导入食谱」下无法使用。 ","date":"2025-06-30","objectID":"/posts/mealie%E6%9E%84%E5%BB%BA%E4%BA%8B%E6%97%A0%E5%B7%A8%E7%BB%86%E7%9A%84%E9%A3%9F%E8%B0%B1%E7%AE%A1%E7%90%86%E7%B3%BB%E7%BB%9F/:4:0","tags":null,"title":"Mealie！构建事无巨细的食谱管理系统","uri":"/posts/mealie%E6%9E%84%E5%BB%BA%E4%BA%8B%E6%97%A0%E5%B7%A8%E7%BB%86%E7%9A%84%E9%A3%9F%E8%B0%B1%E7%AE%A1%E7%90%86%E7%B3%BB%E7%BB%9F/#添加食谱"},{"categories":"玩转NAS","collections":["玩转NAS"],"content":"19.1 手动创建\r在左上角点击按钮： 给食谱添加一个不可重复的名称： 然后会进入详细设置页面，在这里可以点击「设定」，可以显示出更多的细节内容： 食材方面可以选择之前设定的枚举值进行填入，这里也可以直接创建新的枚举值并填入： 同样，分类、标签、所需用具也可以创建新的值： 做法方面也可以设置的相当的详细，如下： 营养方面可以进行的设置也很多，包括卡路里、碳水、胆固醇、脂肪、纤维、蛋白质、饱和脂肪、钠、糖、反式脂肪、不饱和脂肪： 其他的一些信息可以放到备注里面去： ","date":"2025-06-30","objectID":"/posts/mealie%E6%9E%84%E5%BB%BA%E4%BA%8B%E6%97%A0%E5%B7%A8%E7%BB%86%E7%9A%84%E9%A3%9F%E8%B0%B1%E7%AE%A1%E7%90%86%E7%B3%BB%E7%BB%9F/:4:1","tags":null,"title":"Mealie！构建事无巨细的食谱管理系统","uri":"/posts/mealie%E6%9E%84%E5%BB%BA%E4%BA%8B%E6%97%A0%E5%B7%A8%E7%BB%86%E7%9A%84%E9%A3%9F%E8%B0%B1%E7%AE%A1%E7%90%86%E7%B3%BB%E7%BB%9F/#手动创建"},{"categories":"玩转NAS","collections":["玩转NAS"],"content":"19.2 导入食谱\r导入食谱有一个「从 HTML 或者 JSON 导入」的方式，导入数据的模板可以前往这个网址查看： 信息\r我稍微尝试了一些写法之后发现，这样导入可以选择的字段比较有限，信息量不如手动创建多，我个人而言并不推荐使用这种方式。 ","date":"2025-06-30","objectID":"/posts/mealie%E6%9E%84%E5%BB%BA%E4%BA%8B%E6%97%A0%E5%B7%A8%E7%BB%86%E7%9A%84%E9%A3%9F%E8%B0%B1%E7%AE%A1%E7%90%86%E7%B3%BB%E7%BB%9F/:4:2","tags":null,"title":"Mealie！构建事无巨细的食谱管理系统","uri":"/posts/mealie%E6%9E%84%E5%BB%BA%E4%BA%8B%E6%97%A0%E5%B7%A8%E7%BB%86%E7%9A%84%E9%A3%9F%E8%B0%B1%E7%AE%A1%E7%90%86%E7%B3%BB%E7%BB%9F/#导入食谱"},{"categories":"玩转NAS","collections":["玩转NAS"],"content":"19.3 使用 API 新增食谱\r因为导入食谱可以选择的字段有限，手动创建食谱又比较麻烦，难以批量创建，我们可以使用官方提供的 API 模拟手动创建食谱的过程。 首先，点击左上角的「用户头像」可以进入「用户设置」页面，创建一个 「API 令牌」 我们查看一下应用提供的全部 API 接口，应用接入 Swagger,只需要在域名后面添加/docs即可以访问 API 接口了，比如： https://xxx.com/docs 在Recipe:CRUD列表下，我们可以看到「新增食谱」和「修改食谱」的方法： API 令牌\r使用Bearer Token将令牌输入即可： 新增食谱\r查看文档，「新增食谱」可以使用 POST 请求访问接口： https://xxx.com/api/recipes 只需要传入食谱的名称即可： 修改食谱\r查看文档，「修改食谱」需要使用PATCH请求访问： https://xxx.com/api/recipes/ce-shi-shi-pu 需要传入的参数和手动创建时相同，下面给出了一个比较详细的例子： 获取枚举值\r可以看到，上一步「修改食谱」时，凡是涉及到几个枚举值的地方，都需要传入一条完整的枚举值数据，涉及到 ID 号等。 查看文档，需要使用 GET 请求「获取枚举值」的方法主要有以下这些： https://xxx.com/api/groups/labels?page=1\u0026perPage=50 https://xxx.com/api/foods?page=1\u0026perPage=50 https://xxx.com/api/units?page=1\u0026perPage=50 https://xxx.com/api/organizers/categories?page=1\u0026perPage=50 https://xxx.com/api/organizers/tags?page=1\u0026perPage=50 https://xxx.com/api/organizers/tools?page=1\u0026perPage=50 新增枚举值\r当我们所需的枚举值不在查询结果中时，需要使用 POST 请求「新增枚举值」，主要方法如下： 新增标注： https://xxx.com/api/groups/labels 需要传入的参数： 新增食品： https://xxx.com/api/foods 需要传入的参数： 新增单位： https://xxx.com/api/units 需要传入的参数： 新增分类： https://xxx.com/api/organizers/categories 需要传入的参数： 新增标签： https://xxx.com/api/organizers/tags 需要传入的参数： 新增用具： https://xxx.com/api/organizers/tools 需要传入的参数： ","date":"2025-06-30","objectID":"/posts/mealie%E6%9E%84%E5%BB%BA%E4%BA%8B%E6%97%A0%E5%B7%A8%E7%BB%86%E7%9A%84%E9%A3%9F%E8%B0%B1%E7%AE%A1%E7%90%86%E7%B3%BB%E7%BB%9F/:4:3","tags":null,"title":"Mealie！构建事无巨细的食谱管理系统","uri":"/posts/mealie%E6%9E%84%E5%BB%BA%E4%BA%8B%E6%97%A0%E5%B7%A8%E7%BB%86%E7%9A%84%E9%A3%9F%E8%B0%B1%E7%AE%A1%E7%90%86%E7%B3%BB%E7%BB%9F/#使用-api-新增食谱"},{"categories":"玩转NAS","collections":["玩转NAS"],"content":"19.3 使用 API 新增食谱\r因为导入食谱可以选择的字段有限，手动创建食谱又比较麻烦，难以批量创建，我们可以使用官方提供的 API 模拟手动创建食谱的过程。 首先，点击左上角的「用户头像」可以进入「用户设置」页面，创建一个 「API 令牌」 我们查看一下应用提供的全部 API 接口，应用接入 Swagger,只需要在域名后面添加/docs即可以访问 API 接口了，比如： https://xxx.com/docs 在Recipe:CRUD列表下，我们可以看到「新增食谱」和「修改食谱」的方法： API 令牌\r使用Bearer Token将令牌输入即可： 新增食谱\r查看文档，「新增食谱」可以使用 POST 请求访问接口： https://xxx.com/api/recipes 只需要传入食谱的名称即可： 修改食谱\r查看文档，「修改食谱」需要使用PATCH请求访问： https://xxx.com/api/recipes/ce-shi-shi-pu 需要传入的参数和手动创建时相同，下面给出了一个比较详细的例子： 获取枚举值\r可以看到，上一步「修改食谱」时，凡是涉及到几个枚举值的地方，都需要传入一条完整的枚举值数据，涉及到 ID 号等。 查看文档，需要使用 GET 请求「获取枚举值」的方法主要有以下这些： https://xxx.com/api/groups/labels?page=1\u0026perPage=50 https://xxx.com/api/foods?page=1\u0026perPage=50 https://xxx.com/api/units?page=1\u0026perPage=50 https://xxx.com/api/organizers/categories?page=1\u0026perPage=50 https://xxx.com/api/organizers/tags?page=1\u0026perPage=50 https://xxx.com/api/organizers/tools?page=1\u0026perPage=50 新增枚举值\r当我们所需的枚举值不在查询结果中时，需要使用 POST 请求「新增枚举值」，主要方法如下： 新增标注： https://xxx.com/api/groups/labels 需要传入的参数： 新增食品： https://xxx.com/api/foods 需要传入的参数： 新增单位： https://xxx.com/api/units 需要传入的参数： 新增分类： https://xxx.com/api/organizers/categories 需要传入的参数： 新增标签： https://xxx.com/api/organizers/tags 需要传入的参数： 新增用具： https://xxx.com/api/organizers/tools 需要传入的参数： ","date":"2025-06-30","objectID":"/posts/mealie%E6%9E%84%E5%BB%BA%E4%BA%8B%E6%97%A0%E5%B7%A8%E7%BB%86%E7%9A%84%E9%A3%9F%E8%B0%B1%E7%AE%A1%E7%90%86%E7%B3%BB%E7%BB%9F/:4:3","tags":null,"title":"Mealie！构建事无巨细的食谱管理系统","uri":"/posts/mealie%E6%9E%84%E5%BB%BA%E4%BA%8B%E6%97%A0%E5%B7%A8%E7%BB%86%E7%9A%84%E9%A3%9F%E8%B0%B1%E7%AE%A1%E7%90%86%E7%B3%BB%E7%BB%9F/#api-令牌"},{"categories":"玩转NAS","collections":["玩转NAS"],"content":"19.3 使用 API 新增食谱\r因为导入食谱可以选择的字段有限，手动创建食谱又比较麻烦，难以批量创建，我们可以使用官方提供的 API 模拟手动创建食谱的过程。 首先，点击左上角的「用户头像」可以进入「用户设置」页面，创建一个 「API 令牌」 我们查看一下应用提供的全部 API 接口，应用接入 Swagger,只需要在域名后面添加/docs即可以访问 API 接口了，比如： https://xxx.com/docs 在Recipe:CRUD列表下，我们可以看到「新增食谱」和「修改食谱」的方法： API 令牌\r使用Bearer Token将令牌输入即可： 新增食谱\r查看文档，「新增食谱」可以使用 POST 请求访问接口： https://xxx.com/api/recipes 只需要传入食谱的名称即可： 修改食谱\r查看文档，「修改食谱」需要使用PATCH请求访问： https://xxx.com/api/recipes/ce-shi-shi-pu 需要传入的参数和手动创建时相同，下面给出了一个比较详细的例子： 获取枚举值\r可以看到，上一步「修改食谱」时，凡是涉及到几个枚举值的地方，都需要传入一条完整的枚举值数据，涉及到 ID 号等。 查看文档，需要使用 GET 请求「获取枚举值」的方法主要有以下这些： https://xxx.com/api/groups/labels?page=1\u0026perPage=50 https://xxx.com/api/foods?page=1\u0026perPage=50 https://xxx.com/api/units?page=1\u0026perPage=50 https://xxx.com/api/organizers/categories?page=1\u0026perPage=50 https://xxx.com/api/organizers/tags?page=1\u0026perPage=50 https://xxx.com/api/organizers/tools?page=1\u0026perPage=50 新增枚举值\r当我们所需的枚举值不在查询结果中时，需要使用 POST 请求「新增枚举值」，主要方法如下： 新增标注： https://xxx.com/api/groups/labels 需要传入的参数： 新增食品： https://xxx.com/api/foods 需要传入的参数： 新增单位： https://xxx.com/api/units 需要传入的参数： 新增分类： https://xxx.com/api/organizers/categories 需要传入的参数： 新增标签： https://xxx.com/api/organizers/tags 需要传入的参数： 新增用具： https://xxx.com/api/organizers/tools 需要传入的参数： ","date":"2025-06-30","objectID":"/posts/mealie%E6%9E%84%E5%BB%BA%E4%BA%8B%E6%97%A0%E5%B7%A8%E7%BB%86%E7%9A%84%E9%A3%9F%E8%B0%B1%E7%AE%A1%E7%90%86%E7%B3%BB%E7%BB%9F/:4:3","tags":null,"title":"Mealie！构建事无巨细的食谱管理系统","uri":"/posts/mealie%E6%9E%84%E5%BB%BA%E4%BA%8B%E6%97%A0%E5%B7%A8%E7%BB%86%E7%9A%84%E9%A3%9F%E8%B0%B1%E7%AE%A1%E7%90%86%E7%B3%BB%E7%BB%9F/#新增食谱"},{"categories":"玩转NAS","collections":["玩转NAS"],"content":"19.3 使用 API 新增食谱\r因为导入食谱可以选择的字段有限，手动创建食谱又比较麻烦，难以批量创建，我们可以使用官方提供的 API 模拟手动创建食谱的过程。 首先，点击左上角的「用户头像」可以进入「用户设置」页面，创建一个 「API 令牌」 我们查看一下应用提供的全部 API 接口，应用接入 Swagger,只需要在域名后面添加/docs即可以访问 API 接口了，比如： https://xxx.com/docs 在Recipe:CRUD列表下，我们可以看到「新增食谱」和「修改食谱」的方法： API 令牌\r使用Bearer Token将令牌输入即可： 新增食谱\r查看文档，「新增食谱」可以使用 POST 请求访问接口： https://xxx.com/api/recipes 只需要传入食谱的名称即可： 修改食谱\r查看文档，「修改食谱」需要使用PATCH请求访问： https://xxx.com/api/recipes/ce-shi-shi-pu 需要传入的参数和手动创建时相同，下面给出了一个比较详细的例子： 获取枚举值\r可以看到，上一步「修改食谱」时，凡是涉及到几个枚举值的地方，都需要传入一条完整的枚举值数据，涉及到 ID 号等。 查看文档，需要使用 GET 请求「获取枚举值」的方法主要有以下这些： https://xxx.com/api/groups/labels?page=1\u0026perPage=50 https://xxx.com/api/foods?page=1\u0026perPage=50 https://xxx.com/api/units?page=1\u0026perPage=50 https://xxx.com/api/organizers/categories?page=1\u0026perPage=50 https://xxx.com/api/organizers/tags?page=1\u0026perPage=50 https://xxx.com/api/organizers/tools?page=1\u0026perPage=50 新增枚举值\r当我们所需的枚举值不在查询结果中时，需要使用 POST 请求「新增枚举值」，主要方法如下： 新增标注： https://xxx.com/api/groups/labels 需要传入的参数： 新增食品： https://xxx.com/api/foods 需要传入的参数： 新增单位： https://xxx.com/api/units 需要传入的参数： 新增分类： https://xxx.com/api/organizers/categories 需要传入的参数： 新增标签： https://xxx.com/api/organizers/tags 需要传入的参数： 新增用具： https://xxx.com/api/organizers/tools 需要传入的参数： ","date":"2025-06-30","objectID":"/posts/mealie%E6%9E%84%E5%BB%BA%E4%BA%8B%E6%97%A0%E5%B7%A8%E7%BB%86%E7%9A%84%E9%A3%9F%E8%B0%B1%E7%AE%A1%E7%90%86%E7%B3%BB%E7%BB%9F/:4:3","tags":null,"title":"Mealie！构建事无巨细的食谱管理系统","uri":"/posts/mealie%E6%9E%84%E5%BB%BA%E4%BA%8B%E6%97%A0%E5%B7%A8%E7%BB%86%E7%9A%84%E9%A3%9F%E8%B0%B1%E7%AE%A1%E7%90%86%E7%B3%BB%E7%BB%9F/#修改食谱"},{"categories":"玩转NAS","collections":["玩转NAS"],"content":"19.3 使用 API 新增食谱\r因为导入食谱可以选择的字段有限，手动创建食谱又比较麻烦，难以批量创建，我们可以使用官方提供的 API 模拟手动创建食谱的过程。 首先，点击左上角的「用户头像」可以进入「用户设置」页面，创建一个 「API 令牌」 我们查看一下应用提供的全部 API 接口，应用接入 Swagger,只需要在域名后面添加/docs即可以访问 API 接口了，比如： https://xxx.com/docs 在Recipe:CRUD列表下，我们可以看到「新增食谱」和「修改食谱」的方法： API 令牌\r使用Bearer Token将令牌输入即可： 新增食谱\r查看文档，「新增食谱」可以使用 POST 请求访问接口： https://xxx.com/api/recipes 只需要传入食谱的名称即可： 修改食谱\r查看文档，「修改食谱」需要使用PATCH请求访问： https://xxx.com/api/recipes/ce-shi-shi-pu 需要传入的参数和手动创建时相同，下面给出了一个比较详细的例子： 获取枚举值\r可以看到，上一步「修改食谱」时，凡是涉及到几个枚举值的地方，都需要传入一条完整的枚举值数据，涉及到 ID 号等。 查看文档，需要使用 GET 请求「获取枚举值」的方法主要有以下这些： https://xxx.com/api/groups/labels?page=1\u0026perPage=50 https://xxx.com/api/foods?page=1\u0026perPage=50 https://xxx.com/api/units?page=1\u0026perPage=50 https://xxx.com/api/organizers/categories?page=1\u0026perPage=50 https://xxx.com/api/organizers/tags?page=1\u0026perPage=50 https://xxx.com/api/organizers/tools?page=1\u0026perPage=50 新增枚举值\r当我们所需的枚举值不在查询结果中时，需要使用 POST 请求「新增枚举值」，主要方法如下： 新增标注： https://xxx.com/api/groups/labels 需要传入的参数： 新增食品： https://xxx.com/api/foods 需要传入的参数： 新增单位： https://xxx.com/api/units 需要传入的参数： 新增分类： https://xxx.com/api/organizers/categories 需要传入的参数： 新增标签： https://xxx.com/api/organizers/tags 需要传入的参数： 新增用具： https://xxx.com/api/organizers/tools 需要传入的参数： ","date":"2025-06-30","objectID":"/posts/mealie%E6%9E%84%E5%BB%BA%E4%BA%8B%E6%97%A0%E5%B7%A8%E7%BB%86%E7%9A%84%E9%A3%9F%E8%B0%B1%E7%AE%A1%E7%90%86%E7%B3%BB%E7%BB%9F/:4:3","tags":null,"title":"Mealie！构建事无巨细的食谱管理系统","uri":"/posts/mealie%E6%9E%84%E5%BB%BA%E4%BA%8B%E6%97%A0%E5%B7%A8%E7%BB%86%E7%9A%84%E9%A3%9F%E8%B0%B1%E7%AE%A1%E7%90%86%E7%B3%BB%E7%BB%9F/#获取枚举值"},{"categories":"玩转NAS","collections":["玩转NAS"],"content":"19.3 使用 API 新增食谱\r因为导入食谱可以选择的字段有限，手动创建食谱又比较麻烦，难以批量创建，我们可以使用官方提供的 API 模拟手动创建食谱的过程。 首先，点击左上角的「用户头像」可以进入「用户设置」页面，创建一个 「API 令牌」 我们查看一下应用提供的全部 API 接口，应用接入 Swagger,只需要在域名后面添加/docs即可以访问 API 接口了，比如： https://xxx.com/docs 在Recipe:CRUD列表下，我们可以看到「新增食谱」和「修改食谱」的方法： API 令牌\r使用Bearer Token将令牌输入即可： 新增食谱\r查看文档，「新增食谱」可以使用 POST 请求访问接口： https://xxx.com/api/recipes 只需要传入食谱的名称即可： 修改食谱\r查看文档，「修改食谱」需要使用PATCH请求访问： https://xxx.com/api/recipes/ce-shi-shi-pu 需要传入的参数和手动创建时相同，下面给出了一个比较详细的例子： 获取枚举值\r可以看到，上一步「修改食谱」时，凡是涉及到几个枚举值的地方，都需要传入一条完整的枚举值数据，涉及到 ID 号等。 查看文档，需要使用 GET 请求「获取枚举值」的方法主要有以下这些： https://xxx.com/api/groups/labels?page=1\u0026perPage=50 https://xxx.com/api/foods?page=1\u0026perPage=50 https://xxx.com/api/units?page=1\u0026perPage=50 https://xxx.com/api/organizers/categories?page=1\u0026perPage=50 https://xxx.com/api/organizers/tags?page=1\u0026perPage=50 https://xxx.com/api/organizers/tools?page=1\u0026perPage=50 新增枚举值\r当我们所需的枚举值不在查询结果中时，需要使用 POST 请求「新增枚举值」，主要方法如下： 新增标注： https://xxx.com/api/groups/labels 需要传入的参数： 新增食品： https://xxx.com/api/foods 需要传入的参数： 新增单位： https://xxx.com/api/units 需要传入的参数： 新增分类： https://xxx.com/api/organizers/categories 需要传入的参数： 新增标签： https://xxx.com/api/organizers/tags 需要传入的参数： 新增用具： https://xxx.com/api/organizers/tools 需要传入的参数： ","date":"2025-06-30","objectID":"/posts/mealie%E6%9E%84%E5%BB%BA%E4%BA%8B%E6%97%A0%E5%B7%A8%E7%BB%86%E7%9A%84%E9%A3%9F%E8%B0%B1%E7%AE%A1%E7%90%86%E7%B3%BB%E7%BB%9F/:4:3","tags":null,"title":"Mealie！构建事无巨细的食谱管理系统","uri":"/posts/mealie%E6%9E%84%E5%BB%BA%E4%BA%8B%E6%97%A0%E5%B7%A8%E7%BB%86%E7%9A%84%E9%A3%9F%E8%B0%B1%E7%AE%A1%E7%90%86%E7%B3%BB%E7%BB%9F/#新增枚举值"},{"categories":"玩转NAS","collections":["玩转NAS"],"content":"第 20 节 总结\r根据上面列出的使用 API 新增食谱的方法，我们可以知晓，理论上，我们是可以使用一个流程化的程序来快速生成一条食谱数据的。 信息\r实际上，大部分现实中我们所见到的菜谱，并不会像 Mealie 这么详细的列举出所有可以填入的参数，很多参数是不会直接写在食谱上的，而是要根据食谱，结合常识和一定的计算才可以推断出来。 示例\r就比如，绝大部分食谱上根本不可能印有食谱的营养数据，而食物的热量、蛋白质、脂肪等参数，经过一定的推断和计算是可以算个大概的。 所以在后续，使用 AI 工具自动推算出这些数据，并且自动生成相应的数据格式并写入 Mealie 数据库才是一套更加行之有效的方式。 ","date":"2025-06-30","objectID":"/posts/mealie%E6%9E%84%E5%BB%BA%E4%BA%8B%E6%97%A0%E5%B7%A8%E7%BB%86%E7%9A%84%E9%A3%9F%E8%B0%B1%E7%AE%A1%E7%90%86%E7%B3%BB%E7%BB%9F/:5:0","tags":null,"title":"Mealie！构建事无巨细的食谱管理系统","uri":"/posts/mealie%E6%9E%84%E5%BB%BA%E4%BA%8B%E6%97%A0%E5%B7%A8%E7%BB%86%E7%9A%84%E9%A3%9F%E8%B0%B1%E7%AE%A1%E7%90%86%E7%B3%BB%E7%BB%9F/#总结"},{"categories":"旅游","collections":["超超的奇妙冒险"],"content":"第 1 节 江南水乡\r趁龙舟训练的间隙，我溜达进了朱家角古镇，首先映入眼帘的是古镇附近的一片开阔水域，岸边砖块整齐堆砌，对岸是一排排精致的独栋小楼，水面波光粼粼、清风徐徐，置身其中十分惬意。 继续往里走，便看到了朱家角的标志性打卡墙——「I Love 朱家角」造型墙，其中「I」和「Love」巧妙地组成了「朱家角」的汉字部分，设计别具匠心，让人忍不住驻足合影留念。 ","date":"2025-06-14","objectID":"/posts/%E9%97%B2%E6%9D%A5%E6%BC%AB%E6%AD%A5%E6%9C%B1%E5%AE%B6%E8%A7%92%E6%B0%B4%E4%B9%A1%E7%83%9F%E9%9B%A8%E5%85%A5%E6%A2%A6%E6%9D%A5/:1:0","tags":["旅游"],"title":"闲来漫步朱家角，水乡烟雨入梦来","uri":"/posts/%E9%97%B2%E6%9D%A5%E6%BC%AB%E6%AD%A5%E6%9C%B1%E5%AE%B6%E8%A7%92%E6%B0%B4%E4%B9%A1%E7%83%9F%E9%9B%A8%E5%85%A5%E6%A2%A6%E6%9D%A5/#江南水乡"},{"categories":"旅游","collections":["超超的奇妙冒险"],"content":"第 2 节 课植园\r走进课植园的第一进厅堂，抬头便见匾额上题写着「蓬荜生辉」四个大字，厅内方桌两侧各置一只青花瓷瓶，整间屋子弥漫着浓厚的文人雅士气息。 继续往里走，另一处厅堂的匾额上书「抚琴听风」，一面硕大的金属屏风立于堂前，仿佛能听见琴声随风飘荡，令人心驰神往。 穿过厅堂步入庭院，眼前豁然开朗——亭台轩榭错落有致，一汪碧波清澈见底，亭台的台阶缓缓没入水中，倒映着天光云影，极富诗意。 池畔立着一座造型别致的石雕，表面千疮百孔、玲珑剔透，仿佛经岁月雕琢而成，别具一番风骨。 不远处一座拱桥横跨水面，弧线优美流畅，桥身与水中的倒影相接，恰如一个完美的圆环，静卧在碧波之上。 移步至室内，一组蜡像栩栩如生地复刻了文人墨客的日常生活，只见一位雅士伏案挥毫、笔走龙蛇，将满腹才情倾注于纸上。 旁边还有几位文人围坐品茗，一人执杯细啜、神情悠然，其余或凝神静听、或低语交谈，生动再现了当年课植园中诗酒唱和的闲雅时光。 ","date":"2025-06-14","objectID":"/posts/%E9%97%B2%E6%9D%A5%E6%BC%AB%E6%AD%A5%E6%9C%B1%E5%AE%B6%E8%A7%92%E6%B0%B4%E4%B9%A1%E7%83%9F%E9%9B%A8%E5%85%A5%E6%A2%A6%E6%9D%A5/:2:0","tags":["旅游"],"title":"闲来漫步朱家角，水乡烟雨入梦来","uri":"/posts/%E9%97%B2%E6%9D%A5%E6%BC%AB%E6%AD%A5%E6%9C%B1%E5%AE%B6%E8%A7%92%E6%B0%B4%E4%B9%A1%E7%83%9F%E9%9B%A8%E5%85%A5%E6%A2%A6%E6%9D%A5/#课植园"},{"categories":"旅游","collections":["超超的奇妙冒险"],"content":"第 3 节 老街与码头\r漫步在青浦朱家角的老街上，两侧店铺林立，传统小吃的香味与手工艺品店里的琳琅满目交织在一起，充满了江南古镇独有的烟火气息。继续往前走，便来到了码头边，从这里可以乘船游览整个朱家角，沿着蜿蜒的水道缓缓前行，两岸白墙黛瓦、小桥流水，仿佛一幅流动的水墨画卷徐徐展开。 ","date":"2025-06-14","objectID":"/posts/%E9%97%B2%E6%9D%A5%E6%BC%AB%E6%AD%A5%E6%9C%B1%E5%AE%B6%E8%A7%92%E6%B0%B4%E4%B9%A1%E7%83%9F%E9%9B%A8%E5%85%A5%E6%A2%A6%E6%9D%A5/:3:0","tags":["旅游"],"title":"闲来漫步朱家角，水乡烟雨入梦来","uri":"/posts/%E9%97%B2%E6%9D%A5%E6%BC%AB%E6%AD%A5%E6%9C%B1%E5%AE%B6%E8%A7%92%E6%B0%B4%E4%B9%A1%E7%83%9F%E9%9B%A8%E5%85%A5%E6%A2%A6%E6%9D%A5/#老街与码头"},{"categories":"旅游","collections":["超超的奇妙冒险"],"content":"第 4 节 放生桥与泰安桥\r沿着老街走下去，映入眼帘的便是明代古桥——放生桥，它横跨于宽阔的水面上，是上海地区最长、最高的古桥，气势颇为壮观。 登上放生桥远眺，河道两侧白墙黑瓦的古典民居沿水而立，宽阔的水路上星星点点漂着几艘船只，水乡全景尽收眼底，视野格外开阔。 换个角度望去，另一边的水路同样宽广，远方的地平线清晰可见，船只悠然划过，两岸人家错落有致，一派宁静悠远的江南韵味。 离开放生桥，来到不远处的泰安桥，这座建于明代万历年间的古桥是朱家角最陡的一座。从桥上俯瞰，河道变得狭长而曲折，两岸房屋层层叠叠，遮挡住了远方的天际线，别有一番幽深静谧的景致。 ","date":"2025-06-14","objectID":"/posts/%E9%97%B2%E6%9D%A5%E6%BC%AB%E6%AD%A5%E6%9C%B1%E5%AE%B6%E8%A7%92%E6%B0%B4%E4%B9%A1%E7%83%9F%E9%9B%A8%E5%85%A5%E6%A2%A6%E6%9D%A5/:4:0","tags":["旅游"],"title":"闲来漫步朱家角，水乡烟雨入梦来","uri":"/posts/%E9%97%B2%E6%9D%A5%E6%BC%AB%E6%AD%A5%E6%9C%B1%E5%AE%B6%E8%A7%92%E6%B0%B4%E4%B9%A1%E7%83%9F%E9%9B%A8%E5%85%A5%E6%A2%A6%E6%9D%A5/#放生桥与泰安桥"},{"categories":"旅游","collections":["超超的奇妙冒险"],"content":"第 5 节 大清邮局旧址\r走近朱家角大清邮局旧址，墙体斑驳残缺，露出的红砖在岁月的侵蚀下显得格外沧桑，牌匾上「朱家角邮局」几个大字静静诉说着百年邮政的厚重历史。 继续向前，一尊黑色的邮筒矗立在门前，仿佛一位沉默的守望者，见证着无数信件的往来与时光的流转。 踏入邮局内部，一枚枚老旧的邮票静静地陈列其中，泛黄的纸面与模糊的图案透露出浓郁的历史气息。 再细细端详那些盖着邮戳的信封，一枚枚深浅不一的印记承载着往昔的温度，让人不禁遥想那个车马慢、书信远的年代。 ","date":"2025-06-14","objectID":"/posts/%E9%97%B2%E6%9D%A5%E6%BC%AB%E6%AD%A5%E6%9C%B1%E5%AE%B6%E8%A7%92%E6%B0%B4%E4%B9%A1%E7%83%9F%E9%9B%A8%E5%85%A5%E6%A2%A6%E6%9D%A5/:5:0","tags":["旅游"],"title":"闲来漫步朱家角，水乡烟雨入梦来","uri":"/posts/%E9%97%B2%E6%9D%A5%E6%BC%AB%E6%AD%A5%E6%9C%B1%E5%AE%B6%E8%A7%92%E6%B0%B4%E4%B9%A1%E7%83%9F%E9%9B%A8%E5%85%A5%E6%A2%A6%E6%9D%A5/#大清邮局旧址"},{"categories":"健身","collections":null,"content":"第 1 节 概述\r摘要\r由于最近本人参加了某个划龙舟活动，进行了高强度的划船训练，因而身体出现了全身性的酸痛。为了放松肌肉，本人拿出了自己压箱底的筋膜枪，并挨个试验了各种头，结合自身的感受，记录一下筋膜枪的使用。 我买的筋膜枪有 6 个头，分别是 2 个球形头 1 个气垫头 1 个扁平头 1 个 U型头 1 个子弹头 ","date":"2025-05-17","objectID":"/posts/%E4%BD%BF%E7%94%A8%E7%AD%8B%E8%86%9C%E6%9E%AA%E6%94%BE%E6%9D%BE%E8%82%8C%E8%82%89/:1:0","tags":["健身"],"title":"使用筋膜枪放松肌肉","uri":"/posts/%E4%BD%BF%E7%94%A8%E7%AD%8B%E8%86%9C%E6%9E%AA%E6%94%BE%E6%9D%BE%E8%82%8C%E8%82%89/#概述"},{"categories":"健身","collections":null,"content":"第 2 节 各种头的选择\r","date":"2025-05-17","objectID":"/posts/%E4%BD%BF%E7%94%A8%E7%AD%8B%E8%86%9C%E6%9E%AA%E6%94%BE%E6%9D%BE%E8%82%8C%E8%82%89/:2:0","tags":["健身"],"title":"使用筋膜枪放松肌肉","uri":"/posts/%E4%BD%BF%E7%94%A8%E7%AD%8B%E8%86%9C%E6%9E%AA%E6%94%BE%E6%9D%BE%E8%82%8C%E8%82%89/#各种头的选择"},{"categories":"健身","collections":null,"content":"2.1 气垫头\r气垫头的穿透力最小，有足够用的缓冲，适合一些使用筋膜枪容易感到不适的部位，比如，用在胸部可以有效缓解呼吸的不适感，用在臀部可以有效缓解肠道的不适感，用在腰部可以有效缓解腰部的不适感。 ","date":"2025-05-17","objectID":"/posts/%E4%BD%BF%E7%94%A8%E7%AD%8B%E8%86%9C%E6%9E%AA%E6%94%BE%E6%9D%BE%E8%82%8C%E8%82%89/:2:1","tags":["健身"],"title":"使用筋膜枪放松肌肉","uri":"/posts/%E4%BD%BF%E7%94%A8%E7%AD%8B%E8%86%9C%E6%9E%AA%E6%94%BE%E6%9D%BE%E8%82%8C%E8%82%89/#气垫头"},{"categories":"健身","collections":null,"content":"2.2 球形头\r球形头是比较通用的头，穿透力适中，大的球形头比较适合用来放松腿部，小的球形头比较适合用来放手手臂。 ","date":"2025-05-17","objectID":"/posts/%E4%BD%BF%E7%94%A8%E7%AD%8B%E8%86%9C%E6%9E%AA%E6%94%BE%E6%9D%BE%E8%82%8C%E8%82%89/:2:2","tags":["健身"],"title":"使用筋膜枪放松肌肉","uri":"/posts/%E4%BD%BF%E7%94%A8%E7%AD%8B%E8%86%9C%E6%9E%AA%E6%94%BE%E6%9D%BE%E8%82%8C%E8%82%89/#球形头"},{"categories":"健身","collections":null,"content":"2.3 扁平头\r扁平头比球形头穿透力稍强，适用范围基本相同，可以用来放松手臂小臂、小腿侧面。 ","date":"2025-05-17","objectID":"/posts/%E4%BD%BF%E7%94%A8%E7%AD%8B%E8%86%9C%E6%9E%AA%E6%94%BE%E6%9D%BE%E8%82%8C%E8%82%89/:2:3","tags":["健身"],"title":"使用筋膜枪放松肌肉","uri":"/posts/%E4%BD%BF%E7%94%A8%E7%AD%8B%E8%86%9C%E6%9E%AA%E6%94%BE%E6%9D%BE%E8%82%8C%E8%82%89/#扁平头"},{"categories":"健身","collections":null,"content":"2.4 U 型头\rU 型头的穿透力比较强，适合放松一些稍硬的肌肉，比如，小腿后侧、脊椎两侧、肩部。 ","date":"2025-05-17","objectID":"/posts/%E4%BD%BF%E7%94%A8%E7%AD%8B%E8%86%9C%E6%9E%AA%E6%94%BE%E6%9D%BE%E8%82%8C%E8%82%89/:2:4","tags":["健身"],"title":"使用筋膜枪放松肌肉","uri":"/posts/%E4%BD%BF%E7%94%A8%E7%AD%8B%E8%86%9C%E6%9E%AA%E6%94%BE%E6%9D%BE%E8%82%8C%E8%82%89/#u-型头"},{"categories":"健身","collections":null,"content":"2.5 子弹头\r子弹头的穿透力是最强的，适合对于痛点的放松。 ","date":"2025-05-17","objectID":"/posts/%E4%BD%BF%E7%94%A8%E7%AD%8B%E8%86%9C%E6%9E%AA%E6%94%BE%E6%9D%BE%E8%82%8C%E8%82%89/:2:5","tags":["健身"],"title":"使用筋膜枪放松肌肉","uri":"/posts/%E4%BD%BF%E7%94%A8%E7%AD%8B%E8%86%9C%E6%9E%AA%E6%94%BE%E6%9D%BE%E8%82%8C%E8%82%89/#子弹头"},{"categories":"机器学习","collections":["机器学习基础"],"content":"第 10 节 概述\r数据规约：产生更小的但保持原数据完整性的新数据集，在规约后的数据集上进行分析和挖掘将更有效率。 数据规约的意义在于： 降低无效、错误数据对建模的影响，提高建模的准确性。 少量且具代表性的数据将大幅缩减数据挖掘所需的时间。 降低储存数据的成本。 ","date":"2025-05-15","objectID":"/posts/%E6%9C%BA%E5%99%A8%E5%AD%A6%E4%B9%A0%E5%9F%BA%E7%A1%80~07.%E6%95%B0%E6%8D%AE%E8%A7%84%E7%BA%A6/:1:0","tags":["机器学习"],"title":"机器学习基础~07.数据规约","uri":"/posts/%E6%9C%BA%E5%99%A8%E5%AD%A6%E4%B9%A0%E5%9F%BA%E7%A1%80~07.%E6%95%B0%E6%8D%AE%E8%A7%84%E7%BA%A6/#概述"},{"categories":"机器学习","collections":["机器学习基础"],"content":"第 11 节 属性规约\r流形学习(manifold learning) ：现实世界中，许多数据集存在于高维空间中，但是这些数据可能分布在比数据维度低得多的流形上。流形学习的目标是将这种复杂的高维数据映射到一个更低维的表示，以便更好地理解数据的内在结构、降低噪声影响以及进行可视化和分析。 属性规约：通过属性合并创建新属性维数，或者直接通过删除不相关的属性维数来减少数据维数，从而提高数据挖掘的效率、降低计算成本。属性规约的目标是寻找出最小的属性子集并确保新数据子集的概率分布尽可能地接近原来数据集的概率分布。 ","date":"2025-05-15","objectID":"/posts/%E6%9C%BA%E5%99%A8%E5%AD%A6%E4%B9%A0%E5%9F%BA%E7%A1%80~07.%E6%95%B0%E6%8D%AE%E8%A7%84%E7%BA%A6/:2:0","tags":["机器学习"],"title":"机器学习基础~07.数据规约","uri":"/posts/%E6%9C%BA%E5%99%A8%E5%AD%A6%E4%B9%A0%E5%9F%BA%E7%A1%80~07.%E6%95%B0%E6%8D%AE%E8%A7%84%E7%BA%A6/#属性规约"},{"categories":"机器学习","collections":["机器学习基础"],"content":"11.1 特征选择\r过滤法(Filter)\r单变量\r当单个变量的值符合以下特征时，可以剔除掉该变量： 缺失百分比(Missing Percentage)：缺失样本比例过多且难以填补。 方差(Variance)：连续型变量的方差接近于0，说明其特征值趋向于单一值的状态，对模型帮助不大。 频数(Frequency)：类别型变量的枚举值集中在单一某枚举值上。 import numpy as np from sklearn.datasets import load_iris from sklearn.feature_selection import VarianceThreshold # 加载示例数据（鸢尾花数据集） data = load_iris() X = data.data # 特征 y = data.target # 目标类别 # 低方差滤波 variance_selector = VarianceThreshold(threshold=0.5) X_low_variance = variance_selector.fit_transform(X) 多变量\r多个变量之间，有以下情形： 自变量与自变量之间：删除高度相关的特征，避免多重共线性。 自变量和因变量之间：相关性越高，说明特征对模型预测目标更重要，建议保留。 连续型 vs 连续型\r皮尔逊相关系数(Pearson Correlation Coefficient)：需要两个变量都服从正态分布，皮尔逊相关系数反映两个变量的线性相关程度，大于 0 的时候表示两者正相关，小于 0 的时候表示两者负相关。当两个变量线性相关时，相关系数趋于 1 或 -1，正负号指向正负相关关系。 $$ r=\\frac{\\sum\\left(X_i-\\bar{X}\\right)\\left(Y_i-\\bar{Y}\\right)}{\\sqrt{\\sum\\left(X_i-\\bar{X}\\right)^2 \\sum\\left(Y_i-\\bar{Y}\\right)^2}} $$ 斯皮尔曼相关系数(Spearman’s Rank Correlation Coefficient)：用于衡量两个变量的单调关系（不一定是线性关系），适用于顺序数据或不满足正态分布的连续数据。它基于变量的排序（Rank） 计算，而非原始数据值。 #特征选择（pearson 相关系数法） from sklearn.datasets import load_iris from sklearn.feature_selection import SelectKBest from scipy.stats import pearsonr import numpy as np import pandas as pd iris = load_iris() x = pd.DataFrame(iris.data) y = pd.Series(iris.target) features = iris.feature_names # 两种方法都是 pearson 相关系数法 selector = SelectKBest(lambda X,Y:x.apply(lambda x:x.corr(y)),k=3).fit(x,y) selector = SelectKBest(lambda X,Y:np.array([pearsonr(X[:,i],Y)[0] for i in range(4)]),k=3).fit(x,y) new_x = selector.transform(x) support = [*zip(features,selector.get_support())] 连续型 vs 类别型\r方差分析(Analysis of variance, ANOVA)：检验不同组下的平均数是否存在显著差异。 方差分析前需要满足3个假设: 每组样本具备方差同质性 组内样本服从正态分布 样本间需要独立。 问题\r判断 1、2 和 3 班的同学的数学平均分是否有显著区别？ 班级为类别型变量，数学分数为连续型变量，如果班级与数学分数有相关性，比如 1 班同学数学会更好些，则说明不同班的数学平均分有显著区别。 零假设：三个班的数学分数没有显著区别。 验证方式：看组间方差是否大于组内方差，如果组间方差 \u003e 组内方差，说明存在至少一个分布相对于其他分布较远，则可以考虑拒绝零假设。 肯德尔等级相关系数(Kendall tau rank correlation coefficient) 问题\r评价学历与工资的相关性 肯德尔系数会对按学历对样本排序： 若学历和工资排名相同，则Kendall系数为1，两变量正相关； 若学历和工资完全相反，则系数为-1，两变量负相关； 而如果学历和工资完全独立，系数为0。 类别型vs类别型\r卡方检验(Chi-squared Test)：用于检验两个类别型变量之间的相关性。零假设：两变量之间不相关。卡方值高，说明两变量之间具有相关性的可能性更大。 互信息(Mutual Information)：衡量变量之间相互依赖程度 #载入数据 from sklearn.datasets import load_iris #特征选择 from sklearn.feature_selection import SelectKBest #卡方检验 from sklearn.feature_selection import chi2 iris = load_iris() x = iris.data y = iris.target features = iris.feature_names selector = SelectKBest(chi2,k=2).fit(x,y) support = {k:v for k,v in zip(features,selector.get_support())} 包裹法(Wrapper)\r完全搜索\r完全搜索：遍历所有可能组合的特征子集，然后输入模型，选择最佳模型分数的特征子集。不推荐使用，计算开销过大。 启发式搜索\r启发式搜索：利用启发式信息不断缩小搜索空间的方法。在特征选择中，模型分数或特征权重可作为启发式信息。 前向特征选择（Forward Feature Selection）： 从空特征集开始，逐步添加对模型性能有贡献的特征，直到达到所需的特征数量或达到最佳性能。 反向特征消除（Recursive Feature Elimination，RFE）： 从所有特征开始，反复拟合模型并排除不重要的特征，直到达到所需的特征数量。 基模型可以是 LR、决策树、SVM等。 import numpy as np from sklearn.datasets import load_iris from sklearn.feature_selection import SelectKBest,mutual_info_classif from sklearn.feature_selection import RFE # 反向特征消除 rfe_selector = RFE(estimator=rf_classifier, n_features_to_select=2) X_selected = rfe_selector.fit_transform(X, y) # 使用互信息进行前向特征选择 num_features_to_select = 2 selector = SelectKBest(score_func=mutual_info_classif, k=num_features_to_select) X_selected = selector.fit_transform(X, y) 随机搜索\r随机特征子集：随机选择多个特征子集，然后分别评估模型表现，选择评估分数高的特征子集。 随机目标 (Null Importance) 在原始数据集运行模型获取特征重要性; shuffle 多次标签，每次 shuffle 后获取假标签下的特征重要性; 计算真假标签下的特征重要性差异，并基于差异，筛选特征。 问题\r随机目标 (Null Importance) 为什么能够筛选出真正重要的特征？ 对于真正强健、稳定且重要的特征，在真标签下特征很重要，但一旦标签打乱，这些优质特征的重要性就会变差。 相反地，如果某特征在原始标签下表现一般，但打乱标签后，居然重要性上升，那么这个特征反而不靠谱，应当剔除掉。 举一个简单易懂的例子： 示例\r把 userID 作为特征加入模型，预测不同 userID 属于哪类消费人群，显然这个特征对于预测没有任何作用。 但是对于一个过拟合的模型，它可以会学到 userID 到消费人群的直接映射关系，相当于模型直接记住了这个userID是什么消费人群。如果把标签打乱，模型依然会把 userID 直接映射到打乱的标签上，不管是真标签还是假标签，userID 都是最重要的特征。 嵌入法(Embedded)\r嵌入法：是将特征选择过程与学习器训练过程融为一体，二者在同一优化过程中完成，在学习器训练过程中自动地进行了特征选择。 常见的嵌入法： 基于L1/L2惩罚项的 基于SVM的 基于树/森林的特征选择。 分类\rimport numpy as np from sklearn.datasets import load_iris from sklearn.feature_selection import SelectFromModel from sklearn.linear_model impor","date":"2025-05-15","objectID":"/posts/%E6%9C%BA%E5%99%A8%E5%AD%A6%E4%B9%A0%E5%9F%BA%E7%A1%80~07.%E6%95%B0%E6%8D%AE%E8%A7%84%E7%BA%A6/:2:1","tags":["机器学习"],"title":"机器学习基础~07.数据规约","uri":"/posts/%E6%9C%BA%E5%99%A8%E5%AD%A6%E4%B9%A0%E5%9F%BA%E7%A1%80~07.%E6%95%B0%E6%8D%AE%E8%A7%84%E7%BA%A6/#特征选择"},{"categories":"机器学习","collections":["机器学习基础"],"content":"11.1 特征选择\r过滤法(Filter)\r单变量\r当单个变量的值符合以下特征时，可以剔除掉该变量： 缺失百分比(Missing Percentage)：缺失样本比例过多且难以填补。 方差(Variance)：连续型变量的方差接近于0，说明其特征值趋向于单一值的状态，对模型帮助不大。 频数(Frequency)：类别型变量的枚举值集中在单一某枚举值上。 import numpy as np from sklearn.datasets import load_iris from sklearn.feature_selection import VarianceThreshold # 加载示例数据（鸢尾花数据集） data = load_iris() X = data.data # 特征 y = data.target # 目标类别 # 低方差滤波 variance_selector = VarianceThreshold(threshold=0.5) X_low_variance = variance_selector.fit_transform(X) 多变量\r多个变量之间，有以下情形： 自变量与自变量之间：删除高度相关的特征，避免多重共线性。 自变量和因变量之间：相关性越高，说明特征对模型预测目标更重要，建议保留。 连续型 vs 连续型\r皮尔逊相关系数(Pearson Correlation Coefficient)：需要两个变量都服从正态分布，皮尔逊相关系数反映两个变量的线性相关程度，大于 0 的时候表示两者正相关，小于 0 的时候表示两者负相关。当两个变量线性相关时，相关系数趋于 1 或 -1，正负号指向正负相关关系。 $$ r=\\frac{\\sum\\left(X_i-\\bar{X}\\right)\\left(Y_i-\\bar{Y}\\right)}{\\sqrt{\\sum\\left(X_i-\\bar{X}\\right)^2 \\sum\\left(Y_i-\\bar{Y}\\right)^2}} $$ 斯皮尔曼相关系数(Spearman’s Rank Correlation Coefficient)：用于衡量两个变量的单调关系（不一定是线性关系），适用于顺序数据或不满足正态分布的连续数据。它基于变量的排序（Rank） 计算，而非原始数据值。 #特征选择（pearson 相关系数法） from sklearn.datasets import load_iris from sklearn.feature_selection import SelectKBest from scipy.stats import pearsonr import numpy as np import pandas as pd iris = load_iris() x = pd.DataFrame(iris.data) y = pd.Series(iris.target) features = iris.feature_names # 两种方法都是 pearson 相关系数法 selector = SelectKBest(lambda X,Y:x.apply(lambda x:x.corr(y)),k=3).fit(x,y) selector = SelectKBest(lambda X,Y:np.array([pearsonr(X[:,i],Y)[0] for i in range(4)]),k=3).fit(x,y) new_x = selector.transform(x) support = [*zip(features,selector.get_support())] 连续型 vs 类别型\r方差分析(Analysis of variance, ANOVA)：检验不同组下的平均数是否存在显著差异。 方差分析前需要满足3个假设: 每组样本具备方差同质性 组内样本服从正态分布 样本间需要独立。 问题\r判断 1、2 和 3 班的同学的数学平均分是否有显著区别？ 班级为类别型变量，数学分数为连续型变量，如果班级与数学分数有相关性，比如 1 班同学数学会更好些，则说明不同班的数学平均分有显著区别。 零假设：三个班的数学分数没有显著区别。 验证方式：看组间方差是否大于组内方差，如果组间方差 \u003e 组内方差，说明存在至少一个分布相对于其他分布较远，则可以考虑拒绝零假设。 肯德尔等级相关系数(Kendall tau rank correlation coefficient) 问题\r评价学历与工资的相关性 肯德尔系数会对按学历对样本排序： 若学历和工资排名相同，则Kendall系数为1，两变量正相关； 若学历和工资完全相反，则系数为-1，两变量负相关； 而如果学历和工资完全独立，系数为0。 类别型vs类别型\r卡方检验(Chi-squared Test)：用于检验两个类别型变量之间的相关性。零假设：两变量之间不相关。卡方值高，说明两变量之间具有相关性的可能性更大。 互信息(Mutual Information)：衡量变量之间相互依赖程度 #载入数据 from sklearn.datasets import load_iris #特征选择 from sklearn.feature_selection import SelectKBest #卡方检验 from sklearn.feature_selection import chi2 iris = load_iris() x = iris.data y = iris.target features = iris.feature_names selector = SelectKBest(chi2,k=2).fit(x,y) support = {k:v for k,v in zip(features,selector.get_support())} 包裹法(Wrapper)\r完全搜索\r完全搜索：遍历所有可能组合的特征子集，然后输入模型，选择最佳模型分数的特征子集。不推荐使用，计算开销过大。 启发式搜索\r启发式搜索：利用启发式信息不断缩小搜索空间的方法。在特征选择中，模型分数或特征权重可作为启发式信息。 前向特征选择（Forward Feature Selection）： 从空特征集开始，逐步添加对模型性能有贡献的特征，直到达到所需的特征数量或达到最佳性能。 反向特征消除（Recursive Feature Elimination，RFE）： 从所有特征开始，反复拟合模型并排除不重要的特征，直到达到所需的特征数量。 基模型可以是 LR、决策树、SVM等。 import numpy as np from sklearn.datasets import load_iris from sklearn.feature_selection import SelectKBest,mutual_info_classif from sklearn.feature_selection import RFE # 反向特征消除 rfe_selector = RFE(estimator=rf_classifier, n_features_to_select=2) X_selected = rfe_selector.fit_transform(X, y) # 使用互信息进行前向特征选择 num_features_to_select = 2 selector = SelectKBest(score_func=mutual_info_classif, k=num_features_to_select) X_selected = selector.fit_transform(X, y) 随机搜索\r随机特征子集：随机选择多个特征子集，然后分别评估模型表现，选择评估分数高的特征子集。 随机目标 (Null Importance) 在原始数据集运行模型获取特征重要性; shuffle 多次标签，每次 shuffle 后获取假标签下的特征重要性; 计算真假标签下的特征重要性差异，并基于差异，筛选特征。 问题\r随机目标 (Null Importance) 为什么能够筛选出真正重要的特征？ 对于真正强健、稳定且重要的特征，在真标签下特征很重要，但一旦标签打乱，这些优质特征的重要性就会变差。 相反地，如果某特征在原始标签下表现一般，但打乱标签后，居然重要性上升，那么这个特征反而不靠谱，应当剔除掉。 举一个简单易懂的例子： 示例\r把 userID 作为特征加入模型，预测不同 userID 属于哪类消费人群，显然这个特征对于预测没有任何作用。 但是对于一个过拟合的模型，它可以会学到 userID 到消费人群的直接映射关系，相当于模型直接记住了这个userID是什么消费人群。如果把标签打乱，模型依然会把 userID 直接映射到打乱的标签上，不管是真标签还是假标签，userID 都是最重要的特征。 嵌入法(Embedded)\r嵌入法：是将特征选择过程与学习器训练过程融为一体，二者在同一优化过程中完成，在学习器训练过程中自动地进行了特征选择。 常见的嵌入法： 基于L1/L2惩罚项的 基于SVM的 基于树/森林的特征选择。 分类\rimport numpy as np from sklearn.datasets import load_iris from sklearn.feature_selection import SelectFromModel from sklearn.linear_model impor","date":"2025-05-15","objectID":"/posts/%E6%9C%BA%E5%99%A8%E5%AD%A6%E4%B9%A0%E5%9F%BA%E7%A1%80~07.%E6%95%B0%E6%8D%AE%E8%A7%84%E7%BA%A6/:2:1","tags":["机器学习"],"title":"机器学习基础~07.数据规约","uri":"/posts/%E6%9C%BA%E5%99%A8%E5%AD%A6%E4%B9%A0%E5%9F%BA%E7%A1%80~07.%E6%95%B0%E6%8D%AE%E8%A7%84%E7%BA%A6/#过滤法filter"},{"categories":"机器学习","collections":["机器学习基础"],"content":"11.1 特征选择\r过滤法(Filter)\r单变量\r当单个变量的值符合以下特征时，可以剔除掉该变量： 缺失百分比(Missing Percentage)：缺失样本比例过多且难以填补。 方差(Variance)：连续型变量的方差接近于0，说明其特征值趋向于单一值的状态，对模型帮助不大。 频数(Frequency)：类别型变量的枚举值集中在单一某枚举值上。 import numpy as np from sklearn.datasets import load_iris from sklearn.feature_selection import VarianceThreshold # 加载示例数据（鸢尾花数据集） data = load_iris() X = data.data # 特征 y = data.target # 目标类别 # 低方差滤波 variance_selector = VarianceThreshold(threshold=0.5) X_low_variance = variance_selector.fit_transform(X) 多变量\r多个变量之间，有以下情形： 自变量与自变量之间：删除高度相关的特征，避免多重共线性。 自变量和因变量之间：相关性越高，说明特征对模型预测目标更重要，建议保留。 连续型 vs 连续型\r皮尔逊相关系数(Pearson Correlation Coefficient)：需要两个变量都服从正态分布，皮尔逊相关系数反映两个变量的线性相关程度，大于 0 的时候表示两者正相关，小于 0 的时候表示两者负相关。当两个变量线性相关时，相关系数趋于 1 或 -1，正负号指向正负相关关系。 $$ r=\\frac{\\sum\\left(X_i-\\bar{X}\\right)\\left(Y_i-\\bar{Y}\\right)}{\\sqrt{\\sum\\left(X_i-\\bar{X}\\right)^2 \\sum\\left(Y_i-\\bar{Y}\\right)^2}} $$ 斯皮尔曼相关系数(Spearman’s Rank Correlation Coefficient)：用于衡量两个变量的单调关系（不一定是线性关系），适用于顺序数据或不满足正态分布的连续数据。它基于变量的排序（Rank） 计算，而非原始数据值。 #特征选择（pearson 相关系数法） from sklearn.datasets import load_iris from sklearn.feature_selection import SelectKBest from scipy.stats import pearsonr import numpy as np import pandas as pd iris = load_iris() x = pd.DataFrame(iris.data) y = pd.Series(iris.target) features = iris.feature_names # 两种方法都是 pearson 相关系数法 selector = SelectKBest(lambda X,Y:x.apply(lambda x:x.corr(y)),k=3).fit(x,y) selector = SelectKBest(lambda X,Y:np.array([pearsonr(X[:,i],Y)[0] for i in range(4)]),k=3).fit(x,y) new_x = selector.transform(x) support = [*zip(features,selector.get_support())] 连续型 vs 类别型\r方差分析(Analysis of variance, ANOVA)：检验不同组下的平均数是否存在显著差异。 方差分析前需要满足3个假设: 每组样本具备方差同质性 组内样本服从正态分布 样本间需要独立。 问题\r判断 1、2 和 3 班的同学的数学平均分是否有显著区别？ 班级为类别型变量，数学分数为连续型变量，如果班级与数学分数有相关性，比如 1 班同学数学会更好些，则说明不同班的数学平均分有显著区别。 零假设：三个班的数学分数没有显著区别。 验证方式：看组间方差是否大于组内方差，如果组间方差 \u003e 组内方差，说明存在至少一个分布相对于其他分布较远，则可以考虑拒绝零假设。 肯德尔等级相关系数(Kendall tau rank correlation coefficient) 问题\r评价学历与工资的相关性 肯德尔系数会对按学历对样本排序： 若学历和工资排名相同，则Kendall系数为1，两变量正相关； 若学历和工资完全相反，则系数为-1，两变量负相关； 而如果学历和工资完全独立，系数为0。 类别型vs类别型\r卡方检验(Chi-squared Test)：用于检验两个类别型变量之间的相关性。零假设：两变量之间不相关。卡方值高，说明两变量之间具有相关性的可能性更大。 互信息(Mutual Information)：衡量变量之间相互依赖程度 #载入数据 from sklearn.datasets import load_iris #特征选择 from sklearn.feature_selection import SelectKBest #卡方检验 from sklearn.feature_selection import chi2 iris = load_iris() x = iris.data y = iris.target features = iris.feature_names selector = SelectKBest(chi2,k=2).fit(x,y) support = {k:v for k,v in zip(features,selector.get_support())} 包裹法(Wrapper)\r完全搜索\r完全搜索：遍历所有可能组合的特征子集，然后输入模型，选择最佳模型分数的特征子集。不推荐使用，计算开销过大。 启发式搜索\r启发式搜索：利用启发式信息不断缩小搜索空间的方法。在特征选择中，模型分数或特征权重可作为启发式信息。 前向特征选择（Forward Feature Selection）： 从空特征集开始，逐步添加对模型性能有贡献的特征，直到达到所需的特征数量或达到最佳性能。 反向特征消除（Recursive Feature Elimination，RFE）： 从所有特征开始，反复拟合模型并排除不重要的特征，直到达到所需的特征数量。 基模型可以是 LR、决策树、SVM等。 import numpy as np from sklearn.datasets import load_iris from sklearn.feature_selection import SelectKBest,mutual_info_classif from sklearn.feature_selection import RFE # 反向特征消除 rfe_selector = RFE(estimator=rf_classifier, n_features_to_select=2) X_selected = rfe_selector.fit_transform(X, y) # 使用互信息进行前向特征选择 num_features_to_select = 2 selector = SelectKBest(score_func=mutual_info_classif, k=num_features_to_select) X_selected = selector.fit_transform(X, y) 随机搜索\r随机特征子集：随机选择多个特征子集，然后分别评估模型表现，选择评估分数高的特征子集。 随机目标 (Null Importance) 在原始数据集运行模型获取特征重要性; shuffle 多次标签，每次 shuffle 后获取假标签下的特征重要性; 计算真假标签下的特征重要性差异，并基于差异，筛选特征。 问题\r随机目标 (Null Importance) 为什么能够筛选出真正重要的特征？ 对于真正强健、稳定且重要的特征，在真标签下特征很重要，但一旦标签打乱，这些优质特征的重要性就会变差。 相反地，如果某特征在原始标签下表现一般，但打乱标签后，居然重要性上升，那么这个特征反而不靠谱，应当剔除掉。 举一个简单易懂的例子： 示例\r把 userID 作为特征加入模型，预测不同 userID 属于哪类消费人群，显然这个特征对于预测没有任何作用。 但是对于一个过拟合的模型，它可以会学到 userID 到消费人群的直接映射关系，相当于模型直接记住了这个userID是什么消费人群。如果把标签打乱，模型依然会把 userID 直接映射到打乱的标签上，不管是真标签还是假标签，userID 都是最重要的特征。 嵌入法(Embedded)\r嵌入法：是将特征选择过程与学习器训练过程融为一体，二者在同一优化过程中完成，在学习器训练过程中自动地进行了特征选择。 常见的嵌入法： 基于L1/L2惩罚项的 基于SVM的 基于树/森林的特征选择。 分类\rimport numpy as np from sklearn.datasets import load_iris from sklearn.feature_selection import SelectFromModel from sklearn.linear_model impor","date":"2025-05-15","objectID":"/posts/%E6%9C%BA%E5%99%A8%E5%AD%A6%E4%B9%A0%E5%9F%BA%E7%A1%80~07.%E6%95%B0%E6%8D%AE%E8%A7%84%E7%BA%A6/:2:1","tags":["机器学习"],"title":"机器学习基础~07.数据规约","uri":"/posts/%E6%9C%BA%E5%99%A8%E5%AD%A6%E4%B9%A0%E5%9F%BA%E7%A1%80~07.%E6%95%B0%E6%8D%AE%E8%A7%84%E7%BA%A6/#单变量"},{"categories":"机器学习","collections":["机器学习基础"],"content":"11.1 特征选择\r过滤法(Filter)\r单变量\r当单个变量的值符合以下特征时，可以剔除掉该变量： 缺失百分比(Missing Percentage)：缺失样本比例过多且难以填补。 方差(Variance)：连续型变量的方差接近于0，说明其特征值趋向于单一值的状态，对模型帮助不大。 频数(Frequency)：类别型变量的枚举值集中在单一某枚举值上。 import numpy as np from sklearn.datasets import load_iris from sklearn.feature_selection import VarianceThreshold # 加载示例数据（鸢尾花数据集） data = load_iris() X = data.data # 特征 y = data.target # 目标类别 # 低方差滤波 variance_selector = VarianceThreshold(threshold=0.5) X_low_variance = variance_selector.fit_transform(X) 多变量\r多个变量之间，有以下情形： 自变量与自变量之间：删除高度相关的特征，避免多重共线性。 自变量和因变量之间：相关性越高，说明特征对模型预测目标更重要，建议保留。 连续型 vs 连续型\r皮尔逊相关系数(Pearson Correlation Coefficient)：需要两个变量都服从正态分布，皮尔逊相关系数反映两个变量的线性相关程度，大于 0 的时候表示两者正相关，小于 0 的时候表示两者负相关。当两个变量线性相关时，相关系数趋于 1 或 -1，正负号指向正负相关关系。 $$ r=\\frac{\\sum\\left(X_i-\\bar{X}\\right)\\left(Y_i-\\bar{Y}\\right)}{\\sqrt{\\sum\\left(X_i-\\bar{X}\\right)^2 \\sum\\left(Y_i-\\bar{Y}\\right)^2}} $$ 斯皮尔曼相关系数(Spearman’s Rank Correlation Coefficient)：用于衡量两个变量的单调关系（不一定是线性关系），适用于顺序数据或不满足正态分布的连续数据。它基于变量的排序（Rank） 计算，而非原始数据值。 #特征选择（pearson 相关系数法） from sklearn.datasets import load_iris from sklearn.feature_selection import SelectKBest from scipy.stats import pearsonr import numpy as np import pandas as pd iris = load_iris() x = pd.DataFrame(iris.data) y = pd.Series(iris.target) features = iris.feature_names # 两种方法都是 pearson 相关系数法 selector = SelectKBest(lambda X,Y:x.apply(lambda x:x.corr(y)),k=3).fit(x,y) selector = SelectKBest(lambda X,Y:np.array([pearsonr(X[:,i],Y)[0] for i in range(4)]),k=3).fit(x,y) new_x = selector.transform(x) support = [*zip(features,selector.get_support())] 连续型 vs 类别型\r方差分析(Analysis of variance, ANOVA)：检验不同组下的平均数是否存在显著差异。 方差分析前需要满足3个假设: 每组样本具备方差同质性 组内样本服从正态分布 样本间需要独立。 问题\r判断 1、2 和 3 班的同学的数学平均分是否有显著区别？ 班级为类别型变量，数学分数为连续型变量，如果班级与数学分数有相关性，比如 1 班同学数学会更好些，则说明不同班的数学平均分有显著区别。 零假设：三个班的数学分数没有显著区别。 验证方式：看组间方差是否大于组内方差，如果组间方差 \u003e 组内方差，说明存在至少一个分布相对于其他分布较远，则可以考虑拒绝零假设。 肯德尔等级相关系数(Kendall tau rank correlation coefficient) 问题\r评价学历与工资的相关性 肯德尔系数会对按学历对样本排序： 若学历和工资排名相同，则Kendall系数为1，两变量正相关； 若学历和工资完全相反，则系数为-1，两变量负相关； 而如果学历和工资完全独立，系数为0。 类别型vs类别型\r卡方检验(Chi-squared Test)：用于检验两个类别型变量之间的相关性。零假设：两变量之间不相关。卡方值高，说明两变量之间具有相关性的可能性更大。 互信息(Mutual Information)：衡量变量之间相互依赖程度 #载入数据 from sklearn.datasets import load_iris #特征选择 from sklearn.feature_selection import SelectKBest #卡方检验 from sklearn.feature_selection import chi2 iris = load_iris() x = iris.data y = iris.target features = iris.feature_names selector = SelectKBest(chi2,k=2).fit(x,y) support = {k:v for k,v in zip(features,selector.get_support())} 包裹法(Wrapper)\r完全搜索\r完全搜索：遍历所有可能组合的特征子集，然后输入模型，选择最佳模型分数的特征子集。不推荐使用，计算开销过大。 启发式搜索\r启发式搜索：利用启发式信息不断缩小搜索空间的方法。在特征选择中，模型分数或特征权重可作为启发式信息。 前向特征选择（Forward Feature Selection）： 从空特征集开始，逐步添加对模型性能有贡献的特征，直到达到所需的特征数量或达到最佳性能。 反向特征消除（Recursive Feature Elimination，RFE）： 从所有特征开始，反复拟合模型并排除不重要的特征，直到达到所需的特征数量。 基模型可以是 LR、决策树、SVM等。 import numpy as np from sklearn.datasets import load_iris from sklearn.feature_selection import SelectKBest,mutual_info_classif from sklearn.feature_selection import RFE # 反向特征消除 rfe_selector = RFE(estimator=rf_classifier, n_features_to_select=2) X_selected = rfe_selector.fit_transform(X, y) # 使用互信息进行前向特征选择 num_features_to_select = 2 selector = SelectKBest(score_func=mutual_info_classif, k=num_features_to_select) X_selected = selector.fit_transform(X, y) 随机搜索\r随机特征子集：随机选择多个特征子集，然后分别评估模型表现，选择评估分数高的特征子集。 随机目标 (Null Importance) 在原始数据集运行模型获取特征重要性; shuffle 多次标签，每次 shuffle 后获取假标签下的特征重要性; 计算真假标签下的特征重要性差异，并基于差异，筛选特征。 问题\r随机目标 (Null Importance) 为什么能够筛选出真正重要的特征？ 对于真正强健、稳定且重要的特征，在真标签下特征很重要，但一旦标签打乱，这些优质特征的重要性就会变差。 相反地，如果某特征在原始标签下表现一般，但打乱标签后，居然重要性上升，那么这个特征反而不靠谱，应当剔除掉。 举一个简单易懂的例子： 示例\r把 userID 作为特征加入模型，预测不同 userID 属于哪类消费人群，显然这个特征对于预测没有任何作用。 但是对于一个过拟合的模型，它可以会学到 userID 到消费人群的直接映射关系，相当于模型直接记住了这个userID是什么消费人群。如果把标签打乱，模型依然会把 userID 直接映射到打乱的标签上，不管是真标签还是假标签，userID 都是最重要的特征。 嵌入法(Embedded)\r嵌入法：是将特征选择过程与学习器训练过程融为一体，二者在同一优化过程中完成，在学习器训练过程中自动地进行了特征选择。 常见的嵌入法： 基于L1/L2惩罚项的 基于SVM的 基于树/森林的特征选择。 分类\rimport numpy as np from sklearn.datasets import load_iris from sklearn.feature_selection import SelectFromModel from sklearn.linear_model impor","date":"2025-05-15","objectID":"/posts/%E6%9C%BA%E5%99%A8%E5%AD%A6%E4%B9%A0%E5%9F%BA%E7%A1%80~07.%E6%95%B0%E6%8D%AE%E8%A7%84%E7%BA%A6/:2:1","tags":["机器学习"],"title":"机器学习基础~07.数据规约","uri":"/posts/%E6%9C%BA%E5%99%A8%E5%AD%A6%E4%B9%A0%E5%9F%BA%E7%A1%80~07.%E6%95%B0%E6%8D%AE%E8%A7%84%E7%BA%A6/#多变量"},{"categories":"机器学习","collections":["机器学习基础"],"content":"11.1 特征选择\r过滤法(Filter)\r单变量\r当单个变量的值符合以下特征时，可以剔除掉该变量： 缺失百分比(Missing Percentage)：缺失样本比例过多且难以填补。 方差(Variance)：连续型变量的方差接近于0，说明其特征值趋向于单一值的状态，对模型帮助不大。 频数(Frequency)：类别型变量的枚举值集中在单一某枚举值上。 import numpy as np from sklearn.datasets import load_iris from sklearn.feature_selection import VarianceThreshold # 加载示例数据（鸢尾花数据集） data = load_iris() X = data.data # 特征 y = data.target # 目标类别 # 低方差滤波 variance_selector = VarianceThreshold(threshold=0.5) X_low_variance = variance_selector.fit_transform(X) 多变量\r多个变量之间，有以下情形： 自变量与自变量之间：删除高度相关的特征，避免多重共线性。 自变量和因变量之间：相关性越高，说明特征对模型预测目标更重要，建议保留。 连续型 vs 连续型\r皮尔逊相关系数(Pearson Correlation Coefficient)：需要两个变量都服从正态分布，皮尔逊相关系数反映两个变量的线性相关程度，大于 0 的时候表示两者正相关，小于 0 的时候表示两者负相关。当两个变量线性相关时，相关系数趋于 1 或 -1，正负号指向正负相关关系。 $$ r=\\frac{\\sum\\left(X_i-\\bar{X}\\right)\\left(Y_i-\\bar{Y}\\right)}{\\sqrt{\\sum\\left(X_i-\\bar{X}\\right)^2 \\sum\\left(Y_i-\\bar{Y}\\right)^2}} $$ 斯皮尔曼相关系数(Spearman’s Rank Correlation Coefficient)：用于衡量两个变量的单调关系（不一定是线性关系），适用于顺序数据或不满足正态分布的连续数据。它基于变量的排序（Rank） 计算，而非原始数据值。 #特征选择（pearson 相关系数法） from sklearn.datasets import load_iris from sklearn.feature_selection import SelectKBest from scipy.stats import pearsonr import numpy as np import pandas as pd iris = load_iris() x = pd.DataFrame(iris.data) y = pd.Series(iris.target) features = iris.feature_names # 两种方法都是 pearson 相关系数法 selector = SelectKBest(lambda X,Y:x.apply(lambda x:x.corr(y)),k=3).fit(x,y) selector = SelectKBest(lambda X,Y:np.array([pearsonr(X[:,i],Y)[0] for i in range(4)]),k=3).fit(x,y) new_x = selector.transform(x) support = [*zip(features,selector.get_support())] 连续型 vs 类别型\r方差分析(Analysis of variance, ANOVA)：检验不同组下的平均数是否存在显著差异。 方差分析前需要满足3个假设: 每组样本具备方差同质性 组内样本服从正态分布 样本间需要独立。 问题\r判断 1、2 和 3 班的同学的数学平均分是否有显著区别？ 班级为类别型变量，数学分数为连续型变量，如果班级与数学分数有相关性，比如 1 班同学数学会更好些，则说明不同班的数学平均分有显著区别。 零假设：三个班的数学分数没有显著区别。 验证方式：看组间方差是否大于组内方差，如果组间方差 \u003e 组内方差，说明存在至少一个分布相对于其他分布较远，则可以考虑拒绝零假设。 肯德尔等级相关系数(Kendall tau rank correlation coefficient) 问题\r评价学历与工资的相关性 肯德尔系数会对按学历对样本排序： 若学历和工资排名相同，则Kendall系数为1，两变量正相关； 若学历和工资完全相反，则系数为-1，两变量负相关； 而如果学历和工资完全独立，系数为0。 类别型vs类别型\r卡方检验(Chi-squared Test)：用于检验两个类别型变量之间的相关性。零假设：两变量之间不相关。卡方值高，说明两变量之间具有相关性的可能性更大。 互信息(Mutual Information)：衡量变量之间相互依赖程度 #载入数据 from sklearn.datasets import load_iris #特征选择 from sklearn.feature_selection import SelectKBest #卡方检验 from sklearn.feature_selection import chi2 iris = load_iris() x = iris.data y = iris.target features = iris.feature_names selector = SelectKBest(chi2,k=2).fit(x,y) support = {k:v for k,v in zip(features,selector.get_support())} 包裹法(Wrapper)\r完全搜索\r完全搜索：遍历所有可能组合的特征子集，然后输入模型，选择最佳模型分数的特征子集。不推荐使用，计算开销过大。 启发式搜索\r启发式搜索：利用启发式信息不断缩小搜索空间的方法。在特征选择中，模型分数或特征权重可作为启发式信息。 前向特征选择（Forward Feature Selection）： 从空特征集开始，逐步添加对模型性能有贡献的特征，直到达到所需的特征数量或达到最佳性能。 反向特征消除（Recursive Feature Elimination，RFE）： 从所有特征开始，反复拟合模型并排除不重要的特征，直到达到所需的特征数量。 基模型可以是 LR、决策树、SVM等。 import numpy as np from sklearn.datasets import load_iris from sklearn.feature_selection import SelectKBest,mutual_info_classif from sklearn.feature_selection import RFE # 反向特征消除 rfe_selector = RFE(estimator=rf_classifier, n_features_to_select=2) X_selected = rfe_selector.fit_transform(X, y) # 使用互信息进行前向特征选择 num_features_to_select = 2 selector = SelectKBest(score_func=mutual_info_classif, k=num_features_to_select) X_selected = selector.fit_transform(X, y) 随机搜索\r随机特征子集：随机选择多个特征子集，然后分别评估模型表现，选择评估分数高的特征子集。 随机目标 (Null Importance) 在原始数据集运行模型获取特征重要性; shuffle 多次标签，每次 shuffle 后获取假标签下的特征重要性; 计算真假标签下的特征重要性差异，并基于差异，筛选特征。 问题\r随机目标 (Null Importance) 为什么能够筛选出真正重要的特征？ 对于真正强健、稳定且重要的特征，在真标签下特征很重要，但一旦标签打乱，这些优质特征的重要性就会变差。 相反地，如果某特征在原始标签下表现一般，但打乱标签后，居然重要性上升，那么这个特征反而不靠谱，应当剔除掉。 举一个简单易懂的例子： 示例\r把 userID 作为特征加入模型，预测不同 userID 属于哪类消费人群，显然这个特征对于预测没有任何作用。 但是对于一个过拟合的模型，它可以会学到 userID 到消费人群的直接映射关系，相当于模型直接记住了这个userID是什么消费人群。如果把标签打乱，模型依然会把 userID 直接映射到打乱的标签上，不管是真标签还是假标签，userID 都是最重要的特征。 嵌入法(Embedded)\r嵌入法：是将特征选择过程与学习器训练过程融为一体，二者在同一优化过程中完成，在学习器训练过程中自动地进行了特征选择。 常见的嵌入法： 基于L1/L2惩罚项的 基于SVM的 基于树/森林的特征选择。 分类\rimport numpy as np from sklearn.datasets import load_iris from sklearn.feature_selection import SelectFromModel from sklearn.linear_model impor","date":"2025-05-15","objectID":"/posts/%E6%9C%BA%E5%99%A8%E5%AD%A6%E4%B9%A0%E5%9F%BA%E7%A1%80~07.%E6%95%B0%E6%8D%AE%E8%A7%84%E7%BA%A6/:2:1","tags":["机器学习"],"title":"机器学习基础~07.数据规约","uri":"/posts/%E6%9C%BA%E5%99%A8%E5%AD%A6%E4%B9%A0%E5%9F%BA%E7%A1%80~07.%E6%95%B0%E6%8D%AE%E8%A7%84%E7%BA%A6/#连续型-vs-连续型"},{"categories":"机器学习","collections":["机器学习基础"],"content":"11.1 特征选择\r过滤法(Filter)\r单变量\r当单个变量的值符合以下特征时，可以剔除掉该变量： 缺失百分比(Missing Percentage)：缺失样本比例过多且难以填补。 方差(Variance)：连续型变量的方差接近于0，说明其特征值趋向于单一值的状态，对模型帮助不大。 频数(Frequency)：类别型变量的枚举值集中在单一某枚举值上。 import numpy as np from sklearn.datasets import load_iris from sklearn.feature_selection import VarianceThreshold # 加载示例数据（鸢尾花数据集） data = load_iris() X = data.data # 特征 y = data.target # 目标类别 # 低方差滤波 variance_selector = VarianceThreshold(threshold=0.5) X_low_variance = variance_selector.fit_transform(X) 多变量\r多个变量之间，有以下情形： 自变量与自变量之间：删除高度相关的特征，避免多重共线性。 自变量和因变量之间：相关性越高，说明特征对模型预测目标更重要，建议保留。 连续型 vs 连续型\r皮尔逊相关系数(Pearson Correlation Coefficient)：需要两个变量都服从正态分布，皮尔逊相关系数反映两个变量的线性相关程度，大于 0 的时候表示两者正相关，小于 0 的时候表示两者负相关。当两个变量线性相关时，相关系数趋于 1 或 -1，正负号指向正负相关关系。 $$ r=\\frac{\\sum\\left(X_i-\\bar{X}\\right)\\left(Y_i-\\bar{Y}\\right)}{\\sqrt{\\sum\\left(X_i-\\bar{X}\\right)^2 \\sum\\left(Y_i-\\bar{Y}\\right)^2}} $$ 斯皮尔曼相关系数(Spearman’s Rank Correlation Coefficient)：用于衡量两个变量的单调关系（不一定是线性关系），适用于顺序数据或不满足正态分布的连续数据。它基于变量的排序（Rank） 计算，而非原始数据值。 #特征选择（pearson 相关系数法） from sklearn.datasets import load_iris from sklearn.feature_selection import SelectKBest from scipy.stats import pearsonr import numpy as np import pandas as pd iris = load_iris() x = pd.DataFrame(iris.data) y = pd.Series(iris.target) features = iris.feature_names # 两种方法都是 pearson 相关系数法 selector = SelectKBest(lambda X,Y:x.apply(lambda x:x.corr(y)),k=3).fit(x,y) selector = SelectKBest(lambda X,Y:np.array([pearsonr(X[:,i],Y)[0] for i in range(4)]),k=3).fit(x,y) new_x = selector.transform(x) support = [*zip(features,selector.get_support())] 连续型 vs 类别型\r方差分析(Analysis of variance, ANOVA)：检验不同组下的平均数是否存在显著差异。 方差分析前需要满足3个假设: 每组样本具备方差同质性 组内样本服从正态分布 样本间需要独立。 问题\r判断 1、2 和 3 班的同学的数学平均分是否有显著区别？ 班级为类别型变量，数学分数为连续型变量，如果班级与数学分数有相关性，比如 1 班同学数学会更好些，则说明不同班的数学平均分有显著区别。 零假设：三个班的数学分数没有显著区别。 验证方式：看组间方差是否大于组内方差，如果组间方差 \u003e 组内方差，说明存在至少一个分布相对于其他分布较远，则可以考虑拒绝零假设。 肯德尔等级相关系数(Kendall tau rank correlation coefficient) 问题\r评价学历与工资的相关性 肯德尔系数会对按学历对样本排序： 若学历和工资排名相同，则Kendall系数为1，两变量正相关； 若学历和工资完全相反，则系数为-1，两变量负相关； 而如果学历和工资完全独立，系数为0。 类别型vs类别型\r卡方检验(Chi-squared Test)：用于检验两个类别型变量之间的相关性。零假设：两变量之间不相关。卡方值高，说明两变量之间具有相关性的可能性更大。 互信息(Mutual Information)：衡量变量之间相互依赖程度 #载入数据 from sklearn.datasets import load_iris #特征选择 from sklearn.feature_selection import SelectKBest #卡方检验 from sklearn.feature_selection import chi2 iris = load_iris() x = iris.data y = iris.target features = iris.feature_names selector = SelectKBest(chi2,k=2).fit(x,y) support = {k:v for k,v in zip(features,selector.get_support())} 包裹法(Wrapper)\r完全搜索\r完全搜索：遍历所有可能组合的特征子集，然后输入模型，选择最佳模型分数的特征子集。不推荐使用，计算开销过大。 启发式搜索\r启发式搜索：利用启发式信息不断缩小搜索空间的方法。在特征选择中，模型分数或特征权重可作为启发式信息。 前向特征选择（Forward Feature Selection）： 从空特征集开始，逐步添加对模型性能有贡献的特征，直到达到所需的特征数量或达到最佳性能。 反向特征消除（Recursive Feature Elimination，RFE）： 从所有特征开始，反复拟合模型并排除不重要的特征，直到达到所需的特征数量。 基模型可以是 LR、决策树、SVM等。 import numpy as np from sklearn.datasets import load_iris from sklearn.feature_selection import SelectKBest,mutual_info_classif from sklearn.feature_selection import RFE # 反向特征消除 rfe_selector = RFE(estimator=rf_classifier, n_features_to_select=2) X_selected = rfe_selector.fit_transform(X, y) # 使用互信息进行前向特征选择 num_features_to_select = 2 selector = SelectKBest(score_func=mutual_info_classif, k=num_features_to_select) X_selected = selector.fit_transform(X, y) 随机搜索\r随机特征子集：随机选择多个特征子集，然后分别评估模型表现，选择评估分数高的特征子集。 随机目标 (Null Importance) 在原始数据集运行模型获取特征重要性; shuffle 多次标签，每次 shuffle 后获取假标签下的特征重要性; 计算真假标签下的特征重要性差异，并基于差异，筛选特征。 问题\r随机目标 (Null Importance) 为什么能够筛选出真正重要的特征？ 对于真正强健、稳定且重要的特征，在真标签下特征很重要，但一旦标签打乱，这些优质特征的重要性就会变差。 相反地，如果某特征在原始标签下表现一般，但打乱标签后，居然重要性上升，那么这个特征反而不靠谱，应当剔除掉。 举一个简单易懂的例子： 示例\r把 userID 作为特征加入模型，预测不同 userID 属于哪类消费人群，显然这个特征对于预测没有任何作用。 但是对于一个过拟合的模型，它可以会学到 userID 到消费人群的直接映射关系，相当于模型直接记住了这个userID是什么消费人群。如果把标签打乱，模型依然会把 userID 直接映射到打乱的标签上，不管是真标签还是假标签，userID 都是最重要的特征。 嵌入法(Embedded)\r嵌入法：是将特征选择过程与学习器训练过程融为一体，二者在同一优化过程中完成，在学习器训练过程中自动地进行了特征选择。 常见的嵌入法： 基于L1/L2惩罚项的 基于SVM的 基于树/森林的特征选择。 分类\rimport numpy as np from sklearn.datasets import load_iris from sklearn.feature_selection import SelectFromModel from sklearn.linear_model impor","date":"2025-05-15","objectID":"/posts/%E6%9C%BA%E5%99%A8%E5%AD%A6%E4%B9%A0%E5%9F%BA%E7%A1%80~07.%E6%95%B0%E6%8D%AE%E8%A7%84%E7%BA%A6/:2:1","tags":["机器学习"],"title":"机器学习基础~07.数据规约","uri":"/posts/%E6%9C%BA%E5%99%A8%E5%AD%A6%E4%B9%A0%E5%9F%BA%E7%A1%80~07.%E6%95%B0%E6%8D%AE%E8%A7%84%E7%BA%A6/#连续型-vs-类别型"},{"categories":"机器学习","collections":["机器学习基础"],"content":"11.1 特征选择\r过滤法(Filter)\r单变量\r当单个变量的值符合以下特征时，可以剔除掉该变量： 缺失百分比(Missing Percentage)：缺失样本比例过多且难以填补。 方差(Variance)：连续型变量的方差接近于0，说明其特征值趋向于单一值的状态，对模型帮助不大。 频数(Frequency)：类别型变量的枚举值集中在单一某枚举值上。 import numpy as np from sklearn.datasets import load_iris from sklearn.feature_selection import VarianceThreshold # 加载示例数据（鸢尾花数据集） data = load_iris() X = data.data # 特征 y = data.target # 目标类别 # 低方差滤波 variance_selector = VarianceThreshold(threshold=0.5) X_low_variance = variance_selector.fit_transform(X) 多变量\r多个变量之间，有以下情形： 自变量与自变量之间：删除高度相关的特征，避免多重共线性。 自变量和因变量之间：相关性越高，说明特征对模型预测目标更重要，建议保留。 连续型 vs 连续型\r皮尔逊相关系数(Pearson Correlation Coefficient)：需要两个变量都服从正态分布，皮尔逊相关系数反映两个变量的线性相关程度，大于 0 的时候表示两者正相关，小于 0 的时候表示两者负相关。当两个变量线性相关时，相关系数趋于 1 或 -1，正负号指向正负相关关系。 $$ r=\\frac{\\sum\\left(X_i-\\bar{X}\\right)\\left(Y_i-\\bar{Y}\\right)}{\\sqrt{\\sum\\left(X_i-\\bar{X}\\right)^2 \\sum\\left(Y_i-\\bar{Y}\\right)^2}} $$ 斯皮尔曼相关系数(Spearman’s Rank Correlation Coefficient)：用于衡量两个变量的单调关系（不一定是线性关系），适用于顺序数据或不满足正态分布的连续数据。它基于变量的排序（Rank） 计算，而非原始数据值。 #特征选择（pearson 相关系数法） from sklearn.datasets import load_iris from sklearn.feature_selection import SelectKBest from scipy.stats import pearsonr import numpy as np import pandas as pd iris = load_iris() x = pd.DataFrame(iris.data) y = pd.Series(iris.target) features = iris.feature_names # 两种方法都是 pearson 相关系数法 selector = SelectKBest(lambda X,Y:x.apply(lambda x:x.corr(y)),k=3).fit(x,y) selector = SelectKBest(lambda X,Y:np.array([pearsonr(X[:,i],Y)[0] for i in range(4)]),k=3).fit(x,y) new_x = selector.transform(x) support = [*zip(features,selector.get_support())] 连续型 vs 类别型\r方差分析(Analysis of variance, ANOVA)：检验不同组下的平均数是否存在显著差异。 方差分析前需要满足3个假设: 每组样本具备方差同质性 组内样本服从正态分布 样本间需要独立。 问题\r判断 1、2 和 3 班的同学的数学平均分是否有显著区别？ 班级为类别型变量，数学分数为连续型变量，如果班级与数学分数有相关性，比如 1 班同学数学会更好些，则说明不同班的数学平均分有显著区别。 零假设：三个班的数学分数没有显著区别。 验证方式：看组间方差是否大于组内方差，如果组间方差 \u003e 组内方差，说明存在至少一个分布相对于其他分布较远，则可以考虑拒绝零假设。 肯德尔等级相关系数(Kendall tau rank correlation coefficient) 问题\r评价学历与工资的相关性 肯德尔系数会对按学历对样本排序： 若学历和工资排名相同，则Kendall系数为1，两变量正相关； 若学历和工资完全相反，则系数为-1，两变量负相关； 而如果学历和工资完全独立，系数为0。 类别型vs类别型\r卡方检验(Chi-squared Test)：用于检验两个类别型变量之间的相关性。零假设：两变量之间不相关。卡方值高，说明两变量之间具有相关性的可能性更大。 互信息(Mutual Information)：衡量变量之间相互依赖程度 #载入数据 from sklearn.datasets import load_iris #特征选择 from sklearn.feature_selection import SelectKBest #卡方检验 from sklearn.feature_selection import chi2 iris = load_iris() x = iris.data y = iris.target features = iris.feature_names selector = SelectKBest(chi2,k=2).fit(x,y) support = {k:v for k,v in zip(features,selector.get_support())} 包裹法(Wrapper)\r完全搜索\r完全搜索：遍历所有可能组合的特征子集，然后输入模型，选择最佳模型分数的特征子集。不推荐使用，计算开销过大。 启发式搜索\r启发式搜索：利用启发式信息不断缩小搜索空间的方法。在特征选择中，模型分数或特征权重可作为启发式信息。 前向特征选择（Forward Feature Selection）： 从空特征集开始，逐步添加对模型性能有贡献的特征，直到达到所需的特征数量或达到最佳性能。 反向特征消除（Recursive Feature Elimination，RFE）： 从所有特征开始，反复拟合模型并排除不重要的特征，直到达到所需的特征数量。 基模型可以是 LR、决策树、SVM等。 import numpy as np from sklearn.datasets import load_iris from sklearn.feature_selection import SelectKBest,mutual_info_classif from sklearn.feature_selection import RFE # 反向特征消除 rfe_selector = RFE(estimator=rf_classifier, n_features_to_select=2) X_selected = rfe_selector.fit_transform(X, y) # 使用互信息进行前向特征选择 num_features_to_select = 2 selector = SelectKBest(score_func=mutual_info_classif, k=num_features_to_select) X_selected = selector.fit_transform(X, y) 随机搜索\r随机特征子集：随机选择多个特征子集，然后分别评估模型表现，选择评估分数高的特征子集。 随机目标 (Null Importance) 在原始数据集运行模型获取特征重要性; shuffle 多次标签，每次 shuffle 后获取假标签下的特征重要性; 计算真假标签下的特征重要性差异，并基于差异，筛选特征。 问题\r随机目标 (Null Importance) 为什么能够筛选出真正重要的特征？ 对于真正强健、稳定且重要的特征，在真标签下特征很重要，但一旦标签打乱，这些优质特征的重要性就会变差。 相反地，如果某特征在原始标签下表现一般，但打乱标签后，居然重要性上升，那么这个特征反而不靠谱，应当剔除掉。 举一个简单易懂的例子： 示例\r把 userID 作为特征加入模型，预测不同 userID 属于哪类消费人群，显然这个特征对于预测没有任何作用。 但是对于一个过拟合的模型，它可以会学到 userID 到消费人群的直接映射关系，相当于模型直接记住了这个userID是什么消费人群。如果把标签打乱，模型依然会把 userID 直接映射到打乱的标签上，不管是真标签还是假标签，userID 都是最重要的特征。 嵌入法(Embedded)\r嵌入法：是将特征选择过程与学习器训练过程融为一体，二者在同一优化过程中完成，在学习器训练过程中自动地进行了特征选择。 常见的嵌入法： 基于L1/L2惩罚项的 基于SVM的 基于树/森林的特征选择。 分类\rimport numpy as np from sklearn.datasets import load_iris from sklearn.feature_selection import SelectFromModel from sklearn.linear_model impor","date":"2025-05-15","objectID":"/posts/%E6%9C%BA%E5%99%A8%E5%AD%A6%E4%B9%A0%E5%9F%BA%E7%A1%80~07.%E6%95%B0%E6%8D%AE%E8%A7%84%E7%BA%A6/:2:1","tags":["机器学习"],"title":"机器学习基础~07.数据规约","uri":"/posts/%E6%9C%BA%E5%99%A8%E5%AD%A6%E4%B9%A0%E5%9F%BA%E7%A1%80~07.%E6%95%B0%E6%8D%AE%E8%A7%84%E7%BA%A6/#类别型vs类别型"},{"categories":"机器学习","collections":["机器学习基础"],"content":"11.1 特征选择\r过滤法(Filter)\r单变量\r当单个变量的值符合以下特征时，可以剔除掉该变量： 缺失百分比(Missing Percentage)：缺失样本比例过多且难以填补。 方差(Variance)：连续型变量的方差接近于0，说明其特征值趋向于单一值的状态，对模型帮助不大。 频数(Frequency)：类别型变量的枚举值集中在单一某枚举值上。 import numpy as np from sklearn.datasets import load_iris from sklearn.feature_selection import VarianceThreshold # 加载示例数据（鸢尾花数据集） data = load_iris() X = data.data # 特征 y = data.target # 目标类别 # 低方差滤波 variance_selector = VarianceThreshold(threshold=0.5) X_low_variance = variance_selector.fit_transform(X) 多变量\r多个变量之间，有以下情形： 自变量与自变量之间：删除高度相关的特征，避免多重共线性。 自变量和因变量之间：相关性越高，说明特征对模型预测目标更重要，建议保留。 连续型 vs 连续型\r皮尔逊相关系数(Pearson Correlation Coefficient)：需要两个变量都服从正态分布，皮尔逊相关系数反映两个变量的线性相关程度，大于 0 的时候表示两者正相关，小于 0 的时候表示两者负相关。当两个变量线性相关时，相关系数趋于 1 或 -1，正负号指向正负相关关系。 $$ r=\\frac{\\sum\\left(X_i-\\bar{X}\\right)\\left(Y_i-\\bar{Y}\\right)}{\\sqrt{\\sum\\left(X_i-\\bar{X}\\right)^2 \\sum\\left(Y_i-\\bar{Y}\\right)^2}} $$ 斯皮尔曼相关系数(Spearman’s Rank Correlation Coefficient)：用于衡量两个变量的单调关系（不一定是线性关系），适用于顺序数据或不满足正态分布的连续数据。它基于变量的排序（Rank） 计算，而非原始数据值。 #特征选择（pearson 相关系数法） from sklearn.datasets import load_iris from sklearn.feature_selection import SelectKBest from scipy.stats import pearsonr import numpy as np import pandas as pd iris = load_iris() x = pd.DataFrame(iris.data) y = pd.Series(iris.target) features = iris.feature_names # 两种方法都是 pearson 相关系数法 selector = SelectKBest(lambda X,Y:x.apply(lambda x:x.corr(y)),k=3).fit(x,y) selector = SelectKBest(lambda X,Y:np.array([pearsonr(X[:,i],Y)[0] for i in range(4)]),k=3).fit(x,y) new_x = selector.transform(x) support = [*zip(features,selector.get_support())] 连续型 vs 类别型\r方差分析(Analysis of variance, ANOVA)：检验不同组下的平均数是否存在显著差异。 方差分析前需要满足3个假设: 每组样本具备方差同质性 组内样本服从正态分布 样本间需要独立。 问题\r判断 1、2 和 3 班的同学的数学平均分是否有显著区别？ 班级为类别型变量，数学分数为连续型变量，如果班级与数学分数有相关性，比如 1 班同学数学会更好些，则说明不同班的数学平均分有显著区别。 零假设：三个班的数学分数没有显著区别。 验证方式：看组间方差是否大于组内方差，如果组间方差 \u003e 组内方差，说明存在至少一个分布相对于其他分布较远，则可以考虑拒绝零假设。 肯德尔等级相关系数(Kendall tau rank correlation coefficient) 问题\r评价学历与工资的相关性 肯德尔系数会对按学历对样本排序： 若学历和工资排名相同，则Kendall系数为1，两变量正相关； 若学历和工资完全相反，则系数为-1，两变量负相关； 而如果学历和工资完全独立，系数为0。 类别型vs类别型\r卡方检验(Chi-squared Test)：用于检验两个类别型变量之间的相关性。零假设：两变量之间不相关。卡方值高，说明两变量之间具有相关性的可能性更大。 互信息(Mutual Information)：衡量变量之间相互依赖程度 #载入数据 from sklearn.datasets import load_iris #特征选择 from sklearn.feature_selection import SelectKBest #卡方检验 from sklearn.feature_selection import chi2 iris = load_iris() x = iris.data y = iris.target features = iris.feature_names selector = SelectKBest(chi2,k=2).fit(x,y) support = {k:v for k,v in zip(features,selector.get_support())} 包裹法(Wrapper)\r完全搜索\r完全搜索：遍历所有可能组合的特征子集，然后输入模型，选择最佳模型分数的特征子集。不推荐使用，计算开销过大。 启发式搜索\r启发式搜索：利用启发式信息不断缩小搜索空间的方法。在特征选择中，模型分数或特征权重可作为启发式信息。 前向特征选择（Forward Feature Selection）： 从空特征集开始，逐步添加对模型性能有贡献的特征，直到达到所需的特征数量或达到最佳性能。 反向特征消除（Recursive Feature Elimination，RFE）： 从所有特征开始，反复拟合模型并排除不重要的特征，直到达到所需的特征数量。 基模型可以是 LR、决策树、SVM等。 import numpy as np from sklearn.datasets import load_iris from sklearn.feature_selection import SelectKBest,mutual_info_classif from sklearn.feature_selection import RFE # 反向特征消除 rfe_selector = RFE(estimator=rf_classifier, n_features_to_select=2) X_selected = rfe_selector.fit_transform(X, y) # 使用互信息进行前向特征选择 num_features_to_select = 2 selector = SelectKBest(score_func=mutual_info_classif, k=num_features_to_select) X_selected = selector.fit_transform(X, y) 随机搜索\r随机特征子集：随机选择多个特征子集，然后分别评估模型表现，选择评估分数高的特征子集。 随机目标 (Null Importance) 在原始数据集运行模型获取特征重要性; shuffle 多次标签，每次 shuffle 后获取假标签下的特征重要性; 计算真假标签下的特征重要性差异，并基于差异，筛选特征。 问题\r随机目标 (Null Importance) 为什么能够筛选出真正重要的特征？ 对于真正强健、稳定且重要的特征，在真标签下特征很重要，但一旦标签打乱，这些优质特征的重要性就会变差。 相反地，如果某特征在原始标签下表现一般，但打乱标签后，居然重要性上升，那么这个特征反而不靠谱，应当剔除掉。 举一个简单易懂的例子： 示例\r把 userID 作为特征加入模型，预测不同 userID 属于哪类消费人群，显然这个特征对于预测没有任何作用。 但是对于一个过拟合的模型，它可以会学到 userID 到消费人群的直接映射关系，相当于模型直接记住了这个userID是什么消费人群。如果把标签打乱，模型依然会把 userID 直接映射到打乱的标签上，不管是真标签还是假标签，userID 都是最重要的特征。 嵌入法(Embedded)\r嵌入法：是将特征选择过程与学习器训练过程融为一体，二者在同一优化过程中完成，在学习器训练过程中自动地进行了特征选择。 常见的嵌入法： 基于L1/L2惩罚项的 基于SVM的 基于树/森林的特征选择。 分类\rimport numpy as np from sklearn.datasets import load_iris from sklearn.feature_selection import SelectFromModel from sklearn.linear_model impor","date":"2025-05-15","objectID":"/posts/%E6%9C%BA%E5%99%A8%E5%AD%A6%E4%B9%A0%E5%9F%BA%E7%A1%80~07.%E6%95%B0%E6%8D%AE%E8%A7%84%E7%BA%A6/:2:1","tags":["机器学习"],"title":"机器学习基础~07.数据规约","uri":"/posts/%E6%9C%BA%E5%99%A8%E5%AD%A6%E4%B9%A0%E5%9F%BA%E7%A1%80~07.%E6%95%B0%E6%8D%AE%E8%A7%84%E7%BA%A6/#包裹法wrapper"},{"categories":"机器学习","collections":["机器学习基础"],"content":"11.1 特征选择\r过滤法(Filter)\r单变量\r当单个变量的值符合以下特征时，可以剔除掉该变量： 缺失百分比(Missing Percentage)：缺失样本比例过多且难以填补。 方差(Variance)：连续型变量的方差接近于0，说明其特征值趋向于单一值的状态，对模型帮助不大。 频数(Frequency)：类别型变量的枚举值集中在单一某枚举值上。 import numpy as np from sklearn.datasets import load_iris from sklearn.feature_selection import VarianceThreshold # 加载示例数据（鸢尾花数据集） data = load_iris() X = data.data # 特征 y = data.target # 目标类别 # 低方差滤波 variance_selector = VarianceThreshold(threshold=0.5) X_low_variance = variance_selector.fit_transform(X) 多变量\r多个变量之间，有以下情形： 自变量与自变量之间：删除高度相关的特征，避免多重共线性。 自变量和因变量之间：相关性越高，说明特征对模型预测目标更重要，建议保留。 连续型 vs 连续型\r皮尔逊相关系数(Pearson Correlation Coefficient)：需要两个变量都服从正态分布，皮尔逊相关系数反映两个变量的线性相关程度，大于 0 的时候表示两者正相关，小于 0 的时候表示两者负相关。当两个变量线性相关时，相关系数趋于 1 或 -1，正负号指向正负相关关系。 $$ r=\\frac{\\sum\\left(X_i-\\bar{X}\\right)\\left(Y_i-\\bar{Y}\\right)}{\\sqrt{\\sum\\left(X_i-\\bar{X}\\right)^2 \\sum\\left(Y_i-\\bar{Y}\\right)^2}} $$ 斯皮尔曼相关系数(Spearman’s Rank Correlation Coefficient)：用于衡量两个变量的单调关系（不一定是线性关系），适用于顺序数据或不满足正态分布的连续数据。它基于变量的排序（Rank） 计算，而非原始数据值。 #特征选择（pearson 相关系数法） from sklearn.datasets import load_iris from sklearn.feature_selection import SelectKBest from scipy.stats import pearsonr import numpy as np import pandas as pd iris = load_iris() x = pd.DataFrame(iris.data) y = pd.Series(iris.target) features = iris.feature_names # 两种方法都是 pearson 相关系数法 selector = SelectKBest(lambda X,Y:x.apply(lambda x:x.corr(y)),k=3).fit(x,y) selector = SelectKBest(lambda X,Y:np.array([pearsonr(X[:,i],Y)[0] for i in range(4)]),k=3).fit(x,y) new_x = selector.transform(x) support = [*zip(features,selector.get_support())] 连续型 vs 类别型\r方差分析(Analysis of variance, ANOVA)：检验不同组下的平均数是否存在显著差异。 方差分析前需要满足3个假设: 每组样本具备方差同质性 组内样本服从正态分布 样本间需要独立。 问题\r判断 1、2 和 3 班的同学的数学平均分是否有显著区别？ 班级为类别型变量，数学分数为连续型变量，如果班级与数学分数有相关性，比如 1 班同学数学会更好些，则说明不同班的数学平均分有显著区别。 零假设：三个班的数学分数没有显著区别。 验证方式：看组间方差是否大于组内方差，如果组间方差 \u003e 组内方差，说明存在至少一个分布相对于其他分布较远，则可以考虑拒绝零假设。 肯德尔等级相关系数(Kendall tau rank correlation coefficient) 问题\r评价学历与工资的相关性 肯德尔系数会对按学历对样本排序： 若学历和工资排名相同，则Kendall系数为1，两变量正相关； 若学历和工资完全相反，则系数为-1，两变量负相关； 而如果学历和工资完全独立，系数为0。 类别型vs类别型\r卡方检验(Chi-squared Test)：用于检验两个类别型变量之间的相关性。零假设：两变量之间不相关。卡方值高，说明两变量之间具有相关性的可能性更大。 互信息(Mutual Information)：衡量变量之间相互依赖程度 #载入数据 from sklearn.datasets import load_iris #特征选择 from sklearn.feature_selection import SelectKBest #卡方检验 from sklearn.feature_selection import chi2 iris = load_iris() x = iris.data y = iris.target features = iris.feature_names selector = SelectKBest(chi2,k=2).fit(x,y) support = {k:v for k,v in zip(features,selector.get_support())} 包裹法(Wrapper)\r完全搜索\r完全搜索：遍历所有可能组合的特征子集，然后输入模型，选择最佳模型分数的特征子集。不推荐使用，计算开销过大。 启发式搜索\r启发式搜索：利用启发式信息不断缩小搜索空间的方法。在特征选择中，模型分数或特征权重可作为启发式信息。 前向特征选择（Forward Feature Selection）： 从空特征集开始，逐步添加对模型性能有贡献的特征，直到达到所需的特征数量或达到最佳性能。 反向特征消除（Recursive Feature Elimination，RFE）： 从所有特征开始，反复拟合模型并排除不重要的特征，直到达到所需的特征数量。 基模型可以是 LR、决策树、SVM等。 import numpy as np from sklearn.datasets import load_iris from sklearn.feature_selection import SelectKBest,mutual_info_classif from sklearn.feature_selection import RFE # 反向特征消除 rfe_selector = RFE(estimator=rf_classifier, n_features_to_select=2) X_selected = rfe_selector.fit_transform(X, y) # 使用互信息进行前向特征选择 num_features_to_select = 2 selector = SelectKBest(score_func=mutual_info_classif, k=num_features_to_select) X_selected = selector.fit_transform(X, y) 随机搜索\r随机特征子集：随机选择多个特征子集，然后分别评估模型表现，选择评估分数高的特征子集。 随机目标 (Null Importance) 在原始数据集运行模型获取特征重要性; shuffle 多次标签，每次 shuffle 后获取假标签下的特征重要性; 计算真假标签下的特征重要性差异，并基于差异，筛选特征。 问题\r随机目标 (Null Importance) 为什么能够筛选出真正重要的特征？ 对于真正强健、稳定且重要的特征，在真标签下特征很重要，但一旦标签打乱，这些优质特征的重要性就会变差。 相反地，如果某特征在原始标签下表现一般，但打乱标签后，居然重要性上升，那么这个特征反而不靠谱，应当剔除掉。 举一个简单易懂的例子： 示例\r把 userID 作为特征加入模型，预测不同 userID 属于哪类消费人群，显然这个特征对于预测没有任何作用。 但是对于一个过拟合的模型，它可以会学到 userID 到消费人群的直接映射关系，相当于模型直接记住了这个userID是什么消费人群。如果把标签打乱，模型依然会把 userID 直接映射到打乱的标签上，不管是真标签还是假标签，userID 都是最重要的特征。 嵌入法(Embedded)\r嵌入法：是将特征选择过程与学习器训练过程融为一体，二者在同一优化过程中完成，在学习器训练过程中自动地进行了特征选择。 常见的嵌入法： 基于L1/L2惩罚项的 基于SVM的 基于树/森林的特征选择。 分类\rimport numpy as np from sklearn.datasets import load_iris from sklearn.feature_selection import SelectFromModel from sklearn.linear_model impor","date":"2025-05-15","objectID":"/posts/%E6%9C%BA%E5%99%A8%E5%AD%A6%E4%B9%A0%E5%9F%BA%E7%A1%80~07.%E6%95%B0%E6%8D%AE%E8%A7%84%E7%BA%A6/:2:1","tags":["机器学习"],"title":"机器学习基础~07.数据规约","uri":"/posts/%E6%9C%BA%E5%99%A8%E5%AD%A6%E4%B9%A0%E5%9F%BA%E7%A1%80~07.%E6%95%B0%E6%8D%AE%E8%A7%84%E7%BA%A6/#完全搜索"},{"categories":"机器学习","collections":["机器学习基础"],"content":"11.1 特征选择\r过滤法(Filter)\r单变量\r当单个变量的值符合以下特征时，可以剔除掉该变量： 缺失百分比(Missing Percentage)：缺失样本比例过多且难以填补。 方差(Variance)：连续型变量的方差接近于0，说明其特征值趋向于单一值的状态，对模型帮助不大。 频数(Frequency)：类别型变量的枚举值集中在单一某枚举值上。 import numpy as np from sklearn.datasets import load_iris from sklearn.feature_selection import VarianceThreshold # 加载示例数据（鸢尾花数据集） data = load_iris() X = data.data # 特征 y = data.target # 目标类别 # 低方差滤波 variance_selector = VarianceThreshold(threshold=0.5) X_low_variance = variance_selector.fit_transform(X) 多变量\r多个变量之间，有以下情形： 自变量与自变量之间：删除高度相关的特征，避免多重共线性。 自变量和因变量之间：相关性越高，说明特征对模型预测目标更重要，建议保留。 连续型 vs 连续型\r皮尔逊相关系数(Pearson Correlation Coefficient)：需要两个变量都服从正态分布，皮尔逊相关系数反映两个变量的线性相关程度，大于 0 的时候表示两者正相关，小于 0 的时候表示两者负相关。当两个变量线性相关时，相关系数趋于 1 或 -1，正负号指向正负相关关系。 $$ r=\\frac{\\sum\\left(X_i-\\bar{X}\\right)\\left(Y_i-\\bar{Y}\\right)}{\\sqrt{\\sum\\left(X_i-\\bar{X}\\right)^2 \\sum\\left(Y_i-\\bar{Y}\\right)^2}} $$ 斯皮尔曼相关系数(Spearman’s Rank Correlation Coefficient)：用于衡量两个变量的单调关系（不一定是线性关系），适用于顺序数据或不满足正态分布的连续数据。它基于变量的排序（Rank） 计算，而非原始数据值。 #特征选择（pearson 相关系数法） from sklearn.datasets import load_iris from sklearn.feature_selection import SelectKBest from scipy.stats import pearsonr import numpy as np import pandas as pd iris = load_iris() x = pd.DataFrame(iris.data) y = pd.Series(iris.target) features = iris.feature_names # 两种方法都是 pearson 相关系数法 selector = SelectKBest(lambda X,Y:x.apply(lambda x:x.corr(y)),k=3).fit(x,y) selector = SelectKBest(lambda X,Y:np.array([pearsonr(X[:,i],Y)[0] for i in range(4)]),k=3).fit(x,y) new_x = selector.transform(x) support = [*zip(features,selector.get_support())] 连续型 vs 类别型\r方差分析(Analysis of variance, ANOVA)：检验不同组下的平均数是否存在显著差异。 方差分析前需要满足3个假设: 每组样本具备方差同质性 组内样本服从正态分布 样本间需要独立。 问题\r判断 1、2 和 3 班的同学的数学平均分是否有显著区别？ 班级为类别型变量，数学分数为连续型变量，如果班级与数学分数有相关性，比如 1 班同学数学会更好些，则说明不同班的数学平均分有显著区别。 零假设：三个班的数学分数没有显著区别。 验证方式：看组间方差是否大于组内方差，如果组间方差 \u003e 组内方差，说明存在至少一个分布相对于其他分布较远，则可以考虑拒绝零假设。 肯德尔等级相关系数(Kendall tau rank correlation coefficient) 问题\r评价学历与工资的相关性 肯德尔系数会对按学历对样本排序： 若学历和工资排名相同，则Kendall系数为1，两变量正相关； 若学历和工资完全相反，则系数为-1，两变量负相关； 而如果学历和工资完全独立，系数为0。 类别型vs类别型\r卡方检验(Chi-squared Test)：用于检验两个类别型变量之间的相关性。零假设：两变量之间不相关。卡方值高，说明两变量之间具有相关性的可能性更大。 互信息(Mutual Information)：衡量变量之间相互依赖程度 #载入数据 from sklearn.datasets import load_iris #特征选择 from sklearn.feature_selection import SelectKBest #卡方检验 from sklearn.feature_selection import chi2 iris = load_iris() x = iris.data y = iris.target features = iris.feature_names selector = SelectKBest(chi2,k=2).fit(x,y) support = {k:v for k,v in zip(features,selector.get_support())} 包裹法(Wrapper)\r完全搜索\r完全搜索：遍历所有可能组合的特征子集，然后输入模型，选择最佳模型分数的特征子集。不推荐使用，计算开销过大。 启发式搜索\r启发式搜索：利用启发式信息不断缩小搜索空间的方法。在特征选择中，模型分数或特征权重可作为启发式信息。 前向特征选择（Forward Feature Selection）： 从空特征集开始，逐步添加对模型性能有贡献的特征，直到达到所需的特征数量或达到最佳性能。 反向特征消除（Recursive Feature Elimination，RFE）： 从所有特征开始，反复拟合模型并排除不重要的特征，直到达到所需的特征数量。 基模型可以是 LR、决策树、SVM等。 import numpy as np from sklearn.datasets import load_iris from sklearn.feature_selection import SelectKBest,mutual_info_classif from sklearn.feature_selection import RFE # 反向特征消除 rfe_selector = RFE(estimator=rf_classifier, n_features_to_select=2) X_selected = rfe_selector.fit_transform(X, y) # 使用互信息进行前向特征选择 num_features_to_select = 2 selector = SelectKBest(score_func=mutual_info_classif, k=num_features_to_select) X_selected = selector.fit_transform(X, y) 随机搜索\r随机特征子集：随机选择多个特征子集，然后分别评估模型表现，选择评估分数高的特征子集。 随机目标 (Null Importance) 在原始数据集运行模型获取特征重要性; shuffle 多次标签，每次 shuffle 后获取假标签下的特征重要性; 计算真假标签下的特征重要性差异，并基于差异，筛选特征。 问题\r随机目标 (Null Importance) 为什么能够筛选出真正重要的特征？ 对于真正强健、稳定且重要的特征，在真标签下特征很重要，但一旦标签打乱，这些优质特征的重要性就会变差。 相反地，如果某特征在原始标签下表现一般，但打乱标签后，居然重要性上升，那么这个特征反而不靠谱，应当剔除掉。 举一个简单易懂的例子： 示例\r把 userID 作为特征加入模型，预测不同 userID 属于哪类消费人群，显然这个特征对于预测没有任何作用。 但是对于一个过拟合的模型，它可以会学到 userID 到消费人群的直接映射关系，相当于模型直接记住了这个userID是什么消费人群。如果把标签打乱，模型依然会把 userID 直接映射到打乱的标签上，不管是真标签还是假标签，userID 都是最重要的特征。 嵌入法(Embedded)\r嵌入法：是将特征选择过程与学习器训练过程融为一体，二者在同一优化过程中完成，在学习器训练过程中自动地进行了特征选择。 常见的嵌入法： 基于L1/L2惩罚项的 基于SVM的 基于树/森林的特征选择。 分类\rimport numpy as np from sklearn.datasets import load_iris from sklearn.feature_selection import SelectFromModel from sklearn.linear_model impor","date":"2025-05-15","objectID":"/posts/%E6%9C%BA%E5%99%A8%E5%AD%A6%E4%B9%A0%E5%9F%BA%E7%A1%80~07.%E6%95%B0%E6%8D%AE%E8%A7%84%E7%BA%A6/:2:1","tags":["机器学习"],"title":"机器学习基础~07.数据规约","uri":"/posts/%E6%9C%BA%E5%99%A8%E5%AD%A6%E4%B9%A0%E5%9F%BA%E7%A1%80~07.%E6%95%B0%E6%8D%AE%E8%A7%84%E7%BA%A6/#启发式搜索"},{"categories":"机器学习","collections":["机器学习基础"],"content":"11.1 特征选择\r过滤法(Filter)\r单变量\r当单个变量的值符合以下特征时，可以剔除掉该变量： 缺失百分比(Missing Percentage)：缺失样本比例过多且难以填补。 方差(Variance)：连续型变量的方差接近于0，说明其特征值趋向于单一值的状态，对模型帮助不大。 频数(Frequency)：类别型变量的枚举值集中在单一某枚举值上。 import numpy as np from sklearn.datasets import load_iris from sklearn.feature_selection import VarianceThreshold # 加载示例数据（鸢尾花数据集） data = load_iris() X = data.data # 特征 y = data.target # 目标类别 # 低方差滤波 variance_selector = VarianceThreshold(threshold=0.5) X_low_variance = variance_selector.fit_transform(X) 多变量\r多个变量之间，有以下情形： 自变量与自变量之间：删除高度相关的特征，避免多重共线性。 自变量和因变量之间：相关性越高，说明特征对模型预测目标更重要，建议保留。 连续型 vs 连续型\r皮尔逊相关系数(Pearson Correlation Coefficient)：需要两个变量都服从正态分布，皮尔逊相关系数反映两个变量的线性相关程度，大于 0 的时候表示两者正相关，小于 0 的时候表示两者负相关。当两个变量线性相关时，相关系数趋于 1 或 -1，正负号指向正负相关关系。 $$ r=\\frac{\\sum\\left(X_i-\\bar{X}\\right)\\left(Y_i-\\bar{Y}\\right)}{\\sqrt{\\sum\\left(X_i-\\bar{X}\\right)^2 \\sum\\left(Y_i-\\bar{Y}\\right)^2}} $$ 斯皮尔曼相关系数(Spearman’s Rank Correlation Coefficient)：用于衡量两个变量的单调关系（不一定是线性关系），适用于顺序数据或不满足正态分布的连续数据。它基于变量的排序（Rank） 计算，而非原始数据值。 #特征选择（pearson 相关系数法） from sklearn.datasets import load_iris from sklearn.feature_selection import SelectKBest from scipy.stats import pearsonr import numpy as np import pandas as pd iris = load_iris() x = pd.DataFrame(iris.data) y = pd.Series(iris.target) features = iris.feature_names # 两种方法都是 pearson 相关系数法 selector = SelectKBest(lambda X,Y:x.apply(lambda x:x.corr(y)),k=3).fit(x,y) selector = SelectKBest(lambda X,Y:np.array([pearsonr(X[:,i],Y)[0] for i in range(4)]),k=3).fit(x,y) new_x = selector.transform(x) support = [*zip(features,selector.get_support())] 连续型 vs 类别型\r方差分析(Analysis of variance, ANOVA)：检验不同组下的平均数是否存在显著差异。 方差分析前需要满足3个假设: 每组样本具备方差同质性 组内样本服从正态分布 样本间需要独立。 问题\r判断 1、2 和 3 班的同学的数学平均分是否有显著区别？ 班级为类别型变量，数学分数为连续型变量，如果班级与数学分数有相关性，比如 1 班同学数学会更好些，则说明不同班的数学平均分有显著区别。 零假设：三个班的数学分数没有显著区别。 验证方式：看组间方差是否大于组内方差，如果组间方差 \u003e 组内方差，说明存在至少一个分布相对于其他分布较远，则可以考虑拒绝零假设。 肯德尔等级相关系数(Kendall tau rank correlation coefficient) 问题\r评价学历与工资的相关性 肯德尔系数会对按学历对样本排序： 若学历和工资排名相同，则Kendall系数为1，两变量正相关； 若学历和工资完全相反，则系数为-1，两变量负相关； 而如果学历和工资完全独立，系数为0。 类别型vs类别型\r卡方检验(Chi-squared Test)：用于检验两个类别型变量之间的相关性。零假设：两变量之间不相关。卡方值高，说明两变量之间具有相关性的可能性更大。 互信息(Mutual Information)：衡量变量之间相互依赖程度 #载入数据 from sklearn.datasets import load_iris #特征选择 from sklearn.feature_selection import SelectKBest #卡方检验 from sklearn.feature_selection import chi2 iris = load_iris() x = iris.data y = iris.target features = iris.feature_names selector = SelectKBest(chi2,k=2).fit(x,y) support = {k:v for k,v in zip(features,selector.get_support())} 包裹法(Wrapper)\r完全搜索\r完全搜索：遍历所有可能组合的特征子集，然后输入模型，选择最佳模型分数的特征子集。不推荐使用，计算开销过大。 启发式搜索\r启发式搜索：利用启发式信息不断缩小搜索空间的方法。在特征选择中，模型分数或特征权重可作为启发式信息。 前向特征选择（Forward Feature Selection）： 从空特征集开始，逐步添加对模型性能有贡献的特征，直到达到所需的特征数量或达到最佳性能。 反向特征消除（Recursive Feature Elimination，RFE）： 从所有特征开始，反复拟合模型并排除不重要的特征，直到达到所需的特征数量。 基模型可以是 LR、决策树、SVM等。 import numpy as np from sklearn.datasets import load_iris from sklearn.feature_selection import SelectKBest,mutual_info_classif from sklearn.feature_selection import RFE # 反向特征消除 rfe_selector = RFE(estimator=rf_classifier, n_features_to_select=2) X_selected = rfe_selector.fit_transform(X, y) # 使用互信息进行前向特征选择 num_features_to_select = 2 selector = SelectKBest(score_func=mutual_info_classif, k=num_features_to_select) X_selected = selector.fit_transform(X, y) 随机搜索\r随机特征子集：随机选择多个特征子集，然后分别评估模型表现，选择评估分数高的特征子集。 随机目标 (Null Importance) 在原始数据集运行模型获取特征重要性; shuffle 多次标签，每次 shuffle 后获取假标签下的特征重要性; 计算真假标签下的特征重要性差异，并基于差异，筛选特征。 问题\r随机目标 (Null Importance) 为什么能够筛选出真正重要的特征？ 对于真正强健、稳定且重要的特征，在真标签下特征很重要，但一旦标签打乱，这些优质特征的重要性就会变差。 相反地，如果某特征在原始标签下表现一般，但打乱标签后，居然重要性上升，那么这个特征反而不靠谱，应当剔除掉。 举一个简单易懂的例子： 示例\r把 userID 作为特征加入模型，预测不同 userID 属于哪类消费人群，显然这个特征对于预测没有任何作用。 但是对于一个过拟合的模型，它可以会学到 userID 到消费人群的直接映射关系，相当于模型直接记住了这个userID是什么消费人群。如果把标签打乱，模型依然会把 userID 直接映射到打乱的标签上，不管是真标签还是假标签，userID 都是最重要的特征。 嵌入法(Embedded)\r嵌入法：是将特征选择过程与学习器训练过程融为一体，二者在同一优化过程中完成，在学习器训练过程中自动地进行了特征选择。 常见的嵌入法： 基于L1/L2惩罚项的 基于SVM的 基于树/森林的特征选择。 分类\rimport numpy as np from sklearn.datasets import load_iris from sklearn.feature_selection import SelectFromModel from sklearn.linear_model impor","date":"2025-05-15","objectID":"/posts/%E6%9C%BA%E5%99%A8%E5%AD%A6%E4%B9%A0%E5%9F%BA%E7%A1%80~07.%E6%95%B0%E6%8D%AE%E8%A7%84%E7%BA%A6/:2:1","tags":["机器学习"],"title":"机器学习基础~07.数据规约","uri":"/posts/%E6%9C%BA%E5%99%A8%E5%AD%A6%E4%B9%A0%E5%9F%BA%E7%A1%80~07.%E6%95%B0%E6%8D%AE%E8%A7%84%E7%BA%A6/#随机搜索"},{"categories":"机器学习","collections":["机器学习基础"],"content":"11.1 特征选择\r过滤法(Filter)\r单变量\r当单个变量的值符合以下特征时，可以剔除掉该变量： 缺失百分比(Missing Percentage)：缺失样本比例过多且难以填补。 方差(Variance)：连续型变量的方差接近于0，说明其特征值趋向于单一值的状态，对模型帮助不大。 频数(Frequency)：类别型变量的枚举值集中在单一某枚举值上。 import numpy as np from sklearn.datasets import load_iris from sklearn.feature_selection import VarianceThreshold # 加载示例数据（鸢尾花数据集） data = load_iris() X = data.data # 特征 y = data.target # 目标类别 # 低方差滤波 variance_selector = VarianceThreshold(threshold=0.5) X_low_variance = variance_selector.fit_transform(X) 多变量\r多个变量之间，有以下情形： 自变量与自变量之间：删除高度相关的特征，避免多重共线性。 自变量和因变量之间：相关性越高，说明特征对模型预测目标更重要，建议保留。 连续型 vs 连续型\r皮尔逊相关系数(Pearson Correlation Coefficient)：需要两个变量都服从正态分布，皮尔逊相关系数反映两个变量的线性相关程度，大于 0 的时候表示两者正相关，小于 0 的时候表示两者负相关。当两个变量线性相关时，相关系数趋于 1 或 -1，正负号指向正负相关关系。 $$ r=\\frac{\\sum\\left(X_i-\\bar{X}\\right)\\left(Y_i-\\bar{Y}\\right)}{\\sqrt{\\sum\\left(X_i-\\bar{X}\\right)^2 \\sum\\left(Y_i-\\bar{Y}\\right)^2}} $$ 斯皮尔曼相关系数(Spearman’s Rank Correlation Coefficient)：用于衡量两个变量的单调关系（不一定是线性关系），适用于顺序数据或不满足正态分布的连续数据。它基于变量的排序（Rank） 计算，而非原始数据值。 #特征选择（pearson 相关系数法） from sklearn.datasets import load_iris from sklearn.feature_selection import SelectKBest from scipy.stats import pearsonr import numpy as np import pandas as pd iris = load_iris() x = pd.DataFrame(iris.data) y = pd.Series(iris.target) features = iris.feature_names # 两种方法都是 pearson 相关系数法 selector = SelectKBest(lambda X,Y:x.apply(lambda x:x.corr(y)),k=3).fit(x,y) selector = SelectKBest(lambda X,Y:np.array([pearsonr(X[:,i],Y)[0] for i in range(4)]),k=3).fit(x,y) new_x = selector.transform(x) support = [*zip(features,selector.get_support())] 连续型 vs 类别型\r方差分析(Analysis of variance, ANOVA)：检验不同组下的平均数是否存在显著差异。 方差分析前需要满足3个假设: 每组样本具备方差同质性 组内样本服从正态分布 样本间需要独立。 问题\r判断 1、2 和 3 班的同学的数学平均分是否有显著区别？ 班级为类别型变量，数学分数为连续型变量，如果班级与数学分数有相关性，比如 1 班同学数学会更好些，则说明不同班的数学平均分有显著区别。 零假设：三个班的数学分数没有显著区别。 验证方式：看组间方差是否大于组内方差，如果组间方差 \u003e 组内方差，说明存在至少一个分布相对于其他分布较远，则可以考虑拒绝零假设。 肯德尔等级相关系数(Kendall tau rank correlation coefficient) 问题\r评价学历与工资的相关性 肯德尔系数会对按学历对样本排序： 若学历和工资排名相同，则Kendall系数为1，两变量正相关； 若学历和工资完全相反，则系数为-1，两变量负相关； 而如果学历和工资完全独立，系数为0。 类别型vs类别型\r卡方检验(Chi-squared Test)：用于检验两个类别型变量之间的相关性。零假设：两变量之间不相关。卡方值高，说明两变量之间具有相关性的可能性更大。 互信息(Mutual Information)：衡量变量之间相互依赖程度 #载入数据 from sklearn.datasets import load_iris #特征选择 from sklearn.feature_selection import SelectKBest #卡方检验 from sklearn.feature_selection import chi2 iris = load_iris() x = iris.data y = iris.target features = iris.feature_names selector = SelectKBest(chi2,k=2).fit(x,y) support = {k:v for k,v in zip(features,selector.get_support())} 包裹法(Wrapper)\r完全搜索\r完全搜索：遍历所有可能组合的特征子集，然后输入模型，选择最佳模型分数的特征子集。不推荐使用，计算开销过大。 启发式搜索\r启发式搜索：利用启发式信息不断缩小搜索空间的方法。在特征选择中，模型分数或特征权重可作为启发式信息。 前向特征选择（Forward Feature Selection）： 从空特征集开始，逐步添加对模型性能有贡献的特征，直到达到所需的特征数量或达到最佳性能。 反向特征消除（Recursive Feature Elimination，RFE）： 从所有特征开始，反复拟合模型并排除不重要的特征，直到达到所需的特征数量。 基模型可以是 LR、决策树、SVM等。 import numpy as np from sklearn.datasets import load_iris from sklearn.feature_selection import SelectKBest,mutual_info_classif from sklearn.feature_selection import RFE # 反向特征消除 rfe_selector = RFE(estimator=rf_classifier, n_features_to_select=2) X_selected = rfe_selector.fit_transform(X, y) # 使用互信息进行前向特征选择 num_features_to_select = 2 selector = SelectKBest(score_func=mutual_info_classif, k=num_features_to_select) X_selected = selector.fit_transform(X, y) 随机搜索\r随机特征子集：随机选择多个特征子集，然后分别评估模型表现，选择评估分数高的特征子集。 随机目标 (Null Importance) 在原始数据集运行模型获取特征重要性; shuffle 多次标签，每次 shuffle 后获取假标签下的特征重要性; 计算真假标签下的特征重要性差异，并基于差异，筛选特征。 问题\r随机目标 (Null Importance) 为什么能够筛选出真正重要的特征？ 对于真正强健、稳定且重要的特征，在真标签下特征很重要，但一旦标签打乱，这些优质特征的重要性就会变差。 相反地，如果某特征在原始标签下表现一般，但打乱标签后，居然重要性上升，那么这个特征反而不靠谱，应当剔除掉。 举一个简单易懂的例子： 示例\r把 userID 作为特征加入模型，预测不同 userID 属于哪类消费人群，显然这个特征对于预测没有任何作用。 但是对于一个过拟合的模型，它可以会学到 userID 到消费人群的直接映射关系，相当于模型直接记住了这个userID是什么消费人群。如果把标签打乱，模型依然会把 userID 直接映射到打乱的标签上，不管是真标签还是假标签，userID 都是最重要的特征。 嵌入法(Embedded)\r嵌入法：是将特征选择过程与学习器训练过程融为一体，二者在同一优化过程中完成，在学习器训练过程中自动地进行了特征选择。 常见的嵌入法： 基于L1/L2惩罚项的 基于SVM的 基于树/森林的特征选择。 分类\rimport numpy as np from sklearn.datasets import load_iris from sklearn.feature_selection import SelectFromModel from sklearn.linear_model impor","date":"2025-05-15","objectID":"/posts/%E6%9C%BA%E5%99%A8%E5%AD%A6%E4%B9%A0%E5%9F%BA%E7%A1%80~07.%E6%95%B0%E6%8D%AE%E8%A7%84%E7%BA%A6/:2:1","tags":["机器学习"],"title":"机器学习基础~07.数据规约","uri":"/posts/%E6%9C%BA%E5%99%A8%E5%AD%A6%E4%B9%A0%E5%9F%BA%E7%A1%80~07.%E6%95%B0%E6%8D%AE%E8%A7%84%E7%BA%A6/#嵌入法embedded"},{"categories":"机器学习","collections":["机器学习基础"],"content":"11.1 特征选择\r过滤法(Filter)\r单变量\r当单个变量的值符合以下特征时，可以剔除掉该变量： 缺失百分比(Missing Percentage)：缺失样本比例过多且难以填补。 方差(Variance)：连续型变量的方差接近于0，说明其特征值趋向于单一值的状态，对模型帮助不大。 频数(Frequency)：类别型变量的枚举值集中在单一某枚举值上。 import numpy as np from sklearn.datasets import load_iris from sklearn.feature_selection import VarianceThreshold # 加载示例数据（鸢尾花数据集） data = load_iris() X = data.data # 特征 y = data.target # 目标类别 # 低方差滤波 variance_selector = VarianceThreshold(threshold=0.5) X_low_variance = variance_selector.fit_transform(X) 多变量\r多个变量之间，有以下情形： 自变量与自变量之间：删除高度相关的特征，避免多重共线性。 自变量和因变量之间：相关性越高，说明特征对模型预测目标更重要，建议保留。 连续型 vs 连续型\r皮尔逊相关系数(Pearson Correlation Coefficient)：需要两个变量都服从正态分布，皮尔逊相关系数反映两个变量的线性相关程度，大于 0 的时候表示两者正相关，小于 0 的时候表示两者负相关。当两个变量线性相关时，相关系数趋于 1 或 -1，正负号指向正负相关关系。 $$ r=\\frac{\\sum\\left(X_i-\\bar{X}\\right)\\left(Y_i-\\bar{Y}\\right)}{\\sqrt{\\sum\\left(X_i-\\bar{X}\\right)^2 \\sum\\left(Y_i-\\bar{Y}\\right)^2}} $$ 斯皮尔曼相关系数(Spearman’s Rank Correlation Coefficient)：用于衡量两个变量的单调关系（不一定是线性关系），适用于顺序数据或不满足正态分布的连续数据。它基于变量的排序（Rank） 计算，而非原始数据值。 #特征选择（pearson 相关系数法） from sklearn.datasets import load_iris from sklearn.feature_selection import SelectKBest from scipy.stats import pearsonr import numpy as np import pandas as pd iris = load_iris() x = pd.DataFrame(iris.data) y = pd.Series(iris.target) features = iris.feature_names # 两种方法都是 pearson 相关系数法 selector = SelectKBest(lambda X,Y:x.apply(lambda x:x.corr(y)),k=3).fit(x,y) selector = SelectKBest(lambda X,Y:np.array([pearsonr(X[:,i],Y)[0] for i in range(4)]),k=3).fit(x,y) new_x = selector.transform(x) support = [*zip(features,selector.get_support())] 连续型 vs 类别型\r方差分析(Analysis of variance, ANOVA)：检验不同组下的平均数是否存在显著差异。 方差分析前需要满足3个假设: 每组样本具备方差同质性 组内样本服从正态分布 样本间需要独立。 问题\r判断 1、2 和 3 班的同学的数学平均分是否有显著区别？ 班级为类别型变量，数学分数为连续型变量，如果班级与数学分数有相关性，比如 1 班同学数学会更好些，则说明不同班的数学平均分有显著区别。 零假设：三个班的数学分数没有显著区别。 验证方式：看组间方差是否大于组内方差，如果组间方差 \u003e 组内方差，说明存在至少一个分布相对于其他分布较远，则可以考虑拒绝零假设。 肯德尔等级相关系数(Kendall tau rank correlation coefficient) 问题\r评价学历与工资的相关性 肯德尔系数会对按学历对样本排序： 若学历和工资排名相同，则Kendall系数为1，两变量正相关； 若学历和工资完全相反，则系数为-1，两变量负相关； 而如果学历和工资完全独立，系数为0。 类别型vs类别型\r卡方检验(Chi-squared Test)：用于检验两个类别型变量之间的相关性。零假设：两变量之间不相关。卡方值高，说明两变量之间具有相关性的可能性更大。 互信息(Mutual Information)：衡量变量之间相互依赖程度 #载入数据 from sklearn.datasets import load_iris #特征选择 from sklearn.feature_selection import SelectKBest #卡方检验 from sklearn.feature_selection import chi2 iris = load_iris() x = iris.data y = iris.target features = iris.feature_names selector = SelectKBest(chi2,k=2).fit(x,y) support = {k:v for k,v in zip(features,selector.get_support())} 包裹法(Wrapper)\r完全搜索\r完全搜索：遍历所有可能组合的特征子集，然后输入模型，选择最佳模型分数的特征子集。不推荐使用，计算开销过大。 启发式搜索\r启发式搜索：利用启发式信息不断缩小搜索空间的方法。在特征选择中，模型分数或特征权重可作为启发式信息。 前向特征选择（Forward Feature Selection）： 从空特征集开始，逐步添加对模型性能有贡献的特征，直到达到所需的特征数量或达到最佳性能。 反向特征消除（Recursive Feature Elimination，RFE）： 从所有特征开始，反复拟合模型并排除不重要的特征，直到达到所需的特征数量。 基模型可以是 LR、决策树、SVM等。 import numpy as np from sklearn.datasets import load_iris from sklearn.feature_selection import SelectKBest,mutual_info_classif from sklearn.feature_selection import RFE # 反向特征消除 rfe_selector = RFE(estimator=rf_classifier, n_features_to_select=2) X_selected = rfe_selector.fit_transform(X, y) # 使用互信息进行前向特征选择 num_features_to_select = 2 selector = SelectKBest(score_func=mutual_info_classif, k=num_features_to_select) X_selected = selector.fit_transform(X, y) 随机搜索\r随机特征子集：随机选择多个特征子集，然后分别评估模型表现，选择评估分数高的特征子集。 随机目标 (Null Importance) 在原始数据集运行模型获取特征重要性; shuffle 多次标签，每次 shuffle 后获取假标签下的特征重要性; 计算真假标签下的特征重要性差异，并基于差异，筛选特征。 问题\r随机目标 (Null Importance) 为什么能够筛选出真正重要的特征？ 对于真正强健、稳定且重要的特征，在真标签下特征很重要，但一旦标签打乱，这些优质特征的重要性就会变差。 相反地，如果某特征在原始标签下表现一般，但打乱标签后，居然重要性上升，那么这个特征反而不靠谱，应当剔除掉。 举一个简单易懂的例子： 示例\r把 userID 作为特征加入模型，预测不同 userID 属于哪类消费人群，显然这个特征对于预测没有任何作用。 但是对于一个过拟合的模型，它可以会学到 userID 到消费人群的直接映射关系，相当于模型直接记住了这个userID是什么消费人群。如果把标签打乱，模型依然会把 userID 直接映射到打乱的标签上，不管是真标签还是假标签，userID 都是最重要的特征。 嵌入法(Embedded)\r嵌入法：是将特征选择过程与学习器训练过程融为一体，二者在同一优化过程中完成，在学习器训练过程中自动地进行了特征选择。 常见的嵌入法： 基于L1/L2惩罚项的 基于SVM的 基于树/森林的特征选择。 分类\rimport numpy as np from sklearn.datasets import load_iris from sklearn.feature_selection import SelectFromModel from sklearn.linear_model impor","date":"2025-05-15","objectID":"/posts/%E6%9C%BA%E5%99%A8%E5%AD%A6%E4%B9%A0%E5%9F%BA%E7%A1%80~07.%E6%95%B0%E6%8D%AE%E8%A7%84%E7%BA%A6/:2:1","tags":["机器学习"],"title":"机器学习基础~07.数据规约","uri":"/posts/%E6%9C%BA%E5%99%A8%E5%AD%A6%E4%B9%A0%E5%9F%BA%E7%A1%80~07.%E6%95%B0%E6%8D%AE%E8%A7%84%E7%BA%A6/#分类"},{"categories":"机器学习","collections":["机器学习基础"],"content":"11.1 特征选择\r过滤法(Filter)\r单变量\r当单个变量的值符合以下特征时，可以剔除掉该变量： 缺失百分比(Missing Percentage)：缺失样本比例过多且难以填补。 方差(Variance)：连续型变量的方差接近于0，说明其特征值趋向于单一值的状态，对模型帮助不大。 频数(Frequency)：类别型变量的枚举值集中在单一某枚举值上。 import numpy as np from sklearn.datasets import load_iris from sklearn.feature_selection import VarianceThreshold # 加载示例数据（鸢尾花数据集） data = load_iris() X = data.data # 特征 y = data.target # 目标类别 # 低方差滤波 variance_selector = VarianceThreshold(threshold=0.5) X_low_variance = variance_selector.fit_transform(X) 多变量\r多个变量之间，有以下情形： 自变量与自变量之间：删除高度相关的特征，避免多重共线性。 自变量和因变量之间：相关性越高，说明特征对模型预测目标更重要，建议保留。 连续型 vs 连续型\r皮尔逊相关系数(Pearson Correlation Coefficient)：需要两个变量都服从正态分布，皮尔逊相关系数反映两个变量的线性相关程度，大于 0 的时候表示两者正相关，小于 0 的时候表示两者负相关。当两个变量线性相关时，相关系数趋于 1 或 -1，正负号指向正负相关关系。 $$ r=\\frac{\\sum\\left(X_i-\\bar{X}\\right)\\left(Y_i-\\bar{Y}\\right)}{\\sqrt{\\sum\\left(X_i-\\bar{X}\\right)^2 \\sum\\left(Y_i-\\bar{Y}\\right)^2}} $$ 斯皮尔曼相关系数(Spearman’s Rank Correlation Coefficient)：用于衡量两个变量的单调关系（不一定是线性关系），适用于顺序数据或不满足正态分布的连续数据。它基于变量的排序（Rank） 计算，而非原始数据值。 #特征选择（pearson 相关系数法） from sklearn.datasets import load_iris from sklearn.feature_selection import SelectKBest from scipy.stats import pearsonr import numpy as np import pandas as pd iris = load_iris() x = pd.DataFrame(iris.data) y = pd.Series(iris.target) features = iris.feature_names # 两种方法都是 pearson 相关系数法 selector = SelectKBest(lambda X,Y:x.apply(lambda x:x.corr(y)),k=3).fit(x,y) selector = SelectKBest(lambda X,Y:np.array([pearsonr(X[:,i],Y)[0] for i in range(4)]),k=3).fit(x,y) new_x = selector.transform(x) support = [*zip(features,selector.get_support())] 连续型 vs 类别型\r方差分析(Analysis of variance, ANOVA)：检验不同组下的平均数是否存在显著差异。 方差分析前需要满足3个假设: 每组样本具备方差同质性 组内样本服从正态分布 样本间需要独立。 问题\r判断 1、2 和 3 班的同学的数学平均分是否有显著区别？ 班级为类别型变量，数学分数为连续型变量，如果班级与数学分数有相关性，比如 1 班同学数学会更好些，则说明不同班的数学平均分有显著区别。 零假设：三个班的数学分数没有显著区别。 验证方式：看组间方差是否大于组内方差，如果组间方差 \u003e 组内方差，说明存在至少一个分布相对于其他分布较远，则可以考虑拒绝零假设。 肯德尔等级相关系数(Kendall tau rank correlation coefficient) 问题\r评价学历与工资的相关性 肯德尔系数会对按学历对样本排序： 若学历和工资排名相同，则Kendall系数为1，两变量正相关； 若学历和工资完全相反，则系数为-1，两变量负相关； 而如果学历和工资完全独立，系数为0。 类别型vs类别型\r卡方检验(Chi-squared Test)：用于检验两个类别型变量之间的相关性。零假设：两变量之间不相关。卡方值高，说明两变量之间具有相关性的可能性更大。 互信息(Mutual Information)：衡量变量之间相互依赖程度 #载入数据 from sklearn.datasets import load_iris #特征选择 from sklearn.feature_selection import SelectKBest #卡方检验 from sklearn.feature_selection import chi2 iris = load_iris() x = iris.data y = iris.target features = iris.feature_names selector = SelectKBest(chi2,k=2).fit(x,y) support = {k:v for k,v in zip(features,selector.get_support())} 包裹法(Wrapper)\r完全搜索\r完全搜索：遍历所有可能组合的特征子集，然后输入模型，选择最佳模型分数的特征子集。不推荐使用，计算开销过大。 启发式搜索\r启发式搜索：利用启发式信息不断缩小搜索空间的方法。在特征选择中，模型分数或特征权重可作为启发式信息。 前向特征选择（Forward Feature Selection）： 从空特征集开始，逐步添加对模型性能有贡献的特征，直到达到所需的特征数量或达到最佳性能。 反向特征消除（Recursive Feature Elimination，RFE）： 从所有特征开始，反复拟合模型并排除不重要的特征，直到达到所需的特征数量。 基模型可以是 LR、决策树、SVM等。 import numpy as np from sklearn.datasets import load_iris from sklearn.feature_selection import SelectKBest,mutual_info_classif from sklearn.feature_selection import RFE # 反向特征消除 rfe_selector = RFE(estimator=rf_classifier, n_features_to_select=2) X_selected = rfe_selector.fit_transform(X, y) # 使用互信息进行前向特征选择 num_features_to_select = 2 selector = SelectKBest(score_func=mutual_info_classif, k=num_features_to_select) X_selected = selector.fit_transform(X, y) 随机搜索\r随机特征子集：随机选择多个特征子集，然后分别评估模型表现，选择评估分数高的特征子集。 随机目标 (Null Importance) 在原始数据集运行模型获取特征重要性; shuffle 多次标签，每次 shuffle 后获取假标签下的特征重要性; 计算真假标签下的特征重要性差异，并基于差异，筛选特征。 问题\r随机目标 (Null Importance) 为什么能够筛选出真正重要的特征？ 对于真正强健、稳定且重要的特征，在真标签下特征很重要，但一旦标签打乱，这些优质特征的重要性就会变差。 相反地，如果某特征在原始标签下表现一般，但打乱标签后，居然重要性上升，那么这个特征反而不靠谱，应当剔除掉。 举一个简单易懂的例子： 示例\r把 userID 作为特征加入模型，预测不同 userID 属于哪类消费人群，显然这个特征对于预测没有任何作用。 但是对于一个过拟合的模型，它可以会学到 userID 到消费人群的直接映射关系，相当于模型直接记住了这个userID是什么消费人群。如果把标签打乱，模型依然会把 userID 直接映射到打乱的标签上，不管是真标签还是假标签，userID 都是最重要的特征。 嵌入法(Embedded)\r嵌入法：是将特征选择过程与学习器训练过程融为一体，二者在同一优化过程中完成，在学习器训练过程中自动地进行了特征选择。 常见的嵌入法： 基于L1/L2惩罚项的 基于SVM的 基于树/森林的特征选择。 分类\rimport numpy as np from sklearn.datasets import load_iris from sklearn.feature_selection import SelectFromModel from sklearn.linear_model impor","date":"2025-05-15","objectID":"/posts/%E6%9C%BA%E5%99%A8%E5%AD%A6%E4%B9%A0%E5%9F%BA%E7%A1%80~07.%E6%95%B0%E6%8D%AE%E8%A7%84%E7%BA%A6/:2:1","tags":["机器学习"],"title":"机器学习基础~07.数据规约","uri":"/posts/%E6%9C%BA%E5%99%A8%E5%AD%A6%E4%B9%A0%E5%9F%BA%E7%A1%80~07.%E6%95%B0%E6%8D%AE%E8%A7%84%E7%BA%A6/#回归"},{"categories":"机器学习","collections":["机器学习基础"],"content":"11.2 降维\r降维可以减少数据的维度，从而减少存储和计算成本，并防止维度灾难。 降维方法分为线性降维方法和非线性降维方法。 线性降维方法\r主成分分析(PCA)\r主成分分析（Principal Component Analysis，PCA） 是一种常用的降维技术，用于将高维数据投影到低维空间，以保留尽可能多的数据方差。它通过找到数据中的主要方差方向（主成分），将数据投影到这些主成分上，从而实现降低数据维度的目的。每个主成分都是原始特征的线性组合，且彼此正交。 在信号处理领域，我们认为信号具有较大方差，噪声具有较小方差，信号与噪声之比称为信噪比。 信噪比越大意味着数据的质量越好，反之，信噪比越小意味着数据的质量越小。 由此我们不难引出PCA的目标，即最大化投影方差，也就是让数据在主轴上投影的方差最大。 import numpy as np from sklearn.decomposition import PCA from sklearn.datasets import load_iris # 加载示例数据（鸢尾花数据集） data = load_iris() X = data.data # 特征 y = data.target # 目标类别 # n_components：\u003e=1时表示想要求得的主成分个数，传入小于1的float类型，表示保留下的主成分的特征保留度 # 保留98％的方差 pca = PCA(n_components = 0.98) # 主成分个数2 pca = PCA(n_components=2) # 执行主成分分析 X_pca = pca.fit_transform(X) # 输出降维后的数据 print(\"Original Data Shape:\", X.shape) print(\"PCA Transformed Data Shape:\", X_pca.shape) 线性判别分析(LDA)\r线性判别分析（Linear Discriminant Analysis，LDA） 是一种用于分类和降维的统计方法，用于在多类别问题中找到最佳的投影方向，以便在新空间中实现类别的最大可分性。与主成分分析（PCA）不同，LDA是有监督的方法，它考虑了类别信息来优化投影方向。 LDA的目标是将不同类别的样本在新的低维空间中最大程度地分开，同时尽量将同一类别的样本投影到靠近一起的位置。 它通过计算类间散布矩阵（类别之间的差异）和类内散布矩阵（类别内的差异）来选择最佳的投影方向。 最终，LDA会选择投影方向，使得类间散布矩阵与类内散布矩阵的比值最大化。 import numpy as np from sklearn.discriminant_analysis import LinearDiscriminantAnalysis from sklearn.datasets import load_iris # 加载示例数据（鸢尾花数据集） data = load_iris() X = data.data # 特征 y = data.target # 目标类别 # 创建LDA对象，指定降维后的维度数量 num_components = 2 lda = LinearDiscriminantAnalysis(n_components=num_components) # 执行线性判别分析 X_lda = lda.fit_transform(X, y) # 输出降维后的数据 print(\"Original Data Shape:\", X.shape) print(\"LDA Transformed Data Shape:\", X_lda.shape) PCA vs. LDA\r相同点 两者均可以对数据进行降维。 两者在降维时均使用了矩阵特征分解的思想。 两者都假设数据符合高斯分布。 不同点 LDA是有监督的降维方法，而PCA是无监督的降维方法。 LDA降维最多降到类别数 k-1 的维数，而PCA没有这个限制。 LDA除了可以用于降维，还可以用于分类。 LDA选择分类性能最好的投影方向，而PCA选择样本点投影具有最大方差的方向。 独立分量分析(ICA)\r独立分量分析（Independent Component Analysis，ICA） 是一种用于盲源分离的统计方法，用于从混合信号中恢复原始信号，前提是这些信号是相互独立的。ICA 假设混合信号是通过线性组合和一定的非线性变换得到的，目标是通过找到一组分离独立信号的线性组合来还原原始信号。 import numpy as np from sklearn.decomposition import FastICA from sklearn.datasets import load_iris # 加载示例数据（鸢尾花数据集） data = load_iris() X = data.data # 特征 y = data.target # 目标类别 # 使用FastICA进行独立分量分析 ica = FastICA(n_components=2) S_pred = ica.fit_transform(X) # 输出分离的独立信号 print(\"Original Signals:\\n\", S) print(\"Mixed Signals:\\n\", X) print(\"Separated Signals:\\n\", S_pred) 非线性降维方法\r核主成分分析(Kernel PCA)\r核主成分分析（Kernel Principal Component Analysis，Kernel PCA） 是主成分分析（PCA）的一种扩展形式，用于在非线性数据上进行降维。与传统的PCA不同，核主成分分析通过应用核函数来处理非线性关系，从而在高维特征空间中找到最佳的投影方向。核主成分分析的步骤与传统主成分分析类似，但在计算协方差矩阵时，它使用了核函数来实现非线性变换。这使得核主成分分析能够在保留非线性特征的同时，实现数据的降维。 计算步骤： 中心化数据：同样将每个特征值减去对应特征的均值，以确保数据的中心位于原点。 计算核矩阵：使用选择的核函数（如径向基函数核rbf）计算核矩阵，核矩阵的每个元素表示两个样本之间的核函数值。 计算中心化核矩阵：对核矩阵进行中心化处理，确保中心位于原点。 特征值分解：对中心化核矩阵进行特征值分解，得到特征值和特征向量。 选择主成分：根据特征值的大小，选择要保留的主成分数量。 核函数： 线性核（Linear Kernel）： 适用范围：适用于线性可分的数据。 公式：$K(x, y) = x^T y$ 多项式核（Polynomial Kernel）： 适用范围：适用于数据具有多项式关系的情况。 公式：$K(x, y) = (x^T y + c)^d$ 径向基函数核（RBF Kernel）： 适用范围：适用于各种非线性关系，常用于核PCA中。 公式：$K(x, y) = \\exp(-\\gamma |x - y|^2)$ Sigmoid核： 适用范围：适用于捕捉数据间的非线性关系，但在某些情况下可能不如其他核函数效果好。 公式：$K(x, y) = \\tanh(\\alpha x^T y + c)$ import numpy as np from sklearn.decomposition import KernelPCA from sklearn.datasets import make_circles # 生成非线性数据（环形数据） X, y = make_circles(n_samples=400, factor=0.3, noise=0.05) # 使用KernelPCA进行降维 kernel_pca = KernelPCA(n_components=2, kernel='rbf') X_kpca = kernel_pca.fit_transform(X) # 输出降维后的数据 print(\"Original Data Shape:\", X.shape) print(\"KernelPCA Transformed Data Shape:\", X_kpca.shape) 多维缩放（MDS）\r多维缩放（Multidimensional Scaling，MDS） 是一种用于在低维空间中可视化高维数据的技术。它通过保持数据点之间的距离关系来将数据点映射到一个更低维度的空间，以便于可视化和分析。 度量MDS ：试图在低维空间中保持数据点之间的欧氏距离或其他距离度量。它通过优化过程来调整低维空间中的点的位置，使得它们之间的距离与原始高维空间中的距离尽量接近。 非度量MDS： 关注于保持数据点之间的顺序关系，而不一定保持精确的距离。它通过定义一种映射函数，将原始高维空间中的排序关系映射到低维空间中，使得排列顺序尽量保持一致。 import numpy as np from sklearn.manifold import MDS from sklearn.datasets import load_digits import matplotlib.pyplot as plt # 加载示例数据（手写数字数据集） data = load_digits() X = data.data # 特征 y = data.target # 目标类别 # 使用MDS进行降维 mds = MDS(n_components=2) X_mds = mds.fit_transform(X) # 输出降维后的数据 print(\"Original Data Shape:\", X","date":"2025-05-15","objectID":"/posts/%E6%9C%BA%E5%99%A8%E5%AD%A6%E4%B9%A0%E5%9F%BA%E7%A1%80~07.%E6%95%B0%E6%8D%AE%E8%A7%84%E7%BA%A6/:2:2","tags":["机器学习"],"title":"机器学习基础~07.数据规约","uri":"/posts/%E6%9C%BA%E5%99%A8%E5%AD%A6%E4%B9%A0%E5%9F%BA%E7%A1%80~07.%E6%95%B0%E6%8D%AE%E8%A7%84%E7%BA%A6/#降维"},{"categories":"机器学习","collections":["机器学习基础"],"content":"11.2 降维\r降维可以减少数据的维度，从而减少存储和计算成本，并防止维度灾难。 降维方法分为线性降维方法和非线性降维方法。 线性降维方法\r主成分分析(PCA)\r主成分分析（Principal Component Analysis，PCA） 是一种常用的降维技术，用于将高维数据投影到低维空间，以保留尽可能多的数据方差。它通过找到数据中的主要方差方向（主成分），将数据投影到这些主成分上，从而实现降低数据维度的目的。每个主成分都是原始特征的线性组合，且彼此正交。 在信号处理领域，我们认为信号具有较大方差，噪声具有较小方差，信号与噪声之比称为信噪比。 信噪比越大意味着数据的质量越好，反之，信噪比越小意味着数据的质量越小。 由此我们不难引出PCA的目标，即最大化投影方差，也就是让数据在主轴上投影的方差最大。 import numpy as np from sklearn.decomposition import PCA from sklearn.datasets import load_iris # 加载示例数据（鸢尾花数据集） data = load_iris() X = data.data # 特征 y = data.target # 目标类别 # n_components：\u003e=1时表示想要求得的主成分个数，传入小于1的float类型，表示保留下的主成分的特征保留度 # 保留98％的方差 pca = PCA(n_components = 0.98) # 主成分个数2 pca = PCA(n_components=2) # 执行主成分分析 X_pca = pca.fit_transform(X) # 输出降维后的数据 print(\"Original Data Shape:\", X.shape) print(\"PCA Transformed Data Shape:\", X_pca.shape) 线性判别分析(LDA)\r线性判别分析（Linear Discriminant Analysis，LDA） 是一种用于分类和降维的统计方法，用于在多类别问题中找到最佳的投影方向，以便在新空间中实现类别的最大可分性。与主成分分析（PCA）不同，LDA是有监督的方法，它考虑了类别信息来优化投影方向。 LDA的目标是将不同类别的样本在新的低维空间中最大程度地分开，同时尽量将同一类别的样本投影到靠近一起的位置。 它通过计算类间散布矩阵（类别之间的差异）和类内散布矩阵（类别内的差异）来选择最佳的投影方向。 最终，LDA会选择投影方向，使得类间散布矩阵与类内散布矩阵的比值最大化。 import numpy as np from sklearn.discriminant_analysis import LinearDiscriminantAnalysis from sklearn.datasets import load_iris # 加载示例数据（鸢尾花数据集） data = load_iris() X = data.data # 特征 y = data.target # 目标类别 # 创建LDA对象，指定降维后的维度数量 num_components = 2 lda = LinearDiscriminantAnalysis(n_components=num_components) # 执行线性判别分析 X_lda = lda.fit_transform(X, y) # 输出降维后的数据 print(\"Original Data Shape:\", X.shape) print(\"LDA Transformed Data Shape:\", X_lda.shape) PCA vs. LDA\r相同点 两者均可以对数据进行降维。 两者在降维时均使用了矩阵特征分解的思想。 两者都假设数据符合高斯分布。 不同点 LDA是有监督的降维方法，而PCA是无监督的降维方法。 LDA降维最多降到类别数 k-1 的维数，而PCA没有这个限制。 LDA除了可以用于降维，还可以用于分类。 LDA选择分类性能最好的投影方向，而PCA选择样本点投影具有最大方差的方向。 独立分量分析(ICA)\r独立分量分析（Independent Component Analysis，ICA） 是一种用于盲源分离的统计方法，用于从混合信号中恢复原始信号，前提是这些信号是相互独立的。ICA 假设混合信号是通过线性组合和一定的非线性变换得到的，目标是通过找到一组分离独立信号的线性组合来还原原始信号。 import numpy as np from sklearn.decomposition import FastICA from sklearn.datasets import load_iris # 加载示例数据（鸢尾花数据集） data = load_iris() X = data.data # 特征 y = data.target # 目标类别 # 使用FastICA进行独立分量分析 ica = FastICA(n_components=2) S_pred = ica.fit_transform(X) # 输出分离的独立信号 print(\"Original Signals:\\n\", S) print(\"Mixed Signals:\\n\", X) print(\"Separated Signals:\\n\", S_pred) 非线性降维方法\r核主成分分析(Kernel PCA)\r核主成分分析（Kernel Principal Component Analysis，Kernel PCA） 是主成分分析（PCA）的一种扩展形式，用于在非线性数据上进行降维。与传统的PCA不同，核主成分分析通过应用核函数来处理非线性关系，从而在高维特征空间中找到最佳的投影方向。核主成分分析的步骤与传统主成分分析类似，但在计算协方差矩阵时，它使用了核函数来实现非线性变换。这使得核主成分分析能够在保留非线性特征的同时，实现数据的降维。 计算步骤： 中心化数据：同样将每个特征值减去对应特征的均值，以确保数据的中心位于原点。 计算核矩阵：使用选择的核函数（如径向基函数核rbf）计算核矩阵，核矩阵的每个元素表示两个样本之间的核函数值。 计算中心化核矩阵：对核矩阵进行中心化处理，确保中心位于原点。 特征值分解：对中心化核矩阵进行特征值分解，得到特征值和特征向量。 选择主成分：根据特征值的大小，选择要保留的主成分数量。 核函数： 线性核（Linear Kernel）： 适用范围：适用于线性可分的数据。 公式：$K(x, y) = x^T y$ 多项式核（Polynomial Kernel）： 适用范围：适用于数据具有多项式关系的情况。 公式：$K(x, y) = (x^T y + c)^d$ 径向基函数核（RBF Kernel）： 适用范围：适用于各种非线性关系，常用于核PCA中。 公式：$K(x, y) = \\exp(-\\gamma |x - y|^2)$ Sigmoid核： 适用范围：适用于捕捉数据间的非线性关系，但在某些情况下可能不如其他核函数效果好。 公式：$K(x, y) = \\tanh(\\alpha x^T y + c)$ import numpy as np from sklearn.decomposition import KernelPCA from sklearn.datasets import make_circles # 生成非线性数据（环形数据） X, y = make_circles(n_samples=400, factor=0.3, noise=0.05) # 使用KernelPCA进行降维 kernel_pca = KernelPCA(n_components=2, kernel='rbf') X_kpca = kernel_pca.fit_transform(X) # 输出降维后的数据 print(\"Original Data Shape:\", X.shape) print(\"KernelPCA Transformed Data Shape:\", X_kpca.shape) 多维缩放（MDS）\r多维缩放（Multidimensional Scaling，MDS） 是一种用于在低维空间中可视化高维数据的技术。它通过保持数据点之间的距离关系来将数据点映射到一个更低维度的空间，以便于可视化和分析。 度量MDS ：试图在低维空间中保持数据点之间的欧氏距离或其他距离度量。它通过优化过程来调整低维空间中的点的位置，使得它们之间的距离与原始高维空间中的距离尽量接近。 非度量MDS： 关注于保持数据点之间的顺序关系，而不一定保持精确的距离。它通过定义一种映射函数，将原始高维空间中的排序关系映射到低维空间中，使得排列顺序尽量保持一致。 import numpy as np from sklearn.manifold import MDS from sklearn.datasets import load_digits import matplotlib.pyplot as plt # 加载示例数据（手写数字数据集） data = load_digits() X = data.data # 特征 y = data.target # 目标类别 # 使用MDS进行降维 mds = MDS(n_components=2) X_mds = mds.fit_transform(X) # 输出降维后的数据 print(\"Original Data Shape:\", X","date":"2025-05-15","objectID":"/posts/%E6%9C%BA%E5%99%A8%E5%AD%A6%E4%B9%A0%E5%9F%BA%E7%A1%80~07.%E6%95%B0%E6%8D%AE%E8%A7%84%E7%BA%A6/:2:2","tags":["机器学习"],"title":"机器学习基础~07.数据规约","uri":"/posts/%E6%9C%BA%E5%99%A8%E5%AD%A6%E4%B9%A0%E5%9F%BA%E7%A1%80~07.%E6%95%B0%E6%8D%AE%E8%A7%84%E7%BA%A6/#线性降维方法"},{"categories":"机器学习","collections":["机器学习基础"],"content":"11.2 降维\r降维可以减少数据的维度，从而减少存储和计算成本，并防止维度灾难。 降维方法分为线性降维方法和非线性降维方法。 线性降维方法\r主成分分析(PCA)\r主成分分析（Principal Component Analysis，PCA） 是一种常用的降维技术，用于将高维数据投影到低维空间，以保留尽可能多的数据方差。它通过找到数据中的主要方差方向（主成分），将数据投影到这些主成分上，从而实现降低数据维度的目的。每个主成分都是原始特征的线性组合，且彼此正交。 在信号处理领域，我们认为信号具有较大方差，噪声具有较小方差，信号与噪声之比称为信噪比。 信噪比越大意味着数据的质量越好，反之，信噪比越小意味着数据的质量越小。 由此我们不难引出PCA的目标，即最大化投影方差，也就是让数据在主轴上投影的方差最大。 import numpy as np from sklearn.decomposition import PCA from sklearn.datasets import load_iris # 加载示例数据（鸢尾花数据集） data = load_iris() X = data.data # 特征 y = data.target # 目标类别 # n_components：\u003e=1时表示想要求得的主成分个数，传入小于1的float类型，表示保留下的主成分的特征保留度 # 保留98％的方差 pca = PCA(n_components = 0.98) # 主成分个数2 pca = PCA(n_components=2) # 执行主成分分析 X_pca = pca.fit_transform(X) # 输出降维后的数据 print(\"Original Data Shape:\", X.shape) print(\"PCA Transformed Data Shape:\", X_pca.shape) 线性判别分析(LDA)\r线性判别分析（Linear Discriminant Analysis，LDA） 是一种用于分类和降维的统计方法，用于在多类别问题中找到最佳的投影方向，以便在新空间中实现类别的最大可分性。与主成分分析（PCA）不同，LDA是有监督的方法，它考虑了类别信息来优化投影方向。 LDA的目标是将不同类别的样本在新的低维空间中最大程度地分开，同时尽量将同一类别的样本投影到靠近一起的位置。 它通过计算类间散布矩阵（类别之间的差异）和类内散布矩阵（类别内的差异）来选择最佳的投影方向。 最终，LDA会选择投影方向，使得类间散布矩阵与类内散布矩阵的比值最大化。 import numpy as np from sklearn.discriminant_analysis import LinearDiscriminantAnalysis from sklearn.datasets import load_iris # 加载示例数据（鸢尾花数据集） data = load_iris() X = data.data # 特征 y = data.target # 目标类别 # 创建LDA对象，指定降维后的维度数量 num_components = 2 lda = LinearDiscriminantAnalysis(n_components=num_components) # 执行线性判别分析 X_lda = lda.fit_transform(X, y) # 输出降维后的数据 print(\"Original Data Shape:\", X.shape) print(\"LDA Transformed Data Shape:\", X_lda.shape) PCA vs. LDA\r相同点 两者均可以对数据进行降维。 两者在降维时均使用了矩阵特征分解的思想。 两者都假设数据符合高斯分布。 不同点 LDA是有监督的降维方法，而PCA是无监督的降维方法。 LDA降维最多降到类别数 k-1 的维数，而PCA没有这个限制。 LDA除了可以用于降维，还可以用于分类。 LDA选择分类性能最好的投影方向，而PCA选择样本点投影具有最大方差的方向。 独立分量分析(ICA)\r独立分量分析（Independent Component Analysis，ICA） 是一种用于盲源分离的统计方法，用于从混合信号中恢复原始信号，前提是这些信号是相互独立的。ICA 假设混合信号是通过线性组合和一定的非线性变换得到的，目标是通过找到一组分离独立信号的线性组合来还原原始信号。 import numpy as np from sklearn.decomposition import FastICA from sklearn.datasets import load_iris # 加载示例数据（鸢尾花数据集） data = load_iris() X = data.data # 特征 y = data.target # 目标类别 # 使用FastICA进行独立分量分析 ica = FastICA(n_components=2) S_pred = ica.fit_transform(X) # 输出分离的独立信号 print(\"Original Signals:\\n\", S) print(\"Mixed Signals:\\n\", X) print(\"Separated Signals:\\n\", S_pred) 非线性降维方法\r核主成分分析(Kernel PCA)\r核主成分分析（Kernel Principal Component Analysis，Kernel PCA） 是主成分分析（PCA）的一种扩展形式，用于在非线性数据上进行降维。与传统的PCA不同，核主成分分析通过应用核函数来处理非线性关系，从而在高维特征空间中找到最佳的投影方向。核主成分分析的步骤与传统主成分分析类似，但在计算协方差矩阵时，它使用了核函数来实现非线性变换。这使得核主成分分析能够在保留非线性特征的同时，实现数据的降维。 计算步骤： 中心化数据：同样将每个特征值减去对应特征的均值，以确保数据的中心位于原点。 计算核矩阵：使用选择的核函数（如径向基函数核rbf）计算核矩阵，核矩阵的每个元素表示两个样本之间的核函数值。 计算中心化核矩阵：对核矩阵进行中心化处理，确保中心位于原点。 特征值分解：对中心化核矩阵进行特征值分解，得到特征值和特征向量。 选择主成分：根据特征值的大小，选择要保留的主成分数量。 核函数： 线性核（Linear Kernel）： 适用范围：适用于线性可分的数据。 公式：$K(x, y) = x^T y$ 多项式核（Polynomial Kernel）： 适用范围：适用于数据具有多项式关系的情况。 公式：$K(x, y) = (x^T y + c)^d$ 径向基函数核（RBF Kernel）： 适用范围：适用于各种非线性关系，常用于核PCA中。 公式：$K(x, y) = \\exp(-\\gamma |x - y|^2)$ Sigmoid核： 适用范围：适用于捕捉数据间的非线性关系，但在某些情况下可能不如其他核函数效果好。 公式：$K(x, y) = \\tanh(\\alpha x^T y + c)$ import numpy as np from sklearn.decomposition import KernelPCA from sklearn.datasets import make_circles # 生成非线性数据（环形数据） X, y = make_circles(n_samples=400, factor=0.3, noise=0.05) # 使用KernelPCA进行降维 kernel_pca = KernelPCA(n_components=2, kernel='rbf') X_kpca = kernel_pca.fit_transform(X) # 输出降维后的数据 print(\"Original Data Shape:\", X.shape) print(\"KernelPCA Transformed Data Shape:\", X_kpca.shape) 多维缩放（MDS）\r多维缩放（Multidimensional Scaling，MDS） 是一种用于在低维空间中可视化高维数据的技术。它通过保持数据点之间的距离关系来将数据点映射到一个更低维度的空间，以便于可视化和分析。 度量MDS ：试图在低维空间中保持数据点之间的欧氏距离或其他距离度量。它通过优化过程来调整低维空间中的点的位置，使得它们之间的距离与原始高维空间中的距离尽量接近。 非度量MDS： 关注于保持数据点之间的顺序关系，而不一定保持精确的距离。它通过定义一种映射函数，将原始高维空间中的排序关系映射到低维空间中，使得排列顺序尽量保持一致。 import numpy as np from sklearn.manifold import MDS from sklearn.datasets import load_digits import matplotlib.pyplot as plt # 加载示例数据（手写数字数据集） data = load_digits() X = data.data # 特征 y = data.target # 目标类别 # 使用MDS进行降维 mds = MDS(n_components=2) X_mds = mds.fit_transform(X) # 输出降维后的数据 print(\"Original Data Shape:\", X","date":"2025-05-15","objectID":"/posts/%E6%9C%BA%E5%99%A8%E5%AD%A6%E4%B9%A0%E5%9F%BA%E7%A1%80~07.%E6%95%B0%E6%8D%AE%E8%A7%84%E7%BA%A6/:2:2","tags":["机器学习"],"title":"机器学习基础~07.数据规约","uri":"/posts/%E6%9C%BA%E5%99%A8%E5%AD%A6%E4%B9%A0%E5%9F%BA%E7%A1%80~07.%E6%95%B0%E6%8D%AE%E8%A7%84%E7%BA%A6/#主成分分析pca"},{"categories":"机器学习","collections":["机器学习基础"],"content":"11.2 降维\r降维可以减少数据的维度，从而减少存储和计算成本，并防止维度灾难。 降维方法分为线性降维方法和非线性降维方法。 线性降维方法\r主成分分析(PCA)\r主成分分析（Principal Component Analysis，PCA） 是一种常用的降维技术，用于将高维数据投影到低维空间，以保留尽可能多的数据方差。它通过找到数据中的主要方差方向（主成分），将数据投影到这些主成分上，从而实现降低数据维度的目的。每个主成分都是原始特征的线性组合，且彼此正交。 在信号处理领域，我们认为信号具有较大方差，噪声具有较小方差，信号与噪声之比称为信噪比。 信噪比越大意味着数据的质量越好，反之，信噪比越小意味着数据的质量越小。 由此我们不难引出PCA的目标，即最大化投影方差，也就是让数据在主轴上投影的方差最大。 import numpy as np from sklearn.decomposition import PCA from sklearn.datasets import load_iris # 加载示例数据（鸢尾花数据集） data = load_iris() X = data.data # 特征 y = data.target # 目标类别 # n_components：\u003e=1时表示想要求得的主成分个数，传入小于1的float类型，表示保留下的主成分的特征保留度 # 保留98％的方差 pca = PCA(n_components = 0.98) # 主成分个数2 pca = PCA(n_components=2) # 执行主成分分析 X_pca = pca.fit_transform(X) # 输出降维后的数据 print(\"Original Data Shape:\", X.shape) print(\"PCA Transformed Data Shape:\", X_pca.shape) 线性判别分析(LDA)\r线性判别分析（Linear Discriminant Analysis，LDA） 是一种用于分类和降维的统计方法，用于在多类别问题中找到最佳的投影方向，以便在新空间中实现类别的最大可分性。与主成分分析（PCA）不同，LDA是有监督的方法，它考虑了类别信息来优化投影方向。 LDA的目标是将不同类别的样本在新的低维空间中最大程度地分开，同时尽量将同一类别的样本投影到靠近一起的位置。 它通过计算类间散布矩阵（类别之间的差异）和类内散布矩阵（类别内的差异）来选择最佳的投影方向。 最终，LDA会选择投影方向，使得类间散布矩阵与类内散布矩阵的比值最大化。 import numpy as np from sklearn.discriminant_analysis import LinearDiscriminantAnalysis from sklearn.datasets import load_iris # 加载示例数据（鸢尾花数据集） data = load_iris() X = data.data # 特征 y = data.target # 目标类别 # 创建LDA对象，指定降维后的维度数量 num_components = 2 lda = LinearDiscriminantAnalysis(n_components=num_components) # 执行线性判别分析 X_lda = lda.fit_transform(X, y) # 输出降维后的数据 print(\"Original Data Shape:\", X.shape) print(\"LDA Transformed Data Shape:\", X_lda.shape) PCA vs. LDA\r相同点 两者均可以对数据进行降维。 两者在降维时均使用了矩阵特征分解的思想。 两者都假设数据符合高斯分布。 不同点 LDA是有监督的降维方法，而PCA是无监督的降维方法。 LDA降维最多降到类别数 k-1 的维数，而PCA没有这个限制。 LDA除了可以用于降维，还可以用于分类。 LDA选择分类性能最好的投影方向，而PCA选择样本点投影具有最大方差的方向。 独立分量分析(ICA)\r独立分量分析（Independent Component Analysis，ICA） 是一种用于盲源分离的统计方法，用于从混合信号中恢复原始信号，前提是这些信号是相互独立的。ICA 假设混合信号是通过线性组合和一定的非线性变换得到的，目标是通过找到一组分离独立信号的线性组合来还原原始信号。 import numpy as np from sklearn.decomposition import FastICA from sklearn.datasets import load_iris # 加载示例数据（鸢尾花数据集） data = load_iris() X = data.data # 特征 y = data.target # 目标类别 # 使用FastICA进行独立分量分析 ica = FastICA(n_components=2) S_pred = ica.fit_transform(X) # 输出分离的独立信号 print(\"Original Signals:\\n\", S) print(\"Mixed Signals:\\n\", X) print(\"Separated Signals:\\n\", S_pred) 非线性降维方法\r核主成分分析(Kernel PCA)\r核主成分分析（Kernel Principal Component Analysis，Kernel PCA） 是主成分分析（PCA）的一种扩展形式，用于在非线性数据上进行降维。与传统的PCA不同，核主成分分析通过应用核函数来处理非线性关系，从而在高维特征空间中找到最佳的投影方向。核主成分分析的步骤与传统主成分分析类似，但在计算协方差矩阵时，它使用了核函数来实现非线性变换。这使得核主成分分析能够在保留非线性特征的同时，实现数据的降维。 计算步骤： 中心化数据：同样将每个特征值减去对应特征的均值，以确保数据的中心位于原点。 计算核矩阵：使用选择的核函数（如径向基函数核rbf）计算核矩阵，核矩阵的每个元素表示两个样本之间的核函数值。 计算中心化核矩阵：对核矩阵进行中心化处理，确保中心位于原点。 特征值分解：对中心化核矩阵进行特征值分解，得到特征值和特征向量。 选择主成分：根据特征值的大小，选择要保留的主成分数量。 核函数： 线性核（Linear Kernel）： 适用范围：适用于线性可分的数据。 公式：$K(x, y) = x^T y$ 多项式核（Polynomial Kernel）： 适用范围：适用于数据具有多项式关系的情况。 公式：$K(x, y) = (x^T y + c)^d$ 径向基函数核（RBF Kernel）： 适用范围：适用于各种非线性关系，常用于核PCA中。 公式：$K(x, y) = \\exp(-\\gamma |x - y|^2)$ Sigmoid核： 适用范围：适用于捕捉数据间的非线性关系，但在某些情况下可能不如其他核函数效果好。 公式：$K(x, y) = \\tanh(\\alpha x^T y + c)$ import numpy as np from sklearn.decomposition import KernelPCA from sklearn.datasets import make_circles # 生成非线性数据（环形数据） X, y = make_circles(n_samples=400, factor=0.3, noise=0.05) # 使用KernelPCA进行降维 kernel_pca = KernelPCA(n_components=2, kernel='rbf') X_kpca = kernel_pca.fit_transform(X) # 输出降维后的数据 print(\"Original Data Shape:\", X.shape) print(\"KernelPCA Transformed Data Shape:\", X_kpca.shape) 多维缩放（MDS）\r多维缩放（Multidimensional Scaling，MDS） 是一种用于在低维空间中可视化高维数据的技术。它通过保持数据点之间的距离关系来将数据点映射到一个更低维度的空间，以便于可视化和分析。 度量MDS ：试图在低维空间中保持数据点之间的欧氏距离或其他距离度量。它通过优化过程来调整低维空间中的点的位置，使得它们之间的距离与原始高维空间中的距离尽量接近。 非度量MDS： 关注于保持数据点之间的顺序关系，而不一定保持精确的距离。它通过定义一种映射函数，将原始高维空间中的排序关系映射到低维空间中，使得排列顺序尽量保持一致。 import numpy as np from sklearn.manifold import MDS from sklearn.datasets import load_digits import matplotlib.pyplot as plt # 加载示例数据（手写数字数据集） data = load_digits() X = data.data # 特征 y = data.target # 目标类别 # 使用MDS进行降维 mds = MDS(n_components=2) X_mds = mds.fit_transform(X) # 输出降维后的数据 print(\"Original Data Shape:\", X","date":"2025-05-15","objectID":"/posts/%E6%9C%BA%E5%99%A8%E5%AD%A6%E4%B9%A0%E5%9F%BA%E7%A1%80~07.%E6%95%B0%E6%8D%AE%E8%A7%84%E7%BA%A6/:2:2","tags":["机器学习"],"title":"机器学习基础~07.数据规约","uri":"/posts/%E6%9C%BA%E5%99%A8%E5%AD%A6%E4%B9%A0%E5%9F%BA%E7%A1%80~07.%E6%95%B0%E6%8D%AE%E8%A7%84%E7%BA%A6/#线性判别分析lda"},{"categories":"机器学习","collections":["机器学习基础"],"content":"11.2 降维\r降维可以减少数据的维度，从而减少存储和计算成本，并防止维度灾难。 降维方法分为线性降维方法和非线性降维方法。 线性降维方法\r主成分分析(PCA)\r主成分分析（Principal Component Analysis，PCA） 是一种常用的降维技术，用于将高维数据投影到低维空间，以保留尽可能多的数据方差。它通过找到数据中的主要方差方向（主成分），将数据投影到这些主成分上，从而实现降低数据维度的目的。每个主成分都是原始特征的线性组合，且彼此正交。 在信号处理领域，我们认为信号具有较大方差，噪声具有较小方差，信号与噪声之比称为信噪比。 信噪比越大意味着数据的质量越好，反之，信噪比越小意味着数据的质量越小。 由此我们不难引出PCA的目标，即最大化投影方差，也就是让数据在主轴上投影的方差最大。 import numpy as np from sklearn.decomposition import PCA from sklearn.datasets import load_iris # 加载示例数据（鸢尾花数据集） data = load_iris() X = data.data # 特征 y = data.target # 目标类别 # n_components：\u003e=1时表示想要求得的主成分个数，传入小于1的float类型，表示保留下的主成分的特征保留度 # 保留98％的方差 pca = PCA(n_components = 0.98) # 主成分个数2 pca = PCA(n_components=2) # 执行主成分分析 X_pca = pca.fit_transform(X) # 输出降维后的数据 print(\"Original Data Shape:\", X.shape) print(\"PCA Transformed Data Shape:\", X_pca.shape) 线性判别分析(LDA)\r线性判别分析（Linear Discriminant Analysis，LDA） 是一种用于分类和降维的统计方法，用于在多类别问题中找到最佳的投影方向，以便在新空间中实现类别的最大可分性。与主成分分析（PCA）不同，LDA是有监督的方法，它考虑了类别信息来优化投影方向。 LDA的目标是将不同类别的样本在新的低维空间中最大程度地分开，同时尽量将同一类别的样本投影到靠近一起的位置。 它通过计算类间散布矩阵（类别之间的差异）和类内散布矩阵（类别内的差异）来选择最佳的投影方向。 最终，LDA会选择投影方向，使得类间散布矩阵与类内散布矩阵的比值最大化。 import numpy as np from sklearn.discriminant_analysis import LinearDiscriminantAnalysis from sklearn.datasets import load_iris # 加载示例数据（鸢尾花数据集） data = load_iris() X = data.data # 特征 y = data.target # 目标类别 # 创建LDA对象，指定降维后的维度数量 num_components = 2 lda = LinearDiscriminantAnalysis(n_components=num_components) # 执行线性判别分析 X_lda = lda.fit_transform(X, y) # 输出降维后的数据 print(\"Original Data Shape:\", X.shape) print(\"LDA Transformed Data Shape:\", X_lda.shape) PCA vs. LDA\r相同点 两者均可以对数据进行降维。 两者在降维时均使用了矩阵特征分解的思想。 两者都假设数据符合高斯分布。 不同点 LDA是有监督的降维方法，而PCA是无监督的降维方法。 LDA降维最多降到类别数 k-1 的维数，而PCA没有这个限制。 LDA除了可以用于降维，还可以用于分类。 LDA选择分类性能最好的投影方向，而PCA选择样本点投影具有最大方差的方向。 独立分量分析(ICA)\r独立分量分析（Independent Component Analysis，ICA） 是一种用于盲源分离的统计方法，用于从混合信号中恢复原始信号，前提是这些信号是相互独立的。ICA 假设混合信号是通过线性组合和一定的非线性变换得到的，目标是通过找到一组分离独立信号的线性组合来还原原始信号。 import numpy as np from sklearn.decomposition import FastICA from sklearn.datasets import load_iris # 加载示例数据（鸢尾花数据集） data = load_iris() X = data.data # 特征 y = data.target # 目标类别 # 使用FastICA进行独立分量分析 ica = FastICA(n_components=2) S_pred = ica.fit_transform(X) # 输出分离的独立信号 print(\"Original Signals:\\n\", S) print(\"Mixed Signals:\\n\", X) print(\"Separated Signals:\\n\", S_pred) 非线性降维方法\r核主成分分析(Kernel PCA)\r核主成分分析（Kernel Principal Component Analysis，Kernel PCA） 是主成分分析（PCA）的一种扩展形式，用于在非线性数据上进行降维。与传统的PCA不同，核主成分分析通过应用核函数来处理非线性关系，从而在高维特征空间中找到最佳的投影方向。核主成分分析的步骤与传统主成分分析类似，但在计算协方差矩阵时，它使用了核函数来实现非线性变换。这使得核主成分分析能够在保留非线性特征的同时，实现数据的降维。 计算步骤： 中心化数据：同样将每个特征值减去对应特征的均值，以确保数据的中心位于原点。 计算核矩阵：使用选择的核函数（如径向基函数核rbf）计算核矩阵，核矩阵的每个元素表示两个样本之间的核函数值。 计算中心化核矩阵：对核矩阵进行中心化处理，确保中心位于原点。 特征值分解：对中心化核矩阵进行特征值分解，得到特征值和特征向量。 选择主成分：根据特征值的大小，选择要保留的主成分数量。 核函数： 线性核（Linear Kernel）： 适用范围：适用于线性可分的数据。 公式：$K(x, y) = x^T y$ 多项式核（Polynomial Kernel）： 适用范围：适用于数据具有多项式关系的情况。 公式：$K(x, y) = (x^T y + c)^d$ 径向基函数核（RBF Kernel）： 适用范围：适用于各种非线性关系，常用于核PCA中。 公式：$K(x, y) = \\exp(-\\gamma |x - y|^2)$ Sigmoid核： 适用范围：适用于捕捉数据间的非线性关系，但在某些情况下可能不如其他核函数效果好。 公式：$K(x, y) = \\tanh(\\alpha x^T y + c)$ import numpy as np from sklearn.decomposition import KernelPCA from sklearn.datasets import make_circles # 生成非线性数据（环形数据） X, y = make_circles(n_samples=400, factor=0.3, noise=0.05) # 使用KernelPCA进行降维 kernel_pca = KernelPCA(n_components=2, kernel='rbf') X_kpca = kernel_pca.fit_transform(X) # 输出降维后的数据 print(\"Original Data Shape:\", X.shape) print(\"KernelPCA Transformed Data Shape:\", X_kpca.shape) 多维缩放（MDS）\r多维缩放（Multidimensional Scaling，MDS） 是一种用于在低维空间中可视化高维数据的技术。它通过保持数据点之间的距离关系来将数据点映射到一个更低维度的空间，以便于可视化和分析。 度量MDS ：试图在低维空间中保持数据点之间的欧氏距离或其他距离度量。它通过优化过程来调整低维空间中的点的位置，使得它们之间的距离与原始高维空间中的距离尽量接近。 非度量MDS： 关注于保持数据点之间的顺序关系，而不一定保持精确的距离。它通过定义一种映射函数，将原始高维空间中的排序关系映射到低维空间中，使得排列顺序尽量保持一致。 import numpy as np from sklearn.manifold import MDS from sklearn.datasets import load_digits import matplotlib.pyplot as plt # 加载示例数据（手写数字数据集） data = load_digits() X = data.data # 特征 y = data.target # 目标类别 # 使用MDS进行降维 mds = MDS(n_components=2) X_mds = mds.fit_transform(X) # 输出降维后的数据 print(\"Original Data Shape:\", X","date":"2025-05-15","objectID":"/posts/%E6%9C%BA%E5%99%A8%E5%AD%A6%E4%B9%A0%E5%9F%BA%E7%A1%80~07.%E6%95%B0%E6%8D%AE%E8%A7%84%E7%BA%A6/:2:2","tags":["机器学习"],"title":"机器学习基础~07.数据规约","uri":"/posts/%E6%9C%BA%E5%99%A8%E5%AD%A6%E4%B9%A0%E5%9F%BA%E7%A1%80~07.%E6%95%B0%E6%8D%AE%E8%A7%84%E7%BA%A6/#pca-vs-lda"},{"categories":"机器学习","collections":["机器学习基础"],"content":"11.2 降维\r降维可以减少数据的维度，从而减少存储和计算成本，并防止维度灾难。 降维方法分为线性降维方法和非线性降维方法。 线性降维方法\r主成分分析(PCA)\r主成分分析（Principal Component Analysis，PCA） 是一种常用的降维技术，用于将高维数据投影到低维空间，以保留尽可能多的数据方差。它通过找到数据中的主要方差方向（主成分），将数据投影到这些主成分上，从而实现降低数据维度的目的。每个主成分都是原始特征的线性组合，且彼此正交。 在信号处理领域，我们认为信号具有较大方差，噪声具有较小方差，信号与噪声之比称为信噪比。 信噪比越大意味着数据的质量越好，反之，信噪比越小意味着数据的质量越小。 由此我们不难引出PCA的目标，即最大化投影方差，也就是让数据在主轴上投影的方差最大。 import numpy as np from sklearn.decomposition import PCA from sklearn.datasets import load_iris # 加载示例数据（鸢尾花数据集） data = load_iris() X = data.data # 特征 y = data.target # 目标类别 # n_components：\u003e=1时表示想要求得的主成分个数，传入小于1的float类型，表示保留下的主成分的特征保留度 # 保留98％的方差 pca = PCA(n_components = 0.98) # 主成分个数2 pca = PCA(n_components=2) # 执行主成分分析 X_pca = pca.fit_transform(X) # 输出降维后的数据 print(\"Original Data Shape:\", X.shape) print(\"PCA Transformed Data Shape:\", X_pca.shape) 线性判别分析(LDA)\r线性判别分析（Linear Discriminant Analysis，LDA） 是一种用于分类和降维的统计方法，用于在多类别问题中找到最佳的投影方向，以便在新空间中实现类别的最大可分性。与主成分分析（PCA）不同，LDA是有监督的方法，它考虑了类别信息来优化投影方向。 LDA的目标是将不同类别的样本在新的低维空间中最大程度地分开，同时尽量将同一类别的样本投影到靠近一起的位置。 它通过计算类间散布矩阵（类别之间的差异）和类内散布矩阵（类别内的差异）来选择最佳的投影方向。 最终，LDA会选择投影方向，使得类间散布矩阵与类内散布矩阵的比值最大化。 import numpy as np from sklearn.discriminant_analysis import LinearDiscriminantAnalysis from sklearn.datasets import load_iris # 加载示例数据（鸢尾花数据集） data = load_iris() X = data.data # 特征 y = data.target # 目标类别 # 创建LDA对象，指定降维后的维度数量 num_components = 2 lda = LinearDiscriminantAnalysis(n_components=num_components) # 执行线性判别分析 X_lda = lda.fit_transform(X, y) # 输出降维后的数据 print(\"Original Data Shape:\", X.shape) print(\"LDA Transformed Data Shape:\", X_lda.shape) PCA vs. LDA\r相同点 两者均可以对数据进行降维。 两者在降维时均使用了矩阵特征分解的思想。 两者都假设数据符合高斯分布。 不同点 LDA是有监督的降维方法，而PCA是无监督的降维方法。 LDA降维最多降到类别数 k-1 的维数，而PCA没有这个限制。 LDA除了可以用于降维，还可以用于分类。 LDA选择分类性能最好的投影方向，而PCA选择样本点投影具有最大方差的方向。 独立分量分析(ICA)\r独立分量分析（Independent Component Analysis，ICA） 是一种用于盲源分离的统计方法，用于从混合信号中恢复原始信号，前提是这些信号是相互独立的。ICA 假设混合信号是通过线性组合和一定的非线性变换得到的，目标是通过找到一组分离独立信号的线性组合来还原原始信号。 import numpy as np from sklearn.decomposition import FastICA from sklearn.datasets import load_iris # 加载示例数据（鸢尾花数据集） data = load_iris() X = data.data # 特征 y = data.target # 目标类别 # 使用FastICA进行独立分量分析 ica = FastICA(n_components=2) S_pred = ica.fit_transform(X) # 输出分离的独立信号 print(\"Original Signals:\\n\", S) print(\"Mixed Signals:\\n\", X) print(\"Separated Signals:\\n\", S_pred) 非线性降维方法\r核主成分分析(Kernel PCA)\r核主成分分析（Kernel Principal Component Analysis，Kernel PCA） 是主成分分析（PCA）的一种扩展形式，用于在非线性数据上进行降维。与传统的PCA不同，核主成分分析通过应用核函数来处理非线性关系，从而在高维特征空间中找到最佳的投影方向。核主成分分析的步骤与传统主成分分析类似，但在计算协方差矩阵时，它使用了核函数来实现非线性变换。这使得核主成分分析能够在保留非线性特征的同时，实现数据的降维。 计算步骤： 中心化数据：同样将每个特征值减去对应特征的均值，以确保数据的中心位于原点。 计算核矩阵：使用选择的核函数（如径向基函数核rbf）计算核矩阵，核矩阵的每个元素表示两个样本之间的核函数值。 计算中心化核矩阵：对核矩阵进行中心化处理，确保中心位于原点。 特征值分解：对中心化核矩阵进行特征值分解，得到特征值和特征向量。 选择主成分：根据特征值的大小，选择要保留的主成分数量。 核函数： 线性核（Linear Kernel）： 适用范围：适用于线性可分的数据。 公式：$K(x, y) = x^T y$ 多项式核（Polynomial Kernel）： 适用范围：适用于数据具有多项式关系的情况。 公式：$K(x, y) = (x^T y + c)^d$ 径向基函数核（RBF Kernel）： 适用范围：适用于各种非线性关系，常用于核PCA中。 公式：$K(x, y) = \\exp(-\\gamma |x - y|^2)$ Sigmoid核： 适用范围：适用于捕捉数据间的非线性关系，但在某些情况下可能不如其他核函数效果好。 公式：$K(x, y) = \\tanh(\\alpha x^T y + c)$ import numpy as np from sklearn.decomposition import KernelPCA from sklearn.datasets import make_circles # 生成非线性数据（环形数据） X, y = make_circles(n_samples=400, factor=0.3, noise=0.05) # 使用KernelPCA进行降维 kernel_pca = KernelPCA(n_components=2, kernel='rbf') X_kpca = kernel_pca.fit_transform(X) # 输出降维后的数据 print(\"Original Data Shape:\", X.shape) print(\"KernelPCA Transformed Data Shape:\", X_kpca.shape) 多维缩放（MDS）\r多维缩放（Multidimensional Scaling，MDS） 是一种用于在低维空间中可视化高维数据的技术。它通过保持数据点之间的距离关系来将数据点映射到一个更低维度的空间，以便于可视化和分析。 度量MDS ：试图在低维空间中保持数据点之间的欧氏距离或其他距离度量。它通过优化过程来调整低维空间中的点的位置，使得它们之间的距离与原始高维空间中的距离尽量接近。 非度量MDS： 关注于保持数据点之间的顺序关系，而不一定保持精确的距离。它通过定义一种映射函数，将原始高维空间中的排序关系映射到低维空间中，使得排列顺序尽量保持一致。 import numpy as np from sklearn.manifold import MDS from sklearn.datasets import load_digits import matplotlib.pyplot as plt # 加载示例数据（手写数字数据集） data = load_digits() X = data.data # 特征 y = data.target # 目标类别 # 使用MDS进行降维 mds = MDS(n_components=2) X_mds = mds.fit_transform(X) # 输出降维后的数据 print(\"Original Data Shape:\", X","date":"2025-05-15","objectID":"/posts/%E6%9C%BA%E5%99%A8%E5%AD%A6%E4%B9%A0%E5%9F%BA%E7%A1%80~07.%E6%95%B0%E6%8D%AE%E8%A7%84%E7%BA%A6/:2:2","tags":["机器学习"],"title":"机器学习基础~07.数据规约","uri":"/posts/%E6%9C%BA%E5%99%A8%E5%AD%A6%E4%B9%A0%E5%9F%BA%E7%A1%80~07.%E6%95%B0%E6%8D%AE%E8%A7%84%E7%BA%A6/#独立分量分析ica"},{"categories":"机器学习","collections":["机器学习基础"],"content":"11.2 降维\r降维可以减少数据的维度，从而减少存储和计算成本，并防止维度灾难。 降维方法分为线性降维方法和非线性降维方法。 线性降维方法\r主成分分析(PCA)\r主成分分析（Principal Component Analysis，PCA） 是一种常用的降维技术，用于将高维数据投影到低维空间，以保留尽可能多的数据方差。它通过找到数据中的主要方差方向（主成分），将数据投影到这些主成分上，从而实现降低数据维度的目的。每个主成分都是原始特征的线性组合，且彼此正交。 在信号处理领域，我们认为信号具有较大方差，噪声具有较小方差，信号与噪声之比称为信噪比。 信噪比越大意味着数据的质量越好，反之，信噪比越小意味着数据的质量越小。 由此我们不难引出PCA的目标，即最大化投影方差，也就是让数据在主轴上投影的方差最大。 import numpy as np from sklearn.decomposition import PCA from sklearn.datasets import load_iris # 加载示例数据（鸢尾花数据集） data = load_iris() X = data.data # 特征 y = data.target # 目标类别 # n_components：\u003e=1时表示想要求得的主成分个数，传入小于1的float类型，表示保留下的主成分的特征保留度 # 保留98％的方差 pca = PCA(n_components = 0.98) # 主成分个数2 pca = PCA(n_components=2) # 执行主成分分析 X_pca = pca.fit_transform(X) # 输出降维后的数据 print(\"Original Data Shape:\", X.shape) print(\"PCA Transformed Data Shape:\", X_pca.shape) 线性判别分析(LDA)\r线性判别分析（Linear Discriminant Analysis，LDA） 是一种用于分类和降维的统计方法，用于在多类别问题中找到最佳的投影方向，以便在新空间中实现类别的最大可分性。与主成分分析（PCA）不同，LDA是有监督的方法，它考虑了类别信息来优化投影方向。 LDA的目标是将不同类别的样本在新的低维空间中最大程度地分开，同时尽量将同一类别的样本投影到靠近一起的位置。 它通过计算类间散布矩阵（类别之间的差异）和类内散布矩阵（类别内的差异）来选择最佳的投影方向。 最终，LDA会选择投影方向，使得类间散布矩阵与类内散布矩阵的比值最大化。 import numpy as np from sklearn.discriminant_analysis import LinearDiscriminantAnalysis from sklearn.datasets import load_iris # 加载示例数据（鸢尾花数据集） data = load_iris() X = data.data # 特征 y = data.target # 目标类别 # 创建LDA对象，指定降维后的维度数量 num_components = 2 lda = LinearDiscriminantAnalysis(n_components=num_components) # 执行线性判别分析 X_lda = lda.fit_transform(X, y) # 输出降维后的数据 print(\"Original Data Shape:\", X.shape) print(\"LDA Transformed Data Shape:\", X_lda.shape) PCA vs. LDA\r相同点 两者均可以对数据进行降维。 两者在降维时均使用了矩阵特征分解的思想。 两者都假设数据符合高斯分布。 不同点 LDA是有监督的降维方法，而PCA是无监督的降维方法。 LDA降维最多降到类别数 k-1 的维数，而PCA没有这个限制。 LDA除了可以用于降维，还可以用于分类。 LDA选择分类性能最好的投影方向，而PCA选择样本点投影具有最大方差的方向。 独立分量分析(ICA)\r独立分量分析（Independent Component Analysis，ICA） 是一种用于盲源分离的统计方法，用于从混合信号中恢复原始信号，前提是这些信号是相互独立的。ICA 假设混合信号是通过线性组合和一定的非线性变换得到的，目标是通过找到一组分离独立信号的线性组合来还原原始信号。 import numpy as np from sklearn.decomposition import FastICA from sklearn.datasets import load_iris # 加载示例数据（鸢尾花数据集） data = load_iris() X = data.data # 特征 y = data.target # 目标类别 # 使用FastICA进行独立分量分析 ica = FastICA(n_components=2) S_pred = ica.fit_transform(X) # 输出分离的独立信号 print(\"Original Signals:\\n\", S) print(\"Mixed Signals:\\n\", X) print(\"Separated Signals:\\n\", S_pred) 非线性降维方法\r核主成分分析(Kernel PCA)\r核主成分分析（Kernel Principal Component Analysis，Kernel PCA） 是主成分分析（PCA）的一种扩展形式，用于在非线性数据上进行降维。与传统的PCA不同，核主成分分析通过应用核函数来处理非线性关系，从而在高维特征空间中找到最佳的投影方向。核主成分分析的步骤与传统主成分分析类似，但在计算协方差矩阵时，它使用了核函数来实现非线性变换。这使得核主成分分析能够在保留非线性特征的同时，实现数据的降维。 计算步骤： 中心化数据：同样将每个特征值减去对应特征的均值，以确保数据的中心位于原点。 计算核矩阵：使用选择的核函数（如径向基函数核rbf）计算核矩阵，核矩阵的每个元素表示两个样本之间的核函数值。 计算中心化核矩阵：对核矩阵进行中心化处理，确保中心位于原点。 特征值分解：对中心化核矩阵进行特征值分解，得到特征值和特征向量。 选择主成分：根据特征值的大小，选择要保留的主成分数量。 核函数： 线性核（Linear Kernel）： 适用范围：适用于线性可分的数据。 公式：$K(x, y) = x^T y$ 多项式核（Polynomial Kernel）： 适用范围：适用于数据具有多项式关系的情况。 公式：$K(x, y) = (x^T y + c)^d$ 径向基函数核（RBF Kernel）： 适用范围：适用于各种非线性关系，常用于核PCA中。 公式：$K(x, y) = \\exp(-\\gamma |x - y|^2)$ Sigmoid核： 适用范围：适用于捕捉数据间的非线性关系，但在某些情况下可能不如其他核函数效果好。 公式：$K(x, y) = \\tanh(\\alpha x^T y + c)$ import numpy as np from sklearn.decomposition import KernelPCA from sklearn.datasets import make_circles # 生成非线性数据（环形数据） X, y = make_circles(n_samples=400, factor=0.3, noise=0.05) # 使用KernelPCA进行降维 kernel_pca = KernelPCA(n_components=2, kernel='rbf') X_kpca = kernel_pca.fit_transform(X) # 输出降维后的数据 print(\"Original Data Shape:\", X.shape) print(\"KernelPCA Transformed Data Shape:\", X_kpca.shape) 多维缩放（MDS）\r多维缩放（Multidimensional Scaling，MDS） 是一种用于在低维空间中可视化高维数据的技术。它通过保持数据点之间的距离关系来将数据点映射到一个更低维度的空间，以便于可视化和分析。 度量MDS ：试图在低维空间中保持数据点之间的欧氏距离或其他距离度量。它通过优化过程来调整低维空间中的点的位置，使得它们之间的距离与原始高维空间中的距离尽量接近。 非度量MDS： 关注于保持数据点之间的顺序关系，而不一定保持精确的距离。它通过定义一种映射函数，将原始高维空间中的排序关系映射到低维空间中，使得排列顺序尽量保持一致。 import numpy as np from sklearn.manifold import MDS from sklearn.datasets import load_digits import matplotlib.pyplot as plt # 加载示例数据（手写数字数据集） data = load_digits() X = data.data # 特征 y = data.target # 目标类别 # 使用MDS进行降维 mds = MDS(n_components=2) X_mds = mds.fit_transform(X) # 输出降维后的数据 print(\"Original Data Shape:\", X","date":"2025-05-15","objectID":"/posts/%E6%9C%BA%E5%99%A8%E5%AD%A6%E4%B9%A0%E5%9F%BA%E7%A1%80~07.%E6%95%B0%E6%8D%AE%E8%A7%84%E7%BA%A6/:2:2","tags":["机器学习"],"title":"机器学习基础~07.数据规约","uri":"/posts/%E6%9C%BA%E5%99%A8%E5%AD%A6%E4%B9%A0%E5%9F%BA%E7%A1%80~07.%E6%95%B0%E6%8D%AE%E8%A7%84%E7%BA%A6/#非线性降维方法"},{"categories":"机器学习","collections":["机器学习基础"],"content":"11.2 降维\r降维可以减少数据的维度，从而减少存储和计算成本，并防止维度灾难。 降维方法分为线性降维方法和非线性降维方法。 线性降维方法\r主成分分析(PCA)\r主成分分析（Principal Component Analysis，PCA） 是一种常用的降维技术，用于将高维数据投影到低维空间，以保留尽可能多的数据方差。它通过找到数据中的主要方差方向（主成分），将数据投影到这些主成分上，从而实现降低数据维度的目的。每个主成分都是原始特征的线性组合，且彼此正交。 在信号处理领域，我们认为信号具有较大方差，噪声具有较小方差，信号与噪声之比称为信噪比。 信噪比越大意味着数据的质量越好，反之，信噪比越小意味着数据的质量越小。 由此我们不难引出PCA的目标，即最大化投影方差，也就是让数据在主轴上投影的方差最大。 import numpy as np from sklearn.decomposition import PCA from sklearn.datasets import load_iris # 加载示例数据（鸢尾花数据集） data = load_iris() X = data.data # 特征 y = data.target # 目标类别 # n_components：\u003e=1时表示想要求得的主成分个数，传入小于1的float类型，表示保留下的主成分的特征保留度 # 保留98％的方差 pca = PCA(n_components = 0.98) # 主成分个数2 pca = PCA(n_components=2) # 执行主成分分析 X_pca = pca.fit_transform(X) # 输出降维后的数据 print(\"Original Data Shape:\", X.shape) print(\"PCA Transformed Data Shape:\", X_pca.shape) 线性判别分析(LDA)\r线性判别分析（Linear Discriminant Analysis，LDA） 是一种用于分类和降维的统计方法，用于在多类别问题中找到最佳的投影方向，以便在新空间中实现类别的最大可分性。与主成分分析（PCA）不同，LDA是有监督的方法，它考虑了类别信息来优化投影方向。 LDA的目标是将不同类别的样本在新的低维空间中最大程度地分开，同时尽量将同一类别的样本投影到靠近一起的位置。 它通过计算类间散布矩阵（类别之间的差异）和类内散布矩阵（类别内的差异）来选择最佳的投影方向。 最终，LDA会选择投影方向，使得类间散布矩阵与类内散布矩阵的比值最大化。 import numpy as np from sklearn.discriminant_analysis import LinearDiscriminantAnalysis from sklearn.datasets import load_iris # 加载示例数据（鸢尾花数据集） data = load_iris() X = data.data # 特征 y = data.target # 目标类别 # 创建LDA对象，指定降维后的维度数量 num_components = 2 lda = LinearDiscriminantAnalysis(n_components=num_components) # 执行线性判别分析 X_lda = lda.fit_transform(X, y) # 输出降维后的数据 print(\"Original Data Shape:\", X.shape) print(\"LDA Transformed Data Shape:\", X_lda.shape) PCA vs. LDA\r相同点 两者均可以对数据进行降维。 两者在降维时均使用了矩阵特征分解的思想。 两者都假设数据符合高斯分布。 不同点 LDA是有监督的降维方法，而PCA是无监督的降维方法。 LDA降维最多降到类别数 k-1 的维数，而PCA没有这个限制。 LDA除了可以用于降维，还可以用于分类。 LDA选择分类性能最好的投影方向，而PCA选择样本点投影具有最大方差的方向。 独立分量分析(ICA)\r独立分量分析（Independent Component Analysis，ICA） 是一种用于盲源分离的统计方法，用于从混合信号中恢复原始信号，前提是这些信号是相互独立的。ICA 假设混合信号是通过线性组合和一定的非线性变换得到的，目标是通过找到一组分离独立信号的线性组合来还原原始信号。 import numpy as np from sklearn.decomposition import FastICA from sklearn.datasets import load_iris # 加载示例数据（鸢尾花数据集） data = load_iris() X = data.data # 特征 y = data.target # 目标类别 # 使用FastICA进行独立分量分析 ica = FastICA(n_components=2) S_pred = ica.fit_transform(X) # 输出分离的独立信号 print(\"Original Signals:\\n\", S) print(\"Mixed Signals:\\n\", X) print(\"Separated Signals:\\n\", S_pred) 非线性降维方法\r核主成分分析(Kernel PCA)\r核主成分分析（Kernel Principal Component Analysis，Kernel PCA） 是主成分分析（PCA）的一种扩展形式，用于在非线性数据上进行降维。与传统的PCA不同，核主成分分析通过应用核函数来处理非线性关系，从而在高维特征空间中找到最佳的投影方向。核主成分分析的步骤与传统主成分分析类似，但在计算协方差矩阵时，它使用了核函数来实现非线性变换。这使得核主成分分析能够在保留非线性特征的同时，实现数据的降维。 计算步骤： 中心化数据：同样将每个特征值减去对应特征的均值，以确保数据的中心位于原点。 计算核矩阵：使用选择的核函数（如径向基函数核rbf）计算核矩阵，核矩阵的每个元素表示两个样本之间的核函数值。 计算中心化核矩阵：对核矩阵进行中心化处理，确保中心位于原点。 特征值分解：对中心化核矩阵进行特征值分解，得到特征值和特征向量。 选择主成分：根据特征值的大小，选择要保留的主成分数量。 核函数： 线性核（Linear Kernel）： 适用范围：适用于线性可分的数据。 公式：$K(x, y) = x^T y$ 多项式核（Polynomial Kernel）： 适用范围：适用于数据具有多项式关系的情况。 公式：$K(x, y) = (x^T y + c)^d$ 径向基函数核（RBF Kernel）： 适用范围：适用于各种非线性关系，常用于核PCA中。 公式：$K(x, y) = \\exp(-\\gamma |x - y|^2)$ Sigmoid核： 适用范围：适用于捕捉数据间的非线性关系，但在某些情况下可能不如其他核函数效果好。 公式：$K(x, y) = \\tanh(\\alpha x^T y + c)$ import numpy as np from sklearn.decomposition import KernelPCA from sklearn.datasets import make_circles # 生成非线性数据（环形数据） X, y = make_circles(n_samples=400, factor=0.3, noise=0.05) # 使用KernelPCA进行降维 kernel_pca = KernelPCA(n_components=2, kernel='rbf') X_kpca = kernel_pca.fit_transform(X) # 输出降维后的数据 print(\"Original Data Shape:\", X.shape) print(\"KernelPCA Transformed Data Shape:\", X_kpca.shape) 多维缩放（MDS）\r多维缩放（Multidimensional Scaling，MDS） 是一种用于在低维空间中可视化高维数据的技术。它通过保持数据点之间的距离关系来将数据点映射到一个更低维度的空间，以便于可视化和分析。 度量MDS ：试图在低维空间中保持数据点之间的欧氏距离或其他距离度量。它通过优化过程来调整低维空间中的点的位置，使得它们之间的距离与原始高维空间中的距离尽量接近。 非度量MDS： 关注于保持数据点之间的顺序关系，而不一定保持精确的距离。它通过定义一种映射函数，将原始高维空间中的排序关系映射到低维空间中，使得排列顺序尽量保持一致。 import numpy as np from sklearn.manifold import MDS from sklearn.datasets import load_digits import matplotlib.pyplot as plt # 加载示例数据（手写数字数据集） data = load_digits() X = data.data # 特征 y = data.target # 目标类别 # 使用MDS进行降维 mds = MDS(n_components=2) X_mds = mds.fit_transform(X) # 输出降维后的数据 print(\"Original Data Shape:\", X","date":"2025-05-15","objectID":"/posts/%E6%9C%BA%E5%99%A8%E5%AD%A6%E4%B9%A0%E5%9F%BA%E7%A1%80~07.%E6%95%B0%E6%8D%AE%E8%A7%84%E7%BA%A6/:2:2","tags":["机器学习"],"title":"机器学习基础~07.数据规约","uri":"/posts/%E6%9C%BA%E5%99%A8%E5%AD%A6%E4%B9%A0%E5%9F%BA%E7%A1%80~07.%E6%95%B0%E6%8D%AE%E8%A7%84%E7%BA%A6/#核主成分分析kernel-pca"},{"categories":"机器学习","collections":["机器学习基础"],"content":"11.2 降维\r降维可以减少数据的维度，从而减少存储和计算成本，并防止维度灾难。 降维方法分为线性降维方法和非线性降维方法。 线性降维方法\r主成分分析(PCA)\r主成分分析（Principal Component Analysis，PCA） 是一种常用的降维技术，用于将高维数据投影到低维空间，以保留尽可能多的数据方差。它通过找到数据中的主要方差方向（主成分），将数据投影到这些主成分上，从而实现降低数据维度的目的。每个主成分都是原始特征的线性组合，且彼此正交。 在信号处理领域，我们认为信号具有较大方差，噪声具有较小方差，信号与噪声之比称为信噪比。 信噪比越大意味着数据的质量越好，反之，信噪比越小意味着数据的质量越小。 由此我们不难引出PCA的目标，即最大化投影方差，也就是让数据在主轴上投影的方差最大。 import numpy as np from sklearn.decomposition import PCA from sklearn.datasets import load_iris # 加载示例数据（鸢尾花数据集） data = load_iris() X = data.data # 特征 y = data.target # 目标类别 # n_components：\u003e=1时表示想要求得的主成分个数，传入小于1的float类型，表示保留下的主成分的特征保留度 # 保留98％的方差 pca = PCA(n_components = 0.98) # 主成分个数2 pca = PCA(n_components=2) # 执行主成分分析 X_pca = pca.fit_transform(X) # 输出降维后的数据 print(\"Original Data Shape:\", X.shape) print(\"PCA Transformed Data Shape:\", X_pca.shape) 线性判别分析(LDA)\r线性判别分析（Linear Discriminant Analysis，LDA） 是一种用于分类和降维的统计方法，用于在多类别问题中找到最佳的投影方向，以便在新空间中实现类别的最大可分性。与主成分分析（PCA）不同，LDA是有监督的方法，它考虑了类别信息来优化投影方向。 LDA的目标是将不同类别的样本在新的低维空间中最大程度地分开，同时尽量将同一类别的样本投影到靠近一起的位置。 它通过计算类间散布矩阵（类别之间的差异）和类内散布矩阵（类别内的差异）来选择最佳的投影方向。 最终，LDA会选择投影方向，使得类间散布矩阵与类内散布矩阵的比值最大化。 import numpy as np from sklearn.discriminant_analysis import LinearDiscriminantAnalysis from sklearn.datasets import load_iris # 加载示例数据（鸢尾花数据集） data = load_iris() X = data.data # 特征 y = data.target # 目标类别 # 创建LDA对象，指定降维后的维度数量 num_components = 2 lda = LinearDiscriminantAnalysis(n_components=num_components) # 执行线性判别分析 X_lda = lda.fit_transform(X, y) # 输出降维后的数据 print(\"Original Data Shape:\", X.shape) print(\"LDA Transformed Data Shape:\", X_lda.shape) PCA vs. LDA\r相同点 两者均可以对数据进行降维。 两者在降维时均使用了矩阵特征分解的思想。 两者都假设数据符合高斯分布。 不同点 LDA是有监督的降维方法，而PCA是无监督的降维方法。 LDA降维最多降到类别数 k-1 的维数，而PCA没有这个限制。 LDA除了可以用于降维，还可以用于分类。 LDA选择分类性能最好的投影方向，而PCA选择样本点投影具有最大方差的方向。 独立分量分析(ICA)\r独立分量分析（Independent Component Analysis，ICA） 是一种用于盲源分离的统计方法，用于从混合信号中恢复原始信号，前提是这些信号是相互独立的。ICA 假设混合信号是通过线性组合和一定的非线性变换得到的，目标是通过找到一组分离独立信号的线性组合来还原原始信号。 import numpy as np from sklearn.decomposition import FastICA from sklearn.datasets import load_iris # 加载示例数据（鸢尾花数据集） data = load_iris() X = data.data # 特征 y = data.target # 目标类别 # 使用FastICA进行独立分量分析 ica = FastICA(n_components=2) S_pred = ica.fit_transform(X) # 输出分离的独立信号 print(\"Original Signals:\\n\", S) print(\"Mixed Signals:\\n\", X) print(\"Separated Signals:\\n\", S_pred) 非线性降维方法\r核主成分分析(Kernel PCA)\r核主成分分析（Kernel Principal Component Analysis，Kernel PCA） 是主成分分析（PCA）的一种扩展形式，用于在非线性数据上进行降维。与传统的PCA不同，核主成分分析通过应用核函数来处理非线性关系，从而在高维特征空间中找到最佳的投影方向。核主成分分析的步骤与传统主成分分析类似，但在计算协方差矩阵时，它使用了核函数来实现非线性变换。这使得核主成分分析能够在保留非线性特征的同时，实现数据的降维。 计算步骤： 中心化数据：同样将每个特征值减去对应特征的均值，以确保数据的中心位于原点。 计算核矩阵：使用选择的核函数（如径向基函数核rbf）计算核矩阵，核矩阵的每个元素表示两个样本之间的核函数值。 计算中心化核矩阵：对核矩阵进行中心化处理，确保中心位于原点。 特征值分解：对中心化核矩阵进行特征值分解，得到特征值和特征向量。 选择主成分：根据特征值的大小，选择要保留的主成分数量。 核函数： 线性核（Linear Kernel）： 适用范围：适用于线性可分的数据。 公式：$K(x, y) = x^T y$ 多项式核（Polynomial Kernel）： 适用范围：适用于数据具有多项式关系的情况。 公式：$K(x, y) = (x^T y + c)^d$ 径向基函数核（RBF Kernel）： 适用范围：适用于各种非线性关系，常用于核PCA中。 公式：$K(x, y) = \\exp(-\\gamma |x - y|^2)$ Sigmoid核： 适用范围：适用于捕捉数据间的非线性关系，但在某些情况下可能不如其他核函数效果好。 公式：$K(x, y) = \\tanh(\\alpha x^T y + c)$ import numpy as np from sklearn.decomposition import KernelPCA from sklearn.datasets import make_circles # 生成非线性数据（环形数据） X, y = make_circles(n_samples=400, factor=0.3, noise=0.05) # 使用KernelPCA进行降维 kernel_pca = KernelPCA(n_components=2, kernel='rbf') X_kpca = kernel_pca.fit_transform(X) # 输出降维后的数据 print(\"Original Data Shape:\", X.shape) print(\"KernelPCA Transformed Data Shape:\", X_kpca.shape) 多维缩放（MDS）\r多维缩放（Multidimensional Scaling，MDS） 是一种用于在低维空间中可视化高维数据的技术。它通过保持数据点之间的距离关系来将数据点映射到一个更低维度的空间，以便于可视化和分析。 度量MDS ：试图在低维空间中保持数据点之间的欧氏距离或其他距离度量。它通过优化过程来调整低维空间中的点的位置，使得它们之间的距离与原始高维空间中的距离尽量接近。 非度量MDS： 关注于保持数据点之间的顺序关系，而不一定保持精确的距离。它通过定义一种映射函数，将原始高维空间中的排序关系映射到低维空间中，使得排列顺序尽量保持一致。 import numpy as np from sklearn.manifold import MDS from sklearn.datasets import load_digits import matplotlib.pyplot as plt # 加载示例数据（手写数字数据集） data = load_digits() X = data.data # 特征 y = data.target # 目标类别 # 使用MDS进行降维 mds = MDS(n_components=2) X_mds = mds.fit_transform(X) # 输出降维后的数据 print(\"Original Data Shape:\", X","date":"2025-05-15","objectID":"/posts/%E6%9C%BA%E5%99%A8%E5%AD%A6%E4%B9%A0%E5%9F%BA%E7%A1%80~07.%E6%95%B0%E6%8D%AE%E8%A7%84%E7%BA%A6/:2:2","tags":["机器学习"],"title":"机器学习基础~07.数据规约","uri":"/posts/%E6%9C%BA%E5%99%A8%E5%AD%A6%E4%B9%A0%E5%9F%BA%E7%A1%80~07.%E6%95%B0%E6%8D%AE%E8%A7%84%E7%BA%A6/#多维缩放mds"},{"categories":"机器学习","collections":["机器学习基础"],"content":"11.2 降维\r降维可以减少数据的维度，从而减少存储和计算成本，并防止维度灾难。 降维方法分为线性降维方法和非线性降维方法。 线性降维方法\r主成分分析(PCA)\r主成分分析（Principal Component Analysis，PCA） 是一种常用的降维技术，用于将高维数据投影到低维空间，以保留尽可能多的数据方差。它通过找到数据中的主要方差方向（主成分），将数据投影到这些主成分上，从而实现降低数据维度的目的。每个主成分都是原始特征的线性组合，且彼此正交。 在信号处理领域，我们认为信号具有较大方差，噪声具有较小方差，信号与噪声之比称为信噪比。 信噪比越大意味着数据的质量越好，反之，信噪比越小意味着数据的质量越小。 由此我们不难引出PCA的目标，即最大化投影方差，也就是让数据在主轴上投影的方差最大。 import numpy as np from sklearn.decomposition import PCA from sklearn.datasets import load_iris # 加载示例数据（鸢尾花数据集） data = load_iris() X = data.data # 特征 y = data.target # 目标类别 # n_components：\u003e=1时表示想要求得的主成分个数，传入小于1的float类型，表示保留下的主成分的特征保留度 # 保留98％的方差 pca = PCA(n_components = 0.98) # 主成分个数2 pca = PCA(n_components=2) # 执行主成分分析 X_pca = pca.fit_transform(X) # 输出降维后的数据 print(\"Original Data Shape:\", X.shape) print(\"PCA Transformed Data Shape:\", X_pca.shape) 线性判别分析(LDA)\r线性判别分析（Linear Discriminant Analysis，LDA） 是一种用于分类和降维的统计方法，用于在多类别问题中找到最佳的投影方向，以便在新空间中实现类别的最大可分性。与主成分分析（PCA）不同，LDA是有监督的方法，它考虑了类别信息来优化投影方向。 LDA的目标是将不同类别的样本在新的低维空间中最大程度地分开，同时尽量将同一类别的样本投影到靠近一起的位置。 它通过计算类间散布矩阵（类别之间的差异）和类内散布矩阵（类别内的差异）来选择最佳的投影方向。 最终，LDA会选择投影方向，使得类间散布矩阵与类内散布矩阵的比值最大化。 import numpy as np from sklearn.discriminant_analysis import LinearDiscriminantAnalysis from sklearn.datasets import load_iris # 加载示例数据（鸢尾花数据集） data = load_iris() X = data.data # 特征 y = data.target # 目标类别 # 创建LDA对象，指定降维后的维度数量 num_components = 2 lda = LinearDiscriminantAnalysis(n_components=num_components) # 执行线性判别分析 X_lda = lda.fit_transform(X, y) # 输出降维后的数据 print(\"Original Data Shape:\", X.shape) print(\"LDA Transformed Data Shape:\", X_lda.shape) PCA vs. LDA\r相同点 两者均可以对数据进行降维。 两者在降维时均使用了矩阵特征分解的思想。 两者都假设数据符合高斯分布。 不同点 LDA是有监督的降维方法，而PCA是无监督的降维方法。 LDA降维最多降到类别数 k-1 的维数，而PCA没有这个限制。 LDA除了可以用于降维，还可以用于分类。 LDA选择分类性能最好的投影方向，而PCA选择样本点投影具有最大方差的方向。 独立分量分析(ICA)\r独立分量分析（Independent Component Analysis，ICA） 是一种用于盲源分离的统计方法，用于从混合信号中恢复原始信号，前提是这些信号是相互独立的。ICA 假设混合信号是通过线性组合和一定的非线性变换得到的，目标是通过找到一组分离独立信号的线性组合来还原原始信号。 import numpy as np from sklearn.decomposition import FastICA from sklearn.datasets import load_iris # 加载示例数据（鸢尾花数据集） data = load_iris() X = data.data # 特征 y = data.target # 目标类别 # 使用FastICA进行独立分量分析 ica = FastICA(n_components=2) S_pred = ica.fit_transform(X) # 输出分离的独立信号 print(\"Original Signals:\\n\", S) print(\"Mixed Signals:\\n\", X) print(\"Separated Signals:\\n\", S_pred) 非线性降维方法\r核主成分分析(Kernel PCA)\r核主成分分析（Kernel Principal Component Analysis，Kernel PCA） 是主成分分析（PCA）的一种扩展形式，用于在非线性数据上进行降维。与传统的PCA不同，核主成分分析通过应用核函数来处理非线性关系，从而在高维特征空间中找到最佳的投影方向。核主成分分析的步骤与传统主成分分析类似，但在计算协方差矩阵时，它使用了核函数来实现非线性变换。这使得核主成分分析能够在保留非线性特征的同时，实现数据的降维。 计算步骤： 中心化数据：同样将每个特征值减去对应特征的均值，以确保数据的中心位于原点。 计算核矩阵：使用选择的核函数（如径向基函数核rbf）计算核矩阵，核矩阵的每个元素表示两个样本之间的核函数值。 计算中心化核矩阵：对核矩阵进行中心化处理，确保中心位于原点。 特征值分解：对中心化核矩阵进行特征值分解，得到特征值和特征向量。 选择主成分：根据特征值的大小，选择要保留的主成分数量。 核函数： 线性核（Linear Kernel）： 适用范围：适用于线性可分的数据。 公式：$K(x, y) = x^T y$ 多项式核（Polynomial Kernel）： 适用范围：适用于数据具有多项式关系的情况。 公式：$K(x, y) = (x^T y + c)^d$ 径向基函数核（RBF Kernel）： 适用范围：适用于各种非线性关系，常用于核PCA中。 公式：$K(x, y) = \\exp(-\\gamma |x - y|^2)$ Sigmoid核： 适用范围：适用于捕捉数据间的非线性关系，但在某些情况下可能不如其他核函数效果好。 公式：$K(x, y) = \\tanh(\\alpha x^T y + c)$ import numpy as np from sklearn.decomposition import KernelPCA from sklearn.datasets import make_circles # 生成非线性数据（环形数据） X, y = make_circles(n_samples=400, factor=0.3, noise=0.05) # 使用KernelPCA进行降维 kernel_pca = KernelPCA(n_components=2, kernel='rbf') X_kpca = kernel_pca.fit_transform(X) # 输出降维后的数据 print(\"Original Data Shape:\", X.shape) print(\"KernelPCA Transformed Data Shape:\", X_kpca.shape) 多维缩放（MDS）\r多维缩放（Multidimensional Scaling，MDS） 是一种用于在低维空间中可视化高维数据的技术。它通过保持数据点之间的距离关系来将数据点映射到一个更低维度的空间，以便于可视化和分析。 度量MDS ：试图在低维空间中保持数据点之间的欧氏距离或其他距离度量。它通过优化过程来调整低维空间中的点的位置，使得它们之间的距离与原始高维空间中的距离尽量接近。 非度量MDS： 关注于保持数据点之间的顺序关系，而不一定保持精确的距离。它通过定义一种映射函数，将原始高维空间中的排序关系映射到低维空间中，使得排列顺序尽量保持一致。 import numpy as np from sklearn.manifold import MDS from sklearn.datasets import load_digits import matplotlib.pyplot as plt # 加载示例数据（手写数字数据集） data = load_digits() X = data.data # 特征 y = data.target # 目标类别 # 使用MDS进行降维 mds = MDS(n_components=2) X_mds = mds.fit_transform(X) # 输出降维后的数据 print(\"Original Data Shape:\", X","date":"2025-05-15","objectID":"/posts/%E6%9C%BA%E5%99%A8%E5%AD%A6%E4%B9%A0%E5%9F%BA%E7%A1%80~07.%E6%95%B0%E6%8D%AE%E8%A7%84%E7%BA%A6/:2:2","tags":["机器学习"],"title":"机器学习基础~07.数据规约","uri":"/posts/%E6%9C%BA%E5%99%A8%E5%AD%A6%E4%B9%A0%E5%9F%BA%E7%A1%80~07.%E6%95%B0%E6%8D%AE%E8%A7%84%E7%BA%A6/#等度量映射isomap"},{"categories":"机器学习","collections":["机器学习基础"],"content":"11.2 降维\r降维可以减少数据的维度，从而减少存储和计算成本，并防止维度灾难。 降维方法分为线性降维方法和非线性降维方法。 线性降维方法\r主成分分析(PCA)\r主成分分析（Principal Component Analysis，PCA） 是一种常用的降维技术，用于将高维数据投影到低维空间，以保留尽可能多的数据方差。它通过找到数据中的主要方差方向（主成分），将数据投影到这些主成分上，从而实现降低数据维度的目的。每个主成分都是原始特征的线性组合，且彼此正交。 在信号处理领域，我们认为信号具有较大方差，噪声具有较小方差，信号与噪声之比称为信噪比。 信噪比越大意味着数据的质量越好，反之，信噪比越小意味着数据的质量越小。 由此我们不难引出PCA的目标，即最大化投影方差，也就是让数据在主轴上投影的方差最大。 import numpy as np from sklearn.decomposition import PCA from sklearn.datasets import load_iris # 加载示例数据（鸢尾花数据集） data = load_iris() X = data.data # 特征 y = data.target # 目标类别 # n_components：\u003e=1时表示想要求得的主成分个数，传入小于1的float类型，表示保留下的主成分的特征保留度 # 保留98％的方差 pca = PCA(n_components = 0.98) # 主成分个数2 pca = PCA(n_components=2) # 执行主成分分析 X_pca = pca.fit_transform(X) # 输出降维后的数据 print(\"Original Data Shape:\", X.shape) print(\"PCA Transformed Data Shape:\", X_pca.shape) 线性判别分析(LDA)\r线性判别分析（Linear Discriminant Analysis，LDA） 是一种用于分类和降维的统计方法，用于在多类别问题中找到最佳的投影方向，以便在新空间中实现类别的最大可分性。与主成分分析（PCA）不同，LDA是有监督的方法，它考虑了类别信息来优化投影方向。 LDA的目标是将不同类别的样本在新的低维空间中最大程度地分开，同时尽量将同一类别的样本投影到靠近一起的位置。 它通过计算类间散布矩阵（类别之间的差异）和类内散布矩阵（类别内的差异）来选择最佳的投影方向。 最终，LDA会选择投影方向，使得类间散布矩阵与类内散布矩阵的比值最大化。 import numpy as np from sklearn.discriminant_analysis import LinearDiscriminantAnalysis from sklearn.datasets import load_iris # 加载示例数据（鸢尾花数据集） data = load_iris() X = data.data # 特征 y = data.target # 目标类别 # 创建LDA对象，指定降维后的维度数量 num_components = 2 lda = LinearDiscriminantAnalysis(n_components=num_components) # 执行线性判别分析 X_lda = lda.fit_transform(X, y) # 输出降维后的数据 print(\"Original Data Shape:\", X.shape) print(\"LDA Transformed Data Shape:\", X_lda.shape) PCA vs. LDA\r相同点 两者均可以对数据进行降维。 两者在降维时均使用了矩阵特征分解的思想。 两者都假设数据符合高斯分布。 不同点 LDA是有监督的降维方法，而PCA是无监督的降维方法。 LDA降维最多降到类别数 k-1 的维数，而PCA没有这个限制。 LDA除了可以用于降维，还可以用于分类。 LDA选择分类性能最好的投影方向，而PCA选择样本点投影具有最大方差的方向。 独立分量分析(ICA)\r独立分量分析（Independent Component Analysis，ICA） 是一种用于盲源分离的统计方法，用于从混合信号中恢复原始信号，前提是这些信号是相互独立的。ICA 假设混合信号是通过线性组合和一定的非线性变换得到的，目标是通过找到一组分离独立信号的线性组合来还原原始信号。 import numpy as np from sklearn.decomposition import FastICA from sklearn.datasets import load_iris # 加载示例数据（鸢尾花数据集） data = load_iris() X = data.data # 特征 y = data.target # 目标类别 # 使用FastICA进行独立分量分析 ica = FastICA(n_components=2) S_pred = ica.fit_transform(X) # 输出分离的独立信号 print(\"Original Signals:\\n\", S) print(\"Mixed Signals:\\n\", X) print(\"Separated Signals:\\n\", S_pred) 非线性降维方法\r核主成分分析(Kernel PCA)\r核主成分分析（Kernel Principal Component Analysis，Kernel PCA） 是主成分分析（PCA）的一种扩展形式，用于在非线性数据上进行降维。与传统的PCA不同，核主成分分析通过应用核函数来处理非线性关系，从而在高维特征空间中找到最佳的投影方向。核主成分分析的步骤与传统主成分分析类似，但在计算协方差矩阵时，它使用了核函数来实现非线性变换。这使得核主成分分析能够在保留非线性特征的同时，实现数据的降维。 计算步骤： 中心化数据：同样将每个特征值减去对应特征的均值，以确保数据的中心位于原点。 计算核矩阵：使用选择的核函数（如径向基函数核rbf）计算核矩阵，核矩阵的每个元素表示两个样本之间的核函数值。 计算中心化核矩阵：对核矩阵进行中心化处理，确保中心位于原点。 特征值分解：对中心化核矩阵进行特征值分解，得到特征值和特征向量。 选择主成分：根据特征值的大小，选择要保留的主成分数量。 核函数： 线性核（Linear Kernel）： 适用范围：适用于线性可分的数据。 公式：$K(x, y) = x^T y$ 多项式核（Polynomial Kernel）： 适用范围：适用于数据具有多项式关系的情况。 公式：$K(x, y) = (x^T y + c)^d$ 径向基函数核（RBF Kernel）： 适用范围：适用于各种非线性关系，常用于核PCA中。 公式：$K(x, y) = \\exp(-\\gamma |x - y|^2)$ Sigmoid核： 适用范围：适用于捕捉数据间的非线性关系，但在某些情况下可能不如其他核函数效果好。 公式：$K(x, y) = \\tanh(\\alpha x^T y + c)$ import numpy as np from sklearn.decomposition import KernelPCA from sklearn.datasets import make_circles # 生成非线性数据（环形数据） X, y = make_circles(n_samples=400, factor=0.3, noise=0.05) # 使用KernelPCA进行降维 kernel_pca = KernelPCA(n_components=2, kernel='rbf') X_kpca = kernel_pca.fit_transform(X) # 输出降维后的数据 print(\"Original Data Shape:\", X.shape) print(\"KernelPCA Transformed Data Shape:\", X_kpca.shape) 多维缩放（MDS）\r多维缩放（Multidimensional Scaling，MDS） 是一种用于在低维空间中可视化高维数据的技术。它通过保持数据点之间的距离关系来将数据点映射到一个更低维度的空间，以便于可视化和分析。 度量MDS ：试图在低维空间中保持数据点之间的欧氏距离或其他距离度量。它通过优化过程来调整低维空间中的点的位置，使得它们之间的距离与原始高维空间中的距离尽量接近。 非度量MDS： 关注于保持数据点之间的顺序关系，而不一定保持精确的距离。它通过定义一种映射函数，将原始高维空间中的排序关系映射到低维空间中，使得排列顺序尽量保持一致。 import numpy as np from sklearn.manifold import MDS from sklearn.datasets import load_digits import matplotlib.pyplot as plt # 加载示例数据（手写数字数据集） data = load_digits() X = data.data # 特征 y = data.target # 目标类别 # 使用MDS进行降维 mds = MDS(n_components=2) X_mds = mds.fit_transform(X) # 输出降维后的数据 print(\"Original Data Shape:\", X","date":"2025-05-15","objectID":"/posts/%E6%9C%BA%E5%99%A8%E5%AD%A6%E4%B9%A0%E5%9F%BA%E7%A1%80~07.%E6%95%B0%E6%8D%AE%E8%A7%84%E7%BA%A6/:2:2","tags":["机器学习"],"title":"机器学习基础~07.数据规约","uri":"/posts/%E6%9C%BA%E5%99%A8%E5%AD%A6%E4%B9%A0%E5%9F%BA%E7%A1%80~07.%E6%95%B0%E6%8D%AE%E8%A7%84%E7%BA%A6/#局部线性嵌入lle"},{"categories":"机器学习","collections":["机器学习基础"],"content":"11.2 降维\r降维可以减少数据的维度，从而减少存储和计算成本，并防止维度灾难。 降维方法分为线性降维方法和非线性降维方法。 线性降维方法\r主成分分析(PCA)\r主成分分析（Principal Component Analysis，PCA） 是一种常用的降维技术，用于将高维数据投影到低维空间，以保留尽可能多的数据方差。它通过找到数据中的主要方差方向（主成分），将数据投影到这些主成分上，从而实现降低数据维度的目的。每个主成分都是原始特征的线性组合，且彼此正交。 在信号处理领域，我们认为信号具有较大方差，噪声具有较小方差，信号与噪声之比称为信噪比。 信噪比越大意味着数据的质量越好，反之，信噪比越小意味着数据的质量越小。 由此我们不难引出PCA的目标，即最大化投影方差，也就是让数据在主轴上投影的方差最大。 import numpy as np from sklearn.decomposition import PCA from sklearn.datasets import load_iris # 加载示例数据（鸢尾花数据集） data = load_iris() X = data.data # 特征 y = data.target # 目标类别 # n_components：\u003e=1时表示想要求得的主成分个数，传入小于1的float类型，表示保留下的主成分的特征保留度 # 保留98％的方差 pca = PCA(n_components = 0.98) # 主成分个数2 pca = PCA(n_components=2) # 执行主成分分析 X_pca = pca.fit_transform(X) # 输出降维后的数据 print(\"Original Data Shape:\", X.shape) print(\"PCA Transformed Data Shape:\", X_pca.shape) 线性判别分析(LDA)\r线性判别分析（Linear Discriminant Analysis，LDA） 是一种用于分类和降维的统计方法，用于在多类别问题中找到最佳的投影方向，以便在新空间中实现类别的最大可分性。与主成分分析（PCA）不同，LDA是有监督的方法，它考虑了类别信息来优化投影方向。 LDA的目标是将不同类别的样本在新的低维空间中最大程度地分开，同时尽量将同一类别的样本投影到靠近一起的位置。 它通过计算类间散布矩阵（类别之间的差异）和类内散布矩阵（类别内的差异）来选择最佳的投影方向。 最终，LDA会选择投影方向，使得类间散布矩阵与类内散布矩阵的比值最大化。 import numpy as np from sklearn.discriminant_analysis import LinearDiscriminantAnalysis from sklearn.datasets import load_iris # 加载示例数据（鸢尾花数据集） data = load_iris() X = data.data # 特征 y = data.target # 目标类别 # 创建LDA对象，指定降维后的维度数量 num_components = 2 lda = LinearDiscriminantAnalysis(n_components=num_components) # 执行线性判别分析 X_lda = lda.fit_transform(X, y) # 输出降维后的数据 print(\"Original Data Shape:\", X.shape) print(\"LDA Transformed Data Shape:\", X_lda.shape) PCA vs. LDA\r相同点 两者均可以对数据进行降维。 两者在降维时均使用了矩阵特征分解的思想。 两者都假设数据符合高斯分布。 不同点 LDA是有监督的降维方法，而PCA是无监督的降维方法。 LDA降维最多降到类别数 k-1 的维数，而PCA没有这个限制。 LDA除了可以用于降维，还可以用于分类。 LDA选择分类性能最好的投影方向，而PCA选择样本点投影具有最大方差的方向。 独立分量分析(ICA)\r独立分量分析（Independent Component Analysis，ICA） 是一种用于盲源分离的统计方法，用于从混合信号中恢复原始信号，前提是这些信号是相互独立的。ICA 假设混合信号是通过线性组合和一定的非线性变换得到的，目标是通过找到一组分离独立信号的线性组合来还原原始信号。 import numpy as np from sklearn.decomposition import FastICA from sklearn.datasets import load_iris # 加载示例数据（鸢尾花数据集） data = load_iris() X = data.data # 特征 y = data.target # 目标类别 # 使用FastICA进行独立分量分析 ica = FastICA(n_components=2) S_pred = ica.fit_transform(X) # 输出分离的独立信号 print(\"Original Signals:\\n\", S) print(\"Mixed Signals:\\n\", X) print(\"Separated Signals:\\n\", S_pred) 非线性降维方法\r核主成分分析(Kernel PCA)\r核主成分分析（Kernel Principal Component Analysis，Kernel PCA） 是主成分分析（PCA）的一种扩展形式，用于在非线性数据上进行降维。与传统的PCA不同，核主成分分析通过应用核函数来处理非线性关系，从而在高维特征空间中找到最佳的投影方向。核主成分分析的步骤与传统主成分分析类似，但在计算协方差矩阵时，它使用了核函数来实现非线性变换。这使得核主成分分析能够在保留非线性特征的同时，实现数据的降维。 计算步骤： 中心化数据：同样将每个特征值减去对应特征的均值，以确保数据的中心位于原点。 计算核矩阵：使用选择的核函数（如径向基函数核rbf）计算核矩阵，核矩阵的每个元素表示两个样本之间的核函数值。 计算中心化核矩阵：对核矩阵进行中心化处理，确保中心位于原点。 特征值分解：对中心化核矩阵进行特征值分解，得到特征值和特征向量。 选择主成分：根据特征值的大小，选择要保留的主成分数量。 核函数： 线性核（Linear Kernel）： 适用范围：适用于线性可分的数据。 公式：$K(x, y) = x^T y$ 多项式核（Polynomial Kernel）： 适用范围：适用于数据具有多项式关系的情况。 公式：$K(x, y) = (x^T y + c)^d$ 径向基函数核（RBF Kernel）： 适用范围：适用于各种非线性关系，常用于核PCA中。 公式：$K(x, y) = \\exp(-\\gamma |x - y|^2)$ Sigmoid核： 适用范围：适用于捕捉数据间的非线性关系，但在某些情况下可能不如其他核函数效果好。 公式：$K(x, y) = \\tanh(\\alpha x^T y + c)$ import numpy as np from sklearn.decomposition import KernelPCA from sklearn.datasets import make_circles # 生成非线性数据（环形数据） X, y = make_circles(n_samples=400, factor=0.3, noise=0.05) # 使用KernelPCA进行降维 kernel_pca = KernelPCA(n_components=2, kernel='rbf') X_kpca = kernel_pca.fit_transform(X) # 输出降维后的数据 print(\"Original Data Shape:\", X.shape) print(\"KernelPCA Transformed Data Shape:\", X_kpca.shape) 多维缩放（MDS）\r多维缩放（Multidimensional Scaling，MDS） 是一种用于在低维空间中可视化高维数据的技术。它通过保持数据点之间的距离关系来将数据点映射到一个更低维度的空间，以便于可视化和分析。 度量MDS ：试图在低维空间中保持数据点之间的欧氏距离或其他距离度量。它通过优化过程来调整低维空间中的点的位置，使得它们之间的距离与原始高维空间中的距离尽量接近。 非度量MDS： 关注于保持数据点之间的顺序关系，而不一定保持精确的距离。它通过定义一种映射函数，将原始高维空间中的排序关系映射到低维空间中，使得排列顺序尽量保持一致。 import numpy as np from sklearn.manifold import MDS from sklearn.datasets import load_digits import matplotlib.pyplot as plt # 加载示例数据（手写数字数据集） data = load_digits() X = data.data # 特征 y = data.target # 目标类别 # 使用MDS进行降维 mds = MDS(n_components=2) X_mds = mds.fit_transform(X) # 输出降维后的数据 print(\"Original Data Shape:\", X","date":"2025-05-15","objectID":"/posts/%E6%9C%BA%E5%99%A8%E5%AD%A6%E4%B9%A0%E5%9F%BA%E7%A1%80~07.%E6%95%B0%E6%8D%AE%E8%A7%84%E7%BA%A6/:2:2","tags":["机器学习"],"title":"机器学习基础~07.数据规约","uri":"/posts/%E6%9C%BA%E5%99%A8%E5%AD%A6%E4%B9%A0%E5%9F%BA%E7%A1%80~07.%E6%95%B0%E6%8D%AE%E8%A7%84%E7%BA%A6/#t-分布随机近邻嵌入t-sne"},{"categories":"机器学习","collections":["机器学习基础"],"content":"第 12 节 数值规约\r数值规约通过选择替代的、较小的数据来减少数据量，包括有参数方法和无参数方法两类。 有参数方法是使用一个模型来评估数据，只需存放参数，而不需要存放实际数据。例如，回归（线性回归和多元回归）和对数线性模型（近似离散属性集中的多维概率分布）。 无参数方法就需要存放实际数据，例如，直方图、聚类、抽样（采样）、参数回归。 ","date":"2025-05-15","objectID":"/posts/%E6%9C%BA%E5%99%A8%E5%AD%A6%E4%B9%A0%E5%9F%BA%E7%A1%80~07.%E6%95%B0%E6%8D%AE%E8%A7%84%E7%BA%A6/:3:0","tags":["机器学习"],"title":"机器学习基础~07.数据规约","uri":"/posts/%E6%9C%BA%E5%99%A8%E5%AD%A6%E4%B9%A0%E5%9F%BA%E7%A1%80~07.%E6%95%B0%E6%8D%AE%E8%A7%84%E7%BA%A6/#数值规约"},{"categories":"机器学习","collections":["机器学习基础"],"content":"12.1 直方图\r直方图使用分箱来近似数据分布，是一种流行的数据规约形式。属性A的直方图将A的数据分布划分为不相交的子集或桶。如果每个桶只代表单个属性值/频率对，则该桶称为单桶。通常，桶表示给定属性的一个连续区间。 ","date":"2025-05-15","objectID":"/posts/%E6%9C%BA%E5%99%A8%E5%AD%A6%E4%B9%A0%E5%9F%BA%E7%A1%80~07.%E6%95%B0%E6%8D%AE%E8%A7%84%E7%BA%A6/:3:1","tags":["机器学习"],"title":"机器学习基础~07.数据规约","uri":"/posts/%E6%9C%BA%E5%99%A8%E5%AD%A6%E4%B9%A0%E5%9F%BA%E7%A1%80~07.%E6%95%B0%E6%8D%AE%E8%A7%84%E7%BA%A6/#直方图"},{"categories":"机器学习","collections":["机器学习基础"],"content":"12.2 聚类\r聚类技术是将数据元组（即记录，数据表中的一行）视为对象。它将对象划分为簇，使一个簇中的对象相互“相似”，而与其他簇中的对象“相异”。在数据规约中，用数据的簇替换实际数据。该技术的有效性依赖于簇的定义是否符合数据的分布性质。 ","date":"2025-05-15","objectID":"/posts/%E6%9C%BA%E5%99%A8%E5%AD%A6%E4%B9%A0%E5%9F%BA%E7%A1%80~07.%E6%95%B0%E6%8D%AE%E8%A7%84%E7%BA%A6/:3:2","tags":["机器学习"],"title":"机器学习基础~07.数据规约","uri":"/posts/%E6%9C%BA%E5%99%A8%E5%AD%A6%E4%B9%A0%E5%9F%BA%E7%A1%80~07.%E6%95%B0%E6%8D%AE%E8%A7%84%E7%BA%A6/#聚类"},{"categories":"机器学习","collections":["机器学习基础"],"content":"12.3 抽样\r抽样也是一种数据规约技术，它用比原始数据小得多的随机样本（子集）表示原始数据集。假定原始数据集 D 包含 N 个元组，可以采用抽样方法对 D 进行抽样。 ","date":"2025-05-15","objectID":"/posts/%E6%9C%BA%E5%99%A8%E5%AD%A6%E4%B9%A0%E5%9F%BA%E7%A1%80~07.%E6%95%B0%E6%8D%AE%E8%A7%84%E7%BA%A6/:3:3","tags":["机器学习"],"title":"机器学习基础~07.数据规约","uri":"/posts/%E6%9C%BA%E5%99%A8%E5%AD%A6%E4%B9%A0%E5%9F%BA%E7%A1%80~07.%E6%95%B0%E6%8D%AE%E8%A7%84%E7%BA%A6/#抽样"},{"categories":"机器学习","collections":["机器学习基础"],"content":"12.4 参数回归\r简单线性模型和对数线性模型可以用来近似给定的数据。用简单线性模型对数据建模，使之拟合为一条直线。 ","date":"2025-05-15","objectID":"/posts/%E6%9C%BA%E5%99%A8%E5%AD%A6%E4%B9%A0%E5%9F%BA%E7%A1%80~07.%E6%95%B0%E6%8D%AE%E8%A7%84%E7%BA%A6/:3:4","tags":["机器学习"],"title":"机器学习基础~07.数据规约","uri":"/posts/%E6%9C%BA%E5%99%A8%E5%AD%A6%E4%B9%A0%E5%9F%BA%E7%A1%80~07.%E6%95%B0%E6%8D%AE%E8%A7%84%E7%BA%A6/#参数回归"},{"categories":"机器学习","collections":["机器学习基础"],"content":"第 13 节 简单的函数变换\r常见的函数变换有平方、开方、取对数、差分等。 $$ x’\\ =\\ x^2 $$ $$ x’\\ =\\ \\sqrt{x} $$ $$ x’\\ =\\ \\log(x) $$ $$ \\nabla f(x_k)\\ =\\ f(x_{k+1})-f(x_k) $$ 简单的函数变换常用来将不具有正态分布的数据变换成具有正态分布的数据； 在时间序列分析中，有时简单的对数变换或者差分运算就可以将非平稳序列转换成平稳序列。 ","date":"2025-05-15","objectID":"/posts/%E6%9C%BA%E5%99%A8%E5%AD%A6%E4%B9%A0%E5%9F%BA%E7%A1%80~06.%E6%95%B0%E6%8D%AE%E5%8F%98%E6%8D%A2/:1:0","tags":["机器学习"],"title":"机器学习基础~06.数据变换","uri":"/posts/%E6%9C%BA%E5%99%A8%E5%AD%A6%E4%B9%A0%E5%9F%BA%E7%A1%80~06.%E6%95%B0%E6%8D%AE%E5%8F%98%E6%8D%A2/#简单的函数变换"},{"categories":"机器学习","collections":["机器学习基础"],"content":"第 14 节 规范化\r不同评价指标往往具有不同的量纲，数值间的差别可能很大，不进行处理可能会影响到数据分析的结果。 为了消除指标之间的量纲和取值范围差异的影响，需要进行规范化处理，将数据按照比例进行缩放，使之落入一个特定的区域，便于进行综合分析。 标准化（Standardization） ：标准化也称为 Z-score 标准化，通过将数据的均值转化为 0，标准差转化为 1，使数据服从标准正态分布。标准化适用于特征的分布不符合正态分布的情况。 归一化（Normalization） ：归一化将数据范围缩放到 [0, 1] 区间内。这通常是通过对原始值减去最小值并除以范围来实现的。归一化适用于需要确保数据的比例关系不受影响的情况。 Softmax变换（Softmax Transformation） ：Softmax 变换通常用于多类别分类问题中，将原始分数转换为概率分布。它将分数转换为归一化的概率，以便可以预测每个类别的概率。 import numpy as np from sklearn.preprocessing import StandardScaler, MinMaxScaler from scipy.special import softmax # 示例数据 data = np.array([[1, 2, 3], [4, 5, 6], [7, 8, 9]]) # 标准化（Z-score标准化） scaler = StandardScaler() data_standardized = scaler.fit_transform(data) # 归一化（MinMax缩放） minmax_scaler = MinMaxScaler() data_normalized = minmax_scaler.fit_transform(data) # Softmax变换 data_softmax = softmax(data, axis=1) print(\"Original Data:\\n\", data) print(\"Standardized Data:\\n\", data_standardized) print(\"Normalized Data:\\n\", data_normalized) print(\"Softmax Transformed Data:\\n\", data_softmax) 分组归一化：在归一化之前先进行分组，对每一组分别进行归一化，适用于组间差异非常大的情形。 grouped = df.groupby('Areas') df3 = pd.DataFrame() for name,group in grouped: df2 = group[['Age','Salary']] group[['Age','Salary']] = (df2-df2.min())/(df2.max()-df2.min()) df3 = pd.concat([df3,group]) df3.sort_index() ","date":"2025-05-15","objectID":"/posts/%E6%9C%BA%E5%99%A8%E5%AD%A6%E4%B9%A0%E5%9F%BA%E7%A1%80~06.%E6%95%B0%E6%8D%AE%E5%8F%98%E6%8D%A2/:2:0","tags":["机器学习"],"title":"机器学习基础~06.数据变换","uri":"/posts/%E6%9C%BA%E5%99%A8%E5%AD%A6%E4%B9%A0%E5%9F%BA%E7%A1%80~06.%E6%95%B0%E6%8D%AE%E5%8F%98%E6%8D%A2/#规范化"},{"categories":"机器学习","collections":["机器学习基础"],"content":"第 15 节 类型变换\r摘要\r有时由于数据存储类型错误、未正确读取、数据缺失的情况，数据类型并非我们希望的类型，又或者我们需要对数据类型进行变换，符合我们的需求，这时候需要用到「类型变换」。 ","date":"2025-05-15","objectID":"/posts/%E6%9C%BA%E5%99%A8%E5%AD%A6%E4%B9%A0%E5%9F%BA%E7%A1%80~06.%E6%95%B0%E6%8D%AE%E5%8F%98%E6%8D%A2/:3:0","tags":["机器学习"],"title":"机器学习基础~06.数据变换","uri":"/posts/%E6%9C%BA%E5%99%A8%E5%AD%A6%E4%B9%A0%E5%9F%BA%E7%A1%80~06.%E6%95%B0%E6%8D%AE%E5%8F%98%E6%8D%A2/#类型变换"},{"categories":"机器学习","collections":["机器学习基础"],"content":"15.1 转换为数字类型\r比如，某些数字类型的数据可能被错误保存成字符串，可以使用如下方式转为数字类型： # apply 是对某一行或者某一列进行计算，有axis参数 # applymap 是对所有的元素进行计算 df[['Age','Salary']] = df[['Age','Salary']].apply(pd.to_numeric) ","date":"2025-05-15","objectID":"/posts/%E6%9C%BA%E5%99%A8%E5%AD%A6%E4%B9%A0%E5%9F%BA%E7%A1%80~06.%E6%95%B0%E6%8D%AE%E5%8F%98%E6%8D%A2/:3:1","tags":["机器学习"],"title":"机器学习基础~06.数据变换","uri":"/posts/%E6%9C%BA%E5%99%A8%E5%AD%A6%E4%B9%A0%E5%9F%BA%E7%A1%80~06.%E6%95%B0%E6%8D%AE%E5%8F%98%E6%8D%A2/#转换为数字类型"},{"categories":"机器学习","collections":["机器学习基础"],"content":"15.2 字符串转换为时间类型\r默认情况下，pd.to_datetime 将尝试识别以下日期时间格式： ISO 8601 格式，包括日期和时间，例如：2021-09-13 12:30:45 或 2021-09-13T12:30:45。 日期格式，例如：2021-09-13。 时间格式，例如：12:30:45。 其他常见日期时间格式，例如：09/13/2021 或 13-Sep-2021。 import pandas as pd from datetime import datetime # 使用 strptime 转换为日期类型 df['Date'] = df['Date'].apply(lambda x:datetime.strptime(x,'%d/%m/%Y').date()) # 使用 pd.to_datetime 转换为日期类型 df['日期']= pd.to_datetime(df['日期'],format='%d/%m/%Y') ","date":"2025-05-15","objectID":"/posts/%E6%9C%BA%E5%99%A8%E5%AD%A6%E4%B9%A0%E5%9F%BA%E7%A1%80~06.%E6%95%B0%E6%8D%AE%E5%8F%98%E6%8D%A2/:3:2","tags":["机器学习"],"title":"机器学习基础~06.数据变换","uri":"/posts/%E6%9C%BA%E5%99%A8%E5%AD%A6%E4%B9%A0%E5%9F%BA%E7%A1%80~06.%E6%95%B0%E6%8D%AE%E5%8F%98%E6%8D%A2/#字符串转换为时间类型"},{"categories":"机器学习","collections":["机器学习基础"],"content":"15.3 日期转换为字符串类型\r日期格式可以按照设定方式格式化为字符串，常见的日期时间格式代码及其含义如下： %Y: 四位年份（例如，2021）。 %y: 两位年份（例如，21，通常用于表示年份的最后两位）。 %m: 月份（01-12）。 %d: 日期（01-31）。 %H: 24小时制的小时（00-23）。 %I: 12小时制的小时（01-12）。 %M: 分钟（00-59）。 %S: 秒（00-59）。 %f: 微秒（000000-999999）。 %j: 年份中的天数（001-366）。 %U: 年份中的星期数，以星期日为一周的开始（00-53）。 %W: 年份中的星期数，以星期一为一周的开始（00-53）。 %c: 日期时间的本地表示（例如，Tue Sep 13 12:30:45 2021）。 %x: 本地日期表示（例如，09/13/21，根据本地设置）。 %X: 本地时间表示（例如，12:30:45，根据本地设置）。 %a: 缩写的星期几名称（例如，Mon，Tue）。 %A: 完整的星期几名称（例如，Monday，Tuesday）。 %b: 缩写的月份名称（例如，Jan，Feb）。 %B: 完整的月份名称（例如，January，February）。 %p: AM/PM（仅适用于12小时制，例如，AM，PM）。 %z: 时区的偏移量（例如，+0530）。 %Z: 时区的名称或缩写（例如，UTC，EST）。 %W: 年份中的星期数，以星期一为一周的开始（00-53）。 # 使用 strftime 转换为字符串类型 test[\"Date\"] = test[\"Date\"].apply(lambda x:x.strftime(\"%Y/%m/%d\")) ","date":"2025-05-15","objectID":"/posts/%E6%9C%BA%E5%99%A8%E5%AD%A6%E4%B9%A0%E5%9F%BA%E7%A1%80~06.%E6%95%B0%E6%8D%AE%E5%8F%98%E6%8D%A2/:3:3","tags":["机器学习"],"title":"机器学习基础~06.数据变换","uri":"/posts/%E6%9C%BA%E5%99%A8%E5%AD%A6%E4%B9%A0%E5%9F%BA%E7%A1%80~06.%E6%95%B0%E6%8D%AE%E5%8F%98%E6%8D%A2/#日期转换为字符串类型"},{"categories":"机器学习","collections":["机器学习基础"],"content":"15.4 分箱-连续转换为离散\r数据分箱（Data Binning） 将连续变量分组为一系列“箱”或“区间”，以便于分析。 等宽分箱\r等宽分箱：是将数据划分为固定大小的区间。这通常需要指定区间的数量或宽度。等宽分箱能够快速划分数据，并且适用于数值分布均匀的数据。 import pandas as pd #将数据num1按照节点0，30，60，90，100分为5组 bins = [0,30,60,90,100] num = [3,60,50,43,70,52,26,37,0,80,56,77,35,67,100] label = ['组1','组2','组3','组4'] cat = pd.cut(num,bins,labels = label) cat.value_counts() 等频分箱\r等频分箱：是将数据划分为固定数量的区间，每个区间包含相同数量的数据点。这个方法能够在处理数据分布不均匀的情况下，避免某些区间的数据点过多或过少的问题。 #将数据num1划分为5组，使每组的元素数量相同 label = ['组1','组2','组3','组4','组5'] cat2 = pd.qcut(num1,5) cat2 cat2.value_counts() 聚类分箱\r聚类分箱：是一种基于聚类算法的分箱方法，它能够自动将数据分为若干组，每组内的数据点相似度较高。这个方法适用于数据分布不均匀或数据自身不具有明显的规律性的情况。 import numpy as np from sklearn.cluster import KMeans # 创建示例数据 data = np.array([1, 2, 2.5, 3, 5, 6, 7, 9, 10, 11, 15, 16, 20]).reshape(-1, 1) # 设置要分的箱数 n_bins = 3 # 使用K均值聚类进行分箱 kmeans = KMeans(n_clusters=n_bins, random_state=0).fit(data) labels = kmeans.labels_ cluster_centers = kmeans.cluster_centers_ # 打印每个数据点所属的箱 for i, label in enumerate(labels): print(f\"Data point {data[i][0]} belongs to bin {label + 1}\") # 打印每个箱的中心点 for bin_num, center in enumerate(cluster_centers): print(f\"Bin {bin_num + 1} center: {center[0]}\") ","date":"2025-05-15","objectID":"/posts/%E6%9C%BA%E5%99%A8%E5%AD%A6%E4%B9%A0%E5%9F%BA%E7%A1%80~06.%E6%95%B0%E6%8D%AE%E5%8F%98%E6%8D%A2/:3:4","tags":["机器学习"],"title":"机器学习基础~06.数据变换","uri":"/posts/%E6%9C%BA%E5%99%A8%E5%AD%A6%E4%B9%A0%E5%9F%BA%E7%A1%80~06.%E6%95%B0%E6%8D%AE%E5%8F%98%E6%8D%A2/#分箱-连续转换为离散"},{"categories":"机器学习","collections":["机器学习基础"],"content":"15.4 分箱-连续转换为离散\r数据分箱（Data Binning） 将连续变量分组为一系列“箱”或“区间”，以便于分析。 等宽分箱\r等宽分箱：是将数据划分为固定大小的区间。这通常需要指定区间的数量或宽度。等宽分箱能够快速划分数据，并且适用于数值分布均匀的数据。 import pandas as pd #将数据num1按照节点0，30，60，90，100分为5组 bins = [0,30,60,90,100] num = [3,60,50,43,70,52,26,37,0,80,56,77,35,67,100] label = ['组1','组2','组3','组4'] cat = pd.cut(num,bins,labels = label) cat.value_counts() 等频分箱\r等频分箱：是将数据划分为固定数量的区间，每个区间包含相同数量的数据点。这个方法能够在处理数据分布不均匀的情况下，避免某些区间的数据点过多或过少的问题。 #将数据num1划分为5组，使每组的元素数量相同 label = ['组1','组2','组3','组4','组5'] cat2 = pd.qcut(num1,5) cat2 cat2.value_counts() 聚类分箱\r聚类分箱：是一种基于聚类算法的分箱方法，它能够自动将数据分为若干组，每组内的数据点相似度较高。这个方法适用于数据分布不均匀或数据自身不具有明显的规律性的情况。 import numpy as np from sklearn.cluster import KMeans # 创建示例数据 data = np.array([1, 2, 2.5, 3, 5, 6, 7, 9, 10, 11, 15, 16, 20]).reshape(-1, 1) # 设置要分的箱数 n_bins = 3 # 使用K均值聚类进行分箱 kmeans = KMeans(n_clusters=n_bins, random_state=0).fit(data) labels = kmeans.labels_ cluster_centers = kmeans.cluster_centers_ # 打印每个数据点所属的箱 for i, label in enumerate(labels): print(f\"Data point {data[i][0]} belongs to bin {label + 1}\") # 打印每个箱的中心点 for bin_num, center in enumerate(cluster_centers): print(f\"Bin {bin_num + 1} center: {center[0]}\") ","date":"2025-05-15","objectID":"/posts/%E6%9C%BA%E5%99%A8%E5%AD%A6%E4%B9%A0%E5%9F%BA%E7%A1%80~06.%E6%95%B0%E6%8D%AE%E5%8F%98%E6%8D%A2/:3:4","tags":["机器学习"],"title":"机器学习基础~06.数据变换","uri":"/posts/%E6%9C%BA%E5%99%A8%E5%AD%A6%E4%B9%A0%E5%9F%BA%E7%A1%80~06.%E6%95%B0%E6%8D%AE%E5%8F%98%E6%8D%A2/#等宽分箱"},{"categories":"机器学习","collections":["机器学习基础"],"content":"15.4 分箱-连续转换为离散\r数据分箱（Data Binning） 将连续变量分组为一系列“箱”或“区间”，以便于分析。 等宽分箱\r等宽分箱：是将数据划分为固定大小的区间。这通常需要指定区间的数量或宽度。等宽分箱能够快速划分数据，并且适用于数值分布均匀的数据。 import pandas as pd #将数据num1按照节点0，30，60，90，100分为5组 bins = [0,30,60,90,100] num = [3,60,50,43,70,52,26,37,0,80,56,77,35,67,100] label = ['组1','组2','组3','组4'] cat = pd.cut(num,bins,labels = label) cat.value_counts() 等频分箱\r等频分箱：是将数据划分为固定数量的区间，每个区间包含相同数量的数据点。这个方法能够在处理数据分布不均匀的情况下，避免某些区间的数据点过多或过少的问题。 #将数据num1划分为5组，使每组的元素数量相同 label = ['组1','组2','组3','组4','组5'] cat2 = pd.qcut(num1,5) cat2 cat2.value_counts() 聚类分箱\r聚类分箱：是一种基于聚类算法的分箱方法，它能够自动将数据分为若干组，每组内的数据点相似度较高。这个方法适用于数据分布不均匀或数据自身不具有明显的规律性的情况。 import numpy as np from sklearn.cluster import KMeans # 创建示例数据 data = np.array([1, 2, 2.5, 3, 5, 6, 7, 9, 10, 11, 15, 16, 20]).reshape(-1, 1) # 设置要分的箱数 n_bins = 3 # 使用K均值聚类进行分箱 kmeans = KMeans(n_clusters=n_bins, random_state=0).fit(data) labels = kmeans.labels_ cluster_centers = kmeans.cluster_centers_ # 打印每个数据点所属的箱 for i, label in enumerate(labels): print(f\"Data point {data[i][0]} belongs to bin {label + 1}\") # 打印每个箱的中心点 for bin_num, center in enumerate(cluster_centers): print(f\"Bin {bin_num + 1} center: {center[0]}\") ","date":"2025-05-15","objectID":"/posts/%E6%9C%BA%E5%99%A8%E5%AD%A6%E4%B9%A0%E5%9F%BA%E7%A1%80~06.%E6%95%B0%E6%8D%AE%E5%8F%98%E6%8D%A2/:3:4","tags":["机器学习"],"title":"机器学习基础~06.数据变换","uri":"/posts/%E6%9C%BA%E5%99%A8%E5%AD%A6%E4%B9%A0%E5%9F%BA%E7%A1%80~06.%E6%95%B0%E6%8D%AE%E5%8F%98%E6%8D%A2/#等频分箱"},{"categories":"机器学习","collections":["机器学习基础"],"content":"15.4 分箱-连续转换为离散\r数据分箱（Data Binning） 将连续变量分组为一系列“箱”或“区间”，以便于分析。 等宽分箱\r等宽分箱：是将数据划分为固定大小的区间。这通常需要指定区间的数量或宽度。等宽分箱能够快速划分数据，并且适用于数值分布均匀的数据。 import pandas as pd #将数据num1按照节点0，30，60，90，100分为5组 bins = [0,30,60,90,100] num = [3,60,50,43,70,52,26,37,0,80,56,77,35,67,100] label = ['组1','组2','组3','组4'] cat = pd.cut(num,bins,labels = label) cat.value_counts() 等频分箱\r等频分箱：是将数据划分为固定数量的区间，每个区间包含相同数量的数据点。这个方法能够在处理数据分布不均匀的情况下，避免某些区间的数据点过多或过少的问题。 #将数据num1划分为5组，使每组的元素数量相同 label = ['组1','组2','组3','组4','组5'] cat2 = pd.qcut(num1,5) cat2 cat2.value_counts() 聚类分箱\r聚类分箱：是一种基于聚类算法的分箱方法，它能够自动将数据分为若干组，每组内的数据点相似度较高。这个方法适用于数据分布不均匀或数据自身不具有明显的规律性的情况。 import numpy as np from sklearn.cluster import KMeans # 创建示例数据 data = np.array([1, 2, 2.5, 3, 5, 6, 7, 9, 10, 11, 15, 16, 20]).reshape(-1, 1) # 设置要分的箱数 n_bins = 3 # 使用K均值聚类进行分箱 kmeans = KMeans(n_clusters=n_bins, random_state=0).fit(data) labels = kmeans.labels_ cluster_centers = kmeans.cluster_centers_ # 打印每个数据点所属的箱 for i, label in enumerate(labels): print(f\"Data point {data[i][0]} belongs to bin {label + 1}\") # 打印每个箱的中心点 for bin_num, center in enumerate(cluster_centers): print(f\"Bin {bin_num + 1} center: {center[0]}\") ","date":"2025-05-15","objectID":"/posts/%E6%9C%BA%E5%99%A8%E5%AD%A6%E4%B9%A0%E5%9F%BA%E7%A1%80~06.%E6%95%B0%E6%8D%AE%E5%8F%98%E6%8D%A2/:3:4","tags":["机器学习"],"title":"机器学习基础~06.数据变换","uri":"/posts/%E6%9C%BA%E5%99%A8%E5%AD%A6%E4%B9%A0%E5%9F%BA%E7%A1%80~06.%E6%95%B0%E6%8D%AE%E5%8F%98%E6%8D%A2/#聚类分箱"},{"categories":"机器学习","collections":["机器学习基础"],"content":"15.5 编码-离散转换为连续\r编码（Encoding） 是将非数值型数据（如文字、类别、符号）转换为数值型数据的过程，以便计算机能够理解和处理。在机器学习和数据分析中，大多数算法（如回归、神经网络）只能处理数字，因此需要对文本、类别等数据进行编码转换。 数值编码 (Label Encoding)\r数值编码给每个类别分配一个唯一的数字，如下： from sklearn.preprocessing import LabelEncoder # 创建一个 LabelEncoder 对象 label_encoder = LabelEncoder() # 指定需要编码的列名列表 columns_to_encode = ['Color', 'Size', 'Shape'] # 对指定列进行编码 df_encoded = df.copy() df_encoded[columns_to_encode] = df_encoded[columns_to_encode].apply(lambda col: label_encoder.fit_transform(col)) # 训练模型（以随机森林分类器为例） X = df_encoded[columns_to_encode] y = df_encoded['Label'] model = RandomForestClassifier() model.fit(X, y) # 对测试数据进行编码 test_data_encoded[columns_to_encode] = test_data_encoded[columns_to_encode].apply(lambda col: label_encoder.transform(col)) # 使用模型进行预测 predicted_labels = model.predict(test_data_encoded) # 输出预测结果 print(\"Predicted Labels:\", predicted_labels) 独热编码\r独热编码每个类别变成一列，用 0 或 1 表示是否存在，将类别转换为二进制向量。 # get_dummies pd.get_dummies(data['线路名称'].unique()) # OneHotEncoder from sklearn.preprocessing import OneHotEncoder encoder = OneHotEncoder() data['线路名称'] = encoder.fit_transform(data['线路名称']) ","date":"2025-05-15","objectID":"/posts/%E6%9C%BA%E5%99%A8%E5%AD%A6%E4%B9%A0%E5%9F%BA%E7%A1%80~06.%E6%95%B0%E6%8D%AE%E5%8F%98%E6%8D%A2/:3:5","tags":["机器学习"],"title":"机器学习基础~06.数据变换","uri":"/posts/%E6%9C%BA%E5%99%A8%E5%AD%A6%E4%B9%A0%E5%9F%BA%E7%A1%80~06.%E6%95%B0%E6%8D%AE%E5%8F%98%E6%8D%A2/#编码-离散转换为连续"},{"categories":"机器学习","collections":["机器学习基础"],"content":"15.5 编码-离散转换为连续\r编码（Encoding） 是将非数值型数据（如文字、类别、符号）转换为数值型数据的过程，以便计算机能够理解和处理。在机器学习和数据分析中，大多数算法（如回归、神经网络）只能处理数字，因此需要对文本、类别等数据进行编码转换。 数值编码 (Label Encoding)\r数值编码给每个类别分配一个唯一的数字，如下： from sklearn.preprocessing import LabelEncoder # 创建一个 LabelEncoder 对象 label_encoder = LabelEncoder() # 指定需要编码的列名列表 columns_to_encode = ['Color', 'Size', 'Shape'] # 对指定列进行编码 df_encoded = df.copy() df_encoded[columns_to_encode] = df_encoded[columns_to_encode].apply(lambda col: label_encoder.fit_transform(col)) # 训练模型（以随机森林分类器为例） X = df_encoded[columns_to_encode] y = df_encoded['Label'] model = RandomForestClassifier() model.fit(X, y) # 对测试数据进行编码 test_data_encoded[columns_to_encode] = test_data_encoded[columns_to_encode].apply(lambda col: label_encoder.transform(col)) # 使用模型进行预测 predicted_labels = model.predict(test_data_encoded) # 输出预测结果 print(\"Predicted Labels:\", predicted_labels) 独热编码\r独热编码每个类别变成一列，用 0 或 1 表示是否存在，将类别转换为二进制向量。 # get_dummies pd.get_dummies(data['线路名称'].unique()) # OneHotEncoder from sklearn.preprocessing import OneHotEncoder encoder = OneHotEncoder() data['线路名称'] = encoder.fit_transform(data['线路名称']) ","date":"2025-05-15","objectID":"/posts/%E6%9C%BA%E5%99%A8%E5%AD%A6%E4%B9%A0%E5%9F%BA%E7%A1%80~06.%E6%95%B0%E6%8D%AE%E5%8F%98%E6%8D%A2/:3:5","tags":["机器学习"],"title":"机器学习基础~06.数据变换","uri":"/posts/%E6%9C%BA%E5%99%A8%E5%AD%A6%E4%B9%A0%E5%9F%BA%E7%A1%80~06.%E6%95%B0%E6%8D%AE%E5%8F%98%E6%8D%A2/#数值编码-label-encoding"},{"categories":"机器学习","collections":["机器学习基础"],"content":"15.5 编码-离散转换为连续\r编码（Encoding） 是将非数值型数据（如文字、类别、符号）转换为数值型数据的过程，以便计算机能够理解和处理。在机器学习和数据分析中，大多数算法（如回归、神经网络）只能处理数字，因此需要对文本、类别等数据进行编码转换。 数值编码 (Label Encoding)\r数值编码给每个类别分配一个唯一的数字，如下： from sklearn.preprocessing import LabelEncoder # 创建一个 LabelEncoder 对象 label_encoder = LabelEncoder() # 指定需要编码的列名列表 columns_to_encode = ['Color', 'Size', 'Shape'] # 对指定列进行编码 df_encoded = df.copy() df_encoded[columns_to_encode] = df_encoded[columns_to_encode].apply(lambda col: label_encoder.fit_transform(col)) # 训练模型（以随机森林分类器为例） X = df_encoded[columns_to_encode] y = df_encoded['Label'] model = RandomForestClassifier() model.fit(X, y) # 对测试数据进行编码 test_data_encoded[columns_to_encode] = test_data_encoded[columns_to_encode].apply(lambda col: label_encoder.transform(col)) # 使用模型进行预测 predicted_labels = model.predict(test_data_encoded) # 输出预测结果 print(\"Predicted Labels:\", predicted_labels) 独热编码\r独热编码每个类别变成一列，用 0 或 1 表示是否存在，将类别转换为二进制向量。 # get_dummies pd.get_dummies(data['线路名称'].unique()) # OneHotEncoder from sklearn.preprocessing import OneHotEncoder encoder = OneHotEncoder() data['线路名称'] = encoder.fit_transform(data['线路名称']) ","date":"2025-05-15","objectID":"/posts/%E6%9C%BA%E5%99%A8%E5%AD%A6%E4%B9%A0%E5%9F%BA%E7%A1%80~06.%E6%95%B0%E6%8D%AE%E5%8F%98%E6%8D%A2/:3:5","tags":["机器学习"],"title":"机器学习基础~06.数据变换","uri":"/posts/%E6%9C%BA%E5%99%A8%E5%AD%A6%E4%B9%A0%E5%9F%BA%E7%A1%80~06.%E6%95%B0%E6%8D%AE%E5%8F%98%E6%8D%A2/#独热编码"},{"categories":"机器学习","collections":["机器学习基础"],"content":"第 16 节 属性构造\r通过已有属性通过一定的计算生成新属性，增强数据表达能力。 ","date":"2025-05-15","objectID":"/posts/%E6%9C%BA%E5%99%A8%E5%AD%A6%E4%B9%A0%E5%9F%BA%E7%A1%80~06.%E6%95%B0%E6%8D%AE%E5%8F%98%E6%8D%A2/:4:0","tags":["机器学习"],"title":"机器学习基础~06.数据变换","uri":"/posts/%E6%9C%BA%E5%99%A8%E5%AD%A6%E4%B9%A0%E5%9F%BA%E7%A1%80~06.%E6%95%B0%E6%8D%AE%E5%8F%98%E6%8D%A2/#属性构造"},{"categories":"机器学习","collections":["机器学习基础"],"content":"16.1 时间处理\r从数据中提取出部分时间信息，比如季节信息或者小时信息。 import pandas as pd from datetime import datetime # string 类型的属性新增小时 df['hour'] = df['Time'].apply(lambda x: datetime.strptime(x, '%H:%M:%S').hour) # 或者 df['hour'] = df['Time'].str.split(':',expand=True)[0] # datetime 类型的属性新增季节 df['季度']=df['日期'].apply(lambda x: x.quarter) # 或者 df['季度']=df['日期'].dt.quarter ","date":"2025-05-15","objectID":"/posts/%E6%9C%BA%E5%99%A8%E5%AD%A6%E4%B9%A0%E5%9F%BA%E7%A1%80~06.%E6%95%B0%E6%8D%AE%E5%8F%98%E6%8D%A2/:4:1","tags":["机器学习"],"title":"机器学习基础~06.数据变换","uri":"/posts/%E6%9C%BA%E5%99%A8%E5%AD%A6%E4%B9%A0%E5%9F%BA%E7%A1%80~06.%E6%95%B0%E6%8D%AE%E5%8F%98%E6%8D%A2/#时间处理"},{"categories":"机器学习","collections":["机器学习基础"],"content":"16.2 分组 Groupby\r分组函数：将数据按照某个或某些字段进行分组，后续衔接的函数将以组为单位在组内进行运算。 #导入包 import pandas as pd import numpy as np #加载数据 data = pd.read_csv( 'data.csv') #按用户编号分组，计算最大缴费金额 result = data.groupby( '用户编号')[['缴费金额']].max() #按用户编号分组，计算最小缴费金额 result = data.groupby( '用户编号')[['缴费金额']].min() #按用户编号分组，计算平均缴费金额 result = data.groupby( '用户编号')[['缴费金额']].mean() ","date":"2025-05-15","objectID":"/posts/%E6%9C%BA%E5%99%A8%E5%AD%A6%E4%B9%A0%E5%9F%BA%E7%A1%80~06.%E6%95%B0%E6%8D%AE%E5%8F%98%E6%8D%A2/:4:2","tags":["机器学习"],"title":"机器学习基础~06.数据变换","uri":"/posts/%E6%9C%BA%E5%99%A8%E5%AD%A6%E4%B9%A0%E5%9F%BA%E7%A1%80~06.%E6%95%B0%E6%8D%AE%E5%8F%98%E6%8D%A2/#分组-groupby"},{"categories":"机器学习","collections":["机器学习基础"],"content":"16.3 聚合 Agg\r聚合函数：将数据按照某个或某些字段进行分组，在组内分别计算某些统计量，如下： 'mean': 列的均值。 'median': 列的中位数。 'var': 列的方差。 'std': 列的标准差。 'skew': 列的偏度（Skewness）。 'kurt': 列的峰度（Kurtosis）。 'sum': 列的总和。 'min': 列的最小值。 'max': 列的最大值。 'count': 列的非缺失值数量。 'mad': 列的绝对平均偏差（Mean Absolute Deviation）。 'sem': 列的标准误差（Standard Error of the Mean）。 'prod': 列的积。 'cumsum': 列的累积和。 'cumprod': 列的累积积。 df= pd.read_csv( 'data.csv ') result = df.groupby('户号')['电量'].agg(['sum','mean','max','min']) result.columns=['总电量','平均电量','最大电量','最小电量'] result.index.names=['用户'] ","date":"2025-05-15","objectID":"/posts/%E6%9C%BA%E5%99%A8%E5%AD%A6%E4%B9%A0%E5%9F%BA%E7%A1%80~06.%E6%95%B0%E6%8D%AE%E5%8F%98%E6%8D%A2/:4:3","tags":["机器学习"],"title":"机器学习基础~06.数据变换","uri":"/posts/%E6%9C%BA%E5%99%A8%E5%AD%A6%E4%B9%A0%E5%9F%BA%E7%A1%80~06.%E6%95%B0%E6%8D%AE%E5%8F%98%E6%8D%A2/#聚合-agg"},{"categories":"机器学习","collections":["机器学习基础"],"content":"16.4 重塑和透视\r数据重塑 (reshaping) ：是指改变数据的行和列的排列方式。 数据透视 (pivoting) ：是指通过旋转数据的行和列，以重新排列数据，并根据指定的聚合函数来生成新的数据。 长格式：类似流水账，每一行代表一个观察值，比如某个学生某科目期中考试成绩。 宽格式：更像是“矩阵”，每一行代表一个特定观察条件，比如某个特定学生的学号。此外，宽格式数据的列用于表示不同的特征或维度，比如特定科目。 Stack\rstack() 函数用于将数据从宽格式转换为长格式。 df.stack(level='Subject').reset_index().rename(columns={0: 'Final'}) Unstack\runstack() 函数用于将数据从长格式转换为宽格式。 df.unstack('Class') Melt\rmelt() 将原始数据中的多列合并为一列，并根据其他列的值对新列进行重复。可以理解为stack()的一种泛化形式。 指定id_vars参数，表示保持不变的列 指定value_vars参数，表示需要被转换的列 melted_df = df.melt(id_vars='Student ID',var_name='Subject',value_vars=['Art','Math','Science'],value_name='Score') Pivot\rpivot()可以理解为一种长格式转换为宽格式的特殊情况。 指定index，表示新 DataFrame 的行索引 指定columns，表示新 DataFrame 的列索引 指定values，表示新 DataFrame 的填充数据的值 df.pivot(index='Student ID',columns='Subject',values='Midterm') 我们可以用pivot_table()完成一样的操作，和pivot()不同的是，pivot_table()可以不用指定columns（也可以指定，此时同pivot()）。 df.pivot_table(index=['Subject', 'Student ID'],values=['Midterm','Final']) ","date":"2025-05-15","objectID":"/posts/%E6%9C%BA%E5%99%A8%E5%AD%A6%E4%B9%A0%E5%9F%BA%E7%A1%80~06.%E6%95%B0%E6%8D%AE%E5%8F%98%E6%8D%A2/:4:4","tags":["机器学习"],"title":"机器学习基础~06.数据变换","uri":"/posts/%E6%9C%BA%E5%99%A8%E5%AD%A6%E4%B9%A0%E5%9F%BA%E7%A1%80~06.%E6%95%B0%E6%8D%AE%E5%8F%98%E6%8D%A2/#重塑和透视"},{"categories":"机器学习","collections":["机器学习基础"],"content":"16.4 重塑和透视\r数据重塑 (reshaping) ：是指改变数据的行和列的排列方式。 数据透视 (pivoting) ：是指通过旋转数据的行和列，以重新排列数据，并根据指定的聚合函数来生成新的数据。 长格式：类似流水账，每一行代表一个观察值，比如某个学生某科目期中考试成绩。 宽格式：更像是“矩阵”，每一行代表一个特定观察条件，比如某个特定学生的学号。此外，宽格式数据的列用于表示不同的特征或维度，比如特定科目。 Stack\rstack() 函数用于将数据从宽格式转换为长格式。 df.stack(level='Subject').reset_index().rename(columns={0: 'Final'}) Unstack\runstack() 函数用于将数据从长格式转换为宽格式。 df.unstack('Class') Melt\rmelt() 将原始数据中的多列合并为一列，并根据其他列的值对新列进行重复。可以理解为stack()的一种泛化形式。 指定id_vars参数，表示保持不变的列 指定value_vars参数，表示需要被转换的列 melted_df = df.melt(id_vars='Student ID',var_name='Subject',value_vars=['Art','Math','Science'],value_name='Score') Pivot\rpivot()可以理解为一种长格式转换为宽格式的特殊情况。 指定index，表示新 DataFrame 的行索引 指定columns，表示新 DataFrame 的列索引 指定values，表示新 DataFrame 的填充数据的值 df.pivot(index='Student ID',columns='Subject',values='Midterm') 我们可以用pivot_table()完成一样的操作，和pivot()不同的是，pivot_table()可以不用指定columns（也可以指定，此时同pivot()）。 df.pivot_table(index=['Subject', 'Student ID'],values=['Midterm','Final']) ","date":"2025-05-15","objectID":"/posts/%E6%9C%BA%E5%99%A8%E5%AD%A6%E4%B9%A0%E5%9F%BA%E7%A1%80~06.%E6%95%B0%E6%8D%AE%E5%8F%98%E6%8D%A2/:4:4","tags":["机器学习"],"title":"机器学习基础~06.数据变换","uri":"/posts/%E6%9C%BA%E5%99%A8%E5%AD%A6%E4%B9%A0%E5%9F%BA%E7%A1%80~06.%E6%95%B0%E6%8D%AE%E5%8F%98%E6%8D%A2/#stack"},{"categories":"机器学习","collections":["机器学习基础"],"content":"16.4 重塑和透视\r数据重塑 (reshaping) ：是指改变数据的行和列的排列方式。 数据透视 (pivoting) ：是指通过旋转数据的行和列，以重新排列数据，并根据指定的聚合函数来生成新的数据。 长格式：类似流水账，每一行代表一个观察值，比如某个学生某科目期中考试成绩。 宽格式：更像是“矩阵”，每一行代表一个特定观察条件，比如某个特定学生的学号。此外，宽格式数据的列用于表示不同的特征或维度，比如特定科目。 Stack\rstack() 函数用于将数据从宽格式转换为长格式。 df.stack(level='Subject').reset_index().rename(columns={0: 'Final'}) Unstack\runstack() 函数用于将数据从长格式转换为宽格式。 df.unstack('Class') Melt\rmelt() 将原始数据中的多列合并为一列，并根据其他列的值对新列进行重复。可以理解为stack()的一种泛化形式。 指定id_vars参数，表示保持不变的列 指定value_vars参数，表示需要被转换的列 melted_df = df.melt(id_vars='Student ID',var_name='Subject',value_vars=['Art','Math','Science'],value_name='Score') Pivot\rpivot()可以理解为一种长格式转换为宽格式的特殊情况。 指定index，表示新 DataFrame 的行索引 指定columns，表示新 DataFrame 的列索引 指定values，表示新 DataFrame 的填充数据的值 df.pivot(index='Student ID',columns='Subject',values='Midterm') 我们可以用pivot_table()完成一样的操作，和pivot()不同的是，pivot_table()可以不用指定columns（也可以指定，此时同pivot()）。 df.pivot_table(index=['Subject', 'Student ID'],values=['Midterm','Final']) ","date":"2025-05-15","objectID":"/posts/%E6%9C%BA%E5%99%A8%E5%AD%A6%E4%B9%A0%E5%9F%BA%E7%A1%80~06.%E6%95%B0%E6%8D%AE%E5%8F%98%E6%8D%A2/:4:4","tags":["机器学习"],"title":"机器学习基础~06.数据变换","uri":"/posts/%E6%9C%BA%E5%99%A8%E5%AD%A6%E4%B9%A0%E5%9F%BA%E7%A1%80~06.%E6%95%B0%E6%8D%AE%E5%8F%98%E6%8D%A2/#unstack"},{"categories":"机器学习","collections":["机器学习基础"],"content":"16.4 重塑和透视\r数据重塑 (reshaping) ：是指改变数据的行和列的排列方式。 数据透视 (pivoting) ：是指通过旋转数据的行和列，以重新排列数据，并根据指定的聚合函数来生成新的数据。 长格式：类似流水账，每一行代表一个观察值，比如某个学生某科目期中考试成绩。 宽格式：更像是“矩阵”，每一行代表一个特定观察条件，比如某个特定学生的学号。此外，宽格式数据的列用于表示不同的特征或维度，比如特定科目。 Stack\rstack() 函数用于将数据从宽格式转换为长格式。 df.stack(level='Subject').reset_index().rename(columns={0: 'Final'}) Unstack\runstack() 函数用于将数据从长格式转换为宽格式。 df.unstack('Class') Melt\rmelt() 将原始数据中的多列合并为一列，并根据其他列的值对新列进行重复。可以理解为stack()的一种泛化形式。 指定id_vars参数，表示保持不变的列 指定value_vars参数，表示需要被转换的列 melted_df = df.melt(id_vars='Student ID',var_name='Subject',value_vars=['Art','Math','Science'],value_name='Score') Pivot\rpivot()可以理解为一种长格式转换为宽格式的特殊情况。 指定index，表示新 DataFrame 的行索引 指定columns，表示新 DataFrame 的列索引 指定values，表示新 DataFrame 的填充数据的值 df.pivot(index='Student ID',columns='Subject',values='Midterm') 我们可以用pivot_table()完成一样的操作，和pivot()不同的是，pivot_table()可以不用指定columns（也可以指定，此时同pivot()）。 df.pivot_table(index=['Subject', 'Student ID'],values=['Midterm','Final']) ","date":"2025-05-15","objectID":"/posts/%E6%9C%BA%E5%99%A8%E5%AD%A6%E4%B9%A0%E5%9F%BA%E7%A1%80~06.%E6%95%B0%E6%8D%AE%E5%8F%98%E6%8D%A2/:4:4","tags":["机器学习"],"title":"机器学习基础~06.数据变换","uri":"/posts/%E6%9C%BA%E5%99%A8%E5%AD%A6%E4%B9%A0%E5%9F%BA%E7%A1%80~06.%E6%95%B0%E6%8D%AE%E5%8F%98%E6%8D%A2/#melt"},{"categories":"机器学习","collections":["机器学习基础"],"content":"16.4 重塑和透视\r数据重塑 (reshaping) ：是指改变数据的行和列的排列方式。 数据透视 (pivoting) ：是指通过旋转数据的行和列，以重新排列数据，并根据指定的聚合函数来生成新的数据。 长格式：类似流水账，每一行代表一个观察值，比如某个学生某科目期中考试成绩。 宽格式：更像是“矩阵”，每一行代表一个特定观察条件，比如某个特定学生的学号。此外，宽格式数据的列用于表示不同的特征或维度，比如特定科目。 Stack\rstack() 函数用于将数据从宽格式转换为长格式。 df.stack(level='Subject').reset_index().rename(columns={0: 'Final'}) Unstack\runstack() 函数用于将数据从长格式转换为宽格式。 df.unstack('Class') Melt\rmelt() 将原始数据中的多列合并为一列，并根据其他列的值对新列进行重复。可以理解为stack()的一种泛化形式。 指定id_vars参数，表示保持不变的列 指定value_vars参数，表示需要被转换的列 melted_df = df.melt(id_vars='Student ID',var_name='Subject',value_vars=['Art','Math','Science'],value_name='Score') Pivot\rpivot()可以理解为一种长格式转换为宽格式的特殊情况。 指定index，表示新 DataFrame 的行索引 指定columns，表示新 DataFrame 的列索引 指定values，表示新 DataFrame 的填充数据的值 df.pivot(index='Student ID',columns='Subject',values='Midterm') 我们可以用pivot_table()完成一样的操作，和pivot()不同的是，pivot_table()可以不用指定columns（也可以指定，此时同pivot()）。 df.pivot_table(index=['Subject', 'Student ID'],values=['Midterm','Final']) ","date":"2025-05-15","objectID":"/posts/%E6%9C%BA%E5%99%A8%E5%AD%A6%E4%B9%A0%E5%9F%BA%E7%A1%80~06.%E6%95%B0%E6%8D%AE%E5%8F%98%E6%8D%A2/:4:4","tags":["机器学习"],"title":"机器学习基础~06.数据变换","uri":"/posts/%E6%9C%BA%E5%99%A8%E5%AD%A6%E4%B9%A0%E5%9F%BA%E7%A1%80~06.%E6%95%B0%E6%8D%AE%E5%8F%98%E6%8D%A2/#pivot"},{"categories":"机器学习","collections":["机器学习基础"],"content":"第 7 节 数据合并\r数据合并将行或者列相同的数据进行合并，通过axis参数指定根据行还是列进行合并。 axis=0，数据按照列进行堆放，要求多组数据的列数相同。 axis=1，数据按照行进行堆放，要求多组数据的行数相同。 # 使用 concat() 函数沿着行方向合并数据 concatenated_data = pd.concat([data1, data2], ignore_index=True) # 打印合并后的数据 print(concatenated_data) ","date":"2025-05-15","objectID":"/posts/%E6%9C%BA%E5%99%A8%E5%AD%A6%E4%B9%A0%E5%9F%BA%E7%A1%80~05.%E6%95%B0%E6%8D%AE%E9%9B%86%E6%88%90/:1:0","tags":["机器学习"],"title":"机器学习基础~05.数据集成","uri":"/posts/%E6%9C%BA%E5%99%A8%E5%AD%A6%E4%B9%A0%E5%9F%BA%E7%A1%80~05.%E6%95%B0%E6%8D%AE%E9%9B%86%E6%88%90/#数据合并"},{"categories":"机器学习","collections":["机器学习基础"],"content":"第 8 节 数据连接\r数据连接取相同的key进行合并，通过参数how指定当两者的key不匹配时的处理方式。 import pandas as pd df1=pd.DataFrame({'key':[ 'a','e','b','a','c','a','b'], 'data1':range(7)}) df2=pd.DataFrame({'key':['a','b','c','f'] ,'data2':range(4)}) #根据key合并df1、dF2，取交集 result1 = pd.merge(df1,df2,on='key',how=\"inner\") #根据key合并df1、df2，取并集 result2 = pd.merge(df1,df2,on='key',how=\"outer\") #根据key合并df1、df2，拼接df2对象 result3 = pd .merge(df2,df1,on='key',how=\"left\") ","date":"2025-05-15","objectID":"/posts/%E6%9C%BA%E5%99%A8%E5%AD%A6%E4%B9%A0%E5%9F%BA%E7%A1%80~05.%E6%95%B0%E6%8D%AE%E9%9B%86%E6%88%90/:2:0","tags":["机器学习"],"title":"机器学习基础~05.数据集成","uri":"/posts/%E6%9C%BA%E5%99%A8%E5%AD%A6%E4%B9%A0%E5%9F%BA%E7%A1%80~05.%E6%95%B0%E6%8D%AE%E9%9B%86%E6%88%90/#数据连接"},{"categories":"机器学习","collections":["机器学习基础"],"content":"第 10 节 缺失值\r摘要\r缺失值的处理分为判断缺失值、展示缺失值、填补（删除）缺失值几步。 ","date":"2025-05-15","objectID":"/posts/%E6%9C%BA%E5%99%A8%E5%AD%A6%E4%B9%A0%E5%9F%BA%E7%A1%80~04.%E6%95%B0%E6%8D%AE%E6%B8%85%E6%B4%97/:1:0","tags":["机器学习"],"title":"机器学习基础~04.数据清洗","uri":"/posts/%E6%9C%BA%E5%99%A8%E5%AD%A6%E4%B9%A0%E5%9F%BA%E7%A1%80~04.%E6%95%B0%E6%8D%AE%E6%B8%85%E6%B4%97/#缺失值"},{"categories":"机器学习","collections":["机器学习基础"],"content":"10.1 判断缺失值\r判断数据中是否存在缺失值，以及缺失值的个数，方法如下： df = pd.read_csv('data.csv',encoding='utf-8-sig',index_col=0) #判断每列是否有缺失值 df.isnull().any() #缺失值个数 df.isnull().sum() ","date":"2025-05-15","objectID":"/posts/%E6%9C%BA%E5%99%A8%E5%AD%A6%E4%B9%A0%E5%9F%BA%E7%A1%80~04.%E6%95%B0%E6%8D%AE%E6%B8%85%E6%B4%97/:1:1","tags":["机器学习"],"title":"机器学习基础~04.数据清洗","uri":"/posts/%E6%9C%BA%E5%99%A8%E5%AD%A6%E4%B9%A0%E5%9F%BA%E7%A1%80~04.%E6%95%B0%E6%8D%AE%E6%B8%85%E6%B4%97/#判断缺失值"},{"categories":"机器学习","collections":["机器学习基础"],"content":"10.2 展示缺失值\r使用热力图展示数据中的缺失值，方法如下： import pandas as pd import seaborn as sns import matplotlib.pyplot as plt plt.figure(figsize=(8, 6)) sns.heatmap(df.isnull(), cmap='viridis', cbar=False) plt.title('Missing Values Heatmap') plt.show() ","date":"2025-05-15","objectID":"/posts/%E6%9C%BA%E5%99%A8%E5%AD%A6%E4%B9%A0%E5%9F%BA%E7%A1%80~04.%E6%95%B0%E6%8D%AE%E6%B8%85%E6%B4%97/:1:2","tags":["机器学习"],"title":"机器学习基础~04.数据清洗","uri":"/posts/%E6%9C%BA%E5%99%A8%E5%AD%A6%E4%B9%A0%E5%9F%BA%E7%A1%80~04.%E6%95%B0%E6%8D%AE%E6%B8%85%E6%B4%97/#展示缺失值"},{"categories":"机器学习","collections":["机器学习基础"],"content":"10.3 删除缺失值\r如果缺失值较少，可以采用删除缺失值的方法处理，方法如下： df2 = df.copy() #删除df2中缺失值所在行 df2.dropna(axis = 0,inplace = True) ","date":"2025-05-15","objectID":"/posts/%E6%9C%BA%E5%99%A8%E5%AD%A6%E4%B9%A0%E5%9F%BA%E7%A1%80~04.%E6%95%B0%E6%8D%AE%E6%B8%85%E6%B4%97/:1:3","tags":["机器学习"],"title":"机器学习基础~04.数据清洗","uri":"/posts/%E6%9C%BA%E5%99%A8%E5%AD%A6%E4%B9%A0%E5%9F%BA%E7%A1%80~04.%E6%95%B0%E6%8D%AE%E6%B8%85%E6%B4%97/#删除缺失值"},{"categories":"机器学习","collections":["机器学习基础"],"content":"10.4 填补缺失值\r缺失值较多时，可以考虑填补缺失值的方法处理，包括： 插值法：通过插值方法填补缺失值，如均值插值、中位数插值、最近邻插值、多项式插值等。 模型法：使用回归、决策树或神经网络等模型预测缺失值，但需要先对数据进行训练和测试，可能会导致模型的过拟合和不准确。 多重填补法：使用多个模型进行填补，可以提高填补缺失值的准确性和可靠性。 # 均值插值 df2.apply(lambda x:x.fillna(x.mean(),inplace=True)) # 中位数插值 df2.apply(lambda x:x.fillna(x.median(),inplace=True)) # 均值插值 from sklearn.impute import SimpleImputer imputer = SimpleImputer(strategy='mean') X_imputed = imputer.fit_transform(X) # 回归法 from sklearn.impute import IterativeImputer imputer = IterativeImputer() X_imputed = imputer.fit_transform(X) # 最近邻插值 from sklearn.impute import KNNImputer imputer = KNNImputer() X_imputed = imputer.fit_transform(X) ","date":"2025-05-15","objectID":"/posts/%E6%9C%BA%E5%99%A8%E5%AD%A6%E4%B9%A0%E5%9F%BA%E7%A1%80~04.%E6%95%B0%E6%8D%AE%E6%B8%85%E6%B4%97/:1:4","tags":["机器学习"],"title":"机器学习基础~04.数据清洗","uri":"/posts/%E6%9C%BA%E5%99%A8%E5%AD%A6%E4%B9%A0%E5%9F%BA%E7%A1%80~04.%E6%95%B0%E6%8D%AE%E6%B8%85%E6%B4%97/#填补缺失值"},{"categories":"机器学习","collections":["机器学习基础"],"content":"第 11 节 异常值\r常见的异常值检测方法包括基于统计学的方法、基于距离的方法、基于密度的方法和基于模型的方法。 处理异常值的方法包括删除、替换、调整或利用异常值建立新的模型等。 函数 介绍 sklearn.ensemble.IsolationForest() 使用隔离森林方法检测异常值。 sklearn.svm.OneClassSVM() 使用支持向量机方法进行单类异常值检测。 sklearn.covariance.EllipticEnvelope() 使用基于高斯分布的椭圆包络方法检测异常值。 sklearn.neighbors.LocalOutlierFactor() 使用局部离群因子方法检测异常值。 sklearn.covariance.RobustCovariance() 使用鲁棒协方差估计进行异常值检测。 sklearn.covariance.mahalanobis() 计算马哈拉诺比斯距离来检测异常值。 ","date":"2025-05-15","objectID":"/posts/%E6%9C%BA%E5%99%A8%E5%AD%A6%E4%B9%A0%E5%9F%BA%E7%A1%80~04.%E6%95%B0%E6%8D%AE%E6%B8%85%E6%B4%97/:2:0","tags":["机器学习"],"title":"机器学习基础~04.数据清洗","uri":"/posts/%E6%9C%BA%E5%99%A8%E5%AD%A6%E4%B9%A0%E5%9F%BA%E7%A1%80~04.%E6%95%B0%E6%8D%AE%E6%B8%85%E6%B4%97/#异常值"},{"categories":"机器学习","collections":["机器学习基础"],"content":"11.1 绘制决策边界\r决策边界是判断一个值是否是异常值的边界条件，可以直观展示判断异常值的过程： import numpy as np import matplotlib.pyplot as plt from sklearn.svm import OneClassSVM # 创建一个示例数据集 np.random.seed(42) X = 0.3 * np.random.randn(100, 2) X_train = np.r_[X + 2, X - 2] # 创建 OneClassSVM 模型 clf = OneClassSVM(nu=0.1, kernel=\"rbf\", gamma=0.1) clf.fit(X_train) # 创建一个网格来绘制决策边界 xx, yy = np.meshgrid(np.linspace(-5, 5, 500), np.linspace(-5, 5, 500)) Z = clf.decision_function(np.c_[xx.ravel(), yy.ravel()]) Z = Z.reshape(xx.shape) # 绘制决策边界和支持向量 plt.figure(figsize=(10, 10)) plt.contourf(xx, yy, Z, levels=np.linspace(Z.min(), 0, 2), cmap=plt.cm.PuBu) plt.contour(xx, yy, Z, levels=[0], linewidths=2, colors='darkred') plt.scatter(X_train[:, 0], X_train[:, 1], s=10, color='black') plt.xlim((-5, 5)) plt.ylim((-5, 5)) plt.title(\"OneClassSVM - Decision Boundary\") plt.show() ","date":"2025-05-15","objectID":"/posts/%E6%9C%BA%E5%99%A8%E5%AD%A6%E4%B9%A0%E5%9F%BA%E7%A1%80~04.%E6%95%B0%E6%8D%AE%E6%B8%85%E6%B4%97/:2:1","tags":["机器学习"],"title":"机器学习基础~04.数据清洗","uri":"/posts/%E6%9C%BA%E5%99%A8%E5%AD%A6%E4%B9%A0%E5%9F%BA%E7%A1%80~04.%E6%95%B0%E6%8D%AE%E6%B8%85%E6%B4%97/#绘制决策边界"},{"categories":"机器学习","collections":["机器学习基础"],"content":"11.2 填充异常值\r将异常值填充为中位数的方法如下： # 3 sigma 异常值填充为中位数 def replace_outliers_with_median(column): # 计算均值和标准差 mean = column.mean() std = column.std() # 计算下限和上限 lower_limit = mean - 3 * std upper_limit = mean + 3 * std # 替换离群点为中位数 return column.apply(lambda x: x if (lower_limit \u003c= x \u003c= upper_limit) else column.median()) # 使用 apply 方法处理每一列 df_cleaned = df.apply(replace_outliers_with_median) ","date":"2025-05-15","objectID":"/posts/%E6%9C%BA%E5%99%A8%E5%AD%A6%E4%B9%A0%E5%9F%BA%E7%A1%80~04.%E6%95%B0%E6%8D%AE%E6%B8%85%E6%B4%97/:2:2","tags":["机器学习"],"title":"机器学习基础~04.数据清洗","uri":"/posts/%E6%9C%BA%E5%99%A8%E5%AD%A6%E4%B9%A0%E5%9F%BA%E7%A1%80~04.%E6%95%B0%E6%8D%AE%E6%B8%85%E6%B4%97/#填充异常值"},{"categories":"机器学习","collections":["机器学习基础"],"content":"第 12 节 重复值\r展示并删除重复值的方法如下： import pandas as pd data = pd.read_csv('example.csv') # 展示重复数据 mask = data.duplicated(subset=['ID','Salary','Age']) data[mask] # 删除重复数据 new_data = data.drop_duplicates(subset=['ID','Salary','Age'],keep = 'first') new_data ","date":"2025-05-15","objectID":"/posts/%E6%9C%BA%E5%99%A8%E5%AD%A6%E4%B9%A0%E5%9F%BA%E7%A1%80~04.%E6%95%B0%E6%8D%AE%E6%B8%85%E6%B4%97/:3:0","tags":["机器学习"],"title":"机器学习基础~04.数据清洗","uri":"/posts/%E6%9C%BA%E5%99%A8%E5%AD%A6%E4%B9%A0%E5%9F%BA%E7%A1%80~04.%E6%95%B0%E6%8D%AE%E6%B8%85%E6%B4%97/#重复值"},{"categories":"机器学习","collections":["机器学习基础"],"content":"第 4 节 数据准备\r摘要\r数据准备分为特征提取、类型转换、数据清洗、数据集成、数据变换、数据规约几个步骤。 数据准备的过程可以细分为以下步骤： 特征提取：从原始数据中提取有用的特征或属性，以便机器学习模型可以理解和使用。 在文本数据中，特征可以是词频、TF-IDF等 在图像数据中，特征可以是像素值、颜色直方图等 类型转换：将数据的类型进行转换，以便其适合模型的输入要求。 源数据类型 目标数据类型 方法 数值型 类别型 离散化 类别型 数值型 二元化 文本 数值型 潜在语义分析（LSA） 时序 离散序列 SAX 时序 多维数值型 DWT、DFT 离散序列 多维数值型 DWT、DFT 空间 多维数值型 二维DWT 图 多维数值型 MDS、图谱 任何类型 图 相似图（可用性较有限） 数据清洗：去除或纠正数据中的错误、缺失、重复或异常值的过程。这可以提高模型的性能和稳定性。清洗数据可以包括填充缺失值、删除重复值、处理异常值等 数据集成：从多个数据源或表中合并数据，以便进行分析和建模。这可能涉及到数据连接、合并和转换，确保数据在合并后的格式中保持一致。 数据变换：将数据进行标准化、归一化或其他变换，以便让不同特征具有相似的尺度，提高模型的性能和收敛速度。 数据规约：通过降维技术来减少数据的维度，以减少存储和计算成本，并防止维度灾难。常见的方法包括主成分分析（PCA）和线性判别分析（LDA）等。 在后续篇章中，本文将详细讲述以上数据准备的步骤。 信息\r为了统一数据挖掘和机器学习的知识体系，后续篇章中将对相似的内容进行合并，类型转换的部分将放至数据变换中讲述，特征提取的部分将放至数据规约中讲述。 ","date":"2025-05-15","objectID":"/posts/%E6%9C%BA%E5%99%A8%E5%AD%A6%E4%B9%A0%E5%9F%BA%E7%A1%80~03.%E6%95%B0%E6%8D%AE%E5%87%86%E5%A4%87/:1:0","tags":["机器学习"],"title":"机器学习基础~03.数据准备","uri":"/posts/%E6%9C%BA%E5%99%A8%E5%AD%A6%E4%B9%A0%E5%9F%BA%E7%A1%80~03.%E6%95%B0%E6%8D%AE%E5%87%86%E5%A4%87/#数据准备"},{"categories":"机器学习","collections":["机器学习基础"],"content":"\r摘要\r要进行机器学习，首先要获取数据，本文将讲述从各种数据源中获取数据的过程。 ","date":"2025-05-15","objectID":"/posts/%E6%9C%BA%E5%99%A8%E5%AD%A6%E4%B9%A0%E5%9F%BA%E7%A1%80~02.%E6%95%B0%E6%8D%AE%E8%AF%BB%E5%8F%96/:0:0","tags":["机器学习"],"title":"机器学习基础~02.数据读取","uri":"/posts/%E6%9C%BA%E5%99%A8%E5%AD%A6%E4%B9%A0%E5%9F%BA%E7%A1%80~02.%E6%95%B0%E6%8D%AE%E8%AF%BB%E5%8F%96/#"},{"categories":"机器学习","collections":["机器学习基础"],"content":"第 13 节 读取txt文本数据\r问题\r二维数据保存在txt文本文件中，每一行为一条数据，数据的字段以分隔符/$/分隔，应该如何读取？ ","date":"2025-05-15","objectID":"/posts/%E6%9C%BA%E5%99%A8%E5%AD%A6%E4%B9%A0%E5%9F%BA%E7%A1%80~02.%E6%95%B0%E6%8D%AE%E8%AF%BB%E5%8F%96/:1:0","tags":["机器学习"],"title":"机器学习基础~02.数据读取","uri":"/posts/%E6%9C%BA%E5%99%A8%E5%AD%A6%E4%B9%A0%E5%9F%BA%E7%A1%80~02.%E6%95%B0%E6%8D%AE%E8%AF%BB%E5%8F%96/#读取txt文本数据"},{"categories":"机器学习","collections":["机器学习基础"],"content":"13.1 自定义函数读取\r可以自定义函数读取txt文件，方法如下： def read_txt(path,separate): with open(path, 'rt', encoding = 'UTF-8-sig') as f: lines = f.readlines() return [line.replace('\\n','').split(separate) for line in lines] df = read_txt('data.txt','/$/') ","date":"2025-05-15","objectID":"/posts/%E6%9C%BA%E5%99%A8%E5%AD%A6%E4%B9%A0%E5%9F%BA%E7%A1%80~02.%E6%95%B0%E6%8D%AE%E8%AF%BB%E5%8F%96/:1:1","tags":["机器学习"],"title":"机器学习基础~02.数据读取","uri":"/posts/%E6%9C%BA%E5%99%A8%E5%AD%A6%E4%B9%A0%E5%9F%BA%E7%A1%80~02.%E6%95%B0%E6%8D%AE%E8%AF%BB%E5%8F%96/#自定义函数读取"},{"categories":"机器学习","collections":["机器学习基础"],"content":"13.2 使用 read_table 读取数据\r可以使用方法read_table进行读取，该方法在旧版本的pandas中被使用，新版本中被 read_csv 替代， 参数sep长度超过1个字符将被解释为正则表达式，也会强制使用Python解析引擎。 import pandas as pd df = pd.read_table('data.txt', sep='/\\$/', names = ['id', 'f1', 'f2', 'f3', 'f4'],engine='python') ","date":"2025-05-15","objectID":"/posts/%E6%9C%BA%E5%99%A8%E5%AD%A6%E4%B9%A0%E5%9F%BA%E7%A1%80~02.%E6%95%B0%E6%8D%AE%E8%AF%BB%E5%8F%96/:1:2","tags":["机器学习"],"title":"机器学习基础~02.数据读取","uri":"/posts/%E6%9C%BA%E5%99%A8%E5%AD%A6%E4%B9%A0%E5%9F%BA%E7%A1%80~02.%E6%95%B0%E6%8D%AE%E8%AF%BB%E5%8F%96/#使用"},{"categories":"机器学习","collections":["机器学习基础"],"content":"第 14 节 读取csv数据\r问题\r数据保存在csv文件中，应该如何读取？ ","date":"2025-05-15","objectID":"/posts/%E6%9C%BA%E5%99%A8%E5%AD%A6%E4%B9%A0%E5%9F%BA%E7%A1%80~02.%E6%95%B0%E6%8D%AE%E8%AF%BB%E5%8F%96/:2:0","tags":["机器学习"],"title":"机器学习基础~02.数据读取","uri":"/posts/%E6%9C%BA%E5%99%A8%E5%AD%A6%E4%B9%A0%E5%9F%BA%E7%A1%80~02.%E6%95%B0%E6%8D%AE%E8%AF%BB%E5%8F%96/#读取csv数据"},{"categories":"机器学习","collections":["机器学习基础"],"content":"14.1 使用 read_csv 读取数据\rread_csv是读取csv文件的常用方法，使用方法如下： # 利用read_csv函数，打开指定路径的文件 df1 = pd.read_csv('data.csv') # names 属性用来重定义列名 df2 = pd.read_csv('data.csv', names = ['id', 'f1', 'f2', 'f3', 'f4']) # index_col 属性用来指定哪一列作为索引列 df3 = pd.read_csv('data.csv', index_col = 'id') # skiprows 属性可以指定读取文件时需要跳过哪些行 df4 = pd.read_csv('data.csv', skiprows = [1,3]) ","date":"2025-05-15","objectID":"/posts/%E6%9C%BA%E5%99%A8%E5%AD%A6%E4%B9%A0%E5%9F%BA%E7%A1%80~02.%E6%95%B0%E6%8D%AE%E8%AF%BB%E5%8F%96/:2:1","tags":["机器学习"],"title":"机器学习基础~02.数据读取","uri":"/posts/%E6%9C%BA%E5%99%A8%E5%AD%A6%E4%B9%A0%E5%9F%BA%E7%A1%80~02.%E6%95%B0%E6%8D%AE%E8%AF%BB%E5%8F%96/#使用-read_csv-读取数据"},{"categories":"机器学习","collections":["机器学习基础"],"content":"第 15 节 读取excel数据\r问题\r数据是excel表格格式的，应该如何读取？ ","date":"2025-05-15","objectID":"/posts/%E6%9C%BA%E5%99%A8%E5%AD%A6%E4%B9%A0%E5%9F%BA%E7%A1%80~02.%E6%95%B0%E6%8D%AE%E8%AF%BB%E5%8F%96/:3:0","tags":["机器学习"],"title":"机器学习基础~02.数据读取","uri":"/posts/%E6%9C%BA%E5%99%A8%E5%AD%A6%E4%B9%A0%E5%9F%BA%E7%A1%80~02.%E6%95%B0%E6%8D%AE%E8%AF%BB%E5%8F%96/#读取excel数据"},{"categories":"机器学习","collections":["机器学习基础"],"content":"15.1 使用read_excel读取数据\r读写xls格式文件需要安装xlrd和xlwt库，安装方法： pip install xlrd xlwt 读写xlsx格式文件需要安装openpyxl库，安装方法： pip install openpyxl read_excel是读取excel表格文件的常用方法，使用方法如下： df = pd.read_excel('data.xlsx') # 读取第一个工作表（默认） df = pd.read_excel('data.xlsx', sheet_name=0) # 读取名为\"Sales\"的工作表 df = pd.read_excel('data.xlsx', sheet_name='Sales') # 第 3 行为表头（跳过前两行） df = pd.read_excel('data.xlsx', header=2) # 跳过第1, 3, 5行（0-based） df = pd.read_excel('data.xlsx', skiprows=[0, 2, 4]) ","date":"2025-05-15","objectID":"/posts/%E6%9C%BA%E5%99%A8%E5%AD%A6%E4%B9%A0%E5%9F%BA%E7%A1%80~02.%E6%95%B0%E6%8D%AE%E8%AF%BB%E5%8F%96/:3:1","tags":["机器学习"],"title":"机器学习基础~02.数据读取","uri":"/posts/%E6%9C%BA%E5%99%A8%E5%AD%A6%E4%B9%A0%E5%9F%BA%E7%A1%80~02.%E6%95%B0%E6%8D%AE%E8%AF%BB%E5%8F%96/#使用read_excel读取数据"},{"categories":"机器学习","collections":["机器学习基础"],"content":"第 16 节 读取数据库数据\r问题\r数据存放在数据库中，应该如何读取？ ","date":"2025-05-15","objectID":"/posts/%E6%9C%BA%E5%99%A8%E5%AD%A6%E4%B9%A0%E5%9F%BA%E7%A1%80~02.%E6%95%B0%E6%8D%AE%E8%AF%BB%E5%8F%96/:4:0","tags":["机器学习"],"title":"机器学习基础~02.数据读取","uri":"/posts/%E6%9C%BA%E5%99%A8%E5%AD%A6%E4%B9%A0%E5%9F%BA%E7%A1%80~02.%E6%95%B0%E6%8D%AE%E8%AF%BB%E5%8F%96/#读取数据库数据"},{"categories":"机器学习","collections":["机器学习基础"],"content":"16.1 通过 pandas 调用 sqlalchemy 连接数据库\r使用 pandas调用sqlalchemy连接数据库读取数据更加简洁迅速，方法如下： import pandas as pd from sqlalchemy import create_engine # SQLite 连接示例（使用文件路径）： # engine = create_engine('sqlite:///example.db') # MySQL 连接示例： # engine = create_engine('mysql://username:password@localhost/dbname') # PostgreSQL 连接示例： # engine = create_engine('postgresql://username:password@localhost/dbname') # SQL Server 连接示例： # engine = create_engine('mssql+pyodbc://username:password@server/dbname') # Oracle 连接示例（需要 cx_Oracle 库）： # engine = create_engine('oracle+cx_oracle://username:password@hostname:port/service_name') # 创建 SQLAlchemy Engine engine = create_engine('sqlite:///example.db') # 使用 pandas 的 read_sql() 函数读取数据并创建数据帧 query = \"SELECT * FROM users\" df = pd.read_sql(query, engine) ","date":"2025-05-15","objectID":"/posts/%E6%9C%BA%E5%99%A8%E5%AD%A6%E4%B9%A0%E5%9F%BA%E7%A1%80~02.%E6%95%B0%E6%8D%AE%E8%AF%BB%E5%8F%96/:4:1","tags":["机器学习"],"title":"机器学习基础~02.数据读取","uri":"/posts/%E6%9C%BA%E5%99%A8%E5%AD%A6%E4%B9%A0%E5%9F%BA%E7%A1%80~02.%E6%95%B0%E6%8D%AE%E8%AF%BB%E5%8F%96/#通过-pandas-调用-sqlalchemy-连接数据库"},{"categories":"机器学习","collections":["机器学习基础"],"content":"第 1 节 概述\r摘要\r本文讲述数据挖掘和机器学习的概念，以及其基本工作流程。 数据挖掘 (Data Mining) ：是一种知识发现，是从海量数据中提取隐含的、先前未知的、具有潜在价值的信息。 机器学习（Machine Learning） ：是通过算法使计算机从数据中学习规律，建立模型，使计算机能够自动做出决策和预测。 信息\r「数据挖掘」和「机器学习」有着很强的相关性，知识体系有许多交叉，本文不对两者进行区分，统一讲述其工作流程。 工作流程： 商业理解：确定业务对象、确定数据挖掘目标、制订工程计划 数据理解：收集初始数据、描述数据 、探索数据、验证数据质量 数据准备：特征提取与选择、数据清洗 、数据转换 、数据集成 建立模型：选择建模技术、生成测试设计、构建和评估模型 模型评估：评估结果、查看数据挖掘过程、确定后续步骤 模型部署：计划部署、监视和维护 、生成最终报告、复查 在接下来的篇章中，我们将逐个讲述这些过程。 ","date":"2025-05-15","objectID":"/posts/%E6%9C%BA%E5%99%A8%E5%AD%A6%E4%B9%A0%E5%9F%BA%E7%A1%80~01.%E6%A6%82%E8%BF%B0/:1:0","tags":["机器学习"],"title":"机器学习基础~01.概述","uri":"/posts/%E6%9C%BA%E5%99%A8%E5%AD%A6%E4%B9%A0%E5%9F%BA%E7%A1%80~01.%E6%A6%82%E8%BF%B0/#概述"},{"categories":["网页开发"],"collections":["前端"],"content":"第 1 节 概述\r这个博客是采用 Hugo 进行搭建的，Hugo 使用 Go 语言编写，编译速度远超其他生成器（如 Jekyll、Hexo）。即使是上千篇文章，也能在几秒内完成构建，适合频繁更新内容的场景。 主题采用 FixIt 进行二次修改，FixIt 本身就是一个较为简洁、直观的主题，能够很轻松的构建出好看的博客页面，并且支持各种的拓展，可以制作出功能丰富的博客。官方有一个使用说明。 出于我个人的需求，我希望我的博客能够更加层次分明。能够一眼看出各个等级的标题，图片、表格、公式、代码和强调的文字都可以在文章中一眼看出，并且颜色不用太过于繁杂，最好比较简约。 这样，在后续我想要复看我写的各种博客、笔记的时候，能够一眼看出我当时想要留下的重要部分，防止自己迷失在众多的文字当中。 ","date":"2025-05-14","objectID":"/posts/%E5%8D%9A%E5%AE%A2%E4%B8%BB%E9%A2%98%E8%AE%A9fixit%E6%9B%B4%E5%8A%A0%E4%BC%9F%E5%A4%A7/:1:0","tags":["前端"],"title":"博客主题，让FixIt更加伟大！","uri":"/posts/%E5%8D%9A%E5%AE%A2%E4%B8%BB%E9%A2%98%E8%AE%A9fixit%E6%9B%B4%E5%8A%A0%E4%BC%9F%E5%A4%A7/#概述"},{"categories":["网页开发"],"collections":["前端"],"content":"第 2 节 主题样式构思\r","date":"2025-05-14","objectID":"/posts/%E5%8D%9A%E5%AE%A2%E4%B8%BB%E9%A2%98%E8%AE%A9fixit%E6%9B%B4%E5%8A%A0%E4%BC%9F%E5%A4%A7/:2:0","tags":["前端"],"title":"博客主题，让FixIt更加伟大！","uri":"/posts/%E5%8D%9A%E5%AE%A2%E4%B8%BB%E9%A2%98%E8%AE%A9fixit%E6%9B%B4%E5%8A%A0%E4%BC%9F%E5%A4%A7/#主题样式构思"},{"categories":["网页开发"],"collections":["前端"],"content":"2.1 标题\r首先，标题一般分为六级，从H1 到 H6，在这个主题当中，标题是从 H2 到 H6 的，分为 5 级，构思如下： 字体：标题的字体和正文是不相同的，标题使用字体「霞鹜文楷」，正文使用中文字体「方正书宋」和英文字体「Minion Pro」 前缀：H2 标题使用前缀「第 x 节」，H3 标题使用前缀「x.x」，后面的标题不使用前缀 文本颜色：H2 标题使用红色字体，H3 标题使用蓝色字体，H4 标题和 H5 标题分别使用相同的红色字体和蓝色字体，H6 使用默认颜色 公式/代码：标题中的公式和代码的样式和文本统一 目录：在目录中只显示到 4 级标题，防止标题级数过多导致繁杂。 ","date":"2025-05-14","objectID":"/posts/%E5%8D%9A%E5%AE%A2%E4%B8%BB%E9%A2%98%E8%AE%A9fixit%E6%9B%B4%E5%8A%A0%E4%BC%9F%E5%A4%A7/:2:1","tags":["前端"],"title":"博客主题，让FixIt更加伟大！","uri":"/posts/%E5%8D%9A%E5%AE%A2%E4%B8%BB%E9%A2%98%E8%AE%A9fixit%E6%9B%B4%E5%8A%A0%E4%BC%9F%E5%A4%A7/#标题"},{"categories":["网页开发"],"collections":["前端"],"content":"2.2 正文\r正文构思如下： 强调：正文中文本强调不加粗，颜色为红色，红色较二级标题红稍浅。 超链接：正文中超链接颜色为蓝色，蓝色较三级标题蓝稍浅。 代码：正文中单行代码块和「强调」颜色相同。 留白：正文中的强调、超链接、单行代码块的左右留出一些空白，使得文字更加突出，间距为0.25 rem。 字体：正文中的强调、超链接、单行代码块使用和标题相同的「霞鹜文楷」。 ","date":"2025-05-14","objectID":"/posts/%E5%8D%9A%E5%AE%A2%E4%B8%BB%E9%A2%98%E8%AE%A9fixit%E6%9B%B4%E5%8A%A0%E4%BC%9F%E5%A4%A7/:2:2","tags":["前端"],"title":"博客主题，让FixIt更加伟大！","uri":"/posts/%E5%8D%9A%E5%AE%A2%E4%B8%BB%E9%A2%98%E8%AE%A9fixit%E6%9B%B4%E5%8A%A0%E4%BC%9F%E5%A4%A7/#正文"},{"categories":["网页开发"],"collections":["前端"],"content":"2.3 Admonition\rAdmonition 是一种首先使用在「obsidian」中的 markdown 扩展语法，相比较于原生的 markdown 语法quote，能够表现的语义更加丰富，呈现效果如下： 信息\r这是一个简单的 Admonition 样式 Admonition 中的标题和正文样式需要进行一定的修改，以配合 Admonition 自带的背景颜色： 颜色：Admonition 的标题和正文中的强调和单行代码使用 Admonition 框左侧竖线的颜色。 字体：标题、正文中强调和单行代码使用和标题相同的「霞鹜文楷」 ","date":"2025-05-14","objectID":"/posts/%E5%8D%9A%E5%AE%A2%E4%B8%BB%E9%A2%98%E8%AE%A9fixit%E6%9B%B4%E5%8A%A0%E4%BC%9F%E5%A4%A7/:2:3","tags":["前端"],"title":"博客主题，让FixIt更加伟大！","uri":"/posts/%E5%8D%9A%E5%AE%A2%E4%B8%BB%E9%A2%98%E8%AE%A9fixit%E6%9B%B4%E5%8A%A0%E4%BC%9F%E5%A4%A7/#admonition"},{"categories":["网页开发"],"collections":["前端"],"content":"2.4 图片\r间距：图片修改其上下间距，间距为2 rem，使得图片从文本中突出出来。 ","date":"2025-05-14","objectID":"/posts/%E5%8D%9A%E5%AE%A2%E4%B8%BB%E9%A2%98%E8%AE%A9fixit%E6%9B%B4%E5%8A%A0%E4%BC%9F%E5%A4%A7/:2:4","tags":["前端"],"title":"博客主题，让FixIt更加伟大！","uri":"/posts/%E5%8D%9A%E5%AE%A2%E4%B8%BB%E9%A2%98%E8%AE%A9fixit%E6%9B%B4%E5%8A%A0%E4%BC%9F%E5%A4%A7/#图片"},{"categories":["网页开发"],"collections":["前端"],"content":"2.5 表格\r间距：表格修改上下间距，间距为1 rem，使得表格从文本中突出出来。 表头字体：表格的表头使用和标题相同的「霞鹜文楷」。 表头颜色：表格的表头使用超链接的颜色。 ","date":"2025-05-14","objectID":"/posts/%E5%8D%9A%E5%AE%A2%E4%B8%BB%E9%A2%98%E8%AE%A9fixit%E6%9B%B4%E5%8A%A0%E4%BC%9F%E5%A4%A7/:2:5","tags":["前端"],"title":"博客主题，让FixIt更加伟大！","uri":"/posts/%E5%8D%9A%E5%AE%A2%E4%B8%BB%E9%A2%98%E8%AE%A9fixit%E6%9B%B4%E5%8A%A0%E4%BC%9F%E5%A4%A7/#表格"},{"categories":["网页开发"],"collections":["前端"],"content":"第 3 节 修改过程\r主要修改 2 个文件，assets/css/_override.scss和assets/css/_custom.scss ","date":"2025-05-14","objectID":"/posts/%E5%8D%9A%E5%AE%A2%E4%B8%BB%E9%A2%98%E8%AE%A9fixit%E6%9B%B4%E5%8A%A0%E4%BC%9F%E5%A4%A7/:3:0","tags":["前端"],"title":"博客主题，让FixIt更加伟大！","uri":"/posts/%E5%8D%9A%E5%AE%A2%E4%B8%BB%E9%A2%98%E8%AE%A9fixit%E6%9B%B4%E5%8A%A0%E4%BC%9F%E5%A4%A7/#修改过程"},{"categories":["网页开发"],"collections":["前端"],"content":"3.1 修改_override.scss\r这个文件用于覆盖主题自带的字体和颜色等变量名，对于一些简单的修改，只要覆盖变量名就可以实现全局修改的，可以直接在这边覆盖变量名即可。 ","date":"2025-05-14","objectID":"/posts/%E5%8D%9A%E5%AE%A2%E4%B8%BB%E9%A2%98%E8%AE%A9fixit%E6%9B%B4%E5%8A%A0%E4%BC%9F%E5%A4%A7/:3:1","tags":["前端"],"title":"博客主题，让FixIt更加伟大！","uri":"/posts/%E5%8D%9A%E5%AE%A2%E4%B8%BB%E9%A2%98%E8%AE%A9fixit%E6%9B%B4%E5%8A%A0%E4%BC%9F%E5%A4%A7/#修改_overridescss"},{"categories":["网页开发"],"collections":["前端"],"content":"3.2 修改_custom.scss\r这个文件主要用来覆盖 CSS 样式，如果我们针对不同的情形，同一markdown公式展现出不同的样式，比如如下情景： 信息\r信息这个 Admonition 样式中强调和 code 需要配合Admonition 自带的背景颜色。 而在一般的文本中强调和code表现出另外一套样式，需要修改_custom.scss。 修改的时候，借助浏览器的「开发者工具」即可，找到当前markdown公式优先级较高的 scss 匹配方式，在该种匹配方式下修改样式即可。 ","date":"2025-05-14","objectID":"/posts/%E5%8D%9A%E5%AE%A2%E4%B8%BB%E9%A2%98%E8%AE%A9fixit%E6%9B%B4%E5%8A%A0%E4%BC%9F%E5%A4%A7/:3:2","tags":["前端"],"title":"博客主题，让FixIt更加伟大！","uri":"/posts/%E5%8D%9A%E5%AE%A2%E4%B8%BB%E9%A2%98%E8%AE%A9fixit%E6%9B%B4%E5%8A%A0%E4%BC%9F%E5%A4%A7/#修改_customscss"},{"categories":"旅游","collections":["超超的奇妙冒险"],"content":"第 1 节 导览图\r一走进上海野生动物园大门，便能看到入口处醒目的导览图，上面清晰地标注了整个园区的布局和动物分布，让我对即将开始的探索之旅充满了期待。 ","date":"2025-04-20","objectID":"/posts/%E4%B8%8A%E6%B5%B7%E9%87%8E%E7%94%9F%E5%8A%A8%E7%89%A9%E5%9B%AD%E4%B8%8E%E7%99%BE%E5%85%BD%E7%9A%84%E6%B8%A9%E6%9F%94%E9%82%82%E9%80%85/:1:0","tags":["旅游"],"title":"上海野生动物园：与百兽的温柔邂逅","uri":"/posts/%E4%B8%8A%E6%B5%B7%E9%87%8E%E7%94%9F%E5%8A%A8%E7%89%A9%E5%9B%AD%E4%B8%8E%E7%99%BE%E5%85%BD%E7%9A%84%E6%B8%A9%E6%9F%94%E9%82%82%E9%80%85/#导览图"},{"categories":"旅游","collections":["超超的奇妙冒险"],"content":"第 2 节 热带鸟\r","date":"2025-04-20","objectID":"/posts/%E4%B8%8A%E6%B5%B7%E9%87%8E%E7%94%9F%E5%8A%A8%E7%89%A9%E5%9B%AD%E4%B8%8E%E7%99%BE%E5%85%BD%E7%9A%84%E6%B8%A9%E6%9F%94%E9%82%82%E9%80%85/:2:0","tags":["旅游"],"title":"上海野生动物园：与百兽的温柔邂逅","uri":"/posts/%E4%B8%8A%E6%B5%B7%E9%87%8E%E7%94%9F%E5%8A%A8%E7%89%A9%E5%9B%AD%E4%B8%8E%E7%99%BE%E5%85%BD%E7%9A%84%E6%B8%A9%E6%9F%94%E9%82%82%E9%80%85/#热带鸟"},{"categories":"旅游","collections":["超超的奇妙冒险"],"content":"2.1 蓝黄金刚鹦鹉\r走进上海野生动物园的热带鸟区，首先映入眼帘的是一只蓝黄金刚鹦鹉，它那湛蓝色的背部和亮黄色的腹部在阳光下熠熠生辉，格外引人注目。 ","date":"2025-04-20","objectID":"/posts/%E4%B8%8A%E6%B5%B7%E9%87%8E%E7%94%9F%E5%8A%A8%E7%89%A9%E5%9B%AD%E4%B8%8E%E7%99%BE%E5%85%BD%E7%9A%84%E6%B8%A9%E6%9F%94%E9%82%82%E9%80%85/:2:1","tags":["旅游"],"title":"上海野生动物园：与百兽的温柔邂逅","uri":"/posts/%E4%B8%8A%E6%B5%B7%E9%87%8E%E7%94%9F%E5%8A%A8%E7%89%A9%E5%9B%AD%E4%B8%8E%E7%99%BE%E5%85%BD%E7%9A%84%E6%B8%A9%E6%9F%94%E9%82%82%E9%80%85/#蓝黄金刚鹦鹉"},{"categories":"旅游","collections":["超超的奇妙冒险"],"content":"2.2 红绿金刚鹦鹉\r紧接着，旁边一只红绿金刚鹦鹉正悠闲地梳理羽毛，红绿交织的羽毛在光线下泛出金属般的光泽，同样光彩照人。 ","date":"2025-04-20","objectID":"/posts/%E4%B8%8A%E6%B5%B7%E9%87%8E%E7%94%9F%E5%8A%A8%E7%89%A9%E5%9B%AD%E4%B8%8E%E7%99%BE%E5%85%BD%E7%9A%84%E6%B8%A9%E6%9F%94%E9%82%82%E9%80%85/:2:2","tags":["旅游"],"title":"上海野生动物园：与百兽的温柔邂逅","uri":"/posts/%E4%B8%8A%E6%B5%B7%E9%87%8E%E7%94%9F%E5%8A%A8%E7%89%A9%E5%9B%AD%E4%B8%8E%E7%99%BE%E5%85%BD%E7%9A%84%E6%B8%A9%E6%9F%94%E9%82%82%E9%80%85/#红绿金刚鹦鹉"},{"categories":"旅游","collections":["超超的奇妙冒险"],"content":"2.3 鞭笞巨嘴鸟\r再往前走几步，一只鞭笞巨嘴鸟停歇在枝头，它那巨大的橘黄色喙格外抢眼，仿佛是大自然精心雕琢的艺术品。 ","date":"2025-04-20","objectID":"/posts/%E4%B8%8A%E6%B5%B7%E9%87%8E%E7%94%9F%E5%8A%A8%E7%89%A9%E5%9B%AD%E4%B8%8E%E7%99%BE%E5%85%BD%E7%9A%84%E6%B8%A9%E6%9F%94%E9%82%82%E9%80%85/:2:3","tags":["旅游"],"title":"上海野生动物园：与百兽的温柔邂逅","uri":"/posts/%E4%B8%8A%E6%B5%B7%E9%87%8E%E7%94%9F%E5%8A%A8%E7%89%A9%E5%9B%AD%E4%B8%8E%E7%99%BE%E5%85%BD%E7%9A%84%E6%B8%A9%E6%9F%94%E9%82%82%E9%80%85/#鞭笞巨嘴鸟"},{"categories":"旅游","collections":["超超的奇妙冒险"],"content":"2.4 黄冠亚马逊鹦鹉\r不远处，一只黄冠亚马逊鹦鹉正歪着脑袋好奇地打量着游客，头顶那抹鲜亮的黄色冠羽显得格外精神。 ","date":"2025-04-20","objectID":"/posts/%E4%B8%8A%E6%B5%B7%E9%87%8E%E7%94%9F%E5%8A%A8%E7%89%A9%E5%9B%AD%E4%B8%8E%E7%99%BE%E5%85%BD%E7%9A%84%E6%B8%A9%E6%9F%94%E9%82%82%E9%80%85/:2:4","tags":["旅游"],"title":"上海野生动物园：与百兽的温柔邂逅","uri":"/posts/%E4%B8%8A%E6%B5%B7%E9%87%8E%E7%94%9F%E5%8A%A8%E7%89%A9%E5%9B%AD%E4%B8%8E%E7%99%BE%E5%85%BD%E7%9A%84%E6%B8%A9%E6%9F%94%E9%82%82%E9%80%85/#黄冠亚马逊鹦鹉"},{"categories":"旅游","collections":["超超的奇妙冒险"],"content":"2.5 美洲红鹮\r继续往里走，几只美洲红鹮正在浅水中低头觅食，它们通体殷红的身影倒映在水中，如同一团流动的火焰，为这片热带天地画上了浓墨重彩的一笔。 ","date":"2025-04-20","objectID":"/posts/%E4%B8%8A%E6%B5%B7%E9%87%8E%E7%94%9F%E5%8A%A8%E7%89%A9%E5%9B%AD%E4%B8%8E%E7%99%BE%E5%85%BD%E7%9A%84%E6%B8%A9%E6%9F%94%E9%82%82%E9%80%85/:2:5","tags":["旅游"],"title":"上海野生动物园：与百兽的温柔邂逅","uri":"/posts/%E4%B8%8A%E6%B5%B7%E9%87%8E%E7%94%9F%E5%8A%A8%E7%89%A9%E5%9B%AD%E4%B8%8E%E7%99%BE%E5%85%BD%E7%9A%84%E6%B8%A9%E6%9F%94%E9%82%82%E9%80%85/#美洲红鹮"},{"categories":"旅游","collections":["超超的奇妙冒险"],"content":"第 3 节 小动物\r","date":"2025-04-20","objectID":"/posts/%E4%B8%8A%E6%B5%B7%E9%87%8E%E7%94%9F%E5%8A%A8%E7%89%A9%E5%9B%AD%E4%B8%8E%E7%99%BE%E5%85%BD%E7%9A%84%E6%B8%A9%E6%9F%94%E9%82%82%E9%80%85/:3:0","tags":["旅游"],"title":"上海野生动物园：与百兽的温柔邂逅","uri":"/posts/%E4%B8%8A%E6%B5%B7%E9%87%8E%E7%94%9F%E5%8A%A8%E7%89%A9%E5%9B%AD%E4%B8%8E%E7%99%BE%E5%85%BD%E7%9A%84%E6%B8%A9%E6%9F%94%E9%82%82%E9%80%85/#小动物"},{"categories":"旅游","collections":["超超的奇妙冒险"],"content":"3.1 细尾獴\r走进上海野生动物园的细尾獴展区，几只可爱的细尾獴——也就是大家常说的\"丁满\"——正精神抖擞地站立在原地放哨，它们用后腿支撑着身体，小脑袋警觉地转动着，那认真又呆萌的模样惹得游客们纷纷驻足拍照。 ","date":"2025-04-20","objectID":"/posts/%E4%B8%8A%E6%B5%B7%E9%87%8E%E7%94%9F%E5%8A%A8%E7%89%A9%E5%9B%AD%E4%B8%8E%E7%99%BE%E5%85%BD%E7%9A%84%E6%B8%A9%E6%9F%94%E9%82%82%E9%80%85/:3:1","tags":["旅游"],"title":"上海野生动物园：与百兽的温柔邂逅","uri":"/posts/%E4%B8%8A%E6%B5%B7%E9%87%8E%E7%94%9F%E5%8A%A8%E7%89%A9%E5%9B%AD%E4%B8%8E%E7%99%BE%E5%85%BD%E7%9A%84%E6%B8%A9%E6%9F%94%E9%82%82%E9%80%85/#细尾獴"},{"categories":"旅游","collections":["超超的奇妙冒险"],"content":"3.2 缅甸蟒\r走进上海野生动物园的缅甸蟒展区，一条体型硕大的巨蟒正盘踞在展箱之中，目测长度惊人，真正应了游客口中「很大一条」的评价，那粗壮的躯干和沉稳的姿态令人不由得屏息惊叹。 ","date":"2025-04-20","objectID":"/posts/%E4%B8%8A%E6%B5%B7%E9%87%8E%E7%94%9F%E5%8A%A8%E7%89%A9%E5%9B%AD%E4%B8%8E%E7%99%BE%E5%85%BD%E7%9A%84%E6%B8%A9%E6%9F%94%E9%82%82%E9%80%85/:3:2","tags":["旅游"],"title":"上海野生动物园：与百兽的温柔邂逅","uri":"/posts/%E4%B8%8A%E6%B5%B7%E9%87%8E%E7%94%9F%E5%8A%A8%E7%89%A9%E5%9B%AD%E4%B8%8E%E7%99%BE%E5%85%BD%E7%9A%84%E6%B8%A9%E6%9F%94%E9%82%82%E9%80%85/#缅甸蟒"},{"categories":"旅游","collections":["超超的奇妙冒险"],"content":"3.3 耳廓狐\r走进上海野生动物园的耳廓狐展区，一眼就看到那几只小小的耳廓狐，它们竖起大耳朵，圆溜溜的眼睛好奇地打量四周，模样实在是可爱极了。 ","date":"2025-04-20","objectID":"/posts/%E4%B8%8A%E6%B5%B7%E9%87%8E%E7%94%9F%E5%8A%A8%E7%89%A9%E5%9B%AD%E4%B8%8E%E7%99%BE%E5%85%BD%E7%9A%84%E6%B8%A9%E6%9F%94%E9%82%82%E9%80%85/:3:3","tags":["旅游"],"title":"上海野生动物园：与百兽的温柔邂逅","uri":"/posts/%E4%B8%8A%E6%B5%B7%E9%87%8E%E7%94%9F%E5%8A%A8%E7%89%A9%E5%9B%AD%E4%B8%8E%E7%99%BE%E5%85%BD%E7%9A%84%E6%B8%A9%E6%9F%94%E9%82%82%E9%80%85/#耳廓狐"},{"categories":"旅游","collections":["超超的奇妙冒险"],"content":"3.4 赤麻鸭\r走进上海野生动物园的水禽湖畔，一只可爱的赤麻鸭幼崽正在悠闲地划水，它橘红色的绒毛在阳光下泛着柔和的光泽，小脚蹼在水面下轻快地拨动，荡起一圈圈涟漪，看起来自在又惬意。 ","date":"2025-04-20","objectID":"/posts/%E4%B8%8A%E6%B5%B7%E9%87%8E%E7%94%9F%E5%8A%A8%E7%89%A9%E5%9B%AD%E4%B8%8E%E7%99%BE%E5%85%BD%E7%9A%84%E6%B8%A9%E6%9F%94%E9%82%82%E9%80%85/:3:4","tags":["旅游"],"title":"上海野生动物园：与百兽的温柔邂逅","uri":"/posts/%E4%B8%8A%E6%B5%B7%E9%87%8E%E7%94%9F%E5%8A%A8%E7%89%A9%E5%9B%AD%E4%B8%8E%E7%99%BE%E5%85%BD%E7%9A%84%E6%B8%A9%E6%9F%94%E9%82%82%E9%80%85/#赤麻鸭"},{"categories":"旅游","collections":["超超的奇妙冒险"],"content":"3.5 黑白领狐猴\r来到上海野生动物园的黑白领狐猴展区，只见这小家伙正躲在小房子里头不出来，任凭游客们在外面翘首企盼，它却自顾自地缩在角落里，只偶尔露出一个毛茸茸的脑袋，让人又好气又好笑。 ","date":"2025-04-20","objectID":"/posts/%E4%B8%8A%E6%B5%B7%E9%87%8E%E7%94%9F%E5%8A%A8%E7%89%A9%E5%9B%AD%E4%B8%8E%E7%99%BE%E5%85%BD%E7%9A%84%E6%B8%A9%E6%9F%94%E9%82%82%E9%80%85/:3:5","tags":["旅游"],"title":"上海野生动物园：与百兽的温柔邂逅","uri":"/posts/%E4%B8%8A%E6%B5%B7%E9%87%8E%E7%94%9F%E5%8A%A8%E7%89%A9%E5%9B%AD%E4%B8%8E%E7%99%BE%E5%85%BD%E7%9A%84%E6%B8%A9%E6%9F%94%E9%82%82%E9%80%85/#黑白领狐猴"},{"categories":"旅游","collections":["超超的奇妙冒险"],"content":"第 4 节 中型动物\r","date":"2025-04-20","objectID":"/posts/%E4%B8%8A%E6%B5%B7%E9%87%8E%E7%94%9F%E5%8A%A8%E7%89%A9%E5%9B%AD%E4%B8%8E%E7%99%BE%E5%85%BD%E7%9A%84%E6%B8%A9%E6%9F%94%E9%82%82%E9%80%85/:4:0","tags":["旅游"],"title":"上海野生动物园：与百兽的温柔邂逅","uri":"/posts/%E4%B8%8A%E6%B5%B7%E9%87%8E%E7%94%9F%E5%8A%A8%E7%89%A9%E5%9B%AD%E4%B8%8E%E7%99%BE%E5%85%BD%E7%9A%84%E6%B8%A9%E6%9F%94%E9%82%82%E9%80%85/#中型动物"},{"categories":"旅游","collections":["超超的奇妙冒险"],"content":"4.1 梅花鹿\r走进上海野生动物园的梅花鹿区，几只梅花鹿正悠闲地在草地上低头觅食，细长的脖颈微微弯曲，轻轻啃食着嫩绿的青草，模样格外温顺可爱。 ","date":"2025-04-20","objectID":"/posts/%E4%B8%8A%E6%B5%B7%E9%87%8E%E7%94%9F%E5%8A%A8%E7%89%A9%E5%9B%AD%E4%B8%8E%E7%99%BE%E5%85%BD%E7%9A%84%E6%B8%A9%E6%9F%94%E9%82%82%E9%80%85/:4:1","tags":["旅游"],"title":"上海野生动物园：与百兽的温柔邂逅","uri":"/posts/%E4%B8%8A%E6%B5%B7%E9%87%8E%E7%94%9F%E5%8A%A8%E7%89%A9%E5%9B%AD%E4%B8%8E%E7%99%BE%E5%85%BD%E7%9A%84%E6%B8%A9%E6%9F%94%E9%82%82%E9%80%85/#梅花鹿"},{"categories":"旅游","collections":["超超的奇妙冒险"],"content":"4.2 骏马\r几匹骏马正围绕着表演舞台欢快地奔跑，鬃毛随风飘扬，矫健的身姿在阳光下格外夺目，仿佛在向游客们展示着它们的活力与优雅。 ","date":"2025-04-20","objectID":"/posts/%E4%B8%8A%E6%B5%B7%E9%87%8E%E7%94%9F%E5%8A%A8%E7%89%A9%E5%9B%AD%E4%B8%8E%E7%99%BE%E5%85%BD%E7%9A%84%E6%B8%A9%E6%9F%94%E9%82%82%E9%80%85/:4:2","tags":["旅游"],"title":"上海野生动物园：与百兽的温柔邂逅","uri":"/posts/%E4%B8%8A%E6%B5%B7%E9%87%8E%E7%94%9F%E5%8A%A8%E7%89%A9%E5%9B%AD%E4%B8%8E%E7%99%BE%E5%85%BD%E7%9A%84%E6%B8%A9%E6%9F%94%E9%82%82%E9%80%85/#骏马"},{"categories":"旅游","collections":["超超的奇妙冒险"],"content":"4.3 犀牛\r远远望去，犀牛在地上安静地踱步，体型竟显得娇小可爱，与想象中庞然大物的印象颇有出入。 走近细看才发觉，它的皮肤粗糙而厚实，布满深深的褶皱与纹路，每一道纹理都仿佛诉说着岁月的风霜与野性的力量。 ","date":"2025-04-20","objectID":"/posts/%E4%B8%8A%E6%B5%B7%E9%87%8E%E7%94%9F%E5%8A%A8%E7%89%A9%E5%9B%AD%E4%B8%8E%E7%99%BE%E5%85%BD%E7%9A%84%E6%B8%A9%E6%9F%94%E9%82%82%E9%80%85/:4:3","tags":["旅游"],"title":"上海野生动物园：与百兽的温柔邂逅","uri":"/posts/%E4%B8%8A%E6%B5%B7%E9%87%8E%E7%94%9F%E5%8A%A8%E7%89%A9%E5%9B%AD%E4%B8%8E%E7%99%BE%E5%85%BD%E7%9A%84%E6%B8%A9%E6%9F%94%E9%82%82%E9%80%85/#犀牛"},{"categories":"旅游","collections":["超超的奇妙冒险"],"content":"4.4 河马\r一只河马正慵懒地趴在地上，皮肤竟透着粉粉嫩嫩的光泽，看上去格外憨态可掬、惹人喜爱。 ","date":"2025-04-20","objectID":"/posts/%E4%B8%8A%E6%B5%B7%E9%87%8E%E7%94%9F%E5%8A%A8%E7%89%A9%E5%9B%AD%E4%B8%8E%E7%99%BE%E5%85%BD%E7%9A%84%E6%B8%A9%E6%9F%94%E9%82%82%E9%80%85/:4:4","tags":["旅游"],"title":"上海野生动物园：与百兽的温柔邂逅","uri":"/posts/%E4%B8%8A%E6%B5%B7%E9%87%8E%E7%94%9F%E5%8A%A8%E7%89%A9%E5%9B%AD%E4%B8%8E%E7%99%BE%E5%85%BD%E7%9A%84%E6%B8%A9%E6%9F%94%E9%82%82%E9%80%85/#河马"},{"categories":"旅游","collections":["超超的奇妙冒险"],"content":"4.5 羚牛\r一只羚牛正在悠闲活动，它浑身覆盖着金黄色的厚实毛发，憨态可掬的模样立刻吸引了我的注意。 走近细看，这只别名“金毛羚牛”的大家伙正惬意地站在围栏边给自己挠痒——虽然名字里带着“牛”字，但它的身形与动作其实更接近羊，显得格外有趣。 ","date":"2025-04-20","objectID":"/posts/%E4%B8%8A%E6%B5%B7%E9%87%8E%E7%94%9F%E5%8A%A8%E7%89%A9%E5%9B%AD%E4%B8%8E%E7%99%BE%E5%85%BD%E7%9A%84%E6%B8%A9%E6%9F%94%E9%82%82%E9%80%85/:4:5","tags":["旅游"],"title":"上海野生动物园：与百兽的温柔邂逅","uri":"/posts/%E4%B8%8A%E6%B5%B7%E9%87%8E%E7%94%9F%E5%8A%A8%E7%89%A9%E5%9B%AD%E4%B8%8E%E7%99%BE%E5%85%BD%E7%9A%84%E6%B8%A9%E6%9F%94%E9%82%82%E9%80%85/#羚牛"},{"categories":"旅游","collections":["超超的奇妙冒险"],"content":"4.6 袋鼠\r两只袋鼠正低头在草地上专心觅食，时不时用前爪拨开草丛寻找鲜嫩的草叶，模样十分认真可爱。 一只袋鼠突然蹦跶起来，两条强有力的后腿一蹬便跃出数米远，在空中划出一道灵动的弧线，落地后还不忘回头张望，仿佛在炫耀自己的弹跳本领。 ","date":"2025-04-20","objectID":"/posts/%E4%B8%8A%E6%B5%B7%E9%87%8E%E7%94%9F%E5%8A%A8%E7%89%A9%E5%9B%AD%E4%B8%8E%E7%99%BE%E5%85%BD%E7%9A%84%E6%B8%A9%E6%9F%94%E9%82%82%E9%80%85/:4:6","tags":["旅游"],"title":"上海野生动物园：与百兽的温柔邂逅","uri":"/posts/%E4%B8%8A%E6%B5%B7%E9%87%8E%E7%94%9F%E5%8A%A8%E7%89%A9%E5%9B%AD%E4%B8%8E%E7%99%BE%E5%85%BD%E7%9A%84%E6%B8%A9%E6%9F%94%E9%82%82%E9%80%85/#袋鼠"},{"categories":"旅游","collections":["超超的奇妙冒险"],"content":"4.7 羊驼\r一只羊驼正悠闲地踱步，那副憨态可掬的模样配上蓬松的绒毛，真是非常呆萌可爱。 ","date":"2025-04-20","objectID":"/posts/%E4%B8%8A%E6%B5%B7%E9%87%8E%E7%94%9F%E5%8A%A8%E7%89%A9%E5%9B%AD%E4%B8%8E%E7%99%BE%E5%85%BD%E7%9A%84%E6%B8%A9%E6%9F%94%E9%82%82%E9%80%85/:4:7","tags":["旅游"],"title":"上海野生动物园：与百兽的温柔邂逅","uri":"/posts/%E4%B8%8A%E6%B5%B7%E9%87%8E%E7%94%9F%E5%8A%A8%E7%89%A9%E5%9B%AD%E4%B8%8E%E7%99%BE%E5%85%BD%E7%9A%84%E6%B8%A9%E6%9F%94%E9%82%82%E9%80%85/#羊驼"},{"categories":"旅游","collections":["超超的奇妙冒险"],"content":"第 5 节 大型动物\r","date":"2025-04-20","objectID":"/posts/%E4%B8%8A%E6%B5%B7%E9%87%8E%E7%94%9F%E5%8A%A8%E7%89%A9%E5%9B%AD%E4%B8%8E%E7%99%BE%E5%85%BD%E7%9A%84%E6%B8%A9%E6%9F%94%E9%82%82%E9%80%85/:5:0","tags":["旅游"],"title":"上海野生动物园：与百兽的温柔邂逅","uri":"/posts/%E4%B8%8A%E6%B5%B7%E9%87%8E%E7%94%9F%E5%8A%A8%E7%89%A9%E5%9B%AD%E4%B8%8E%E7%99%BE%E5%85%BD%E7%9A%84%E6%B8%A9%E6%9F%94%E9%82%82%E9%80%85/#大型动物"},{"categories":"旅游","collections":["超超的奇妙冒险"],"content":"5.1 长颈鹿\r一只高大的长颈鹿正悠然自得地伸出长长的舌头，卷食着树梢上翠绿的嫩叶，模样十分憨态可掬。 ","date":"2025-04-20","objectID":"/posts/%E4%B8%8A%E6%B5%B7%E9%87%8E%E7%94%9F%E5%8A%A8%E7%89%A9%E5%9B%AD%E4%B8%8E%E7%99%BE%E5%85%BD%E7%9A%84%E6%B8%A9%E6%9F%94%E9%82%82%E9%80%85/:5:1","tags":["旅游"],"title":"上海野生动物园：与百兽的温柔邂逅","uri":"/posts/%E4%B8%8A%E6%B5%B7%E9%87%8E%E7%94%9F%E5%8A%A8%E7%89%A9%E5%9B%AD%E4%B8%8E%E7%99%BE%E5%85%BD%E7%9A%84%E6%B8%A9%E6%9F%94%E9%82%82%E9%80%85/#长颈鹿"},{"categories":"旅游","collections":["超超的奇妙冒险"],"content":"5.2 狮子\r走进上海野生动物园的狮子展区，远远便看见一只母狮正慵懒地躺在草地上，阳光洒在它金色的皮毛上，显得格外威严而优雅。 忽然，它似乎察觉到了我的目光，缓缓抬起头来，一双琥珀色的眼睛与我四目相对，那一瞬间空气仿佛凝固了。 紧接着，这只母狮竟然站起身来，迈着沉稳而优雅的步伐，一步步朝我的方向走来，我的心不由得提到了嗓子眼。 它走到围栏前停下，隔着安全距离静静注视着我，眼神中透着好奇而非敌意，这难得的近距离对视让我终生难忘。 ","date":"2025-04-20","objectID":"/posts/%E4%B8%8A%E6%B5%B7%E9%87%8E%E7%94%9F%E5%8A%A8%E7%89%A9%E5%9B%AD%E4%B8%8E%E7%99%BE%E5%85%BD%E7%9A%84%E6%B8%A9%E6%9F%94%E9%82%82%E9%80%85/:5:2","tags":["旅游"],"title":"上海野生动物园：与百兽的温柔邂逅","uri":"/posts/%E4%B8%8A%E6%B5%B7%E9%87%8E%E7%94%9F%E5%8A%A8%E7%89%A9%E5%9B%AD%E4%B8%8E%E7%99%BE%E5%85%BD%E7%9A%84%E6%B8%A9%E6%9F%94%E9%82%82%E9%80%85/#狮子"},{"categories":"旅游","collections":["超超的奇妙冒险"],"content":"5.3 老虎\r走进上海野生动物园的猛兽区，远远就看见一只威风凛凛的老虎正趴在草地上，眼神中透着一股无奈与慵懒。 再靠近一些，这只老虎依然纹丝不动，仿佛对周遭的一切都提不起兴趣，一副生无可恋的模样，让人忍不住觉得有些可爱又好笑。 ","date":"2025-04-20","objectID":"/posts/%E4%B8%8A%E6%B5%B7%E9%87%8E%E7%94%9F%E5%8A%A8%E7%89%A9%E5%9B%AD%E4%B8%8E%E7%99%BE%E5%85%BD%E7%9A%84%E6%B8%A9%E6%9F%94%E9%82%82%E9%80%85/:5:3","tags":["旅游"],"title":"上海野生动物园：与百兽的温柔邂逅","uri":"/posts/%E4%B8%8A%E6%B5%B7%E9%87%8E%E7%94%9F%E5%8A%A8%E7%89%A9%E5%9B%AD%E4%B8%8E%E7%99%BE%E5%85%BD%E7%9A%84%E6%B8%A9%E6%9F%94%E9%82%82%E9%80%85/#老虎"},{"categories":"旅游","collections":["超超的奇妙冒险"],"content":"5.4 棕熊\r走进上海野生动物园的棕熊展区，几只体型巨大的棕熊正赫然立于眼前，那魁梧的身躯和沉稳的步伐散发出极强的威慑力，让人不敢轻易靠近。 ","date":"2025-04-20","objectID":"/posts/%E4%B8%8A%E6%B5%B7%E9%87%8E%E7%94%9F%E5%8A%A8%E7%89%A9%E5%9B%AD%E4%B8%8E%E7%99%BE%E5%85%BD%E7%9A%84%E6%B8%A9%E6%9F%94%E9%82%82%E9%80%85/:5:4","tags":["旅游"],"title":"上海野生动物园：与百兽的温柔邂逅","uri":"/posts/%E4%B8%8A%E6%B5%B7%E9%87%8E%E7%94%9F%E5%8A%A8%E7%89%A9%E5%9B%AD%E4%B8%8E%E7%99%BE%E5%85%BD%E7%9A%84%E6%B8%A9%E6%9F%94%E9%82%82%E9%80%85/#棕熊"},{"categories":"旅游","collections":["超超的奇妙冒险"],"content":"5.5 亚洲象\r走进上海野生动物园的舞台区，三头大象正在驯兽师的指挥下与观众热情互动，它用灵活的长鼻子卷起一束青草，场面既滑稽又惹人喜爱。 接着来到舞台外，这里的亚洲象将鼻子伸入桶中吸水，再高高扬起鼻子把水送入口中，憨态可掬的模样引得周围游客纷纷鼓掌欢笑。 ","date":"2025-04-20","objectID":"/posts/%E4%B8%8A%E6%B5%B7%E9%87%8E%E7%94%9F%E5%8A%A8%E7%89%A9%E5%9B%AD%E4%B8%8E%E7%99%BE%E5%85%BD%E7%9A%84%E6%B8%A9%E6%9F%94%E9%82%82%E9%80%85/:5:5","tags":["旅游"],"title":"上海野生动物园：与百兽的温柔邂逅","uri":"/posts/%E4%B8%8A%E6%B5%B7%E9%87%8E%E7%94%9F%E5%8A%A8%E7%89%A9%E5%9B%AD%E4%B8%8E%E7%99%BE%E5%85%BD%E7%9A%84%E6%B8%A9%E6%9F%94%E9%82%82%E9%80%85/#亚洲象"},{"categories":"旅游","collections":["超超的奇妙冒险"],"content":"5.6 非洲象\r走进上海野生动物园的非洲象展区，一头体型庞大的非洲象正悠闲地漫步在草地上，最引人注目的莫过于它那对巨大的耳朵，形状神似非洲大陆的地图，令人啧啧称奇。 据说它们生活在炎热的非洲草原上，这对如巨扇般的耳朵不仅能帮助散热降温，还能灵敏地捕捉远方的声音，此刻它正漫无目的地徜徉着，悠然自得的样子透着几分从容与惬意。 ","date":"2025-04-20","objectID":"/posts/%E4%B8%8A%E6%B5%B7%E9%87%8E%E7%94%9F%E5%8A%A8%E7%89%A9%E5%9B%AD%E4%B8%8E%E7%99%BE%E5%85%BD%E7%9A%84%E6%B8%A9%E6%9F%94%E9%82%82%E9%80%85/:5:6","tags":["旅游"],"title":"上海野生动物园：与百兽的温柔邂逅","uri":"/posts/%E4%B8%8A%E6%B5%B7%E9%87%8E%E7%94%9F%E5%8A%A8%E7%89%A9%E5%9B%AD%E4%B8%8E%E7%99%BE%E5%85%BD%E7%9A%84%E6%B8%A9%E6%9F%94%E9%82%82%E9%80%85/#非洲象"},{"categories":["量化交易"],"collections":["量化交易"],"content":"第 10 节 概述\r一般来说，想要轻松获取高质量的量化数据，需要去一些金融数据提供商的网址注册，并且支付一定的订阅费用，然后使用他们提供的 API 来获取量化数据。 在我们接触量化交易初期，一般是不想承担这样的费用的。使用免费的数据源，先进行一定的尝试，依照效果和具体的需求，再去考虑付费的数据源，才是大多数人的想法。 摘要\r本文的主要内容，就是记录本人尝试使用各种免费数据源，构建出量化数据集的过程。 ","date":"2025-01-24","objectID":"/posts/%E9%87%8F%E5%8C%96%E6%95%B0%E6%8D%AE%E6%BA%90/:1:0","tags":[],"title":"量化数据源","uri":"/posts/%E9%87%8F%E5%8C%96%E6%95%B0%E6%8D%AE%E6%BA%90/#概述"},{"categories":["量化交易"],"collections":["量化交易"],"content":"10.1 老虎开放平台\r老虎证券官方提供的 API，使用的前提是在老虎证券已经开户。首先需要前往官方网址进行注册，注册以后前往个人中心，会自动生成一个RSA的公钥和私钥，可以直接选择下载（如果不用老虎交易，下载模拟账户的就可以），会得到一个tiger_openapi_config.properties 文件，我们保存好这个文件。 信息\r老虎证券比较好的地方在于他是使用RSA的私钥进行认证的，无需本地安装专用的客户端，就可以使用他提供的 API 获取数据，当然也可以交易，这点相对于盈透和富途是有优势的。 使用前需要安装对应 python 库 pip install tigeropen 官方文档有很详细说明如何使用 API，简中文档，一看就懂。API 进行一些基本使用是免费的，比如查询历史价格、股票信息等，L1、L2 的数据是需要付费的，大陆 IP 的用户可以免费使用 A 股的 L1 数据和港股的 L2 数据，富途那边也是一样，对大陆用户很友好。 老虎对请求频率有一定的限制，详见网址，对于历史数据的访问有一定的限制，详见网址： 满足其中任意一项要求即可 历史最大可拉取的股票/ETF K线标的数量 实时行情订阅数 实时深度行情订阅数 开通API权限 20 20 10 总资产 \u003e 1万 USD 成交额 \u003e 10万 USD 200 100 20 总资产 \u003e 5万 USD 成交额 \u003e 50万 USD 500 500 100 总资产 \u003e 50万 USD 成交额 \u003e 200万 USD 1000 1000 200 总资产 \u003e 100万 USD 成交额 \u003e 500万 USD 2000 2000 500 技巧\r老虎 API 不用客户端，适合获取量化数据。 富途界面美观，适合用来看盘。 盈透佣金少，点差低，融资利率低，适合交易。 ","date":"2025-01-24","objectID":"/posts/%E9%87%8F%E5%8C%96%E6%95%B0%E6%8D%AE%E6%BA%90/:1:1","tags":[],"title":"量化数据源","uri":"/posts/%E9%87%8F%E5%8C%96%E6%95%B0%E6%8D%AE%E6%BA%90/#老虎开放平台"},{"categories":["量化交易"],"collections":["量化交易"],"content":"10.2 富途 API\r富途官方提供的 API，使用前提是在富途已经开户。需要下载官方提供的客户端 OpenD，这种方式和盈透类似，因为香港的券商接入的是盈透的接口，所以其实也可以理解。 使用前需要安装对应 python 库 pip install futu-api 官方文档有很详细说明如何使用 API，简中文档，同样一看就懂。大陆 IP 的用户可以免费使用 A 股的 L1 数据和港股的 L2 数据。历史数据的额度比老虎那边要大方一点。 用户类型 订阅额度 历史 K 线额度 开户用户 100 100 总资产达 1 万 HKD 300 300 满足任意一条： 1. 总资产达 50 万 HKD； 2. 月交易笔数 \u003e 200； 3. 月交易额 \u003e 200 万 HKD 1000 1000 满足任意一条： 1. 总资产达 500 万 HKD； 2. 月交易笔数 \u003e 2000； 3. 月交易额 \u003e 2000 万 HKD 2000 2000 ","date":"2025-01-24","objectID":"/posts/%E9%87%8F%E5%8C%96%E6%95%B0%E6%8D%AE%E6%BA%90/:1:2","tags":[],"title":"量化数据源","uri":"/posts/%E9%87%8F%E5%8C%96%E6%95%B0%E6%8D%AE%E6%BA%90/#富途-api"},{"categories":["量化交易"],"collections":["量化交易"],"content":"10.3 yfinnance\r因为使用的是公共的 API 接口，访问速率上有一定的限制，频繁访问会获取数据失败，安装方式如下，直接安装就可以用： pip install yfinance ","date":"2025-01-24","objectID":"/posts/%E9%87%8F%E5%8C%96%E6%95%B0%E6%8D%AE%E6%BA%90/:1:3","tags":[],"title":"量化数据源","uri":"/posts/%E9%87%8F%E5%8C%96%E6%95%B0%E6%8D%AE%E6%BA%90/#yfinnance"},{"categories":["量化交易"],"collections":["量化交易"],"content":"10.4 Polygon.io\r相对限制比较多，很多数据都收费，返回格式也比较乱，体验一般。 去官网注册一个账号，就可以获取到 API Key，然后安装 python 库，如下： pip install polygon-api-client 参考文档包括官方Web Api文档和使用 Python Client 的文档 ","date":"2025-01-24","objectID":"/posts/%E9%87%8F%E5%8C%96%E6%95%B0%E6%8D%AE%E6%BA%90/:1:4","tags":[],"title":"量化数据源","uri":"/posts/%E9%87%8F%E5%8C%96%E6%95%B0%E6%8D%AE%E6%BA%90/#polygonio"},{"categories":["量化交易"],"collections":["量化交易"],"content":"10.5 Alpha Vantage\r相对限制比较多，很多数据都收费，返回格式也比较乱，体验一般。 去官网注册一个账号，就可以获取到 API Key pip install alpha_vantage 参考文档包括官方 Web Api 文档和使用 Python 的文档 ","date":"2025-01-24","objectID":"/posts/%E9%87%8F%E5%8C%96%E6%95%B0%E6%8D%AE%E6%BA%90/:1:5","tags":[],"title":"量化数据源","uri":"/posts/%E9%87%8F%E5%8C%96%E6%95%B0%E6%8D%AE%E6%BA%90/#alpha-vantage"},{"categories":["量化交易"],"collections":["量化交易"],"content":"第 11 节 确定股票范围\r主要是一些个人的喜好和倾向，仅供参考。 首先，是只考虑购买美股，不考虑场外交易的类别。 然后，考虑近一个月的平均日成交股数，股数多代表流动性好，股票热门，不容易受到庄家操控。 然后，考虑市值大小，市值太小很可能是毛票，更多是炒作，不适合长期投资。 ","date":"2025-01-24","objectID":"/posts/%E9%87%8F%E5%8C%96%E6%95%B0%E6%8D%AE%E6%BA%90/:2:0","tags":[],"title":"量化数据源","uri":"/posts/%E9%87%8F%E5%8C%96%E6%95%B0%E6%8D%AE%E6%BA%90/#确定股票范围"},{"categories":["量化交易"],"collections":["量化交易"],"content":"11.1 查看全部股票\r可以看到对应股票市场上一共有多少支股票，以及对应名称。 from tigeropen.tiger_open_config import TigerOpenClientConfig from tigeropen.quote.quote_client import QuoteClient from tigeropen.common.consts import Market client_config = TigerOpenClientConfig(props_path='tiger_openapi_config.properties') quote_client = QuoteClient(client_config) symbols = quote_client.get_symbol_names(market=Market.US,include_otc=False) Market 的取值可以参考下表，完整枚举见网址。 标识 市场 ALL 全部 US 美股 HK 港股 CN A股 SG 新加坡 AU 澳大利亚 NZ 新西兰 ","date":"2025-01-24","objectID":"/posts/%E9%87%8F%E5%8C%96%E6%95%B0%E6%8D%AE%E6%BA%90/:2:1","tags":[],"title":"量化数据源","uri":"/posts/%E9%87%8F%E5%8C%96%E6%95%B0%E6%8D%AE%E6%BA%90/#查看全部股票"},{"categories":["量化交易"],"collections":["量化交易"],"content":"11.2 筛选股票\r使用老虎选股器\r按照对应条件筛选出我们需要的股票，比如市值要大于1亿美元，成交量大于1000万股。 import pandas as pd from tigeropen.quote.domain.filter import StockFilter from tigeropen.common.consts.filter_fields import StockField filter1 = StockFilter(StockField.MarketValue,filter_min=100000000) filter2 = StockFilter(StockField.Volume,filter_min=10000000) scanner_result = quote_client.market_scanner(filters=[filter1,filter2], market=Market.US) sr = pd.Series([item.symbol for item in scanner_result.items],name='symbol').sort_values() sr.to_csv('stock.csv',index=False) 筛选的方式包括StockField,AccumulateField,FinancialField,MultiTagField四种： 类型 说明 StockField 简单指标筛选条件，包括价格（高开低收，最新价等），成交量，股本，市值，涨跌幅，市盈率，换手率等因子。 筛选字段说明 AccumulateField 累积指标筛选条件，包括累积涨跌幅，资产增长率，净利润增长率，每股收益，净利润，营业利润，营业收入，ROA（净资产收益率），经营现金流，资产负债率等。累积指标周期可以是：近五分钟，近5日，10日，20日，近半年，一年，两年，五年，一季度报，三季度报，中报等等。筛选字段说明 FinancialField 财务指标筛选条件，包括毛利，净利率，总负债/股东权益，总负债/总资产，流动比率，资产回报率，净利润，经营现金流，总资产，港股通净买入额，年化收益率等等。财务指标目前只支持LTM（最近12个月的年报指标）类型的财报查询。筛选字段说明 MultiTagField 多标签关联关系筛选条件，基于行业，概念，历史股价新高，52周内股价新高，是否为OTC，是否支持期权，股票类型（股票，ETF），是否破发等指标来选股。筛选字段说明 使用富途选股器\r富途提供了一个网页版的选股器，网址是这个，使用起来要比老虎的 API 直观许多，我们先在图形化界面选好我们需要的指标，然后使用一定的爬虫技术，就可以获取到结果了。 首先，我们在图形化界面选择好我们需要的指标，按下回车查询，会有一个名为get-screener-list的Post请求，获取到params如下： params = { 'exchanges': [], 'plates': [], 'values': [ { 'key': 'marketValue', 'value': { 'min': 1, 'max': None } }, { 'key': 'volume-intraday-volumeCumulativeAverage', 'value': { 'period': 30, 'min': 1000, 'max': None } } ], 'isWatch': False, 'market': 2, 'dataFrom': 0, 'dataMaxCount': 50, 'extendsRetrie': True, 'sort': { 'key': 'changeRatio', 'order': 'desc' }, 'accountStatus': False } 信息\r这里的dataMaxCount默认是50，改大一点也是可以的。 然后我们参考这个网址教的爬虫方法，获取到quote-token这个参数，我已经把它写成了 python 的格式了。 import hashlib import hmac import json def get_quote_token(params): t = json.dumps(params,separators=(',', ':')) hmac_result = hmac.new(key=\"quote_web\".encode('utf-8'),msg=t.encode('utf-8'),digestmod=hashlib.sha512).hexdigest() hash_result = hashlib.sha256(hmac_result[:10].encode('utf-8')).hexdigest()[:10] return hash_result 然后调用这个函数，可以获取到quote-token，csrf-token和富途账号相关，每个人不一样，并且唯一。 import requests import pandas as pd from utils import get_quote_token quote_token = get_quote_token(params) csrf_token = 'xxxxx' headers = { 'User-Agent':'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/131.0.0.0 Safari/537.36', 'accept':'application/json, text/plain, */*', 'accept-encoding':'gzip, deflate, br, zstd', 'accept-language':'zh-CN,zh;q=0.9', 'Content-Type':'application/json', 'referer':'https://www.futunn.com/screener', 'origin':'https://www.futunn.com', 'futu-x-csrf-token':csrf_token, 'quote-token':quote_token } cookies = { 'csrfToken':csrf_token } url = 'https://www.futunn.com/quote-api/quote-v2/get-screener-list' response = requests.post(url, headers=headers, json=params,cookies=cookies) if response.status_code == 200: data = response.json() df = pd.DataFrame(data['data']['list']) df.to_csv('stock.csv',index=False) else: print(f\"请求失败: {response.text}\") ","date":"2025-01-24","objectID":"/posts/%E9%87%8F%E5%8C%96%E6%95%B0%E6%8D%AE%E6%BA%90/:2:2","tags":[],"title":"量化数据源","uri":"/posts/%E9%87%8F%E5%8C%96%E6%95%B0%E6%8D%AE%E6%BA%90/#筛选股票"},{"categories":["量化交易"],"collections":["量化交易"],"content":"11.2 筛选股票\r使用老虎选股器\r按照对应条件筛选出我们需要的股票，比如市值要大于1亿美元，成交量大于1000万股。 import pandas as pd from tigeropen.quote.domain.filter import StockFilter from tigeropen.common.consts.filter_fields import StockField filter1 = StockFilter(StockField.MarketValue,filter_min=100000000) filter2 = StockFilter(StockField.Volume,filter_min=10000000) scanner_result = quote_client.market_scanner(filters=[filter1,filter2], market=Market.US) sr = pd.Series([item.symbol for item in scanner_result.items],name='symbol').sort_values() sr.to_csv('stock.csv',index=False) 筛选的方式包括StockField,AccumulateField,FinancialField,MultiTagField四种： 类型 说明 StockField 简单指标筛选条件，包括价格（高开低收，最新价等），成交量，股本，市值，涨跌幅，市盈率，换手率等因子。 筛选字段说明 AccumulateField 累积指标筛选条件，包括累积涨跌幅，资产增长率，净利润增长率，每股收益，净利润，营业利润，营业收入，ROA（净资产收益率），经营现金流，资产负债率等。累积指标周期可以是：近五分钟，近5日，10日，20日，近半年，一年，两年，五年，一季度报，三季度报，中报等等。筛选字段说明 FinancialField 财务指标筛选条件，包括毛利，净利率，总负债/股东权益，总负债/总资产，流动比率，资产回报率，净利润，经营现金流，总资产，港股通净买入额，年化收益率等等。财务指标目前只支持LTM（最近12个月的年报指标）类型的财报查询。筛选字段说明 MultiTagField 多标签关联关系筛选条件，基于行业，概念，历史股价新高，52周内股价新高，是否为OTC，是否支持期权，股票类型（股票，ETF），是否破发等指标来选股。筛选字段说明 使用富途选股器\r富途提供了一个网页版的选股器，网址是这个，使用起来要比老虎的 API 直观许多，我们先在图形化界面选好我们需要的指标，然后使用一定的爬虫技术，就可以获取到结果了。 首先，我们在图形化界面选择好我们需要的指标，按下回车查询，会有一个名为get-screener-list的Post请求，获取到params如下： params = { 'exchanges': [], 'plates': [], 'values': [ { 'key': 'marketValue', 'value': { 'min': 1, 'max': None } }, { 'key': 'volume-intraday-volumeCumulativeAverage', 'value': { 'period': 30, 'min': 1000, 'max': None } } ], 'isWatch': False, 'market': 2, 'dataFrom': 0, 'dataMaxCount': 50, 'extendsRetrie': True, 'sort': { 'key': 'changeRatio', 'order': 'desc' }, 'accountStatus': False } 信息\r这里的dataMaxCount默认是50，改大一点也是可以的。 然后我们参考这个网址教的爬虫方法，获取到quote-token这个参数，我已经把它写成了 python 的格式了。 import hashlib import hmac import json def get_quote_token(params): t = json.dumps(params,separators=(',', ':')) hmac_result = hmac.new(key=\"quote_web\".encode('utf-8'),msg=t.encode('utf-8'),digestmod=hashlib.sha512).hexdigest() hash_result = hashlib.sha256(hmac_result[:10].encode('utf-8')).hexdigest()[:10] return hash_result 然后调用这个函数，可以获取到quote-token，csrf-token和富途账号相关，每个人不一样，并且唯一。 import requests import pandas as pd from utils import get_quote_token quote_token = get_quote_token(params) csrf_token = 'xxxxx' headers = { 'User-Agent':'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/131.0.0.0 Safari/537.36', 'accept':'application/json, text/plain, */*', 'accept-encoding':'gzip, deflate, br, zstd', 'accept-language':'zh-CN,zh;q=0.9', 'Content-Type':'application/json', 'referer':'https://www.futunn.com/screener', 'origin':'https://www.futunn.com', 'futu-x-csrf-token':csrf_token, 'quote-token':quote_token } cookies = { 'csrfToken':csrf_token } url = 'https://www.futunn.com/quote-api/quote-v2/get-screener-list' response = requests.post(url, headers=headers, json=params,cookies=cookies) if response.status_code == 200: data = response.json() df = pd.DataFrame(data['data']['list']) df.to_csv('stock.csv',index=False) else: print(f\"请求失败: {response.text}\") ","date":"2025-01-24","objectID":"/posts/%E9%87%8F%E5%8C%96%E6%95%B0%E6%8D%AE%E6%BA%90/:2:2","tags":[],"title":"量化数据源","uri":"/posts/%E9%87%8F%E5%8C%96%E6%95%B0%E6%8D%AE%E6%BA%90/#使用老虎选股器"},{"categories":["量化交易"],"collections":["量化交易"],"content":"11.2 筛选股票\r使用老虎选股器\r按照对应条件筛选出我们需要的股票，比如市值要大于1亿美元，成交量大于1000万股。 import pandas as pd from tigeropen.quote.domain.filter import StockFilter from tigeropen.common.consts.filter_fields import StockField filter1 = StockFilter(StockField.MarketValue,filter_min=100000000) filter2 = StockFilter(StockField.Volume,filter_min=10000000) scanner_result = quote_client.market_scanner(filters=[filter1,filter2], market=Market.US) sr = pd.Series([item.symbol for item in scanner_result.items],name='symbol').sort_values() sr.to_csv('stock.csv',index=False) 筛选的方式包括StockField,AccumulateField,FinancialField,MultiTagField四种： 类型 说明 StockField 简单指标筛选条件，包括价格（高开低收，最新价等），成交量，股本，市值，涨跌幅，市盈率，换手率等因子。 筛选字段说明 AccumulateField 累积指标筛选条件，包括累积涨跌幅，资产增长率，净利润增长率，每股收益，净利润，营业利润，营业收入，ROA（净资产收益率），经营现金流，资产负债率等。累积指标周期可以是：近五分钟，近5日，10日，20日，近半年，一年，两年，五年，一季度报，三季度报，中报等等。筛选字段说明 FinancialField 财务指标筛选条件，包括毛利，净利率，总负债/股东权益，总负债/总资产，流动比率，资产回报率，净利润，经营现金流，总资产，港股通净买入额，年化收益率等等。财务指标目前只支持LTM（最近12个月的年报指标）类型的财报查询。筛选字段说明 MultiTagField 多标签关联关系筛选条件，基于行业，概念，历史股价新高，52周内股价新高，是否为OTC，是否支持期权，股票类型（股票，ETF），是否破发等指标来选股。筛选字段说明 使用富途选股器\r富途提供了一个网页版的选股器，网址是这个，使用起来要比老虎的 API 直观许多，我们先在图形化界面选好我们需要的指标，然后使用一定的爬虫技术，就可以获取到结果了。 首先，我们在图形化界面选择好我们需要的指标，按下回车查询，会有一个名为get-screener-list的Post请求，获取到params如下： params = { 'exchanges': [], 'plates': [], 'values': [ { 'key': 'marketValue', 'value': { 'min': 1, 'max': None } }, { 'key': 'volume-intraday-volumeCumulativeAverage', 'value': { 'period': 30, 'min': 1000, 'max': None } } ], 'isWatch': False, 'market': 2, 'dataFrom': 0, 'dataMaxCount': 50, 'extendsRetrie': True, 'sort': { 'key': 'changeRatio', 'order': 'desc' }, 'accountStatus': False } 信息\r这里的dataMaxCount默认是50，改大一点也是可以的。 然后我们参考这个网址教的爬虫方法，获取到quote-token这个参数，我已经把它写成了 python 的格式了。 import hashlib import hmac import json def get_quote_token(params): t = json.dumps(params,separators=(',', ':')) hmac_result = hmac.new(key=\"quote_web\".encode('utf-8'),msg=t.encode('utf-8'),digestmod=hashlib.sha512).hexdigest() hash_result = hashlib.sha256(hmac_result[:10].encode('utf-8')).hexdigest()[:10] return hash_result 然后调用这个函数，可以获取到quote-token，csrf-token和富途账号相关，每个人不一样，并且唯一。 import requests import pandas as pd from utils import get_quote_token quote_token = get_quote_token(params) csrf_token = 'xxxxx' headers = { 'User-Agent':'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/131.0.0.0 Safari/537.36', 'accept':'application/json, text/plain, */*', 'accept-encoding':'gzip, deflate, br, zstd', 'accept-language':'zh-CN,zh;q=0.9', 'Content-Type':'application/json', 'referer':'https://www.futunn.com/screener', 'origin':'https://www.futunn.com', 'futu-x-csrf-token':csrf_token, 'quote-token':quote_token } cookies = { 'csrfToken':csrf_token } url = 'https://www.futunn.com/quote-api/quote-v2/get-screener-list' response = requests.post(url, headers=headers, json=params,cookies=cookies) if response.status_code == 200: data = response.json() df = pd.DataFrame(data['data']['list']) df.to_csv('stock.csv',index=False) else: print(f\"请求失败: {response.text}\") ","date":"2025-01-24","objectID":"/posts/%E9%87%8F%E5%8C%96%E6%95%B0%E6%8D%AE%E6%BA%90/:2:2","tags":[],"title":"量化数据源","uri":"/posts/%E9%87%8F%E5%8C%96%E6%95%B0%E6%8D%AE%E6%BA%90/#使用富途选股器"},{"categories":["量化交易"],"collections":["量化交易"],"content":"第 12 节 获取历史数据\r","date":"2025-01-24","objectID":"/posts/%E9%87%8F%E5%8C%96%E6%95%B0%E6%8D%AE%E6%BA%90/:3:0","tags":[],"title":"量化数据源","uri":"/posts/%E9%87%8F%E5%8C%96%E6%95%B0%E6%8D%AE%E6%BA%90/#获取历史数据"},{"categories":["量化交易"],"collections":["量化交易"],"content":"12.1 获取日线数据\r和使用富途选股器时相同，稍作一些修改，stockId从之前的stock.csv中获取，type为 2 代表日线数据： import time params = { \"stockId\": \"xxx\", \"marketType\": \"2\", \"type\": \"2\", \"marketCode\": \"11\", \"instrumentType\": \"3\", \"subInstrumentType\": \"0\", \"_\": str(int(time.time())) } 写一个函数来获取 k 线数据： def get_kline(stock_id): params = { \"stockId\": str(stock_id), \"marketType\": \"2\", \"type\": \"2\", \"marketCode\": \"11\", \"instrumentType\": \"3\", \"subInstrumentType\": \"0\", \"_\": str(int(time.time())) } quote_token = get_quote_token(params) csrf_token = 'xxxxx' headers = { 'User-Agent':'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/131.0.0.0 Safari/537.36', 'accept':'application/json, text/plain, */*', 'accept-encoding':'gzip, deflate, br, zstd', 'accept-language':'zh-CN,zh;q=0.9', 'Content-Type':'application/json', 'origin':'https://www.futunn.com', 'futu-x-csrf-token':csrf_token, 'quote-token':quote_token } cookies = { 'csrfToken':csrf_token } url = 'https://www.futunn.com/quote-api/quote-v2/get-kline' response = requests.get(url, headers=headers, params=params,cookies=cookies) if response.status_code == 200: data = response.json() return data 写一个循环，来获取全部的 k 线数据： import time from sqlalchemy import create_engine engine = create_engine('mysql+pymysql://USERNAME:PASSWORD@HOST:PORT/DATABASE') for idx,row in stock.iterrows(): stock_id = row['stockId'] stock_code = row['stockCode'] print(f'{idx+1}/{len(stock)} {stock_code}') data = get_kline(stock_id) df = pd.DataFrame(data['data']['list']) df = df.drop('lc',axis=1) df['k'] = pd.to_datetime(df['k'],unit='s').dt.tz_localize('UTC').dt.tz_convert('US/Eastern') df['r'] = df['r']*100 df['symbol'] = stock_code df.columns=['datetime','open','close','high','low','volumn','turnover','turnover_rate','close_price_diff','symbol'] begin = datetime.strftime(datetime.now()-timedelta(days=365*2),'%Y-%m-%d %H:%M:%S') result = df[df['datetime']\u003ebegin] result.to_sql('DailyStock', con=engine, if_exists='append', index=False) time.sleep(5) ","date":"2025-01-24","objectID":"/posts/%E9%87%8F%E5%8C%96%E6%95%B0%E6%8D%AE%E6%BA%90/:3:1","tags":[],"title":"量化数据源","uri":"/posts/%E9%87%8F%E5%8C%96%E6%95%B0%E6%8D%AE%E6%BA%90/#获取日线数据"},{"categories":["量化交易"],"collections":["量化交易"],"content":"12.2 获取财报数据\r财报数据每个季度会出一次，同样从富途网页获取。财报的url有两个，一个是公共接口，一个是查询接口，因为查询接口的可定制性更高，可以获取更长时间的财报数据，我们使用查询接口来获取： from datetime import datetime params = { 'fetchType': '2', 'code': 'xxxxx', 'market': 'US', 'quarter': '8', 'date': datetime.strftime(datetime.now(),'%Y-%m-%d'), 'flag': '0', 'size': '12', '_':str(int(time.time())) } 写一个函数来获取财报数据： def get_report(symbol,fetchType): params = { 'fetchType': str(fetchType), 'code': symbol, 'market': 'US', 'quarter': '8', 'date': datetime.strftime(datetime.now(),'%Y-%m-%d'), 'flag': '0', 'size': '12', '_':str(int(time.time())) } quote_token = get_quote_token(params) csrf_token = 'xxxxx' headers = { 'User-Agent':'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/131.0.0.0 Safari/537.36', 'accept':'application/json, text/plain, */*', 'accept-encoding':'gzip, deflate, br, zstd', 'accept-language':'zh-CN,zh;q=0.9', 'Content-Type':'application/json', 'origin':'https://www.futunn.com', 'futu-x-csrf-token':csrf_token, 'quote-token':quote_token } cookies = { 'csrfToken':csrf_token } url = 'https://www.futunn.com/quote-api/quote-v2/get-reports-data' response = requests.get(url, headers=headers, params=params,cookies=cookies) if response.status_code == 200: data = response.json() return data 写一个循环，来获取全部的财报数据： import time from sqlalchemy import create_engine engine = create_engine('mysql+pymysql://USERNAME:PASSWORD@HOST:PORT/DATABASE') for idx,row in stock.iterrows(): stock_code = row['stockCode'] print(f'{idx+1}/{len(stock)} {stock_code}') data = get_report(stock_code,2) df = pd.DataFrame([[row['raw_value'] for row in item ] for item in data['data']['list']['values']],columns=data['data']['list']['keys']) df['symbol'] = stock_code columns = ['symbol','PeriodEndingDate','TotalRevenue','OperatingRevenue','GrossProfit', 'NetIncome','NetIncomeCommonStockholders','BasicEPS','DilutedEPS', 'OperatingIncome','InterestIncome','InterestExpense','TotalExpenses', 'OperatingExpense','ResearchAndDevelopment','DepreciationAmortizationDepletion', 'ProvisionForDoubtfulAccounts','PretaxIncome','NetPremiumsWritten','NetRealizedGainLossOnInvestments', 'TaxProvision','MinorityInterests','SpecialIncomeCharges','CumulativeEffectOfAccountingChange', 'NetIncomeFromTaxLossCarryforward','DividendPerShare','TotalPremiumsEarned','NetInvestmentIncome'] result = df[columns].copy() result['PeriodEndingDate'] = pd.to_datetime(result['PeriodEndingDate'],unit='s').dt.tz_localize('UTC').dt.tz_convert('US/Eastern') begin = datetime.strftime(datetime.now()-timedelta(days=365*2),'%Y-%m-%d') result = result[0:(result['PeriodEndingDate']\u003ebegin).sum()+1].copy() result.to_sql('StockReport', con=engine, if_exists='append', index=False) time.sleep(5) 信息\r财报的指标有很多个，我问过GPT了，这几个指标比较重要。 变量名 中文含义 symbol 股票代码 PeriodEndingDate 财报期结束日期 TotalRevenue 总收入 OperatingRevenue 营业收入 GrossProfit 毛利 GrossProfitMargin 毛利率 NetIncome 净利润 NetIncomeCommonStockholders 普通股股东的净利润 BasicEPS 基本每股收益 DilutedEPS 稀释每股收益 OperatingIncome 营业收入（经营性收入） InterestIncome 利息收入 InterestExpense 利息支出 TotalExpenses 总费用 OperatingExpense 营业费用 ResearchAndDevelopment 研发费用 DepreciationAmortizationDepletion 折旧、摊销和耗竭费用 ProvisionForDoubtfulAccounts 坏账准备 PretaxIncome 税前利润 NetPremiumsWritten 已承保保费净额 NetRealizedGainLossOnInvestments 投资的已实现盈亏 TaxProvision 税收准备金 MinorityInterests 少数股东权益 SpecialIncomeCharges 特殊收入和费用 CumulativeEffectOfAccountingChange 会计政策变更的累计影响 NetIncomeFromTaxLossCarryforward 税收损失结转产生的净利润 DividendPerShare 每股股息 TotalPremiumsEarned 已赚保费总额 NetInvestmentIncome 净投资收入 信息\rfetchType的值可以取1、2、3、4对应财报的4个部分，分别是财务指标、利润表、资产负债表、现金流量表，处理方式和上面相同，修改对应数字即可，至于哪些指标有用，可以问问GPT，毕竟我也不知道。 财务指标表\r变量名 中文含义 说明 ROIC 投入资本回报率 衡量公司通过投资资本获得的回报，越高说明资本利用效率越好。 ROE 净资产收益率 衡量公司股东权益的回报率，反映股东投资的盈利能力。 ROA 总资产收益率 衡量公司资产利用效率，越高表明公司资产利用越有效。 GrossMargin 毛利率 衡量公司销售收入中扣除销售成本后的盈利能力，越高表示盈利能力越强。 OperatingMargin 营业利润率 衡量公司主营业务的盈利能力，不包括非经常性收入和费用。 NetMargin 净利润率 衡量公司每单位销售收入带来的净利润，反映公司整体盈利能力。 EBITDAMargin 息税折旧摊销前利润率 衡量公司运营效率，忽略融资和投资活动带来的影响。 FCFSalesRatio 自由现金流销售比率 衡量每单位销售收入对应的自由现金流，反映公司的现金流状况。 FCFNetIncomeR","date":"2025-01-24","objectID":"/posts/%E9%87%8F%E5%8C%96%E6%95%B0%E6%8D%AE%E6%BA%90/:3:2","tags":[],"title":"量化数据源","uri":"/posts/%E9%87%8F%E5%8C%96%E6%95%B0%E6%8D%AE%E6%BA%90/#获取财报数据"},{"categories":["量化交易"],"collections":["量化交易"],"content":"12.2 获取财报数据\r财报数据每个季度会出一次，同样从富途网页获取。财报的url有两个，一个是公共接口，一个是查询接口，因为查询接口的可定制性更高，可以获取更长时间的财报数据，我们使用查询接口来获取： from datetime import datetime params = { 'fetchType': '2', 'code': 'xxxxx', 'market': 'US', 'quarter': '8', 'date': datetime.strftime(datetime.now(),'%Y-%m-%d'), 'flag': '0', 'size': '12', '_':str(int(time.time())) } 写一个函数来获取财报数据： def get_report(symbol,fetchType): params = { 'fetchType': str(fetchType), 'code': symbol, 'market': 'US', 'quarter': '8', 'date': datetime.strftime(datetime.now(),'%Y-%m-%d'), 'flag': '0', 'size': '12', '_':str(int(time.time())) } quote_token = get_quote_token(params) csrf_token = 'xxxxx' headers = { 'User-Agent':'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/131.0.0.0 Safari/537.36', 'accept':'application/json, text/plain, */*', 'accept-encoding':'gzip, deflate, br, zstd', 'accept-language':'zh-CN,zh;q=0.9', 'Content-Type':'application/json', 'origin':'https://www.futunn.com', 'futu-x-csrf-token':csrf_token, 'quote-token':quote_token } cookies = { 'csrfToken':csrf_token } url = 'https://www.futunn.com/quote-api/quote-v2/get-reports-data' response = requests.get(url, headers=headers, params=params,cookies=cookies) if response.status_code == 200: data = response.json() return data 写一个循环，来获取全部的财报数据： import time from sqlalchemy import create_engine engine = create_engine('mysql+pymysql://USERNAME:PASSWORD@HOST:PORT/DATABASE') for idx,row in stock.iterrows(): stock_code = row['stockCode'] print(f'{idx+1}/{len(stock)} {stock_code}') data = get_report(stock_code,2) df = pd.DataFrame([[row['raw_value'] for row in item ] for item in data['data']['list']['values']],columns=data['data']['list']['keys']) df['symbol'] = stock_code columns = ['symbol','PeriodEndingDate','TotalRevenue','OperatingRevenue','GrossProfit', 'NetIncome','NetIncomeCommonStockholders','BasicEPS','DilutedEPS', 'OperatingIncome','InterestIncome','InterestExpense','TotalExpenses', 'OperatingExpense','ResearchAndDevelopment','DepreciationAmortizationDepletion', 'ProvisionForDoubtfulAccounts','PretaxIncome','NetPremiumsWritten','NetRealizedGainLossOnInvestments', 'TaxProvision','MinorityInterests','SpecialIncomeCharges','CumulativeEffectOfAccountingChange', 'NetIncomeFromTaxLossCarryforward','DividendPerShare','TotalPremiumsEarned','NetInvestmentIncome'] result = df[columns].copy() result['PeriodEndingDate'] = pd.to_datetime(result['PeriodEndingDate'],unit='s').dt.tz_localize('UTC').dt.tz_convert('US/Eastern') begin = datetime.strftime(datetime.now()-timedelta(days=365*2),'%Y-%m-%d') result = result[0:(result['PeriodEndingDate']\u003ebegin).sum()+1].copy() result.to_sql('StockReport', con=engine, if_exists='append', index=False) time.sleep(5) 信息\r财报的指标有很多个，我问过GPT了，这几个指标比较重要。 变量名 中文含义 symbol 股票代码 PeriodEndingDate 财报期结束日期 TotalRevenue 总收入 OperatingRevenue 营业收入 GrossProfit 毛利 GrossProfitMargin 毛利率 NetIncome 净利润 NetIncomeCommonStockholders 普通股股东的净利润 BasicEPS 基本每股收益 DilutedEPS 稀释每股收益 OperatingIncome 营业收入（经营性收入） InterestIncome 利息收入 InterestExpense 利息支出 TotalExpenses 总费用 OperatingExpense 营业费用 ResearchAndDevelopment 研发费用 DepreciationAmortizationDepletion 折旧、摊销和耗竭费用 ProvisionForDoubtfulAccounts 坏账准备 PretaxIncome 税前利润 NetPremiumsWritten 已承保保费净额 NetRealizedGainLossOnInvestments 投资的已实现盈亏 TaxProvision 税收准备金 MinorityInterests 少数股东权益 SpecialIncomeCharges 特殊收入和费用 CumulativeEffectOfAccountingChange 会计政策变更的累计影响 NetIncomeFromTaxLossCarryforward 税收损失结转产生的净利润 DividendPerShare 每股股息 TotalPremiumsEarned 已赚保费总额 NetInvestmentIncome 净投资收入 信息\rfetchType的值可以取1、2、3、4对应财报的4个部分，分别是财务指标、利润表、资产负债表、现金流量表，处理方式和上面相同，修改对应数字即可，至于哪些指标有用，可以问问GPT，毕竟我也不知道。 财务指标表\r变量名 中文含义 说明 ROIC 投入资本回报率 衡量公司通过投资资本获得的回报，越高说明资本利用效率越好。 ROE 净资产收益率 衡量公司股东权益的回报率，反映股东投资的盈利能力。 ROA 总资产收益率 衡量公司资产利用效率，越高表明公司资产利用越有效。 GrossMargin 毛利率 衡量公司销售收入中扣除销售成本后的盈利能力，越高表示盈利能力越强。 OperatingMargin 营业利润率 衡量公司主营业务的盈利能力，不包括非经常性收入和费用。 NetMargin 净利润率 衡量公司每单位销售收入带来的净利润，反映公司整体盈利能力。 EBITDAMargin 息税折旧摊销前利润率 衡量公司运营效率，忽略融资和投资活动带来的影响。 FCFSalesRatio 自由现金流销售比率 衡量每单位销售收入对应的自由现金流，反映公司的现金流状况。 FCFNetIncomeR","date":"2025-01-24","objectID":"/posts/%E9%87%8F%E5%8C%96%E6%95%B0%E6%8D%AE%E6%BA%90/:3:2","tags":[],"title":"量化数据源","uri":"/posts/%E9%87%8F%E5%8C%96%E6%95%B0%E6%8D%AE%E6%BA%90/#财务指标表"},{"categories":["量化交易"],"collections":["量化交易"],"content":"12.2 获取财报数据\r财报数据每个季度会出一次，同样从富途网页获取。财报的url有两个，一个是公共接口，一个是查询接口，因为查询接口的可定制性更高，可以获取更长时间的财报数据，我们使用查询接口来获取： from datetime import datetime params = { 'fetchType': '2', 'code': 'xxxxx', 'market': 'US', 'quarter': '8', 'date': datetime.strftime(datetime.now(),'%Y-%m-%d'), 'flag': '0', 'size': '12', '_':str(int(time.time())) } 写一个函数来获取财报数据： def get_report(symbol,fetchType): params = { 'fetchType': str(fetchType), 'code': symbol, 'market': 'US', 'quarter': '8', 'date': datetime.strftime(datetime.now(),'%Y-%m-%d'), 'flag': '0', 'size': '12', '_':str(int(time.time())) } quote_token = get_quote_token(params) csrf_token = 'xxxxx' headers = { 'User-Agent':'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/131.0.0.0 Safari/537.36', 'accept':'application/json, text/plain, */*', 'accept-encoding':'gzip, deflate, br, zstd', 'accept-language':'zh-CN,zh;q=0.9', 'Content-Type':'application/json', 'origin':'https://www.futunn.com', 'futu-x-csrf-token':csrf_token, 'quote-token':quote_token } cookies = { 'csrfToken':csrf_token } url = 'https://www.futunn.com/quote-api/quote-v2/get-reports-data' response = requests.get(url, headers=headers, params=params,cookies=cookies) if response.status_code == 200: data = response.json() return data 写一个循环，来获取全部的财报数据： import time from sqlalchemy import create_engine engine = create_engine('mysql+pymysql://USERNAME:PASSWORD@HOST:PORT/DATABASE') for idx,row in stock.iterrows(): stock_code = row['stockCode'] print(f'{idx+1}/{len(stock)} {stock_code}') data = get_report(stock_code,2) df = pd.DataFrame([[row['raw_value'] for row in item ] for item in data['data']['list']['values']],columns=data['data']['list']['keys']) df['symbol'] = stock_code columns = ['symbol','PeriodEndingDate','TotalRevenue','OperatingRevenue','GrossProfit', 'NetIncome','NetIncomeCommonStockholders','BasicEPS','DilutedEPS', 'OperatingIncome','InterestIncome','InterestExpense','TotalExpenses', 'OperatingExpense','ResearchAndDevelopment','DepreciationAmortizationDepletion', 'ProvisionForDoubtfulAccounts','PretaxIncome','NetPremiumsWritten','NetRealizedGainLossOnInvestments', 'TaxProvision','MinorityInterests','SpecialIncomeCharges','CumulativeEffectOfAccountingChange', 'NetIncomeFromTaxLossCarryforward','DividendPerShare','TotalPremiumsEarned','NetInvestmentIncome'] result = df[columns].copy() result['PeriodEndingDate'] = pd.to_datetime(result['PeriodEndingDate'],unit='s').dt.tz_localize('UTC').dt.tz_convert('US/Eastern') begin = datetime.strftime(datetime.now()-timedelta(days=365*2),'%Y-%m-%d') result = result[0:(result['PeriodEndingDate']\u003ebegin).sum()+1].copy() result.to_sql('StockReport', con=engine, if_exists='append', index=False) time.sleep(5) 信息\r财报的指标有很多个，我问过GPT了，这几个指标比较重要。 变量名 中文含义 symbol 股票代码 PeriodEndingDate 财报期结束日期 TotalRevenue 总收入 OperatingRevenue 营业收入 GrossProfit 毛利 GrossProfitMargin 毛利率 NetIncome 净利润 NetIncomeCommonStockholders 普通股股东的净利润 BasicEPS 基本每股收益 DilutedEPS 稀释每股收益 OperatingIncome 营业收入（经营性收入） InterestIncome 利息收入 InterestExpense 利息支出 TotalExpenses 总费用 OperatingExpense 营业费用 ResearchAndDevelopment 研发费用 DepreciationAmortizationDepletion 折旧、摊销和耗竭费用 ProvisionForDoubtfulAccounts 坏账准备 PretaxIncome 税前利润 NetPremiumsWritten 已承保保费净额 NetRealizedGainLossOnInvestments 投资的已实现盈亏 TaxProvision 税收准备金 MinorityInterests 少数股东权益 SpecialIncomeCharges 特殊收入和费用 CumulativeEffectOfAccountingChange 会计政策变更的累计影响 NetIncomeFromTaxLossCarryforward 税收损失结转产生的净利润 DividendPerShare 每股股息 TotalPremiumsEarned 已赚保费总额 NetInvestmentIncome 净投资收入 信息\rfetchType的值可以取1、2、3、4对应财报的4个部分，分别是财务指标、利润表、资产负债表、现金流量表，处理方式和上面相同，修改对应数字即可，至于哪些指标有用，可以问问GPT，毕竟我也不知道。 财务指标表\r变量名 中文含义 说明 ROIC 投入资本回报率 衡量公司通过投资资本获得的回报，越高说明资本利用效率越好。 ROE 净资产收益率 衡量公司股东权益的回报率，反映股东投资的盈利能力。 ROA 总资产收益率 衡量公司资产利用效率，越高表明公司资产利用越有效。 GrossMargin 毛利率 衡量公司销售收入中扣除销售成本后的盈利能力，越高表示盈利能力越强。 OperatingMargin 营业利润率 衡量公司主营业务的盈利能力，不包括非经常性收入和费用。 NetMargin 净利润率 衡量公司每单位销售收入带来的净利润，反映公司整体盈利能力。 EBITDAMargin 息税折旧摊销前利润率 衡量公司运营效率，忽略融资和投资活动带来的影响。 FCFSalesRatio 自由现金流销售比率 衡量每单位销售收入对应的自由现金流，反映公司的现金流状况。 FCFNetIncomeR","date":"2025-01-24","objectID":"/posts/%E9%87%8F%E5%8C%96%E6%95%B0%E6%8D%AE%E6%BA%90/:3:2","tags":[],"title":"量化数据源","uri":"/posts/%E9%87%8F%E5%8C%96%E6%95%B0%E6%8D%AE%E6%BA%90/#利润表"},{"categories":["量化交易"],"collections":["量化交易"],"content":"12.2 获取财报数据\r财报数据每个季度会出一次，同样从富途网页获取。财报的url有两个，一个是公共接口，一个是查询接口，因为查询接口的可定制性更高，可以获取更长时间的财报数据，我们使用查询接口来获取： from datetime import datetime params = { 'fetchType': '2', 'code': 'xxxxx', 'market': 'US', 'quarter': '8', 'date': datetime.strftime(datetime.now(),'%Y-%m-%d'), 'flag': '0', 'size': '12', '_':str(int(time.time())) } 写一个函数来获取财报数据： def get_report(symbol,fetchType): params = { 'fetchType': str(fetchType), 'code': symbol, 'market': 'US', 'quarter': '8', 'date': datetime.strftime(datetime.now(),'%Y-%m-%d'), 'flag': '0', 'size': '12', '_':str(int(time.time())) } quote_token = get_quote_token(params) csrf_token = 'xxxxx' headers = { 'User-Agent':'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/131.0.0.0 Safari/537.36', 'accept':'application/json, text/plain, */*', 'accept-encoding':'gzip, deflate, br, zstd', 'accept-language':'zh-CN,zh;q=0.9', 'Content-Type':'application/json', 'origin':'https://www.futunn.com', 'futu-x-csrf-token':csrf_token, 'quote-token':quote_token } cookies = { 'csrfToken':csrf_token } url = 'https://www.futunn.com/quote-api/quote-v2/get-reports-data' response = requests.get(url, headers=headers, params=params,cookies=cookies) if response.status_code == 200: data = response.json() return data 写一个循环，来获取全部的财报数据： import time from sqlalchemy import create_engine engine = create_engine('mysql+pymysql://USERNAME:PASSWORD@HOST:PORT/DATABASE') for idx,row in stock.iterrows(): stock_code = row['stockCode'] print(f'{idx+1}/{len(stock)} {stock_code}') data = get_report(stock_code,2) df = pd.DataFrame([[row['raw_value'] for row in item ] for item in data['data']['list']['values']],columns=data['data']['list']['keys']) df['symbol'] = stock_code columns = ['symbol','PeriodEndingDate','TotalRevenue','OperatingRevenue','GrossProfit', 'NetIncome','NetIncomeCommonStockholders','BasicEPS','DilutedEPS', 'OperatingIncome','InterestIncome','InterestExpense','TotalExpenses', 'OperatingExpense','ResearchAndDevelopment','DepreciationAmortizationDepletion', 'ProvisionForDoubtfulAccounts','PretaxIncome','NetPremiumsWritten','NetRealizedGainLossOnInvestments', 'TaxProvision','MinorityInterests','SpecialIncomeCharges','CumulativeEffectOfAccountingChange', 'NetIncomeFromTaxLossCarryforward','DividendPerShare','TotalPremiumsEarned','NetInvestmentIncome'] result = df[columns].copy() result['PeriodEndingDate'] = pd.to_datetime(result['PeriodEndingDate'],unit='s').dt.tz_localize('UTC').dt.tz_convert('US/Eastern') begin = datetime.strftime(datetime.now()-timedelta(days=365*2),'%Y-%m-%d') result = result[0:(result['PeriodEndingDate']\u003ebegin).sum()+1].copy() result.to_sql('StockReport', con=engine, if_exists='append', index=False) time.sleep(5) 信息\r财报的指标有很多个，我问过GPT了，这几个指标比较重要。 变量名 中文含义 symbol 股票代码 PeriodEndingDate 财报期结束日期 TotalRevenue 总收入 OperatingRevenue 营业收入 GrossProfit 毛利 GrossProfitMargin 毛利率 NetIncome 净利润 NetIncomeCommonStockholders 普通股股东的净利润 BasicEPS 基本每股收益 DilutedEPS 稀释每股收益 OperatingIncome 营业收入（经营性收入） InterestIncome 利息收入 InterestExpense 利息支出 TotalExpenses 总费用 OperatingExpense 营业费用 ResearchAndDevelopment 研发费用 DepreciationAmortizationDepletion 折旧、摊销和耗竭费用 ProvisionForDoubtfulAccounts 坏账准备 PretaxIncome 税前利润 NetPremiumsWritten 已承保保费净额 NetRealizedGainLossOnInvestments 投资的已实现盈亏 TaxProvision 税收准备金 MinorityInterests 少数股东权益 SpecialIncomeCharges 特殊收入和费用 CumulativeEffectOfAccountingChange 会计政策变更的累计影响 NetIncomeFromTaxLossCarryforward 税收损失结转产生的净利润 DividendPerShare 每股股息 TotalPremiumsEarned 已赚保费总额 NetInvestmentIncome 净投资收入 信息\rfetchType的值可以取1、2、3、4对应财报的4个部分，分别是财务指标、利润表、资产负债表、现金流量表，处理方式和上面相同，修改对应数字即可，至于哪些指标有用，可以问问GPT，毕竟我也不知道。 财务指标表\r变量名 中文含义 说明 ROIC 投入资本回报率 衡量公司通过投资资本获得的回报，越高说明资本利用效率越好。 ROE 净资产收益率 衡量公司股东权益的回报率，反映股东投资的盈利能力。 ROA 总资产收益率 衡量公司资产利用效率，越高表明公司资产利用越有效。 GrossMargin 毛利率 衡量公司销售收入中扣除销售成本后的盈利能力，越高表示盈利能力越强。 OperatingMargin 营业利润率 衡量公司主营业务的盈利能力，不包括非经常性收入和费用。 NetMargin 净利润率 衡量公司每单位销售收入带来的净利润，反映公司整体盈利能力。 EBITDAMargin 息税折旧摊销前利润率 衡量公司运营效率，忽略融资和投资活动带来的影响。 FCFSalesRatio 自由现金流销售比率 衡量每单位销售收入对应的自由现金流，反映公司的现金流状况。 FCFNetIncomeR","date":"2025-01-24","objectID":"/posts/%E9%87%8F%E5%8C%96%E6%95%B0%E6%8D%AE%E6%BA%90/:3:2","tags":[],"title":"量化数据源","uri":"/posts/%E9%87%8F%E5%8C%96%E6%95%B0%E6%8D%AE%E6%BA%90/#资产负债表"},{"categories":["量化交易"],"collections":["量化交易"],"content":"12.2 获取财报数据\r财报数据每个季度会出一次，同样从富途网页获取。财报的url有两个，一个是公共接口，一个是查询接口，因为查询接口的可定制性更高，可以获取更长时间的财报数据，我们使用查询接口来获取： from datetime import datetime params = { 'fetchType': '2', 'code': 'xxxxx', 'market': 'US', 'quarter': '8', 'date': datetime.strftime(datetime.now(),'%Y-%m-%d'), 'flag': '0', 'size': '12', '_':str(int(time.time())) } 写一个函数来获取财报数据： def get_report(symbol,fetchType): params = { 'fetchType': str(fetchType), 'code': symbol, 'market': 'US', 'quarter': '8', 'date': datetime.strftime(datetime.now(),'%Y-%m-%d'), 'flag': '0', 'size': '12', '_':str(int(time.time())) } quote_token = get_quote_token(params) csrf_token = 'xxxxx' headers = { 'User-Agent':'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/131.0.0.0 Safari/537.36', 'accept':'application/json, text/plain, */*', 'accept-encoding':'gzip, deflate, br, zstd', 'accept-language':'zh-CN,zh;q=0.9', 'Content-Type':'application/json', 'origin':'https://www.futunn.com', 'futu-x-csrf-token':csrf_token, 'quote-token':quote_token } cookies = { 'csrfToken':csrf_token } url = 'https://www.futunn.com/quote-api/quote-v2/get-reports-data' response = requests.get(url, headers=headers, params=params,cookies=cookies) if response.status_code == 200: data = response.json() return data 写一个循环，来获取全部的财报数据： import time from sqlalchemy import create_engine engine = create_engine('mysql+pymysql://USERNAME:PASSWORD@HOST:PORT/DATABASE') for idx,row in stock.iterrows(): stock_code = row['stockCode'] print(f'{idx+1}/{len(stock)} {stock_code}') data = get_report(stock_code,2) df = pd.DataFrame([[row['raw_value'] for row in item ] for item in data['data']['list']['values']],columns=data['data']['list']['keys']) df['symbol'] = stock_code columns = ['symbol','PeriodEndingDate','TotalRevenue','OperatingRevenue','GrossProfit', 'NetIncome','NetIncomeCommonStockholders','BasicEPS','DilutedEPS', 'OperatingIncome','InterestIncome','InterestExpense','TotalExpenses', 'OperatingExpense','ResearchAndDevelopment','DepreciationAmortizationDepletion', 'ProvisionForDoubtfulAccounts','PretaxIncome','NetPremiumsWritten','NetRealizedGainLossOnInvestments', 'TaxProvision','MinorityInterests','SpecialIncomeCharges','CumulativeEffectOfAccountingChange', 'NetIncomeFromTaxLossCarryforward','DividendPerShare','TotalPremiumsEarned','NetInvestmentIncome'] result = df[columns].copy() result['PeriodEndingDate'] = pd.to_datetime(result['PeriodEndingDate'],unit='s').dt.tz_localize('UTC').dt.tz_convert('US/Eastern') begin = datetime.strftime(datetime.now()-timedelta(days=365*2),'%Y-%m-%d') result = result[0:(result['PeriodEndingDate']\u003ebegin).sum()+1].copy() result.to_sql('StockReport', con=engine, if_exists='append', index=False) time.sleep(5) 信息\r财报的指标有很多个，我问过GPT了，这几个指标比较重要。 变量名 中文含义 symbol 股票代码 PeriodEndingDate 财报期结束日期 TotalRevenue 总收入 OperatingRevenue 营业收入 GrossProfit 毛利 GrossProfitMargin 毛利率 NetIncome 净利润 NetIncomeCommonStockholders 普通股股东的净利润 BasicEPS 基本每股收益 DilutedEPS 稀释每股收益 OperatingIncome 营业收入（经营性收入） InterestIncome 利息收入 InterestExpense 利息支出 TotalExpenses 总费用 OperatingExpense 营业费用 ResearchAndDevelopment 研发费用 DepreciationAmortizationDepletion 折旧、摊销和耗竭费用 ProvisionForDoubtfulAccounts 坏账准备 PretaxIncome 税前利润 NetPremiumsWritten 已承保保费净额 NetRealizedGainLossOnInvestments 投资的已实现盈亏 TaxProvision 税收准备金 MinorityInterests 少数股东权益 SpecialIncomeCharges 特殊收入和费用 CumulativeEffectOfAccountingChange 会计政策变更的累计影响 NetIncomeFromTaxLossCarryforward 税收损失结转产生的净利润 DividendPerShare 每股股息 TotalPremiumsEarned 已赚保费总额 NetInvestmentIncome 净投资收入 信息\rfetchType的值可以取1、2、3、4对应财报的4个部分，分别是财务指标、利润表、资产负债表、现金流量表，处理方式和上面相同，修改对应数字即可，至于哪些指标有用，可以问问GPT，毕竟我也不知道。 财务指标表\r变量名 中文含义 说明 ROIC 投入资本回报率 衡量公司通过投资资本获得的回报，越高说明资本利用效率越好。 ROE 净资产收益率 衡量公司股东权益的回报率，反映股东投资的盈利能力。 ROA 总资产收益率 衡量公司资产利用效率，越高表明公司资产利用越有效。 GrossMargin 毛利率 衡量公司销售收入中扣除销售成本后的盈利能力，越高表示盈利能力越强。 OperatingMargin 营业利润率 衡量公司主营业务的盈利能力，不包括非经常性收入和费用。 NetMargin 净利润率 衡量公司每单位销售收入带来的净利润，反映公司整体盈利能力。 EBITDAMargin 息税折旧摊销前利润率 衡量公司运营效率，忽略融资和投资活动带来的影响。 FCFSalesRatio 自由现金流销售比率 衡量每单位销售收入对应的自由现金流，反映公司的现金流状况。 FCFNetIncomeR","date":"2025-01-24","objectID":"/posts/%E9%87%8F%E5%8C%96%E6%95%B0%E6%8D%AE%E6%BA%90/:3:2","tags":[],"title":"量化数据源","uri":"/posts/%E9%87%8F%E5%8C%96%E6%95%B0%E6%8D%AE%E6%BA%90/#现金流量表"},{"categories":["量化交易"],"collections":["量化交易"],"content":"第 10 节 概述\rIB Gateway 是 Interactive Brokers (IB) 提供的轻量级应用程序，允许用户通过 API 接口与 IB 的交易系统进行连接。与 TWS (Trader Workstation) 相比，IB Gateway 更适合程序化交易，因为它： • 没有 GUI 界面，降低资源占用。 • 支持持续运行，更稳定，适合无人值守的量化策略。 • 提供完整的 API 支持，包括行情数据、下单、账户管理等功能。 IB Gateway 可以通过 IB API（支持多种语言：Python、Java、C++、C# 等） 进行交互，使开发者能够编写自己的量化交易程序。 ","date":"2025-01-13","objectID":"/posts/ib-gateway-api-%E8%87%AA%E5%8A%A8%E4%BA%A4%E6%98%93/:1:0","tags":[],"title":"IB Gateway API 自动交易","uri":"/posts/ib-gateway-api-%E8%87%AA%E5%8A%A8%E4%BA%A4%E6%98%93/#概述"},{"categories":["量化交易"],"collections":["量化交易"],"content":"第 11 节 服务器端\r","date":"2025-01-13","objectID":"/posts/ib-gateway-api-%E8%87%AA%E5%8A%A8%E4%BA%A4%E6%98%93/:2:0","tags":[],"title":"IB Gateway API 自动交易","uri":"/posts/ib-gateway-api-%E8%87%AA%E5%8A%A8%E4%BA%A4%E6%98%93/#服务器端"},{"categories":["量化交易"],"collections":["量化交易"],"content":"11.1 容器部署\r使用这个 Github 项目，因为作者一直在持续更新，且部署起来相对简单。 docker run -itd --name ibkr --restart always \\ -p PORT1:6080 \\ -p PORT2:8888 \\ --ulimit nofile=10000 \\ -e USERNAME=USERNAME \\ -e PASSWORD=PASSWORD \\ -e TWOFA_TIMEOUT_ACTION=restart \\ -e GATEWAY_OR_TWS=gateway \\ -e IBC_TradingMode=paper \\ -e IBC_ReadOnlyApi=no \\ -e TWS_SETTINGS_PATH=/settings \\ -v /PATH/IBKR/settings:/settings:rw \\ ghcr.io/extrange/ibkr:latest 6080端口是 noVNC 的地址，用于访问 IB Gateway GUI 界面 8888端口是 API 的地址，客户端连接该地址访问服务器 GATEWAY_OR_TWS为了轻量选gateway TWOFA_TIMEOUT_ACTION表示timeout自动重启 IBC_TradingMode表示部署在测试环境，上线时改成live IBC_ReadOnlyApi表示可以下单，而非只读 ","date":"2025-01-13","objectID":"/posts/ib-gateway-api-%E8%87%AA%E5%8A%A8%E4%BA%A4%E6%98%93/:2:1","tags":[],"title":"IB Gateway API 自动交易","uri":"/posts/ib-gateway-api-%E8%87%AA%E5%8A%A8%E4%BA%A4%E6%98%93/#容器部署"},{"categories":["量化交易"],"collections":["量化交易"],"content":"11.2 容器设置\rConfiguration -\u003e Lock and Exit 为了安全考虑，IB Gateway 需要设置自动退出或者自动重启，设置在中午的 12:15 比较不错。 Configuration -\u003e API -\u003e Settings 取消只读，这样才可以下单交易 Configuration -\u003e API -\u003e Precautions 取消预防措施，下单不会因为预防措施而被忽略 ","date":"2025-01-13","objectID":"/posts/ib-gateway-api-%E8%87%AA%E5%8A%A8%E4%BA%A4%E6%98%93/:2:2","tags":[],"title":"IB Gateway API 自动交易","uri":"/posts/ib-gateway-api-%E8%87%AA%E5%8A%A8%E4%BA%A4%E6%98%93/#容器设置"},{"categories":["量化交易"],"collections":["量化交易"],"content":"第 12 节 客户端\rIB API 最初基于 Java 开发，由于 IB 的核心交易系统是用 Java 编写的，所以官方 API 最早是为 Java 提供的支持，后来才逐渐扩展到其他语言。 尽管 IB 提供了多种语言的 API，但因为 API 的核心逻辑和结构源于 Java 实现，所以其他语言版本的 API 在设计和使用上受到 Java 风格的影响，尤其是 Python 版本，经常被开发者吐槽“难用”。 所以就出现了基于 IB API 包装下的第三方实现，最初的项目名为 ib_insync，由于原作者去世，其他继任者维护的项目变更为 ib_async，两者的使用方法相同，文档也可以互相参考。 ","date":"2025-01-13","objectID":"/posts/ib-gateway-api-%E8%87%AA%E5%8A%A8%E4%BA%A4%E6%98%93/:3:0","tags":[],"title":"IB Gateway API 自动交易","uri":"/posts/ib-gateway-api-%E8%87%AA%E5%8A%A8%E4%BA%A4%E6%98%93/#客户端"},{"categories":["量化交易"],"collections":["量化交易"],"content":"12.1 安装\rpip install ib_async ","date":"2025-01-13","objectID":"/posts/ib-gateway-api-%E8%87%AA%E5%8A%A8%E4%BA%A4%E6%98%93/:3:1","tags":[],"title":"IB Gateway API 自动交易","uri":"/posts/ib-gateway-api-%E8%87%AA%E5%8A%A8%E4%BA%A4%E6%98%93/#安装"},{"categories":["量化交易"],"collections":["量化交易"],"content":"12.2 连接服务器\rfrom ib_async import * # 使用notebook 或者 jypyterlab 时 startLoop() util.startLoop() ib = IB() ib.connect('192.168.0.103', 7751, clientId=0) # 最后断开连接 ib.disconnect() ","date":"2025-01-13","objectID":"/posts/ib-gateway-api-%E8%87%AA%E5%8A%A8%E4%BA%A4%E6%98%93/:3:2","tags":[],"title":"IB Gateway API 自动交易","uri":"/posts/ib-gateway-api-%E8%87%AA%E5%8A%A8%E4%BA%A4%E6%98%93/#连接服务器"},{"categories":["量化交易"],"collections":["量化交易"],"content":"12.3 账户信息\r持仓信息\r# 获取账户持仓 positions = ib.positions() # 输出持仓信息 for position in positions: print(position) 比如，可能显示以下信息： # 持有 100股 苹果 (AAPL)，每股成本 145 美元 Position( contract=Stock( symbol='AAPL', exchange='SMART', currency='USD' ), position=100, avgCost=145.0 ) 交易信息\r# 获取已成交的交易记录 trades = ib.trades() # 输出交易记录 for trade in trades: print(trade) # 获取未平仓的交易 open_trades = ib.openTrades() # 输出未平仓交易 for trade in open_trades: print(trade) 比如，可能显示以下信息： # 以每股150美元限价单购买了200股苹果 (AAPL) Trade( order=Order( id=12347, action='BUY', totalQuantity=200, lmtPrice=150.0 ), contract=Stock( symbol='AAPL', exchange='SMART', currency='USD' ), price=150.0, qty=200, time='2025-01-13 10:30:00' ) 资金状态\r# 获取账户资金状态 account_values = ib.accountValues() # 输出账户资金信息 for value in account_values: print(value) 比如，可能显示以下信息： # 账户现金余额为50,000美元 AccountValue( account='DU123456', tag='CashBalance', value='50000.0', currency='USD' ) Tag 描述 CashBalance 现金余额 AvailableFunds 可用资金 BuyingPower 购买力 NetLiquidation 净清算值 RealizedPnL 已实现盈亏 UnrealizedPnL 未实现盈亏 使用现金，没有融资\r$现金余额 = 可用资金$ 购买力无参考意义 使用保证金，融资\r$可用资金 = 现金余额+融资额度-已用保证金$ $购买力=可用资金\\times融资倍数$ ","date":"2025-01-13","objectID":"/posts/ib-gateway-api-%E8%87%AA%E5%8A%A8%E4%BA%A4%E6%98%93/:3:3","tags":[],"title":"IB Gateway API 自动交易","uri":"/posts/ib-gateway-api-%E8%87%AA%E5%8A%A8%E4%BA%A4%E6%98%93/#账户信息"},{"categories":["量化交易"],"collections":["量化交易"],"content":"12.3 账户信息\r持仓信息\r# 获取账户持仓 positions = ib.positions() # 输出持仓信息 for position in positions: print(position) 比如，可能显示以下信息： # 持有 100股 苹果 (AAPL)，每股成本 145 美元 Position( contract=Stock( symbol='AAPL', exchange='SMART', currency='USD' ), position=100, avgCost=145.0 ) 交易信息\r# 获取已成交的交易记录 trades = ib.trades() # 输出交易记录 for trade in trades: print(trade) # 获取未平仓的交易 open_trades = ib.openTrades() # 输出未平仓交易 for trade in open_trades: print(trade) 比如，可能显示以下信息： # 以每股150美元限价单购买了200股苹果 (AAPL) Trade( order=Order( id=12347, action='BUY', totalQuantity=200, lmtPrice=150.0 ), contract=Stock( symbol='AAPL', exchange='SMART', currency='USD' ), price=150.0, qty=200, time='2025-01-13 10:30:00' ) 资金状态\r# 获取账户资金状态 account_values = ib.accountValues() # 输出账户资金信息 for value in account_values: print(value) 比如，可能显示以下信息： # 账户现金余额为50,000美元 AccountValue( account='DU123456', tag='CashBalance', value='50000.0', currency='USD' ) Tag 描述 CashBalance 现金余额 AvailableFunds 可用资金 BuyingPower 购买力 NetLiquidation 净清算值 RealizedPnL 已实现盈亏 UnrealizedPnL 未实现盈亏 使用现金，没有融资\r$现金余额 = 可用资金$ 购买力无参考意义 使用保证金，融资\r$可用资金 = 现金余额+融资额度-已用保证金$ $购买力=可用资金\\times融资倍数$ ","date":"2025-01-13","objectID":"/posts/ib-gateway-api-%E8%87%AA%E5%8A%A8%E4%BA%A4%E6%98%93/:3:3","tags":[],"title":"IB Gateway API 自动交易","uri":"/posts/ib-gateway-api-%E8%87%AA%E5%8A%A8%E4%BA%A4%E6%98%93/#持仓信息"},{"categories":["量化交易"],"collections":["量化交易"],"content":"12.3 账户信息\r持仓信息\r# 获取账户持仓 positions = ib.positions() # 输出持仓信息 for position in positions: print(position) 比如，可能显示以下信息： # 持有 100股 苹果 (AAPL)，每股成本 145 美元 Position( contract=Stock( symbol='AAPL', exchange='SMART', currency='USD' ), position=100, avgCost=145.0 ) 交易信息\r# 获取已成交的交易记录 trades = ib.trades() # 输出交易记录 for trade in trades: print(trade) # 获取未平仓的交易 open_trades = ib.openTrades() # 输出未平仓交易 for trade in open_trades: print(trade) 比如，可能显示以下信息： # 以每股150美元限价单购买了200股苹果 (AAPL) Trade( order=Order( id=12347, action='BUY', totalQuantity=200, lmtPrice=150.0 ), contract=Stock( symbol='AAPL', exchange='SMART', currency='USD' ), price=150.0, qty=200, time='2025-01-13 10:30:00' ) 资金状态\r# 获取账户资金状态 account_values = ib.accountValues() # 输出账户资金信息 for value in account_values: print(value) 比如，可能显示以下信息： # 账户现金余额为50,000美元 AccountValue( account='DU123456', tag='CashBalance', value='50000.0', currency='USD' ) Tag 描述 CashBalance 现金余额 AvailableFunds 可用资金 BuyingPower 购买力 NetLiquidation 净清算值 RealizedPnL 已实现盈亏 UnrealizedPnL 未实现盈亏 使用现金，没有融资\r$现金余额 = 可用资金$ 购买力无参考意义 使用保证金，融资\r$可用资金 = 现金余额+融资额度-已用保证金$ $购买力=可用资金\\times融资倍数$ ","date":"2025-01-13","objectID":"/posts/ib-gateway-api-%E8%87%AA%E5%8A%A8%E4%BA%A4%E6%98%93/:3:3","tags":[],"title":"IB Gateway API 自动交易","uri":"/posts/ib-gateway-api-%E8%87%AA%E5%8A%A8%E4%BA%A4%E6%98%93/#交易信息"},{"categories":["量化交易"],"collections":["量化交易"],"content":"12.3 账户信息\r持仓信息\r# 获取账户持仓 positions = ib.positions() # 输出持仓信息 for position in positions: print(position) 比如，可能显示以下信息： # 持有 100股 苹果 (AAPL)，每股成本 145 美元 Position( contract=Stock( symbol='AAPL', exchange='SMART', currency='USD' ), position=100, avgCost=145.0 ) 交易信息\r# 获取已成交的交易记录 trades = ib.trades() # 输出交易记录 for trade in trades: print(trade) # 获取未平仓的交易 open_trades = ib.openTrades() # 输出未平仓交易 for trade in open_trades: print(trade) 比如，可能显示以下信息： # 以每股150美元限价单购买了200股苹果 (AAPL) Trade( order=Order( id=12347, action='BUY', totalQuantity=200, lmtPrice=150.0 ), contract=Stock( symbol='AAPL', exchange='SMART', currency='USD' ), price=150.0, qty=200, time='2025-01-13 10:30:00' ) 资金状态\r# 获取账户资金状态 account_values = ib.accountValues() # 输出账户资金信息 for value in account_values: print(value) 比如，可能显示以下信息： # 账户现金余额为50,000美元 AccountValue( account='DU123456', tag='CashBalance', value='50000.0', currency='USD' ) Tag 描述 CashBalance 现金余额 AvailableFunds 可用资金 BuyingPower 购买力 NetLiquidation 净清算值 RealizedPnL 已实现盈亏 UnrealizedPnL 未实现盈亏 使用现金，没有融资\r$现金余额 = 可用资金$ 购买力无参考意义 使用保证金，融资\r$可用资金 = 现金余额+融资额度-已用保证金$ $购买力=可用资金\\times融资倍数$ ","date":"2025-01-13","objectID":"/posts/ib-gateway-api-%E8%87%AA%E5%8A%A8%E4%BA%A4%E6%98%93/:3:3","tags":[],"title":"IB Gateway API 自动交易","uri":"/posts/ib-gateway-api-%E8%87%AA%E5%8A%A8%E4%BA%A4%E6%98%93/#资金状态"},{"categories":["量化交易"],"collections":["量化交易"],"content":"12.3 账户信息\r持仓信息\r# 获取账户持仓 positions = ib.positions() # 输出持仓信息 for position in positions: print(position) 比如，可能显示以下信息： # 持有 100股 苹果 (AAPL)，每股成本 145 美元 Position( contract=Stock( symbol='AAPL', exchange='SMART', currency='USD' ), position=100, avgCost=145.0 ) 交易信息\r# 获取已成交的交易记录 trades = ib.trades() # 输出交易记录 for trade in trades: print(trade) # 获取未平仓的交易 open_trades = ib.openTrades() # 输出未平仓交易 for trade in open_trades: print(trade) 比如，可能显示以下信息： # 以每股150美元限价单购买了200股苹果 (AAPL) Trade( order=Order( id=12347, action='BUY', totalQuantity=200, lmtPrice=150.0 ), contract=Stock( symbol='AAPL', exchange='SMART', currency='USD' ), price=150.0, qty=200, time='2025-01-13 10:30:00' ) 资金状态\r# 获取账户资金状态 account_values = ib.accountValues() # 输出账户资金信息 for value in account_values: print(value) 比如，可能显示以下信息： # 账户现金余额为50,000美元 AccountValue( account='DU123456', tag='CashBalance', value='50000.0', currency='USD' ) Tag 描述 CashBalance 现金余额 AvailableFunds 可用资金 BuyingPower 购买力 NetLiquidation 净清算值 RealizedPnL 已实现盈亏 UnrealizedPnL 未实现盈亏 使用现金，没有融资\r$现金余额 = 可用资金$ 购买力无参考意义 使用保证金，融资\r$可用资金 = 现金余额+融资额度-已用保证金$ $购买力=可用资金\\times融资倍数$ ","date":"2025-01-13","objectID":"/posts/ib-gateway-api-%E8%87%AA%E5%8A%A8%E4%BA%A4%E6%98%93/:3:3","tags":[],"title":"IB Gateway API 自动交易","uri":"/posts/ib-gateway-api-%E8%87%AA%E5%8A%A8%E4%BA%A4%E6%98%93/#使用现金没有融资"},{"categories":["量化交易"],"collections":["量化交易"],"content":"12.3 账户信息\r持仓信息\r# 获取账户持仓 positions = ib.positions() # 输出持仓信息 for position in positions: print(position) 比如，可能显示以下信息： # 持有 100股 苹果 (AAPL)，每股成本 145 美元 Position( contract=Stock( symbol='AAPL', exchange='SMART', currency='USD' ), position=100, avgCost=145.0 ) 交易信息\r# 获取已成交的交易记录 trades = ib.trades() # 输出交易记录 for trade in trades: print(trade) # 获取未平仓的交易 open_trades = ib.openTrades() # 输出未平仓交易 for trade in open_trades: print(trade) 比如，可能显示以下信息： # 以每股150美元限价单购买了200股苹果 (AAPL) Trade( order=Order( id=12347, action='BUY', totalQuantity=200, lmtPrice=150.0 ), contract=Stock( symbol='AAPL', exchange='SMART', currency='USD' ), price=150.0, qty=200, time='2025-01-13 10:30:00' ) 资金状态\r# 获取账户资金状态 account_values = ib.accountValues() # 输出账户资金信息 for value in account_values: print(value) 比如，可能显示以下信息： # 账户现金余额为50,000美元 AccountValue( account='DU123456', tag='CashBalance', value='50000.0', currency='USD' ) Tag 描述 CashBalance 现金余额 AvailableFunds 可用资金 BuyingPower 购买力 NetLiquidation 净清算值 RealizedPnL 已实现盈亏 UnrealizedPnL 未实现盈亏 使用现金，没有融资\r$现金余额 = 可用资金$ 购买力无参考意义 使用保证金，融资\r$可用资金 = 现金余额+融资额度-已用保证金$ $购买力=可用资金\\times融资倍数$ ","date":"2025-01-13","objectID":"/posts/ib-gateway-api-%E8%87%AA%E5%8A%A8%E4%BA%A4%E6%98%93/:3:3","tags":[],"title":"IB Gateway API 自动交易","uri":"/posts/ib-gateway-api-%E8%87%AA%E5%8A%A8%E4%BA%A4%E6%98%93/#使用保证金融资"},{"categories":["量化交易"],"collections":["量化交易"],"content":"12.4 创建交易\r创建合约\r# 创建一个股票合约 contract = Stock( symbol='AAPL', exchange='SMART', currency='USD' ) # 创建一个期权合约 contract = Option( symbol='AAPL', lastTradeDateOrContractMonth='20250119', strike=150, right='C', exchange='SMART', currency='USD' ) 还有其他合约，比如期货（Futures）、外汇（Forex）、债券（Bond）、指数（Index）等。 创建订单\r# 市价单 order = MarketOrder('BUY', 100) # 以市场价买入100股 # 限价单 order = LimitOrder('BUY', 100, 150) # 以150美元或更低价格买入100股 # 止损单 order = StopOrder('SELL', 100, 140) # 当价格达到140美元时卖出100股 # 限价止损 order = StopLimitOrder('SELL', 100, 140, 139) # 当价格达到140时卖出100股，且不低于139美元 # 区间订单 order = BracketOrder('BUY', 100, 150, 155, 145) # 以150美元买入100股，止盈155，止损145 创建交易\rtrade = ib.placeOrder(contract, order) # 取消交易 ib.cancelOrder(order) ","date":"2025-01-13","objectID":"/posts/ib-gateway-api-%E8%87%AA%E5%8A%A8%E4%BA%A4%E6%98%93/:3:4","tags":[],"title":"IB Gateway API 自动交易","uri":"/posts/ib-gateway-api-%E8%87%AA%E5%8A%A8%E4%BA%A4%E6%98%93/#创建交易"},{"categories":["量化交易"],"collections":["量化交易"],"content":"12.4 创建交易\r创建合约\r# 创建一个股票合约 contract = Stock( symbol='AAPL', exchange='SMART', currency='USD' ) # 创建一个期权合约 contract = Option( symbol='AAPL', lastTradeDateOrContractMonth='20250119', strike=150, right='C', exchange='SMART', currency='USD' ) 还有其他合约，比如期货（Futures）、外汇（Forex）、债券（Bond）、指数（Index）等。 创建订单\r# 市价单 order = MarketOrder('BUY', 100) # 以市场价买入100股 # 限价单 order = LimitOrder('BUY', 100, 150) # 以150美元或更低价格买入100股 # 止损单 order = StopOrder('SELL', 100, 140) # 当价格达到140美元时卖出100股 # 限价止损 order = StopLimitOrder('SELL', 100, 140, 139) # 当价格达到140时卖出100股，且不低于139美元 # 区间订单 order = BracketOrder('BUY', 100, 150, 155, 145) # 以150美元买入100股，止盈155，止损145 创建交易\rtrade = ib.placeOrder(contract, order) # 取消交易 ib.cancelOrder(order) ","date":"2025-01-13","objectID":"/posts/ib-gateway-api-%E8%87%AA%E5%8A%A8%E4%BA%A4%E6%98%93/:3:4","tags":[],"title":"IB Gateway API 自动交易","uri":"/posts/ib-gateway-api-%E8%87%AA%E5%8A%A8%E4%BA%A4%E6%98%93/#创建合约"},{"categories":["量化交易"],"collections":["量化交易"],"content":"12.4 创建交易\r创建合约\r# 创建一个股票合约 contract = Stock( symbol='AAPL', exchange='SMART', currency='USD' ) # 创建一个期权合约 contract = Option( symbol='AAPL', lastTradeDateOrContractMonth='20250119', strike=150, right='C', exchange='SMART', currency='USD' ) 还有其他合约，比如期货（Futures）、外汇（Forex）、债券（Bond）、指数（Index）等。 创建订单\r# 市价单 order = MarketOrder('BUY', 100) # 以市场价买入100股 # 限价单 order = LimitOrder('BUY', 100, 150) # 以150美元或更低价格买入100股 # 止损单 order = StopOrder('SELL', 100, 140) # 当价格达到140美元时卖出100股 # 限价止损 order = StopLimitOrder('SELL', 100, 140, 139) # 当价格达到140时卖出100股，且不低于139美元 # 区间订单 order = BracketOrder('BUY', 100, 150, 155, 145) # 以150美元买入100股，止盈155，止损145 创建交易\rtrade = ib.placeOrder(contract, order) # 取消交易 ib.cancelOrder(order) ","date":"2025-01-13","objectID":"/posts/ib-gateway-api-%E8%87%AA%E5%8A%A8%E4%BA%A4%E6%98%93/:3:4","tags":[],"title":"IB Gateway API 自动交易","uri":"/posts/ib-gateway-api-%E8%87%AA%E5%8A%A8%E4%BA%A4%E6%98%93/#创建订单"},{"categories":["量化交易"],"collections":["量化交易"],"content":"12.4 创建交易\r创建合约\r# 创建一个股票合约 contract = Stock( symbol='AAPL', exchange='SMART', currency='USD' ) # 创建一个期权合约 contract = Option( symbol='AAPL', lastTradeDateOrContractMonth='20250119', strike=150, right='C', exchange='SMART', currency='USD' ) 还有其他合约，比如期货（Futures）、外汇（Forex）、债券（Bond）、指数（Index）等。 创建订单\r# 市价单 order = MarketOrder('BUY', 100) # 以市场价买入100股 # 限价单 order = LimitOrder('BUY', 100, 150) # 以150美元或更低价格买入100股 # 止损单 order = StopOrder('SELL', 100, 140) # 当价格达到140美元时卖出100股 # 限价止损 order = StopLimitOrder('SELL', 100, 140, 139) # 当价格达到140时卖出100股，且不低于139美元 # 区间订单 order = BracketOrder('BUY', 100, 150, 155, 145) # 以150美元买入100股，止盈155，止损145 创建交易\rtrade = ib.placeOrder(contract, order) # 取消交易 ib.cancelOrder(order) ","date":"2025-01-13","objectID":"/posts/ib-gateway-api-%E8%87%AA%E5%8A%A8%E4%BA%A4%E6%98%93/:3:4","tags":[],"title":"IB Gateway API 自动交易","uri":"/posts/ib-gateway-api-%E8%87%AA%E5%8A%A8%E4%BA%A4%E6%98%93/#创建交易-1"},{"categories":["量化交易"],"collections":["量化交易"],"content":"12.5 历史数据\rbars = ib.reqHistoricalData( contract, # 合约对象 endDateTime='', # 结束时间 durationStr='60 D', # 请求过去60天的历史数据 barSizeSetting='1 hour',# 每个数据点代表1小时 whatToShow='TRADES', # 请求成交数据 useRTH=True, # 仅包含常规交易时段的数据 formatDate=1 # 返回日期为字符串格式 ) endDateTime格式为2025-01-13 16:00:00，缺失表示当前时刻 durationStr单位有D天、W周、M月、Y年 barSizeSetting单位有min(s)、hour(s)、day(s) whatToShow可选值： TRADES成交价 MIDPOINT中间价 BID买价 ASK卖价 ADJUSTED_LAST调整后的成交价，前复权价格 useRTH：True 为常规交易时间段，False 为全天数据 formatDate：1表示2025-01-13 16:00:00这种字符串格式，2表示时间戳 信息\rbarSizeSetting设置成1 sec时，可以获取历史 Tick 数据。因为个人难以训练足够庞大的模型对 Tick 数据进行处理并预测，因此不讨论 Tick 数据。 ","date":"2025-01-13","objectID":"/posts/ib-gateway-api-%E8%87%AA%E5%8A%A8%E4%BA%A4%E6%98%93/:3:5","tags":[],"title":"IB Gateway API 自动交易","uri":"/posts/ib-gateway-api-%E8%87%AA%E5%8A%A8%E4%BA%A4%E6%98%93/#历史数据"},{"categories":["量化交易"],"collections":["量化交易"],"content":"12.6 热门股票\r获取股票\r# 创建扫描器订阅：筛选涨幅较大和成交量较大的股票 scanner = ScannerSubscription( numberOfRows=10, # 获取前10个股票 instrument='STK', # 股票 locationCode='STK.US.MAJOR', # 美国股票市场 scanCode='TOP_PERC_GAIN', # 漲幅最大 abovePrice=5, # 股票价格大于5美元 belowPrice=500, # 股票价格小于500美元 marketCapAbove=1000000000, # 市值大于10亿 averageOptionVolumeAbove=100 # 平均期权成交量大于100 ) # 执行扫描 scan_data = ib.reqScannerData(scanner) # 提取扫描结果中的股票信息 stocks = [] for item in scan_data: stock_info = { 'Symbol': item.contract.symbol, 'Name': item.contract.localSymbol, 'Price': item.marketPrice, 'Change Percent': item.changePercent, 'Volume': item.volume } stocks.append(stock_info) # 将数据转换为 DataFrame df = pd.DataFrame(stocks) # 输出数据 print(df) 绘制热力图\rimport seaborn as sns import matplotlib.pyplot as plt # 选择需要展示的列：涨幅和成交量 df_heatmap = df[['Symbol', 'Change Percent', 'Volume']] # 设置画布大小 plt.figure(figsize=(10, 8)) # 使用 Seaborn 绘制热力图，设置颜色映射和注释显示数据 heatmap_data = df_heatmap.set_index('Symbol').T sns.heatmap(heatmap_data, annot=True, cmap='YlGnBu', fmt='.2f', linewidths=0.5) # 设置标题 plt.title('Heatmap of Stock Change Percent and Volume', fontsize=16) # 显示图形 plt.show() ","date":"2025-01-13","objectID":"/posts/ib-gateway-api-%E8%87%AA%E5%8A%A8%E4%BA%A4%E6%98%93/:3:6","tags":[],"title":"IB Gateway API 自动交易","uri":"/posts/ib-gateway-api-%E8%87%AA%E5%8A%A8%E4%BA%A4%E6%98%93/#热门股票"},{"categories":["量化交易"],"collections":["量化交易"],"content":"12.6 热门股票\r获取股票\r# 创建扫描器订阅：筛选涨幅较大和成交量较大的股票 scanner = ScannerSubscription( numberOfRows=10, # 获取前10个股票 instrument='STK', # 股票 locationCode='STK.US.MAJOR', # 美国股票市场 scanCode='TOP_PERC_GAIN', # 漲幅最大 abovePrice=5, # 股票价格大于5美元 belowPrice=500, # 股票价格小于500美元 marketCapAbove=1000000000, # 市值大于10亿 averageOptionVolumeAbove=100 # 平均期权成交量大于100 ) # 执行扫描 scan_data = ib.reqScannerData(scanner) # 提取扫描结果中的股票信息 stocks = [] for item in scan_data: stock_info = { 'Symbol': item.contract.symbol, 'Name': item.contract.localSymbol, 'Price': item.marketPrice, 'Change Percent': item.changePercent, 'Volume': item.volume } stocks.append(stock_info) # 将数据转换为 DataFrame df = pd.DataFrame(stocks) # 输出数据 print(df) 绘制热力图\rimport seaborn as sns import matplotlib.pyplot as plt # 选择需要展示的列：涨幅和成交量 df_heatmap = df[['Symbol', 'Change Percent', 'Volume']] # 设置画布大小 plt.figure(figsize=(10, 8)) # 使用 Seaborn 绘制热力图，设置颜色映射和注释显示数据 heatmap_data = df_heatmap.set_index('Symbol').T sns.heatmap(heatmap_data, annot=True, cmap='YlGnBu', fmt='.2f', linewidths=0.5) # 设置标题 plt.title('Heatmap of Stock Change Percent and Volume', fontsize=16) # 显示图形 plt.show() ","date":"2025-01-13","objectID":"/posts/ib-gateway-api-%E8%87%AA%E5%8A%A8%E4%BA%A4%E6%98%93/:3:6","tags":[],"title":"IB Gateway API 自动交易","uri":"/posts/ib-gateway-api-%E8%87%AA%E5%8A%A8%E4%BA%A4%E6%98%93/#获取股票"},{"categories":["量化交易"],"collections":["量化交易"],"content":"12.6 热门股票\r获取股票\r# 创建扫描器订阅：筛选涨幅较大和成交量较大的股票 scanner = ScannerSubscription( numberOfRows=10, # 获取前10个股票 instrument='STK', # 股票 locationCode='STK.US.MAJOR', # 美国股票市场 scanCode='TOP_PERC_GAIN', # 漲幅最大 abovePrice=5, # 股票价格大于5美元 belowPrice=500, # 股票价格小于500美元 marketCapAbove=1000000000, # 市值大于10亿 averageOptionVolumeAbove=100 # 平均期权成交量大于100 ) # 执行扫描 scan_data = ib.reqScannerData(scanner) # 提取扫描结果中的股票信息 stocks = [] for item in scan_data: stock_info = { 'Symbol': item.contract.symbol, 'Name': item.contract.localSymbol, 'Price': item.marketPrice, 'Change Percent': item.changePercent, 'Volume': item.volume } stocks.append(stock_info) # 将数据转换为 DataFrame df = pd.DataFrame(stocks) # 输出数据 print(df) 绘制热力图\rimport seaborn as sns import matplotlib.pyplot as plt # 选择需要展示的列：涨幅和成交量 df_heatmap = df[['Symbol', 'Change Percent', 'Volume']] # 设置画布大小 plt.figure(figsize=(10, 8)) # 使用 Seaborn 绘制热力图，设置颜色映射和注释显示数据 heatmap_data = df_heatmap.set_index('Symbol').T sns.heatmap(heatmap_data, annot=True, cmap='YlGnBu', fmt='.2f', linewidths=0.5) # 设置标题 plt.title('Heatmap of Stock Change Percent and Volume', fontsize=16) # 显示图形 plt.show() ","date":"2025-01-13","objectID":"/posts/ib-gateway-api-%E8%87%AA%E5%8A%A8%E4%BA%A4%E6%98%93/:3:6","tags":[],"title":"IB Gateway API 自动交易","uri":"/posts/ib-gateway-api-%E8%87%AA%E5%8A%A8%E4%BA%A4%E6%98%93/#绘制热力图"},{"categories":["玩转NAS"],"collections":["玩转NAS"],"content":"第 10 节 概述\rBI（Business Intelligence）商业智能软件是一类旨在帮助企业从大量数据中提取、分析、可视化、报告和洞察商业信息的工具。它们通过提供可视化的报表、仪表盘、数据分析和决策支持，帮助企业管理者和决策者做出数据驱动的决策。 数据集成与连接 能够连接各种数据源，形成一个统一的数据池。 数据分析与挖掘 能对数据进行各种分析，如趋势分析、聚类分析、预测分析等。 数据可视化 提供图表、仪表盘、地图、报表等多种可视化方式，使数据更加易于理解和呈现。 ","date":"2025-01-12","objectID":"/posts/metabase-%E6%95%B0%E6%8D%AE%E5%8F%AF%E8%A7%86%E5%8C%96/:1:0","tags":["可视化"],"title":"MetaBase 数据可视化","uri":"/posts/metabase-%E6%95%B0%E6%8D%AE%E5%8F%AF%E8%A7%86%E5%8C%96/#概述"},{"categories":["玩转NAS"],"collections":["玩转NAS"],"content":"10.1 常见的开源 BI 软件\rSuperset 主要以桌面端为主，移动端访问体验不佳，基于 Flask 和 Python，相对较重。 Redash 支持响应式设计，基于 Python 和 Flask，相比 Superset 要轻量一些。 Metabase 提供较好的移动端支持，基于 Java，相对轻量。 信息\r本人使用下来，SuperSet 的移动端体验不太好，安装的过程中还遇到了一些坑。从 Redash 的官网来看，UI 的设计不是很美观。MetaBase 整体使用下来，UI 设计较为可以，移动端体验还行，对中文的支持还可以。于是就选择使用 MetaBase 作为 NAS 上配置的 BI 软件，用于数据展示。 ","date":"2025-01-12","objectID":"/posts/metabase-%E6%95%B0%E6%8D%AE%E5%8F%AF%E8%A7%86%E5%8C%96/:1:1","tags":["可视化"],"title":"MetaBase 数据可视化","uri":"/posts/metabase-%E6%95%B0%E6%8D%AE%E5%8F%AF%E8%A7%86%E5%8C%96/#常见的开源-bi-软件"},{"categories":["玩转NAS"],"collections":["玩转NAS"],"content":"第 11 节 软件部署\r","date":"2025-01-12","objectID":"/posts/metabase-%E6%95%B0%E6%8D%AE%E5%8F%AF%E8%A7%86%E5%8C%96/:2:0","tags":["可视化"],"title":"MetaBase 数据可视化","uri":"/posts/metabase-%E6%95%B0%E6%8D%AE%E5%8F%AF%E8%A7%86%E5%8C%96/#软件部署"},{"categories":["玩转NAS"],"collections":["玩转NAS"],"content":"11.1 安装容器\rdocker run -itd --name MetaBase --restart always \\ -p PORT:3000 \\ -e MB_DB_TYPE=mysql \\ -e MB_DB_DBNAME=metabase \\ -e MB_DB_HOST=HOST \\ -e MB_DB_PORT=PORT2 \\ -e MB_DB_USER=USER \\ -e MB_DB_PASS=PASSWORD \\ metabase/metabase:latest 信息\r软件部署时，可以通过-e来指定数据库，用于存放初始化数据，不指定时，会使用 H2 数据库，存放到容器内部。 第一次登录时，需要设置用户名、邮箱和密码，也可以连接数据库，一步步设置完即可。 ","date":"2025-01-12","objectID":"/posts/metabase-%E6%95%B0%E6%8D%AE%E5%8F%AF%E8%A7%86%E5%8C%96/:2:1","tags":["可视化"],"title":"MetaBase 数据可视化","uri":"/posts/metabase-%E6%95%B0%E6%8D%AE%E5%8F%AF%E8%A7%86%E5%8C%96/#安装容器"},{"categories":["玩转NAS"],"collections":["玩转NAS"],"content":"11.2 设置管理后台\r设置 -\u003e 通用 这里可以设置网站的 URL，需要公网访问时，需要设置。 默认的表名和字段名的显示会被修改，可以在这里取消，进行 SQL 查询的时候不变 设置 -\u003e 本土化 可以设置日期、时间、货币等的显示方式。 设置 -\u003e 公共分享 这里可以管理哪些内容可以公开访问。 ","date":"2025-01-12","objectID":"/posts/metabase-%E6%95%B0%E6%8D%AE%E5%8F%AF%E8%A7%86%E5%8C%96/:2:2","tags":["可视化"],"title":"MetaBase 数据可视化","uri":"/posts/metabase-%E6%95%B0%E6%8D%AE%E5%8F%AF%E8%A7%86%E5%8C%96/#设置管理后台"},{"categories":["玩转NAS"],"collections":["玩转NAS"],"content":"第 12 节 创建页面\r可以创建的内容如上所示： 问题 / SQL 查询是最基本的分析单元，问题是以图形界面构建 SQL 查询，而 SQL 查询是直接以 SQL 语句构建查询。获取数据后，绑定相应可视化方法，生成图表。 仪表板(Dashboards) 由一系列的问题 / SQL 查询组成，仪表板上有多个图表。 集合(Collections) 用来存放仪表板和问题 / SQL 查询 模型(Models) 将数据库中的表、视图或查询转换为业务层面的逻辑模型，用于简化数据的使用。 公制 (Metrics) 关键的业务指标，用于跟踪和呈现重要的统计数据，如销售额、用户增长等。 ","date":"2025-01-12","objectID":"/posts/metabase-%E6%95%B0%E6%8D%AE%E5%8F%AF%E8%A7%86%E5%8C%96/:3:0","tags":["可视化"],"title":"MetaBase 数据可视化","uri":"/posts/metabase-%E6%95%B0%E6%8D%AE%E5%8F%AF%E8%A7%86%E5%8C%96/#创建页面"},{"categories":["玩转NAS"],"collections":["玩转NAS"],"content":"12.1 问题 / SQL 查询\r问题的展开界面如下，可以看出，这就是图形化的 SQL 查询： 对于 SQL 查询比较熟悉，直接使用 SQL 查询可以更快实现更加复杂的查询。比如，进行如下查询： SELECT datetime, MAX(cyclemax) AS cyclemax FROM GIL_FXHLZ WHERE datetime \u003e= CURDATE() - INTERVAL 7 DAY GROUP BY datetime; 获取数据： 选择可视化，并且双击对应图表： 可以根据个人喜好进行一定的设置： 警告\r这里对于数据格式，有一定的要求。字符型（离散），数值型（连续）只能填写在特定的位置。关系型数据库因为有数据类型，因而支持较好。不建议使用非关系型数据库。 ","date":"2025-01-12","objectID":"/posts/metabase-%E6%95%B0%E6%8D%AE%E5%8F%AF%E8%A7%86%E5%8C%96/:3:1","tags":["可视化"],"title":"MetaBase 数据可视化","uri":"/posts/metabase-%E6%95%B0%E6%8D%AE%E5%8F%AF%E8%A7%86%E5%8C%96/#问题--sql-查询"},{"categories":["玩转NAS"],"collections":["玩转NAS"],"content":"12.2 仪表板\r仪表板横向上被分成了24个小格，纵向上不限，图表在横纵方向上可以自定义占用多少小格。 仪表板可以创建多个标签页，比如图中的最大值和有效值。有一些预设排版样式，可供参考，通常自己拖拽问题/SQL查询组件到相应的位置即可。 仪表板可以被分享为公共链接： 公共链接可以直接发送给他人查看，也可以以\u003ciframe\u003e格式镶嵌在其他网页中，比如使用这篇文章所说的 WxPusher 消息推送推送到微信。 iframe = \"\"\" \u003ciframe src=\"https://bi.papergate.top:5000/public/dashboard/xxxxx\" frameborder=\"0\" style=\"width: 100%; height: 950px;\" allowtransparency\u003e \u003c/iframe\u003e \"\"\" send_wxpusher(summary='title', content=iframe) ","date":"2025-01-12","objectID":"/posts/metabase-%E6%95%B0%E6%8D%AE%E5%8F%AF%E8%A7%86%E5%8C%96/:3:2","tags":["可视化"],"title":"MetaBase 数据可视化","uri":"/posts/metabase-%E6%95%B0%E6%8D%AE%E5%8F%AF%E8%A7%86%E5%8C%96/#仪表板"},{"categories":["玩转NAS"],"collections":["玩转NAS"],"content":"第 7 节 概述\r问题\r如何尽可能简单的说服其他人给他推送消息？ 首先，不能安装额外的App，最好就通过微信来推送 其次，操作步骤要足够的无脑，最好扫个码就一键搞定 然后，下班就不能被打扰，最好能够设置消息免打扰 最后，推送的内容要一眼能看懂，最好要比自己去获取消息轻松一万倍 WxPusher (微信推送服务)是一个使用微信公众号作为通道的，实时信息推送平台，你可以通过调用API的方式，把信息推送到微信上，无需安装额外的软件，即可做到信息实时通知。 你可以使用WxPusher来做服务器报警通知、抢课通知、抢票通知，信息更新提示等。 限制说明： WxPusher 会保留 7 天的数据 ，7 天以后不再提供可靠性保证，会不定时清理历史消息 单条消息的数据长度(字符数)限制是： content\u003c40000 summary\u003c20 url\u003c400 单条消息最大发送 UID\u003c2000 ，单条消息最大发送 topicIds\u003c5 单个微信用户，每天最多接收 2000 条消息 最大 QPS 不能超过 2，比如最多连续 10 秒调用 20 次发送接口，超过这个限制会被系统拦截。 以上都是官网对于这款产品的介绍。当然，这款产品真正吸引我的是，我觉得我终于可以简单的说服其他人给他推送消息了。 失败\r我尝试过通过 Web 协议登录微信去推送消息，比如这款项目，很快微信便告知我这是违规的，并且还警告要封我号。 失败\r我尝试过通过企业微信的机器人去推送消息，发现对方也必须去下载一个企业微信，并且和我在同一个内部群才可以使用。 失败\r我思考过能不能让别人下载一个 App，然后通过 App 推送给他们，可是转念一想，安装教程是我来写，搞不好安装还是我来装。 失败\r我查看过各大短信服务商的报价，发现发短信通知并不经济。而且，我明明是消息提供者，居然还要自费推送，简直岂有此理。 ","date":"2025-01-12","objectID":"/posts/wxpusher-%E6%B6%88%E6%81%AF%E6%8E%A8%E9%80%81/:1:0","tags":["消息推送"],"title":"WxPusher 消息推送","uri":"/posts/wxpusher-%E6%B6%88%E6%81%AF%E6%8E%A8%E9%80%81/#概述"},{"categories":["玩转NAS"],"collections":["玩转NAS"],"content":"第 8 节 WxPusher\r","date":"2025-01-12","objectID":"/posts/wxpusher-%E6%B6%88%E6%81%AF%E6%8E%A8%E9%80%81/:2:0","tags":["消息推送"],"title":"WxPusher 消息推送","uri":"/posts/wxpusher-%E6%B6%88%E6%81%AF%E6%8E%A8%E9%80%81/#wxpusher"},{"categories":["玩转NAS"],"collections":["玩转NAS"],"content":"8.1 登录管理后台\r访问网址，使用微信扫描二维码，新用户首次扫码自动注册，进入管理后台。 ","date":"2025-01-12","objectID":"/posts/wxpusher-%E6%B6%88%E6%81%AF%E6%8E%A8%E9%80%81/:2:1","tags":["消息推送"],"title":"WxPusher 消息推送","uri":"/posts/wxpusher-%E6%B6%88%E6%81%AF%E6%8E%A8%E9%80%81/#登录管理后台"},{"categories":["玩转NAS"],"collections":["玩转NAS"],"content":"8.2 创建新应用\r把必要内容填写即可，选填内容用处不是特别大。 回调地址：可以不填写，不填写用户关注的时候，就不会有回调，你不能拿到用户的 UID，参考回调说明。 设置地址：可以不填写，填写以后，用户在微信端打开「我的订阅」，可以直接跳转到这个地址，并且会携带 UID 作为参数，方便做定制化页面展示。 关注提示：用户关注或者扫应用码的时候发送给用户的提示，你可以不填写，WxPusher 会提供一个默认文案。你也可以在用户关注回调给你 UID 的时候，再主动推送一个提示消息给用户。 信息\r创建应用以后会得到一个 AppToken ，复制下来备用。 ","date":"2025-01-12","objectID":"/posts/wxpusher-%E6%B6%88%E6%81%AF%E6%8E%A8%E9%80%81/:2:2","tags":["消息推送"],"title":"WxPusher 消息推送","uri":"/posts/wxpusher-%E6%B6%88%E6%81%AF%E6%8E%A8%E9%80%81/#创建新应用"},{"categories":["玩转NAS"],"collections":["玩转NAS"],"content":"8.3 创建主题\r创建完应用之后，就可以实现单独发送了，但是为了实现群发，需要创建一个主题。 信息\r创建完主题以后会得到一个 TopicId，当然，这个也需要复制下来备用。 ","date":"2025-01-12","objectID":"/posts/wxpusher-%E6%B6%88%E6%81%AF%E6%8E%A8%E9%80%81/:2:3","tags":["消息推送"],"title":"WxPusher 消息推送","uri":"/posts/wxpusher-%E6%B6%88%E6%81%AF%E6%8E%A8%E9%80%81/#创建主题"},{"categories":["玩转NAS"],"collections":["玩转NAS"],"content":"8.4 发送订阅\r有两种方式，扫二维码和链接，发送给其他人，让他订阅。 ","date":"2025-01-12","objectID":"/posts/wxpusher-%E6%B6%88%E6%81%AF%E6%8E%A8%E9%80%81/:2:4","tags":["消息推送"],"title":"WxPusher 消息推送","uri":"/posts/wxpusher-%E6%B6%88%E6%81%AF%E6%8E%A8%E9%80%81/#发送订阅"},{"categories":["玩转NAS"],"collections":["玩转NAS"],"content":"8.5 推送消息\r虽然官方提供的 API 还有其他功能，但是大部分时候，能够推送消息就足够了。推送消息的方式是通过 POST 请求发送 JSON 数据到官方 API 服务器即可。 topicIds和uids至少一个，可以填两个，填写的topicIds群发，填写的uids单发。 contentType1：表示文字，2：表示html，3：表示markdown summary表示推送的标题，可选 url表示原文链接，可选 verifyPayType是否验证订阅时间，0：不验证，1：只发送给付费的用户，2：只发送给未订阅或者订阅过期的用户 技巧\r一般contentType=2就可以，功能最为强大，比如可以放置\u003ciframe\u003e引入其他页面的内容。 使用python实现一下数据推送： import requests APP_TOKEN = ‘xxxxx’ def send_wxpusher(summary, content): headers = { 'Content-Type': 'application/json' } data = { \"appToken\": APP_TOKEN, \"summary\": summary, \"content\": content, \"contentType\": 2, \"topicIds\": [ xxxxx ], \"verifyPayType\": 0 } response = requests.post(url=f\"https://wxpusher.zjiecode.com/api/send/message\", headers=headers,json=data) ","date":"2025-01-12","objectID":"/posts/wxpusher-%E6%B6%88%E6%81%AF%E6%8E%A8%E9%80%81/:2:5","tags":["消息推送"],"title":"WxPusher 消息推送","uri":"/posts/wxpusher-%E6%B6%88%E6%81%AF%E6%8E%A8%E9%80%81/#推送消息"},{"categories":["玩转NAS"],"collections":["玩转NAS"],"content":"第 7 节 概述\rWireGuard 可以创建点对点连接，让客户端在外网访问Nas的内网应用，那么我们把思路逆转过来，Nas 的内网当然也可以获取客户端的访问流量。 摘要\r本文主要介绍如何通过 MitmProxy 配合WireGuard 抓取客户端的访问流量，并以解析某考试App为例，说明如何使用 python 程序解析流量，并修改返回结果。 ","date":"2025-01-01","objectID":"/posts/%E9%80%86%E8%BD%ACmitmproxy--wireguard-%E4%B8%AD%E9%97%B4%E4%BA%BA%E4%BB%A3%E7%90%86%E6%8A%93%E5%8C%85/:1:0","tags":["网络代理"],"title":"逆转！MitmProxy + WireGuard 中间人代理抓包","uri":"/posts/%E9%80%86%E8%BD%ACmitmproxy--wireguard-%E4%B8%AD%E9%97%B4%E4%BA%BA%E4%BB%A3%E7%90%86%E6%8A%93%E5%8C%85/#概述"},{"categories":["玩转NAS"],"collections":["玩转NAS"],"content":"第 8 节 MitmProxy\rMitmProxy 是一个开源的中间人代理（Man-in-the-Middle Proxy），用于 HTTP 和 HTTPS 流量的捕获、修改和分析。它允许开发者、测试人员或安全专家查看、修改并调试客户端与服务器之间的通信。常见的用途包括： 流量拦截和分析：可以监控和分析通过代理的 HTTP/HTTPS 请求和响应。 修改流量：实时修改请求或响应内容，例如修改响应的 JSON 数据，或者拦截请求以模拟不同的条件。 调试和测试：对 API 请求和 Web 应用进行调试，捕捉和测试不同的网络请求。 SSL/TLS解密：MitmProxy 能通过生成自己的 SSL 证书来解密 HTTPS 流量，提供清晰的内容查看。 它支持命令行界面（CLI）、Web界面和 Python 脚本接口，可以高度自定义操作和扩展功能。 当然，也有一些功能比较类似的软件，比如 BurpSuite、Fiddler、TcpDump 等。 BurpSuite 和 Fiddler 桌面端更为常见，很少在 Docker 上面部署 TcpDump 在Linux 更为常见，可以在 Docker 上面部署，但是不支持 SSL/TLS 解密，所以获取到的数据包都是加密的 MitmProxy 这一类的软件直接部署在公网上面其实并不安全，因为其他人也可以通过 MitmProxy 发送流量来攻击设备，所以将 MitmProxy 和 WireGuard 配合使用，部署在内网中是比较合适的。 MitmProxy 原生就支持 WireGuard 模式，并且已经实现了对 WireGuard 协议流量的解析，部署起来相当方便。 ","date":"2025-01-01","objectID":"/posts/%E9%80%86%E8%BD%ACmitmproxy--wireguard-%E4%B8%AD%E9%97%B4%E4%BA%BA%E4%BB%A3%E7%90%86%E6%8A%93%E5%8C%85/:2:0","tags":["网络代理"],"title":"逆转！MitmProxy + WireGuard 中间人代理抓包","uri":"/posts/%E9%80%86%E8%BD%ACmitmproxy--wireguard-%E4%B8%AD%E9%97%B4%E4%BA%BA%E4%BB%A3%E7%90%86%E6%8A%93%E5%8C%85/#mitmproxy"},{"categories":["玩转NAS"],"collections":["玩转NAS"],"content":"8.1 部署 Docker 镜像\rdocker run -itd --name mitmproxy --restart always \\ -v /PATH/mitmproxy/mitmproxy:/home/mitmproxy/.mitmproxy \\ -v /PATH/mitmproxy/scripts:/scripts \\ -p PORT1:8081 \\ -p PORT2:51820/udp \\ mitmproxy/mitmproxy \\ mitmweb \\ --web-host 0.0.0.0 \\ --mode wireguard \\ -s /scripts/main.py MitmProxy 有三种工具，详见网页，我这里使用了 mitmweb，使用mitweb时，需要映射8081端口用来访问 Web 界面。 mitmproxy（命令行模式）提供了一个交互式的命令行界面（CLI）。它允许用户实时查看、修改、重放 HTTP/HTTPS 流量。 mitmweb（Web 界面模式）Web 界面版本，提供了一个基于浏览器的图形化界面来查看和修改流量。 mitmdump（批量模式）类似于 TcpDump，它以非交互式方式捕获和处理流量，并将数据输出到命令行或保存到文件中。 MitmProxy 有几种运行模式，详见网页，我这里使用的是 WireGuard 模式，使用该模式时，需要映射51820/udp端口用来代理，并且映射的端口要能够公网访问。使用其他模式时，使用的是8080端口作为代理。 在此模式下，MitmProxy 运行内部 WireGuard 服务器，设备可以使用标准 WireGuard 客户端应用程序连接到该服务器。 信息\r这里的main.py是需要自己编写的，详见下文。 ","date":"2025-01-01","objectID":"/posts/%E9%80%86%E8%BD%ACmitmproxy--wireguard-%E4%B8%AD%E9%97%B4%E4%BA%BA%E4%BB%A3%E7%90%86%E6%8A%93%E5%8C%85/:2:1","tags":["网络代理"],"title":"逆转！MitmProxy + WireGuard 中间人代理抓包","uri":"/posts/%E9%80%86%E8%BD%ACmitmproxy--wireguard-%E4%B8%AD%E9%97%B4%E4%BA%BA%E4%BB%A3%E7%90%86%E6%8A%93%E5%8C%85/#部署-docker-镜像"},{"categories":["玩转NAS"],"collections":["玩转NAS"],"content":"8.2 连接到服务器\r使用浏览器访问mitmweb界面，可以在Capture页面找到二维码，扫码即可添加连接 在容器的/home/mitmproxy/.mitmproxy文件夹下也能找到wireguard.conf记录了客户端的公钥和私钥，已经在构建容器时映射到外部了。 ","date":"2025-01-01","objectID":"/posts/%E9%80%86%E8%BD%ACmitmproxy--wireguard-%E4%B8%AD%E9%97%B4%E4%BA%BA%E4%BB%A3%E7%90%86%E6%8A%93%E5%8C%85/:2:2","tags":["网络代理"],"title":"逆转！MitmProxy + WireGuard 中间人代理抓包","uri":"/posts/%E9%80%86%E8%BD%ACmitmproxy--wireguard-%E4%B8%AD%E9%97%B4%E4%BA%BA%E4%BB%A3%E7%90%86%E6%8A%93%E5%8C%85/#连接到服务器"},{"categories":["玩转NAS"],"collections":["玩转NAS"],"content":"8.3 安装证书\r手机连接 MitmProxy 服务器后访问页面，选择合适的证书下载并安装 http://mitm.it 通用 -\u003e VPN与设备管理 -\u003e mitmproxy 通用 -\u003e 关于本机 -\u003e 证书信任设置 -\u003e 针对根证书启用完全信任 ","date":"2025-01-01","objectID":"/posts/%E9%80%86%E8%BD%ACmitmproxy--wireguard-%E4%B8%AD%E9%97%B4%E4%BA%BA%E4%BB%A3%E7%90%86%E6%8A%93%E5%8C%85/:2:3","tags":["网络代理"],"title":"逆转！MitmProxy + WireGuard 中间人代理抓包","uri":"/posts/%E9%80%86%E8%BD%ACmitmproxy--wireguard-%E4%B8%AD%E9%97%B4%E4%BA%BA%E4%BB%A3%E7%90%86%E6%8A%93%E5%8C%85/#安装证书"},{"categories":["玩转NAS"],"collections":["玩转NAS"],"content":"8.4 编写处理程序\r以实现解析某考试 App 为例，讲述如何编写程序，我们的目标是获取到该 App 返回的题目数据，并且把正确的选项内容改成选我，错误的选项内容改成- 获取数据格式\r先获取数据格式，可以看到，返回的数据是 Json 格式 分析数据，选项主要存储在OPTION_CONTENT和QUESTION_OPTIONS_CONTENT 编写程序\r随着不断使用，解析程序会越来越多，为了保证可扩展性，使用addons来编写程序 编写exam.py\rfrom mitmproxy import http import json class Exam: def response(self,flow) -\u003e None: # 检查请求的 URL 是否包含 'getMockQuestionList' if 'getMockQuestionList' in flow.request.url and flow.response.headers.get(\"content-type\", \"\").startswith(\"application/json\"): try: # 解析 JSON 数据 data = json.loads(flow.response.content) # 修改每道题目的选项内容 if \"data\" in data and \"question\" in data[\"data\"]: for question in data[\"data\"][\"question\"]: right_answer = question.get(\"RIGHT_ANSWERS\", \"\") option_content = question.get(\"OPTION_CONTENT\", \"\") options = option_content.split(\"|\") # 创建一个新的选项内容列表 modified_options = [] modified_content = [] for label in ['A','B','C','D','E','F','G','H'][0:len(options)]: if label in right_answer: modified_options.append(f'{label}、选我') modified_content.append('选我') else: modified_options.append(f'{label}、-') modified_content.append('-') # 更新 QUESTION_OPTIONS_CONTENT 为修改后的选项 question[\"OPTION_CONTENT\"] = \"|\".join(modified_options) question[\"QUESTION_OPTIONS_CONTENT\"] = \"|\".join(modified_content) # 将修改后的数据重新赋值给响应内容 flow.response.content = json.dumps(data, ensure_ascii=False).encode(\"utf-8\") except Exception as e: print(f\"Error processing response: {e}\") addons = [ Exam() ] 扩充main.py\rfrom exam import Exam addons = [ Exam() ] 运行结果\r正确答案一目了然，轻松获得了 100 分。 ","date":"2025-01-01","objectID":"/posts/%E9%80%86%E8%BD%ACmitmproxy--wireguard-%E4%B8%AD%E9%97%B4%E4%BA%BA%E4%BB%A3%E7%90%86%E6%8A%93%E5%8C%85/:2:4","tags":["网络代理"],"title":"逆转！MitmProxy + WireGuard 中间人代理抓包","uri":"/posts/%E9%80%86%E8%BD%ACmitmproxy--wireguard-%E4%B8%AD%E9%97%B4%E4%BA%BA%E4%BB%A3%E7%90%86%E6%8A%93%E5%8C%85/#编写处理程序"},{"categories":["玩转NAS"],"collections":["玩转NAS"],"content":"8.4 编写处理程序\r以实现解析某考试 App 为例，讲述如何编写程序，我们的目标是获取到该 App 返回的题目数据，并且把正确的选项内容改成选我，错误的选项内容改成- 获取数据格式\r先获取数据格式，可以看到，返回的数据是 Json 格式 分析数据，选项主要存储在OPTION_CONTENT和QUESTION_OPTIONS_CONTENT 编写程序\r随着不断使用，解析程序会越来越多，为了保证可扩展性，使用addons来编写程序 编写exam.py\rfrom mitmproxy import http import json class Exam: def response(self,flow) -\u003e None: # 检查请求的 URL 是否包含 'getMockQuestionList' if 'getMockQuestionList' in flow.request.url and flow.response.headers.get(\"content-type\", \"\").startswith(\"application/json\"): try: # 解析 JSON 数据 data = json.loads(flow.response.content) # 修改每道题目的选项内容 if \"data\" in data and \"question\" in data[\"data\"]: for question in data[\"data\"][\"question\"]: right_answer = question.get(\"RIGHT_ANSWERS\", \"\") option_content = question.get(\"OPTION_CONTENT\", \"\") options = option_content.split(\"|\") # 创建一个新的选项内容列表 modified_options = [] modified_content = [] for label in ['A','B','C','D','E','F','G','H'][0:len(options)]: if label in right_answer: modified_options.append(f'{label}、选我') modified_content.append('选我') else: modified_options.append(f'{label}、-') modified_content.append('-') # 更新 QUESTION_OPTIONS_CONTENT 为修改后的选项 question[\"OPTION_CONTENT\"] = \"|\".join(modified_options) question[\"QUESTION_OPTIONS_CONTENT\"] = \"|\".join(modified_content) # 将修改后的数据重新赋值给响应内容 flow.response.content = json.dumps(data, ensure_ascii=False).encode(\"utf-8\") except Exception as e: print(f\"Error processing response: {e}\") addons = [ Exam() ] 扩充main.py\rfrom exam import Exam addons = [ Exam() ] 运行结果\r正确答案一目了然，轻松获得了 100 分。 ","date":"2025-01-01","objectID":"/posts/%E9%80%86%E8%BD%ACmitmproxy--wireguard-%E4%B8%AD%E9%97%B4%E4%BA%BA%E4%BB%A3%E7%90%86%E6%8A%93%E5%8C%85/:2:4","tags":["网络代理"],"title":"逆转！MitmProxy + WireGuard 中间人代理抓包","uri":"/posts/%E9%80%86%E8%BD%ACmitmproxy--wireguard-%E4%B8%AD%E9%97%B4%E4%BA%BA%E4%BB%A3%E7%90%86%E6%8A%93%E5%8C%85/#获取数据格式"},{"categories":["玩转NAS"],"collections":["玩转NAS"],"content":"8.4 编写处理程序\r以实现解析某考试 App 为例，讲述如何编写程序，我们的目标是获取到该 App 返回的题目数据，并且把正确的选项内容改成选我，错误的选项内容改成- 获取数据格式\r先获取数据格式，可以看到，返回的数据是 Json 格式 分析数据，选项主要存储在OPTION_CONTENT和QUESTION_OPTIONS_CONTENT 编写程序\r随着不断使用，解析程序会越来越多，为了保证可扩展性，使用addons来编写程序 编写exam.py\rfrom mitmproxy import http import json class Exam: def response(self,flow) -\u003e None: # 检查请求的 URL 是否包含 'getMockQuestionList' if 'getMockQuestionList' in flow.request.url and flow.response.headers.get(\"content-type\", \"\").startswith(\"application/json\"): try: # 解析 JSON 数据 data = json.loads(flow.response.content) # 修改每道题目的选项内容 if \"data\" in data and \"question\" in data[\"data\"]: for question in data[\"data\"][\"question\"]: right_answer = question.get(\"RIGHT_ANSWERS\", \"\") option_content = question.get(\"OPTION_CONTENT\", \"\") options = option_content.split(\"|\") # 创建一个新的选项内容列表 modified_options = [] modified_content = [] for label in ['A','B','C','D','E','F','G','H'][0:len(options)]: if label in right_answer: modified_options.append(f'{label}、选我') modified_content.append('选我') else: modified_options.append(f'{label}、-') modified_content.append('-') # 更新 QUESTION_OPTIONS_CONTENT 为修改后的选项 question[\"OPTION_CONTENT\"] = \"|\".join(modified_options) question[\"QUESTION_OPTIONS_CONTENT\"] = \"|\".join(modified_content) # 将修改后的数据重新赋值给响应内容 flow.response.content = json.dumps(data, ensure_ascii=False).encode(\"utf-8\") except Exception as e: print(f\"Error processing response: {e}\") addons = [ Exam() ] 扩充main.py\rfrom exam import Exam addons = [ Exam() ] 运行结果\r正确答案一目了然，轻松获得了 100 分。 ","date":"2025-01-01","objectID":"/posts/%E9%80%86%E8%BD%ACmitmproxy--wireguard-%E4%B8%AD%E9%97%B4%E4%BA%BA%E4%BB%A3%E7%90%86%E6%8A%93%E5%8C%85/:2:4","tags":["网络代理"],"title":"逆转！MitmProxy + WireGuard 中间人代理抓包","uri":"/posts/%E9%80%86%E8%BD%ACmitmproxy--wireguard-%E4%B8%AD%E9%97%B4%E4%BA%BA%E4%BB%A3%E7%90%86%E6%8A%93%E5%8C%85/#编写程序"},{"categories":["玩转NAS"],"collections":["玩转NAS"],"content":"8.4 编写处理程序\r以实现解析某考试 App 为例，讲述如何编写程序，我们的目标是获取到该 App 返回的题目数据，并且把正确的选项内容改成选我，错误的选项内容改成- 获取数据格式\r先获取数据格式，可以看到，返回的数据是 Json 格式 分析数据，选项主要存储在OPTION_CONTENT和QUESTION_OPTIONS_CONTENT 编写程序\r随着不断使用，解析程序会越来越多，为了保证可扩展性，使用addons来编写程序 编写exam.py\rfrom mitmproxy import http import json class Exam: def response(self,flow) -\u003e None: # 检查请求的 URL 是否包含 'getMockQuestionList' if 'getMockQuestionList' in flow.request.url and flow.response.headers.get(\"content-type\", \"\").startswith(\"application/json\"): try: # 解析 JSON 数据 data = json.loads(flow.response.content) # 修改每道题目的选项内容 if \"data\" in data and \"question\" in data[\"data\"]: for question in data[\"data\"][\"question\"]: right_answer = question.get(\"RIGHT_ANSWERS\", \"\") option_content = question.get(\"OPTION_CONTENT\", \"\") options = option_content.split(\"|\") # 创建一个新的选项内容列表 modified_options = [] modified_content = [] for label in ['A','B','C','D','E','F','G','H'][0:len(options)]: if label in right_answer: modified_options.append(f'{label}、选我') modified_content.append('选我') else: modified_options.append(f'{label}、-') modified_content.append('-') # 更新 QUESTION_OPTIONS_CONTENT 为修改后的选项 question[\"OPTION_CONTENT\"] = \"|\".join(modified_options) question[\"QUESTION_OPTIONS_CONTENT\"] = \"|\".join(modified_content) # 将修改后的数据重新赋值给响应内容 flow.response.content = json.dumps(data, ensure_ascii=False).encode(\"utf-8\") except Exception as e: print(f\"Error processing response: {e}\") addons = [ Exam() ] 扩充main.py\rfrom exam import Exam addons = [ Exam() ] 运行结果\r正确答案一目了然，轻松获得了 100 分。 ","date":"2025-01-01","objectID":"/posts/%E9%80%86%E8%BD%ACmitmproxy--wireguard-%E4%B8%AD%E9%97%B4%E4%BA%BA%E4%BB%A3%E7%90%86%E6%8A%93%E5%8C%85/:2:4","tags":["网络代理"],"title":"逆转！MitmProxy + WireGuard 中间人代理抓包","uri":"/posts/%E9%80%86%E8%BD%ACmitmproxy--wireguard-%E4%B8%AD%E9%97%B4%E4%BA%BA%E4%BB%A3%E7%90%86%E6%8A%93%E5%8C%85/#编写exampy"},{"categories":["玩转NAS"],"collections":["玩转NAS"],"content":"8.4 编写处理程序\r以实现解析某考试 App 为例，讲述如何编写程序，我们的目标是获取到该 App 返回的题目数据，并且把正确的选项内容改成选我，错误的选项内容改成- 获取数据格式\r先获取数据格式，可以看到，返回的数据是 Json 格式 分析数据，选项主要存储在OPTION_CONTENT和QUESTION_OPTIONS_CONTENT 编写程序\r随着不断使用，解析程序会越来越多，为了保证可扩展性，使用addons来编写程序 编写exam.py\rfrom mitmproxy import http import json class Exam: def response(self,flow) -\u003e None: # 检查请求的 URL 是否包含 'getMockQuestionList' if 'getMockQuestionList' in flow.request.url and flow.response.headers.get(\"content-type\", \"\").startswith(\"application/json\"): try: # 解析 JSON 数据 data = json.loads(flow.response.content) # 修改每道题目的选项内容 if \"data\" in data and \"question\" in data[\"data\"]: for question in data[\"data\"][\"question\"]: right_answer = question.get(\"RIGHT_ANSWERS\", \"\") option_content = question.get(\"OPTION_CONTENT\", \"\") options = option_content.split(\"|\") # 创建一个新的选项内容列表 modified_options = [] modified_content = [] for label in ['A','B','C','D','E','F','G','H'][0:len(options)]: if label in right_answer: modified_options.append(f'{label}、选我') modified_content.append('选我') else: modified_options.append(f'{label}、-') modified_content.append('-') # 更新 QUESTION_OPTIONS_CONTENT 为修改后的选项 question[\"OPTION_CONTENT\"] = \"|\".join(modified_options) question[\"QUESTION_OPTIONS_CONTENT\"] = \"|\".join(modified_content) # 将修改后的数据重新赋值给响应内容 flow.response.content = json.dumps(data, ensure_ascii=False).encode(\"utf-8\") except Exception as e: print(f\"Error processing response: {e}\") addons = [ Exam() ] 扩充main.py\rfrom exam import Exam addons = [ Exam() ] 运行结果\r正确答案一目了然，轻松获得了 100 分。 ","date":"2025-01-01","objectID":"/posts/%E9%80%86%E8%BD%ACmitmproxy--wireguard-%E4%B8%AD%E9%97%B4%E4%BA%BA%E4%BB%A3%E7%90%86%E6%8A%93%E5%8C%85/:2:4","tags":["网络代理"],"title":"逆转！MitmProxy + WireGuard 中间人代理抓包","uri":"/posts/%E9%80%86%E8%BD%ACmitmproxy--wireguard-%E4%B8%AD%E9%97%B4%E4%BA%BA%E4%BB%A3%E7%90%86%E6%8A%93%E5%8C%85/#扩充mainpy"},{"categories":["玩转NAS"],"collections":["玩转NAS"],"content":"8.4 编写处理程序\r以实现解析某考试 App 为例，讲述如何编写程序，我们的目标是获取到该 App 返回的题目数据，并且把正确的选项内容改成选我，错误的选项内容改成- 获取数据格式\r先获取数据格式，可以看到，返回的数据是 Json 格式 分析数据，选项主要存储在OPTION_CONTENT和QUESTION_OPTIONS_CONTENT 编写程序\r随着不断使用，解析程序会越来越多，为了保证可扩展性，使用addons来编写程序 编写exam.py\rfrom mitmproxy import http import json class Exam: def response(self,flow) -\u003e None: # 检查请求的 URL 是否包含 'getMockQuestionList' if 'getMockQuestionList' in flow.request.url and flow.response.headers.get(\"content-type\", \"\").startswith(\"application/json\"): try: # 解析 JSON 数据 data = json.loads(flow.response.content) # 修改每道题目的选项内容 if \"data\" in data and \"question\" in data[\"data\"]: for question in data[\"data\"][\"question\"]: right_answer = question.get(\"RIGHT_ANSWERS\", \"\") option_content = question.get(\"OPTION_CONTENT\", \"\") options = option_content.split(\"|\") # 创建一个新的选项内容列表 modified_options = [] modified_content = [] for label in ['A','B','C','D','E','F','G','H'][0:len(options)]: if label in right_answer: modified_options.append(f'{label}、选我') modified_content.append('选我') else: modified_options.append(f'{label}、-') modified_content.append('-') # 更新 QUESTION_OPTIONS_CONTENT 为修改后的选项 question[\"OPTION_CONTENT\"] = \"|\".join(modified_options) question[\"QUESTION_OPTIONS_CONTENT\"] = \"|\".join(modified_content) # 将修改后的数据重新赋值给响应内容 flow.response.content = json.dumps(data, ensure_ascii=False).encode(\"utf-8\") except Exception as e: print(f\"Error processing response: {e}\") addons = [ Exam() ] 扩充main.py\rfrom exam import Exam addons = [ Exam() ] 运行结果\r正确答案一目了然，轻松获得了 100 分。 ","date":"2025-01-01","objectID":"/posts/%E9%80%86%E8%BD%ACmitmproxy--wireguard-%E4%B8%AD%E9%97%B4%E4%BA%BA%E4%BB%A3%E7%90%86%E6%8A%93%E5%8C%85/:2:4","tags":["网络代理"],"title":"逆转！MitmProxy + WireGuard 中间人代理抓包","uri":"/posts/%E9%80%86%E8%BD%ACmitmproxy--wireguard-%E4%B8%AD%E9%97%B4%E4%BA%BA%E4%BB%A3%E7%90%86%E6%8A%93%E5%8C%85/#运行结果"},{"categories":["玩转NAS"],"collections":["玩转NAS"],"content":"第 13 节 概述\r在使用 NAS 的过程中，并非所有 Docker 应用都需要暴露在公网环境中。为了提升安全性，我们通常希望某些应用仅限于内网访问。然而，这也意味着当我们身处外网时，无法直接访问这些内网应用。 要实现外网访问内网应用的便利性，同时保持安全性，最佳解决方案就是通过 VPN 连接。 WireGuard 是一个简单、高效、安全的 VPN 协议，主要用于加密网络通信。其基本原理可以分为以下几个核心部分： 点对点加密连接： WireGuard 使用 公钥加密 进行身份验证和加密通信。每个节点都有一个 私钥 和 公钥，公钥用于标识节点并加密数据，私钥用于解密。 客户端和服务器之间通过交换公钥来建立加密连接，而不需要传统的 SSL/TLS 证书。 无状态加密： WireGuard 的加密过程是无状态的。每个数据包都包含足够的信息来验证其来源、加密内容和有效性，避免了传统 VPN 协议中复杂的会话管理和密钥交换过程。 它使用 ChaCha20（用于加密）、Poly1305（用于认证）等现代加密算法，确保了安全性和性能。 简单配置： WireGuard 的配置文件非常简洁，客户端和服务器配置都只需定义各自的公钥、私钥以及允许的 IP 地址和端口。相比 OpenVPN 和 IPsec，WireGuard 更容易配置和管理。 高效性和低延迟： WireGuard 设计目标之一是低延迟和高效性，因此在性能上比传统 VPN 协议（如 OpenVPN 和 IPsec）更优。它比其他 VPN 协议消耗更少的资源，能够更快速地建立和维护连接。 可以去这个网站下载WireGuard 摘要\r下文主要讲述如何部署 WireGuard 的服务器端和客户端，分别部署好服务器端和客户端就可以实现 WirdGuard 的点对点加密连接了 ","date":"2024-12-31","objectID":"/posts/wireguard-%E5%8A%A0%E5%AF%86%E9%80%9A%E4%BF%A1/:1:0","tags":["网络代理"],"title":"WireGuard 加密通信","uri":"/posts/wireguard-%E5%8A%A0%E5%AF%86%E9%80%9A%E4%BF%A1/#概述"},{"categories":["玩转NAS"],"collections":["玩转NAS"],"content":"第 14 节 部署服务器端\r信息\r服务器端和客户端本质上没有什么区别，因为 WireGuard 就是点对点连接的，有公网 IP 的就是服务器端，没有的就是客户端。 ","date":"2024-12-31","objectID":"/posts/wireguard-%E5%8A%A0%E5%AF%86%E9%80%9A%E4%BF%A1/:2:0","tags":["网络代理"],"title":"WireGuard 加密通信","uri":"/posts/wireguard-%E5%8A%A0%E5%AF%86%E9%80%9A%E4%BF%A1/#部署服务器端"},{"categories":["玩转NAS"],"collections":["玩转NAS"],"content":"14.1 下载 WireGuard\r威联通自带应用 QVPN，可以直接部署 WireGuard 服务器，当然，也可以用 Docker 去部署一个WireGuard 服务器。 ","date":"2024-12-31","objectID":"/posts/wireguard-%E5%8A%A0%E5%AF%86%E9%80%9A%E4%BF%A1/:2:1","tags":["网络代理"],"title":"WireGuard 加密通信","uri":"/posts/wireguard-%E5%8A%A0%E5%AF%86%E9%80%9A%E4%BF%A1/#下载-wireguard"},{"categories":["玩转NAS"],"collections":["玩转NAS"],"content":"14.2 配置服务器\r生成服务器端的公钥和私钥，公钥复制出来备用 监听端口设置一个自己喜欢的端口号 ","date":"2024-12-31","objectID":"/posts/wireguard-%E5%8A%A0%E5%AF%86%E9%80%9A%E4%BF%A1/:2:2","tags":["网络代理"],"title":"WireGuard 加密通信","uri":"/posts/wireguard-%E5%8A%A0%E5%AF%86%E9%80%9A%E4%BF%A1/#配置服务器"},{"categories":["玩转NAS"],"collections":["玩转NAS"],"content":"14.3 配置对等节点\r客户端公钥在配置客户端时再获取 ","date":"2024-12-31","objectID":"/posts/wireguard-%E5%8A%A0%E5%AF%86%E9%80%9A%E4%BF%A1/:2:3","tags":["网络代理"],"title":"WireGuard 加密通信","uri":"/posts/wireguard-%E5%8A%A0%E5%AF%86%E9%80%9A%E4%BF%A1/#配置对等节点"},{"categories":["玩转NAS"],"collections":["玩转NAS"],"content":"第 15 节 部署客户端\r","date":"2024-12-31","objectID":"/posts/wireguard-%E5%8A%A0%E5%AF%86%E9%80%9A%E4%BF%A1/:3:0","tags":["网络代理"],"title":"WireGuard 加密通信","uri":"/posts/wireguard-%E5%8A%A0%E5%AF%86%E9%80%9A%E4%BF%A1/#部署客户端"},{"categories":["玩转NAS"],"collections":["玩转NAS"],"content":"15.1 下载 WireGuard\r可以去这个网站下载WireGuard ","date":"2024-12-31","objectID":"/posts/wireguard-%E5%8A%A0%E5%AF%86%E9%80%9A%E4%BF%A1/:3:1","tags":["网络代理"],"title":"WireGuard 加密通信","uri":"/posts/wireguard-%E5%8A%A0%E5%AF%86%E9%80%9A%E4%BF%A1/#下载-wireguard-1"},{"categories":["玩转NAS"],"collections":["玩转NAS"],"content":"15.2 配置客户端\r添加一个空隧道，自动会生成客户端的公钥和私钥 填写好服务器端的相关参数 [Interface] PrivateKey = xxxxx Address = 198.18.7.2/32 DNS = 1.1.1.1 [Peer] PublicKey = xxxxx AllowedIPs = 0.0.0.0/0, ::/0 Endpoint = address:port PersistentKeepalive = 25 ","date":"2024-12-31","objectID":"/posts/wireguard-%E5%8A%A0%E5%AF%86%E9%80%9A%E4%BF%A1/:3:2","tags":["网络代理"],"title":"WireGuard 加密通信","uri":"/posts/wireguard-%E5%8A%A0%E5%AF%86%E9%80%9A%E4%BF%A1/#配置客户端"},{"categories":["玩转NAS"],"collections":["玩转NAS"],"content":"第 16 节 实际效果\r使用客户端连接服务器端以后，服务器端就会建立一个局域网，服务器端的虚拟IP地址就是198.18.7.1，客户端的虚拟IP地址就是198.18.7.2，他们两个在一个网段，所以能够互相连接 在我这边大概就是这个效果： 客户端可以正常连接 服务器端显示连接信息 正常打开部署在内网的应用 ","date":"2024-12-31","objectID":"/posts/wireguard-%E5%8A%A0%E5%AF%86%E9%80%9A%E4%BF%A1/:4:0","tags":["网络代理"],"title":"WireGuard 加密通信","uri":"/posts/wireguard-%E5%8A%A0%E5%AF%86%E9%80%9A%E4%BF%A1/#实际效果"},{"categories":["量化交易"],"collections":["量化交易"],"content":"第 1 节 资本资产定价模型 CAPM\r资本资产定价模型（Capital Asset Pricing Model, 简称 CAPM）是金融学中用于描述资产风险与预期收益之间关系的重要模型。它由威廉·夏普（William Sharpe）、约翰·林特纳（John Lintner）等人在20世纪60年代发展而来，是现代投资组合理论的重要组成部分。 摘要\r投资者的投资回报率由无风险回报和市场风险溢价两部分组成，且市场风险溢价与承担的系统性风险成正比，即风险越大，收益越大。 ","date":"2024-12-30","objectID":"/posts/%E8%B5%84%E4%BA%A7%E5%AE%9A%E4%BB%B7%E6%A8%A1%E5%9E%8B/:1:0","tags":null,"title":"资产定价模型","uri":"/posts/%E8%B5%84%E4%BA%A7%E5%AE%9A%E4%BB%B7%E6%A8%A1%E5%9E%8B/#资本资产定价模型-capm"},{"categories":["量化交易"],"collections":["量化交易"],"content":"1.1 公式\r$$ E\\left(R_i\\right)=R_f+\\beta_i\\left[E\\left(R_m\\right)-R_f\\right] $$ $E(R_i)$ 资产 $i$ 的预期收益率。 $R_f$ 无风险收益率，通常为政府债券的收益率。 $E(R_m)$ 市场组合的预期收益率。 $\\beta_i$ 资产 $i$ 的贝塔系数，表示资产相对于市场组合的系统性风险。 $$ \\beta_i = \\frac{\\text{Cov}(R_i, R_m)}{\\text{Var}(R_m)} $$ $\\text{Cov}$ 是资产收益与市场收益的协方差，$\\text{Var}$ 是市场收益的方差。 ","date":"2024-12-30","objectID":"/posts/%E8%B5%84%E4%BA%A7%E5%AE%9A%E4%BB%B7%E6%A8%A1%E5%9E%8B/:1:1","tags":null,"title":"资产定价模型","uri":"/posts/%E8%B5%84%E4%BA%A7%E5%AE%9A%E4%BB%B7%E6%A8%A1%E5%9E%8B/#公式"},{"categories":["量化交易"],"collections":["量化交易"],"content":"1.2 市场风险与特定风险：\r市场风险（系统性风险）：由整体经济因素引起，无法通过分散投资消除。 特定风险（非系统性风险）：与单个资产或企业相关，通过多样化投资可以减少。 CAPM 只考虑市场风险，假设特定风险已经通过分散投资被消除。 ","date":"2024-12-30","objectID":"/posts/%E8%B5%84%E4%BA%A7%E5%AE%9A%E4%BB%B7%E6%A8%A1%E5%9E%8B/:1:2","tags":null,"title":"资产定价模型","uri":"/posts/%E8%B5%84%E4%BA%A7%E5%AE%9A%E4%BB%B7%E6%A8%A1%E5%9E%8B/#市场风险与特定风险"},{"categories":["量化交易"],"collections":["量化交易"],"content":"1.3 假设条件\r投资者是理性的，追求收益最大化且厌恶风险。 市场是完全竞争的，无摩擦（如税收、交易费用等）。 投资者只关心投资的期望收益和方差。 所有资产可以无限分割并被交易。 ","date":"2024-12-30","objectID":"/posts/%E8%B5%84%E4%BA%A7%E5%AE%9A%E4%BB%B7%E6%A8%A1%E5%9E%8B/:1:3","tags":null,"title":"资产定价模型","uri":"/posts/%E8%B5%84%E4%BA%A7%E5%AE%9A%E4%BB%B7%E6%A8%A1%E5%9E%8B/#假设条件"},{"categories":["量化交易"],"collections":["量化交易"],"content":"1.4 局限性\r假设过于理想化，与实际市场可能存在差距。 $\\beta$ 只衡量系统性风险，未考虑其他影响收益的因素。 无风险收益率和市场组合的预期收益在实际操作中难以精确估计。 ","date":"2024-12-30","objectID":"/posts/%E8%B5%84%E4%BA%A7%E5%AE%9A%E4%BB%B7%E6%A8%A1%E5%9E%8B/:1:4","tags":null,"title":"资产定价模型","uri":"/posts/%E8%B5%84%E4%BA%A7%E5%AE%9A%E4%BB%B7%E6%A8%A1%E5%9E%8B/#局限性"},{"categories":["量化交易"],"collections":["量化交易"],"content":"第 2 节 三因子模型\r三因子模型（Fama-French Three-Factor Model）是由尤金·法玛（Eugene Fama）和肯尼斯·弗伦奇（Kenneth French）提出的资产定价模型，是对资本资产定价模型（CAPM）的扩展。它通过引入两个额外的因子来更好地解释资产收益的来源。 摘要\r资产的预期收益由市场风险、公司规模（小公司相较于大公司的超额收益）和价值属性（价值型股票相较于成长型股票的超额收益）共同决定 ","date":"2024-12-30","objectID":"/posts/%E8%B5%84%E4%BA%A7%E5%AE%9A%E4%BB%B7%E6%A8%A1%E5%9E%8B/:2:0","tags":null,"title":"资产定价模型","uri":"/posts/%E8%B5%84%E4%BA%A7%E5%AE%9A%E4%BB%B7%E6%A8%A1%E5%9E%8B/#三因子模型"},{"categories":["量化交易"],"collections":["量化交易"],"content":"2.1 公式\r$$ E(R_i) = R_f + \\beta_m [E(R_m) - R_f] + \\beta_s \\cdot \\text{SMB} + \\beta_h \\cdot \\text{HML} $$ SMB (Small Minus Big) 小市值收益与大市值收益之间的差异，表示公司规模因子。 HML (High Minus Low) 高账面市值比收益与低账面市值比收益之间的差异，表示价值因子。 $\\beta_s$ 资产对规模因子的敏感性。 $\\beta_h$ 资产对价值因子的敏感性。 信息\rSMB 和 HML 的计算涉及到一些复杂的加权运算，需要同时考虑账面市值和市值。 ","date":"2024-12-30","objectID":"/posts/%E8%B5%84%E4%BA%A7%E5%AE%9A%E4%BB%B7%E6%A8%A1%E5%9E%8B/:2:1","tags":null,"title":"资产定价模型","uri":"/posts/%E8%B5%84%E4%BA%A7%E5%AE%9A%E4%BB%B7%E6%A8%A1%E5%9E%8B/#公式-1"},{"categories":["量化交易"],"collections":["量化交易"],"content":"2.2 假设条件\r市场对规模和价值因子进行风险定价。 资产收益可以用这三类因子的线性组合来解释。 ","date":"2024-12-30","objectID":"/posts/%E8%B5%84%E4%BA%A7%E5%AE%9A%E4%BB%B7%E6%A8%A1%E5%9E%8B/:2:2","tags":null,"title":"资产定价模型","uri":"/posts/%E8%B5%84%E4%BA%A7%E5%AE%9A%E4%BB%B7%E6%A8%A1%E5%9E%8B/#假设条件-1"},{"categories":["量化交易"],"collections":["量化交易"],"content":"2.3 局限性\r因子选择问题 为何选择规模和价值作为因子，是否有更好的因子尚存争议。 过于依赖历史数据 模型基于历史统计数据，未来可能不适用。 忽略其他潜在因子 如动量因子、盈利因子等。 ","date":"2024-12-30","objectID":"/posts/%E8%B5%84%E4%BA%A7%E5%AE%9A%E4%BB%B7%E6%A8%A1%E5%9E%8B/:2:3","tags":null,"title":"资产定价模型","uri":"/posts/%E8%B5%84%E4%BA%A7%E5%AE%9A%E4%BB%B7%E6%A8%A1%E5%9E%8B/#局限性-1"},{"categories":["量化交易"],"collections":["量化交易"],"content":"第 3 节 五因子模型\r五因子模型（Fama-French Five-Factor Model）是由尤金·法玛（Eugene Fama）和肯尼斯·弗伦奇（Kenneth French）在2015年提出的，它是在三因子模型的基础上扩展而来的。除了传统的市场因子、规模因子（SMB）和价值因子（HML），五因子模型还增加了盈利因子（RMW）和投资因子（CMA），旨在更全面地解释股票的预期收益。 摘要\r资产的预期收益由市场风险、公司规模、价值属性、盈利能力（高盈利公司相对于低盈利公司的超额收益）、投资水平（低投资公司相对于高投资公司的超额收益）共同决定 ","date":"2024-12-30","objectID":"/posts/%E8%B5%84%E4%BA%A7%E5%AE%9A%E4%BB%B7%E6%A8%A1%E5%9E%8B/:3:0","tags":null,"title":"资产定价模型","uri":"/posts/%E8%B5%84%E4%BA%A7%E5%AE%9A%E4%BB%B7%E6%A8%A1%E5%9E%8B/#五因子模型"},{"categories":["量化交易"],"collections":["量化交易"],"content":"3.1 公式\r$$ E(R_i) = R_f + \\beta_m [E(R_m) - R_f] + \\beta_s \\cdot \\text{SMB} + \\beta_h \\cdot \\text{HML} + \\beta_{rm} \\cdot \\text{RMW} + \\beta_{cm} \\cdot \\text{CMA} $$ RMW 盈利因子（Robust Minus Weak），反映高盈利能力公司相对于低盈利能力公司的超额收益。 CMA 投资因子（Conservative Minus Aggressive），反映低投资公司相对于高投资公司的超额收益。 $\\beta_{rm}$ 资产对盈利因子的敏感性。 $\\beta_{cm}$ 资产对投资因子的敏感性。 ","date":"2024-12-30","objectID":"/posts/%E8%B5%84%E4%BA%A7%E5%AE%9A%E4%BB%B7%E6%A8%A1%E5%9E%8B/:3:1","tags":null,"title":"资产定价模型","uri":"/posts/%E8%B5%84%E4%BA%A7%E5%AE%9A%E4%BB%B7%E6%A8%A1%E5%9E%8B/#公式-2"},{"categories":["量化交易"],"collections":["量化交易"],"content":"3.2 局限性\r复杂性增加：尽管五因子模型在解释资产收益方面更为精确，但引入了更多的因子和计算步骤，可能导致更高的计算复杂度。 因子有效性：并非所有市场环境下这五个因子都能稳定有效地解释收益，某些因子可能在特定时间段或市场条件下表现不佳。 ","date":"2024-12-30","objectID":"/posts/%E8%B5%84%E4%BA%A7%E5%AE%9A%E4%BB%B7%E6%A8%A1%E5%9E%8B/:3:2","tags":null,"title":"资产定价模型","uri":"/posts/%E8%B5%84%E4%BA%A7%E5%AE%9A%E4%BB%B7%E6%A8%A1%E5%9E%8B/#局限性-2"},{"categories":["量化交易"],"collections":["量化交易"],"content":"第 4 节 六因子模型\r六因子模型（Fama-French Six-Factor Model）是在五因子模型基础上的进一步扩展，由尤金·法玛（Eugene Fama）和肯尼斯·弗伦奇（Kenneth French）在2017年提出。六因子模型在五因子模型的基础上增加了动量因子（MOM），旨在更全面地解释资产的预期收益。动量因子的引入使得模型能够更好地捕捉到股票价格的惯性效应，即过去表现较好的股票通常会继续表现良好，反之亦然。 摘要\r资产的预期收益由市场风险、公司规模、价值属性、盈利能力、投资水平、过去表现（过去表现良好的股票相对于过去表现较差的股票的超额收益）共同决定 ","date":"2024-12-30","objectID":"/posts/%E8%B5%84%E4%BA%A7%E5%AE%9A%E4%BB%B7%E6%A8%A1%E5%9E%8B/:4:0","tags":null,"title":"资产定价模型","uri":"/posts/%E8%B5%84%E4%BA%A7%E5%AE%9A%E4%BB%B7%E6%A8%A1%E5%9E%8B/#六因子模型"},{"categories":["量化交易"],"collections":["量化交易"],"content":"4.1 公式\r$$ E(R_i) = R_f + \\beta_m [E(R_m) - R_f] + \\beta_s \\cdot \\text{SMB} + \\beta_h \\cdot \\text{HML} + \\beta_{rm} \\cdot \\text{RMW} + \\beta_{cm} \\cdot \\text{CMA} + \\beta_{mom} \\cdot \\text{MOM} $$ MOM：动量因子（Momentum），反映过去表现良好的股票相对于过去表现较差的股票的超额收益。 $\\beta_{mom}$ 资产对动量因子的敏感性。 ","date":"2024-12-30","objectID":"/posts/%E8%B5%84%E4%BA%A7%E5%AE%9A%E4%BB%B7%E6%A8%A1%E5%9E%8B/:4:1","tags":null,"title":"资产定价模型","uri":"/posts/%E8%B5%84%E4%BA%A7%E5%AE%9A%E4%BB%B7%E6%A8%A1%E5%9E%8B/#公式-3"},{"categories":["量化交易"],"collections":["量化交易"],"content":"4.2 局限性\r复杂性：增加了更多的因子，模型的计算和理解难度有所提升。 因子有效性：尽管动量因子在短期内能有效解释部分市场行为，但其长期有效性仍存在争议，尤其是在大规模市场波动或大事件发生后。 ","date":"2024-12-30","objectID":"/posts/%E8%B5%84%E4%BA%A7%E5%AE%9A%E4%BB%B7%E6%A8%A1%E5%9E%8B/:4:2","tags":null,"title":"资产定价模型","uri":"/posts/%E8%B5%84%E4%BA%A7%E5%AE%9A%E4%BB%B7%E6%A8%A1%E5%9E%8B/#局限性-3"},{"categories":["量化交易"],"collections":["量化交易"],"content":"第 5 节 Q5 模型\rQ5 模型在 Fama-French 五因子模型的基础上，进一步增加了投资风格因子（Q因子）以更好地解释资产的收益。 摘要\r考虑市场因子、规模因子、价值因子、盈利因子和投资因子外，还引入了投资者情绪和市场环境等非传统因素，以捕捉更多影响资产收益的因素。 ","date":"2024-12-30","objectID":"/posts/%E8%B5%84%E4%BA%A7%E5%AE%9A%E4%BB%B7%E6%A8%A1%E5%9E%8B/:5:0","tags":null,"title":"资产定价模型","uri":"/posts/%E8%B5%84%E4%BA%A7%E5%AE%9A%E4%BB%B7%E6%A8%A1%E5%9E%8B/#q5-模型"},{"categories":["量化交易"],"collections":["量化交易"],"content":"5.1 情绪指标\r情绪指标通常通过分析市场行为和投资者的情绪来衡量，可以包括以下内容： 市场波动性：如VIX指数（波动率指数），反映市场的不确定性。 投资者情绪调查数据：例如个人投资者的乐观/悲观预期。 交易量：高交易量可能意味着投资者情绪高涨，反之则表明情绪较为冷淡。 ","date":"2024-12-30","objectID":"/posts/%E8%B5%84%E4%BA%A7%E5%AE%9A%E4%BB%B7%E6%A8%A1%E5%9E%8B/:5:1","tags":null,"title":"资产定价模型","uri":"/posts/%E8%B5%84%E4%BA%A7%E5%AE%9A%E4%BB%B7%E6%A8%A1%E5%9E%8B/#情绪指标"},{"categories":["量化交易"],"collections":["量化交易"],"content":"5.2 市场环境因子\r市场环境因子通常考虑宏观经济因素、市场流动性等： 利率变动：利率的变化可能影响资本成本和股票估值。 经济增长指标：如GDP增速、消费者信心指数等，反映经济周期的阶段。 市场流动性：包括市场的资金流动性，通常通过广义货币供应量（如M2）等指标来衡量。 ","date":"2024-12-30","objectID":"/posts/%E8%B5%84%E4%BA%A7%E5%AE%9A%E4%BB%B7%E6%A8%A1%E5%9E%8B/:5:2","tags":null,"title":"资产定价模型","uri":"/posts/%E8%B5%84%E4%BA%A7%E5%AE%9A%E4%BB%B7%E6%A8%A1%E5%9E%8B/#市场环境因子"},{"categories":["玩转NAS"],"collections":["玩转NAS"],"content":"第 10 节 概述\rChanify 是一个简单的消息推送工具。可以利用提供的 API 来发送消息推送到自己的 iOS 设备上，项目在 Github 上。安装教程可以参考这篇文章。 Chanify 包括这些功能： 支持自定义频道分类消息 支持部署自己的节点服务器 依照去中心化设计的系统架构 随机账号生成保护隐私 支持文本/图片/音频/文件等多种消息格式 Chanify 的功能实现主要是依赖于苹果的 APNS 服务，和 Chanify 相同原理的产品有 Bark 。 摘要\r以下内容主要在讲我如何部署自己的节点服务器，以及配置了哪些客户端推送消息到手机。 ","date":"2024-12-29","objectID":"/posts/chanify-%E6%B6%88%E6%81%AF%E6%8E%A8%E9%80%81/:1:0","tags":["消息推送"],"title":"Chanify 消息推送","uri":"/posts/chanify-%E6%B6%88%E6%81%AF%E6%8E%A8%E9%80%81/#概述"},{"categories":["玩转NAS"],"collections":["玩转NAS"],"content":"第 11 节 添加服务端\r","date":"2024-12-29","objectID":"/posts/chanify-%E6%B6%88%E6%81%AF%E6%8E%A8%E9%80%81/:2:0","tags":["消息推送"],"title":"Chanify 消息推送","uri":"/posts/chanify-%E6%B6%88%E6%81%AF%E6%8E%A8%E9%80%81/#添加服务端"},{"categories":["玩转NAS"],"collections":["玩转NAS"],"content":"11.1 拉取镜像\rdocker pull wizjin/chanify:latest ","date":"2024-12-29","objectID":"/posts/chanify-%E6%B6%88%E6%81%AF%E6%8E%A8%E9%80%81/:2:1","tags":["消息推送"],"title":"Chanify 消息推送","uri":"/posts/chanify-%E6%B6%88%E6%81%AF%E6%8E%A8%E9%80%81/#拉取镜像"},{"categories":["玩转NAS"],"collections":["玩转NAS"],"content":"11.2 数据库\r创建一个MySQL数据库，取名随意，例如chanify ","date":"2024-12-29","objectID":"/posts/chanify-%E6%B6%88%E6%81%AF%E6%8E%A8%E9%80%81/:2:2","tags":["消息推送"],"title":"Chanify 消息推送","uri":"/posts/chanify-%E6%B6%88%E6%81%AF%E6%8E%A8%E9%80%81/#数据库"},{"categories":["玩转NAS"],"collections":["玩转NAS"],"content":"11.3 部署镜像\rdocker run -itd --name=chanify --restart always \\ -p PORT:80 \\ -v /PATH/chanify/data:/root/.chanify \\ -v /PATH/chanify/chanify.yml:/root/.chanify.yml \\ wizjin/chanify:latest serve \\ --endpoint=http://ENDPOINT_ADDRESS:PORT \\ --dburl=\"mysql://USERNAME:PASSWORD@tcp(ADDRESS:PORT)/chanify?charset=utf8mb4\u0026parseTime=true\u0026loc=Local\" endpoint节点入口，用来访问服务端，设置为公网地址或者内网地址，比如https://message.papergate.top:5000 dburl非必须，不配置会使用SQLite ","date":"2024-12-29","objectID":"/posts/chanify-%E6%B6%88%E6%81%AF%E6%8E%A8%E9%80%81/:2:3","tags":["消息推送"],"title":"Chanify 消息推送","uri":"/posts/chanify-%E6%B6%88%E6%81%AF%E6%8E%A8%E9%80%81/#部署镜像"},{"categories":["玩转NAS"],"collections":["玩转NAS"],"content":"11.4 修改 chanify.yml\r# 服务器端 server: host: 0.0.0.0 # 监听地址 port: 80 # 监听端口 endpoint: http://ENDPOINT_ADDRESS:PORT name: SERVER_NAME # 服务器名称，自取 datapath: /root/.chanify dburl: mysql://USERNAME:PASSWORD@tcp(ADDRESS:PORT)/chanify?charset=utf8mb4\u0026parseTime=true\u0026loc=Local http: - readtimeout: 10s - writetimeout: 10s register: enable: false # 不允许注册 whitelist: # 白名单 - XXXXXXXXXXXXXX # 设置自己手机 App 的用户ID ","date":"2024-12-29","objectID":"/posts/chanify-%E6%B6%88%E6%81%AF%E6%8E%A8%E9%80%81/:2:4","tags":["消息推送"],"title":"Chanify 消息推送","uri":"/posts/chanify-%E6%B6%88%E6%81%AF%E6%8E%A8%E9%80%81/#修改-chanifyyml"},{"categories":["玩转NAS"],"collections":["玩转NAS"],"content":"11.5 使用 App 扫码\r访问网址，获取二维码： http://ENDPOINT_ADDRESS:PORT 扫描二维码，添加节点。 ","date":"2024-12-29","objectID":"/posts/chanify-%E6%B6%88%E6%81%AF%E6%8E%A8%E9%80%81/:2:5","tags":["消息推送"],"title":"Chanify 消息推送","uri":"/posts/chanify-%E6%B6%88%E6%81%AF%E6%8E%A8%E9%80%81/#使用-app-扫码"},{"categories":["玩转NAS"],"collections":["玩转NAS"],"content":"第 12 节 添加客户端\r","date":"2024-12-29","objectID":"/posts/chanify-%E6%B6%88%E6%81%AF%E6%8E%A8%E9%80%81/:3:0","tags":["消息推送"],"title":"Chanify 消息推送","uri":"/posts/chanify-%E6%B6%88%E6%81%AF%E6%8E%A8%E9%80%81/#添加客户端"},{"categories":["玩转NAS"],"collections":["玩转NAS"],"content":"12.1 添加频道\r手机端频道页面长按右上角二维码按钮，在弹出框选择添加频道 给频道设置名称和图标 频道页面向左滑动图标，查看详情，复制令牌备用 ","date":"2024-12-29","objectID":"/posts/chanify-%E6%B6%88%E6%81%AF%E6%8E%A8%E9%80%81/:3:1","tags":["消息推送"],"title":"Chanify 消息推送","uri":"/posts/chanify-%E6%B6%88%E6%81%AF%E6%8E%A8%E9%80%81/#添加频道"},{"categories":["玩转NAS"],"collections":["玩转NAS"],"content":"12.2 Chrome 浏览器推送手机\r添加频道\r按照之前的说法添加一个频道，比如命名为Chrome 下载插件\r可以从Chrome Web Store下载插件. 插件有以下功能: 发送选中的文本/图片/音频/链接消息到 Chanify 发送网页链接到 Chanify 进行如下配置： 信息\r中断等级的含义在下一节 选择发送目标修改为Chrome 修改 chanify.yml\r增加如下内容 client： chrome: sound: 1 # 是否有提示音 endpoint: https://message.papergate.top:5000 token: xxxxx interruption-level: time-sensitive sound声音提醒： 1 启用默认声音 声音代码，例如 bell interruption-level中断级别： active：点亮屏幕并播放声音。 passive：不点亮屏幕或播放声音。 time-sensitive：点亮屏幕并播放声音，在“勿扰模式”期间显示。 示例\r到此为止已经配置完成，比如发送一段文本 在手机上便可以接收到如下内容： ","date":"2024-12-29","objectID":"/posts/chanify-%E6%B6%88%E6%81%AF%E6%8E%A8%E9%80%81/:3:2","tags":["消息推送"],"title":"Chanify 消息推送","uri":"/posts/chanify-%E6%B6%88%E6%81%AF%E6%8E%A8%E9%80%81/#chrome-浏览器推送手机"},{"categories":["玩转NAS"],"collections":["玩转NAS"],"content":"12.2 Chrome 浏览器推送手机\r添加频道\r按照之前的说法添加一个频道，比如命名为Chrome 下载插件\r可以从Chrome Web Store下载插件. 插件有以下功能: 发送选中的文本/图片/音频/链接消息到 Chanify 发送网页链接到 Chanify 进行如下配置： 信息\r中断等级的含义在下一节 选择发送目标修改为Chrome 修改 chanify.yml\r增加如下内容 client： chrome: sound: 1 # 是否有提示音 endpoint: https://message.papergate.top:5000 token: xxxxx interruption-level: time-sensitive sound声音提醒： 1 启用默认声音 声音代码，例如 bell interruption-level中断级别： active：点亮屏幕并播放声音。 passive：不点亮屏幕或播放声音。 time-sensitive：点亮屏幕并播放声音，在“勿扰模式”期间显示。 示例\r到此为止已经配置完成，比如发送一段文本 在手机上便可以接收到如下内容： ","date":"2024-12-29","objectID":"/posts/chanify-%E6%B6%88%E6%81%AF%E6%8E%A8%E9%80%81/:3:2","tags":["消息推送"],"title":"Chanify 消息推送","uri":"/posts/chanify-%E6%B6%88%E6%81%AF%E6%8E%A8%E9%80%81/#添加频道-1"},{"categories":["玩转NAS"],"collections":["玩转NAS"],"content":"12.2 Chrome 浏览器推送手机\r添加频道\r按照之前的说法添加一个频道，比如命名为Chrome 下载插件\r可以从Chrome Web Store下载插件. 插件有以下功能: 发送选中的文本/图片/音频/链接消息到 Chanify 发送网页链接到 Chanify 进行如下配置： 信息\r中断等级的含义在下一节 选择发送目标修改为Chrome 修改 chanify.yml\r增加如下内容 client： chrome: sound: 1 # 是否有提示音 endpoint: https://message.papergate.top:5000 token: xxxxx interruption-level: time-sensitive sound声音提醒： 1 启用默认声音 声音代码，例如 bell interruption-level中断级别： active：点亮屏幕并播放声音。 passive：不点亮屏幕或播放声音。 time-sensitive：点亮屏幕并播放声音，在“勿扰模式”期间显示。 示例\r到此为止已经配置完成，比如发送一段文本 在手机上便可以接收到如下内容： ","date":"2024-12-29","objectID":"/posts/chanify-%E6%B6%88%E6%81%AF%E6%8E%A8%E9%80%81/:3:2","tags":["消息推送"],"title":"Chanify 消息推送","uri":"/posts/chanify-%E6%B6%88%E6%81%AF%E6%8E%A8%E9%80%81/#下载插件"},{"categories":["玩转NAS"],"collections":["玩转NAS"],"content":"12.2 Chrome 浏览器推送手机\r添加频道\r按照之前的说法添加一个频道，比如命名为Chrome 下载插件\r可以从Chrome Web Store下载插件. 插件有以下功能: 发送选中的文本/图片/音频/链接消息到 Chanify 发送网页链接到 Chanify 进行如下配置： 信息\r中断等级的含义在下一节 选择发送目标修改为Chrome 修改 chanify.yml\r增加如下内容 client： chrome: sound: 1 # 是否有提示音 endpoint: https://message.papergate.top:5000 token: xxxxx interruption-level: time-sensitive sound声音提醒： 1 启用默认声音 声音代码，例如 bell interruption-level中断级别： active：点亮屏幕并播放声音。 passive：不点亮屏幕或播放声音。 time-sensitive：点亮屏幕并播放声音，在“勿扰模式”期间显示。 示例\r到此为止已经配置完成，比如发送一段文本 在手机上便可以接收到如下内容： ","date":"2024-12-29","objectID":"/posts/chanify-%E6%B6%88%E6%81%AF%E6%8E%A8%E9%80%81/:3:2","tags":["消息推送"],"title":"Chanify 消息推送","uri":"/posts/chanify-%E6%B6%88%E6%81%AF%E6%8E%A8%E9%80%81/#修改-chanifyyml-1"},{"categories":["玩转NAS"],"collections":["玩转NAS"],"content":"12.2 Chrome 浏览器推送手机\r添加频道\r按照之前的说法添加一个频道，比如命名为Chrome 下载插件\r可以从Chrome Web Store下载插件. 插件有以下功能: 发送选中的文本/图片/音频/链接消息到 Chanify 发送网页链接到 Chanify 进行如下配置： 信息\r中断等级的含义在下一节 选择发送目标修改为Chrome 修改 chanify.yml\r增加如下内容 client： chrome: sound: 1 # 是否有提示音 endpoint: https://message.papergate.top:5000 token: xxxxx interruption-level: time-sensitive sound声音提醒： 1 启用默认声音 声音代码，例如 bell interruption-level中断级别： active：点亮屏幕并播放声音。 passive：不点亮屏幕或播放声音。 time-sensitive：点亮屏幕并播放声音，在“勿扰模式”期间显示。 示例\r到此为止已经配置完成，比如发送一段文本 在手机上便可以接收到如下内容： ","date":"2024-12-29","objectID":"/posts/chanify-%E6%B6%88%E6%81%AF%E6%8E%A8%E9%80%81/:3:2","tags":["消息推送"],"title":"Chanify 消息推送","uri":"/posts/chanify-%E6%B6%88%E6%81%AF%E6%8E%A8%E9%80%81/#示例"},{"categories":["玩转NAS"],"collections":["玩转NAS"],"content":"12.3 威联通 Nas syslog 推送手机\r添加频道\r按照之前的说法添加一个频道，比如命名为Nas 配置短信服务\r控制台 -\u003e 通知中心 -\u003e 服务账户和设备配对 -\u003e 短信 -\u003e 添加 SMSC 服务 URL 模板格式如下，可以多配几个不同的，修改一下title的值：： # 1 http://ADDRESS:PORT/v1/sender/TOKEN?title=syslog\u0026text=@@Text@@\u0026sound=1\u0026interruption-level=time-sensitive\u0026@@PhoneNumber@@ # 2 http://ADDRESS:PORT/v1/sender/TOKEN?title=告警信息\u0026text=@@Text@@\u0026sound=1\u0026interruption-level=time-sensitive\u0026@@PhoneNumber@@ # 3 http://ADDRESS:PORT/v1/sender/TOKEN?title=应用信息\u0026text=@@Text@@\u0026sound=1\u0026interruption-level=time-sensitive\u0026@@PhoneNumber@@ 信息\r因为@@PhoneNumber@@是必填的，所以随便放入即可 配置通知规则\r控制台 -\u003e 通知中心 -\u003e 系统通知规则 -\u003e 创建规则 手机号随便填写即可 多创建几个规则，用于不同情形 修改 chanify.yml\r增加如下内容 client: nas: sound: 1 # 是否有提示音 endpoint: https://message.papergate.top:5000 token: xxxxx interruption-level: time-sensitive sound声音提醒： 1 启用默认声音 声音代码，例如 bell interruption-level中断级别： active：点亮屏幕并播放声音。 passive：不点亮屏幕或播放声音。 time-sensitive：点亮屏幕并播放声音，在“勿扰模式”期间显示。 示例\r","date":"2024-12-29","objectID":"/posts/chanify-%E6%B6%88%E6%81%AF%E6%8E%A8%E9%80%81/:3:3","tags":["消息推送"],"title":"Chanify 消息推送","uri":"/posts/chanify-%E6%B6%88%E6%81%AF%E6%8E%A8%E9%80%81/#威联通-nas-syslog-推送手机"},{"categories":["玩转NAS"],"collections":["玩转NAS"],"content":"12.3 威联通 Nas syslog 推送手机\r添加频道\r按照之前的说法添加一个频道，比如命名为Nas 配置短信服务\r控制台 -\u003e 通知中心 -\u003e 服务账户和设备配对 -\u003e 短信 -\u003e 添加 SMSC 服务 URL 模板格式如下，可以多配几个不同的，修改一下title的值：： # 1 http://ADDRESS:PORT/v1/sender/TOKEN?title=syslog\u0026text=@@Text@@\u0026sound=1\u0026interruption-level=time-sensitive\u0026@@PhoneNumber@@ # 2 http://ADDRESS:PORT/v1/sender/TOKEN?title=告警信息\u0026text=@@Text@@\u0026sound=1\u0026interruption-level=time-sensitive\u0026@@PhoneNumber@@ # 3 http://ADDRESS:PORT/v1/sender/TOKEN?title=应用信息\u0026text=@@Text@@\u0026sound=1\u0026interruption-level=time-sensitive\u0026@@PhoneNumber@@ 信息\r因为@@PhoneNumber@@是必填的，所以随便放入即可 配置通知规则\r控制台 -\u003e 通知中心 -\u003e 系统通知规则 -\u003e 创建规则 手机号随便填写即可 多创建几个规则，用于不同情形 修改 chanify.yml\r增加如下内容 client: nas: sound: 1 # 是否有提示音 endpoint: https://message.papergate.top:5000 token: xxxxx interruption-level: time-sensitive sound声音提醒： 1 启用默认声音 声音代码，例如 bell interruption-level中断级别： active：点亮屏幕并播放声音。 passive：不点亮屏幕或播放声音。 time-sensitive：点亮屏幕并播放声音，在“勿扰模式”期间显示。 示例\r","date":"2024-12-29","objectID":"/posts/chanify-%E6%B6%88%E6%81%AF%E6%8E%A8%E9%80%81/:3:3","tags":["消息推送"],"title":"Chanify 消息推送","uri":"/posts/chanify-%E6%B6%88%E6%81%AF%E6%8E%A8%E9%80%81/#添加频道-2"},{"categories":["玩转NAS"],"collections":["玩转NAS"],"content":"12.3 威联通 Nas syslog 推送手机\r添加频道\r按照之前的说法添加一个频道，比如命名为Nas 配置短信服务\r控制台 -\u003e 通知中心 -\u003e 服务账户和设备配对 -\u003e 短信 -\u003e 添加 SMSC 服务 URL 模板格式如下，可以多配几个不同的，修改一下title的值：： # 1 http://ADDRESS:PORT/v1/sender/TOKEN?title=syslog\u0026text=@@Text@@\u0026sound=1\u0026interruption-level=time-sensitive\u0026@@PhoneNumber@@ # 2 http://ADDRESS:PORT/v1/sender/TOKEN?title=告警信息\u0026text=@@Text@@\u0026sound=1\u0026interruption-level=time-sensitive\u0026@@PhoneNumber@@ # 3 http://ADDRESS:PORT/v1/sender/TOKEN?title=应用信息\u0026text=@@Text@@\u0026sound=1\u0026interruption-level=time-sensitive\u0026@@PhoneNumber@@ 信息\r因为@@PhoneNumber@@是必填的，所以随便放入即可 配置通知规则\r控制台 -\u003e 通知中心 -\u003e 系统通知规则 -\u003e 创建规则 手机号随便填写即可 多创建几个规则，用于不同情形 修改 chanify.yml\r增加如下内容 client: nas: sound: 1 # 是否有提示音 endpoint: https://message.papergate.top:5000 token: xxxxx interruption-level: time-sensitive sound声音提醒： 1 启用默认声音 声音代码，例如 bell interruption-level中断级别： active：点亮屏幕并播放声音。 passive：不点亮屏幕或播放声音。 time-sensitive：点亮屏幕并播放声音，在“勿扰模式”期间显示。 示例\r","date":"2024-12-29","objectID":"/posts/chanify-%E6%B6%88%E6%81%AF%E6%8E%A8%E9%80%81/:3:3","tags":["消息推送"],"title":"Chanify 消息推送","uri":"/posts/chanify-%E6%B6%88%E6%81%AF%E6%8E%A8%E9%80%81/#配置短信服务"},{"categories":["玩转NAS"],"collections":["玩转NAS"],"content":"12.3 威联通 Nas syslog 推送手机\r添加频道\r按照之前的说法添加一个频道，比如命名为Nas 配置短信服务\r控制台 -\u003e 通知中心 -\u003e 服务账户和设备配对 -\u003e 短信 -\u003e 添加 SMSC 服务 URL 模板格式如下，可以多配几个不同的，修改一下title的值：： # 1 http://ADDRESS:PORT/v1/sender/TOKEN?title=syslog\u0026text=@@Text@@\u0026sound=1\u0026interruption-level=time-sensitive\u0026@@PhoneNumber@@ # 2 http://ADDRESS:PORT/v1/sender/TOKEN?title=告警信息\u0026text=@@Text@@\u0026sound=1\u0026interruption-level=time-sensitive\u0026@@PhoneNumber@@ # 3 http://ADDRESS:PORT/v1/sender/TOKEN?title=应用信息\u0026text=@@Text@@\u0026sound=1\u0026interruption-level=time-sensitive\u0026@@PhoneNumber@@ 信息\r因为@@PhoneNumber@@是必填的，所以随便放入即可 配置通知规则\r控制台 -\u003e 通知中心 -\u003e 系统通知规则 -\u003e 创建规则 手机号随便填写即可 多创建几个规则，用于不同情形 修改 chanify.yml\r增加如下内容 client: nas: sound: 1 # 是否有提示音 endpoint: https://message.papergate.top:5000 token: xxxxx interruption-level: time-sensitive sound声音提醒： 1 启用默认声音 声音代码，例如 bell interruption-level中断级别： active：点亮屏幕并播放声音。 passive：不点亮屏幕或播放声音。 time-sensitive：点亮屏幕并播放声音，在“勿扰模式”期间显示。 示例\r","date":"2024-12-29","objectID":"/posts/chanify-%E6%B6%88%E6%81%AF%E6%8E%A8%E9%80%81/:3:3","tags":["消息推送"],"title":"Chanify 消息推送","uri":"/posts/chanify-%E6%B6%88%E6%81%AF%E6%8E%A8%E9%80%81/#配置通知规则"},{"categories":["玩转NAS"],"collections":["玩转NAS"],"content":"12.3 威联通 Nas syslog 推送手机\r添加频道\r按照之前的说法添加一个频道，比如命名为Nas 配置短信服务\r控制台 -\u003e 通知中心 -\u003e 服务账户和设备配对 -\u003e 短信 -\u003e 添加 SMSC 服务 URL 模板格式如下，可以多配几个不同的，修改一下title的值：： # 1 http://ADDRESS:PORT/v1/sender/TOKEN?title=syslog\u0026text=@@Text@@\u0026sound=1\u0026interruption-level=time-sensitive\u0026@@PhoneNumber@@ # 2 http://ADDRESS:PORT/v1/sender/TOKEN?title=告警信息\u0026text=@@Text@@\u0026sound=1\u0026interruption-level=time-sensitive\u0026@@PhoneNumber@@ # 3 http://ADDRESS:PORT/v1/sender/TOKEN?title=应用信息\u0026text=@@Text@@\u0026sound=1\u0026interruption-level=time-sensitive\u0026@@PhoneNumber@@ 信息\r因为@@PhoneNumber@@是必填的，所以随便放入即可 配置通知规则\r控制台 -\u003e 通知中心 -\u003e 系统通知规则 -\u003e 创建规则 手机号随便填写即可 多创建几个规则，用于不同情形 修改 chanify.yml\r增加如下内容 client: nas: sound: 1 # 是否有提示音 endpoint: https://message.papergate.top:5000 token: xxxxx interruption-level: time-sensitive sound声音提醒： 1 启用默认声音 声音代码，例如 bell interruption-level中断级别： active：点亮屏幕并播放声音。 passive：不点亮屏幕或播放声音。 time-sensitive：点亮屏幕并播放声音，在“勿扰模式”期间显示。 示例\r","date":"2024-12-29","objectID":"/posts/chanify-%E6%B6%88%E6%81%AF%E6%8E%A8%E9%80%81/:3:3","tags":["消息推送"],"title":"Chanify 消息推送","uri":"/posts/chanify-%E6%B6%88%E6%81%AF%E6%8E%A8%E9%80%81/#修改-chanifyyml-2"},{"categories":["玩转NAS"],"collections":["玩转NAS"],"content":"12.3 威联通 Nas syslog 推送手机\r添加频道\r按照之前的说法添加一个频道，比如命名为Nas 配置短信服务\r控制台 -\u003e 通知中心 -\u003e 服务账户和设备配对 -\u003e 短信 -\u003e 添加 SMSC 服务 URL 模板格式如下，可以多配几个不同的，修改一下title的值：： # 1 http://ADDRESS:PORT/v1/sender/TOKEN?title=syslog\u0026text=@@Text@@\u0026sound=1\u0026interruption-level=time-sensitive\u0026@@PhoneNumber@@ # 2 http://ADDRESS:PORT/v1/sender/TOKEN?title=告警信息\u0026text=@@Text@@\u0026sound=1\u0026interruption-level=time-sensitive\u0026@@PhoneNumber@@ # 3 http://ADDRESS:PORT/v1/sender/TOKEN?title=应用信息\u0026text=@@Text@@\u0026sound=1\u0026interruption-level=time-sensitive\u0026@@PhoneNumber@@ 信息\r因为@@PhoneNumber@@是必填的，所以随便放入即可 配置通知规则\r控制台 -\u003e 通知中心 -\u003e 系统通知规则 -\u003e 创建规则 手机号随便填写即可 多创建几个规则，用于不同情形 修改 chanify.yml\r增加如下内容 client: nas: sound: 1 # 是否有提示音 endpoint: https://message.papergate.top:5000 token: xxxxx interruption-level: time-sensitive sound声音提醒： 1 启用默认声音 声音代码，例如 bell interruption-level中断级别： active：点亮屏幕并播放声音。 passive：不点亮屏幕或播放声音。 time-sensitive：点亮屏幕并播放声音，在“勿扰模式”期间显示。 示例\r","date":"2024-12-29","objectID":"/posts/chanify-%E6%B6%88%E6%81%AF%E6%8E%A8%E9%80%81/:3:3","tags":["消息推送"],"title":"Chanify 消息推送","uri":"/posts/chanify-%E6%B6%88%E6%81%AF%E6%8E%A8%E9%80%81/#示例-1"},{"categories":["玩转NAS"],"collections":["玩转NAS"],"content":"12.4 Crawlab 推送手机\r爬虫运行完成有时需要汇报一下结果，需要使用python脚本推送数据到手机 添加频道\r按照之前的说法添加一个频道，比如命名为crawlab 写python脚本\rimport requests TOKEN = 'xxxxx' def send_message(title,text): data = { 'token':TOKEN, 'title': title, 'text': text, 'sound': 1, 'interruptionlevel': 'time-sensitive' } requests.post(url=f\"https://ADDRESS:PORT/v1/sender/{TOKEN}\", json=data) 信息\r建议使用 POST 方式，以 JSON 方式传输数据，其他方式不支持设置title 修改 chanify.yml\r增加如下内容 client: crawlab: sound: 1 # 是否有提示音 endpoint: https://message.papergate.top:5000 token: xxxxx interruption-level: time-sensitive sound声音提醒： 1 启用默认声音 声音代码，例如 bell interruption-level中断级别： active：点亮屏幕并播放声音。 passive：不点亮屏幕或播放声音。 time-sensitive：点亮屏幕并播放声音，在“勿扰模式”期间显示。 ","date":"2024-12-29","objectID":"/posts/chanify-%E6%B6%88%E6%81%AF%E6%8E%A8%E9%80%81/:3:4","tags":["消息推送"],"title":"Chanify 消息推送","uri":"/posts/chanify-%E6%B6%88%E6%81%AF%E6%8E%A8%E9%80%81/#crawlab-推送手机"},{"categories":["玩转NAS"],"collections":["玩转NAS"],"content":"12.4 Crawlab 推送手机\r爬虫运行完成有时需要汇报一下结果，需要使用python脚本推送数据到手机 添加频道\r按照之前的说法添加一个频道，比如命名为crawlab 写python脚本\rimport requests TOKEN = 'xxxxx' def send_message(title,text): data = { 'token':TOKEN, 'title': title, 'text': text, 'sound': 1, 'interruptionlevel': 'time-sensitive' } requests.post(url=f\"https://ADDRESS:PORT/v1/sender/{TOKEN}\", json=data) 信息\r建议使用 POST 方式，以 JSON 方式传输数据，其他方式不支持设置title 修改 chanify.yml\r增加如下内容 client: crawlab: sound: 1 # 是否有提示音 endpoint: https://message.papergate.top:5000 token: xxxxx interruption-level: time-sensitive sound声音提醒： 1 启用默认声音 声音代码，例如 bell interruption-level中断级别： active：点亮屏幕并播放声音。 passive：不点亮屏幕或播放声音。 time-sensitive：点亮屏幕并播放声音，在“勿扰模式”期间显示。 ","date":"2024-12-29","objectID":"/posts/chanify-%E6%B6%88%E6%81%AF%E6%8E%A8%E9%80%81/:3:4","tags":["消息推送"],"title":"Chanify 消息推送","uri":"/posts/chanify-%E6%B6%88%E6%81%AF%E6%8E%A8%E9%80%81/#添加频道-3"},{"categories":["玩转NAS"],"collections":["玩转NAS"],"content":"12.4 Crawlab 推送手机\r爬虫运行完成有时需要汇报一下结果，需要使用python脚本推送数据到手机 添加频道\r按照之前的说法添加一个频道，比如命名为crawlab 写python脚本\rimport requests TOKEN = 'xxxxx' def send_message(title,text): data = { 'token':TOKEN, 'title': title, 'text': text, 'sound': 1, 'interruptionlevel': 'time-sensitive' } requests.post(url=f\"https://ADDRESS:PORT/v1/sender/{TOKEN}\", json=data) 信息\r建议使用 POST 方式，以 JSON 方式传输数据，其他方式不支持设置title 修改 chanify.yml\r增加如下内容 client: crawlab: sound: 1 # 是否有提示音 endpoint: https://message.papergate.top:5000 token: xxxxx interruption-level: time-sensitive sound声音提醒： 1 启用默认声音 声音代码，例如 bell interruption-level中断级别： active：点亮屏幕并播放声音。 passive：不点亮屏幕或播放声音。 time-sensitive：点亮屏幕并播放声音，在“勿扰模式”期间显示。 ","date":"2024-12-29","objectID":"/posts/chanify-%E6%B6%88%E6%81%AF%E6%8E%A8%E9%80%81/:3:4","tags":["消息推送"],"title":"Chanify 消息推送","uri":"/posts/chanify-%E6%B6%88%E6%81%AF%E6%8E%A8%E9%80%81/#写python脚本"},{"categories":["玩转NAS"],"collections":["玩转NAS"],"content":"12.4 Crawlab 推送手机\r爬虫运行完成有时需要汇报一下结果，需要使用python脚本推送数据到手机 添加频道\r按照之前的说法添加一个频道，比如命名为crawlab 写python脚本\rimport requests TOKEN = 'xxxxx' def send_message(title,text): data = { 'token':TOKEN, 'title': title, 'text': text, 'sound': 1, 'interruptionlevel': 'time-sensitive' } requests.post(url=f\"https://ADDRESS:PORT/v1/sender/{TOKEN}\", json=data) 信息\r建议使用 POST 方式，以 JSON 方式传输数据，其他方式不支持设置title 修改 chanify.yml\r增加如下内容 client: crawlab: sound: 1 # 是否有提示音 endpoint: https://message.papergate.top:5000 token: xxxxx interruption-level: time-sensitive sound声音提醒： 1 启用默认声音 声音代码，例如 bell interruption-level中断级别： active：点亮屏幕并播放声音。 passive：不点亮屏幕或播放声音。 time-sensitive：点亮屏幕并播放声音，在“勿扰模式”期间显示。 ","date":"2024-12-29","objectID":"/posts/chanify-%E6%B6%88%E6%81%AF%E6%8E%A8%E9%80%81/:3:4","tags":["消息推送"],"title":"Chanify 消息推送","uri":"/posts/chanify-%E6%B6%88%E6%81%AF%E6%8E%A8%E9%80%81/#修改-chanifyyml-3"},{"categories":["玩转NAS"],"collections":["玩转NAS"],"content":"12.5 Artalk 评论系统推送手机\r添加频道\r按照之前的说法添加一个频道，比如命名为comment 设置 WebHook\rArtalk 后台设置一下 WebHook 要发送的 URL 编写后端进行解析转发\r比如用 Docker 在内网环境简单搭建一个 FastApi from fastapi import FastAPI, Request from urllib.parse import unquote from utils import send_message app = FastAPI() @app.post(\"/artalk\") async def receive_webhook(request: Request): # 获取请求的 JSON 数据 data = await request.json() # 提取关键字段 comment = data.get(\"comment\", {}) comment_nick = comment.get(\"nick\", \"未知用户\") comment_date = comment.get(\"date\", \"未知时间\") page_url = comment.get(\"page_url\", \"\") comment_content = comment.get(\"content\", \"无内容\") # 从 URL 提取文章名称并进行 URL 解码 try: article_name_encoded = page_url.split(\"posts/\")[1].split(\"/\")[0] article_name = unquote(article_name_encoded) except (IndexError, ValueError): article_name = \"未知文章\" # 构造标题和内容 title = f\"{comment_nick} 发表了评论\" text = f\"博客名称：{article_name}\\n时间：{comment_date}\\n评论内容：{comment_content}\" send_message(title=title, text=text) 修改 chanify.yml\r增加如下内容 client: comment: sound: 1 # 是否有提示音 endpoint: https://message.papergate.top:5000 token: xxxxx interruption-level: time-sensitive sound声音提醒： 1 启用默认声音 声音代码，例如 bell interruption-level中断级别： active：点亮屏幕并播放声音。 passive：不点亮屏幕或播放声音。 time-sensitive：点亮屏幕并播放声音，在“勿扰模式”期间显示。 示例\r","date":"2024-12-29","objectID":"/posts/chanify-%E6%B6%88%E6%81%AF%E6%8E%A8%E9%80%81/:3:5","tags":["消息推送"],"title":"Chanify 消息推送","uri":"/posts/chanify-%E6%B6%88%E6%81%AF%E6%8E%A8%E9%80%81/#artalk-评论系统推送手机"},{"categories":["玩转NAS"],"collections":["玩转NAS"],"content":"12.5 Artalk 评论系统推送手机\r添加频道\r按照之前的说法添加一个频道，比如命名为comment 设置 WebHook\rArtalk 后台设置一下 WebHook 要发送的 URL 编写后端进行解析转发\r比如用 Docker 在内网环境简单搭建一个 FastApi from fastapi import FastAPI, Request from urllib.parse import unquote from utils import send_message app = FastAPI() @app.post(\"/artalk\") async def receive_webhook(request: Request): # 获取请求的 JSON 数据 data = await request.json() # 提取关键字段 comment = data.get(\"comment\", {}) comment_nick = comment.get(\"nick\", \"未知用户\") comment_date = comment.get(\"date\", \"未知时间\") page_url = comment.get(\"page_url\", \"\") comment_content = comment.get(\"content\", \"无内容\") # 从 URL 提取文章名称并进行 URL 解码 try: article_name_encoded = page_url.split(\"posts/\")[1].split(\"/\")[0] article_name = unquote(article_name_encoded) except (IndexError, ValueError): article_name = \"未知文章\" # 构造标题和内容 title = f\"{comment_nick} 发表了评论\" text = f\"博客名称：{article_name}\\n时间：{comment_date}\\n评论内容：{comment_content}\" send_message(title=title, text=text) 修改 chanify.yml\r增加如下内容 client: comment: sound: 1 # 是否有提示音 endpoint: https://message.papergate.top:5000 token: xxxxx interruption-level: time-sensitive sound声音提醒： 1 启用默认声音 声音代码，例如 bell interruption-level中断级别： active：点亮屏幕并播放声音。 passive：不点亮屏幕或播放声音。 time-sensitive：点亮屏幕并播放声音，在“勿扰模式”期间显示。 示例\r","date":"2024-12-29","objectID":"/posts/chanify-%E6%B6%88%E6%81%AF%E6%8E%A8%E9%80%81/:3:5","tags":["消息推送"],"title":"Chanify 消息推送","uri":"/posts/chanify-%E6%B6%88%E6%81%AF%E6%8E%A8%E9%80%81/#添加频道-4"},{"categories":["玩转NAS"],"collections":["玩转NAS"],"content":"12.5 Artalk 评论系统推送手机\r添加频道\r按照之前的说法添加一个频道，比如命名为comment 设置 WebHook\rArtalk 后台设置一下 WebHook 要发送的 URL 编写后端进行解析转发\r比如用 Docker 在内网环境简单搭建一个 FastApi from fastapi import FastAPI, Request from urllib.parse import unquote from utils import send_message app = FastAPI() @app.post(\"/artalk\") async def receive_webhook(request: Request): # 获取请求的 JSON 数据 data = await request.json() # 提取关键字段 comment = data.get(\"comment\", {}) comment_nick = comment.get(\"nick\", \"未知用户\") comment_date = comment.get(\"date\", \"未知时间\") page_url = comment.get(\"page_url\", \"\") comment_content = comment.get(\"content\", \"无内容\") # 从 URL 提取文章名称并进行 URL 解码 try: article_name_encoded = page_url.split(\"posts/\")[1].split(\"/\")[0] article_name = unquote(article_name_encoded) except (IndexError, ValueError): article_name = \"未知文章\" # 构造标题和内容 title = f\"{comment_nick} 发表了评论\" text = f\"博客名称：{article_name}\\n时间：{comment_date}\\n评论内容：{comment_content}\" send_message(title=title, text=text) 修改 chanify.yml\r增加如下内容 client: comment: sound: 1 # 是否有提示音 endpoint: https://message.papergate.top:5000 token: xxxxx interruption-level: time-sensitive sound声音提醒： 1 启用默认声音 声音代码，例如 bell interruption-level中断级别： active：点亮屏幕并播放声音。 passive：不点亮屏幕或播放声音。 time-sensitive：点亮屏幕并播放声音，在“勿扰模式”期间显示。 示例\r","date":"2024-12-29","objectID":"/posts/chanify-%E6%B6%88%E6%81%AF%E6%8E%A8%E9%80%81/:3:5","tags":["消息推送"],"title":"Chanify 消息推送","uri":"/posts/chanify-%E6%B6%88%E6%81%AF%E6%8E%A8%E9%80%81/#设置-webhook"},{"categories":["玩转NAS"],"collections":["玩转NAS"],"content":"12.5 Artalk 评论系统推送手机\r添加频道\r按照之前的说法添加一个频道，比如命名为comment 设置 WebHook\rArtalk 后台设置一下 WebHook 要发送的 URL 编写后端进行解析转发\r比如用 Docker 在内网环境简单搭建一个 FastApi from fastapi import FastAPI, Request from urllib.parse import unquote from utils import send_message app = FastAPI() @app.post(\"/artalk\") async def receive_webhook(request: Request): # 获取请求的 JSON 数据 data = await request.json() # 提取关键字段 comment = data.get(\"comment\", {}) comment_nick = comment.get(\"nick\", \"未知用户\") comment_date = comment.get(\"date\", \"未知时间\") page_url = comment.get(\"page_url\", \"\") comment_content = comment.get(\"content\", \"无内容\") # 从 URL 提取文章名称并进行 URL 解码 try: article_name_encoded = page_url.split(\"posts/\")[1].split(\"/\")[0] article_name = unquote(article_name_encoded) except (IndexError, ValueError): article_name = \"未知文章\" # 构造标题和内容 title = f\"{comment_nick} 发表了评论\" text = f\"博客名称：{article_name}\\n时间：{comment_date}\\n评论内容：{comment_content}\" send_message(title=title, text=text) 修改 chanify.yml\r增加如下内容 client: comment: sound: 1 # 是否有提示音 endpoint: https://message.papergate.top:5000 token: xxxxx interruption-level: time-sensitive sound声音提醒： 1 启用默认声音 声音代码，例如 bell interruption-level中断级别： active：点亮屏幕并播放声音。 passive：不点亮屏幕或播放声音。 time-sensitive：点亮屏幕并播放声音，在“勿扰模式”期间显示。 示例\r","date":"2024-12-29","objectID":"/posts/chanify-%E6%B6%88%E6%81%AF%E6%8E%A8%E9%80%81/:3:5","tags":["消息推送"],"title":"Chanify 消息推送","uri":"/posts/chanify-%E6%B6%88%E6%81%AF%E6%8E%A8%E9%80%81/#编写后端进行解析转发"},{"categories":["玩转NAS"],"collections":["玩转NAS"],"content":"12.5 Artalk 评论系统推送手机\r添加频道\r按照之前的说法添加一个频道，比如命名为comment 设置 WebHook\rArtalk 后台设置一下 WebHook 要发送的 URL 编写后端进行解析转发\r比如用 Docker 在内网环境简单搭建一个 FastApi from fastapi import FastAPI, Request from urllib.parse import unquote from utils import send_message app = FastAPI() @app.post(\"/artalk\") async def receive_webhook(request: Request): # 获取请求的 JSON 数据 data = await request.json() # 提取关键字段 comment = data.get(\"comment\", {}) comment_nick = comment.get(\"nick\", \"未知用户\") comment_date = comment.get(\"date\", \"未知时间\") page_url = comment.get(\"page_url\", \"\") comment_content = comment.get(\"content\", \"无内容\") # 从 URL 提取文章名称并进行 URL 解码 try: article_name_encoded = page_url.split(\"posts/\")[1].split(\"/\")[0] article_name = unquote(article_name_encoded) except (IndexError, ValueError): article_name = \"未知文章\" # 构造标题和内容 title = f\"{comment_nick} 发表了评论\" text = f\"博客名称：{article_name}\\n时间：{comment_date}\\n评论内容：{comment_content}\" send_message(title=title, text=text) 修改 chanify.yml\r增加如下内容 client: comment: sound: 1 # 是否有提示音 endpoint: https://message.papergate.top:5000 token: xxxxx interruption-level: time-sensitive sound声音提醒： 1 启用默认声音 声音代码，例如 bell interruption-level中断级别： active：点亮屏幕并播放声音。 passive：不点亮屏幕或播放声音。 time-sensitive：点亮屏幕并播放声音，在“勿扰模式”期间显示。 示例\r","date":"2024-12-29","objectID":"/posts/chanify-%E6%B6%88%E6%81%AF%E6%8E%A8%E9%80%81/:3:5","tags":["消息推送"],"title":"Chanify 消息推送","uri":"/posts/chanify-%E6%B6%88%E6%81%AF%E6%8E%A8%E9%80%81/#修改-chanifyyml-4"},{"categories":["玩转NAS"],"collections":["玩转NAS"],"content":"12.5 Artalk 评论系统推送手机\r添加频道\r按照之前的说法添加一个频道，比如命名为comment 设置 WebHook\rArtalk 后台设置一下 WebHook 要发送的 URL 编写后端进行解析转发\r比如用 Docker 在内网环境简单搭建一个 FastApi from fastapi import FastAPI, Request from urllib.parse import unquote from utils import send_message app = FastAPI() @app.post(\"/artalk\") async def receive_webhook(request: Request): # 获取请求的 JSON 数据 data = await request.json() # 提取关键字段 comment = data.get(\"comment\", {}) comment_nick = comment.get(\"nick\", \"未知用户\") comment_date = comment.get(\"date\", \"未知时间\") page_url = comment.get(\"page_url\", \"\") comment_content = comment.get(\"content\", \"无内容\") # 从 URL 提取文章名称并进行 URL 解码 try: article_name_encoded = page_url.split(\"posts/\")[1].split(\"/\")[0] article_name = unquote(article_name_encoded) except (IndexError, ValueError): article_name = \"未知文章\" # 构造标题和内容 title = f\"{comment_nick} 发表了评论\" text = f\"博客名称：{article_name}\\n时间：{comment_date}\\n评论内容：{comment_content}\" send_message(title=title, text=text) 修改 chanify.yml\r增加如下内容 client: comment: sound: 1 # 是否有提示音 endpoint: https://message.papergate.top:5000 token: xxxxx interruption-level: time-sensitive sound声音提醒： 1 启用默认声音 声音代码，例如 bell interruption-level中断级别： active：点亮屏幕并播放声音。 passive：不点亮屏幕或播放声音。 time-sensitive：点亮屏幕并播放声音，在“勿扰模式”期间显示。 示例\r","date":"2024-12-29","objectID":"/posts/chanify-%E6%B6%88%E6%81%AF%E6%8E%A8%E9%80%81/:3:5","tags":["消息推送"],"title":"Chanify 消息推送","uri":"/posts/chanify-%E6%B6%88%E6%81%AF%E6%8E%A8%E9%80%81/#示例-2"},{"categories":["玩转NAS"],"collections":["玩转NAS"],"content":"第 7 节 概述\rCrawlab 是强大的 网络爬虫管理平台（WCMP），它能够运行多种编程语言（包括 Python、Go、Node.js、Java、C#）或爬虫框架（包括 Scrapy、Colly、Selenium、Puppeteer）开发的网路爬虫。它能够用来运行、管理和监控网络爬虫，特别是对可溯性、可扩展性以及稳定性要求较高的生产环境。 ","date":"2024-12-25","objectID":"/posts/crawlab-%E7%BD%91%E7%BB%9C%E7%88%AC%E8%99%AB%E7%AE%A1%E7%90%86/:1:0","tags":["爬虫"],"title":"Crawlab 网络爬虫管理","uri":"/posts/crawlab-%E7%BD%91%E7%BB%9C%E7%88%AC%E8%99%AB%E7%AE%A1%E7%90%86/#概述"},{"categories":["玩转NAS"],"collections":["玩转NAS"],"content":"7.1 基本组成\rgraph TD;\rSpider --\u003e|belongs to| Project;\rSpider --\u003e|has| Schedule;\rSpider --\u003e|runs| Task;\rSchedule --\u003e|triggers| Task;\rTask --\u003e|runs on| Node;\rgraph TD;\rSpider --\u003e|belongs to| Project;\rSpider --\u003e|has| Schedule;\rSpider --\u003e|runs| Task;\rSchedule --\u003e|triggers| Task;\rTask --\u003e|runs on| Node;\rgraph TD;\rSpider --\u003e|belongs to| Project;\rSpider --\u003e|has| Schedule;\rSpider --\u003e|runs| Task;\rSchedule --\u003e|triggers| Task;\rTask --\u003e|runs on| Node;\rgraph TD;\rSpider --\u003e|belongs to| Project;\rSpider --\u003e|has| Schedule;\rSpider --\u003e|runs| Task;\rSchedule --\u003e|triggers| Task;\rTask --\u003e|runs on| Node;\rCrawlab 中的基本单位是Spider，一个Spider代表一个爬虫项目 多个Spider可以组成一个Project，代表同类爬虫项目，Spider也可以单独运行 Spider可以直接手动运行，生成一个Task，也可以创建Schedule，通过Schedule触发运行生成Task Task运行在Node上，一个Node代表一台服务器，Node由一台Master服务器和若干Worker服务器组成 通常任务仅需一台Master服务器即可，官方不建议在一台服务器上搭建多个Node 在一台服务器上搭建多个Node可以较为简单地实现多进程爬虫，对于爬虫任务比较多或者CPU密集型的任务可能会有作用 ","date":"2024-12-25","objectID":"/posts/crawlab-%E7%BD%91%E7%BB%9C%E7%88%AC%E8%99%AB%E7%AE%A1%E7%90%86/:1:1","tags":["爬虫"],"title":"Crawlab 网络爬虫管理","uri":"/posts/crawlab-%E7%BD%91%E7%BB%9C%E7%88%AC%E8%99%AB%E7%AE%A1%E7%90%86/#基本组成"},{"categories":["玩转NAS"],"collections":["玩转NAS"],"content":"7.2 数据集成\rScrapy\r在 settings.py 文件中，将 crawlab.CrawlabPipeline 添加到 Item_PIPELINES 中，可以自动存储爬取到的数据： ITEM_PIPELINES = { 'crawlab.CrawlabPipeline': 300, } 去item.py中新增一个Item，并且yield item即可。 Python\r将爬取到的数据通过以下方式手动保存： from crawlab import save_item save_item({'title': 'example', 'url': 'https://example.com'}) Selenium\rselenium 中和 python 一样需要手动保存数据，此外还需要无头浏览器等设置以保证正常运行： chrome_options.add_argument('--headless') chrome_options.add_argument('--no-sandbox') chrome_options.add_argument('--disable-dev-shm-usage') ","date":"2024-12-25","objectID":"/posts/crawlab-%E7%BD%91%E7%BB%9C%E7%88%AC%E8%99%AB%E7%AE%A1%E7%90%86/:1:2","tags":["爬虫"],"title":"Crawlab 网络爬虫管理","uri":"/posts/crawlab-%E7%BD%91%E7%BB%9C%E7%88%AC%E8%99%AB%E7%AE%A1%E7%90%86/#数据集成"},{"categories":["玩转NAS"],"collections":["玩转NAS"],"content":"7.2 数据集成\rScrapy\r在 settings.py 文件中，将 crawlab.CrawlabPipeline 添加到 Item_PIPELINES 中，可以自动存储爬取到的数据： ITEM_PIPELINES = { 'crawlab.CrawlabPipeline': 300, } 去item.py中新增一个Item，并且yield item即可。 Python\r将爬取到的数据通过以下方式手动保存： from crawlab import save_item save_item({'title': 'example', 'url': 'https://example.com'}) Selenium\rselenium 中和 python 一样需要手动保存数据，此外还需要无头浏览器等设置以保证正常运行： chrome_options.add_argument('--headless') chrome_options.add_argument('--no-sandbox') chrome_options.add_argument('--disable-dev-shm-usage') ","date":"2024-12-25","objectID":"/posts/crawlab-%E7%BD%91%E7%BB%9C%E7%88%AC%E8%99%AB%E7%AE%A1%E7%90%86/:1:2","tags":["爬虫"],"title":"Crawlab 网络爬虫管理","uri":"/posts/crawlab-%E7%BD%91%E7%BB%9C%E7%88%AC%E8%99%AB%E7%AE%A1%E7%90%86/#scrapy"},{"categories":["玩转NAS"],"collections":["玩转NAS"],"content":"7.2 数据集成\rScrapy\r在 settings.py 文件中，将 crawlab.CrawlabPipeline 添加到 Item_PIPELINES 中，可以自动存储爬取到的数据： ITEM_PIPELINES = { 'crawlab.CrawlabPipeline': 300, } 去item.py中新增一个Item，并且yield item即可。 Python\r将爬取到的数据通过以下方式手动保存： from crawlab import save_item save_item({'title': 'example', 'url': 'https://example.com'}) Selenium\rselenium 中和 python 一样需要手动保存数据，此外还需要无头浏览器等设置以保证正常运行： chrome_options.add_argument('--headless') chrome_options.add_argument('--no-sandbox') chrome_options.add_argument('--disable-dev-shm-usage') ","date":"2024-12-25","objectID":"/posts/crawlab-%E7%BD%91%E7%BB%9C%E7%88%AC%E8%99%AB%E7%AE%A1%E7%90%86/:1:2","tags":["爬虫"],"title":"Crawlab 网络爬虫管理","uri":"/posts/crawlab-%E7%BD%91%E7%BB%9C%E7%88%AC%E8%99%AB%E7%AE%A1%E7%90%86/#python"},{"categories":["玩转NAS"],"collections":["玩转NAS"],"content":"7.2 数据集成\rScrapy\r在 settings.py 文件中，将 crawlab.CrawlabPipeline 添加到 Item_PIPELINES 中，可以自动存储爬取到的数据： ITEM_PIPELINES = { 'crawlab.CrawlabPipeline': 300, } 去item.py中新增一个Item，并且yield item即可。 Python\r将爬取到的数据通过以下方式手动保存： from crawlab import save_item save_item({'title': 'example', 'url': 'https://example.com'}) Selenium\rselenium 中和 python 一样需要手动保存数据，此外还需要无头浏览器等设置以保证正常运行： chrome_options.add_argument('--headless') chrome_options.add_argument('--no-sandbox') chrome_options.add_argument('--disable-dev-shm-usage') ","date":"2024-12-25","objectID":"/posts/crawlab-%E7%BD%91%E7%BB%9C%E7%88%AC%E8%99%AB%E7%AE%A1%E7%90%86/:1:2","tags":["爬虫"],"title":"Crawlab 网络爬虫管理","uri":"/posts/crawlab-%E7%BD%91%E7%BB%9C%E7%88%AC%E8%99%AB%E7%AE%A1%E7%90%86/#selenium"},{"categories":["玩转NAS"],"collections":["玩转NAS"],"content":"7.3 数据库\r目前 Crawlab 支持MongoDB、MySQL、PostgreSQL、SQL Server、SQLite、CockroachDB、ElasticSearch、Kafka等多种数据库。 对于MongoDB这种 NoSQL 的数据库，Crawlab 处理起来是非常简单的，直接使用上面所说的数据集成方法，无需手动创建数据库，就可以一步到位实现数据存储，并且可以实现数据的扩展，灵活性较好。但是，由于没有数据类型，实际进行数据分析时，会遇到一些问题。 所以就本人经验而言，对于结构化的数据，为了数据分析时更加轻松，还是尽可能使用关系型数据库会更好一些。使用关系型数据库需要提前构建数据表，Crawlab并不支持自动创建关系型数据表，并且类型推断结果也不准确。 需要手动额外创建的字段有两个_id和_tid _id是主键，需要设置成自增，比如在 MySQL 中可以设置数据类型为bigint _tid是任务的 ID 号，相同 ID 号的数据表明是在同一次任务中添加的，数据值由 Crawlab 来添加，数据类型为字符串，对于是否为主键没有要求，比如在 MySQL 中可以设置数据类型为varchar ","date":"2024-12-25","objectID":"/posts/crawlab-%E7%BD%91%E7%BB%9C%E7%88%AC%E8%99%AB%E7%AE%A1%E7%90%86/:1:3","tags":["爬虫"],"title":"Crawlab 网络爬虫管理","uri":"/posts/crawlab-%E7%BD%91%E7%BB%9C%E7%88%AC%E8%99%AB%E7%AE%A1%E7%90%86/#数据库"},{"categories":["玩转NAS"],"collections":["玩转NAS"],"content":"7.4 定时任务\r定时任务采用 Cron 表达式来表示执行任务的周期： * * * * * Command_to_execute | | | | | | | | | Day of the Week ( 0 - 6 ) ( Sunday = 0 ) | | | | | | | Month ( 1 - 12 ) | | | | | Day of Month ( 1 - 31 ) | | | Hour ( 0 - 23 ) | Min ( 0 - 59 ) ","date":"2024-12-25","objectID":"/posts/crawlab-%E7%BD%91%E7%BB%9C%E7%88%AC%E8%99%AB%E7%AE%A1%E7%90%86/:1:4","tags":["爬虫"],"title":"Crawlab 网络爬虫管理","uri":"/posts/crawlab-%E7%BD%91%E7%BB%9C%E7%88%AC%E8%99%AB%E7%AE%A1%E7%90%86/#定时任务"},{"categories":["玩转NAS"],"collections":["玩转NAS"],"content":"第 8 节 Crawlab 运行 Scrapy 项目\r","date":"2024-12-25","objectID":"/posts/crawlab-%E7%BD%91%E7%BB%9C%E7%88%AC%E8%99%AB%E7%AE%A1%E7%90%86/:2:0","tags":["爬虫"],"title":"Crawlab 网络爬虫管理","uri":"/posts/crawlab-%E7%BD%91%E7%BB%9C%E7%88%AC%E8%99%AB%E7%AE%A1%E7%90%86/#crawlab-运行-scrapy-项目"},{"categories":["玩转NAS"],"collections":["玩转NAS"],"content":"8.1 创建项目\rscrapy startproject PROJECT_NAME cd PROJECT_NAME scrapy genspider SPIDER_NAME URL git init curl -L -o .gitignore https://www.toptal.com/developers/gitignore/api/python,scrapy touch README.md git add . git commit -m \"Initial commit for Scrapy project\" git remote add origin http://git.papergate.top:5000/aphros/PROJECT_NAME.git git push -u origin main ","date":"2024-12-25","objectID":"/posts/crawlab-%E7%BD%91%E7%BB%9C%E7%88%AC%E8%99%AB%E7%AE%A1%E7%90%86/:2:1","tags":["爬虫"],"title":"Crawlab 网络爬虫管理","uri":"/posts/crawlab-%E7%BD%91%E7%BB%9C%E7%88%AC%E8%99%AB%E7%AE%A1%E7%90%86/#创建项目"},{"categories":["玩转NAS"],"collections":["玩转NAS"],"content":"8.2 编写爬虫程序\r这里通过一个官方示例进行说明：快速教程 打开 scrapy_quotes/scrapy_quotes/spiders/quotes.py 并将如下内容替换掉原文件内容。 # scrapy_quotes/scrapy_quotes/spiders/quotes.py # 导入 scrapy 库，这是一个用于编写网络爬虫的 Python 框架 import scrapy # 定义一个名为 QuotesSpider 的爬虫类，继承自 scrapy.Spider 类 class QuotesSpider(scrapy.Spider): # 为爬虫指定一个名称，这个名称在 Scrapy 项目中是唯一的 name = 'quotes' # 指定允许爬取的域名列表，这里只有一个域名 quotes.toscrape.com allowed_domains = ['quotes.toscrape.com'] # 设置爬虫的起始 URL 列表，爬虫将从这些 URL 开始抓取数据 start_urls = ['http://quotes.toscrape.com/'] # 定义一个名为 parse 的方法，这是爬虫的主要处理逻辑 def parse(self, response): # 使用 CSS 选择器选择页面中所有 div.quote 元素，并遍历这些元素 for quote in response.css(\"div.quote\"): # 生成一个包含名言警句、作者和标签信息的字典 yield { # 提取名言警句文本 'text': quote.css(\"span.text::text\").extract_first(), # 提取作者姓名 'author': quote.css(\"small.author::text\").extract_first(), # 提取标签列表，然后使用 ','.join(...) 将标签列表连接成一个字符串 'tags': ','.join(quote.css(\"div.tags \u003e a.tag::text\").extract()) } # 使用 CSS 选择器选择下一页链接，并提取链接 URL next_page_url = response.css(\"li.next \u003e a::attr(href)\").extract_first() # 检查下一页链接是否存在 if next_page_url is not None: # 如果下一页链接存在，创建一个新的 scrapy.Request 对象，并将其提交给 Scrapy 引擎，以便继续爬取下一页 yield scrapy.Request(response.urljoin(next_page_url)) 然后打开文件 scrapy_quotes/scrapy_quotes/settings.py 并将如下代码加入到文件最后。 # 加入到 scrapy_quotes/scrapy_quotes/settings.py 最后 ITEM_PIPELINES = { 'crawlab.CrawlabPipeline': 300, } ","date":"2024-12-25","objectID":"/posts/crawlab-%E7%BD%91%E7%BB%9C%E7%88%AC%E8%99%AB%E7%AE%A1%E7%90%86/:2:2","tags":["爬虫"],"title":"Crawlab 网络爬虫管理","uri":"/posts/crawlab-%E7%BD%91%E7%BB%9C%E7%88%AC%E8%99%AB%E7%AE%A1%E7%90%86/#编写爬虫程序"},{"categories":["玩转NAS"],"collections":["玩转NAS"],"content":"8.3 创建 Crawlab 爬虫\r爬虫-\u003e新建爬虫 # 名称 CRAWLAB_SPIDER_NAME # 执行命令 scrapy crawl SCRAPY_SPIDER_NAME ","date":"2024-12-25","objectID":"/posts/crawlab-%E7%BD%91%E7%BB%9C%E7%88%AC%E8%99%AB%E7%AE%A1%E7%90%86/:2:3","tags":["爬虫"],"title":"Crawlab 网络爬虫管理","uri":"/posts/crawlab-%E7%BD%91%E7%BB%9C%E7%88%AC%E8%99%AB%E7%AE%A1%E7%90%86/#创建-crawlab-爬虫"},{"categories":["玩转NAS"],"collections":["玩转NAS"],"content":"8.4 从 Git 拉取 Scrapy 项目\r","date":"2024-12-25","objectID":"/posts/crawlab-%E7%BD%91%E7%BB%9C%E7%88%AC%E8%99%AB%E7%AE%A1%E7%90%86/:2:4","tags":["爬虫"],"title":"Crawlab 网络爬虫管理","uri":"/posts/crawlab-%E7%BD%91%E7%BB%9C%E7%88%AC%E8%99%AB%E7%AE%A1%E7%90%86/#从-git-拉取-scrapy-项目"},{"categories":["玩转NAS"],"collections":["玩转NAS"],"content":"8.5 运行爬虫\r","date":"2024-12-25","objectID":"/posts/crawlab-%E7%BD%91%E7%BB%9C%E7%88%AC%E8%99%AB%E7%AE%A1%E7%90%86/:2:5","tags":["爬虫"],"title":"Crawlab 网络爬虫管理","uri":"/posts/crawlab-%E7%BD%91%E7%BB%9C%E7%88%AC%E8%99%AB%E7%AE%A1%E7%90%86/#运行爬虫"},{"categories":["玩转NAS"],"collections":["玩转NAS"],"content":"8.6 查看执行情况\r爬取到的数据如下： ","date":"2024-12-25","objectID":"/posts/crawlab-%E7%BD%91%E7%BB%9C%E7%88%AC%E8%99%AB%E7%AE%A1%E7%90%86/:2:6","tags":["爬虫"],"title":"Crawlab 网络爬虫管理","uri":"/posts/crawlab-%E7%BD%91%E7%BB%9C%E7%88%AC%E8%99%AB%E7%AE%A1%E7%90%86/#查看执行情况"},{"categories":"旅游","collections":["超超的奇妙冒险"],"content":"第 1 节 启程\r踏入洛阳老君山，首先映入眼帘的便是一座古朴的石碑，它静静矗立，仿佛在诉说这座道教圣地的千年传奇。 石碑身后，太上老君的雕塑巍然端坐，目光深邃而悠远，俯瞰着前来朝圣的芸芸众生，让人不由心生敬畏。 穿过刻有“众妙门”三字的山门，便正式来到了夜爬起点，望着前方绵延23公里的登山道，心中既期待又兴奋，一段难忘的旅程就此展开。 ","date":"2024-06-10","objectID":"/posts/%E5%A4%9C%E7%88%AC%E8%80%81%E5%90%9B%E5%B1%B1%E7%AD%89%E4%B8%80%E5%9C%BA%E9%87%91%E9%A1%B6%E4%BA%91%E6%B5%B7%E6%97%A5%E5%87%BA/:1:0","tags":["旅游"],"title":"夜爬老君山，等一场金顶云海日出","uri":"/posts/%E5%A4%9C%E7%88%AC%E8%80%81%E5%90%9B%E5%B1%B1%E7%AD%89%E4%B8%80%E5%9C%BA%E9%87%91%E9%A1%B6%E4%BA%91%E6%B5%B7%E6%97%A5%E5%87%BA/#启程"},{"categories":"旅游","collections":["超超的奇妙冒险"],"content":"第 2 节 上山\r沿着上山的路前行，发现一条抄近道的小径，不少游客都选择走近路，大家排着队鱼贯而上，气氛热闹非凡。 顺着队伍继续向上，远远便望见一座气势恢宏的牌坊，上书「景室」二字，在群山掩映下颇有古意。 走近细看，景室大门雕梁画栋，飞檐翘角，古朴而庄严，仿佛在诉说着千百年的岁月故事。 穿过景室大门继续攀登，不觉间便来到了中天门，此处视野开阔，山风拂面，令人心旷神怡。 再往前走，金殿赫然出现在眼前，在暮色中泛着温暖的金光，宛如天宫般神圣而壮丽。 站在高处俯瞰山下的景色，层峦叠嶂尽收眼底，远山如黛，天地辽阔，所有疲惫瞬间烟消云散。 夜色渐浓时再往山下望去，只见万家灯火璀璨通明，整座山城流光溢彩，完全不像夜晚，倒像是繁星坠落人间。 ","date":"2024-06-10","objectID":"/posts/%E5%A4%9C%E7%88%AC%E8%80%81%E5%90%9B%E5%B1%B1%E7%AD%89%E4%B8%80%E5%9C%BA%E9%87%91%E9%A1%B6%E4%BA%91%E6%B5%B7%E6%97%A5%E5%87%BA/:2:0","tags":["旅游"],"title":"夜爬老君山，等一场金顶云海日出","uri":"/posts/%E5%A4%9C%E7%88%AC%E8%80%81%E5%90%9B%E5%B1%B1%E7%AD%89%E4%B8%80%E5%9C%BA%E9%87%91%E9%A1%B6%E4%BA%91%E6%B5%B7%E6%97%A5%E5%87%BA/#上山"},{"categories":"旅游","collections":["超超的奇妙冒险"],"content":"第 3 节 日出\r拂晓时分，我登上了洛阳老君山的山顶，天际线处渐渐泛起一抹鱼肚白，东方既白，夜色正缓缓褪去。 紧接着，天边开始晕染开淡淡的橘红色光晕，像是有人用画笔轻轻勾勒出黎明的轮廓，群山也慢慢显露出朦胧的剪影。 不多时，那道橘红逐渐加深、扩散，云层被染成了绚烂的霞光，整个天际仿佛一幅壮丽的油画徐徐展开。 随后，一弧金色的光芒从山峦背后悄悄探出头来，霞光万道，瞬间唤醒了沉睡的山川万物。 太阳继续攀升，半轮红日跃出云海，光芒愈发耀眼，将老君山的峰峦叠嶂映照得层次分明、金碧辉煌。 终于，旭日挣脱了地平线的束缚，完全升上了天空，金色的阳光洒满群山，新的一天在巍峨壮阔中盛大开启。 ","date":"2024-06-10","objectID":"/posts/%E5%A4%9C%E7%88%AC%E8%80%81%E5%90%9B%E5%B1%B1%E7%AD%89%E4%B8%80%E5%9C%BA%E9%87%91%E9%A1%B6%E4%BA%91%E6%B5%B7%E6%97%A5%E5%87%BA/:3:0","tags":["旅游"],"title":"夜爬老君山，等一场金顶云海日出","uri":"/posts/%E5%A4%9C%E7%88%AC%E8%80%81%E5%90%9B%E5%B1%B1%E7%AD%89%E4%B8%80%E5%9C%BA%E9%87%91%E9%A1%B6%E4%BA%91%E6%B5%B7%E6%97%A5%E5%87%BA/#日出"},{"categories":"旅游","collections":["超超的奇妙冒险"],"content":"第 4 节 登顶\r登上老君山金殿，白天的这里早已人潮涌动，游客络绎不绝，金色殿顶在阳光下熠熠生辉。 随后我避开人群，沿着山路向玉皇顶进发，越往上走视野越发开阔。 登顶玉皇顶，俯瞰下方连绵的殿堂群，层叠的飞檐翘角在群山环绕下显得格外壮观。 下山途中经过南天门，石阶蜿蜒而下，回首望去，山巅的殿宇已渐渐隐入云雾之中。 ","date":"2024-06-10","objectID":"/posts/%E5%A4%9C%E7%88%AC%E8%80%81%E5%90%9B%E5%B1%B1%E7%AD%89%E4%B8%80%E5%9C%BA%E9%87%91%E9%A1%B6%E4%BA%91%E6%B5%B7%E6%97%A5%E5%87%BA/:4:0","tags":["旅游"],"title":"夜爬老君山，等一场金顶云海日出","uri":"/posts/%E5%A4%9C%E7%88%AC%E8%80%81%E5%90%9B%E5%B1%B1%E7%AD%89%E4%B8%80%E5%9C%BA%E9%87%91%E9%A1%B6%E4%BA%91%E6%B5%B7%E6%97%A5%E5%87%BA/#登顶"},{"categories":"旅游","collections":["超超的奇妙冒险"],"content":"第 5 节 十里画屏\r走进洛阳老君山的十里画屏，一条悬空长廊映入眼帘，纵看长廊笔直延伸出去很远，右侧紧邻绝壁，崖壁陡峭险峻，让人不由得心生敬畏。 再往前几步，换个角度横看长廊，只见它单侧支撑在悬崖之上，仿佛悬于半空，行走其间颇有凌空踏虚之感。 继续沿着长廊前行，来到拐弯处，发现长廊的轮廓完美贴合着山体的自然走势，人工与天然在此处和谐交融，别具匠心。 仍是在长廊的拐弯处驻足回望，头顶的阳光十分明媚，洒在蜿蜒的长廊和嶙峋的山石上，光影交错之间，十里画屏的壮美尽收眼底。 ","date":"2024-06-10","objectID":"/posts/%E5%A4%9C%E7%88%AC%E8%80%81%E5%90%9B%E5%B1%B1%E7%AD%89%E4%B8%80%E5%9C%BA%E9%87%91%E9%A1%B6%E4%BA%91%E6%B5%B7%E6%97%A5%E5%87%BA/:5:0","tags":["旅游"],"title":"夜爬老君山，等一场金顶云海日出","uri":"/posts/%E5%A4%9C%E7%88%AC%E8%80%81%E5%90%9B%E5%B1%B1%E7%AD%89%E4%B8%80%E5%9C%BA%E9%87%91%E9%A1%B6%E4%BA%91%E6%B5%B7%E6%97%A5%E5%87%BA/#十里画屏"},{"categories":"旅游","collections":["超超的奇妙冒险"],"content":"第 6 节 山峰\r清晨时分，我站在洛阳老君山的山巅，眼前繁茂的树枝上挂满了红色的祈愿牌，在微风中轻轻摇曳，承载着无数游人的心愿与祝福。 远眺天际，一轮旭日正从群山之间冉冉升起，金色的光芒穿透晨雾，将整片山峦染上一层温暖的光晕。 山间浓雾缠绕，宛若仙境，薄纱般的雾气在碧绿的山体间缓缓流淌，让连绵起伏的峰峦显得灵气充裕、如梦似幻。 继续前行，但见朝霞映照下的山体碧绿欲滴，延绵不绝的山脊线条优美，配合天边绚烂的霞光，构成一幅绝美的自然画卷。 再往前看，山势愈发险峻，绝壁上顽强地生长着各类绿植，陡峭的尖峰直插云霄，令人不禁感叹大自然的鬼斧神工。 不远处有一座天然石头堆砌而成的洞穴，洞口不大，高度有限，需弯腰才能通行，充满了原始而神秘的野趣。 弯腰穿过石洞后回望，整座老君山在晨雾与朝霞的笼罩下愈发巍峨壮丽，这一路的险峻与柔美交织，让人久久不忍离去。 ","date":"2024-06-10","objectID":"/posts/%E5%A4%9C%E7%88%AC%E8%80%81%E5%90%9B%E5%B1%B1%E7%AD%89%E4%B8%80%E5%9C%BA%E9%87%91%E9%A1%B6%E4%BA%91%E6%B5%B7%E6%97%A5%E5%87%BA/:6:0","tags":["旅游"],"title":"夜爬老君山，等一场金顶云海日出","uri":"/posts/%E5%A4%9C%E7%88%AC%E8%80%81%E5%90%9B%E5%B1%B1%E7%AD%89%E4%B8%80%E5%9C%BA%E9%87%91%E9%A1%B6%E4%BA%91%E6%B5%B7%E6%97%A5%E5%87%BA/#山峰"},{"categories":"旅游","collections":["超超的奇妙冒险"],"content":"第 7 节 植被\r下山途中，途径一片茂密的垂柳，枝条细长柔软、自然垂落，在微风中轻轻摇曳，仿佛在向来客招手致意。 继续沿着山路前行，路旁生长着一片阔叶树，枝叶繁茂、绿意盎然，将整条小径笼罩在清凉的树荫之下。 不经意间低头看去，石阶旁的牵牛花正盛情绽放，那标志性的喇叭形花朵极具辨识度，为这片绿意增添了一抹亮丽色彩。 再往前走，六月雪交错丛生，洁白的花朵如霜似雪，衬着山间薄雾弥漫，颇有几分不食人间烟火的仙气韵味。 ","date":"2024-06-10","objectID":"/posts/%E5%A4%9C%E7%88%AC%E8%80%81%E5%90%9B%E5%B1%B1%E7%AD%89%E4%B8%80%E5%9C%BA%E9%87%91%E9%A1%B6%E4%BA%91%E6%B5%B7%E6%97%A5%E5%87%BA/:7:0","tags":["旅游"],"title":"夜爬老君山，等一场金顶云海日出","uri":"/posts/%E5%A4%9C%E7%88%AC%E8%80%81%E5%90%9B%E5%B1%B1%E7%AD%89%E4%B8%80%E5%9C%BA%E9%87%91%E9%A1%B6%E4%BA%91%E6%B5%B7%E6%97%A5%E5%87%BA/#植被"},{"categories":"旅游","collections":["超超的奇妙冒险"],"content":"第 1 节 龙门石窟\r刚踏入龙门石窟景区，抬头便见山壁上凿刻着醒目的「龙门」二字，脚下是一座横跨伊水的长桥，三个宽敞的桥洞下河水静静流淌，供往来的游船穿行而过。 走上大桥回望，桥上游人如织，来自五湖四海的游客都向着这片千年石窟汇聚而来，热闹非凡。 穿过桥头，最热门的佛像前早已排起了长队，大家有序地等待着与这千年古迹合影留念，可见其受欢迎程度。 继续往里走，在洞窟深处见到了著名的释迦牟尼雕像，尽管历经风雨侵蚀，依旧神态庄严，让人不由心生敬畏。 移步旁边的石窟，另一尊佛像映入眼帘，安详的姿态同样令人驻足良久。 紧接着不远处又是一尊佛像，层层叠叠的造像布满了整面崖壁，每一尊都承载着千年的信仰与匠心。 最有趣的是其中一尊佛像，圆润的脸庞配上憨厚的表情活像个可爱的表情包，让人忍俊不禁，在这庄严之地平添了几分趣味。 走到河对岸回头望去，整座石窟的面貌尽收眼底，只见崖壁上千疮百孔，那是岁月与风霜留下的印记。 伊水河面上不时有游船缓缓驶过，船上的游客仰望着这片壮观的石窟群，想必也是满心的震撼。 站在对岸远眺，龙门石窟的全景铺展开来，千年的文明就这样静静伫立在伊水两岸，无声诉说着昔日的辉煌。 ","date":"2024-06-09","objectID":"/posts/%E4%BC%8A%E6%B0%B4%E7%95%94%E7%9A%84%E5%8D%83%E5%B9%B4%E5%9B%9E%E5%93%8D%E7%9F%B3%E7%AA%9F%E5%BA%84%E4%B8%A5%E4%B8%8E%E8%AF%97%E9%AA%A8%E9%A3%8E%E6%B5%81/:1:0","tags":["旅游"],"title":"伊水畔的千年回响：石窟庄严与诗骨风流","uri":"/posts/%E4%BC%8A%E6%B0%B4%E7%95%94%E7%9A%84%E5%8D%83%E5%B9%B4%E5%9B%9E%E5%93%8D%E7%9F%B3%E7%AA%9F%E5%BA%84%E4%B8%A5%E4%B8%8E%E8%AF%97%E9%AA%A8%E9%A3%8E%E6%B5%81/#龙门石窟"},{"categories":"旅游","collections":["超超的奇妙冒险"],"content":"第 2 节 香山寺\r走进旁边的香山寺，抬头便望见大雄宝殿上悬挂的门匾，笔力遒劲，尽显古刹的庄重与肃穆。 步入殿内，三尊佛像赫然在目，释迦牟尼端坐正中，阿傩与迦叶分立两侧，神态安详，令人心生敬畏。 随后我环视屋内，两只造型凶猛的螭吻张着大嘴，作为古代高级建筑上的经典装饰，为这座寺院平添了几分威严。 最后看到的是当年僧侣或守窟人员使用过的生活器皿，粗糙的陶器上浸满了岁月的痕迹，让人不禁遥想千百年前寺中的晨钟暮鼓与日常烟火。 ","date":"2024-06-09","objectID":"/posts/%E4%BC%8A%E6%B0%B4%E7%95%94%E7%9A%84%E5%8D%83%E5%B9%B4%E5%9B%9E%E5%93%8D%E7%9F%B3%E7%AA%9F%E5%BA%84%E4%B8%A5%E4%B8%8E%E8%AF%97%E9%AA%A8%E9%A3%8E%E6%B5%81/:2:0","tags":["旅游"],"title":"伊水畔的千年回响：石窟庄严与诗骨风流","uri":"/posts/%E4%BC%8A%E6%B0%B4%E7%95%94%E7%9A%84%E5%8D%83%E5%B9%B4%E5%9B%9E%E5%93%8D%E7%9F%B3%E7%AA%9F%E5%BA%84%E4%B8%A5%E4%B8%8E%E8%AF%97%E9%AA%A8%E9%A3%8E%E6%B5%81/#香山寺"},{"categories":"旅游","collections":["超超的奇妙冒险"],"content":"第 3 节 白居易墓园\r走进白居易墓园，一座精致的墓碑静静矗立在苍松翠柏之间，碑身雕刻细腻，透着一股文人雅士的淡然与从容。 凑近细看，墓碑上的纹饰与刻字都极为考究，每一处线条都仿佛在诉说着诗王生前的风采与后人的敬仰之情。 墓碑前方伫立着白居易的雕像，正面望去，诗人目光深邃、神态安详，而转到侧边观看时，那微微扬起的下颌更显出一股不羁的风骨与文人气质。 继续往墓园深处走去，一方小巧的池塘映入眼帘，水面倒映着天光云影，为这片静谧的安息之地平添了几分灵动与诗意。 ","date":"2024-06-09","objectID":"/posts/%E4%BC%8A%E6%B0%B4%E7%95%94%E7%9A%84%E5%8D%83%E5%B9%B4%E5%9B%9E%E5%93%8D%E7%9F%B3%E7%AA%9F%E5%BA%84%E4%B8%A5%E4%B8%8E%E8%AF%97%E9%AA%A8%E9%A3%8E%E6%B5%81/:3:0","tags":["旅游"],"title":"伊水畔的千年回响：石窟庄严与诗骨风流","uri":"/posts/%E4%BC%8A%E6%B0%B4%E7%95%94%E7%9A%84%E5%8D%83%E5%B9%B4%E5%9B%9E%E5%93%8D%E7%9F%B3%E7%AA%9F%E5%BA%84%E4%B8%A5%E4%B8%8E%E8%AF%97%E9%AA%A8%E9%A3%8E%E6%B5%81/#白居易墓园"},{"categories":"旅游","collections":["超超的奇妙冒险"],"content":"第 1 节 开封博物馆\r步入开封博物馆，首先映入眼帘的是远古时期中原先民的生活场景复原，那些简陋却充满智慧的工具与器物，仿佛在无声诉说着华夏文明最初的脉动。 紧接着不远处陈列着古代祭祀或重大聚会的模拟场景，周围的铁钩、长矛头等破城与防御工具，让人遥想当年这片土地上金戈铁马的峥嵘岁月。 再往前走，一尊典型的北朝至隋唐时期的一佛二菩萨石刻造像赫然伫立，主尊佛像背光火焰翻腾、两侧菩萨恭敬侍立，中原佛教艺术的雄浑与庄严令人屏息。 随后映入眼帘的是几方墓志铭与石刻文献，斑驳的碑面上镌刻着岁月的痕迹，那些沉睡的文字记录着一个个鲜为人知的历史瞬间。 视线一转，一尊具有典型唐代特征的人面镇墓兽静静蹲坐，大耳高竖、独角冲天，虽已跨越千年，仍坚守着驱邪避灾的使命。 不远处一尊唐三彩天王俑威风凛凛，身披铠甲、脚踏卧兽，那怒目圆睁的威猛神态，让人仿佛看到了盛唐时期王侯将相的赫赫威仪。 接着欣赏一件精美的透雕镂空器物，那繁复的镂空工艺与细腻的彩绘纹饰交相辉映，古代工匠的巧思与审美令人叹为观止。 再往前看，一座瑞兽纹铜熏炉引人注目，炉盖上翻腾的云龙纹透雕精致绝伦，可以想象当年香烟袅袅升腾时，该是何等如梦似幻的景象。 继续前行，一组胎质洁白、色彩淡雅的瓷器珍品映入眼帘，每只杯子上都绘有不同花卉图案，清雅脱俗中透出宋代美学的极致追求。 接着来到木版年画展厅，工作台上摆放着棕刷、刻刀、宣纸等工具，生动还原了朱仙镇木版年画套色印刷的工艺场景，民间艺术的温度触手可及。 不远处一组蹴鞠场景雕塑活力四射，两人矫健的身姿定格在凌空踢球的瞬间，在《清明上河图》的背景映衬下，宋代市井的欢快气息扑面而来。 最后，一组李白举杯畅饮的雕塑将思绪带回那个文风鼎盛的年代，诗人豪迈的吟诵仿佛穿越时空依然在耳畔回响，为这段博物馆之旅画上了诗意盎然的句点。 ","date":"2024-06-08","objectID":"/posts/%E6%B1%B4%E6%A2%81%E4%B8%80%E6%97%A5%E7%A9%BF%E8%B6%8A%E5%8D%83%E5%B9%B4/:1:0","tags":["旅游"],"title":"汴梁一日，穿越千年","uri":"/posts/%E6%B1%B4%E6%A2%81%E4%B8%80%E6%97%A5%E7%A9%BF%E8%B6%8A%E5%8D%83%E5%B9%B4/#开封博物馆"},{"categories":"旅游","collections":["超超的奇妙冒险"],"content":"第 2 节 大梁门\r走近开封大梁门，一座气势恢宏的宋朝风格城门映入眼帘，高大的城楼飞檐翘角，仿佛瞬间将人拉回了千年前的北宋都城。 随后登上城墙，从高处俯瞰整座城楼的轮廓，青砖灰瓦间尽显古都的厚重与庄严，视野豁然开朗。 馆内能见到不同朝代的城墙与马道层层叠压，形成了\"城下有城、路下有路、店下有店\"的奇特地质景观，历史的年轮在此清晰可见。 ","date":"2024-06-08","objectID":"/posts/%E6%B1%B4%E6%A2%81%E4%B8%80%E6%97%A5%E7%A9%BF%E8%B6%8A%E5%8D%83%E5%B9%B4/:2:0","tags":["旅游"],"title":"汴梁一日，穿越千年","uri":"/posts/%E6%B1%B4%E6%A2%81%E4%B8%80%E6%97%A5%E7%A9%BF%E8%B6%8A%E5%8D%83%E5%B9%B4/#大梁门"},{"categories":"旅游","collections":["超超的奇妙冒险"],"content":"第 3 节 翰园碑林\r走进翰园碑林，眼前豁然开朗，一片宽广的水域铺展开来，湖面上游船点点，悠然自得。 走近细看，这些游船都是用木桨驱动，游客们一下下划动船桨，荡起层层涟漪，颇有古风韵味。 继续往里走，一座仰圣山映入眼帘，山顶伫立着孔子的雕像，庄严肃穆，令人心生敬意。 沿着山路拾级而上，忽然有泉水从山间迸出，水流近在咫尺，清凉之感扑面而来。 登上山顶后视野豁然开朗，隔壁清明上河园的景致尽收眼底，整个园区一览无余，视角极佳。 再往前，一座索道横跨眼前，桥面比较晃动，每走一步都需紧握绳索，颇考验胆量。 不远处还有另一条索道，只见有人正试着踩在铁链上一步步挪过去，动作惊险又令人佩服。 在这座集山水园林与人文底蕴于一体的园子里漫步，每一步都能感受到自然与文化的交融，令人流连忘返。 ","date":"2024-06-08","objectID":"/posts/%E6%B1%B4%E6%A2%81%E4%B8%80%E6%97%A5%E7%A9%BF%E8%B6%8A%E5%8D%83%E5%B9%B4/:3:0","tags":["旅游"],"title":"汴梁一日，穿越千年","uri":"/posts/%E6%B1%B4%E6%A2%81%E4%B8%80%E6%97%A5%E7%A9%BF%E8%B6%8A%E5%8D%83%E5%B9%B4/#翰园碑林"},{"categories":"旅游","collections":["超超的奇妙冒险"],"content":"第 4 节 清明上河园\r走进开封清明上河园，仿佛穿越回北宋盛世，眼前最先映入眼帘的是几艘宋代风格的小型游船，静静地停靠在岸边，船身的雕花木纹透着古朴的韵味。 接着往远处望去，只见几艘更为宏大的宋代风格游船缓缓驶来，高大的船楼与层叠的飞檐彰显着当年的繁华气象。 登上高处俯瞰，整个园区的池塘水面豁然开朗，视野极为开阔，让人顿感心旷神怡。 水域的对岸，成片宋代风格的楼宇建筑错落有致地排列着，红墙黛瓦在阳光下显得格外庄重大气。 走近细看这些建筑，才发现它们远比想象中更为高大宏伟，飞檐斗拱层层叠叠，气势恢宏。 随后跨过一座白色的拱桥，桥身修长优雅，弧度柔美，走在上面仿佛踏着彩虹前行。 沿着桥走过很长一段路，终于来到了码头附近，这里游人如织，热闹非凡。 码头边驶过一艘装饰着龙头的游船，龙首高扬，威风凛凛，随时准备启航一般。 离开码头继续前行，不经意间注意到水面上点缀着几朵荷花样式的装饰品，精巧别致，为平静的水面增添了几分灵动。 水面平静如镜，清澈见底，远处的荷叶层层叠叠地铺展开来，为这幅水景画卷添上了浓墨重彩的一笔。 再走近一些观摩，只见满池塘的荷叶密密匝匝地挤在一起，翠色欲滴，生机盎然。 水面上水汽弥漫，氤氲缭绕，仿佛给这片荷塘蒙上了一层薄薄的轻纱，如梦似幻。 离开这片水景，继续往里走，一片宋代风格的市集出现在眼前，街道两旁店铺林立，古色古香。 市集里人头攒动，摩肩接踵，头顶上挂着一排排灯笼，张灯结彩，处处洋溢着热闹的烟火气息。 集市一角有一家包子铺格外引人注目——孙二娘包子，店内还打着人肉的噱头招揽顾客，令人忍俊不禁。 凑近看，一笼刚出笼的包子热气腾腾，皮薄馅大，褶皱均匀，让人食欲大增。 吃完包子继续逛，不远处一棵樱花树吸引了我的目光，树上挂满了红色的许愿签，承载着游人们的美好心愿。 旁边树上白色的小花正盛开着，一团团一簇簇，如云似雪，微风拂过便洒下阵阵花瓣雨。 转身发现旁边立着一座宋代风格的擂台，台面上整齐摆放着刀枪剑戟等十八般兵器，仿佛随时会有一场比武大会。 最后路过一处有趣的地方——景区的施工公告竟然写着「奉官府之命施工」，这份古今交融的幽默感，让人会心一笑，也为这次穿越之旅画上了圆满的句号。 ","date":"2024-06-08","objectID":"/posts/%E6%B1%B4%E6%A2%81%E4%B8%80%E6%97%A5%E7%A9%BF%E8%B6%8A%E5%8D%83%E5%B9%B4/:4:0","tags":["旅游"],"title":"汴梁一日，穿越千年","uri":"/posts/%E6%B1%B4%E6%A2%81%E4%B8%80%E6%97%A5%E7%A9%BF%E8%B6%8A%E5%8D%83%E5%B9%B4/#清明上河园"},{"categories":"旅游","collections":["超超的奇妙冒险"],"content":"第 5 节 万岁山\r夜幕降临，开封万岁山在璀璨灯光的映衬下宛如一座不夜城，五彩斑斓的光影将古建筑群装点得如梦似幻。 循着躁动的音乐声继续往里走，现场正举办着热闹非凡的音乐节，人潮涌动、欢呼声此起彼伏，整个园区洋溢着青春的活力与热情。 走到一处角落时不禁莞尔——水浒好汉宋江的雕像手中竟被人塞上一根香烟，烟雾缭绕间仿佛下一秒就要来一句「来根华子」，古今混搭的幽默感，也为这趟夜游增添了几分趣味。 ","date":"2024-06-08","objectID":"/posts/%E6%B1%B4%E6%A2%81%E4%B8%80%E6%97%A5%E7%A9%BF%E8%B6%8A%E5%8D%83%E5%B9%B4/:5:0","tags":["旅游"],"title":"汴梁一日，穿越千年","uri":"/posts/%E6%B1%B4%E6%A2%81%E4%B8%80%E6%97%A5%E7%A9%BF%E8%B6%8A%E5%8D%83%E5%B9%B4/#万岁山"},{"categories":"旅游","collections":["超超的奇妙冒险"],"content":"第 6 节 包公祠\r踏入包公祠，首先映入眼帘的便是那块高悬的门匾，笔力遒劲，扑面而来的宋朝风韵瞬间将人拉回千年前的东京梦华。 继续往里走，一尊庄严的包公雕像端坐于正堂之中，他面色如铁、目光如炬，那股凛然正气令人不由得心生敬意。 转身看向一侧，陈列着包氏支谱，林林总总的姓名背后是一个家族源远流长的历史传承。 旁边还展出了不少当时的文献资料，泛黄的纸页间记录着北宋官场的风云变幻与铁面无私的判案故事。 再往前行，一座还原开封府的微缩模型映入眼帘，街巷纵横、衙署俨然，让人得以一窥昔日汴京的繁华气象。 不远处便是包公的蜡像，他身着官服端坐案前，仿佛下一秒就要升堂问案，神态栩栩如生。 而另一侧的陈世美蜡像则被衙役按住双臂，身体前倾，神情惶惧而僵滞，与包公怒目圆睁、气势逼人的形象形成强烈反差，整组场景戏剧冲突感十足。 最后看到的，是那三口还原度极高的铡刀——龙头铡、虎头铡、狗头铡依次排列，寒光凛凛，无声地诉说着「王子犯法与庶民同罪」的千古信条。 ","date":"2024-06-08","objectID":"/posts/%E6%B1%B4%E6%A2%81%E4%B8%80%E6%97%A5%E7%A9%BF%E8%B6%8A%E5%8D%83%E5%B9%B4/:6:0","tags":["旅游"],"title":"汴梁一日，穿越千年","uri":"/posts/%E6%B1%B4%E6%A2%81%E4%B8%80%E6%97%A5%E7%A9%BF%E8%B6%8A%E5%8D%83%E5%B9%B4/#包公祠"},{"categories":"旅游","collections":["超超的奇妙冒险"],"content":"第 7 节 开封府\r朱墙巍峨，飞檐高挑，开封府的城楼在蓝天下更显庄严肃穆。“开封府”三个鎏金大字高悬门上，仿佛一下子将人带回北宋时期那座声名赫赫的府衙。还未入内，便已能感受到这里扑面而来的历史气息与威严感。 走进开封府，庭院中的水池里养着许多锦鲤，一条条膘肥体壮、悠然游弋，在阳光下鳞光闪闪，引得游客纷纷驻足投喂。 继续往里走，殿内陈列着一组与包公祠相似的蜡像，生动再现了《铡美案》中陈世美被押上堂的场景，包拯铁面无私、正气凛然，令人不禁为这千古公案感慨不已。 ","date":"2024-06-08","objectID":"/posts/%E6%B1%B4%E6%A2%81%E4%B8%80%E6%97%A5%E7%A9%BF%E8%B6%8A%E5%8D%83%E5%B9%B4/:7:0","tags":["旅游"],"title":"汴梁一日，穿越千年","uri":"/posts/%E6%B1%B4%E6%A2%81%E4%B8%80%E6%97%A5%E7%A9%BF%E8%B6%8A%E5%8D%83%E5%B9%B4/#开封府"},{"categories":"旅游","collections":["超超的奇妙冒险"],"content":"第 1 节 山峰\r走进嵩山，眼前的山体凹凸不平，岩壁上布满了大大小小的凸起，仿佛大地用苍劲的笔触勾勒出的原始肌理。 沿着山路前行，一座弧形的山峰映入眼帘，线条柔和而有力，像是被风与水共同雕琢而成的天然雕塑。 继续往下走，山脚处天然汇聚成一汪清澈的水塘，碧波微澜，倒映着周围的峰峦，宛如一块镶嵌在山间的翡翠。 凑近细看，水塘清澈见底，几缕水草在水中轻轻摇曳，水面的倒影与真实的山石虚实交织，静谧而动人。 随后穿过两座山峰之间形成的天然通道，石壁陡峭，通道极为狭窄，仅容一人侧身通过，抬头只见一线天光。 登上山顶俯瞰，只见山间松树枝头系满了红色的祈愿牌，随风轻轻摆动；极目远眺，山下的村落与田野尽收眼底，令人心旷神怡。 ","date":"2024-06-07","objectID":"/posts/%E5%B5%A9%E5%B1%B1%E5%AF%BB%E5%B9%BD%E8%AE%B0%E7%A2%A7%E6%B3%A2%E7%BF%A0%E8%89%B2%E9%97%B4%E7%99%BB%E9%A1%B6%E5%B3%BB%E6%9E%81%E4%B9%8B%E5%B7%85/:1:0","tags":["旅游"],"title":"嵩山寻幽记：碧波翠色间，登顶峻极之巅","uri":"/posts/%E5%B5%A9%E5%B1%B1%E5%AF%BB%E5%B9%BD%E8%AE%B0%E7%A2%A7%E6%B3%A2%E7%BF%A0%E8%89%B2%E9%97%B4%E7%99%BB%E9%A1%B6%E5%B3%BB%E6%9E%81%E4%B9%8B%E5%B7%85/#山峰"},{"categories":"旅游","collections":["超超的奇妙冒险"],"content":"第 2 节 植被\r抬眼望去，整面山坡都被茂密的植被覆盖，翠绿的树木层层叠叠，宛如一幅生机盎然的绿色画卷。 沿着山路向上，目光所及之处尽是郁郁葱葱的绿意，树木枝繁叶茂，将整座山体包裹得严严实实。 再往前细看，翠绿的树丛中悄然探出几株开满粉色花朵的树，粉绿相间，格外惹眼，为这满山的翠色增添了一抹温柔的浪漫。 继续漫步其中，深深呼吸着山林间清新的空气，四周植被密不透风，阳光透过叶隙洒下斑驳光影，让人彻底沉浸在这片绿色的怀抱之中。 ","date":"2024-06-07","objectID":"/posts/%E5%B5%A9%E5%B1%B1%E5%AF%BB%E5%B9%BD%E8%AE%B0%E7%A2%A7%E6%B3%A2%E7%BF%A0%E8%89%B2%E9%97%B4%E7%99%BB%E9%A1%B6%E5%B3%BB%E6%9E%81%E4%B9%8B%E5%B7%85/:2:0","tags":["旅游"],"title":"嵩山寻幽记：碧波翠色间，登顶峻极之巅","uri":"/posts/%E5%B5%A9%E5%B1%B1%E5%AF%BB%E5%B9%BD%E8%AE%B0%E7%A2%A7%E6%B3%A2%E7%BF%A0%E8%89%B2%E9%97%B4%E7%99%BB%E9%A1%B6%E5%B3%BB%E6%9E%81%E4%B9%8B%E5%B7%85/#植被"},{"categories":"旅游","collections":["超超的奇妙冒险"],"content":"第 3 节 奇观\r一座横跨山涧的狭窄吊桥，走在上面晃晃悠悠，脚下是幽深的谷底，令人既紧张又兴奋。 穿过吊桥继续前行，一处天然形成的洞穴赫然出现在眼前，洞内怪石嶙峋，仿佛大自然鬼斧神工的杰作。 再往上走便到了山上的道馆，庭院中一座鼎炉引人注目，上面赫然刻着「三极圣母宫」几个大字，为这清幽之地增添了几分庄严神秘的氛围。 ","date":"2024-06-07","objectID":"/posts/%E5%B5%A9%E5%B1%B1%E5%AF%BB%E5%B9%BD%E8%AE%B0%E7%A2%A7%E6%B3%A2%E7%BF%A0%E8%89%B2%E9%97%B4%E7%99%BB%E9%A1%B6%E5%B3%BB%E6%9E%81%E4%B9%8B%E5%B7%85/:3:0","tags":["旅游"],"title":"嵩山寻幽记：碧波翠色间，登顶峻极之巅","uri":"/posts/%E5%B5%A9%E5%B1%B1%E5%AF%BB%E5%B9%BD%E8%AE%B0%E7%A2%A7%E6%B3%A2%E7%BF%A0%E8%89%B2%E9%97%B4%E7%99%BB%E9%A1%B6%E5%B3%BB%E6%9E%81%E4%B9%8B%E5%B7%85/#奇观"},{"categories":"旅游","collections":["超超的奇妙冒险"],"content":"第 4 节 登顶\r经过数小时的攀登，终于登上了嵩山之巅——峻极峰，我坐在那块刻着1491.73米海拔的石头上，兴奋地对着镜头比了个胜利的手势。 随后站起身来自拍一张，正对镜头，背后是一望无际的澄澈天空，仿佛整个天地都为我此刻的征服感作背景。 再转身背对镜头，张开双臂平举伸直，山风拂面而来，那一刻仿佛拥有了整个山川的拥抱。 ","date":"2024-06-07","objectID":"/posts/%E5%B5%A9%E5%B1%B1%E5%AF%BB%E5%B9%BD%E8%AE%B0%E7%A2%A7%E6%B3%A2%E7%BF%A0%E8%89%B2%E9%97%B4%E7%99%BB%E9%A1%B6%E5%B3%BB%E6%9E%81%E4%B9%8B%E5%B7%85/:4:0","tags":["旅游"],"title":"嵩山寻幽记：碧波翠色间，登顶峻极之巅","uri":"/posts/%E5%B5%A9%E5%B1%B1%E5%AF%BB%E5%B9%BD%E8%AE%B0%E7%A2%A7%E6%B3%A2%E7%BF%A0%E8%89%B2%E9%97%B4%E7%99%BB%E9%A1%B6%E5%B3%BB%E6%9E%81%E4%B9%8B%E5%B7%85/#登顶"},{"categories":"旅游","collections":["超超的奇妙冒险"],"content":"第 1 节 二七广场\r夜幕降临，我来到郑州二七区的二七广场，首先映入眼帘的是巍峨耸立的二七纪念馆纪念塔，塔身上赫然写着「中国共产党万岁」，顶部那颗闪亮的五角星在夜色中格外醒目，整座建筑散发着浓厚的红色革命气息。 随后漫步至广场边的商圈，发现三只造型一模一样的小熊雕塑，其中一只正悠闲地坐着，憨态可掬的模样为繁华的广场增添了几分趣味。 接着看到另一只小熊张开双臂做出拥抱的姿势，仿佛在热情欢迎每一位到访的游客，让人忍不住也想上前合影。 再往前走，第三只小熊则双手叉腰，摆出一副神气十足的架势，三只小熊姿态各异却又和谐有趣。 最后来到德化步行商业街，这里有一块写着「I ❤ 郑州」的文字标识牌，夜晚灯光映衬下显得格外亮眼，俨然成了年轻人争相打卡的网红地标。 ","date":"2024-06-04","objectID":"/posts/%E5%A4%9C%E6%B8%B8%E9%83%91%E5%B7%9E%E4%B8%80%E7%A2%97%E8%83%A1%E8%BE%A3%E6%B1%A4%E4%B8%8E%E6%BB%A1%E5%9F%8E%E6%B5%81%E5%85%89/:1:0","tags":["旅游"],"title":"夜游郑州：一碗胡辣汤与满城流光","uri":"/posts/%E5%A4%9C%E6%B8%B8%E9%83%91%E5%B7%9E%E4%B8%80%E7%A2%97%E8%83%A1%E8%BE%A3%E6%B1%A4%E4%B8%8E%E6%BB%A1%E5%9F%8E%E6%B5%81%E5%85%89/#二七广场"},{"categories":"旅游","collections":["超超的奇妙冒险"],"content":"第 2 节 郑州记忆\r走进郑州记忆，最先映入眼帘的是一座用文字堆砌而成的二七纪念塔，无数景点名称与故事凝聚在这座地标之上，仿佛整座城市的历史都镌刻其中。 继续往里走，花店旁竖立着一块写满理想的路牌——“升职又加薪，多买玫瑰，少谈是非，早日退休，精神离职”，字里行间流露着当代人对自由生活的向往，身旁的深紫满天星在夜色中静静绽放，平添几分浪漫与倔强。 再往前几步，一面带有年代感的墙体映入眼帘，上面赫然写着\"嫁郎嫁心不嫁财，娶妻娶德不娶色\"，在传统与现代的交织中，这段夜游郑州记忆的旅程多了一份沉淀与思索。 ","date":"2024-06-04","objectID":"/posts/%E5%A4%9C%E6%B8%B8%E9%83%91%E5%B7%9E%E4%B8%80%E7%A2%97%E8%83%A1%E8%BE%A3%E6%B1%A4%E4%B8%8E%E6%BB%A1%E5%9F%8E%E6%B5%81%E5%85%89/:2:0","tags":["旅游"],"title":"夜游郑州：一碗胡辣汤与满城流光","uri":"/posts/%E5%A4%9C%E6%B8%B8%E9%83%91%E5%B7%9E%E4%B8%80%E7%A2%97%E8%83%A1%E8%BE%A3%E6%B1%A4%E4%B8%8E%E6%BB%A1%E5%9F%8E%E6%B5%81%E5%85%89/#郑州记忆"},{"categories":"旅游","collections":["超超的奇妙冒险"],"content":"第 3 节 大玉米楼\r夜幕降临，郑州主城区的大玉米楼闪烁着五颜六色的灯光，瞬间点亮了整片天际线，这座地标建筑在夜色中格外引人注目。 走近细看，大玉米楼的灯光不断变幻着色彩，从金黄到紫红再到湛蓝，流光溢彩，宛如一根巨大的彩色玉米矗立在城市中央。 随后移步至大玉米楼下的码头，夜色中散步的人群络绎不绝，人们三三两两地沿着河岸漫步，享受着凉爽的晚风。 继续沿着河边前行，大玉米楼在水中投下清晰的倒影，灯光与水面交相辉映，虚实之间构成了一幅美丽的画卷。 再往前看，河对岸的楼宇和大桥也在水面上映出了斑斓的倒影，整条河流仿佛变成了一面巨大的彩色镜子。 桥底下，一艘游轮缓缓驶过，船身泛着柔和绚丽的彩光，划破平静的水面，为这夜景增添了几分灵动与浪漫。 水面波光粼粼，灯光碎成千万片金鳞在水波间跳动闪烁，让人不由得沉醉在这迷人的郑州之夜中。 ","date":"2024-06-04","objectID":"/posts/%E5%A4%9C%E6%B8%B8%E9%83%91%E5%B7%9E%E4%B8%80%E7%A2%97%E8%83%A1%E8%BE%A3%E6%B1%A4%E4%B8%8E%E6%BB%A1%E5%9F%8E%E6%B5%81%E5%85%89/:3:0","tags":["旅游"],"title":"夜游郑州：一碗胡辣汤与满城流光","uri":"/posts/%E5%A4%9C%E6%B8%B8%E9%83%91%E5%B7%9E%E4%B8%80%E7%A2%97%E8%83%A1%E8%BE%A3%E6%B1%A4%E4%B8%8E%E6%BB%A1%E5%9F%8E%E6%B5%81%E5%85%89/#大玉米楼"},{"categories":"旅游","collections":["超超的奇妙冒险"],"content":"第 4 节 中原福塔\r中原福塔矗立在郑州回民区的天际线上，通体散发出绚丽多彩的灯光，整个塔身璀璨夺目，高耸入云，仿佛一座连接天地间的光影巨柱，令人仰望惊叹。 ","date":"2024-06-04","objectID":"/posts/%E5%A4%9C%E6%B8%B8%E9%83%91%E5%B7%9E%E4%B8%80%E7%A2%97%E8%83%A1%E8%BE%A3%E6%B1%A4%E4%B8%8E%E6%BB%A1%E5%9F%8E%E6%B5%81%E5%85%89/:4:0","tags":["旅游"],"title":"夜游郑州：一碗胡辣汤与满城流光","uri":"/posts/%E5%A4%9C%E6%B8%B8%E9%83%91%E5%B7%9E%E4%B8%80%E7%A2%97%E8%83%A1%E8%BE%A3%E6%B1%A4%E4%B8%8E%E6%BB%A1%E5%9F%8E%E6%B5%81%E5%85%89/#中原福塔"},{"categories":"旅游","collections":["超超的奇妙冒险"],"content":"第 5 节 美食\r第一站直奔久负盛名的方中山胡辣汤店。还未走近，浓郁的胡椒香气便已扑面而来，店内热气腾腾，一碗热气翻滚的胡辣汤端上桌来，汤汁浓稠醇厚，牛肉片与面筋、粉条交织其中，一口下去麻辣鲜香直冲天灵盖，不愧是郑州最有名气的早餐名片。 ","date":"2024-06-04","objectID":"/posts/%E5%A4%9C%E6%B8%B8%E9%83%91%E5%B7%9E%E4%B8%80%E7%A2%97%E8%83%A1%E8%BE%A3%E6%B1%A4%E4%B8%8E%E6%BB%A1%E5%9F%8E%E6%B5%81%E5%85%89/:5:0","tags":["旅游"],"title":"夜游郑州：一碗胡辣汤与满城流光","uri":"/posts/%E5%A4%9C%E6%B8%B8%E9%83%91%E5%B7%9E%E4%B8%80%E7%A2%97%E8%83%A1%E8%BE%A3%E6%B1%A4%E4%B8%8E%E6%BB%A1%E5%9F%8E%E6%B5%81%E5%85%89/#美食"},{"categories":"旅游","collections":["超超的奇妙冒险"],"content":"第 1 节 陕西历史博物馆\r还未走进馆内，远远便望见陕西历史博物馆那气势恢宏的建筑，「陕西历史博物馆」几个大字在阳光下格外夺目。 馆前一方池塘倒映着整座建筑，虽游人如织，但那份庄重与大气依然令人心折。 ","date":"2024-05-22","objectID":"/posts/%E6%96%87%E7%89%A9%E6%97%A0%E8%A8%80%E8%B6%8A%E5%8D%83%E5%B9%B4%E8%8A%99%E8%93%89%E5%9B%AD%E9%87%8C%E8%A7%81%E7%9B%9B%E5%94%90/:1:0","tags":["旅游"],"title":"文物无言越千年，芙蓉园里见盛唐","uri":"/posts/%E6%96%87%E7%89%A9%E6%97%A0%E8%A8%80%E8%B6%8A%E5%8D%83%E5%B9%B4%E8%8A%99%E8%93%89%E5%9B%AD%E9%87%8C%E8%A7%81%E7%9B%9B%E5%94%90/#陕西历史博物馆"},{"categories":"旅游","collections":["超超的奇妙冒险"],"content":"1.1 石器时代\r步入展厅，首先映入眼帘的是蓝田猿人复原像，它生动再现了约115万至70万年前旧石器时代早期陕西先民的生存状态。 继续前行，一件新石器时代仰韶文化的彩陶盆静静陈列，盆身绘有黑色几何纹饰，是先民原始审美与祭祀文化的珍贵见证。 ","date":"2024-05-22","objectID":"/posts/%E6%96%87%E7%89%A9%E6%97%A0%E8%A8%80%E8%B6%8A%E5%8D%83%E5%B9%B4%E8%8A%99%E8%93%89%E5%9B%AD%E9%87%8C%E8%A7%81%E7%9B%9B%E5%94%90/:1:1","tags":["旅游"],"title":"文物无言越千年，芙蓉园里见盛唐","uri":"/posts/%E6%96%87%E7%89%A9%E6%97%A0%E8%A8%80%E8%B6%8A%E5%8D%83%E5%B9%B4%E8%8A%99%E8%93%89%E5%9B%AD%E9%87%8C%E8%A7%81%E7%9B%9B%E5%94%90/#石器时代"},{"categories":"旅游","collections":["超超的奇妙冒险"],"content":"1.2 商周\r再往前走，一尊商代后期的兽面纹鼎气势逼人，鼎身布满乳丁纹和饕餮纹，尽显早期青铜器的庄重与威严。 不远处陈列着一组西周编钟，它们排列有序，作为礼乐制度的象征，见证了西周时期成熟的社会等级与文化规范。 ","date":"2024-05-22","objectID":"/posts/%E6%96%87%E7%89%A9%E6%97%A0%E8%A8%80%E8%B6%8A%E5%8D%83%E5%B9%B4%E8%8A%99%E8%93%89%E5%9B%AD%E9%87%8C%E8%A7%81%E7%9B%9B%E5%94%90/:1:2","tags":["旅游"],"title":"文物无言越千年，芙蓉园里见盛唐","uri":"/posts/%E6%96%87%E7%89%A9%E6%97%A0%E8%A8%80%E8%B6%8A%E5%8D%83%E5%B9%B4%E8%8A%99%E8%93%89%E5%9B%AD%E9%87%8C%E8%A7%81%E7%9B%9B%E5%94%90/#商周"},{"categories":"旅游","collections":["超超的奇妙冒险"],"content":"1.3 汉\r接着看到的是一件汉代陶仓，作为陪葬冥器，它真实还原了汉代建筑的样貌，包括屋顶的瓦垄和透气小孔，体现了「事死如事生」的丧葬观念。 旁边还有一尊汉代绿釉陶狗，姿态矫健、神情警觉，反映了当时畜牧业在家畜饲养方面的发展。 再往前看，几枚玉璧质地莹润、纹饰精美，它们是古代祭祀与丧葬仪式中不可或缺的重要礼玉。 一旁的钱范（钱币模具）则展示了古代中国精湛的青铜铸造工艺，同时也见证了货币制度的演变。 ","date":"2024-05-22","objectID":"/posts/%E6%96%87%E7%89%A9%E6%97%A0%E8%A8%80%E8%B6%8A%E5%8D%83%E5%B9%B4%E8%8A%99%E8%93%89%E5%9B%AD%E9%87%8C%E8%A7%81%E7%9B%9B%E5%94%90/:1:3","tags":["旅游"],"title":"文物无言越千年，芙蓉园里见盛唐","uri":"/posts/%E6%96%87%E7%89%A9%E6%97%A0%E8%A8%80%E8%B6%8A%E5%8D%83%E5%B9%B4%E8%8A%99%E8%93%89%E5%9B%AD%E9%87%8C%E8%A7%81%E7%9B%9B%E5%94%90/#汉"},{"categories":"旅游","collections":["超超的奇妙冒险"],"content":"1.4 唐\r走进唐代展区，一座唐三彩院落模型色彩明艳、布局完整，生动再现了唐代贵族宅邸的风貌。 不远处的古代铁质农具——铁锸和铁斧，则提醒着我从春秋战国开始铁器的推广对生产力发展产生的巨大推动作用。 再往前走，一尊唐三彩低头马格外动人，马儿低头似在寻觅食物或饮水，线条流畅且充满肌肉感。 旁边是一件唐代白瓷双龙耳瓶，胎质细腻釉色洁白，双龙探颈入瓶口的造型别具匠心。 展柜中陈列着唐代开元通宝钱币，它作为唐代主力货币，奠定了此后一千多年中国硬币的基本格式。 接着看到的是唐代刻花银碗，碗体上精美的锤揲纹饰展现了盛唐时期中外文化交流的艺术结晶。 一旁的金碗更是奢华至极，它代表了唐代金银器制作的顶尖水平，也是皇室贵族身份的象征。 再往前，一组明代彩绘釉陶仪仗俑群规模宏大，骑马俑、乐俑、侍从俑等详细刻画了当时的官服与仪仗制度。 ","date":"2024-05-22","objectID":"/posts/%E6%96%87%E7%89%A9%E6%97%A0%E8%A8%80%E8%B6%8A%E5%8D%83%E5%B9%B4%E8%8A%99%E8%93%89%E5%9B%AD%E9%87%8C%E8%A7%81%E7%9B%9B%E5%94%90/:1:4","tags":["旅游"],"title":"文物无言越千年，芙蓉园里见盛唐","uri":"/posts/%E6%96%87%E7%89%A9%E6%97%A0%E8%A8%80%E8%B6%8A%E5%8D%83%E5%B9%B4%E8%8A%99%E8%93%89%E5%9B%AD%E9%87%8C%E8%A7%81%E7%9B%9B%E5%94%90/#唐"},{"categories":"旅游","collections":["超超的奇妙冒险"],"content":"1.5 近代\r最后，一份中国人民志愿军五六五团全体指战员写给毛主席的信令人动容，它承载着抗美援朝时期战士们对祖国的赤诚与必胜的信念。 ","date":"2024-05-22","objectID":"/posts/%E6%96%87%E7%89%A9%E6%97%A0%E8%A8%80%E8%B6%8A%E5%8D%83%E5%B9%B4%E8%8A%99%E8%93%89%E5%9B%AD%E9%87%8C%E8%A7%81%E7%9B%9B%E5%94%90/:1:5","tags":["旅游"],"title":"文物无言越千年，芙蓉园里见盛唐","uri":"/posts/%E6%96%87%E7%89%A9%E6%97%A0%E8%A8%80%E8%B6%8A%E5%8D%83%E5%B9%B4%E8%8A%99%E8%93%89%E5%9B%AD%E9%87%8C%E8%A7%81%E7%9B%9B%E5%94%90/#近代"},{"categories":"旅游","collections":["超超的奇妙冒险"],"content":"第 2 节 大唐芙蓉园\r刚踏入大唐芙蓉园，首先映入眼帘的是那气势恢宏的匾额，“大唐芙蓉园\"几个大字以红色边框精心包裹，笔触间尽显盛唐风韵，仿佛一瞬间便将人拉回了那个繁华似锦的年代。 再往前走，一座巍峨的大殿矗立在眼前，殿身上赫然写着\"大唐芙蓉园”，金碧辉煌、气派非凡，让人不禁驻足仰望。 绕过殿堂，来到园中的假山与喷泉旁，清澈的水流从山石间倾泻而下，恰逢阳光洒落，一道绚丽的彩虹悄然浮现于水雾之上，美不胜收。 继续沿着蜿蜒的道路前行，随手拍下两旁的古风建筑与葱郁景致，漫步其间，每一步都仿佛踏在历史的画卷中。 随后循着喧闹声来到演出现场，一场精彩的实景演出正在上演，演员们身着战袍、策马奔腾，场面十分壮观。 目光所及之处，只见骑手们严阵以待、英姿飒爽，马蹄声声与激昂的配乐交织在一起，让人真切感受到了大唐将士的威武与豪情。 ","date":"2024-05-22","objectID":"/posts/%E6%96%87%E7%89%A9%E6%97%A0%E8%A8%80%E8%B6%8A%E5%8D%83%E5%B9%B4%E8%8A%99%E8%93%89%E5%9B%AD%E9%87%8C%E8%A7%81%E7%9B%9B%E5%94%90/:2:0","tags":["旅游"],"title":"文物无言越千年，芙蓉园里见盛唐","uri":"/posts/%E6%96%87%E7%89%A9%E6%97%A0%E8%A8%80%E8%B6%8A%E5%8D%83%E5%B9%B4%E8%8A%99%E8%93%89%E5%9B%AD%E9%87%8C%E8%A7%81%E7%9B%9B%E5%94%90/#大唐芙蓉园"},{"categories":"旅游","collections":["超超的奇妙冒险"],"content":"第 1 节 启程\r来到华山脚下，抬头便见一座古朴雄伟的山门矗立在眼前，门楣上镌刻着\"华山\"两个苍劲有力的大字，笔锋遒劲、气势恢宏，让人尚未登山便已感受到这座名山的巍峨之气。 ","date":"2024-05-22","objectID":"/posts/%E5%A4%9C%E7%88%AC%E5%8D%8E%E5%B1%B1%E5%9C%A8%E7%81%AF%E7%81%AB%E4%B8%8E%E6%9C%9D%E9%9C%9E%E4%B9%8B%E9%97%B4/:1:0","tags":["旅游"],"title":"夜爬华山：在灯火与朝霞之间","uri":"/posts/%E5%A4%9C%E7%88%AC%E5%8D%8E%E5%B1%B1%E5%9C%A8%E7%81%AF%E7%81%AB%E4%B8%8E%E6%9C%9D%E9%9C%9E%E4%B9%8B%E9%97%B4/#启程"},{"categories":"旅游","collections":["超超的奇妙冒险"],"content":"第 2 节 夜景\r夜幕降临，我来到华山脚下，眼前瞬间被一片璀璨灯火所包围，山脚下的古镇与商铺灯火通明，热闹非凡。 继续向前走去，街道两旁的红灯笼与霓虹交相辉映，将整片区域照得如同白昼一般，游客们三三两两地漫步其中，烟火气十足。 抬头仰望，华山巍峨的轮廓在夜色中若隐若现，而脚下却是亮堂堂的一片繁华，登山前的这片温暖灯火让人倍感安心。 再往前走几步，灯光沿着山路蜿蜒而上，仿佛一条发光的绸带系在山腰，为即将开始的夜爬之旅增添了几分期待与浪漫。 ","date":"2024-05-22","objectID":"/posts/%E5%A4%9C%E7%88%AC%E5%8D%8E%E5%B1%B1%E5%9C%A8%E7%81%AF%E7%81%AB%E4%B8%8E%E6%9C%9D%E9%9C%9E%E4%B9%8B%E9%97%B4/:2:0","tags":["旅游"],"title":"夜爬华山：在灯火与朝霞之间","uri":"/posts/%E5%A4%9C%E7%88%AC%E5%8D%8E%E5%B1%B1%E5%9C%A8%E7%81%AF%E7%81%AB%E4%B8%8E%E6%9C%9D%E9%9C%9E%E4%B9%8B%E9%97%B4/#夜景"},{"categories":"旅游","collections":["超超的奇妙冒险"],"content":"第 3 节 上山之路\r夜爬的序幕从山脚开始就已热闹非凡，石阶上人头攒动，手电筒的光束交织成一条流动的光河，大家三三两两结伴而行，谈笑声在山谷间回荡。 继续往上攀登，阶梯愈发陡峭，但人群的热度丝毫不减，人挨着人、肩并着肩，虽然前行缓慢，却多了几分相互鼓劲的温暖，整个华山之夜因这熙攘的人潮而显得格外生机勃勃。 ","date":"2024-05-22","objectID":"/posts/%E5%A4%9C%E7%88%AC%E5%8D%8E%E5%B1%B1%E5%9C%A8%E7%81%AF%E7%81%AB%E4%B8%8E%E6%9C%9D%E9%9C%9E%E4%B9%8B%E9%97%B4/:3:0","tags":["旅游"],"title":"夜爬华山：在灯火与朝霞之间","uri":"/posts/%E5%A4%9C%E7%88%AC%E5%8D%8E%E5%B1%B1%E5%9C%A8%E7%81%AF%E7%81%AB%E4%B8%8E%E6%9C%9D%E9%9C%9E%E4%B9%8B%E9%97%B4/#上山之路"},{"categories":"旅游","collections":["超超的奇妙冒险"],"content":"第 4 节 休憩点\r在华山的一处休息点小憩，眼前张灯结彩，树上密密匝匝地挂满了红色的许愿牌，在微风中轻轻摇曳，承载着无数旅人美好的愿景与期盼，为这座险峻的名山平添了几分温馨与喜庆的气息。 ","date":"2024-05-22","objectID":"/posts/%E5%A4%9C%E7%88%AC%E5%8D%8E%E5%B1%B1%E5%9C%A8%E7%81%AF%E7%81%AB%E4%B8%8E%E6%9C%9D%E9%9C%9E%E4%B9%8B%E9%97%B4/:4:0","tags":["旅游"],"title":"夜爬华山：在灯火与朝霞之间","uri":"/posts/%E5%A4%9C%E7%88%AC%E5%8D%8E%E5%B1%B1%E5%9C%A8%E7%81%AF%E7%81%AB%E4%B8%8E%E6%9C%9D%E9%9C%9E%E4%B9%8B%E9%97%B4/#休憩点"},{"categories":"旅游","collections":["超超的奇妙冒险"],"content":"第 5 节 日出东方\r凌晨时分，我早已站在华山之巅，天际还是一片深沉的灰蓝色，群山在朦胧的晨雾中若隐若现，万籁俱寂，只能听见山风轻拂的细响。 渐渐地，天边泛起了一抹微弱的鱼肚白，那光线极淡极柔，像是给远方的山峦勾勒出一道浅浅的银边。 随后，东方的云层开始透出淡淡的橘红色，天空仿佛被轻轻点燃，温柔的色彩在山峦间悄然蔓延开来。 紧接着，一轮红日露出一小弯弧线，像是羞涩的少女偷偷探出头来，光芒开始驱散四周的黑暗。 太阳缓慢而坚定地向上攀升，半圆形的光球悬于云海之上，金色的光芒如利剑般刺破雾霭，照亮了整个山峦。 再往前看，朝阳终于完全跃出了地平线，圆润饱满，霞光万丈，整片天空被染成了一片绚烂的橙红色。 此刻，太阳高高挂在天空之中，光芒耀眼而温暖，脚下的云海翻腾起伏，山峰在阳光下熠熠生辉，这番壮丽景象让人心潮澎湃、久久难忘。 ","date":"2024-05-22","objectID":"/posts/%E5%A4%9C%E7%88%AC%E5%8D%8E%E5%B1%B1%E5%9C%A8%E7%81%AF%E7%81%AB%E4%B8%8E%E6%9C%9D%E9%9C%9E%E4%B9%8B%E9%97%B4/:5:0","tags":["旅游"],"title":"夜爬华山：在灯火与朝霞之间","uri":"/posts/%E5%A4%9C%E7%88%AC%E5%8D%8E%E5%B1%B1%E5%9C%A8%E7%81%AF%E7%81%AB%E4%B8%8E%E6%9C%9D%E9%9C%9E%E4%B9%8B%E9%97%B4/#日出东方"},{"categories":"旅游","collections":["超超的奇妙冒险"],"content":"第 6 节 植被\r下山途中，沿着蜿蜒的石阶缓缓前行，白天的华山褪去了清晨的朦胧与夜晚的神秘，山林间的每一寸景致都显得格外清晰动人。 再往前看，阳光透过茂密的树冠洒下斑驳光影，山风拂过带来阵阵松涛与草木的清香，让这趟下山的旅途别有一番宁静悠远的滋味。 ","date":"2024-05-22","objectID":"/posts/%E5%A4%9C%E7%88%AC%E5%8D%8E%E5%B1%B1%E5%9C%A8%E7%81%AF%E7%81%AB%E4%B8%8E%E6%9C%9D%E9%9C%9E%E4%B9%8B%E9%97%B4/:6:0","tags":["旅游"],"title":"夜爬华山：在灯火与朝霞之间","uri":"/posts/%E5%A4%9C%E7%88%AC%E5%8D%8E%E5%B1%B1%E5%9C%A8%E7%81%AF%E7%81%AB%E4%B8%8E%E6%9C%9D%E9%9C%9E%E4%B9%8B%E9%97%B4/#植被"},{"categories":"旅游","collections":["超超的奇妙冒险"],"content":"第 7 节 山峰\r刚到华山脚下，抬头仰望，只见山势陡峭，峰峦如削，那股直插云霄的险峻气势便让人心头一凛。 沿着石阶拾级而上，两侧悬崖深不见底，窄窄的栈道仅容一人通过，每一步都走得心惊胆战。 再往前走，迎面便是近乎垂直的岩壁，铁索在风中微微摇晃，攀爬其中方知何谓「一失足成千古恨」。 终于登上峰顶，俯瞰脚下云海翻涌，方才惊险万分的一路跋涉，此刻都化作了眼前这壮丽绝伦的风景。 ","date":"2024-05-22","objectID":"/posts/%E5%A4%9C%E7%88%AC%E5%8D%8E%E5%B1%B1%E5%9C%A8%E7%81%AF%E7%81%AB%E4%B8%8E%E6%9C%9D%E9%9C%9E%E4%B9%8B%E9%97%B4/:7:0","tags":["旅游"],"title":"夜爬华山：在灯火与朝霞之间","uri":"/posts/%E5%A4%9C%E7%88%AC%E5%8D%8E%E5%B1%B1%E5%9C%A8%E7%81%AF%E7%81%AB%E4%B8%8E%E6%9C%9D%E9%9C%9E%E4%B9%8B%E9%97%B4/#山峰"},{"categories":"旅游","collections":["超超的奇妙冒险"],"content":"第 8 节 小小华山，轻松拿下\r来到华山脚下，一块写着「小小华山，轻松拿下」的路牌格外醒目，让人会心一笑——攀登的疲惫在这一刻化作了满满的成就感，仿佛所有的付出都得到了最好的回报。 ","date":"2024-05-22","objectID":"/posts/%E5%A4%9C%E7%88%AC%E5%8D%8E%E5%B1%B1%E5%9C%A8%E7%81%AF%E7%81%AB%E4%B8%8E%E6%9C%9D%E9%9C%9E%E4%B9%8B%E9%97%B4/:8:0","tags":["旅游"],"title":"夜爬华山：在灯火与朝霞之间","uri":"/posts/%E5%A4%9C%E7%88%AC%E5%8D%8E%E5%B1%B1%E5%9C%A8%E7%81%AF%E7%81%AB%E4%B8%8E%E6%9C%9D%E9%9C%9E%E4%B9%8B%E9%97%B4/#小小华山轻松拿下"},{"categories":"旅游","collections":["超超的奇妙冒险"],"content":"第 1 节 秦始皇兵马俑\r刚进入景区，一尊高大的秦始皇雕像便巍然矗立在前方，气势威严，仿佛在无声地诉说着两千年前的帝国荣光。 随后走入一号坑展厅，站在观景台上俯瞰，只见成千上万个陶俑整齐列阵，军容浩荡，场面极为震撼。 换个角度再看过去，兵马俑的队列依然望不到尽头，每一排每一列都井然有序，令人不禁遥想当年秦军的雄风。 而在另一侧，几位工作人员正用现代科技设备小心地发掘和修复着破碎的陶俑，古老文明与当代技术在此交汇，令人肃然起敬。 走到近处细看一尊单独的步兵俑，他的面容、发髻甚至铠甲纹路都刻画得细致入微，仿佛下一秒就要活过来一般。 离开兵马俑坑，我走进了旁边的文创商店，迎面就看到一组名为“五鼠运财”的艺术品，五只小鼠姿态各异，寓意吉祥。 再往里走，一件“封侯拜相”的摆件吸引了我的目光——猴子与大象的组合精妙地运用了谐音，寄托着步步高升的美好愿望。 一旁的翠绿玉白菜同样引人注目，取“百财”之谐音，寄托了招财聚财的好彩头，让人忍不住多看几眼。 继续前行，一尊唐代三彩骑马俑静静陈列在展柜中，釉色流光溢彩，马匹肥壮矫健，与先前秦俑的肃穆风格截然不同，展现了另一种盛世气象。 ","date":"2024-05-21","objectID":"/posts/%E7%A7%A6%E4%BF%91%E9%9D%99%E9%BB%98%E9%AA%8A%E5%B1%B1%E7%81%AF%E8%B5%B7%E4%B8%80%E6%97%A5%E7%A9%BF%E8%B6%8A%E4%B8%A4%E5%8D%83%E5%B9%B4/:1:0","tags":["旅游"],"title":"秦俑静默，骊山灯起：一日穿越两千年","uri":"/posts/%E7%A7%A6%E4%BF%91%E9%9D%99%E9%BB%98%E9%AA%8A%E5%B1%B1%E7%81%AF%E8%B5%B7%E4%B8%80%E6%97%A5%E7%A9%BF%E8%B6%8A%E4%B8%A4%E5%8D%83%E5%B9%B4/#秦始皇兵马俑"},{"categories":"旅游","collections":["超超的奇妙冒险"],"content":"第 2 节 华清池\r踏入华清池，首先映入眼帘的是那翩翩欲飞的飞天造型，仿佛将人瞬间拉回了盛唐的浪漫与辉煌。 转身望向那碧绿的池水，澄澈如镜，倒映着池畔的垂柳和远处的楼阁。 微风拂过，柳条轻摇，远方古雅的楼影安静地沉在水中，与实景交相辉映，别有一番韵味。 抬眼远眺，骊山静默地横亘在天际，山色空蒙，为这座皇家园林添了几分苍茫之感。 待到夜幕降临，山上因《长恨歌》演出而亮起点点灯火，如繁星洒落山间，与池水中的光影交织成一幅梦幻的画卷。 ","date":"2024-05-21","objectID":"/posts/%E7%A7%A6%E4%BF%91%E9%9D%99%E9%BB%98%E9%AA%8A%E5%B1%B1%E7%81%AF%E8%B5%B7%E4%B8%80%E6%97%A5%E7%A9%BF%E8%B6%8A%E4%B8%A4%E5%8D%83%E5%B9%B4/:2:0","tags":["旅游"],"title":"秦俑静默，骊山灯起：一日穿越两千年","uri":"/posts/%E7%A7%A6%E4%BF%91%E9%9D%99%E9%BB%98%E9%AA%8A%E5%B1%B1%E7%81%AF%E8%B5%B7%E4%B8%80%E6%97%A5%E7%A9%BF%E8%B6%8A%E4%B8%A4%E5%8D%83%E5%B9%B4/#华清池"},{"categories":"旅游","collections":["超超的奇妙冒险"],"content":"第 3 节 长恨歌\r走进临潼华清宫，大型实景历史舞剧《长恨歌》的序幕缓缓拉开，眼前是盛唐宫廷的盛世欢宴，歌舞升平，一派繁华景象。 舞台中央，杨贵妃身披霓裳羽衣翩翩起舞，唐玄宗为之倾倒，两人在这如梦如幻的场景中相遇，一段千古绝恋就此开启。 远远望去，灯火辉煌处唐玄宗与杨贵妃深情对望，那惊鸿一瞥注定了两人一生的牵绊。 接着场景切换至骊山脚下，华清宫中处处弥漫着温泉的氤氲水汽，两人的爱恋在此愈发浓郁。 再往前看，他们在宫中相依相伴、琴瑟和鸣，尽情享受着只羡鸳鸯不羡仙的甜蜜时光。 然而好景不长，安史之乱骤然爆发，舞台上火光飞溅、烈焰翻腾，战争的阴影瞬间笼罩了一切。 熊熊烈火席卷舞台，炽热的气浪直逼观众席，即便坐在台下也能感受到那令人窒息的灼热与毁灭。 当战火逐渐平息，舞台灯光转为柔和梦幻，仿佛踏入了另一个时空。 仙雾缭绕的天界中，历经磨难的恋人终于得以重逢，彼此的眼中满是思念与深情。 正如诗中所言「在天愿作比翼鸟」，他们跨越生死界限，化作比翼鸟相伴永远，为这段凄美的爱情传说画上了圆满的句号。 ","date":"2024-05-21","objectID":"/posts/%E7%A7%A6%E4%BF%91%E9%9D%99%E9%BB%98%E9%AA%8A%E5%B1%B1%E7%81%AF%E8%B5%B7%E4%B8%80%E6%97%A5%E7%A9%BF%E8%B6%8A%E4%B8%A4%E5%8D%83%E5%B9%B4/:3:0","tags":["旅游"],"title":"秦俑静默，骊山灯起：一日穿越两千年","uri":"/posts/%E7%A7%A6%E4%BF%91%E9%9D%99%E9%BB%98%E9%AA%8A%E5%B1%B1%E7%81%AF%E8%B5%B7%E4%B8%80%E6%97%A5%E7%A9%BF%E8%B6%8A%E4%B8%A4%E5%8D%83%E5%B9%B4/#长恨歌"},{"categories":"旅游","collections":["超超的奇妙冒险"],"content":"第 1 节 华夏文旅大剧院-驼铃传奇\r","date":"2024-05-21","objectID":"/posts/%E4%B8%80%E5%9C%BA%E9%A9%BC%E9%93%83%E4%BC%A0%E5%A5%87%E8%B5%B0%E5%AE%8C%E5%8D%83%E5%B9%B4%E4%B8%9D%E8%B7%AF%E7%9A%84%E7%A6%BB%E5%90%88%E6%82%B2%E6%AC%A2/:1:0","tags":["旅游"],"title":"一场《驼铃传奇》，走完千年丝路的离合悲欢","uri":"/posts/%E4%B8%80%E5%9C%BA%E9%A9%BC%E9%93%83%E4%BC%A0%E5%A5%87%E8%B5%B0%E5%AE%8C%E5%8D%83%E5%B9%B4%E4%B8%9D%E8%B7%AF%E7%9A%84%E7%A6%BB%E5%90%88%E6%82%B2%E6%AC%A2/#华夏文旅大剧院-驼铃传奇"},{"categories":"旅游","collections":["超超的奇妙冒险"],"content":"1.1 岁月再现\r走进华夏文旅大剧院，《驼铃传奇》的序幕《岁月再现》缓缓拉开，仿佛穿越了时空的隧道，将我带回到丝绸之路的历史起点，置身于大唐与丝路交织的宏大背景之中。 ","date":"2024-05-21","objectID":"/posts/%E4%B8%80%E5%9C%BA%E9%A9%BC%E9%93%83%E4%BC%A0%E5%A5%87%E8%B5%B0%E5%AE%8C%E5%8D%83%E5%B9%B4%E4%B8%9D%E8%B7%AF%E7%9A%84%E7%A6%BB%E5%90%88%E6%82%B2%E6%AC%A2/:1:1","tags":["旅游"],"title":"一场《驼铃传奇》，走完千年丝路的离合悲欢","uri":"/posts/%E4%B8%80%E5%9C%BA%E9%A9%BC%E9%93%83%E4%BC%A0%E5%A5%87%E8%B5%B0%E5%AE%8C%E5%8D%83%E5%B9%B4%E4%B8%9D%E8%B7%AF%E7%9A%84%E7%A6%BB%E5%90%88%E6%82%B2%E6%AC%A2/#岁月再现"},{"categories":"旅游","collections":["超超的奇妙冒险"],"content":"1.2 送君千里\r随后，第一幕「送君千里」映入眼帘，驼队从长安启程，亲人们依依不舍地挥手告别——眼前的房屋缓缓展开，屋内送别的场景令人动容，既有离别的惆怅，又饱含对远方未知的期待。 ","date":"2024-05-21","objectID":"/posts/%E4%B8%80%E5%9C%BA%E9%A9%BC%E9%93%83%E4%BC%A0%E5%A5%87%E8%B5%B0%E5%AE%8C%E5%8D%83%E5%B9%B4%E4%B8%9D%E8%B7%AF%E7%9A%84%E7%A6%BB%E5%90%88%E6%82%B2%E6%AC%A2/:1:2","tags":["旅游"],"title":"一场《驼铃传奇》，走完千年丝路的离合悲欢","uri":"/posts/%E4%B8%80%E5%9C%BA%E9%A9%BC%E9%93%83%E4%BC%A0%E5%A5%87%E8%B5%B0%E5%AE%8C%E5%8D%83%E5%B9%B4%E4%B8%9D%E8%B7%AF%E7%9A%84%E7%A6%BB%E5%90%88%E6%82%B2%E6%AC%A2/#送君千里"},{"categories":"旅游","collections":["超超的奇妙冒险"],"content":"1.3 狼道遇险\r接着画面一转，驼队已进入茫茫荒漠，风雪交加，天地苍茫，赶路的身影在严寒中显得格外艰辛。 继续前行，风沙愈发猛烈，商旅们裹紧衣袍，顶着风雪艰难跋涉，每一步都踏在生存的边缘。 再往前走，荒原之上，虽然未见狼群身影，但风雪中的驼铃声响与紧张的氛围已让人感受到潜在的危机——这便是第二幕「狼道遇险」的危险前奏。 ","date":"2024-05-21","objectID":"/posts/%E4%B8%80%E5%9C%BA%E9%A9%BC%E9%93%83%E4%BC%A0%E5%A5%87%E8%B5%B0%E5%AE%8C%E5%8D%83%E5%B9%B4%E4%B8%9D%E8%B7%AF%E7%9A%84%E7%A6%BB%E5%90%88%E6%82%B2%E6%AC%A2/:1:3","tags":["旅游"],"title":"一场《驼铃传奇》，走完千年丝路的离合悲欢","uri":"/posts/%E4%B8%80%E5%9C%BA%E9%A9%BC%E9%93%83%E4%BC%A0%E5%A5%87%E8%B5%B0%E5%AE%8C%E5%8D%83%E5%B9%B4%E4%B8%9D%E8%B7%AF%E7%9A%84%E7%A6%BB%E5%90%88%E6%82%B2%E6%AC%A2/#狼道遇险"},{"categories":"旅游","collections":["超超的奇妙冒险"],"content":"1.4 异国风情\r穿过险境，第三幕「异国风情」如一幅绚丽画卷展开，西域的土地上，不同民族的舞者们身着鲜艳服饰，跳起欢快的舞蹈。 继续观赏，异域的音乐声回荡在耳畔，舞者们的每一个旋转都诉说着文化交流的故事。 再往前走，来自中亚地区的风俗展示令人目不暇接，五彩斑斓的服饰与热情洋溢的表演相得益彰。 不远处，更远地区的民族风情也一一呈现，仿佛沿着丝绸之路一路西行，每一步都有新的文化惊喜。 ","date":"2024-05-21","objectID":"/posts/%E4%B8%80%E5%9C%BA%E9%A9%BC%E9%93%83%E4%BC%A0%E5%A5%87%E8%B5%B0%E5%AE%8C%E5%8D%83%E5%B9%B4%E4%B8%9D%E8%B7%AF%E7%9A%84%E7%A6%BB%E5%90%88%E6%82%B2%E6%AC%A2/:1:4","tags":["旅游"],"title":"一场《驼铃传奇》，走完千年丝路的离合悲欢","uri":"/posts/%E4%B8%80%E5%9C%BA%E9%A9%BC%E9%93%83%E4%BC%A0%E5%A5%87%E8%B5%B0%E5%AE%8C%E5%8D%83%E5%B9%B4%E4%B8%9D%E8%B7%AF%E7%9A%84%E7%A6%BB%E5%90%88%E6%82%B2%E6%AC%A2/#异国风情"},{"categories":"旅游","collections":["超超的奇妙冒险"],"content":"1.5 祥雨洗尘\r继续前行，第四幕「祥雨洗尘」带来了宗教与精神的洗礼，庄严的佛像浮现眼前，慈悲的目光注视着每一位旅人。 另一幅画面中，佛像的庄严法相更显清晰，金光闪烁间，仿佛能听见远古的梵音。 再往前走，两位僧人出现在视野中，他们的造型酷似玄奘法师，彷佛正沿着丝路西行取经。 紧接着，僧人们的身影伴随佛光，他们的脚步坚定，仿佛在传递着信仰的力量。 随后，骆驼的身影出现在画面中，这些丝路上的忠实伙伴，见证了无数商旅的艰辛与虔诚。 另一幅骆驼的画面中，它们与僧侣同行，在漫漫黄沙中勾勒出一幅信仰与坚持的画卷。 最后，圣水从高处倾泻而下，壮观的水幕如天降甘霖，象征着经历磨难之后的净化与希望——这是整场演出中最令人震撼的精神洗礼。 ","date":"2024-05-21","objectID":"/posts/%E4%B8%80%E5%9C%BA%E9%A9%BC%E9%93%83%E4%BC%A0%E5%A5%87%E8%B5%B0%E5%AE%8C%E5%8D%83%E5%B9%B4%E4%B8%9D%E8%B7%AF%E7%9A%84%E7%A6%BB%E5%90%88%E6%82%B2%E6%AC%A2/:1:5","tags":["旅游"],"title":"一场《驼铃传奇》，走完千年丝路的离合悲欢","uri":"/posts/%E4%B8%80%E5%9C%BA%E9%A9%BC%E9%93%83%E4%BC%A0%E5%A5%87%E8%B5%B0%E5%AE%8C%E5%8D%83%E5%B9%B4%E4%B8%9D%E8%B7%AF%E7%9A%84%E7%A6%BB%E5%90%88%E6%82%B2%E6%AC%A2/#祥雨洗尘"},{"categories":"旅游","collections":["超超的奇妙冒险"],"content":"1.6 迎郎归来\r紧接着，第五幕「迎郎归来」温情上演，舞台上孩子与母亲久别重逢，相见的那一刻，无尽的思念之情溢于言表。 继续看去，母子相认的瞬间令人动容。 再往前，母子紧紧相拥，所有的等待与牵挂在这一刻化作了温暖的拥抱，感人至深。 随后场景切换，一对年轻情侣在桥上相遇，两人深情对望，仿佛有千言万语想要诉说。 最终，他们在桥上紧紧相拥，团圆的美好感染了在场的每一位观众，温暖了整座剧场。 ","date":"2024-05-21","objectID":"/posts/%E4%B8%80%E5%9C%BA%E9%A9%BC%E9%93%83%E4%BC%A0%E5%A5%87%E8%B5%B0%E5%AE%8C%E5%8D%83%E5%B9%B4%E4%B8%9D%E8%B7%AF%E7%9A%84%E7%A6%BB%E5%90%88%E6%82%B2%E6%AC%A2/:1:6","tags":["旅游"],"title":"一场《驼铃传奇》，走完千年丝路的离合悲欢","uri":"/posts/%E4%B8%80%E5%9C%BA%E9%A9%BC%E9%93%83%E4%BC%A0%E5%A5%87%E8%B5%B0%E5%AE%8C%E5%8D%83%E5%B9%B4%E4%B8%9D%E8%B7%AF%E7%9A%84%E7%A6%BB%E5%90%88%E6%82%B2%E6%AC%A2/#迎郎归来"},{"categories":"旅游","collections":["超超的奇妙冒险"],"content":"1.7 华夏盛世\r最后一幕「华夏盛世」，商队荣耀归来，长安城一片繁华盛景，丝绸之路带来的文明交融与繁荣在此刻达到巅峰，为这场震撼人心的丝路之旅画上了完美的句号。 ","date":"2024-05-21","objectID":"/posts/%E4%B8%80%E5%9C%BA%E9%A9%BC%E9%93%83%E4%BC%A0%E5%A5%87%E8%B5%B0%E5%AE%8C%E5%8D%83%E5%B9%B4%E4%B8%9D%E8%B7%AF%E7%9A%84%E7%A6%BB%E5%90%88%E6%82%B2%E6%AC%A2/:1:7","tags":["旅游"],"title":"一场《驼铃传奇》，走完千年丝路的离合悲欢","uri":"/posts/%E4%B8%80%E5%9C%BA%E9%A9%BC%E9%93%83%E4%BC%A0%E5%A5%87%E8%B5%B0%E5%AE%8C%E5%8D%83%E5%B9%B4%E4%B8%9D%E8%B7%AF%E7%9A%84%E7%A6%BB%E5%90%88%E6%82%B2%E6%AC%A2/#华夏盛世"},{"categories":"旅游","collections":["超超的奇妙冒险"],"content":"第 1 节 新城广场\r刚踏入新城广场，首先映入眼帘的是一座气势恢宏的喷泉，水柱伴随着节奏高高跃起，在阳光下折射出晶莹的光彩，瞬间让整个广场充满了活力。 目光越过喷泉，不远处矗立着一座豪华大酒店，其宏伟的建筑轮廓与湛蓝的天空交相辉映，显得格外壮观而恢宏。 转身向广场一侧望去，陕西省政府大楼庄严地坐落在那里，简约而气派的线条散发出一种沉稳的威严感，让人不禁肃然起敬。 而在广场中央，成群的白鸽正悠闲地踱步觅食，游客们纷纷伸手喂食，鸽子们扑棱着翅膀争相啄食，那可爱又热闹的场景为这座庄重的广场增添了几分灵动与温情。 ","date":"2024-05-20","objectID":"/posts/%E8%A5%BF%E5%AE%89%E7%9A%84%E6%98%BC%E5%A4%9C%E4%B9%8B%E9%97%B4%E8%97%8F%E7%9D%80%E5%8D%83%E5%B9%B4%E7%83%9F%E7%81%AB%E6%B0%94/:1:0","tags":["旅游"],"title":"西安的昼夜之间，藏着千年烟火气","uri":"/posts/%E8%A5%BF%E5%AE%89%E7%9A%84%E6%98%BC%E5%A4%9C%E4%B9%8B%E9%97%B4%E8%97%8F%E7%9D%80%E5%8D%83%E5%B9%B4%E7%83%9F%E7%81%AB%E6%B0%94/#新城广场"},{"categories":"旅游","collections":["超超的奇妙冒险"],"content":"第 2 节 永兴坊\r永兴坊入口处人头攒动，网红打卡氛围浓厚，游客纷纷在此合影留念。 墙上一个大大的\"biang\"字格外醒目，仿佛瞬间就将我拉进了陕西的烟火气中。 美食街内各式摊位一字排开，烟火气十足，但售卖的小吃与其他城市美食街颇为相似。 游客手持特色小吃边走边逛，虽然品类常见，但热闹的街市氛围依然令人愉快。 ","date":"2024-05-20","objectID":"/posts/%E8%A5%BF%E5%AE%89%E7%9A%84%E6%98%BC%E5%A4%9C%E4%B9%8B%E9%97%B4%E8%97%8F%E7%9D%80%E5%8D%83%E5%B9%B4%E7%83%9F%E7%81%AB%E6%B0%94/:2:0","tags":["旅游"],"title":"西安的昼夜之间，藏着千年烟火气","uri":"/posts/%E8%A5%BF%E5%AE%89%E7%9A%84%E6%98%BC%E5%A4%9C%E4%B9%8B%E9%97%B4%E8%97%8F%E7%9D%80%E5%8D%83%E5%B9%B4%E7%83%9F%E7%81%AB%E6%B0%94/#永兴坊"},{"categories":"旅游","collections":["超超的奇妙冒险"],"content":"第 3 节 钟楼\r初到钟楼时正值白昼，这座古建筑在阳光的照耀下显得格外庄重恢弘，飞檐翘角清晰可见，青砖灰瓦沉淀着岁月的厚重感。 等到夜幕降临再回到这里，眼前的钟楼已换上璀璨的金色华服，在深蓝夜空的映衬下通体流光溢彩，与白天的沉稳形成鲜明对比，让人不禁为古都的昼夜魅力所折服。 ","date":"2024-05-20","objectID":"/posts/%E8%A5%BF%E5%AE%89%E7%9A%84%E6%98%BC%E5%A4%9C%E4%B9%8B%E9%97%B4%E8%97%8F%E7%9D%80%E5%8D%83%E5%B9%B4%E7%83%9F%E7%81%AB%E6%B0%94/:3:0","tags":["旅游"],"title":"西安的昼夜之间，藏着千年烟火气","uri":"/posts/%E8%A5%BF%E5%AE%89%E7%9A%84%E6%98%BC%E5%A4%9C%E4%B9%8B%E9%97%B4%E8%97%8F%E7%9D%80%E5%8D%83%E5%B9%B4%E7%83%9F%E7%81%AB%E6%B0%94/#钟楼"},{"categories":"旅游","collections":["超超的奇妙冒险"],"content":"第 4 节 鼓楼\r站在西安鼓楼前，仰望这座古老的建筑，气势恢宏、庄严巍峨，扑面而来的历史厚重感令人心生敬畏。 走近细看，鼓楼的飞檐斗拱与精美彩绘尽显匠心，每一处细节都诉说着古都昔日的繁华与荣耀。 随后转身来到鼓楼边上的回民小吃街，人潮如织、热闹非凡，空气中弥漫着各种美食的诱人香气。 继续往里走，偶然拐进一家餐厅，内部别有洞天——亭台水榭、烟雾缭绕，仿佛步入了一处世外桃源，仙气飘飘。 ","date":"2024-05-20","objectID":"/posts/%E8%A5%BF%E5%AE%89%E7%9A%84%E6%98%BC%E5%A4%9C%E4%B9%8B%E9%97%B4%E8%97%8F%E7%9D%80%E5%8D%83%E5%B9%B4%E7%83%9F%E7%81%AB%E6%B0%94/:4:0","tags":["旅游"],"title":"西安的昼夜之间，藏着千年烟火气","uri":"/posts/%E8%A5%BF%E5%AE%89%E7%9A%84%E6%98%BC%E5%A4%9C%E4%B9%8B%E9%97%B4%E8%97%8F%E7%9D%80%E5%8D%83%E5%B9%B4%E7%83%9F%E7%81%AB%E6%B0%94/#鼓楼"},{"categories":"旅游","collections":["超超的奇妙冒险"],"content":"第 5 节 高家大院\r走进高家大院，首先映入眼帘的是极具古典风韵的传统建筑，飞檐翘角间尽显古朴之美。 随后移步室内，雅致的陈设与静谧的氛围让人顿感心平气和。 接着，目光落在桌上摆放的精致茶具上，仿佛能想象到主人家在此品茗论道的闲适时光。 继续往庭院深处走去，浓厚的乡土气息扑面而来，一处院落里竟栖息着许多鸭子。 再走近些，只见这些鸭子悠然自得地在院中踱步嬉戏，十分可爱。 最后，我不禁驻足观望，它们憨态可掬的模样为这座古朴宅院增添了许多生机与趣味。 ","date":"2024-05-20","objectID":"/posts/%E8%A5%BF%E5%AE%89%E7%9A%84%E6%98%BC%E5%A4%9C%E4%B9%8B%E9%97%B4%E8%97%8F%E7%9D%80%E5%8D%83%E5%B9%B4%E7%83%9F%E7%81%AB%E6%B0%94/:5:0","tags":["旅游"],"title":"西安的昼夜之间，藏着千年烟火气","uri":"/posts/%E8%A5%BF%E5%AE%89%E7%9A%84%E6%98%BC%E5%A4%9C%E4%B9%8B%E9%97%B4%E8%97%8F%E7%9D%80%E5%8D%83%E5%B9%B4%E7%83%9F%E7%81%AB%E6%B0%94/#高家大院"},{"categories":"旅游","collections":["超超的奇妙冒险"],"content":"第 6 节 西安城墙\r登上西安城墙，扑面而来的便是浓厚的历史气息，古老的砖石虽历经沧桑，但近年来的翻新修缮让城墙焕发新生，脚下的每一块砖都仿佛在诉说千年的故事。 待到夜幕降临，城墙上灯火璀璨、流光溢彩，整座城墙在灯光的映衬下更显宏伟壮观，漫步其间，仿佛穿越了时空，令人流连忘返。 ","date":"2024-05-20","objectID":"/posts/%E8%A5%BF%E5%AE%89%E7%9A%84%E6%98%BC%E5%A4%9C%E4%B9%8B%E9%97%B4%E8%97%8F%E7%9D%80%E5%8D%83%E5%B9%B4%E7%83%9F%E7%81%AB%E6%B0%94/:6:0","tags":["旅游"],"title":"西安的昼夜之间，藏着千年烟火气","uri":"/posts/%E8%A5%BF%E5%AE%89%E7%9A%84%E6%98%BC%E5%A4%9C%E4%B9%8B%E9%97%B4%E8%97%8F%E7%9D%80%E5%8D%83%E5%B9%B4%E7%83%9F%E7%81%AB%E6%B0%94/#西安城墙"},{"categories":"旅游","collections":["超超的奇妙冒险"],"content":"第 1 节 渔人码头\r从香港乘船抵达澳门外港客运码头，这座城市的独特气质便扑面而来——大部分地标建筑实为赌场，连入住的酒店也是赌场的附属业务，乘坐电梯到特定楼层便可直通赌场，围绕着博彩业形成了一条龙的奢华服务链条，不过日常消费反而平价亲民，相同价格下的酒店房间比香港宽敞近一倍。 距离码头最近的渔人码头自然成为第一站，招牌透亮夺目，水面波光粼粼倒映着璀璨灯光，两棵热带树上缠绕的灯珠随风轻摇，将澳门的夜色点缀得格外迷人。 ","date":"2024-02-24","objectID":"/posts/%E7%83%9F%E8%8A%B1%E4%B8%8E%E9%87%91%E6%AE%BF%E6%BE%B3%E9%97%A8%E4%B8%80%E5%A4%9C%E6%BC%AB%E6%B8%B8/:1:0","tags":["旅游"],"title":"烟花与金殿：澳门一夜漫游","uri":"/posts/%E7%83%9F%E8%8A%B1%E4%B8%8E%E9%87%91%E6%AE%BF%E6%BE%B3%E9%97%A8%E4%B8%80%E5%A4%9C%E6%BC%AB%E6%B8%B8/#渔人码头"},{"categories":"旅游","collections":["超超的奇妙冒险"],"content":"第 2 节 烟花秀\r刚到澳门不久，路边便有人问我是不是来看烟花的，说是听说澳门科学馆那边有烟花秀，我欣然答道没错，随后便与众人一同小跑前往。 抵达时烟花秀刚刚开始，漆黑的夜空中突然炸开几颗星星点点的火光，像春天的种子在夜幕里悄然萌芽。 紧接着烟花越放越密，一朵朵在半空中绽放开来，火光四溅，照亮了整片海面和欢呼的人群。 最后烟花铺满了整个天际，满天繁星般的光芒层层叠叠地炸开，仿佛一场盛大的告别，将夜色彻底点燃。 ","date":"2024-02-24","objectID":"/posts/%E7%83%9F%E8%8A%B1%E4%B8%8E%E9%87%91%E6%AE%BF%E6%BE%B3%E9%97%A8%E4%B8%80%E5%A4%9C%E6%BC%AB%E6%B8%B8/:2:0","tags":["旅游"],"title":"烟花与金殿：澳门一夜漫游","uri":"/posts/%E7%83%9F%E8%8A%B1%E4%B8%8E%E9%87%91%E6%AE%BF%E6%BE%B3%E9%97%A8%E4%B8%80%E5%A4%9C%E6%BC%AB%E6%B8%B8/#烟花秀"},{"categories":"旅游","collections":["超超的奇妙冒险"],"content":"第 3 节 妈祖阁\r走进澳门妈祖阁，首先映入眼帘的是庄严古朴的大门，上方悬挂着鎏金牌匾，在阳光下熠熠生辉，仿佛在无声诉说着这座庙宇数百年的沧桑与灵验。 跨过门槛往里头走，地方虽不算大，却别有洞天，只见一块巨石兀然矗立，上刻「名岩」二字，为这片清幽之地平添了几分古朴韵味。 继续往里探寻，一串串螺旋状的塔香从高处垂挂而下，下方系着的红色小纸条上写满了信众的姓名与心愿，旋香寓意「好运常转」，也寄托着香火不断、福泽绵延的美好祈愿。 ","date":"2024-02-24","objectID":"/posts/%E7%83%9F%E8%8A%B1%E4%B8%8E%E9%87%91%E6%AE%BF%E6%BE%B3%E9%97%A8%E4%B8%80%E5%A4%9C%E6%BC%AB%E6%B8%B8/:3:0","tags":["旅游"],"title":"烟花与金殿：澳门一夜漫游","uri":"/posts/%E7%83%9F%E8%8A%B1%E4%B8%8E%E9%87%91%E6%AE%BF%E6%BE%B3%E9%97%A8%E4%B8%80%E5%A4%9C%E6%BC%AB%E6%B8%B8/#妈祖阁"},{"categories":"旅游","collections":["超超的奇妙冒险"],"content":"第 4 节 澳门塔\r前往妈祖阁的途中，远远便望见了澳门塔的挺拔身姿，即使只是路过时随手拍下的全景照片，也无法完整收尽它的巍峨，待走近后想必更觉高耸入云。 ","date":"2024-02-24","objectID":"/posts/%E7%83%9F%E8%8A%B1%E4%B8%8E%E9%87%91%E6%AE%BF%E6%BE%B3%E9%97%A8%E4%B8%80%E5%A4%9C%E6%BC%AB%E6%B8%B8/:4:0","tags":["旅游"],"title":"烟花与金殿：澳门一夜漫游","uri":"/posts/%E7%83%9F%E8%8A%B1%E4%B8%8E%E9%87%91%E6%AE%BF%E6%BE%B3%E9%97%A8%E4%B8%80%E5%A4%9C%E6%BC%AB%E6%B8%B8/#澳门塔"},{"categories":"旅游","collections":["超超的奇妙冒险"],"content":"第 5 节 米高梅\r刚迈进澳门米高梅的大门，一尊金光闪闪的雄狮雕像便映入眼帘，它威武地蹲坐在大堂中央，成为这座奢华殿堂最醒目的标志。 再往里走几步，另一尊银色的米高梅狮子与之遥遥相对，一金一银、一暖一冷，仿佛在无声地诉说着这座赌城的双面魅力。 继续深入大堂，周围的装修风格骤然转为古典欧洲风情，精致的雕花、柔和的灯光与高挑的天花板相得益彰，整个空间弥漫着优雅而高贵的格调。 走出室内抬头仰望，洁白的墙体上矗立着欧洲尖塔状建筑，远远望去竟有如一座庄严的天主教堂，让人一时恍惚身处欧洲古城之中。 随后转场至美狮米高梅，远远便望见那独特的外立面——几个颜色各异的矩形块错落有致地堆砌在一起，造型前卫又充满视觉冲击力。 步入其中，内部的装修风格明显更加现代，简约的线条、时尚的装饰，还有豪车店直接开在赌场旁，奢华与速度感在此完美交融。 在不远处的绿化带中，一座用植被做成的长颈鹿雕塑栩栩如生，毛茸茸的质感与逼真的神态让人忍不住赞叹园艺师的高超手法。 再往前走几步，一辆兰博基尼静静地停在展示区中，正面看去已是气势非凡，而从侧面打量，那流畅而凌厉的线条更显帅气逼人。 ","date":"2024-02-24","objectID":"/posts/%E7%83%9F%E8%8A%B1%E4%B8%8E%E9%87%91%E6%AE%BF%E6%BE%B3%E9%97%A8%E4%B8%80%E5%A4%9C%E6%BC%AB%E6%B8%B8/:5:0","tags":["旅游"],"title":"烟花与金殿：澳门一夜漫游","uri":"/posts/%E7%83%9F%E8%8A%B1%E4%B8%8E%E9%87%91%E6%AE%BF%E6%BE%B3%E9%97%A8%E4%B8%80%E5%A4%9C%E6%BC%AB%E6%B8%B8/#米高梅"},{"categories":"旅游","collections":["超超的奇妙冒险"],"content":"第 6 节 巴黎人/伦敦人/威尼斯人\r走进澳门威尼斯人、巴黎人与伦敦人组成的奢华度假村群，仿佛瞬间穿越到了欧洲大陆，三大地标汇聚一堂，令人目不暇接。 随后步入伦敦人区域，眼前高耸的大本钟复刻版在深红色灯光的映照下格外帅气，英伦风韵与现代科技感在此完美交融。 弧形顶棚遮蔽了整片天空，营造出如未来都市般的沉浸氛围，偶尔有车辆从光影交织的街道中缓缓驶过，画面如同电影场景。 继续往里走，竟在伦敦人内部发现了哈利波特中的 9¾ 车站站台，那个推着行李车穿墙而入的经典场景就静静地立在眼前，让人不禁会心一笑。 接着穿过九又四分之三站台的魔法氛围，顺势踏入另一侧的威尼斯人区域，这里的一切都围绕威尼斯地标圣马可广场构建而成。 广场周围运河蜿蜒、拱桥横跨，贡多拉船夫悠扬的歌声回荡在水道之间，让人恍惚间真的置身于水城威尼斯之中。 ","date":"2024-02-24","objectID":"/posts/%E7%83%9F%E8%8A%B1%E4%B8%8E%E9%87%91%E6%AE%BF%E6%BE%B3%E9%97%A8%E4%B8%80%E5%A4%9C%E6%BC%AB%E6%B8%B8/:6:0","tags":["旅游"],"title":"烟花与金殿：澳门一夜漫游","uri":"/posts/%E7%83%9F%E8%8A%B1%E4%B8%8E%E9%87%91%E6%AE%BF%E6%BE%B3%E9%97%A8%E4%B8%80%E5%A4%9C%E6%BC%AB%E6%B8%B8/#巴黎人伦敦人威尼斯人"},{"categories":"旅游","collections":["超超的奇妙冒险"],"content":"第 7 节 永利皇宫\r踏入澳门永利皇宫，一股令人震撼的奢华气息扑面而来，整个赌场装修如同皇家宫殿般金碧辉煌，占地面积之大竟让手机信号消失殆尽，只得连上他们的WIFI才能保持网络通畅。 缓步前行，脚下是铺满大地的红色地毯，两侧的店铺金光闪耀、鳞次栉比，仿佛漫步在皇室的御用商街中，处处彰显着极致的富贵与气派。 再往前望去，一驾黑金色的马车静静矗立在殿堂中央，精致的雕花与暗金的光泽交织出古典贵族般的优雅与威严。 抬头仰望，一座手举五环的天使雕像映入眼帘，通体鎏金的天使姿态优雅而神圣，在灯光的映照下熠熠生辉，仿佛在向每一位来客宣告这里的非凡气度。 继续往里走，连洗手间的长廊都装修得金碧辉煌，水晶吊灯与金色墙饰交相辉映，让人恍惚间分不清自己究竟身在宫殿还是盥洗室。 洗手池同样是金光璀璨、华美至极，面对这般奢靡至极的装潢，不禁对我幼小的心灵造成了极大的震撼与冲击。 ","date":"2024-02-24","objectID":"/posts/%E7%83%9F%E8%8A%B1%E4%B8%8E%E9%87%91%E6%AE%BF%E6%BE%B3%E9%97%A8%E4%B8%80%E5%A4%9C%E6%BC%AB%E6%B8%B8/:7:0","tags":["旅游"],"title":"烟花与金殿：澳门一夜漫游","uri":"/posts/%E7%83%9F%E8%8A%B1%E4%B8%8E%E9%87%91%E6%AE%BF%E6%BE%B3%E9%97%A8%E4%B8%80%E5%A4%9C%E6%BC%AB%E6%B8%B8/#永利皇宫"},{"categories":"旅游","collections":["超超的奇妙冒险"],"content":"第 8 节 赛车博物馆\r因为距离不远，便顺路走进了澳门赛车博物馆，买票入场后迎面就看到英国摩托车名将罗·哈斯拉姆的蜡像，他曾六次称霸澳门格兰披治摩托车大赛，黑红色的赛车服让人一秒回到那个热血年代。 紧接着是摩托车赛史上最成功的车手迈克尔·鲁特，九冠王的辉煌战绩令人惊叹，蜡像定格了他挥舞香槟、戴着胜利花环的经典瞬间，仿佛还能听到当年的欢呼声。 再往前走，看见了七届F1世界冠军刘易斯·汉密尔顿的蜡像，他身披英国国旗的姿态颇为醒目，虽未在澳门夺冠，但早年征战东望洋赛道的经历依然令人津津乐道。 不远处则是被誉为“澳门先生”的艾度亚度·莫他拿，他是史上首位蝉联澳门F3冠军的车手，在这条狭窄曲折的赛道上堪称传奇一般的存在。 看完蜡像后，我和其他游客一起体验了F1赛车和摩托车对战游戏，坐在模拟座舱里操控还真有些手忙脚乱——车不太好开，一不小心就撞墙，随后又看一位小孩哥专注地给赛车换轮胎，虽然整个流程都能体验一遍，但整体趣味性其实一般。 ","date":"2024-02-24","objectID":"/posts/%E7%83%9F%E8%8A%B1%E4%B8%8E%E9%87%91%E6%AE%BF%E6%BE%B3%E9%97%A8%E4%B8%80%E5%A4%9C%E6%BC%AB%E6%B8%B8/:8:0","tags":["旅游"],"title":"烟花与金殿：澳门一夜漫游","uri":"/posts/%E7%83%9F%E8%8A%B1%E4%B8%8E%E9%87%91%E6%AE%BF%E6%BE%B3%E9%97%A8%E4%B8%80%E5%A4%9C%E6%BC%AB%E6%B8%B8/#赛车博物馆"},{"categories":"旅游","collections":["超超的奇妙冒险"],"content":"第 1 节 太平山\r从西营盘的酒店出发，搭乘公交一路向上来到太平山顶，夜色中的维多利亚港两岸灯火璀璨，九龙半岛与香港岛的万千楼宇尽收眼底，真正领略到这座不夜城的震撼与繁华。 山顶上零星点缀着几栋豪宅别墅，在寸土寸金的香港能拥有这样一方净土俯瞰万家灯火，想必是件极为惬意的事，不过夜已渐深，得赶在零点前搭上最后一班公交下山。 ","date":"2024-02-22","objectID":"/posts/%E4%BB%8E%E5%A4%AA%E5%B9%B3%E5%B1%B1%E9%A1%B6%E5%88%B0%E8%BF%AA%E5%A3%AB%E5%B0%BC%E9%A6%99%E6%B8%AF%E7%9A%84%E6%98%BC%E5%A4%9C%E5%A5%87%E9%81%87/:1:0","tags":["旅游"],"title":"从太平山顶到迪士尼，香港的昼夜奇遇","uri":"/posts/%E4%BB%8E%E5%A4%AA%E5%B9%B3%E5%B1%B1%E9%A1%B6%E5%88%B0%E8%BF%AA%E5%A3%AB%E5%B0%BC%E9%A6%99%E6%B8%AF%E7%9A%84%E6%98%BC%E5%A4%9C%E5%A5%87%E9%81%87/#太平山"},{"categories":"旅游","collections":["超超的奇妙冒险"],"content":"第 2 节 香港摩天轮\r来到香港中环，首先映入眼帘的便是那座高耸入云的摩天轮，乘坐上去，维港两岸的壮丽景色尽收眼底。 随着轿厢缓缓升高，身旁那些摩天大楼的顶部渐渐隐没在云雾之中，方知香港的天际线竟是如此高不可攀。 接着从高处远眺对岸，港面上星星点点的船只往来穿梭，为这片繁华的海港增添了几分灵动与生机。 转身俯瞰本侧的码头，只见巨轮静静停泊，一艘船便能容纳上百名乘客，令人不禁感叹这座城市的繁忙与宏大。 ","date":"2024-02-22","objectID":"/posts/%E4%BB%8E%E5%A4%AA%E5%B9%B3%E5%B1%B1%E9%A1%B6%E5%88%B0%E8%BF%AA%E5%A3%AB%E5%B0%BC%E9%A6%99%E6%B8%AF%E7%9A%84%E6%98%BC%E5%A4%9C%E5%A5%87%E9%81%87/:2:0","tags":["旅游"],"title":"从太平山顶到迪士尼，香港的昼夜奇遇","uri":"/posts/%E4%BB%8E%E5%A4%AA%E5%B9%B3%E5%B1%B1%E9%A1%B6%E5%88%B0%E8%BF%AA%E5%A3%AB%E5%B0%BC%E9%A6%99%E6%B8%AF%E7%9A%84%E6%98%BC%E5%A4%9C%E5%A5%87%E9%81%87/#香港摩天轮"},{"categories":"旅游","collections":["超超的奇妙冒险"],"content":"第 3 节 金紫荆广场\r抵达金紫荆广场时已是深夜，灯火阑珊处却见一群本地人正兴致勃勃地学跳舞，为这静谧的夜色增添了几分市井活力。 不远处的艺术装置更是引人注目——造型奇特的机器人雕像穿着古怪的服饰、摆着诡异的姿势，仿佛从未来穿越而来，而那道圆弧型的时间隧道内侧镶嵌着线条状的灯带，流光溢彩间给人一种穿梭时空的迷幻错觉。 ","date":"2024-02-22","objectID":"/posts/%E4%BB%8E%E5%A4%AA%E5%B9%B3%E5%B1%B1%E9%A1%B6%E5%88%B0%E8%BF%AA%E5%A3%AB%E5%B0%BC%E9%A6%99%E6%B8%AF%E7%9A%84%E6%98%BC%E5%A4%9C%E5%A5%87%E9%81%87/:3:0","tags":["旅游"],"title":"从太平山顶到迪士尼，香港的昼夜奇遇","uri":"/posts/%E4%BB%8E%E5%A4%AA%E5%B9%B3%E5%B1%B1%E9%A1%B6%E5%88%B0%E8%BF%AA%E5%A3%AB%E5%B0%BC%E9%A6%99%E6%B8%AF%E7%9A%84%E6%98%BC%E5%A4%9C%E5%A5%87%E9%81%87/#金紫荆广场"},{"categories":"旅游","collections":["超超的奇妙冒险"],"content":"第 4 节 沿岸步行\r夜色渐浓，香港沿岸步行道上却是另一番热闹景象——遛狗的人牵着绳子慢悠悠走着，夜跑的身影从身边掠过，还有人干脆躺在长椅上望天发呆，各种夜里不睡觉的人儿在这海滨步道上各自安好。 走着走着，路边出现了三只造型别致的布熊猫，金属支架作骨、布料包裹在外，第一只懒洋洋地趴在一个金元宝上，圆滚滚的身子十分讨喜。 再往前看，第二只熊猫正拉着一条写着「幸福满载」的横幅，仿佛在为这座不夜城送上最温暖的祝福。 不远处第三只熊猫坐在地上，手捧金元宝，左手握着手机，旁边还有一只眨着眼睛、右手推着单车的Q版小动物，萌态十足，让人忍不住会心一笑。 继续前行，路边的座椅设计同样令人惊喜——波澜起伏的造型宛如流动的曲面，鞍形曲面的线条极具艺术感，坐上去仿佛被海浪轻柔托起。 最后，一座矗立在街头的艺术品映入眼帘，那独特的鞍形曲面造型像极了巨大的薯片，既现代又俏皮，为这片海滨夜色画上了充满趣味的句号。 ","date":"2024-02-22","objectID":"/posts/%E4%BB%8E%E5%A4%AA%E5%B9%B3%E5%B1%B1%E9%A1%B6%E5%88%B0%E8%BF%AA%E5%A3%AB%E5%B0%BC%E9%A6%99%E6%B8%AF%E7%9A%84%E6%98%BC%E5%A4%9C%E5%A5%87%E9%81%87/:4:0","tags":["旅游"],"title":"从太平山顶到迪士尼，香港的昼夜奇遇","uri":"/posts/%E4%BB%8E%E5%A4%AA%E5%B9%B3%E5%B1%B1%E9%A1%B6%E5%88%B0%E8%BF%AA%E5%A3%AB%E5%B0%BC%E9%A6%99%E6%B8%AF%E7%9A%84%E6%98%BC%E5%A4%9C%E5%A5%87%E9%81%87/#沿岸步行"},{"categories":"旅游","collections":["超超的奇妙冒险"],"content":"第 5 节 铜锣湾\r沿着铜锣湾的海滨走去，几处公园在夜幕中依然开放着，保安亭里虽有人值守，却并不怎么过问往来的人，颇有几分形同虚设的闲散意味。 岸边摆放着几把躺椅，不少游人漫无目的地坐在那里，不刷手机也不交谈，只是静静地吹着海风，目光投向远处的港口。 眼前是一堆乱石滩涂，而对岸却灯火璀璨，仔细辨认，那闪烁的光芒中赫然映着「维多利亚港」几个大字，恍如隔世。 ","date":"2024-02-22","objectID":"/posts/%E4%BB%8E%E5%A4%AA%E5%B9%B3%E5%B1%B1%E9%A1%B6%E5%88%B0%E8%BF%AA%E5%A3%AB%E5%B0%BC%E9%A6%99%E6%B8%AF%E7%9A%84%E6%98%BC%E5%A4%9C%E5%A5%87%E9%81%87/:5:0","tags":["旅游"],"title":"从太平山顶到迪士尼，香港的昼夜奇遇","uri":"/posts/%E4%BB%8E%E5%A4%AA%E5%B9%B3%E5%B1%B1%E9%A1%B6%E5%88%B0%E8%BF%AA%E5%A3%AB%E5%B0%BC%E9%A6%99%E6%B8%AF%E7%9A%84%E6%98%BC%E5%A4%9C%E5%A5%87%E9%81%87/#铜锣湾"},{"categories":"旅游","collections":["超超的奇妙冒险"],"content":"第 6 节 叮叮车\r返程时，乘坐了最具标志性的交通工具——叮叮车，每当到站时那清脆的「叮」一声响起，这名字便由此而来，而这样的画面在九龙那边是见不到的。 乘坐叮叮车穿流于街巷之间，透过车窗望去，路面街景与呼啸而过的车流交织成一道道流动的光影残影，仿佛整座城市的节奏都在这摇晃中缓缓展开。 ","date":"2024-02-22","objectID":"/posts/%E4%BB%8E%E5%A4%AA%E5%B9%B3%E5%B1%B1%E9%A1%B6%E5%88%B0%E8%BF%AA%E5%A3%AB%E5%B0%BC%E9%A6%99%E6%B8%AF%E7%9A%84%E6%98%BC%E5%A4%9C%E5%A5%87%E9%81%87/:6:0","tags":["旅游"],"title":"从太平山顶到迪士尼，香港的昼夜奇遇","uri":"/posts/%E4%BB%8E%E5%A4%AA%E5%B9%B3%E5%B1%B1%E9%A1%B6%E5%88%B0%E8%BF%AA%E5%A3%AB%E5%B0%BC%E9%A6%99%E6%B8%AF%E7%9A%84%E6%98%BC%E5%A4%9C%E5%A5%87%E9%81%87/#叮叮车"},{"categories":"旅游","collections":["超超的奇妙冒险"],"content":"第 7 节 维多利亚港\r从香港摩天轮旁的码头登上天星小轮，最先映入眼帘的是一艘通体纯白的游轮静静停靠在岸边，简洁的船身在阳光下显得格外干净利落。 随后视线被旁边一艘五彩斑斓的游轮吸引，船身绘满了明快的色彩，宛如海面上漂浮的一座流动乐园。 天色渐暗，不远处一艘发出柔和红光的游轮缓缓驶过，深邃夜色中那道红光格外醒目，为维多利亚港增添了几分浪漫气息。 乘船缓缓前行，两岸的壮丽景色尽收眼底——一边是香港岛的摩天大楼，一边是九龙半岛的璀璨灯火，繁华与宁静在此刻交织。 再晚些时候，万家灯火的璀璨倒映在水面上，随着海浪轻轻摇曳，波光粼粼的碎金让人沉醉其中、流连忘返。 渡轮缓缓靠岸，天星小轮载着满满一船乘客抵达对岸的九龙半岛，短短几分钟的航程却饱览了维多利亚港最迷人的夜景。 ","date":"2024-02-22","objectID":"/posts/%E4%BB%8E%E5%A4%AA%E5%B9%B3%E5%B1%B1%E9%A1%B6%E5%88%B0%E8%BF%AA%E5%A3%AB%E5%B0%BC%E9%A6%99%E6%B8%AF%E7%9A%84%E6%98%BC%E5%A4%9C%E5%A5%87%E9%81%87/:7:0","tags":["旅游"],"title":"从太平山顶到迪士尼，香港的昼夜奇遇","uri":"/posts/%E4%BB%8E%E5%A4%AA%E5%B9%B3%E5%B1%B1%E9%A1%B6%E5%88%B0%E8%BF%AA%E5%A3%AB%E5%B0%BC%E9%A6%99%E6%B8%AF%E7%9A%84%E6%98%BC%E5%A4%9C%E5%A5%87%E9%81%87/#维多利亚港"},{"categories":"旅游","collections":["超超的奇妙冒险"],"content":"第 8 节 尖沙咀/佐敦/油麻地/旺角\r一路走过尖沙咀往旺角方向，沿街商铺林立，大商场与小商贩交织，商业气息扑面而来。有趣的是不少店家虽支持微信和支付宝，却按1:1收取人民币，好在提前准备了港币，老老实实付港币才不吃这个暗亏。 继续往里走，偶遇一个地下停车场入口，满墙的灯光装饰错落有致，透着高级感，让人忍不住多看几眼。 再往前，一栋墨绿色的建筑格外显眼，通体深绿、顶部点缀着星星点点的灯光，远远望去酷似一棵巨大的圣诞树矗立在街角。 登上高处俯瞰，密密麻麻的高楼大厦拔地而起，楼间距极窄，楼身直插云霄不见顶端，香港的逼仄与繁华在此刻尽收眼底。 ","date":"2024-02-22","objectID":"/posts/%E4%BB%8E%E5%A4%AA%E5%B9%B3%E5%B1%B1%E9%A1%B6%E5%88%B0%E8%BF%AA%E5%A3%AB%E5%B0%BC%E9%A6%99%E6%B8%AF%E7%9A%84%E6%98%BC%E5%A4%9C%E5%A5%87%E9%81%87/:8:0","tags":["旅游"],"title":"从太平山顶到迪士尼，香港的昼夜奇遇","uri":"/posts/%E4%BB%8E%E5%A4%AA%E5%B9%B3%E5%B1%B1%E9%A1%B6%E5%88%B0%E8%BF%AA%E5%A3%AB%E5%B0%BC%E9%A6%99%E6%B8%AF%E7%9A%84%E6%98%BC%E5%A4%9C%E5%A5%87%E9%81%87/#尖沙咀佐敦油麻地旺角"},{"categories":"旅游","collections":["超超的奇妙冒险"],"content":"第 9 节 迪士尼\r一走进香港迪士尼，首先映入眼帘的便是标志性的鲸鱼喷泉雕塑，米老鼠正站在冲浪板上随水花起舞，瞬间让人感受到这座乐园独有的活力与趣味。 继续向前，很快就来到了充满漫威元素的区域，一面巨大的美国队长盾牌矗立在眼前，金属质感与经典星纹的还原度极高，瞬间点燃了心中的英雄情结。 紧接着，一尊等比例的钢铁侠战甲陈列在展台上，每一处装甲细节都精雕细琢，仿佛下一秒马克战甲就会亮起反应堆的光芒。 转身离开漫威世界，不远处便是小小世界的标志性建筑，外立面由五彩斑斓的几何图形、圆形时钟面孔和拼贴板构成，宛如一座童话里的彩色城堡。 随后步入魔雪奇缘世界，眼前的阿伦黛尔小镇完美复刻了北欧风情——坡屋顶木屋错落有致，湖畔停泊着古朴的帆船，远处阿伦黛尔城堡与白雪覆盖的北山交相辉映。 再往前，雪岭滑雪橇的过山车轨道映入眼帘，灰色的石砌灯塔静静伫立，穿梭于岩石与雪山之间的列车疾驰而过，下方是碧波荡漾的湖水，画面既刺激又唯美。 接着来到梦幻世界，小熊维尼历险记的场景中，袋鼠妈妈正用一条红色布条拉着小豆，防止顽皮的小家伙被风吹走，画面充满了温馨的童趣。 不远处，小熊维尼自己则牵着气球缓缓飘向天空，只留下一个圆滚滚的背影，俏皮又可爱。 最后走进乐园商店，琳琅满目的达菲、雪莉玫和玲娜贝儿毛绒玩偶整齐陈列在货架上，每个角色手中都牵着印有自己头像的彩色气球，让人忍不住想把这份美好带回家。 ","date":"2024-02-22","objectID":"/posts/%E4%BB%8E%E5%A4%AA%E5%B9%B3%E5%B1%B1%E9%A1%B6%E5%88%B0%E8%BF%AA%E5%A3%AB%E5%B0%BC%E9%A6%99%E6%B8%AF%E7%9A%84%E6%98%BC%E5%A4%9C%E5%A5%87%E9%81%87/:9:0","tags":["旅游"],"title":"从太平山顶到迪士尼，香港的昼夜奇遇","uri":"/posts/%E4%BB%8E%E5%A4%AA%E5%B9%B3%E5%B1%B1%E9%A1%B6%E5%88%B0%E8%BF%AA%E5%A3%AB%E5%B0%BC%E9%A6%99%E6%B8%AF%E7%9A%84%E6%98%BC%E5%A4%9C%E5%A5%87%E9%81%87/#迪士尼"},{"categories":"旅游","collections":["超超的奇妙冒险"],"content":"第 1 节 人民公园\r久闻成都宜游，特意做了功课将住宿选在春熙路，多条地铁交汇出行十分便利，抵达时已是夜晚，灯火璀璨、人潮涌动，果然是个散步的好去处。 漫步其间，处处张灯结彩，整条街弥漫着热闹欢快的气息，夜风拂面，让人瞬间融入了这座城市的松弛节奏中。 再往前走，三只用布料制成的熊猫映入眼帘，它们体内透出暖光，画着川剧脸谱，摆出表演戏曲的姿势，憨态可掬。 灯光映照下，熊猫脸谱的细节格外生动，传统戏曲元素与潮流街区在此奇妙碰撞，为成都的夜增添了一分野趣与可爱。 ","date":"2023-08-09","objectID":"/posts/%E5%9C%A8%E6%88%90%E9%83%BD%E8%A1%97%E5%A4%B4%E9%81%87%E8%A7%81%E7%86%8A%E7%8C%AB%E4%B8%8E%E7%83%9F%E7%81%AB%E6%B0%94/:1:0","tags":["旅游"],"title":"在成都街头，遇见熊猫与烟火气","uri":"/posts/%E5%9C%A8%E6%88%90%E9%83%BD%E8%A1%97%E5%A4%B4%E9%81%87%E8%A7%81%E7%86%8A%E7%8C%AB%E4%B8%8E%E7%83%9F%E7%81%AB%E6%B0%94/#人民公园"},{"categories":"旅游","collections":["超超的奇妙冒险"],"content":"第 2 节 锦里\r走进成都锦里古街，首先映入眼帘的是高悬的牌匾，鎏金字迹在日光下熠熠生辉，透着一股古朴雅致的韵味。 抬头望去，横梁上悬挂着成排的红灯笼，与古色古香的建筑相映成趣，仿佛瞬间穿越回了热闹的古代集市。 沿着小吃街继续往里走，头顶密密匝匝的红灯笼将整条街映照得格外喜庆，空气中飘散着各种美食的诱人香气。 再往前几步，惊喜地发现了刘、关、张三人的Q版雕塑，他们正笑盈盈地向来往游人挥手致意，憨态可掬的模样十分讨喜。 ","date":"2023-08-09","objectID":"/posts/%E5%9C%A8%E6%88%90%E9%83%BD%E8%A1%97%E5%A4%B4%E9%81%87%E8%A7%81%E7%86%8A%E7%8C%AB%E4%B8%8E%E7%83%9F%E7%81%AB%E6%B0%94/:2:0","tags":["旅游"],"title":"在成都街头，遇见熊猫与烟火气","uri":"/posts/%E5%9C%A8%E6%88%90%E9%83%BD%E8%A1%97%E5%A4%B4%E9%81%87%E8%A7%81%E7%86%8A%E7%8C%AB%E4%B8%8E%E7%83%9F%E7%81%AB%E6%B0%94/#锦里"},{"categories":"旅游","collections":["超超的奇妙冒险"],"content":"第 3 节 春熙路/太古里\r来到成都最繁华的春熙路，两侧店铺林立、人来人往，热闹非凡的商业气息扑面而来，让人瞬间感受到这座城市的活力。 抬头望去，一座造型独特的大楼格外引人注目——IFS国际金融中心顶部三面无墙，只剩一面矗立，极具设计感。 更引人注目的是，一只巨大的熊猫正趴在楼顶边缘，憨态可掬的模样成了这里最标志性的打卡景观。 逛累了便买了一杯竹筒装的网红冰淇淋，上面印着大大的「成都」二字，拿在手里摆拍一张，也算入乡随俗了。 ","date":"2023-08-09","objectID":"/posts/%E5%9C%A8%E6%88%90%E9%83%BD%E8%A1%97%E5%A4%B4%E9%81%87%E8%A7%81%E7%86%8A%E7%8C%AB%E4%B8%8E%E7%83%9F%E7%81%AB%E6%B0%94/:3:0","tags":["旅游"],"title":"在成都街头，遇见熊猫与烟火气","uri":"/posts/%E5%9C%A8%E6%88%90%E9%83%BD%E8%A1%97%E5%A4%B4%E9%81%87%E8%A7%81%E7%86%8A%E7%8C%AB%E4%B8%8E%E7%83%9F%E7%81%AB%E6%B0%94/#春熙路太古里"},{"categories":"旅游","collections":["超超的奇妙冒险"],"content":"第 4 节 九眼桥\r来到成都的九眼桥，虽说是重建之作，桥身并未保留真正的九个桥眼，但夜幕降临后，桥洞底部透出的灯光与河面倒影交相辉映，竟真的在水面上幻化出一只只灵动的眼睛，九眼桥之名因此流传至今。 视线缓缓移开水面，河对岸的房屋顶上一排排灯带勾勒出金碧辉煌的轮廓，挑檐飞角在暖黄灯光的映照下格外精致动人，整片建筑宛如镶了金边的古画铺展开来。 再往上看，一座六角星形状的阁楼矗立于屋顶之上，由四根柱子稳稳托举，顶部向外延伸高高挑起，充满张力与气势，在夜色中成为最引人注目的天际线。 最后登上高处俯瞰整座城市的夜景，万家灯火连成一片璀璨星河，成都的繁华与活力尽收眼底，这座不夜城的魅力在此刻展现得淋漓尽致。 ","date":"2023-08-09","objectID":"/posts/%E5%9C%A8%E6%88%90%E9%83%BD%E8%A1%97%E5%A4%B4%E9%81%87%E8%A7%81%E7%86%8A%E7%8C%AB%E4%B8%8E%E7%83%9F%E7%81%AB%E6%B0%94/:4:0","tags":["旅游"],"title":"在成都街头，遇见熊猫与烟火气","uri":"/posts/%E5%9C%A8%E6%88%90%E9%83%BD%E8%A1%97%E5%A4%B4%E9%81%87%E8%A7%81%E7%86%8A%E7%8C%AB%E4%B8%8E%E7%83%9F%E7%81%AB%E6%B0%94/#九眼桥"},{"categories":"旅游","collections":["超超的奇妙冒险"],"content":"第 5 节 文殊坊\r还未踏入文殊坊，抬头便望见那座青绿挑檐、鎏金字体的牌匾，古朴庄重的气息扑面而来，让人瞬间沉静下来。 走入寺内才发现，这座寺庙并不大，且不收门票，每位来客还能免费领取一支清香；香火缭绕间，整座寺庙的全景尽收眼底，烟雾与光影交织，更添几分禅意。 继续往里走，庭院一角静静卧着一尊石雕乌龟，龟甲纹理清晰可辨，在当地人心中，它象征着长寿与福泽，不少香客路过都会轻轻抚摸。 再往前几步，一尊石雕大象映入眼帘，象身温润厚重，与一旁的「般若」二字相映成趣，仿佛在无声地诉说着佛家智慧与力量的圆满结合。 ","date":"2023-08-09","objectID":"/posts/%E5%9C%A8%E6%88%90%E9%83%BD%E8%A1%97%E5%A4%B4%E9%81%87%E8%A7%81%E7%86%8A%E7%8C%AB%E4%B8%8E%E7%83%9F%E7%81%AB%E6%B0%94/:5:0","tags":["旅游"],"title":"在成都街头，遇见熊猫与烟火气","uri":"/posts/%E5%9C%A8%E6%88%90%E9%83%BD%E8%A1%97%E5%A4%B4%E9%81%87%E8%A7%81%E7%86%8A%E7%8C%AB%E4%B8%8E%E7%83%9F%E7%81%AB%E6%B0%94/#文殊坊"},{"categories":"旅游","collections":["超超的奇妙冒险"],"content":"第 6 节 大熊猫基地\r走进成都大熊猫繁育研究基地，站在高处俯瞰整个园区，满目葱茏之中各式场馆错落有致，蜿蜒的道路串联起这片国宝栖息之地，规模之大令人惊叹。 视线拉近一些，可以更清楚地看见园区内分布着许多各具特色的建筑，绿树掩映间的道路四通八达，仿佛一张精心编织的游览网络。 沿着其中一条主路前行，一条笔直的道路径直通向一座红顶建筑，那是今天探访的第一站，心中不禁充满期待。 步入室内场馆，一只大熊猫正背对着游客悠然自得地坐着，憨态可掬的背影让人会心一笑。 再往里走，另一只大熊猫正趴在地上专注地舔着地面，那副认真又呆萌的模样引得周围游客纷纷举起相机。 不远处，一只大熊猫四仰八叉地倒头就睡，全然不顾周围的热闹喧嚣，睡姿格外洒脱随性。 继续前行，只见一只大熊猫背对着我们，正津津有味地咀嚼着鲜嫩的竹叶，吃得十分投入。 天气实在太过炎热，一只大熊猫正正对着面前的大冰块舔舐降温，用最质朴的方式抵御夏天的暑气。 在另一处场馆，两只大熊猫躲进了树木搭建的木质小窝中，悬在空中相依而眠，画面温馨又治愈。 来到幼崽馆，刚出生的熊猫幼崽小小一团，粉嫩的模样让人忍不住心生怜爱，这是生命最初的奇迹。 走出室内来到户外广场，一座熊猫母子相依偎的雕像映入眼帘，熊猫妈妈温柔地护着身前的宝宝，亲情融融。 旁边还有两只站立的Q版熊猫雕像，手捧礼盒做出指路的手势，俏皮可爱的造型为园区增添了不少童趣。 游览结束前，买了一杯有着精美熊猫图案的文旅饮品，杯身上的熊猫是纯手工绘制的，细节栩栩如生，为这趟旅程画上了圆满的句号。 ","date":"2023-08-09","objectID":"/posts/%E5%9C%A8%E6%88%90%E9%83%BD%E8%A1%97%E5%A4%B4%E9%81%87%E8%A7%81%E7%86%8A%E7%8C%AB%E4%B8%8E%E7%83%9F%E7%81%AB%E6%B0%94/:6:0","tags":["旅游"],"title":"在成都街头，遇见熊猫与烟火气","uri":"/posts/%E5%9C%A8%E6%88%90%E9%83%BD%E8%A1%97%E5%A4%B4%E9%81%87%E8%A7%81%E7%86%8A%E7%8C%AB%E4%B8%8E%E7%83%9F%E7%81%AB%E6%B0%94/#大熊猫基地"},{"categories":"旅游","collections":["超超的奇妙冒险"],"content":"第 1 节 祖庙\r步入祖庙，首先映入眼帘的是热闹非凡的舞狮表演，一只黄色的狮子跨立而站，姿态威武，在烈日下依然精气神十足。 紧接着，一只红色的狮子活泼地走向人群，与观众亲密互动，逗得大人小孩笑声不断，现场气氛热烈。 再往前看，一只白色的狮子后脚立于木桩之上，前脚悬空，这一高难度动作将表演者的精湛技艺展现得淋漓尽致。 随后，两只黄色和红色的狮子一同向远方奔去，为这场精彩绝伦的表演画上了圆满的句号。 离开喧闹的广场继续往里走，我来到紫霄宫前，匾额上「紫霄宫」三个大字古朴庄重，这里供奉着佛山人亲切称为「大父母」的北帝。 步入殿内，一件精美的佛山金漆木雕映入眼帘，多层镂空雕刻玲珑剔透，几厘米高的小人五官、服饰和神态都清晰可辨，无愧于「东方艺术之宫」的美誉。 ","date":"2023-05-31","objectID":"/posts/%E7%8B%AE%E8%88%9E%E4%BD%9B%E5%B1%B1%E5%8F%A4%E5%BA%99%E9%9B%84%E9%A3%8E%E4%B8%8E%E5%8D%83%E7%81%AF%E5%A4%9C%E8%89%B2/:1:0","tags":["旅游"],"title":"狮舞佛山：古庙雄风与千灯夜色","uri":"/posts/%E7%8B%AE%E8%88%9E%E4%BD%9B%E5%B1%B1%E5%8F%A4%E5%BA%99%E9%9B%84%E9%A3%8E%E4%B8%8E%E5%8D%83%E7%81%AF%E5%A4%9C%E8%89%B2/#祖庙"},{"categories":"旅游","collections":["超超的奇妙冒险"],"content":"第 2 节 岭南天地\r踏入佛山祖庙旁的岭南天地，这座开在古庙旁边的购物中心别有一番风味，狭窄的弄堂两侧，左边是红色砖墙，右边是白色石块，新旧交融的氛围让人忍不住想深入探索。 继续往里走，抬头便见树上挂满了醒狮挂件，一块醒狮牌匾赫然在目，周围摆放着五个色彩鲜艳的舞狮头套，左右各一、下方三个，浓浓的岭南非遗气息扑面而来。 再往前走，狭窄的弄堂里有一块路牌从墙边突出来，黑色墙体斑驳剥落，岁月的痕迹清晰可见，仿佛在诉说着这座古城的故事。 离开弄堂转角处，一家古朴的粥店静静伫立，同样是黑色斑驳的墙面，散发着老佛山日常市井生活的烟火气，让人忍不住想进去喝上一碗暖心暖胃的粥。 ","date":"2023-05-31","objectID":"/posts/%E7%8B%AE%E8%88%9E%E4%BD%9B%E5%B1%B1%E5%8F%A4%E5%BA%99%E9%9B%84%E9%A3%8E%E4%B8%8E%E5%8D%83%E7%81%AF%E5%A4%9C%E8%89%B2/:2:0","tags":["旅游"],"title":"狮舞佛山：古庙雄风与千灯夜色","uri":"/posts/%E7%8B%AE%E8%88%9E%E4%BD%9B%E5%B1%B1%E5%8F%A4%E5%BA%99%E9%9B%84%E9%A3%8E%E4%B8%8E%E5%8D%83%E7%81%AF%E5%A4%9C%E8%89%B2/#岭南天地"},{"categories":"旅游","collections":["超超的奇妙冒险"],"content":"第 3 节 南风古灶\r走进佛山南风古灶，这处规模虽不算大却充满韵味的景点，处处洋溢着浓厚的陶瓷文化气息。展馆内详细讲述了本地陶瓷发展的悠久历史，而技艺精湛的师傅正现场演示陶器制作过程，让人不禁驻足观赏；四周陈列着琳琅满目的陶瓷纪念品，还能亲自动手体验制陶的乐趣，感受泥土在指尖流转的独特魅力。 ","date":"2023-05-31","objectID":"/posts/%E7%8B%AE%E8%88%9E%E4%BD%9B%E5%B1%B1%E5%8F%A4%E5%BA%99%E9%9B%84%E9%A3%8E%E4%B8%8E%E5%8D%83%E7%81%AF%E5%A4%9C%E8%89%B2/:3:0","tags":["旅游"],"title":"狮舞佛山：古庙雄风与千灯夜色","uri":"/posts/%E7%8B%AE%E8%88%9E%E4%BD%9B%E5%B1%B1%E5%8F%A4%E5%BA%99%E9%9B%84%E9%A3%8E%E4%B8%8E%E5%8D%83%E7%81%AF%E5%A4%9C%E8%89%B2/#南风古灶"},{"categories":"旅游","collections":["超超的奇妙冒险"],"content":"第 4 节 佛山创意产业园\r离开南风古灶后，我顺路拐进了佛山创意产业园，天气实在炎热，急需一杯冰爽的柠檬水来降温解暑，整个人才算缓了过来。 这里是个充满个性的网红打卡地，随处可见挂满各式标语的挂牌，每一条都张扬着独特的态度，随手一拍都很有意思。 继续往里走，发现这里还能摆人体造型拍照，我试了试自己的身体，发现实在没有什么辨识度，便灵机一动。 于是果断摆出了某个经典舞蹈造型，这个动作极具辨识度，瞬间成为了全场焦点，自己也忍不住笑出声来。 ","date":"2023-05-31","objectID":"/posts/%E7%8B%AE%E8%88%9E%E4%BD%9B%E5%B1%B1%E5%8F%A4%E5%BA%99%E9%9B%84%E9%A3%8E%E4%B8%8E%E5%8D%83%E7%81%AF%E5%A4%9C%E8%89%B2/:4:0","tags":["旅游"],"title":"狮舞佛山：古庙雄风与千灯夜色","uri":"/posts/%E7%8B%AE%E8%88%9E%E4%BD%9B%E5%B1%B1%E5%8F%A4%E5%BA%99%E9%9B%84%E9%A3%8E%E4%B8%8E%E5%8D%83%E7%81%AF%E5%A4%9C%E8%89%B2/#佛山创意产业园"},{"categories":"旅游","collections":["超超的奇妙冒险"],"content":"第 5 节 千灯湖\r傍晚时分，我守候在佛山千灯湖畔，费了好大的劲终于等到夜幕降临，刹那间湖面被万千灯盏点亮，水面倒映着璀璨灯光，仿佛真的有千盏明灯在水中闪烁。 再往湖畔深处走去，一座舞狮头形状的框架映入眼帘，灯条勾勒出雄狮的轮廓，在夜色中流光溢彩，仿佛有舞狮在夜幕下灵动出没，为这宁静的湖畔增添了浓郁的岭南风情。 ","date":"2023-05-31","objectID":"/posts/%E7%8B%AE%E8%88%9E%E4%BD%9B%E5%B1%B1%E5%8F%A4%E5%BA%99%E9%9B%84%E9%A3%8E%E4%B8%8E%E5%8D%83%E7%81%AF%E5%A4%9C%E8%89%B2/:5:0","tags":["旅游"],"title":"狮舞佛山：古庙雄风与千灯夜色","uri":"/posts/%E7%8B%AE%E8%88%9E%E4%BD%9B%E5%B1%B1%E5%8F%A4%E5%BA%99%E9%9B%84%E9%A3%8E%E4%B8%8E%E5%8D%83%E7%81%AF%E5%A4%9C%E8%89%B2/#千灯湖"},{"categories":"旅游","collections":["超超的奇妙冒险"],"content":"第 1 节 初见大良\r在广州游玩至夜幕降临后，我便搭车前往以美食闻名的顺德，这座佛系小城物价亲民、生活安逸，甫一下车便来到中心城区「大良」，初见那亮着温暖黄色光束的钟楼站路牌，在夜色中格外好看，仿佛在温柔地迎接每一位到访的旅人。 ","date":"2023-05-30","objectID":"/posts/%E9%A1%BA%E5%BE%B7%E5%AF%BB%E5%91%B3%E8%AE%B0%E5%A4%9C%E8%89%B2%E9%92%9F%E6%A5%BC%E4%B8%8E%E8%88%8C%E5%B0%96%E4%B8%8A%E7%9A%84%E5%B2%AD%E5%8D%97/:1:0","tags":["旅游"],"title":"顺德寻味记：夜色钟楼与舌尖上的岭南","uri":"/posts/%E9%A1%BA%E5%BE%B7%E5%AF%BB%E5%91%B3%E8%AE%B0%E5%A4%9C%E8%89%B2%E9%92%9F%E6%A5%BC%E4%B8%8E%E8%88%8C%E5%B0%96%E4%B8%8A%E7%9A%84%E5%B2%AD%E5%8D%97/#初见大良"},{"categories":"旅游","collections":["超超的奇妙冒险"],"content":"第 2 节 金凤凰广场\r入住酒店旁便是佛山顺德区的金凤凰广场，趁清晨前去散步，广场上几只海鸥形雕塑振翅欲飞，为这座城市的早晨增添了几分灵动的气息。 随后步入一旁的绿道，抬眼望去，一条空中廊道横跨上方，笔直延伸向天际，与周围的绿化景观相映成趣。 继续沿着廊道下方行走，两侧草木葱茏、绿意盎然，在闷热的天气里，置身廊道下倒也有几分惬意与清凉。 ","date":"2023-05-30","objectID":"/posts/%E9%A1%BA%E5%BE%B7%E5%AF%BB%E5%91%B3%E8%AE%B0%E5%A4%9C%E8%89%B2%E9%92%9F%E6%A5%BC%E4%B8%8E%E8%88%8C%E5%B0%96%E4%B8%8A%E7%9A%84%E5%B2%AD%E5%8D%97/:2:0","tags":["旅游"],"title":"顺德寻味记：夜色钟楼与舌尖上的岭南","uri":"/posts/%E9%A1%BA%E5%BE%B7%E5%AF%BB%E5%91%B3%E8%AE%B0%E5%A4%9C%E8%89%B2%E9%92%9F%E6%A5%BC%E4%B8%8E%E8%88%8C%E5%B0%96%E4%B8%8A%E7%9A%84%E5%B2%AD%E5%8D%97/#金凤凰广场"},{"categories":"旅游","collections":["超超的奇妙冒险"],"content":"第 3 节 清晖园\r踏入清晖园，这座被誉为岭南四大名园之一的园林果然名不虚传，迎面便见四只龙腾金属喷头四向而立，朝着中间柱状图腾的方向喷涌出水，气势颇为壮观。 随后移步至园内小池塘，满池荷花正值盛开时节，荷叶田田、花影摇曳，为这座古典园林平添了几分生机与诗意。 继续往里走，来到一座清幽的院子，院中央一棵蜿蜒的古树映入眼帘，错综复杂的枝条盘曲伸展，仿佛在诉说着百年的岁月沧桑。 再往前行至一片池塘边，水中成群锦鲤悠然游弋，池面上零星点缀着鹅卵石组成的小步道，踏石而行，别有一番新奇趣味。 ","date":"2023-05-30","objectID":"/posts/%E9%A1%BA%E5%BE%B7%E5%AF%BB%E5%91%B3%E8%AE%B0%E5%A4%9C%E8%89%B2%E9%92%9F%E6%A5%BC%E4%B8%8E%E8%88%8C%E5%B0%96%E4%B8%8A%E7%9A%84%E5%B2%AD%E5%8D%97/:3:0","tags":["旅游"],"title":"顺德寻味记：夜色钟楼与舌尖上的岭南","uri":"/posts/%E9%A1%BA%E5%BE%B7%E5%AF%BB%E5%91%B3%E8%AE%B0%E5%A4%9C%E8%89%B2%E9%92%9F%E6%A5%BC%E4%B8%8E%E8%88%8C%E5%B0%96%E4%B8%8A%E7%9A%84%E5%B2%AD%E5%8D%97/#清晖园"},{"categories":"旅游","collections":["超超的奇妙冒险"],"content":"第 4 节 华盖山步行街\r走进佛山顺德的华盖山步行街，这里是一条充满烟火气的美食街，两旁既有历史悠久的老字号，也有新晋的网红小吃，远远便望见一座蓝黄红三色相间的奇特建筑，配色大胆前卫，格外引人注目。 继续往前走，我来到慕名已久的「民信老铺」——这家店铺可是省级非物质文化遗产，点上一碗招牌双皮奶，再上到二楼参观双皮奶的制作工艺历史，不禁感叹传统美食背后的匠心传承。 品尝完双皮奶，我又被不远处的「啊爆柠檬茶」吸引了过去，这家网红品牌门口围满了年轻人，大伙儿都举着柠檬茶对着招牌拍照打卡。 我也不甘落后，学着网红们的姿势举起柠檬茶对着招牌定格下这一刻，清爽的柠檬香气配上这热闹的街景，为这段顺德美食之旅画上了圆满的句号。 ","date":"2023-05-30","objectID":"/posts/%E9%A1%BA%E5%BE%B7%E5%AF%BB%E5%91%B3%E8%AE%B0%E5%A4%9C%E8%89%B2%E9%92%9F%E6%A5%BC%E4%B8%8E%E8%88%8C%E5%B0%96%E4%B8%8A%E7%9A%84%E5%B2%AD%E5%8D%97/:4:0","tags":["旅游"],"title":"顺德寻味记：夜色钟楼与舌尖上的岭南","uri":"/posts/%E9%A1%BA%E5%BE%B7%E5%AF%BB%E5%91%B3%E8%AE%B0%E5%A4%9C%E8%89%B2%E9%92%9F%E6%A5%BC%E4%B8%8E%E8%88%8C%E5%B0%96%E4%B8%8A%E7%9A%84%E5%B2%AD%E5%8D%97/#华盖山步行街"},{"categories":"旅游","collections":["超超的奇妙冒险"],"content":"第 5 节 金榜\r从华盖山步行街延续而来，金榜的街巷依然热闹非凡，两侧小吃店铺林立，路面虽窄却充满了古镇老街的烟火气息。 继续往里走，便看到那座纯白高大的金榜牌坊矗立在眼前，气势十足，而它的一角更是别有意味——左侧是斑驳的老房子，右侧是崭新的街道，新旧交叠，时光在此刻有了清晰的轮廓。 再往前几步，我走进了「金榜欢记牛乳」，点了一杯招牌牛乳，奶香浓郁、口感顺滑，一口下去，满嘴都是属于顺德的地道味道。 ","date":"2023-05-30","objectID":"/posts/%E9%A1%BA%E5%BE%B7%E5%AF%BB%E5%91%B3%E8%AE%B0%E5%A4%9C%E8%89%B2%E9%92%9F%E6%A5%BC%E4%B8%8E%E8%88%8C%E5%B0%96%E4%B8%8A%E7%9A%84%E5%B2%AD%E5%8D%97/:5:0","tags":["旅游"],"title":"顺德寻味记：夜色钟楼与舌尖上的岭南","uri":"/posts/%E9%A1%BA%E5%BE%B7%E5%AF%BB%E5%91%B3%E8%AE%B0%E5%A4%9C%E8%89%B2%E9%92%9F%E6%A5%BC%E4%B8%8E%E8%88%8C%E5%B0%96%E4%B8%8A%E7%9A%84%E5%B2%AD%E5%8D%97/#金榜"},{"categories":"旅游","collections":["超超的奇妙冒险"],"content":"第 6 节 西山庙\r从「大良钟楼」旁边顺路拐进了西山庙，抬头望去，大门牌匾上赫然写着「西山庙」三个大字，虽说没什么特别之处，倒也清静古朴。 继续往里走，庙内供奉着关圣帝君，只是令人诧异的是，这里的关公像并非我们所熟悉的那副红脸模样，反而面容黝黑，倒是头一回见着这般独特的塑像。 ","date":"2023-05-30","objectID":"/posts/%E9%A1%BA%E5%BE%B7%E5%AF%BB%E5%91%B3%E8%AE%B0%E5%A4%9C%E8%89%B2%E9%92%9F%E6%A5%BC%E4%B8%8E%E8%88%8C%E5%B0%96%E4%B8%8A%E7%9A%84%E5%B2%AD%E5%8D%97/:6:0","tags":["旅游"],"title":"顺德寻味记：夜色钟楼与舌尖上的岭南","uri":"/posts/%E9%A1%BA%E5%BE%B7%E5%AF%BB%E5%91%B3%E8%AE%B0%E5%A4%9C%E8%89%B2%E9%92%9F%E6%A5%BC%E4%B8%8E%E8%88%8C%E5%B0%96%E4%B8%8A%E7%9A%84%E5%B2%AD%E5%8D%97/#西山庙"},{"categories":"旅游","collections":["超超的奇妙冒险"],"content":"第 7 节 凤岭公园\r沿着前往大良钟楼的方向，我顺路来到了凤岭公园，这座不算太高的小山丘倒是别有洞天，拾级而上，绿荫掩映间已然能感受到俯瞰顺德的绝佳视野。 继续往上攀登，一座三层六方、红栏环绕的独特高塔映入眼帘，登顶远眺，脚下密密麻麻的房屋尽收眼底，整个顺德的风光一览无余。 ","date":"2023-05-30","objectID":"/posts/%E9%A1%BA%E5%BE%B7%E5%AF%BB%E5%91%B3%E8%AE%B0%E5%A4%9C%E8%89%B2%E9%92%9F%E6%A5%BC%E4%B8%8E%E8%88%8C%E5%B0%96%E4%B8%8A%E7%9A%84%E5%B2%AD%E5%8D%97/:7:0","tags":["旅游"],"title":"顺德寻味记：夜色钟楼与舌尖上的岭南","uri":"/posts/%E9%A1%BA%E5%BE%B7%E5%AF%BB%E5%91%B3%E8%AE%B0%E5%A4%9C%E8%89%B2%E9%92%9F%E6%A5%BC%E4%B8%8E%E8%88%8C%E5%B0%96%E4%B8%8A%E7%9A%84%E5%B2%AD%E5%8D%97/#凤岭公园"},{"categories":"旅游","collections":["超超的奇妙冒险"],"content":"第 8 节 钟楼\r来到佛山顺德区的钟楼，这座建筑并没有想象中那么宏大，反而透着一股亲切的生活气息，楼下聚集了不少老年人在悠闲地打着牌，欢声笑语间尽显岭南老街的烟火日常。 顺着楼梯登上钟楼，楼上悬挂着一口圆钟，古朴的铜面上镌刻着岁月的痕迹，仿佛轻轻一敲便能唤醒这座小城沉睡的记忆。 ","date":"2023-05-30","objectID":"/posts/%E9%A1%BA%E5%BE%B7%E5%AF%BB%E5%91%B3%E8%AE%B0%E5%A4%9C%E8%89%B2%E9%92%9F%E6%A5%BC%E4%B8%8E%E8%88%8C%E5%B0%96%E4%B8%8A%E7%9A%84%E5%B2%AD%E5%8D%97/:8:0","tags":["旅游"],"title":"顺德寻味记：夜色钟楼与舌尖上的岭南","uri":"/posts/%E9%A1%BA%E5%BE%B7%E5%AF%BB%E5%91%B3%E8%AE%B0%E5%A4%9C%E8%89%B2%E9%92%9F%E6%A5%BC%E4%B8%8E%E8%88%8C%E5%B0%96%E4%B8%8A%E7%9A%84%E5%B2%AD%E5%8D%97/#钟楼"},{"categories":"旅游","collections":["超超的奇妙冒险"],"content":"第 9 节 欢乐海岸\r来到佛山顺德区的欢乐海岸，这是一座比较新的大型游乐场，整体氛围感十足，尤其是夜幕降临后，华灯初上，整个园区瞬间变得璀璨迷人。 抬眼望去，远处发光的高塔与摩天轮交相辉映，走近一看，摩天轮的巨大身形更是壮观，光影流转间，浪漫气息扑面而来。 继续往里走，发现了一家酷劲十足的酒吧，里头陈列着许多摩托车可供参观，现代工业风与娱乐氛围巧妙融合，别具一格。 从酒吧出来，碰巧遇到一位老哥在街头唱歌，我便坐下来听了一会儿，还跟着一块唱了起来，老哥热情地邀请我上台合唱，这份意外的快乐令人难忘。 ","date":"2023-05-30","objectID":"/posts/%E9%A1%BA%E5%BE%B7%E5%AF%BB%E5%91%B3%E8%AE%B0%E5%A4%9C%E8%89%B2%E9%92%9F%E6%A5%BC%E4%B8%8E%E8%88%8C%E5%B0%96%E4%B8%8A%E7%9A%84%E5%B2%AD%E5%8D%97/:9:0","tags":["旅游"],"title":"顺德寻味记：夜色钟楼与舌尖上的岭南","uri":"/posts/%E9%A1%BA%E5%BE%B7%E5%AF%BB%E5%91%B3%E8%AE%B0%E5%A4%9C%E8%89%B2%E9%92%9F%E6%A5%BC%E4%B8%8E%E8%88%8C%E5%B0%96%E4%B8%8A%E7%9A%84%E5%B2%AD%E5%8D%97/#欢乐海岸"},{"categories":"旅游","collections":["超超的奇妙冒险"],"content":"第 10 节 顺峰山公园\r步入顺峰山公园，眼前豁然开朗，一座高大的牌坊巍然矗立，匾额上书「顺峰揽腾」四个大字，气势恢宏，令人顿生豪迈之情。 穿过牌坊，一潭清澈的碧波映入眼帘，湖面极为宽广，仿佛与天际相接，水天一色，令人心旷神怡。 继续沿湖畔前行，远处一座六方挑檐的寺庙高塔拔地而起，直插云霄，在蓝天白云的映衬下更显庄严肃穆。 再走近些，湖中一片荷叶亭亭玉立，碧绿的莲叶间点缀着含苞待放的荷花，与远处的高塔相映成趣，构成了一幅动人的岭南画卷。 ","date":"2023-05-30","objectID":"/posts/%E9%A1%BA%E5%BE%B7%E5%AF%BB%E5%91%B3%E8%AE%B0%E5%A4%9C%E8%89%B2%E9%92%9F%E6%A5%BC%E4%B8%8E%E8%88%8C%E5%B0%96%E4%B8%8A%E7%9A%84%E5%B2%AD%E5%8D%97/:10:0","tags":["旅游"],"title":"顺德寻味记：夜色钟楼与舌尖上的岭南","uri":"/posts/%E9%A1%BA%E5%BE%B7%E5%AF%BB%E5%91%B3%E8%AE%B0%E5%A4%9C%E8%89%B2%E9%92%9F%E6%A5%BC%E4%B8%8E%E8%88%8C%E5%B0%96%E4%B8%8A%E7%9A%84%E5%B2%AD%E5%8D%97/#顺峰山公园"},{"categories":"旅游","collections":["超超的奇妙冒险"],"content":"第 11 节 顺德博物馆\r走进顺德博物馆，原本只是顺道一游，没想到展厅之多出乎意料，逐一逛下来倒也花了不少时间。首先映入眼帘的是一件青花五彩瓷器，釉色温润、纹饰繁复，在灯光下泛着幽幽的光泽，仿佛在静静诉说着百年前的匠人匠心。 再往前是一面巨大的戏曲脸谱墙，各式脸谱色彩斑斓、神态各异，从红脸的忠勇到白脸的奸诈，一应俱全，看得人目不暇接。 接着来到李小龙纪念馆，馆内陈列着他的照片、书籍和雕塑等物件，这位从顺德走向世界的功夫巨星，仿佛透过这些展品依然在讲述着他的传奇故事。 继续往里走，一组组栩栩如生的雕塑映入眼帘，雕刻着当地居民的生活百态——渔民晒网、船工劳作、市井买卖，沿海人家的日常被凝固在石雕之中，充满了浓郁的生活气息。 ","date":"2023-05-30","objectID":"/posts/%E9%A1%BA%E5%BE%B7%E5%AF%BB%E5%91%B3%E8%AE%B0%E5%A4%9C%E8%89%B2%E9%92%9F%E6%A5%BC%E4%B8%8E%E8%88%8C%E5%B0%96%E4%B8%8A%E7%9A%84%E5%B2%AD%E5%8D%97/:11:0","tags":["旅游"],"title":"顺德寻味记：夜色钟楼与舌尖上的岭南","uri":"/posts/%E9%A1%BA%E5%BE%B7%E5%AF%BB%E5%91%B3%E8%AE%B0%E5%A4%9C%E8%89%B2%E9%92%9F%E6%A5%BC%E4%B8%8E%E8%88%8C%E5%B0%96%E4%B8%8A%E7%9A%84%E5%B2%AD%E5%8D%97/#顺德博物馆"},{"categories":"旅游","collections":["超超的奇妙冒险"],"content":"第 12 节 柴油机 1959\r穿过大良河来到对岸的容桂，眼前景象顿时变得破旧了不少，看起来这里的发展步伐确实慢了许多。远远便见一块写着＂柴油机 1959＂的牌匾立在门口，这个由旧柴油机厂改造而成的网红景点，虽然创意可嘉，却也透露出容桂在寻找文旅出路时的几分无奈。 继续往里走，一条经过改造的购物街出现在眼前，新潮的店铺与斑驳的老厂房并置一隅，试图以混搭风格吸引游客驻足，但稀稀拉拉的人流还是暴露了这个景点＂网红＂外表下的些许冷清。 ","date":"2023-05-30","objectID":"/posts/%E9%A1%BA%E5%BE%B7%E5%AF%BB%E5%91%B3%E8%AE%B0%E5%A4%9C%E8%89%B2%E9%92%9F%E6%A5%BC%E4%B8%8E%E8%88%8C%E5%B0%96%E4%B8%8A%E7%9A%84%E5%B2%AD%E5%8D%97/:12:0","tags":["旅游"],"title":"顺德寻味记：夜色钟楼与舌尖上的岭南","uri":"/posts/%E9%A1%BA%E5%BE%B7%E5%AF%BB%E5%91%B3%E8%AE%B0%E5%A4%9C%E8%89%B2%E9%92%9F%E6%A5%BC%E4%B8%8E%E8%88%8C%E5%B0%96%E4%B8%8A%E7%9A%84%E5%B2%AD%E5%8D%97/#柴油机-1959"},{"categories":"旅游","collections":["超超的奇妙冒险"],"content":"第 13 节 渔人码头\r来到佛山顺德容桂的渔人码头，这个颇有名气的网红景点虽不算大，但处处都是风景，一进门就看到那排发光的台阶，在夜色中格外引人注目。 随后目光被不远处的灯塔吸引，塔身缠绕着星星点点的灯珠，晶莹闪烁，远远望去就像一棵被灯光点缀的圣诞树，充满了浪漫气息。 再往前走到码头边，一艘白色游轮静静地停靠在水面，它的线条尖锐利落，透着一股英气，与周围的柔美灯光形成了别样的对比。 ","date":"2023-05-30","objectID":"/posts/%E9%A1%BA%E5%BE%B7%E5%AF%BB%E5%91%B3%E8%AE%B0%E5%A4%9C%E8%89%B2%E9%92%9F%E6%A5%BC%E4%B8%8E%E8%88%8C%E5%B0%96%E4%B8%8A%E7%9A%84%E5%B2%AD%E5%8D%97/:13:0","tags":["旅游"],"title":"顺德寻味记：夜色钟楼与舌尖上的岭南","uri":"/posts/%E9%A1%BA%E5%BE%B7%E5%AF%BB%E5%91%B3%E8%AE%B0%E5%A4%9C%E8%89%B2%E9%92%9F%E6%A5%BC%E4%B8%8E%E8%88%8C%E5%B0%96%E4%B8%8A%E7%9A%84%E5%B2%AD%E5%8D%97/#渔人码头"},{"categories":"旅游","collections":["超超的奇妙冒险"],"content":"第 14 节 美食\r在顺德寻味的第一站，多亏了聊天群结识的「顺德首富」哥带路，才找到这家连招牌都不太显眼的小店，价格却着实让人惊喜——连吃带打包了六个大鸡腿，总共才花了100块，吃得那叫一个心满意足。 随后慕名来到本地人常去的「龙的酒楼」喝早茶，大堂里座无虚席，点上一壶热茶，配几样精致点心，看着周围的街坊邻里悠闲地聊着天，才真正体会到顺德人那一份从容的生活味道。 接着转战「红星广发煲仔饭」，掀开砂锅盖的瞬间热气扑面而来，锅巴烤得焦黄酥脆，用勺子轻轻一敲便发出清脆的声响，配合铺得满满当当的黄鳝一起吃，格外下饭。 细看这煲黄鳝饭，黄鳝切得厚实饱满、油光锃亮，底下的锅巴被火候拿捏得刚刚好，呈现出诱人的焦黄色泽，光是看着就让人食欲大开，这一趟顺德美食之旅真是处处有惊喜。 ","date":"2023-05-30","objectID":"/posts/%E9%A1%BA%E5%BE%B7%E5%AF%BB%E5%91%B3%E8%AE%B0%E5%A4%9C%E8%89%B2%E9%92%9F%E6%A5%BC%E4%B8%8E%E8%88%8C%E5%B0%96%E4%B8%8A%E7%9A%84%E5%B2%AD%E5%8D%97/:14:0","tags":["旅游"],"title":"顺德寻味记：夜色钟楼与舌尖上的岭南","uri":"/posts/%E9%A1%BA%E5%BE%B7%E5%AF%BB%E5%91%B3%E8%AE%B0%E5%A4%9C%E8%89%B2%E9%92%9F%E6%A5%BC%E4%B8%8E%E8%88%8C%E5%B0%96%E4%B8%8A%E7%9A%84%E5%B2%AD%E5%8D%97/#美食"},{"categories":"旅游","collections":["超超的奇妙冒险"],"content":"第 1 节 广州塔\r刚踏入广州塔脚下，偶遇一位小哥将手机贴在地上仰拍高耸入云的塔身，我这个摄影新手也有样学样，蹲下身子学着他的角度拍了一张，画面中广州塔直插天际，气势非凡。 随后乘电梯登顶广州塔，俯瞰整座城市，宽阔的珠江河道蜿蜒伸向天际，一座大桥横跨两岸，两侧高楼林立，尽显大都市的壮阔。 换个方向望去，视野中竟出现一片绿洲，又有一座大桥横跨其上，与刚才的繁华景象形成鲜明对比，让人感受到广州的多元面貌。 再抬眼远眺，广州的天空雾蒙蒙的，与江水的颜色浑然一体，水天一色之间透着一种朦胧而静谧的美感，令人久久不愿离去。 ","date":"2023-05-29","objectID":"/posts/%E5%B9%BF%E5%B7%9E%E7%9A%84%E6%98%BC%E4%B8%8E%E5%A4%9C%E5%A1%94%E5%BD%B1%E6%B1%9F%E7%81%AF%E9%97%B4%E7%9A%84%E7%99%BE%E5%B9%B4%E9%A3%8E%E4%BA%91/:1:0","tags":["旅游"],"title":"广州的昼与夜：塔影江灯间的百年风云","uri":"/posts/%E5%B9%BF%E5%B7%9E%E7%9A%84%E6%98%BC%E4%B8%8E%E5%A4%9C%E5%A1%94%E5%BD%B1%E6%B1%9F%E7%81%AF%E9%97%B4%E7%9A%84%E7%99%BE%E5%B9%B4%E9%A3%8E%E4%BA%91/#广州塔"},{"categories":"旅游","collections":["超超的奇妙冒险"],"content":"第 2 节 沙面\r走进广州沙面，这片曾经的英法租界如今已蜕变为热门的网红打卡地，首先映入眼帘的是一座红色墙体的建筑，线条硬朗、色彩鲜明，透着浓郁的西方建筑风情，仿佛瞬间将人拉回到百年前的租界时光。 接着往前走，一座黄色墙体的西式小洋楼静静矗立，温暖的色调与精致的窗棂相得益彰，透着一股闲适浪漫的异国情调。 继续漫步，灰白色的墙面已有些许褪色，斑驳的痕迹无声诉说着岁月的流转，历史的厚重感在这朴素的外表下悄然沉淀。 俯身望去，路面在两侧楼宇的夹拥下延伸出一条笔直的车道，正巧一辆车缓缓驶过，动静之间勾勒出一幅充满意境的画面，让人久久驻足。 ","date":"2023-05-29","objectID":"/posts/%E5%B9%BF%E5%B7%9E%E7%9A%84%E6%98%BC%E4%B8%8E%E5%A4%9C%E5%A1%94%E5%BD%B1%E6%B1%9F%E7%81%AF%E9%97%B4%E7%9A%84%E7%99%BE%E5%B9%B4%E9%A3%8E%E4%BA%91/:2:0","tags":["旅游"],"title":"广州的昼与夜：塔影江灯间的百年风云","uri":"/posts/%E5%B9%BF%E5%B7%9E%E7%9A%84%E6%98%BC%E4%B8%8E%E5%A4%9C%E5%A1%94%E5%BD%B1%E6%B1%9F%E7%81%AF%E9%97%B4%E7%9A%84%E7%99%BE%E5%B9%B4%E9%A3%8E%E4%BA%91/#沙面"},{"categories":"旅游","collections":["超超的奇妙冒险"],"content":"第 3 节 林则徐纪念园\r趁着等待「珠江夜游」发船的空档，我顺便走进了林则徐纪念园，一入园便望见一尊硕大的林则徐雕像巍然矗立，神情坚毅，令人肃然起敬。 接着往里走，一幅虎门销烟的壁画映入眼帘，画面描绘得栩栩如生，仿佛将那段波澜壮阔的历史重新呈现在眼前。 继续向园区深处走去，几尊威风凛凛的大炮整齐摆放，炮口指向远方，为这座纪念园增添了几分庄严肃穆的历史厚重感。 ","date":"2023-05-29","objectID":"/posts/%E5%B9%BF%E5%B7%9E%E7%9A%84%E6%98%BC%E4%B8%8E%E5%A4%9C%E5%A1%94%E5%BD%B1%E6%B1%9F%E7%81%AF%E9%97%B4%E7%9A%84%E7%99%BE%E5%B9%B4%E9%A3%8E%E4%BA%91/:3:0","tags":["旅游"],"title":"广州的昼与夜：塔影江灯间的百年风云","uri":"/posts/%E5%B9%BF%E5%B7%9E%E7%9A%84%E6%98%BC%E4%B8%8E%E5%A4%9C%E5%A1%94%E5%BD%B1%E6%B1%9F%E7%81%AF%E9%97%B4%E7%9A%84%E7%99%BE%E5%B9%B4%E9%A3%8E%E4%BA%91/#林则徐纪念园"},{"categories":"旅游","collections":["超超的奇妙冒险"],"content":"第 4 节 珠江夜游\r登上珠江夜游的游船，船缓缓驶离码头，江风拂面，两岸灯火渐次亮起，广州的夜晚就在这片波光中拉开了序幕。 船行至江心，近距离仰望广州塔，整座塔高耸入云，在夜色中闪烁着璀璨光芒，与天边那一轮明月交相辉映，画面美得令人屏息。 继续前行，沿岸的CBD商圈灯火辉煌、鳞次栉比，绚丽的灯光倒映在江面上，恍惚间真有几分外滩的韵味。 再往前望去，整片商圈的光影在水面铺展开来，江水被灯光染成斑斓的色彩，波光粼粼间，广州的繁华与浪漫尽收眼底。 ","date":"2023-05-29","objectID":"/posts/%E5%B9%BF%E5%B7%9E%E7%9A%84%E6%98%BC%E4%B8%8E%E5%A4%9C%E5%A1%94%E5%BD%B1%E6%B1%9F%E7%81%AF%E9%97%B4%E7%9A%84%E7%99%BE%E5%B9%B4%E9%A3%8E%E4%BA%91/:4:0","tags":["旅游"],"title":"广州的昼与夜：塔影江灯间的百年风云","uri":"/posts/%E5%B9%BF%E5%B7%9E%E7%9A%84%E6%98%BC%E4%B8%8E%E5%A4%9C%E5%A1%94%E5%BD%B1%E6%B1%9F%E7%81%AF%E9%97%B4%E7%9A%84%E7%99%BE%E5%B9%B4%E9%A3%8E%E4%BA%91/#珠江夜游"},{"categories":"旅游","collections":["超超的奇妙冒险"],"content":"第 5 节 黄埔军校\r临别前抽出半天时间前往黄埔军校参观，刚到校门口便看到陆军军官学校的门匾高悬，不少学生也在此研学，校园面积广阔，师生们列队而行、纪律井然，倒也不觉拥挤。 继续往里走，来到孙中山先生曾栖息的小屋，屋内陈设保留着浓郁的民国风味，让人仿佛穿越回那个风云激荡的年代。 再往前，墙上的孙中山照片和文字介绍静静陈列，先生的面容与事迹在眼前变得鲜活起来，历史的温度触手可及。 接着看到一些泛黄的文献资料，有《孙总理演讲集》《黄埔潮》《北伐宣言》等，纸页虽已发旧，字里行间却依旧激荡着当年的革命豪情。 不远处展柜里陈列着几份军官的任命状，纸张同样泛黄，布满岁月的痕迹，每一份背后都是一段跌宕起伏的人生。 再往前走，不同时期的黄埔军校毕业证书整齐排列，从一期到后续各期，泛黄的纸张见证了一代代热血青年的成长与奔赴。 站在这些珍贵的历史遗物前，不禁感叹天下英雄如过江之鲫，无数黄埔学子从这里走出，书写了近代史上的壮阔篇章。 ","date":"2023-05-29","objectID":"/posts/%E5%B9%BF%E5%B7%9E%E7%9A%84%E6%98%BC%E4%B8%8E%E5%A4%9C%E5%A1%94%E5%BD%B1%E6%B1%9F%E7%81%AF%E9%97%B4%E7%9A%84%E7%99%BE%E5%B9%B4%E9%A3%8E%E4%BA%91/:5:0","tags":["旅游"],"title":"广州的昼与夜：塔影江灯间的百年风云","uri":"/posts/%E5%B9%BF%E5%B7%9E%E7%9A%84%E6%98%BC%E4%B8%8E%E5%A4%9C%E5%A1%94%E5%BD%B1%E6%B1%9F%E7%81%AF%E9%97%B4%E7%9A%84%E7%99%BE%E5%B9%B4%E9%A3%8E%E4%BA%91/#黄埔军校"}]