[{"content":"我的第一个开源贡献：修复 DeepSeek-TUI 的 Markdown 渲染问题 背景 DeepSeek-TUI 是一个用 Rust 编写的终端 AI 助手，基于 DeepSeek V4 模型，支持 1M token 上下文。我在日常使用中发现 AI 的回复里 Markdown 格式完全没有被渲染，严重影响阅读体验。这是我第一次接触 Rust，也是我第一次向开源项目提交 PR。\n问题现象 使用时发现三类渲染问题：\n1. 表格原样输出\n| 名称 | 版本 | 状态 | |----------|------|------| | React | 18.x | 稳定 | 分隔行 |---| 也直接显示出来，没有任何格式化。\n2. 粗体/斜体标记符没有被剥掉\n**粗体文本** 和 *斜体文本* 星号原样显示，没有加粗或斜体效果。\n3. 水平线没有渲染\n--- 直接显示成三个横杠，没有渲染成分隔线。\n代码分析 项目的渲染逻辑在 crates/tui/src/tui/markdown_render.rs。读完代码后理解了它的设计：\n两阶段渲染架构 源文本 → parse() → ParsedMarkdown (AST) → render_parsed(width) → Vec\u0026lt;Line\u0026gt; parse 阶段：与终端宽度无关，把源文本分类成 Block 枚举的各种变体 render 阶段：依赖宽度，做折行和样式渲染 这个设计的好处是终端 resize 时只需重新 render，不需要重新 parse，性能更好。\n问题根因 问题一：Block 枚举缺少表格和水平线类型\n原来的 Block 枚举只有：\npub enum Block { Heading { level: usize, text: String }, HeadingRule, ListItem { bullet: String, text: String }, Code { line: String }, Paragraph { text: String }, Blank, } 没有 HorizontalRule 和 TableRow，所以 --- 和 | col | 都被当成普通段落原样输出。\n问题二：内联格式按词分割导致跨词标记识别失败\n原来的 render_line_with_links 函数是这样处理的：\nfor word in line.split_whitespace() { let is_link = looks_like_link(word); let style = if is_link { link_style } else { base_style }; // ... } 按空格分词后逐词判断，**bold text** 会被拆成 **bold 和 text** 两个词，每个词单独看都不符合 **...** 的格式，所以识别失败，星号原样输出。\n修复方案 第一步：新增 Block 变体 在 Block 枚举里加入两个新类型：\npub enum Block { Heading { level: usize, text: String }, HeadingRule, + /// A standalone `---` / `***` / `___` horizontal rule. + HorizontalRule, ListItem { bullet: String, text: String }, Code { line: String }, + /// A table row: cells split on `|`. Separator rows (`|---|`) are dropped. + TableRow(Vec\u0026lt;String\u0026gt;), Paragraph { text: String }, Blank, } 第二步：在 parse() 里识别新类型 在解析循环里加入水平线和表格的识别逻辑：\n+ if is_horizontal_rule(trimmed) { + blocks.push(Block::HorizontalRule); + continue; + } + + match parse_table_row(trimmed) { + Some(cells) =\u0026gt; { + blocks.push(Block::TableRow(cells)); + continue; + } + None if trimmed.starts_with(\u0026#39;|\u0026#39;) =\u0026gt; continue, // separator row — drop it + None =\u0026gt; {} + } + if raw_line.is_empty() { 关键点：parse_table_row 返回 None 且行以 | 开头时，说明是分隔行（|---|），直接 continue 丢弃，不降级为 Paragraph。\nis_horizontal_rule 实现：\nfn is_horizontal_rule(line: \u0026amp;str) -\u0026gt; bool { let stripped: String = line.chars().filter(|c| !c.is_whitespace()).collect(); (stripped.chars().all(|c| c == \u0026#39;-\u0026#39;) || stripped.chars().all(|c| c == \u0026#39;*\u0026#39;) || stripped.chars().all(|c| c == \u0026#39;_\u0026#39;)) \u0026amp;\u0026amp; stripped.len() \u0026gt;= 3 } 去掉空白字符后，判断是否全由 -、* 或 _ 组成且长度 ≥ 3。\nparse_table_row 实现：\nfn parse_table_row(line: \u0026amp;str) -\u0026gt; Option\u0026lt;Vec\u0026lt;String\u0026gt;\u0026gt; { if !line.starts_with(\u0026#39;|\u0026#39;) { return None; } let inner = line.trim_matches(\u0026#39;|\u0026#39;); let cells: Vec\u0026lt;String\u0026gt; = inner.split(\u0026#39;|\u0026#39;).map(|c| c.trim().to_string()).collect(); // Separator row: every non-empty cell is only dashes/colons/spaces if cells .iter() .all(|c| c.is_empty() || c.chars().all(|ch| ch == \u0026#39;-\u0026#39; || ch == \u0026#39;:\u0026#39; || ch == \u0026#39; \u0026#39;)) { return None; } Some(cells) } 去掉首尾 | 后按 | 分割，trim 每个单元格。如果所有非空单元格都只含 -、: 或空格，说明是分隔行，返回 None。\n第三步：重写内联格式解析 原来按词分割的方式无法处理跨词的 **bold text**，需要先扫描整行识别所有内联标记，再做折行。\n新增 parse_inline_spans 函数，返回 (文本, 样式) 的列表：\nfn parse_inline_spans(line: \u0026amp;str, base_style: Style, link_style: Style) -\u0026gt; Vec\u0026lt;(String, Style)\u0026gt; { let bold_style = base_style.add_modifier(Modifier::BOLD); let italic_style = base_style.add_modifier(Modifier::ITALIC); let mut out = Vec::new(); let mut rest = line; while !rest.is_empty() { // **bold** if let Some(end) = rest.strip_prefix(\u0026#34;**\u0026#34;).and_then(|s| s.find(\u0026#34;**\u0026#34;)) { let inner = \u0026amp;rest[2..2 + end]; out.push((inner.to_string(), bold_style)); rest = \u0026amp;rest[2 + end + 2..]; continue; } // __bold__ if let Some(end) = rest.strip_prefix(\u0026#34;__\u0026#34;).and_then(|s| s.find(\u0026#34;__\u0026#34;)) { let inner = \u0026amp;rest[2..2 + end]; out.push((inner.to_string(), bold_style)); rest = \u0026amp;rest[2 + end + 2..]; continue; } // *italic* if rest.starts_with(\u0026#39;*\u0026#39;) \u0026amp;\u0026amp; !rest.starts_with(\u0026#34;**\u0026#34;) \u0026amp;\u0026amp; let Some(end) = rest[1..].find(\u0026#39;*\u0026#39;) { let inner = \u0026amp;rest[1..1 + end]; out.push((inner.to_string(), italic_style)); rest = \u0026amp;rest[1 + end + 1..]; continue; } // _italic_ if rest.starts_with(\u0026#39;_\u0026#39;) \u0026amp;\u0026amp; !rest.starts_with(\u0026#34;__\u0026#34;) \u0026amp;\u0026amp; let Some(end) = rest[1..].find(\u0026#39;_\u0026#39;) { let inner = \u0026amp;rest[1..1 + end]; out.push((inner.to_string(), italic_style)); rest = \u0026amp;rest[1 + end + 1..]; continue; } // URL: consume until whitespace if rest.starts_with(\u0026#34;http://\u0026#34;) || rest.starts_with(\u0026#34;https://\u0026#34;) { let end = rest.find(char::is_whitespace).unwrap_or(rest.len()); let url = \u0026amp;rest[..end]; let content = if osc8::enabled() { osc8::wrap_link(url, url) } else { url.to_string() }; out.push((content, link_style)); rest = \u0026amp;rest[end..]; continue; } // Plain text: consume until next marker or URL let next = find_next_marker(rest).max(rest.chars().next().map_or(1, |c| c.len_utf8())); out.push((rest[..next].to_string(), base_style)); rest = \u0026amp;rest[next..]; } out } render_line_with_links 改为先调用 parse_inline_spans 得到带样式的 token 列表，再做折行：\n- for word in line.split_whitespace() { - let is_link = looks_like_link(word); - let style = if is_link { link_style } else { base_style }; - // ... - } + let tokens = parse_inline_spans(line, base_style, link_style); + let mut words: Vec\u0026lt;(String, Style)\u0026gt; = Vec::new(); + for (text, style) in tokens { + let mut first = true; + for part in text.split(\u0026#39; \u0026#39;) { + if !first { + words.push((\u0026#34; \u0026#34;.to_string(), style)); + } + if !part.is_empty() { + words.push((part.to_string(), style)); + } + first = false; + } + } 第四步：在 render_parsed() 里渲染新类型 + Block::HorizontalRule =\u0026gt; { + out.push(Line::from(Span::styled( + \u0026#34;─\u0026#34;.repeat(width.min(60)), + Style::default().fg(palette::TEXT_DIM), + ))); + } + Block::TableRow(cells) =\u0026gt; { + out.extend(render_table_row(cells, width, base_style)); + } render_table_row 实现：\nfn render_table_row(cells: \u0026amp;[String], width: usize, base_style: Style) -\u0026gt; Vec\u0026lt;Line\u0026lt;\u0026#39;static\u0026gt;\u0026gt; { if cells.is_empty() { return vec![Line::from(\u0026#34;\u0026#34;)]; } let col_width = (width.saturating_sub(3 * cells.len() + 1)) / cells.len(); let col_width = col_width.max(4); let sep_style = Style::default().fg(palette::TEXT_DIM); let mut spans: Vec\u0026lt;Span\u0026gt; = vec![Span::styled(\u0026#34;│ \u0026#34;.to_string(), sep_style)]; for (i, cell) in cells.iter().enumerate() { // 超出列宽时截断并加省略号 let truncated = if cell.width() \u0026gt; col_width { let mut s = String::new(); let mut w = 0; for ch in cell.chars() { let cw = ch.width().unwrap_or(1); if w + cw + 1 \u0026gt; col_width { s.push(\u0026#39;…\u0026#39;); break; } s.push(ch); w += cw; } s } else { cell.clone() }; // 单元格内容也走内联格式解析（支持表格里的粗体） let cell_spans = parse_inline_spans(\u0026amp;truncated, base_style, link_style()); let cell_width: usize = cell_spans.iter().map(|(t, _)| t.width()).sum(); let pad = col_width.saturating_sub(cell_width); for (text, style) in cell_spans { spans.push(Span::styled(text, style)); } spans.push(Span::raw(\u0026#34; \u0026#34;.repeat(pad))); if i + 1 \u0026lt; cells.len() { spans.push(Span::styled(\u0026#34; │ \u0026#34;.to_string(), sep_style)); } else { spans.push(Span::styled(\u0026#34; │\u0026#34;.to_string(), sep_style)); } } vec![Line::from(spans)] } 用 │（Unicode 竖线）而不是 ASCII | 作为分隔符，视觉上更整洁。列宽计算时用 UnicodeWidthChar 按字符实际显示宽度计算，正确处理中文等宽字符。\n第五步：PR 审查后的修复 PR 提交后，gemini-code-assist bot 指出了两处小问题：\n修复1：空格 token 的样式应继承当前 token 的样式\n-words.push((\u0026#34; \u0026#34;.to_string(), base_style)); +words.push((\u0026#34; \u0026#34;.to_string(), style)); 修复2：表格列宽计算没有考虑分隔符宽度\n-let col_width = (width.saturating_sub(cells.len() + 1)) / cells.len(); +let col_width = (width.saturating_sub(3 * cells.len() + 1)) / cells.len(); 每列分隔符占 3 个字符（│），原来只减了 1，导致列宽计算偏大，表格会溢出终端宽度。\n测试 新增了三个测试用例：\n#[test] fn table_separator_row_is_dropped() { let src = \u0026#34;| 项目属性 | 详情 |\\n|----------|------|\\n| **语言** | Rust 1.85+ |\\n\u0026#34;; let parsed = parse(src); let table_rows: Vec\u0026lt;_\u0026gt; = parsed.blocks.iter() .filter(|b| matches!(b, Block::TableRow(_))) .collect(); assert_eq!(table_rows.len(), 2); // 只有表头和数据行，没有分隔行 } #[test] fn bold_markers_stripped_in_render() { let src = \u0026#34;这是一个 **Rust 工作区项目**，包含多个 crate。\\n\u0026#34;; let lines = render_markdown(src, 80, Style::default()); let text: String = lines.iter() .flat_map(|l| l.spans.iter().map(|s| s.content.as_ref())) .collect(); assert!(!text.contains(\u0026#34;**\u0026#34;)); // 星号不能出现在输出里 assert!(text.contains(\u0026#34;Rust\u0026#34;)); // 内容要保留 } #[test] fn table_renders_with_pipe_separator() { let src = \u0026#34;| 文件 | 改动 |\\n|---|---|\\n| foo.rs | 重写 |\\n\u0026#34;; let lines = render_markdown(src, 60, Style::default()); let text: String = lines.iter() .flat_map(|l| l.spans.iter().map(|s| s.content.as_ref())) .collect(); assert!(text.contains(\u0026#39;│\u0026#39;)); // 用 Unicode 竖线 assert!(!text.contains(\u0026#34;|---|\u0026#34;)); // 分隔行不能出现 } 最终 12 个测试全部通过。\n结果 PR 被作者 merge，修复从 v0.8.10 起包含在所有版本中。项目当天冲上 GitHub Trending 第一，我的头像也挂在上面。\n注意：通过 npm install / deepseek update 安装的预编译二进制可能不包含此修复（取决于作者打包时间）。通过 cargo install 从源码编译可以确保拿到最新版本。\n收获 技术层面：\n第一次读懂并修改 Rust 项目，了解了枚举、模式匹配、字符串切片的基本用法 理解了\u0026quot;解析与渲染分离\u0026quot;的架构设计——parse 结果与终端宽度无关，resize 时只需重新 render 学会了用 UnicodeWidthChar 正确计算中文等宽字符的显示宽度 了解了预编译二进制分发（npm）和从源码编译（cargo install）的区别 流程层面：\n第一次完整走完 fork → 修改 → 测试 → PR → review → merge 的开源贡献流程 了解了 CI bot（gemini-code-assist）的代码审查方式，以及如何根据 review 意见修改代码 ","permalink":"https://wyxbupt.me/posts/%E5%BC%80%E6%BA%90%E5%B7%A5%E4%BD%9C%E7%B3%BB%E5%88%97/deepseek-tui-markdown%E6%A0%BC%E5%BC%8F%E4%BF%AE%E5%A4%8D/","summary":"\u003ch1 id=\"我的第一个开源贡献修复-deepseek-tui-的-markdown-渲染问题\"\u003e我的第一个开源贡献：修复 DeepSeek-TUI 的 Markdown 渲染问题\u003c/h1\u003e\n\u003ch2 id=\"背景\"\u003e背景\u003c/h2\u003e\n\u003cp\u003e\u003ca href=\"https://github.com/Hmbown/DeepSeek-TUI\"\u003eDeepSeek-TUI\u003c/a\u003e 是一个用 Rust 编写的终端 AI 助手，基于 DeepSeek V4 模型，支持 1M token 上下文。我在日常使用中发现 AI 的回复里 Markdown 格式完全没有被渲染，严重影响阅读体验。这是我第一次接触 Rust，也是我第一次向开源项目提交 PR。\u003c/p\u003e\n\u003ch2 id=\"问题现象\"\u003e问题现象\u003c/h2\u003e\n\u003cp\u003e使用时发现三类渲染问题：\u003c/p\u003e\n\u003cp\u003e\u003cstrong\u003e1. 表格原样输出\u003c/strong\u003e\u003c/p\u003e\n\u003cpre tabindex=\"0\"\u003e\u003ccode\u003e| 名称 | 版本 | 状态 |\n|----------|------|------|\n| React | 18.x | 稳定 |\n\u003c/code\u003e\u003c/pre\u003e\u003cp\u003e分隔行 \u003ccode\u003e|---|\u003c/code\u003e 也直接显示出来，没有任何格式化。\u003c/p\u003e\n\u003cp\u003e\u003cstrong\u003e2. 粗体/斜体标记符没有被剥掉\u003c/strong\u003e\u003c/p\u003e\n\u003cpre tabindex=\"0\"\u003e\u003ccode\u003e**粗体文本** 和 *斜体文本*\n\u003c/code\u003e\u003c/pre\u003e\u003cp\u003e星号原样显示，没有加粗或斜体效果。\u003c/p\u003e\n\u003cp\u003e\u003cstrong\u003e3. 水平线没有渲染\u003c/strong\u003e\u003c/p\u003e\n\u003cpre tabindex=\"0\"\u003e\u003ccode\u003e---\n\u003c/code\u003e\u003c/pre\u003e\u003cp\u003e直接显示成三个横杠，没有渲染成分隔线。\u003c/p\u003e\n\u003chr\u003e\n\u003ch2 id=\"代码分析\"\u003e代码分析\u003c/h2\u003e\n\u003cp\u003e项目的渲染逻辑在 \u003ccode\u003ecrates/tui/src/tui/markdown_render.rs\u003c/code\u003e。读完代码后理解了它的设计：\u003c/p\u003e\n\u003ch3 id=\"两阶段渲染架构\"\u003e两阶段渲染架构\u003c/h3\u003e\n\u003cpre tabindex=\"0\"\u003e\u003ccode\u003e源文本 → parse() → ParsedMarkdown (AST) → render_parsed(width) → Vec\u0026lt;Line\u0026gt;\n\u003c/code\u003e\u003c/pre\u003e\u003cul\u003e\n\u003cli\u003e\u003cstrong\u003eparse 阶段\u003c/strong\u003e：与终端宽度无关，把源文本分类成 \u003ccode\u003eBlock\u003c/code\u003e 枚举的各种变体\u003c/li\u003e\n\u003cli\u003e\u003cstrong\u003erender 阶段\u003c/strong\u003e：依赖宽度，做折行和样式渲染\u003c/li\u003e\n\u003c/ul\u003e\n\u003cp\u003e这个设计的好处是终端 resize 时只需重新 render，不需要重新 parse，性能更好。\u003c/p\u003e","title":"Deepseek-TUI Markdown格式修复"},{"content":"详细内容敬请期待\u0026ndash;这里先放个Longcat的论文\nSKILL0.pdf\n","permalink":"https://wyxbupt.me/posts/agentic-rl%E7%B3%BB%E5%88%97/longcat-skill0%E5%8F%8A%E5%85%B6%E5%BB%B6%E4%BC%B8%E6%80%9D%E8%80%83---skilllora/","summary":"\u003cp\u003e详细内容敬请期待\u0026ndash;这里先放个Longcat的论文\u003c/p\u003e\n\u003cp\u003e\u003ca href=\"/agentic-RL%E7%B3%BB%E5%88%97/SKILL0.pdf\"\u003eSKILL0.pdf\u003c/a\u003e\u003c/p\u003e","title":"LONGCAT-SKILL0及其延伸思考---SkillLoRA"},{"content":"敬请期待\n","permalink":"https://wyxbupt.me/posts/agentic-rl%E7%B3%BB%E5%88%97/rock-as-backend-harbor%E6%90%AD%E5%BB%BA/","summary":"\u003cp\u003e敬请期待\u003c/p\u003e","title":"ROCK-as-Backend-Harbor搭建"},{"content":"今日最大感悟 可以都记在这里 就比如这个网站的使用方法\n博客维护手册 最后更新：2026-05-04\n一、架构概览 你的电脑 服务器 (134.209.97.93)\n┌──────────────────────┐ ┌──────────────────┐\n│ blog-papers/ (Obsidian)│ 部署脚本 │ /var/www/blog/ │\n│ ├ llm学习记录/ │──Hugo构建──▶│ Nginx │\n│ ├ 通信原理/ │ SCP上传 │ ↓ │\n│ ├ 胡思乱想/ │ │ wyxbupt.me │\n│ └ templates/ │ │ │\n├──────────────────────┤ └──────────────────┘\n│ blog-scripts/ │\n│ └ deploy-blog.ps1 │\n│ └ rebuild-section-indexes.py │\n└──────────────────────┘\n二、日常使用 写文章 打开 Obsidian，vault 在 E:\\_MyCollegeLife\\blog-papers\n在对应文件夹里新建笔记：\nllm学习记录/ → LLM相关\n通信原理/ → 通信课程\n胡思乱想/ → 随笔杂谈\n用模板：Ctrl+T → 选择 博客模板\n写正文\n部署 powershell -File E:_MyCollegeLife\\blog-scripts\\deploy-blog.ps1\n部署脚本自动做：\n扫描所有 .md 文件，补全缺失的 frontmatter（标题、日期、分类）\n自动将日期补上 +08:00 时区（防止被 Hugo 当成未来文章排除）\n重建每个板块的 _index.md\nHugo 构建\nSCP 上传到服务器\n查看效果 页面 URL 首页/全部文章 https://wyxbupt.me/ LLM学习记录 https://wyxbupt.me/posts/llm学习记录/ 通信原理 https://wyxbupt.me/posts/通信原理/ 胡思乱想 https://wyxbupt.me/posts/胡思乱想/ 三、Markdown 写作语法 基础 一级标题 二级标题 粗体 斜体 删除线 代码\n​\n无序列表 有序列表 引用\n​\n链接\n代码块 def hello(): print(\u0026#34;Hello\u0026#34;) 数学公式（LaTeX） 行内：$E = mc^2$\n块公式：\n$$ \\int_0^\\infty e^{-x^2} dx = \\frac{\\sqrt{\\pi}}{2} $$多行对齐：\n$$ \\begin{aligned} Q_\\theta(s_t,a) \u0026= r_t + \\gamma V_\\theta(s_{t+1}) \\\\ A_\\theta(s_t,a) \u0026= Q_\\theta(s_t,a) - V_\\theta(s_t) \\end{aligned} $$支持的数学环境：aligned、align*、cases、matrix 等。\n图片 Obsidian 粘贴图片后，把 ![[Pasted image xxx.png]] 改成：\n图片文件存在 E:\\_MyCollegeLife\\blog-papers\\（Obsidian vault 根目录），会自动发布。\nPDF / 附件链接 PDF 放在 blog-papers 下面也会自动发布，但不会自动出现在文章列表里，需要在 .md 里手动写链接。\n不要写 Obsidian 的 [[SKILL0.pdf]]，博客里要写标准 Markdown 链接：\n[SKILL0.pdf](/agentic-RL系列/SKILL0.pdf) 规则是：\n[显示文字](/目录名/文件名.pdf) 例如文件在：\nE:\\_MyCollegeLife\\blog-papers\\agentic-RL系列\\SKILL0.pdf 网站链接就是：\n[SKILL0.pdf](/agentic-RL系列/SKILL0.pdf) 不要用 ./SKILL0.pdf。文章页面生成后 URL 会多一层文章 slug，浏览器可能会去错误的目录找文件，导致 404。\nCallout（提示框） [!note] 笔记\n内容\n​\n[!warning] 注意\n内容\n表格 列1 列2 列3 A B C 四、Frontmatter 说明 部署脚本会自动补全，也可以手动写：\ntitle: \u0026ldquo;文章标题\u0026rdquo;\ndate: \u0026ldquo;2026-05-04T00:00:00+08:00\u0026rdquo; # 时区必须 +08:00！\ndraft: false\ntags: [标签1, 标签2]\ncategories: [\u0026ldquo;板块名\u0026rdquo;]\nsummary: \u0026ldquo;摘要（显示在卡片上）\u0026rdquo;\nmath: true # 如果这篇文章需要LaTeX（默认全局开启） 重要：日期必须带时区 +08:00！ 部署脚本会自动补，不用担心。\n五、已知问题与解决 新文章不显示？ 部署脚本已自动修复：每次部署会扫描所有日期，补上 +08:00\n如果还是不显示，检查 draft: false\nLaTeX公式不渲染？ Hugo 已配置 Goldmark passthrough，$$ 和 $ 会原样保留给 KaTeX\n浏览器硬刷新：Ctrl+F5\n图片不显示？ 把 Obsidian 的 ![[...]] 改成标准 ![](/xxx.png)\n图片存在 blog-papers/ 下任意位置即可\n终端显示乱码？ 只是 PowerShell 显示问题，文件本身是 UTF-8，不影响网站\n用 Python 操作中文文件更安全\n六、修改过的文件 文件 说明 blog-papers/胡思乱想/平庸之恶.md 日期加时区 blog/layouts/posts/section.html 板块页卡片渲染 blog/layouts/partials/math.html KaTeX 数学支持 blog/layouts/partials/extend_head.html 加载 KaTeX blog-scripts/rebuild-section-indexes.py 板块索引生成 blog-scripts/deploy-blog.ps1 日期自动规范化 blog/hugo.toml math + Goldmark passthrough + 静态文件挂载 七、备份 文章归档在 E:\\_MyCollegeLife\\blog-archive\\，不在 Hugo 发布范围内。\n","permalink":"https://wyxbupt.me/posts/%E8%83%A1%E6%80%9D%E4%B9%B1%E6%83%B3/%E5%AD%A6%E4%B9%A0%E5%90%84%E7%A7%8D%E4%B8%9C%E8%A5%BF%E5%90%8E%E4%B8%80%E5%AE%9A%E8%A6%81%E8%AE%B0%E7%AC%94%E8%AE%B0/","summary":"\u003cp\u003e今日最大感悟 可以都记在这里 就比如这个网站的使用方法\u003c/p\u003e\n\u003ch1 id=\"博客维护手册\"\u003e博客维护手册\u003c/h1\u003e\n\u003cblockquote\u003e\n\u003cp\u003e最后更新：2026-05-04\u003c/p\u003e\n\u003c/blockquote\u003e\n\u003chr\u003e\n\u003ch2 id=\"一架构概览\"\u003e一、架构概览\u003c/h2\u003e\n\u003cp\u003e你的电脑                              服务器 (134.209.97.93)\u003cbr\u003e\n┌──────────────────────┐              ┌──────────────────┐\u003cbr\u003e\n│ blog-papers/ (Obsidian)│  部署脚本     │ /var/www/blog/    │\u003cbr\u003e\n│  ├ llm学习记录/        │──Hugo构建──▶│  Nginx            │\u003cbr\u003e\n│  ├ 通信原理/           │   SCP上传    │  ↓                │\u003cbr\u003e\n│  ├ 胡思乱想/           │              │  wyxbupt.me       │\u003cbr\u003e\n│  └ templates/         │              │                  │\u003cbr\u003e\n├──────────────────────┤              └──────────────────┘\u003cbr\u003e\n│ blog-scripts/         │\u003cbr\u003e\n│  └ deploy-blog.ps1    │\u003cbr\u003e\n│  └ rebuild-section-indexes.py │\u003cbr\u003e\n└──────────────────────┘\u003c/p\u003e\n\u003ch2 id=\"二日常使用\"\u003e二、日常使用\u003c/h2\u003e\n\u003ch3 id=\"写文章\"\u003e写文章\u003c/h3\u003e\n\u003col\u003e\n\u003cli\u003e\n\u003cp\u003e打开 Obsidian，vault 在 \u003ccode\u003eE:\\_MyCollegeLife\\blog-papers\u003c/code\u003e\u003c/p\u003e\n\u003c/li\u003e\n\u003cli\u003e\n\u003cp\u003e在对应文件夹里新建笔记：\u003c/p\u003e\n\u003cul\u003e\n\u003cli\u003e\n\u003cp\u003e\u003ccode\u003ellm学习记录/\u003c/code\u003e → LLM相关\u003c/p\u003e\n\u003c/li\u003e\n\u003cli\u003e\n\u003cp\u003e\u003ccode\u003e通信原理/\u003c/code\u003e → 通信课程\u003c/p\u003e","title":"学习各种东西后一定要记笔记"},{"content":"","permalink":"https://wyxbupt.me/posts/%E8%83%A1%E6%80%9D%E4%B9%B1%E6%83%B3/5.22-%E7%9C%8B%E5%A4%A7%E5%8E%82%E9%9D%92%E5%B9%B4%E5%9B%B0%E5%A2%83%E5%90%8E%E7%9A%84%E6%84%9F%E6%83%B3/","summary":"","title":"5.22 看大厂青年困境后的感想"},{"content":"1.1 通信的基本概念 通信是从一地向另一地传递消息的过程。\n1.2 通信系统模型 基本的通信系统包括：信源、发送设备、信道、接收设备、信宿。\n","permalink":"https://wyxbupt.me/posts/%E9%80%9A%E4%BF%A1%E5%8E%9F%E7%90%86/%E9%80%9A%E4%BF%A1%E5%8E%9F%E7%90%86%E7%AC%AC%E4%B8%80%E7%AB%A0/","summary":"通信系统基本模型、信息与信号基础概念","title":"通信原理第一章 - 绪论"},{"content":"这是测试文章：Obsidian 写笔记 → Hugo 构建 → 自动部署。\n如果你在网站上看到这段文字，说明发布流程正常工作！\n","permalink":"https://wyxbupt.me/posts/llm%E5%AD%A6%E4%B9%A0%E8%AE%B0%E5%BD%95/test-workflow/","summary":"\u003cp\u003e这是测试文章：Obsidian 写笔记 → Hugo 构建 → 自动部署。\u003c/p\u003e\n\u003cp\u003e如果你在网站上看到这段文字，说明发布流程正常工作！\u003c/p\u003e","title":"Test Workflow"},{"content":"harbor opencode\n","permalink":"https://wyxbupt.me/posts/llm%E5%AD%A6%E4%B9%A0%E8%AE%B0%E5%BD%95/agent-harness/","summary":"\u003cp\u003eharbor opencode\u003c/p\u003e","title":"agent-harness"},{"content":"ppo、grpo、gspo\n","permalink":"https://wyxbupt.me/posts/llm%E5%AD%A6%E4%B9%A0%E8%AE%B0%E5%BD%95/ppo/","summary":"\u003cp\u003eppo、grpo、gspo\u003c/p\u003e","title":"ppo"},{"content":"从强化学习到PPO的理论部分（学习笔记） 从强化学习到PPO的理论部分（学习笔记）1.强化学习的基本概念2.理论推导部分3.针对R(τ)n的优化3.1对R(τ)n引入时间步的考量3.2状态价值函数，动作价值函数，优势函数3.3时序差分和GAE4.Proximal Policy Optimization(PPO)邻近策略优化4.1On-policy和Off-Policy4.2重要性采样4.3最终的形式\n最近从头开始学习强化学习以及PPO，花了两天的时间才搞懂了一些基本的概念和流程。主要参考了B站UP主RethinkFun的《零基础学习强化学习算法：ppo》，在这里记录一下自己的学习笔记。\n1.强化学习的基本概念 State（状态），\nAction（动作），\nEnvironment（系统设定等环境），\nAgent（智能体或模型）\nReward：奖励，指Agent在某个State下执行了某个Action与Environment交互之后得到的奖励,记为r\nPolicy：策略，主要指Agent在某个State下执行各个Action的概率分布，记为$\\pi_{\\theta}(a|s)$\nTrajectory：轨迹，指在策略$\\theta$下的某次具体的路径，也即Policy的一次采样值，记为$\\tau$，由一连串的${{s_{1},a_1,s_2,a_2,\\ldots}}$构成\nReturn:回报，指某次Trajectory结束后得到的累积Reward\n所以强化学习的目标是：训练一个Policy神经网络$\\pi$,在所有状态State下，做出相应的Action，得到相应的Reward，使得Return的期望最大\n或者表述为：训练一个Policy神经网络$\\pi$,在所有的Trajectory下，Return的期望最大\n2.理论推导部分 前面提到，强化学习的目标是最大化Return的期望，Return的期望可以表示为\n$$ E(R(\\tau))_{\\tau\\in P_\\theta(\\tau)} = \\sum_\\tau R(\\tau)*P_\\theta (\\tau) $$而对于公式中的 $P_{\\theta}(\\tau)$ ,表示的是该策略下选到该轨迹的概率，根据Trajectory的定义，其可以表示为：\n$$ P_\\theta (\\tau) = \\prod_{t=1}^T \\pi_\\theta(a_t|s_t)*p(s_{t+1}|s_t,a_t) $$这里要怎么理解呢？根据Trajectory的定义，轨迹是由一连串的$s_1,a_1,s_2,a_2,\\ldots,s_t,a_t$构成，因此可由某状态下执行某动作的概率*某个动作导致下一状态的概率连乘表示，其中$\\pi_\\theta(a_t|s_t)$由Policy决定（由下标也可以看出来），而$p(s_{t+1}|s_t,a_t)$由Environment决定。\n接着，我们知道在深度学习中，不管是求损失函数的最小值还是目标函数的最大值，都需要用到梯度下降法或者梯度上升法，所以这里我们来求$E(R(\\tau))_{\\tau\\in P_\\theta(\\tau)}$的梯度\n$$ \\begin{align*} \\nabla_\\theta E(R(\\tau))_{\\tau\\in P_\\theta(\\tau)}\u0026= \\nabla_\\theta \\sum_\\tau R(\\tau)*P_\\theta (\\tau) \\\\ \u0026=\\sum_\\tau R(\\tau)*\\nabla_\\theta P_\\theta(\\tau)\\\\ \\end{align*} $$这里涉及到一个技巧：$\\nabla ln(f(x)) = \\frac{\\nabla f(x)}{f(x)}$,因此我们可以用$P_\\theta(\\tau) \\nabla_\\theta ln(P_\\theta (\\tau))$替换掉公式中的$\\nabla_\\theta P_\\theta (\\tau)$,得到\n$$ \\begin{align*} \u0026=\\sum_\\tau R(\\tau)*P_\\theta(\\tau)\\nabla_\\theta ln(P_\\theta(\\tau))\\\\ \u0026=\\sum_\\tau R(\\tau)*P_\\theta(\\tau)\\sum_{t=1}^T\\nabla_\\theta ln\\pi_\\theta(a_t|s_t)\\\\ \u0026=E_{\\tau\\in P_\\theta(\\tau)}(R(\\tau)\\sum_{t=1}^T\\nabla_\\theta ln\\pi_\\theta(a_t|s_t)) \\end{align*} $$这里不难理解，用到了期望的基本定义；之后我们再次用到期望的一个性质：\n$$ E(X)_{x\\in p(X)} = \\sum_x x*p(x)\\approx \\frac{1}{n}\\sum_{i=1}^nx _{x\\in p(x)} $$因此可得\n$$ \\begin{align*} \u0026=\\frac{1}{N}\\sum_{n=1}^NR(\\tau^n)\\sum_{t=1}^T\\nabla_\\theta ln\\pi_\\theta(a_t|s_t)\\\\ \u0026=\\frac{1}{N}\\sum_{n=1}^N\\sum_{t=1}^TR(\\tau^n)\\nabla_\\theta ln\\pi_\\theta(a_t|s_t) \\end{align*} $$到这里，我们已经得到了训练的目标函数，目标函数的梯度，为了与深度学习统一，我们这里给目标函数加上负号作为损失函数进行统一处理\n$$ Loss =-\\frac{1}{N}\\sum_{n=1}^N\\sum_{t=1}^TR(\\tau^n)ln\\pi_\\theta(a_t|s_t) $$式中的$R(\\tau^n)$由n次采样实际得到\n$\\pi_\\theta(a_t|s_t)$由策略神经网络得到\n3.针对$R(\\tau)^n$的优化 3.1对$R(\\tau)^n$引入时间步的考量 在上面的公式中，奖励函数$R(\\tau^n)$被定义为一次轨迹后得到的总的累积回报，与时间步t无关，用这个累积回报来对此次策略中的所有action进行调整，这么做其实并不太客观，因为某个action发生后，他只会对当前及以后的reward产生影响，且随着时间步的推移，这个影响应当越来越小。\n举个例子：如果在第一步，agent就选择了一个十分糟糕的action导致扣分100分，即使后面的action都在补救，也导致最后的return很低，这种情况下如果对所有的action都进行低概率方向的调整显然是不合理的\n因此我们引入单步的奖励函数来评估每一步具体动作的奖励，并引入衰减因子$\\gamma$来表示其对后续时间步影响的衰减，具体公式如下：\n$$ R(\\tau^n)\\to\\sum_{t^{\\prime}=t}^{T_n}\\gamma^{t^{\\prime}-t}r_{t^{\\prime}}^n=R_t^n $$因此我们原本的公式变为\n$$ \\frac{1}{N}\\sum_{n=1}^N\\sum_{t=1}^TR_t^n\\nabla_\\theta ln\\pi_\\theta(a_t|s_t) $$此外还有一种情况：当某个state下所有action都能得到正的reward或者都得到负的reward（但具体数值不同）时，目前的公式可能无法体现各个action的相对优势，因此我们可以在R_t^n基础上减去一个baseline，得到如下公式\n$$ \\frac{1}{N}\\sum_{n=1}^N\\sum_{t=1}^T(R_t^n-B(s_n^t))\\nabla_\\theta ln\\pi_\\theta(a_t|s_t) $$到这里公式其实已经很完备了，但是问题来了，R_t^n是怎么得到的呢？是通过完成某个Trajectory后得到的实际各步的奖励计算得来的，也就是对奖励分布的一次随机采样，方差很大。 这里可能不好理解，我解释一下，由于Environment是具有随机性的，因此即使在相同的State下采取了相同的Action，得到的Reward也并不一定，而是服从一定的概率分布，所以如果对其进行随机采样，就会导致方差较大的问题。\n这里用AI举的一个例子辅助说明：\n想象你正在工地搬砖计算日工资：\n$R_t^n$就像你某一天实际干完所有活拿到的具体工资（比如今天搬了100块，工头给了250块）。天气、工头心情、意外因素都会影响它，每天波动很大。 3.2状态价值函数，动作价值函数，优势函数 因此我们接着引入了以下概念：\nAction-Value Function :\n动作价值函数$Q_\\theta(s,a)$，在State s下做出Action a得到的期望回报\nState-Value Function：\n状态价值函数$V_\\theta(s)$,在State s下的期望回报\nAdvantage Function：\n优势价值函数$A_\\theta(s,a) = Q_\\theta(s,a)-V_\\theta(s)$,在状态s下，做出动作a，能带来多少的优势\n重要：这里得到的几个值，都是估计值，或者叫预测值，通常是通过训练一个状态价值模型/动作价值模型来进行预测\n这么做的好处是，不仅避免了单次采样方差大的问题，还能在本次轨迹没有结束时给出某一动作带来的优势或好处\n（当然可能有人会问，这么做是减小了方差，那难道不会导致偏差变大吗？这个问题我会在之后解释）\n在这里我们可以很直观的看出，$A_\\theta(s,a) = Q_\\theta(s,a)-V_\\theta(s)$ 是用来替换原公式中的$R_t^n-B(s_n^t)$\n所以这时候我们的公式变成了\n$$ \\frac{1}{N}\\sum_{n=1}^N\\sum_{t=1}^TA_\\theta(s,a) \\nabla_\\theta ln\\pi_\\theta(a_t|s_t) $$现在我们来考虑一下$Q_\\theta(s,a)$**和$V_\\theta(s)$的关系：\n这里直接给出公式：\n$\\begin{aligned}\u0026Q_{\\theta}(s_{t},a)=r_{t}+\\gamma*V_{\\theta}(s_{t+1})\\\\\u0026A_{\\theta}(s_{t},a)=r_{t}+\\gamma*V_{\\theta}(s_{t+1})-V_{\\theta}(s_{t})\\end{aligned}$\n这两个公式也很直观：某个动作的价值=执行该动作后立刻获得的短期价值+其导致的下一状态的状态价值（是一种长期价值）\n这样要求优势价值函数，只需要训练一个状态价值函数模型，而不需要同时训练状态价值模型和动作价值模型来进行预测\n3.3时序差分和GAE 回收之前的问题，既然$V_\\theta(s_{t+1})$是预测值/估计值，怎么保证$V_\\theta(s_{t+1})$的估计不会有过大的方差和偏差呢？\n我们从状态价值的定义出发，可以想到，一个状态价值的期望，是对该状态下选择的动作的价值以及各个动作导致的新状态的价值求期望，用公式可以表示为\n$$ V_\\theta(s_{t+1}) = E_{a_{t+1}\\in\\pi_\\theta(\\cdot|s_{t+1})}[E_{s_{t+2}\\in p(\\cdot|s_{t+1},a_{t+1})}[r_{t+1}+\\gamma V_\\theta(s_{t+2})]] $$其中：\n内层期望：对 状态转移概率$p(s_{t+2}|s_{t+1},a_{t+1})$积分（环境随机性）\n外层期望：对策略概率$\\pi_\\theta(a_{t+1}|s_{t+1})$积分（动作随机性）\n但是，在算法的实际训练过程中是无法直接计算双期望的（需遍历所有可能的$a_{t+1}$和$s_{t+2})$，因此我们采用采样的方法来近似期望值：\n$$ V_\\theta(s_{t+1}) \\approx r_{t+1} + \\gamma V_\\theta(s_{t+2}) $$这里其实仍然存在$R^n_t$那里的问题，随机采样一定会引入方差，且即使经过迭代，公式里仍然有$V_\\theta(s_{t+2})$,仍然是一个估计值，一定有偏差。\n针对这两个问题，我们继续往下看。不难发现，公式中的$V_\\theta(s_{t+2})$仍然可以用相同的方式进行采样近似，得到两步乃至多步采样的估计值。\n那随着采样步数的增加，对于$V_\\theta(s_{t+1})$的估计的方差和偏差分别是怎么变化呢？\n这里先说结论：方差增大，偏差减小\n方差增大很好理解，因为每多一步采样都是对r_t的采样，都会引入短期奖励的方差\n那怎么理解偏差减小呢？\n这里举一个例子：\n当你在从某地驱车赶往另一地，一开始导航软件提示预估到达时间为30分钟，当你驾驶10分钟之后再看导航，预估到达时间是18分钟，那么很显然，对于更短路程的估计（18分钟）+已经确定的10分钟是比一开始估计的30分钟更准确的，也就是说，对于这段路程，28分钟是比30分钟偏差更小的估计。\n这里的10分钟其实就代表了公式中采样确定的r_{t},对r_t进行越多时间步的采样，也即已知条件越多，未知时间步越少，对V_\\theta(s_{t})的估计越准确，偏差也就越小。\n这种方法叫做TD（时序差分方法）\n这里我们定义一个中间变量$\\delta$以简化书写:\n$$ \\begin{aligned}\u0026\\delta_{t}^{V}=r_{t}+\\gamma*V_{\\theta}(s_{t+1})-V_{\\theta}(s_{t})\\\\\u0026\\delta_{t+1}^{V}=r_{t+1}+\\gamma*V_{\\theta}(s_{t+2})-V_{\\theta}(s_{t+1})\\end{aligned} $$这样上面的优势函数的分步采样可以写为：\n$$ \\begin{aligned}\u0026A_\\theta^1(s_t,a)=\\delta_{t}^{V}\\\\\u0026A_\\theta^2(s_t,a)=\\delta_{t}^{V}+\\gamma\\delta_{t+1}^{V}\\\\\u0026A_\\theta^3(s_t,a)=\\delta_{t}^{V}+\\gamma\\delta_{t+1}^{V}+\\gamma^{2}\\delta_{t+2}^{V}\\\\\u0026:\\end{aligned} $$接下来我们就要考虑一个问题：那我们要采样多少步来平衡偏差和方差呢？\nGAE给出的思路是：我全都要\n公式如下：\n$$ A_\\theta^{GAE}(s_{t},a) =(1-\\lambda)(A_\\theta^1+\\lambda A_\\theta^2+\\lambda ^2A_\\theta^3+\\cdots) $$GAE的思路是对不同步的采样值进行加权平均，这里的\\lambda是一个超参数。\n将\\delta代入可以得到\n$$ \\begin{align*} A_\\theta^{GAE}(s_{t},a) \u0026=(1-\\lambda)(A_\\theta^1+\\lambda A_\\theta^2+\\lambda ^2A_\\theta^3+\\cdots)\\\\ \u0026\\begin{aligned}\u0026=(1-\\lambda)(\\delta_{t}^{\\nu}+\\lambda*(\\delta_{t}^{\\nu}+\\gamma\\delta_{t+1}^{\\nu})+\\lambda^{2}(\\delta_{t}^{\\nu}+\\gamma\\delta_{t+1}^{\\nu}+\\gamma^{2}\\delta_{t+2}^{\\nu})+\\cdots)\\\\\u0026=(1-\\lambda)(\\delta_{t}^{\\nu}(1+\\lambda+\\lambda^{2}+\\cdots)+\\gamma\\delta_{t+1}^{\\nu}*(\\lambda+\\lambda^{2}+\\cdots)+\\cdots)\\\\\u0026=(1-\\lambda)(\\delta_{t}^{\\nu}\\frac{1}{1-\\lambda}+\\gamma\\delta_{t+1}^{\\nu}\\frac{\\lambda}{1-\\lambda}+\\cdots)\\\\\u0026=\\sum_{b=0}^{\\infty}(\\gamma\\lambda)^{b}\\delta_{t+b}^{\\nu}\\end{aligned} \\end{align*} $$至此我们得到了如下公式：\n$$ \\begin{aligned} \u0026\\delta_{t}^{V}=r_{t}+\\gamma*V_{\\theta}(s_{t+1})-V_{\\theta}(s_{t})\\\\ \u0026A_{\\theta}^{GAE}(s_{t},a)=\\sum_{b=0}^{\\infty}(\\gamma\\lambda)^{b}\\delta_{t+b}^{V}\\\\ \u0026\\frac{1}{N}\\sum_{n=1}^{N}\\sum_{t=1}^{T_{n}}A_{\\theta}^{GAE}(s_{n}^{t},a_{n}^{t})\\nabla_\\theta\\mathrm{ln}\\pi_{\\theta}(a_{n}^{t}|s_{n}^{t})\\\\ \u0026Loss =-\\frac{1}{N}\\sum_{n=1}^{N}\\sum_{t=1}^{T_{n}}A_{\\theta}^{GAE}(s_{n}^{t},a_{n}^{t})\\mathrm{ln}\\pi_{\\theta}(a_{n}^{t}|s_{n}^{t}) \\end{aligned} $$这里的$V_\\theta(s_t)$​由训练好的状态价值网络进行估计。很显然的是，状态价值网络并不是训练好后就不再变化的，其参数应该与策略网络一起迭代。实际上，由于状态价值网络和策略网络输入均为状态，二者共用输入层和特征提取层，这部分称为共享层；但有各自的输出层，策略网络的输出层是softmax层对各个动作的概率做决策，而价值网络输出的价值的数值。\n策略网络和价值网络的这种模式被称为**Actor-Critic(演员-评论家）**架构\n损失函数：最小化时序差分误差 (TD Error) 的均方：\n$$ L_v = \\frac{1}{N}\\sum||V_\\theta(s_t)-(r_t+\\gamma V_{\\theta}(s_{t+1}))||^2 $$我们已经得到了策略网络和价值网络训练的损失函数，在实际训练过程中，二者的共享层参数更新要综合考虑两个损失函数，共享层参数兼具“做决策”和“打分”的功能，而输出层参数更新则使用各自的损失函数进行更新。\n4.Proximal Policy Optimization(PPO)邻近策略优化 4.1On-policy和Off-Policy 我们先讲一个例子：\n课堂上，老师针对小明的表现进行表扬或批评，如果老师表扬小明，小明就加强老师表扬的行为，如果老师批评小明，小明就减少老师批评的行为，这种模式叫做On-Policy,也就是我们前面推导的方式。这种方式中，获得数据和进行训练的模型是同一个，也就是说，模型需要先进行交互得到数据，再用数据训练模型，再用新模型获取新数据，再进行训练，这样循环往复。这样做存在的一个问题是：\n每次得到的数据用完即丢，模型将耗费大量的时间在获取数据上，大大限制了训练速度和效率。\n那有什么办法来提高效率呢？\n回到刚刚的例子中，如果你是小明的同学，你是不是可以根据老师对小明的批评和表扬来调整自己的行为呢？老师表扬小明的行为，你也去加强，老师批评小明的行为，你也去减少，这种模式就叫做Off-Policy\n当然，你毕竟不是小明，所以你的调整应该根据你的行为与小明行为的相对比较来调整，比如说小明上课玩手机的概率是0.2，而你上课玩手机的概率是0.4，那么小明被批评后你降低玩手机概率的强度就应该是小明的二倍。这就叫重要性采样\n4.2重要性采样 $$ \\begin{aligned}\\operatorname{E}(f(x))_{x\\sim p(x)}\u0026=\\sum_{x}f(x)*p(x)\\\\\u0026=\\sum_{x}f(x)*p(x)\\frac{q(x)}{q(x)}\\\\\u0026=\\sum_{x}f(x)\\frac{p(x)}{q(x)}*q(x)\\\\\u0026=\\mathrm{E}(f(x)\\frac{p(x)}{q(x)})_{x\\sim q(x)}\\\\\u0026\\approx\\frac{1}{N}\\sum_{n=1}^{N}f(x)\\frac{p(x)}{q(x)}_{x\\sim q(x)}\\end{aligned} $$利用重要性采样，我们就可以将原本的On-Policy替换为Off-Policy\n$$ \\begin{aligned}\u0026\\frac{1}{N}\\sum_{n=1}^N\\sum_{t=1}^{T_n}A_\\theta^{GAE}(s_n^t,a_n^t)\\nabla\\mathrm{log}P_\\theta(a_n^t|s_n^t)----On-Policy\\\\ \u0026\\to \\frac{1}{N}\\sum_{n=1}^{N}\\sum_{t=1}^{T_{n}}A_{\\theta'}^{GAE}(s_{n}^{t},a_{n}^{t})\\frac{P_{\\theta}(a_{n}^{t}|s_{n}^{t})}{P_{\\theta'}(a_{n}^{t}|s_{n}^{t})}\\nabla\\mathrm{ln}P_{\\theta}(a_{n}^{t}|s_{n}^{t})----Off-Policy\\\\ \u0026\\begin{aligned}\u0026=\\frac{1}{N}\\sum_{n=1}^{N}\\sum_{t=1}^{T_{n}}A_{\\theta^{\\prime}}^{GAE}(s_{n}^{t},a_{n}^{t})\\frac{P_{\\theta}(a_{n}^{t}|s_{n}^{t})}{P_{\\theta^{\\prime}}(a_{n}^{t}|s_{n}^{t})}\\frac{\\nabla P_{\\theta}(a_{n}^{t}|s_{n}^{t})}{P_{\\theta}(a_{n}^{t}|s_{n}^{t})}\\\\\u0026=\\frac{1}{N}\\sum_{n=1}^{N}\\sum_{t=1}^{T_{n}}A_{\\theta^{\\prime}}^{GAE}(s_{n}^{t},a_{n}^{t})\\frac{\\nabla P_{\\theta}(a_{n}^{t}|s_{n}^{t})}{P_{\\theta^{\\prime}}(a_{n}^{t}|s_{n}^{t})}\\end{aligned} \\end{aligned} $$因此可得损失函数：\n$$ \\mathrm{Loss}=-\\frac{1}{N}\\sum_{n=1}^{N}\\sum_{t=1}^{T_{n}}A_{\\theta'}^{GAE}(s_{n}^{t},a_{n}^{t})\\frac{P_{\\theta}(a_{n}^{t}|s_{n}^{t})}{P_{\\theta'}(a_{n}^{t}|s_{n}^{t})} $$但是这里还有一个限制，就是参考策略\\theta\u0026rsquo;和要训练的策略\\theta不能相差太多，\n还是以刚才的例子来说，老师批评的学生的情况和你的情况最相似时，对你的帮助越大。假如你是一个好学生，小明是一个坏学生，老师批评小明考试不能交白卷，对你来说便没有意义，因为你不可能考试交白卷；同样的，老师表扬小明这节课虽然没听课，但是也没有骂老师，这个表扬对你也没有意义，因为你不可能上课骂老师。\n所以很自然的我们想到，我们应该对两个策略之间的相似度做一定的约束，怎么衡量两个概率分布的相似度呢？当然是KL散度。\n4.3最终的形式 由此我们得到下面两种损失函数的形式：\n$$ \\begin{align*} \u0026Loss_{ppo}=-\\frac{1}{N}\\sum_{n=1}^{N}\\sum_{t=1}^{T_{n}}A_{\\theta'}^{GAE}(s_{n}^{t},a_{n}^{t})\\frac{P_{\\theta}(a_{n}^{t}|s_{n}^{t})}{P_{\\theta'}(a_{n}^{t}|s_{n}^{t})}+\\beta KL(P_{\\theta'},P_{\\theta})\\\\ \u0026Loss_{ppo2}=-\\frac{1}{N}\\sum_{n=1}^{N}\\sum_{t=1}^{T_{n}}min(A_{\\theta'}^{GAE}(s_{n}^{t},a_{n}^{t})\\frac{P_{\\theta}(a_{n}^{t}|s_{n}^{t})}{P_{\\theta'}(a_{n}^{t}|s_{n}^{t})},clip(\\frac{P_{\\theta}(a_{n}^{t}|s_{n}^{t})}{P_{\\theta'}(a_{n}^{t}|s_{n}^{t})},1-\\varepsilon,1+\\varepsilon)A_{\\theta'}^{GAE}(s_{n}^{t},a_{n}^{t})) \\end{align*} $$在第一个公式中，我们将KL散度在目标函数中减掉，意味着我们要最大化目标函数，则KL散度不能过大；但KL散度的计算并不容易，因此有第二个公式，通过截断函数替代KL散度，防止训练策略和参考策略相差过大。\n对于第二个公式我进一步解释一下：截断函数表示当\\frac{P_{\\theta}(a_{n}^{t}|s_{n}^{t})}{P_{\\theta\u0026rsquo;}(a_{n}^{t}|s_{n}^{t})}在1-\\epsilon到1+\\epsilon之间时，取对应的值，超出范围时取边界值。\n这里的min是因为Loss函数中带有负号，如果从目标函数角度来看，这里应该是max(当然负号也要去掉），\n$$ J(\\theta)_{ppo2}=\\frac{1}{N}\\sum_{n=1}^{N}\\sum_{t=1}^{T_{n}}max(A_{\\theta'}^{GAE}(s_{n}^{t},a_{n}^{t})\\frac{P_{\\theta}(a_{n}^{t}|s_{n}^{t})}{P_{\\theta'}(a_{n}^{t}|s_{n}^{t})},clip(\\frac{P_{\\theta}(a_{n}^{t}|s_{n}^{t})}{P_{\\theta'}(a_{n}^{t}|s_{n}^{t})},1-\\varepsilon,1+\\varepsilon)A_{\\theta'}^{GAE}(s_{n}^{t},a_{n}^{t})) $$max保证了，当某一动作在参考策略带来的优势函数为正值时，在目标策略里应该提高该动作的概率，但也不能超过边界导致两个策略相差太大，而某动作带来负面影响，应该减小该动作在目标策略中的概率，但同样不能小于边界值。\n还有一个问题是关于参考策略\\theta\u0026rsquo;的更新，一般采取的办法是每K步将当前策略参数 θ 复制给 θ′\n至此完成了从强化学习基础到PPO的理论推导\n","permalink":"https://wyxbupt.me/posts/llm%E5%AD%A6%E4%B9%A0%E8%AE%B0%E5%BD%95/%E4%BB%8E%E5%BC%BA%E5%8C%96%E5%AD%A6%E4%B9%A0%E5%88%B0ppo%E7%9A%84%E7%90%86%E8%AE%BA%E9%83%A8%E5%88%86%E5%AD%A6%E4%B9%A0%E7%AC%94%E8%AE%B0/","summary":"\u003ch1 id=\"从强化学习到ppo的理论部分学习笔记\"\u003e从强化学习到PPO的理论部分（学习笔记）\u003c/h1\u003e\n\u003cp\u003e从强化学习到PPO的理论部分（学习笔记）1.强化学习的基本概念2.理论推导部分3.针对R(τ)n的优化3.1对R(τ)n引入时间步的考量3.2状态价值函数，动作价值函数，优势函数3.3时序差分和GAE4.Proximal Policy Optimization(PPO)邻近策略优化4.1On-policy和Off-Policy4.2重要性采样4.3最终的形式\u003c/p\u003e\n\u003cp\u003e最近从头开始学习强化学习以及PPO，花了两天的时间才搞懂了一些基本的概念和流程。主要参考了B站UP主RethinkFun的《零基础学习强化学习算法：ppo》，在这里记录一下自己的学习笔记。\u003c/p\u003e\n\u003ch4 id=\"1强化学习的基本概念\"\u003e1.强化学习的基本概念\u003c/h4\u003e\n\u003cp\u003eState（状态），\u003c/p\u003e\n\u003cp\u003eAction（动作），\u003c/p\u003e\n\u003cp\u003eEnvironment（系统设定等环境），\u003c/p\u003e\n\u003cp\u003eAgent（智能体或模型）\u003c/p\u003e\n\u003cp\u003eReward：奖励，指Agent在某个State下执行了某个Action与Environment交互之后得到的奖励,记为r\u003c/p\u003e\n\u003cp\u003ePolicy：策略，主要指Agent在某个State下执行各个Action的概率分布，记为$\\pi_{\\theta}(a|s)$\u003c/p\u003e\n\u003cp\u003eTrajectory：轨迹，指在策略$\\theta$下的某次具体的路径，也即Policy的一次采样值，记为$\\tau$，由一连串的${{s_{1},a_1,s_2,a_2,\\ldots}}$构成\u003c/p\u003e\n\u003cp\u003eReturn:回报，指某次Trajectory结束后得到的累积Reward\u003c/p\u003e\n\u003cp\u003e所以\u003cstrong\u003e强化学习的目标是\u003c/strong\u003e：\u003cstrong\u003e训练一个Policy神经网络$\\pi$,在所有状态State下，做出相应的Action，得到相应的Reward，使得Return的期望最大\u003c/strong\u003e\u003c/p\u003e\n\u003cp\u003e或者表述为：\u003cstrong\u003e训练一个Policy神经网络$\\pi$,在所有的Trajectory下，Return的期望最大\u003c/strong\u003e\u003c/p\u003e\n\u003ch4 id=\"2理论推导部分\"\u003e2.理论推导部分\u003c/h4\u003e\n\u003cp\u003e前面提到，强化学习的目标是最大化Return的期望，Return的期望可以表示为\u003c/p\u003e\n$$  \nE(R(\\tau))_{\\tau\\in P_\\theta(\\tau)} = \\sum_\\tau R(\\tau)*P_\\theta (\\tau)  \n$$\u003cp\u003e而对于公式中的 $P_{\\theta}(\\tau)$ ,表示的是该策略下选到该轨迹的概率，根据Trajectory的定义，其可以表示为：\u003c/p\u003e\n$$  \nP_\\theta (\\tau) = \\prod_{t=1}^T \\pi_\\theta(a_t|s_t)*p(s_{t+1}|s_t,a_t)  \n$$\u003cp\u003e这里要怎么理解呢？根据Trajectory的定义，轨迹是由一连串的$s_1,a_1,s_2,a_2,\\ldots,s_t,a_t$构成，因此可由某状态下执行某动作的概率*某个动作导致下一状态的概率连乘表示，其中$\\pi_\\theta(a_t|s_t)$由Policy决定（由下标也可以看出来），而$p(s_{t+1}|s_t,a_t)$由Environment决定。\u003c/p\u003e\n\u003cp\u003e接着，我们知道在深度学习中，不管是求损失函数的最小值还是目标函数的最大值，都需要用到梯度下降法或者梯度上升法，所以这里我们来求$E(R(\\tau))_{\\tau\\in P_\\theta(\\tau)}$的梯度\u003c/p\u003e\n$$  \n\\begin{align*} \\nabla_\\theta E(R(\\tau))_{\\tau\\in P_\\theta(\\tau)}\u0026= \\nabla_\\theta \\sum_\\tau R(\\tau)*P_\\theta (\\tau) \\\\ \u0026=\\sum_\\tau R(\\tau)*\\nabla_\\theta P_\\theta(\\tau)\\\\ \\end{align*}  \n$$\u003cp\u003e这里涉及到一个技巧：$\\nabla ln(f(x)) = \\frac{\\nabla f(x)}{f(x)}$,因此我们可以用$P_\\theta(\\tau) \\nabla_\\theta ln(P_\\theta (\\tau))$替换掉公式中的$\\nabla_\\theta P_\\theta (\\tau)$,得到\u003c/p\u003e\n$$  \n\\begin{align*} \u0026=\\sum_\\tau R(\\tau)*P_\\theta(\\tau)\\nabla_\\theta ln(P_\\theta(\\tau))\\\\ \u0026=\\sum_\\tau R(\\tau)*P_\\theta(\\tau)\\sum_{t=1}^T\\nabla_\\theta ln\\pi_\\theta(a_t|s_t)\\\\ \u0026=E_{\\tau\\in P_\\theta(\\tau)}(R(\\tau)\\sum_{t=1}^T\\nabla_\\theta ln\\pi_\\theta(a_t|s_t)) \\end{align*}  \n$$\u003cp\u003e这里不难理解，用到了期望的基本定义；之后我们再次用到期望的一个性质：\u003c/p\u003e","title":"从强化学习到PPO的理论部分（学习笔记）"},{"content":"随波逐流，逃避反思，对某一抽象立场/观点/词汇/符号的人云亦云，进而导致的盲目造神或群起而攻之里，没有对自己的具体行为对具体事件造成的具体影响的思考，并将其脱罪于所谓群体的意识。\n这是我在未了解到这个专业词汇前，对这种现象的理解和总结。\n以下举两个例子来进一步说明：\n1.“嘉豪”梗：\n我第一次听说这个梗，对其理解是对某些自认小众/独特而产生优越感并进行标榜的群体进行的讽刺。我不回避的说，如今回想自己初高中时期，这样的行为与心态也非没有过，或者可能再过几年，回想现在的自己，可能也会自嘲一句“当时的自己真是个嘉豪”，所以当我第一次听到这个梗时，除了稍微觉得会有一些对真实姓名为“嘉豪”的人造成伤害的道德洁癖外，并未对这个梗有太多的抵触，只将其作为某种中立或善意的但不完全无害的自嘲方式。\n但后来我的观察里，这个词逐渐被泛化成了对一切刺痛自己神经的看法/行为/人物的无差别攻击。只要看不惯，一句“XX 嘉豪”“豪死我了”就可以在不区分“具体情境”“具体对象”的情况下形成某种诡异的共识，进而左脚踩右脚的互相支持互相加深刻板印象，最后进行群起攻之。\n虽然这样的趋势可以预想，但依旧让人难过。当这个原本有具体语境的圈地自萌的词汇，变成了一种无差别攻击的符号与标签，其本身带来的道德影响就已经无法回避，值得重视。但令人难过的是，在群体无意识的浪潮里，该道德影响背后的责任无从追溯，个体层面的责任被无限分散消解，仅一句“玩梗而已”就足以在心理和道德层面双重脱罪，而追究群体责任，又无异于从雪崩里揪出那几片最大的雪花。\n更无解的是，这种梗还存在着一种逻辑闭环。我甚至可以预见的是，以上文字发出，我很快就会被贴上一个词：“反思嘉豪”/“哲学嘉豪”。这就是其无解之处，任何想要打破闭环的因素都可以轻易被同化消解，进而依旧在闭环里反复。\n2.“胖猫”梗：\n当初“胖猫”事件引发“大桥外卖潮”与激烈的男女对立，而在事件迎来所谓反转后，“胖猫”作为逝者又被迅速消解为娱乐符号并被各种“玩梗”。令我震惊的是，这两波浪潮中的竟然几乎是同一批人。一旦问起原因，只有“当时被情绪煽动”“好像反转了”“玩梗而已”这样抽象的人云亦云的回答。在这场浪潮里，人们似乎并无对具体事物的痛感、对具体人物的触感，只有对某种身份认同（不管是纯爱的道德符号，还是讽刺意味的娱乐符号）的狂热追求。点一份外卖，我就是纯爱战士的坚定支持者；玩一次梗，我就不是玩不起梗的故作清高的严肃无趣者。\n这两个案例看似不同，但指向的是同一个深层机制：个体在群体中的自我解散。\n胖猫事件里，人们不需要真的关心胖猫是谁，只需要\u0026quot;纯爱战士\u0026quot;这个身份标签来确认自己的道德站位。嘉豪梗里，人们不需要真的分析对方错在哪里，只需要\u0026quot;嘉豪\u0026quot;这个标签来宣告对方的无效和自己的优越。一个是向外投射道德感，一个是向内确认归属感——但操作方式一模一样：用标签替代思考，用立场替代判断。\n更值得追问的是：为什么这种机制在今天如此高效？\n我觉得关键在于——互联网生态对\u0026quot;反思\u0026quot;的系统性惩罚。一个认真思考的人，在公共空间里面对的是一个非常不对称的局面：\n• 贴标签：一秒，零消耗\n• 认真回应：十分钟，还要承担被攻击的风险\n• 解构严肃讨论：一句\u0026quot;玩梗而已\u0026quot;，快乐，还有点赞\n• 严肃讨论：吃力不讨好，被说上纲上线\n这是一个激励不思考、惩罚思考的负反馈循环。当\u0026quot;随便\u0026quot;的成本趋近于零，而\u0026quot;认真\u0026quot;的成本越来越高的时候，平庸之恶就不再需要\u0026quot;恶人\u0026quot;了——它只需要无数个随手点了转发、随手回了哈哈的人。每个人都没有\u0026quot;故意做恶\u0026quot;，但合在一起就是一片没有主体的暴政。\n那剩下一个更难的问题：知道这些之后，还能怎么办？\n我没有乐观的通解。但几个不成熟的方向也许值得试试看：\n一是不回避具体性。 在自己的小范围里，坚持和具体的人谈具体的事。当别人说\u0026quot;这人是个嘉豪\u0026quot;的时候，追问一句\u0026quot;他到底说了什么让你不舒服\u0026quot;——哪怕这句话被当成\u0026quot;反思嘉豪\u0026quot;，也值得说。\n二是不放弃自我审视。 就像我自己回看初中时的心态时说的\u0026quot;当时的自己真是个嘉豪\u0026quot;——这种诚实本身就是对平庸之恶的抵抗。承认自己也在系统里，比站在外面评判别人难得多，但也诚实得多。\n三是不追求速效。 这种层面的改变不可能靠一次呼吁、一篇文章、一个热搜完成。它更像是一种长期的、隐秘的自我训练——在每个想随手贴标签的瞬间多停一秒，在每个想跟风的时候多问一句\u0026quot;我确定吗\u0026quot;。\n","permalink":"https://wyxbupt.me/posts/%E8%83%A1%E6%80%9D%E4%B9%B1%E6%83%B3/%E5%B9%B3%E5%BA%B8%E4%B9%8B%E6%81%B6/","summary":"\u003cp\u003e随波逐流，逃避反思，对某一抽象立场/观点/词汇/符号的人云亦云，进而导致的盲目造神或群起而攻之里，没有对自己的具体行为对具体事件造成的具体影响的思考，并将其脱罪于所谓群体的意识。\u003cbr\u003e\n这是我在未了解到这个专业词汇前，对这种现象的理解和总结。\u003cbr\u003e\n以下举两个例子来进一步说明：\u003cbr\u003e\n1.“嘉豪”梗：\u003cbr\u003e\n我第一次听说这个梗，对其理解是对某些自认小众/独特而产生优越感并进行标榜的群体进行的讽刺。我不回避的说，如今回想自己初高中时期，这样的行为与心态也非没有过，或者可能再过几年，回想现在的自己，可能也会自嘲一句“当时的自己真是个嘉豪”，所以当我第一次听到这个梗时，除了稍微觉得会有一些对真实姓名为“嘉豪”的人造成伤害的道德洁癖外，并未对这个梗有太多的抵触，只将其作为某种中立或善意的但不完全无害的自嘲方式。\u003cbr\u003e\n但后来我的观察里，这个词逐渐被泛化成了对一切刺痛自己神经的看法/行为/人物的无差别攻击。只要看不惯，一句“XX 嘉豪”“豪死我了”就可以在不区分“具体情境”“具体对象”的情况下形成某种诡异的共识，进而左脚踩右脚的互相支持互相加深刻板印象，最后进行群起攻之。\u003cbr\u003e\n虽然这样的趋势可以预想，但依旧让人难过。当这个原本有具体语境的圈地自萌的词汇，变成了一种无差别攻击的符号与标签，其本身带来的道德影响就已经无法回避，值得重视。但令人难过的是，在群体无意识的浪潮里，该道德影响背后的责任无从追溯，个体层面的责任被无限分散消解，仅一句“玩梗而已”就足以在心理和道德层面双重脱罪，而追究群体责任，又无异于从雪崩里揪出那几片最大的雪花。\u003cbr\u003e\n更无解的是，这种梗还存在着一种逻辑闭环。我甚至可以预见的是，以上文字发出，我很快就会被贴上一个词：“反思嘉豪”/“哲学嘉豪”。这就是其无解之处，任何想要打破闭环的因素都可以轻易被同化消解，进而依旧在闭环里反复。\u003cbr\u003e\n2.“胖猫”梗：\u003cbr\u003e\n当初“胖猫”事件引发“大桥外卖潮”与激烈的男女对立，而在事件迎来所谓反转后，“胖猫”作为逝者又被迅速消解为娱乐符号并被各种“玩梗”。令我震惊的是，这两波浪潮中的竟然几乎是同一批人。一旦问起原因，只有“当时被情绪煽动”“好像反转了”“玩梗而已”这样抽象的人云亦云的回答。在这场浪潮里，人们似乎并无对具体事物的痛感、对具体人物的触感，只有对某种身份认同（不管是纯爱的道德符号，还是讽刺意味的娱乐符号）的狂热追求。点一份外卖，我就是纯爱战士的坚定支持者；玩一次梗，我就不是玩不起梗的故作清高的严肃无趣者。\u003cbr\u003e\n这两个案例看似不同，但指向的是同一个深层机制：个体在群体中的自我解散。\u003cbr\u003e\n胖猫事件里，人们不需要真的关心胖猫是谁，只需要\u0026quot;纯爱战士\u0026quot;这个身份标签来确认自己的道德站位。嘉豪梗里，人们不需要真的分析对方错在哪里，只需要\u0026quot;嘉豪\u0026quot;这个标签来宣告对方的无效和自己的优越。一个是向外投射道德感，一个是向内确认归属感——但操作方式一模一样：用标签替代思考，用立场替代判断。\u003cbr\u003e\n更值得追问的是：为什么这种机制在今天如此高效？\u003cbr\u003e\n我觉得关键在于——互联网生态对\u0026quot;反思\u0026quot;的系统性惩罚。一个认真思考的人，在公共空间里面对的是一个非常不对称的局面：\u003cbr\u003e\n• 贴标签：一秒，零消耗\u003cbr\u003e\n• 认真回应：十分钟，还要承担被攻击的风险\u003cbr\u003e\n• 解构严肃讨论：一句\u0026quot;玩梗而已\u0026quot;，快乐，还有点赞\u003cbr\u003e\n• 严肃讨论：吃力不讨好，被说上纲上线\u003cbr\u003e\n这是一个激励不思考、惩罚思考的负反馈循环。当\u0026quot;随便\u0026quot;的成本趋近于零，而\u0026quot;认真\u0026quot;的成本越来越高的时候，平庸之恶就不再需要\u0026quot;恶人\u0026quot;了——它只需要无数个随手点了转发、随手回了哈哈的人。每个人都没有\u0026quot;故意做恶\u0026quot;，但合在一起就是一片没有主体的暴政。\u003cbr\u003e\n那剩下一个更难的问题：知道这些之后，还能怎么办？\u003cbr\u003e\n我没有乐观的通解。但几个不成熟的方向也许值得试试看：\u003cbr\u003e\n一是不回避具体性。 在自己的小范围里，坚持和具体的人谈具体的事。当别人说\u0026quot;这人是个嘉豪\u0026quot;的时候，追问一句\u0026quot;他到底说了什么让你不舒服\u0026quot;——哪怕这句话被当成\u0026quot;反思嘉豪\u0026quot;，也值得说。\u003cbr\u003e\n二是不放弃自我审视。 就像我自己回看初中时的心态时说的\u0026quot;当时的自己真是个嘉豪\u0026quot;——这种诚实本身就是对平庸之恶的抵抗。承认自己也在系统里，比站在外面评判别人难得多，但也诚实得多。\u003cbr\u003e\n三是不追求速效。 这种层面的改变不可能靠一次呼吁、一篇文章、一个热搜完成。它更像是一种长期的、隐秘的自我训练——在每个想随手贴标签的瞬间多停一秒，在每个想跟风的时候多问一句\u0026quot;我确定吗\u0026quot;。\u003c/p\u003e","title":"平庸之恶"},{"content":"最近听到一个让我一下通了不少事的观点：心理的最小单位是感受。\n我顺着这个想了很久，写下来算是一次梳理吧。\n为什么是感受，不是道理？ 我以前总觉得\u0026quot;想通一个道理就能解决问题\u0026quot;。比如我知道了\u0026quot;我不配得感是因为小时候怎样怎样\u0026quot;，好了，我知道了，那下次应该就能正常了吧？\n但不行。下次遇到同样的场景，那股难受的感觉还是会自动冒出来。道理在脑子里清清楚楚地跑，感受在身体里平行地跑，两套系统根本不互通。你能用一个道理说服一个人，但你很难用一个道理说服自己身体里的那道程序。\n后来我理解了，感受比认知来得早太多。你还在发育、还没学会说话的时候，被抱、被放下、被注视、被忽略，就已经在积累感受数据了。那些东西存得比道理深得多，层级完全不一样。认知像是后来盖在土地上的楼，感受是那片地本身的土质和地基。你可以在楼上装修，但地基不行就是不行，装修得再漂亮也没用。\n道理是面子，感受是里子。\n面子可以换，里子是盖在骨头上的。\n感受链路是怎么形成的 一个本能的感受产生了——比如考了第一名，很高兴，本能地想要被看见、被夸奖。\n如果这个时刻，有人接住了你——\u0026ldquo;哇你真的好棒\u0026rdquo;——这个感受就完成了它的生命周期，自然地释放、消散了。然后继续下一个感受。\n但如果没被接住呢？\n感受不会凭空消失。它会被打包存档，而且不是原样存，是带着一条备注一起存。备注的内容取决于环境给你的反馈。\n我的情况是：每次考第一，回到家想要被认可，换来的不是表扬，而是\u0026quot;别张扬\u0026quot;\u0026ldquo;低调点\u0026quot;\u0026ldquo;枪打出头鸟\u0026rdquo;。看得出来，我妈是真心觉得这是为我好。但问题是，那个\u0026quot;想被看见\u0026quot;的感受没有消失，它只是被压下去了。而且每一次被压抑都在上面叠了一层新感受：\u0026ldquo;想要被看见是会带来尴尬的\u0026quot;\u0026ldquo;想要被表扬是我的问题。\u0026rdquo;\n一次、两次、上百次之后，那条备注就固化成了一道自动运行的程序——不需要你思考，不需要你允许，它自己就在跑了。那条程序大概长这样：做得好 → 但我不该被看见 → 必须先对标最厉害的人 → 看看那些最厉害的人什么样 → 完了我差太远 → 开始幻想他们笑话我 → 算了别做了。\n有没有发现，从\u0026quot;做得好\u0026quot;到\u0026quot;算了别做了\u0026rdquo;，中间没有一个步骤是理性参与的。全是自动的。这就是心理链路的可怕之处——它不是你可以选择打开关闭的东西，它已经长在你的系统里了。\n另一条链路类似，不过是关于\u0026quot;想要\u0026quot;的。小时候想要玩具，但家里条件一般，于是学会了\u0026quot;我不说，我说了就是不懂事\u0026rdquo;。换来一句\u0026quot;真懂事\u0026quot;当奖励。久了就变成：我想要 → 但我不该要 → 如果我想要了我就不是好孩子 → 所以我不配。这条链路的终点，就是成年后每次为自己花点钱，负罪感先于快乐到达。\n两条链路，一条管\u0026quot;被看见\u0026quot;，一条管\u0026quot;被满足\u0026quot;。你想想，一个人活在这个世界上，主要不就这两件事吗？\n为什么能说出来就是进步 这玩意儿最坑人的地方在于它会藏在你看不见的地方。你不会在结账前对自己说\u0026quot;好了，不配得模式即将启动\u0026quot;——你只是突然觉得一阵不舒服，一阵负罪感突然升起，但不知道从哪来的。它的运行是无声的，是你身体里的一道后台程序，从你有记忆之前就在跑，跑到你都忘了它是跑着的。\n所以当你能把这些东西用语言描述出来的时候，最根本的变化已经发生了：你从被它驱动，变成了看它怎么跑的人。 你不再是被程序控制的人，你是能坐下来读那一段代码的人。这个距离本身就意味着变化已经开始了。\n我觉得这是所有疗愈的共同起点。不是因为说出来就能改，而是因为你终于不用再被它暗中操控了。 然后呢 说能说出来就完了，那太理想化了。问题摆在那了，路还得走。\n我不信\u0026quot;对抗\u0026quot;这一套。你越是用力对抗一条链路，它就越被强化——因为它会识别出\u0026quot;威胁\u0026quot;，变得更活跃。更像是在跟一个程序较劲，你越用力按，它越反弹。\n我更相信的方法是从小处试。今天想吃什么就去吃，想说什么话就说，想发的一条动态就发——不先过那道\u0026quot;我配吗\u0026quot;\u0026ldquo;他会不会笑话我\u0026quot;的审查。不是放纵，是教你的大脑说：看，我的感受是可信的。我在做我本能想做的事，没出事。一次两次不会改变什么。但当你积累了足够多的\u0026quot;安全经验\u0026rdquo;，大脑会慢慢开始接受：原来\u0026quot;被看见\u0026quot;不会出事，原来\u0026quot;被满足\u0026quot;也不会被抛弃。那两条旧的链路不会消失，但它们旁边会长出新的路径。下次再走到那个岔路口的时候，你会有多一个选择。-最后这框架不局限于原生家庭那点事。工作中不敢跟老板提加薪、不敢拒绝不合理的安排、亲密关系里不敢表达真正的需求、社交场合不敢展现真实的自己——底层都是同一个机制：某些本能的感受，在成长过程中被贴上了\u0026quot;不能碰\u0026quot;的标签。而成长的过程，就是把那些被贴上\u0026quot;危险\u0026quot;标签的感受，一个一个地取下来，重新检验一遍。说到底它们本来就不是危险的。它们只是没有被好好接住。\n所以我们应当从感受着眼，来解决那些所谓的表象问题\n","permalink":"https://wyxbupt.me/posts/%E8%83%A1%E6%80%9D%E4%B9%B1%E6%83%B3/%E5%BF%83%E7%90%86%E7%9A%84%E6%9C%80%E5%B0%8F%E5%8D%95%E4%BD%8D%E6%98%AF%E6%84%9F%E5%8F%97/","summary":"\u003ch2 id=\"为什么是感受不是道理\"\u003e最近听到一个让我一下通了不少事的观点：心理的最小单位是感受。\u003cbr\u003e\n我顺着这个想了很久，写下来算是一次梳理吧。\u003cbr\u003e\n为什么是感受，不是道理？\u003c/h2\u003e\n\u003cp\u003e我以前总觉得\u0026quot;想通一个道理就能解决问题\u0026quot;。比如我知道了\u0026quot;我不配得感是因为小时候怎样怎样\u0026quot;，好了，我知道了，那下次应该就能正常了吧？\u003cbr\u003e\n但不行。下次遇到同样的场景，那股难受的感觉还是会自动冒出来。道理在脑子里清清楚楚地跑，感受在身体里平行地跑，两套系统根本不互通。你能用一个道理说服一个人，但你很难用一个道理说服自己身体里的那道程序。\u003cbr\u003e\n后来我理解了，感受比认知来得早太多。你还在发育、还没学会说话的时候，被抱、被放下、被注视、被忽略，就已经在积累感受数据了。那些东西存得比道理深得多，层级完全不一样。认知像是后来盖在土地上的楼，感受是那片地本身的土质和地基。你可以在楼上装修，但地基不行就是不行，装修得再漂亮也没用。\u003cbr\u003e\n道理是面子，感受是里子。\u003cbr\u003e\n面子可以换，里子是盖在骨头上的。\u003c/p\u003e\n\u003ch2 id=\"感受链路是怎么形成的\"\u003e感受链路是怎么形成的\u003c/h2\u003e\n\u003cp\u003e一个本能的感受产生了——比如考了第一名，很高兴，本能地想要被看见、被夸奖。\u003cbr\u003e\n如果这个时刻，有人接住了你——\u0026ldquo;哇你真的好棒\u0026rdquo;——这个感受就完成了它的生命周期，自然地释放、消散了。然后继续下一个感受。\u003cbr\u003e\n但如果没被接住呢？\u003cbr\u003e\n感受不会凭空消失。它会被打包存档，而且不是原样存，是带着一条备注一起存。备注的内容取决于环境给你的反馈。\u003cbr\u003e\n我的情况是：每次考第一，回到家想要被认可，换来的不是表扬，而是\u0026quot;别张扬\u0026quot;\u0026ldquo;低调点\u0026quot;\u0026ldquo;枪打出头鸟\u0026rdquo;。看得出来，我妈是真心觉得这是为我好。但问题是，那个\u0026quot;想被看见\u0026quot;的感受没有消失，它只是被压下去了。而且每一次被压抑都在上面叠了一层新感受：\u0026ldquo;想要被看见是会带来尴尬的\u0026quot;\u0026ldquo;想要被表扬是我的问题。\u0026rdquo;\u003cbr\u003e\n一次、两次、上百次之后，那条备注就固化成了一道自动运行的程序——不需要你思考，不需要你允许，它自己就在跑了。那条程序大概长这样：做得好 → 但我不该被看见 → 必须先对标最厉害的人 → 看看那些最厉害的人什么样 → 完了我差太远 → 开始幻想他们笑话我 → 算了别做了。\u003cbr\u003e\n有没有发现，从\u0026quot;做得好\u0026quot;到\u0026quot;算了别做了\u0026rdquo;，中间没有一个步骤是理性参与的。全是自动的。这就是心理链路的可怕之处——它不是你可以选择打开关闭的东西，它已经长在你的系统里了。\u003cbr\u003e\n另一条链路类似，不过是关于\u0026quot;想要\u0026quot;的。小时候想要玩具，但家里条件一般，于是学会了\u0026quot;我不说，我说了就是不懂事\u0026rdquo;。换来一句\u0026quot;真懂事\u0026quot;当奖励。久了就变成：我想要 → 但我不该要 → 如果我想要了我就不是好孩子 → 所以我不配。这条链路的终点，就是成年后每次为自己花点钱，负罪感先于快乐到达。\u003cbr\u003e\n两条链路，一条管\u0026quot;被看见\u0026quot;，一条管\u0026quot;被满足\u0026quot;。你想想，一个人活在这个世界上，主要不就这两件事吗？\u003c/p\u003e\n\u003ch2 id=\"为什么能说出来就是进步\"\u003e为什么能说出来就是进步\u003c/h2\u003e\n\u003ch2 id=\"然后呢\"\u003e这玩意儿最坑人的地方在于它会藏在你看不见的地方。你不会在结账前对自己说\u0026quot;好了，不配得模式即将启动\u0026quot;——你只是突然觉得一阵不舒服，一阵负罪感突然升起，但不知道从哪来的。它的运行是无声的，是你身体里的一道后台程序，从你有记忆之前就在跑，跑到你都忘了它是跑着的。\u003cbr\u003e\n所以当你能把这些东西用语言描述出来的时候，最根本的变化已经发生了：你从被它驱动，变成了看它怎么跑的人。 你不再是被程序控制的人，你是能坐下来读那一段代码的人。这个距离本身就意味着变化已经开始了。\u003cbr\u003e\n我觉得这是所有疗愈的共同起点。不是因为说出来就能改，而是因为你终于不用再被它暗中操控了。 \u003cbr\u003e\n然后呢\u003c/h2\u003e\n\u003cp\u003e说能说出来就完了，那太理想化了。问题摆在那了，路还得走。\u003cbr\u003e\n我不信\u0026quot;对抗\u0026quot;这一套。你越是用力对抗一条链路，它就越被强化——因为它会识别出\u0026quot;威胁\u0026quot;，变得更活跃。更像是在跟一个程序较劲，你越用力按，它越反弹。\u003cbr\u003e\n我更相信的方法是从小处试。今天想吃什么就去吃，想说什么话就说，想发的一条动态就发——不先过那道\u0026quot;我配吗\u0026quot;\u0026ldquo;他会不会笑话我\u0026quot;的审查。不是放纵，是教你的大脑说：看，我的感受是可信的。我在做我本能想做的事，没出事。一次两次不会改变什么。但当你积累了足够多的\u0026quot;安全经验\u0026rdquo;，大脑会慢慢开始接受：原来\u0026quot;被看见\u0026quot;不会出事，原来\u0026quot;被满足\u0026quot;也不会被抛弃。那两条旧的链路不会消失，但它们旁边会长出新的路径。下次再走到那个岔路口的时候，你会有多一个选择。-最后这框架不局限于原生家庭那点事。工作中不敢跟老板提加薪、不敢拒绝不合理的安排、亲密关系里不敢表达真正的需求、社交场合不敢展现真实的自己——底层都是同一个机制：某些本能的感受，在成长过程中被贴上了\u0026quot;不能碰\u0026quot;的标签。而成长的过程，就是把那些被贴上\u0026quot;危险\u0026quot;标签的感受，一个一个地取下来，重新检验一遍。说到底它们本来就不是危险的。它们只是没有被好好接住。\u003c/p\u003e\n\u003cp\u003e所以我们应当从感受着眼，来解决那些所谓的表象问题\u003c/p\u003e","title":"心理的最小单位是感受"},{"content":"欢迎来到我的学习笔记博客。\n这里会记录课程整理、复习要点和实验总结。\n","permalink":"https://wyxbupt.me/posts/llm%E5%AD%A6%E4%B9%A0%E8%AE%B0%E5%BD%95/hello-world/","summary":"\u003cp\u003e欢迎来到我的学习笔记博客。\u003c/p\u003e\n\u003cp\u003e这里会记录课程整理、复习要点和实验总结。\u003c/p\u003e","title":"Hello World"}]