diff --git a/.gitignore b/.gitignore index 1e7ffde..63f307b 100644 --- a/.gitignore +++ b/.gitignore @@ -5,5 +5,4 @@ db.json node_modules/ public/ .deploy*/ -_multiconfig.yml -private.md \ No newline at end of file +_multiconfig.yml \ No newline at end of file diff --git a/.obsidian/app.json b/.obsidian/app.json deleted file mode 100644 index 2e67d9b..0000000 --- a/.obsidian/app.json +++ /dev/null @@ -1,18 +0,0 @@ -{ - "alwaysUpdateLinks": true, - "newFileLocation": "folder", - "newFileFolderPath": "source/_posts", - "newLinkFormat": "shortest", - "useMarkdownLinks": false, - "attachmentFolderPath": "source/images", - "userIgnoreFilters": [ - "node_modules/", - "themes/", - "source/categories/", - "source/archives/", - "source/comment/", - "source/tags/" - ], - "trashOption": "local", - "promptDelete": true -} \ No newline at end of file diff --git a/.obsidian/appearance.json b/.obsidian/appearance.json deleted file mode 100644 index 5029c57..0000000 --- a/.obsidian/appearance.json +++ /dev/null @@ -1,3 +0,0 @@ -{ - "accentColor": "#cb743a" -} \ No newline at end of file diff --git a/.obsidian/core-plugins.json b/.obsidian/core-plugins.json deleted file mode 100644 index 436f43c..0000000 --- a/.obsidian/core-plugins.json +++ /dev/null @@ -1,30 +0,0 @@ -{ - "file-explorer": true, - "global-search": true, - "switcher": true, - "graph": true, - "backlink": true, - "canvas": true, - "outgoing-link": true, - "tag-pane": true, - "properties": false, - "page-preview": true, - "daily-notes": true, - "templates": true, - "note-composer": true, - "command-palette": true, - "slash-command": false, - "editor-status": true, - "bookmarks": true, - "markdown-importer": false, - "zk-prefixer": false, - "random-note": false, - "outline": true, - "word-count": true, - "slides": false, - "audio-recorder": false, - "workspaces": false, - "file-recovery": true, - "publish": false, - "sync": false -} \ No newline at end of file diff --git a/.obsidian/templates.json b/.obsidian/templates.json deleted file mode 100644 index 81c3c02..0000000 --- a/.obsidian/templates.json +++ /dev/null @@ -1,4 +0,0 @@ -{ - "folder": "scaffolds", - "timeFormat": "HH:mm:ss" -} \ No newline at end of file diff --git a/.obsidian/workspace.json b/.obsidian/workspace.json deleted file mode 100644 index 88f24b8..0000000 --- a/.obsidian/workspace.json +++ /dev/null @@ -1,176 +0,0 @@ -{ - "main": { - "id": "2f49879301c55476", - "type": "split", - "children": [ - { - "id": "a0d65d44e78c2fe8", - "type": "tabs", - "children": [ - { - "id": "7d3fda9cd31fc426", - "type": "leaf", - "state": { - "type": "empty", - "state": {}, - "icon": "lucide-file", - "title": "新标签页" - } - } - ] - } - ], - "direction": "vertical" - }, - "left": { - "id": "e0eb01a562f849c6", - "type": "split", - "children": [ - { - "id": "250cc37ec4b78e51", - "type": "tabs", - "children": [ - { - "id": "5669aa2f4526eafc", - "type": "leaf", - "state": { - "type": "file-explorer", - "state": { - "sortOrder": "alphabetical" - }, - "icon": "lucide-folder-closed", - "title": "文件列表" - } - }, - { - "id": "cb2e16d94212c858", - "type": "leaf", - "state": { - "type": "search", - "state": { - "query": "", - "matchingCase": false, - "explainSearch": false, - "collapseAll": false, - "extraContext": false, - "sortOrder": "alphabetical" - }, - "icon": "lucide-search", - "title": "搜索" - } - }, - { - "id": "cd0c6745c605f686", - "type": "leaf", - "state": { - "type": "bookmarks", - "state": {}, - "icon": "lucide-bookmark", - "title": "书签" - } - } - ] - } - ], - "direction": "horizontal", - "width": 281.5 - }, - "right": { - "id": "7d2fc522cd417b79", - "type": "split", - "children": [ - { - "id": "5b3dea84d2206217", - "type": "tabs", - "children": [ - { - "id": "e4bbf9197a0345e2", - "type": "leaf", - "state": { - "type": "backlink", - "state": { - "collapseAll": false, - "extraContext": false, - "sortOrder": "alphabetical", - "showSearch": false, - "searchQuery": "", - "backlinkCollapsed": false, - "unlinkedCollapsed": true - }, - "icon": "links-coming-in", - "title": "反向链接" - } - }, - { - "id": "0f9749a6c755e960", - "type": "leaf", - "state": { - "type": "outgoing-link", - "state": { - "linksCollapsed": false, - "unlinkedCollapsed": true - }, - "icon": "links-going-out", - "title": "出链" - } - }, - { - "id": "ec9e9b5140aa2675", - "type": "leaf", - "state": { - "type": "tag", - "state": { - "sortOrder": "frequency", - "useHierarchy": true - }, - "icon": "lucide-tags", - "title": "标签" - } - }, - { - "id": "115d6f86abb434f9", - "type": "leaf", - "state": { - "type": "outline", - "state": {}, - "icon": "lucide-list", - "title": "大纲" - } - } - ] - } - ], - "direction": "horizontal", - "width": 300, - "collapsed": true - }, - "left-ribbon": { - "hiddenItems": { - "switcher:打开快速切换": false, - "graph:查看关系图谱": false, - "canvas:新建白板": false, - "daily-notes:打开/创建今天的日记": false, - "templates:插入模板": false, - "command-palette:打开命令面板": false - } - }, - "active": "7d3fda9cd31fc426", - "lastOpenFiles": [ - "source/_posts/随机过程及其应用.md", - "source/images/随机过程及其应用img/Pasted image 20250101125925.png", - "source/images/随机过程及其应用img/Pasted image 20250101125913.png", - "source/images/随机过程及其应用img/Pasted image 20250101125832.png", - "source/images/随机过程及其应用img/Pasted image 20250101125810.png", - "source/images/随机过程及其应用img/Pasted image 20250101125802.png", - "source/images/随机过程及其应用img/Pasted image 20250101125720.png", - "source/images/随机过程及其应用img/Pasted image 20250101125529.png", - "source/images/随机过程及其应用img/Pasted image 20241231225452.png", - "source/images/随机过程及其应用img/Pasted image 20241231225443.png", - "source/images/随机过程及其应用img/Pasted image 20241231204818.png", - "source/images/随机过程及其应用img", - "source/_posts/first.md", - "source/_posts/2025-04-27.md", - "未命名.canvas", - "source/about/index.md" - ] -} \ No newline at end of file diff --git "a/.trash/\346\234\252\345\221\275\345\220\215.canvas" "b/.trash/\346\234\252\345\221\275\345\220\215.canvas" deleted file mode 100644 index 9e26dfe..0000000 --- "a/.trash/\346\234\252\345\221\275\345\220\215.canvas" +++ /dev/null @@ -1 +0,0 @@ -{} \ No newline at end of file diff --git a/README.md b/README.md deleted file mode 100644 index 2282978..0000000 --- a/README.md +++ /dev/null @@ -1,75 +0,0 @@ -# Zip95297's Homepage with Hexo and Obsidian - -# 工具 - -## Note Application - -Obsidian - -## hexo - -用了github deploy action 之后有时间可以做一个帮助大家一键部署的脚本 或者 可以直接参考官方文档自己来 - -## utils - -由于本地笔记仓库ObsidianNote中有一些不想 post 上来 所以做了一些小工具联动 -通过这种方式 我就可以在本地 写一篇文章 并不把 任何附件上传 只 post 我想公开的记录 -或者对于已经有 obsidian 通过这些命令进行 homepage 的推送 - -在obsidian中 一篇笔记的结构如下: -```shell -√ -> zjb @ 课程笔记 % tree 随机过程及其应用 -随机过程及其应用 -├── 随机过程及其应用.md -└── img - ├── Pasted image 20241227212820.png - └── Pasted image 20241227213221.png -``` - -所以 当我准备post一篇来自 Obsidian 仓库中的文章 时,做如下操作: -1. 复制 `{name}.md` 文件 到 `source/_post` 目录 -2. 复制 `{name}/imgs` 目录 到 `source/images/{name}` 目录 -3. [x] 对 复制过来的 `{name}.md` 添加 笔记头文件 记录:推送时间、分类、tag ... -4. [x] 对 复制过来的 `{name}.md` 中的 `![[{URL}]]` 进行格式替换 - 说明: 用当前仓库中的 `source/images` 当做图床 然后在 [Github CDN](https://www.jsdelivr.com/github) 对这个仓库进行加速,并且通过 `utils/chURL` 对url格式进行替换 -5. [x] 用 `ffmpeg` ?之类的对 images 中的图片资源进行压缩 (因为github中一个仓库的大小限制是1G 单个文件限制是 10MB) - 最后选择用的是 cwebp -6. [x] 一键部署 当前的本地仓库 push & github actions - -计划将 1-5 步 做成交互式的脚本 在 `utils` 中,在 tui 中选择要 post 的 ObsidianNoteDir - -部署步骤分离出来 - -# 其他 - -最近事情有点多 之后在写 shell 做吧 - -# 使用方法 - -现在已经写完了为了方便自己记忆 写下使用方法 (因为当时写的时候忘记统一路径格式。。。以后一定要注意) - -- ./post - 交互式在 ObsidianNote 中选择要推送的笔记 (要求笔记的附件 imgs or img 在同目录下) -- utils/addAttribute : 给一个笔记添加属性 - 直接在根目录中: zjb—blog % `utils/addAttribute ./source/_posts/first.md` - 到post中(可以自动补全): _posts % `../../utils/addAttribute first.md ` -- utils/chURL : 把 md 笔记中的 ObsidianNote \!\[\[\]\] 转换为 CDN 加速后的图床 地址 - 直接在根目录中: zjb—blog % `utils/chURL 随机过程及其应用.md` - 到post中(可以自动补全): _posts % `../../utils/chURL SwinIR.md` -- utils/cleanBAK : 清理 chURL 时候的备份文件 - 在任意位置 -- utils/deploy: push 到 github 仓库 (自动执行 github action ) - 这个就在根目录zjb-blog用吧 -- utils/extractIMG 压缩图像 - 可以在根目录 :zjb-blog % `utils/extractIMG profile` - 在 source/images 里面用: images % `../../utils/extractIMG profile` -- utils/list\* : 在任何位置 - \*=Post 显示 所有推送的笔记 - \*=Obsidian 显示 Obsidian 中的笔记 -- utils/rmPost : - 这个填的是 没有后缀的(防止误删 自己打吧) `utils/rmPost -n|--note ` -- utils/post_cli : - Usage: utils/post_cli -n|--note [-i|--img ] - 后面的用绝对路径吧还是 - -忘记怎么用可以只输个命令 会有 `usage` diff --git a/_config.butterfly.yml b/_config.butterfly.yml index 1f85d7b..06cb670 100644 --- a/_config.butterfly.yml +++ b/_config.butterfly.yml @@ -1,282 +1,108 @@ -# Butterfly 主题配置文件 -# https://butterfly.js.org/posts/dc584b87/ -# -------------------------------------- -# 主题配置 +# Hexo Configuration +## Docs: https://hexo.io/docs/configuration.html +## Source: https://github.com/hexojs/hexo/ + +# Site title: 西郊有密林 祝君出重围 -description: '一个人' +#subtitle: +description: '哈哈哈哈哈哈' keywords: -author: zip95297 +author: 赵俊博 language: zh-CN timezone: Asia/Shanghai -url: http://zip95297.github.io +# URL +## Set your site url here. For example, if you use GitHub Page, set url as 'https://username.github.io/project' +url: http://example.com permalink: :year/:month/:day/:title/ permalink_defaults: pretty_urls: trailing_index: true # Set to false to remove trailing 'index.html' from permalinks trailing_html: true # Set to false to remove trailing '.html' from permalinks -# 谷歌分析统计 -google_analytics: G-MJK71R0R14 - -# 字数统计 -wordcount: - enable: true - # Display the word count of the article in post meta - post_wordcount: true - # Display the time to read the article in post meta - min2read: true - # Display the total word count of the website in aside's webinfo - total_wordcount: true - - -# 弹窗 -snackbar: - enable: true - position: bottom-center - bg_light: '#49b1f5' #light mode时弹窗背景 - bg_dark: '#2d3035' #dark mode时弹窗背景 - -# prefetch (预加载) -instantpage: true - -nav: - # 導航欄 Logo 圖片 - logo: - # 是否顯示標題 - display_title: true - # 是否固定導航欄 - fixed: false - -# 首页文章布局 5 / 7 -index_layout: 7 -# 加载动画 -preloader: true -# 是否顯示返回頂部按鈕 -back2top: true -# 首页图片 -index_img: https://cdn.jsdelivr.net/gh/zip95297/zip95297.github.io@main/source/images/profile/bg2.webp -default_top_img: https://cdn.jsdelivr.net/gh/zip95297/zip95297.github.io@main/source/images/profile/bg2.webp -top_img: https://cdn.jsdelivr.net/gh/zip95297/zip95297.github.io@main/source/images/profile/bg2.webp -# 背景图 -background: 'linear-gradient(20deg, #d0d1d1, #536d90, #363c50)' -# background: -# 页脚图 -footer_img: true -# favicon 浏览器的图标 -favicon: https://cdn.jsdelivr.net/gh/zip95297/zip95297.github.io@main/source/images/profile/profile.webp?raw=true -# 归档页 的图片 -archive_img: https://cdn.jsdelivr.net/gh/zip95297/zip95297.github.io@main/source/images/profile/bg1.webp -# 分类页 的图片 -category_img: https://cdn.jsdelivr.net/gh/zip95297/zip95297.github.io@main/source/images/profile/bg4.webp -# tag 的图片 -tag_img: https://cdn.jsdelivr.net/gh/zip95297/zip95297.github.io@main/source/images/profile/bg3.webp -# 灯箱效果 -lightbox: medium_zoom - -# 美化页面显示 -beautify: - enable: false - field: site # site/post - title-prefix-icon: '\f10d' # '\f0c1' 这个在这个网站上找:https://fontawesome.com/search?ip=classic&s=solid&o=r - title-prefix-icon-color: '#3D4E67' - -# 黑色遮罩 -mask: - header: true - footer: true - -# 贊助/打賞 -reward: - # 是否啟用打賞 - enable: true - # 打賞案例文本 - text: 赞赏作者~ - QR_code: - - img: https://cdn.jsdelivr.net/gh/zip95297/zip95297.github.io@main/source/images/profile/wxpay.webp - # link: - text: 微信 - - img: https://cdn.jsdelivr.net/gh/zip95297/zip95297.github.io@main/source/images/profile/alipay.webp - # link: - text: 支付宝 - -# 评论 -comments: - # Up to two comments system, the first will be shown as default - # Leave it empty if you don't need comments - # Choose: Disqus/Disqusjs/Livere/Gitalk/Valine/Waline/Utterances/Facebook Comments/Twikoo/Giscus/Remark42/Artalk - # Format of two comments system : Disqus,Waline - use: giscus - # Display the comment name next to the button - text: false - # Lazyload: The comment system will be load when comment element enters the browser's viewport. - # If you set it to true, the comment count will be invalid - lazyload: true - # Display comment count in post's top_img - count: false - # Display comment count in Home Page - card_post_count: false - -# Giscus -# https://giscus.app/ -giscus: - repo: zip95297/zip95297.github.io - repo_id: R_kgDOL1gerA - category_id: DIC_kwDOL1gerM4Cptdq - theme: - light: light - dark: dark - option: - -copy: - enable: true - # Add the copyright information after copied content - copyright: - enable: true - limit_count: 50 - -toc: - post: true - page: false - number: true - expand: false - # Only for post - style_simple: false - scroll_percent: true - -post_copyright: - # 是否启用版权声明 - enable: true - # 是否进行文章 URL 解码 - decode: true - # 作者链接 - author_href: - # 许可证类型 - license: CC BY-NC-SA 4.0 - # 许可证链接 - license_url: https://creativecommons.org/licenses/by-nc-sa/4.0/ - -# 滚动状态百分比 -rightside_scroll_percent: true -# 阅读模式按钮 -readmode: true - -post_meta: - # Home Page - page: - # Choose: created / updated / both - date_type: created - # Choose: date / relative - date_format: date - categories: true - tags: true - label: true - post: - # Choose: left / center - position: left - # Choose: created / updated / both - date_type: both - # Choose: date / relative - date_format: relative - categories: true - tags: true - label: true - -# 錨點設置 -anchor: - # 滾動時,URL 將根據標題 ID 更新 - auto_update: false - # 點擊標題滾動並更新錨點 - click_to_scroll: true - -# 侧边栏 -aside: - enable: true - hide: false - button: true - mobile: false # display on mobile - position: right # left or right,决定侧边栏在右边还是在左边 - card_author: - enable: true - description: 一个人 - button: - enable: true - icon: fab fa-github - text: 捞的一的Github主页 - link: https://github.com/zip95297 - card_announcement: - enable: true - content: 最近没什么要分享的 - card_recent_post: - # 是否顯示最近文章卡片 - enable: true - # 顯示文章數量,0 表示顯示所有 - limit: 2 - card_archives: - # 是否顯示歸檔卡片 - enable: false - -share: - # 選擇:sharejs / addtoany - # 如果不需要分享功能,保持為空 - use: sharejs - - # Share.js - # https://github.com/overtrue/share.js - sharejs: - sites: facebook,twitter,wechat,weibo,qq - - # AddToAny - # https://www.addtoany.com/ - addtoany: - item: facebook,twitter,wechat,sina_weibo,facebook_messenger,email,copy_link - -# 鼠標點擊效果: 文字 -clickShowText: - # 是否啟用文字效果 - enable: true - text: - - zzZ - - Zzz - - zZz - fontSize: 15px - # 是否隨機顯示文字 - random: false - # 是否在移動設備上啟用 - mobile: true - - -# 頁腳設置 -footer: - owner: - # 是否啟用所有者顯示 - enable: true - # 網站創建年份 - since: 2002 - # 自定義文本 - custom_text: 只有这么点东西 - # 主題和框架的版權聲明 - copyright: false - -social: - fab fa-github: https://github.com/zip95297 || Github || "#hdhfbb" - fas fa-envelope: mailto:zip95297@gmail.com || Email || "#000000" - -# 文章背景 - -# 背景彩带 -canvas_nest: - enable: true - # Color of lines, default: '0,0,0'; RGB values: (R,G,B).(note: use ',' to separate.) - color: '30,30,45' - # The opacity of line (0~1) - opacity: 0.5 - # The z-index property of the background - zIndex: -1 - # The number of lines - count: 99 - mobile: false - +# Directory +source_dir: source +public_dir: public +tag_dir: tags +archive_dir: archives +category_dir: categories +code_dir: downloads/code +i18n_dir: :lang +skip_render: + +# Writing +new_post_name: :title.md # File name of new posts +default_layout: post +titlecase: false # Transform title into titlecase +external_link: + enable: true # Open external links in new tab + field: site # Apply to the whole site + exclude: '' +filename_case: 0 +render_drafts: false +post_asset_folder: false +relative_link: false +future: true +syntax_highlighter: highlight.js +highlight: + line_number: true + auto_detect: false + tab_replace: '' + wrap: true + hljs: false +prismjs: + preprocess: true + line_number: true + tab_replace: '' + +# Home page setting +# path: Root path for your blogs index page. (default = '') +# per_page: Posts displayed per page. (0 = disable pagination) +# order_by: Posts order. (Order by date descending by default) +index_generator: + path: '' + per_page: 10 + order_by: -date + +# Category & Tag +default_category: uncategorized +category_map: +tag_map: + +# Metadata elements +## https://developer.mozilla.org/en-US/docs/Web/HTML/Element/meta +meta_generator: true + +# Date / Time format +## Hexo uses Moment.js to parse and display date +## You can customize the date format as defined in +## http://momentjs.com/docs/#/displaying/format/ +date_format: YYYY-MM-DD +time_format: HH:mm:ss +## updated_option supports 'mtime', 'date', 'empty' +updated_option: 'mtime' + +# Pagination +## Set per_page to 0 to disable pagination +per_page: 10 +pagination_dir: page + +# Include / Exclude file(s) +## include:/exclude: options only apply to the 'source/' folder +include: +exclude: +ignore: + +# Extensions +## Plugins: https://hexo.io/plugins/ +## Themes: https://hexo.io/themes/ +#theme: landscape +theme: butterfly + +# Deployment +## Docs: https://hexo.io/docs/one-command-deployment +deploy: + type: '' menu: 主页: / || fas fa-home @@ -285,12 +111,13 @@ menu: 标签: /tags/ || fa fa-tags 归档: /archives/ || fa fa-folder-open 生活 || fas fa-list: - # 分享: /share/ || fa fa-comments-o + 分享: /share/ || fa fa-comments-o + 相册: /photos/ || fa fa-camera-retro 音乐: /music/ || fa fa-music - 照片: /photos/ || fa fa-camera-retro 影视: /movies/ || fas fa-video 友链: /links/ || fa fa-link - # 留言板: /comment/ || fa fa-paper-plane + 留言板: /comment/ || fa fa-paper-plane + #留言板: /messageboard/ || fa fa-paper-plane 关于笔者: /about/ || fas fa-heart @@ -308,7 +135,7 @@ local_search: enable: true labels: input_placeholder: Search for Posts - hits_empty: "坏了坏了!没找到 ${query}" # 如果没有查到内容相关内容显示 + hits_empty: "坏了!没找到 ${query}" # 如果没有查到内容相关内容显示 # the subtitle on homepage (主頁subtitle) subtitle: @@ -331,31 +158,5 @@ subtitle: # 如果关闭打字效果,subtitle只会现示sub的第一行文字 sub: - 广告位🪧招租 - - 招租🪧广告位 -# - 广告位🪧招租 - -# 头像 -avatar: - img: https://cdn.jsdelivr.net/gh/zip95297/zip95297.github.io@main/source/images/profile/profile.webp?raw=true - effect: false # 是否启用头像旋转效果(可选) - -# 数学公式 -math: - # Choose: mathjax, katex - # Leave it empty if you don't need math - use: mathjax - # 如果設置為 true,將在每個頁面加載 mathjax/katex 腳本 - # 如果設置為 false,將根據你的設置加載 mathjax/katex 腳本(在頁面的 front-matter 中添加 'mathjax: true' 或者 'katex: true') - per_page: false - hide_scrollbar: false - - mathjax: - # Enable the contextual menu - enableMenu: true - # Choose: all / ams / none, This controls whether equations are numbered and how - tags: none - - katex: - # Enable the copy KaTeX formula - copy_tex: false - + - 广告位🪧招租 + - 广告位🪧招租 diff --git a/.trash/2025-04-27.md b/_config.landscape.yml similarity index 100% rename from .trash/2025-04-27.md rename to _config.landscape.yml diff --git a/_config.yml b/_config.yml index fe0e63a..21be09e 100644 --- a/_config.yml +++ b/_config.yml @@ -1,20 +1,19 @@ # Hexo Configuration ## Docs: https://hexo.io/docs/configuration.html ## Source: https://github.com/hexojs/hexo/ -## 这是全局配置文件 和主题无关 butterfly 的config是只对主题生效的 # Site title: 西郊有密林 祝君出重围 subtitle: '广告位🪧招租' -description: '一个人' +description: '哈哈哈哈哈哈' keywords: -author: zip95297 +author: 赵俊博 language: zh-CN timezone: Asia/Shanghai # URL ## Set your site url here. For example, if you use GitHub Page, set url as 'https://username.github.io/project' -url: http://zip95297.github.io +url: http://example.com permalink: :year/:month/:day/:title/ permalink_defaults: pretty_urls: @@ -22,30 +21,17 @@ pretty_urls: trailing_html: true # Set to false to remove trailing '.html' from permalinks # Directory -# suorce dir 是用来存放源文件的 .md 文件 source_dir: source -# public dir 是用来存放编译后的文件的 hexo generate 之后会把文件放到这个目录下 public_dir: public -# 生成的标签网页 存放在这个目录下 tag_dir: tags -# 生成的归档网页 存放在这个目录下 archive_dir: archives -# 生成的分类网页 存放在这个目录下 category_dir: categories -# 如果文章提供代码下载链接 存放在这个里 code_dir: downloads/code -# 语言目录 支持多语言用的 我暂时不需要 i18n_dir: :lang -# 这个是用来存放不需要编译或者渲染的文件的 例如 视频图片pdf skip_render: -# 写完一个 markdown 放在 source/_post 目录下 -# 这个目录下的文件会被 hexo generate 自动编译成 html 输出到 public 目录下 - # Writing -# 新建文章的文件名格式 hexo new "hello" 就会在 source/_posts 目录下生成一个 hello.md 文件 new_post_name: :title.md # File name of new posts -# 默认用的模板 themes/butterfly/layout/_layout/post.ejs default_layout: post titlecase: false # Transform title into titlecase external_link: @@ -54,17 +40,13 @@ external_link: exclude: '' filename_case: 0 render_drafts: false -# 每个文章有单独的文件夹 写文章时会同时生成一个同名文件夹,用来放图片之类的资源 -post_asset_folder: true -marked: - prependRoot: true - postAsset: true +post_asset_folder: false relative_link: false future: true syntax_highlighter: highlight.js highlight: enable: true - line_number: true + line_number: false auto_detect: false tab_replace: '' wrap: true @@ -80,7 +62,7 @@ prismjs: # order_by: Posts order. (Order by date descending by default) index_generator: path: '' - per_page: 15 + per_page: 10 order_by: -date # Category & Tag @@ -99,11 +81,11 @@ meta_generator: true date_format: YYYY-MM-DD time_format: HH:mm:ss ## updated_option supports 'mtime', 'date', 'empty' -updated_option: 'date' +updated_option: 'mtime' # Pagination ## Set per_page to 0 to disable pagination -per_page: 15 +per_page: 10 pagination_dir: page # Include / Exclude file(s) @@ -118,24 +100,7 @@ ignore: #theme: landscape theme: butterfly -# 支持公式渲染 -markdown: - # 一级目录无法跳转 - anchors: - level: 1 - # 这个是给锚点显示图标 - permalink: false - - render: - html: true - xhtmlOut: true - breaks: true - linkify: true - typographer: true - # Deployment ## Docs: https://hexo.io/docs/one-command-deployment deploy: - type: git - repo: https://github.com/zip95297/zip95297.github.io.git - branch: gh-pages + type: '' diff --git a/package-lock.json b/package-lock.json index 4b125e7..72fe6e0 100644 --- a/package-lock.json +++ b/package-lock.json @@ -8,27 +8,18 @@ "name": "hexo-site", "version": "0.0.0", "dependencies": { - "@neilsustc/markdown-it-katex": "^1.0.0", - "@renbaoshuo/markdown-it-katex": "^2.0.2", - "hexo": "^7.0.0", - "hexo-blog-encrypt": "^3.1.9", - "hexo-deployer-git": "^4.0.0", - "hexo-filter-mathjax": "^0.9.0", + "hexo": "^7.3.0", "hexo-generator-archive": "^2.0.0", "hexo-generator-category": "^2.0.0", "hexo-generator-index": "^3.0.0", "hexo-generator-search": "^2.4.3", "hexo-generator-tag": "^2.0.0", "hexo-renderer-ejs": "^2.0.0", - "hexo-renderer-markdown-it": "^7.1.1", - "hexo-renderer-pug": "^3.0.0", - "hexo-renderer-stylus": "^3.0.1", + "hexo-renderer-marked": "^6.0.0", + "hexo-renderer-stylus": "^3.0.0", "hexo-server": "^3.0.0", "hexo-theme-butterfly": "^4.13.0", - "hexo-theme-landscape": "^1.0.0", - "hexo-wordcount": "^6.0.1", - "katex": "^0.16.22", - "markdown-it-katex": "^2.0.3" + "hexo-theme-landscape": "^1.0.0" } }, "node_modules/@adobe/css-tools": { @@ -76,33 +67,12 @@ "node": ">=6.9.0" } }, - "node_modules/@neilsustc/markdown-it-katex": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/@neilsustc/markdown-it-katex/-/markdown-it-katex-1.0.0.tgz", - "integrity": "sha512-2u21S69739IGrNA/Q2xfcurox5LFXq2sp3CPeqx7yDNjy3/SC8taL6QAIYxupRuEUPNS5+OXq9DmwcdKjs90pg==", - "dependencies": { - "katex": "*" - }, - "peerDependencies": { - "katex": "0", - "markdown-it": ">=6" - } - }, - "node_modules/@renbaoshuo/markdown-it-katex": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/@renbaoshuo/markdown-it-katex/-/markdown-it-katex-2.0.2.tgz", - "integrity": "sha512-peKX5VcHY2DXQ2AQaTduWkGwaQ2QSlscoOqRmOt6Fm9W+WmYacWnkzGsQeLlDw/lg9ZPSkE/SPSZnJC/kDEwnA==", - "peerDependencies": { - "katex": "*", - "markdown-it": ">=6" - } - }, - "node_modules/@xmldom/xmldom": { - "version": "0.9.8", - "resolved": "https://registry.npmjs.org/@xmldom/xmldom/-/xmldom-0.9.8.tgz", - "integrity": "sha512-p96FSY54r+WJ50FIOsCOjyj/wavs8921hG5+kVMmZgKcvIKxMXHTrjNJvRgWa/zuX3B6t2lijLNFaOyuxUH+2A==", + "node_modules/@tootallnate/once": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/@tootallnate/once/-/once-2.0.0.tgz", + "integrity": "sha512-XCuKFP5PS55gnMVu3dty8KPatLqUoy/ZYzDzAGCQ8JNFCkLXzmI7vNHCR+XpbZaMWQK/vQubr7PkYq8g470J/A==", "engines": { - "node": ">=14.6" + "node": ">= 10" } }, "node_modules/a-sync-waterfall": { @@ -110,6 +80,12 @@ "resolved": "https://registry.npmjs.org/a-sync-waterfall/-/a-sync-waterfall-1.0.1.tgz", "integrity": "sha512-RYTOHHdWipFUliRFMCS4X2Yn2X8M87V/OpSqWzKKOGhzqyUxzyVmhHDH9sAvG+ZuQf/TAOFsLCpMw09I1ufUnA==" }, + "node_modules/abab": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/abab/-/abab-2.0.6.tgz", + "integrity": "sha512-j2afSsaIENvHZN2B8GOpF566vZ5WVk5opAiMTvWgaQT8DkbOqsTfvNAvHoRGU2zzP8cPoqys+xHTRDWW8L+/BA==", + "deprecated": "Use your platform's native atob() and btoa() methods instead" + }, "node_modules/abbrev": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/abbrev/-/abbrev-2.0.0.tgz", @@ -130,6 +106,66 @@ "node": ">= 0.6" } }, + "node_modules/acorn": { + "version": "8.11.3", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.11.3.tgz", + "integrity": "sha512-Y9rRfJG5jcKOE0CLisYbojUjIrIEE7AGMzA/Sm4BslANhbS+cDMpgBdcPT91oJ7OuJ9hYJBx59RjbhxVnrF8Xg==", + "bin": { + "acorn": "bin/acorn" + }, + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/acorn-globals": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/acorn-globals/-/acorn-globals-7.0.1.tgz", + "integrity": "sha512-umOSDSDrfHbTNPuNpC2NSnnA3LUrqpevPb4T9jRx4MagXNS0rs+gwiTcAvqCRmsD6utzsrzNt+ebm00SNWiC3Q==", + "dependencies": { + "acorn": "^8.1.0", + "acorn-walk": "^8.0.2" + } + }, + "node_modules/acorn-walk": { + "version": "8.3.2", + "resolved": "https://registry.npmjs.org/acorn-walk/-/acorn-walk-8.3.2.tgz", + "integrity": "sha512-cjkyv4OtNCIeqhHrfS81QWXoCBPExR/J62oyEqepVw8WaQeSqpW2uhuLPh1m9eWhDuOo/jUXVTlifvesOWp/4A==", + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/agent-base": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-6.0.2.tgz", + "integrity": "sha512-RZNwNclF7+MS/8bDg70amg32dyeZGZxiDuQmZxKLAlQjr3jGyLx+4Kkk58UO7D2QdgFIQCovuSuZESne6RG6XQ==", + "dependencies": { + "debug": "4" + }, + "engines": { + "node": ">= 6.0.0" + } + }, + "node_modules/agent-base/node_modules/debug": { + "version": "4.3.4", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", + "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", + "dependencies": { + "ms": "2.1.2" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/agent-base/node_modules/ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==" + }, "node_modules/ansi-regex": { "version": "5.0.1", "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", @@ -189,6 +225,11 @@ "resolved": "https://registry.npmjs.org/async/-/async-3.2.5.tgz", "integrity": "sha512-baNZyqaaLhyLVKm/DlvdW051MSgO6b8eVfIezl9E5PqWxFgzLm/wQntEW4zOytVburDEr0JlALEpdOFwvErLsg==" }, + "node_modules/asynckit": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", + "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==" + }, "node_modules/babel-walk": { "version": "3.0.0-canary-5", "resolved": "https://registry.npmjs.org/babel-walk/-/babel-walk-3.0.0-canary-5.tgz", @@ -278,18 +319,6 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/call-bind-apply-helpers": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz", - "integrity": "sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==", - "dependencies": { - "es-errors": "^1.3.0", - "function-bind": "^1.1.2" - }, - "engines": { - "node": ">= 0.4" - } - }, "node_modules/camel-case": { "version": "4.1.2", "resolved": "https://registry.npmjs.org/camel-case/-/camel-case-4.1.2.tgz", @@ -361,6 +390,17 @@ "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==" }, + "node_modules/combined-stream": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", + "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==", + "dependencies": { + "delayed-stream": "~1.0.0" + }, + "engines": { + "node": ">= 0.8" + } + }, "node_modules/command-exists": { "version": "1.2.9", "resolved": "https://registry.npmjs.org/command-exists/-/command-exists-1.2.9.tgz", @@ -443,12 +483,46 @@ "node": ">= 8" } }, + "node_modules/cssom": { + "version": "0.5.0", + "resolved": "https://registry.npmjs.org/cssom/-/cssom-0.5.0.tgz", + "integrity": "sha512-iKuQcq+NdHqlAcwUY0o/HL69XQrUaQdMjmStJ8JFmUaiiQErlhrmuigkg/CU4E2J0IyUKUrMAgl36TvN67MqTw==" + }, + "node_modules/cssstyle": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/cssstyle/-/cssstyle-2.3.0.tgz", + "integrity": "sha512-AZL67abkUzIuvcHqk7c09cezpGNcxUxU4Ioi/05xHk4DQeTkWmGYftIE6ctU6AEt+Gn4n1lDStOtj7FKycP71A==", + "dependencies": { + "cssom": "~0.3.6" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/cssstyle/node_modules/cssom": { + "version": "0.3.8", + "resolved": "https://registry.npmjs.org/cssom/-/cssom-0.3.8.tgz", + "integrity": "sha512-b0tGHbfegbhPJpxpiBPU2sCkigAqtM9O121le6bbOlgyV+NyGyCmVfJ6QW9eRjz8CpNfWEOYBIMIGRYkLwsIYg==" + }, "node_modules/cuid": { "version": "2.1.8", "resolved": "https://registry.npmjs.org/cuid/-/cuid-2.1.8.tgz", "integrity": "sha512-xiEMER6E7TlTPnDxrM4eRiC6TRgjNX9xzEZ5U/Se2YJKr7Mq4pJn/2XEHjl3STcSh96GmkHPcBXLES8M29wyyg==", "deprecated": "Cuid and other k-sortable and non-cryptographic ids (Ulid, ObjectId, KSUID, all UUIDs) are all insecure. Use @paralleldrive/cuid2 instead." }, + "node_modules/data-urls": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/data-urls/-/data-urls-3.0.2.tgz", + "integrity": "sha512-Jy/tj3ldjZJo63sVAvg6LHt2mHvl4V6AgRAmNDtLdm7faqtsx+aJG42rsyCo9JCoRVKwPFzKlIPx3DIibwSIaQ==", + "dependencies": { + "abab": "^2.0.6", + "whatwg-mimetype": "^3.0.0", + "whatwg-url": "^11.0.0" + }, + "engines": { + "node": ">=12" + } + }, "node_modules/debug": { "version": "2.6.9", "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", @@ -457,6 +531,11 @@ "ms": "2.0.0" } }, + "node_modules/decimal.js": { + "version": "10.4.3", + "resolved": "https://registry.npmjs.org/decimal.js/-/decimal.js-10.4.3.tgz", + "integrity": "sha512-VBBaLc1MgL5XpzgIP7ny5Z6Nx3UrRkIViUkPUdtl9aya5amy3De1gsUUSB1g3+3sExYNjCAsAznmukyxCb1GRA==" + }, "node_modules/deepmerge": { "version": "4.3.1", "resolved": "https://registry.npmjs.org/deepmerge/-/deepmerge-4.3.1.tgz", @@ -489,6 +568,14 @@ "node": ">=8" } }, + "node_modules/delayed-stream": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", + "integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==", + "engines": { + "node": ">=0.4.0" + } + }, "node_modules/depd": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz", @@ -535,6 +622,18 @@ } ] }, + "node_modules/domexception": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/domexception/-/domexception-4.0.0.tgz", + "integrity": "sha512-A2is4PLG+eeSfoTMA95/s4pvAoSo2mKtiM5jlHkAVewmiO8ISFTFKZjH7UAM1Atli/OT/7JHOrJRJiMKUZKYBw==", + "deprecated": "Use your platform's native DOMException instead", + "dependencies": { + "webidl-conversions": "^7.0.0" + }, + "engines": { + "node": ">=12" + } + }, "node_modules/domhandler": { "version": "5.0.3", "resolved": "https://registry.npmjs.org/domhandler/-/domhandler-5.0.3.tgz", @@ -549,6 +648,11 @@ "url": "https://github.com/fb55/domhandler?sponsor=1" } }, + "node_modules/dompurify": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/dompurify/-/dompurify-3.1.2.tgz", + "integrity": "sha512-hLGGBI1tw5N8qTELr3blKjAML/LY4ANxksbS612UiJyDfyf/2D092Pvm+S7pmeTGJRqvlJkFzBoHBQKgQlOQVg==" + }, "node_modules/domutils": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/domutils/-/domutils-3.1.0.tgz", @@ -562,24 +666,6 @@ "url": "https://github.com/fb55/domutils?sponsor=1" } }, - "node_modules/dunder-proto": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz", - "integrity": "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==", - "dependencies": { - "call-bind-apply-helpers": "^1.0.1", - "es-errors": "^1.3.0", - "gopd": "^1.2.0" - }, - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/eastasianwidth": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/eastasianwidth/-/eastasianwidth-0.2.0.tgz", - "integrity": "sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==" - }, "node_modules/ee-first": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz", @@ -619,9 +705,12 @@ } }, "node_modules/es-define-property": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.1.tgz", - "integrity": "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==", + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.0.tgz", + "integrity": "sha512-jxayLKShrEqqzJ0eumQbVhTYQM27CfT1T35+gCgDFoL82JLsXqTJ76zv6A0YLOgEnLUMvLzsDsGIrl8NFpT2gQ==", + "dependencies": { + "get-intrinsic": "^1.2.4" + }, "engines": { "node": ">= 0.4" } @@ -634,28 +723,29 @@ "node": ">= 0.4" } }, - "node_modules/es-object-atoms": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.1.1.tgz", - "integrity": "sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==", - "dependencies": { - "es-errors": "^1.3.0" - }, - "engines": { - "node": ">= 0.4" - } - }, "node_modules/escape-html": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz", "integrity": "sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==" }, - "node_modules/esm": { - "version": "3.2.25", - "resolved": "https://registry.npmjs.org/esm/-/esm-3.2.25.tgz", - "integrity": "sha512-U1suiZ2oDVWv4zPO56S0NcR5QriEahGtdN2OR6FiOG4WJvcjBVFB0qI4+eKoWFH483PKGuLuu6V8Z4T5g63UVA==", + "node_modules/escodegen": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/escodegen/-/escodegen-2.1.0.tgz", + "integrity": "sha512-2NlIDTwUWJN0mRPQOdtQBzbUHvdGY2P1VXSyU83Q3xKxM7WHX2Ql8dKq782Q9TgQUNOLEzEYu9bzLNj1q88I5w==", + "dependencies": { + "esprima": "^4.0.1", + "estraverse": "^5.2.0", + "esutils": "^2.0.2" + }, + "bin": { + "escodegen": "bin/escodegen.js", + "esgenerate": "bin/esgenerate.js" + }, "engines": { - "node": ">=6" + "node": ">=6.0" + }, + "optionalDependencies": { + "source-map": "~0.6.1" } }, "node_modules/esprima": { @@ -670,6 +760,22 @@ "node": ">=4" } }, + "node_modules/estraverse": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", + "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", + "engines": { + "node": ">=4.0" + } + }, + "node_modules/esutils": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz", + "integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==", + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/etag": { "version": "1.8.1", "resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz", @@ -738,6 +844,19 @@ "node": ">= 0.8" } }, + "node_modules/form-data": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.0.tgz", + "integrity": "sha512-ETEklSGi5t0QMZuiXoA/Q6vcnxcLQP5vdugSpuAyi6SVGi2clPPp+xgEhuMaHC+zGgn31Kd235W35f7Hykkaww==", + "dependencies": { + "asynckit": "^0.4.0", + "combined-stream": "^1.0.8", + "mime-types": "^2.1.12" + }, + "engines": { + "node": ">= 6" + } + }, "node_modules/fresh": { "version": "0.5.2", "resolved": "https://registry.npmjs.org/fresh/-/fresh-0.5.2.tgz", @@ -773,20 +892,15 @@ } }, "node_modules/get-intrinsic": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.3.0.tgz", - "integrity": "sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==", + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.2.4.tgz", + "integrity": "sha512-5uYhsJH8VJBTv7oslg4BznJYhDoRI6waYCxMmCdnTrcCrHA/fCFKoTFz2JKKE0HdDFUF7/oQuhzumXJK7paBRQ==", "dependencies": { - "call-bind-apply-helpers": "^1.0.2", - "es-define-property": "^1.0.1", "es-errors": "^1.3.0", - "es-object-atoms": "^1.1.1", "function-bind": "^1.1.2", - "get-proto": "^1.0.1", - "gopd": "^1.2.0", - "has-symbols": "^1.1.0", - "hasown": "^2.0.2", - "math-intrinsics": "^1.1.0" + "has-proto": "^1.0.1", + "has-symbols": "^1.0.3", + "hasown": "^2.0.0" }, "engines": { "node": ">= 0.4" @@ -795,18 +909,6 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/get-proto": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/get-proto/-/get-proto-1.0.1.tgz", - "integrity": "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==", - "dependencies": { - "dunder-proto": "^1.0.1", - "es-object-atoms": "^1.0.0" - }, - "engines": { - "node": ">= 0.4" - } - }, "node_modules/glob": { "version": "7.2.3", "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", @@ -838,11 +940,11 @@ } }, "node_modules/gopd": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz", - "integrity": "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==", - "engines": { - "node": ">= 0.4" + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.0.1.tgz", + "integrity": "sha512-d65bNlIadxvpb/A2abVdlqKqV563juRnZ1Wtk6s1sIR8uNsXR70xqIzVqxVf1eTqDunwT2MkczEeaezCKTZhwA==", + "dependencies": { + "get-intrinsic": "^1.1.3" }, "funding": { "url": "https://github.com/sponsors/ljharb" @@ -872,10 +974,21 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/has-proto": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/has-proto/-/has-proto-1.0.3.tgz", + "integrity": "sha512-SJ1amZAJUiZS+PhsVLf5tGydlaVB8EdFpaSO4gmiUKUOxk8qzn5AIy4ZeJUmh22znIdk/uMAUT2pl3FxzVUH+Q==", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/has-symbols": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.1.0.tgz", - "integrity": "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==", + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.3.tgz", + "integrity": "sha512-l3LCuF6MgDNwTDKkdYGEihYjt5pRPbEg46rtlmnSPlUbgmB8LOIrKJbYYFBSbnPaJexMKtiPO8hmeRjRz2Td+A==", "engines": { "node": ">= 0.4" }, @@ -909,9 +1022,9 @@ } }, "node_modules/hexo": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/hexo/-/hexo-7.2.0.tgz", - "integrity": "sha512-RYIzl7jfG0i2jH/k5IZg4C1anyHfmKHNUsBKIn9LU0V3iQ0WQrwuOLFDJwaZDenqmzHYJhAVCGAkrBDfF/IlVg==", + "version": "7.3.0", + "resolved": "https://registry.npmjs.org/hexo/-/hexo-7.3.0.tgz", + "integrity": "sha512-dOe8mzBKrvjubW5oBmyhcnQDpC+M2xmAMLae5K+o+SkHxyvAhShkS2VQZoTsOLIJKY6xilv7dzCjCvE7ol/NHQ==", "dependencies": { "abbrev": "^2.0.0", "archy": "^1.0.0", @@ -949,11 +1062,6 @@ "url": "https://opencollective.com/hexo" } }, - "node_modules/hexo-blog-encrypt": { - "version": "3.1.9", - "resolved": "https://registry.npmjs.org/hexo-blog-encrypt/-/hexo-blog-encrypt-3.1.9.tgz", - "integrity": "sha512-dzuZiW5mD8t1FOhJXqc+IgV1Tc45YtatJb/w8k4PyT9McQxsROQXsBw+zbDembzuqMnbnkCeT2SapZ/r9AgAcw==" - }, "node_modules/hexo-cli": { "version": "4.3.2", "resolved": "https://registry.npmjs.org/hexo-cli/-/hexo-cli-4.3.2.tgz", @@ -977,125 +1085,6 @@ "node": ">=14" } }, - "node_modules/hexo-deployer-git": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/hexo-deployer-git/-/hexo-deployer-git-4.0.0.tgz", - "integrity": "sha512-28t1Q+4taB/UaBAP52W3mD/wcCwa2y2zBieUfBJFBZudbmVgiKJB5YedYILeyI5QByaUKAOwoupmdTbocdQ+CQ==", - "dependencies": { - "bluebird": "^3.7.2", - "hexo-fs": "^4.0.0", - "hexo-util": "^2.7.0", - "luxon": "^3.0.4", - "nunjucks": "^3.2.3", - "picocolors": "^1.0.0" - }, - "engines": { - "node": ">=14" - } - }, - "node_modules/hexo-deployer-git/node_modules/dom-serializer": { - "version": "1.4.1", - "resolved": "https://registry.npmjs.org/dom-serializer/-/dom-serializer-1.4.1.tgz", - "integrity": "sha512-VHwB3KfrcOOkelEG2ZOfxqLZdfkil8PtJi4P8N2MMXucZq2yLp75ClViUlOVwyoHEDjYU433Aq+5zWP61+RGag==", - "dependencies": { - "domelementtype": "^2.0.1", - "domhandler": "^4.2.0", - "entities": "^2.0.0" - }, - "funding": { - "url": "https://github.com/cheeriojs/dom-serializer?sponsor=1" - } - }, - "node_modules/hexo-deployer-git/node_modules/dom-serializer/node_modules/entities": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/entities/-/entities-2.2.0.tgz", - "integrity": "sha512-p92if5Nz619I0w+akJrLZH0MX0Pb5DX39XOwQTtXSdQQOaYH03S1uIQp4mhOZtAXrxq4ViO67YTiLBo2638o9A==", - "funding": { - "url": "https://github.com/fb55/entities?sponsor=1" - } - }, - "node_modules/hexo-deployer-git/node_modules/domhandler": { - "version": "4.3.1", - "resolved": "https://registry.npmjs.org/domhandler/-/domhandler-4.3.1.tgz", - "integrity": "sha512-GrwoxYN+uWlzO8uhUXRl0P+kHE4GtVPfYzVLcUxPL7KNdHKj66vvlhiweIHqYYXWlw+T8iLMp42Lm67ghw4WMQ==", - "dependencies": { - "domelementtype": "^2.2.0" - }, - "engines": { - "node": ">= 4" - }, - "funding": { - "url": "https://github.com/fb55/domhandler?sponsor=1" - } - }, - "node_modules/hexo-deployer-git/node_modules/domutils": { - "version": "2.8.0", - "resolved": "https://registry.npmjs.org/domutils/-/domutils-2.8.0.tgz", - "integrity": "sha512-w96Cjofp72M5IIhpjgobBimYEfoPjx1Vx0BSX9P30WBdZW2WIKU0T1Bd0kz2eNZ9ikjKgHbEyKx8BB6H1L3h3A==", - "dependencies": { - "dom-serializer": "^1.0.1", - "domelementtype": "^2.2.0", - "domhandler": "^4.2.0" - }, - "funding": { - "url": "https://github.com/fb55/domutils?sponsor=1" - } - }, - "node_modules/hexo-deployer-git/node_modules/entities": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/entities/-/entities-3.0.1.tgz", - "integrity": "sha512-WiyBqoomrwMdFG1e0kqvASYfnlb0lp8M5o5Fw2OFq1hNZxxcNk8Ik0Xm7LxzBhuidnZB/UtBqVCgUz3kBOP51Q==", - "engines": { - "node": ">=0.12" - }, - "funding": { - "url": "https://github.com/fb55/entities?sponsor=1" - } - }, - "node_modules/hexo-deployer-git/node_modules/hexo-util": { - "version": "2.7.0", - "resolved": "https://registry.npmjs.org/hexo-util/-/hexo-util-2.7.0.tgz", - "integrity": "sha512-hQM3h34nhDg0bSe/Tg1lnpODvNkz7h2u0+lZGzlKL0Oufp+5KCAEUX9wal7/xC7ax3/cwEn8IuoU75kNpZLpJQ==", - "dependencies": { - "bluebird": "^3.5.2", - "camel-case": "^4.0.0", - "cross-spawn": "^7.0.0", - "deepmerge": "^4.2.2", - "highlight.js": "^11.0.1", - "htmlparser2": "^7.0.0", - "prismjs": "^1.17.1", - "strip-indent": "^3.0.0" - }, - "engines": { - "node": ">=12.4.0" - } - }, - "node_modules/hexo-deployer-git/node_modules/htmlparser2": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/htmlparser2/-/htmlparser2-7.2.0.tgz", - "integrity": "sha512-H7MImA4MS6cw7nbyURtLPO1Tms7C5H602LRETv95z1MxO/7CP7rDVROehUYeYBUYEON94NXXDEPmZuq+hX4sog==", - "funding": [ - "https://github.com/fb55/htmlparser2?sponsor=1", - { - "type": "github", - "url": "https://github.com/sponsors/fb55" - } - ], - "dependencies": { - "domelementtype": "^2.0.1", - "domhandler": "^4.2.2", - "domutils": "^2.8.0", - "entities": "^3.0.1" - } - }, - "node_modules/hexo-filter-mathjax": { - "version": "0.9.0", - "resolved": "https://registry.npmjs.org/hexo-filter-mathjax/-/hexo-filter-mathjax-0.9.0.tgz", - "integrity": "sha512-tTeaRLQjE60m4dSHA+SCXHYsTcqN6bGGxA+3Zzw6GHMBweIZNKXOS0ctUq32PuwejFlLKUSAeQdhhvTBOrgKMA==", - "dependencies": { - "mathjax-full": "3.2.2" - } - }, "node_modules/hexo-front-matter": { "version": "4.2.1", "resolved": "https://registry.npmjs.org/hexo-front-matter/-/hexo-front-matter-4.2.1.tgz", @@ -1218,24 +1207,15 @@ "node": ">=12" } }, - "node_modules/hexo-renderer-markdown-it": { - "version": "7.1.1", - "resolved": "https://registry.npmjs.org/hexo-renderer-markdown-it/-/hexo-renderer-markdown-it-7.1.1.tgz", - "integrity": "sha512-BxI2j2f/l7lOgb7DiT1M4GcP/QhR8/rjMlYx4MEPog/9NTpYhbaspiVsw3tGXOsZVmu+cVgBYoeyIQsFYvv3rw==", + "node_modules/hexo-renderer-marked": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/hexo-renderer-marked/-/hexo-renderer-marked-6.3.0.tgz", + "integrity": "sha512-V/ATcJ+tZHkTJSbScPzzHKmrwVMohU8i9MfuX9jp07Un/NpPtaTP821unP3JPu+O1nNLWMi+3xRbFRdm+8vajw==", "dependencies": { - "hexo-util": "^3.0.1", - "markdown-it": "^13.0.1", - "markdown-it-abbr": "^1.0.4", - "markdown-it-attrs": "^4.1.3", - "markdown-it-cjk-breaks": "^1.1.2", - "markdown-it-container": "^3.0.0", - "markdown-it-deflist": "^2.0.3", - "markdown-it-emoji": "^2.0.0", - "markdown-it-footnote": "^3.0.1", - "markdown-it-ins": "^3.0.0", - "markdown-it-mark": "^3.0.0", - "markdown-it-sub": "^1.0.0", - "markdown-it-sup": "^1.0.0" + "dompurify": "^3.0.3", + "hexo-util": "^3.1.0", + "jsdom": "^20.0.1", + "marked": "^4.3.0" }, "engines": { "node": ">=14" @@ -1314,11 +1294,6 @@ "node": ">=14" } }, - "node_modules/hexo-wordcount": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/hexo-wordcount/-/hexo-wordcount-6.0.1.tgz", - "integrity": "sha512-tbo2P9xRWEKQmRf7+XuPjx9It1MnaE26nA+EEb2DN39gK1x+26W7Nm4Iyp4AugQjBWYYDx7OLn4gp1WFxQpQew==" - }, "node_modules/highlight.js": { "version": "11.9.0", "resolved": "https://registry.npmjs.org/highlight.js/-/highlight.js-11.9.0.tgz", @@ -1327,6 +1302,17 @@ "node": ">=12.0.0" } }, + "node_modules/html-encoding-sniffer": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/html-encoding-sniffer/-/html-encoding-sniffer-3.0.0.tgz", + "integrity": "sha512-oWv4T4yJ52iKrufjnyZPkrN0CH3QnrUqdB6In1g5Fe1mia8GmF36gnfNySxoZtxD5+NmYw1EElVXiBk93UeskA==", + "dependencies": { + "whatwg-encoding": "^2.0.0" + }, + "engines": { + "node": ">=12" + } + }, "node_modules/htmlparser2": { "version": "9.1.0", "resolved": "https://registry.npmjs.org/htmlparser2/-/htmlparser2-9.1.0.tgz", @@ -1368,6 +1354,84 @@ "node": ">= 0.8" } }, + "node_modules/http-proxy-agent": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/http-proxy-agent/-/http-proxy-agent-5.0.0.tgz", + "integrity": "sha512-n2hY8YdoRE1i7r6M0w9DIw5GgZN0G25P8zLCRQ8rjXtTU3vsNFBI/vWK/UIeE6g5MUUz6avwAPXmL6Fy9D/90w==", + "dependencies": { + "@tootallnate/once": "2", + "agent-base": "6", + "debug": "4" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/http-proxy-agent/node_modules/debug": { + "version": "4.3.4", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", + "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", + "dependencies": { + "ms": "2.1.2" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/http-proxy-agent/node_modules/ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==" + }, + "node_modules/https-proxy-agent": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-5.0.1.tgz", + "integrity": "sha512-dFcAjpTQFgoLMzC2VwU+C/CbS7uRL0lWmxDITmqm7C+7F0Odmj6s9l6alZc6AELXhrnggM2CeWSXHGOdX2YtwA==", + "dependencies": { + "agent-base": "6", + "debug": "4" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/https-proxy-agent/node_modules/debug": { + "version": "4.3.4", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", + "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", + "dependencies": { + "ms": "2.1.2" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/https-proxy-agent/node_modules/ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==" + }, + "node_modules/iconv-lite": { + "version": "0.6.3", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.6.3.tgz", + "integrity": "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==", + "dependencies": { + "safer-buffer": ">= 2.1.2 < 3.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/inflight": { "version": "1.0.6", "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", @@ -1473,6 +1537,11 @@ "node": ">=0.10.0" } }, + "node_modules/is-potential-custom-element-name": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/is-potential-custom-element-name/-/is-potential-custom-element-name-1.0.1.tgz", + "integrity": "sha512-bCYeRA2rVibKZd+s2625gGnGF/t7DSqDs4dP7CrLA1m7jKWz6pps0LpYLJN8Q64HtmPKJ1hrN3nzPNKFEKOUiQ==" + }, "node_modules/is-promise": { "version": "2.2.2", "resolved": "https://registry.npmjs.org/is-promise/-/is-promise-2.2.2.tgz", @@ -1553,6 +1622,50 @@ "js-yaml": "4.x" } }, + "node_modules/jsdom": { + "version": "20.0.3", + "resolved": "https://registry.npmjs.org/jsdom/-/jsdom-20.0.3.tgz", + "integrity": "sha512-SYhBvTh89tTfCD/CRdSOm13mOBa42iTaTyfyEWBdKcGdPxPtLFBXuHR8XHb33YNYaP+lLbmSvBTsnoesCNJEsQ==", + "dependencies": { + "abab": "^2.0.6", + "acorn": "^8.8.1", + "acorn-globals": "^7.0.0", + "cssom": "^0.5.0", + "cssstyle": "^2.3.0", + "data-urls": "^3.0.2", + "decimal.js": "^10.4.2", + "domexception": "^4.0.0", + "escodegen": "^2.0.0", + "form-data": "^4.0.0", + "html-encoding-sniffer": "^3.0.0", + "http-proxy-agent": "^5.0.0", + "https-proxy-agent": "^5.0.1", + "is-potential-custom-element-name": "^1.0.1", + "nwsapi": "^2.2.2", + "parse5": "^7.1.1", + "saxes": "^6.0.0", + "symbol-tree": "^3.2.4", + "tough-cookie": "^4.1.2", + "w3c-xmlserializer": "^4.0.0", + "webidl-conversions": "^7.0.0", + "whatwg-encoding": "^2.0.0", + "whatwg-mimetype": "^3.0.0", + "whatwg-url": "^11.0.0", + "ws": "^8.11.0", + "xml-name-validator": "^4.0.0" + }, + "engines": { + "node": ">=14" + }, + "peerDependencies": { + "canvas": "^2.5.0" + }, + "peerDependenciesMeta": { + "canvas": { + "optional": true + } + } + }, "node_modules/jsonparse": { "version": "1.3.1", "resolved": "https://registry.npmjs.org/jsonparse/-/jsonparse-1.3.1.tgz", @@ -1570,37 +1683,6 @@ "promise": "^7.0.1" } }, - "node_modules/katex": { - "version": "0.16.22", - "resolved": "https://registry.npmjs.org/katex/-/katex-0.16.22.tgz", - "integrity": "sha512-XCHRdUw4lf3SKBaJe4EvgqIuWwkPSo9XoeO8GjQW94Bp7TWv9hNhzZjZ+OH9yf1UmLygb7DIT5GSFQiyt16zYg==", - "funding": [ - "https://opencollective.com/katex", - "https://github.com/sponsors/katex" - ], - "dependencies": { - "commander": "^8.3.0" - }, - "bin": { - "katex": "cli.js" - } - }, - "node_modules/katex/node_modules/commander": { - "version": "8.3.0", - "resolved": "https://registry.npmjs.org/commander/-/commander-8.3.0.tgz", - "integrity": "sha512-OkTL9umf+He2DZkUq8f8J9of7yL6RJKI24dVITBmNfZBmri9zYZQrKkuXiKhyfPSu8tUhnVBB1iKXevvnlR4Ww==", - "engines": { - "node": ">= 12" - } - }, - "node_modules/linkify-it": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/linkify-it/-/linkify-it-4.0.1.tgz", - "integrity": "sha512-C7bfi1UZmoj8+PQx22XyeXCuBlokoyWQL5pWSP+EI6nzRylyThouddufc2c1NDIcP9k5agmN9fLpA7VNJfIiqw==", - "dependencies": { - "uc.micro": "^1.0.1" - } - }, "node_modules/lower-case": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/lower-case/-/lower-case-2.0.2.tgz", @@ -1609,157 +1691,17 @@ "tslib": "^2.0.3" } }, - "node_modules/luxon": { - "version": "3.6.1", - "resolved": "https://registry.npmjs.org/luxon/-/luxon-3.6.1.tgz", - "integrity": "sha512-tJLxrKJhO2ukZ5z0gyjY1zPh3Rh88Ej9P7jNrZiHMUXHae1yvI2imgOZtL1TO8TW6biMMKfTtAOoEJANgtWBMQ==", - "engines": { - "node": ">=12" - } - }, - "node_modules/markdown-it": { - "version": "13.0.2", - "resolved": "https://registry.npmjs.org/markdown-it/-/markdown-it-13.0.2.tgz", - "integrity": "sha512-FtwnEuuK+2yVU7goGn/MJ0WBZMM9ZPgU9spqlFs7/A/pDIUNSOQZhUgOqYCficIuR2QaFnrt8LHqBWsbTAoI5w==", - "dependencies": { - "argparse": "^2.0.1", - "entities": "~3.0.1", - "linkify-it": "^4.0.1", - "mdurl": "^1.0.1", - "uc.micro": "^1.0.5" - }, + "node_modules/marked": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/marked/-/marked-4.3.0.tgz", + "integrity": "sha512-PRsaiG84bK+AMvxziE/lCFss8juXjNaWzVbN5tXAm4XjeaS9NAHhop+PjQxz2A9h8Q4M/xGmzP8vqNwy6JeK0A==", "bin": { - "markdown-it": "bin/markdown-it.js" - } - }, - "node_modules/markdown-it-abbr": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/markdown-it-abbr/-/markdown-it-abbr-1.0.4.tgz", - "integrity": "sha512-ZeA4Z4SaBbYysZap5iZcxKmlPL6bYA8grqhzJIHB1ikn7njnzaP8uwbtuXc4YXD5LicI4/2Xmc0VwmSiFV04gg==" - }, - "node_modules/markdown-it-attrs": { - "version": "4.3.1", - "resolved": "https://registry.npmjs.org/markdown-it-attrs/-/markdown-it-attrs-4.3.1.tgz", - "integrity": "sha512-/ko6cba+H6gdZ0DOw7BbNMZtfuJTRp9g/IrGIuz8lYc/EfnmWRpaR3CFPnNbVz0LDvF8Gf1hFGPqrQqq7De0rg==", - "engines": { - "node": ">=6" + "marked": "bin/marked.js" }, - "peerDependencies": { - "markdown-it": ">= 9.0.0" - } - }, - "node_modules/markdown-it-cjk-breaks": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/markdown-it-cjk-breaks/-/markdown-it-cjk-breaks-1.1.3.tgz", - "integrity": "sha512-/gX3LueMp+5FdUkqcFPK5nHI6t85uq1rMv8yhrmCOZhU90XqybQj8OT1hVrxrdseajaHLPBK43xLzEKPosgTDA==", - "dependencies": { - "eastasianwidth": "~0.2.0" - } - }, - "node_modules/markdown-it-container": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/markdown-it-container/-/markdown-it-container-3.0.0.tgz", - "integrity": "sha512-y6oKTq4BB9OQuY/KLfk/O3ysFhB3IMYoIWhGJEidXt1NQFocFK2sA2t0NYZAMyMShAGL6x5OPIbrmXPIqaN9rw==" - }, - "node_modules/markdown-it-deflist": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/markdown-it-deflist/-/markdown-it-deflist-2.1.0.tgz", - "integrity": "sha512-3OuqoRUlSxJiuQYu0cWTLHNhhq2xtoSFqsZK8plANg91+RJQU1ziQ6lA2LzmFAEes18uPBsHZpcX6We5l76Nzg==" - }, - "node_modules/markdown-it-emoji": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/markdown-it-emoji/-/markdown-it-emoji-2.0.2.tgz", - "integrity": "sha512-zLftSaNrKuYl0kR5zm4gxXjHaOI3FAOEaloKmRA5hijmJZvSjmxcokOLlzycb/HXlUFWzXqpIEoyEMCE4i9MvQ==" - }, - "node_modules/markdown-it-footnote": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/markdown-it-footnote/-/markdown-it-footnote-3.0.3.tgz", - "integrity": "sha512-YZMSuCGVZAjzKMn+xqIco9d1cLGxbELHZ9do/TSYVzraooV8ypsppKNmUJ0fVH5ljkCInQAtFpm8Rb3eXSrt5w==" - }, - "node_modules/markdown-it-ins": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/markdown-it-ins/-/markdown-it-ins-3.0.1.tgz", - "integrity": "sha512-32SSfZqSzqyAmmQ4SHvhxbFqSzPDqsZgMHDwxqPzp+v+t8RsmqsBZRG+RfRQskJko9PfKC2/oxyOs4Yg/CfiRw==" - }, - "node_modules/markdown-it-katex": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/markdown-it-katex/-/markdown-it-katex-2.0.3.tgz", - "integrity": "sha512-nUkkMtRWeg7OpdflamflE/Ho/pWl64Lk9wNBKOmaj33XkQdumhXAIYhI0WO03GeiycPCsxbmX536V5NEXpC3Ng==", - "dependencies": { - "katex": "^0.6.0" - } - }, - "node_modules/markdown-it-katex/node_modules/katex": { - "version": "0.6.0", - "resolved": "https://registry.npmjs.org/katex/-/katex-0.6.0.tgz", - "integrity": "sha512-rS4mY3SvHYg5LtQV6RBcK0if7ur6plyEukAOV+jGGPqFImuzu8fHL6M752iBmRGoUyF0bhZbAPoezehn7xYksA==", - "dependencies": { - "match-at": "^0.1.0" - }, - "bin": { - "katex": "cli.js" - } - }, - "node_modules/markdown-it-mark": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/markdown-it-mark/-/markdown-it-mark-3.0.1.tgz", - "integrity": "sha512-HyxjAu6BRsdt6Xcv6TKVQnkz/E70TdGXEFHRYBGLncRE9lBFwDNLVtFojKxjJWgJ+5XxUwLaHXy+2sGBbDn+4A==" - }, - "node_modules/markdown-it-sub": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/markdown-it-sub/-/markdown-it-sub-1.0.0.tgz", - "integrity": "sha512-z2Rm/LzEE1wzwTSDrI+FlPEveAAbgdAdPhdWarq/ZGJrGW/uCQbKAnhoCsE4hAbc3SEym26+W2z/VQB0cQiA9Q==" - }, - "node_modules/markdown-it-sup": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/markdown-it-sup/-/markdown-it-sup-1.0.0.tgz", - "integrity": "sha512-E32m0nV9iyhRR7CrhnzL5msqic7rL1juWre6TQNxsnApg7Uf+F97JOKxUijg5YwXz86lZ0mqfOnutoryyNdntQ==" - }, - "node_modules/markdown-it/node_modules/entities": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/entities/-/entities-3.0.1.tgz", - "integrity": "sha512-WiyBqoomrwMdFG1e0kqvASYfnlb0lp8M5o5Fw2OFq1hNZxxcNk8Ik0Xm7LxzBhuidnZB/UtBqVCgUz3kBOP51Q==", "engines": { - "node": ">=0.12" - }, - "funding": { - "url": "https://github.com/fb55/entities?sponsor=1" - } - }, - "node_modules/match-at": { - "version": "0.1.1", - "resolved": "https://registry.npmjs.org/match-at/-/match-at-0.1.1.tgz", - "integrity": "sha512-h4Yd392z9mST+dzc+yjuybOGFNOZjmXIPKWjxBd1Bb23r4SmDOsk2NYCU2BMUBGbSpZqwVsZYNq26QS3xfaT3Q==" - }, - "node_modules/math-intrinsics": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz", - "integrity": "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==", - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/mathjax-full": { - "version": "3.2.2", - "resolved": "https://registry.npmjs.org/mathjax-full/-/mathjax-full-3.2.2.tgz", - "integrity": "sha512-+LfG9Fik+OuI8SLwsiR02IVdjcnRCy5MufYLi0C3TdMT56L/pjB0alMVGgoWJF8pN9Rc7FESycZB9BMNWIid5w==", - "dependencies": { - "esm": "^3.2.25", - "mhchemparser": "^4.1.0", - "mj-context-menu": "^0.6.1", - "speech-rule-engine": "^4.0.6" + "node": ">= 12" } }, - "node_modules/mdurl": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/mdurl/-/mdurl-1.0.1.tgz", - "integrity": "sha512-/sKlQJCBYVY9Ers9hqzKou4H6V5UWc/M59TH2dvkt+84itfnq7uFOMLpOiOS4ujvHP4etln18fmIxA5R5fll0g==" - }, - "node_modules/mhchemparser": { - "version": "4.2.1", - "resolved": "https://registry.npmjs.org/mhchemparser/-/mhchemparser-4.2.1.tgz", - "integrity": "sha512-kYmyrCirqJf3zZ9t/0wGgRZ4/ZJw//VwaRVGA75C4nhE60vtnIzhl9J9ndkX/h6hxSN7pjg/cE0VxbnNM+bnDQ==" - }, "node_modules/micro-memoize": { "version": "4.1.2", "resolved": "https://registry.npmjs.org/micro-memoize/-/micro-memoize-4.1.2.tgz", @@ -1834,11 +1776,6 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/mj-context-menu": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/mj-context-menu/-/mj-context-menu-0.6.1.tgz", - "integrity": "sha512-7NO5s6n10TIV96d4g2uDpG7ZDpIhMh0QNfGdJw/W47JswFcosz457wqz/b5sAKvl12sxINGFCn80NZHKwxQEXA==" - }, "node_modules/moize": { "version": "6.1.6", "resolved": "https://registry.npmjs.org/moize/-/moize-6.1.6.tgz", @@ -1947,6 +1884,11 @@ } } }, + "node_modules/nwsapi": { + "version": "2.2.9", + "resolved": "https://registry.npmjs.org/nwsapi/-/nwsapi-2.2.9.tgz", + "integrity": "sha512-2f3F0SEEer8bBu0dsNCFF50N0cTThV1nWFYcEYFZttdW0lDAoybv9cQoK7X7/68Z89S7FoRrVjP1LPX4XRf9vg==" + }, "node_modules/object-assign": { "version": "4.1.1", "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", @@ -1998,6 +1940,17 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/parse5": { + "version": "7.1.2", + "resolved": "https://registry.npmjs.org/parse5/-/parse5-7.1.2.tgz", + "integrity": "sha512-Czj1WaSVpaoj0wbhMzLmWD69anp2WH7FXMB9n1Sy8/ZFF9jolSQVMu1Ij5WIyGmcBmhk7EOndpO4mIpihVqAXw==", + "dependencies": { + "entities": "^4.4.0" + }, + "funding": { + "url": "https://github.com/inikulin/parse5?sponsor=1" + } + }, "node_modules/parseurl": { "version": "1.3.3", "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz", @@ -2076,6 +2029,11 @@ "asap": "~2.0.3" } }, + "node_modules/psl": { + "version": "1.9.0", + "resolved": "https://registry.npmjs.org/psl/-/psl-1.9.0.tgz", + "integrity": "sha512-E/ZsdU4HLs/68gYzgGTkMicWTLPdAftJLfJFlLUAAKZGkStNU72sZjT66SnMDVOfOWY/YAoiD7Jxa9iHvngcag==" + }, "node_modules/pug": { "version": "3.0.2", "resolved": "https://registry.npmjs.org/pug/-/pug-3.0.2.tgz", @@ -2188,6 +2146,19 @@ "resolved": "https://registry.npmjs.org/pug-walk/-/pug-walk-2.0.0.tgz", "integrity": "sha512-yYELe9Q5q9IQhuvqsZNwA5hfPkMJ8u92bQLIMcsMxf/VADjNtEYptU+inlufAFYcWdHlwNfZOEnOOQrZrcyJCQ==" }, + "node_modules/punycode": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz", + "integrity": "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==", + "engines": { + "node": ">=6" + } + }, + "node_modules/querystringify": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/querystringify/-/querystringify-2.2.0.tgz", + "integrity": "sha512-FIqgj2EUvTa7R50u0rGsyTftzjYmv/a3hO345bZNrqabNqjtgiDMgmo4mkUjd+nzU5oF3dClKqFIPUKybUyqoQ==" + }, "node_modules/range-parser": { "version": "1.2.1", "resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.1.tgz", @@ -2220,6 +2191,11 @@ "node": ">=8.10.0" } }, + "node_modules/requires-port": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/requires-port/-/requires-port-1.0.0.tgz", + "integrity": "sha512-KigOCHcocU3XODJxsu8i/j8T9tzT4adHiecwORRQ0ZZFcp7ahwXuRU1m+yuO90C5ZUyGeGfocHDI14M3L3yDAQ==" + }, "node_modules/resolve": { "version": "1.22.8", "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.8.tgz", @@ -2246,11 +2222,27 @@ "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==" }, + "node_modules/safer-buffer": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", + "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==" + }, "node_modules/sax": { "version": "1.3.0", "resolved": "https://registry.npmjs.org/sax/-/sax-1.3.0.tgz", "integrity": "sha512-0s+oAmw9zLl1V1cS9BtZN7JAd0cW5e0QH4W3LWEK6a4LaLEA2OTpGYWDY+6XasBLtz6wkm3u1xRw95mRuJ59WA==" }, + "node_modules/saxes": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/saxes/-/saxes-6.0.0.tgz", + "integrity": "sha512-xAg7SOnEhrm5zI3puOOKyy1OMcMlIJZYNJY7xLBwSze0UjhPLnWfj2GF2EpT0jmzaJKIWKHLsaSSajf35bcYnA==", + "dependencies": { + "xmlchars": "^2.2.0" + }, + "engines": { + "node": ">=v12.22.7" + } + }, "node_modules/send": { "version": "0.18.0", "resolved": "https://registry.npmjs.org/send/-/send-0.18.0.tgz", @@ -2363,25 +2355,13 @@ "node": ">=8" } }, - "node_modules/speech-rule-engine": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/speech-rule-engine/-/speech-rule-engine-4.1.2.tgz", - "integrity": "sha512-S6ji+flMEga+1QU79NDbwZ8Ivf0S/MpupQQiIC0rTpU/ZTKgcajijJJb1OcByBQDjrXCN1/DJtGz4ZJeBMPGJw==", - "dependencies": { - "@xmldom/xmldom": "0.9.8", - "commander": "13.1.0", - "wicked-good-xpath": "1.3.0" - }, - "bin": { - "sre": "bin/sre" - } - }, - "node_modules/speech-rule-engine/node_modules/commander": { - "version": "13.1.0", - "resolved": "https://registry.npmjs.org/commander/-/commander-13.1.0.tgz", - "integrity": "sha512-/rFeCpNJQbhSZjGVwO9RFV3xPqbnERS8MmIQzCtD/zl6gpJuV/bMLuN92oG3F7d8oDEHHRrujSXNUr8fpjntKw==", + "node_modules/source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "optional": true, "engines": { - "node": ">=18" + "node": ">=0.10.0" } }, "node_modules/sprintf-js": { @@ -2518,6 +2498,11 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/symbol-tree": { + "version": "3.2.4", + "resolved": "https://registry.npmjs.org/symbol-tree/-/symbol-tree-3.2.4.tgz", + "integrity": "sha512-9QNk5KwDF+Bvz+PyObkmSYjI5ksVUYtjW7AU22r2NKcfLJcXp96hkDWU3+XndOsUb+AQ9QhfzfCT2O+CNWT5Tw==" + }, "node_modules/text-table": { "version": "0.2.0", "resolved": "https://registry.npmjs.org/text-table/-/text-table-0.2.0.tgz", @@ -2579,15 +2564,43 @@ "resolved": "https://registry.npmjs.org/token-stream/-/token-stream-1.0.0.tgz", "integrity": "sha512-VSsyNPPW74RpHwR8Fc21uubwHY7wMDeJLys2IX5zJNih+OnAnaifKHo+1LHT7DAdloQ7apeaaWg8l7qnf/TnEg==" }, + "node_modules/tough-cookie": { + "version": "4.1.4", + "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-4.1.4.tgz", + "integrity": "sha512-Loo5UUvLD9ScZ6jh8beX1T6sO1w2/MpCRpEP7V280GKMVUQ0Jzar2U3UJPsrdbziLEMMhu3Ujnq//rhiFuIeag==", + "dependencies": { + "psl": "^1.1.33", + "punycode": "^2.1.1", + "universalify": "^0.2.0", + "url-parse": "^1.5.3" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/tr46": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/tr46/-/tr46-3.0.0.tgz", + "integrity": "sha512-l7FvfAHlcmulp8kr+flpQZmVwtu7nfRV7NZujtN0OqES8EL4O4e0qqzL0DC5gAvx/ZC/9lk6rhcUwYvkBnBnYA==", + "dependencies": { + "punycode": "^2.1.1" + }, + "engines": { + "node": ">=12" + } + }, "node_modules/tslib": { "version": "2.6.2", "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.2.tgz", "integrity": "sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q==" }, - "node_modules/uc.micro": { - "version": "1.0.6", - "resolved": "https://registry.npmjs.org/uc.micro/-/uc.micro-1.0.6.tgz", - "integrity": "sha512-8Y75pvTYkLJW2hWQHXxoqRgV7qb9B+9vFEtidML+7koHUFapnVJAZ6cKs+Qjz5Aw3aZWHMC6u0wJE3At+nSGwA==" + "node_modules/universalify": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/universalify/-/universalify-0.2.0.tgz", + "integrity": "sha512-CJ1QgKmNg3CwvAv/kOFmtnEN05f0D/cn9QntgNOQlQF9dgvVTHj3t+8JPdjqawCHk7V/KA+fbUqzZ9XWhcqPUg==", + "engines": { + "node": ">= 4.0.0" + } }, "node_modules/unpipe": { "version": "1.0.0", @@ -2597,6 +2610,15 @@ "node": ">= 0.8" } }, + "node_modules/url-parse": { + "version": "1.5.10", + "resolved": "https://registry.npmjs.org/url-parse/-/url-parse-1.5.10.tgz", + "integrity": "sha512-WypcfiRhfeUP9vvF0j6rw0J3hrWrw6iZv3+22h6iRMJ/8z1Tj6XfLP4DsUix5MhMPnXpiHDoKyoZ/bdCkwBCiQ==", + "dependencies": { + "querystringify": "^2.1.1", + "requires-port": "^1.0.0" + } + }, "node_modules/util-deprecate": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", @@ -2626,6 +2648,17 @@ "node": ">=0.10.0" } }, + "node_modules/w3c-xmlserializer": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/w3c-xmlserializer/-/w3c-xmlserializer-4.0.0.tgz", + "integrity": "sha512-d+BFHzbiCx6zGfz0HyQ6Rg69w9k19nviJspaj4yNscGjrHu94sVP+aRm75yEbCh+r2/yR+7q6hux9LVtbuTGBw==", + "dependencies": { + "xml-name-validator": "^4.0.0" + }, + "engines": { + "node": ">=14" + } + }, "node_modules/warehouse": { "version": "5.0.1", "resolved": "https://registry.npmjs.org/warehouse/-/warehouse-5.0.1.tgz", @@ -2644,6 +2677,45 @@ "node": ">=14" } }, + "node_modules/webidl-conversions": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-7.0.0.tgz", + "integrity": "sha512-VwddBukDzu71offAQR975unBIGqfKZpM+8ZX6ySk8nYhVoo5CYaZyzt3YBvYtRtO+aoGlqxPg/B87NGVZ/fu6g==", + "engines": { + "node": ">=12" + } + }, + "node_modules/whatwg-encoding": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/whatwg-encoding/-/whatwg-encoding-2.0.0.tgz", + "integrity": "sha512-p41ogyeMUrw3jWclHWTQg1k05DSVXPLcVxRTYsXUk+ZooOCZLcoYgPZ/HL/D/N+uQPOtcp1me1WhBEaX02mhWg==", + "dependencies": { + "iconv-lite": "0.6.3" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/whatwg-mimetype": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/whatwg-mimetype/-/whatwg-mimetype-3.0.0.tgz", + "integrity": "sha512-nt+N2dzIutVRxARx1nghPKGv1xHikU7HKdfafKkLNLindmPU/ch3U31NOCGGA/dmPcmb1VlofO0vnKAcsm0o/Q==", + "engines": { + "node": ">=12" + } + }, + "node_modules/whatwg-url": { + "version": "11.0.0", + "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-11.0.0.tgz", + "integrity": "sha512-RKT8HExMpoYx4igMiVMY83lN6UeITKJlBQ+vR/8ZJ8OCdSiN3RwCq+9gH0+Xzj0+5IrM6i4j/6LuvzbZIQgEcQ==", + "dependencies": { + "tr46": "^3.0.0", + "webidl-conversions": "^7.0.0" + }, + "engines": { + "node": ">=12" + } + }, "node_modules/which": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", @@ -2658,11 +2730,6 @@ "node": ">= 8" } }, - "node_modules/wicked-good-xpath": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/wicked-good-xpath/-/wicked-good-xpath-1.3.0.tgz", - "integrity": "sha512-Gd9+TUn5nXdwj/hFsPVx5cuHHiF5Bwuc30jZ4+ronF1qHK5O7HD0sgmXWSEgwKquT3ClLoKPVbO6qGwVwLzvAw==" - }, "node_modules/with": { "version": "7.0.2", "resolved": "https://registry.npmjs.org/with/-/with-7.0.2.tgz", @@ -2681,6 +2748,39 @@ "version": "1.0.2", "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==" + }, + "node_modules/ws": { + "version": "8.17.0", + "resolved": "https://registry.npmjs.org/ws/-/ws-8.17.0.tgz", + "integrity": "sha512-uJq6108EgZMAl20KagGkzCKfMEjxmKvZHG7Tlq0Z6nOky7YF7aq4mOx6xK8TJ/i1LeK4Qus7INktacctDgY8Ow==", + "engines": { + "node": ">=10.0.0" + }, + "peerDependencies": { + "bufferutil": "^4.0.1", + "utf-8-validate": ">=5.0.2" + }, + "peerDependenciesMeta": { + "bufferutil": { + "optional": true + }, + "utf-8-validate": { + "optional": true + } + } + }, + "node_modules/xml-name-validator": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/xml-name-validator/-/xml-name-validator-4.0.0.tgz", + "integrity": "sha512-ICP2e+jsHvAj2E2lIHxa5tjXRlKDJo4IdvPvCXbXQGdzSfmSpNVyIKMvoZHjDY9DP0zV17iI85o90vRFXNccRw==", + "engines": { + "node": ">=12" + } + }, + "node_modules/xmlchars": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/xmlchars/-/xmlchars-2.2.0.tgz", + "integrity": "sha512-JZnDKK8B0RCDw84FNdDAIpZK+JuJw+s7Lz8nksI7SIuU3UXJJslUthsi+uWBUYOwPFwW7W7PRLRfUKpxjtjFCw==" } } } diff --git a/package.json b/package.json index f85d8e7..5bb4a67 100644 --- a/package.json +++ b/package.json @@ -12,26 +12,17 @@ "version": "7.2.0" }, "dependencies": { - "@neilsustc/markdown-it-katex": "^1.0.0", - "@renbaoshuo/markdown-it-katex": "^2.0.2", - "hexo": "^7.0.0", - "hexo-blog-encrypt": "^3.1.9", - "hexo-deployer-git": "^4.0.0", - "hexo-filter-mathjax": "^0.9.0", + "hexo": "^7.3.0", "hexo-generator-archive": "^2.0.0", "hexo-generator-category": "^2.0.0", "hexo-generator-index": "^3.0.0", "hexo-generator-search": "^2.4.3", "hexo-generator-tag": "^2.0.0", "hexo-renderer-ejs": "^2.0.0", - "hexo-renderer-markdown-it": "^7.1.1", - "hexo-renderer-pug": "^3.0.0", - "hexo-renderer-stylus": "^3.0.1", + "hexo-renderer-marked": "^6.0.0", + "hexo-renderer-stylus": "^3.0.0", "hexo-server": "^3.0.0", "hexo-theme-butterfly": "^4.13.0", - "hexo-theme-landscape": "^1.0.0", - "hexo-wordcount": "^6.0.1", - "katex": "^0.16.22", - "markdown-it-katex": "^2.0.3" + "hexo-theme-landscape": "^1.0.0" } } diff --git a/post b/post deleted file mode 100755 index c0fe8e5..0000000 --- a/post +++ /dev/null @@ -1,2 +0,0 @@ -#!/bin/bash -python3 /Users/zip95297/Repository/BLOG/zjb-blog/utils/post.py \ No newline at end of file diff --git a/source/_data/link.yml b/source/_data/link.yml deleted file mode 100644 index 2c5675f..0000000 --- a/source/_data/link.yml +++ /dev/null @@ -1,35 +0,0 @@ -- class_name: 友情链接 - class_desc: PYQ 属于是 - link_list: - - name: Hershel - link: https://hershel1215.github.io/ - avatar: https://s2.loli.net/2024/12/05/Uke9q1Y2tLjh8EP.jpg - descr: 贺博的博客 - - name: Yintel - link: https://blog.yintel.top/ - avatar: https://blog.yintel.top/img/avatar.png - descr: 尹神的博客 - - name: gugubird-sb - link: https://gugubird13.github.io/ - avatar: https://i.loli.net/2021/02/24/5O1day2nriDzjSu.png - descr: 打瓦喜欢被我带的博客 - - -- class_name: 网站 - class_desc: 常用的工具网站 - link_list: - - name: 公式 to Latex - link: https://simpletex.cn/ai/latex_ocr - avatar: https://simpletex.cn/img/logo.3a879d25.png - descr: 很好用的公式转latex网站 - - - name: 伴奏下载 - link: http://search.5sing.kugou.com/?type=bz - avatar: - descr: 免费伴奏下载网站,如果不可下载可以调试网页->网络监控,找到mp3文件url然后wget - - - name: 升降调&变奏 - link: https://vocalremover.org/zh/pitch - avatar: - descr: 歌曲升降调变奏 - diff --git a/source/_posts/Aligning Cyber Space with Physical World A Comprehensive Survey on Embodied AI.md b/source/_posts/Aligning Cyber Space with Physical World A Comprehensive Survey on Embodied AI.md deleted file mode 100644 index 7d3ca8c..0000000 --- a/source/_posts/Aligning Cyber Space with Physical World A Comprehensive Survey on Embodied AI.md +++ /dev/null @@ -1,895 +0,0 @@ ---- -title: "Aligning Cyber Space with Physical World A Comprehensive Survey on Embodied AI" -date: 2025-09-12 04:25:20 -updated: 2025-09-28 17:56:39 -mathjax: true -categories: 深度学习 -tags: - - 深度学习 - - 具身智能 - - 论文阅读 - - 综述 -description: 阅读具身智能的一篇综述,记录一下文章主要内容 -comments: true ---- -这是一篇Embodied AI的综述文章,介绍了Embodied AI的几个研究方向以及现状。 - -# 背景介绍 - -在计算机空间Cyber-Space中的智能体,被称为非具身智能体,而在物理世界中的智能体则称之为具身智能。 - -Embodied AI对AGI的实现很重要,是链接计算机世界与物理世界之间的各种应用的基础。 -目前多模态大模型(MLM)和世界模型(WM)使具身模型具有更强大的感知、交互以及规划能力。这些模型表现出卓越的模拟能力和对物理定律的良好理解,这使得具体模型能够全面理解物理和真实环境。具身智能也被认为是这些模型的最佳载体。 - -具身智能最初是由 Alan Turing 于 1950 年提出的 Embodied Turing Test,该测试旨在确定具身代理是否能够显示不仅限于解决计算机空间,而且还有物理世界中的复杂性和不可预测性问题。 - - -具身智能和非具身智能的对比如下: - -| 类型 | 所在环境 | 物理实体 | 描述 | 代表 | -| ----- | ---------- | -------- | --------- | --------- | -| 非具身AI | CyberSpace | 无 | 认知与物理实体分离 | ChatGPT | -| 具身AI | 物理空间 | 机器人,车... | 认知融入于物理实体 | 谷歌机器人RT-1 | - -为了实现AGI,具身智能是一个基本途径。Embodied AI其具有控制物理实体,可以和模拟或者物理环境交互。(这点和AGI相同) - -基于MLMs和WMs的具身智能的总体框架。具身智能使用具身世界模型作为它的“大脑”。它具有理解虚拟物理环境并主动感知多模态元素的能力。它可以充分理解人类意图,符合人类价值和事件因果关系,分解复杂的任务并执行可靠的行动,以及与人类交互并利用知识和工具。如图: -![](https://cdn.jsdelivr.net/gh/zip95297/zip95297.github.io@main/source/images/Aligning%20Cyber%20Space%20with%20Physical%20World%20%20A%20%20Comprehensive%20Survey%20on%20Embodied%20AI/Pasted%20image%2020241024155615.webp?raw=true) - -具身智能包含的不同研究方向: -1. 具身机器人 -2. 模拟器 --下面四个是任务: -3. 具身感知(视觉主动感知)embodied perception -4. 具身交互 embodied interaction -5. 多模态(具身)智能体 embodied agents -6. 模拟到现实的机器人控制 sim-to-real robotic control -![](https://cdn.jsdelivr.net/gh/zip95297/zip95297.github.io@main/source/images/Aligning%20Cyber%20Space%20with%20Physical%20World%20%20A%20%20Comprehensive%20Survey%20on%20Embodied%20AI/Pasted%20image%2020241024193939.webp?raw=true) - -# 下面对各个方向进行介绍: - -## 具身机器人 Embodied Robots - -能与环境主动交互的实体类型有很多,例如 机器人、智能家电、智能眼镜、自动驾驶汽车等等。其中机器人是最有代表性的实体。 - -机器人可以被设计成各种形式,来完成特定任务。例如 定基机器人、轮式、履带式、四足、人形、仿生机器人等等。 - -### 定基机器人 Fixed-based Robots - -定基机器人具有紧凑性和操作精度、稳定性高的特点,一般配备高精度传感器和执行器,可以使其实现微米级操作精度。可编程,用户可以通过编程可以使其适用于各种任务场景。 - -但是固定的底座设计限制了他们的操作范围和灵活性,难以在大范围内移动或者调整位置、以及与人类或者其他机器人合作。 - -### 轮式、履带式机器人 - -受益于其常规的移动方式,这些机器人可以面对更复杂多样的场景。 - -#### 轮式机器人 Wheeled Robots - -具有高效的移动性(在平面上)、结构简单、成本相对较低、能源效率高的优点,广泛应用于物流、仓储、安检等等领域。 - -这些机器人常常配备激光雷达和摄像头等高精度传感器,可以实现自主导航和环境感知。例如kiva、jackal机器人。 - -但是在复杂地形或者恶劣环境中,移动性有限,负载能力和机动性也会受到一定限制。 - -#### 履带式机器人 Tracked Robots - -履带式机器人在非平面上的机动性更强,有强大的越野能力。履带与地面接触面积更大,分散重量,防止在松软地形下沉。应用于恶劣、特殊环境下的任务:农业、建筑、灾难恢复、军事。 - -由于履带系统的高摩擦力,履带式机器人能源效率较低。此外,平面上的移动速度比轮式机器人慢,灵活性和可操作性也较差。 - -### 四足机器人 - -稳定性和适应性较强,可以通过雷达或者摄像头来进行环境感知。甚至可以主动导航避开障碍物。 - -设计复杂而且制造成本很高,限制在成本敏感领域的使用。此外,在复杂环境下的电池续航能力有限,需要频繁充电或更换电池才能长时间运行。 - -### 人形机器人 - -手部设计使它们能够执行复杂的任务具有多个自由度和高精度传感器,使它们能够模仿人手的抓取和操纵能力,这在医疗手术和精密制造等领域尤其重要。可以行走、跑步、爬楼梯、识别面孔和手势,适合接待和引导服务,可以识别情绪并进行自然语言交流,广泛应用于客户服务和教育环境中。 - -但是,由于其复杂的控制系统,人形机器人在复杂环境下保持运行稳定性和可靠性面临挑战。 - -### 仿生机器人 - -通过模拟自然生物体的高效运动和功能,在复杂和动态的环境中执行任务。在医疗保健、环境监测和生物研究等领域展现出巨大的潜力。仿生设计可以通过模仿生物有机体的高效运动机制来显着提高机器人的能源效率,使它们在能源消耗方面更加经济。 - -但是,其设计和制造工艺复杂且成本高昂,限制了大规模生产和广泛应用。其次,由于采用柔性材料和复杂的运动机制,仿生机器人在极端环境下的耐用性和可靠性受到限制。 - -## 具身模拟器 Embodied Simulators - -为了使智能体能够与环境交互,需要构建一个真实的模拟环境。这需要考虑环境的物理特征、物体的属性及其相互作用。具身模拟器,提供了高效经济的实验环境。可以为具身智能提供不同的测试环境,模拟潜在危险场景。 - -下面介绍两种模拟器:基于底层模拟的通用模拟器和基于现实场景的模拟器。 - -### 基于底层仿真的通用模拟器 general simulator based on underlying simulation - -在物理世界中的 物理交互 和 动态变化 是 不可替代的,但是在物理世界直接部署具身模型进行训练或者测试的成本太高。而通用模拟器提供一个几乎仿真(物理世界)的虚拟环境。可以在其中进行算法开发、模型训练。在成本、时间、安全上都具有一定优势。本文介绍了3种常见的通用模拟器:Isaac Sim、Gazebo、PyBullet. -#### Isaac Sim - -isaac sim是NVDIA为机器人和AI研究提供的先进的仿真平台,具有高保真物理模拟、实时光线追踪、广泛的机器人模型库和深度学习支持。应用场景包括自动驾驶、工业自动化和人机交互。 -#### Gazebo - -gazebo是一个用于机器人研究的开源模拟器,支持各种传感器的仿真,拥有丰富的机器人库,主要用于机器人导航与控制以及多机器人系统。 -#### PyBullet - -PyBullet是Bullet 物理引擎的python接口,易于使用,具有多种传感器模拟和深度学习集成。支持实时物理模拟,包括刚体动力学、碰撞检测和约束求解 - -#### 各种仿真平台 - -下图介绍了10个通用模拟器的主要特点和主要应用场景。它们各自在具体人工智能领域提供独特的优势。 -综合 - HFPS:高保真物理模拟; - HQGR:高质量图形渲染; -RRL:丰富的机器人库; -DLS:深度学习支持; -LSPC:大规模并行计算; -ROS:与 ROS 紧密集成; -MSS:多传感器模拟; -CP:跨平台; -NAV:机器人导航; -AD:自动驾驶; -RL:强化学习; -LSPS:大规模并行; -SIM MR:多机器人系统; -RS:机器人仿真 -![](https://cdn.jsdelivr.net/gh/zip95297/zip95297.github.io@main/source/images/Aligning%20Cyber%20Space%20with%20Physical%20World%20%20A%20%20Comprehensive%20Survey%20on%20Embodied%20AI/Pasted%20image%2020241025021829.webp?raw=true) -各种仿真平台的可视化效果如下: -![](https://cdn.jsdelivr.net/gh/zip95297/zip95297.github.io@main/source/images/Aligning%20Cyber%20Space%20with%20Physical%20World%20%20A%20%20Comprehensive%20Survey%20on%20Embodied%20AI/Pasted%20image%2020241025022108.webp?raw=true) - -### 基于现实场景的模拟器 the simulator based on real scenes - -在**家庭活动**中实现通用的具身智能体一直是具身人工智能研究领域的主要焦点。这些实体代理需要深入了解人类的日常生活并执行复杂的实体任务,例如室内环境中的导航和交互。 - -基于这种特定的复杂任务场景,模拟器的需要尽可能接近现实世界。这一要求对模拟器的复杂性和真实性有很高的要求。这些模拟器主要从现实世界收集数据,创建逼真的 3D 资源,并使用 UE5 和 Unity 等 3D 游戏引擎构建场景。这些模拟器的侧重点各不相同,有的具有丰富的交互场景对象以及分配给它们的物理属性(例如打开/关闭甚至冷/热),有的支持多智能体模拟。来自开放资源或定制的多个实体代理(例如人类和机器狗)可以在模拟器中合作,自由移动,并与场景进行简单的交互。有的甚至实现刚体、软体、织物、流体等多种材质的物理交互模拟,并提供与物体交互时的情境声音。可以支持用户自定义环境进行训练。 - -各种现实场景模拟器的条件: -![](https://cdn.jsdelivr.net/gh/zip95297/zip95297.github.io@main/source/images/Aligning%20Cyber%20Space%20with%20Physical%20World%20%20A%20%20Comprehensive%20Survey%20on%20Embodied%20AI/Pasted%20image%2020241025022601.webp?raw=true) -现实场景模拟器的可视化场景: -![](https://cdn.jsdelivr.net/gh/zip95297/zip95297.github.io@main/source/images/Aligning%20Cyber%20Space%20with%20Physical%20World%20%20A%20%20Comprehensive%20Survey%20on%20Embodied%20AI/Pasted%20image%2020241025022653.webp?raw=true) - -## 具身感知 Embodied Perception - -未来视觉感知的启明星是以具身为中心的视觉推理和社交智能,即具身感知任务。这种任务和仅仅识别图像中的物体不同,具身感知要求Agent可以在物理世界中移动并且与环境进行交互,要求具身智能体对3D空间和动态的环境有更加深入的了解。具身感知需要视觉感知和推理、理解场景内的3D关系,以及基于视觉信息预测和执行复杂任务。 - -具身智能体的感知任务包含以下方向: -1. 主动视觉感知 -2. 3D视觉定位 -3. 视觉语言导航 -4. 非视觉感知-触觉 - -### 主动视觉感知 Active Visual Perception - -主动视觉感知系统状态估计、场景感知和环境探索等基本能力。这些能力在 视觉SLAM(视觉同步定位和映射,visual simultaneous localization and mapping)和3D场景理解、主动探索 任务中 被 广泛研究。 - -下面是主动视觉感知的示意图: 3D 场景理解、vSLAM 为 **被动**视觉感知 提供了基础,而主动探索可以为被动感知系统提供主动性。这三个要素相辅相成,对于主动视觉感知系统至关重要。 -![](https://cdn.jsdelivr.net/gh/zip95297/zip95297.github.io@main/source/images/Aligning%20Cyber%20Space%20with%20Physical%20World%20%20A%20%20Comprehensive%20Survey%20on%20Embodied%20AI/Pasted%20image%2020241025024259.webp?raw=true) - -这些研究领域有助于开发强大的主动视觉感知系统,促进复杂动态环境中改善环境交互和导航。而其中的 三个组成部分 的研究现状如下表: -![](https://cdn.jsdelivr.net/gh/zip95297/zip95297.github.io@main/source/images/Aligning%20Cyber%20Space%20with%20Physical%20World%20%20A%20%20Comprehensive%20Survey%20on%20Embodied%20AI/Pasted%20image%2020241025025003.webp?raw=true) - -下面大概介绍各个组成部分的主要方法。 -#### 视觉 同步定位 和 映射 vSLAM : Visual Simultaneous Localization and Mapping - -同步定位和映射(SLAM)是一种 在确定 可移动机器人在位置环境的位置 的同时 构造所在位置环境的地图的技术。 - -基于距离的SLAM通过测距仪,创建点云,但是成本高且提供的信息很少。 - -视觉SLAM 通过 机载摄像机 获取帧 然后 构建环境的表示。硬件成本低,小规模场景准确率高,可以捕获到丰富的环境信息。经典的vSLAM又可以分为 传统vSLAM 和 语义vSLAM。 - -##### 传统vSLAM :Tradition - -通过 图像信息 和 多视图几何原理 估计robot在位置环境中的姿势,来建造由点云构成的低级地图(稀疏地图、半密集地图)。 但是由于 低级地图的点云 与 环境中的物体 并不直接对应,所以robots很难 对其进行解释或者利用。 - -例子中的方法有 MonoSLAM MSCKF ORB-SLAM PTAM DTAM -##### 语义vSLAM :Semantic - -#TODO 什么是语义SLAM - -与 语义信息处理方法 集成的的 语义vSLAM系统的出现,让robots有更强对未知环境的感知能力。 - -早期的方法有SLAM++,通过 实时的3D目标识别与追踪 来创建高效的对象图,从而实现在复杂环境的重定位和目标检测功能。 -CubeSLAM和HDP-SLAM将3D矩形引入地图来构建轻量语义图。 - QuadricSLAM 采用语义 3D 椭球体来实现复杂几何环境中物体形状和姿势的精确建模。 - So-SLAM 在室内环境中纳入了完全耦合的空间结构约束(共面性、共线性和邻近性)。 - 为了应对动态环境的挑战,DS-SLAM、DynaSLAM 和SG-SLAM 采用语义分割进行运动一致性检查,并采用多视图几何算法来识别和过滤动态对象,确保稳定的定位和建图。 - OVD-SLAM 利用语义、深度和光流信息来区分没有预定义标签的动态区域,实现更准确和鲁棒的定位。 - GSSLAM 利用 3D 高斯表示,通过实时可微分泼溅渲染管道和自适应扩展策略来平衡效率和准确性。 - -#### 3D场景理解 3D Scence Understanding - -#TODO 主要方法的插图 - -3D场景理解旨在区分对象的语义,识别其位置,并从3D场景数据中推断几何属性,这是自动驾驶、机器人导航和人机交互的基础等。可以使用 LiDAR 或 RGB-D 传感器等 3D 扫描工具将场景记录为 3D 点云。与图像不同,点云稀疏、无序且不规则,使得场景解释很有难度。 - -近年所提出的基于深度学习理解3D场景方法,分为:基于投影(projection)的方法、基于体素(voxel)的方法、基于点(point)的方法。这三种方法是对点云的不同处理方式、角度。 -基于投影的方法(MV3D、PointPillars、MVCNN)将3D点投影到各种图像平面上,然后采用CNN的2D主干进行特征提取。 -基于voxel的方法将点云转化为规则的voxel网格以促进3D卷积运算(例如VoxNet,SSCNet),还有一些通过稀疏卷积提高效率。 -基于点的方法直接处理点云(PointNet、PointNet++、PointMLP)。或者为了模型的可拓展性有基于Transformer或者Mamba的架构。 - -#### 主动探索 Active Exploration - -上面提到的3D场景理解方法,赋予了robots被动感知环境的能力,但是感知系统的信息获取和决策不适应不断变化的场景。 - -被动的感知方式为为robots对环境的理解提供了基础,但是这种理解是从特定(或者说给定)的角度。有一定的局限性。 - -但是由于robots可以移动以及与环境进行互动,结合这一特点,使robots获得主动感知探索环境的能力,通过与环境交互、通过观察环境的视角获取更多信息,使其加深对环境的理解能力。这也是近些方法的做法。 - -##### 例如: -- 有人提出了一种好奇的机器人,它通过与环境的物理交互来学习视觉表示,而不是仅仅依赖数据集中的类别标签。为了解决具有不同形态的机器人之间的交互式对象感知的挑战。 -- 提出了一种多阶段投影框架,通过学习的探索性交互传递隐式知识,使机器人能够有效地识别物体属性,而无需从头开始重新学习。 -- 认识到自主捕获信息性观察结果的挑战。 [114]提出了一种强化学习方法,其中代理通过减少其环境中未观察到的部分的不确定性来学习主动获取信息丰富的视觉观察,使用循环神经网络主动完成全景场景和 3D 对象形状。 -- 引入了一种无地图规划框架,该框架迭代地定位 RGB 相机以捕获未知场景中信息最丰富的图像,并在基于图像的神经渲染中使用新颖的不确定性估计来引导数据收集到最不确定的视图。 -- [116]开发了一种机器人探索算法,该算法使用状态值函数来预测未来状态的值,结合离线蒙特卡罗训练、在线时间差异自适应和基于传感器信息覆盖的内在奖励函数。 - -### 3D视觉定位 3D Visual Grounding - -3D VG(visual grounding)区别于2D VG,不只是在平面中考察,而是包含了对象的深度,视角以及空间关系。这类任务涉及 使用自然语言描述在3D环境中的定位对象。近期的3D VG方法大致分为两类 :2-stage和1-stage: -![](https://cdn.jsdelivr.net/gh/zip95297/zip95297.github.io@main/source/images/Aligning%20Cyber%20Space%20with%20Physical%20World%20%20A%20%20Comprehensive%20Survey%20on%20Embodied%20AI/Pasted%20image%2020241025170849.webp?raw=true) - -##### 两阶段3D VG : Two-Stage 3D Visual Grounding Methods - -类似于2D任务,3D grounding的早期研究主要通过两个阶段的检测然后匹配流程。早期现实用预训练的检测器或者分割器,从3D场景中的众多 3D目标 建议(proposal)中提取特征,然后将其与 语言查询特征融合 来匹配得到目标对象。 - -重点主要在第二阶段(语言查询的特征和3D对象proposal的特征相关性找到最佳匹配)。例如Referlt3D、TGNN不仅学习将proposal特征与文本embedding相匹配,而且还通过图神经网络对对象之间的上下文关系进行编码,增强对物体的理解。 - -最近随着transformer在NLP和CV中的出色表现,很多研究通过transformer提取融合视觉语言特征。 -LanguageRefer 基于 Transformer 的架构,结合了 3D 空间嵌入、语言描述和类标签嵌入,以实现强大的 3D 视觉定位。 -3DVG-Transformer是一种用于 3D 点云的关系感知视觉定位方法,具有用于关系增强提案生成的坐标引导上下文聚合模块和用于跨模式提案消歧的多重注意模块。 -为了实现对 3D 对象和引用表达式进行更细粒度的推理,TransRefer3D使用实体和关系感知注意力增强了跨模态特征表示,结合了自我注意力、实体感知注意力和关系感知注意力。 - -现有的 3D VG 方法通常依赖于大量标记数据进行训练,或者在处理复杂语言查询时表现出局限性。结合LLM的强大的语言理解能力: -LLM-Grounder 提出了一种不需要标记数据的开放词汇 3D VG流水线:利用LLM 分解查询 并生成 对象识别计划 ,然后评估空间和常识关系以选择最匹配的对象。 -ZSVG3D 设计了一种零样本 开放词汇 3D VG方法,该方法使用 LLM 来识别相关对象并执行推理,将此过程转换为脚本化视觉程序,然后转化为可执行的 Python 代码来预测对象位置。捕获依赖于视图的查询并理解 3D 空间中的空间关系。 - -但是 这些两阶段方法面临着确定proposal数量的困境,因为第一阶段的3D检测器需要采样关键点来表示整个3D场景并为每个关键点生成相应的proposal。 稀疏的proposal可能会忽略第一阶段的目标,从而使它们在第二阶段无法匹配。 相反,密集的proposal可能 包含 冗余 对象,由于proposal间关系过于复杂,导致第二阶段的目标区分困难。此外,关键点采样策略与语言无关,这增加了检测器识别与语言相关的proposal的难度。如图所示: -![](https://cdn.jsdelivr.net/gh/zip95297/zip95297.github.io@main/source/images/Aligning%20Cyber%20Space%20with%20Physical%20World%20%20A%20%20Comprehensive%20Survey%20on%20Embodied%20AI/Pasted%20image%2020241025173342.webp?raw=true) - -##### 一阶段3D VG : One-Stage 3D Visual Grounding Methods - -#TODO 介绍一下这个流程 - -二阶段是先 给出 proposal 然后在 对 这些proposal与给出的文字特征进行匹配。 -而一阶段3D Visual Grounding则是 通过 语言查询queries 把 特征提取 和 目标检测 集成在 -起。根据语言描述逐步选择关键点。有的方法(MDFETR,GLIP)会提取 耦合所有单词的在句子中的特征 或者 直接更关注描述中的 物品名称,但是忽略单词中的信息。 EDA提出了解耦句子中的文本属性。 - -### 视觉语言导航 Visual Language Navigation - -以视觉观察以及语言指令作为输入,使robot具有在环境中穿梭的能力。 -旨在使Agent可以 根据语言指令穿越于位置的环境中。要求agent可以理解复杂多样的视觉观察对象,以及不同粒度的语言指令。一般来说 VLN 有 视觉信息 和 语言指令 作为输入。 - -视觉信息:所经过的轨迹上的观察视频、或者 一组 时序 观察图片。 -语言指令:Agent 需要达到的目标 或者 需要完成任务。完整的目标、粗略的任务描述。 -Navigation:Embodied Agent 则通过 上面的信息,从候选动作列表中选择 一个或者一组 动作 来执行以满足要求,单个任务或者有交互的任务,公式如下:$$Action = \mathcal{M}(O,H,I)$$ -其中 $Action$ 是智能体所选择的一个或一组动作,$\mathcal{M}$ 是model, $O$ 是**当时的**观察作为视觉输入, $H$ 是历史信息 ,$I$ 则是语言指令。 - -在VLN任务中,成功率(Success Rate,性能)、轨迹长度(Trajectory Length,效率)、成功每单位路径 SPL(Success Weighted by Path Length,综合指标). - -#### 用到的数据集 - -由于语言指令的 详细程度不一,要求的Agent完成的任务有无交互动作、顺序要求,对VLN有不同的挑战。基于这些差异有以下数据集: -- Room to Room ,基于Matterport3D,代理根据分步指令进行导航,根据视觉观察选择下一个相邻的导航图节点前进,直到到达目标位置。代理需要动态跟踪进度,以使导航过程与细粒度指令保持一致。 -- Room for Room,将R2R中的路径延伸到更长的轨迹,这需要实体代理更强的远程指令和历史对齐能力。 -- VLN-CE,将R2R和R4R扩展到连续环境,体现的智能体可以在场景中自由移动。这使得实体主体的行动决策变得更加困难。 -- TOUCHDOWN,基于Google Street View和Matterport3D模拟器创建,代理按照指令在纽约市的街景渲染模拟中导航,以找到指定的对象。 -- REVERIE要求具身智能能够准确定位由简洁的、人工注释的高级自然语言指令指定的远处不可见的目标物体,这意味着具身智能体需要在大量的物体中找到场景中的目标物体。 -- SOON,代理接收从粗到细的长而复杂的指令,以在 3D 环境中找到目标对象。在导航过程中,智能体首先搜索较大的区域,然后根据视觉场景和指令逐渐缩小搜索范围。这使得SOON的导航具有目标导向性并且与初始位置无关。 -- DDN,只提供人类需求,而不指定明确的对象。代理需要在场景中导航以找到满足人类需求的对象。 -- ALFRED,基于AI2-THOR 模拟器,实体需要理解环境观察结果,并根据粗粒度和细粒度的指令在交互环境中完成家务任务。 -- DialFRED 是 ALFRED 的扩展,允许代理在导航和交互过程中提出问题以获得帮助。这些数据集都引入了额外的预言(Additional Oracles),Agent需要通过提问来获取更多有利于导航的信息。 -- OVMM,基于Habitat 模拟器,Agent需要在未知环境中拾取任何对象并将其放置在指定位置。智能体需要在家庭环境中定位目标物体,导航并抓取它,然后导航到目标位置并放下该物体。 -- Behaviour-1K ,基于OmniGibson 模拟器。基于人类需求,包含 1,000 个长序列、复杂、依赖技能的日常任务。智能体需要完成长跨度的导航交互任务,其中包含数千个基于视觉信息和语言指令的低级操作步骤。这些复杂的任务需要很强的理解和记忆能力。 -- CVDN 要求实体代理根据对话历史导航到目标,并在不确定时提出问题以帮助决定下一步行动。 -#### 相关的方法 - -受到到LLM影响,主要分为两个方向:Memory Understanding Based ,Future-Prediction Based。 -![](https://cdn.jsdelivr.net/gh/zip95297/zip95297.github.io@main/source/images/Aligning%20Cyber%20Space%20with%20Physical%20World%20%20A%20%20Comprehensive%20Survey%20on%20Embodied%20AI/Pasted%20image%2020241027182915.webp?raw=true) - -##### 基于记忆理解 Memory Understanding Based - Past Learning - -侧重于对环境的感知和理解,模型 大多 根据对历史轨迹上的观察进行设计。并且由于 VLN 任务 属于 部分可观测的 马尔可夫决策过程,其中的未来状态的观测结果取决于当下智能体在环境中的行为。历史信息对于navigation的决策有很大作用。所以基于记忆的方法也是主流方法。 - -基于图的学习是该方法的重要组成部分,通常以图的形式表示navigation的过程,Agent在每个时间节点得到信息编码为图的一个节点。然后通过全局或者部分的图信息作为历史信息。Navigation Graph 将环境离散化,但是理解编码环境也是重要的部分。还有结合大模型,应用大模型强大的理解记忆能力。下面是一些方法:(有些对编码进行改进,有些侧重于理解方式以及信息处理,还有的结合LLM) - -- LVERG分别编码每个节点的语言信息和视觉信息,设计新的语言和视觉实体关系图来建模文本和视觉之间的模态间关系以及视觉实体之间的模态内关系。 -- LM-Nav 使用目标条件距离函数来推断原始观测集之间的联系并构建导航图,并通过LLM从指令中提取地标,使用视觉语言模型将它们与导航图的节点进行匹配。 -- HOP不是基于图学习,但其方法与图类似,要求模型对不同粒度的时间有序信息进行建模,从而实现对历史轨迹和记忆的深入理解。 -- VER 通过 2D-3D 采样将物理世界量化为结构化 3D 单元,提供细粒度的几何细节和语义。不同的学习方案探索如何更好地利用历史轨迹和记忆。 -- 通过对抗性学习,CMG在模仿学习和探索激励方案之间交替,有效加强了对指令和历史轨迹的理解,缩短了训练和推理之间的差异。 -- GOAT通过后门调整因果学习(BACL)和前门调整因果学习(FACL)直接训练无偏模型,与视觉、导航历史及其组合到指令进行对比学习,使智能体能够更充分地利用信息。 -- RCM提出的增强型跨模态匹配方法使用面向目标的外部奖励和面向指令的内部奖励来进行全局和局部的跨模态接地,并通过自监督模仿学习从自己的历史良好决策中学习。 -- FSTT将TTA引入VLN,并在时间步和任务两个尺度上对模型的梯度和模型参数进行优化,有效提高了模型性能。 -- NaviLLM通过视觉编码器将历史观察序列集成到嵌入空间中,将融合编码的多模态信息输入到LLM中并对其进行微调,在多个基准上达到了state-of-the-art。 -- NaVid对历史信息的编码进行了改进,通过不同程度的池化实现对历史观测值和当前观测值的不同程度的信息保留。 -- DiscussNav将不同能力的大型模型专家分配给不同的角色,驱动大型模型在导航动作之前进行讨论以完成导航决策,并在零样本VLN中取得了优异的性能。 -##### 基于未来预测 Future-Prediction Based - Future Learning - -这种方法更注重对未来状态的理解、建模以及预测。这种方式对环境有更加深刻的理解,尤其是在连续环境中。 - -基于未来预测的Visual Language Navigation方法(未来预测视觉语言导航)主要旨在通过对环境和未来路径的预测,提升导航模型在未知环境中的表现和决策能力。该方法主要通过图学习和环境编码来预测可行路径和未来观测,从而简化复杂的导航任务,帮助模型在从离散环境过渡到连续环境时更好地表现。 - -- **图学习和路径预测**:在未来预测方法中,常利用图学习进行路径点预测。例如,BGBL和ETPNav方法通过当前导航图节点的观测来预测可移动路径点,旨在通过从离散到连续环境的迁移,提升导航的精确度和稳定性。 - -- **环境编码**:为了更好地理解未来环境,NvEM模型设计了一种融合编码方法,通过主题模块和参考模块从全局和局部视角对邻近视野进行编码。这种方法帮助导航模型学习对未来观测的理解,从而更加准确地预测未来环境。 - -- **多级表示与路径树构建**:HNR模型采用层级神经辐射表示来预测未来环境的视觉表示,通过三维特征空间编码构建可导航的未来路径树。这种方法从不同层级预测未来环境,为导航决策提供了更丰富的参考。 - -- **强化学习的应用**:部分方法采用强化学习进行未来状态的预测。例如,LookBY利用强化学习让预测模块模拟未来的状态和回报,这样可以将“当前观测”和“未来观测的预测”直接映射为行动,从而显著提升导航的表现。 - -- **大型模型的‘想象’功能**:大型语言模型的丰富世界知识和零样本能力,为未来预测方法提供了新的可能性。比如,MiC模型要求语言模型根据指令直接预测目标及其可能位置,通过场景感知来提供导航指示。这一方法要求大型模型充分发挥‘想象力’,并通过提示词构建假想场景来辅助导航。 - -### 非视觉感知(触觉 Non-Visual Perception: Tactile - -触觉感知是具身智能体在物理世界中理解和操作物体的一项重要能力,它能够提供关于物体的纹理、硬度和温度等详细信息,帮助智能体更精确地执行复杂任务。通过触觉传感器,智能体可以在实际操作中获得与视觉信息互补的触觉数据,增强其在高精度任务中的表现。 -在触觉感知任务中,**传感器设计** 决定了Agent获取的信息, 然后介绍数据集 ,最后介绍 触觉感知方法。 - -#### 预备:传感器设计、数据集介绍 - -在触觉信息获取中,传感器有三类: - -- **非视觉(Non-vision-based)传感器**:主要使用电学和机械原理,感知力、压力、振动和温度等低维度信息,代表如BioTac。此类传感器直接测量触觉物理量,适用于检测简单触觉信息。 -- **视觉(Vision-based)触觉传感器**:基于光学原理,通过图像(触觉表面的纹理和形变)来捕获触觉信息。例如GelSight和DIGIT传感器利用凝胶表面的变形图像来感知物体表面特性,应用广泛,尤其是在需要精细触觉信息的场景中。 - ![](https://cdn.jsdelivr.net/gh/zip95297/zip95297.github.io@main/source/images/Aligning%20Cyber%20Space%20with%20Physical%20World%20%20A%20%20Comprehensive%20Survey%20on%20Embodied%20AI/Pasted%20image%2020241029050047.webp?raw=true) -- **多模态(Multi-modal)传感器**:视觉触觉传感器和非视觉触觉传感器,模仿人类皮肤,整合压力、接近度、加速度和温度等多种信息,采用柔性材料设计,能够提供更加全面的触觉数据,提升智能体的适应性和灵活性。 - - **多模态传感器**是指能够同时采集和融合多种不同类型信息的传感器,模拟人类多感官整合的能力。它们常用于具身AI和机器人系统中,通过集成触觉、温度、压力、接近度等不同的感知模式,帮助智能体更全面地理解物体或环境特性。这类传感器的设计灵感源于人类皮肤,它们的应用可以有效提升机器人的感知能力,尤其在精细操作和动态环境下表现出色。 - - **多模态传感器的主要类型和特点** - - 1. **压力和触觉组合**:同时感知压力和表面触觉细节,这一组合通常采用柔性材料传感器,能够检测物体硬度、表面纹理和接触压力。典型应用包括抓取力控制和柔性操作。 - 2. **接近度与触觉组合**:可以在不直接接触物体的情况下检测其存在和距离(接近度),并在接触后感知表面细节。接近度传感器如电容式或红外式传感器,结合触觉传感器,广泛应用于避免碰撞和精细抓取。 - 3. **温度与触觉组合**:感知温度变化和表面特性,帮助机器人判断物体的材质和状态,如液体温度或表面湿度变化。常用于食品处理、医疗领域等温度敏感的任务。 - 4. **压力、加速度与触觉组合**:通过集成压力和加速度传感器,机器人不仅能感知表面信息,还能感知物体的运动和振动模式。该组合特别适合检测对象的振动、滑动和稳定性,提升操作的精确性。 - - **多模态传感器的应用场景** - - • **人机交互**:在机器人手指或表面上安装多模态传感器,可以帮助其识别手握物体的力道、温度变化等,提升人机协作的安全性和准确性。 - • **复杂抓取与操作**:在工业和服务机器人中,能确保机器人适应多种物体类型和不同操作环境,增强稳定性和适应性。 - • **环境监测与适应**:多模态传感器能帮助机器人更好地适应不同环境,例如识别火灾中的高温环境,或在湿滑环境中保持平衡。 - -![](https://cdn.jsdelivr.net/gh/zip95297/zip95297.github.io@main/source/images/Aligning%20Cyber%20Space%20with%20Physical%20World%20%20A%20%20Comprehensive%20Survey%20on%20Embodied%20AI/Pasted%20image%2020241029050221.webp?raw=true) - -数据集则有如下几种: - -![](https://cdn.jsdelivr.net/gh/zip95297/zip95297.github.io@main/source/images/Aligning%20Cyber%20Space%20with%20Physical%20World%20%20A%20%20Comprehensive%20Survey%20on%20Embodied%20AI/Pasted%20image%2020241029045840.webp?raw=true) - -- **非视觉传感器数据集**:主要由BioTac等传感器采集,包含电极值、力向量和接触位置,适用于力估计和抓取操作的研究。 -- **视觉传感器数据集**:采集凝胶变形的高分辨率图像,包含丰富的对象数据,如日常物品、自然环境、不同材料等。此类数据集可用于精细的纹理识别和操作任务。 - -#### 触觉感知 方法 - 根据应用 讲 对应方法 - -触觉感知的应用被分为3类:触觉估计、机器人操作、多模态信息融合(分类、识别)。每种应用中都有一些方法。 - -##### 触觉估计 Tactile Estimation - -在触觉感知中的估计(Estimation)任务里,研究主要集中在物体的形状、力以及滑动检测的估计。以下是估计任务的主要方法和发展: -1. **早期方法** - 研究人员最初通过简单的阈值方法或卷积神经网络(CNN)来处理形状、力和滑动测量。这些方法基于触觉图像的颜色变化和标记物分布的变化来推断物体信息。 - -2. **触觉图像生成**: - 触觉图像生成是估计任务的重要组成部分,目标是将视觉数据转换为触觉图像。最初的研究使用RGB-D图像作为输入,通过深度学习模型输出触觉图像。近年来,随着图像生成技术的快速发展,Higuera等人和Yang等人应用扩散模型(Diffusion Model)生成触觉图像,效果显著提升。 - - 触觉图像的生成通常基于触觉传感器表面与物体接触时的变形或压痕,传感器通过内置的摄像头或压力分布图记录下变形情况,变形数据被处理为图像,可以进一步用于分析物体的特性,如表面纹理、硬度、接触力等。 - -3. **物体重建**: - 物体重建任务分为二维和三维重建: - - **二维重建**:主要关注物体的形状和分割。 - - **三维重建**:关注物体表面、姿态,甚至是整个场景的感知。早期方法多采用数学方法、自编码器和神经网络,将视觉和触觉特征(有时包括点云)结合,实现物体的重建。 - -##### 机器操作 Robotic Manipulation - -**Robotic Manipulation中的触觉任务**涉及通过触觉感知实现对物体的精细操作,其中弥合**模拟到真实环境(Sim-to-Real)的差距**至关重要。为了实现高精度、实时的操作任务。 -有 基于强化学习 和 基于生成对抗网络(GAN) 两种主要方法。 -###### 强化学习方法 - -- **Visuotactile-RL**:为现有强化学习方法提出了改进,包括触觉门控、触觉数据增强和视觉降质处理等。这些改进措施使触觉感知在复杂环境中更具鲁棒性。 -- **Rotateit**:这是一个基于多模态感知输入的指尖物体多轴旋转系统,利用了带特权信息的强化学习策略进行网络训练,并实现了在线推理。 -- **基于触觉的目标设定**:提出了一种基于深度强化学习的目标导向物体推动方法,完全依赖触觉感知,并采用了基于目标的模型无关和模型驱动强化学习策略,使机器人能够精确地将物体推动至目标位置。 -- **AnyRotate**:关注手持物体的多轴旋转操作,该系统利用触觉密集特征构建了连续接触特征表示,提供触觉反馈以在模拟环境中训练策略,同时通过训练观测模型来实现零样本策略转移,有效地缩小了Sim-to-Real差距。 -###### GAN-Based - -- ACTNet 提出了一种无监督的对抗域适应方法来缩小像素级触觉感知任务的域差距。引入了自适应相关注意机制来改进生成器,该生成器能够利用全局信息并关注显着区域。然而,像素级域自适应会导致错误累积、性能下降、结构复杂性和训练成本增加。 -- STR-Net 提出了一种用于触觉图像的特征级无监督框架,缩小了特征级触觉感知任务的sim-real gap - -##### 材料识别 - - 触觉感知任务中的识别任务主要集中于材料分类和多模态理解。研究方法可以分为传统方法和基于大型语言模型(LLMs)与视觉语言模型(VLMs)的方法。 - -###### 传统方法(Traditional Methods) - -传统方法主要依赖于自动编码器、自监督学习以及联合训练,以提升触觉表示学习的效果。方法如下: -- **自动编码器(Autoencoder)**:用于生成简洁的触觉数据表示,减少数据维度以提升处理效率。 -- Polic等人使用卷积神经网络自动编码器对光学触觉传感器图像进行降维。 -- Gao等人设计了一个监督型递归自动编码器,处理异构传感器数据集。 -- Cao等人提出了TacMAE,一个用于处理不完整触觉数据的掩码自动编码器。 -- Zhang等人引入了MAE4GM,这是一种多模态自动编码器,能够整合视觉和触觉数据。 -- **联合训练方法(Joint Training Methods)**:为了将触觉与其他模态相结合,联合训练方法在触觉表示学习中起到关键作用。 -- Yuan等人训练了包含深度、视觉和触觉数据的卷积神经网络(CNNs)。 -- Lee等人采用变分贝叶斯方法,整合了多模态传感器数据(如力传感器序列和末端执行器度量数据)。 -- **自监督方法(Self-supervised Methods)**:自监督学习通过对比学习将不同模态数据结合,进一步增强触觉表示。 -- Lin等人简单地将触觉输入与多种视觉输入配对。 -- Yang等人使用视觉-触觉对比多视角特征。 -- Kerr等人采用了InfoNCE损失,Guzey等人则使用了BYOL方法。这些对比学习方法在触觉表示学习中奠定了坚实基础。 - - - -###### 基于LLMs和VLMs的方法 - -大型语言模型(LLM)和视觉语言模型(VLM)最近在跨模态理解和零样本学习方面表现出色。以下是其在触觉识别中的应用: - • **对比预训练(Contrastive Pretraining)**:研究者通过对比预训练方法,将触觉数据与视觉和语言模态对齐。 - • Yang等人、Fu等人和Yu等人都采用了对比学习方法,将触觉数据编码并与视觉、语言模态对齐。 - • **任务适配(Task Adaptation)**:通过如LLaMA等大型语言模型的微调,可以实现如触觉描述等具体任务。大型语言模型和视觉语言模型在理解触觉数据并跨模态生成描述上展示了优越性能,进一步推动了触觉表示学习的进展。 - -## 具身交互 Embodied Interaction - -具身交互是指Agent在物理或者虚拟世界中与环境交互的任务场景。经典的具身交互任务有 Embodied问答 、 具身智能体抓取任务。 - -### 具身问答 Embodied Question Answering - -在EQA(Embodied Question Answering)任务中,智能体需要从第一人称视角探索环境,以收集必要信息来回答指定问题。具备自主探索和决策能力的智能体不仅需要选择合适的行动来探索环境,还需判断何时停止探索以回答问题。现有研究关注不同类型的问题。 -![](https://cdn.jsdelivr.net/gh/zip95297/zip95297.github.io@main/source/images/Aligning%20Cyber%20Space%20with%20Physical%20World%20%20A%20%20Comprehensive%20Survey%20on%20Embodied%20AI/Pasted%20image%2020241031012655.webp?raw=true) - -#### 类型如下 - -1. **Single Objective(单一目标)**:例如“花瓶在哪个房间?”这种问题类型要求智能体找到特定物体或位置的单一信息。智能体需要进行环境探索,定位到物品的具体位置才能回答问题。 - -2. **Multiple Objectives(多重目标)**:例如“餐桌和电视柜的颜色是否相同?”这种类型的问题涉及多项物体之间的比较。智能体需要找到并观察多个物体的特性,并对比分析后得出答案。 - -3. **Multi-agent(多智能体)**:例如“屋内是否有洗衣机?”这是一个多智能体协作的问题类型。多个智能体可以在不同区域独立探索,然后将各自的信息整合起来以得出答案。 - -4. **Interaction(互动)**:例如“冰箱里有苹果吗?”这种类型要求智能体进行特定的互动行为(如打开冰箱)来获取答案。这种问题类型测试了智能体的交互能力。 - -5. **Knowledge-based(基于知识)**:例如“客厅里有能降低温度的物品吗?”这种类型需要智能体结合已有的知识(如空调的用途)来解答问题,而不只是依赖视觉识别。 - -6. **Episodic Memory(情景记忆)**:例如“钟在哪里?”这种问题类型需要智能体记住之前探索过程中的视觉信息,基于情景记忆来提供答案,避免重复探索。 - -7. **Object States(物体状态)**:例如“有人可以在客厅看电视吗?”这种类型的问题关注环境中物体的当前状态(如电视是否打开、窗帘是否拉开),通过状态判断来推断可行性或答案。 -#### 数据集 - -由于实际环境中机器人实验受限于场景和硬件,模拟器作为虚拟实验平台,为构建EQA数据集提供了合适的环境条件。在模拟器中训练和测试模型显著降低了实验成本,并提高了模型在真实机器上部署的成功率。本文简要介绍了几个EQA数据集,如表IX所示。 - -• **EQA v1**:第一个设计用于EQA的数据集,基于SUNCG数据集中的合成3D室内场景,通过House3D模拟器生成,包含了四种类型的问题:位置、颜色、颜色房间和介词问题,分布在750多个环境中,共5000多个问题。 - -• **MT-EQA**:在House3D中构建,基于SUNCG,扩展至多物体设置,设计了包括颜色、距离和大小比较在内的六种问题类型,涵盖588个环境中的19287个问题。 - -• **MP3D-EQA**:基于Matterport3D数据集开发的模拟器MINOS,扩展了EQA任务到真实3D环境,生成了包含位置、颜色、颜色房间等问题的1136个问题,覆盖83个家庭环境。 - -• **IQUAD V1**:在AI2-THOR上构建,包含存在、计数和空间关系三种问题类型,通过预设模板生成了75000多个选择题,每个问题均有独特的场景配置,要求智能体具备较强的互动能力。 - -• **VideoNavQA**:将视觉推理与导航分离,生成了101000对视频和问题,问题分布在House3D环境中,涵盖存在、计数和定位等八大类28种问题。 - -• **SQA3D**:专注于场景理解,基于ScanNet场景数据提供6800个独特情境、20400个描述和33400个推理问题。 - -• **K-EQA**:在AI2Thor上构建,增加了逻辑关系和知识图谱,包含6000个不同环境中的60000个问题。 - -• **OpenEQA**:首个开放词汇EQA数据集,支持情节记忆(EM-EQA)和主动探索(A-EQA)两种任务,覆盖180多个真实环境中的1600多个问题。 - -• **HM-EQA**:利用GPT-4生成的问题数据集,包含500个问题,涵盖识别、计数、存在、状态和位置问题。 - -• **S-EQA**:利用GPT-4在VirtualHome中生成数据,基于余弦相似度筛选数据,增强了数据集的多样性,通过判断物体状态集合是否达到存在性”是/否”的结论来回答问题。 - -#### 方法介绍 - -在EQA(Embodied Question Answering)任务中,主要涉及导航和问答子任务。实现方法可分为两类:基于神经网络的方法和基于LLMs(大规模语言模型)/VLMs(视觉语言模型)的方法。 - -##### 神经网络方法 - -神经网络方法侧重于模块化设计和任务复杂度扩展 - -**早期方法**:最初,研究人员主要通过深度神经网络来解决EQA任务。这些模型使用模仿学习和强化学习等技术进行训练和微调。Das等人提出的EQA模型包含视觉、语言、导航和回答四个模块,主要通过卷积神经网络(CNN)和循环神经网络(RNN)构建,并采用两阶段训练:先利用自动生成的导航示范数据对导航和回答模块进行独立训练,再通过策略梯度法微调导航架构。 - -**后续改进**:后续研究在保持基本模块的基础上,通过将导航和问答模块整合至统一的随机梯度下降(SGD)训练流程中,以实现联合训练,避免深度强化学习的复杂性。 - -**复杂性扩展**:为增加任务的复杂性,有些研究扩展了多目标和多智能体任务。通过特征提取和场景重建等方法,模型能够整合智能体探索中获取的信息。 - -**动态环境中的交互**:为处理智能体与动态环境的交互,Gordon等人引入了分层交互记忆网络,结合任务选择的规划器和执行任务的低层控制器,并使用Egocentric Spatial GRU(esGRU)存储空间记忆。 - -**外部知识的引入**:针对之前模型在应对复杂问题时无法利用外部知识的缺陷,Tan等人提出了一个框架,利用神经程序合成方法和知识/3D场景图表,帮助智能体获取与对象相关的信息,并通过蒙特卡洛树搜索(MCTS)确定智能体的下一步移动位置。 - -##### **LLMs/VLMs方法** - -LLMs/VLMs方法则利用其自然语言处理和视觉理解的强大能力,通过探索和语言描述等手段更高效地处理EQA任务。 - -• **任务应用**:随着LLMs和VLMs的快速发展,研究人员尝试在EQA任务中应用这些模型,而不进行额外的微调。Majumdar等人探索了将LLMs和VLMs用于情景记忆EQA(EM-EQA)任务和主动EQA(A-EQA)任务。对于EM-EQA任务,他们使用盲LLM、带情景记忆语言描述的Socratic LLMs、场景图描述的Socratic LLMs以及处理多帧场景的VLM。 -- EM-EQA - 1. **盲LLM(Blind LLM)**:这是指在没有视觉输入的情况下,仅通过语言输入来回答问题的LLM。这种模型只能依赖文本描述,因此在环境探索任务中具有明显的局限性,因为它无法“看到”周围环境。 - - 2. **带情景记忆语言描述的Socratic LLM(Socratic LLM with Episodic Memory Descriptions)**:这是指一种Socratic方法的LLM,这种方法通过文字描述来存储和传递情景记忆。智能体在探索环境的过程中会积累情景记忆,这些记忆被转换为语言描述输入到LLM中,帮助它回答基于历史探索信息的问题。 - - 3. **场景图描述的Socratic LLM(Socratic LLM with Scene Graph Descriptions)**:这种Socratic方法的LLM接收的是场景图的描述。场景图是一种结构化的图表,展示了环境中的对象及其关系,通过这种图表描述输入,LLM可以利用这些信息更好地理解环境中的空间和物体分布,从而回答问题。 - - 4. **处理多帧场景的VLM(VLM Processing Multiple Scene Frames)**:VLM(视觉语言模型)在这里用来处理多帧图像信息。它通过接收来自环境的多个视角或帧的图像输入,结合语言信息来推断和回答问题。多帧场景信息让VLM能够更全面地感知环境,增强其回答问题的准确性。 - -• **前沿探索方法**:在A-EQA任务中,使用基于前沿探索(FBE)的方法以进行问题无关的环境探索,一些研究使用FBE识别待探索区域并构建语义地图。此外,采用一致性预测或图像-文本匹配等技术避免过度探索。 - -• **回答强化**:Patel等人则重点关注问答任务,利用多智能体LLM探索环境,并独立给出“是”或“否”的回答,随后由中央回答模型汇总各智能体的反馈,生成更稳健的答案。 - -#### 度量标准 - -在EQA任务中,模型的性能评估通常基于导航和问答两个方面: - -- **评估指标:** - **导航指标**:许多研究沿用Das等人提出的方法,采用以下指标来评估导航性能: - - • **dT**:导航完成时与目标物体的距离。 - - • **d∆**:从初始位置到最终位置的目标距离变化量。 - - • **dmin**:在整个导航过程中,智能体与目标的最小距离。 - -- **问答指标**:问答性能评估主要包括: - - • **平均排名(MR)**:正确答案在候选答案列表中的平均排名。 - - • **准确率**:答案的准确率。 - - • **LLM-Match**:由Majumdar等人提出的指标,用于评估开放词汇答案的正确性,该指标结合了LLM的正确性评估。此外,他们通过将智能体路径的归一化长度作为权重,引入了路径效率的评估。 - - -- 局限性: - - • **数据集**:构建数据集需要大量人力和资源,目前的大规模数据集仍然较少。此外,各个数据集的评估指标并不一致,这使得不同模型之间的测试和性能对比变得复杂。 - - • **模型**:尽管LLM的进步提升了模型的表现,但与人类水平仍有显著差距。未来的研究可能更多地关注如何高效存储智能体探索的环境信息,指导智能体基于环境记忆和问题进行行动规划,同时增强其行为的可解释性。 - -### 具身抓取 Embodied Grasping - -具身交互不仅涉及与人类的问答互动,还包括根据人类指令执行操作,如抓取和放置物体,从而完成机器人、人类和物体之间的交互。体感抓取要求智能体具备全面的语义理解、场景感知、决策能力以及稳健的控制规划。 -具身抓取方法将传统的机器人运动学抓取技术与大规模模型(如LLMs和视觉-语言基础模型)相结合,使智能体能够在多感官感知下执行抓取任务,包括视觉主动感知、语言理解和推理等能力。这种整合使得智能体能够通过视觉、语言和语义的协同工作,更高效地完成复杂的抓取任务。 - -![](https://cdn.jsdelivr.net/gh/zip95297/zip95297.github.io@main/source/images/Aligning%20Cyber%20Space%20with%20Physical%20World%20%20A%20%20Comprehensive%20Survey%20on%20Embodied%20AI/Pasted%20image%2020241031014542.webp?raw=true) -上图是这个Embodied Grasping任务的概述。 -(a). 针对不同类型任务的语言引导抓取示例 -(b).人-智能体-物体的交互概览,智能体在其中完成体感抓取任务,体现了智能体如何根据人类的指令与周围物体进行物理操作的能力。这种交互不仅增强了机器人在家用服务和工业环境中的实用性,还让机器人在多感官整合下对语义和场景理解的复杂任务中表现得更加智能。 -(c).显示“Language Guided Grasping”主题的谷歌学术搜索结果 - -#### 抓取器 - -目前抓取器主要有以下两种: -- **两指平行夹爪**:抓取姿态通常分为两种类型: - - - **4-DOF(四自由度)**:通过三维位置和顶向手部方位(偏航角)来定义抓取姿态,通常称为“顶向抓取”。 - - - **6-DOF(六自由度)**:通过六维的位置和方向来定义抓取姿态,增加了灵活性,可以适应更复杂的抓取场景。 - -- **五指灵巧手**:例如**ShadowHand**,是一种常用的五指机器人灵巧手,具有26个自由度。高自由度带来了生成有效抓取姿态和规划执行轨迹的复杂性,但也使其具备了更高的抓取灵活性,适合处理复杂的抓取任务。 - -#### 数据集 - -为了支持具身抓取任务的研究,研究人员生成了大量的抓取数据集。这些数据集通常包含基于图像(RGB、深度)、点云或3D场景的抓取标注数据。随着多模态大模型(MLMs)和基础语言模型在机器人抓取领域的应用需求的增加,现有数据集也被扩展或重构为语义抓取数据集。这些数据集在研究基于语言的抓取模型时起到了关键作用,使智能体能够建立广泛的语义理解。 - -传统抓取数据集包括单一物体场景和复杂物体堆叠场景,提供符合运动学的稳定抓取标注(4-DOF或6-DOF)。这些数据可以从真实桌面环境中收集,通常包含RGB、深度和点云数据,或来自虚拟环境中的图像、点云或场景模型。这些数据集虽然对抓取模型有用,但缺乏语义信息。因此,这些数据集通过加入语义表达得到了扩展,使语言、视觉和抓取任务能够结合起来。通过加入语义信息,智能体可以更好地理解和执行抓取任务,这一增强使得发展更复杂、更具语义感知的抓取模型成为可能,促进了与环境的更直观和有效的交互。 -![](https://cdn.jsdelivr.net/gh/zip95297/zip95297.github.io@main/source/images/Aligning%20Cyber%20Space%20with%20Physical%20World%20%20A%20%20Comprehensive%20Survey%20on%20Embodied%20AI/Pasted%20image%2020241031015536.webp?raw=true) - -#### 度量标准 - -在具身抓取任务中,深度学习的**损失函数(loss)或优化目标**主要取决于任务的具体目标,包括抓取姿态生成、物体识别、语言指令理解等多个方面。以下是常见的优化目标: - -**1. 抓取姿态生成损失(Grasp Pose Generation Loss)** - -- **回归损失**:如果模型需要预测抓取的位置和角度,则会使用回归损失(如均方误差 MSE)来优化预测姿态和目标姿态之间的差异。 -- **分类损失**:在多种抓取姿态可选的情况下,使用交叉熵损失(Cross-Entropy Loss)来优化抓取姿态的选择。通过将每个候选姿态作为一个类别,模型可以学习选择最合适的抓取姿态。 - -**2. 视觉和语言对齐损失(Vision-Language Alignment Loss)** - -- **对比损失(Contrastive Loss)**:在视觉-语言模型(如CLIPORT)中,通过对比损失函数来优化视觉和语言的对齐。例如,图像和描述的特征在嵌入空间中的距离应最小化,而与其他描述的距离应最大化。 -- **嵌入空间损失**:利用L2范数或余弦相似度等距离度量方法,优化图像特征和语言特征在共享嵌入空间中的相似性,以确保语义上相关的视觉和语言信息保持一致。 - -**3. 多任务损失(Multi-task Loss)** - -- 具身抓取任务中可能包含多种子任务(如物体检测、抓取姿态选择、路径规划等),多任务损失会为每个子任务设置一个单独的损失函数,再通过加权的方式对所有损失进行优化。这样可以使模型在多个任务间取得平衡,提高任务的整体性能。 - -**4. 物体检测与分割损失(Object Detection and Segmentation Loss)** - -- **分类损失**:用于物体类别的识别,例如使用交叉熵损失来区分不同物体类别。 - -- **边界框回归损失(Bounding Box Regression Loss)**:用于预测物体的准确位置,通常使用平滑L1损失来最小化预测的边界框与真实边界框之间的差异。 - -- **分割损失**:在需要精细分割的任务中,使用二值交叉熵损失或Dice系数损失来优化物体分割的准确性。 - -**5. 抓取成功率损失(Grasp Success Loss)** - -- 为确保生成的抓取姿态能够成功抓取物体,许多具身抓取模型会定义一个二分类的成功率损失。该损失可以用二元交叉熵损失(Binary Cross-Entropy Loss)或对比损失等来衡量预测的抓取姿态是否能实现稳定抓取。 -- 例如,在模拟器中标记每次抓取是否成功,然后训练模型最小化失败的概率,使得模型生成的姿态在真实环境中更具鲁棒性。 - -**6. 强化学习奖励优化(Reinforcement Learning Reward Optimization)** - -- 在具身抓取中,如果使用了强化学习,优化目标会以奖励信号(reward)为核心。典型的奖励包括: -- **到达目标物体的奖励**:智能体接近目标物体时给予正奖励。 -- **完成抓取的奖励**:成功抓取并保持物体不掉落给予高奖励。 -- **路径优化**:缩短到目标的路径长度或避免碰撞的奖励。 -- 强化学习算法通过最大化累计奖励的方式优化抓取策略,例如策略梯度方法(如PPO)或Q-learning方法。 - -**7. 场景重构和特征对齐损失(Scene Reconstruction and Feature Alignment Loss)** - -- 在模块化方法(如F3RM或GaussianGrasper)中,使用特征对齐或场景重构损失,优化模型对场景结构的理解。 -- **重建损失**:在3D场景中生成物体特征和空间结构时,可能会用到L2损失来最小化实际场景与重建场景之间的误差,以提高空间推理能力。 - -总结来看,具身抓取任务的深度学习优化目标通常涉及抓取姿态生成、视觉-语言对齐、多任务平衡、抓取成功率等多方面的损失,通过综合这些损失项,使得智能体能够在复杂环境中高效、准确地完成具身抓取任务。 - -#### 语言引导抓取 - -**语言引导抓取**的概念从多模态模型的整合中演化而来,结合了多模态大模型(MLMs)以赋予智能体语义场景推理能力,使其能够根据人类的显式或隐式指令执行抓取操作。近年来,随着大规模语言模型(LLMs)的进步,研究人员对这一主题表现出越来越高的兴趣。 - -在当前的研究趋势中,抓取任务逐渐聚焦于开放世界场景,强调**开放集泛化**方法,通过MLMs的泛化能力,机器人能够在开放环境中更智能和高效地执行抓取任务。在语言引导抓取中,语义信息可以源自显式指令和隐式指令。 - -• **显式指令**:指令明确指定要抓取的物体类别,例如“抓起香蕉”或“抓起苹果”。 - -• **隐式指令**:需要推理来识别要抓取的物体或物体的某个部分,涉及**空间推理**和**逻辑推理**。 - -• **空间推理**:指令可能包含物体或要抓取部分的空间关系,需要根据场景中物体的空间关系推断抓取姿态。例如,“抓取棕色纸巾盒右侧的键盘”需要智能体理解物体的空间排列并进行推理。 - -• **逻辑推理**:指令可能包含需要推理的人类意图的逻辑关系,从而抓取目标。例如,“我渴了,你能给我点喝的吗?”这可能会促使智能体递上一杯水或一瓶饮料,智能体必须确保液体不会在传递过程中洒出,从而生成合理的抓取姿态。 - -通过将语义理解与空间和逻辑推理相结合,使得智能体能够高效准确地完成复杂的抓取任务。 - -#### 端到端方法 - -端到端方法通过整合视觉和语言模型来完成从语义理解到抓取生成的全流程。 - -• **CLIPORT**:这是一个语言驱动的模仿学习智能体,结合了预训练视觉语言模型CLIP和Transporter Net,创建了一个端到端的双流架构,用于语义理解和抓取生成。该模型通过大量从虚拟环境中收集的专家示范数据进行训练,使智能体能够执行语义驱动的抓取任务。 - -• **CROG**:基于OCID数据集,提出了视觉-语言-抓取数据集,并引入了具有竞争力的端到端基准。它利用了CLIP的视觉基础能力,从图像-文本对中直接学习抓取合成。 - -• **Reasoning Grasping**:在GraspNet1 Billion数据集上首次引入推理抓取基准数据集,提出了一个结合多模态LLM与视觉抓取框架的模型,能够基于语义和视觉生成抓取姿态。 - -• **SemGrasp**:这是一种基于语义的抓取生成方法,将语义信息纳入抓取表示中,从而生成灵巧手的抓取姿态。引入了离散表示,将抓取空间与语义空间对齐,以根据语言指令生成抓取姿态。为了支持训练,还提出了一个大规模的抓取-文本对齐数据集CapGrasp。 - -#### 模块化方法 - -模块化方法通过将语言和视觉特征分阶段处理,提升了模型的灵活性和可泛化能力。 - -• **F3RM**:此方法试图将CLIP的文本-图像先验扩展到3D空间,使用提取的特征进行语言定位,随后生成抓取姿态。它结合了精确的3D几何与2D基础模型中的丰富语义,利用CLIP提取的特征通过自然语言指定要操作的物体,展现了对未见表达和新物体类别的泛化能力。 - -• **GaussianGrasper**:利用3D高斯场实现语言引导抓取任务。该方法首先构建3D高斯场,然后进行特征提取,再基于提取的特征进行语言定位,最终通过最先进的预训练抓取网络生成抓取姿态。它结合了开放词汇语义和精确的几何特征,使得机器人能够根据语言指令执行抓取。 - -这些方法利用端到端和模块化框架,提升了机器人理解和执行复杂抓取任务的能力,借助自然语言指令,使得机器人能够完成更为复杂的任务。 - -## 具身智能体 Embodied Agent - -Agent : 感知环境,采取行动,完成目标。(RL中的Agent) -多模态大模型将这个任务扩展到现实场景的应用上。变成具身Agent。 -![](https://cdn.jsdelivr.net/gh/zip95297/zip95297.github.io@main/source/images/Aligning%20Cyber%20Space%20with%20Physical%20World%20%20A%20%20Comprehensive%20Survey%20on%20Embodied%20AI/Pasted%20image%2020241101003208.webp?raw=true) - -具身Agent一般具有强大的多模态感知、交互、规划能力。在多模态Embodied Agent在完成任务时,一般: - - 将复杂任务分解为sub task - - 有效利用具身感知和交互模型完成这些子任务 -任务的规划不仅在行动之前,在行动开始之后,与环境产生交互中也需要将信息反馈给Planer调整规划。 - -具身Agent的研究方向有下面几个:多模态具身Agent、具身任务规划、具身行动规划 - -### 多模态具身Agent模型 Embodied Multimodal Foundation Model - -具身智能体需要通过视觉识别环境、通过听觉理解指令,并对自身状态具备清晰的感知,以便执行复杂的交互与操作。为此,需要一个整合多模态感知与自然语言处理能力的模型来增强智能体的理解和决策能力,即“多模态具身基础模型”应运而生。 -近期,Google DeepMind研究发现,利用基础模型及大规模多样化数据集是最佳策略。他们基于“机器人Transformer”(RT)开发了一系列工作,为具身智能体的未来研究提供了重要参考。 - -#### **模型进展** - -在基础机器人模型的发展上,已经从最早的SayCan模型进化为RT系列: - -• **SayCan模型**:采用了三个独立模型,分别用于计划、可行性判断和低级策略。 - -• **Q-Transformer**:统一了可行性判断与低级策略, - -• **PaLM-E**:整合了计划与可行性判断。 - -• **RT-2**:在此基础上进一步进展,将计划、可行性和低级策略整合为单一模型,使得各个部分之间的正向传递和协同放大成为可能。 - -RT-2引入了“视觉-语言-行动”(VLA)模型,具备“连贯思维”推理能力,能够在多个步骤中进行语义推理,例如在不同语境下选择替代工具或饮料。随后,RT-H(RT Hierarchy)通过层次化的动作规划实现了端到端的机器人变换模型,能够在更细粒度上对任务规划进行推理。 - -#### **数据集与模型开放** - -为了应对具身模型的泛化问题,Google与33家学术机构合作,创建了包含22种不同数据类型的综合Open X-Embodiment数据集,并基于该数据集训练了通用大型模型RT-X。Open X-Embodiment推动了更多开源视觉语言模型(VLM)的应用,例如基于LLaVA的EmbodiedGPT和基于Flamingo的RoboFlamingo。尽管Open X-Embodiment提供了大量数据集,构建数据集仍然是具身机器人平台快速演进中的一大挑战。AutoRT为此创建了一个系统,能在新环境中部署机器人以收集训练数据,借助LLM提升学习能力,通过更全面和多样的数据来扩展学习。 - -#### **性能和效率优化** - -具身模型中基于Transformer的架构需要处理大量上下文数据,包括视觉、语言和具身状态等信息,以及当前任务的记忆,因而在效率上存在一定问题。例如,RT-2的推理频率仅为1-3Hz。为解决这一问题,研究者尝试通过量化和蒸馏模型来提升效率,同时改进模型框架是另一种可行方案。SARA-RT采用了更高效的线性注意力,而RoboMamba则利用了适合长序列任务的mamba架构,推理速度比现有机器人多模态模型快七倍。 - -#### **高层次任务规划与低层次行动执行的协作** - -基于生成模型的RT在高层任务理解与规划方面表现出色,但在低层次行动规划上有所限制。为此,Google提出了RT-Trajectory,通过自动添加机器人的轨迹,提供低层视觉线索以学习机器人控制策略。在RT-2的基础上,RT-H引入了分层的动作框架,通过中间的语言动作将高层任务描述与低层机器人运动关联起来。 - -尽管VLA模型在高层次规划和可行性任务上展示了能力,但在低层物理交互中表现出新技能的能力较弱,且受限于数据集中技能类别,导致执行操作时动作不够灵活。未来的研究应当将强化学习引入大型模型的训练框架,以提升模型在真实环境中的低层物理交互策略学习与优化能力,从而使其更灵活、精确地执行多样化的物理动作。 - -### 具身任务规划 Embodied Task Planning - -在具身任务规划中,如“将苹果放在盘子上”这类任务通常会被分解为若干子任务,例如“找到苹果,拾起苹果”,“找到盘子”,“放下苹果”。找到物体(导航任务)或拾取/放下操作(抓取任务)并不在任务规划的范围内,这些动作通常在模拟器中被预定义,或在真实世界中使用预训练策略模型执行,例如使用CLIPort完成抓取任务。传统的具身任务规划方法通常基于显式规则和逻辑推理,例如使用符号规划算法(如STRIPS和PDDL)或搜索算法(如MCTS和A*)生成计划。然而,这些方法依赖于预定义规则、约束和启发式信息,难以适应环境中的动态变化或不可预见的情况。 - -**1)基于LLM的规划方法:利用大规模语言模型的生成能力** - -在自然语言模型扩展之前,任务规划通常通过在具身指令数据集上训练模型实现。例如FILM利用BERT在Alfred和Alfworld数据集上实现了此功能。然而,这种方法受到训练集示例的限制,难以有效对应物理世界的实际情况。随着LLM具备的突现能力,现在可以利用其内在的世界知识和连贯思维推理,将抽象任务分解为多个步骤并合理规划。例如,Translated LM和Inner Monologue可以在不额外训练的情况下,将复杂任务分解为可管理的步骤,并利用其内部逻辑和知识体系生成解决方案。ReAd则作为多智能体协作框架,通过不同提示实现高效的计划自我优化。 - -此外,部分方法将过去成功的例子抽象为记忆库中的技能,在推理过程中参考这些技能以提高任务规划成功率。一些研究还通过代码而非自然语言进行推理,使任务规划以代码的形式基于可用的API库生成。此外,多轮推理也可以有效地纠正计划中的潜在错误,这是许多基于LLM的智能体研究的重点。例如Socratic Models和Socratic Planner使用提问法来推导可靠的计划。 - -在任务规划过程中,由于任务复杂性或真实环境的动态性,潜在的失败可能发生。通常情况下,这种失败源于规划器未能全面考虑实际场景中的细节。由于缺乏视觉信息,规划出的子任务可能与实际情境不符,导致任务失败。因此,在规划或执行过程中整合视觉信息是必要的,这可以显著提升任务规划的准确性和可行性,更好地应对真实环境中的挑战。 - -**2)利用具身感知模型的视觉信息进行规划** - -基于以上讨论,将视觉信息进一步融入任务规划(或重新规划)显得尤为重要。在此过程中,视觉输入提供的对象标签、位置或描述可以为LLM分解任务和执行任务提供关键参考。通过视觉信息,LLM可以更准确地识别当前环境中的目标对象和障碍物,从而优化任务步骤或调整子任务目标。一些研究使用对象检测器在任务执行过程中查询环境中存在的对象,并将这些信息反馈给LLM,以修正当前计划中的不合理步骤。例如,RoboGPT考虑同一任务中类似对象的不同命名,从而提高重新规划的合理性。 - -然而,标签提供的信息仍然有限,是否可以提供更全面的场景信息?SayPlan提出使用分层3D场景图表示环境,有效缓解了在多楼层和多房间大型环境中的任务规划挑战。ConceptGraphs同样采用3D场景图向LLM提供环境信息,与SayPlan相比,它还具备开放世界的对象检测功能,且任务规划以代码格式呈现,更高效并更符合复杂任务需求。 - -尽管提供视觉提示,LLM仍可能难以捕捉环境的复杂性和动态变化,导致理解错误和任务失败。例如,如果毛巾锁在浴室柜里,智能体可能会不断在浴室中搜索,而未考虑这一可能性。为了解决这一问题,需要开发更鲁棒的算法以整合多种感官数据,增强智能体对环境的理解。此外,即使视觉信息有限,历史数据和上下文推理也可帮助智能体做出合理的判断和决策。这种多模态整合和基于上下文的推理方法不仅能提高任务执行成功率,还为具身人工智能的进步提供了新视角。 - -**3)利用视觉语言模型(VLMs)进行规划** - -与通过外部视觉模型将环境信息转化为文本描述不同,视觉语言模型(VLMs)可以在潜在空间中捕捉视觉细节,尤其是难以用对象标签表示的上下文信息。VLMs能够辨识视觉现象背后的规则,例如即使环境中没有看到毛巾,也可以推测毛巾可能被放在柜子里。这一过程展示了抽象视觉特征与结构化文本特征如何在潜在空间中更有效地对齐。 - -在**EmbodiedGPT**中,Embodied-Former模块将具身信息、视觉信息和文本信息对齐,有效地在任务规划中考虑智能体的状态和环境信息。与直接使用第三人称视角图像的EmbodiedGPT不同,**LEO**将2D自我视角图像和3D场景编码为视觉tokens,从而能够更全面地感知3D世界信息并执行任务。类似地,**EIF-Unknow**模型利用从体素特征提取的语义特征图作为视觉tokens,与文本tokens一同输入到经过训练的LLaVA模型中进行任务规划。 - -此外,具身多模态基础模型或VLA模型在RT系列、PaLM-E和Matcha等研究中通过大量数据集进行训练,实现了具身场景中视觉和文本特征的对齐。然而,任务规划只是智能体完成指令任务的第一步,后续的动作规划决定了任务能否真正完成。 - -在RoboGPT的实验中,任务规划的准确率达到了96%,但整体任务完成率仅为60%,受限于低级规划器的性能。因此,具身智能体能否从“想象任务如何完成”的网络空间过渡到“与环境互动并完成任务”的物理世界,关键在于有效的动作规划。 - -### 具身动作规划 Embodied Action Planning - -在任务规划与动作规划的定义和区别中可以看到,动作规划必须应对现实世界的不确定性,因为任务规划所提供的子任务的粒度不足以指导智能体与环境交互。通常,智能体可以通过以下两种方式实现动作规划: - -1. 使用预训练的具身感知和具身干预模型作为工具,通过API逐步完成任务规划指定的子任务; -2. 利用VLA模型的内在能力来推导动作规划。 - -此外,动作规划的执行结果会反馈给任务规划器,以调整和改进任务规划。 - -**1)利用API进行动作规划** - -典型的方法是将各种训练良好的策略模型的定义和描述作为上下文提供给LLMs,使其理解这些工具并确定何时以及如何调用它们来执行特定任务。此外,通过生成代码,可以将更细化的工具抽象为一个函数库进行调用,而不是直接将子任务的参数传递给导航和抓取模型。面对环境的不确定性,Reflexion可以在执行过程中进一步调整这些工具,以实现更好的泛化效果。 - -优化这些工具可以增强智能体的稳健性,同时可能需要新的工具来完成未知任务。DEPS在零样本学习的前提下,为LLMs赋予各种角色设置,使其在与环境交互时学习多样技能。在后续交互中,LLMs可以学会选择和组合这些技能,以开发出新的技能。这种分层规划范式使智能体能够专注于高级任务规划和决策,而将具体的动作执行委托给策略模型,从而简化了开发过程。 - -任务规划器与动作规划器的模块化设计实现了独立开发、测试和优化,提升了系统的灵活性和可维护性。这一方法使智能体能够通过调用不同的动作规划器适应各种任务和环境,便于修改,而无需大幅调整智能体的结构。然而,调用外部策略模型可能引入延迟,影响响应时间和效率,特别是在实时任务中。智能体的表现严重依赖于策略模型的质量,如果策略模型效果不佳,整体表现也会受到影响。 - -**2)利用VLA模型进行动作规划** - -与传统方法不同,VLA模型在同一系统内实现任务规划和动作执行,减少了通信延迟,提高了系统响应速度和效率。在VLA模型中,感知、决策和执行模块的紧密整合使系统能够更高效地处理复杂任务,并适应动态环境的变化。这种整合还便于实时反馈,使智能体能够自主调整策略,从而增强任务执行的鲁棒性和适应性。 - -然而,这一模式显然更加复杂且成本较高,特别是在处理复杂或长期任务时。此外,关键问题在于,缺乏具身世界模型的动作规划器,仅依靠LLM的内部知识难以模拟物理规律。这一限制阻碍了智能体在物理世界中准确高效地完成任务,难以实现从网络空间向物理世界的无缝迁移。 - -## 模拟到现实中的迁移 Sim-to-Real Adaptation - -在具身AI中,Sim-to-Real适应指的是将智能体在模拟环境中(网络空间)学习的能力或行为迁移到真实世界场景(物理世界)的过程。其目的是验证并提升在模拟环境中开发的算法、模型和控制策略在真实环境中的稳健性和可靠性。为了实现Sim-to-Real适应,需要具身世界模型、数据收集与训练方法、以及具身控制算法这三大关键要素。 - -### **具身世界模型** Embodied World Model - -Sim-to-Real适应的关键之一在于创建尽可能接近真实世界的模拟环境模型,使算法在迁移时能够更好地泛化。世界模型旨在构建一个从感知到动作的端到端模型,或通过预测方式从输入到输出的映射关系。与VLA模型不同,VLA模型首先在大规模互联网数据集上训练以获得高层次的能力,再通过真实世界数据进行微调。而世界模型从零开始基于物理世界数据进行训练,随着数据量增加逐步积累高层能力,更类似人类神经反射系统,更适合在输入输出结构化的场景中使用,如自动驾驶(输入:视觉,输出:油门、刹车、方向盘)或对象分类任务。 - -学习世界模型为物理仿真领域带来了重大进展。与传统仿真方法相比,世界模型能够在信息不完全的情况下进行推理、满足实时计算需求,并随着时间提高预测精度。这种预测能力让机器人逐步发展出物理直觉,以便在现实世界中更好地运行。根据不同的学习流程,世界模型分为生成型方法、预测型方法和知识驱动型方法,具体方法详见表XI。 -![](https://cdn.jsdelivr.net/gh/zip95297/zip95297.github.io@main/source/images/Aligning%20Cyber%20Space%20with%20Physical%20World%20%20A%20%20Comprehensive%20Survey%20on%20Embodied%20AI/Pasted%20image%2020241101012729.webp?raw=true) - -#### **1)生成型方法 Generation-based Methods** - -随着模型和数据规模的不断扩大,生成型模型已展现出理解和生成符合物理规律的图像(如World Models)、视频(如Sora、Pandora)、点云(如3D-VLA)等数据的能力。这表明生成型模型不仅能捕捉数据的统计特性,还能通过内在结构和机制模拟现实世界的物理与因果关系。由于其内嵌的世界知识,生成型模型不仅仅是识别工具,还具备一定的世界模型特征,这种特性可以用于提升其他模型的表现。通过挖掘生成模型中的世界知识,可以增强模型的泛化性与鲁棒性,使其更适应新环境,提高对未知数据的预测精度。 - -然而,生成型模型也存在一些限制。当数据分布显著偏斜或训练数据不足时,生成模型可能产生不准确或扭曲的输出。其训练过程通常需要大量计算资源和时间,且缺乏解释性,使其在实际应用中面临挑战。当前的研究致力于提升生成模型的效率、增强解释性,并处理数据偏差等问题。随着研究的不断深入,生成模型在未来应用中有望展现更大的潜力和价值。 - -#### **2)预测型方法 Prediction-based Methods** - -预测型世界模型通过构建内部表示来预测和理解环境,基于提供的条件重构潜在空间中的特征,从而捕捉更深层次的语义与世界知识。这种模型将输入映射到潜在空间中,通过该空间提取并利用高层语义信息,使机器人能更准确地进行具身下游任务(如iVideoGPT、IRASim、STP、MuDreamer)。相比像素级信息,潜在特征可以抽象化并解耦不同类型知识,使模型在处理复杂任务和场景时表现更好,提升了泛化能力。 - -在时空建模中,世界模型需要根据对象的当前状态和交互方式预测其后续状态,并将此信息与内在知识结合。具身世界模型通过整合感知信息与先验知识,对环境进行动态预测。这一方法不仅依赖于感知数据,还依靠世界知识来推断环境变化,从而生成更准确的时空预测。与像素级处理相比,基于潜在空间的操作能以较低成本保持不同环境中的高性能。然而,模型在处理未见过的环境和情况时可能表现出一定的局限性和不稳定性,且解耦的世界知识在潜在空间中可能存在解释性问题。 - -#### **3)知识驱动型方法 Knowledge-driven Methods** - -知识驱动型世界模型通过人工构建的知识注入模型,使其具备世界知识。在真实-仿真-真实(real2sim2real)方法中,利用现实世界的知识来构建符合物理规律的模拟器,对机器人进行训练,从而提高模型的鲁棒性和泛化能力。此外,人工构建的常识或符合物理的知识也常用于生成模型或模拟器,例如ElastoGen、One-2-3-45和PLoT等。这种方法在模型中加入物理约束,提升了生成任务的准确性和解释性。这些物理约束确保模型知识的准确性和一致性,减少训练和应用中的不确定性。 - -一些方法将人工创建的物理规则与LLMs或MLMs结合,利用其常识能力生成多样化且语义丰富的场景。例如,Holodeck、LEGENT和GRUtopia通过自动优化空间布局实现丰富的场景生成,为具身智能体提供多样化的环境,从而促进通用具身智能体的发展。 - -### **数据收集与训练 Data Collection and Training** - -在Sim-to-Real适应中,高质量数据至关重要。传统的数据收集方法往往依赖昂贵的设备和精确操作,过程耗时且劳动密集,且灵活性不足。近期,已经提出了多种高效且低成本的数据收集和训练方法,用于高质量的示例数据收集。以下总结了在现实和模拟环境中进行数据收集的多种方法(见图16中的示例数据)。 -#### **1)现实世界数据 Real-World Data** - -在大量、丰富的数据集上训练高容量模型已展现出强大的能力,并在下游应用中取得了显著成功。例如,LLMs如ChatGPT、GPT-4和LLaMA在自然语言处理领域表现出色,并在下游任务中提供了强大的问题解决能力。类似地,是否可以在机器人领域训练一个具身大模型,使其具备较强的泛化能力并能适应新的场景和机器人任务?实现这一目标需要大量具身数据集为模型提供训练数据。 - -例如,**Open X-Embodiment**是一个具身数据集,来源于22种不同的机器人,包含527种技能和160,266个任务。收集的数据是机器人执行操作过程中的真实示例数据,主要关注家庭和厨房场景,涉及家具、食物和餐具等物品,操作以拾取和放置任务为主,部分任务涉及更复杂的操作。在该数据集上训练的高容量模型RT-X表现出优异的迁移能力。 - -此外,UMI提出了一个数据收集与策略学习框架,设计了一款便携式手持夹持器和简洁界面,以实现便携、低成本和信息丰富的数据收集,尤其适用于双手和动态操作示例的数据收集。通过简单地修改训练数据,机器人可以实现零样本的双手精准任务的泛化能力。 - -另一个低成本的全身移动操作系统**Mobile ALOHA**,用于在全身移动条件下收集双手操作的数据,如煎虾和上菜等任务。通过该系统和静态ALOHA收集的数据训练智能体,可显著提升移动操作任务的表现,这类智能体可以用于家庭助手或工作助手。此外,人类-智能体协作数据收集中,人类和智能体在数据收集时共同学习,减少人类工作量,加速数据采集并提高数据质量。在具身场景中,收集数据时由人类提供初始动作输入,随后智能体通过迭代扰动和去噪过程优化这些动作,从而生成高质量的操作示例。 - -#### **2)模拟数据 Simulated Data** - -现实世界的数据收集方法常需大量人力、物力和时间,因此大多数情况下,研究者可以选择在模拟环境中收集数据集以进行模型训练。模拟环境中的数据收集不需要大量资源,且通常可以通过程序自动化,节省大量时间。例如,CLIPORT和Transporter Networks使用Pybullet模拟器收集示例数据进行端到端网络模型训练,成功将模型从模拟迁移到现实。**GAPartNet**构建了一个大规模的以部件为中心的交互数据集,提供丰富的部件级别标注,用于感知和交互任务。**SemGrasp**创建了一个大规模的抓取文本对齐数据集CapGrasp,用于虚拟环境中的灵巧抓取任务。 - -#### **3)Sim-to-Real迁移范式 Sim-to-Real Paradigms** - -近期,多个Sim-to-Real迁移范式被提出,通过在模拟环境中进行大量学习,再将其迁移到现实中,以减少昂贵的现实示例数据需求。以下列出五种Sim-to-Real迁移范式: -![](https://cdn.jsdelivr.net/gh/zip95297/zip95297.github.io@main/source/images/Aligning%20Cyber%20Space%20with%20Physical%20World%20%20A%20%20Comprehensive%20Survey%20on%20Embodied%20AI/Pasted%20image%2020241101012906.webp?raw=true) - -• **Real2Sim2Real**:利用强化学习(RL)在“数字孪生”仿真环境中增强模仿学习。首先使用NeRF和VR对场景进行扫描和重建,将构建的场景导入模拟器以实现从真实到仿真的精度,接着在模拟中对专家示例策略进行细化,最后将策略迁移到现实环境中执行。 - -• **TRANSIC**:通过实时人类干预矫正机器人在现实场景中的行为,以缩小Sim-to-Real差距。首先,机器人在模拟中通过RL建立基础策略,随后将这些策略应用于真实机器人,错误时由人类通过远程控制实时纠正行为。干预数据用于训练残差策略,整合基础和残差策略以确保真实应用中的顺畅轨迹。 - -• **域随机化 Domain Randomization**:在模拟训练期间通过参数随机化来增强模型的泛化能力。在仿真中通过随机化参数覆盖多种条件,提高了模型的鲁棒性,使其能够从模拟环境部署到现实环境。 - -• **系统辨识 System Identification**:通过构建现实环境中物理场景的精确数学模型,涵盖动力学和视觉渲染等参数,使模拟环境更接近现实场景,从而促进从模拟到现实的顺利迁移。 - -• **Lang4sim2real**:使用自然语言作为桥梁,以图像的文本描述作为跨域统一信号。先用带有跨域语言描述的图像数据预训练编码器,再使用域不变表示进行多任务语言条件行为克隆策略的训练。该方法通过模拟数据中的丰富信息弥补了现实数据的不足,增强了Sim-to-Real迁移能力。 - -### 具身控制 Embodied Control - -具身控制通过与环境的交互学习并使用奖励机制优化行为,以获得最优策略,从而避免传统物理建模方法的局限性。具身控制方法主要分为两类: - -#### **1)深度强化学习(DRL)** - -深度强化学习(DRL)可以处理高维数据并学习复杂行为模式,适合用于决策与控制任务。例如,**混合动态策略梯度(HDPG)被应用于双足机器人行走控制,使得控制策略可以根据多个标准动态优化。另一个例子DeepGait**,是一种针对地形的步态控制网络,结合了基于模型的运动规划和强化学习方法。它包括一个地形感知规划器,用于生成步态序列和基座运动,以指导机器人朝向目标方向前进。还包含一个步态与基座控制器,用于在执行这些序列时保持平衡。规划器和控制器都通过神经网络参数化,并通过深度强化学习算法优化。 - -#### **2)模仿学习(Imitation Learning)** - -深度强化学习的一个主要缺点是需要大量数据进行多次尝试。为了解决这个问题,模仿学习通过高质量的示例数据来减少数据使用。为提高数据效率,提出了**离线RL + 在线RL**的组合方法,旨在降低交互成本并确保安全性。首先,离线RL从静态的、预收集的大数据集中学习策略,随后将这些策略部署到真实环境中,进行实时交互和探索,并根据反馈进行调整。代表性的模仿学习方法有**ALOHA**和**Mobile ALOHA**,这两种方法均通过人类示例提升了数据效率。 - -尽管具身AI涵盖了高层次的算法、模型和规划模块,其最基础的组成部分仍然是具身控制。因此,如何控制物理实体并赋予其“物理智能”成为关键问题。具身控制直接涉及硬件,如控制关节运动、末端执行器位置以及行走速度。例如,对于机械臂,了解末端执行器的位置后,如何规划关节的运动以到达目标位置?对于类人机器人,如何在掌握运动模式的前提下控制关节以达到预期姿势?这些都是控制领域需要解决的关键问题。 - -#### **具身控制的其他应用** - -一些研究工作专注于机器人控制以提升动作的灵活性。例如,**一种基于视觉的全身控制框架**连接了机器人手臂和机器人狗,利用12个腿部关节、6个手臂关节和1个夹持器,通过追踪机器狗的速度和手臂末端执行器的位置实现更灵活的控制。此外,一些研究利用传统方法控制双足机器人行走,诸如MIT的Cheetah 3、ANYmal和Atlas等机器人,配备稳健的行走控制器,可执行跳跃和跨越障碍等敏捷运动任务。 - -另外,其他研究聚焦于类人机器人控制,使其可以模仿人类动作和行为,以适应各种人类行为模拟任务。 - -![](https://cdn.jsdelivr.net/gh/zip95297/zip95297.github.io@main/source/images/Aligning%20Cyber%20Space%20with%20Physical%20World%20%20A%20%20Comprehensive%20Survey%20on%20Embodied%20AI/Pasted%20image%2020241101013442.webp?raw=true) - -#### **具身控制的整合:RL与Sim-to-Real** - -具身控制将RL与Sim-to-Real技术相结合,通过与环境的交互来优化策略,使机器人能够探索未知领域,甚至超越人类能力,并适应非结构化环境。尽管机器人可以模仿许多人的行为,完成任务仍然需要基于环境反馈的RL训练。最具挑战性的场景是接触密集型任务,这些任务需要实时调整以响应操作对象的状态、变形、材质和受力等反馈信息,这时RL变得不可或缺。 - -在多模态语言模型(MLMs)的时代,MLMs具备对场景语义的广泛理解,可为RL提供稳健的奖励函数。此外,RL对于大模型的任务对齐也至关重要。未来,在经过预训练和微调之后,RL仍然需要与物理世界对齐,以确保模型在真实环境中的有效部署。 - -### **全机器人整合 All Robots in One** - -尽管已有数据集(如Open X-Embodiment)为预训练具身智能体提供了统一结构,但当前数据级别的限制仍然阻碍着通用型具身智能体的发展,主要问题包括:缺乏标准化格式、多样性不足以及数据量不足。特定任务的数据集无法满足训练通用型智能体的需求。现有数据集在多模态感知方面也不够全面——目前没有任何一个数据集能够同时整合图像、3D视觉、文本、触觉和听觉输入。此外,多机器人数据集中缺乏统一格式,这使得数据处理和加载变得复杂,不同机器人平台之间的控制对象表示不兼容,数据量不足以支撑大规模预训练,缺少结合模拟和现实数据的数据集,难以有效解决模拟到现实的迁移问题。 - -#### **ARIO标准的提出** - -![](https://cdn.jsdelivr.net/gh/zip95297/zip95297.github.io@main/source/images/Aligning%20Cyber%20Space%20with%20Physical%20World%20%20A%20%20Comprehensive%20Survey%20on%20Embodied%20AI/Pasted%20image%2020241101013803.webp?raw=true) -为了应对这些挑战,ARIO(All Robots In One)被引入,作为一种新的数据集标准,优化了现有数据集并促进了更通用的具身AI智能体的发展。ARIO标准采用统一格式记录了不同形态机器人之间的控制和运动数据,具有以下主要特性: - -1. **时间戳机制**:通过时间戳标准化数据收集,解决了机器人动作频率和传感器帧率的差异问题。ARIO的统一格式能够适应多种机器人类型的可变数据,确保时间戳的精准性。 - -2. **多样数据集成**:ARIO标准支持集成多机器人数据,便于开发高性能、具备良好泛化能力的具身AI模型,为具身AI数据集提供了理想格式。 - -#### **ARIO大规模数据集的构建** - -在ARIO标准基础上,开发了一个统一的大规模ARIO数据集,包含约300万个情境,涵盖258个系列和321,064个任务。此数据集的构建方式多样化,包括: - -1. **现实世界数据收集**:通过自定义平台收集了3,662个情境,涵盖了105种以上的任务; -2. **基于模拟的数据生成**:利用如Habitat、MuJoCo和SeaWave等平台生成数据,涵盖1,198个任务,共收集了703,088个情境; -3. **开源数据集的标准转换**:将现有的开源数据集转换为ARIO标准,为数据集增加了2,326,438个情境,涉及319,761个任务。 - -ARIO数据集不仅解决了现有数据集的限制,还通过提供一个连贯的框架支持数据收集和表示,促进了更强大、通用的具身AI智能体的研发。借助ARIO标准的统一格式和多样数据,未来的具身AI智能体将能更灵活地导航和与物理世界互动,适应日益复杂和多样的任务场景。 - -# 面对的挑战 - -**1)高质量机器人数据集** - -获取足够的现实机器人数据仍是重大挑战。收集这些数据不仅耗时且资源密集,单靠模拟数据会加剧模拟到现实的差距问题。构建多样的现实机器人数据集需要机构间的紧密合作。此外,为提高模拟数据质量,需开发更真实且高效的模拟器。当前的RT-1通过机器人图像和自然语言指令的预训练模型在导航和抓取任务中取得了良好效果,但获取现实机器人数据集仍然困难。为了构建跨场景、跨任务的具身通用模型,需构建大规模数据集,结合高质量模拟环境数据辅助现实数据。 - -**2)高效利用人类示例数据** - -高效利用人类示例数据需要通过人类的行为和动作示例来训练和改进机器人系统,包括收集、处理和学习人类执行的任务。现有的R3M方法使用动作标签和人类示例数据来学习可泛化的表示,但对于复杂任务的效率仍需提升。有效利用非结构化、多标签和多模态的人类示例数据,结合动作标签数据,将提高具身模型在动态环境中的表现和适应能力,使其更好地执行复杂任务。 - -**3)复杂环境认知** - -复杂环境认知是指具身智能体在物理或虚拟环境中感知、理解和导航复杂现实环境的能力。当前的Say-Can模型基于大规模常识知识,通过预训练LLM模型的任务分解机制来进行简单任务规划,但在理解复杂环境中的长期任务上有所欠缺。为了使机器人系统更具通用性,具身智能体需具备跨场景的知识迁移和泛化能力。这要求构建适应性强且可扩展的具身智能体架构,以处理复杂场景和多样化任务。 - -**4)长期任务执行** - -对于机器人而言,执行单个指令往往意味着长期任务,例如“清洁厨房”涉及多个低层次行动(如重新排列物体、清扫地板、擦拭桌子等)。成功完成此类任务需要机器人能够规划并执行一系列长时间跨度的行动。当前的高层任务规划器在特定场景中取得了初步成功,但在具身任务中仍显不足。应开发具备强大感知能力和常识知识的高效规划器,以应对复杂的长期任务需求。 - -**5)因果关系发现** - -现有的数据驱动具身智能体依赖于数据内在的相关性做出决策,无法真正理解知识、行为与环境之间的因果关系。这种模型方式会导致策略偏差,难以在现实环境中以可解释、稳健且可靠的方式运行。因此,具身智能体需具备世界知识,能够自主进行因果推理。通过交互了解世界并利用溯因推理学习其运作规律,可进一步增强多模态具身智能体在复杂环境中的适应性和决策可靠性。 - -**6)持续学习** - -在机器人应用中,持续学习对于在多样环境中部署机器人学习策略至关重要,但目前仍是一个未充分探索的领域。虽然近期一些研究探讨了持续学习的子主题(如增量学习、快速运动适应和人类在环学习),但这些方案多针对单一任务或平台,尚未应用于基础模型。未来的研究方向包括: - -• 在微调最新数据时混合使用不同比例的先前数据分布,以减轻灾难性遗忘; - -• 从先前分布中开发高效原型或课程用于任务推理; - -• 提高在线学习算法的训练稳定性和样本效率; - -• 将大容量模型无缝集成到控制框架中,以实现实时推理,可通过分层学习或慢-快控制实现。 - -**7)统一评估基准** - -虽然有许多基准可评估低层次控制策略,但它们在评估的技能方面差异较大,且对象和场景往往受限于模拟器的约束。为全面评估具身模型,需要采用包含多种技能的基准,使用真实模拟器进行评估。在高层任务规划器方面,许多基准主要通过问答任务评估规划能力。然而,理想的评估方式是结合高层任务规划器和低层次控制策略来执行长期任务并测量成功率,而不仅依赖于对规划器的孤立评估。这种综合方法提供了对具身AI系统能力的更全面的评估。 diff --git a/source/_posts/Diffusion.md b/source/_posts/Diffusion.md deleted file mode 100644 index 7606f99..0000000 --- a/source/_posts/Diffusion.md +++ /dev/null @@ -1,75 +0,0 @@ ---- -title: "Diffusion相关知识" -date: 2025-10-08 04:03:25 -updated: 2025-10-18 16:42:19 -mathjax: true -tags: - - 深度学习 - - Diffusion -categories: 深度学习 -comments: false ---- -# Diffusion学习 - -- [x] [知乎DDIM&DDPM](https://zhuanlan.zhihu.com/p/666552214) -- [x] [HuggingFace Diffusion公开课](https://github.com/huggingface/diffusion-models-class.git) -- [x] [b站讲的很好的DDPM](https://www.bilibili.com/video/BV1p24y1K7Pf/?vd_source=fc131029c76216a5e8da1df9dbb8fea1) - -# HuggingFace Diffusion Models Course - -[HuggingFace Diffusion公开课](https://github.com/huggingface/diffusion-models-class.git) - -# DDPM - Denoising Diffusion Probabilistic Models - -DDPM 用 $x_t$ 找到 $x_{t-1}$ 的**分布**,再从这个分布中**采样**得到一个 $x_{t-1}$,循环往复就得到了一个 $x_0$。 - -为什么推理速度慢: - -- DDPM 有一个超参数 $T$,马尔可夫链(Markov chain)的总长度(通常 $T=1000$),需要采样1000步才能得到一个好的图像。 - -- $T$ 设置小一些(如100或50)不能加速的原因如下: - 由DDPM中单步加噪公式: - $$x_t=\sqrt{\alpha_t}x_{t-1}+\sqrt{1-\alpha_t}\epsilon$$ - 根据马尔可夫性质,可以由 $x_0$ 一步得到 $x_t$,每次增加一个很小的噪声: - $$x_t=\sqrt{\bar{\alpha}_t}x_{0}+\sqrt{1-\bar{\alpha}_t}\epsilon\ , \quad \bar{\alpha}_t=\alpha_1\alpha_2\alpha_3...\alpha_{t-1}\alpha_t$$ - 其中希望 $\alpha_t \rightarrow 1, \alpha_t \lt 1$(可能取 0.9),在单步加噪时候尽量保留原图的样子,然后加一个很小的噪声。 - 同时,希望 $\bar{\alpha}_T \rightarrow 0$ 这样 $x_T \rightarrow \epsilon \sim \mathcal{N}(0,1)$,这样这个马尔可夫链的末端就是一个标准正态分布。要想满足上面两点,$T$ 必须足够大,所以不能减小 $T$ 的设定值。 - -- 能不能跳步 reverse?(例如 $x_T \rightarrow x_{T-5} \rightarrow ... \rightarrow x_0$) - 不能,原因如下: - DDPM的目标是为了拟合一个概率分布 $p(x_{t-1}|x_0,x_t)$,这个目标分布(后验分布)通过贝叶斯公式推导为严格的高斯分布: - $$p(x_{t-1} \mid x_t, x_0) = \mathcal{N}(x_{t-1}; \tilde{\mu}_t(x_t, x_0), \tilde{\beta}_t I)$$ - 其中均值 $\tilde{\mu}_t = \frac{\sqrt{\alpha_t}(1-\bar{\alpha}_{t-1})}{1-\bar{\alpha}_t} x_t + \frac{\sqrt{\bar{\alpha}_{t-1}}\beta_t}{1-\bar{\alpha}_t} x_0$。 - 该数学推导**强依赖于马尔可夫假设**。在马尔可夫链中,当前状态的转移只与严格相邻的前一个状态有关。如果强制跨步(跳过中间的 $t$),则破坏了马尔可夫链的转移概率结构,导致网络预测的单步噪声 $\epsilon_\theta$ 无法匹配跨步后的真实后验分布,从而引发严重的误差累积。 - -# DDIM - Denoising Diffusion Implicit Models - - -**核心思想:打破马尔可夫假设** - -DDIM 的核心贡献在于证明了:只要前向过程的边缘分布 $q(x_t|x_0)$ 与 DDPM 保持一致(即 $x_t$ 仍然可以写成 $\sqrt{\bar{\alpha}_t}x_0 + \sqrt{1-\bar{\alpha}_t}\epsilon$),就可以构造出非马尔可夫的前向过程。这种构造使得模型可以使用与 DDPM 完全一致的目标函数进行训练,但在采样时解除了严格的步步相连限制。 - -**采样公式重构** - -DDIM 推导出了一个新的广义反向采样公式: - -$$x_{t-1} = \sqrt{\bar{\alpha}_{t-1}} \underbrace{\left( \frac{x_t - \sqrt{1 - \bar{\alpha}_t} \epsilon_\theta(x_t, t)}{\sqrt{\bar{\alpha}_t}} \right)}_{\text{预测的 } x_0} + \underbrace{\sqrt{1 - \bar{\alpha}_{t-1} - \sigma_t^2} \cdot \epsilon_\theta(x_t, t)}_{\text{指向 } x_t \text{ 的方向}} + \underbrace{\sigma_t \epsilon_t}_{\text{随机噪声}}$$ - -该公式逻辑上分为三项: - -1. 用当前 $x_t$ 和网络预测的噪声去估算出的原图 $x_0$。 -2. 指向 $x_t$ 方向的梯度修正项。 -3. 注入的方差大小为 $\sigma_t$ 的随机噪声。 - -**超参数 $\sigma_t$ 的意义** : $\sigma_t$ 决定了生成过程的随机性: -- 当 $\sigma_t = \sqrt{\frac{1-\bar{\alpha}_{t-1}}{1-\bar{\alpha}_t}} \sqrt{1-\frac{\bar{\alpha}_t}{\bar{\alpha}_{t-1}}}$ 时:公式退化回标准的 **DDPM**,具备完整的随机性。 -- 当 $\sigma_t = 0$ 时:随机噪声项消失,整个逆向生成过程变成**确定性**的(Deterministic)。这就是 **DDIM**。由于它是确定性常微分方程(ODE)的近似求解,给定相同的初始噪声 $x_T$,最终生成的 $x_0$ 是固定的。 - -**为什么 DDIM 可以跳步加速?** - -因为 DDIM 的生成公式只依赖于时间步 $t$ 和 $t-1$ 对应的先验参数 $\bar{\alpha}$(而不是像 DDPM 那样依赖 $\alpha_t$ 的马尔可夫单步递推),我们完全可以定义一个更短的时间步子序列 $\tau$(例如 $S=50$,序列为 $\tau_1, \tau_2, ..., \tau_S$)。 - -在跳步采样时,只需将公式中的 $t$ 和 $t-1$ 替换为子序列中的相邻步 $\tau_i$ 和 $\tau_{i-1}$: - -$$x_{\tau_{i-1}} = \sqrt{\bar{\alpha}_{\tau_{i-1}}} \left( \frac{x_{\tau_i} - \sqrt{1 - \bar{\alpha}_{\tau_i}} \epsilon_\theta(x_{\tau_i}, \tau_i)}{\sqrt{\bar{\alpha}_{\tau_i}}} \right) + \sqrt{1 - \bar{\alpha}_{\tau_{i-1}} - \sigma_{\tau_i}^2} \cdot \epsilon_\theta(x_{\tau_i}, \tau_i) + \sigma_{\tau_i} \epsilon$$ - diff --git "a/source/_posts/GIT \345\270\270\347\224\250\345\221\275\344\273\244.md" "b/source/_posts/GIT \345\270\270\347\224\250\345\221\275\344\273\244.md" deleted file mode 100644 index 96b937b..0000000 --- "a/source/_posts/GIT \345\270\270\347\224\250\345\221\275\344\273\244.md" +++ /dev/null @@ -1,349 +0,0 @@ ---- -title: "GIT 常用命令" -date: 2025-05-02 04:20:18 -updated: 2025-05-02 04:25:00 -tags: - - GIT - - 常用软件 - - 终端命令 -categories: 终端相关 -comments: false ---- -# 写在前面 - -`git` 作为 版本管理软件 还是很常用的 但是感觉每次要用什么的时候都不太熟练 还要麻烦去查 所以记录一下常用的 如果以后还有的话继续补充 。 - -目前我常用的命令主要分为 [本地](#本地命令) 和 [远程](#远程命令) ,还有一些在新设备上的 [配置](#配置命令) 命令 这个就放在最后 使用频率很低。 - ---- -# 本地命令 - -## git-add - -`add` 就是把 本地的 文件修改 添加到 暂存区(stage),只是加到暂存区 还没有 `commit` 就没有添加到版本库 没有提交的 `commit id` ,stage 如果不想要了可以用 [reset](#git-reset) 恢复。 -```shell -git add . -git add -git add -p # 这个是交互式 添加文件的部分到 stage -``` -还有一个 把本地和远程仓库连接起来的在 [add remote](#git-add-remote) -## git-commit - -`commit` 就是把 stage 中的修改 提交到 本地 版本库 中。 -```shell -git commit -m "commit msg" -git commit --amend # 修改上一次的提交信息 把新的更改 合并到上一次提交 -``` - -## git-checkout - -`checkout` 用于 对 分支 的操作:切换分支 -```shell -# 切换到 branch 分支上 -git checkout - -# 撤销 仓库中 被追踪(不包括新建的文件)文件的修改(无论有没有 add) -git checkout . - -# 创建一个新分支并立即切换到该分支 -git checkout -b -``` - -创建并且切换 -```shell -git checkout -b -``` - -把文件切换到 指定的 commit-id 的状态 这个可以用 [diff](##git-diff) -```shell -git checkout -- -``` - -## git-branch - -`git branch` 用于查看、创建或删除分支: -```shell -# 查看本地所有分支 -git branch - -# 查看本地和远程的所有分支 -git branch -a - -# 查看本地分支及其最新提交 -git branch -v - -# 查看分支的详细信息(包括分支的最后一次提交、状态等) -git branch -vv - -# 创建一个新分支,但不切换 -git branch - -# 创建一个新分支并立即切换到该分支 -git checkout -b - -# 删除本地分支 -git branch -d # 删除已合并的分支 -git branch -D # 强制删除未合并的分支 - -# 重命名当前分支 -git branch -m - -# 查看分支之间的差异 -git diff -``` - -## git-log - -`git log` 用于查看提交历史。列出从当前分支的最新提交开始,按时间倒序排列的所有提交记录。可以查看每个提交的详细信息,提交哈希、作者、日期以及提交信息。 -```shell -# 查看当前分支的提交历史 -git log - -# 查看简洁的提交历史(只显示提交哈希和提交信息) -git log --oneline - -# 显示提交历史,并且包括每个提交的修改差异 -git log -p - -# 查看某个特定文件的提交历史 -git log - -# 查看某个范围内的提交记录,比如最近10个提交 -git log -n 10 - -# 查看某个作者的提交历史 -git log --author="author-name" - -# 查看某个提交后所有的提交记录 -git log .. -``` - - -## git-diff - -`git diff` 用于查看工作区、暂存区与版本库之间的差异。它可以帮助你在提交之前查看文件修改内容,或者查看某次提交和当前工作区的区别。 -```shell -# 查看工作区和暂存区之间的差异 就是上次 add 之后又改了什么 -git diff - -# 查看暂存区和最近一次提交之间的差异( add 比 上次 commit 改了什么 -git diff --cached - -# 查看工作区与某次提交之间的差异 -git diff - -# 查看两个提交之间的差异 -git diff - -# 查看某个文件的差异 -git diff - -# 查看某个文件在暂存区和当前工作区的差异 -git diff --cached -``` - -## git-status - -查看当前 仓库 状态:分支 有哪些 更改 暂存 未被追踪 -```shell -git status -``` - -## git-reset - -`reset` 用于撤销提交和修改 重置 stage 或者当前分支,下面这种 `HEAD~1` 的用法 就是上一个 commit , 也可以直接用 `commit-id` 回退到指定版本 -```shell -# 撤销 当前 branch 的最后一次 commit 和 add, commit 还是修改过的 -git reset HEAD~1 # 这个就是 --mixed - -# 撤销最后一次 commit ,add 还在,文件修改也还在 -git reset --soft HEAD~1 - -# 撤销 commit 和 add 恢复文件到上个状态 !!!会把文件修改也给删掉 -git reset --hard HEAD~1 -``` - -## git-merge - -合并分支到当前分支 : -```shell -# 把指定 branch 的 提交 合并到当前的 分支 -git merge - -# 如果有无法解决的冲突 就放弃合并 -git merge --abort - -# 非快进合并,将合并历史记录保留下来,方便回溯 -git merge --no-ff -``` - -## git-rebase - -把一个分支的修改应用到另一个分支 -```shell -# 将 当前分支 的提交 应用到 指定分支 branch 上,达到合并的效果,但不会生成多余的合并提交。 -git rebase - -# 当 rebase 中遇到 冲突 并解决后,继续rebase -git rebase --continue - -# 取消 rebase 恢复到 rebase 之前的状态 -git rebase --abort -``` - ---- -# 远程命令 -## git-add-remote - -`git add remote` 用于将远程仓库链接到本地仓库,以便可以推送和拉取代码。每个远程仓库会有一个名字(通常为 `origin`),可以通过这个命令将本地仓库与远程仓库建立连接。`name` 一般是 `origin` -```shell -# 添加远程仓库 -git remote add - -# 查看远程仓库信息 -git remote -v - -# 修改远程仓库的 URL -git remote set-url - -# 移除远程仓库 -git remote remove -``` - -## git-push - -`git push` 用于将本地的提交推送到远程仓库。默认情况下,`git push` 会将当前分支的提交推送到远程仓库中对应的分支。 -```shell -# 将本地的当前分支推送到远程仓库的相应分支 -git push - -# 将本地的指定分支推送到远程仓库 -git push -git push origin main -# 第一次要加上 -u 参数 --set-upstream -# 将本地分支与远程分支建立跟踪关系。-u 后 origin/main 就和本地 main 关联了 -# 之后就可以直接 git push / pull - -# 强制推送,覆盖远程仓库的历史 -git push --force - -# 推送所有本地分支 -git push --all -``` - -## git-pull - -`git pull` 用于从远程仓库拉取最新的更改并自动合并到本地分支。它实际上是 `git fetch` 和 `git merge` 的组合,先拉取远程的更改,再将其合并到当前分支。 -```shell -# 从远程仓库拉取当前分支的最新提交并合并 -git pull - -# 拉取并合并指定分支的更改 -git pull - -# 只拉取远程仓库的更新,不进行合并 -git pull --no-merge - -# 拉取并与当前分支重新合并(常用于避免合并提交) -git pull --rebase -``` - - ---- -# 配置命令 - -git 的 下载 [Download git](https://git-scm.com/downloads) - -版本检查: -```shell -git --version -``` - -## git-config - -列出当前的 config 信息: -```shell -git config --list -``` - -设置 **全局** 信息 ,第三个是对 git 设置全局代理 : -```shell -git config --global user.name "zip95297" -git config --global user.email "zip95297@gmail.com" -git config --global http.proxy socks5://127.0.0.1:7890 -``` - 这个代理端口的位置 要查看科学上网的 开放端口,或者 科学上网软件 打开了允许局域网连接 也可以用 局域网中开了代理的设备(一般用于服务器) - -对某一个仓库设置信息(需要在 仓库目录 下使用): -```shell -git config --local user.name "zip95297" -git config --local user.email "zip95297@gmail.com" -``` - -还有一个 `init.defaultbranch` 的变量记得设置为 main (好像下载之后就会有这个) - -配置对 `github.com` 的 ssh 连接,之后就可以用 ssh 的 URL 访问了: -```shell -ssh-keygen -t rsa -C "zip95297@gmail.com" # 创建 公私钥 对 -# 然后在 github > settings > ssh and gpg keys 中添加自己的公钥,之后测试链接就通了 -√ -> zip @ ~ % ssh -T git@github.com -Hi zip95297! You've successfully authenticated, but GitHub does not provide shell access. -``` -配置之后 `git clone` 等 可以用 git 或者 http 连接。 - -# 关于从版本库或者工作区中删除 - -有三个命令主要: clean (清除untracked),restore(删工作区) ,restore --staged (移除暂存区到工作区) - -## 删除 !新建!文件 (未跟踪) - -clean 是用来删 没被跟踪的 文件和目录 也就是:( 对 已经跟踪 的文件不会有影响) -- 没有被add过 -- 在历史提交中没有(untracked) -下面是具体用法 -```shell -# 查看哪些文件将被删除(--dry-run|彩排的意思) -git clean -n - -# 强制删除 -git clean -f - -# 删除整个目录目录 -git clean -d - -# 包括 ignore 的 -git clean -x - -# 只删除 ignore 的 -git clean -X -``` - -## 恢复到 add 之后的状态 (放弃工作区) - -恢复到 add 之后的 也就是:放弃 工作区修改,保留 暂存区修改 (add 过的会保存) -```shell -git restore -``` - -## 恢复到 上次commit 之后的状态 (放弃工作区和暂存区) - -恢复到 上次 commit 之后的状态分为两步: -1. 放弃暂存区 -2. 放弃工作区 -```shell -git restore --staged # 放弃暂存区 -git restore # 放弃工作区 -``` - -## git ignore 中新添了 规则 - -可以直接指定 从暂存区中 remove 从 **版本库** 中移除: 不删除本地文件 (如果没有 --cached 参数 文件也会被删除) -```shell -git rm --cached -``` - -或者:把所有内容从 版本库 中移除跟踪 重新add -```shell -git rm --cached . -git add . -``` diff --git "a/source/_posts/Linux\344\270\255\347\275\221\347\273\234\347\233\270\345\205\263\347\232\204\345\221\275\344\273\244.md" "b/source/_posts/Linux\344\270\255\347\275\221\347\273\234\347\233\270\345\205\263\347\232\204\345\221\275\344\273\244.md" deleted file mode 100644 index 6016332..0000000 --- "a/source/_posts/Linux\344\270\255\347\275\221\347\273\234\347\233\270\345\205\263\347\232\204\345\221\275\344\273\244.md" +++ /dev/null @@ -1,67 +0,0 @@ ---- -title: "Linux中网络相关的命令" -date: 2025-06-27 20:51:47 -updated: 2025-07-11 20:59:43 -tags: - - 网络 - - 常用软件 - - 终端工具 -categories: 终端相关 -comments: false ---- -# 写在前面 - -目前用到的有: -- ping -- ifconfig -- curl -- netstat -- nc -- route -- lsof - -软件: -- frp -- chisel -- rsync & scp -- proxychains - -# 命令介绍 - -## route - -如果有多张网卡启用: -- en0 wifi -- en12 有线宽带 - -使用这个命令添加路由:使 向 10.10.5.4 的流量 走 10.11.3.254 网关 gate_way -```shell -sudo route -n add 10.10.5.4 10.11.3.254 -``` - -查看路由表:[netstat](#netstat) - -## netstat - -查看路由表: -```shell -netstat -rn -``` - -## lsof - -查看端口被哪个进程占用: -```shell -lsof -i : -``` - -## curl - -几个好用的URL: -- cip.cc -- ifconfig.me -- ipinfo.io - - -## nc - diff --git "a/source/_posts/MacOS&Linux\344\270\255\347\232\204\345\267\245\345\205\267.md" "b/source/_posts/MacOS&Linux\344\270\255\347\232\204\345\267\245\345\205\267.md" deleted file mode 100644 index 2a42692..0000000 --- "a/source/_posts/MacOS&Linux\344\270\255\347\232\204\345\267\245\345\205\267.md" +++ /dev/null @@ -1,199 +0,0 @@ ---- -title: "MacOS&Linux中的工具" -date: 2026-1-02 04:14:55 -updated: 2026-1-02 04:14:55 -mathjax: true -tags: - - 终端工具 - - 常用软件 -categories: 实用技巧 -comments: false ---- -# 一些 CLI 小工具 - -* [x-cmd](https://cn.x-cmd.com/start/) -* **proxychains**: 用于代理 CLI 中的命令。在 Clash 中如果不打开增强模式,CLI 中的应用流量不会被代理,使用 proxychains 可以为 CLI 应用添加代理。注意:在 macOS 中,由于 SIP(系统完整性保护),`/usr/bin` 中的命令行程序并不会被代理。在局域网中,proxychains 的 `ip:port` 配置可以是一个开了允许局域网连接的代理设备上的代理端口。 -* **bat**: `cat` plusplus,可以用 `-l` 参数指定语法。 -* **btop**: TUI top 软件。 -* **ranger**: CLI 中的文件 explorer。 -* **yazi**: 比 ranger 更好用,功能更丰富。 -* **eza**: 可以显示 Nerd Font 的 `ls` 工具,可以用 `alias` 替代 `ls`, `ll`, `la` 等。 -* **fastfetch**: 展示系统的一些信息。 -* **fzf**: 模糊搜索 (fuzzyfind)。可以配合管道交互式查找,如果仅输入 `fzf` 则是查找文件。 -* **starship**: 用于配置 SHELL PROMPT。 -* **zoxide**: 比 zsh plugin 的 jump 更好用的快速跳转。 -* **thefuck**: 输错命令了可以 `fuck` 一下自动纠正。 -* **ncdu**: 用于查看目录中项目的大小。 -* **tldr**: `man` 手册的易读版本。 -* **tmux**: 终端复用工具,无须多言。 -* **neovim**: `vim` plus plus。 -* **tree**: 树状展示目录结构。 -* **sshfs**: 挂载远程目录。 -* **sudo cupsctl WebInterface=yes**: 打开 Command Line 打印的网页接口。 -* **pgrep**: 专门查进程的 grep,例如 `pgrep -au zjb frp`。 -* **ffmpeg**: 音视频处理工具。 -* **7-zip (sevenzip)**: 压缩工具。安装:`brew install 7-zip`,用法:`7zz`。 -* **d (mac 自带)**: 先用 `d` 列出来最近访问的文件夹,输入前面的数字进入该文件夹。 -* **where / which**: 查找命令路径。 -* **whence -v / type**: 显示函数在哪个文件。 -* **iperf3**: 用于测试网速的小工具。 - -### Zsh 配置参考 - -**插件配置 (`~/.zshrc`)**: -```sh -plugins=( - git - web-search - zsh-autosuggestions - sudo - # autojump - autoupdate - zsh-syntax-highlighting -) -``` - -**Prompt 与耗时统计配置**: -```bash -source $ZSH/oh-my-zsh.sh - -export PS1="%(?.%F{green}√.%F{red}?%?)%f -> zjb @ %B%F{white}%1~%f%b %# " -export RPROMPT="%*" - -# 记录命令开始时间(毫秒) -preexec() { - timer_start=$(perl -MTime::HiRes=time -e 'printf("%.0f\n", time * 1000)') -} - -# 命令执行后计算耗时并更新右提示符 -precmd() { - if [[ -n "$timer_start" ]]; then - local timer_end=$(perl -MTime::HiRes=time -e 'printf("%.0f\n", time * 1000)') - local elapsed=$((timer_end - timer_start)) - elapsed=$((elapsed-21)) - local mins=$((elapsed / 60000)) - local secs=$(( (elapsed % 60000) / 1000 )) - local ms=$((elapsed % 1000)) - - local duration="" - [[ $mins -gt 0 ]] && duration+="${mins}m " - [[ $secs -gt 0 ]] && duration+="${secs}s " - [[ $ms -gt 0 ]] && duration+="${ms}ms" - - if [[ -n "$duration" ]]; then - RPROMPT="%(?.%F{green}󱄛 .%F{red}󱄛 )${duration}%f %*" - else - RPROMPT="%*" - fi - - unset timer_start - else - RPROMPT="%*" - fi -} -``` - ---- - -# Mac 上的软件 - -* **ice**: Menu bar 管理软件。 -* **loop**: 窗口管理软件。 -* **itsycal**: Menu bar 上的日历小软件。 -* **AlDente**: 高级电池管理。 -* **AppCleaner**: 软件卸载辅助。 -* **AutoRaise**: 激活 cursor 下的窗口。 -* **BetterDisplay**: 显示器 DDC 控制。 -* **ClashX Pro**: 代理工具,无须多言。 -* **Easydict**: 好用的全局调用的翻译软件。 -* **Folder Preview**: Space 预览目录内容。 -* **IINA**: 视频播放软件。 -* **Input Source Pro**: 自动输入法切换。 -* **iTerm2**: 第一个使用的 Mac 终端软件。 -* **kitty**: 很好用的终端,第二个使用的。 -* **LuLu**: Mac 中的防火墙,可以高度自定义。 -* **Mac Mouse Fix**: 鼠标加强工具,可以为使用鼠标和触控板时单独设置自然滚动的开关。 -* **Maccy**: 剪贴板管理(已被 Raycast 代替)。 -* **MediaMate**: Notch bar 美化。 -* **Microsoft To Do**: 待办事项管理。 -* **OBS**: 录屏软件。[配置教程](https://www.bilibili.com/opus/1044838772918714377)(注:记得把视频中的画布分辨率设置成显示器硬件分辨率,然后 10bit 屏幕设置为 main10)。 -* **Obsidian**: Markdown 笔记软件。 -* **Raycast**: Spotlight 的代替软件。 -* **Snipaste**: 截图软件。 -* **Topit**: 窗口置顶软件,用的很少但是不能没有。 -* **VS Code**: 代码编辑器,无须多言。 -* **RWTS PDFwriter**: 虚拟打印机。[GitHub Repo](https://github.com/rodyager/RWTS-PDFwriter)。卸载路径:`/Library/Printers/RWTS/PDFwriter/uninstall`(注:只能新建一个目录打印)。 - -### 系统清理与优化 - -如果用的时间长变卡了,可以执行以下操作: - -```shell -# 重启 Dock 和 Finder -killall Dock Finder - -# 清理缓存内存 inactive -sudo purge - -# 注意:不要用 sudo -# 删除用户目录下的应用缓存文件 -rm -rf ~/Library/Caches/* - -# 清理用户缓存但更安全的方式 -find ~/Library/Caches -type f -delete -``` - ---- - -# Mac 上的快捷键与系统配置 - -### 常用快捷键 - -| 快捷键 | 功能 | -| :--- | :--- | -| `Cmd + Opt + V` | Notch | -| `Cmd + Opt + B` | 启动台 | -| `Cmd + Opt + D` | Dock | -| `Cmd + Opt + T` | 台前调度 | -| `Opt + Space` | 拖拽预览中的 PDF | - -### 系统配置与命令记录 - -2. **SSH 访问**: 系统设置 -> 共享 -> 远程登录,设置手机 SSH 到电脑。 -3. **重要文件**: `~/Respository/OS_study.dmg` -4. **DNS 屏蔽**: 在 `/etc/hosts` 中修改本地 DNS 屏蔽 Apple 的更新。 -5. **Dock 自动隐藏延迟控制**: - ```sh - # 取消延迟 - defaults write com.apple.dock "autohide-delay" -float "0" && killall Dock - # 恢复默认 - defaults delete com.apple.dock "autohide-delay" && killall Dock - ``` -6. **手势拖动窗口配置**: - ```sh - # 用 Ctrl + Cmd 拖动窗口 - defaults write -g NSWindowShouldDragOnGesture -bool true - # 恢复默认 - defaults delete -g NSWindowShouldDragOnGesture - ``` -7. **关闭 Cursor View UI 特效**: - ```sh - sudo defaults write /Library/Preferences/FeatureFlags/Domain/UIKit.plist redesigned_text_cursor -dict-add Enabled -bool NO - ``` -8. **关闭输入法切换动画**(似乎无效): - ```sh - defaults write kCFPreferencesAnyApplication TSMLanguageIndicatorEnabled -bool false - ``` -9. **参考文档**: [Mac 张](https://qnswkjn28n.feishu.cn/wiki/T8uJwQH4YiIy7BkHyQpczpyDnug) - ---- - -# 一些 Tips - -* **静态路由配置**: 如果 `en0` 连接了 WiFi,`en12` 连接了有线网络,直接 `ssh 10.10.5.4` 可能根据路由表(使用 `route -rn` 查看)走默认的 `en12`。如果这个 IP 实际在 WiFi 环境下,可以用以下命令向路由表中添加静态路由,指定访问 `10.10.5.4` 的网关: - ```sh - sudo route -n add 10.10.5.4 10.11.3.254 - ``` - *(注:后面的 IP 是该 WiFi 环境中的网关,可以在 `netstat` 中查看。)* - -* **UI 异常记录**: 有时候这个图标消失,怀疑是 CursorUI 进程出问题了。 diff --git a/source/_posts/ResShift.md b/source/_posts/ResShift.md deleted file mode 100644 index ae3e985..0000000 --- a/source/_posts/ResShift.md +++ /dev/null @@ -1,110 +0,0 @@ ---- -title: "ResShift 论文阅读" -date: 2025-07-15 20:04:50 -updated: 2025-07-15 20:05:12 -mathjax: true -tags: - - 深度学习 - - 超分重建 - - 论文阅读 - - Diffusion -categories: 深度学习 -comments: false ---- -# ResShift - -## Motivation - -主要想法:缩短马氏链,加速反向传播过程 - -传统的方法是:从高斯分布中采样 Pure Noise 然后逐步 reverse 得到 一个图像。 - -主要问题都是 沿用了 原本 DDPM 中的马氏链 (太长,从 pure noise 开始还原)导致要经过很多次迭代才能 生成出一张图片。而且 reverse 过程过长 还会导致 生成的图像过于平滑。 -(?可能是因为 纹理细节被当做噪声给 过滤掉 ? 如何 balance 高频 和 噪声?) -(?多次经过 diffusion 的reverse 相当于 过了很多次低通滤波? ) - -> One common approach involves inserting the LR image into the input of current diffusion model. and retraining the model from scratch on the training data for SR. 一种方法是 把 LR 插入到输入中 (在Google的Image Super-Resolution via Iterative Refinement )论文中 将 Pure Noise 和 LR concat 然后在 Unet 中 做 reverse - -> Another popular way is to use an unconditional pre-trained diffusion model as a prior and modify its reverse path to generate the expected HR image. 通过 LR 引导 反向过程 ,类似于 LDM 中的 Attn 的作用,但是也是从 Pure Noise 开始的 - -在 超分 任务中 目标是生成 HR , 有先验数据 LR 。通过利用 LR 来得到 生成的HR图像,来缩短马氏链:类似于 直接截断 Pure Noise -> LR(这个LR 不直接是 LR 而是马氏链中接近的一个 节点) ,从 LR 开始进行 reverse 得到 HR。 - -## 前向过程 - -记: - HR 为 $x_0$ , LR 为 $y_0$ ,两者之间距离 Error 为 $e_0$ - -论文 的 核心想法 是:transit from $x_0$ to $y_0$ by gradually shifting their residual $e_0$ through a Markov chain with length T. - -参数序列 $\{\eta_t\}^T_{t-1}$ 随 t 单调增,t=1 时$\eta=0$,t=T 时$\eta=1$ - -逐步加噪: -$$ -q(x_t\mid x_{t-1},y_0)=N(x_t;x_{t-1}+\alpha_te_0,k^2\alpha_tI) -$$ -其中 $\alpha_t=\eta_t-\eta_{t-1}$ , $k$ 用来控制方差。通过逐步加噪的公式(正态分布可加性)可得到:(论文中证明任意步长t的边际分布解析可积) - -一步加噪: -$$ -q(x_t\mid x_0,y_0) = N(x_t;x_0+\eta_t e_0,k^2\eta_t I) -$$ -## 反向过程 - -$$ -p(x_0\left|y_0\right.)=\int p(x_T\left|y_0\right.)\prod_{t=1}^Tp_\theta\left(x_{t-1}\left|x_t\right.,y_0\right.)dx_{1:T} -$$ -在这个 式子中 $p(x_T\left|y_0\right.)\approx N(x_T\left|y_0\right.,k^2I)$ , $p_\theta\left(x_{t-1}\left|x_t\right.,y_0\right.)$ 是 diffusion模型 其中 $\theta$ 就是可学习参数。 - -反向过程 的目的就是为了 估计 给出 $y_0$ 为条件 的 $x_0$ 的后验分布。 - -其中的 $x_T$ 在这个里就是 加噪T步 的图像,整个过程就是从 $x_t$ 还原到 $x_0$ - -## 参数的优化 - -根据 扩散模型文献中 的假设 $p_\theta$ 为: -$$ -p_{{\theta}}({x}_{t-1}|{x}_t,{y}_0)={N}({x}_{t-1};{\mu}_{{\theta}}({x}_t,{y}_0,t),{\Sigma}_{{\theta}}({x}_t,{y}_0,t)) -$$ - -优化目标就是,最小化证据下界: -$$ -\min_{{\theta}}\sum_tD_{{KL}}\left[q({x}_{t-1}|{x}_t,{x}_0,{y}_0)\|p_{{\theta}}({x}_{t-1}|{x}_t,{y}_0)\right] -$$ - -其实就是让模型 **预测的** 前一时刻 图像 靠近 **真实的**前一时刻图像的 分布。 - -反向过程和参数的优化和传统的 Diffusion 几乎没什么区别 - -## 噪声策略 Noise Schedule - -根据前项过程的式子可以看出,噪声的方差由 $\kappa\sqrt{\eta_T}$ 控制 。在LDM中提到第一步加噪的方差应该足够小(e.g., 0.04 in LDM),从而确保 $q(x_1|x_0,y_0)\approx q(x_0)$ , $\eta_T$ 应该尽可能接近1 (前向过程中的均值)。所以文中的 $\eta_T$ Schedule 如下: - 当T=1 时: $\eta_1=min((\frac{0.04}{k})^2,0.001)$ - 当 $T\in[2,T-1]$ , $\sqrt{\eta_t} = \sqrt{\eta_1} \times b_0^{\beta_t}$ , $\beta_t= (\frac{t-1}{T-1})^p \times (T-1) , b_0=exp[\frac{1}{2(T-1)}log \frac{\eta_T}{\eta_1}]$ ,p 是超参数 - - - -# 代码中的细节 - -[ResShift Repo](https://github.com/zsyOAOA/ResShift) 原文仓库在此处 - -用了 VQ-VAE 作为 autoencoder - -LPIPS 通常是一个训练好的 感知相似度模型 一个计算相似度的方法 - -UNetModelSwin 用这个作为 diffusion model - -## 数据 - -### 训练数据 - -训练时候用的 256x256的 HR 图像 根据 LDM 从 ImageNet 的训练集中随机裁剪出,然后使用 RealESRGAN 的 退化流程 合成的 LR 图像。 - -![](https://cdn.jsdelivr.net/gh/zip95297/zip95297.github.io@main/source/images/ResShift/Pasted%20image%2020250515211826.webp?raw=true) - -### 测试数据 - -基于常用退化模型合成了一个测试数据集,用了 ImageNet中取3000张,还有RealSR,和自己收集的 - -## 模型 - -使用 Unet 作为 Diffusion 的网络结构,用 Swin Transformer 块儿 替换 UNet中的 自关注层 diff --git a/source/_posts/SwinIR.md b/source/_posts/SwinIR.md deleted file mode 100644 index bf2cf38..0000000 --- a/source/_posts/SwinIR.md +++ /dev/null @@ -1,267 +0,0 @@ ---- -title: SwinIR 代码阅读 -date: 2025-03-16 15:32:09 -updated: 2025-04-30 00:42:24 -mathjax: true -tags: - - 深度学习 - - ViT - - SwinTransformer -categories: 深度学习 -description: 阅读SwinIR论文,在BasicSR详细阅读了一下SwinIR的代码 -comments: false ---- - - -# pipeline - -输入x = \[ B, C, H, W\] -## ClassicSR (pixcelShuffle) - -### 1.ConvFirst: 仅仅提高输入的纬度 (也是浅层特征提取 UpSample - -先用一个卷积`Conv2d(in_C,emd_dim,3,1,1)` 把输入通道数(一般是3)扩到embedding的channel 得到` embedding` 在这个过程中 输入图像的H, W都没有变化 `embedding` 大小为 \[B, emd_dim, H, W\] - -### 2. ForwardFeature - -```python -def forward_features(self, x): - x_size = (x.shape[2], x.shape[3]) - x = self.patch_embed(x) - if self.ape: - x = x + self.absolute_pos_embed - x = self.pos_drop(x) - for layer in self.layers: - x = layer(x, x_size) - x = self.norm(x) # b seq_len c - x = self.patch_unembed(x, x_size) - return x -``` -#### 1)PatchEmbedding -先将x后两个维度展平 \[B,emd_dim,H,W\] $\rightarrow$ \[B,emd_dim,H\*W\] -然后将第1个和第二个纬度交换位置 即\[B,H\*W, emd_dim] - -如果有的话 用 [[torch#nn. LayerNorm (embedding_dim)|nn.LayerNorm]] 对 x 做归一化 - -```python -def forward(self, x): - x = x.flatten(2).transpose(1, 2) # b Ph*Pw c - if self.norm is not None: - x = self.norm(x) - return x -``` - -然后对 x 做 [[torch#Dropout|Dropout]] - -#### 2) Transformer Layer (**Residual Swin Transformer Block**) -通过 设置个数 层layer 每层layer 是一个 **Residual Swin Transformer Block** -RSTB 由 一个 BasicLayer 作为主要部分 ,还有一些对数据流残差链接 以及卷积的基本模块 -对于BasicLayer中有depth个 **SwinTransformerBlock** -输入到这一层的 x 的 shape 是 \[ B, H\*W, emd_dim] -##### ResidulSwinTranformerBlock - RSTB - -一层 layer 包括 一个 RSTB ,在RSTB中: -1. x = basicLayer(x) -2. x = patch_unembed(x) -3. x = conv(x) -4. x = patch_embed(x) -###### BasicLayer -一个BL中有depth个 SwinTransformerBlock 在通过这些 SwinTransformerBlock 前后 x 的shape不发生改变 -###### PatchUnEmbeded -在PatchEmbed中\[ B, C, H, W] 的 张量 被 patchEmbed 为 \[ B, H\*W, emd_dim] 这一步是将这个张量重新view成 \[ B, emd_dim, H, W] 的形状来做下一步卷积 -```python -def forward(self, x, x_size): - x = x.transpose(1, 2).view(x.shape[0], self.embed_dim, x_size[0], x_size[1]) - return x -``` - -#### 3)UnEmbeded -对经过了注意力的 x LN之后 重新转换成 \[ B, emd_dim, H, W] 这个形状 - -### 3. 残差链接 - -在 \[1.ConvFirst] 中将原本的3通道图像转换成了 60通道 -在 \[2.ForwardFeature] 中的transformer 依旧保持 60通道以及输入大小 - -将 \[1.ConvFirst] 与 \[2.ForwardFeature] 的输出相加 -张量的大小依然是 \[B, emd_dim, H, W\] - -然后将残差链接后的 x 在进行一次卷积,这个卷积的定义如下: -```python -if resi_connection == '1conv': - self.conv_after_body = nn.Conv2d(embed_dim, embed_dim, 3, 1, 1) -elif resi_connection == '3conv': - # to save parameters and memory - self.conv_after_body = nn.Sequential( - nn.Conv2d(embed_dim, embed_dim // 4, 3, 1, 1), - nn.LeakyReLU(negative_slope=0.2, inplace=True), - nn.Conv2d(embed_dim // 4, embed_dim // 4, 1, 1, 0), - nn.LeakyReLU(negative_slope=0.2, inplace=True), - nn.Conv2d(embed_dim // 4, embed_dim, 3, 1, 1) - ) -``` - -这个卷积起到了局部特征融合的作用 - -第二种方式增加了模型的非线性建模能力: -1. 第一个 3×3 卷积:降维到 embed_dim // 4(压缩通道) -2. 中间 1×1 卷积:保持通道数不变(增加非线性建模能力) -3. 最后一个 3×3 卷积:恢复通道到原始 embed_dim - -### 4. UpSample - -下面是关于pixelShuffle的一些函数定义 -```python -self.conv_before_upsample = nn.Sequential( - nn.Conv2d(embed_dim, num_feat, 3, 1, 1), nn.LeakyReLU(inplace=True) -) -self.upsample = Upsample(upscale, num_feat) -self.conv_last = nn.Conv2d(num_feat, num_out_ch, 3, 1, 1) -``` - -在上采样之前 先做一个卷积和ReLU,然后进行上采样: -```python -class Upsample(nn.Sequential): -"""上采样模块 -Args: -scale (int): 缩放因子。支持的缩放因子:2^n和3 -num_feat (int): 中间特征的通道数 -""" -def __init__(self, scale, num_feat): - m = [] - if (scale & (scale - 1)) == 0: # scale = 2^n - for _ in range(int(math.log(scale, 2))): - # 先变深再 pixelshuffle - m.append(nn.Conv2d(num_feat, 4 * num_feat, 3, 1, 1)) - m.append(nn.PixelShuffle(2)) - elif scale == 3: - m.append(nn.Conv2d(num_feat, 9 * num_feat, 3, 1, 1)) - m.append(nn.PixelShuffle(3)) - else: - raise ValueError(f'scale {scale} is not supported.\ - Supported scales: 2^n and 3.') - super(Upsample, self).__init__(*m) -``` -先变深再[[torch#PixelShiffle|PixelShuffle]] 和下面轻量SR的很像 区别在于 对于scale = 2**n 这个逐步2倍放大,这一步仅仅把特征图的HW对其为scale之后的 但是通道数并没有对齐,之后还要再用一个卷积,达到目标通道数3 -```python -self.conv_last = nn.Conv2d(num_feat, num_out_ch, 3, 1, 1) -``` - -而下面LWSR的是 一步直接上采样到目标的通道数 pixelshuffle之后直接是目标图像 - - -## LightWeightSR (pixcelShuffleDirect) - -### 1.ConvFirst: 仅仅提高输入的纬度 (也是浅层特征提取 UpSample - -先用一个卷积`Conv2d(in_C,emd_dim,3,1,1)` 把输入通道数(一般是3)扩到embedding的channel 得到` embedding` 在这个过程中 输入图像的H, W都没有变化 `embedding` 大小为 \[B, emd_dim, H, W\] - -### 2. ForwardFeature - -```python -def forward_features(self, x): - x_size = (x.shape[2], x.shape[3]) - x = self.patch_embed(x) - if self.ape: - x = x + self.absolute_pos_embed - x = self.pos_drop(x) - for layer in self.layers: - x = layer(x, x_size) - x = self.norm(x) # b seq_len c - x = self.patch_unembed(x, x_size) - return x -``` -#### 1)PatchEmbedding -先将x后两个维度展平 \[B,emd_dim,H,W\] $\rightarrow$ \[B,emd_dim,H\*W\] -然后将第1个和第二个纬度交换位置 即\[B,H\*W, emd_dim] - -如果有的话 用 [[torch#nn. LayerNorm (embedding_dim)|nn.LayerNorm]] 对 x 做归一化 - -```python -def forward(self, x): - x = x.flatten(2).transpose(1, 2) # b Ph*Pw c - if self.norm is not None: - x = self.norm(x) - return x -``` - -然后对 x 做 [[torch#Dropout|Dropout]] - -#### 2) Transformer Layer (**Residual Swin Transformer Block**) -通过 设置个数 层layer 每层layer 是一个 **Residual Swin Transformer Block** -RSTB 由 一个 BasicLayer 作为主要部分 ,还有一些对数据流残差链接 以及卷积的基本模块 -对于BasicLayer中有depth个 **SwinTransformerBlock** -输入到这一层的 x 的 shape 是 \[ B, H\*W, emd_dim] -##### ResidulSwinTranformerBlock - RSTB - -一层 layer 包括 一个 RSTB ,在RSTB中: -1. x = basicLayer(x) -2. x = patch_unembed(x) -3. x = conv(x) -4. x = patch_embed(x) -###### BasicLayer -一个BL中有depth个 SwinTransformerBlock 在通过这些 SwinTransformerBlock 前后 x 的shape不发生改变 -###### PatchUnEmbeded -在PatchEmbed中\[ B, C, H, W] 的 张量 被 patchEmbed 为 \[ B, H\*W, emd_dim] 这一步是将这个张量重新view成 \[ B, emd_dim, H, W] 的形状来做下一步卷积 -```python -def forward(self, x, x_size): - x = x.transpose(1, 2).view(x.shape[0], self.embed_dim, x_size[0], x_size[1]) - return x -``` - -#### 3)UnEmbeded -对经过了注意力的 x LN之后 重新转换成 \[ B, emd_dim, H, W] 这个形状 - -### 3. 残差链接 - -在 \[1.ConvFirst] 中将原本的3通道图像转换成了 60通道 -在 \[2.ForwardFeature] 中的transformer 依旧保持 60通道以及输入大小 - -将 \[1.ConvFirst] 与 \[2.ForwardFeature] 的输出相加 -张量的大小依然是 \[B, emd_dim, H, W\] - -然后将残差链接后的 x 在进行一次卷积,这个卷积的定义如下: -```python -if resi_connection == '1conv': - self.conv_after_body = nn.Conv2d(embed_dim, embed_dim, 3, 1, 1) -elif resi_connection == '3conv': - # to save parameters and memory - self.conv_after_body = nn.Sequential( - nn.Conv2d(embed_dim, embed_dim // 4, 3, 1, 1), - nn.LeakyReLU(negative_slope=0.2, inplace=True), - nn.Conv2d(embed_dim // 4, embed_dim // 4, 1, 1, 0), - nn.LeakyReLU(negative_slope=0.2, inplace=True), - nn.Conv2d(embed_dim // 4, embed_dim, 3, 1, 1) - ) -``` - -这个卷积起到了局部特征融合的作用 - -第二种方式增加了模型的非线性建模能力: -1. 第一个 3×3 卷积:降维到 embed_dim // 4(压缩通道) -2. 中间 1×1 卷积:保持通道数不变(增加非线性建模能力) -3. 最后一个 3×3 卷积:恢复通道到原始 embed_dim - -### 4. UpSampleOneStep (为了节省参数 在 LightWerightSR 中 用 OneStep) - -经过残差相加之后,张量大小保持为 \[B, emd_dim, H, W],接下来通过 UpsampleOneStep 模块进行上采样以恢复图像的原始分辨率。该模块的设计目标是**参数量小、结构简单、适用于轻量化模型**,整体包括: - -1. 一个 Conv2d 层:将 emd_dim 通道映射到 (scale_factor^2) × num_out_ch 通道。(OneStep的体现) - -2. 一个 [[torch#PixelShiffle|PixelShuffle]] 层:把通道还原为输出图像的通道数,并进行空间上采样。 - -```python -class UpsampleOneStep(nn.Sequential): -"""UpsampleOneStep模块(与Upsample的区别在于它总是只有1conv + 1pixelshuffle) -用于轻量级SR以节省参数 -Args: -scale (int): 缩放因子。支持的缩放因子:2^n和3 -num_feat (int): 中间特征的通道数 -""" -def __init__(self, scale, num_feat, num_out_ch, input_resolution=None): - self.num_feat = num_feat - self.input_resolution = input_resolution - m = [] - m.append(nn.Conv2d(num_feat, (scale**2) * num_out_ch, 3, 1, 1)) - m.append(nn.PixelShuffle(scale)) - super(UpsampleOneStep, self).__init__(*m) diff --git a/source/_posts/hello-world.md b/source/_posts/hello-world.md new file mode 100644 index 0000000..821780c --- /dev/null +++ b/source/_posts/hello-world.md @@ -0,0 +1,38 @@ +--- +title: Hello World +--- +Welcome to [Hexo](https://hexo.io/)! This is your very first post. Check [documentation](https://hexo.io/docs/) for more info. If you get any problems when using Hexo, you can find the answer in [troubleshooting](https://hexo.io/docs/troubleshooting.html) or you can ask me on [GitHub](https://github.com/hexojs/hexo/issues). + +## Quick Start + +### Create a new post + +``` bash +$ hexo new "My New Post" +``` + +More info: [Writing](https://hexo.io/docs/writing.html) + +### Run server + +``` bash +$ hexo server +``` + +More info: [Server](https://hexo.io/docs/server.html) + +### Generate static files + +``` bash +$ hexo generate +``` + +More info: [Generating](https://hexo.io/docs/generating.html) + +### Deploy to remote sites + +``` bash +$ hexo deploy +``` + +More info: [Deployment](https://hexo.io/docs/one-command-deployment.html) diff --git a/source/_posts/kitty.md b/source/_posts/kitty.md deleted file mode 100644 index b678124..0000000 --- a/source/_posts/kitty.md +++ /dev/null @@ -1,49 +0,0 @@ ---- -title: "Kitty 终端模拟器" -date: 2025-06-06 05:16:18 -updated: 2025-07-24 05:16:34 -tags: - - 常用软件 - - 终端模拟器 - - kitty -categories: 终端相关 -description: kitty 是一个特别好用的终端模拟器 之前用的是iTerm2 换成kitty后更好用了! -comments: true ---- -# 写在前面 - -最近在用 kitty 终端 在研究怎么把它配置的更好用,dotfile打包到github上面 ,挂一个官方链接:[Kitty](https://sw.kovidgoyal.net/kitty/) 。 引用一篇写的很好的 博客 [BLOG](https://www.escapelife.site/posts/8e342b57.html) - -> Warning: - 在配置mapping时候某个键不生效 检查一下是不是 后面的配置覆盖了的问题 - -如果在 source 时候 exit 1 可以用 `bash -x ~/.bashrc` 或者 `zsh -x ~/.zshrc` 进入debug - -# Kitty - GPU 加速的终端 - -## kitten copyboard - -可以在所有服务器上面 `alias copy='kitten copyboard'` 然后就可以像 pbcopy 一样使用,具体请参阅官方文档 : [kitten clipboard](https://sw.kovidgoyal.net/kitty/kittens/clipboard/) - -一些常用用法: - -```shell -# Copy an image to the clipboard: -kitten clipboard picture.png - -# Copy an image and some text to the clipboard: -kitten clipboard picture.jpg text.txt - -# Copy text from STDIN and an image to the clipboard: -echo hello | kitten clipboard picture.png /dev/stdin - -# Copy any raster image available on the clipboard to a PNG file: -kitten clipboard -g picture.png - -# Copy an image to a file and text to STDOUT: -kitten clipboard -g picture.png /dev/stdout - -# List the formats available on the system clipboard -kitten clipboard -g -m . /dev/stdout -``` - diff --git "a/source/_posts/nanovllm\347\233\270\345\205\263.md" "b/source/_posts/nanovllm\347\233\270\345\205\263.md" deleted file mode 100644 index 18ac794..0000000 --- "a/source/_posts/nanovllm\347\233\270\345\205\263.md" +++ /dev/null @@ -1,84 +0,0 @@ ---- -title: "nanovllm相关" -date: 2025-07-12 04:34:18 -updated: 2025-10-03 04:34:18 -mathjax: true -tags: - - 深度学习 -categories: 深度学习 -comments: false ---- -# 模型架构与 FlashAttention 底层优化解析 - -以下是对推理引擎中模型架构与注意力机制核心算子的技术推演与重构,侧重于计算瓶颈的分析、数学逻辑的推导以及硬件层面的 Trade-off。 - -## 1. 模型架构配置分析 - -根据提取的维度信息(16 个 Q Head,8 个 KV Head,Head Dim = 128),该模型采用了 **GQA (Grouped Query Attention)** 架构,Q 与 KV 的比例为 2:1。 -![](https://cdn.jsdelivr.net/gh/zip95297/zip95297.github.io@main/source/images/nanovllm%E7%9B%B8%E5%85%B3/Pasted%20image%2020260306195828.webp?raw=true) - -- **基础设施影响**:相较于传统的 MHA (Multi-Head Attention),GQA 是推理阶段(尤其是 Decode 阶段)缓解 Memory Bound 的关键设计。KV Cache 的显存占用直接减少了 50%,极大降低了显存带宽的读写压力,并允许系统维持更大的 Batch Size 从而提升系统吞吐。 - -- **归一化选择**:采用 **RMSNorm** 替代 LayerNorm,移除了均值计算(Mean-centering),在保证模型收敛和精度的前提下,减少了规约操作(Reduction)的开销,提升了前向推理的速度。 - - -## 2. 标准 Attention 的硬件瓶颈 (Memory Wall) - -传统 Attention 的计算流程受制于 GPU 的内存层级架构(HBM 与 SRAM 的速度差)。核心问题在于 IO Bound,而非 Compute Bound。 - -![](https://cdn.jsdelivr.net/gh/zip95297/zip95297.github.io@main/source/images/nanovllm%E7%9B%B8%E5%85%B3/Pasted%20image%2020260309203632.webp?raw=true) -对于序列长度为 $N$,特征维度为 $d$ 的输入: - -1. **读 Q, K**:从 HBM 读取所需数据,计算注意力分数矩阵 $S = QK^T$。 -2. **写 S**:将 $N \times N$ 的 $S$ 矩阵写入 HBM。 -3. **读 S, 写 P**:读取 $S$,计算 $P = \text{Softmax}(S)$,写回 HBM。 -4. **读 P, 读 V**:读取 $P$ 和 $V$,计算输出 $O = PV$。 -5. **写 O**:将 $N \times d$ 的 $O$ 写回 HBM。 - -其访存复杂度(IO Complexity)为 $O(Nd + N^2)$。当 $N$ 增大时(长上下文),对 $N \times N$ 尺寸的中间矩阵 $S$ 和 $P$ 的频繁 HBM 读写会彻底打满显存带宽,导致计算单元(SM)处于长时间的等待状态(空转)。 - -## 3. FlashAttention 核心解法之一:Online Softmax - -为了消除 $O(N^2)$ 的中间矩阵存储,FlashAttention 通过分块计算(Tiling)将整个过程融合(Fusion)为一个算子。这里的数学难点在于 Softmax 的分母需要全局信息,无法直接分块计算。 - -![](https://cdn.jsdelivr.net/gh/zip95297/zip95297.github.io@main/source/images/nanovllm%E7%9B%B8%E5%85%B3/Pasted%20image%2020260309204255.webp?raw=true) - -Online Softmax 的核心思想是:**维护局部最大值和局部指数和,当引入新块时,通过缩放(Rescaling)修正历史结果**。 - -假设我们将输入分成多个块。对于第 $k$ 个块,我们计算并维护三个局部变量: - -- **局部最大值**:$m^{(k)} = \max(x^{(k)})$ -- **局部指数和**:$l^{(k)} = \sum e^{x^{(k)} - m^{(k)}}$ -- **未归一化的输出值**:$\tilde{O}^{(k)}$ - -当第 $k$ 块计算完成,接下来要合并第 $k+1$ 块时,递推更新逻辑如下: - -1. **更新全局最大值**: - $$m_{new} = \max(m^{(k)}, m^{(k+1)})$$ -2. **修正历史指数和并更新总和**: - 原有的和 $l^{(k)}$ 是基于旧的最大值 $m^{(k)}$ 计算的,需要乘上衰减因子 $e^{m^{(k)} - m_{new}}$ 进行修正。 - $$l_{new} = l^{(k)} \cdot e^{m^{(k)} - m_{new}} + l^{(k+1)} \cdot e^{m^{(k+1)} - m_{new}}$$ -3. **修正历史输出并累加新块**: - 原有的未归一化输出同样需要用衰减因子修正,再加上新块的贡献。 - $$\tilde{O}_{new} = \tilde{O}^{(k)} \cdot e^{m^{(k)} - m_{new}} + \tilde{O}^{(k+1)} \cdot e^{m^{(k+1)} - m_{new}}$$ - -在所有分块遍历完成后,只需要执行一次最终的除法 $\tilde{O}_{final} / l_{final}$,即可得到完全正确的 Softmax 结果。这个过程避免了任何全局 $N \times N$ 矩阵的物化(Materialization)。 - -## 4. FlashAttention 核心解法之二:Tiling 与硬件映射 - -有了 Online Softmax 的数学基础,就可以将计算逻辑映射到硬件上,最大化利用 SRAM。 - -- **内存分配**:SRAM 的空间有限。需要根据 SRAM 大小设定分块参数 $B_r$(行块大小,对应 Q)和 $B_c$(列块大小,对应 K 和 V)。在 SRAM 中只需分配能够容纳下 $Q_{block}, K_{block}, V_{block}$ 以及 $O_{block}$ 的空间。对于 FP16,占用大致为 $\frac{M}{4d \times 2}$。 - -- **双层循环调度**: - 1. 外层循环:遍历 KV 的块。将 $K_{block}$ 和 $V_{block}$ 从 HBM 加载到 SRAM。 - 2. 内层循环:遍历 Q 的块。将 $Q_{block}$ 加载到 SRAM,与 $K_{block}$ 计算局部 $S_{block}$。 - 3. 立即在 SRAM 中执行 Online Softmax 更新,随后乘上 $V_{block}$ 更新 $O_{block}$。 - 4. (但是 innerloop 每次结束 都要写 Oml 下次还要返回,所以v2 在Q tiling 并行,遍历KV 不用反复读写Olm,更快切减小溢出风险。) - -## 5. 思考 - -**用计算换 IO (Compute for Memory)** - -FlashAttention 实际上**增加**了总的浮点运算次数(FLOPs)。因为在 Online Softmax 中引入了大量的 $e^{m_1 - m_2}$ 标量乘法与缩放操作,并且在反向传播(Backward)阶段,由于没有存储 $P$ 和 $S$,必须再前向重算一次 Attention。 - diff --git a/source/_posts/ssh.md b/source/_posts/ssh.md deleted file mode 100644 index 83a13ec..0000000 --- a/source/_posts/ssh.md +++ /dev/null @@ -1,175 +0,0 @@ ---- -title: "SSH 命令的详细使用" -date: 2025-05-28 01:24:21 -updated: 2025-07-18 04:48:31 -tags: - - 常用软件 - - ssh - - 网络 -categories: 终端相关 -comments: false ---- -# SSH - Secure Shell Protocol - -## SSH OPTION 总览 - -`ssh` 最常用的 的连接方式就是 `ssh user@ip` 端口不指定的话默认是 22 ,或者通过 配置 `.ssh/config` 进行登录。 - -### 常用的 - -`ssh` 的 常用参数有下面这些: -```shell -# 指定连接哪个端口 --p - -# 显示 ssh 连接时的详细输出 --v -# 如果使用 -J 参数 可以用 -vv 详细两层详细信息 - -# 跳板机 这个下面详细讲吧 --J - -# 允许 X11 转发,可以把 远程机器的图形程序 通过ssh 传输到本地 -# 条件:远程支持 X11 ,本地有 X server (跟系统有关) --X -# 禁止 X11 转发 --x - -# 直接指定 登录的目标用户 --l -ssh -l user IP - -# 指定从哪个 本地IP 发起连接 (本地有多个网卡-IP) --b - -# 强制给 给 远程命令分配一个 伪终端 -# 例如 ssh Target "vim " 如果没有分配终端就用不了 --t -# 强制 不分配 peseudo tty --T - -# 在远程机器上执行命令 -ssh REMOTE "shell" -``` - -### 转发相关 - -和转发相关的有: -```shell -# 本地某个端口 转发到 远端指定机器的指定端口 -# 本地转发 下面细说 下面这两个也需要 指定 host: user@IP --L :: - -# 远端的某个端口 转发到 本地的指定端口 --R :: - -# 认证后 把 ssh 进程 放在后台 -# 这个一般搭配 端口转发 或者 传入让 远程机器执行的 命令 -# 后面要加一个 command 例如 sleep true 之类的 -# 端口转发时候想常驻链接 可以用 死循环 "while true;do sleep 1;done" --f - -# 允许 允许非本机 通过局域网 连接本地转发的端口 大概是这样 --g - -# 让转发仅转发 不在远程执行命令 -# 如果没有这个 转发会在远程打开 远程shell会话 --N -``` - -### 基本没用过 - -下面是我目前基本没用过的参数,之后遇到了再补充: -```shell -# 启用压缩 减少数据传输量 --C - -# quiet MUTE所有的 ssh 警告和诊断信息 --q - -# 指定 私钥路径 一般在 ~/config/.ssh --i - -# 开启 认证代理 转发功能 可以理解成把私钥转发 -# 通过这个 可以先用私钥连到 A(没有这个私钥) 然后 可以直接免密到 B(有对应公钥) --A -# 关闭 认证代理转发 - -# 指定 用户级配置 config 如果开了这个 系统配置 /etc/ssh/ssh_config 会被忽略 -# 默认是 ~/.ssh/config --F -``` - -## SSH -J 跳板机 - -`-J` 参数可以让通过 跳板机登录到目标主机:例如 我想登录目标机器 `target` 但是要连接的 `target` 我在自己的网络环境中无法访问, 我可以直接访问的是 `jump` ,如果 `jump` 可以访问 `target`,那么就可以通过下面的命令进行连接: -```shell -ssh -J user_A@jump.com[:port] user_B@target.com [-p ] -# 其中 -J 的使用方法如下: --J [user@]jump_host[:port] -``` -上面这个命令等效为: `ssh jump` 然后在 `jump` 上:`ssh target` - -> 这个命令也支持 多个跳板: - `ssh -J user1@jump1,user2@jump2 user@target` - -## SSH 端口转发 - -`ssh -L/-R :: HOST` 后面的参数相当于在 `port` 前面隐含了 本地的地址。这个命令建立了一个 ssh隧道 来对网络上的流量进行转发(Forwarding)。 - -这个还没验证过:默认情况下,SSH 端口转发可能**只允许来自 SSH 客户端本机的连接**,如果想允许外面的 可以在第一个 port 前 加上 0.0.0.0 - -> 访问某个端口 就会给某个端口发送 请求 的流量,通过转发 可以把 流量 从一个 port 转发到另一个 port - -### 本地转发 - 访问本地的流量 F 远程 - -`ssh -L` 用于把 本地的端口 的流量 转发到 远程主机的端口 可以通过本地的某个端口 访问remote的某个端口(在ssh隧道上),例如: 这个 remote addr 可以是 localhost 也可以是别的 ip (如果某个服务只开在10.10.5.4 ,那么就不用 localhost 而是用 10.10.5.4) -```shell -ssh -L 9999::8888 user@remote_host -``` - -通过这个命令: -- 本地会打开 9999 端口 -- 建立转发: - 所有发往本地 9999 端口的流量 会通过 ssh 隧道 转发到远程 remote 上的 `remote addr:9999` -- 打开一个shell(如果不像打开shell 可以用 -f 在后台运行,可以使用 -g 允许局域网内的连接) - -举个例子:比如我要用 T4 localhost:7860 的服务 我就可以: -```shell -ssh -4(指定ipv4) -f -L 7860:localhost:7860 ICCT-T "while true;do sleep 10;done" -``` - -### 远程转发 - 访问远程的流量 F 本地 - -`ssh -R` 用于把 远程端口 的流量 转发到 本地的某个端口上。也就是允许 远程机器访问本地的服务。 -```shell -ssh -R 6666::22 user@remote_host -``` - -通过这个命令: -- 远程上打开 6666 端口 -- 所有发往 远程 6666 的流量都被转发到 本地 的 22 -- 打开一个shell - -> 这两个命令 的第一项 隐含了 R的ip 或者 L的ip -## 配置文件 - -在 用户目录下 `~/.ssh/config` 这是 通过 `ssh` 访问远程机器时候读取的文件 配置连接远程机器时候的各种参数,可以通过 `ssh Host` 来代替 `ssh` 中的长串参数传递。 - -一般来说 通过 `ssh user@hostname -p Port` 连接主机 也可以用下面的配置 -```yaml -Host <别名> - Hostname - User <要登录的用户> - Port <要连接的端口> - PreferredAuthentications <认证方式> # 常用的有 password publickey 可以都选(按顺序) - IdentityFile <私钥位置> - -# 还没有用过 - ForwardAgent <是否允许转发> yes / no # 对应前面的 -A 参数 - ProxyCommand ssh -q -W %h:%p gateway.example.com # 允许通过 ssh 命令 转发ssh连接 - LocalForward 8080 localhost:80 # 设置转发 - RemoteForward 9090 localhost:90 - Compression yes # 压缩流量 用于节省带宽 -``` - diff --git a/source/_posts/torch.md b/source/_posts/torch.md deleted file mode 100644 index 6f69e12..0000000 --- a/source/_posts/torch.md +++ /dev/null @@ -1,286 +0,0 @@ ---- -title: "SwinIR中相关基础Torch操作" -date: 2025-03-16 03:52:59 -updated: 2025-03-18 13:21:13 -mathjax: true -tags: - - 深度学习 -categories: 深度学习 -comments: false ---- -# 概念 - - - -## 归一化 - - - -将不同尺度或单位的数据,按一定规则转换到相同尺度下,常用于数据预处理,使其具有统一的分布范围或特性,便于后续的处理或分析。 - - - -归一化主要是为了加速收敛(不同特征数值范围差异大时梯度下降会震荡)、避免数值问题(防止某些特征因数值过大而主导梯度更新)。 - - - -常见的归一化方法: - - - -**最大最小归一化(Min-Max)** - -$$x'=\cfrac{x-x_{min}}{x_{max}-x_{min}}$$ - -把数据线性放缩到 [0,1] 区间,保留原始分布形状,但对异常值敏感 - - - -**Z-score 标准化** - -$$x'=\cfrac{x-\mu}{\sigma}$$ - -把数据转换成均值为 0、标准差为 1 的分布 N(0,1),对异常值更鲁棒。PyTorch 中的 normalization 默认用这个。 - - - -**L2 归一化** - -$$x'=\cfrac{x}{\|x\|_2}$$ - -将向量归一化到单位球面上,常用于特征向量归一化 - -# 相关函数 - - - -## nn.LayerNorm - - - -输入是 `[B, N, D]` - -- B:Batch size,一个批次中的样本数量 - -- N:Sequence length,一个样本中的 token 数量 - -- D:Embedding dimension,每个 token 的 embedding 维度 - - - -LayerNorm 对每个样本的每个 token 的 embedding 向量独立做 Z-score 归一化(不跨样本,不跨 token) - - - -$$ -\mu_{b,n} = \frac{1}{D} \sum_{d=1}^{D} x_{b,n,d} \quad \quad \sigma_{b,n}^2 = \frac{1}{D} \sum_{d=1}^{D} (x_{b,n,d} - \mu_{b,n})^2 -$$ - - - -$$ -y_{b,n,d} = \gamma \cdot \frac{x_{b,n,d} - \mu_{b,n}}{\sqrt{\sigma_{b,n}^2 + \epsilon}} + \beta -$$ - - - -其中 $\gamma$ 和 $\beta$ 是可学习的仿射变换参数,$\epsilon$ 防止除零(默认 1e-5) - - - -使用时要指定 `normalized_shape` 为 embedding 维度: - -```python - -layer_norm = nn.LayerNorm(normalized_shape=512) - -x = torch.randn(32, 128, 512) # [batch, seq_len, emb_dim] - -output = layer_norm(x) - -``` - - - -LN 抹平了不同样本之间的大小关系,保留了不同特征维度之间的相对关系。所以更适合 NLP 任务,一个样本的特征就是不同 token 的 embedding,LN 保留了特征之间的时序关系。Transformer 基本都用 LayerNorm。 - - - -好处是不受 batch size 影响,batch=1 也能正常工作,训练和推理行为一致。 - -## nn.BatchNorm - - - -输入是 `[B, C, H, W]`(图像)或 `[B, C, L]`(序列) - -- B:Batch size - -- C:Channel,通道数 - -- H, W:图像的高度和宽度 - -- L:序列长度 - - - -BatchNorm 对一个 batch 的所有样本在每个通道上做归一化,也就是对每个通道计算所有样本的均值和方差。 - - - -对于图像数据: - -$$ -\mu_c = \frac{1}{B \cdot H \cdot W} \sum_{b=1}^{B} \sum_{h=1}^{H} \sum_{w=1}^{W} x_{b,c,h,w} -$$ - - - -$$ -\sigma_c^2 = \frac{1}{B \cdot H \cdot W} \sum_{b=1}^{B} \sum_{h=1}^{H} \sum_{w=1}^{W} (x_{b,c,h,w} - \mu_c)^2 -$$ - - - -$$ -\hat{x}_{b,c,h,w} = \gamma_c \cdot \frac{x_{b,c,h,w} - \mu_c}{\sqrt{\sigma_c^2 + \epsilon}} + \beta_c -$$ - - - -其中 $\gamma_c$ 和 $\beta_c$ 是每个通道的可学习参数 - - - -使用: - -```python - -batch_norm = nn.BatchNorm2d(num_features=64) # 64 个通道 - -x = torch.randn(32, 64, 28, 28) - -output = batch_norm(x) - -``` - - - -训练时用当前 batch 的均值和方差,同时更新移动平均统计量;推理时用训练时累积的移动平均统计量(running_mean 和 running_var)。 - - - -BN 抹平了不同特征(通道)之间的大小关系,保留了不同样本之间的相对关系。所以更适合 CV 任务,比如图像分类,不同样本之间的大小关系得以保留。CNN 的卷积层后面通常接 BatchNorm。 - - - -注意 batch size 太小(<8)时效果会变差,统计量不稳定。不适合序列长度变化大的 NLP 任务。 - - - -## Dropout - - - -训练时随机将张量中的元素按概率 `p` 设置为 0,防止过拟合。剩余的激活神经元会按 `1/(1-p)` 缩放,保持输出期望值不变。 - - - -$$ -y = \begin{cases} -0 & \text{with probability } p \\ -\frac{x}{1-p} & \text{with probability } 1-p -\end{cases} -$$ - - - -原理是强制网络学习更鲁棒的特征,不依赖特定神经元的组合。每次训练相当于训练一个不同的子网络,推理时相当于多个模型的平均。 - - - -### nn.Dropout - - - -自动根据 `model.train()` / `model.eval()` 切换状态,推荐用这个: - - - -```python - -class MyModel(nn.Module): - def __init__(self): - super().__init__() - self.dropout = nn.Dropout(p=0.5) - self.fc = nn.Linear(512, 256) - - def forward(self, x): - x = self.fc(x) - x = self.dropout(x) # 训练时生效,推理时自动关闭 - return x -``` - - - -### nn.functional.dropout - - - -通过 `training` 参数手动控制: - - - -```python -x = F.dropout(x, p=0.5, training=training) -``` - - - -常用配置:全连接层 `p=0.5`,卷积层用 `nn.Dropout2d` 通常 `p=0.2~0.3`,Transformer 通常 `p=0.1`。dropout 率太高会欠拟合,太低防止过拟合效果不明显。 - -## nn.PixelShuffle - - - -PixelShuffle 是一种空间重排操作(Sub-Pixel Convolution),将图像的通道信息重新分布到空间维度上,实现上采样(放大图像)。 - - - -维度变换:`[B, C × r², H, W]` → `[B, C, H × r, W × r]`,其中 `r` 是上采样因子(upscale_factor) - - - -工作原理:假设 `r=2`,输入 `[B, 4C, H, W]`,会将 `4C` 个通道重组为 `C × 2 × 2` 的形式,每个 `2×2` 的通道块对应输出空间的一个 `2×2` 区域,最后输出 `[B, C, 2H, 2W]`。 - - - -示例(单通道,r=2): - -``` -输入: [B, 4, H, W] (4个通道) -┌─────┬─────┐ -│ C0 │ C1 │ -├─────┼─────┤ -│ C2 │ C3 │ -└─────┴─────┘ - -输出: [B, 1, 2H, 2W] (1个通道,空间扩大2倍) -┌──┬──┐ -│C0│C1│ -├──┼──┤ -│C2│C3│ -└──┴──┘ -``` - -使用: -```python -pixel_shuffle = nn.PixelShuffle(upscale_factor=2) # r=2 -x = torch.randn(1, 64, 32, 32) # [1, 64, 32, 32] -output = pixel_shuffle(x) # [1, 16, 64, 64] -# 通道数: 64 / (2^2) = 16,空间尺寸: 32 * 2 = 64 -``` - -常用于超分辨率(Super-Resolution)和图像生成(GAN、VAE)的上采样阶段,可以替代转置卷积避免棋盘效应。好处是纯重排操作不增加参数,计算也比双线性插值 + 卷积快。 - -注意输入通道数必须是 `C × r²` 的形式,通常在 PixelShuffle 之前用卷积层生成足够的通道数。 diff --git "a/source/_posts/\346\234\215\345\212\241\345\231\250\344\273\243\347\220\206\347\233\270\345\205\263\351\227\256\351\242\230.md" "b/source/_posts/\346\234\215\345\212\241\345\231\250\344\273\243\347\220\206\347\233\270\345\205\263\351\227\256\351\242\230.md" deleted file mode 100644 index adff4fe..0000000 --- "a/source/_posts/\346\234\215\345\212\241\345\231\250\344\273\243\347\220\206\347\233\270\345\205\263\351\227\256\351\242\230.md" +++ /dev/null @@ -1,117 +0,0 @@ ---- -title: "服务器代理相关问题" -date: 2025-07-30 02:08:21 -updated: 2025-07-30 21:32:52 -tags: - - 网络 - - ssh - - 网络代理 -categories: 实用技巧 -comments: false ---- -# 服务器代理相关问题 - -## 转发内网网页到本地 - -在 非内网环境下访问内网网页比较麻烦,记录一下解决这种问题的方式 - -### 环境描述 - -一台内网中的linux服务器,使用 公网服务器 + frp 进行内网穿透 - -在内网环境下访问的 两种网页(目前为止遇到这两种): -1. 服务 直接开在这个服务器上 -2. 服务 没在该服务器上 - -### 第一种 服务 在服务器上 - -例如,一个 自建的 overleaf 网页服务开在 服务器的 8888 端口。 - -#### Step1 - -可以直接 在服务器中查看 这个 overleaf 开在哪个 ip 上 ,命令如下: -```shell -sudo lsof -i :8888 -``` -这里已经事先知道了服务开在了 10.10.5.4 这个内网 IP - -#### Step2 - -直接将本地某个端口的流量转发到 服务器 对应 ip:port 上: -```shell -ssh -L 8888:10.10.5.4:8888 user@公网服务器ip -p -``` - -然后就可以直接 通过 http://localhost:8888 来访问这个主机的服务了 - - -### 第二种 服务 不在服务器上 - -在内网中 需要访问的 url 例如: -- https://modeloverfit.com -- https://git.modeloverfit.com -经过测试发现子域名和根域名是同一个ip和端口443 - -想要访问这些内网环境下的page,使用下面方式(比较简单,还有一些nginx之类的方法): - -#### Step1 - -通过 ssh -D 动态转发?好像是,将内网服务器当做代理服务器,代理本地流量: -```shell -ssh -D 7899 user@ip -p port -``` -由于本地clash用了7890端口,⚠️注意不要冲突 - -#### Step2 - -到这里为止已经在本地打开了一代理端口7899,发送到7899端口的流量将会被 内网服务器代理,可以用下面 命令测试一下: -```shell -curl -x socks5://localhost:7899 -k https://git.modeloverfit.com/users/sign_in -``` -> 说明: -x 指定curl的代理 -k 用于 https 忽略证书安全之类的验证大概是 - -这时已经能正确获得改内网url的完整内容了,下面配合chrome内核浏览器进行设置代理规则 - -#### Step3 - -- 下载 [SwitchyOmega](https://chrome.google.com/webstore/detail/proxy-switchyomega/padekgcemlokbadohgkifijomclgjgif?hl=en-US)插件 - -在这个插件内: - -- 设置代理服务器 也就是 localhost 7899 -![](https://cdn.jsdelivr.net/gh/zip95297/zip95297.github.io@main/source/images/%E6%9C%8D%E5%8A%A1%E5%99%A8%E4%BB%A3%E7%90%86%E7%9B%B8%E5%85%B3%E9%97%AE%E9%A2%98/Pasted%20image%2020250528193554.webp?raw=true) - -- 设置对于modeloverfit及其子域名的代理规则: -![](https://cdn.jsdelivr.net/gh/zip95297/zip95297.github.io@main/source/images/%E6%9C%8D%E5%8A%A1%E5%99%A8%E4%BB%A3%E7%90%86%E7%9B%B8%E5%85%B3%E9%97%AE%E9%A2%98/Pasted%20image%2020250528193708.webp?raw=true) - -- 最后记得打开插件 使用上面配置的代理设置,然后就可以直接访问内网的url了 -![](https://cdn.jsdelivr.net/gh/zip95297/zip95297.github.io@main/source/images/%E6%9C%8D%E5%8A%A1%E5%99%A8%E4%BB%A3%E7%90%86%E7%9B%B8%E5%85%B3%E9%97%AE%E9%A2%98/Pasted%20image%2020250528193913.webp?raw=true) - -## 给服务器打开代理 - -环境: - 内网服务器 ICCT-T - 公网服务器 Z - 用 frp 把 Z 的 6000 转发到 T4 的 22 建立 ssh 穿透 - ICCT-T-P = Z -p 6789 - 本地 PC 有代理 localhost:7890 - -### Step1 - -打开本地的代理 Clash - -### Step2 - -把 远端 1080 端口的流量转到本地代理端口: -```shell -ssh -R 1080:localhost:7890 ICCT-T-P -``` - -### Step3 - -设置 服务器上 的代理端口 : -在 ~/.proxychains/proxychains.conf 中添加 -```conf -http 127.0.0.1 1080 -``` - diff --git "a/source/_posts/\346\234\272\345\231\250\345\255\246\344\271\240\345\244\215\344\271\240\350\265\204\346\226\231.md" "b/source/_posts/\346\234\272\345\231\250\345\255\246\344\271\240\345\244\215\344\271\240\350\265\204\346\226\231.md" deleted file mode 100644 index 3495926..0000000 --- "a/source/_posts/\346\234\272\345\231\250\345\255\246\344\271\240\345\244\215\344\271\240\350\265\204\346\226\231.md" +++ /dev/null @@ -1,1134 +0,0 @@ ---- -title: "机器学习复习资料" -date: 2025-04-28 23:52:47 -updated: 2025-04-30 05:46:15 -mathjax: true -categories: 期末复习 -tags: - - 机器学习 - - 期末复习 - - 深度学习 -comments: false ---- -# 大纲复习资料 - -# 第一章 机器学习简介 - -## 机器学习的概念、要素、类型 - -### 概念 - -机器学习(Machine Learning,ML)是指从有限的观测数据中学习(或“猜测”)出具有一般性的规律,并将这些规律应用到未观测数据样本上的方法。主要研究内容是学习算法。 - -**机器学习**(英语:machine learning)是人工智能的一个分支。机器学习理论主要是设计和分析一些让计算机可以自动学习的算法。机器学习算法是一类从数据]中自动分析获得规律,并利用规律对未知数据进行预测的算法。因为学习算法中涉及了大量的统计学理论,机器学习与推断统计学联系尤为密切,也被称为**统计学习理论**。算法设计方面,机器学习理论关注可以实现的,行之有效的学习算法(要防止错误累积)。很多推论问题属于非程序化決策,所以部分的机器学习研究是开发容易处理的近似算法。(Wikipedia) - -计算机科学家汤姆·米切尔在其著作的Machine Learning一书中定义的机器学习为: well-posed learning problem - -> A computer program is said to learn from experience E with respect to some class of tasks T and performance measure P, if its performance at tasks in T, as measured by P, improves with experience E. -> -> ——Tom Mitchell,Machine Learning - -良好的学习问题 是 以性能量度P进行衡量,如果一个计算机程序在某类任务T上的性能,随着经验E而提升,那么我们称这个计算机程序能从经验E中学习。 - -### 要素 三个 - -- 模型(这里的线性 指的 是对参数 w ) - - 线性方法(指的是 w的线性函数): - $$ - f(x,\theta)=w^Tx+b - $$ - - 广义线性方法: - $$ - f(x,\theta)=w^T \phi (x) +b - $$ - 其中 $\phi (x)$ 是 可学习的 非线性基函数 (基函数用于对x进行特征变换) - - 非线性方法:例如神经网络 - $$ - f(x) = \sum_{j=1}^{m} v_j \cdot \sigma(\mathbf{w}_j^\top \mathbf{x} + b_j) + b - $$ - $\sigma(\cdot)$ 是激活函数(如 ReLU、Sigmoid)这个模型的 非线性来自激活函数和多层结构。 - -- 学习准则 (学习目标 优化目标) - - 期望风险:这个是最理论化的目标 指的是 损失值在真实数据分布下的期望,但是由于 数据的真实分布 $P(x,y)$ 是不可知的,所以通常难以计算 - $$ - \cal{R}(f)= \mathbb{E}_{(x,y)\sim P(x,y)} \left[ L(f(x), y) \right] - $$ - - 经验风险:根据大数定律 当数据量足够大时 可以用频率当做分布 所以有经验风险 - $$ - \cal{R}_{emp}(f) = \frac{1}{n} \sum_{i=1}^{n} L(f(x_i), y_i) - $$ - - ***结构***风险最小化:在结构风险基础上加上正则化项 防止模型过拟合 控制模型的复杂程度 - $$ - \cal{R}_{srm}(f) = R_{emp}(f) + \lambda \cdot \Omega(f) - $$ - 其中 $\lambda$ 是控制正则损失系数 - - - 最大间隔准则: 在SVM支持向量机中的学习准则 - $$ - \min \frac{1}{2} ||w||^2 \quad \text{s.t. } y_i(w^\top x_i + b) \ge 1 - $$ - 间接最小化结构,优先选更稳健的模型 - - - 最大似然估计(MLE):最大似然 - $$ - \max_\theta \prod_{i=1}^n P(y_i|x_i;\theta) \quad \text{或} \quad \max_\theta \sum_{i=1}^n \log P(y_i|x_i;\theta) - $$ - - 贝叶斯后验最大化(MAP): - $$ - \max_\theta P(\theta|D) = \max_\theta P(D|\theta) P(\theta) - $$ - 加了先验的 MLE 可以当做正则化的MLE - -- 优化 - - 梯度下降方法 - - |方法名|特点|备注| - |---|---|---| - |**标准梯度下降(GD)**|每次用全量样本更新|计算量大,收敛稳定| - |**随机梯度下降(SGD)**|每次只用一个样本|噪声大但效率高| - |**小批量梯度下降(Mini-batch GD)**|综合GD和SGD优点|深度学习常用| - |**Momentum(动量法)**|加速收敛,抗震荡|模拟物理惯性| - |**Nesterov Accelerated Gradient(NAG)**|改进动量法|预判未来梯度方向| - |**Adagrad**|自适应每个参数学习率|学习率随训练减小,可能太快收敛| - |**RMSProp**|改进Adagrad,控制过快下降|常用于RNN| - |**Adam**|融合Momentum和RMSProp|最常用,鲁棒性好| - |**AdamW**|改进了Adam的权重衰减策略|Transformer常用| - - - 二阶优化方法 - - |方法名|特点|备注| - |---|---|---| - |**牛顿法(Newton’s Method)**|用二阶导数加快收敛|每次迭代代价高| - |**拟牛顿法(如BFGS、L-BFGS)**|近似二阶信息|在小数据场景表现好| - |**自然梯度法(Natural Gradient)**|考虑参数空间结构|常用于贝叶斯/强化学习| - - - 无梯度优化 - - |方法名|特点|适用场景| - |---|---|---| - |**网格搜索 / 随机搜索**|超参数调优经典方法|参数少时可用| - |**进化算法(如遗传算法GA)**|模拟自然选择过程|黑盒优化| - |**粒子群优化(PSO)**|模拟群体智能|连续优化有效| - |**模拟退火(SA)**|模拟物理退火过程|全局优化倾向强| - |**贝叶斯优化(BO)**|利用代理模型迭代搜索|少样本高效搜索| - -## 过拟合、欠拟合、正则化 - -### 过拟合 - -过拟合(Over-fitting)是指 模型在训练集上面误差很低,但是在测试机上误差很高。对训练集拟合的能力过强。 -![](https://cdn.jsdelivr.net/gh/zip95297/zip95297.github.io@main/source/images/%E6%9C%BA%E5%99%A8%E5%AD%A6%E4%B9%A0%E5%A4%8D%E4%B9%A0%E8%B5%84%E6%96%99/Pasted%20image%2020250425170110.webp?raw=true) - -过拟合的解决方法: -- 扩大数据集 -- 正则化 (奥卡姆剃刀原理 简单有效) - 如果没有必要就不要增大模型的数量 -- 通过 验证集合 来选择模型 - K折交叉验证:训练K次判断模型能力 - 留一法 K=N -### 欠拟合 - -模型在训练集和测试集上的误差都很大。由于模型能力不足(不够灵活) -![](https://cdn.jsdelivr.net/gh/zip95297/zip95297.github.io@main/source/images/%E6%9C%BA%E5%99%A8%E5%AD%A6%E4%B9%A0%E5%A4%8D%E4%B9%A0%E8%B5%84%E6%96%99/Pasted%20image%2020250425170633.webp?raw=true) - -### 正则化 - -对模型的损失中 添加正则化损失 例如L1、L2 -奥卡姆定律 简单有效 -![](https://cdn.jsdelivr.net/gh/zip95297/zip95297.github.io@main/source/images/%E6%9C%BA%E5%99%A8%E5%AD%A6%E4%B9%A0%E5%A4%8D%E4%B9%A0%E8%B5%84%E6%96%99/Pasted%20image%2020250425171150.webp?raw=true) - -## 贝叶斯公式 - -后验 似然x先验 -$$ -p(Y|X)=\cfrac{P(X|Y)P(Y)}{P(X)} -$$ - -# 第二章 概率论与优化 - -## 概率的概念、贝叶斯学派vs频率轮学派 - -### **概率的概念** - -概率是对**不确定事件发生可能性**的度量。在数学上,概率是定义在样本空间上的一个函数,取值范围是 \[0,1] 。 - -但不同学派对“概率的意义”有不同理解,主要有: -### **频率学派(Frequentist)** - -#### 观点: - -概率是一个事件在**大量重复试验中出现的相对频率**。 -通过大量独立实验将概率解释为事件发生频率的均值(大数定律)。 - -> 举个例子:如果你连续掷硬币很多次,有一半是正面,那我们就说正面概率是 0.5。 - -#### 特点: - -- 概率是客观的,跟你是否知道某个信息无关。 -- 参数是**固定未知数**,不能被建模成随机变量。 -- 推断方法:最大似然估计(MLE)、假设检验、置信区间等。 - -### **贝叶斯学派(Bayesian)** - -#### 观点: - -概率是表示一个人对事件发生的**主观相信程度**,是**对不确定性的度量**。 - -将概率解释为信念度(degree ofbelief)。当考虑的试验次数非常少的时候,贝叶斯方法的解释非常有用。此外,贝叶斯理论将我们对于随机过程的先验知识纳入考虑,当我们获得新数据的时候,这个先验的概率分布就会被更新到后验分布中。 - -> 举个例子:如果你知道这枚硬币偏向正面,你就可以给出“正面概率0.8”,即使你还没开始掷。 - -#### 特点: - -- 概率可以用于表示对未知参数的信念。 -- 参数也可以是**随机变量**。 -- 推断方法:利用贝叶斯公式更新先验分布得到后验分布。 - -$$ -p(\theta | x) = \frac{p(x | \theta) \cdot p(\theta)}{p(x)} -$$ - ---- - -### 两者对比总结: - -| 内容 | 频率学派 | 贝叶斯学派 | -| ---- | -------- | ------------- | -| 概率定义 | 事件长时间频率 | 主观信念 | -| 参数看法 | 固定未知值 | 随机变量 | -| 推断结果 | 单个估计值 | 概率分布 | -| 常用方法 | MLE、假设检验 | 贝叶斯公式、先验-后验推断 | - -## 伯努利分布、高斯分布 - -### 伯努利分布 Bernouli - -就是 单次的 二项分布 : -$$ -Bern(x|\mu)=\mu^x(1-\mu)^{1-x} \ \ \ ,\ x\in\{0,1\} -$$ -期望 $\mathbb{E}[x]=\mu$ , 方差 $var[x]=\mu(1-\mu)$ - -二项分布: -$$ -Bin(m|N,\mu) = C^m_N \mu^m (1-\mu)^{N-m} -$$ -期望 $\mathbb{E}[x]=N\mu$ , 方差 $var[x]=N\mu(1-\mu)$ - -共轭性:![](https://cdn.jsdelivr.net/gh/zip95297/zip95297.github.io@main/source/images/%E6%9C%BA%E5%99%A8%E5%AD%A6%E4%B9%A0%E5%A4%8D%E4%B9%A0%E8%B5%84%E6%96%99/Pasted%20image%2020250426030533.webp?raw=true) - -### 高斯分布 - -就是正态分布: -$$ -{N}(x|\mu,\sigma^2)=\cfrac{1}{(2\pi\sigma^2)^{1/2}}exp\{-\frac{1}{2\sigma^2}(x-\mu)^2\} -$$ -期望 $\mathbb{E}[x]=\mu$ , 方差 $var[x]=\sigma^2$ - -## 极大似然估计、最大后验估计 - -极大似然估计 和 最大后验估计 都是 机器学习三要素中的 学习准则 - -### 极大似然估计(Maximum Likelihood Estimate)MLE - -选择让 观测到数据出现的概率最大的 一组参数 , 下面这个P就是似然函数 -$$ -\hat{\theta} = \arg\max_\theta P(\cal{D}|\theta) -$$ -### 最大后验估计 (Maximum A Posteriori estimate)MAP - -在给定 先验参数分布 和 数据 $\cal{D}$ 的情况下 最有可能的 参数 $\theta$ -$$ -\hat{\theta} = \arg\max_\theta P(\theta|\cal{D})=\arg\max_\theta \cfrac{P(\cal{D}|\theta)P(\theta)}{P(\cal{D})} -$$ - - -## 梯度下降以及不同形式 - -Goal: -$$ -\min_x f(x) -$$ -Just iterate -$$ -x_{t+1}=x_t-\eta_t\nabla f(x_t) -$$ -where $\eta_t$ is learning rate - -![](https://cdn.jsdelivr.net/gh/zip95297/zip95297.github.io@main/source/images/%E6%9C%BA%E5%99%A8%E5%AD%A6%E4%B9%A0%E5%A4%8D%E4%B9%A0%E8%B5%84%E6%96%99/Pasted%20image%2020250426031557.webp?raw=true) - -| 方法名 | 特点 | 备注 | -| -------------------------------------- | ------------------ | ---------------------- | -| **标准梯度下降(GD)** | 每次用全量样本更新 | 计算量大,收敛稳定。也叫批量梯度下降法BGD | -| **随机梯度下降(SGD)** | 每次只用一个样本 | 噪声大但效率高 | -| **小批量梯度下降(Mini-batch GD)** | 综合GD和SGD优点 | 深度学习常用 | -| **Momentum(动量法)** | 加速收敛,抗震荡 | 模拟物理惯性 | -| **Nesterov Accelerated Gradient(NAG)** | 改进动量法 | 预判未来梯度方向 | -| **Adagrad** | 自适应每个参数学习率 | 学习率随训练减小,可能太快收敛 | -| **RMSProp** | 改进Adagrad,控制过快下降 | 常用于RNN | -| **Adam** | 融合Momentum和RMSProp | 最常用,鲁棒性好 | -| **AdamW** | 改进了Adam的权重衰减策略 | Transformer常用 | -![](https://cdn.jsdelivr.net/gh/zip95297/zip95297.github.io@main/source/images/%E6%9C%BA%E5%99%A8%E5%AD%A6%E4%B9%A0%E5%A4%8D%E4%B9%A0%E8%B5%84%E6%96%99/Pasted%20image%2020250426032114.webp?raw=true) -![](https://cdn.jsdelivr.net/gh/zip95297/zip95297.github.io@main/source/images/%E6%9C%BA%E5%99%A8%E5%AD%A6%E4%B9%A0%E5%A4%8D%E4%B9%A0%E8%B5%84%E6%96%99/Pasted%20image%2020250426032336.webp?raw=true) -![](https://cdn.jsdelivr.net/gh/zip95297/zip95297.github.io@main/source/images/%E6%9C%BA%E5%99%A8%E5%AD%A6%E4%B9%A0%E5%A4%8D%E4%B9%A0%E8%B5%84%E6%96%99/Pasted%20image%2020250426032422.webp?raw=true) - -# 第三章 回归的线性模型 - -## 介绍 - -> 什么是回归? 回归指的是 让模型 预测连续的数值型输出(即实数范围内的值) - -> 什么是线性模型? 线性模型指的是 模型 是 参数w 的线性函数 一般形式为 - $$ - f(x)=w_1x_1+w_2x_2+...+w_dx_d+b - $$ - -向量形式为 -$$ -f(x)=w^Tx +b -$$ - -其中 $\vec{x} =(x_1,x_2....x_d)$ 是输入的特征 线性模型是w的线性模型 x 如果经过基函数变换为 $\phi(x)$ 仍是线性模型。 -线性模型的优点:简单易建模 可解释性强 非线性模型(引入层级结构或者高位映射)的基础 。 - ----- -线性回归是什么? -给定数据集 $D=\{(x_1,y_1),(x_2,y_2),...,(x_m,y_m)\}$,其中 $x_i=(x_{i1};x_{i2};...;x_{id}),y_i\in\mathbb{R}$ -线性回归的目的 学到一个线性模型 尽可能 准确地 预测 实值输出 - -## 线性回归最小二乘法 - -建立线性模型 为了方便 表达 把 b 和 1 增广入 w x 中 -![](https://cdn.jsdelivr.net/gh/zip95297/zip95297.github.io@main/source/images/%E6%9C%BA%E5%99%A8%E5%AD%A6%E4%B9%A0%E5%A4%8D%E4%B9%A0%E8%B5%84%E6%96%99/Pasted%20image%2020250426155115.webp?raw=true) -![](https://cdn.jsdelivr.net/gh/zip95297/zip95297.github.io@main/source/images/%E6%9C%BA%E5%99%A8%E5%AD%A6%E4%B9%A0%E5%A4%8D%E4%B9%A0%E8%B5%84%E6%96%99/Pasted%20image%2020250426155201.webp?raw=true) -![](https://cdn.jsdelivr.net/gh/zip95297/zip95297.github.io@main/source/images/%E6%9C%BA%E5%99%A8%E5%AD%A6%E4%B9%A0%E5%A4%8D%E4%B9%A0%E8%B5%84%E6%96%99/Pasted%20image%2020250426155225.webp?raw=true) -![](https://cdn.jsdelivr.net/gh/zip95297/zip95297.github.io@main/source/images/%E6%9C%BA%E5%99%A8%E5%AD%A6%E4%B9%A0%E5%A4%8D%E4%B9%A0%E8%B5%84%E6%96%99/Pasted%20image%2020250426155301.webp?raw=true) - -### 多元线性回归 -![](https://cdn.jsdelivr.net/gh/zip95297/zip95297.github.io@main/source/images/%E6%9C%BA%E5%99%A8%E5%AD%A6%E4%B9%A0%E5%A4%8D%E4%B9%A0%E8%B5%84%E6%96%99/Pasted%20image%2020250426155423.webp?raw=true) -![](https://cdn.jsdelivr.net/gh/zip95297/zip95297.github.io@main/source/images/%E6%9C%BA%E5%99%A8%E5%AD%A6%E4%B9%A0%E5%A4%8D%E4%B9%A0%E8%B5%84%E6%96%99/Pasted%20image%2020250426155429.webp?raw=true) -![](https://cdn.jsdelivr.net/gh/zip95297/zip95297.github.io@main/source/images/%E6%9C%BA%E5%99%A8%E5%AD%A6%E4%B9%A0%E5%A4%8D%E4%B9%A0%E8%B5%84%E6%96%99/Pasted%20image%2020250426155434.webp?raw=true) - -### 基函数 - -![](https://cdn.jsdelivr.net/gh/zip95297/zip95297.github.io@main/source/images/%E6%9C%BA%E5%99%A8%E5%AD%A6%E4%B9%A0%E5%A4%8D%E4%B9%A0%E8%B5%84%E6%96%99/Pasted%20image%2020250426155455.webp?raw=true) -![](https://cdn.jsdelivr.net/gh/zip95297/zip95297.github.io@main/source/images/%E6%9C%BA%E5%99%A8%E5%AD%A6%E4%B9%A0%E5%A4%8D%E4%B9%A0%E8%B5%84%E6%96%99/Pasted%20image%2020250426155505.webp?raw=true) -基函数 可以选择 -- 多项式基函数\[$\phi_j(x)=x^j$\] -- 高斯基函数 \[$\phi_j(x)=exp\{-\frac{(x-\mu_j)^2}{2s^2}\}$\] -- sigmoid 基函数\[$\phi_j(x)=\sigma(\frac{x-\mu_j}{s}) ,where\ \ \sigma(a)=\frac{1}{1+exp(-a)}$\] - -## 岭回归 Ridge Regression 山脊回归 - -岭回归就是在原本的loss上加上了 权重产生的 权重衰减正则化项 -![](https://cdn.jsdelivr.net/gh/zip95297/zip95297.github.io@main/source/images/%E6%9C%BA%E5%99%A8%E5%AD%A6%E4%B9%A0%E5%A4%8D%E4%B9%A0%E8%B5%84%E6%96%99/Pasted%20image%2020250426160411.webp?raw=true) -![](https://cdn.jsdelivr.net/gh/zip95297/zip95297.github.io@main/source/images/%E6%9C%BA%E5%99%A8%E5%AD%A6%E4%B9%A0%E5%A4%8D%E4%B9%A0%E8%B5%84%E6%96%99/Pasted%20image%2020250426160417.webp?raw=true) - -最大化后验概率 等价于 最小化正则化的平方和误差函数 -![](https://cdn.jsdelivr.net/gh/zip95297/zip95297.github.io@main/source/images/%E6%9C%BA%E5%99%A8%E5%AD%A6%E4%B9%A0%E5%A4%8D%E4%B9%A0%E8%B5%84%E6%96%99/Pasted%20image%2020250426160617.webp?raw=true) - -闭式解与几何解释 -![](https://cdn.jsdelivr.net/gh/zip95297/zip95297.github.io@main/source/images/%E6%9C%BA%E5%99%A8%E5%AD%A6%E4%B9%A0%E5%A4%8D%E4%B9%A0%E8%B5%84%E6%96%99/Pasted%20image%2020250426160732.webp?raw=true) - -## Lasso 回归 套索回归 -L1 loss -![](https://cdn.jsdelivr.net/gh/zip95297/zip95297.github.io@main/source/images/%E6%9C%BA%E5%99%A8%E5%AD%A6%E4%B9%A0%E5%A4%8D%E4%B9%A0%E8%B5%84%E6%96%99/Pasted%20image%2020250426160802.webp?raw=true) - -Lasso回归能够使模型 具有稀疏性 -![](https://cdn.jsdelivr.net/gh/zip95297/zip95297.github.io@main/source/images/%E6%9C%BA%E5%99%A8%E5%AD%A6%E4%B9%A0%E5%A4%8D%E4%B9%A0%E8%B5%84%E6%96%99/Pasted%20image%2020250426160911.webp?raw=true) - - -# 第四章 基础的分类方法 - -回归是多个输入特征被映射到一个或者多个连续的目标变量 -分类则是 输入特征x被映射到K个离散类别之一$C_k$ -## 线性分类概念 - -线性分类,模型指的是 分类面 是 x 的 线性 函数 (分类函数 f 可能是非线性函数 例如在外面加了一层 sigmoid) -区别于前面的线性回归(对于参数是线性的) - -推广的线性模型 用非线性函数对w的线性函数的结果进行激活![](https://cdn.jsdelivr.net/gh/zip95297/zip95297.github.io@main/source/images/%E6%9C%BA%E5%99%A8%E5%AD%A6%E4%B9%A0%E5%A4%8D%E4%B9%A0%E8%B5%84%E6%96%99/Pasted%20image%2020250426165956.webp?raw=true) -![](https://cdn.jsdelivr.net/gh/zip95297/zip95297.github.io@main/source/images/%E6%9C%BA%E5%99%A8%E5%AD%A6%E4%B9%A0%E5%A4%8D%E4%B9%A0%E8%B5%84%E6%96%99/Pasted%20image%2020250426162156.webp?raw=true) -![](https://cdn.jsdelivr.net/gh/zip95297/zip95297.github.io@main/source/images/%E6%9C%BA%E5%99%A8%E5%AD%A6%E4%B9%A0%E5%A4%8D%E4%B9%A0%E8%B5%84%E6%96%99/Pasted%20image%2020250426162206.webp?raw=true) - -下面说的三种 感知机 logistic回归 朴素贝叶斯分类器 -感知机和logistic回归两种是严格线性的 决策面是超平面 -朴素贝叶斯分类器 通常是非线性 其决策边界取决于特征的条件概率分布(如高斯朴素贝叶斯的决策边界是二次曲面) -若特征的条件概率分布属于***指数族(如泊松分布)***,且满足特定条件时,可能得到线性决策边界。 -## 三种类型:判别函数、概率生成模型、概率判别模型 - -### 判别函数 Discriminant Function (感知器) - ->这个是直接说出结果是哪个 下面的会给出概率 然后再决策是哪个 - -直接找到一个函数 $f(x)$ 把每个输入 x 直接映射为 类别标签,例如 感知机 SVM 之类的 - -线性判别函数就是指 决策面 是 超 平面 的判别函数 - -#### 二分类情形 -线性判别函数 y=0 就是 决策平面 -![](https://cdn.jsdelivr.net/gh/zip95297/zip95297.github.io@main/source/images/%E6%9C%BA%E5%99%A8%E5%AD%A6%E4%B9%A0%E5%A4%8D%E4%B9%A0%E8%B5%84%E6%96%99/Pasted%20image%2020250426170353.webp?raw=true) -几何性质 【2|1】![](https://cdn.jsdelivr.net/gh/zip95297/zip95297.github.io@main/source/images/%E6%9C%BA%E5%99%A8%E5%AD%A6%E4%B9%A0%E5%A4%8D%E4%B9%A0%E8%B5%84%E6%96%99/Pasted%20image%2020250426170523.webp?raw=true) - -如过用一个二分类器 处理多分类的情况,可以: -- 一对其他 分类器(K-1个二分类器) -- 一对一 分类器 ($C^2_k$ 个二分类器) -但是会出现不可分的区域![](https://cdn.jsdelivr.net/gh/zip95297/zip95297.github.io@main/source/images/%E6%9C%BA%E5%99%A8%E5%AD%A6%E4%B9%A0%E5%A4%8D%E4%B9%A0%E8%B5%84%E6%96%99/Pasted%20image%2020250426170742.webp?raw=true) -#### 多分类情形 -K类判别函数 $y_k(x)=w_k^Tx+w_{k0}$ 如果一个k=k\*大于 其他所有的 y 那就是Ck\* 类 -![](https://cdn.jsdelivr.net/gh/zip95297/zip95297.github.io@main/source/images/%E6%9C%BA%E5%99%A8%E5%AD%A6%E4%B9%A0%E5%A4%8D%E4%B9%A0%E8%B5%84%E6%96%99/Pasted%20image%2020250426171017.webp?raw=true) - -### 概率判别模型 Probabilistic Discriminative Models (logistic回归\*) - -直接对 后验概率 $p(C_k|x)$ 建模,然后用决策论来确定x的输入类别 比如取最高的等等。例如 逻辑(logistic)回归、神经网络(最后softmax的结果就是一组后验概率) -#### Logistic回归 (分类模型) - -在线性模型的基础上 加上了激活函数 得到概率大小 下面的例子用的是sigmoid函数 -![](https://cdn.jsdelivr.net/gh/zip95297/zip95297.github.io@main/source/images/%E6%9C%BA%E5%99%A8%E5%AD%A6%E4%B9%A0%E5%A4%8D%E4%B9%A0%E8%B5%84%E6%96%99/Pasted%20image%2020250426171443.webp?raw=true) -![](https://cdn.jsdelivr.net/gh/zip95297/zip95297.github.io@main/source/images/%E6%9C%BA%E5%99%A8%E5%AD%A6%E4%B9%A0%E5%A4%8D%E4%B9%A0%E8%B5%84%E6%96%99/Pasted%20image%2020250426171825.webp?raw=true) -直接对y=1 对线性函数激活得到概率 -![](https://cdn.jsdelivr.net/gh/zip95297/zip95297.github.io@main/source/images/%E6%9C%BA%E5%99%A8%E5%AD%A6%E4%B9%A0%E5%A4%8D%E4%B9%A0%E8%B5%84%E6%96%99/Pasted%20image%2020250426171929.webp?raw=true) -决策面就是两个类别的后验概率相等 也就是 $p(C_1|x)=p(C_2|x)$ 可以根据这个推出逻辑回归模型的决策面是线性的![](https://cdn.jsdelivr.net/gh/zip95297/zip95297.github.io@main/source/images/%E6%9C%BA%E5%99%A8%E5%AD%A6%E4%B9%A0%E5%A4%8D%E4%B9%A0%E8%B5%84%E6%96%99/Pasted%20image%2020250426172010.webp?raw=true) -sigmoid:![](https://cdn.jsdelivr.net/gh/zip95297/zip95297.github.io@main/source/images/%E6%9C%BA%E5%99%A8%E5%AD%A6%E4%B9%A0%E5%A4%8D%E4%B9%A0%E8%B5%84%E6%96%99/Pasted%20image%2020250426171645.webp?raw=true) -##### 逻辑回归模型的训练 - -![](https://cdn.jsdelivr.net/gh/zip95297/zip95297.github.io@main/source/images/%E6%9C%BA%E5%99%A8%E5%AD%A6%E4%B9%A0%E5%A4%8D%E4%B9%A0%E8%B5%84%E6%96%99/Pasted%20image%2020250426172506.webp?raw=true) -![](https://cdn.jsdelivr.net/gh/zip95297/zip95297.github.io@main/source/images/%E6%9C%BA%E5%99%A8%E5%AD%A6%E4%B9%A0%E5%A4%8D%E4%B9%A0%E8%B5%84%E6%96%99/Pasted%20image%2020250426172544.webp?raw=true) -![](https://cdn.jsdelivr.net/gh/zip95297/zip95297.github.io@main/source/images/%E6%9C%BA%E5%99%A8%E5%AD%A6%E4%B9%A0%E5%A4%8D%E4%B9%A0%E8%B5%84%E6%96%99/Pasted%20image%2020250426172553.webp?raw=true) -![](https://cdn.jsdelivr.net/gh/zip95297/zip95297.github.io@main/source/images/%E6%9C%BA%E5%99%A8%E5%AD%A6%E4%B9%A0%E5%A4%8D%E4%B9%A0%E8%B5%84%E6%96%99/Pasted%20image%2020250426172652.webp?raw=true) -逻辑回归就是在线性回归模型的基础上套了一个sigmoid LOSS就是两个类别的的交叉熵![](https://cdn.jsdelivr.net/gh/zip95297/zip95297.github.io@main/source/images/%E6%9C%BA%E5%99%A8%E5%AD%A6%E4%B9%A0%E5%A4%8D%E4%B9%A0%E8%B5%84%E6%96%99/Pasted%20image%2020250426172717.webp?raw=true) -##### 逻辑回归和神经网络模型的对比 - -| **特性** | **逻辑回归** | **神经网络** | -| --------- | ---------------------------- | ----------------------------- | -| **模型结构** | 单层(仅输入层 + 输出层) | 多层(输入层 + 隐藏层 + 输出层) | -| **决策边界** | 线性(超平面) | 非线性(可拟合复杂边界) | -| **表达能力** | 只能解决线性可分问题 | 可逼近任意复杂函数(通用近似定理) | -| **参数优化** | 使用梯度下降法优化权重 \( w \) | 使用反向传播(Backpropagation)优化多层权重 | -| **输出类型** | 概率(通过Sigmoid函数) | 概率(Softmax)或任意值(取决于输出层设计) | -| **适用任务** | 二分类(默认)、可扩展至多分类(One-vs-Rest) | 二分类、多分类、回归、生成任务等 | -| **计算复杂度** | 低(参数少,训练快) | 高(参数多,需大量数据和计算资源) | -| **可解释性** | 高(权重直接反映特征重要性) | 低(黑箱模型,难以解释隐藏层) | - -### 概率生成模型 Probabilistic Generative Models (朴素贝叶斯分类器\*) - -先对 类的条件密度 $p(x|C_k)$ 和 先验类概率分布 $p(C_k)$ 建模,然后再用 贝叶斯定理 计算 后验类概率分布 $p(C_k|x)$ : -$$ -p(C_k|x)=\cfrac{p(x|C_k)p(C_k)}{p(x)} -$$ -最后,使用决策论来确定每个输入x的类别 - -等价地,直接对 联合概率分布 $p(x,C_k)$ 建模 , 再做归一化得到后验概率。 - -统计类别分布 得到先验 在类内 统计属性分布 得到似然 -#### 朴素贝叶斯分类器 - -估计后验概率的主要困难在于:类条件概率是 x 在所有属性上的联合概率(输入的x有很多维度), 难以用有先限的训练样本估计获得 - -朴素贝叶斯分类器 采用 ***属性条件独立性假设*** , 每个属性独立地对分类结果发生影响。 - -更具上述假设 后验概率可以写为(d是属性个数 i是第i个属性): -$$ -P(c|x)=\cfrac{P(c)P(x|c)}{P(x)}=\cfrac{P(c)}{P(x)}\prod_{i=1}^dP(x_i|c) -$$ -对于所有类别 P(x) 相同 , 所以***贝叶斯判定准则***为: -$$ -h_{nb}(x)=\underset{c\in y}{\arg\max} \, P(c)\prod^d_{i=1}P(x_i|c) -$$ -即对每个样本x 选择后验概率最大的类 - -##### 朴素贝叶斯分类器计算例子 - -![](https://cdn.jsdelivr.net/gh/zip95297/zip95297.github.io@main/source/images/%E6%9C%BA%E5%99%A8%E5%AD%A6%E4%B9%A0%E5%A4%8D%E4%B9%A0%E8%B5%84%E6%96%99/Pasted%20image%2020250426175412.webp?raw=true) -![](https://cdn.jsdelivr.net/gh/zip95297/zip95297.github.io@main/source/images/%E6%9C%BA%E5%99%A8%E5%AD%A6%E4%B9%A0%E5%A4%8D%E4%B9%A0%E8%B5%84%E6%96%99/Pasted%20image%2020250426175419.webp?raw=true) -![](https://cdn.jsdelivr.net/gh/zip95297/zip95297.github.io@main/source/images/%E6%9C%BA%E5%99%A8%E5%AD%A6%E4%B9%A0%E5%A4%8D%E4%B9%A0%E8%B5%84%E6%96%99/Pasted%20image%2020250426175424.webp?raw=true) - -# 第五章 支持向量机 与 核方法 - -## SVM原理 - -支持向量机的三个关键想法: -- 通过优化来求解一个超平面分类器 -- 寻找最大间隔分类器来提高模型的泛化能力(结构化风险最小) -- 采用 **核技巧** 使得在高维度特征空间的计算更有效率 - -假设数据是线性可分的,那么一定存在两条平行的直线分别经过两类样本最外侧的数据 设这两条直线是 $wx+b=1$ 、$wx+b=-1$ 设置是 ±1 没有什么特殊意义 主要是为了方便表示间距margin - -在这个设置下 相当于学习一个基于间隔的分类器 -![](https://cdn.jsdelivr.net/gh/zip95297/zip95297.github.io@main/source/images/%E6%9C%BA%E5%99%A8%E5%AD%A6%E4%B9%A0%E5%A4%8D%E4%B9%A0%E8%B5%84%E6%96%99/Pasted%20image%2020250426204559.webp?raw=true) -> 为什么要写成 $\frac{1}{2}||w||^2$ ? - 主要是为了简化下面的计算,如果写成 $||w||$ 在计算梯度时候会出现 $\sqrt{w^Tw}$ - -### 对偶问题 - -在解决这个问题时候 使用 拉格朗日乘子法 将其转化为对偶问题: -1. 引入拉格朗日乘子 $\alpha_i \ge 0$ 得到拉格朗日函数 -$$ -L(w,b,\alpha)=\frac{1}{2}||w||^2-\sum^m_{i=1}\alpha_i(y_i(w^Tx_i+b)-1) -$$ - -2. 令 $L(w,b,\alpha)$ 对 $w$ 和 $b$ 的偏导数为0可得: -$$ -w=\sum^m_{i=1}\alpha_iy_ix_i\ \ ,\ \sum^m_{i=1}\alpha_iy_i=0 -$$ - -3. 回代: ![](https://cdn.jsdelivr.net/gh/zip95297/zip95297.github.io@main/source/images/%E6%9C%BA%E5%99%A8%E5%AD%A6%E4%B9%A0%E5%A4%8D%E4%B9%A0%E8%B5%84%E6%96%99/Pasted%20image%2020250427134510.webp?raw=true) - - -> 拉格朗日乘子法:通过乘子将约束引入函数得到一个无约束的函数 - ![](https://cdn.jsdelivr.net/gh/zip95297/zip95297.github.io@main/source/images/%E6%9C%BA%E5%99%A8%E5%AD%A6%E4%B9%A0%E5%A4%8D%E4%B9%A0%E8%B5%84%E6%96%99/Pasted%20image%2020250426210330.webp?raw=true) - - -支持向量就是 wx+b=±1 的向量 ![](https://cdn.jsdelivr.net/gh/zip95297/zip95297.github.io@main/source/images/%E6%9C%BA%E5%99%A8%E5%AD%A6%E4%B9%A0%E5%A4%8D%E4%B9%A0%E8%B5%84%E6%96%99/Pasted%20image%2020250427135009.webp?raw=true) - -## 核技巧 - -一般的 上面所提到的支持向量机是对于 数据线性可分 的情况 , 但是可能存在线性不可分的数据,这样就要把 x 变换到高维空间 使其在高维空间中线性可分。 - -设样本 x 映射后的向量为 $\phi(x)$ ,划分超平面是 $f(x)=w^T\phi(x)+b$ -![](https://cdn.jsdelivr.net/gh/zip95297/zip95297.github.io@main/source/images/%E6%9C%BA%E5%99%A8%E5%AD%A6%E4%B9%A0%E5%A4%8D%E4%B9%A0%E8%B5%84%E6%96%99/Pasted%20image%2020250427135943.webp?raw=true) - -也就是说 两个x的高维表示 都是内积的形式存在 不用显式的计算出来 所以引入 ***核函数*** 来代替: -$$ -\cal{K}(x_i,x_j)=\phi(x_i)^T\phi(x_j) -$$ - -核函数的优点:不需要知道和计算 $\phi(x)$ (纬度很高 计算量很大) 即使特征空间的纬度非常高,计算仍是可行的,因为 核函数 是在原空间内运算。 - -> 存在判定![](https://cdn.jsdelivr.net/gh/zip95297/zip95297.github.io@main/source/images/%E6%9C%BA%E5%99%A8%E5%AD%A6%E4%B9%A0%E5%A4%8D%E4%B9%A0%E8%B5%84%E6%96%99/Pasted%20image%2020250427143630.webp?raw=true) - -> 常用核函数 ![](https://cdn.jsdelivr.net/gh/zip95297/zip95297.github.io@main/source/images/%E6%9C%BA%E5%99%A8%E5%AD%A6%E4%B9%A0%E5%A4%8D%E4%B9%A0%E8%B5%84%E6%96%99/Pasted%20image%2020250427143846.webp?raw=true) - -> 实现非线性分类![](https://cdn.jsdelivr.net/gh/zip95297/zip95297.github.io@main/source/images/%E6%9C%BA%E5%99%A8%E5%AD%A6%E4%B9%A0%E5%A4%8D%E4%B9%A0%E8%B5%84%E6%96%99/Pasted%20image%2020250427144027.webp?raw=true) - 通过这个解出 $\alpha^*$ 后 可以得到 决策函数 $f(x)=sgn(\sum^l_{i=1}y_i\alpha^*K(x_i,x)+b^*)$ - ![](https://cdn.jsdelivr.net/gh/zip95297/zip95297.github.io@main/source/images/%E6%9C%BA%E5%99%A8%E5%AD%A6%E4%B9%A0%E5%A4%8D%E4%B9%A0%E8%B5%84%E6%96%99/Pasted%20image%2020250427144249.webp?raw=true) - - -# 第六章 集成学习 - -## 集成学习的概念和类型 - -集成学习 是通过 构建并结合多个学习器来完成学习任务,提升性能![](https://cdn.jsdelivr.net/gh/zip95297/zip95297.github.io@main/source/images/%E6%9C%BA%E5%99%A8%E5%AD%A6%E4%B9%A0%E5%A4%8D%E4%B9%A0%E8%B5%84%E6%96%99/Pasted%20image%2020250427150521.webp?raw=true) - -学习器可以是 同质的 也可以是 异质的 (个体学习器由不同的学习算法生成 成为 组件学习期) -其中的同质 个体学习器 称为 基学习器 对应的算法叫做 基学习算法 - -弱学习器:精度略高于随机 强学习器:精度较高 - -![](https://cdn.jsdelivr.net/gh/zip95297/zip95297.github.io@main/source/images/%E6%9C%BA%E5%99%A8%E5%AD%A6%E4%B9%A0%E5%A4%8D%E4%B9%A0%E8%B5%84%E6%96%99/Pasted%20image%2020250427151810.webp?raw=true)![](https://cdn.jsdelivr.net/gh/zip95297/zip95297.github.io@main/source/images/%E6%9C%BA%E5%99%A8%E5%AD%A6%E4%B9%A0%E5%A4%8D%E4%B9%A0%E8%B5%84%E6%96%99/Pasted%20image%2020250427151819.webp?raw=true) - -> 简单分析 - ![](https://cdn.jsdelivr.net/gh/zip95297/zip95297.github.io@main/source/images/%E6%9C%BA%E5%99%A8%E5%AD%A6%E4%B9%A0%E5%A4%8D%E4%B9%A0%E8%B5%84%E6%96%99/Pasted%20image%2020250427150618.webp?raw=true) - ![](https://cdn.jsdelivr.net/gh/zip95297/zip95297.github.io@main/source/images/%E6%9C%BA%E5%99%A8%E5%AD%A6%E4%B9%A0%E5%A4%8D%E4%B9%A0%E8%B5%84%E6%96%99/Pasted%20image%2020250427150622.webp?raw=true) - ![](https://cdn.jsdelivr.net/gh/zip95297/zip95297.github.io@main/source/images/%E6%9C%BA%E5%99%A8%E5%AD%A6%E4%B9%A0%E5%A4%8D%E4%B9%A0%E8%B5%84%E6%96%99/Pasted%20image%2020250427150640.webp?raw=true) - 然而我们必须注意到,上面的分析有一个关键假设:基学习器的误差相互 独立.在现实任务中,个体学习器是为解决同一个问题训练出来的,它们显然不 可能相互独立!事实上,个体学习器的“准确性”和“多样性”本身就存在冲 突.一般的,准确性很高之后,要增加多样性就需牺牲准确性,事实上,如何产 生并结合“好而不同”的个体学习器,恰是集成学习研究的核心. - -根据个体学习器的生成方式,目前的集成学习方法大致可分为两大类,即 个体学习器间存在强依赖关系、必须串行生成的序列化方法,以及个体学习器 间不存在强依赖关系、可同时生成的并行化方法;前者的代表是Boosting,后 者的代表是 Bagging 和“随机森林”(Random Forest). -## AdaBoost算法流程 - -![](https://cdn.jsdelivr.net/gh/zip95297/zip95297.github.io@main/source/images/%E6%9C%BA%E5%99%A8%E5%AD%A6%E4%B9%A0%E5%A4%8D%E4%B9%A0%E8%B5%84%E6%96%99/Pasted%20image%2020250427151909.webp?raw=true)![](https://cdn.jsdelivr.net/gh/zip95297/zip95297.github.io@main/source/images/%E6%9C%BA%E5%99%A8%E5%AD%A6%E4%B9%A0%E5%A4%8D%E4%B9%A0%E8%B5%84%E6%96%99/Pasted%20image%2020250427151924.webp?raw=true) - -![](https://cdn.jsdelivr.net/gh/zip95297/zip95297.github.io@main/source/images/%E6%9C%BA%E5%99%A8%E5%AD%A6%E4%B9%A0%E5%A4%8D%E4%B9%A0%E8%B5%84%E6%96%99/Pasted%20image%2020250427151700.webp?raw=true) -![](https://cdn.jsdelivr.net/gh/zip95297/zip95297.github.io@main/source/images/%E6%9C%BA%E5%99%A8%E5%AD%A6%E4%B9%A0%E5%A4%8D%E4%B9%A0%E8%B5%84%E6%96%99/Pasted%20image%2020250427153537.webp?raw=true) - -![](https://cdn.jsdelivr.net/gh/zip95297/zip95297.github.io@main/source/images/%E6%9C%BA%E5%99%A8%E5%AD%A6%E4%B9%A0%E5%A4%8D%E4%B9%A0%E8%B5%84%E6%96%99/Pasted%20image%2020250427153627.webp?raw=true) -![](https://cdn.jsdelivr.net/gh/zip95297/zip95297.github.io@main/source/images/%E6%9C%BA%E5%99%A8%E5%AD%A6%E4%B9%A0%E5%A4%8D%E4%B9%A0%E8%B5%84%E6%96%99/Pasted%20image%2020250427153635.webp?raw=true) -![](https://cdn.jsdelivr.net/gh/zip95297/zip95297.github.io@main/source/images/%E6%9C%BA%E5%99%A8%E5%AD%A6%E4%B9%A0%E5%A4%8D%E4%B9%A0%E8%B5%84%E6%96%99/Pasted%20image%2020250427153643.webp?raw=true) -![](https://cdn.jsdelivr.net/gh/zip95297/zip95297.github.io@main/source/images/%E6%9C%BA%E5%99%A8%E5%AD%A6%E4%B9%A0%E5%A4%8D%E4%B9%A0%E8%B5%84%E6%96%99/Pasted%20image%2020250427153648.webp?raw=true) -这个 $Z_m$ 其实就是对 $w_{mi}e^{\alpha_m}$ 做softmax 让下次的模型更注重错误的样本 -![](https://cdn.jsdelivr.net/gh/zip95297/zip95297.github.io@main/source/images/%E6%9C%BA%E5%99%A8%E5%AD%A6%E4%B9%A0%E5%A4%8D%E4%B9%A0%E8%B5%84%E6%96%99/Pasted%20image%2020250427154804.webp?raw=true) - -### 例子 - -![](https://cdn.jsdelivr.net/gh/zip95297/zip95297.github.io@main/source/images/%E6%9C%BA%E5%99%A8%E5%AD%A6%E4%B9%A0%E5%A4%8D%E4%B9%A0%E8%B5%84%E6%96%99/Pasted%20image%2020250427153858.webp?raw=true) -![](https://cdn.jsdelivr.net/gh/zip95297/zip95297.github.io@main/source/images/%E6%9C%BA%E5%99%A8%E5%AD%A6%E4%B9%A0%E5%A4%8D%E4%B9%A0%E8%B5%84%E6%96%99/Pasted%20image%2020250427153906.webp?raw=true) -![](https://cdn.jsdelivr.net/gh/zip95297/zip95297.github.io@main/source/images/%E6%9C%BA%E5%99%A8%E5%AD%A6%E4%B9%A0%E5%A4%8D%E4%B9%A0%E8%B5%84%E6%96%99/Pasted%20image%2020250427153933.webp?raw=true) -![](https://cdn.jsdelivr.net/gh/zip95297/zip95297.github.io@main/source/images/%E6%9C%BA%E5%99%A8%E5%AD%A6%E4%B9%A0%E5%A4%8D%E4%B9%A0%E8%B5%84%E6%96%99/Pasted%20image%2020250427153939.webp?raw=true) -![](https://cdn.jsdelivr.net/gh/zip95297/zip95297.github.io@main/source/images/%E6%9C%BA%E5%99%A8%E5%AD%A6%E4%B9%A0%E5%A4%8D%E4%B9%A0%E8%B5%84%E6%96%99/Pasted%20image%2020250427154142.webp?raw=true) - -## Bagging算法流程 - -![](https://cdn.jsdelivr.net/gh/zip95297/zip95297.github.io@main/source/images/%E6%9C%BA%E5%99%A8%E5%AD%A6%E4%B9%A0%E5%A4%8D%E4%B9%A0%E8%B5%84%E6%96%99/Pasted%20image%2020250427155504.webp?raw=true) -欲得到泛化性能强的集成,集成中的个体学习器应尽可能相 互独立;虽然“独立”在现实任务中无法做到,但可以设法使基学习器尽可能 具有较大的差异.给定一个训练数据集,一种可能的做法是对训练样本进行采 样,产生出若干个不同的子集,再从每个数据子集中训练出一个基学习器.这 样,由于训练数据不同,我们获得的基学习器可望具有比较大的差异.然而,为 获得好的集成,我们同时还希望个体学习器不能太差.如果采样出的每个子集 都完全不同,则每个基学习器只用到了一小部分训练数据,甚至不足以进行有 效学习,这显然无法确保产生出比较好的基学习器.为解决这个问题,我们可考 虑使用相互有交叠的采样子集. - -![](https://cdn.jsdelivr.net/gh/zip95297/zip95297.github.io@main/source/images/%E6%9C%BA%E5%99%A8%E5%AD%A6%E4%B9%A0%E5%A4%8D%E4%B9%A0%E8%B5%84%E6%96%99/Pasted%20image%2020250427155736.webp?raw=true) -大I 是指数函数 在里面为真假时取1,0 -对预测输出进行结合时,通常对分类任务使用简单投票法,对于回归问题使用简单平均法。 -![](https://cdn.jsdelivr.net/gh/zip95297/zip95297.github.io@main/source/images/%E6%9C%BA%E5%99%A8%E5%AD%A6%E4%B9%A0%E5%A4%8D%E4%B9%A0%E8%B5%84%E6%96%99/Pasted%20image%2020250427160408.webp?raw=true) - - ->获取每个基学习器的训练集方法![](https://cdn.jsdelivr.net/gh/zip95297/zip95297.github.io@main/source/images/%E6%9C%BA%E5%99%A8%E5%AD%A6%E4%B9%A0%E5%A4%8D%E4%B9%A0%E8%B5%84%E6%96%99/Pasted%20image%2020250427160230.webp?raw=true) - 包外估计 用 训练集 不包括x的对x做预测![](https://cdn.jsdelivr.net/gh/zip95297/zip95297.github.io@main/source/images/%E6%9C%BA%E5%99%A8%E5%AD%A6%E4%B9%A0%E5%A4%8D%E4%B9%A0%E8%B5%84%E6%96%99/Pasted%20image%2020250427160456.webp?raw=true) - - -# 第七章 聚类方法 - -> 无监督学习 - 是机器学习的一种范式,其核心特点是模型从**未标注的数据**中自动发现隐藏的模式、结构或规律,而无需人工提供标签(即没有明确的“正确答案”作为监督信号)。与监督学习不同,无监督学习的目标是探索数据本身的内在特性。 - - 模型: 函数 $z=g_\theta(x)$ , 条件概率分布 $P_\theta(z|x)$ 或者条件概率分布 $P_\theta(x|z)$ - - 策略:目标函数优化 - - 算法: 迭代算法,通过迭代达到对目标函数的最优化 - -> 聚类 - 是将样本集合中相似的样本(实例)分配到相同的类,不相似的样本分配到不同的类。 聚类时,样本通常是欧氏空间中的向量,类别不是事先给定,而 是从数据中自动发现,但类别的个数通常是事先给定的。样本之间的相似度或距离由应用决定。 ·如果一个样本只能属于一个类,则称为硬聚类(hard clustering),如果一个样本可以属于多个类,则称软聚类(soft clustering)。 - 聚类算法有 原型(k均值、高斯混合模型)、密度、层次、谱聚类 - -## K均值算法流程以及特点 - -K均值聚类是基于样本集合划分的聚类算法。将样本集合划分为k个子集,构成k个类,将n个样本分到k个类别中,每个样本到其所属类的中心距离最小。硬聚类算法,每个样本只有一个类。 -![](https://cdn.jsdelivr.net/gh/zip95297/zip95297.github.io@main/source/images/%E6%9C%BA%E5%99%A8%E5%AD%A6%E4%B9%A0%E5%A4%8D%E4%B9%A0%E8%B5%84%E6%96%99/Pasted%20image%2020250427165900.webp?raw=true) -![](https://cdn.jsdelivr.net/gh/zip95297/zip95297.github.io@main/source/images/%E6%9C%BA%E5%99%A8%E5%AD%A6%E4%B9%A0%E5%A4%8D%E4%B9%A0%E8%B5%84%E6%96%99/Pasted%20image%2020250427170505.webp?raw=true) -### \_K均值划分策略 - -![](https://cdn.jsdelivr.net/gh/zip95297/zip95297.github.io@main/source/images/%E6%9C%BA%E5%99%A8%E5%AD%A6%E4%B9%A0%E5%A4%8D%E4%B9%A0%E8%B5%84%E6%96%99/Pasted%20image%2020250427170031.webp?raw=true) -![](https://cdn.jsdelivr.net/gh/zip95297/zip95297.github.io@main/source/images/%E6%9C%BA%E5%99%A8%E5%AD%A6%E4%B9%A0%E5%A4%8D%E4%B9%A0%E8%B5%84%E6%96%99/Pasted%20image%2020250427170044.webp?raw=true) -![](https://cdn.jsdelivr.net/gh/zip95297/zip95297.github.io@main/source/images/%E6%9C%BA%E5%99%A8%E5%AD%A6%E4%B9%A0%E5%A4%8D%E4%B9%A0%E8%B5%84%E6%96%99/Pasted%20image%2020250427170137.webp?raw=true) - - - - -### K均值算法实现 - -![](https://cdn.jsdelivr.net/gh/zip95297/zip95297.github.io@main/source/images/%E6%9C%BA%E5%99%A8%E5%AD%A6%E4%B9%A0%E5%A4%8D%E4%B9%A0%E8%B5%84%E6%96%99/Pasted%20image%2020250427170239.webp?raw=true) -![](https://cdn.jsdelivr.net/gh/zip95297/zip95297.github.io@main/source/images/%E6%9C%BA%E5%99%A8%E5%AD%A6%E4%B9%A0%E5%A4%8D%E4%B9%A0%E8%B5%84%E6%96%99/Pasted%20image%2020250427170247.webp?raw=true) - - - - -### K均值算法特点 - -![](https://cdn.jsdelivr.net/gh/zip95297/zip95297.github.io@main/source/images/%E6%9C%BA%E5%99%A8%E5%AD%A6%E4%B9%A0%E5%A4%8D%E4%B9%A0%E8%B5%84%E6%96%99/Pasted%20image%2020250427170404.webp?raw=true) -![](https://cdn.jsdelivr.net/gh/zip95297/zip95297.github.io@main/source/images/%E6%9C%BA%E5%99%A8%E5%AD%A6%E4%B9%A0%E5%A4%8D%E4%B9%A0%E8%B5%84%E6%96%99/Pasted%20image%2020250427170411.webp?raw=true) -![](https://cdn.jsdelivr.net/gh/zip95297/zip95297.github.io@main/source/images/%E6%9C%BA%E5%99%A8%E5%AD%A6%E4%B9%A0%E5%A4%8D%E4%B9%A0%E8%B5%84%E6%96%99/Pasted%20image%2020250427170436.webp?raw=true) -![](https://cdn.jsdelivr.net/gh/zip95297/zip95297.github.io@main/source/images/%E6%9C%BA%E5%99%A8%E5%AD%A6%E4%B9%A0%E5%A4%8D%E4%B9%A0%E8%B5%84%E6%96%99/Pasted%20image%2020250427170554.webp?raw=true) - - - -# 第八章 混合模型与期望最大 - -## EM算法流程、收敛性以及应用 - -> DEF: - 最大期望算法(Expectation-maximization algorithm,又译为期望最大化算法),是在概率模型中寻找参数最大似然估计或者最大后验估计的算法,其中概率模型依赖于无法观测的隐性变量。最大期望算法经过两个步骤交替进行计算,==**第一步**==是计算期望(E),利用对隐藏变量的现有估计值,计算其最大似然估计值;==**第二步**==是最大化(M),最大化在E步上求得的最大似然值来计算参数的值。M步上找到的参数估计值被用于下一个E步计算中,这个过程不断交替进行。 - ->举个例子: - 如果要统计一个学校男女生身高体重的分布,男生的分布和女生肯定是两个不同的分布,但是如果数据中只有身高体重Y 并未标注 男生女生Z 那么如果要找到男生女生的分布就 比较困难, EM算法就是为了解决这种问题 - -> 理解: - 通过 估计的 隐变量 的先验 计算 所有观测数据Y 的 似然概率 然后用 最大似然 得到 每条Y 最有可能对应的 Z 的值,然后 根据估计过后数据 更新 Z 的估计先验 重复迭代这个过程 (实际上这个Z不是一个常量 而是一个随机变量)![](https://cdn.jsdelivr.net/gh/zip95297/zip95297.github.io@main/source/images/%E6%9C%BA%E5%99%A8%E5%AD%A6%E4%B9%A0%E5%A4%8D%E4%B9%A0%E8%B5%84%E6%96%99/Pasted%20image%2020250427173617.webp?raw=true) - -### EM算法流程 - -![](https://cdn.jsdelivr.net/gh/zip95297/zip95297.github.io@main/source/images/%E6%9C%BA%E5%99%A8%E5%AD%A6%E4%B9%A0%E5%A4%8D%E4%B9%A0%E8%B5%84%E6%96%99/Pasted%20image%2020250427174902.webp?raw=true) -![](https://cdn.jsdelivr.net/gh/zip95297/zip95297.github.io@main/source/images/%E6%9C%BA%E5%99%A8%E5%AD%A6%E4%B9%A0%E5%A4%8D%E4%B9%A0%E8%B5%84%E6%96%99/Pasted%20image%2020250427174911.webp?raw=true) - - -### \_EM特点 - -![](https://cdn.jsdelivr.net/gh/zip95297/zip95297.github.io@main/source/images/%E6%9C%BA%E5%99%A8%E5%AD%A6%E4%B9%A0%E5%A4%8D%E4%B9%A0%E8%B5%84%E6%96%99/Pasted%20image%2020250427174444.webp?raw=true) - -### EM算法的收敛性 - -![](https://cdn.jsdelivr.net/gh/zip95297/zip95297.github.io@main/source/images/%E6%9C%BA%E5%99%A8%E5%AD%A6%E4%B9%A0%E5%A4%8D%E4%B9%A0%E8%B5%84%E6%96%99/Pasted%20image%2020250427175204.webp?raw=true) -![](https://cdn.jsdelivr.net/gh/zip95297/zip95297.github.io@main/source/images/%E6%9C%BA%E5%99%A8%E5%AD%A6%E4%B9%A0%E5%A4%8D%E4%B9%A0%E8%B5%84%E6%96%99/Pasted%20image%2020250427175209.webp?raw=true) -![](https://cdn.jsdelivr.net/gh/zip95297/zip95297.github.io@main/source/images/%E6%9C%BA%E5%99%A8%E5%AD%A6%E4%B9%A0%E5%A4%8D%E4%B9%A0%E8%B5%84%E6%96%99/Pasted%20image%2020250427175215.webp?raw=true) -![](https://cdn.jsdelivr.net/gh/zip95297/zip95297.github.io@main/source/images/%E6%9C%BA%E5%99%A8%E5%AD%A6%E4%B9%A0%E5%A4%8D%E4%B9%A0%E8%B5%84%E6%96%99/Pasted%20image%2020250427175312.webp?raw=true) -![](https://cdn.jsdelivr.net/gh/zip95297/zip95297.github.io@main/source/images/%E6%9C%BA%E5%99%A8%E5%AD%A6%E4%B9%A0%E5%A4%8D%E4%B9%A0%E8%B5%84%E6%96%99/Pasted%20image%2020250427175359.webp?raw=true) - - - - -### EM算法的应用 - -- EM算法在⾼斯混合模型学习 -- EM在伯努利混合模型上的应用 - -## 混合高斯模型GMM的原理和特点 - -![](https://cdn.jsdelivr.net/gh/zip95297/zip95297.github.io@main/source/images/%E6%9C%BA%E5%99%A8%E5%AD%A6%E4%B9%A0%E5%A4%8D%E4%B9%A0%E8%B5%84%E6%96%99/Pasted%20image%2020250427180153.webp?raw=true) -![](https://cdn.jsdelivr.net/gh/zip95297/zip95297.github.io@main/source/images/%E6%9C%BA%E5%99%A8%E5%AD%A6%E4%B9%A0%E5%A4%8D%E4%B9%A0%E8%B5%84%E6%96%99/Pasted%20image%2020250427180223.webp?raw=true) -![](https://cdn.jsdelivr.net/gh/zip95297/zip95297.github.io@main/source/images/%E6%9C%BA%E5%99%A8%E5%AD%A6%E4%B9%A0%E5%A4%8D%E4%B9%A0%E8%B5%84%E6%96%99/Pasted%20image%2020250427180236.webp?raw=true) -![](https://cdn.jsdelivr.net/gh/zip95297/zip95297.github.io@main/source/images/%E6%9C%BA%E5%99%A8%E5%AD%A6%E4%B9%A0%E5%A4%8D%E4%B9%A0%E8%B5%84%E6%96%99/Pasted%20image%2020250427180243.webp?raw=true) -![](https://cdn.jsdelivr.net/gh/zip95297/zip95297.github.io@main/source/images/%E6%9C%BA%E5%99%A8%E5%AD%A6%E4%B9%A0%E5%A4%8D%E4%B9%A0%E8%B5%84%E6%96%99/Pasted%20image%2020250427180250.webp?raw=true) -![](https://cdn.jsdelivr.net/gh/zip95297/zip95297.github.io@main/source/images/%E6%9C%BA%E5%99%A8%E5%AD%A6%E4%B9%A0%E5%A4%8D%E4%B9%A0%E8%B5%84%E6%96%99/Pasted%20image%2020250427180259.webp?raw=true) -![](https://cdn.jsdelivr.net/gh/zip95297/zip95297.github.io@main/source/images/%E6%9C%BA%E5%99%A8%E5%AD%A6%E4%B9%A0%E5%A4%8D%E4%B9%A0%E8%B5%84%E6%96%99/Pasted%20image%2020250427180306.webp?raw=true) - -> PPT: - ![](https://cdn.jsdelivr.net/gh/zip95297/zip95297.github.io@main/source/images/%E6%9C%BA%E5%99%A8%E5%AD%A6%E4%B9%A0%E5%A4%8D%E4%B9%A0%E8%B5%84%E6%96%99/Pasted%20image%2020250427191301.webp?raw=true) - ![](https://cdn.jsdelivr.net/gh/zip95297/zip95297.github.io@main/source/images/%E6%9C%BA%E5%99%A8%E5%AD%A6%E4%B9%A0%E5%A4%8D%E4%B9%A0%E8%B5%84%E6%96%99/Pasted%20image%2020250427191307.webp?raw=true) - ![](https://cdn.jsdelivr.net/gh/zip95297/zip95297.github.io@main/source/images/%E6%9C%BA%E5%99%A8%E5%AD%A6%E4%B9%A0%E5%A4%8D%E4%B9%A0%E8%B5%84%E6%96%99/Pasted%20image%2020250427191343.webp?raw=true) - ![](https://cdn.jsdelivr.net/gh/zip95297/zip95297.github.io@main/source/images/%E6%9C%BA%E5%99%A8%E5%AD%A6%E4%B9%A0%E5%A4%8D%E4%B9%A0%E8%B5%84%E6%96%99/Pasted%20image%2020250427191349.webp?raw=true) - 具体内容找PPT - -# 第九章 神经网络与深度学习 - -## 介绍 - -> 神经网络的要素: - ![](https://cdn.jsdelivr.net/gh/zip95297/zip95297.github.io@main/source/images/%E6%9C%BA%E5%99%A8%E5%AD%A6%E4%B9%A0%E5%A4%8D%E4%B9%A0%E8%B5%84%E6%96%99/Pasted%20image%2020250427191930.webp?raw=true) - -> 网络结构: - ![](https://cdn.jsdelivr.net/gh/zip95297/zip95297.github.io@main/source/images/%E6%9C%BA%E5%99%A8%E5%AD%A6%E4%B9%A0%E5%A4%8D%E4%B9%A0%E8%B5%84%E6%96%99/Pasted%20image%2020250427193810.webp?raw=true) - -## 前馈神经网络、梯度下降算法、BP反向传播算法流程 - -### 前馈神经网络(全连接神经网络,MLP) - -整个网络中没有反馈 全部是正向连接 -![](https://cdn.jsdelivr.net/gh/zip95297/zip95297.github.io@main/source/images/%E6%9C%BA%E5%99%A8%E5%AD%A6%E4%B9%A0%E5%A4%8D%E4%B9%A0%E8%B5%84%E6%96%99/Pasted%20image%2020250427193836.webp?raw=true) -![](https://cdn.jsdelivr.net/gh/zip95297/zip95297.github.io@main/source/images/%E6%9C%BA%E5%99%A8%E5%AD%A6%E4%B9%A0%E5%A4%8D%E4%B9%A0%E8%B5%84%E6%96%99/Pasted%20image%2020250427203930.webp?raw=true) -![](https://cdn.jsdelivr.net/gh/zip95297/zip95297.github.io@main/source/images/%E6%9C%BA%E5%99%A8%E5%AD%A6%E4%B9%A0%E5%A4%8D%E4%B9%A0%E8%B5%84%E6%96%99/Pasted%20image%2020250427204015.webp?raw=true) -全连接神经网络的缺点 -![](https://cdn.jsdelivr.net/gh/zip95297/zip95297.github.io@main/source/images/%E6%9C%BA%E5%99%A8%E5%AD%A6%E4%B9%A0%E5%A4%8D%E4%B9%A0%E8%B5%84%E6%96%99/Pasted%20image%2020250428164432.webp?raw=true) -![](https://cdn.jsdelivr.net/gh/zip95297/zip95297.github.io@main/source/images/%E6%9C%BA%E5%99%A8%E5%AD%A6%E4%B9%A0%E5%A4%8D%E4%B9%A0%E8%B5%84%E6%96%99/Pasted%20image%2020250428164436.webp?raw=true) - - -### BP反向传播算法 - -对每个权重求梯度 - -> 构造一个两层的Net - ![](https://cdn.jsdelivr.net/gh/zip95297/zip95297.github.io@main/source/images/%E6%9C%BA%E5%99%A8%E5%AD%A6%E4%B9%A0%E5%A4%8D%E4%B9%A0%E8%B5%84%E6%96%99/Pasted%20image%2020250427211113.webp?raw=true)![](https://cdn.jsdelivr.net/gh/zip95297/zip95297.github.io@main/source/images/%E6%9C%BA%E5%99%A8%E5%AD%A6%E4%B9%A0%E5%A4%8D%E4%B9%A0%E8%B5%84%E6%96%99/Pasted%20image%2020250427211120.webp?raw=true)![](https://cdn.jsdelivr.net/gh/zip95297/zip95297.github.io@main/source/images/%E6%9C%BA%E5%99%A8%E5%AD%A6%E4%B9%A0%E5%A4%8D%E4%B9%A0%E8%B5%84%E6%96%99/Pasted%20image%2020250427211125.webp?raw=true)![](https://cdn.jsdelivr.net/gh/zip95297/zip95297.github.io@main/source/images/%E6%9C%BA%E5%99%A8%E5%AD%A6%E4%B9%A0%E5%A4%8D%E4%B9%A0%E8%B5%84%E6%96%99/Pasted%20image%2020250427211305.webp?raw=true)![](https://cdn.jsdelivr.net/gh/zip95297/zip95297.github.io@main/source/images/%E6%9C%BA%E5%99%A8%E5%AD%A6%E4%B9%A0%E5%A4%8D%E4%B9%A0%E8%B5%84%E6%96%99/Pasted%20image%2020250427211142.webp?raw=true)![](https://cdn.jsdelivr.net/gh/zip95297/zip95297.github.io@main/source/images/%E6%9C%BA%E5%99%A8%E5%AD%A6%E4%B9%A0%E5%A4%8D%E4%B9%A0%E8%B5%84%E6%96%99/Pasted%20image%2020250427211244.webp?raw=true) -#### BP算法原理 -> 前向过程![](https://cdn.jsdelivr.net/gh/zip95297/zip95297.github.io@main/source/images/%E6%9C%BA%E5%99%A8%E5%AD%A6%E4%B9%A0%E5%A4%8D%E4%B9%A0%E8%B5%84%E6%96%99/Pasted%20image%2020250427211113.webp?raw=true)![](https://cdn.jsdelivr.net/gh/zip95297/zip95297.github.io@main/source/images/%E6%9C%BA%E5%99%A8%E5%AD%A6%E4%B9%A0%E5%A4%8D%E4%B9%A0%E8%B5%84%E6%96%99/Pasted%20image%2020250427211142.webp?raw=true) - -在上面正向传播过程中,loss对wij的梯度为: -![](https://cdn.jsdelivr.net/gh/zip95297/zip95297.github.io@main/source/images/%E6%9C%BA%E5%99%A8%E5%AD%A6%E4%B9%A0%E5%A4%8D%E4%B9%A0%E8%B5%84%E6%96%99/Pasted%20image%2020250427211353.webp?raw=true) -![](https://cdn.jsdelivr.net/gh/zip95297/zip95297.github.io@main/source/images/%E6%9C%BA%E5%99%A8%E5%AD%A6%E4%B9%A0%E5%A4%8D%E4%B9%A0%E8%B5%84%E6%96%99/Pasted%20image%2020250427212939.webp?raw=true) -也就是 E 对一个权重 第J层第I个w 的梯度: -$$ -\cfrac{\partial E_n}{\partial w_{ji}}=\cfrac{\partial E_n}{\partial a_{j}}\cfrac{\partial a_j}{\partial w_{ji}} -$$ - -???为什么不是这种形式??如果relu的斜率是1 上面下面基本相等了 -$$ -\cfrac{\partial E_n}{\partial w_{ji}}=\cfrac{\partial E_n}{\partial z_{j}}\cfrac{\partial z_j}{\partial a_j}\cfrac{\partial a_j}{\partial w_{ji}} -$$ - - -| 符号 | 意义 | 举例 | -| --- | -------- | ----------- | -| a | 还没激活的加权和 | $w×x+b$ | -| z | 激活函数后的输出 | $\sigma(z)$ | - -![](https://cdn.jsdelivr.net/gh/zip95297/zip95297.github.io@main/source/images/%E6%9C%BA%E5%99%A8%E5%AD%A6%E4%B9%A0%E5%A4%8D%E4%B9%A0%E8%B5%84%E6%96%99/Pasted%20image%2020250427213023.webp?raw=true) -![](https://cdn.jsdelivr.net/gh/zip95297/zip95297.github.io@main/source/images/%E6%9C%BA%E5%99%A8%E5%AD%A6%E4%B9%A0%E5%A4%8D%E4%B9%A0%E8%B5%84%E6%96%99/Pasted%20image%2020250427213046.webp?raw=true) -![](https://cdn.jsdelivr.net/gh/zip95297/zip95297.github.io@main/source/images/%E6%9C%BA%E5%99%A8%E5%AD%A6%E4%B9%A0%E5%A4%8D%E4%B9%A0%E8%B5%84%E6%96%99/Pasted%20image%2020250427213052.webp?raw=true) -![](https://cdn.jsdelivr.net/gh/zip95297/zip95297.github.io@main/source/images/%E6%9C%BA%E5%99%A8%E5%AD%A6%E4%B9%A0%E5%A4%8D%E4%B9%A0%E8%B5%84%E6%96%99/Pasted%20image%2020250427213058.webp?raw=true) - -#### BP算法示例 - -![](https://cdn.jsdelivr.net/gh/zip95297/zip95297.github.io@main/source/images/%E6%9C%BA%E5%99%A8%E5%AD%A6%E4%B9%A0%E5%A4%8D%E4%B9%A0%E8%B5%84%E6%96%99/Pasted%20image%2020250427213347.webp?raw=true) -![](https://cdn.jsdelivr.net/gh/zip95297/zip95297.github.io@main/source/images/%E6%9C%BA%E5%99%A8%E5%AD%A6%E4%B9%A0%E5%A4%8D%E4%B9%A0%E8%B5%84%E6%96%99/Pasted%20image%2020250427213352.webp?raw=true) -![](https://cdn.jsdelivr.net/gh/zip95297/zip95297.github.io@main/source/images/%E6%9C%BA%E5%99%A8%E5%AD%A6%E4%B9%A0%E5%A4%8D%E4%B9%A0%E8%B5%84%E6%96%99/Pasted%20image%2020250427213501.webp?raw=true) -![](https://cdn.jsdelivr.net/gh/zip95297/zip95297.github.io@main/source/images/%E6%9C%BA%E5%99%A8%E5%AD%A6%E4%B9%A0%E5%A4%8D%E4%B9%A0%E8%B5%84%E6%96%99/Pasted%20image%2020250427213547.webp?raw=true) -![](https://cdn.jsdelivr.net/gh/zip95297/zip95297.github.io@main/source/images/%E6%9C%BA%E5%99%A8%E5%AD%A6%E4%B9%A0%E5%A4%8D%E4%B9%A0%E8%B5%84%E6%96%99/Pasted%20image%2020250427213551.webp?raw=true) -![](https://cdn.jsdelivr.net/gh/zip95297/zip95297.github.io@main/source/images/%E6%9C%BA%E5%99%A8%E5%AD%A6%E4%B9%A0%E5%A4%8D%E4%B9%A0%E8%B5%84%E6%96%99/Pasted%20image%2020250427213600.webp?raw=true) - - - -### 梯度下降算法 - -李航 附录A 常见的梯度下降算法在 第一章 三要素中 - -## CNN 基本结构,卷积、池化的计算、常用的激活函数 - -### CNN基本结构 - -上面提到了全链接前馈神经网络的缺点:参数多、局部不变性特征难以提取、展开丢失空间信息、参数多容易过拟合 - -所以提出了卷积神经网络 CNN 也是一种 前馈神经网络 受生物学上的 感受野机制启发。在结构上有三个**特点**: **局部感知、权重共享、空间或者时间上的下采样** - -> 在视觉神经系统中 一个神经元的感受野 是 指视网膜上的特定区域,只有在这个区域内的刺激才能激活改神经元。 - -全连接层,导致的参数量过大,浪费资源而且没有这么多的训练数据 -局部连接层,在位置相同的地方数据类似(人脸识别)参数量小一些 -**卷积层**,不同位置共享权重,通过可学习的卷积核进行卷积 - -![](https://cdn.jsdelivr.net/gh/zip95297/zip95297.github.io@main/source/images/%E6%9C%BA%E5%99%A8%E5%AD%A6%E4%B9%A0%E5%A4%8D%E4%B9%A0%E8%B5%84%E6%96%99/Pasted%20image%2020250428172619.webp?raw=true) -这个里面的汇聚层是 池化 - -![](https://cdn.jsdelivr.net/gh/zip95297/zip95297.github.io@main/source/images/%E6%9C%BA%E5%99%A8%E5%AD%A6%E4%B9%A0%E5%A4%8D%E4%B9%A0%E8%B5%84%E6%96%99/Pasted%20image%2020250428172637.webp?raw=true) - - -### 卷积操作 Convolution - -![](https://cdn.jsdelivr.net/gh/zip95297/zip95297.github.io@main/source/images/%E6%9C%BA%E5%99%A8%E5%AD%A6%E4%B9%A0%E5%A4%8D%E4%B9%A0%E8%B5%84%E6%96%99/Pasted%20image%2020250428170259.webp?raw=true) -![](https://cdn.jsdelivr.net/gh/zip95297/zip95297.github.io@main/source/images/%E6%9C%BA%E5%99%A8%E5%AD%A6%E4%B9%A0%E5%A4%8D%E4%B9%A0%E8%B5%84%E6%96%99/Pasted%20image%2020250428170324.webp?raw=true) -padding就是在外面对图像进行补0操作 stride就是kernel移动的步长是多少。 -![](https://cdn.jsdelivr.net/gh/zip95297/zip95297.github.io@main/source/images/%E6%9C%BA%E5%99%A8%E5%AD%A6%E4%B9%A0%E5%A4%8D%E4%B9%A0%E8%B5%84%E6%96%99/Pasted%20image%2020250428170446.webp?raw=true) - -一个卷积核卷出来一层 有多少个卷积核 卷完后通道就是多少 每个卷积核的通道数应该与输入特征图的通道数对齐 -#### 卷积后feature map的size - -对于给定的输入尺寸 $size:[H,W]$,填充 $padding \in \{true, false,Valid,Same\}$,步长 $stride = s$,卷积核大小为 $(k, k)$,输出尺寸 $size_O$可以通过以下公式计算: - -##### 1. 没有填充的情况 (padding = false): - -$size_O = \left[ \left\lfloor \cfrac{H - k}{s} + 1 \right\rfloor, \left\lfloor \cfrac{W - k}{s} + 1 \right\rfloor \right]$ - -##### 2. 有填充的情况 (padding = true): - -对于填充,通常我们使用"same"填充来保证输出尺寸与输入尺寸相同。在这种情况下,填充的大小为: - -$padding_H = \left\lceil \cfrac{k - 1}{2} \right\rceil, \quad padding_W = \left\lceil \cfrac{k - 1}{2} \right\rceil$ - -然后输出尺寸为: - -$size\_O = \left[ \left\lfloor \cfrac{H + 2 \times padding_H - k}{s} + 1 \right\rfloor, \left\lfloor \cfrac{W + 2 \times padding_W - k}{s} + 1 \right\rfloor \right]$ - -这就是计算输出尺寸 $size_O$ 的方法。 - - -### 池化 Pooling Layer - -``` -如果我们想让“眼睛检测器”这个滤波器(例如卷积神经网络中的卷积核)对于眼睛的**精确位置**不那么敏感,可以通过 **池化pooling** 操作来提高检测的鲁棒性。 - -1. **眼睛检测器(filter)**:我们假设滤波器的目的是检测图像中的“眼睛”特征。当滤波器在图像中滑动时,它会响应图像中眼睛特征的位置并给出一定的响应值。 - -2. **精确位置问题**:眼睛可能出现在图像的不同位置,或者可能由于某些因素(如图像旋转、缩放等)导致眼睛的相对位置发生变化。如果我们直接使用卷积操作,滤波器的响应可能只在特定位置对眼睛有效,但对于其他位置就不适用了,这样就无法达到“位置不敏感”的效果。 - -3. **池化(Pooling)操作**:池化操作(例如**最大池化(Max Pooling)**)可以帮助解决这个问题。它的工作原理是:在特定区域(如 \(2 \times 2\) 区域)内,池化操作选择最大的响应值(如最大池化),而不是关心具体的精确位置。通过这种方式,无论眼睛在图像的哪个位置,池化层都会保留局部区域内的最强响应,从而使模型对眼睛的**空间位置变化**更加**鲁棒**。 - -假设我们用一个 \(3 \times 3\) 的滤波器来检测眼睛,滤波器响应在图像某个位置给出了一个高值,但如果眼睛稍微移动,响应值可能会变得很低。如果我们在多个位置应用池化操作,池化层会选取该区域中的最大响应值。这样,即使眼睛的位置发生变化,池化后的响应值仍然较为稳定。 -``` - -![](https://cdn.jsdelivr.net/gh/zip95297/zip95297.github.io@main/source/images/%E6%9C%BA%E5%99%A8%E5%AD%A6%E4%B9%A0%E5%A4%8D%E4%B9%A0%E8%B5%84%E6%96%99/Pasted%20image%2020250428172157.webp?raw=true) -不在关注具体在哪个像素 而是知道在哪个大概区域 - -![](https://cdn.jsdelivr.net/gh/zip95297/zip95297.github.io@main/source/images/%E6%9C%BA%E5%99%A8%E5%AD%A6%E4%B9%A0%E5%A4%8D%E4%B9%A0%E8%B5%84%E6%96%99/Pasted%20image%2020250428172317.webp?raw=true) -![](https://cdn.jsdelivr.net/gh/zip95297/zip95297.github.io@main/source/images/%E6%9C%BA%E5%99%A8%E5%AD%A6%E4%B9%A0%E5%A4%8D%E4%B9%A0%E8%B5%84%E6%96%99/Pasted%20image%2020250428172443.webp?raw=true) - - -### 常用的激活函数 - -![](https://cdn.jsdelivr.net/gh/zip95297/zip95297.github.io@main/source/images/%E6%9C%BA%E5%99%A8%E5%AD%A6%E4%B9%A0%E5%A4%8D%E4%B9%A0%E8%B5%84%E6%96%99/Pasted%20image%2020250428173021.webp?raw=true) -![](https://cdn.jsdelivr.net/gh/zip95297/zip95297.github.io@main/source/images/%E6%9C%BA%E5%99%A8%E5%AD%A6%E4%B9%A0%E5%A4%8D%E4%B9%A0%E8%B5%84%E6%96%99/Pasted%20image%2020250428173026.webp?raw=true) -![](https://cdn.jsdelivr.net/gh/zip95297/zip95297.github.io@main/source/images/%E6%9C%BA%E5%99%A8%E5%AD%A6%E4%B9%A0%E5%A4%8D%E4%B9%A0%E8%B5%84%E6%96%99/Pasted%20image%2020250428173033.webp?raw=true) - - -## RNN基本结构以及存在的问题、LSTM的基本结构 - -### RNN 循环神经网络 - -由于卷积网络的局限性:1.接受固定长的向量作为输入,产生固定长度的输出;2.计算的数量是固定的 - -但是有时候需要处理时序和序列的数据,接受和生成变长的输入或输出 因此引出了循环神经网络RNN (Recurrent) - -> RNN例子 - ![](https://cdn.jsdelivr.net/gh/zip95297/zip95297.github.io@main/source/images/%E6%9C%BA%E5%99%A8%E5%AD%A6%E4%B9%A0%E5%A4%8D%E4%B9%A0%E8%B5%84%E6%96%99/Pasted%20image%2020250428181633.webp?raw=true) - ![](https://cdn.jsdelivr.net/gh/zip95297/zip95297.github.io@main/source/images/%E6%9C%BA%E5%99%A8%E5%AD%A6%E4%B9%A0%E5%A4%8D%E4%B9%A0%E8%B5%84%E6%96%99/Pasted%20image%2020250428181756.webp?raw=true)同步就是 输入后输出所有对应位置 - ![](https://cdn.jsdelivr.net/gh/zip95297/zip95297.github.io@main/source/images/%E6%9C%BA%E5%99%A8%E5%AD%A6%E4%B9%A0%E5%A4%8D%E4%B9%A0%E8%B5%84%E6%96%99/Pasted%20image%2020250428181814.webp?raw=true)异步是输入后逐个输出上一个 - -#### 循环神经网络的特点 -![](https://cdn.jsdelivr.net/gh/zip95297/zip95297.github.io@main/source/images/%E6%9C%BA%E5%99%A8%E5%AD%A6%E4%B9%A0%E5%A4%8D%E4%B9%A0%E8%B5%84%E6%96%99/Pasted%20image%2020250428185603.webp?raw=true) - -#### RNN的结构 -![](https://cdn.jsdelivr.net/gh/zip95297/zip95297.github.io@main/source/images/%E6%9C%BA%E5%99%A8%E5%AD%A6%E4%B9%A0%E5%A4%8D%E4%B9%A0%E8%B5%84%E6%96%99/Pasted%20image%2020250428191046.webp?raw=true) - -#### RNN的训练算法 -RNN训练时候用BPTT算法![](https://cdn.jsdelivr.net/gh/zip95297/zip95297.github.io@main/source/images/%E6%9C%BA%E5%99%A8%E5%AD%A6%E4%B9%A0%E5%A4%8D%E4%B9%A0%E8%B5%84%E6%96%99/Pasted%20image%2020250428191151.webp?raw=true) -![](https://cdn.jsdelivr.net/gh/zip95297/zip95297.github.io@main/source/images/%E6%9C%BA%E5%99%A8%E5%AD%A6%E4%B9%A0%E5%A4%8D%E4%B9%A0%E8%B5%84%E6%96%99/Pasted%20image%2020250428191210.webp?raw=true) -![](https://cdn.jsdelivr.net/gh/zip95297/zip95297.github.io@main/source/images/%E6%9C%BA%E5%99%A8%E5%AD%A6%E4%B9%A0%E5%A4%8D%E4%B9%A0%E8%B5%84%E6%96%99/Pasted%20image%2020250428191215.webp?raw=true) - -#### RNN的长期依赖问题 - -因为上面所示 梯度反向传播中的累乘 很容易出现 梯度消失或者梯度爆炸 ,这是因为RNN在时间维度上非常深。想要改进这个问题,对于梯度爆照:需要用 权重衰减 或者 权重截断 方法 ;对于梯度消失:需要改进模型来解决 - -直观感受长期依赖![](https://cdn.jsdelivr.net/gh/zip95297/zip95297.github.io@main/source/images/%E6%9C%BA%E5%99%A8%E5%AD%A6%E4%B9%A0%E5%A4%8D%E4%B9%A0%E8%B5%84%E6%96%99/Pasted%20image%2020250428191525.webp?raw=true) - -**越早的输入影响就越小,越晚的输入影响就越大,而且这个逻辑是固定的,无法改变。** -### LSTM 长短期记忆网络 - -为了解决上面 RNN 对早晚输入注意程度不一致,而且逻辑无法改变的问题 ,引入了LSTM。 - -LSTM 保留了较长序列中的 重要信息,忽略不重要的信息。这样就解决了 RNN 的短期记忆的问题。 -![](https://cdn.jsdelivr.net/gh/zip95297/zip95297.github.io@main/source/images/%E6%9C%BA%E5%99%A8%E5%AD%A6%E4%B9%A0%E5%A4%8D%E4%B9%A0%E8%B5%84%E6%96%99/Pasted%20image%2020250428191811.webp?raw=true) - -#### LSTM 的基本结构 - -> LSTM的思想: - ![](https://cdn.jsdelivr.net/gh/zip95297/zip95297.github.io@main/source/images/%E6%9C%BA%E5%99%A8%E5%AD%A6%E4%B9%A0%E5%A4%8D%E4%B9%A0%E8%B5%84%E6%96%99/Pasted%20image%2020250428191843.webp?raw=true) - -> LSTM vs RNN : - ![](https://cdn.jsdelivr.net/gh/zip95297/zip95297.github.io@main/source/images/%E6%9C%BA%E5%99%A8%E5%AD%A6%E4%B9%A0%E5%A4%8D%E4%B9%A0%E8%B5%84%E6%96%99/Pasted%20image%2020250428191911.webp?raw=true) - ->下图中: - 指出了 LSTM(长短期记忆网络)中的以下关键部分: - 1. **Write some new cell content**(写入新的细胞状态内容) - - 对应 **输入门(Input Gate)** 的计算,决定哪些新信息会被存储到细胞状态中。 - 2. **Forget some cell content**(遗忘部分细胞状态内容) - - 对应 **遗忘门(Forget Gate)** 的计算,决定哪些旧信息会被丢弃。 - 3. **Compute the forget gate**(计算遗忘门) - - 明确提到遗忘门的操作,通过 sigmoid 函数决定保留或遗忘多少历史信息。 - 4. **Compute the input gate**(计算输入门) - - 明确提到输入门的操作,通过 sigmoid 函数决定更新哪些新信息。 - 5. **Compute the new cell content**(计算新的细胞状态内容) - - 结合输入门和候选细胞状态(通常由 tanh 函数生成),更新细胞状态。 - 6. **Compute the output gate**(计算输出门) - - 对应 **输出门(Output Gate)** 的计算,通过 sigmoid 函数决定输出哪些信息,再结合 tanh 处理的细胞状态生成最终输出。 - 7. **候选细胞状态(Candidate Cell State)**:通常由当前输入和前一隐藏状态通过 tanh 函数生成,用于后续细胞状态更新。 - 8. **隐藏状态(Hidden State)**:作为 LSTM 的输出,传递到下一时间步或网络层。 - - -![](https://cdn.jsdelivr.net/gh/zip95297/zip95297.github.io@main/source/images/%E6%9C%BA%E5%99%A8%E5%AD%A6%E4%B9%A0%E5%A4%8D%E4%B9%A0%E8%B5%84%E6%96%99/Pasted%20image%2020250428191921.webp?raw=true) -![](https://cdn.jsdelivr.net/gh/zip95297/zip95297.github.io@main/source/images/%E6%9C%BA%E5%99%A8%E5%AD%A6%E4%B9%A0%E5%A4%8D%E4%B9%A0%E8%B5%84%E6%96%99/Pasted%20image%2020250428191926.webp?raw=true) -![](https://cdn.jsdelivr.net/gh/zip95297/zip95297.github.io@main/source/images/%E6%9C%BA%E5%99%A8%E5%AD%A6%E4%B9%A0%E5%A4%8D%E4%B9%A0%E8%B5%84%E6%96%99/Pasted%20image%2020250428191933.webp?raw=true) -![](https://cdn.jsdelivr.net/gh/zip95297/zip95297.github.io@main/source/images/%E6%9C%BA%E5%99%A8%E5%AD%A6%E4%B9%A0%E5%A4%8D%E4%B9%A0%E8%B5%84%E6%96%99/Pasted%20image%2020250428191938.webp?raw=true) -![](https://cdn.jsdelivr.net/gh/zip95297/zip95297.github.io@main/source/images/%E6%9C%BA%E5%99%A8%E5%AD%A6%E4%B9%A0%E5%A4%8D%E4%B9%A0%E8%B5%84%E6%96%99/Pasted%20image%2020250428191944.webp?raw=true) -![](https://cdn.jsdelivr.net/gh/zip95297/zip95297.github.io@main/source/images/%E6%9C%BA%E5%99%A8%E5%AD%A6%E4%B9%A0%E5%A4%8D%E4%B9%A0%E8%B5%84%E6%96%99/Pasted%20image%2020250428192509.webp?raw=true) - - -## Transformer基本结构以及特点 - -### Attention - ->非自主提示 无参数汇聚(最大 平均池化) - ![](https://cdn.jsdelivr.net/gh/zip95297/zip95297.github.io@main/source/images/%E6%9C%BA%E5%99%A8%E5%AD%A6%E4%B9%A0%E5%A4%8D%E4%B9%A0%E8%B5%84%E6%96%99/Pasted%20image%2020250428204927.webp?raw=true) - -自主提示 注意力机制 - ![](https://cdn.jsdelivr.net/gh/zip95297/zip95297.github.io@main/source/images/%E6%9C%BA%E5%99%A8%E5%AD%A6%E4%B9%A0%E5%A4%8D%E4%B9%A0%E8%B5%84%E6%96%99/Pasted%20image%2020250428204949.webp?raw=true) - ![](https://cdn.jsdelivr.net/gh/zip95297/zip95297.github.io@main/source/images/%E6%9C%BA%E5%99%A8%E5%AD%A6%E4%B9%A0%E5%A4%8D%E4%B9%A0%E8%B5%84%E6%96%99/Pasted%20image%2020250428205146.webp?raw=true) - -> 软性注意力机制 value 就是自己 - ![](https://cdn.jsdelivr.net/gh/zip95297/zip95297.github.io@main/source/images/%E6%9C%BA%E5%99%A8%E5%AD%A6%E4%B9%A0%E5%A4%8D%E4%B9%A0%E8%B5%84%E6%96%99/Pasted%20image%2020250428205713.webp?raw=true) - ![](https://cdn.jsdelivr.net/gh/zip95297/zip95297.github.io@main/source/images/%E6%9C%BA%E5%99%A8%E5%AD%A6%E4%B9%A0%E5%A4%8D%E4%B9%A0%E8%B5%84%E6%96%99/Pasted%20image%2020250428205725.webp?raw=true) - -> 硬性注意力 QKV - ![](https://cdn.jsdelivr.net/gh/zip95297/zip95297.github.io@main/source/images/%E6%9C%BA%E5%99%A8%E5%AD%A6%E4%B9%A0%E5%A4%8D%E4%B9%A0%E8%B5%84%E6%96%99/Pasted%20image%2020250428205815.webp?raw=true) - -> 多头注意力 多组查询Q KV是同一个 - ![](https://cdn.jsdelivr.net/gh/zip95297/zip95297.github.io@main/source/images/%E6%9C%BA%E5%99%A8%E5%AD%A6%E4%B9%A0%E5%A4%8D%E4%B9%A0%E8%B5%84%E6%96%99/Pasted%20image%2020250428205905.webp?raw=true) - -> 自注意力 - h2 只得到了 x1 x3 x2 的信息 卷积和循环只有local![](https://cdn.jsdelivr.net/gh/zip95297/zip95297.github.io@main/source/images/%E6%9C%BA%E5%99%A8%E5%AD%A6%E4%B9%A0%E5%A4%8D%E4%B9%A0%E8%B5%84%E6%96%99/Pasted%20image%2020250428210013.webp?raw=true) - 全连接没法处理变长![](https://cdn.jsdelivr.net/gh/zip95297/zip95297.github.io@main/source/images/%E6%9C%BA%E5%99%A8%E5%AD%A6%E4%B9%A0%E5%A4%8D%E4%B9%A0%E8%B5%84%E6%96%99/Pasted%20image%2020250428210304.webp?raw=true) - 自注意力示意图:![](https://cdn.jsdelivr.net/gh/zip95297/zip95297.github.io@main/source/images/%E6%9C%BA%E5%99%A8%E5%AD%A6%E4%B9%A0%E5%A4%8D%E4%B9%A0%E8%B5%84%E6%96%99/Pasted%20image%2020250428210332.webp?raw=true) - ![](https://cdn.jsdelivr.net/gh/zip95297/zip95297.github.io@main/source/images/%E6%9C%BA%E5%99%A8%E5%AD%A6%E4%B9%A0%E5%A4%8D%E4%B9%A0%E8%B5%84%E6%96%99/Pasted%20image%2020250428210351.webp?raw=true) - ![](https://cdn.jsdelivr.net/gh/zip95297/zip95297.github.io@main/source/images/%E6%9C%BA%E5%99%A8%E5%AD%A6%E4%B9%A0%E5%A4%8D%E4%B9%A0%E8%B5%84%E6%96%99/Pasted%20image%2020250428210405.webp?raw=true) - ![](https://cdn.jsdelivr.net/gh/zip95297/zip95297.github.io@main/source/images/%E6%9C%BA%E5%99%A8%E5%AD%A6%E4%B9%A0%E5%A4%8D%E4%B9%A0%E8%B5%84%E6%96%99/Pasted%20image%2020250428210424.webp?raw=true) - -### Transformer基本结构 - -自回归模型 为了建模一个变长序列出现的概率![](https://cdn.jsdelivr.net/gh/zip95297/zip95297.github.io@main/source/images/%E6%9C%BA%E5%99%A8%E5%AD%A6%E4%B9%A0%E5%A4%8D%E4%B9%A0%E8%B5%84%E6%96%99/Pasted%20image%2020250428210627.webp?raw=true) - -![](https://cdn.jsdelivr.net/gh/zip95297/zip95297.github.io@main/source/images/%E6%9C%BA%E5%99%A8%E5%AD%A6%E4%B9%A0%E5%A4%8D%E4%B9%A0%E8%B5%84%E6%96%99/Pasted%20image%2020250428210732.webp?raw=true) -![](https://cdn.jsdelivr.net/gh/zip95297/zip95297.github.io@main/source/images/%E6%9C%BA%E5%99%A8%E5%AD%A6%E4%B9%A0%E5%A4%8D%E4%B9%A0%E8%B5%84%E6%96%99/Pasted%20image%2020250428210740.webp?raw=true) -![](https://cdn.jsdelivr.net/gh/zip95297/zip95297.github.io@main/source/images/%E6%9C%BA%E5%99%A8%E5%AD%A6%E4%B9%A0%E5%A4%8D%E4%B9%A0%E8%B5%84%E6%96%99/Pasted%20image%2020250428210852.webp?raw=true) -![](https://cdn.jsdelivr.net/gh/zip95297/zip95297.github.io@main/source/images/%E6%9C%BA%E5%99%A8%E5%AD%A6%E4%B9%A0%E5%A4%8D%E4%B9%A0%E8%B5%84%E6%96%99/Pasted%20image%2020250428210923.webp?raw=true) -![](https://cdn.jsdelivr.net/gh/zip95297/zip95297.github.io@main/source/images/%E6%9C%BA%E5%99%A8%E5%AD%A6%E4%B9%A0%E5%A4%8D%E4%B9%A0%E8%B5%84%E6%96%99/Pasted%20image%2020250428210938.webp?raw=true) -![](https://cdn.jsdelivr.net/gh/zip95297/zip95297.github.io@main/source/images/%E6%9C%BA%E5%99%A8%E5%AD%A6%E4%B9%A0%E5%A4%8D%E4%B9%A0%E8%B5%84%E6%96%99/Pasted%20image%2020250428210944.webp?raw=true) -![](https://cdn.jsdelivr.net/gh/zip95297/zip95297.github.io@main/source/images/%E6%9C%BA%E5%99%A8%E5%AD%A6%E4%B9%A0%E5%A4%8D%E4%B9%A0%E8%B5%84%E6%96%99/Pasted%20image%2020250428211153.webp?raw=true) -![](https://cdn.jsdelivr.net/gh/zip95297/zip95297.github.io@main/source/images/%E6%9C%BA%E5%99%A8%E5%AD%A6%E4%B9%A0%E5%A4%8D%E4%B9%A0%E8%B5%84%E6%96%99/Pasted%20image%2020250428211434.webp?raw=true) - -### Transformer的特点 - -![](https://cdn.jsdelivr.net/gh/zip95297/zip95297.github.io@main/source/images/%E6%9C%BA%E5%99%A8%E5%AD%A6%E4%B9%A0%E5%A4%8D%E4%B9%A0%E8%B5%84%E6%96%99/Pasted%20image%2020250428211509.webp?raw=true) -![](https://cdn.jsdelivr.net/gh/zip95297/zip95297.github.io@main/source/images/%E6%9C%BA%E5%99%A8%E5%AD%A6%E4%B9%A0%E5%A4%8D%E4%B9%A0%E8%B5%84%E6%96%99/Pasted%20image%2020250428211515.webp?raw=true) - -## 预训练语言模型BERT和GPT - -![](https://cdn.jsdelivr.net/gh/zip95297/zip95297.github.io@main/source/images/%E6%9C%BA%E5%99%A8%E5%AD%A6%E4%B9%A0%E5%A4%8D%E4%B9%A0%E8%B5%84%E6%96%99/Pasted%20image%2020250428214346.webp?raw=true) -![](https://cdn.jsdelivr.net/gh/zip95297/zip95297.github.io@main/source/images/%E6%9C%BA%E5%99%A8%E5%AD%A6%E4%B9%A0%E5%A4%8D%E4%B9%A0%E8%B5%84%E6%96%99/Pasted%20image%2020250428214406.webp?raw=true) -![](https://cdn.jsdelivr.net/gh/zip95297/zip95297.github.io@main/source/images/%E6%9C%BA%E5%99%A8%E5%AD%A6%E4%B9%A0%E5%A4%8D%E4%B9%A0%E8%B5%84%E6%96%99/Pasted%20image%2020250428214410.webp?raw=true) - -### BERT模型 -mask在中间的 不像 decoder 在一个地方 后面做mask -![](https://cdn.jsdelivr.net/gh/zip95297/zip95297.github.io@main/source/images/%E6%9C%BA%E5%99%A8%E5%AD%A6%E4%B9%A0%E5%A4%8D%E4%B9%A0%E8%B5%84%E6%96%99/Pasted%20image%2020250428213016.webp?raw=true) -![](https://cdn.jsdelivr.net/gh/zip95297/zip95297.github.io@main/source/images/%E6%9C%BA%E5%99%A8%E5%AD%A6%E4%B9%A0%E5%A4%8D%E4%B9%A0%E8%B5%84%E6%96%99/Pasted%20image%2020250428214420.webp?raw=true) -![](https://cdn.jsdelivr.net/gh/zip95297/zip95297.github.io@main/source/images/%E6%9C%BA%E5%99%A8%E5%AD%A6%E4%B9%A0%E5%A4%8D%E4%B9%A0%E8%B5%84%E6%96%99/Pasted%20image%2020250428214438.webp?raw=true) -![](https://cdn.jsdelivr.net/gh/zip95297/zip95297.github.io@main/source/images/%E6%9C%BA%E5%99%A8%E5%AD%A6%E4%B9%A0%E5%A4%8D%E4%B9%A0%E8%B5%84%E6%96%99/Pasted%20image%2020250428214442.webp?raw=true) -![](https://cdn.jsdelivr.net/gh/zip95297/zip95297.github.io@main/source/images/%E6%9C%BA%E5%99%A8%E5%AD%A6%E4%B9%A0%E5%A4%8D%E4%B9%A0%E8%B5%84%E6%96%99/Pasted%20image%2020250428214509.webp?raw=true) -![](https://cdn.jsdelivr.net/gh/zip95297/zip95297.github.io@main/source/images/%E6%9C%BA%E5%99%A8%E5%AD%A6%E4%B9%A0%E5%A4%8D%E4%B9%A0%E8%B5%84%E6%96%99/Pasted%20image%2020250428214538.webp?raw=true) -![](https://cdn.jsdelivr.net/gh/zip95297/zip95297.github.io@main/source/images/%E6%9C%BA%E5%99%A8%E5%AD%A6%E4%B9%A0%E5%A4%8D%E4%B9%A0%E8%B5%84%E6%96%99/Pasted%20image%2020250428214544.webp?raw=true) -分类![](https://cdn.jsdelivr.net/gh/zip95297/zip95297.github.io@main/source/images/%E6%9C%BA%E5%99%A8%E5%AD%A6%E4%B9%A0%E5%A4%8D%E4%B9%A0%E8%B5%84%E6%96%99/Pasted%20image%2020250428214621.webp?raw=true) -词性标注![](https://cdn.jsdelivr.net/gh/zip95297/zip95297.github.io@main/source/images/%E6%9C%BA%E5%99%A8%E5%AD%A6%E4%B9%A0%E5%A4%8D%E4%B9%A0%E8%B5%84%E6%96%99/Pasted%20image%2020250428214655.webp?raw=true) -对对话分类![](https://cdn.jsdelivr.net/gh/zip95297/zip95297.github.io@main/source/images/%E6%9C%BA%E5%99%A8%E5%AD%A6%E4%B9%A0%E5%A4%8D%E4%B9%A0%E8%B5%84%E6%96%99/Pasted%20image%2020250428214727.webp?raw=true) -![](https://cdn.jsdelivr.net/gh/zip95297/zip95297.github.io@main/source/images/%E6%9C%BA%E5%99%A8%E5%AD%A6%E4%B9%A0%E5%A4%8D%E4%B9%A0%E8%B5%84%E6%96%99/Pasted%20image%2020250428214750.webp?raw=true) - -### GPT -![](https://cdn.jsdelivr.net/gh/zip95297/zip95297.github.io@main/source/images/%E6%9C%BA%E5%99%A8%E5%AD%A6%E4%B9%A0%E5%A4%8D%E4%B9%A0%E8%B5%84%E6%96%99/Pasted%20image%2020250428213255.webp?raw=true) -![](https://cdn.jsdelivr.net/gh/zip95297/zip95297.github.io@main/source/images/%E6%9C%BA%E5%99%A8%E5%AD%A6%E4%B9%A0%E5%A4%8D%E4%B9%A0%E8%B5%84%E6%96%99/Pasted%20image%2020250428214933.webp?raw=true) -![](https://cdn.jsdelivr.net/gh/zip95297/zip95297.github.io@main/source/images/%E6%9C%BA%E5%99%A8%E5%AD%A6%E4%B9%A0%E5%A4%8D%E4%B9%A0%E8%B5%84%E6%96%99/Pasted%20image%2020250428214940.webp?raw=true) -![](https://cdn.jsdelivr.net/gh/zip95297/zip95297.github.io@main/source/images/%E6%9C%BA%E5%99%A8%E5%AD%A6%E4%B9%A0%E5%A4%8D%E4%B9%A0%E8%B5%84%E6%96%99/Pasted%20image%2020250428215019.webp?raw=true) -![](https://cdn.jsdelivr.net/gh/zip95297/zip95297.github.io@main/source/images/%E6%9C%BA%E5%99%A8%E5%AD%A6%E4%B9%A0%E5%A4%8D%E4%B9%A0%E8%B5%84%E6%96%99/Pasted%20image%2020250428215034.webp?raw=true) - -### 区别 - -![](https://cdn.jsdelivr.net/gh/zip95297/zip95297.github.io@main/source/images/%E6%9C%BA%E5%99%A8%E5%AD%A6%E4%B9%A0%E5%A4%8D%E4%B9%A0%E8%B5%84%E6%96%99/Pasted%20image%2020250428215116.webp?raw=true) -![](https://cdn.jsdelivr.net/gh/zip95297/zip95297.github.io@main/source/images/%E6%9C%BA%E5%99%A8%E5%AD%A6%E4%B9%A0%E5%A4%8D%E4%B9%A0%E8%B5%84%E6%96%99/Pasted%20image%2020250428215147.webp?raw=true) - - - - - -# 第十章 强化学习 - -## 强化学习基本原理与构成 - -强化学习(Reinforcement Learning, 简称 RL)是机器学习的范式和方法论之一,用于描述和解决智能体(Agent)在与环境(Environment)的交互过程中通过学习策略(Policy)以达成奖励或回报(Reward)最大化或实现特定目标的问题。 - - - -> 强化学习和其他机器学习范式不同的的原因: - 没有 做loss的步骤 非及时反馈![](https://cdn.jsdelivr.net/gh/zip95297/zip95297.github.io@main/source/images/%E6%9C%BA%E5%99%A8%E5%AD%A6%E4%B9%A0%E5%A4%8D%E4%B9%A0%E8%B5%84%E6%96%99/Pasted%20image%2020250428215449.webp?raw=true) - -奖励: -![](https://cdn.jsdelivr.net/gh/zip95297/zip95297.github.io@main/source/images/%E6%9C%BA%E5%99%A8%E5%AD%A6%E4%B9%A0%E5%A4%8D%E4%B9%A0%E8%B5%84%E6%96%99/Pasted%20image%2020250428215605.webp?raw=true) -例如: -乘坐直升机进⾏特技⻜⾏操作 - • 因遵循期望轨迹获得正奖励 - • 因坠毁获得负奖励 - -在西洋双陆棋比赛中击败世界冠军 - • +/− 因赢得/输掉⼀局比赛获得的正负奖励 - -让类人机器人⾏⾛ - • 向前移动给予正奖励 - • 摔倒给予负奖励 - -决策序列![](https://cdn.jsdelivr.net/gh/zip95297/zip95297.github.io@main/source/images/%E6%9C%BA%E5%99%A8%E5%AD%A6%E4%B9%A0%E5%A4%8D%E4%B9%A0%E8%B5%84%E6%96%99/Pasted%20image%2020250428215708.webp?raw=true) - -智能体与环境的交互![](https://cdn.jsdelivr.net/gh/zip95297/zip95297.github.io@main/source/images/%E6%9C%BA%E5%99%A8%E5%AD%A6%E4%B9%A0%E5%A4%8D%E4%B9%A0%E8%B5%84%E6%96%99/Pasted%20image%2020250428215754.webp?raw=true) - -历史与状态![](https://cdn.jsdelivr.net/gh/zip95297/zip95297.github.io@main/source/images/%E6%9C%BA%E5%99%A8%E5%AD%A6%E4%B9%A0%E5%A4%8D%E4%B9%A0%E8%B5%84%E6%96%99/Pasted%20image%2020250428215804.webp?raw=true) - -信息状态![](https://cdn.jsdelivr.net/gh/zip95297/zip95297.github.io@main/source/images/%E6%9C%BA%E5%99%A8%E5%AD%A6%E4%B9%A0%E5%A4%8D%E4%B9%A0%E8%B5%84%E6%96%99/Pasted%20image%2020250428215821.webp?raw=true) - -![](https://cdn.jsdelivr.net/gh/zip95297/zip95297.github.io@main/source/images/%E6%9C%BA%E5%99%A8%E5%AD%A6%E4%B9%A0%E5%A4%8D%E4%B9%A0%E8%B5%84%E6%96%99/Pasted%20image%2020250428220733.webp?raw=true) - - -## 智能体的构成 - -主要组件![](https://cdn.jsdelivr.net/gh/zip95297/zip95297.github.io@main/source/images/%E6%9C%BA%E5%99%A8%E5%AD%A6%E4%B9%A0%E5%A4%8D%E4%B9%A0%E8%B5%84%E6%96%99/Pasted%20image%2020250428215925.webp?raw=true) - -1. 策略![](https://cdn.jsdelivr.net/gh/zip95297/zip95297.github.io@main/source/images/%E6%9C%BA%E5%99%A8%E5%AD%A6%E4%B9%A0%E5%A4%8D%E4%B9%A0%E8%B5%84%E6%96%99/Pasted%20image%2020250428215948.webp?raw=true) -2. 价值函数![](https://cdn.jsdelivr.net/gh/zip95297/zip95297.github.io@main/source/images/%E6%9C%BA%E5%99%A8%E5%AD%A6%E4%B9%A0%E5%A4%8D%E4%B9%A0%E8%B5%84%E6%96%99/Pasted%20image%2020250428215956.webp?raw=true) -3. 模型![](https://cdn.jsdelivr.net/gh/zip95297/zip95297.github.io@main/source/images/%E6%9C%BA%E5%99%A8%E5%AD%A6%E4%B9%A0%E5%A4%8D%E4%B9%A0%E8%B5%84%E6%96%99/Pasted%20image%2020250428220011.webp?raw=true) - -### 智能体分类 - -![](https://cdn.jsdelivr.net/gh/zip95297/zip95297.github.io@main/source/images/%E6%9C%BA%E5%99%A8%E5%AD%A6%E4%B9%A0%E5%A4%8D%E4%B9%A0%E8%B5%84%E6%96%99/Pasted%20image%2020250428220326.webp?raw=true) -![](https://cdn.jsdelivr.net/gh/zip95297/zip95297.github.io@main/source/images/%E6%9C%BA%E5%99%A8%E5%AD%A6%E4%B9%A0%E5%A4%8D%E4%B9%A0%E8%B5%84%E6%96%99/Pasted%20image%2020250428220331.webp?raw=true) -![](https://cdn.jsdelivr.net/gh/zip95297/zip95297.github.io@main/source/images/%E6%9C%BA%E5%99%A8%E5%AD%A6%E4%B9%A0%E5%A4%8D%E4%B9%A0%E8%B5%84%E6%96%99/Pasted%20image%2020250428220348.webp?raw=true) - - -## 有模型与无模型的区别 - -| **维度** | **有模型(Model-Based)** | **无模型(Model-Free)** | -| -------- | --------------------------------------------------- | ------------------------------- | -| **定义** | 智能体已知环境动态(状态转移概率 \( P_{ss'}^a \) 和奖励函数 \( R_s^a \)) | 智能体直接通过与环境的交互学习,无需环境模型 | -| **核心方法** | 动态规划(DP)、值迭代、策略迭代 | 蒙特卡罗(MC)、时序差分(TD)、Q学习、Sarsa | -| **优缺点** | ✅ 样本效率高(无需实际交互)
❌ 依赖精确模型,难以处理复杂环境 | ✅ 适应性强,适用于未知环境
❌ 样本效率低,收敛慢 | -| **关键步骤** | 1. 构建环境模型
2. 基于模型计算最优策略 | 1. 直接交互获取经验
2. 通过经验更新价值/策略 | -| **典型算法** | 值迭代(Value Iteration)、策略迭代(Policy Iteration) | Q学习(Q-Learning)、Sarsa、REINFORCE | -| **应用场景** | 棋类游戏(已知规则)、仿真环境 | 机器人控制、游戏AI(如Atari) | - -**定义与核心区别** -- **有模型(Model-Based)**: - - 智能体**已知环境模型**,即状态转移概率 \(P(s'|s,a)\) 和奖励函数 \(R(s,a)\)。 - - 通过模型进行**离线规划**(如动态规划),无需与环境实时交互即可优化策略。 - - **典型方法**:动态规划(DP)、值迭代(Value Iteration)、策略迭代(Policy Iteration)。 - - **优点**:样本效率高(无需大量试错),适合小规模或已知模型的问题。 - - **缺点**:模型难以精确获取,复杂环境(如连续状态或高维空间)中不适用。 - -- **无模型(Model-Free)**: - - 智能体**不依赖环境模型**,通过与环境交互直接学习策略或价值函数。 - - 通过**试错**(Trial-and-Error)收集经验,逐步优化策略。 - - **典型方法**:Q-learning、Sarsa、蒙特卡洛(MC)、深度Q网络(DQN)。 - - **优点**:适用于未知或复杂环境,更贴近实际应用场景。 - - **缺点**:样本效率低,训练时间长,可能陷入局部最优。 - -**示例对比** -- **迷宫问题**: - - 有模型:已知迷宫地图,直接计算最短路径。 - - 无模型:通过不断尝试移动并记录奖励,逐步学习最优路径。 -## 基于价值与基于策略的区别 - -| **维度** | **基于价值(Value-Based)** | **基于策略(Policy-Based)** | -|-------------------|----------------------------------------------------|---------------------------------------------------| -| **核心目标** | 学习价值函数 \( V(s) \) 或 \( Q(s,a) \),通过价值选择动作(如ε-贪心) | 直接优化策略函数 \( \pi(a\|s) \),无需价值函数 | -| **策略类型** | 隐式策略(如贪心策略) | 显式策略(可随机或确定性) | -| **优缺点** | ✅ 适合离散动作空间
❌ 难以处理高维/连续动作 | ✅ 支持连续动作、随机策略
❌ 高方差、局部最优 | -| **收敛性** | 可能震荡(如Q学习) | 更平滑但可能陷入局部最优 | -| **典型算法** | Q学习、DQN、Sarsa | REINFORCE、PPO、策略梯度(Policy Gradient) | -| **应用场景** | 离散控制(如Atari游戏) | 连续控制(如机器人行走)、博弈论(如石头剪刀布) | - - -**定义与核心区别** -- **基于价值(Value-Based)**: - - 核心是学习**价值函数**(如状态价值 \(V(s)\) 或动作价值 \(Q(s,a)\)),通过选择价值最大的动作间接优化策略。 - - 策略通常是隐式的(如 \(\epsilon\)-贪心策略)。 - - **典型方法**:Q-learning、Sarsa、DQN。 - - **优点**:稳定性高,适合离散动作空间。 - - **缺点**:难以处理高维或连续动作空间,无法学习随机策略。 - -- **基于策略(Policy-Based)**: - - 直接学习**策略函数** \(\pi(a|s)\),参数化策略并优化其性能。 - - 策略可以是确定性的或随机性的。 - - **典型方法**:策略梯度(REINFORCE)、PPO(Proximal Policy Optimization)。 - - **优点**:适合连续动作空间,能学习随机策略(如博弈中的混合策略)。 - - **缺点**:方差高,收敛速度慢,易陷入局部最优。 - -**示例对比** -- **机器人行走**: - - 基于价值:计算每个动作的价值,选择最大价值的动作(如关节转动角度)。 - - 基于策略:直接输出动作分布(如关节转动的概率),通过梯度上升优化。 - -**混合方法:演员-评论家(Actor-Critic)** -- 结合价值函数(Critic)和策略函数(Actor),Critic评估动作价值,Actor优化策略。 -- **典型方法**:A3C、DDPG。 - - -## 常用强化学习方法的名称 - -| **方法名称** | **类型** | **有模型/无模型** | **核心思想** | -|--------------------|-------------------|-------------------|------------------------------------------| -| **Q-learning** | 基于价值 | 无模型 | 通过最大化Q值更新策略,离线策略(Off-Policy)。 | -| **Sarsa** | 基于价值 | 无模型 | 在线策略(On-Policy),使用当前策略选择动作。 | -| **DQN** | 基于价值 | 无模型 | 深度网络近似Q值,引入经验回放和固定目标网络。 | -| **策略梯度(REINFORCE)** | 基于策略 | 无模型 | 直接优化策略,通过蒙特卡洛回报计算梯度。 | -| **PPO** | 基于策略 | 无模型 | 限制策略更新幅度,提升训练稳定性。 | -| **Actor-Critic** | 混合方法(价值+策略) | 无模型 | Actor输出策略,Critic评估价值,降低方差。 | -| **动态规划(DP)** | 基于价值 | 有模型 | 利用环境模型迭代计算最优价值函数。 | -| **蒙特卡洛(MC)** | 基于价值 | 无模型 | 通过完整回合的回报更新价值函数。 | - -**适用场景总结** -- **有模型方法**:环境模型已知且规模较小(如棋盘游戏、简单控制)。 -- **无模型方法**:环境复杂或未知(如机器人控制、游戏AI)。 -- **基于价值方法**:动作空间离散且维度低(如Atari游戏)。 -- **基于策略方法**:动作空间连续或需要随机策略(如机械臂操控、多智能体博弈)。 - -### 详细一点的 - -#### 1. **动态规划(Dynamic Programming, DP)** - - **特点**:要求已知MDP模型,通过贝尔曼方程迭代求解。 - - **算法**: - - *值迭代*(Page 63):直接优化值函数 \( V(s) \)。 - - *策略迭代*(Page 60):交替进行策略评估和改进。 - -#### 2. **无模型方法(Model-Free)** - - **蒙特卡罗(MC)**(Page 71): - - 通过完整回合的经验更新(如 \( V(s) \leftarrow V(s) + \alpha(G_t - V(s)) \))。 - - 高方差但无偏,适用于回合制任务。 - - **时序差分(TD)**(Page 76): - - *TD(0)*:单步更新(\( V(s) \leftarrow V(s) + \alpha(r + \gamma V(s') - V(s)) \))。 - - *n-step TD*(Page 87):多步混合MC和TD。 - - **Q学习(Q-Learning)**(Page 102): - - 离线策略,更新规则:\( Q(s,a) \leftarrow Q(s,a) + \alpha(r + \gamma \max_{a'} Q(s',a') - Q(s,a)) \)。 - - **Sarsa**(Page 99): - - 在线策略,更新规则:\( Q(s,a) \leftarrow Q(s,a) + \alpha(r + \gamma Q(s',a') - Q(s,a)) \)。 - -#### 3. **价值函数近似(Value Approximation)** - - **DQN**(Page 126): - - 使用神经网络近似Q函数,引入经验回放和固定目标网络。 - - **改进方法**:Double DQN、Dueling DQN(Page 133)。 - -#### 4. **策略优化(Policy Optimization)** - - **策略梯度**(Page 143): - - REINFORCE(Page 145):蒙特卡罗策略梯度,\( \Delta \theta = \alpha \nabla_\theta \log \pi_\theta(s,a) G_t \)。 - - **Actor-Critic**(Page 146): - - 结合值函数(Critic)和策略(Actor),如A2C、A3C。 - -#### 5. **混合方法** diff --git "a/source/_posts/\350\256\241\347\256\227\346\234\272\351\253\230\347\272\247\347\263\273\347\273\237\347\273\223\346\236\204.md" "b/source/_posts/\350\256\241\347\256\227\346\234\272\351\253\230\347\272\247\347\263\273\347\273\237\347\273\223\346\236\204.md" deleted file mode 100644 index 4417c4f..0000000 --- "a/source/_posts/\350\256\241\347\256\227\346\234\272\351\253\230\347\272\247\347\263\273\347\273\237\347\273\223\346\236\204.md" +++ /dev/null @@ -1,526 +0,0 @@ ---- -title: "计算机高级系统结构" -date: 2024-12-26 23:55:27 -updated: 2025-04-30 01:17:38 -mathjax: true -categories: 期末复习 -tags: - - 期末复习 - - 系统结构 - - 底层原理 -comments: false ---- - -# 第一章 量化设计与分析基础 - -## 计算机分类类别 - -![](https://cdn.jsdelivr.net/gh/zip95297/zip95297.github.io@main/source/images/%E8%AE%A1%E7%AE%97%E6%9C%BA%E9%AB%98%E7%BA%A7%E7%B3%BB%E7%BB%9F%E7%BB%93%E6%9E%84/Pasted%20image%2020241226140214.webp?raw=true) -![](https://cdn.jsdelivr.net/gh/zip95297/zip95297.github.io@main/source/images/%E8%AE%A1%E7%AE%97%E6%9C%BA%E9%AB%98%E7%BA%A7%E7%B3%BB%E7%BB%9F%E7%BB%93%E6%9E%84/Pasted%20image%2020241226140309.webp?raw=true) -嵌入式作为一个系统的一部分,还应考虑其实时性 - -## 计算机系统结构定义和计算机的设计任务:指令集结构概念以及要素 - -![](https://cdn.jsdelivr.net/gh/zip95297/zip95297.github.io@main/source/images/%E8%AE%A1%E7%AE%97%E6%9C%BA%E9%AB%98%E7%BA%A7%E7%B3%BB%E7%BB%9F%E7%BB%93%E6%9E%84/Pasted%20image%2020241226140503.webp?raw=true) -![](https://cdn.jsdelivr.net/gh/zip95297/zip95297.github.io@main/source/images/%E8%AE%A1%E7%AE%97%E6%9C%BA%E9%AB%98%E7%BA%A7%E7%B3%BB%E7%BB%9F%E7%BB%93%E6%9E%84/Pasted%20image%2020241226140708.webp?raw=true) - -![](https://cdn.jsdelivr.net/gh/zip95297/zip95297.github.io@main/source/images/%E8%AE%A1%E7%AE%97%E6%9C%BA%E9%AB%98%E7%BA%A7%E7%B3%BB%E7%BB%9F%E7%BB%93%E6%9E%84/Pasted%20image%2020241226140521.webp?raw=true) -![](https://cdn.jsdelivr.net/gh/zip95297/zip95297.github.io@main/source/images/%E8%AE%A1%E7%AE%97%E6%9C%BA%E9%AB%98%E7%BA%A7%E7%B3%BB%E7%BB%9F%E7%BB%93%E6%9E%84/Pasted%20image%2020241226140547.webp?raw=true) -![](https://cdn.jsdelivr.net/gh/zip95297/zip95297.github.io@main/source/images/%E8%AE%A1%E7%AE%97%E6%9C%BA%E9%AB%98%E7%BA%A7%E7%B3%BB%E7%BB%9F%E7%BB%93%E6%9E%84/Pasted%20image%2020241226140600.webp?raw=true) - - -## 实现的技术趋势:技术发展趋势 - -![](https://cdn.jsdelivr.net/gh/zip95297/zip95297.github.io@main/source/images/%E8%AE%A1%E7%AE%97%E6%9C%BA%E9%AB%98%E7%BA%A7%E7%B3%BB%E7%BB%9F%E7%BB%93%E6%9E%84/Pasted%20image%2020241226140815.webp?raw=true) - -![](https://cdn.jsdelivr.net/gh/zip95297/zip95297.github.io@main/source/images/%E8%AE%A1%E7%AE%97%E6%9C%BA%E9%AB%98%E7%BA%A7%E7%B3%BB%E7%BB%9F%E7%BB%93%E6%9E%84/Pasted%20image%2020241226140923.webp?raw=true) -cache用的是sram 主存用的是dram -![](https://cdn.jsdelivr.net/gh/zip95297/zip95297.github.io@main/source/images/%E8%AE%A1%E7%AE%97%E6%9C%BA%E9%AB%98%E7%BA%A7%E7%B3%BB%E7%BB%9F%E7%BB%93%E6%9E%84/Pasted%20image%2020241226140933.webp?raw=true) -![](https://cdn.jsdelivr.net/gh/zip95297/zip95297.github.io@main/source/images/%E8%AE%A1%E7%AE%97%E6%9C%BA%E9%AB%98%E7%BA%A7%E7%B3%BB%E7%BB%9F%E7%BB%93%E6%9E%84/Pasted%20image%2020241226140947.webp?raw=true) -![](https://cdn.jsdelivr.net/gh/zip95297/zip95297.github.io@main/source/images/%E8%AE%A1%E7%AE%97%E6%9C%BA%E9%AB%98%E7%BA%A7%E7%B3%BB%E7%BB%9F%E7%BB%93%E6%9E%84/Pasted%20image%2020241226141117.webp?raw=true) - - -## 集成电路功耗的趋势:功耗的概念 - -![](https://cdn.jsdelivr.net/gh/zip95297/zip95297.github.io@main/source/images/%E8%AE%A1%E7%AE%97%E6%9C%BA%E9%AB%98%E7%BA%A7%E7%B3%BB%E7%BB%9F%E7%BB%93%E6%9E%84/Pasted%20image%2020241226141154.webp?raw=true) - -![](https://cdn.jsdelivr.net/gh/zip95297/zip95297.github.io@main/source/images/%E8%AE%A1%E7%AE%97%E6%9C%BA%E9%AB%98%E7%BA%A7%E7%B3%BB%E7%BB%9F%E7%BB%93%E6%9E%84/Pasted%20image%2020241226141328.webp?raw=true) -![](https://cdn.jsdelivr.net/gh/zip95297/zip95297.github.io@main/source/images/%E8%AE%A1%E7%AE%97%E6%9C%BA%E9%AB%98%E7%BA%A7%E7%B3%BB%E7%BB%9F%E7%BB%93%E6%9E%84/Pasted%20image%2020241226141355.webp?raw=true) -![](https://cdn.jsdelivr.net/gh/zip95297/zip95297.github.io@main/source/images/%E8%AE%A1%E7%AE%97%E6%9C%BA%E9%AB%98%E7%BA%A7%E7%B3%BB%E7%BB%9F%E7%BB%93%E6%9E%84/Pasted%20image%2020241226141407.webp?raw=true) - - -## 可靠性:计算可靠性的方法 - -![](https://cdn.jsdelivr.net/gh/zip95297/zip95297.github.io@main/source/images/%E8%AE%A1%E7%AE%97%E6%9C%BA%E9%AB%98%E7%BA%A7%E7%B3%BB%E7%BB%9F%E7%BB%93%E6%9E%84/Pasted%20image%2020241226141524.webp?raw=true) -![](https://cdn.jsdelivr.net/gh/zip95297/zip95297.github.io@main/source/images/%E8%AE%A1%E7%AE%97%E6%9C%BA%E9%AB%98%E7%BA%A7%E7%B3%BB%E7%BB%9F%E7%BB%93%E6%9E%84/Pasted%20image%2020241226141531.webp?raw=true) -![](https://cdn.jsdelivr.net/gh/zip95297/zip95297.github.io@main/source/images/%E8%AE%A1%E7%AE%97%E6%9C%BA%E9%AB%98%E7%BA%A7%E7%B3%BB%E7%BB%9F%E7%BB%93%E6%9E%84/Pasted%20image%2020241226141648.webp?raw=true) -![](https://cdn.jsdelivr.net/gh/zip95297/zip95297.github.io@main/source/images/%E8%AE%A1%E7%AE%97%E6%9C%BA%E9%AB%98%E7%BA%A7%E7%B3%BB%E7%BB%9F%E7%BB%93%E6%9E%84/Pasted%20image%2020241226141726.webp?raw=true) - -## 测量、报告和总结计算机性能:计算机主要性能指标 - -![](https://cdn.jsdelivr.net/gh/zip95297/zip95297.github.io@main/source/images/%E8%AE%A1%E7%AE%97%E6%9C%BA%E9%AB%98%E7%BA%A7%E7%B3%BB%E7%BB%9F%E7%BB%93%E6%9E%84/Pasted%20image%2020241226141828.webp?raw=true) -![](https://cdn.jsdelivr.net/gh/zip95297/zip95297.github.io@main/source/images/%E8%AE%A1%E7%AE%97%E6%9C%BA%E9%AB%98%E7%BA%A7%E7%B3%BB%E7%BB%9F%E7%BB%93%E6%9E%84/Pasted%20image%2020241226141910.webp?raw=true) - -![](https://cdn.jsdelivr.net/gh/zip95297/zip95297.github.io@main/source/images/%E8%AE%A1%E7%AE%97%E6%9C%BA%E9%AB%98%E7%BA%A7%E7%B3%BB%E7%BB%9F%E7%BB%93%E6%9E%84/Pasted%20image%2020241226141919.webp?raw=true) -如果一个是RISC一个CISC这种MIPS的比较方法就不公平 (CPI可能不一样) -可以先算IPS每秒执行多少指令,然后再除1million(1后面6个0) -![](https://cdn.jsdelivr.net/gh/zip95297/zip95297.github.io@main/source/images/%E8%AE%A1%E7%AE%97%E6%9C%BA%E9%AB%98%E7%BA%A7%E7%B3%BB%E7%BB%9F%E7%BB%93%E6%9E%84/Pasted%20image%2020241226142150.webp?raw=true) -![](https://cdn.jsdelivr.net/gh/zip95297/zip95297.github.io@main/source/images/%E8%AE%A1%E7%AE%97%E6%9C%BA%E9%AB%98%E7%BA%A7%E7%B3%BB%E7%BB%9F%E7%BB%93%E6%9E%84/Pasted%20image%2020241226142202.webp?raw=true) - -![](https://cdn.jsdelivr.net/gh/zip95297/zip95297.github.io@main/source/images/%E8%AE%A1%E7%AE%97%E6%9C%BA%E9%AB%98%E7%BA%A7%E7%B3%BB%E7%BB%9F%E7%BB%93%E6%9E%84/Pasted%20image%2020241226142341.webp?raw=true) -![](https://cdn.jsdelivr.net/gh/zip95297/zip95297.github.io@main/source/images/%E8%AE%A1%E7%AE%97%E6%9C%BA%E9%AB%98%E7%BA%A7%E7%B3%BB%E7%BB%9F%E7%BB%93%E6%9E%84/Pasted%20image%2020241226142346.webp?raw=true) - -![](https://cdn.jsdelivr.net/gh/zip95297/zip95297.github.io@main/source/images/%E8%AE%A1%E7%AE%97%E6%9C%BA%E9%AB%98%E7%BA%A7%E7%B3%BB%E7%BB%9F%E7%BB%93%E6%9E%84/Pasted%20image%2020241226142353.webp?raw=true) - -![](https://cdn.jsdelivr.net/gh/zip95297/zip95297.github.io@main/source/images/%E8%AE%A1%E7%AE%97%E6%9C%BA%E9%AB%98%E7%BA%A7%E7%B3%BB%E7%BB%9F%E7%BB%93%E6%9E%84/Pasted%20image%2020241226142537.webp?raw=true) -![](https://cdn.jsdelivr.net/gh/zip95297/zip95297.github.io@main/source/images/%E8%AE%A1%E7%AE%97%E6%9C%BA%E9%AB%98%E7%BA%A7%E7%B3%BB%E7%BB%9F%E7%BB%93%E6%9E%84/Pasted%20image%2020241226142542.webp?raw=true) - -![](https://cdn.jsdelivr.net/gh/zip95297/zip95297.github.io@main/source/images/%E8%AE%A1%E7%AE%97%E6%9C%BA%E9%AB%98%E7%BA%A7%E7%B3%BB%E7%BB%9F%E7%BB%93%E6%9E%84/Pasted%20image%2020241226142618.webp?raw=true) - -## 计算机设计的量化原则:Amdahl定律 - -![](https://cdn.jsdelivr.net/gh/zip95297/zip95297.github.io@main/source/images/%E8%AE%A1%E7%AE%97%E6%9C%BA%E9%AB%98%E7%BA%A7%E7%B3%BB%E7%BB%9F%E7%BB%93%E6%9E%84/Pasted%20image%2020241226142636.webp?raw=true) - -![](https://cdn.jsdelivr.net/gh/zip95297/zip95297.github.io@main/source/images/%E8%AE%A1%E7%AE%97%E6%9C%BA%E9%AB%98%E7%BA%A7%E7%B3%BB%E7%BB%9F%E7%BB%93%E6%9E%84/Pasted%20image%2020241226142647.webp?raw=true) -![](https://cdn.jsdelivr.net/gh/zip95297/zip95297.github.io@main/source/images/%E8%AE%A1%E7%AE%97%E6%9C%BA%E9%AB%98%E7%BA%A7%E7%B3%BB%E7%BB%9F%E7%BB%93%E6%9E%84/Pasted%20image%2020241226142655.webp?raw=true) -![](https://cdn.jsdelivr.net/gh/zip95297/zip95297.github.io@main/source/images/%E8%AE%A1%E7%AE%97%E6%9C%BA%E9%AB%98%E7%BA%A7%E7%B3%BB%E7%BB%9F%E7%BB%93%E6%9E%84/Pasted%20image%2020241226142702.webp?raw=true) -![](https://cdn.jsdelivr.net/gh/zip95297/zip95297.github.io@main/source/images/%E8%AE%A1%E7%AE%97%E6%9C%BA%E9%AB%98%E7%BA%A7%E7%B3%BB%E7%BB%9F%E7%BB%93%E6%9E%84/Pasted%20image%2020241226142713.webp?raw=true) -![](https://cdn.jsdelivr.net/gh/zip95297/zip95297.github.io@main/source/images/%E8%AE%A1%E7%AE%97%E6%9C%BA%E9%AB%98%E7%BA%A7%E7%B3%BB%E7%BB%9F%E7%BB%93%E6%9E%84/Pasted%20image%2020241226142720.webp?raw=true) -![](https://cdn.jsdelivr.net/gh/zip95297/zip95297.github.io@main/source/images/%E8%AE%A1%E7%AE%97%E6%9C%BA%E9%AB%98%E7%BA%A7%E7%B3%BB%E7%BB%9F%E7%BB%93%E6%9E%84/Pasted%20image%2020241226142730.webp?raw=true) -![](https://cdn.jsdelivr.net/gh/zip95297/zip95297.github.io@main/source/images/%E8%AE%A1%E7%AE%97%E6%9C%BA%E9%AB%98%E7%BA%A7%E7%B3%BB%E7%BB%9F%E7%BB%93%E6%9E%84/Pasted%20image%2020241226142741.webp?raw=true) -![](https://cdn.jsdelivr.net/gh/zip95297/zip95297.github.io@main/source/images/%E8%AE%A1%E7%AE%97%E6%9C%BA%E9%AB%98%E7%BA%A7%E7%B3%BB%E7%BB%9F%E7%BB%93%E6%9E%84/Pasted%20image%2020241226142752.webp?raw=true) -![](https://cdn.jsdelivr.net/gh/zip95297/zip95297.github.io@main/source/images/%E8%AE%A1%E7%AE%97%E6%9C%BA%E9%AB%98%E7%BA%A7%E7%B3%BB%E7%BB%9F%E7%BB%93%E6%9E%84/Pasted%20image%2020241226142810.webp?raw=true) -![](https://cdn.jsdelivr.net/gh/zip95297/zip95297.github.io@main/source/images/%E8%AE%A1%E7%AE%97%E6%9C%BA%E9%AB%98%E7%BA%A7%E7%B3%BB%E7%BB%9F%E7%BB%93%E6%9E%84/Pasted%20image%2020241226142824.webp?raw=true) - -## 陷阱 - -![](https://cdn.jsdelivr.net/gh/zip95297/zip95297.github.io@main/source/images/%E8%AE%A1%E7%AE%97%E6%9C%BA%E9%AB%98%E7%BA%A7%E7%B3%BB%E7%BB%9F%E7%BB%93%E6%9E%84/Pasted%20image%2020241226142933.webp?raw=true) -![](https://cdn.jsdelivr.net/gh/zip95297/zip95297.github.io@main/source/images/%E8%AE%A1%E7%AE%97%E6%9C%BA%E9%AB%98%E7%BA%A7%E7%B3%BB%E7%BB%9F%E7%BB%93%E6%9E%84/Pasted%20image%2020241226142949.webp?raw=true) -![](https://cdn.jsdelivr.net/gh/zip95297/zip95297.github.io@main/source/images/%E8%AE%A1%E7%AE%97%E6%9C%BA%E9%AB%98%E7%BA%A7%E7%B3%BB%E7%BB%9F%E7%BB%93%E6%9E%84/Pasted%20image%2020241226143053.webp?raw=true) -![](https://cdn.jsdelivr.net/gh/zip95297/zip95297.github.io@main/source/images/%E8%AE%A1%E7%AE%97%E6%9C%BA%E9%AB%98%E7%BA%A7%E7%B3%BB%E7%BB%9F%E7%BB%93%E6%9E%84/Pasted%20image%2020241226143058.webp?raw=true) - -![](https://cdn.jsdelivr.net/gh/zip95297/zip95297.github.io@main/source/images/%E8%AE%A1%E7%AE%97%E6%9C%BA%E9%AB%98%E7%BA%A7%E7%B3%BB%E7%BB%9F%E7%BB%93%E6%9E%84/Pasted%20image%2020241226143225.webp?raw=true) - -# 第二章 指令系统原理与实例 - -## 指令集系统结构的分类:指令集系统的不同结构 - -![](https://cdn.jsdelivr.net/gh/zip95297/zip95297.github.io@main/source/images/%E8%AE%A1%E7%AE%97%E6%9C%BA%E9%AB%98%E7%BA%A7%E7%B3%BB%E7%BB%9F%E7%BB%93%E6%9E%84/Pasted%20image%2020241226144553.webp?raw=true) -![](https://cdn.jsdelivr.net/gh/zip95297/zip95297.github.io@main/source/images/%E8%AE%A1%E7%AE%97%E6%9C%BA%E9%AB%98%E7%BA%A7%E7%B3%BB%E7%BB%9F%E7%BB%93%E6%9E%84/Pasted%20image%2020241226144559.webp?raw=true) -![](https://cdn.jsdelivr.net/gh/zip95297/zip95297.github.io@main/source/images/%E8%AE%A1%E7%AE%97%E6%9C%BA%E9%AB%98%E7%BA%A7%E7%B3%BB%E7%BB%9F%E7%BB%93%E6%9E%84/Pasted%20image%2020241226144616.webp?raw=true) - -![](https://cdn.jsdelivr.net/gh/zip95297/zip95297.github.io@main/source/images/%E8%AE%A1%E7%AE%97%E6%9C%BA%E9%AB%98%E7%BA%A7%E7%B3%BB%E7%BB%9F%E7%BB%93%E6%9E%84/Pasted%20image%2020241226144705.webp?raw=true) -![](https://cdn.jsdelivr.net/gh/zip95297/zip95297.github.io@main/source/images/%E8%AE%A1%E7%AE%97%E6%9C%BA%E9%AB%98%E7%BA%A7%E7%B3%BB%E7%BB%9F%E7%BB%93%E6%9E%84/Pasted%20image%2020241226144726.webp?raw=true) -![](https://cdn.jsdelivr.net/gh/zip95297/zip95297.github.io@main/source/images/%E8%AE%A1%E7%AE%97%E6%9C%BA%E9%AB%98%E7%BA%A7%E7%B3%BB%E7%BB%9F%E7%BB%93%E6%9E%84/Pasted%20image%2020241226144745.webp?raw=true) -![](https://cdn.jsdelivr.net/gh/zip95297/zip95297.github.io@main/source/images/%E8%AE%A1%E7%AE%97%E6%9C%BA%E9%AB%98%E7%BA%A7%E7%B3%BB%E7%BB%9F%E7%BB%93%E6%9E%84/Pasted%20image%2020241226144852.webp?raw=true) - - -## 存储器寻址:大小端模式及地址对齐 - -![](https://cdn.jsdelivr.net/gh/zip95297/zip95297.github.io@main/source/images/%E8%AE%A1%E7%AE%97%E6%9C%BA%E9%AB%98%E7%BA%A7%E7%B3%BB%E7%BB%9F%E7%BB%93%E6%9E%84/Pasted%20image%2020241226144916.webp?raw=true) -![](https://cdn.jsdelivr.net/gh/zip95297/zip95297.github.io@main/source/images/%E8%AE%A1%E7%AE%97%E6%9C%BA%E9%AB%98%E7%BA%A7%E7%B3%BB%E7%BB%9F%E7%BB%93%E6%9E%84/Pasted%20image%2020241226144958.webp?raw=true) -![](https://cdn.jsdelivr.net/gh/zip95297/zip95297.github.io@main/source/images/%E8%AE%A1%E7%AE%97%E6%9C%BA%E9%AB%98%E7%BA%A7%E7%B3%BB%E7%BB%9F%E7%BB%93%E6%9E%84/Pasted%20image%2020241226145009.webp?raw=true) -![](https://cdn.jsdelivr.net/gh/zip95297/zip95297.github.io@main/source/images/%E8%AE%A1%E7%AE%97%E6%9C%BA%E9%AB%98%E7%BA%A7%E7%B3%BB%E7%BB%9F%E7%BB%93%E6%9E%84/Pasted%20image%2020241226145049.webp?raw=true) - -![](https://cdn.jsdelivr.net/gh/zip95297/zip95297.github.io@main/source/images/%E8%AE%A1%E7%AE%97%E6%9C%BA%E9%AB%98%E7%BA%A7%E7%B3%BB%E7%BB%9F%E7%BB%93%E6%9E%84/Pasted%20image%2020241226145102.webp?raw=true) -![](https://cdn.jsdelivr.net/gh/zip95297/zip95297.github.io@main/source/images/%E8%AE%A1%E7%AE%97%E6%9C%BA%E9%AB%98%E7%BA%A7%E7%B3%BB%E7%BB%9F%E7%BB%93%E6%9E%84/Pasted%20image%2020241226145112.webp?raw=true) -![](https://cdn.jsdelivr.net/gh/zip95297/zip95297.github.io@main/source/images/%E8%AE%A1%E7%AE%97%E6%9C%BA%E9%AB%98%E7%BA%A7%E7%B3%BB%E7%BB%9F%E7%BB%93%E6%9E%84/Pasted%20image%2020241226145157.webp?raw=true) -![](https://cdn.jsdelivr.net/gh/zip95297/zip95297.github.io@main/source/images/%E8%AE%A1%E7%AE%97%E6%9C%BA%E9%AB%98%E7%BA%A7%E7%B3%BB%E7%BB%9F%E7%BB%93%E6%9E%84/Pasted%20image%2020241226145215.webp?raw=true) -## MIPS系统结构:MIPS指令集结构 - -![](https://cdn.jsdelivr.net/gh/zip95297/zip95297.github.io@main/source/images/%E8%AE%A1%E7%AE%97%E6%9C%BA%E9%AB%98%E7%BA%A7%E7%B3%BB%E7%BB%9F%E7%BB%93%E6%9E%84/Pasted%20image%2020241226145808.webp?raw=true) -![](https://cdn.jsdelivr.net/gh/zip95297/zip95297.github.io@main/source/images/%E8%AE%A1%E7%AE%97%E6%9C%BA%E9%AB%98%E7%BA%A7%E7%B3%BB%E7%BB%9F%E7%BB%93%E6%9E%84/Pasted%20image%2020241226145816.webp?raw=true) -![](https://cdn.jsdelivr.net/gh/zip95297/zip95297.github.io@main/source/images/%E8%AE%A1%E7%AE%97%E6%9C%BA%E9%AB%98%E7%BA%A7%E7%B3%BB%E7%BB%9F%E7%BB%93%E6%9E%84/Pasted%20image%2020241226145830.webp?raw=true) -![](https://cdn.jsdelivr.net/gh/zip95297/zip95297.github.io@main/source/images/%E8%AE%A1%E7%AE%97%E6%9C%BA%E9%AB%98%E7%BA%A7%E7%B3%BB%E7%BB%9F%E7%BB%93%E6%9E%84/Pasted%20image%2020241226145843.webp?raw=true) -![](https://cdn.jsdelivr.net/gh/zip95297/zip95297.github.io@main/source/images/%E8%AE%A1%E7%AE%97%E6%9C%BA%E9%AB%98%E7%BA%A7%E7%B3%BB%E7%BB%9F%E7%BB%93%E6%9E%84/Pasted%20image%2020241226145909.webp?raw=true) -![](https://cdn.jsdelivr.net/gh/zip95297/zip95297.github.io@main/source/images/%E8%AE%A1%E7%AE%97%E6%9C%BA%E9%AB%98%E7%BA%A7%E7%B3%BB%E7%BB%9F%E7%BB%93%E6%9E%84/Pasted%20image%2020241226145922.webp?raw=true) - -## 其他 - -![](https://cdn.jsdelivr.net/gh/zip95297/zip95297.github.io@main/source/images/%E8%AE%A1%E7%AE%97%E6%9C%BA%E9%AB%98%E7%BA%A7%E7%B3%BB%E7%BB%9F%E7%BB%93%E6%9E%84/Pasted%20image%2020241226145440.webp?raw=true) -![](https://cdn.jsdelivr.net/gh/zip95297/zip95297.github.io@main/source/images/%E8%AE%A1%E7%AE%97%E6%9C%BA%E9%AB%98%E7%BA%A7%E7%B3%BB%E7%BB%9F%E7%BB%93%E6%9E%84/Pasted%20image%2020241226145509.webp?raw=true) -![](https://cdn.jsdelivr.net/gh/zip95297/zip95297.github.io@main/source/images/%E8%AE%A1%E7%AE%97%E6%9C%BA%E9%AB%98%E7%BA%A7%E7%B3%BB%E7%BB%9F%E7%BB%93%E6%9E%84/Pasted%20image%2020241226145535.webp?raw=true) -![](https://cdn.jsdelivr.net/gh/zip95297/zip95297.github.io@main/source/images/%E8%AE%A1%E7%AE%97%E6%9C%BA%E9%AB%98%E7%BA%A7%E7%B3%BB%E7%BB%9F%E7%BB%93%E6%9E%84/Pasted%20image%2020241226145550.webp?raw=true) -所以跳转指令中imm 在sign后要<<2 因为表示的是指令个数,一个指令4字节 -![](https://cdn.jsdelivr.net/gh/zip95297/zip95297.github.io@main/source/images/%E8%AE%A1%E7%AE%97%E6%9C%BA%E9%AB%98%E7%BA%A7%E7%B3%BB%E7%BB%9F%E7%BB%93%E6%9E%84/Pasted%20image%2020241226145702.webp?raw=true) -# 第三章 单周期MIPS处理器的设计 - -## add, sub, addi, subi, lw, sw, beq, j 每条指令在单周期处理器中的执行逻辑以及 上述指令的指令编码、代码、功能以及在单周期中的数据通路,条件分支指令的地址计算、单周期各功能部件的控制信号值判断 - -![](https://cdn.jsdelivr.net/gh/zip95297/zip95297.github.io@main/source/images/%E8%AE%A1%E7%AE%97%E6%9C%BA%E9%AB%98%E7%BA%A7%E7%B3%BB%E7%BB%9F%E7%BB%93%E6%9E%84/Pasted%20image%2020241226150322.webp?raw=true) -只有位运算 & j是0拓展 -![](https://cdn.jsdelivr.net/gh/zip95297/zip95297.github.io@main/source/images/%E8%AE%A1%E7%AE%97%E6%9C%BA%E9%AB%98%E7%BA%A7%E7%B3%BB%E7%BB%9F%E7%BB%93%E6%9E%84/Pasted%20image%2020241226150359.webp?raw=true) -![](https://cdn.jsdelivr.net/gh/zip95297/zip95297.github.io@main/source/images/%E8%AE%A1%E7%AE%97%E6%9C%BA%E9%AB%98%E7%BA%A7%E7%B3%BB%E7%BB%9F%E7%BB%93%E6%9E%84/Pasted%20image%2020241226150508.webp?raw=true) -![](https://cdn.jsdelivr.net/gh/zip95297/zip95297.github.io@main/source/images/%E8%AE%A1%E7%AE%97%E6%9C%BA%E9%AB%98%E7%BA%A7%E7%B3%BB%E7%BB%9F%E7%BB%93%E6%9E%84/Pasted%20image%2020241226151838.webp?raw=true) -![](https://cdn.jsdelivr.net/gh/zip95297/zip95297.github.io@main/source/images/%E8%AE%A1%E7%AE%97%E6%9C%BA%E9%AB%98%E7%BA%A7%E7%B3%BB%E7%BB%9F%E7%BB%93%E6%9E%84/Pasted%20image%2020241226151850.webp?raw=true) -![](https://cdn.jsdelivr.net/gh/zip95297/zip95297.github.io@main/source/images/%E8%AE%A1%E7%AE%97%E6%9C%BA%E9%AB%98%E7%BA%A7%E7%B3%BB%E7%BB%9F%E7%BB%93%E6%9E%84/Pasted%20image%2020241226151917.webp?raw=true) - -## 中断和异常的处理时机 - -![](https://cdn.jsdelivr.net/gh/zip95297/zip95297.github.io@main/source/images/%E8%AE%A1%E7%AE%97%E6%9C%BA%E9%AB%98%E7%BA%A7%E7%B3%BB%E7%BB%9F%E7%BB%93%E6%9E%84/Pasted%20image%2020241226152028.webp?raw=true) -![](https://cdn.jsdelivr.net/gh/zip95297/zip95297.github.io@main/source/images/%E8%AE%A1%E7%AE%97%E6%9C%BA%E9%AB%98%E7%BA%A7%E7%B3%BB%E7%BB%9F%E7%BB%93%E6%9E%84/Pasted%20image%2020241226152035.webp?raw=true) -![](https://cdn.jsdelivr.net/gh/zip95297/zip95297.github.io@main/source/images/%E8%AE%A1%E7%AE%97%E6%9C%BA%E9%AB%98%E7%BA%A7%E7%B3%BB%E7%BB%9F%E7%BB%93%E6%9E%84/Pasted%20image%2020241226152053.webp?raw=true) -![](https://cdn.jsdelivr.net/gh/zip95297/zip95297.github.io@main/source/images/%E8%AE%A1%E7%AE%97%E6%9C%BA%E9%AB%98%E7%BA%A7%E7%B3%BB%E7%BB%9F%E7%BB%93%E6%9E%84/Pasted%20image%2020241226152118.webp?raw=true) -![](https://cdn.jsdelivr.net/gh/zip95297/zip95297.github.io@main/source/images/%E8%AE%A1%E7%AE%97%E6%9C%BA%E9%AB%98%E7%BA%A7%E7%B3%BB%E7%BB%9F%E7%BB%93%E6%9E%84/Pasted%20image%2020241226152124.webp?raw=true) -![](https://cdn.jsdelivr.net/gh/zip95297/zip95297.github.io@main/source/images/%E8%AE%A1%E7%AE%97%E6%9C%BA%E9%AB%98%E7%BA%A7%E7%B3%BB%E7%BB%9F%E7%BB%93%E6%9E%84/Pasted%20image%2020241226152137.webp?raw=true) -![](https://cdn.jsdelivr.net/gh/zip95297/zip95297.github.io@main/source/images/%E8%AE%A1%E7%AE%97%E6%9C%BA%E9%AB%98%E7%BA%A7%E7%B3%BB%E7%BB%9F%E7%BB%93%E6%9E%84/Pasted%20image%2020241226152150.webp?raw=true) - -# 第四章 流水线技术及指令级并行 - -## 流水线的概念、分类 - -![](https://cdn.jsdelivr.net/gh/zip95297/zip95297.github.io@main/source/images/%E8%AE%A1%E7%AE%97%E6%9C%BA%E9%AB%98%E7%BA%A7%E7%B3%BB%E7%BB%9F%E7%BB%93%E6%9E%84/Pasted%20image%2020241226152703.webp?raw=true) -![](https://cdn.jsdelivr.net/gh/zip95297/zip95297.github.io@main/source/images/%E8%AE%A1%E7%AE%97%E6%9C%BA%E9%AB%98%E7%BA%A7%E7%B3%BB%E7%BB%9F%E7%BB%93%E6%9E%84/Pasted%20image%2020241226152644.webp?raw=true) -![](https://cdn.jsdelivr.net/gh/zip95297/zip95297.github.io@main/source/images/%E8%AE%A1%E7%AE%97%E6%9C%BA%E9%AB%98%E7%BA%A7%E7%B3%BB%E7%BB%9F%E7%BB%93%E6%9E%84/Pasted%20image%2020241226152739.webp?raw=true) - -![](https://cdn.jsdelivr.net/gh/zip95297/zip95297.github.io@main/source/images/%E8%AE%A1%E7%AE%97%E6%9C%BA%E9%AB%98%E7%BA%A7%E7%B3%BB%E7%BB%9F%E7%BB%93%E6%9E%84/Pasted%20image%2020241226152811.webp?raw=true) -![](https://cdn.jsdelivr.net/gh/zip95297/zip95297.github.io@main/source/images/%E8%AE%A1%E7%AE%97%E6%9C%BA%E9%AB%98%E7%BA%A7%E7%B3%BB%E7%BB%9F%E7%BB%93%E6%9E%84/Pasted%20image%2020241226152819.webp?raw=true) -![](https://cdn.jsdelivr.net/gh/zip95297/zip95297.github.io@main/source/images/%E8%AE%A1%E7%AE%97%E6%9C%BA%E9%AB%98%E7%BA%A7%E7%B3%BB%E7%BB%9F%E7%BB%93%E6%9E%84/Pasted%20image%2020241226152830.webp?raw=true) -![](https://cdn.jsdelivr.net/gh/zip95297/zip95297.github.io@main/source/images/%E8%AE%A1%E7%AE%97%E6%9C%BA%E9%AB%98%E7%BA%A7%E7%B3%BB%E7%BB%9F%E7%BB%93%E6%9E%84/Pasted%20image%2020241226152837.webp?raw=true) -![](https://cdn.jsdelivr.net/gh/zip95297/zip95297.github.io@main/source/images/%E8%AE%A1%E7%AE%97%E6%9C%BA%E9%AB%98%E7%BA%A7%E7%B3%BB%E7%BB%9F%E7%BB%93%E6%9E%84/Pasted%20image%2020241226152843.webp?raw=true) -![](https://cdn.jsdelivr.net/gh/zip95297/zip95297.github.io@main/source/images/%E8%AE%A1%E7%AE%97%E6%9C%BA%E9%AB%98%E7%BA%A7%E7%B3%BB%E7%BB%9F%E7%BB%93%E6%9E%84/Pasted%20image%2020241226152852.webp?raw=true) -![](https://cdn.jsdelivr.net/gh/zip95297/zip95297.github.io@main/source/images/%E8%AE%A1%E7%AE%97%E6%9C%BA%E9%AB%98%E7%BA%A7%E7%B3%BB%E7%BB%9F%E7%BB%93%E6%9E%84/Pasted%20image%2020241226152902.webp?raw=true) -![](https://cdn.jsdelivr.net/gh/zip95297/zip95297.github.io@main/source/images/%E8%AE%A1%E7%AE%97%E6%9C%BA%E9%AB%98%E7%BA%A7%E7%B3%BB%E7%BB%9F%E7%BB%93%E6%9E%84/Pasted%20image%2020241226152909.webp?raw=true) -![](https://cdn.jsdelivr.net/gh/zip95297/zip95297.github.io@main/source/images/%E8%AE%A1%E7%AE%97%E6%9C%BA%E9%AB%98%E7%BA%A7%E7%B3%BB%E7%BB%9F%E7%BB%93%E6%9E%84/Pasted%20image%2020241226152914.webp?raw=true) -![](https://cdn.jsdelivr.net/gh/zip95297/zip95297.github.io@main/source/images/%E8%AE%A1%E7%AE%97%E6%9C%BA%E9%AB%98%E7%BA%A7%E7%B3%BB%E7%BB%9F%E7%BB%93%E6%9E%84/Pasted%20image%2020241226152920.webp?raw=true) -![](https://cdn.jsdelivr.net/gh/zip95297/zip95297.github.io@main/source/images/%E8%AE%A1%E7%AE%97%E6%9C%BA%E9%AB%98%E7%BA%A7%E7%B3%BB%E7%BB%9F%E7%BB%93%E6%9E%84/Pasted%20image%2020241226152928.webp?raw=true) - - - -## 流水线的时空图及性能指标计算 - -![](https://cdn.jsdelivr.net/gh/zip95297/zip95297.github.io@main/source/images/%E8%AE%A1%E7%AE%97%E6%9C%BA%E9%AB%98%E7%BA%A7%E7%B3%BB%E7%BB%9F%E7%BB%93%E6%9E%84/Pasted%20image%2020241226153028.webp?raw=true)![](https://cdn.jsdelivr.net/gh/zip95297/zip95297.github.io@main/source/images/%E8%AE%A1%E7%AE%97%E6%9C%BA%E9%AB%98%E7%BA%A7%E7%B3%BB%E7%BB%9F%E7%BB%93%E6%9E%84/Pasted%20image%2020241226153023.webp?raw=true) -![](https://cdn.jsdelivr.net/gh/zip95297/zip95297.github.io@main/source/images/%E8%AE%A1%E7%AE%97%E6%9C%BA%E9%AB%98%E7%BA%A7%E7%B3%BB%E7%BB%9F%E7%BB%93%E6%9E%84/Pasted%20image%2020241226153038.webp?raw=true) -![](https://cdn.jsdelivr.net/gh/zip95297/zip95297.github.io@main/source/images/%E8%AE%A1%E7%AE%97%E6%9C%BA%E9%AB%98%E7%BA%A7%E7%B3%BB%E7%BB%9F%E7%BB%93%E6%9E%84/Pasted%20image%2020241226153045.webp?raw=true) -![](https://cdn.jsdelivr.net/gh/zip95297/zip95297.github.io@main/source/images/%E8%AE%A1%E7%AE%97%E6%9C%BA%E9%AB%98%E7%BA%A7%E7%B3%BB%E7%BB%9F%E7%BB%93%E6%9E%84/Pasted%20image%2020241226153142.webp?raw=true) -![](https://cdn.jsdelivr.net/gh/zip95297/zip95297.github.io@main/source/images/%E8%AE%A1%E7%AE%97%E6%9C%BA%E9%AB%98%E7%BA%A7%E7%B3%BB%E7%BB%9F%E7%BB%93%E6%9E%84/Pasted%20image%2020241226153150.webp?raw=true) -![](https://cdn.jsdelivr.net/gh/zip95297/zip95297.github.io@main/source/images/%E8%AE%A1%E7%AE%97%E6%9C%BA%E9%AB%98%E7%BA%A7%E7%B3%BB%E7%BB%9F%E7%BB%93%E6%9E%84/Pasted%20image%2020241226153214.webp?raw=true) -![](https://cdn.jsdelivr.net/gh/zip95297/zip95297.github.io@main/source/images/%E8%AE%A1%E7%AE%97%E6%9C%BA%E9%AB%98%E7%BA%A7%E7%B3%BB%E7%BB%9F%E7%BB%93%E6%9E%84/Pasted%20image%2020241226153247.webp?raw=true) -![](https://cdn.jsdelivr.net/gh/zip95297/zip95297.github.io@main/source/images/%E8%AE%A1%E7%AE%97%E6%9C%BA%E9%AB%98%E7%BA%A7%E7%B3%BB%E7%BB%9F%E7%BB%93%E6%9E%84/Pasted%20image%2020241226153308.webp?raw=true) - -![](https://cdn.jsdelivr.net/gh/zip95297/zip95297.github.io@main/source/images/%E8%AE%A1%E7%AE%97%E6%9C%BA%E9%AB%98%E7%BA%A7%E7%B3%BB%E7%BB%9F%E7%BB%93%E6%9E%84/Pasted%20image%2020241226153320.webp?raw=true)![](https://cdn.jsdelivr.net/gh/zip95297/zip95297.github.io@main/source/images/%E8%AE%A1%E7%AE%97%E6%9C%BA%E9%AB%98%E7%BA%A7%E7%B3%BB%E7%BB%9F%E7%BB%93%E6%9E%84/Pasted%20image%2020241226153324.webp?raw=true) -![](https://cdn.jsdelivr.net/gh/zip95297/zip95297.github.io@main/source/images/%E8%AE%A1%E7%AE%97%E6%9C%BA%E9%AB%98%E7%BA%A7%E7%B3%BB%E7%BB%9F%E7%BB%93%E6%9E%84/Pasted%20image%2020241226153330.webp?raw=true) -![](https://cdn.jsdelivr.net/gh/zip95297/zip95297.github.io@main/source/images/%E8%AE%A1%E7%AE%97%E6%9C%BA%E9%AB%98%E7%BA%A7%E7%B3%BB%E7%BB%9F%E7%BB%93%E6%9E%84/Pasted%20image%2020241226153336.webp?raw=true) -![](https://cdn.jsdelivr.net/gh/zip95297/zip95297.github.io@main/source/images/%E8%AE%A1%E7%AE%97%E6%9C%BA%E9%AB%98%E7%BA%A7%E7%B3%BB%E7%BB%9F%E7%BB%93%E6%9E%84/Pasted%20image%2020241226153341.webp?raw=true) -![](https://cdn.jsdelivr.net/gh/zip95297/zip95297.github.io@main/source/images/%E8%AE%A1%E7%AE%97%E6%9C%BA%E9%AB%98%E7%BA%A7%E7%B3%BB%E7%BB%9F%E7%BB%93%E6%9E%84/Pasted%20image%2020241226153401.webp?raw=true) -![](https://cdn.jsdelivr.net/gh/zip95297/zip95297.github.io@main/source/images/%E8%AE%A1%E7%AE%97%E6%9C%BA%E9%AB%98%E7%BA%A7%E7%B3%BB%E7%BB%9F%E7%BB%93%E6%9E%84/Pasted%20image%2020241226153412.webp?raw=true) - -![](https://cdn.jsdelivr.net/gh/zip95297/zip95297.github.io@main/source/images/%E8%AE%A1%E7%AE%97%E6%9C%BA%E9%AB%98%E7%BA%A7%E7%B3%BB%E7%BB%9F%E7%BB%93%E6%9E%84/Pasted%20image%2020241226153515.webp?raw=true) - - -![](https://cdn.jsdelivr.net/gh/zip95297/zip95297.github.io@main/source/images/%E8%AE%A1%E7%AE%97%E6%9C%BA%E9%AB%98%E7%BA%A7%E7%B3%BB%E7%BB%9F%E7%BB%93%E6%9E%84/Pasted%20image%2020241226154009.webp?raw=true) -![](https://cdn.jsdelivr.net/gh/zip95297/zip95297.github.io@main/source/images/%E8%AE%A1%E7%AE%97%E6%9C%BA%E9%AB%98%E7%BA%A7%E7%B3%BB%E7%BB%9F%E7%BB%93%E6%9E%84/Pasted%20image%2020241226154025.webp?raw=true) - -## add, sub, addi, or, ori, lw, sw, beq每条指令在5级流水线的执行过程 - -## 结构冒险、数据冒险和控制冒险的判断,以及需要暂停的时钟周期数的判断(控制冒险的解决性能依赖于分支地址计算阶段和分支条件判断阶段) - -![](https://cdn.jsdelivr.net/gh/zip95297/zip95297.github.io@main/source/images/%E8%AE%A1%E7%AE%97%E6%9C%BA%E9%AB%98%E7%BA%A7%E7%B3%BB%E7%BB%9F%E7%BB%93%E6%9E%84/Pasted%20image%2020241226155023.webp?raw=true) -### 结构冒险以及三个解决办法 -![](https://cdn.jsdelivr.net/gh/zip95297/zip95297.github.io@main/source/images/%E8%AE%A1%E7%AE%97%E6%9C%BA%E9%AB%98%E7%BA%A7%E7%B3%BB%E7%BB%9F%E7%BB%93%E6%9E%84/Pasted%20image%2020241226171805.webp?raw=true) -![](https://cdn.jsdelivr.net/gh/zip95297/zip95297.github.io@main/source/images/%E8%AE%A1%E7%AE%97%E6%9C%BA%E9%AB%98%E7%BA%A7%E7%B3%BB%E7%BB%9F%E7%BB%93%E6%9E%84/Pasted%20image%2020241226171813.webp?raw=true) -![](https://cdn.jsdelivr.net/gh/zip95297/zip95297.github.io@main/source/images/%E8%AE%A1%E7%AE%97%E6%9C%BA%E9%AB%98%E7%BA%A7%E7%B3%BB%E7%BB%9F%E7%BB%93%E6%9E%84/Pasted%20image%2020241226171826.webp?raw=true) -![](https://cdn.jsdelivr.net/gh/zip95297/zip95297.github.io@main/source/images/%E8%AE%A1%E7%AE%97%E6%9C%BA%E9%AB%98%E7%BA%A7%E7%B3%BB%E7%BB%9F%E7%BB%93%E6%9E%84/Pasted%20image%2020241226172002.webp?raw=true) - -### 数据冒险以及解决办法 -数据冒险指的是 写后读 RAW -![](https://cdn.jsdelivr.net/gh/zip95297/zip95297.github.io@main/source/images/%E8%AE%A1%E7%AE%97%E6%9C%BA%E9%AB%98%E7%BA%A7%E7%B3%BB%E7%BB%9F%E7%BB%93%E6%9E%84/Pasted%20image%2020241226172141.webp?raw=true) -![](https://cdn.jsdelivr.net/gh/zip95297/zip95297.github.io@main/source/images/%E8%AE%A1%E7%AE%97%E6%9C%BA%E9%AB%98%E7%BA%A7%E7%B3%BB%E7%BB%9F%E7%BB%93%E6%9E%84/Pasted%20image%2020241226172148.webp?raw=true) -![](https://cdn.jsdelivr.net/gh/zip95297/zip95297.github.io@main/source/images/%E8%AE%A1%E7%AE%97%E6%9C%BA%E9%AB%98%E7%BA%A7%E7%B3%BB%E7%BB%9F%E7%BB%93%E6%9E%84/Pasted%20image%2020241226172218.webp?raw=true) -![](https://cdn.jsdelivr.net/gh/zip95297/zip95297.github.io@main/source/images/%E8%AE%A1%E7%AE%97%E6%9C%BA%E9%AB%98%E7%BA%A7%E7%B3%BB%E7%BB%9F%E7%BB%93%E6%9E%84/Pasted%20image%2020241226172241.webp?raw=true) -![](https://cdn.jsdelivr.net/gh/zip95297/zip95297.github.io@main/source/images/%E8%AE%A1%E7%AE%97%E6%9C%BA%E9%AB%98%E7%BA%A7%E7%B3%BB%E7%BB%9F%E7%BB%93%E6%9E%84/Pasted%20image%2020241226172251.webp?raw=true) - -![](https://cdn.jsdelivr.net/gh/zip95297/zip95297.github.io@main/source/images/%E8%AE%A1%E7%AE%97%E6%9C%BA%E9%AB%98%E7%BA%A7%E7%B3%BB%E7%BB%9F%E7%BB%93%E6%9E%84/Pasted%20image%2020241226173516.webp?raw=true) -![](https://cdn.jsdelivr.net/gh/zip95297/zip95297.github.io@main/source/images/%E8%AE%A1%E7%AE%97%E6%9C%BA%E9%AB%98%E7%BA%A7%E7%B3%BB%E7%BB%9F%E7%BB%93%E6%9E%84/Pasted%20image%2020241226173528.webp?raw=true) -办法2:数据前推 -![](https://cdn.jsdelivr.net/gh/zip95297/zip95297.github.io@main/source/images/%E8%AE%A1%E7%AE%97%E6%9C%BA%E9%AB%98%E7%BA%A7%E7%B3%BB%E7%BB%9F%E7%BB%93%E6%9E%84/Pasted%20image%2020241226173621.webp?raw=true) -注意是 从 流水线寄存器 中取出数据到操作数中 -![](https://cdn.jsdelivr.net/gh/zip95297/zip95297.github.io@main/source/images/%E8%AE%A1%E7%AE%97%E6%9C%BA%E9%AB%98%E7%BA%A7%E7%B3%BB%E7%BB%9F%E7%BB%93%E6%9E%84/Pasted%20image%2020241226174011.webp?raw=true) -对于 lw 。。 op 。。 这种数据冒险,由于lw在 MEM阶段过后 结果才存在在流水线寄存器中 -所以要对下面的在IF ID中进行stall 暂停流水线:保持PC寄存器和IF/ID寄存器不变 -![](https://cdn.jsdelivr.net/gh/zip95297/zip95297.github.io@main/source/images/%E8%AE%A1%E7%AE%97%E6%9C%BA%E9%AB%98%E7%BA%A7%E7%B3%BB%E7%BB%9F%E7%BB%93%E6%9E%84/Pasted%20image%2020241226174142.webp?raw=true) -![](https://cdn.jsdelivr.net/gh/zip95297/zip95297.github.io@main/source/images/%E8%AE%A1%E7%AE%97%E6%9C%BA%E9%AB%98%E7%BA%A7%E7%B3%BB%E7%BB%9F%E7%BB%93%E6%9E%84/Pasted%20image%2020241226174150.webp?raw=true) -![](https://cdn.jsdelivr.net/gh/zip95297/zip95297.github.io@main/source/images/%E8%AE%A1%E7%AE%97%E6%9C%BA%E9%AB%98%E7%BA%A7%E7%B3%BB%E7%BB%9F%E7%BB%93%E6%9E%84/Pasted%20image%2020241226174245.webp?raw=true) - - -### 控制冒险以及解决办法 - -![](https://cdn.jsdelivr.net/gh/zip95297/zip95297.github.io@main/source/images/%E8%AE%A1%E7%AE%97%E6%9C%BA%E9%AB%98%E7%BA%A7%E7%B3%BB%E7%BB%9F%E7%BB%93%E6%9E%84/Pasted%20image%2020241226172317.webp?raw=true) -![](https://cdn.jsdelivr.net/gh/zip95297/zip95297.github.io@main/source/images/%E8%AE%A1%E7%AE%97%E6%9C%BA%E9%AB%98%E7%BA%A7%E7%B3%BB%E7%BB%9F%E7%BB%93%E6%9E%84/Pasted%20image%2020241226172403.webp?raw=true) -![](https://cdn.jsdelivr.net/gh/zip95297/zip95297.github.io@main/source/images/%E8%AE%A1%E7%AE%97%E6%9C%BA%E9%AB%98%E7%BA%A7%E7%B3%BB%E7%BB%9F%E7%BB%93%E6%9E%84/Pasted%20image%2020241226172439.webp?raw=true) -![](https://cdn.jsdelivr.net/gh/zip95297/zip95297.github.io@main/source/images/%E8%AE%A1%E7%AE%97%E6%9C%BA%E9%AB%98%E7%BA%A7%E7%B3%BB%E7%BB%9F%E7%BB%93%E6%9E%84/Pasted%20image%2020241226172511.webp?raw=true) -![](https://cdn.jsdelivr.net/gh/zip95297/zip95297.github.io@main/source/images/%E8%AE%A1%E7%AE%97%E6%9C%BA%E9%AB%98%E7%BA%A7%E7%B3%BB%E7%BB%9F%E7%BB%93%E6%9E%84/Pasted%20image%2020241226172616.webp?raw=true) -![](https://cdn.jsdelivr.net/gh/zip95297/zip95297.github.io@main/source/images/%E8%AE%A1%E7%AE%97%E6%9C%BA%E9%AB%98%E7%BA%A7%E7%B3%BB%E7%BB%9F%E7%BB%93%E6%9E%84/Pasted%20image%2020241226172719.webp?raw=true) -![](https://cdn.jsdelivr.net/gh/zip95297/zip95297.github.io@main/source/images/%E8%AE%A1%E7%AE%97%E6%9C%BA%E9%AB%98%E7%BA%A7%E7%B3%BB%E7%BB%9F%E7%BB%93%E6%9E%84/Pasted%20image%2020241226172726.webp?raw=true) -![](https://cdn.jsdelivr.net/gh/zip95297/zip95297.github.io@main/source/images/%E8%AE%A1%E7%AE%97%E6%9C%BA%E9%AB%98%E7%BA%A7%E7%B3%BB%E7%BB%9F%E7%BB%93%E6%9E%84/Pasted%20image%2020241226172732.webp?raw=true) -![](https://cdn.jsdelivr.net/gh/zip95297/zip95297.github.io@main/source/images/%E8%AE%A1%E7%AE%97%E6%9C%BA%E9%AB%98%E7%BA%A7%E7%B3%BB%E7%BB%9F%E7%BB%93%E6%9E%84/Pasted%20image%2020241226172758.webp?raw=true) -![](https://cdn.jsdelivr.net/gh/zip95297/zip95297.github.io@main/source/images/%E8%AE%A1%E7%AE%97%E6%9C%BA%E9%AB%98%E7%BA%A7%E7%B3%BB%E7%BB%9F%E7%BB%93%E6%9E%84/Pasted%20image%2020241226172751.webp?raw=true) -![](https://cdn.jsdelivr.net/gh/zip95297/zip95297.github.io@main/source/images/%E8%AE%A1%E7%AE%97%E6%9C%BA%E9%AB%98%E7%BA%A7%E7%B3%BB%E7%BB%9F%E7%BB%93%E6%9E%84/Pasted%20image%2020241226172833.webp?raw=true) -![](https://cdn.jsdelivr.net/gh/zip95297/zip95297.github.io@main/source/images/%E8%AE%A1%E7%AE%97%E6%9C%BA%E9%AB%98%E7%BA%A7%E7%B3%BB%E7%BB%9F%E7%BB%93%E6%9E%84/Pasted%20image%2020241226172917.webp?raw=true) -![](https://cdn.jsdelivr.net/gh/zip95297/zip95297.github.io@main/source/images/%E8%AE%A1%E7%AE%97%E6%9C%BA%E9%AB%98%E7%BA%A7%E7%B3%BB%E7%BB%9F%E7%BB%93%E6%9E%84/Pasted%20image%2020241226172954.webp?raw=true) - -如果add在alu中 mem阶段过后 pc在得到正确的下一个指令地址 -![](https://cdn.jsdelivr.net/gh/zip95297/zip95297.github.io@main/source/images/%E8%AE%A1%E7%AE%97%E6%9C%BA%E9%AB%98%E7%BA%A7%E7%B3%BB%E7%BB%9F%E7%BB%93%E6%9E%84/Pasted%20image%2020241226174440.webp?raw=true) -在ID及计算转移地址 -![](https://cdn.jsdelivr.net/gh/zip95297/zip95297.github.io@main/source/images/%E8%AE%A1%E7%AE%97%E6%9C%BA%E9%AB%98%E7%BA%A7%E7%B3%BB%E7%BB%9F%E7%BB%93%E6%9E%84/Pasted%20image%2020241226174506.webp?raw=true) -![](https://cdn.jsdelivr.net/gh/zip95297/zip95297.github.io@main/source/images/%E8%AE%A1%E7%AE%97%E6%9C%BA%E9%AB%98%E7%BA%A7%E7%B3%BB%E7%BB%9F%E7%BB%93%E6%9E%84/Pasted%20image%2020241226174829.webp?raw=true) -如果分支预测错误就flush ID/EXE寄存器 ,即把转移指令的下一个指令转为NOP 空指令。 -flush - 指令编码变全0:流水线定义编码为全0的指令为nop指令,语义:什么工作都不做。 IF.Flush有效,则将该指令在打入IF/ID寄存器时变为nop指令。 - 指令编码不变:将该指令的所有写信号全部关闭。 - -## 结构冒险、数据冒险和控制冒险的解决办法 - -stall理解为关闭要等待指令的写信号 - -结构冒险: - 1. 插入暂停周期,即让流水线在完成前一条指令对数据的存储器访问时,暂停取后一条指令(指令存储器)的操作 - 2. 设置相互独立的指令存储器和数据存储器或设置相互独立的指令Cache和数据Cache - 3. 预取指令技术(在重叠操作中,当前一条指令在执行过程中就需要提前取出后面的指令进行相应处理,这种提前取出后继指令进行相应处理,称为先行(预取)。) - -数据冒险: - 1. 写回WB操作提前半个周期 - 2. 数据前推:如果发现与结下来的1、2条指令存在数据冒险。从流水线寄存器中拿出 数据到冲突的指令操作数。 由于要检测两个位置,每个ALU输入需要一个 四路复选。 - - 对于 lw 。。 op 。。 这种数据冒险,由于lw在 MEM阶段过后 结果才存在在流水线寄存器中 - 所以要对下面的在IF ID中进行stall 暂停流水线:保持PC寄存器和IF/ID寄存器不变 - -控制冒险: - 1. 转移Stalls ,但会造成大的性能损失 - 2. 改进分支预测技术 - - 静态预测:例如总是预测分支不发生(即假设分支不发生,属于静态预测机制)(成功率在50%左右)。 - - 动态预测: 转移预测缓存 1位或者2位 或者 转移目标缓存(把为转移的后继指令保存预测地址的转移预测Cache称为转移目标缓存或转移目标Cache。) - 3. 延迟转移、缩小延迟槽:延迟转移是指在转移指令后顺序执行一条无关的指令,然后再进行转移。缩小延迟槽是尽早计算出转移目标地址,例如EXE阶段的ADD移动到 ID阶段 - -## 流水线中处理中断和异常的方法 - -![](https://cdn.jsdelivr.net/gh/zip95297/zip95297.github.io@main/source/images/%E8%AE%A1%E7%AE%97%E6%9C%BA%E9%AB%98%E7%BA%A7%E7%B3%BB%E7%BB%9F%E7%BB%93%E6%9E%84/Pasted%20image%2020241226175556.webp?raw=true) -![](https://cdn.jsdelivr.net/gh/zip95297/zip95297.github.io@main/source/images/%E8%AE%A1%E7%AE%97%E6%9C%BA%E9%AB%98%E7%BA%A7%E7%B3%BB%E7%BB%9F%E7%BB%93%E6%9E%84/Pasted%20image%2020241226175605.webp?raw=true) -![](https://cdn.jsdelivr.net/gh/zip95297/zip95297.github.io@main/source/images/%E8%AE%A1%E7%AE%97%E6%9C%BA%E9%AB%98%E7%BA%A7%E7%B3%BB%E7%BB%9F%E7%BB%93%E6%9E%84/Pasted%20image%2020241226175611.webp?raw=true) -处理完后PC是PC_origin+4 -![](https://cdn.jsdelivr.net/gh/zip95297/zip95297.github.io@main/source/images/%E8%AE%A1%E7%AE%97%E6%9C%BA%E9%AB%98%E7%BA%A7%E7%B3%BB%E7%BB%9F%E7%BB%93%E6%9E%84/Pasted%20image%2020241226175710.webp?raw=true) - -流水线处理机处理异常的方式: -![](https://cdn.jsdelivr.net/gh/zip95297/zip95297.github.io@main/source/images/%E8%AE%A1%E7%AE%97%E6%9C%BA%E9%AB%98%E7%BA%A7%E7%B3%BB%E7%BB%9F%E7%BB%93%E6%9E%84/Pasted%20image%2020241226175837.webp?raw=true) -![](https://cdn.jsdelivr.net/gh/zip95297/zip95297.github.io@main/source/images/%E8%AE%A1%E7%AE%97%E6%9C%BA%E9%AB%98%E7%BA%A7%E7%B3%BB%E7%BB%9F%E7%BB%93%E6%9E%84/Pasted%20image%2020241226175855.webp?raw=true) -!!EX/MEM被清除是周期结束之后清楚掉add的结果 在周期完成之后才进行异常的处理 -![](https://cdn.jsdelivr.net/gh/zip95297/zip95297.github.io@main/source/images/%E8%AE%A1%E7%AE%97%E6%9C%BA%E9%AB%98%E7%BA%A7%E7%B3%BB%E7%BB%9F%E7%BB%93%E6%9E%84/Pasted%20image%2020241226175904.webp?raw=true) -![](https://cdn.jsdelivr.net/gh/zip95297/zip95297.github.io@main/source/images/%E8%AE%A1%E7%AE%97%E6%9C%BA%E9%AB%98%E7%BA%A7%E7%B3%BB%E7%BB%9F%E7%BB%93%E6%9E%84/Pasted%20image%2020241226180024.webp?raw=true) -![](https://cdn.jsdelivr.net/gh/zip95297/zip95297.github.io@main/source/images/%E8%AE%A1%E7%AE%97%E6%9C%BA%E9%AB%98%E7%BA%A7%E7%B3%BB%E7%BB%9F%E7%BB%93%E6%9E%84/Pasted%20image%2020241226180037.webp?raw=true) - -### 精确非精确异常 - -![](https://cdn.jsdelivr.net/gh/zip95297/zip95297.github.io@main/source/images/%E8%AE%A1%E7%AE%97%E6%9C%BA%E9%AB%98%E7%BA%A7%E7%B3%BB%E7%BB%9F%E7%BB%93%E6%9E%84/Pasted%20image%2020241226180817.webp?raw=true) -![](https://cdn.jsdelivr.net/gh/zip95297/zip95297.github.io@main/source/images/%E8%AE%A1%E7%AE%97%E6%9C%BA%E9%AB%98%E7%BA%A7%E7%B3%BB%E7%BB%9F%E7%BB%93%E6%9E%84/Pasted%20image%2020241226180846.webp?raw=true) -![](https://cdn.jsdelivr.net/gh/zip95297/zip95297.github.io@main/source/images/%E8%AE%A1%E7%AE%97%E6%9C%BA%E9%AB%98%E7%BA%A7%E7%B3%BB%E7%BB%9F%E7%BB%93%E6%9E%84/Pasted%20image%2020241226180911.webp?raw=true) -同一个周期内多个阶段出现异常,先处理最早流入流水线的异常。 -![](https://cdn.jsdelivr.net/gh/zip95297/zip95297.github.io@main/source/images/%E8%AE%A1%E7%AE%97%E6%9C%BA%E9%AB%98%E7%BA%A7%E7%B3%BB%E7%BB%9F%E7%BB%93%E6%9E%84/Pasted%20image%2020241226180920.webp?raw=true) - -![](https://cdn.jsdelivr.net/gh/zip95297/zip95297.github.io@main/source/images/%E8%AE%A1%E7%AE%97%E6%9C%BA%E9%AB%98%E7%BA%A7%E7%B3%BB%E7%BB%9F%E7%BB%93%E6%9E%84/Pasted%20image%2020241226181015.webp?raw=true) -![](https://cdn.jsdelivr.net/gh/zip95297/zip95297.github.io@main/source/images/%E8%AE%A1%E7%AE%97%E6%9C%BA%E9%AB%98%E7%BA%A7%E7%B3%BB%E7%BB%9F%E7%BB%93%E6%9E%84/Pasted%20image%2020241226181043.webp?raw=true) - - -## 其他 - -打乱顺序可以提高并行程度,有静态调度(编译器)和动态调度(硬件实现) -![](https://cdn.jsdelivr.net/gh/zip95297/zip95297.github.io@main/source/images/%E8%AE%A1%E7%AE%97%E6%9C%BA%E9%AB%98%E7%BA%A7%E7%B3%BB%E7%BB%9F%E7%BB%93%E6%9E%84/Pasted%20image%2020241226181305.webp?raw=true) -![](https://cdn.jsdelivr.net/gh/zip95297/zip95297.github.io@main/source/images/%E8%AE%A1%E7%AE%97%E6%9C%BA%E9%AB%98%E7%BA%A7%E7%B3%BB%E7%BB%9F%E7%BB%93%E6%9E%84/Pasted%20image%2020241226181312.webp?raw=true) -写后写 读后写 和数据冒险(写后读)这几种不能调整顺序。 -![](https://cdn.jsdelivr.net/gh/zip95297/zip95297.github.io@main/source/images/%E8%AE%A1%E7%AE%97%E6%9C%BA%E9%AB%98%E7%BA%A7%E7%B3%BB%E7%BB%9F%E7%BB%93%E6%9E%84/Pasted%20image%2020241226181323.webp?raw=true) - -循环展开 -![](https://cdn.jsdelivr.net/gh/zip95297/zip95297.github.io@main/source/images/%E8%AE%A1%E7%AE%97%E6%9C%BA%E9%AB%98%E7%BA%A7%E7%B3%BB%E7%BB%9F%E7%BB%93%E6%9E%84/Pasted%20image%2020241226181437.webp?raw=true) - -还有一些利用了时空相似性提高cache命中的方法 例如 合并数组、合并独立循环、调整循环顺序等。 - -## 记分牌/Tomasulo动态调度算法!!!!!!!!!!!!!! - -### 计分牌 - -四个阶段 - -1. IS(Issue):顺序发射指令 - - (进入条件)在执行发射之前,检查 : 1.结构冲冲突 即busy; 2. 写写冲突,检查 寄存器状态表 是不是有 写写冲突 - - (计分牌记录内容) - - 把要使用的部件 Busy置为Yes 记录Op Fd Fj Fk Qj Qk Rj Rk记录 - - 在寄存器状态表中 要写的寄存器来源记为当前功能单元 - -2. RO(Read Operand):读操作数 - - (进入条件)Rj · Rk = 1 两个操作数必须都要同时准备好 解决了先写(还没准备好,要等准备好之后才能读)再读(数据冒险) - - (计分牌记录内容) - - R yes转为no 已经被读 Qj Qk转为0 - -3. EXE 执行 - -4. WB(WriteBack):写回 - - (进入条件)检查要写的寄存器是不是其他已经准备好的内容 比如要写F0 但是有条指令要用F0 而且状态是yes 那么就不能写。 解决的是读写冲突 - - (计分牌内容)如果有Q在等待当前WB的部件,把Q去掉,F写进去,R置为Yes 然后把状态寄存器中的等待部件释放 释放BUSY - - -三张表: -1. 指令状态表 记录各个指令运行到哪个状态 - - Instruction IS RO EXE WB -2. 计分板 记录各个部件的情况 - - 部件名 Busy Op(instruction) Fd Fj Fk Qj Qk Rj Rk Q指的是要来源于哪个部件或者功能单元 R指的是是否准备好 -3. 寄存器状态表 记录各个寄存器将要被谁 哪个部件(功能单元) 写入 - -### Tomasulo动态调度算法 - -数据传递主要是用CDB 公共数据总线 - -每个部件之前都有 保留站 reverse station - -![](https://cdn.jsdelivr.net/gh/zip95297/zip95297.github.io@main/source/images/%E8%AE%A1%E7%AE%97%E6%9C%BA%E9%AB%98%E7%BA%A7%E7%B3%BB%E7%BB%9F%E7%BB%93%E6%9E%84/Pasted%20image%2020241226212059.webp?raw=true) -访存unit是单独的store buffer 和load buffer - -三个阶段 -1. IS(Issue):顺序发射指令 - - 如果要进入的部件的 保留站 有位置 就把当前指令载入到保留站中,若其中的操作数在寄存器就绪就将其送入保留站,如果未就绪就在保留站中记录产生该操作数的保留站编号(操作数寄存器名换成了保留站名)。 - - 如果是访存操作且有空的缓冲就流出到缓冲。 - - 如果没有空的保留站和缓冲,即有结构相关,就不流出。 - - 如果IS时候源操作数在FPReg中有值 就直接填入进来(防止WAR)即数据冒险 - - 如果有操作数还没写完,就在Q中记录tag - -2. EXE 执行: 如果保留站的操作数未计算出,就用保留站编号监视CDB(公共数据总线),一旦有结果就取到保留站中,当两个操作数就绪,进入执行阶段,执行指令操作。解决先写后读相关(RAW)。 - -3. WB(WriteBack):功能部件完成计算后,将结果连同产生该结果的保留站号一起送到CDB上。根据流出时的记录,所有等待本保留站结果的保留站、存缓冲、目标寄存器将同时从CDB上获得所需数据。 - -三张表: - -1. 指令状态表 记录各个指令运行到哪个状态 - - Instruction IS EXE WB -2. 计分板 记录各个保留站的情况 - - (保留站每一条的)标志tag Busy Op(instruction) Vj Vk Qj Qk A - - V是操作数的数值,Q是在等待的保留站条目的tag 两个只有一个有效 Q=0 表示 操作数在V中或者不需要这个操作数,A存的是存储器地址 -3. 寄存器状态表 记录各个寄存器将要被谁 哪个 tag(最新的) 写入 也可以包括寄存器的值 - -如果有写写冲突,因为 顺序issuse 在寄存器堆中都会记录正确的最新的数据来源 - -load在exe阶段可以计算出要访问的地址,然后再WB阶段才写回 -# 第五章 存储系统 - -## 存储器的分类和主要特点 - -SRAM一般CACHE DRAM一般是MEM 因为D要刷新,重启之后数据都没有了。 -![](https://cdn.jsdelivr.net/gh/zip95297/zip95297.github.io@main/source/images/%E8%AE%A1%E7%AE%97%E6%9C%BA%E9%AB%98%E7%BA%A7%E7%B3%BB%E7%BB%9F%E7%BB%93%E6%9E%84/Pasted%20image%2020241226182450.webp?raw=true) -![](https://cdn.jsdelivr.net/gh/zip95297/zip95297.github.io@main/source/images/%E8%AE%A1%E7%AE%97%E6%9C%BA%E9%AB%98%E7%BA%A7%E7%B3%BB%E7%BB%9F%E7%BB%93%E6%9E%84/Pasted%20image%2020241226182541.webp?raw=true) -![](https://cdn.jsdelivr.net/gh/zip95297/zip95297.github.io@main/source/images/%E8%AE%A1%E7%AE%97%E6%9C%BA%E9%AB%98%E7%BA%A7%E7%B3%BB%E7%BB%9F%E7%BB%93%E6%9E%84/Pasted%20image%2020241226183212.webp?raw=true) -![](https://cdn.jsdelivr.net/gh/zip95297/zip95297.github.io@main/source/images/%E8%AE%A1%E7%AE%97%E6%9C%BA%E9%AB%98%E7%BA%A7%E7%B3%BB%E7%BB%9F%E7%BB%93%E6%9E%84/Pasted%20image%2020241226183218.webp?raw=true) - - -![](https://cdn.jsdelivr.net/gh/zip95297/zip95297.github.io@main/source/images/%E8%AE%A1%E7%AE%97%E6%9C%BA%E9%AB%98%E7%BA%A7%E7%B3%BB%E7%BB%9F%E7%BB%93%E6%9E%84/Pasted%20image%2020241226182636.webp?raw=true) -![](https://cdn.jsdelivr.net/gh/zip95297/zip95297.github.io@main/source/images/%E8%AE%A1%E7%AE%97%E6%9C%BA%E9%AB%98%E7%BA%A7%E7%B3%BB%E7%BB%9F%E7%BB%93%E6%9E%84/Pasted%20image%2020241226182644.webp?raw=true) -![](https://cdn.jsdelivr.net/gh/zip95297/zip95297.github.io@main/source/images/%E8%AE%A1%E7%AE%97%E6%9C%BA%E9%AB%98%E7%BA%A7%E7%B3%BB%E7%BB%9F%E7%BB%93%E6%9E%84/Pasted%20image%2020241226183149.webp?raw=true) -## Cache的三种映像关系:全相联、直接映像、组相联 - -![](https://cdn.jsdelivr.net/gh/zip95297/zip95297.github.io@main/source/images/%E8%AE%A1%E7%AE%97%E6%9C%BA%E9%AB%98%E7%BA%A7%E7%B3%BB%E7%BB%9F%E7%BB%93%E6%9E%84/Pasted%20image%2020241226183304.webp?raw=true) -![](https://cdn.jsdelivr.net/gh/zip95297/zip95297.github.io@main/source/images/%E8%AE%A1%E7%AE%97%E6%9C%BA%E9%AB%98%E7%BA%A7%E7%B3%BB%E7%BB%9F%E7%BB%93%E6%9E%84/Pasted%20image%2020241226183311.webp?raw=true) -![](https://cdn.jsdelivr.net/gh/zip95297/zip95297.github.io@main/source/images/%E8%AE%A1%E7%AE%97%E6%9C%BA%E9%AB%98%E7%BA%A7%E7%B3%BB%E7%BB%9F%E7%BB%93%E6%9E%84/Pasted%20image%2020241226183320.webp?raw=true) -第几组是由较低位地址决定的(offset之前的低位地址) -![](https://cdn.jsdelivr.net/gh/zip95297/zip95297.github.io@main/source/images/%E8%AE%A1%E7%AE%97%E6%9C%BA%E9%AB%98%E7%BA%A7%E7%B3%BB%E7%BB%9F%E7%BB%93%E6%9E%84/Pasted%20image%2020241226183330.webp?raw=true) -![](https://cdn.jsdelivr.net/gh/zip95297/zip95297.github.io@main/source/images/%E8%AE%A1%E7%AE%97%E6%9C%BA%E9%AB%98%E7%BA%A7%E7%B3%BB%E7%BB%9F%E7%BB%93%E6%9E%84/Pasted%20image%2020241226183437.webp?raw=true) - -相联度越高 路数就越多 组数就越少,全相联只有一组,很多路 -## 主存地址Tag、Index、块内偏移三个字段的计算 - -![](https://cdn.jsdelivr.net/gh/zip95297/zip95297.github.io@main/source/images/%E8%AE%A1%E7%AE%97%E6%9C%BA%E9%AB%98%E7%BA%A7%E7%B3%BB%E7%BB%9F%E7%BB%93%E6%9E%84/Pasted%20image%2020241226183718.webp?raw=true) -![](https://cdn.jsdelivr.net/gh/zip95297/zip95297.github.io@main/source/images/%E8%AE%A1%E7%AE%97%E6%9C%BA%E9%AB%98%E7%BA%A7%E7%B3%BB%E7%BB%9F%E7%BB%93%E6%9E%84/Pasted%20image%2020241226183726.webp?raw=true) -![](https://cdn.jsdelivr.net/gh/zip95297/zip95297.github.io@main/source/images/%E8%AE%A1%E7%AE%97%E6%9C%BA%E9%AB%98%E7%BA%A7%E7%B3%BB%E7%BB%9F%E7%BB%93%E6%9E%84/Pasted%20image%2020241226183745.webp?raw=true) -![](https://cdn.jsdelivr.net/gh/zip95297/zip95297.github.io@main/source/images/%E8%AE%A1%E7%AE%97%E6%9C%BA%E9%AB%98%E7%BA%A7%E7%B3%BB%E7%BB%9F%E7%BB%93%E6%9E%84/Pasted%20image%2020241226183801.webp?raw=true) -索引是用来找在哪一组的,cache的组越多,index位就越多,全相联只有一个组,所以不用index,除了block offset都是tag - -## Cache块的替换策略 - -![](https://cdn.jsdelivr.net/gh/zip95297/zip95297.github.io@main/source/images/%E8%AE%A1%E7%AE%97%E6%9C%BA%E9%AB%98%E7%BA%A7%E7%B3%BB%E7%BB%9F%E7%BB%93%E6%9E%84/Pasted%20image%2020241226183934.webp?raw=true) -FIFO的替换看cache中谁先进来的 -![](https://cdn.jsdelivr.net/gh/zip95297/zip95297.github.io@main/source/images/%E8%AE%A1%E7%AE%97%E6%9C%BA%E9%AB%98%E7%BA%A7%E7%B3%BB%E7%BB%9F%E7%BB%93%E6%9E%84/Pasted%20image%2020241226183941.webp?raw=true) -LRU看访问顺序中谁最早被访问 -![](https://cdn.jsdelivr.net/gh/zip95297/zip95297.github.io@main/source/images/%E8%AE%A1%E7%AE%97%E6%9C%BA%E9%AB%98%E7%BA%A7%E7%B3%BB%E7%BB%9F%E7%BB%93%E6%9E%84/Pasted%20image%2020241226183949.webp?raw=true) - - -## Cache的读写过程 -![](https://cdn.jsdelivr.net/gh/zip95297/zip95297.github.io@main/source/images/%E8%AE%A1%E7%AE%97%E6%9C%BA%E9%AB%98%E7%BA%A7%E7%B3%BB%E7%BB%9F%E7%BB%93%E6%9E%84/Pasted%20image%2020241226184109.webp?raw=true) -![](https://cdn.jsdelivr.net/gh/zip95297/zip95297.github.io@main/source/images/%E8%AE%A1%E7%AE%97%E6%9C%BA%E9%AB%98%E7%BA%A7%E7%B3%BB%E7%BB%9F%E7%BB%93%E6%9E%84/Pasted%20image%2020241226184116.webp?raw=true) -![](https://cdn.jsdelivr.net/gh/zip95297/zip95297.github.io@main/source/images/%E8%AE%A1%E7%AE%97%E6%9C%BA%E9%AB%98%E7%BA%A7%E7%B3%BB%E7%BB%9F%E7%BB%93%E6%9E%84/Pasted%20image%2020241226184454.webp?raw=true) -![](https://cdn.jsdelivr.net/gh/zip95297/zip95297.github.io@main/source/images/%E8%AE%A1%E7%AE%97%E6%9C%BA%E9%AB%98%E7%BA%A7%E7%B3%BB%E7%BB%9F%E7%BB%93%E6%9E%84/Pasted%20image%2020241226184514.webp?raw=true) -![](https://cdn.jsdelivr.net/gh/zip95297/zip95297.github.io@main/source/images/%E8%AE%A1%E7%AE%97%E6%9C%BA%E9%AB%98%E7%BA%A7%E7%B3%BB%E7%BB%9F%E7%BB%93%E6%9E%84/Pasted%20image%2020241226184623.webp?raw=true) -因为写到buffer里面的速度更快 - -![](https://cdn.jsdelivr.net/gh/zip95297/zip95297.github.io@main/source/images/%E8%AE%A1%E7%AE%97%E6%9C%BA%E9%AB%98%E7%BA%A7%E7%B3%BB%E7%BB%9F%E7%BB%93%E6%9E%84/Pasted%20image%2020241226184642.webp?raw=true) -![](https://cdn.jsdelivr.net/gh/zip95297/zip95297.github.io@main/source/images/%E8%AE%A1%E7%AE%97%E6%9C%BA%E9%AB%98%E7%BA%A7%E7%B3%BB%E7%BB%9F%E7%BB%93%E6%9E%84/Pasted%20image%2020241226185053.webp?raw=true) -![](https://cdn.jsdelivr.net/gh/zip95297/zip95297.github.io@main/source/images/%E8%AE%A1%E7%AE%97%E6%9C%BA%E9%AB%98%E7%BA%A7%E7%B3%BB%E7%BB%9F%E7%BB%93%E6%9E%84/Pasted%20image%2020241226185114.webp?raw=true) - - -## 平均访存时间和CPU时间的计算 - -![](https://cdn.jsdelivr.net/gh/zip95297/zip95297.github.io@main/source/images/%E8%AE%A1%E7%AE%97%E6%9C%BA%E9%AB%98%E7%BA%A7%E7%B3%BB%E7%BB%9F%E7%BB%93%E6%9E%84/Pasted%20image%2020241226185139.webp?raw=true) -![](https://cdn.jsdelivr.net/gh/zip95297/zip95297.github.io@main/source/images/%E8%AE%A1%E7%AE%97%E6%9C%BA%E9%AB%98%E7%BA%A7%E7%B3%BB%E7%BB%9F%E7%BB%93%E6%9E%84/Pasted%20image%2020241226185149.webp?raw=true) -![](https://cdn.jsdelivr.net/gh/zip95297/zip95297.github.io@main/source/images/%E8%AE%A1%E7%AE%97%E6%9C%BA%E9%AB%98%E7%BA%A7%E7%B3%BB%E7%BB%9F%E7%BB%93%E6%9E%84/Pasted%20image%2020241226185312.webp?raw=true) -![](https://cdn.jsdelivr.net/gh/zip95297/zip95297.github.io@main/source/images/%E8%AE%A1%E7%AE%97%E6%9C%BA%E9%AB%98%E7%BA%A7%E7%B3%BB%E7%BB%9F%E7%BB%93%E6%9E%84/Pasted%20image%2020241226185352.webp?raw=true) - - -## Cache失效率的类别,以及每种失效率的解决方法有哪些 - -强制失效 容量失效 冲突失效 -![](https://cdn.jsdelivr.net/gh/zip95297/zip95297.github.io@main/source/images/%E8%AE%A1%E7%AE%97%E6%9C%BA%E9%AB%98%E7%BA%A7%E7%B3%BB%E7%BB%9F%E7%BB%93%E6%9E%84/Pasted%20image%2020241226185854.webp?raw=true) -相联度高,路数变多,冲突减少,但是因为要硬件同时比较更多的tag有可能使时钟周期变长 -![](https://cdn.jsdelivr.net/gh/zip95297/zip95297.github.io@main/source/images/%E8%AE%A1%E7%AE%97%E6%9C%BA%E9%AB%98%E7%BA%A7%E7%B3%BB%E7%BB%9F%E7%BB%93%E6%9E%84/Pasted%20image%2020241226185932.webp?raw=true) -![](https://cdn.jsdelivr.net/gh/zip95297/zip95297.github.io@main/source/images/%E8%AE%A1%E7%AE%97%E6%9C%BA%E9%AB%98%E7%BA%A7%E7%B3%BB%E7%BB%9F%E7%BB%93%E6%9E%84/Pasted%20image%2020241226190039.webp?raw=true) -强制失效跟程序的大小有关系,跟容量没关系。减少强制失效,可以增加快大小,每次装入更多的程序。 -![](https://cdn.jsdelivr.net/gh/zip95297/zip95297.github.io@main/source/images/%E8%AE%A1%E7%AE%97%E6%9C%BA%E9%AB%98%E7%BA%A7%E7%B3%BB%E7%BB%9F%E7%BB%93%E6%9E%84/Pasted%20image%2020241226190110.webp?raw=true) - -![](https://cdn.jsdelivr.net/gh/zip95297/zip95297.github.io@main/source/images/%E8%AE%A1%E7%AE%97%E6%9C%BA%E9%AB%98%E7%BA%A7%E7%B3%BB%E7%BB%9F%E7%BB%93%E6%9E%84/Pasted%20image%2020241226190308.webp?raw=true) - -![](https://cdn.jsdelivr.net/gh/zip95297/zip95297.github.io@main/source/images/%E8%AE%A1%E7%AE%97%E6%9C%BA%E9%AB%98%E7%BA%A7%E7%B3%BB%E7%BB%9F%E7%BB%93%E6%9E%84/Pasted%20image%2020241226190330.webp?raw=true) -减少 强制失效 但是增大了 容量失效(cache不够大) 和 冲突失效 -Cache容量越大,使失效率达到最低的块大小就越大 - -![](https://cdn.jsdelivr.net/gh/zip95297/zip95297.github.io@main/source/images/%E8%AE%A1%E7%AE%97%E6%9C%BA%E9%AB%98%E7%BA%A7%E7%B3%BB%E7%BB%9F%E7%BB%93%E6%9E%84/Pasted%20image%2020241226190506.webp?raw=true) -增加块大小的方法会在降低失效率的同时增加失效开销,而提高相联度则是以增加命中时间为代价。 - -![](https://cdn.jsdelivr.net/gh/zip95297/zip95297.github.io@main/source/images/%E8%AE%A1%E7%AE%97%E6%9C%BA%E9%AB%98%E7%BA%A7%E7%B3%BB%E7%BB%9F%E7%BB%93%E6%9E%84/Pasted%20image%2020241226190609.webp?raw=true) -增大cache容量减小了 容量失效 和 冲突失效 - -![](https://cdn.jsdelivr.net/gh/zip95297/zip95297.github.io@main/source/images/%E8%AE%A1%E7%AE%97%E6%9C%BA%E9%AB%98%E7%BA%A7%E7%B3%BB%E7%BB%9F%E7%BB%93%E6%9E%84/Pasted%20image%2020241226190653.webp?raw=true) -![](https://cdn.jsdelivr.net/gh/zip95297/zip95297.github.io@main/source/images/%E8%AE%A1%E7%AE%97%E6%9C%BA%E9%AB%98%E7%BA%A7%E7%B3%BB%E7%BB%9F%E7%BB%93%E6%9E%84/Pasted%20image%2020241226190739.webp?raw=true) -![](https://cdn.jsdelivr.net/gh/zip95297/zip95297.github.io@main/source/images/%E8%AE%A1%E7%AE%97%E6%9C%BA%E9%AB%98%E7%BA%A7%E7%B3%BB%E7%BB%9F%E7%BB%93%E6%9E%84/Pasted%20image%2020241226190750.webp?raw=true) -![](https://cdn.jsdelivr.net/gh/zip95297/zip95297.github.io@main/source/images/%E8%AE%A1%E7%AE%97%E6%9C%BA%E9%AB%98%E7%BA%A7%E7%B3%BB%E7%BB%9F%E7%BB%93%E6%9E%84/Pasted%20image%2020241226190805.webp?raw=true) - -### 减小失效开销: -![](https://cdn.jsdelivr.net/gh/zip95297/zip95297.github.io@main/source/images/%E8%AE%A1%E7%AE%97%E6%9C%BA%E9%AB%98%E7%BA%A7%E7%B3%BB%E7%BB%9F%E7%BB%93%E6%9E%84/Pasted%20image%2020241226190830.webp?raw=true) -![](https://cdn.jsdelivr.net/gh/zip95297/zip95297.github.io@main/source/images/%E8%AE%A1%E7%AE%97%E6%9C%BA%E9%AB%98%E7%BA%A7%E7%B3%BB%E7%BB%9F%E7%BB%93%E6%9E%84/Pasted%20image%2020241226190904.webp?raw=true) -![](https://cdn.jsdelivr.net/gh/zip95297/zip95297.github.io@main/source/images/%E8%AE%A1%E7%AE%97%E6%9C%BA%E9%AB%98%E7%BA%A7%E7%B3%BB%E7%BB%9F%E7%BB%93%E6%9E%84/Pasted%20image%2020241226190915.webp?raw=true) -![](https://cdn.jsdelivr.net/gh/zip95297/zip95297.github.io@main/source/images/%E8%AE%A1%E7%AE%97%E6%9C%BA%E9%AB%98%E7%BA%A7%E7%B3%BB%E7%BB%9F%E7%BB%93%E6%9E%84/Pasted%20image%2020241226190950.webp?raw=true) -![](https://cdn.jsdelivr.net/gh/zip95297/zip95297.github.io@main/source/images/%E8%AE%A1%E7%AE%97%E6%9C%BA%E9%AB%98%E7%BA%A7%E7%B3%BB%E7%BB%9F%E7%BB%93%E6%9E%84/Pasted%20image%2020241226191000.webp?raw=true) -![](https://cdn.jsdelivr.net/gh/zip95297/zip95297.github.io@main/source/images/%E8%AE%A1%E7%AE%97%E6%9C%BA%E9%AB%98%E7%BA%A7%E7%B3%BB%E7%BB%9F%E7%BB%93%E6%9E%84/Pasted%20image%2020241226191041.webp?raw=true) -![](https://cdn.jsdelivr.net/gh/zip95297/zip95297.github.io@main/source/images/%E8%AE%A1%E7%AE%97%E6%9C%BA%E9%AB%98%E7%BA%A7%E7%B3%BB%E7%BB%9F%E7%BB%93%E6%9E%84/Pasted%20image%2020241226191119.webp?raw=true) -![](https://cdn.jsdelivr.net/gh/zip95297/zip95297.github.io@main/source/images/%E8%AE%A1%E7%AE%97%E6%9C%BA%E9%AB%98%E7%BA%A7%E7%B3%BB%E7%BB%9F%E7%BB%93%E6%9E%84/Pasted%20image%2020241226191151.webp?raw=true) -![](https://cdn.jsdelivr.net/gh/zip95297/zip95297.github.io@main/source/images/%E8%AE%A1%E7%AE%97%E6%9C%BA%E9%AB%98%E7%BA%A7%E7%B3%BB%E7%BB%9F%E7%BB%93%E6%9E%84/Pasted%20image%2020241226191210.webp?raw=true) - -### 减小命中时间 - -![](https://cdn.jsdelivr.net/gh/zip95297/zip95297.github.io@main/source/images/%E8%AE%A1%E7%AE%97%E6%9C%BA%E9%AB%98%E7%BA%A7%E7%B3%BB%E7%BB%9F%E7%BB%93%E6%9E%84/Pasted%20image%2020241226191251.webp?raw=true) -![](https://cdn.jsdelivr.net/gh/zip95297/zip95297.github.io@main/source/images/%E8%AE%A1%E7%AE%97%E6%9C%BA%E9%AB%98%E7%BA%A7%E7%B3%BB%E7%BB%9F%E7%BB%93%E6%9E%84/Pasted%20image%2020241226191301.webp?raw=true) - - -## 虚拟地址到物理地址的转换过程,TLB表的原理和作用,与Cache的关系,访存时间的最好情况和最坏情况的判断 - -![](https://cdn.jsdelivr.net/gh/zip95297/zip95297.github.io@main/source/images/%E8%AE%A1%E7%AE%97%E6%9C%BA%E9%AB%98%E7%BA%A7%E7%B3%BB%E7%BB%9F%E7%BB%93%E6%9E%84/Pasted%20image%2020241226191317.webp?raw=true) -![](https://cdn.jsdelivr.net/gh/zip95297/zip95297.github.io@main/source/images/%E8%AE%A1%E7%AE%97%E6%9C%BA%E9%AB%98%E7%BA%A7%E7%B3%BB%E7%BB%9F%E7%BB%93%E6%9E%84/Pasted%20image%2020241226191425.webp?raw=true) -![](https://cdn.jsdelivr.net/gh/zip95297/zip95297.github.io@main/source/images/%E8%AE%A1%E7%AE%97%E6%9C%BA%E9%AB%98%E7%BA%A7%E7%B3%BB%E7%BB%9F%E7%BB%93%E6%9E%84/Pasted%20image%2020241226191453.webp?raw=true) -由于写磁盘太长,页表应采用写回机制 - -页表 直接相联 通过虚拟地址索引 然后 -![](https://cdn.jsdelivr.net/gh/zip95297/zip95297.github.io@main/source/images/%E8%AE%A1%E7%AE%97%E6%9C%BA%E9%AB%98%E7%BA%A7%E7%B3%BB%E7%BB%9F%E7%BB%93%E6%9E%84/Pasted%20image%2020241226191536.webp?raw=true) -![](https://cdn.jsdelivr.net/gh/zip95297/zip95297.github.io@main/source/images/%E8%AE%A1%E7%AE%97%E6%9C%BA%E9%AB%98%E7%BA%A7%E7%B3%BB%E7%BB%9F%E7%BB%93%E6%9E%84/Pasted%20image%2020241226191819.webp?raw=true) -![](https://cdn.jsdelivr.net/gh/zip95297/zip95297.github.io@main/source/images/%E8%AE%A1%E7%AE%97%E6%9C%BA%E9%AB%98%E7%BA%A7%E7%B3%BB%E7%BB%9F%E7%BB%93%E6%9E%84/Pasted%20image%2020241226191854.webp?raw=true) -![](https://cdn.jsdelivr.net/gh/zip95297/zip95297.github.io@main/source/images/%E8%AE%A1%E7%AE%97%E6%9C%BA%E9%AB%98%E7%BA%A7%E7%B3%BB%E7%BB%9F%E7%BB%93%E6%9E%84/Pasted%20image%2020241226191923.webp?raw=true) -![](https://cdn.jsdelivr.net/gh/zip95297/zip95297.github.io@main/source/images/%E8%AE%A1%E7%AE%97%E6%9C%BA%E9%AB%98%E7%BA%A7%E7%B3%BB%E7%BB%9F%E7%BB%93%E6%9E%84/Pasted%20image%2020241226191950.webp?raw=true) -![](https://cdn.jsdelivr.net/gh/zip95297/zip95297.github.io@main/source/images/%E8%AE%A1%E7%AE%97%E6%9C%BA%E9%AB%98%E7%BA%A7%E7%B3%BB%E7%BB%9F%E7%BB%93%E6%9E%84/Pasted%20image%2020241226192014.webp?raw=true) -TLB是全相联 页表一般是直接相联 -![](https://cdn.jsdelivr.net/gh/zip95297/zip95297.github.io@main/source/images/%E8%AE%A1%E7%AE%97%E6%9C%BA%E9%AB%98%E7%BA%A7%E7%B3%BB%E7%BB%9F%E7%BB%93%E6%9E%84/Pasted%20image%2020241226192035.webp?raw=true) -![](https://cdn.jsdelivr.net/gh/zip95297/zip95297.github.io@main/source/images/%E8%AE%A1%E7%AE%97%E6%9C%BA%E9%AB%98%E7%BA%A7%E7%B3%BB%E7%BB%9F%E7%BB%93%E6%9E%84/Pasted%20image%2020241226192100.webp?raw=true) -![](https://cdn.jsdelivr.net/gh/zip95297/zip95297.github.io@main/source/images/%E8%AE%A1%E7%AE%97%E6%9C%BA%E9%AB%98%E7%BA%A7%E7%B3%BB%E7%BB%9F%E7%BB%93%E6%9E%84/Pasted%20image%2020241226192105.webp?raw=true) -查TLB时虚拟地址是tag - -![](https://cdn.jsdelivr.net/gh/zip95297/zip95297.github.io@main/source/images/%E8%AE%A1%E7%AE%97%E6%9C%BA%E9%AB%98%E7%BA%A7%E7%B3%BB%E7%BB%9F%E7%BB%93%E6%9E%84/Pasted%20image%2020241226192400.webp?raw=true) -![](https://cdn.jsdelivr.net/gh/zip95297/zip95297.github.io@main/source/images/%E8%AE%A1%E7%AE%97%E6%9C%BA%E9%AB%98%E7%BA%A7%E7%B3%BB%E7%BB%9F%E7%BB%93%E6%9E%84/Pasted%20image%2020241226192406.webp?raw=true) diff --git "a/source/_posts/\350\275\273\351\207\217\346\216\250\347\220\206\346\211\213\350\256\260.md" "b/source/_posts/\350\275\273\351\207\217\346\216\250\347\220\206\346\211\213\350\256\260.md" deleted file mode 100644 index 2d4b1a2..0000000 --- "a/source/_posts/\350\275\273\351\207\217\346\216\250\347\220\206\346\211\213\350\256\260.md" +++ /dev/null @@ -1,222 +0,0 @@ ---- -title: "轻量推理手记" -date: 2025-07-22 14:32:56 -updated: 2025-11-13 04:42:20 -mathjax: true -tags: - - 深度学习 -categories: 深度学习 -comments: false ---- -# 目前的项目结构 - -```bash -├── bench.py -├── example.py -├── LICENSE -├── llm_serving.py -├── nanovllm -│   ├── config.py -│   ├── engine -│   │   ├── async_llm_engine.py -│   │   ├── block_manager.py -│   │   ├── llm_engine.py -│   │   ├── model_runner.py -│   │   ├── scheduler.py -│   │   └── sequence.py -│   ├── layers -│   │   ├── activation.py -│   │   ├── attention.py -│   │   ├── embed_head.py -│   │   ├── layernorm.py -│   │   ├── linear.py -│   │   ├── rotary_embedding.py -│   │   └── sampler.py -│   ├── llm.py -│   ├── models -│   │   ├── llama.py -│   │   └── qwen3.py -│   ├── sampling_params.py -│   └── utils -│   ├── context.py -│   └── loader.py -├── pyproject.toml -├── README.md -├── serving_bench.py -└── uv.lock -``` - -离线推理时候 main函数实例化 LLMEngine 在rank0上init,init时候现在副卡init ModelRunner 然后进入loop 阻塞在 shm的read上。然后在rank0上init ModelRunner 不进入loop。在main中调用 -LLMEngine的generate方法 在里面call run函数,call的时候卡0把run写进shm 副卡read读出输出不再阻塞 然后副卡根据shm读出的call什么 也调用call 所有卡一起推理。也就是执行CausalModel的forward得到logits 这时候分成几份在多个卡上,然后最后LMHead将 hiddenstate * 最后的分类头 得到每个token的分数 logits 然后 gather到卡0上。然后交给采样器跟觉策略进行采样。 -# 待实现的功能 - -- [x] 实现 flash_attention @Triton - ![](https://cdn.jsdelivr.net/gh/zip95297/zip95297.github.io@main/source/images/%E8%BD%BB%E9%87%8F%E6%8E%A8%E7%90%86%E6%89%8B%E8%AE%B0/Pasted%20image%2020260317225553.webp?raw=true) - compute bound -- [x] 实现 paged_attention_v1 @Triton 取所有kv -- [x] 实现 paged_attention_v2 @Triton 对kv做online softmax -- [x] 实现 flash_decoding @Triton - @Triton 并行计算 m l acc 然后 @Triton reduction 但是要申请空间 最开始按照blockn 64做 OOM了 - 发现应该是按照split 对seq分块,split num 可以根据 context len 切换? - ![](https://cdn.jsdelivr.net/gh/zip95297/zip95297.github.io@main/source/images/%E8%BD%BB%E9%87%8F%E6%8E%A8%E7%90%86%E6%89%8B%E8%AE%B0/Pasted%20image%2020260317230029.webp?raw=true) - ![](https://cdn.jsdelivr.net/gh/zip95297/zip95297.github.io@main/source/images/%E8%BD%BB%E9%87%8F%E6%8E%A8%E7%90%86%E6%89%8B%E8%AE%B0/Pasted%20image%2020260317230155.webp?raw=true)reduce 的代价很高 -- [x] Nsight System 分析性能瓶颈 - ![](https://cdn.jsdelivr.net/gh/zip95297/zip95297.github.io@main/source/images/%E8%BD%BB%E9%87%8F%E6%8E%A8%E7%90%86%E6%89%8B%E8%AE%B0/Pasted%20image%2020260317225947.webp?raw=true) - 程序调度的代价很高 -- [x] 实现 OnlineInfer、 Countinuous Batching -- [x] 实现 KV Cache量化 -- [1] 实现 Chunked Prefill ,调度规则 (flash -- [x] RMSLayerNrom @Triton 还有什么用Triton写,分析一下 linear? -- [x] 支持 llama -- [x] CUDAGraph - -# 以后有时间了做 - -- [ ] KVCache 的卸载 和 Recomputation -- [ ] 实现 KV Cache 异步加载 -- [ ] 实现 AWQ 量化支持 -- [ ] 实现 投机采样 -- [ ] 实现 PD 分离 -- [ ] 支持 Deepseek -- [ ] 支持 Qwen3-MoE -- [ ] TP 之后实现 PP - -# flash_attention - -输入是 QKV 计算 O 用 online softmax 更新 QKVO的维度是 seqlen,headnum,headdim - -用triton并行的时候分为三个维度: - 1. 对输入Q的seqlen按照BLOCKM分块 - 2. 对 Q 的头分块 - -在每个program里面的操作维度是 tl.arange(0,head_dim) - -然后注意写triton是要 梳理 tensor的shape, 标注清楚 offset ,取两个维度时候注意标注 `[None,:]` - -# page_attention - -按照 batch_size,head_num 两个维度 并行,并行的时候 对 context 中的 所有 kvcache 块 中的 token 逐个遍历 增量更新 O ,最开始的计算方式是想把所有context 中的kv全部中上下文中拿出来,但是这样设计的显存访问代价过高,所以更改设计方式,把 contextlen 按照 BLOCK_M 进行划分 然后对 BLOCKM中的每个token依次softmax 但是还是很慢。不如直接对BLOCKM中一个kv矩阵并行处理,计算QK 然后增量更新。 - -最开始是对每个token逐个online softmax 后来分块算 - -# flash_decoding - -在 pageAttn 基础上 对每个 decode token 的 context 分split 按 split 并行计算 o 然后最后 reduce - -`flash_decoding_kernel`, `flash_decoding_reduce_kernel`, `flash_decoding` 最后集成进一个函数 - -问题:最开始 设计的时候遇到一个问题,split size 设置成了 64 目的是为了减少 对 L2 cache的访问,但是实现后发现,这样的虽然提高了 qk 计算 m l acc 的并行度,但是由于每个 layer 都要申请一段空间去存放临时结果,运行的时候如果保持 GPU 利用率 会导致 OOM 而且速度几乎没有提升,和预想的结果偏差较大。 - -分析原因发现,计算的并行度虽然提升了,但是 reduce 的代价会变高很多,因为临时结果有 max_context_len/BLOCK_N 个 reduce时候要依次遍历。而且由于BLOCKN很小,所以每个layer申请的内存空间较大。 - -解决办法:将 seqlen 按照 预设好的 split 划分成几个部分,然后每个program 处理 一个 split 所有kvcache 。这样做解决了 OOM 和 reduce 代价过大的问题。实现了真正的 flashdecoding。 - -没命中的 block 直接 把 attention 置为 0 当做无效块处理 - -遗留的问题 : -1. split 是不是可以根据 contextlen 动态调整 -2. 在调度的时候是不是可以优先调度上下文长度均匀的seq 这样可以避免一些 kernel 在空转? - - -# 实现 online infer , Stream Infer - -AsyncLLMEngine 是 llm engine 的一个 异步 wrapper -https://github.com/vllm-project/vllm/blob/main/vllm/v1/engine/async_llm.py -https://github.com/vllm-project/vllm/blob/main/vllm/v1/engine/llm_engine.py - -为了能异步获取seqdecode出的结果,对Sequence类加一个asyncio.queue,每次step decode出结果就加进来 - -先实现 (异步生成器函数)streaming generate 输入和 LLMEngine 保持同步,主要逻辑是这样: -``` -1. seq = await add_request 异步添加请求 简化模型 一个req 一个seq -2. 依次读 seq 中decode出的结果直到DONE - while True: 循环yield 直到结束 - item = await seq._queue.get() - if item == DONE : break - yield tokenizer.decode(item) -``` - -之后遍历这个就可以得到流式结果了,对于非流式的generate 其实就是流式的generate 最后把结果join起来。 - -现在addReq逻辑实则是同步的,虽然写成了一个协程函数 但是不async也可以。addReq会确认模型的运行循环是开着的。 - -还有需要注意的是,每次addreq时候,需要确认 主事件(engine的step) 还在 这样加入的请求才能被 不断的 schedule。但是现在的schedule策略是 prefill优先,之后可以调整一下。 - -Continuous Batching 就是 模型 每次step后 都要 调度 主要是为了结束 finish 的 seq(也就是decode完成的 seq)的生命周期,不要占着batch空转 - - -# Chunked Prefill 调度规则 - -vllm中的 scheduler -https://github.com/vllm-project/vllm/blob/main/vllm/v1/core/sched/scheduler.py -``` - # NOTE(woosuk) on the scheduling algorithm: - # There's no "decoding phase" nor "prefill phase" in the scheduler. - # Each request just has the num_computed_tokens and - # num_tokens_with_spec. num_tokens_with_spec = - # len(prompt_token_ids) + len(output_token_ids) + len(spec_token_ids). - # At each step, the scheduler tries to assign tokens to the requests - # so that each request's num_computed_tokens can catch up its - # num_tokens_with_spec. This is general enough to cover - # chunked prefills, prefix caching, speculative decoding, - # and the "jump decoding" optimization in the future. -``` -无阶段调度不分PD,先塞decode 再塞prefill 与 Chunked Prefill - -为什么要chunked prefill? 在没有实现 PD 分离 的情况下,如果来了一个很长的prompt ,prefill的耗时太高,会到时decode请求的latency增大。而且现有的 scheduler 的 结构 是 PD 分阶段完成,导致在decoding时候的计算资源利用率很低。所以将 请求 prefill 的prompt 拆分成chunk 然后 按照 decode优先,然后prefill chunk的顺序塞满 一个 batch。而且如果来了两个 一长一短的prompt 拆分成chunk也会提高短序列TTFT速度。 也可以理解成充分利用decode时候未被充分利用的计算资源 - -1. 先实现了 schedule 的调度策略 **调度完一个seq的最后一个chunk 就把它放到running队列了有问题** -2. blockmanager 中 对 chunk的 allocate -3. llmengine 中的 chunkstep 函数 -4. modelrunner的run函数 -5. run 调用的 prepare mixed batch函数 因为应用了对话模板 默认 prefill的请求长度不可能是1 -6. attention的计算不分开 怎么做一个 unified attention ,flashattention with KVcache -7. LMHead 对于混合batch 要进行特殊的处理 decode+最后一个chunk的token - -对context的组织也很难,还有一个seq传输序列化的时候忘记往getstate里面写status属性了 - -先把两种请求留在一个batch里面但是分开做 attention 吧,分成两种Attn和合成一种有什么区别? -先分开做的:prefill的部分用flashAttnwithKVCache 然后 decode的部分用 flashdecoding - -这两个block manager 很不一样 chunkedprefill因为在 prefill 的时候要用到cache 所以 seq的num cached tokens 要在post process里面更新。 - -这个和PD 分阶段计算的时候blockmanager分配的策略不一样,分阶段的时候,是在allocate直接就给seq的numcachedtoken加好,但是用了chunkedprefill的话会导致,inputids直接变成0 -![](https://cdn.jsdelivr.net/gh/zip95297/zip95297.github.io@main/source/images/%E8%BD%BB%E9%87%8F%E6%8E%A8%E7%90%86%E6%89%8B%E8%AE%B0/Pasted%20image%2020260330011123.webp?raw=true) -![](https://cdn.jsdelivr.net/gh/zip95297/zip95297.github.io@main/source/images/%E8%BD%BB%E9%87%8F%E6%8E%A8%E7%90%86%E6%89%8B%E8%AE%B0/Pasted%20image%2020260330011131.webp?raw=true) -cpu调度代价太高 - -# KVCache量化 int 8 - -在前向时候 把每个kv 量化存入cache int8 : -127 127 -vec / max(|vec|) * 127 这个量化方法就是把原本的数放缩到 int8 的全部空间,然后记录scale 就是 max值 -最开始将全部kvcache进行反量化,但是发现可能会OOM 而且计算速度特别慢 增大了memoryAccess的负担。所以将反量化的过程融合进 flashdecoding内,尽可能的保证了速度。 - -这个在做的时候 发现,一开始是全量 dequant出来,既不省内存 而且非常耗时。所以改成在flashdecoding时候再dequant出来 - -# RMSnorm 用triton实现 - -原本使用torch.compile 然后自己实现triton的时候 -x = x + residual 按行分块做 把x给view 最后总吞吐反而下降了,分析之后发现,x+res访存太耗时了, -所以 应该写融合算子 读 add 写 res的操作都在里面实现。 -但是现在并行度是整个 输入的第一个维度(view(-1,x.shape\[-1\]))如果尝试分块会不会效果更好? - -对输入向量的最后一个维度。但是并行度有点高,kernel launch overhead 会不会有点高? 解决这个可以靠 融合算子,cudaGraph等烧录 - - - -# 支持llama - -gate_up_proj 等有几个要在 modelforCausalLLM里面写进一个字典里。否则load会出错 -Attention里面的头数是一个卡的头数!!!!!!!!!发现单卡没问题 多卡报错 -basemodel就是纯自回归补全 instructmodel 才应该应用对话模板 - -# Thinking - -Decode 时候因为要访问全部的上下文 所以Decode的时间复杂度应该是On,但是有以下面对办法: - - Sliding Window Attention 只看最近W个token前面的扔了 - - KV Compression / Pooling 把前面的多个kv 进行压缩 - - Sparse Attention 稀疏注意力 只关注部分token - - Linear Attention(核方法)、Retrieval / Memory 模型 - -为什么用triton \ CUDA 而不是torck的张量广播? - Triton 能精准控制 GPU 硬件层级的内存访问和计算调度,把注意力计算的核心逻辑「塞进 L1/L2 Cache」完成,彻底降低对高延迟 HBM(显存)的访问;而 PyTorch 张量广播的并行是「逻辑层」的,无法精细控制硬件缓存,最终仍会导致大量低效的 HBM 访问。 diff --git "a/source/_posts/\350\275\273\351\207\217\346\216\250\347\220\206\346\241\206\346\236\266.md" "b/source/_posts/\350\275\273\351\207\217\346\216\250\347\220\206\346\241\206\346\236\266.md" deleted file mode 100644 index 902fc55..0000000 --- "a/source/_posts/\350\275\273\351\207\217\346\216\250\347\220\206\346\241\206\346\236\266.md" +++ /dev/null @@ -1,86 +0,0 @@ ---- -title: "轻量推理框架" -date: 2025-09-02 04:24:04 -updated: 2025-09-05 02:33:12 -mathjax: true -tags: - - 深度学习 -categories: 深度学习 -comments: false ---- -这是一份针对 EchoLLMServing 推理引擎架构及核心算子优化的工程笔记梳理。内容已针对技术深度、架构权衡(Trade-off)以及实现细节进行了重构,,并补充了底层逻辑分析。 - ---- - -# EchoLLMServing 推理框架笔记 - -## 1. 架构设计与分布式执行逻辑 - -当前项目采用了多进程架构支持张量并行(Tensor Parallelism),控制流与数据流分离: -* **控制平面**:Rank 0 作为主节点(`LLMEngine`),负责请求接收、调度(Scheduler)和元数据管理(可结合 Gloo 进行跨进程协调)。 -* **数据平面**:副卡初始化 `ModelRunner` 并阻塞于共享内存(shm)或类似 ZeroMQ 的异步队列等待指令。 -* **执行流**:Rank 0 在 `generate` 中调用 `run`,将计算元信息写入 shm,触发所有 Rank 并行执行前向传播。在 LMHead 阶段,各卡计算出局部的 logits 后,通过 All-Reduce 或 Gather 汇总到 Rank 0,由采样器(Sampler)根据策略输出 Token。 - -**架构审视**:这种主从架构简化了调度的复杂度,但在大规模并发下,Rank 0 的调度开销可能成为瓶颈。引入 CUDA Graph 是降低调度开销的有效手段,通过捕获算子执行序列,能够显著减少 Kernel Launch Overhead。 - -## 2. 核心算子实现与优化 (@Triton) - -### FlashAttention -* **定位**:解决 Prefill 阶段的 Compute Bound 问题。 -* **实现逻辑**:对输入 Q 的 `seq_len` 按 `BLOCK_M` 分块,对外层 Head 分块。在 Triton program 内处理 `tl.arange(0, head_dim)` 的向量化计算。 -* **Trade-off**:虽然极大地提升了计算密度,但对 SRAM 容量要求高。`BLOCK_M` 的大小受限于硬件的 Shared Memory 上限。 - -### PagedAttention (V1 -> V2 演进) -* **V1 的问题**:最初尝试将 context 中的全部 KV 取出再计算,导致严重的 Memory Bound。显存带宽被非连续的访存彻底打满,吞吐极低。 -* **V2 的改进**:将 context_len 按 `BLOCK_M` 划分,对 KV 矩阵分块并行计算 QK,进行 Online Softmax 增量更新。减少了不必要的显存搬运,将数据尽可能留在 SRAM 中处理。 - -### FlashDecoding (长序列 Decode 优化) -在长上下文 Decode 时,传统的 PagedAttention 仍面临带宽瓶颈。引入 Split-K 机制与双 Kernel 架构优化: -* **OOM 与 Reduce 代价分析**:最初将 split size 固定为 64,导致每个 block 都会产生临时结果(`m`, `l`, `acc`)。如果并行度过高且 `BLOCK_N` 很小,申请的 Global Memory 空间会急剧膨胀导致 OOM,且最终的 Reduce Kernel 需要遍历巨大的临时数组,抵消了并行带来的收益。 -* **解决方案**:将 `seq_len` 按照预设的 split 划分为几个 chunk,每个 program 处理一个 split 内的所有 KV Cache。 - -**针对笔记中遗留问题的解答:** -* **问题:Split_num 是否可以根据 `context_len` 动态调整?** - * **解答**:必须动态调整。固定的 split_num 会导致短序列的 kernel 切分过细(增加 kernel 启动和 reduce 开销),而长序列切分不足(无法充分利用 SM)。通常的启发式策略是:`split_num = max(1, context_len // optimal_chunk_size)`,其中 `optimal_chunk_size` 根据 GPU 的 SM 数量和 Cache 大小在 Profiling 中动态标定(例如 256 或 512)。 -* **问题:调度时是否优先调度上下文长度均匀的 Seq 以避免 Kernel 空转?** - * **解答**:是的。在未实现完全 Unified Attention 的情况下,同 Batch 内序列长度差异过大会导致严重的 Padding 计算浪费或线程束(Warp)分化。通过持续批处理(Continuous Batching)结合细粒度的 Block 管理,可以最大程度缓解这个问题。 - -### Fused Add-RMSNorm 与 Triton 拓展 -* **问题分析**:使用 PyTorch 原生的 `x = x + residual` 然后做 RMSNorm 时,发现总吞吐下降,通过 Nsight Systems (nsys) 分析证实,内存拷贝与读写开销(Memory Access)是主因。 -* **Triton 实现权衡**: - * 写融合算子,将读、Add、写 Res、Norm 全部在一个 Kernel 内完成。 - * **关于分块与 Kernel Launch Overhead**:对输入向量的最后一个维度(`hidden_dim`)进行处理。如果 `hidden_dim` 过大(如 Llama 的 4096),可以将其拆分为几个 Block。并行度高导致的 Launch Overhead 可以通过 CUDA Graph 解决。关键在于利用 `tl.load` 时的向量化读写(保证首地址对齐)。 - -## 3. 调度、显存管理与特性支持 - -### 异步推理与 Continuous Batching -AsyncLLMEngine 通过 `asyncio.queue` 实现了请求的非阻塞生成。Continuous Batching 的核心价值在于打破了静态 Batch 的生命周期绑定,每次 Step 后立即回收完成状态的 Seq 所占用的 Block 资源,并打入新请求,极大提升了吞吐。 - -### Chunked Prefill 调度规则 -* **核心动机**:解决长短 Prompt 混杂时的 Time to First Token (TTFT) 延迟毛刺,以及分离阶段导致 Decode 计算资源利用率低的问题。 -* **Block Manager 状态不一致问题**:Chunked Prefill 会导致 `num_cached_tokens` 与实际输入的 `input_ids` 长度在单次 forward 中不匹配,必须在 post-process 中进行增量状态维护。 - -**针对笔记中遗留问题的解答:** -* **问题:混合 Batch 时,分开做两种 Attn 和合成一种(Unified Attention)有什么区别?** - * **解答**: - 1. **分开做(分离 Kernel)**:工程实现简单。Prefill 的请求走 FlashAttention,Decode 的请求走 FlashDecoding。缺点是 Batch 较小时,两个 Kernel 依次 Launch,可能无法跑满 GPU 的 SM,存在硬件资源的波谷。 - 2. **合成一种(Unified Attention)**:将 Decode 视为长度为 1 的特殊 Prefill 阶段,使用一套底层机制处理。这要求极高的指针与索引管理技巧(类似 FlashInfer 的设计)。优点是只需 Launch 一个 Kernel,SM 占用率极高;缺点是分支预测和 Warp 分化处理非常复杂。前期建议分开做,后期追求极限性能时再考虑 Unified。 - -### KV Cache INT8 量化 -* **工程经验**:最初全量反量化(Dequantization)导致严重的显存读写墙,耗时极高。 -* **正确路径**:将 Per-token 反量化逻辑直接 Fuse 进 FlashAttention/FlashDecoding Kernel 内。在读取 INT8 Cache 到 SRAM 后,立即使用伴随的 Scale 因子还原为 FP16 进行计算。这一操作以极小的计算开销(Compute)换取了 50% 的显存带宽(Bandwidth)与容量收益。 - -## 4. 底层架构思考 (Thinking & Trade-offs) - -### Decode 阶段 $O(N)$ 复杂度缓解策略 -Decode 本质上是 Memory Bound,复杂度随上下文线性增长。常见缓解方案: -* **算法层**:Sliding Window Attention (SWA)、KV 压缩/池化(如 H2O)、稀疏注意力。 -* **系统层**:前缀缓存(RadixTree LRU Prefix Cache),将公共的前缀(如 System Prompt)复用,避免重复计算并节省 KV Cache 空间。 - -### 为什么必须用 Triton/CUDA 代替 PyTorch 张量广播? -PyTorch 的张量广播发生在**逻辑层和 Global Memory 层面**。它会产生大量隐式的中间张量,导致 HBM 被反复读写(Memory Wall)。Triton 和 CUDA 的核心优势在于**显存层级控制**:将高复杂度的 $O(N^2)$ 计算约束在 L1/L2 Cache 或 SRAM 中,将对外部 HBM 的访问降低到 $O(N)$ 级别。在算力远大于带宽的 Ampere/Hopper 架构上,这是唯一解。 - -### 还有什么值得用 Triton 优化的算子? -* **Fused RoPE + QKV 投影**:将旋转位置编码直接融合进 QKV 的 Linear 计算之后,避免显存的来回搬运。 -* **Fused SwiGLU (或 GeLU)**:激活函数通常是单纯的 element-wise 操作,典型的 Memory Bound,非常适合融合。 -* **MoE Top-K Routing**:如果未来支持 Qwen3-MoE,专家路由的 Top-K 计算、Softmax 及 token 重排,使用 Triton 可以大幅降低开销。 diff --git "a/source/_posts/\351\242\230\347\233\256\347\254\224\350\256\260.md" "b/source/_posts/\351\242\230\347\233\256\347\254\224\350\256\260.md" deleted file mode 100644 index 751d8f0..0000000 --- "a/source/_posts/\351\242\230\347\233\256\347\254\224\350\256\260.md" +++ /dev/null @@ -1,5518 +0,0 @@ ---- -title: "LeetCode题目笔记" -date: 2025-12-12 13:50:46 -updated: 2025-12-12 13:50:46 -mathjax: true -tags: - - LeetCode - - Job -categories: 实用技巧 -comments: true ---- -这个笔记用来记录 第一次刷 LeetCode Hot 100 -# 1. [两数之和](https://leetcode.cn/problems/two-sum/)1-251210 - -tag: 哈希表 - -```python -class Solution: - def twoSum(self, nums: List[int], target: int) -> List[int]: - dic = {} - for i in range(len(nums)): - residual = target - nums[i] - if nums[i] in dic.keys(): - return [i, dic[nums[i]]] - if residual not in dic.keys(): - dic[residual] = i -``` - -对每个数 留下 索引(value) 以及 到 target 的差(index),之后遇到 target差 的数,直接读出索引就行 - -# 2. [字母异位词分组](https://leetcode.cn/problems/group-anagrams/)49-251210 - -tag: 哈希表 排序 - -## 思路一:哈希 - -```python -class Solution: - - def groupAnagrams(self, strs: List[str]) -> List[List[str]]: - map = {} - for item in strs: - v = self.str2value(item) - if v in map.keys(): - map[v].append(item) - else: - map[v] = [item] - return list(map.values()) - - def str2value(self, stR:str) -> int : - res = 0 - for char in stR: - index = ord(char)-ord('a') - res += 10**(index) - return res*len(stR) -``` - -第一次的思路就是 把一个 str 得到一个顺序无关的哈希值。但是复杂度好像有点高。 - -更好的实现方式:使用了 `from collections import defaultdict` 内置函数 - -使用 defaultdict 是内置 dict 的子类,核心特性是,当访问不存在的键时,会自动创建这个键并赋值为 **指定默认值** 而不是抛出 KeyError。例如:初始化 `mp=collections.defaultdict(list)` 如果访问了不存在的键就会默认为空list (传入int就是默认0) - -还有一个 dict 的索引不能是 list dict set,可以是int float tuple str bool None frozenset ,list 可以转为tuple - -```python -class Solution: - def groupAnagrams(self, strs: List[str]) -> List[List[str]]: - dic = collections.defaultdict(list) - for s in strs: - count = [0] * 26 - for c in s: - count[ord(c)-ord('a')]+=1 - # 这一步的时间复杂度是 O(k)+1 ,k=26 tuple生成这个hash值需要 list长度的时间 - dic[tuple(count)].append(s) - return list(dic.values()) -``` - -这个方法时间复杂度是 O(n(k+s)) s是字符集的大小 26,每个 str 要 k 来遍历字符, s来生成 hash表的键。 - -## 思路二-排序\* 最优 - -对每个str排序,然后将排序的这个 字符串 当做字典的索引(哈希值)时间复杂度是 O(nklogk) - -内置函数 `sorted` 函数 输入字符串 返回一个按照 unicode 编码的 char list -''.join(list(char)) 把charlist 拼成一个串 注意这个join是 字符串的子函数 - -```python -class Solution: - def groupAnagrams(self, strs: List[str]) -> List[List[str]]: - dic = collections.defaultdict(list) - for s in strs: - index = ''.join(sorted(s)) - dic[index].append(s) - return list(dic.values()) -``` - -# 3. [最长连续序列](https://leetcode.cn/problems/longest-consecutive-sequence/)128-251210 - -tag: 并查集 set - -这个题要求了时间复杂度为O(n) 所以不考虑排序的方法,首先用 set 对 nums 去重,然后找到 所有序列开头的数字 并计算 这个序列的长度,**由于每个数仅进入一次内层循环**,所以时间复杂度符合要求。 - -```python -class Solution: - def longestConsecutive(self, nums: List[int]) -> int: - res = 0 - nums_set = set(nums) - for i in nums_set: - if i-1 in nums_set: - continue - # 否则说明这个数是 一个序列的开头 - l = 1 - p = i+1 - while p in nums_set: - l+=1 - p+=1 - res = max(res,l) - return res -``` - -下面这个节省20ms的时间 -```python -class Solution: - def longestConsecutive(self, nums: List[int]) -> int: - res = 0 - nums_set = set(nums) - for i in nums_set: - if i-1 in nums_set: - continue - # 否则说明这个数是 一个序列的开头 - p = i+1 - while p in nums_set: - p+=1 - res = max(res,p-i) - return res -``` - -这个算法的关键是找到起点 避免对一个数的重复遍历 -list 转 set 还有 判断 in set 的操作 时间复杂度是 O(1), 如果是用 list 的 in 时间复杂度是 O(n) - -# 4. [移动零](https://leetcode.cn/problems/move-zeroes/)283-251210 - -tag: 双指针 快慢指针 - -## 思路一 遇到零放后面 - -list : remove是移除 第一个 value,pop是删掉指定index的item -顺序遍历,把0移到最后 - -```python -class Solution: - def moveZeroes(self, nums: List[int]) -> None: - """ - Do not return anything, modify nums in-place instead. - """ - i=0 - last = len(nums) - while i < last : - if nums[i] == 0 : - nums.pop(i) - nums.append(0) - last-=1 - i-=1 - i+=1 -``` - -## 思路二 栈 遇到非零压入栈 - -```python -class Solution: - def moveZeroes(self, nums: List[int]) -> None: - stack_size=0 - for num in nums: - if num : - nums[stack_size] = num - stack_size+=1 - nums[stack_size:len(nums)] = [0]*(len(nums)-stack_size) -``` - -这个方法在最坏的情况下(全是0) 要遍历两次数组 - -## 思路三 双指针\* 最优 - -依次把非零元素移动到 数组靠左边的空位置上。参考快速排序的想法,快拍要确定一个待分割的元素x作为中间点,然后小于等于x放到左边,大于x放到右边。 - -一个指向第一个0 另一个遇到第一个非零就和第一个0换位置,然后指向第一个0的后移一位(仍然是第一个0) (这个理解好像不太好) - -换一个想法,两个指针,慢的说明 其左边 全部都是 保留顺序的非零(也就是指向待处理序列的第一个,只有交换了也就是处理好了,才移动),快的去找下一个需要被处理的非零数字。这样保留了原本的顺序。 - -```python -class Solution: - def moveZeroes(self, nums: List[int]) -> None: - slow=fast=0 - while fast < len(nums): - if nums[fast]: - nums[slow],nums[fast]=nums[fast],nums[slow] - slow+=1 - fast+=1 -``` - -# 5. [盛最多水的容器](https://leetcode.cn/problems/container-with-most-water/)11-251210 - -tag: 双指针 - -首先对于壁 i int: - ans=0 - i,j = 0,len(height)-1 - while i!=j : - ans = max(self.S(i,j,height),ans) - if height[i] int: - l, r = 0, len(height) - 1 - ans = 0 - while l < r: - # w = r - l - h = min(height[l], height[r]) - ans = max(ans, (r - l) * h) - while height[l] <= h and l < r: l += 1 - while height[r] <= h and l < r: r -= 1 - return ans -``` - - -通过节省搜索空间的想法,还可以进一步,通过记录高度的最大值,得到最大的S,然后直接return - -```python -class Solution: - def maxArea(self, height: List[int]) -> int: - max_area = 0 - l, r = 0, len(height) - 1 - max_height = max(height) - - while l < r: - if max_height * (r - l) < max_area: - return max_area - cur_area = min(height[l], height[r]) * (r - l) - if cur_area > max_area: - max_area = cur_area - if height[l] > height[r]: - r -= 1 - else: - l += 1 - return max_area -``` - -# 6. [三数之和](https://leetcode.cn/problems/3sum/)15-251211 - -tag: 双指针 - -最简单的想法 对于每个数 当做一个两数之和来做,但是由于两数之和的解法复杂度是 $O(n)$,这种做法的复杂度是 $O(n^2)$. - -还有一种是用双指针,来找和为 0 的 也是最优的。 - -```python -class Solution: - def threeSum(self, nums: List[int]) -> List[List[int]]: - nums.sort() - ans = [] - n = len(nums) - for i in range(n - 2): - x = nums[i] - if i > 0 and x == nums[i - 1]: # 跳过重复数字 - continue - if x > 0 or nums[-1]<0 : # 优化一 - break - if x + nums[-2] + nums[-1] < 0: # 优化二 - continue - j = i + 1 - k = n - 1 - while j < k: - s = x + nums[j] + nums[k] - if s > 0: - k -= 1 - elif s < 0: - j += 1 - else: # 三数之和为 0 - ans.append([x, nums[j], nums[k]]) - j += 1 - while j < k and nums[j] == nums[j - 1]: # 跳过重复数字 - j += 1 - k -= 1 - while k > j and nums[k] == nums[k + 1]: # 跳过重复数字 - k -= 1 - return ans -``` - -这个方法和 第一个复杂度都是$O(n^2)$ 但是双指针要快一些啊,是因为 枚举 + 哈希表法虽是 O (n²),但哈希表的额外开销、随机内存访问导致常数项远大于双指针;1. 双指针法的「$O(n^2)$」是**真 $O(n^2)$**(外层 n 次,内层 n 次),且常数项极小; - -# 7. [接雨水](https://leetcode.cn/problems/trapping-rain-water/)42-251211 - -tag: 单调栈 动态规划 双指针 - -## 思路一:单调栈\* - -最开始的思路是 遍历所有h 而不是index 有一个问题就是,如果相等的话之后来了更高的没办法记录宽度,所以尝试用index做一下 - -```python -class Solution: - def trap(self, height: List[int]) -> int: - stack = [] - ans = 0 - for i,h in enumerate(height) : - while stack and h > height[stack[-1]] : - top = stack.pop() - if not stack : - break - # 这个是重点部分 好好想一下 - ans += (min(height[stack[-1]],h)-height[top])*(i-stack[-1]-1) - stack.append(i) - return ans -``` -这个部分我觉得最难的是 每次加多少面积,由于单调栈有单调递减的特征,所以栈顶元素一定是最小的,计算的思路就是 记录 每次pop 添加的水 是一排 就是接雨水 水平线往上的? - -## 思路二:动态规划 - -每个位置 i 能接的水的数量 = min( i 左边的最大高度, i 右边的最大高度) - h\[i\] ,所以暴力搜索就是: -```python -class Solution: - def trap(self, height: List[int]) -> int: - ans = 0 - l = len(height) - dp = [0] * l - # 暴力 - for i in range(1,l-1) : - dp[i] = max(min(max(height[0:i]),max(height[i+1:])) - height[i],0) - return sum(dp) -``` - -在内循环中,在数组中找max操作复杂度为 $O(n)$ , 所以这个暴力搜索的复杂度为 $O(n^2)$ ,通过 动态规划的思路 先用两个list: $O(n)$ 来记录 index i 左边的最大和右边的最大height是多少。 - -```python -class Solution: - def trap(self, height: List[int]) -> int: - l = len(height) - ans = [0] * l - # 左侧最大值 不包括 - leftMax = [0] * l - # 右侧最大值 不包括 - rightMax = [0] * l - maxh = 0 - maxr = 0 - # 这里也可以用动态规划的状态转移方程 会更快 - for i in range(l) : - leftMax[i] = maxh - maxh = maxh if maxh > height[i] else height[i] - rightMax[l-1-i] = maxr - maxr = maxr if maxr > height[l-1-i] else height[l-1-i] - for i in range(1,l-1) : - # ans[i] = max(min(max(height[0:i]),max(height[i+1:])) - height[i],0) - ans[i] = max(min(leftMax[i],rightMax[i]) - height[i],0) - return sum(ans) -``` - -## 思路三:双指针 - -本质上和动态规划类似,只是用双指针的方法可以把空间复杂度减到O1,时间复杂度还是 $O(n)$ - -```python -class Solution: - def trap(self, height: List[int]) -> int: - ans = 0 - left, right = 0, len(height) - 1 - leftMax = rightMax = 0 - while left < right: - leftMax = max(leftMax, height[left]) - rightMax = max(rightMax, height[right]) - if height[left] < height[right]: - ans += leftMax - height[left] - left += 1 - else: - ans += rightMax - height[right] - right -= 1 - return ans -``` - -# 8. [无重复字符的最长子串](https://leetcode.cn/problems/longest-substring-without-repeating-characters/)3-251212 - -tag: 滑动窗口 - -这个比较简单,可以学习一下别人的代码思路。下面这个是我的。 - -```python -class Solution: - def lengthOfLongestSubstring(self, s: str) -> int: - ans,i,l = 0,0,len(s) - window = collections.deque() - count = collections.defaultdict(int) - while i < l : - while i < l and count[s[i]] == 0 : - window.append(s[i]) - count[s[i]] += 1 - i += 1 - ans = max(len(window),ans) - while i < l and count[s[i]]!=0 : - f = window.popleft() - count[f] -= 1 - return ans -``` - -用 set(字典本身就适合处理 是否重复这样的事情) -```python -class Solution: - def lengthOfLongestSubstring(self, s: str) -> int: - # 滑动窗口 - left = 0 - window = set() - max_length = 0 - for right in range(len(s)): - while s[right] in window: - window.remove(s[left]) - left += 1 - window.add(s[right]) - if len(window) > max_length: - max_length = len(window) - return max_length -``` - -这个2ms 最快 -```python -class Solution: - def lengthOfLongestSubstring(self, s: str) -> int: - n = len(s) - if n <= 1: - return n - _dict = {} - start = -1 - res = 0 - for i, c in enumerate( s ): - if c in _dict and _dict[c] > start: - start = _dict[c] - res = max(res, i - start) - _dict[c] = i - return res -``` - -# 9. [找到字符串中所有字母异位词](https://leetcode.cn/problems/find-all-anagrams-in-a-string/)438-251212 - -tag: 滑动窗口 - -自己写的解 -```python -class Solution: - def findAnagrams(self, s: str, p: str) -> List[int]: - ans = [] - count = {} - window = collections.deque() - for c in p : - count[c] = 0 - for c in p : - count[c] -= 1 - count['other'] = 0 - start = 0 - for c in s : - window.append(c) - if c in count.keys() : - count[c] += 1 - while count[c] > 0 : - f = window.popleft() - start += 1 - count[f] -= 1 - else : - count['other'] += 1 - while count['other'] != 0 : - f = window.popleft() - start += 1 - if f in count.keys() : - count[f] -= 1 - else : - count['other'] -= 1 - if len(window) == len(p) : - ans.append(start) - return ans -``` - -# 10. [和为 K 的子数组](https://leetcode.cn/problems/subarray-sum-equals-k/)560-251212 - -tag: 前缀和 - -维护一个前n项和 sumn 然后 如果遇到 现在的 sumn - k 存在(x个) 那么就 有x个子数组符合 -```python -class Solution: - def subarraySum(self, nums: List[int], k: int) -> int: - ans,sumn = 0, 0 - cnt = collections.defaultdict(int) - for n in nums: - cnt[sumn] += 1 - sumn += n - ans += cnt[sumn - k ] - return ans -``` - -直到思路之后写了一次也没写对,当成两数之和了,存了index 应该是维护一个sumn为一个数的序列个数,之后再做一次 - -# 11. [滑动窗口最大值](https://leetcode.cn/problems/sliding-window-maximum/)239-251215 - -tag: 大根堆 / 单调队列 优先队列 - -## 思路一:大根堆 - -对于最大值这种问题,可以 维护一个 优先队列(大根堆)来做,也就是放入新元素是按照 我们想要的顺序存好。这个思路很容易想到,要善用这个数据结构。暴力的方法是,每次加入新元素就用$O(k)$ 来找一下窗口中的最大值,但是每个元素在整个流程中几乎被遍历k次,可以节省时间的方法就是记录在窗口中的元素的大小信息。也就是排序,但是如果每次来新的都排序,时间反而大于$O(k)$,因此需要我们维护一个 大根堆来做。在python中 最小堆是 :heapq.heapify(list) 可以将一个list转换成最**小**堆 这个只保证**堆顶是 最小的** 而不是有序的 - -```python -class Solution: - def maxSlidingWindow(self, nums: List[int], k: int) -> List[int]: - maxheap = [(-nums[i],i) for i in range(0,k) ] - # 这个python 标准库的最小堆 - heapq.heapify(maxheap) - ans = [ -maxheap[0][0] ] - for i in range(k,len(nums)) : - heapq.heappush(maxheap,(-nums[i],i)) - while maxheap[0][1] < i-k+1 : - heapq.heappop(maxheap) - ans.append(-maxheap[0][0]) - return ans -``` - -## 思路二:单调队列(优先队列)\* - -对于下标 $i List[int]: - q = collections.deque() - ans = [] - for i in range(0,len(nums)) : - while q and nums[i] >= nums[q[-1]] : - q.pop() - q.append(i) - while q[0] <= i-k : - q.popleft() - if i >= k-1 : - ans.append(nums[q[0]]) - return ans -``` - -维护了这样的单调递减序列,保证 开头是最大的,而且开头还得在窗口中。 - -# 12. [最小覆盖子串](https://leetcode.cn/problems/minimum-window-substring/)76-251215 - -tag: 滑动窗口 - -```python -class Solution: - def minWindow(self, s:str, t: str) -> str: - # 维护一个缺少多少的表 - w = collections.deque() - rq = {} - ansl ,l = 9999999,0 - ans = '' - for c in t : - if c in rq.keys() : - rq[c] += 1 - else : - rq[c] = 1 - # 处理队列,保证 rq 都得是 0 - for c in s : - w.append(c) - l += 1 - if c in rq.keys() : - rq[c] -= 1 - while w : - if w[0] in rq.keys() : - print(rq[w[0]]) - if w[0] in rq.keys() : - if rq[w[0]] >= 0 : - break - else : - rq[w[0]] += 1 - w.popleft() - l -= 1 - jump = False - for i in rq.values() : - if i > 0 : - jump = True - if ansl > l and jump == False : - ansl = l - ans = ''.join(w) - return ans -``` - -# 13. [最大子数组和](https://leetcode.cn/problems/maximum-subarray/)53-251215 - -tag: 动态规划 分治法 前缀和 做题目的时候不要被刷过的题目的思路限制住方向 也要做新的想法尝试 - -## 思路一:DP - -以 index 为 **结尾** 的最大子序列和 -```python -class Solution : - def maxSubArray(self, nums: List[int]) -> int : - # 以index 为结尾的最大值 - dp = [0] * len(nums) - for i,n in enumerate(nums) : - dp[i] = max(dp[i-1]+nums[i],nums[i]) - return max(dp) -``` - -## 思路二:分治法(还没学1216) - -官方题解说这个类似于 「线段树求解最长公共上升子序列问题」的pushup操作 - - - -## 思路三: 前缀和 - -参考 5盛水最多的容器中的动态规划方法,记录一个index左边最大和右边最小 -```python -# class Solution: -# def maxSubArray(self, nums: List[int]) -> int: -# # 一个index左边的最小值,一个index右边的最大值 -# leftMin,rightMax = [10001]*(len(nums)+1),[-10001]*(len(nums)+1) -# sn = [0] * (len(nums)+1) -# for i in range(len(nums)) : -# sn[i+1] = nums[i] -# for i in range(1,len(sn)) : -# sn[i] += sn[i-1] -# leftMin[0] = sn[0] -# rightMax[-1] = sn[-1] -# for i in range(1,len(sn)) : -# leftMin[i] = min(leftMin[i-1],sn[i]) -# rightMax[len(sn)-1-i] = max(rightMax[len(sn)-i],sn[len(sn)-1-i]) -# ans = -99999999 -# for i in range(len(sn)-1) : -# if ans < rightMax[i+1] - leftMin[i] : -# ans = rightMax[i+1] - leftMin[i] -# return ans -``` - -维护两个太慢了,因为这个主要的约束 是 最小值在最大值的右边,通过记录最小值,然后计算 s\[n] 和 min 的差 来更新 结果: - -```python -class Solution : - def maxSubArray(self, nums: List[int]) -> int : - tsum, tmin, ans = 0, 0, -99999 - for n in nums : - tsum += n - ans = max(ans,tsum-tmin) - tmin = min(tmin,tsum) - return ans -``` - -# 14. [合并区间](https://leetcode.cn/problems/merge-intervals/)56-251216 - -tag: 排序 数组 单调栈 - -这个重点在于 排序的时候使用 lambda 表达式 lambda是个函数,lambda x: y 意思是输入 x 输出 y ,排序时候用sort(key =) - -```python -# 20ms -class Solution: - def merge(self, intervals: List[List[int]]) -> List[List[int]]: - intervals.sort(key=lambda x: x[0]) - i = 0 - while True : - if i+1 >= len(intervals) : - break - if intervals[i][1] >= intervals[i+1][0] : - new_iv = [intervals[i][0],max(intervals[i][1],intervals[i+1][1])] - intervals.remove(intervals[i+1]) - intervals[i] = new_iv - else : i += 1 - return intervals -``` - -现在这种 每次的 remove 很慢 总体 20ms左右,还有一种方法是 维护一个 单调的栈,可以合并就合并后压栈否则就直接压. 这样结果就会快一点 - -```python -# 5ms -class Solution: - def push(self,res_inv,item) : - if len(res_inv) == 0 : - res_inv.append(item) - top = res_inv[-1] - if top[1] >= item[0] : - res_inv.pop() - item = [top[0],max(top[1],item[1])] - res_inv.append(item) - return res_inv - - def merge(self, intervals: List[List[int]]) -> List[List[int]]: - ans = [] - intervals.sort(key=lambda x:x[0]) - for iv in intervals : - self.push(ans,iv) - return ans -``` - -# 15. [轮转数组](https://leetcode.cn/problems/rotate-array/)189-251216 - -```python -class Solution: - def rotate(self, nums: List[int], k: int) -> None: - """ - Do not return anything, modify nums in-place instead. - """ - k = k%len(nums) - nums[:] = nums[len(nums)-k:] + nums[:len(nums)-k] -``` -需要注意的两个是 k 可能超过 nums 的长度,需要通过%来计算 时间移动了多少步 -然后 下一行中 如果 nums\[:] 写成 nums ,那么nums就不会被修改 这个是python的一个特性 写成nums就是修改的这个局部变量 而不是修改了这个引用。 - -这个时间复杂度是 $O(n)$ - -\*最快的办法是反转数组: - -```python -class Solution: - def rotate(self, nums: List[int], k: int) -> None: - """ - Do not return anything, modify nums in-place instead. - """ - - # [1,2,3,4,5,6,7] - # 1. 反转数组 [7,6,5,4,3,2,1] - # 2. 反转前k=3个元素 [5,6,7, 4,3,2,1] - # 3. 反转剩余元素 [5,6,7, 1,2,3,4] - n = len(nums) - k = k % n - nums.reverse() - nums[:k] = nums[:k][::-1] - nums[k:] = nums[k:][::-1] -``` - -# 16. [除自身以外数组的乘积](https://leetcode.cn/problems/product-of-array-except-self/)238-251216 - -#前后缀 - -## 思路一:前后缀积 - -这个 也是用 前缀后缀积 但是不需要 维护两个list 因为我们知道最后的目的是 在 index位置 ans\[i] = $\prod_{j\ne i}nums_j$ 所以不妨 先正向遍历拿到前面的积 再反向遍历,拿到后面的积。 - -```python -class Solution: - def productExceptSelf(self, nums: List[int]) -> List[int]: - length = len(nums) - answer = [0] * length - answer[0] = 1 - for i in range(1, length): - answer[i] = nums[i - 1] * answer[i - 1] - R = 1 - for i in reversed(range(length)): - answer[i] = answer[i] * R - R *= nums[i] - return answer -``` - -## UES - -这个解法考虑了0 同时用了reduce函数可以看一下 - -```python -class Solution: - def productExceptSelf(self, nums: List[int]) -> List[int]: - if 0 in nums: - ret= [0]*len(nums) - if nums.count(0)>=2: - return ret - ret[nums.index(0)]=reduce(mul,(i for i in nums if i!=0),) - return ret - p=reduce(mul,nums,) - if p!=0: - return [p//i for i in nums] -``` -## Trash - -用 前缀积 后缀积 来在 $O(n)$ 时间复杂度解决这个问题 但是 空间复杂度是 $O(n)$ - -```python -class Solution: - def productExceptSelf(self, nums: List[int]) -> List[int]: - fD, bD = nums[:], nums[:] # 定义前缀后缀积 - for i in range(1,len(nums)) : - fD[i] *= fD[i-1] - bD[len(nums)-i-1] *= bD[len(nums)-i] - ans = [ bD[1] ] - for i in range(1,len(nums)-1) : - ans.append( fD[i-1] * bD[i+1] ) - ans.append(fD[-2]) - return ans -``` - -# 17. [缺失的第一个正数](https://leetcode.cn/problems/first-missing-positive/)41-251216 - - -## 思路一:原地哈希(节省内存) - -参考这个:[寻找文件副本](https://leetcode.cn/problems/shu-zu-zhong-zhong-fu-de-shu-zi-lcof/) - -ans 最大的情况 就是 1,len(nums)都在 ,那么如果把 nums 中的 值 作为 index 那么肯定有 index 用不完的情况,第一个没用的就是 答案。 -v\[index] 是负数,那么这个index 可以直接拿来用,如果 v\[i]>0 那么我们就需要把 这个 v\[i] 也给记录下来。向链表一样顺着找 - -```python -class Solution: - def firstMissingPositive(self, nums: List[int]) -> int: - l = len(nums) - for i in range(len(nums)) : - target = nums[i]-1 - if target < 0 or target >= l : - continue - while nums[target] <= l and nums[target] > 0 : - if nums[target] == target + 1 : - break - next_target = nums[target] - 1 - nums[target] = target + 1 - target = next_target - nums[target] = target + 1 - for i in range(len(nums)) : - if i+1 != nums[i] : - return i+1 - else : - return l+1 -``` - - -题解中的原地哈希,这个比上面的方法更好 -```python -class Solution: - def firstMissingPositive(self, nums: List[int]) -> int: - n = len(nums) - for i in range(n): - if nums[i] <= 0: - nums[i] = n + 1 - for i in range(n): - num = abs(nums[i]) - if num <= n: - nums[num - 1] = -abs(nums[num - 1]) - for i in range(n): - if nums[i] > 0: - return i + 1 - return n + 1 -``` -## Trash -题目中限制了空间复杂度是常数,如果不考虑空间,这个方法很快 -```python -class Solution: - def firstMissingPositive(self, nums: List[int]) -> int: - ans = len(nums)+1 - s = set(nums) - for i in range(len(nums),0,-1) : - if i in s : - continue - else : ans = min(ans,i) - return ans -``` - -# 18. [矩阵置零](https://leetcode.cn/problems/set-matrix-zeroes/)73-251216 - -先处理行再处理列 这个是 $O(mn)$ 时间复杂度。空间复杂度是 $O(n)$ - -```python -class Solution: - def setZeroes(self, matrix: List[List[int]]) -> None: - """ - Do not return anything, modify matrix in-place instead. - """ - c_index = set() - for i,n in enumerate(matrix) : - if 0 in n : - index = [ -1 if n[index]!=0 else index for index in range(len(n)) ] - [c_index.add(x) for x in index] - matrix[i] = [0] * len(n) - for i,n in enumerate(matrix) : - for j in range(len(n)) : - if j in c_index: - matrix[i][j] = 0 -``` - -# 19. [螺旋矩阵](https://leetcode.cn/problems/spiral-matrix/)54-251216 - -遇到墙换方向 - -```python -class Solution: - def spiralOrder(self, matrix: List[List[int]]) -> List[int]: - ans = [] - i,j = 0,0 - down = len(matrix) - right = len(matrix[0]) - total = down * right - left = 1 - up = 1 - - di,dj = 0,1 - cnt = 0 - if right == 1 : - ans = [ i[0] for i in matrix ] - return ans - - while True : - print(i,j) - ans.append(matrix[i][j]) - cnt += 1 - if cnt == total : - break - i,j = i+di,j+dj - if j == right-1 and di == 0 and dj == 1 : - di,dj = 1,0 - up = up + 1 - if i == down-1 and di == 1 and dj == 0 : - di,dj = 0,-1 - right = right - 1 - if j == left - 1 and di == 0 and dj == -1 : - di,dj = -1,0 - down = down - 1 - if i == up - 1 and di == -1 and dj == 0 : - di,dj = 0,1 - left = left + 1 - return ans -``` - - -# 20. [旋转图像](https://leetcode.cn/problems/rotate-image/)48-251216 - -主要是下标处理 这几个矩阵相关的题目 - -```python -class Solution: - def p_single_pos(self,nums,i,j): - self.swap(nums,i,j,j,self.col-i-1) - self.swap(nums,i,j,self.row-i-1,self.col-j-1) - self.swap(nums,i,j,self.row-j-1,i) - - def swap(self,nums,i,j,k,l): - temp = nums[i][j] - nums[i][j] = nums[k][l] - nums[k][l] = temp - - def rotate(self, matrix: List[List[int]]) -> None: - """ - Do not return anything, modify matrix in-place instead. - """ - self.row = len(matrix) - self.col = len(matrix[0]) - for i in range(self.col//2 + 1) : - pos_list = [ [i,x] for x in range(i,self.col-i-1) ] - for t in pos_list : - self.p_single_pos(matrix,t[0],t[1]) -``` - -# 21. [搜索二维矩阵 II](https://leetcode.cn/problems/search-a-2d-matrix-ii/)240-251216 - -tag: 二分查找 分治法 - -## 思路一:Z字形查找 - -最简单的想法,时间复杂度是 $O(n+m)$ - -```python -class Solution: - def searchMatrix(self, matrix: List[List[int]], target: int) -> bool: - maxColIndex = len(matrix[0])-1 - for i,n in enumerate(matrix[0]) : - if n >= target : - maxColIndex = i-1 - if n == target : - return True - print(maxColIndex) - for i in range(0,maxColIndex+1): - for j in range(len(matrix)) : - if matrix[j][i] == target : - return True - if matrix[j][i] > target : - break - return False -``` - -## 思路二:二分查找 - -```python -class Solution: - def searchMatrix(self, matrix: List[List[int]], target: int) -> bool: - for row in matrix: - if row[0] > target : - break - idx = bisect.bisect_left(row, target) - if idx < len(row) and row[idx] == target: - return True - return False -``` - -防止不会 自己写一下 binary search -```python -class Solution: - def bs(self,nums,target): - mid = (len(nums))//2 - print(mid,nums) - if nums[0] > target : - return None - if nums[-1] < target : - return None - if nums[mid] > target : - return self.bs(nums[0:mid],target) - elif nums[mid] < target : - return self.bs(nums[mid+1:],target) - else : return mid - - def searchMatrix(self, matrix: List[List[int]], target: int) -> bool: - for row in matrix: - if row[0] > target : - break - idx = self.bs(row, target) - if idx!=None : - return True - return False -``` - -## 思路三:贪心Z* 从左下角开始搜索! - -操了 这个和前面的 Z搜索区别在于 从左下角开始搜索,这样的话就可以沿着 减增减 的序列搜索了 可以排除更多不可能区域. 我觉得这个想法**非常好**。 -[参考题解](https://leetcode.cn/problems/search-a-2d-matrix-ii/solutions/2361487/240-sou-suo-er-wei-ju-zhen-iitan-xin-qin-7mtf) - -```python -class Solution: - def searchMatrix(self, matrix, target) -> bool : - i, j = len(matrix)-1,0 - while i>=0 and j<=len(matrix[0])-1 : - if matrix[i][j] == target : - return True - i,j = (i-1,j) if matrix[i][j] > target else (i,j+1) - # if matrix[i][j] > target : - # i -= 1 - # else : - # j += 1 - return False -``` - -# 22. [相交链表](https://leetcode.cn/problems/intersection-of-two-linked-lists/)160-251217 - -## 思路一:双指针\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\* - -这个太帅了 -![](https://cdn.jsdelivr.net/gh/zip95297/zip95297.github.io@main/source/images/%E9%A2%98%E7%9B%AE%E7%AC%94%E8%AE%B0/Pasted%20image%2020251217145442.webp?raw=true) - -```python -class Solution: - def getIntersectionNode(self, headA: ListNode, headB: ListNode) -> Optional[ListNode]: - pA, pB = headA, headB - while pA != pB : - pA = pA.next if pA else headB - pB = pB.next if pB else headA - return pA -``` - - -## 思路二:哈希 - -在这个过程中改变了list结构,这样的方法就是记录那些节点是访问过的 -可以 用一个 set 记录哪些访问过,就不用这个了 但是用 Set 需要 o(m)的空间 - -```python -class Solution: - def getIntersectionNode(self, headA: ListNode, headB: ListNode) -> Optional[ListNode]: - startA = headA - while headA : - headA.val = -1 - headA.val - headA = headA.next - ans = None - while headB : - if headB.val < 0 and ans == None : - # headB.val = -headB.val - 1 - ans = headB - break - headB = headB.next - headA = startA - while headA : - headA.val = -headA.val - 1 - headA = headA.next - return ans -``` - -## 思路三:对齐开始 - -先全部遍历一下,找到两个的长度,然后将较长的往前移动几个,这下 AB list 长度相同,接下来就直接一起同步 next 就可以 但是 实际时间复杂度是 O(m+2n-len(chonghe)) - -# 23. [反转链表](https://leetcode.cn/problems/reverse-linked-list/)206-251217 - -tag: 递归 迭代 非暴力迭代 - -## 思路一:递归 - -写递归算法时候的一定要理清思路,想好怎么递归调用的。 - -首先思路很明确,就是要reverse \[A, ...] 就是 reverse ... ,然后把A 拼在后面。 有两点需要注意的,第一个是A 的next 需要变成None ,第二个就是 ... 的tail 的next 需要变成 A 然后递归下去就好。 - -另外做这个题的时候需要 return list 的 head 所以要在开始先把 tail 给记录下来以return - -```python -# 递归 -class Solution: - # reverse head 后面的 包括head - def r(self,head) : - # print(head) - # tail = head - # while tail.next!=None : - # tail = tail.next - if head.next : - self.r(head.next) - head.next.next = head - head.next = None - # print(tail) - - def reverseList(self, head: Optional[ListNode]) -> Optional[ListNode]: - if head == None : - return - tail = head - while tail.next!=None : - tail = tail.next - self.r(head) - return tail -``` - -## 思路二:迭代*: - -找到head开头的链的末端 往前指 前一个指空 (这是一个$O(n^2)$的垃圾方法),好好理理下面的那个 - -```python -# 不用看这个 复杂度不对 -class Solution: - def reverseList(self, head: Optional[ListNode]) -> Optional[ListNode]: - tail = head - if head is None : - return - while tail.next : tail = tail.next - while head.next : - # 找head的tail - p = head - pre = None - while p.next : - pre = p - p = p.next - else : - p.next = pre - pre.next = None - return tail -``` - -这个时间复杂度很高 应该换一个,逐步对每个都处理: - -```python -class Solution: - def reverseList(self, head: Optional[ListNode]) -> Optional[ListNode]: - prev, p = None, head - while p : - n = p.next - p.next = prev - prev = p - p = n - return prev -``` - -后来又写了一个 和这个人的方法 一模一样了,是这样做到的:反转列表 对于每一个 node ,next置none 然后把原本的next 的 next 置为这个node 这样会得到一个很不好的 循环。但是换一个思路,从第二个开始来说 相当于我要做的是 将这个 next 置为 prev (而不是把下一个的next 置为自己,这个是区别所在),那么我需要保留prev即可 然后 这个ptr 每次移动一个。由于next会改,所以要在最开始先记住node.next - -```python - def reverseKOnce(self, head) : - prev = None - while head : - n = head.next - head.next = prev - prev = head - head = n - return prev -``` -# 24. [回文链表](https://leetcode.cn/problems/palindrome-linked-list/)234-251217 - -## 思路一: 栈判断 - -用栈判断 但是感觉写的不好 很多下标处理: - -```python -class Solution: - def isPalindrome(self, head: Optional[ListNode]) -> bool: - # 先统计长度 然后用栈 判断 - stack = [] - p, l = head, 0 - while p : - p = p.next - l += 1 - if l == 1 : - return True - mid = l//2 - isJumpMid = True if l%2 == 1 else False - i, p = 1, head - while p : - if i <= mid : - stack.append(p.val) - else : - if stack.pop() != p.val : - return False - if i == mid and isJumpMid : - i+=1 - p = p.next - i+=1 - if p : - p = p.next - return True -``` - -## 思路二:复制到数组后双指针 - -这个方法也很快 - -```python -class Solution: - def isPalindrome(self, head: ListNode) -> bool: - vals = [] - current_node = head - while current_node is not None: - vals.append(current_node.val) - current_node = current_node.next - return vals == vals[::-1] -``` - -## 思路三:快慢指针 - -反转后面部分的 list 然后再 用两个指针比较 $O(n)$ $O(1)$ - -```python -class Solution: - - def isPalindrome(self, head: ListNode) -> bool: - if head is None: - return True - - # 找到前半部分链表的尾节点并反转后半部分链表 - first_half_end = self.end_of_first_half(head) - second_half_start = self.reverse_list(first_half_end.next) - - # 判断是否回文 - result = True - first_position = head - second_position = second_half_start - while result and second_position is not None: - if first_position.val != second_position.val: - result = False - first_position = first_position.next - second_position = second_position.next - - # 还原链表并返回结果 - first_half_end.next = self.reverse_list(second_half_start) - return result - - def end_of_first_half(self, head): - fast = head - slow = head - while fast.next is not None and fast.next.next is not None: - fast = fast.next.next - slow = slow.next - return slow - - def reverse_list(self, head): - previous = None - current = head - while current is not None: - next_node = current.next - current.next = previous - previous = current - current = next_node - return previous -``` - -# 25. [环形链表](https://leetcode.cn/problems/linked-list-cycle/)141-251217 - -tag: 快慢指针 集合 重复 -## 思路一:用集合判断是否访问过 - -这个方法空间复杂度是 $O(n)$ ,时间复杂度也是 $O(n)$ -```python -class Solution: - def hasCycle(self, head: Optional[ListNode]) -> bool: - visited = set() - while head : - if head in visited : - return True - visited.add(head) - head = head.next - return False -``` - -## 思路二: 快慢指针* - -$O(1)$ 空间复杂度,$O(n)$时间复杂度看是否重合。下面捋一下思路: -对于一个有环的 list ,环外的 长度为 $x$ ,环长度 为 $L$ ,那么如果一个快指针 一次走两个,慢指针一次走一个,那么,慢的先走x,同时 相当于 快的进入环内后走了x,然后两者相遇的方程 :$i=(x+2i)\%L$ -,对于这个 同余方程 一定有解,可以搜一下。 - -虽然快指针一次走两个,但是快慢之间的距离,其实是每次只**减少一**。所以一定会相遇 - -```python -# 快慢指针 -class Solution: - def hasCycle(self, head: Optional[ListNode]) -> bool: - slow, fast = head, head - if not head : - return False - while slow and fast : - slow = slow.next - fast = fast.next.next if fast.next and fast.next.next else None - if slow == fast and slow : - return True - return False -``` - -我的判断逻辑写得不好,可以学一下别人的: -```python -class Solution: - def hasCycle(self, head: Optional[ListNode]) -> bool: - slow, fast = head, head - while fast and fast.next: - slow = slow.next - fast = fast.next.next - if slow == fast: - return True - return False -``` - -# 26. [环形链表 II](https://leetcode.cn/problems/linked-list-cycle-ii/)142-251217 - -## 思路一:集合 - -这个空间复杂度高 简单 不写了 - -## 思路二:快慢指针* - -一个list 非环长 x 环长 L ,slow 走到环开头, fast 距离环开头 x 。此时 fast 还需要追 L-x ,由于每次 移动 fast 和 slow 的距离-=1 ,然后可以计算出 第一相遇位置,距离 环开头(倒退数) L-x (还需要L-x步,两者距离减为0),距离环结尾(前进数)x 。有了meetAt 这个点,再在 开头放置一个 ptr ,ptr 和 slow 一起同步移动,两者都移动 x 就都到了 开头。 - -```python -class Solution: - def detectCycle(self, head: Optional[ListNode]) -> Optional[ListNode]: - ptr, slow, fast, meetAt = head, head, head, None - # l = 0 - while fast and fast.next : - slow = slow.next - # l += 1 - fast = fast.next.next - if slow == fast : - meetAt = slow - break - if not meetAt : - return - while ptr != slow : - ptr = ptr.next - slow = slow.next - return ptr -``` - -我觉得这两个题都很有意思 - -# 27. [合并两个有序链表](https://leetcode.cn/problems/merge-two-sorted-lists/)21-251217 - -先找到小的开头,作为 p1 然后 比较插入即可 - -```python -class Solution: - def mergeTwoLists(self, list1: Optional[ListNode], list2: Optional[ListNode]) -> Optional[ListNode]: - if not list1 : - return list2 - if not list2 : - return list1 - if list1.val < list2.val : - p1, p2 = list1, list2 - else : - p1, p2 = list2, list1 - ans = p1 - while p2 : - # print(f'1 1: {p1}') - # print(f'1 2: {p2}') - while p1.next and p1.next.val <= p2.val : - # print(f'2 1: {p1}') - # print(f'2 2: {p2}') - p1 = p1.next - # print(f'2-1: {p1}') - # print(f'2-2: {p2}') - if p1.next : - # p2 插入到 p1 和 下一个之间 - # print(f'3 1: {p1}') - # print(f'3 2: {p2}') - p2n = p2.next - p1n = p1.next - p1.next = p2 - p2.next = p1n - p2 = p2n - else : - p1.next = p2 - break - return ans -``` - -学习一下别人写的,用了一个新的节点 作为开头,之后在这个开头后添加 : - -```python -class Solution: - def mergeTwoLists(self, list1: Optional[ListNode], list2: Optional[ListNode]) -> Optional[ListNode]: - dummy = cur = ListNode() - while list1 and list2: - if list1.val < list2.val: - cur.next = list1 - list1 = list1.next - else: - cur.next = list2 - list2 = list2.next - cur = cur.next - - cur.next = list1 or list2 - return dummy.next -``` - -# 28. [两数相加](https://leetcode.cn/problems/add-two-numbers/)2-251218 - -这个不难 重要的是细节处理 - -```python -class Solution: - def addTwoNumbers(self, l1: Optional[ListNode], l2: Optional[ListNode]) -> Optional[ListNode]: - Csignal = 0 - ans, pl1, pl2 = l1, None, None - while l1 and l2 : - val = (l1.val + l2.val + Csignal)%10 - Csignal = (l1.val + l2.val + Csignal)//10 - l1.val = val - pl1, pl2 = l1, l2 - l1, l2 = l1.next, l2.next - if not l1 and not l2 and Csignal: - pl1.next = ListNode(Csignal) - return ans - if not l1 : - pl1.next = l2 - l1 = pl1.next - while l1 : - val = (l1.val + Csignal)%10 - Csignal = (l1.val + Csignal)//10 - l1.val = val - if not l1.next and Csignal : - l1.next = ListNode(Csignal) - break - l1 = l1.next - return ans -``` - -# 29. [删除链表的倒数第 N 个结点](https://leetcode.cn/problems/remove-nth-node-from-end-of-list/)19-251218 - -#快慢指针 - -遍历一次的方法 是 快慢指针 这个一开始没想到 !!!!!!!!!!!! -相当于我手动留了两个距离为 n 的 ptr 后面的到了结尾 前面的到 倒数第n个 - -```python -class Solution: - def removeNthFromEnd(self, head: Optional[ListNode], n: int) -> Optional[ListNode]: - slow = fast = head - preSlow = None - while n : - fast = fast.next - n -= 1 - while fast : - fast = fast.next - preSlow = slow - slow = slow.next - if not preSlow : - return head.next - preSlow.next = slow.next - return head -``` - -# 30. [两两交换链表中的节点](https://leetcode.cn/problems/swap-nodes-in-pairs/)24-251218 - -虽然很简单,但是感觉写的不好,可以学一下别的人代码 - -```python -class Solution: - def swapPairs(self, head: Optional[ListNode]) -> Optional[ListNode]: - prev, ptr = None, head - ans = head.next if head and head.next else head - flag = 0 - while ptr : - if flag < 1 : - if prev : - prev.next = ptr.next if ptr.next else ptr - prev = ptr - ptr = ptr.next - flag += 1 - continue - flag = 0 - n = ptr.next - ptr.next = prev - prev.next = n - ptr = n - return ans -``` - -官方的迭代方法:我发现 官方在做list 相关的题目时候,喜欢用一个dummyhead来作为答案。应该参考一下这个想法 - -```python -class Solution: - def swapPairs(self, head: ListNode) -> ListNode: - dummyHead = ListNode(0) - dummyHead.next = head - temp = dummyHead - while temp.next and temp.next.next: - node1 = temp.next - node2 = temp.next.next - temp.next = node2 - node1.next = node2.next - node2.next = node1 - temp = node1 - return dummyHead.next -``` - -# 31. [K 个一组翻转链表](https://leetcode.cn/problems/reverse-nodes-in-k-group/)25-251218 - -上一题是这个题目 k=2 的特殊情况。用迭代的做法写,逐个找所有的k长子list 然后reverse - -```python -class Solution: - # 反转k个 - def reverseKOnce(self, head) : - tail = head - prev = None - while head : - n = head.next - head.next = prev - prev = head - head = n - return prev,tail - - def reverseKGroup(self, head: Optional[ListNode], k: int) -> Optional[ListNode]: - ans = prev_subtail = ListNode(0) - if k == 1: - return head - n = None - while True : - start = head - sublen = 1 - while head : - head = head.next - sublen += 1 - if sublen == k and head : - n = head.next - head.next = None - head = n - break - if head or not n: - subhead,subtail = self.reverseKOnce(start) - prev_subtail.next = subhead - prev_subtail = subtail - if not n : - break - else : - prev_subtail.next = start - break - return ans.next -``` - -# 32. [随机链表的复制](https://leetcode.cn/problems/copy-list-with-random-pointer/)138-251218-重新写 - -这个方法复杂度应该是 $O(n^2)$ 先按照next 的方向复制list 然后找random 方法是 对于一个node 从两个list的头开始找,知道找origin的random 然后就把 新的random也置为这个 - -```python -class Solution: - def copyRandomList(self, head: 'Optional[Node]') -> 'Optional[Node]': - p = dummyHead = Node(0) - phead = head - while phead : - p.next = Node(phead.val) - phead = phead.next - p = p.next - # 到这里先复制list 到dummy 之后处理random - p = dummyHead.next - p_origin = head - while p : - if p_origin.random is None : - p.random = None - p = p.next - p_origin = p_origin.next - if not p : - break - - q_origin = head - q = dummyHead.next - while q : - if q_origin == p_origin.random : - p.random = q - break - q = q.next - q_origin = q_origin.next - p = p.next - p_origin = p_origin.next - return dummyHead.next - - while dummyHead : - print(f"{dummyHead.val}-{dummyHead.random.val if dummyHead and dummyHead.random else 'null'}",end=' ') - dummyHead = dummyHead.next -``` - -别人的方法 还没看 - -```python -class Solution: - def copyRandomList(self, head: 'Optional[Node]') -> 'Optional[Node]': - if head is None: - return None - - - cur = head - while cur: - cur.next = Node(cur.val, cur.next) - cur = cur.next.next - - cur = head - while cur: - if cur.random: - cur.next.random = cur.random.next - cur = cur.next.next - - cur = head.next - while cur.next: - cur.next = cur.next.next - cur = cur.next - - return head.next -``` - -必须要看看题解 然后好好做一下 这会儿太累了,之后看吧留个坑 - -# 33. [排序链表](https://leetcode.cn/problems/sort-list/)148-251219*-重新写 - -#并归排序 - -这个可以分为 自底向上并归 和 自顶向下并归(需要递归)。 - -自顶向下,用了递归 空间复杂度是 $O(logn)$ -```python -class Solution: - def merge(self, head1: Optional[ListNode], head2: Optional[ListNode]) : - p = dummy = ListNode(-999) - while head1 and head2 : - if head1.val < head2.val : - p.next = head1 - head1 = head1.next - else : - p.next = head2 - head2 = head2.next - p = p.next - if head1 : - p.next = head1 - if head2 : - p.next = head2 - return dummy.next - - # start包括 end不包括 - def sort(self,start ,end) : - if not start : - return start - if start.next == end : - start.next = None - return start - fast = slow = start - while fast != end : - slow = slow.next - fast = fast.next - if fast != end : - fast = fast.next - mid = slow - return self.merge(self.sort(start,mid),self.sort(mid,end)) - - - def sortList(self, head: Optional[ListNode]) -> Optional[ListNode]: - return self.sort(head,None) -``` - -自底向上:这个我写不动了 之后回来再写一次吧,重要的是 获取长度 然后处理 - -```python -class Solution: - def merge(self, head1: Optional[ListNode], head2: Optional[ListNode]) : - p = dummy = ListNode(-999) - while head1 and head2 : - if head1.val < head2.val : - p.next = head1 - head1 = head1.next - else : - p.next = head2 - head2 = head2.next - p = p.next - if head1 : - p.next = head1 - if head2 : - p.next = head2 - return dummy.next - - def sortList(self, head: Optional[ListNode]) -> Optional[ListNode]: - length = 0 - node = head - while node: - length += 1 - node = node.next - - dummyHead = ListNode(0, head) - subLength = 1 - while subLength < length: - prev, curr = dummyHead, dummyHead.next - while curr: - head1 = curr - for i in range(1, subLength): - if curr.next: - curr = curr.next - else: - break - head2 = curr.next - curr.next = None - curr = head2 - for i in range(1, subLength): - if curr and curr.next: - curr = curr.next - else: - break - - succ = None - if curr: - succ = curr.next - curr.next = None - - merged = self.merge(head1, head2) - prev.next = merged - while prev.next: - prev = prev.next - curr = succ - subLength <<= 1 - - return dummyHead.next -``` - -# 34. [合并 K 个升序链表](https://leetcode.cn/problems/merge-k-sorted-lists/)23-251219 - -最开始想的用单调栈 好像不可行,现在考虑用最小堆 - -```python -class Solution: - def mergeKLists(self, lists: List[Optional[ListNode]]) -> Optional[ListNode]: - # val,node - minHeapNode = [] - for i, n in enumerate(lists) : - if n : - minHeapNode.append((n.val,i,n)) - heapq.heapify(minHeapNode) - p = dummy = ListNode(0) - i = len(lists) - while minHeapNode : - # print(minHeapNode) - top = heapq.heappop(minHeapNode)[2] - p.next = top - p = p.next - if top.next : - heapq.heappush(minHeapNode,(top.next.val,i,top.next)) - i += 1 - return dummy.next -``` - -# 35. [LRU 缓存](https://leetcode.cn/problems/lru-cache/)146-251219 - -## 题解思路:双链表+hash - -用collections . orderDict - -双链表用来维护出入队列,然后hash用来快速根据key找到value - -```python -class LRUCache: - def __init__(self, capacity: int): - self.used = 0 - self.capacity = capacity - self.dummyStart = Node(-2,-2,None,None) - self.dummyEnd = Node(-1,-1,None,None) - self.dummyStart.next = self.dummyEnd - self.dummyEnd.prev = self.dummyStart - self.key2addr = {} - - def get(self, key: int) -> int: - if not key in self.key2addr.keys() : - return -1 - ptr = self.key2addr[key] - ptr.prev.next = ptr.next - ptr.next.prev = ptr.prev - - ptr.next = self.dummyEnd - ptr.prev = self.dummyEnd.prev - self.dummyEnd.prev.next = ptr - self.dummyEnd.prev = ptr - # self.print('get') - return ptr.value - - - def put(self, key: int, value: int) -> None: - if key in self.key2addr.keys() : - self.removeNode(self.key2addr[key]) - self.used -= 1 - if self.used == self.capacity : - self.key2addr.pop(self.dummyStart.next.key) - self.removeNode(self.dummyStart.next) - else : self.used += 1 - new = Node(key=key,value=value,prev=self.dummyEnd.prev,next=self.dummyEnd) - self.dummyEnd.prev.next = new - self.dummyEnd.prev = new - self.key2addr[key] = new - # self.print('put') - - def removeNode(self,ptr) : - ptr.prev.next = ptr.next - ptr.next.prev = ptr.prev - - def print(self,str) : - p = self.dummyStart - print(f'{str}',end=' ') - while p : - print(f'({p.key}-{p.value})',end=' ') - p = p.next - print(f'used:{self.used}/{self.capacity}') - -class Node : - def __init__(self, key=0, value=0, next=None, prev=None) : - self.value = value - self.key = key - self.next = next - self.prev = prev -``` - -## Trash - -用队列,但是 get put 的复杂度是 O(capacity) remove -为什么要用双链?因为remove操作 需要 o(c) 去找到 value ,但是如果用Node,可以在字典中存入其地址。O(1) 就可以找到。 -```python -class LRUCache: - def __init__(self, capacity: int): - self.capacity = capacity - self.dic = {} - self.used = 0 # que 的 size - self.que = [] - def get(self, key: int) -> int: - if key not in self.dic.keys() : - return -1 - value = self.dic[key] - self.que.remove(key) - self.que.append(key) - # print(f'get {self.dic},{self.que}') - return value - def put(self, key: int, value: int) -> None: - if key in self.dic.keys() : - self.dic[key] = value - self.que.remove(key) - self.que.append(key) - # print(f'put {self.dic},{self.que}') - return - if self.used < self.capacity : - self.que.append(key) - self.dic[key] = value - self.used += 1 - # print(f'put {self.dic},{self.que}') - return - delkey = self.que[0] - self.que = self.que[1:] - self.dic.pop(delkey) - self.que.append(key) - self.dic[key] = value - # print(f'put {self.dic},{self.que}') -``` - - -# 36. [二叉树的中序遍历](https://leetcode.cn/problems/binary-tree-inorder-traversal/)94-251222 - -要知道中序遍历和先序遍历的while写法是不一样的,区别在于先append还是先 访问(cur=) -## 思路一:递归 - -这个写法比较简单 - -```python -class Solution: - def mid(self,root,ans): - if root : - self.mid(root.left,ans) - ans.append(root.val) - self.mid(root.right,ans) - - def inorderTraversal(self, root: Optional[TreeNode]) -> List[int]: - ans = [] - self.mid(root,ans) - return ans -``` - -## 思路二:迭代\*\* - -我觉得这个迭代写的很好,下来再看看 - -```python -class Solution: - def inorderTraversal(self, root: Optional[TreeNode]) -> List[int]: - stack = [] - ans = [] - while stack or root : - while root : - stack.append(root) - root = root.left - root = stack.pop() - ans.append(root.val) - root = root.right - return ans -``` - -自己写的 -```python - stk = [] - while stk or root : - while root : - stk.append(root) - root = root.left - root = stk.pop() - print(root.val, end=' ') - print(stk) - root = root.right -``` - -# 37. [二叉树的最大深度](https://leetcode.cn/problems/maximum-depth-of-binary-tree/)104-251222 - -## 思路一:DFS - -```python -class Solution: - def maxDepth(self, root: Optional[TreeNode]) -> int: - if not root : return 0 - return max(self.maxDepth(root.left)+1,self.maxDepth(root.right)+1) -``` -## 思路二:BFS - -```python -class Solution: - def maxDepth(self, root: Optional[TreeNode]) -> int: - ans = 0 - que = [root] - next_que = [] - if not root : - return 0 - while que : - node = que[0] - que.remove(node) - if node.left : - next_que.append(node.left) - if node.right : - next_que.append(node.right) - if not que : - ans += 1 - que = next_que - next_que = [] - return ans -``` - -# 38. [翻转二叉树](https://leetcode.cn/problems/invert-binary-tree/)226-251222 - -## 思路一:BFS - -对每一个节点 进行左右节点的互换,因为所有节点只访问一次。所以可行 - -```python -class Solution: - def invertTree(self, root: Optional[TreeNode]) -> Optional[TreeNode]: - que = [root] - next_que = [] - if not root : - return root - while que : - node = que[0] - que.remove(node) - t = node.left - node.left = node.right - node.right = t - if node.left : - next_que.append(node.left) - if node.right : - next_que.append(node.right) - if not que : - que = next_que - next_que = [] - return root -``` - -## 思路二:递归 - -递归的方法都比较简单 - -```python -class Solution: - def invertTree(self, root: Optional[TreeNode]) -> Optional[TreeNode]: - if not root : - return - t = root.left - root.left = root.right - root.right = t - self.invertTree(root.left) - self.invertTree(root.right) - return root -``` - -# 39. [对称二叉树](https://leetcode.cn/problems/symmetric-tree/)101-251222 - -## 思路一:递归 - -递归的思路比较简单 - -```python -class Solution: - def digui(self,left,right): - if not left and not right : - return True - if left and right and left.val == right.val : - return True & self.digui(left.left,right.right) & self.digui(left.right,right.left) - else : return False - - def isSymmetric(self, root: Optional[TreeNode]) -> bool: - return self.digui(root,root) -``` - -## 思路二:迭代 - -```python -class Solution: - def isSymmetric(self, root) -> bool : - left_ptr = right_ptr = root - left_que = [root.left] - right_que = [root.right] - while left_que and right_que : - left_ptr, right_ptr = left_que[0], right_que[0] - left_que.remove(left_que[0]) - right_que.remove(right_que[0]) - if ( left_ptr and not right_ptr ) or ( not left_ptr and right_ptr ) : - return False - if left_ptr and right_ptr and left_ptr.val != right_ptr.val : - return False - if not left_ptr and not right_ptr : - continue - # append 的时候也要 对称 - left_que.append(left_ptr.left) - left_que.append(left_ptr.right) - right_que.append(right_ptr.right) - right_que.append(right_ptr.left) - return True - -``` - -# 40. [二叉树的直径](https://leetcode.cn/problems/diameter-of-binary-tree/)543-251222 - -## 思路一:递归 - -每一个节点的 计算出来的直径都是 左边的深度加右边的深度,只需要维护一个最大的就可以,但是这里把工具函数 以及self.ans写在solution函数内,这么做方便维护max - -```python -class Solution : - def diameterOfBinaryTree(self, root: Optional[TreeNode]) -> int: - self.ans = -1 - def DepthOfNode(n): - if not n : - return 0 - l = DepthOfNode(n.left) - r = DepthOfNode(n.right) - self.ans = max(self.ans, l+r) - return max(l,r)+1 - DepthOfNode(root) - return self.ans -``` - -## 思路二:迭代 - -找深度还是递归,但是只是用BFS来更新ans - -## Trash Ologn\^2 - -超时了,记录每个叶子节点的path 然后计算路径 - -```python -class Solution: - def distance(self,path1,path2) -> int : - # 非公共长度的和 - if path1 == '' or path2 == '' : - return max(len(path1),len(path2)) - i=0 - while path1[i] == path2[i] : - i += 1 - return len(path1) + len(path2) - i*2 - - - def diameterOfBinaryTree(self, root: Optional[TreeNode]) -> int: - que = deque([root]) - root.path = '' - mp = {} - paths = [] - while que : - ptr = que.popleft() - if not ptr : - continue - if not ptr.left and not ptr.right : - paths.append(ptr.path) - que.append(ptr.left) - que.append(ptr.right) - if ptr.left : ptr.left.path = ptr.path + 'l' - if ptr.right : ptr.right.path = ptr.path + 'r' - ans = len(paths[-1]) - for i in range(len(paths)) : - for j in range(i+1,len(paths)) : - ans = max(ans,self.distance(paths[i],paths[j])) - return ans -``` - -# 41. [二叉树的层序遍历](https://leetcode.cn/problems/binary-tree-level-order-traversal/)102-251222 - -BFS即可 - -```python -class Solution: - def levelOrder(self, root: Optional[TreeNode]) -> List[List[int]]: - if not root : return [] - que = deque([root]) - next_que = [] - ans = [] - while que : - ans.append([]) - while que : - cur = que.popleft() - if not cur : - continue - ans[-1].append(cur.val) - if cur.left : next_que.append(cur.left) - if cur.right : next_que.append(cur.right) - que = deque(next_que) - next_que = [] - return ans -``` - -# 42. [将有序数组转换为二叉搜索树](https://leetcode.cn/problems/convert-sorted-array-to-binary-search-tree/)108-251223 - -## 思路一:递归 - -一个sorted 数组转换成 树,root节点一定是mid,那么只需要向归并排序一样 依次将左右的的根节点找到即可 - -```python -class Solution: - def nums2tree(self, nums) -> TreeNode : - if nums == [] : return None - mid = len(nums)//2 - left_nums = nums[0:mid] if mid != 0 else [] - right_nums = nums[mid+1:] if mid Optional[TreeNode]: - return self.nums2tree(nums) -``` - -## 题解中有别的思路基于中序遍历 - -# 43. [验证二叉搜索树](https://leetcode.cn/problems/validate-binary-search-tree/)98-251223\*\*\* - -## 思路一:中序遍历判断数组是否有序 - -```python -class Solution: - - def isValidBST(self, root: Optional[TreeNode]) -> bool: - self.nums = [] - def front(root): - if not root : return - if root.left : - front(root.left) - self.nums.append(root.val) - if root.right : - front(root.right) - front(root) - p = -999999999999 - for n in self.nums : - if p >= n : return False - p = n - return True -``` - -## 思路二:递归\*\*\* - -我觉得这个递归有点难,需要处理 右子树的最小值不能超过root 左子树的最大值不能超过 root,学一下官方的思路。helper用来判断 一个树 是不是 BST。 - -```python -class Solution: - def isValidBST(self, root: TreeNode) -> bool: - def helper(node, lower = float('-inf'), upper = float('inf')) -> bool: - if not node: - return True - val = node.val - if val <= lower or val >= upper: - return False - if not helper(node.right, val, upper): - return False - if not helper(node.left, lower, val): - return False - return True - return helper(root) -``` - -下面是自己写的 上面是题解写的 题解写的递归思路 **更更更更** 好 - -```python -class Solution: - def isValidBST(self, root: Optional[TreeNode]) -> bool: - def isBST(node,lowwer=float('-inf'),upper=float('+inf')): - if not node : - return True - # print(node.left.val if node.left else None,node.val,node.right.val if node.right else None,lowwer,upper) - if ( node.left and node.left.val >= node.val ) or ( node.right and node.right.val <= node.val ): - return False - if (node.left and node.left.val <= lowwer) or (node.right and node.right.val >= upper) : - return False - return isBST(node.left,lowwer=lowwer,upper=node.val) and isBST(node.right,lowwer=node.val,upper=upper) - return isBST(root) -``` - -# 44. [二叉搜索树中第 K 小的元素](https://leetcode.cn/problems/kth-smallest-element-in-a-bst/)230-251223 - -## 思路一:中序遍历 - -将二叉查找树转换成数组 $O(n)$ 然后 查找一次即可 下面是递归写法 - -```python -class Solution: - def kthSmallest(self, root: Optional[TreeNode], k: int) -> int: - self.ans = [] - def mid_iter(root): - if not root : return - if root.left : mid_iter(root.left) - self.ans.append(root.val) - if root.right: mid_iter(root.right) - mid_iter(root) - return self.ans[k-1] -``` - - 下面是迭代的写法 注意每次while中的意思:遇到左叶子存在就优先处理左叶子,然后先把现在存起到栈中。知道遇到没有左叶子的,遍历这个节点,然后处理右叶子。这个流程构成了一个循环。 - - 换一种理解,先往左走找到最小的 然后 找到大的进入 stk的pop 和 进入右叶子 我觉得第一个更好理解一点。 - -```python -class Solution: - def kthSmallest(self, root: Optional[TreeNode], k: int) -> int: - stk = [] - nums = [] - while stk or root : - while root : - stk.append(root) - root = root.left - root = stk.pop() - nums.append(root.val) - k -= 1 - if k == 0 : - return root.val - root = root.right -``` - -## 思路二:记录node 的节点数(看题解)下面这两个还是学一下吧 - -## 思路三:转成AVL平衡二叉树(看题解) - -# 45. [二叉树的右视图](https://leetcode.cn/problems/binary-tree-right-side-view/)199-251223 - -## 思路一:BFS - -通过BFS把每一行加入队列的最后一个记录下来。 -```python -# BFS 找到最后一个 -class Solution: - def rightSideView(self, root: Optional[TreeNode]) -> List[int]: - que = deque([root]) - next_que = [] - ans = [] - if not root : return [] - while que : - ans.append(que[-1].val) - while que : - cur = que.popleft() - if not cur : continue - if cur.left : next_que.append(cur.left) - if cur.right: next_que.append(cur.right) - que = deque(next_que) - next_que = [] - return ans -``` - -## 思路二:DFS\*\*\*\* 之前都没写过DFS,DFS使用stk 把左右加进来 - -在深度优先搜索时 如果优先搜索 右侧节点,那么每层访问的第一个 节点 一定是最右侧的节点,那么我们可以记录一个深度。遇到第一个这个深度的节点,就记录下来。下面是题解的做法 : -```python -class Solution: - def rightSideView(self, root: TreeNode) -> List[int]: - rightmost_value_at_depth = dict() # 深度为索引,存放节点的值 - max_depth = -1 - - stack = [(root, 0)] - while stack: - node, depth = stack.pop() - - if node is not None: - # 维护二叉树的最大深度 - max_depth = max(max_depth, depth) - - # 如果不存在对应深度的节点我们才插入 - rightmost_value_at_depth.setdefault(depth, node.val) - - stack.append((node.left, depth + 1)) - stack.append((node.right, depth + 1)) - - return [rightmost_value_at_depth[depth] for depth in range(max_depth + 1)] -``` -要知道中序遍历和先序遍历的while写法是不一样的,区别在于先append还是先 访问(cur=) - -# 46. [二叉树展开为链表](https://leetcode.cn/problems/flatten-binary-tree-to-linked-list/)114-251223 - -## Trash 用了新的空间 - -```python -class Solution: - def flatten(self, root: Optional[TreeNode]) -> None: - """ - Do not return anything, modify root in-place instead. - """ - if not root : return - dummy = TreeNode(0,None,None) - ptr = dummy - stk = [root] - while stk : - cur = stk.pop() - if not cur : continue - ptr.right = TreeNode(cur.val,None,None) - ptr = ptr.right - # print(cur.val,end='') - stk.append(cur.right) - stk.append(cur.left) - - root.right = dummy.right.right - root.left = None -``` - -## 思路一:存下来prev - -因为cur已经被访问过了所以可以修改了,prev执指向现在cur 就可以修改其左右叶子值了 -```python -class Solution: - def flatten(self, root: Optional[TreeNode]) -> None: - """ - Do not return anything, modify root in-place instead. - """ - stack = [root] if root else [] - prev = None - while stack: - cur = stack.pop() - if prev: - prev.left, prev.right = None, cur - if cur.right: stack.append(cur.right) - if cur.left: stack.append(cur.left) - prev = cur -``` - - -## 思路二:找到前驱节点\*\*\* - - 这个题题解的方法也很多,之后仔细看一下。下面这个方法是常数空间复杂度的方法 - - ```python -class Solution: - def flatten(self, root: TreeNode) -> None: - curr = root - while curr: - if curr.left: - predecessor = nxt = curr.left - while predecessor.right: - predecessor = predecessor.right - predecessor.right = curr.right - curr.left = None - curr.right = nxt - curr = curr.right -``` - -# 47. [从前序与中序遍历序列构造二叉树](https://leetcode.cn/problems/construct-binary-tree-from-preorder-and-inorder-traversal/)105-251223 - -## 思路一:递归(没看 - -终点在于根据 index i 找到 preorder的左右 但是这个递归还是很慢 也是 Trash - -```python -class Solution: - def buildTree(self, preorder: List[int], inorder: List[int]) -> Optional[TreeNode]: - if preorder == [] : return None - # for i,n in enumerate(inorder) : - # if n == preorder[0] : - # break - i = inorder.index(preorder[0]) - left_inorder = inorder[0:i] - right_inorder = inorder[i+1:] - # 可以知道 i 左右的数量 - left_preorder = preorder[1:i+1] - # for x in preorder : - # if x in left_inorder : - # left_preorder.append(x) - right_preorder = preorder[i+1:] - # for x in preorder : - # if x in right_inorder : - # right_preorder.append(x) - # print(left_preorder,right_preorder) - return TreeNode(preorder[0],self.buildTree(left_preorder,left_inorder),self.buildTree(right_preorder,right_inorder)) -``` - -下面是我的改进 还不是最优的:重点在于通过v找i时候用hash 把$O(n)$复杂度降低为 $O(1)$ -```python -# # 递归 -class Solution: - def buildTree_helper(self, preorder: List[int], inorder: List[int], abs_start=0) -> Optional[TreeNode]: - if len(preorder) == 0 : return None - i = self.mp[preorder[0]] - abs_start - return TreeNode(preorder[0],self.buildTree_helper(preorder[1:i+1],inorder[0:i],abs_start=abs_start),self.buildTree_helper(preorder[i+1:],inorder[i+1:],abs_start=abs_start+i+1)) - def buildTree(self, preorder: List[int], inorder: List[int]) -> Optional[TreeNode]: - self.mp = { v:i for i,v in enumerate(inorder) } - return self.buildTree_helper(preorder, inorder) -``` - -\*\*\*\*\*\*下面是官方的递归方法,速度快了很多 - -```python -class Solution: - def buildTree(self, preorder: List[int], inorder: List[int]) -> TreeNode: - def myBuildTree(preorder_left: int, preorder_right: int, inorder_left: int, inorder_right: int): - if preorder_left > preorder_right: - return None - - # 前序遍历中的第一个节点就是根节点 - preorder_root = preorder_left - # 在中序遍历中定位根节点 - inorder_root = index[preorder[preorder_root]] - - # 先把根节点建立出来 - root = TreeNode(preorder[preorder_root]) - # 得到左子树中的节点数目 - size_left_subtree = inorder_root - inorder_left - # 递归地构造左子树,并连接到根节点 - # 先序遍历中「从 左边界+1 开始的 size_left_subtree」个元素就对应了中序遍历中「从 左边界 开始到 根节点定位-1」的元素 - root.left = myBuildTree(preorder_left + 1, preorder_left + size_left_subtree, inorder_left, inorder_root - 1) - # 递归地构造右子树,并连接到根节点 - # 先序遍历中「从 左边界+1+左子树节点数目 开始到 右边界」的元素就对应了中序遍历中「从 根节点定位+1 到 右边界」的元素 - root.right = myBuildTree(preorder_left + size_left_subtree + 1, preorder_right, inorder_root + 1, inorder_right) - return root - - n = len(preorder) - # 构造哈希映射,帮助我们快速定位根节点 - index = {element: i for i, element in enumerate(inorder)} - return myBuildTree(0, n - 1, 0, n - 1) - -``` - -## 思路二:迭代(没看 - -```python -class Solution: - def buildTree(self, preorder: List[int], inorder: List[int]) -> TreeNode: - if not preorder: - return None - - root = TreeNode(preorder[0]) - stack = [root] - inorderIndex = 0 - for i in range(1, len(preorder)): - preorderVal = preorder[i] - node = stack[-1] - if node.val != inorder[inorderIndex]: - node.left = TreeNode(preorderVal) - stack.append(node.left) - else: - while stack and stack[-1].val == inorder[inorderIndex]: - node = stack.pop() - inorderIndex += 1 - node.right = TreeNode(preorderVal) - stack.append(node.right) - - return root -``` - -## Trash:递归拆分先序nums时候超时 - -答案对但是超时了 - -```python -class Solution: - def buildTree(self, preorder: List[int], inorder: List[int]) -> Optional[TreeNode]: - if preorder == [] : return None - for i,n in enumerate(inorder) : - if n == preorder[0] : - break - left_inorder = inorder[0:i] - right_inorder = inorder[i+1:] - left_preorder = [] - for x in preorder : - if x in left_inorder : - left_preorder.append(x) - right_preorder = [] - for x in preorder : - if x in right_inorder : - right_preorder.append(x) - # print(left_preorder,right_preorder) - return TreeNode(preorder[0],self.buildTree(left_preorder,left_inorder),self.buildTree(right_preorder,right_inorder)) -``` - -# 48. [路径总和 III](https://leetcode.cn/problems/path-sum-iii/)437-251224 - -## 思路一:前缀和\* - -$O(n)$ 用递归深度搜索 去遍历所有节点,记录前缀和,然后用hash来找到对应的,如果找到就把他拿出。那么最大的问题是:例如根节点,hash中记录了左子树的所有需要的哈希值,然后对于右子树 如果查到,就会出现一条通过root的路径,解决办法就是:记录的 (需要找到的)prefix+sum 只在子树范围内生效。(我觉得也是这个题的难点) -hash中记录的是,prefix+sum(像两数之和一样,遇到val 就查 target-val在不在。但是这个是两数之差)。 - -```python -class Solution: - def pathSum(self, root: Optional[TreeNode], targetSum: int) -> int: - if not root : return 0 - mp = defaultdict(int) - mp[targetSum] = 1 - self.ans = 0 - - def DFS(root,fatherPrefix): - if not root : return - root.prefixSum = fatherPrefix + root.val - self.ans += mp[root.prefixSum] - mp[root.prefixSum+targetSum]+=1 - DFS(root.left,root.prefixSum) - DFS(root.right,root.prefixSum) - ########################################## - #这一句很重要 保证了 这个节点只在子树中记录在mp中 - mp[root.prefixSum+targetSum]-=1 ########## - - DFS(root,0) - - return self.ans -``` - -## 思路二:深度优先搜索-非最优 - -这个思路没啥用我觉得 时间复杂度是 $O(n^2)$ 上一个复杂度是 $O(n)$ 这个思路就是写一个helper 用于找到起点为root的所有和为sum 的path数量,然后(DFS)遍历所有的node 得到所有path - -```python -class Solution: - def pathSum(self, root: Optional[TreeNode], targetSum: int) -> int: - # 定义一个helper函数求出 以 root 为起点 并且和为 Sum 的数量 - def startRootSum(root,Sum) : - if not root : return 0 - return (1 if root.val == Sum else 0) + startRootSum(root.left,Sum-root.val) + startRootSum(root.right,Sum-root.val) - # 遍历所有节点,找到以所有节点开头 path 和为 targetSum 的数量 这个递归就是一个 DFS - if not root : return 0 - ans = 0 - ans += startRootSum(root,targetSum) - ans += self.pathSum(root.left,targetSum) - ans += self.pathSum(root.right,targetSum) - return ans -``` -## Trash 迭代前缀和 - -先尝试了用迭代方法去写前缀和:跑不通。接下来尝试用递归去求 - -```python -class Solution: - def pathSum(self, root: Optional[TreeNode], targetSum: int) -> int: - if not root : return 0 - self.mp = {targetSum:1} - self.ans = 0 - def DFS(root): - stk = [root] - root.prefixSum = root.val - while stk : - cur = stk.pop() - if cur == root.right or cur == root.left : - if root.val != 0: - self.mp = { targetSum:1 ,root.val+targetSum:1} - else : - self.mp = {targetSum:2} - print(cur.val,cur.prefixSum) - if cur.prefixSum in self.mp.keys(): - self.ans += self.mp[cur.prefixSum] - print(cur.val) - if targetSum+cur.prefixSum in self.mp.keys() : - self.mp[targetSum+cur.prefixSum] += 1 - else : - self.mp[targetSum+cur.prefixSum] = 1 - - if cur.right: - stk.append(cur.right) - cur.right.prefixSum = cur.prefixSum + cur.right.val - if cur.left : - stk.append(cur.left) - cur.left.prefixSum = cur.prefixSum + cur.left.val - print(self.mp,self.ans) - DFS(root) - print(self.ans) - return self.ans -``` - -print位置换一下就是前中后序遍历,但是迭代的写法很不一样,谦虚需要while + stk 中序需要 while+que -```python -def DFS(root): - if not root : return - DFS(root.left) - print(root.val) - DFS(root.right) -``` -# 49. [二叉树的最近公共祖先](https://leetcode.cn/problems/lowest-common-ancestor-of-a-binary-tree/)236-251224 - -## 思路一:记录所有节点的father,然后用set查 - -1.先用DFS遍历树,然后记录每个节点的father 2.把p的所有father存在在个set 3.遍历q的father(从q开始)遇到 在 set:p.fathers 中的节点就返回 - -```python -class Solution: - def lowestCommonAncestor(self, root: 'TreeNode', p: 'TreeNode', q: 'TreeNode') -> 'TreeNode': - def DFS(root): - if not root : return - if root.left : root.left.father = root - if root.right: root.right.father = root - DFS(root.left) - DFS(root.right) - DFS(root) - root.father = None - pFathers = set() - while p : - pFathers.add(p) - p=p.father - while q : - if q in pFathers : - return q - q = q.father -``` - -## 思路二:递归 - -写一个工具函数判断 这个root节点为根的树中包不包含 p,q ,那么最后结果一定是 ans 包含,ans的左右孩子都不包含,下面是自己的写法,但是感觉把问题写复杂了,之后可以学习一下题解的写法。 - -```python -class Solution: - def lowestCommonAncestor(self, root: 'TreeNode', p: 'TreeNode', q: 'TreeNode') -> 'TreeNode': - def DFS(root,p,q) : - if not root : return - root.p_in = False - root.q_in = False - if not root.left and not root.right : - root.q_in = False - root.p_in = False - if root.val == p.val : - root.p_in = True - root.q_in = False - # print(f'p-{root}') - if root.val == q.val : - root.p_in = False - root.q_in = True - # print(f'q-{root}') - DFS(root.left,p,q) - DFS(root.right,p,q) - lpi = root.left.p_in if root.left else False - rpi = root.right.p_in if root.right else False - lqi = root.left.q_in if root.left else False - rqi = root.right.q_in if root.right else False - root.p_in = lpi or rpi or root.p_in - root.q_in = lqi or rqi or root.q_in - # print(f'val:{root.val},p_in:{root.p_in},q_in:{root.q_in}') - DFS(root,p,q) - while root.p_in and root.q_in : - # print(root.val) - if root.left and root.left.p_in and root.left.q_in : - root = root.left - elif root.right and root.right.p_in and root.right.q_in : - root = root.right - else : return root -``` - -题解写法没有py我没粘 这个是前面的写法 (\* **这个想法好好啊** \*) - -```python -class Solution: - def lowestCommonAncestor(self, root: 'TreeNode', p: 'TreeNode', q: 'TreeNode') -> 'TreeNode': - # 如果当前节点为p或者q 公共父节点是p或者q本身 - # 如果左右都找到了可能的父节点,返回公共节点 - # 左右都没找到,返回空节点 - if root in (None,p,q): - # 3元组 - return root - left = self.lowestCommonAncestor(root.left,p,q) - right = self.lowestCommonAncestor(root.right,p,q) - - if left and right: - return root - - # 左和右 谁找到了就返回谁 - return left or right -``` - -# 50. [二叉树中的最大路径和](https://leetcode.cn/problems/binary-tree-maximum-path-sum/)124-251224 - -## 思路一:递归-从trash优化过来的 - -在trash中 每次求解 root 树的最大路径 都要再递归找各个节点的 路径,对这个做法优化:在一个递归中得到 从一个节点开始的最大值 ,这样不用每次找一个节点都要找。而是存在一个属性内 - -```python -class Solution: - # root 树的最大path - def maxPathSum(self, root: Optional[TreeNode]) -> int: - if not root : - return 0 - left_child_max = self.maxPathSum(root.left) if root.left else -1001 - right_child_max = self.maxPathSum(root.right) if root.right else -1001 - start_l_max = root.left.startRootMax if root.left else 0 - start_r_max = root.right.startRootMax if root.right else 0 - child_max = max(left_child_max,right_child_max) - root.startRootMax = root.val + max(0, start_l_max, start_r_max) - # print(f'val-{root.val},sMax-{root.startRootMax},{left_child_max}-{right_child_max}-{root.val + start_l_max + start_r_max}') - include_root = max(root.val, root.startRootMax, root.val + start_l_max + start_r_max) - return max(include_root,child_max) -``` - -这是一个题解的方法,可以参考一下:很简洁 - -```python -class Solution: - def maxPathSum(self, root: Optional[TreeNode]) -> int: - res = -inf - def findmax(root): - if not root: - return 0 - nonlocal res - left = findmax(root.left) - if left < 0: - left = 0 - right = findmax(root.right) - if right < 0: - right = 0 - if left + right + root.val > res: - res = left + right + root.val - return max(left,right) + root.val - findmax(root) - return res -``` - -参考后来写的方法!!!!!!!这个写的可以 -```python -class Solution: - def maxPathSum(self, root: Optional[TreeNode]) -> int: - self.ans = -inf - # 从root开始的最大path,但是在每个节点判断 root 连接两边的是不是更大 - def dfs(root): - if not root : return 0 - left = max(dfs(root.left),0) - right = max(dfs(root.right),0) - # print(root.val,left,right,left+right+root.val) - self.ans = max(self.ans,left+right+root.val) - return root.val + max(left,right) - dfs(root) - return self.ans -``` - -## Trash: 超时了 - -第一想法是这样的 但是有两层递归 $O(n^2)$ 太慢了 - -```python -class Solution: - # root 树的最大path - def maxPathSum(self, root: Optional[TreeNode]) -> int: - # 以root为起点的 path 最大值 - def startRootMax(root) : - if not root : - return 0 - return root.val + max(0, startRootMax(root.left), startRootMax(root.right)) - if not root : - return 0 - start_root = max(root.val, startRootMax(root), root.val + startRootMax(root.left)+startRootMax(root.right)) - return max(start_root,self.maxPathSum(root.left) if root.left else -1001,self.maxPathSum(root.right) if root.right else -1001) -``` - -# 51. [岛屿数量](https://leetcode.cn/problems/number-of-islands/)200-251224 - -## 思路一:DFS - -深度搜索,处理越界。如果还是1则置为已经遍历过 - -```python -class Solution: - def numIslands(self, grid: List[List[str]]) -> int: - def dfs(i,j): - if i<0 or i>=len(grid): return 0 - if j<0 or j>=len(grid[0]): return 0 - if grid[i][j] != '1' : return 0 - grid[i][j] = '2' - dfs(i-1,j) - dfs(i+1,j) - dfs(i,j-1) - dfs(i,j+1) - return 1 - ans = 0 - for i in range(len(grid)) : - for j in range(len(grid[0])) : - ans += dfs(i,j) - return ans -``` - -## 思路二:BFS 队列和栈 在入的时候就标记 - -发现一个问题:如果这么写的话会超时 因为同一个点多次入队。 - -```python -class Solution: - def numIslands(self, grid: List[List[str]]) -> int: - r = len(grid) - c = len(grid[0]) - ans = 0 - for i in range(r) : - for j in range(c) : - if grid[i][j] == '1' : - ans += 1 - que = deque([(i,j)]) - while que : - row,col = que.popleft() - grid[row][col] = '2' - for x,y in [ (row-1,col), (row+1,col), (row,col-1), (row,col+1) ] : - if 0<=x int: - r = len(grid) - c = len(grid[0]) - ans = 0 - for i in range(r) : - for j in range(c) : - if grid[i][j] == '1' : - ans += 1 - grid[i][j] = '2' - que = deque([(i,j)]) - while que : - row,col = que.popleft() - # grid[row][col] = '2' - for x,y in [ (row-1,col), (row+1,col), (row,col-1), (row,col+1) ] : - if 0<=x int: - r = len(grid) - c = len(grid[0]) - self.ans = 0 - def dfs(i,j) : - self.ans += 4 - grid[i][j] = 2 - for x,y in [ (i-1,j), (i+1,j), (i,j-1), (i,j+1) ]: - if 0<=x= 1 : - self.ans -= 1 - if 0<=x int: - r = len(grid) - c = len(grid[0]) - def BFS(que: List): - time = 0 - if not que : return 0 - que = deque(que) - next_que = [] - while que : - row,col = que.popleft() - for x,y in [ (row+1,col), (row-1,col), (row,col+1), (row,col-1) ] : - if 0<=x int: - r = len(grid) - c = len(grid[0]) - mp = defaultdict(int) - g = copy.deepcopy(grid) - def BFS(i,j,grid) : - time = 1 - que = deque([(i,j)]) - mp[(i,j)] = 0 - next_que = [] - while que : - row,col = que.popleft() - for x,y in [ (row-1,col), (row+1,col), (row,col-1), (row,col+1) ] : - if 0<=x bool: - mp = {} - for l in prerequisites : - if l[0] in mp.keys() : - mp[l[0]].append(l[1]) - else : - mp[l[0]] = [l[1]] - # 判断哈希表中有没有环 - -``` - -## 思路一:DFS - -拓扑排序 省去了排序 只判断环 - -```python -class Solution: - def canFinish(self, numCourses: int, prerequisites: List[List[int]]) -> bool: - status = [0] * numCourses - mp = defaultdict(list) - for i in prerequisites : - mp[i[0]].append(i[1]) - def dfs(u: int): - status[u] = 1 - for v in mp[u]: - if status[v] == 0 : - if dfs(v) : return True - elif status[v] == 1 : - # 有环 - return True - status[u] = 2 - for i in range(numCourses) : - if dfs(i) : return False - return True -``` - -## 思路二:BFS - -```python -class Solution: - def canFinish(self, numCourses: int, prerequisites: List[List[int]]) -> bool: - in_edges = [0] * numCourses - # 维护哈希表 - mp = defaultdict(list) - for i in prerequisites : - mp[i[0]].append(i[1]) - in_edges[i[1]] += 1 - # 找到最先学的 - que = deque() - for i in range(numCourses): - if in_edges[i] == 0 : - que.append(i) - visited = 0 - while que : - visited += 1 - cur = que.popleft() - print(cur,end=' ') - for v in mp[cur] : - in_edges[v] -= 1 - if in_edges[v] == 0 : - que.append(v) - return visited == numCourses -``` - -## 课程表II-DFS - -```python -class Solution: - def findOrder(self, numCourses: int, prerequisites: List[List[int]]) -> List[int]: - # 记录每个节点,维护每个节点的状态,0是还没搜索,1是正在搜索,2是搜索完了 - status = [0] * numCourses - # 先获取依赖关系 - mp = defaultdict(list) - for i in prerequisites : - mp[i[0]].append(i[1]) - existCycle = False - ans = [] - def dfs(u: int) : - status[u] = 1 - for v in mp[u]: - if status[v] == 0 : - if dfs(v) : return True - elif status[v] == 1 : - # 说明遇到了环 - return True - status[u] = 2 - ans.append(u) - return False - - for i in range(numCourses): - if not existCycle and status[i] == 0 : - if dfs(i) : - return [] - - return ans if not existCycle else [] - -``` - -# 54. [实现 Trie (前缀树)](https://leetcode.cn/problems/implement-trie-prefix-tree/)208-251225 - -插入和查找 用 dict 就可以简单实现,所以关键点在于 startWith -如果用暴力的方法那么如下,这个方法的 startwith是不可接受的(1750ms),能不能用一个新的结构来做呢? -```python -class Trie: - - def __init__(self): - self.list = [] - - def insert(self, word: str) -> None: - self.list.append(word) - - def search(self, word: str) -> bool: - return word in self.list - - def startsWith(self, prefix: str) -> bool: - lp = len(prefix) - for v in self.list : - p = v[0:min(lp,len(v))] - if p == prefix : - return True - return False -``` - -稍微为字典优化一下就变成:75ms 还是挺慢的 正态mu是56 -```python -class Trie: - - def __init__(self): - self.list = {} - - def insert(self, word: str) -> None: - self.list[word] = True - sub = '' - for c in word : - sub+=c - if sub not in self.list.keys() : - self.list[sub] = False - - def search(self, word: str) -> bool: - if word not in self.list.keys() : - return False - return self.list[word] - - def startsWith(self, prefix: str) -> bool: - return prefix in self.list.keys() -``` - -## 思路:字典树 - -字典套字典感觉太难理解了 - -### 不好的写法 -递归insert字典树 感觉有点慢 5.24% -```python -def c2i(char) -> int : - return 0 if char=='#' else ord(char) -ord('a') + 1 - -class Trie: - def __init__(self): - self.next = [ None ] * 27 - def insert(self, word: str) -> None: - if not word : return - if word[-1] != '#' : word += '#' - node = Trie() if not self.next[c2i(word[0])] else self.next[c2i(word[0])] - node.insert(word[1:]) - self.next[c2i(word[0])] = node - - def search(self, word: str) -> bool: - return self.startsWith(word+'#') - - def startsWith(self, prefix: str) -> bool: - node = self - for c in prefix : - i = c2i(c) - node = node.next[i] - if not node : return False - return True -``` -### 好的写法之后再写一遍 -```python -class Trie: - def __init__(self): - self.trie = {} - - def insert(self, word: str) -> None: - node = self.trie - for char in word: - ####### 下面这一行是重点 如果 char 在key中 就返回node[char] - ####### 如果不在 就node[char]={}再返回 - node = node.setdefault(char, {}) - node['#'] = True # 标记单词结束 - - def search(self, word: str) -> bool: - return self.startsWith(word+'#') - - def startsWith(self, prefix: str) -> bool: - node = self.trie - for char in prefix: - if char not in node: - return False - node = node[char] - return True -``` - -# 55. [全排列](https://leetcode.cn/problems/permutations/)46-251225 - -复杂度是 $O(n!)$ - -用递归做 -```python -def Digui(nums): - ans = [] - if len(nums) == 1 : - return [[nums[0]]] - for i in range(len(nums)) : - cp = nums[:] - t = cp[0] - cp[0] = cp[i] - cp[i] = t - sub_ans = Digui(cp[1:]) - for b in sub_ans : - a = [cp[0]] + b - ans.append(a) - return ans - -class Solution: - def permute(self, nums: List[int]) -> List[List[int]]: - return Digui(nums) -``` - -题解的做法: -```python -class Solution: - def permute(self, nums: List[int]) -> List[List[int]] : - # index 尾巴 全排列 - def dfs(index): - if index == len(nums): - return res.append(nums[:]) - for i in range(index , len(nums)): - nums[i],nums[index] = nums[index],nums[i] - dfs(index+1) - nums[index],nums[i] = nums[i],nums[index] - res = [] - dfs(0) - return res -``` - -# 56. [子集](https://leetcode.cn/problems/subsets/)78-251229 - -递归,和上一个思路一样。但是这个是组合 把输出分成 1和 后面的 先求解 digui(后面的) 然后再在后面的解中每个和1 结合,加上 后面的解 就是 一个完成的解 -```python -def Digui(nums) : - print(nums) - if len(nums)==1 : - return [nums,[]] - sub_ans = Digui(nums[1:]) - ans = sub_ans[:] - # print(sub_ans) - for s in sub_ans : - a = [nums[0]] + s - print(f'-- {a} {[nums[0]]} {s}') - ans.append(a) - # print(ans) - return ans - -class Solution: - def subsets(self, nums: List[int]) -> List[List[int]]: - return Digui(nums) -``` - -# 57. [电话号码的字母组合](https://leetcode.cn/problems/letter-combinations-of-a-phone-number/)17-20251229 - -递归 - -```python -mp = { - 2:['a','b','c'], - 3:['d','e','f'], - 4:['g','h','i'], - 5:['j','k','l'], - 6:['m','n','o'], - 7:['p','q','r','s'], - 8:['t','u','v'], - 9:['w','x','y','z'], -} - -def c2i(char) : - return ord(char)-ord('0') - -def Digui(str): - index,remain = c2i(str[0]),str[1:] - wl = mp[index] - if len(remain) == 0 : - return wl - sub_ans = Digui(remain) - ans = [] - for sub in sub_ans : - for c in wl : - ans.append(c+sub) - return ans - -class Solution: - def letterCombinations(self, digits: str) -> List[str]: - return Digui(digits) -``` - -# 58. [组合总和](https://leetcode.cn/problems/combination-sum/)39-20251229\* 跳过了 之后自己写一下 - -这个是最快的解法 -```python -class Solution: - def combinationSum(self, candidates: List[int], target: int) -> List[List[int]]: - def backtrack(summ,startIndex): - if summ == target: - res.append(path[:]) - return - for i in range(startIndex,n): - if summ + candidates[i] > target: - break - path.append(candidates[i]) - backtrack(summ+candidates[i],i) - path.pop() - - - res=[] - path = [] - n = len(candidates) - candidates.sort() - summ,startIndex = 0,0 - backtrack(summ,startIndex) - return res -``` - - -# 59. [括号生成](https://leetcode.cn/problems/generate-parentheses/)22-20251230 - -## 思路一:动态规划 递归 Catalan 结构 -这个递归的思路可以看看 重点在于 把每个问题 拆分成 (a)b 找到 ab的子问题,ab可以为空。我觉得本质还是递归,不太懂跟动态规划有什么具体关系,下次刷到可以再想想。 -另外关于拆分子问题这个,其实就是 n-1 对的 sub_ans 然后把这一对新的括号加在任意的位置 -```python -class Solution: - @lru_cache(None) - def generateParenthesis(self, n: int) -> List[str]: - if n == 0: - return [''] - ans = [] - for c in range(n): - for left in self.generateParenthesis(c): - for right in self.generateParenthesis(n-1-c): - ans.append('({}){}'.format(left, right)) - return ans -``` - -## 别的思路 -递归得到所有的 组合,然后用一个函数遍历测试对不对,但是这个复杂度有指数项 不好。 - -# 60. [单词搜索](https://leetcode.cn/problems/word-search/)79-20251230 - -## 无优化的回溯-DFS - -这个方法的时间完全不能接受,找到开头,然后dfs所有的path - -```python -class Solution: - def exist(self, board: List[List[str]], word: str) -> bool: - r = len(board) - c = len(board[0]) - def dfs(i,j,target) : - cur_char = board[i][j] - board[i][j] = '#' - if len(target) == 1 : - board[i][j] = cur_char - return cur_char == target - target_char, next_target_char = target[0], target[1] - if cur_char != target_char : - board[i][j] = cur_char - return False - for t in [(i+1,j),(i-1,j),(i,j+1),(i,j-1)] : - u,v = t[0],t[1] - if 0 <= u < r and 0 <= v < c and board[u][v] != '#' : - if dfs(u,v,target[1:]) : return True - board[i][j] = cur_char - return False - - for i in range(r) : - for j in range(c) : - if board[i][j] == word[0] and dfs(i,j,word) : return True - return False -``` - -题解写的回溯: -```python -class Solution: - def exist(self, board: List[List[str]], word: str) -> bool: - directions = [(0, 1), (0, -1), (1, 0), (-1, 0)] - - def check(i: int, j: int, k: int) -> bool: - if board[i][j] != word[k]: - return False - if k == len(word) - 1: - return True - - visited.add((i, j)) - result = False - for di, dj in directions: - newi, newj = i + di, j + dj - if 0 <= newi < len(board) and 0 <= newj < len(board[0]): - if (newi, newj) not in visited: - if check(newi, newj, k + 1): - result = True - break - - visited.remove((i, j)) - return result - - h, w = len(board), len(board[0]) - visited = set() - for i in range(h): - for j in range(w): - if check(i, j, 0): - return True - - return False -``` - -## 优化方法 - -1. 统计word 中各个字母的数量,如果board 中不符合 就直接 return False -2. 找到 word\[0] word\[-1] 这两个字母哪个在 board 中出现的少,如果 word\[-1] 出现的少,那么就把word reverse 这样可以减少入口的数量。 -```python -class Solution: - def exist(self, board: List[List[str]], word: str) -> bool: - def dfs(i,j,target): - if board[i][j] != target[0] : return False - if len(target) == 1 : return True - res = False - t, board[i][j] = board[i][j], '#' - for (u,v) in [(0, 1), (0, -1), (1, 0), (-1, 0)] : - u,v = i+u, j+v - if 0<=u mp[word[-1]] - for c in word : - mp[c]-=1 - if mp[c]<0 : return False - word = word[::-1] if ifReverse else word[:] - for x in range(len(board)) : - for y in range(len(board[0])) : - if board[x][y] == word[0] and dfs(x,y,word) : - return True - return False -``` - -## 再优化一次 - -延续上面的2思路,是不是找到一个位置,使该位置两边的搜索数量最少,就可以了? -```python -class Solution: - def exist(self, board: List[List[str]], word: str) -> bool: - def dfs(i,j,target): - if board[i][j] != target[0] : return False - if len(target) == 1 : return True - res = False - t, board[i][j] = board[i][j], '#' - for (u,v) in [(0, 1), (0, -1), (1, 0), (-1, 0)] : - u,v = i+u, j+v - if 0<=u List[List[str]]: - l = len(s) - # 用动态规划记录 i,j 是不是回文串 # dp 的本质就是 用已有状态(解) 递推 之后的状态(解) - dp = [ [False] * (l) for i in range(l) ] - for i in range(l) : - for j in range(l): - if i>=j : dp[i][j] = True - for i in range(l,-1,-1) : - for j in range(i+1,l) : - dp[i][j] = dp[i+1][j-1] and s[i] == s[j] - # 到这里位置 记录了每个 i,j(包含) 是否是回文 - - ret = list() - ans = list() - # 下面回溯搜索:从下标i开始,把 s[i:] 切成 若干回文子串,枚举所有可能的切法 - def dfs(i: int): - if i == l: - ret.append(ans[:]) - return - for j in range(i, l): - if dp[i][j]: - ans.append(s[i:j+1]) - dfs(j + 1) - ans.pop() - # 其实意思就是 从 0 开始 然后依次找 从合法的位置分割,对于每个合法的分割位置,再当做开头,再对后面的分割 - # 分割到 l 就把这一次分割的答案记下来 - dfs(0) - return ret -``` - -# 62. [N 皇后](https://leetcode.cn/problems/n-queens/)51-20260104\*\*\*\*\*\* - -## 思路一:按行搜索 - -好难啊😭,看了题解之后按照题解的思路做了一个。题解里面的想法是按行搜索,而不是按照位置搜索。是因为行本身可以作为一个索引,每次搜索一行的答案就行了。这样的话就没有那么难处理。(相比于按照按位置处理) - -```python -class Solution: - def solveNQueens(self, n: int) -> List[List[str]]: - col = set() - diag_add = set() - diag_mns = set() - # 找到 第row行 Q可以放在哪 - def tuple2board(tl): - res = [] - for i,j in tl : - t = ['.'] * n - t[j] = 'Q' - res.append(''.join(t)) - ans.append(res) - - def dfs(row): - if row == n : - tuple2board(tuple_ans) - return - for i in range(n) : - if i in col or (row+i) in diag_add or (row-i) in diag_mns : - continue - col.add(i) - diag_add.add(row+i) - diag_mns.add(row-i) - tuple_ans.append((row,i)) - dfs(row+1) - tuple_ans.pop() - col.remove(i) - diag_add.remove(row+i) - diag_mns.remove(row-i) - tuple_ans = [] - ans = [] - dfs(0) - return ans -``` - -## 思路二:位运算\*\*\*\*\*\* - -pass 还没学暂时 脑子转不动了 - -# 63. [搜索插入位置](https://leetcode.cn/problems/search-insert-position/)35-20260105 - -Binary Search - -第一次写的时候 `def bs(num_list, target): ` 但是这样有个问题是index不好处理 - -```python -class Solution: - def searchInsert(self, nums: List[int], target: int) -> int: - def bs(nl,tar): - l = len(nl) - if l == 0 : return 0 - mid = l//2 - if nl[mid] == tar : return mid - if nl[mid] > tar : - res = bs(nl[:mid],tar) - return res - if nl[mid] < tar : - res = bs(nl[mid+1:],tar) - # 我觉得这个比较关键, 找后部分的数组时候要把现在的 mid 给存下来 - # 还有一种方法是 直接用 left right 去找 - return mid + res + 1 - - return bs(nums,target) -``` - -用left rihgt 写的 bs 这个是 闭闭区间 -```python -class Solution: - def searchInsert(self, nums: List[int], target: int) -> int: - left, right = 0, len(nums) - 1 - while left <= right: - mid = (left + right) // 2 - if target > nums[mid]: - left = mid + 1 - else: - right = mid - 1 - - return left - -``` - -# 64. [搜索二维矩阵](https://leetcode.cn/problems/search-a-2d-matrix/)74-20260105 - -二分搜索,思路挺简单的,先对行首二分搜索,然后对定位到的行继续二分搜索。 - -```python -class Solution: - def searchMatrix(self, matrix: List[List[int]], target: int) -> bool: - # 先找到在哪一行,up down 是个 闭开区间 - up, down = 0, len(matrix) - while up < down : - mid = (up+down)//2 - if matrix[mid][0] == target : return True - if matrix[mid][0] > target : - down = mid - if matrix[mid][0] < target : - up = mid + 1 - nums=matrix[up-1][:] - up, down = 0, len(nums) - while up < down : - mid = (up+down)//2 - if nums[mid] == target : return True - if nums[mid] > target : - down = mid - if nums[mid] < target : - up = mid + 1 - return False -``` - -## 二分搜索的几种写法 注意循环不变量 - -```python -# lower_bound 返回最小的满足 nums[i] >= target 的 i -# 如果数组为空,或者所有数都 < target,则返回 len(nums) -# 要求 nums 是非递减的,即 nums[i] <= nums[i + 1] - -# 闭区间写法 -def lower_bound(nums: List[int], target: int) -> int: - left, right = 0, len(nums) - 1 # 闭区间 [left, right] - while left <= right: # 区间不为空 - # 循环不变量: - # nums[left-1] < target - # nums[right+1] >= target - mid = (left + right) // 2 - if nums[mid] < target: - left = mid + 1 # 范围缩小到 [mid+1, right] - else: - right = mid - 1 # 范围缩小到 [left, mid-1] - return left - -# 左闭右开区间写法 -def lower_bound2(nums: List[int], target: int) -> int: - left = 0 - right = len(nums) # 左闭右开区间 [left, right) - while left < right: # 区间不为空 - # 循环不变量: - # nums[left-1] < target - # nums[right] >= target - mid = (left + right) // 2 - if nums[mid] < target: - left = mid + 1 # 范围缩小到 [mid+1, right) - else: - right = mid # 范围缩小到 [left, mid) - return left # 或者 right - -# 开区间写法 -def lower_bound3(nums: List[int], target: int) -> int: - left, right = -1, len(nums) # 开区间 (left, right) - while left + 1 < right: # 区间不为空 - mid = (left + right) // 2 - # 循环不变量: - # nums[left] < target - # nums[right] >= target - if nums[mid] < target: - left = mid # 范围缩小到 (mid, right) - else: - right = mid # 范围缩小到 (left, mid) - return right - -class Solution: - def searchInsert(self, nums: List[int], target: int) -> int: - return lower_bound(nums, target) # 选择其中一种写法即可 - -作者:灵茶山艾府 -链接:https://leetcode.cn/problems/search-insert-position/solutions/2023391/er-fen-cha-zhao-zong-shi-xie-bu-dui-yi-g-nq23/ -来源:力扣(LeetCode) -著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。 -``` - -# 65. [在排序数组中查找元素的第一个和最后一个位置](https://leetcode.cn/problems/find-first-and-last-position-of-element-in-sorted-array/)34-20260105 - -先用二分找到这个数的位置,然后找下一个数的位置,得到结果 - -```python -class Solution: - def searchRange(self, nums: List[int], target: int) -> List[int]: - # 找到的是 左到右 第一个大于或等于 target 的 index - def bs(nums_list,tar) : - # print(nums_list,tar) - left, right = 0, len(nums_list) - while left < right : - mid = (left+right)//2 - if nums_list[mid] < tar : left = mid + 1 - if nums_list[mid] >= tar : right = mid - return left - if not nums : return [ -1, -1 ] - start = bs(nums,target) - if start >= len(nums) or nums[start] != target : return [ -1, -1 ] - return [ start, bs(nums,target+1)-1 ] -``` - -但是这样要两次 bs ,(而且我好像发现 二分搜索 闭闭区间 好像更好写) - -```python -def findlowerindex(nums: List[int], target: int)-> int: - left = 0 - right = len(nums)-1 - - while left<=right: - mid = (left + right )//2 - if nums[mid] List[int]: - if findlowerindex(nums,target)==len(nums) or nums[findlowerindex(nums,target)] != target: - return [-1,-1] - return[findlowerindex(nums, target),findlowerindex(nums, target+1)-1] -``` - -# 66. [搜索旋转排序数组](https://leetcode.cn/problems/search-in-rotated-sorted-array/)33-20260105 - -做一下 [寻找旋转排序数组中的最小值](https://leetcode.cn/problems/find-minimum-in-rotated-sorted-array/)这个可以,我觉得这个66题目的下标处理很麻烦,一定要注意 - -## 思路一:两次bs - -```python -class Solution: - # 153. 寻找旋转排序数组中的最小值(返回的是下标) - def findMin(self, nums: List[int]) -> int: - left, right = 0, len(nums) - 1 - while left < right: - mid = (left + right) // 2 - if nums[mid] < nums[right]: - right = mid - else: - left = mid + 1 - return right - - # 有序数组中找 target 的下标 - def lower_bound(self, nums: List[int], left: int, right: int, target: int) -> int: - while left + 1 < right: # 开区间不为空 - mid = (left + right) // 2 - # 循环不变量: - # nums[right] >= target - # nums[left] < target - if nums[mid] >= target: - right = mid # 范围缩小到 (left, mid) - else: - left = mid # 范围缩小到 (mid, right) - return right if nums[right] == target else -1 - - def search(self, nums: List[int], target: int) -> int: - i = self.findMin(nums) - if target > nums[-1]: # target 在第一段 - return self.lower_bound(nums, -1, i, target) # 开区间 (-1, i) - # target 在第二段 - return self.lower_bound(nums, i - 1, len(nums), target) # 开区间 (i-1, n) -``` - -## 思路二:一次bs 这个我还没看懂 - -```python -class Solution: - def search(self, nums: List[int], target: int) -> int: - n = len(nums) - l, r = 0, n - 1 - while l < r: - mid = (l + r) // 2 - x = nums[mid] - if x >= nums[0] and (target > x or target < nums[0]): - l = mid + 1 - elif x < target < nums[0]: - l = mid + 1 - else: - r = mid - return -1 if nums[r] != target else r -``` -# 67. [寻找旋转排序数组中的最小值](https://leetcode.cn/problems/find-minimum-in-rotated-sorted-array/)153-20260105 - -我觉得难点在下标处理 -```python -class Solution: - def findMin(self, nums: List[int]) -> int: - left, right = 0, len(nums)-1 - while left < right : - mid = (left+right)//2 - if nums[mid] < nums[right] : - right = mid - else : left = mid + 1 - # print(left,right) - return nums[left] -``` - -```python -class Solution: - def findMin(self,nums): - left, right = 0, len(nums)-1 - while left < right : - mid = (left+right)//2 - if nums[mid] <= nums[right] : - right = mid - else : - left = mid + 1 - print(left,right) - return nums[left] -``` - -# 68. [寻找两个正序数组的中位数](https://leetcode.cn/problems/median-of-two-sorted-arrays/)4-20260105\*\*\*\*\*\*\*\* - -这个题目挺重要的 看一下官方题解吧 - -## 思路一:按大小依次数 硬找中位数 这个方法不太好 - -```python -class Solution: - # 双指针 移动 len/2 次 找到 - def findMedianSortedArrays(self, nums1: List[int], nums2: List[int]) -> float: - l1, l2 = len(nums1), len(nums2) - if l1 == 1 and l2 == 1 : return (nums1[0] + nums2[0]) /2 - nums1.append(9999) - nums2.append(9999) - # mid 在 第几个 - midl = (l1+l2-1)//2 + 1 - midr = (l1+l2)//2 + 1 - count, p1, p2 = 0, -1, -1 - # print(l1,l2,midl,midr) - ans = 0 - while True : - if nums1[p1+1] < nums2[p2+1] : - p1 += 1 - t = nums1[p1] - else : - p2 += 1 - t = nums2[p2] - count += 1 - # print(count,t) - if count == midl : - ans += t - if count == midr : - ans += t - break - return ans/2 -``` - -## 思路二:双指针 分组 找到两组 - -一个 [题解](https://leetcode.cn/problems/median-of-two-sorted-arrays/solutions/2950686/tu-jie-xun-xu-jian-jin-cong-shuang-zhi-z-p2gd) 写的挺好的 可以看一下: 这个方法的关键想法在于怎么把两个数组分成两组。我觉得写的特别好。从一个的头和另一个的尾巴开始遍历。找到符合条件的。只到分组成功,一定要再看一下这个题解。这俩时间复杂度都是 m+n -```python -class Solution: - def findMedianSortedArrays(self, a: List[int], b: List[int]) -> float: - if len(a) > len(b): - a, b = b, a # 保证下面的 i 可以从 0 开始枚举 - - m, n = len(a), len(b) - a = [-inf] + a + [inf] - b = [-inf] + b + [inf] - - # 枚举 nums1 有 i 个数在第一组 - # 那么 nums2 有 j = (m + n + 1) // 2 - i 个数在第一组 - i, j = 0, (m + n + 1) // 2 - while True: - if a[i] <= b[j + 1] and a[i + 1] > b[j]: # 写 >= 也可以 - max1 = max(a[i], b[j]) # 第一组的最大值 - min2 = min(a[i + 1], b[j + 1]) # 第二组的最小值 - return max1 if (m + n) % 2 else (max1 + min2) / 2 - i += 1 # 继续枚举 - j -= 1 -``` - -## 思路三:二分查找\* - -```python -class Solution: - def findMedianSortedArrays(self, nums1: List[int], nums2: List[int]) -> float: - def getKthElement(k): - """ - - 主要思路:要找到第 k (k>1) 小的元素,那么就取 pivot1 = nums1[k/2-1] 和 pivot2 = nums2[k/2-1] 进行比较 - - 这里的 "/" 表示整除 - - nums1 中小于等于 pivot1 的元素有 nums1[0 .. k/2-2] 共计 k/2-1 个 - - nums2 中小于等于 pivot2 的元素有 nums2[0 .. k/2-2] 共计 k/2-1 个 - - 取 pivot = min(pivot1, pivot2),两个数组中小于等于 pivot 的元素共计不会超过 (k/2-1) + (k/2-1) <= k-2 个 - - 这样 pivot 本身最大也只能是第 k-1 小的元素 - - 如果 pivot = pivot1,那么 nums1[0 .. k/2-1] 都不可能是第 k 小的元素。把这些元素全部 "删除",剩下的作为新的 nums1 数组 - - 如果 pivot = pivot2,那么 nums2[0 .. k/2-1] 都不可能是第 k 小的元素。把这些元素全部 "删除",剩下的作为新的 nums2 数组 - - 由于我们 "删除" 了一些元素(这些元素都比第 k 小的元素要小),因此需要修改 k 的值,减去删除的数的个数 - """ - - index1, index2 = 0, 0 - while True: - # 特殊情况 - if index1 == m: - return nums2[index2 + k - 1] - if index2 == n: - return nums1[index1 + k - 1] - if k == 1: - return min(nums1[index1], nums2[index2]) - - # 正常情况 - newIndex1 = min(index1 + k // 2 - 1, m - 1) - newIndex2 = min(index2 + k // 2 - 1, n - 1) - pivot1, pivot2 = nums1[newIndex1], nums2[newIndex2] - if pivot1 <= pivot2: - k -= newIndex1 - index1 + 1 - index1 = newIndex1 + 1 - else: - k -= newIndex2 - index2 + 1 - index2 = newIndex2 + 1 - - m, n = len(nums1), len(nums2) - totalLength = m + n - if totalLength % 2 == 1: - return getKthElement((totalLength + 1) // 2) - else: - return (getKthElement(totalLength // 2) + getKthElement(totalLength // 2 + 1)) / 2 - - -作者:力扣官方题解 -链接:https://leetcode.cn/problems/median-of-two-sorted-arrays/solutions/258842/xun-zhao-liang-ge-you-xu-shu-zu-de-zhong-wei-s-114/ -来源:力扣(LeetCode) -著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。 -``` -## 思路四:二分发划分数组\* - -```python -class Solution: - def findMedianSortedArrays(self, a: List[int], b: List[int]) -> float: - if len(a) > len(b): - a, b = b, a - - m, n = len(a), len(b) - # 循环不变量:a[left] <= b[j+1] - # 循环不变量:a[right] > b[j+1] - left, right = -1, m - while left + 1 < right: # 开区间 (left, right) 不为空 - i = (left + right) // 2 - j = (m + n - 3) // 2 - i - if a[i] <= b[j + 1]: - left = i # 缩小二分区间为 (i, right) - else: - right = i # 缩小二分区间为 (left, i) - - # 此时 left 等于 right-1 - # a[left] <= b[j+1] 且 a[right] > b[j'+1] = b[j],所以答案是 i=left - i = left - j = (m + n - 3) // 2 - i - ai = a[i] if i >= 0 else -inf - bj = b[j] if j >= 0 else -inf - ai1 = a[i + 1] if i + 1 < m else inf - bj1 = b[j + 1] if j + 1 < n else inf - max1 = max(ai, bj) - min2 = min(ai1, bj1) - return max1 if (m + n) % 2 else (max1 + min2) / 2 - -作者:灵茶山艾府 -链接:https://leetcode.cn/problems/median-of-two-sorted-arrays/solutions/2950686/tu-jie-xun-xu-jian-jin-cong-shuang-zhi-z-p2gd/ -来源:力扣(LeetCode) -著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。 -``` - -# 69. [有效的括号](https://leetcode.cn/problems/valid-parentheses/)20-20260105 - -用栈即可 比较简单这个 - -```python -class Solution: - def isValid(self, s: str) -> bool: - def isPair(c1,c2): - if c1 == '(' : return c2==')' - if c1 == '{' : return c2=='}' - if c1 == '[' : return c2==']' - stk = ['#'] - for c in s : - if isPair(stk[-1],c) : - print(c) - stk.pop() - else : stk.append(c) - return len(stk) == 1 -``` - -尝试优化 但是好像没效果 -```python -class Solution: - def isValid(self, s: str) -> bool: - def isPair(c1,c2): - if c1 == '(' : return c2==')' - if c1 == '[' : return c2==']' - if c1 == '{' : return c2=='}' - def isError(c1,c2): - if c1 == '(' : return c2==']' or c2=='}' - if c1 == '[' : return c2==')' or c2=='}' - if c1 == '{' : return c2==']' or c2==')' - stk = ['#'] - if len(s)%2 == 1 : return False # 这个 mod2 不要忘掉 - for c in s : - if isError(stk[-1],c) : return False - if isPair(stk[-1],c) : - print(c) - stk.pop() - else : stk.append(c) - return len(stk) == 1 -``` - -# 70. [最小栈](https://leetcode.cn/problems/min-stack/)155-20260105 - -## 思路:单调栈 - -用单调栈试试: - -```python -class MinStack: - - def __init__(self): - self.stk = [] - self.min_stk = [+inf] # monotonic stack - - def push(self, val: int) -> None: - self.stk.append(val) - if val <= self.min_stk[-1] : self.min_stk.append(val) - - def pop(self) -> None: - v = self.stk.pop() - if v == self.min_stk[-1] : self.min_stk.pop() - - def top(self) -> int: - return self.stk[-1] - - def getMin(self) -> int: - return self.min_stk[-1] -``` - -题解写法: - -```python -class MinStack: - def __init__(self): - # 这里的 0 写成任意数都可以,反正用不到 - self.st = [(0, inf)] # 栈底哨兵 - - def push(self, val: int) -> None: - self.st.append((val, min(self.st[-1][1], val))) - - def pop(self) -> None: - self.st.pop() - - def top(self) -> int: - return self.st[-1][0] - - def getMin(self) -> int: - return self.st[-1][1] - -作者:灵茶山艾府 -链接:https://leetcode.cn/problems/min-stack/solutions/2974438/ben-zhi-shi-wei-hu-qian-zhui-zui-xiao-zh-x0g8/ -来源:力扣(LeetCode) -著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。 -``` - -## 思考:最小堆 - -维护一个栈记录数据,以及一个小顶堆,维护最小值。查看的速度是 O1 ,插入删除的速度是 :$O(logn)$ -这个方法比上面的慢 - -# 71. [字符串解码](https://leetcode.cn/problems/decode-string/)394-20260105没写出来\*\*\*\*\*\*\*\* - -这个题我没写出来 -## 思路一:辅助栈法 - -遇到 \[ 时候 :记录 重复次数,处理到当前之前的字符结果,并把这个临时结果 res 置为 空 -遇到 \] 时候 :弹栈 此时 两个括号内的内容 在 res 中,然后括号前的内容在栈顶,拼接即可 得到新的 res - -```python -class Solution: - def decodeString(self, s: str) -> str: - stack, res, multi = [], "", 0 - for c in s: - if c == '[': - stack.append([multi, res]) - res, multi = "", 0 - elif c == ']': - cur_multi, last_res = stack.pop() - res = last_res + cur_multi * res - elif '0' <= c <= '9': - multi = multi * 10 + int(c) - else: - res += c - return res -``` - -## 思路二:递归 - -递归处理括号内的字符串,遇到括号就dfs 传入的 位置 i 标记了 这个括号内字符串的开始,感觉和上面的本质上没有区别 - -```python -class Solution: - def decodeString(self, s: str) -> str: - def dfs(s, i): - res, multi = "", 0 - while i < len(s): - if '0' <= s[i] <= '9': - multi = multi * 10 + int(s[i]) - elif s[i] == '[': - i, tmp = dfs(s, i + 1) - res += multi * tmp - multi = 0 - elif s[i] == ']': - return i, res - else: - res += s[i] - i += 1 - return res - return dfs(s,0) -``` - -# 72. [每日温度](https://leetcode.cn/problems/daily-temperatures/)739-20260105 - -## 思路一:单调栈 - -这个题目 和 单调递减栈 接雨水那个好像啊 ,先试试。 - -```python -class Solution: - # 用这个例子想一下 - # 4 5 6 2 0 3 7 4 - # 1 1 4 2 1 1 0 0 - def dailyTemperatures(self, t: List[int]) -> List[int]: - # 单调递减栈,记录index,被弹走时计算 index差值 最后留在stk中的就是 0 用 0 初始化 - stk = [0] - ans = [0] * len(t) - for i in range(1,len(t)) : - while stk and t[i] > t[stk[-1]] : - index = stk.pop() - ans[index] = i - index - stk.append(i) - return ans -``` - -## 思路二:别的写的另一种方法 暂时没仔细看 - -```python -class Solution: - def dailyTemperatures(self, temperatures: List[int]) -> List[int]: - n = len(temperatures) - hottest = 0 - answer = [0] * n - - for curr_day in range(n - 1, -1, -1): - current_temp = temperatures[curr_day] - if current_temp >= hottest: - hottest = current_temp - continue - # [73,74,75,71,69,72,76,73] - days = 1 - while temperatures[curr_day + days] <= current_temp: - # Use information from answer to search for the next warmer day - days += answer[curr_day + days] - answer[curr_day] = days - - return answer -``` - -# 73. [柱状图中最大的矩形](https://leetcode.cn/problems/largest-rectangle-in-histogram/)84-20260106\*\* - -## 思路一:单调栈 - -关键在于 对每个高度遍历的想法 我觉得,之后的就比较简单了 - -```python -# 单调栈 对每根柱子h找到 第一个 小于h 的 前一个就是边界 -class Solution: - def largestRectangleArea(self, heights: List[int]) -> int: - # 类似温度的方法 记录 下一个小于他的位置 - def nextLess(nums): - stk = [] - res = [ 0 ] * len(nums) - for i in range(len(nums)): - while stk and nums[stk[-1]] > nums[i] : - top = stk.pop() - res[top] = i-top - stk.append(i) - return res - ans = 0 - right = nextLess(heights) - left = nextLess(heights[::-1])[::-1] - for i in range(len(heights)) : - r = i+right[i]-1 if right[i] != 0 else len(heights)-1 - l = i-left[i]+1 if left[i] != 0 else 0 - ans = max( (r-l+1)*heights[i] , ans ) - return ans - -``` - -下面这个代码是 找 每个元素 **左边** 第一个小于他的,维护 单调递增栈 出站不更新,入栈之前更新。 上面的写法是找右边第一个小/大于他的,是在 pop 时候更新。 -```python - for i,n in enumerate(heights) : - top = 0 - while stk and heights[stk[-1]] > n : - top = stk.pop() - res[i] = i - stk[-1] if stk else 0 - stk.append(i) - print(res) -``` - -但是这个算法的复杂度虽然是 $O(n)$ 但是 速度还是比较慢 遍历了三次数组 ,下面用一次遍历解决问题 - -## 思路二:单调栈+优化 (最优 - -既然入栈确定左边界 出栈确定右边界 能不能一次确定h的两个边界 -```python -class Solution: - # 对一个 H - # 找到 右边 第一个比他小的 (pop后更新) 单调递增栈 - # 找到 左边 第一个比他小的 (append前更新) 单调递增栈 - def largestRectangleArea(self, heights: List[int]) -> int: - # 维护单调递增栈 保证每个元素都能出入栈 - heights = [0]+heights+[0] - stk = [] - left, right = [0]*len(heights),[0]*len(heights) - for i,n in enumerate(heights) : - while stk and n <= heights[stk[-1]] : - top = stk.pop() - right[top] = i - left[i] = stk[-1] if stk else 0 - stk.append(i) - return max((right[i]-left[i]-1)*heights[i] for i in range(len(heights))) -``` - -这个优化 还是要遍历两次,遍历一次的是:(\*\*\*\*\*\*\*\*\*这个没自己写过) - -```python -class Solution: - def largestRectangleArea(self, heights: List[int]) -> int: - # heights前面补上零,原因是单个柱子也可以拿出来计算 - # 同时在末尾也加上零,否则无法触发剩余在栈中元素的计算 - heights.insert(0, 0) - heights.append(0) - stack = [0] - res = 0 - for i in range(1, len(heights)): - h = heights[i] - if stack and h == heights[stack[-1]]: - stack.pop() - else: - while stack and h < heights[stack[-1]]: - mid = stack.pop() - l, r = stack[-1], i - res = max(res, heights[mid] * (r - l - 1)) - stack.append(i) - return res -``` - -## 超时 - -1. 遍历index - -```python -class Solution: - def largestRectangleArea(self, heights: List[int]) -> int: - ans = 0 - l = len(heights) - for i in range(l): - for j in range(i,l): - S = min(heights[i:j+1])*(j-i+1) - ans = max(ans,S) - return ans -``` - -2. 遍历高度:遍历高度 h 为什么能找到最优解?是因为这种方法找到了 最小值为 h,也就是高度为 h 能组成的最大矩形。 -```python -# 遍历高 -class Solution: - def largestRectangleArea(self, heights: List[int]) -> int: - ans = 0 - l = len(heights) - for i in range(l): - h = heights[i] - left, right = i, i - while left > 0 and heights[left-1] >= h : - left -= 1 - while right < l-1 and heights[right+1] >= h : - right += 1 - S = h*(right-left+1) - print(left,right,h,S) - ans = max(S,ans) - return ans -``` - -# 74. [数组中的第K个最大元素](https://leetcode.cn/problems/kth-largest-element-in-an-array/)215-20260109 - -[这个题解写的很好](https://leetcode.cn/problems/kth-largest-element-in-an-array/solutions/2361969/215-shu-zu-zhong-de-di-k-ge-zui-da-yuan-d786p) 这个题的思路就是用快速排序 的思想,把一个数放入正确的位置,如果放进去的位置刚好是k 那就 return。难得地方在于快速排序怎么写。 - -这个思路最简单的实现方式 - -```python -class Solution: - def findKthLargest(self, nums: List[int], k: int) -> int: - def quick_select(nums,k): - pivot = random.choice(nums) - le, gt, eq = [],[],[] - for n in nums : - if n < pivot : le.append(n) - elif n > pivot : gt.append(n) - elif n == pivot : eq.append(n) - if len(gt) >= k : - return quick_select(gt,k) - if len(gt+eq) >= k : - return pivot - return quick_select(le,k-len(gt+eq)) - return quick_select(nums,k) -``` - - -这个会超时,因为这个每次选择的pivot都是 第一个 如果数组有序的话, 会导致 时间复杂度到 $O(n^2)$ ,那么怎么改呢? -```python -class Solution: - def findKthLargest(self, nums: List[int], k: int) -> int: - # 闭闭 - def quick_select(i,j): - l,r = i,j - pivot = nums[i] - while i < j : - while i < j and pivot >= nums[j] : - j -= 1 - nums[i] = nums[j] - while i < j and nums[i] >= pivot : - i += 1 - nums[j] = nums[i] - nums[i] = pivot - # print(l,r,nums,i,j) - if i+1 == k : return pivot - if i+1 < k : return quick_select(i+1,r) - if i+1 > k : return quick_select(l,i-1) - - return quick_select(0,len(nums)-1) -``` - -这样还是超时 超时的关键在于 while 中的两个 大于等于号,之后想办法优化一下。 -另外 内层第一个while后的交换 其实可以写到后面 一起交换。但是交换后要移动一下ij ,通过这种方式可能可以把 等号去掉 -```python -class Solution: - def findKthLargest(self, nums: List[int], k: int) -> int: - # 闭闭 - def quick_select(i,j): - l,r = i,j - # pivot_index = random.randint(i,j) - # nums[i], nums[pivot_index] = nums[pivot_index], nums[i] - pivot = nums[i] - while i < j : - while i < j and pivot >= nums[j] : - j -= 1 - nums[i] = nums[j] - while i < j and nums[i] >= pivot : - i += 1 - nums[j] = nums[i] - nums[i] = pivot - # print(l,r,nums,i,j) - if i+1 == k : return pivot - if i+1 < k : return quick_select(i+1,r) - if i+1 > k : return quick_select(l,i-1) - - return quick_select(0,len(nums)-1) -``` - -看一下别人的: - - -邪修: 桶排序 -```python -class Solution: - def findKthLargest(self, nums: List[int], k: int) -> int: - bound = int(1e4) - max_size = 2 * bound + 1 - print(max_size) - num_cnt = [0] * max_size - for num in nums: - num_cnt[num + bound] += 1 - for i in range(max_size-1, -1, -1): - if k <= num_cnt[i]: - return i - bound - else: - k -= num_cnt[i] -``` - -## 快速排序笔记 - -首先 需要明确的是 快速排序的本质是 找到一个数组中 一个基准数的位置。这个基准数 一般取 第一个位置 。然后各放两个指针,左边的指向基准数,右边的指向末尾。算法流程就是,逐个向前检查后面的元素是不是比 这个 pivot 大,大就继续检查 直到 右指针 的数 小于 pivot 那么就和 pivot 交换位置。现在pivot 在 右边,就一次检查左边的。交换位置。 下面例子是降序 - -- 交换的写法 -```python - # 闭闭 - def quick_select(i,j): - pivot = nums[i] - print(nums,pivot) - while i < j : - while i < j and nums[i] >= nums[j] : - j -= 1 - print(i,j) - nums[j], nums[i] = nums[i], nums[j] - print(nums) - while i < j and nums[i] >= nums[j] : - i += 1 - print(i,j) - nums[j], nums[i] = nums[i], nums[j] - print(nums) - print(nums,i,j,pivot) - nums[i] = pivot -# 因为要交换 下面的写法更好 - def quick_select(i,j): - pivot = nums[i] - print(nums,pivot) - while i < j : - while i < j and pivot >= nums[j] : - j -= 1 - print(i,j) - nums[j], nums[i] = nums[i], nums[j] - print(nums) - while i < j and nums[i] >= pivot : - i += 1 - print(i,j) - nums[j], nums[i] = nums[i], nums[j] - print(nums) - print(nums,i,j,pivot) - nums[i] = pivot -``` - -- 不交换的写法,上面的 流程中 找到 j 比 pivot大,那么只需要把 说明这个 j 应该在前面。(而不是这个j 应该变小,因为我们还不知道i对不对)所以就把i变成j -```python - # 闭闭 - def quick_select(i,j): - pivot = nums[i] - print(nums,pivot,i,j) - while i < j : - while i < j and pivot >= nums[j] : - j -= 1 - print(i,j) - nums[i] = nums[j] - print(nums) - while i < j and nums[i] >= pivot : - i += 1 - print(i,j) - nums[j] = nums[i] - print(nums) - nums[i] = pivot - print(nums,i,j,pivot) -``` - -# 75. [前 K 个高频元素](https://leetcode.cn/problems/top-k-frequent-elements/)347-20260110 - -## 思路一:优先队列 - -简单的想法 统计格式 然后优先队列 -```python -class Solution: - def topKFrequent(self, nums: List[int], k: int) -> List[int]: - KK = k - cnt = defaultdict(int) - for n in nums : - cnt[n] += 1 - p = [] - for k,v in cnt.items() : - p.append((k,v)) - p = sorted(p,key=lambda x: x[1],reverse=True) - return [ p[i][0] for i in range(KK) ] -``` - -## 思路二:堆 - -```python -from typing import List -from collections import Counter -import heapq - -class Solution: - def topKFrequent(self, nums: List[int], k: int) -> List[int]: - freq = Counter(nums) - heap = [] # (count, num) - - for num, c in freq.items(): - if len(heap) < k: - heapq.heappush(heap, (c, num)) - else: - if c > heap[0][0]: - heapq.heapreplace(heap, (c, num)) - - return [num for c, num in heap] -``` - -# 76. [数据流的中位数](https://leetcode.cn/problems/find-median-from-data-stream/)295-20260110 - -## 思路一:用堆维护中位数 - -维护一个 大堆 一个 小堆 两边数量均衡 且满足 大堆顶 小于 小堆顶 那么中位数就很好找了。那么怎么维护这样一个结构呢? -不妨先想 怎么维护两个 堆顶 元素的大小。 不妨这样想: 每次都往大堆(升序数组左半部分)中加 然后 把堆顶 冒到 小顶堆(升序数组后半部分),这样 小顶恒大于大顶。 -但是这样有个问题,每次这样的话会导致,大顶为空。那么就加一个判断:如果小顶的元素数量超过了大顶,那么久:小顶吐出来一个给大顶,因为是小顶吐出来的,依然满足这个顺序。 -```python -class MedianFinder: - def __init__(self): - # 前半个的最大 和 后半个的最小 - self.max_heap = [] - self.min_heap = [] - - def addNum(self, num: int) -> None: - # 每次都往 max 中 添加 然后 平衡两边的 min 中push的元素都是从max中来的 - heapq.heappush(self.max_heap,-num) - heapq.heappush(self.min_heap,-heapq.heappop(self.max_heap)) - if len(self.max_heap) < len(self.min_heap) : - heapq.heappush(self.max_heap, -heapq.heappop(self.min_heap)) - return - - def findMedian(self) -> float: - if len(self.max_heap) - len(self.min_heap) == 0 : - return (self.min_heap[0] - self.max_heap[0])/2 - else: return -self.max_heap[0] -``` - -下面这个是比较快的写法。上面的写法 每次都要把元素加入到大堆 然后再做处理,有咩有简单点的方法呢? 这个的关键是 push pop :我们首先明确 两个堆 必须是 大顶的数量 不少于小顶 且最多多一个 才是合理的。而且我们明确,为了保持一致性,两个堆的数据有序性,需要靠堆之间传递元素来满足。 -那么如果两个数量相等,那么就往小顶堆塞一个 然后小顶堆再吐一个给大。反之反之。 -```python -from heapq import * - -class MedianFinder: - def __init__(self): - self.A = [] # 小顶堆,保存较大的一半 - self.B = [] # 大顶堆,保存较小的一半 - - def addNum(self, num: int) -> None: - if len(self.A) != len(self.B): - heappush(self.B, -heappushpop(self.A, num)) - else: - heappush(self.A, -heappushpop(self.B, -num)) - - def findMedian(self) -> float: - if len(self.A) != len(self.B) : - return self.A[0] - else: - return (self.A[0] - self.B[0]) / 2.0 -``` - -## Trash 二分查找找位置 然后插入 -时间复杂度是 $O(n)$,因为有个insert操作 -```python -class MedianFinder: - def __init__(self): - self.nums = [] - - def addNum(self, num: int) -> None: - l,r = 0, len(self.nums)-1 - while l<=r : - mid = (l+r)//2 - #找 第一个大于他的位置 - if self.nums[mid] > num : - r = mid-1 - else : - l = mid+1 - self.nums.insert(l,num) - - def findMedian(self) -> float: - l = len(self.nums) - return (self.nums[l//2] + self.nums[(l-1)//2])/2 -``` - -# 77. [买卖股票的最佳时机](https://leetcode.cn/problems/best-time-to-buy-and-sell-stock/)121-20260112 - -## 思路一:记录每个位置后面的最大值 - -```python -class Solution: - def maxProfit(self, prices: List[int]) -> int: - # 记录每个位置后面的最大值 - t_max = prices[-1] - ans = 0 - for i in range(len(prices)) : - index = len(prices)-i-1 - t_max = max(t_max,prices[index]) - ans = max(t_max-prices[index],ans) - return ans -``` - -不用max 只用 一次len 会加速速度 如果更新了最低价 就不需要 更新ans了 -```python -class Solution: - def maxProfit(self, prices: List[int]) -> int: - # 记录每个位置之前的最小值 - past_min, ans = +inf, 0 - for p in prices : - past_min = p if p < past_min else past_min - ans = p-past_min if (p-past_min) > ans else ans - return ans -``` - -# 78. [跳跃游戏](https://leetcode.cn/problems/jump-game/)55-20260112 - -记录能跳到的最远位置 为什么这个贪心是正确的呢?这个题目中 能到的位置 是连续的 也就是 确定了最远到的位置之后,之前的位置一定都是可以访问的。 nums到farest 之间一定是连续的。 -```python -class Solution: - def canJump(self, nums: List[int]) -> bool: - farest_index, l = 0, len(nums) - for i in range(l) : - if i > farest_index : break - farest_index = max(farest_index,i + nums[i]) - return farest_index >= l-1 -``` - -```python -class Solution: - def canJump(self, nums: List[int]) -> bool: - farest_index, l = 0, len(nums) - for i in range(l) : - if i > farest_index : return False - farest_index = max(farest_index,i + nums[i]) - return True -``` - -# 79. [跳跃游戏 II](https://leetcode.cn/problems/jump-game-ii/)45-20260112 - -## 思路一:贪心 - UES - -每次跳的时候 维护一个边界 边界变的时候 step +1 mlgb 这个贪心太难想了,其实本质是记录了一个 几步最远可以跳到哪里。贪心 有意思 -```python -class Solution: - def jump(self, nums: List[int]) -> int: - # 维护 跳到一个位置时候 能到的最大位置是多少 还是贪心 - step, farest, end, l = 0, 0, 0, len(nums) - if l == 1 :return 0 - for i in range(l) : - farest = max(nums[i] + i, farest) - if i == end : - end = farest - step += 1 - # print(step,end) # 几步最远可以跳到哪 - if end >= l-1 : return step - -``` - -## Trash - 动态规划 但是不好 - -时间复杂度很高 -```python -class Solution: - def jump(self, nums: List[int]) -> int: - # 记录到达每个位置的 最小步数 - l = len(nums) - dp = [+inf] * l - dp[0] = 0 - for i in range(l): - for j in range(1,nums[i]+1) : - if i+j == l : break - dp[i+j] = min(dp[i+j],dp[i]+1) - return dp[-1] - -``` - -有时间可以再做一下这两个 [跳跃游戏 III](https://leetcode.cn/problems/jump-game-iii/) [跳跃游戏 VII](https://leetcode.cn/problems/jump-game-vii/) -# 80. [划分字母区间](https://leetcode.cn/problems/partition-labels/)763-20260112 - -首先 写在外面 - !important: 什么时候用贪心通俗理解:“只顾眼前最优,就能得到最终最优”,无后效性。 这个题目就是很无后效性 -## 思路一:贪心 - -这个就是把start end 排成这样的:然后找分割位置就行 -``` ---- - -- - ---- - - - --- - -- - - - --- - -- -``` - -```python -class Solution: - def partitionLabels(self, s: str) -> List[int]: - start,end,l = {},{},len(s) - for i in range(l) : - cf = s[i] - cl = s[l-1-i] - if cf not in start.keys() : - start[cf] = i - if cl not in end.keys() : - end[cl] = l-i-1 - # print(start,end) - mp = [(start[c],end[c]) for c in start.keys() ] - mp = sorted(mp,key= lambda x: x[0]) - # print(mp) - ans,nans ,split_index = [],0,0 - for item in mp : - if split_index < item[0] : - ans.append(split_index+1-nans) - nans = split_index+1 - split_index = max(item[1],split_index) - ans.append(l-nans) - return ans -``` - -## 思路二:另一种贪心 - -其实我觉得本质是差不多的 可以好好看看别人的代码,这个是别人写的 上面是我自己写 -```python -class Solution: - def partitionLabels(self, s: str) -> List[int]: - last_occ = {} - for i in range(len(s)): - last_occ[s[i]] = i - - start = 0 - end = 0 - ans = [] - for i in range(len(s)): - end = max(end,last_occ[s[i]]) - if i == end: - ans.append(end -start + 1) - start = i + 1 - - return ans -``` - -# 81. [爬楼梯](https://leetcode.cn/problems/climbing-stairs/)70-20260112 - -我觉得这个是很经典的动态规划 像分硬币一样 很简单 - -```python -class Solution: - def climbStairs(self, n: int) -> int: - dp = [1] * (n+1) - for i in range(2,n+1) : - dp[i] = dp[i-1] + dp[i-2] - return dp[n] -``` - -虽然这个题目很简单,但是[题解](https://leetcode.cn/problems/climbing-stairs/solutions/286022/pa-lou-ti-by-leetcode-solution)里面的两个解法很牛逼 可以看看 一个是斐波那契的通项公式 还有一个是矩阵的快速幂 - -# 82. [杨辉三角](https://leetcode.cn/problems/pascals-triangle/)118-20260112 - -```python -class Solution: - def generate(self, numRows: int) -> List[List[int]]: - ans,last = [[1]],[1] - for i in range(2,numRows+1) : - temp = [1]*i - for j in range(1,i-1) : - temp[j] = last[j-1]+last[j] - last = temp - ans.append(temp) - return ans -``` - -下面是题解: -```python -class Solution: - def generate(self, numRows: int) -> List[List[int]]: - ret = list() - for i in range(numRows): - row = list() - for j in range(0, i + 1): - if j == 0 or j == i: - row.append(1) - else: - row.append(ret[i - 1][j] + ret[i - 1][j - 1]) - ret.append(row) - return ret -``` - -# 83. [打家劫舍](https://leetcode.cn/problems/house-robber/)198-20260112 - -这个就是零一背包感觉 也很简单 注意这里的dp是处理到 i ,i 偷或不偷 跟dp没关系 -```python -class Solution: - def rob(self, nums: List[int]) -> int: - # 处理完(偷或者不偷)第i个房子后的 最大值 - dp,l = nums[:],len(nums) - if l == 1 : return nums[0] - dp[1] = max(nums[0:2]) - for i in range(2,l) : - dp[i] = max(dp[i-2]+nums[i],dp[i-1]) - return dp[-1] -``` - -而且只需要看前两个,所以不用开一块内存 - -```python -class Solution: - def rob(self, nums: List[int]) -> int: - if not nums: - return 0 - - size = len(nums) - if size == 1: - return nums[0] - - first, second = nums[0], max(nums[0], nums[1]) - for i in range(2, size): - first, second = second, max(first + nums[i], second) - - return second -``` - -# 84. [完全平方数](https://leetcode.cn/problems/perfect-squares/)279-20260112 - -这个也很简单 -```python -class Solution: - def numSquares(self, n: int) -> int: - dp = [+inf] * (n+1) - dp[0],dp[1] = 0,1 - for i in range(n+1): - for j in range(1,int(sqrt(i))+1) : - dp[i] = min(dp[i],dp[i-j**2]+1) - return dp[n] - -``` - -# 85. [零钱兑换](https://leetcode.cn/problems/coin-change/)322-20260112(位运算\*\*) - -这个也很简答,coins 排序一下 可以break掉一部分。 -```python -class Solution: - def coinChange(self, coins: List[int], amount: int) -> int: - dp = [0] + [+inf] * amount - coins = sorted(coins) - for i in range(1,amount+1) : - for c in coins : - if i-c < 0 : break - dp[i] = min(dp[i], dp[i-c]+1 ) - return dp[amount] if dp[amount] != inf else -1 -``` - -## 思路?位运算 NB -还没看懂 感觉有点牛逼 -```python -class Solution: - def coinChange(self, coins: List[int], amount: int) -> int: - mask = (1 << (amount + 1)) - 1 - dp = 1 - count = 0 - coins = set(coin for coin in coins if coin <= amount) - while not dp >> amount: - count += 1 - new = dp - for coin in coins: - shifted = (dp & (mask >> coin)) << coin - new |= shifted - if new == dp: - return -1 - dp = new - return count -``` - -# 86. [单词拆分](https://leetcode.cn/problems/word-break/)139-20260112 - -## 思路一: 动态规划 -其实最开始的尝试 也就是下面的 dp 设置 是正确的 但是用了 BFS做而不是动态规划 -自己又写了一遍 对了 -```python -class Solution: - def wordBreak(self, s: str, wordDict: List[str]) -> bool: - # 到index 为止(包括) 能不能被组成 - dp = [False] * (len(s)+1) - dp[0] = True - wordDict = set(wordDict) - wordLenList = set() - for w in wordDict: - wordLenList.add(len(w)) - wordLenList = sorted(list(wordLenList)) - for i in range(len(s)+1): - for l in wordLenList : - if i-l < 0: break - dp[i] = dp[i-l] and s[i-l:i] in wordDict - if dp[i] : break - print(i,l,i-l,dp[i-l],s[i-l:i],dp[i]) - print(dp) - return dp[-1] -``` -## Trash-BFS - -BFS 但是不对 -```python -class Solution: - def wordBreak(self, s: str, wordDict: List[str]) -> bool: - #如果就不存在的字母直接 False - WW = '' - for w in wordDict : - WW+=w - charSet = set(WW) - for c in s: - if c not in charSet : return False - - # 到index 为止(包括) 能不能被组成 - dp = [False] * (len(s)+1) - dp[0] = True - wordDict = set(wordDict) - wordlenlist = set() - for w in wordDict : - wordlenlist.add(len(w)) - wordlenlist = sorted(list(wordlenlist)) - print(wordlenlist) - lastTrue = [0] - que = deque([0]) - while que : - index = que.popleft() - for l in wordlenlist : - if index+l > len(s) : break - if s[index:index+l] in wordDict : - dp[index+l] = True - que.append(index+l) - print(dp) - return dp[-1] - -``` - -## 思路二:BFS 题解的写法 - -```python -class Solution: - def wordBreak(self, s: str, wordDict: List[str]) -> bool: - max_len = max(map(len, wordDict)) # 用于限制下面 j 的循环次数 - words = set(wordDict) # 便于快速判断 s[j:i] in words - - @cache # 缓存装饰器,避免重复计算 dfs 的结果(记忆化) - def dfs(i: int) -> bool: - if i == 0: # 成功拆分! - return True - for j in range(i - 1, max(i - max_len - 1, -1), -1): - if s[j:i] in words and dfs(j): - return True - return False - - return dfs(len(s)) -``` - -# 87. [最长递增子序列](https://leetcode.cn/problems/longest-increasing-subsequence/)300-20260112 - -## 思路一: 动态规划 - - dp 数组是 以i为结尾的 子序列 最大长度,这个方法的复杂度是$O(n^2)$ ,怎么优化呢我觉得在找的时候用别的办法。维护一个单调递增栈?下面试一下。 - -```python -class Solution: - def lengthOfLIS(self, nums: List[int]) -> int: - ans = 1 - # 以第i个数为结尾的子序列 (必选i) - dp = [1] * len(nums) - for i in range(len(nums)) : - # 得往前找比他小的 - for j in range(i) : - if nums[j] < nums[i] : - dp[i] = max(dp[i],dp[j]+1) - ans = max(ans,dp[i]) - return ans -``` - -## ERROR -这个是错误的,来个0把前面的弹飞完了 \尴尬 -```python -# 加上单调栈 -class Solution: - def lengthOfLIS(self, nums: List[int]) -> int: - ans = 1 - # 以第i个数为结尾的子序列 (必选i) - dp = [1] * len(nums) - # 维护一个单调栈 简化内层循环 - stk = [] - for i in range(len(nums)) : - while stk and nums[stk[-1]] >= nums[i] : - stk.pop() - stk.append(i) - print(i,stk) - # 得往前找比他小的 - for j in stk : - if nums[j] < nums[i] : - dp[i] = max(dp[i],dp[j]+1) - ans = max(ans,dp[i]) - print(dp) - return ans -``` - -## 思路二:贪心+二分 - -这个太帅了 贪心都太帅了 -```python -#贪心算法,为了让子序列尽可能长,所以就选择尽可能小的值组成上升序列,然后得到尽可能长的上升序列。 -class Solution: - def lengthOfLIS(self, nums: List[int]) -> int: - # 用 dp 维护一个 dp[i] 表示 **长度为 i 的最长上升子序列的末尾元素的最小值** dp 并不直接是一个子序列注意定义 - d = [] - for n in nums: - # 如果这个数能构成上升序列就加进来 - if not d or n > d[-1]: - d.append(n) - # 若构成不了,也要把这个数换进来,用二分查找 找该换到的位置。(为了保证数组尽量上升的慢)更新的是len为 mid 结尾的最小元素 - else: - l, r = 0, len(d) - 1 - loc = r - while l <= r: - mid = (l + r) // 2 - if d[mid] >= n: - loc = mid - r = mid - 1 - else: - l = mid + 1 - d[loc] = n - print(n,d) - return len(d) -``` - -# 88. [乘积最大子数组](https://leetcode.cn/problems/maximum-product-subarray/)152-20260112\* - -这个题目的题解写的很好,看题解吧。重点是维护一个max 一个min -```python -class Solution: - def maxProduct(self, nums: List[int]) -> int: - # dp 以j为结尾的成绩的最大值 - dp_max = nums[:] - dp_min = nums[:] - for i in range(1,len(nums)): - dp_max[i] = max(dp_max[i-1]*nums[i],nums[i],dp_min[i-1]*nums[i]) - dp_min[i] = min(dp_min[i-1]*nums[i],nums[i],dp_max[i-1]*nums[i]) - return max(dp_max) -``` - -# 89. [分割等和子集](https://leetcode.cn/problems/partition-equal-subset-sum/)416-20260115 - -写了一个 $O(n^2)$ 的方法 不是最好的 -```python -class Solution: - def canPartition(self, nums: List[int]) -> bool: - target = sum(nums)/2 - if target%1!=0 : return False - mp = defaultdict(int) - mp[int(target)] = 1 - for n in nums: - ks = list(mp.keys()) - for k in ks: - if k==n : return True - if k-n > 0 : mp[k-n]+=1 - mp[target-n]+=1 - return False - -``` - - -## 思路一:递归 - -```python -from functools import lru_cache - -class Solution: - def canPartition(self, nums: List[int]) -> bool: - #sumV = sum(nums) - # can I find a subset of integers which can - # summ to sumV/2 - - s = sum(nums) - if s % 2 == 1: - return False - - half = s//2 - - def subSum(currSum,i): - if i < 0: - return False - if currSum + nums[i] == half: - return True - if currSum + nums[i] < half: - if subSum(currSum+ nums[i],i-1): - return True - else: - return subSum(currSum,i-1) - return subSum(0,len(nums)-1) -``` - -## 思路二:位运算 -看不懂 -```python -class Solution: - def canPartition(self, nums: List[int]) -> bool: - s = sum(nums) - if s%2:return False - bit = 1 - target = s //2 - for x in nums: - bit |= (bit << x) - return (bit >> target) & 1 == 1 - - # @cache - # def dfs(i, cur): - # if i < 0: - # return False - # #这项选还是不选 - # if cur == target: - # return True - # else: - # return dfs(i-1, cur + nums[i]) or dfs(i-1, cur) - return dfs(n-1, 0) -``` - -# 90. [最长有效括号](https://leetcode.cn/problems/longest-valid-parentheses/)32-20260115\*\*\*\*\* - -## 思路一:栈\*\*\*\*\*\*\*\*\*\* - -注意每次存下( 的位置 ,然后 遇到 )就pop 如果空了就把这个)的位置记录为新的开始 否则 更新答案。 - -```python -class Solution: - def longestValidParentheses(self, s: str) -> int: - # 滑动窗口对应一个栈,start end 是闭开 - ans,stk = 0,[-1] - for i in range(len(s)) : - if s[i] == '(' : stk.append(i) - else : - stk.pop() - if not stk : - stk.append(i) - else : - ans = max(ans,i-stk[-1]) - return ans -``` - -## 思路二:动态规划 没看懂 - -记录以i位置结尾的最大子串 -```python -class Solution: - def longestValidParentheses(self, s: str) -> int: - # 以i位置为结尾的最大子串 - if not s : return 0 - if s=='()' : return 2 - dp = [0] * len(s) - for i in range(1,len(s)) : - if s[i] == '(' : dp[i] = 0 - else : - if s[i-1] == '(' : dp[i] = 2 + (dp[i-2] if i-2 >= 0 else 0) - elif i-dp[i-1]>0 and s[i-dp[i-1]-1]=='(': - dp[i] = dp[i-1]+ (dp[i-dp[i-1]-2] if i-dp[i-1]-2 >=0 else 0) + 2 - return max(dp) -``` - -# 91. [不同路径](https://leetcode.cn/problems/unique-paths/)62-20260115 - -## 思路一:排列组合 -这个不就是排列组合么 -```python -A = [1] * 200 -for i in range(1,200) : - A[i] = A[i-1] * i - -class Solution: - def uniquePaths(self, m: int, n: int) -> int: - # A(m+n-2)/(A(n-1)*A(m-1)) - return int(A[m+n-2]/(A[n-1]*A[m-1])) -``` - -## 思路二:动态规划 - -```python -class Solution: - def uniquePaths(self, m: int, n: int) -> int: - f = [[1] * n] + [[1] + [0] * (n - 1) for _ in range(m - 1)] - print(f) - for i in range(1, m): - for j in range(1, n): - f[i][j] = f[i - 1][j] + f[i][j - 1] - return f[m - 1][n - 1] -``` - -# 92. [最小路径和](https://leetcode.cn/problems/minimum-path-sum/)64-20260115 - -经典动态规划 应该挺简单的 - -```python -class Solution: - def minPathSum(self, grid: List[List[int]]) -> int: - # dp到达某个点的最短距离 - for r in range(1,len(grid)): - grid[r][0] += grid[r-1][0] - for c in range(1,len(grid[0])): - grid[0][c] += grid[0][c-1] - for r in range(1,len(grid)) : - for c in range(1,len(grid[0])) : - grid[r][c] += min(grid[r-1][c],grid[r][c-1]) - return grid[-1][-1] - -``` - -# 93. [最长回文子串](https://leetcode.cn/problems/longest-palindromic-substring/)5-20260115 - -## 很慢 - -dp 用 判断 i,j 是不是 用 前一个判断就行 但是是 $O(n^2)$ 的复杂度 -```python -class Solution: - def longestPalindrome(self, s: str) -> str: - # dp 以 i 为结尾的最长子序列 这个难以实现 试试 i,j 是否构成 回文串 - l = len(s) - isPal = set() - for i in range(l) : - isPal.add((i,i)) - isPal.add((i+1,i)) # cbbd 这种 - ans = s[0] - # ij 是不是 依赖 i+1 j-1 画个i*j图 确定遍历顺序 - for j in range(l) : - for i in range(j,-1,-1): - if (i+1,j-1) in isPal and s[i]==s[j] : - isPal.add((i,j)) - ans = s[i:j+1] if j-i+1 > len(ans) else ans - return ans - -``` - -## 思路一: 中心拓展算法 - -在上面的基础上 ,遍历每个回文中心和 长度 -![](https://cdn.jsdelivr.net/gh/zip95297/zip95297.github.io@main/source/images/%E9%A2%98%E7%9B%AE%E7%AC%94%E8%AE%B0/Pasted%20image%2020260116154906.webp?raw=true) -```python -class Solution: - def longestPalindrome(self, s: str) -> str: - if not s: # 处理空字符串边界case - return "" - - l = len(s) - isPal = set() - # 初始化:单个字符是回文(i,i),空区间(i+1,i)表示偶数长度回文的初始状态 - for i in range(l): - isPal.add((i, i)) - isPal.add((i+1, i)) # 对应偶数长度回文的初始(如cbbd中bb的初始) - ans = s[0] # 初始最长回文为第一个字符 - - # 中心扩展:遍历每个可能的中心(奇数长度中心为i,偶数长度中心为i和i+1) - for i in range(l): - # -------- 处理奇数长度回文(中心为i) -------- - lenPal = 0 # 扩展半径,初始0(仅包含中心i) - # 扩展条件:左右边界不越界,且扩展后的字符相等 - while i - lenPal - 1 >= 0 and i + lenPal + 1 < l: - # 核心:只要左右字符相等,就构成新的回文,无需依赖之前的isPal(简化逻辑) - if s[i - lenPal - 1] == s[i + lenPal + 1]: - new_left = i - lenPal - 1 - new_right = i + lenPal + 1 - isPal.add((new_left, new_right)) - # 更新最长回文 - if new_right - new_left + 1 > len(ans): - ans = s[new_left:new_right + 1] - lenPal += 1 - else: - break # 无法扩展,终止当前中心的奇数扩展 - - # -------- 处理偶数长度回文(中心为i和i+1) -------- - lenPal_even = 0 # 偶数扩展半径,初始0 - while i - lenPal_even >= 0 and i + lenPal_even + 1 < l: - # 核心:偶数回文需要i-lenPal_even 和 i+lenPal_even+1 字符相等 - if s[i - lenPal_even] == s[i + lenPal_even + 1]: - new_left = i - lenPal_even - new_right = i + lenPal_even + 1 - isPal.add((new_left, new_right)) - # 更新最长回文 - if new_right - new_left + 1 > len(ans): - ans = s[new_left:new_right + 1] - lenPal_even += 1 - else: - break # 无法扩展,终止当前中心的偶数扩展 - - return ans -``` - -## 思路二:马拉车算法 Manacher (一般不作为面试内容 - -```python -class Solution: - def expand(self, s, left, right): - """中心扩展算法,计算当前节点的臂长""" - while left >= 0 and right < len(s) and s[left] == s[right]: - left -= 1 - right += 1 - return (right - left - 2) // 2 - - def longestPalindrome(self, s: str) -> str: - # 马拉车算法 - - # 1.初始化 - # 初始化输出结果的指针位置 - start, end = 0, -1 - # 初始化字符串 - s = '#' + '#'.join(list(s)) + '#' - # 记录每个中心的回文臂长 - arm_len = [] - # 历史到达的最远的右指针位置 - right = -1 - # 历史到达最远右指针位置对应的回文中心 - j = -1 - # 当前节点为中心的回文臂长 - - # 2.遍历回文中心 - for i in range(len(s)): - # 3.如果此时遍历的中心点已经超过了最远的有边界位置 - if i > right: - cur_arm_len = self.expand(s, i, i) - # 4.如果还没超过 - else: - # 4.1计算对称点 - i_sym = 2*j - i - # 4.2计算开始遍历的臂长 - min_arm_len = min(arm_len[i_sym], right - i) - # 4.3更新臂长 - cur_arm_len = self.expand(s, i - min_arm_len, i + min_arm_len) - # 5.记录当前位置的臂长 - arm_len.append(cur_arm_len) - # 6.如果当前臂长超过了最长记录,更新状态 - if i + cur_arm_len > right: - right = i + cur_arm_len - j = i - # 7.如果长度大于输出的结果 - if 2 * cur_arm_len + 1> end - start: - start = i - cur_arm_len - end = i + cur_arm_len - - # 8.切片输出跳过# - return s[start + 1: end + 1 : 2] -``` - -## 最好\*:别人写的中心拓展 - -```python -class Solution: - def longestPalindrome(self, s: str) -> str: - n = len(s) - ans_left = ans_right = 0 - #奇数长度回文的中心:n 个,偶数中心:n-1个;总共:n + (n-1) = 2n - 1 个中心 - for i in range(2*n-1): - l,r = i//2, (i+1)//2 #通过整数除法处理奇偶中心 - while l >= 0 and r < n and s[l] == s[r]: - l -= 1 - r += 1 - if r - l - 1 > ans_right - ans_left: - ans_left,ans_right = l+1, r - return s[ans_left : ans_right] -``` - -# 94. [最长公共子序列](https://leetcode.cn/problems/longest-common-subsequence/)1143-20260116 - -动态规划 注意DP数组的定义 - -```python -class Solution: - def longestCommonSubsequence(self, text1: str, text2: str) -> int: - # dp i,j 表示 t1 [0:i] t2[0,j] 的最长公共序列长度 闭 - dp = [[0]*(len(text2)+1) for _ in range((len(text1)+1))] - for i in range(1,len(text1)+1): - for j in range(1,len(text2)+1): - if text1[i-1] == text2[j-1] : - dp[i][j] = dp[i-1][j-1]+1 - else : - dp[i][j] = max( dp[i-1][j], dp[i][j-1] ) - return dp[-1][-1] - -``` - -## 位运算 - -```python -class Solution: - def longestCommonSubsequence(self, text1: str, text2: str) -> int: - # Step 1: Record positions of each character in text1 - char_masks = {} - for i, char in enumerate(text1): - char_masks[char] = char_masks.get(char, 0) | (1 << i) - - # v stores the "increment" bits (1 if dp[j] > dp[j-1]) - v = 0 - for char in text2: - # x marks all possible match positions OR existing increments - x = char_masks.get(char, 0) | v - - # (v << 1) | 1 creates a "borrow" bit to start the chain reaction - # x - (...) performs bit-parallel search for the first available '1' - # x ^ (...) identifies which bits were affected by the subtraction - # x & (...) picks the bits that actually contribute to the LCS increment - v = x & (x ^ (x - ((v << 1) | 1))) - - return v.bit_count() # count_set_bits(v) -``` - -# 95. [编辑距离](https://leetcode.cn/problems/edit-distance/)72-20260116(没做) - -这两个题好难啊 - -# 96. [只出现一次的数字](https://leetcode.cn/problems/single-number/)136-20260116 - -## 技巧:位运算 - -```python -class Solution: - def singleNumber(self, nums: List[int]) -> int: - ans = 0 - for n in nums : - ans^=n - return ans -``` - -# 97. [多数元素](https://leetcode.cn/problems/majority-element/)169-20260116 - -## 用on的空间 - -```python -class Solution: - def majorityElement(self, nums: List[int]) -> int: - mp = defaultdict(int) - for n in nums : - mp[n]+=1 - for k,v in mp.items() : - if v > len(nums)/2 : - return k -``` - -```python -class Solution: - def majorityElement(self, nums: List[int]) -> int: - nums.sort() - return nums[(len(nums) - 1) // 2] -``` - -## 用o1的空间 随机找 - -```python -class Solution: - def majorityElement(self, nums: List[int]) -> int: - majority_count = len(nums) // 2 - while True: - candidate = random.choice(nums) - if sum(1 for elem in nums if elem == candidate) > majority_count: - return candidate -``` - -## Boyer-Moore 投票算法 - -```python -class Solution: - def majorityElement(self, nums: List[int]) -> int: - count = 0 - candidate = None - - for num in nums: - if count == 0: - candidate = num - count += (1 if num == candidate else -1) - - return candidate - -作者:力扣官方题解 -链接:https://leetcode.cn/problems/majority-element/solutions/146074/duo-shu-yuan-su-by-leetcode-solution/ -来源:力扣(LeetCode) -著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。 -``` - -# 98. [颜色分类](https://leetcode.cn/problems/sort-colors/)75-20260116(没做) - -# 99. [下一个排列](https://leetcode.cn/problems/next-permutation/)31-20260116 - - - - diff --git a/source/about/index.md b/source/about/index.md index f4372c4..292213c 100644 --- a/source/about/index.md +++ b/source/about/index.md @@ -1,7 +1,4 @@ --- -title: 关于 我 +title: about date: 2024-05-02 15:32:09 -comments: false --- - -{% btn 'https://api.day.app/9oE8FWFSRx2KMnF9ixm3DU/GithubPage/Someone%20Send%20You%20A%20LIKE!',SendMELike,far fa-hand-point-right,block center larger %} \ No newline at end of file diff --git a/source/bak/Aligning Cyber Space with Physical World A Comprehensive Survey on Embodied AI.md.bak b/source/bak/Aligning Cyber Space with Physical World A Comprehensive Survey on Embodied AI.md.bak deleted file mode 100644 index 7beb931..0000000 --- a/source/bak/Aligning Cyber Space with Physical World A Comprehensive Survey on Embodied AI.md.bak +++ /dev/null @@ -1,889 +0,0 @@ ---- -title: "Aligning Cyber Space with Physical World A Comprehensive Survey on Embodied AI" -date: 2024-11-02 04:25:20 -updated: 2025-05-01 17:56:39 -math: true -tags: 深度学习 -categories: 深度学习 ---- -这是一篇Embodied AI的综述文章,介绍了Embodied AI的几个研究方向以及现状。 - -# 背景介绍 - -在计算机空间Cyber-Space中的智能体,被称为非具身智能体,而在物理世界中的智能体则称之为具身智能。 - -Embodied AI对AGI的实现很重要,是链接计算机世界与物理世界之间的各种应用的基础。 -目前多模态大模型(MLM)和世界模型(WM)使具身模型具有更强大的感知、交互以及规划能力。这些模型表现出卓越的模拟能力和对物理定律的良好理解,这使得具体模型能够全面理解物理和真实环境。具身智能也被认为是这些模型的最佳载体。 - -具身智能最初是由 Alan Turing 于 1950 年提出的 Embodied Turing Test,该测试旨在确定具身代理是否能够显示不仅限于解决计算机空间,而且还有物理世界中的复杂性和不可预测性问题。 - - -具身智能和非具身智能的对比如下: - -| 类型 | 所在环境 | 物理实体 | 描述 | 代表 | -| ----- | ---------- | -------- | --------- | --------- | -| 非具身AI | CyberSpace | 无 | 认知与物理实体分离 | ChatGPT | -| 具身AI | 物理空间 | 机器人,车... | 认知融入于物理实体 | 谷歌机器人RT-1 | - -为了实现AGI,具身智能是一个基本途径。Embodied AI其具有控制物理实体,可以和模拟或者物理环境交互。(这点和AGI相同) - -基于MLMs和WMs的具身智能的总体框架。具身智能使用具身世界模型作为它的“大脑”。它具有理解虚拟物理环境并主动感知多模态元素的能力。它可以充分理解人类意图,符合人类价值和事件因果关系,分解复杂的任务并执行可靠的行动,以及与人类交互并利用知识和工具。如图: -![[Pasted image 20241024155615.png]] - -具身智能包含的不同研究方向: -1. 具身机器人 -2. 模拟器 --下面四个是任务: -3. 具身感知(视觉主动感知)embodied perception -4. 具身交互 embodied interaction -5. 多模态(具身)智能体 embodied agents -6. 模拟到现实的机器人控制 sim-to-real robotic control -![[Pasted image 20241024193939.png]] - -# 下面对各个方向进行介绍: - -## 具身机器人 Embodied Robots - -能与环境主动交互的实体类型有很多,例如 机器人、智能家电、智能眼镜、自动驾驶汽车等等。其中机器人是最有代表性的实体。 - -机器人可以被设计成各种形式,来完成特定任务。例如 定基机器人、轮式、履带式、四足、人形、仿生机器人等等。 - -### 定基机器人 Fixed-based Robots - -定基机器人具有紧凑性和操作精度、稳定性高的特点,一般配备高精度传感器和执行器,可以使其实现微米级操作精度。可编程,用户可以通过编程可以使其适用于各种任务场景。 - -但是固定的底座设计限制了他们的操作范围和灵活性,难以在大范围内移动或者调整位置、以及与人类或者其他机器人合作。 - -### 轮式、履带式机器人 - -受益于其常规的移动方式,这些机器人可以面对更复杂多样的场景。 - -#### 轮式机器人 Wheeled Robots - -具有高效的移动性(在平面上)、结构简单、成本相对较低、能源效率高的优点,广泛应用于物流、仓储、安检等等领域。 - -这些机器人常常配备激光雷达和摄像头等高精度传感器,可以实现自主导航和环境感知。例如kiva、jackal机器人。 - -但是在复杂地形或者恶劣环境中,移动性有限,负载能力和机动性也会受到一定限制。 - -#### 履带式机器人 Tracked Robots - -履带式机器人在非平面上的机动性更强,有强大的越野能力。履带与地面接触面积更大,分散重量,防止在松软地形下沉。应用于恶劣、特殊环境下的任务:农业、建筑、灾难恢复、军事。 - -由于履带系统的高摩擦力,履带式机器人能源效率较低。此外,平面上的移动速度比轮式机器人慢,灵活性和可操作性也较差。 - -### 四足机器人 - -稳定性和适应性较强,可以通过雷达或者摄像头来进行环境感知。甚至可以主动导航避开障碍物。 - -设计复杂而且制造成本很高,限制在成本敏感领域的使用。此外,在复杂环境下的电池续航能力有限,需要频繁充电或更换电池才能长时间运行。 - -### 人形机器人 - -手部设计使它们能够执行复杂的任务具有多个自由度和高精度传感器,使它们能够模仿人手的抓取和操纵能力,这在医疗手术和精密制造等领域尤其重要。可以行走、跑步、爬楼梯、识别面孔和手势,适合接待和引导服务,可以识别情绪并进行自然语言交流,广泛应用于客户服务和教育环境中。 - -但是,由于其复杂的控制系统,人形机器人在复杂环境下保持运行稳定性和可靠性面临挑战。 - -### 仿生机器人 - -通过模拟自然生物体的高效运动和功能,在复杂和动态的环境中执行任务。在医疗保健、环境监测和生物研究等领域展现出巨大的潜力。仿生设计可以通过模仿生物有机体的高效运动机制来显着提高机器人的能源效率,使它们在能源消耗方面更加经济。 - -但是,其设计和制造工艺复杂且成本高昂,限制了大规模生产和广泛应用。其次,由于采用柔性材料和复杂的运动机制,仿生机器人在极端环境下的耐用性和可靠性受到限制。 - -## 具身模拟器 Embodied Simulators - -为了使智能体能够与环境交互,需要构建一个真实的模拟环境。这需要考虑环境的物理特征、物体的属性及其相互作用。具身模拟器,提供了高效经济的实验环境。可以为具身智能提供不同的测试环境,模拟潜在危险场景。 - -下面介绍两种模拟器:基于底层模拟的通用模拟器和基于现实场景的模拟器。 - -### 基于底层仿真的通用模拟器 general simulator based on underlying simulation - -在物理世界中的 物理交互 和 动态变化 是 不可替代的,但是在物理世界直接部署具身模型进行训练或者测试的成本太高。而通用模拟器提供一个几乎仿真(物理世界)的虚拟环境。可以在其中进行算法开发、模型训练。在成本、时间、安全上都具有一定优势。本文介绍了3种常见的通用模拟器:Isaac Sim、Gazebo、PyBullet. -#### Isaac Sim - -isaac sim是NVDIA为机器人和AI研究提供的先进的仿真平台,具有高保真物理模拟、实时光线追踪、广泛的机器人模型库和深度学习支持。应用场景包括自动驾驶、工业自动化和人机交互。 -#### Gazebo - -gazebo是一个用于机器人研究的开源模拟器,支持各种传感器的仿真,拥有丰富的机器人库,主要用于机器人导航与控制以及多机器人系统。 -#### PyBullet - -PyBullet是Bullet 物理引擎的python接口,易于使用,具有多种传感器模拟和深度学习集成。支持实时物理模拟,包括刚体动力学、碰撞检测和约束求解 - -#### 各种仿真平台 - -下图介绍了10个通用模拟器的主要特点和主要应用场景。它们各自在具体人工智能领域提供独特的优势。 -综合 - HFPS:高保真物理模拟; - HQGR:高质量图形渲染; -RRL:丰富的机器人库; -DLS:深度学习支持; -LSPC:大规模并行计算; -ROS:与 ROS 紧密集成; -MSS:多传感器模拟; -CP:跨平台; -NAV:机器人导航; -AD:自动驾驶; -RL:强化学习; -LSPS:大规模并行; -SIM MR:多机器人系统; -RS:机器人仿真 -![[Pasted image 20241025021829.png]] -各种仿真平台的可视化效果如下: -![[Pasted image 20241025022108.png]] - -### 基于现实场景的模拟器 the simulator based on real scenes - -在**家庭活动**中实现通用的具身智能体一直是具身人工智能研究领域的主要焦点。这些实体代理需要深入了解人类的日常生活并执行复杂的实体任务,例如室内环境中的导航和交互。 - -基于这种特定的复杂任务场景,模拟器的需要尽可能接近现实世界。这一要求对模拟器的复杂性和真实性有很高的要求。这些模拟器主要从现实世界收集数据,创建逼真的 3D 资源,并使用 UE5 和 Unity 等 3D 游戏引擎构建场景。这些模拟器的侧重点各不相同,有的具有丰富的交互场景对象以及分配给它们的物理属性(例如打开/关闭甚至冷/热),有的支持多智能体模拟。来自开放资源或定制的多个实体代理(例如人类和机器狗)可以在模拟器中合作,自由移动,并与场景进行简单的交互。有的甚至实现刚体、软体、织物、流体等多种材质的物理交互模拟,并提供与物体交互时的情境声音。可以支持用户自定义环境进行训练。 - -各种现实场景模拟器的条件: -![[Pasted image 20241025022601.png]] -现实场景模拟器的可视化场景: -![[Pasted image 20241025022653.png]] - -## 具身感知 Embodied Perception - -未来视觉感知的启明星是以具身为中心的视觉推理和社交智能,即具身感知任务。这种任务和仅仅识别图像中的物体不同,具身感知要求Agent可以在物理世界中移动并且与环境进行交互,要求具身智能体对3D空间和动态的环境有更加深入的了解。具身感知需要视觉感知和推理、理解场景内的3D关系,以及基于视觉信息预测和执行复杂任务。 - -具身智能体的感知任务包含以下方向: -1. 主动视觉感知 -2. 3D视觉定位 -3. 视觉语言导航 -4. 非视觉感知-触觉 - -### 主动视觉感知 Active Visual Perception - -主动视觉感知系统状态估计、场景感知和环境探索等基本能力。这些能力在 视觉SLAM(视觉同步定位和映射,visual simultaneous localization and mapping)和3D场景理解、主动探索 任务中 被 广泛研究。 - -下面是主动视觉感知的示意图: 3D 场景理解、vSLAM 为 **被动**视觉感知 提供了基础,而主动探索可以为被动感知系统提供主动性。这三个要素相辅相成,对于主动视觉感知系统至关重要。 -![[Pasted image 20241025024259.png]] - -这些研究领域有助于开发强大的主动视觉感知系统,促进复杂动态环境中改善环境交互和导航。而其中的 三个组成部分 的研究现状如下表: -![[Pasted image 20241025025003.png]] - -下面大概介绍各个组成部分的主要方法。 -#### 视觉 同步定位 和 映射 vSLAM : Visual Simultaneous Localization and Mapping - -同步定位和映射(SLAM)是一种 在确定 可移动机器人在位置环境的位置 的同时 构造所在位置环境的地图的技术。 - -基于距离的SLAM通过测距仪,创建点云,但是成本高且提供的信息很少。 - -视觉SLAM 通过 机载摄像机 获取帧 然后 构建环境的表示。硬件成本低,小规模场景准确率高,可以捕获到丰富的环境信息。经典的vSLAM又可以分为 传统vSLAM 和 语义vSLAM。 - -##### 传统vSLAM :Tradition - -通过 图像信息 和 多视图几何原理 估计robot在位置环境中的姿势,来建造由点云构成的低级地图(稀疏地图、半密集地图)。 但是由于 低级地图的点云 与 环境中的物体 并不直接对应,所以robots很难 对其进行解释或者利用。 - -例子中的方法有 MonoSLAM MSCKF ORB-SLAM PTAM DTAM -##### 语义vSLAM :Semantic - -#TODO 什么是语义SLAM - -与 语义信息处理方法 集成的的 语义vSLAM系统的出现,让robots有更强对未知环境的感知能力。 - -早期的方法有SLAM++,通过 实时的3D目标识别与追踪 来创建高效的对象图,从而实现在复杂环境的重定位和目标检测功能。 -CubeSLAM和HDP-SLAM将3D矩形引入地图来构建轻量语义图。 - QuadricSLAM 采用语义 3D 椭球体来实现复杂几何环境中物体形状和姿势的精确建模。 - So-SLAM 在室内环境中纳入了完全耦合的空间结构约束(共面性、共线性和邻近性)。 - 为了应对动态环境的挑战,DS-SLAM、DynaSLAM 和SG-SLAM 采用语义分割进行运动一致性检查,并采用多视图几何算法来识别和过滤动态对象,确保稳定的定位和建图。 - OVD-SLAM 利用语义、深度和光流信息来区分没有预定义标签的动态区域,实现更准确和鲁棒的定位。 - GSSLAM 利用 3D 高斯表示,通过实时可微分泼溅渲染管道和自适应扩展策略来平衡效率和准确性。 - -#### 3D场景理解 3D Scence Understanding - -#TODO 主要方法的插图 - -3D场景理解旨在区分对象的语义,识别其位置,并从3D场景数据中推断几何属性,这是自动驾驶、机器人导航和人机交互的基础等。可以使用 LiDAR 或 RGB-D 传感器等 3D 扫描工具将场景记录为 3D 点云。与图像不同,点云稀疏、无序且不规则,使得场景解释很有难度。 - -近年所提出的基于深度学习理解3D场景方法,分为:基于投影(projection)的方法、基于体素(voxel)的方法、基于点(point)的方法。这三种方法是对点云的不同处理方式、角度。 -基于投影的方法(MV3D、PointPillars、MVCNN)将3D点投影到各种图像平面上,然后采用CNN的2D主干进行特征提取。 -基于voxel的方法将点云转化为规则的voxel网格以促进3D卷积运算(例如VoxNet,SSCNet),还有一些通过稀疏卷积提高效率。 -基于点的方法直接处理点云(PointNet、PointNet++、PointMLP)。或者为了模型的可拓展性有基于Transformer或者Mamba的架构。 - -#### 主动探索 Active Exploration - -上面提到的3D场景理解方法,赋予了robots被动感知环境的能力,但是感知系统的信息获取和决策不适应不断变化的场景。 - -被动的感知方式为为robots对环境的理解提供了基础,但是这种理解是从特定(或者说给定)的角度。有一定的局限性。 - -但是由于robots可以移动以及与环境进行互动,结合这一特点,使robots获得主动感知探索环境的能力,通过与环境交互、通过观察环境的视角获取更多信息,使其加深对环境的理解能力。这也是近些方法的做法。 - -##### 例如: -- 有人提出了一种好奇的机器人,它通过与环境的物理交互来学习视觉表示,而不是仅仅依赖数据集中的类别标签。为了解决具有不同形态的机器人之间的交互式对象感知的挑战。 -- 提出了一种多阶段投影框架,通过学习的探索性交互传递隐式知识,使机器人能够有效地识别物体属性,而无需从头开始重新学习。 -- 认识到自主捕获信息性观察结果的挑战。 [114]提出了一种强化学习方法,其中代理通过减少其环境中未观察到的部分的不确定性来学习主动获取信息丰富的视觉观察,使用循环神经网络主动完成全景场景和 3D 对象形状。 -- 引入了一种无地图规划框架,该框架迭代地定位 RGB 相机以捕获未知场景中信息最丰富的图像,并在基于图像的神经渲染中使用新颖的不确定性估计来引导数据收集到最不确定的视图。 -- [116]开发了一种机器人探索算法,该算法使用状态值函数来预测未来状态的值,结合离线蒙特卡罗训练、在线时间差异自适应和基于传感器信息覆盖的内在奖励函数。 - -### 3D视觉定位 3D Visual Grounding - -3D VG(visual grounding)区别于2D VG,不只是在平面中考察,而是包含了对象的深度,视角以及空间关系。这类任务涉及 使用自然语言描述在3D环境中的定位对象。近期的3D VG方法大致分为两类 :2-stage和1-stage: -![[Pasted image 20241025170849.png]] - -##### 两阶段3D VG : Two-Stage 3D Visual Grounding Methods - -类似于2D任务,3D grounding的早期研究主要通过两个阶段的检测然后匹配流程。早期现实用预训练的检测器或者分割器,从3D场景中的众多 3D目标 建议(proposal)中提取特征,然后将其与 语言查询特征融合 来匹配得到目标对象。 - -重点主要在第二阶段(语言查询的特征和3D对象proposal的特征相关性找到最佳匹配)。例如Referlt3D、TGNN不仅学习将proposal特征与文本embedding相匹配,而且还通过图神经网络对对象之间的上下文关系进行编码,增强对物体的理解。 - -最近随着transformer在NLP和CV中的出色表现,很多研究通过transformer提取融合视觉语言特征。 -LanguageRefer 基于 Transformer 的架构,结合了 3D 空间嵌入、语言描述和类标签嵌入,以实现强大的 3D 视觉定位。 -3DVG-Transformer是一种用于 3D 点云的关系感知视觉定位方法,具有用于关系增强提案生成的坐标引导上下文聚合模块和用于跨模式提案消歧的多重注意模块。 -为了实现对 3D 对象和引用表达式进行更细粒度的推理,TransRefer3D使用实体和关系感知注意力增强了跨模态特征表示,结合了自我注意力、实体感知注意力和关系感知注意力。 - -现有的 3D VG 方法通常依赖于大量标记数据进行训练,或者在处理复杂语言查询时表现出局限性。结合LLM的强大的语言理解能力: -LLM-Grounder 提出了一种不需要标记数据的开放词汇 3D VG流水线:利用LLM 分解查询 并生成 对象识别计划 ,然后评估空间和常识关系以选择最匹配的对象。 -ZSVG3D 设计了一种零样本 开放词汇 3D VG方法,该方法使用 LLM 来识别相关对象并执行推理,将此过程转换为脚本化视觉程序,然后转化为可执行的 Python 代码来预测对象位置。捕获依赖于视图的查询并理解 3D 空间中的空间关系。 - -但是 这些两阶段方法面临着确定proposal数量的困境,因为第一阶段的3D检测器需要采样关键点来表示整个3D场景并为每个关键点生成相应的proposal。 稀疏的proposal可能会忽略第一阶段的目标,从而使它们在第二阶段无法匹配。 相反,密集的proposal可能 包含 冗余 对象,由于proposal间关系过于复杂,导致第二阶段的目标区分困难。此外,关键点采样策略与语言无关,这增加了检测器识别与语言相关的proposal的难度。如图所示: -![[Pasted image 20241025173342.png]] - -##### 一阶段3D VG : One-Stage 3D Visual Grounding Methods - -#TODO 介绍一下这个流程 - -二阶段是先 给出 proposal 然后在 对 这些proposal与给出的文字特征进行匹配。 -而一阶段3D Visual Grounding则是 通过 语言查询queries 把 特征提取 和 目标检测 集成在 -起。根据语言描述逐步选择关键点。有的方法(MDFETR,GLIP)会提取 耦合所有单词的在句子中的特征 或者 直接更关注描述中的 物品名称,但是忽略单词中的信息。 EDA提出了解耦句子中的文本属性。 - -### 视觉语言导航 Visual Language Navigation - -以视觉观察以及语言指令作为输入,使robot具有在环境中穿梭的能力。 -旨在使Agent可以 根据语言指令穿越于位置的环境中。要求agent可以理解复杂多样的视觉观察对象,以及不同粒度的语言指令。一般来说 VLN 有 视觉信息 和 语言指令 作为输入。 - -视觉信息:所经过的轨迹上的观察视频、或者 一组 时序 观察图片。 -语言指令:Agent 需要达到的目标 或者 需要完成任务。完整的目标、粗略的任务描述。 -Navigation:Embodied Agent 则通过 上面的信息,从候选动作列表中选择 一个或者一组 动作 来执行以满足要求,单个任务或者有交互的任务,公式如下:$$Action = \mathcal{M}(O,H,I)$$ -其中 $Action$ 是智能体所选择的一个或一组动作,$\mathcal{M}$ 是model, $O$ 是**当时的**观察作为视觉输入, $H$ 是历史信息 ,$I$ 则是语言指令。 - -在VLN任务中,成功率(Success Rate,性能)、轨迹长度(Trajectory Length,效率)、成功每单位路径 SPL(Success Weighted by Path Length,综合指标). - -#### 用到的数据集 - -由于语言指令的 详细程度不一,要求的Agent完成的任务有无交互动作、顺序要求,对VLN有不同的挑战。基于这些差异有以下数据集: -- Room to Room ,基于Matterport3D,代理根据分步指令进行导航,根据视觉观察选择下一个相邻的导航图节点前进,直到到达目标位置。代理需要动态跟踪进度,以使导航过程与细粒度指令保持一致。 -- Room for Room,将R2R中的路径延伸到更长的轨迹,这需要实体代理更强的远程指令和历史对齐能力。 -- VLN-CE,将R2R和R4R扩展到连续环境,体现的智能体可以在场景中自由移动。这使得实体主体的行动决策变得更加困难。 -- TOUCHDOWN,基于Google Street View和Matterport3D模拟器创建,代理按照指令在纽约市的街景渲染模拟中导航,以找到指定的对象。 -- REVERIE要求具身智能能够准确定位由简洁的、人工注释的高级自然语言指令指定的远处不可见的目标物体,这意味着具身智能体需要在大量的物体中找到场景中的目标物体。 -- SOON,代理接收从粗到细的长而复杂的指令,以在 3D 环境中找到目标对象。在导航过程中,智能体首先搜索较大的区域,然后根据视觉场景和指令逐渐缩小搜索范围。这使得SOON的导航具有目标导向性并且与初始位置无关。 -- DDN,只提供人类需求,而不指定明确的对象。代理需要在场景中导航以找到满足人类需求的对象。 -- ALFRED,基于AI2-THOR 模拟器,实体需要理解环境观察结果,并根据粗粒度和细粒度的指令在交互环境中完成家务任务。 -- DialFRED 是 ALFRED 的扩展,允许代理在导航和交互过程中提出问题以获得帮助。这些数据集都引入了额外的预言(Additional Oracles),Agent需要通过提问来获取更多有利于导航的信息。 -- OVMM,基于Habitat 模拟器,Agent需要在未知环境中拾取任何对象并将其放置在指定位置。智能体需要在家庭环境中定位目标物体,导航并抓取它,然后导航到目标位置并放下该物体。 -- Behaviour-1K ,基于OmniGibson 模拟器。基于人类需求,包含 1,000 个长序列、复杂、依赖技能的日常任务。智能体需要完成长跨度的导航交互任务,其中包含数千个基于视觉信息和语言指令的低级操作步骤。这些复杂的任务需要很强的理解和记忆能力。 -- CVDN 要求实体代理根据对话历史导航到目标,并在不确定时提出问题以帮助决定下一步行动。 -#### 相关的方法 - -受到到LLM影响,主要分为两个方向:Memory Understanding Based ,Future-Prediction Based。 -![[Pasted image 20241027182915.png]] - -##### 基于记忆理解 Memory Understanding Based - Past Learning - -侧重于对环境的感知和理解,模型 大多 根据对历史轨迹上的观察进行设计。并且由于 VLN 任务 属于 部分可观测的 马尔可夫决策过程,其中的未来状态的观测结果取决于当下智能体在环境中的行为。历史信息对于navigation的决策有很大作用。所以基于记忆的方法也是主流方法。 - -基于图的学习是该方法的重要组成部分,通常以图的形式表示navigation的过程,Agent在每个时间节点得到信息编码为图的一个节点。然后通过全局或者部分的图信息作为历史信息。Navigation Graph 将环境离散化,但是理解编码环境也是重要的部分。还有结合大模型,应用大模型强大的理解记忆能力。下面是一些方法:(有些对编码进行改进,有些侧重于理解方式以及信息处理,还有的结合LLM) - -- LVERG分别编码每个节点的语言信息和视觉信息,设计新的语言和视觉实体关系图来建模文本和视觉之间的模态间关系以及视觉实体之间的模态内关系。 -- LM-Nav 使用目标条件距离函数来推断原始观测集之间的联系并构建导航图,并通过LLM从指令中提取地标,使用视觉语言模型将它们与导航图的节点进行匹配。 -- HOP不是基于图学习,但其方法与图类似,要求模型对不同粒度的时间有序信息进行建模,从而实现对历史轨迹和记忆的深入理解。 -- VER 通过 2D-3D 采样将物理世界量化为结构化 3D 单元,提供细粒度的几何细节和语义。不同的学习方案探索如何更好地利用历史轨迹和记忆。 -- 通过对抗性学习,CMG在模仿学习和探索激励方案之间交替,有效加强了对指令和历史轨迹的理解,缩短了训练和推理之间的差异。 -- GOAT通过后门调整因果学习(BACL)和前门调整因果学习(FACL)直接训练无偏模型,与视觉、导航历史及其组合到指令进行对比学习,使智能体能够更充分地利用信息。 -- RCM提出的增强型跨模态匹配方法使用面向目标的外部奖励和面向指令的内部奖励来进行全局和局部的跨模态接地,并通过自监督模仿学习从自己的历史良好决策中学习。 -- FSTT将TTA引入VLN,并在时间步和任务两个尺度上对模型的梯度和模型参数进行优化,有效提高了模型性能。 -- NaviLLM通过视觉编码器将历史观察序列集成到嵌入空间中,将融合编码的多模态信息输入到LLM中并对其进行微调,在多个基准上达到了state-of-the-art。 -- NaVid对历史信息的编码进行了改进,通过不同程度的池化实现对历史观测值和当前观测值的不同程度的信息保留。 -- DiscussNav将不同能力的大型模型专家分配给不同的角色,驱动大型模型在导航动作之前进行讨论以完成导航决策,并在零样本VLN中取得了优异的性能。 -##### 基于未来预测 Future-Prediction Based - Future Learning - -这种方法更注重对未来状态的理解、建模以及预测。这种方式对环境有更加深刻的理解,尤其是在连续环境中。 - -基于未来预测的Visual Language Navigation方法(未来预测视觉语言导航)主要旨在通过对环境和未来路径的预测,提升导航模型在未知环境中的表现和决策能力。该方法主要通过图学习和环境编码来预测可行路径和未来观测,从而简化复杂的导航任务,帮助模型在从离散环境过渡到连续环境时更好地表现。 - -- **图学习和路径预测**:在未来预测方法中,常利用图学习进行路径点预测。例如,BGBL和ETPNav方法通过当前导航图节点的观测来预测可移动路径点,旨在通过从离散到连续环境的迁移,提升导航的精确度和稳定性。 - -- **环境编码**:为了更好地理解未来环境,NvEM模型设计了一种融合编码方法,通过主题模块和参考模块从全局和局部视角对邻近视野进行编码。这种方法帮助导航模型学习对未来观测的理解,从而更加准确地预测未来环境。 - -- **多级表示与路径树构建**:HNR模型采用层级神经辐射表示来预测未来环境的视觉表示,通过三维特征空间编码构建可导航的未来路径树。这种方法从不同层级预测未来环境,为导航决策提供了更丰富的参考。 - -- **强化学习的应用**:部分方法采用强化学习进行未来状态的预测。例如,LookBY利用强化学习让预测模块模拟未来的状态和回报,这样可以将“当前观测”和“未来观测的预测”直接映射为行动,从而显著提升导航的表现。 - -- **大型模型的‘想象’功能**:大型语言模型的丰富世界知识和零样本能力,为未来预测方法提供了新的可能性。比如,MiC模型要求语言模型根据指令直接预测目标及其可能位置,通过场景感知来提供导航指示。这一方法要求大型模型充分发挥‘想象力’,并通过提示词构建假想场景来辅助导航。 - -### 非视觉感知(触觉 Non-Visual Perception: Tactile - -触觉感知是具身智能体在物理世界中理解和操作物体的一项重要能力,它能够提供关于物体的纹理、硬度和温度等详细信息,帮助智能体更精确地执行复杂任务。通过触觉传感器,智能体可以在实际操作中获得与视觉信息互补的触觉数据,增强其在高精度任务中的表现。 -在触觉感知任务中,**传感器设计** 决定了Agent获取的信息, 然后介绍数据集 ,最后介绍 触觉感知方法。 - -#### 预备:传感器设计、数据集介绍 - -在触觉信息获取中,传感器有三类: - -- **非视觉(Non-vision-based)传感器**:主要使用电学和机械原理,感知力、压力、振动和温度等低维度信息,代表如BioTac。此类传感器直接测量触觉物理量,适用于检测简单触觉信息。 -- **视觉(Vision-based)触觉传感器**:基于光学原理,通过图像(触觉表面的纹理和形变)来捕获触觉信息。例如GelSight和DIGIT传感器利用凝胶表面的变形图像来感知物体表面特性,应用广泛,尤其是在需要精细触觉信息的场景中。 - ![[Pasted image 20241029050047.png]] -- **多模态(Multi-modal)传感器**:视觉触觉传感器和非视觉触觉传感器,模仿人类皮肤,整合压力、接近度、加速度和温度等多种信息,采用柔性材料设计,能够提供更加全面的触觉数据,提升智能体的适应性和灵活性。 - - **多模态传感器**是指能够同时采集和融合多种不同类型信息的传感器,模拟人类多感官整合的能力。它们常用于具身AI和机器人系统中,通过集成触觉、温度、压力、接近度等不同的感知模式,帮助智能体更全面地理解物体或环境特性。这类传感器的设计灵感源于人类皮肤,它们的应用可以有效提升机器人的感知能力,尤其在精细操作和动态环境下表现出色。 - - **多模态传感器的主要类型和特点** - - 1. **压力和触觉组合**:同时感知压力和表面触觉细节,这一组合通常采用柔性材料传感器,能够检测物体硬度、表面纹理和接触压力。典型应用包括抓取力控制和柔性操作。 - 2. **接近度与触觉组合**:可以在不直接接触物体的情况下检测其存在和距离(接近度),并在接触后感知表面细节。接近度传感器如电容式或红外式传感器,结合触觉传感器,广泛应用于避免碰撞和精细抓取。 - 3. **温度与触觉组合**:感知温度变化和表面特性,帮助机器人判断物体的材质和状态,如液体温度或表面湿度变化。常用于食品处理、医疗领域等温度敏感的任务。 - 4. **压力、加速度与触觉组合**:通过集成压力和加速度传感器,机器人不仅能感知表面信息,还能感知物体的运动和振动模式。该组合特别适合检测对象的振动、滑动和稳定性,提升操作的精确性。 - - **多模态传感器的应用场景** - - • **人机交互**:在机器人手指或表面上安装多模态传感器,可以帮助其识别手握物体的力道、温度变化等,提升人机协作的安全性和准确性。 - • **复杂抓取与操作**:在工业和服务机器人中,能确保机器人适应多种物体类型和不同操作环境,增强稳定性和适应性。 - • **环境监测与适应**:多模态传感器能帮助机器人更好地适应不同环境,例如识别火灾中的高温环境,或在湿滑环境中保持平衡。 - -![[Pasted image 20241029050221.png]] - -数据集则有如下几种: - -![[Pasted image 20241029045840.png]] - -- **非视觉传感器数据集**:主要由BioTac等传感器采集,包含电极值、力向量和接触位置,适用于力估计和抓取操作的研究。 -- **视觉传感器数据集**:采集凝胶变形的高分辨率图像,包含丰富的对象数据,如日常物品、自然环境、不同材料等。此类数据集可用于精细的纹理识别和操作任务。 - -#### 触觉感知 方法 - 根据应用 讲 对应方法 - -触觉感知的应用被分为3类:触觉估计、机器人操作、多模态信息融合(分类、识别)。每种应用中都有一些方法。 - -##### 触觉估计 Tactile Estimation - -在触觉感知中的估计(Estimation)任务里,研究主要集中在物体的形状、力以及滑动检测的估计。以下是估计任务的主要方法和发展: -1. **早期方法** - 研究人员最初通过简单的阈值方法或卷积神经网络(CNN)来处理形状、力和滑动测量。这些方法基于触觉图像的颜色变化和标记物分布的变化来推断物体信息。 - -2. **触觉图像生成**: - 触觉图像生成是估计任务的重要组成部分,目标是将视觉数据转换为触觉图像。最初的研究使用RGB-D图像作为输入,通过深度学习模型输出触觉图像。近年来,随着图像生成技术的快速发展,Higuera等人和Yang等人应用扩散模型(Diffusion Model)生成触觉图像,效果显著提升。 - - 触觉图像的生成通常基于触觉传感器表面与物体接触时的变形或压痕,传感器通过内置的摄像头或压力分布图记录下变形情况,变形数据被处理为图像,可以进一步用于分析物体的特性,如表面纹理、硬度、接触力等。 - -3. **物体重建**: - 物体重建任务分为二维和三维重建: - - **二维重建**:主要关注物体的形状和分割。 - - **三维重建**:关注物体表面、姿态,甚至是整个场景的感知。早期方法多采用数学方法、自编码器和神经网络,将视觉和触觉特征(有时包括点云)结合,实现物体的重建。 - -##### 机器操作 Robotic Manipulation - -**Robotic Manipulation中的触觉任务**涉及通过触觉感知实现对物体的精细操作,其中弥合**模拟到真实环境(Sim-to-Real)的差距**至关重要。为了实现高精度、实时的操作任务。 -有 基于强化学习 和 基于生成对抗网络(GAN) 两种主要方法。 -###### 强化学习方法 - -- **Visuotactile-RL**:为现有强化学习方法提出了改进,包括触觉门控、触觉数据增强和视觉降质处理等。这些改进措施使触觉感知在复杂环境中更具鲁棒性。 -- **Rotateit**:这是一个基于多模态感知输入的指尖物体多轴旋转系统,利用了带特权信息的强化学习策略进行网络训练,并实现了在线推理。 -- **基于触觉的目标设定**:提出了一种基于深度强化学习的目标导向物体推动方法,完全依赖触觉感知,并采用了基于目标的模型无关和模型驱动强化学习策略,使机器人能够精确地将物体推动至目标位置。 -- **AnyRotate**:关注手持物体的多轴旋转操作,该系统利用触觉密集特征构建了连续接触特征表示,提供触觉反馈以在模拟环境中训练策略,同时通过训练观测模型来实现零样本策略转移,有效地缩小了Sim-to-Real差距。 -###### GAN-Based - -- ACTNet 提出了一种无监督的对抗域适应方法来缩小像素级触觉感知任务的域差距。引入了自适应相关注意机制来改进生成器,该生成器能够利用全局信息并关注显着区域。然而,像素级域自适应会导致错误累积、性能下降、结构复杂性和训练成本增加。 -- STR-Net 提出了一种用于触觉图像的特征级无监督框架,缩小了特征级触觉感知任务的sim-real gap - -##### 材料识别 - - 触觉感知任务中的识别任务主要集中于材料分类和多模态理解。研究方法可以分为传统方法和基于大型语言模型(LLMs)与视觉语言模型(VLMs)的方法。 - -###### 传统方法(Traditional Methods) - -传统方法主要依赖于自动编码器、自监督学习以及联合训练,以提升触觉表示学习的效果。方法如下: -- **自动编码器(Autoencoder)**:用于生成简洁的触觉数据表示,减少数据维度以提升处理效率。 -- Polic等人使用卷积神经网络自动编码器对光学触觉传感器图像进行降维。 -- Gao等人设计了一个监督型递归自动编码器,处理异构传感器数据集。 -- Cao等人提出了TacMAE,一个用于处理不完整触觉数据的掩码自动编码器。 -- Zhang等人引入了MAE4GM,这是一种多模态自动编码器,能够整合视觉和触觉数据。 -- **联合训练方法(Joint Training Methods)**:为了将触觉与其他模态相结合,联合训练方法在触觉表示学习中起到关键作用。 -- Yuan等人训练了包含深度、视觉和触觉数据的卷积神经网络(CNNs)。 -- Lee等人采用变分贝叶斯方法,整合了多模态传感器数据(如力传感器序列和末端执行器度量数据)。 -- **自监督方法(Self-supervised Methods)**:自监督学习通过对比学习将不同模态数据结合,进一步增强触觉表示。 -- Lin等人简单地将触觉输入与多种视觉输入配对。 -- Yang等人使用视觉-触觉对比多视角特征。 -- Kerr等人采用了InfoNCE损失,Guzey等人则使用了BYOL方法。这些对比学习方法在触觉表示学习中奠定了坚实基础。 - - - -###### 基于LLMs和VLMs的方法 - -大型语言模型(LLM)和视觉语言模型(VLM)最近在跨模态理解和零样本学习方面表现出色。以下是其在触觉识别中的应用: - • **对比预训练(Contrastive Pretraining)**:研究者通过对比预训练方法,将触觉数据与视觉和语言模态对齐。 - • Yang等人、Fu等人和Yu等人都采用了对比学习方法,将触觉数据编码并与视觉、语言模态对齐。 - • **任务适配(Task Adaptation)**:通过如LLaMA等大型语言模型的微调,可以实现如触觉描述等具体任务。大型语言模型和视觉语言模型在理解触觉数据并跨模态生成描述上展示了优越性能,进一步推动了触觉表示学习的进展。 - -## 具身交互 Embodied Interaction - -具身交互是指Agent在物理或者虚拟世界中与环境交互的任务场景。经典的具身交互任务有 Embodied问答 、 具身智能体抓取任务。 - -### 具身问答 Embodied Question Answering - -在EQA(Embodied Question Answering)任务中,智能体需要从第一人称视角探索环境,以收集必要信息来回答指定问题。具备自主探索和决策能力的智能体不仅需要选择合适的行动来探索环境,还需判断何时停止探索以回答问题。现有研究关注不同类型的问题。 -![[Pasted image 20241031012655.png]] - -#### 类型如下 - -1. **Single Objective(单一目标)**:例如“花瓶在哪个房间?”这种问题类型要求智能体找到特定物体或位置的单一信息。智能体需要进行环境探索,定位到物品的具体位置才能回答问题。 - -2. **Multiple Objectives(多重目标)**:例如“餐桌和电视柜的颜色是否相同?”这种类型的问题涉及多项物体之间的比较。智能体需要找到并观察多个物体的特性,并对比分析后得出答案。 - -3. **Multi-agent(多智能体)**:例如“屋内是否有洗衣机?”这是一个多智能体协作的问题类型。多个智能体可以在不同区域独立探索,然后将各自的信息整合起来以得出答案。 - -4. **Interaction(互动)**:例如“冰箱里有苹果吗?”这种类型要求智能体进行特定的互动行为(如打开冰箱)来获取答案。这种问题类型测试了智能体的交互能力。 - -5. **Knowledge-based(基于知识)**:例如“客厅里有能降低温度的物品吗?”这种类型需要智能体结合已有的知识(如空调的用途)来解答问题,而不只是依赖视觉识别。 - -6. **Episodic Memory(情景记忆)**:例如“钟在哪里?”这种问题类型需要智能体记住之前探索过程中的视觉信息,基于情景记忆来提供答案,避免重复探索。 - -7. **Object States(物体状态)**:例如“有人可以在客厅看电视吗?”这种类型的问题关注环境中物体的当前状态(如电视是否打开、窗帘是否拉开),通过状态判断来推断可行性或答案。 -#### 数据集 - -由于实际环境中机器人实验受限于场景和硬件,模拟器作为虚拟实验平台,为构建EQA数据集提供了合适的环境条件。在模拟器中训练和测试模型显著降低了实验成本,并提高了模型在真实机器上部署的成功率。本文简要介绍了几个EQA数据集,如表IX所示。 - -• **EQA v1**:第一个设计用于EQA的数据集,基于SUNCG数据集中的合成3D室内场景,通过House3D模拟器生成,包含了四种类型的问题:位置、颜色、颜色房间和介词问题,分布在750多个环境中,共5000多个问题。 - -• **MT-EQA**:在House3D中构建,基于SUNCG,扩展至多物体设置,设计了包括颜色、距离和大小比较在内的六种问题类型,涵盖588个环境中的19287个问题。 - -• **MP3D-EQA**:基于Matterport3D数据集开发的模拟器MINOS,扩展了EQA任务到真实3D环境,生成了包含位置、颜色、颜色房间等问题的1136个问题,覆盖83个家庭环境。 - -• **IQUAD V1**:在AI2-THOR上构建,包含存在、计数和空间关系三种问题类型,通过预设模板生成了75000多个选择题,每个问题均有独特的场景配置,要求智能体具备较强的互动能力。 - -• **VideoNavQA**:将视觉推理与导航分离,生成了101000对视频和问题,问题分布在House3D环境中,涵盖存在、计数和定位等八大类28种问题。 - -• **SQA3D**:专注于场景理解,基于ScanNet场景数据提供6800个独特情境、20400个描述和33400个推理问题。 - -• **K-EQA**:在AI2Thor上构建,增加了逻辑关系和知识图谱,包含6000个不同环境中的60000个问题。 - -• **OpenEQA**:首个开放词汇EQA数据集,支持情节记忆(EM-EQA)和主动探索(A-EQA)两种任务,覆盖180多个真实环境中的1600多个问题。 - -• **HM-EQA**:利用GPT-4生成的问题数据集,包含500个问题,涵盖识别、计数、存在、状态和位置问题。 - -• **S-EQA**:利用GPT-4在VirtualHome中生成数据,基于余弦相似度筛选数据,增强了数据集的多样性,通过判断物体状态集合是否达到存在性”是/否”的结论来回答问题。 - -#### 方法介绍 - -在EQA(Embodied Question Answering)任务中,主要涉及导航和问答子任务。实现方法可分为两类:基于神经网络的方法和基于LLMs(大规模语言模型)/VLMs(视觉语言模型)的方法。 - -##### 神经网络方法 - -神经网络方法侧重于模块化设计和任务复杂度扩展 - -**早期方法**:最初,研究人员主要通过深度神经网络来解决EQA任务。这些模型使用模仿学习和强化学习等技术进行训练和微调。Das等人提出的EQA模型包含视觉、语言、导航和回答四个模块,主要通过卷积神经网络(CNN)和循环神经网络(RNN)构建,并采用两阶段训练:先利用自动生成的导航示范数据对导航和回答模块进行独立训练,再通过策略梯度法微调导航架构。 - -**后续改进**:后续研究在保持基本模块的基础上,通过将导航和问答模块整合至统一的随机梯度下降(SGD)训练流程中,以实现联合训练,避免深度强化学习的复杂性。 - -**复杂性扩展**:为增加任务的复杂性,有些研究扩展了多目标和多智能体任务。通过特征提取和场景重建等方法,模型能够整合智能体探索中获取的信息。 - -**动态环境中的交互**:为处理智能体与动态环境的交互,Gordon等人引入了分层交互记忆网络,结合任务选择的规划器和执行任务的低层控制器,并使用Egocentric Spatial GRU(esGRU)存储空间记忆。 - -**外部知识的引入**:针对之前模型在应对复杂问题时无法利用外部知识的缺陷,Tan等人提出了一个框架,利用神经程序合成方法和知识/3D场景图表,帮助智能体获取与对象相关的信息,并通过蒙特卡洛树搜索(MCTS)确定智能体的下一步移动位置。 - -##### **LLMs/VLMs方法** - -LLMs/VLMs方法则利用其自然语言处理和视觉理解的强大能力,通过探索和语言描述等手段更高效地处理EQA任务。 - -• **任务应用**:随着LLMs和VLMs的快速发展,研究人员尝试在EQA任务中应用这些模型,而不进行额外的微调。Majumdar等人探索了将LLMs和VLMs用于情景记忆EQA(EM-EQA)任务和主动EQA(A-EQA)任务。对于EM-EQA任务,他们使用盲LLM、带情景记忆语言描述的Socratic LLMs、场景图描述的Socratic LLMs以及处理多帧场景的VLM。 -- EM-EQA - 1. **盲LLM(Blind LLM)**:这是指在没有视觉输入的情况下,仅通过语言输入来回答问题的LLM。这种模型只能依赖文本描述,因此在环境探索任务中具有明显的局限性,因为它无法“看到”周围环境。 - - 2. **带情景记忆语言描述的Socratic LLM(Socratic LLM with Episodic Memory Descriptions)**:这是指一种Socratic方法的LLM,这种方法通过文字描述来存储和传递情景记忆。智能体在探索环境的过程中会积累情景记忆,这些记忆被转换为语言描述输入到LLM中,帮助它回答基于历史探索信息的问题。 - - 3. **场景图描述的Socratic LLM(Socratic LLM with Scene Graph Descriptions)**:这种Socratic方法的LLM接收的是场景图的描述。场景图是一种结构化的图表,展示了环境中的对象及其关系,通过这种图表描述输入,LLM可以利用这些信息更好地理解环境中的空间和物体分布,从而回答问题。 - - 4. **处理多帧场景的VLM(VLM Processing Multiple Scene Frames)**:VLM(视觉语言模型)在这里用来处理多帧图像信息。它通过接收来自环境的多个视角或帧的图像输入,结合语言信息来推断和回答问题。多帧场景信息让VLM能够更全面地感知环境,增强其回答问题的准确性。 - -• **前沿探索方法**:在A-EQA任务中,使用基于前沿探索(FBE)的方法以进行问题无关的环境探索,一些研究使用FBE识别待探索区域并构建语义地图。此外,采用一致性预测或图像-文本匹配等技术避免过度探索。 - -• **回答强化**:Patel等人则重点关注问答任务,利用多智能体LLM探索环境,并独立给出“是”或“否”的回答,随后由中央回答模型汇总各智能体的反馈,生成更稳健的答案。 - -#### 度量标准 - -在EQA任务中,模型的性能评估通常基于导航和问答两个方面: - -- **评估指标:** - **导航指标**:许多研究沿用Das等人提出的方法,采用以下指标来评估导航性能: - - • **dT**:导航完成时与目标物体的距离。 - - • **d∆**:从初始位置到最终位置的目标距离变化量。 - - • **dmin**:在整个导航过程中,智能体与目标的最小距离。 - -- **问答指标**:问答性能评估主要包括: - - • **平均排名(MR)**:正确答案在候选答案列表中的平均排名。 - - • **准确率**:答案的准确率。 - - • **LLM-Match**:由Majumdar等人提出的指标,用于评估开放词汇答案的正确性,该指标结合了LLM的正确性评估。此外,他们通过将智能体路径的归一化长度作为权重,引入了路径效率的评估。 - - -- 局限性: - - • **数据集**:构建数据集需要大量人力和资源,目前的大规模数据集仍然较少。此外,各个数据集的评估指标并不一致,这使得不同模型之间的测试和性能对比变得复杂。 - - • **模型**:尽管LLM的进步提升了模型的表现,但与人类水平仍有显著差距。未来的研究可能更多地关注如何高效存储智能体探索的环境信息,指导智能体基于环境记忆和问题进行行动规划,同时增强其行为的可解释性。 - -### 具身抓取 Embodied Grasping - -具身交互不仅涉及与人类的问答互动,还包括根据人类指令执行操作,如抓取和放置物体,从而完成机器人、人类和物体之间的交互。体感抓取要求智能体具备全面的语义理解、场景感知、决策能力以及稳健的控制规划。 -具身抓取方法将传统的机器人运动学抓取技术与大规模模型(如LLMs和视觉-语言基础模型)相结合,使智能体能够在多感官感知下执行抓取任务,包括视觉主动感知、语言理解和推理等能力。这种整合使得智能体能够通过视觉、语言和语义的协同工作,更高效地完成复杂的抓取任务。 - -![[Pasted image 20241031014542.png]] -上图是这个Embodied Grasping任务的概述。 -(a). 针对不同类型任务的语言引导抓取示例 -(b).人-智能体-物体的交互概览,智能体在其中完成体感抓取任务,体现了智能体如何根据人类的指令与周围物体进行物理操作的能力。这种交互不仅增强了机器人在家用服务和工业环境中的实用性,还让机器人在多感官整合下对语义和场景理解的复杂任务中表现得更加智能。 -(c).显示“Language Guided Grasping”主题的谷歌学术搜索结果 - -#### 抓取器 - -目前抓取器主要有以下两种: -- **两指平行夹爪**:抓取姿态通常分为两种类型: - - - **4-DOF(四自由度)**:通过三维位置和顶向手部方位(偏航角)来定义抓取姿态,通常称为“顶向抓取”。 - - - **6-DOF(六自由度)**:通过六维的位置和方向来定义抓取姿态,增加了灵活性,可以适应更复杂的抓取场景。 - -- **五指灵巧手**:例如**ShadowHand**,是一种常用的五指机器人灵巧手,具有26个自由度。高自由度带来了生成有效抓取姿态和规划执行轨迹的复杂性,但也使其具备了更高的抓取灵活性,适合处理复杂的抓取任务。 - -#### 数据集 - -为了支持具身抓取任务的研究,研究人员生成了大量的抓取数据集。这些数据集通常包含基于图像(RGB、深度)、点云或3D场景的抓取标注数据。随着多模态大模型(MLMs)和基础语言模型在机器人抓取领域的应用需求的增加,现有数据集也被扩展或重构为语义抓取数据集。这些数据集在研究基于语言的抓取模型时起到了关键作用,使智能体能够建立广泛的语义理解。 - -传统抓取数据集包括单一物体场景和复杂物体堆叠场景,提供符合运动学的稳定抓取标注(4-DOF或6-DOF)。这些数据可以从真实桌面环境中收集,通常包含RGB、深度和点云数据,或来自虚拟环境中的图像、点云或场景模型。这些数据集虽然对抓取模型有用,但缺乏语义信息。因此,这些数据集通过加入语义表达得到了扩展,使语言、视觉和抓取任务能够结合起来。通过加入语义信息,智能体可以更好地理解和执行抓取任务,这一增强使得发展更复杂、更具语义感知的抓取模型成为可能,促进了与环境的更直观和有效的交互。 -![[Pasted image 20241031015536.png]] - -#### 度量标准 - -在具身抓取任务中,深度学习的**损失函数(loss)或优化目标**主要取决于任务的具体目标,包括抓取姿态生成、物体识别、语言指令理解等多个方面。以下是常见的优化目标: - -**1. 抓取姿态生成损失(Grasp Pose Generation Loss)** - -- **回归损失**:如果模型需要预测抓取的位置和角度,则会使用回归损失(如均方误差 MSE)来优化预测姿态和目标姿态之间的差异。 -- **分类损失**:在多种抓取姿态可选的情况下,使用交叉熵损失(Cross-Entropy Loss)来优化抓取姿态的选择。通过将每个候选姿态作为一个类别,模型可以学习选择最合适的抓取姿态。 - -**2. 视觉和语言对齐损失(Vision-Language Alignment Loss)** - -- **对比损失(Contrastive Loss)**:在视觉-语言模型(如CLIPORT)中,通过对比损失函数来优化视觉和语言的对齐。例如,图像和描述的特征在嵌入空间中的距离应最小化,而与其他描述的距离应最大化。 -- **嵌入空间损失**:利用L2范数或余弦相似度等距离度量方法,优化图像特征和语言特征在共享嵌入空间中的相似性,以确保语义上相关的视觉和语言信息保持一致。 - -**3. 多任务损失(Multi-task Loss)** - -- 具身抓取任务中可能包含多种子任务(如物体检测、抓取姿态选择、路径规划等),多任务损失会为每个子任务设置一个单独的损失函数,再通过加权的方式对所有损失进行优化。这样可以使模型在多个任务间取得平衡,提高任务的整体性能。 - -**4. 物体检测与分割损失(Object Detection and Segmentation Loss)** - -- **分类损失**:用于物体类别的识别,例如使用交叉熵损失来区分不同物体类别。 - -- **边界框回归损失(Bounding Box Regression Loss)**:用于预测物体的准确位置,通常使用平滑L1损失来最小化预测的边界框与真实边界框之间的差异。 - -- **分割损失**:在需要精细分割的任务中,使用二值交叉熵损失或Dice系数损失来优化物体分割的准确性。 - -**5. 抓取成功率损失(Grasp Success Loss)** - -- 为确保生成的抓取姿态能够成功抓取物体,许多具身抓取模型会定义一个二分类的成功率损失。该损失可以用二元交叉熵损失(Binary Cross-Entropy Loss)或对比损失等来衡量预测的抓取姿态是否能实现稳定抓取。 -- 例如,在模拟器中标记每次抓取是否成功,然后训练模型最小化失败的概率,使得模型生成的姿态在真实环境中更具鲁棒性。 - -**6. 强化学习奖励优化(Reinforcement Learning Reward Optimization)** - -- 在具身抓取中,如果使用了强化学习,优化目标会以奖励信号(reward)为核心。典型的奖励包括: -- **到达目标物体的奖励**:智能体接近目标物体时给予正奖励。 -- **完成抓取的奖励**:成功抓取并保持物体不掉落给予高奖励。 -- **路径优化**:缩短到目标的路径长度或避免碰撞的奖励。 -- 强化学习算法通过最大化累计奖励的方式优化抓取策略,例如策略梯度方法(如PPO)或Q-learning方法。 - -**7. 场景重构和特征对齐损失(Scene Reconstruction and Feature Alignment Loss)** - -- 在模块化方法(如F3RM或GaussianGrasper)中,使用特征对齐或场景重构损失,优化模型对场景结构的理解。 -- **重建损失**:在3D场景中生成物体特征和空间结构时,可能会用到L2损失来最小化实际场景与重建场景之间的误差,以提高空间推理能力。 - -总结来看,具身抓取任务的深度学习优化目标通常涉及抓取姿态生成、视觉-语言对齐、多任务平衡、抓取成功率等多方面的损失,通过综合这些损失项,使得智能体能够在复杂环境中高效、准确地完成具身抓取任务。 - -#### 语言引导抓取 - -**语言引导抓取**的概念从多模态模型的整合中演化而来,结合了多模态大模型(MLMs)以赋予智能体语义场景推理能力,使其能够根据人类的显式或隐式指令执行抓取操作。近年来,随着大规模语言模型(LLMs)的进步,研究人员对这一主题表现出越来越高的兴趣。 - -在当前的研究趋势中,抓取任务逐渐聚焦于开放世界场景,强调**开放集泛化**方法,通过MLMs的泛化能力,机器人能够在开放环境中更智能和高效地执行抓取任务。在语言引导抓取中,语义信息可以源自显式指令和隐式指令。 - -• **显式指令**:指令明确指定要抓取的物体类别,例如“抓起香蕉”或“抓起苹果”。 - -• **隐式指令**:需要推理来识别要抓取的物体或物体的某个部分,涉及**空间推理**和**逻辑推理**。 - -• **空间推理**:指令可能包含物体或要抓取部分的空间关系,需要根据场景中物体的空间关系推断抓取姿态。例如,“抓取棕色纸巾盒右侧的键盘”需要智能体理解物体的空间排列并进行推理。 - -• **逻辑推理**:指令可能包含需要推理的人类意图的逻辑关系,从而抓取目标。例如,“我渴了,你能给我点喝的吗?”这可能会促使智能体递上一杯水或一瓶饮料,智能体必须确保液体不会在传递过程中洒出,从而生成合理的抓取姿态。 - -通过将语义理解与空间和逻辑推理相结合,使得智能体能够高效准确地完成复杂的抓取任务。 - -#### 端到端方法 - -端到端方法通过整合视觉和语言模型来完成从语义理解到抓取生成的全流程。 - -• **CLIPORT**:这是一个语言驱动的模仿学习智能体,结合了预训练视觉语言模型CLIP和Transporter Net,创建了一个端到端的双流架构,用于语义理解和抓取生成。该模型通过大量从虚拟环境中收集的专家示范数据进行训练,使智能体能够执行语义驱动的抓取任务。 - -• **CROG**:基于OCID数据集,提出了视觉-语言-抓取数据集,并引入了具有竞争力的端到端基准。它利用了CLIP的视觉基础能力,从图像-文本对中直接学习抓取合成。 - -• **Reasoning Grasping**:在GraspNet1 Billion数据集上首次引入推理抓取基准数据集,提出了一个结合多模态LLM与视觉抓取框架的模型,能够基于语义和视觉生成抓取姿态。 - -• **SemGrasp**:这是一种基于语义的抓取生成方法,将语义信息纳入抓取表示中,从而生成灵巧手的抓取姿态。引入了离散表示,将抓取空间与语义空间对齐,以根据语言指令生成抓取姿态。为了支持训练,还提出了一个大规模的抓取-文本对齐数据集CapGrasp。 - -#### 模块化方法 - -模块化方法通过将语言和视觉特征分阶段处理,提升了模型的灵活性和可泛化能力。 - -• **F3RM**:此方法试图将CLIP的文本-图像先验扩展到3D空间,使用提取的特征进行语言定位,随后生成抓取姿态。它结合了精确的3D几何与2D基础模型中的丰富语义,利用CLIP提取的特征通过自然语言指定要操作的物体,展现了对未见表达和新物体类别的泛化能力。 - -• **GaussianGrasper**:利用3D高斯场实现语言引导抓取任务。该方法首先构建3D高斯场,然后进行特征提取,再基于提取的特征进行语言定位,最终通过最先进的预训练抓取网络生成抓取姿态。它结合了开放词汇语义和精确的几何特征,使得机器人能够根据语言指令执行抓取。 - -这些方法利用端到端和模块化框架,提升了机器人理解和执行复杂抓取任务的能力,借助自然语言指令,使得机器人能够完成更为复杂的任务。 - -## 具身智能体 Embodied Agent - -Agent : 感知环境,采取行动,完成目标。(RL中的Agent) -多模态大模型将这个任务扩展到现实场景的应用上。变成具身Agent。 -![[Pasted image 20241101003208.png]] - -具身Agent一般具有强大的多模态感知、交互、规划能力。在多模态Embodied Agent在完成任务时,一般: - - 将复杂任务分解为sub task - - 有效利用具身感知和交互模型完成这些子任务 -任务的规划不仅在行动之前,在行动开始之后,与环境产生交互中也需要将信息反馈给Planer调整规划。 - -具身Agent的研究方向有下面几个:多模态具身Agent、具身任务规划、具身行动规划 - -### 多模态具身Agent模型 Embodied Multimodal Foundation Model - -具身智能体需要通过视觉识别环境、通过听觉理解指令,并对自身状态具备清晰的感知,以便执行复杂的交互与操作。为此,需要一个整合多模态感知与自然语言处理能力的模型来增强智能体的理解和决策能力,即“多模态具身基础模型”应运而生。 -近期,Google DeepMind研究发现,利用基础模型及大规模多样化数据集是最佳策略。他们基于“机器人Transformer”(RT)开发了一系列工作,为具身智能体的未来研究提供了重要参考。 - -#### **模型进展** - -在基础机器人模型的发展上,已经从最早的SayCan模型进化为RT系列: - -• **SayCan模型**:采用了三个独立模型,分别用于计划、可行性判断和低级策略。 - -• **Q-Transformer**:统一了可行性判断与低级策略, - -• **PaLM-E**:整合了计划与可行性判断。 - -• **RT-2**:在此基础上进一步进展,将计划、可行性和低级策略整合为单一模型,使得各个部分之间的正向传递和协同放大成为可能。 - -RT-2引入了“视觉-语言-行动”(VLA)模型,具备“连贯思维”推理能力,能够在多个步骤中进行语义推理,例如在不同语境下选择替代工具或饮料。随后,RT-H(RT Hierarchy)通过层次化的动作规划实现了端到端的机器人变换模型,能够在更细粒度上对任务规划进行推理。 - -#### **数据集与模型开放** - -为了应对具身模型的泛化问题,Google与33家学术机构合作,创建了包含22种不同数据类型的综合Open X-Embodiment数据集,并基于该数据集训练了通用大型模型RT-X。Open X-Embodiment推动了更多开源视觉语言模型(VLM)的应用,例如基于LLaVA的EmbodiedGPT和基于Flamingo的RoboFlamingo。尽管Open X-Embodiment提供了大量数据集,构建数据集仍然是具身机器人平台快速演进中的一大挑战。AutoRT为此创建了一个系统,能在新环境中部署机器人以收集训练数据,借助LLM提升学习能力,通过更全面和多样的数据来扩展学习。 - -#### **性能和效率优化** - -具身模型中基于Transformer的架构需要处理大量上下文数据,包括视觉、语言和具身状态等信息,以及当前任务的记忆,因而在效率上存在一定问题。例如,RT-2的推理频率仅为1-3Hz。为解决这一问题,研究者尝试通过量化和蒸馏模型来提升效率,同时改进模型框架是另一种可行方案。SARA-RT采用了更高效的线性注意力,而RoboMamba则利用了适合长序列任务的mamba架构,推理速度比现有机器人多模态模型快七倍。 - -#### **高层次任务规划与低层次行动执行的协作** - -基于生成模型的RT在高层任务理解与规划方面表现出色,但在低层次行动规划上有所限制。为此,Google提出了RT-Trajectory,通过自动添加机器人的轨迹,提供低层视觉线索以学习机器人控制策略。在RT-2的基础上,RT-H引入了分层的动作框架,通过中间的语言动作将高层任务描述与低层机器人运动关联起来。 - -尽管VLA模型在高层次规划和可行性任务上展示了能力,但在低层物理交互中表现出新技能的能力较弱,且受限于数据集中技能类别,导致执行操作时动作不够灵活。未来的研究应当将强化学习引入大型模型的训练框架,以提升模型在真实环境中的低层物理交互策略学习与优化能力,从而使其更灵活、精确地执行多样化的物理动作。 - -### 具身任务规划 Embodied Task Planning - -在具身任务规划中,如“将苹果放在盘子上”这类任务通常会被分解为若干子任务,例如“找到苹果,拾起苹果”,“找到盘子”,“放下苹果”。找到物体(导航任务)或拾取/放下操作(抓取任务)并不在任务规划的范围内,这些动作通常在模拟器中被预定义,或在真实世界中使用预训练策略模型执行,例如使用CLIPort完成抓取任务。传统的具身任务规划方法通常基于显式规则和逻辑推理,例如使用符号规划算法(如STRIPS和PDDL)或搜索算法(如MCTS和A*)生成计划。然而,这些方法依赖于预定义规则、约束和启发式信息,难以适应环境中的动态变化或不可预见的情况。 - -**1)基于LLM的规划方法:利用大规模语言模型的生成能力** - -在自然语言模型扩展之前,任务规划通常通过在具身指令数据集上训练模型实现。例如FILM利用BERT在Alfred和Alfworld数据集上实现了此功能。然而,这种方法受到训练集示例的限制,难以有效对应物理世界的实际情况。随着LLM具备的突现能力,现在可以利用其内在的世界知识和连贯思维推理,将抽象任务分解为多个步骤并合理规划。例如,Translated LM和Inner Monologue可以在不额外训练的情况下,将复杂任务分解为可管理的步骤,并利用其内部逻辑和知识体系生成解决方案。ReAd则作为多智能体协作框架,通过不同提示实现高效的计划自我优化。 - -此外,部分方法将过去成功的例子抽象为记忆库中的技能,在推理过程中参考这些技能以提高任务规划成功率。一些研究还通过代码而非自然语言进行推理,使任务规划以代码的形式基于可用的API库生成。此外,多轮推理也可以有效地纠正计划中的潜在错误,这是许多基于LLM的智能体研究的重点。例如Socratic Models和Socratic Planner使用提问法来推导可靠的计划。 - -在任务规划过程中,由于任务复杂性或真实环境的动态性,潜在的失败可能发生。通常情况下,这种失败源于规划器未能全面考虑实际场景中的细节。由于缺乏视觉信息,规划出的子任务可能与实际情境不符,导致任务失败。因此,在规划或执行过程中整合视觉信息是必要的,这可以显著提升任务规划的准确性和可行性,更好地应对真实环境中的挑战。 - -**2)利用具身感知模型的视觉信息进行规划** - -基于以上讨论,将视觉信息进一步融入任务规划(或重新规划)显得尤为重要。在此过程中,视觉输入提供的对象标签、位置或描述可以为LLM分解任务和执行任务提供关键参考。通过视觉信息,LLM可以更准确地识别当前环境中的目标对象和障碍物,从而优化任务步骤或调整子任务目标。一些研究使用对象检测器在任务执行过程中查询环境中存在的对象,并将这些信息反馈给LLM,以修正当前计划中的不合理步骤。例如,RoboGPT考虑同一任务中类似对象的不同命名,从而提高重新规划的合理性。 - -然而,标签提供的信息仍然有限,是否可以提供更全面的场景信息?SayPlan提出使用分层3D场景图表示环境,有效缓解了在多楼层和多房间大型环境中的任务规划挑战。ConceptGraphs同样采用3D场景图向LLM提供环境信息,与SayPlan相比,它还具备开放世界的对象检测功能,且任务规划以代码格式呈现,更高效并更符合复杂任务需求。 - -尽管提供视觉提示,LLM仍可能难以捕捉环境的复杂性和动态变化,导致理解错误和任务失败。例如,如果毛巾锁在浴室柜里,智能体可能会不断在浴室中搜索,而未考虑这一可能性。为了解决这一问题,需要开发更鲁棒的算法以整合多种感官数据,增强智能体对环境的理解。此外,即使视觉信息有限,历史数据和上下文推理也可帮助智能体做出合理的判断和决策。这种多模态整合和基于上下文的推理方法不仅能提高任务执行成功率,还为具身人工智能的进步提供了新视角。 - -**3)利用视觉语言模型(VLMs)进行规划** - -与通过外部视觉模型将环境信息转化为文本描述不同,视觉语言模型(VLMs)可以在潜在空间中捕捉视觉细节,尤其是难以用对象标签表示的上下文信息。VLMs能够辨识视觉现象背后的规则,例如即使环境中没有看到毛巾,也可以推测毛巾可能被放在柜子里。这一过程展示了抽象视觉特征与结构化文本特征如何在潜在空间中更有效地对齐。 - -在**EmbodiedGPT**中,Embodied-Former模块将具身信息、视觉信息和文本信息对齐,有效地在任务规划中考虑智能体的状态和环境信息。与直接使用第三人称视角图像的EmbodiedGPT不同,**LEO**将2D自我视角图像和3D场景编码为视觉tokens,从而能够更全面地感知3D世界信息并执行任务。类似地,**EIF-Unknow**模型利用从体素特征提取的语义特征图作为视觉tokens,与文本tokens一同输入到经过训练的LLaVA模型中进行任务规划。 - -此外,具身多模态基础模型或VLA模型在RT系列、PaLM-E和Matcha等研究中通过大量数据集进行训练,实现了具身场景中视觉和文本特征的对齐。然而,任务规划只是智能体完成指令任务的第一步,后续的动作规划决定了任务能否真正完成。 - -在RoboGPT的实验中,任务规划的准确率达到了96%,但整体任务完成率仅为60%,受限于低级规划器的性能。因此,具身智能体能否从“想象任务如何完成”的网络空间过渡到“与环境互动并完成任务”的物理世界,关键在于有效的动作规划。 - -### 具身动作规划 Embodied Action Planning - -在任务规划与动作规划的定义和区别中可以看到,动作规划必须应对现实世界的不确定性,因为任务规划所提供的子任务的粒度不足以指导智能体与环境交互。通常,智能体可以通过以下两种方式实现动作规划: - -1. 使用预训练的具身感知和具身干预模型作为工具,通过API逐步完成任务规划指定的子任务; -2. 利用VLA模型的内在能力来推导动作规划。 - -此外,动作规划的执行结果会反馈给任务规划器,以调整和改进任务规划。 - -**1)利用API进行动作规划** - -典型的方法是将各种训练良好的策略模型的定义和描述作为上下文提供给LLMs,使其理解这些工具并确定何时以及如何调用它们来执行特定任务。此外,通过生成代码,可以将更细化的工具抽象为一个函数库进行调用,而不是直接将子任务的参数传递给导航和抓取模型。面对环境的不确定性,Reflexion可以在执行过程中进一步调整这些工具,以实现更好的泛化效果。 - -优化这些工具可以增强智能体的稳健性,同时可能需要新的工具来完成未知任务。DEPS在零样本学习的前提下,为LLMs赋予各种角色设置,使其在与环境交互时学习多样技能。在后续交互中,LLMs可以学会选择和组合这些技能,以开发出新的技能。这种分层规划范式使智能体能够专注于高级任务规划和决策,而将具体的动作执行委托给策略模型,从而简化了开发过程。 - -任务规划器与动作规划器的模块化设计实现了独立开发、测试和优化,提升了系统的灵活性和可维护性。这一方法使智能体能够通过调用不同的动作规划器适应各种任务和环境,便于修改,而无需大幅调整智能体的结构。然而,调用外部策略模型可能引入延迟,影响响应时间和效率,特别是在实时任务中。智能体的表现严重依赖于策略模型的质量,如果策略模型效果不佳,整体表现也会受到影响。 - -**2)利用VLA模型进行动作规划** - -与传统方法不同,VLA模型在同一系统内实现任务规划和动作执行,减少了通信延迟,提高了系统响应速度和效率。在VLA模型中,感知、决策和执行模块的紧密整合使系统能够更高效地处理复杂任务,并适应动态环境的变化。这种整合还便于实时反馈,使智能体能够自主调整策略,从而增强任务执行的鲁棒性和适应性。 - -然而,这一模式显然更加复杂且成本较高,特别是在处理复杂或长期任务时。此外,关键问题在于,缺乏具身世界模型的动作规划器,仅依靠LLM的内部知识难以模拟物理规律。这一限制阻碍了智能体在物理世界中准确高效地完成任务,难以实现从网络空间向物理世界的无缝迁移。 - -## 模拟到现实中的迁移 Sim-to-Real Adaptation - -在具身AI中,Sim-to-Real适应指的是将智能体在模拟环境中(网络空间)学习的能力或行为迁移到真实世界场景(物理世界)的过程。其目的是验证并提升在模拟环境中开发的算法、模型和控制策略在真实环境中的稳健性和可靠性。为了实现Sim-to-Real适应,需要具身世界模型、数据收集与训练方法、以及具身控制算法这三大关键要素。 - -### **具身世界模型** Embodied World Model - -Sim-to-Real适应的关键之一在于创建尽可能接近真实世界的模拟环境模型,使算法在迁移时能够更好地泛化。世界模型旨在构建一个从感知到动作的端到端模型,或通过预测方式从输入到输出的映射关系。与VLA模型不同,VLA模型首先在大规模互联网数据集上训练以获得高层次的能力,再通过真实世界数据进行微调。而世界模型从零开始基于物理世界数据进行训练,随着数据量增加逐步积累高层能力,更类似人类神经反射系统,更适合在输入输出结构化的场景中使用,如自动驾驶(输入:视觉,输出:油门、刹车、方向盘)或对象分类任务。 - -学习世界模型为物理仿真领域带来了重大进展。与传统仿真方法相比,世界模型能够在信息不完全的情况下进行推理、满足实时计算需求,并随着时间提高预测精度。这种预测能力让机器人逐步发展出物理直觉,以便在现实世界中更好地运行。根据不同的学习流程,世界模型分为生成型方法、预测型方法和知识驱动型方法,具体方法详见表XI。 -![[Pasted image 20241101012729.png]] - -#### **1)生成型方法 Generation-based Methods** - -随着模型和数据规模的不断扩大,生成型模型已展现出理解和生成符合物理规律的图像(如World Models)、视频(如Sora、Pandora)、点云(如3D-VLA)等数据的能力。这表明生成型模型不仅能捕捉数据的统计特性,还能通过内在结构和机制模拟现实世界的物理与因果关系。由于其内嵌的世界知识,生成型模型不仅仅是识别工具,还具备一定的世界模型特征,这种特性可以用于提升其他模型的表现。通过挖掘生成模型中的世界知识,可以增强模型的泛化性与鲁棒性,使其更适应新环境,提高对未知数据的预测精度。 - -然而,生成型模型也存在一些限制。当数据分布显著偏斜或训练数据不足时,生成模型可能产生不准确或扭曲的输出。其训练过程通常需要大量计算资源和时间,且缺乏解释性,使其在实际应用中面临挑战。当前的研究致力于提升生成模型的效率、增强解释性,并处理数据偏差等问题。随着研究的不断深入,生成模型在未来应用中有望展现更大的潜力和价值。 - -#### **2)预测型方法 Prediction-based Methods** - -预测型世界模型通过构建内部表示来预测和理解环境,基于提供的条件重构潜在空间中的特征,从而捕捉更深层次的语义与世界知识。这种模型将输入映射到潜在空间中,通过该空间提取并利用高层语义信息,使机器人能更准确地进行具身下游任务(如iVideoGPT、IRASim、STP、MuDreamer)。相比像素级信息,潜在特征可以抽象化并解耦不同类型知识,使模型在处理复杂任务和场景时表现更好,提升了泛化能力。 - -在时空建模中,世界模型需要根据对象的当前状态和交互方式预测其后续状态,并将此信息与内在知识结合。具身世界模型通过整合感知信息与先验知识,对环境进行动态预测。这一方法不仅依赖于感知数据,还依靠世界知识来推断环境变化,从而生成更准确的时空预测。与像素级处理相比,基于潜在空间的操作能以较低成本保持不同环境中的高性能。然而,模型在处理未见过的环境和情况时可能表现出一定的局限性和不稳定性,且解耦的世界知识在潜在空间中可能存在解释性问题。 - -#### **3)知识驱动型方法 Knowledge-driven Methods** - -知识驱动型世界模型通过人工构建的知识注入模型,使其具备世界知识。在真实-仿真-真实(real2sim2real)方法中,利用现实世界的知识来构建符合物理规律的模拟器,对机器人进行训练,从而提高模型的鲁棒性和泛化能力。此外,人工构建的常识或符合物理的知识也常用于生成模型或模拟器,例如ElastoGen、One-2-3-45和PLoT等。这种方法在模型中加入物理约束,提升了生成任务的准确性和解释性。这些物理约束确保模型知识的准确性和一致性,减少训练和应用中的不确定性。 - -一些方法将人工创建的物理规则与LLMs或MLMs结合,利用其常识能力生成多样化且语义丰富的场景。例如,Holodeck、LEGENT和GRUtopia通过自动优化空间布局实现丰富的场景生成,为具身智能体提供多样化的环境,从而促进通用具身智能体的发展。 - -### **数据收集与训练 Data Collection and Training** - -在Sim-to-Real适应中,高质量数据至关重要。传统的数据收集方法往往依赖昂贵的设备和精确操作,过程耗时且劳动密集,且灵活性不足。近期,已经提出了多种高效且低成本的数据收集和训练方法,用于高质量的示例数据收集。以下总结了在现实和模拟环境中进行数据收集的多种方法(见图16中的示例数据)。 -#### **1)现实世界数据 Real-World Data** - -在大量、丰富的数据集上训练高容量模型已展现出强大的能力,并在下游应用中取得了显著成功。例如,LLMs如ChatGPT、GPT-4和LLaMA在自然语言处理领域表现出色,并在下游任务中提供了强大的问题解决能力。类似地,是否可以在机器人领域训练一个具身大模型,使其具备较强的泛化能力并能适应新的场景和机器人任务?实现这一目标需要大量具身数据集为模型提供训练数据。 - -例如,**Open X-Embodiment**是一个具身数据集,来源于22种不同的机器人,包含527种技能和160,266个任务。收集的数据是机器人执行操作过程中的真实示例数据,主要关注家庭和厨房场景,涉及家具、食物和餐具等物品,操作以拾取和放置任务为主,部分任务涉及更复杂的操作。在该数据集上训练的高容量模型RT-X表现出优异的迁移能力。 - -此外,UMI提出了一个数据收集与策略学习框架,设计了一款便携式手持夹持器和简洁界面,以实现便携、低成本和信息丰富的数据收集,尤其适用于双手和动态操作示例的数据收集。通过简单地修改训练数据,机器人可以实现零样本的双手精准任务的泛化能力。 - -另一个低成本的全身移动操作系统**Mobile ALOHA**,用于在全身移动条件下收集双手操作的数据,如煎虾和上菜等任务。通过该系统和静态ALOHA收集的数据训练智能体,可显著提升移动操作任务的表现,这类智能体可以用于家庭助手或工作助手。此外,人类-智能体协作数据收集中,人类和智能体在数据收集时共同学习,减少人类工作量,加速数据采集并提高数据质量。在具身场景中,收集数据时由人类提供初始动作输入,随后智能体通过迭代扰动和去噪过程优化这些动作,从而生成高质量的操作示例。 - -#### **2)模拟数据 Simulated Data** - -现实世界的数据收集方法常需大量人力、物力和时间,因此大多数情况下,研究者可以选择在模拟环境中收集数据集以进行模型训练。模拟环境中的数据收集不需要大量资源,且通常可以通过程序自动化,节省大量时间。例如,CLIPORT和Transporter Networks使用Pybullet模拟器收集示例数据进行端到端网络模型训练,成功将模型从模拟迁移到现实。**GAPartNet**构建了一个大规模的以部件为中心的交互数据集,提供丰富的部件级别标注,用于感知和交互任务。**SemGrasp**创建了一个大规模的抓取文本对齐数据集CapGrasp,用于虚拟环境中的灵巧抓取任务。 - -#### **3)Sim-to-Real迁移范式 Sim-to-Real Paradigms** - -近期,多个Sim-to-Real迁移范式被提出,通过在模拟环境中进行大量学习,再将其迁移到现实中,以减少昂贵的现实示例数据需求。以下列出五种Sim-to-Real迁移范式: -![[Pasted image 20241101012906.png]] - -• **Real2Sim2Real**:利用强化学习(RL)在“数字孪生”仿真环境中增强模仿学习。首先使用NeRF和VR对场景进行扫描和重建,将构建的场景导入模拟器以实现从真实到仿真的精度,接着在模拟中对专家示例策略进行细化,最后将策略迁移到现实环境中执行。 - -• **TRANSIC**:通过实时人类干预矫正机器人在现实场景中的行为,以缩小Sim-to-Real差距。首先,机器人在模拟中通过RL建立基础策略,随后将这些策略应用于真实机器人,错误时由人类通过远程控制实时纠正行为。干预数据用于训练残差策略,整合基础和残差策略以确保真实应用中的顺畅轨迹。 - -• **域随机化 Domain Randomization**:在模拟训练期间通过参数随机化来增强模型的泛化能力。在仿真中通过随机化参数覆盖多种条件,提高了模型的鲁棒性,使其能够从模拟环境部署到现实环境。 - -• **系统辨识 System Identification**:通过构建现实环境中物理场景的精确数学模型,涵盖动力学和视觉渲染等参数,使模拟环境更接近现实场景,从而促进从模拟到现实的顺利迁移。 - -• **Lang4sim2real**:使用自然语言作为桥梁,以图像的文本描述作为跨域统一信号。先用带有跨域语言描述的图像数据预训练编码器,再使用域不变表示进行多任务语言条件行为克隆策略的训练。该方法通过模拟数据中的丰富信息弥补了现实数据的不足,增强了Sim-to-Real迁移能力。 - -### 具身控制 Embodied Control - -具身控制通过与环境的交互学习并使用奖励机制优化行为,以获得最优策略,从而避免传统物理建模方法的局限性。具身控制方法主要分为两类: - -#### **1)深度强化学习(DRL)** - -深度强化学习(DRL)可以处理高维数据并学习复杂行为模式,适合用于决策与控制任务。例如,**混合动态策略梯度(HDPG)被应用于双足机器人行走控制,使得控制策略可以根据多个标准动态优化。另一个例子DeepGait**,是一种针对地形的步态控制网络,结合了基于模型的运动规划和强化学习方法。它包括一个地形感知规划器,用于生成步态序列和基座运动,以指导机器人朝向目标方向前进。还包含一个步态与基座控制器,用于在执行这些序列时保持平衡。规划器和控制器都通过神经网络参数化,并通过深度强化学习算法优化。 - -#### **2)模仿学习(Imitation Learning)** - -深度强化学习的一个主要缺点是需要大量数据进行多次尝试。为了解决这个问题,模仿学习通过高质量的示例数据来减少数据使用。为提高数据效率,提出了**离线RL + 在线RL**的组合方法,旨在降低交互成本并确保安全性。首先,离线RL从静态的、预收集的大数据集中学习策略,随后将这些策略部署到真实环境中,进行实时交互和探索,并根据反馈进行调整。代表性的模仿学习方法有**ALOHA**和**Mobile ALOHA**,这两种方法均通过人类示例提升了数据效率。 - -尽管具身AI涵盖了高层次的算法、模型和规划模块,其最基础的组成部分仍然是具身控制。因此,如何控制物理实体并赋予其“物理智能”成为关键问题。具身控制直接涉及硬件,如控制关节运动、末端执行器位置以及行走速度。例如,对于机械臂,了解末端执行器的位置后,如何规划关节的运动以到达目标位置?对于类人机器人,如何在掌握运动模式的前提下控制关节以达到预期姿势?这些都是控制领域需要解决的关键问题。 - -#### **具身控制的其他应用** - -一些研究工作专注于机器人控制以提升动作的灵活性。例如,**一种基于视觉的全身控制框架**连接了机器人手臂和机器人狗,利用12个腿部关节、6个手臂关节和1个夹持器,通过追踪机器狗的速度和手臂末端执行器的位置实现更灵活的控制。此外,一些研究利用传统方法控制双足机器人行走,诸如MIT的Cheetah 3、ANYmal和Atlas等机器人,配备稳健的行走控制器,可执行跳跃和跨越障碍等敏捷运动任务。 - -另外,其他研究聚焦于类人机器人控制,使其可以模仿人类动作和行为,以适应各种人类行为模拟任务。 - -![[Pasted image 20241101013442.png]] - -#### **具身控制的整合:RL与Sim-to-Real** - -具身控制将RL与Sim-to-Real技术相结合,通过与环境的交互来优化策略,使机器人能够探索未知领域,甚至超越人类能力,并适应非结构化环境。尽管机器人可以模仿许多人的行为,完成任务仍然需要基于环境反馈的RL训练。最具挑战性的场景是接触密集型任务,这些任务需要实时调整以响应操作对象的状态、变形、材质和受力等反馈信息,这时RL变得不可或缺。 - -在多模态语言模型(MLMs)的时代,MLMs具备对场景语义的广泛理解,可为RL提供稳健的奖励函数。此外,RL对于大模型的任务对齐也至关重要。未来,在经过预训练和微调之后,RL仍然需要与物理世界对齐,以确保模型在真实环境中的有效部署。 - -### **全机器人整合 All Robots in One** - -尽管已有数据集(如Open X-Embodiment)为预训练具身智能体提供了统一结构,但当前数据级别的限制仍然阻碍着通用型具身智能体的发展,主要问题包括:缺乏标准化格式、多样性不足以及数据量不足。特定任务的数据集无法满足训练通用型智能体的需求。现有数据集在多模态感知方面也不够全面——目前没有任何一个数据集能够同时整合图像、3D视觉、文本、触觉和听觉输入。此外,多机器人数据集中缺乏统一格式,这使得数据处理和加载变得复杂,不同机器人平台之间的控制对象表示不兼容,数据量不足以支撑大规模预训练,缺少结合模拟和现实数据的数据集,难以有效解决模拟到现实的迁移问题。 - -#### **ARIO标准的提出** - -![[Pasted image 20241101013803.png]] -为了应对这些挑战,ARIO(All Robots In One)被引入,作为一种新的数据集标准,优化了现有数据集并促进了更通用的具身AI智能体的发展。ARIO标准采用统一格式记录了不同形态机器人之间的控制和运动数据,具有以下主要特性: - -1. **时间戳机制**:通过时间戳标准化数据收集,解决了机器人动作频率和传感器帧率的差异问题。ARIO的统一格式能够适应多种机器人类型的可变数据,确保时间戳的精准性。 - -2. **多样数据集成**:ARIO标准支持集成多机器人数据,便于开发高性能、具备良好泛化能力的具身AI模型,为具身AI数据集提供了理想格式。 - -#### **ARIO大规模数据集的构建** - -在ARIO标准基础上,开发了一个统一的大规模ARIO数据集,包含约300万个情境,涵盖258个系列和321,064个任务。此数据集的构建方式多样化,包括: - -1. **现实世界数据收集**:通过自定义平台收集了3,662个情境,涵盖了105种以上的任务; -2. **基于模拟的数据生成**:利用如Habitat、MuJoCo和SeaWave等平台生成数据,涵盖1,198个任务,共收集了703,088个情境; -3. **开源数据集的标准转换**:将现有的开源数据集转换为ARIO标准,为数据集增加了2,326,438个情境,涉及319,761个任务。 - -ARIO数据集不仅解决了现有数据集的限制,还通过提供一个连贯的框架支持数据收集和表示,促进了更强大、通用的具身AI智能体的研发。借助ARIO标准的统一格式和多样数据,未来的具身AI智能体将能更灵活地导航和与物理世界互动,适应日益复杂和多样的任务场景。 - -# 面对的挑战 - -**1)高质量机器人数据集** - -获取足够的现实机器人数据仍是重大挑战。收集这些数据不仅耗时且资源密集,单靠模拟数据会加剧模拟到现实的差距问题。构建多样的现实机器人数据集需要机构间的紧密合作。此外,为提高模拟数据质量,需开发更真实且高效的模拟器。当前的RT-1通过机器人图像和自然语言指令的预训练模型在导航和抓取任务中取得了良好效果,但获取现实机器人数据集仍然困难。为了构建跨场景、跨任务的具身通用模型,需构建大规模数据集,结合高质量模拟环境数据辅助现实数据。 - -**2)高效利用人类示例数据** - -高效利用人类示例数据需要通过人类的行为和动作示例来训练和改进机器人系统,包括收集、处理和学习人类执行的任务。现有的R3M方法使用动作标签和人类示例数据来学习可泛化的表示,但对于复杂任务的效率仍需提升。有效利用非结构化、多标签和多模态的人类示例数据,结合动作标签数据,将提高具身模型在动态环境中的表现和适应能力,使其更好地执行复杂任务。 - -**3)复杂环境认知** - -复杂环境认知是指具身智能体在物理或虚拟环境中感知、理解和导航复杂现实环境的能力。当前的Say-Can模型基于大规模常识知识,通过预训练LLM模型的任务分解机制来进行简单任务规划,但在理解复杂环境中的长期任务上有所欠缺。为了使机器人系统更具通用性,具身智能体需具备跨场景的知识迁移和泛化能力。这要求构建适应性强且可扩展的具身智能体架构,以处理复杂场景和多样化任务。 - -**4)长期任务执行** - -对于机器人而言,执行单个指令往往意味着长期任务,例如“清洁厨房”涉及多个低层次行动(如重新排列物体、清扫地板、擦拭桌子等)。成功完成此类任务需要机器人能够规划并执行一系列长时间跨度的行动。当前的高层任务规划器在特定场景中取得了初步成功,但在具身任务中仍显不足。应开发具备强大感知能力和常识知识的高效规划器,以应对复杂的长期任务需求。 - -**5)因果关系发现** - -现有的数据驱动具身智能体依赖于数据内在的相关性做出决策,无法真正理解知识、行为与环境之间的因果关系。这种模型方式会导致策略偏差,难以在现实环境中以可解释、稳健且可靠的方式运行。因此,具身智能体需具备世界知识,能够自主进行因果推理。通过交互了解世界并利用溯因推理学习其运作规律,可进一步增强多模态具身智能体在复杂环境中的适应性和决策可靠性。 - -**6)持续学习** - -在机器人应用中,持续学习对于在多样环境中部署机器人学习策略至关重要,但目前仍是一个未充分探索的领域。虽然近期一些研究探讨了持续学习的子主题(如增量学习、快速运动适应和人类在环学习),但这些方案多针对单一任务或平台,尚未应用于基础模型。未来的研究方向包括: - -• 在微调最新数据时混合使用不同比例的先前数据分布,以减轻灾难性遗忘; - -• 从先前分布中开发高效原型或课程用于任务推理; - -• 提高在线学习算法的训练稳定性和样本效率; - -• 将大容量模型无缝集成到控制框架中,以实现实时推理,可通过分层学习或慢-快控制实现。 - -**7)统一评估基准** - -虽然有许多基准可评估低层次控制策略,但它们在评估的技能方面差异较大,且对象和场景往往受限于模拟器的约束。为全面评估具身模型,需要采用包含多种技能的基准,使用真实模拟器进行评估。在高层任务规划器方面,许多基准主要通过问答任务评估规划能力。然而,理想的评估方式是结合高层任务规划器和低层次控制策略来执行长期任务并测量成功率,而不仅依赖于对规划器的孤立评估。这种综合方法提供了对具身AI系统能力的更全面的评估。 diff --git a/source/bak/Diffusion.md.bak b/source/bak/Diffusion.md.bak deleted file mode 100644 index cb98bdd..0000000 --- a/source/bak/Diffusion.md.bak +++ /dev/null @@ -1,76 +0,0 @@ ---- -title: "Diffusion" -date: 2026-04-02 04:03:25 -updated: 2026-04-02 04:03:25 -mathjax: true -tags: - - 深度学习 - - Diffusion -categories: 深度学习 -comments: false ---- -# Diffusion学习 - -- [x] [知乎DDIM&DDPM](https://zhuanlan.zhihu.com/p/666552214) -- [x] [HuggingFace Diffusion公开课](https://github.com/huggingface/diffusion-models-class.git) -- [x] [b站讲的很好的DDPM](https://www.bilibili.com/video/BV1p24y1K7Pf/?vd_source=fc131029c76216a5e8da1df9dbb8fea1) - -# HuggingFace Diffusion Models Course - -[HuggingFace Diffusion公开课](https://github.com/huggingface/diffusion-models-class.git) - -# DDPM - Denoising Diffusion Probabilistic Models - -DDPM 用 $x_t$ 找到 $x_{t-1}$ 的**分布**,再从这个分布中**采样**得到一个 $x_{t-1}$,循环往复就得到了一个 $x_0$。 - -为什么推理速度慢: - -- DDPM 有一个超参数 $T$,马尔可夫链(Markov chain)的总长度(通常 $T=1000$),需要采样1000步才能得到一个好的图像。 - -- $T$ 设置小一些(如100或50)不能加速的原因如下: - 由DDPM中单步加噪公式: - $$x_t=\sqrt{\alpha_t}x_{t-1}+\sqrt{1-\alpha_t}\epsilon$$ - 根据马尔可夫性质,可以由 $x_0$ 一步得到 $x_t$,每次增加一个很小的噪声: - $$x_t=\sqrt{\bar{\alpha}_t}x_{0}+\sqrt{1-\bar{\alpha}_t}\epsilon\ , \quad \bar{\alpha}_t=\alpha_1\alpha_2\alpha_3...\alpha_{t-1}\alpha_t$$ - 其中希望 $\alpha_t \rightarrow 1, \alpha_t \lt 1$(可能取 0.9),在单步加噪时候尽量保留原图的样子,然后加一个很小的噪声。 - 同时,希望 $\bar{\alpha}_T \rightarrow 0$ 这样 $x_T \rightarrow \epsilon \sim \mathcal{N}(0,1)$,这样这个马尔可夫链的末端就是一个标准正态分布。要想满足上面两点,$T$ 必须足够大,所以不能减小 $T$ 的设定值。 - -- 能不能跳步 reverse?(例如 $x_T \rightarrow x_{T-5} \rightarrow ... \rightarrow x_0$) - 不能,原因如下: - DDPM的目标是为了拟合一个概率分布 $p(x_{t-1}|x_0,x_t)$,这个目标分布(后验分布)通过贝叶斯公式推导为严格的高斯分布: - $$p(x_{t-1} \mid x_t, x_0) = \mathcal{N}(x_{t-1}; \tilde{\mu}_t(x_t, x_0), \tilde{\beta}_t I)$$ - 其中均值 $\tilde{\mu}_t = \frac{\sqrt{\alpha_t}(1-\bar{\alpha}_{t-1})}{1-\bar{\alpha}_t} x_t + \frac{\sqrt{\bar{\alpha}_{t-1}}\beta_t}{1-\bar{\alpha}_t} x_0$。 - 该数学推导**强依赖于马尔可夫假设**。在马尔可夫链中,当前状态的转移只与严格相邻的前一个状态有关。如果强制跨步(跳过中间的 $t$),则破坏了马尔可夫链的转移概率结构,导致网络预测的单步噪声 $\epsilon_\theta$ 无法匹配跨步后的真实后验分布,从而引发严重的误差累积。 - -# DDIM - Denoising Diffusion Implicit Models - - -**核心思想:打破马尔可夫假设** - -DDIM 的核心贡献在于证明了:只要前向过程的边缘分布 $q(x_t|x_0)$ 与 DDPM 保持一致(即 $x_t$ 仍然可以写成 $\sqrt{\bar{\alpha}_t}x_0 + \sqrt{1-\bar{\alpha}_t}\epsilon$),就可以构造出非马尔可夫的前向过程。这种构造使得模型可以使用与 DDPM 完全一致的目标函数进行训练,但在采样时解除了严格的步步相连限制。 - -**采样公式重构** - -DDIM 推导出了一个新的广义反向采样公式: - -$$x_{t-1} = \sqrt{\bar{\alpha}_{t-1}} \underbrace{\left( \frac{x_t - \sqrt{1 - \bar{\alpha}_t} \epsilon_\theta(x_t, t)}{\sqrt{\bar{\alpha}_t}} \right)}_{\text{预测的 } x_0} + \underbrace{\sqrt{1 - \bar{\alpha}_{t-1} - \sigma_t^2} \cdot \epsilon_\theta(x_t, t)}_{\text{指向 } x_t \text{ 的方向}} + \underbrace{\sigma_t \epsilon_t}_{\text{随机噪声}}$$ - -该公式逻辑上分为三项: - -1. 用当前 $x_t$ 和网络预测的噪声去估算出的原图 $x_0$。 -2. 指向 $x_t$ 方向的梯度修正项。 -3. 注入的方差大小为 $\sigma_t$ 的随机噪声。 - -**超参数 $\sigma_t$ 的意义** : $\sigma_t$ 决定了生成过程的随机性: -- 当 $\sigma_t = \sqrt{\frac{1-\bar{\alpha}_{t-1}}{1-\bar{\alpha}_t}} \sqrt{1-\frac{\bar{\alpha}_t}{\bar{\alpha}_{t-1}}}$ 时:公式退化回标准的 **DDPM**,具备完整的随机性。 -- 当 $\sigma_t = 0$ 时:随机噪声项消失,整个逆向生成过程变成**确定性**的(Deterministic)。这就是 **DDIM**。由于它是确定性常微分方程(ODE)的近似求解,给定相同的初始噪声 $x_T$,最终生成的 $x_0$ 是固定的。 - -**为什么 DDIM 可以跳步加速?** - -因为 DDIM 的生成公式只依赖于时间步 $t$ 和 $t-1$ 对应的先验参数 $\bar{\alpha}$(而不是像 DDPM 那样依赖 $\alpha_t$ 的马尔可夫单步递推),我们完全可以定义一个更短的时间步子序列 $\tau$(例如 $S=50$,序列为 $\tau_1, \tau_2, ..., \tau_S$)。 - -在跳步采样时,只需将公式中的 $t$ 和 $t-1$ 替换为子序列中的相邻步 $\tau_i$ 和 $\tau_{i-1}$: - -$$x_{\tau_{i-1}} = \sqrt{\bar{\alpha}_{\tau_{i-1}}} \left( \frac{x_{\tau_i} - \sqrt{1 - \bar{\alpha}_{\tau_i}} \epsilon_\theta(x_{\tau_i}, \tau_i)}{\sqrt{\bar{\alpha}_{\tau_i}}} \right) + \sqrt{1 - \bar{\alpha}_{\tau_{i-1}} - \sigma_{\tau_i}^2} \cdot \epsilon_\theta(x_{\tau_i}, \tau_i) + \sigma_{\tau_i} \epsilon$$ - -通过这种方式,DDIM 能够直接使用原本为 DDPM 训练好的网络权重,将采样步数从 1000 步压缩到几十步,大幅提升了推理速度,且生成质量下降极少。 \ No newline at end of file diff --git "a/source/bak/GIT \345\270\270\347\224\250\345\221\275\344\273\244.md.bak" "b/source/bak/GIT \345\270\270\347\224\250\345\221\275\344\273\244.md.bak" deleted file mode 100644 index 96b937b..0000000 --- "a/source/bak/GIT \345\270\270\347\224\250\345\221\275\344\273\244.md.bak" +++ /dev/null @@ -1,349 +0,0 @@ ---- -title: "GIT 常用命令" -date: 2025-05-02 04:20:18 -updated: 2025-05-02 04:25:00 -tags: - - GIT - - 常用软件 - - 终端命令 -categories: 终端相关 -comments: false ---- -# 写在前面 - -`git` 作为 版本管理软件 还是很常用的 但是感觉每次要用什么的时候都不太熟练 还要麻烦去查 所以记录一下常用的 如果以后还有的话继续补充 。 - -目前我常用的命令主要分为 [本地](#本地命令) 和 [远程](#远程命令) ,还有一些在新设备上的 [配置](#配置命令) 命令 这个就放在最后 使用频率很低。 - ---- -# 本地命令 - -## git-add - -`add` 就是把 本地的 文件修改 添加到 暂存区(stage),只是加到暂存区 还没有 `commit` 就没有添加到版本库 没有提交的 `commit id` ,stage 如果不想要了可以用 [reset](#git-reset) 恢复。 -```shell -git add . -git add -git add -p # 这个是交互式 添加文件的部分到 stage -``` -还有一个 把本地和远程仓库连接起来的在 [add remote](#git-add-remote) -## git-commit - -`commit` 就是把 stage 中的修改 提交到 本地 版本库 中。 -```shell -git commit -m "commit msg" -git commit --amend # 修改上一次的提交信息 把新的更改 合并到上一次提交 -``` - -## git-checkout - -`checkout` 用于 对 分支 的操作:切换分支 -```shell -# 切换到 branch 分支上 -git checkout - -# 撤销 仓库中 被追踪(不包括新建的文件)文件的修改(无论有没有 add) -git checkout . - -# 创建一个新分支并立即切换到该分支 -git checkout -b -``` - -创建并且切换 -```shell -git checkout -b -``` - -把文件切换到 指定的 commit-id 的状态 这个可以用 [diff](##git-diff) -```shell -git checkout -- -``` - -## git-branch - -`git branch` 用于查看、创建或删除分支: -```shell -# 查看本地所有分支 -git branch - -# 查看本地和远程的所有分支 -git branch -a - -# 查看本地分支及其最新提交 -git branch -v - -# 查看分支的详细信息(包括分支的最后一次提交、状态等) -git branch -vv - -# 创建一个新分支,但不切换 -git branch - -# 创建一个新分支并立即切换到该分支 -git checkout -b - -# 删除本地分支 -git branch -d # 删除已合并的分支 -git branch -D # 强制删除未合并的分支 - -# 重命名当前分支 -git branch -m - -# 查看分支之间的差异 -git diff -``` - -## git-log - -`git log` 用于查看提交历史。列出从当前分支的最新提交开始,按时间倒序排列的所有提交记录。可以查看每个提交的详细信息,提交哈希、作者、日期以及提交信息。 -```shell -# 查看当前分支的提交历史 -git log - -# 查看简洁的提交历史(只显示提交哈希和提交信息) -git log --oneline - -# 显示提交历史,并且包括每个提交的修改差异 -git log -p - -# 查看某个特定文件的提交历史 -git log - -# 查看某个范围内的提交记录,比如最近10个提交 -git log -n 10 - -# 查看某个作者的提交历史 -git log --author="author-name" - -# 查看某个提交后所有的提交记录 -git log .. -``` - - -## git-diff - -`git diff` 用于查看工作区、暂存区与版本库之间的差异。它可以帮助你在提交之前查看文件修改内容,或者查看某次提交和当前工作区的区别。 -```shell -# 查看工作区和暂存区之间的差异 就是上次 add 之后又改了什么 -git diff - -# 查看暂存区和最近一次提交之间的差异( add 比 上次 commit 改了什么 -git diff --cached - -# 查看工作区与某次提交之间的差异 -git diff - -# 查看两个提交之间的差异 -git diff - -# 查看某个文件的差异 -git diff - -# 查看某个文件在暂存区和当前工作区的差异 -git diff --cached -``` - -## git-status - -查看当前 仓库 状态:分支 有哪些 更改 暂存 未被追踪 -```shell -git status -``` - -## git-reset - -`reset` 用于撤销提交和修改 重置 stage 或者当前分支,下面这种 `HEAD~1` 的用法 就是上一个 commit , 也可以直接用 `commit-id` 回退到指定版本 -```shell -# 撤销 当前 branch 的最后一次 commit 和 add, commit 还是修改过的 -git reset HEAD~1 # 这个就是 --mixed - -# 撤销最后一次 commit ,add 还在,文件修改也还在 -git reset --soft HEAD~1 - -# 撤销 commit 和 add 恢复文件到上个状态 !!!会把文件修改也给删掉 -git reset --hard HEAD~1 -``` - -## git-merge - -合并分支到当前分支 : -```shell -# 把指定 branch 的 提交 合并到当前的 分支 -git merge - -# 如果有无法解决的冲突 就放弃合并 -git merge --abort - -# 非快进合并,将合并历史记录保留下来,方便回溯 -git merge --no-ff -``` - -## git-rebase - -把一个分支的修改应用到另一个分支 -```shell -# 将 当前分支 的提交 应用到 指定分支 branch 上,达到合并的效果,但不会生成多余的合并提交。 -git rebase - -# 当 rebase 中遇到 冲突 并解决后,继续rebase -git rebase --continue - -# 取消 rebase 恢复到 rebase 之前的状态 -git rebase --abort -``` - ---- -# 远程命令 -## git-add-remote - -`git add remote` 用于将远程仓库链接到本地仓库,以便可以推送和拉取代码。每个远程仓库会有一个名字(通常为 `origin`),可以通过这个命令将本地仓库与远程仓库建立连接。`name` 一般是 `origin` -```shell -# 添加远程仓库 -git remote add - -# 查看远程仓库信息 -git remote -v - -# 修改远程仓库的 URL -git remote set-url - -# 移除远程仓库 -git remote remove -``` - -## git-push - -`git push` 用于将本地的提交推送到远程仓库。默认情况下,`git push` 会将当前分支的提交推送到远程仓库中对应的分支。 -```shell -# 将本地的当前分支推送到远程仓库的相应分支 -git push - -# 将本地的指定分支推送到远程仓库 -git push -git push origin main -# 第一次要加上 -u 参数 --set-upstream -# 将本地分支与远程分支建立跟踪关系。-u 后 origin/main 就和本地 main 关联了 -# 之后就可以直接 git push / pull - -# 强制推送,覆盖远程仓库的历史 -git push --force - -# 推送所有本地分支 -git push --all -``` - -## git-pull - -`git pull` 用于从远程仓库拉取最新的更改并自动合并到本地分支。它实际上是 `git fetch` 和 `git merge` 的组合,先拉取远程的更改,再将其合并到当前分支。 -```shell -# 从远程仓库拉取当前分支的最新提交并合并 -git pull - -# 拉取并合并指定分支的更改 -git pull - -# 只拉取远程仓库的更新,不进行合并 -git pull --no-merge - -# 拉取并与当前分支重新合并(常用于避免合并提交) -git pull --rebase -``` - - ---- -# 配置命令 - -git 的 下载 [Download git](https://git-scm.com/downloads) - -版本检查: -```shell -git --version -``` - -## git-config - -列出当前的 config 信息: -```shell -git config --list -``` - -设置 **全局** 信息 ,第三个是对 git 设置全局代理 : -```shell -git config --global user.name "zip95297" -git config --global user.email "zip95297@gmail.com" -git config --global http.proxy socks5://127.0.0.1:7890 -``` - 这个代理端口的位置 要查看科学上网的 开放端口,或者 科学上网软件 打开了允许局域网连接 也可以用 局域网中开了代理的设备(一般用于服务器) - -对某一个仓库设置信息(需要在 仓库目录 下使用): -```shell -git config --local user.name "zip95297" -git config --local user.email "zip95297@gmail.com" -``` - -还有一个 `init.defaultbranch` 的变量记得设置为 main (好像下载之后就会有这个) - -配置对 `github.com` 的 ssh 连接,之后就可以用 ssh 的 URL 访问了: -```shell -ssh-keygen -t rsa -C "zip95297@gmail.com" # 创建 公私钥 对 -# 然后在 github > settings > ssh and gpg keys 中添加自己的公钥,之后测试链接就通了 -√ -> zip @ ~ % ssh -T git@github.com -Hi zip95297! You've successfully authenticated, but GitHub does not provide shell access. -``` -配置之后 `git clone` 等 可以用 git 或者 http 连接。 - -# 关于从版本库或者工作区中删除 - -有三个命令主要: clean (清除untracked),restore(删工作区) ,restore --staged (移除暂存区到工作区) - -## 删除 !新建!文件 (未跟踪) - -clean 是用来删 没被跟踪的 文件和目录 也就是:( 对 已经跟踪 的文件不会有影响) -- 没有被add过 -- 在历史提交中没有(untracked) -下面是具体用法 -```shell -# 查看哪些文件将被删除(--dry-run|彩排的意思) -git clean -n - -# 强制删除 -git clean -f - -# 删除整个目录目录 -git clean -d - -# 包括 ignore 的 -git clean -x - -# 只删除 ignore 的 -git clean -X -``` - -## 恢复到 add 之后的状态 (放弃工作区) - -恢复到 add 之后的 也就是:放弃 工作区修改,保留 暂存区修改 (add 过的会保存) -```shell -git restore -``` - -## 恢复到 上次commit 之后的状态 (放弃工作区和暂存区) - -恢复到 上次 commit 之后的状态分为两步: -1. 放弃暂存区 -2. 放弃工作区 -```shell -git restore --staged # 放弃暂存区 -git restore # 放弃工作区 -``` - -## git ignore 中新添了 规则 - -可以直接指定 从暂存区中 remove 从 **版本库** 中移除: 不删除本地文件 (如果没有 --cached 参数 文件也会被删除) -```shell -git rm --cached -``` - -或者:把所有内容从 版本库 中移除跟踪 重新add -```shell -git rm --cached . -git add . -``` diff --git "a/source/bak/GIT \345\270\270\347\224\250\345\221\275\344\273\244.md.bak.update" "b/source/bak/GIT \345\270\270\347\224\250\345\221\275\344\273\244.md.bak.update" deleted file mode 100644 index 96b937b..0000000 --- "a/source/bak/GIT \345\270\270\347\224\250\345\221\275\344\273\244.md.bak.update" +++ /dev/null @@ -1,349 +0,0 @@ ---- -title: "GIT 常用命令" -date: 2025-05-02 04:20:18 -updated: 2025-05-02 04:25:00 -tags: - - GIT - - 常用软件 - - 终端命令 -categories: 终端相关 -comments: false ---- -# 写在前面 - -`git` 作为 版本管理软件 还是很常用的 但是感觉每次要用什么的时候都不太熟练 还要麻烦去查 所以记录一下常用的 如果以后还有的话继续补充 。 - -目前我常用的命令主要分为 [本地](#本地命令) 和 [远程](#远程命令) ,还有一些在新设备上的 [配置](#配置命令) 命令 这个就放在最后 使用频率很低。 - ---- -# 本地命令 - -## git-add - -`add` 就是把 本地的 文件修改 添加到 暂存区(stage),只是加到暂存区 还没有 `commit` 就没有添加到版本库 没有提交的 `commit id` ,stage 如果不想要了可以用 [reset](#git-reset) 恢复。 -```shell -git add . -git add -git add -p # 这个是交互式 添加文件的部分到 stage -``` -还有一个 把本地和远程仓库连接起来的在 [add remote](#git-add-remote) -## git-commit - -`commit` 就是把 stage 中的修改 提交到 本地 版本库 中。 -```shell -git commit -m "commit msg" -git commit --amend # 修改上一次的提交信息 把新的更改 合并到上一次提交 -``` - -## git-checkout - -`checkout` 用于 对 分支 的操作:切换分支 -```shell -# 切换到 branch 分支上 -git checkout - -# 撤销 仓库中 被追踪(不包括新建的文件)文件的修改(无论有没有 add) -git checkout . - -# 创建一个新分支并立即切换到该分支 -git checkout -b -``` - -创建并且切换 -```shell -git checkout -b -``` - -把文件切换到 指定的 commit-id 的状态 这个可以用 [diff](##git-diff) -```shell -git checkout -- -``` - -## git-branch - -`git branch` 用于查看、创建或删除分支: -```shell -# 查看本地所有分支 -git branch - -# 查看本地和远程的所有分支 -git branch -a - -# 查看本地分支及其最新提交 -git branch -v - -# 查看分支的详细信息(包括分支的最后一次提交、状态等) -git branch -vv - -# 创建一个新分支,但不切换 -git branch - -# 创建一个新分支并立即切换到该分支 -git checkout -b - -# 删除本地分支 -git branch -d # 删除已合并的分支 -git branch -D # 强制删除未合并的分支 - -# 重命名当前分支 -git branch -m - -# 查看分支之间的差异 -git diff -``` - -## git-log - -`git log` 用于查看提交历史。列出从当前分支的最新提交开始,按时间倒序排列的所有提交记录。可以查看每个提交的详细信息,提交哈希、作者、日期以及提交信息。 -```shell -# 查看当前分支的提交历史 -git log - -# 查看简洁的提交历史(只显示提交哈希和提交信息) -git log --oneline - -# 显示提交历史,并且包括每个提交的修改差异 -git log -p - -# 查看某个特定文件的提交历史 -git log - -# 查看某个范围内的提交记录,比如最近10个提交 -git log -n 10 - -# 查看某个作者的提交历史 -git log --author="author-name" - -# 查看某个提交后所有的提交记录 -git log .. -``` - - -## git-diff - -`git diff` 用于查看工作区、暂存区与版本库之间的差异。它可以帮助你在提交之前查看文件修改内容,或者查看某次提交和当前工作区的区别。 -```shell -# 查看工作区和暂存区之间的差异 就是上次 add 之后又改了什么 -git diff - -# 查看暂存区和最近一次提交之间的差异( add 比 上次 commit 改了什么 -git diff --cached - -# 查看工作区与某次提交之间的差异 -git diff - -# 查看两个提交之间的差异 -git diff - -# 查看某个文件的差异 -git diff - -# 查看某个文件在暂存区和当前工作区的差异 -git diff --cached -``` - -## git-status - -查看当前 仓库 状态:分支 有哪些 更改 暂存 未被追踪 -```shell -git status -``` - -## git-reset - -`reset` 用于撤销提交和修改 重置 stage 或者当前分支,下面这种 `HEAD~1` 的用法 就是上一个 commit , 也可以直接用 `commit-id` 回退到指定版本 -```shell -# 撤销 当前 branch 的最后一次 commit 和 add, commit 还是修改过的 -git reset HEAD~1 # 这个就是 --mixed - -# 撤销最后一次 commit ,add 还在,文件修改也还在 -git reset --soft HEAD~1 - -# 撤销 commit 和 add 恢复文件到上个状态 !!!会把文件修改也给删掉 -git reset --hard HEAD~1 -``` - -## git-merge - -合并分支到当前分支 : -```shell -# 把指定 branch 的 提交 合并到当前的 分支 -git merge - -# 如果有无法解决的冲突 就放弃合并 -git merge --abort - -# 非快进合并,将合并历史记录保留下来,方便回溯 -git merge --no-ff -``` - -## git-rebase - -把一个分支的修改应用到另一个分支 -```shell -# 将 当前分支 的提交 应用到 指定分支 branch 上,达到合并的效果,但不会生成多余的合并提交。 -git rebase - -# 当 rebase 中遇到 冲突 并解决后,继续rebase -git rebase --continue - -# 取消 rebase 恢复到 rebase 之前的状态 -git rebase --abort -``` - ---- -# 远程命令 -## git-add-remote - -`git add remote` 用于将远程仓库链接到本地仓库,以便可以推送和拉取代码。每个远程仓库会有一个名字(通常为 `origin`),可以通过这个命令将本地仓库与远程仓库建立连接。`name` 一般是 `origin` -```shell -# 添加远程仓库 -git remote add - -# 查看远程仓库信息 -git remote -v - -# 修改远程仓库的 URL -git remote set-url - -# 移除远程仓库 -git remote remove -``` - -## git-push - -`git push` 用于将本地的提交推送到远程仓库。默认情况下,`git push` 会将当前分支的提交推送到远程仓库中对应的分支。 -```shell -# 将本地的当前分支推送到远程仓库的相应分支 -git push - -# 将本地的指定分支推送到远程仓库 -git push -git push origin main -# 第一次要加上 -u 参数 --set-upstream -# 将本地分支与远程分支建立跟踪关系。-u 后 origin/main 就和本地 main 关联了 -# 之后就可以直接 git push / pull - -# 强制推送,覆盖远程仓库的历史 -git push --force - -# 推送所有本地分支 -git push --all -``` - -## git-pull - -`git pull` 用于从远程仓库拉取最新的更改并自动合并到本地分支。它实际上是 `git fetch` 和 `git merge` 的组合,先拉取远程的更改,再将其合并到当前分支。 -```shell -# 从远程仓库拉取当前分支的最新提交并合并 -git pull - -# 拉取并合并指定分支的更改 -git pull - -# 只拉取远程仓库的更新,不进行合并 -git pull --no-merge - -# 拉取并与当前分支重新合并(常用于避免合并提交) -git pull --rebase -``` - - ---- -# 配置命令 - -git 的 下载 [Download git](https://git-scm.com/downloads) - -版本检查: -```shell -git --version -``` - -## git-config - -列出当前的 config 信息: -```shell -git config --list -``` - -设置 **全局** 信息 ,第三个是对 git 设置全局代理 : -```shell -git config --global user.name "zip95297" -git config --global user.email "zip95297@gmail.com" -git config --global http.proxy socks5://127.0.0.1:7890 -``` - 这个代理端口的位置 要查看科学上网的 开放端口,或者 科学上网软件 打开了允许局域网连接 也可以用 局域网中开了代理的设备(一般用于服务器) - -对某一个仓库设置信息(需要在 仓库目录 下使用): -```shell -git config --local user.name "zip95297" -git config --local user.email "zip95297@gmail.com" -``` - -还有一个 `init.defaultbranch` 的变量记得设置为 main (好像下载之后就会有这个) - -配置对 `github.com` 的 ssh 连接,之后就可以用 ssh 的 URL 访问了: -```shell -ssh-keygen -t rsa -C "zip95297@gmail.com" # 创建 公私钥 对 -# 然后在 github > settings > ssh and gpg keys 中添加自己的公钥,之后测试链接就通了 -√ -> zip @ ~ % ssh -T git@github.com -Hi zip95297! You've successfully authenticated, but GitHub does not provide shell access. -``` -配置之后 `git clone` 等 可以用 git 或者 http 连接。 - -# 关于从版本库或者工作区中删除 - -有三个命令主要: clean (清除untracked),restore(删工作区) ,restore --staged (移除暂存区到工作区) - -## 删除 !新建!文件 (未跟踪) - -clean 是用来删 没被跟踪的 文件和目录 也就是:( 对 已经跟踪 的文件不会有影响) -- 没有被add过 -- 在历史提交中没有(untracked) -下面是具体用法 -```shell -# 查看哪些文件将被删除(--dry-run|彩排的意思) -git clean -n - -# 强制删除 -git clean -f - -# 删除整个目录目录 -git clean -d - -# 包括 ignore 的 -git clean -x - -# 只删除 ignore 的 -git clean -X -``` - -## 恢复到 add 之后的状态 (放弃工作区) - -恢复到 add 之后的 也就是:放弃 工作区修改,保留 暂存区修改 (add 过的会保存) -```shell -git restore -``` - -## 恢复到 上次commit 之后的状态 (放弃工作区和暂存区) - -恢复到 上次 commit 之后的状态分为两步: -1. 放弃暂存区 -2. 放弃工作区 -```shell -git restore --staged # 放弃暂存区 -git restore # 放弃工作区 -``` - -## git ignore 中新添了 规则 - -可以直接指定 从暂存区中 remove 从 **版本库** 中移除: 不删除本地文件 (如果没有 --cached 参数 文件也会被删除) -```shell -git rm --cached -``` - -或者:把所有内容从 版本库 中移除跟踪 重新add -```shell -git rm --cached . -git add . -``` diff --git "a/source/bak/GIT\\ \345\270\270\347\224\250\345\221\275\344\273\244.md.bak" "b/source/bak/GIT\\ \345\270\270\347\224\250\345\221\275\344\273\244.md.bak" deleted file mode 100644 index 7538ab0..0000000 --- "a/source/bak/GIT\\ \345\270\270\347\224\250\345\221\275\344\273\244.md.bak" +++ /dev/null @@ -1,346 +0,0 @@ ---- -# 写在前面 - -`git` 作为 版本管理软件 还是很常用的 但是感觉每次要用什么的时候都不太熟练 还要麻烦去查 所以记录一下常用的 如果以后还有的话继续补充 。 - -目前我常用的命令主要分为 [本地](#本地命令) 和 [远程](#远程命令) ,还有一些在新设备上的 [配置](#配置命令) 命令 这个就放在最后 使用频率很低。 - ---- -# 写在前面 - -`git` 作为 版本管理软件 还是很常用的 但是感觉每次要用什么的时候都不太熟练 还要麻烦去查 所以记录一下常用的 如果以后还有的话继续补充 。 - -目前我常用的命令主要分为 [本地](#本地命令) 和 [远程](#远程命令) ,还有一些在新设备上的 [配置](#配置命令) 命令 这个就放在最后 使用频率很低。 - ---- -# 本地命令 - -## git-add - -`add` 就是把 本地的 文件修改 添加到 暂存区(stage),只是加到暂存区 还没有 `commit` 就没有添加到版本库 没有提交的 `commit id` ,stage 如果不想要了可以用 [reset](#git-reset) 恢复。 -```shell -git add . -git add -git add -p # 这个是交互式 添加文件的部分到 stage -``` -还有一个 把本地和远程仓库连接起来的在 [add remote](#git-add-remote) -## git-commit - -`commit` 就是把 stage 中的修改 提交到 本地 版本库 中。 -```shell -git commit -m "commit msg" -git commit --amend # 修改上一次的提交信息 把新的更改 合并到上一次提交 -``` - -## git-checkout - -`checkout` 用于 对 分支 的操作:切换分支 -```shell -# 切换到 branch 分支上 -git checkout - -# 撤销 仓库中 被追踪(不包括新建的文件)文件的修改(无论有没有 add) -git checkout . - -# 创建一个新分支并立即切换到该分支 -git checkout -b -``` - -创建并且切换 -```shell -git checkout -b -``` - -把文件切换到 指定的 commit-id 的状态 这个可以用 [diff](##git-diff) -```shell -git checkout -- -``` - -## git-branch - -`git branch` 用于查看、创建或删除分支: -```shell -# 查看本地所有分支 -git branch - -# 查看本地和远程的所有分支 -git branch -a - -# 查看本地分支及其最新提交 -git branch -v - -# 查看分支的详细信息(包括分支的最后一次提交、状态等) -git branch -vv - -# 创建一个新分支,但不切换 -git branch - -# 创建一个新分支并立即切换到该分支 -git checkout -b - -# 删除本地分支 -git branch -d # 删除已合并的分支 -git branch -D # 强制删除未合并的分支 - -# 重命名当前分支 -git branch -m - -# 查看分支之间的差异 -git diff -``` - -## git-log - -`git log` 用于查看提交历史。列出从当前分支的最新提交开始,按时间倒序排列的所有提交记录。可以查看每个提交的详细信息,提交哈希、作者、日期以及提交信息。 -```shell -# 查看当前分支的提交历史 -git log - -# 查看简洁的提交历史(只显示提交哈希和提交信息) -git log --oneline - -# 显示提交历史,并且包括每个提交的修改差异 -git log -p - -# 查看某个特定文件的提交历史 -git log - -# 查看某个范围内的提交记录,比如最近10个提交 -git log -n 10 - -# 查看某个作者的提交历史 -git log --author="author-name" - -# 查看某个提交后所有的提交记录 -git log .. -``` - - -## git-diff - -`git diff` 用于查看工作区、暂存区与版本库之间的差异。它可以帮助你在提交之前查看文件修改内容,或者查看某次提交和当前工作区的区别。 -```shell -# 查看工作区和暂存区之间的差异 就是上次 add 之后又改了什么 -git diff - -# 查看暂存区和最近一次提交之间的差异( add 比 上次 commit 改了什么 -git diff --cached - -# 查看工作区与某次提交之间的差异 -git diff - -# 查看两个提交之间的差异 -git diff - -# 查看某个文件的差异 -git diff - -# 查看某个文件在暂存区和当前工作区的差异 -git diff --cached -``` - -## git-status - -查看当前 仓库 状态:分支 有哪些 更改 暂存 未被追踪 -```shell -git status -``` - -## git-reset - -`reset` 用于撤销提交和修改 重置 stage 或者当前分支,下面这种 `HEAD~1` 的用法 就是上一个 commit , 也可以直接用 `commit-id` 回退到指定版本 -```shell -# 撤销 当前 branch 的最后一次 commit 和 add, commit 还是修改过的 -git reset HEAD~1 # 这个就是 --mixed - -# 撤销最后一次 commit ,add 还在,文件修改也还在 -git reset --soft HEAD~1 - -# 撤销 commit 和 add 恢复文件到上个状态 !!!会把文件修改也给删掉 -git reset --hard HEAD~1 -``` - -## git-merge - -合并分支到当前分支 : -```shell -# 把指定 branch 的 提交 合并到当前的 分支 -git merge - -# 如果有无法解决的冲突 就放弃合并 -git merge --abort - -# 非快进合并,将合并历史记录保留下来,方便回溯 -git merge --no-ff -``` - -## git-rebase - -把一个分支的修改应用到另一个分支 -```shell -# 将 当前分支 的提交 应用到 指定分支 branch 上,达到合并的效果,但不会生成多余的合并提交。 -git rebase - -# 当 rebase 中遇到 冲突 并解决后,继续rebase -git rebase --continue - -# 取消 rebase 恢复到 rebase 之前的状态 -git rebase --abort -``` - ---- -# 远程命令 -## git-add-remote - -`git add remote` 用于将远程仓库链接到本地仓库,以便可以推送和拉取代码。每个远程仓库会有一个名字(通常为 `origin`),可以通过这个命令将本地仓库与远程仓库建立连接。`name` 一般是 `origin` -```shell -# 添加远程仓库 -git remote add - -# 查看远程仓库信息 -git remote -v - -# 修改远程仓库的 URL -git remote set-url - -# 移除远程仓库 -git remote remove -``` - -## git-push - -`git push` 用于将本地的提交推送到远程仓库。默认情况下,`git push` 会将当前分支的提交推送到远程仓库中对应的分支。 -```shell -# 将本地的当前分支推送到远程仓库的相应分支 -git push - -# 将本地的指定分支推送到远程仓库 -git push -git push origin main -# 第一次要加上 -u 参数 --set-upstream -# 将本地分支与远程分支建立跟踪关系。-u 后 origin/main 就和本地 main 关联了 -# 之后就可以直接 git push / pull - -# 强制推送,覆盖远程仓库的历史 -git push --force - -# 推送所有本地分支 -git push --all -``` - -## git-pull - -`git pull` 用于从远程仓库拉取最新的更改并自动合并到本地分支。它实际上是 `git fetch` 和 `git merge` 的组合,先拉取远程的更改,再将其合并到当前分支。 -```shell -# 从远程仓库拉取当前分支的最新提交并合并 -git pull - -# 拉取并合并指定分支的更改 -git pull - -# 只拉取远程仓库的更新,不进行合并 -git pull --no-merge - -# 拉取并与当前分支重新合并(常用于避免合并提交) -git pull --rebase -``` - - ---- -# 配置命令 - -git 的 下载 [Download git](https://git-scm.com/downloads) - -版本检查: -```shell -git --version -``` - -## git-config - -列出当前的 config 信息: -```shell -git config --list -``` - -设置 **全局** 信息 ,第三个是对 git 设置全局代理 : -```shell -git config --global user.name "zip95297" -git config --global user.email "zip95297@gmail.com" -git config --global http.proxy socks5://127.0.0.1:7890 -``` - 这个代理端口的位置 要查看科学上网的 开放端口,或者 科学上网软件 打开了允许局域网连接 也可以用 局域网中开了代理的设备(一般用于服务器) - -对某一个仓库设置信息(需要在 仓库目录 下使用): -```shell -git config --local user.name "zip95297" -git config --local user.email "zip95297@gmail.com" -``` - -还有一个 `init.defaultbranch` 的变量记得设置为 main (好像下载之后就会有这个) - -配置对 `github.com` 的 ssh 连接,之后就可以用 ssh 的 URL 访问了: -```shell -ssh-keygen -t rsa -C "zip95297@gmail.com" # 创建 公私钥 对 -# 然后在 github > settings > ssh and gpg keys 中添加自己的公钥,之后测试链接就通了 -√ -> zip @ ~ % ssh -T git@github.com -Hi zip95297! You've successfully authenticated, but GitHub does not provide shell access. -``` -配置之后 `git clone` 等 可以用 git 或者 http 连接。 - -# 关于从版本库或者工作区中删除 - -有三个命令主要: clean (清除untracked),restore(删工作区) ,restore --staged (移除暂存区到工作区) - -## 删除 !新建!文件 (未跟踪) - -clean 是用来删 没被跟踪的 文件和目录 也就是:( 对 已经跟踪 的文件不会有影响) -- 没有被add过 -- 在历史提交中没有(untracked) -下面是具体用法 -```shell -# 查看哪些文件将被删除(--dry-run|彩排的意思) -git clean -n - -# 强制删除 -git clean -f - -# 删除整个目录目录 -git clean -d - -# 包括 ignore 的 -git clean -x - -# 只删除 ignore 的 -git clean -X -``` - -## 恢复到 add 之后的状态 (放弃工作区) - -恢复到 add 之后的 也就是:放弃 工作区修改,保留 暂存区修改 (add 过的会保存) -```shell -git restore -``` - -## 恢复到 上次commit 之后的状态 (放弃工作区和暂存区) - -恢复到 上次 commit 之后的状态分为两步: -1. 放弃暂存区 -2. 放弃工作区 -```shell -git restore --staged # 放弃暂存区 -git restore # 放弃工作区 -``` - -## git ignore 中新添了 规则 - -可以直接指定 从暂存区中 remove 从 **版本库** 中移除: 不删除本地文件 (如果没有 --cached 参数 文件也会被删除) -```shell -git rm --cached -``` - -或者:把所有内容从 版本库 中移除跟踪 重新add -```shell -git rm --cached . -git add . -``` \ No newline at end of file diff --git "a/source/bak/Linux\344\270\255\347\275\221\347\273\234\347\233\270\345\205\263\347\232\204\345\221\275\344\273\244.md.bak" "b/source/bak/Linux\344\270\255\347\275\221\347\273\234\347\233\270\345\205\263\347\232\204\345\221\275\344\273\244.md.bak" deleted file mode 100644 index d144b4a..0000000 --- "a/source/bak/Linux\344\270\255\347\275\221\347\273\234\347\233\270\345\205\263\347\232\204\345\221\275\344\273\244.md.bak" +++ /dev/null @@ -1,67 +0,0 @@ ---- -title: "Linux中网络相关的命令" -date: 2025-05-07 20:51:47 -updated: 2025-05-07 20:59:43 -tags: - - 网络 - - 常用软件 - - 终端工具 -categories: 终端相关 -comments: false ---- -# 写在前面 - -目前用到的有: -- ping -- ifconfig -- curl -- netstat -- nc -- route -- lsof - -软件: -- frp -- chisel -- rsync & scp -- proxychains - -# 命令介绍 - -## route - -如果有多张网卡启用: -- en0 wifi -- en12 有线宽带 - -使用这个命令添加路由:使 向 10.10.5.4 的流量 走 10.11.3.254 网关 gate_way -```shell -sudo route -n add 10.10.5.4 10.11.3.254 -``` - -查看路由表:[netstat](#netstat) - -## netstat - -查看路由表: -```shell -netstat -rn -``` - -## lsof - -查看端口被哪个进程占用: -```shell -lsof -i : -``` - -## curl - -几个好用的URL: -- cip.cc -- ifconfig.me -- ipinfo.io - - -## nc - diff --git "a/source/bak/MacOS&Linux\344\270\255\347\232\204\345\267\245\345\205\267.md.bak" "b/source/bak/MacOS&Linux\344\270\255\347\232\204\345\267\245\345\205\267.md.bak" deleted file mode 100644 index a259c79..0000000 --- "a/source/bak/MacOS&Linux\344\270\255\347\232\204\345\267\245\345\205\267.md.bak" +++ /dev/null @@ -1,200 +0,0 @@ ---- -title: "MacOS&Linux中的工具" -date: 2026-04-02 04:14:55 -updated: 2026-04-02 04:14:55 -mathjax: true -tags: - - 终端工具 - - 常用软件 -categories: 实用技巧 -comments: false ---- -# 一些 CLI 小工具 - -* [x-cmd](https://cn.x-cmd.com/start/) -* **proxychains**: 用于代理 CLI 中的命令。在 Clash 中如果不打开增强模式,CLI 中的应用流量不会被代理,使用 proxychains 可以为 CLI 应用添加代理。注意:在 macOS 中,由于 SIP(系统完整性保护),`/usr/bin` 中的命令行程序并不会被代理。在局域网中,proxychains 的 `ip:port` 配置可以是一个开了允许局域网连接的代理设备上的代理端口。 -* **bat**: `cat` plusplus,可以用 `-l` 参数指定语法。 -* **btop**: TUI top 软件。 -* **ranger**: CLI 中的文件 explorer。 -* **yazi**: 比 ranger 更好用,功能更丰富。 -* **eza**: 可以显示 Nerd Font 的 `ls` 工具,可以用 `alias` 替代 `ls`, `ll`, `la` 等。 -* **fastfetch**: 展示系统的一些信息。 -* **fzf**: 模糊搜索 (fuzzyfind)。可以配合管道交互式查找,如果仅输入 `fzf` 则是查找文件。 -* **starship**: 用于配置 SHELL PROMPT。 -* **zoxide**: 比 zsh plugin 的 jump 更好用的快速跳转。 -* **thefuck**: 输错命令了可以 `fuck` 一下自动纠正。 -* **ncdu**: 用于查看目录中项目的大小。 -* **tldr**: `man` 手册的易读版本。 -* **tmux**: 终端复用工具,无须多言。 -* **neovim**: `vim` plus plus。 -* **tree**: 树状展示目录结构。 -* **sshfs**: 挂载远程目录。 -* **sudo cupsctl WebInterface=yes**: 打开 Command Line 打印的网页接口。 -* **pgrep**: 专门查进程的 grep,例如 `pgrep -au zjb frp`。 -* **ffmpeg**: 音视频处理工具。 -* **7-zip (sevenzip)**: 压缩工具。安装:`brew install 7-zip`,用法:`7zz`。 -* **d (mac 自带)**: 先用 `d` 列出来最近访问的文件夹,输入前面的数字进入该文件夹。 -* **where / which**: 查找命令路径。 -* **whence -v / type**: 显示函数在哪个文件。 -* **iperf3**: 用于测试网速的小工具。 - -### Zsh 配置参考 - -**插件配置 (`~/.zshrc`)**: -```sh -plugins=( - git - web-search - zsh-autosuggestions - sudo - # autojump - autoupdate - zsh-syntax-highlighting -) -``` - -**Prompt 与耗时统计配置**: -```bash -source $ZSH/oh-my-zsh.sh - -export PS1="%(?.%F{green}√.%F{red}?%?)%f -> zjb @ %B%F{white}%1~%f%b %# " -export RPROMPT="%*" - -# 记录命令开始时间(毫秒) -preexec() { - timer_start=$(perl -MTime::HiRes=time -e 'printf("%.0f\n", time * 1000)') -} - -# 命令执行后计算耗时并更新右提示符 -precmd() { - if [[ -n "$timer_start" ]]; then - local timer_end=$(perl -MTime::HiRes=time -e 'printf("%.0f\n", time * 1000)') - local elapsed=$((timer_end - timer_start)) - elapsed=$((elapsed-21)) - local mins=$((elapsed / 60000)) - local secs=$(( (elapsed % 60000) / 1000 )) - local ms=$((elapsed % 1000)) - - local duration="" - [[ $mins -gt 0 ]] && duration+="${mins}m " - [[ $secs -gt 0 ]] && duration+="${secs}s " - [[ $ms -gt 0 ]] && duration+="${ms}ms" - - if [[ -n "$duration" ]]; then - RPROMPT="%(?.%F{green}󱄛 .%F{red}󱄛 )${duration}%f %*" - else - RPROMPT="%*" - fi - - unset timer_start - else - RPROMPT="%*" - fi -} -``` - ---- - -# Mac 上的软件 - -* **ice**: Menu bar 管理软件。 -* **loop**: 窗口管理软件。 -* **itsycal**: Menu bar 上的日历小软件。 -* **AlDente**: 高级电池管理。 -* **AppCleaner**: 软件卸载辅助。 -* **AutoRaise**: 激活 cursor 下的窗口。 -* **BetterDisplay**: 显示器 DDC 控制。 -* **ClashX Pro**: 代理工具,无须多言。 -* **Easydict**: 好用的全局调用的翻译软件。 -* **Folder Preview**: Space 预览目录内容。 -* **IINA**: 视频播放软件。 -* **Input Source Pro**: 自动输入法切换。 -* **iTerm2**: 第一个使用的 Mac 终端软件。 -* **kitty**: 很好用的终端,第二个使用的。 -* **LuLu**: Mac 中的防火墙,可以高度自定义。 -* **Mac Mouse Fix**: 鼠标加强工具,可以为使用鼠标和触控板时单独设置自然滚动的开关。 -* **Maccy**: 剪贴板管理(已被 Raycast 代替)。 -* **MediaMate**: Notch bar 美化。 -* **Microsoft To Do**: 待办事项管理。 -* **OBS**: 录屏软件。[配置教程](https://www.bilibili.com/opus/1044838772918714377)(注:记得把视频中的画布分辨率设置成显示器硬件分辨率,然后 10bit 屏幕设置为 main10)。 -* **Obsidian**: Markdown 笔记软件。 -* **Raycast**: Spotlight 的代替软件。 -* **Snipaste**: 截图软件。 -* **Topit**: 窗口置顶软件,用的很少但是不能没有。 -* **VS Code**: 代码编辑器,无须多言。 -* **RWTS PDFwriter**: 虚拟打印机。[GitHub Repo](https://github.com/rodyager/RWTS-PDFwriter)。卸载路径:`/Library/Printers/RWTS/PDFwriter/uninstall`(注:只能新建一个目录打印)。 - -### 系统清理与优化 - -如果用的时间长变卡了,可以执行以下操作: - -```shell -# 重启 Dock 和 Finder -killall Dock Finder - -# 清理缓存内存 inactive -sudo purge - -# 注意:不要用 sudo -# 删除用户目录下的应用缓存文件 -rm -rf ~/Library/Caches/* - -# 清理用户缓存但更安全的方式 -find ~/Library/Caches -type f -delete -``` - ---- - -# Mac 上的快捷键与系统配置 - -### 常用快捷键 - -| 快捷键 | 功能 | -| :--- | :--- | -| `Cmd + Opt + V` | Notch | -| `Cmd + Opt + B` | 启动台 | -| `Cmd + Opt + D` | Dock | -| `Cmd + Opt + T` | 台前调度 | -| `Opt + Space` | 拖拽预览中的 PDF | - -### 系统配置与命令记录 - -2. **SSH 访问**: 系统设置 -> 共享 -> 远程登录,设置手机 SSH 到电脑。 -3. **重要文件**: `~/Respository/OS_study.dmg` -4. **DNS 屏蔽**: 在 `/etc/hosts` 中修改本地 DNS 屏蔽 Apple 的更新。 -5. **Dock 自动隐藏延迟控制**: - ```sh - # 取消延迟 - defaults write com.apple.dock "autohide-delay" -float "0" && killall Dock - # 恢复默认 - defaults delete com.apple.dock "autohide-delay" && killall Dock - ``` -6. **手势拖动窗口配置**: - ```sh - # 用 Ctrl + Cmd 拖动窗口 - defaults write -g NSWindowShouldDragOnGesture -bool true - # 恢复默认 - defaults delete -g NSWindowShouldDragOnGesture - ``` -7. **关闭 Cursor View UI 特效**: - ```sh - sudo defaults write /Library/Preferences/FeatureFlags/Domain/UIKit.plist redesigned_text_cursor -dict-add Enabled -bool NO - ``` -8. **关闭输入法切换动画**(似乎无效): - ```sh - defaults write kCFPreferencesAnyApplication TSMLanguageIndicatorEnabled -bool false - ``` -9. **参考文档**: [Mac 张](https://qnswkjn28n.feishu.cn/wiki/T8uJwQH4YiIy7BkHyQpczpyDnug) - ---- - -# 一些 Tips - -* **静态路由配置**: 如果 `en0` 连接了 WiFi,`en12` 连接了有线网络,直接 `ssh 10.10.5.4` 可能根据路由表(使用 `route -rn` 查看)走默认的 `en12`。如果这个 IP 实际在 WiFi 环境下,可以用以下命令向路由表中添加静态路由,指定访问 `10.10.5.4` 的网关: - ```sh - sudo route -n add 10.10.5.4 10.11.3.254 - ``` - *(注:后面的 IP 是该 WiFi 环境中的网关,可以在 `netstat` 中查看。)* - -* **UI 异常记录**: 有时候这个图标消失,怀疑是 CursorUI 进程出问题了。 -![[Pasted image 20250428184655.png]] \ No newline at end of file diff --git a/source/bak/ResShift.md.bak b/source/bak/ResShift.md.bak deleted file mode 100644 index a7cc590..0000000 --- a/source/bak/ResShift.md.bak +++ /dev/null @@ -1,110 +0,0 @@ ---- -title: "ResShift 论文阅读" -date: 2025-05-15 20:04:50 -updated: 2025-05-15 20:05:12 -mathjax: true -tags: - - 深度学习 - - 超分重建 - - 论文阅读 - - Diffusion -categories: 深度学习 -comments: false ---- -# ResShift - -## Motivation - -主要想法:缩短马氏链,加速反向传播过程 - -传统的方法是:从高斯分布中采样 Pure Noise 然后逐步 reverse 得到 一个图像。 - -主要问题都是 沿用了 原本 DDPM 中的马氏链 (太长,从 pure noise 开始还原)导致要经过很多次迭代才能 生成出一张图片。而且 reverse 过程过长 还会导致 生成的图像过于平滑。 -(?可能是因为 纹理细节被当做噪声给 过滤掉 ? 如何 balance 高频 和 噪声?) -(?多次经过 diffusion 的reverse 相当于 过了很多次低通滤波? ) - -> One common approach involves inserting the LR image into the input of current diffusion model. and retraining the model from scratch on the training data for SR. 一种方法是 把 LR 插入到输入中 (在Google的Image Super-Resolution via Iterative Refinement )论文中 将 Pure Noise 和 LR concat 然后在 Unet 中 做 reverse - -> Another popular way is to use an unconditional pre-trained diffusion model as a prior and modify its reverse path to generate the expected HR image. 通过 LR 引导 反向过程 ,类似于 LDM 中的 Attn 的作用,但是也是从 Pure Noise 开始的 - -在 超分 任务中 目标是生成 HR , 有先验数据 LR 。通过利用 LR 来得到 生成的HR图像,来缩短马氏链:类似于 直接截断 Pure Noise -> LR(这个LR 不直接是 LR 而是马氏链中接近的一个 节点) ,从 LR 开始进行 reverse 得到 HR。 - -## 前向过程 - -记: - HR 为 $x_0$ , LR 为 $y_0$ ,两者之间距离 Error 为 $e_0$ - -论文 的 核心想法 是:transit from $x_0$ to $y_0$ by gradually shifting their residual $e_0$ through a Markov chain with length T. - -参数序列 $\{\eta_t\}^T_{t-1}$ 随 t 单调增,t=1 时$\eta=0$,t=T 时$\eta=1$ - -逐步加噪: -$$ -q(x_t\mid x_{t-1},y_0)=N(x_t;x_{t-1}+\alpha_te_0,k^2\alpha_tI) -$$ -其中 $\alpha_t=\eta_t-\eta_{t-1}$ , $k$ 用来控制方差。通过逐步加噪的公式(正态分布可加性)可得到:(论文中证明任意步长t的边际分布解析可积) - -一步加噪: -$$ -q(x_t\mid x_0,y_0) = N(x_t;x_0+\eta_t e_0,k^2\eta_t I) -$$ -## 反向过程 - -$$ -p(x_0\left|y_0\right.)=\int p(x_T\left|y_0\right.)\prod_{t=1}^Tp_\theta\left(x_{t-1}\left|x_t\right.,y_0\right.)dx_{1:T} -$$ -在这个 式子中 $p(x_T\left|y_0\right.)\approx N(x_T\left|y_0\right.,k^2I)$ , $p_\theta\left(x_{t-1}\left|x_t\right.,y_0\right.)$ 是 diffusion模型 其中 $\theta$ 就是可学习参数。 - -反向过程 的目的就是为了 估计 给出 $y_0$ 为条件 的 $x_0$ 的后验分布。 - -其中的 $x_T$ 在这个里就是 加噪T步 的图像,整个过程就是从 $x_t$ 还原到 $x_0$ - -## 参数的优化 - -根据 扩散模型文献中 的假设 $p_\theta$ 为: -$$ -p_{{\theta}}({x}_{t-1}|{x}_t,{y}_0)={N}({x}_{t-1};{\mu}_{{\theta}}({x}_t,{y}_0,t),{\Sigma}_{{\theta}}({x}_t,{y}_0,t)) -$$ - -优化目标就是,最小化证据下界: -$$ -\min_{{\theta}}\sum_tD_{{KL}}\left[q({x}_{t-1}|{x}_t,{x}_0,{y}_0)\|p_{{\theta}}({x}_{t-1}|{x}_t,{y}_0)\right] -$$ - -其实就是让模型 **预测的** 前一时刻 图像 靠近 **真实的**前一时刻图像的 分布。 - -反向过程和参数的优化和传统的 Diffusion 几乎没什么区别 - -## 噪声策略 Noise Schedule - -根据前项过程的式子可以看出,噪声的方差由 $\kappa\sqrt{\eta_T}$ 控制 。在LDM中提到第一步加噪的方差应该足够小(e.g., 0.04 in LDM),从而确保 $q(x_1|x_0,y_0)\approx q(x_0)$ , $\eta_T$ 应该尽可能接近1 (前向过程中的均值)。所以文中的 $\eta_T$ Schedule 如下: - 当T=1 时: $\eta_1=min((\frac{0.04}{k})^2,0.001)$ - 当 $T\in[2,T-1]$ , $\sqrt{\eta_t} = \sqrt{\eta_1} \times b_0^{\beta_t}$ , $\beta_t= (\frac{t-1}{T-1})^p \times (T-1) , b_0=exp[\frac{1}{2(T-1)}log \frac{\eta_T}{\eta_1}]$ ,p 是超参数 - - - -# 代码中的细节 - -[ResShift Repo](https://github.com/zsyOAOA/ResShift) 原文仓库在此处 - -用了 VQ-VAE 作为 autoencoder - -LPIPS 通常是一个训练好的 感知相似度模型 一个计算相似度的方法 - -UNetModelSwin 用这个作为 diffusion model - -## 数据 - -### 训练数据 - -训练时候用的 256x256的 HR 图像 根据 LDM 从 ImageNet 的训练集中随机裁剪出,然后使用 RealESRGAN 的 退化流程 合成的 LR 图像。 - -![[Pasted image 20250515211826.png]] - -### 测试数据 - -基于常用退化模型合成了一个测试数据集,用了 ImageNet中取3000张,还有RealSR,和自己收集的 - -## 模型 - -使用 Unet 作为 Diffusion 的网络结构,用 Swin Transformer 块儿 替换 UNet中的 自关注层 diff --git a/source/bak/ResShift.md.bak.update b/source/bak/ResShift.md.bak.update deleted file mode 100644 index a2de8fe..0000000 --- a/source/bak/ResShift.md.bak.update +++ /dev/null @@ -1,110 +0,0 @@ ---- -title: "ResShift 论文阅读" -date: 2025-05-15 20:04:50 -updated: 2025-05-15 20:05:12 -mathjax: true -tags: - - 深度学习 - - 超分重建 - - 论文阅读 - - Diffusion -categories: 深度学习 -comments: false ---- -# ResShift - -## Motivation - -主要想法:缩短马氏链,加速反向传播过程 - -传统的方法是:从高斯分布中采样 Pure Noise 然后逐步 reverse 得到 一个图像。 - -主要问题都是 沿用了 原本 DDPM 中的马氏链 (太长,从 pure noise 开始还原)导致要经过很多次迭代才能 生成出一张图片。而且 reverse 过程过长 还会导致 生成的图像过于平滑。 -(?可能是因为 纹理细节被当做噪声给 过滤掉 ? 如何 balance 高频 和 噪声?) -(?多次经过 diffusion 的reverse 相当于 过了很多次低通滤波? ) - -> One common approach involves inserting the LR image into the input of current diffusion model. and retraining the model from scratch on the training data for SR. 一种方法是 把 LR 插入到输入中 (在Google的Image Super-Resolution via Iterative Refinement )论文中 将 Pure Noise 和 LR concat 然后在 Unet 中 做 reverse - -> Another popular way is to use an unconditional pre-trained diffusion model as a prior and modify its reverse path to generate the expected HR image. 通过 LR 引导 反向过程 ,类似于 LDM 中的 Attn 的作用,但是也是从 Pure Noise 开始的 - -在 超分 任务中 目标是生成 HR , 有先验数据 LR 。通过利用 LR 来得到 生成的HR图像,来缩短马氏链:类似于 直接截断 Pure Noise -> LR(这个LR 不直接是 LR 而是马氏链中接近的一个 节点) ,从 LR 开始进行 reverse 得到 HR。 - -## 前向过程 - -记: - HR 为 $x_0$ , LR 为 $y_0$ ,两者之间距离 Error 为 $e_0$ - -论文 的 核心想法 是:transit from $x_0$ to $y_0$ by gradually shifting their residual $e_0$ through a Markov chain with length T. - -参数序列 $\{\eta_t\}^T_{t-1}$ 随 t 单调增,t=1 时$\eta=0$,t=T 时$\eta=1$ - -逐步加噪: -$$ -q(x_t\mid x_{t-1},y_0)=N(x_t;x_{t-1}+\alpha_te_0,k^2\alpha_tI) -$$ -其中 $\alpha_t=\eta_t-\eta_{t-1}$ , $k$ 用来控制方差。通过逐步加噪的公式(正态分布可加性)可得到:(论文中证明任意步长t的边际分布解析可积) - -一步加噪: -$$ -q(x_t\mid x_0,y_0) = N(x_t;x_0+\eta_t e_0,k^2\eta_t I) -$$ -## 反向过程 - -$$ -p(x_0\left|y_0\right.)=\int p(x_T\left|y_0\right.)\prod_{t=1}^Tp_\theta\left(x_{t-1}\left|x_t\right.,y_0\right.)dx_{1:T} -$$ -在这个 式子中 $p(x_T\left|y_0\right.)\approx N(x_T\left|y_0\right.,k^2I)$ , $p_\theta\left(x_{t-1}\left|x_t\right.,y_0\right.)$ 是 diffusion模型 其中 $\theta$ 就是可学习参数。 - -反向过程 的目的就是为了 估计 给出 $y_0$ 为条件 的 $x_0$ 的后验分布。 - -其中的 $x_T$ 在这个里就是 加噪T步 的图像,整个过程就是从 $x_t$ 还原到 $x_0$ - -## 参数的优化 - -根据 扩散模型文献中 的假设 $p_\theta$ 为: -$$ -p_{{\theta}}({x}_{t-1}|{x}_t,{y}_0)=\mathcal{N}({x}_{t-1};{\mu}_{{\theta}}({x}_t,{y}_0,t),{\Sigma}_{{\theta}}({x}_t,{y}_0,t)) -$$ - -优化目标就是,最小化证据下界: -$$ -\min_{{\theta}}\sum_tD_{\mathrm{KL}}\left[q({x}_{t-1}|{x}_t,{x}_0,{y}_0)\|p_{{\theta}}({x}_{t-1}|{x}_t,{y}_0)\right] -$$ - -其实就是让模型 **预测的** 前一时刻 图像 靠近 **真实的**前一时刻图像的 分布。 - -反向过程和参数的优化和传统的 Diffusion 几乎没什么区别 - -## 噪声策略 Noise Schedule - -根据前项过程的式子可以看出,噪声的方差由 $\kappa\sqrt{\eta_T}$ 控制 。在LDM中提到第一步加噪的方差应该足够小(e.g., 0.04 in LDM),从而确保 $q(x_1|x_0,y_0)\approx q(x_0)$ , $\eta_T$ 应该尽可能接近1 (前向过程中的均值)。所以文中的 $\eta_T$ Schedule 如下: - 当T=1 时: $\eta_1=min((\frac{0.04}{k})^2,0.001)$ - 当 $T\in[2,T-1]$ , $\sqrt{\eta_t} = \sqrt{\eta_1} \times b_0^{\beta_t}$ , $\beta_t= (\frac{t-1}{T-1})^p \times (T-1) , b_0=exp[\frac{1}{2(T-1)}log \frac{\eta_T}{\eta_1}]$ ,p 是超参数 - - - -# 代码中的细节 - -[ResShift Repo](https://github.com/zsyOAOA/ResShift) 原文仓库在此处 - -用了 VQ-VAE 作为 autoencoder - -LPIPS 通常是一个训练好的 感知相似度模型 一个计算相似度的方法 - -UNetModelSwin 用这个作为 diffusion model - -## 数据 - -### 训练数据 - -训练时候用的 256x256的 HR 图像 根据 LDM 从 ImageNet 的训练集中随机裁剪出,然后使用 RealESRGAN 的 退化流程 合成的 LR 图像。 - -![](https://cdn.jsdelivr.net/gh/zip95297/zip95297.github.io@main/source/images/ResShift/Pasted%20image%2020250515211826.webp?raw=true) - -### 测试数据 - -基于常用退化模型合成了一个测试数据集,用了 ImageNet中取3000张,还有RealSR,和自己收集的 - -## 模型 - -使用 Unet 作为 Diffusion 的网络结构,用 Swin Transformer 块儿 替换 UNet中的 自关注层 diff --git a/source/bak/kitty.md.bak b/source/bak/kitty.md.bak deleted file mode 100644 index 523ac6f..0000000 --- a/source/bak/kitty.md.bak +++ /dev/null @@ -1,50 +0,0 @@ ---- -title: "kitty" -date: 2025-05-04 05:16:18 -updated: 2025-05-04 05:16:34 -tags: - - 常用软件 - - 终端模拟器 - - kitty -categories: 终端相关 -description: kitty 是一个特别好用的终端模拟器 之前用的是iTerm2 换成kitty后更好用了! -comments: true ---- -# 写在前面 - -最近在用 kitty 终端 在研究怎么把它配置的更好用,dotfile打包到github上面 ,挂一个官方链接:[Kitty](https://sw.kovidgoyal.net/kitty/) 。 引用一篇写的很好的 博客 [BLOG](https://www.escapelife.site/posts/8e342b57.html) - -> Warning: - 在配置mapping时候某个键不生效 检查一下是不是 后面的配置覆盖了的问题 - -如果在 source 时候 exit 1 可以用 `bash -x ~/.bashrc` 或者 `zsh -x ~/.zshrc` 进入debug - -# Kitty - GPU 加速的终端 - -## kitten copyboard - -可以在所有服务器上面 `alias copy='kitten copyboard'` 然后就可以像 pbcopy 一样使用,具体请参阅官方文档 : [kitten clipboard](https://sw.kovidgoyal.net/kitty/kittens/clipboard/) - -一些常用用法: - -```shell -# Copy an image to the clipboard: -kitten clipboard picture.png - -# Copy an image and some text to the clipboard: -kitten clipboard picture.jpg text.txt - -# Copy text from STDIN and an image to the clipboard: -echo hello | kitten clipboard picture.png /dev/stdin - -# Copy any raster image available on the clipboard to a PNG file: -kitten clipboard -g picture.png - -# Copy an image to a file and text to STDOUT: -kitten clipboard -g picture.png /dev/stdout - -# List the formats available on the system clipboard -kitten clipboard -g -m . /dev/stdout -``` - -## 其他特性以后用到了继续补 \ No newline at end of file diff --git "a/source/bak/nanovllm\347\233\270\345\205\263.md.bak" "b/source/bak/nanovllm\347\233\270\345\205\263.md.bak" deleted file mode 100644 index 6dccfb5..0000000 --- "a/source/bak/nanovllm\347\233\270\345\205\263.md.bak" +++ /dev/null @@ -1,85 +0,0 @@ ---- -title: "nanovllm相关" -date: 2026-04-02 04:34:18 -updated: 2026-04-02 04:34:18 -mathjax: true -tags: - - 深度学习 -categories: 深度学习 -comments: false ---- -# 模型架构与 FlashAttention 底层优化解析 - -以下是对推理引擎中模型架构与注意力机制核心算子的技术推演与重构,侧重于计算瓶颈的分析、数学逻辑的推导以及硬件层面的 Trade-off。 - -## 1. 模型架构配置分析 - -根据提取的维度信息(16 个 Q Head,8 个 KV Head,Head Dim = 128),该模型采用了 **GQA (Grouped Query Attention)** 架构,Q 与 KV 的比例为 2:1。 -![[Pasted image 20260306195828.png]] - -- **基础设施影响**:相较于传统的 MHA (Multi-Head Attention),GQA 是推理阶段(尤其是 Decode 阶段)缓解 Memory Bound 的关键设计。KV Cache 的显存占用直接减少了 50%,极大降低了显存带宽的读写压力,并允许系统维持更大的 Batch Size 从而提升系统吞吐。 - -- **归一化选择**:采用 **RMSNorm** 替代 LayerNorm,移除了均值计算(Mean-centering),在保证模型收敛和精度的前提下,减少了规约操作(Reduction)的开销,提升了前向推理的速度。 - - -## 2. 标准 Attention 的硬件瓶颈 (Memory Wall) - -传统 Attention 的计算流程受制于 GPU 的内存层级架构(HBM 与 SRAM 的速度差)。核心问题在于 IO Bound,而非 Compute Bound。 - -![[Pasted image 20260309203632.png]] -对于序列长度为 $N$,特征维度为 $d$ 的输入: - -1. **读 Q, K**:从 HBM 读取所需数据,计算注意力分数矩阵 $S = QK^T$。 -2. **写 S**:将 $N \times N$ 的 $S$ 矩阵写入 HBM。 -3. **读 S, 写 P**:读取 $S$,计算 $P = \text{Softmax}(S)$,写回 HBM。 -4. **读 P, 读 V**:读取 $P$ 和 $V$,计算输出 $O = PV$。 -5. **写 O**:将 $N \times d$ 的 $O$ 写回 HBM。 - -其访存复杂度(IO Complexity)为 $O(Nd + N^2)$。当 $N$ 增大时(长上下文),对 $N \times N$ 尺寸的中间矩阵 $S$ 和 $P$ 的频繁 HBM 读写会彻底打满显存带宽,导致计算单元(SM)处于长时间的等待状态(空转)。 - -## 3. FlashAttention 核心解法之一:Online Softmax - -为了消除 $O(N^2)$ 的中间矩阵存储,FlashAttention 通过分块计算(Tiling)将整个过程融合(Fusion)为一个算子。这里的数学难点在于 Softmax 的分母需要全局信息,无法直接分块计算。 - -![[Pasted image 20260309204255.png]] - -Online Softmax 的核心思想是:**维护局部最大值和局部指数和,当引入新块时,通过缩放(Rescaling)修正历史结果**。 - -假设我们将输入分成多个块。对于第 $k$ 个块,我们计算并维护三个局部变量: - -- **局部最大值**:$m^{(k)} = \max(x^{(k)})$ -- **局部指数和**:$l^{(k)} = \sum e^{x^{(k)} - m^{(k)}}$ -- **未归一化的输出值**:$\tilde{O}^{(k)}$ - -当第 $k$ 块计算完成,接下来要合并第 $k+1$ 块时,递推更新逻辑如下: - -1. **更新全局最大值**: - $$m_{new} = \max(m^{(k)}, m^{(k+1)})$$ -2. **修正历史指数和并更新总和**: - 原有的和 $l^{(k)}$ 是基于旧的最大值 $m^{(k)}$ 计算的,需要乘上衰减因子 $e^{m^{(k)} - m_{new}}$ 进行修正。 - $$l_{new} = l^{(k)} \cdot e^{m^{(k)} - m_{new}} + l^{(k+1)} \cdot e^{m^{(k+1)} - m_{new}}$$ -3. **修正历史输出并累加新块**: - 原有的未归一化输出同样需要用衰减因子修正,再加上新块的贡献。 - $$\tilde{O}_{new} = \tilde{O}^{(k)} \cdot e^{m^{(k)} - m_{new}} + \tilde{O}^{(k+1)} \cdot e^{m^{(k+1)} - m_{new}}$$ - -在所有分块遍历完成后,只需要执行一次最终的除法 $\tilde{O}_{final} / l_{final}$,即可得到完全正确的 Softmax 结果。这个过程避免了任何全局 $N \times N$ 矩阵的物化(Materialization)。 - -## 4. FlashAttention 核心解法之二:Tiling 与硬件映射 - -有了 Online Softmax 的数学基础,就可以将计算逻辑映射到硬件上,最大化利用 SRAM。 - -- **内存分配**:SRAM 的空间有限。需要根据 SRAM 大小设定分块参数 $B_r$(行块大小,对应 Q)和 $B_c$(列块大小,对应 K 和 V)。在 SRAM 中只需分配能够容纳下 $Q_{block}, K_{block}, V_{block}$ 以及 $O_{block}$ 的空间。对于 FP16,占用大致为 $\frac{M}{4d \times 2}$。 - -- **双层循环调度**: - 1. 外层循环:遍历 KV 的块。将 $K_{block}$ 和 $V_{block}$ 从 HBM 加载到 SRAM。 - 2. 内层循环:遍历 Q 的块。将 $Q_{block}$ 加载到 SRAM,与 $K_{block}$ 计算局部 $S_{block}$。 - 3. 立即在 SRAM 中执行 Online Softmax 更新,随后乘上 $V_{block}$ 更新 $O_{block}$。 - 4. (但是 innerloop 每次结束 都要写 Oml 下次还要返回,所以v2 在Q tiling 并行,遍历KV 不用反复读写Olm,更快切减小溢出风险。) - -## 5. 思考 - -**用计算换 IO (Compute for Memory)** - -FlashAttention 实际上**增加**了总的浮点运算次数(FLOPs)。因为在 Online Softmax 中引入了大量的 $e^{m_1 - m_2}$ 标量乘法与缩放操作,并且在反向传播(Backward)阶段,由于没有存储 $P$ 和 $S$,必须再前向重算一次 Attention。 - -之所以这种策略能带来数倍的速度提升,是因为在当前及未来的 GPU 架构中,**算力(TFLOPS)的增长速度远超显存带宽(GB/s)的增长**。通过增加常数级别的计算量,彻底消灭了 $O(N^2)$ 的 HBM 读写(将其降为 $O(N^2 d^2 / M)$),这是一个在推理引擎底层架构设计中极其划算的 Trade-off。 \ No newline at end of file diff --git a/source/bak/ssh.md.bak b/source/bak/ssh.md.bak deleted file mode 100644 index d61c581..0000000 --- a/source/bak/ssh.md.bak +++ /dev/null @@ -1,175 +0,0 @@ ---- -title: "SSH 命令的详细使用" -date: 2025-05-03 01:24:21 -updated: 2025-05-04 04:48:31 -tags: - - 常用软件 - - ssh - - 网络 -categories: 终端相关 -comments: false ---- -# SSH - Secure Shell Protocol - -## SSH OPTION 总览 - -`ssh` 最常用的 的连接方式就是 `ssh user@ip` 端口不指定的话默认是 22 ,或者通过 配置 `.ssh/config` 进行登录。 - -### 常用的 - -`ssh` 的 常用参数有下面这些: -```shell -# 指定连接哪个端口 --p - -# 显示 ssh 连接时的详细输出 --v -# 如果使用 -J 参数 可以用 -vv 详细两层详细信息 - -# 跳板机 这个下面详细讲吧 --J - -# 允许 X11 转发,可以把 远程机器的图形程序 通过ssh 传输到本地 -# 条件:远程支持 X11 ,本地有 X server (跟系统有关) --X -# 禁止 X11 转发 --x - -# 直接指定 登录的目标用户 --l -ssh -l user IP - -# 指定从哪个 本地IP 发起连接 (本地有多个网卡-IP) --b - -# 强制给 给 远程命令分配一个 伪终端 -# 例如 ssh Target "vim " 如果没有分配终端就用不了 --t -# 强制 不分配 peseudo tty --T - -# 在远程机器上执行命令 -ssh REMOTE "shell" -``` - -### 转发相关 - -和转发相关的有: -```shell -# 本地某个端口 转发到 远端指定机器的指定端口 -# 本地转发 下面细说 下面这两个也需要 指定 host: user@IP --L :: - -# 远端的某个端口 转发到 本地的指定端口 --R :: - -# 认证后 把 ssh 进程 放在后台 -# 这个一般搭配 端口转发 或者 传入让 远程机器执行的 命令 -# 后面要加一个 command 例如 sleep true 之类的 -# 端口转发时候想常驻链接 可以用 死循环 "while true;do sleep 1;done" --f - -# 允许 允许非本机 通过局域网 连接本地转发的端口 大概是这样 --g - -# 让转发仅转发 不在远程执行命令 -# 如果没有这个 转发会在远程打开 远程shell会话 --N -``` - -### 基本没用过 - -下面是我目前基本没用过的参数,之后遇到了再补充: -```shell -# 启用压缩 减少数据传输量 --C - -# quiet MUTE所有的 ssh 警告和诊断信息 --q - -# 指定 私钥路径 一般在 ~/config/.ssh --i - -# 开启 认证代理 转发功能 可以理解成把私钥转发 -# 通过这个 可以先用私钥连到 A(没有这个私钥) 然后 可以直接免密到 B(有对应公钥) --A -# 关闭 认证代理转发 - -# 指定 用户级配置 config 如果开了这个 系统配置 /etc/ssh/ssh_config 会被忽略 -# 默认是 ~/.ssh/config --F -``` - -## SSH -J 跳板机 - -`-J` 参数可以让通过 跳板机登录到目标主机:例如 我想登录目标机器 `target` 但是要连接的 `target` 我在自己的网络环境中无法访问, 我可以直接访问的是 `jump` ,如果 `jump` 可以访问 `target`,那么就可以通过下面的命令进行连接: -```shell -ssh -J user_A@jump.com[:port] user_B@target.com [-p ] -# 其中 -J 的使用方法如下: --J [user@]jump_host[:port] -``` -上面这个命令等效为: `ssh jump` 然后在 `jump` 上:`ssh target` - -> 这个命令也支持 多个跳板: - `ssh -J user1@jump1,user2@jump2 user@target` - -## SSH 端口转发 - -`ssh -L/-R :: HOST` 后面的参数相当于在 `port` 前面隐含了 本地的地址。这个命令建立了一个 ssh隧道 来对网络上的流量进行转发(Forwarding)。 - -这个还没验证过:默认情况下,SSH 端口转发可能**只允许来自 SSH 客户端本机的连接**,如果想允许外面的 可以在第一个 port 前 加上 0.0.0.0 - -> 访问某个端口 就会给某个端口发送 请求 的流量,通过转发 可以把 流量 从一个 port 转发到另一个 port - -### 本地转发 - 访问本地的流量 F 远程 - -`ssh -L` 用于把 本地的端口 的流量 转发到 远程主机的端口 可以通过本地的某个端口 访问remote的某个端口(在ssh隧道上),例如: 这个 remote addr 可以是 localhost 也可以是别的 ip (如果某个服务只开在10.10.5.4 ,那么就不用 localhost 而是用 10.10.5.4) -```shell -ssh -L 9999::8888 user@remote_host -``` - -通过这个命令: -- 本地会打开 9999 端口 -- 建立转发: - 所有发往本地 9999 端口的流量 会通过 ssh 隧道 转发到远程 remote 上的 `remote addr:9999` -- 打开一个shell(如果不像打开shell 可以用 -f 在后台运行,可以使用 -g 允许局域网内的连接) - -举个例子:比如我要用 T4 localhost:7860 的服务 我就可以: -```shell -ssh -4(指定ipv4) -f -L 7860:localhost:7860 ICCT-T "while true;do sleep 10;done" -``` - -### 远程转发 - 访问远程的流量 F 本地 - -`ssh -R` 用于把 远程端口 的流量 转发到 本地的某个端口上。也就是允许 远程机器访问本地的服务。 -```shell -ssh -R 6666::22 user@remote_host -``` - -通过这个命令: -- 远程上打开 6666 端口 -- 所有发往 远程 6666 的流量都被转发到 本地 的 22 -- 打开一个shell - -> 这两个命令 的第一项 隐含了 R的ip 或者 L的ip -## 配置文件 - -在 用户目录下 `~/.ssh/config` 这是 通过 `ssh` 访问远程机器时候读取的文件 配置连接远程机器时候的各种参数,可以通过 `ssh Host` 来代替 `ssh` 中的长串参数传递。 - -一般来说 通过 `ssh user@hostname -p Port` 连接主机 也可以用下面的配置 -```yaml -Host <别名> - Hostname - User <要登录的用户> - Port <要连接的端口> - PreferredAuthentications <认证方式> # 常用的有 password publickey 可以都选(按顺序) - IdentityFile <私钥位置> - -# 还没有用过 - ForwardAgent <是否允许转发> yes / no # 对应前面的 -A 参数 - ProxyCommand ssh -q -W %h:%p gateway.example.com # 允许通过 ssh 命令 转发ssh连接 - LocalForward 8080 localhost:80 # 设置转发 - RemoteForward 9090 localhost:90 - Compression yes # 压缩流量 用于节省带宽 -``` - diff --git a/source/bak/ssh.md.bak.update b/source/bak/ssh.md.bak.update deleted file mode 100644 index c48253a..0000000 --- a/source/bak/ssh.md.bak.update +++ /dev/null @@ -1,167 +0,0 @@ ---- -title: "SSH 命令的详细使用" -date: 2025-05-03 01:24:21 -updated: 2025-05-04 04:48:31 -tags: - - 常用软件 - - ssh - - 网络 -categories: 终端相关 -comments: false ---- -# SSH - Secure Shell Protocol - -## SSH OPTION 总览 - -`ssh` 最常用的 的连接方式就是 `ssh user@ip` 端口不指定的话默认是 22 ,或者通过 配置 `.ssh/config` 进行登录。 - -### 常用的 - -`ssh` 的 常用参数有下面这些: -```shell -# 指定连接哪个端口 --p - -# 显示 ssh 连接时的详细输出 --v -# 如果使用 -J 参数 可以用 -vv 详细两层详细信息 - -# 跳板机 这个下面详细讲吧 --J - -# 允许 X11 转发,可以把 远程机器的图形程序 通过ssh 传输到本地 -# 条件:远程支持 X11 ,本地有 X server (跟系统有关) --X -# 禁止 X11 转发 --x - -# 直接指定 登录的目标用户 --l -ssh -l user IP - -# 指定从哪个 本地IP 发起连接 (本地有多个网卡-IP) --b - -# 强制给 给 远程命令分配一个 伪终端 -# 例如 ssh Target "vim " 如果没有分配终端就用不了 --t -# 强制 不分配 peseudo tty --T - -# 在远程机器上执行命令 -ssh REMOTE "shell" -``` - -### 转发相关 - -和转发相关的有: -```shell -# 本地某个端口 转发到 远端指定机器的指定端口 -# 本地转发 下面细说 下面这两个也需要 指定 host: user@IP --L :: - -# 远端的某个端口 转发到 本地的指定端口 --R :: - -# 认证后 把 ssh 进程 放在后台 -# 这个一般搭配 端口转发 或者 传入让 远程机器执行的 命令 --f - -# 允许 允许非本机 通过局域网 连接本地转发的端口 大概是这样 --g - -# 让转发仅转发 不在远程执行命令 -# 如果没有这个 转发会在远程打开 远程shell会话 --N -``` - -### 基本没用过 - -下面是我目前基本没用过的参数,之后遇到了再补充: -```shell -# 启用压缩 减少数据传输量 --C - -# quiet MUTE所有的 ssh 警告和诊断信息 --q - -# 指定 私钥路径 一般在 ~/config/.ssh --i - -# 开启 认证代理 转发功能 可以理解成把私钥转发 -# 通过这个 可以先用私钥连到 A(没有这个私钥) 然后 可以直接免密到 B(有对应公钥) --A -# 关闭 认证代理转发 - -# 指定 用户级配置 config 如果开了这个 系统配置 /etc/ssh/ssh_config 会被忽略 -# 默认是 ~/.ssh/config --F -``` - -## SSH -J 跳板机 - -`-J` 参数可以让通过 跳板机登录到目标主机:例如 我想登录目标机器 `target` 但是要连接的 `target` 我在自己的网络环境中无法访问, 我可以直接访问的是 `jump` ,如果 `jump` 可以访问 `target`,那么就可以通过下面的命令进行连接: -```shell -ssh -J user_A@jump.com[:port] user_B@target.com [-p ] -# 其中 -J 的使用方法如下: --J [user@]jump_host[:port] -``` -上面这个命令等效为: `ssh jump` 然后在 `jump` 上:`ssh target` - -> 这个命令也支持 多个跳板: - `ssh -J user1@jump1,user2@jump2 user@target` - -## SSH 端口转发 - -`ssh -L/-R :: HOST` 后面的参数相当于在 `port` 前面隐含了 本地的地址。这个命令建立了一个 ssh隧道 来对网络上的流量进行转发(Forwarding)。 - -这个还没验证过:默认情况下,SSH 端口转发可能**只允许来自 SSH 客户端本机的连接**,如果想允许外面的 可以在第一个 port 前 加上 0.0.0.0 - -> 访问某个端口 就会给某个端口发送 请求 的流量,通过转发 可以把 流量 从一个 port 转发到另一个 port - -### 本地转发 - 访问本地的流量 F 远程 - -`ssh -L` 用于把 本地的端口 的流量 转发到 远程主机的端口 可以通过本地的某个端口 访问remote的某个端口(在ssh隧道上),例如: 这个 remote addr 可以是 localhost 也可以是别的 ip (如果某个服务只开在10.10.5.4 ,那么就不用 localhost 而是用 10.10.5.4) -```shell -ssh -L 9999::8888 user@remote_host -``` - -通过这个命令: -- 本地会打开 9999 端口 -- 建立转发: - 所有发往本地 9999 端口的流量 会通过 ssh 隧道 转发到远程 remote 上的 `remote addr:9999` -- 打开一个shell(如果不像打开shell 可以用 -f 在后台运行,可以使用 -g 允许局域网内的连接) - -### 远程转发 - 访问远程的流量 F 本地 - -`ssh -R` 用于把 远程端口 的流量 转发到 本地的某个端口上。也就是允许 远程机器访问本地的服务。 -```shell -ssh -R 6666::22 user@remote_host -``` - -通过这个命令: -- 远程上打开 6666 端口 -- 所有发往 远程 6666 的流量都被转发到 本地 的 22 -- 打开一个shell - -> 这两个命令 的第一项 隐含了 R的ip 或者 L的ip -## 配置文件 - -在 用户目录下 `~/.ssh/config` 这是 通过 `ssh` 访问远程机器时候读取的文件 配置连接远程机器时候的各种参数,可以通过 `ssh Host` 来代替 `ssh` 中的长串参数传递。 - -一般来说 通过 `ssh user@hostname -p Port` 连接主机 也可以用下面的配置 -```yaml -Host <别名> - Hostname - User <要登录的用户> - Port <要连接的端口> - PreferredAuthentications <认证方式> # 常用的有 password publickey 可以都选(按顺序) - IdentityFile <私钥位置> - -# 还没有用过 - ForwardAgent <是否允许转发> yes / no # 对应前面的 -A 参数 - ProxyCommand ssh -q -W %h:%p gateway.example.com # 允许通过 ssh 命令 转发ssh连接 - LocalForward 8080 localhost:80 # 设置转发 - RemoteForward 9090 localhost:90 - Compression yes # 压缩流量 用于节省带宽 -``` diff --git a/source/bak/torch.md.bak b/source/bak/torch.md.bak deleted file mode 100644 index 3b98668..0000000 --- a/source/bak/torch.md.bak +++ /dev/null @@ -1,286 +0,0 @@ ---- -title: "torch" -date: 2026-04-02 03:52:59 -updated: 2026-04-02 03:52:59 -mathjax: true -tags: - - 深度学习 -categories: 深度学习 -comments: false ---- -# 概念 - - - -## 归一化 - - - -将不同尺度或单位的数据,按一定规则转换到相同尺度下,常用于数据预处理,使其具有统一的分布范围或特性,便于后续的处理或分析。 - - - -归一化主要是为了加速收敛(不同特征数值范围差异大时梯度下降会震荡)、避免数值问题(防止某些特征因数值过大而主导梯度更新)。 - - - -常见的归一化方法: - - - -**最大最小归一化(Min-Max)** - -$$x'=\cfrac{x-x_{min}}{x_{max}-x_{min}}$$ - -把数据线性放缩到 [0,1] 区间,保留原始分布形状,但对异常值敏感 - - - -**Z-score 标准化** - -$$x'=\cfrac{x-\mu}{\sigma}$$ - -把数据转换成均值为 0、标准差为 1 的分布 N(0,1),对异常值更鲁棒。PyTorch 中的 normalization 默认用这个。 - - - -**L2 归一化** - -$$x'=\cfrac{x}{\|x\|_2}$$ - -将向量归一化到单位球面上,常用于特征向量归一化 - -# 相关函数 - - - -## nn.LayerNorm - - - -输入是 `[B, N, D]` - -- B:Batch size,一个批次中的样本数量 - -- N:Sequence length,一个样本中的 token 数量 - -- D:Embedding dimension,每个 token 的 embedding 维度 - - - -LayerNorm 对每个样本的每个 token 的 embedding 向量独立做 Z-score 归一化(不跨样本,不跨 token) - - - -$$ -\mu_{b,n} = \frac{1}{D} \sum_{d=1}^{D} x_{b,n,d} \quad \quad \sigma_{b,n}^2 = \frac{1}{D} \sum_{d=1}^{D} (x_{b,n,d} - \mu_{b,n})^2 -$$ - - - -$$ -y_{b,n,d} = \gamma \cdot \frac{x_{b,n,d} - \mu_{b,n}}{\sqrt{\sigma_{b,n}^2 + \epsilon}} + \beta -$$ - - - -其中 $\gamma$ 和 $\beta$ 是可学习的仿射变换参数,$\epsilon$ 防止除零(默认 1e-5) - - - -使用时要指定 `normalized_shape` 为 embedding 维度: - -```python - -layer_norm = nn.LayerNorm(normalized_shape=512) - -x = torch.randn(32, 128, 512) # [batch, seq_len, emb_dim] - -output = layer_norm(x) - -``` - - - -LN 抹平了不同样本之间的大小关系,保留了不同特征维度之间的相对关系。所以更适合 NLP 任务,一个样本的特征就是不同 token 的 embedding,LN 保留了特征之间的时序关系。Transformer 基本都用 LayerNorm。 - - - -好处是不受 batch size 影响,batch=1 也能正常工作,训练和推理行为一致。 - -## nn.BatchNorm - - - -输入是 `[B, C, H, W]`(图像)或 `[B, C, L]`(序列) - -- B:Batch size - -- C:Channel,通道数 - -- H, W:图像的高度和宽度 - -- L:序列长度 - - - -BatchNorm 对一个 batch 的所有样本在每个通道上做归一化,也就是对每个通道计算所有样本的均值和方差。 - - - -对于图像数据: - -$$ -\mu_c = \frac{1}{B \cdot H \cdot W} \sum_{b=1}^{B} \sum_{h=1}^{H} \sum_{w=1}^{W} x_{b,c,h,w} -$$ - - - -$$ -\sigma_c^2 = \frac{1}{B \cdot H \cdot W} \sum_{b=1}^{B} \sum_{h=1}^{H} \sum_{w=1}^{W} (x_{b,c,h,w} - \mu_c)^2 -$$ - - - -$$ -\hat{x}_{b,c,h,w} = \gamma_c \cdot \frac{x_{b,c,h,w} - \mu_c}{\sqrt{\sigma_c^2 + \epsilon}} + \beta_c -$$ - - - -其中 $\gamma_c$ 和 $\beta_c$ 是每个通道的可学习参数 - - - -使用: - -```python - -batch_norm = nn.BatchNorm2d(num_features=64) # 64 个通道 - -x = torch.randn(32, 64, 28, 28) - -output = batch_norm(x) - -``` - - - -训练时用当前 batch 的均值和方差,同时更新移动平均统计量;推理时用训练时累积的移动平均统计量(running_mean 和 running_var)。 - - - -BN 抹平了不同特征(通道)之间的大小关系,保留了不同样本之间的相对关系。所以更适合 CV 任务,比如图像分类,不同样本之间的大小关系得以保留。CNN 的卷积层后面通常接 BatchNorm。 - - - -注意 batch size 太小(<8)时效果会变差,统计量不稳定。不适合序列长度变化大的 NLP 任务。 - - - -## Dropout - - - -训练时随机将张量中的元素按概率 `p` 设置为 0,防止过拟合。剩余的激活神经元会按 `1/(1-p)` 缩放,保持输出期望值不变。 - - - -$$ -y = \begin{cases} -0 & \text{with probability } p \\ -\frac{x}{1-p} & \text{with probability } 1-p -\end{cases} -$$ - - - -原理是强制网络学习更鲁棒的特征,不依赖特定神经元的组合。每次训练相当于训练一个不同的子网络,推理时相当于多个模型的平均。 - - - -### nn.Dropout - - - -自动根据 `model.train()` / `model.eval()` 切换状态,推荐用这个: - - - -```python - -class MyModel(nn.Module): - def __init__(self): - super().__init__() - self.dropout = nn.Dropout(p=0.5) - self.fc = nn.Linear(512, 256) - - def forward(self, x): - x = self.fc(x) - x = self.dropout(x) # 训练时生效,推理时自动关闭 - return x -``` - - - -### nn.functional.dropout - - - -通过 `training` 参数手动控制: - - - -```python -x = F.dropout(x, p=0.5, training=training) -``` - - - -常用配置:全连接层 `p=0.5`,卷积层用 `nn.Dropout2d` 通常 `p=0.2~0.3`,Transformer 通常 `p=0.1`。dropout 率太高会欠拟合,太低防止过拟合效果不明显。 - -## nn.PixelShuffle - - - -PixelShuffle 是一种空间重排操作(Sub-Pixel Convolution),将图像的通道信息重新分布到空间维度上,实现上采样(放大图像)。 - - - -维度变换:`[B, C × r², H, W]` → `[B, C, H × r, W × r]`,其中 `r` 是上采样因子(upscale_factor) - - - -工作原理:假设 `r=2`,输入 `[B, 4C, H, W]`,会将 `4C` 个通道重组为 `C × 2 × 2` 的形式,每个 `2×2` 的通道块对应输出空间的一个 `2×2` 区域,最后输出 `[B, C, 2H, 2W]`。 - - - -示例(单通道,r=2): - -``` -输入: [B, 4, H, W] (4个通道) -┌─────┬─────┐ -│ C0 │ C1 │ -├─────┼─────┤ -│ C2 │ C3 │ -└─────┴─────┘ - -输出: [B, 1, 2H, 2W] (1个通道,空间扩大2倍) -┌──┬──┐ -│C0│C1│ -├──┼──┤ -│C2│C3│ -└──┴──┘ -``` - -使用: -```python -pixel_shuffle = nn.PixelShuffle(upscale_factor=2) # r=2 -x = torch.randn(1, 64, 32, 32) # [1, 64, 32, 32] -output = pixel_shuffle(x) # [1, 16, 64, 64] -# 通道数: 64 / (2^2) = 16,空间尺寸: 32 * 2 = 64 -``` - -常用于超分辨率(Super-Resolution)和图像生成(GAN、VAE)的上采样阶段,可以替代转置卷积避免棋盘效应。好处是纯重排操作不增加参数,计算也比双线性插值 + 卷积快。 - -注意输入通道数必须是 `C × r²` 的形式,通常在 PixelShuffle 之前用卷积层生成足够的通道数。 diff --git a/source/bak/torch.md.bak.update b/source/bak/torch.md.bak.update deleted file mode 100644 index abad8b6..0000000 --- a/source/bak/torch.md.bak.update +++ /dev/null @@ -1,338 +0,0 @@ ---- -title: "torch" -date: 2026-04-02 03:52:59 -updated: 2026-04-02 03:52:59 -mathjax: true -tags: - - 深度学习 -categories: 深度学习 -comments: false ---- -# 概念 - - - -## 归一化 - - - -将不同尺度或单位的数据,按一定规则转换到相同尺度下,常用于数据预处理,使其具有统一的分布范围或特性,便于后续的处理或分析。 - - - -归一化主要是为了加速收敛(不同特征数值范围差异大时梯度下降会震荡)、避免数值问题(防止某些特征因数值过大而主导梯度更新)。 - - - -常见的归一化方法: - - - -**最大最小归一化(Min-Max)** - -$$x'=\cfrac{x-x_{min}}{x_{max}-x_{min}}$$ - -把数据线性放缩到 [0,1] 区间,保留原始分布形状,但对异常值敏感 - - - -**Z-score 标准化** - -$$x'=\cfrac{x-\mu}{\sigma}$$ - -把数据转换成均值为 0、标准差为 1 的分布 N(0,1),对异常值更鲁棒。PyTorch 中的 normalization 默认用这个。 - - - -**L2 归一化** - -$$x'=\cfrac{x}{\|x\|_2}$$ - -将向量归一化到单位球面上,常用于特征向量归一化 - -# 相关函数 - - - -## nn.LayerNorm - - - -输入是 `[B, N, D]` - -- B:Batch size,一个批次中的样本数量 - -- N:Sequence length,一个样本中的 token 数量 - -- D:Embedding dimension,每个 token 的 embedding 维度 - - - -LayerNorm 对每个样本的每个 token 的 embedding 向量独立做 Z-score 归一化(不跨样本,不跨 token) - - - -$$ - -\mu_{b,n} = \frac{1}{D} \sum_{d=1}^{D} x_{b,n,d} \quad \quad \sigma_{b,n}^2 = \frac{1}{D} \sum_{d=1}^{D} (x_{b,n,d} - \mu_{b,n})^2 - -$$ - - - -$$ - -y_{b,n,d} = \gamma \cdot \frac{x_{b,n,d} - \mu_{b,n}}{\sqrt{\sigma_{b,n}^2 + \epsilon}} + \beta - -$$ - - - -其中 $\gamma$ 和 $\beta$ 是可学习的仿射变换参数,$\epsilon$ 防止除零(默认 1e-5) - - - -使用时要指定 `normalized_shape` 为 embedding 维度: - -```python - -layer_norm = nn.LayerNorm(normalized_shape=512) - -x = torch.randn(32, 128, 512) # [batch, seq_len, emb_dim] - -output = layer_norm(x) - -``` - - - -LN 抹平了不同样本之间的大小关系,保留了不同特征维度之间的相对关系。所以更适合 NLP 任务,一个样本的特征就是不同 token 的 embedding,LN 保留了特征之间的时序关系。Transformer 基本都用 LayerNorm。 - - - -好处是不受 batch size 影响,batch=1 也能正常工作,训练和推理行为一致。 - -## nn.BatchNorm - - - -输入是 `[B, C, H, W]`(图像)或 `[B, C, L]`(序列) - -- B:Batch size - -- C:Channel,通道数 - -- H, W:图像的高度和宽度 - -- L:序列长度 - - - -BatchNorm 对一个 batch 的所有样本在每个通道上做归一化,也就是对每个通道计算所有样本的均值和方差。 - - - -对于图像数据: - -$$ - -\mu_c = \frac{1}{B \cdot H \cdot W} \sum_{b=1}^{B} \sum_{h=1}^{H} \sum_{w=1}^{W} x_{b,c,h,w} - -$$ - - - -$$ - -\sigma_c^2 = \frac{1}{B \cdot H \cdot W} \sum_{b=1}^{B} \sum_{h=1}^{H} \sum_{w=1}^{W} (x_{b,c,h,w} - \mu_c)^2 - -$$ - - - -$$ - -\hat{x}_{b,c,h,w} = \gamma_c \cdot \frac{x_{b,c,h,w} - \mu_c}{\sqrt{\sigma_c^2 + \epsilon}} + \beta_c - -$$ - - - -其中 $\gamma_c$ 和 $\beta_c$ 是每个通道的可学习参数 - - - -使用: - -```python - -batch_norm = nn.BatchNorm2d(num_features=64) # 64 个通道 - -x = torch.randn(32, 64, 28, 28) - -output = batch_norm(x) - -``` - - - -训练时用当前 batch 的均值和方差,同时更新移动平均统计量;推理时用训练时累积的移动平均统计量(running_mean 和 running_var)。 - - - -BN 抹平了不同特征(通道)之间的大小关系,保留了不同样本之间的相对关系。所以更适合 CV 任务,比如图像分类,不同样本之间的大小关系得以保留。CNN 的卷积层后面通常接 BatchNorm。 - - - -注意 batch size 太小(<8)时效果会变差,统计量不稳定。不适合序列长度变化大的 NLP 任务。 - - - -## Dropout - - - -训练时随机将张量中的元素按概率 `p` 设置为 0,防止过拟合。剩余的激活神经元会按 `1/(1-p)` 缩放,保持输出期望值不变。 - - - -$$ - -y = \begin{cases} - -0 & \text{with probability } p \\ - -\frac{x}{1-p} & \text{with probability } 1-p - -\end{cases} - -$$ - - - -原理是强制网络学习更鲁棒的特征,不依赖特定神经元的组合。每次训练相当于训练一个不同的子网络,推理时相当于多个模型的平均。 - - - -### nn.Dropout - - - -自动根据 `model.train()` / `model.eval()` 切换状态,推荐用这个: - - - -```python - -class MyModel(nn.Module): - -def __init__(self): - -super().__init__() - -self.dropout = nn.Dropout(p=0.5) - -self.fc = nn.Linear(512, 256) - - - -def forward(self, x): - -x = self.fc(x) - -x = self.dropout(x) # 训练时生效,推理时自动关闭 - -return x - -``` - - - -### nn.functional.dropout - - - -通过 `training` 参数手动控制: - - - -```python - -x = F.dropout(x, p=0.5, training=training) - -``` - - - -常用配置:全连接层 `p=0.5`,卷积层用 `nn.Dropout2d` 通常 `p=0.2~0.3`,Transformer 通常 `p=0.1`。dropout 率太高会欠拟合,太低防止过拟合效果不明显。 - -## nn.PixelShuffle - - - -PixelShuffle 是一种空间重排操作(Sub-Pixel Convolution),将图像的通道信息重新分布到空间维度上,实现上采样(放大图像)。 - - - -维度变换:`[B, C × r², H, W]` → `[B, C, H × r, W × r]`,其中 `r` 是上采样因子(upscale_factor) - - - -工作原理:假设 `r=2`,输入 `[B, 4C, H, W]`,会将 `4C` 个通道重组为 `C × 2 × 2` 的形式,每个 `2×2` 的通道块对应输出空间的一个 `2×2` 区域,最后输出 `[B, C, 2H, 2W]`。 - - - -示例(单通道,r=2): - -``` - -输入: [B, 4, H, W] (4个通道) - -┌─────┬─────┐ - -│ C0 │ C1 │ - -├─────┼─────┤ - -│ C2 │ C3 │ - -└─────┴─────┘ - - - -输出: [B, 1, 2H, 2W] (1个通道,空间扩大2倍) - -┌──┬──┐ - -│C0│C1│ - -├──┼──┤ - -│C2│C3│ - -└──┴──┘ - -``` - - - -使用: - -```python - -pixel_shuffle = nn.PixelShuffle(upscale_factor=2) # r=2 - -x = torch.randn(1, 64, 32, 32) # [1, 64, 32, 32] - -output = pixel_shuffle(x) # [1, 16, 64, 64] - -# 通道数: 64 / (2^2) = 16,空间尺寸: 32 * 2 = 64 - -``` - - - -常用于超分辨率(Super-Resolution)和图像生成(GAN、VAE)的上采样阶段,可以替代转置卷积避免棋盘效应。好处是纯重排操作不增加参数,计算也比双线性插值 + 卷积快。 - - - diff --git "a/source/bak/\346\234\215\345\212\241\345\231\250\344\273\243\347\220\206\347\233\270\345\205\263\351\227\256\351\242\230.md.bak" "b/source/bak/\346\234\215\345\212\241\345\231\250\344\273\243\347\220\206\347\233\270\345\205\263\351\227\256\351\242\230.md.bak" deleted file mode 100644 index 9c1a5bf..0000000 --- "a/source/bak/\346\234\215\345\212\241\345\231\250\344\273\243\347\220\206\347\233\270\345\205\263\351\227\256\351\242\230.md.bak" +++ /dev/null @@ -1,116 +0,0 @@ ---- -title: "服务器代理相关问题" -date: 2025-05-30 02:08:21 -updated: 2025-05-30 02:09:52 -tags: - - 网络 - - ssh - - 网络代理 -categories: 实用技巧 -comments: false ---- -# 服务器代理相关问题 - -## 转发内网网页到本地 - -在 非内网环境下访问内网网页比较麻烦,记录一下解决这种问题的方式 - -### 环境描述 - -一台内网中的linux服务器,使用 公网服务器 + frp 进行内网穿透 - -在内网环境下访问的 两种网页(目前为止遇到这两种): -1. 服务 直接开在这个服务器上 -2. 服务 没在该服务器上 - -### 第一种 服务 在服务器上 - -例如,一个 自建的 overleaf 网页服务开在 服务器的 8888 端口。 - -#### Step1 - -可以直接 在服务器中查看 这个 overleaf 开在哪个 ip 上 ,命令如下: -```shell -sudo lsof -i :8888 -``` -这里已经事先知道了服务开在了 10.10.5.4 这个内网 IP - -#### Step2 - -直接将本地某个端口的流量转发到 服务器 对应 ip:port 上: -```shell -ssh -L 8888:10.10.5.4:8888 user@公网服务器ip -p -``` - -然后就可以直接 通过 http://localhost:8888 来访问这个主机的服务了 - - -### 第二种 服务 不在服务器上 - -在内网中 需要访问的 url 例如: -- https://modeloverfit.com -- https://git.modeloverfit.com -经过测试发现子域名和根域名是同一个ip和端口443 - -想要访问这些内网环境下的page,使用下面方式(比较简单,还有一些nginx之类的方法): - -#### Step1 - -通过 ssh -D 动态转发?好像是,将内网服务器当做代理服务器,代理本地流量: -```shell -ssh -D 7899 user@ip -p port -``` -由于本地clash用了7890端口,⚠️注意不要冲突 - -#### Step2 - -到这里为止已经在本地打开了一代理端口7899,发送到7899端口的流量将会被 内网服务器代理,可以用下面 命令测试一下: -```shell -curl -x socks5://localhost:7899 -k https://git.modeloverfit.com/users/sign_in -``` -> 说明: -x 指定curl的代理 -k 用于 https 忽略证书安全之类的验证大概是 - -这时已经能正确获得改内网url的完整内容了,下面配合chrome内核浏览器进行设置代理规则 - -#### Step3 - -- 下载 [SwitchyOmega](https://chrome.google.com/webstore/detail/proxy-switchyomega/padekgcemlokbadohgkifijomclgjgif?hl=en-US)插件 - -在这个插件内: - -- 设置代理服务器 也就是 localhost 7899 -![[Pasted image 20250528193554.png]] - -- 设置对于modeloverfit及其子域名的代理规则: -![[Pasted image 20250528193708.png]] - -- 最后记得打开插件 使用上面配置的代理设置,然后就可以直接访问内网的url了 -![[Pasted image 20250528193913.png]] - -## 给服务器打开代理 - -环境: - 内网服务器 ICCT-T - 公网服务器 Z - 用 frp 把 Z 的 6000 转发到 T4 的 22 建立 ssh 穿透 - ICCT-T-P = Z -p 6789 - 本地 PC 有代理 localhost:7890 - -### Step1 - -打开本地的代理 Clash - -### Step2 - -把 远端 1080 端口的流量转到本地代理端口: -```shell -ssh -R 1080:localhost:7890 ICCT-T-P -``` - -### Step3 - -设置 服务器上 的代理端口 : -在 ~/.proxychains/proxychains.conf 中添加 -```conf -http 127.0.0.1 1080 -``` \ No newline at end of file diff --git "a/source/bak/\346\234\272\345\231\250\345\255\246\344\271\240\345\244\215\344\271\240\350\265\204\346\226\231.md.bak" "b/source/bak/\346\234\272\345\231\250\345\255\246\344\271\240\345\244\215\344\271\240\350\265\204\346\226\231.md.bak" deleted file mode 100644 index 77f6695..0000000 --- "a/source/bak/\346\234\272\345\231\250\345\255\246\344\271\240\345\244\215\344\271\240\350\265\204\346\226\231.md.bak" +++ /dev/null @@ -1,1135 +0,0 @@ ---- -title: "机器学习复习资料" -date: 2025-04-28 23:52:47 -updated: 2025-04-30 05:46:15 -mathjax: true -categories: 期末复习 -tags: - - 机器学习 - - 期末复习 - - 深度学习 -comments: false ---- -# 大纲复习资料 - -# 第一章 机器学习简介 - -## 机器学习的概念、要素、类型 - -### 概念 - -机器学习(Machine Learning,ML)是指从有限的观测数据中学习(或“猜测”)出具有一般性的规律,并将这些规律应用到未观测数据样本上的方法。主要研究内容是学习算法。 - -**机器学习**(英语:machine learning)是人工智能的一个分支。机器学习理论主要是设计和分析一些让计算机可以自动学习的算法。机器学习算法是一类从数据]中自动分析获得规律,并利用规律对未知数据进行预测的算法。因为学习算法中涉及了大量的统计学理论,机器学习与推断统计学联系尤为密切,也被称为**统计学习理论**。算法设计方面,机器学习理论关注可以实现的,行之有效的学习算法(要防止错误累积)。很多推论问题属于非程序化決策,所以部分的机器学习研究是开发容易处理的近似算法。(Wikipedia) - -计算机科学家汤姆·米切尔在其著作的Machine Learning一书中定义的机器学习为: well-posed learning problem - -> A computer program is said to learn from experience E with respect to some class of tasks T and performance measure P, if its performance at tasks in T, as measured by P, improves with experience E. -> -> ——Tom Mitchell,Machine Learning - -良好的学习问题 是 以性能量度P进行衡量,如果一个计算机程序在某类任务T上的性能,随着经验E而提升,那么我们称这个计算机程序能从经验E中学习。 - -### 要素 三个 - -- 模型(这里的线性 指的 是对参数 w ) - - 线性方法(指的是 w的线性函数): - $$ - f(x,\theta)=w^Tx+b - $$ - - 广义线性方法: - $$ - f(x,\theta)=w^T \phi (x) +b - $$ - 其中 $\phi (x)$ 是 可学习的 非线性基函数 (基函数用于对x进行特征变换) - - 非线性方法:例如神经网络 - $$ - f(x) = \sum_{j=1}^{m} v_j \cdot \sigma(\mathbf{w}_j^\top \mathbf{x} + b_j) + b - $$ - $\sigma(\cdot)$ 是激活函数(如 ReLU、Sigmoid)这个模型的 非线性来自激活函数和多层结构。 - -- 学习准则 (学习目标 优化目标) - - 期望风险:这个是最理论化的目标 指的是 损失值在真实数据分布下的期望,但是由于 数据的真实分布 $P(x,y)$ 是不可知的,所以通常难以计算 - $$ - \cal{R}(f)= \mathbb{E}_{(x,y)\sim P(x,y)} \left[ L(f(x), y) \right] - $$ - - 经验风险:根据大数定律 当数据量足够大时 可以用频率当做分布 所以有经验风险 - $$ - \cal{R}_{emp}(f) = \frac{1}{n} \sum_{i=1}^{n} L(f(x_i), y_i) - $$ - - ***结构***风险最小化:在结构风险基础上加上正则化项 防止模型过拟合 控制模型的复杂程度 - $$ - \cal{R}_{srm}(f) = R_{emp}(f) + \lambda \cdot \Omega(f) - $$ - 其中 $\lambda$ 是控制正则损失系数 - - - 最大间隔准则: 在SVM支持向量机中的学习准则 - $$ - \min \frac{1}{2} ||w||^2 \quad \text{s.t. } y_i(w^\top x_i + b) \ge 1 - $$ - 间接最小化结构,优先选更稳健的模型 - - - 最大似然估计(MLE):最大似然 - $$ - \max_\theta \prod_{i=1}^n P(y_i|x_i;\theta) \quad \text{或} \quad \max_\theta \sum_{i=1}^n \log P(y_i|x_i;\theta) - $$ - - 贝叶斯后验最大化(MAP): - $$ - \max_\theta P(\theta|D) = \max_\theta P(D|\theta) P(\theta) - $$ - 加了先验的 MLE 可以当做正则化的MLE - -- 优化 - - 梯度下降方法 - - |方法名|特点|备注| - |---|---|---| - |**标准梯度下降(GD)**|每次用全量样本更新|计算量大,收敛稳定| - |**随机梯度下降(SGD)**|每次只用一个样本|噪声大但效率高| - |**小批量梯度下降(Mini-batch GD)**|综合GD和SGD优点|深度学习常用| - |**Momentum(动量法)**|加速收敛,抗震荡|模拟物理惯性| - |**Nesterov Accelerated Gradient(NAG)**|改进动量法|预判未来梯度方向| - |**Adagrad**|自适应每个参数学习率|学习率随训练减小,可能太快收敛| - |**RMSProp**|改进Adagrad,控制过快下降|常用于RNN| - |**Adam**|融合Momentum和RMSProp|最常用,鲁棒性好| - |**AdamW**|改进了Adam的权重衰减策略|Transformer常用| - - - 二阶优化方法 - - |方法名|特点|备注| - |---|---|---| - |**牛顿法(Newton’s Method)**|用二阶导数加快收敛|每次迭代代价高| - |**拟牛顿法(如BFGS、L-BFGS)**|近似二阶信息|在小数据场景表现好| - |**自然梯度法(Natural Gradient)**|考虑参数空间结构|常用于贝叶斯/强化学习| - - - 无梯度优化 - - |方法名|特点|适用场景| - |---|---|---| - |**网格搜索 / 随机搜索**|超参数调优经典方法|参数少时可用| - |**进化算法(如遗传算法GA)**|模拟自然选择过程|黑盒优化| - |**粒子群优化(PSO)**|模拟群体智能|连续优化有效| - |**模拟退火(SA)**|模拟物理退火过程|全局优化倾向强| - |**贝叶斯优化(BO)**|利用代理模型迭代搜索|少样本高效搜索| - -## 过拟合、欠拟合、正则化 - -### 过拟合 - -过拟合(Over-fitting)是指 模型在训练集上面误差很低,但是在测试机上误差很高。对训练集拟合的能力过强。 -![[Pasted image 20250425170110.png]] - -过拟合的解决方法: -- 扩大数据集 -- 正则化 (奥卡姆剃刀原理 简单有效) - 如果没有必要就不要增大模型的数量 -- 通过 验证集合 来选择模型 - K折交叉验证:训练K次判断模型能力 - 留一法 K=N -### 欠拟合 - -模型在训练集和测试集上的误差都很大。由于模型能力不足(不够灵活) -![[Pasted image 20250425170633.png]] - -### 正则化 - -对模型的损失中 添加正则化损失 例如L1、L2 -奥卡姆定律 简单有效 -![[Pasted image 20250425171150.png]] - -## 贝叶斯公式 - -后验 似然x先验 -$$ -p(Y|X)=\cfrac{P(X|Y)P(Y)}{P(X)} -$$ - -# 第二章 概率论与优化 - -## 概率的概念、贝叶斯学派vs频率轮学派 - -### **概率的概念** - -概率是对**不确定事件发生可能性**的度量。在数学上,概率是定义在样本空间上的一个函数,取值范围是 \[0,1] 。 - -但不同学派对“概率的意义”有不同理解,主要有: -### **频率学派(Frequentist)** - -#### 观点: - -概率是一个事件在**大量重复试验中出现的相对频率**。 -通过大量独立实验将概率解释为事件发生频率的均值(大数定律)。 - -> 举个例子:如果你连续掷硬币很多次,有一半是正面,那我们就说正面概率是 0.5。 - -#### 特点: - -- 概率是客观的,跟你是否知道某个信息无关。 -- 参数是**固定未知数**,不能被建模成随机变量。 -- 推断方法:最大似然估计(MLE)、假设检验、置信区间等。 - -### **贝叶斯学派(Bayesian)** - -#### 观点: - -概率是表示一个人对事件发生的**主观相信程度**,是**对不确定性的度量**。 - -将概率解释为信念度(degree ofbelief)。当考虑的试验次数非常少的时候,贝叶斯方法的解释非常有用。此外,贝叶斯理论将我们对于随机过程的先验知识纳入考虑,当我们获得新数据的时候,这个先验的概率分布就会被更新到后验分布中。 - -> 举个例子:如果你知道这枚硬币偏向正面,你就可以给出“正面概率0.8”,即使你还没开始掷。 - -#### 特点: - -- 概率可以用于表示对未知参数的信念。 -- 参数也可以是**随机变量**。 -- 推断方法:利用贝叶斯公式更新先验分布得到后验分布。 - -$$ -p(\theta | x) = \frac{p(x | \theta) \cdot p(\theta)}{p(x)} -$$ - ---- - -### 两者对比总结: - -| 内容 | 频率学派 | 贝叶斯学派 | -| ---- | -------- | ------------- | -| 概率定义 | 事件长时间频率 | 主观信念 | -| 参数看法 | 固定未知值 | 随机变量 | -| 推断结果 | 单个估计值 | 概率分布 | -| 常用方法 | MLE、假设检验 | 贝叶斯公式、先验-后验推断 | - -## 伯努利分布、高斯分布 - -### 伯努利分布 Bernouli - -就是 单次的 二项分布 : -$$ -Bern(x|\mu)=\mu^x(1-\mu)^{1-x} \ \ \ ,\ x\in\{0,1\} -$$ -期望 $\mathbb{E}[x]=\mu$ , 方差 $var[x]=\mu(1-\mu)$ - -二项分布: -$$ -Bin(m|N,\mu) = C^m_N \mu^m (1-\mu)^{N-m} -$$ -期望 $\mathbb{E}[x]=N\mu$ , 方差 $var[x]=N\mu(1-\mu)$ - -共轭性:![[Pasted image 20250426030533.png]] - -### 高斯分布 - -就是正态分布: -$$ -{N}(x|\mu,\sigma^2)=\cfrac{1}{(2\pi\sigma^2)^{1/2}}exp\{-\frac{1}{2\sigma^2}(x-\mu)^2\} -$$ -期望 $\mathbb{E}[x]=\mu$ , 方差 $var[x]=\sigma^2$ - -## 极大似然估计、最大后验估计 - -极大似然估计 和 最大后验估计 都是 机器学习三要素中的 学习准则 - -### 极大似然估计(Maximum Likelihood Estimate)MLE - -选择让 观测到数据出现的概率最大的 一组参数 , 下面这个P就是似然函数 -$$ -\hat{\theta} = \arg\max_\theta P(\cal{D}|\theta) -$$ -### 最大后验估计 (Maximum A Posteriori estimate)MAP - -在给定 先验参数分布 和 数据 $\cal{D}$ 的情况下 最有可能的 参数 $\theta$ -$$ -\hat{\theta} = \arg\max_\theta P(\theta|\cal{D})=\arg\max_\theta \cfrac{P(\cal{D}|\theta)P(\theta)}{P(\cal{D})} -$$ - - -## 梯度下降以及不同形式 - -Goal: -$$ -\min_x f(x) -$$ -Just iterate -$$ -x_{t+1}=x_t-\eta_t\nabla f(x_t) -$$ -where $\eta_t$ is learning rate - -![[Pasted image 20250426031557.png]] - -| 方法名 | 特点 | 备注 | -| -------------------------------------- | ------------------ | ---------------------- | -| **标准梯度下降(GD)** | 每次用全量样本更新 | 计算量大,收敛稳定。也叫批量梯度下降法BGD | -| **随机梯度下降(SGD)** | 每次只用一个样本 | 噪声大但效率高 | -| **小批量梯度下降(Mini-batch GD)** | 综合GD和SGD优点 | 深度学习常用 | -| **Momentum(动量法)** | 加速收敛,抗震荡 | 模拟物理惯性 | -| **Nesterov Accelerated Gradient(NAG)** | 改进动量法 | 预判未来梯度方向 | -| **Adagrad** | 自适应每个参数学习率 | 学习率随训练减小,可能太快收敛 | -| **RMSProp** | 改进Adagrad,控制过快下降 | 常用于RNN | -| **Adam** | 融合Momentum和RMSProp | 最常用,鲁棒性好 | -| **AdamW** | 改进了Adam的权重衰减策略 | Transformer常用 | -![[Pasted image 20250426032114.png]] -![[Pasted image 20250426032336.png]] -![[Pasted image 20250426032422.png]] - -# 第三章 回归的线性模型 - -## 介绍 - -> 什么是回归? 回归指的是 让模型 预测连续的数值型输出(即实数范围内的值) - -> 什么是线性模型? 线性模型指的是 模型 是 参数w 的线性函数 一般形式为 - $$ - f(x)=w_1x_1+w_2x_2+...+w_dx_d+b - $$ - -向量形式为 -$$ -f(x)=w^Tx +b -$$ - -其中 $\vec{x} =(x_1,x_2....x_d)$ 是输入的特征 线性模型是w的线性模型 x 如果经过基函数变换为 $\phi(x)$ 仍是线性模型。 -线性模型的优点:简单易建模 可解释性强 非线性模型(引入层级结构或者高位映射)的基础 。 - ----- -线性回归是什么? -给定数据集 $D=\{(x_1,y_1),(x_2,y_2),...,(x_m,y_m)\}$,其中 $x_i=(x_{i1};x_{i2};...;x_{id}),y_i\in\mathbb{R}$ -线性回归的目的 学到一个线性模型 尽可能 准确地 预测 实值输出 - -## 线性回归最小二乘法 - -建立线性模型 为了方便 表达 把 b 和 1 增广入 w x 中 -![[Pasted image 20250426155115.png]] -![[Pasted image 20250426155201.png]] -![[Pasted image 20250426155225.png]] -![[Pasted image 20250426155301.png]] - -### 多元线性回归 -![[Pasted image 20250426155423.png]] -![[Pasted image 20250426155429.png]] -![[Pasted image 20250426155434.png]] - -### 基函数 - -![[Pasted image 20250426155455.png]] -![[Pasted image 20250426155505.png]] -基函数 可以选择 -- 多项式基函数\[$\phi_j(x)=x^j$\] -- 高斯基函数 \[$\phi_j(x)=exp\{-\frac{(x-\mu_j)^2}{2s^2}\}$\] -- sigmoid 基函数\[$\phi_j(x)=\sigma(\frac{x-\mu_j}{s}) ,where\ \ \sigma(a)=\frac{1}{1+exp(-a)}$\] - -## 岭回归 Ridge Regression 山脊回归 - -岭回归就是在原本的loss上加上了 权重产生的 权重衰减正则化项 -![[Pasted image 20250426160411.png]] -![[Pasted image 20250426160417.png]] - -最大化后验概率 等价于 最小化正则化的平方和误差函数 -![[Pasted image 20250426160617.png]] - -闭式解与几何解释 -![[Pasted image 20250426160732.png]] - -## Lasso 回归 套索回归 -L1 loss -![[Pasted image 20250426160802.png]] - -Lasso回归能够使模型 具有稀疏性 -![[Pasted image 20250426160911.png]] - - -# 第四章 基础的分类方法 - -回归是多个输入特征被映射到一个或者多个连续的目标变量 -分类则是 输入特征x被映射到K个离散类别之一$C_k$ -## 线性分类概念 - -线性分类,模型指的是 分类面 是 x 的 线性 函数 (分类函数 f 可能是非线性函数 例如在外面加了一层 sigmoid) -区别于前面的线性回归(对于参数是线性的) - -推广的线性模型 用非线性函数对w的线性函数的结果进行激活![[Pasted image 20250426165956.png]] -![[Pasted image 20250426162156.png]] -![[Pasted image 20250426162206.png]] - -下面说的三种 感知机 logistic回归 朴素贝叶斯分类器 -感知机和logistic回归两种是严格线性的 决策面是超平面 -朴素贝叶斯分类器 通常是非线性 其决策边界取决于特征的条件概率分布(如高斯朴素贝叶斯的决策边界是二次曲面) -若特征的条件概率分布属于***指数族(如泊松分布)***,且满足特定条件时,可能得到线性决策边界。 -## 三种类型:判别函数、概率生成模型、概率判别模型 - -### 判别函数 Discriminant Function (感知器) - ->这个是直接说出结果是哪个 下面的会给出概率 然后再决策是哪个 - -直接找到一个函数 $f(x)$ 把每个输入 x 直接映射为 类别标签,例如 感知机 SVM 之类的 - -线性判别函数就是指 决策面 是 超 平面 的判别函数 - -#### 二分类情形 -线性判别函数 y=0 就是 决策平面 -![[Pasted image 20250426170353.png]] -几何性质 【2|1】![[Pasted image 20250426170523.png]] - -如过用一个二分类器 处理多分类的情况,可以: -- 一对其他 分类器(K-1个二分类器) -- 一对一 分类器 ($C^2_k$ 个二分类器) -但是会出现不可分的区域![[Pasted image 20250426170742.png]] -#### 多分类情形 -K类判别函数 $y_k(x)=w_k^Tx+w_{k0}$ 如果一个k=k\*大于 其他所有的 y 那就是Ck\* 类 -![[Pasted image 20250426171017.png]] - -### 概率判别模型 Probabilistic Discriminative Models (logistic回归\*) - -直接对 后验概率 $p(C_k|x)$ 建模,然后用决策论来确定x的输入类别 比如取最高的等等。例如 逻辑(logistic)回归、神经网络(最后softmax的结果就是一组后验概率) -#### Logistic回归 (分类模型) - -在线性模型的基础上 加上了激活函数 得到概率大小 下面的例子用的是sigmoid函数 -![[Pasted image 20250426171443.png]] -![[Pasted image 20250426171825.png]] -直接对y=1 对线性函数激活得到概率 -![[Pasted image 20250426171929.png]] -决策面就是两个类别的后验概率相等 也就是 $p(C_1|x)=p(C_2|x)$ 可以根据这个推出逻辑回归模型的决策面是线性的![[Pasted image 20250426172010.png]] -sigmoid:![[Pasted image 20250426171645.png]] -##### 逻辑回归模型的训练 - -![[Pasted image 20250426172506.png]] -![[Pasted image 20250426172544.png]] -![[Pasted image 20250426172553.png]] -![[Pasted image 20250426172652.png]] -逻辑回归就是在线性回归模型的基础上套了一个sigmoid LOSS就是两个类别的的交叉熵![[Pasted image 20250426172717.png]] -##### 逻辑回归和神经网络模型的对比 - -| **特性** | **逻辑回归** | **神经网络** | -| --------- | ---------------------------- | ----------------------------- | -| **模型结构** | 单层(仅输入层 + 输出层) | 多层(输入层 + 隐藏层 + 输出层) | -| **决策边界** | 线性(超平面) | 非线性(可拟合复杂边界) | -| **表达能力** | 只能解决线性可分问题 | 可逼近任意复杂函数(通用近似定理) | -| **参数优化** | 使用梯度下降法优化权重 \( w \) | 使用反向传播(Backpropagation)优化多层权重 | -| **输出类型** | 概率(通过Sigmoid函数) | 概率(Softmax)或任意值(取决于输出层设计) | -| **适用任务** | 二分类(默认)、可扩展至多分类(One-vs-Rest) | 二分类、多分类、回归、生成任务等 | -| **计算复杂度** | 低(参数少,训练快) | 高(参数多,需大量数据和计算资源) | -| **可解释性** | 高(权重直接反映特征重要性) | 低(黑箱模型,难以解释隐藏层) | - -### 概率生成模型 Probabilistic Generative Models (朴素贝叶斯分类器\*) - -先对 类的条件密度 $p(x|C_k)$ 和 先验类概率分布 $p(C_k)$ 建模,然后再用 贝叶斯定理 计算 后验类概率分布 $p(C_k|x)$ : -$$ -p(C_k|x)=\cfrac{p(x|C_k)p(C_k)}{p(x)} -$$ -最后,使用决策论来确定每个输入x的类别 - -等价地,直接对 联合概率分布 $p(x,C_k)$ 建模 , 再做归一化得到后验概率。 - -统计类别分布 得到先验 在类内 统计属性分布 得到似然 -#### 朴素贝叶斯分类器 - -估计后验概率的主要困难在于:类条件概率是 x 在所有属性上的联合概率(输入的x有很多维度), 难以用有先限的训练样本估计获得 - -朴素贝叶斯分类器 采用 ***属性条件独立性假设*** , 每个属性独立地对分类结果发生影响。 - -更具上述假设 后验概率可以写为(d是属性个数 i是第i个属性): -$$ -P(c|x)=\cfrac{P(c)P(x|c)}{P(x)}=\cfrac{P(c)}{P(x)}\prod_{i=1}^dP(x_i|c) -$$ -对于所有类别 P(x) 相同 , 所以***贝叶斯判定准则***为: -$$ -h_{nb}(x)=\underset{c\in y}{\arg\max} \, P(c)\prod^d_{i=1}P(x_i|c) -$$ -即对每个样本x 选择后验概率最大的类 - -##### 朴素贝叶斯分类器计算例子 - -![[Pasted image 20250426175412.png]] -![[Pasted image 20250426175419.png]] -![[Pasted image 20250426175424.png]] - -# 第五章 支持向量机 与 核方法 - -## SVM原理 - -支持向量机的三个关键想法: -- 通过优化来求解一个超平面分类器 -- 寻找最大间隔分类器来提高模型的泛化能力(结构化风险最小) -- 采用 **核技巧** 使得在高维度特征空间的计算更有效率 - -假设数据是线性可分的,那么一定存在两条平行的直线分别经过两类样本最外侧的数据 设这两条直线是 $wx+b=1$ 、$wx+b=-1$ 设置是 ±1 没有什么特殊意义 主要是为了方便表示间距margin - -在这个设置下 相当于学习一个基于间隔的分类器 -![[Pasted image 20250426204559.png]] -> 为什么要写成 $\frac{1}{2}||w||^2$ ? - 主要是为了简化下面的计算,如果写成 $||w||$ 在计算梯度时候会出现 $\sqrt{w^Tw}$ - -### 对偶问题 - -在解决这个问题时候 使用 拉格朗日乘子法 将其转化为对偶问题: -1. 引入拉格朗日乘子 $\alpha_i \ge 0$ 得到拉格朗日函数 -$$ -L(w,b,\alpha)=\frac{1}{2}||w||^2-\sum^m_{i=1}\alpha_i(y_i(w^Tx_i+b)-1) -$$ - -2. 令 $L(w,b,\alpha)$ 对 $w$ 和 $b$ 的偏导数为0可得: -$$ -w=\sum^m_{i=1}\alpha_iy_ix_i\ \ ,\ \sum^m_{i=1}\alpha_iy_i=0 -$$ - -3. 回代: ![[Pasted image 20250427134510.png]] - - -> 拉格朗日乘子法:通过乘子将约束引入函数得到一个无约束的函数 - ![[Pasted image 20250426210330.png]] - - -支持向量就是 wx+b=±1 的向量 ![[Pasted image 20250427135009.png]] - -## 核技巧 - -一般的 上面所提到的支持向量机是对于 数据线性可分 的情况 , 但是可能存在线性不可分的数据,这样就要把 x 变换到高维空间 使其在高维空间中线性可分。 - -设样本 x 映射后的向量为 $\phi(x)$ ,划分超平面是 $f(x)=w^T\phi(x)+b$ -![[Pasted image 20250427135943.png]] - -也就是说 两个x的高维表示 都是内积的形式存在 不用显式的计算出来 所以引入 ***核函数*** 来代替: -$$ -\cal{K}(x_i,x_j)=\phi(x_i)^T\phi(x_j) -$$ - -核函数的优点:不需要知道和计算 $\phi(x)$ (纬度很高 计算量很大) 即使特征空间的纬度非常高,计算仍是可行的,因为 核函数 是在原空间内运算。 - -> 存在判定![[Pasted image 20250427143630.png]] - -> 常用核函数 ![[Pasted image 20250427143846.png]] - -> 实现非线性分类![[Pasted image 20250427144027.png]] - 通过这个解出 $\alpha^*$ 后 可以得到 决策函数 $f(x)=sgn(\sum^l_{i=1}y_i\alpha^*K(x_i,x)+b^*)$ - ![[Pasted image 20250427144249.png]] - - -# 第六章 集成学习 - -## 集成学习的概念和类型 - -集成学习 是通过 构建并结合多个学习器来完成学习任务,提升性能![[Pasted image 20250427150521.png]] - -学习器可以是 同质的 也可以是 异质的 (个体学习器由不同的学习算法生成 成为 组件学习期) -其中的同质 个体学习器 称为 基学习器 对应的算法叫做 基学习算法 - -弱学习器:精度略高于随机 强学习器:精度较高 - -![[Pasted image 20250427151810.png]]![[Pasted image 20250427151819.png]] - -> 简单分析 - ![[Pasted image 20250427150618.png]] - ![[Pasted image 20250427150622.png]] - ![[Pasted image 20250427150640.png]] - 然而我们必须注意到,上面的分析有一个关键假设:基学习器的误差相互 独立.在现实任务中,个体学习器是为解决同一个问题训练出来的,它们显然不 可能相互独立!事实上,个体学习器的“准确性”和“多样性”本身就存在冲 突.一般的,准确性很高之后,要增加多样性就需牺牲准确性,事实上,如何产 生并结合“好而不同”的个体学习器,恰是集成学习研究的核心. - -根据个体学习器的生成方式,目前的集成学习方法大致可分为两大类,即 个体学习器间存在强依赖关系、必须串行生成的序列化方法,以及个体学习器 间不存在强依赖关系、可同时生成的并行化方法;前者的代表是Boosting,后 者的代表是 Bagging 和“随机森林”(Random Forest). -## AdaBoost算法流程 - -![[Pasted image 20250427151909.png]]![[Pasted image 20250427151924.png]] - -![[Pasted image 20250427151700.png]] -![[Pasted image 20250427153537.png]] - -![[Pasted image 20250427153627.png]] -![[Pasted image 20250427153635.png]] -![[Pasted image 20250427153643.png]] -![[Pasted image 20250427153648.png]] -这个 $Z_m$ 其实就是对 $w_{mi}e^{\alpha_m}$ 做softmax 让下次的模型更注重错误的样本 -![[Pasted image 20250427154804.png]] - -### 例子 - -![[Pasted image 20250427153858.png]] -![[Pasted image 20250427153906.png]] -![[Pasted image 20250427153933.png]] -![[Pasted image 20250427153939.png]] -![[Pasted image 20250427154142.png]] - -## Bagging算法流程 - -![[Pasted image 20250427155504.png]] -欲得到泛化性能强的集成,集成中的个体学习器应尽可能相 互独立;虽然“独立”在现实任务中无法做到,但可以设法使基学习器尽可能 具有较大的差异.给定一个训练数据集,一种可能的做法是对训练样本进行采 样,产生出若干个不同的子集,再从每个数据子集中训练出一个基学习器.这 样,由于训练数据不同,我们获得的基学习器可望具有比较大的差异.然而,为 获得好的集成,我们同时还希望个体学习器不能太差.如果采样出的每个子集 都完全不同,则每个基学习器只用到了一小部分训练数据,甚至不足以进行有 效学习,这显然无法确保产生出比较好的基学习器.为解决这个问题,我们可考 虑使用相互有交叠的采样子集. - -![[Pasted image 20250427155736.png]] -大I 是指数函数 在里面为真假时取1,0 -对预测输出进行结合时,通常对分类任务使用简单投票法,对于回归问题使用简单平均法。 -![[Pasted image 20250427160408.png]] - - ->获取每个基学习器的训练集方法![[Pasted image 20250427160230.png]] - 包外估计 用 训练集 不包括x的对x做预测![[Pasted image 20250427160456.png]] - - -# 第七章 聚类方法 - -> 无监督学习 - 是机器学习的一种范式,其核心特点是模型从**未标注的数据**中自动发现隐藏的模式、结构或规律,而无需人工提供标签(即没有明确的“正确答案”作为监督信号)。与监督学习不同,无监督学习的目标是探索数据本身的内在特性。 - - 模型: 函数 $z=g_\theta(x)$ , 条件概率分布 $P_\theta(z|x)$ 或者条件概率分布 $P_\theta(x|z)$ - - 策略:目标函数优化 - - 算法: 迭代算法,通过迭代达到对目标函数的最优化 - -> 聚类 - 是将样本集合中相似的样本(实例)分配到相同的类,不相似的样本分配到不同的类。 聚类时,样本通常是欧氏空间中的向量,类别不是事先给定,而 是从数据中自动发现,但类别的个数通常是事先给定的。样本之间的相似度或距离由应用决定。 ·如果一个样本只能属于一个类,则称为硬聚类(hard clustering),如果一个样本可以属于多个类,则称软聚类(soft clustering)。 - 聚类算法有 原型(k均值、高斯混合模型)、密度、层次、谱聚类 - -## K均值算法流程以及特点 - -K均值聚类是基于样本集合划分的聚类算法。将样本集合划分为k个子集,构成k个类,将n个样本分到k个类别中,每个样本到其所属类的中心距离最小。硬聚类算法,每个样本只有一个类。 -![[Pasted image 20250427165900.png]] -![[Pasted image 20250427170505.png]] -### \_K均值划分策略 - -![[Pasted image 20250427170031.png]] -![[Pasted image 20250427170044.png]] -![[Pasted image 20250427170137.png]] - - - - -### K均值算法实现 - -![[Pasted image 20250427170239.png]] -![[Pasted image 20250427170247.png]] - - - - -### K均值算法特点 - -![[Pasted image 20250427170404.png]] -![[Pasted image 20250427170411.png]] -![[Pasted image 20250427170436.png]] -![[Pasted image 20250427170554.png]] - - - -# 第八章 混合模型与期望最大 - -## EM算法流程、收敛性以及应用 - -> DEF: - 最大期望算法(Expectation-maximization algorithm,又译为期望最大化算法),是在概率模型中寻找参数最大似然估计或者最大后验估计的算法,其中概率模型依赖于无法观测的隐性变量。最大期望算法经过两个步骤交替进行计算,==**第一步**==是计算期望(E),利用对隐藏变量的现有估计值,计算其最大似然估计值;==**第二步**==是最大化(M),最大化在E步上求得的最大似然值来计算参数的值。M步上找到的参数估计值被用于下一个E步计算中,这个过程不断交替进行。 - ->举个例子: - 如果要统计一个学校男女生身高体重的分布,男生的分布和女生肯定是两个不同的分布,但是如果数据中只有身高体重Y 并未标注 男生女生Z 那么如果要找到男生女生的分布就 比较困难, EM算法就是为了解决这种问题 - -> 理解: - 通过 估计的 隐变量 的先验 计算 所有观测数据Y 的 似然概率 然后用 最大似然 得到 每条Y 最有可能对应的 Z 的值,然后 根据估计过后数据 更新 Z 的估计先验 重复迭代这个过程 (实际上这个Z不是一个常量 而是一个随机变量)![[Pasted image 20250427173617.png]] - -### EM算法流程 - -![[Pasted image 20250427174902.png]] -![[Pasted image 20250427174911.png]] - - -### \_EM特点 - -![[Pasted image 20250427174444.png]] - -### EM算法的收敛性 - -![[Pasted image 20250427175204.png]] -![[Pasted image 20250427175209.png]] -![[Pasted image 20250427175215.png]] -![[Pasted image 20250427175312.png]] -![[Pasted image 20250427175359.png]] - - - - -### EM算法的应用 - -- EM算法在⾼斯混合模型学习 -- EM在伯努利混合模型上的应用 - -## 混合高斯模型GMM的原理和特点 - -![[Pasted image 20250427180153.png]] -![[Pasted image 20250427180223.png]] -![[Pasted image 20250427180236.png]] -![[Pasted image 20250427180243.png]] -![[Pasted image 20250427180250.png]] -![[Pasted image 20250427180259.png]] -![[Pasted image 20250427180306.png]] - -> PPT: - ![[Pasted image 20250427191301.png]] - ![[Pasted image 20250427191307.png]] - ![[Pasted image 20250427191343.png]] - ![[Pasted image 20250427191349.png]] - 具体内容找PPT - -# 第九章 神经网络与深度学习 - -## 介绍 - -> 神经网络的要素: - ![[Pasted image 20250427191930.png]] - -> 网络结构: - ![[Pasted image 20250427193810.png]] - -## 前馈神经网络、梯度下降算法、BP反向传播算法流程 - -### 前馈神经网络(全连接神经网络,MLP) - -整个网络中没有反馈 全部是正向连接 -![[Pasted image 20250427193836.png]] -![[Pasted image 20250427203930.png]] -![[Pasted image 20250427204015.png]] -全连接神经网络的缺点 -![[Pasted image 20250428164432.png]] -![[Pasted image 20250428164436.png]] - - -### BP反向传播算法 - -对每个权重求梯度 - -> 构造一个两层的Net - ![[Pasted image 20250427211113.png]]![[Pasted image 20250427211120.png]]![[Pasted image 20250427211125.png]]![[Pasted image 20250427211305.png]]![[Pasted image 20250427211142.png]]![[Pasted image 20250427211244.png]] -#### BP算法原理 -> 前向过程![[Pasted image 20250427211113.png]]![[Pasted image 20250427211142.png]] - -在上面正向传播过程中,loss对wij的梯度为: -![[Pasted image 20250427211353.png]] -![[Pasted image 20250427212939.png]] -也就是 E 对一个权重 第J层第I个w 的梯度: -$$ -\cfrac{\partial E_n}{\partial w_{ji}}=\cfrac{\partial E_n}{\partial a_{j}}\cfrac{\partial a_j}{\partial w_{ji}} -$$ - -???为什么不是这种形式??如果relu的斜率是1 上面下面基本相等了 -$$ -\cfrac{\partial E_n}{\partial w_{ji}}=\cfrac{\partial E_n}{\partial z_{j}}\cfrac{\partial z_j}{\partial a_j}\cfrac{\partial a_j}{\partial w_{ji}} -$$ - - -| 符号 | 意义 | 举例 | -| --- | -------- | ----------- | -| a | 还没激活的加权和 | $w×x+b$ | -| z | 激活函数后的输出 | $\sigma(z)$ | - -![[Pasted image 20250427213023.png]] -![[Pasted image 20250427213046.png]] -![[Pasted image 20250427213052.png]] -![[Pasted image 20250427213058.png]] - -#### BP算法示例 - -![[Pasted image 20250427213347.png]] -![[Pasted image 20250427213352.png]] -![[Pasted image 20250427213501.png]] -![[Pasted image 20250427213547.png]] -![[Pasted image 20250427213551.png]] -![[Pasted image 20250427213600.png]] - - - -### 梯度下降算法 - -李航 附录A 常见的梯度下降算法在 第一章 三要素中 - -## CNN 基本结构,卷积、池化的计算、常用的激活函数 - -### CNN基本结构 - -上面提到了全链接前馈神经网络的缺点:参数多、局部不变性特征难以提取、展开丢失空间信息、参数多容易过拟合 - -所以提出了卷积神经网络 CNN 也是一种 前馈神经网络 受生物学上的 感受野机制启发。在结构上有三个**特点**: **局部感知、权重共享、空间或者时间上的下采样** - -> 在视觉神经系统中 一个神经元的感受野 是 指视网膜上的特定区域,只有在这个区域内的刺激才能激活改神经元。 - -全连接层,导致的参数量过大,浪费资源而且没有这么多的训练数据 -局部连接层,在位置相同的地方数据类似(人脸识别)参数量小一些 -**卷积层**,不同位置共享权重,通过可学习的卷积核进行卷积 - -![[Pasted image 20250428172619.png]] -这个里面的汇聚层是 池化 - -![[Pasted image 20250428172637.png]] - - -### 卷积操作 Convolution - -![[Pasted image 20250428170259.png]] -![[Pasted image 20250428170324.png]] -padding就是在外面对图像进行补0操作 stride就是kernel移动的步长是多少。 -![[Pasted image 20250428170446.png]] - -一个卷积核卷出来一层 有多少个卷积核 卷完后通道就是多少 每个卷积核的通道数应该与输入特征图的通道数对齐 -#### 卷积后feature map的size - -对于给定的输入尺寸 $size:[H,W]$,填充 $padding \in \{true, false,Valid,Same\}$,步长 $stride = s$,卷积核大小为 $(k, k)$,输出尺寸 $size_O$可以通过以下公式计算: - -##### 1. 没有填充的情况 (padding = false): - -$size_O = \left[ \left\lfloor \cfrac{H - k}{s} + 1 \right\rfloor, \left\lfloor \cfrac{W - k}{s} + 1 \right\rfloor \right]$ - -##### 2. 有填充的情况 (padding = true): - -对于填充,通常我们使用"same"填充来保证输出尺寸与输入尺寸相同。在这种情况下,填充的大小为: - -$padding_H = \left\lceil \cfrac{k - 1}{2} \right\rceil, \quad padding_W = \left\lceil \cfrac{k - 1}{2} \right\rceil$ - -然后输出尺寸为: - -$size\_O = \left[ \left\lfloor \cfrac{H + 2 \times padding_H - k}{s} + 1 \right\rfloor, \left\lfloor \cfrac{W + 2 \times padding_W - k}{s} + 1 \right\rfloor \right]$ - -这就是计算输出尺寸 $size_O$ 的方法。 - - -### 池化 Pooling Layer - -``` -如果我们想让“眼睛检测器”这个滤波器(例如卷积神经网络中的卷积核)对于眼睛的**精确位置**不那么敏感,可以通过 **池化pooling** 操作来提高检测的鲁棒性。 - -1. **眼睛检测器(filter)**:我们假设滤波器的目的是检测图像中的“眼睛”特征。当滤波器在图像中滑动时,它会响应图像中眼睛特征的位置并给出一定的响应值。 - -2. **精确位置问题**:眼睛可能出现在图像的不同位置,或者可能由于某些因素(如图像旋转、缩放等)导致眼睛的相对位置发生变化。如果我们直接使用卷积操作,滤波器的响应可能只在特定位置对眼睛有效,但对于其他位置就不适用了,这样就无法达到“位置不敏感”的效果。 - -3. **池化(Pooling)操作**:池化操作(例如**最大池化(Max Pooling)**)可以帮助解决这个问题。它的工作原理是:在特定区域(如 \(2 \times 2\) 区域)内,池化操作选择最大的响应值(如最大池化),而不是关心具体的精确位置。通过这种方式,无论眼睛在图像的哪个位置,池化层都会保留局部区域内的最强响应,从而使模型对眼睛的**空间位置变化**更加**鲁棒**。 - -假设我们用一个 \(3 \times 3\) 的滤波器来检测眼睛,滤波器响应在图像某个位置给出了一个高值,但如果眼睛稍微移动,响应值可能会变得很低。如果我们在多个位置应用池化操作,池化层会选取该区域中的最大响应值。这样,即使眼睛的位置发生变化,池化后的响应值仍然较为稳定。 -``` - -![[Pasted image 20250428172157.png]] -不在关注具体在哪个像素 而是知道在哪个大概区域 - -![[Pasted image 20250428172317.png]] -![[Pasted image 20250428172443.png]] - - -### 常用的激活函数 - -![[Pasted image 20250428173021.png]] -![[Pasted image 20250428173026.png]] -![[Pasted image 20250428173033.png]] - - -## RNN基本结构以及存在的问题、LSTM的基本结构 - -### RNN 循环神经网络 - -由于卷积网络的局限性:1.接受固定长的向量作为输入,产生固定长度的输出;2.计算的数量是固定的 - -但是有时候需要处理时序和序列的数据,接受和生成变长的输入或输出 因此引出了循环神经网络RNN (Recurrent) - -> RNN例子 - ![[Pasted image 20250428181633.png]] - ![[Pasted image 20250428181756.png]]同步就是 输入后输出所有对应位置 - ![[Pasted image 20250428181814.png]]异步是输入后逐个输出上一个 - -#### 循环神经网络的特点 -![[Pasted image 20250428185603.png]] - -#### RNN的结构 -![[Pasted image 20250428191046.png]] - -#### RNN的训练算法 -RNN训练时候用BPTT算法![[Pasted image 20250428191151.png]] -![[Pasted image 20250428191210.png]] -![[Pasted image 20250428191215.png]] - -#### RNN的长期依赖问题 - -因为上面所示 梯度反向传播中的累乘 很容易出现 梯度消失或者梯度爆炸 ,这是因为RNN在时间维度上非常深。想要改进这个问题,对于梯度爆照:需要用 权重衰减 或者 权重截断 方法 ;对于梯度消失:需要改进模型来解决 - -直观感受长期依赖![[Pasted image 20250428191525.png]] - -**越早的输入影响就越小,越晚的输入影响就越大,而且这个逻辑是固定的,无法改变。** -### LSTM 长短期记忆网络 - -为了解决上面 RNN 对早晚输入注意程度不一致,而且逻辑无法改变的问题 ,引入了LSTM。 - -LSTM 保留了较长序列中的 重要信息,忽略不重要的信息。这样就解决了 RNN 的短期记忆的问题。 -![[Pasted image 20250428191811.png]] - -#### LSTM 的基本结构 - -> LSTM的思想: - ![[Pasted image 20250428191843.png]] - -> LSTM vs RNN : - ![[Pasted image 20250428191911.png]] - ->下图中: - 指出了 LSTM(长短期记忆网络)中的以下关键部分: - 1. **Write some new cell content**(写入新的细胞状态内容) - - 对应 **输入门(Input Gate)** 的计算,决定哪些新信息会被存储到细胞状态中。 - 2. **Forget some cell content**(遗忘部分细胞状态内容) - - 对应 **遗忘门(Forget Gate)** 的计算,决定哪些旧信息会被丢弃。 - 3. **Compute the forget gate**(计算遗忘门) - - 明确提到遗忘门的操作,通过 sigmoid 函数决定保留或遗忘多少历史信息。 - 4. **Compute the input gate**(计算输入门) - - 明确提到输入门的操作,通过 sigmoid 函数决定更新哪些新信息。 - 5. **Compute the new cell content**(计算新的细胞状态内容) - - 结合输入门和候选细胞状态(通常由 tanh 函数生成),更新细胞状态。 - 6. **Compute the output gate**(计算输出门) - - 对应 **输出门(Output Gate)** 的计算,通过 sigmoid 函数决定输出哪些信息,再结合 tanh 处理的细胞状态生成最终输出。 - 7. **候选细胞状态(Candidate Cell State)**:通常由当前输入和前一隐藏状态通过 tanh 函数生成,用于后续细胞状态更新。 - 8. **隐藏状态(Hidden State)**:作为 LSTM 的输出,传递到下一时间步或网络层。 - - -![[Pasted image 20250428191921.png]] -![[Pasted image 20250428191926.png]] -![[Pasted image 20250428191933.png]] -![[Pasted image 20250428191938.png]] -![[Pasted image 20250428191944.png]] -![[Pasted image 20250428192509.png]] - - -## Transformer基本结构以及特点 - -### Attention - ->非自主提示 无参数汇聚(最大 平均池化) - ![[Pasted image 20250428204927.png]] - -自主提示 注意力机制 - ![[Pasted image 20250428204949.png]] - ![[Pasted image 20250428205146.png]] - -> 软性注意力机制 value 就是自己 - ![[Pasted image 20250428205713.png]] - ![[Pasted image 20250428205725.png]] - -> 硬性注意力 QKV - ![[Pasted image 20250428205815.png]] - -> 多头注意力 多组查询Q KV是同一个 - ![[Pasted image 20250428205905.png]] - -> 自注意力 - h2 只得到了 x1 x3 x2 的信息 卷积和循环只有local![[Pasted image 20250428210013.png]] - 全连接没法处理变长![[Pasted image 20250428210304.png]] - 自注意力示意图:![[Pasted image 20250428210332.png]] - ![[Pasted image 20250428210351.png]] - ![[Pasted image 20250428210405.png]] - ![[Pasted image 20250428210424.png]] - -### Transformer基本结构 - -自回归模型 为了建模一个变长序列出现的概率![[Pasted image 20250428210627.png]] - -![[Pasted image 20250428210732.png]] -![[Pasted image 20250428210740.png]] -![[Pasted image 20250428210852.png]] -![[Pasted image 20250428210923.png]] -![[Pasted image 20250428210938.png]] -![[Pasted image 20250428210944.png]] -![[Pasted image 20250428211153.png]] -![[Pasted image 20250428211434.png]] - -### Transformer的特点 - -![[Pasted image 20250428211509.png]] -![[Pasted image 20250428211515.png]] - -## 预训练语言模型BERT和GPT - -![[Pasted image 20250428214346.png]] -![[Pasted image 20250428214406.png]] -![[Pasted image 20250428214410.png]] - -### BERT模型 -mask在中间的 不像 decoder 在一个地方 后面做mask -![[Pasted image 20250428213016.png]] -![[Pasted image 20250428214420.png]] -![[Pasted image 20250428214438.png]] -![[Pasted image 20250428214442.png]] -![[Pasted image 20250428214509.png]] -![[Pasted image 20250428214538.png]] -![[Pasted image 20250428214544.png]] -分类![[Pasted image 20250428214621.png]] -词性标注![[Pasted image 20250428214655.png]] -对对话分类![[Pasted image 20250428214727.png]] -![[Pasted image 20250428214750.png]] - -### GPT -![[Pasted image 20250428213255.png]] -![[Pasted image 20250428214933.png]] -![[Pasted image 20250428214940.png]] -![[Pasted image 20250428215019.png]] -![[Pasted image 20250428215034.png]] - -### 区别 - -![[Pasted image 20250428215116.png]] -![[Pasted image 20250428215147.png]] - - - - - -# 第十章 强化学习 - -## 强化学习基本原理与构成 - -强化学习(Reinforcement Learning, 简称 RL)是机器学习的范式和方法论之一,用于描述和解决智能体(Agent)在与环境(Environment)的交互过程中通过学习策略(Policy)以达成奖励或回报(Reward)最大化或实现特定目标的问题。 - - - -> 强化学习和其他机器学习范式不同的的原因: - 没有 做loss的步骤 非及时反馈![[Pasted image 20250428215449.png]] - -奖励: -![[Pasted image 20250428215605.png]] -例如: -乘坐直升机进⾏特技⻜⾏操作 - • 因遵循期望轨迹获得正奖励 - • 因坠毁获得负奖励 - -在西洋双陆棋比赛中击败世界冠军 - • +/− 因赢得/输掉⼀局比赛获得的正负奖励 - -让类人机器人⾏⾛ - • 向前移动给予正奖励 - • 摔倒给予负奖励 - -决策序列![[Pasted image 20250428215708.png]] - -智能体与环境的交互![[Pasted image 20250428215754.png]] - -历史与状态![[Pasted image 20250428215804.png]] - -信息状态![[Pasted image 20250428215821.png]] - -![[Pasted image 20250428220733.png]] - - -## 智能体的构成 - -主要组件![[Pasted image 20250428215925.png]] - -1. 策略![[Pasted image 20250428215948.png]] -2. 价值函数![[Pasted image 20250428215956.png]] -3. 模型![[Pasted image 20250428220011.png]] - -### 智能体分类 - -![[Pasted image 20250428220326.png]] -![[Pasted image 20250428220331.png]] -![[Pasted image 20250428220348.png]] - - -## 有模型与无模型的区别 - -| **维度** | **有模型(Model-Based)** | **无模型(Model-Free)** | -| -------- | --------------------------------------------------- | ------------------------------- | -| **定义** | 智能体已知环境动态(状态转移概率 \( P_{ss'}^a \) 和奖励函数 \( R_s^a \)) | 智能体直接通过与环境的交互学习,无需环境模型 | -| **核心方法** | 动态规划(DP)、值迭代、策略迭代 | 蒙特卡罗(MC)、时序差分(TD)、Q学习、Sarsa | -| **优缺点** | ✅ 样本效率高(无需实际交互)
❌ 依赖精确模型,难以处理复杂环境 | ✅ 适应性强,适用于未知环境
❌ 样本效率低,收敛慢 | -| **关键步骤** | 1. 构建环境模型
2. 基于模型计算最优策略 | 1. 直接交互获取经验
2. 通过经验更新价值/策略 | -| **典型算法** | 值迭代(Value Iteration)、策略迭代(Policy Iteration) | Q学习(Q-Learning)、Sarsa、REINFORCE | -| **应用场景** | 棋类游戏(已知规则)、仿真环境 | 机器人控制、游戏AI(如Atari) | - -**定义与核心区别** -- **有模型(Model-Based)**: - - 智能体**已知环境模型**,即状态转移概率 \(P(s'|s,a)\) 和奖励函数 \(R(s,a)\)。 - - 通过模型进行**离线规划**(如动态规划),无需与环境实时交互即可优化策略。 - - **典型方法**:动态规划(DP)、值迭代(Value Iteration)、策略迭代(Policy Iteration)。 - - **优点**:样本效率高(无需大量试错),适合小规模或已知模型的问题。 - - **缺点**:模型难以精确获取,复杂环境(如连续状态或高维空间)中不适用。 - -- **无模型(Model-Free)**: - - 智能体**不依赖环境模型**,通过与环境交互直接学习策略或价值函数。 - - 通过**试错**(Trial-and-Error)收集经验,逐步优化策略。 - - **典型方法**:Q-learning、Sarsa、蒙特卡洛(MC)、深度Q网络(DQN)。 - - **优点**:适用于未知或复杂环境,更贴近实际应用场景。 - - **缺点**:样本效率低,训练时间长,可能陷入局部最优。 - -**示例对比** -- **迷宫问题**: - - 有模型:已知迷宫地图,直接计算最短路径。 - - 无模型:通过不断尝试移动并记录奖励,逐步学习最优路径。 -## 基于价值与基于策略的区别 - -| **维度** | **基于价值(Value-Based)** | **基于策略(Policy-Based)** | -|-------------------|----------------------------------------------------|---------------------------------------------------| -| **核心目标** | 学习价值函数 \( V(s) \) 或 \( Q(s,a) \),通过价值选择动作(如ε-贪心) | 直接优化策略函数 \( \pi(a\|s) \),无需价值函数 | -| **策略类型** | 隐式策略(如贪心策略) | 显式策略(可随机或确定性) | -| **优缺点** | ✅ 适合离散动作空间
❌ 难以处理高维/连续动作 | ✅ 支持连续动作、随机策略
❌ 高方差、局部最优 | -| **收敛性** | 可能震荡(如Q学习) | 更平滑但可能陷入局部最优 | -| **典型算法** | Q学习、DQN、Sarsa | REINFORCE、PPO、策略梯度(Policy Gradient) | -| **应用场景** | 离散控制(如Atari游戏) | 连续控制(如机器人行走)、博弈论(如石头剪刀布) | - - -**定义与核心区别** -- **基于价值(Value-Based)**: - - 核心是学习**价值函数**(如状态价值 \(V(s)\) 或动作价值 \(Q(s,a)\)),通过选择价值最大的动作间接优化策略。 - - 策略通常是隐式的(如 \(\epsilon\)-贪心策略)。 - - **典型方法**:Q-learning、Sarsa、DQN。 - - **优点**:稳定性高,适合离散动作空间。 - - **缺点**:难以处理高维或连续动作空间,无法学习随机策略。 - -- **基于策略(Policy-Based)**: - - 直接学习**策略函数** \(\pi(a|s)\),参数化策略并优化其性能。 - - 策略可以是确定性的或随机性的。 - - **典型方法**:策略梯度(REINFORCE)、PPO(Proximal Policy Optimization)。 - - **优点**:适合连续动作空间,能学习随机策略(如博弈中的混合策略)。 - - **缺点**:方差高,收敛速度慢,易陷入局部最优。 - -**示例对比** -- **机器人行走**: - - 基于价值:计算每个动作的价值,选择最大价值的动作(如关节转动角度)。 - - 基于策略:直接输出动作分布(如关节转动的概率),通过梯度上升优化。 - -**混合方法:演员-评论家(Actor-Critic)** -- 结合价值函数(Critic)和策略函数(Actor),Critic评估动作价值,Actor优化策略。 -- **典型方法**:A3C、DDPG。 - - -## 常用强化学习方法的名称 - -| **方法名称** | **类型** | **有模型/无模型** | **核心思想** | -|--------------------|-------------------|-------------------|------------------------------------------| -| **Q-learning** | 基于价值 | 无模型 | 通过最大化Q值更新策略,离线策略(Off-Policy)。 | -| **Sarsa** | 基于价值 | 无模型 | 在线策略(On-Policy),使用当前策略选择动作。 | -| **DQN** | 基于价值 | 无模型 | 深度网络近似Q值,引入经验回放和固定目标网络。 | -| **策略梯度(REINFORCE)** | 基于策略 | 无模型 | 直接优化策略,通过蒙特卡洛回报计算梯度。 | -| **PPO** | 基于策略 | 无模型 | 限制策略更新幅度,提升训练稳定性。 | -| **Actor-Critic** | 混合方法(价值+策略) | 无模型 | Actor输出策略,Critic评估价值,降低方差。 | -| **动态规划(DP)** | 基于价值 | 有模型 | 利用环境模型迭代计算最优价值函数。 | -| **蒙特卡洛(MC)** | 基于价值 | 无模型 | 通过完整回合的回报更新价值函数。 | - -**适用场景总结** -- **有模型方法**:环境模型已知且规模较小(如棋盘游戏、简单控制)。 -- **无模型方法**:环境复杂或未知(如机器人控制、游戏AI)。 -- **基于价值方法**:动作空间离散且维度低(如Atari游戏)。 -- **基于策略方法**:动作空间连续或需要随机策略(如机械臂操控、多智能体博弈)。 - -### 详细一点的 - -#### 1. **动态规划(Dynamic Programming, DP)** - - **特点**:要求已知MDP模型,通过贝尔曼方程迭代求解。 - - **算法**: - - *值迭代*(Page 63):直接优化值函数 \( V(s) \)。 - - *策略迭代*(Page 60):交替进行策略评估和改进。 - -#### 2. **无模型方法(Model-Free)** - - **蒙特卡罗(MC)**(Page 71): - - 通过完整回合的经验更新(如 \( V(s) \leftarrow V(s) + \alpha(G_t - V(s)) \))。 - - 高方差但无偏,适用于回合制任务。 - - **时序差分(TD)**(Page 76): - - *TD(0)*:单步更新(\( V(s) \leftarrow V(s) + \alpha(r + \gamma V(s') - V(s)) \))。 - - *n-step TD*(Page 87):多步混合MC和TD。 - - **Q学习(Q-Learning)**(Page 102): - - 离线策略,更新规则:\( Q(s,a) \leftarrow Q(s,a) + \alpha(r + \gamma \max_{a'} Q(s',a') - Q(s,a)) \)。 - - **Sarsa**(Page 99): - - 在线策略,更新规则:\( Q(s,a) \leftarrow Q(s,a) + \alpha(r + \gamma Q(s',a') - Q(s,a)) \)。 - -#### 3. **价值函数近似(Value Approximation)** - - **DQN**(Page 126): - - 使用神经网络近似Q函数,引入经验回放和固定目标网络。 - - **改进方法**:Double DQN、Dueling DQN(Page 133)。 - -#### 4. **策略优化(Policy Optimization)** - - **策略梯度**(Page 143): - - REINFORCE(Page 145):蒙特卡罗策略梯度,\( \Delta \theta = \alpha \nabla_\theta \log \pi_\theta(s,a) G_t \)。 - - **Actor-Critic**(Page 146): - - 结合值函数(Critic)和策略(Actor),如A2C、A3C。 - -#### 5. **混合方法** - - **PPO、TRPO**:通过约束策略更新提升稳定性。 \ No newline at end of file diff --git "a/source/bak/\346\234\272\345\231\250\345\255\246\344\271\240\345\244\215\344\271\240\350\265\204\346\226\231.md.bak.update" "b/source/bak/\346\234\272\345\231\250\345\255\246\344\271\240\345\244\215\344\271\240\350\265\204\346\226\231.md.bak.update" deleted file mode 100644 index 3495926..0000000 --- "a/source/bak/\346\234\272\345\231\250\345\255\246\344\271\240\345\244\215\344\271\240\350\265\204\346\226\231.md.bak.update" +++ /dev/null @@ -1,1134 +0,0 @@ ---- -title: "机器学习复习资料" -date: 2025-04-28 23:52:47 -updated: 2025-04-30 05:46:15 -mathjax: true -categories: 期末复习 -tags: - - 机器学习 - - 期末复习 - - 深度学习 -comments: false ---- -# 大纲复习资料 - -# 第一章 机器学习简介 - -## 机器学习的概念、要素、类型 - -### 概念 - -机器学习(Machine Learning,ML)是指从有限的观测数据中学习(或“猜测”)出具有一般性的规律,并将这些规律应用到未观测数据样本上的方法。主要研究内容是学习算法。 - -**机器学习**(英语:machine learning)是人工智能的一个分支。机器学习理论主要是设计和分析一些让计算机可以自动学习的算法。机器学习算法是一类从数据]中自动分析获得规律,并利用规律对未知数据进行预测的算法。因为学习算法中涉及了大量的统计学理论,机器学习与推断统计学联系尤为密切,也被称为**统计学习理论**。算法设计方面,机器学习理论关注可以实现的,行之有效的学习算法(要防止错误累积)。很多推论问题属于非程序化決策,所以部分的机器学习研究是开发容易处理的近似算法。(Wikipedia) - -计算机科学家汤姆·米切尔在其著作的Machine Learning一书中定义的机器学习为: well-posed learning problem - -> A computer program is said to learn from experience E with respect to some class of tasks T and performance measure P, if its performance at tasks in T, as measured by P, improves with experience E. -> -> ——Tom Mitchell,Machine Learning - -良好的学习问题 是 以性能量度P进行衡量,如果一个计算机程序在某类任务T上的性能,随着经验E而提升,那么我们称这个计算机程序能从经验E中学习。 - -### 要素 三个 - -- 模型(这里的线性 指的 是对参数 w ) - - 线性方法(指的是 w的线性函数): - $$ - f(x,\theta)=w^Tx+b - $$ - - 广义线性方法: - $$ - f(x,\theta)=w^T \phi (x) +b - $$ - 其中 $\phi (x)$ 是 可学习的 非线性基函数 (基函数用于对x进行特征变换) - - 非线性方法:例如神经网络 - $$ - f(x) = \sum_{j=1}^{m} v_j \cdot \sigma(\mathbf{w}_j^\top \mathbf{x} + b_j) + b - $$ - $\sigma(\cdot)$ 是激活函数(如 ReLU、Sigmoid)这个模型的 非线性来自激活函数和多层结构。 - -- 学习准则 (学习目标 优化目标) - - 期望风险:这个是最理论化的目标 指的是 损失值在真实数据分布下的期望,但是由于 数据的真实分布 $P(x,y)$ 是不可知的,所以通常难以计算 - $$ - \cal{R}(f)= \mathbb{E}_{(x,y)\sim P(x,y)} \left[ L(f(x), y) \right] - $$ - - 经验风险:根据大数定律 当数据量足够大时 可以用频率当做分布 所以有经验风险 - $$ - \cal{R}_{emp}(f) = \frac{1}{n} \sum_{i=1}^{n} L(f(x_i), y_i) - $$ - - ***结构***风险最小化:在结构风险基础上加上正则化项 防止模型过拟合 控制模型的复杂程度 - $$ - \cal{R}_{srm}(f) = R_{emp}(f) + \lambda \cdot \Omega(f) - $$ - 其中 $\lambda$ 是控制正则损失系数 - - - 最大间隔准则: 在SVM支持向量机中的学习准则 - $$ - \min \frac{1}{2} ||w||^2 \quad \text{s.t. } y_i(w^\top x_i + b) \ge 1 - $$ - 间接最小化结构,优先选更稳健的模型 - - - 最大似然估计(MLE):最大似然 - $$ - \max_\theta \prod_{i=1}^n P(y_i|x_i;\theta) \quad \text{或} \quad \max_\theta \sum_{i=1}^n \log P(y_i|x_i;\theta) - $$ - - 贝叶斯后验最大化(MAP): - $$ - \max_\theta P(\theta|D) = \max_\theta P(D|\theta) P(\theta) - $$ - 加了先验的 MLE 可以当做正则化的MLE - -- 优化 - - 梯度下降方法 - - |方法名|特点|备注| - |---|---|---| - |**标准梯度下降(GD)**|每次用全量样本更新|计算量大,收敛稳定| - |**随机梯度下降(SGD)**|每次只用一个样本|噪声大但效率高| - |**小批量梯度下降(Mini-batch GD)**|综合GD和SGD优点|深度学习常用| - |**Momentum(动量法)**|加速收敛,抗震荡|模拟物理惯性| - |**Nesterov Accelerated Gradient(NAG)**|改进动量法|预判未来梯度方向| - |**Adagrad**|自适应每个参数学习率|学习率随训练减小,可能太快收敛| - |**RMSProp**|改进Adagrad,控制过快下降|常用于RNN| - |**Adam**|融合Momentum和RMSProp|最常用,鲁棒性好| - |**AdamW**|改进了Adam的权重衰减策略|Transformer常用| - - - 二阶优化方法 - - |方法名|特点|备注| - |---|---|---| - |**牛顿法(Newton’s Method)**|用二阶导数加快收敛|每次迭代代价高| - |**拟牛顿法(如BFGS、L-BFGS)**|近似二阶信息|在小数据场景表现好| - |**自然梯度法(Natural Gradient)**|考虑参数空间结构|常用于贝叶斯/强化学习| - - - 无梯度优化 - - |方法名|特点|适用场景| - |---|---|---| - |**网格搜索 / 随机搜索**|超参数调优经典方法|参数少时可用| - |**进化算法(如遗传算法GA)**|模拟自然选择过程|黑盒优化| - |**粒子群优化(PSO)**|模拟群体智能|连续优化有效| - |**模拟退火(SA)**|模拟物理退火过程|全局优化倾向强| - |**贝叶斯优化(BO)**|利用代理模型迭代搜索|少样本高效搜索| - -## 过拟合、欠拟合、正则化 - -### 过拟合 - -过拟合(Over-fitting)是指 模型在训练集上面误差很低,但是在测试机上误差很高。对训练集拟合的能力过强。 -![](https://cdn.jsdelivr.net/gh/zip95297/zip95297.github.io@main/source/images/%E6%9C%BA%E5%99%A8%E5%AD%A6%E4%B9%A0%E5%A4%8D%E4%B9%A0%E8%B5%84%E6%96%99/Pasted%20image%2020250425170110.webp?raw=true) - -过拟合的解决方法: -- 扩大数据集 -- 正则化 (奥卡姆剃刀原理 简单有效) - 如果没有必要就不要增大模型的数量 -- 通过 验证集合 来选择模型 - K折交叉验证:训练K次判断模型能力 - 留一法 K=N -### 欠拟合 - -模型在训练集和测试集上的误差都很大。由于模型能力不足(不够灵活) -![](https://cdn.jsdelivr.net/gh/zip95297/zip95297.github.io@main/source/images/%E6%9C%BA%E5%99%A8%E5%AD%A6%E4%B9%A0%E5%A4%8D%E4%B9%A0%E8%B5%84%E6%96%99/Pasted%20image%2020250425170633.webp?raw=true) - -### 正则化 - -对模型的损失中 添加正则化损失 例如L1、L2 -奥卡姆定律 简单有效 -![](https://cdn.jsdelivr.net/gh/zip95297/zip95297.github.io@main/source/images/%E6%9C%BA%E5%99%A8%E5%AD%A6%E4%B9%A0%E5%A4%8D%E4%B9%A0%E8%B5%84%E6%96%99/Pasted%20image%2020250425171150.webp?raw=true) - -## 贝叶斯公式 - -后验 似然x先验 -$$ -p(Y|X)=\cfrac{P(X|Y)P(Y)}{P(X)} -$$ - -# 第二章 概率论与优化 - -## 概率的概念、贝叶斯学派vs频率轮学派 - -### **概率的概念** - -概率是对**不确定事件发生可能性**的度量。在数学上,概率是定义在样本空间上的一个函数,取值范围是 \[0,1] 。 - -但不同学派对“概率的意义”有不同理解,主要有: -### **频率学派(Frequentist)** - -#### 观点: - -概率是一个事件在**大量重复试验中出现的相对频率**。 -通过大量独立实验将概率解释为事件发生频率的均值(大数定律)。 - -> 举个例子:如果你连续掷硬币很多次,有一半是正面,那我们就说正面概率是 0.5。 - -#### 特点: - -- 概率是客观的,跟你是否知道某个信息无关。 -- 参数是**固定未知数**,不能被建模成随机变量。 -- 推断方法:最大似然估计(MLE)、假设检验、置信区间等。 - -### **贝叶斯学派(Bayesian)** - -#### 观点: - -概率是表示一个人对事件发生的**主观相信程度**,是**对不确定性的度量**。 - -将概率解释为信念度(degree ofbelief)。当考虑的试验次数非常少的时候,贝叶斯方法的解释非常有用。此外,贝叶斯理论将我们对于随机过程的先验知识纳入考虑,当我们获得新数据的时候,这个先验的概率分布就会被更新到后验分布中。 - -> 举个例子:如果你知道这枚硬币偏向正面,你就可以给出“正面概率0.8”,即使你还没开始掷。 - -#### 特点: - -- 概率可以用于表示对未知参数的信念。 -- 参数也可以是**随机变量**。 -- 推断方法:利用贝叶斯公式更新先验分布得到后验分布。 - -$$ -p(\theta | x) = \frac{p(x | \theta) \cdot p(\theta)}{p(x)} -$$ - ---- - -### 两者对比总结: - -| 内容 | 频率学派 | 贝叶斯学派 | -| ---- | -------- | ------------- | -| 概率定义 | 事件长时间频率 | 主观信念 | -| 参数看法 | 固定未知值 | 随机变量 | -| 推断结果 | 单个估计值 | 概率分布 | -| 常用方法 | MLE、假设检验 | 贝叶斯公式、先验-后验推断 | - -## 伯努利分布、高斯分布 - -### 伯努利分布 Bernouli - -就是 单次的 二项分布 : -$$ -Bern(x|\mu)=\mu^x(1-\mu)^{1-x} \ \ \ ,\ x\in\{0,1\} -$$ -期望 $\mathbb{E}[x]=\mu$ , 方差 $var[x]=\mu(1-\mu)$ - -二项分布: -$$ -Bin(m|N,\mu) = C^m_N \mu^m (1-\mu)^{N-m} -$$ -期望 $\mathbb{E}[x]=N\mu$ , 方差 $var[x]=N\mu(1-\mu)$ - -共轭性:![](https://cdn.jsdelivr.net/gh/zip95297/zip95297.github.io@main/source/images/%E6%9C%BA%E5%99%A8%E5%AD%A6%E4%B9%A0%E5%A4%8D%E4%B9%A0%E8%B5%84%E6%96%99/Pasted%20image%2020250426030533.webp?raw=true) - -### 高斯分布 - -就是正态分布: -$$ -{N}(x|\mu,\sigma^2)=\cfrac{1}{(2\pi\sigma^2)^{1/2}}exp\{-\frac{1}{2\sigma^2}(x-\mu)^2\} -$$ -期望 $\mathbb{E}[x]=\mu$ , 方差 $var[x]=\sigma^2$ - -## 极大似然估计、最大后验估计 - -极大似然估计 和 最大后验估计 都是 机器学习三要素中的 学习准则 - -### 极大似然估计(Maximum Likelihood Estimate)MLE - -选择让 观测到数据出现的概率最大的 一组参数 , 下面这个P就是似然函数 -$$ -\hat{\theta} = \arg\max_\theta P(\cal{D}|\theta) -$$ -### 最大后验估计 (Maximum A Posteriori estimate)MAP - -在给定 先验参数分布 和 数据 $\cal{D}$ 的情况下 最有可能的 参数 $\theta$ -$$ -\hat{\theta} = \arg\max_\theta P(\theta|\cal{D})=\arg\max_\theta \cfrac{P(\cal{D}|\theta)P(\theta)}{P(\cal{D})} -$$ - - -## 梯度下降以及不同形式 - -Goal: -$$ -\min_x f(x) -$$ -Just iterate -$$ -x_{t+1}=x_t-\eta_t\nabla f(x_t) -$$ -where $\eta_t$ is learning rate - -![](https://cdn.jsdelivr.net/gh/zip95297/zip95297.github.io@main/source/images/%E6%9C%BA%E5%99%A8%E5%AD%A6%E4%B9%A0%E5%A4%8D%E4%B9%A0%E8%B5%84%E6%96%99/Pasted%20image%2020250426031557.webp?raw=true) - -| 方法名 | 特点 | 备注 | -| -------------------------------------- | ------------------ | ---------------------- | -| **标准梯度下降(GD)** | 每次用全量样本更新 | 计算量大,收敛稳定。也叫批量梯度下降法BGD | -| **随机梯度下降(SGD)** | 每次只用一个样本 | 噪声大但效率高 | -| **小批量梯度下降(Mini-batch GD)** | 综合GD和SGD优点 | 深度学习常用 | -| **Momentum(动量法)** | 加速收敛,抗震荡 | 模拟物理惯性 | -| **Nesterov Accelerated Gradient(NAG)** | 改进动量法 | 预判未来梯度方向 | -| **Adagrad** | 自适应每个参数学习率 | 学习率随训练减小,可能太快收敛 | -| **RMSProp** | 改进Adagrad,控制过快下降 | 常用于RNN | -| **Adam** | 融合Momentum和RMSProp | 最常用,鲁棒性好 | -| **AdamW** | 改进了Adam的权重衰减策略 | Transformer常用 | -![](https://cdn.jsdelivr.net/gh/zip95297/zip95297.github.io@main/source/images/%E6%9C%BA%E5%99%A8%E5%AD%A6%E4%B9%A0%E5%A4%8D%E4%B9%A0%E8%B5%84%E6%96%99/Pasted%20image%2020250426032114.webp?raw=true) -![](https://cdn.jsdelivr.net/gh/zip95297/zip95297.github.io@main/source/images/%E6%9C%BA%E5%99%A8%E5%AD%A6%E4%B9%A0%E5%A4%8D%E4%B9%A0%E8%B5%84%E6%96%99/Pasted%20image%2020250426032336.webp?raw=true) -![](https://cdn.jsdelivr.net/gh/zip95297/zip95297.github.io@main/source/images/%E6%9C%BA%E5%99%A8%E5%AD%A6%E4%B9%A0%E5%A4%8D%E4%B9%A0%E8%B5%84%E6%96%99/Pasted%20image%2020250426032422.webp?raw=true) - -# 第三章 回归的线性模型 - -## 介绍 - -> 什么是回归? 回归指的是 让模型 预测连续的数值型输出(即实数范围内的值) - -> 什么是线性模型? 线性模型指的是 模型 是 参数w 的线性函数 一般形式为 - $$ - f(x)=w_1x_1+w_2x_2+...+w_dx_d+b - $$ - -向量形式为 -$$ -f(x)=w^Tx +b -$$ - -其中 $\vec{x} =(x_1,x_2....x_d)$ 是输入的特征 线性模型是w的线性模型 x 如果经过基函数变换为 $\phi(x)$ 仍是线性模型。 -线性模型的优点:简单易建模 可解释性强 非线性模型(引入层级结构或者高位映射)的基础 。 - ----- -线性回归是什么? -给定数据集 $D=\{(x_1,y_1),(x_2,y_2),...,(x_m,y_m)\}$,其中 $x_i=(x_{i1};x_{i2};...;x_{id}),y_i\in\mathbb{R}$ -线性回归的目的 学到一个线性模型 尽可能 准确地 预测 实值输出 - -## 线性回归最小二乘法 - -建立线性模型 为了方便 表达 把 b 和 1 增广入 w x 中 -![](https://cdn.jsdelivr.net/gh/zip95297/zip95297.github.io@main/source/images/%E6%9C%BA%E5%99%A8%E5%AD%A6%E4%B9%A0%E5%A4%8D%E4%B9%A0%E8%B5%84%E6%96%99/Pasted%20image%2020250426155115.webp?raw=true) -![](https://cdn.jsdelivr.net/gh/zip95297/zip95297.github.io@main/source/images/%E6%9C%BA%E5%99%A8%E5%AD%A6%E4%B9%A0%E5%A4%8D%E4%B9%A0%E8%B5%84%E6%96%99/Pasted%20image%2020250426155201.webp?raw=true) -![](https://cdn.jsdelivr.net/gh/zip95297/zip95297.github.io@main/source/images/%E6%9C%BA%E5%99%A8%E5%AD%A6%E4%B9%A0%E5%A4%8D%E4%B9%A0%E8%B5%84%E6%96%99/Pasted%20image%2020250426155225.webp?raw=true) -![](https://cdn.jsdelivr.net/gh/zip95297/zip95297.github.io@main/source/images/%E6%9C%BA%E5%99%A8%E5%AD%A6%E4%B9%A0%E5%A4%8D%E4%B9%A0%E8%B5%84%E6%96%99/Pasted%20image%2020250426155301.webp?raw=true) - -### 多元线性回归 -![](https://cdn.jsdelivr.net/gh/zip95297/zip95297.github.io@main/source/images/%E6%9C%BA%E5%99%A8%E5%AD%A6%E4%B9%A0%E5%A4%8D%E4%B9%A0%E8%B5%84%E6%96%99/Pasted%20image%2020250426155423.webp?raw=true) -![](https://cdn.jsdelivr.net/gh/zip95297/zip95297.github.io@main/source/images/%E6%9C%BA%E5%99%A8%E5%AD%A6%E4%B9%A0%E5%A4%8D%E4%B9%A0%E8%B5%84%E6%96%99/Pasted%20image%2020250426155429.webp?raw=true) -![](https://cdn.jsdelivr.net/gh/zip95297/zip95297.github.io@main/source/images/%E6%9C%BA%E5%99%A8%E5%AD%A6%E4%B9%A0%E5%A4%8D%E4%B9%A0%E8%B5%84%E6%96%99/Pasted%20image%2020250426155434.webp?raw=true) - -### 基函数 - -![](https://cdn.jsdelivr.net/gh/zip95297/zip95297.github.io@main/source/images/%E6%9C%BA%E5%99%A8%E5%AD%A6%E4%B9%A0%E5%A4%8D%E4%B9%A0%E8%B5%84%E6%96%99/Pasted%20image%2020250426155455.webp?raw=true) -![](https://cdn.jsdelivr.net/gh/zip95297/zip95297.github.io@main/source/images/%E6%9C%BA%E5%99%A8%E5%AD%A6%E4%B9%A0%E5%A4%8D%E4%B9%A0%E8%B5%84%E6%96%99/Pasted%20image%2020250426155505.webp?raw=true) -基函数 可以选择 -- 多项式基函数\[$\phi_j(x)=x^j$\] -- 高斯基函数 \[$\phi_j(x)=exp\{-\frac{(x-\mu_j)^2}{2s^2}\}$\] -- sigmoid 基函数\[$\phi_j(x)=\sigma(\frac{x-\mu_j}{s}) ,where\ \ \sigma(a)=\frac{1}{1+exp(-a)}$\] - -## 岭回归 Ridge Regression 山脊回归 - -岭回归就是在原本的loss上加上了 权重产生的 权重衰减正则化项 -![](https://cdn.jsdelivr.net/gh/zip95297/zip95297.github.io@main/source/images/%E6%9C%BA%E5%99%A8%E5%AD%A6%E4%B9%A0%E5%A4%8D%E4%B9%A0%E8%B5%84%E6%96%99/Pasted%20image%2020250426160411.webp?raw=true) -![](https://cdn.jsdelivr.net/gh/zip95297/zip95297.github.io@main/source/images/%E6%9C%BA%E5%99%A8%E5%AD%A6%E4%B9%A0%E5%A4%8D%E4%B9%A0%E8%B5%84%E6%96%99/Pasted%20image%2020250426160417.webp?raw=true) - -最大化后验概率 等价于 最小化正则化的平方和误差函数 -![](https://cdn.jsdelivr.net/gh/zip95297/zip95297.github.io@main/source/images/%E6%9C%BA%E5%99%A8%E5%AD%A6%E4%B9%A0%E5%A4%8D%E4%B9%A0%E8%B5%84%E6%96%99/Pasted%20image%2020250426160617.webp?raw=true) - -闭式解与几何解释 -![](https://cdn.jsdelivr.net/gh/zip95297/zip95297.github.io@main/source/images/%E6%9C%BA%E5%99%A8%E5%AD%A6%E4%B9%A0%E5%A4%8D%E4%B9%A0%E8%B5%84%E6%96%99/Pasted%20image%2020250426160732.webp?raw=true) - -## Lasso 回归 套索回归 -L1 loss -![](https://cdn.jsdelivr.net/gh/zip95297/zip95297.github.io@main/source/images/%E6%9C%BA%E5%99%A8%E5%AD%A6%E4%B9%A0%E5%A4%8D%E4%B9%A0%E8%B5%84%E6%96%99/Pasted%20image%2020250426160802.webp?raw=true) - -Lasso回归能够使模型 具有稀疏性 -![](https://cdn.jsdelivr.net/gh/zip95297/zip95297.github.io@main/source/images/%E6%9C%BA%E5%99%A8%E5%AD%A6%E4%B9%A0%E5%A4%8D%E4%B9%A0%E8%B5%84%E6%96%99/Pasted%20image%2020250426160911.webp?raw=true) - - -# 第四章 基础的分类方法 - -回归是多个输入特征被映射到一个或者多个连续的目标变量 -分类则是 输入特征x被映射到K个离散类别之一$C_k$ -## 线性分类概念 - -线性分类,模型指的是 分类面 是 x 的 线性 函数 (分类函数 f 可能是非线性函数 例如在外面加了一层 sigmoid) -区别于前面的线性回归(对于参数是线性的) - -推广的线性模型 用非线性函数对w的线性函数的结果进行激活![](https://cdn.jsdelivr.net/gh/zip95297/zip95297.github.io@main/source/images/%E6%9C%BA%E5%99%A8%E5%AD%A6%E4%B9%A0%E5%A4%8D%E4%B9%A0%E8%B5%84%E6%96%99/Pasted%20image%2020250426165956.webp?raw=true) -![](https://cdn.jsdelivr.net/gh/zip95297/zip95297.github.io@main/source/images/%E6%9C%BA%E5%99%A8%E5%AD%A6%E4%B9%A0%E5%A4%8D%E4%B9%A0%E8%B5%84%E6%96%99/Pasted%20image%2020250426162156.webp?raw=true) -![](https://cdn.jsdelivr.net/gh/zip95297/zip95297.github.io@main/source/images/%E6%9C%BA%E5%99%A8%E5%AD%A6%E4%B9%A0%E5%A4%8D%E4%B9%A0%E8%B5%84%E6%96%99/Pasted%20image%2020250426162206.webp?raw=true) - -下面说的三种 感知机 logistic回归 朴素贝叶斯分类器 -感知机和logistic回归两种是严格线性的 决策面是超平面 -朴素贝叶斯分类器 通常是非线性 其决策边界取决于特征的条件概率分布(如高斯朴素贝叶斯的决策边界是二次曲面) -若特征的条件概率分布属于***指数族(如泊松分布)***,且满足特定条件时,可能得到线性决策边界。 -## 三种类型:判别函数、概率生成模型、概率判别模型 - -### 判别函数 Discriminant Function (感知器) - ->这个是直接说出结果是哪个 下面的会给出概率 然后再决策是哪个 - -直接找到一个函数 $f(x)$ 把每个输入 x 直接映射为 类别标签,例如 感知机 SVM 之类的 - -线性判别函数就是指 决策面 是 超 平面 的判别函数 - -#### 二分类情形 -线性判别函数 y=0 就是 决策平面 -![](https://cdn.jsdelivr.net/gh/zip95297/zip95297.github.io@main/source/images/%E6%9C%BA%E5%99%A8%E5%AD%A6%E4%B9%A0%E5%A4%8D%E4%B9%A0%E8%B5%84%E6%96%99/Pasted%20image%2020250426170353.webp?raw=true) -几何性质 【2|1】![](https://cdn.jsdelivr.net/gh/zip95297/zip95297.github.io@main/source/images/%E6%9C%BA%E5%99%A8%E5%AD%A6%E4%B9%A0%E5%A4%8D%E4%B9%A0%E8%B5%84%E6%96%99/Pasted%20image%2020250426170523.webp?raw=true) - -如过用一个二分类器 处理多分类的情况,可以: -- 一对其他 分类器(K-1个二分类器) -- 一对一 分类器 ($C^2_k$ 个二分类器) -但是会出现不可分的区域![](https://cdn.jsdelivr.net/gh/zip95297/zip95297.github.io@main/source/images/%E6%9C%BA%E5%99%A8%E5%AD%A6%E4%B9%A0%E5%A4%8D%E4%B9%A0%E8%B5%84%E6%96%99/Pasted%20image%2020250426170742.webp?raw=true) -#### 多分类情形 -K类判别函数 $y_k(x)=w_k^Tx+w_{k0}$ 如果一个k=k\*大于 其他所有的 y 那就是Ck\* 类 -![](https://cdn.jsdelivr.net/gh/zip95297/zip95297.github.io@main/source/images/%E6%9C%BA%E5%99%A8%E5%AD%A6%E4%B9%A0%E5%A4%8D%E4%B9%A0%E8%B5%84%E6%96%99/Pasted%20image%2020250426171017.webp?raw=true) - -### 概率判别模型 Probabilistic Discriminative Models (logistic回归\*) - -直接对 后验概率 $p(C_k|x)$ 建模,然后用决策论来确定x的输入类别 比如取最高的等等。例如 逻辑(logistic)回归、神经网络(最后softmax的结果就是一组后验概率) -#### Logistic回归 (分类模型) - -在线性模型的基础上 加上了激活函数 得到概率大小 下面的例子用的是sigmoid函数 -![](https://cdn.jsdelivr.net/gh/zip95297/zip95297.github.io@main/source/images/%E6%9C%BA%E5%99%A8%E5%AD%A6%E4%B9%A0%E5%A4%8D%E4%B9%A0%E8%B5%84%E6%96%99/Pasted%20image%2020250426171443.webp?raw=true) -![](https://cdn.jsdelivr.net/gh/zip95297/zip95297.github.io@main/source/images/%E6%9C%BA%E5%99%A8%E5%AD%A6%E4%B9%A0%E5%A4%8D%E4%B9%A0%E8%B5%84%E6%96%99/Pasted%20image%2020250426171825.webp?raw=true) -直接对y=1 对线性函数激活得到概率 -![](https://cdn.jsdelivr.net/gh/zip95297/zip95297.github.io@main/source/images/%E6%9C%BA%E5%99%A8%E5%AD%A6%E4%B9%A0%E5%A4%8D%E4%B9%A0%E8%B5%84%E6%96%99/Pasted%20image%2020250426171929.webp?raw=true) -决策面就是两个类别的后验概率相等 也就是 $p(C_1|x)=p(C_2|x)$ 可以根据这个推出逻辑回归模型的决策面是线性的![](https://cdn.jsdelivr.net/gh/zip95297/zip95297.github.io@main/source/images/%E6%9C%BA%E5%99%A8%E5%AD%A6%E4%B9%A0%E5%A4%8D%E4%B9%A0%E8%B5%84%E6%96%99/Pasted%20image%2020250426172010.webp?raw=true) -sigmoid:![](https://cdn.jsdelivr.net/gh/zip95297/zip95297.github.io@main/source/images/%E6%9C%BA%E5%99%A8%E5%AD%A6%E4%B9%A0%E5%A4%8D%E4%B9%A0%E8%B5%84%E6%96%99/Pasted%20image%2020250426171645.webp?raw=true) -##### 逻辑回归模型的训练 - -![](https://cdn.jsdelivr.net/gh/zip95297/zip95297.github.io@main/source/images/%E6%9C%BA%E5%99%A8%E5%AD%A6%E4%B9%A0%E5%A4%8D%E4%B9%A0%E8%B5%84%E6%96%99/Pasted%20image%2020250426172506.webp?raw=true) -![](https://cdn.jsdelivr.net/gh/zip95297/zip95297.github.io@main/source/images/%E6%9C%BA%E5%99%A8%E5%AD%A6%E4%B9%A0%E5%A4%8D%E4%B9%A0%E8%B5%84%E6%96%99/Pasted%20image%2020250426172544.webp?raw=true) -![](https://cdn.jsdelivr.net/gh/zip95297/zip95297.github.io@main/source/images/%E6%9C%BA%E5%99%A8%E5%AD%A6%E4%B9%A0%E5%A4%8D%E4%B9%A0%E8%B5%84%E6%96%99/Pasted%20image%2020250426172553.webp?raw=true) -![](https://cdn.jsdelivr.net/gh/zip95297/zip95297.github.io@main/source/images/%E6%9C%BA%E5%99%A8%E5%AD%A6%E4%B9%A0%E5%A4%8D%E4%B9%A0%E8%B5%84%E6%96%99/Pasted%20image%2020250426172652.webp?raw=true) -逻辑回归就是在线性回归模型的基础上套了一个sigmoid LOSS就是两个类别的的交叉熵![](https://cdn.jsdelivr.net/gh/zip95297/zip95297.github.io@main/source/images/%E6%9C%BA%E5%99%A8%E5%AD%A6%E4%B9%A0%E5%A4%8D%E4%B9%A0%E8%B5%84%E6%96%99/Pasted%20image%2020250426172717.webp?raw=true) -##### 逻辑回归和神经网络模型的对比 - -| **特性** | **逻辑回归** | **神经网络** | -| --------- | ---------------------------- | ----------------------------- | -| **模型结构** | 单层(仅输入层 + 输出层) | 多层(输入层 + 隐藏层 + 输出层) | -| **决策边界** | 线性(超平面) | 非线性(可拟合复杂边界) | -| **表达能力** | 只能解决线性可分问题 | 可逼近任意复杂函数(通用近似定理) | -| **参数优化** | 使用梯度下降法优化权重 \( w \) | 使用反向传播(Backpropagation)优化多层权重 | -| **输出类型** | 概率(通过Sigmoid函数) | 概率(Softmax)或任意值(取决于输出层设计) | -| **适用任务** | 二分类(默认)、可扩展至多分类(One-vs-Rest) | 二分类、多分类、回归、生成任务等 | -| **计算复杂度** | 低(参数少,训练快) | 高(参数多,需大量数据和计算资源) | -| **可解释性** | 高(权重直接反映特征重要性) | 低(黑箱模型,难以解释隐藏层) | - -### 概率生成模型 Probabilistic Generative Models (朴素贝叶斯分类器\*) - -先对 类的条件密度 $p(x|C_k)$ 和 先验类概率分布 $p(C_k)$ 建模,然后再用 贝叶斯定理 计算 后验类概率分布 $p(C_k|x)$ : -$$ -p(C_k|x)=\cfrac{p(x|C_k)p(C_k)}{p(x)} -$$ -最后,使用决策论来确定每个输入x的类别 - -等价地,直接对 联合概率分布 $p(x,C_k)$ 建模 , 再做归一化得到后验概率。 - -统计类别分布 得到先验 在类内 统计属性分布 得到似然 -#### 朴素贝叶斯分类器 - -估计后验概率的主要困难在于:类条件概率是 x 在所有属性上的联合概率(输入的x有很多维度), 难以用有先限的训练样本估计获得 - -朴素贝叶斯分类器 采用 ***属性条件独立性假设*** , 每个属性独立地对分类结果发生影响。 - -更具上述假设 后验概率可以写为(d是属性个数 i是第i个属性): -$$ -P(c|x)=\cfrac{P(c)P(x|c)}{P(x)}=\cfrac{P(c)}{P(x)}\prod_{i=1}^dP(x_i|c) -$$ -对于所有类别 P(x) 相同 , 所以***贝叶斯判定准则***为: -$$ -h_{nb}(x)=\underset{c\in y}{\arg\max} \, P(c)\prod^d_{i=1}P(x_i|c) -$$ -即对每个样本x 选择后验概率最大的类 - -##### 朴素贝叶斯分类器计算例子 - -![](https://cdn.jsdelivr.net/gh/zip95297/zip95297.github.io@main/source/images/%E6%9C%BA%E5%99%A8%E5%AD%A6%E4%B9%A0%E5%A4%8D%E4%B9%A0%E8%B5%84%E6%96%99/Pasted%20image%2020250426175412.webp?raw=true) -![](https://cdn.jsdelivr.net/gh/zip95297/zip95297.github.io@main/source/images/%E6%9C%BA%E5%99%A8%E5%AD%A6%E4%B9%A0%E5%A4%8D%E4%B9%A0%E8%B5%84%E6%96%99/Pasted%20image%2020250426175419.webp?raw=true) -![](https://cdn.jsdelivr.net/gh/zip95297/zip95297.github.io@main/source/images/%E6%9C%BA%E5%99%A8%E5%AD%A6%E4%B9%A0%E5%A4%8D%E4%B9%A0%E8%B5%84%E6%96%99/Pasted%20image%2020250426175424.webp?raw=true) - -# 第五章 支持向量机 与 核方法 - -## SVM原理 - -支持向量机的三个关键想法: -- 通过优化来求解一个超平面分类器 -- 寻找最大间隔分类器来提高模型的泛化能力(结构化风险最小) -- 采用 **核技巧** 使得在高维度特征空间的计算更有效率 - -假设数据是线性可分的,那么一定存在两条平行的直线分别经过两类样本最外侧的数据 设这两条直线是 $wx+b=1$ 、$wx+b=-1$ 设置是 ±1 没有什么特殊意义 主要是为了方便表示间距margin - -在这个设置下 相当于学习一个基于间隔的分类器 -![](https://cdn.jsdelivr.net/gh/zip95297/zip95297.github.io@main/source/images/%E6%9C%BA%E5%99%A8%E5%AD%A6%E4%B9%A0%E5%A4%8D%E4%B9%A0%E8%B5%84%E6%96%99/Pasted%20image%2020250426204559.webp?raw=true) -> 为什么要写成 $\frac{1}{2}||w||^2$ ? - 主要是为了简化下面的计算,如果写成 $||w||$ 在计算梯度时候会出现 $\sqrt{w^Tw}$ - -### 对偶问题 - -在解决这个问题时候 使用 拉格朗日乘子法 将其转化为对偶问题: -1. 引入拉格朗日乘子 $\alpha_i \ge 0$ 得到拉格朗日函数 -$$ -L(w,b,\alpha)=\frac{1}{2}||w||^2-\sum^m_{i=1}\alpha_i(y_i(w^Tx_i+b)-1) -$$ - -2. 令 $L(w,b,\alpha)$ 对 $w$ 和 $b$ 的偏导数为0可得: -$$ -w=\sum^m_{i=1}\alpha_iy_ix_i\ \ ,\ \sum^m_{i=1}\alpha_iy_i=0 -$$ - -3. 回代: ![](https://cdn.jsdelivr.net/gh/zip95297/zip95297.github.io@main/source/images/%E6%9C%BA%E5%99%A8%E5%AD%A6%E4%B9%A0%E5%A4%8D%E4%B9%A0%E8%B5%84%E6%96%99/Pasted%20image%2020250427134510.webp?raw=true) - - -> 拉格朗日乘子法:通过乘子将约束引入函数得到一个无约束的函数 - ![](https://cdn.jsdelivr.net/gh/zip95297/zip95297.github.io@main/source/images/%E6%9C%BA%E5%99%A8%E5%AD%A6%E4%B9%A0%E5%A4%8D%E4%B9%A0%E8%B5%84%E6%96%99/Pasted%20image%2020250426210330.webp?raw=true) - - -支持向量就是 wx+b=±1 的向量 ![](https://cdn.jsdelivr.net/gh/zip95297/zip95297.github.io@main/source/images/%E6%9C%BA%E5%99%A8%E5%AD%A6%E4%B9%A0%E5%A4%8D%E4%B9%A0%E8%B5%84%E6%96%99/Pasted%20image%2020250427135009.webp?raw=true) - -## 核技巧 - -一般的 上面所提到的支持向量机是对于 数据线性可分 的情况 , 但是可能存在线性不可分的数据,这样就要把 x 变换到高维空间 使其在高维空间中线性可分。 - -设样本 x 映射后的向量为 $\phi(x)$ ,划分超平面是 $f(x)=w^T\phi(x)+b$ -![](https://cdn.jsdelivr.net/gh/zip95297/zip95297.github.io@main/source/images/%E6%9C%BA%E5%99%A8%E5%AD%A6%E4%B9%A0%E5%A4%8D%E4%B9%A0%E8%B5%84%E6%96%99/Pasted%20image%2020250427135943.webp?raw=true) - -也就是说 两个x的高维表示 都是内积的形式存在 不用显式的计算出来 所以引入 ***核函数*** 来代替: -$$ -\cal{K}(x_i,x_j)=\phi(x_i)^T\phi(x_j) -$$ - -核函数的优点:不需要知道和计算 $\phi(x)$ (纬度很高 计算量很大) 即使特征空间的纬度非常高,计算仍是可行的,因为 核函数 是在原空间内运算。 - -> 存在判定![](https://cdn.jsdelivr.net/gh/zip95297/zip95297.github.io@main/source/images/%E6%9C%BA%E5%99%A8%E5%AD%A6%E4%B9%A0%E5%A4%8D%E4%B9%A0%E8%B5%84%E6%96%99/Pasted%20image%2020250427143630.webp?raw=true) - -> 常用核函数 ![](https://cdn.jsdelivr.net/gh/zip95297/zip95297.github.io@main/source/images/%E6%9C%BA%E5%99%A8%E5%AD%A6%E4%B9%A0%E5%A4%8D%E4%B9%A0%E8%B5%84%E6%96%99/Pasted%20image%2020250427143846.webp?raw=true) - -> 实现非线性分类![](https://cdn.jsdelivr.net/gh/zip95297/zip95297.github.io@main/source/images/%E6%9C%BA%E5%99%A8%E5%AD%A6%E4%B9%A0%E5%A4%8D%E4%B9%A0%E8%B5%84%E6%96%99/Pasted%20image%2020250427144027.webp?raw=true) - 通过这个解出 $\alpha^*$ 后 可以得到 决策函数 $f(x)=sgn(\sum^l_{i=1}y_i\alpha^*K(x_i,x)+b^*)$ - ![](https://cdn.jsdelivr.net/gh/zip95297/zip95297.github.io@main/source/images/%E6%9C%BA%E5%99%A8%E5%AD%A6%E4%B9%A0%E5%A4%8D%E4%B9%A0%E8%B5%84%E6%96%99/Pasted%20image%2020250427144249.webp?raw=true) - - -# 第六章 集成学习 - -## 集成学习的概念和类型 - -集成学习 是通过 构建并结合多个学习器来完成学习任务,提升性能![](https://cdn.jsdelivr.net/gh/zip95297/zip95297.github.io@main/source/images/%E6%9C%BA%E5%99%A8%E5%AD%A6%E4%B9%A0%E5%A4%8D%E4%B9%A0%E8%B5%84%E6%96%99/Pasted%20image%2020250427150521.webp?raw=true) - -学习器可以是 同质的 也可以是 异质的 (个体学习器由不同的学习算法生成 成为 组件学习期) -其中的同质 个体学习器 称为 基学习器 对应的算法叫做 基学习算法 - -弱学习器:精度略高于随机 强学习器:精度较高 - -![](https://cdn.jsdelivr.net/gh/zip95297/zip95297.github.io@main/source/images/%E6%9C%BA%E5%99%A8%E5%AD%A6%E4%B9%A0%E5%A4%8D%E4%B9%A0%E8%B5%84%E6%96%99/Pasted%20image%2020250427151810.webp?raw=true)![](https://cdn.jsdelivr.net/gh/zip95297/zip95297.github.io@main/source/images/%E6%9C%BA%E5%99%A8%E5%AD%A6%E4%B9%A0%E5%A4%8D%E4%B9%A0%E8%B5%84%E6%96%99/Pasted%20image%2020250427151819.webp?raw=true) - -> 简单分析 - ![](https://cdn.jsdelivr.net/gh/zip95297/zip95297.github.io@main/source/images/%E6%9C%BA%E5%99%A8%E5%AD%A6%E4%B9%A0%E5%A4%8D%E4%B9%A0%E8%B5%84%E6%96%99/Pasted%20image%2020250427150618.webp?raw=true) - ![](https://cdn.jsdelivr.net/gh/zip95297/zip95297.github.io@main/source/images/%E6%9C%BA%E5%99%A8%E5%AD%A6%E4%B9%A0%E5%A4%8D%E4%B9%A0%E8%B5%84%E6%96%99/Pasted%20image%2020250427150622.webp?raw=true) - ![](https://cdn.jsdelivr.net/gh/zip95297/zip95297.github.io@main/source/images/%E6%9C%BA%E5%99%A8%E5%AD%A6%E4%B9%A0%E5%A4%8D%E4%B9%A0%E8%B5%84%E6%96%99/Pasted%20image%2020250427150640.webp?raw=true) - 然而我们必须注意到,上面的分析有一个关键假设:基学习器的误差相互 独立.在现实任务中,个体学习器是为解决同一个问题训练出来的,它们显然不 可能相互独立!事实上,个体学习器的“准确性”和“多样性”本身就存在冲 突.一般的,准确性很高之后,要增加多样性就需牺牲准确性,事实上,如何产 生并结合“好而不同”的个体学习器,恰是集成学习研究的核心. - -根据个体学习器的生成方式,目前的集成学习方法大致可分为两大类,即 个体学习器间存在强依赖关系、必须串行生成的序列化方法,以及个体学习器 间不存在强依赖关系、可同时生成的并行化方法;前者的代表是Boosting,后 者的代表是 Bagging 和“随机森林”(Random Forest). -## AdaBoost算法流程 - -![](https://cdn.jsdelivr.net/gh/zip95297/zip95297.github.io@main/source/images/%E6%9C%BA%E5%99%A8%E5%AD%A6%E4%B9%A0%E5%A4%8D%E4%B9%A0%E8%B5%84%E6%96%99/Pasted%20image%2020250427151909.webp?raw=true)![](https://cdn.jsdelivr.net/gh/zip95297/zip95297.github.io@main/source/images/%E6%9C%BA%E5%99%A8%E5%AD%A6%E4%B9%A0%E5%A4%8D%E4%B9%A0%E8%B5%84%E6%96%99/Pasted%20image%2020250427151924.webp?raw=true) - -![](https://cdn.jsdelivr.net/gh/zip95297/zip95297.github.io@main/source/images/%E6%9C%BA%E5%99%A8%E5%AD%A6%E4%B9%A0%E5%A4%8D%E4%B9%A0%E8%B5%84%E6%96%99/Pasted%20image%2020250427151700.webp?raw=true) -![](https://cdn.jsdelivr.net/gh/zip95297/zip95297.github.io@main/source/images/%E6%9C%BA%E5%99%A8%E5%AD%A6%E4%B9%A0%E5%A4%8D%E4%B9%A0%E8%B5%84%E6%96%99/Pasted%20image%2020250427153537.webp?raw=true) - -![](https://cdn.jsdelivr.net/gh/zip95297/zip95297.github.io@main/source/images/%E6%9C%BA%E5%99%A8%E5%AD%A6%E4%B9%A0%E5%A4%8D%E4%B9%A0%E8%B5%84%E6%96%99/Pasted%20image%2020250427153627.webp?raw=true) -![](https://cdn.jsdelivr.net/gh/zip95297/zip95297.github.io@main/source/images/%E6%9C%BA%E5%99%A8%E5%AD%A6%E4%B9%A0%E5%A4%8D%E4%B9%A0%E8%B5%84%E6%96%99/Pasted%20image%2020250427153635.webp?raw=true) -![](https://cdn.jsdelivr.net/gh/zip95297/zip95297.github.io@main/source/images/%E6%9C%BA%E5%99%A8%E5%AD%A6%E4%B9%A0%E5%A4%8D%E4%B9%A0%E8%B5%84%E6%96%99/Pasted%20image%2020250427153643.webp?raw=true) -![](https://cdn.jsdelivr.net/gh/zip95297/zip95297.github.io@main/source/images/%E6%9C%BA%E5%99%A8%E5%AD%A6%E4%B9%A0%E5%A4%8D%E4%B9%A0%E8%B5%84%E6%96%99/Pasted%20image%2020250427153648.webp?raw=true) -这个 $Z_m$ 其实就是对 $w_{mi}e^{\alpha_m}$ 做softmax 让下次的模型更注重错误的样本 -![](https://cdn.jsdelivr.net/gh/zip95297/zip95297.github.io@main/source/images/%E6%9C%BA%E5%99%A8%E5%AD%A6%E4%B9%A0%E5%A4%8D%E4%B9%A0%E8%B5%84%E6%96%99/Pasted%20image%2020250427154804.webp?raw=true) - -### 例子 - -![](https://cdn.jsdelivr.net/gh/zip95297/zip95297.github.io@main/source/images/%E6%9C%BA%E5%99%A8%E5%AD%A6%E4%B9%A0%E5%A4%8D%E4%B9%A0%E8%B5%84%E6%96%99/Pasted%20image%2020250427153858.webp?raw=true) -![](https://cdn.jsdelivr.net/gh/zip95297/zip95297.github.io@main/source/images/%E6%9C%BA%E5%99%A8%E5%AD%A6%E4%B9%A0%E5%A4%8D%E4%B9%A0%E8%B5%84%E6%96%99/Pasted%20image%2020250427153906.webp?raw=true) -![](https://cdn.jsdelivr.net/gh/zip95297/zip95297.github.io@main/source/images/%E6%9C%BA%E5%99%A8%E5%AD%A6%E4%B9%A0%E5%A4%8D%E4%B9%A0%E8%B5%84%E6%96%99/Pasted%20image%2020250427153933.webp?raw=true) -![](https://cdn.jsdelivr.net/gh/zip95297/zip95297.github.io@main/source/images/%E6%9C%BA%E5%99%A8%E5%AD%A6%E4%B9%A0%E5%A4%8D%E4%B9%A0%E8%B5%84%E6%96%99/Pasted%20image%2020250427153939.webp?raw=true) -![](https://cdn.jsdelivr.net/gh/zip95297/zip95297.github.io@main/source/images/%E6%9C%BA%E5%99%A8%E5%AD%A6%E4%B9%A0%E5%A4%8D%E4%B9%A0%E8%B5%84%E6%96%99/Pasted%20image%2020250427154142.webp?raw=true) - -## Bagging算法流程 - -![](https://cdn.jsdelivr.net/gh/zip95297/zip95297.github.io@main/source/images/%E6%9C%BA%E5%99%A8%E5%AD%A6%E4%B9%A0%E5%A4%8D%E4%B9%A0%E8%B5%84%E6%96%99/Pasted%20image%2020250427155504.webp?raw=true) -欲得到泛化性能强的集成,集成中的个体学习器应尽可能相 互独立;虽然“独立”在现实任务中无法做到,但可以设法使基学习器尽可能 具有较大的差异.给定一个训练数据集,一种可能的做法是对训练样本进行采 样,产生出若干个不同的子集,再从每个数据子集中训练出一个基学习器.这 样,由于训练数据不同,我们获得的基学习器可望具有比较大的差异.然而,为 获得好的集成,我们同时还希望个体学习器不能太差.如果采样出的每个子集 都完全不同,则每个基学习器只用到了一小部分训练数据,甚至不足以进行有 效学习,这显然无法确保产生出比较好的基学习器.为解决这个问题,我们可考 虑使用相互有交叠的采样子集. - -![](https://cdn.jsdelivr.net/gh/zip95297/zip95297.github.io@main/source/images/%E6%9C%BA%E5%99%A8%E5%AD%A6%E4%B9%A0%E5%A4%8D%E4%B9%A0%E8%B5%84%E6%96%99/Pasted%20image%2020250427155736.webp?raw=true) -大I 是指数函数 在里面为真假时取1,0 -对预测输出进行结合时,通常对分类任务使用简单投票法,对于回归问题使用简单平均法。 -![](https://cdn.jsdelivr.net/gh/zip95297/zip95297.github.io@main/source/images/%E6%9C%BA%E5%99%A8%E5%AD%A6%E4%B9%A0%E5%A4%8D%E4%B9%A0%E8%B5%84%E6%96%99/Pasted%20image%2020250427160408.webp?raw=true) - - ->获取每个基学习器的训练集方法![](https://cdn.jsdelivr.net/gh/zip95297/zip95297.github.io@main/source/images/%E6%9C%BA%E5%99%A8%E5%AD%A6%E4%B9%A0%E5%A4%8D%E4%B9%A0%E8%B5%84%E6%96%99/Pasted%20image%2020250427160230.webp?raw=true) - 包外估计 用 训练集 不包括x的对x做预测![](https://cdn.jsdelivr.net/gh/zip95297/zip95297.github.io@main/source/images/%E6%9C%BA%E5%99%A8%E5%AD%A6%E4%B9%A0%E5%A4%8D%E4%B9%A0%E8%B5%84%E6%96%99/Pasted%20image%2020250427160456.webp?raw=true) - - -# 第七章 聚类方法 - -> 无监督学习 - 是机器学习的一种范式,其核心特点是模型从**未标注的数据**中自动发现隐藏的模式、结构或规律,而无需人工提供标签(即没有明确的“正确答案”作为监督信号)。与监督学习不同,无监督学习的目标是探索数据本身的内在特性。 - - 模型: 函数 $z=g_\theta(x)$ , 条件概率分布 $P_\theta(z|x)$ 或者条件概率分布 $P_\theta(x|z)$ - - 策略:目标函数优化 - - 算法: 迭代算法,通过迭代达到对目标函数的最优化 - -> 聚类 - 是将样本集合中相似的样本(实例)分配到相同的类,不相似的样本分配到不同的类。 聚类时,样本通常是欧氏空间中的向量,类别不是事先给定,而 是从数据中自动发现,但类别的个数通常是事先给定的。样本之间的相似度或距离由应用决定。 ·如果一个样本只能属于一个类,则称为硬聚类(hard clustering),如果一个样本可以属于多个类,则称软聚类(soft clustering)。 - 聚类算法有 原型(k均值、高斯混合模型)、密度、层次、谱聚类 - -## K均值算法流程以及特点 - -K均值聚类是基于样本集合划分的聚类算法。将样本集合划分为k个子集,构成k个类,将n个样本分到k个类别中,每个样本到其所属类的中心距离最小。硬聚类算法,每个样本只有一个类。 -![](https://cdn.jsdelivr.net/gh/zip95297/zip95297.github.io@main/source/images/%E6%9C%BA%E5%99%A8%E5%AD%A6%E4%B9%A0%E5%A4%8D%E4%B9%A0%E8%B5%84%E6%96%99/Pasted%20image%2020250427165900.webp?raw=true) -![](https://cdn.jsdelivr.net/gh/zip95297/zip95297.github.io@main/source/images/%E6%9C%BA%E5%99%A8%E5%AD%A6%E4%B9%A0%E5%A4%8D%E4%B9%A0%E8%B5%84%E6%96%99/Pasted%20image%2020250427170505.webp?raw=true) -### \_K均值划分策略 - -![](https://cdn.jsdelivr.net/gh/zip95297/zip95297.github.io@main/source/images/%E6%9C%BA%E5%99%A8%E5%AD%A6%E4%B9%A0%E5%A4%8D%E4%B9%A0%E8%B5%84%E6%96%99/Pasted%20image%2020250427170031.webp?raw=true) -![](https://cdn.jsdelivr.net/gh/zip95297/zip95297.github.io@main/source/images/%E6%9C%BA%E5%99%A8%E5%AD%A6%E4%B9%A0%E5%A4%8D%E4%B9%A0%E8%B5%84%E6%96%99/Pasted%20image%2020250427170044.webp?raw=true) -![](https://cdn.jsdelivr.net/gh/zip95297/zip95297.github.io@main/source/images/%E6%9C%BA%E5%99%A8%E5%AD%A6%E4%B9%A0%E5%A4%8D%E4%B9%A0%E8%B5%84%E6%96%99/Pasted%20image%2020250427170137.webp?raw=true) - - - - -### K均值算法实现 - -![](https://cdn.jsdelivr.net/gh/zip95297/zip95297.github.io@main/source/images/%E6%9C%BA%E5%99%A8%E5%AD%A6%E4%B9%A0%E5%A4%8D%E4%B9%A0%E8%B5%84%E6%96%99/Pasted%20image%2020250427170239.webp?raw=true) -![](https://cdn.jsdelivr.net/gh/zip95297/zip95297.github.io@main/source/images/%E6%9C%BA%E5%99%A8%E5%AD%A6%E4%B9%A0%E5%A4%8D%E4%B9%A0%E8%B5%84%E6%96%99/Pasted%20image%2020250427170247.webp?raw=true) - - - - -### K均值算法特点 - -![](https://cdn.jsdelivr.net/gh/zip95297/zip95297.github.io@main/source/images/%E6%9C%BA%E5%99%A8%E5%AD%A6%E4%B9%A0%E5%A4%8D%E4%B9%A0%E8%B5%84%E6%96%99/Pasted%20image%2020250427170404.webp?raw=true) -![](https://cdn.jsdelivr.net/gh/zip95297/zip95297.github.io@main/source/images/%E6%9C%BA%E5%99%A8%E5%AD%A6%E4%B9%A0%E5%A4%8D%E4%B9%A0%E8%B5%84%E6%96%99/Pasted%20image%2020250427170411.webp?raw=true) -![](https://cdn.jsdelivr.net/gh/zip95297/zip95297.github.io@main/source/images/%E6%9C%BA%E5%99%A8%E5%AD%A6%E4%B9%A0%E5%A4%8D%E4%B9%A0%E8%B5%84%E6%96%99/Pasted%20image%2020250427170436.webp?raw=true) -![](https://cdn.jsdelivr.net/gh/zip95297/zip95297.github.io@main/source/images/%E6%9C%BA%E5%99%A8%E5%AD%A6%E4%B9%A0%E5%A4%8D%E4%B9%A0%E8%B5%84%E6%96%99/Pasted%20image%2020250427170554.webp?raw=true) - - - -# 第八章 混合模型与期望最大 - -## EM算法流程、收敛性以及应用 - -> DEF: - 最大期望算法(Expectation-maximization algorithm,又译为期望最大化算法),是在概率模型中寻找参数最大似然估计或者最大后验估计的算法,其中概率模型依赖于无法观测的隐性变量。最大期望算法经过两个步骤交替进行计算,==**第一步**==是计算期望(E),利用对隐藏变量的现有估计值,计算其最大似然估计值;==**第二步**==是最大化(M),最大化在E步上求得的最大似然值来计算参数的值。M步上找到的参数估计值被用于下一个E步计算中,这个过程不断交替进行。 - ->举个例子: - 如果要统计一个学校男女生身高体重的分布,男生的分布和女生肯定是两个不同的分布,但是如果数据中只有身高体重Y 并未标注 男生女生Z 那么如果要找到男生女生的分布就 比较困难, EM算法就是为了解决这种问题 - -> 理解: - 通过 估计的 隐变量 的先验 计算 所有观测数据Y 的 似然概率 然后用 最大似然 得到 每条Y 最有可能对应的 Z 的值,然后 根据估计过后数据 更新 Z 的估计先验 重复迭代这个过程 (实际上这个Z不是一个常量 而是一个随机变量)![](https://cdn.jsdelivr.net/gh/zip95297/zip95297.github.io@main/source/images/%E6%9C%BA%E5%99%A8%E5%AD%A6%E4%B9%A0%E5%A4%8D%E4%B9%A0%E8%B5%84%E6%96%99/Pasted%20image%2020250427173617.webp?raw=true) - -### EM算法流程 - -![](https://cdn.jsdelivr.net/gh/zip95297/zip95297.github.io@main/source/images/%E6%9C%BA%E5%99%A8%E5%AD%A6%E4%B9%A0%E5%A4%8D%E4%B9%A0%E8%B5%84%E6%96%99/Pasted%20image%2020250427174902.webp?raw=true) -![](https://cdn.jsdelivr.net/gh/zip95297/zip95297.github.io@main/source/images/%E6%9C%BA%E5%99%A8%E5%AD%A6%E4%B9%A0%E5%A4%8D%E4%B9%A0%E8%B5%84%E6%96%99/Pasted%20image%2020250427174911.webp?raw=true) - - -### \_EM特点 - -![](https://cdn.jsdelivr.net/gh/zip95297/zip95297.github.io@main/source/images/%E6%9C%BA%E5%99%A8%E5%AD%A6%E4%B9%A0%E5%A4%8D%E4%B9%A0%E8%B5%84%E6%96%99/Pasted%20image%2020250427174444.webp?raw=true) - -### EM算法的收敛性 - -![](https://cdn.jsdelivr.net/gh/zip95297/zip95297.github.io@main/source/images/%E6%9C%BA%E5%99%A8%E5%AD%A6%E4%B9%A0%E5%A4%8D%E4%B9%A0%E8%B5%84%E6%96%99/Pasted%20image%2020250427175204.webp?raw=true) -![](https://cdn.jsdelivr.net/gh/zip95297/zip95297.github.io@main/source/images/%E6%9C%BA%E5%99%A8%E5%AD%A6%E4%B9%A0%E5%A4%8D%E4%B9%A0%E8%B5%84%E6%96%99/Pasted%20image%2020250427175209.webp?raw=true) -![](https://cdn.jsdelivr.net/gh/zip95297/zip95297.github.io@main/source/images/%E6%9C%BA%E5%99%A8%E5%AD%A6%E4%B9%A0%E5%A4%8D%E4%B9%A0%E8%B5%84%E6%96%99/Pasted%20image%2020250427175215.webp?raw=true) -![](https://cdn.jsdelivr.net/gh/zip95297/zip95297.github.io@main/source/images/%E6%9C%BA%E5%99%A8%E5%AD%A6%E4%B9%A0%E5%A4%8D%E4%B9%A0%E8%B5%84%E6%96%99/Pasted%20image%2020250427175312.webp?raw=true) -![](https://cdn.jsdelivr.net/gh/zip95297/zip95297.github.io@main/source/images/%E6%9C%BA%E5%99%A8%E5%AD%A6%E4%B9%A0%E5%A4%8D%E4%B9%A0%E8%B5%84%E6%96%99/Pasted%20image%2020250427175359.webp?raw=true) - - - - -### EM算法的应用 - -- EM算法在⾼斯混合模型学习 -- EM在伯努利混合模型上的应用 - -## 混合高斯模型GMM的原理和特点 - -![](https://cdn.jsdelivr.net/gh/zip95297/zip95297.github.io@main/source/images/%E6%9C%BA%E5%99%A8%E5%AD%A6%E4%B9%A0%E5%A4%8D%E4%B9%A0%E8%B5%84%E6%96%99/Pasted%20image%2020250427180153.webp?raw=true) -![](https://cdn.jsdelivr.net/gh/zip95297/zip95297.github.io@main/source/images/%E6%9C%BA%E5%99%A8%E5%AD%A6%E4%B9%A0%E5%A4%8D%E4%B9%A0%E8%B5%84%E6%96%99/Pasted%20image%2020250427180223.webp?raw=true) -![](https://cdn.jsdelivr.net/gh/zip95297/zip95297.github.io@main/source/images/%E6%9C%BA%E5%99%A8%E5%AD%A6%E4%B9%A0%E5%A4%8D%E4%B9%A0%E8%B5%84%E6%96%99/Pasted%20image%2020250427180236.webp?raw=true) -![](https://cdn.jsdelivr.net/gh/zip95297/zip95297.github.io@main/source/images/%E6%9C%BA%E5%99%A8%E5%AD%A6%E4%B9%A0%E5%A4%8D%E4%B9%A0%E8%B5%84%E6%96%99/Pasted%20image%2020250427180243.webp?raw=true) -![](https://cdn.jsdelivr.net/gh/zip95297/zip95297.github.io@main/source/images/%E6%9C%BA%E5%99%A8%E5%AD%A6%E4%B9%A0%E5%A4%8D%E4%B9%A0%E8%B5%84%E6%96%99/Pasted%20image%2020250427180250.webp?raw=true) -![](https://cdn.jsdelivr.net/gh/zip95297/zip95297.github.io@main/source/images/%E6%9C%BA%E5%99%A8%E5%AD%A6%E4%B9%A0%E5%A4%8D%E4%B9%A0%E8%B5%84%E6%96%99/Pasted%20image%2020250427180259.webp?raw=true) -![](https://cdn.jsdelivr.net/gh/zip95297/zip95297.github.io@main/source/images/%E6%9C%BA%E5%99%A8%E5%AD%A6%E4%B9%A0%E5%A4%8D%E4%B9%A0%E8%B5%84%E6%96%99/Pasted%20image%2020250427180306.webp?raw=true) - -> PPT: - ![](https://cdn.jsdelivr.net/gh/zip95297/zip95297.github.io@main/source/images/%E6%9C%BA%E5%99%A8%E5%AD%A6%E4%B9%A0%E5%A4%8D%E4%B9%A0%E8%B5%84%E6%96%99/Pasted%20image%2020250427191301.webp?raw=true) - ![](https://cdn.jsdelivr.net/gh/zip95297/zip95297.github.io@main/source/images/%E6%9C%BA%E5%99%A8%E5%AD%A6%E4%B9%A0%E5%A4%8D%E4%B9%A0%E8%B5%84%E6%96%99/Pasted%20image%2020250427191307.webp?raw=true) - ![](https://cdn.jsdelivr.net/gh/zip95297/zip95297.github.io@main/source/images/%E6%9C%BA%E5%99%A8%E5%AD%A6%E4%B9%A0%E5%A4%8D%E4%B9%A0%E8%B5%84%E6%96%99/Pasted%20image%2020250427191343.webp?raw=true) - ![](https://cdn.jsdelivr.net/gh/zip95297/zip95297.github.io@main/source/images/%E6%9C%BA%E5%99%A8%E5%AD%A6%E4%B9%A0%E5%A4%8D%E4%B9%A0%E8%B5%84%E6%96%99/Pasted%20image%2020250427191349.webp?raw=true) - 具体内容找PPT - -# 第九章 神经网络与深度学习 - -## 介绍 - -> 神经网络的要素: - ![](https://cdn.jsdelivr.net/gh/zip95297/zip95297.github.io@main/source/images/%E6%9C%BA%E5%99%A8%E5%AD%A6%E4%B9%A0%E5%A4%8D%E4%B9%A0%E8%B5%84%E6%96%99/Pasted%20image%2020250427191930.webp?raw=true) - -> 网络结构: - ![](https://cdn.jsdelivr.net/gh/zip95297/zip95297.github.io@main/source/images/%E6%9C%BA%E5%99%A8%E5%AD%A6%E4%B9%A0%E5%A4%8D%E4%B9%A0%E8%B5%84%E6%96%99/Pasted%20image%2020250427193810.webp?raw=true) - -## 前馈神经网络、梯度下降算法、BP反向传播算法流程 - -### 前馈神经网络(全连接神经网络,MLP) - -整个网络中没有反馈 全部是正向连接 -![](https://cdn.jsdelivr.net/gh/zip95297/zip95297.github.io@main/source/images/%E6%9C%BA%E5%99%A8%E5%AD%A6%E4%B9%A0%E5%A4%8D%E4%B9%A0%E8%B5%84%E6%96%99/Pasted%20image%2020250427193836.webp?raw=true) -![](https://cdn.jsdelivr.net/gh/zip95297/zip95297.github.io@main/source/images/%E6%9C%BA%E5%99%A8%E5%AD%A6%E4%B9%A0%E5%A4%8D%E4%B9%A0%E8%B5%84%E6%96%99/Pasted%20image%2020250427203930.webp?raw=true) -![](https://cdn.jsdelivr.net/gh/zip95297/zip95297.github.io@main/source/images/%E6%9C%BA%E5%99%A8%E5%AD%A6%E4%B9%A0%E5%A4%8D%E4%B9%A0%E8%B5%84%E6%96%99/Pasted%20image%2020250427204015.webp?raw=true) -全连接神经网络的缺点 -![](https://cdn.jsdelivr.net/gh/zip95297/zip95297.github.io@main/source/images/%E6%9C%BA%E5%99%A8%E5%AD%A6%E4%B9%A0%E5%A4%8D%E4%B9%A0%E8%B5%84%E6%96%99/Pasted%20image%2020250428164432.webp?raw=true) -![](https://cdn.jsdelivr.net/gh/zip95297/zip95297.github.io@main/source/images/%E6%9C%BA%E5%99%A8%E5%AD%A6%E4%B9%A0%E5%A4%8D%E4%B9%A0%E8%B5%84%E6%96%99/Pasted%20image%2020250428164436.webp?raw=true) - - -### BP反向传播算法 - -对每个权重求梯度 - -> 构造一个两层的Net - ![](https://cdn.jsdelivr.net/gh/zip95297/zip95297.github.io@main/source/images/%E6%9C%BA%E5%99%A8%E5%AD%A6%E4%B9%A0%E5%A4%8D%E4%B9%A0%E8%B5%84%E6%96%99/Pasted%20image%2020250427211113.webp?raw=true)![](https://cdn.jsdelivr.net/gh/zip95297/zip95297.github.io@main/source/images/%E6%9C%BA%E5%99%A8%E5%AD%A6%E4%B9%A0%E5%A4%8D%E4%B9%A0%E8%B5%84%E6%96%99/Pasted%20image%2020250427211120.webp?raw=true)![](https://cdn.jsdelivr.net/gh/zip95297/zip95297.github.io@main/source/images/%E6%9C%BA%E5%99%A8%E5%AD%A6%E4%B9%A0%E5%A4%8D%E4%B9%A0%E8%B5%84%E6%96%99/Pasted%20image%2020250427211125.webp?raw=true)![](https://cdn.jsdelivr.net/gh/zip95297/zip95297.github.io@main/source/images/%E6%9C%BA%E5%99%A8%E5%AD%A6%E4%B9%A0%E5%A4%8D%E4%B9%A0%E8%B5%84%E6%96%99/Pasted%20image%2020250427211305.webp?raw=true)![](https://cdn.jsdelivr.net/gh/zip95297/zip95297.github.io@main/source/images/%E6%9C%BA%E5%99%A8%E5%AD%A6%E4%B9%A0%E5%A4%8D%E4%B9%A0%E8%B5%84%E6%96%99/Pasted%20image%2020250427211142.webp?raw=true)![](https://cdn.jsdelivr.net/gh/zip95297/zip95297.github.io@main/source/images/%E6%9C%BA%E5%99%A8%E5%AD%A6%E4%B9%A0%E5%A4%8D%E4%B9%A0%E8%B5%84%E6%96%99/Pasted%20image%2020250427211244.webp?raw=true) -#### BP算法原理 -> 前向过程![](https://cdn.jsdelivr.net/gh/zip95297/zip95297.github.io@main/source/images/%E6%9C%BA%E5%99%A8%E5%AD%A6%E4%B9%A0%E5%A4%8D%E4%B9%A0%E8%B5%84%E6%96%99/Pasted%20image%2020250427211113.webp?raw=true)![](https://cdn.jsdelivr.net/gh/zip95297/zip95297.github.io@main/source/images/%E6%9C%BA%E5%99%A8%E5%AD%A6%E4%B9%A0%E5%A4%8D%E4%B9%A0%E8%B5%84%E6%96%99/Pasted%20image%2020250427211142.webp?raw=true) - -在上面正向传播过程中,loss对wij的梯度为: -![](https://cdn.jsdelivr.net/gh/zip95297/zip95297.github.io@main/source/images/%E6%9C%BA%E5%99%A8%E5%AD%A6%E4%B9%A0%E5%A4%8D%E4%B9%A0%E8%B5%84%E6%96%99/Pasted%20image%2020250427211353.webp?raw=true) -![](https://cdn.jsdelivr.net/gh/zip95297/zip95297.github.io@main/source/images/%E6%9C%BA%E5%99%A8%E5%AD%A6%E4%B9%A0%E5%A4%8D%E4%B9%A0%E8%B5%84%E6%96%99/Pasted%20image%2020250427212939.webp?raw=true) -也就是 E 对一个权重 第J层第I个w 的梯度: -$$ -\cfrac{\partial E_n}{\partial w_{ji}}=\cfrac{\partial E_n}{\partial a_{j}}\cfrac{\partial a_j}{\partial w_{ji}} -$$ - -???为什么不是这种形式??如果relu的斜率是1 上面下面基本相等了 -$$ -\cfrac{\partial E_n}{\partial w_{ji}}=\cfrac{\partial E_n}{\partial z_{j}}\cfrac{\partial z_j}{\partial a_j}\cfrac{\partial a_j}{\partial w_{ji}} -$$ - - -| 符号 | 意义 | 举例 | -| --- | -------- | ----------- | -| a | 还没激活的加权和 | $w×x+b$ | -| z | 激活函数后的输出 | $\sigma(z)$ | - -![](https://cdn.jsdelivr.net/gh/zip95297/zip95297.github.io@main/source/images/%E6%9C%BA%E5%99%A8%E5%AD%A6%E4%B9%A0%E5%A4%8D%E4%B9%A0%E8%B5%84%E6%96%99/Pasted%20image%2020250427213023.webp?raw=true) -![](https://cdn.jsdelivr.net/gh/zip95297/zip95297.github.io@main/source/images/%E6%9C%BA%E5%99%A8%E5%AD%A6%E4%B9%A0%E5%A4%8D%E4%B9%A0%E8%B5%84%E6%96%99/Pasted%20image%2020250427213046.webp?raw=true) -![](https://cdn.jsdelivr.net/gh/zip95297/zip95297.github.io@main/source/images/%E6%9C%BA%E5%99%A8%E5%AD%A6%E4%B9%A0%E5%A4%8D%E4%B9%A0%E8%B5%84%E6%96%99/Pasted%20image%2020250427213052.webp?raw=true) -![](https://cdn.jsdelivr.net/gh/zip95297/zip95297.github.io@main/source/images/%E6%9C%BA%E5%99%A8%E5%AD%A6%E4%B9%A0%E5%A4%8D%E4%B9%A0%E8%B5%84%E6%96%99/Pasted%20image%2020250427213058.webp?raw=true) - -#### BP算法示例 - -![](https://cdn.jsdelivr.net/gh/zip95297/zip95297.github.io@main/source/images/%E6%9C%BA%E5%99%A8%E5%AD%A6%E4%B9%A0%E5%A4%8D%E4%B9%A0%E8%B5%84%E6%96%99/Pasted%20image%2020250427213347.webp?raw=true) -![](https://cdn.jsdelivr.net/gh/zip95297/zip95297.github.io@main/source/images/%E6%9C%BA%E5%99%A8%E5%AD%A6%E4%B9%A0%E5%A4%8D%E4%B9%A0%E8%B5%84%E6%96%99/Pasted%20image%2020250427213352.webp?raw=true) -![](https://cdn.jsdelivr.net/gh/zip95297/zip95297.github.io@main/source/images/%E6%9C%BA%E5%99%A8%E5%AD%A6%E4%B9%A0%E5%A4%8D%E4%B9%A0%E8%B5%84%E6%96%99/Pasted%20image%2020250427213501.webp?raw=true) -![](https://cdn.jsdelivr.net/gh/zip95297/zip95297.github.io@main/source/images/%E6%9C%BA%E5%99%A8%E5%AD%A6%E4%B9%A0%E5%A4%8D%E4%B9%A0%E8%B5%84%E6%96%99/Pasted%20image%2020250427213547.webp?raw=true) -![](https://cdn.jsdelivr.net/gh/zip95297/zip95297.github.io@main/source/images/%E6%9C%BA%E5%99%A8%E5%AD%A6%E4%B9%A0%E5%A4%8D%E4%B9%A0%E8%B5%84%E6%96%99/Pasted%20image%2020250427213551.webp?raw=true) -![](https://cdn.jsdelivr.net/gh/zip95297/zip95297.github.io@main/source/images/%E6%9C%BA%E5%99%A8%E5%AD%A6%E4%B9%A0%E5%A4%8D%E4%B9%A0%E8%B5%84%E6%96%99/Pasted%20image%2020250427213600.webp?raw=true) - - - -### 梯度下降算法 - -李航 附录A 常见的梯度下降算法在 第一章 三要素中 - -## CNN 基本结构,卷积、池化的计算、常用的激活函数 - -### CNN基本结构 - -上面提到了全链接前馈神经网络的缺点:参数多、局部不变性特征难以提取、展开丢失空间信息、参数多容易过拟合 - -所以提出了卷积神经网络 CNN 也是一种 前馈神经网络 受生物学上的 感受野机制启发。在结构上有三个**特点**: **局部感知、权重共享、空间或者时间上的下采样** - -> 在视觉神经系统中 一个神经元的感受野 是 指视网膜上的特定区域,只有在这个区域内的刺激才能激活改神经元。 - -全连接层,导致的参数量过大,浪费资源而且没有这么多的训练数据 -局部连接层,在位置相同的地方数据类似(人脸识别)参数量小一些 -**卷积层**,不同位置共享权重,通过可学习的卷积核进行卷积 - -![](https://cdn.jsdelivr.net/gh/zip95297/zip95297.github.io@main/source/images/%E6%9C%BA%E5%99%A8%E5%AD%A6%E4%B9%A0%E5%A4%8D%E4%B9%A0%E8%B5%84%E6%96%99/Pasted%20image%2020250428172619.webp?raw=true) -这个里面的汇聚层是 池化 - -![](https://cdn.jsdelivr.net/gh/zip95297/zip95297.github.io@main/source/images/%E6%9C%BA%E5%99%A8%E5%AD%A6%E4%B9%A0%E5%A4%8D%E4%B9%A0%E8%B5%84%E6%96%99/Pasted%20image%2020250428172637.webp?raw=true) - - -### 卷积操作 Convolution - -![](https://cdn.jsdelivr.net/gh/zip95297/zip95297.github.io@main/source/images/%E6%9C%BA%E5%99%A8%E5%AD%A6%E4%B9%A0%E5%A4%8D%E4%B9%A0%E8%B5%84%E6%96%99/Pasted%20image%2020250428170259.webp?raw=true) -![](https://cdn.jsdelivr.net/gh/zip95297/zip95297.github.io@main/source/images/%E6%9C%BA%E5%99%A8%E5%AD%A6%E4%B9%A0%E5%A4%8D%E4%B9%A0%E8%B5%84%E6%96%99/Pasted%20image%2020250428170324.webp?raw=true) -padding就是在外面对图像进行补0操作 stride就是kernel移动的步长是多少。 -![](https://cdn.jsdelivr.net/gh/zip95297/zip95297.github.io@main/source/images/%E6%9C%BA%E5%99%A8%E5%AD%A6%E4%B9%A0%E5%A4%8D%E4%B9%A0%E8%B5%84%E6%96%99/Pasted%20image%2020250428170446.webp?raw=true) - -一个卷积核卷出来一层 有多少个卷积核 卷完后通道就是多少 每个卷积核的通道数应该与输入特征图的通道数对齐 -#### 卷积后feature map的size - -对于给定的输入尺寸 $size:[H,W]$,填充 $padding \in \{true, false,Valid,Same\}$,步长 $stride = s$,卷积核大小为 $(k, k)$,输出尺寸 $size_O$可以通过以下公式计算: - -##### 1. 没有填充的情况 (padding = false): - -$size_O = \left[ \left\lfloor \cfrac{H - k}{s} + 1 \right\rfloor, \left\lfloor \cfrac{W - k}{s} + 1 \right\rfloor \right]$ - -##### 2. 有填充的情况 (padding = true): - -对于填充,通常我们使用"same"填充来保证输出尺寸与输入尺寸相同。在这种情况下,填充的大小为: - -$padding_H = \left\lceil \cfrac{k - 1}{2} \right\rceil, \quad padding_W = \left\lceil \cfrac{k - 1}{2} \right\rceil$ - -然后输出尺寸为: - -$size\_O = \left[ \left\lfloor \cfrac{H + 2 \times padding_H - k}{s} + 1 \right\rfloor, \left\lfloor \cfrac{W + 2 \times padding_W - k}{s} + 1 \right\rfloor \right]$ - -这就是计算输出尺寸 $size_O$ 的方法。 - - -### 池化 Pooling Layer - -``` -如果我们想让“眼睛检测器”这个滤波器(例如卷积神经网络中的卷积核)对于眼睛的**精确位置**不那么敏感,可以通过 **池化pooling** 操作来提高检测的鲁棒性。 - -1. **眼睛检测器(filter)**:我们假设滤波器的目的是检测图像中的“眼睛”特征。当滤波器在图像中滑动时,它会响应图像中眼睛特征的位置并给出一定的响应值。 - -2. **精确位置问题**:眼睛可能出现在图像的不同位置,或者可能由于某些因素(如图像旋转、缩放等)导致眼睛的相对位置发生变化。如果我们直接使用卷积操作,滤波器的响应可能只在特定位置对眼睛有效,但对于其他位置就不适用了,这样就无法达到“位置不敏感”的效果。 - -3. **池化(Pooling)操作**:池化操作(例如**最大池化(Max Pooling)**)可以帮助解决这个问题。它的工作原理是:在特定区域(如 \(2 \times 2\) 区域)内,池化操作选择最大的响应值(如最大池化),而不是关心具体的精确位置。通过这种方式,无论眼睛在图像的哪个位置,池化层都会保留局部区域内的最强响应,从而使模型对眼睛的**空间位置变化**更加**鲁棒**。 - -假设我们用一个 \(3 \times 3\) 的滤波器来检测眼睛,滤波器响应在图像某个位置给出了一个高值,但如果眼睛稍微移动,响应值可能会变得很低。如果我们在多个位置应用池化操作,池化层会选取该区域中的最大响应值。这样,即使眼睛的位置发生变化,池化后的响应值仍然较为稳定。 -``` - -![](https://cdn.jsdelivr.net/gh/zip95297/zip95297.github.io@main/source/images/%E6%9C%BA%E5%99%A8%E5%AD%A6%E4%B9%A0%E5%A4%8D%E4%B9%A0%E8%B5%84%E6%96%99/Pasted%20image%2020250428172157.webp?raw=true) -不在关注具体在哪个像素 而是知道在哪个大概区域 - -![](https://cdn.jsdelivr.net/gh/zip95297/zip95297.github.io@main/source/images/%E6%9C%BA%E5%99%A8%E5%AD%A6%E4%B9%A0%E5%A4%8D%E4%B9%A0%E8%B5%84%E6%96%99/Pasted%20image%2020250428172317.webp?raw=true) -![](https://cdn.jsdelivr.net/gh/zip95297/zip95297.github.io@main/source/images/%E6%9C%BA%E5%99%A8%E5%AD%A6%E4%B9%A0%E5%A4%8D%E4%B9%A0%E8%B5%84%E6%96%99/Pasted%20image%2020250428172443.webp?raw=true) - - -### 常用的激活函数 - -![](https://cdn.jsdelivr.net/gh/zip95297/zip95297.github.io@main/source/images/%E6%9C%BA%E5%99%A8%E5%AD%A6%E4%B9%A0%E5%A4%8D%E4%B9%A0%E8%B5%84%E6%96%99/Pasted%20image%2020250428173021.webp?raw=true) -![](https://cdn.jsdelivr.net/gh/zip95297/zip95297.github.io@main/source/images/%E6%9C%BA%E5%99%A8%E5%AD%A6%E4%B9%A0%E5%A4%8D%E4%B9%A0%E8%B5%84%E6%96%99/Pasted%20image%2020250428173026.webp?raw=true) -![](https://cdn.jsdelivr.net/gh/zip95297/zip95297.github.io@main/source/images/%E6%9C%BA%E5%99%A8%E5%AD%A6%E4%B9%A0%E5%A4%8D%E4%B9%A0%E8%B5%84%E6%96%99/Pasted%20image%2020250428173033.webp?raw=true) - - -## RNN基本结构以及存在的问题、LSTM的基本结构 - -### RNN 循环神经网络 - -由于卷积网络的局限性:1.接受固定长的向量作为输入,产生固定长度的输出;2.计算的数量是固定的 - -但是有时候需要处理时序和序列的数据,接受和生成变长的输入或输出 因此引出了循环神经网络RNN (Recurrent) - -> RNN例子 - ![](https://cdn.jsdelivr.net/gh/zip95297/zip95297.github.io@main/source/images/%E6%9C%BA%E5%99%A8%E5%AD%A6%E4%B9%A0%E5%A4%8D%E4%B9%A0%E8%B5%84%E6%96%99/Pasted%20image%2020250428181633.webp?raw=true) - ![](https://cdn.jsdelivr.net/gh/zip95297/zip95297.github.io@main/source/images/%E6%9C%BA%E5%99%A8%E5%AD%A6%E4%B9%A0%E5%A4%8D%E4%B9%A0%E8%B5%84%E6%96%99/Pasted%20image%2020250428181756.webp?raw=true)同步就是 输入后输出所有对应位置 - ![](https://cdn.jsdelivr.net/gh/zip95297/zip95297.github.io@main/source/images/%E6%9C%BA%E5%99%A8%E5%AD%A6%E4%B9%A0%E5%A4%8D%E4%B9%A0%E8%B5%84%E6%96%99/Pasted%20image%2020250428181814.webp?raw=true)异步是输入后逐个输出上一个 - -#### 循环神经网络的特点 -![](https://cdn.jsdelivr.net/gh/zip95297/zip95297.github.io@main/source/images/%E6%9C%BA%E5%99%A8%E5%AD%A6%E4%B9%A0%E5%A4%8D%E4%B9%A0%E8%B5%84%E6%96%99/Pasted%20image%2020250428185603.webp?raw=true) - -#### RNN的结构 -![](https://cdn.jsdelivr.net/gh/zip95297/zip95297.github.io@main/source/images/%E6%9C%BA%E5%99%A8%E5%AD%A6%E4%B9%A0%E5%A4%8D%E4%B9%A0%E8%B5%84%E6%96%99/Pasted%20image%2020250428191046.webp?raw=true) - -#### RNN的训练算法 -RNN训练时候用BPTT算法![](https://cdn.jsdelivr.net/gh/zip95297/zip95297.github.io@main/source/images/%E6%9C%BA%E5%99%A8%E5%AD%A6%E4%B9%A0%E5%A4%8D%E4%B9%A0%E8%B5%84%E6%96%99/Pasted%20image%2020250428191151.webp?raw=true) -![](https://cdn.jsdelivr.net/gh/zip95297/zip95297.github.io@main/source/images/%E6%9C%BA%E5%99%A8%E5%AD%A6%E4%B9%A0%E5%A4%8D%E4%B9%A0%E8%B5%84%E6%96%99/Pasted%20image%2020250428191210.webp?raw=true) -![](https://cdn.jsdelivr.net/gh/zip95297/zip95297.github.io@main/source/images/%E6%9C%BA%E5%99%A8%E5%AD%A6%E4%B9%A0%E5%A4%8D%E4%B9%A0%E8%B5%84%E6%96%99/Pasted%20image%2020250428191215.webp?raw=true) - -#### RNN的长期依赖问题 - -因为上面所示 梯度反向传播中的累乘 很容易出现 梯度消失或者梯度爆炸 ,这是因为RNN在时间维度上非常深。想要改进这个问题,对于梯度爆照:需要用 权重衰减 或者 权重截断 方法 ;对于梯度消失:需要改进模型来解决 - -直观感受长期依赖![](https://cdn.jsdelivr.net/gh/zip95297/zip95297.github.io@main/source/images/%E6%9C%BA%E5%99%A8%E5%AD%A6%E4%B9%A0%E5%A4%8D%E4%B9%A0%E8%B5%84%E6%96%99/Pasted%20image%2020250428191525.webp?raw=true) - -**越早的输入影响就越小,越晚的输入影响就越大,而且这个逻辑是固定的,无法改变。** -### LSTM 长短期记忆网络 - -为了解决上面 RNN 对早晚输入注意程度不一致,而且逻辑无法改变的问题 ,引入了LSTM。 - -LSTM 保留了较长序列中的 重要信息,忽略不重要的信息。这样就解决了 RNN 的短期记忆的问题。 -![](https://cdn.jsdelivr.net/gh/zip95297/zip95297.github.io@main/source/images/%E6%9C%BA%E5%99%A8%E5%AD%A6%E4%B9%A0%E5%A4%8D%E4%B9%A0%E8%B5%84%E6%96%99/Pasted%20image%2020250428191811.webp?raw=true) - -#### LSTM 的基本结构 - -> LSTM的思想: - ![](https://cdn.jsdelivr.net/gh/zip95297/zip95297.github.io@main/source/images/%E6%9C%BA%E5%99%A8%E5%AD%A6%E4%B9%A0%E5%A4%8D%E4%B9%A0%E8%B5%84%E6%96%99/Pasted%20image%2020250428191843.webp?raw=true) - -> LSTM vs RNN : - ![](https://cdn.jsdelivr.net/gh/zip95297/zip95297.github.io@main/source/images/%E6%9C%BA%E5%99%A8%E5%AD%A6%E4%B9%A0%E5%A4%8D%E4%B9%A0%E8%B5%84%E6%96%99/Pasted%20image%2020250428191911.webp?raw=true) - ->下图中: - 指出了 LSTM(长短期记忆网络)中的以下关键部分: - 1. **Write some new cell content**(写入新的细胞状态内容) - - 对应 **输入门(Input Gate)** 的计算,决定哪些新信息会被存储到细胞状态中。 - 2. **Forget some cell content**(遗忘部分细胞状态内容) - - 对应 **遗忘门(Forget Gate)** 的计算,决定哪些旧信息会被丢弃。 - 3. **Compute the forget gate**(计算遗忘门) - - 明确提到遗忘门的操作,通过 sigmoid 函数决定保留或遗忘多少历史信息。 - 4. **Compute the input gate**(计算输入门) - - 明确提到输入门的操作,通过 sigmoid 函数决定更新哪些新信息。 - 5. **Compute the new cell content**(计算新的细胞状态内容) - - 结合输入门和候选细胞状态(通常由 tanh 函数生成),更新细胞状态。 - 6. **Compute the output gate**(计算输出门) - - 对应 **输出门(Output Gate)** 的计算,通过 sigmoid 函数决定输出哪些信息,再结合 tanh 处理的细胞状态生成最终输出。 - 7. **候选细胞状态(Candidate Cell State)**:通常由当前输入和前一隐藏状态通过 tanh 函数生成,用于后续细胞状态更新。 - 8. **隐藏状态(Hidden State)**:作为 LSTM 的输出,传递到下一时间步或网络层。 - - -![](https://cdn.jsdelivr.net/gh/zip95297/zip95297.github.io@main/source/images/%E6%9C%BA%E5%99%A8%E5%AD%A6%E4%B9%A0%E5%A4%8D%E4%B9%A0%E8%B5%84%E6%96%99/Pasted%20image%2020250428191921.webp?raw=true) -![](https://cdn.jsdelivr.net/gh/zip95297/zip95297.github.io@main/source/images/%E6%9C%BA%E5%99%A8%E5%AD%A6%E4%B9%A0%E5%A4%8D%E4%B9%A0%E8%B5%84%E6%96%99/Pasted%20image%2020250428191926.webp?raw=true) -![](https://cdn.jsdelivr.net/gh/zip95297/zip95297.github.io@main/source/images/%E6%9C%BA%E5%99%A8%E5%AD%A6%E4%B9%A0%E5%A4%8D%E4%B9%A0%E8%B5%84%E6%96%99/Pasted%20image%2020250428191933.webp?raw=true) -![](https://cdn.jsdelivr.net/gh/zip95297/zip95297.github.io@main/source/images/%E6%9C%BA%E5%99%A8%E5%AD%A6%E4%B9%A0%E5%A4%8D%E4%B9%A0%E8%B5%84%E6%96%99/Pasted%20image%2020250428191938.webp?raw=true) -![](https://cdn.jsdelivr.net/gh/zip95297/zip95297.github.io@main/source/images/%E6%9C%BA%E5%99%A8%E5%AD%A6%E4%B9%A0%E5%A4%8D%E4%B9%A0%E8%B5%84%E6%96%99/Pasted%20image%2020250428191944.webp?raw=true) -![](https://cdn.jsdelivr.net/gh/zip95297/zip95297.github.io@main/source/images/%E6%9C%BA%E5%99%A8%E5%AD%A6%E4%B9%A0%E5%A4%8D%E4%B9%A0%E8%B5%84%E6%96%99/Pasted%20image%2020250428192509.webp?raw=true) - - -## Transformer基本结构以及特点 - -### Attention - ->非自主提示 无参数汇聚(最大 平均池化) - ![](https://cdn.jsdelivr.net/gh/zip95297/zip95297.github.io@main/source/images/%E6%9C%BA%E5%99%A8%E5%AD%A6%E4%B9%A0%E5%A4%8D%E4%B9%A0%E8%B5%84%E6%96%99/Pasted%20image%2020250428204927.webp?raw=true) - -自主提示 注意力机制 - ![](https://cdn.jsdelivr.net/gh/zip95297/zip95297.github.io@main/source/images/%E6%9C%BA%E5%99%A8%E5%AD%A6%E4%B9%A0%E5%A4%8D%E4%B9%A0%E8%B5%84%E6%96%99/Pasted%20image%2020250428204949.webp?raw=true) - ![](https://cdn.jsdelivr.net/gh/zip95297/zip95297.github.io@main/source/images/%E6%9C%BA%E5%99%A8%E5%AD%A6%E4%B9%A0%E5%A4%8D%E4%B9%A0%E8%B5%84%E6%96%99/Pasted%20image%2020250428205146.webp?raw=true) - -> 软性注意力机制 value 就是自己 - ![](https://cdn.jsdelivr.net/gh/zip95297/zip95297.github.io@main/source/images/%E6%9C%BA%E5%99%A8%E5%AD%A6%E4%B9%A0%E5%A4%8D%E4%B9%A0%E8%B5%84%E6%96%99/Pasted%20image%2020250428205713.webp?raw=true) - ![](https://cdn.jsdelivr.net/gh/zip95297/zip95297.github.io@main/source/images/%E6%9C%BA%E5%99%A8%E5%AD%A6%E4%B9%A0%E5%A4%8D%E4%B9%A0%E8%B5%84%E6%96%99/Pasted%20image%2020250428205725.webp?raw=true) - -> 硬性注意力 QKV - ![](https://cdn.jsdelivr.net/gh/zip95297/zip95297.github.io@main/source/images/%E6%9C%BA%E5%99%A8%E5%AD%A6%E4%B9%A0%E5%A4%8D%E4%B9%A0%E8%B5%84%E6%96%99/Pasted%20image%2020250428205815.webp?raw=true) - -> 多头注意力 多组查询Q KV是同一个 - ![](https://cdn.jsdelivr.net/gh/zip95297/zip95297.github.io@main/source/images/%E6%9C%BA%E5%99%A8%E5%AD%A6%E4%B9%A0%E5%A4%8D%E4%B9%A0%E8%B5%84%E6%96%99/Pasted%20image%2020250428205905.webp?raw=true) - -> 自注意力 - h2 只得到了 x1 x3 x2 的信息 卷积和循环只有local![](https://cdn.jsdelivr.net/gh/zip95297/zip95297.github.io@main/source/images/%E6%9C%BA%E5%99%A8%E5%AD%A6%E4%B9%A0%E5%A4%8D%E4%B9%A0%E8%B5%84%E6%96%99/Pasted%20image%2020250428210013.webp?raw=true) - 全连接没法处理变长![](https://cdn.jsdelivr.net/gh/zip95297/zip95297.github.io@main/source/images/%E6%9C%BA%E5%99%A8%E5%AD%A6%E4%B9%A0%E5%A4%8D%E4%B9%A0%E8%B5%84%E6%96%99/Pasted%20image%2020250428210304.webp?raw=true) - 自注意力示意图:![](https://cdn.jsdelivr.net/gh/zip95297/zip95297.github.io@main/source/images/%E6%9C%BA%E5%99%A8%E5%AD%A6%E4%B9%A0%E5%A4%8D%E4%B9%A0%E8%B5%84%E6%96%99/Pasted%20image%2020250428210332.webp?raw=true) - ![](https://cdn.jsdelivr.net/gh/zip95297/zip95297.github.io@main/source/images/%E6%9C%BA%E5%99%A8%E5%AD%A6%E4%B9%A0%E5%A4%8D%E4%B9%A0%E8%B5%84%E6%96%99/Pasted%20image%2020250428210351.webp?raw=true) - ![](https://cdn.jsdelivr.net/gh/zip95297/zip95297.github.io@main/source/images/%E6%9C%BA%E5%99%A8%E5%AD%A6%E4%B9%A0%E5%A4%8D%E4%B9%A0%E8%B5%84%E6%96%99/Pasted%20image%2020250428210405.webp?raw=true) - ![](https://cdn.jsdelivr.net/gh/zip95297/zip95297.github.io@main/source/images/%E6%9C%BA%E5%99%A8%E5%AD%A6%E4%B9%A0%E5%A4%8D%E4%B9%A0%E8%B5%84%E6%96%99/Pasted%20image%2020250428210424.webp?raw=true) - -### Transformer基本结构 - -自回归模型 为了建模一个变长序列出现的概率![](https://cdn.jsdelivr.net/gh/zip95297/zip95297.github.io@main/source/images/%E6%9C%BA%E5%99%A8%E5%AD%A6%E4%B9%A0%E5%A4%8D%E4%B9%A0%E8%B5%84%E6%96%99/Pasted%20image%2020250428210627.webp?raw=true) - -![](https://cdn.jsdelivr.net/gh/zip95297/zip95297.github.io@main/source/images/%E6%9C%BA%E5%99%A8%E5%AD%A6%E4%B9%A0%E5%A4%8D%E4%B9%A0%E8%B5%84%E6%96%99/Pasted%20image%2020250428210732.webp?raw=true) -![](https://cdn.jsdelivr.net/gh/zip95297/zip95297.github.io@main/source/images/%E6%9C%BA%E5%99%A8%E5%AD%A6%E4%B9%A0%E5%A4%8D%E4%B9%A0%E8%B5%84%E6%96%99/Pasted%20image%2020250428210740.webp?raw=true) -![](https://cdn.jsdelivr.net/gh/zip95297/zip95297.github.io@main/source/images/%E6%9C%BA%E5%99%A8%E5%AD%A6%E4%B9%A0%E5%A4%8D%E4%B9%A0%E8%B5%84%E6%96%99/Pasted%20image%2020250428210852.webp?raw=true) -![](https://cdn.jsdelivr.net/gh/zip95297/zip95297.github.io@main/source/images/%E6%9C%BA%E5%99%A8%E5%AD%A6%E4%B9%A0%E5%A4%8D%E4%B9%A0%E8%B5%84%E6%96%99/Pasted%20image%2020250428210923.webp?raw=true) -![](https://cdn.jsdelivr.net/gh/zip95297/zip95297.github.io@main/source/images/%E6%9C%BA%E5%99%A8%E5%AD%A6%E4%B9%A0%E5%A4%8D%E4%B9%A0%E8%B5%84%E6%96%99/Pasted%20image%2020250428210938.webp?raw=true) -![](https://cdn.jsdelivr.net/gh/zip95297/zip95297.github.io@main/source/images/%E6%9C%BA%E5%99%A8%E5%AD%A6%E4%B9%A0%E5%A4%8D%E4%B9%A0%E8%B5%84%E6%96%99/Pasted%20image%2020250428210944.webp?raw=true) -![](https://cdn.jsdelivr.net/gh/zip95297/zip95297.github.io@main/source/images/%E6%9C%BA%E5%99%A8%E5%AD%A6%E4%B9%A0%E5%A4%8D%E4%B9%A0%E8%B5%84%E6%96%99/Pasted%20image%2020250428211153.webp?raw=true) -![](https://cdn.jsdelivr.net/gh/zip95297/zip95297.github.io@main/source/images/%E6%9C%BA%E5%99%A8%E5%AD%A6%E4%B9%A0%E5%A4%8D%E4%B9%A0%E8%B5%84%E6%96%99/Pasted%20image%2020250428211434.webp?raw=true) - -### Transformer的特点 - -![](https://cdn.jsdelivr.net/gh/zip95297/zip95297.github.io@main/source/images/%E6%9C%BA%E5%99%A8%E5%AD%A6%E4%B9%A0%E5%A4%8D%E4%B9%A0%E8%B5%84%E6%96%99/Pasted%20image%2020250428211509.webp?raw=true) -![](https://cdn.jsdelivr.net/gh/zip95297/zip95297.github.io@main/source/images/%E6%9C%BA%E5%99%A8%E5%AD%A6%E4%B9%A0%E5%A4%8D%E4%B9%A0%E8%B5%84%E6%96%99/Pasted%20image%2020250428211515.webp?raw=true) - -## 预训练语言模型BERT和GPT - -![](https://cdn.jsdelivr.net/gh/zip95297/zip95297.github.io@main/source/images/%E6%9C%BA%E5%99%A8%E5%AD%A6%E4%B9%A0%E5%A4%8D%E4%B9%A0%E8%B5%84%E6%96%99/Pasted%20image%2020250428214346.webp?raw=true) -![](https://cdn.jsdelivr.net/gh/zip95297/zip95297.github.io@main/source/images/%E6%9C%BA%E5%99%A8%E5%AD%A6%E4%B9%A0%E5%A4%8D%E4%B9%A0%E8%B5%84%E6%96%99/Pasted%20image%2020250428214406.webp?raw=true) -![](https://cdn.jsdelivr.net/gh/zip95297/zip95297.github.io@main/source/images/%E6%9C%BA%E5%99%A8%E5%AD%A6%E4%B9%A0%E5%A4%8D%E4%B9%A0%E8%B5%84%E6%96%99/Pasted%20image%2020250428214410.webp?raw=true) - -### BERT模型 -mask在中间的 不像 decoder 在一个地方 后面做mask -![](https://cdn.jsdelivr.net/gh/zip95297/zip95297.github.io@main/source/images/%E6%9C%BA%E5%99%A8%E5%AD%A6%E4%B9%A0%E5%A4%8D%E4%B9%A0%E8%B5%84%E6%96%99/Pasted%20image%2020250428213016.webp?raw=true) -![](https://cdn.jsdelivr.net/gh/zip95297/zip95297.github.io@main/source/images/%E6%9C%BA%E5%99%A8%E5%AD%A6%E4%B9%A0%E5%A4%8D%E4%B9%A0%E8%B5%84%E6%96%99/Pasted%20image%2020250428214420.webp?raw=true) -![](https://cdn.jsdelivr.net/gh/zip95297/zip95297.github.io@main/source/images/%E6%9C%BA%E5%99%A8%E5%AD%A6%E4%B9%A0%E5%A4%8D%E4%B9%A0%E8%B5%84%E6%96%99/Pasted%20image%2020250428214438.webp?raw=true) -![](https://cdn.jsdelivr.net/gh/zip95297/zip95297.github.io@main/source/images/%E6%9C%BA%E5%99%A8%E5%AD%A6%E4%B9%A0%E5%A4%8D%E4%B9%A0%E8%B5%84%E6%96%99/Pasted%20image%2020250428214442.webp?raw=true) -![](https://cdn.jsdelivr.net/gh/zip95297/zip95297.github.io@main/source/images/%E6%9C%BA%E5%99%A8%E5%AD%A6%E4%B9%A0%E5%A4%8D%E4%B9%A0%E8%B5%84%E6%96%99/Pasted%20image%2020250428214509.webp?raw=true) -![](https://cdn.jsdelivr.net/gh/zip95297/zip95297.github.io@main/source/images/%E6%9C%BA%E5%99%A8%E5%AD%A6%E4%B9%A0%E5%A4%8D%E4%B9%A0%E8%B5%84%E6%96%99/Pasted%20image%2020250428214538.webp?raw=true) -![](https://cdn.jsdelivr.net/gh/zip95297/zip95297.github.io@main/source/images/%E6%9C%BA%E5%99%A8%E5%AD%A6%E4%B9%A0%E5%A4%8D%E4%B9%A0%E8%B5%84%E6%96%99/Pasted%20image%2020250428214544.webp?raw=true) -分类![](https://cdn.jsdelivr.net/gh/zip95297/zip95297.github.io@main/source/images/%E6%9C%BA%E5%99%A8%E5%AD%A6%E4%B9%A0%E5%A4%8D%E4%B9%A0%E8%B5%84%E6%96%99/Pasted%20image%2020250428214621.webp?raw=true) -词性标注![](https://cdn.jsdelivr.net/gh/zip95297/zip95297.github.io@main/source/images/%E6%9C%BA%E5%99%A8%E5%AD%A6%E4%B9%A0%E5%A4%8D%E4%B9%A0%E8%B5%84%E6%96%99/Pasted%20image%2020250428214655.webp?raw=true) -对对话分类![](https://cdn.jsdelivr.net/gh/zip95297/zip95297.github.io@main/source/images/%E6%9C%BA%E5%99%A8%E5%AD%A6%E4%B9%A0%E5%A4%8D%E4%B9%A0%E8%B5%84%E6%96%99/Pasted%20image%2020250428214727.webp?raw=true) -![](https://cdn.jsdelivr.net/gh/zip95297/zip95297.github.io@main/source/images/%E6%9C%BA%E5%99%A8%E5%AD%A6%E4%B9%A0%E5%A4%8D%E4%B9%A0%E8%B5%84%E6%96%99/Pasted%20image%2020250428214750.webp?raw=true) - -### GPT -![](https://cdn.jsdelivr.net/gh/zip95297/zip95297.github.io@main/source/images/%E6%9C%BA%E5%99%A8%E5%AD%A6%E4%B9%A0%E5%A4%8D%E4%B9%A0%E8%B5%84%E6%96%99/Pasted%20image%2020250428213255.webp?raw=true) -![](https://cdn.jsdelivr.net/gh/zip95297/zip95297.github.io@main/source/images/%E6%9C%BA%E5%99%A8%E5%AD%A6%E4%B9%A0%E5%A4%8D%E4%B9%A0%E8%B5%84%E6%96%99/Pasted%20image%2020250428214933.webp?raw=true) -![](https://cdn.jsdelivr.net/gh/zip95297/zip95297.github.io@main/source/images/%E6%9C%BA%E5%99%A8%E5%AD%A6%E4%B9%A0%E5%A4%8D%E4%B9%A0%E8%B5%84%E6%96%99/Pasted%20image%2020250428214940.webp?raw=true) -![](https://cdn.jsdelivr.net/gh/zip95297/zip95297.github.io@main/source/images/%E6%9C%BA%E5%99%A8%E5%AD%A6%E4%B9%A0%E5%A4%8D%E4%B9%A0%E8%B5%84%E6%96%99/Pasted%20image%2020250428215019.webp?raw=true) -![](https://cdn.jsdelivr.net/gh/zip95297/zip95297.github.io@main/source/images/%E6%9C%BA%E5%99%A8%E5%AD%A6%E4%B9%A0%E5%A4%8D%E4%B9%A0%E8%B5%84%E6%96%99/Pasted%20image%2020250428215034.webp?raw=true) - -### 区别 - -![](https://cdn.jsdelivr.net/gh/zip95297/zip95297.github.io@main/source/images/%E6%9C%BA%E5%99%A8%E5%AD%A6%E4%B9%A0%E5%A4%8D%E4%B9%A0%E8%B5%84%E6%96%99/Pasted%20image%2020250428215116.webp?raw=true) -![](https://cdn.jsdelivr.net/gh/zip95297/zip95297.github.io@main/source/images/%E6%9C%BA%E5%99%A8%E5%AD%A6%E4%B9%A0%E5%A4%8D%E4%B9%A0%E8%B5%84%E6%96%99/Pasted%20image%2020250428215147.webp?raw=true) - - - - - -# 第十章 强化学习 - -## 强化学习基本原理与构成 - -强化学习(Reinforcement Learning, 简称 RL)是机器学习的范式和方法论之一,用于描述和解决智能体(Agent)在与环境(Environment)的交互过程中通过学习策略(Policy)以达成奖励或回报(Reward)最大化或实现特定目标的问题。 - - - -> 强化学习和其他机器学习范式不同的的原因: - 没有 做loss的步骤 非及时反馈![](https://cdn.jsdelivr.net/gh/zip95297/zip95297.github.io@main/source/images/%E6%9C%BA%E5%99%A8%E5%AD%A6%E4%B9%A0%E5%A4%8D%E4%B9%A0%E8%B5%84%E6%96%99/Pasted%20image%2020250428215449.webp?raw=true) - -奖励: -![](https://cdn.jsdelivr.net/gh/zip95297/zip95297.github.io@main/source/images/%E6%9C%BA%E5%99%A8%E5%AD%A6%E4%B9%A0%E5%A4%8D%E4%B9%A0%E8%B5%84%E6%96%99/Pasted%20image%2020250428215605.webp?raw=true) -例如: -乘坐直升机进⾏特技⻜⾏操作 - • 因遵循期望轨迹获得正奖励 - • 因坠毁获得负奖励 - -在西洋双陆棋比赛中击败世界冠军 - • +/− 因赢得/输掉⼀局比赛获得的正负奖励 - -让类人机器人⾏⾛ - • 向前移动给予正奖励 - • 摔倒给予负奖励 - -决策序列![](https://cdn.jsdelivr.net/gh/zip95297/zip95297.github.io@main/source/images/%E6%9C%BA%E5%99%A8%E5%AD%A6%E4%B9%A0%E5%A4%8D%E4%B9%A0%E8%B5%84%E6%96%99/Pasted%20image%2020250428215708.webp?raw=true) - -智能体与环境的交互![](https://cdn.jsdelivr.net/gh/zip95297/zip95297.github.io@main/source/images/%E6%9C%BA%E5%99%A8%E5%AD%A6%E4%B9%A0%E5%A4%8D%E4%B9%A0%E8%B5%84%E6%96%99/Pasted%20image%2020250428215754.webp?raw=true) - -历史与状态![](https://cdn.jsdelivr.net/gh/zip95297/zip95297.github.io@main/source/images/%E6%9C%BA%E5%99%A8%E5%AD%A6%E4%B9%A0%E5%A4%8D%E4%B9%A0%E8%B5%84%E6%96%99/Pasted%20image%2020250428215804.webp?raw=true) - -信息状态![](https://cdn.jsdelivr.net/gh/zip95297/zip95297.github.io@main/source/images/%E6%9C%BA%E5%99%A8%E5%AD%A6%E4%B9%A0%E5%A4%8D%E4%B9%A0%E8%B5%84%E6%96%99/Pasted%20image%2020250428215821.webp?raw=true) - -![](https://cdn.jsdelivr.net/gh/zip95297/zip95297.github.io@main/source/images/%E6%9C%BA%E5%99%A8%E5%AD%A6%E4%B9%A0%E5%A4%8D%E4%B9%A0%E8%B5%84%E6%96%99/Pasted%20image%2020250428220733.webp?raw=true) - - -## 智能体的构成 - -主要组件![](https://cdn.jsdelivr.net/gh/zip95297/zip95297.github.io@main/source/images/%E6%9C%BA%E5%99%A8%E5%AD%A6%E4%B9%A0%E5%A4%8D%E4%B9%A0%E8%B5%84%E6%96%99/Pasted%20image%2020250428215925.webp?raw=true) - -1. 策略![](https://cdn.jsdelivr.net/gh/zip95297/zip95297.github.io@main/source/images/%E6%9C%BA%E5%99%A8%E5%AD%A6%E4%B9%A0%E5%A4%8D%E4%B9%A0%E8%B5%84%E6%96%99/Pasted%20image%2020250428215948.webp?raw=true) -2. 价值函数![](https://cdn.jsdelivr.net/gh/zip95297/zip95297.github.io@main/source/images/%E6%9C%BA%E5%99%A8%E5%AD%A6%E4%B9%A0%E5%A4%8D%E4%B9%A0%E8%B5%84%E6%96%99/Pasted%20image%2020250428215956.webp?raw=true) -3. 模型![](https://cdn.jsdelivr.net/gh/zip95297/zip95297.github.io@main/source/images/%E6%9C%BA%E5%99%A8%E5%AD%A6%E4%B9%A0%E5%A4%8D%E4%B9%A0%E8%B5%84%E6%96%99/Pasted%20image%2020250428220011.webp?raw=true) - -### 智能体分类 - -![](https://cdn.jsdelivr.net/gh/zip95297/zip95297.github.io@main/source/images/%E6%9C%BA%E5%99%A8%E5%AD%A6%E4%B9%A0%E5%A4%8D%E4%B9%A0%E8%B5%84%E6%96%99/Pasted%20image%2020250428220326.webp?raw=true) -![](https://cdn.jsdelivr.net/gh/zip95297/zip95297.github.io@main/source/images/%E6%9C%BA%E5%99%A8%E5%AD%A6%E4%B9%A0%E5%A4%8D%E4%B9%A0%E8%B5%84%E6%96%99/Pasted%20image%2020250428220331.webp?raw=true) -![](https://cdn.jsdelivr.net/gh/zip95297/zip95297.github.io@main/source/images/%E6%9C%BA%E5%99%A8%E5%AD%A6%E4%B9%A0%E5%A4%8D%E4%B9%A0%E8%B5%84%E6%96%99/Pasted%20image%2020250428220348.webp?raw=true) - - -## 有模型与无模型的区别 - -| **维度** | **有模型(Model-Based)** | **无模型(Model-Free)** | -| -------- | --------------------------------------------------- | ------------------------------- | -| **定义** | 智能体已知环境动态(状态转移概率 \( P_{ss'}^a \) 和奖励函数 \( R_s^a \)) | 智能体直接通过与环境的交互学习,无需环境模型 | -| **核心方法** | 动态规划(DP)、值迭代、策略迭代 | 蒙特卡罗(MC)、时序差分(TD)、Q学习、Sarsa | -| **优缺点** | ✅ 样本效率高(无需实际交互)
❌ 依赖精确模型,难以处理复杂环境 | ✅ 适应性强,适用于未知环境
❌ 样本效率低,收敛慢 | -| **关键步骤** | 1. 构建环境模型
2. 基于模型计算最优策略 | 1. 直接交互获取经验
2. 通过经验更新价值/策略 | -| **典型算法** | 值迭代(Value Iteration)、策略迭代(Policy Iteration) | Q学习(Q-Learning)、Sarsa、REINFORCE | -| **应用场景** | 棋类游戏(已知规则)、仿真环境 | 机器人控制、游戏AI(如Atari) | - -**定义与核心区别** -- **有模型(Model-Based)**: - - 智能体**已知环境模型**,即状态转移概率 \(P(s'|s,a)\) 和奖励函数 \(R(s,a)\)。 - - 通过模型进行**离线规划**(如动态规划),无需与环境实时交互即可优化策略。 - - **典型方法**:动态规划(DP)、值迭代(Value Iteration)、策略迭代(Policy Iteration)。 - - **优点**:样本效率高(无需大量试错),适合小规模或已知模型的问题。 - - **缺点**:模型难以精确获取,复杂环境(如连续状态或高维空间)中不适用。 - -- **无模型(Model-Free)**: - - 智能体**不依赖环境模型**,通过与环境交互直接学习策略或价值函数。 - - 通过**试错**(Trial-and-Error)收集经验,逐步优化策略。 - - **典型方法**:Q-learning、Sarsa、蒙特卡洛(MC)、深度Q网络(DQN)。 - - **优点**:适用于未知或复杂环境,更贴近实际应用场景。 - - **缺点**:样本效率低,训练时间长,可能陷入局部最优。 - -**示例对比** -- **迷宫问题**: - - 有模型:已知迷宫地图,直接计算最短路径。 - - 无模型:通过不断尝试移动并记录奖励,逐步学习最优路径。 -## 基于价值与基于策略的区别 - -| **维度** | **基于价值(Value-Based)** | **基于策略(Policy-Based)** | -|-------------------|----------------------------------------------------|---------------------------------------------------| -| **核心目标** | 学习价值函数 \( V(s) \) 或 \( Q(s,a) \),通过价值选择动作(如ε-贪心) | 直接优化策略函数 \( \pi(a\|s) \),无需价值函数 | -| **策略类型** | 隐式策略(如贪心策略) | 显式策略(可随机或确定性) | -| **优缺点** | ✅ 适合离散动作空间
❌ 难以处理高维/连续动作 | ✅ 支持连续动作、随机策略
❌ 高方差、局部最优 | -| **收敛性** | 可能震荡(如Q学习) | 更平滑但可能陷入局部最优 | -| **典型算法** | Q学习、DQN、Sarsa | REINFORCE、PPO、策略梯度(Policy Gradient) | -| **应用场景** | 离散控制(如Atari游戏) | 连续控制(如机器人行走)、博弈论(如石头剪刀布) | - - -**定义与核心区别** -- **基于价值(Value-Based)**: - - 核心是学习**价值函数**(如状态价值 \(V(s)\) 或动作价值 \(Q(s,a)\)),通过选择价值最大的动作间接优化策略。 - - 策略通常是隐式的(如 \(\epsilon\)-贪心策略)。 - - **典型方法**:Q-learning、Sarsa、DQN。 - - **优点**:稳定性高,适合离散动作空间。 - - **缺点**:难以处理高维或连续动作空间,无法学习随机策略。 - -- **基于策略(Policy-Based)**: - - 直接学习**策略函数** \(\pi(a|s)\),参数化策略并优化其性能。 - - 策略可以是确定性的或随机性的。 - - **典型方法**:策略梯度(REINFORCE)、PPO(Proximal Policy Optimization)。 - - **优点**:适合连续动作空间,能学习随机策略(如博弈中的混合策略)。 - - **缺点**:方差高,收敛速度慢,易陷入局部最优。 - -**示例对比** -- **机器人行走**: - - 基于价值:计算每个动作的价值,选择最大价值的动作(如关节转动角度)。 - - 基于策略:直接输出动作分布(如关节转动的概率),通过梯度上升优化。 - -**混合方法:演员-评论家(Actor-Critic)** -- 结合价值函数(Critic)和策略函数(Actor),Critic评估动作价值,Actor优化策略。 -- **典型方法**:A3C、DDPG。 - - -## 常用强化学习方法的名称 - -| **方法名称** | **类型** | **有模型/无模型** | **核心思想** | -|--------------------|-------------------|-------------------|------------------------------------------| -| **Q-learning** | 基于价值 | 无模型 | 通过最大化Q值更新策略,离线策略(Off-Policy)。 | -| **Sarsa** | 基于价值 | 无模型 | 在线策略(On-Policy),使用当前策略选择动作。 | -| **DQN** | 基于价值 | 无模型 | 深度网络近似Q值,引入经验回放和固定目标网络。 | -| **策略梯度(REINFORCE)** | 基于策略 | 无模型 | 直接优化策略,通过蒙特卡洛回报计算梯度。 | -| **PPO** | 基于策略 | 无模型 | 限制策略更新幅度,提升训练稳定性。 | -| **Actor-Critic** | 混合方法(价值+策略) | 无模型 | Actor输出策略,Critic评估价值,降低方差。 | -| **动态规划(DP)** | 基于价值 | 有模型 | 利用环境模型迭代计算最优价值函数。 | -| **蒙特卡洛(MC)** | 基于价值 | 无模型 | 通过完整回合的回报更新价值函数。 | - -**适用场景总结** -- **有模型方法**:环境模型已知且规模较小(如棋盘游戏、简单控制)。 -- **无模型方法**:环境复杂或未知(如机器人控制、游戏AI)。 -- **基于价值方法**:动作空间离散且维度低(如Atari游戏)。 -- **基于策略方法**:动作空间连续或需要随机策略(如机械臂操控、多智能体博弈)。 - -### 详细一点的 - -#### 1. **动态规划(Dynamic Programming, DP)** - - **特点**:要求已知MDP模型,通过贝尔曼方程迭代求解。 - - **算法**: - - *值迭代*(Page 63):直接优化值函数 \( V(s) \)。 - - *策略迭代*(Page 60):交替进行策略评估和改进。 - -#### 2. **无模型方法(Model-Free)** - - **蒙特卡罗(MC)**(Page 71): - - 通过完整回合的经验更新(如 \( V(s) \leftarrow V(s) + \alpha(G_t - V(s)) \))。 - - 高方差但无偏,适用于回合制任务。 - - **时序差分(TD)**(Page 76): - - *TD(0)*:单步更新(\( V(s) \leftarrow V(s) + \alpha(r + \gamma V(s') - V(s)) \))。 - - *n-step TD*(Page 87):多步混合MC和TD。 - - **Q学习(Q-Learning)**(Page 102): - - 离线策略,更新规则:\( Q(s,a) \leftarrow Q(s,a) + \alpha(r + \gamma \max_{a'} Q(s',a') - Q(s,a)) \)。 - - **Sarsa**(Page 99): - - 在线策略,更新规则:\( Q(s,a) \leftarrow Q(s,a) + \alpha(r + \gamma Q(s',a') - Q(s,a)) \)。 - -#### 3. **价值函数近似(Value Approximation)** - - **DQN**(Page 126): - - 使用神经网络近似Q函数,引入经验回放和固定目标网络。 - - **改进方法**:Double DQN、Dueling DQN(Page 133)。 - -#### 4. **策略优化(Policy Optimization)** - - **策略梯度**(Page 143): - - REINFORCE(Page 145):蒙特卡罗策略梯度,\( \Delta \theta = \alpha \nabla_\theta \log \pi_\theta(s,a) G_t \)。 - - **Actor-Critic**(Page 146): - - 结合值函数(Critic)和策略(Actor),如A2C、A3C。 - -#### 5. **混合方法** diff --git "a/source/bak/\350\275\254\345\217\221\345\206\205\347\275\221\347\275\221\351\241\265\345\210\260\346\234\254\345\234\260.md.bak" "b/source/bak/\350\275\254\345\217\221\345\206\205\347\275\221\347\275\221\351\241\265\345\210\260\346\234\254\345\234\260.md.bak" deleted file mode 100644 index 3ca3f29..0000000 --- "a/source/bak/\350\275\254\345\217\221\345\206\205\347\275\221\347\275\221\351\241\265\345\210\260\346\234\254\345\234\260.md.bak" +++ /dev/null @@ -1,12 +0,0 @@ ---- -title: "转发内网网页到本地" -date: 2025-05-28 19:39:17 -updated: 2025-05-28 19:39:40 -tags: - - 网络 - - ssh - - 网络代理 -categories: 实用技巧 -comments: false ---- - diff --git "a/source/bak/\350\275\254\345\217\221\345\206\205\347\275\221\347\275\221\351\241\265\345\210\260\346\234\254\345\234\260.md.bak.update" "b/source/bak/\350\275\254\345\217\221\345\206\205\347\275\221\347\275\221\351\241\265\345\210\260\346\234\254\345\234\260.md.bak.update" deleted file mode 100644 index 8ed7227..0000000 --- "a/source/bak/\350\275\254\345\217\221\345\206\205\347\275\221\347\275\221\351\241\265\345\210\260\346\234\254\345\234\260.md.bak.update" +++ /dev/null @@ -1,87 +0,0 @@ ---- -title: "转发内网网页到本地" -date: 2025-05-28 19:39:17 -updated: 2025-05-28 19:39:40 -tags: - - 网络 - - ssh - - 网络代理 -categories: 实用技巧 -comments: false ---- -# 转发内网网页到本地 - -在 非内网环境下访问内网网页比较麻烦,记录一下解决这种问题的方式 - -## 环境描述 - -一台内网中的linux服务器,使用 公网服务器 + frp 进行内网穿透 - -在内网环境下访问的 两种网页(目前为止遇到这两种): -1. 服务 直接开在这个服务器上 -2. 服务 没在该服务器上 - -## 第一种 服务 在服务器上 - -例如,一个 自建的 overleaf 网页服务开在 服务器的 8888 端口。 - -### Step1 - -可以直接 在服务器中查看 这个 overleaf 开在哪个 ip 上 ,命令如下: -```shell -sudo lsof -i :8888 -``` -这里已经事先知道了服务开在了 10.10.5.4 这个内网 IP - -### Step2 - -直接将本地某个端口的流量转发到 服务器 对应 ip:port 上: -```shell -ssh -L 8888:10.10.5.4:8888 user@公网服务器ip -p -``` - -然后就可以直接 通过 http://localhost:8888 来访问这个主机的服务了 - - -## 第二种 服务 不在服务器上 - -在内网中 需要访问的 url 例如: -- https://modeloverfit.com -- https://git.modeloverfit.com -经过测试发现子域名和根域名是同一个ip和端口443 - -想要访问这些内网环境下的page,使用下面方式(比较简单,还有一些nginx之类的方法): - -### Step1 - -通过 ssh -D 动态转发?好像是,将内网服务器当做代理服务器,代理本地流量: -```shell -ssh -D 7899 user@ip -p port -``` -由于本地clash用了7890端口,⚠️注意不要冲突 - -### Step2 - -到这里为止已经在本地打开了一代理端口7899,发送到7899端口的流量将会被 内网服务器代理,可以用下面 命令测试一下: -```shell -curl -x socks5://localhost:7899 -k https://git.modeloverfit.com/users/sign_in -``` -> 说明: -x 指定curl的代理 -k 用于 https 忽略证书安全之类的验证大概是 - -这时已经能正确获得改内网url的完整内容了,下面配合chrome内核浏览器进行设置代理规则 - -### Step3 - -- 下载 [SwitchyOmega](https://chrome.google.com/webstore/detail/proxy-switchyomega/padekgcemlokbadohgkifijomclgjgif?hl=en-US)插件 - -在这个插件内: - -- 设置代理服务器 也就是 localhost 7899 -![](https://cdn.jsdelivr.net/gh/zip95297/zip95297.github.io@main/source/images/%E8%BD%AC%E5%8F%91%E5%86%85%E7%BD%91%E7%BD%91%E9%A1%B5%E5%88%B0%E6%9C%AC%E5%9C%B0/Pasted%20image%2020250528193554.webp?raw=true) - -- 设置对于modeloverfit及其子域名的代理规则: -![](https://cdn.jsdelivr.net/gh/zip95297/zip95297.github.io@main/source/images/%E8%BD%AC%E5%8F%91%E5%86%85%E7%BD%91%E7%BD%91%E9%A1%B5%E5%88%B0%E6%9C%AC%E5%9C%B0/Pasted%20image%2020250528193708.webp?raw=true) - -- 最后记得打开插件 使用上面配置的代理设置,然后就可以直接访问内网的url了 -![](https://cdn.jsdelivr.net/gh/zip95297/zip95297.github.io@main/source/images/%E8%BD%AC%E5%8F%91%E5%86%85%E7%BD%91%E7%BD%91%E9%A1%B5%E5%88%B0%E6%9C%AC%E5%9C%B0/Pasted%20image%2020250528193913.webp?raw=true) - diff --git "a/source/bak/\350\275\273\351\207\217\346\216\250\347\220\206\346\211\213\350\256\260.md.bak" "b/source/bak/\350\275\273\351\207\217\346\216\250\347\220\206\346\211\213\350\256\260.md.bak" deleted file mode 100644 index 1a4c2a6..0000000 --- "a/source/bak/\350\275\273\351\207\217\346\216\250\347\220\206\346\211\213\350\256\260.md.bak" +++ /dev/null @@ -1,222 +0,0 @@ ---- -title: "轻量推理手记" -date: 2026-04-02 04:42:20 -updated: 2026-04-02 04:42:20 -mathjax: true -tags: - - 深度学习 -categories: 深度学习 -comments: false ---- -# 目前的项目结构 - -```bash -├── bench.py -├── example.py -├── LICENSE -├── llm_serving.py -├── nanovllm -│   ├── config.py -│   ├── engine -│   │   ├── async_llm_engine.py -│   │   ├── block_manager.py -│   │   ├── llm_engine.py -│   │   ├── model_runner.py -│   │   ├── scheduler.py -│   │   └── sequence.py -│   ├── layers -│   │   ├── activation.py -│   │   ├── attention.py -│   │   ├── embed_head.py -│   │   ├── layernorm.py -│   │   ├── linear.py -│   │   ├── rotary_embedding.py -│   │   └── sampler.py -│   ├── llm.py -│   ├── models -│   │   ├── llama.py -│   │   └── qwen3.py -│   ├── sampling_params.py -│   └── utils -│   ├── context.py -│   └── loader.py -├── pyproject.toml -├── README.md -├── serving_bench.py -└── uv.lock -``` - -离线推理时候 main函数实例化 LLMEngine 在rank0上init,init时候现在副卡init ModelRunner 然后进入loop 阻塞在 shm的read上。然后在rank0上init ModelRunner 不进入loop。在main中调用 -LLMEngine的generate方法 在里面call run函数,call的时候卡0把run写进shm 副卡read读出输出不再阻塞 然后副卡根据shm读出的call什么 也调用call 所有卡一起推理。也就是执行CausalModel的forward得到logits 这时候分成几份在多个卡上,然后最后LMHead将 hiddenstate * 最后的分类头 得到每个token的分数 logits 然后 gather到卡0上。然后交给采样器跟觉策略进行采样。 -# 待实现的功能 - -- [x] 实现 flash_attention @Triton - ![[Pasted image 20260317225553.png]] - compute bound -- [x] 实现 paged_attention_v1 @Triton 取所有kv -- [x] 实现 paged_attention_v2 @Triton 对kv做online softmax -- [x] 实现 flash_decoding @Triton - @Triton 并行计算 m l acc 然后 @Triton reduction 但是要申请空间 最开始按照blockn 64做 OOM了 - 发现应该是按照split 对seq分块,split num 可以根据 context len 切换? - ![[Pasted image 20260317230029.png]] - ![[Pasted image 20260317230155.png]]reduce 的代价很高 -- [x] Nsight System 分析性能瓶颈 - ![[Pasted image 20260317225947.png]] - 程序调度的代价很高 -- [x] 实现 OnlineInfer、 Countinuous Batching -- [x] 实现 KV Cache量化 -- [1] 实现 Chunked Prefill ,调度规则 (flash -- [x] RMSLayerNrom @Triton 还有什么用Triton写,分析一下 linear? -- [x] 支持 llama -- [x] CUDAGraph - -# 以后有时间了做 - -- [ ] KVCache 的卸载 和 Recomputation -- [ ] 实现 KV Cache 异步加载 -- [ ] 实现 AWQ 量化支持 -- [ ] 实现 投机采样 -- [ ] 实现 PD 分离 -- [ ] 支持 Deepseek -- [ ] 支持 Qwen3-MoE -- [ ] TP 之后实现 PP - -# flash_attention - -输入是 QKV 计算 O 用 online softmax 更新 QKVO的维度是 seqlen,headnum,headdim - -用triton并行的时候分为三个维度: - 1. 对输入Q的seqlen按照BLOCKM分块 - 2. 对 Q 的头分块 - -在每个program里面的操作维度是 tl.arange(0,head_dim) - -然后注意写triton是要 梳理 tensor的shape, 标注清楚 offset ,取两个维度时候注意标注 `[None,:]` - -# page_attention - -按照 batch_size,head_num 两个维度 并行,并行的时候 对 context 中的 所有 kvcache 块 中的 token 逐个遍历 增量更新 O ,最开始的计算方式是想把所有context 中的kv全部中上下文中拿出来,但是这样设计的显存访问代价过高,所以更改设计方式,把 contextlen 按照 BLOCK_M 进行划分 然后对 BLOCKM中的每个token依次softmax 但是还是很慢。不如直接对BLOCKM中一个kv矩阵并行处理,计算QK 然后增量更新。 - -最开始是对每个token逐个online softmax 后来分块算 - -# flash_decoding - -在 pageAttn 基础上 对每个 decode token 的 context 分split 按 split 并行计算 o 然后最后 reduce - -`flash_decoding_kernel`, `flash_decoding_reduce_kernel`, `flash_decoding` 最后集成进一个函数 - -问题:最开始 设计的时候遇到一个问题,split size 设置成了 64 目的是为了减少 对 L2 cache的访问,但是实现后发现,这样的虽然提高了 qk 计算 m l acc 的并行度,但是由于每个 layer 都要申请一段空间去存放临时结果,运行的时候如果保持 GPU 利用率 会导致 OOM 而且速度几乎没有提升,和预想的结果偏差较大。 - -分析原因发现,计算的并行度虽然提升了,但是 reduce 的代价会变高很多,因为临时结果有 max_context_len/BLOCK_N 个 reduce时候要依次遍历。而且由于BLOCKN很小,所以每个layer申请的内存空间较大。 - -解决办法:将 seqlen 按照 预设好的 split 划分成几个部分,然后每个program 处理 一个 split 所有kvcache 。这样做解决了 OOM 和 reduce 代价过大的问题。实现了真正的 flashdecoding。 - -没命中的 block 直接 把 attention 置为 0 当做无效块处理 - -遗留的问题 : -1. split 是不是可以根据 contextlen 动态调整 -2. 在调度的时候是不是可以优先调度上下文长度均匀的seq 这样可以避免一些 kernel 在空转? - - -# 实现 online infer , Stream Infer - -AsyncLLMEngine 是 llm engine 的一个 异步 wrapper -https://github.com/vllm-project/vllm/blob/main/vllm/v1/engine/async_llm.py -https://github.com/vllm-project/vllm/blob/main/vllm/v1/engine/llm_engine.py - -为了能异步获取seqdecode出的结果,对Sequence类加一个asyncio.queue,每次step decode出结果就加进来 - -先实现 (异步生成器函数)streaming generate 输入和 LLMEngine 保持同步,主要逻辑是这样: -``` -1. seq = await add_request 异步添加请求 简化模型 一个req 一个seq -2. 依次读 seq 中decode出的结果直到DONE - while True: 循环yield 直到结束 - item = await seq._queue.get() - if item == DONE : break - yield tokenizer.decode(item) -``` - -之后遍历这个就可以得到流式结果了,对于非流式的generate 其实就是流式的generate 最后把结果join起来。 - -现在addReq逻辑实则是同步的,虽然写成了一个协程函数 但是不async也可以。addReq会确认模型的运行循环是开着的。 - -还有需要注意的是,每次addreq时候,需要确认 主事件(engine的step) 还在 这样加入的请求才能被 不断的 schedule。但是现在的schedule策略是 prefill优先,之后可以调整一下。 - -Continuous Batching 就是 模型 每次step后 都要 调度 主要是为了结束 finish 的 seq(也就是decode完成的 seq)的生命周期,不要占着batch空转 - - -# Chunked Prefill 调度规则 - -vllm中的 scheduler -https://github.com/vllm-project/vllm/blob/main/vllm/v1/core/sched/scheduler.py -``` - # NOTE(woosuk) on the scheduling algorithm: - # There's no "decoding phase" nor "prefill phase" in the scheduler. - # Each request just has the num_computed_tokens and - # num_tokens_with_spec. num_tokens_with_spec = - # len(prompt_token_ids) + len(output_token_ids) + len(spec_token_ids). - # At each step, the scheduler tries to assign tokens to the requests - # so that each request's num_computed_tokens can catch up its - # num_tokens_with_spec. This is general enough to cover - # chunked prefills, prefix caching, speculative decoding, - # and the "jump decoding" optimization in the future. -``` -无阶段调度不分PD,先塞decode 再塞prefill 与 Chunked Prefill - -为什么要chunked prefill? 在没有实现 PD 分离 的情况下,如果来了一个很长的prompt ,prefill的耗时太高,会到时decode请求的latency增大。而且现有的 scheduler 的 结构 是 PD 分阶段完成,导致在decoding时候的计算资源利用率很低。所以将 请求 prefill 的prompt 拆分成chunk 然后 按照 decode优先,然后prefill chunk的顺序塞满 一个 batch。而且如果来了两个 一长一短的prompt 拆分成chunk也会提高短序列TTFT速度。 也可以理解成充分利用decode时候未被充分利用的计算资源 - -1. 先实现了 schedule 的调度策略 **调度完一个seq的最后一个chunk 就把它放到running队列了有问题** -2. blockmanager 中 对 chunk的 allocate -3. llmengine 中的 chunkstep 函数 -4. modelrunner的run函数 -5. run 调用的 prepare mixed batch函数 因为应用了对话模板 默认 prefill的请求长度不可能是1 -6. attention的计算不分开 怎么做一个 unified attention ,flashattention with KVcache -7. LMHead 对于混合batch 要进行特殊的处理 decode+最后一个chunk的token - -对context的组织也很难,还有一个seq传输序列化的时候忘记往getstate里面写status属性了 - -先把两种请求留在一个batch里面但是分开做 attention 吧,分成两种Attn和合成一种有什么区别? -先分开做的:prefill的部分用flashAttnwithKVCache 然后 decode的部分用 flashdecoding - -这两个block manager 很不一样 chunkedprefill因为在 prefill 的时候要用到cache 所以 seq的num cached tokens 要在post process里面更新。 - -这个和PD 分阶段计算的时候blockmanager分配的策略不一样,分阶段的时候,是在allocate直接就给seq的numcachedtoken加好,但是用了chunkedprefill的话会导致,inputids直接变成0 -![[Pasted image 20260330011123.png]] -![[Pasted image 20260330011131.png]] -cpu调度代价太高 - -# KVCache量化 int 8 - -在前向时候 把每个kv 量化存入cache int8 : -127 127 -vec / max(|vec|) * 127 这个量化方法就是把原本的数放缩到 int8 的全部空间,然后记录scale 就是 max值 -最开始将全部kvcache进行反量化,但是发现可能会OOM 而且计算速度特别慢 增大了memoryAccess的负担。所以将反量化的过程融合进 flashdecoding内,尽可能的保证了速度。 - -这个在做的时候 发现,一开始是全量 dequant出来,既不省内存 而且非常耗时。所以改成在flashdecoding时候再dequant出来 - -# RMSnorm 用triton实现 - -原本使用torch.compile 然后自己实现triton的时候 -x = x + residual 按行分块做 把x给view 最后总吞吐反而下降了,分析之后发现,x+res访存太耗时了, -所以 应该写融合算子 读 add 写 res的操作都在里面实现。 -但是现在并行度是整个 输入的第一个维度(view(-1,x.shape\[-1\]))如果尝试分块会不会效果更好? - -对输入向量的最后一个维度。但是并行度有点高,kernel launch overhead 会不会有点高? 解决这个可以靠 融合算子,cudaGraph等烧录 - - - -# 支持llama - -gate_up_proj 等有几个要在 modelforCausalLLM里面写进一个字典里。否则load会出错 -Attention里面的头数是一个卡的头数!!!!!!!!!发现单卡没问题 多卡报错 -basemodel就是纯自回归补全 instructmodel 才应该应用对话模板 - -# Thinking - -Decode 时候因为要访问全部的上下文 所以Decode的时间复杂度应该是On,但是有以下面对办法: - - Sliding Window Attention 只看最近W个token前面的扔了 - - KV Compression / Pooling 把前面的多个kv 进行压缩 - - Sparse Attention 稀疏注意力 只关注部分token - - Linear Attention(核方法)、Retrieval / Memory 模型 - -为什么用triton \ CUDA 而不是torck的张量广播? - Triton 能精准控制 GPU 硬件层级的内存访问和计算调度,把注意力计算的核心逻辑「塞进 L1/L2 Cache」完成,彻底降低对高延迟 HBM(显存)的访问;而 PyTorch 张量广播的并行是「逻辑层」的,无法精细控制硬件缓存,最终仍会导致大量低效的 HBM 访问。 diff --git "a/source/bak/\350\275\273\351\207\217\346\216\250\347\220\206\346\241\206\346\236\266.md.bak" "b/source/bak/\350\275\273\351\207\217\346\216\250\347\220\206\346\241\206\346\236\266.md.bak" deleted file mode 100644 index e78c52d..0000000 --- "a/source/bak/\350\275\273\351\207\217\346\216\250\347\220\206\346\241\206\346\236\266.md.bak" +++ /dev/null @@ -1,87 +0,0 @@ ---- -title: "轻量推理框架" -date: 2026-04-02 04:24:04 -updated: 2026-04-02 04:24:04 -mathjax: true -tags: - - 深度学习 -categories: 深度学习 -comments: false ---- -这是一份针对 EchoLLMServing 推理引擎架构及核心算子优化的工程笔记梳理。内容已针对技术深度、架构权衡(Trade-off)以及实现细节进行了重构,,并补充了底层逻辑分析。 - ---- - -# EchoLLMServing 推理框架研发笔记 - -## 1. 架构设计与分布式执行逻辑 - -当前项目采用了多进程架构支持张量并行(Tensor Parallelism),控制流与数据流分离: -* **控制平面**:Rank 0 作为主节点(`LLMEngine`),负责请求接收、调度(Scheduler)和元数据管理(可结合 Gloo 进行跨进程协调)。 -* **数据平面**:副卡初始化 `ModelRunner` 并阻塞于共享内存(shm)或类似 ZeroMQ 的异步队列等待指令。 -* **执行流**:Rank 0 在 `generate` 中调用 `run`,将计算元信息写入 shm,触发所有 Rank 并行执行前向传播。在 LMHead 阶段,各卡计算出局部的 logits 后,通过 All-Reduce 或 Gather 汇总到 Rank 0,由采样器(Sampler)根据策略输出 Token。 - -**架构审视**:这种主从架构简化了调度的复杂度,但在大规模并发下,Rank 0 的调度开销可能成为瓶颈。引入 CUDA Graph 是降低调度开销的有效手段,通过捕获算子执行序列,能够显著减少 Kernel Launch Overhead。 - -## 2. 核心算子实现与优化 (@Triton) - -### FlashAttention -* **定位**:解决 Prefill 阶段的 Compute Bound 问题。 -* **实现逻辑**:对输入 Q 的 `seq_len` 按 `BLOCK_M` 分块,对外层 Head 分块。在 Triton program 内处理 `tl.arange(0, head_dim)` 的向量化计算。 -* **Trade-off**:虽然极大地提升了计算密度,但对 SRAM 容量要求高。`BLOCK_M` 的大小受限于硬件的 Shared Memory 上限。 - -### PagedAttention (V1 -> V2 演进) -* **V1 的问题**:最初尝试将 context 中的全部 KV 取出再计算,导致严重的 Memory Bound。显存带宽被非连续的访存彻底打满,吞吐极低。 -* **V2 的改进**:将 context_len 按 `BLOCK_M` 划分,对 KV 矩阵分块并行计算 QK,进行 Online Softmax 增量更新。减少了不必要的显存搬运,将数据尽可能留在 SRAM 中处理。 - -### FlashDecoding (长序列 Decode 优化) -在长上下文 Decode 时,传统的 PagedAttention 仍面临带宽瓶颈。引入 Split-K 机制与双 Kernel 架构优化: -* **OOM 与 Reduce 代价分析**:最初将 split size 固定为 64,导致每个 block 都会产生临时结果(`m`, `l`, `acc`)。如果并行度过高且 `BLOCK_N` 很小,申请的 Global Memory 空间会急剧膨胀导致 OOM,且最终的 Reduce Kernel 需要遍历巨大的临时数组,抵消了并行带来的收益。 -* **解决方案**:将 `seq_len` 按照预设的 split 划分为几个 chunk,每个 program 处理一个 split 内的所有 KV Cache。 - -**针对笔记中遗留问题的解答:** -* **问题:Split_num 是否可以根据 `context_len` 动态调整?** - * **解答**:必须动态调整。固定的 split_num 会导致短序列的 kernel 切分过细(增加 kernel 启动和 reduce 开销),而长序列切分不足(无法充分利用 SM)。通常的启发式策略是:`split_num = max(1, context_len // optimal_chunk_size)`,其中 `optimal_chunk_size` 根据 GPU 的 SM 数量和 Cache 大小在 Profiling 中动态标定(例如 256 或 512)。 -* **问题:调度时是否优先调度上下文长度均匀的 Seq 以避免 Kernel 空转?** - * **解答**:是的。在未实现完全 Unified Attention 的情况下,同 Batch 内序列长度差异过大会导致严重的 Padding 计算浪费或线程束(Warp)分化。通过持续批处理(Continuous Batching)结合细粒度的 Block 管理,可以最大程度缓解这个问题。 - -### Fused Add-RMSNorm 与 Triton 拓展 -* **问题分析**:使用 PyTorch 原生的 `x = x + residual` 然后做 RMSNorm 时,发现总吞吐下降,通过 Nsight Systems (nsys) 分析证实,内存拷贝与读写开销(Memory Access)是主因。 -* **Triton 实现权衡**: - * 写融合算子,将读、Add、写 Res、Norm 全部在一个 Kernel 内完成。 - * **关于分块与 Kernel Launch Overhead**:对输入向量的最后一个维度(`hidden_dim`)进行处理。如果 `hidden_dim` 过大(如 Llama 的 4096),可以将其拆分为几个 Block。并行度高导致的 Launch Overhead 可以通过 CUDA Graph 解决。关键在于利用 `tl.load` 时的向量化读写(保证首地址对齐)。 - -## 3. 调度、显存管理与特性支持 - -### 异步推理与 Continuous Batching -AsyncLLMEngine 通过 `asyncio.queue` 实现了请求的非阻塞生成。Continuous Batching 的核心价值在于打破了静态 Batch 的生命周期绑定,每次 Step 后立即回收完成状态的 Seq 所占用的 Block 资源,并打入新请求,极大提升了吞吐。 - -### Chunked Prefill 调度规则 -* **核心动机**:解决长短 Prompt 混杂时的 Time to First Token (TTFT) 延迟毛刺,以及分离阶段导致 Decode 计算资源利用率低的问题。 -* **Block Manager 状态不一致问题**:Chunked Prefill 会导致 `num_cached_tokens` 与实际输入的 `input_ids` 长度在单次 forward 中不匹配,必须在 post-process 中进行增量状态维护。 - -**针对笔记中遗留问题的解答:** -* **问题:混合 Batch 时,分开做两种 Attn 和合成一种(Unified Attention)有什么区别?** - * **解答**: - 1. **分开做(分离 Kernel)**:工程实现简单。Prefill 的请求走 FlashAttention,Decode 的请求走 FlashDecoding。缺点是 Batch 较小时,两个 Kernel 依次 Launch,可能无法跑满 GPU 的 SM,存在硬件资源的波谷。 - 2. **合成一种(Unified Attention)**:将 Decode 视为长度为 1 的特殊 Prefill 阶段,使用一套底层机制处理。这要求极高的指针与索引管理技巧(类似 FlashInfer 的设计)。优点是只需 Launch 一个 Kernel,SM 占用率极高;缺点是分支预测和 Warp 分化处理非常复杂。前期建议分开做,后期追求极限性能时再考虑 Unified。 - -### KV Cache INT8 量化 -* **工程经验**:最初全量反量化(Dequantization)导致严重的显存读写墙,耗时极高。 -* **正确路径**:将 Per-token 反量化逻辑直接 Fuse 进 FlashAttention/FlashDecoding Kernel 内。在读取 INT8 Cache 到 SRAM 后,立即使用伴随的 Scale 因子还原为 FP16 进行计算。这一操作以极小的计算开销(Compute)换取了 50% 的显存带宽(Bandwidth)与容量收益。 - -## 4. 底层架构思考 (Thinking & Trade-offs) - -### Decode 阶段 $O(N)$ 复杂度缓解策略 -Decode 本质上是 Memory Bound,复杂度随上下文线性增长。常见缓解方案: -* **算法层**:Sliding Window Attention (SWA)、KV 压缩/池化(如 H2O)、稀疏注意力。 -* **系统层**:前缀缓存(RadixTree LRU Prefix Cache),将公共的前缀(如 System Prompt)复用,避免重复计算并节省 KV Cache 空间。 - -### 为什么必须用 Triton/CUDA 代替 PyTorch 张量广播? -PyTorch 的张量广播发生在**逻辑层和 Global Memory 层面**。它会产生大量隐式的中间张量,导致 HBM 被反复读写(Memory Wall)。Triton 和 CUDA 的核心优势在于**显存层级控制**:将高复杂度的 $O(N^2)$ 计算约束在 L1/L2 Cache 或 SRAM 中,将对外部 HBM 的访问降低到 $O(N)$ 级别。在算力远大于带宽的 Ampere/Hopper 架构上,这是唯一解。 - -### 还有什么值得用 Triton 优化的算子? -* **Fused RoPE + QKV 投影**:将旋转位置编码直接融合进 QKV 的 Linear 计算之后,避免显存的来回搬运。 -* **Fused SwiGLU (或 GeLU)**:激活函数通常是单纯的 element-wise 操作,典型的 Memory Bound,非常适合融合。 -* **MoE Top-K Routing**:如果未来支持 Qwen3-MoE,专家路由的 Top-K 计算、Softmax 及 token 重排,使用 Triton 可以大幅降低开销。 -* **动态量化(Dynamic Quantization)**:在 W8A8 甚至 W4A16 场景下,使用 Triton 实时计算 token 级别的 scale 并执行量化矩阵乘。 \ No newline at end of file diff --git "a/source/bak/\351\242\230\347\233\256\347\254\224\350\256\260.md.bak" "b/source/bak/\351\242\230\347\233\256\347\254\224\350\256\260.md.bak" deleted file mode 100644 index 9fdc252..0000000 --- "a/source/bak/\351\242\230\347\233\256\347\254\224\350\256\260.md.bak" +++ /dev/null @@ -1,5518 +0,0 @@ ---- -title: "题目笔记" -date: 2025-12-12 13:50:46 -updated: 2025-12-12 13:50:46 -mathjax: true -tags: - - LeetCode - - Job -categories: 实用技巧 -comments: true ---- -这个笔记用来记录 第一次刷 LeetCode Hot 100 -# 1. [两数之和](https://leetcode.cn/problems/two-sum/)1-251210 - -tag: 哈希表 - -```python -class Solution: - def twoSum(self, nums: List[int], target: int) -> List[int]: - dic = {} - for i in range(len(nums)): - residual = target - nums[i] - if nums[i] in dic.keys(): - return [i, dic[nums[i]]] - if residual not in dic.keys(): - dic[residual] = i -``` - -对每个数 留下 索引(value) 以及 到 target 的差(index),之后遇到 target差 的数,直接读出索引就行 - -# 2. [字母异位词分组](https://leetcode.cn/problems/group-anagrams/)49-251210 - -tag: 哈希表 排序 - -## 思路一:哈希 - -```python -class Solution: - - def groupAnagrams(self, strs: List[str]) -> List[List[str]]: - map = {} - for item in strs: - v = self.str2value(item) - if v in map.keys(): - map[v].append(item) - else: - map[v] = [item] - return list(map.values()) - - def str2value(self, stR:str) -> int : - res = 0 - for char in stR: - index = ord(char)-ord('a') - res += 10**(index) - return res*len(stR) -``` - -第一次的思路就是 把一个 str 得到一个顺序无关的哈希值。但是复杂度好像有点高。 - -更好的实现方式:使用了 `from collections import defaultdict` 内置函数 - -使用 defaultdict 是内置 dict 的子类,核心特性是,当访问不存在的键时,会自动创建这个键并赋值为 **指定默认值** 而不是抛出 KeyError。例如:初始化 `mp=collections.defaultdict(list)` 如果访问了不存在的键就会默认为空list (传入int就是默认0) - -还有一个 dict 的索引不能是 list dict set,可以是int float tuple str bool None frozenset ,list 可以转为tuple - -```python -class Solution: - def groupAnagrams(self, strs: List[str]) -> List[List[str]]: - dic = collections.defaultdict(list) - for s in strs: - count = [0] * 26 - for c in s: - count[ord(c)-ord('a')]+=1 - # 这一步的时间复杂度是 O(k)+1 ,k=26 tuple生成这个hash值需要 list长度的时间 - dic[tuple(count)].append(s) - return list(dic.values()) -``` - -这个方法时间复杂度是 O(n(k+s)) s是字符集的大小 26,每个 str 要 k 来遍历字符, s来生成 hash表的键。 - -## 思路二-排序\* 最优 - -对每个str排序,然后将排序的这个 字符串 当做字典的索引(哈希值)时间复杂度是 O(nklogk) - -内置函数 `sorted` 函数 输入字符串 返回一个按照 unicode 编码的 char list -''.join(list(char)) 把charlist 拼成一个串 注意这个join是 字符串的子函数 - -```python -class Solution: - def groupAnagrams(self, strs: List[str]) -> List[List[str]]: - dic = collections.defaultdict(list) - for s in strs: - index = ''.join(sorted(s)) - dic[index].append(s) - return list(dic.values()) -``` - -# 3. [最长连续序列](https://leetcode.cn/problems/longest-consecutive-sequence/)128-251210 - -tag: 并查集 set - -这个题要求了时间复杂度为O(n) 所以不考虑排序的方法,首先用 set 对 nums 去重,然后找到 所有序列开头的数字 并计算 这个序列的长度,**由于每个数仅进入一次内层循环**,所以时间复杂度符合要求。 - -```python -class Solution: - def longestConsecutive(self, nums: List[int]) -> int: - res = 0 - nums_set = set(nums) - for i in nums_set: - if i-1 in nums_set: - continue - # 否则说明这个数是 一个序列的开头 - l = 1 - p = i+1 - while p in nums_set: - l+=1 - p+=1 - res = max(res,l) - return res -``` - -下面这个节省20ms的时间 -```python -class Solution: - def longestConsecutive(self, nums: List[int]) -> int: - res = 0 - nums_set = set(nums) - for i in nums_set: - if i-1 in nums_set: - continue - # 否则说明这个数是 一个序列的开头 - p = i+1 - while p in nums_set: - p+=1 - res = max(res,p-i) - return res -``` - -这个算法的关键是找到起点 避免对一个数的重复遍历 -list 转 set 还有 判断 in set 的操作 时间复杂度是 O(1), 如果是用 list 的 in 时间复杂度是 O(n) - -# 4. [移动零](https://leetcode.cn/problems/move-zeroes/)283-251210 - -tag: 双指针 快慢指针 - -## 思路一 遇到零放后面 - -list : remove是移除 第一个 value,pop是删掉指定index的item -顺序遍历,把0移到最后 - -```python -class Solution: - def moveZeroes(self, nums: List[int]) -> None: - """ - Do not return anything, modify nums in-place instead. - """ - i=0 - last = len(nums) - while i < last : - if nums[i] == 0 : - nums.pop(i) - nums.append(0) - last-=1 - i-=1 - i+=1 -``` - -## 思路二 栈 遇到非零压入栈 - -```python -class Solution: - def moveZeroes(self, nums: List[int]) -> None: - stack_size=0 - for num in nums: - if num : - nums[stack_size] = num - stack_size+=1 - nums[stack_size:len(nums)] = [0]*(len(nums)-stack_size) -``` - -这个方法在最坏的情况下(全是0) 要遍历两次数组 - -## 思路三 双指针\* 最优 - -依次把非零元素移动到 数组靠左边的空位置上。参考快速排序的想法,快拍要确定一个待分割的元素x作为中间点,然后小于等于x放到左边,大于x放到右边。 - -一个指向第一个0 另一个遇到第一个非零就和第一个0换位置,然后指向第一个0的后移一位(仍然是第一个0) (这个理解好像不太好) - -换一个想法,两个指针,慢的说明 其左边 全部都是 保留顺序的非零(也就是指向待处理序列的第一个,只有交换了也就是处理好了,才移动),快的去找下一个需要被处理的非零数字。这样保留了原本的顺序。 - -```python -class Solution: - def moveZeroes(self, nums: List[int]) -> None: - slow=fast=0 - while fast < len(nums): - if nums[fast]: - nums[slow],nums[fast]=nums[fast],nums[slow] - slow+=1 - fast+=1 -``` - -# 5. [盛最多水的容器](https://leetcode.cn/problems/container-with-most-water/)11-251210 - -tag: 双指针 - -首先对于壁 i int: - ans=0 - i,j = 0,len(height)-1 - while i!=j : - ans = max(self.S(i,j,height),ans) - if height[i] int: - l, r = 0, len(height) - 1 - ans = 0 - while l < r: - # w = r - l - h = min(height[l], height[r]) - ans = max(ans, (r - l) * h) - while height[l] <= h and l < r: l += 1 - while height[r] <= h and l < r: r -= 1 - return ans -``` - - -通过节省搜索空间的想法,还可以进一步,通过记录高度的最大值,得到最大的S,然后直接return - -```python -class Solution: - def maxArea(self, height: List[int]) -> int: - max_area = 0 - l, r = 0, len(height) - 1 - max_height = max(height) - - while l < r: - if max_height * (r - l) < max_area: - return max_area - cur_area = min(height[l], height[r]) * (r - l) - if cur_area > max_area: - max_area = cur_area - if height[l] > height[r]: - r -= 1 - else: - l += 1 - return max_area -``` - -# 6. [三数之和](https://leetcode.cn/problems/3sum/)15-251211 - -tag: 双指针 - -最简单的想法 对于每个数 当做一个两数之和来做,但是由于两数之和的解法复杂度是 $O(n)$,这种做法的复杂度是 $O(n^2)$. - -还有一种是用双指针,来找和为 0 的 也是最优的。 - -```python -class Solution: - def threeSum(self, nums: List[int]) -> List[List[int]]: - nums.sort() - ans = [] - n = len(nums) - for i in range(n - 2): - x = nums[i] - if i > 0 and x == nums[i - 1]: # 跳过重复数字 - continue - if x > 0 or nums[-1]<0 : # 优化一 - break - if x + nums[-2] + nums[-1] < 0: # 优化二 - continue - j = i + 1 - k = n - 1 - while j < k: - s = x + nums[j] + nums[k] - if s > 0: - k -= 1 - elif s < 0: - j += 1 - else: # 三数之和为 0 - ans.append([x, nums[j], nums[k]]) - j += 1 - while j < k and nums[j] == nums[j - 1]: # 跳过重复数字 - j += 1 - k -= 1 - while k > j and nums[k] == nums[k + 1]: # 跳过重复数字 - k -= 1 - return ans -``` - -这个方法和 第一个复杂度都是$O(n^2)$ 但是双指针要快一些啊,是因为 枚举 + 哈希表法虽是 O (n²),但哈希表的额外开销、随机内存访问导致常数项远大于双指针;1. 双指针法的「$O(n^2)$」是**真 $O(n^2)$**(外层 n 次,内层 n 次),且常数项极小; - -# 7. [接雨水](https://leetcode.cn/problems/trapping-rain-water/)42-251211 - -tag: 单调栈 动态规划 双指针 - -## 思路一:单调栈\* - -最开始的思路是 遍历所有h 而不是index 有一个问题就是,如果相等的话之后来了更高的没办法记录宽度,所以尝试用index做一下 - -```python -class Solution: - def trap(self, height: List[int]) -> int: - stack = [] - ans = 0 - for i,h in enumerate(height) : - while stack and h > height[stack[-1]] : - top = stack.pop() - if not stack : - break - # 这个是重点部分 好好想一下 - ans += (min(height[stack[-1]],h)-height[top])*(i-stack[-1]-1) - stack.append(i) - return ans -``` -这个部分我觉得最难的是 每次加多少面积,由于单调栈有单调递减的特征,所以栈顶元素一定是最小的,计算的思路就是 记录 每次pop 添加的水 是一排 就是接雨水 水平线往上的? - -## 思路二:动态规划 - -每个位置 i 能接的水的数量 = min( i 左边的最大高度, i 右边的最大高度) - h\[i\] ,所以暴力搜索就是: -```python -class Solution: - def trap(self, height: List[int]) -> int: - ans = 0 - l = len(height) - dp = [0] * l - # 暴力 - for i in range(1,l-1) : - dp[i] = max(min(max(height[0:i]),max(height[i+1:])) - height[i],0) - return sum(dp) -``` - -在内循环中,在数组中找max操作复杂度为 $O(n)$ , 所以这个暴力搜索的复杂度为 $O(n^2)$ ,通过 动态规划的思路 先用两个list: $O(n)$ 来记录 index i 左边的最大和右边的最大height是多少。 - -```python -class Solution: - def trap(self, height: List[int]) -> int: - l = len(height) - ans = [0] * l - # 左侧最大值 不包括 - leftMax = [0] * l - # 右侧最大值 不包括 - rightMax = [0] * l - maxh = 0 - maxr = 0 - # 这里也可以用动态规划的状态转移方程 会更快 - for i in range(l) : - leftMax[i] = maxh - maxh = maxh if maxh > height[i] else height[i] - rightMax[l-1-i] = maxr - maxr = maxr if maxr > height[l-1-i] else height[l-1-i] - for i in range(1,l-1) : - # ans[i] = max(min(max(height[0:i]),max(height[i+1:])) - height[i],0) - ans[i] = max(min(leftMax[i],rightMax[i]) - height[i],0) - return sum(ans) -``` - -## 思路三:双指针 - -本质上和动态规划类似,只是用双指针的方法可以把空间复杂度减到O1,时间复杂度还是 $O(n)$ - -```python -class Solution: - def trap(self, height: List[int]) -> int: - ans = 0 - left, right = 0, len(height) - 1 - leftMax = rightMax = 0 - while left < right: - leftMax = max(leftMax, height[left]) - rightMax = max(rightMax, height[right]) - if height[left] < height[right]: - ans += leftMax - height[left] - left += 1 - else: - ans += rightMax - height[right] - right -= 1 - return ans -``` - -# 8. [无重复字符的最长子串](https://leetcode.cn/problems/longest-substring-without-repeating-characters/)3-251212 - -tag: 滑动窗口 - -这个比较简单,可以学习一下别人的代码思路。下面这个是我的。 - -```python -class Solution: - def lengthOfLongestSubstring(self, s: str) -> int: - ans,i,l = 0,0,len(s) - window = collections.deque() - count = collections.defaultdict(int) - while i < l : - while i < l and count[s[i]] == 0 : - window.append(s[i]) - count[s[i]] += 1 - i += 1 - ans = max(len(window),ans) - while i < l and count[s[i]]!=0 : - f = window.popleft() - count[f] -= 1 - return ans -``` - -用 set(字典本身就适合处理 是否重复这样的事情) -```python -class Solution: - def lengthOfLongestSubstring(self, s: str) -> int: - # 滑动窗口 - left = 0 - window = set() - max_length = 0 - for right in range(len(s)): - while s[right] in window: - window.remove(s[left]) - left += 1 - window.add(s[right]) - if len(window) > max_length: - max_length = len(window) - return max_length -``` - -这个2ms 最快 -```python -class Solution: - def lengthOfLongestSubstring(self, s: str) -> int: - n = len(s) - if n <= 1: - return n - _dict = {} - start = -1 - res = 0 - for i, c in enumerate( s ): - if c in _dict and _dict[c] > start: - start = _dict[c] - res = max(res, i - start) - _dict[c] = i - return res -``` - -# 9. [找到字符串中所有字母异位词](https://leetcode.cn/problems/find-all-anagrams-in-a-string/)438-251212 - -tag: 滑动窗口 - -自己写的解 -```python -class Solution: - def findAnagrams(self, s: str, p: str) -> List[int]: - ans = [] - count = {} - window = collections.deque() - for c in p : - count[c] = 0 - for c in p : - count[c] -= 1 - count['other'] = 0 - start = 0 - for c in s : - window.append(c) - if c in count.keys() : - count[c] += 1 - while count[c] > 0 : - f = window.popleft() - start += 1 - count[f] -= 1 - else : - count['other'] += 1 - while count['other'] != 0 : - f = window.popleft() - start += 1 - if f in count.keys() : - count[f] -= 1 - else : - count['other'] -= 1 - if len(window) == len(p) : - ans.append(start) - return ans -``` - -# 10. [和为 K 的子数组](https://leetcode.cn/problems/subarray-sum-equals-k/)560-251212 - -tag: 前缀和 - -维护一个前n项和 sumn 然后 如果遇到 现在的 sumn - k 存在(x个) 那么就 有x个子数组符合 -```python -class Solution: - def subarraySum(self, nums: List[int], k: int) -> int: - ans,sumn = 0, 0 - cnt = collections.defaultdict(int) - for n in nums: - cnt[sumn] += 1 - sumn += n - ans += cnt[sumn - k ] - return ans -``` - -直到思路之后写了一次也没写对,当成两数之和了,存了index 应该是维护一个sumn为一个数的序列个数,之后再做一次 - -# 11. [滑动窗口最大值](https://leetcode.cn/problems/sliding-window-maximum/)239-251215 - -tag: 大根堆 / 单调队列 优先队列 - -## 思路一:大根堆 - -对于最大值这种问题,可以 维护一个 优先队列(大根堆)来做,也就是放入新元素是按照 我们想要的顺序存好。这个思路很容易想到,要善用这个数据结构。暴力的方法是,每次加入新元素就用$O(k)$ 来找一下窗口中的最大值,但是每个元素在整个流程中几乎被遍历k次,可以节省时间的方法就是记录在窗口中的元素的大小信息。也就是排序,但是如果每次来新的都排序,时间反而大于$O(k)$,因此需要我们维护一个 大根堆来做。在python中 最小堆是 :heapq.heapify(list) 可以将一个list转换成最**小**堆 这个只保证**堆顶是 最小的** 而不是有序的 - -```python -class Solution: - def maxSlidingWindow(self, nums: List[int], k: int) -> List[int]: - maxheap = [(-nums[i],i) for i in range(0,k) ] - # 这个python 标准库的最小堆 - heapq.heapify(maxheap) - ans = [ -maxheap[0][0] ] - for i in range(k,len(nums)) : - heapq.heappush(maxheap,(-nums[i],i)) - while maxheap[0][1] < i-k+1 : - heapq.heappop(maxheap) - ans.append(-maxheap[0][0]) - return ans -``` - -## 思路二:单调队列(优先队列)\* - -对于下标 $i List[int]: - q = collections.deque() - ans = [] - for i in range(0,len(nums)) : - while q and nums[i] >= nums[q[-1]] : - q.pop() - q.append(i) - while q[0] <= i-k : - q.popleft() - if i >= k-1 : - ans.append(nums[q[0]]) - return ans -``` - -维护了这样的单调递减序列,保证 开头是最大的,而且开头还得在窗口中。 - -# 12. [最小覆盖子串](https://leetcode.cn/problems/minimum-window-substring/)76-251215 - -tag: 滑动窗口 - -```python -class Solution: - def minWindow(self, s:str, t: str) -> str: - # 维护一个缺少多少的表 - w = collections.deque() - rq = {} - ansl ,l = 9999999,0 - ans = '' - for c in t : - if c in rq.keys() : - rq[c] += 1 - else : - rq[c] = 1 - # 处理队列,保证 rq 都得是 0 - for c in s : - w.append(c) - l += 1 - if c in rq.keys() : - rq[c] -= 1 - while w : - if w[0] in rq.keys() : - print(rq[w[0]]) - if w[0] in rq.keys() : - if rq[w[0]] >= 0 : - break - else : - rq[w[0]] += 1 - w.popleft() - l -= 1 - jump = False - for i in rq.values() : - if i > 0 : - jump = True - if ansl > l and jump == False : - ansl = l - ans = ''.join(w) - return ans -``` - -# 13. [最大子数组和](https://leetcode.cn/problems/maximum-subarray/)53-251215 - -tag: 动态规划 分治法 前缀和 做题目的时候不要被刷过的题目的思路限制住方向 也要做新的想法尝试 - -## 思路一:DP - -以 index 为 **结尾** 的最大子序列和 -```python -class Solution : - def maxSubArray(self, nums: List[int]) -> int : - # 以index 为结尾的最大值 - dp = [0] * len(nums) - for i,n in enumerate(nums) : - dp[i] = max(dp[i-1]+nums[i],nums[i]) - return max(dp) -``` - -## 思路二:分治法(还没学1216) - -官方题解说这个类似于 「线段树求解最长公共上升子序列问题」的pushup操作 - - - -## 思路三: 前缀和 - -参考 5盛水最多的容器中的动态规划方法,记录一个index左边最大和右边最小 -```python -# class Solution: -# def maxSubArray(self, nums: List[int]) -> int: -# # 一个index左边的最小值,一个index右边的最大值 -# leftMin,rightMax = [10001]*(len(nums)+1),[-10001]*(len(nums)+1) -# sn = [0] * (len(nums)+1) -# for i in range(len(nums)) : -# sn[i+1] = nums[i] -# for i in range(1,len(sn)) : -# sn[i] += sn[i-1] -# leftMin[0] = sn[0] -# rightMax[-1] = sn[-1] -# for i in range(1,len(sn)) : -# leftMin[i] = min(leftMin[i-1],sn[i]) -# rightMax[len(sn)-1-i] = max(rightMax[len(sn)-i],sn[len(sn)-1-i]) -# ans = -99999999 -# for i in range(len(sn)-1) : -# if ans < rightMax[i+1] - leftMin[i] : -# ans = rightMax[i+1] - leftMin[i] -# return ans -``` - -维护两个太慢了,因为这个主要的约束 是 最小值在最大值的右边,通过记录最小值,然后计算 s\[n] 和 min 的差 来更新 结果: - -```python -class Solution : - def maxSubArray(self, nums: List[int]) -> int : - tsum, tmin, ans = 0, 0, -99999 - for n in nums : - tsum += n - ans = max(ans,tsum-tmin) - tmin = min(tmin,tsum) - return ans -``` - -# 14. [合并区间](https://leetcode.cn/problems/merge-intervals/)56-251216 - -tag: 排序 数组 单调栈 - -这个重点在于 排序的时候使用 lambda 表达式 lambda是个函数,lambda x: y 意思是输入 x 输出 y ,排序时候用sort(key =) - -```python -# 20ms -class Solution: - def merge(self, intervals: List[List[int]]) -> List[List[int]]: - intervals.sort(key=lambda x: x[0]) - i = 0 - while True : - if i+1 >= len(intervals) : - break - if intervals[i][1] >= intervals[i+1][0] : - new_iv = [intervals[i][0],max(intervals[i][1],intervals[i+1][1])] - intervals.remove(intervals[i+1]) - intervals[i] = new_iv - else : i += 1 - return intervals -``` - -现在这种 每次的 remove 很慢 总体 20ms左右,还有一种方法是 维护一个 单调的栈,可以合并就合并后压栈否则就直接压. 这样结果就会快一点 - -```python -# 5ms -class Solution: - def push(self,res_inv,item) : - if len(res_inv) == 0 : - res_inv.append(item) - top = res_inv[-1] - if top[1] >= item[0] : - res_inv.pop() - item = [top[0],max(top[1],item[1])] - res_inv.append(item) - return res_inv - - def merge(self, intervals: List[List[int]]) -> List[List[int]]: - ans = [] - intervals.sort(key=lambda x:x[0]) - for iv in intervals : - self.push(ans,iv) - return ans -``` - -# 15. [轮转数组](https://leetcode.cn/problems/rotate-array/)189-251216 - -```python -class Solution: - def rotate(self, nums: List[int], k: int) -> None: - """ - Do not return anything, modify nums in-place instead. - """ - k = k%len(nums) - nums[:] = nums[len(nums)-k:] + nums[:len(nums)-k] -``` -需要注意的两个是 k 可能超过 nums 的长度,需要通过%来计算 时间移动了多少步 -然后 下一行中 如果 nums\[:] 写成 nums ,那么nums就不会被修改 这个是python的一个特性 写成nums就是修改的这个局部变量 而不是修改了这个引用。 - -这个时间复杂度是 $O(n)$ - -\*最快的办法是反转数组: - -```python -class Solution: - def rotate(self, nums: List[int], k: int) -> None: - """ - Do not return anything, modify nums in-place instead. - """ - - # [1,2,3,4,5,6,7] - # 1. 反转数组 [7,6,5,4,3,2,1] - # 2. 反转前k=3个元素 [5,6,7, 4,3,2,1] - # 3. 反转剩余元素 [5,6,7, 1,2,3,4] - n = len(nums) - k = k % n - nums.reverse() - nums[:k] = nums[:k][::-1] - nums[k:] = nums[k:][::-1] -``` - -# 16. [除自身以外数组的乘积](https://leetcode.cn/problems/product-of-array-except-self/)238-251216 - -#前后缀 - -## 思路一:前后缀积 - -这个 也是用 前缀后缀积 但是不需要 维护两个list 因为我们知道最后的目的是 在 index位置 ans\[i] = $\prod_{j\ne i}nums_j$ 所以不妨 先正向遍历拿到前面的积 再反向遍历,拿到后面的积。 - -```python -class Solution: - def productExceptSelf(self, nums: List[int]) -> List[int]: - length = len(nums) - answer = [0] * length - answer[0] = 1 - for i in range(1, length): - answer[i] = nums[i - 1] * answer[i - 1] - R = 1 - for i in reversed(range(length)): - answer[i] = answer[i] * R - R *= nums[i] - return answer -``` - -## UES - -这个解法考虑了0 同时用了reduce函数可以看一下 - -```python -class Solution: - def productExceptSelf(self, nums: List[int]) -> List[int]: - if 0 in nums: - ret= [0]*len(nums) - if nums.count(0)>=2: - return ret - ret[nums.index(0)]=reduce(mul,(i for i in nums if i!=0),) - return ret - p=reduce(mul,nums,) - if p!=0: - return [p//i for i in nums] -``` -## Trash - -用 前缀积 后缀积 来在 $O(n)$ 时间复杂度解决这个问题 但是 空间复杂度是 $O(n)$ - -```python -class Solution: - def productExceptSelf(self, nums: List[int]) -> List[int]: - fD, bD = nums[:], nums[:] # 定义前缀后缀积 - for i in range(1,len(nums)) : - fD[i] *= fD[i-1] - bD[len(nums)-i-1] *= bD[len(nums)-i] - ans = [ bD[1] ] - for i in range(1,len(nums)-1) : - ans.append( fD[i-1] * bD[i+1] ) - ans.append(fD[-2]) - return ans -``` - -# 17. [缺失的第一个正数](https://leetcode.cn/problems/first-missing-positive/)41-251216 - - -## 思路一:原地哈希(节省内存) - -参考这个:[寻找文件副本](https://leetcode.cn/problems/shu-zu-zhong-zhong-fu-de-shu-zi-lcof/) - -ans 最大的情况 就是 1,len(nums)都在 ,那么如果把 nums 中的 值 作为 index 那么肯定有 index 用不完的情况,第一个没用的就是 答案。 -v\[index] 是负数,那么这个index 可以直接拿来用,如果 v\[i]>0 那么我们就需要把 这个 v\[i] 也给记录下来。向链表一样顺着找 - -```python -class Solution: - def firstMissingPositive(self, nums: List[int]) -> int: - l = len(nums) - for i in range(len(nums)) : - target = nums[i]-1 - if target < 0 or target >= l : - continue - while nums[target] <= l and nums[target] > 0 : - if nums[target] == target + 1 : - break - next_target = nums[target] - 1 - nums[target] = target + 1 - target = next_target - nums[target] = target + 1 - for i in range(len(nums)) : - if i+1 != nums[i] : - return i+1 - else : - return l+1 -``` - - -题解中的原地哈希,这个比上面的方法更好 -```python -class Solution: - def firstMissingPositive(self, nums: List[int]) -> int: - n = len(nums) - for i in range(n): - if nums[i] <= 0: - nums[i] = n + 1 - for i in range(n): - num = abs(nums[i]) - if num <= n: - nums[num - 1] = -abs(nums[num - 1]) - for i in range(n): - if nums[i] > 0: - return i + 1 - return n + 1 -``` -## Trash -题目中限制了空间复杂度是常数,如果不考虑空间,这个方法很快 -```python -class Solution: - def firstMissingPositive(self, nums: List[int]) -> int: - ans = len(nums)+1 - s = set(nums) - for i in range(len(nums),0,-1) : - if i in s : - continue - else : ans = min(ans,i) - return ans -``` - -# 18. [矩阵置零](https://leetcode.cn/problems/set-matrix-zeroes/)73-251216 - -先处理行再处理列 这个是 $O(mn)$ 时间复杂度。空间复杂度是 $O(n)$ - -```python -class Solution: - def setZeroes(self, matrix: List[List[int]]) -> None: - """ - Do not return anything, modify matrix in-place instead. - """ - c_index = set() - for i,n in enumerate(matrix) : - if 0 in n : - index = [ -1 if n[index]!=0 else index for index in range(len(n)) ] - [c_index.add(x) for x in index] - matrix[i] = [0] * len(n) - for i,n in enumerate(matrix) : - for j in range(len(n)) : - if j in c_index: - matrix[i][j] = 0 -``` - -# 19. [螺旋矩阵](https://leetcode.cn/problems/spiral-matrix/)54-251216 - -遇到墙换方向 - -```python -class Solution: - def spiralOrder(self, matrix: List[List[int]]) -> List[int]: - ans = [] - i,j = 0,0 - down = len(matrix) - right = len(matrix[0]) - total = down * right - left = 1 - up = 1 - - di,dj = 0,1 - cnt = 0 - if right == 1 : - ans = [ i[0] for i in matrix ] - return ans - - while True : - print(i,j) - ans.append(matrix[i][j]) - cnt += 1 - if cnt == total : - break - i,j = i+di,j+dj - if j == right-1 and di == 0 and dj == 1 : - di,dj = 1,0 - up = up + 1 - if i == down-1 and di == 1 and dj == 0 : - di,dj = 0,-1 - right = right - 1 - if j == left - 1 and di == 0 and dj == -1 : - di,dj = -1,0 - down = down - 1 - if i == up - 1 and di == -1 and dj == 0 : - di,dj = 0,1 - left = left + 1 - return ans -``` - - -# 20. [旋转图像](https://leetcode.cn/problems/rotate-image/)48-251216 - -主要是下标处理 这几个矩阵相关的题目 - -```python -class Solution: - def p_single_pos(self,nums,i,j): - self.swap(nums,i,j,j,self.col-i-1) - self.swap(nums,i,j,self.row-i-1,self.col-j-1) - self.swap(nums,i,j,self.row-j-1,i) - - def swap(self,nums,i,j,k,l): - temp = nums[i][j] - nums[i][j] = nums[k][l] - nums[k][l] = temp - - def rotate(self, matrix: List[List[int]]) -> None: - """ - Do not return anything, modify matrix in-place instead. - """ - self.row = len(matrix) - self.col = len(matrix[0]) - for i in range(self.col//2 + 1) : - pos_list = [ [i,x] for x in range(i,self.col-i-1) ] - for t in pos_list : - self.p_single_pos(matrix,t[0],t[1]) -``` - -# 21. [搜索二维矩阵 II](https://leetcode.cn/problems/search-a-2d-matrix-ii/)240-251216 - -tag: 二分查找 分治法 - -## 思路一:Z字形查找 - -最简单的想法,时间复杂度是 $O(n+m)$ - -```python -class Solution: - def searchMatrix(self, matrix: List[List[int]], target: int) -> bool: - maxColIndex = len(matrix[0])-1 - for i,n in enumerate(matrix[0]) : - if n >= target : - maxColIndex = i-1 - if n == target : - return True - print(maxColIndex) - for i in range(0,maxColIndex+1): - for j in range(len(matrix)) : - if matrix[j][i] == target : - return True - if matrix[j][i] > target : - break - return False -``` - -## 思路二:二分查找 - -```python -class Solution: - def searchMatrix(self, matrix: List[List[int]], target: int) -> bool: - for row in matrix: - if row[0] > target : - break - idx = bisect.bisect_left(row, target) - if idx < len(row) and row[idx] == target: - return True - return False -``` - -防止不会 自己写一下 binary search -```python -class Solution: - def bs(self,nums,target): - mid = (len(nums))//2 - print(mid,nums) - if nums[0] > target : - return None - if nums[-1] < target : - return None - if nums[mid] > target : - return self.bs(nums[0:mid],target) - elif nums[mid] < target : - return self.bs(nums[mid+1:],target) - else : return mid - - def searchMatrix(self, matrix: List[List[int]], target: int) -> bool: - for row in matrix: - if row[0] > target : - break - idx = self.bs(row, target) - if idx!=None : - return True - return False -``` - -## 思路三:贪心Z* 从左下角开始搜索! - -操了 这个和前面的 Z搜索区别在于 从左下角开始搜索,这样的话就可以沿着 减增减 的序列搜索了 可以排除更多不可能区域. 我觉得这个想法**非常好**。 -[参考题解](https://leetcode.cn/problems/search-a-2d-matrix-ii/solutions/2361487/240-sou-suo-er-wei-ju-zhen-iitan-xin-qin-7mtf) - -```python -class Solution: - def searchMatrix(self, matrix, target) -> bool : - i, j = len(matrix)-1,0 - while i>=0 and j<=len(matrix[0])-1 : - if matrix[i][j] == target : - return True - i,j = (i-1,j) if matrix[i][j] > target else (i,j+1) - # if matrix[i][j] > target : - # i -= 1 - # else : - # j += 1 - return False -``` - -# 22. [相交链表](https://leetcode.cn/problems/intersection-of-two-linked-lists/)160-251217 - -## 思路一:双指针\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\* - -这个太帅了 -![[Pasted image 20251217145442.png]] - -```python -class Solution: - def getIntersectionNode(self, headA: ListNode, headB: ListNode) -> Optional[ListNode]: - pA, pB = headA, headB - while pA != pB : - pA = pA.next if pA else headB - pB = pB.next if pB else headA - return pA -``` - - -## 思路二:哈希 - -在这个过程中改变了list结构,这样的方法就是记录那些节点是访问过的 -可以 用一个 set 记录哪些访问过,就不用这个了 但是用 Set 需要 o(m)的空间 - -```python -class Solution: - def getIntersectionNode(self, headA: ListNode, headB: ListNode) -> Optional[ListNode]: - startA = headA - while headA : - headA.val = -1 - headA.val - headA = headA.next - ans = None - while headB : - if headB.val < 0 and ans == None : - # headB.val = -headB.val - 1 - ans = headB - break - headB = headB.next - headA = startA - while headA : - headA.val = -headA.val - 1 - headA = headA.next - return ans -``` - -## 思路三:对齐开始 - -先全部遍历一下,找到两个的长度,然后将较长的往前移动几个,这下 AB list 长度相同,接下来就直接一起同步 next 就可以 但是 实际时间复杂度是 O(m+2n-len(chonghe)) - -# 23. [反转链表](https://leetcode.cn/problems/reverse-linked-list/)206-251217 - -tag: 递归 迭代 非暴力迭代 - -## 思路一:递归 - -写递归算法时候的一定要理清思路,想好怎么递归调用的。 - -首先思路很明确,就是要reverse \[A, ...] 就是 reverse ... ,然后把A 拼在后面。 有两点需要注意的,第一个是A 的next 需要变成None ,第二个就是 ... 的tail 的next 需要变成 A 然后递归下去就好。 - -另外做这个题的时候需要 return list 的 head 所以要在开始先把 tail 给记录下来以return - -```python -# 递归 -class Solution: - # reverse head 后面的 包括head - def r(self,head) : - # print(head) - # tail = head - # while tail.next!=None : - # tail = tail.next - if head.next : - self.r(head.next) - head.next.next = head - head.next = None - # print(tail) - - def reverseList(self, head: Optional[ListNode]) -> Optional[ListNode]: - if head == None : - return - tail = head - while tail.next!=None : - tail = tail.next - self.r(head) - return tail -``` - -## 思路二:迭代*: - -找到head开头的链的末端 往前指 前一个指空 (这是一个$O(n^2)$的垃圾方法),好好理理下面的那个 - -```python -# 不用看这个 复杂度不对 -class Solution: - def reverseList(self, head: Optional[ListNode]) -> Optional[ListNode]: - tail = head - if head is None : - return - while tail.next : tail = tail.next - while head.next : - # 找head的tail - p = head - pre = None - while p.next : - pre = p - p = p.next - else : - p.next = pre - pre.next = None - return tail -``` - -这个时间复杂度很高 应该换一个,逐步对每个都处理: - -```python -class Solution: - def reverseList(self, head: Optional[ListNode]) -> Optional[ListNode]: - prev, p = None, head - while p : - n = p.next - p.next = prev - prev = p - p = n - return prev -``` - -后来又写了一个 和这个人的方法 一模一样了,是这样做到的:反转列表 对于每一个 node ,next置none 然后把原本的next 的 next 置为这个node 这样会得到一个很不好的 循环。但是换一个思路,从第二个开始来说 相当于我要做的是 将这个 next 置为 prev (而不是把下一个的next 置为自己,这个是区别所在),那么我需要保留prev即可 然后 这个ptr 每次移动一个。由于next会改,所以要在最开始先记住node.next - -```python - def reverseKOnce(self, head) : - prev = None - while head : - n = head.next - head.next = prev - prev = head - head = n - return prev -``` -# 24. [回文链表](https://leetcode.cn/problems/palindrome-linked-list/)234-251217 - -## 思路一: 栈判断 - -用栈判断 但是感觉写的不好 很多下标处理: - -```python -class Solution: - def isPalindrome(self, head: Optional[ListNode]) -> bool: - # 先统计长度 然后用栈 判断 - stack = [] - p, l = head, 0 - while p : - p = p.next - l += 1 - if l == 1 : - return True - mid = l//2 - isJumpMid = True if l%2 == 1 else False - i, p = 1, head - while p : - if i <= mid : - stack.append(p.val) - else : - if stack.pop() != p.val : - return False - if i == mid and isJumpMid : - i+=1 - p = p.next - i+=1 - if p : - p = p.next - return True -``` - -## 思路二:复制到数组后双指针 - -这个方法也很快 - -```python -class Solution: - def isPalindrome(self, head: ListNode) -> bool: - vals = [] - current_node = head - while current_node is not None: - vals.append(current_node.val) - current_node = current_node.next - return vals == vals[::-1] -``` - -## 思路三:快慢指针 - -反转后面部分的 list 然后再 用两个指针比较 $O(n)$ $O(1)$ - -```python -class Solution: - - def isPalindrome(self, head: ListNode) -> bool: - if head is None: - return True - - # 找到前半部分链表的尾节点并反转后半部分链表 - first_half_end = self.end_of_first_half(head) - second_half_start = self.reverse_list(first_half_end.next) - - # 判断是否回文 - result = True - first_position = head - second_position = second_half_start - while result and second_position is not None: - if first_position.val != second_position.val: - result = False - first_position = first_position.next - second_position = second_position.next - - # 还原链表并返回结果 - first_half_end.next = self.reverse_list(second_half_start) - return result - - def end_of_first_half(self, head): - fast = head - slow = head - while fast.next is not None and fast.next.next is not None: - fast = fast.next.next - slow = slow.next - return slow - - def reverse_list(self, head): - previous = None - current = head - while current is not None: - next_node = current.next - current.next = previous - previous = current - current = next_node - return previous -``` - -# 25. [环形链表](https://leetcode.cn/problems/linked-list-cycle/)141-251217 - -tag: 快慢指针 集合 重复 -## 思路一:用集合判断是否访问过 - -这个方法空间复杂度是 $O(n)$ ,时间复杂度也是 $O(n)$ -```python -class Solution: - def hasCycle(self, head: Optional[ListNode]) -> bool: - visited = set() - while head : - if head in visited : - return True - visited.add(head) - head = head.next - return False -``` - -## 思路二: 快慢指针* - -$O(1)$ 空间复杂度,$O(n)$时间复杂度看是否重合。下面捋一下思路: -对于一个有环的 list ,环外的 长度为 $x$ ,环长度 为 $L$ ,那么如果一个快指针 一次走两个,慢指针一次走一个,那么,慢的先走x,同时 相当于 快的进入环内后走了x,然后两者相遇的方程 :$i=(x+2i)\%L$ -,对于这个 同余方程 一定有解,可以搜一下。 - -虽然快指针一次走两个,但是快慢之间的距离,其实是每次只**减少一**。所以一定会相遇 - -```python -# 快慢指针 -class Solution: - def hasCycle(self, head: Optional[ListNode]) -> bool: - slow, fast = head, head - if not head : - return False - while slow and fast : - slow = slow.next - fast = fast.next.next if fast.next and fast.next.next else None - if slow == fast and slow : - return True - return False -``` - -我的判断逻辑写得不好,可以学一下别人的: -```python -class Solution: - def hasCycle(self, head: Optional[ListNode]) -> bool: - slow, fast = head, head - while fast and fast.next: - slow = slow.next - fast = fast.next.next - if slow == fast: - return True - return False -``` - -# 26. [环形链表 II](https://leetcode.cn/problems/linked-list-cycle-ii/)142-251217 - -## 思路一:集合 - -这个空间复杂度高 简单 不写了 - -## 思路二:快慢指针* - -一个list 非环长 x 环长 L ,slow 走到环开头, fast 距离环开头 x 。此时 fast 还需要追 L-x ,由于每次 移动 fast 和 slow 的距离-=1 ,然后可以计算出 第一相遇位置,距离 环开头(倒退数) L-x (还需要L-x步,两者距离减为0),距离环结尾(前进数)x 。有了meetAt 这个点,再在 开头放置一个 ptr ,ptr 和 slow 一起同步移动,两者都移动 x 就都到了 开头。 - -```python -class Solution: - def detectCycle(self, head: Optional[ListNode]) -> Optional[ListNode]: - ptr, slow, fast, meetAt = head, head, head, None - # l = 0 - while fast and fast.next : - slow = slow.next - # l += 1 - fast = fast.next.next - if slow == fast : - meetAt = slow - break - if not meetAt : - return - while ptr != slow : - ptr = ptr.next - slow = slow.next - return ptr -``` - -我觉得这两个题都很有意思 - -# 27. [合并两个有序链表](https://leetcode.cn/problems/merge-two-sorted-lists/)21-251217 - -先找到小的开头,作为 p1 然后 比较插入即可 - -```python -class Solution: - def mergeTwoLists(self, list1: Optional[ListNode], list2: Optional[ListNode]) -> Optional[ListNode]: - if not list1 : - return list2 - if not list2 : - return list1 - if list1.val < list2.val : - p1, p2 = list1, list2 - else : - p1, p2 = list2, list1 - ans = p1 - while p2 : - # print(f'1 1: {p1}') - # print(f'1 2: {p2}') - while p1.next and p1.next.val <= p2.val : - # print(f'2 1: {p1}') - # print(f'2 2: {p2}') - p1 = p1.next - # print(f'2-1: {p1}') - # print(f'2-2: {p2}') - if p1.next : - # p2 插入到 p1 和 下一个之间 - # print(f'3 1: {p1}') - # print(f'3 2: {p2}') - p2n = p2.next - p1n = p1.next - p1.next = p2 - p2.next = p1n - p2 = p2n - else : - p1.next = p2 - break - return ans -``` - -学习一下别人写的,用了一个新的节点 作为开头,之后在这个开头后添加 : - -```python -class Solution: - def mergeTwoLists(self, list1: Optional[ListNode], list2: Optional[ListNode]) -> Optional[ListNode]: - dummy = cur = ListNode() - while list1 and list2: - if list1.val < list2.val: - cur.next = list1 - list1 = list1.next - else: - cur.next = list2 - list2 = list2.next - cur = cur.next - - cur.next = list1 or list2 - return dummy.next -``` - -# 28. [两数相加](https://leetcode.cn/problems/add-two-numbers/)2-251218 - -这个不难 重要的是细节处理 - -```python -class Solution: - def addTwoNumbers(self, l1: Optional[ListNode], l2: Optional[ListNode]) -> Optional[ListNode]: - Csignal = 0 - ans, pl1, pl2 = l1, None, None - while l1 and l2 : - val = (l1.val + l2.val + Csignal)%10 - Csignal = (l1.val + l2.val + Csignal)//10 - l1.val = val - pl1, pl2 = l1, l2 - l1, l2 = l1.next, l2.next - if not l1 and not l2 and Csignal: - pl1.next = ListNode(Csignal) - return ans - if not l1 : - pl1.next = l2 - l1 = pl1.next - while l1 : - val = (l1.val + Csignal)%10 - Csignal = (l1.val + Csignal)//10 - l1.val = val - if not l1.next and Csignal : - l1.next = ListNode(Csignal) - break - l1 = l1.next - return ans -``` - -# 29. [删除链表的倒数第 N 个结点](https://leetcode.cn/problems/remove-nth-node-from-end-of-list/)19-251218 - -#快慢指针 - -遍历一次的方法 是 快慢指针 这个一开始没想到 !!!!!!!!!!!! -相当于我手动留了两个距离为 n 的 ptr 后面的到了结尾 前面的到 倒数第n个 - -```python -class Solution: - def removeNthFromEnd(self, head: Optional[ListNode], n: int) -> Optional[ListNode]: - slow = fast = head - preSlow = None - while n : - fast = fast.next - n -= 1 - while fast : - fast = fast.next - preSlow = slow - slow = slow.next - if not preSlow : - return head.next - preSlow.next = slow.next - return head -``` - -# 30. [两两交换链表中的节点](https://leetcode.cn/problems/swap-nodes-in-pairs/)24-251218 - -虽然很简单,但是感觉写的不好,可以学一下别的人代码 - -```python -class Solution: - def swapPairs(self, head: Optional[ListNode]) -> Optional[ListNode]: - prev, ptr = None, head - ans = head.next if head and head.next else head - flag = 0 - while ptr : - if flag < 1 : - if prev : - prev.next = ptr.next if ptr.next else ptr - prev = ptr - ptr = ptr.next - flag += 1 - continue - flag = 0 - n = ptr.next - ptr.next = prev - prev.next = n - ptr = n - return ans -``` - -官方的迭代方法:我发现 官方在做list 相关的题目时候,喜欢用一个dummyhead来作为答案。应该参考一下这个想法 - -```python -class Solution: - def swapPairs(self, head: ListNode) -> ListNode: - dummyHead = ListNode(0) - dummyHead.next = head - temp = dummyHead - while temp.next and temp.next.next: - node1 = temp.next - node2 = temp.next.next - temp.next = node2 - node1.next = node2.next - node2.next = node1 - temp = node1 - return dummyHead.next -``` - -# 31. [K 个一组翻转链表](https://leetcode.cn/problems/reverse-nodes-in-k-group/)25-251218 - -上一题是这个题目 k=2 的特殊情况。用迭代的做法写,逐个找所有的k长子list 然后reverse - -```python -class Solution: - # 反转k个 - def reverseKOnce(self, head) : - tail = head - prev = None - while head : - n = head.next - head.next = prev - prev = head - head = n - return prev,tail - - def reverseKGroup(self, head: Optional[ListNode], k: int) -> Optional[ListNode]: - ans = prev_subtail = ListNode(0) - if k == 1: - return head - n = None - while True : - start = head - sublen = 1 - while head : - head = head.next - sublen += 1 - if sublen == k and head : - n = head.next - head.next = None - head = n - break - if head or not n: - subhead,subtail = self.reverseKOnce(start) - prev_subtail.next = subhead - prev_subtail = subtail - if not n : - break - else : - prev_subtail.next = start - break - return ans.next -``` - -# 32. [随机链表的复制](https://leetcode.cn/problems/copy-list-with-random-pointer/)138-251218-重新写 - -这个方法复杂度应该是 $O(n^2)$ 先按照next 的方向复制list 然后找random 方法是 对于一个node 从两个list的头开始找,知道找origin的random 然后就把 新的random也置为这个 - -```python -class Solution: - def copyRandomList(self, head: 'Optional[Node]') -> 'Optional[Node]': - p = dummyHead = Node(0) - phead = head - while phead : - p.next = Node(phead.val) - phead = phead.next - p = p.next - # 到这里先复制list 到dummy 之后处理random - p = dummyHead.next - p_origin = head - while p : - if p_origin.random is None : - p.random = None - p = p.next - p_origin = p_origin.next - if not p : - break - - q_origin = head - q = dummyHead.next - while q : - if q_origin == p_origin.random : - p.random = q - break - q = q.next - q_origin = q_origin.next - p = p.next - p_origin = p_origin.next - return dummyHead.next - - while dummyHead : - print(f"{dummyHead.val}-{dummyHead.random.val if dummyHead and dummyHead.random else 'null'}",end=' ') - dummyHead = dummyHead.next -``` - -别人的方法 还没看 - -```python -class Solution: - def copyRandomList(self, head: 'Optional[Node]') -> 'Optional[Node]': - if head is None: - return None - - - cur = head - while cur: - cur.next = Node(cur.val, cur.next) - cur = cur.next.next - - cur = head - while cur: - if cur.random: - cur.next.random = cur.random.next - cur = cur.next.next - - cur = head.next - while cur.next: - cur.next = cur.next.next - cur = cur.next - - return head.next -``` - -必须要看看题解 然后好好做一下 这会儿太累了,之后看吧留个坑 - -# 33. [排序链表](https://leetcode.cn/problems/sort-list/)148-251219*-重新写 - -#并归排序 - -这个可以分为 自底向上并归 和 自顶向下并归(需要递归)。 - -自顶向下,用了递归 空间复杂度是 $O(logn)$ -```python -class Solution: - def merge(self, head1: Optional[ListNode], head2: Optional[ListNode]) : - p = dummy = ListNode(-999) - while head1 and head2 : - if head1.val < head2.val : - p.next = head1 - head1 = head1.next - else : - p.next = head2 - head2 = head2.next - p = p.next - if head1 : - p.next = head1 - if head2 : - p.next = head2 - return dummy.next - - # start包括 end不包括 - def sort(self,start ,end) : - if not start : - return start - if start.next == end : - start.next = None - return start - fast = slow = start - while fast != end : - slow = slow.next - fast = fast.next - if fast != end : - fast = fast.next - mid = slow - return self.merge(self.sort(start,mid),self.sort(mid,end)) - - - def sortList(self, head: Optional[ListNode]) -> Optional[ListNode]: - return self.sort(head,None) -``` - -自底向上:这个我写不动了 之后回来再写一次吧,重要的是 获取长度 然后处理 - -```python -class Solution: - def merge(self, head1: Optional[ListNode], head2: Optional[ListNode]) : - p = dummy = ListNode(-999) - while head1 and head2 : - if head1.val < head2.val : - p.next = head1 - head1 = head1.next - else : - p.next = head2 - head2 = head2.next - p = p.next - if head1 : - p.next = head1 - if head2 : - p.next = head2 - return dummy.next - - def sortList(self, head: Optional[ListNode]) -> Optional[ListNode]: - length = 0 - node = head - while node: - length += 1 - node = node.next - - dummyHead = ListNode(0, head) - subLength = 1 - while subLength < length: - prev, curr = dummyHead, dummyHead.next - while curr: - head1 = curr - for i in range(1, subLength): - if curr.next: - curr = curr.next - else: - break - head2 = curr.next - curr.next = None - curr = head2 - for i in range(1, subLength): - if curr and curr.next: - curr = curr.next - else: - break - - succ = None - if curr: - succ = curr.next - curr.next = None - - merged = self.merge(head1, head2) - prev.next = merged - while prev.next: - prev = prev.next - curr = succ - subLength <<= 1 - - return dummyHead.next -``` - -# 34. [合并 K 个升序链表](https://leetcode.cn/problems/merge-k-sorted-lists/)23-251219 - -最开始想的用单调栈 好像不可行,现在考虑用最小堆 - -```python -class Solution: - def mergeKLists(self, lists: List[Optional[ListNode]]) -> Optional[ListNode]: - # val,node - minHeapNode = [] - for i, n in enumerate(lists) : - if n : - minHeapNode.append((n.val,i,n)) - heapq.heapify(minHeapNode) - p = dummy = ListNode(0) - i = len(lists) - while minHeapNode : - # print(minHeapNode) - top = heapq.heappop(minHeapNode)[2] - p.next = top - p = p.next - if top.next : - heapq.heappush(minHeapNode,(top.next.val,i,top.next)) - i += 1 - return dummy.next -``` - -# 35. [LRU 缓存](https://leetcode.cn/problems/lru-cache/)146-251219 - -## 题解思路:双链表+hash - -用collections . orderDict - -双链表用来维护出入队列,然后hash用来快速根据key找到value - -```python -class LRUCache: - def __init__(self, capacity: int): - self.used = 0 - self.capacity = capacity - self.dummyStart = Node(-2,-2,None,None) - self.dummyEnd = Node(-1,-1,None,None) - self.dummyStart.next = self.dummyEnd - self.dummyEnd.prev = self.dummyStart - self.key2addr = {} - - def get(self, key: int) -> int: - if not key in self.key2addr.keys() : - return -1 - ptr = self.key2addr[key] - ptr.prev.next = ptr.next - ptr.next.prev = ptr.prev - - ptr.next = self.dummyEnd - ptr.prev = self.dummyEnd.prev - self.dummyEnd.prev.next = ptr - self.dummyEnd.prev = ptr - # self.print('get') - return ptr.value - - - def put(self, key: int, value: int) -> None: - if key in self.key2addr.keys() : - self.removeNode(self.key2addr[key]) - self.used -= 1 - if self.used == self.capacity : - self.key2addr.pop(self.dummyStart.next.key) - self.removeNode(self.dummyStart.next) - else : self.used += 1 - new = Node(key=key,value=value,prev=self.dummyEnd.prev,next=self.dummyEnd) - self.dummyEnd.prev.next = new - self.dummyEnd.prev = new - self.key2addr[key] = new - # self.print('put') - - def removeNode(self,ptr) : - ptr.prev.next = ptr.next - ptr.next.prev = ptr.prev - - def print(self,str) : - p = self.dummyStart - print(f'{str}',end=' ') - while p : - print(f'({p.key}-{p.value})',end=' ') - p = p.next - print(f'used:{self.used}/{self.capacity}') - -class Node : - def __init__(self, key=0, value=0, next=None, prev=None) : - self.value = value - self.key = key - self.next = next - self.prev = prev -``` - -## Trash - -用队列,但是 get put 的复杂度是 O(capacity) remove -为什么要用双链?因为remove操作 需要 o(c) 去找到 value ,但是如果用Node,可以在字典中存入其地址。O(1) 就可以找到。 -```python -class LRUCache: - def __init__(self, capacity: int): - self.capacity = capacity - self.dic = {} - self.used = 0 # que 的 size - self.que = [] - def get(self, key: int) -> int: - if key not in self.dic.keys() : - return -1 - value = self.dic[key] - self.que.remove(key) - self.que.append(key) - # print(f'get {self.dic},{self.que}') - return value - def put(self, key: int, value: int) -> None: - if key in self.dic.keys() : - self.dic[key] = value - self.que.remove(key) - self.que.append(key) - # print(f'put {self.dic},{self.que}') - return - if self.used < self.capacity : - self.que.append(key) - self.dic[key] = value - self.used += 1 - # print(f'put {self.dic},{self.que}') - return - delkey = self.que[0] - self.que = self.que[1:] - self.dic.pop(delkey) - self.que.append(key) - self.dic[key] = value - # print(f'put {self.dic},{self.que}') -``` - - -# 36. [二叉树的中序遍历](https://leetcode.cn/problems/binary-tree-inorder-traversal/)94-251222 - -要知道中序遍历和先序遍历的while写法是不一样的,区别在于先append还是先 访问(cur=) -## 思路一:递归 - -这个写法比较简单 - -```python -class Solution: - def mid(self,root,ans): - if root : - self.mid(root.left,ans) - ans.append(root.val) - self.mid(root.right,ans) - - def inorderTraversal(self, root: Optional[TreeNode]) -> List[int]: - ans = [] - self.mid(root,ans) - return ans -``` - -## 思路二:迭代\*\* - -我觉得这个迭代写的很好,下来再看看 - -```python -class Solution: - def inorderTraversal(self, root: Optional[TreeNode]) -> List[int]: - stack = [] - ans = [] - while stack or root : - while root : - stack.append(root) - root = root.left - root = stack.pop() - ans.append(root.val) - root = root.right - return ans -``` - -自己写的 -```python - stk = [] - while stk or root : - while root : - stk.append(root) - root = root.left - root = stk.pop() - print(root.val, end=' ') - print(stk) - root = root.right -``` - -# 37. [二叉树的最大深度](https://leetcode.cn/problems/maximum-depth-of-binary-tree/)104-251222 - -## 思路一:DFS - -```python -class Solution: - def maxDepth(self, root: Optional[TreeNode]) -> int: - if not root : return 0 - return max(self.maxDepth(root.left)+1,self.maxDepth(root.right)+1) -``` -## 思路二:BFS - -```python -class Solution: - def maxDepth(self, root: Optional[TreeNode]) -> int: - ans = 0 - que = [root] - next_que = [] - if not root : - return 0 - while que : - node = que[0] - que.remove(node) - if node.left : - next_que.append(node.left) - if node.right : - next_que.append(node.right) - if not que : - ans += 1 - que = next_que - next_que = [] - return ans -``` - -# 38. [翻转二叉树](https://leetcode.cn/problems/invert-binary-tree/)226-251222 - -## 思路一:BFS - -对每一个节点 进行左右节点的互换,因为所有节点只访问一次。所以可行 - -```python -class Solution: - def invertTree(self, root: Optional[TreeNode]) -> Optional[TreeNode]: - que = [root] - next_que = [] - if not root : - return root - while que : - node = que[0] - que.remove(node) - t = node.left - node.left = node.right - node.right = t - if node.left : - next_que.append(node.left) - if node.right : - next_que.append(node.right) - if not que : - que = next_que - next_que = [] - return root -``` - -## 思路二:递归 - -递归的方法都比较简单 - -```python -class Solution: - def invertTree(self, root: Optional[TreeNode]) -> Optional[TreeNode]: - if not root : - return - t = root.left - root.left = root.right - root.right = t - self.invertTree(root.left) - self.invertTree(root.right) - return root -``` - -# 39. [对称二叉树](https://leetcode.cn/problems/symmetric-tree/)101-251222 - -## 思路一:递归 - -递归的思路比较简单 - -```python -class Solution: - def digui(self,left,right): - if not left and not right : - return True - if left and right and left.val == right.val : - return True & self.digui(left.left,right.right) & self.digui(left.right,right.left) - else : return False - - def isSymmetric(self, root: Optional[TreeNode]) -> bool: - return self.digui(root,root) -``` - -## 思路二:迭代 - -```python -class Solution: - def isSymmetric(self, root) -> bool : - left_ptr = right_ptr = root - left_que = [root.left] - right_que = [root.right] - while left_que and right_que : - left_ptr, right_ptr = left_que[0], right_que[0] - left_que.remove(left_que[0]) - right_que.remove(right_que[0]) - if ( left_ptr and not right_ptr ) or ( not left_ptr and right_ptr ) : - return False - if left_ptr and right_ptr and left_ptr.val != right_ptr.val : - return False - if not left_ptr and not right_ptr : - continue - # append 的时候也要 对称 - left_que.append(left_ptr.left) - left_que.append(left_ptr.right) - right_que.append(right_ptr.right) - right_que.append(right_ptr.left) - return True - -``` - -# 40. [二叉树的直径](https://leetcode.cn/problems/diameter-of-binary-tree/)543-251222 - -## 思路一:递归 - -每一个节点的 计算出来的直径都是 左边的深度加右边的深度,只需要维护一个最大的就可以,但是这里把工具函数 以及self.ans写在solution函数内,这么做方便维护max - -```python -class Solution : - def diameterOfBinaryTree(self, root: Optional[TreeNode]) -> int: - self.ans = -1 - def DepthOfNode(n): - if not n : - return 0 - l = DepthOfNode(n.left) - r = DepthOfNode(n.right) - self.ans = max(self.ans, l+r) - return max(l,r)+1 - DepthOfNode(root) - return self.ans -``` - -## 思路二:迭代 - -找深度还是递归,但是只是用BFS来更新ans - -## Trash Ologn\^2 - -超时了,记录每个叶子节点的path 然后计算路径 - -```python -class Solution: - def distance(self,path1,path2) -> int : - # 非公共长度的和 - if path1 == '' or path2 == '' : - return max(len(path1),len(path2)) - i=0 - while path1[i] == path2[i] : - i += 1 - return len(path1) + len(path2) - i*2 - - - def diameterOfBinaryTree(self, root: Optional[TreeNode]) -> int: - que = deque([root]) - root.path = '' - mp = {} - paths = [] - while que : - ptr = que.popleft() - if not ptr : - continue - if not ptr.left and not ptr.right : - paths.append(ptr.path) - que.append(ptr.left) - que.append(ptr.right) - if ptr.left : ptr.left.path = ptr.path + 'l' - if ptr.right : ptr.right.path = ptr.path + 'r' - ans = len(paths[-1]) - for i in range(len(paths)) : - for j in range(i+1,len(paths)) : - ans = max(ans,self.distance(paths[i],paths[j])) - return ans -``` - -# 41. [二叉树的层序遍历](https://leetcode.cn/problems/binary-tree-level-order-traversal/)102-251222 - -BFS即可 - -```python -class Solution: - def levelOrder(self, root: Optional[TreeNode]) -> List[List[int]]: - if not root : return [] - que = deque([root]) - next_que = [] - ans = [] - while que : - ans.append([]) - while que : - cur = que.popleft() - if not cur : - continue - ans[-1].append(cur.val) - if cur.left : next_que.append(cur.left) - if cur.right : next_que.append(cur.right) - que = deque(next_que) - next_que = [] - return ans -``` - -# 42. [将有序数组转换为二叉搜索树](https://leetcode.cn/problems/convert-sorted-array-to-binary-search-tree/)108-251223 - -## 思路一:递归 - -一个sorted 数组转换成 树,root节点一定是mid,那么只需要向归并排序一样 依次将左右的的根节点找到即可 - -```python -class Solution: - def nums2tree(self, nums) -> TreeNode : - if nums == [] : return None - mid = len(nums)//2 - left_nums = nums[0:mid] if mid != 0 else [] - right_nums = nums[mid+1:] if mid Optional[TreeNode]: - return self.nums2tree(nums) -``` - -## 题解中有别的思路基于中序遍历 - -# 43. [验证二叉搜索树](https://leetcode.cn/problems/validate-binary-search-tree/)98-251223\*\*\* - -## 思路一:中序遍历判断数组是否有序 - -```python -class Solution: - - def isValidBST(self, root: Optional[TreeNode]) -> bool: - self.nums = [] - def front(root): - if not root : return - if root.left : - front(root.left) - self.nums.append(root.val) - if root.right : - front(root.right) - front(root) - p = -999999999999 - for n in self.nums : - if p >= n : return False - p = n - return True -``` - -## 思路二:递归\*\*\* - -我觉得这个递归有点难,需要处理 右子树的最小值不能超过root 左子树的最大值不能超过 root,学一下官方的思路。helper用来判断 一个树 是不是 BST。 - -```python -class Solution: - def isValidBST(self, root: TreeNode) -> bool: - def helper(node, lower = float('-inf'), upper = float('inf')) -> bool: - if not node: - return True - val = node.val - if val <= lower or val >= upper: - return False - if not helper(node.right, val, upper): - return False - if not helper(node.left, lower, val): - return False - return True - return helper(root) -``` - -下面是自己写的 上面是题解写的 题解写的递归思路 **更更更更** 好 - -```python -class Solution: - def isValidBST(self, root: Optional[TreeNode]) -> bool: - def isBST(node,lowwer=float('-inf'),upper=float('+inf')): - if not node : - return True - # print(node.left.val if node.left else None,node.val,node.right.val if node.right else None,lowwer,upper) - if ( node.left and node.left.val >= node.val ) or ( node.right and node.right.val <= node.val ): - return False - if (node.left and node.left.val <= lowwer) or (node.right and node.right.val >= upper) : - return False - return isBST(node.left,lowwer=lowwer,upper=node.val) and isBST(node.right,lowwer=node.val,upper=upper) - return isBST(root) -``` - -# 44. [二叉搜索树中第 K 小的元素](https://leetcode.cn/problems/kth-smallest-element-in-a-bst/)230-251223 - -## 思路一:中序遍历 - -将二叉查找树转换成数组 $O(n)$ 然后 查找一次即可 下面是递归写法 - -```python -class Solution: - def kthSmallest(self, root: Optional[TreeNode], k: int) -> int: - self.ans = [] - def mid_iter(root): - if not root : return - if root.left : mid_iter(root.left) - self.ans.append(root.val) - if root.right: mid_iter(root.right) - mid_iter(root) - return self.ans[k-1] -``` - - 下面是迭代的写法 注意每次while中的意思:遇到左叶子存在就优先处理左叶子,然后先把现在存起到栈中。知道遇到没有左叶子的,遍历这个节点,然后处理右叶子。这个流程构成了一个循环。 - - 换一种理解,先往左走找到最小的 然后 找到大的进入 stk的pop 和 进入右叶子 我觉得第一个更好理解一点。 - -```python -class Solution: - def kthSmallest(self, root: Optional[TreeNode], k: int) -> int: - stk = [] - nums = [] - while stk or root : - while root : - stk.append(root) - root = root.left - root = stk.pop() - nums.append(root.val) - k -= 1 - if k == 0 : - return root.val - root = root.right -``` - -## 思路二:记录node 的节点数(看题解)下面这两个还是学一下吧 - -## 思路三:转成AVL平衡二叉树(看题解) - -# 45. [二叉树的右视图](https://leetcode.cn/problems/binary-tree-right-side-view/)199-251223 - -## 思路一:BFS - -通过BFS把每一行加入队列的最后一个记录下来。 -```python -# BFS 找到最后一个 -class Solution: - def rightSideView(self, root: Optional[TreeNode]) -> List[int]: - que = deque([root]) - next_que = [] - ans = [] - if not root : return [] - while que : - ans.append(que[-1].val) - while que : - cur = que.popleft() - if not cur : continue - if cur.left : next_que.append(cur.left) - if cur.right: next_que.append(cur.right) - que = deque(next_que) - next_que = [] - return ans -``` - -## 思路二:DFS\*\*\*\* 之前都没写过DFS,DFS使用stk 把左右加进来 - -在深度优先搜索时 如果优先搜索 右侧节点,那么每层访问的第一个 节点 一定是最右侧的节点,那么我们可以记录一个深度。遇到第一个这个深度的节点,就记录下来。下面是题解的做法 : -```python -class Solution: - def rightSideView(self, root: TreeNode) -> List[int]: - rightmost_value_at_depth = dict() # 深度为索引,存放节点的值 - max_depth = -1 - - stack = [(root, 0)] - while stack: - node, depth = stack.pop() - - if node is not None: - # 维护二叉树的最大深度 - max_depth = max(max_depth, depth) - - # 如果不存在对应深度的节点我们才插入 - rightmost_value_at_depth.setdefault(depth, node.val) - - stack.append((node.left, depth + 1)) - stack.append((node.right, depth + 1)) - - return [rightmost_value_at_depth[depth] for depth in range(max_depth + 1)] -``` -要知道中序遍历和先序遍历的while写法是不一样的,区别在于先append还是先 访问(cur=) - -# 46. [二叉树展开为链表](https://leetcode.cn/problems/flatten-binary-tree-to-linked-list/)114-251223 - -## Trash 用了新的空间 - -```python -class Solution: - def flatten(self, root: Optional[TreeNode]) -> None: - """ - Do not return anything, modify root in-place instead. - """ - if not root : return - dummy = TreeNode(0,None,None) - ptr = dummy - stk = [root] - while stk : - cur = stk.pop() - if not cur : continue - ptr.right = TreeNode(cur.val,None,None) - ptr = ptr.right - # print(cur.val,end='') - stk.append(cur.right) - stk.append(cur.left) - - root.right = dummy.right.right - root.left = None -``` - -## 思路一:存下来prev - -因为cur已经被访问过了所以可以修改了,prev执指向现在cur 就可以修改其左右叶子值了 -```python -class Solution: - def flatten(self, root: Optional[TreeNode]) -> None: - """ - Do not return anything, modify root in-place instead. - """ - stack = [root] if root else [] - prev = None - while stack: - cur = stack.pop() - if prev: - prev.left, prev.right = None, cur - if cur.right: stack.append(cur.right) - if cur.left: stack.append(cur.left) - prev = cur -``` - - -## 思路二:找到前驱节点\*\*\* - - 这个题题解的方法也很多,之后仔细看一下。下面这个方法是常数空间复杂度的方法 - - ```python -class Solution: - def flatten(self, root: TreeNode) -> None: - curr = root - while curr: - if curr.left: - predecessor = nxt = curr.left - while predecessor.right: - predecessor = predecessor.right - predecessor.right = curr.right - curr.left = None - curr.right = nxt - curr = curr.right -``` - -# 47. [从前序与中序遍历序列构造二叉树](https://leetcode.cn/problems/construct-binary-tree-from-preorder-and-inorder-traversal/)105-251223 - -## 思路一:递归(没看 - -终点在于根据 index i 找到 preorder的左右 但是这个递归还是很慢 也是 Trash - -```python -class Solution: - def buildTree(self, preorder: List[int], inorder: List[int]) -> Optional[TreeNode]: - if preorder == [] : return None - # for i,n in enumerate(inorder) : - # if n == preorder[0] : - # break - i = inorder.index(preorder[0]) - left_inorder = inorder[0:i] - right_inorder = inorder[i+1:] - # 可以知道 i 左右的数量 - left_preorder = preorder[1:i+1] - # for x in preorder : - # if x in left_inorder : - # left_preorder.append(x) - right_preorder = preorder[i+1:] - # for x in preorder : - # if x in right_inorder : - # right_preorder.append(x) - # print(left_preorder,right_preorder) - return TreeNode(preorder[0],self.buildTree(left_preorder,left_inorder),self.buildTree(right_preorder,right_inorder)) -``` - -下面是我的改进 还不是最优的:重点在于通过v找i时候用hash 把$O(n)$复杂度降低为 $O(1)$ -```python -# # 递归 -class Solution: - def buildTree_helper(self, preorder: List[int], inorder: List[int], abs_start=0) -> Optional[TreeNode]: - if len(preorder) == 0 : return None - i = self.mp[preorder[0]] - abs_start - return TreeNode(preorder[0],self.buildTree_helper(preorder[1:i+1],inorder[0:i],abs_start=abs_start),self.buildTree_helper(preorder[i+1:],inorder[i+1:],abs_start=abs_start+i+1)) - def buildTree(self, preorder: List[int], inorder: List[int]) -> Optional[TreeNode]: - self.mp = { v:i for i,v in enumerate(inorder) } - return self.buildTree_helper(preorder, inorder) -``` - -\*\*\*\*\*\*下面是官方的递归方法,速度快了很多 - -```python -class Solution: - def buildTree(self, preorder: List[int], inorder: List[int]) -> TreeNode: - def myBuildTree(preorder_left: int, preorder_right: int, inorder_left: int, inorder_right: int): - if preorder_left > preorder_right: - return None - - # 前序遍历中的第一个节点就是根节点 - preorder_root = preorder_left - # 在中序遍历中定位根节点 - inorder_root = index[preorder[preorder_root]] - - # 先把根节点建立出来 - root = TreeNode(preorder[preorder_root]) - # 得到左子树中的节点数目 - size_left_subtree = inorder_root - inorder_left - # 递归地构造左子树,并连接到根节点 - # 先序遍历中「从 左边界+1 开始的 size_left_subtree」个元素就对应了中序遍历中「从 左边界 开始到 根节点定位-1」的元素 - root.left = myBuildTree(preorder_left + 1, preorder_left + size_left_subtree, inorder_left, inorder_root - 1) - # 递归地构造右子树,并连接到根节点 - # 先序遍历中「从 左边界+1+左子树节点数目 开始到 右边界」的元素就对应了中序遍历中「从 根节点定位+1 到 右边界」的元素 - root.right = myBuildTree(preorder_left + size_left_subtree + 1, preorder_right, inorder_root + 1, inorder_right) - return root - - n = len(preorder) - # 构造哈希映射,帮助我们快速定位根节点 - index = {element: i for i, element in enumerate(inorder)} - return myBuildTree(0, n - 1, 0, n - 1) - -``` - -## 思路二:迭代(没看 - -```python -class Solution: - def buildTree(self, preorder: List[int], inorder: List[int]) -> TreeNode: - if not preorder: - return None - - root = TreeNode(preorder[0]) - stack = [root] - inorderIndex = 0 - for i in range(1, len(preorder)): - preorderVal = preorder[i] - node = stack[-1] - if node.val != inorder[inorderIndex]: - node.left = TreeNode(preorderVal) - stack.append(node.left) - else: - while stack and stack[-1].val == inorder[inorderIndex]: - node = stack.pop() - inorderIndex += 1 - node.right = TreeNode(preorderVal) - stack.append(node.right) - - return root -``` - -## Trash:递归拆分先序nums时候超时 - -答案对但是超时了 - -```python -class Solution: - def buildTree(self, preorder: List[int], inorder: List[int]) -> Optional[TreeNode]: - if preorder == [] : return None - for i,n in enumerate(inorder) : - if n == preorder[0] : - break - left_inorder = inorder[0:i] - right_inorder = inorder[i+1:] - left_preorder = [] - for x in preorder : - if x in left_inorder : - left_preorder.append(x) - right_preorder = [] - for x in preorder : - if x in right_inorder : - right_preorder.append(x) - # print(left_preorder,right_preorder) - return TreeNode(preorder[0],self.buildTree(left_preorder,left_inorder),self.buildTree(right_preorder,right_inorder)) -``` - -# 48. [路径总和 III](https://leetcode.cn/problems/path-sum-iii/)437-251224 - -## 思路一:前缀和\* - -$O(n)$ 用递归深度搜索 去遍历所有节点,记录前缀和,然后用hash来找到对应的,如果找到就把他拿出。那么最大的问题是:例如根节点,hash中记录了左子树的所有需要的哈希值,然后对于右子树 如果查到,就会出现一条通过root的路径,解决办法就是:记录的 (需要找到的)prefix+sum 只在子树范围内生效。(我觉得也是这个题的难点) -hash中记录的是,prefix+sum(像两数之和一样,遇到val 就查 target-val在不在。但是这个是两数之差)。 - -```python -class Solution: - def pathSum(self, root: Optional[TreeNode], targetSum: int) -> int: - if not root : return 0 - mp = defaultdict(int) - mp[targetSum] = 1 - self.ans = 0 - - def DFS(root,fatherPrefix): - if not root : return - root.prefixSum = fatherPrefix + root.val - self.ans += mp[root.prefixSum] - mp[root.prefixSum+targetSum]+=1 - DFS(root.left,root.prefixSum) - DFS(root.right,root.prefixSum) - ########################################## - #这一句很重要 保证了 这个节点只在子树中记录在mp中 - mp[root.prefixSum+targetSum]-=1 ########## - - DFS(root,0) - - return self.ans -``` - -## 思路二:深度优先搜索-非最优 - -这个思路没啥用我觉得 时间复杂度是 $O(n^2)$ 上一个复杂度是 $O(n)$ 这个思路就是写一个helper 用于找到起点为root的所有和为sum 的path数量,然后(DFS)遍历所有的node 得到所有path - -```python -class Solution: - def pathSum(self, root: Optional[TreeNode], targetSum: int) -> int: - # 定义一个helper函数求出 以 root 为起点 并且和为 Sum 的数量 - def startRootSum(root,Sum) : - if not root : return 0 - return (1 if root.val == Sum else 0) + startRootSum(root.left,Sum-root.val) + startRootSum(root.right,Sum-root.val) - # 遍历所有节点,找到以所有节点开头 path 和为 targetSum 的数量 这个递归就是一个 DFS - if not root : return 0 - ans = 0 - ans += startRootSum(root,targetSum) - ans += self.pathSum(root.left,targetSum) - ans += self.pathSum(root.right,targetSum) - return ans -``` -## Trash 迭代前缀和 - -先尝试了用迭代方法去写前缀和:跑不通。接下来尝试用递归去求 - -```python -class Solution: - def pathSum(self, root: Optional[TreeNode], targetSum: int) -> int: - if not root : return 0 - self.mp = {targetSum:1} - self.ans = 0 - def DFS(root): - stk = [root] - root.prefixSum = root.val - while stk : - cur = stk.pop() - if cur == root.right or cur == root.left : - if root.val != 0: - self.mp = { targetSum:1 ,root.val+targetSum:1} - else : - self.mp = {targetSum:2} - print(cur.val,cur.prefixSum) - if cur.prefixSum in self.mp.keys(): - self.ans += self.mp[cur.prefixSum] - print(cur.val) - if targetSum+cur.prefixSum in self.mp.keys() : - self.mp[targetSum+cur.prefixSum] += 1 - else : - self.mp[targetSum+cur.prefixSum] = 1 - - if cur.right: - stk.append(cur.right) - cur.right.prefixSum = cur.prefixSum + cur.right.val - if cur.left : - stk.append(cur.left) - cur.left.prefixSum = cur.prefixSum + cur.left.val - print(self.mp,self.ans) - DFS(root) - print(self.ans) - return self.ans -``` - -print位置换一下就是前中后序遍历,但是迭代的写法很不一样,谦虚需要while + stk 中序需要 while+que -```python -def DFS(root): - if not root : return - DFS(root.left) - print(root.val) - DFS(root.right) -``` -# 49. [二叉树的最近公共祖先](https://leetcode.cn/problems/lowest-common-ancestor-of-a-binary-tree/)236-251224 - -## 思路一:记录所有节点的father,然后用set查 - -1.先用DFS遍历树,然后记录每个节点的father 2.把p的所有father存在在个set 3.遍历q的father(从q开始)遇到 在 set:p.fathers 中的节点就返回 - -```python -class Solution: - def lowestCommonAncestor(self, root: 'TreeNode', p: 'TreeNode', q: 'TreeNode') -> 'TreeNode': - def DFS(root): - if not root : return - if root.left : root.left.father = root - if root.right: root.right.father = root - DFS(root.left) - DFS(root.right) - DFS(root) - root.father = None - pFathers = set() - while p : - pFathers.add(p) - p=p.father - while q : - if q in pFathers : - return q - q = q.father -``` - -## 思路二:递归 - -写一个工具函数判断 这个root节点为根的树中包不包含 p,q ,那么最后结果一定是 ans 包含,ans的左右孩子都不包含,下面是自己的写法,但是感觉把问题写复杂了,之后可以学习一下题解的写法。 - -```python -class Solution: - def lowestCommonAncestor(self, root: 'TreeNode', p: 'TreeNode', q: 'TreeNode') -> 'TreeNode': - def DFS(root,p,q) : - if not root : return - root.p_in = False - root.q_in = False - if not root.left and not root.right : - root.q_in = False - root.p_in = False - if root.val == p.val : - root.p_in = True - root.q_in = False - # print(f'p-{root}') - if root.val == q.val : - root.p_in = False - root.q_in = True - # print(f'q-{root}') - DFS(root.left,p,q) - DFS(root.right,p,q) - lpi = root.left.p_in if root.left else False - rpi = root.right.p_in if root.right else False - lqi = root.left.q_in if root.left else False - rqi = root.right.q_in if root.right else False - root.p_in = lpi or rpi or root.p_in - root.q_in = lqi or rqi or root.q_in - # print(f'val:{root.val},p_in:{root.p_in},q_in:{root.q_in}') - DFS(root,p,q) - while root.p_in and root.q_in : - # print(root.val) - if root.left and root.left.p_in and root.left.q_in : - root = root.left - elif root.right and root.right.p_in and root.right.q_in : - root = root.right - else : return root -``` - -题解写法没有py我没粘 这个是前面的写法 (\* **这个想法好好啊** \*) - -```python -class Solution: - def lowestCommonAncestor(self, root: 'TreeNode', p: 'TreeNode', q: 'TreeNode') -> 'TreeNode': - # 如果当前节点为p或者q 公共父节点是p或者q本身 - # 如果左右都找到了可能的父节点,返回公共节点 - # 左右都没找到,返回空节点 - if root in (None,p,q): - # 3元组 - return root - left = self.lowestCommonAncestor(root.left,p,q) - right = self.lowestCommonAncestor(root.right,p,q) - - if left and right: - return root - - # 左和右 谁找到了就返回谁 - return left or right -``` - -# 50. [二叉树中的最大路径和](https://leetcode.cn/problems/binary-tree-maximum-path-sum/)124-251224 - -## 思路一:递归-从trash优化过来的 - -在trash中 每次求解 root 树的最大路径 都要再递归找各个节点的 路径,对这个做法优化:在一个递归中得到 从一个节点开始的最大值 ,这样不用每次找一个节点都要找。而是存在一个属性内 - -```python -class Solution: - # root 树的最大path - def maxPathSum(self, root: Optional[TreeNode]) -> int: - if not root : - return 0 - left_child_max = self.maxPathSum(root.left) if root.left else -1001 - right_child_max = self.maxPathSum(root.right) if root.right else -1001 - start_l_max = root.left.startRootMax if root.left else 0 - start_r_max = root.right.startRootMax if root.right else 0 - child_max = max(left_child_max,right_child_max) - root.startRootMax = root.val + max(0, start_l_max, start_r_max) - # print(f'val-{root.val},sMax-{root.startRootMax},{left_child_max}-{right_child_max}-{root.val + start_l_max + start_r_max}') - include_root = max(root.val, root.startRootMax, root.val + start_l_max + start_r_max) - return max(include_root,child_max) -``` - -这是一个题解的方法,可以参考一下:很简洁 - -```python -class Solution: - def maxPathSum(self, root: Optional[TreeNode]) -> int: - res = -inf - def findmax(root): - if not root: - return 0 - nonlocal res - left = findmax(root.left) - if left < 0: - left = 0 - right = findmax(root.right) - if right < 0: - right = 0 - if left + right + root.val > res: - res = left + right + root.val - return max(left,right) + root.val - findmax(root) - return res -``` - -参考后来写的方法!!!!!!!这个写的可以 -```python -class Solution: - def maxPathSum(self, root: Optional[TreeNode]) -> int: - self.ans = -inf - # 从root开始的最大path,但是在每个节点判断 root 连接两边的是不是更大 - def dfs(root): - if not root : return 0 - left = max(dfs(root.left),0) - right = max(dfs(root.right),0) - # print(root.val,left,right,left+right+root.val) - self.ans = max(self.ans,left+right+root.val) - return root.val + max(left,right) - dfs(root) - return self.ans -``` - -## Trash: 超时了 - -第一想法是这样的 但是有两层递归 $O(n^2)$ 太慢了 - -```python -class Solution: - # root 树的最大path - def maxPathSum(self, root: Optional[TreeNode]) -> int: - # 以root为起点的 path 最大值 - def startRootMax(root) : - if not root : - return 0 - return root.val + max(0, startRootMax(root.left), startRootMax(root.right)) - if not root : - return 0 - start_root = max(root.val, startRootMax(root), root.val + startRootMax(root.left)+startRootMax(root.right)) - return max(start_root,self.maxPathSum(root.left) if root.left else -1001,self.maxPathSum(root.right) if root.right else -1001) -``` - -# 51. [岛屿数量](https://leetcode.cn/problems/number-of-islands/)200-251224 - -## 思路一:DFS - -深度搜索,处理越界。如果还是1则置为已经遍历过 - -```python -class Solution: - def numIslands(self, grid: List[List[str]]) -> int: - def dfs(i,j): - if i<0 or i>=len(grid): return 0 - if j<0 or j>=len(grid[0]): return 0 - if grid[i][j] != '1' : return 0 - grid[i][j] = '2' - dfs(i-1,j) - dfs(i+1,j) - dfs(i,j-1) - dfs(i,j+1) - return 1 - ans = 0 - for i in range(len(grid)) : - for j in range(len(grid[0])) : - ans += dfs(i,j) - return ans -``` - -## 思路二:BFS 队列和栈 在入的时候就标记 - -发现一个问题:如果这么写的话会超时 因为同一个点多次入队。 - -```python -class Solution: - def numIslands(self, grid: List[List[str]]) -> int: - r = len(grid) - c = len(grid[0]) - ans = 0 - for i in range(r) : - for j in range(c) : - if grid[i][j] == '1' : - ans += 1 - que = deque([(i,j)]) - while que : - row,col = que.popleft() - grid[row][col] = '2' - for x,y in [ (row-1,col), (row+1,col), (row,col-1), (row,col+1) ] : - if 0<=x int: - r = len(grid) - c = len(grid[0]) - ans = 0 - for i in range(r) : - for j in range(c) : - if grid[i][j] == '1' : - ans += 1 - grid[i][j] = '2' - que = deque([(i,j)]) - while que : - row,col = que.popleft() - # grid[row][col] = '2' - for x,y in [ (row-1,col), (row+1,col), (row,col-1), (row,col+1) ] : - if 0<=x int: - r = len(grid) - c = len(grid[0]) - self.ans = 0 - def dfs(i,j) : - self.ans += 4 - grid[i][j] = 2 - for x,y in [ (i-1,j), (i+1,j), (i,j-1), (i,j+1) ]: - if 0<=x= 1 : - self.ans -= 1 - if 0<=x int: - r = len(grid) - c = len(grid[0]) - def BFS(que: List): - time = 0 - if not que : return 0 - que = deque(que) - next_que = [] - while que : - row,col = que.popleft() - for x,y in [ (row+1,col), (row-1,col), (row,col+1), (row,col-1) ] : - if 0<=x int: - r = len(grid) - c = len(grid[0]) - mp = defaultdict(int) - g = copy.deepcopy(grid) - def BFS(i,j,grid) : - time = 1 - que = deque([(i,j)]) - mp[(i,j)] = 0 - next_que = [] - while que : - row,col = que.popleft() - for x,y in [ (row-1,col), (row+1,col), (row,col-1), (row,col+1) ] : - if 0<=x bool: - mp = {} - for l in prerequisites : - if l[0] in mp.keys() : - mp[l[0]].append(l[1]) - else : - mp[l[0]] = [l[1]] - # 判断哈希表中有没有环 - -``` - -## 思路一:DFS - -拓扑排序 省去了排序 只判断环 - -```python -class Solution: - def canFinish(self, numCourses: int, prerequisites: List[List[int]]) -> bool: - status = [0] * numCourses - mp = defaultdict(list) - for i in prerequisites : - mp[i[0]].append(i[1]) - def dfs(u: int): - status[u] = 1 - for v in mp[u]: - if status[v] == 0 : - if dfs(v) : return True - elif status[v] == 1 : - # 有环 - return True - status[u] = 2 - for i in range(numCourses) : - if dfs(i) : return False - return True -``` - -## 思路二:BFS - -```python -class Solution: - def canFinish(self, numCourses: int, prerequisites: List[List[int]]) -> bool: - in_edges = [0] * numCourses - # 维护哈希表 - mp = defaultdict(list) - for i in prerequisites : - mp[i[0]].append(i[1]) - in_edges[i[1]] += 1 - # 找到最先学的 - que = deque() - for i in range(numCourses): - if in_edges[i] == 0 : - que.append(i) - visited = 0 - while que : - visited += 1 - cur = que.popleft() - print(cur,end=' ') - for v in mp[cur] : - in_edges[v] -= 1 - if in_edges[v] == 0 : - que.append(v) - return visited == numCourses -``` - -## 课程表II-DFS - -```python -class Solution: - def findOrder(self, numCourses: int, prerequisites: List[List[int]]) -> List[int]: - # 记录每个节点,维护每个节点的状态,0是还没搜索,1是正在搜索,2是搜索完了 - status = [0] * numCourses - # 先获取依赖关系 - mp = defaultdict(list) - for i in prerequisites : - mp[i[0]].append(i[1]) - existCycle = False - ans = [] - def dfs(u: int) : - status[u] = 1 - for v in mp[u]: - if status[v] == 0 : - if dfs(v) : return True - elif status[v] == 1 : - # 说明遇到了环 - return True - status[u] = 2 - ans.append(u) - return False - - for i in range(numCourses): - if not existCycle and status[i] == 0 : - if dfs(i) : - return [] - - return ans if not existCycle else [] - -``` - -# 54. [实现 Trie (前缀树)](https://leetcode.cn/problems/implement-trie-prefix-tree/)208-251225 - -插入和查找 用 dict 就可以简单实现,所以关键点在于 startWith -如果用暴力的方法那么如下,这个方法的 startwith是不可接受的(1750ms),能不能用一个新的结构来做呢? -```python -class Trie: - - def __init__(self): - self.list = [] - - def insert(self, word: str) -> None: - self.list.append(word) - - def search(self, word: str) -> bool: - return word in self.list - - def startsWith(self, prefix: str) -> bool: - lp = len(prefix) - for v in self.list : - p = v[0:min(lp,len(v))] - if p == prefix : - return True - return False -``` - -稍微为字典优化一下就变成:75ms 还是挺慢的 正态mu是56 -```python -class Trie: - - def __init__(self): - self.list = {} - - def insert(self, word: str) -> None: - self.list[word] = True - sub = '' - for c in word : - sub+=c - if sub not in self.list.keys() : - self.list[sub] = False - - def search(self, word: str) -> bool: - if word not in self.list.keys() : - return False - return self.list[word] - - def startsWith(self, prefix: str) -> bool: - return prefix in self.list.keys() -``` - -## 思路:字典树 - -字典套字典感觉太难理解了 - -### 不好的写法 -递归insert字典树 感觉有点慢 5.24% -```python -def c2i(char) -> int : - return 0 if char=='#' else ord(char) -ord('a') + 1 - -class Trie: - def __init__(self): - self.next = [ None ] * 27 - def insert(self, word: str) -> None: - if not word : return - if word[-1] != '#' : word += '#' - node = Trie() if not self.next[c2i(word[0])] else self.next[c2i(word[0])] - node.insert(word[1:]) - self.next[c2i(word[0])] = node - - def search(self, word: str) -> bool: - return self.startsWith(word+'#') - - def startsWith(self, prefix: str) -> bool: - node = self - for c in prefix : - i = c2i(c) - node = node.next[i] - if not node : return False - return True -``` -### 好的写法之后再写一遍 -```python -class Trie: - def __init__(self): - self.trie = {} - - def insert(self, word: str) -> None: - node = self.trie - for char in word: - ####### 下面这一行是重点 如果 char 在key中 就返回node[char] - ####### 如果不在 就node[char]={}再返回 - node = node.setdefault(char, {}) - node['#'] = True # 标记单词结束 - - def search(self, word: str) -> bool: - return self.startsWith(word+'#') - - def startsWith(self, prefix: str) -> bool: - node = self.trie - for char in prefix: - if char not in node: - return False - node = node[char] - return True -``` - -# 55. [全排列](https://leetcode.cn/problems/permutations/)46-251225 - -复杂度是 $O(n!)$ - -用递归做 -```python -def Digui(nums): - ans = [] - if len(nums) == 1 : - return [[nums[0]]] - for i in range(len(nums)) : - cp = nums[:] - t = cp[0] - cp[0] = cp[i] - cp[i] = t - sub_ans = Digui(cp[1:]) - for b in sub_ans : - a = [cp[0]] + b - ans.append(a) - return ans - -class Solution: - def permute(self, nums: List[int]) -> List[List[int]]: - return Digui(nums) -``` - -题解的做法: -```python -class Solution: - def permute(self, nums: List[int]) -> List[List[int]] : - # index 尾巴 全排列 - def dfs(index): - if index == len(nums): - return res.append(nums[:]) - for i in range(index , len(nums)): - nums[i],nums[index] = nums[index],nums[i] - dfs(index+1) - nums[index],nums[i] = nums[i],nums[index] - res = [] - dfs(0) - return res -``` - -# 56. [子集](https://leetcode.cn/problems/subsets/)78-251229 - -递归,和上一个思路一样。但是这个是组合 把输出分成 1和 后面的 先求解 digui(后面的) 然后再在后面的解中每个和1 结合,加上 后面的解 就是 一个完成的解 -```python -def Digui(nums) : - print(nums) - if len(nums)==1 : - return [nums,[]] - sub_ans = Digui(nums[1:]) - ans = sub_ans[:] - # print(sub_ans) - for s in sub_ans : - a = [nums[0]] + s - print(f'-- {a} {[nums[0]]} {s}') - ans.append(a) - # print(ans) - return ans - -class Solution: - def subsets(self, nums: List[int]) -> List[List[int]]: - return Digui(nums) -``` - -# 57. [电话号码的字母组合](https://leetcode.cn/problems/letter-combinations-of-a-phone-number/)17-20251229 - -递归 - -```python -mp = { - 2:['a','b','c'], - 3:['d','e','f'], - 4:['g','h','i'], - 5:['j','k','l'], - 6:['m','n','o'], - 7:['p','q','r','s'], - 8:['t','u','v'], - 9:['w','x','y','z'], -} - -def c2i(char) : - return ord(char)-ord('0') - -def Digui(str): - index,remain = c2i(str[0]),str[1:] - wl = mp[index] - if len(remain) == 0 : - return wl - sub_ans = Digui(remain) - ans = [] - for sub in sub_ans : - for c in wl : - ans.append(c+sub) - return ans - -class Solution: - def letterCombinations(self, digits: str) -> List[str]: - return Digui(digits) -``` - -# 58. [组合总和](https://leetcode.cn/problems/combination-sum/)39-20251229\* 跳过了 之后自己写一下 - -这个是最快的解法 -```python -class Solution: - def combinationSum(self, candidates: List[int], target: int) -> List[List[int]]: - def backtrack(summ,startIndex): - if summ == target: - res.append(path[:]) - return - for i in range(startIndex,n): - if summ + candidates[i] > target: - break - path.append(candidates[i]) - backtrack(summ+candidates[i],i) - path.pop() - - - res=[] - path = [] - n = len(candidates) - candidates.sort() - summ,startIndex = 0,0 - backtrack(summ,startIndex) - return res -``` - - -# 59. [括号生成](https://leetcode.cn/problems/generate-parentheses/)22-20251230 - -## 思路一:动态规划 递归 Catalan 结构 -这个递归的思路可以看看 重点在于 把每个问题 拆分成 (a)b 找到 ab的子问题,ab可以为空。我觉得本质还是递归,不太懂跟动态规划有什么具体关系,下次刷到可以再想想。 -另外关于拆分子问题这个,其实就是 n-1 对的 sub_ans 然后把这一对新的括号加在任意的位置 -```python -class Solution: - @lru_cache(None) - def generateParenthesis(self, n: int) -> List[str]: - if n == 0: - return [''] - ans = [] - for c in range(n): - for left in self.generateParenthesis(c): - for right in self.generateParenthesis(n-1-c): - ans.append('({}){}'.format(left, right)) - return ans -``` - -## 别的思路 -递归得到所有的 组合,然后用一个函数遍历测试对不对,但是这个复杂度有指数项 不好。 - -# 60. [单词搜索](https://leetcode.cn/problems/word-search/)79-20251230 - -## 无优化的回溯-DFS - -这个方法的时间完全不能接受,找到开头,然后dfs所有的path - -```python -class Solution: - def exist(self, board: List[List[str]], word: str) -> bool: - r = len(board) - c = len(board[0]) - def dfs(i,j,target) : - cur_char = board[i][j] - board[i][j] = '#' - if len(target) == 1 : - board[i][j] = cur_char - return cur_char == target - target_char, next_target_char = target[0], target[1] - if cur_char != target_char : - board[i][j] = cur_char - return False - for t in [(i+1,j),(i-1,j),(i,j+1),(i,j-1)] : - u,v = t[0],t[1] - if 0 <= u < r and 0 <= v < c and board[u][v] != '#' : - if dfs(u,v,target[1:]) : return True - board[i][j] = cur_char - return False - - for i in range(r) : - for j in range(c) : - if board[i][j] == word[0] and dfs(i,j,word) : return True - return False -``` - -题解写的回溯: -```python -class Solution: - def exist(self, board: List[List[str]], word: str) -> bool: - directions = [(0, 1), (0, -1), (1, 0), (-1, 0)] - - def check(i: int, j: int, k: int) -> bool: - if board[i][j] != word[k]: - return False - if k == len(word) - 1: - return True - - visited.add((i, j)) - result = False - for di, dj in directions: - newi, newj = i + di, j + dj - if 0 <= newi < len(board) and 0 <= newj < len(board[0]): - if (newi, newj) not in visited: - if check(newi, newj, k + 1): - result = True - break - - visited.remove((i, j)) - return result - - h, w = len(board), len(board[0]) - visited = set() - for i in range(h): - for j in range(w): - if check(i, j, 0): - return True - - return False -``` - -## 优化方法 - -1. 统计word 中各个字母的数量,如果board 中不符合 就直接 return False -2. 找到 word\[0] word\[-1] 这两个字母哪个在 board 中出现的少,如果 word\[-1] 出现的少,那么就把word reverse 这样可以减少入口的数量。 -```python -class Solution: - def exist(self, board: List[List[str]], word: str) -> bool: - def dfs(i,j,target): - if board[i][j] != target[0] : return False - if len(target) == 1 : return True - res = False - t, board[i][j] = board[i][j], '#' - for (u,v) in [(0, 1), (0, -1), (1, 0), (-1, 0)] : - u,v = i+u, j+v - if 0<=u mp[word[-1]] - for c in word : - mp[c]-=1 - if mp[c]<0 : return False - word = word[::-1] if ifReverse else word[:] - for x in range(len(board)) : - for y in range(len(board[0])) : - if board[x][y] == word[0] and dfs(x,y,word) : - return True - return False -``` - -## 再优化一次 - -延续上面的2思路,是不是找到一个位置,使该位置两边的搜索数量最少,就可以了? -```python -class Solution: - def exist(self, board: List[List[str]], word: str) -> bool: - def dfs(i,j,target): - if board[i][j] != target[0] : return False - if len(target) == 1 : return True - res = False - t, board[i][j] = board[i][j], '#' - for (u,v) in [(0, 1), (0, -1), (1, 0), (-1, 0)] : - u,v = i+u, j+v - if 0<=u List[List[str]]: - l = len(s) - # 用动态规划记录 i,j 是不是回文串 # dp 的本质就是 用已有状态(解) 递推 之后的状态(解) - dp = [ [False] * (l) for i in range(l) ] - for i in range(l) : - for j in range(l): - if i>=j : dp[i][j] = True - for i in range(l,-1,-1) : - for j in range(i+1,l) : - dp[i][j] = dp[i+1][j-1] and s[i] == s[j] - # 到这里位置 记录了每个 i,j(包含) 是否是回文 - - ret = list() - ans = list() - # 下面回溯搜索:从下标i开始,把 s[i:] 切成 若干回文子串,枚举所有可能的切法 - def dfs(i: int): - if i == l: - ret.append(ans[:]) - return - for j in range(i, l): - if dp[i][j]: - ans.append(s[i:j+1]) - dfs(j + 1) - ans.pop() - # 其实意思就是 从 0 开始 然后依次找 从合法的位置分割,对于每个合法的分割位置,再当做开头,再对后面的分割 - # 分割到 l 就把这一次分割的答案记下来 - dfs(0) - return ret -``` - -# 62. [N 皇后](https://leetcode.cn/problems/n-queens/)51-20260104\*\*\*\*\*\* - -## 思路一:按行搜索 - -好难啊😭,看了题解之后按照题解的思路做了一个。题解里面的想法是按行搜索,而不是按照位置搜索。是因为行本身可以作为一个索引,每次搜索一行的答案就行了。这样的话就没有那么难处理。(相比于按照按位置处理) - -```python -class Solution: - def solveNQueens(self, n: int) -> List[List[str]]: - col = set() - diag_add = set() - diag_mns = set() - # 找到 第row行 Q可以放在哪 - def tuple2board(tl): - res = [] - for i,j in tl : - t = ['.'] * n - t[j] = 'Q' - res.append(''.join(t)) - ans.append(res) - - def dfs(row): - if row == n : - tuple2board(tuple_ans) - return - for i in range(n) : - if i in col or (row+i) in diag_add or (row-i) in diag_mns : - continue - col.add(i) - diag_add.add(row+i) - diag_mns.add(row-i) - tuple_ans.append((row,i)) - dfs(row+1) - tuple_ans.pop() - col.remove(i) - diag_add.remove(row+i) - diag_mns.remove(row-i) - tuple_ans = [] - ans = [] - dfs(0) - return ans -``` - -## 思路二:位运算\*\*\*\*\*\* - -pass 还没学暂时 脑子转不动了 - -# 63. [搜索插入位置](https://leetcode.cn/problems/search-insert-position/)35-20260105 - -Binary Search - -第一次写的时候 `def bs(num_list, target): ` 但是这样有个问题是index不好处理 - -```python -class Solution: - def searchInsert(self, nums: List[int], target: int) -> int: - def bs(nl,tar): - l = len(nl) - if l == 0 : return 0 - mid = l//2 - if nl[mid] == tar : return mid - if nl[mid] > tar : - res = bs(nl[:mid],tar) - return res - if nl[mid] < tar : - res = bs(nl[mid+1:],tar) - # 我觉得这个比较关键, 找后部分的数组时候要把现在的 mid 给存下来 - # 还有一种方法是 直接用 left right 去找 - return mid + res + 1 - - return bs(nums,target) -``` - -用left rihgt 写的 bs 这个是 闭闭区间 -```python -class Solution: - def searchInsert(self, nums: List[int], target: int) -> int: - left, right = 0, len(nums) - 1 - while left <= right: - mid = (left + right) // 2 - if target > nums[mid]: - left = mid + 1 - else: - right = mid - 1 - - return left - -``` - -# 64. [搜索二维矩阵](https://leetcode.cn/problems/search-a-2d-matrix/)74-20260105 - -二分搜索,思路挺简单的,先对行首二分搜索,然后对定位到的行继续二分搜索。 - -```python -class Solution: - def searchMatrix(self, matrix: List[List[int]], target: int) -> bool: - # 先找到在哪一行,up down 是个 闭开区间 - up, down = 0, len(matrix) - while up < down : - mid = (up+down)//2 - if matrix[mid][0] == target : return True - if matrix[mid][0] > target : - down = mid - if matrix[mid][0] < target : - up = mid + 1 - nums=matrix[up-1][:] - up, down = 0, len(nums) - while up < down : - mid = (up+down)//2 - if nums[mid] == target : return True - if nums[mid] > target : - down = mid - if nums[mid] < target : - up = mid + 1 - return False -``` - -## 二分搜索的几种写法 注意循环不变量 - -```python -# lower_bound 返回最小的满足 nums[i] >= target 的 i -# 如果数组为空,或者所有数都 < target,则返回 len(nums) -# 要求 nums 是非递减的,即 nums[i] <= nums[i + 1] - -# 闭区间写法 -def lower_bound(nums: List[int], target: int) -> int: - left, right = 0, len(nums) - 1 # 闭区间 [left, right] - while left <= right: # 区间不为空 - # 循环不变量: - # nums[left-1] < target - # nums[right+1] >= target - mid = (left + right) // 2 - if nums[mid] < target: - left = mid + 1 # 范围缩小到 [mid+1, right] - else: - right = mid - 1 # 范围缩小到 [left, mid-1] - return left - -# 左闭右开区间写法 -def lower_bound2(nums: List[int], target: int) -> int: - left = 0 - right = len(nums) # 左闭右开区间 [left, right) - while left < right: # 区间不为空 - # 循环不变量: - # nums[left-1] < target - # nums[right] >= target - mid = (left + right) // 2 - if nums[mid] < target: - left = mid + 1 # 范围缩小到 [mid+1, right) - else: - right = mid # 范围缩小到 [left, mid) - return left # 或者 right - -# 开区间写法 -def lower_bound3(nums: List[int], target: int) -> int: - left, right = -1, len(nums) # 开区间 (left, right) - while left + 1 < right: # 区间不为空 - mid = (left + right) // 2 - # 循环不变量: - # nums[left] < target - # nums[right] >= target - if nums[mid] < target: - left = mid # 范围缩小到 (mid, right) - else: - right = mid # 范围缩小到 (left, mid) - return right - -class Solution: - def searchInsert(self, nums: List[int], target: int) -> int: - return lower_bound(nums, target) # 选择其中一种写法即可 - -作者:灵茶山艾府 -链接:https://leetcode.cn/problems/search-insert-position/solutions/2023391/er-fen-cha-zhao-zong-shi-xie-bu-dui-yi-g-nq23/ -来源:力扣(LeetCode) -著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。 -``` - -# 65. [在排序数组中查找元素的第一个和最后一个位置](https://leetcode.cn/problems/find-first-and-last-position-of-element-in-sorted-array/)34-20260105 - -先用二分找到这个数的位置,然后找下一个数的位置,得到结果 - -```python -class Solution: - def searchRange(self, nums: List[int], target: int) -> List[int]: - # 找到的是 左到右 第一个大于或等于 target 的 index - def bs(nums_list,tar) : - # print(nums_list,tar) - left, right = 0, len(nums_list) - while left < right : - mid = (left+right)//2 - if nums_list[mid] < tar : left = mid + 1 - if nums_list[mid] >= tar : right = mid - return left - if not nums : return [ -1, -1 ] - start = bs(nums,target) - if start >= len(nums) or nums[start] != target : return [ -1, -1 ] - return [ start, bs(nums,target+1)-1 ] -``` - -但是这样要两次 bs ,(而且我好像发现 二分搜索 闭闭区间 好像更好写) - -```python -def findlowerindex(nums: List[int], target: int)-> int: - left = 0 - right = len(nums)-1 - - while left<=right: - mid = (left + right )//2 - if nums[mid] List[int]: - if findlowerindex(nums,target)==len(nums) or nums[findlowerindex(nums,target)] != target: - return [-1,-1] - return[findlowerindex(nums, target),findlowerindex(nums, target+1)-1] -``` - -# 66. [搜索旋转排序数组](https://leetcode.cn/problems/search-in-rotated-sorted-array/)33-20260105 - -做一下 [寻找旋转排序数组中的最小值](https://leetcode.cn/problems/find-minimum-in-rotated-sorted-array/)这个可以,我觉得这个66题目的下标处理很麻烦,一定要注意 - -## 思路一:两次bs - -```python -class Solution: - # 153. 寻找旋转排序数组中的最小值(返回的是下标) - def findMin(self, nums: List[int]) -> int: - left, right = 0, len(nums) - 1 - while left < right: - mid = (left + right) // 2 - if nums[mid] < nums[right]: - right = mid - else: - left = mid + 1 - return right - - # 有序数组中找 target 的下标 - def lower_bound(self, nums: List[int], left: int, right: int, target: int) -> int: - while left + 1 < right: # 开区间不为空 - mid = (left + right) // 2 - # 循环不变量: - # nums[right] >= target - # nums[left] < target - if nums[mid] >= target: - right = mid # 范围缩小到 (left, mid) - else: - left = mid # 范围缩小到 (mid, right) - return right if nums[right] == target else -1 - - def search(self, nums: List[int], target: int) -> int: - i = self.findMin(nums) - if target > nums[-1]: # target 在第一段 - return self.lower_bound(nums, -1, i, target) # 开区间 (-1, i) - # target 在第二段 - return self.lower_bound(nums, i - 1, len(nums), target) # 开区间 (i-1, n) -``` - -## 思路二:一次bs 这个我还没看懂 - -```python -class Solution: - def search(self, nums: List[int], target: int) -> int: - n = len(nums) - l, r = 0, n - 1 - while l < r: - mid = (l + r) // 2 - x = nums[mid] - if x >= nums[0] and (target > x or target < nums[0]): - l = mid + 1 - elif x < target < nums[0]: - l = mid + 1 - else: - r = mid - return -1 if nums[r] != target else r -``` -# 67. [寻找旋转排序数组中的最小值](https://leetcode.cn/problems/find-minimum-in-rotated-sorted-array/)153-20260105 - -我觉得难点在下标处理 -```python -class Solution: - def findMin(self, nums: List[int]) -> int: - left, right = 0, len(nums)-1 - while left < right : - mid = (left+right)//2 - if nums[mid] < nums[right] : - right = mid - else : left = mid + 1 - # print(left,right) - return nums[left] -``` - -```python -class Solution: - def findMin(self,nums): - left, right = 0, len(nums)-1 - while left < right : - mid = (left+right)//2 - if nums[mid] <= nums[right] : - right = mid - else : - left = mid + 1 - print(left,right) - return nums[left] -``` - -# 68. [寻找两个正序数组的中位数](https://leetcode.cn/problems/median-of-two-sorted-arrays/)4-20260105\*\*\*\*\*\*\*\* - -这个题目挺重要的 看一下官方题解吧 - -## 思路一:按大小依次数 硬找中位数 这个方法不太好 - -```python -class Solution: - # 双指针 移动 len/2 次 找到 - def findMedianSortedArrays(self, nums1: List[int], nums2: List[int]) -> float: - l1, l2 = len(nums1), len(nums2) - if l1 == 1 and l2 == 1 : return (nums1[0] + nums2[0]) /2 - nums1.append(9999) - nums2.append(9999) - # mid 在 第几个 - midl = (l1+l2-1)//2 + 1 - midr = (l1+l2)//2 + 1 - count, p1, p2 = 0, -1, -1 - # print(l1,l2,midl,midr) - ans = 0 - while True : - if nums1[p1+1] < nums2[p2+1] : - p1 += 1 - t = nums1[p1] - else : - p2 += 1 - t = nums2[p2] - count += 1 - # print(count,t) - if count == midl : - ans += t - if count == midr : - ans += t - break - return ans/2 -``` - -## 思路二:双指针 分组 找到两组 - -一个 [题解](https://leetcode.cn/problems/median-of-two-sorted-arrays/solutions/2950686/tu-jie-xun-xu-jian-jin-cong-shuang-zhi-z-p2gd) 写的挺好的 可以看一下: 这个方法的关键想法在于怎么把两个数组分成两组。我觉得写的特别好。从一个的头和另一个的尾巴开始遍历。找到符合条件的。只到分组成功,一定要再看一下这个题解。这俩时间复杂度都是 m+n -```python -class Solution: - def findMedianSortedArrays(self, a: List[int], b: List[int]) -> float: - if len(a) > len(b): - a, b = b, a # 保证下面的 i 可以从 0 开始枚举 - - m, n = len(a), len(b) - a = [-inf] + a + [inf] - b = [-inf] + b + [inf] - - # 枚举 nums1 有 i 个数在第一组 - # 那么 nums2 有 j = (m + n + 1) // 2 - i 个数在第一组 - i, j = 0, (m + n + 1) // 2 - while True: - if a[i] <= b[j + 1] and a[i + 1] > b[j]: # 写 >= 也可以 - max1 = max(a[i], b[j]) # 第一组的最大值 - min2 = min(a[i + 1], b[j + 1]) # 第二组的最小值 - return max1 if (m + n) % 2 else (max1 + min2) / 2 - i += 1 # 继续枚举 - j -= 1 -``` - -## 思路三:二分查找\* - -```python -class Solution: - def findMedianSortedArrays(self, nums1: List[int], nums2: List[int]) -> float: - def getKthElement(k): - """ - - 主要思路:要找到第 k (k>1) 小的元素,那么就取 pivot1 = nums1[k/2-1] 和 pivot2 = nums2[k/2-1] 进行比较 - - 这里的 "/" 表示整除 - - nums1 中小于等于 pivot1 的元素有 nums1[0 .. k/2-2] 共计 k/2-1 个 - - nums2 中小于等于 pivot2 的元素有 nums2[0 .. k/2-2] 共计 k/2-1 个 - - 取 pivot = min(pivot1, pivot2),两个数组中小于等于 pivot 的元素共计不会超过 (k/2-1) + (k/2-1) <= k-2 个 - - 这样 pivot 本身最大也只能是第 k-1 小的元素 - - 如果 pivot = pivot1,那么 nums1[0 .. k/2-1] 都不可能是第 k 小的元素。把这些元素全部 "删除",剩下的作为新的 nums1 数组 - - 如果 pivot = pivot2,那么 nums2[0 .. k/2-1] 都不可能是第 k 小的元素。把这些元素全部 "删除",剩下的作为新的 nums2 数组 - - 由于我们 "删除" 了一些元素(这些元素都比第 k 小的元素要小),因此需要修改 k 的值,减去删除的数的个数 - """ - - index1, index2 = 0, 0 - while True: - # 特殊情况 - if index1 == m: - return nums2[index2 + k - 1] - if index2 == n: - return nums1[index1 + k - 1] - if k == 1: - return min(nums1[index1], nums2[index2]) - - # 正常情况 - newIndex1 = min(index1 + k // 2 - 1, m - 1) - newIndex2 = min(index2 + k // 2 - 1, n - 1) - pivot1, pivot2 = nums1[newIndex1], nums2[newIndex2] - if pivot1 <= pivot2: - k -= newIndex1 - index1 + 1 - index1 = newIndex1 + 1 - else: - k -= newIndex2 - index2 + 1 - index2 = newIndex2 + 1 - - m, n = len(nums1), len(nums2) - totalLength = m + n - if totalLength % 2 == 1: - return getKthElement((totalLength + 1) // 2) - else: - return (getKthElement(totalLength // 2) + getKthElement(totalLength // 2 + 1)) / 2 - - -作者:力扣官方题解 -链接:https://leetcode.cn/problems/median-of-two-sorted-arrays/solutions/258842/xun-zhao-liang-ge-you-xu-shu-zu-de-zhong-wei-s-114/ -来源:力扣(LeetCode) -著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。 -``` -## 思路四:二分发划分数组\* - -```python -class Solution: - def findMedianSortedArrays(self, a: List[int], b: List[int]) -> float: - if len(a) > len(b): - a, b = b, a - - m, n = len(a), len(b) - # 循环不变量:a[left] <= b[j+1] - # 循环不变量:a[right] > b[j+1] - left, right = -1, m - while left + 1 < right: # 开区间 (left, right) 不为空 - i = (left + right) // 2 - j = (m + n - 3) // 2 - i - if a[i] <= b[j + 1]: - left = i # 缩小二分区间为 (i, right) - else: - right = i # 缩小二分区间为 (left, i) - - # 此时 left 等于 right-1 - # a[left] <= b[j+1] 且 a[right] > b[j'+1] = b[j],所以答案是 i=left - i = left - j = (m + n - 3) // 2 - i - ai = a[i] if i >= 0 else -inf - bj = b[j] if j >= 0 else -inf - ai1 = a[i + 1] if i + 1 < m else inf - bj1 = b[j + 1] if j + 1 < n else inf - max1 = max(ai, bj) - min2 = min(ai1, bj1) - return max1 if (m + n) % 2 else (max1 + min2) / 2 - -作者:灵茶山艾府 -链接:https://leetcode.cn/problems/median-of-two-sorted-arrays/solutions/2950686/tu-jie-xun-xu-jian-jin-cong-shuang-zhi-z-p2gd/ -来源:力扣(LeetCode) -著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。 -``` - -# 69. [有效的括号](https://leetcode.cn/problems/valid-parentheses/)20-20260105 - -用栈即可 比较简单这个 - -```python -class Solution: - def isValid(self, s: str) -> bool: - def isPair(c1,c2): - if c1 == '(' : return c2==')' - if c1 == '{' : return c2=='}' - if c1 == '[' : return c2==']' - stk = ['#'] - for c in s : - if isPair(stk[-1],c) : - print(c) - stk.pop() - else : stk.append(c) - return len(stk) == 1 -``` - -尝试优化 但是好像没效果 -```python -class Solution: - def isValid(self, s: str) -> bool: - def isPair(c1,c2): - if c1 == '(' : return c2==')' - if c1 == '[' : return c2==']' - if c1 == '{' : return c2=='}' - def isError(c1,c2): - if c1 == '(' : return c2==']' or c2=='}' - if c1 == '[' : return c2==')' or c2=='}' - if c1 == '{' : return c2==']' or c2==')' - stk = ['#'] - if len(s)%2 == 1 : return False # 这个 mod2 不要忘掉 - for c in s : - if isError(stk[-1],c) : return False - if isPair(stk[-1],c) : - print(c) - stk.pop() - else : stk.append(c) - return len(stk) == 1 -``` - -# 70. [最小栈](https://leetcode.cn/problems/min-stack/)155-20260105 - -## 思路:单调栈 - -用单调栈试试: - -```python -class MinStack: - - def __init__(self): - self.stk = [] - self.min_stk = [+inf] # monotonic stack - - def push(self, val: int) -> None: - self.stk.append(val) - if val <= self.min_stk[-1] : self.min_stk.append(val) - - def pop(self) -> None: - v = self.stk.pop() - if v == self.min_stk[-1] : self.min_stk.pop() - - def top(self) -> int: - return self.stk[-1] - - def getMin(self) -> int: - return self.min_stk[-1] -``` - -题解写法: - -```python -class MinStack: - def __init__(self): - # 这里的 0 写成任意数都可以,反正用不到 - self.st = [(0, inf)] # 栈底哨兵 - - def push(self, val: int) -> None: - self.st.append((val, min(self.st[-1][1], val))) - - def pop(self) -> None: - self.st.pop() - - def top(self) -> int: - return self.st[-1][0] - - def getMin(self) -> int: - return self.st[-1][1] - -作者:灵茶山艾府 -链接:https://leetcode.cn/problems/min-stack/solutions/2974438/ben-zhi-shi-wei-hu-qian-zhui-zui-xiao-zh-x0g8/ -来源:力扣(LeetCode) -著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。 -``` - -## 思考:最小堆 - -维护一个栈记录数据,以及一个小顶堆,维护最小值。查看的速度是 O1 ,插入删除的速度是 :$O(logn)$ -这个方法比上面的慢 - -# 71. [字符串解码](https://leetcode.cn/problems/decode-string/)394-20260105没写出来\*\*\*\*\*\*\*\* - -这个题我没写出来 -## 思路一:辅助栈法 - -遇到 \[ 时候 :记录 重复次数,处理到当前之前的字符结果,并把这个临时结果 res 置为 空 -遇到 \] 时候 :弹栈 此时 两个括号内的内容 在 res 中,然后括号前的内容在栈顶,拼接即可 得到新的 res - -```python -class Solution: - def decodeString(self, s: str) -> str: - stack, res, multi = [], "", 0 - for c in s: - if c == '[': - stack.append([multi, res]) - res, multi = "", 0 - elif c == ']': - cur_multi, last_res = stack.pop() - res = last_res + cur_multi * res - elif '0' <= c <= '9': - multi = multi * 10 + int(c) - else: - res += c - return res -``` - -## 思路二:递归 - -递归处理括号内的字符串,遇到括号就dfs 传入的 位置 i 标记了 这个括号内字符串的开始,感觉和上面的本质上没有区别 - -```python -class Solution: - def decodeString(self, s: str) -> str: - def dfs(s, i): - res, multi = "", 0 - while i < len(s): - if '0' <= s[i] <= '9': - multi = multi * 10 + int(s[i]) - elif s[i] == '[': - i, tmp = dfs(s, i + 1) - res += multi * tmp - multi = 0 - elif s[i] == ']': - return i, res - else: - res += s[i] - i += 1 - return res - return dfs(s,0) -``` - -# 72. [每日温度](https://leetcode.cn/problems/daily-temperatures/)739-20260105 - -## 思路一:单调栈 - -这个题目 和 单调递减栈 接雨水那个好像啊 ,先试试。 - -```python -class Solution: - # 用这个例子想一下 - # 4 5 6 2 0 3 7 4 - # 1 1 4 2 1 1 0 0 - def dailyTemperatures(self, t: List[int]) -> List[int]: - # 单调递减栈,记录index,被弹走时计算 index差值 最后留在stk中的就是 0 用 0 初始化 - stk = [0] - ans = [0] * len(t) - for i in range(1,len(t)) : - while stk and t[i] > t[stk[-1]] : - index = stk.pop() - ans[index] = i - index - stk.append(i) - return ans -``` - -## 思路二:别的写的另一种方法 暂时没仔细看 - -```python -class Solution: - def dailyTemperatures(self, temperatures: List[int]) -> List[int]: - n = len(temperatures) - hottest = 0 - answer = [0] * n - - for curr_day in range(n - 1, -1, -1): - current_temp = temperatures[curr_day] - if current_temp >= hottest: - hottest = current_temp - continue - # [73,74,75,71,69,72,76,73] - days = 1 - while temperatures[curr_day + days] <= current_temp: - # Use information from answer to search for the next warmer day - days += answer[curr_day + days] - answer[curr_day] = days - - return answer -``` - -# 73. [柱状图中最大的矩形](https://leetcode.cn/problems/largest-rectangle-in-histogram/)84-20260106\*\* - -## 思路一:单调栈 - -关键在于 对每个高度遍历的想法 我觉得,之后的就比较简单了 - -```python -# 单调栈 对每根柱子h找到 第一个 小于h 的 前一个就是边界 -class Solution: - def largestRectangleArea(self, heights: List[int]) -> int: - # 类似温度的方法 记录 下一个小于他的位置 - def nextLess(nums): - stk = [] - res = [ 0 ] * len(nums) - for i in range(len(nums)): - while stk and nums[stk[-1]] > nums[i] : - top = stk.pop() - res[top] = i-top - stk.append(i) - return res - ans = 0 - right = nextLess(heights) - left = nextLess(heights[::-1])[::-1] - for i in range(len(heights)) : - r = i+right[i]-1 if right[i] != 0 else len(heights)-1 - l = i-left[i]+1 if left[i] != 0 else 0 - ans = max( (r-l+1)*heights[i] , ans ) - return ans - -``` - -下面这个代码是 找 每个元素 **左边** 第一个小于他的,维护 单调递增栈 出站不更新,入栈之前更新。 上面的写法是找右边第一个小/大于他的,是在 pop 时候更新。 -```python - for i,n in enumerate(heights) : - top = 0 - while stk and heights[stk[-1]] > n : - top = stk.pop() - res[i] = i - stk[-1] if stk else 0 - stk.append(i) - print(res) -``` - -但是这个算法的复杂度虽然是 $O(n)$ 但是 速度还是比较慢 遍历了三次数组 ,下面用一次遍历解决问题 - -## 思路二:单调栈+优化 (最优 - -既然入栈确定左边界 出栈确定右边界 能不能一次确定h的两个边界 -```python -class Solution: - # 对一个 H - # 找到 右边 第一个比他小的 (pop后更新) 单调递增栈 - # 找到 左边 第一个比他小的 (append前更新) 单调递增栈 - def largestRectangleArea(self, heights: List[int]) -> int: - # 维护单调递增栈 保证每个元素都能出入栈 - heights = [0]+heights+[0] - stk = [] - left, right = [0]*len(heights),[0]*len(heights) - for i,n in enumerate(heights) : - while stk and n <= heights[stk[-1]] : - top = stk.pop() - right[top] = i - left[i] = stk[-1] if stk else 0 - stk.append(i) - return max((right[i]-left[i]-1)*heights[i] for i in range(len(heights))) -``` - -这个优化 还是要遍历两次,遍历一次的是:(\*\*\*\*\*\*\*\*\*这个没自己写过) - -```python -class Solution: - def largestRectangleArea(self, heights: List[int]) -> int: - # heights前面补上零,原因是单个柱子也可以拿出来计算 - # 同时在末尾也加上零,否则无法触发剩余在栈中元素的计算 - heights.insert(0, 0) - heights.append(0) - stack = [0] - res = 0 - for i in range(1, len(heights)): - h = heights[i] - if stack and h == heights[stack[-1]]: - stack.pop() - else: - while stack and h < heights[stack[-1]]: - mid = stack.pop() - l, r = stack[-1], i - res = max(res, heights[mid] * (r - l - 1)) - stack.append(i) - return res -``` - -## 超时 - -1. 遍历index - -```python -class Solution: - def largestRectangleArea(self, heights: List[int]) -> int: - ans = 0 - l = len(heights) - for i in range(l): - for j in range(i,l): - S = min(heights[i:j+1])*(j-i+1) - ans = max(ans,S) - return ans -``` - -2. 遍历高度:遍历高度 h 为什么能找到最优解?是因为这种方法找到了 最小值为 h,也就是高度为 h 能组成的最大矩形。 -```python -# 遍历高 -class Solution: - def largestRectangleArea(self, heights: List[int]) -> int: - ans = 0 - l = len(heights) - for i in range(l): - h = heights[i] - left, right = i, i - while left > 0 and heights[left-1] >= h : - left -= 1 - while right < l-1 and heights[right+1] >= h : - right += 1 - S = h*(right-left+1) - print(left,right,h,S) - ans = max(S,ans) - return ans -``` - -# 74. [数组中的第K个最大元素](https://leetcode.cn/problems/kth-largest-element-in-an-array/)215-20260109 - -[这个题解写的很好](https://leetcode.cn/problems/kth-largest-element-in-an-array/solutions/2361969/215-shu-zu-zhong-de-di-k-ge-zui-da-yuan-d786p) 这个题的思路就是用快速排序 的思想,把一个数放入正确的位置,如果放进去的位置刚好是k 那就 return。难得地方在于快速排序怎么写。 - -这个思路最简单的实现方式 - -```python -class Solution: - def findKthLargest(self, nums: List[int], k: int) -> int: - def quick_select(nums,k): - pivot = random.choice(nums) - le, gt, eq = [],[],[] - for n in nums : - if n < pivot : le.append(n) - elif n > pivot : gt.append(n) - elif n == pivot : eq.append(n) - if len(gt) >= k : - return quick_select(gt,k) - if len(gt+eq) >= k : - return pivot - return quick_select(le,k-len(gt+eq)) - return quick_select(nums,k) -``` - - -这个会超时,因为这个每次选择的pivot都是 第一个 如果数组有序的话, 会导致 时间复杂度到 $O(n^2)$ ,那么怎么改呢? -```python -class Solution: - def findKthLargest(self, nums: List[int], k: int) -> int: - # 闭闭 - def quick_select(i,j): - l,r = i,j - pivot = nums[i] - while i < j : - while i < j and pivot >= nums[j] : - j -= 1 - nums[i] = nums[j] - while i < j and nums[i] >= pivot : - i += 1 - nums[j] = nums[i] - nums[i] = pivot - # print(l,r,nums,i,j) - if i+1 == k : return pivot - if i+1 < k : return quick_select(i+1,r) - if i+1 > k : return quick_select(l,i-1) - - return quick_select(0,len(nums)-1) -``` - -这样还是超时 超时的关键在于 while 中的两个 大于等于号,之后想办法优化一下。 -另外 内层第一个while后的交换 其实可以写到后面 一起交换。但是交换后要移动一下ij ,通过这种方式可能可以把 等号去掉 -```python -class Solution: - def findKthLargest(self, nums: List[int], k: int) -> int: - # 闭闭 - def quick_select(i,j): - l,r = i,j - # pivot_index = random.randint(i,j) - # nums[i], nums[pivot_index] = nums[pivot_index], nums[i] - pivot = nums[i] - while i < j : - while i < j and pivot >= nums[j] : - j -= 1 - nums[i] = nums[j] - while i < j and nums[i] >= pivot : - i += 1 - nums[j] = nums[i] - nums[i] = pivot - # print(l,r,nums,i,j) - if i+1 == k : return pivot - if i+1 < k : return quick_select(i+1,r) - if i+1 > k : return quick_select(l,i-1) - - return quick_select(0,len(nums)-1) -``` - -看一下别人的: - - -邪修: 桶排序 -```python -class Solution: - def findKthLargest(self, nums: List[int], k: int) -> int: - bound = int(1e4) - max_size = 2 * bound + 1 - print(max_size) - num_cnt = [0] * max_size - for num in nums: - num_cnt[num + bound] += 1 - for i in range(max_size-1, -1, -1): - if k <= num_cnt[i]: - return i - bound - else: - k -= num_cnt[i] -``` - -## 快速排序笔记 - -首先 需要明确的是 快速排序的本质是 找到一个数组中 一个基准数的位置。这个基准数 一般取 第一个位置 。然后各放两个指针,左边的指向基准数,右边的指向末尾。算法流程就是,逐个向前检查后面的元素是不是比 这个 pivot 大,大就继续检查 直到 右指针 的数 小于 pivot 那么就和 pivot 交换位置。现在pivot 在 右边,就一次检查左边的。交换位置。 下面例子是降序 - -- 交换的写法 -```python - # 闭闭 - def quick_select(i,j): - pivot = nums[i] - print(nums,pivot) - while i < j : - while i < j and nums[i] >= nums[j] : - j -= 1 - print(i,j) - nums[j], nums[i] = nums[i], nums[j] - print(nums) - while i < j and nums[i] >= nums[j] : - i += 1 - print(i,j) - nums[j], nums[i] = nums[i], nums[j] - print(nums) - print(nums,i,j,pivot) - nums[i] = pivot -# 因为要交换 下面的写法更好 - def quick_select(i,j): - pivot = nums[i] - print(nums,pivot) - while i < j : - while i < j and pivot >= nums[j] : - j -= 1 - print(i,j) - nums[j], nums[i] = nums[i], nums[j] - print(nums) - while i < j and nums[i] >= pivot : - i += 1 - print(i,j) - nums[j], nums[i] = nums[i], nums[j] - print(nums) - print(nums,i,j,pivot) - nums[i] = pivot -``` - -- 不交换的写法,上面的 流程中 找到 j 比 pivot大,那么只需要把 说明这个 j 应该在前面。(而不是这个j 应该变小,因为我们还不知道i对不对)所以就把i变成j -```python - # 闭闭 - def quick_select(i,j): - pivot = nums[i] - print(nums,pivot,i,j) - while i < j : - while i < j and pivot >= nums[j] : - j -= 1 - print(i,j) - nums[i] = nums[j] - print(nums) - while i < j and nums[i] >= pivot : - i += 1 - print(i,j) - nums[j] = nums[i] - print(nums) - nums[i] = pivot - print(nums,i,j,pivot) -``` - -# 75. [前 K 个高频元素](https://leetcode.cn/problems/top-k-frequent-elements/)347-20260110 - -## 思路一:优先队列 - -简单的想法 统计格式 然后优先队列 -```python -class Solution: - def topKFrequent(self, nums: List[int], k: int) -> List[int]: - KK = k - cnt = defaultdict(int) - for n in nums : - cnt[n] += 1 - p = [] - for k,v in cnt.items() : - p.append((k,v)) - p = sorted(p,key=lambda x: x[1],reverse=True) - return [ p[i][0] for i in range(KK) ] -``` - -## 思路二:堆 - -```python -from typing import List -from collections import Counter -import heapq - -class Solution: - def topKFrequent(self, nums: List[int], k: int) -> List[int]: - freq = Counter(nums) - heap = [] # (count, num) - - for num, c in freq.items(): - if len(heap) < k: - heapq.heappush(heap, (c, num)) - else: - if c > heap[0][0]: - heapq.heapreplace(heap, (c, num)) - - return [num for c, num in heap] -``` - -# 76. [数据流的中位数](https://leetcode.cn/problems/find-median-from-data-stream/)295-20260110 - -## 思路一:用堆维护中位数 - -维护一个 大堆 一个 小堆 两边数量均衡 且满足 大堆顶 小于 小堆顶 那么中位数就很好找了。那么怎么维护这样一个结构呢? -不妨先想 怎么维护两个 堆顶 元素的大小。 不妨这样想: 每次都往大堆(升序数组左半部分)中加 然后 把堆顶 冒到 小顶堆(升序数组后半部分),这样 小顶恒大于大顶。 -但是这样有个问题,每次这样的话会导致,大顶为空。那么就加一个判断:如果小顶的元素数量超过了大顶,那么久:小顶吐出来一个给大顶,因为是小顶吐出来的,依然满足这个顺序。 -```python -class MedianFinder: - def __init__(self): - # 前半个的最大 和 后半个的最小 - self.max_heap = [] - self.min_heap = [] - - def addNum(self, num: int) -> None: - # 每次都往 max 中 添加 然后 平衡两边的 min 中push的元素都是从max中来的 - heapq.heappush(self.max_heap,-num) - heapq.heappush(self.min_heap,-heapq.heappop(self.max_heap)) - if len(self.max_heap) < len(self.min_heap) : - heapq.heappush(self.max_heap, -heapq.heappop(self.min_heap)) - return - - def findMedian(self) -> float: - if len(self.max_heap) - len(self.min_heap) == 0 : - return (self.min_heap[0] - self.max_heap[0])/2 - else: return -self.max_heap[0] -``` - -下面这个是比较快的写法。上面的写法 每次都要把元素加入到大堆 然后再做处理,有咩有简单点的方法呢? 这个的关键是 push pop :我们首先明确 两个堆 必须是 大顶的数量 不少于小顶 且最多多一个 才是合理的。而且我们明确,为了保持一致性,两个堆的数据有序性,需要靠堆之间传递元素来满足。 -那么如果两个数量相等,那么就往小顶堆塞一个 然后小顶堆再吐一个给大。反之反之。 -```python -from heapq import * - -class MedianFinder: - def __init__(self): - self.A = [] # 小顶堆,保存较大的一半 - self.B = [] # 大顶堆,保存较小的一半 - - def addNum(self, num: int) -> None: - if len(self.A) != len(self.B): - heappush(self.B, -heappushpop(self.A, num)) - else: - heappush(self.A, -heappushpop(self.B, -num)) - - def findMedian(self) -> float: - if len(self.A) != len(self.B) : - return self.A[0] - else: - return (self.A[0] - self.B[0]) / 2.0 -``` - -## Trash 二分查找找位置 然后插入 -时间复杂度是 $O(n)$,因为有个insert操作 -```python -class MedianFinder: - def __init__(self): - self.nums = [] - - def addNum(self, num: int) -> None: - l,r = 0, len(self.nums)-1 - while l<=r : - mid = (l+r)//2 - #找 第一个大于他的位置 - if self.nums[mid] > num : - r = mid-1 - else : - l = mid+1 - self.nums.insert(l,num) - - def findMedian(self) -> float: - l = len(self.nums) - return (self.nums[l//2] + self.nums[(l-1)//2])/2 -``` - -# 77. [买卖股票的最佳时机](https://leetcode.cn/problems/best-time-to-buy-and-sell-stock/)121-20260112 - -## 思路一:记录每个位置后面的最大值 - -```python -class Solution: - def maxProfit(self, prices: List[int]) -> int: - # 记录每个位置后面的最大值 - t_max = prices[-1] - ans = 0 - for i in range(len(prices)) : - index = len(prices)-i-1 - t_max = max(t_max,prices[index]) - ans = max(t_max-prices[index],ans) - return ans -``` - -不用max 只用 一次len 会加速速度 如果更新了最低价 就不需要 更新ans了 -```python -class Solution: - def maxProfit(self, prices: List[int]) -> int: - # 记录每个位置之前的最小值 - past_min, ans = +inf, 0 - for p in prices : - past_min = p if p < past_min else past_min - ans = p-past_min if (p-past_min) > ans else ans - return ans -``` - -# 78. [跳跃游戏](https://leetcode.cn/problems/jump-game/)55-20260112 - -记录能跳到的最远位置 为什么这个贪心是正确的呢?这个题目中 能到的位置 是连续的 也就是 确定了最远到的位置之后,之前的位置一定都是可以访问的。 nums到farest 之间一定是连续的。 -```python -class Solution: - def canJump(self, nums: List[int]) -> bool: - farest_index, l = 0, len(nums) - for i in range(l) : - if i > farest_index : break - farest_index = max(farest_index,i + nums[i]) - return farest_index >= l-1 -``` - -```python -class Solution: - def canJump(self, nums: List[int]) -> bool: - farest_index, l = 0, len(nums) - for i in range(l) : - if i > farest_index : return False - farest_index = max(farest_index,i + nums[i]) - return True -``` - -# 79. [跳跃游戏 II](https://leetcode.cn/problems/jump-game-ii/)45-20260112 - -## 思路一:贪心 - UES - -每次跳的时候 维护一个边界 边界变的时候 step +1 mlgb 这个贪心太难想了,其实本质是记录了一个 几步最远可以跳到哪里。贪心 有意思 -```python -class Solution: - def jump(self, nums: List[int]) -> int: - # 维护 跳到一个位置时候 能到的最大位置是多少 还是贪心 - step, farest, end, l = 0, 0, 0, len(nums) - if l == 1 :return 0 - for i in range(l) : - farest = max(nums[i] + i, farest) - if i == end : - end = farest - step += 1 - # print(step,end) # 几步最远可以跳到哪 - if end >= l-1 : return step - -``` - -## Trash - 动态规划 但是不好 - -时间复杂度很高 -```python -class Solution: - def jump(self, nums: List[int]) -> int: - # 记录到达每个位置的 最小步数 - l = len(nums) - dp = [+inf] * l - dp[0] = 0 - for i in range(l): - for j in range(1,nums[i]+1) : - if i+j == l : break - dp[i+j] = min(dp[i+j],dp[i]+1) - return dp[-1] - -``` - -有时间可以再做一下这两个 [跳跃游戏 III](https://leetcode.cn/problems/jump-game-iii/) [跳跃游戏 VII](https://leetcode.cn/problems/jump-game-vii/) -# 80. [划分字母区间](https://leetcode.cn/problems/partition-labels/)763-20260112 - -首先 写在外面 - !important: 什么时候用贪心通俗理解:“只顾眼前最优,就能得到最终最优”,无后效性。 这个题目就是很无后效性 -## 思路一:贪心 - -这个就是把start end 排成这样的:然后找分割位置就行 -``` ---- - -- - ---- - - - --- - -- - - - --- - -- -``` - -```python -class Solution: - def partitionLabels(self, s: str) -> List[int]: - start,end,l = {},{},len(s) - for i in range(l) : - cf = s[i] - cl = s[l-1-i] - if cf not in start.keys() : - start[cf] = i - if cl not in end.keys() : - end[cl] = l-i-1 - # print(start,end) - mp = [(start[c],end[c]) for c in start.keys() ] - mp = sorted(mp,key= lambda x: x[0]) - # print(mp) - ans,nans ,split_index = [],0,0 - for item in mp : - if split_index < item[0] : - ans.append(split_index+1-nans) - nans = split_index+1 - split_index = max(item[1],split_index) - ans.append(l-nans) - return ans -``` - -## 思路二:另一种贪心 - -其实我觉得本质是差不多的 可以好好看看别人的代码,这个是别人写的 上面是我自己写 -```python -class Solution: - def partitionLabels(self, s: str) -> List[int]: - last_occ = {} - for i in range(len(s)): - last_occ[s[i]] = i - - start = 0 - end = 0 - ans = [] - for i in range(len(s)): - end = max(end,last_occ[s[i]]) - if i == end: - ans.append(end -start + 1) - start = i + 1 - - return ans -``` - -# 81. [爬楼梯](https://leetcode.cn/problems/climbing-stairs/)70-20260112 - -我觉得这个是很经典的动态规划 像分硬币一样 很简单 - -```python -class Solution: - def climbStairs(self, n: int) -> int: - dp = [1] * (n+1) - for i in range(2,n+1) : - dp[i] = dp[i-1] + dp[i-2] - return dp[n] -``` - -虽然这个题目很简单,但是[题解](https://leetcode.cn/problems/climbing-stairs/solutions/286022/pa-lou-ti-by-leetcode-solution)里面的两个解法很牛逼 可以看看 一个是斐波那契的通项公式 还有一个是矩阵的快速幂 - -# 82. [杨辉三角](https://leetcode.cn/problems/pascals-triangle/)118-20260112 - -```python -class Solution: - def generate(self, numRows: int) -> List[List[int]]: - ans,last = [[1]],[1] - for i in range(2,numRows+1) : - temp = [1]*i - for j in range(1,i-1) : - temp[j] = last[j-1]+last[j] - last = temp - ans.append(temp) - return ans -``` - -下面是题解: -```python -class Solution: - def generate(self, numRows: int) -> List[List[int]]: - ret = list() - for i in range(numRows): - row = list() - for j in range(0, i + 1): - if j == 0 or j == i: - row.append(1) - else: - row.append(ret[i - 1][j] + ret[i - 1][j - 1]) - ret.append(row) - return ret -``` - -# 83. [打家劫舍](https://leetcode.cn/problems/house-robber/)198-20260112 - -这个就是零一背包感觉 也很简单 注意这里的dp是处理到 i ,i 偷或不偷 跟dp没关系 -```python -class Solution: - def rob(self, nums: List[int]) -> int: - # 处理完(偷或者不偷)第i个房子后的 最大值 - dp,l = nums[:],len(nums) - if l == 1 : return nums[0] - dp[1] = max(nums[0:2]) - for i in range(2,l) : - dp[i] = max(dp[i-2]+nums[i],dp[i-1]) - return dp[-1] -``` - -而且只需要看前两个,所以不用开一块内存 - -```python -class Solution: - def rob(self, nums: List[int]) -> int: - if not nums: - return 0 - - size = len(nums) - if size == 1: - return nums[0] - - first, second = nums[0], max(nums[0], nums[1]) - for i in range(2, size): - first, second = second, max(first + nums[i], second) - - return second -``` - -# 84. [完全平方数](https://leetcode.cn/problems/perfect-squares/)279-20260112 - -这个也很简单 -```python -class Solution: - def numSquares(self, n: int) -> int: - dp = [+inf] * (n+1) - dp[0],dp[1] = 0,1 - for i in range(n+1): - for j in range(1,int(sqrt(i))+1) : - dp[i] = min(dp[i],dp[i-j**2]+1) - return dp[n] - -``` - -# 85. [零钱兑换](https://leetcode.cn/problems/coin-change/)322-20260112(位运算\*\*) - -这个也很简答,coins 排序一下 可以break掉一部分。 -```python -class Solution: - def coinChange(self, coins: List[int], amount: int) -> int: - dp = [0] + [+inf] * amount - coins = sorted(coins) - for i in range(1,amount+1) : - for c in coins : - if i-c < 0 : break - dp[i] = min(dp[i], dp[i-c]+1 ) - return dp[amount] if dp[amount] != inf else -1 -``` - -## 思路?位运算 NB -还没看懂 感觉有点牛逼 -```python -class Solution: - def coinChange(self, coins: List[int], amount: int) -> int: - mask = (1 << (amount + 1)) - 1 - dp = 1 - count = 0 - coins = set(coin for coin in coins if coin <= amount) - while not dp >> amount: - count += 1 - new = dp - for coin in coins: - shifted = (dp & (mask >> coin)) << coin - new |= shifted - if new == dp: - return -1 - dp = new - return count -``` - -# 86. [单词拆分](https://leetcode.cn/problems/word-break/)139-20260112 - -## 思路一: 动态规划 -其实最开始的尝试 也就是下面的 dp 设置 是正确的 但是用了 BFS做而不是动态规划 -自己又写了一遍 对了 -```python -class Solution: - def wordBreak(self, s: str, wordDict: List[str]) -> bool: - # 到index 为止(包括) 能不能被组成 - dp = [False] * (len(s)+1) - dp[0] = True - wordDict = set(wordDict) - wordLenList = set() - for w in wordDict: - wordLenList.add(len(w)) - wordLenList = sorted(list(wordLenList)) - for i in range(len(s)+1): - for l in wordLenList : - if i-l < 0: break - dp[i] = dp[i-l] and s[i-l:i] in wordDict - if dp[i] : break - print(i,l,i-l,dp[i-l],s[i-l:i],dp[i]) - print(dp) - return dp[-1] -``` -## Trash-BFS - -BFS 但是不对 -```python -class Solution: - def wordBreak(self, s: str, wordDict: List[str]) -> bool: - #如果就不存在的字母直接 False - WW = '' - for w in wordDict : - WW+=w - charSet = set(WW) - for c in s: - if c not in charSet : return False - - # 到index 为止(包括) 能不能被组成 - dp = [False] * (len(s)+1) - dp[0] = True - wordDict = set(wordDict) - wordlenlist = set() - for w in wordDict : - wordlenlist.add(len(w)) - wordlenlist = sorted(list(wordlenlist)) - print(wordlenlist) - lastTrue = [0] - que = deque([0]) - while que : - index = que.popleft() - for l in wordlenlist : - if index+l > len(s) : break - if s[index:index+l] in wordDict : - dp[index+l] = True - que.append(index+l) - print(dp) - return dp[-1] - -``` - -## 思路二:BFS 题解的写法 - -```python -class Solution: - def wordBreak(self, s: str, wordDict: List[str]) -> bool: - max_len = max(map(len, wordDict)) # 用于限制下面 j 的循环次数 - words = set(wordDict) # 便于快速判断 s[j:i] in words - - @cache # 缓存装饰器,避免重复计算 dfs 的结果(记忆化) - def dfs(i: int) -> bool: - if i == 0: # 成功拆分! - return True - for j in range(i - 1, max(i - max_len - 1, -1), -1): - if s[j:i] in words and dfs(j): - return True - return False - - return dfs(len(s)) -``` - -# 87. [最长递增子序列](https://leetcode.cn/problems/longest-increasing-subsequence/)300-20260112 - -## 思路一: 动态规划 - - dp 数组是 以i为结尾的 子序列 最大长度,这个方法的复杂度是$O(n^2)$ ,怎么优化呢我觉得在找的时候用别的办法。维护一个单调递增栈?下面试一下。 - -```python -class Solution: - def lengthOfLIS(self, nums: List[int]) -> int: - ans = 1 - # 以第i个数为结尾的子序列 (必选i) - dp = [1] * len(nums) - for i in range(len(nums)) : - # 得往前找比他小的 - for j in range(i) : - if nums[j] < nums[i] : - dp[i] = max(dp[i],dp[j]+1) - ans = max(ans,dp[i]) - return ans -``` - -## ERROR -这个是错误的,来个0把前面的弹飞完了 \尴尬 -```python -# 加上单调栈 -class Solution: - def lengthOfLIS(self, nums: List[int]) -> int: - ans = 1 - # 以第i个数为结尾的子序列 (必选i) - dp = [1] * len(nums) - # 维护一个单调栈 简化内层循环 - stk = [] - for i in range(len(nums)) : - while stk and nums[stk[-1]] >= nums[i] : - stk.pop() - stk.append(i) - print(i,stk) - # 得往前找比他小的 - for j in stk : - if nums[j] < nums[i] : - dp[i] = max(dp[i],dp[j]+1) - ans = max(ans,dp[i]) - print(dp) - return ans -``` - -## 思路二:贪心+二分 - -这个太帅了 贪心都太帅了 -```python -#贪心算法,为了让子序列尽可能长,所以就选择尽可能小的值组成上升序列,然后得到尽可能长的上升序列。 -class Solution: - def lengthOfLIS(self, nums: List[int]) -> int: - # 用 dp 维护一个 dp[i] 表示 **长度为 i 的最长上升子序列的末尾元素的最小值** dp 并不直接是一个子序列注意定义 - d = [] - for n in nums: - # 如果这个数能构成上升序列就加进来 - if not d or n > d[-1]: - d.append(n) - # 若构成不了,也要把这个数换进来,用二分查找 找该换到的位置。(为了保证数组尽量上升的慢)更新的是len为 mid 结尾的最小元素 - else: - l, r = 0, len(d) - 1 - loc = r - while l <= r: - mid = (l + r) // 2 - if d[mid] >= n: - loc = mid - r = mid - 1 - else: - l = mid + 1 - d[loc] = n - print(n,d) - return len(d) -``` - -# 88. [乘积最大子数组](https://leetcode.cn/problems/maximum-product-subarray/)152-20260112\* - -这个题目的题解写的很好,看题解吧。重点是维护一个max 一个min -```python -class Solution: - def maxProduct(self, nums: List[int]) -> int: - # dp 以j为结尾的成绩的最大值 - dp_max = nums[:] - dp_min = nums[:] - for i in range(1,len(nums)): - dp_max[i] = max(dp_max[i-1]*nums[i],nums[i],dp_min[i-1]*nums[i]) - dp_min[i] = min(dp_min[i-1]*nums[i],nums[i],dp_max[i-1]*nums[i]) - return max(dp_max) -``` - -# 89. [分割等和子集](https://leetcode.cn/problems/partition-equal-subset-sum/)416-20260115 - -写了一个 $O(n^2)$ 的方法 不是最好的 -```python -class Solution: - def canPartition(self, nums: List[int]) -> bool: - target = sum(nums)/2 - if target%1!=0 : return False - mp = defaultdict(int) - mp[int(target)] = 1 - for n in nums: - ks = list(mp.keys()) - for k in ks: - if k==n : return True - if k-n > 0 : mp[k-n]+=1 - mp[target-n]+=1 - return False - -``` - - -## 思路一:递归 - -```python -from functools import lru_cache - -class Solution: - def canPartition(self, nums: List[int]) -> bool: - #sumV = sum(nums) - # can I find a subset of integers which can - # summ to sumV/2 - - s = sum(nums) - if s % 2 == 1: - return False - - half = s//2 - - def subSum(currSum,i): - if i < 0: - return False - if currSum + nums[i] == half: - return True - if currSum + nums[i] < half: - if subSum(currSum+ nums[i],i-1): - return True - else: - return subSum(currSum,i-1) - return subSum(0,len(nums)-1) -``` - -## 思路二:位运算 -看不懂 -```python -class Solution: - def canPartition(self, nums: List[int]) -> bool: - s = sum(nums) - if s%2:return False - bit = 1 - target = s //2 - for x in nums: - bit |= (bit << x) - return (bit >> target) & 1 == 1 - - # @cache - # def dfs(i, cur): - # if i < 0: - # return False - # #这项选还是不选 - # if cur == target: - # return True - # else: - # return dfs(i-1, cur + nums[i]) or dfs(i-1, cur) - return dfs(n-1, 0) -``` - -# 90. [最长有效括号](https://leetcode.cn/problems/longest-valid-parentheses/)32-20260115\*\*\*\*\* - -## 思路一:栈\*\*\*\*\*\*\*\*\*\* - -注意每次存下( 的位置 ,然后 遇到 )就pop 如果空了就把这个)的位置记录为新的开始 否则 更新答案。 - -```python -class Solution: - def longestValidParentheses(self, s: str) -> int: - # 滑动窗口对应一个栈,start end 是闭开 - ans,stk = 0,[-1] - for i in range(len(s)) : - if s[i] == '(' : stk.append(i) - else : - stk.pop() - if not stk : - stk.append(i) - else : - ans = max(ans,i-stk[-1]) - return ans -``` - -## 思路二:动态规划 没看懂 - -记录以i位置结尾的最大子串 -```python -class Solution: - def longestValidParentheses(self, s: str) -> int: - # 以i位置为结尾的最大子串 - if not s : return 0 - if s=='()' : return 2 - dp = [0] * len(s) - for i in range(1,len(s)) : - if s[i] == '(' : dp[i] = 0 - else : - if s[i-1] == '(' : dp[i] = 2 + (dp[i-2] if i-2 >= 0 else 0) - elif i-dp[i-1]>0 and s[i-dp[i-1]-1]=='(': - dp[i] = dp[i-1]+ (dp[i-dp[i-1]-2] if i-dp[i-1]-2 >=0 else 0) + 2 - return max(dp) -``` - -# 91. [不同路径](https://leetcode.cn/problems/unique-paths/)62-20260115 - -## 思路一:排列组合 -这个不就是排列组合么 -```python -A = [1] * 200 -for i in range(1,200) : - A[i] = A[i-1] * i - -class Solution: - def uniquePaths(self, m: int, n: int) -> int: - # A(m+n-2)/(A(n-1)*A(m-1)) - return int(A[m+n-2]/(A[n-1]*A[m-1])) -``` - -## 思路二:动态规划 - -```python -class Solution: - def uniquePaths(self, m: int, n: int) -> int: - f = [[1] * n] + [[1] + [0] * (n - 1) for _ in range(m - 1)] - print(f) - for i in range(1, m): - for j in range(1, n): - f[i][j] = f[i - 1][j] + f[i][j - 1] - return f[m - 1][n - 1] -``` - -# 92. [最小路径和](https://leetcode.cn/problems/minimum-path-sum/)64-20260115 - -经典动态规划 应该挺简单的 - -```python -class Solution: - def minPathSum(self, grid: List[List[int]]) -> int: - # dp到达某个点的最短距离 - for r in range(1,len(grid)): - grid[r][0] += grid[r-1][0] - for c in range(1,len(grid[0])): - grid[0][c] += grid[0][c-1] - for r in range(1,len(grid)) : - for c in range(1,len(grid[0])) : - grid[r][c] += min(grid[r-1][c],grid[r][c-1]) - return grid[-1][-1] - -``` - -# 93. [最长回文子串](https://leetcode.cn/problems/longest-palindromic-substring/)5-20260115 - -## 很慢 - -dp 用 判断 i,j 是不是 用 前一个判断就行 但是是 $O(n^2)$ 的复杂度 -```python -class Solution: - def longestPalindrome(self, s: str) -> str: - # dp 以 i 为结尾的最长子序列 这个难以实现 试试 i,j 是否构成 回文串 - l = len(s) - isPal = set() - for i in range(l) : - isPal.add((i,i)) - isPal.add((i+1,i)) # cbbd 这种 - ans = s[0] - # ij 是不是 依赖 i+1 j-1 画个i*j图 确定遍历顺序 - for j in range(l) : - for i in range(j,-1,-1): - if (i+1,j-1) in isPal and s[i]==s[j] : - isPal.add((i,j)) - ans = s[i:j+1] if j-i+1 > len(ans) else ans - return ans - -``` - -## 思路一: 中心拓展算法 - -在上面的基础上 ,遍历每个回文中心和 长度 -![[Pasted image 20260116154906.png]] -```python -class Solution: - def longestPalindrome(self, s: str) -> str: - if not s: # 处理空字符串边界case - return "" - - l = len(s) - isPal = set() - # 初始化:单个字符是回文(i,i),空区间(i+1,i)表示偶数长度回文的初始状态 - for i in range(l): - isPal.add((i, i)) - isPal.add((i+1, i)) # 对应偶数长度回文的初始(如cbbd中bb的初始) - ans = s[0] # 初始最长回文为第一个字符 - - # 中心扩展:遍历每个可能的中心(奇数长度中心为i,偶数长度中心为i和i+1) - for i in range(l): - # -------- 处理奇数长度回文(中心为i) -------- - lenPal = 0 # 扩展半径,初始0(仅包含中心i) - # 扩展条件:左右边界不越界,且扩展后的字符相等 - while i - lenPal - 1 >= 0 and i + lenPal + 1 < l: - # 核心:只要左右字符相等,就构成新的回文,无需依赖之前的isPal(简化逻辑) - if s[i - lenPal - 1] == s[i + lenPal + 1]: - new_left = i - lenPal - 1 - new_right = i + lenPal + 1 - isPal.add((new_left, new_right)) - # 更新最长回文 - if new_right - new_left + 1 > len(ans): - ans = s[new_left:new_right + 1] - lenPal += 1 - else: - break # 无法扩展,终止当前中心的奇数扩展 - - # -------- 处理偶数长度回文(中心为i和i+1) -------- - lenPal_even = 0 # 偶数扩展半径,初始0 - while i - lenPal_even >= 0 and i + lenPal_even + 1 < l: - # 核心:偶数回文需要i-lenPal_even 和 i+lenPal_even+1 字符相等 - if s[i - lenPal_even] == s[i + lenPal_even + 1]: - new_left = i - lenPal_even - new_right = i + lenPal_even + 1 - isPal.add((new_left, new_right)) - # 更新最长回文 - if new_right - new_left + 1 > len(ans): - ans = s[new_left:new_right + 1] - lenPal_even += 1 - else: - break # 无法扩展,终止当前中心的偶数扩展 - - return ans -``` - -## 思路二:马拉车算法 Manacher (一般不作为面试内容 - -```python -class Solution: - def expand(self, s, left, right): - """中心扩展算法,计算当前节点的臂长""" - while left >= 0 and right < len(s) and s[left] == s[right]: - left -= 1 - right += 1 - return (right - left - 2) // 2 - - def longestPalindrome(self, s: str) -> str: - # 马拉车算法 - - # 1.初始化 - # 初始化输出结果的指针位置 - start, end = 0, -1 - # 初始化字符串 - s = '#' + '#'.join(list(s)) + '#' - # 记录每个中心的回文臂长 - arm_len = [] - # 历史到达的最远的右指针位置 - right = -1 - # 历史到达最远右指针位置对应的回文中心 - j = -1 - # 当前节点为中心的回文臂长 - - # 2.遍历回文中心 - for i in range(len(s)): - # 3.如果此时遍历的中心点已经超过了最远的有边界位置 - if i > right: - cur_arm_len = self.expand(s, i, i) - # 4.如果还没超过 - else: - # 4.1计算对称点 - i_sym = 2*j - i - # 4.2计算开始遍历的臂长 - min_arm_len = min(arm_len[i_sym], right - i) - # 4.3更新臂长 - cur_arm_len = self.expand(s, i - min_arm_len, i + min_arm_len) - # 5.记录当前位置的臂长 - arm_len.append(cur_arm_len) - # 6.如果当前臂长超过了最长记录,更新状态 - if i + cur_arm_len > right: - right = i + cur_arm_len - j = i - # 7.如果长度大于输出的结果 - if 2 * cur_arm_len + 1> end - start: - start = i - cur_arm_len - end = i + cur_arm_len - - # 8.切片输出跳过# - return s[start + 1: end + 1 : 2] -``` - -## 最好\*:别人写的中心拓展 - -```python -class Solution: - def longestPalindrome(self, s: str) -> str: - n = len(s) - ans_left = ans_right = 0 - #奇数长度回文的中心:n 个,偶数中心:n-1个;总共:n + (n-1) = 2n - 1 个中心 - for i in range(2*n-1): - l,r = i//2, (i+1)//2 #通过整数除法处理奇偶中心 - while l >= 0 and r < n and s[l] == s[r]: - l -= 1 - r += 1 - if r - l - 1 > ans_right - ans_left: - ans_left,ans_right = l+1, r - return s[ans_left : ans_right] -``` - -# 94. [最长公共子序列](https://leetcode.cn/problems/longest-common-subsequence/)1143-20260116 - -动态规划 注意DP数组的定义 - -```python -class Solution: - def longestCommonSubsequence(self, text1: str, text2: str) -> int: - # dp i,j 表示 t1 [0:i] t2[0,j] 的最长公共序列长度 闭 - dp = [[0]*(len(text2)+1) for _ in range((len(text1)+1))] - for i in range(1,len(text1)+1): - for j in range(1,len(text2)+1): - if text1[i-1] == text2[j-1] : - dp[i][j] = dp[i-1][j-1]+1 - else : - dp[i][j] = max( dp[i-1][j], dp[i][j-1] ) - return dp[-1][-1] - -``` - -## 位运算 - -```python -class Solution: - def longestCommonSubsequence(self, text1: str, text2: str) -> int: - # Step 1: Record positions of each character in text1 - char_masks = {} - for i, char in enumerate(text1): - char_masks[char] = char_masks.get(char, 0) | (1 << i) - - # v stores the "increment" bits (1 if dp[j] > dp[j-1]) - v = 0 - for char in text2: - # x marks all possible match positions OR existing increments - x = char_masks.get(char, 0) | v - - # (v << 1) | 1 creates a "borrow" bit to start the chain reaction - # x - (...) performs bit-parallel search for the first available '1' - # x ^ (...) identifies which bits were affected by the subtraction - # x & (...) picks the bits that actually contribute to the LCS increment - v = x & (x ^ (x - ((v << 1) | 1))) - - return v.bit_count() # count_set_bits(v) -``` - -# 95. [编辑距离](https://leetcode.cn/problems/edit-distance/)72-20260116(没做) - -这两个题好难啊 - -# 96. [只出现一次的数字](https://leetcode.cn/problems/single-number/)136-20260116 - -## 技巧:位运算 - -```python -class Solution: - def singleNumber(self, nums: List[int]) -> int: - ans = 0 - for n in nums : - ans^=n - return ans -``` - -# 97. [多数元素](https://leetcode.cn/problems/majority-element/)169-20260116 - -## 用on的空间 - -```python -class Solution: - def majorityElement(self, nums: List[int]) -> int: - mp = defaultdict(int) - for n in nums : - mp[n]+=1 - for k,v in mp.items() : - if v > len(nums)/2 : - return k -``` - -```python -class Solution: - def majorityElement(self, nums: List[int]) -> int: - nums.sort() - return nums[(len(nums) - 1) // 2] -``` - -## 用o1的空间 随机找 - -```python -class Solution: - def majorityElement(self, nums: List[int]) -> int: - majority_count = len(nums) // 2 - while True: - candidate = random.choice(nums) - if sum(1 for elem in nums if elem == candidate) > majority_count: - return candidate -``` - -## Boyer-Moore 投票算法 - -```python -class Solution: - def majorityElement(self, nums: List[int]) -> int: - count = 0 - candidate = None - - for num in nums: - if count == 0: - candidate = num - count += (1 if num == candidate else -1) - - return candidate - -作者:力扣官方题解 -链接:https://leetcode.cn/problems/majority-element/solutions/146074/duo-shu-yuan-su-by-leetcode-solution/ -来源:力扣(LeetCode) -著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。 -``` - -# 98. [颜色分类](https://leetcode.cn/problems/sort-colors/)75-20260116(没做) - -# 99. [下一个排列](https://leetcode.cn/problems/next-permutation/)31-20260116 - - - - diff --git "a/source/bak/\351\242\230\347\233\256\347\254\224\350\256\260.md.bak.update" "b/source/bak/\351\242\230\347\233\256\347\254\224\350\256\260.md.bak.update" deleted file mode 100644 index c3f125c..0000000 --- "a/source/bak/\351\242\230\347\233\256\347\254\224\350\256\260.md.bak.update" +++ /dev/null @@ -1,3385 +0,0 @@ ---- -title: "题目笔记" -date: 2025-12-12 13:50:46 -updated: 2025-12-12 13:50:46 -mathjax: true -tags: - - LeetCode - - Job -categories: 实用技巧 -comments: true ---- -这个笔记用来记录 第一次刷 LeetCode Hot 100 -# 1. [两数之和](https://leetcode.cn/problems/two-sum/)1-251210 - -tag: 哈希表 - -```python -class Solution: - def twoSum(self, nums: List[int], target: int) -> List[int]: - dic = {} - for i in range(len(nums)): - residual = target - nums[i] - if nums[i] in dic.keys(): - return [i, dic[nums[i]]] - if residual not in dic.keys(): - dic[residual] = i -``` - -对每个数 留下 索引(value) 以及 到 target 的差(index),之后遇到 target差 的数,直接读出索引就行 - -# 2. [字母异位词分组](https://leetcode.cn/problems/group-anagrams/)49-251210 - -tag: 哈希表 排序 - -## 思路一:哈希 - -```python -class Solution: - - def groupAnagrams(self, strs: List[str]) -> List[List[str]]: - map = {} - for item in strs: - v = self.str2value(item) - if v in map.keys(): - map[v].append(item) - else: - map[v] = [item] - return list(map.values()) - - def str2value(self, stR:str) -> int : - res = 0 - for char in stR: - index = ord(char)-ord('a') - res += 10**(index) - return res*len(stR) -``` - -第一次的思路就是 把一个 str 得到一个顺序无关的哈希值。但是复杂度好像有点高。 - -更好的实现方式:使用了 `from collections import defaultdict` 内置函数 - -使用 defaultdict 是内置 dict 的子类,核心特性是,当访问不存在的键时,会自动创建这个键并赋值为 **指定默认值** 而不是抛出 KeyError。例如:初始化 `mp=collections.defaultdict(list)` 如果访问了不存在的键就会默认为空list (传入int就是默认0) - -还有一个 dict 的索引不能是 list dict set,可以是int float tuple str bool None frozenset ,list 可以转为tuple - -```python -class Solution: - def groupAnagrams(self, strs: List[str]) -> List[List[str]]: - dic = collections.defaultdict(list) - for s in strs: - count = [0] * 26 - for c in s: - count[ord(c)-ord('a')]+=1 - # 这一步的时间复杂度是 O(k)+1 ,k=26 tuple生成这个hash值需要 list长度的时间 - dic[tuple(count)].append(s) - return list(dic.values()) -``` - -这个方法时间复杂度是 O(n(k+s)) s是字符集的大小 26,每个 str 要 k 来遍历字符, s来生成 hash表的键。 - -## 思路二-排序\* 最优 - -对每个str排序,然后将排序的这个 字符串 当做字典的索引(哈希值)时间复杂度是 O(nklogk) - -内置函数 `sorted` 函数 输入字符串 返回一个按照 unicode 编码的 char list -''.join(list(char)) 把charlist 拼成一个串 注意这个join是 字符串的子函数 - -```python -class Solution: - def groupAnagrams(self, strs: List[str]) -> List[List[str]]: - dic = collections.defaultdict(list) - for s in strs: - index = ''.join(sorted(s)) - dic[index].append(s) - return list(dic.values()) -``` - -# 3. [最长连续序列](https://leetcode.cn/problems/longest-consecutive-sequence/)128-251210 - -tag: 并查集 set - -这个题要求了时间复杂度为O(n) 所以不考虑排序的方法,首先用 set 对 nums 去重,然后找到 所有序列开头的数字 并计算 这个序列的长度,**由于每个数仅进入一次内层循环**,所以时间复杂度符合要求。 - -```python -class Solution: - def longestConsecutive(self, nums: List[int]) -> int: - res = 0 - nums_set = set(nums) - for i in nums_set: - if i-1 in nums_set: - continue - # 否则说明这个数是 一个序列的开头 - l = 1 - p = i+1 - while p in nums_set: - l+=1 - p+=1 - res = max(res,l) - return res -``` - -下面这个节省20ms的时间 -```python -class Solution: - def longestConsecutive(self, nums: List[int]) -> int: - res = 0 - nums_set = set(nums) - for i in nums_set: - if i-1 in nums_set: - continue - # 否则说明这个数是 一个序列的开头 - p = i+1 - while p in nums_set: - p+=1 - res = max(res,p-i) - return res -``` - -这个算法的关键是找到起点 避免对一个数的重复遍历 -list 转 set 还有 判断 in set 的操作 时间复杂度是 O(1), 如果是用 list 的 in 时间复杂度是 O(n) - -# 4. [移动零](https://leetcode.cn/problems/move-zeroes/)283-251210 - -tag: 双指针 快慢指针 - -## 思路一 遇到零放后面 - -list : remove是移除 第一个 value,pop是删掉指定index的item -顺序遍历,把0移到最后 - -```python -class Solution: - def moveZeroes(self, nums: List[int]) -> None: - """ - Do not return anything, modify nums in-place instead. - """ - i=0 - last = len(nums) - while i < last : - if nums[i] == 0 : - nums.pop(i) - nums.append(0) - last-=1 - i-=1 - i+=1 -``` - -## 思路二 栈 遇到非零压入栈 - -```python -class Solution: - def moveZeroes(self, nums: List[int]) -> None: - stack_size=0 - for num in nums: - if num : - nums[stack_size] = num - stack_size+=1 - nums[stack_size:len(nums)] = [0]*(len(nums)-stack_size) -``` - -这个方法在最坏的情况下(全是0) 要遍历两次数组 - -## 思路三 双指针\* 最优 - -依次把非零元素移动到 数组靠左边的空位置上。参考快速排序的想法,快拍要确定一个待分割的元素x作为中间点,然后小于等于x放到左边,大于x放到右边。 - -一个指向第一个0 另一个遇到第一个非零就和第一个0换位置,然后指向第一个0的后移一位(仍然是第一个0) (这个理解好像不太好) - -换一个想法,两个指针,慢的说明 其左边 全部都是 保留顺序的非零(也就是指向待处理序列的第一个,只有交换了也就是处理好了,才移动),快的去找下一个需要被处理的非零数字。这样保留了原本的顺序。 - -```python -class Solution: - def moveZeroes(self, nums: List[int]) -> None: - slow=fast=0 - while fast < len(nums): - if nums[fast]: - nums[slow],nums[fast]=nums[fast],nums[slow] - slow+=1 - fast+=1 -``` - -# 5. [盛最多水的容器](https://leetcode.cn/problems/container-with-most-water/)11-251210 - -tag: 双指针 - -首先对于壁 i int: - ans=0 - i,j = 0,len(height)-1 - while i!=j : - ans = max(self.S(i,j,height),ans) - if height[i] int: - l, r = 0, len(height) - 1 - ans = 0 - while l < r: - # w = r - l - h = min(height[l], height[r]) - ans = max(ans, (r - l) * h) - while height[l] <= h and l < r: l += 1 - while height[r] <= h and l < r: r -= 1 - return ans -``` - - -通过节省搜索空间的想法,还可以进一步,通过记录高度的最大值,得到最大的S,然后直接return - -```python -class Solution: - def maxArea(self, height: List[int]) -> int: - max_area = 0 - l, r = 0, len(height) - 1 - max_height = max(height) - - while l < r: - if max_height * (r - l) < max_area: - return max_area - cur_area = min(height[l], height[r]) * (r - l) - if cur_area > max_area: - max_area = cur_area - if height[l] > height[r]: - r -= 1 - else: - l += 1 - return max_area -``` - -# 6. [三数之和](https://leetcode.cn/problems/3sum/)15-251211 - -tag: 双指针 - -最简单的想法 对于每个数 当做一个两数之和来做,但是由于两数之和的解法复杂度是 $O(n)$,这种做法的复杂度是 $O(n^2)$. - -还有一种是用双指针,来找和为 0 的 也是最优的。 - -```python -class Solution: - def threeSum(self, nums: List[int]) -> List[List[int]]: - nums.sort() - ans = [] - n = len(nums) - for i in range(n - 2): - x = nums[i] - if i > 0 and x == nums[i - 1]: # 跳过重复数字 - continue - if x > 0 or nums[-1]<0 : # 优化一 - break - if x + nums[-2] + nums[-1] < 0: # 优化二 - continue - j = i + 1 - k = n - 1 - while j < k: - s = x + nums[j] + nums[k] - if s > 0: - k -= 1 - elif s < 0: - j += 1 - else: # 三数之和为 0 - ans.append([x, nums[j], nums[k]]) - j += 1 - while j < k and nums[j] == nums[j - 1]: # 跳过重复数字 - j += 1 - k -= 1 - while k > j and nums[k] == nums[k + 1]: # 跳过重复数字 - k -= 1 - return ans -``` - -这个方法和 第一个复杂度都是$O(n^2)$ 但是双指针要快一些啊,是因为 枚举 + 哈希表法虽是 O (n²),但哈希表的额外开销、随机内存访问导致常数项远大于双指针;1. 双指针法的「$O(n^2)$」是**真 $O(n^2)$**(外层 n 次,内层 n 次),且常数项极小; - -# 7. [接雨水](https://leetcode.cn/problems/trapping-rain-water/)42-251211 - -tag: 单调栈 动态规划 双指针 - -## 思路一:单调栈\* - -最开始的思路是 遍历所有h 而不是index 有一个问题就是,如果相等的话之后来了更高的没办法记录宽度,所以尝试用index做一下 - -```python -class Solution: - def trap(self, height: List[int]) -> int: - stack = [] - ans = 0 - for i,h in enumerate(height) : - while stack and h > height[stack[-1]] : - top = stack.pop() - if not stack : - break - # 这个是重点部分 好好想一下 - ans += (min(height[stack[-1]],h)-height[top])*(i-stack[-1]-1) - stack.append(i) - return ans -``` -这个部分我觉得最难的是 每次加多少面积,由于单调栈有单调递减的特征,所以栈顶元素一定是最小的,计算的思路就是 记录 每次pop 添加的水 是一排 就是接雨水 水平线往上的? - -## 思路二:动态规划 - -每个位置 i 能接的水的数量 = min( i 左边的最大高度, i 右边的最大高度) - h\[i\] ,所以暴力搜索就是: -```python -class Solution: - def trap(self, height: List[int]) -> int: - ans = 0 - l = len(height) - dp = [0] * l - # 暴力 - for i in range(1,l-1) : - dp[i] = max(min(max(height[0:i]),max(height[i+1:])) - height[i],0) - return sum(dp) -``` - -在内循环中,在数组中找max操作复杂度为 $O(n)$ , 所以这个暴力搜索的复杂度为 $O(n^2)$ ,通过 动态规划的思路 先用两个list: $O(n)$ 来记录 index i 左边的最大和右边的最大height是多少。 - -```python -class Solution: - def trap(self, height: List[int]) -> int: - l = len(height) - ans = [0] * l - # 左侧最大值 不包括 - leftMax = [0] * l - # 右侧最大值 不包括 - rightMax = [0] * l - maxh = 0 - maxr = 0 - # 这里也可以用动态规划的状态转移方程 会更快 - for i in range(l) : - leftMax[i] = maxh - maxh = maxh if maxh > height[i] else height[i] - rightMax[l-1-i] = maxr - maxr = maxr if maxr > height[l-1-i] else height[l-1-i] - for i in range(1,l-1) : - # ans[i] = max(min(max(height[0:i]),max(height[i+1:])) - height[i],0) - ans[i] = max(min(leftMax[i],rightMax[i]) - height[i],0) - return sum(ans) -``` - -## 思路三:双指针 - -本质上和动态规划类似,只是用双指针的方法可以把空间复杂度减到O1,时间复杂度还是 $O(n)$ - -```python -class Solution: - def trap(self, height: List[int]) -> int: - ans = 0 - left, right = 0, len(height) - 1 - leftMax = rightMax = 0 - while left < right: - leftMax = max(leftMax, height[left]) - rightMax = max(rightMax, height[right]) - if height[left] < height[right]: - ans += leftMax - height[left] - left += 1 - else: - ans += rightMax - height[right] - right -= 1 - return ans -``` - -# 8. [无重复字符的最长子串](https://leetcode.cn/problems/longest-substring-without-repeating-characters/)3-251212 - -tag: 滑动窗口 - -这个比较简单,可以学习一下别人的代码思路。下面这个是我的。 - -```python -class Solution: - def lengthOfLongestSubstring(self, s: str) -> int: - ans,i,l = 0,0,len(s) - window = collections.deque() - count = collections.defaultdict(int) - while i < l : - while i < l and count[s[i]] == 0 : - window.append(s[i]) - count[s[i]] += 1 - i += 1 - ans = max(len(window),ans) - while i < l and count[s[i]]!=0 : - f = window.popleft() - count[f] -= 1 - return ans -``` - -用 set(字典本身就适合处理 是否重复这样的事情) -```python -class Solution: - def lengthOfLongestSubstring(self, s: str) -> int: - # 滑动窗口 - left = 0 - window = set() - max_length = 0 - for right in range(len(s)): - while s[right] in window: - window.remove(s[left]) - left += 1 - window.add(s[right]) - if len(window) > max_length: - max_length = len(window) - return max_length -``` - -这个2ms 最快 -```python -class Solution: - def lengthOfLongestSubstring(self, s: str) -> int: - n = len(s) - if n <= 1: - return n - _dict = {} - start = -1 - res = 0 - for i, c in enumerate( s ): - if c in _dict and _dict[c] > start: - start = _dict[c] - res = max(res, i - start) - _dict[c] = i - return res -``` - -# 9. [找到字符串中所有字母异位词](https://leetcode.cn/problems/find-all-anagrams-in-a-string/)438-251212 - -tag: 滑动窗口 - -自己写的解 -```python -class Solution: - def findAnagrams(self, s: str, p: str) -> List[int]: - ans = [] - count = {} - window = collections.deque() - for c in p : - count[c] = 0 - for c in p : - count[c] -= 1 - count['other'] = 0 - start = 0 - for c in s : - window.append(c) - if c in count.keys() : - count[c] += 1 - while count[c] > 0 : - f = window.popleft() - start += 1 - count[f] -= 1 - else : - count['other'] += 1 - while count['other'] != 0 : - f = window.popleft() - start += 1 - if f in count.keys() : - count[f] -= 1 - else : - count['other'] -= 1 - if len(window) == len(p) : - ans.append(start) - return ans -``` - -# 10. [和为 K 的子数组](https://leetcode.cn/problems/subarray-sum-equals-k/)560-251212 - -tag: 前缀和 - -维护一个前n项和 sumn 然后 如果遇到 现在的 sumn - k 存在(x个) 那么就 有x个子数组符合 -```python -class Solution: - def subarraySum(self, nums: List[int], k: int) -> int: - ans,sumn = 0, 0 - cnt = collections.defaultdict(int) - for n in nums: - cnt[sumn] += 1 - sumn += n - ans += cnt[sumn - k ] - return ans -``` - -直到思路之后写了一次也没写对,当成两数之和了,存了index 应该是维护一个sumn为一个数的序列个数,之后再做一次 - -# 11. [滑动窗口最大值](https://leetcode.cn/problems/sliding-window-maximum/)239-251215 - -tag: 大根堆 / 单调队列 优先队列 - -## 思路一:大根堆 - -对于最大值这种问题,可以 维护一个 优先队列(大根堆)来做,也就是放入新元素是按照 我们想要的顺序存好。这个思路很容易想到,要善用这个数据结构。暴力的方法是,每次加入新元素就用$O(k)$ 来找一下窗口中的最大值,但是每个元素在整个流程中几乎被遍历k次,可以节省时间的方法就是记录在窗口中的元素的大小信息。也就是排序,但是如果每次来新的都排序,时间反而大于$O(k)$,因此需要我们维护一个 大根堆来做。在python中 最小堆是 :heapq.heapify(list) 可以将一个list转换成最**小**堆 这个只保证**堆顶是 最小的** 而不是有序的 - -```python -class Solution: - def maxSlidingWindow(self, nums: List[int], k: int) -> List[int]: - maxheap = [(-nums[i],i) for i in range(0,k) ] - # 这个python 标准库的最小堆 - heapq.heapify(maxheap) - ans = [ -maxheap[0][0] ] - for i in range(k,len(nums)) : - heapq.heappush(maxheap,(-nums[i],i)) - while maxheap[0][1] < i-k+1 : - heapq.heappop(maxheap) - ans.append(-maxheap[0][0]) - return ans -``` - -## 思路二:单调队列(优先队列)\* - -对于下标 $i List[int]: - q = collections.deque() - ans = [] - for i in range(0,len(nums)) : - while q and nums[i] >= nums[q[-1]] : - q.pop() - q.append(i) - while q[0] <= i-k : - q.popleft() - if i >= k-1 : - ans.append(nums[q[0]]) - return ans -``` - -维护了这样的单调递减序列,保证 开头是最大的,而且开头还得在窗口中。 - -# 12. [最小覆盖子串](https://leetcode.cn/problems/minimum-window-substring/)76-251215 - -tag: 滑动窗口 - -```python -class Solution: - def minWindow(self, s:str, t: str) -> str: - # 维护一个缺少多少的表 - w = collections.deque() - rq = {} - ansl ,l = 9999999,0 - ans = '' - for c in t : - if c in rq.keys() : - rq[c] += 1 - else : - rq[c] = 1 - # 处理队列,保证 rq 都得是 0 - for c in s : - w.append(c) - l += 1 - if c in rq.keys() : - rq[c] -= 1 - while w : - if w[0] in rq.keys() : - print(rq[w[0]]) - if w[0] in rq.keys() : - if rq[w[0]] >= 0 : - break - else : - rq[w[0]] += 1 - w.popleft() - l -= 1 - jump = False - for i in rq.values() : - if i > 0 : - jump = True - if ansl > l and jump == False : - ansl = l - ans = ''.join(w) - return ans -``` - -# 13. [最大子数组和](https://leetcode.cn/problems/maximum-subarray/)53-251215 - -tag: 动态规划 分治法 前缀和 做题目的时候不要被刷过的题目的思路限制住方向 也要做新的想法尝试 - -## 思路一:DP - -以 index 为 **结尾** 的最大子序列和 -```python -class Solution : - def maxSubArray(self, nums: List[int]) -> int : - # 以index 为结尾的最大值 - dp = [0] * len(nums) - for i,n in enumerate(nums) : - dp[i] = max(dp[i-1]+nums[i],nums[i]) - return max(dp) -``` - -## 思路二:分治法(还没学1216) - -官方题解说这个类似于 「线段树求解最长公共上升子序列问题」的pushup操作 - - - -## 思路三: 前缀和 - -参考 5盛水最多的容器中的动态规划方法,记录一个index左边最大和右边最小 -```python -# class Solution: -# def maxSubArray(self, nums: List[int]) -> int: -# # 一个index左边的最小值,一个index右边的最大值 -# leftMin,rightMax = [10001]*(len(nums)+1),[-10001]*(len(nums)+1) -# sn = [0] * (len(nums)+1) -# for i in range(len(nums)) : -# sn[i+1] = nums[i] -# for i in range(1,len(sn)) : -# sn[i] += sn[i-1] -# leftMin[0] = sn[0] -# rightMax[-1] = sn[-1] -# for i in range(1,len(sn)) : -# leftMin[i] = min(leftMin[i-1],sn[i]) -# rightMax[len(sn)-1-i] = max(rightMax[len(sn)-i],sn[len(sn)-1-i]) -# ans = -99999999 -# for i in range(len(sn)-1) : -# if ans < rightMax[i+1] - leftMin[i] : -# ans = rightMax[i+1] - leftMin[i] -# return ans -``` - -维护两个太慢了,因为这个主要的约束 是 最小值在最大值的右边,通过记录最小值,然后计算 s\[n] 和 min 的差 来更新 结果: - -```python -class Solution : - def maxSubArray(self, nums: List[int]) -> int : - tsum, tmin, ans = 0, 0, -99999 - for n in nums : - tsum += n - ans = max(ans,tsum-tmin) - tmin = min(tmin,tsum) - return ans -``` - -# 14. [合并区间](https://leetcode.cn/problems/merge-intervals/)56-251216 - -tag: 排序 数组 单调栈 - -这个重点在于 排序的时候使用 lambda 表达式 lambda是个函数,lambda x: y 意思是输入 x 输出 y ,排序时候用sort(key =) - -```python -# 20ms -class Solution: - def merge(self, intervals: List[List[int]]) -> List[List[int]]: - intervals.sort(key=lambda x: x[0]) - i = 0 - while True : - if i+1 >= len(intervals) : - break - if intervals[i][1] >= intervals[i+1][0] : - new_iv = [intervals[i][0],max(intervals[i][1],intervals[i+1][1])] - intervals.remove(intervals[i+1]) - intervals[i] = new_iv - else : i += 1 - return intervals -``` - -现在这种 每次的 remove 很慢 总体 20ms左右,还有一种方法是 维护一个 单调的栈,可以合并就合并后压栈否则就直接压. 这样结果就会快一点 - -```python -# 5ms -class Solution: - def push(self,res_inv,item) : - if len(res_inv) == 0 : - res_inv.append(item) - top = res_inv[-1] - if top[1] >= item[0] : - res_inv.pop() - item = [top[0],max(top[1],item[1])] - res_inv.append(item) - return res_inv - - def merge(self, intervals: List[List[int]]) -> List[List[int]]: - ans = [] - intervals.sort(key=lambda x:x[0]) - for iv in intervals : - self.push(ans,iv) - return ans -``` - -# 15. [轮转数组](https://leetcode.cn/problems/rotate-array/)189-251216 - -```python -class Solution: - def rotate(self, nums: List[int], k: int) -> None: - """ - Do not return anything, modify nums in-place instead. - """ - k = k%len(nums) - nums[:] = nums[len(nums)-k:] + nums[:len(nums)-k] -``` -需要注意的两个是 k 可能超过 nums 的长度,需要通过%来计算 时间移动了多少步 -然后 下一行中 如果 nums\[:] 写成 nums ,那么nums就不会被修改 这个是python的一个特性 写成nums就是修改的这个局部变量 而不是修改了这个引用。 - -这个时间复杂度是 $O(n)$ - -\*最快的办法是反转数组: - -```python -class Solution: - def rotate(self, nums: List[int], k: int) -> None: - """ - Do not return anything, modify nums in-place instead. - """ - - # [1,2,3,4,5,6,7] - # 1. 反转数组 [7,6,5,4,3,2,1] - # 2. 反转前k=3个元素 [5,6,7, 4,3,2,1] - # 3. 反转剩余元素 [5,6,7, 1,2,3,4] - n = len(nums) - k = k % n - nums.reverse() - nums[:k] = nums[:k][::-1] - nums[k:] = nums[k:][::-1] -``` - -# 16. [除自身以外数组的乘积](https://leetcode.cn/problems/product-of-array-except-self/)238-251216 - -#前后缀 - -## 思路一:前后缀积 - -这个 也是用 前缀后缀积 但是不需要 维护两个list 因为我们知道最后的目的是 在 index位置 ans\[i] = $\prod_{j\ne i}nums_j$ 所以不妨 先正向遍历拿到前面的积 再反向遍历,拿到后面的积。 - -```python -class Solution: - def productExceptSelf(self, nums: List[int]) -> List[int]: - length = len(nums) - answer = [0] * length - answer[0] = 1 - for i in range(1, length): - answer[i] = nums[i - 1] * answer[i - 1] - R = 1 - for i in reversed(range(length)): - answer[i] = answer[i] * R - R *= nums[i] - return answer -``` - -## UES - -这个解法考虑了0 同时用了reduce函数可以看一下 - -```python -class Solution: - def productExceptSelf(self, nums: List[int]) -> List[int]: - if 0 in nums: - ret= [0]*len(nums) - if nums.count(0)>=2: - return ret - ret[nums.index(0)]=reduce(mul,(i for i in nums if i!=0),) - return ret - p=reduce(mul,nums,) - if p!=0: - return [p//i for i in nums] -``` -## Trash - -用 前缀积 后缀积 来在 $O(n)$ 时间复杂度解决这个问题 但是 空间复杂度是 $O(n)$ - -```python -class Solution: - def productExceptSelf(self, nums: List[int]) -> List[int]: - fD, bD = nums[:], nums[:] # 定义前缀后缀积 - for i in range(1,len(nums)) : - fD[i] *= fD[i-1] - bD[len(nums)-i-1] *= bD[len(nums)-i] - ans = [ bD[1] ] - for i in range(1,len(nums)-1) : - ans.append( fD[i-1] * bD[i+1] ) - ans.append(fD[-2]) - return ans -``` - -# 17. [缺失的第一个正数](https://leetcode.cn/problems/first-missing-positive/)41-251216 - - -## 思路一:原地哈希(节省内存) - -参考这个:[寻找文件副本](https://leetcode.cn/problems/shu-zu-zhong-zhong-fu-de-shu-zi-lcof/) - -ans 最大的情况 就是 1,len(nums)都在 ,那么如果把 nums 中的 值 作为 index 那么肯定有 index 用不完的情况,第一个没用的就是 答案。 -v\[index] 是负数,那么这个index 可以直接拿来用,如果 v\[i]>0 那么我们就需要把 这个 v\[i] 也给记录下来。向链表一样顺着找 - -```python -class Solution: - def firstMissingPositive(self, nums: List[int]) -> int: - l = len(nums) - for i in range(len(nums)) : - target = nums[i]-1 - if target < 0 or target >= l : - continue - while nums[target] <= l and nums[target] > 0 : - if nums[target] == target + 1 : - break - next_target = nums[target] - 1 - nums[target] = target + 1 - target = next_target - nums[target] = target + 1 - for i in range(len(nums)) : - if i+1 != nums[i] : - return i+1 - else : - return l+1 -``` - - -题解中的原地哈希,这个比上面的方法更好 -```python -class Solution: - def firstMissingPositive(self, nums: List[int]) -> int: - n = len(nums) - for i in range(n): - if nums[i] <= 0: - nums[i] = n + 1 - for i in range(n): - num = abs(nums[i]) - if num <= n: - nums[num - 1] = -abs(nums[num - 1]) - for i in range(n): - if nums[i] > 0: - return i + 1 - return n + 1 -``` -## Trash -题目中限制了空间复杂度是常数,如果不考虑空间,这个方法很快 -```python -class Solution: - def firstMissingPositive(self, nums: List[int]) -> int: - ans = len(nums)+1 - s = set(nums) - for i in range(len(nums),0,-1) : - if i in s : - continue - else : ans = min(ans,i) - return ans -``` - -# 18. [矩阵置零](https://leetcode.cn/problems/set-matrix-zeroes/)73-251216 - -先处理行再处理列 这个是 $O(mn)$ 时间复杂度。空间复杂度是 $O(n)$ - -```python -class Solution: - def setZeroes(self, matrix: List[List[int]]) -> None: - """ - Do not return anything, modify matrix in-place instead. - """ - c_index = set() - for i,n in enumerate(matrix) : - if 0 in n : - index = [ -1 if n[index]!=0 else index for index in range(len(n)) ] - [c_index.add(x) for x in index] - matrix[i] = [0] * len(n) - for i,n in enumerate(matrix) : - for j in range(len(n)) : - if j in c_index: - matrix[i][j] = 0 -``` - -# 19. [螺旋矩阵](https://leetcode.cn/problems/spiral-matrix/)54-251216 - -遇到墙换方向 - -```python -class Solution: - def spiralOrder(self, matrix: List[List[int]]) -> List[int]: - ans = [] - i,j = 0,0 - down = len(matrix) - right = len(matrix[0]) - total = down * right - left = 1 - up = 1 - - di,dj = 0,1 - cnt = 0 - if right == 1 : - ans = [ i[0] for i in matrix ] - return ans - - while True : - print(i,j) - ans.append(matrix[i][j]) - cnt += 1 - if cnt == total : - break - i,j = i+di,j+dj - if j == right-1 and di == 0 and dj == 1 : - di,dj = 1,0 - up = up + 1 - if i == down-1 and di == 1 and dj == 0 : - di,dj = 0,-1 - right = right - 1 - if j == left - 1 and di == 0 and dj == -1 : - di,dj = -1,0 - down = down - 1 - if i == up - 1 and di == -1 and dj == 0 : - di,dj = 0,1 - left = left + 1 - return ans -``` - - -# 20. [旋转图像](https://leetcode.cn/problems/rotate-image/)48-251216 - -主要是下标处理 这几个矩阵相关的题目 - -```python -class Solution: - def p_single_pos(self,nums,i,j): - self.swap(nums,i,j,j,self.col-i-1) - self.swap(nums,i,j,self.row-i-1,self.col-j-1) - self.swap(nums,i,j,self.row-j-1,i) - - def swap(self,nums,i,j,k,l): - temp = nums[i][j] - nums[i][j] = nums[k][l] - nums[k][l] = temp - - def rotate(self, matrix: List[List[int]]) -> None: - """ - Do not return anything, modify matrix in-place instead. - """ - self.row = len(matrix) - self.col = len(matrix[0]) - for i in range(self.col//2 + 1) : - pos_list = [ [i,x] for x in range(i,self.col-i-1) ] - for t in pos_list : - self.p_single_pos(matrix,t[0],t[1]) -``` - -# 21. [搜索二维矩阵 II](https://leetcode.cn/problems/search-a-2d-matrix-ii/)240-251216 - -tag: 二分查找 分治法 - -## 思路一:Z字形查找 - -最简单的想法,时间复杂度是 $O(n+m)$ - -```python -class Solution: - def searchMatrix(self, matrix: List[List[int]], target: int) -> bool: - maxColIndex = len(matrix[0])-1 - for i,n in enumerate(matrix[0]) : - if n >= target : - maxColIndex = i-1 - if n == target : - return True - print(maxColIndex) - for i in range(0,maxColIndex+1): - for j in range(len(matrix)) : - if matrix[j][i] == target : - return True - if matrix[j][i] > target : - break - return False -``` - -## 思路二:二分查找 - -```python -class Solution: - def searchMatrix(self, matrix: List[List[int]], target: int) -> bool: - for row in matrix: - if row[0] > target : - break - idx = bisect.bisect_left(row, target) - if idx < len(row) and row[idx] == target: - return True - return False -``` - -防止不会 自己写一下 binary search -```python -class Solution: - def bs(self,nums,target): - mid = (len(nums))//2 - print(mid,nums) - if nums[0] > target : - return None - if nums[-1] < target : - return None - if nums[mid] > target : - return self.bs(nums[0:mid],target) - elif nums[mid] < target : - return self.bs(nums[mid+1:],target) - else : return mid - - def searchMatrix(self, matrix: List[List[int]], target: int) -> bool: - for row in matrix: - if row[0] > target : - break - idx = self.bs(row, target) - if idx!=None : - return True - return False -``` - -## 思路三:贪心Z* 从左下角开始搜索! - -操了 这个和前面的 Z搜索区别在于 从左下角开始搜索,这样的话就可以沿着 减增减 的序列搜索了 可以排除更多不可能区域. 我觉得这个想法**非常好**。 -[参考题解](https://leetcode.cn/problems/search-a-2d-matrix-ii/solutions/2361487/240-sou-suo-er-wei-ju-zhen-iitan-xin-qin-7mtf) - -```python -class Solution: - def searchMatrix(self, matrix, target) -> bool : - i, j = len(matrix)-1,0 - while i>=0 and j<=len(matrix[0])-1 : - if matrix[i][j] == target : - return True - i,j = (i-1,j) if matrix[i][j] > target else (i,j+1) - # if matrix[i][j] > target : - # i -= 1 - # else : - # j += 1 - return False -``` - -# 22. [相交链表](https://leetcode.cn/problems/intersection-of-two-linked-lists/)160-251217 - -## 思路一:双指针\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\* - -这个太帅了 -![](https://cdn.jsdelivr.net/gh/zip95297/zip95297.github.io@main/source/images/%E9%A2%98%E7%9B%AE%E7%AC%94%E8%AE%B0/Pasted%20image%2020251217145442.webp?raw=true) - -```python -class Solution: - def getIntersectionNode(self, headA: ListNode, headB: ListNode) -> Optional[ListNode]: - pA, pB = headA, headB - while pA != pB : - pA = pA.next if pA else headB - pB = pB.next if pB else headA - return pA -``` - - -## 思路二:哈希 - -在这个过程中改变了list结构,这样的方法就是记录那些节点是访问过的 -可以 用一个 set 记录哪些访问过,就不用这个了 但是用 Set 需要 o(m)的空间 - -```python -class Solution: - def getIntersectionNode(self, headA: ListNode, headB: ListNode) -> Optional[ListNode]: - startA = headA - while headA : - headA.val = -1 - headA.val - headA = headA.next - ans = None - while headB : - if headB.val < 0 and ans == None : - # headB.val = -headB.val - 1 - ans = headB - break - headB = headB.next - headA = startA - while headA : - headA.val = -headA.val - 1 - headA = headA.next - return ans -``` - -## 思路三:对齐开始 - -先全部遍历一下,找到两个的长度,然后将较长的往前移动几个,这下 AB list 长度相同,接下来就直接一起同步 next 就可以 但是 实际时间复杂度是 O(m+2n-len(chonghe)) - -# 23. [反转链表](https://leetcode.cn/problems/reverse-linked-list/)206-251217 - -tag: 递归 迭代 非暴力迭代 - -## 思路一:递归 - -写递归算法时候的一定要理清思路,想好怎么递归调用的。 - -首先思路很明确,就是要reverse \[A, ...] 就是 reverse ... ,然后把A 拼在后面。 有两点需要注意的,第一个是A 的next 需要变成None ,第二个就是 ... 的tail 的next 需要变成 A 然后递归下去就好。 - -另外做这个题的时候需要 return list 的 head 所以要在开始先把 tail 给记录下来以return - -```python -# 递归 -class Solution: - # reverse head 后面的 包括head - def r(self,head) : - # print(head) - # tail = head - # while tail.next!=None : - # tail = tail.next - if head.next : - self.r(head.next) - head.next.next = head - head.next = None - # print(tail) - - def reverseList(self, head: Optional[ListNode]) -> Optional[ListNode]: - if head == None : - return - tail = head - while tail.next!=None : - tail = tail.next - self.r(head) - return tail -``` - -## 思路二:迭代*: - -找到head开头的链的末端 往前指 前一个指空 (这是一个$O(n^2)$的垃圾方法),好好理理下面的那个 - -```python -# 不用看这个 复杂度不对 -class Solution: - def reverseList(self, head: Optional[ListNode]) -> Optional[ListNode]: - tail = head - if head is None : - return - while tail.next : tail = tail.next - while head.next : - # 找head的tail - p = head - pre = None - while p.next : - pre = p - p = p.next - else : - p.next = pre - pre.next = None - return tail -``` - -这个时间复杂度很高 应该换一个,逐步对每个都处理: - -```python -class Solution: - def reverseList(self, head: Optional[ListNode]) -> Optional[ListNode]: - prev, p = None, head - while p : - n = p.next - p.next = prev - prev = p - p = n - return prev -``` - -后来又写了一个 和这个人的方法 一模一样了,是这样做到的:反转列表 对于每一个 node ,next置none 然后把原本的next 的 next 置为这个node 这样会得到一个很不好的 循环。但是换一个思路,从第二个开始来说 相当于我要做的是 将这个 next 置为 prev (而不是把下一个的next 置为自己,这个是区别所在),那么我需要保留prev即可 然后 这个ptr 每次移动一个。由于next会改,所以要在最开始先记住node.next - -```python - def reverseKOnce(self, head) : - prev = None - while head : - n = head.next - head.next = prev - prev = head - head = n - return prev -``` -# 24. [回文链表](https://leetcode.cn/problems/palindrome-linked-list/)234-251217 - -## 思路一: 栈判断 - -用栈判断 但是感觉写的不好 很多下标处理: - -```python -class Solution: - def isPalindrome(self, head: Optional[ListNode]) -> bool: - # 先统计长度 然后用栈 判断 - stack = [] - p, l = head, 0 - while p : - p = p.next - l += 1 - if l == 1 : - return True - mid = l//2 - isJumpMid = True if l%2 == 1 else False - i, p = 1, head - while p : - if i <= mid : - stack.append(p.val) - else : - if stack.pop() != p.val : - return False - if i == mid and isJumpMid : - i+=1 - p = p.next - i+=1 - if p : - p = p.next - return True -``` - -## 思路二:复制到数组后双指针 - -这个方法也很快 - -```python -class Solution: - def isPalindrome(self, head: ListNode) -> bool: - vals = [] - current_node = head - while current_node is not None: - vals.append(current_node.val) - current_node = current_node.next - return vals == vals[::-1] -``` - -## 思路三:快慢指针 - -反转后面部分的 list 然后再 用两个指针比较 $O(n)$ $O(1)$ - -```python -class Solution: - - def isPalindrome(self, head: ListNode) -> bool: - if head is None: - return True - - # 找到前半部分链表的尾节点并反转后半部分链表 - first_half_end = self.end_of_first_half(head) - second_half_start = self.reverse_list(first_half_end.next) - - # 判断是否回文 - result = True - first_position = head - second_position = second_half_start - while result and second_position is not None: - if first_position.val != second_position.val: - result = False - first_position = first_position.next - second_position = second_position.next - - # 还原链表并返回结果 - first_half_end.next = self.reverse_list(second_half_start) - return result - - def end_of_first_half(self, head): - fast = head - slow = head - while fast.next is not None and fast.next.next is not None: - fast = fast.next.next - slow = slow.next - return slow - - def reverse_list(self, head): - previous = None - current = head - while current is not None: - next_node = current.next - current.next = previous - previous = current - current = next_node - return previous -``` - -# 25. [环形链表](https://leetcode.cn/problems/linked-list-cycle/)141-251217 - -tag: 快慢指针 集合 重复 -## 思路一:用集合判断是否访问过 - -这个方法空间复杂度是 $O(n)$ ,时间复杂度也是 $O(n)$ -```python -class Solution: - def hasCycle(self, head: Optional[ListNode]) -> bool: - visited = set() - while head : - if head in visited : - return True - visited.add(head) - head = head.next - return False -``` - -## 思路二: 快慢指针* - -$O(1)$ 空间复杂度,$O(n)$时间复杂度看是否重合。下面捋一下思路: -对于一个有环的 list ,环外的 长度为 $x$ ,环长度 为 $L$ ,那么如果一个快指针 一次走两个,慢指针一次走一个,那么,慢的先走x,同时 相当于 快的进入环内后走了x,然后两者相遇的方程 :$i=(x+2i)\%L$ -,对于这个 同余方程 一定有解,可以搜一下。 - -虽然快指针一次走两个,但是快慢之间的距离,其实是每次只**减少一**。所以一定会相遇 - -```python -# 快慢指针 -class Solution: - def hasCycle(self, head: Optional[ListNode]) -> bool: - slow, fast = head, head - if not head : - return False - while slow and fast : - slow = slow.next - fast = fast.next.next if fast.next and fast.next.next else None - if slow == fast and slow : - return True - return False -``` - -我的判断逻辑写得不好,可以学一下别人的: -```python -class Solution: - def hasCycle(self, head: Optional[ListNode]) -> bool: - slow, fast = head, head - while fast and fast.next: - slow = slow.next - fast = fast.next.next - if slow == fast: - return True - return False -``` - -# 26. [环形链表 II](https://leetcode.cn/problems/linked-list-cycle-ii/)142-251217 - -## 思路一:集合 - -这个空间复杂度高 简单 不写了 - -## 思路二:快慢指针* - -一个list 非环长 x 环长 L ,slow 走到环开头, fast 距离环开头 x 。此时 fast 还需要追 L-x ,由于每次 移动 fast 和 slow 的距离-=1 ,然后可以计算出 第一相遇位置,距离 环开头(倒退数) L-x (还需要L-x步,两者距离减为0),距离环结尾(前进数)x 。有了meetAt 这个点,再在 开头放置一个 ptr ,ptr 和 slow 一起同步移动,两者都移动 x 就都到了 开头。 - -```python -class Solution: - def detectCycle(self, head: Optional[ListNode]) -> Optional[ListNode]: - ptr, slow, fast, meetAt = head, head, head, None - # l = 0 - while fast and fast.next : - slow = slow.next - # l += 1 - fast = fast.next.next - if slow == fast : - meetAt = slow - break - if not meetAt : - return - while ptr != slow : - ptr = ptr.next - slow = slow.next - return ptr -``` - -我觉得这两个题都很有意思 - -# 27. [合并两个有序链表](https://leetcode.cn/problems/merge-two-sorted-lists/)21-251217 - -先找到小的开头,作为 p1 然后 比较插入即可 - -```python -class Solution: - def mergeTwoLists(self, list1: Optional[ListNode], list2: Optional[ListNode]) -> Optional[ListNode]: - if not list1 : - return list2 - if not list2 : - return list1 - if list1.val < list2.val : - p1, p2 = list1, list2 - else : - p1, p2 = list2, list1 - ans = p1 - while p2 : - # print(f'1 1: {p1}') - # print(f'1 2: {p2}') - while p1.next and p1.next.val <= p2.val : - # print(f'2 1: {p1}') - # print(f'2 2: {p2}') - p1 = p1.next - # print(f'2-1: {p1}') - # print(f'2-2: {p2}') - if p1.next : - # p2 插入到 p1 和 下一个之间 - # print(f'3 1: {p1}') - # print(f'3 2: {p2}') - p2n = p2.next - p1n = p1.next - p1.next = p2 - p2.next = p1n - p2 = p2n - else : - p1.next = p2 - break - return ans -``` - -学习一下别人写的,用了一个新的节点 作为开头,之后在这个开头后添加 : - -```python -class Solution: - def mergeTwoLists(self, list1: Optional[ListNode], list2: Optional[ListNode]) -> Optional[ListNode]: - dummy = cur = ListNode() - while list1 and list2: - if list1.val < list2.val: - cur.next = list1 - list1 = list1.next - else: - cur.next = list2 - list2 = list2.next - cur = cur.next - - cur.next = list1 or list2 - return dummy.next -``` - -# 28. [两数相加](https://leetcode.cn/problems/add-two-numbers/)2-251218 - -这个不难 重要的是细节处理 - -```python -class Solution: - def addTwoNumbers(self, l1: Optional[ListNode], l2: Optional[ListNode]) -> Optional[ListNode]: - Csignal = 0 - ans, pl1, pl2 = l1, None, None - while l1 and l2 : - val = (l1.val + l2.val + Csignal)%10 - Csignal = (l1.val + l2.val + Csignal)//10 - l1.val = val - pl1, pl2 = l1, l2 - l1, l2 = l1.next, l2.next - if not l1 and not l2 and Csignal: - pl1.next = ListNode(Csignal) - return ans - if not l1 : - pl1.next = l2 - l1 = pl1.next - while l1 : - val = (l1.val + Csignal)%10 - Csignal = (l1.val + Csignal)//10 - l1.val = val - if not l1.next and Csignal : - l1.next = ListNode(Csignal) - break - l1 = l1.next - return ans -``` - -# 29. [删除链表的倒数第 N 个结点](https://leetcode.cn/problems/remove-nth-node-from-end-of-list/)19-251218 - -#快慢指针 - -遍历一次的方法 是 快慢指针 这个一开始没想到 !!!!!!!!!!!! -相当于我手动留了两个距离为 n 的 ptr 后面的到了结尾 前面的到 倒数第n个 - -```python -class Solution: - def removeNthFromEnd(self, head: Optional[ListNode], n: int) -> Optional[ListNode]: - slow = fast = head - preSlow = None - while n : - fast = fast.next - n -= 1 - while fast : - fast = fast.next - preSlow = slow - slow = slow.next - if not preSlow : - return head.next - preSlow.next = slow.next - return head -``` - -# 30. [两两交换链表中的节点](https://leetcode.cn/problems/swap-nodes-in-pairs/)24-251218 - -虽然很简单,但是感觉写的不好,可以学一下别的人代码 - -```python -class Solution: - def swapPairs(self, head: Optional[ListNode]) -> Optional[ListNode]: - prev, ptr = None, head - ans = head.next if head and head.next else head - flag = 0 - while ptr : - if flag < 1 : - if prev : - prev.next = ptr.next if ptr.next else ptr - prev = ptr - ptr = ptr.next - flag += 1 - continue - flag = 0 - n = ptr.next - ptr.next = prev - prev.next = n - ptr = n - return ans -``` - -官方的迭代方法:我发现 官方在做list 相关的题目时候,喜欢用一个dummyhead来作为答案。应该参考一下这个想法 - -```python -class Solution: - def swapPairs(self, head: ListNode) -> ListNode: - dummyHead = ListNode(0) - dummyHead.next = head - temp = dummyHead - while temp.next and temp.next.next: - node1 = temp.next - node2 = temp.next.next - temp.next = node2 - node1.next = node2.next - node2.next = node1 - temp = node1 - return dummyHead.next -``` - -# 31. [K 个一组翻转链表](https://leetcode.cn/problems/reverse-nodes-in-k-group/)25-251218 - -上一题是这个题目 k=2 的特殊情况。用迭代的做法写,逐个找所有的k长子list 然后reverse - -```python -class Solution: - # 反转k个 - def reverseKOnce(self, head) : - tail = head - prev = None - while head : - n = head.next - head.next = prev - prev = head - head = n - return prev,tail - - def reverseKGroup(self, head: Optional[ListNode], k: int) -> Optional[ListNode]: - ans = prev_subtail = ListNode(0) - if k == 1: - return head - n = None - while True : - start = head - sublen = 1 - while head : - head = head.next - sublen += 1 - if sublen == k and head : - n = head.next - head.next = None - head = n - break - if head or not n: - subhead,subtail = self.reverseKOnce(start) - prev_subtail.next = subhead - prev_subtail = subtail - if not n : - break - else : - prev_subtail.next = start - break - return ans.next -``` - -# 32. [随机链表的复制](https://leetcode.cn/problems/copy-list-with-random-pointer/)138-251218-重新写 - -这个方法复杂度应该是 $O(n^2)$ 先按照next 的方向复制list 然后找random 方法是 对于一个node 从两个list的头开始找,知道找origin的random 然后就把 新的random也置为这个 - -```python -class Solution: - def copyRandomList(self, head: 'Optional[Node]') -> 'Optional[Node]': - p = dummyHead = Node(0) - phead = head - while phead : - p.next = Node(phead.val) - phead = phead.next - p = p.next - # 到这里先复制list 到dummy 之后处理random - p = dummyHead.next - p_origin = head - while p : - if p_origin.random is None : - p.random = None - p = p.next - p_origin = p_origin.next - if not p : - break - - q_origin = head - q = dummyHead.next - while q : - if q_origin == p_origin.random : - p.random = q - break - q = q.next - q_origin = q_origin.next - p = p.next - p_origin = p_origin.next - return dummyHead.next - - while dummyHead : - print(f"{dummyHead.val}-{dummyHead.random.val if dummyHead and dummyHead.random else 'null'}",end=' ') - dummyHead = dummyHead.next -``` - -别人的方法 还没看 - -```python -class Solution: - def copyRandomList(self, head: 'Optional[Node]') -> 'Optional[Node]': - if head is None: - return None - - - cur = head - while cur: - cur.next = Node(cur.val, cur.next) - cur = cur.next.next - - cur = head - while cur: - if cur.random: - cur.next.random = cur.random.next - cur = cur.next.next - - cur = head.next - while cur.next: - cur.next = cur.next.next - cur = cur.next - - return head.next -``` - -必须要看看题解 然后好好做一下 这会儿太累了,之后看吧留个坑 - -# 33. [排序链表](https://leetcode.cn/problems/sort-list/)148-251219*-重新写 - -#并归排序 - -这个可以分为 自底向上并归 和 自顶向下并归(需要递归)。 - -自顶向下,用了递归 空间复杂度是 $O(logn)$ -```python -class Solution: - def merge(self, head1: Optional[ListNode], head2: Optional[ListNode]) : - p = dummy = ListNode(-999) - while head1 and head2 : - if head1.val < head2.val : - p.next = head1 - head1 = head1.next - else : - p.next = head2 - head2 = head2.next - p = p.next - if head1 : - p.next = head1 - if head2 : - p.next = head2 - return dummy.next - - # start包括 end不包括 - def sort(self,start ,end) : - if not start : - return start - if start.next == end : - start.next = None - return start - fast = slow = start - while fast != end : - slow = slow.next - fast = fast.next - if fast != end : - fast = fast.next - mid = slow - return self.merge(self.sort(start,mid),self.sort(mid,end)) - - - def sortList(self, head: Optional[ListNode]) -> Optional[ListNode]: - return self.sort(head,None) -``` - -自底向上:这个我写不动了 之后回来再写一次吧,重要的是 获取长度 然后处理 - -```python -class Solution: - def merge(self, head1: Optional[ListNode], head2: Optional[ListNode]) : - p = dummy = ListNode(-999) - while head1 and head2 : - if head1.val < head2.val : - p.next = head1 - head1 = head1.next - else : - p.next = head2 - head2 = head2.next - p = p.next - if head1 : - p.next = head1 - if head2 : - p.next = head2 - return dummy.next - - def sortList(self, head: Optional[ListNode]) -> Optional[ListNode]: - length = 0 - node = head - while node: - length += 1 - node = node.next - - dummyHead = ListNode(0, head) - subLength = 1 - while subLength < length: - prev, curr = dummyHead, dummyHead.next - while curr: - head1 = curr - for i in range(1, subLength): - if curr.next: - curr = curr.next - else: - break - head2 = curr.next - curr.next = None - curr = head2 - for i in range(1, subLength): - if curr and curr.next: - curr = curr.next - else: - break - - succ = None - if curr: - succ = curr.next - curr.next = None - - merged = self.merge(head1, head2) - prev.next = merged - while prev.next: - prev = prev.next - curr = succ - subLength <<= 1 - - return dummyHead.next -``` - -# 34. [合并 K 个升序链表](https://leetcode.cn/problems/merge-k-sorted-lists/)23-251219 - -最开始想的用单调栈 好像不可行,现在考虑用最小堆 - -```python -class Solution: - def mergeKLists(self, lists: List[Optional[ListNode]]) -> Optional[ListNode]: - # val,node - minHeapNode = [] - for i, n in enumerate(lists) : - if n : - minHeapNode.append((n.val,i,n)) - heapq.heapify(minHeapNode) - p = dummy = ListNode(0) - i = len(lists) - while minHeapNode : - # print(minHeapNode) - top = heapq.heappop(minHeapNode)[2] - p.next = top - p = p.next - if top.next : - heapq.heappush(minHeapNode,(top.next.val,i,top.next)) - i += 1 - return dummy.next -``` - -# 35. [LRU 缓存](https://leetcode.cn/problems/lru-cache/)146-251219 - -## 题解思路:双链表+hash - -用collections . orderDict - -双链表用来维护出入队列,然后hash用来快速根据key找到value - -```python -class LRUCache: - def __init__(self, capacity: int): - self.used = 0 - self.capacity = capacity - self.dummyStart = Node(-2,-2,None,None) - self.dummyEnd = Node(-1,-1,None,None) - self.dummyStart.next = self.dummyEnd - self.dummyEnd.prev = self.dummyStart - self.key2addr = {} - - def get(self, key: int) -> int: - if not key in self.key2addr.keys() : - return -1 - ptr = self.key2addr[key] - ptr.prev.next = ptr.next - ptr.next.prev = ptr.prev - - ptr.next = self.dummyEnd - ptr.prev = self.dummyEnd.prev - self.dummyEnd.prev.next = ptr - self.dummyEnd.prev = ptr - # self.print('get') - return ptr.value - - - def put(self, key: int, value: int) -> None: - if key in self.key2addr.keys() : - self.removeNode(self.key2addr[key]) - self.used -= 1 - if self.used == self.capacity : - self.key2addr.pop(self.dummyStart.next.key) - self.removeNode(self.dummyStart.next) - else : self.used += 1 - new = Node(key=key,value=value,prev=self.dummyEnd.prev,next=self.dummyEnd) - self.dummyEnd.prev.next = new - self.dummyEnd.prev = new - self.key2addr[key] = new - # self.print('put') - - def removeNode(self,ptr) : - ptr.prev.next = ptr.next - ptr.next.prev = ptr.prev - - def print(self,str) : - p = self.dummyStart - print(f'{str}',end=' ') - while p : - print(f'({p.key}-{p.value})',end=' ') - p = p.next - print(f'used:{self.used}/{self.capacity}') - -class Node : - def __init__(self, key=0, value=0, next=None, prev=None) : - self.value = value - self.key = key - self.next = next - self.prev = prev -``` - -## Trash - -用队列,但是 get put 的复杂度是 O(capacity) remove -为什么要用双链?因为remove操作 需要 o(c) 去找到 value ,但是如果用Node,可以在字典中存入其地址。O(1) 就可以找到。 -```python -class LRUCache: - def __init__(self, capacity: int): - self.capacity = capacity - self.dic = {} - self.used = 0 # que 的 size - self.que = [] - def get(self, key: int) -> int: - if key not in self.dic.keys() : - return -1 - value = self.dic[key] - self.que.remove(key) - self.que.append(key) - # print(f'get {self.dic},{self.que}') - return value - def put(self, key: int, value: int) -> None: - if key in self.dic.keys() : - self.dic[key] = value - self.que.remove(key) - self.que.append(key) - # print(f'put {self.dic},{self.que}') - return - if self.used < self.capacity : - self.que.append(key) - self.dic[key] = value - self.used += 1 - # print(f'put {self.dic},{self.que}') - return - delkey = self.que[0] - self.que = self.que[1:] - self.dic.pop(delkey) - self.que.append(key) - self.dic[key] = value - # print(f'put {self.dic},{self.que}') -``` - - -# 36. [二叉树的中序遍历](https://leetcode.cn/problems/binary-tree-inorder-traversal/)94-251222 - -要知道中序遍历和先序遍历的while写法是不一样的,区别在于先append还是先 访问(cur=) -## 思路一:递归 - -这个写法比较简单 - -```python -class Solution: - def mid(self,root,ans): - if root : - self.mid(root.left,ans) - ans.append(root.val) - self.mid(root.right,ans) - - def inorderTraversal(self, root: Optional[TreeNode]) -> List[int]: - ans = [] - self.mid(root,ans) - return ans -``` - -## 思路二:迭代\*\* - -我觉得这个迭代写的很好,下来再看看 - -```python -class Solution: - def inorderTraversal(self, root: Optional[TreeNode]) -> List[int]: - stack = [] - ans = [] - while stack or root : - while root : - stack.append(root) - root = root.left - root = stack.pop() - ans.append(root.val) - root = root.right - return ans -``` - -自己写的 -```python - stk = [] - while stk or root : - while root : - stk.append(root) - root = root.left - root = stk.pop() - print(root.val, end=' ') - print(stk) - root = root.right -``` - -# 37. [二叉树的最大深度](https://leetcode.cn/problems/maximum-depth-of-binary-tree/)104-251222 - -## 思路一:DFS - -```python -class Solution: - def maxDepth(self, root: Optional[TreeNode]) -> int: - if not root : return 0 - return max(self.maxDepth(root.left)+1,self.maxDepth(root.right)+1) -``` -## 思路二:BFS - -```python -class Solution: - def maxDepth(self, root: Optional[TreeNode]) -> int: - ans = 0 - que = [root] - next_que = [] - if not root : - return 0 - while que : - node = que[0] - que.remove(node) - if node.left : - next_que.append(node.left) - if node.right : - next_que.append(node.right) - if not que : - ans += 1 - que = next_que - next_que = [] - return ans -``` - -# 38. [翻转二叉树](https://leetcode.cn/problems/invert-binary-tree/)226-251222 - -## 思路一:BFS - -对每一个节点 进行左右节点的互换,因为所有节点只访问一次。所以可行 - -```python -class Solution: - def invertTree(self, root: Optional[TreeNode]) -> Optional[TreeNode]: - que = [root] - next_que = [] - if not root : - return root - while que : - node = que[0] - que.remove(node) - t = node.left - node.left = node.right - node.right = t - if node.left : - next_que.append(node.left) - if node.right : - next_que.append(node.right) - if not que : - que = next_que - next_que = [] - return root -``` - -## 思路二:递归 - -递归的方法都比较简单 - -```python -class Solution: - def invertTree(self, root: Optional[TreeNode]) -> Optional[TreeNode]: - if not root : - return - t = root.left - root.left = root.right - root.right = t - self.invertTree(root.left) - self.invertTree(root.right) - return root -``` - -# 39. [对称二叉树](https://leetcode.cn/problems/symmetric-tree/)101-251222 - -## 思路一:递归 - -递归的思路比较简单 - -```python -class Solution: - def digui(self,left,right): - if not left and not right : - return True - if left and right and left.val == right.val : - return True & self.digui(left.left,right.right) & self.digui(left.right,right.left) - else : return False - - def isSymmetric(self, root: Optional[TreeNode]) -> bool: - return self.digui(root,root) -``` - -## 思路二:迭代 - -```python -class Solution: - def isSymmetric(self, root) -> bool : - left_ptr = right_ptr = root - left_que = [root.left] - right_que = [root.right] - while left_que and right_que : - left_ptr, right_ptr = left_que[0], right_que[0] - left_que.remove(left_que[0]) - right_que.remove(right_que[0]) - if ( left_ptr and not right_ptr ) or ( not left_ptr and right_ptr ) : - return False - if left_ptr and right_ptr and left_ptr.val != right_ptr.val : - return False - if not left_ptr and not right_ptr : - continue - # append 的时候也要 对称 - left_que.append(left_ptr.left) - left_que.append(left_ptr.right) - right_que.append(right_ptr.right) - right_que.append(right_ptr.left) - return True - -``` - -# 40. [二叉树的直径](https://leetcode.cn/problems/diameter-of-binary-tree/)543-251222 - -## 思路一:递归 - -每一个节点的 计算出来的直径都是 左边的深度加右边的深度,只需要维护一个最大的就可以,但是这里把工具函数 以及self.ans写在solution函数内,这么做方便维护max - -```python -class Solution : - def diameterOfBinaryTree(self, root: Optional[TreeNode]) -> int: - self.ans = -1 - def DepthOfNode(n): - if not n : - return 0 - l = DepthOfNode(n.left) - r = DepthOfNode(n.right) - self.ans = max(self.ans, l+r) - return max(l,r)+1 - DepthOfNode(root) - return self.ans -``` - -## 思路二:迭代 - -找深度还是递归,但是只是用BFS来更新ans - -## Trash Ologn\^2 - -超时了,记录每个叶子节点的path 然后计算路径 - -```python -class Solution: - def distance(self,path1,path2) -> int : - # 非公共长度的和 - if path1 == '' or path2 == '' : - return max(len(path1),len(path2)) - i=0 - while path1[i] == path2[i] : - i += 1 - return len(path1) + len(path2) - i*2 - - - def diameterOfBinaryTree(self, root: Optional[TreeNode]) -> int: - que = deque([root]) - root.path = '' - mp = {} - paths = [] - while que : - ptr = que.popleft() - if not ptr : - continue - if not ptr.left and not ptr.right : - paths.append(ptr.path) - que.append(ptr.left) - que.append(ptr.right) - if ptr.left : ptr.left.path = ptr.path + 'l' - if ptr.right : ptr.right.path = ptr.path + 'r' - ans = len(paths[-1]) - for i in range(len(paths)) : - for j in range(i+1,len(paths)) : - ans = max(ans,self.distance(paths[i],paths[j])) - return ans -``` - -# 41. [二叉树的层序遍历](https://leetcode.cn/problems/binary-tree-level-order-traversal/)102-251222 - -BFS即可 - -```python -class Solution: - def levelOrder(self, root: Optional[TreeNode]) -> List[List[int]]: - if not root : return [] - que = deque([root]) - next_que = [] - ans = [] - while que : - ans.append([]) - while que : - cur = que.popleft() - if not cur : - continue - ans[-1].append(cur.val) - if cur.left : next_que.append(cur.left) - if cur.right : next_que.append(cur.right) - que = deque(next_que) - next_que = [] - return ans -``` - -# 42. [将有序数组转换为二叉搜索树](https://leetcode.cn/problems/convert-sorted-array-to-binary-search-tree/)108-251223 - -## 思路一:递归 - -一个sorted 数组转换成 树,root节点一定是mid,那么只需要向归并排序一样 依次将左右的的根节点找到即可 - -```python -class Solution: - def nums2tree(self, nums) -> TreeNode : - if nums == [] : return None - mid = len(nums)//2 - left_nums = nums[0:mid] if mid != 0 else [] - right_nums = nums[mid+1:] if mid Optional[TreeNode]: - return self.nums2tree(nums) -``` - -## 题解中有别的思路基于中序遍历 - -# 43. [验证二叉搜索树](https://leetcode.cn/problems/validate-binary-search-tree/)98-251223\*\*\* - -## 思路一:中序遍历判断数组是否有序 - -```python -class Solution: - - def isValidBST(self, root: Optional[TreeNode]) -> bool: - self.nums = [] - def front(root): - if not root : return - if root.left : - front(root.left) - self.nums.append(root.val) - if root.right : - front(root.right) - front(root) - p = -999999999999 - for n in self.nums : - if p >= n : return False - p = n - return True -``` - -## 思路二:递归\*\*\* - -我觉得这个递归有点难,需要处理 右子树的最小值不能超过root 左子树的最大值不能超过 root,学一下官方的思路。helper用来判断 一个树 是不是 BST。 - -```python -class Solution: - def isValidBST(self, root: TreeNode) -> bool: - def helper(node, lower = float('-inf'), upper = float('inf')) -> bool: - if not node: - return True - val = node.val - if val <= lower or val >= upper: - return False - if not helper(node.right, val, upper): - return False - if not helper(node.left, lower, val): - return False - return True - return helper(root) -``` - -下面是自己写的 上面是题解写的 题解写的递归思路 **更更更更** 好 - -```python -class Solution: - def isValidBST(self, root: Optional[TreeNode]) -> bool: - def isBST(node,lowwer=float('-inf'),upper=float('+inf')): - if not node : - return True - # print(node.left.val if node.left else None,node.val,node.right.val if node.right else None,lowwer,upper) - if ( node.left and node.left.val >= node.val ) or ( node.right and node.right.val <= node.val ): - return False - if (node.left and node.left.val <= lowwer) or (node.right and node.right.val >= upper) : - return False - return isBST(node.left,lowwer=lowwer,upper=node.val) and isBST(node.right,lowwer=node.val,upper=upper) - return isBST(root) -``` - -# 44. [二叉搜索树中第 K 小的元素](https://leetcode.cn/problems/kth-smallest-element-in-a-bst/)230-251223 - -## 思路一:中序遍历 - -将二叉查找树转换成数组 $O(n)$ 然后 查找一次即可 下面是递归写法 - -```python -class Solution: - def kthSmallest(self, root: Optional[TreeNode], k: int) -> int: - self.ans = [] - def mid_iter(root): - if not root : return - if root.left : mid_iter(root.left) - self.ans.append(root.val) - if root.right: mid_iter(root.right) - mid_iter(root) - return self.ans[k-1] -``` - - 下面是迭代的写法 注意每次while中的意思:遇到左叶子存在就优先处理左叶子,然后先把现在存起到栈中。知道遇到没有左叶子的,遍历这个节点,然后处理右叶子。这个流程构成了一个循环。 - - 换一种理解,先往左走找到最小的 然后 找到大的进入 stk的pop 和 进入右叶子 我觉得第一个更好理解一点。 - -```python -class Solution: - def kthSmallest(self, root: Optional[TreeNode], k: int) -> int: - stk = [] - nums = [] - while stk or root : - while root : - stk.append(root) - root = root.left - root = stk.pop() - nums.append(root.val) - k -= 1 - if k == 0 : - return root.val - root = root.right -``` - -## 思路二:记录node 的节点数(看题解)下面这两个还是学一下吧 - -## 思路三:转成AVL平衡二叉树(看题解) - -# 45. [二叉树的右视图](https://leetcode.cn/problems/binary-tree-right-side-view/)199-251223 - -## 思路一:BFS - -通过BFS把每一行加入队列的最后一个记录下来。 -```python -# BFS 找到最后一个 -class Solution: - def rightSideView(self, root: Optional[TreeNode]) -> List[int]: - que = deque([root]) - next_que = [] - ans = [] - if not root : return [] - while que : - ans.append(que[-1].val) - while que : - cur = que.popleft() - if not cur : continue - if cur.left : next_que.append(cur.left) - if cur.right: next_que.append(cur.right) - que = deque(next_que) - next_que = [] - return ans -``` - -## 思路二:DFS\*\*\*\* 之前都没写过DFS,DFS使用stk 把左右加进来 - -在深度优先搜索时 如果优先搜索 右侧节点,那么每层访问的第一个 节点 一定是最右侧的节点,那么我们可以记录一个深度。遇到第一个这个深度的节点,就记录下来。下面是题解的做法 : -```python -class Solution: - def rightSideView(self, root: TreeNode) -> List[int]: - rightmost_value_at_depth = dict() # 深度为索引,存放节点的值 - max_depth = -1 - - stack = [(root, 0)] - while stack: - node, depth = stack.pop() - - if node is not None: - # 维护二叉树的最大深度 - max_depth = max(max_depth, depth) - - # 如果不存在对应深度的节点我们才插入 - rightmost_value_at_depth.setdefault(depth, node.val) - - stack.append((node.left, depth + 1)) - stack.append((node.right, depth + 1)) - - return [rightmost_value_at_depth[depth] for depth in range(max_depth + 1)] -``` -要知道中序遍历和先序遍历的while写法是不一样的,区别在于先append还是先 访问(cur=) - -# 46. [二叉树展开为链表](https://leetcode.cn/problems/flatten-binary-tree-to-linked-list/)114-251223 - -## Trash 用了新的空间 - -```python -class Solution: - def flatten(self, root: Optional[TreeNode]) -> None: - """ - Do not return anything, modify root in-place instead. - """ - if not root : return - dummy = TreeNode(0,None,None) - ptr = dummy - stk = [root] - while stk : - cur = stk.pop() - if not cur : continue - ptr.right = TreeNode(cur.val,None,None) - ptr = ptr.right - # print(cur.val,end='') - stk.append(cur.right) - stk.append(cur.left) - - root.right = dummy.right.right - root.left = None -``` - -## 思路一:存下来prev - -因为cur已经被访问过了所以可以修改了,prev执指向现在cur 就可以修改其左右叶子值了 -```python -class Solution: - def flatten(self, root: Optional[TreeNode]) -> None: - """ - Do not return anything, modify root in-place instead. - """ - stack = [root] if root else [] - prev = None - while stack: - cur = stack.pop() - if prev: - prev.left, prev.right = None, cur - if cur.right: stack.append(cur.right) - if cur.left: stack.append(cur.left) - prev = cur -``` - - -## 思路二:找到前驱节点\*\*\* - - 这个题题解的方法也很多,之后仔细看一下。下面这个方法是常数空间复杂度的方法 - - ```python -class Solution: - def flatten(self, root: TreeNode) -> None: - curr = root - while curr: - if curr.left: - predecessor = nxt = curr.left - while predecessor.right: - predecessor = predecessor.right - predecessor.right = curr.right - curr.left = None - curr.right = nxt - curr = curr.right -``` - -# 47. [从前序与中序遍历序列构造二叉树](https://leetcode.cn/problems/construct-binary-tree-from-preorder-and-inorder-traversal/)105-251223 - -## 思路一:递归(没看 - -终点在于根据 index i 找到 preorder的左右 但是这个递归还是很慢 也是 Trash - -```python -class Solution: - def buildTree(self, preorder: List[int], inorder: List[int]) -> Optional[TreeNode]: - if preorder == [] : return None - # for i,n in enumerate(inorder) : - # if n == preorder[0] : - # break - i = inorder.index(preorder[0]) - left_inorder = inorder[0:i] - right_inorder = inorder[i+1:] - # 可以知道 i 左右的数量 - left_preorder = preorder[1:i+1] - # for x in preorder : - # if x in left_inorder : - # left_preorder.append(x) - right_preorder = preorder[i+1:] - # for x in preorder : - # if x in right_inorder : - # right_preorder.append(x) - # print(left_preorder,right_preorder) - return TreeNode(preorder[0],self.buildTree(left_preorder,left_inorder),self.buildTree(right_preorder,right_inorder)) -``` - -下面是我的改进 还不是最优的:重点在于通过v找i时候用hash 把$O(n)$复杂度降低为 $O(1)$ -```python -# # 递归 -class Solution: - def buildTree_helper(self, preorder: List[int], inorder: List[int], abs_start=0) -> Optional[TreeNode]: - if len(preorder) == 0 : return None - i = self.mp[preorder[0]] - abs_start - return TreeNode(preorder[0],self.buildTree_helper(preorder[1:i+1],inorder[0:i],abs_start=abs_start),self.buildTree_helper(preorder[i+1:],inorder[i+1:],abs_start=abs_start+i+1)) - def buildTree(self, preorder: List[int], inorder: List[int]) -> Optional[TreeNode]: - self.mp = { v:i for i,v in enumerate(inorder) } - return self.buildTree_helper(preorder, inorder) -``` - -\*\*\*\*\*\*下面是官方的递归方法,速度快了很多 - -```python -class Solution: - def buildTree(self, preorder: List[int], inorder: List[int]) -> TreeNode: - def myBuildTree(preorder_left: int, preorder_right: int, inorder_left: int, inorder_right: int): - if preorder_left > preorder_right: - return None - - # 前序遍历中的第一个节点就是根节点 - preorder_root = preorder_left - # 在中序遍历中定位根节点 - inorder_root = index[preorder[preorder_root]] - - # 先把根节点建立出来 - root = TreeNode(preorder[preorder_root]) - # 得到左子树中的节点数目 - size_left_subtree = inorder_root - inorder_left - # 递归地构造左子树,并连接到根节点 - # 先序遍历中「从 左边界+1 开始的 size_left_subtree」个元素就对应了中序遍历中「从 左边界 开始到 根节点定位-1」的元素 - root.left = myBuildTree(preorder_left + 1, preorder_left + size_left_subtree, inorder_left, inorder_root - 1) - # 递归地构造右子树,并连接到根节点 - # 先序遍历中「从 左边界+1+左子树节点数目 开始到 右边界」的元素就对应了中序遍历中「从 根节点定位+1 到 右边界」的元素 - root.right = myBuildTree(preorder_left + size_left_subtree + 1, preorder_right, inorder_root + 1, inorder_right) - return root - - n = len(preorder) - # 构造哈希映射,帮助我们快速定位根节点 - index = {element: i for i, element in enumerate(inorder)} - return myBuildTree(0, n - 1, 0, n - 1) - -``` - -## 思路二:迭代(没看 - -```python -class Solution: - def buildTree(self, preorder: List[int], inorder: List[int]) -> TreeNode: - if not preorder: - return None - - root = TreeNode(preorder[0]) - stack = [root] - inorderIndex = 0 - for i in range(1, len(preorder)): - preorderVal = preorder[i] - node = stack[-1] - if node.val != inorder[inorderIndex]: - node.left = TreeNode(preorderVal) - stack.append(node.left) - else: - while stack and stack[-1].val == inorder[inorderIndex]: - node = stack.pop() - inorderIndex += 1 - node.right = TreeNode(preorderVal) - stack.append(node.right) - - return root -``` - -## Trash:递归拆分先序nums时候超时 - -答案对但是超时了 - -```python -class Solution: - def buildTree(self, preorder: List[int], inorder: List[int]) -> Optional[TreeNode]: - if preorder == [] : return None - for i,n in enumerate(inorder) : - if n == preorder[0] : - break - left_inorder = inorder[0:i] - right_inorder = inorder[i+1:] - left_preorder = [] - for x in preorder : - if x in left_inorder : - left_preorder.append(x) - right_preorder = [] - for x in preorder : - if x in right_inorder : - right_preorder.append(x) - # print(left_preorder,right_preorder) - return TreeNode(preorder[0],self.buildTree(left_preorder,left_inorder),self.buildTree(right_preorder,right_inorder)) -``` - -# 48. [路径总和 III](https://leetcode.cn/problems/path-sum-iii/)437-251224 - -## 思路一:前缀和\* - -$O(n)$ 用递归深度搜索 去遍历所有节点,记录前缀和,然后用hash来找到对应的,如果找到就把他拿出。那么最大的问题是:例如根节点,hash中记录了左子树的所有需要的哈希值,然后对于右子树 如果查到,就会出现一条通过root的路径,解决办法就是:记录的 (需要找到的)prefix+sum 只在子树范围内生效。(我觉得也是这个题的难点) -hash中记录的是,prefix+sum(像两数之和一样,遇到val 就查 target-val在不在。但是这个是两数之差)。 - -```python -class Solution: - def pathSum(self, root: Optional[TreeNode], targetSum: int) -> int: - if not root : return 0 - mp = defaultdict(int) - mp[targetSum] = 1 - self.ans = 0 - - def DFS(root,fatherPrefix): - if not root : return - root.prefixSum = fatherPrefix + root.val - self.ans += mp[root.prefixSum] - mp[root.prefixSum+targetSum]+=1 - DFS(root.left,root.prefixSum) - DFS(root.right,root.prefixSum) - ########################################## - #这一句很重要 保证了 这个节点只在子树中记录在mp中 - mp[root.prefixSum+targetSum]-=1 ########## - - DFS(root,0) - - return self.ans -``` - -## 思路二:深度优先搜索-非最优 - -这个思路没啥用我觉得 时间复杂度是 $O(n^2)$ 上一个复杂度是 $O(n)$ 这个思路就是写一个helper 用于找到起点为root的所有和为sum 的path数量,然后(DFS)遍历所有的node 得到所有path - -```python -class Solution: - def pathSum(self, root: Optional[TreeNode], targetSum: int) -> int: - # 定义一个helper函数求出 以 root 为起点 并且和为 Sum 的数量 - def startRootSum(root,Sum) : - if not root : return 0 - return (1 if root.val == Sum else 0) + startRootSum(root.left,Sum-root.val) + startRootSum(root.right,Sum-root.val) - # 遍历所有节点,找到以所有节点开头 path 和为 targetSum 的数量 这个递归就是一个 DFS - if not root : return 0 - ans = 0 - ans += startRootSum(root,targetSum) - ans += self.pathSum(root.left,targetSum) - ans += self.pathSum(root.right,targetSum) - return ans -``` -## Trash 迭代前缀和 - -先尝试了用迭代方法去写前缀和:跑不通。接下来尝试用递归去求 - -```python -class Solution: - def pathSum(self, root: Optional[TreeNode], targetSum: int) -> int: - if not root : return 0 - self.mp = {targetSum:1} - self.ans = 0 - def DFS(root): - stk = [root] - root.prefixSum = root.val - while stk : - cur = stk.pop() - if cur == root.right or cur == root.left : - if root.val != 0: - self.mp = { targetSum:1 ,root.val+targetSum:1} - else : - self.mp = {targetSum:2} - print(cur.val,cur.prefixSum) - if cur.prefixSum in self.mp.keys(): - self.ans += self.mp[cur.prefixSum] - print(cur.val) - if targetSum+cur.prefixSum in self.mp.keys() : - self.mp[targetSum+cur.prefixSum] += 1 - else : - self.mp[targetSum+cur.prefixSum] = 1 - - if cur.right: - stk.append(cur.right) - cur.right.prefixSum = cur.prefixSum + cur.right.val - if cur.left : - stk.append(cur.left) - cur.left.prefixSum = cur.prefixSum + cur.left.val - print(self.mp,self.ans) - DFS(root) - print(self.ans) - return self.ans -``` - -print位置换一下就是前中后序遍历,但是迭代的写法很不一样,谦虚需要while + stk 中序需要 while+que -```python -def DFS(root): - if not root : return - DFS(root.left) - print(root.val) - DFS(root.right) -``` -# 49. [二叉树的最近公共祖先](https://leetcode.cn/problems/lowest-common-ancestor-of-a-binary-tree/)236-251224 - -## 思路一:记录所有节点的father,然后用set查 - -1.先用DFS遍历树,然后记录每个节点的father 2.把p的所有father存在在个set 3.遍历q的father(从q开始)遇到 在 set:p.fathers 中的节点就返回 - -```python -class Solution: - def lowestCommonAncestor(self, root: 'TreeNode', p: 'TreeNode', q: 'TreeNode') -> 'TreeNode': - def DFS(root): - if not root : return - if root.left : root.left.father = root - if root.right: root.right.father = root - DFS(root.left) - DFS(root.right) - DFS(root) - root.father = None - pFathers = set() - while p : - pFathers.add(p) - p=p.father - while q : - if q in pFathers : - return q - q = q.father -``` - -## 思路二:递归 - -写一个工具函数判断 这个root节点为根的树中包不包含 p,q ,那么最后结果一定是 ans 包含,ans的左右孩子都不包含,下面是自己的写法,但是感觉把问题写复杂了,之后可以学习一下题解的写法。 - -```python -class Solution: - def lowestCommonAncestor(self, root: 'TreeNode', p: 'TreeNode', q: 'TreeNode') -> 'TreeNode': - def DFS(root,p,q) : - if not root : return - root.p_in = False - root.q_in = False - if not root.left and not root.right : - root.q_in = False - root.p_in = False - if root.val == p.val : - root.p_in = True - root.q_in = False - # print(f'p-{root}') - if root.val == q.val : - root.p_in = False - root.q_in = True - # print(f'q-{root}') - DFS(root.left,p,q) - DFS(root.right,p,q) - lpi = root.left.p_in if root.left else False - rpi = root.right.p_in if root.right else False - lqi = root.left.q_in if root.left else False - rqi = root.right.q_in if root.right else False - root.p_in = lpi or rpi or root.p_in - root.q_in = lqi or rqi or root.q_in - # print(f'val:{root.val},p_in:{root.p_in},q_in:{root.q_in}') - DFS(root,p,q) - while root.p_in and root.q_in : - # print(root.val) - if root.left and root.left.p_in and root.left.q_in : - root = root.left - elif root.right and root.right.p_in and root.right.q_in : - root = root.right - else : return root -``` - -题解写法没有py我没粘 这个是前面的写法 (\* **这个想法好好啊** \*) - -```python -class Solution: - def lowestCommonAncestor(self, root: 'TreeNode', p: 'TreeNode', q: 'TreeNode') -> 'TreeNode': - # 如果当前节点为p或者q 公共父节点是p或者q本身 - # 如果左右都找到了可能的父节点,返回公共节点 - # 左右都没找到,返回空节点 - if root in (None,p,q): - # 3元组 - return root - left = self.lowestCommonAncestor(root.left,p,q) - right = self.lowestCommonAncestor(root.right,p,q) - - if left and right: - return root - - # 左和右 谁找到了就返回谁 - return left or right -``` - -# 50. [二叉树中的最大路径和](https://leetcode.cn/problems/binary-tree-maximum-path-sum/)124-251224 - -## 思路一:递归-从trash优化过来的 - -在trash中 每次求解 root 树的最大路径 都要再递归找各个节点的 路径,对这个做法优化:在一个递归中得到 从一个节点开始的最大值 ,这样不用每次找一个节点都要找。而是存在一个属性内 - -```python -class Solution: - # root 树的最大path - def maxPathSum(self, root: Optional[TreeNode]) -> int: - if not root : - return 0 - left_child_max = self.maxPathSum(root.left) if root.left else -1001 - right_child_max = self.maxPathSum(root.right) if root.right else -1001 - start_l_max = root.left.startRootMax if root.left else 0 - start_r_max = root.right.startRootMax if root.right else 0 - child_max = max(left_child_max,right_child_max) - root.startRootMax = root.val + max(0, start_l_max, start_r_max) - # print(f'val-{root.val},sMax-{root.startRootMax},{left_child_max}-{right_child_max}-{root.val + start_l_max + start_r_max}') - include_root = max(root.val, root.startRootMax, root.val + start_l_max + start_r_max) - return max(include_root,child_max) -``` - -这是一个题解的方法,可以参考一下:很简洁 - -```python -class Solution: - def maxPathSum(self, root: Optional[TreeNode]) -> int: - res = -inf - def findmax(root): - if not root: - return 0 - nonlocal res - left = findmax(root.left) - if left < 0: - left = 0 - right = findmax(root.right) - if right < 0: - right = 0 - if left + right + root.val > res: - res = left + right + root.val - return max(left,right) + root.val - findmax(root) - return res -``` - -参考后来写的方法!!!!!!!这个写的可以 -```python -class Solution: - def maxPathSum(self, root: Optional[TreeNode]) -> int: - self.ans = -inf - # 从root开始的最大path,但是在每个节点判断 root 连接两边的是不是更大 - def dfs(root): - if not root : return 0 - left = max(dfs(root.left),0) - right = max(dfs(root.right),0) - # print(root.val,left,right,left+right+root.val) - self.ans = max(self.ans,left+right+root.val) - return root.val + max(left,right) - dfs(root) - return self.ans -``` - -## Trash: 超时了 - -第一想法是这样的 但是有两层递归 $O(n^2)$ 太慢了 - -```python -class Solution: - # root 树的最大path - def maxPathSum(self, root: Optional[TreeNode]) -> int: - # 以root为起点的 path 最大值 - def startRootMax(root) : - if not root : - return 0 - return root.val + max(0, startRootMax(root.left), startRootMax(root.right)) - if not root : - return 0 - start_root = max(root.val, startRootMax(root), root.val + startRootMax(root.left)+startRootMax(root.right)) - return max(start_root,self.maxPathSum(root.left) if root.left else -1001,self.maxPathSum(root.right) if root.right else -1001) -``` - -# 51. [岛屿数量](https://leetcode.cn/problems/number-of-islands/)200-251224 - -## 思路一:DFS - -深度搜索,处理越界。如果还是1则置为已经遍历过 - -```python -class Solution: - def numIslands(self, grid: List[List[str]]) -> int: - def dfs(i,j): - if i<0 or i>=len(grid): return 0 - if j<0 or j>=len(grid[0]): return 0 - if grid[i][j] != '1' : return 0 - grid[i][j] = '2' - dfs(i-1,j) - dfs(i+1,j) - dfs(i,j-1) - dfs(i,j+1) - return 1 - ans = 0 - for i in range(len(grid)) : - for j in range(len(grid[0])) : - ans += dfs(i,j) - return ans -``` - -## 思路二:BFS 队列和栈 在入的时候就标记 - -发现一个问题:如果这么写的话会超时 因为同一个点多次入队。 - -```python -class Solution: - def numIslands(self, grid: List[List[str]]) -> int: - r = len(grid) - c = len(grid[0]) - ans = 0 - for i in range(r) : - for j in range(c) : - if grid[i][j] == '1' : - ans += 1 - que = deque([(i,j)]) - while que : - row,col = que.popleft() - grid[row][col] = '2' - for x,y in [ (row-1,col), (row+1,col), (row,col-1), (row,col+1) ] : - if 0<=x int: - r = len(grid) - c = len(grid[0]) - ans = 0 - for i in range(r) : - for j in range(c) : - if grid[i][j] == '1' : - ans += 1 - grid[i][j] = '2' - que = deque([(i,j)]) - while que : - row,col = que.popleft() - # grid[row][col] = '2' - for x,y in [ (row-1,col), (row+1,col), (row,col-1), (row,col+1) ] : - if 0<=x int: - r = len(grid) - c = len(grid[0]) - self.ans = 0 - def dfs(i,j) : - self.ans += 4 - grid[i][j] = 2 - for x,y in [ (i-1,j), (i+1,j), (i,j-1), (i,j+1) ]: - if 0<=x= 1 : - self.ans -= 1 - if 0<=x int: - r = len(grid) - c = len(grid[0]) - def BFS(que: List): - time = 0 - if not que : return 0 - que = deque(que) - next_que = [] - while que : - row,col = que.popleft() - for x,y in [ (row+1,col), (row-1,col), (row,col+1), (row,col-1) ] : - if 0<=x int: - r = len(grid) - c = len(grid[0]) - mp = defaultdict(int) - g = copy.deepcopy(grid) - def BFS(i,j,grid) : - time = 1 - que = deque([(i,j)]) - mp[(i,j)] = 0 - next_que = [] - while que : - row,col = que.popleft() - for x,y in [ (row-1,col), (row+1,col), (row,col-1), (row,col+1) ] : - if 0<=x bool: - mp = {} - for l in prerequisites : - if l[0] in mp.keys() : - mp[l[0]].append(l[1]) - else : - mp[l[0]] = [l[1]] - # 判断哈希表中有没有环 - -``` - -## 思路一:DFS - -拓扑排序 省去了排序 只判断环 - -```python -class Solution: - def canFinish(self, numCourses: int, prerequisites: List[List[int]]) -> bool: - status = [0] * numCourses - mp = defaultdict(list) - for i in prerequisites : - mp[i[0]].append(i[1]) - def dfs(u: int): - status[u] = 1 - for v in mp[u]: - if status[v] == 0 : - if dfs(v) : return True - elif status[v] == 1 : - # 有环 - return True - status[u] = 2 - for i in range(numCourses) : - if dfs(i) : return False - return True -``` - -## 思路二:BFS - -```python -class Solution: - def canFinish(self, numCourses: int, prerequisites: List[List[int]]) -> bool: - in_edges = [0] * numCourses - # 维护哈希表 - mp = defaultdict(list) - for i in prerequisites : - mp[i[0]].append(i[1]) - in_edges[i[1]] += 1 - # 找到最先学的 - que = deque() - for i in range(numCourses): - if in_edges[i] == 0 : - que.append(i) - visited = 0 - while que : - visited += 1 - cur = que.popleft() - print(cur,end=' ') - for v in mp[cur] : - in_edges[v] -= 1 - if in_edges[v] == 0 : - que.append(v) - return visited == numCourses -``` - -## 课程表II-DFS - -```python -class Solution: - def findOrder(self, numCourses: int, prerequisites: List[List[int]]) -> List[int]: - # 记录每个节点,维护每个节点的状态,0是还没搜索,1是正在搜索,2是搜索完了 - status = [0] * numCourses - # 先获取依赖关系 - mp = defaultdict(list) - for i in prerequisites : - mp[i[0]].append(i[1]) - existCycle = False - ans = [] - def dfs(u: int) : - status[u] = 1 - for v in mp[u]: - if status[v] == 0 : - if dfs(v) : return True - elif status[v] == 1 : - # 说明遇到了环 - return True - status[u] = 2 - ans.append(u) - return False - - for i in range(numCourses): - if not existCycle and status[i] == 0 : - if dfs(i) : - return [] - - return ans if not existCycle else [] - -``` - -# 54. [实现 Trie (前缀树)](https://leetcode.cn/problems/implement-trie-prefix-tree/)208-251225 - -插入和查找 用 dict 就可以简单实现,所以关键点在于 startWith -如果用暴力的方法那么如下,这个方法的 startwith是不可接受的(1750ms),能不能用一个新的结构来做呢? -```python -class Trie: - - def __init__(self): - self.list = [] - - def insert(self, word: str) -> None: - self.list.append(word) - - def search(self, word: str) -> bool: - return word in self.list - - def startsWith(self, prefix: str) -> bool: - lp = len(prefix) - for v in self.list : - p = v[0:min(lp,len(v))] - if p == prefix : - return True - return False -``` - -稍微为字典优化一下就变成:75ms 还是挺慢的 正态mu是56 -```python -class Trie: - - def __init__(self): - self.list = {} - - def insert(self, word: str) -> None: - self.list[word] = True - sub = '' - for c in word : - sub+=c - if sub not in self.list.keys() : - self.list[sub] = False - - def search(self, word: str) -> bool: - if word not in self.list.keys() : - return False - return self.list[word] - - def startsWith(self, prefix: str) -> bool: - return prefix in self.list.keys() -``` - -## 思路:字典树 - -字典套字典感觉太难理解了 - -### 不好的写法 -递归insert字典树 感觉有点慢 5.24% -```python -def c2i(char) -> int : - return 0 if char=='#' else ord(char) -ord('a') + 1 - -class Trie: - def __init__(self): - self.next = [ None ] * 27 - def insert(self, word: str) -> None: - if not word : return - if word[-1] != '#' : word += '#' - node = Trie() if not self.next[c2i(word[0])] else self.next[c2i(word[0])] - node.insert(word[1:]) - self.next[c2i(word[0])] = node - - def search(self, word: str) -> bool: - return self.startsWith(word+'#') - - def startsWith(self, prefix: str) -> bool: - node = self - for c in prefix : - i = c2i(c) - node = node.next[i] - if not node : return False - return True -``` -### 好的写法之后再写一遍 -```python -class Trie: - def __init__(self): - self.trie = {} - - def insert(self, word: str) -> None: - node = self.trie - for char in word: - ####### 下面这一行是重点 如果 char 在key中 就返回node[char] - ####### 如果不在 就node[char]={}再返回 - node = node.setdefault(char, {}) - node['#'] = True # 标记单词结束 - - def search(self, word: str) -> bool: - return self.startsWith(word+'#') - - def startsWith(self, prefix: str) -> bool: - node = self.trie - for char in prefix: - if char not in node: - return False - node = node[char] - return True -``` - -# 55. [全排列](https://leetcode.cn/problems/permutations/)46-251225 - -复杂度是 $O(n!)$ - -用递归做 -```python -def Digui(nums): - ans = [] - if len(nums) == 1 : - return [[nums[0]]] - for i in range(len(nums)) : - cp = nums[:] - t = cp[0] - cp[0] = cp[i] - cp[i] = t - sub_ans = Digui(cp[1:]) - for b in sub_ans : - a = [cp[0]] + b - ans.append(a) - return ans - -class Solution: - def permute(self, nums: List[int]) -> List[List[int]]: - return Digui(nums) -``` - -题解的做法: -```python -class Solution: - def permute(self, nums: List[int]) -> List[List[int]] : - # index 尾巴 全排列 - def dfs(index): - if index == len(nums): - return res.append(nums[:]) - for i in range(index , len(nums)): - nums[i],nums[index] = nums[index],nums[i] - dfs(index+1) - nums[index],nums[i] = nums[i],nums[index] - res = [] - dfs(0) - return res -``` - -# 56. [子集](https://leetcode.cn/problems/subsets/)78-251229 - -递归,和上一个思路一样。但是这个是组合 把输出分成 1和 后面的 先求解 digui(后面的) 然后再在后面的解中每个和1 结合,加上 后面的解 就是 一个完成的解 -```python -def Digui(nums) : - print(nums) - if len(nums)==1 : - return [nums,[]] - sub_ans = Digui(nums[1:]) - ans = sub_ans[:] - # print(sub_ans) - for s in sub_ans : - a = [nums[0]] + s - print(f'-- {a} {[nums[0]]} {s}') - ans.append(a) - # print(ans) - return ans - -class Solution: - def subsets(self, nums: List[int]) -> List[List[int]]: - return Digui(nums) -``` - -# 57. [电话号码的字母组合](https://leetcode.cn/problems/letter-combinations-of-a-phone-number/)17-20251229 - -递归 - -```python -mp = { - 2:['a','b','c'], - 3:['d','e','f'], - 4:['g','h','i'], - 5:['j','k','l'], - 6:['m','n','o'], - 7:['p','q','r','s'], - 8:['t','u','v'], - 9:['w','x','y','z'], -} - -def c2i(char) : - return ord(char)-ord('0') - -def Digui(str): - index,remain = c2i(str[0]),str[1:] - wl = mp[index] - if len(remain) == 0 : - return wl - sub_ans = Digui(remain) - ans = [] - for sub in sub_ans : - for c in wl : - ans.append(c+sub) - return ans - -class Solution: - def letterCombinations(self, digits: str) -> List[str]: - return Digui(digits) -``` - -# 58. [组合总和](https://leetcode.cn/problems/combination-sum/)39-20251229\* 跳过了 之后自己写一下 - -这个是最快的解法 -```python -class Solution: - def combinationSum(self, candidates: List[int], target: int) -> List[List[int]]: - def backtrack(summ,startIndex): - if summ == target: - res.append(path[:]) - return - for i in range(startIndex,n): - if summ + candidates[i] > target: - break - path.append(candidates[i]) - backtrack(summ+candidates[i],i) - path.pop() - - - res=[] - path = [] - n = len(candidates) - candidates.sort() - summ,startIndex = 0,0 - backtrack(summ,startIndex) - return res -``` - - -# 59. [括号生成](https://leetcode.cn/problems/generate-parentheses/)22-20251230 - -## 思路一:动态规划 递归 -这个递归的思路可以看看 重点在于 把每个问题 拆分成 (a)b 找到 ab的子问题,ab可以为空。我觉得本质还是递归,不太懂跟动态规划有什么具体关系,下次刷到可以再想想。我现在刚演完元旦晚会,好饿20251230210208 -另外关于拆分子问题这个,其实就是 n-1 对的 sub_ans 然后把这一对新的括号加在任意的位置 -```python -class Solution: - @lru_cache(None) - def generateParenthesis(self, n: int) -> List[str]: - if n == 0: - return [''] - ans = [] - for c in range(n): - for left in self.generateParenthesis(c): - for right in self.generateParenthesis(n-1-c): - ans.append('({}){}'.format(left, right)) - return ans -``` - -## 别的思路 -递归得到所有的 组合,然后用一个函数遍历测试对不对,但是这个复杂度有指数项 不好。 - -# 60. [单词搜索](https://leetcode.cn/problems/word-search/)79-20251230 - -## Trash-DFS - -这个方法的时间完全不能接受,找到开头,然后dfs所有的path - -```python -class Solution: - def exist(self, board: List[List[str]], word: str) -> bool: - r = len(board) - c = len(board[0]) - def dfs(i,j,target) : - cur_char = board[i][j] - board[i][j] = '#' - if len(target) == 1 : - board[i][j] = cur_char - return cur_char == target - target_char, next_target_char = target[0], target[1] - if cur_char != target_char : - board[i][j] = cur_char - return False - for t in [(i+1,j),(i-1,j),(i,j+1),(i,j-1)] : - u,v = t[0],t[1] - if 0 <= u < r and 0 <= v < c and board[u][v] != '#' : - if dfs(u,v,target[1:]) : return True - board[i][j] = cur_char - return False - - for i in range(r) : - for j in range(c) : - if board[i][j] == word[0] and dfs(i,j,word) : return True - return False -``` - -## 题解方法 - -todo diff --git a/source/categories/index.md b/source/categories/index.md index ee55ab2..2e0e1f7 100644 --- a/source/categories/index.md +++ b/source/categories/index.md @@ -1,7 +1,4 @@ --- -title: 分 类 -date: 2025-04-30 06:27:58 -type: categories -top_img: https://cdn.jsdelivr.net/gh/zip95297/zip95297.github.io@main/source/images/profile/bg4.webp -comments: false +title: categories +date: 2024-05-02 15:30:00 --- diff --git a/source/images/Aligning Cyber Space with Physical World A Comprehensive Survey on Embodied AI/Pasted image 20241024155615.webp b/source/images/Aligning Cyber Space with Physical World A Comprehensive Survey on Embodied AI/Pasted image 20241024155615.webp deleted file mode 100644 index ac4e5b5..0000000 Binary files a/source/images/Aligning Cyber Space with Physical World A Comprehensive Survey on Embodied AI/Pasted image 20241024155615.webp and /dev/null differ diff --git a/source/images/Aligning Cyber Space with Physical World A Comprehensive Survey on Embodied AI/Pasted image 20241024193939.webp b/source/images/Aligning Cyber Space with Physical World A Comprehensive Survey on Embodied AI/Pasted image 20241024193939.webp deleted file mode 100644 index 770a089..0000000 Binary files a/source/images/Aligning Cyber Space with Physical World A Comprehensive Survey on Embodied AI/Pasted image 20241024193939.webp and /dev/null differ diff --git a/source/images/Aligning Cyber Space with Physical World A Comprehensive Survey on Embodied AI/Pasted image 20241025021829.webp b/source/images/Aligning Cyber Space with Physical World A Comprehensive Survey on Embodied AI/Pasted image 20241025021829.webp deleted file mode 100644 index 6ab971a..0000000 Binary files a/source/images/Aligning Cyber Space with Physical World A Comprehensive Survey on Embodied AI/Pasted image 20241025021829.webp and /dev/null differ diff --git a/source/images/Aligning Cyber Space with Physical World A Comprehensive Survey on Embodied AI/Pasted image 20241025022108.webp b/source/images/Aligning Cyber Space with Physical World A Comprehensive Survey on Embodied AI/Pasted image 20241025022108.webp deleted file mode 100644 index ce44739..0000000 Binary files a/source/images/Aligning Cyber Space with Physical World A Comprehensive Survey on Embodied AI/Pasted image 20241025022108.webp and /dev/null differ diff --git a/source/images/Aligning Cyber Space with Physical World A Comprehensive Survey on Embodied AI/Pasted image 20241025022601.webp b/source/images/Aligning Cyber Space with Physical World A Comprehensive Survey on Embodied AI/Pasted image 20241025022601.webp deleted file mode 100644 index 8216b3f..0000000 Binary files a/source/images/Aligning Cyber Space with Physical World A Comprehensive Survey on Embodied AI/Pasted image 20241025022601.webp and /dev/null differ diff --git a/source/images/Aligning Cyber Space with Physical World A Comprehensive Survey on Embodied AI/Pasted image 20241025022653.webp b/source/images/Aligning Cyber Space with Physical World A Comprehensive Survey on Embodied AI/Pasted image 20241025022653.webp deleted file mode 100644 index 40efd1c..0000000 Binary files a/source/images/Aligning Cyber Space with Physical World A Comprehensive Survey on Embodied AI/Pasted image 20241025022653.webp and /dev/null differ diff --git a/source/images/Aligning Cyber Space with Physical World A Comprehensive Survey on Embodied AI/Pasted image 20241025024259.webp b/source/images/Aligning Cyber Space with Physical World A Comprehensive Survey on Embodied AI/Pasted image 20241025024259.webp deleted file mode 100644 index 603211d..0000000 Binary files a/source/images/Aligning Cyber Space with Physical World A Comprehensive Survey on Embodied AI/Pasted image 20241025024259.webp and /dev/null differ diff --git a/source/images/Aligning Cyber Space with Physical World A Comprehensive Survey on Embodied AI/Pasted image 20241025025003.webp b/source/images/Aligning Cyber Space with Physical World A Comprehensive Survey on Embodied AI/Pasted image 20241025025003.webp deleted file mode 100644 index 6081e0d..0000000 Binary files a/source/images/Aligning Cyber Space with Physical World A Comprehensive Survey on Embodied AI/Pasted image 20241025025003.webp and /dev/null differ diff --git a/source/images/Aligning Cyber Space with Physical World A Comprehensive Survey on Embodied AI/Pasted image 20241025170849.webp b/source/images/Aligning Cyber Space with Physical World A Comprehensive Survey on Embodied AI/Pasted image 20241025170849.webp deleted file mode 100644 index 48acc35..0000000 Binary files a/source/images/Aligning Cyber Space with Physical World A Comprehensive Survey on Embodied AI/Pasted image 20241025170849.webp and /dev/null differ diff --git a/source/images/Aligning Cyber Space with Physical World A Comprehensive Survey on Embodied AI/Pasted image 20241025173342.webp b/source/images/Aligning Cyber Space with Physical World A Comprehensive Survey on Embodied AI/Pasted image 20241025173342.webp deleted file mode 100644 index b806ddf..0000000 Binary files a/source/images/Aligning Cyber Space with Physical World A Comprehensive Survey on Embodied AI/Pasted image 20241025173342.webp and /dev/null differ diff --git a/source/images/Aligning Cyber Space with Physical World A Comprehensive Survey on Embodied AI/Pasted image 20241027182915.webp b/source/images/Aligning Cyber Space with Physical World A Comprehensive Survey on Embodied AI/Pasted image 20241027182915.webp deleted file mode 100644 index e8a91d0..0000000 Binary files a/source/images/Aligning Cyber Space with Physical World A Comprehensive Survey on Embodied AI/Pasted image 20241027182915.webp and /dev/null differ diff --git a/source/images/Aligning Cyber Space with Physical World A Comprehensive Survey on Embodied AI/Pasted image 20241029045840.webp b/source/images/Aligning Cyber Space with Physical World A Comprehensive Survey on Embodied AI/Pasted image 20241029045840.webp deleted file mode 100644 index 98b81ab..0000000 Binary files a/source/images/Aligning Cyber Space with Physical World A Comprehensive Survey on Embodied AI/Pasted image 20241029045840.webp and /dev/null differ diff --git a/source/images/Aligning Cyber Space with Physical World A Comprehensive Survey on Embodied AI/Pasted image 20241029050047.webp b/source/images/Aligning Cyber Space with Physical World A Comprehensive Survey on Embodied AI/Pasted image 20241029050047.webp deleted file mode 100644 index d510f5d..0000000 Binary files a/source/images/Aligning Cyber Space with Physical World A Comprehensive Survey on Embodied AI/Pasted image 20241029050047.webp and /dev/null differ diff --git a/source/images/Aligning Cyber Space with Physical World A Comprehensive Survey on Embodied AI/Pasted image 20241029050221.webp b/source/images/Aligning Cyber Space with Physical World A Comprehensive Survey on Embodied AI/Pasted image 20241029050221.webp deleted file mode 100644 index 47a4495..0000000 Binary files a/source/images/Aligning Cyber Space with Physical World A Comprehensive Survey on Embodied AI/Pasted image 20241029050221.webp and /dev/null differ diff --git a/source/images/Aligning Cyber Space with Physical World A Comprehensive Survey on Embodied AI/Pasted image 20241031012655.webp b/source/images/Aligning Cyber Space with Physical World A Comprehensive Survey on Embodied AI/Pasted image 20241031012655.webp deleted file mode 100644 index f5d9332..0000000 Binary files a/source/images/Aligning Cyber Space with Physical World A Comprehensive Survey on Embodied AI/Pasted image 20241031012655.webp and /dev/null differ diff --git a/source/images/Aligning Cyber Space with Physical World A Comprehensive Survey on Embodied AI/Pasted image 20241031014542.webp b/source/images/Aligning Cyber Space with Physical World A Comprehensive Survey on Embodied AI/Pasted image 20241031014542.webp deleted file mode 100644 index af7e9d0..0000000 Binary files a/source/images/Aligning Cyber Space with Physical World A Comprehensive Survey on Embodied AI/Pasted image 20241031014542.webp and /dev/null differ diff --git a/source/images/Aligning Cyber Space with Physical World A Comprehensive Survey on Embodied AI/Pasted image 20241031015536.webp b/source/images/Aligning Cyber Space with Physical World A Comprehensive Survey on Embodied AI/Pasted image 20241031015536.webp deleted file mode 100644 index 2b7b3a6..0000000 Binary files a/source/images/Aligning Cyber Space with Physical World A Comprehensive Survey on Embodied AI/Pasted image 20241031015536.webp and /dev/null differ diff --git a/source/images/Aligning Cyber Space with Physical World A Comprehensive Survey on Embodied AI/Pasted image 20241101003208.webp b/source/images/Aligning Cyber Space with Physical World A Comprehensive Survey on Embodied AI/Pasted image 20241101003208.webp deleted file mode 100644 index 477896b..0000000 Binary files a/source/images/Aligning Cyber Space with Physical World A Comprehensive Survey on Embodied AI/Pasted image 20241101003208.webp and /dev/null differ diff --git a/source/images/Aligning Cyber Space with Physical World A Comprehensive Survey on Embodied AI/Pasted image 20241101012729.webp b/source/images/Aligning Cyber Space with Physical World A Comprehensive Survey on Embodied AI/Pasted image 20241101012729.webp deleted file mode 100644 index c52135e..0000000 Binary files a/source/images/Aligning Cyber Space with Physical World A Comprehensive Survey on Embodied AI/Pasted image 20241101012729.webp and /dev/null differ diff --git a/source/images/Aligning Cyber Space with Physical World A Comprehensive Survey on Embodied AI/Pasted image 20241101012906.webp b/source/images/Aligning Cyber Space with Physical World A Comprehensive Survey on Embodied AI/Pasted image 20241101012906.webp deleted file mode 100644 index bb82362..0000000 Binary files a/source/images/Aligning Cyber Space with Physical World A Comprehensive Survey on Embodied AI/Pasted image 20241101012906.webp and /dev/null differ diff --git a/source/images/Aligning Cyber Space with Physical World A Comprehensive Survey on Embodied AI/Pasted image 20241101013442.webp b/source/images/Aligning Cyber Space with Physical World A Comprehensive Survey on Embodied AI/Pasted image 20241101013442.webp deleted file mode 100644 index 98ea75b..0000000 Binary files a/source/images/Aligning Cyber Space with Physical World A Comprehensive Survey on Embodied AI/Pasted image 20241101013442.webp and /dev/null differ diff --git a/source/images/Aligning Cyber Space with Physical World A Comprehensive Survey on Embodied AI/Pasted image 20241101013803.webp b/source/images/Aligning Cyber Space with Physical World A Comprehensive Survey on Embodied AI/Pasted image 20241101013803.webp deleted file mode 100644 index ad6f989..0000000 Binary files a/source/images/Aligning Cyber Space with Physical World A Comprehensive Survey on Embodied AI/Pasted image 20241101013803.webp and /dev/null differ diff --git "a/source/images/MacOS&Linux\344\270\255\347\232\204\345\267\245\345\205\267/Pasted image 20250428184655.webp" "b/source/images/MacOS&Linux\344\270\255\347\232\204\345\267\245\345\205\267/Pasted image 20250428184655.webp" deleted file mode 100644 index d8d2596..0000000 Binary files "a/source/images/MacOS&Linux\344\270\255\347\232\204\345\267\245\345\205\267/Pasted image 20250428184655.webp" and /dev/null differ diff --git a/source/images/ResShift/Pasted image 20250515211826.webp b/source/images/ResShift/Pasted image 20250515211826.webp deleted file mode 100644 index 63f09b4..0000000 Binary files a/source/images/ResShift/Pasted image 20250515211826.webp and /dev/null differ diff --git "a/source/images/nanovllm\347\233\270\345\205\263/Pasted image 20260306195828.webp" "b/source/images/nanovllm\347\233\270\345\205\263/Pasted image 20260306195828.webp" deleted file mode 100644 index 3f62b6c..0000000 Binary files "a/source/images/nanovllm\347\233\270\345\205\263/Pasted image 20260306195828.webp" and /dev/null differ diff --git "a/source/images/nanovllm\347\233\270\345\205\263/Pasted image 20260309203632.webp" "b/source/images/nanovllm\347\233\270\345\205\263/Pasted image 20260309203632.webp" deleted file mode 100644 index 786303f..0000000 Binary files "a/source/images/nanovllm\347\233\270\345\205\263/Pasted image 20260309203632.webp" and /dev/null differ diff --git "a/source/images/nanovllm\347\233\270\345\205\263/Pasted image 20260309204255.webp" "b/source/images/nanovllm\347\233\270\345\205\263/Pasted image 20260309204255.webp" deleted file mode 100644 index 6c526dd..0000000 Binary files "a/source/images/nanovllm\347\233\270\345\205\263/Pasted image 20260309204255.webp" and /dev/null differ diff --git a/source/images/profile/alipay.webp b/source/images/profile/alipay.webp deleted file mode 100644 index 623796e..0000000 Binary files a/source/images/profile/alipay.webp and /dev/null differ diff --git a/source/images/profile/bg1.webp b/source/images/profile/bg1.webp deleted file mode 100644 index a96e9a6..0000000 Binary files a/source/images/profile/bg1.webp and /dev/null differ diff --git a/source/images/profile/bg2.webp b/source/images/profile/bg2.webp deleted file mode 100644 index 8363fbf..0000000 Binary files a/source/images/profile/bg2.webp and /dev/null differ diff --git a/source/images/profile/bg3.webp b/source/images/profile/bg3.webp deleted file mode 100644 index 495a406..0000000 Binary files a/source/images/profile/bg3.webp and /dev/null differ diff --git a/source/images/profile/bg4.webp b/source/images/profile/bg4.webp deleted file mode 100644 index 27ab9bd..0000000 Binary files a/source/images/profile/bg4.webp and /dev/null differ diff --git a/source/images/profile/bg5.webp b/source/images/profile/bg5.webp deleted file mode 100644 index 129b285..0000000 Binary files a/source/images/profile/bg5.webp and /dev/null differ diff --git a/source/images/profile/bg6.webp b/source/images/profile/bg6.webp deleted file mode 100644 index 119a4da..0000000 Binary files a/source/images/profile/bg6.webp and /dev/null differ diff --git a/source/images/profile/profile.webp b/source/images/profile/profile.webp deleted file mode 100644 index fc89551..0000000 Binary files a/source/images/profile/profile.webp and /dev/null differ diff --git a/source/images/profile/simpletex.webp b/source/images/profile/simpletex.webp deleted file mode 100644 index d5b8c5d..0000000 Binary files a/source/images/profile/simpletex.webp and /dev/null differ diff --git a/source/images/profile/wxpay.webp b/source/images/profile/wxpay.webp deleted file mode 100644 index f74e19d..0000000 Binary files a/source/images/profile/wxpay.webp and /dev/null differ diff --git "a/source/images/\346\234\215\345\212\241\345\231\250\344\273\243\347\220\206\347\233\270\345\205\263\351\227\256\351\242\230/Pasted image 20250528193554.webp" "b/source/images/\346\234\215\345\212\241\345\231\250\344\273\243\347\220\206\347\233\270\345\205\263\351\227\256\351\242\230/Pasted image 20250528193554.webp" deleted file mode 100644 index 41732ca..0000000 Binary files "a/source/images/\346\234\215\345\212\241\345\231\250\344\273\243\347\220\206\347\233\270\345\205\263\351\227\256\351\242\230/Pasted image 20250528193554.webp" and /dev/null differ diff --git "a/source/images/\346\234\215\345\212\241\345\231\250\344\273\243\347\220\206\347\233\270\345\205\263\351\227\256\351\242\230/Pasted image 20250528193708.webp" "b/source/images/\346\234\215\345\212\241\345\231\250\344\273\243\347\220\206\347\233\270\345\205\263\351\227\256\351\242\230/Pasted image 20250528193708.webp" deleted file mode 100644 index 3b3dd51..0000000 Binary files "a/source/images/\346\234\215\345\212\241\345\231\250\344\273\243\347\220\206\347\233\270\345\205\263\351\227\256\351\242\230/Pasted image 20250528193708.webp" and /dev/null differ diff --git "a/source/images/\346\234\215\345\212\241\345\231\250\344\273\243\347\220\206\347\233\270\345\205\263\351\227\256\351\242\230/Pasted image 20250528193913.webp" "b/source/images/\346\234\215\345\212\241\345\231\250\344\273\243\347\220\206\347\233\270\345\205\263\351\227\256\351\242\230/Pasted image 20250528193913.webp" deleted file mode 100644 index d6d8f65..0000000 Binary files "a/source/images/\346\234\215\345\212\241\345\231\250\344\273\243\347\220\206\347\233\270\345\205\263\351\227\256\351\242\230/Pasted image 20250528193913.webp" and /dev/null differ diff --git "a/source/images/\346\234\272\345\231\250\345\255\246\344\271\240\345\244\215\344\271\240\350\265\204\346\226\231/Pasted image 20250425170110.webp" "b/source/images/\346\234\272\345\231\250\345\255\246\344\271\240\345\244\215\344\271\240\350\265\204\346\226\231/Pasted image 20250425170110.webp" deleted file mode 100644 index 06a298e..0000000 Binary files "a/source/images/\346\234\272\345\231\250\345\255\246\344\271\240\345\244\215\344\271\240\350\265\204\346\226\231/Pasted image 20250425170110.webp" and /dev/null differ diff --git "a/source/images/\346\234\272\345\231\250\345\255\246\344\271\240\345\244\215\344\271\240\350\265\204\346\226\231/Pasted image 20250425170633.webp" "b/source/images/\346\234\272\345\231\250\345\255\246\344\271\240\345\244\215\344\271\240\350\265\204\346\226\231/Pasted image 20250425170633.webp" deleted file mode 100644 index a54635e..0000000 Binary files "a/source/images/\346\234\272\345\231\250\345\255\246\344\271\240\345\244\215\344\271\240\350\265\204\346\226\231/Pasted image 20250425170633.webp" and /dev/null differ diff --git "a/source/images/\346\234\272\345\231\250\345\255\246\344\271\240\345\244\215\344\271\240\350\265\204\346\226\231/Pasted image 20250425171150.webp" "b/source/images/\346\234\272\345\231\250\345\255\246\344\271\240\345\244\215\344\271\240\350\265\204\346\226\231/Pasted image 20250425171150.webp" deleted file mode 100644 index fbd9ba4..0000000 Binary files "a/source/images/\346\234\272\345\231\250\345\255\246\344\271\240\345\244\215\344\271\240\350\265\204\346\226\231/Pasted image 20250425171150.webp" and /dev/null differ diff --git "a/source/images/\346\234\272\345\231\250\345\255\246\344\271\240\345\244\215\344\271\240\350\265\204\346\226\231/Pasted image 20250426030533.webp" "b/source/images/\346\234\272\345\231\250\345\255\246\344\271\240\345\244\215\344\271\240\350\265\204\346\226\231/Pasted image 20250426030533.webp" deleted file mode 100644 index 716d456..0000000 Binary files "a/source/images/\346\234\272\345\231\250\345\255\246\344\271\240\345\244\215\344\271\240\350\265\204\346\226\231/Pasted image 20250426030533.webp" and /dev/null differ diff --git "a/source/images/\346\234\272\345\231\250\345\255\246\344\271\240\345\244\215\344\271\240\350\265\204\346\226\231/Pasted image 20250426031557.webp" "b/source/images/\346\234\272\345\231\250\345\255\246\344\271\240\345\244\215\344\271\240\350\265\204\346\226\231/Pasted image 20250426031557.webp" deleted file mode 100644 index eead8f4..0000000 Binary files "a/source/images/\346\234\272\345\231\250\345\255\246\344\271\240\345\244\215\344\271\240\350\265\204\346\226\231/Pasted image 20250426031557.webp" and /dev/null differ diff --git "a/source/images/\346\234\272\345\231\250\345\255\246\344\271\240\345\244\215\344\271\240\350\265\204\346\226\231/Pasted image 20250426032114.webp" "b/source/images/\346\234\272\345\231\250\345\255\246\344\271\240\345\244\215\344\271\240\350\265\204\346\226\231/Pasted image 20250426032114.webp" deleted file mode 100644 index 8064c60..0000000 Binary files "a/source/images/\346\234\272\345\231\250\345\255\246\344\271\240\345\244\215\344\271\240\350\265\204\346\226\231/Pasted image 20250426032114.webp" and /dev/null differ diff --git "a/source/images/\346\234\272\345\231\250\345\255\246\344\271\240\345\244\215\344\271\240\350\265\204\346\226\231/Pasted image 20250426032336.webp" "b/source/images/\346\234\272\345\231\250\345\255\246\344\271\240\345\244\215\344\271\240\350\265\204\346\226\231/Pasted image 20250426032336.webp" deleted file mode 100644 index 59c5388..0000000 Binary files "a/source/images/\346\234\272\345\231\250\345\255\246\344\271\240\345\244\215\344\271\240\350\265\204\346\226\231/Pasted image 20250426032336.webp" and /dev/null differ diff --git "a/source/images/\346\234\272\345\231\250\345\255\246\344\271\240\345\244\215\344\271\240\350\265\204\346\226\231/Pasted image 20250426032422.webp" "b/source/images/\346\234\272\345\231\250\345\255\246\344\271\240\345\244\215\344\271\240\350\265\204\346\226\231/Pasted image 20250426032422.webp" deleted file mode 100644 index 2782e34..0000000 Binary files "a/source/images/\346\234\272\345\231\250\345\255\246\344\271\240\345\244\215\344\271\240\350\265\204\346\226\231/Pasted image 20250426032422.webp" and /dev/null differ diff --git "a/source/images/\346\234\272\345\231\250\345\255\246\344\271\240\345\244\215\344\271\240\350\265\204\346\226\231/Pasted image 20250426155115.webp" "b/source/images/\346\234\272\345\231\250\345\255\246\344\271\240\345\244\215\344\271\240\350\265\204\346\226\231/Pasted image 20250426155115.webp" deleted file mode 100644 index ce3e5e5..0000000 Binary files "a/source/images/\346\234\272\345\231\250\345\255\246\344\271\240\345\244\215\344\271\240\350\265\204\346\226\231/Pasted image 20250426155115.webp" and /dev/null differ diff --git "a/source/images/\346\234\272\345\231\250\345\255\246\344\271\240\345\244\215\344\271\240\350\265\204\346\226\231/Pasted image 20250426155201.webp" "b/source/images/\346\234\272\345\231\250\345\255\246\344\271\240\345\244\215\344\271\240\350\265\204\346\226\231/Pasted image 20250426155201.webp" deleted file mode 100644 index 6e184a6..0000000 Binary files "a/source/images/\346\234\272\345\231\250\345\255\246\344\271\240\345\244\215\344\271\240\350\265\204\346\226\231/Pasted image 20250426155201.webp" and /dev/null differ diff --git "a/source/images/\346\234\272\345\231\250\345\255\246\344\271\240\345\244\215\344\271\240\350\265\204\346\226\231/Pasted image 20250426155225.webp" "b/source/images/\346\234\272\345\231\250\345\255\246\344\271\240\345\244\215\344\271\240\350\265\204\346\226\231/Pasted image 20250426155225.webp" deleted file mode 100644 index bb728e0..0000000 Binary files "a/source/images/\346\234\272\345\231\250\345\255\246\344\271\240\345\244\215\344\271\240\350\265\204\346\226\231/Pasted image 20250426155225.webp" and /dev/null differ diff --git "a/source/images/\346\234\272\345\231\250\345\255\246\344\271\240\345\244\215\344\271\240\350\265\204\346\226\231/Pasted image 20250426155301.webp" "b/source/images/\346\234\272\345\231\250\345\255\246\344\271\240\345\244\215\344\271\240\350\265\204\346\226\231/Pasted image 20250426155301.webp" deleted file mode 100644 index a1b6c1e..0000000 Binary files "a/source/images/\346\234\272\345\231\250\345\255\246\344\271\240\345\244\215\344\271\240\350\265\204\346\226\231/Pasted image 20250426155301.webp" and /dev/null differ diff --git "a/source/images/\346\234\272\345\231\250\345\255\246\344\271\240\345\244\215\344\271\240\350\265\204\346\226\231/Pasted image 20250426155423.webp" "b/source/images/\346\234\272\345\231\250\345\255\246\344\271\240\345\244\215\344\271\240\350\265\204\346\226\231/Pasted image 20250426155423.webp" deleted file mode 100644 index 7f78be7..0000000 Binary files "a/source/images/\346\234\272\345\231\250\345\255\246\344\271\240\345\244\215\344\271\240\350\265\204\346\226\231/Pasted image 20250426155423.webp" and /dev/null differ diff --git "a/source/images/\346\234\272\345\231\250\345\255\246\344\271\240\345\244\215\344\271\240\350\265\204\346\226\231/Pasted image 20250426155429.webp" "b/source/images/\346\234\272\345\231\250\345\255\246\344\271\240\345\244\215\344\271\240\350\265\204\346\226\231/Pasted image 20250426155429.webp" deleted file mode 100644 index ee7b2cf..0000000 Binary files "a/source/images/\346\234\272\345\231\250\345\255\246\344\271\240\345\244\215\344\271\240\350\265\204\346\226\231/Pasted image 20250426155429.webp" and /dev/null differ diff --git "a/source/images/\346\234\272\345\231\250\345\255\246\344\271\240\345\244\215\344\271\240\350\265\204\346\226\231/Pasted image 20250426155434.webp" "b/source/images/\346\234\272\345\231\250\345\255\246\344\271\240\345\244\215\344\271\240\350\265\204\346\226\231/Pasted image 20250426155434.webp" deleted file mode 100644 index af66fe4..0000000 Binary files "a/source/images/\346\234\272\345\231\250\345\255\246\344\271\240\345\244\215\344\271\240\350\265\204\346\226\231/Pasted image 20250426155434.webp" and /dev/null differ diff --git "a/source/images/\346\234\272\345\231\250\345\255\246\344\271\240\345\244\215\344\271\240\350\265\204\346\226\231/Pasted image 20250426155455.webp" "b/source/images/\346\234\272\345\231\250\345\255\246\344\271\240\345\244\215\344\271\240\350\265\204\346\226\231/Pasted image 20250426155455.webp" deleted file mode 100644 index 47636c8..0000000 Binary files "a/source/images/\346\234\272\345\231\250\345\255\246\344\271\240\345\244\215\344\271\240\350\265\204\346\226\231/Pasted image 20250426155455.webp" and /dev/null differ diff --git "a/source/images/\346\234\272\345\231\250\345\255\246\344\271\240\345\244\215\344\271\240\350\265\204\346\226\231/Pasted image 20250426155505.webp" "b/source/images/\346\234\272\345\231\250\345\255\246\344\271\240\345\244\215\344\271\240\350\265\204\346\226\231/Pasted image 20250426155505.webp" deleted file mode 100644 index b83746c..0000000 Binary files "a/source/images/\346\234\272\345\231\250\345\255\246\344\271\240\345\244\215\344\271\240\350\265\204\346\226\231/Pasted image 20250426155505.webp" and /dev/null differ diff --git "a/source/images/\346\234\272\345\231\250\345\255\246\344\271\240\345\244\215\344\271\240\350\265\204\346\226\231/Pasted image 20250426160411.webp" "b/source/images/\346\234\272\345\231\250\345\255\246\344\271\240\345\244\215\344\271\240\350\265\204\346\226\231/Pasted image 20250426160411.webp" deleted file mode 100644 index e518aa6..0000000 Binary files "a/source/images/\346\234\272\345\231\250\345\255\246\344\271\240\345\244\215\344\271\240\350\265\204\346\226\231/Pasted image 20250426160411.webp" and /dev/null differ diff --git "a/source/images/\346\234\272\345\231\250\345\255\246\344\271\240\345\244\215\344\271\240\350\265\204\346\226\231/Pasted image 20250426160417.webp" "b/source/images/\346\234\272\345\231\250\345\255\246\344\271\240\345\244\215\344\271\240\350\265\204\346\226\231/Pasted image 20250426160417.webp" deleted file mode 100644 index 58a5e1c..0000000 Binary files "a/source/images/\346\234\272\345\231\250\345\255\246\344\271\240\345\244\215\344\271\240\350\265\204\346\226\231/Pasted image 20250426160417.webp" and /dev/null differ diff --git "a/source/images/\346\234\272\345\231\250\345\255\246\344\271\240\345\244\215\344\271\240\350\265\204\346\226\231/Pasted image 20250426160617.webp" "b/source/images/\346\234\272\345\231\250\345\255\246\344\271\240\345\244\215\344\271\240\350\265\204\346\226\231/Pasted image 20250426160617.webp" deleted file mode 100644 index 80999ac..0000000 Binary files "a/source/images/\346\234\272\345\231\250\345\255\246\344\271\240\345\244\215\344\271\240\350\265\204\346\226\231/Pasted image 20250426160617.webp" and /dev/null differ diff --git "a/source/images/\346\234\272\345\231\250\345\255\246\344\271\240\345\244\215\344\271\240\350\265\204\346\226\231/Pasted image 20250426160732.webp" "b/source/images/\346\234\272\345\231\250\345\255\246\344\271\240\345\244\215\344\271\240\350\265\204\346\226\231/Pasted image 20250426160732.webp" deleted file mode 100644 index 2b3c35e..0000000 Binary files "a/source/images/\346\234\272\345\231\250\345\255\246\344\271\240\345\244\215\344\271\240\350\265\204\346\226\231/Pasted image 20250426160732.webp" and /dev/null differ diff --git "a/source/images/\346\234\272\345\231\250\345\255\246\344\271\240\345\244\215\344\271\240\350\265\204\346\226\231/Pasted image 20250426160802.webp" "b/source/images/\346\234\272\345\231\250\345\255\246\344\271\240\345\244\215\344\271\240\350\265\204\346\226\231/Pasted image 20250426160802.webp" deleted file mode 100644 index 3e8f059..0000000 Binary files "a/source/images/\346\234\272\345\231\250\345\255\246\344\271\240\345\244\215\344\271\240\350\265\204\346\226\231/Pasted image 20250426160802.webp" and /dev/null differ diff --git "a/source/images/\346\234\272\345\231\250\345\255\246\344\271\240\345\244\215\344\271\240\350\265\204\346\226\231/Pasted image 20250426160911.webp" "b/source/images/\346\234\272\345\231\250\345\255\246\344\271\240\345\244\215\344\271\240\350\265\204\346\226\231/Pasted image 20250426160911.webp" deleted file mode 100644 index 9232b38..0000000 Binary files "a/source/images/\346\234\272\345\231\250\345\255\246\344\271\240\345\244\215\344\271\240\350\265\204\346\226\231/Pasted image 20250426160911.webp" and /dev/null differ diff --git "a/source/images/\346\234\272\345\231\250\345\255\246\344\271\240\345\244\215\344\271\240\350\265\204\346\226\231/Pasted image 20250426162156.webp" "b/source/images/\346\234\272\345\231\250\345\255\246\344\271\240\345\244\215\344\271\240\350\265\204\346\226\231/Pasted image 20250426162156.webp" deleted file mode 100644 index 6bd48e9..0000000 Binary files "a/source/images/\346\234\272\345\231\250\345\255\246\344\271\240\345\244\215\344\271\240\350\265\204\346\226\231/Pasted image 20250426162156.webp" and /dev/null differ diff --git "a/source/images/\346\234\272\345\231\250\345\255\246\344\271\240\345\244\215\344\271\240\350\265\204\346\226\231/Pasted image 20250426162206.webp" "b/source/images/\346\234\272\345\231\250\345\255\246\344\271\240\345\244\215\344\271\240\350\265\204\346\226\231/Pasted image 20250426162206.webp" deleted file mode 100644 index bd8a43b..0000000 Binary files "a/source/images/\346\234\272\345\231\250\345\255\246\344\271\240\345\244\215\344\271\240\350\265\204\346\226\231/Pasted image 20250426162206.webp" and /dev/null differ diff --git "a/source/images/\346\234\272\345\231\250\345\255\246\344\271\240\345\244\215\344\271\240\350\265\204\346\226\231/Pasted image 20250426165956.webp" "b/source/images/\346\234\272\345\231\250\345\255\246\344\271\240\345\244\215\344\271\240\350\265\204\346\226\231/Pasted image 20250426165956.webp" deleted file mode 100644 index b23bbcc..0000000 Binary files "a/source/images/\346\234\272\345\231\250\345\255\246\344\271\240\345\244\215\344\271\240\350\265\204\346\226\231/Pasted image 20250426165956.webp" and /dev/null differ diff --git "a/source/images/\346\234\272\345\231\250\345\255\246\344\271\240\345\244\215\344\271\240\350\265\204\346\226\231/Pasted image 20250426170353.webp" "b/source/images/\346\234\272\345\231\250\345\255\246\344\271\240\345\244\215\344\271\240\350\265\204\346\226\231/Pasted image 20250426170353.webp" deleted file mode 100644 index 7b075fd..0000000 Binary files "a/source/images/\346\234\272\345\231\250\345\255\246\344\271\240\345\244\215\344\271\240\350\265\204\346\226\231/Pasted image 20250426170353.webp" and /dev/null differ diff --git "a/source/images/\346\234\272\345\231\250\345\255\246\344\271\240\345\244\215\344\271\240\350\265\204\346\226\231/Pasted image 20250426170523.webp" "b/source/images/\346\234\272\345\231\250\345\255\246\344\271\240\345\244\215\344\271\240\350\265\204\346\226\231/Pasted image 20250426170523.webp" deleted file mode 100644 index 392a2bc..0000000 Binary files "a/source/images/\346\234\272\345\231\250\345\255\246\344\271\240\345\244\215\344\271\240\350\265\204\346\226\231/Pasted image 20250426170523.webp" and /dev/null differ diff --git "a/source/images/\346\234\272\345\231\250\345\255\246\344\271\240\345\244\215\344\271\240\350\265\204\346\226\231/Pasted image 20250426170742.webp" "b/source/images/\346\234\272\345\231\250\345\255\246\344\271\240\345\244\215\344\271\240\350\265\204\346\226\231/Pasted image 20250426170742.webp" deleted file mode 100644 index 5f57f8c..0000000 Binary files "a/source/images/\346\234\272\345\231\250\345\255\246\344\271\240\345\244\215\344\271\240\350\265\204\346\226\231/Pasted image 20250426170742.webp" and /dev/null differ diff --git "a/source/images/\346\234\272\345\231\250\345\255\246\344\271\240\345\244\215\344\271\240\350\265\204\346\226\231/Pasted image 20250426171017.webp" "b/source/images/\346\234\272\345\231\250\345\255\246\344\271\240\345\244\215\344\271\240\350\265\204\346\226\231/Pasted image 20250426171017.webp" deleted file mode 100644 index c5862b7..0000000 Binary files "a/source/images/\346\234\272\345\231\250\345\255\246\344\271\240\345\244\215\344\271\240\350\265\204\346\226\231/Pasted image 20250426171017.webp" and /dev/null differ diff --git "a/source/images/\346\234\272\345\231\250\345\255\246\344\271\240\345\244\215\344\271\240\350\265\204\346\226\231/Pasted image 20250426171443.webp" "b/source/images/\346\234\272\345\231\250\345\255\246\344\271\240\345\244\215\344\271\240\350\265\204\346\226\231/Pasted image 20250426171443.webp" deleted file mode 100644 index ff5d4aa..0000000 Binary files "a/source/images/\346\234\272\345\231\250\345\255\246\344\271\240\345\244\215\344\271\240\350\265\204\346\226\231/Pasted image 20250426171443.webp" and /dev/null differ diff --git "a/source/images/\346\234\272\345\231\250\345\255\246\344\271\240\345\244\215\344\271\240\350\265\204\346\226\231/Pasted image 20250426171645.webp" "b/source/images/\346\234\272\345\231\250\345\255\246\344\271\240\345\244\215\344\271\240\350\265\204\346\226\231/Pasted image 20250426171645.webp" deleted file mode 100644 index bd40d5b..0000000 Binary files "a/source/images/\346\234\272\345\231\250\345\255\246\344\271\240\345\244\215\344\271\240\350\265\204\346\226\231/Pasted image 20250426171645.webp" and /dev/null differ diff --git "a/source/images/\346\234\272\345\231\250\345\255\246\344\271\240\345\244\215\344\271\240\350\265\204\346\226\231/Pasted image 20250426171825.webp" "b/source/images/\346\234\272\345\231\250\345\255\246\344\271\240\345\244\215\344\271\240\350\265\204\346\226\231/Pasted image 20250426171825.webp" deleted file mode 100644 index 16440cb..0000000 Binary files "a/source/images/\346\234\272\345\231\250\345\255\246\344\271\240\345\244\215\344\271\240\350\265\204\346\226\231/Pasted image 20250426171825.webp" and /dev/null differ diff --git "a/source/images/\346\234\272\345\231\250\345\255\246\344\271\240\345\244\215\344\271\240\350\265\204\346\226\231/Pasted image 20250426171929.webp" "b/source/images/\346\234\272\345\231\250\345\255\246\344\271\240\345\244\215\344\271\240\350\265\204\346\226\231/Pasted image 20250426171929.webp" deleted file mode 100644 index 8b495fd..0000000 Binary files "a/source/images/\346\234\272\345\231\250\345\255\246\344\271\240\345\244\215\344\271\240\350\265\204\346\226\231/Pasted image 20250426171929.webp" and /dev/null differ diff --git "a/source/images/\346\234\272\345\231\250\345\255\246\344\271\240\345\244\215\344\271\240\350\265\204\346\226\231/Pasted image 20250426172010.webp" "b/source/images/\346\234\272\345\231\250\345\255\246\344\271\240\345\244\215\344\271\240\350\265\204\346\226\231/Pasted image 20250426172010.webp" deleted file mode 100644 index 531e9d1..0000000 Binary files "a/source/images/\346\234\272\345\231\250\345\255\246\344\271\240\345\244\215\344\271\240\350\265\204\346\226\231/Pasted image 20250426172010.webp" and /dev/null differ diff --git "a/source/images/\346\234\272\345\231\250\345\255\246\344\271\240\345\244\215\344\271\240\350\265\204\346\226\231/Pasted image 20250426172506.webp" "b/source/images/\346\234\272\345\231\250\345\255\246\344\271\240\345\244\215\344\271\240\350\265\204\346\226\231/Pasted image 20250426172506.webp" deleted file mode 100644 index 2fa0df3..0000000 Binary files "a/source/images/\346\234\272\345\231\250\345\255\246\344\271\240\345\244\215\344\271\240\350\265\204\346\226\231/Pasted image 20250426172506.webp" and /dev/null differ diff --git "a/source/images/\346\234\272\345\231\250\345\255\246\344\271\240\345\244\215\344\271\240\350\265\204\346\226\231/Pasted image 20250426172544.webp" "b/source/images/\346\234\272\345\231\250\345\255\246\344\271\240\345\244\215\344\271\240\350\265\204\346\226\231/Pasted image 20250426172544.webp" deleted file mode 100644 index 0deff5f..0000000 Binary files "a/source/images/\346\234\272\345\231\250\345\255\246\344\271\240\345\244\215\344\271\240\350\265\204\346\226\231/Pasted image 20250426172544.webp" and /dev/null differ diff --git "a/source/images/\346\234\272\345\231\250\345\255\246\344\271\240\345\244\215\344\271\240\350\265\204\346\226\231/Pasted image 20250426172553.webp" "b/source/images/\346\234\272\345\231\250\345\255\246\344\271\240\345\244\215\344\271\240\350\265\204\346\226\231/Pasted image 20250426172553.webp" deleted file mode 100644 index 47a44e2..0000000 Binary files "a/source/images/\346\234\272\345\231\250\345\255\246\344\271\240\345\244\215\344\271\240\350\265\204\346\226\231/Pasted image 20250426172553.webp" and /dev/null differ diff --git "a/source/images/\346\234\272\345\231\250\345\255\246\344\271\240\345\244\215\344\271\240\350\265\204\346\226\231/Pasted image 20250426172652.webp" "b/source/images/\346\234\272\345\231\250\345\255\246\344\271\240\345\244\215\344\271\240\350\265\204\346\226\231/Pasted image 20250426172652.webp" deleted file mode 100644 index a8a5504..0000000 Binary files "a/source/images/\346\234\272\345\231\250\345\255\246\344\271\240\345\244\215\344\271\240\350\265\204\346\226\231/Pasted image 20250426172652.webp" and /dev/null differ diff --git "a/source/images/\346\234\272\345\231\250\345\255\246\344\271\240\345\244\215\344\271\240\350\265\204\346\226\231/Pasted image 20250426172717.webp" "b/source/images/\346\234\272\345\231\250\345\255\246\344\271\240\345\244\215\344\271\240\350\265\204\346\226\231/Pasted image 20250426172717.webp" deleted file mode 100644 index 345e820..0000000 Binary files "a/source/images/\346\234\272\345\231\250\345\255\246\344\271\240\345\244\215\344\271\240\350\265\204\346\226\231/Pasted image 20250426172717.webp" and /dev/null differ diff --git "a/source/images/\346\234\272\345\231\250\345\255\246\344\271\240\345\244\215\344\271\240\350\265\204\346\226\231/Pasted image 20250426175412.webp" "b/source/images/\346\234\272\345\231\250\345\255\246\344\271\240\345\244\215\344\271\240\350\265\204\346\226\231/Pasted image 20250426175412.webp" deleted file mode 100644 index 2e0e53d..0000000 Binary files "a/source/images/\346\234\272\345\231\250\345\255\246\344\271\240\345\244\215\344\271\240\350\265\204\346\226\231/Pasted image 20250426175412.webp" and /dev/null differ diff --git "a/source/images/\346\234\272\345\231\250\345\255\246\344\271\240\345\244\215\344\271\240\350\265\204\346\226\231/Pasted image 20250426175419.webp" "b/source/images/\346\234\272\345\231\250\345\255\246\344\271\240\345\244\215\344\271\240\350\265\204\346\226\231/Pasted image 20250426175419.webp" deleted file mode 100644 index 26afb65..0000000 Binary files "a/source/images/\346\234\272\345\231\250\345\255\246\344\271\240\345\244\215\344\271\240\350\265\204\346\226\231/Pasted image 20250426175419.webp" and /dev/null differ diff --git "a/source/images/\346\234\272\345\231\250\345\255\246\344\271\240\345\244\215\344\271\240\350\265\204\346\226\231/Pasted image 20250426175424.webp" "b/source/images/\346\234\272\345\231\250\345\255\246\344\271\240\345\244\215\344\271\240\350\265\204\346\226\231/Pasted image 20250426175424.webp" deleted file mode 100644 index e8c2796..0000000 Binary files "a/source/images/\346\234\272\345\231\250\345\255\246\344\271\240\345\244\215\344\271\240\350\265\204\346\226\231/Pasted image 20250426175424.webp" and /dev/null differ diff --git "a/source/images/\346\234\272\345\231\250\345\255\246\344\271\240\345\244\215\344\271\240\350\265\204\346\226\231/Pasted image 20250426204559.webp" "b/source/images/\346\234\272\345\231\250\345\255\246\344\271\240\345\244\215\344\271\240\350\265\204\346\226\231/Pasted image 20250426204559.webp" deleted file mode 100644 index 9c167ec..0000000 Binary files "a/source/images/\346\234\272\345\231\250\345\255\246\344\271\240\345\244\215\344\271\240\350\265\204\346\226\231/Pasted image 20250426204559.webp" and /dev/null differ diff --git "a/source/images/\346\234\272\345\231\250\345\255\246\344\271\240\345\244\215\344\271\240\350\265\204\346\226\231/Pasted image 20250426210330.webp" "b/source/images/\346\234\272\345\231\250\345\255\246\344\271\240\345\244\215\344\271\240\350\265\204\346\226\231/Pasted image 20250426210330.webp" deleted file mode 100644 index 5bd4ff6..0000000 Binary files "a/source/images/\346\234\272\345\231\250\345\255\246\344\271\240\345\244\215\344\271\240\350\265\204\346\226\231/Pasted image 20250426210330.webp" and /dev/null differ diff --git "a/source/images/\346\234\272\345\231\250\345\255\246\344\271\240\345\244\215\344\271\240\350\265\204\346\226\231/Pasted image 20250427134510.webp" "b/source/images/\346\234\272\345\231\250\345\255\246\344\271\240\345\244\215\344\271\240\350\265\204\346\226\231/Pasted image 20250427134510.webp" deleted file mode 100644 index b6ab6dc..0000000 Binary files "a/source/images/\346\234\272\345\231\250\345\255\246\344\271\240\345\244\215\344\271\240\350\265\204\346\226\231/Pasted image 20250427134510.webp" and /dev/null differ diff --git "a/source/images/\346\234\272\345\231\250\345\255\246\344\271\240\345\244\215\344\271\240\350\265\204\346\226\231/Pasted image 20250427135009.webp" "b/source/images/\346\234\272\345\231\250\345\255\246\344\271\240\345\244\215\344\271\240\350\265\204\346\226\231/Pasted image 20250427135009.webp" deleted file mode 100644 index 738b51e..0000000 Binary files "a/source/images/\346\234\272\345\231\250\345\255\246\344\271\240\345\244\215\344\271\240\350\265\204\346\226\231/Pasted image 20250427135009.webp" and /dev/null differ diff --git "a/source/images/\346\234\272\345\231\250\345\255\246\344\271\240\345\244\215\344\271\240\350\265\204\346\226\231/Pasted image 20250427135943.webp" "b/source/images/\346\234\272\345\231\250\345\255\246\344\271\240\345\244\215\344\271\240\350\265\204\346\226\231/Pasted image 20250427135943.webp" deleted file mode 100644 index 994c494..0000000 Binary files "a/source/images/\346\234\272\345\231\250\345\255\246\344\271\240\345\244\215\344\271\240\350\265\204\346\226\231/Pasted image 20250427135943.webp" and /dev/null differ diff --git "a/source/images/\346\234\272\345\231\250\345\255\246\344\271\240\345\244\215\344\271\240\350\265\204\346\226\231/Pasted image 20250427143630.webp" "b/source/images/\346\234\272\345\231\250\345\255\246\344\271\240\345\244\215\344\271\240\350\265\204\346\226\231/Pasted image 20250427143630.webp" deleted file mode 100644 index 122d4e2..0000000 Binary files "a/source/images/\346\234\272\345\231\250\345\255\246\344\271\240\345\244\215\344\271\240\350\265\204\346\226\231/Pasted image 20250427143630.webp" and /dev/null differ diff --git "a/source/images/\346\234\272\345\231\250\345\255\246\344\271\240\345\244\215\344\271\240\350\265\204\346\226\231/Pasted image 20250427143846.webp" "b/source/images/\346\234\272\345\231\250\345\255\246\344\271\240\345\244\215\344\271\240\350\265\204\346\226\231/Pasted image 20250427143846.webp" deleted file mode 100644 index ab9a1f3..0000000 Binary files "a/source/images/\346\234\272\345\231\250\345\255\246\344\271\240\345\244\215\344\271\240\350\265\204\346\226\231/Pasted image 20250427143846.webp" and /dev/null differ diff --git "a/source/images/\346\234\272\345\231\250\345\255\246\344\271\240\345\244\215\344\271\240\350\265\204\346\226\231/Pasted image 20250427144027.webp" "b/source/images/\346\234\272\345\231\250\345\255\246\344\271\240\345\244\215\344\271\240\350\265\204\346\226\231/Pasted image 20250427144027.webp" deleted file mode 100644 index 0df68de..0000000 Binary files "a/source/images/\346\234\272\345\231\250\345\255\246\344\271\240\345\244\215\344\271\240\350\265\204\346\226\231/Pasted image 20250427144027.webp" and /dev/null differ diff --git "a/source/images/\346\234\272\345\231\250\345\255\246\344\271\240\345\244\215\344\271\240\350\265\204\346\226\231/Pasted image 20250427144249.webp" "b/source/images/\346\234\272\345\231\250\345\255\246\344\271\240\345\244\215\344\271\240\350\265\204\346\226\231/Pasted image 20250427144249.webp" deleted file mode 100644 index 61550f9..0000000 Binary files "a/source/images/\346\234\272\345\231\250\345\255\246\344\271\240\345\244\215\344\271\240\350\265\204\346\226\231/Pasted image 20250427144249.webp" and /dev/null differ diff --git "a/source/images/\346\234\272\345\231\250\345\255\246\344\271\240\345\244\215\344\271\240\350\265\204\346\226\231/Pasted image 20250427150521.webp" "b/source/images/\346\234\272\345\231\250\345\255\246\344\271\240\345\244\215\344\271\240\350\265\204\346\226\231/Pasted image 20250427150521.webp" deleted file mode 100644 index 0395551..0000000 Binary files "a/source/images/\346\234\272\345\231\250\345\255\246\344\271\240\345\244\215\344\271\240\350\265\204\346\226\231/Pasted image 20250427150521.webp" and /dev/null differ diff --git "a/source/images/\346\234\272\345\231\250\345\255\246\344\271\240\345\244\215\344\271\240\350\265\204\346\226\231/Pasted image 20250427150618.webp" "b/source/images/\346\234\272\345\231\250\345\255\246\344\271\240\345\244\215\344\271\240\350\265\204\346\226\231/Pasted image 20250427150618.webp" deleted file mode 100644 index c6b7bff..0000000 Binary files "a/source/images/\346\234\272\345\231\250\345\255\246\344\271\240\345\244\215\344\271\240\350\265\204\346\226\231/Pasted image 20250427150618.webp" and /dev/null differ diff --git "a/source/images/\346\234\272\345\231\250\345\255\246\344\271\240\345\244\215\344\271\240\350\265\204\346\226\231/Pasted image 20250427150622.webp" "b/source/images/\346\234\272\345\231\250\345\255\246\344\271\240\345\244\215\344\271\240\350\265\204\346\226\231/Pasted image 20250427150622.webp" deleted file mode 100644 index ca4d4b1..0000000 Binary files "a/source/images/\346\234\272\345\231\250\345\255\246\344\271\240\345\244\215\344\271\240\350\265\204\346\226\231/Pasted image 20250427150622.webp" and /dev/null differ diff --git "a/source/images/\346\234\272\345\231\250\345\255\246\344\271\240\345\244\215\344\271\240\350\265\204\346\226\231/Pasted image 20250427150640.webp" "b/source/images/\346\234\272\345\231\250\345\255\246\344\271\240\345\244\215\344\271\240\350\265\204\346\226\231/Pasted image 20250427150640.webp" deleted file mode 100644 index 15ea7e9..0000000 Binary files "a/source/images/\346\234\272\345\231\250\345\255\246\344\271\240\345\244\215\344\271\240\350\265\204\346\226\231/Pasted image 20250427150640.webp" and /dev/null differ diff --git "a/source/images/\346\234\272\345\231\250\345\255\246\344\271\240\345\244\215\344\271\240\350\265\204\346\226\231/Pasted image 20250427151700.webp" "b/source/images/\346\234\272\345\231\250\345\255\246\344\271\240\345\244\215\344\271\240\350\265\204\346\226\231/Pasted image 20250427151700.webp" deleted file mode 100644 index 3a19784..0000000 Binary files "a/source/images/\346\234\272\345\231\250\345\255\246\344\271\240\345\244\215\344\271\240\350\265\204\346\226\231/Pasted image 20250427151700.webp" and /dev/null differ diff --git "a/source/images/\346\234\272\345\231\250\345\255\246\344\271\240\345\244\215\344\271\240\350\265\204\346\226\231/Pasted image 20250427151810.webp" "b/source/images/\346\234\272\345\231\250\345\255\246\344\271\240\345\244\215\344\271\240\350\265\204\346\226\231/Pasted image 20250427151810.webp" deleted file mode 100644 index 824bbd4..0000000 Binary files "a/source/images/\346\234\272\345\231\250\345\255\246\344\271\240\345\244\215\344\271\240\350\265\204\346\226\231/Pasted image 20250427151810.webp" and /dev/null differ diff --git "a/source/images/\346\234\272\345\231\250\345\255\246\344\271\240\345\244\215\344\271\240\350\265\204\346\226\231/Pasted image 20250427151819.webp" "b/source/images/\346\234\272\345\231\250\345\255\246\344\271\240\345\244\215\344\271\240\350\265\204\346\226\231/Pasted image 20250427151819.webp" deleted file mode 100644 index 0eb372c..0000000 Binary files "a/source/images/\346\234\272\345\231\250\345\255\246\344\271\240\345\244\215\344\271\240\350\265\204\346\226\231/Pasted image 20250427151819.webp" and /dev/null differ diff --git "a/source/images/\346\234\272\345\231\250\345\255\246\344\271\240\345\244\215\344\271\240\350\265\204\346\226\231/Pasted image 20250427151909.webp" "b/source/images/\346\234\272\345\231\250\345\255\246\344\271\240\345\244\215\344\271\240\350\265\204\346\226\231/Pasted image 20250427151909.webp" deleted file mode 100644 index ec668ff..0000000 Binary files "a/source/images/\346\234\272\345\231\250\345\255\246\344\271\240\345\244\215\344\271\240\350\265\204\346\226\231/Pasted image 20250427151909.webp" and /dev/null differ diff --git "a/source/images/\346\234\272\345\231\250\345\255\246\344\271\240\345\244\215\344\271\240\350\265\204\346\226\231/Pasted image 20250427151924.webp" "b/source/images/\346\234\272\345\231\250\345\255\246\344\271\240\345\244\215\344\271\240\350\265\204\346\226\231/Pasted image 20250427151924.webp" deleted file mode 100644 index 3b88439..0000000 Binary files "a/source/images/\346\234\272\345\231\250\345\255\246\344\271\240\345\244\215\344\271\240\350\265\204\346\226\231/Pasted image 20250427151924.webp" and /dev/null differ diff --git "a/source/images/\346\234\272\345\231\250\345\255\246\344\271\240\345\244\215\344\271\240\350\265\204\346\226\231/Pasted image 20250427153537.webp" "b/source/images/\346\234\272\345\231\250\345\255\246\344\271\240\345\244\215\344\271\240\350\265\204\346\226\231/Pasted image 20250427153537.webp" deleted file mode 100644 index 30fafa7..0000000 Binary files "a/source/images/\346\234\272\345\231\250\345\255\246\344\271\240\345\244\215\344\271\240\350\265\204\346\226\231/Pasted image 20250427153537.webp" and /dev/null differ diff --git "a/source/images/\346\234\272\345\231\250\345\255\246\344\271\240\345\244\215\344\271\240\350\265\204\346\226\231/Pasted image 20250427153627.webp" "b/source/images/\346\234\272\345\231\250\345\255\246\344\271\240\345\244\215\344\271\240\350\265\204\346\226\231/Pasted image 20250427153627.webp" deleted file mode 100644 index a37030a..0000000 Binary files "a/source/images/\346\234\272\345\231\250\345\255\246\344\271\240\345\244\215\344\271\240\350\265\204\346\226\231/Pasted image 20250427153627.webp" and /dev/null differ diff --git "a/source/images/\346\234\272\345\231\250\345\255\246\344\271\240\345\244\215\344\271\240\350\265\204\346\226\231/Pasted image 20250427153635.webp" "b/source/images/\346\234\272\345\231\250\345\255\246\344\271\240\345\244\215\344\271\240\350\265\204\346\226\231/Pasted image 20250427153635.webp" deleted file mode 100644 index 4de50bf..0000000 Binary files "a/source/images/\346\234\272\345\231\250\345\255\246\344\271\240\345\244\215\344\271\240\350\265\204\346\226\231/Pasted image 20250427153635.webp" and /dev/null differ diff --git "a/source/images/\346\234\272\345\231\250\345\255\246\344\271\240\345\244\215\344\271\240\350\265\204\346\226\231/Pasted image 20250427153643.webp" "b/source/images/\346\234\272\345\231\250\345\255\246\344\271\240\345\244\215\344\271\240\350\265\204\346\226\231/Pasted image 20250427153643.webp" deleted file mode 100644 index b4fe7b6..0000000 Binary files "a/source/images/\346\234\272\345\231\250\345\255\246\344\271\240\345\244\215\344\271\240\350\265\204\346\226\231/Pasted image 20250427153643.webp" and /dev/null differ diff --git "a/source/images/\346\234\272\345\231\250\345\255\246\344\271\240\345\244\215\344\271\240\350\265\204\346\226\231/Pasted image 20250427153648.webp" "b/source/images/\346\234\272\345\231\250\345\255\246\344\271\240\345\244\215\344\271\240\350\265\204\346\226\231/Pasted image 20250427153648.webp" deleted file mode 100644 index 2bf35e5..0000000 Binary files "a/source/images/\346\234\272\345\231\250\345\255\246\344\271\240\345\244\215\344\271\240\350\265\204\346\226\231/Pasted image 20250427153648.webp" and /dev/null differ diff --git "a/source/images/\346\234\272\345\231\250\345\255\246\344\271\240\345\244\215\344\271\240\350\265\204\346\226\231/Pasted image 20250427153858.webp" "b/source/images/\346\234\272\345\231\250\345\255\246\344\271\240\345\244\215\344\271\240\350\265\204\346\226\231/Pasted image 20250427153858.webp" deleted file mode 100644 index 6b1bd90..0000000 Binary files "a/source/images/\346\234\272\345\231\250\345\255\246\344\271\240\345\244\215\344\271\240\350\265\204\346\226\231/Pasted image 20250427153858.webp" and /dev/null differ diff --git "a/source/images/\346\234\272\345\231\250\345\255\246\344\271\240\345\244\215\344\271\240\350\265\204\346\226\231/Pasted image 20250427153906.webp" "b/source/images/\346\234\272\345\231\250\345\255\246\344\271\240\345\244\215\344\271\240\350\265\204\346\226\231/Pasted image 20250427153906.webp" deleted file mode 100644 index 810fcc1..0000000 Binary files "a/source/images/\346\234\272\345\231\250\345\255\246\344\271\240\345\244\215\344\271\240\350\265\204\346\226\231/Pasted image 20250427153906.webp" and /dev/null differ diff --git "a/source/images/\346\234\272\345\231\250\345\255\246\344\271\240\345\244\215\344\271\240\350\265\204\346\226\231/Pasted image 20250427153933.webp" "b/source/images/\346\234\272\345\231\250\345\255\246\344\271\240\345\244\215\344\271\240\350\265\204\346\226\231/Pasted image 20250427153933.webp" deleted file mode 100644 index 531c1f0..0000000 Binary files "a/source/images/\346\234\272\345\231\250\345\255\246\344\271\240\345\244\215\344\271\240\350\265\204\346\226\231/Pasted image 20250427153933.webp" and /dev/null differ diff --git "a/source/images/\346\234\272\345\231\250\345\255\246\344\271\240\345\244\215\344\271\240\350\265\204\346\226\231/Pasted image 20250427153939.webp" "b/source/images/\346\234\272\345\231\250\345\255\246\344\271\240\345\244\215\344\271\240\350\265\204\346\226\231/Pasted image 20250427153939.webp" deleted file mode 100644 index 489e2c7..0000000 Binary files "a/source/images/\346\234\272\345\231\250\345\255\246\344\271\240\345\244\215\344\271\240\350\265\204\346\226\231/Pasted image 20250427153939.webp" and /dev/null differ diff --git "a/source/images/\346\234\272\345\231\250\345\255\246\344\271\240\345\244\215\344\271\240\350\265\204\346\226\231/Pasted image 20250427154142.webp" "b/source/images/\346\234\272\345\231\250\345\255\246\344\271\240\345\244\215\344\271\240\350\265\204\346\226\231/Pasted image 20250427154142.webp" deleted file mode 100644 index b34ec33..0000000 Binary files "a/source/images/\346\234\272\345\231\250\345\255\246\344\271\240\345\244\215\344\271\240\350\265\204\346\226\231/Pasted image 20250427154142.webp" and /dev/null differ diff --git "a/source/images/\346\234\272\345\231\250\345\255\246\344\271\240\345\244\215\344\271\240\350\265\204\346\226\231/Pasted image 20250427154804.webp" "b/source/images/\346\234\272\345\231\250\345\255\246\344\271\240\345\244\215\344\271\240\350\265\204\346\226\231/Pasted image 20250427154804.webp" deleted file mode 100644 index e0b8986..0000000 Binary files "a/source/images/\346\234\272\345\231\250\345\255\246\344\271\240\345\244\215\344\271\240\350\265\204\346\226\231/Pasted image 20250427154804.webp" and /dev/null differ diff --git "a/source/images/\346\234\272\345\231\250\345\255\246\344\271\240\345\244\215\344\271\240\350\265\204\346\226\231/Pasted image 20250427155504.webp" "b/source/images/\346\234\272\345\231\250\345\255\246\344\271\240\345\244\215\344\271\240\350\265\204\346\226\231/Pasted image 20250427155504.webp" deleted file mode 100644 index 9da07d4..0000000 Binary files "a/source/images/\346\234\272\345\231\250\345\255\246\344\271\240\345\244\215\344\271\240\350\265\204\346\226\231/Pasted image 20250427155504.webp" and /dev/null differ diff --git "a/source/images/\346\234\272\345\231\250\345\255\246\344\271\240\345\244\215\344\271\240\350\265\204\346\226\231/Pasted image 20250427155736.webp" "b/source/images/\346\234\272\345\231\250\345\255\246\344\271\240\345\244\215\344\271\240\350\265\204\346\226\231/Pasted image 20250427155736.webp" deleted file mode 100644 index bafdebe..0000000 Binary files "a/source/images/\346\234\272\345\231\250\345\255\246\344\271\240\345\244\215\344\271\240\350\265\204\346\226\231/Pasted image 20250427155736.webp" and /dev/null differ diff --git "a/source/images/\346\234\272\345\231\250\345\255\246\344\271\240\345\244\215\344\271\240\350\265\204\346\226\231/Pasted image 20250427160230.webp" "b/source/images/\346\234\272\345\231\250\345\255\246\344\271\240\345\244\215\344\271\240\350\265\204\346\226\231/Pasted image 20250427160230.webp" deleted file mode 100644 index 0db3f64..0000000 Binary files "a/source/images/\346\234\272\345\231\250\345\255\246\344\271\240\345\244\215\344\271\240\350\265\204\346\226\231/Pasted image 20250427160230.webp" and /dev/null differ diff --git "a/source/images/\346\234\272\345\231\250\345\255\246\344\271\240\345\244\215\344\271\240\350\265\204\346\226\231/Pasted image 20250427160408.webp" "b/source/images/\346\234\272\345\231\250\345\255\246\344\271\240\345\244\215\344\271\240\350\265\204\346\226\231/Pasted image 20250427160408.webp" deleted file mode 100644 index f7b5d65..0000000 Binary files "a/source/images/\346\234\272\345\231\250\345\255\246\344\271\240\345\244\215\344\271\240\350\265\204\346\226\231/Pasted image 20250427160408.webp" and /dev/null differ diff --git "a/source/images/\346\234\272\345\231\250\345\255\246\344\271\240\345\244\215\344\271\240\350\265\204\346\226\231/Pasted image 20250427160456.webp" "b/source/images/\346\234\272\345\231\250\345\255\246\344\271\240\345\244\215\344\271\240\350\265\204\346\226\231/Pasted image 20250427160456.webp" deleted file mode 100644 index 6fbcb9a..0000000 Binary files "a/source/images/\346\234\272\345\231\250\345\255\246\344\271\240\345\244\215\344\271\240\350\265\204\346\226\231/Pasted image 20250427160456.webp" and /dev/null differ diff --git "a/source/images/\346\234\272\345\231\250\345\255\246\344\271\240\345\244\215\344\271\240\350\265\204\346\226\231/Pasted image 20250427165900.webp" "b/source/images/\346\234\272\345\231\250\345\255\246\344\271\240\345\244\215\344\271\240\350\265\204\346\226\231/Pasted image 20250427165900.webp" deleted file mode 100644 index 7d8b71c..0000000 Binary files "a/source/images/\346\234\272\345\231\250\345\255\246\344\271\240\345\244\215\344\271\240\350\265\204\346\226\231/Pasted image 20250427165900.webp" and /dev/null differ diff --git "a/source/images/\346\234\272\345\231\250\345\255\246\344\271\240\345\244\215\344\271\240\350\265\204\346\226\231/Pasted image 20250427170031.webp" "b/source/images/\346\234\272\345\231\250\345\255\246\344\271\240\345\244\215\344\271\240\350\265\204\346\226\231/Pasted image 20250427170031.webp" deleted file mode 100644 index 8f8e467..0000000 Binary files "a/source/images/\346\234\272\345\231\250\345\255\246\344\271\240\345\244\215\344\271\240\350\265\204\346\226\231/Pasted image 20250427170031.webp" and /dev/null differ diff --git "a/source/images/\346\234\272\345\231\250\345\255\246\344\271\240\345\244\215\344\271\240\350\265\204\346\226\231/Pasted image 20250427170044.webp" "b/source/images/\346\234\272\345\231\250\345\255\246\344\271\240\345\244\215\344\271\240\350\265\204\346\226\231/Pasted image 20250427170044.webp" deleted file mode 100644 index 5e84f79..0000000 Binary files "a/source/images/\346\234\272\345\231\250\345\255\246\344\271\240\345\244\215\344\271\240\350\265\204\346\226\231/Pasted image 20250427170044.webp" and /dev/null differ diff --git "a/source/images/\346\234\272\345\231\250\345\255\246\344\271\240\345\244\215\344\271\240\350\265\204\346\226\231/Pasted image 20250427170137.webp" "b/source/images/\346\234\272\345\231\250\345\255\246\344\271\240\345\244\215\344\271\240\350\265\204\346\226\231/Pasted image 20250427170137.webp" deleted file mode 100644 index fa3048d..0000000 Binary files "a/source/images/\346\234\272\345\231\250\345\255\246\344\271\240\345\244\215\344\271\240\350\265\204\346\226\231/Pasted image 20250427170137.webp" and /dev/null differ diff --git "a/source/images/\346\234\272\345\231\250\345\255\246\344\271\240\345\244\215\344\271\240\350\265\204\346\226\231/Pasted image 20250427170239.webp" "b/source/images/\346\234\272\345\231\250\345\255\246\344\271\240\345\244\215\344\271\240\350\265\204\346\226\231/Pasted image 20250427170239.webp" deleted file mode 100644 index 869ff08..0000000 Binary files "a/source/images/\346\234\272\345\231\250\345\255\246\344\271\240\345\244\215\344\271\240\350\265\204\346\226\231/Pasted image 20250427170239.webp" and /dev/null differ diff --git "a/source/images/\346\234\272\345\231\250\345\255\246\344\271\240\345\244\215\344\271\240\350\265\204\346\226\231/Pasted image 20250427170247.webp" "b/source/images/\346\234\272\345\231\250\345\255\246\344\271\240\345\244\215\344\271\240\350\265\204\346\226\231/Pasted image 20250427170247.webp" deleted file mode 100644 index 092d5b9..0000000 Binary files "a/source/images/\346\234\272\345\231\250\345\255\246\344\271\240\345\244\215\344\271\240\350\265\204\346\226\231/Pasted image 20250427170247.webp" and /dev/null differ diff --git "a/source/images/\346\234\272\345\231\250\345\255\246\344\271\240\345\244\215\344\271\240\350\265\204\346\226\231/Pasted image 20250427170404.webp" "b/source/images/\346\234\272\345\231\250\345\255\246\344\271\240\345\244\215\344\271\240\350\265\204\346\226\231/Pasted image 20250427170404.webp" deleted file mode 100644 index 8ee7230..0000000 Binary files "a/source/images/\346\234\272\345\231\250\345\255\246\344\271\240\345\244\215\344\271\240\350\265\204\346\226\231/Pasted image 20250427170404.webp" and /dev/null differ diff --git "a/source/images/\346\234\272\345\231\250\345\255\246\344\271\240\345\244\215\344\271\240\350\265\204\346\226\231/Pasted image 20250427170411.webp" "b/source/images/\346\234\272\345\231\250\345\255\246\344\271\240\345\244\215\344\271\240\350\265\204\346\226\231/Pasted image 20250427170411.webp" deleted file mode 100644 index ed7a7b6..0000000 Binary files "a/source/images/\346\234\272\345\231\250\345\255\246\344\271\240\345\244\215\344\271\240\350\265\204\346\226\231/Pasted image 20250427170411.webp" and /dev/null differ diff --git "a/source/images/\346\234\272\345\231\250\345\255\246\344\271\240\345\244\215\344\271\240\350\265\204\346\226\231/Pasted image 20250427170436.webp" "b/source/images/\346\234\272\345\231\250\345\255\246\344\271\240\345\244\215\344\271\240\350\265\204\346\226\231/Pasted image 20250427170436.webp" deleted file mode 100644 index cd84635..0000000 Binary files "a/source/images/\346\234\272\345\231\250\345\255\246\344\271\240\345\244\215\344\271\240\350\265\204\346\226\231/Pasted image 20250427170436.webp" and /dev/null differ diff --git "a/source/images/\346\234\272\345\231\250\345\255\246\344\271\240\345\244\215\344\271\240\350\265\204\346\226\231/Pasted image 20250427170505.webp" "b/source/images/\346\234\272\345\231\250\345\255\246\344\271\240\345\244\215\344\271\240\350\265\204\346\226\231/Pasted image 20250427170505.webp" deleted file mode 100644 index 243a8da..0000000 Binary files "a/source/images/\346\234\272\345\231\250\345\255\246\344\271\240\345\244\215\344\271\240\350\265\204\346\226\231/Pasted image 20250427170505.webp" and /dev/null differ diff --git "a/source/images/\346\234\272\345\231\250\345\255\246\344\271\240\345\244\215\344\271\240\350\265\204\346\226\231/Pasted image 20250427170554.webp" "b/source/images/\346\234\272\345\231\250\345\255\246\344\271\240\345\244\215\344\271\240\350\265\204\346\226\231/Pasted image 20250427170554.webp" deleted file mode 100644 index bc3ec56..0000000 Binary files "a/source/images/\346\234\272\345\231\250\345\255\246\344\271\240\345\244\215\344\271\240\350\265\204\346\226\231/Pasted image 20250427170554.webp" and /dev/null differ diff --git "a/source/images/\346\234\272\345\231\250\345\255\246\344\271\240\345\244\215\344\271\240\350\265\204\346\226\231/Pasted image 20250427173617.webp" "b/source/images/\346\234\272\345\231\250\345\255\246\344\271\240\345\244\215\344\271\240\350\265\204\346\226\231/Pasted image 20250427173617.webp" deleted file mode 100644 index 58a097a..0000000 Binary files "a/source/images/\346\234\272\345\231\250\345\255\246\344\271\240\345\244\215\344\271\240\350\265\204\346\226\231/Pasted image 20250427173617.webp" and /dev/null differ diff --git "a/source/images/\346\234\272\345\231\250\345\255\246\344\271\240\345\244\215\344\271\240\350\265\204\346\226\231/Pasted image 20250427174444.webp" "b/source/images/\346\234\272\345\231\250\345\255\246\344\271\240\345\244\215\344\271\240\350\265\204\346\226\231/Pasted image 20250427174444.webp" deleted file mode 100644 index c8b0224..0000000 Binary files "a/source/images/\346\234\272\345\231\250\345\255\246\344\271\240\345\244\215\344\271\240\350\265\204\346\226\231/Pasted image 20250427174444.webp" and /dev/null differ diff --git "a/source/images/\346\234\272\345\231\250\345\255\246\344\271\240\345\244\215\344\271\240\350\265\204\346\226\231/Pasted image 20250427174902.webp" "b/source/images/\346\234\272\345\231\250\345\255\246\344\271\240\345\244\215\344\271\240\350\265\204\346\226\231/Pasted image 20250427174902.webp" deleted file mode 100644 index a75e3fa..0000000 Binary files "a/source/images/\346\234\272\345\231\250\345\255\246\344\271\240\345\244\215\344\271\240\350\265\204\346\226\231/Pasted image 20250427174902.webp" and /dev/null differ diff --git "a/source/images/\346\234\272\345\231\250\345\255\246\344\271\240\345\244\215\344\271\240\350\265\204\346\226\231/Pasted image 20250427174911.webp" "b/source/images/\346\234\272\345\231\250\345\255\246\344\271\240\345\244\215\344\271\240\350\265\204\346\226\231/Pasted image 20250427174911.webp" deleted file mode 100644 index bdd6cb0..0000000 Binary files "a/source/images/\346\234\272\345\231\250\345\255\246\344\271\240\345\244\215\344\271\240\350\265\204\346\226\231/Pasted image 20250427174911.webp" and /dev/null differ diff --git "a/source/images/\346\234\272\345\231\250\345\255\246\344\271\240\345\244\215\344\271\240\350\265\204\346\226\231/Pasted image 20250427175204.webp" "b/source/images/\346\234\272\345\231\250\345\255\246\344\271\240\345\244\215\344\271\240\350\265\204\346\226\231/Pasted image 20250427175204.webp" deleted file mode 100644 index 1ae5299..0000000 Binary files "a/source/images/\346\234\272\345\231\250\345\255\246\344\271\240\345\244\215\344\271\240\350\265\204\346\226\231/Pasted image 20250427175204.webp" and /dev/null differ diff --git "a/source/images/\346\234\272\345\231\250\345\255\246\344\271\240\345\244\215\344\271\240\350\265\204\346\226\231/Pasted image 20250427175209.webp" "b/source/images/\346\234\272\345\231\250\345\255\246\344\271\240\345\244\215\344\271\240\350\265\204\346\226\231/Pasted image 20250427175209.webp" deleted file mode 100644 index 46a625a..0000000 Binary files "a/source/images/\346\234\272\345\231\250\345\255\246\344\271\240\345\244\215\344\271\240\350\265\204\346\226\231/Pasted image 20250427175209.webp" and /dev/null differ diff --git "a/source/images/\346\234\272\345\231\250\345\255\246\344\271\240\345\244\215\344\271\240\350\265\204\346\226\231/Pasted image 20250427175215.webp" "b/source/images/\346\234\272\345\231\250\345\255\246\344\271\240\345\244\215\344\271\240\350\265\204\346\226\231/Pasted image 20250427175215.webp" deleted file mode 100644 index 6ec4318..0000000 Binary files "a/source/images/\346\234\272\345\231\250\345\255\246\344\271\240\345\244\215\344\271\240\350\265\204\346\226\231/Pasted image 20250427175215.webp" and /dev/null differ diff --git "a/source/images/\346\234\272\345\231\250\345\255\246\344\271\240\345\244\215\344\271\240\350\265\204\346\226\231/Pasted image 20250427175312.webp" "b/source/images/\346\234\272\345\231\250\345\255\246\344\271\240\345\244\215\344\271\240\350\265\204\346\226\231/Pasted image 20250427175312.webp" deleted file mode 100644 index 52106bd..0000000 Binary files "a/source/images/\346\234\272\345\231\250\345\255\246\344\271\240\345\244\215\344\271\240\350\265\204\346\226\231/Pasted image 20250427175312.webp" and /dev/null differ diff --git "a/source/images/\346\234\272\345\231\250\345\255\246\344\271\240\345\244\215\344\271\240\350\265\204\346\226\231/Pasted image 20250427175359.webp" "b/source/images/\346\234\272\345\231\250\345\255\246\344\271\240\345\244\215\344\271\240\350\265\204\346\226\231/Pasted image 20250427175359.webp" deleted file mode 100644 index daaadba..0000000 Binary files "a/source/images/\346\234\272\345\231\250\345\255\246\344\271\240\345\244\215\344\271\240\350\265\204\346\226\231/Pasted image 20250427175359.webp" and /dev/null differ diff --git "a/source/images/\346\234\272\345\231\250\345\255\246\344\271\240\345\244\215\344\271\240\350\265\204\346\226\231/Pasted image 20250427180153.webp" "b/source/images/\346\234\272\345\231\250\345\255\246\344\271\240\345\244\215\344\271\240\350\265\204\346\226\231/Pasted image 20250427180153.webp" deleted file mode 100644 index ae153a5..0000000 Binary files "a/source/images/\346\234\272\345\231\250\345\255\246\344\271\240\345\244\215\344\271\240\350\265\204\346\226\231/Pasted image 20250427180153.webp" and /dev/null differ diff --git "a/source/images/\346\234\272\345\231\250\345\255\246\344\271\240\345\244\215\344\271\240\350\265\204\346\226\231/Pasted image 20250427180223.webp" "b/source/images/\346\234\272\345\231\250\345\255\246\344\271\240\345\244\215\344\271\240\350\265\204\346\226\231/Pasted image 20250427180223.webp" deleted file mode 100644 index 207a7d8..0000000 Binary files "a/source/images/\346\234\272\345\231\250\345\255\246\344\271\240\345\244\215\344\271\240\350\265\204\346\226\231/Pasted image 20250427180223.webp" and /dev/null differ diff --git "a/source/images/\346\234\272\345\231\250\345\255\246\344\271\240\345\244\215\344\271\240\350\265\204\346\226\231/Pasted image 20250427180236.webp" "b/source/images/\346\234\272\345\231\250\345\255\246\344\271\240\345\244\215\344\271\240\350\265\204\346\226\231/Pasted image 20250427180236.webp" deleted file mode 100644 index e862188..0000000 Binary files "a/source/images/\346\234\272\345\231\250\345\255\246\344\271\240\345\244\215\344\271\240\350\265\204\346\226\231/Pasted image 20250427180236.webp" and /dev/null differ diff --git "a/source/images/\346\234\272\345\231\250\345\255\246\344\271\240\345\244\215\344\271\240\350\265\204\346\226\231/Pasted image 20250427180243.webp" "b/source/images/\346\234\272\345\231\250\345\255\246\344\271\240\345\244\215\344\271\240\350\265\204\346\226\231/Pasted image 20250427180243.webp" deleted file mode 100644 index 0dc516b..0000000 Binary files "a/source/images/\346\234\272\345\231\250\345\255\246\344\271\240\345\244\215\344\271\240\350\265\204\346\226\231/Pasted image 20250427180243.webp" and /dev/null differ diff --git "a/source/images/\346\234\272\345\231\250\345\255\246\344\271\240\345\244\215\344\271\240\350\265\204\346\226\231/Pasted image 20250427180250.webp" "b/source/images/\346\234\272\345\231\250\345\255\246\344\271\240\345\244\215\344\271\240\350\265\204\346\226\231/Pasted image 20250427180250.webp" deleted file mode 100644 index 38e7643..0000000 Binary files "a/source/images/\346\234\272\345\231\250\345\255\246\344\271\240\345\244\215\344\271\240\350\265\204\346\226\231/Pasted image 20250427180250.webp" and /dev/null differ diff --git "a/source/images/\346\234\272\345\231\250\345\255\246\344\271\240\345\244\215\344\271\240\350\265\204\346\226\231/Pasted image 20250427180259.webp" "b/source/images/\346\234\272\345\231\250\345\255\246\344\271\240\345\244\215\344\271\240\350\265\204\346\226\231/Pasted image 20250427180259.webp" deleted file mode 100644 index 5278683..0000000 Binary files "a/source/images/\346\234\272\345\231\250\345\255\246\344\271\240\345\244\215\344\271\240\350\265\204\346\226\231/Pasted image 20250427180259.webp" and /dev/null differ diff --git "a/source/images/\346\234\272\345\231\250\345\255\246\344\271\240\345\244\215\344\271\240\350\265\204\346\226\231/Pasted image 20250427180306.webp" "b/source/images/\346\234\272\345\231\250\345\255\246\344\271\240\345\244\215\344\271\240\350\265\204\346\226\231/Pasted image 20250427180306.webp" deleted file mode 100644 index 0a21439..0000000 Binary files "a/source/images/\346\234\272\345\231\250\345\255\246\344\271\240\345\244\215\344\271\240\350\265\204\346\226\231/Pasted image 20250427180306.webp" and /dev/null differ diff --git "a/source/images/\346\234\272\345\231\250\345\255\246\344\271\240\345\244\215\344\271\240\350\265\204\346\226\231/Pasted image 20250427191301.webp" "b/source/images/\346\234\272\345\231\250\345\255\246\344\271\240\345\244\215\344\271\240\350\265\204\346\226\231/Pasted image 20250427191301.webp" deleted file mode 100644 index 1083d5f..0000000 Binary files "a/source/images/\346\234\272\345\231\250\345\255\246\344\271\240\345\244\215\344\271\240\350\265\204\346\226\231/Pasted image 20250427191301.webp" and /dev/null differ diff --git "a/source/images/\346\234\272\345\231\250\345\255\246\344\271\240\345\244\215\344\271\240\350\265\204\346\226\231/Pasted image 20250427191307.webp" "b/source/images/\346\234\272\345\231\250\345\255\246\344\271\240\345\244\215\344\271\240\350\265\204\346\226\231/Pasted image 20250427191307.webp" deleted file mode 100644 index fed7dbf..0000000 Binary files "a/source/images/\346\234\272\345\231\250\345\255\246\344\271\240\345\244\215\344\271\240\350\265\204\346\226\231/Pasted image 20250427191307.webp" and /dev/null differ diff --git "a/source/images/\346\234\272\345\231\250\345\255\246\344\271\240\345\244\215\344\271\240\350\265\204\346\226\231/Pasted image 20250427191343.webp" "b/source/images/\346\234\272\345\231\250\345\255\246\344\271\240\345\244\215\344\271\240\350\265\204\346\226\231/Pasted image 20250427191343.webp" deleted file mode 100644 index d461bfa..0000000 Binary files "a/source/images/\346\234\272\345\231\250\345\255\246\344\271\240\345\244\215\344\271\240\350\265\204\346\226\231/Pasted image 20250427191343.webp" and /dev/null differ diff --git "a/source/images/\346\234\272\345\231\250\345\255\246\344\271\240\345\244\215\344\271\240\350\265\204\346\226\231/Pasted image 20250427191349.webp" "b/source/images/\346\234\272\345\231\250\345\255\246\344\271\240\345\244\215\344\271\240\350\265\204\346\226\231/Pasted image 20250427191349.webp" deleted file mode 100644 index 6a78ec5..0000000 Binary files "a/source/images/\346\234\272\345\231\250\345\255\246\344\271\240\345\244\215\344\271\240\350\265\204\346\226\231/Pasted image 20250427191349.webp" and /dev/null differ diff --git "a/source/images/\346\234\272\345\231\250\345\255\246\344\271\240\345\244\215\344\271\240\350\265\204\346\226\231/Pasted image 20250427191930.webp" "b/source/images/\346\234\272\345\231\250\345\255\246\344\271\240\345\244\215\344\271\240\350\265\204\346\226\231/Pasted image 20250427191930.webp" deleted file mode 100644 index 83f21b0..0000000 Binary files "a/source/images/\346\234\272\345\231\250\345\255\246\344\271\240\345\244\215\344\271\240\350\265\204\346\226\231/Pasted image 20250427191930.webp" and /dev/null differ diff --git "a/source/images/\346\234\272\345\231\250\345\255\246\344\271\240\345\244\215\344\271\240\350\265\204\346\226\231/Pasted image 20250427193810.webp" "b/source/images/\346\234\272\345\231\250\345\255\246\344\271\240\345\244\215\344\271\240\350\265\204\346\226\231/Pasted image 20250427193810.webp" deleted file mode 100644 index e6d183b..0000000 Binary files "a/source/images/\346\234\272\345\231\250\345\255\246\344\271\240\345\244\215\344\271\240\350\265\204\346\226\231/Pasted image 20250427193810.webp" and /dev/null differ diff --git "a/source/images/\346\234\272\345\231\250\345\255\246\344\271\240\345\244\215\344\271\240\350\265\204\346\226\231/Pasted image 20250427193836.webp" "b/source/images/\346\234\272\345\231\250\345\255\246\344\271\240\345\244\215\344\271\240\350\265\204\346\226\231/Pasted image 20250427193836.webp" deleted file mode 100644 index bc4d7d4..0000000 Binary files "a/source/images/\346\234\272\345\231\250\345\255\246\344\271\240\345\244\215\344\271\240\350\265\204\346\226\231/Pasted image 20250427193836.webp" and /dev/null differ diff --git "a/source/images/\346\234\272\345\231\250\345\255\246\344\271\240\345\244\215\344\271\240\350\265\204\346\226\231/Pasted image 20250427203930.webp" "b/source/images/\346\234\272\345\231\250\345\255\246\344\271\240\345\244\215\344\271\240\350\265\204\346\226\231/Pasted image 20250427203930.webp" deleted file mode 100644 index 598881f..0000000 Binary files "a/source/images/\346\234\272\345\231\250\345\255\246\344\271\240\345\244\215\344\271\240\350\265\204\346\226\231/Pasted image 20250427203930.webp" and /dev/null differ diff --git "a/source/images/\346\234\272\345\231\250\345\255\246\344\271\240\345\244\215\344\271\240\350\265\204\346\226\231/Pasted image 20250427204015.webp" "b/source/images/\346\234\272\345\231\250\345\255\246\344\271\240\345\244\215\344\271\240\350\265\204\346\226\231/Pasted image 20250427204015.webp" deleted file mode 100644 index 287fe6c..0000000 Binary files "a/source/images/\346\234\272\345\231\250\345\255\246\344\271\240\345\244\215\344\271\240\350\265\204\346\226\231/Pasted image 20250427204015.webp" and /dev/null differ diff --git "a/source/images/\346\234\272\345\231\250\345\255\246\344\271\240\345\244\215\344\271\240\350\265\204\346\226\231/Pasted image 20250427211113.webp" "b/source/images/\346\234\272\345\231\250\345\255\246\344\271\240\345\244\215\344\271\240\350\265\204\346\226\231/Pasted image 20250427211113.webp" deleted file mode 100644 index e1494eb..0000000 Binary files "a/source/images/\346\234\272\345\231\250\345\255\246\344\271\240\345\244\215\344\271\240\350\265\204\346\226\231/Pasted image 20250427211113.webp" and /dev/null differ diff --git "a/source/images/\346\234\272\345\231\250\345\255\246\344\271\240\345\244\215\344\271\240\350\265\204\346\226\231/Pasted image 20250427211120.webp" "b/source/images/\346\234\272\345\231\250\345\255\246\344\271\240\345\244\215\344\271\240\350\265\204\346\226\231/Pasted image 20250427211120.webp" deleted file mode 100644 index b8548d4..0000000 Binary files "a/source/images/\346\234\272\345\231\250\345\255\246\344\271\240\345\244\215\344\271\240\350\265\204\346\226\231/Pasted image 20250427211120.webp" and /dev/null differ diff --git "a/source/images/\346\234\272\345\231\250\345\255\246\344\271\240\345\244\215\344\271\240\350\265\204\346\226\231/Pasted image 20250427211125.webp" "b/source/images/\346\234\272\345\231\250\345\255\246\344\271\240\345\244\215\344\271\240\350\265\204\346\226\231/Pasted image 20250427211125.webp" deleted file mode 100644 index fa45705..0000000 Binary files "a/source/images/\346\234\272\345\231\250\345\255\246\344\271\240\345\244\215\344\271\240\350\265\204\346\226\231/Pasted image 20250427211125.webp" and /dev/null differ diff --git "a/source/images/\346\234\272\345\231\250\345\255\246\344\271\240\345\244\215\344\271\240\350\265\204\346\226\231/Pasted image 20250427211142.webp" "b/source/images/\346\234\272\345\231\250\345\255\246\344\271\240\345\244\215\344\271\240\350\265\204\346\226\231/Pasted image 20250427211142.webp" deleted file mode 100644 index 385ad46..0000000 Binary files "a/source/images/\346\234\272\345\231\250\345\255\246\344\271\240\345\244\215\344\271\240\350\265\204\346\226\231/Pasted image 20250427211142.webp" and /dev/null differ diff --git "a/source/images/\346\234\272\345\231\250\345\255\246\344\271\240\345\244\215\344\271\240\350\265\204\346\226\231/Pasted image 20250427211244.webp" "b/source/images/\346\234\272\345\231\250\345\255\246\344\271\240\345\244\215\344\271\240\350\265\204\346\226\231/Pasted image 20250427211244.webp" deleted file mode 100644 index 71c9fd3..0000000 Binary files "a/source/images/\346\234\272\345\231\250\345\255\246\344\271\240\345\244\215\344\271\240\350\265\204\346\226\231/Pasted image 20250427211244.webp" and /dev/null differ diff --git "a/source/images/\346\234\272\345\231\250\345\255\246\344\271\240\345\244\215\344\271\240\350\265\204\346\226\231/Pasted image 20250427211305.webp" "b/source/images/\346\234\272\345\231\250\345\255\246\344\271\240\345\244\215\344\271\240\350\265\204\346\226\231/Pasted image 20250427211305.webp" deleted file mode 100644 index 9ca0618..0000000 Binary files "a/source/images/\346\234\272\345\231\250\345\255\246\344\271\240\345\244\215\344\271\240\350\265\204\346\226\231/Pasted image 20250427211305.webp" and /dev/null differ diff --git "a/source/images/\346\234\272\345\231\250\345\255\246\344\271\240\345\244\215\344\271\240\350\265\204\346\226\231/Pasted image 20250427211353.webp" "b/source/images/\346\234\272\345\231\250\345\255\246\344\271\240\345\244\215\344\271\240\350\265\204\346\226\231/Pasted image 20250427211353.webp" deleted file mode 100644 index 23e2bb4..0000000 Binary files "a/source/images/\346\234\272\345\231\250\345\255\246\344\271\240\345\244\215\344\271\240\350\265\204\346\226\231/Pasted image 20250427211353.webp" and /dev/null differ diff --git "a/source/images/\346\234\272\345\231\250\345\255\246\344\271\240\345\244\215\344\271\240\350\265\204\346\226\231/Pasted image 20250427212939.webp" "b/source/images/\346\234\272\345\231\250\345\255\246\344\271\240\345\244\215\344\271\240\350\265\204\346\226\231/Pasted image 20250427212939.webp" deleted file mode 100644 index efd0bce..0000000 Binary files "a/source/images/\346\234\272\345\231\250\345\255\246\344\271\240\345\244\215\344\271\240\350\265\204\346\226\231/Pasted image 20250427212939.webp" and /dev/null differ diff --git "a/source/images/\346\234\272\345\231\250\345\255\246\344\271\240\345\244\215\344\271\240\350\265\204\346\226\231/Pasted image 20250427213023.webp" "b/source/images/\346\234\272\345\231\250\345\255\246\344\271\240\345\244\215\344\271\240\350\265\204\346\226\231/Pasted image 20250427213023.webp" deleted file mode 100644 index 7b0d9e7..0000000 Binary files "a/source/images/\346\234\272\345\231\250\345\255\246\344\271\240\345\244\215\344\271\240\350\265\204\346\226\231/Pasted image 20250427213023.webp" and /dev/null differ diff --git "a/source/images/\346\234\272\345\231\250\345\255\246\344\271\240\345\244\215\344\271\240\350\265\204\346\226\231/Pasted image 20250427213046.webp" "b/source/images/\346\234\272\345\231\250\345\255\246\344\271\240\345\244\215\344\271\240\350\265\204\346\226\231/Pasted image 20250427213046.webp" deleted file mode 100644 index 7ff6927..0000000 Binary files "a/source/images/\346\234\272\345\231\250\345\255\246\344\271\240\345\244\215\344\271\240\350\265\204\346\226\231/Pasted image 20250427213046.webp" and /dev/null differ diff --git "a/source/images/\346\234\272\345\231\250\345\255\246\344\271\240\345\244\215\344\271\240\350\265\204\346\226\231/Pasted image 20250427213052.webp" "b/source/images/\346\234\272\345\231\250\345\255\246\344\271\240\345\244\215\344\271\240\350\265\204\346\226\231/Pasted image 20250427213052.webp" deleted file mode 100644 index ef5433b..0000000 Binary files "a/source/images/\346\234\272\345\231\250\345\255\246\344\271\240\345\244\215\344\271\240\350\265\204\346\226\231/Pasted image 20250427213052.webp" and /dev/null differ diff --git "a/source/images/\346\234\272\345\231\250\345\255\246\344\271\240\345\244\215\344\271\240\350\265\204\346\226\231/Pasted image 20250427213058.webp" "b/source/images/\346\234\272\345\231\250\345\255\246\344\271\240\345\244\215\344\271\240\350\265\204\346\226\231/Pasted image 20250427213058.webp" deleted file mode 100644 index e8cf5ca..0000000 Binary files "a/source/images/\346\234\272\345\231\250\345\255\246\344\271\240\345\244\215\344\271\240\350\265\204\346\226\231/Pasted image 20250427213058.webp" and /dev/null differ diff --git "a/source/images/\346\234\272\345\231\250\345\255\246\344\271\240\345\244\215\344\271\240\350\265\204\346\226\231/Pasted image 20250427213347.webp" "b/source/images/\346\234\272\345\231\250\345\255\246\344\271\240\345\244\215\344\271\240\350\265\204\346\226\231/Pasted image 20250427213347.webp" deleted file mode 100644 index 3df447d..0000000 Binary files "a/source/images/\346\234\272\345\231\250\345\255\246\344\271\240\345\244\215\344\271\240\350\265\204\346\226\231/Pasted image 20250427213347.webp" and /dev/null differ diff --git "a/source/images/\346\234\272\345\231\250\345\255\246\344\271\240\345\244\215\344\271\240\350\265\204\346\226\231/Pasted image 20250427213352.webp" "b/source/images/\346\234\272\345\231\250\345\255\246\344\271\240\345\244\215\344\271\240\350\265\204\346\226\231/Pasted image 20250427213352.webp" deleted file mode 100644 index e9b8cf6..0000000 Binary files "a/source/images/\346\234\272\345\231\250\345\255\246\344\271\240\345\244\215\344\271\240\350\265\204\346\226\231/Pasted image 20250427213352.webp" and /dev/null differ diff --git "a/source/images/\346\234\272\345\231\250\345\255\246\344\271\240\345\244\215\344\271\240\350\265\204\346\226\231/Pasted image 20250427213501.webp" "b/source/images/\346\234\272\345\231\250\345\255\246\344\271\240\345\244\215\344\271\240\350\265\204\346\226\231/Pasted image 20250427213501.webp" deleted file mode 100644 index d0bee7a..0000000 Binary files "a/source/images/\346\234\272\345\231\250\345\255\246\344\271\240\345\244\215\344\271\240\350\265\204\346\226\231/Pasted image 20250427213501.webp" and /dev/null differ diff --git "a/source/images/\346\234\272\345\231\250\345\255\246\344\271\240\345\244\215\344\271\240\350\265\204\346\226\231/Pasted image 20250427213547.webp" "b/source/images/\346\234\272\345\231\250\345\255\246\344\271\240\345\244\215\344\271\240\350\265\204\346\226\231/Pasted image 20250427213547.webp" deleted file mode 100644 index 8368345..0000000 Binary files "a/source/images/\346\234\272\345\231\250\345\255\246\344\271\240\345\244\215\344\271\240\350\265\204\346\226\231/Pasted image 20250427213547.webp" and /dev/null differ diff --git "a/source/images/\346\234\272\345\231\250\345\255\246\344\271\240\345\244\215\344\271\240\350\265\204\346\226\231/Pasted image 20250427213551.webp" "b/source/images/\346\234\272\345\231\250\345\255\246\344\271\240\345\244\215\344\271\240\350\265\204\346\226\231/Pasted image 20250427213551.webp" deleted file mode 100644 index 686407f..0000000 Binary files "a/source/images/\346\234\272\345\231\250\345\255\246\344\271\240\345\244\215\344\271\240\350\265\204\346\226\231/Pasted image 20250427213551.webp" and /dev/null differ diff --git "a/source/images/\346\234\272\345\231\250\345\255\246\344\271\240\345\244\215\344\271\240\350\265\204\346\226\231/Pasted image 20250427213600.webp" "b/source/images/\346\234\272\345\231\250\345\255\246\344\271\240\345\244\215\344\271\240\350\265\204\346\226\231/Pasted image 20250427213600.webp" deleted file mode 100644 index 9e99a11..0000000 Binary files "a/source/images/\346\234\272\345\231\250\345\255\246\344\271\240\345\244\215\344\271\240\350\265\204\346\226\231/Pasted image 20250427213600.webp" and /dev/null differ diff --git "a/source/images/\346\234\272\345\231\250\345\255\246\344\271\240\345\244\215\344\271\240\350\265\204\346\226\231/Pasted image 20250428164432.webp" "b/source/images/\346\234\272\345\231\250\345\255\246\344\271\240\345\244\215\344\271\240\350\265\204\346\226\231/Pasted image 20250428164432.webp" deleted file mode 100644 index 2f35851..0000000 Binary files "a/source/images/\346\234\272\345\231\250\345\255\246\344\271\240\345\244\215\344\271\240\350\265\204\346\226\231/Pasted image 20250428164432.webp" and /dev/null differ diff --git "a/source/images/\346\234\272\345\231\250\345\255\246\344\271\240\345\244\215\344\271\240\350\265\204\346\226\231/Pasted image 20250428164436.webp" "b/source/images/\346\234\272\345\231\250\345\255\246\344\271\240\345\244\215\344\271\240\350\265\204\346\226\231/Pasted image 20250428164436.webp" deleted file mode 100644 index 2fc73c8..0000000 Binary files "a/source/images/\346\234\272\345\231\250\345\255\246\344\271\240\345\244\215\344\271\240\350\265\204\346\226\231/Pasted image 20250428164436.webp" and /dev/null differ diff --git "a/source/images/\346\234\272\345\231\250\345\255\246\344\271\240\345\244\215\344\271\240\350\265\204\346\226\231/Pasted image 20250428170259.webp" "b/source/images/\346\234\272\345\231\250\345\255\246\344\271\240\345\244\215\344\271\240\350\265\204\346\226\231/Pasted image 20250428170259.webp" deleted file mode 100644 index ebbdc9a..0000000 Binary files "a/source/images/\346\234\272\345\231\250\345\255\246\344\271\240\345\244\215\344\271\240\350\265\204\346\226\231/Pasted image 20250428170259.webp" and /dev/null differ diff --git "a/source/images/\346\234\272\345\231\250\345\255\246\344\271\240\345\244\215\344\271\240\350\265\204\346\226\231/Pasted image 20250428170324.webp" "b/source/images/\346\234\272\345\231\250\345\255\246\344\271\240\345\244\215\344\271\240\350\265\204\346\226\231/Pasted image 20250428170324.webp" deleted file mode 100644 index 39303e5..0000000 Binary files "a/source/images/\346\234\272\345\231\250\345\255\246\344\271\240\345\244\215\344\271\240\350\265\204\346\226\231/Pasted image 20250428170324.webp" and /dev/null differ diff --git "a/source/images/\346\234\272\345\231\250\345\255\246\344\271\240\345\244\215\344\271\240\350\265\204\346\226\231/Pasted image 20250428170446.webp" "b/source/images/\346\234\272\345\231\250\345\255\246\344\271\240\345\244\215\344\271\240\350\265\204\346\226\231/Pasted image 20250428170446.webp" deleted file mode 100644 index a562c77..0000000 Binary files "a/source/images/\346\234\272\345\231\250\345\255\246\344\271\240\345\244\215\344\271\240\350\265\204\346\226\231/Pasted image 20250428170446.webp" and /dev/null differ diff --git "a/source/images/\346\234\272\345\231\250\345\255\246\344\271\240\345\244\215\344\271\240\350\265\204\346\226\231/Pasted image 20250428172157.webp" "b/source/images/\346\234\272\345\231\250\345\255\246\344\271\240\345\244\215\344\271\240\350\265\204\346\226\231/Pasted image 20250428172157.webp" deleted file mode 100644 index d8a59e3..0000000 Binary files "a/source/images/\346\234\272\345\231\250\345\255\246\344\271\240\345\244\215\344\271\240\350\265\204\346\226\231/Pasted image 20250428172157.webp" and /dev/null differ diff --git "a/source/images/\346\234\272\345\231\250\345\255\246\344\271\240\345\244\215\344\271\240\350\265\204\346\226\231/Pasted image 20250428172317.webp" "b/source/images/\346\234\272\345\231\250\345\255\246\344\271\240\345\244\215\344\271\240\350\265\204\346\226\231/Pasted image 20250428172317.webp" deleted file mode 100644 index d5509d1..0000000 Binary files "a/source/images/\346\234\272\345\231\250\345\255\246\344\271\240\345\244\215\344\271\240\350\265\204\346\226\231/Pasted image 20250428172317.webp" and /dev/null differ diff --git "a/source/images/\346\234\272\345\231\250\345\255\246\344\271\240\345\244\215\344\271\240\350\265\204\346\226\231/Pasted image 20250428172443.webp" "b/source/images/\346\234\272\345\231\250\345\255\246\344\271\240\345\244\215\344\271\240\350\265\204\346\226\231/Pasted image 20250428172443.webp" deleted file mode 100644 index 61e433d..0000000 Binary files "a/source/images/\346\234\272\345\231\250\345\255\246\344\271\240\345\244\215\344\271\240\350\265\204\346\226\231/Pasted image 20250428172443.webp" and /dev/null differ diff --git "a/source/images/\346\234\272\345\231\250\345\255\246\344\271\240\345\244\215\344\271\240\350\265\204\346\226\231/Pasted image 20250428172619.webp" "b/source/images/\346\234\272\345\231\250\345\255\246\344\271\240\345\244\215\344\271\240\350\265\204\346\226\231/Pasted image 20250428172619.webp" deleted file mode 100644 index abdcfad..0000000 Binary files "a/source/images/\346\234\272\345\231\250\345\255\246\344\271\240\345\244\215\344\271\240\350\265\204\346\226\231/Pasted image 20250428172619.webp" and /dev/null differ diff --git "a/source/images/\346\234\272\345\231\250\345\255\246\344\271\240\345\244\215\344\271\240\350\265\204\346\226\231/Pasted image 20250428172637.webp" "b/source/images/\346\234\272\345\231\250\345\255\246\344\271\240\345\244\215\344\271\240\350\265\204\346\226\231/Pasted image 20250428172637.webp" deleted file mode 100644 index 4027241..0000000 Binary files "a/source/images/\346\234\272\345\231\250\345\255\246\344\271\240\345\244\215\344\271\240\350\265\204\346\226\231/Pasted image 20250428172637.webp" and /dev/null differ diff --git "a/source/images/\346\234\272\345\231\250\345\255\246\344\271\240\345\244\215\344\271\240\350\265\204\346\226\231/Pasted image 20250428173021.webp" "b/source/images/\346\234\272\345\231\250\345\255\246\344\271\240\345\244\215\344\271\240\350\265\204\346\226\231/Pasted image 20250428173021.webp" deleted file mode 100644 index 19592ae..0000000 Binary files "a/source/images/\346\234\272\345\231\250\345\255\246\344\271\240\345\244\215\344\271\240\350\265\204\346\226\231/Pasted image 20250428173021.webp" and /dev/null differ diff --git "a/source/images/\346\234\272\345\231\250\345\255\246\344\271\240\345\244\215\344\271\240\350\265\204\346\226\231/Pasted image 20250428173026.webp" "b/source/images/\346\234\272\345\231\250\345\255\246\344\271\240\345\244\215\344\271\240\350\265\204\346\226\231/Pasted image 20250428173026.webp" deleted file mode 100644 index 6d0d281..0000000 Binary files "a/source/images/\346\234\272\345\231\250\345\255\246\344\271\240\345\244\215\344\271\240\350\265\204\346\226\231/Pasted image 20250428173026.webp" and /dev/null differ diff --git "a/source/images/\346\234\272\345\231\250\345\255\246\344\271\240\345\244\215\344\271\240\350\265\204\346\226\231/Pasted image 20250428173033.webp" "b/source/images/\346\234\272\345\231\250\345\255\246\344\271\240\345\244\215\344\271\240\350\265\204\346\226\231/Pasted image 20250428173033.webp" deleted file mode 100644 index b677873..0000000 Binary files "a/source/images/\346\234\272\345\231\250\345\255\246\344\271\240\345\244\215\344\271\240\350\265\204\346\226\231/Pasted image 20250428173033.webp" and /dev/null differ diff --git "a/source/images/\346\234\272\345\231\250\345\255\246\344\271\240\345\244\215\344\271\240\350\265\204\346\226\231/Pasted image 20250428181633.webp" "b/source/images/\346\234\272\345\231\250\345\255\246\344\271\240\345\244\215\344\271\240\350\265\204\346\226\231/Pasted image 20250428181633.webp" deleted file mode 100644 index 527331c..0000000 Binary files "a/source/images/\346\234\272\345\231\250\345\255\246\344\271\240\345\244\215\344\271\240\350\265\204\346\226\231/Pasted image 20250428181633.webp" and /dev/null differ diff --git "a/source/images/\346\234\272\345\231\250\345\255\246\344\271\240\345\244\215\344\271\240\350\265\204\346\226\231/Pasted image 20250428181756.webp" "b/source/images/\346\234\272\345\231\250\345\255\246\344\271\240\345\244\215\344\271\240\350\265\204\346\226\231/Pasted image 20250428181756.webp" deleted file mode 100644 index 85db5c9..0000000 Binary files "a/source/images/\346\234\272\345\231\250\345\255\246\344\271\240\345\244\215\344\271\240\350\265\204\346\226\231/Pasted image 20250428181756.webp" and /dev/null differ diff --git "a/source/images/\346\234\272\345\231\250\345\255\246\344\271\240\345\244\215\344\271\240\350\265\204\346\226\231/Pasted image 20250428181814.webp" "b/source/images/\346\234\272\345\231\250\345\255\246\344\271\240\345\244\215\344\271\240\350\265\204\346\226\231/Pasted image 20250428181814.webp" deleted file mode 100644 index 1e0b206..0000000 Binary files "a/source/images/\346\234\272\345\231\250\345\255\246\344\271\240\345\244\215\344\271\240\350\265\204\346\226\231/Pasted image 20250428181814.webp" and /dev/null differ diff --git "a/source/images/\346\234\272\345\231\250\345\255\246\344\271\240\345\244\215\344\271\240\350\265\204\346\226\231/Pasted image 20250428185603.webp" "b/source/images/\346\234\272\345\231\250\345\255\246\344\271\240\345\244\215\344\271\240\350\265\204\346\226\231/Pasted image 20250428185603.webp" deleted file mode 100644 index 19a1566..0000000 Binary files "a/source/images/\346\234\272\345\231\250\345\255\246\344\271\240\345\244\215\344\271\240\350\265\204\346\226\231/Pasted image 20250428185603.webp" and /dev/null differ diff --git "a/source/images/\346\234\272\345\231\250\345\255\246\344\271\240\345\244\215\344\271\240\350\265\204\346\226\231/Pasted image 20250428191046.webp" "b/source/images/\346\234\272\345\231\250\345\255\246\344\271\240\345\244\215\344\271\240\350\265\204\346\226\231/Pasted image 20250428191046.webp" deleted file mode 100644 index 2c2df4f..0000000 Binary files "a/source/images/\346\234\272\345\231\250\345\255\246\344\271\240\345\244\215\344\271\240\350\265\204\346\226\231/Pasted image 20250428191046.webp" and /dev/null differ diff --git "a/source/images/\346\234\272\345\231\250\345\255\246\344\271\240\345\244\215\344\271\240\350\265\204\346\226\231/Pasted image 20250428191151.webp" "b/source/images/\346\234\272\345\231\250\345\255\246\344\271\240\345\244\215\344\271\240\350\265\204\346\226\231/Pasted image 20250428191151.webp" deleted file mode 100644 index 6d8d839..0000000 Binary files "a/source/images/\346\234\272\345\231\250\345\255\246\344\271\240\345\244\215\344\271\240\350\265\204\346\226\231/Pasted image 20250428191151.webp" and /dev/null differ diff --git "a/source/images/\346\234\272\345\231\250\345\255\246\344\271\240\345\244\215\344\271\240\350\265\204\346\226\231/Pasted image 20250428191210.webp" "b/source/images/\346\234\272\345\231\250\345\255\246\344\271\240\345\244\215\344\271\240\350\265\204\346\226\231/Pasted image 20250428191210.webp" deleted file mode 100644 index 4a6b7c5..0000000 Binary files "a/source/images/\346\234\272\345\231\250\345\255\246\344\271\240\345\244\215\344\271\240\350\265\204\346\226\231/Pasted image 20250428191210.webp" and /dev/null differ diff --git "a/source/images/\346\234\272\345\231\250\345\255\246\344\271\240\345\244\215\344\271\240\350\265\204\346\226\231/Pasted image 20250428191215.webp" "b/source/images/\346\234\272\345\231\250\345\255\246\344\271\240\345\244\215\344\271\240\350\265\204\346\226\231/Pasted image 20250428191215.webp" deleted file mode 100644 index bb51286..0000000 Binary files "a/source/images/\346\234\272\345\231\250\345\255\246\344\271\240\345\244\215\344\271\240\350\265\204\346\226\231/Pasted image 20250428191215.webp" and /dev/null differ diff --git "a/source/images/\346\234\272\345\231\250\345\255\246\344\271\240\345\244\215\344\271\240\350\265\204\346\226\231/Pasted image 20250428191525.webp" "b/source/images/\346\234\272\345\231\250\345\255\246\344\271\240\345\244\215\344\271\240\350\265\204\346\226\231/Pasted image 20250428191525.webp" deleted file mode 100644 index 8202fe4..0000000 Binary files "a/source/images/\346\234\272\345\231\250\345\255\246\344\271\240\345\244\215\344\271\240\350\265\204\346\226\231/Pasted image 20250428191525.webp" and /dev/null differ diff --git "a/source/images/\346\234\272\345\231\250\345\255\246\344\271\240\345\244\215\344\271\240\350\265\204\346\226\231/Pasted image 20250428191811.webp" "b/source/images/\346\234\272\345\231\250\345\255\246\344\271\240\345\244\215\344\271\240\350\265\204\346\226\231/Pasted image 20250428191811.webp" deleted file mode 100644 index 792f701..0000000 Binary files "a/source/images/\346\234\272\345\231\250\345\255\246\344\271\240\345\244\215\344\271\240\350\265\204\346\226\231/Pasted image 20250428191811.webp" and /dev/null differ diff --git "a/source/images/\346\234\272\345\231\250\345\255\246\344\271\240\345\244\215\344\271\240\350\265\204\346\226\231/Pasted image 20250428191843.webp" "b/source/images/\346\234\272\345\231\250\345\255\246\344\271\240\345\244\215\344\271\240\350\265\204\346\226\231/Pasted image 20250428191843.webp" deleted file mode 100644 index 80a4275..0000000 Binary files "a/source/images/\346\234\272\345\231\250\345\255\246\344\271\240\345\244\215\344\271\240\350\265\204\346\226\231/Pasted image 20250428191843.webp" and /dev/null differ diff --git "a/source/images/\346\234\272\345\231\250\345\255\246\344\271\240\345\244\215\344\271\240\350\265\204\346\226\231/Pasted image 20250428191911.webp" "b/source/images/\346\234\272\345\231\250\345\255\246\344\271\240\345\244\215\344\271\240\350\265\204\346\226\231/Pasted image 20250428191911.webp" deleted file mode 100644 index 64bf7b6..0000000 Binary files "a/source/images/\346\234\272\345\231\250\345\255\246\344\271\240\345\244\215\344\271\240\350\265\204\346\226\231/Pasted image 20250428191911.webp" and /dev/null differ diff --git "a/source/images/\346\234\272\345\231\250\345\255\246\344\271\240\345\244\215\344\271\240\350\265\204\346\226\231/Pasted image 20250428191921.webp" "b/source/images/\346\234\272\345\231\250\345\255\246\344\271\240\345\244\215\344\271\240\350\265\204\346\226\231/Pasted image 20250428191921.webp" deleted file mode 100644 index 40461df..0000000 Binary files "a/source/images/\346\234\272\345\231\250\345\255\246\344\271\240\345\244\215\344\271\240\350\265\204\346\226\231/Pasted image 20250428191921.webp" and /dev/null differ diff --git "a/source/images/\346\234\272\345\231\250\345\255\246\344\271\240\345\244\215\344\271\240\350\265\204\346\226\231/Pasted image 20250428191926.webp" "b/source/images/\346\234\272\345\231\250\345\255\246\344\271\240\345\244\215\344\271\240\350\265\204\346\226\231/Pasted image 20250428191926.webp" deleted file mode 100644 index 482c051..0000000 Binary files "a/source/images/\346\234\272\345\231\250\345\255\246\344\271\240\345\244\215\344\271\240\350\265\204\346\226\231/Pasted image 20250428191926.webp" and /dev/null differ diff --git "a/source/images/\346\234\272\345\231\250\345\255\246\344\271\240\345\244\215\344\271\240\350\265\204\346\226\231/Pasted image 20250428191933.webp" "b/source/images/\346\234\272\345\231\250\345\255\246\344\271\240\345\244\215\344\271\240\350\265\204\346\226\231/Pasted image 20250428191933.webp" deleted file mode 100644 index 2cbf7cd..0000000 Binary files "a/source/images/\346\234\272\345\231\250\345\255\246\344\271\240\345\244\215\344\271\240\350\265\204\346\226\231/Pasted image 20250428191933.webp" and /dev/null differ diff --git "a/source/images/\346\234\272\345\231\250\345\255\246\344\271\240\345\244\215\344\271\240\350\265\204\346\226\231/Pasted image 20250428191938.webp" "b/source/images/\346\234\272\345\231\250\345\255\246\344\271\240\345\244\215\344\271\240\350\265\204\346\226\231/Pasted image 20250428191938.webp" deleted file mode 100644 index 70209d2..0000000 Binary files "a/source/images/\346\234\272\345\231\250\345\255\246\344\271\240\345\244\215\344\271\240\350\265\204\346\226\231/Pasted image 20250428191938.webp" and /dev/null differ diff --git "a/source/images/\346\234\272\345\231\250\345\255\246\344\271\240\345\244\215\344\271\240\350\265\204\346\226\231/Pasted image 20250428191944.webp" "b/source/images/\346\234\272\345\231\250\345\255\246\344\271\240\345\244\215\344\271\240\350\265\204\346\226\231/Pasted image 20250428191944.webp" deleted file mode 100644 index e185ddb..0000000 Binary files "a/source/images/\346\234\272\345\231\250\345\255\246\344\271\240\345\244\215\344\271\240\350\265\204\346\226\231/Pasted image 20250428191944.webp" and /dev/null differ diff --git "a/source/images/\346\234\272\345\231\250\345\255\246\344\271\240\345\244\215\344\271\240\350\265\204\346\226\231/Pasted image 20250428192509.webp" "b/source/images/\346\234\272\345\231\250\345\255\246\344\271\240\345\244\215\344\271\240\350\265\204\346\226\231/Pasted image 20250428192509.webp" deleted file mode 100644 index 28ce2e2..0000000 Binary files "a/source/images/\346\234\272\345\231\250\345\255\246\344\271\240\345\244\215\344\271\240\350\265\204\346\226\231/Pasted image 20250428192509.webp" and /dev/null differ diff --git "a/source/images/\346\234\272\345\231\250\345\255\246\344\271\240\345\244\215\344\271\240\350\265\204\346\226\231/Pasted image 20250428204927.webp" "b/source/images/\346\234\272\345\231\250\345\255\246\344\271\240\345\244\215\344\271\240\350\265\204\346\226\231/Pasted image 20250428204927.webp" deleted file mode 100644 index babba18..0000000 Binary files "a/source/images/\346\234\272\345\231\250\345\255\246\344\271\240\345\244\215\344\271\240\350\265\204\346\226\231/Pasted image 20250428204927.webp" and /dev/null differ diff --git "a/source/images/\346\234\272\345\231\250\345\255\246\344\271\240\345\244\215\344\271\240\350\265\204\346\226\231/Pasted image 20250428204949.webp" "b/source/images/\346\234\272\345\231\250\345\255\246\344\271\240\345\244\215\344\271\240\350\265\204\346\226\231/Pasted image 20250428204949.webp" deleted file mode 100644 index 88a2122..0000000 Binary files "a/source/images/\346\234\272\345\231\250\345\255\246\344\271\240\345\244\215\344\271\240\350\265\204\346\226\231/Pasted image 20250428204949.webp" and /dev/null differ diff --git "a/source/images/\346\234\272\345\231\250\345\255\246\344\271\240\345\244\215\344\271\240\350\265\204\346\226\231/Pasted image 20250428205146.webp" "b/source/images/\346\234\272\345\231\250\345\255\246\344\271\240\345\244\215\344\271\240\350\265\204\346\226\231/Pasted image 20250428205146.webp" deleted file mode 100644 index 3d99470..0000000 Binary files "a/source/images/\346\234\272\345\231\250\345\255\246\344\271\240\345\244\215\344\271\240\350\265\204\346\226\231/Pasted image 20250428205146.webp" and /dev/null differ diff --git "a/source/images/\346\234\272\345\231\250\345\255\246\344\271\240\345\244\215\344\271\240\350\265\204\346\226\231/Pasted image 20250428205713.webp" "b/source/images/\346\234\272\345\231\250\345\255\246\344\271\240\345\244\215\344\271\240\350\265\204\346\226\231/Pasted image 20250428205713.webp" deleted file mode 100644 index 3365147..0000000 Binary files "a/source/images/\346\234\272\345\231\250\345\255\246\344\271\240\345\244\215\344\271\240\350\265\204\346\226\231/Pasted image 20250428205713.webp" and /dev/null differ diff --git "a/source/images/\346\234\272\345\231\250\345\255\246\344\271\240\345\244\215\344\271\240\350\265\204\346\226\231/Pasted image 20250428205725.webp" "b/source/images/\346\234\272\345\231\250\345\255\246\344\271\240\345\244\215\344\271\240\350\265\204\346\226\231/Pasted image 20250428205725.webp" deleted file mode 100644 index 7da30c0..0000000 Binary files "a/source/images/\346\234\272\345\231\250\345\255\246\344\271\240\345\244\215\344\271\240\350\265\204\346\226\231/Pasted image 20250428205725.webp" and /dev/null differ diff --git "a/source/images/\346\234\272\345\231\250\345\255\246\344\271\240\345\244\215\344\271\240\350\265\204\346\226\231/Pasted image 20250428205815.webp" "b/source/images/\346\234\272\345\231\250\345\255\246\344\271\240\345\244\215\344\271\240\350\265\204\346\226\231/Pasted image 20250428205815.webp" deleted file mode 100644 index 021a8a4..0000000 Binary files "a/source/images/\346\234\272\345\231\250\345\255\246\344\271\240\345\244\215\344\271\240\350\265\204\346\226\231/Pasted image 20250428205815.webp" and /dev/null differ diff --git "a/source/images/\346\234\272\345\231\250\345\255\246\344\271\240\345\244\215\344\271\240\350\265\204\346\226\231/Pasted image 20250428205905.webp" "b/source/images/\346\234\272\345\231\250\345\255\246\344\271\240\345\244\215\344\271\240\350\265\204\346\226\231/Pasted image 20250428205905.webp" deleted file mode 100644 index fd21001..0000000 Binary files "a/source/images/\346\234\272\345\231\250\345\255\246\344\271\240\345\244\215\344\271\240\350\265\204\346\226\231/Pasted image 20250428205905.webp" and /dev/null differ diff --git "a/source/images/\346\234\272\345\231\250\345\255\246\344\271\240\345\244\215\344\271\240\350\265\204\346\226\231/Pasted image 20250428210013.webp" "b/source/images/\346\234\272\345\231\250\345\255\246\344\271\240\345\244\215\344\271\240\350\265\204\346\226\231/Pasted image 20250428210013.webp" deleted file mode 100644 index 63e6524..0000000 Binary files "a/source/images/\346\234\272\345\231\250\345\255\246\344\271\240\345\244\215\344\271\240\350\265\204\346\226\231/Pasted image 20250428210013.webp" and /dev/null differ diff --git "a/source/images/\346\234\272\345\231\250\345\255\246\344\271\240\345\244\215\344\271\240\350\265\204\346\226\231/Pasted image 20250428210304.webp" "b/source/images/\346\234\272\345\231\250\345\255\246\344\271\240\345\244\215\344\271\240\350\265\204\346\226\231/Pasted image 20250428210304.webp" deleted file mode 100644 index 87d1a59..0000000 Binary files "a/source/images/\346\234\272\345\231\250\345\255\246\344\271\240\345\244\215\344\271\240\350\265\204\346\226\231/Pasted image 20250428210304.webp" and /dev/null differ diff --git "a/source/images/\346\234\272\345\231\250\345\255\246\344\271\240\345\244\215\344\271\240\350\265\204\346\226\231/Pasted image 20250428210332.webp" "b/source/images/\346\234\272\345\231\250\345\255\246\344\271\240\345\244\215\344\271\240\350\265\204\346\226\231/Pasted image 20250428210332.webp" deleted file mode 100644 index 667f0a1..0000000 Binary files "a/source/images/\346\234\272\345\231\250\345\255\246\344\271\240\345\244\215\344\271\240\350\265\204\346\226\231/Pasted image 20250428210332.webp" and /dev/null differ diff --git "a/source/images/\346\234\272\345\231\250\345\255\246\344\271\240\345\244\215\344\271\240\350\265\204\346\226\231/Pasted image 20250428210351.webp" "b/source/images/\346\234\272\345\231\250\345\255\246\344\271\240\345\244\215\344\271\240\350\265\204\346\226\231/Pasted image 20250428210351.webp" deleted file mode 100644 index 76919b8..0000000 Binary files "a/source/images/\346\234\272\345\231\250\345\255\246\344\271\240\345\244\215\344\271\240\350\265\204\346\226\231/Pasted image 20250428210351.webp" and /dev/null differ diff --git "a/source/images/\346\234\272\345\231\250\345\255\246\344\271\240\345\244\215\344\271\240\350\265\204\346\226\231/Pasted image 20250428210405.webp" "b/source/images/\346\234\272\345\231\250\345\255\246\344\271\240\345\244\215\344\271\240\350\265\204\346\226\231/Pasted image 20250428210405.webp" deleted file mode 100644 index e2d3167..0000000 Binary files "a/source/images/\346\234\272\345\231\250\345\255\246\344\271\240\345\244\215\344\271\240\350\265\204\346\226\231/Pasted image 20250428210405.webp" and /dev/null differ diff --git "a/source/images/\346\234\272\345\231\250\345\255\246\344\271\240\345\244\215\344\271\240\350\265\204\346\226\231/Pasted image 20250428210424.webp" "b/source/images/\346\234\272\345\231\250\345\255\246\344\271\240\345\244\215\344\271\240\350\265\204\346\226\231/Pasted image 20250428210424.webp" deleted file mode 100644 index 6c3ea3c..0000000 Binary files "a/source/images/\346\234\272\345\231\250\345\255\246\344\271\240\345\244\215\344\271\240\350\265\204\346\226\231/Pasted image 20250428210424.webp" and /dev/null differ diff --git "a/source/images/\346\234\272\345\231\250\345\255\246\344\271\240\345\244\215\344\271\240\350\265\204\346\226\231/Pasted image 20250428210627.webp" "b/source/images/\346\234\272\345\231\250\345\255\246\344\271\240\345\244\215\344\271\240\350\265\204\346\226\231/Pasted image 20250428210627.webp" deleted file mode 100644 index a13222c..0000000 Binary files "a/source/images/\346\234\272\345\231\250\345\255\246\344\271\240\345\244\215\344\271\240\350\265\204\346\226\231/Pasted image 20250428210627.webp" and /dev/null differ diff --git "a/source/images/\346\234\272\345\231\250\345\255\246\344\271\240\345\244\215\344\271\240\350\265\204\346\226\231/Pasted image 20250428210732.webp" "b/source/images/\346\234\272\345\231\250\345\255\246\344\271\240\345\244\215\344\271\240\350\265\204\346\226\231/Pasted image 20250428210732.webp" deleted file mode 100644 index 95b29eb..0000000 Binary files "a/source/images/\346\234\272\345\231\250\345\255\246\344\271\240\345\244\215\344\271\240\350\265\204\346\226\231/Pasted image 20250428210732.webp" and /dev/null differ diff --git "a/source/images/\346\234\272\345\231\250\345\255\246\344\271\240\345\244\215\344\271\240\350\265\204\346\226\231/Pasted image 20250428210740.webp" "b/source/images/\346\234\272\345\231\250\345\255\246\344\271\240\345\244\215\344\271\240\350\265\204\346\226\231/Pasted image 20250428210740.webp" deleted file mode 100644 index 7eb2b6c..0000000 Binary files "a/source/images/\346\234\272\345\231\250\345\255\246\344\271\240\345\244\215\344\271\240\350\265\204\346\226\231/Pasted image 20250428210740.webp" and /dev/null differ diff --git "a/source/images/\346\234\272\345\231\250\345\255\246\344\271\240\345\244\215\344\271\240\350\265\204\346\226\231/Pasted image 20250428210852.webp" "b/source/images/\346\234\272\345\231\250\345\255\246\344\271\240\345\244\215\344\271\240\350\265\204\346\226\231/Pasted image 20250428210852.webp" deleted file mode 100644 index ab5987d..0000000 Binary files "a/source/images/\346\234\272\345\231\250\345\255\246\344\271\240\345\244\215\344\271\240\350\265\204\346\226\231/Pasted image 20250428210852.webp" and /dev/null differ diff --git "a/source/images/\346\234\272\345\231\250\345\255\246\344\271\240\345\244\215\344\271\240\350\265\204\346\226\231/Pasted image 20250428210923.webp" "b/source/images/\346\234\272\345\231\250\345\255\246\344\271\240\345\244\215\344\271\240\350\265\204\346\226\231/Pasted image 20250428210923.webp" deleted file mode 100644 index 9e72487..0000000 Binary files "a/source/images/\346\234\272\345\231\250\345\255\246\344\271\240\345\244\215\344\271\240\350\265\204\346\226\231/Pasted image 20250428210923.webp" and /dev/null differ diff --git "a/source/images/\346\234\272\345\231\250\345\255\246\344\271\240\345\244\215\344\271\240\350\265\204\346\226\231/Pasted image 20250428210938.webp" "b/source/images/\346\234\272\345\231\250\345\255\246\344\271\240\345\244\215\344\271\240\350\265\204\346\226\231/Pasted image 20250428210938.webp" deleted file mode 100644 index 99a5f46..0000000 Binary files "a/source/images/\346\234\272\345\231\250\345\255\246\344\271\240\345\244\215\344\271\240\350\265\204\346\226\231/Pasted image 20250428210938.webp" and /dev/null differ diff --git "a/source/images/\346\234\272\345\231\250\345\255\246\344\271\240\345\244\215\344\271\240\350\265\204\346\226\231/Pasted image 20250428210944.webp" "b/source/images/\346\234\272\345\231\250\345\255\246\344\271\240\345\244\215\344\271\240\350\265\204\346\226\231/Pasted image 20250428210944.webp" deleted file mode 100644 index 00e1d3e..0000000 Binary files "a/source/images/\346\234\272\345\231\250\345\255\246\344\271\240\345\244\215\344\271\240\350\265\204\346\226\231/Pasted image 20250428210944.webp" and /dev/null differ diff --git "a/source/images/\346\234\272\345\231\250\345\255\246\344\271\240\345\244\215\344\271\240\350\265\204\346\226\231/Pasted image 20250428211153.webp" "b/source/images/\346\234\272\345\231\250\345\255\246\344\271\240\345\244\215\344\271\240\350\265\204\346\226\231/Pasted image 20250428211153.webp" deleted file mode 100644 index 79d06e0..0000000 Binary files "a/source/images/\346\234\272\345\231\250\345\255\246\344\271\240\345\244\215\344\271\240\350\265\204\346\226\231/Pasted image 20250428211153.webp" and /dev/null differ diff --git "a/source/images/\346\234\272\345\231\250\345\255\246\344\271\240\345\244\215\344\271\240\350\265\204\346\226\231/Pasted image 20250428211434.webp" "b/source/images/\346\234\272\345\231\250\345\255\246\344\271\240\345\244\215\344\271\240\350\265\204\346\226\231/Pasted image 20250428211434.webp" deleted file mode 100644 index 3ac658f..0000000 Binary files "a/source/images/\346\234\272\345\231\250\345\255\246\344\271\240\345\244\215\344\271\240\350\265\204\346\226\231/Pasted image 20250428211434.webp" and /dev/null differ diff --git "a/source/images/\346\234\272\345\231\250\345\255\246\344\271\240\345\244\215\344\271\240\350\265\204\346\226\231/Pasted image 20250428211509.webp" "b/source/images/\346\234\272\345\231\250\345\255\246\344\271\240\345\244\215\344\271\240\350\265\204\346\226\231/Pasted image 20250428211509.webp" deleted file mode 100644 index 1e10a6a..0000000 Binary files "a/source/images/\346\234\272\345\231\250\345\255\246\344\271\240\345\244\215\344\271\240\350\265\204\346\226\231/Pasted image 20250428211509.webp" and /dev/null differ diff --git "a/source/images/\346\234\272\345\231\250\345\255\246\344\271\240\345\244\215\344\271\240\350\265\204\346\226\231/Pasted image 20250428211515.webp" "b/source/images/\346\234\272\345\231\250\345\255\246\344\271\240\345\244\215\344\271\240\350\265\204\346\226\231/Pasted image 20250428211515.webp" deleted file mode 100644 index 64eb3f7..0000000 Binary files "a/source/images/\346\234\272\345\231\250\345\255\246\344\271\240\345\244\215\344\271\240\350\265\204\346\226\231/Pasted image 20250428211515.webp" and /dev/null differ diff --git "a/source/images/\346\234\272\345\231\250\345\255\246\344\271\240\345\244\215\344\271\240\350\265\204\346\226\231/Pasted image 20250428213016.webp" "b/source/images/\346\234\272\345\231\250\345\255\246\344\271\240\345\244\215\344\271\240\350\265\204\346\226\231/Pasted image 20250428213016.webp" deleted file mode 100644 index 6ba77bf..0000000 Binary files "a/source/images/\346\234\272\345\231\250\345\255\246\344\271\240\345\244\215\344\271\240\350\265\204\346\226\231/Pasted image 20250428213016.webp" and /dev/null differ diff --git "a/source/images/\346\234\272\345\231\250\345\255\246\344\271\240\345\244\215\344\271\240\350\265\204\346\226\231/Pasted image 20250428213255.webp" "b/source/images/\346\234\272\345\231\250\345\255\246\344\271\240\345\244\215\344\271\240\350\265\204\346\226\231/Pasted image 20250428213255.webp" deleted file mode 100644 index 583f60c..0000000 Binary files "a/source/images/\346\234\272\345\231\250\345\255\246\344\271\240\345\244\215\344\271\240\350\265\204\346\226\231/Pasted image 20250428213255.webp" and /dev/null differ diff --git "a/source/images/\346\234\272\345\231\250\345\255\246\344\271\240\345\244\215\344\271\240\350\265\204\346\226\231/Pasted image 20250428214346.webp" "b/source/images/\346\234\272\345\231\250\345\255\246\344\271\240\345\244\215\344\271\240\350\265\204\346\226\231/Pasted image 20250428214346.webp" deleted file mode 100644 index 7fba386..0000000 Binary files "a/source/images/\346\234\272\345\231\250\345\255\246\344\271\240\345\244\215\344\271\240\350\265\204\346\226\231/Pasted image 20250428214346.webp" and /dev/null differ diff --git "a/source/images/\346\234\272\345\231\250\345\255\246\344\271\240\345\244\215\344\271\240\350\265\204\346\226\231/Pasted image 20250428214406.webp" "b/source/images/\346\234\272\345\231\250\345\255\246\344\271\240\345\244\215\344\271\240\350\265\204\346\226\231/Pasted image 20250428214406.webp" deleted file mode 100644 index 3fc7aa0..0000000 Binary files "a/source/images/\346\234\272\345\231\250\345\255\246\344\271\240\345\244\215\344\271\240\350\265\204\346\226\231/Pasted image 20250428214406.webp" and /dev/null differ diff --git "a/source/images/\346\234\272\345\231\250\345\255\246\344\271\240\345\244\215\344\271\240\350\265\204\346\226\231/Pasted image 20250428214410.webp" "b/source/images/\346\234\272\345\231\250\345\255\246\344\271\240\345\244\215\344\271\240\350\265\204\346\226\231/Pasted image 20250428214410.webp" deleted file mode 100644 index 2e441ec..0000000 Binary files "a/source/images/\346\234\272\345\231\250\345\255\246\344\271\240\345\244\215\344\271\240\350\265\204\346\226\231/Pasted image 20250428214410.webp" and /dev/null differ diff --git "a/source/images/\346\234\272\345\231\250\345\255\246\344\271\240\345\244\215\344\271\240\350\265\204\346\226\231/Pasted image 20250428214420.webp" "b/source/images/\346\234\272\345\231\250\345\255\246\344\271\240\345\244\215\344\271\240\350\265\204\346\226\231/Pasted image 20250428214420.webp" deleted file mode 100644 index be56114..0000000 Binary files "a/source/images/\346\234\272\345\231\250\345\255\246\344\271\240\345\244\215\344\271\240\350\265\204\346\226\231/Pasted image 20250428214420.webp" and /dev/null differ diff --git "a/source/images/\346\234\272\345\231\250\345\255\246\344\271\240\345\244\215\344\271\240\350\265\204\346\226\231/Pasted image 20250428214438.webp" "b/source/images/\346\234\272\345\231\250\345\255\246\344\271\240\345\244\215\344\271\240\350\265\204\346\226\231/Pasted image 20250428214438.webp" deleted file mode 100644 index 2fddfb3..0000000 Binary files "a/source/images/\346\234\272\345\231\250\345\255\246\344\271\240\345\244\215\344\271\240\350\265\204\346\226\231/Pasted image 20250428214438.webp" and /dev/null differ diff --git "a/source/images/\346\234\272\345\231\250\345\255\246\344\271\240\345\244\215\344\271\240\350\265\204\346\226\231/Pasted image 20250428214442.webp" "b/source/images/\346\234\272\345\231\250\345\255\246\344\271\240\345\244\215\344\271\240\350\265\204\346\226\231/Pasted image 20250428214442.webp" deleted file mode 100644 index 34b8653..0000000 Binary files "a/source/images/\346\234\272\345\231\250\345\255\246\344\271\240\345\244\215\344\271\240\350\265\204\346\226\231/Pasted image 20250428214442.webp" and /dev/null differ diff --git "a/source/images/\346\234\272\345\231\250\345\255\246\344\271\240\345\244\215\344\271\240\350\265\204\346\226\231/Pasted image 20250428214509.webp" "b/source/images/\346\234\272\345\231\250\345\255\246\344\271\240\345\244\215\344\271\240\350\265\204\346\226\231/Pasted image 20250428214509.webp" deleted file mode 100644 index d42f1bd..0000000 Binary files "a/source/images/\346\234\272\345\231\250\345\255\246\344\271\240\345\244\215\344\271\240\350\265\204\346\226\231/Pasted image 20250428214509.webp" and /dev/null differ diff --git "a/source/images/\346\234\272\345\231\250\345\255\246\344\271\240\345\244\215\344\271\240\350\265\204\346\226\231/Pasted image 20250428214538.webp" "b/source/images/\346\234\272\345\231\250\345\255\246\344\271\240\345\244\215\344\271\240\350\265\204\346\226\231/Pasted image 20250428214538.webp" deleted file mode 100644 index 0b3d02c..0000000 Binary files "a/source/images/\346\234\272\345\231\250\345\255\246\344\271\240\345\244\215\344\271\240\350\265\204\346\226\231/Pasted image 20250428214538.webp" and /dev/null differ diff --git "a/source/images/\346\234\272\345\231\250\345\255\246\344\271\240\345\244\215\344\271\240\350\265\204\346\226\231/Pasted image 20250428214544.webp" "b/source/images/\346\234\272\345\231\250\345\255\246\344\271\240\345\244\215\344\271\240\350\265\204\346\226\231/Pasted image 20250428214544.webp" deleted file mode 100644 index bfa725a..0000000 Binary files "a/source/images/\346\234\272\345\231\250\345\255\246\344\271\240\345\244\215\344\271\240\350\265\204\346\226\231/Pasted image 20250428214544.webp" and /dev/null differ diff --git "a/source/images/\346\234\272\345\231\250\345\255\246\344\271\240\345\244\215\344\271\240\350\265\204\346\226\231/Pasted image 20250428214621.webp" "b/source/images/\346\234\272\345\231\250\345\255\246\344\271\240\345\244\215\344\271\240\350\265\204\346\226\231/Pasted image 20250428214621.webp" deleted file mode 100644 index 05ee245..0000000 Binary files "a/source/images/\346\234\272\345\231\250\345\255\246\344\271\240\345\244\215\344\271\240\350\265\204\346\226\231/Pasted image 20250428214621.webp" and /dev/null differ diff --git "a/source/images/\346\234\272\345\231\250\345\255\246\344\271\240\345\244\215\344\271\240\350\265\204\346\226\231/Pasted image 20250428214655.webp" "b/source/images/\346\234\272\345\231\250\345\255\246\344\271\240\345\244\215\344\271\240\350\265\204\346\226\231/Pasted image 20250428214655.webp" deleted file mode 100644 index 7726b1f..0000000 Binary files "a/source/images/\346\234\272\345\231\250\345\255\246\344\271\240\345\244\215\344\271\240\350\265\204\346\226\231/Pasted image 20250428214655.webp" and /dev/null differ diff --git "a/source/images/\346\234\272\345\231\250\345\255\246\344\271\240\345\244\215\344\271\240\350\265\204\346\226\231/Pasted image 20250428214727.webp" "b/source/images/\346\234\272\345\231\250\345\255\246\344\271\240\345\244\215\344\271\240\350\265\204\346\226\231/Pasted image 20250428214727.webp" deleted file mode 100644 index f034b74..0000000 Binary files "a/source/images/\346\234\272\345\231\250\345\255\246\344\271\240\345\244\215\344\271\240\350\265\204\346\226\231/Pasted image 20250428214727.webp" and /dev/null differ diff --git "a/source/images/\346\234\272\345\231\250\345\255\246\344\271\240\345\244\215\344\271\240\350\265\204\346\226\231/Pasted image 20250428214750.webp" "b/source/images/\346\234\272\345\231\250\345\255\246\344\271\240\345\244\215\344\271\240\350\265\204\346\226\231/Pasted image 20250428214750.webp" deleted file mode 100644 index 32b665b..0000000 Binary files "a/source/images/\346\234\272\345\231\250\345\255\246\344\271\240\345\244\215\344\271\240\350\265\204\346\226\231/Pasted image 20250428214750.webp" and /dev/null differ diff --git "a/source/images/\346\234\272\345\231\250\345\255\246\344\271\240\345\244\215\344\271\240\350\265\204\346\226\231/Pasted image 20250428214933.webp" "b/source/images/\346\234\272\345\231\250\345\255\246\344\271\240\345\244\215\344\271\240\350\265\204\346\226\231/Pasted image 20250428214933.webp" deleted file mode 100644 index 18cfacd..0000000 Binary files "a/source/images/\346\234\272\345\231\250\345\255\246\344\271\240\345\244\215\344\271\240\350\265\204\346\226\231/Pasted image 20250428214933.webp" and /dev/null differ diff --git "a/source/images/\346\234\272\345\231\250\345\255\246\344\271\240\345\244\215\344\271\240\350\265\204\346\226\231/Pasted image 20250428214940.webp" "b/source/images/\346\234\272\345\231\250\345\255\246\344\271\240\345\244\215\344\271\240\350\265\204\346\226\231/Pasted image 20250428214940.webp" deleted file mode 100644 index f0e26ff..0000000 Binary files "a/source/images/\346\234\272\345\231\250\345\255\246\344\271\240\345\244\215\344\271\240\350\265\204\346\226\231/Pasted image 20250428214940.webp" and /dev/null differ diff --git "a/source/images/\346\234\272\345\231\250\345\255\246\344\271\240\345\244\215\344\271\240\350\265\204\346\226\231/Pasted image 20250428215019.webp" "b/source/images/\346\234\272\345\231\250\345\255\246\344\271\240\345\244\215\344\271\240\350\265\204\346\226\231/Pasted image 20250428215019.webp" deleted file mode 100644 index 9b7971c..0000000 Binary files "a/source/images/\346\234\272\345\231\250\345\255\246\344\271\240\345\244\215\344\271\240\350\265\204\346\226\231/Pasted image 20250428215019.webp" and /dev/null differ diff --git "a/source/images/\346\234\272\345\231\250\345\255\246\344\271\240\345\244\215\344\271\240\350\265\204\346\226\231/Pasted image 20250428215034.webp" "b/source/images/\346\234\272\345\231\250\345\255\246\344\271\240\345\244\215\344\271\240\350\265\204\346\226\231/Pasted image 20250428215034.webp" deleted file mode 100644 index 05dd9de..0000000 Binary files "a/source/images/\346\234\272\345\231\250\345\255\246\344\271\240\345\244\215\344\271\240\350\265\204\346\226\231/Pasted image 20250428215034.webp" and /dev/null differ diff --git "a/source/images/\346\234\272\345\231\250\345\255\246\344\271\240\345\244\215\344\271\240\350\265\204\346\226\231/Pasted image 20250428215116.webp" "b/source/images/\346\234\272\345\231\250\345\255\246\344\271\240\345\244\215\344\271\240\350\265\204\346\226\231/Pasted image 20250428215116.webp" deleted file mode 100644 index 600483f..0000000 Binary files "a/source/images/\346\234\272\345\231\250\345\255\246\344\271\240\345\244\215\344\271\240\350\265\204\346\226\231/Pasted image 20250428215116.webp" and /dev/null differ diff --git "a/source/images/\346\234\272\345\231\250\345\255\246\344\271\240\345\244\215\344\271\240\350\265\204\346\226\231/Pasted image 20250428215147.webp" "b/source/images/\346\234\272\345\231\250\345\255\246\344\271\240\345\244\215\344\271\240\350\265\204\346\226\231/Pasted image 20250428215147.webp" deleted file mode 100644 index 0d7eece..0000000 Binary files "a/source/images/\346\234\272\345\231\250\345\255\246\344\271\240\345\244\215\344\271\240\350\265\204\346\226\231/Pasted image 20250428215147.webp" and /dev/null differ diff --git "a/source/images/\346\234\272\345\231\250\345\255\246\344\271\240\345\244\215\344\271\240\350\265\204\346\226\231/Pasted image 20250428215449.webp" "b/source/images/\346\234\272\345\231\250\345\255\246\344\271\240\345\244\215\344\271\240\350\265\204\346\226\231/Pasted image 20250428215449.webp" deleted file mode 100644 index 93e493f..0000000 Binary files "a/source/images/\346\234\272\345\231\250\345\255\246\344\271\240\345\244\215\344\271\240\350\265\204\346\226\231/Pasted image 20250428215449.webp" and /dev/null differ diff --git "a/source/images/\346\234\272\345\231\250\345\255\246\344\271\240\345\244\215\344\271\240\350\265\204\346\226\231/Pasted image 20250428215605.webp" "b/source/images/\346\234\272\345\231\250\345\255\246\344\271\240\345\244\215\344\271\240\350\265\204\346\226\231/Pasted image 20250428215605.webp" deleted file mode 100644 index d23d713..0000000 Binary files "a/source/images/\346\234\272\345\231\250\345\255\246\344\271\240\345\244\215\344\271\240\350\265\204\346\226\231/Pasted image 20250428215605.webp" and /dev/null differ diff --git "a/source/images/\346\234\272\345\231\250\345\255\246\344\271\240\345\244\215\344\271\240\350\265\204\346\226\231/Pasted image 20250428215708.webp" "b/source/images/\346\234\272\345\231\250\345\255\246\344\271\240\345\244\215\344\271\240\350\265\204\346\226\231/Pasted image 20250428215708.webp" deleted file mode 100644 index ff17617..0000000 Binary files "a/source/images/\346\234\272\345\231\250\345\255\246\344\271\240\345\244\215\344\271\240\350\265\204\346\226\231/Pasted image 20250428215708.webp" and /dev/null differ diff --git "a/source/images/\346\234\272\345\231\250\345\255\246\344\271\240\345\244\215\344\271\240\350\265\204\346\226\231/Pasted image 20250428215754.webp" "b/source/images/\346\234\272\345\231\250\345\255\246\344\271\240\345\244\215\344\271\240\350\265\204\346\226\231/Pasted image 20250428215754.webp" deleted file mode 100644 index 8d111ee..0000000 Binary files "a/source/images/\346\234\272\345\231\250\345\255\246\344\271\240\345\244\215\344\271\240\350\265\204\346\226\231/Pasted image 20250428215754.webp" and /dev/null differ diff --git "a/source/images/\346\234\272\345\231\250\345\255\246\344\271\240\345\244\215\344\271\240\350\265\204\346\226\231/Pasted image 20250428215804.webp" "b/source/images/\346\234\272\345\231\250\345\255\246\344\271\240\345\244\215\344\271\240\350\265\204\346\226\231/Pasted image 20250428215804.webp" deleted file mode 100644 index a978621..0000000 Binary files "a/source/images/\346\234\272\345\231\250\345\255\246\344\271\240\345\244\215\344\271\240\350\265\204\346\226\231/Pasted image 20250428215804.webp" and /dev/null differ diff --git "a/source/images/\346\234\272\345\231\250\345\255\246\344\271\240\345\244\215\344\271\240\350\265\204\346\226\231/Pasted image 20250428215821.webp" "b/source/images/\346\234\272\345\231\250\345\255\246\344\271\240\345\244\215\344\271\240\350\265\204\346\226\231/Pasted image 20250428215821.webp" deleted file mode 100644 index 030aa54..0000000 Binary files "a/source/images/\346\234\272\345\231\250\345\255\246\344\271\240\345\244\215\344\271\240\350\265\204\346\226\231/Pasted image 20250428215821.webp" and /dev/null differ diff --git "a/source/images/\346\234\272\345\231\250\345\255\246\344\271\240\345\244\215\344\271\240\350\265\204\346\226\231/Pasted image 20250428215925.webp" "b/source/images/\346\234\272\345\231\250\345\255\246\344\271\240\345\244\215\344\271\240\350\265\204\346\226\231/Pasted image 20250428215925.webp" deleted file mode 100644 index f6063ae..0000000 Binary files "a/source/images/\346\234\272\345\231\250\345\255\246\344\271\240\345\244\215\344\271\240\350\265\204\346\226\231/Pasted image 20250428215925.webp" and /dev/null differ diff --git "a/source/images/\346\234\272\345\231\250\345\255\246\344\271\240\345\244\215\344\271\240\350\265\204\346\226\231/Pasted image 20250428215948.webp" "b/source/images/\346\234\272\345\231\250\345\255\246\344\271\240\345\244\215\344\271\240\350\265\204\346\226\231/Pasted image 20250428215948.webp" deleted file mode 100644 index 72fe1b5..0000000 Binary files "a/source/images/\346\234\272\345\231\250\345\255\246\344\271\240\345\244\215\344\271\240\350\265\204\346\226\231/Pasted image 20250428215948.webp" and /dev/null differ diff --git "a/source/images/\346\234\272\345\231\250\345\255\246\344\271\240\345\244\215\344\271\240\350\265\204\346\226\231/Pasted image 20250428215956.webp" "b/source/images/\346\234\272\345\231\250\345\255\246\344\271\240\345\244\215\344\271\240\350\265\204\346\226\231/Pasted image 20250428215956.webp" deleted file mode 100644 index a13df11..0000000 Binary files "a/source/images/\346\234\272\345\231\250\345\255\246\344\271\240\345\244\215\344\271\240\350\265\204\346\226\231/Pasted image 20250428215956.webp" and /dev/null differ diff --git "a/source/images/\346\234\272\345\231\250\345\255\246\344\271\240\345\244\215\344\271\240\350\265\204\346\226\231/Pasted image 20250428220011.webp" "b/source/images/\346\234\272\345\231\250\345\255\246\344\271\240\345\244\215\344\271\240\350\265\204\346\226\231/Pasted image 20250428220011.webp" deleted file mode 100644 index 410ca62..0000000 Binary files "a/source/images/\346\234\272\345\231\250\345\255\246\344\271\240\345\244\215\344\271\240\350\265\204\346\226\231/Pasted image 20250428220011.webp" and /dev/null differ diff --git "a/source/images/\346\234\272\345\231\250\345\255\246\344\271\240\345\244\215\344\271\240\350\265\204\346\226\231/Pasted image 20250428220326.webp" "b/source/images/\346\234\272\345\231\250\345\255\246\344\271\240\345\244\215\344\271\240\350\265\204\346\226\231/Pasted image 20250428220326.webp" deleted file mode 100644 index cc4080f..0000000 Binary files "a/source/images/\346\234\272\345\231\250\345\255\246\344\271\240\345\244\215\344\271\240\350\265\204\346\226\231/Pasted image 20250428220326.webp" and /dev/null differ diff --git "a/source/images/\346\234\272\345\231\250\345\255\246\344\271\240\345\244\215\344\271\240\350\265\204\346\226\231/Pasted image 20250428220331.webp" "b/source/images/\346\234\272\345\231\250\345\255\246\344\271\240\345\244\215\344\271\240\350\265\204\346\226\231/Pasted image 20250428220331.webp" deleted file mode 100644 index 7ff0a26..0000000 Binary files "a/source/images/\346\234\272\345\231\250\345\255\246\344\271\240\345\244\215\344\271\240\350\265\204\346\226\231/Pasted image 20250428220331.webp" and /dev/null differ diff --git "a/source/images/\346\234\272\345\231\250\345\255\246\344\271\240\345\244\215\344\271\240\350\265\204\346\226\231/Pasted image 20250428220348.webp" "b/source/images/\346\234\272\345\231\250\345\255\246\344\271\240\345\244\215\344\271\240\350\265\204\346\226\231/Pasted image 20250428220348.webp" deleted file mode 100644 index cf1ef83..0000000 Binary files "a/source/images/\346\234\272\345\231\250\345\255\246\344\271\240\345\244\215\344\271\240\350\265\204\346\226\231/Pasted image 20250428220348.webp" and /dev/null differ diff --git "a/source/images/\346\234\272\345\231\250\345\255\246\344\271\240\345\244\215\344\271\240\350\265\204\346\226\231/Pasted image 20250428220733.webp" "b/source/images/\346\234\272\345\231\250\345\255\246\344\271\240\345\244\215\344\271\240\350\265\204\346\226\231/Pasted image 20250428220733.webp" deleted file mode 100644 index 3fca87e..0000000 Binary files "a/source/images/\346\234\272\345\231\250\345\255\246\344\271\240\345\244\215\344\271\240\350\265\204\346\226\231/Pasted image 20250428220733.webp" and /dev/null differ diff --git "a/source/images/\350\256\241\347\256\227\346\234\272\351\253\230\347\272\247\347\263\273\347\273\237\347\273\223\346\236\204/Pasted image 20241226140214.webp" "b/source/images/\350\256\241\347\256\227\346\234\272\351\253\230\347\272\247\347\263\273\347\273\237\347\273\223\346\236\204/Pasted image 20241226140214.webp" deleted file mode 100644 index e83257a..0000000 Binary files "a/source/images/\350\256\241\347\256\227\346\234\272\351\253\230\347\272\247\347\263\273\347\273\237\347\273\223\346\236\204/Pasted image 20241226140214.webp" and /dev/null differ diff --git "a/source/images/\350\256\241\347\256\227\346\234\272\351\253\230\347\272\247\347\263\273\347\273\237\347\273\223\346\236\204/Pasted image 20241226140309.webp" "b/source/images/\350\256\241\347\256\227\346\234\272\351\253\230\347\272\247\347\263\273\347\273\237\347\273\223\346\236\204/Pasted image 20241226140309.webp" deleted file mode 100644 index d22e558..0000000 Binary files "a/source/images/\350\256\241\347\256\227\346\234\272\351\253\230\347\272\247\347\263\273\347\273\237\347\273\223\346\236\204/Pasted image 20241226140309.webp" and /dev/null differ diff --git "a/source/images/\350\256\241\347\256\227\346\234\272\351\253\230\347\272\247\347\263\273\347\273\237\347\273\223\346\236\204/Pasted image 20241226140503.webp" "b/source/images/\350\256\241\347\256\227\346\234\272\351\253\230\347\272\247\347\263\273\347\273\237\347\273\223\346\236\204/Pasted image 20241226140503.webp" deleted file mode 100644 index 5f7252d..0000000 Binary files "a/source/images/\350\256\241\347\256\227\346\234\272\351\253\230\347\272\247\347\263\273\347\273\237\347\273\223\346\236\204/Pasted image 20241226140503.webp" and /dev/null differ diff --git "a/source/images/\350\256\241\347\256\227\346\234\272\351\253\230\347\272\247\347\263\273\347\273\237\347\273\223\346\236\204/Pasted image 20241226140521.webp" "b/source/images/\350\256\241\347\256\227\346\234\272\351\253\230\347\272\247\347\263\273\347\273\237\347\273\223\346\236\204/Pasted image 20241226140521.webp" deleted file mode 100644 index 5c563ad..0000000 Binary files "a/source/images/\350\256\241\347\256\227\346\234\272\351\253\230\347\272\247\347\263\273\347\273\237\347\273\223\346\236\204/Pasted image 20241226140521.webp" and /dev/null differ diff --git "a/source/images/\350\256\241\347\256\227\346\234\272\351\253\230\347\272\247\347\263\273\347\273\237\347\273\223\346\236\204/Pasted image 20241226140547.webp" "b/source/images/\350\256\241\347\256\227\346\234\272\351\253\230\347\272\247\347\263\273\347\273\237\347\273\223\346\236\204/Pasted image 20241226140547.webp" deleted file mode 100644 index 3bf9ed3..0000000 Binary files "a/source/images/\350\256\241\347\256\227\346\234\272\351\253\230\347\272\247\347\263\273\347\273\237\347\273\223\346\236\204/Pasted image 20241226140547.webp" and /dev/null differ diff --git "a/source/images/\350\256\241\347\256\227\346\234\272\351\253\230\347\272\247\347\263\273\347\273\237\347\273\223\346\236\204/Pasted image 20241226140600.webp" "b/source/images/\350\256\241\347\256\227\346\234\272\351\253\230\347\272\247\347\263\273\347\273\237\347\273\223\346\236\204/Pasted image 20241226140600.webp" deleted file mode 100644 index 18a2ed2..0000000 Binary files "a/source/images/\350\256\241\347\256\227\346\234\272\351\253\230\347\272\247\347\263\273\347\273\237\347\273\223\346\236\204/Pasted image 20241226140600.webp" and /dev/null differ diff --git "a/source/images/\350\256\241\347\256\227\346\234\272\351\253\230\347\272\247\347\263\273\347\273\237\347\273\223\346\236\204/Pasted image 20241226140708.webp" "b/source/images/\350\256\241\347\256\227\346\234\272\351\253\230\347\272\247\347\263\273\347\273\237\347\273\223\346\236\204/Pasted image 20241226140708.webp" deleted file mode 100644 index 401f12e..0000000 Binary files "a/source/images/\350\256\241\347\256\227\346\234\272\351\253\230\347\272\247\347\263\273\347\273\237\347\273\223\346\236\204/Pasted image 20241226140708.webp" and /dev/null differ diff --git "a/source/images/\350\256\241\347\256\227\346\234\272\351\253\230\347\272\247\347\263\273\347\273\237\347\273\223\346\236\204/Pasted image 20241226140815.webp" "b/source/images/\350\256\241\347\256\227\346\234\272\351\253\230\347\272\247\347\263\273\347\273\237\347\273\223\346\236\204/Pasted image 20241226140815.webp" deleted file mode 100644 index 7176d08..0000000 Binary files "a/source/images/\350\256\241\347\256\227\346\234\272\351\253\230\347\272\247\347\263\273\347\273\237\347\273\223\346\236\204/Pasted image 20241226140815.webp" and /dev/null differ diff --git "a/source/images/\350\256\241\347\256\227\346\234\272\351\253\230\347\272\247\347\263\273\347\273\237\347\273\223\346\236\204/Pasted image 20241226140923.webp" "b/source/images/\350\256\241\347\256\227\346\234\272\351\253\230\347\272\247\347\263\273\347\273\237\347\273\223\346\236\204/Pasted image 20241226140923.webp" deleted file mode 100644 index 2b3f34f..0000000 Binary files "a/source/images/\350\256\241\347\256\227\346\234\272\351\253\230\347\272\247\347\263\273\347\273\237\347\273\223\346\236\204/Pasted image 20241226140923.webp" and /dev/null differ diff --git "a/source/images/\350\256\241\347\256\227\346\234\272\351\253\230\347\272\247\347\263\273\347\273\237\347\273\223\346\236\204/Pasted image 20241226140933.webp" "b/source/images/\350\256\241\347\256\227\346\234\272\351\253\230\347\272\247\347\263\273\347\273\237\347\273\223\346\236\204/Pasted image 20241226140933.webp" deleted file mode 100644 index d2b58bb..0000000 Binary files "a/source/images/\350\256\241\347\256\227\346\234\272\351\253\230\347\272\247\347\263\273\347\273\237\347\273\223\346\236\204/Pasted image 20241226140933.webp" and /dev/null differ diff --git "a/source/images/\350\256\241\347\256\227\346\234\272\351\253\230\347\272\247\347\263\273\347\273\237\347\273\223\346\236\204/Pasted image 20241226140947.webp" "b/source/images/\350\256\241\347\256\227\346\234\272\351\253\230\347\272\247\347\263\273\347\273\237\347\273\223\346\236\204/Pasted image 20241226140947.webp" deleted file mode 100644 index 25e5e50..0000000 Binary files "a/source/images/\350\256\241\347\256\227\346\234\272\351\253\230\347\272\247\347\263\273\347\273\237\347\273\223\346\236\204/Pasted image 20241226140947.webp" and /dev/null differ diff --git "a/source/images/\350\256\241\347\256\227\346\234\272\351\253\230\347\272\247\347\263\273\347\273\237\347\273\223\346\236\204/Pasted image 20241226141117.webp" "b/source/images/\350\256\241\347\256\227\346\234\272\351\253\230\347\272\247\347\263\273\347\273\237\347\273\223\346\236\204/Pasted image 20241226141117.webp" deleted file mode 100644 index 3455ad9..0000000 Binary files "a/source/images/\350\256\241\347\256\227\346\234\272\351\253\230\347\272\247\347\263\273\347\273\237\347\273\223\346\236\204/Pasted image 20241226141117.webp" and /dev/null differ diff --git "a/source/images/\350\256\241\347\256\227\346\234\272\351\253\230\347\272\247\347\263\273\347\273\237\347\273\223\346\236\204/Pasted image 20241226141154.webp" "b/source/images/\350\256\241\347\256\227\346\234\272\351\253\230\347\272\247\347\263\273\347\273\237\347\273\223\346\236\204/Pasted image 20241226141154.webp" deleted file mode 100644 index 6c24ef0..0000000 Binary files "a/source/images/\350\256\241\347\256\227\346\234\272\351\253\230\347\272\247\347\263\273\347\273\237\347\273\223\346\236\204/Pasted image 20241226141154.webp" and /dev/null differ diff --git "a/source/images/\350\256\241\347\256\227\346\234\272\351\253\230\347\272\247\347\263\273\347\273\237\347\273\223\346\236\204/Pasted image 20241226141328.webp" "b/source/images/\350\256\241\347\256\227\346\234\272\351\253\230\347\272\247\347\263\273\347\273\237\347\273\223\346\236\204/Pasted image 20241226141328.webp" deleted file mode 100644 index 23aa8dc..0000000 Binary files "a/source/images/\350\256\241\347\256\227\346\234\272\351\253\230\347\272\247\347\263\273\347\273\237\347\273\223\346\236\204/Pasted image 20241226141328.webp" and /dev/null differ diff --git "a/source/images/\350\256\241\347\256\227\346\234\272\351\253\230\347\272\247\347\263\273\347\273\237\347\273\223\346\236\204/Pasted image 20241226141355.webp" "b/source/images/\350\256\241\347\256\227\346\234\272\351\253\230\347\272\247\347\263\273\347\273\237\347\273\223\346\236\204/Pasted image 20241226141355.webp" deleted file mode 100644 index cd227d4..0000000 Binary files "a/source/images/\350\256\241\347\256\227\346\234\272\351\253\230\347\272\247\347\263\273\347\273\237\347\273\223\346\236\204/Pasted image 20241226141355.webp" and /dev/null differ diff --git "a/source/images/\350\256\241\347\256\227\346\234\272\351\253\230\347\272\247\347\263\273\347\273\237\347\273\223\346\236\204/Pasted image 20241226141407.webp" "b/source/images/\350\256\241\347\256\227\346\234\272\351\253\230\347\272\247\347\263\273\347\273\237\347\273\223\346\236\204/Pasted image 20241226141407.webp" deleted file mode 100644 index 08295b2..0000000 Binary files "a/source/images/\350\256\241\347\256\227\346\234\272\351\253\230\347\272\247\347\263\273\347\273\237\347\273\223\346\236\204/Pasted image 20241226141407.webp" and /dev/null differ diff --git "a/source/images/\350\256\241\347\256\227\346\234\272\351\253\230\347\272\247\347\263\273\347\273\237\347\273\223\346\236\204/Pasted image 20241226141524.webp" "b/source/images/\350\256\241\347\256\227\346\234\272\351\253\230\347\272\247\347\263\273\347\273\237\347\273\223\346\236\204/Pasted image 20241226141524.webp" deleted file mode 100644 index 23cb84c..0000000 Binary files "a/source/images/\350\256\241\347\256\227\346\234\272\351\253\230\347\272\247\347\263\273\347\273\237\347\273\223\346\236\204/Pasted image 20241226141524.webp" and /dev/null differ diff --git "a/source/images/\350\256\241\347\256\227\346\234\272\351\253\230\347\272\247\347\263\273\347\273\237\347\273\223\346\236\204/Pasted image 20241226141531.webp" "b/source/images/\350\256\241\347\256\227\346\234\272\351\253\230\347\272\247\347\263\273\347\273\237\347\273\223\346\236\204/Pasted image 20241226141531.webp" deleted file mode 100644 index e44513b..0000000 Binary files "a/source/images/\350\256\241\347\256\227\346\234\272\351\253\230\347\272\247\347\263\273\347\273\237\347\273\223\346\236\204/Pasted image 20241226141531.webp" and /dev/null differ diff --git "a/source/images/\350\256\241\347\256\227\346\234\272\351\253\230\347\272\247\347\263\273\347\273\237\347\273\223\346\236\204/Pasted image 20241226141648.webp" "b/source/images/\350\256\241\347\256\227\346\234\272\351\253\230\347\272\247\347\263\273\347\273\237\347\273\223\346\236\204/Pasted image 20241226141648.webp" deleted file mode 100644 index 403610d..0000000 Binary files "a/source/images/\350\256\241\347\256\227\346\234\272\351\253\230\347\272\247\347\263\273\347\273\237\347\273\223\346\236\204/Pasted image 20241226141648.webp" and /dev/null differ diff --git "a/source/images/\350\256\241\347\256\227\346\234\272\351\253\230\347\272\247\347\263\273\347\273\237\347\273\223\346\236\204/Pasted image 20241226141726.webp" "b/source/images/\350\256\241\347\256\227\346\234\272\351\253\230\347\272\247\347\263\273\347\273\237\347\273\223\346\236\204/Pasted image 20241226141726.webp" deleted file mode 100644 index b30272a..0000000 Binary files "a/source/images/\350\256\241\347\256\227\346\234\272\351\253\230\347\272\247\347\263\273\347\273\237\347\273\223\346\236\204/Pasted image 20241226141726.webp" and /dev/null differ diff --git "a/source/images/\350\256\241\347\256\227\346\234\272\351\253\230\347\272\247\347\263\273\347\273\237\347\273\223\346\236\204/Pasted image 20241226141828.webp" "b/source/images/\350\256\241\347\256\227\346\234\272\351\253\230\347\272\247\347\263\273\347\273\237\347\273\223\346\236\204/Pasted image 20241226141828.webp" deleted file mode 100644 index 38c3a03..0000000 Binary files "a/source/images/\350\256\241\347\256\227\346\234\272\351\253\230\347\272\247\347\263\273\347\273\237\347\273\223\346\236\204/Pasted image 20241226141828.webp" and /dev/null differ diff --git "a/source/images/\350\256\241\347\256\227\346\234\272\351\253\230\347\272\247\347\263\273\347\273\237\347\273\223\346\236\204/Pasted image 20241226141910.webp" "b/source/images/\350\256\241\347\256\227\346\234\272\351\253\230\347\272\247\347\263\273\347\273\237\347\273\223\346\236\204/Pasted image 20241226141910.webp" deleted file mode 100644 index 972cdaf..0000000 Binary files "a/source/images/\350\256\241\347\256\227\346\234\272\351\253\230\347\272\247\347\263\273\347\273\237\347\273\223\346\236\204/Pasted image 20241226141910.webp" and /dev/null differ diff --git "a/source/images/\350\256\241\347\256\227\346\234\272\351\253\230\347\272\247\347\263\273\347\273\237\347\273\223\346\236\204/Pasted image 20241226141919.webp" "b/source/images/\350\256\241\347\256\227\346\234\272\351\253\230\347\272\247\347\263\273\347\273\237\347\273\223\346\236\204/Pasted image 20241226141919.webp" deleted file mode 100644 index d016f61..0000000 Binary files "a/source/images/\350\256\241\347\256\227\346\234\272\351\253\230\347\272\247\347\263\273\347\273\237\347\273\223\346\236\204/Pasted image 20241226141919.webp" and /dev/null differ diff --git "a/source/images/\350\256\241\347\256\227\346\234\272\351\253\230\347\272\247\347\263\273\347\273\237\347\273\223\346\236\204/Pasted image 20241226142150.webp" "b/source/images/\350\256\241\347\256\227\346\234\272\351\253\230\347\272\247\347\263\273\347\273\237\347\273\223\346\236\204/Pasted image 20241226142150.webp" deleted file mode 100644 index 3ecb483..0000000 Binary files "a/source/images/\350\256\241\347\256\227\346\234\272\351\253\230\347\272\247\347\263\273\347\273\237\347\273\223\346\236\204/Pasted image 20241226142150.webp" and /dev/null differ diff --git "a/source/images/\350\256\241\347\256\227\346\234\272\351\253\230\347\272\247\347\263\273\347\273\237\347\273\223\346\236\204/Pasted image 20241226142202.webp" "b/source/images/\350\256\241\347\256\227\346\234\272\351\253\230\347\272\247\347\263\273\347\273\237\347\273\223\346\236\204/Pasted image 20241226142202.webp" deleted file mode 100644 index 03b6551..0000000 Binary files "a/source/images/\350\256\241\347\256\227\346\234\272\351\253\230\347\272\247\347\263\273\347\273\237\347\273\223\346\236\204/Pasted image 20241226142202.webp" and /dev/null differ diff --git "a/source/images/\350\256\241\347\256\227\346\234\272\351\253\230\347\272\247\347\263\273\347\273\237\347\273\223\346\236\204/Pasted image 20241226142341.webp" "b/source/images/\350\256\241\347\256\227\346\234\272\351\253\230\347\272\247\347\263\273\347\273\237\347\273\223\346\236\204/Pasted image 20241226142341.webp" deleted file mode 100644 index c533617..0000000 Binary files "a/source/images/\350\256\241\347\256\227\346\234\272\351\253\230\347\272\247\347\263\273\347\273\237\347\273\223\346\236\204/Pasted image 20241226142341.webp" and /dev/null differ diff --git "a/source/images/\350\256\241\347\256\227\346\234\272\351\253\230\347\272\247\347\263\273\347\273\237\347\273\223\346\236\204/Pasted image 20241226142346.webp" "b/source/images/\350\256\241\347\256\227\346\234\272\351\253\230\347\272\247\347\263\273\347\273\237\347\273\223\346\236\204/Pasted image 20241226142346.webp" deleted file mode 100644 index 9f82d02..0000000 Binary files "a/source/images/\350\256\241\347\256\227\346\234\272\351\253\230\347\272\247\347\263\273\347\273\237\347\273\223\346\236\204/Pasted image 20241226142346.webp" and /dev/null differ diff --git "a/source/images/\350\256\241\347\256\227\346\234\272\351\253\230\347\272\247\347\263\273\347\273\237\347\273\223\346\236\204/Pasted image 20241226142353.webp" "b/source/images/\350\256\241\347\256\227\346\234\272\351\253\230\347\272\247\347\263\273\347\273\237\347\273\223\346\236\204/Pasted image 20241226142353.webp" deleted file mode 100644 index 97a1f51..0000000 Binary files "a/source/images/\350\256\241\347\256\227\346\234\272\351\253\230\347\272\247\347\263\273\347\273\237\347\273\223\346\236\204/Pasted image 20241226142353.webp" and /dev/null differ diff --git "a/source/images/\350\256\241\347\256\227\346\234\272\351\253\230\347\272\247\347\263\273\347\273\237\347\273\223\346\236\204/Pasted image 20241226142537.webp" "b/source/images/\350\256\241\347\256\227\346\234\272\351\253\230\347\272\247\347\263\273\347\273\237\347\273\223\346\236\204/Pasted image 20241226142537.webp" deleted file mode 100644 index 4174ad2..0000000 Binary files "a/source/images/\350\256\241\347\256\227\346\234\272\351\253\230\347\272\247\347\263\273\347\273\237\347\273\223\346\236\204/Pasted image 20241226142537.webp" and /dev/null differ diff --git "a/source/images/\350\256\241\347\256\227\346\234\272\351\253\230\347\272\247\347\263\273\347\273\237\347\273\223\346\236\204/Pasted image 20241226142542.webp" "b/source/images/\350\256\241\347\256\227\346\234\272\351\253\230\347\272\247\347\263\273\347\273\237\347\273\223\346\236\204/Pasted image 20241226142542.webp" deleted file mode 100644 index 7054311..0000000 Binary files "a/source/images/\350\256\241\347\256\227\346\234\272\351\253\230\347\272\247\347\263\273\347\273\237\347\273\223\346\236\204/Pasted image 20241226142542.webp" and /dev/null differ diff --git "a/source/images/\350\256\241\347\256\227\346\234\272\351\253\230\347\272\247\347\263\273\347\273\237\347\273\223\346\236\204/Pasted image 20241226142618.webp" "b/source/images/\350\256\241\347\256\227\346\234\272\351\253\230\347\272\247\347\263\273\347\273\237\347\273\223\346\236\204/Pasted image 20241226142618.webp" deleted file mode 100644 index 764dd4b..0000000 Binary files "a/source/images/\350\256\241\347\256\227\346\234\272\351\253\230\347\272\247\347\263\273\347\273\237\347\273\223\346\236\204/Pasted image 20241226142618.webp" and /dev/null differ diff --git "a/source/images/\350\256\241\347\256\227\346\234\272\351\253\230\347\272\247\347\263\273\347\273\237\347\273\223\346\236\204/Pasted image 20241226142636.webp" "b/source/images/\350\256\241\347\256\227\346\234\272\351\253\230\347\272\247\347\263\273\347\273\237\347\273\223\346\236\204/Pasted image 20241226142636.webp" deleted file mode 100644 index ea29171..0000000 Binary files "a/source/images/\350\256\241\347\256\227\346\234\272\351\253\230\347\272\247\347\263\273\347\273\237\347\273\223\346\236\204/Pasted image 20241226142636.webp" and /dev/null differ diff --git "a/source/images/\350\256\241\347\256\227\346\234\272\351\253\230\347\272\247\347\263\273\347\273\237\347\273\223\346\236\204/Pasted image 20241226142647.webp" "b/source/images/\350\256\241\347\256\227\346\234\272\351\253\230\347\272\247\347\263\273\347\273\237\347\273\223\346\236\204/Pasted image 20241226142647.webp" deleted file mode 100644 index 269b50a..0000000 Binary files "a/source/images/\350\256\241\347\256\227\346\234\272\351\253\230\347\272\247\347\263\273\347\273\237\347\273\223\346\236\204/Pasted image 20241226142647.webp" and /dev/null differ diff --git "a/source/images/\350\256\241\347\256\227\346\234\272\351\253\230\347\272\247\347\263\273\347\273\237\347\273\223\346\236\204/Pasted image 20241226142655.webp" "b/source/images/\350\256\241\347\256\227\346\234\272\351\253\230\347\272\247\347\263\273\347\273\237\347\273\223\346\236\204/Pasted image 20241226142655.webp" deleted file mode 100644 index 266f0d3..0000000 Binary files "a/source/images/\350\256\241\347\256\227\346\234\272\351\253\230\347\272\247\347\263\273\347\273\237\347\273\223\346\236\204/Pasted image 20241226142655.webp" and /dev/null differ diff --git "a/source/images/\350\256\241\347\256\227\346\234\272\351\253\230\347\272\247\347\263\273\347\273\237\347\273\223\346\236\204/Pasted image 20241226142702.webp" "b/source/images/\350\256\241\347\256\227\346\234\272\351\253\230\347\272\247\347\263\273\347\273\237\347\273\223\346\236\204/Pasted image 20241226142702.webp" deleted file mode 100644 index b9b3511..0000000 Binary files "a/source/images/\350\256\241\347\256\227\346\234\272\351\253\230\347\272\247\347\263\273\347\273\237\347\273\223\346\236\204/Pasted image 20241226142702.webp" and /dev/null differ diff --git "a/source/images/\350\256\241\347\256\227\346\234\272\351\253\230\347\272\247\347\263\273\347\273\237\347\273\223\346\236\204/Pasted image 20241226142713.webp" "b/source/images/\350\256\241\347\256\227\346\234\272\351\253\230\347\272\247\347\263\273\347\273\237\347\273\223\346\236\204/Pasted image 20241226142713.webp" deleted file mode 100644 index 3a0a99a..0000000 Binary files "a/source/images/\350\256\241\347\256\227\346\234\272\351\253\230\347\272\247\347\263\273\347\273\237\347\273\223\346\236\204/Pasted image 20241226142713.webp" and /dev/null differ diff --git "a/source/images/\350\256\241\347\256\227\346\234\272\351\253\230\347\272\247\347\263\273\347\273\237\347\273\223\346\236\204/Pasted image 20241226142720.webp" "b/source/images/\350\256\241\347\256\227\346\234\272\351\253\230\347\272\247\347\263\273\347\273\237\347\273\223\346\236\204/Pasted image 20241226142720.webp" deleted file mode 100644 index 37cbdac..0000000 Binary files "a/source/images/\350\256\241\347\256\227\346\234\272\351\253\230\347\272\247\347\263\273\347\273\237\347\273\223\346\236\204/Pasted image 20241226142720.webp" and /dev/null differ diff --git "a/source/images/\350\256\241\347\256\227\346\234\272\351\253\230\347\272\247\347\263\273\347\273\237\347\273\223\346\236\204/Pasted image 20241226142730.webp" "b/source/images/\350\256\241\347\256\227\346\234\272\351\253\230\347\272\247\347\263\273\347\273\237\347\273\223\346\236\204/Pasted image 20241226142730.webp" deleted file mode 100644 index 2dcc1d2..0000000 Binary files "a/source/images/\350\256\241\347\256\227\346\234\272\351\253\230\347\272\247\347\263\273\347\273\237\347\273\223\346\236\204/Pasted image 20241226142730.webp" and /dev/null differ diff --git "a/source/images/\350\256\241\347\256\227\346\234\272\351\253\230\347\272\247\347\263\273\347\273\237\347\273\223\346\236\204/Pasted image 20241226142741.webp" "b/source/images/\350\256\241\347\256\227\346\234\272\351\253\230\347\272\247\347\263\273\347\273\237\347\273\223\346\236\204/Pasted image 20241226142741.webp" deleted file mode 100644 index a123bd7..0000000 Binary files "a/source/images/\350\256\241\347\256\227\346\234\272\351\253\230\347\272\247\347\263\273\347\273\237\347\273\223\346\236\204/Pasted image 20241226142741.webp" and /dev/null differ diff --git "a/source/images/\350\256\241\347\256\227\346\234\272\351\253\230\347\272\247\347\263\273\347\273\237\347\273\223\346\236\204/Pasted image 20241226142752.webp" "b/source/images/\350\256\241\347\256\227\346\234\272\351\253\230\347\272\247\347\263\273\347\273\237\347\273\223\346\236\204/Pasted image 20241226142752.webp" deleted file mode 100644 index 52665ba..0000000 Binary files "a/source/images/\350\256\241\347\256\227\346\234\272\351\253\230\347\272\247\347\263\273\347\273\237\347\273\223\346\236\204/Pasted image 20241226142752.webp" and /dev/null differ diff --git "a/source/images/\350\256\241\347\256\227\346\234\272\351\253\230\347\272\247\347\263\273\347\273\237\347\273\223\346\236\204/Pasted image 20241226142810.webp" "b/source/images/\350\256\241\347\256\227\346\234\272\351\253\230\347\272\247\347\263\273\347\273\237\347\273\223\346\236\204/Pasted image 20241226142810.webp" deleted file mode 100644 index c66f257..0000000 Binary files "a/source/images/\350\256\241\347\256\227\346\234\272\351\253\230\347\272\247\347\263\273\347\273\237\347\273\223\346\236\204/Pasted image 20241226142810.webp" and /dev/null differ diff --git "a/source/images/\350\256\241\347\256\227\346\234\272\351\253\230\347\272\247\347\263\273\347\273\237\347\273\223\346\236\204/Pasted image 20241226142824.webp" "b/source/images/\350\256\241\347\256\227\346\234\272\351\253\230\347\272\247\347\263\273\347\273\237\347\273\223\346\236\204/Pasted image 20241226142824.webp" deleted file mode 100644 index 7b0e0a4..0000000 Binary files "a/source/images/\350\256\241\347\256\227\346\234\272\351\253\230\347\272\247\347\263\273\347\273\237\347\273\223\346\236\204/Pasted image 20241226142824.webp" and /dev/null differ diff --git "a/source/images/\350\256\241\347\256\227\346\234\272\351\253\230\347\272\247\347\263\273\347\273\237\347\273\223\346\236\204/Pasted image 20241226142933.webp" "b/source/images/\350\256\241\347\256\227\346\234\272\351\253\230\347\272\247\347\263\273\347\273\237\347\273\223\346\236\204/Pasted image 20241226142933.webp" deleted file mode 100644 index 4d3576e..0000000 Binary files "a/source/images/\350\256\241\347\256\227\346\234\272\351\253\230\347\272\247\347\263\273\347\273\237\347\273\223\346\236\204/Pasted image 20241226142933.webp" and /dev/null differ diff --git "a/source/images/\350\256\241\347\256\227\346\234\272\351\253\230\347\272\247\347\263\273\347\273\237\347\273\223\346\236\204/Pasted image 20241226142949.webp" "b/source/images/\350\256\241\347\256\227\346\234\272\351\253\230\347\272\247\347\263\273\347\273\237\347\273\223\346\236\204/Pasted image 20241226142949.webp" deleted file mode 100644 index f23b8e8..0000000 Binary files "a/source/images/\350\256\241\347\256\227\346\234\272\351\253\230\347\272\247\347\263\273\347\273\237\347\273\223\346\236\204/Pasted image 20241226142949.webp" and /dev/null differ diff --git "a/source/images/\350\256\241\347\256\227\346\234\272\351\253\230\347\272\247\347\263\273\347\273\237\347\273\223\346\236\204/Pasted image 20241226143053.webp" "b/source/images/\350\256\241\347\256\227\346\234\272\351\253\230\347\272\247\347\263\273\347\273\237\347\273\223\346\236\204/Pasted image 20241226143053.webp" deleted file mode 100644 index c509952..0000000 Binary files "a/source/images/\350\256\241\347\256\227\346\234\272\351\253\230\347\272\247\347\263\273\347\273\237\347\273\223\346\236\204/Pasted image 20241226143053.webp" and /dev/null differ diff --git "a/source/images/\350\256\241\347\256\227\346\234\272\351\253\230\347\272\247\347\263\273\347\273\237\347\273\223\346\236\204/Pasted image 20241226143058.webp" "b/source/images/\350\256\241\347\256\227\346\234\272\351\253\230\347\272\247\347\263\273\347\273\237\347\273\223\346\236\204/Pasted image 20241226143058.webp" deleted file mode 100644 index c8cfbeb..0000000 Binary files "a/source/images/\350\256\241\347\256\227\346\234\272\351\253\230\347\272\247\347\263\273\347\273\237\347\273\223\346\236\204/Pasted image 20241226143058.webp" and /dev/null differ diff --git "a/source/images/\350\256\241\347\256\227\346\234\272\351\253\230\347\272\247\347\263\273\347\273\237\347\273\223\346\236\204/Pasted image 20241226143225.webp" "b/source/images/\350\256\241\347\256\227\346\234\272\351\253\230\347\272\247\347\263\273\347\273\237\347\273\223\346\236\204/Pasted image 20241226143225.webp" deleted file mode 100644 index 1e0a973..0000000 Binary files "a/source/images/\350\256\241\347\256\227\346\234\272\351\253\230\347\272\247\347\263\273\347\273\237\347\273\223\346\236\204/Pasted image 20241226143225.webp" and /dev/null differ diff --git "a/source/images/\350\256\241\347\256\227\346\234\272\351\253\230\347\272\247\347\263\273\347\273\237\347\273\223\346\236\204/Pasted image 20241226144553.webp" "b/source/images/\350\256\241\347\256\227\346\234\272\351\253\230\347\272\247\347\263\273\347\273\237\347\273\223\346\236\204/Pasted image 20241226144553.webp" deleted file mode 100644 index 72d28b3..0000000 Binary files "a/source/images/\350\256\241\347\256\227\346\234\272\351\253\230\347\272\247\347\263\273\347\273\237\347\273\223\346\236\204/Pasted image 20241226144553.webp" and /dev/null differ diff --git "a/source/images/\350\256\241\347\256\227\346\234\272\351\253\230\347\272\247\347\263\273\347\273\237\347\273\223\346\236\204/Pasted image 20241226144559.webp" "b/source/images/\350\256\241\347\256\227\346\234\272\351\253\230\347\272\247\347\263\273\347\273\237\347\273\223\346\236\204/Pasted image 20241226144559.webp" deleted file mode 100644 index d82d920..0000000 Binary files "a/source/images/\350\256\241\347\256\227\346\234\272\351\253\230\347\272\247\347\263\273\347\273\237\347\273\223\346\236\204/Pasted image 20241226144559.webp" and /dev/null differ diff --git "a/source/images/\350\256\241\347\256\227\346\234\272\351\253\230\347\272\247\347\263\273\347\273\237\347\273\223\346\236\204/Pasted image 20241226144616.webp" "b/source/images/\350\256\241\347\256\227\346\234\272\351\253\230\347\272\247\347\263\273\347\273\237\347\273\223\346\236\204/Pasted image 20241226144616.webp" deleted file mode 100644 index 6d5272d..0000000 Binary files "a/source/images/\350\256\241\347\256\227\346\234\272\351\253\230\347\272\247\347\263\273\347\273\237\347\273\223\346\236\204/Pasted image 20241226144616.webp" and /dev/null differ diff --git "a/source/images/\350\256\241\347\256\227\346\234\272\351\253\230\347\272\247\347\263\273\347\273\237\347\273\223\346\236\204/Pasted image 20241226144705.webp" "b/source/images/\350\256\241\347\256\227\346\234\272\351\253\230\347\272\247\347\263\273\347\273\237\347\273\223\346\236\204/Pasted image 20241226144705.webp" deleted file mode 100644 index aee398d..0000000 Binary files "a/source/images/\350\256\241\347\256\227\346\234\272\351\253\230\347\272\247\347\263\273\347\273\237\347\273\223\346\236\204/Pasted image 20241226144705.webp" and /dev/null differ diff --git "a/source/images/\350\256\241\347\256\227\346\234\272\351\253\230\347\272\247\347\263\273\347\273\237\347\273\223\346\236\204/Pasted image 20241226144726.webp" "b/source/images/\350\256\241\347\256\227\346\234\272\351\253\230\347\272\247\347\263\273\347\273\237\347\273\223\346\236\204/Pasted image 20241226144726.webp" deleted file mode 100644 index 2200fb6..0000000 Binary files "a/source/images/\350\256\241\347\256\227\346\234\272\351\253\230\347\272\247\347\263\273\347\273\237\347\273\223\346\236\204/Pasted image 20241226144726.webp" and /dev/null differ diff --git "a/source/images/\350\256\241\347\256\227\346\234\272\351\253\230\347\272\247\347\263\273\347\273\237\347\273\223\346\236\204/Pasted image 20241226144745.webp" "b/source/images/\350\256\241\347\256\227\346\234\272\351\253\230\347\272\247\347\263\273\347\273\237\347\273\223\346\236\204/Pasted image 20241226144745.webp" deleted file mode 100644 index a1ef6a8..0000000 Binary files "a/source/images/\350\256\241\347\256\227\346\234\272\351\253\230\347\272\247\347\263\273\347\273\237\347\273\223\346\236\204/Pasted image 20241226144745.webp" and /dev/null differ diff --git "a/source/images/\350\256\241\347\256\227\346\234\272\351\253\230\347\272\247\347\263\273\347\273\237\347\273\223\346\236\204/Pasted image 20241226144852.webp" "b/source/images/\350\256\241\347\256\227\346\234\272\351\253\230\347\272\247\347\263\273\347\273\237\347\273\223\346\236\204/Pasted image 20241226144852.webp" deleted file mode 100644 index a06969f..0000000 Binary files "a/source/images/\350\256\241\347\256\227\346\234\272\351\253\230\347\272\247\347\263\273\347\273\237\347\273\223\346\236\204/Pasted image 20241226144852.webp" and /dev/null differ diff --git "a/source/images/\350\256\241\347\256\227\346\234\272\351\253\230\347\272\247\347\263\273\347\273\237\347\273\223\346\236\204/Pasted image 20241226144916.webp" "b/source/images/\350\256\241\347\256\227\346\234\272\351\253\230\347\272\247\347\263\273\347\273\237\347\273\223\346\236\204/Pasted image 20241226144916.webp" deleted file mode 100644 index 7b38eb3..0000000 Binary files "a/source/images/\350\256\241\347\256\227\346\234\272\351\253\230\347\272\247\347\263\273\347\273\237\347\273\223\346\236\204/Pasted image 20241226144916.webp" and /dev/null differ diff --git "a/source/images/\350\256\241\347\256\227\346\234\272\351\253\230\347\272\247\347\263\273\347\273\237\347\273\223\346\236\204/Pasted image 20241226144958.webp" "b/source/images/\350\256\241\347\256\227\346\234\272\351\253\230\347\272\247\347\263\273\347\273\237\347\273\223\346\236\204/Pasted image 20241226144958.webp" deleted file mode 100644 index 0c20408..0000000 Binary files "a/source/images/\350\256\241\347\256\227\346\234\272\351\253\230\347\272\247\347\263\273\347\273\237\347\273\223\346\236\204/Pasted image 20241226144958.webp" and /dev/null differ diff --git "a/source/images/\350\256\241\347\256\227\346\234\272\351\253\230\347\272\247\347\263\273\347\273\237\347\273\223\346\236\204/Pasted image 20241226145009.webp" "b/source/images/\350\256\241\347\256\227\346\234\272\351\253\230\347\272\247\347\263\273\347\273\237\347\273\223\346\236\204/Pasted image 20241226145009.webp" deleted file mode 100644 index ba2a77b..0000000 Binary files "a/source/images/\350\256\241\347\256\227\346\234\272\351\253\230\347\272\247\347\263\273\347\273\237\347\273\223\346\236\204/Pasted image 20241226145009.webp" and /dev/null differ diff --git "a/source/images/\350\256\241\347\256\227\346\234\272\351\253\230\347\272\247\347\263\273\347\273\237\347\273\223\346\236\204/Pasted image 20241226145049.webp" "b/source/images/\350\256\241\347\256\227\346\234\272\351\253\230\347\272\247\347\263\273\347\273\237\347\273\223\346\236\204/Pasted image 20241226145049.webp" deleted file mode 100644 index a05b76e..0000000 Binary files "a/source/images/\350\256\241\347\256\227\346\234\272\351\253\230\347\272\247\347\263\273\347\273\237\347\273\223\346\236\204/Pasted image 20241226145049.webp" and /dev/null differ diff --git "a/source/images/\350\256\241\347\256\227\346\234\272\351\253\230\347\272\247\347\263\273\347\273\237\347\273\223\346\236\204/Pasted image 20241226145102.webp" "b/source/images/\350\256\241\347\256\227\346\234\272\351\253\230\347\272\247\347\263\273\347\273\237\347\273\223\346\236\204/Pasted image 20241226145102.webp" deleted file mode 100644 index 8692e66..0000000 Binary files "a/source/images/\350\256\241\347\256\227\346\234\272\351\253\230\347\272\247\347\263\273\347\273\237\347\273\223\346\236\204/Pasted image 20241226145102.webp" and /dev/null differ diff --git "a/source/images/\350\256\241\347\256\227\346\234\272\351\253\230\347\272\247\347\263\273\347\273\237\347\273\223\346\236\204/Pasted image 20241226145112.webp" "b/source/images/\350\256\241\347\256\227\346\234\272\351\253\230\347\272\247\347\263\273\347\273\237\347\273\223\346\236\204/Pasted image 20241226145112.webp" deleted file mode 100644 index cd7cd5f..0000000 Binary files "a/source/images/\350\256\241\347\256\227\346\234\272\351\253\230\347\272\247\347\263\273\347\273\237\347\273\223\346\236\204/Pasted image 20241226145112.webp" and /dev/null differ diff --git "a/source/images/\350\256\241\347\256\227\346\234\272\351\253\230\347\272\247\347\263\273\347\273\237\347\273\223\346\236\204/Pasted image 20241226145157.webp" "b/source/images/\350\256\241\347\256\227\346\234\272\351\253\230\347\272\247\347\263\273\347\273\237\347\273\223\346\236\204/Pasted image 20241226145157.webp" deleted file mode 100644 index 2a05301..0000000 Binary files "a/source/images/\350\256\241\347\256\227\346\234\272\351\253\230\347\272\247\347\263\273\347\273\237\347\273\223\346\236\204/Pasted image 20241226145157.webp" and /dev/null differ diff --git "a/source/images/\350\256\241\347\256\227\346\234\272\351\253\230\347\272\247\347\263\273\347\273\237\347\273\223\346\236\204/Pasted image 20241226145215.webp" "b/source/images/\350\256\241\347\256\227\346\234\272\351\253\230\347\272\247\347\263\273\347\273\237\347\273\223\346\236\204/Pasted image 20241226145215.webp" deleted file mode 100644 index 1ba528d..0000000 Binary files "a/source/images/\350\256\241\347\256\227\346\234\272\351\253\230\347\272\247\347\263\273\347\273\237\347\273\223\346\236\204/Pasted image 20241226145215.webp" and /dev/null differ diff --git "a/source/images/\350\256\241\347\256\227\346\234\272\351\253\230\347\272\247\347\263\273\347\273\237\347\273\223\346\236\204/Pasted image 20241226145440.webp" "b/source/images/\350\256\241\347\256\227\346\234\272\351\253\230\347\272\247\347\263\273\347\273\237\347\273\223\346\236\204/Pasted image 20241226145440.webp" deleted file mode 100644 index efc465f..0000000 Binary files "a/source/images/\350\256\241\347\256\227\346\234\272\351\253\230\347\272\247\347\263\273\347\273\237\347\273\223\346\236\204/Pasted image 20241226145440.webp" and /dev/null differ diff --git "a/source/images/\350\256\241\347\256\227\346\234\272\351\253\230\347\272\247\347\263\273\347\273\237\347\273\223\346\236\204/Pasted image 20241226145509.webp" "b/source/images/\350\256\241\347\256\227\346\234\272\351\253\230\347\272\247\347\263\273\347\273\237\347\273\223\346\236\204/Pasted image 20241226145509.webp" deleted file mode 100644 index cff77c5..0000000 Binary files "a/source/images/\350\256\241\347\256\227\346\234\272\351\253\230\347\272\247\347\263\273\347\273\237\347\273\223\346\236\204/Pasted image 20241226145509.webp" and /dev/null differ diff --git "a/source/images/\350\256\241\347\256\227\346\234\272\351\253\230\347\272\247\347\263\273\347\273\237\347\273\223\346\236\204/Pasted image 20241226145535.webp" "b/source/images/\350\256\241\347\256\227\346\234\272\351\253\230\347\272\247\347\263\273\347\273\237\347\273\223\346\236\204/Pasted image 20241226145535.webp" deleted file mode 100644 index c0b75cd..0000000 Binary files "a/source/images/\350\256\241\347\256\227\346\234\272\351\253\230\347\272\247\347\263\273\347\273\237\347\273\223\346\236\204/Pasted image 20241226145535.webp" and /dev/null differ diff --git "a/source/images/\350\256\241\347\256\227\346\234\272\351\253\230\347\272\247\347\263\273\347\273\237\347\273\223\346\236\204/Pasted image 20241226145550.webp" "b/source/images/\350\256\241\347\256\227\346\234\272\351\253\230\347\272\247\347\263\273\347\273\237\347\273\223\346\236\204/Pasted image 20241226145550.webp" deleted file mode 100644 index fc8bfa1..0000000 Binary files "a/source/images/\350\256\241\347\256\227\346\234\272\351\253\230\347\272\247\347\263\273\347\273\237\347\273\223\346\236\204/Pasted image 20241226145550.webp" and /dev/null differ diff --git "a/source/images/\350\256\241\347\256\227\346\234\272\351\253\230\347\272\247\347\263\273\347\273\237\347\273\223\346\236\204/Pasted image 20241226145702.webp" "b/source/images/\350\256\241\347\256\227\346\234\272\351\253\230\347\272\247\347\263\273\347\273\237\347\273\223\346\236\204/Pasted image 20241226145702.webp" deleted file mode 100644 index 489d32f..0000000 Binary files "a/source/images/\350\256\241\347\256\227\346\234\272\351\253\230\347\272\247\347\263\273\347\273\237\347\273\223\346\236\204/Pasted image 20241226145702.webp" and /dev/null differ diff --git "a/source/images/\350\256\241\347\256\227\346\234\272\351\253\230\347\272\247\347\263\273\347\273\237\347\273\223\346\236\204/Pasted image 20241226145808.webp" "b/source/images/\350\256\241\347\256\227\346\234\272\351\253\230\347\272\247\347\263\273\347\273\237\347\273\223\346\236\204/Pasted image 20241226145808.webp" deleted file mode 100644 index 9d50828..0000000 Binary files "a/source/images/\350\256\241\347\256\227\346\234\272\351\253\230\347\272\247\347\263\273\347\273\237\347\273\223\346\236\204/Pasted image 20241226145808.webp" and /dev/null differ diff --git "a/source/images/\350\256\241\347\256\227\346\234\272\351\253\230\347\272\247\347\263\273\347\273\237\347\273\223\346\236\204/Pasted image 20241226145816.webp" "b/source/images/\350\256\241\347\256\227\346\234\272\351\253\230\347\272\247\347\263\273\347\273\237\347\273\223\346\236\204/Pasted image 20241226145816.webp" deleted file mode 100644 index de010d4..0000000 Binary files "a/source/images/\350\256\241\347\256\227\346\234\272\351\253\230\347\272\247\347\263\273\347\273\237\347\273\223\346\236\204/Pasted image 20241226145816.webp" and /dev/null differ diff --git "a/source/images/\350\256\241\347\256\227\346\234\272\351\253\230\347\272\247\347\263\273\347\273\237\347\273\223\346\236\204/Pasted image 20241226145830.webp" "b/source/images/\350\256\241\347\256\227\346\234\272\351\253\230\347\272\247\347\263\273\347\273\237\347\273\223\346\236\204/Pasted image 20241226145830.webp" deleted file mode 100644 index eb26b65..0000000 Binary files "a/source/images/\350\256\241\347\256\227\346\234\272\351\253\230\347\272\247\347\263\273\347\273\237\347\273\223\346\236\204/Pasted image 20241226145830.webp" and /dev/null differ diff --git "a/source/images/\350\256\241\347\256\227\346\234\272\351\253\230\347\272\247\347\263\273\347\273\237\347\273\223\346\236\204/Pasted image 20241226145843.webp" "b/source/images/\350\256\241\347\256\227\346\234\272\351\253\230\347\272\247\347\263\273\347\273\237\347\273\223\346\236\204/Pasted image 20241226145843.webp" deleted file mode 100644 index 033f99e..0000000 Binary files "a/source/images/\350\256\241\347\256\227\346\234\272\351\253\230\347\272\247\347\263\273\347\273\237\347\273\223\346\236\204/Pasted image 20241226145843.webp" and /dev/null differ diff --git "a/source/images/\350\256\241\347\256\227\346\234\272\351\253\230\347\272\247\347\263\273\347\273\237\347\273\223\346\236\204/Pasted image 20241226145909.webp" "b/source/images/\350\256\241\347\256\227\346\234\272\351\253\230\347\272\247\347\263\273\347\273\237\347\273\223\346\236\204/Pasted image 20241226145909.webp" deleted file mode 100644 index b568690..0000000 Binary files "a/source/images/\350\256\241\347\256\227\346\234\272\351\253\230\347\272\247\347\263\273\347\273\237\347\273\223\346\236\204/Pasted image 20241226145909.webp" and /dev/null differ diff --git "a/source/images/\350\256\241\347\256\227\346\234\272\351\253\230\347\272\247\347\263\273\347\273\237\347\273\223\346\236\204/Pasted image 20241226145922.webp" "b/source/images/\350\256\241\347\256\227\346\234\272\351\253\230\347\272\247\347\263\273\347\273\237\347\273\223\346\236\204/Pasted image 20241226145922.webp" deleted file mode 100644 index cc4f9a0..0000000 Binary files "a/source/images/\350\256\241\347\256\227\346\234\272\351\253\230\347\272\247\347\263\273\347\273\237\347\273\223\346\236\204/Pasted image 20241226145922.webp" and /dev/null differ diff --git "a/source/images/\350\256\241\347\256\227\346\234\272\351\253\230\347\272\247\347\263\273\347\273\237\347\273\223\346\236\204/Pasted image 20241226150322.webp" "b/source/images/\350\256\241\347\256\227\346\234\272\351\253\230\347\272\247\347\263\273\347\273\237\347\273\223\346\236\204/Pasted image 20241226150322.webp" deleted file mode 100644 index 2a46882..0000000 Binary files "a/source/images/\350\256\241\347\256\227\346\234\272\351\253\230\347\272\247\347\263\273\347\273\237\347\273\223\346\236\204/Pasted image 20241226150322.webp" and /dev/null differ diff --git "a/source/images/\350\256\241\347\256\227\346\234\272\351\253\230\347\272\247\347\263\273\347\273\237\347\273\223\346\236\204/Pasted image 20241226150359.webp" "b/source/images/\350\256\241\347\256\227\346\234\272\351\253\230\347\272\247\347\263\273\347\273\237\347\273\223\346\236\204/Pasted image 20241226150359.webp" deleted file mode 100644 index 4a8bbed..0000000 Binary files "a/source/images/\350\256\241\347\256\227\346\234\272\351\253\230\347\272\247\347\263\273\347\273\237\347\273\223\346\236\204/Pasted image 20241226150359.webp" and /dev/null differ diff --git "a/source/images/\350\256\241\347\256\227\346\234\272\351\253\230\347\272\247\347\263\273\347\273\237\347\273\223\346\236\204/Pasted image 20241226150508.webp" "b/source/images/\350\256\241\347\256\227\346\234\272\351\253\230\347\272\247\347\263\273\347\273\237\347\273\223\346\236\204/Pasted image 20241226150508.webp" deleted file mode 100644 index 40274b0..0000000 Binary files "a/source/images/\350\256\241\347\256\227\346\234\272\351\253\230\347\272\247\347\263\273\347\273\237\347\273\223\346\236\204/Pasted image 20241226150508.webp" and /dev/null differ diff --git "a/source/images/\350\256\241\347\256\227\346\234\272\351\253\230\347\272\247\347\263\273\347\273\237\347\273\223\346\236\204/Pasted image 20241226151838.webp" "b/source/images/\350\256\241\347\256\227\346\234\272\351\253\230\347\272\247\347\263\273\347\273\237\347\273\223\346\236\204/Pasted image 20241226151838.webp" deleted file mode 100644 index a967986..0000000 Binary files "a/source/images/\350\256\241\347\256\227\346\234\272\351\253\230\347\272\247\347\263\273\347\273\237\347\273\223\346\236\204/Pasted image 20241226151838.webp" and /dev/null differ diff --git "a/source/images/\350\256\241\347\256\227\346\234\272\351\253\230\347\272\247\347\263\273\347\273\237\347\273\223\346\236\204/Pasted image 20241226151850.webp" "b/source/images/\350\256\241\347\256\227\346\234\272\351\253\230\347\272\247\347\263\273\347\273\237\347\273\223\346\236\204/Pasted image 20241226151850.webp" deleted file mode 100644 index 7152e40..0000000 Binary files "a/source/images/\350\256\241\347\256\227\346\234\272\351\253\230\347\272\247\347\263\273\347\273\237\347\273\223\346\236\204/Pasted image 20241226151850.webp" and /dev/null differ diff --git "a/source/images/\350\256\241\347\256\227\346\234\272\351\253\230\347\272\247\347\263\273\347\273\237\347\273\223\346\236\204/Pasted image 20241226151917.webp" "b/source/images/\350\256\241\347\256\227\346\234\272\351\253\230\347\272\247\347\263\273\347\273\237\347\273\223\346\236\204/Pasted image 20241226151917.webp" deleted file mode 100644 index c6d1eb9..0000000 Binary files "a/source/images/\350\256\241\347\256\227\346\234\272\351\253\230\347\272\247\347\263\273\347\273\237\347\273\223\346\236\204/Pasted image 20241226151917.webp" and /dev/null differ diff --git "a/source/images/\350\256\241\347\256\227\346\234\272\351\253\230\347\272\247\347\263\273\347\273\237\347\273\223\346\236\204/Pasted image 20241226152028.webp" "b/source/images/\350\256\241\347\256\227\346\234\272\351\253\230\347\272\247\347\263\273\347\273\237\347\273\223\346\236\204/Pasted image 20241226152028.webp" deleted file mode 100644 index 3fd4e75..0000000 Binary files "a/source/images/\350\256\241\347\256\227\346\234\272\351\253\230\347\272\247\347\263\273\347\273\237\347\273\223\346\236\204/Pasted image 20241226152028.webp" and /dev/null differ diff --git "a/source/images/\350\256\241\347\256\227\346\234\272\351\253\230\347\272\247\347\263\273\347\273\237\347\273\223\346\236\204/Pasted image 20241226152035.webp" "b/source/images/\350\256\241\347\256\227\346\234\272\351\253\230\347\272\247\347\263\273\347\273\237\347\273\223\346\236\204/Pasted image 20241226152035.webp" deleted file mode 100644 index 9af5865..0000000 Binary files "a/source/images/\350\256\241\347\256\227\346\234\272\351\253\230\347\272\247\347\263\273\347\273\237\347\273\223\346\236\204/Pasted image 20241226152035.webp" and /dev/null differ diff --git "a/source/images/\350\256\241\347\256\227\346\234\272\351\253\230\347\272\247\347\263\273\347\273\237\347\273\223\346\236\204/Pasted image 20241226152053.webp" "b/source/images/\350\256\241\347\256\227\346\234\272\351\253\230\347\272\247\347\263\273\347\273\237\347\273\223\346\236\204/Pasted image 20241226152053.webp" deleted file mode 100644 index 039b5b9..0000000 Binary files "a/source/images/\350\256\241\347\256\227\346\234\272\351\253\230\347\272\247\347\263\273\347\273\237\347\273\223\346\236\204/Pasted image 20241226152053.webp" and /dev/null differ diff --git "a/source/images/\350\256\241\347\256\227\346\234\272\351\253\230\347\272\247\347\263\273\347\273\237\347\273\223\346\236\204/Pasted image 20241226152118.webp" "b/source/images/\350\256\241\347\256\227\346\234\272\351\253\230\347\272\247\347\263\273\347\273\237\347\273\223\346\236\204/Pasted image 20241226152118.webp" deleted file mode 100644 index eb304cd..0000000 Binary files "a/source/images/\350\256\241\347\256\227\346\234\272\351\253\230\347\272\247\347\263\273\347\273\237\347\273\223\346\236\204/Pasted image 20241226152118.webp" and /dev/null differ diff --git "a/source/images/\350\256\241\347\256\227\346\234\272\351\253\230\347\272\247\347\263\273\347\273\237\347\273\223\346\236\204/Pasted image 20241226152124.webp" "b/source/images/\350\256\241\347\256\227\346\234\272\351\253\230\347\272\247\347\263\273\347\273\237\347\273\223\346\236\204/Pasted image 20241226152124.webp" deleted file mode 100644 index e5320ec..0000000 Binary files "a/source/images/\350\256\241\347\256\227\346\234\272\351\253\230\347\272\247\347\263\273\347\273\237\347\273\223\346\236\204/Pasted image 20241226152124.webp" and /dev/null differ diff --git "a/source/images/\350\256\241\347\256\227\346\234\272\351\253\230\347\272\247\347\263\273\347\273\237\347\273\223\346\236\204/Pasted image 20241226152137.webp" "b/source/images/\350\256\241\347\256\227\346\234\272\351\253\230\347\272\247\347\263\273\347\273\237\347\273\223\346\236\204/Pasted image 20241226152137.webp" deleted file mode 100644 index 7b4700f..0000000 Binary files "a/source/images/\350\256\241\347\256\227\346\234\272\351\253\230\347\272\247\347\263\273\347\273\237\347\273\223\346\236\204/Pasted image 20241226152137.webp" and /dev/null differ diff --git "a/source/images/\350\256\241\347\256\227\346\234\272\351\253\230\347\272\247\347\263\273\347\273\237\347\273\223\346\236\204/Pasted image 20241226152150.webp" "b/source/images/\350\256\241\347\256\227\346\234\272\351\253\230\347\272\247\347\263\273\347\273\237\347\273\223\346\236\204/Pasted image 20241226152150.webp" deleted file mode 100644 index 8daf7c7..0000000 Binary files "a/source/images/\350\256\241\347\256\227\346\234\272\351\253\230\347\272\247\347\263\273\347\273\237\347\273\223\346\236\204/Pasted image 20241226152150.webp" and /dev/null differ diff --git "a/source/images/\350\256\241\347\256\227\346\234\272\351\253\230\347\272\247\347\263\273\347\273\237\347\273\223\346\236\204/Pasted image 20241226152644.webp" "b/source/images/\350\256\241\347\256\227\346\234\272\351\253\230\347\272\247\347\263\273\347\273\237\347\273\223\346\236\204/Pasted image 20241226152644.webp" deleted file mode 100644 index 57bc434..0000000 Binary files "a/source/images/\350\256\241\347\256\227\346\234\272\351\253\230\347\272\247\347\263\273\347\273\237\347\273\223\346\236\204/Pasted image 20241226152644.webp" and /dev/null differ diff --git "a/source/images/\350\256\241\347\256\227\346\234\272\351\253\230\347\272\247\347\263\273\347\273\237\347\273\223\346\236\204/Pasted image 20241226152703.webp" "b/source/images/\350\256\241\347\256\227\346\234\272\351\253\230\347\272\247\347\263\273\347\273\237\347\273\223\346\236\204/Pasted image 20241226152703.webp" deleted file mode 100644 index e4047d1..0000000 Binary files "a/source/images/\350\256\241\347\256\227\346\234\272\351\253\230\347\272\247\347\263\273\347\273\237\347\273\223\346\236\204/Pasted image 20241226152703.webp" and /dev/null differ diff --git "a/source/images/\350\256\241\347\256\227\346\234\272\351\253\230\347\272\247\347\263\273\347\273\237\347\273\223\346\236\204/Pasted image 20241226152739.webp" "b/source/images/\350\256\241\347\256\227\346\234\272\351\253\230\347\272\247\347\263\273\347\273\237\347\273\223\346\236\204/Pasted image 20241226152739.webp" deleted file mode 100644 index 8fb8fbc..0000000 Binary files "a/source/images/\350\256\241\347\256\227\346\234\272\351\253\230\347\272\247\347\263\273\347\273\237\347\273\223\346\236\204/Pasted image 20241226152739.webp" and /dev/null differ diff --git "a/source/images/\350\256\241\347\256\227\346\234\272\351\253\230\347\272\247\347\263\273\347\273\237\347\273\223\346\236\204/Pasted image 20241226152811.webp" "b/source/images/\350\256\241\347\256\227\346\234\272\351\253\230\347\272\247\347\263\273\347\273\237\347\273\223\346\236\204/Pasted image 20241226152811.webp" deleted file mode 100644 index 59b94c3..0000000 Binary files "a/source/images/\350\256\241\347\256\227\346\234\272\351\253\230\347\272\247\347\263\273\347\273\237\347\273\223\346\236\204/Pasted image 20241226152811.webp" and /dev/null differ diff --git "a/source/images/\350\256\241\347\256\227\346\234\272\351\253\230\347\272\247\347\263\273\347\273\237\347\273\223\346\236\204/Pasted image 20241226152819.webp" "b/source/images/\350\256\241\347\256\227\346\234\272\351\253\230\347\272\247\347\263\273\347\273\237\347\273\223\346\236\204/Pasted image 20241226152819.webp" deleted file mode 100644 index f1f6794..0000000 Binary files "a/source/images/\350\256\241\347\256\227\346\234\272\351\253\230\347\272\247\347\263\273\347\273\237\347\273\223\346\236\204/Pasted image 20241226152819.webp" and /dev/null differ diff --git "a/source/images/\350\256\241\347\256\227\346\234\272\351\253\230\347\272\247\347\263\273\347\273\237\347\273\223\346\236\204/Pasted image 20241226152830.webp" "b/source/images/\350\256\241\347\256\227\346\234\272\351\253\230\347\272\247\347\263\273\347\273\237\347\273\223\346\236\204/Pasted image 20241226152830.webp" deleted file mode 100644 index 1239da3..0000000 Binary files "a/source/images/\350\256\241\347\256\227\346\234\272\351\253\230\347\272\247\347\263\273\347\273\237\347\273\223\346\236\204/Pasted image 20241226152830.webp" and /dev/null differ diff --git "a/source/images/\350\256\241\347\256\227\346\234\272\351\253\230\347\272\247\347\263\273\347\273\237\347\273\223\346\236\204/Pasted image 20241226152837.webp" "b/source/images/\350\256\241\347\256\227\346\234\272\351\253\230\347\272\247\347\263\273\347\273\237\347\273\223\346\236\204/Pasted image 20241226152837.webp" deleted file mode 100644 index 65886f5..0000000 Binary files "a/source/images/\350\256\241\347\256\227\346\234\272\351\253\230\347\272\247\347\263\273\347\273\237\347\273\223\346\236\204/Pasted image 20241226152837.webp" and /dev/null differ diff --git "a/source/images/\350\256\241\347\256\227\346\234\272\351\253\230\347\272\247\347\263\273\347\273\237\347\273\223\346\236\204/Pasted image 20241226152843.webp" "b/source/images/\350\256\241\347\256\227\346\234\272\351\253\230\347\272\247\347\263\273\347\273\237\347\273\223\346\236\204/Pasted image 20241226152843.webp" deleted file mode 100644 index a749342..0000000 Binary files "a/source/images/\350\256\241\347\256\227\346\234\272\351\253\230\347\272\247\347\263\273\347\273\237\347\273\223\346\236\204/Pasted image 20241226152843.webp" and /dev/null differ diff --git "a/source/images/\350\256\241\347\256\227\346\234\272\351\253\230\347\272\247\347\263\273\347\273\237\347\273\223\346\236\204/Pasted image 20241226152852.webp" "b/source/images/\350\256\241\347\256\227\346\234\272\351\253\230\347\272\247\347\263\273\347\273\237\347\273\223\346\236\204/Pasted image 20241226152852.webp" deleted file mode 100644 index 094e53a..0000000 Binary files "a/source/images/\350\256\241\347\256\227\346\234\272\351\253\230\347\272\247\347\263\273\347\273\237\347\273\223\346\236\204/Pasted image 20241226152852.webp" and /dev/null differ diff --git "a/source/images/\350\256\241\347\256\227\346\234\272\351\253\230\347\272\247\347\263\273\347\273\237\347\273\223\346\236\204/Pasted image 20241226152902.webp" "b/source/images/\350\256\241\347\256\227\346\234\272\351\253\230\347\272\247\347\263\273\347\273\237\347\273\223\346\236\204/Pasted image 20241226152902.webp" deleted file mode 100644 index b61c436..0000000 Binary files "a/source/images/\350\256\241\347\256\227\346\234\272\351\253\230\347\272\247\347\263\273\347\273\237\347\273\223\346\236\204/Pasted image 20241226152902.webp" and /dev/null differ diff --git "a/source/images/\350\256\241\347\256\227\346\234\272\351\253\230\347\272\247\347\263\273\347\273\237\347\273\223\346\236\204/Pasted image 20241226152909.webp" "b/source/images/\350\256\241\347\256\227\346\234\272\351\253\230\347\272\247\347\263\273\347\273\237\347\273\223\346\236\204/Pasted image 20241226152909.webp" deleted file mode 100644 index 40471c1..0000000 Binary files "a/source/images/\350\256\241\347\256\227\346\234\272\351\253\230\347\272\247\347\263\273\347\273\237\347\273\223\346\236\204/Pasted image 20241226152909.webp" and /dev/null differ diff --git "a/source/images/\350\256\241\347\256\227\346\234\272\351\253\230\347\272\247\347\263\273\347\273\237\347\273\223\346\236\204/Pasted image 20241226152914.webp" "b/source/images/\350\256\241\347\256\227\346\234\272\351\253\230\347\272\247\347\263\273\347\273\237\347\273\223\346\236\204/Pasted image 20241226152914.webp" deleted file mode 100644 index d4f40c5..0000000 Binary files "a/source/images/\350\256\241\347\256\227\346\234\272\351\253\230\347\272\247\347\263\273\347\273\237\347\273\223\346\236\204/Pasted image 20241226152914.webp" and /dev/null differ diff --git "a/source/images/\350\256\241\347\256\227\346\234\272\351\253\230\347\272\247\347\263\273\347\273\237\347\273\223\346\236\204/Pasted image 20241226152920.webp" "b/source/images/\350\256\241\347\256\227\346\234\272\351\253\230\347\272\247\347\263\273\347\273\237\347\273\223\346\236\204/Pasted image 20241226152920.webp" deleted file mode 100644 index ad543f2..0000000 Binary files "a/source/images/\350\256\241\347\256\227\346\234\272\351\253\230\347\272\247\347\263\273\347\273\237\347\273\223\346\236\204/Pasted image 20241226152920.webp" and /dev/null differ diff --git "a/source/images/\350\256\241\347\256\227\346\234\272\351\253\230\347\272\247\347\263\273\347\273\237\347\273\223\346\236\204/Pasted image 20241226152928.webp" "b/source/images/\350\256\241\347\256\227\346\234\272\351\253\230\347\272\247\347\263\273\347\273\237\347\273\223\346\236\204/Pasted image 20241226152928.webp" deleted file mode 100644 index 419b943..0000000 Binary files "a/source/images/\350\256\241\347\256\227\346\234\272\351\253\230\347\272\247\347\263\273\347\273\237\347\273\223\346\236\204/Pasted image 20241226152928.webp" and /dev/null differ diff --git "a/source/images/\350\256\241\347\256\227\346\234\272\351\253\230\347\272\247\347\263\273\347\273\237\347\273\223\346\236\204/Pasted image 20241226153023.webp" "b/source/images/\350\256\241\347\256\227\346\234\272\351\253\230\347\272\247\347\263\273\347\273\237\347\273\223\346\236\204/Pasted image 20241226153023.webp" deleted file mode 100644 index 9ccf5a5..0000000 Binary files "a/source/images/\350\256\241\347\256\227\346\234\272\351\253\230\347\272\247\347\263\273\347\273\237\347\273\223\346\236\204/Pasted image 20241226153023.webp" and /dev/null differ diff --git "a/source/images/\350\256\241\347\256\227\346\234\272\351\253\230\347\272\247\347\263\273\347\273\237\347\273\223\346\236\204/Pasted image 20241226153028.webp" "b/source/images/\350\256\241\347\256\227\346\234\272\351\253\230\347\272\247\347\263\273\347\273\237\347\273\223\346\236\204/Pasted image 20241226153028.webp" deleted file mode 100644 index 6d5647c..0000000 Binary files "a/source/images/\350\256\241\347\256\227\346\234\272\351\253\230\347\272\247\347\263\273\347\273\237\347\273\223\346\236\204/Pasted image 20241226153028.webp" and /dev/null differ diff --git "a/source/images/\350\256\241\347\256\227\346\234\272\351\253\230\347\272\247\347\263\273\347\273\237\347\273\223\346\236\204/Pasted image 20241226153038.webp" "b/source/images/\350\256\241\347\256\227\346\234\272\351\253\230\347\272\247\347\263\273\347\273\237\347\273\223\346\236\204/Pasted image 20241226153038.webp" deleted file mode 100644 index 46dfe39..0000000 Binary files "a/source/images/\350\256\241\347\256\227\346\234\272\351\253\230\347\272\247\347\263\273\347\273\237\347\273\223\346\236\204/Pasted image 20241226153038.webp" and /dev/null differ diff --git "a/source/images/\350\256\241\347\256\227\346\234\272\351\253\230\347\272\247\347\263\273\347\273\237\347\273\223\346\236\204/Pasted image 20241226153045.webp" "b/source/images/\350\256\241\347\256\227\346\234\272\351\253\230\347\272\247\347\263\273\347\273\237\347\273\223\346\236\204/Pasted image 20241226153045.webp" deleted file mode 100644 index 923b270..0000000 Binary files "a/source/images/\350\256\241\347\256\227\346\234\272\351\253\230\347\272\247\347\263\273\347\273\237\347\273\223\346\236\204/Pasted image 20241226153045.webp" and /dev/null differ diff --git "a/source/images/\350\256\241\347\256\227\346\234\272\351\253\230\347\272\247\347\263\273\347\273\237\347\273\223\346\236\204/Pasted image 20241226153142.webp" "b/source/images/\350\256\241\347\256\227\346\234\272\351\253\230\347\272\247\347\263\273\347\273\237\347\273\223\346\236\204/Pasted image 20241226153142.webp" deleted file mode 100644 index 3495dfe..0000000 Binary files "a/source/images/\350\256\241\347\256\227\346\234\272\351\253\230\347\272\247\347\263\273\347\273\237\347\273\223\346\236\204/Pasted image 20241226153142.webp" and /dev/null differ diff --git "a/source/images/\350\256\241\347\256\227\346\234\272\351\253\230\347\272\247\347\263\273\347\273\237\347\273\223\346\236\204/Pasted image 20241226153150.webp" "b/source/images/\350\256\241\347\256\227\346\234\272\351\253\230\347\272\247\347\263\273\347\273\237\347\273\223\346\236\204/Pasted image 20241226153150.webp" deleted file mode 100644 index 6da5b3c..0000000 Binary files "a/source/images/\350\256\241\347\256\227\346\234\272\351\253\230\347\272\247\347\263\273\347\273\237\347\273\223\346\236\204/Pasted image 20241226153150.webp" and /dev/null differ diff --git "a/source/images/\350\256\241\347\256\227\346\234\272\351\253\230\347\272\247\347\263\273\347\273\237\347\273\223\346\236\204/Pasted image 20241226153214.webp" "b/source/images/\350\256\241\347\256\227\346\234\272\351\253\230\347\272\247\347\263\273\347\273\237\347\273\223\346\236\204/Pasted image 20241226153214.webp" deleted file mode 100644 index 14355be..0000000 Binary files "a/source/images/\350\256\241\347\256\227\346\234\272\351\253\230\347\272\247\347\263\273\347\273\237\347\273\223\346\236\204/Pasted image 20241226153214.webp" and /dev/null differ diff --git "a/source/images/\350\256\241\347\256\227\346\234\272\351\253\230\347\272\247\347\263\273\347\273\237\347\273\223\346\236\204/Pasted image 20241226153247.webp" "b/source/images/\350\256\241\347\256\227\346\234\272\351\253\230\347\272\247\347\263\273\347\273\237\347\273\223\346\236\204/Pasted image 20241226153247.webp" deleted file mode 100644 index 46b5c52..0000000 Binary files "a/source/images/\350\256\241\347\256\227\346\234\272\351\253\230\347\272\247\347\263\273\347\273\237\347\273\223\346\236\204/Pasted image 20241226153247.webp" and /dev/null differ diff --git "a/source/images/\350\256\241\347\256\227\346\234\272\351\253\230\347\272\247\347\263\273\347\273\237\347\273\223\346\236\204/Pasted image 20241226153308.webp" "b/source/images/\350\256\241\347\256\227\346\234\272\351\253\230\347\272\247\347\263\273\347\273\237\347\273\223\346\236\204/Pasted image 20241226153308.webp" deleted file mode 100644 index 6f46d0f..0000000 Binary files "a/source/images/\350\256\241\347\256\227\346\234\272\351\253\230\347\272\247\347\263\273\347\273\237\347\273\223\346\236\204/Pasted image 20241226153308.webp" and /dev/null differ diff --git "a/source/images/\350\256\241\347\256\227\346\234\272\351\253\230\347\272\247\347\263\273\347\273\237\347\273\223\346\236\204/Pasted image 20241226153320.webp" "b/source/images/\350\256\241\347\256\227\346\234\272\351\253\230\347\272\247\347\263\273\347\273\237\347\273\223\346\236\204/Pasted image 20241226153320.webp" deleted file mode 100644 index 18b411d..0000000 Binary files "a/source/images/\350\256\241\347\256\227\346\234\272\351\253\230\347\272\247\347\263\273\347\273\237\347\273\223\346\236\204/Pasted image 20241226153320.webp" and /dev/null differ diff --git "a/source/images/\350\256\241\347\256\227\346\234\272\351\253\230\347\272\247\347\263\273\347\273\237\347\273\223\346\236\204/Pasted image 20241226153324.webp" "b/source/images/\350\256\241\347\256\227\346\234\272\351\253\230\347\272\247\347\263\273\347\273\237\347\273\223\346\236\204/Pasted image 20241226153324.webp" deleted file mode 100644 index d78ca92..0000000 Binary files "a/source/images/\350\256\241\347\256\227\346\234\272\351\253\230\347\272\247\347\263\273\347\273\237\347\273\223\346\236\204/Pasted image 20241226153324.webp" and /dev/null differ diff --git "a/source/images/\350\256\241\347\256\227\346\234\272\351\253\230\347\272\247\347\263\273\347\273\237\347\273\223\346\236\204/Pasted image 20241226153330.webp" "b/source/images/\350\256\241\347\256\227\346\234\272\351\253\230\347\272\247\347\263\273\347\273\237\347\273\223\346\236\204/Pasted image 20241226153330.webp" deleted file mode 100644 index b3315ce..0000000 Binary files "a/source/images/\350\256\241\347\256\227\346\234\272\351\253\230\347\272\247\347\263\273\347\273\237\347\273\223\346\236\204/Pasted image 20241226153330.webp" and /dev/null differ diff --git "a/source/images/\350\256\241\347\256\227\346\234\272\351\253\230\347\272\247\347\263\273\347\273\237\347\273\223\346\236\204/Pasted image 20241226153336.webp" "b/source/images/\350\256\241\347\256\227\346\234\272\351\253\230\347\272\247\347\263\273\347\273\237\347\273\223\346\236\204/Pasted image 20241226153336.webp" deleted file mode 100644 index 86526f5..0000000 Binary files "a/source/images/\350\256\241\347\256\227\346\234\272\351\253\230\347\272\247\347\263\273\347\273\237\347\273\223\346\236\204/Pasted image 20241226153336.webp" and /dev/null differ diff --git "a/source/images/\350\256\241\347\256\227\346\234\272\351\253\230\347\272\247\347\263\273\347\273\237\347\273\223\346\236\204/Pasted image 20241226153341.webp" "b/source/images/\350\256\241\347\256\227\346\234\272\351\253\230\347\272\247\347\263\273\347\273\237\347\273\223\346\236\204/Pasted image 20241226153341.webp" deleted file mode 100644 index 85cca07..0000000 Binary files "a/source/images/\350\256\241\347\256\227\346\234\272\351\253\230\347\272\247\347\263\273\347\273\237\347\273\223\346\236\204/Pasted image 20241226153341.webp" and /dev/null differ diff --git "a/source/images/\350\256\241\347\256\227\346\234\272\351\253\230\347\272\247\347\263\273\347\273\237\347\273\223\346\236\204/Pasted image 20241226153401.webp" "b/source/images/\350\256\241\347\256\227\346\234\272\351\253\230\347\272\247\347\263\273\347\273\237\347\273\223\346\236\204/Pasted image 20241226153401.webp" deleted file mode 100644 index 2e0eebb..0000000 Binary files "a/source/images/\350\256\241\347\256\227\346\234\272\351\253\230\347\272\247\347\263\273\347\273\237\347\273\223\346\236\204/Pasted image 20241226153401.webp" and /dev/null differ diff --git "a/source/images/\350\256\241\347\256\227\346\234\272\351\253\230\347\272\247\347\263\273\347\273\237\347\273\223\346\236\204/Pasted image 20241226153412.webp" "b/source/images/\350\256\241\347\256\227\346\234\272\351\253\230\347\272\247\347\263\273\347\273\237\347\273\223\346\236\204/Pasted image 20241226153412.webp" deleted file mode 100644 index 6659fc3..0000000 Binary files "a/source/images/\350\256\241\347\256\227\346\234\272\351\253\230\347\272\247\347\263\273\347\273\237\347\273\223\346\236\204/Pasted image 20241226153412.webp" and /dev/null differ diff --git "a/source/images/\350\256\241\347\256\227\346\234\272\351\253\230\347\272\247\347\263\273\347\273\237\347\273\223\346\236\204/Pasted image 20241226153515.webp" "b/source/images/\350\256\241\347\256\227\346\234\272\351\253\230\347\272\247\347\263\273\347\273\237\347\273\223\346\236\204/Pasted image 20241226153515.webp" deleted file mode 100644 index 4671700..0000000 Binary files "a/source/images/\350\256\241\347\256\227\346\234\272\351\253\230\347\272\247\347\263\273\347\273\237\347\273\223\346\236\204/Pasted image 20241226153515.webp" and /dev/null differ diff --git "a/source/images/\350\256\241\347\256\227\346\234\272\351\253\230\347\272\247\347\263\273\347\273\237\347\273\223\346\236\204/Pasted image 20241226154009.webp" "b/source/images/\350\256\241\347\256\227\346\234\272\351\253\230\347\272\247\347\263\273\347\273\237\347\273\223\346\236\204/Pasted image 20241226154009.webp" deleted file mode 100644 index 945f376..0000000 Binary files "a/source/images/\350\256\241\347\256\227\346\234\272\351\253\230\347\272\247\347\263\273\347\273\237\347\273\223\346\236\204/Pasted image 20241226154009.webp" and /dev/null differ diff --git "a/source/images/\350\256\241\347\256\227\346\234\272\351\253\230\347\272\247\347\263\273\347\273\237\347\273\223\346\236\204/Pasted image 20241226154025.webp" "b/source/images/\350\256\241\347\256\227\346\234\272\351\253\230\347\272\247\347\263\273\347\273\237\347\273\223\346\236\204/Pasted image 20241226154025.webp" deleted file mode 100644 index 38dac75..0000000 Binary files "a/source/images/\350\256\241\347\256\227\346\234\272\351\253\230\347\272\247\347\263\273\347\273\237\347\273\223\346\236\204/Pasted image 20241226154025.webp" and /dev/null differ diff --git "a/source/images/\350\256\241\347\256\227\346\234\272\351\253\230\347\272\247\347\263\273\347\273\237\347\273\223\346\236\204/Pasted image 20241226155023.webp" "b/source/images/\350\256\241\347\256\227\346\234\272\351\253\230\347\272\247\347\263\273\347\273\237\347\273\223\346\236\204/Pasted image 20241226155023.webp" deleted file mode 100644 index aa33694..0000000 Binary files "a/source/images/\350\256\241\347\256\227\346\234\272\351\253\230\347\272\247\347\263\273\347\273\237\347\273\223\346\236\204/Pasted image 20241226155023.webp" and /dev/null differ diff --git "a/source/images/\350\256\241\347\256\227\346\234\272\351\253\230\347\272\247\347\263\273\347\273\237\347\273\223\346\236\204/Pasted image 20241226171805.webp" "b/source/images/\350\256\241\347\256\227\346\234\272\351\253\230\347\272\247\347\263\273\347\273\237\347\273\223\346\236\204/Pasted image 20241226171805.webp" deleted file mode 100644 index a675449..0000000 Binary files "a/source/images/\350\256\241\347\256\227\346\234\272\351\253\230\347\272\247\347\263\273\347\273\237\347\273\223\346\236\204/Pasted image 20241226171805.webp" and /dev/null differ diff --git "a/source/images/\350\256\241\347\256\227\346\234\272\351\253\230\347\272\247\347\263\273\347\273\237\347\273\223\346\236\204/Pasted image 20241226171813.webp" "b/source/images/\350\256\241\347\256\227\346\234\272\351\253\230\347\272\247\347\263\273\347\273\237\347\273\223\346\236\204/Pasted image 20241226171813.webp" deleted file mode 100644 index 37b94b5..0000000 Binary files "a/source/images/\350\256\241\347\256\227\346\234\272\351\253\230\347\272\247\347\263\273\347\273\237\347\273\223\346\236\204/Pasted image 20241226171813.webp" and /dev/null differ diff --git "a/source/images/\350\256\241\347\256\227\346\234\272\351\253\230\347\272\247\347\263\273\347\273\237\347\273\223\346\236\204/Pasted image 20241226171826.webp" "b/source/images/\350\256\241\347\256\227\346\234\272\351\253\230\347\272\247\347\263\273\347\273\237\347\273\223\346\236\204/Pasted image 20241226171826.webp" deleted file mode 100644 index 5efee92..0000000 Binary files "a/source/images/\350\256\241\347\256\227\346\234\272\351\253\230\347\272\247\347\263\273\347\273\237\347\273\223\346\236\204/Pasted image 20241226171826.webp" and /dev/null differ diff --git "a/source/images/\350\256\241\347\256\227\346\234\272\351\253\230\347\272\247\347\263\273\347\273\237\347\273\223\346\236\204/Pasted image 20241226172002.webp" "b/source/images/\350\256\241\347\256\227\346\234\272\351\253\230\347\272\247\347\263\273\347\273\237\347\273\223\346\236\204/Pasted image 20241226172002.webp" deleted file mode 100644 index 883900f..0000000 Binary files "a/source/images/\350\256\241\347\256\227\346\234\272\351\253\230\347\272\247\347\263\273\347\273\237\347\273\223\346\236\204/Pasted image 20241226172002.webp" and /dev/null differ diff --git "a/source/images/\350\256\241\347\256\227\346\234\272\351\253\230\347\272\247\347\263\273\347\273\237\347\273\223\346\236\204/Pasted image 20241226172141.webp" "b/source/images/\350\256\241\347\256\227\346\234\272\351\253\230\347\272\247\347\263\273\347\273\237\347\273\223\346\236\204/Pasted image 20241226172141.webp" deleted file mode 100644 index cef52f8..0000000 Binary files "a/source/images/\350\256\241\347\256\227\346\234\272\351\253\230\347\272\247\347\263\273\347\273\237\347\273\223\346\236\204/Pasted image 20241226172141.webp" and /dev/null differ diff --git "a/source/images/\350\256\241\347\256\227\346\234\272\351\253\230\347\272\247\347\263\273\347\273\237\347\273\223\346\236\204/Pasted image 20241226172148.webp" "b/source/images/\350\256\241\347\256\227\346\234\272\351\253\230\347\272\247\347\263\273\347\273\237\347\273\223\346\236\204/Pasted image 20241226172148.webp" deleted file mode 100644 index 4a35ca2..0000000 Binary files "a/source/images/\350\256\241\347\256\227\346\234\272\351\253\230\347\272\247\347\263\273\347\273\237\347\273\223\346\236\204/Pasted image 20241226172148.webp" and /dev/null differ diff --git "a/source/images/\350\256\241\347\256\227\346\234\272\351\253\230\347\272\247\347\263\273\347\273\237\347\273\223\346\236\204/Pasted image 20241226172218.webp" "b/source/images/\350\256\241\347\256\227\346\234\272\351\253\230\347\272\247\347\263\273\347\273\237\347\273\223\346\236\204/Pasted image 20241226172218.webp" deleted file mode 100644 index b41079c..0000000 Binary files "a/source/images/\350\256\241\347\256\227\346\234\272\351\253\230\347\272\247\347\263\273\347\273\237\347\273\223\346\236\204/Pasted image 20241226172218.webp" and /dev/null differ diff --git "a/source/images/\350\256\241\347\256\227\346\234\272\351\253\230\347\272\247\347\263\273\347\273\237\347\273\223\346\236\204/Pasted image 20241226172241.webp" "b/source/images/\350\256\241\347\256\227\346\234\272\351\253\230\347\272\247\347\263\273\347\273\237\347\273\223\346\236\204/Pasted image 20241226172241.webp" deleted file mode 100644 index 4a0bab1..0000000 Binary files "a/source/images/\350\256\241\347\256\227\346\234\272\351\253\230\347\272\247\347\263\273\347\273\237\347\273\223\346\236\204/Pasted image 20241226172241.webp" and /dev/null differ diff --git "a/source/images/\350\256\241\347\256\227\346\234\272\351\253\230\347\272\247\347\263\273\347\273\237\347\273\223\346\236\204/Pasted image 20241226172251.webp" "b/source/images/\350\256\241\347\256\227\346\234\272\351\253\230\347\272\247\347\263\273\347\273\237\347\273\223\346\236\204/Pasted image 20241226172251.webp" deleted file mode 100644 index 6be84e6..0000000 Binary files "a/source/images/\350\256\241\347\256\227\346\234\272\351\253\230\347\272\247\347\263\273\347\273\237\347\273\223\346\236\204/Pasted image 20241226172251.webp" and /dev/null differ diff --git "a/source/images/\350\256\241\347\256\227\346\234\272\351\253\230\347\272\247\347\263\273\347\273\237\347\273\223\346\236\204/Pasted image 20241226172317.webp" "b/source/images/\350\256\241\347\256\227\346\234\272\351\253\230\347\272\247\347\263\273\347\273\237\347\273\223\346\236\204/Pasted image 20241226172317.webp" deleted file mode 100644 index 90fd290..0000000 Binary files "a/source/images/\350\256\241\347\256\227\346\234\272\351\253\230\347\272\247\347\263\273\347\273\237\347\273\223\346\236\204/Pasted image 20241226172317.webp" and /dev/null differ diff --git "a/source/images/\350\256\241\347\256\227\346\234\272\351\253\230\347\272\247\347\263\273\347\273\237\347\273\223\346\236\204/Pasted image 20241226172403.webp" "b/source/images/\350\256\241\347\256\227\346\234\272\351\253\230\347\272\247\347\263\273\347\273\237\347\273\223\346\236\204/Pasted image 20241226172403.webp" deleted file mode 100644 index 2db6c4a..0000000 Binary files "a/source/images/\350\256\241\347\256\227\346\234\272\351\253\230\347\272\247\347\263\273\347\273\237\347\273\223\346\236\204/Pasted image 20241226172403.webp" and /dev/null differ diff --git "a/source/images/\350\256\241\347\256\227\346\234\272\351\253\230\347\272\247\347\263\273\347\273\237\347\273\223\346\236\204/Pasted image 20241226172439.webp" "b/source/images/\350\256\241\347\256\227\346\234\272\351\253\230\347\272\247\347\263\273\347\273\237\347\273\223\346\236\204/Pasted image 20241226172439.webp" deleted file mode 100644 index 35d498e..0000000 Binary files "a/source/images/\350\256\241\347\256\227\346\234\272\351\253\230\347\272\247\347\263\273\347\273\237\347\273\223\346\236\204/Pasted image 20241226172439.webp" and /dev/null differ diff --git "a/source/images/\350\256\241\347\256\227\346\234\272\351\253\230\347\272\247\347\263\273\347\273\237\347\273\223\346\236\204/Pasted image 20241226172511.webp" "b/source/images/\350\256\241\347\256\227\346\234\272\351\253\230\347\272\247\347\263\273\347\273\237\347\273\223\346\236\204/Pasted image 20241226172511.webp" deleted file mode 100644 index 7cc52e8..0000000 Binary files "a/source/images/\350\256\241\347\256\227\346\234\272\351\253\230\347\272\247\347\263\273\347\273\237\347\273\223\346\236\204/Pasted image 20241226172511.webp" and /dev/null differ diff --git "a/source/images/\350\256\241\347\256\227\346\234\272\351\253\230\347\272\247\347\263\273\347\273\237\347\273\223\346\236\204/Pasted image 20241226172616.webp" "b/source/images/\350\256\241\347\256\227\346\234\272\351\253\230\347\272\247\347\263\273\347\273\237\347\273\223\346\236\204/Pasted image 20241226172616.webp" deleted file mode 100644 index 0a49bbc..0000000 Binary files "a/source/images/\350\256\241\347\256\227\346\234\272\351\253\230\347\272\247\347\263\273\347\273\237\347\273\223\346\236\204/Pasted image 20241226172616.webp" and /dev/null differ diff --git "a/source/images/\350\256\241\347\256\227\346\234\272\351\253\230\347\272\247\347\263\273\347\273\237\347\273\223\346\236\204/Pasted image 20241226172719.webp" "b/source/images/\350\256\241\347\256\227\346\234\272\351\253\230\347\272\247\347\263\273\347\273\237\347\273\223\346\236\204/Pasted image 20241226172719.webp" deleted file mode 100644 index 092fee2..0000000 Binary files "a/source/images/\350\256\241\347\256\227\346\234\272\351\253\230\347\272\247\347\263\273\347\273\237\347\273\223\346\236\204/Pasted image 20241226172719.webp" and /dev/null differ diff --git "a/source/images/\350\256\241\347\256\227\346\234\272\351\253\230\347\272\247\347\263\273\347\273\237\347\273\223\346\236\204/Pasted image 20241226172726.webp" "b/source/images/\350\256\241\347\256\227\346\234\272\351\253\230\347\272\247\347\263\273\347\273\237\347\273\223\346\236\204/Pasted image 20241226172726.webp" deleted file mode 100644 index 9a7bf77..0000000 Binary files "a/source/images/\350\256\241\347\256\227\346\234\272\351\253\230\347\272\247\347\263\273\347\273\237\347\273\223\346\236\204/Pasted image 20241226172726.webp" and /dev/null differ diff --git "a/source/images/\350\256\241\347\256\227\346\234\272\351\253\230\347\272\247\347\263\273\347\273\237\347\273\223\346\236\204/Pasted image 20241226172732.webp" "b/source/images/\350\256\241\347\256\227\346\234\272\351\253\230\347\272\247\347\263\273\347\273\237\347\273\223\346\236\204/Pasted image 20241226172732.webp" deleted file mode 100644 index cd1ecac..0000000 Binary files "a/source/images/\350\256\241\347\256\227\346\234\272\351\253\230\347\272\247\347\263\273\347\273\237\347\273\223\346\236\204/Pasted image 20241226172732.webp" and /dev/null differ diff --git "a/source/images/\350\256\241\347\256\227\346\234\272\351\253\230\347\272\247\347\263\273\347\273\237\347\273\223\346\236\204/Pasted image 20241226172751.webp" "b/source/images/\350\256\241\347\256\227\346\234\272\351\253\230\347\272\247\347\263\273\347\273\237\347\273\223\346\236\204/Pasted image 20241226172751.webp" deleted file mode 100644 index 7fb421b..0000000 Binary files "a/source/images/\350\256\241\347\256\227\346\234\272\351\253\230\347\272\247\347\263\273\347\273\237\347\273\223\346\236\204/Pasted image 20241226172751.webp" and /dev/null differ diff --git "a/source/images/\350\256\241\347\256\227\346\234\272\351\253\230\347\272\247\347\263\273\347\273\237\347\273\223\346\236\204/Pasted image 20241226172758.webp" "b/source/images/\350\256\241\347\256\227\346\234\272\351\253\230\347\272\247\347\263\273\347\273\237\347\273\223\346\236\204/Pasted image 20241226172758.webp" deleted file mode 100644 index c59aea5..0000000 Binary files "a/source/images/\350\256\241\347\256\227\346\234\272\351\253\230\347\272\247\347\263\273\347\273\237\347\273\223\346\236\204/Pasted image 20241226172758.webp" and /dev/null differ diff --git "a/source/images/\350\256\241\347\256\227\346\234\272\351\253\230\347\272\247\347\263\273\347\273\237\347\273\223\346\236\204/Pasted image 20241226172833.webp" "b/source/images/\350\256\241\347\256\227\346\234\272\351\253\230\347\272\247\347\263\273\347\273\237\347\273\223\346\236\204/Pasted image 20241226172833.webp" deleted file mode 100644 index 7b7d3a2..0000000 Binary files "a/source/images/\350\256\241\347\256\227\346\234\272\351\253\230\347\272\247\347\263\273\347\273\237\347\273\223\346\236\204/Pasted image 20241226172833.webp" and /dev/null differ diff --git "a/source/images/\350\256\241\347\256\227\346\234\272\351\253\230\347\272\247\347\263\273\347\273\237\347\273\223\346\236\204/Pasted image 20241226172917.webp" "b/source/images/\350\256\241\347\256\227\346\234\272\351\253\230\347\272\247\347\263\273\347\273\237\347\273\223\346\236\204/Pasted image 20241226172917.webp" deleted file mode 100644 index 9cc75fd..0000000 Binary files "a/source/images/\350\256\241\347\256\227\346\234\272\351\253\230\347\272\247\347\263\273\347\273\237\347\273\223\346\236\204/Pasted image 20241226172917.webp" and /dev/null differ diff --git "a/source/images/\350\256\241\347\256\227\346\234\272\351\253\230\347\272\247\347\263\273\347\273\237\347\273\223\346\236\204/Pasted image 20241226172954.webp" "b/source/images/\350\256\241\347\256\227\346\234\272\351\253\230\347\272\247\347\263\273\347\273\237\347\273\223\346\236\204/Pasted image 20241226172954.webp" deleted file mode 100644 index e7ca2c4..0000000 Binary files "a/source/images/\350\256\241\347\256\227\346\234\272\351\253\230\347\272\247\347\263\273\347\273\237\347\273\223\346\236\204/Pasted image 20241226172954.webp" and /dev/null differ diff --git "a/source/images/\350\256\241\347\256\227\346\234\272\351\253\230\347\272\247\347\263\273\347\273\237\347\273\223\346\236\204/Pasted image 20241226173516.webp" "b/source/images/\350\256\241\347\256\227\346\234\272\351\253\230\347\272\247\347\263\273\347\273\237\347\273\223\346\236\204/Pasted image 20241226173516.webp" deleted file mode 100644 index 10ad3e3..0000000 Binary files "a/source/images/\350\256\241\347\256\227\346\234\272\351\253\230\347\272\247\347\263\273\347\273\237\347\273\223\346\236\204/Pasted image 20241226173516.webp" and /dev/null differ diff --git "a/source/images/\350\256\241\347\256\227\346\234\272\351\253\230\347\272\247\347\263\273\347\273\237\347\273\223\346\236\204/Pasted image 20241226173528.webp" "b/source/images/\350\256\241\347\256\227\346\234\272\351\253\230\347\272\247\347\263\273\347\273\237\347\273\223\346\236\204/Pasted image 20241226173528.webp" deleted file mode 100644 index be45cdc..0000000 Binary files "a/source/images/\350\256\241\347\256\227\346\234\272\351\253\230\347\272\247\347\263\273\347\273\237\347\273\223\346\236\204/Pasted image 20241226173528.webp" and /dev/null differ diff --git "a/source/images/\350\256\241\347\256\227\346\234\272\351\253\230\347\272\247\347\263\273\347\273\237\347\273\223\346\236\204/Pasted image 20241226173621.webp" "b/source/images/\350\256\241\347\256\227\346\234\272\351\253\230\347\272\247\347\263\273\347\273\237\347\273\223\346\236\204/Pasted image 20241226173621.webp" deleted file mode 100644 index e6c41f0..0000000 Binary files "a/source/images/\350\256\241\347\256\227\346\234\272\351\253\230\347\272\247\347\263\273\347\273\237\347\273\223\346\236\204/Pasted image 20241226173621.webp" and /dev/null differ diff --git "a/source/images/\350\256\241\347\256\227\346\234\272\351\253\230\347\272\247\347\263\273\347\273\237\347\273\223\346\236\204/Pasted image 20241226174011.webp" "b/source/images/\350\256\241\347\256\227\346\234\272\351\253\230\347\272\247\347\263\273\347\273\237\347\273\223\346\236\204/Pasted image 20241226174011.webp" deleted file mode 100644 index f23c565..0000000 Binary files "a/source/images/\350\256\241\347\256\227\346\234\272\351\253\230\347\272\247\347\263\273\347\273\237\347\273\223\346\236\204/Pasted image 20241226174011.webp" and /dev/null differ diff --git "a/source/images/\350\256\241\347\256\227\346\234\272\351\253\230\347\272\247\347\263\273\347\273\237\347\273\223\346\236\204/Pasted image 20241226174142.webp" "b/source/images/\350\256\241\347\256\227\346\234\272\351\253\230\347\272\247\347\263\273\347\273\237\347\273\223\346\236\204/Pasted image 20241226174142.webp" deleted file mode 100644 index 4ef8908..0000000 Binary files "a/source/images/\350\256\241\347\256\227\346\234\272\351\253\230\347\272\247\347\263\273\347\273\237\347\273\223\346\236\204/Pasted image 20241226174142.webp" and /dev/null differ diff --git "a/source/images/\350\256\241\347\256\227\346\234\272\351\253\230\347\272\247\347\263\273\347\273\237\347\273\223\346\236\204/Pasted image 20241226174150.webp" "b/source/images/\350\256\241\347\256\227\346\234\272\351\253\230\347\272\247\347\263\273\347\273\237\347\273\223\346\236\204/Pasted image 20241226174150.webp" deleted file mode 100644 index c1c11bd..0000000 Binary files "a/source/images/\350\256\241\347\256\227\346\234\272\351\253\230\347\272\247\347\263\273\347\273\237\347\273\223\346\236\204/Pasted image 20241226174150.webp" and /dev/null differ diff --git "a/source/images/\350\256\241\347\256\227\346\234\272\351\253\230\347\272\247\347\263\273\347\273\237\347\273\223\346\236\204/Pasted image 20241226174245.webp" "b/source/images/\350\256\241\347\256\227\346\234\272\351\253\230\347\272\247\347\263\273\347\273\237\347\273\223\346\236\204/Pasted image 20241226174245.webp" deleted file mode 100644 index 8baac15..0000000 Binary files "a/source/images/\350\256\241\347\256\227\346\234\272\351\253\230\347\272\247\347\263\273\347\273\237\347\273\223\346\236\204/Pasted image 20241226174245.webp" and /dev/null differ diff --git "a/source/images/\350\256\241\347\256\227\346\234\272\351\253\230\347\272\247\347\263\273\347\273\237\347\273\223\346\236\204/Pasted image 20241226174440.webp" "b/source/images/\350\256\241\347\256\227\346\234\272\351\253\230\347\272\247\347\263\273\347\273\237\347\273\223\346\236\204/Pasted image 20241226174440.webp" deleted file mode 100644 index 72153a4..0000000 Binary files "a/source/images/\350\256\241\347\256\227\346\234\272\351\253\230\347\272\247\347\263\273\347\273\237\347\273\223\346\236\204/Pasted image 20241226174440.webp" and /dev/null differ diff --git "a/source/images/\350\256\241\347\256\227\346\234\272\351\253\230\347\272\247\347\263\273\347\273\237\347\273\223\346\236\204/Pasted image 20241226174506.webp" "b/source/images/\350\256\241\347\256\227\346\234\272\351\253\230\347\272\247\347\263\273\347\273\237\347\273\223\346\236\204/Pasted image 20241226174506.webp" deleted file mode 100644 index 139e605..0000000 Binary files "a/source/images/\350\256\241\347\256\227\346\234\272\351\253\230\347\272\247\347\263\273\347\273\237\347\273\223\346\236\204/Pasted image 20241226174506.webp" and /dev/null differ diff --git "a/source/images/\350\256\241\347\256\227\346\234\272\351\253\230\347\272\247\347\263\273\347\273\237\347\273\223\346\236\204/Pasted image 20241226174829.webp" "b/source/images/\350\256\241\347\256\227\346\234\272\351\253\230\347\272\247\347\263\273\347\273\237\347\273\223\346\236\204/Pasted image 20241226174829.webp" deleted file mode 100644 index b7b6b44..0000000 Binary files "a/source/images/\350\256\241\347\256\227\346\234\272\351\253\230\347\272\247\347\263\273\347\273\237\347\273\223\346\236\204/Pasted image 20241226174829.webp" and /dev/null differ diff --git "a/source/images/\350\256\241\347\256\227\346\234\272\351\253\230\347\272\247\347\263\273\347\273\237\347\273\223\346\236\204/Pasted image 20241226175556.webp" "b/source/images/\350\256\241\347\256\227\346\234\272\351\253\230\347\272\247\347\263\273\347\273\237\347\273\223\346\236\204/Pasted image 20241226175556.webp" deleted file mode 100644 index 58bb180..0000000 Binary files "a/source/images/\350\256\241\347\256\227\346\234\272\351\253\230\347\272\247\347\263\273\347\273\237\347\273\223\346\236\204/Pasted image 20241226175556.webp" and /dev/null differ diff --git "a/source/images/\350\256\241\347\256\227\346\234\272\351\253\230\347\272\247\347\263\273\347\273\237\347\273\223\346\236\204/Pasted image 20241226175605.webp" "b/source/images/\350\256\241\347\256\227\346\234\272\351\253\230\347\272\247\347\263\273\347\273\237\347\273\223\346\236\204/Pasted image 20241226175605.webp" deleted file mode 100644 index 60e2990..0000000 Binary files "a/source/images/\350\256\241\347\256\227\346\234\272\351\253\230\347\272\247\347\263\273\347\273\237\347\273\223\346\236\204/Pasted image 20241226175605.webp" and /dev/null differ diff --git "a/source/images/\350\256\241\347\256\227\346\234\272\351\253\230\347\272\247\347\263\273\347\273\237\347\273\223\346\236\204/Pasted image 20241226175611.webp" "b/source/images/\350\256\241\347\256\227\346\234\272\351\253\230\347\272\247\347\263\273\347\273\237\347\273\223\346\236\204/Pasted image 20241226175611.webp" deleted file mode 100644 index d44167b..0000000 Binary files "a/source/images/\350\256\241\347\256\227\346\234\272\351\253\230\347\272\247\347\263\273\347\273\237\347\273\223\346\236\204/Pasted image 20241226175611.webp" and /dev/null differ diff --git "a/source/images/\350\256\241\347\256\227\346\234\272\351\253\230\347\272\247\347\263\273\347\273\237\347\273\223\346\236\204/Pasted image 20241226175710.webp" "b/source/images/\350\256\241\347\256\227\346\234\272\351\253\230\347\272\247\347\263\273\347\273\237\347\273\223\346\236\204/Pasted image 20241226175710.webp" deleted file mode 100644 index e4cb0d2..0000000 Binary files "a/source/images/\350\256\241\347\256\227\346\234\272\351\253\230\347\272\247\347\263\273\347\273\237\347\273\223\346\236\204/Pasted image 20241226175710.webp" and /dev/null differ diff --git "a/source/images/\350\256\241\347\256\227\346\234\272\351\253\230\347\272\247\347\263\273\347\273\237\347\273\223\346\236\204/Pasted image 20241226175837.webp" "b/source/images/\350\256\241\347\256\227\346\234\272\351\253\230\347\272\247\347\263\273\347\273\237\347\273\223\346\236\204/Pasted image 20241226175837.webp" deleted file mode 100644 index ce64b5b..0000000 Binary files "a/source/images/\350\256\241\347\256\227\346\234\272\351\253\230\347\272\247\347\263\273\347\273\237\347\273\223\346\236\204/Pasted image 20241226175837.webp" and /dev/null differ diff --git "a/source/images/\350\256\241\347\256\227\346\234\272\351\253\230\347\272\247\347\263\273\347\273\237\347\273\223\346\236\204/Pasted image 20241226175855.webp" "b/source/images/\350\256\241\347\256\227\346\234\272\351\253\230\347\272\247\347\263\273\347\273\237\347\273\223\346\236\204/Pasted image 20241226175855.webp" deleted file mode 100644 index 94a5789..0000000 Binary files "a/source/images/\350\256\241\347\256\227\346\234\272\351\253\230\347\272\247\347\263\273\347\273\237\347\273\223\346\236\204/Pasted image 20241226175855.webp" and /dev/null differ diff --git "a/source/images/\350\256\241\347\256\227\346\234\272\351\253\230\347\272\247\347\263\273\347\273\237\347\273\223\346\236\204/Pasted image 20241226175904.webp" "b/source/images/\350\256\241\347\256\227\346\234\272\351\253\230\347\272\247\347\263\273\347\273\237\347\273\223\346\236\204/Pasted image 20241226175904.webp" deleted file mode 100644 index cda351d..0000000 Binary files "a/source/images/\350\256\241\347\256\227\346\234\272\351\253\230\347\272\247\347\263\273\347\273\237\347\273\223\346\236\204/Pasted image 20241226175904.webp" and /dev/null differ diff --git "a/source/images/\350\256\241\347\256\227\346\234\272\351\253\230\347\272\247\347\263\273\347\273\237\347\273\223\346\236\204/Pasted image 20241226180024.webp" "b/source/images/\350\256\241\347\256\227\346\234\272\351\253\230\347\272\247\347\263\273\347\273\237\347\273\223\346\236\204/Pasted image 20241226180024.webp" deleted file mode 100644 index 5e6591d..0000000 Binary files "a/source/images/\350\256\241\347\256\227\346\234\272\351\253\230\347\272\247\347\263\273\347\273\237\347\273\223\346\236\204/Pasted image 20241226180024.webp" and /dev/null differ diff --git "a/source/images/\350\256\241\347\256\227\346\234\272\351\253\230\347\272\247\347\263\273\347\273\237\347\273\223\346\236\204/Pasted image 20241226180037.webp" "b/source/images/\350\256\241\347\256\227\346\234\272\351\253\230\347\272\247\347\263\273\347\273\237\347\273\223\346\236\204/Pasted image 20241226180037.webp" deleted file mode 100644 index aa281e3..0000000 Binary files "a/source/images/\350\256\241\347\256\227\346\234\272\351\253\230\347\272\247\347\263\273\347\273\237\347\273\223\346\236\204/Pasted image 20241226180037.webp" and /dev/null differ diff --git "a/source/images/\350\256\241\347\256\227\346\234\272\351\253\230\347\272\247\347\263\273\347\273\237\347\273\223\346\236\204/Pasted image 20241226180817.webp" "b/source/images/\350\256\241\347\256\227\346\234\272\351\253\230\347\272\247\347\263\273\347\273\237\347\273\223\346\236\204/Pasted image 20241226180817.webp" deleted file mode 100644 index aaff05c..0000000 Binary files "a/source/images/\350\256\241\347\256\227\346\234\272\351\253\230\347\272\247\347\263\273\347\273\237\347\273\223\346\236\204/Pasted image 20241226180817.webp" and /dev/null differ diff --git "a/source/images/\350\256\241\347\256\227\346\234\272\351\253\230\347\272\247\347\263\273\347\273\237\347\273\223\346\236\204/Pasted image 20241226180846.webp" "b/source/images/\350\256\241\347\256\227\346\234\272\351\253\230\347\272\247\347\263\273\347\273\237\347\273\223\346\236\204/Pasted image 20241226180846.webp" deleted file mode 100644 index 060636c..0000000 Binary files "a/source/images/\350\256\241\347\256\227\346\234\272\351\253\230\347\272\247\347\263\273\347\273\237\347\273\223\346\236\204/Pasted image 20241226180846.webp" and /dev/null differ diff --git "a/source/images/\350\256\241\347\256\227\346\234\272\351\253\230\347\272\247\347\263\273\347\273\237\347\273\223\346\236\204/Pasted image 20241226180911.webp" "b/source/images/\350\256\241\347\256\227\346\234\272\351\253\230\347\272\247\347\263\273\347\273\237\347\273\223\346\236\204/Pasted image 20241226180911.webp" deleted file mode 100644 index 945a8d7..0000000 Binary files "a/source/images/\350\256\241\347\256\227\346\234\272\351\253\230\347\272\247\347\263\273\347\273\237\347\273\223\346\236\204/Pasted image 20241226180911.webp" and /dev/null differ diff --git "a/source/images/\350\256\241\347\256\227\346\234\272\351\253\230\347\272\247\347\263\273\347\273\237\347\273\223\346\236\204/Pasted image 20241226180920.webp" "b/source/images/\350\256\241\347\256\227\346\234\272\351\253\230\347\272\247\347\263\273\347\273\237\347\273\223\346\236\204/Pasted image 20241226180920.webp" deleted file mode 100644 index 1e7f48e..0000000 Binary files "a/source/images/\350\256\241\347\256\227\346\234\272\351\253\230\347\272\247\347\263\273\347\273\237\347\273\223\346\236\204/Pasted image 20241226180920.webp" and /dev/null differ diff --git "a/source/images/\350\256\241\347\256\227\346\234\272\351\253\230\347\272\247\347\263\273\347\273\237\347\273\223\346\236\204/Pasted image 20241226181015.webp" "b/source/images/\350\256\241\347\256\227\346\234\272\351\253\230\347\272\247\347\263\273\347\273\237\347\273\223\346\236\204/Pasted image 20241226181015.webp" deleted file mode 100644 index 3e2a176..0000000 Binary files "a/source/images/\350\256\241\347\256\227\346\234\272\351\253\230\347\272\247\347\263\273\347\273\237\347\273\223\346\236\204/Pasted image 20241226181015.webp" and /dev/null differ diff --git "a/source/images/\350\256\241\347\256\227\346\234\272\351\253\230\347\272\247\347\263\273\347\273\237\347\273\223\346\236\204/Pasted image 20241226181043.webp" "b/source/images/\350\256\241\347\256\227\346\234\272\351\253\230\347\272\247\347\263\273\347\273\237\347\273\223\346\236\204/Pasted image 20241226181043.webp" deleted file mode 100644 index 7ec9553..0000000 Binary files "a/source/images/\350\256\241\347\256\227\346\234\272\351\253\230\347\272\247\347\263\273\347\273\237\347\273\223\346\236\204/Pasted image 20241226181043.webp" and /dev/null differ diff --git "a/source/images/\350\256\241\347\256\227\346\234\272\351\253\230\347\272\247\347\263\273\347\273\237\347\273\223\346\236\204/Pasted image 20241226181305.webp" "b/source/images/\350\256\241\347\256\227\346\234\272\351\253\230\347\272\247\347\263\273\347\273\237\347\273\223\346\236\204/Pasted image 20241226181305.webp" deleted file mode 100644 index f91f8af..0000000 Binary files "a/source/images/\350\256\241\347\256\227\346\234\272\351\253\230\347\272\247\347\263\273\347\273\237\347\273\223\346\236\204/Pasted image 20241226181305.webp" and /dev/null differ diff --git "a/source/images/\350\256\241\347\256\227\346\234\272\351\253\230\347\272\247\347\263\273\347\273\237\347\273\223\346\236\204/Pasted image 20241226181312.webp" "b/source/images/\350\256\241\347\256\227\346\234\272\351\253\230\347\272\247\347\263\273\347\273\237\347\273\223\346\236\204/Pasted image 20241226181312.webp" deleted file mode 100644 index dc09037..0000000 Binary files "a/source/images/\350\256\241\347\256\227\346\234\272\351\253\230\347\272\247\347\263\273\347\273\237\347\273\223\346\236\204/Pasted image 20241226181312.webp" and /dev/null differ diff --git "a/source/images/\350\256\241\347\256\227\346\234\272\351\253\230\347\272\247\347\263\273\347\273\237\347\273\223\346\236\204/Pasted image 20241226181323.webp" "b/source/images/\350\256\241\347\256\227\346\234\272\351\253\230\347\272\247\347\263\273\347\273\237\347\273\223\346\236\204/Pasted image 20241226181323.webp" deleted file mode 100644 index fb7813b..0000000 Binary files "a/source/images/\350\256\241\347\256\227\346\234\272\351\253\230\347\272\247\347\263\273\347\273\237\347\273\223\346\236\204/Pasted image 20241226181323.webp" and /dev/null differ diff --git "a/source/images/\350\256\241\347\256\227\346\234\272\351\253\230\347\272\247\347\263\273\347\273\237\347\273\223\346\236\204/Pasted image 20241226181437.webp" "b/source/images/\350\256\241\347\256\227\346\234\272\351\253\230\347\272\247\347\263\273\347\273\237\347\273\223\346\236\204/Pasted image 20241226181437.webp" deleted file mode 100644 index 3086130..0000000 Binary files "a/source/images/\350\256\241\347\256\227\346\234\272\351\253\230\347\272\247\347\263\273\347\273\237\347\273\223\346\236\204/Pasted image 20241226181437.webp" and /dev/null differ diff --git "a/source/images/\350\256\241\347\256\227\346\234\272\351\253\230\347\272\247\347\263\273\347\273\237\347\273\223\346\236\204/Pasted image 20241226182450.webp" "b/source/images/\350\256\241\347\256\227\346\234\272\351\253\230\347\272\247\347\263\273\347\273\237\347\273\223\346\236\204/Pasted image 20241226182450.webp" deleted file mode 100644 index 4018d52..0000000 Binary files "a/source/images/\350\256\241\347\256\227\346\234\272\351\253\230\347\272\247\347\263\273\347\273\237\347\273\223\346\236\204/Pasted image 20241226182450.webp" and /dev/null differ diff --git "a/source/images/\350\256\241\347\256\227\346\234\272\351\253\230\347\272\247\347\263\273\347\273\237\347\273\223\346\236\204/Pasted image 20241226182541.webp" "b/source/images/\350\256\241\347\256\227\346\234\272\351\253\230\347\272\247\347\263\273\347\273\237\347\273\223\346\236\204/Pasted image 20241226182541.webp" deleted file mode 100644 index b78b77d..0000000 Binary files "a/source/images/\350\256\241\347\256\227\346\234\272\351\253\230\347\272\247\347\263\273\347\273\237\347\273\223\346\236\204/Pasted image 20241226182541.webp" and /dev/null differ diff --git "a/source/images/\350\256\241\347\256\227\346\234\272\351\253\230\347\272\247\347\263\273\347\273\237\347\273\223\346\236\204/Pasted image 20241226182636.webp" "b/source/images/\350\256\241\347\256\227\346\234\272\351\253\230\347\272\247\347\263\273\347\273\237\347\273\223\346\236\204/Pasted image 20241226182636.webp" deleted file mode 100644 index 4a24f59..0000000 Binary files "a/source/images/\350\256\241\347\256\227\346\234\272\351\253\230\347\272\247\347\263\273\347\273\237\347\273\223\346\236\204/Pasted image 20241226182636.webp" and /dev/null differ diff --git "a/source/images/\350\256\241\347\256\227\346\234\272\351\253\230\347\272\247\347\263\273\347\273\237\347\273\223\346\236\204/Pasted image 20241226182644.webp" "b/source/images/\350\256\241\347\256\227\346\234\272\351\253\230\347\272\247\347\263\273\347\273\237\347\273\223\346\236\204/Pasted image 20241226182644.webp" deleted file mode 100644 index 123e395..0000000 Binary files "a/source/images/\350\256\241\347\256\227\346\234\272\351\253\230\347\272\247\347\263\273\347\273\237\347\273\223\346\236\204/Pasted image 20241226182644.webp" and /dev/null differ diff --git "a/source/images/\350\256\241\347\256\227\346\234\272\351\253\230\347\272\247\347\263\273\347\273\237\347\273\223\346\236\204/Pasted image 20241226183149.webp" "b/source/images/\350\256\241\347\256\227\346\234\272\351\253\230\347\272\247\347\263\273\347\273\237\347\273\223\346\236\204/Pasted image 20241226183149.webp" deleted file mode 100644 index f268f6d..0000000 Binary files "a/source/images/\350\256\241\347\256\227\346\234\272\351\253\230\347\272\247\347\263\273\347\273\237\347\273\223\346\236\204/Pasted image 20241226183149.webp" and /dev/null differ diff --git "a/source/images/\350\256\241\347\256\227\346\234\272\351\253\230\347\272\247\347\263\273\347\273\237\347\273\223\346\236\204/Pasted image 20241226183212.webp" "b/source/images/\350\256\241\347\256\227\346\234\272\351\253\230\347\272\247\347\263\273\347\273\237\347\273\223\346\236\204/Pasted image 20241226183212.webp" deleted file mode 100644 index 7ddbe2f..0000000 Binary files "a/source/images/\350\256\241\347\256\227\346\234\272\351\253\230\347\272\247\347\263\273\347\273\237\347\273\223\346\236\204/Pasted image 20241226183212.webp" and /dev/null differ diff --git "a/source/images/\350\256\241\347\256\227\346\234\272\351\253\230\347\272\247\347\263\273\347\273\237\347\273\223\346\236\204/Pasted image 20241226183218.webp" "b/source/images/\350\256\241\347\256\227\346\234\272\351\253\230\347\272\247\347\263\273\347\273\237\347\273\223\346\236\204/Pasted image 20241226183218.webp" deleted file mode 100644 index 10fa0a5..0000000 Binary files "a/source/images/\350\256\241\347\256\227\346\234\272\351\253\230\347\272\247\347\263\273\347\273\237\347\273\223\346\236\204/Pasted image 20241226183218.webp" and /dev/null differ diff --git "a/source/images/\350\256\241\347\256\227\346\234\272\351\253\230\347\272\247\347\263\273\347\273\237\347\273\223\346\236\204/Pasted image 20241226183304.webp" "b/source/images/\350\256\241\347\256\227\346\234\272\351\253\230\347\272\247\347\263\273\347\273\237\347\273\223\346\236\204/Pasted image 20241226183304.webp" deleted file mode 100644 index 26e825b..0000000 Binary files "a/source/images/\350\256\241\347\256\227\346\234\272\351\253\230\347\272\247\347\263\273\347\273\237\347\273\223\346\236\204/Pasted image 20241226183304.webp" and /dev/null differ diff --git "a/source/images/\350\256\241\347\256\227\346\234\272\351\253\230\347\272\247\347\263\273\347\273\237\347\273\223\346\236\204/Pasted image 20241226183311.webp" "b/source/images/\350\256\241\347\256\227\346\234\272\351\253\230\347\272\247\347\263\273\347\273\237\347\273\223\346\236\204/Pasted image 20241226183311.webp" deleted file mode 100644 index 7e92b19..0000000 Binary files "a/source/images/\350\256\241\347\256\227\346\234\272\351\253\230\347\272\247\347\263\273\347\273\237\347\273\223\346\236\204/Pasted image 20241226183311.webp" and /dev/null differ diff --git "a/source/images/\350\256\241\347\256\227\346\234\272\351\253\230\347\272\247\347\263\273\347\273\237\347\273\223\346\236\204/Pasted image 20241226183320.webp" "b/source/images/\350\256\241\347\256\227\346\234\272\351\253\230\347\272\247\347\263\273\347\273\237\347\273\223\346\236\204/Pasted image 20241226183320.webp" deleted file mode 100644 index 89cce59..0000000 Binary files "a/source/images/\350\256\241\347\256\227\346\234\272\351\253\230\347\272\247\347\263\273\347\273\237\347\273\223\346\236\204/Pasted image 20241226183320.webp" and /dev/null differ diff --git "a/source/images/\350\256\241\347\256\227\346\234\272\351\253\230\347\272\247\347\263\273\347\273\237\347\273\223\346\236\204/Pasted image 20241226183330.webp" "b/source/images/\350\256\241\347\256\227\346\234\272\351\253\230\347\272\247\347\263\273\347\273\237\347\273\223\346\236\204/Pasted image 20241226183330.webp" deleted file mode 100644 index 5c54664..0000000 Binary files "a/source/images/\350\256\241\347\256\227\346\234\272\351\253\230\347\272\247\347\263\273\347\273\237\347\273\223\346\236\204/Pasted image 20241226183330.webp" and /dev/null differ diff --git "a/source/images/\350\256\241\347\256\227\346\234\272\351\253\230\347\272\247\347\263\273\347\273\237\347\273\223\346\236\204/Pasted image 20241226183437.webp" "b/source/images/\350\256\241\347\256\227\346\234\272\351\253\230\347\272\247\347\263\273\347\273\237\347\273\223\346\236\204/Pasted image 20241226183437.webp" deleted file mode 100644 index 3778de6..0000000 Binary files "a/source/images/\350\256\241\347\256\227\346\234\272\351\253\230\347\272\247\347\263\273\347\273\237\347\273\223\346\236\204/Pasted image 20241226183437.webp" and /dev/null differ diff --git "a/source/images/\350\256\241\347\256\227\346\234\272\351\253\230\347\272\247\347\263\273\347\273\237\347\273\223\346\236\204/Pasted image 20241226183718.webp" "b/source/images/\350\256\241\347\256\227\346\234\272\351\253\230\347\272\247\347\263\273\347\273\237\347\273\223\346\236\204/Pasted image 20241226183718.webp" deleted file mode 100644 index 0903e78..0000000 Binary files "a/source/images/\350\256\241\347\256\227\346\234\272\351\253\230\347\272\247\347\263\273\347\273\237\347\273\223\346\236\204/Pasted image 20241226183718.webp" and /dev/null differ diff --git "a/source/images/\350\256\241\347\256\227\346\234\272\351\253\230\347\272\247\347\263\273\347\273\237\347\273\223\346\236\204/Pasted image 20241226183726.webp" "b/source/images/\350\256\241\347\256\227\346\234\272\351\253\230\347\272\247\347\263\273\347\273\237\347\273\223\346\236\204/Pasted image 20241226183726.webp" deleted file mode 100644 index 2a3f81c..0000000 Binary files "a/source/images/\350\256\241\347\256\227\346\234\272\351\253\230\347\272\247\347\263\273\347\273\237\347\273\223\346\236\204/Pasted image 20241226183726.webp" and /dev/null differ diff --git "a/source/images/\350\256\241\347\256\227\346\234\272\351\253\230\347\272\247\347\263\273\347\273\237\347\273\223\346\236\204/Pasted image 20241226183745.webp" "b/source/images/\350\256\241\347\256\227\346\234\272\351\253\230\347\272\247\347\263\273\347\273\237\347\273\223\346\236\204/Pasted image 20241226183745.webp" deleted file mode 100644 index 9ca461f..0000000 Binary files "a/source/images/\350\256\241\347\256\227\346\234\272\351\253\230\347\272\247\347\263\273\347\273\237\347\273\223\346\236\204/Pasted image 20241226183745.webp" and /dev/null differ diff --git "a/source/images/\350\256\241\347\256\227\346\234\272\351\253\230\347\272\247\347\263\273\347\273\237\347\273\223\346\236\204/Pasted image 20241226183801.webp" "b/source/images/\350\256\241\347\256\227\346\234\272\351\253\230\347\272\247\347\263\273\347\273\237\347\273\223\346\236\204/Pasted image 20241226183801.webp" deleted file mode 100644 index c99eaad..0000000 Binary files "a/source/images/\350\256\241\347\256\227\346\234\272\351\253\230\347\272\247\347\263\273\347\273\237\347\273\223\346\236\204/Pasted image 20241226183801.webp" and /dev/null differ diff --git "a/source/images/\350\256\241\347\256\227\346\234\272\351\253\230\347\272\247\347\263\273\347\273\237\347\273\223\346\236\204/Pasted image 20241226183934.webp" "b/source/images/\350\256\241\347\256\227\346\234\272\351\253\230\347\272\247\347\263\273\347\273\237\347\273\223\346\236\204/Pasted image 20241226183934.webp" deleted file mode 100644 index 2a06b90..0000000 Binary files "a/source/images/\350\256\241\347\256\227\346\234\272\351\253\230\347\272\247\347\263\273\347\273\237\347\273\223\346\236\204/Pasted image 20241226183934.webp" and /dev/null differ diff --git "a/source/images/\350\256\241\347\256\227\346\234\272\351\253\230\347\272\247\347\263\273\347\273\237\347\273\223\346\236\204/Pasted image 20241226183941.webp" "b/source/images/\350\256\241\347\256\227\346\234\272\351\253\230\347\272\247\347\263\273\347\273\237\347\273\223\346\236\204/Pasted image 20241226183941.webp" deleted file mode 100644 index 844f046..0000000 Binary files "a/source/images/\350\256\241\347\256\227\346\234\272\351\253\230\347\272\247\347\263\273\347\273\237\347\273\223\346\236\204/Pasted image 20241226183941.webp" and /dev/null differ diff --git "a/source/images/\350\256\241\347\256\227\346\234\272\351\253\230\347\272\247\347\263\273\347\273\237\347\273\223\346\236\204/Pasted image 20241226183949.webp" "b/source/images/\350\256\241\347\256\227\346\234\272\351\253\230\347\272\247\347\263\273\347\273\237\347\273\223\346\236\204/Pasted image 20241226183949.webp" deleted file mode 100644 index 4637435..0000000 Binary files "a/source/images/\350\256\241\347\256\227\346\234\272\351\253\230\347\272\247\347\263\273\347\273\237\347\273\223\346\236\204/Pasted image 20241226183949.webp" and /dev/null differ diff --git "a/source/images/\350\256\241\347\256\227\346\234\272\351\253\230\347\272\247\347\263\273\347\273\237\347\273\223\346\236\204/Pasted image 20241226184109.webp" "b/source/images/\350\256\241\347\256\227\346\234\272\351\253\230\347\272\247\347\263\273\347\273\237\347\273\223\346\236\204/Pasted image 20241226184109.webp" deleted file mode 100644 index 8564a19..0000000 Binary files "a/source/images/\350\256\241\347\256\227\346\234\272\351\253\230\347\272\247\347\263\273\347\273\237\347\273\223\346\236\204/Pasted image 20241226184109.webp" and /dev/null differ diff --git "a/source/images/\350\256\241\347\256\227\346\234\272\351\253\230\347\272\247\347\263\273\347\273\237\347\273\223\346\236\204/Pasted image 20241226184116.webp" "b/source/images/\350\256\241\347\256\227\346\234\272\351\253\230\347\272\247\347\263\273\347\273\237\347\273\223\346\236\204/Pasted image 20241226184116.webp" deleted file mode 100644 index 62adf97..0000000 Binary files "a/source/images/\350\256\241\347\256\227\346\234\272\351\253\230\347\272\247\347\263\273\347\273\237\347\273\223\346\236\204/Pasted image 20241226184116.webp" and /dev/null differ diff --git "a/source/images/\350\256\241\347\256\227\346\234\272\351\253\230\347\272\247\347\263\273\347\273\237\347\273\223\346\236\204/Pasted image 20241226184454.webp" "b/source/images/\350\256\241\347\256\227\346\234\272\351\253\230\347\272\247\347\263\273\347\273\237\347\273\223\346\236\204/Pasted image 20241226184454.webp" deleted file mode 100644 index 4405cbc..0000000 Binary files "a/source/images/\350\256\241\347\256\227\346\234\272\351\253\230\347\272\247\347\263\273\347\273\237\347\273\223\346\236\204/Pasted image 20241226184454.webp" and /dev/null differ diff --git "a/source/images/\350\256\241\347\256\227\346\234\272\351\253\230\347\272\247\347\263\273\347\273\237\347\273\223\346\236\204/Pasted image 20241226184514.webp" "b/source/images/\350\256\241\347\256\227\346\234\272\351\253\230\347\272\247\347\263\273\347\273\237\347\273\223\346\236\204/Pasted image 20241226184514.webp" deleted file mode 100644 index 70cdd65..0000000 Binary files "a/source/images/\350\256\241\347\256\227\346\234\272\351\253\230\347\272\247\347\263\273\347\273\237\347\273\223\346\236\204/Pasted image 20241226184514.webp" and /dev/null differ diff --git "a/source/images/\350\256\241\347\256\227\346\234\272\351\253\230\347\272\247\347\263\273\347\273\237\347\273\223\346\236\204/Pasted image 20241226184623.webp" "b/source/images/\350\256\241\347\256\227\346\234\272\351\253\230\347\272\247\347\263\273\347\273\237\347\273\223\346\236\204/Pasted image 20241226184623.webp" deleted file mode 100644 index 0843a26..0000000 Binary files "a/source/images/\350\256\241\347\256\227\346\234\272\351\253\230\347\272\247\347\263\273\347\273\237\347\273\223\346\236\204/Pasted image 20241226184623.webp" and /dev/null differ diff --git "a/source/images/\350\256\241\347\256\227\346\234\272\351\253\230\347\272\247\347\263\273\347\273\237\347\273\223\346\236\204/Pasted image 20241226184642.webp" "b/source/images/\350\256\241\347\256\227\346\234\272\351\253\230\347\272\247\347\263\273\347\273\237\347\273\223\346\236\204/Pasted image 20241226184642.webp" deleted file mode 100644 index 623a707..0000000 Binary files "a/source/images/\350\256\241\347\256\227\346\234\272\351\253\230\347\272\247\347\263\273\347\273\237\347\273\223\346\236\204/Pasted image 20241226184642.webp" and /dev/null differ diff --git "a/source/images/\350\256\241\347\256\227\346\234\272\351\253\230\347\272\247\347\263\273\347\273\237\347\273\223\346\236\204/Pasted image 20241226185053.webp" "b/source/images/\350\256\241\347\256\227\346\234\272\351\253\230\347\272\247\347\263\273\347\273\237\347\273\223\346\236\204/Pasted image 20241226185053.webp" deleted file mode 100644 index ff3f837..0000000 Binary files "a/source/images/\350\256\241\347\256\227\346\234\272\351\253\230\347\272\247\347\263\273\347\273\237\347\273\223\346\236\204/Pasted image 20241226185053.webp" and /dev/null differ diff --git "a/source/images/\350\256\241\347\256\227\346\234\272\351\253\230\347\272\247\347\263\273\347\273\237\347\273\223\346\236\204/Pasted image 20241226185114.webp" "b/source/images/\350\256\241\347\256\227\346\234\272\351\253\230\347\272\247\347\263\273\347\273\237\347\273\223\346\236\204/Pasted image 20241226185114.webp" deleted file mode 100644 index 3378f08..0000000 Binary files "a/source/images/\350\256\241\347\256\227\346\234\272\351\253\230\347\272\247\347\263\273\347\273\237\347\273\223\346\236\204/Pasted image 20241226185114.webp" and /dev/null differ diff --git "a/source/images/\350\256\241\347\256\227\346\234\272\351\253\230\347\272\247\347\263\273\347\273\237\347\273\223\346\236\204/Pasted image 20241226185139.webp" "b/source/images/\350\256\241\347\256\227\346\234\272\351\253\230\347\272\247\347\263\273\347\273\237\347\273\223\346\236\204/Pasted image 20241226185139.webp" deleted file mode 100644 index 28449d5..0000000 Binary files "a/source/images/\350\256\241\347\256\227\346\234\272\351\253\230\347\272\247\347\263\273\347\273\237\347\273\223\346\236\204/Pasted image 20241226185139.webp" and /dev/null differ diff --git "a/source/images/\350\256\241\347\256\227\346\234\272\351\253\230\347\272\247\347\263\273\347\273\237\347\273\223\346\236\204/Pasted image 20241226185149.webp" "b/source/images/\350\256\241\347\256\227\346\234\272\351\253\230\347\272\247\347\263\273\347\273\237\347\273\223\346\236\204/Pasted image 20241226185149.webp" deleted file mode 100644 index c17521a..0000000 Binary files "a/source/images/\350\256\241\347\256\227\346\234\272\351\253\230\347\272\247\347\263\273\347\273\237\347\273\223\346\236\204/Pasted image 20241226185149.webp" and /dev/null differ diff --git "a/source/images/\350\256\241\347\256\227\346\234\272\351\253\230\347\272\247\347\263\273\347\273\237\347\273\223\346\236\204/Pasted image 20241226185312.webp" "b/source/images/\350\256\241\347\256\227\346\234\272\351\253\230\347\272\247\347\263\273\347\273\237\347\273\223\346\236\204/Pasted image 20241226185312.webp" deleted file mode 100644 index 5f37c99..0000000 Binary files "a/source/images/\350\256\241\347\256\227\346\234\272\351\253\230\347\272\247\347\263\273\347\273\237\347\273\223\346\236\204/Pasted image 20241226185312.webp" and /dev/null differ diff --git "a/source/images/\350\256\241\347\256\227\346\234\272\351\253\230\347\272\247\347\263\273\347\273\237\347\273\223\346\236\204/Pasted image 20241226185352.webp" "b/source/images/\350\256\241\347\256\227\346\234\272\351\253\230\347\272\247\347\263\273\347\273\237\347\273\223\346\236\204/Pasted image 20241226185352.webp" deleted file mode 100644 index 988901a..0000000 Binary files "a/source/images/\350\256\241\347\256\227\346\234\272\351\253\230\347\272\247\347\263\273\347\273\237\347\273\223\346\236\204/Pasted image 20241226185352.webp" and /dev/null differ diff --git "a/source/images/\350\256\241\347\256\227\346\234\272\351\253\230\347\272\247\347\263\273\347\273\237\347\273\223\346\236\204/Pasted image 20241226185854.webp" "b/source/images/\350\256\241\347\256\227\346\234\272\351\253\230\347\272\247\347\263\273\347\273\237\347\273\223\346\236\204/Pasted image 20241226185854.webp" deleted file mode 100644 index a64d13a..0000000 Binary files "a/source/images/\350\256\241\347\256\227\346\234\272\351\253\230\347\272\247\347\263\273\347\273\237\347\273\223\346\236\204/Pasted image 20241226185854.webp" and /dev/null differ diff --git "a/source/images/\350\256\241\347\256\227\346\234\272\351\253\230\347\272\247\347\263\273\347\273\237\347\273\223\346\236\204/Pasted image 20241226185932.webp" "b/source/images/\350\256\241\347\256\227\346\234\272\351\253\230\347\272\247\347\263\273\347\273\237\347\273\223\346\236\204/Pasted image 20241226185932.webp" deleted file mode 100644 index 543cbec..0000000 Binary files "a/source/images/\350\256\241\347\256\227\346\234\272\351\253\230\347\272\247\347\263\273\347\273\237\347\273\223\346\236\204/Pasted image 20241226185932.webp" and /dev/null differ diff --git "a/source/images/\350\256\241\347\256\227\346\234\272\351\253\230\347\272\247\347\263\273\347\273\237\347\273\223\346\236\204/Pasted image 20241226190039.webp" "b/source/images/\350\256\241\347\256\227\346\234\272\351\253\230\347\272\247\347\263\273\347\273\237\347\273\223\346\236\204/Pasted image 20241226190039.webp" deleted file mode 100644 index 99258a5..0000000 Binary files "a/source/images/\350\256\241\347\256\227\346\234\272\351\253\230\347\272\247\347\263\273\347\273\237\347\273\223\346\236\204/Pasted image 20241226190039.webp" and /dev/null differ diff --git "a/source/images/\350\256\241\347\256\227\346\234\272\351\253\230\347\272\247\347\263\273\347\273\237\347\273\223\346\236\204/Pasted image 20241226190110.webp" "b/source/images/\350\256\241\347\256\227\346\234\272\351\253\230\347\272\247\347\263\273\347\273\237\347\273\223\346\236\204/Pasted image 20241226190110.webp" deleted file mode 100644 index 082219b..0000000 Binary files "a/source/images/\350\256\241\347\256\227\346\234\272\351\253\230\347\272\247\347\263\273\347\273\237\347\273\223\346\236\204/Pasted image 20241226190110.webp" and /dev/null differ diff --git "a/source/images/\350\256\241\347\256\227\346\234\272\351\253\230\347\272\247\347\263\273\347\273\237\347\273\223\346\236\204/Pasted image 20241226190308.webp" "b/source/images/\350\256\241\347\256\227\346\234\272\351\253\230\347\272\247\347\263\273\347\273\237\347\273\223\346\236\204/Pasted image 20241226190308.webp" deleted file mode 100644 index 58e8758..0000000 Binary files "a/source/images/\350\256\241\347\256\227\346\234\272\351\253\230\347\272\247\347\263\273\347\273\237\347\273\223\346\236\204/Pasted image 20241226190308.webp" and /dev/null differ diff --git "a/source/images/\350\256\241\347\256\227\346\234\272\351\253\230\347\272\247\347\263\273\347\273\237\347\273\223\346\236\204/Pasted image 20241226190330.webp" "b/source/images/\350\256\241\347\256\227\346\234\272\351\253\230\347\272\247\347\263\273\347\273\237\347\273\223\346\236\204/Pasted image 20241226190330.webp" deleted file mode 100644 index e20c0a9..0000000 Binary files "a/source/images/\350\256\241\347\256\227\346\234\272\351\253\230\347\272\247\347\263\273\347\273\237\347\273\223\346\236\204/Pasted image 20241226190330.webp" and /dev/null differ diff --git "a/source/images/\350\256\241\347\256\227\346\234\272\351\253\230\347\272\247\347\263\273\347\273\237\347\273\223\346\236\204/Pasted image 20241226190506.webp" "b/source/images/\350\256\241\347\256\227\346\234\272\351\253\230\347\272\247\347\263\273\347\273\237\347\273\223\346\236\204/Pasted image 20241226190506.webp" deleted file mode 100644 index eec70eb..0000000 Binary files "a/source/images/\350\256\241\347\256\227\346\234\272\351\253\230\347\272\247\347\263\273\347\273\237\347\273\223\346\236\204/Pasted image 20241226190506.webp" and /dev/null differ diff --git "a/source/images/\350\256\241\347\256\227\346\234\272\351\253\230\347\272\247\347\263\273\347\273\237\347\273\223\346\236\204/Pasted image 20241226190609.webp" "b/source/images/\350\256\241\347\256\227\346\234\272\351\253\230\347\272\247\347\263\273\347\273\237\347\273\223\346\236\204/Pasted image 20241226190609.webp" deleted file mode 100644 index 489486c..0000000 Binary files "a/source/images/\350\256\241\347\256\227\346\234\272\351\253\230\347\272\247\347\263\273\347\273\237\347\273\223\346\236\204/Pasted image 20241226190609.webp" and /dev/null differ diff --git "a/source/images/\350\256\241\347\256\227\346\234\272\351\253\230\347\272\247\347\263\273\347\273\237\347\273\223\346\236\204/Pasted image 20241226190653.webp" "b/source/images/\350\256\241\347\256\227\346\234\272\351\253\230\347\272\247\347\263\273\347\273\237\347\273\223\346\236\204/Pasted image 20241226190653.webp" deleted file mode 100644 index 16696d0..0000000 Binary files "a/source/images/\350\256\241\347\256\227\346\234\272\351\253\230\347\272\247\347\263\273\347\273\237\347\273\223\346\236\204/Pasted image 20241226190653.webp" and /dev/null differ diff --git "a/source/images/\350\256\241\347\256\227\346\234\272\351\253\230\347\272\247\347\263\273\347\273\237\347\273\223\346\236\204/Pasted image 20241226190739.webp" "b/source/images/\350\256\241\347\256\227\346\234\272\351\253\230\347\272\247\347\263\273\347\273\237\347\273\223\346\236\204/Pasted image 20241226190739.webp" deleted file mode 100644 index 4259177..0000000 Binary files "a/source/images/\350\256\241\347\256\227\346\234\272\351\253\230\347\272\247\347\263\273\347\273\237\347\273\223\346\236\204/Pasted image 20241226190739.webp" and /dev/null differ diff --git "a/source/images/\350\256\241\347\256\227\346\234\272\351\253\230\347\272\247\347\263\273\347\273\237\347\273\223\346\236\204/Pasted image 20241226190750.webp" "b/source/images/\350\256\241\347\256\227\346\234\272\351\253\230\347\272\247\347\263\273\347\273\237\347\273\223\346\236\204/Pasted image 20241226190750.webp" deleted file mode 100644 index df24081..0000000 Binary files "a/source/images/\350\256\241\347\256\227\346\234\272\351\253\230\347\272\247\347\263\273\347\273\237\347\273\223\346\236\204/Pasted image 20241226190750.webp" and /dev/null differ diff --git "a/source/images/\350\256\241\347\256\227\346\234\272\351\253\230\347\272\247\347\263\273\347\273\237\347\273\223\346\236\204/Pasted image 20241226190805.webp" "b/source/images/\350\256\241\347\256\227\346\234\272\351\253\230\347\272\247\347\263\273\347\273\237\347\273\223\346\236\204/Pasted image 20241226190805.webp" deleted file mode 100644 index 4908b03..0000000 Binary files "a/source/images/\350\256\241\347\256\227\346\234\272\351\253\230\347\272\247\347\263\273\347\273\237\347\273\223\346\236\204/Pasted image 20241226190805.webp" and /dev/null differ diff --git "a/source/images/\350\256\241\347\256\227\346\234\272\351\253\230\347\272\247\347\263\273\347\273\237\347\273\223\346\236\204/Pasted image 20241226190830.webp" "b/source/images/\350\256\241\347\256\227\346\234\272\351\253\230\347\272\247\347\263\273\347\273\237\347\273\223\346\236\204/Pasted image 20241226190830.webp" deleted file mode 100644 index 67a0c0a..0000000 Binary files "a/source/images/\350\256\241\347\256\227\346\234\272\351\253\230\347\272\247\347\263\273\347\273\237\347\273\223\346\236\204/Pasted image 20241226190830.webp" and /dev/null differ diff --git "a/source/images/\350\256\241\347\256\227\346\234\272\351\253\230\347\272\247\347\263\273\347\273\237\347\273\223\346\236\204/Pasted image 20241226190904.webp" "b/source/images/\350\256\241\347\256\227\346\234\272\351\253\230\347\272\247\347\263\273\347\273\237\347\273\223\346\236\204/Pasted image 20241226190904.webp" deleted file mode 100644 index 43414a5..0000000 Binary files "a/source/images/\350\256\241\347\256\227\346\234\272\351\253\230\347\272\247\347\263\273\347\273\237\347\273\223\346\236\204/Pasted image 20241226190904.webp" and /dev/null differ diff --git "a/source/images/\350\256\241\347\256\227\346\234\272\351\253\230\347\272\247\347\263\273\347\273\237\347\273\223\346\236\204/Pasted image 20241226190915.webp" "b/source/images/\350\256\241\347\256\227\346\234\272\351\253\230\347\272\247\347\263\273\347\273\237\347\273\223\346\236\204/Pasted image 20241226190915.webp" deleted file mode 100644 index ee2bd57..0000000 Binary files "a/source/images/\350\256\241\347\256\227\346\234\272\351\253\230\347\272\247\347\263\273\347\273\237\347\273\223\346\236\204/Pasted image 20241226190915.webp" and /dev/null differ diff --git "a/source/images/\350\256\241\347\256\227\346\234\272\351\253\230\347\272\247\347\263\273\347\273\237\347\273\223\346\236\204/Pasted image 20241226190950.webp" "b/source/images/\350\256\241\347\256\227\346\234\272\351\253\230\347\272\247\347\263\273\347\273\237\347\273\223\346\236\204/Pasted image 20241226190950.webp" deleted file mode 100644 index f08d15e..0000000 Binary files "a/source/images/\350\256\241\347\256\227\346\234\272\351\253\230\347\272\247\347\263\273\347\273\237\347\273\223\346\236\204/Pasted image 20241226190950.webp" and /dev/null differ diff --git "a/source/images/\350\256\241\347\256\227\346\234\272\351\253\230\347\272\247\347\263\273\347\273\237\347\273\223\346\236\204/Pasted image 20241226191000.webp" "b/source/images/\350\256\241\347\256\227\346\234\272\351\253\230\347\272\247\347\263\273\347\273\237\347\273\223\346\236\204/Pasted image 20241226191000.webp" deleted file mode 100644 index e99c917..0000000 Binary files "a/source/images/\350\256\241\347\256\227\346\234\272\351\253\230\347\272\247\347\263\273\347\273\237\347\273\223\346\236\204/Pasted image 20241226191000.webp" and /dev/null differ diff --git "a/source/images/\350\256\241\347\256\227\346\234\272\351\253\230\347\272\247\347\263\273\347\273\237\347\273\223\346\236\204/Pasted image 20241226191041.webp" "b/source/images/\350\256\241\347\256\227\346\234\272\351\253\230\347\272\247\347\263\273\347\273\237\347\273\223\346\236\204/Pasted image 20241226191041.webp" deleted file mode 100644 index d0d445e..0000000 Binary files "a/source/images/\350\256\241\347\256\227\346\234\272\351\253\230\347\272\247\347\263\273\347\273\237\347\273\223\346\236\204/Pasted image 20241226191041.webp" and /dev/null differ diff --git "a/source/images/\350\256\241\347\256\227\346\234\272\351\253\230\347\272\247\347\263\273\347\273\237\347\273\223\346\236\204/Pasted image 20241226191119.webp" "b/source/images/\350\256\241\347\256\227\346\234\272\351\253\230\347\272\247\347\263\273\347\273\237\347\273\223\346\236\204/Pasted image 20241226191119.webp" deleted file mode 100644 index 8827af2..0000000 Binary files "a/source/images/\350\256\241\347\256\227\346\234\272\351\253\230\347\272\247\347\263\273\347\273\237\347\273\223\346\236\204/Pasted image 20241226191119.webp" and /dev/null differ diff --git "a/source/images/\350\256\241\347\256\227\346\234\272\351\253\230\347\272\247\347\263\273\347\273\237\347\273\223\346\236\204/Pasted image 20241226191151.webp" "b/source/images/\350\256\241\347\256\227\346\234\272\351\253\230\347\272\247\347\263\273\347\273\237\347\273\223\346\236\204/Pasted image 20241226191151.webp" deleted file mode 100644 index 871696e..0000000 Binary files "a/source/images/\350\256\241\347\256\227\346\234\272\351\253\230\347\272\247\347\263\273\347\273\237\347\273\223\346\236\204/Pasted image 20241226191151.webp" and /dev/null differ diff --git "a/source/images/\350\256\241\347\256\227\346\234\272\351\253\230\347\272\247\347\263\273\347\273\237\347\273\223\346\236\204/Pasted image 20241226191210.webp" "b/source/images/\350\256\241\347\256\227\346\234\272\351\253\230\347\272\247\347\263\273\347\273\237\347\273\223\346\236\204/Pasted image 20241226191210.webp" deleted file mode 100644 index d04255b..0000000 Binary files "a/source/images/\350\256\241\347\256\227\346\234\272\351\253\230\347\272\247\347\263\273\347\273\237\347\273\223\346\236\204/Pasted image 20241226191210.webp" and /dev/null differ diff --git "a/source/images/\350\256\241\347\256\227\346\234\272\351\253\230\347\272\247\347\263\273\347\273\237\347\273\223\346\236\204/Pasted image 20241226191251.webp" "b/source/images/\350\256\241\347\256\227\346\234\272\351\253\230\347\272\247\347\263\273\347\273\237\347\273\223\346\236\204/Pasted image 20241226191251.webp" deleted file mode 100644 index 0f60020..0000000 Binary files "a/source/images/\350\256\241\347\256\227\346\234\272\351\253\230\347\272\247\347\263\273\347\273\237\347\273\223\346\236\204/Pasted image 20241226191251.webp" and /dev/null differ diff --git "a/source/images/\350\256\241\347\256\227\346\234\272\351\253\230\347\272\247\347\263\273\347\273\237\347\273\223\346\236\204/Pasted image 20241226191301.webp" "b/source/images/\350\256\241\347\256\227\346\234\272\351\253\230\347\272\247\347\263\273\347\273\237\347\273\223\346\236\204/Pasted image 20241226191301.webp" deleted file mode 100644 index 1dd9062..0000000 Binary files "a/source/images/\350\256\241\347\256\227\346\234\272\351\253\230\347\272\247\347\263\273\347\273\237\347\273\223\346\236\204/Pasted image 20241226191301.webp" and /dev/null differ diff --git "a/source/images/\350\256\241\347\256\227\346\234\272\351\253\230\347\272\247\347\263\273\347\273\237\347\273\223\346\236\204/Pasted image 20241226191317.webp" "b/source/images/\350\256\241\347\256\227\346\234\272\351\253\230\347\272\247\347\263\273\347\273\237\347\273\223\346\236\204/Pasted image 20241226191317.webp" deleted file mode 100644 index d7c3062..0000000 Binary files "a/source/images/\350\256\241\347\256\227\346\234\272\351\253\230\347\272\247\347\263\273\347\273\237\347\273\223\346\236\204/Pasted image 20241226191317.webp" and /dev/null differ diff --git "a/source/images/\350\256\241\347\256\227\346\234\272\351\253\230\347\272\247\347\263\273\347\273\237\347\273\223\346\236\204/Pasted image 20241226191425.webp" "b/source/images/\350\256\241\347\256\227\346\234\272\351\253\230\347\272\247\347\263\273\347\273\237\347\273\223\346\236\204/Pasted image 20241226191425.webp" deleted file mode 100644 index 71d7901..0000000 Binary files "a/source/images/\350\256\241\347\256\227\346\234\272\351\253\230\347\272\247\347\263\273\347\273\237\347\273\223\346\236\204/Pasted image 20241226191425.webp" and /dev/null differ diff --git "a/source/images/\350\256\241\347\256\227\346\234\272\351\253\230\347\272\247\347\263\273\347\273\237\347\273\223\346\236\204/Pasted image 20241226191453.webp" "b/source/images/\350\256\241\347\256\227\346\234\272\351\253\230\347\272\247\347\263\273\347\273\237\347\273\223\346\236\204/Pasted image 20241226191453.webp" deleted file mode 100644 index a1ecf53..0000000 Binary files "a/source/images/\350\256\241\347\256\227\346\234\272\351\253\230\347\272\247\347\263\273\347\273\237\347\273\223\346\236\204/Pasted image 20241226191453.webp" and /dev/null differ diff --git "a/source/images/\350\256\241\347\256\227\346\234\272\351\253\230\347\272\247\347\263\273\347\273\237\347\273\223\346\236\204/Pasted image 20241226191536.webp" "b/source/images/\350\256\241\347\256\227\346\234\272\351\253\230\347\272\247\347\263\273\347\273\237\347\273\223\346\236\204/Pasted image 20241226191536.webp" deleted file mode 100644 index 01847a7..0000000 Binary files "a/source/images/\350\256\241\347\256\227\346\234\272\351\253\230\347\272\247\347\263\273\347\273\237\347\273\223\346\236\204/Pasted image 20241226191536.webp" and /dev/null differ diff --git "a/source/images/\350\256\241\347\256\227\346\234\272\351\253\230\347\272\247\347\263\273\347\273\237\347\273\223\346\236\204/Pasted image 20241226191819.webp" "b/source/images/\350\256\241\347\256\227\346\234\272\351\253\230\347\272\247\347\263\273\347\273\237\347\273\223\346\236\204/Pasted image 20241226191819.webp" deleted file mode 100644 index e8cf83b..0000000 Binary files "a/source/images/\350\256\241\347\256\227\346\234\272\351\253\230\347\272\247\347\263\273\347\273\237\347\273\223\346\236\204/Pasted image 20241226191819.webp" and /dev/null differ diff --git "a/source/images/\350\256\241\347\256\227\346\234\272\351\253\230\347\272\247\347\263\273\347\273\237\347\273\223\346\236\204/Pasted image 20241226191854.webp" "b/source/images/\350\256\241\347\256\227\346\234\272\351\253\230\347\272\247\347\263\273\347\273\237\347\273\223\346\236\204/Pasted image 20241226191854.webp" deleted file mode 100644 index 4927c8b..0000000 Binary files "a/source/images/\350\256\241\347\256\227\346\234\272\351\253\230\347\272\247\347\263\273\347\273\237\347\273\223\346\236\204/Pasted image 20241226191854.webp" and /dev/null differ diff --git "a/source/images/\350\256\241\347\256\227\346\234\272\351\253\230\347\272\247\347\263\273\347\273\237\347\273\223\346\236\204/Pasted image 20241226191923.webp" "b/source/images/\350\256\241\347\256\227\346\234\272\351\253\230\347\272\247\347\263\273\347\273\237\347\273\223\346\236\204/Pasted image 20241226191923.webp" deleted file mode 100644 index 309b893..0000000 Binary files "a/source/images/\350\256\241\347\256\227\346\234\272\351\253\230\347\272\247\347\263\273\347\273\237\347\273\223\346\236\204/Pasted image 20241226191923.webp" and /dev/null differ diff --git "a/source/images/\350\256\241\347\256\227\346\234\272\351\253\230\347\272\247\347\263\273\347\273\237\347\273\223\346\236\204/Pasted image 20241226191950.webp" "b/source/images/\350\256\241\347\256\227\346\234\272\351\253\230\347\272\247\347\263\273\347\273\237\347\273\223\346\236\204/Pasted image 20241226191950.webp" deleted file mode 100644 index 5b57c8d..0000000 Binary files "a/source/images/\350\256\241\347\256\227\346\234\272\351\253\230\347\272\247\347\263\273\347\273\237\347\273\223\346\236\204/Pasted image 20241226191950.webp" and /dev/null differ diff --git "a/source/images/\350\256\241\347\256\227\346\234\272\351\253\230\347\272\247\347\263\273\347\273\237\347\273\223\346\236\204/Pasted image 20241226192014.webp" "b/source/images/\350\256\241\347\256\227\346\234\272\351\253\230\347\272\247\347\263\273\347\273\237\347\273\223\346\236\204/Pasted image 20241226192014.webp" deleted file mode 100644 index 0d974d5..0000000 Binary files "a/source/images/\350\256\241\347\256\227\346\234\272\351\253\230\347\272\247\347\263\273\347\273\237\347\273\223\346\236\204/Pasted image 20241226192014.webp" and /dev/null differ diff --git "a/source/images/\350\256\241\347\256\227\346\234\272\351\253\230\347\272\247\347\263\273\347\273\237\347\273\223\346\236\204/Pasted image 20241226192035.webp" "b/source/images/\350\256\241\347\256\227\346\234\272\351\253\230\347\272\247\347\263\273\347\273\237\347\273\223\346\236\204/Pasted image 20241226192035.webp" deleted file mode 100644 index fafe842..0000000 Binary files "a/source/images/\350\256\241\347\256\227\346\234\272\351\253\230\347\272\247\347\263\273\347\273\237\347\273\223\346\236\204/Pasted image 20241226192035.webp" and /dev/null differ diff --git "a/source/images/\350\256\241\347\256\227\346\234\272\351\253\230\347\272\247\347\263\273\347\273\237\347\273\223\346\236\204/Pasted image 20241226192100.webp" "b/source/images/\350\256\241\347\256\227\346\234\272\351\253\230\347\272\247\347\263\273\347\273\237\347\273\223\346\236\204/Pasted image 20241226192100.webp" deleted file mode 100644 index ada36ea..0000000 Binary files "a/source/images/\350\256\241\347\256\227\346\234\272\351\253\230\347\272\247\347\263\273\347\273\237\347\273\223\346\236\204/Pasted image 20241226192100.webp" and /dev/null differ diff --git "a/source/images/\350\256\241\347\256\227\346\234\272\351\253\230\347\272\247\347\263\273\347\273\237\347\273\223\346\236\204/Pasted image 20241226192105.webp" "b/source/images/\350\256\241\347\256\227\346\234\272\351\253\230\347\272\247\347\263\273\347\273\237\347\273\223\346\236\204/Pasted image 20241226192105.webp" deleted file mode 100644 index 7ccbe82..0000000 Binary files "a/source/images/\350\256\241\347\256\227\346\234\272\351\253\230\347\272\247\347\263\273\347\273\237\347\273\223\346\236\204/Pasted image 20241226192105.webp" and /dev/null differ diff --git "a/source/images/\350\256\241\347\256\227\346\234\272\351\253\230\347\272\247\347\263\273\347\273\237\347\273\223\346\236\204/Pasted image 20241226192400.webp" "b/source/images/\350\256\241\347\256\227\346\234\272\351\253\230\347\272\247\347\263\273\347\273\237\347\273\223\346\236\204/Pasted image 20241226192400.webp" deleted file mode 100644 index 0a5b6fd..0000000 Binary files "a/source/images/\350\256\241\347\256\227\346\234\272\351\253\230\347\272\247\347\263\273\347\273\237\347\273\223\346\236\204/Pasted image 20241226192400.webp" and /dev/null differ diff --git "a/source/images/\350\256\241\347\256\227\346\234\272\351\253\230\347\272\247\347\263\273\347\273\237\347\273\223\346\236\204/Pasted image 20241226192406.webp" "b/source/images/\350\256\241\347\256\227\346\234\272\351\253\230\347\272\247\347\263\273\347\273\237\347\273\223\346\236\204/Pasted image 20241226192406.webp" deleted file mode 100644 index f6a1049..0000000 Binary files "a/source/images/\350\256\241\347\256\227\346\234\272\351\253\230\347\272\247\347\263\273\347\273\237\347\273\223\346\236\204/Pasted image 20241226192406.webp" and /dev/null differ diff --git "a/source/images/\350\256\241\347\256\227\346\234\272\351\253\230\347\272\247\347\263\273\347\273\237\347\273\223\346\236\204/Pasted image 20241226212059.webp" "b/source/images/\350\256\241\347\256\227\346\234\272\351\253\230\347\272\247\347\263\273\347\273\237\347\273\223\346\236\204/Pasted image 20241226212059.webp" deleted file mode 100644 index b3e94f8..0000000 Binary files "a/source/images/\350\256\241\347\256\227\346\234\272\351\253\230\347\272\247\347\263\273\347\273\237\347\273\223\346\236\204/Pasted image 20241226212059.webp" and /dev/null differ diff --git "a/source/images/\350\275\254\345\217\221\345\206\205\347\275\221\347\275\221\351\241\265\345\210\260\346\234\254\345\234\260/Pasted image 20250528193554.webp" "b/source/images/\350\275\254\345\217\221\345\206\205\347\275\221\347\275\221\351\241\265\345\210\260\346\234\254\345\234\260/Pasted image 20250528193554.webp" deleted file mode 100644 index 41732ca..0000000 Binary files "a/source/images/\350\275\254\345\217\221\345\206\205\347\275\221\347\275\221\351\241\265\345\210\260\346\234\254\345\234\260/Pasted image 20250528193554.webp" and /dev/null differ diff --git "a/source/images/\350\275\254\345\217\221\345\206\205\347\275\221\347\275\221\351\241\265\345\210\260\346\234\254\345\234\260/Pasted image 20250528193708.webp" "b/source/images/\350\275\254\345\217\221\345\206\205\347\275\221\347\275\221\351\241\265\345\210\260\346\234\254\345\234\260/Pasted image 20250528193708.webp" deleted file mode 100644 index 3b3dd51..0000000 Binary files "a/source/images/\350\275\254\345\217\221\345\206\205\347\275\221\347\275\221\351\241\265\345\210\260\346\234\254\345\234\260/Pasted image 20250528193708.webp" and /dev/null differ diff --git "a/source/images/\350\275\254\345\217\221\345\206\205\347\275\221\347\275\221\351\241\265\345\210\260\346\234\254\345\234\260/Pasted image 20250528193913.webp" "b/source/images/\350\275\254\345\217\221\345\206\205\347\275\221\347\275\221\351\241\265\345\210\260\346\234\254\345\234\260/Pasted image 20250528193913.webp" deleted file mode 100644 index d6d8f65..0000000 Binary files "a/source/images/\350\275\254\345\217\221\345\206\205\347\275\221\347\275\221\351\241\265\345\210\260\346\234\254\345\234\260/Pasted image 20250528193913.webp" and /dev/null differ diff --git "a/source/images/\350\275\273\351\207\217\346\216\250\347\220\206\346\211\213\350\256\260/Pasted image 20260317225553.webp" "b/source/images/\350\275\273\351\207\217\346\216\250\347\220\206\346\211\213\350\256\260/Pasted image 20260317225553.webp" deleted file mode 100644 index e449975..0000000 Binary files "a/source/images/\350\275\273\351\207\217\346\216\250\347\220\206\346\211\213\350\256\260/Pasted image 20260317225553.webp" and /dev/null differ diff --git "a/source/images/\350\275\273\351\207\217\346\216\250\347\220\206\346\211\213\350\256\260/Pasted image 20260317225947.webp" "b/source/images/\350\275\273\351\207\217\346\216\250\347\220\206\346\211\213\350\256\260/Pasted image 20260317225947.webp" deleted file mode 100644 index 884dbd9..0000000 Binary files "a/source/images/\350\275\273\351\207\217\346\216\250\347\220\206\346\211\213\350\256\260/Pasted image 20260317225947.webp" and /dev/null differ diff --git "a/source/images/\350\275\273\351\207\217\346\216\250\347\220\206\346\211\213\350\256\260/Pasted image 20260317230029.webp" "b/source/images/\350\275\273\351\207\217\346\216\250\347\220\206\346\211\213\350\256\260/Pasted image 20260317230029.webp" deleted file mode 100644 index 85bbe53..0000000 Binary files "a/source/images/\350\275\273\351\207\217\346\216\250\347\220\206\346\211\213\350\256\260/Pasted image 20260317230029.webp" and /dev/null differ diff --git "a/source/images/\350\275\273\351\207\217\346\216\250\347\220\206\346\211\213\350\256\260/Pasted image 20260317230155.webp" "b/source/images/\350\275\273\351\207\217\346\216\250\347\220\206\346\211\213\350\256\260/Pasted image 20260317230155.webp" deleted file mode 100644 index d055c21..0000000 Binary files "a/source/images/\350\275\273\351\207\217\346\216\250\347\220\206\346\211\213\350\256\260/Pasted image 20260317230155.webp" and /dev/null differ diff --git "a/source/images/\350\275\273\351\207\217\346\216\250\347\220\206\346\211\213\350\256\260/Pasted image 20260330011123.webp" "b/source/images/\350\275\273\351\207\217\346\216\250\347\220\206\346\211\213\350\256\260/Pasted image 20260330011123.webp" deleted file mode 100644 index f07abea..0000000 Binary files "a/source/images/\350\275\273\351\207\217\346\216\250\347\220\206\346\211\213\350\256\260/Pasted image 20260330011123.webp" and /dev/null differ diff --git "a/source/images/\350\275\273\351\207\217\346\216\250\347\220\206\346\211\213\350\256\260/Pasted image 20260330011131.webp" "b/source/images/\350\275\273\351\207\217\346\216\250\347\220\206\346\211\213\350\256\260/Pasted image 20260330011131.webp" deleted file mode 100644 index ef262a1..0000000 Binary files "a/source/images/\350\275\273\351\207\217\346\216\250\347\220\206\346\211\213\350\256\260/Pasted image 20260330011131.webp" and /dev/null differ diff --git "a/source/images/\351\242\230\347\233\256\347\254\224\350\256\260/Pasted image 20251211160109.webp" "b/source/images/\351\242\230\347\233\256\347\254\224\350\256\260/Pasted image 20251211160109.webp" deleted file mode 100644 index ddf8323..0000000 Binary files "a/source/images/\351\242\230\347\233\256\347\254\224\350\256\260/Pasted image 20251211160109.webp" and /dev/null differ diff --git "a/source/images/\351\242\230\347\233\256\347\254\224\350\256\260/Pasted image 20251217145442.webp" "b/source/images/\351\242\230\347\233\256\347\254\224\350\256\260/Pasted image 20251217145442.webp" deleted file mode 100644 index adba708..0000000 Binary files "a/source/images/\351\242\230\347\233\256\347\254\224\350\256\260/Pasted image 20251217145442.webp" and /dev/null differ diff --git "a/source/images/\351\242\230\347\233\256\347\254\224\350\256\260/Pasted image 20260116154906.webp" "b/source/images/\351\242\230\347\233\256\347\254\224\350\256\260/Pasted image 20260116154906.webp" deleted file mode 100644 index 18234d6..0000000 Binary files "a/source/images/\351\242\230\347\233\256\347\254\224\350\256\260/Pasted image 20260116154906.webp" and /dev/null differ diff --git a/source/links/index.md b/source/links/index.md index c0ea5c1..6c02ac5 100644 --- a/source/links/index.md +++ b/source/links/index.md @@ -1,6 +1,4 @@ --- -title: 友 链 +title: links date: 2024-05-02 15:31:57 -type: "link" --- - diff --git a/source/movies/index-1.md b/source/movies/index-1.md new file mode 100644 index 0000000..e6235d4 --- /dev/null +++ b/source/movies/index-1.md @@ -0,0 +1,4 @@ +--- +title: movies +date: 2024-05-02 15:44:26 +--- diff --git a/source/movies/index.md b/source/movies/index.md index 29e4ddb..365e6d0 100644 --- a/source/movies/index.md +++ b/source/movies/index.md @@ -1,5 +1,4 @@ --- -title: 影 视 +title: /movies date: 2024-05-02 15:31:37 -comments: false --- diff --git a/source/music/index.md b/source/music/index.md index 9aade63..210a5d2 100644 --- a/source/music/index.md +++ b/source/music/index.md @@ -1,5 +1,4 @@ --- -title: MUSIC!! +title: music date: 2024-05-02 15:44:18 -comments: false --- diff --git a/source/photos/index.md b/source/photos/index.md index 2fb3709..3712426 100644 --- a/source/photos/index.md +++ b/source/photos/index.md @@ -1,5 +1,4 @@ --- -title: 照 片 +title: photos date: 2024-05-02 15:31:25 -comments: false --- diff --git a/source/share/index.md b/source/share/index.md index 0853176..3683c0c 100644 --- a/source/share/index.md +++ b/source/share/index.md @@ -1,4 +1,4 @@ --- -title: 分享 +title: share date: 2024-05-02 15:31:19 --- diff --git a/source/tags/index.md b/source/tags/index.md index f5f8184..4b12c9e 100644 --- a/source/tags/index.md +++ b/source/tags/index.md @@ -1,7 +1,4 @@ --- -title: 🏷 标 签 -type: "tags" -top_img: https://cdn.jsdelivr.net/gh/zip95297/zip95297.github.io@main/source/images/profile/bg3.webp -date: 2025-04-30 06:28:03 -comments: false +title: tags +date: 2024-05-02 15:30:35 --- diff --git a/themes/butterfly/.github/FUNDING.yml b/themes/butterfly/.github/FUNDING.yml deleted file mode 100644 index 0cd9243..0000000 --- a/themes/butterfly/.github/FUNDING.yml +++ /dev/null @@ -1,13 +0,0 @@ -# These are supported funding model platforms - -github: # Replace with up to 4 GitHub Sponsors-enabled usernames e.g., [user1, user2] -patreon: # Replace with a single Patreon username -open_collective: # Replace with a single Open Collective username -ko_fi: # Replace with a single Ko-fi username -tidelift: # Replace with a single Tidelift platform-name/package-name e.g., npm/babel -community_bridge: # Replace with a single Community Bridge project-name e.g., cloud-foundry -liberapay: # Replace with a single Liberapay username -issuehunt: # Replace with a single IssueHunt username -otechie: # Replace with a single Otechie username -lfx_crowdfunding: # Replace with a single LFX Crowdfunding project-name e.g., cloud-foundry -custom: ['https://buy.stripe.com/3cs6rP6YA91sbbG5kk','https://jsd.012700.xyz/gh/jerryc127/CDN/Photo/wechat.jpg'] # Replace with up to 4 custom sponsorship URLs e.g., ['link1', 'link2'] diff --git a/themes/butterfly/.github/ISSUE_TEMPLATE/bug_report.yml b/themes/butterfly/.github/ISSUE_TEMPLATE/bug_report.yml deleted file mode 100644 index 48a7590..0000000 --- a/themes/butterfly/.github/ISSUE_TEMPLATE/bug_report.yml +++ /dev/null @@ -1,83 +0,0 @@ -name: Bug report -description: Create a report to help us improve -title: '[Bug]: ' - -body: - - type: markdown - attributes: - value: | - 重要:請依照該模板來提交 - Important: Please follow the template to create a new issue - - - type: input - id: butterfly-ver - attributes: - label: 使用的 Butterfly 版本? | What version of Butterfly are you using? - description: 檢視主題的 package.json | Check the theme's package.json - validations: - required: true - - - type: dropdown - id: modify - attributes: - label: 是否修改過主題文件? | Has the theme files been modified? - options: - - 是 (Yes) - - 否 (No) - validations: - required: true - - - type: dropdown - id: browser - attributes: - label: 使用的瀏覽器? | What browser are you using? - options: - - Chrome - - Edge - - Safari - - Opera - - Other - validations: - required: true - - - type: dropdown - id: platform - attributes: - label: 使用的系統? | What operating system are you using? - options: - - Windows - - macOS - - Linux - - Android - - iOS - - Other - validations: - required: true - - - type: textarea - id: dependencies - attributes: - label: 依賴插件 | Package dependencies information - description: 在 Hexo 根目錄下執行 `npm ls --depth 0` | Run `npm ls --depth 0` in Hexo root directory - render: Text - validations: - required: true - - - type: textarea - id: description - attributes: - label: 問題描述 | Describe the bug - description: 請描述你的問題現象 | A clear and concise description of what the bug is. - placeholder: 請儘量提供截圖來定位問題 | If applicable, add screenshots to help explain your problem - value: - validations: - required: true - - - type: input - id: website - attributes: - label: 出現問題的網站 | Website with the issue - description: 請提供可復現問題的網站地址 | Please provide a website URL where the problem can be reproduced. - placeholder: 請填寫具體的網址,不要填寫 localhost 網站 | Please provide a specific URL, do not use localhost. - validations: - required: true \ No newline at end of file diff --git a/themes/butterfly/.github/ISSUE_TEMPLATE/config.yml b/themes/butterfly/.github/ISSUE_TEMPLATE/config.yml deleted file mode 100644 index 74baf1a..0000000 --- a/themes/butterfly/.github/ISSUE_TEMPLATE/config.yml +++ /dev/null @@ -1,18 +0,0 @@ -blank_issues_enabled: false -contact_links: - - name: Questions about Butterfly - url: https://github.com/jerryc127/hexo-theme-butterfly/discussions - about: 一些使用問題請到 Discussion 詢問。 Please ask questions in Discussion. - - - name: Butterfly Q&A - url: https://butterfly.js.org/posts/98d20436/ - about: Butterfly Q&A - - - name: Telegram - url: https://t.me/bu2fly - about: 'Official Telegram Group' - - - name: QQ 群 - url: https://jq.qq.com/?_wv=1027&k=KU9105XR - about: '群號 1070540070' - diff --git a/themes/butterfly/.github/ISSUE_TEMPLATE/feature_request.yml b/themes/butterfly/.github/ISSUE_TEMPLATE/feature_request.yml deleted file mode 100644 index d8ea8f9..0000000 --- a/themes/butterfly/.github/ISSUE_TEMPLATE/feature_request.yml +++ /dev/null @@ -1,14 +0,0 @@ -name: Feature request -description: Suggest an idea for this project -title: '[Feature]: ' - -body: - - type: textarea - id: feature-request - attributes: - label: 想要的功能 | What feature do you want? - description: 請描述你需要的新功能 | A clear and concise description of what the feature is. - placeholder: - value: - validations: - require: true \ No newline at end of file diff --git a/themes/butterfly/.github/workflows/publish.yml b/themes/butterfly/.github/workflows/publish.yml deleted file mode 100644 index eee034d..0000000 --- a/themes/butterfly/.github/workflows/publish.yml +++ /dev/null @@ -1,19 +0,0 @@ -name: npm publish - -on: - release: - types: [created] -jobs: - build: - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v2 - # Setup .npmrc file to publish to npm - - uses: actions/setup-node@v1 - with: - node-version: '12.x' - registry-url: 'https://registry.npmjs.org' - - run: npm install - - run: npm publish - env: - NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }} \ No newline at end of file diff --git a/themes/butterfly/.github/workflows/stale.yml b/themes/butterfly/.github/workflows/stale.yml deleted file mode 100644 index b7c7ff5..0000000 --- a/themes/butterfly/.github/workflows/stale.yml +++ /dev/null @@ -1,19 +0,0 @@ -name: 'Close stale issues and PRs' -on: - schedule: - - cron: '30 1 * * *' - -jobs: - stale: - runs-on: ubuntu-latest - steps: - - uses: actions/stale@v5 - with: - days-before-issue-stale: 30 - days-before-pr-stale: -1 - days-before-close: 7 - stale-issue-message: 'This issue has been automatically marked as stale because it has not had recent activity. It will be closed if no further activity occurs. Thank you for your contributions.' - close-pr-message: 'This issue has not seen any activity since it was marked stale. Closing.' - stale-issue-label: 'Stale' - exempt-issue-labels: 'pinned,bug,enhancement,documentation,Plan' - operations-per-run: 1000 \ No newline at end of file diff --git a/themes/butterfly/.gitignore b/themes/butterfly/.gitignore deleted file mode 100644 index 65d2992..0000000 --- a/themes/butterfly/.gitignore +++ /dev/null @@ -1,2 +0,0 @@ -.DS_Store -.DS_Store diff --git a/themes/butterfly/LICENSE b/themes/butterfly/LICENSE deleted file mode 100644 index 9b5e401..0000000 --- a/themes/butterfly/LICENSE +++ /dev/null @@ -1,202 +0,0 @@ - - Apache License - Version 2.0, January 2004 - http://www.apache.org/licenses/ - - TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION - - 1. Definitions. - - "License" shall mean the terms and conditions for use, reproduction, - and distribution as defined by Sections 1 through 9 of this document. - - "Licensor" shall mean the copyright owner or entity authorized by - the copyright owner that is granting the License. - - "Legal Entity" shall mean the union of the acting entity and all - other entities that control, are controlled by, or are under common - control with that entity. For the purposes of this definition, - "control" means (i) the power, direct or indirect, to cause the - direction or management of such entity, whether by contract or - otherwise, or (ii) ownership of fifty percent (50%) or more of the - outstanding shares, or (iii) beneficial ownership of such entity. - - "You" (or "Your") shall mean an individual or Legal Entity - exercising permissions granted by this License. - - "Source" form shall mean the preferred form for making modifications, - including but not limited to software source code, documentation - source, and configuration files. - - "Object" form shall mean any form resulting from mechanical - transformation or translation of a Source form, including but - not limited to compiled object code, generated documentation, - and conversions to other media types. - - "Work" shall mean the work of authorship, whether in Source or - Object form, made available under the License, as indicated by a - copyright notice that is included in or attached to the work - (an example is provided in the Appendix below). - - "Derivative Works" shall mean any work, whether in Source or Object - form, that is based on (or derived from) the Work and for which the - editorial revisions, annotations, elaborations, or other modifications - represent, as a whole, an original work of authorship. For the purposes - of this License, Derivative Works shall not include works that remain - separable from, or merely link (or bind by name) to the interfaces of, - the Work and Derivative Works thereof. - - "Contribution" shall mean any work of authorship, including - the original version of the Work and any modifications or additions - to that Work or Derivative Works thereof, that is intentionally - submitted to Licensor for inclusion in the Work by the copyright owner - or by an individual or Legal Entity authorized to submit on behalf of - the copyright owner. For the purposes of this definition, "submitted" - means any form of electronic, verbal, or written communication sent - to the Licensor or its representatives, including but not limited to - communication on electronic mailing lists, source code control systems, - and issue tracking systems that are managed by, or on behalf of, the - Licensor for the purpose of discussing and improving the Work, but - excluding communication that is conspicuously marked or otherwise - designated in writing by the copyright owner as "Not a Contribution." - - "Contributor" shall mean Licensor and any individual or Legal Entity - on behalf of whom a Contribution has been received by Licensor and - subsequently incorporated within the Work. - - 2. Grant of Copyright License. Subject to the terms and conditions of - this License, each Contributor hereby grants to You a perpetual, - worldwide, non-exclusive, no-charge, royalty-free, irrevocable - copyright license to reproduce, prepare Derivative Works of, - publicly display, publicly perform, sublicense, and distribute the - Work and such Derivative Works in Source or Object form. - - 3. Grant of Patent License. Subject to the terms and conditions of - this License, each Contributor hereby grants to You a perpetual, - worldwide, non-exclusive, no-charge, royalty-free, irrevocable - (except as stated in this section) patent license to make, have made, - use, offer to sell, sell, import, and otherwise transfer the Work, - where such license applies only to those patent claims licensable - by such Contributor that are necessarily infringed by their - Contribution(s) alone or by combination of their Contribution(s) - with the Work to which such Contribution(s) was submitted. If You - institute patent litigation against any entity (including a - cross-claim or counterclaim in a lawsuit) alleging that the Work - or a Contribution incorporated within the Work constitutes direct - or contributory patent infringement, then any patent licenses - granted to You under this License for that Work shall terminate - as of the date such litigation is filed. - - 4. Redistribution. You may reproduce and distribute copies of the - Work or Derivative Works thereof in any medium, with or without - modifications, and in Source or Object form, provided that You - meet the following conditions: - - (a) You must give any other recipients of the Work or - Derivative Works a copy of this License; and - - (b) You must cause any modified files to carry prominent notices - stating that You changed the files; and - - (c) You must retain, in the Source form of any Derivative Works - that You distribute, all copyright, patent, trademark, and - attribution notices from the Source form of the Work, - excluding those notices that do not pertain to any part of - the Derivative Works; and - - (d) If the Work includes a "NOTICE" text file as part of its - distribution, then any Derivative Works that You distribute must - include a readable copy of the attribution notices contained - within such NOTICE file, excluding those notices that do not - pertain to any part of the Derivative Works, in at least one - of the following places: within a NOTICE text file distributed - as part of the Derivative Works; within the Source form or - documentation, if provided along with the Derivative Works; or, - within a display generated by the Derivative Works, if and - wherever such third-party notices normally appear. The contents - of the NOTICE file are for informational purposes only and - do not modify the License. You may add Your own attribution - notices within Derivative Works that You distribute, alongside - or as an addendum to the NOTICE text from the Work, provided - that such additional attribution notices cannot be construed - as modifying the License. - - You may add Your own copyright statement to Your modifications and - may provide additional or different license terms and conditions - for use, reproduction, or distribution of Your modifications, or - for any such Derivative Works as a whole, provided Your use, - reproduction, and distribution of the Work otherwise complies with - the conditions stated in this License. - - 5. Submission of Contributions. Unless You explicitly state otherwise, - any Contribution intentionally submitted for inclusion in the Work - by You to the Licensor shall be under the terms and conditions of - this License, without any additional terms or conditions. - Notwithstanding the above, nothing herein shall supersede or modify - the terms of any separate license agreement you may have executed - with Licensor regarding such Contributions. - - 6. Trademarks. This License does not grant permission to use the trade - names, trademarks, service marks, or product names of the Licensor, - except as required for reasonable and customary use in describing the - origin of the Work and reproducing the content of the NOTICE file. - - 7. Disclaimer of Warranty. Unless required by applicable law or - agreed to in writing, Licensor provides the Work (and each - Contributor provides its Contributions) on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or - implied, including, without limitation, any warranties or conditions - of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A - PARTICULAR PURPOSE. You are solely responsible for determining the - appropriateness of using or redistributing the Work and assume any - risks associated with Your exercise of permissions under this License. - - 8. Limitation of Liability. In no event and under no legal theory, - whether in tort (including negligence), contract, or otherwise, - unless required by applicable law (such as deliberate and grossly - negligent acts) or agreed to in writing, shall any Contributor be - liable to You for damages, including any direct, indirect, special, - incidental, or consequential damages of any character arising as a - result of this License or out of the use or inability to use the - Work (including but not limited to damages for loss of goodwill, - work stoppage, computer failure or malfunction, or any and all - other commercial damages or losses), even if such Contributor - has been advised of the possibility of such damages. - - 9. Accepting Warranty or Additional Liability. While redistributing - the Work or Derivative Works thereof, You may choose to offer, - and charge a fee for, acceptance of support, warranty, indemnity, - or other liability obligations and/or rights consistent with this - License. However, in accepting such obligations, You may act only - on Your own behalf and on Your sole responsibility, not on behalf - of any other Contributor, and only if You agree to indemnify, - defend, and hold each Contributor harmless for any liability - incurred by, or claims asserted against, such Contributor by reason - of your accepting any such warranty or additional liability. - - END OF TERMS AND CONDITIONS - - APPENDIX: How to apply the Apache License to your work. - - To apply the Apache License to your work, attach the following - boilerplate notice, with the fields enclosed by brackets "[]" - replaced with your own identifying information. (Don't include - the brackets!) The text should be enclosed in the appropriate - comment syntax for the file format. We also recommend that a - file or class name and description of purpose be included on the - same "printed page" as the copyright notice for easier - identification within third-party archives. - - Copyright [yyyy] [name of copyright owner] - - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. \ No newline at end of file diff --git a/themes/butterfly/README.md b/themes/butterfly/README.md deleted file mode 100644 index e451836..0000000 --- a/themes/butterfly/README.md +++ /dev/null @@ -1,117 +0,0 @@ -
-中文 -
- -
- - - -# hexo-theme-butterfly - -![master version](https://img.shields.io/github/package-json/v/jerryc127/hexo-theme-butterfly/master?color=%231ab1ad&label=master) -![master version](https://img.shields.io/github/package-json/v/jerryc127/hexo-theme-butterfly/dev?label=dev) -![https://img.shields.io/npm/v/hexo-theme-butterfly?color=%09%23bf00ff](https://img.shields.io/npm/v/hexo-theme-butterfly?color=%09%23bf00ff) -![hexo version](https://img.shields.io/badge/hexo-5.3.0+-0e83c) -![license](https://img.shields.io/github/license/jerryc127/hexo-theme-butterfly?color=FF5531) - -📢 Demo: [Butterfly](https://butterfly.js.org/) / [CrazyWong](https://blog.crazywong.com/) - -📖 Docs: [English](https://butterfly.js.org/en/posts/butterfly-docs-en-get-started/) / [Chinese](https://butterfly.js.org/posts/21cfbf15/) - -![](https://cdn.jsdelivr.net/gh/jerryc127/CDN@m2/img/theme-butterfly-readme.png) - -
- ---- - -## 💻 Installation - -### GIT - -> If you are in Mainland China, you can download in [Gitee](https://gitee.com/immyw/hexo-theme-butterfly.git) - -Stable branch [recommend]: - -``` -git clone -b master https://github.com/jerryc127/hexo-theme-butterfly.git themes/butterfly -``` - -Dev branch: - -``` -git clone -b dev https://github.com/jerryc127/hexo-theme-butterfly.git themes/butterfly -``` - -### NPM - -> It supports Hexo 5.0.0 or later - -In Hexo site root directory - -```powershell -npm i hexo-theme-butterfly -``` - -## ⚙ Configuration - - Set theme in the hexo work folder's root config file `_config.yml`: - -> theme: butterfly - - If you don't have pug & stylus renderer, try this: - -> npm install hexo-renderer-pug hexo-renderer-stylus - -## 🎉 Features - -- [x] Card UI Design -- [x] Rounded Design/Squared Design -- [X] Support sub-menu -- [x] Two-column layout -- [x] Responsive Web Design -- [x] Dark Mode -- [x] Pjax -- [x] Read Mode -- [x] Conversion between Traditional and Simplified Chinese -- [X] TOC catalog is available for both computers and mobile phones -- [X] Built-in Syntax Highlighting Themes (darker/pale night/light/ocean), also support customization -- [X] Code Blocks (Display code language/close or expand Code Blocks/Copy Button/word wrap) -- [X] Disable copy/Add a Copyright Notice to the Copied Text -- [X] Search (Algolia Search/Local Search) -- [x] Mathjax and Katex -- [x] Built-in 404 page -- [x] WordCount -- [x] Related articles -- [x] Displays outdated notice for a post -- [x] Share (Sharejs/Addtoany) -- [X] Comment (Disqus/Disqusjs/Livere/Gitalk/Valine/Waline/Utterances/Facebook Comments/Twikoo/Giscus/Remark42/artalk) -- [x] Multiple Comment System Support -- [x] Online Chats (Chatra/Tidio/Crisp) -- [x] Web analytics -- [x] Google AdSense -- [x] Webmaster Verification -- [x] Change website colour scheme -- [x] Typewriter Effect: activate_power_mode -- [x] Background effects (Canvas ribbon/canvas_ribbon_piao/canvas_nest) -- [x] Mouse click effects (Fireworks/Heart/Text) -- [x] Preloader/Loading Animation/pace.js -- [x] Busuanzi visitor counter -- [x] Medium Zoom/Fancybox -- [x] Mermaid -- [x] Chart.js -- [x] Justified Gallery -- [x] Lazyload images -- [x] Instantpage/Snackbar notification toast/PWA...... - -## ✨ Contributors - - - - - -## 📷 Screenshots - -![](https://cdn.jsdelivr.net/gh/jerryc127/CDN@m2/img/butterfly-readme-screenshots-1.jpg) -![](https://cdn.jsdelivr.net/gh/jerryc127/CDN@m2/img/butterfly-readme-screenshots-2.jpg) -![](https://cdn.jsdelivr.net/gh/jerryc127/CDN@m2/img/butterfly-readme-screenshots-3.jpg) -![](https://cdn.jsdelivr.net/gh/jerryc127/CDN@m2/img/butterfly-readme-screenshots-4.jpg) diff --git a/themes/butterfly/README_CN.md b/themes/butterfly/README_CN.md deleted file mode 100644 index 5a42e0e..0000000 --- a/themes/butterfly/README_CN.md +++ /dev/null @@ -1,117 +0,0 @@ -
- English -
- -
- - - -# hexo-theme-butterfly - -![master version](https://img.shields.io/github/package-json/v/jerryc127/hexo-theme-butterfly/master?color=%231ab1ad&label=master) -![master version](https://img.shields.io/github/package-json/v/jerryc127/hexo-theme-butterfly/dev?label=dev) -![https://img.shields.io/npm/v/hexo-theme-butterfly?color=%09%23bf00ff](https://img.shields.io/npm/v/hexo-theme-butterfly?color=%09%23bf00ff) -![hexo version](https://img.shields.io/badge/hexo-5.3.0+-0e83c) -![license](https://img.shields.io/github/license/jerryc127/hexo-theme-butterfly?color=FF5531) - -📢 預覽: [Butterfly](https://butterfly.js.org/) / [CrazyWong](https://blog.crazywong.com/) - -📖 文檔: [中文](https://butterfly.js.org/posts/21cfbf15/) / [English](https://butterfly.js.org/en/posts/butterfly-docs-en-get-started/) - -![](https://cdn.jsdelivr.net/gh/jerryc127/CDN@m2/img/theme-butterfly-readme.png) - -
- ---- - -## 💻 安裝 - -### Git 安裝 - -> 本倉庫同時上傳到 [Gitee](https://gitee.com/immyw/hexo-theme-butterfly.git),如果你訪問 Github 緩慢,可從 Gitee 中下載。 - -在博客根目錄裡安裝穩定版【推薦】 - -```powershell -git clone -b master https://github.com/jerryc127/hexo-theme-butterfly.git themes/butterfly -``` - -如果想要安裝比較新的dev分支,可以 - -```powershell -git clone -b dev https://github.com/jerryc127/hexo-theme-butterfly.git themes/butterfly -``` - -### npm 安裝 - -> 此方法只支持Hexo 5.0.0以上版本 - -在博客根目錄裡 - -```powershell -npm i hexo-theme-butterfly -``` - -## ⚙ 應用主題 - -修改hexo配置文件`_config.yml`,把主題改為`Butterfly` - -``` -theme: butterfly -``` - ->如果你沒有pug以及stylus的渲染器,請下載安裝: npm install hexo-renderer-pug hexo-renderer-stylus --save - -## 🎉 特色 - -- [x] 卡片化設計 -- [x] 圓角化設計/直角化設計 -- [X] 支持二級目錄 -- [x] 雙欄設計 -- [x] 響應式主題 -- [x] 夜間模式 -- [x] Pjax -- [x] 文章閲讀模式 -- [x] 簡體和繁體轉換 -- [X] 電腦和手機都可查看TOC目錄 -- [X] 內置多種代碼配色(darker/pale night/light/ocean),可自定義代碼配色 -- [X] 代碼塊顯示代碼語言/關閉或展開代碼塊/代碼複製/代碼自動換行 -- [X] 可關閉文字複製/可開啟內容複製增加版權信息) -- [X] 兩種搜索( Algolia 搜索和本地搜索) -- [x] Mathjax 和 Katex -- [x] 內置404頁面 -- [x] 顯示字數統計 -- [x] 顯示相關文章 -- [x] 過期文章提醒 -- [x] 多種分享系統(Sharejs/Addtoany) -- [X] 多種評論系統(Disqus/Disqusjs/Livere/Gitalk/Valine/Waline/Utterances/Facebook Comments/Twikoo/Giscus/Remark42/artalk) -- [x] 支持雙評論部署 -- [x] 多種在線聊天(Chatra/Tidio/Crisp) -- [x] 多種分析系統 -- [x] 谷歌廣告/手動廣告位置 -- [x] 各種站長驗證 -- [x] 修改網站配色 -- [x] 打字特效 activate_power_mode -- [x] 多種背景特效(靜止彩帶/動態彩帶/Canvas Nest) -- [x] 多種鼠標點擊特效(煙花/文字/愛心) -- [x] 內置一種 Preloader 加載動畫和 pace.js 加載動畫條 -- [x] 不蒜子訪問統計 -- [x] 兩種大圖模式(Medium Zoom/Fancybox) -- [x] Mermaid 圖表顯示 -- [x] Chart.js 圖表顯示 -- [x] 照片牆 -- [x] 圖片懶加載 -- [x] Instantpage/Snackbar彈窗/PWA...... - -## ✨ 貢獻者 - - - - - -## 📷 截圖 - -![](https://cdn.jsdelivr.net/gh/jerryc127/CDN@m2/img/butterfly-readme-screenshots-1.jpg) -![](https://cdn.jsdelivr.net/gh/jerryc127/CDN@m2/img/butterfly-readme-screenshots-2.jpg) -![](https://cdn.jsdelivr.net/gh/jerryc127/CDN@m2/img/butterfly-readme-screenshots-3.jpg) -![](https://cdn.jsdelivr.net/gh/jerryc127/CDN@m2/img/butterfly-readme-screenshots-4.jpg) diff --git a/themes/butterfly/_config.yml.bak b/themes/butterfly/_config.yml.bak deleted file mode 100644 index 0f70126..0000000 --- a/themes/butterfly/_config.yml.bak +++ /dev/null @@ -1,1106 +0,0 @@ -# -------------------------------------- -# Hexo Butterfly Theme Configuration -# If you have any questions, please refer to the documentation -# Chinese: https://butterfly.js.org/ -# English: https://butterfly.js.org/en/ -# -------------------------------------- - -# -------------------------------------- -# Navigation Settings -# -------------------------------------- - -nav: - # Navigation bar logo image - logo: - display_title: true - # Whether to fix navigation bar - fixed: false - -menu: - # Home: / || fas fa-home - # List||fas fa-list: - # Music: /music/ || fas fa-music - # Movie: /movies/ || fas fa-video - -# -------------------------------------- -# Code Blocks Settings -# -------------------------------------- - -code_blocks: - # Code block theme: darker / pale night / light / ocean / false - theme: light - macStyle: false - # Code block height limit (unit: px) - height_limit: false - word_wrap: false - - # Toolbar - copy: true - language: true - # true: shrink the code blocks | false: expand the code blocks | none: expand code blocks and hide the button - shrink: false - fullpage: false - -# Social media links -# Formal: -# icon: link || the description || color -social: - # fab fa-github: https://github.com/xxxxx || Github || '#24292e' - # fas fa-envelope: mailto:xxxxxx@gmail.com || Email || '#4a7dbe' - -# -------------------------------------- -# Image Settings -# -------------------------------------- - -favicon: /img/favicon.png - -avatar: - img: /img/butterfly-icon.png - effect: false - -# Disable all banner images -disable_top_img: false - -# If the banner of page not setting, it will show the default_top_img -default_top_img: - -# The banner image of index page -index_img: - -# The banner image of archive page -archive_img: - -# Note: tag page, not tags page -tag_img: - -# The banner image of tag page, you can set the banner image for each tag -# Format: -# - tag name: xxxxx -tag_per_img: - -# Note: category page, not categories page -category_img: - -# The banner image of category page, you can set the banner image for each category -# Format: -# - category name: xxxxx -category_per_img: - -# The background image of footer -footer_img: false - -# Website Background -# Can set it to color or image url -background: - -cover: - # Disable the cover or not - index_enable: true - aside_enable: true - archives_enable: true - # When cover is not set, the default cover is displayed - default_cover: - # - xxx.jpg - -# Replace Broken Images -error_img: - flink: /img/friend_404.gif - post_page: /img/404.jpg - -# A simple 404 page -error_404: - enable: false - subtitle: 'Page Not Found' - background: /img/error-page.png - -post_meta: - # Home Page - page: - # Choose: created / updated / both - date_type: created - # Choose: date / relative - date_format: date - categories: true - tags: false - label: true - post: - # Choose: left / center - position: left - # Choose: created / updated / both - date_type: both - # Choose: date / relative - date_format: date - categories: true - tags: true - label: true - -# -------------------------------------- -# Index page settings -# -------------------------------------- - -# The top_img settings of home page -# default: top img - full screen, site info - middle -# The position of site info, eg: 300px/300em/300rem/10% -index_site_info_top: -# The height of top_img, eg: 300px/300em/300rem -index_top_img_height: - -# The subtitle on homepage -subtitle: - enable: false - # Typewriter Effect - effect: true - # Customize typed.js - # https://github.com/mattboldt/typed.js/#customization - typed_option: - # Source - Call the third-party service API (Chinese only) - # It will show the source first, then show the content of sub - # Choose: false/1/2/3 - # false - disable the function - # 1 - hitokoto.cn - # 2 - https://api.aa1.cn/doc/yiyan.html - # 3 - jinrishici.com - source: false - # If you close the typewriter effect, the subtitle will only show the first line of sub - sub: - -# Article layout on the homepage -# 1: Cover on the left, info on the right -# 2: Cover on the right, info on the left -# 3: Cover and info alternate between left and right -# 4: Cover on top, info on the bottom -# 5: Info displayed on the cover -# 6: Masonry layout - Cover on top, info on the bottom -# 7: Masonry layout - Info displayed on the cover -index_layout: 3 - -# Display the article introduction on homepage -# 1: description -# 2: both (if the description exists, it will show description, or show the auto_excerpt) -# 3: auto_excerpt (default) -# false: do not show the article introduction -index_post_content: - method: 3 - # If you set method to 2 or 3, the length need to config - length: 500 - -# -------------------------------------- -# Post Settings -# -------------------------------------- - -toc: - post: true - page: false - number: true - expand: false - # Only for post - style_simple: false - scroll_percent: true - -post_copyright: - enable: true - decode: false - author_href: - license: CC BY-NC-SA 4.0 - license_url: https://creativecommons.org/licenses/by-nc-sa/4.0/ - -# Sponsor/reward -reward: - enable: false - text: - QR_code: - # - img: /img/wechat.jpg - # link: - # text: wechat - # - img: /img/alipay.jpg - # link: - # text: alipay - -# Post edit -# Easily browse and edit blog source code online. -post_edit: - enable: false - # url: https://github.com/user-name/repo-name/edit/branch-name/subdirectory-name/ - # For example: https://github.com/jerryc127/butterfly.js.org/edit/main/source/ - url: - -# Related Articles -related_post: - enable: true - # Number of posts displayed - limit: 6 - # Choose: created / updated - date_type: created - -# Choose: 1 / 2 / false -# 1: The 'next post' will link to old post -# 2: The 'next post' will link to new post -# false: disable pagination -post_pagination: 1 - -# Displays outdated notice for a post -noticeOutdate: - enable: false - # Style: simple / flat - style: flat - # When will it be shown - limit_day: 365 - # Position: top / bottom - position: top - message_prev: It has been - message_next: days since the last update, the content of the article may be outdated. - -# -------------------------------------- -# Footer Settings -# -------------------------------------- -footer: - owner: - enable: true - since: 2019 - custom_text: - # Copyright of theme and framework - copyright: true - -# -------------------------------------- -# Aside Settings -# -------------------------------------- - -aside: - enable: true - hide: false - # Show the button to hide the aside in bottom right button - button: true - mobile: true - # Position: left / right - position: right - display: - archive: true - tag: true - category: true - card_author: - enable: true - description: - button: - enable: true - icon: fab fa-github - text: Follow Me - link: https://github.com/xxxxxx - card_announcement: - enable: true - content: This is my Blog - card_recent_post: - enable: true - # If set 0 will show all - limit: 5 - # Sort: date / updated - sort: date - sort_order: - card_newest_comments: - enable: false - sort_order: - limit: 6 - # Unit: mins, save data to localStorage - storage: 10 - avatar: true - card_categories: - enable: true - # If set 0 will show all - limit: 8 - # Choose: none / true / false - expand: none - sort_order: - card_tags: - enable: true - # If set 0 will show all - limit: 40 - color: false - # Order of tags, random/name/length - orderby: random - # Sort of order. 1, asc for ascending; -1, desc for descending - order: 1 - sort_order: - card_archives: - enable: true - # Type: monthly / yearly - type: monthly - # Eg: YYYY年MM月 - format: MMMM YYYY - # Sort of order. 1, asc for ascending; -1, desc for descending - order: -1 - # If set 0 will show all - limit: 8 - sort_order: - card_post_series: - enable: true - # The title shows the series name - series_title: false - # Order by title or date - orderBy: 'date' - # Sort of order. 1, asc for ascending; -1, desc for descending - order: -1 - card_webinfo: - enable: true - post_count: true - last_push_date: true - sort_order: - # Time difference between publish date and now - # Formal: Month/Day/Year Time or Year/Month/Day Time - # Leave it empty if you don't enable this feature - runtime_date: - -# -------------------------------------- -# Bottom right button -# -------------------------------------- - -# The distance between the bottom right button and the bottom (default unit: px) -rightside_bottom: - -# Conversion between Traditional and Simplified Chinese -translate: - enable: false - # The text of a button - default: 繁 - # the language of website (1 - Traditional Chinese/ 2 - Simplified Chinese) - defaultEncoding: 2 - # Time delay - translateDelay: 0 - # The text of the button when the language is Simplified Chinese - msgToTraditionalChinese: '繁' - # The text of the button when the language is Traditional Chinese - msgToSimplifiedChinese: '簡' - -# Read Mode -readmode: true - -# Dark Mode -darkmode: - enable: true - # Toggle Button to switch dark/light mode - button: true - # Switch dark/light mode automatically - # autoChangeMode: 1 Following System Settings, if the system doesn't support dark mode, it will switch dark mode between 6 pm to 6 am - # autoChangeMode: 2 Switch dark mode between 6 pm to 6 am - # autoChangeMode: false - autoChangeMode: false - # Set the light mode time. The value is between 0 and 24. If not set, the default value is 6 and 18 - start: - end: - -# Show scroll percent in scroll-to-top button -rightside_scroll_percent: false - -# Don't modify the following settings unless you know how they work -# Choose: readmode,translate,darkmode,hideAside,toc,chat,comment -# Don't repeat the same value -rightside_item_order: - enable: false - # Default: readmode,translate,darkmode,hideAside - hide: - # Default: toc,chat,comment - show: - -# -------------------------------------- -# Global Settings -# -------------------------------------- - -anchor: - # When you scroll, the URL will update according to header id. - auto_update: false - # Click the headline to scroll and update the anchor - click_to_scroll: false - -photofigcaption: false - -copy: - enable: true - # Add the copyright information after copied content - copyright: - enable: false - limit_count: 150 - -# Need to install the hexo-wordcount plugin -wordcount: - enable: false - # Display the word count of the article in post meta - post_wordcount: true - # Display the time to read the article in post meta - min2read: true - # Display the total word count of the website in aside's webinfo - total_wordcount: true - -# Busuanzi count for PV / UV in site -busuanzi: - site_uv: true - site_pv: true - page_pv: true - -# -------------------------------------- -# Math -# -------------------------------------- - -# About the per_page -# if you set it to true, it will load mathjax/katex script in each page -# if you set it to false, it will load mathjax/katex script according to your setting (add the 'mathjax: true' or 'katex: true' in page's front-matter) -math: - # Choose: mathjax, katex - # Leave it empty if you don't need math - use: - per_page: true - hide_scrollbar: false - - mathjax: - # Enable the contextual menu - enableMenu: true - # Choose: all / ams / none, This controls whether equations are numbered and how - tags: none - - katex: - # Enable the copy KaTeX formula - copy_tex: false - -# -------------------------------------- -# Search -# -------------------------------------- - -search: - # Choose: algolia_search / local_search / docsearch - # leave it empty if you don't need search - use: - placeholder: - - # Algolia Search - algolia_search: - # Number of search results per page - hitsPerPage: 6 - - # Local Search - local_search: - # Preload the search data when the page loads. - preload: false - # Show top n results per article, show all results by setting to -1 - top_n_per_article: 1 - # Unescape html strings to the readable one. - unescape: false - CDN: - - # Docsearch - # https://docsearch.algolia.com/ - docsearch: - appId: - apiKey: - indexName: - option: - -# -------------------------------------- -# Share System -# -------------------------------------- - -share: - # Choose: sharejs / addtoany - # Leave it empty if you don't need share - use: sharejs - - # Share.js - # https://github.com/overtrue/share.js - sharejs: - sites: facebook,twitter,wechat,weibo,qq - - # AddToAny - # https://www.addtoany.com/ - addtoany: - item: facebook,twitter,wechat,sina_weibo,facebook_messenger,email,copy_link - -# -------------------------------------- -# Comments System -# -------------------------------------- - -comments: - # Up to two comments system, the first will be shown as default - # Leave it empty if you don't need comments - # Choose: Disqus/Disqusjs/Livere/Gitalk/Valine/Waline/Utterances/Facebook Comments/Twikoo/Giscus/Remark42/Artalk - # Format of two comments system : Disqus,Waline - use: - # Display the comment name next to the button - text: true - # Lazyload: The comment system will be load when comment element enters the browser's viewport. - # If you set it to true, the comment count will be invalid - lazyload: false - # Display comment count in post's top_img - count: false - # Display comment count in Home Page - card_post_count: false - -# Disqus -# https://disqus.com/ -disqus: - shortname: - # For newest comments widget - apikey: - -# Alternative Disqus - Render comments with Disqus API -# https://github.com/SukkaW/DisqusJS -disqusjs: - shortname: - apikey: - option: - -# Livere -# https://www.livere.com/ -livere: - uid: - -# Gitalk -# https://github.com/gitalk/gitalk -gitalk: - client_id: - client_secret: - repo: - owner: - admin: - option: - -# Valine -# https://valine.js.org -valine: - appId: - appKey: - avatar: monsterid - # This configuration is suitable for domestic custom domain name users, overseas version will be automatically detected (no need to manually fill in) - serverURLs: - bg: - # Use Valine visitor count as the page view count - visitor: false - option: - -# Waline - A simple comment system with backend support fork from Valine -# https://waline.js.org/ -waline: - serverURL: - bg: - # Use Waline pageview count as the page view count - pageview: false - option: - -# Utterances -# https://utteranc.es/ -utterances: - repo: - # Issue Mapping: pathname/url/title/og:title - issue_term: pathname - # Theme: github-light/github-dark/github-dark-orange/icy-dark/dark-blue/photon-dark - light_theme: github-light - dark_theme: photon-dark - js: - option: - -# Facebook Comments Plugin -# https://developers.facebook.com/docs/plugins/comments/ -facebook_comments: - app_id: - # optional - user_id: - pageSize: 10 - # Choose: social / time / reverse_time - order_by: social - lang: en_US - -# Twikoo -# https://github.com/imaegoo/twikoo -twikoo: - envId: - region: - # Use Twikoo visitor count as the page view count - visitor: false - option: - -# Giscus -# https://giscus.app/ -giscus: - repo: - repo_id: - category_id: - light_theme: light - dark_theme: dark - js: - option: - -# Remark42 -# https://remark42.com/docs/configuration/frontend/ -remark42: - host: - siteId: - option: - -# Artalk -# https://artalk.js.org/guide/frontend/config.html -artalk: - server: - site: - # Use Artalk visitor count as the page view count - visitor: false - option: - -# -------------------------------------- -# Chat Services -# -------------------------------------- - -chat: - # Choose: chatra/tidio/crisp - # Leave it empty if you don't need chat - use: - # Chat Button [recommend] - # It will create a button in the bottom right corner of website, and hide the origin button - rightside_button: false - # The origin chat button is displayed when scrolling up, and the button is hidden when scrolling down - button_hide_show: false - -# https://chatra.io/ -chatra: - id: - -# https://www.tidio.com/ -tidio: - public_key: - -# https://crisp.chat/en/ -crisp: - website_id: - -# -------------------------------------- -# Analysis -# -------------------------------------- - -# https://tongji.baidu.com/web/welcome/login -baidu_analytics: - -# https://analytics.google.com/analytics/web/ -google_analytics: - -# https://www.cloudflare.com/zh-tw/web-analytics/ -cloudflare_analytics: - -# https://clarity.microsoft.com/ -microsoft_clarity: - -# https://umami.is/ -umami_analytics: - enable: false - # For self-hosted setups, configure the hostname of the Umami instance - serverURL: - website_id: - option: - UV_PV: - site_uv: false - site_pv: false - page_pv: false - # Umami Cloud (API key) / self-hosted Umami (token) - token: - -# -------------------------------------- -# Advertisement -# -------------------------------------- - -# Google Adsense -google_adsense: - enable: false - auto_ads: true - js: https://pagead2.googlesyndication.com/pagead/js/adsbygoogle.js - client: - enable_page_level_ads: true - -# Insert ads manually -# Leave it empty if you don't need ads -ad: - # Insert ads in the index (every three posts) - index: - # Insert ads in aside - aside: - # Insert ads in the post (before pagination) - post: - -# -------------------------------------- -# Verification -# -------------------------------------- - -site_verification: - # - name: google-site-verification - # content: xxxxxx - # - name: baidu-site-verification - # content: xxxxxxx - -# -------------------------------------- -# Beautify / Effect -# -------------------------------------- - -# Theme color for customize -# Notice: color value must in double quotes like "#000" or may cause error! - -# theme_color: -# enable: true -# main: "#49B1F5" -# paginator: "#00c4b6" -# button_hover: "#FF7242" -# text_selection: "#00c4b6" -# link_color: "#99a9bf" -# meta_color: "#858585" -# hr_color: "#A4D8FA" -# code_foreground: "#F47466" -# code_background: "rgba(27, 31, 35, .05)" -# toc_color: "#00c4b6" -# blockquote_padding_color: "#49b1f5" -# blockquote_background_color: "#49b1f5" -# scrollbar_color: "#49b1f5" -# meta_theme_color_light: "ffffff" -# meta_theme_color_dark: "#0d0d0d" - -# The user interface setting of category and tag page -# Choose: index - same as Homepage UI / default - same as archives UI -# leave it empty or index -category_ui: -tag_ui: - -# Rounded corners for UI elements -rounded_corners_ui: true - -# Stretches the lines so that each line has equal width -text_align_justify: false - -# Add a mask to the header and footer -mask: - header: true - footer: true - -# Loading Animation -preloader: - enable: false - # source - # 1. fullpage-loading - # 2. pace (progress bar) - source: 1 - # pace theme (see https://codebyzach.github.io/pace/) - pace_css_url: - -# Page Transition -enter_transitions: true - -# Default display mode - light (default) / dark -display_mode: light - -# Configuration for beautifying the content of the article -beautify: - enable: false - # Specify the field to beautify (site or post) - field: post - # Specify the icon to be used as a prefix for the title, such as '\f0c1' - title_prefix_icon: - # Specify the color of the title prefix icon, such as '#F47466' - title_prefix_icon_color: - -# Global font settings -# Don't modify the following settings unless you know how they work -font: - global_font_size: - code_font_size: - font_family: - code_font_family: - -# Font settings for the site title and site subtitle -blog_title_font: - font_link: - font_family: - -# The setting of divider icon -hr_icon: - enable: true - # The unicode value of Font Awesome icon, such as '\3423' - icon: - icon_top: - -# Typewriter Effect -# https://github.com/disjukr/activate-power-mode -activate_power_mode: - enable: false - colorful: true - shake: true - mobile: false - -# Background effects -# -------------------------------------- - -# canvas_ribbon -# See: https://github.com/hustcc/ribbon.js -canvas_ribbon: - enable: false - # The size of ribbon - size: 150 - # The opacity of ribbon (0 ~ 1) - alpha: 0.6 - zIndex: -1 - click_to_change: false - mobile: false - -# Fluttering Ribbon -canvas_fluttering_ribbon: - enable: false - mobile: false - -# canvas_nest -# https://github.com/hustcc/canvas-nest.js -canvas_nest: - enable: false - # Color of lines, default: '0,0,0'; RGB values: (R,G,B).(note: use ',' to separate.) - color: '0,0,255' - # The opacity of line (0~1) - opacity: 0.7 - # The z-index property of the background - zIndex: -1 - # The number of lines - count: 99 - mobile: false - -# Mouse click effects: fireworks -fireworks: - enable: false - zIndex: 9999 - mobile: false - -# Mouse click effects: Heart symbol -click_heart: - enable: false - mobile: false - -# Mouse click effects: words -clickShowText: - enable: false - text: - # - I - # - LOVE - # - YOU - fontSize: 15px - random: false - mobile: false - -# -------------------------------------- -# Lightbox Settings -# -------------------------------------- - -# Choose: fancybox / medium_zoom -# https://github.com/francoischalifour/medium-zoom -# https://fancyapps.com/fancybox/ -# Leave it empty if you don't need lightbox -lightbox: - -# -------------------------------------- -# Tag Plugins settings -# -------------------------------------- - -# Series -series: - enable: false - # Order by title or date - orderBy: 'title' - # Sort of order. 1, asc for ascending; -1, desc for descending - order: 1 - number: true - -# ABCJS - The ABC Music Notation Plugin -# https://github.com/paulrosen/abcjs -abcjs: - enable: false - per_page: true - -# Mermaid -# https://github.com/mermaid-js/mermaid -mermaid: - enable: false - # Write Mermaid diagrams using code blocks - code_write: false - # built-in themes: default / forest / dark / neutral - theme: - light: default - dark: dark - -# chartjs -# see https://www.chartjs.org/docs/latest/ -chartjs: - enable: false - # Do not modify unless you understand how they work. - # The default settings are only used when the MD syntax is not specified. - # General font color for the chart - fontColor: - light: 'rgba(0, 0, 0, 0.8)' - dark: 'rgba(255, 255, 255, 0.8)' - # General border color for the chart - borderColor: - light: 'rgba(0, 0, 0, 0.1)' - dark: 'rgba(255, 255, 255, 0.2)' - # Background color for scale labels on radar and polar area charts - scale_ticks_backdropColor: - light: 'transparent' - dark: 'transparent' - -# Note - Bootstrap Callout -note: - # Note tag style values: - # - simple bs-callout old alert style. Default. - # - modern bs-callout new (v2-v3) alert style. - # - flat flat callout style with background, like on Mozilla or StackOverflow. - # - disabled disable all CSS styles import of note tag. - style: flat - icons: true - border_radius: 3 - # Offset lighter of background in % for modern and flat styles (modern: -12 | 12; flat: -18 | 6). - # Offset also applied to label tag variables. This option can work with disabled note tag. - light_bg_offset: 0 - -# -------------------------------------- -# Other Settings -# -------------------------------------- - -# https://github.com/MoOx/pjax -pjax: - enable: false - # Exclude the specified pages from pjax, such as '/music/' - exclude: - # - /xxxxxx/ - -# Inject the css and script (aplayer/meting) -aplayerInject: - enable: false - per_page: true - -# Snackbar - Toast Notification -# https://github.com/polonel/SnackBar -# position: top-left / top-center / top-right / bottom-left / bottom-center / bottom-right -snackbar: - enable: false - position: bottom-left - # The background color of Toast Notification in light mode and dark mode - bg_light: '#49b1f5' - bg_dark: '#1f1f1f' - -# Instant.page -# https://instant.page/ -instantpage: false - -# Lazyload -# https://github.com/verlok/vanilla-lazyload -lazyload: - enable: false - # Specify the field to use lazyload (site or post) - field: site - placeholder: - blur: false - -# PWA -# See https://github.com/JLHwung/hexo-offline -# --------------- -pwa: - enable: false - manifest: - apple_touch_icon: - favicon_32_32: - favicon_16_16: - mask_icon: - -# Open graph meta tags -# https://hexo.io/docs/helpers#open-graph -Open_Graph_meta: - enable: true - option: - # twitter_card: - # twitter_image: - # twitter_id: - # twitter_site: - # google_plus: - # fb_admins: - # fb_app_id: - -# Structured Data -# https://developers.google.com/search/docs/guides/intro-structured-data -structured_data: true - -# Add the vendor prefixes to ensure compatibility -css_prefix: true - -# Inject -# Insert the code to head (before '' tag) and the bottom (before '' tag) -inject: - head: - # - - bottom: - # - - -# CDN Settings -# Don't modify the following settings unless you know how they work -CDN: - # The CDN provider for internal and third-party scripts - # Options for both: local/jsdelivr/unpkg/cdnjs/custom - # Note: Dev version can only use 'local' for internal scripts - # Note: When setting third-party scripts to 'local', you need to install hexo-butterfly-extjs - internal_provider: local - third_party_provider: jsdelivr - - # Add version number to url, true or false - version: false - - # Custom format - # For example: https://cdn.staticfile.org/${cdnjs_name}/${version}/${min_cdnjs_file} - custom_format: - - option: - # abcjs_basic_js: - # activate_power_mode: - # algolia_js: - # algolia_search: - # aplayer_css: - # aplayer_js: - # artalk_css: - # artalk_js: - # blueimp_md5: - # busuanzi: - # canvas_fluttering_ribbon: - # canvas_nest: - # canvas_ribbon: - # chartjs: - # click_heart: - # clickShowText: - # disqusjs: - # disqusjs_css: - # docsearch_css: - # docsearch_js: - # egjs_infinitegrid: - # fancybox: - # fancybox_css: - # fireworks: - # fontawesome: - # gitalk: - # gitalk_css: - # giscus: - # instantpage: - # instantsearch: - # katex: - # katex_copytex: - # lazyload: - # local_search: - # main: - # main_css: - # mathjax: - # medium_zoom: - # mermaid: - # meting_js: - # prismjs_autoloader: - # prismjs_js: - # prismjs_lineNumber_js: - # pjax: - # sharejs: - # sharejs_css: - # snackbar: - # snackbar_css: - # translate: - # twikoo: - # typed: - # utils: - # valine: - # waline_css: - # waline_js: diff --git a/themes/butterfly/languages/default.yml b/themes/butterfly/languages/default.yml deleted file mode 100644 index 2fe206f..0000000 --- a/themes/butterfly/languages/default.yml +++ /dev/null @@ -1,121 +0,0 @@ -footer: - framework: Framework - theme: Theme - -copy: - success: Copy Successful - error: Copy Failed - noSupport: Browser Not Supported - -page: - articles: All Articles - tag: Tag - category: Category - archives: Archives - -card_post_count: comments - -no_title: Untitled - -post: - created: Created - updated: Updated - wordcount: Word Count - min2read: Reading Time - min2read_unit: mins - page_pv: Post Views - comments: Comments - copyright: - author: Author - link: Link - copyright_notice: Copyright Notice - copyright_content: 'All articles on this blog are licensed under %s unless otherwise stated.' - recommend: Related Articles - edit: Edit - -search: - title: Search - load_data: Loading Database - input_placeholder: Search for Posts - algolia_search: - hits_empty: 'No results found for: ${query}' - hits_stats: '${hits} results found in ${time} ms' - local_search: - hits_empty: 'No results found for: ${query}' - hits_stats: '${hits} articles found' - -pagination: - prev: Previous - next: Next - -comment: Comments - -aside: - articles: Articles - tags: Tags - categories: Categories - card_announcement: Announcement - card_categories: Categories - card_tags: Tags - card_archives: Archives - card_recent_post: Recent Posts - card_webinfo: - headline: Website Info - article_name: Article Count - runtime: - name: Runtime - unit: days - last_push_date: - name: Last Update - site_wordcount: Total Word Count - site_uv_name: Unique Visitors - site_pv_name: Page Views - more_button: View More - card_newest_comments: - headline: Latest Comments - loading_text: Loading... - error: Unable to retrieve comments, please check the configuration - zero: No comments - image: Image - link: Link - code: Code - card_toc: Contents - card_post_series: Post Series - -date_suffix: - just: Just now - min: minutes ago - hour: hours ago - day: days ago - month: months ago - -donate: Sponsor -share: Share - -rightside: - readmode_title: Reading Mode - translate_title: Toggle Between Traditional and Simplified Chinese - night_mode_title: Toggle Between Light and Dark Mode - back_to_top: Back to Top - toc: Table of Contents - scroll_to_comment: Scroll to Comments - setting: Settings - aside: Toggle Between Single-column and Double-column - chat: Chat - -copy_copyright: - author: Author - link: Link - source: Source - info: Copyright belongs to the author. For commercial use, please contact the author for authorization. For non-commercial use, please indicate the source. - -Snackbar: - chs_to_cht: You have switched to Traditional Chinese - cht_to_chs: You have switched to Simplified Chinese - day_to_night: You have switched to Dark Mode - night_to_day: You have switched to Light Mode - -loading: Loading... -load_more: Load More - -error404: Page Not Found diff --git a/themes/butterfly/languages/en.yml b/themes/butterfly/languages/en.yml deleted file mode 100644 index 2fe206f..0000000 --- a/themes/butterfly/languages/en.yml +++ /dev/null @@ -1,121 +0,0 @@ -footer: - framework: Framework - theme: Theme - -copy: - success: Copy Successful - error: Copy Failed - noSupport: Browser Not Supported - -page: - articles: All Articles - tag: Tag - category: Category - archives: Archives - -card_post_count: comments - -no_title: Untitled - -post: - created: Created - updated: Updated - wordcount: Word Count - min2read: Reading Time - min2read_unit: mins - page_pv: Post Views - comments: Comments - copyright: - author: Author - link: Link - copyright_notice: Copyright Notice - copyright_content: 'All articles on this blog are licensed under %s unless otherwise stated.' - recommend: Related Articles - edit: Edit - -search: - title: Search - load_data: Loading Database - input_placeholder: Search for Posts - algolia_search: - hits_empty: 'No results found for: ${query}' - hits_stats: '${hits} results found in ${time} ms' - local_search: - hits_empty: 'No results found for: ${query}' - hits_stats: '${hits} articles found' - -pagination: - prev: Previous - next: Next - -comment: Comments - -aside: - articles: Articles - tags: Tags - categories: Categories - card_announcement: Announcement - card_categories: Categories - card_tags: Tags - card_archives: Archives - card_recent_post: Recent Posts - card_webinfo: - headline: Website Info - article_name: Article Count - runtime: - name: Runtime - unit: days - last_push_date: - name: Last Update - site_wordcount: Total Word Count - site_uv_name: Unique Visitors - site_pv_name: Page Views - more_button: View More - card_newest_comments: - headline: Latest Comments - loading_text: Loading... - error: Unable to retrieve comments, please check the configuration - zero: No comments - image: Image - link: Link - code: Code - card_toc: Contents - card_post_series: Post Series - -date_suffix: - just: Just now - min: minutes ago - hour: hours ago - day: days ago - month: months ago - -donate: Sponsor -share: Share - -rightside: - readmode_title: Reading Mode - translate_title: Toggle Between Traditional and Simplified Chinese - night_mode_title: Toggle Between Light and Dark Mode - back_to_top: Back to Top - toc: Table of Contents - scroll_to_comment: Scroll to Comments - setting: Settings - aside: Toggle Between Single-column and Double-column - chat: Chat - -copy_copyright: - author: Author - link: Link - source: Source - info: Copyright belongs to the author. For commercial use, please contact the author for authorization. For non-commercial use, please indicate the source. - -Snackbar: - chs_to_cht: You have switched to Traditional Chinese - cht_to_chs: You have switched to Simplified Chinese - day_to_night: You have switched to Dark Mode - night_to_day: You have switched to Light Mode - -loading: Loading... -load_more: Load More - -error404: Page Not Found diff --git a/themes/butterfly/languages/ja.yml b/themes/butterfly/languages/ja.yml deleted file mode 100644 index f1ccbb7..0000000 --- a/themes/butterfly/languages/ja.yml +++ /dev/null @@ -1,121 +0,0 @@ -footer: - framework: フレームワーク - theme: テーマ - -copy: - success: コピー成功 - error: コピー失敗 - noSupport: ブラウザが対応していません - -page: - articles: 記事一覧 - tag: タグ - category: カテゴリ - archives: アーカイブ - -card_post_count: コメント数 - -no_title: タイトルなし - -post: - created: 作成日 - updated: 更新日 - wordcount: 総文字数 - min2read: 読む時間 - min2read_unit: 分 - page_pv: 閲覧数 - comments: コメント数 - copyright: - author: 著者 - link: リンク - copyright_notice: 著作権表示 - copyright_content: 'このブログのすべての記事は、%s ライセンスの下で提供されており、特に明記されていない限り、すべての権利を留保します。転載時には出典を明記してください: %s。' - recommend: 関連記事 - edit: 編集 - -search: - title: 検索 - load_data: データベースを読み込んでいます - input_placeholder: 記事を検索 - algolia_search: - hits_empty: '${query} の検索結果が見つかりませんでした。' - hits_stats: '${hits} 件の結果が ${time}ms で見つかりました' - local_search: - hits_empty: '${query} の検索結果が見つかりませんでした。' - hits_stats: '${hits} 件の記事が見つかりました' - -pagination: - prev: 前へ - next: 次へ - -comment: コメント - -aside: - articles: 記事 - tags: タグ - categories: カテゴリ - card_announcement: お知らせ - card_categories: カテゴリ - card_tags: タグ - card_archives: アーカイブ - card_recent_post: 最近の記事 - card_webinfo: - headline: サイト情報 - article_name: 記事数 - runtime: - name: 稼働時間 - unit: 日 - last_push_date: - name: 最終更新日 - site_wordcount: 総文字数 - site_uv_name: ユーザー数 - site_pv_name: ページビュー数 - more_button: もっと見る - card_newest_comments: - headline: 最新コメント - loading_text: ローディング中... - error: コメントを取得できませんでした。設定を確認してください。 - zero: コメントがありません - image: 画像 - link: リンク - code: コード - card_toc: 目次 - card_post_series: シリーズ記事 - -date_suffix: - just: たった今 - min: 分前 - hour: 時間前 - day: 日前 - month: ヶ月前 - -donate: 寄付 -share: 共有 - -rightside: - readmode_title: 読書モード - translate_title: 簡体字と繁体字の切り替え - night_mode_title: ライトモード/ダークモード切り替え - back_to_top: トップに戻る - toc: 目次 - scroll_to_comment: コメントへ移動 - setting: 設定 - aside: シングルカラムとダブルカラムの切り替え - chat: チャット - -copy_copyright: - author: 著者 - link: リンク - source: ソース - info: 著作権は著者に帰属します。商業的利用の場合は著者に連絡して許可を得てください。非商業的利用の場合は出典を明記してください。 - -Snackbar: - chs_to_cht: 繁体字に切り替えました - cht_to_chs: 簡体字に切り替えました - day_to_night: ダークモードに切り替えました - night_to_day: ライトモードに切り替えました - -loading: ローディング中... -load_more: もっと見る - -error404: ページが見つかりません diff --git a/themes/butterfly/languages/ko.yml b/themes/butterfly/languages/ko.yml deleted file mode 100644 index b94c365..0000000 --- a/themes/butterfly/languages/ko.yml +++ /dev/null @@ -1,121 +0,0 @@ -footer: - framework: 프레임워크 - theme: 테마 - -copy: - success: 복사 성공 - error: 복사 실패 - noSupport: 브라우저가 지원되지 않음 - -page: - articles: 모든 글 - tag: 태그 - category: 카테고리 - archives: 아카이브 - -card_post_count: 댓글 수 - -no_title: 제목 없음 - -post: - created: 작성일 - updated: 수정일 - wordcount: 총 글자 수 - min2read: 읽기 시간 - min2read_unit: 분 - page_pv: 조회수 - comments: 댓글 - copyright: - author: 작성자 - link: 링크 - copyright_notice: 저작권 고지 - copyright_content: '이 블로그의 모든 글은 %s 라이선스를 따르며, 별도로 명시되지 않는 한 모든 권리를 보유합니다. 재배포 시 출처를 명시해 주세요: %s.' - recommend: 관련 글 - edit: 편집 - -search: - title: 검색 - load_data: 데이터베이스 로드 중 - input_placeholder: 글 검색 - algolia_search: - hits_empty: '${query}에 대한 결과를 찾을 수 없습니다.' - hits_stats: '${hits}개의 결과를 ${time}ms 만에 찾음' - local_search: - hits_empty: '${query}에 대한 결과를 찾을 수 없습니다.' - hits_stats: '${hits}개의 글을 찾음' - -pagination: - prev: 이전 - next: 다음 - -comment: 댓글 - -aside: - articles: 글 - tags: 태그 - categories: 카테고리 - card_announcement: 공지 - card_categories: 카테고리 - card_tags: 태그 - card_archives: 아카이브 - card_recent_post: 최근 글 - card_webinfo: - headline: 사이트 정보 - article_name: 글 수 - runtime: - name: 운영 시간 - unit: 일 - last_push_date: - name: 마지막 업데이트 - site_wordcount: 총 글자 수 - site_uv_name: 방문자 수 - site_pv_name: 총 조회수 - more_button: 더 보기 - card_newest_comments: - headline: 최신 댓글 - loading_text: 로딩 중... - error: 댓글을 가져올 수 없습니다. 설정을 확인해 주세요. - zero: 댓글 없음 - image: 이미지 - link: 링크 - code: 코드 - card_toc: 목차 - card_post_series: 시리즈 글 - -date_suffix: - just: 방금 - min: 분 전 - hour: 시간 전 - day: 일 전 - month: 달 전 - -donate: 후원 -share: 공유 - -rightside: - readmode_title: 읽기 모드 - translate_title: 번체와 간체 전환 - night_mode_title: 라이트/다크 모드 전환 - back_to_top: 맨 위로 - toc: 목차 - scroll_to_comment: 댓글로 이동 - setting: 설정 - aside: 단일/이중 열 전환 - chat: 채팅 - -copy_copyright: - author: 작성자 - link: 링크 - source: 출처 - info: 저작권은 작성자에게 있습니다. 상업적 사용을 위해서는 작성자의 허가를 받아야 하며, 비상업적 사용 시에는 출처를 명시해 주세요. - -Snackbar: - chs_to_cht: 번체로 전환되었습니다. - cht_to_chs: 간체로 전환되었습니다. - day_to_night: 다크 모드로 전환되었습니다. - night_to_day: 라이트 모드로 전환되었습니다. - -loading: 로딩 중... -load_more: 더 보기 - -error404: 페이지를 찾을 수 없습니다. diff --git a/themes/butterfly/languages/zh-CN.yml b/themes/butterfly/languages/zh-CN.yml deleted file mode 100644 index 32ff836..0000000 --- a/themes/butterfly/languages/zh-CN.yml +++ /dev/null @@ -1,122 +0,0 @@ -footer: - framework: 框架 - theme: 主题 - -copy: - success: 复制成功 - error: 复制失败 - noSupport: 浏览器不支持 - -page: - articles: 全部文章 - tag: 标签 - category: 分类 - archives: 归档 - -card_post_count: 条评论 - -no_title: 无标题 - -post: - created: 发表于 - updated: 更新于 - wordcount: 总字数 - min2read: 阅读时长 - min2read_unit: 分钟 - page_pv: 浏览量 - comments: 评论数 - copyright: - author: 文章作者 - link: 文章链接 - copyright_notice: 版权声明 - copyright_content: '本博客所有文章除特别声明外,均采用 - %s 许可协议。转载请注明来源 %s!' - recommend: 相关推荐 - edit: 编辑 - -search: - title: 搜索 - load_data: 数据加载中 - input_placeholder: 搜索文章 - algolia_search: - hits_empty: '未找到符合您查询的内容:${query}' - hits_stats: '找到 ${hits} 条结果,耗时 ${time} 毫秒' - local_search: - hits_empty: '未找到符合您查询的内容:${query}' - hits_stats: '共找到 ${hits} 篇文章' - -pagination: - prev: 上一篇 - next: 下一篇 - -comment: 评论 - -aside: - articles: 文章 - tags: 标签 - categories: 分类 - card_announcement: 公告 - card_categories: 分类 - card_tags: 标签 - card_archives: 归档 - card_recent_post: 最新文章 - card_webinfo: - headline: 网站信息 - article_name: 文章数目 - runtime: - name: 运行时间 - unit: 天 - last_push_date: - name: 最后更新时间 - site_wordcount: 本站总字数 - site_uv_name: 本站访客数 - site_pv_name: 本站总浏览量 - more_button: 查看更多 - card_newest_comments: - headline: 最新评论 - loading_text: 加载中... - error: 无法获取评论,请确认相关配置是否正确 - zero: 暂无评论 - image: 图片 - link: 链接 - code: 代码 - card_toc: 目录 - card_post_series: 系列文章 - -date_suffix: - just: 刚刚 - min: 分钟前 - hour: 小时前 - day: 天前 - month: 个月前 - -donate: 赞助 -share: 分享 - -rightside: - readmode_title: 阅读模式 - translate_title: 简繁转换 - night_mode_title: 日间和夜间模式切换 - back_to_top: 回到顶部 - toc: 目录 - scroll_to_comment: 前往评论 - setting: 设置 - aside: 单栏和双栏切换 - chat: 聊天 - -copy_copyright: - author: 作者 - link: 链接 - source: 来源 - info: 著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。 - -Snackbar: - chs_to_cht: 已切换为繁体中文 - cht_to_chs: 已切换为简体中文 - day_to_night: 已切换为深色模式 - night_to_day: 已切换为浅色模式 - -loading: 加载中... -load_more: 加载更多 - -error404: 页面未找到 diff --git a/themes/butterfly/languages/zh-HK.yml b/themes/butterfly/languages/zh-HK.yml deleted file mode 100644 index 0ef4487..0000000 --- a/themes/butterfly/languages/zh-HK.yml +++ /dev/null @@ -1,121 +0,0 @@ -footer: - framework: 框架 - theme: 主題 - -copy: - success: 複製成功 - error: 複製失敗 - noSupport: 瀏覽器不支援 - -page: - articles: 全部文章 - tag: 標籤 - category: 分類 - archives: 歸檔 - -card_post_count: 條評論 - -no_title: 無標題 - -post: - created: 發表於 - updated: 更新於 - wordcount: 字數統計 - min2read: 閱讀時間 - min2read_unit: 分鐘 - page_pv: 瀏覽量 - comments: 評論數 - copyright: - author: 文章作者 - link: 文章連結 - copyright_notice: 版權聲明 - copyright_content: '除特別聲明外,本博客所有文章均採用%s 授權協議。轉載請註明出處:%s。' - recommend: 相關文章 - edit: 編輯 - -search: - title: 搜尋 - load_data: 正在加載數據庫 - input_placeholder: 搜尋文章 - algolia_search: - hits_empty: '未找到相關內容:${query}' - hits_stats: '找到 ${hits} 條結果,耗時 ${time} 毫秒' - local_search: - hits_empty: '未找到相關內容:${query}' - hits_stats: '找到 ${hits} 篇文章' - -pagination: - prev: 上一頁 - next: 下一頁 - -comment: 評論 - -aside: - articles: 文章 - tags: 標籤 - categories: 分類 - card_announcement: 公告 - card_categories: 分類 - card_tags: 標籤 - card_archives: 歸檔 - card_recent_post: 最新文章 - card_webinfo: - headline: 網站資訊 - article_name: 文章數目 - runtime: - name: 運行時間 - unit: 天 - last_push_date: - name: 最後更新時間 - site_wordcount: 總字數 - site_uv_name: 訪客數 - site_pv_name: 總瀏覽量 - more_button: 查看更多 - card_newest_comments: - headline: 最新評論 - loading_text: 正在加載... - error: 無法取得評論,請確認配置是否正確 - zero: 暫無評論 - image: 圖片 - link: 連結 - code: 代碼 - card_toc: 目錄 - card_post_series: 系列文章 - -date_suffix: - just: 剛剛 - min: 分鐘前 - hour: 小時前 - day: 天前 - month: 個月前 - -donate: 贊助 -share: 分享 - -rightside: - readmode_title: 閱讀模式 - translate_title: 簡繁轉換 - night_mode_title: 切換日夜模式 - back_to_top: 回到頂部 - toc: 目錄 - scroll_to_comment: 前往評論 - setting: 設定 - aside: 單欄與雙欄切換 - chat: 聊天 - -copy_copyright: - author: 作者 - link: 連結 - source: 來源 - info: 版權屬於作者所有。商業用途請聯絡作者獲得授權,非商業用途請註明出處。 - -Snackbar: - chs_to_cht: 已切換為繁體中文 - cht_to_chs: 已切換為簡體中文 - day_to_night: 已切換為深色模式 - night_to_day: 已切換為淺色模式 - -loading: 正在加載... -load_more: 加載更多 - -error404: 未找到頁面 diff --git a/themes/butterfly/languages/zh-TW.yml b/themes/butterfly/languages/zh-TW.yml deleted file mode 100644 index c9558ef..0000000 --- a/themes/butterfly/languages/zh-TW.yml +++ /dev/null @@ -1,121 +0,0 @@ -footer: - framework: 框架 - theme: 主題 - -copy: - success: 複製成功 - error: 複製失敗 - noSupport: 瀏覽器不支援 - -page: - articles: 所有文章 - tag: 標籤 - category: 分類 - archives: 歸檔 - -card_post_count: 則評論 - -no_title: 無標題 - -post: - created: 發表於 - updated: 更新於 - wordcount: 總字數 - min2read: 閱讀時間 - min2read_unit: 分鐘 - page_pv: 瀏覽量 - comments: 評論數 - copyright: - author: 文章作者 - link: 文章連結 - copyright_notice: 版權聲明 - copyright_content: '本部落格所有文章除特別聲明外,均採用%s 授權協議。轉載請註明來源 %s!' - recommend: 相關推薦 - edit: 編輯 - -search: - title: 搜尋 - load_data: 資料載入中 - input_placeholder: 搜尋文章 - algolia_search: - hits_empty: '找不到符合您查詢的內容:${query}' - hits_stats: '找到 ${hits} 筆結果,耗時 ${time} 毫秒' - local_search: - hits_empty: '找不到符合您查詢的內容:${query}' - hits_stats: '共找到 ${hits} 篇文章' - -pagination: - prev: 上一篇 - next: 下一篇 - -comment: 評論 - -aside: - articles: 文章 - tags: 標籤 - categories: 分類 - card_announcement: 公告 - card_categories: 分類 - card_tags: 標籤 - card_archives: 歸檔 - card_recent_post: 最新文章 - card_webinfo: - headline: 網站資訊 - article_name: 文章數量 - runtime: - name: 運行時間 - unit: 天 - last_push_date: - name: 最後更新時間 - site_wordcount: 總字數 - site_uv_name: 訪客數 - site_pv_name: 總瀏覽量 - more_button: 檢視更多 - card_newest_comments: - headline: 最新評論 - loading_text: 載入中... - error: 無法獲取評論,請確認相關配置是否正確 - zero: 尚無評論 - image: 圖片 - link: 連結 - code: 程式碼 - card_toc: 目錄 - card_post_series: 系列文章 - -date_suffix: - just: 剛剛 - min: 分鐘前 - hour: 小時前 - day: 天前 - month: 個月前 - -donate: 贊助 -share: 分享 - -rightside: - readmode_title: 閱讀模式 - translate_title: 繁簡轉換 - night_mode_title: 日夜模式切換 - back_to_top: 回到頂端 - toc: 目錄 - scroll_to_comment: 前往評論 - setting: 設定 - aside: 單欄和雙欄切換 - chat: 聊天 - -copy_copyright: - author: 作者 - link: 連結 - source: 來源 - info: 著作權歸作者所有。如需商業轉載,請聯絡作者獲得授權,非商業轉載請註明出處。 - -Snackbar: - chs_to_cht: 已切換為繁體中文 - cht_to_chs: 已切換為簡體中文 - day_to_night: 已切換為深色模式 - night_to_day: 已切換為淺色模式 - -loading: 載入中... -load_more: 載入更多 - -error404: 找不到頁面 diff --git a/themes/butterfly/layout/archive.pug b/themes/butterfly/layout/archive.pug deleted file mode 100644 index 31a1b50..0000000 --- a/themes/butterfly/layout/archive.pug +++ /dev/null @@ -1,8 +0,0 @@ -extends includes/layout.pug - -block content - include ./includes/mixins/article-sort.pug - #archive - .article-sort-title= `${_p('page.articles')} - ${getArchiveLength()}` - +articleSort(page.posts) - include includes/pagination.pug \ No newline at end of file diff --git a/themes/butterfly/layout/category.pug b/themes/butterfly/layout/category.pug deleted file mode 100644 index cdc0295..0000000 --- a/themes/butterfly/layout/category.pug +++ /dev/null @@ -1,12 +0,0 @@ -extends includes/layout.pug - -block content - if theme.category_ui == 'index' - include ./includes/mixins/indexPostUI.pug - +indexPostUI - else - include ./includes/mixins/article-sort.pug - #category - .article-sort-title= _p('page.category') + ' - ' + page.category - +articleSort(page.posts) - include includes/pagination.pug \ No newline at end of file diff --git a/themes/butterfly/layout/includes/additional-js.pug b/themes/butterfly/layout/includes/additional-js.pug deleted file mode 100644 index 5312260..0000000 --- a/themes/butterfly/layout/includes/additional-js.pug +++ /dev/null @@ -1,57 +0,0 @@ -div - script(src=url_for(theme.asset.utils)) - script(src=url_for(theme.asset.main)) - - if theme.translate.enable - script(src=url_for(theme.asset.translate)) - - if theme.lightbox - script(src=url_for(theme.asset[theme.lightbox])) - - if theme.instantpage - script(src=url_for(theme.asset.instantpage), type='module') - - if theme.lazyload.enable && !theme.lazyload.native - script(src=url_for(theme.asset.lazyload)) - - if theme.snackbar.enable - script(src=url_for(theme.asset.snackbar)) - - .js-pjax - if needLoadCountJs - != partial("includes/third-party/card-post-count/index", {}, { cache: true }) - - if loadSubJs - include ./third-party/subtitle.pug - - include ./third-party/math/index.pug - include ./third-party/abcjs/index.pug - - if commentsJsLoad - include ./third-party/comments/js.pug - - != partial("includes/third-party/prismjs", {}, { cache: true }) - - if theme.aside.enable && theme.aside.card_newest_comments.enable - if theme.pjax.enable || (globalPageType !== 'post' && page.aside !== false) - != partial("includes/third-party/newest-comments/index", {}, { cache: true }) - - != fragment_cache('injectBottom', function(){return injectHtml(theme.inject.bottom)}) - - != partial("includes/third-party/effect", {}, { cache: true }) - != partial("includes/third-party/chat/index", {}, { cache: true }) - - if theme.aplayerInject && theme.aplayerInject.enable - if theme.pjax.enable || theme.aplayerInject.per_page || page.aplayer - include ./third-party/aplayer.pug - - if theme.pjax.enable - != partial("includes/third-party/pjax", {}, { cache: true }) - - if theme.umami_analytics.enable - != partial("includes/third-party/umami_analytics", {}, { cache: true }) - - if theme.busuanzi.site_uv || theme.busuanzi.site_pv || theme.busuanzi.page_pv - script(async data-pjax src= theme.asset.busuanzi || '//busuanzi.ibruce.info/busuanzi/2.3/busuanzi.pure.mini.js') - - != partial('includes/third-party/search/index', {}, { cache: true }) \ No newline at end of file diff --git a/themes/butterfly/layout/includes/footer.pug b/themes/butterfly/layout/includes/footer.pug deleted file mode 100644 index 0f3dbe3..0000000 --- a/themes/butterfly/layout/includes/footer.pug +++ /dev/null @@ -1,19 +0,0 @@ -#footer-wrap - if theme.footer.owner.enable - - const currentYear = new Date().getFullYear() - - const sinceYear = theme.footer.owner.since - .copyright - if sinceYear && sinceYear != currentYear - != `©${sinceYear} - ${currentYear} By ${config.author}` - else - != `©${currentYear} By ${config.author}` - if theme.footer.copyright - - const v = getVersion() - .framework-info - span= _p('footer.framework') + ' ' - a(href='https://hexo.io')= `Hexo ${v.hexo}` - span.footer-separator | - span= _p('footer.theme') + ' ' - a(href='https://github.com/jerryc127/hexo-theme-butterfly')= `Butterfly ${v.theme}` - if theme.footer.custom_text - .footer_custom_text!= theme.footer.custom_text \ No newline at end of file diff --git a/themes/butterfly/layout/includes/head.pug b/themes/butterfly/layout/includes/head.pug deleted file mode 100644 index 447199b..0000000 --- a/themes/butterfly/layout/includes/head.pug +++ /dev/null @@ -1,77 +0,0 @@ -- var pageTitle -- globalPageType === 'archive' ? page.title = findArchivesTitle(page, theme.menu, date) : '' -case globalPageType - when 'tag' - - pageTitle = _p('page.tag') + ': ' + page.tag - when 'category' - - pageTitle = _p('page.category') + ': ' + page.category - when '404' - - pageTitle = _p('error404') - default - - pageTitle = page.title || config.title || '' - - -- var isSubtitle = config.subtitle ? ' - ' + config.subtitle : '' -- var tabTitle = globalPageType === 'home' || !pageTitle ? config.title + isSubtitle : pageTitle + ' | ' + config.title -- var pageAuthor = config.email ? config.author + ',' + config.email : config.author -- var pageCopyright = config.copyright || config.author -- var themeColorLight = theme.theme_color && theme.theme_color.enable && theme.theme_color.meta_theme_color_light || '#ffffff' -- var themeColorDark = theme.theme_color && theme.theme_color.enable && theme.theme_color.meta_theme_color_dark || '#0d0d0d' -- var themeColor = theme.display_mode === 'dark' ? themeColorDark : themeColorLight - -meta(charset='UTF-8') -meta(http-equiv="X-UA-Compatible" content="IE=edge") -meta(name="viewport" content="width=device-width, initial-scale=1.0,viewport-fit=cover") -title= tabTitle -meta(name="author" content=pageAuthor) -meta(name="copyright" content=pageCopyright) -meta(name ="format-detection" content="telephone=no") -meta(name="theme-color" content=themeColor) - -//- Open_Graph -include ./head/Open_Graph.pug - -//- Structured Data -include ./head/structured_data.pug - -!=favicon_tag(theme.favicon || config.favicon) -link(rel="canonical" href=urlNoIndex(null,config.pretty_urls.trailing_index,config.pretty_urls.trailing_html)) - -//- 預解析 -!=partial('includes/head/preconnect', {}, {cache: true}) - -//- 網站驗證 -!=partial('includes/head/site_verification', {}, {cache: true}) - -//- PWA -if (theme.pwa && theme.pwa.enable) - !=partial('includes/head/pwa', {}, {cache: true}) - -//- main css -link(rel='stylesheet', href=url_for(theme.asset.main_css)) -link(rel='stylesheet', href=url_for(theme.asset.fontawesome)) - -if (theme.snackbar && theme.snackbar.enable) - link(rel='stylesheet', href=url_for(theme.asset.snackbar_css) media="print" onload="this.media='all'") - -if theme.lightbox === 'fancybox' - link(rel='stylesheet' href=url_for(theme.asset.fancybox_css) media="print" onload="this.media='all'") - -!=fragment_cache('injectHeadJs', function(){return inject_head_js()}) - -//- google_adsense -!=partial('includes/head/google_adsense', {}, {cache: true}) - -//- analytics -!=partial('includes/head/analytics', {}, {cache: true}) - -//- font -if theme.blog_title_font && theme.blog_title_font.font_link - link(rel='stylesheet' href=url_for(theme.blog_title_font.font_link) media="print" onload="this.media='all'") - -//- global config -!=partial('includes/head/config', {}, {cache: true}) - -include ./head/config_site.pug - -!=fragment_cache('injectHead', function(){return injectHtml(theme.inject.head)}) diff --git a/themes/butterfly/layout/includes/head/Open_Graph.pug b/themes/butterfly/layout/includes/head/Open_Graph.pug deleted file mode 100644 index d353f3e..0000000 --- a/themes/butterfly/layout/includes/head/Open_Graph.pug +++ /dev/null @@ -1,16 +0,0 @@ -if theme.Open_Graph_meta.enable - - - const coverVal = page.cover_type === 'img' ? page.cover : theme.avatar.img - let ogOption = Object.assign({ - type: globalPageType === 'post' ? 'article' : 'website', - image: coverVal ? full_url_for(coverVal) : '', - fb_admins: theme.facebook_comments.user_id || '', - fb_app_id: theme.facebook_comments.app_id || '', - }, theme.Open_Graph_meta.option) - - - != open_graph(ogOption) -else - - const description = page.description || page.content || page.title || config.description - if description - meta(name="description" content=truncate(description, 150)) - diff --git a/themes/butterfly/layout/includes/head/analytics.pug b/themes/butterfly/layout/includes/head/analytics.pug deleted file mode 100644 index e6aa621..0000000 --- a/themes/butterfly/layout/includes/head/analytics.pug +++ /dev/null @@ -1,34 +0,0 @@ -if theme.baidu_analytics - script. - var _hmt = _hmt || []; - (function() { - var hm = document.createElement("script"); - hm.src = "https://hm.baidu.com/hm.js?!{theme.baidu_analytics}"; - var s = document.getElementsByTagName("script")[0]; - s.parentNode.insertBefore(hm, s); - })(); - btf.addGlobalFn('pjaxComplete', () => { - _hmt.push(['_trackPageview',window.location.pathname]) - }, 'baidu_analytics') - -if theme.google_analytics - script(async src=`https://www.googletagmanager.com/gtag/js?id=${theme.google_analytics}`) - script. - window.dataLayer = window.dataLayer || [] - function gtag(){dataLayer.push(arguments)} - gtag('js', new Date()) - gtag('config', '!{theme.google_analytics}') - btf.addGlobalFn('pjaxComplete', () => { - gtag('config', '!{theme.google_analytics}', {'page_path': window.location.pathname}) - }, 'google_analytics') - -if theme.cloudflare_analytics - script(defer data-pjax src='https://static.cloudflareinsights.com/beacon.min.js' data-cf-beacon=`{"token": "${theme.cloudflare_analytics}"}`) - -if theme.microsoft_clarity - script. - (function(c,l,a,r,i,t,y){ - c[a]=c[a]||function(){(c[a].q=c[a].q||[]).push(arguments)}; - t=l.createElement(r);t.async=1;t.src="https://www.clarity.ms/tag/"+i; - y=l.getElementsByTagName(r)[0];y.parentNode.insertBefore(t,y); - })(window, document, "clarity", "script", "!{theme.microsoft_clarity}"); \ No newline at end of file diff --git a/themes/butterfly/layout/includes/head/config.pug b/themes/butterfly/layout/includes/head/config.pug deleted file mode 100644 index b50ecfe..0000000 --- a/themes/butterfly/layout/includes/head/config.pug +++ /dev/null @@ -1,126 +0,0 @@ -- - let algolia = 'undefined' - if (theme.search.use === 'algolia_search') { - const { ALGOLIA_APP_ID, ALGOLIA_API_KEY, ALGOLIA_INDEX_NAME } = process.env - const { appId, applicationID, apiKey, indexName } = config.algolia - algolia = JSON.stringify({ - appId: ALGOLIA_APP_ID || appId || applicationID, - apiKey: ALGOLIA_API_KEY || apiKey, - indexName: ALGOLIA_INDEX_NAME || indexName, - hitsPerPage: theme.search.algolia_search.hitsPerPage, - // search languages - languages: { - input_placeholder: theme.search.placeholder || _p("search.input_placeholder"), - hits_empty: _p("search.algolia_search.hits_empty"), - hits_stats: _p("search.algolia_search.hits_stats"), - } - }) - } - - let localSearch = 'undefined' - if (theme.search.use === 'local_search') { - const { CDN, preload, top_n_per_article, unescape } = theme.search.local_search - localSearch = JSON.stringify({ - path: CDN || config.root + config.search.path, - preload, - top_n_per_article, - unescape, - languages: { - // search languages - hits_empty: _p("search.local_search.hits_empty"), - hits_stats: _p("search.local_search.hits_stats"), - } - }) - } - - let translate = 'undefined' - if (theme.translate && theme.translate.enable){ - translate = JSON.stringify({ - defaultEncoding: theme.translate.defaultEncoding, - translateDelay: theme.translate.translateDelay, - msgToTraditionalChinese: theme.translate.msgToTraditionalChinese, - msgToSimplifiedChinese: theme.translate.msgToSimplifiedChinese - }) - } - - let copyright = 'undefined' - if (theme.copy.enable && theme.copy.copyright.enable){ - copyright = JSON.stringify({ - limitCount: theme.copy.copyright.limit_count, - languages: { - author: _p("copy_copyright.author") + ': ' + config.author, - link: _p("copy_copyright.link") + ': ', - source: _p("copy_copyright.source") + ': ' + config.title, - info: _p("copy_copyright.info") - } - }) - } - - let Snackbar = 'undefined' - if (theme.snackbar && theme.snackbar.enable) { - Snackbar = JSON.stringify({ - chs_to_cht: _p("Snackbar.chs_to_cht"), - cht_to_chs: _p("Snackbar.cht_to_chs"), - day_to_night: _p("Snackbar.day_to_night"), - night_to_day: _p("Snackbar.night_to_day"), - bgLight: theme.snackbar.bg_light, - bgDark: theme.snackbar.bg_dark, - position: theme.snackbar.position, - }) - } - - let highlight = 'undefined' - let syntaxHighlighter = config.syntax_highlighter - let highlightEnable = syntaxHighlighter ? ['highlight.js', 'prismjs'].includes(syntaxHighlighter) : (config.highlight.enable || config.prismjs.enable) - if (highlightEnable) { - const { copy, language, height_limit, fullpage, macStyle } = theme.code_blocks - highlight = JSON.stringify({ - plugin: syntaxHighlighter ? syntaxHighlighter : config.highlight.enable ? 'highlight.js' : 'prismjs', - highlightCopy: copy, - highlightLang: language, - highlightHeightLimit: height_limit, - highlightFullpage: fullpage, - highlightMacStyle: macStyle - }) - } - -script. - const GLOBAL_CONFIG = { - root: '!{config.root}', - algolia: !{algolia}, - localSearch: !{localSearch}, - translate: !{translate}, - highlight: !{highlight}, - copy: { - success: '!{_p("copy.success")}', - error: '!{_p("copy.error")}', - noSupport: '!{_p("copy.noSupport")}' - }, - relativeDate: { - homepage: !{theme.post_meta.page.date_format === 'relative'}, - post: !{theme.post_meta.post.date_format === 'relative'} - }, - runtime: '!{theme.aside.card_webinfo.runtime_date ? _p("aside.card_webinfo.runtime.unit") : ""}', - dateSuffix: { - just: '!{_p("date_suffix.just")}', - min: '!{_p("date_suffix.min")}', - hour: '!{_p("date_suffix.hour")}', - day: '!{_p("date_suffix.day")}', - month: '!{_p("date_suffix.month")}' - }, - copyright: !{copyright}, - lightbox: '!{ theme.lightbox || 'null' }', - Snackbar: !{Snackbar}, - infinitegrid: { - js: '!{url_for(theme.asset.egjs_infinitegrid)}', - buttonText: '!{_p("load_more")}' - }, - isPhotoFigcaption: !{theme.photofigcaption}, - islazyloadPlugin: !{theme.lazyload.enable && !theme.lazyload.native}, - isAnchor: !{theme.anchor.auto_update || false}, - percent: { - toc: !{theme.toc.scroll_percent}, - rightside: !{theme.rightside_scroll_percent}, - }, - autoDarkmode: !{theme.darkmode.enable && theme.darkmode.autoChangeMode === 1} - } diff --git a/themes/butterfly/layout/includes/head/config_site.pug b/themes/butterfly/layout/includes/head/config_site.pug deleted file mode 100644 index 8f8d3b6..0000000 --- a/themes/butterfly/layout/includes/head/config_site.pug +++ /dev/null @@ -1,25 +0,0 @@ -- - const titleVal = pageTitle.replace(/'/ig,"\\'") - - let isHighlightShrink - if (theme.code_blocks.shrink == 'none') isHighlightShrink = 'undefined' - else if (typeof page.highlight_shrink == 'boolean') isHighlightShrink = page.highlight_shrink - else isHighlightShrink = theme.code_blocks.shrink - - var showToc = false - if (theme.aside.enable && page.aside !== false) { - let tocEnable = false - if (globalPageType === 'post' && theme.toc.post) tocEnable = true - else if (globalPageType === 'page' && theme.toc.page) tocEnable = true - const pageToc = typeof page.toc === 'boolean' ? page.toc : tocEnable - showToc = pageToc && (toc(page.content) !== '' || page.encrypt === true) - } -- - -script#config-diff. - var GLOBAL_CONFIG_SITE = { - title: '!{titleVal}', - isHighlightShrink: !{isHighlightShrink}, - isToc: !{showToc}, - pageType: '!{page.type == 'shuoshuo' ? 'shuoshuo' : globalPageType}' - } diff --git a/themes/butterfly/layout/includes/head/google_adsense.pug b/themes/butterfly/layout/includes/head/google_adsense.pug deleted file mode 100644 index f4dac01..0000000 --- a/themes/butterfly/layout/includes/head/google_adsense.pug +++ /dev/null @@ -1,9 +0,0 @@ -if (theme.google_adsense && theme.google_adsense.enable) - script(async src=theme.google_adsense.js) - - if theme.google_adsense.auto_ads - script. - (adsbygoogle = window.adsbygoogle || []).push({ - google_ad_client: '!{theme.google_adsense.client}', - enable_page_level_ads: '!{theme.google_adsense.enable_page_level_ads}' - }); \ No newline at end of file diff --git a/themes/butterfly/layout/includes/head/preconnect.pug b/themes/butterfly/layout/includes/head/preconnect.pug deleted file mode 100644 index 25bda87..0000000 --- a/themes/butterfly/layout/includes/head/preconnect.pug +++ /dev/null @@ -1,35 +0,0 @@ -- - const { internal_provider, third_party_provider, custom_format } = theme.CDN - const providers = { - 'jsdelivr': '//cdn.jsdelivr.net', - 'cdnjs': '//cdnjs.cloudflare.com', - 'unpkg': '//unpkg.com', - 'custom': custom_format && custom_format.match(/^((https?:)?(\/\/[^/]+)|([^/]+))(\/|$)/)[1] - } -- - -if internal_provider === third_party_provider && internal_provider !== 'local' - link(rel="preconnect" href=providers[internal_provider]) -else - if internal_provider !== 'local' - link(rel="preconnect" href=providers[internal_provider]) - if third_party_provider !== 'local' - link(rel="preconnect" href=providers[third_party_provider]) - -if theme.google_analytics - link(rel="preconnect" href="//www.google-analytics.com" crossorigin='') - -if theme.baidu_analytics - link(rel="preconnect" href="//hm.baidu.com") - -if theme.cloudflare_analytics - link(rel="preconnect" href="//static.cloudflareinsights.com") - -if theme.microsoft_clarity - link(rel="preconnect" href="//www.clarity.ms") - -if theme.blog_title_font && theme.blog_title_font.font_link && theme.blog_title_font.font_link.indexOf('//fonts.googleapis.com') != -1 - link(rel="preconnect" href="//fonts.googleapis.com" crossorigin='') - -if !theme.asset.busuanzi && (theme.busuanzi.site_uv || theme.busuanzi.site_pv || theme.busuanzi.page_pv) - link(rel="preconnect" href="//busuanzi.ibruce.info") \ No newline at end of file diff --git a/themes/butterfly/layout/includes/head/pwa.pug b/themes/butterfly/layout/includes/head/pwa.pug deleted file mode 100644 index 13703fb..0000000 --- a/themes/butterfly/layout/includes/head/pwa.pug +++ /dev/null @@ -1,13 +0,0 @@ -- const { manifest, theme_color, apple_touch_icon, favicon_32_32, favicon_16_16, mask_icon } = theme.pwa - -link(rel="manifest" href=url_for(manifest)) -if theme_color - meta(name="msapplication-TileColor" content=theme_color) -if apple_touch_icon - link(rel="apple-touch-icon" sizes="180x180" href=url_for(apple_touch_icon)) -if favicon_32_32 - link(rel="icon" type="image/png" sizes="32x32" href=url_for(favicon_32_32)) -if favicon_16_16 - link(rel="icon" type="image/png" sizes="16x16" href=url_for(favicon_16_16)) -if mask_icon - link(rel="mask-icon" href=url_for(mask_icon) color="#5bbad5") diff --git a/themes/butterfly/layout/includes/head/site_verification.pug b/themes/butterfly/layout/includes/head/site_verification.pug deleted file mode 100644 index 9130030..0000000 --- a/themes/butterfly/layout/includes/head/site_verification.pug +++ /dev/null @@ -1,3 +0,0 @@ -if theme.site_verification - each item in theme.site_verification - meta(name=item.name content=item.content) \ No newline at end of file diff --git a/themes/butterfly/layout/includes/head/structured_data.pug b/themes/butterfly/layout/includes/head/structured_data.pug deleted file mode 100644 index cacb02c..0000000 --- a/themes/butterfly/layout/includes/head/structured_data.pug +++ /dev/null @@ -1,34 +0,0 @@ -if theme.structured_data && page.layout === 'post' - - - // use json-ld to add structured data - - const title = page.title - const url = page.permalink - const imageVal = page.cover_type === 'img' ? page.cover : theme.avatar.img - const image = imageVal ? full_url_for(imageVal) : '' - const datePublished = page.date.toISOString() - const dateModified = (page.updated || page.date).toISOString() - const author = page.copyright_author || config.author - const authorHrefVal = page.copyright_author_href || theme.post_copyright.author_href || site.url; - const authorHref = full_url_for(authorHrefVal); - - const jsonLd = { - "@context": "https://schema.org", - "@type": "BlogPosting", - "headline": title, - "url": url, - "image": image, - "datePublished": datePublished, - "dateModified": dateModified, - "author": [{ - "@type": "Person", - "name": author, - "url": authorHref - }] - }; - - jsonLdScript = JSON.stringify(jsonLd, null, 2); - - - - script(type="application/ld+json"). - !{jsonLdScript} diff --git a/themes/butterfly/layout/includes/header/index.pug b/themes/butterfly/layout/includes/header/index.pug deleted file mode 100644 index fa39f5c..0000000 --- a/themes/butterfly/layout/includes/header/index.pug +++ /dev/null @@ -1,52 +0,0 @@ -- - const returnTopImg = img => img !== false ? img || theme.default_top_img : false - const isFixedClass = theme.nav.fixed ? ' fixed' : '' - var top_img = false - let headerClassName = 'not-top-img' - var bg_img = '' - -if !theme.disable_top_img && page.top_img !== false - case globalPageType - when 'post' - - top_img = page.top_img || page.cover || theme.default_top_img - when 'page' - - top_img = page.top_img || theme.default_top_img - when 'tag' - - top_img = theme.tag_per_img && theme.tag_per_img[page.tag] || returnTopImg(theme.tag_img) - when 'category' - - top_img = theme.category_per_img && theme.category_per_img[page.category] || returnTopImg(theme.category_img) - when 'home' - - top_img = returnTopImg(theme.index_img) - when 'archive' - - top_img = returnTopImg(theme.archive_img) - default - - top_img = page.top_img || theme.default_top_img - - if top_img !== false - - bg_img = getBgPath(top_img) - - headerClassName = globalPageType === 'home' ? 'full_page' : globalPageType === 'post' ? 'post-bg' : 'not-home-page' - -header#page-header(class=`${headerClassName + isFixedClass}` style=bg_img) - include ./nav.pug - if top_img !== false - if globalPageType === 'post' - include ./post-info.pug - else if globalPageType === 'home' - #site-info - h1#site-title=config.title - if theme.subtitle.enable - - var loadSubJs = true - #site-subtitle - span#subtitle - if theme.social - #site_social_icons - !=partial('includes/header/social', {}, {cache: true}) - #scroll-down - i.fas.fa-angle-down.scroll-down-effects - else - #page-site-info - h1#site-title=page.title || page.tag || page.category - else - //- improve seo - if globalPageType !== 'post' - h1.title-seo=page.title || page.tag || page.category || config.title \ No newline at end of file diff --git a/themes/butterfly/layout/includes/header/menu_item.pug b/themes/butterfly/layout/includes/header/menu_item.pug deleted file mode 100644 index 4b0590c..0000000 --- a/themes/butterfly/layout/includes/header/menu_item.pug +++ /dev/null @@ -1,27 +0,0 @@ -if theme.menu - .menus_items - each value, label in theme.menu - if typeof value !== 'object' - .menus_item - - const [link, icon] = value.split('||').map(part => trim(part)) - a.site-page(href=url_for(link)) - if icon - i.fa-fw(class=icon) - span= ' ' + label - else - .menus_item - - const [groupLabel, groupIcon, groupClass] = label.split('||').map(part => trim(part)) - - const hideClass = groupClass === 'hide' ? 'hide' : '' - span.site-page.group(class=hideClass) - if groupIcon - i.fa-fw(class=groupIcon) - span= ' ' + groupLabel - i.fas.fa-chevron-down - ul.menus_item_child - each val, lab in value - - const [childLink, childIcon] = val.split('||').map(part => trim(part)) - li - a.site-page.child(href=url_for(childLink)) - if childIcon - i.fa-fw(class=childIcon) - span= ' ' + lab \ No newline at end of file diff --git a/themes/butterfly/layout/includes/header/nav.pug b/themes/butterfly/layout/includes/header/nav.pug deleted file mode 100644 index 32fdf68..0000000 --- a/themes/butterfly/layout/includes/header/nav.pug +++ /dev/null @@ -1,22 +0,0 @@ -nav#nav - span#blog-info - a.nav-site-title(href=url_for('/')) - if theme.nav.logo - img.site-icon(src=url_for(theme.nav.logo) alt='Logo') - if theme.nav.display_title - span.site-name=config.title - if globalPageType === 'post' - a.nav-page-title(href=url_for('/')) - span.site-name=(page.title || config.title) - #menus - if theme.search.use - #search-button - span.site-page.social-icon.search - i.fas.fa-search.fa-fw - span= ' ' + _p('search.title') - if theme.menu - != partial('includes/header/menu_item', {}, {cache: true}) - - #toggle-menu - span.site-page - i.fas.fa-bars.fa-fw \ No newline at end of file diff --git a/themes/butterfly/layout/includes/header/post-info.pug b/themes/butterfly/layout/includes/header/post-info.pug deleted file mode 100644 index 2e5100c..0000000 --- a/themes/butterfly/layout/includes/header/post-info.pug +++ /dev/null @@ -1,149 +0,0 @@ -- let comments = theme.comments -#post-info - h1.post-title= page.title || _p('no_title') - if theme.post_edit.enable - a.post-edit-link(href=theme.post_edit.url + page.source title=_p('post.edit') target="_blank") - i.fas.fa-pencil-alt - - #post-meta - .meta-firstline - if theme.post_meta.post.date_type - span.post-meta-date - if theme.post_meta.post.date_type === 'both' - i.far.fa-calendar-alt.fa-fw.post-meta-icon - span.post-meta-label= _p('post.created') - time.post-meta-date-created(datetime=date_xml(page.date) title=_p('post.created') + ' ' + full_date(page.date))= date(page.date, config.date_format) - span.post-meta-separator | - i.fas.fa-history.fa-fw.post-meta-icon - span.post-meta-label= _p('post.updated') - time.post-meta-date-updated(datetime=date_xml(page.updated) title=_p('post.updated') + ' ' + full_date(page.updated))= date(page.updated, config.date_format) - else - - let data_type_update = theme.post_meta.post.date_type === 'updated' - - let date_type = data_type_update ? 'updated' : 'date' - - let date_icon = data_type_update ? 'fas fa-history' : 'far fa-calendar-alt' - - let date_title = data_type_update ? _p('post.updated') : _p('post.created') - i.fa-fw.post-meta-icon(class=date_icon) - span.post-meta-label= date_title - time(datetime=date_xml(page[date_type]) title=date_title + ' ' + full_date(page[date_type]))= date(page[date_type], config.date_format) - if theme.post_meta.post.categories && page.categories.data.length > 0 - span.post-meta-categories - if theme.post_meta.post.date_type - span.post-meta-separator | - each item, index in page.categories.data - i.fas.fa-inbox.fa-fw.post-meta-icon - a(href=url_for(item.path)).post-meta-categories #[=item.name] - if index < page.categories.data.length - 1 - i.fas.fa-angle-right.post-meta-separator - - .meta-secondline - - let postWordcount = theme.wordcount.enable && (theme.wordcount.post_wordcount || theme.wordcount.min2read) - if postWordcount - span.post-meta-separator | - span.post-meta-wordcount - if theme.wordcount.post_wordcount - i.far.fa-file-word.fa-fw.post-meta-icon - span.post-meta-label= _p('post.wordcount') + ':' - span.word-count= wordcount(page.content) - if theme.wordcount.min2read - span.post-meta-separator | - if theme.wordcount.min2read - i.far.fa-clock.fa-fw.post-meta-icon - span.post-meta-label= _p('post.min2read') + ':' - span= min2read(page.content, {cn: 350, en: 160}) + _p('post.min2read_unit') - - //- for pv and count - mixin pvBlock(parent_id, parent_class, parent_title) - span.post-meta-separator | - span(class=parent_class id=parent_id data-flag-title=parent_title) - i.far.fa-eye.fa-fw.post-meta-icon - span.post-meta-label= _p('post.page_pv') + ':' - if block - block - - mixin otherPV() - if theme.umami_analytics.enable && theme.umami_analytics.UV_PV.page_pv - +pvBlock('', '', '') - span#umamiPV(data-path=url_for(page.path)) - i.fa-solid.fa-spinner.fa-spin - else if theme.busuanzi.page_pv - +pvBlock('', 'post-meta-pv-cv', '') - span#busuanzi_value_page_pv - i.fa-solid.fa-spinner.fa-spin - - - const commentUse = comments.use && comments.use[0] - if page.comments !== false && commentUse && !comments.lazyload - if commentUse === 'Valine' && theme.valine.visitor - +pvBlock(url_for(page.path), 'leancloud_visitors', page.title) - span.leancloud-visitors-count - i.fa-solid.fa-spinner.fa-spin - else if commentUse === 'Waline' && theme.waline.pageview - +pvBlock('', '', '') - span.waline-pageview-count(data-path=url_for(page.path)) - i.fa-solid.fa-spinner.fa-spin - else if commentUse === 'Twikoo' && theme.twikoo.visitor - +pvBlock('', '', '') - span#twikoo_visitors - i.fa-solid.fa-spinner.fa-spin - else if commentUse === 'Artalk' && theme.artalk.visitor - +pvBlock('', '', '') - span#ArtalkPV - i.fa-solid.fa-spinner.fa-spin - else - +otherPV() - else - +otherPV() - - if comments.count && !comments.lazyload && page.comments !== false && comments.use - - var whichCount = comments.use[0] - - mixin countBlock - span.post-meta-separator | - span.post-meta-commentcount - i.far.fa-comments.fa-fw.post-meta-icon - span.post-meta-label= _p('post.comments') + ':' - if block - block - - case whichCount - when 'Disqus' - +countBlock - a.disqus-comment-count(href=full_url_for(page.path) + '#post-comment') - i.fa-solid.fa-spinner.fa-spin - when 'Disqusjs' - +countBlock - a.disqusjs-comment-count(href=full_url_for(page.path) + '#post-comment') - i.fa-solid.fa-spinner.fa-spin - when 'Valine' - +countBlock - a(href=url_for(page.path) + '#post-comment' itemprop="discussionUrl") - span.valine-comment-count(data-xid=url_for(page.path) itemprop="commentCount") - i.fa-solid.fa-spinner.fa-spin - when 'Waline' - +countBlock - a(href=url_for(page.path) + '#post-comment') - span.waline-comment-count(data-path=url_for(page.path)) - i.fa-solid.fa-spinner.fa-spin - when 'Gitalk' - +countBlock - a(href=url_for(page.path) + '#post-comment') - span.gitalk-comment-count - i.fa-solid.fa-spinner.fa-spin - when 'Twikoo' - +countBlock - a(href=url_for(page.path) + '#post-comment') - span#twikoo-count - i.fa-solid.fa-spinner.fa-spin - when 'Facebook Comments' - +countBlock - a(href=url_for(page.path) + '#post-comment') - span.fb-comments-count(data-href=urlNoIndex()) - when 'Remark42' - +countBlock - a(href=url_for(page.path) + '#post-comment') - span.remark42__counter(data-url=urlNoIndex()) - i.fa-solid.fa-spinner.fa-spin - when 'Artalk' - +countBlock - a(href=url_for(page.path) + '#post-comment') - span#ArtalkCount - i.fa-solid.fa-spinner.fa-spin \ No newline at end of file diff --git a/themes/butterfly/layout/includes/header/social.pug b/themes/butterfly/layout/includes/header/social.pug deleted file mode 100644 index cc816fa..0000000 --- a/themes/butterfly/layout/includes/header/social.pug +++ /dev/null @@ -1,8 +0,0 @@ -each url, icon in theme.social - - - const [link, title, color] = url.split('||').map(i => trim(i)) - const href = url_for(link) - const iconStyle = color ? `color: ${color.replace(/[\'\"]/g, '')};` : '' - const iconTitle = title || '' - a.social-icon(href=href target="_blank" title=iconTitle) - i(class=icon style=iconStyle) \ No newline at end of file diff --git a/themes/butterfly/layout/includes/layout.pug b/themes/butterfly/layout/includes/layout.pug deleted file mode 100644 index ef6a46b..0000000 --- a/themes/butterfly/layout/includes/layout.pug +++ /dev/null @@ -1,37 +0,0 @@ -- var globalPageType = getPageType(page, is_home) -- var htmlClassHideAside = theme.aside.enable && theme.aside.hide ? 'hide-aside' : '' -- page.aside = globalPageType === 'archive' ? theme.aside.display.archive: globalPageType === 'category' ? theme.aside.display.category : globalPageType === 'tag' ? theme.aside.display.tag : page.aside -- var hideAside = !theme.aside.enable || page.aside === false ? 'hide-aside' : '' -- var pageType = globalPageType === 'post' ? 'post' : 'page' -- pageType = page.type ? pageType + ' type-' + page.type : pageType - -doctype html -html(lang=config.language data-theme=theme.display_mode class=htmlClassHideAside) - head - include ./head.pug - body - !=partial('includes/loading/index', {}, {cache: true}) - - if theme.background - #web_bg(style=getBgPath(theme.background)) - - !=partial('includes/sidebar', {}, {cache: true}) - - #body-wrap(class=pageType) - include ./header/index.pug - - main#content-inner.layout(class=hideAside) - if body - div!= body - else - block content - if theme.aside.enable && page.aside !== false - include widget/index.pug - - - const footerBg = theme.footer_img - - const footer_bg = footerBg ? footerBg === true ? bg_img : getBgPath(footerBg) : '' - footer#footer(style=footer_bg) - !=partial('includes/footer', {}, {cache: true}) - - include ./rightside.pug - include ./additional-js.pug \ No newline at end of file diff --git a/themes/butterfly/layout/includes/loading/fullpage-loading.pug b/themes/butterfly/layout/includes/loading/fullpage-loading.pug deleted file mode 100644 index a3d0098..0000000 --- a/themes/butterfly/layout/includes/loading/fullpage-loading.pug +++ /dev/null @@ -1,33 +0,0 @@ -#loading-box - .loading-left-bg - .loading-right-bg - .spinner-box - .configure-border-1 - .configure-core - .configure-border-2 - .configure-core - .loading-word= _p('loading') - -script. - (()=>{ - const $loadingBox = document.getElementById('loading-box') - const $body = document.body - const preloader = { - endLoading: () => { - $body.style.overflow = '' - $loadingBox.classList.add('loaded') - }, - initLoading: () => { - $body.style.overflow = 'hidden' - $loadingBox.classList.remove('loaded') - } - } - - preloader.initLoading() - window.addEventListener('load', preloader.endLoading) - - if (!{theme.pjax && theme.pjax.enable}) { - btf.addGlobalFn('pjaxSend', preloader.initLoading, 'preloader_init') - btf.addGlobalFn('pjaxComplete', preloader.endLoading, 'preloader_end') - } - })() \ No newline at end of file diff --git a/themes/butterfly/layout/includes/loading/index.pug b/themes/butterfly/layout/includes/loading/index.pug deleted file mode 100644 index b058b58..0000000 --- a/themes/butterfly/layout/includes/loading/index.pug +++ /dev/null @@ -1,5 +0,0 @@ -if theme.preloader.enable - if theme.preloader.source === 1 - include ./fullpage-loading.pug - else - include ./pace.pug \ No newline at end of file diff --git a/themes/butterfly/layout/includes/loading/pace.pug b/themes/butterfly/layout/includes/loading/pace.pug deleted file mode 100644 index ac04c9b..0000000 --- a/themes/butterfly/layout/includes/loading/pace.pug +++ /dev/null @@ -1,12 +0,0 @@ -script. - window.paceOptions = { - restartOnPushState: false - } - - btf.addGlobalFn('pjaxSend', () => { - Pace.restart() - }, 'pace_restart') - - -link(rel="stylesheet", href=url_for(theme.preloader.pace_css_url || theme.asset.pace_default_css)) -script(src=url_for(theme.asset.pace_js)) \ No newline at end of file diff --git a/themes/butterfly/layout/includes/mixins/article-sort.pug b/themes/butterfly/layout/includes/mixins/article-sort.pug deleted file mode 100644 index 79f87da..0000000 --- a/themes/butterfly/layout/includes/mixins/article-sort.pug +++ /dev/null @@ -1,23 +0,0 @@ -mixin articleSort(posts) - .article-sort - - let year - - posts.forEach(article => { - - const tempYear = date(article.date, 'YYYY') - - const noCoverClass = article.cover === false || !theme.cover.archives_enable ? 'no-article-cover' : '' - - const title = article.title || _p('no_title') - if tempYear !== year - - year = tempYear - .article-sort-item.year= year - .article-sort-item(class=noCoverClass) - if article.cover && theme.cover.archives_enable - a.article-sort-item-img(href=url_for(article.path) title=title) - if article.cover_type === 'img' - img(src=url_for(article.cover) alt=title onerror=`this.onerror=null;this.src='${url_for(theme.error_img.post_page)}'`) - else - div(style=`background: ${article.cover}`) - .article-sort-item-info - .article-sort-item-time - i.far.fa-calendar-alt - time.post-meta-date-created(datetime=date_xml(article.date) title=_p('post.created') + ' ' + full_date(article.date))= date(article.date, config.date_format) - a.article-sort-item-title(href=url_for(article.path) title=title)= title - - }) \ No newline at end of file diff --git a/themes/butterfly/layout/includes/mixins/indexPostUI.pug b/themes/butterfly/layout/includes/mixins/indexPostUI.pug deleted file mode 100644 index 874ad5e..0000000 --- a/themes/butterfly/layout/includes/mixins/indexPostUI.pug +++ /dev/null @@ -1,116 +0,0 @@ -mixin indexPostUI() - - const indexLayout = theme.index_layout - - const masonryLayoutClass = (indexLayout === 6 || indexLayout === 7) ? 'masonry' : '' - #recent-posts.recent-posts.nc(class=masonryLayoutClass) - .recent-post-items - each article, index in page.posts.data - .recent-post-item - - const link = article.link || article.path - - const title = article.title || _p('no_title') - - const leftOrRight = indexLayout === 3 ? (index % 2 === 0 ? 'left' : 'right') : (indexLayout === 2 ? 'right' : '') - - const post_cover = article.cover - - const no_cover = article.cover === false || !theme.cover.index_enable ? 'no-cover' : '' - - if post_cover && theme.cover.index_enable - .post_cover(class=leftOrRight) - a(href=url_for(link) title=title) - if article.cover_type === 'img' - img.post-bg(src=url_for(post_cover) onerror=`this.onerror=null;this.src='${url_for(theme.error_img.post_page)}'` alt=title) - else - div.post-bg(style=`background: ${post_cover}`) - .recent-post-info(class=no_cover) - a.article-title(href=url_for(link) title=title) - if globalPageType === 'home' && (article.top || article.sticky > 0) - i.fas.fa-thumbtack.sticky - = title - .article-meta-wrap - if theme.post_meta.page.date_type - span.post-meta-date - if theme.post_meta.page.date_type === 'both' - i.far.fa-calendar-alt - span.article-meta-label=_p('post.created') - time.post-meta-date-created(datetime=date_xml(article.date) title=_p('post.created') + ' ' + full_date(article.date))= date(article.date, config.date_format) - span.article-meta-separator | - i.fas.fa-history - span.article-meta-label=_p('post.updated') - time.post-meta-date-updated(datetime=date_xml(article.updated) title=_p('post.updated') + ' ' + full_date(article.updated))= date(article.updated, config.date_format) - else - - const data_type_updated = theme.post_meta.page.date_type === 'updated' - - const date_type = data_type_updated ? 'updated' : 'date' - - const date_icon = data_type_updated ? 'fas fa-history' : 'far fa-calendar-alt' - - const date_title = data_type_updated ? _p('post.updated') : _p('post.created') - i(class=date_icon) - span.article-meta-label= date_title - time(datetime=date_xml(article[date_type]) title=date_title + ' ' + full_date(article[date_type]))= date(article[date_type], config.date_format) - if theme.post_meta.page.categories && article.categories.data.length > 0 - span.article-meta - span.article-meta-separator | - each item, index in article.categories.data - i.fas.fa-inbox - a(href=url_for(item.path)).article-meta__categories #[=item.name] - if index < article.categories.data.length - 1 - i.fas.fa-angle-right.article-meta-link - if theme.post_meta.page.tags && article.tags.length > 0 - span.article-meta.tags - span.article-meta-separator | - each item, index in article.tags.data - i.fas.fa-tag - a(href=url_for(item.path)).article-meta__tags #[=item.name] - if index < article.tags.data.length - 1 - span.article-meta-link #[='•'] - - mixin countBlockInIndex - - needLoadCountJs = true - span.article-meta - span.article-meta-separator | - i.fas.fa-comments - if block - block - span.article-meta-label= ' ' + _p('card_post_count') - - if theme.comments.card_post_count && theme.comments.use - case theme.comments.use[0] - when 'Disqus' - when 'Disqusjs' - +countBlockInIndex - a.disqus-count(href=full_url_for(link) + '#post-comment') - i.fa-solid.fa-spinner.fa-spin - when 'Valine' - +countBlockInIndex - a(href=url_for(link) + '#post-comment') - span.valine-comment-count(data-xid=url_for(link)) - i.fa-solid.fa-spinner.fa-spin - when 'Waline' - +countBlockInIndex - a(href=url_for(link) + '#post-comment') - span.waline-comment-count(data-path=url_for(link)) - i.fa-solid.fa-spinner.fa-spin - when 'Twikoo' - +countBlockInIndex - a.twikoo-count(href=url_for(link) + '#post-comment') - i.fa-solid.fa-spinner.fa-spin - when 'Facebook Comments' - +countBlockInIndex - a(href=url_for(link) + '#post-comment') - span.fb-comments-count(data-href=urlNoIndex(article.permalink)) - when 'Remark42' - +countBlockInIndex - a(href=url_for(link) + '#post-comment') - span.remark42__counter(data-url=urlNoIndex(article.permalink)) - i.fa-solid.fa-spinner.fa-spin - when 'Artalk' - +countBlockInIndex - a(href=url_for(link) + '#post-comment') - span.artalk-count(data-page-key=url_for(link)) - i.fa-solid.fa-spinner.fa-spin - - //- Display the article introduction on homepage - - const content = postDesc(article) - if content - .content!=content - - if theme.ad && theme.ad.index - if (index + 1) % 3 === 0 - .recent-post-item.ads-wrap!= theme.ad.index - - include ../pagination.pug \ No newline at end of file diff --git a/themes/butterfly/layout/includes/page/404.pug b/themes/butterfly/layout/includes/page/404.pug deleted file mode 100644 index b08b5ad..0000000 --- a/themes/butterfly/layout/includes/page/404.pug +++ /dev/null @@ -1,8 +0,0 @@ -- var top_img_404 = theme.error_404.background || theme.default_top_img - -.error-content - .error-img - img(src=url_for(top_img_404) alt='Page not found') - .error-info - h1.error_title= '404' - .error_subtitle= theme.error_404.subtitle || _p('error404') diff --git a/themes/butterfly/layout/includes/page/categories.pug b/themes/butterfly/layout/includes/page/categories.pug deleted file mode 100644 index 79153c8..0000000 --- a/themes/butterfly/layout/includes/page/categories.pug +++ /dev/null @@ -1 +0,0 @@ -.category-lists!= list_categories() \ No newline at end of file diff --git a/themes/butterfly/layout/includes/page/default-page.pug b/themes/butterfly/layout/includes/page/default-page.pug deleted file mode 100644 index 38f651a..0000000 --- a/themes/butterfly/layout/includes/page/default-page.pug +++ /dev/null @@ -1,2 +0,0 @@ -#article-container.container - != page.content \ No newline at end of file diff --git a/themes/butterfly/layout/includes/page/flink.pug b/themes/butterfly/layout/includes/page/flink.pug deleted file mode 100644 index a4b6e27..0000000 --- a/themes/butterfly/layout/includes/page/flink.pug +++ /dev/null @@ -1,82 +0,0 @@ -#article-container.container - .flink - - let { content, random, flink_url } = page - - let pageContent = content - - if flink_url || random - - const linkData = flink_url ? false : site.data.link || false - script. - (()=>{ - const replaceSymbol = (str) => { - return str.replace(/[\p{P}\p{S}]/gu, "-") - } - - let result = "" - const add = (str) => { - for(let i = 0; i < str.length; i++){ - const replaceClassName = replaceSymbol(str[i].class_name) - const className = str[i].class_name ? `

${str[i].class_name}

` : "" - const classDesc = str[i].class_desc ? `` : "" - - let listResult = "" - const lists = str[i].link_list - if (!{random === true}) { - lists.sort(() => Math.random() - 0.5) - } - for(let j = 0; j < lists.length; j++){ - listResult += ` - ` - } - - result += `${className}${classDesc} ` - } - - document.querySelector(".flink").insertAdjacentHTML("afterbegin", result) - window.lazyLoadInstance && window.lazyLoadInstance.update() - } - - const linkData = !{JSON.stringify(linkData)} - if (!{Boolean(flink_url)}) { - fetch("!{url_for(flink_url)}") - .then(response => response.json()) - .then(add) - } else if (linkData) { - add(linkData) - } - })() - - else - if site.data.link - - let result = "" - each i in site.data.link - - let className = i.class_name ? markdown(`## ${i.class_name}`) : "" - - let classDesc = i.class_desc ? `` : "" - - - let listResult = "" - - each j in i.link_list - - - listResult += ` - ` - - - - - result += `${className}${classDesc} ` - - - pageContent = result + pageContent - != pageContent diff --git a/themes/butterfly/layout/includes/page/shuoshuo.pug b/themes/butterfly/layout/includes/page/shuoshuo.pug deleted file mode 100644 index c107d5c..0000000 --- a/themes/butterfly/layout/includes/page/shuoshuo.pug +++ /dev/null @@ -1,188 +0,0 @@ -//- - author: -//- avatar: -//- date: -//- content: -//- tags: -//- - tag1 -//- - tag2 - -- page.toc = false - -#article-container - if page.comments !== false && theme.comments.use - - commentsJsLoad = true - - script. - (() => { - const commentDiv = `!{partial('includes/third-party/comments/index', {}, {cache: true})}` - - const runDestroy = (shuoshuoComment) => { - if (!shuoshuoComment) return - - for (const [key, fn] of Object.entries(shuoshuoComment)) { - if (key.startsWith('destroy')) fn() - } - } - - window.addCommentToShuoshuo = e => { - const btn = e.target.closest('.shuoshuo-comment-btn') - if (!btn) return - - const ele = btn.closest('.container').nextElementSibling - const { shuoshuoComment } = window - const isInclude = ele.classList.contains('no-comment') - runDestroy(shuoshuoComment) - if (isInclude) { - ele.classList.remove('no-comment') - ele.innerHTML = commentDiv - const key = `${location.pathname.replace(/\/$/, '')}?key=${ele.getAttribute('data-key')}` - btf.switchComments(ele, key) - shuoshuoComment.loadComment && shuoshuoComment.loadComment(ele, key) - } - } - })() - - if page.shuoshuo_url - script. - (() => { - const limitConfig = !{ JSON.stringify(page.limit || {}) } - - const sortDataByDate = data => data.sort((a, b) => new Date(b.date) - new Date(a.date)) - - const filterDataByLimit = (data, limit) => { - if (!limit || !limit.type) return data - if (limit.type === 'num') return data.slice(0, limit.value) - if (limit.type === 'date') { - const limitDate = new Date(limit.value) - return data.filter(item => new Date(item.date) >= limitDate) - } - return data - }; - - const formatToTimeZone = (date) => { - const fullDate = date.length === 10 ? `${date} 00:00:00` : date - const visitorTimeZone = '#{config.timezone}' || Intl.DateTimeFormat().resolvedOptions().timeZone - const options = { - timeZone: visitorTimeZone, - year: 'numeric', - month: '2-digit', - day: '2-digit', - hour: '2-digit', - minute: '2-digit', - second: '2-digit', - hour12: false - } - const [day, month, year, hour, minute, second] = new Intl.DateTimeFormat('en-GB', options) - .format(new Date(fullDate)) - .match(/\d+/g) - return `${year}-${month}-${day} ${hour}:${minute}:${second}` - } - - const loadShuoshuo = async () => { - try { - const response = await fetch('!{url_for(page.shuoshuo_url)}') - let data = await response.json() - - data = filterDataByLimit(sortDataByDate(data), limitConfig) - - const container = document.getElementById('article-container') - let start = 0 - - const renderData = (dataSlice) => { - const content = dataSlice.map(item => { - const formattedDate = formatToTimeZone(item.date) - const tags = item.tags && item.tags.map(tag => `${tag}`).join('') || '' - const commentButton = item.key && !{commentsJsLoad} - ? `
- -
` - : '' - const commentContainer = item.key - ? `
` - : '' - - return ` -
-
-
-
- -
-
-
${item.author || '!{config.author}'}
- -
-
-
${item.content}
- -
- ${commentContainer} -
` - }).join('') - - container.insertAdjacentHTML('beforeend', content) - - window.lazyLoadInstance.update() - btf.loadLightbox(document.querySelectorAll('#article-container img:not(.no-lightbox)')) - } - - const handleIntersection = (entries) => { - if (!entries[0].isIntersecting) return - observer.unobserve(entries[0].target) - - const slice = data.slice(start, start + 10) - renderData(slice) - start += 10 - - if (start < data.length) { - setTimeout(() => observer.observe(container.lastElementChild), 100) - } else { - observer.disconnect() - } - }; - - const observer = new IntersectionObserver(handleIntersection, { - root: null, - rootMargin: '0px', - threshold: 1.0 - }) - - renderData(data.slice(start, 10)) - start += 10 - - if (container.lastElementChild) observer.observe(container.lastElementChild) - } catch (error) { - console.error(error) - } - }; - - window.pjax ? loadShuoshuo() : window.addEventListener('load', loadShuoshuo) - })() - else - if site.data.shuoshuo - each i in shuoshuoFN(site.data.shuoshuo, page) - .shuoshuo-item - .container - .shuoshuo-item-header - .shuoshuo-avatar - img.no-lightbox(src=i.avatar || url_for(theme.avatar.img)) - .shuoshuo-info - .shuoshuo-author=i.author || config.author - time.shuoshuo-date(title=i.date)=i.date - .shuoshuo-content - !=markdown(i.content) - .shuoshuo-footer(class=i.tags && i.tags.length ? 'flex-between' : 'flex-end') - if i.tags - .shuoshuo-tags - each tag in i.tags - span.shuoshuo-tag=tag - if i.key && commentsJsLoad - .shuoshuo-comment-btn(onclick='addCommentToShuoshuo(event)') - i.fa-solid.fa-comments - if i.key && commentsJsLoad - .shuoshuo-comment.no-comment(data-key=i.key) \ No newline at end of file diff --git a/themes/butterfly/layout/includes/page/tags.pug b/themes/butterfly/layout/includes/page/tags.pug deleted file mode 100644 index 9c48197..0000000 --- a/themes/butterfly/layout/includes/page/tags.pug +++ /dev/null @@ -1,2 +0,0 @@ -.tag-cloud-list.text-center - !=cloudTags({source: site.tags, orderby: page.orderby || 'random', order: page.order || 1, minfontsize: 1.2, maxfontsize: 1.5, limit: 0, unit: 'em'}) \ No newline at end of file diff --git a/themes/butterfly/layout/includes/pagination.pug b/themes/butterfly/layout/includes/pagination.pug deleted file mode 100644 index bbfc1eb..0000000 --- a/themes/butterfly/layout/includes/pagination.pug +++ /dev/null @@ -1,37 +0,0 @@ -- - var options = { - prev_text: '', - next_text: '', - mid_size: 1, - escape: false - } - -if globalPageType === 'post' - - let paginationOrder = theme.post_pagination === 1 ? { prev: page.prev, next: page.next } : { prev: page.next, next: page.prev } - - nav#pagination.pagination-post - each direction, key in paginationOrder - if direction - - const getPostDesc = direction.postDesc || postDesc(direction) - - let className = key === 'prev' ? (paginationOrder.next ? '' : 'full-width') : (paginationOrder.prev ? '' : 'full-width') - - className = getPostDesc ? className : className + ' no-desc' - - a.pagination-related(class=className href=url_for(direction.path) title=direction.title) - if direction.cover_type === 'img' - img.cover(src=url_for(direction.cover) onerror=`onerror=null;src='${url_for(theme.error_img.post_page)}'` alt=`cover of ${key === 'prev' ? 'previous' : 'next'} post`) - else - .cover(style=`background: ${direction.cover || 'var(--default-bg-color)'}`) - - .info(class=key === 'prev' ? '' : 'text-right') - .info-1 - .info-item-1=_p(`pagination.${key}`) - .info-item-2!=direction.title - if getPostDesc - .info-2 - .info-item-1!=getPostDesc -else - nav#pagination - .pagination - if globalPageType === 'home' - - options.format = 'page/%d/#content-inner' - !=paginator(options) \ No newline at end of file diff --git a/themes/butterfly/layout/includes/post/outdate-notice.pug b/themes/butterfly/layout/includes/post/outdate-notice.pug deleted file mode 100644 index b0cd21e..0000000 --- a/themes/butterfly/layout/includes/post/outdate-notice.pug +++ /dev/null @@ -1,8 +0,0 @@ -- const { limit_day, message_prev, message_next, position} = theme.noticeOutdate -- const notice_data = { limitDay: limit_day, messagePrev: message_prev, messageNext: message_next, postUpdate: full_date(page.updated)} -if position === 'top' - #post-outdate-notice(data=notice_data hidden) - !=page.content -else - !=page.content - #post-outdate-notice(data=notice_data hidden) \ No newline at end of file diff --git a/themes/butterfly/layout/includes/post/post-copyright.pug b/themes/butterfly/layout/includes/post/post-copyright.pug deleted file mode 100644 index 1f7c50a..0000000 --- a/themes/butterfly/layout/includes/post/post-copyright.pug +++ /dev/null @@ -1,23 +0,0 @@ -if theme.post_copyright.enable && page.copyright !== false - - const author = page.copyright_author || config.author - - const authorHref = page.copyright_author_href || theme.post_copyright.author_href || config.url - - const url = page.copyright_url || page.permalink - - const info = page.copyright_info || _p('post.copyright.copyright_content', theme.post_copyright.license_url, theme.post_copyright.license, config.url, config.title) - .post-copyright - .post-copyright__author - span.post-copyright-meta - i.fas.fa-circle-user.fa-fw - = _p('post.copyright.author') + ": " - span.post-copyright-info - a(href=authorHref)= author - .post-copyright__type - span.post-copyright-meta - i.fas.fa-square-arrow-up-right.fa-fw - = _p('post.copyright.link') + ": " - span.post-copyright-info - a(href=url_for(url))= theme.post_copyright.decode ? decodeURI(url) : url - .post-copyright__notice - span.post-copyright-meta - i.fas.fa-circle-exclamation.fa-fw - = _p('post.copyright.copyright_notice') + ": " - span.post-copyright-info!= info \ No newline at end of file diff --git a/themes/butterfly/layout/includes/post/reward.pug b/themes/butterfly/layout/includes/post/reward.pug deleted file mode 100644 index baf525c..0000000 --- a/themes/butterfly/layout/includes/post/reward.pug +++ /dev/null @@ -1,12 +0,0 @@ -.post-reward - .reward-button - i.fas.fa-qrcode - = theme.reward.text || _p('donate') - .reward-main - ul.reward-all - each item in theme.reward.QR_code - - const clickTo = item.link || item.img - li.reward-item - a(href=url_for(clickTo) target='_blank') - img.post-qr-code-img(src=url_for(item.img) alt=item.text) - .post-qr-code-desc=item.text \ No newline at end of file diff --git a/themes/butterfly/layout/includes/rightside.pug b/themes/butterfly/layout/includes/rightside.pug deleted file mode 100644 index b7608ad..0000000 --- a/themes/butterfly/layout/includes/rightside.pug +++ /dev/null @@ -1,61 +0,0 @@ -- const { readmode, translate, darkmode, aside, chat } = theme -mixin rightsideItem(array) - each item in array - case item - when 'readmode' - if globalPageType === 'post' && readmode - button#readmode(type="button" title=_p('rightside.readmode_title')) - i.fas.fa-book-open - when 'translate' - if translate.enable - button#translateLink(type="button" title=_p('rightside.translate_title'))= translate.default - when 'darkmode' - if darkmode.enable && darkmode.button - button#darkmode(type="button" title=_p('rightside.night_mode_title')) - i.fas.fa-adjust - when 'hideAside' - if aside.enable && aside.button && page.aside !== false - button#hide-aside-btn(type="button" title=_p('rightside.aside')) - i.fas.fa-arrows-alt-h - when 'toc' - if showToc - button#mobile-toc-button.close(type="button" title=_p("rightside.toc")) - i.fas.fa-list-ul - when 'chat' - if chat.rightside_button && chat.use - button#chat-btn(type="button" title=_p("rightside.chat") style="display:none") - i.fas.fa-message - when 'comment' - if commentsJsLoad - a#to_comment(href="#post-comment" title=_p("rightside.scroll_to_comment")) - i.fas.fa-comments - -#rightside - - const { enable, hide, show } = theme.rightside_item_order - - const hideArray = enable ? hide && hide.split(',') : ['readmode','translate','darkmode','hideAside'] - - const showArray = enable ? show && show.split(',') : ['toc','chat','comment'] - - - #rightside-config-hide - if hideArray - +rightsideItem(hideArray) - #rightside-config-show - if enable - if hide - button#rightside-config(type="button" title=_p("rightside.setting")) - i.fas.fa-cog.fa-spin - else - if globalPageType === 'post' - if (readmode || translate.enable || (darkmode.enable && darkmode.button)) - button#rightside-config(type="button" title=_p("rightside.setting")) - i.fas.fa-cog.fa-spin - else if translate.enable || (darkmode.enable && darkmode.button) - button#rightside-config(type="button" title=_p("rightside.setting")) - i.fas.fa-cog.fa-spin - - if showArray - +rightsideItem(showArray) - - button#go-up(type="button" title=_p("rightside.back_to_top")) - span.scroll-percent - i.fas.fa-arrow-up \ No newline at end of file diff --git a/themes/butterfly/layout/includes/sidebar.pug b/themes/butterfly/layout/includes/sidebar.pug deleted file mode 100644 index 22abeff..0000000 --- a/themes/butterfly/layout/includes/sidebar.pug +++ /dev/null @@ -1,18 +0,0 @@ -if theme.menu - #sidebar - #menu-mask - #sidebar-menus - .avatar-img.text-center - img(src=url_for(theme.avatar.img) onerror=`this.onerror=null;this.src='${url_for(theme.error_img.flink)}'` alt="avatar") - .site-data.text-center - a(href=`${url_for(config.archive_dir)}/`) - .headline= _p('aside.articles') - .length-num= site.posts.length - a(href=`${url_for(config.tag_dir)}/`) - .headline= _p('aside.tags') - .length-num= site.tags.length - a(href=`${url_for(config.category_dir)}/`) - .headline= _p('aside.categories') - .length-num= site.categories.length - - != partial('includes/header/menu_item', {}, { cache: true }) \ No newline at end of file diff --git a/themes/butterfly/layout/includes/third-party/abcjs/abcjs.pug b/themes/butterfly/layout/includes/third-party/abcjs/abcjs.pug deleted file mode 100644 index 9de2b97..0000000 --- a/themes/butterfly/layout/includes/third-party/abcjs/abcjs.pug +++ /dev/null @@ -1,46 +0,0 @@ -script. - (function() { - const abcjsInit = function() { - const abcjsFn = function() { - setTimeout(function() { - const sheets = document.querySelectorAll(".abc-music-sheet") - for (let i = 0; i < sheets.length; i++) { - const ele = sheets[i] - if (ele.children.length > 0) continue - - // Parse parameters from data-params attribute - let params = {} - const dp = ele.getAttribute("data-params") - if (dp) { - try { - params = JSON.parse(dp) - } catch (e) { - console.error("Failed to parse data-params:", e) - } - } - - // Merge parsed parameters with the responsive option - // Ensures params content appears before responsive - const options = { ...params, responsive: "resize" } - - // Render the music score using ABCJS.renderAbc - ABCJS.renderAbc(ele, ele.innerHTML, options) - } - }, 100) - } - - if (typeof ABCJS === "object") { - abcjsFn() - } else { - btf.getScript("!{url_for(theme.asset.abcjs_basic_js)}").then(abcjsFn) - } - } - - if (window.pjax) { - abcjsInit() - } else { - window.addEventListener("load", abcjsInit) - } - - btf.addGlobalFn("encrypt", abcjsInit, "abcjs") - })() diff --git a/themes/butterfly/layout/includes/third-party/abcjs/index.pug b/themes/butterfly/layout/includes/third-party/abcjs/index.pug deleted file mode 100644 index 7ae7deb..0000000 --- a/themes/butterfly/layout/includes/third-party/abcjs/index.pug +++ /dev/null @@ -1,3 +0,0 @@ -if theme.abcjs.enable - if theme.abcjs.per_page && (['post','page'].includes(globalPageType)) || page.abcjs - include ./abcjs.pug diff --git a/themes/butterfly/layout/includes/third-party/aplayer.pug b/themes/butterfly/layout/includes/third-party/aplayer.pug deleted file mode 100644 index 4e86609..0000000 --- a/themes/butterfly/layout/includes/third-party/aplayer.pug +++ /dev/null @@ -1,23 +0,0 @@ -link(rel='stylesheet' href=url_for(theme.asset.aplayer_css) media="print" onload="this.media='all'") -script(src=url_for(theme.asset.aplayer_js)) -script(src=url_for(theme.asset.meting_js)) -if theme.pjax.enable - script. - (() => { - const destroyAplayer = () => { - if (window.aplayers) { - for (let i = 0; i < window.aplayers.length; i++) { - if (!window.aplayers[i].options.fixed) { - window.aplayers[i].destroy() - } - } - } - } - - const runMetingJS = () => { - typeof loadMeting === 'function' && document.getElementsByClassName('aplayer').length && loadMeting() - } - - btf.addGlobalFn('pjaxSend', destroyAplayer, 'destroyAplayer') - btf.addGlobalFn('pjaxComplete', loadMeting, 'runMetingJS') - })() diff --git a/themes/butterfly/layout/includes/third-party/card-post-count/artalk.pug b/themes/butterfly/layout/includes/third-party/card-post-count/artalk.pug deleted file mode 100644 index eeccef3..0000000 --- a/themes/butterfly/layout/includes/third-party/card-post-count/artalk.pug +++ /dev/null @@ -1,31 +0,0 @@ -- const { server, site } = theme.artalk - -script. - (() => { - const getArtalkCount = async() => { - try { - const eleGroup = document.querySelectorAll('#recent-posts .artalk-count') - const keyArray = Array.from(eleGroup).map(i => i.getAttribute('data-page-key')) - - const headerList = { - method: 'GET', - } - - const searchParams = new URLSearchParams({ - 'site_name': '!{site}', - 'page_keys': keyArray - }) - - const res = await fetch(`!{server}/api/v2/stats/page_comment?${searchParams}`, headerList) - const result = await res.json() - - keyArray.forEach((key, index) => { - eleGroup[index].textContent = result.data[key] || 0 - }) - } catch (err) { - console.error(err) - } - } - - window.pjax ? getArtalkCount() : window.addEventListener('load', getArtalkCount) - })() \ No newline at end of file diff --git a/themes/butterfly/layout/includes/third-party/card-post-count/disqus.pug b/themes/butterfly/layout/includes/third-party/card-post-count/disqus.pug deleted file mode 100644 index d039d9a..0000000 --- a/themes/butterfly/layout/includes/third-party/card-post-count/disqus.pug +++ /dev/null @@ -1,25 +0,0 @@ -- const { shortname, apikey } = theme.disqus -script. - (() => { - const getCount = async () => { - try { - const eleGroup = document.querySelectorAll('#recent-posts .disqus-count') - const cleanedLinks = Array.from(eleGroup).map(i => `thread:link=${i.href.replace(/#post-comment$/, '')}`); - - const res = await fetch(`https://disqus.com/api/3.0/threads/set.json?forum=!{shortname}&api_key=!{apikey}&${cleanedLinks.join('&')}`,{ - method: 'GET' - }) - const result = await res.json() - - eleGroup.forEach(i => { - const cleanedLink = i.href.replace(/#post-comment$/, '') - const urlData = result.response.find(data => data.link === cleanedLink) || { posts: 0 } - i.textContent = urlData.posts - }) - } catch (err) { - console.error(err) - } - } - - window.pjax ? getCount() : window.addEventListener('load', getCount) - })() diff --git a/themes/butterfly/layout/includes/third-party/card-post-count/fb.pug b/themes/butterfly/layout/includes/third-party/card-post-count/fb.pug deleted file mode 100644 index 408ec2e..0000000 --- a/themes/butterfly/layout/includes/third-party/card-post-count/fb.pug +++ /dev/null @@ -1,18 +0,0 @@ -- const fbSDKVer = 'v20.0' -- const fbSDK = `https://connect.facebook.net/${theme.facebook_comments.lang}/sdk.js#xfbml=1&version=${fbSDKVer}` - -script. - (()=>{ - function loadFBComment () { - if (typeof FB === 'object') FB.XFBML.parse(document.getElementById('recent-posts')) - else { - let ele = document.createElement('script') - ele.setAttribute('src','!{fbSDK}') - ele.setAttribute('async', 'true') - ele.setAttribute('defer', 'true') - ele.setAttribute('crossorigin', 'anonymous') - document.body.appendChild(ele) - } - } - window.pjax ? loadFBComment() : window.addEventListener('load', loadFBComment) - })() diff --git a/themes/butterfly/layout/includes/third-party/card-post-count/index.pug b/themes/butterfly/layout/includes/third-party/card-post-count/index.pug deleted file mode 100644 index 0079318..0000000 --- a/themes/butterfly/layout/includes/third-party/card-post-count/index.pug +++ /dev/null @@ -1,16 +0,0 @@ -case theme.comments.use[0] - when 'Twikoo' - include ./twikoo.pug - when 'Disqus' - when 'Disqusjs' - include ./disqus.pug - when 'Valine' - include ./valine.pug - when 'Waline' - include ./waline.pug - when 'Facebook Comments' - include ./fb.pug - when 'Remark42' - include ./remark42.pug - when 'Artalk' - include ./artalk.pug \ No newline at end of file diff --git a/themes/butterfly/layout/includes/third-party/card-post-count/remark42.pug b/themes/butterfly/layout/includes/third-party/card-post-count/remark42.pug deleted file mode 100644 index 5fbb840..0000000 --- a/themes/butterfly/layout/includes/third-party/card-post-count/remark42.pug +++ /dev/null @@ -1,18 +0,0 @@ -- const { host, siteId, option } = theme.remark42 - -script. - (()=>{ - window.remark_config = Object.assign({ - host: '!{host}', - site_id: '!{siteId}', - },!{JSON.stringify(option)}) - - function getCount () { - const s = document.createElement('script') - s.src = remark_config.host + '/web/counter.js' - s.defer = true - document.head.appendChild(s) - } - - window.pjax ? getCount() : window.addEventListener('load', getCount) - })() \ No newline at end of file diff --git a/themes/butterfly/layout/includes/third-party/card-post-count/twikoo.pug b/themes/butterfly/layout/includes/third-party/card-post-count/twikoo.pug deleted file mode 100644 index e104fa9..0000000 --- a/themes/butterfly/layout/includes/third-party/card-post-count/twikoo.pug +++ /dev/null @@ -1,37 +0,0 @@ -script. - (() => { - const getCommentUrl = () => { - const eleGroup = document.querySelectorAll('#recent-posts .article-title') - let urlArray = [] - eleGroup.forEach(i=>{ - urlArray.push(i.getAttribute('href')) - }) - return urlArray - } - - const getCount = () => { - const runTwikoo = () => { - twikoo.getCommentsCount({ - envId: '!{theme.twikoo.envId}', - region: '!{theme.twikoo.region}', - urls: getCommentUrl(), - includeReply: false - }).then(function (res) { - document.querySelectorAll('#recent-posts .twikoo-count').forEach((item,index) => { - item.textContent = res[index].count - }) - }).catch(function (err) { - console.log(err) - }) - } - - if (typeof twikoo === 'object') { - runTwikoo() - } else { - btf.getScript('!{url_for(theme.asset.twikoo)}').then(runTwikoo) - } - } - - window.pjax ? getCount() : window.addEventListener('load', getCount) - - })() \ No newline at end of file diff --git a/themes/butterfly/layout/includes/third-party/card-post-count/valine.pug b/themes/butterfly/layout/includes/third-party/card-post-count/valine.pug deleted file mode 100644 index 6b583e2..0000000 --- a/themes/butterfly/layout/includes/third-party/card-post-count/valine.pug +++ /dev/null @@ -1,20 +0,0 @@ -script. - (() => { - function loadValine () { - function initValine () { - let initData = { - el: '#vcomment', - appId: '#{theme.valine.appId}', - appKey: '#{theme.valine.appKey}', - serverURLs: '#{theme.valine.serverURLs}' - } - - const valine = new Valine(initData) - } - - if (typeof Valine === 'function') initValine() - else btf.getScript('!{url_for(theme.asset.valine)}').then(initValine) - } - - window.pjax ? loadValine() : window.addEventListener('load', loadValine) - })() diff --git a/themes/butterfly/layout/includes/third-party/card-post-count/waline.pug b/themes/butterfly/layout/includes/third-party/card-post-count/waline.pug deleted file mode 100644 index 96e9657..0000000 --- a/themes/butterfly/layout/includes/third-party/card-post-count/waline.pug +++ /dev/null @@ -1,21 +0,0 @@ -- const serverURL = theme.waline.serverURL.replace(/\/$/, '') -script. - (() => { - async function loadWaline () { - try { - const eleGroup = document.querySelectorAll('#recent-posts .waline-comment-count') - const keyArray = Array.from(eleGroup).map(i => i.getAttribute('data-path')) - - const res = await fetch(`!{serverURL}/api/comment?type=count&url=${keyArray}`, { method: 'GET' }) - const result = await res.json() - - result.data.forEach((count, index) => { - eleGroup[index].textContent = count - }) - } catch (err) { - console.error(err) - } - } - - window.pjax ? loadWaline() : window.addEventListener('load', loadWaline) - })() diff --git a/themes/butterfly/layout/includes/third-party/chat/chatra.pug b/themes/butterfly/layout/includes/third-party/chat/chatra.pug deleted file mode 100644 index e4a6900..0000000 --- a/themes/butterfly/layout/includes/third-party/chat/chatra.pug +++ /dev/null @@ -1,38 +0,0 @@ -//- https://chatra.io/help/api/ -script. - (() => { - window.ChatraID = '#{theme.chatra.id}' - window.Chatra = window.Chatra || function() { - (window.Chatra.q = window.Chatra.q || []).push(arguments) - } - - btf.getScript('https://call.chatra.io/chatra.js').then(() => { - const isChatBtn = !{theme.chat.rightside_button} - const isChatHideShow = !{theme.chat.button_hide_show} - - if (isChatBtn) { - const close = () => { - Chatra('minimizeWidget') - Chatra('hide') - } - - const open = () => { - Chatra('openChat', true) - Chatra('show') - } - - window.ChatraSetup = { startHidden: true } - - window.chatBtnFn = () => document.getElementById('chatra').classList.contains('chatra--expanded') ? close() : open() - - document.getElementById('chat-btn').style.display = 'block' - } else if (isChatHideShow) { - window.chatBtn = { - hide: () => Chatra('hide'), - show: () => Chatra('show') - } - } - }) - })() - - diff --git a/themes/butterfly/layout/includes/third-party/chat/crisp.pug b/themes/butterfly/layout/includes/third-party/chat/crisp.pug deleted file mode 100644 index 6485566..0000000 --- a/themes/butterfly/layout/includes/third-party/chat/crisp.pug +++ /dev/null @@ -1,32 +0,0 @@ -script. - (() => { - window.$crisp = ['safe', true] - window.CRISP_WEBSITE_ID = "!{theme.crisp.website_id}" - - btf.getScript('https://client.crisp.chat/l.js').then(() => { - const isChatBtn = !{theme.chat.rightside_button} - const isChatHideShow = !{theme.chat.button_hide_show} - - if (isChatBtn) { - const open = () => { - $crisp.push(["do", "chat:show"]) - $crisp.push(["do", "chat:open"]) - } - - const close = () => $crisp.push(["do", "chat:hide"]) - - close() - - $crisp.push(["on", "chat:closed", close]) - - window.chatBtnFn = () => $crisp.is("chat:visible") ? close() : open() - - document.getElementById('chat-btn').style.display = 'block' - } else if (isChatHideShow) { - window.chatBtn = { - hide: () => $crisp.push(["do", "chat:hide"]), - show: () => $crisp.push(["do", "chat:show"]) - } - } - }) - })() \ No newline at end of file diff --git a/themes/butterfly/layout/includes/third-party/chat/index.pug b/themes/butterfly/layout/includes/third-party/chat/index.pug deleted file mode 100644 index dada453..0000000 --- a/themes/butterfly/layout/includes/third-party/chat/index.pug +++ /dev/null @@ -1,7 +0,0 @@ -case theme.chat.use - when 'chatra' - include ./chatra.pug - when 'tidio' - include ./tidio.pug - when 'crisp' - include ./crisp.pug \ No newline at end of file diff --git a/themes/butterfly/layout/includes/third-party/chat/tidio.pug b/themes/butterfly/layout/includes/third-party/chat/tidio.pug deleted file mode 100644 index 83ca1ec..0000000 --- a/themes/butterfly/layout/includes/third-party/chat/tidio.pug +++ /dev/null @@ -1,45 +0,0 @@ -script. - (() => { - btf.getScript('//code.tidio.co/!{theme.tidio.public_key}.js').then(() => { - const isChatBtn = !{theme.chat.rightside_button} - const isChatHideShow = !{theme.chat.button_hide_show} - - if (isChatBtn) { - let isShow = false - const close = () => { - window.tidioChatApi.hide() - isShow = false - } - - const open = () => { - window.tidioChatApi.open() - window.tidioChatApi.show() - isShow = true - } - - const onTidioChatApiReady = () => { - window.tidioChatApi.hide() - window.tidioChatApi.on("close", close) - } - if (window.tidioChatApi) { - window.tidioChatApi.on("ready", onTidioChatApiReady) - } else { - document.addEventListener("tidioChat-ready", onTidioChatApiReady) - } - - window.chatBtnFn = () => { - if (!window.tidioChatApi) return - isShow ? close() : open() - } - - document.getElementById('chat-btn').style.display = 'block' - - } else if (isChatHideShow) { - window.chatBtn = { - hide: () => window.tidioChatApi && window.tidioChatApi.hide(), - show: () => window.tidioChatApi && window.tidioChatApi.show() - } - } - }) - })() - diff --git a/themes/butterfly/layout/includes/third-party/comments/artalk.pug b/themes/butterfly/layout/includes/third-party/comments/artalk.pug deleted file mode 100644 index 7d0e857..0000000 --- a/themes/butterfly/layout/includes/third-party/comments/artalk.pug +++ /dev/null @@ -1,73 +0,0 @@ -- const { server, site, option } = theme.artalk -- const { use, lazyload } = theme.comments - -script. - (() => { - let artalkItem = null - const option = !{JSON.stringify(option)} - const isShuoshuo = GLOBAL_CONFIG_SITE.pageType === 'shuoshuo' - - const destroyArtalk = () => { - if (artalkItem) { - artalkItem.destroy() - artalkItem = null - } - } - - const artalkChangeMode = theme => artalkItem && artalkItem.setDarkMode(theme === 'dark') - - const initArtalk = (el = document, pageKey = location.pathname) => { - artalkItem = Artalk.init({ - el: el.querySelector('#artalk-wrap'), - server: '!{server}', - site: '!{site}', - darkMode: document.documentElement.getAttribute('data-theme') === 'dark', - ...option, - pageKey: isShuoshuo ? pageKey : (option && option.pageKey) || pageKey - }) - - if (GLOBAL_CONFIG.lightbox === 'null') return - artalkItem.on('list-loaded', () => { - artalkItem.ctx.get('list').getCommentNodes().forEach(comment => { - const $content = comment.getRender().$content - btf.loadLightbox($content.querySelectorAll('img:not([atk-emoticon])')) - }) - }) - - if (isShuoshuo) { - window.shuoshuoComment.destroyArtalk = () => { - destroyArtalk() - if (el.children.length) { - el.innerHTML = '' - el.classList.add('no-comment') - } - } - } - - btf.addGlobalFn('pjaxSendOnce', destroyArtalk, 'destroyArtalk') - btf.addGlobalFn('themeChange', artalkChangeMode, 'artalk') - } - - const loadArtalk = async (el, pageKey) => { - if (typeof Artalk === 'object') initArtalk(el, pageKey) - else { - await btf.getCSS('!{theme.asset.artalk_css}') - await btf.getScript('!{theme.asset.artalk_js}') - initArtalk(el, pageKey) - } - } - - if (isShuoshuo) { - '!{use[0]}' === 'Artalk' - ? window.shuoshuoComment = { loadComment: loadArtalk } - : window.loadOtherComment = loadArtalk - return - } - - if ('!{use[0]}' === 'Artalk' || !!{lazyload}) { - if (!{lazyload}) btf.loadComment(document.getElementById('artalk-wrap'), loadArtalk) - else setTimeout(loadArtalk, 100) - } else { - window.loadOtherComment = loadArtalk - } - })() \ No newline at end of file diff --git a/themes/butterfly/layout/includes/third-party/comments/disqus.pug b/themes/butterfly/layout/includes/third-party/comments/disqus.pug deleted file mode 100644 index 9fe6454..0000000 --- a/themes/butterfly/layout/includes/third-party/comments/disqus.pug +++ /dev/null @@ -1,80 +0,0 @@ -- const disqusPageTitle = page.title.replace(/'/ig,"\\'") -- const { shortname, apikey } = theme.disqus -- const { use, lazyload, count } = theme.comments - -script. - (() => { - const isShuoshuo = GLOBAL_CONFIG_SITE.pageType === 'shuoshuo' - - const disqusReset = conf => { - window.DISQUS && window.DISQUS.reset({ - reload: true, - config: conf - }) - } - - const loadDisqus = (el, path) => { - if (isShuoshuo) { - window.shuoshuoComment.destroyDisqus = () => { - if (el.children.length) { - el.innerHTML = '' - el.classList.add('no-comment') - } - } - } - - window.disqus_identifier = isShuoshuo ? path : '!{ url_for(page.path) }' - window.disqus_url = isShuoshuo ? location.origin + path : '!{ page.permalink }' - - const disqus_config = function () { - this.page.url = disqus_url - this.page.identifier = disqus_identifier - this.page.title = '!{ disqusPageTitle }' - } - - if (window.DISQUS) disqusReset(disqus_config) - else { - const script = document.createElement('script') - script.src = 'https://!{shortname}.disqus.com/embed.js' - script.setAttribute('data-timestamp', +new Date()) - document.head.appendChild(script) - } - - btf.addGlobalFn('themeChange', () => disqusReset(disqus_config), 'disqus') - } - - const getCount = async() => { - try { - const eleGroup = document.querySelector('#post-meta .disqus-comment-count') - if (!eleGroup) return - const cleanedLinks = eleGroup.href.replace(/#post-comment$/, '') - - const res = await fetch(`https://disqus.com/api/3.0/threads/set.json?forum=!{shortname}&api_key=!{apikey}&thread:link=${cleanedLinks}`,{ - method: 'GET' - }) - const result = await res.json() - - const count = result.response.length ? result.response[0].posts : 0 - eleGroup.textContent = count - } catch (err) { - console.error(err) - } - } - - if (isShuoshuo) { - '!{use[0]}' === 'Disqus' - ? window.shuoshuoComment = { loadComment: loadDisqus } - : window.loadOtherComment = loadDisqus - return - } - - if ('!{use[0]}' === 'Disqus' || !!{lazyload}) { - if (!{lazyload}) btf.loadComment(document.getElementById('disqus_thread'), loadDisqus) - else { - loadDisqus() - !{ count ? `GLOBAL_CONFIG_SITE.pageType === 'post' && getCount()` : '' } - } - } else { - window.loadOtherComment = loadDisqus - } - })() diff --git a/themes/butterfly/layout/includes/third-party/comments/disqusjs.pug b/themes/butterfly/layout/includes/third-party/comments/disqusjs.pug deleted file mode 100644 index ef2a235..0000000 --- a/themes/butterfly/layout/includes/third-party/comments/disqusjs.pug +++ /dev/null @@ -1,87 +0,0 @@ -- let disqusjsPageTitle = page.title && page.title.replace(/'/ig,"\\'") -- const { shortname:dqShortname, apikey:dqApikey, option:dqOption } = theme.disqusjs - -script. - (() => { - const isShuoshuo = GLOBAL_CONFIG_SITE.pageType === 'shuoshuo'== 'shuoshuo' - const dqOption = !{JSON.stringify(dqOption)} - - const destroyDisqusjs = () => { - disqusjs.destroy() - window.disqusjs = null - } - - const themeChange = (el, path) => { - destroyDisqusjs() - initDisqusjs(el, path) - } - - const initDisqusjs = (el = document, path) => { - if (isShuoshuo) { - window.shuoshuoComment.destroyDisqusjs = () => { - destroyDisqusjs() - if (el.children.length) { - el.innerHTML = '' - el.classList.add('no-comment') - } - } - } - - disqusjs = new DisqusJS({ - shortname: '!{dqShortname}', - title: '!{ disqusjsPageTitle }', - apikey: '!{dqApikey}', - ...dqOption, - identifier: isShuoshuo ? path : (dqOption && dqOption.identifier) || '!{ url_for(page.path) }', - url: isShuoshuo ? location.origin + path : (dqOption && dqOption.url) || '!{ page.permalink }' - }) - - disqusjs.render(el.querySelector('#disqusjs-wrap')) - - btf.addGlobalFn('themeChange', () => themeChange(el, path), 'disqusjs') - } - - const loadDisqusjs = async(el, path) => { - if (window.disqusJsLoad) initDisqusjs(el, path) - else { - await btf.getCSS('!{url_for(theme.asset.disqusjs_css)}') - await btf.getScript('!{url_for(theme.asset.disqusjs)}') - initDisqusjs(el, path) - window.disqusJsLoad = true - } - } - - const getCount = async() => { - try { - const eleGroup = document.querySelector('#post-meta .disqusjs-comment-count') - if (!eleGroup) return - const cleanedLinks = eleGroup.href.replace(/#post-comment$/, '') - - const res = await fetch(`https://disqus.com/api/3.0/threads/set.json?forum=!{dqShortname}&api_key=!{dqApikey}&thread:link=${cleanedLinks}`,{ - method: 'GET' - }) - const result = await res.json() - const count = result.response.length ? result.response[0].posts : 0 - eleGroup.textContent = count - } catch (err) { - console.error(err) - } - } - - if (isShuoshuo) { - '!{theme.comments.use[0]}' === 'Disqusjs' - ? window.shuoshuoComment = { loadComment: loadDisqusjs } - : window.loadOtherComment = loadDisqusjs - return - } - - if ('!{theme.comments.use[0]}' === 'Disqusjs' || !!{theme.comments.lazyload}) { - if (!{theme.comments.lazyload}) btf.loadComment(document.getElementById('disqusjs-wrap'), loadDisqusjs) - else { - loadDisqusjs() - !{ theme.comments.count ? `GLOBAL_CONFIG_SITE.pageType === 'post' && getCount()` : '' } - } - } else { - window.loadOtherComment = loadDisqusjs - } - })() \ No newline at end of file diff --git a/themes/butterfly/layout/includes/third-party/comments/facebook_comments.pug b/themes/butterfly/layout/includes/third-party/comments/facebook_comments.pug deleted file mode 100644 index 25945db..0000000 --- a/themes/butterfly/layout/includes/third-party/comments/facebook_comments.pug +++ /dev/null @@ -1,64 +0,0 @@ -- const fbSDKVer = 'v20.0' -- const fbSDK = `https://connect.facebook.net/${theme.facebook_comments.lang}/sdk.js#xfbml=1&version=${fbSDKVer}` - -script. - (()=>{ - const isShuoshuo = GLOBAL_CONFIG_SITE.pageType === 'shuoshuo'== 'shuoshuo' - - const loadFBComment = (el = document, path) => { - if (isShuoshuo) { - window.shuoshuoComment.destroyFB = () => { - if (el.children.length) { - el.innerHTML = '' - el.classList.add('no-comment') - } - } - } - - document.getElementById('fb-root') ? '' : document.body.insertAdjacentHTML('afterend', '
') - - const themeNow = document.documentElement.getAttribute('data-theme') === 'dark' ? 'dark' : 'light' - const $fbComment = el.getElementsByClassName('fb-comments')[0] - $fbComment.setAttribute('data-colorscheme',themeNow) - $fbComment.setAttribute('data-href', isShuoshuo ? '!{urlNoIndex(page.permalink)}' + '#' + path : '!{urlNoIndex(page.permalink)}') - - if (typeof FB === 'object') { - FB.XFBML.parse(document.getElementsByClassName('post-meta-commentcount')[0]) - FB.XFBML.parse(el.querySelector('#post-comment')) - } - else { - let ele = document.createElement('script') - ele.setAttribute('src','!{fbSDK}') - ele.setAttribute('async', 'true') - ele.setAttribute('defer', 'true') - ele.setAttribute('crossorigin', 'anonymous') - ele.setAttribute('id', 'facebook-jssdk') - document.getElementById('fb-root').insertAdjacentElement('afterbegin',ele) - } - } - - const fbModeChange = theme => { - const $fbComment = document.getElementsByClassName('fb-comments')[0] - if ($fbComment && typeof FB === 'object') { - $fbComment.setAttribute('data-colorscheme',theme) - FB.XFBML.parse(document.getElementById('post-comment')) - } - } - - btf.addGlobalFn('themeChange', fbModeChange, 'facebook_comments') - - if (isShuoshuo) { - '!{theme.comments.use[0]}' === 'Facebook Comments' - ? window.shuoshuoComment = { loadComment: loadFBComment } - : window.loadOtherComment = loadFBComment - return - } - - if ('!{theme.comments.use[0]}' === 'Facebook Comments' || !!{theme.comments.lazyload}) { - if (!{theme.comments.lazyload}) btf.loadComment(document.querySelector('#post-comment .fb-comments'), loadFBComment) - else loadFBComment() - } else { - window.loadOtherComment = loadFBComment - } - })() - diff --git a/themes/butterfly/layout/includes/third-party/comments/giscus.pug b/themes/butterfly/layout/includes/third-party/comments/giscus.pug deleted file mode 100644 index c4b9389..0000000 --- a/themes/butterfly/layout/includes/third-party/comments/giscus.pug +++ /dev/null @@ -1,82 +0,0 @@ -- const { use, lazyload } = theme.comments -- const { repo, repo_id, category_id, light_theme, dark_theme, js, option } = theme.giscus -- const giscusUrl = js || 'https://giscus.app/client.js' -- const giscusOriginUrl = new URL(giscusUrl).origin - -script. - (() => { - const isShuoshuo = GLOBAL_CONFIG_SITE.pageType === 'shuoshuo' - const option = !{JSON.stringify(option)} - - const getGiscusTheme = theme => theme === 'dark' ? '!{dark_theme}' : '!{light_theme}' - - const createScriptElement = config => { - const ele = document.createElement('script') - Object.entries(config).forEach(([key, value]) => { - ele.setAttribute(key, value) - }) - return ele - } - - const loadGiscus = (el = document, key) => { - const mappingConfig = isShuoshuo - ? { 'data-mapping': 'specific', 'data-term': key } - : { 'data-mapping': (option && option['data-mapping']) || 'pathname' } - - const giscusConfig = { - src: '!{giscusUrl}', - 'data-repo': '!{repo}', - 'data-repo-id': '!{repo_id}', - 'data-category-id': '!{category_id}', - 'data-theme': getGiscusTheme(document.documentElement.getAttribute('data-theme')), - 'data-reactions-enabled': '1', - crossorigin: 'anonymous', - async: true, - ...option, - ...mappingConfig - } - - const scriptElement = createScriptElement(giscusConfig) - - el.querySelector('#giscus-wrap').appendChild(scriptElement) - - if (isShuoshuo) { - window.shuoshuoComment.destroyGiscus = () => { - if (el.children.length) { - el.innerHTML = '' - el.classList.add('no-comment') - } - } - } - } - - const changeGiscusTheme = theme => { - const iframe = document.querySelector('#giscus-wrap iframe') - if (iframe) { - const message = { - giscus: { - setConfig: { - theme: getGiscusTheme(theme) - } - } - } - iframe.contentWindow.postMessage(message, '!{giscusOriginUrl}') - } - } - - btf.addGlobalFn('themeChange', changeGiscusTheme, 'giscus') - - if (isShuoshuo) { - '!{use[0]}' === 'Giscus' - ? window.shuoshuoComment = { loadComment: loadGiscus } - : window.loadOtherComment = loadGiscus - return - } - - if ('!{use[0]}' === 'Giscus' || !!{lazyload}) { - if (!{lazyload}) btf.loadComment(document.getElementById('giscus-wrap'), loadGiscus) - else loadGiscus() - } else { - window.loadOtherComment = loadGiscus - } - })() diff --git a/themes/butterfly/layout/includes/third-party/comments/gitalk.pug b/themes/butterfly/layout/includes/third-party/comments/gitalk.pug deleted file mode 100644 index 8a201aa..0000000 --- a/themes/butterfly/layout/includes/third-party/comments/gitalk.pug +++ /dev/null @@ -1,64 +0,0 @@ -- const { client_id, client_secret, repo, owner, admin, option } = theme.gitalk - -script. - (() => { - const isShuoshuo = GLOBAL_CONFIG_SITE.pageType === 'shuoshuo' - const option = !{JSON.stringify(option)} - - const commentCount = n => { - const isCommentCount = document.querySelector('#post-meta .gitalk-comment-count') - if (isCommentCount) { - isCommentCount.textContent= n - } - } - - const initGitalk = (el, path) => { - if (isShuoshuo) { - window.shuoshuoComment.destroyGitalk = () => { - if (el.children.length) { - el.innerHTML = '' - el.classList.add('no-comment') - } - } - } - - const gitalk = new Gitalk({ - clientID: '!{client_id}', - clientSecret: '!{client_secret}', - repo: '!{repo}', - owner: '!{owner}', - admin: ['!{admin}'], - updateCountCallback: commentCount, - ...option, - id: isShuoshuo ? path : (option && option.id) || '!{md5(page.path)}' - }) - - gitalk.render('gitalk-container') - } - - const loadGitalk = async(el, path) => { - if (typeof Gitalk === 'function') initGitalk(el, path) - else { - await btf.getCSS('!{url_for(theme.asset.gitalk_css)}') - await btf.getScript('!{url_for(theme.asset.gitalk)}') - initGitalk(el, path) - } - } - - if (isShuoshuo) { - '!{theme.comments.use[0]}' === 'Gitalk' - ? window.shuoshuoComment = { loadComment: loadGitalk } - : window.loadOtherComment = loadGitalk - return - } - - if ('!{theme.comments.use[0]}' === 'Gitalk' || !!{theme.comments.lazyload}) { - if (!{theme.comments.lazyload}) btf.loadComment(document.getElementById('gitalk-container'), loadGitalk) - else loadGitalk() - } else { - window.loadOtherComment = loadGitalk - } - })() - - - diff --git a/themes/butterfly/layout/includes/third-party/comments/index.pug b/themes/butterfly/layout/includes/third-party/comments/index.pug deleted file mode 100644 index 001f0bf..0000000 --- a/themes/butterfly/layout/includes/third-party/comments/index.pug +++ /dev/null @@ -1,46 +0,0 @@ -- let defaultComment = theme.comments.use[0] -hr.custom-hr -#post-comment - .comment-head - .comment-headline - i.fas.fa-comments.fa-fw - span= ' ' + _p('comment') - - if theme.comments.use.length > 1 - .comment-switch - span.first-comment=defaultComment - span#switch-btn - span.second-comment=theme.comments.use[1] - - - .comment-wrap - each name in theme.comments.use - div - case name - when 'Disqus' - #disqus_thread - when 'Valine' - #vcomment.vcomment - when 'Disqusjs' - #disqusjs-wrap - when 'Livere' - #lv-container(data-id="city" data-uid=theme.livere.uid) - when 'Gitalk' - #gitalk-container - when 'Utterances' - #utterances-wrap - when 'Twikoo' - #twikoo-wrap - when 'Waline' - #waline-wrap - when 'Giscus' - #giscus-wrap - when 'Facebook Comments' - .fb-comments(data-colorscheme = theme.display_mode === 'dark' ? 'dark' : 'light' - data-numposts= theme.facebook_comments.pageSize || 10 - data-order-by= theme.facebook_comments.order_by || 'social' - data-width="100%") - when 'Remark42' - #remark42 - when 'Artalk' - #artalk-wrap diff --git a/themes/butterfly/layout/includes/third-party/comments/js.pug b/themes/butterfly/layout/includes/third-party/comments/js.pug deleted file mode 100644 index baa6223..0000000 --- a/themes/butterfly/layout/includes/third-party/comments/js.pug +++ /dev/null @@ -1,26 +0,0 @@ -each name in theme.comments.use - case name - when 'Valine' - !=partial('includes/third-party/comments/valine', {}, {cache: true}) - when 'Disqus' - include ./disqus.pug - when 'Disqusjs' - include ./disqusjs.pug - when 'Livere' - !=partial('includes/third-party/comments/livere', {}, {cache: true}) - when 'Gitalk' - include ./gitalk.pug - when 'Utterances' - !=partial('includes/third-party/comments/utterances', {}, {cache: true}) - when 'Twikoo' - !=partial('includes/third-party/comments/twikoo', {}, {cache: true}) - when 'Waline' - !=partial('includes/third-party/comments/waline', {}, {cache: true}) - when 'Giscus' - !=partial('includes/third-party/comments/giscus', {}, {cache: true}) - when 'Facebook Comments' - include ./facebook_comments.pug - when 'Remark42' - !=partial('includes/third-party/comments/remark42', {}, {cache: true}) - when 'Artalk' - !=partial('includes/third-party/comments/artalk', {}, {cache: true}) \ No newline at end of file diff --git a/themes/butterfly/layout/includes/third-party/comments/livere.pug b/themes/butterfly/layout/includes/third-party/comments/livere.pug deleted file mode 100644 index b1fe23d..0000000 --- a/themes/butterfly/layout/includes/third-party/comments/livere.pug +++ /dev/null @@ -1,47 +0,0 @@ -- const { use, lazyload } = theme.comments - -script. - (() => { - const isShuoshuo = GLOBAL_CONFIG_SITE.pageType === 'shuoshuo' - - const loadLivere = (el, path) => { - window.livereOptions = { - refer: path || location.pathname - } - - if (isShuoshuo) { - window.shuoshuoComment.destroyLivere = () => { - if (el.children.length) { - el.innerHTML = '' - el.classList.add('no-comment') - } - } - } - - if (typeof LivereTower === 'object') window.LivereTower.init() - else { - (function(d, s) { - var j, e = d.getElementsByTagName(s)[0]; - if (typeof LivereTower === 'function') { return; } - j = d.createElement(s); - j.src = 'https://cdn-city.livere.com/js/embed.dist.js'; - j.async = true; - e.parentNode.insertBefore(j, e); - })(document, 'script'); - } - } - - if (isShuoshuo) { - '!{use[0]}' === 'Livere' - ? window.shuoshuoComment = { loadComment: loadLivere } - : window.loadOtherComment = loadLivere - return - } - - if ('!{use[0]}' === 'Livere' || !!{lazyload}) { - if (!{lazyload}) btf.loadComment(document.getElementById('lv-container'), loadLivere) - else loadLivere() - } else { - window.loadOtherComment = loadLivere - } - })() \ No newline at end of file diff --git a/themes/butterfly/layout/includes/third-party/comments/remark42.pug b/themes/butterfly/layout/includes/third-party/comments/remark42.pug deleted file mode 100644 index 2ef7188..0000000 --- a/themes/butterfly/layout/includes/third-party/comments/remark42.pug +++ /dev/null @@ -1,78 +0,0 @@ -- const { host, siteId, option } = theme.remark42 - -script. - (() => { - const isShuoshuo = GLOBAL_CONFIG_SITE.pageType === 'shuoshuo' - const option = !{JSON.stringify(option)} - - const loadScript = src => { - const script = document.createElement('script') - script.src = src - script.defer = true - document.head.appendChild(script) - } - - const addRemark42 = () => loadScript('!{host}/web/embed.js') - - const getCount = () => document.querySelector('.remark42__counter') && loadScript('!{host}/web/count.js') - - const destroyRemark42 = () => window.remark42Instance && window.remark42Instance.destroy() - - const initRemark42 = remark_config => { - if (window.REMARK42) { - destroyRemark42() - window.remark42Instance = window.REMARK42.createInstance({ - ...remark_config - }) - } - } - - const loadRemark42 = (el, path) => { - if (isShuoshuo) { - window.shuoshuoComment.destroyRemark42 = () => { - destroyRemark42() - if (el.children.length) { - el.innerHTML = '' - el.classList.add('no-comment') - } - } - } - - window.remark_config = { - host: '!{host}', - site_id: '!{siteId}', - theme: document.documentElement.getAttribute('data-theme') === 'dark' ? 'dark' : 'light', - ...option, - url: isShuoshuo ? window.location.origin + path : (option && option.url) || window.location.origin + window.location.pathname - } - - if (window.REMARK42) { - initRemark42(remark_config) - getCount() - } else { - addRemark42() - window.addEventListener('REMARK42::ready', () => { - initRemark42(remark_config) - getCount() - }) - } - } - - const remarkChangeMode = theme => window.REMARK42 && window.REMARK42.changeTheme(theme) - - btf.addGlobalFn('themeChange', remarkChangeMode, 'remark42') - - if (isShuoshuo) { - '!{theme.comments.use[0]}' === 'Remark42' - ? window.shuoshuoComment = { loadComment: loadRemark42 } - : window.loadOtherComment = loadRemark42 - return - } - - if ('!{theme.comments.use[0]}' === 'Remark42' || !!{theme.comments.lazyload}) { - if (!{theme.comments.lazyload}) btf.loadComment(document.getElementById('remark42'), loadRemark42) - else loadRemark42() - } else { - window.loadOtherComment = loadRemark42 - } - })() \ No newline at end of file diff --git a/themes/butterfly/layout/includes/third-party/comments/twikoo.pug b/themes/butterfly/layout/includes/third-party/comments/twikoo.pug deleted file mode 100644 index 94bc424..0000000 --- a/themes/butterfly/layout/includes/third-party/comments/twikoo.pug +++ /dev/null @@ -1,64 +0,0 @@ -- const { envId, region, option } = theme.twikoo -- const { use, lazyload, count } = theme.comments - -script. - (() => { - const isShuoshuo = GLOBAL_CONFIG_SITE.pageType === 'shuoshuo' - const option = !{JSON.stringify(option)} - - const getCount = () => { - const countELement = document.getElementById('twikoo-count') - if(!countELement) return - twikoo.getCommentsCount({ - envId: '!{envId}', - region: '!{region}', - urls: [window.location.pathname], - includeReply: false - }).then(res => { - countELement.textContent = res[0].count - }).catch(err => { - console.error(err) - }) - } - - const init = (el = document, path = location.pathname) => { - twikoo.init({ - el: el.querySelector('#twikoo-wrap'), - envId: '!{envId}', - region: '!{region}', - onCommentLoaded: () => { - btf.loadLightbox(document.querySelectorAll('#twikoo .tk-content img:not(.tk-owo-emotion)')) - }, - ...option, - path: isShuoshuo ? path : (option && option.path) || path - }) - - !{count ? `GLOBAL_CONFIG_SITE.pageType === 'post' && getCount()` : ''} - - isShuoshuo && (window.shuoshuoComment.destroyTwikoo = () => { - if (el.children.length) { - el.innerHTML = '' - el.classList.add('no-comment') - } - }) - } - - const loadTwikoo = (el, path) => { - if (typeof twikoo === 'object') setTimeout(() => init(el, path), 0) - else btf.getScript('!{url_for(theme.asset.twikoo)}').then(() => init(el, path)) - } - - if (isShuoshuo) { - '!{use[0]}' === 'Twikoo' - ? window.shuoshuoComment = { loadComment: loadTwikoo } - : window.loadOtherComment = loadTwikoo - return - } - - if ('!{use[0]}' === 'Twikoo' || !!{lazyload}) { - if (!{lazyload}) btf.loadComment(document.getElementById('twikoo-wrap'), loadTwikoo) - else loadTwikoo() - } else { - window.loadOtherComment = loadTwikoo - } - })() \ No newline at end of file diff --git a/themes/butterfly/layout/includes/third-party/comments/utterances.pug b/themes/butterfly/layout/includes/third-party/comments/utterances.pug deleted file mode 100644 index 7084e77..0000000 --- a/themes/butterfly/layout/includes/third-party/comments/utterances.pug +++ /dev/null @@ -1,63 +0,0 @@ -- const { use, lazyload } = theme.comments -- const { repo, issue_term, light_theme, dark_theme, js, option } = theme.utterances -- const utterancesUrl = js || 'https://utteranc.es/client.js' -- const utterancesOriginUrl = new URL(utterancesUrl).origin - -script. - (() => { - const isShuoshuo = GLOBAL_CONFIG_SITE.pageType === 'shuoshuo' - const option = !{JSON.stringify(option)} - const getUtterancesTheme = theme => theme === 'dark' ? '#{dark_theme}' : '#{light_theme}' - - const loadUtterances = (el = document, key) => { - if (isShuoshuo) { - window.shuoshuoComment.destroyUtterances = () => { - if (el.children.length) { - el.innerHTML = '' - el.classList.add('no-comment') - } - } - } - - const config = { - src: '!{utterancesUrl}', - repo: '!{repo}', - theme: getUtterancesTheme(document.documentElement.getAttribute('data-theme')), - crossorigin: 'anonymous', - async: true, - ...option, - 'issue-term': isShuoshuo ? key : (option && option['issue-term']) || '!{issue_term}' - } - - const ele = document.createElement('script') - Object.entries(config).forEach(([key, value]) => ele.setAttribute(key, value)) - el.querySelector('#utterances-wrap').appendChild(ele) - } - - const changeUtterancesTheme = theme => { - const iframe = document.querySelector('#utterances-wrap iframe') - if (iframe) { - const message = { - type: 'set-theme', - theme: getUtterancesTheme(theme) - }; - iframe.contentWindow.postMessage(message, '!{utterancesOriginUrl}') - } - } - - btf.addGlobalFn('themeChange', changeUtterancesTheme, 'utterances') - - if (isShuoshuo) { - '!{use[0]}' === 'Utterances' - ? window.shuoshuoComment = { loadComment: loadUtterances } - : window.loadOtherComment = loadUtterances - return - } - - if ('!{use[0]}' === 'Utterances' || !!{lazyload}) { - if (!{lazyload}) btf.loadComment(document.getElementById('utterances-wrap'), loadUtterances) - else loadUtterances() - } else { - window.loadOtherComment = loadUtterances - } - })() \ No newline at end of file diff --git a/themes/butterfly/layout/includes/third-party/comments/valine.pug b/themes/butterfly/layout/includes/third-party/comments/valine.pug deleted file mode 100644 index 83ca9b5..0000000 --- a/themes/butterfly/layout/includes/third-party/comments/valine.pug +++ /dev/null @@ -1,60 +0,0 @@ -- const { use, lazyload } = theme.comments -- const { appId, appKey, avatar, serverURLs, visitor, option } = theme.valine - -- let emojiMaps = '""' -if site.data.valine - - emojiMaps = JSON.stringify(site.data.valine) - -script. - (() => { - const isShuoshuo = GLOBAL_CONFIG_SITE.pageType === 'shuoshuo' - const option = !{JSON.stringify(option)} - - const initValine = (el, path) => { - if (isShuoshuo) { - window.shuoshuoComment.destroyValine = () => { - if (el.children.length) { - el.innerHTML = '' - el.classList.add('no-comment') - } - } - } - - const valineConfig = { - el: '#vcomment', - appId: '#{appId}', - appKey: '#{appKey}', - avatar: '#{avatar}', - serverURLs: '#{serverURLs}', - emojiMaps: !{emojiMaps}, - visitor: #{visitor}, - ...option, - path: isShuoshuo ? path : (option && option.path) || window.location.pathname - } - - new Valine(valineConfig) - } - - const loadValine = async (el, path) => { - if (typeof Valine === 'function') { - initValine(el, path) - } else { - await btf.getScript('!{url_for(theme.asset.valine)}') - initValine(el, path) - } - } - - if (isShuoshuo) { - '!{use[0]}' === 'Valine' - ? window.shuoshuoComment = { loadComment: loadValine } - : window.loadOtherComment = loadValine - return - } - - if ('!{use[0]}' === 'Valine' || !!{lazyload}) { - if (!{lazyload}) btf.loadComment(document.getElementById('vcomment'),loadValine) - else setTimeout(loadValine, 0) - } else { - window.loadOtherComment = loadValine - } - })() \ No newline at end of file diff --git a/themes/butterfly/layout/includes/third-party/comments/waline.pug b/themes/butterfly/layout/includes/third-party/comments/waline.pug deleted file mode 100644 index a39c5dc..0000000 --- a/themes/butterfly/layout/includes/third-party/comments/waline.pug +++ /dev/null @@ -1,61 +0,0 @@ -- const { serverURL, option, pageview } = theme.waline -- const { lazyload, count, use } = theme.comments - -script. - (() => { - let initFn = window.walineFn || null - const isShuoshuo = GLOBAL_CONFIG_SITE.pageType === 'shuoshuo' - const option = !{JSON.stringify(option)} - - const destroyWaline = ele => ele.destroy() - - const initWaline = (Fn, el = document, path = window.location.pathname) => { - const waline = Fn({ - el: el.querySelector('#waline-wrap'), - serverURL: '!{serverURL}', - pageview: !{lazyload ? false : pageview}, - dark: 'html[data-theme="dark"]', - comment: !{lazyload ? false : count}, - ...option, - path: isShuoshuo ? path : (option && option.path) || path - }) - - if (isShuoshuo) { - window.shuoshuoComment.destroyWaline = () => { - destroyWaline(waline) - if (el.children.length) { - el.innerHTML = '' - el.classList.add('no-comment') - } - } - } - } - - const loadWaline = (el, path) => { - if (initFn) initWaline(initFn, el, path) - else { - btf.getCSS('!{url_for(theme.asset.waline_css)}') - .then(() => import('!{url_for(theme.asset.waline_js)}')) - .then(({ init }) => { - initFn = init || Waline.init - initWaline(initFn, el, path) - window.walineFn = initFn - }) - } - } - - if (isShuoshuo) { - '!{use[0]}' === 'Waline' - ? window.shuoshuoComment = { loadComment: loadWaline } - : window.loadOtherComment = loadWaline - return - } - - if ('!{use[0]}' === 'Waline' || !!{lazyload}) { - if (!{lazyload}) btf.loadComment(document.getElementById('waline-wrap'),loadWaline) - else setTimeout(loadWaline, 0) - } else { - window.loadOtherComment = loadWaline - } - })() - diff --git a/themes/butterfly/layout/includes/third-party/effect.pug b/themes/butterfly/layout/includes/third-party/effect.pug deleted file mode 100644 index 2addbe4..0000000 --- a/themes/butterfly/layout/includes/third-party/effect.pug +++ /dev/null @@ -1,35 +0,0 @@ -if theme.fireworks && theme.fireworks.enable - canvas.fireworks(mobile=`${theme.fireworks.mobile}`) - script(src=url_for(theme.asset.fireworks)) - -if (theme.canvas_ribbon && theme.canvas_ribbon.enable) - script(defer id="ribbon" src=url_for(theme.asset.canvas_ribbon) size=theme.canvas_ribbon.size - alpha=theme.canvas_ribbon.alpha zIndex=theme.canvas_ribbon.zIndex mobile=`${theme.canvas_ribbon.mobile}` data-click=`${theme.canvas_ribbon.click_to_change}`) - -if (theme.canvas_fluttering_ribbon && theme.canvas_fluttering_ribbon.enable) - script(defer id="fluttering_ribbon" mobile=`${theme.canvas_fluttering_ribbon.mobile}` src=url_for(theme.asset.canvas_fluttering_ribbon)) - -if (theme.canvas_nest && theme.canvas_nest.enable) - script#canvas_nest(defer color=theme.canvas_nest.color opacity=theme.canvas_nest.opacity zIndex=theme.canvas_nest.zIndex count=theme.canvas_nest.count mobile=`${theme.canvas_nest.mobile}` src=url_for(theme.asset.canvas_nest)) - -if theme.activate_power_mode.enable - script(src=url_for(theme.asset.activate_power_mode)) - script. - POWERMODE.colorful = !{theme.activate_power_mode.colorful}; - POWERMODE.shake = !{theme.activate_power_mode.shake}; - POWERMODE.mobile = !{theme.activate_power_mode.mobile}; - document.body.addEventListener('input', POWERMODE); - -//- 鼠標特效 -if theme.click_heart && theme.click_heart.enable - script#click-heart(src=url_for(theme.asset.click_heart) async mobile=`${theme.click_heart.mobile}`) - -if theme.clickShowText && theme.clickShowText.enable - script#click-show-text( - src= url_for(theme.asset.clickShowText) - data-mobile= `${theme.clickShowText.mobile}` - data-text= theme.clickShowText.text.join(",") - data-fontsize= theme.clickShowText.fontSize - data-random= `${theme.clickShowText.random}` - async - ) \ No newline at end of file diff --git a/themes/butterfly/layout/includes/third-party/math/chartjs.pug b/themes/butterfly/layout/includes/third-party/math/chartjs.pug deleted file mode 100644 index e51c621..0000000 --- a/themes/butterfly/layout/includes/third-party/math/chartjs.pug +++ /dev/null @@ -1,91 +0,0 @@ -- const { fontColor, borderColor, scale_ticks_backdropColor } = theme.chartjs - -script. - (() => { - const applyThemeDefaultsConfig = theme => { - if (theme === 'dark-mode') { - Chart.defaults.color = "!{fontColor.dark}" - Chart.defaults.borderColor = "!{borderColor.dark}" - Chart.defaults.scale.ticks.backdropColor = "!{scale_ticks_backdropColor.dark}" - } else { - Chart.defaults.color = "!{fontColor.light}" - Chart.defaults.borderColor = "!{borderColor.light}" - Chart.defaults.scale.ticks.backdropColor = "!{scale_ticks_backdropColor.light}" - } - } - - // Recursively traverse the config object and automatically apply theme-specific color schemes - const applyThemeConfig = (obj, theme) => { - if (typeof obj !== 'object' || obj === null) return - - Object.keys(obj).forEach(key => { - const value = obj[key] - // If the property is an object and has theme-specific options, apply them - if (typeof value === 'object' && value !== null) { - if (value[theme]) { - obj[key] = value[theme] // Apply the value for the current theme - } else { - // Recursively process child objects - applyThemeConfig(value, theme) - } - } - }) - } - - const runChartJS = ele => { - window.loadChartJS = true - - Array.from(ele).forEach((item, index) => { - const chartSrc = item.firstElementChild - const chartID = item.getAttribute('data-chartjs-id') || ('chartjs-' + index) // Use custom ID or default ID - const width = item.getAttribute('data-width') - const existingCanvas = document.getElementById(chartID) - - // If a canvas already exists, remove it to avoid rendering duplicates - if (existingCanvas) { - existingCanvas.parentNode.remove() - } - - const chartDefinition = chartSrc.textContent - const canvas = document.createElement('canvas') - canvas.id = chartID - - const div = document.createElement('div') - div.className = 'chartjs-wrap' - - if (width) { - div.style.width = width - } - - div.appendChild(canvas) - chartSrc.insertAdjacentElement('afterend', div) - - const ctx = document.getElementById(chartID).getContext('2d') - - const config = JSON.parse(chartDefinition) - - const theme = document.documentElement.getAttribute('data-theme') === 'dark' ? 'dark-mode' : 'light-mode' - - // Set default styles (initial setup) - applyThemeDefaultsConfig(theme) - - // Automatically traverse the config and apply dual-mode color schemes - applyThemeConfig(config, theme) - - new Chart(ctx, config) - }) - } - - const loadChartJS = () => { - const chartJSEle = document.querySelectorAll('#article-container .chartjs-container') - if (chartJSEle.length === 0) return - - window.loadChartJS ? runChartJS(chartJSEle) : btf.getScript('!{url_for(theme.asset.chartjs)}').then(() => runChartJS(chartJSEle)) - } - - // Listen for theme change events - btf.addGlobalFn('themeChange', loadChartJS, 'chartjs') - btf.addGlobalFn('encrypt', loadChartJS, 'chartjs') - - window.pjax ? loadChartJS() : document.addEventListener('DOMContentLoaded', loadChartJS) - })() diff --git a/themes/butterfly/layout/includes/third-party/math/index.pug b/themes/butterfly/layout/includes/third-party/math/index.pug deleted file mode 100644 index a5bdf41..0000000 --- a/themes/butterfly/layout/includes/third-party/math/index.pug +++ /dev/null @@ -1,14 +0,0 @@ -case theme.math.use - when 'mathjax' - if (theme.math.per_page && (['post','page'].includes(globalPageType))) || page.mathjax - include ./mathjax.pug - - when 'katex' - if (theme.math.per_page && (['post','page'].includes(globalPageType))) || page.katex - include ./katex.pug - -if theme.mermaid.enable - include ./mermaid.pug - -if theme.chartjs.enable - include ./chartjs.pug \ No newline at end of file diff --git a/themes/butterfly/layout/includes/third-party/math/katex.pug b/themes/butterfly/layout/includes/third-party/math/katex.pug deleted file mode 100644 index 6e429a5..0000000 --- a/themes/butterfly/layout/includes/third-party/math/katex.pug +++ /dev/null @@ -1,16 +0,0 @@ -script. - (async () => { - const showKatex = () => { - document.querySelectorAll('#article-container .katex').forEach(el => el.classList.add('katex-show')) - } - - if (!window.katex_js_css) { - window.katex_js_css = true - await btf.getCSS('!{url_for(theme.asset.katex)}') - if (!{theme.math.katex.copy_tex}) { - await btf.getScript('!{url_for(theme.asset.katex_copytex)}') - } - } - - showKatex() - })() \ No newline at end of file diff --git a/themes/butterfly/layout/includes/third-party/math/mathjax.pug b/themes/butterfly/layout/includes/third-party/math/mathjax.pug deleted file mode 100644 index e937dbc..0000000 --- a/themes/butterfly/layout/includes/third-party/math/mathjax.pug +++ /dev/null @@ -1,47 +0,0 @@ -//- Mathjax 3 -- const { tags, enableMenu } = theme.math.mathjax -script. - (() => { - const loadMathjax = () => { - if (!window.MathJax) { - window.MathJax = { - tex: { - inlineMath: [['$', '$'], ['\\(', '\\)']], - tags: '!{tags}', - }, - chtml: { - scale: 1.1 - }, - options: { - enableMenu: !{enableMenu}, - renderActions: { - findScript: [10, doc => { - for (const node of document.querySelectorAll('script[type^="math/tex"]')) { - const display = !!node.type.match(/; *mode=display/) - const math = new doc.options.MathItem(node.textContent, doc.inputJax[0], display) - const text = document.createTextNode('') - node.parentNode.replaceChild(text, node) - math.start = {node: text, delim: '', n: 0} - math.end = {node: text, delim: '', n: 0} - doc.math.push(math) - } - }, ''] - } - } - } - - const script = document.createElement('script') - script.src = '!{url_for(theme.asset.mathjax)}' - script.id = 'MathJax-script' - script.async = true - document.head.appendChild(script) - } else { - MathJax.startup.document.state(0) - MathJax.texReset() - MathJax.typesetPromise() - } - } - - btf.addGlobalFn('encrypt', loadMathjax, 'mathjax') - window.pjax ? loadMathjax() : window.addEventListener('load', loadMathjax) - })() \ No newline at end of file diff --git a/themes/butterfly/layout/includes/third-party/math/mermaid.pug b/themes/butterfly/layout/includes/third-party/math/mermaid.pug deleted file mode 100644 index d28049f..0000000 --- a/themes/butterfly/layout/includes/third-party/math/mermaid.pug +++ /dev/null @@ -1,51 +0,0 @@ -script. - (() => { - const runMermaid = ele => { - window.loadMermaid = true - const theme = document.documentElement.getAttribute('data-theme') === 'dark' ? '!{theme.mermaid.theme.dark}' : '!{theme.mermaid.theme.light}' - - ele.forEach((item, index) => { - const mermaidSrc = item.firstElementChild - const mermaidThemeConfig = `%%{init:{ 'theme':'${theme}'}}%%\n` - const mermaidID = `mermaid-${index}` - const mermaidDefinition = mermaidThemeConfig + mermaidSrc.textContent - - const renderFn = mermaid.render(mermaidID, mermaidDefinition) - const renderMermaid = svg => { - mermaidSrc.insertAdjacentHTML('afterend', svg) - } - - // mermaid v9 and v10 compatibility - typeof renderFn === 'string' ? renderMermaid(renderFn) : renderFn.then(({ svg }) => renderMermaid(svg)) - }) - } - - const codeToMermaid = () => { - const codeMermaidEle = document.querySelectorAll('pre > code.mermaid') - if (codeMermaidEle.length === 0) return - - codeMermaidEle.forEach(ele => { - const preEle = document.createElement('pre') - preEle.className = 'mermaid-src' - preEle.hidden = true - preEle.textContent = ele.textContent - const newEle = document.createElement('div') - newEle.className = 'mermaid-wrap' - newEle.appendChild(preEle) - ele.parentNode.replaceWith(newEle) - }) - } - - const loadMermaid = () => { - if (!{theme.mermaid.code_write}) codeToMermaid() - const $mermaid = document.querySelectorAll('#article-container .mermaid-wrap') - if ($mermaid.length === 0) return - - const runMermaidFn = () => runMermaid($mermaid) - btf.addGlobalFn('themeChange', runMermaidFn, 'mermaid') - window.loadMermaid ? runMermaidFn() : btf.getScript('!{url_for(theme.asset.mermaid)}').then(runMermaidFn) - } - - btf.addGlobalFn('encrypt', loadMermaid, 'mermaid') - window.pjax ? loadMermaid() : document.addEventListener('DOMContentLoaded', loadMermaid) - })() \ No newline at end of file diff --git a/themes/butterfly/layout/includes/third-party/newest-comments/artalk.pug b/themes/butterfly/layout/includes/third-party/newest-comments/artalk.pug deleted file mode 100644 index cf51638..0000000 --- a/themes/butterfly/layout/includes/third-party/newest-comments/artalk.pug +++ /dev/null @@ -1,67 +0,0 @@ -- const { server, site, option } = theme.artalk -- const avatarCdn = (option !== null && option.gravatar && option.gravatar.mirror) || '' -- const avatarDefault = (option !== null && option.gravatar && (option.gravatar.params || option.gravatar.default)) || '' - -!= partial("includes/third-party/newest-comments/common.pug", {}, { cache: true }) - -script. - window.addEventListener('load', () => { - const keyName = 'artalk-newest-comments' - const { changeContent, generateHtml, run } = window.newestComments - - const getAvatarValue = async () => { - const predefinedAvatarCdn = '!{avatarCdn}' - const predefinedAvatarDefault = '!{avatarDefault}' - - const avatarDefaultFormat = e => e.startsWith('d=') ? e : `d=${e}` - - if (predefinedAvatarCdn && predefinedAvatarDefault) { - return { avatarCdn: predefinedAvatarCdn, avatarDefault: avatarDefaultFormat(predefinedAvatarDefault) } - } - - try { - const res = await fetch('!{server}/api/v2/conf') - const result = await res.json() - const { mirror, params, default: defaults } = result.frontend_conf.gravatar - const avatarCdn = predefinedAvatarCdn || mirror - let avatarDefault = avatarDefaultFormat(predefinedAvatarDefault || params || defaults) - return { avatarCdn, avatarDefault} - } catch (e) { - console.error(e) - return { avatarCdn: predefinedAvatarCdn, avatarDefault: avatarDefaultFormat(predefinedAvatarDefault) } - } - } - - const searchParams = new URLSearchParams({ - 'site_name': '!{site}', - 'limit': '!{newestCommentsLimit * 2}', // Fetch more comments to filter pending comments - }) - - const getComment = async (ele) => { - try { - const res = await fetch(`!{server}/api/v2/stats/latest_comments?${searchParams}`) - const result = await res.json() - const { avatarCdn, avatarDefault } = await getAvatarValue() - const artalk = result.data - .filter(e => !e.is_pending) // Filter pending comments - .slice(0, !{newestCommentsLimit}) // Limit the number of comments - .map(e => { - const avatar = avatarCdn && e.email_encrypted ? `${avatarCdn}${e.email_encrypted}?${avatarDefault}` : '' - return { - 'avatar': avatar, - 'content': changeContent(e.content_marked), - 'nick': e.nick, - 'url': e.page_url, - 'date': e.date, - } - }) - btf.saveToLocal.set(keyName, JSON.stringify(artalk), !{theme.aside.card_newest_comments.storage}/(60*24)) - generateHtml(artalk, ele) - } catch (e) { - console.log(e) - ele.textContent= "!{_p('aside.card_newest_comments.error')}" - } - } - - run(keyName, getComment) - }) \ No newline at end of file diff --git a/themes/butterfly/layout/includes/third-party/newest-comments/common.pug b/themes/butterfly/layout/includes/third-party/newest-comments/common.pug deleted file mode 100644 index 9a0814d..0000000 --- a/themes/butterfly/layout/includes/third-party/newest-comments/common.pug +++ /dev/null @@ -1,61 +0,0 @@ -script. - window.newestComments = { - changeContent: content => { - if (content === '') return content - - content = content.replace(/]+>/ig, '[!{_p("aside.card_newest_comments.image")}]') // replace image link - content = content.replace(/]+?href=["']?([^"']+)["']?[^>]*>([^<]+)<\/a>/gi, '[!{_p("aside.card_newest_comments.link")}]') // replace url - content = content.replace(/
.*?<\/pre>/gi, '[!{_p("aside.card_newest_comments.code")}]') // replace code
-      content = content.replace(/.*?<\/code>/gi, '[!{_p("aside.card_newest_comments.code")}]') // replace code
-      content = content.replace(/<[^>]+>/g, "") // remove html tag
-
-      if (content.length > 150) {
-        content = content.substring(0, 150) + '...'
-      }
-      return content
-    },
-
-    generateHtml: (array, ele) => {
-      let result = ''
-
-      if (array.length) {
-        for (let i = 0; i < array.length; i++) {
-          result += '
' - - if (!{theme.aside.card_newest_comments.avatar} && array[i].avatar) { - const imgAttr = '!{theme.lazyload.enable && !theme.lazyload.native ? "data-lazy-src" : "src"}' - const lazyloadNative = '!{theme.lazyload.enable && theme.lazyload.native ? "loading=\"lazy\"" : ""}' - result += `${array[i].nick}` - } - - result += `
- ${array[i].content} -
${array[i].nick} /
-
` - } - } else { - result += '!{_p("aside.card_newest_comments.zero")}' - } - - ele.innerHTML = result - window.lazyLoadInstance && window.lazyLoadInstance.update() - window.pjax && window.pjax.refresh(ele) - }, - - newestCommentInit: (name, getComment) => { - const $dom = document.querySelector('#card-newest-comments .aside-list') - if ($dom) { - const data = btf.saveToLocal.get(name) - if (data) { - newestComments.generateHtml(JSON.parse(data), $dom) - } else { - getComment($dom) - } - } - }, - - run: (name, getComment) => { - newestComments.newestCommentInit(name, getComment) - btf.addGlobalFn('pjaxComplete', () => newestComments.newestCommentInit(name, getComment), name) - } - } \ No newline at end of file diff --git a/themes/butterfly/layout/includes/third-party/newest-comments/disqus-comment.pug b/themes/butterfly/layout/includes/third-party/newest-comments/disqus-comment.pug deleted file mode 100644 index 040d5d0..0000000 --- a/themes/butterfly/layout/includes/third-party/newest-comments/disqus-comment.pug +++ /dev/null @@ -1,34 +0,0 @@ -!= partial("includes/third-party/newest-comments/common.pug", {}, { cache: true }) - -script. - window.addEventListener('load', () => { - const keyName = 'disqus-newest-comments' - const { changeContent, generateHtml, run } = window.newestComments - - const getComment = ele => { - fetch('https://disqus.com/api/3.0/forums/listPosts.json?forum=!{forum}&related=thread&limit=!{newestCommentsLimit}&api_key=!{apiKey}') - .then(response => response.json()) - .then(data => { - const disqusArray = data.response.map(item => { - return { - 'avatar': item.author.avatar.cache, - 'content': changeContent(item.message), - 'nick': item.author.name, - 'url': item.url, - 'date': item.createdAt - } - }) - - btf.saveToLocal.set(keyName, JSON.stringify(disqusArray), !{theme.aside.card_newest_comments.storage}/(60*24)) - generateHtml(disqusArray, ele) - }).catch(e => { - console.error(e) - ele.textContent= "!{_p('aside.card_newest_comments.error')}" - }) - } - - run(keyName, getComment) - }) - - - diff --git a/themes/butterfly/layout/includes/third-party/newest-comments/github-issues.pug b/themes/butterfly/layout/includes/third-party/newest-comments/github-issues.pug deleted file mode 100644 index 5612c8a..0000000 --- a/themes/butterfly/layout/includes/third-party/newest-comments/github-issues.pug +++ /dev/null @@ -1,62 +0,0 @@ -!= partial("includes/third-party/newest-comments/common.pug", {}, { cache: true }) - -script. - window.addEventListener('load', () => { - const keyName = 'github-newest-comments' - const { changeContent, generateHtml, run } = window.newestComments - - const findTrueUrl = (array, ele) => { - Promise.all(array.map(item => - fetch(item.url).then(resp => resp.json()).then(data => { - let urlArray = data.body ? data.body.match(/(https?:\/\/)?([\da-z\.-]+)\.([a-z\.]{2,6})([\/\w \.-]*)*\/?/ig) : [] - if (!Array.isArray(urlArray) || urlArray.length === 0) { - urlArray = [`${data.html_url}`] - } - if (data.user.login === 'utterances-bot') { - return urlArray.pop() - } else { - return urlArray.shift() - } - }) - )).then(res => { - array = array.map((i,index)=> { - return { - ...i, - url: res[index] - } - }) - - btf.saveToLocal.set(keyName, JSON.stringify(array), !{theme.aside.card_newest_comments.storage}/(60*24)) - generateHtml(array, ele) - }); - } - - const getComment = ele => { - fetch('https://api.github.com/repos/!{userRepo}/issues/comments?sort=updated&direction=desc&per_page=!{newestCommentsLimit}&page=1',{ - "headers": { - Accept: 'application/vnd.github.v3.html+json' - } - }) - .then(response => response.json()) - .then(data => { - const githubArray = data.map(item => { - return { - 'avatar': item.user.avatar_url, - 'content': changeContent(item.body_html || item.body), - 'nick': item.user.login, - 'url': item.issue_url, - 'date': item.updated_at - } - }) - findTrueUrl(githubArray, ele) - }).catch(e => { - console.error(e) - ele.textContent= "!{_p('aside.card_newest_comments.error')}" - }) - } - run(keyName, getComment) - }) - - - - diff --git a/themes/butterfly/layout/includes/third-party/newest-comments/index.pug b/themes/butterfly/layout/includes/third-party/newest-comments/index.pug deleted file mode 100644 index eedb4e7..0000000 --- a/themes/butterfly/layout/includes/third-party/newest-comments/index.pug +++ /dev/null @@ -1,34 +0,0 @@ -- let { use } = theme.comments - -if use - - - let forum,apiKey,userRepo - let { limit:newestCommentsLimit } = theme.aside.card_newest_comments - if (newestCommentsLimit > 10 || newestCommentsLimit < 1) newestCommentsLimit = 6 - - case use[0] - when 'Valine' - include ./valine.pug - when 'Waline' - include ./waline.pug - when 'Twikoo' - include ./twikoo-comment.pug - when 'Disqus' - - forum = theme.disqus.shortname - - apiKey = theme.disqus.apikey - include ./disqus-comment.pug - when 'Disqusjs' - - forum = theme.disqusjs.shortname - - apiKey = theme.disqusjs.apikey - include ./disqus-comment.pug - when 'Gitalk' - - let { repo,owner } = theme.gitalk - - userRepo = owner + '/' + repo - include ./github-issues.pug - when 'Utterances' - - userRepo = theme.utterances.repo - include ./github-issues.pug - when 'Remark42' - include ./remark42.pug - when 'Artalk' - include ./artalk.pug \ No newline at end of file diff --git a/themes/butterfly/layout/includes/third-party/newest-comments/remark42.pug b/themes/butterfly/layout/includes/third-party/newest-comments/remark42.pug deleted file mode 100644 index a335968..0000000 --- a/themes/butterfly/layout/includes/third-party/newest-comments/remark42.pug +++ /dev/null @@ -1,31 +0,0 @@ -- const { host, siteId } = theme.remark42 -!= partial("includes/third-party/newest-comments/common.pug", {}, { cache: true }) - -script. - window.addEventListener('load', () => { - const keyName = 'remark42-newest-comments' - const { changeContent, generateHtml, run } = window.newestComments - - const getComment = ele => { - fetch('!{host}/api/v1/last/!{newestCommentsLimit}?site=!{siteId}') - .then(response => response.json()) - .then(data => { - const remark42 = data.map(e => { - return { - 'avatar': e.user.picture, - 'content': changeContent(e.text), - 'nick': e.user.name, - 'url': e.locator.url, - 'date': e.time, - } - }) - btf.saveToLocal.set(keyName, JSON.stringify(remark42), !{theme.aside.card_newest_comments.storage}/(60*24)) - generateHtml(remark42, ele) - }).catch(e => { - console.error(e) - ele.textContent= "!{_p('aside.card_newest_comments.error')}" - }) - } - - run(keyName, getComment) - }) diff --git a/themes/butterfly/layout/includes/third-party/newest-comments/twikoo-comment.pug b/themes/butterfly/layout/includes/third-party/newest-comments/twikoo-comment.pug deleted file mode 100644 index 250efa8..0000000 --- a/themes/butterfly/layout/includes/third-party/newest-comments/twikoo-comment.pug +++ /dev/null @@ -1,45 +0,0 @@ -!= partial("includes/third-party/newest-comments/common.pug", {}, { cache: true }) - -script. - window.addEventListener('load', () => { - const keyName = 'twikoo-newest-comments' - const { changeContent, generateHtml, run } = window.newestComments - - const getComment = ele => { - const runTwikoo = () => { - twikoo.getRecentComments({ - envId: '!{theme.twikoo.envId}', - region: '!{theme.twikoo.region}', - pageSize: !{newestCommentsLimit}, - includeReply: true - }).then(res => { - const twikooArray = res.map(e => { - return { - 'content': changeContent(e.comment), - 'avatar': e.avatar, - 'nick': e.nick, - 'url': e.url + '#' + e.id, - 'date': new Date(e.created).toISOString() - } - }) - - btf.saveToLocal.set(keyName, JSON.stringify(twikooArray), !{theme.aside.card_newest_comments.storage}/(60*24)) - generateHtml(twikooArray, ele) - }).catch(err => { - console.error(err) - ele.textContent= "!{_p('aside.card_newest_comments.error')}" - }) - } - - if (typeof twikoo === 'object') { - runTwikoo() - } else { - btf.getScript('!{url_for(theme.asset.twikoo)}').then(runTwikoo) - } - } - - run(keyName, getComment) - }) - - - diff --git a/themes/butterfly/layout/includes/third-party/newest-comments/valine.pug b/themes/butterfly/layout/includes/third-party/newest-comments/valine.pug deleted file mode 100644 index 6d7742c..0000000 --- a/themes/butterfly/layout/includes/third-party/newest-comments/valine.pug +++ /dev/null @@ -1,51 +0,0 @@ -- let default_avatar = theme.valine.avatar - -script(src=url_for(theme.asset.blueimp_md5)) -!= partial("includes/third-party/newest-comments/common.pug", {}, { cache: true }) - -script. - window.addEventListener('load', () => { - const keyName = 'valine-newest-comments' - const { changeContent, generateHtml, run } = window.newestComments - - const getIcon = (icon, mail) => { - if (icon) return icon - let defaultIcon = '!{ default_avatar ? `?d=${default_avatar}` : ''}' - let iconUrl = `https://gravatar.loli.net/avatar/${md5(mail.toLowerCase()) + defaultIcon}` - return iconUrl - } - - const getComment = ele => { - const serverURL = '!{theme.valine.serverURLs || `https://${theme.valine.appId.substring(0,8)}.api.lncldglobal.com` }' - - var settings = { - "method": "GET", - "headers": { - "X-LC-Id": '!{theme.valine.appId}', - "X-LC-Key": '!{theme.valine.appKey}', - "Content-Type": "application/json" - }, - } - - fetch(`${serverURL}/1.1/classes/Comment?limit=!{newestCommentsLimit}&order=-createdAt`,settings) - .then(response => response.json()) - .then(data => { - const valineArray = data.results.map(e => { - return { - 'avatar': getIcon(e.QQAvatar, e.mail), - 'content': changeContent(e.comment), - 'nick': e.nick, - 'url': e.url + '#' + e.objectId, - 'date': e.updatedAt, - } - }) - btf.saveToLocal.set(keyName, JSON.stringify(valineArray), !{theme.aside.card_newest_comments.storage}/(60*24)) - generateHtml(valineArray, ele) - }).catch(e => { - console.error(e) - ele.textContent= "!{_p('aside.card_newest_comments.error')}" - }) - } - - run(keyName, getComment) - }) diff --git a/themes/butterfly/layout/includes/third-party/newest-comments/waline.pug b/themes/butterfly/layout/includes/third-party/newest-comments/waline.pug deleted file mode 100644 index b8117ef..0000000 --- a/themes/butterfly/layout/includes/third-party/newest-comments/waline.pug +++ /dev/null @@ -1,32 +0,0 @@ -- const serverURL = theme.waline.serverURL.replace(/\/$/, '') - -!= partial("includes/third-party/newest-comments/common.pug", {}, { cache: true }) - -script. - window.addEventListener('load', () => { - const keyName = 'waline-newest-comments' - const { changeContent, generateHtml, run } = window.newestComments - - const getComment = async (ele) => { - try { - const res = await fetch('!{serverURL}/api/comment?type=recent&count=!{newestCommentsLimit}') - const result = await res.json() - const walineArray = result.data.map(e => { - return { - 'content': changeContent(e.comment), - 'avatar': e.avatar, - 'nick': e.nick, - 'url': e.url + '#' + e.objectId, - 'date': e.time || e.insertedAt - } - }) - btf.saveToLocal.set(keyName, JSON.stringify(walineArray), !{theme.aside.card_newest_comments.storage}/(60*24)) - generateHtml(walineArray, ele) - } catch (err) { - console.error(err) - ele.textContent= "!{_p('aside.card_newest_comments.error')}" - } - } - - run(keyName, getComment) - }) diff --git a/themes/butterfly/layout/includes/third-party/pjax.pug b/themes/butterfly/layout/includes/third-party/pjax.pug deleted file mode 100644 index 94ec038..0000000 --- a/themes/butterfly/layout/includes/third-party/pjax.pug +++ /dev/null @@ -1,68 +0,0 @@ -- var pjaxExclude = 'a:not([target="_blank"])' -if theme.pjax.exclude - each val in theme.pjax.exclude - - pjaxExclude += `:not([href="${val}"])` - -- let pjaxSelectors = ['head > title', '#config-diff', '#body-wrap', '#rightside-config-hide', '#rightside-config-show', '.js-pjax'] - -- let choose = theme.comments.use -if choose - if choose.includes('Livere') || choose.includes('Utterances') || choose.includes('Giscus') - - pjaxSelectors.unshift('link[rel="canonical"]') - if theme.Open_Graph_meta.enable - - pjaxSelectors.unshift('meta[property="og:image"]', 'meta[property="og:title"]', 'meta[property="og:url"]', 'meta[property="og:description"]') - else - - pjaxSelectors.unshift('meta[name="description"]') - -script(src=url_for(theme.asset.pjax)) -script. - (() => { - const pjaxSelectors = !{JSON.stringify(pjaxSelectors)} - - window.pjax = new Pjax({ - elements: '!{pjaxExclude}', - selectors: pjaxSelectors, - cacheBust: false, - analytics: !{theme.google_analytics ? true : false}, - scrollRestoration: false - }) - - const triggerPjaxFn = (val) => { - if (!val) return - Object.values(val).forEach(fn => fn()) - } - - document.addEventListener('pjax:send', () => { - // removeEventListener - btf.removeGlobalFnEvent('pjaxSendOnce') - btf.removeGlobalFnEvent('themeChange') - - // reset readmode - const $bodyClassList = document.body.classList - if ($bodyClassList.contains('read-mode')) $bodyClassList.remove('read-mode') - - triggerPjaxFn(window.globalFn.pjaxSend) - }) - - document.addEventListener('pjax:complete', () => { - btf.removeGlobalFnEvent('pjaxCompleteOnce') - document.querySelectorAll('script[data-pjax]').forEach(item => { - const newScript = document.createElement('script') - const content = item.text || item.textContent || item.innerHTML || "" - Array.from(item.attributes).forEach(attr => newScript.setAttribute(attr.name, attr.value)) - newScript.appendChild(document.createTextNode(content)) - item.parentNode.replaceChild(newScript, item) - }) - - triggerPjaxFn(window.globalFn.pjaxComplete) - }) - - document.addEventListener('pjax:error', e => { - if (e.request.status === 404) { - const usePjax = !{theme.pjax && theme.pjax.enable} - !{theme.error_404 && theme.error_404.enable} - ? (usePjax ? pjax.loadUrl('!{url_for("/404.html")}') : window.location.href = '!{url_for("/404.html")}') - : window.location.href = e.request.responseURL - } - }) - })() diff --git a/themes/butterfly/layout/includes/third-party/prismjs.pug b/themes/butterfly/layout/includes/third-party/prismjs.pug deleted file mode 100644 index b8bc784..0000000 --- a/themes/butterfly/layout/includes/third-party/prismjs.pug +++ /dev/null @@ -1,23 +0,0 @@ -- const { prismjs_js, prismjs_autoloader, prismjs_lineNumber_js } = theme.asset -- const { prismjs, syntax_highlighter } = config -- const { enable, preprocess, line_number } = prismjs - -if (syntax_highlighter === 'prismjs' || enable) && !preprocess - script. - (() => { - window.Prism = window.Prism || {} - window.Prism.manual = true - - const highlightAll = () => { - window.Prism.highlightAll() - } - - window.addEventListener('load', highlightAll) - btf.addGlobalFn('pjaxComplete', highlightAll, 'prismjs') - btf.addGlobalFn('encrypt', highlightAll, 'prismjs') - })() - - script(src=url_for(prismjs_js)) - script(src=url_for(prismjs_autoloader)) - if (line_number) - script(src=url_for(prismjs_lineNumber_js)) \ No newline at end of file diff --git a/themes/butterfly/layout/includes/third-party/search/algolia.pug b/themes/butterfly/layout/includes/third-party/search/algolia.pug deleted file mode 100644 index c0dd215..0000000 --- a/themes/butterfly/layout/includes/third-party/search/algolia.pug +++ /dev/null @@ -1,22 +0,0 @@ -#algolia-search - .search-dialog - nav.search-nav - span.search-dialog-title= _p('search.title') - button.search-close-button - i.fas.fa-times - - .search-wrap - #algolia-search-input - hr - #algolia-search-results - #algolia-hits - #algolia-pagination - #algolia-info - .algolia-stats - .algolia-poweredBy - - #search-mask - - script(src=url_for(theme.asset.algolia_search)) - script(src=url_for(theme.asset.instantsearch)) - script(src=url_for(theme.asset.algolia_js)) \ No newline at end of file diff --git a/themes/butterfly/layout/includes/third-party/search/docsearch.pug b/themes/butterfly/layout/includes/third-party/search/docsearch.pug deleted file mode 100644 index bc23afa..0000000 --- a/themes/butterfly/layout/includes/third-party/search/docsearch.pug +++ /dev/null @@ -1,29 +0,0 @@ -- const { placeholder, docsearch: { appId, apiKey, indexName, option } } = theme.search - -.docsearch-wrap - #docsearch(style="display:none") - link(rel="stylesheet" href=url_for(theme.asset.docsearch_css)) - script(src=url_for(theme.asset.docsearch_js)) - script. - (() => { - docsearch(Object.assign({ - appId: '!{appId}', - apiKey: '!{apiKey}', - indexName: '!{indexName}', - container: '#docsearch', - placeholder: '!{ placeholder || _p("search.input_placeholder")}', - }, !{JSON.stringify(option)})) - - const handleClick = () => { - document.querySelector('.DocSearch-Button').click() - } - - const searchClickFn = () => { - btf.addEventListenerPjax(document.querySelector('#search-button > .search'), 'click', handleClick) - } - - searchClickFn() - window.addEventListener('pjax:complete', searchClickFn) - })() - - diff --git a/themes/butterfly/layout/includes/third-party/search/index.pug b/themes/butterfly/layout/includes/third-party/search/index.pug deleted file mode 100644 index f96f4f8..0000000 --- a/themes/butterfly/layout/includes/third-party/search/index.pug +++ /dev/null @@ -1,7 +0,0 @@ -case theme.search.use - when 'algolia_search' - include ./algolia.pug - when 'local_search' - include ./local-search.pug - when 'docsearch' - include ./docsearch.pug \ No newline at end of file diff --git a/themes/butterfly/layout/includes/third-party/search/local-search.pug b/themes/butterfly/layout/includes/third-party/search/local-search.pug deleted file mode 100644 index 6c28dfd..0000000 --- a/themes/butterfly/layout/includes/third-party/search/local-search.pug +++ /dev/null @@ -1,22 +0,0 @@ -#local-search - .search-dialog - nav.search-nav - span.search-dialog-title= _p('search.title') - span#loading-status - button.search-close-button - i.fas.fa-times - - #loading-database.text-center - i.fas.fa-spinner.fa-pulse - span= ' ' + _p("search.load_data") - - .search-wrap - #local-search-input - .local-search-box - input(placeholder=theme.search.placeholder || _p("search.input_placeholder") type="text").local-search-box--input - hr - #local-search-results - #local-search-stats-wrap - #search-mask - - script(src=url_for(theme.asset.local_search)) \ No newline at end of file diff --git a/themes/butterfly/layout/includes/third-party/share/addtoany.pug b/themes/butterfly/layout/includes/third-party/share/addtoany.pug deleted file mode 100644 index 2dfbdb5..0000000 --- a/themes/butterfly/layout/includes/third-party/share/addtoany.pug +++ /dev/null @@ -1,10 +0,0 @@ -.addtoany - .a2a_kit.a2a_kit_size_32.a2a_default_style - - let addtoanyItem = theme.share.addtoany.item.split(',') - each name in addtoanyItem - a(class="a2a_button_" + name) - - a.a2a_dd(href="https://www.addtoany.com/share") -script(async src='https://static.addtoany.com/menu/page.js') - - diff --git a/themes/butterfly/layout/includes/third-party/share/index.pug b/themes/butterfly/layout/includes/third-party/share/index.pug deleted file mode 100644 index c385bac..0000000 --- a/themes/butterfly/layout/includes/third-party/share/index.pug +++ /dev/null @@ -1,9 +0,0 @@ -- const { use } = theme.share - -if use - .post-share - case use - when 'addtoany' - !=partial('includes/third-party/share/addtoany', {}, {cache: true}) - when 'sharejs' - include ./share-js.pug \ No newline at end of file diff --git a/themes/butterfly/layout/includes/third-party/share/share-js.pug b/themes/butterfly/layout/includes/third-party/share/share-js.pug deleted file mode 100644 index 403ccfe..0000000 --- a/themes/butterfly/layout/includes/third-party/share/share-js.pug +++ /dev/null @@ -1,4 +0,0 @@ -- const coverVal = page.cover_type === 'img' ? page.cover : theme.avatar.img -.social-share(data-image=url_for(coverVal) data-sites= theme.share.sharejs.sites) -link(rel='stylesheet' href=url_for(theme.asset.sharejs_css) media="print" onload="this.media='all'") -script(src=url_for(theme.asset.sharejs) defer) \ No newline at end of file diff --git a/themes/butterfly/layout/includes/third-party/subtitle.pug b/themes/butterfly/layout/includes/third-party/subtitle.pug deleted file mode 100644 index 8dbb534..0000000 --- a/themes/butterfly/layout/includes/third-party/subtitle.pug +++ /dev/null @@ -1,113 +0,0 @@ -- const { effect, source, sub, typed_option } = theme.subtitle -- let subContent = sub || new Array() - -script. - window.typedJSFn = { - init: str => { - window.typed = new Typed('#subtitle', Object.assign({ - strings: str, - startDelay: 300, - typeSpeed: 150, - loop: true, - backSpeed: 50, - }, !{JSON.stringify(typed_option)})) - }, - run: subtitleType => { - if (!{effect}) { - if (typeof Typed === 'function') { - subtitleType() - } else { - btf.getScript('!{url_for(theme.asset.typed)}').then(subtitleType) - } - } else { - subtitleType() - } - }, - processSubtitle: (content, extraContents = []) => { - if (!{effect}) { - const sub = !{JSON.stringify(subContent)}.slice() - - if (extraContents.length > 0) { - sub.unshift(...extraContents) - } - - if (typeof content === 'string') { - sub.unshift(content) - } else if (Array.isArray(content)) { - sub.unshift(...content) - } - - sub.length > 0 && typedJSFn.init(sub) - } else { - document.getElementById('subtitle').textContent = typeof content === 'string' ? content : - (Array.isArray(content) && content.length > 0 ? content[0] : '') - } - } - } - btf.addGlobalFn('pjaxSendOnce', () => { typed.destroy() }, 'typedDestroy') - -case source - when 1 - script. - function subtitleType () { - fetch('https://v1.hitokoto.cn') - .then(response => response.json()) - .then(data => { - const from = '出自 ' + data.from - typedJSFn.processSubtitle(data.hitokoto, [from]) - }) - .catch(err => { - console.error('Failed to get the Hitokoto API:', err) - typedJSFn.processSubtitle(!{JSON.stringify(subContent)}) - }) - } - typedJSFn.run(subtitleType) - - when 2 - script. - function subtitleType () { - fetch('https://v.api.aa1.cn/api/yiyan/index.php') - .then(response => response.text()) - .then(data => { - const reg = /

(.*?)<\/p>/g - const result = reg.exec(data) - if (result && result[1]) { - typedJSFn.processSubtitle(result[1]) - } else { - throw new Error('Failed to parse the return value of the Yiyan API') - } - }) - .catch(err => { - console.error('Failed to get the Yiyan API:', err) - typedJSFn.processSubtitle(!{JSON.stringify(subContent.length)}) - }) - } - typedJSFn.run(subtitleType) - - when 3 - script. - function subtitleType () { - btf.getScript('https://sdk.jinrishici.com/v2/browser/jinrishici.js') - .then(() => { - jinrishici.load(result => { - if (result && result.data && result.data.content) { - typedJSFn.processSubtitle(result.data.content) - } else { - throw new Error('Failed to parse the return value of Jinrishici API') - } - }) - }) - .catch(err => { - console.error('Failed to get the Jinrishici API:', err) - typedJSFn.processSubtitle(!{JSON.stringify(subContent.length)}) - }) - } - typedJSFn.run(subtitleType) - - default - if subContent.length > 0 - script. - function subtitleType () { - typedJSFn.processSubtitle(!{JSON.stringify(subContent)}) - } - typedJSFn.run(subtitleType) \ No newline at end of file diff --git a/themes/butterfly/layout/includes/third-party/umami_analytics.pug b/themes/butterfly/layout/includes/third-party/umami_analytics.pug deleted file mode 100644 index 7487425..0000000 --- a/themes/butterfly/layout/includes/third-party/umami_analytics.pug +++ /dev/null @@ -1,65 +0,0 @@ -- let { serverURL, website_id, option, UV_PV } = theme.umami_analytics -- const isServerURL = !!serverURL -- const baseURL = serverURL ? serverURL.replace(/\/$/, '') : 'https://cloud.umami.is' -- const apiUrl = serverURL ? serverURL.replace(/\/$/, '') + '/api' : 'https://api.umami.is/v1' - -script. - (() => { - const option = !{JSON.stringify(option)} - const config = !{JSON.stringify(UV_PV)} - - const runTrack = () => { - umami.track(props => ({ ...props, url: window.location.pathname, title: GLOBAL_CONFIG_SITE.title })) - } - - const loadUmamiJS = () => { - btf.getScript('!{baseURL}/script.js', { - 'data-website-id': '!{website_id}', - 'data-auto-track': 'false', - ...option - }).then(runTrack) - } - - const getData = async (isPost) => { - const now = Date.now() - const keyUrl = isPost ? `&url=${window.location.pathname}` : '' - const headerList = { 'Accept': 'application/json' } - if (!{isServerURL}) headerList['Authorization'] = `Bearer ${config.token}` - else headerList['x-umami-api-key'] = config.token - const res = await fetch(`!{apiUrl}/websites/!{website_id}/stats?startAt=0000000000&endAt=${now}${keyUrl}`, { - method: "GET", - headers: headerList - }) - return await res.json() - } - - const insertData = async () => { - try { - if (GLOBAL_CONFIG_SITE.pageType === 'post' && config.page_pv) { - const pagePV = document.getElementById('umamiPV') - if (pagePV) { - const data = await getData(true) - pagePV.textContent = data.pageviews.value - } - } else { - const data = (config.site_uv || config.site_pv) && await getData() - if (config.site_uv) { - const siteUV = document.getElementById('umami-site-uv') - if (siteUV) siteUV.textContent = data.visitors.value - } - if (config.site_pv) { - const sitePV = document.getElementById('umami-site-pv') - if (sitePV) sitePV.textContent = data.pageviews.value - } - } - } catch (e) { - console.error('Failed to load Umami Analytics:', e) - } - } - - btf.addGlobalFn('pjaxComplete', runTrack, 'umami_analytics_run_track') - btf.addGlobalFn('pjaxComplete', insertData, 'umami_analytics_insert') - - loadUmamiJS() - insertData() - })() \ No newline at end of file diff --git a/themes/butterfly/layout/includes/widget/card_ad.pug b/themes/butterfly/layout/includes/widget/card_ad.pug deleted file mode 100644 index d088f0e..0000000 --- a/themes/butterfly/layout/includes/widget/card_ad.pug +++ /dev/null @@ -1,3 +0,0 @@ -if theme.ad && theme.ad.aside - .card-widget.ads-wrap - != theme.ad.aside diff --git a/themes/butterfly/layout/includes/widget/card_announcement.pug b/themes/butterfly/layout/includes/widget/card_announcement.pug deleted file mode 100644 index a96d06d..0000000 --- a/themes/butterfly/layout/includes/widget/card_announcement.pug +++ /dev/null @@ -1,6 +0,0 @@ -if theme.aside.card_announcement.enable - .card-widget.card-announcement - .item-headline - i.fas.fa-bullhorn.fa-shake - span= _p('aside.card_announcement') - .announcement_content!= theme.aside.card_announcement.content \ No newline at end of file diff --git a/themes/butterfly/layout/includes/widget/card_archives.pug b/themes/butterfly/layout/includes/widget/card_archives.pug deleted file mode 100644 index 0fcd17a..0000000 --- a/themes/butterfly/layout/includes/widget/card_archives.pug +++ /dev/null @@ -1,7 +0,0 @@ -if theme.aside.card_archives.enable - .card-widget.card-archives - - let type = theme.aside.card_archives.type || 'monthly' - - let format = theme.aside.card_archives.format || 'MMMM YYYY' - - let order = theme.aside.card_archives.order || -1 - - let limit = theme.aside.card_archives.limit === 0 ? 0 : theme.aside.card_archives.limit || 8 - != aside_archives({ type:type, format: format, order: order, limit: limit }) diff --git a/themes/butterfly/layout/includes/widget/card_author.pug b/themes/butterfly/layout/includes/widget/card_author.pug deleted file mode 100644 index 7a30df7..0000000 --- a/themes/butterfly/layout/includes/widget/card_author.pug +++ /dev/null @@ -1,26 +0,0 @@ -if theme.aside.card_author.enable - .card-widget.card-info.text-center - .avatar-img - img(src=url_for(theme.avatar.img) onerror=`this.onerror=null;this.src='` + url_for(theme.error_img.flink) + `'` alt="avatar") - .author-info-name= config.author - .author-info-description!= theme.aside.card_author.description || config.description - - .site-data - a(href=url_for(config.archive_dir) + '/') - .headline= _p('aside.articles') - .length-num= site.posts.length - a(href=url_for(config.tag_dir) + '/') - .headline= _p('aside.tags') - .length-num= site.tags.length - a(href=url_for(config.category_dir) + '/') - .headline= _p('aside.categories') - .length-num= site.categories.length - - if theme.aside.card_author.button.enable - a#card-info-btn(href=theme.aside.card_author.button.link) - i(class=theme.aside.card_author.button.icon) - span=theme.aside.card_author.button.text - - if(theme.social) - .card-info-social-icons - !=partial('includes/header/social', {}, {cache: true}) diff --git a/themes/butterfly/layout/includes/widget/card_bottom_self.pug b/themes/butterfly/layout/includes/widget/card_bottom_self.pug deleted file mode 100644 index 31ce298..0000000 --- a/themes/butterfly/layout/includes/widget/card_bottom_self.pug +++ /dev/null @@ -1,9 +0,0 @@ -if site.data.widget && site.data.widget.bottom - each item in site.data.widget.bottom - .card-widget(class=item.class_name id=item.id_name style=item.order ? `order: ${item.order}` : '') - .item-headline - i(class=item.icon) - span=item.name - .item-content - !=item.html - diff --git a/themes/butterfly/layout/includes/widget/card_categories.pug b/themes/butterfly/layout/includes/widget/card_categories.pug deleted file mode 100644 index 770d035..0000000 --- a/themes/butterfly/layout/includes/widget/card_categories.pug +++ /dev/null @@ -1,4 +0,0 @@ -if theme.aside.card_categories.enable - if site.categories.length - .card-widget.card-categories - !=aside_categories({ limit: theme.aside.card_categories.limit === 0 ? 0 : theme.aside.card_categories.limit || 8 , expand: theme.aside.card_categories.expand }) diff --git a/themes/butterfly/layout/includes/widget/card_newest_comment.pug b/themes/butterfly/layout/includes/widget/card_newest_comment.pug deleted file mode 100644 index 6f7d5d2..0000000 --- a/themes/butterfly/layout/includes/widget/card_newest_comment.pug +++ /dev/null @@ -1,7 +0,0 @@ -if theme.aside.card_newest_comments.enable && theme.comments.use && !['Livere','Facebook Comments','Giscus'].includes(theme.comments.use[0]) - .card-widget#card-newest-comments - .item-headline - i.fas.fa-comment-dots - span= _p('aside.card_newest_comments.headline') - .aside-list - span= _p('aside.card_newest_comments.loading_text') diff --git a/themes/butterfly/layout/includes/widget/card_post_series.pug b/themes/butterfly/layout/includes/widget/card_post_series.pug deleted file mode 100644 index 06b82d6..0000000 --- a/themes/butterfly/layout/includes/widget/card_post_series.pug +++ /dev/null @@ -1,21 +0,0 @@ -if theme.aside.card_post_series.enable - - const array = fragment_cache('seriesArr', groupPosts) - .card-widget.card-post-series - .item-headline - i.fa-solid.fa-layer-group - span= theme.aside.card_post_series.series_title ? page.series : _p('aside.card_post_series') - .aside-list - each item in array[page.series] - - const { path, title = _p('no_title'), cover, cover_type, date:dateA } = item - - let link = url_for(path) - - let no_cover = cover === false || !theme.cover.aside_enable ? 'no-cover' : '' - .aside-list-item(class=no_cover) - if cover && theme.cover.aside_enable - a.thumbnail(href=link title=title) - if cover_type === 'img' - img(src=url_for(cover) onerror=`this.onerror=null;this.src='${url_for(theme.error_img.post_page)}'` alt=title) - else - div(style=`background: ${cover}`) - .content - a.title(href=link title=title)= title - time(datetime=date_xml(dateA) title=_p('post.created') + ' ' + full_date(dateA)) #[=date(dateA, config.date_format)] diff --git a/themes/butterfly/layout/includes/widget/card_post_toc.pug b/themes/butterfly/layout/includes/widget/card_post_toc.pug deleted file mode 100644 index 93f350f..0000000 --- a/themes/butterfly/layout/includes/widget/card_post_toc.pug +++ /dev/null @@ -1,14 +0,0 @@ -- let tocNumber = typeof page.toc_number === 'boolean' ? page.toc_number : theme.toc.number -- let tocExpand = typeof page.toc_expand === 'boolean' ? page.toc_expand : theme.toc.expand -- let tocExpandClass = tocExpand ? 'is-expand' : '' - -#card-toc.card-widget - .item-headline - i.fas.fa-stream - span= _p('aside.card_toc') - span.toc-percentage - - if (page.encrypt == true) - .toc-content.toc-div-class(class=tocExpandClass style="display:none")!=toc(page.origin, {list_number: tocNumber}) - else - .toc-content(class=tocExpandClass)!=toc(page.content, {list_number: tocNumber}) diff --git a/themes/butterfly/layout/includes/widget/card_recent_post.pug b/themes/butterfly/layout/includes/widget/card_recent_post.pug deleted file mode 100644 index a8c4647..0000000 --- a/themes/butterfly/layout/includes/widget/card_recent_post.pug +++ /dev/null @@ -1,27 +0,0 @@ -if theme.aside.card_recent_post.enable - .card-widget.card-recent-post - .item-headline - i.fas.fa-history - span= _p('aside.card_recent_post') - .aside-list - - let postLimit = theme.aside.card_recent_post.limit === 0 ? site.posts.length : theme.aside.card_recent_post.limit || 5 - - let sort = theme.aside.card_recent_post.sort === 'updated' ? 'updated' : 'date' - - site.posts.sort(sort, -1).limit(postLimit).each(function(article){ - - let link = article.link || article.path - - let title = article.title || _p('no_title') - - let no_cover = article.cover === false || !theme.cover.aside_enable ? 'no-cover' : '' - - let post_cover = article.cover - .aside-list-item(class=no_cover) - if post_cover && theme.cover.aside_enable - a.thumbnail(href=url_for(link) title=title) - if article.cover_type === 'img' - img(src=url_for(post_cover) onerror=`this.onerror=null;this.src='${url_for(theme.error_img.post_page)}'` alt=title) - else - div(style=`background: ${post_cover}`) - .content - a.title(href=url_for(link) title=title)= title - if theme.aside.card_recent_post.sort === 'updated' - time(datetime=date_xml(article.updated) title=_p('post.updated') + ' ' + full_date(article.updated)) #[=date(article.updated, config.date_format)] - else - time(datetime=date_xml(article.date) title=_p('post.created') + ' ' + full_date(article.date)) #[=date(article.date, config.date_format)] - - }) \ No newline at end of file diff --git a/themes/butterfly/layout/includes/widget/card_tags.pug b/themes/butterfly/layout/includes/widget/card_tags.pug deleted file mode 100644 index b0aaa97..0000000 --- a/themes/butterfly/layout/includes/widget/card_tags.pug +++ /dev/null @@ -1,14 +0,0 @@ -if theme.aside.card_tags.enable - if site.tags.length - .card-widget.card-tags - .item-headline - i.fas.fa-tags - span= _p('aside.card_tags') - - - let { limit, orderby, order } = theme.aside.card_tags - - limit = limit === 0 ? 0 : limit || 40 - - if theme.aside.card_tags.color - .card-tag-cloud!= cloudTags({source: site.tags, orderby: orderby, order: order, minfontsize: 1.15, maxfontsize: 1.45, limit: limit, unit: 'em'}) - else - .card-tag-cloud!= tagcloud({orderby: orderby, order: order, min_font: 1.1, max_font: 1.5, amount: limit , color: true, start_color: '#999', end_color: '#99a9bf', unit: 'em'}) diff --git a/themes/butterfly/layout/includes/widget/card_top_self.pug b/themes/butterfly/layout/includes/widget/card_top_self.pug deleted file mode 100644 index 3cbfca2..0000000 --- a/themes/butterfly/layout/includes/widget/card_top_self.pug +++ /dev/null @@ -1,8 +0,0 @@ -if site.data.widget && site.data.widget.top - each item in site.data.widget.top - .card-widget(class=item.class_name id=item.id_name) - .item-headline - i(class=item.icon) - span=item.name - .item-content - !=item.html \ No newline at end of file diff --git a/themes/butterfly/layout/includes/widget/card_webinfo.pug b/themes/butterfly/layout/includes/widget/card_webinfo.pug deleted file mode 100644 index 6e481e4..0000000 --- a/themes/butterfly/layout/includes/widget/card_webinfo.pug +++ /dev/null @@ -1,44 +0,0 @@ -if theme.aside.card_webinfo.enable - .card-widget.card-webinfo - .item-headline - i.fas.fa-chart-line - span= _p('aside.card_webinfo.headline') - .webinfo - if theme.aside.card_webinfo.post_count - .webinfo-item - .item-name= `${_p('aside.card_webinfo.article_name')} :` - .item-count= site.posts.length - if theme.aside.card_webinfo.runtime_date - .webinfo-item - .item-name= `${_p('aside.card_webinfo.runtime.name')} :` - .item-count#runtimeshow(data-publishDate=date_xml(theme.aside.card_webinfo.runtime_date)) - i.fa-solid.fa-spinner.fa-spin - if theme.wordcount.enable && theme.wordcount.total_wordcount - .webinfo-item - .item-name= `${_p('aside.card_webinfo.site_wordcount')} :` - .item-count= totalcount(site) - if theme.umami_analytics.enable && theme.umami_analytics.UV_PV.site_uv - .webinfo-item - .item-name= `${_p('aside.card_webinfo.site_uv_name')} :` - .item-count#umami-site-uv - i.fa-solid.fa-spinner.fa-spin - else if theme.busuanzi.site_uv - .webinfo-item - .item-name= `${_p('aside.card_webinfo.site_uv_name')} :` - .item-count#busuanzi_value_site_uv - i.fa-solid.fa-spinner.fa-spin - if theme.umami_analytics.enable && theme.umami_analytics.UV_PV.site_pv - .webinfo-item - .item-name= `${_p('aside.card_webinfo.site_pv_name')} :` - .item-count#umami-site-pv - i.fa-solid.fa-spinner.fa-spin - else if theme.busuanzi.site_pv - .webinfo-item - .item-name= `${_p('aside.card_webinfo.site_pv_name')} :` - .item-count#busuanzi_value_site_pv - i.fa-solid.fa-spinner.fa-spin - if theme.aside.card_webinfo.last_push_date - .webinfo-item - .item-name= `${_p('aside.card_webinfo.last_push_date.name')} :` - .item-count#last-push-date(data-lastPushDate=date_xml(Date.now())) - i.fa-solid.fa-spinner.fa-spin \ No newline at end of file diff --git a/themes/butterfly/layout/includes/widget/index.pug b/themes/butterfly/layout/includes/widget/index.pug deleted file mode 100644 index d9d3d01..0000000 --- a/themes/butterfly/layout/includes/widget/index.pug +++ /dev/null @@ -1,36 +0,0 @@ -#aside-content.aside-content - //- post - if globalPageType === 'post' - - const tocStyle = page.toc_style_simple - - const tocStyleVal = tocStyle === true || tocStyle === false ? tocStyle : theme.toc.style_simple - if showToc && tocStyleVal - .sticky_layout - include ./card_post_toc.pug - else - !=partial('includes/widget/card_author', {}, {cache: true}) - !=partial('includes/widget/card_announcement', {}, {cache: true}) - !=partial('includes/widget/card_top_self', {}, {cache: true}) - .sticky_layout - if showToc - include ./card_post_toc.pug - if page.series - include ./card_post_series.pug - !=partial('includes/widget/card_recent_post', {}, {cache: true}) - !=partial('includes/widget/card_ad', {}, {cache: true}) - else - //- page - !=partial('includes/widget/card_author', {}, {cache: true}) - !=partial('includes/widget/card_announcement', {}, {cache: true}) - !=partial('includes/widget/card_top_self', {}, {cache: true}) - - .sticky_layout - if showToc - include ./card_post_toc.pug - !=partial('includes/widget/card_recent_post', {}, {cache: true}) - !=partial('includes/widget/card_ad', {}, {cache: true}) - !=partial('includes/widget/card_newest_comment', {}, {cache: true}) - !=partial('includes/widget/card_categories', {}, {cache: true}) - !=partial('includes/widget/card_tags', {}, {cache: true}) - !=partial('includes/widget/card_archives', {}, {cache: true}) - !=partial('includes/widget/card_webinfo', {}, {cache: true}) - !=partial('includes/widget/card_bottom_self', {}, {cache: true}) \ No newline at end of file diff --git a/themes/butterfly/layout/index.pug b/themes/butterfly/layout/index.pug deleted file mode 100644 index 94e6ec7..0000000 --- a/themes/butterfly/layout/index.pug +++ /dev/null @@ -1,5 +0,0 @@ -extends includes/layout.pug - -block content - include ./includes/mixins/indexPostUI.pug - +indexPostUI \ No newline at end of file diff --git a/themes/butterfly/layout/page.pug b/themes/butterfly/layout/page.pug deleted file mode 100644 index f980fde..0000000 --- a/themes/butterfly/layout/page.pug +++ /dev/null @@ -1,32 +0,0 @@ -extends includes/layout.pug - -block content - - const noCardLayout = ['shuoshuo', '404'].includes(page.type) ? 'nc' : '' - - var commentsJsLoad = false - - mixin commentLoad - if page.comments !== false && theme.comments.use - - commentsJsLoad = true - !=partial('includes/third-party/comments/index', {}, {cache: true}) - - #page(class=noCardLayout) - if top_img === false && page.title - .page-title= page.title - - case page.type - when 'tags' - include includes/page/tags.pug - +commentLoad - when 'link' - include includes/page/flink.pug - +commentLoad - when 'categories' - include includes/page/categories.pug - +commentLoad - when '404' - include includes/page/404.pug - when 'shuoshuo' - include includes/page/shuoshuo.pug - default - include includes/page/default-page.pug - +commentLoad \ No newline at end of file diff --git a/themes/butterfly/layout/post.pug b/themes/butterfly/layout/post.pug deleted file mode 100644 index 46007fb..0000000 --- a/themes/butterfly/layout/post.pug +++ /dev/null @@ -1,35 +0,0 @@ -extends includes/layout.pug - -block content - #post - if top_img === false - include includes/header/post-info.pug - - article#article-container.container.post-content - if theme.noticeOutdate.enable && page.noticeOutdate !== false - include includes/post/outdate-notice.pug - else - !=page.content - include includes/post/post-copyright.pug - .tag_share - if (page.tags.length > 0 && theme.post_meta.post.tags) - .post-meta__tag-list - each item, index in page.tags.data - a(href=url_for(item.path)).post-meta__tags #[=item.name] - include includes/third-party/share/index.pug - - if theme.reward.enable && theme.reward.QR_code - !=partial('includes/post/reward', {}, {cache: true}) - - //- ad - if theme.ad && theme.ad.post - .ads-wrap!=theme.ad.post - - if theme.post_pagination - include includes/pagination.pug - if theme.related_post && theme.related_post.enable - != related_posts(page,site.posts) - - if page.comments !== false && theme.comments.use - - var commentsJsLoad = true - !=partial('includes/third-party/comments/index', {}, {cache: true}) diff --git a/themes/butterfly/layout/tag.pug b/themes/butterfly/layout/tag.pug deleted file mode 100644 index 1022544..0000000 --- a/themes/butterfly/layout/tag.pug +++ /dev/null @@ -1,12 +0,0 @@ -extends includes/layout.pug - -block content - if theme.tag_ui == 'index' - include ./includes/mixins/indexPostUI.pug - +indexPostUI - else - include ./includes/mixins/article-sort.pug - #tag - .article-sort-title= _p('page.tag') + ' - ' + page.tag - +articleSort(page.posts) - include includes/pagination.pug \ No newline at end of file diff --git a/themes/butterfly/package.json b/themes/butterfly/package.json deleted file mode 100644 index 54e3e65..0000000 --- a/themes/butterfly/package.json +++ /dev/null @@ -1,32 +0,0 @@ -{ - "name": "hexo-theme-butterfly", - "version": "5.3.5", - "description": "A Simple and Card UI Design theme for Hexo", - "main": "package.json", - "scripts": { - "test": "echo \"Error: no test specified\" && exit 1" - }, - "keywords": [ - "hexo", - "theme", - "butterfly", - "Card UI Design", - "Jerry", - "hexo-theme-butterfly" - ], - "repository": { - "type" : "git", - "url" : "https://github.com/jerryc127/hexo-theme-butterfly.git" - }, - "bugs": { - "url": "https://github.com/jerryc127/hexo-theme-butterfly/issues", - "email": "my@crazywong.com" - }, - "dependencies": { - "hexo-renderer-stylus": "^3.0.1", - "hexo-renderer-pug": "^3.0.0" - }, - "homepage": "https://butterfly.js.org/", - "author": "Jerry ", - "license": "Apache-2.0" -} diff --git a/themes/butterfly/plugins.yml b/themes/butterfly/plugins.yml deleted file mode 100644 index c79ad44..0000000 --- a/themes/butterfly/plugins.yml +++ /dev/null @@ -1,211 +0,0 @@ -abcjs_basic_js: - name: abcjs - file: dist/abcjs-basic-min.js - version: 6.4.4 -activate_power_mode: - name: butterfly-extsrc - file: dist/activate-power-mode.min.js - version: 1.1.4 -algolia_search: - name: algoliasearch - file: dist/lite/builds/browser.umd.js - version: 5.20.3 -aplayer_css: - name: aplayer - file: dist/APlayer.min.css - version: 1.10.1 -aplayer_js: - name: aplayer - file: dist/APlayer.min.js - version: 1.10.1 -artalk_css: - name: artalk - file: dist/Artalk.css - version: 2.9.1 -artalk_js: - name: artalk - file: dist/Artalk.js - version: 2.9.1 -blueimp_md5: - name: blueimp-md5 - file: js/md5.min.js - version: 2.19.0 -canvas_fluttering_ribbon: - name: butterfly-extsrc - file: dist/canvas-fluttering-ribbon.min.js - version: 1.1.4 -canvas_nest: - name: butterfly-extsrc - file: dist/canvas-nest.min.js - version: 1.1.4 -canvas_ribbon: - name: butterfly-extsrc - file: dist/canvas-ribbon.min.js - version: 1.1.4 -chartjs: - name: chart.js - file: dist/chart.umd.js - version: 4.4.8 -clickShowText: - name: butterfly-extsrc - file: dist/click-show-text.min.js - version: 1.1.4 -click_heart: - name: butterfly-extsrc - file: dist/click-heart.min.js - version: 1.1.4 -disqusjs: - name: disqusjs - file: dist/browser/disqusjs.es2015.umd.min.js - version: 3.0.2 -disqusjs_css: - name: disqusjs - file: dist/browser/styles/disqusjs.css - version: 3.0.2 -docsearch_css: - name: '@docsearch/css' - other_name: docsearch-css - file: dist/style.css - version: 3.9.0 -docsearch_js: - name: '@docsearch/js' - other_name: docsearch-js - file: dist/umd/index.js - version: 3.9.0 -egjs_infinitegrid: - name: '@egjs/infinitegrid' - other_name: egjs-infinitegrid - file: dist/infinitegrid.min.js - version: 4.12.0 -fancybox: - name: '@fancyapps/ui' - file: dist/fancybox/fancybox.umd.js - version: 5.0.36 - other_name: fancyapps-ui -fancybox_css: - name: '@fancyapps/ui' - file: dist/fancybox/fancybox.css - version: 5.0.36 - other_name: fancyapps-ui -fireworks: - name: butterfly-extsrc - file: dist/fireworks.min.js - version: 1.1.4 -fontawesome: - name: '@fortawesome/fontawesome-free' - file: css/all.min.css - other_name: font-awesome - version: 6.7.2 -gitalk: - name: gitalk - file: dist/gitalk.min.js - version: 1.8.0 -gitalk_css: - name: gitalk - file: dist/gitalk.css - version: 1.8.0 -instantpage: - name: instant.page - file: instantpage.js - version: 5.2.0 -instantsearch: - name: instantsearch.js - file: dist/instantsearch.production.min.js - version: 4.77.3 -katex: - name: katex - file: dist/katex.min.css - other_name: KaTeX - version: 0.16.21 -katex_copytex: - name: katex - file: dist/contrib/copy-tex.min.js - other_name: KaTeX - version: 0.16.21 -lazyload: - name: vanilla-lazyload - file: dist/lazyload.iife.min.js - version: 19.1.3 -mathjax: - name: mathjax - file: es5/tex-mml-chtml.js - version: 3.2.2 -medium_zoom: - name: medium-zoom - file: dist/medium-zoom.min.js - version: 1.1.0 -mermaid: - name: mermaid - file: dist/mermaid.min.js - version: 11.4.1 -meting_js: - name: butterfly-extsrc - file: metingjs/dist/Meting.min.js - version: 1.1.4 -pace_default_css: - name: pace-js - other_name: pace - file: themes/blue/pace-theme-minimal.css - version: 1.2.4 -pace_js: - name: pace-js - other_name: pace - file: pace.min.js - version: 1.2.4 -pjax: - name: pjax - file: pjax.min.js - version: 0.2.8 -prismjs_autoloader: - name: prismjs - file: plugins/autoloader/prism-autoloader.min.js - other_name: prism - version: 1.29.0 -prismjs_js: - name: prismjs - file: prism.js - other_name: prism - version: 1.29.0 -prismjs_lineNumber_js: - name: prismjs - file: plugins/line-numbers/prism-line-numbers.min.js - other_name: prism - version: 1.29.0 -sharejs: - name: butterfly-extsrc - file: sharejs/dist/js/social-share.min.js - version: 1.1.4 -sharejs_css: - name: butterfly-extsrc - file: sharejs/dist/css/share.min.css - version: 1.1.4 -snackbar: - name: node-snackbar - file: dist/snackbar.min.js - version: 0.1.16 -snackbar_css: - name: node-snackbar - file: dist/snackbar.min.css - version: 0.1.16 -twikoo: - name: twikoo - file: dist/twikoo.all.min.js - version: 1.6.41 -typed: - name: typed.js - file: dist/typed.umd.js - version: 2.1.0 -valine: - name: valine - file: dist/Valine.min.js - version: 1.5.3 -waline_css: - name: '@waline/client' - file: dist/waline.css - other_name: waline - version: 3.5.5 -waline_js: - name: '@waline/client' - file: dist/waline.js - other_name: waline - version: 3.5.5 diff --git a/themes/butterfly/scripts/common/postDesc.js b/themes/butterfly/scripts/common/postDesc.js deleted file mode 100644 index f2d547f..0000000 --- a/themes/butterfly/scripts/common/postDesc.js +++ /dev/null @@ -1,36 +0,0 @@ -'use strict' - -const { stripHTML, truncate } = require('hexo-util') - -// Truncates the given content to a specified length, removing HTML tags and replacing newlines with spaces. -const truncateContent = (content, length) => { - return truncate(stripHTML(content), { length, separator: ' ' }).replace(/\n/g, ' ') -} - -// Generates a post description based on the provided data and theme configuration. -const postDesc = (data, hexo) => { - const { description, content, postDesc } = data - - if (postDesc) return postDesc - - const { length, method } = hexo.theme.config.index_post_content - - if (method === false) return - - let result - switch (method) { - case 1: - result = description - break - case 2: - result = description || truncateContent(content, length) - break - default: - result = truncateContent(content, length) - } - - data.postDesc = result - return result -} - -module.exports = { truncateContent, postDesc } diff --git a/themes/butterfly/scripts/events/404.js b/themes/butterfly/scripts/events/404.js deleted file mode 100644 index 157ef06..0000000 --- a/themes/butterfly/scripts/events/404.js +++ /dev/null @@ -1,20 +0,0 @@ -/** - * Butterfly - * 404 error page - */ - -'use strict' - -hexo.extend.generator.register('404', function (locals) { - if (!hexo.theme.config.error_404.enable) return - return { - path: '404.html', - layout: ['page'], - data: { - type: '404', - top_img: false, - comments: false, - aside: false - } - } -}) diff --git a/themes/butterfly/scripts/events/cdn.js b/themes/butterfly/scripts/events/cdn.js deleted file mode 100644 index 7d3e31e..0000000 --- a/themes/butterfly/scripts/events/cdn.js +++ /dev/null @@ -1,97 +0,0 @@ -/** - * Butterfly - * Merge CDN - */ - -'use strict' - -const { version } = require('../../package.json') -const path = require('path') - -hexo.extend.filter.register('before_generate', () => { - const themeConfig = hexo.theme.config - const { CDN } = themeConfig - - const thirdPartySrc = hexo.render.renderSync({ path: path.join(hexo.theme_dir, '/plugins.yml'), engine: 'yaml' }) - const internalSrc = { - main: { - name: 'hexo-theme-butterfly', - file: 'js/main.js', - version - }, - utils: { - name: 'hexo-theme-butterfly', - file: 'js/utils.js', - version - }, - translate: { - name: 'hexo-theme-butterfly', - file: 'js/tw_cn.js', - version - }, - local_search: { - name: 'hexo-theme-butterfly', - file: 'js/search/local-search.js', - version - }, - algolia_js: { - name: 'hexo-theme-butterfly', - file: 'js/search/algolia.js', - version - } - } - - const minFile = file => { - return file.replace(/(? '.min' + ext) - } - - const createCDNLink = (data, type, cond = '') => { - Object.keys(data).forEach(key => { - let { name, version, file, other_name } = data[key] - const cdnjs_name = other_name || name - const cdnjs_file = file.replace(/^[lib|dist]*\/|browser\//g, '') - const min_cdnjs_file = minFile(cdnjs_file) - if (cond === 'internal') file = `source/${file}` - const min_file = minFile(file) - const verType = CDN.version ? (type === 'local' ? `?v=${version}` : `@${version}`) : '' - - const value = { - version, - name, - file, - cdnjs_file, - min_file, - min_cdnjs_file, - cdnjs_name - } - - const cdnSource = { - local: cond === 'internal' ? `${cdnjs_file + verType}` : `/pluginsSrc/${name}/${file + verType}`, - jsdelivr: `https://cdn.jsdelivr.net/npm/${name}${verType}/${min_file}`, - unpkg: `https://unpkg.com/${name}${verType}/${file}`, - cdnjs: `https://cdnjs.cloudflare.com/ajax/libs/${cdnjs_name}/${version}/${min_cdnjs_file}`, - custom: (CDN.custom_format || '').replace(/\$\{(.+?)\}/g, (match, $1) => value[$1]) - } - - data[key] = cdnSource[type] - }) - - if (cond === 'internal') data.main_css = 'css/index.css' + (CDN.version ? `?v=${version}` : '') - return data - } - - // delete null value - const deleteNullValue = obj => { - if (!obj) return - for (const i in obj) { - obj[i] === null && delete obj[i] - } - return obj - } - - themeConfig.asset = Object.assign( - createCDNLink(internalSrc, CDN.internal_provider, 'internal'), - createCDNLink(thirdPartySrc, CDN.third_party_provider), - deleteNullValue(CDN.option) - ) -}) diff --git a/themes/butterfly/scripts/events/comment.js b/themes/butterfly/scripts/events/comment.js deleted file mode 100644 index 83a67e9..0000000 --- a/themes/butterfly/scripts/events/comment.js +++ /dev/null @@ -1,24 +0,0 @@ -/** - * Capitalize the first letter of comment name - */ - -hexo.extend.filter.register('before_generate', () => { - const themeConfig = hexo.theme.config - let { use } = themeConfig.comments - if (!use) return - - // Make sure use is an array - use = Array.isArray(use) ? use : use.split(',') - - // Capitalize the first letter of each comment name - use = use.map(item => - item.trim().toLowerCase().replace(/\b[a-z]/g, s => s.toUpperCase()) - ) - - // Disqus and Disqusjs conflict, only keep the first one - if (use.includes('Disqus') && use.includes('Disqusjs')) { - use = [use[0]] - } - - themeConfig.comments.use = use -}) diff --git a/themes/butterfly/scripts/events/init.js b/themes/butterfly/scripts/events/init.js deleted file mode 100644 index dab4696..0000000 --- a/themes/butterfly/scripts/events/init.js +++ /dev/null @@ -1,20 +0,0 @@ -hexo.extend.filter.register('before_generate', () => { - // Get first two digits of the Hexo version number - const { version, log, locals } = hexo - const hexoVer = version.replace(/(^.*\..*)\..*/, '$1') - - if (hexoVer < 5.3) { - log.error('Please update Hexo to V5.3.0 or higher!') - log.error('請把 Hexo 升級到 V5.3.0 或更高的版本!') - process.exit(-1) - } - - if (locals.get) { - const data = locals.get('data') - if (data && data.butterfly) { - log.error("'butterfly.yml' is deprecated. Please use '_config.butterfly.yml'") - log.error("'butterfly.yml' 已經棄用,請使用 '_config.butterfly.yml'") - process.exit(-1) - } - } -}) diff --git a/themes/butterfly/scripts/events/merge_config.js b/themes/butterfly/scripts/events/merge_config.js deleted file mode 100644 index eb27857..0000000 --- a/themes/butterfly/scripts/events/merge_config.js +++ /dev/null @@ -1,583 +0,0 @@ -const { deepMerge } = require('hexo-util') - -hexo.extend.filter.register('before_generate', () => { - const defaultConfig = { - nav: { - logo: null, - display_title: true, - fixed: false - }, - menu: null, - code_blocks: { - theme: 'light', - macStyle: false, - height_limit: false, - word_wrap: false, - copy: true, - language: true, - shrink: false, - fullpage: false - }, - social: null, - favicon: '/img/favicon.png', - avatar: { - img: '/img/butterfly-icon.png', - effect: false - }, - disable_top_img: false, - default_top_img: null, - index_img: null, - archive_img: null, - tag_img: null, - tag_per_img: null, - category_img: null, - category_per_img: null, - footer_img: false, - background: null, - cover: { - index_enable: true, - aside_enable: true, - archives_enable: true, - default_cover: null - }, - error_img: { - flink: '/img/friend_404.gif', - post_page: '/img/404.jpg' - }, - error_404: { - enable: false, - subtitle: 'Page Not Found', - background: '/img/error-page.png' - }, - post_meta: { - page: { - date_type: 'created', - date_format: 'date', - categories: true, - tags: false, - label: true - }, - post: { - position: 'left', - date_type: 'both', - date_format: 'date', - categories: true, - tags: true, - label: true - } - }, - index_site_info_top: null, - index_top_img_height: null, - subtitle: { - enable: false, - effect: true, - typed_option: null, - source: false, - sub: null - }, - index_layout: 3, - index_post_content: { - method: 3, - length: 500 - }, - toc: { - post: true, - page: false, - number: true, - expand: false, - style_simple: false, - scroll_percent: true - }, - post_copyright: { - enable: true, - decode: false, - author_href: null, - license: 'CC BY-NC-SA 4.0', - license_url: 'https://creativecommons.org/licenses/by-nc-sa/4.0/' - }, - reward: { - enable: false, - text: null, - QR_code: null - }, - post_edit: { - enable: false, - url: null - }, - related_post: { - enable: true, - limit: 6, - date_type: 'created' - }, - post_pagination: 1, - noticeOutdate: { - enable: false, - style: 'flat', - limit_day: 365, - position: 'top', - message_prev: 'It has been', - message_next: 'days since the last update, the content of the article may be outdated.' - }, - footer: { - owner: { - enable: true, - since: 2019 - }, - custom_text: null, - copyright: true - }, - aside: { - enable: true, - hide: false, - button: true, - mobile: true, - position: 'right', - display: { - archive: true, - tag: true, - category: true - }, - card_author: { - enable: true, - description: null, - button: { - enable: true, - icon: 'fab fa-github', - text: 'Follow Me', - link: 'https://github.com/xxxxxx' - } - }, - card_announcement: { - enable: true, - content: 'This is my Blog' - }, - card_recent_post: { - enable: true, - limit: 5, - sort: 'date', - sort_order: null - }, - card_newest_comments: { - enable: false, - sort_order: null, - limit: 6, - storage: 10, - avatar: true - }, - card_categories: { - enable: true, - limit: 8, - expand: 'none', - sort_order: null - }, - card_tags: { - enable: true, - limit: 40, - color: false, - orderby: 'random', - order: 1, - sort_order: null - }, - card_archives: { - enable: true, - type: 'monthly', - format: 'MMMM YYYY', - order: -1, - limit: 8, - sort_order: null - }, - card_post_series: { - enable: true, - series_title: false, - orderBy: 'date', - order: -1 - }, - card_webinfo: { - enable: true, - post_count: true, - last_push_date: true, - sort_order: null, - runtime_date: null - } - }, - rightside_bottom: null, - translate: { - enable: false, - default: '繁', - defaultEncoding: 2, - translateDelay: 0, - msgToTraditionalChinese: '繁', - msgToSimplifiedChinese: '簡' - }, - readmode: true, - darkmode: { - enable: true, - button: true, - autoChangeMode: false, - start: null, - end: null - }, - rightside_scroll_percent: false, - rightside_item_order: { - enable: false, - hide: null, - show: null - }, - anchor: { - auto_update: false, - click_to_scroll: false - }, - photofigcaption: false, - copy: { - enable: true, - copyright: { - enable: false, - limit_count: 150 - } - }, - wordcount: { - enable: false, - post_wordcount: true, - min2read: true, - total_wordcount: true - }, - busuanzi: { - site_uv: true, - site_pv: true, - page_pv: true - }, - math: { - use: null, - per_page: true, - hide_scrollbar: false, - mathjax: { - enableMenu: true, - tags: 'none' - }, - katex: { - copy_tex: false - } - }, - search: { - use: null, - placeholder: null, - algolia_search: { - hitsPerPage: 6 - }, - local_search: { - preload: false, - top_n_per_article: 1, - unescape: false, - CDN: null - }, - docsearch: { - appId: null, - apiKey: null, - indexName: null, - option: null - } - }, - share: { - use: 'sharejs', - sharejs: { - sites: 'facebook,twitter,wechat,weibo,qq' - }, - addtoany: { - item: 'facebook,twitter,wechat,sina_weibo,facebook_messenger,email,copy_link' - } - }, - comments: { - use: null, - text: true, - lazyload: false, - count: false, - card_post_count: false - }, - disqus: { - shortname: null, - apikey: null - }, - disqusjs: { - shortname: null, - apikey: null, - option: null - }, - livere: { - uid: null - }, - gitalk: { - client_id: null, - client_secret: null, - repo: null, - owner: null, - admin: null, - option: null - }, - valine: { - appId: null, - appKey: null, - avatar: 'monsterid', - serverURLs: null, - bg: null, - visitor: false, - option: null - }, - waline: { - serverURL: null, - bg: null, - pageview: false, - option: null - }, - utterances: { - repo: null, - issue_term: 'pathname', - light_theme: 'github-light', - dark_theme: 'photon-dark', - js: null, - option: null - }, - facebook_comments: { - app_id: null, - user_id: null, - pageSize: 10, - order_by: 'social', - lang: 'en_US' - }, - twikoo: { - envId: null, - region: null, - visitor: false, - option: null - }, - giscus: { - repo: null, - repo_id: null, - category_id: null, - light_theme: 'light', - dark_theme: 'dark', - js: null, - option: null - }, - remark42: { - host: null, - siteId: null, - option: null - }, - artalk: { - server: null, - site: null, - visitor: false, - option: null - }, - chat: { - use: null, - rightside_button: false, - button_hide_show: false - }, - chatra: { - id: null - }, - tidio: { - public_key: null - }, - crisp: { - website_id: null - }, - baidu_analytics: null, - google_analytics: null, - cloudflare_analytics: null, - microsoft_clarity: null, - umami_analytics: { - enable: false, - serverURL: null, - website_id: null, - option: null, - UV_PV: { - site_uv: false, - site_pv: false, - page_pv: false, - token: null - } - }, - google_adsense: { - enable: false, - auto_ads: true, - js: 'https://pagead2.googlesyndication.com/pagead/js/adsbygoogle.js', - client: null, - enable_page_level_ads: true - }, - ad: { - index: null, - aside: null, - post: null - }, - site_verification: null, - category_ui: null, - tag_ui: null, - rounded_corners_ui: true, - text_align_justify: false, - mask: { - header: true, - footer: true - }, - preloader: { - enable: false, - source: 1, - pace_css_url: null - }, - enter_transitions: true, - display_mode: 'light', - beautify: { - enable: false, - field: 'post', - title_prefix_icon: null, - title_prefix_icon_color: null - }, - font: { - global_font_size: null, - code_font_size: null, - font_family: null, - code_font_family: null - }, - blog_title_font: { - font_link: null, - font_family: null - }, - hr_icon: { - enable: true, - icon: null, - icon_top: null - }, - activate_power_mode: { - enable: false, - colorful: true, - shake: true, - mobile: false - }, - canvas_ribbon: { - enable: false, - size: 150, - alpha: 0.6, - zIndex: -1, - click_to_change: false, - mobile: false - }, - canvas_fluttering_ribbon: { - enable: false, - mobile: false - }, - canvas_nest: { - enable: false, - color: '0,0,255', - opacity: 0.7, - zIndex: -1, - count: 99, - mobile: false - }, - fireworks: { - enable: false, - zIndex: 9999, - mobile: false - }, - click_heart: { - enable: false, - mobile: false - }, - clickShowText: { - enable: false, - text: null, - fontSize: '15px', - random: false, - mobile: false - }, - lightbox: null, - series: { - enable: false, - orderBy: 'title', - order: 1, - number: true - }, - abcjs: { - enable: false, - per_page: true - }, - mermaid: { - enable: false, - code_write: false, - theme: { - light: 'default', - dark: 'dark' - } - }, - chartjs: { - enable: false, - fontColor: { - light: 'rgba(0, 0, 0, 0.8)', - dark: 'rgba(255, 255, 255, 0.8)' - }, - borderColor: { - light: 'rgba(0, 0, 0, 0.1)', - dark: 'rgba(255, 255, 255, 0.2)' - }, - scale_ticks_backdropColor: { - light: 'transparent', - dark: 'transparent' - } - }, - note: { - style: 'flat', - icons: true, - border_radius: 3, - light_bg_offset: 0 - }, - pjax: { - enable: false, - exclude: null - }, - aplayerInject: { - enable: false, - per_page: true - }, - snackbar: { - enable: false, - position: 'bottom-left', - bg_light: '#49b1f5', - bg_dark: '#1f1f1f' - }, - instantpage: false, - lazyload: { - enable: false, - native: false, - field: 'site', - placeholder: null, - blur: false - }, - pwa: { - enable: false, - manifest: null, - apple_touch_icon: null, - favicon_32_32: null, - favicon_16_16: null, - mask_icon: null - }, - Open_Graph_meta: { - enable: true, - option: null - }, - structured_data: true, - css_prefix: true, - inject: { - head: null, - bottom: null - }, - CDN: { - internal_provider: 'local', - third_party_provider: 'jsdelivr', - version: false, - custom_format: null, - option: null - } - } - - hexo.theme.config = deepMerge(defaultConfig, hexo.theme.config) -}, 1) diff --git a/themes/butterfly/scripts/events/stylus.js b/themes/butterfly/scripts/events/stylus.js deleted file mode 100644 index 486a83e..0000000 --- a/themes/butterfly/scripts/events/stylus.js +++ /dev/null @@ -1,24 +0,0 @@ -/** - * Stylus renderer - */ - -'use strict' - -hexo.extend.filter.register('stylus:renderer', style => { - const { syntax_highlighter: syntaxHighlighter, highlight, prismjs } = hexo.config - let { enable: highlightEnable, line_number: highlightLineNumber } = highlight - let { enable: prismjsEnable, line_number: prismjsLineNumber } = prismjs - - // for hexo > 7.0 - if (syntaxHighlighter) { - highlightEnable = syntaxHighlighter === 'highlight.js' - prismjsEnable = syntaxHighlighter === 'prismjs' - } - - style.define('$highlight_enable', highlightEnable) - .define('$highlight_line_number', highlightLineNumber) - .define('$prismjs_enable', prismjsEnable) - .define('$prismjs_line_number', prismjsLineNumber) - .define('$language', hexo.config.language) - // .import(`${this.source_dir.replace(/\\/g, '/')}_data/css/*`) -}) diff --git a/themes/butterfly/scripts/events/welcome.js b/themes/butterfly/scripts/events/welcome.js deleted file mode 100644 index 188763c..0000000 --- a/themes/butterfly/scripts/events/welcome.js +++ /dev/null @@ -1,13 +0,0 @@ -hexo.on('ready', () => { - const { version } = require('../../package.json') - hexo.log.info(` - =================================================================== - ##### # # ##### ##### ###### ##### ###### # # # - # # # # # # # # # # # # # - ##### # # # # ##### # # ##### # # - # # # # # # # ##### # # # - # # # # # # # # # # # # - ##### #### # # ###### # # # ###### # - ${version} - ===================================================================`) -}) diff --git a/themes/butterfly/scripts/filters/post_lazyload.js b/themes/butterfly/scripts/filters/post_lazyload.js deleted file mode 100644 index 98aa681..0000000 --- a/themes/butterfly/scripts/filters/post_lazyload.js +++ /dev/null @@ -1,31 +0,0 @@ -/** - * Butterfly - * lazyload - * replace src to data-lazy-src - */ - -'use strict' - -const urlFor = require('hexo-util').url_for.bind(hexo) - -const lazyload = htmlContent => { - if (hexo.theme.config.lazyload.native) { - return htmlContent.replace(/()/ig, '$1 loading=\'lazy\'$2') - } - - const bg = hexo.theme.config.lazyload.placeholder ? urlFor(hexo.theme.config.lazyload.placeholder) : 'data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7' - return htmlContent.replace(/( { - const { enable, field } = hexo.theme.config.lazyload - if (!enable || field !== 'site') return - return lazyload(data) -}) - -hexo.extend.filter.register('after_post_render', data => { - const { enable, field } = hexo.theme.config.lazyload - if (!enable || field !== 'post') return - data.content = lazyload(data.content) - return data -}) diff --git a/themes/butterfly/scripts/filters/random_cover.js b/themes/butterfly/scripts/filters/random_cover.js deleted file mode 100644 index 7bccbee..0000000 --- a/themes/butterfly/scripts/filters/random_cover.js +++ /dev/null @@ -1,82 +0,0 @@ -/** - * Random cover for posts - */ - -'use strict' - -hexo.extend.generator.register('post', locals => { - const previousIndexes = [] - - const getRandomCover = defaultCover => { - if (!defaultCover) return false - if (!Array.isArray(defaultCover)) return defaultCover - - const coverCount = defaultCover.length - - if (coverCount === 1) { - return defaultCover[0] - } - - const maxPreviousIndexes = coverCount === 2 ? 1 : (coverCount === 3 ? 2 : 3) - - let index - do { - index = Math.floor(Math.random() * coverCount) - } while (previousIndexes.includes(index) && previousIndexes.length < coverCount) - - previousIndexes.push(index) - if (previousIndexes.length > maxPreviousIndexes) { - previousIndexes.shift() - } - - return defaultCover[index] - } - - const handleImg = data => { - const imgTestReg = /\.(png|jpe?g|gif|svg|webp|avif)(\?.*)?$/i - let { cover: coverVal, top_img: topImg } = data - - // Add path to top_img and cover if post_asset_folder is enabled - if (hexo.config.post_asset_folder) { - if (topImg && topImg.indexOf('/') === -1 && imgTestReg.test(topImg)) { - data.top_img = `${data.path}${topImg}` - } - if (coverVal && coverVal.indexOf('/') === -1 && imgTestReg.test(coverVal)) { - data.cover = `${data.path}${coverVal}` - } - } - - if (coverVal === false) return data - - // If cover is not set, use random cover - if (!coverVal) { - const { cover: { default_cover: defaultCover } } = hexo.theme.config - const randomCover = getRandomCover(defaultCover) - data.cover = randomCover - coverVal = randomCover // update coverVal - } - - if (coverVal && (coverVal.indexOf('//') !== -1 || imgTestReg.test(coverVal))) { - data.cover_type = 'img' - } - - return data - } - - // https://github.com/hexojs/hexo/blob/master/lib%2Fplugins%2Fgenerator%2Fpost.ts - const posts = locals.posts.sort('date').toArray() - const { length } = posts - - return posts.map((post, i) => { - if (i) post.prev = posts[i - 1] - if (i < length - 1) post.next = posts[i + 1] - - post.__post = true - - return { - data: handleImg(post), - layout: 'post', - path: post.path - } - }) -}) diff --git a/themes/butterfly/scripts/helpers/aside_archives.js b/themes/butterfly/scripts/helpers/aside_archives.js deleted file mode 100644 index dd07b57..0000000 --- a/themes/butterfly/scripts/helpers/aside_archives.js +++ /dev/null @@ -1,118 +0,0 @@ -'use strict' - -hexo.extend.helper.register('aside_archives', function (options = {}) { - const { config, page, site, url_for, _p } = this - const { - archive_dir: archiveDir, - timezone, - language - } = config - - // Destructure and set default options with object destructuring - const { - type = 'monthly', - format = type === 'monthly' ? 'MMMM YYYY' : 'YYYY', - show_count: showCount = true, - order = -1, - limit, - transform - } = options - - // Optimize locale handling - const lang = toMomentLocale(page.lang || page.language || language) - - // Memoize comparison function to improve performance - const compareFunc = type === 'monthly' - ? (yearA, monthA, yearB, monthB) => yearA === yearB && monthA === monthB - : (yearA, yearB) => yearA === yearB - - // Early return if no posts - if (!site.posts.length) return '' - - // Use reduce for more efficient data processing - const data = site.posts - .sort('date', order) - .reduce((acc, post) => { - let date = post.date.clone() - if (timezone) date = date.tz(timezone) - - const year = date.year() - const month = date.month() + 1 - - if (lang) date = date.locale(lang) - - // Find or create archive entry - const lastEntry = acc[acc.length - 1] - if (!lastEntry || !compareFunc( - lastEntry.year, - lastEntry.month, - year, - month - )) { - acc.push({ - name: date.format(format), - year, - month, - count: 1 - }) - } else { - lastEntry.count++ - } - - return acc - }, []) - - // Create link generator function - const createArchiveLink = item => { - let url = `${archiveDir}/${item.year}/` - if (type === 'monthly') { - url += item.month < 10 ? `0${item.month}/` : `${item.month}/` - } - return url_for(url) - } - - // Limit results efficiently - const limitedData = limit > 0 - ? data.slice(0, Math.min(data.length, limit)) - : data - - // Use template literal for better readability - const archiveHeader = ` -

- - ${_p('aside.card_archives')} - ${data.length > limitedData.length - ? ` - - ` - : ''} -
- ` - - // Use map for generating list items, join for performance - const archiveList = ` - - ` - - return archiveHeader + archiveList -}) - -// Improved locale conversion function -const toMomentLocale = lang => { - if (!lang || ['en', 'default'].includes(lang)) return 'en' - return lang.toLowerCase().replace('_', '-') -} diff --git a/themes/butterfly/scripts/helpers/aside_categories.js b/themes/butterfly/scripts/helpers/aside_categories.js deleted file mode 100644 index faeba94..0000000 --- a/themes/butterfly/scripts/helpers/aside_categories.js +++ /dev/null @@ -1,81 +0,0 @@ -'use strict' - -hexo.extend.helper.register('aside_categories', function (categories, options = {}) { - if (!categories || !Object.prototype.hasOwnProperty.call(categories, 'length')) { - options = categories || {} - categories = this.site.categories - } - - if (!categories || !categories.length) return '' - - const { config } = this - const showCount = Object.prototype.hasOwnProperty.call(options, 'show_count') ? options.show_count : true - const depth = options.depth ? parseInt(options.depth, 10) : 0 - const orderby = options.orderby || 'name' - const order = options.order || 1 - const categoryDir = this.url_for(config.category_dir) - const limit = options.limit === 0 ? categories.length : (options.limit || categories.length) - const isExpand = options.expand !== 'none' - const expandClass = isExpand && options.expand === true ? 'expand' : '' - const buttonLabel = this._p('aside.more_button') - - const prepareQuery = parent => { - const query = parent ? { parent } : { parent: { $exists: false } } - return categories.find(query).sort(orderby, order).filter(cat => cat.length) - } - - const hierarchicalList = (remaining, level = 0, parent) => { - let result = '' - if (remaining > 0) { - prepareQuery(parent).forEach(cat => { - if (remaining > 0) { - remaining -= 1 - let child = '' - if (!depth || level + 1 < depth) { - const childList = hierarchicalList(remaining, level + 1, cat._id) - child = childList.result - remaining = childList.remaining - } - - const parentClass = isExpand && !parent && child ? 'parent' : '' - result += `
  • ` - result += `` - result += `${cat.name}` - - if (showCount) { - result += `${cat.length}` - } - - if (isExpand && !parent && child) { - result += `` - } - - result += '' - - if (child) { - result += `
      ${child}
    ` - } - - result += '
  • ' - } - }) - } - return { result, remaining } - } - - const list = hierarchicalList(limit) - - const moreButton = categories.length > limit - ? ` - ` - : '' - - return `
    - - ${this._p('aside.card_categories')} - ${moreButton} -
    -
      - ${list.result} -
    ` -}) diff --git a/themes/butterfly/scripts/helpers/getArchiveLength.js b/themes/butterfly/scripts/helpers/getArchiveLength.js deleted file mode 100644 index bdeb87c..0000000 --- a/themes/butterfly/scripts/helpers/getArchiveLength.js +++ /dev/null @@ -1,45 +0,0 @@ -hexo.extend.helper.register('getArchiveLength', function () { - const archiveGenerator = hexo.config.archive_generator - const posts = this.site.posts - - const { yearly, monthly, daily } = archiveGenerator - const { year, month, day } = this.page - - // Archives Page - if (!year) return posts.length - - // Function to generate a unique key based on the granularity - const getKey = (post, type) => { - const date = post.date.clone() - const y = date.year() - const m = date.month() + 1 - const d = date.date() - if (type === 'year') return `${y}` - if (type === 'month') return `${y}-${m}` - if (type === 'day') return `${y}-${m}-${d}` - } - - // Create a map to count posts per period - const mapData = this.fragment_cache('createArchiveObj', () => { - const map = new Map() - posts.forEach(post => { - const keyYear = getKey(post, 'year') - const keyMonth = getKey(post, 'month') - const keyDay = getKey(post, 'day') - - if (yearly) map.set(keyYear, (map.get(keyYear) || 0) + 1) - if (monthly) map.set(keyMonth, (map.get(keyMonth) || 0) + 1) - if (daily) map.set(keyDay, (map.get(keyDay) || 0) + 1) - }) - return map - }) - - // Determine the appropriate key to fetch based on current page context - let key - if (yearly && year) key = `${year}` - if (monthly && month) key = `${year}-${month}` - if (daily && day) key = `${year}-${month}-${day}` - - // Return the count for the current period or default to the total posts - return mapData.get(key) || posts.length -}) diff --git a/themes/butterfly/scripts/helpers/inject_head_js.js b/themes/butterfly/scripts/helpers/inject_head_js.js deleted file mode 100644 index 16e9b9a..0000000 --- a/themes/butterfly/scripts/helpers/inject_head_js.js +++ /dev/null @@ -1,155 +0,0 @@ -'use strict' - -hexo.extend.helper.register('inject_head_js', function () { - const { darkmode, aside, pjax } = this.theme - const start = darkmode.start || 6 - const end = darkmode.end || 18 - const { theme_color } = hexo.theme.config - const themeColorLight = theme_color && theme_color.enable ? theme_color.meta_theme_color_light : '#ffffff' - const themeColorDark = theme_color && theme_color.enable ? theme_color.meta_theme_color_dark : '#0d0d0d' - - const createCustomJs = () => ` - const saveToLocal = { - set: (key, value, ttl) => { - if (!ttl) return - const expiry = Date.now() + ttl * 86400000 - localStorage.setItem(key, JSON.stringify({ value, expiry })) - }, - get: key => { - const itemStr = localStorage.getItem(key) - if (!itemStr) return undefined - const { value, expiry } = JSON.parse(itemStr) - if (Date.now() > expiry) { - localStorage.removeItem(key) - return undefined - } - return value - } - } - - window.btf = { - saveToLocal, - getScript: (url, attr = {}) => new Promise((resolve, reject) => { - const script = document.createElement('script') - script.src = url - script.async = true - Object.entries(attr).forEach(([key, val]) => script.setAttribute(key, val)) - script.onload = script.onreadystatechange = () => { - if (!script.readyState || /loaded|complete/.test(script.readyState)) resolve() - } - script.onerror = reject - document.head.appendChild(script) - }), - getCSS: (url, id) => new Promise((resolve, reject) => { - const link = document.createElement('link') - link.rel = 'stylesheet' - link.href = url - if (id) link.id = id - link.onload = link.onreadystatechange = () => { - if (!link.readyState || /loaded|complete/.test(link.readyState)) resolve() - } - link.onerror = reject - document.head.appendChild(link) - }), - addGlobalFn: (key, fn, name = false, parent = window) => { - if (!${pjax.enable} && key.startsWith('pjax')) return - const globalFn = parent.globalFn || {} - globalFn[key] = globalFn[key] || {} - globalFn[key][name || Object.keys(globalFn[key]).length] = fn - parent.globalFn = globalFn - } - } - ` - - const createDarkmodeJs = () => { - if (!darkmode.enable) return '' - - let darkmodeJs = ` - const activateDarkMode = () => { - document.documentElement.setAttribute('data-theme', 'dark') - if (document.querySelector('meta[name="theme-color"]') !== null) { - document.querySelector('meta[name="theme-color"]').setAttribute('content', '${themeColorDark}') - } - } - const activateLightMode = () => { - document.documentElement.setAttribute('data-theme', 'light') - if (document.querySelector('meta[name="theme-color"]') !== null) { - document.querySelector('meta[name="theme-color"]').setAttribute('content', '${themeColorLight}') - } - } - - btf.activateDarkMode = activateDarkMode - btf.activateLightMode = activateLightMode - - const theme = saveToLocal.get('theme') - ` - - switch (darkmode.autoChangeMode) { - case 1: - darkmodeJs += ` - const mediaQueryDark = window.matchMedia('(prefers-color-scheme: dark)') - const mediaQueryLight = window.matchMedia('(prefers-color-scheme: light)') - - if (theme === undefined) { - if (mediaQueryLight.matches) activateLightMode() - else if (mediaQueryDark.matches) activateDarkMode() - else { - const hour = new Date().getHours() - const isNight = hour <= ${start} || hour >= ${end} - isNight ? activateDarkMode() : activateLightMode() - } - mediaQueryDark.addEventListener('change', () => { - if (saveToLocal.get('theme') === undefined) { - e.matches ? activateDarkMode() : activateLightMode() - } - }) - } else { - theme === 'light' ? activateLightMode() : activateDarkMode() - } - ` - break - case 2: - darkmodeJs += ` - const hour = new Date().getHours() - const isNight = hour <= ${start} || hour >= ${end} - if (theme === undefined) isNight ? activateDarkMode() : activateLightMode() - else theme === 'light' ? activateLightMode() : activateDarkMode() - ` - break - default: - darkmodeJs += ` - theme === 'dark' ? activateDarkMode() : theme === 'light' ? activateLightMode() : null - ` - } - - return darkmodeJs - } - - const createAsideStatusJs = () => { - if (!aside.enable || !aside.button) return '' - return ` - const asideStatus = saveToLocal.get('aside-status') - if (asideStatus !== undefined) { - document.documentElement.classList.toggle('hide-aside', asideStatus === 'hide') - } - ` - } - - const createDetectAppleJs = () => ` - const detectApple = () => { - if (/iPad|iPhone|iPod|Macintosh/.test(navigator.userAgent)) { - document.documentElement.classList.add('apple') - } - } - detectApple() - ` - - return `` -}) diff --git a/themes/butterfly/scripts/helpers/page.js b/themes/butterfly/scripts/helpers/page.js deleted file mode 100644 index 9084919..0000000 --- a/themes/butterfly/scripts/helpers/page.js +++ /dev/null @@ -1,152 +0,0 @@ -'use strict' - -const { truncateContent, postDesc } = require('../common/postDesc') -const { prettyUrls } = require('hexo-util') -const crypto = require('crypto') -const moment = require('moment-timezone') - -hexo.extend.helper.register('truncate', truncateContent) - -hexo.extend.helper.register('postDesc', data => { - return postDesc(data, hexo) -}) - -hexo.extend.helper.register('cloudTags', function (options = {}) { - const env = this - let { source, minfontsize, maxfontsize, limit, unit = 'px', orderby, order } = options - - if (limit > 0) { - source = source.limit(limit) - } - - const sizes = [...new Set(source.map(tag => tag.length).sort((a, b) => a - b))] - - const getRandomColor = () => { - const randomColor = () => Math.floor(Math.random() * 201) - const r = randomColor() - const g = randomColor() - const b = randomColor() - return `rgb(${Math.max(r, 50)}, ${Math.max(g, 50)}, ${Math.max(b, 50)})` - } - - const generateStyle = (size, unit) => - `font-size: ${parseFloat(size.toFixed(2)) + unit}; color: ${getRandomColor()};` - - const length = sizes.length - 1 - const result = source.sort(orderby, order).map(tag => { - const ratio = length ? sizes.indexOf(tag.length) / length : 0 - const size = minfontsize + ((maxfontsize - minfontsize) * ratio) - const style = generateStyle(size, unit) - return `${tag.name}` - }).join('') - - return result -}) - -hexo.extend.helper.register('urlNoIndex', function (url = null, trailingIndex = false, trailingHtml = false) { - return prettyUrls(url || this.url, { trailing_index: trailingIndex, trailing_html: trailingHtml }) -}) - -hexo.extend.helper.register('md5', function (path) { - return crypto.createHash('md5').update(decodeURI(this.url_for(path))).digest('hex') -}) - -hexo.extend.helper.register('injectHtml', data => { - return data ? data.join('') : '' -}) - -hexo.extend.helper.register('findArchivesTitle', function (page, menu, date) { - if (page.year) { - const dateStr = page.month ? `${page.year}-${page.month}` : `${page.year}` - const dateFormat = page.month ? hexo.theme.config.aside.card_archives.format : 'YYYY' - return date(dateStr, dateFormat) - } - - const defaultTitle = this._p('page.archives') - if (!menu) return defaultTitle - - const loop = (m) => { - for (const key in m) { - if (typeof m[key] === 'object') { - const result = loop(m[key]) - if (result) return result - } - - if (/\/archives\//.test(m[key])) { - return key - } - } - } - - return loop(menu) || defaultTitle -}) - -hexo.extend.helper.register('getBgPath', path => { - if (!path) return '' - - const absoluteUrlPattern = /^(?:[a-z][a-z\d+.-]*:)?\/\//i - const relativeUrlPattern = /^(\.\/|\.\.\/|\/|[^/]+\/).*$/ - const colorPattern = /^(#|rgb|rgba|hsl|hsla)/i - - if (colorPattern.test(path)) { - return `background-color: ${path};` - } else if (absoluteUrlPattern.test(path) || relativeUrlPattern.test(path)) { - return `background-image: url(${path});` - } else { - return `background: ${path};` - } -}) - -hexo.extend.helper.register('shuoshuoFN', (data, page) => { - const { limit } = page - let finalResult = '' - - // Check if limit.value is a valid date - const isValidDate = date => !isNaN(Date.parse(date)) - - // order by date - const orderByDate = data => data.sort((a, b) => Date.parse(b.date) - Date.parse(a.date)) - - // Apply number limit or time limit conditionally - const limitData = data => { - if (limit && limit.type === 'num' && limit.value > 0) { - return data.slice(0, limit.value) - } else if (limit && limit.type === 'date' && isValidDate(limit.value)) { - const limitDate = Date.parse(limit.value) - return data.filter(item => Date.parse(item.date) >= limitDate) - } - - return data - } - - orderByDate(data) - finalResult = limitData(data) - - // This is a hack method, because hexo treats time as UTC time - // so you need to manually convert the time zone - finalResult.forEach(item => { - const utcDate = moment.utc(item.date).format('YYYY-MM-DD HH:mm:ss') - item.date = moment.tz(utcDate, hexo.config.timezone).format('YYYY-MM-DD HH:mm:ss') - }) - - return finalResult -}) - -hexo.extend.helper.register('getPageType', (page, isHome) => { - const { layout, tag, category, type, archive } = page - if (layout) return layout - if (tag) return 'tag' - if (category) return 'category' - if (archive) return 'archive' - if (type) { - if (type === 'tags' || type === 'categories') return type - else return 'page' - } - if (isHome) return 'home' - return 'post' -}) - -hexo.extend.helper.register('getVersion', () => { - const { version } = require('../../package.json') - return { hexo: hexo.version, theme: version } -}) diff --git a/themes/butterfly/scripts/helpers/related_post.js b/themes/butterfly/scripts/helpers/related_post.js deleted file mode 100644 index 69d9aaa..0000000 --- a/themes/butterfly/scripts/helpers/related_post.js +++ /dev/null @@ -1,97 +0,0 @@ -/* eslint-disable camelcase */ -/** - * Butterfly - * Related Posts - * According the tag - */ - -'use strict' - -const { postDesc } = require('../common/postDesc') - -hexo.extend.helper.register('related_posts', function (currentPost, allPosts) { - let relatedPosts = [] - const tagsData = currentPost.tags - tagsData.length && tagsData.forEach(function (tag) { - allPosts.forEach(function (post) { - if (currentPost.path !== post.path && isTagRelated(tag.name, post.tags)) { - const getPostDesc = post.postDesc || postDesc(post, hexo) - const relatedPost = { - title: post.title, - path: post.path, - cover: post.cover, - cover_type: post.cover_type, - weight: 1, - updated: post.updated, - created: post.date, - postDesc: getPostDesc - } - const index = findItem(relatedPosts, 'path', post.path) - if (index !== -1) { - relatedPosts[index].weight += 1 - } else { - relatedPosts.push(relatedPost) - } - } - }) - }) - - if (relatedPosts.length === 0) { - return '' - } - let result = '' - const hexoConfig = hexo.config - const config = hexo.theme.config - - const limitNum = config.related_post.limit || 6 - const dateType = config.related_post.date_type || 'created' - const headlineLang = this._p('post.recommend') - - relatedPosts = relatedPosts.sort(compare('weight')) - - if (relatedPosts.length > 0) { - result += '