<?xml version="1.0" encoding="utf-8"?>
<feed xmlns="http://www.w3.org/2005/Atom">
  <author>
    <name>迎风起降</name>
  </author>
  <generator uri="https://hexo.io/">Hexo</generator>
  <id>https://x4ai.cn/</id>
  <link href="https://x4ai.cn/" rel="alternate"/>
  <link href="https://x4ai.cn/atom.xml" rel="self"/>
  <rights>All rights reserved 2026, 迎风起降</rights>
  <subtitle>个人博客，记录学习与生活点滴。分享人工智能、学术、VPS、服务器、域名等相关内容。</subtitle>
  <title>过拟合训练日志</title>
  <updated>2026-06-06T02:01:51.544Z</updated>
  <entry>
    <author>
      <name>迎风起降</name>
    </author>
    <category term="学习笔记" scheme="https://x4ai.cn/categories/%E5%AD%A6%E4%B9%A0%E7%AC%94%E8%AE%B0/"/>
    <category term="Memory for Dialogue Agents" scheme="https://x4ai.cn/tags/Memory-for-Dialogue-Agents/"/>
    <content>
      <![CDATA[<div align="center" style="font-size:2.2rem;font-weight:800;line-height:1.2;margin:0.6em 0 0.35em;">From Single to Multi-Granularity: Toward Long-Term Memory Association and Selection of Conversational Agents</div><div align="center" style="font-size:1.25rem;font-weight:700;line-height:1.35;margin:0 0 0.9em;">从单一粒度到多粒度：面向对话代理的长期记忆关联与选择</div><div style="display:flex;gap:8px;flex-wrap:wrap;align-items:center;"><a href="https://github.com/Applied-Machine-Learning-Lab/ICLR2026_MemGAS"><img                           lazyload                       alt="image"                       data-src="https://img.shields.io/badge/Code-GitHub-181717?style=for-the-badge&logo=github&logoColor=white"                         alt="Code"                 ></a><a href="https://iclr.cc/Conferences/2026"><img                           lazyload                       alt="image"                       data-src="https://img.shields.io/badge/Conference-ICLR%202026-4B6CB7?style=for-the-badge"                         alt="Conference"                 ></a></div><img                           lazyload                       alt="image"                       data-src="https://img.102465.xyz/file/24M3RQu6.png"                         alt="ChatGPT Image 2026年4月27日 15_12_09.png" width=100%                  ><h2 id="摘要"><a href="#摘要" class="headerlink" title="摘要"></a>摘要</h2><p>大语言模型（LLMs）近年来已被广泛应用于对话智能体中。然而，用户与智能体之间不断延长的交互会累积大量对话记录，使得上下文窗口有限的 LLMs 难以维持连贯的长期对话记忆，也难以生成个性化回复。尽管检索增强记忆系统已被提出以缓解这一问题，现有方法通常依赖单一粒度的记忆划分与检索。这种方式难以捕捉深层次的记忆关联，往往只能检索到部分有用信息，或引入大量噪声，从而导致性能不佳。为解决这些局限，本文提出了 MemGAS，一个通过构建多粒度关联、自适应选择与检索来增强记忆整合能力的框架。MemGAS 基于多粒度记忆单元，并采用高斯混合模型（Gaussian Mixture Models）将新记忆与历史记忆进行聚类与关联。基于熵的路由器通过评估查询相关性分布，自适应地选择最优粒度，在信息完整性与噪声之间取得平衡。检索到的记忆还会通过基于 LLM 的过滤进一步精炼。四个长期记忆基准上的实验表明，MemGAS 在问答任务和检索任务上均优于当前最先进方法，并在不同查询类型和不同 top-K 设置下取得了更优表现1。</p><h2 id="引言"><a href="#引言" class="headerlink" title="引言"></a>引言</h2><p>近年来，大语言模型（LLMs）已被广泛应用于对话代理中。然而，随着用户与代理之间的交互不断延长，所积累的大量对话记录使得上下文窗口受限的LLMs难以维持连贯的长期对话记忆，也难以提供个性化的响应。尽管基于检索增强的记忆系统已被提出以缓解这一问题，但现有方法通常依赖于<strong>单一粒度的记忆划分与检索</strong>。这种方式难以捕捉深层次的记忆关联，往往导致有用信息的部分缺失或引入大量噪声，从而影响整体性能。</p><p>为了解决上述局限，本文提出了 <strong>MemGAS</strong> 框架，通过构建<strong>多粒度关联、自适应选择与检索机制</strong>来增强记忆整合能力。MemGAS 基于多粒度记忆单元，利用 <strong>高斯混合模型（Gaussian Mixture Models）</strong> 对新记忆进行聚类，并将其与历史记忆建立关联。同时，引入基于熵的路由机制（entropy-based router），通过评估查询相关性分布，自适应选择最优粒度，在信息完整性与噪声之间取得平衡。检索到的记忆还会通过基于LLM的过滤进一步优化。</p><p>在四个长期记忆基准数据集上的实验结果表明，MemGAS 在问答任务和检索任务中均优于当前最先进方法，在不同查询类型和不同 top-K 设置下都表现出更优的性能。</p><p>近期关于LLM智能体外部记忆系统的研究，主要依赖于检索增强生成（Retrieval-Augmented Generation, RAG）范式，并从记忆划分与构建等多个方面探索如何实现高效检索。在记忆划分方面，现有方法主要采用单一粒度（single-granularity）对对话进行分割。一类方法使用会话级片段（session-level chunks）作为检索单元，而另一类方法则采用更细粒度的轮次级划分以捕捉更丰富的细节信息。近期的进展引入了基于主题感知（topic-aware）的划分技术，通过语义一致性对对话进行分组，从而提升围绕主题的一致性检索能力。</p><p>此外，一些研究通过生成记忆摘要，将关键信息压缩为紧凑表示，以提高检索效率。在记忆构建方面，研究者探索了多种结构化组织范式，以增强长期知识的保留能力。例如，RAPTOR（Sarthi et al., 2024）和 MemTree（Rezazadeh et al., 2024）采用树结构来编码记忆单元之间的多尺度关系。一些工作提出了层级化记忆架构（hierarchical memory architectures），构建具备深层抽象能力的复杂系统，从而支持高效的自顶向下检索。还有研究采用基于图的记忆架构，以模拟神经记忆巩固过程，并显式建模实体之间的关系结构。</p><img                           lazyload                       alt="image"                       data-src="https://img.102465.xyz/file/UD3arzsT.png"                         alt="ChatGPT Image 2026年4月27日 14_19_57.png" width=100%                  ><p>然而，尽管现有方法取得了一定进展，但仍然存在两个关键局限：</p><p><strong>（i）多粒度记忆连接不足。</strong> 虽然已有研究尝试将记忆组织为拓扑结构（例如知识图谱或树结构），但这些方法大多聚焦于单一粒度层级——要么是实体级别，要么是会话摘要级别。这种单尺度范式无法建模不同粒度记忆单元之间的跨层交互，导致检索结果往往只包含部分有用信息。如图1所示，在回答跨多轮会话的问题时，需要在不同粒度之间建立语义关联（例如通过共享关键词或摘要将对话1与对话2连接起来）。如果未能建立这种关联，就会出现信息检索不完整的情况（例如仅检索到对话1），从而导致错误答案。</p><p><strong>（ii）缺乏自适应的多粒度记忆选择机制。</strong> 当前方法主要依赖固定的粒度策略（如基于会话或轮次的划分，或由LLM生成的摘要），这往往由于粒度选择不当而导致上下文信息缺失或噪声干扰。尽管基于主题的划分方法能够提升片段内部的一致性，但它们缺乏针对不同查询动态选择粒度的能力。我们在图1(a)中的实证分析表明，根据每个查询自适应地选择最合适的粒度（例如在摘要&#x2F;关键词带来的降噪效果与原始会话的信息完整性之间进行权衡）可以显著提升性能。这一发现凸显了粒度选择在解决“信息-噪声权衡”问题中的重要性。</p><p>基于上述问题，本文提出了 <strong>MemGAS</strong> 框架，通过多粒度关联与自适应选择来构建和检索长期记忆。我们的方法主要包含两个核心策略：</p><p><strong>（i）记忆关联（Memory Association）：</strong> 利用LLMs生成记忆摘要和关键词，构建多粒度记忆单元。在新记忆更新时，采用高斯混合模型（Gaussian Mixture Model）将历史记忆划分为接受集合（相关）与拒绝集合（不相关），并将接受集合中的记忆与新记忆建立关联，从而实现记忆结构的整合与实时更新。</p><p><strong>（ii）粒度选择（Granularity Selection）：</strong> 引入基于熵的路由机制，通过评估查询相关性分布的不确定性，自适应地为不同粒度分配检索权重。随后，利用个性化PageRank算法检索关键记忆，并通过LLM进行过滤以去除冗余信息，从而获得高质量、精炼的记忆内容，提升对话系统的理解能力。</p><p>在四个开源长期记忆基准数据集上的实验表明，MemGAS 在问答任务和检索任务上均显著优于当前最先进方法以及单一粒度方法，并在不同查询类型及不同 top-k 设置下持续取得更优表现。</p><h2 id="方法"><a href="#方法" class="headerlink" title="方法"></a>方法</h2><p>本节首先对任务与数据格式进行定义，随后构建一个动态记忆关联框架，详细介绍一种基于熵的路由机制以选择更合适的粒度，并概述用于检索与过滤高质量上下文信息以生成响应的策略。</p><h3 id="预备知识（Preliminary）"><a href="#预备知识（Preliminary）" class="headerlink" title="预备知识（Preliminary）"></a>预备知识（Preliminary）</h3><p>本文的研究重点在于通过长期对话记忆构建个性化助手。在该设定下，系统利用跨多个会话的用户—代理交互（统称为“记忆”）来构建外部记忆库 $M$。不失一般性地，第 $i$ 个会话可表示为</p><p>\[S_i = \{(u^{(i)}_j, a^{(i)}_j)\}_{j=1}^{n_i}\]</p>其中包含 $n_i$ 轮对话，每一轮由用户输入 $u^{(i)}_j$ 和助手回复 $a^{(i)}_j$ 组成。<p>当助手接收到查询 $q$ 时，我们的目标是通过跨多粒度（包括会话级 $S$、轮次级 $T$、关键词级 $K$ 和摘要级 $U$）的关联机制，从记忆库 $M$ 中检索出相关记忆子集 $M_{\text{rel}} \subseteq M$，并基于这些信息生成响应：</p><p>\[a = \text{LLM}(q, M_{\text{rel}})\]</p><p>该问题的核心挑战在于：一方面，需要建立跨不同粒度的记忆关联；另一方面，需要学习一种自适应的粒度选择函数</p><p>\[\psi: q \rightarrow {\alpha_s, \alpha_t, \alpha_k, \alpha_u}\]</p>以在信息完整性与检索噪声之间实现有效平衡。<h3 id="多粒度关联构建（Multi-Granularity-Association-Construction）"><a href="#多粒度关联构建（Multi-Granularity-Association-Construction）" class="headerlink" title="多粒度关联构建（Multi-Granularity Association Construction）"></a>多粒度关联构建（Multi-Granularity Association Construction）</h3><p>现有方法通常将记忆编码为向量库（例如基于会话级的片段），并通过相似度搜索直接进行信息检索。然而，这类方法忽视了记忆之间更深层次的关联关系。为了解决这一问题，本文提出了一种<strong>关联式记忆构建过程</strong>，用于捕捉多粒度之间的关系。</p><p><strong>多粒度记忆元数据（Multi-Granular Memory Metadata）。</strong> 对于第 $i$ 个会话 $S_i$，利用LLM生成多粒度的元数据，包括会话摘要 $U_i$ 和关键词 $K_i$。同时，将该会话划分为多个对话轮次 $T_i$。形式化表示为：</p><p>\[U_i, K_i = f_{\text{LLM}}(S_i), \quad T_i = \text{segment}(S_i)\]</p><p>其中，$f_{\text{LLM}}$ 表示 LLM 处理函数，$\text{segment}$ 表示分割操作。最终得到的记忆单元 $M_i$ 融合了会话级信息 $S_i$、轮次级对话 $T_i$、摘要 $U_i$ 以及关键词 $K_i$，并存储于记忆库中：</p><p>\[M_i = \{S_i, T_i, U_i, K_i\} \in M\]</p></p><p><strong>动态记忆关联（Dynamical Memory Association）。</strong> 当新增记忆 $M_{\text{new}}$ 被加入时，当前记忆库 $M_{\text{cur}}$ 会更新为：</p><p>\[M_{\text{cur}} \leftarrow M_{\text{cur}} \cup M_{\text{new}}\]</p>为了在新记忆与历史记忆之间建立关联，本文采用基于 高斯混合模型（Gaussian Mixture Model, GMM） 的聚类策略。<p>具体而言，记忆库中每个元素的不同粒度都会被编码为稠密向量表示。对于会话级、轮次级、摘要级和关键词级元数据，分别表示为 $e(S_i)$、$e(T_i)$、$e(U_i)$ 和 $e(K_i)$。因此，整个记忆库 $M_{\text{cur}}$ 可以表示为由这些多粒度向量组成的集合。</p><p>随后，在新记忆 $e(M_{\text{new}})$ 与当前记忆库 $e(M_{\text{cur}})$ 之间计算两两相似度分数，覆盖每个记忆的所有粒度。得到的相似度向量集合 $s_{\text{sim}}$ 会通过 GMM 进行聚类，并划分为两个概率集合：</p><ul><li><strong>接受集合（Accept Set）：</strong> 与 $M_{\text{new}}$ 相似度较高的记忆，与新记忆建立直接关联；</li><li><strong>拒绝集合（Reject Set）：</strong> 与新记忆无关的记忆，在当前阶段不建立连接。</li></ul><p>需要注意的是，相似度向量是在**粒度特定信息（granularity-specific information）**的基础上计算的，这意味着 $M_{\text{new}}$ 的每一种粒度都被视为一个节点，并用于与 $M_{\text{cur}}$ 中的节点建立连接。我们维护一个关联图 $A_{\text{cur}}$ 来存储记忆库 $M$ 中的这些连接关系，其更新方式为：</p><p>\[A_{\text{cur}} \leftarrow A_{\text{cur}} \cup A_{\text{new}}\]</p>其中 $A_{\text{new}}$ 表示 $M_{\text{new}}$ 与其接受集合之间所形成的边。<p>该过程通过有选择地强化在上下文上相关的记忆，模拟了类似人类的记忆巩固机制。</p><h3 id="多粒度路由器（Multi-Granularity-Router）"><a href="#多粒度路由器（Multi-Granularity-Router）" class="headerlink" title="多粒度路由器（Multi-Granularity Router）"></a>多粒度路由器（Multi-Granularity Router）</h3><p>现有方法通常依赖单一预定义的粒度进行记忆检索，这限制了其根据查询内容自适应地优先选择细粒度或粗粒度信息的能力。为了解决这一问题，本文提出了一种基于熵的路由机制（entropy-based router），能够针对每个查询动态选择最合适的粒度。</p><p><strong>基于熵的粒度选择（Entropy-Driven Granularity Selection）。</strong> 对于给定查询 $q$，首先计算其与各个粒度层级 $g \in {\text{session}, \text{turn}, \text{summary}, \text{keyword}}$ 上所有记忆片段之间的相似度。设：</p><p>\[s_g = [\text{sim}(q, M^g_1), \dots, \text{sim}(q, M^g_n)]\]</p><p>表示查询 $q$ 与粒度 $g$ 下 $n$ 个记忆片段之间的相似度分数。随后，将 $s_g$ 归一化为概率分布 $p_g(s_g)$，并计算其香农熵（Shannon entropy）：</p><p>\[H^g = - \sum_{i=1}^{n} p_i^g(s^g)\,\log p_i^g(s^g), \quad\text{where }p_i^g(s^g) = \frac{\exp\big(\operatorname{sim}(q, M_i^g)/\lambda\big)}{\sum_{j=1}^{n} \exp\big(\operatorname{sim}(q, M_j^g)/\lambda\big)},\ \forall i \in \{1, \ldots, n\}.\]</p><p>其中，$H_g$ 用于量化查询 $q$ 在粒度 $g$ 下与记忆匹配的不确定性程度。参数 $\lambda$ 是用于控制熵影响程度的超参数，其具体作用与设置在附录 F 中进行了分析。</p><p><strong>软路由权重（Soft Router Weights）。</strong> 我们的核心动机在于：较低的熵 $H_g$ 通常意味着更高的匹配置信度（例如，较低的不确定性表明查询与某些记忆之间存在明确对应关系）；相反，较高的熵则意味着匹配更加模糊（即难以确定查询对应的具体记忆）。为刻画这一特性，我们通过对<strong>熵的倒数进行归一化</strong>来为不同粒度分配权重：</p><p>\[w_g = \frac{1/H_g}{\sum_{g'=1}^{G} 1/H_{g'}}\]</p><p>其中，$G$ 表示粒度的总数。该公式保证了<strong>熵较低（确定性更高）的粒度会被赋予更大的权重</strong>。因此，在检索过程中，系统会对与查询最具确定性关联的粒度所对应的记忆进行强化，从而无需人工干预即可实现自适应优化。</p><p>关于多粒度关联与路由机制的理论分析，详见附录 H。</p><h3 id="记忆检索与过滤（Memory-Retrieval-and-Filter）"><a href="#记忆检索与过滤（Memory-Retrieval-and-Filter）" class="headerlink" title="记忆检索与过滤（Memory Retrieval and Filter）"></a>记忆检索与过滤（Memory Retrieval and Filter）</h3><p>在完成多粒度记忆关联构建并确定粒度权重之后，我们基于图结构化记忆 ${M_i, A_i} \in M \times A$ 对查询 $q$ 进行相关记忆检索。为充分利用记忆之间的关系，本文采用**个性化PageRank（Personalized PageRank, PPR）**算法进行上下文感知排序。</p><p>在粒度层面上，我们将每个 $M_i^g$（即记忆 $M_i$ 在第 $g$ 个粒度上的节点）视为关联图中的一个独立节点。对于每个粒度 $g$，首先根据公式（4）中路由器分配的权重 $w_g$，计算节点 $M_i^g$ 的初始相关性得分：</p><p>\[\text{score}_i^g = w_g \cdot \text{sim}(q, M_i^g)\]</p><p>其中，$\text{sim}(q, M_i^g)$ 表示查询嵌入 $e(q)$ 与该粒度下记忆嵌入 $e(M_i^g)$ 之间的余弦相似度。所有得分集合 ${\text{score}_i^g}$ 构成了粒度级节点上的个性化初始概率分布。</p><p>随后，我们选取得分最高的前 $\alpha$ 个节点作为种子节点（具体分析见附录 F），并在多粒度关联图上运行PPR算法，使相关性沿图结构传播，从而突出那些既与查询直接相关、又与其他高价值节点紧密连接的节点。算法收敛后，根据最终的PPR得分选取得分最高的前 $k$ 个节点，作为候选上下文。参数 $k$ 的影响在第3.4节中进行了实证分析。</p><p><strong>基于LLM的冗余过滤（LLM-Based Redundancy Filtering）。</strong> 为减少噪声并消除多粒度记忆中的冗余信息，我们引入基于LLM的过滤机制。该机制将检索得到的前 $K$ 条记忆与查询 $q$ 一同输入，通过精心设计的提示词（见附录 J），识别并剔除无关或重复内容。最终提供给响应生成模块的上下文被精炼为仅包含与查询最相关的关键信息，从而生成更加简洁且个性化的回复。</p><p>关于多粒度信息的案例分析见附录 K.2，过滤机制的案例分析见附录 K.3。</p><h3 id="实验（Experiments）"><a href="#实验（Experiments）" class="headerlink" title="实验（Experiments）"></a>实验（Experiments）</h3><h4 id="实验设置（Experimental-Settings）"><a href="#实验设置（Experimental-Settings）" class="headerlink" title="实验设置（Experimental Settings）"></a>实验设置（Experimental Settings）</h4><p><strong>数据集与评估指标（Dataset and Metrics）。</strong> 本文在四个综合性的长期记忆数据集上开展实验：LoCoMo、Long-MT-Bench+、LongMemEval-s 和 LongMemEval-m，这些数据集均用于评估LLM智能体在长期对话场景下的能力。由于本文的方法不涉及训练过程（training-free），因此数据集中的全部问答对均用于评估。更详细的数据集统计信息见附录 A。</p><p>为了全面评估模型性能，本文采用多种评价指标，包括：F1 分数（参考 Maharana 等人的设置）、BLEU（默认使用4-gram）、BERTScore，以及 ROUGE 分数（参考 Pan 等人的设置）。此外，本文还引入 <strong>GPT4o-as-Judge（GPT4o-J）</strong> 作为评估方式，即利用 GPT4o 来评估模型生成回答与参考答案之间的一致性。具体的评估提示词详见附录 J。</p><p><strong>基线方法（Baselines）。</strong> 我们将 MemGAS 与多种方法进行了对比：</p><p>（1）<strong>Full History</strong>：直接利用最新的全部对话记录，最多包含 128k tokens 的上下文；</p><p>两种强大的检索模型：</p><p>（2）<strong>MPNet</strong>，</p><p>（3）<strong>Contriever</strong>；</p><p>四种基于记忆的对话模型：</p><p>（4）<strong>RecurSum</strong>：通过LLM递归生成和更新记忆摘要，以支持上下文相关的响应；</p><p>（5）<strong>MPC</strong>：结合提示工程与外部记忆增强LLM能力；</p><p>（6）<strong>A-Mem</strong>：通过生成笔记与链接结构来组织记忆；</p><p>（7）<strong>SeCom</strong>：将记忆划分为语义一致的主题块，并通过压缩去噪提升检索效果；</p><p>两种结构化RAG模型：</p><p>（8）<strong>HippoRAG 2</strong>：引入知识图谱以提升检索效率；</p><p>（9）<strong>RAPTOR</strong>：通过递归摘要与层级聚类构建树结构以增强检索能力；</p><p>此外，还包括两种近期提出的记忆模型：</p><p><strong>HMEM</strong>：提出四层层级化记忆结构，并结合位置索引机制；</p><p><strong>COMEDY</strong>：提供统一的“One-for-All”压缩记忆框架，无需传统记忆存储结构。</p><p>由于篇幅限制，HMEM 与 COMEDY 的实验结果见附录 I.2，更多细节见附录 B。</p><hr><p><strong>实现细节（Implementation Details）。</strong> 我们在所有任务中均使用 <em>gpt-4o-mini-2024-07-18</em> 作为基础模型，包括多粒度信息生成与问答任务。为保证公平性，所有基线方法均采用一致的生成提示。LLM 的 temperature 设置为 0 以确保结果可复现，所有模型均在 zero-shot 设置下运行，具体提示模板见附录 J。</p><p>在检索设置上，所有模型统一采用 top-3 会话检索策略；对于 SeCom 使用 top-3 片段，对于 RAPTOR 使用会话或摘要。编码模型统一采用 <strong>Contriever</strong> 来生成记忆文本的向量表示。超参数通过网格搜索确定：$\lambda \in {0.1, 0.2, 0.3, 0.5, 0.7, 1.0}$，$\alpha \in {5, 10, 15, 20, 25}$。</p><p>由于 LongMTBench+ 缺乏检索任务的真实标注，因此不纳入检索评估。此外，RAPTOR 与 A-Mem 无法在会话级 Recall 指标下进行评估，因为其存储单元为抽象或重写后的表示，无法与真实会话建立确定映射。在 LongMemEval-m 数据集上，RAPTOR、A-Mem 和 HippoRAG 的结果由于运行成本较高而未提供。</p><p>我们还在不同检索器、生成器及查询类型下对多种方法进行了对比分析（详见附录 E.1、E.2 和 E.3）。此外，附录 F 提供了超参数分析，附录 G 提供了误差分析，附录 D 则讨论了方法的额外开销与效率表现。</p><h4 id="整体结果（Overall-Results）"><a href="#整体结果（Overall-Results）" class="headerlink" title="整体结果（Overall Results）"></a>整体结果（Overall Results）</h4><img                           lazyload                       alt="image"                       data-src="https://img.102465.xyz/file/L8cGNJDG.png"                         alt="image.png" width=100%                  ><p>我们分别在表1和表2中展示了问答任务（Question Answering）和检索任务（Retrieval）的实验结果。此外，单一粒度与多粒度方法的对比结果见附录 C。下面对主要结果进行分析。</p><hr><p><strong>问答结果（Question Answering Results）。</strong> 如表1所示，MemGAS 在大多数数据集和评估指标上均稳定优于其他方法。与使用全部历史上下文、容易引入噪声的 Full History 方法不同，MemGAS 通过有效整合并检索最相关的记忆，实现了更优性能。其他基线方法（如 RecurSum 和 SeCom）虽然在特定粒度上进行建模，但由于缺乏跨粒度记忆关联能力，整体效果仍不理想。</p><p>此外，HippoRAG 2 和 RAPTOR 等方法虽然能够在记忆单元之间建立一定的连接，但未能有效构建多粒度关系及其选择机制，从而限制了性能表现。相比之下，MemGAS 通过多粒度记忆单元的动态构建、自适应路由机制以及冗余过滤策略，实现了更优表现，突出了“关联”和“选择”在记忆管理中的关键作用。</p><p>在效率方面，MemGAS 在保持具有竞争力的 token 使用量的同时，实现了更高质量的问答表现，并且相较于 A-Mem 和 RecurSum 等方法，其延迟相当甚至更低，使其在准确性与效率之间取得了更优平衡。</p><hr><img                           lazyload                       alt="image"                       data-src="https://img.102465.xyz/file/eG0Pj322.png"                         alt="image.png" width=100%                  ><p><strong>检索结果（Retrieval Results）。</strong> 如表2所示，MemGAS 在所有数据集上均表现出色，在 Recall 和 NDCG 等指标上持续取得最佳成绩。这些结果表明，该方法在长期记忆构建与检索方面具有良好的有效性与鲁棒性，能够确保查询与最相关的上下文进行准确匹配。</p><hr><h4 id="消融实验（Ablation-Study）"><a href="#消融实验（Ablation-Study）" class="headerlink" title="消融实验（Ablation Study）"></a>消融实验（Ablation Study）</h4><img                           lazyload                       alt="image"                       data-src="https://img.102465.xyz/file/kjQIVutt.png"                         alt="image.png" width=100%                  ><p>表3中的消融实验结果表明，各个组件在提升问答（QA）和检索性能方面都具有重要作用。无论是单独移除高斯混合模型（GMM）、个性化PageRank（PPR）、记忆关联（MA）还是路由器（Router），都会导致性能持续下降，从而验证了这些模块的关键贡献。尤其是在移除所有组件的情况下，性能下降最为显著：F1 分数从 20.38 降至 13.78，Recall@3 从 78.51 降至 71.06，这进一步说明各模块在整体性能提升中的不可或缺性。</p><p>此外，这些模块引入的额外延迟非常有限，其中问答任务的最大延迟增加仅为 0.0191 秒，检索任务为 0.0079 秒。我们还发现，LLM 的 API 调用占据了整体端到端延迟的 98% 以上，是系统响应时间的主要来源，而本文方法引入的额外开销在实际应用中是可以接受的。</p><p>综上所述，该架构在性能提升与计算效率之间实现了良好的平衡。</p>]]>
    </content>
    <id>https://x4ai.cn/2026/04/27/MemGAS/</id>
    <link href="https://x4ai.cn/2026/04/27/MemGAS/"/>
    <published>2026-04-27T07:06:15.000Z</published>
    <summary>本文翻译了论文 &quot;From Single to Multi-Granularity:Toward Long-Term Memory Association and Selection of Conversational Agents&quot;，提出了 MemGAS 框架，通过多粒度关联与自适应选择来构建和检索长期记忆，显著提升了对话系统的性能。</summary>
    <title>MemGAS论文翻译</title>
    <updated>2026-06-06T02:01:51.544Z</updated>
  </entry>
  <entry>
    <author>
      <name>迎风起降</name>
    </author>
    <category term="学习笔记" scheme="https://x4ai.cn/categories/%E5%AD%A6%E4%B9%A0%E7%AC%94%E8%AE%B0/"/>
    <category term="Memory for Dialogue Agents" scheme="https://x4ai.cn/tags/Memory-for-Dialogue-Agents/"/>
    <content>
      <![CDATA[<div align="center" style="font-size:2.2rem;font-weight:800;line-height:1.2;margin:0.6em 0 0.8em;">THEANINE: Timeline-Enhanced Response Generation with Associative Memory Graphs for Lifelong Dialogue Agents</div><div align="center" style="font-size:1.25rem;font-weight:700;line-height:1.35;margin:0 0 0.9em;">THEANINE：基于关联记忆图的终身对话智能体时间线增强回复生成</div><div style="display:flex;gap:8px;flex-wrap:wrap;align-items:center;">  <a href="https://2025.naacl.org/"><img                           lazyload                       alt="image"                       data-src="https://img.shields.io/badge/Conference-NAACL%202025-B31B1B?style=for-the-badge"                         alt="Conference"                 ></a>  <a href="https://github.com/kimnamssya/Theanine"><img                           lazyload                       alt="image"                       data-src="https://img.shields.io/badge/Code-GitHub-181717?style=for-the-badge&logo=github&logoColor=white"                         alt="Code"                 ></a>  <a href="https://huggingface.co/datasets/LangAGI-Lab/teafarm"><img                           lazyload                       alt="image"                       data-src="https://img.shields.io/badge/Dataset-Hugging%20Face-FF9D00?style=for-the-badge&logo=huggingface&logoColor=white"                         alt="Dataset"                 ></a></div><img                           lazyload                       alt="image"                       data-src="https://img.102465.xyz/file/0GRF2xJ2.png"                         alt="ChatGPT Image 2026年4月27日 12_50_25.png" width=100%                  ><h2 id="摘要"><a href="#摘要" class="headerlink" title="摘要"></a>摘要</h2><p>为了实现长期的人机交互，对话智能体需要持续地记忆所感知的信息，并在生成回复（response generation, RG）时能够恰当地检索这些信息。以往研究多关注通过移除过时记忆来提升检索质量，但我们认为，这类记忆在长期对话中能够为回复生成提供丰富且重要的上下文线索（例如用户行为的变化）。<br>本文提出了 THEANINE，一个基于大语言模型（LLM）的终身对话智能体框架。THEANINE 摒弃了记忆删除机制，而是通过基于时间关系与因果关系对大规模记忆进行关联式管理。在这种关联结构的支持下，THEANINE 通过引入“记忆时间线”（memory timelines）来增强回复生成能力——即由一系列记忆构成的序列，用于刻画相关历史事件的演化过程或因果关系。<br>此外，本文还提出了 TeaFarm，一种基于反事实驱动的评估方案，用于弥补 G-Eval 以及人工评估在衡量智能体将历史记忆整合进回复生成过程中的能力方面的不足。</p><h2 id="引言"><a href="#引言" class="headerlink" title="引言"></a>引言</h2><p>一种具有代表性的方法是将过往对话压缩为摘要式记忆，并在后续交互中检索这些记忆以增强回复生成（response generation, RG）（Xu et al., 2022a；Lu et al., 2023）。然而，随着对话不断累积，记忆规模的增长会对检索质量产生负面影响。尽管可以通过更新旧记忆在一定程度上缓解这一问题（Bae et al., 2022；Zhong et al., 2024），但这种常见做法往往会导致严重的信息丢失。</p><p>如图1(a)所示，在记忆更新时间线中，较早的一条关键记忆（例如一个重要的人设信息：“害怕船只”）被移除，从而导致回复生成不当。另一方面，利用近期大语言模型所具备的大上下文窗口来处理全部对话历史或记忆，虽然可以避免信息丢失¹，但往往会使模型对最新用户输入产生偏置性关注（见图1(b)），进而忽略过去相关的重要上下文（Liu et al., 2024）。</p><p>这些现象揭示了构建终身对话智能体所面临的两个核心挑战：</p><ul><li>（i）记忆构建（Memory construction）：如何在不删除旧记忆的前提下，有效存储大规模历史交互信息？</li><li>（ii）回复生成（Response generation）：在不断增长的记忆空间中，如何识别与当前对话最相关的上下文线索，以生成恰当的回复？</li></ul><p>受上述问题的启发，我们提出将这两个挑战分别但互补地加以解决：</p><p>（i）通过放弃记忆更新机制以避免信息丢失，并以一种关联结构在时间线上保留相关记忆；</p><p>（ii）将整条时间线作为整体进行检索，从而在不断扩展的搜索空间中更好地捕捉相关记忆。</p><p>基于这一思路，我们提出了 THEANINE，一个用于支持终身对话智能体的框架。</p><p>在记忆构建阶段（Phase I），不同于将原始记忆句子简单堆叠（Xu et al., 2022a）——这种方式由于信息结构松散，可能会影响记忆检索效果及回复质量（Mousavi et al., 2023；Chen et al., 2023）——THEANINE 将记忆存储为一个有向图结构。在该图中，受人类记忆机制的启发（即人们倾向于基于事件之间的关联将新记忆连接到已有记忆之上）（Bartlett, 1995），各个记忆节点通过其时间关系以及因果常识关系（Hwang et al., 2021）进行连接。</p><p>在这种关联结构的支持下，在用于回复生成（RG）的记忆检索阶段（Phase II-1），我们超越了传统的 top-k 检索方法，进一步获取完整的“时间线”（timelines），从而避免遗漏那些与当前对话文本重叠较低但仍然重要的记忆（Tao et al., 2023）。最后，为了解决离线记忆构建与在线部署之间的不一致问题，THEANINE 在时间线检索后（Phase II-2）利用大语言模型对其进行基于当前对话的细化，使其能够为回复生成（Phase III）提供更加定制化的信息（Chae et al., 2023）。</p><p>本文的贡献主要体现在以下两个方面：</p><ul><li><p>为实现终身对话智能体，本文提出了 THEANINE，这是一个基于大语言模型的框架，通过引入关系感知的记忆图结构以及“时间线增强”（timeline augmentation）机制来支持长期对话。实验结果表明，在自动评估、基于LLM的评估以及人工评估的回复生成（RG）任务中，THEANINE 均优于具有代表性的基线方法。同时，我们验证了该框架能够显著提升记忆检索质量，其处理流程也更符合人类偏好。据我们所知，这是首次在记忆管理与回复生成中显式建模“时间线”（即相互关联的相关记忆）的工作。</p></li><li><p>另一方面，由于对话与参考记忆之间缺乏“黄金映射”（golden mapping），使得评估基于记忆增强的智能体变得困难。为此，我们提出了 TeaFarm，一种基于反事实驱动的评估流程，用于在无需人工干预的情况下，衡量智能体在引用历史信息方面的表现。</p></li></ul><img                           lazyload                       alt="image"                       data-src="https://img.102465.xyz/file/gFdR1JQ3.png"                         alt="ChatGPT Image 2026年4月27日 11_50_57.png" width=100%                  ><h2 id="方法"><a href="#方法" class="headerlink" title="方法"></a>方法</h2><p>我们提出 <strong>THEANINE</strong>，这是一个面向终身对话智能体的框架，其设计灵感来源于人类在对话过程中进行记忆存储与检索的方式（见图2）。</p><h3 id="记忆图构建（Memory-Graph-Construction-Phase-I）"><a href="#记忆图构建（Memory-Graph-Construction-Phase-I）" class="headerlink" title="记忆图构建（Memory Graph Construction, Phase I）"></a>记忆图构建（Memory Graph Construction, Phase I）</h3><p>为了管理大规模记忆，并为回复生成（RG）提供结构化信息（Mousavi et al., 2023；Chen et al., 2023），我们采用记忆图（memory graph）来建模记忆管理过程。该记忆图定义为：</p><p>$$<br>G &#x3D; (V, E)<br>$$</p><p>其中：</p><p>$$<br>V &#x3D; {m_1, m_2, …, m_{|V|}}<br>$$</p><p>$$<br>m &#x3D; (event, time)<br>$$</p><p>$$<br>E &#x3D; {\langle m_i, r_{ij}, m_j \rangle \mid m_i, m_j \in V \land r_{ij} \in R}<br>$$</p><p>$$<br>R &#x3D; {Cause, Reason, Want, …, SameTopic}<br>$$</p><p>在图 $G$ 中，节点集合 $V$ 表示从对话中总结得到的记忆 $m$。每个记忆 $m &#x3D; (event, time)$ 由一个事件（event）³以及其被形成（总结）的时间（time）构成。任意两个记忆节点之间的有向边 $e \in E$ 表示它们的时间顺序关系以及因果常识关系 $r \in R$。</p><p>在每个对话会话 $t$ 结束时，THEANINE 会将该会话中总结得到的新记忆 $m_{\text{new}}$ 逐一链接到当前的记忆图 $G_t$ 中，从而实现记忆的持续扩展与结构化组织。</p><p><strong>Phase I-1：用于记忆链接的关联记忆识别（Identifying associative memories for memory linking）</strong></p><p>参考人类将新记忆与具有相似事件或主题的既有记忆进行关联的方式（即“关联记忆”，associative memories），<strong>THEANINE</strong> 首先从当前记忆图 $G_t$ 中识别这些关联记忆。</p><p>形式化地，给定一个待存储的新记忆 $m_{\text{new}}$，其关联记忆集合 $M_a$ 定义为在 $G_t$ 中与 $m_{\text{new}}$ 具有最高 $j$ 个文本相似度的记忆节点集合，即满足 $|M_a| &#x3D; j$。</p><p><strong>Phase I-2：关系感知的记忆链接（Relation-aware memory linking）</strong></p><p>直观来看，我们可以通过边将 $m_{\text{new}}$ 与 $m \in M_a$ 连接起来，这些边仅表示它们之间的文本相似性以及时间先后顺序。然而，我们发现这种简化的连接方式（例如“这件事发生了 → 类似事件随后发生”）会导致图结构上下文信息不足，从而难以有效支持回复生成（详见第4节）。</p><p>相比之下，人类在理解事件时，往往会考虑事件之间的关系，例如“一个事件如何影响另一个事件？”或“为什么这个人会做出这样的改变？”。因此，<strong>THEANINE</strong> 采用了一种<strong>关系感知的记忆链接机制</strong>：在两个记忆之间建立边时，不仅编码它们的时间顺序，还引入其因果常识关系 $r \in R$。</p><p>在具体实现中，我们采用了 Hwang 等人（2021）提出的常用关系类型，包括 <em>HinderedBy</em>、<em>Cause</em>、<em>Want</em> 等在内的多种关系（详见附录 B.1）。</p><p>此外，在本文中，“事件”（event）指对话系统所感知的信息，包括说话者的行为或发言，以及对说话者人设（persona）的识别与确认。</p><p>接下来，我们首先确定 $m_{\text{new}}$ 与每个关联记忆之间的关系。形式化地，对于每一对 $m_{\text{new}}$ 和 $m \in M_a$，由大语言模型（LLM）基于它们的事件（event）、时间（time）以及其来源对话，分配一个关系 $r \in R$：</p><p>$$<br>M_a^* &#x3D; { m_i \in M_a \mid \Upsilon(m_i, m_{\text{new}}) \in R }<br>$$</p><p>其中，$\Upsilon(\cdot, m_{\text{new}}) \in R$ 表示该记忆与 $m_{\text{new}}$ 之间被赋予了某种关系 $r \in R$，而所有被成功赋予关系的记忆集合定义为 $M_a^*$。</p><p>换言之，$M_a^*$ 表示那些不仅在文本上相关，而且在语义上（通过因果或常识关系）能够与新记忆建立有效连接的关联记忆子集。</p><p>随后，我们将 $m_{\text{new}}$ 连接到记忆图中。首先，我们定位所有包含至少一个 $m \in M_a^*$ 的连通子图（connected component）$C_i \subset G_t$，如图3(a)和(b)所示：</p><p>$$<br>C &#x3D; { C_i \subset G_t \mid V(C_i) \cap M_a^* \neq \varnothing }<br>$$</p><p>其中，$C$ 表示满足条件的所有连通子图的集合，$V(\cdot)$ 表示“该子图中的节点集合”。</p><p>接着，对于每一个 $C_i \subset C$，我们将 $m_{\text{new}}$ 连接到其中<strong>最新的</strong>（most recent）$m \in M_a^*$（见图3(c)）。与 $m_{\text{new}}$ 建立连接的记忆集合 $M_{\text{linked}}$ 定义为：</p><p>$$<br>M_{\text{linked}} &#x3D; { \Omega\big(V(C_i) \cap M_a^*\big) \mid C_i \subset C }<br>$$</p><p>其中，$\Omega(\cdot)$ 表示“集合中时间上最新的记忆”。</p><p>这种策略确保在每个相关的连通子结构中，仅选择代表当前语境最新状态的记忆与 $m_{\text{new}}$ 建立连接，从而在控制图结构复杂度的同时，保留时间与语义上的关键演化信息。</p><p>在将会话 $t$ 中的所有记忆逐一链接到当前记忆图 $G_t$ 后，我们即可得到更新后的新记忆图 $G_{t+1}$。</p><p>Phase I 的整体流程（记忆图构建阶段）的伪代码如算法1所示。</p><img                           lazyload                       alt="image"                       data-src="https://img.102465.xyz/file/gXdo3AKc.png"                         alt="ChatGPT Image 2026年4月27日 12_35_52.png" width=100%                  ><h3 id="时间线检索与时间线细化（Timeline-Retrieval-and-Timeline-Refinement-Phase-II）"><a href="#时间线检索与时间线细化（Timeline-Retrieval-and-Timeline-Refinement-Phase-II）" class="headerlink" title="时间线检索与时间线细化（Timeline Retrieval and Timeline Refinement, Phase II）"></a>时间线检索与时间线细化（Timeline Retrieval and Timeline Refinement, Phase II）</h3><p>得益于所构建的记忆图结构，<strong>THEANINE</strong> 可以在回复生成（RG）过程中引入与当前对话相关的“事件时间线”（timelines），从而缓解传统记忆管理方式中的信息丢失问题（见图1）。在获得记忆图 $G_{t+1}$ 后，THEANINE 在会话 $t+1$ 的回复生成过程中执行以下步骤：</p><p><strong>准备阶段：Top-k 记忆检索（Top-k memory retrieval）</strong><br>在对话进行过程中，以当前对话上下文 $D &#x3D; {u_i}_{i&#x3D;1}^{n}$（由 $n$ 条话语 $u$ 构成）作为查询，从记忆图中检索出 top-$k$ 个最相关的记忆：</p><p>$$<br>M_{re} &#x3D; \lbrace m_{re,i} \mid i &#x3D; 1,\dots,k \rbrace<br>$$</p><p>这些检索到的记忆将作为后续时间线构建与细化的基础输入。</p><p><strong>Phase II-1：原始记忆时间线的检索与解缠（Retrieving and untangling raw memory timelines）</strong></p><p>我们希望不仅获取 $M_{\text{re}}$ 中的记忆，还能够访问围绕这些记忆展开的相关上下文。形式化地，对于每个 $m_{\text{re}} \in M_{\text{re}}$，我们进一步基于记忆图中的连接结构，收集包含该节点的连通子图 $C_{\text{re}} \subset G_{t+1}$。</p><p>然而，由于图结构的存在，这些记忆集合（即 $C_{\text{re}}$）通常是“缠绕”的（即以复杂方式相互连接）。因此，我们需要将其“解缠”为若干条记忆时间线（memory timelines）。每一条时间线表示一系列围绕 $m_{\text{re}}$ 展开的事件，这些事件可能具有相似的起点，但在后续发展中分化为不同的路径。</p><p>为此，我们首先在 $C_{\text{re}}$ 中定位最早的记忆，作为所有时间线的起点 $m_{\text{start}}$（如图4左所示）：</p><p>$$<br>m_{\text{start}} &#x3D; \Theta\big(V(C_{\text{re}})\big)<br>$$</p><p>其中，$\Theta(\cdot)$ 表示“集合中时间最早的记忆”。</p><p>该起点为后续时间线的展开提供了统一的源头，使得不同事件发展路径可以在一致的时间基准下进行建模与分析。</p><p>接下来，以 $m_{\text{start}}$ 为起点，我们沿着时间的“未来方向”对记忆进行解缠，通过遍历图结构，从 $C_{\text{re}}$ 中提取所有包含 $m_{\text{re}}$ 的线性路径（如图4中所示的两条路径）。这一过程会持续进行，直到到达某个终止节点 $\tau[-1]$，其出度为 0（即 $\deg^{+}(\tau[-1]) &#x3D; 0$，表示没有从该节点出发的有向边）。每一条这样的路径都被视为一条原始记忆时间线 $\tau$，用于刻画 $m_{\text{re}}$ 及其相关事件的一种演化过程：</p><p>$$<br>T &#x3D; {\tau \subset C_{\text{re}} \mid \tau \text{ 是一条有向线性图，且 } m_{\text{start}}, m_{\text{re}} \in \tau \land \deg^{+}(\tau[-1]) &#x3D; 0 }<br>$$</p><p>随后，我们从集合 $T$ 中采样 $n$ 条原始时间线 $\tau$。对所有检索得到的 top-$k$ 记忆重复执行 Phase II-1 后，我们最终获得一组原始记忆时间线集合：</p><p>$$<br>\mathcal{T} &#x3D; \bigcup T, \quad |\mathcal{T}| &#x3D; k \times n<br>$$</p><p>该过程使模型能够从图结构中提取出多条可能的事件发展路径，为后续更精细的时间线筛选与回复生成提供丰富的候选上下文。</p><img                           lazyload                       alt="image"                       data-src="https://img.102465.xyz/file/unEiXRbd.png"                         alt="ChatGPT Image 2026年4月27日 12_56_36.png" width=100%                  ><p><strong>Phase II-2：上下文感知的时间线细化（Context-aware timeline refinement）</strong></p><p>尽管我们已经通过引入时间关系与常识关系构建了信息更丰富的记忆图，但如果直接将检索到的时间线用于回复生成（RG），效果仍可能不理想（见第4节 RQ3）。其原因在于：记忆图是在<strong>离线阶段</strong>构建的，并未考虑当前正在进行的对话上下文。因此，在本阶段中，<strong>THEANINE</strong> 通过“上下文感知的时间线细化”来弥合离线记忆构建与在线对话之间的差距。</p><p>受大语言模型能够对其生成结果进行自我优化与修正的能力启发（Madaan et al., 2024），我们利用 LLM 对原始时间线进行重构，使其转化为更契合当前对话需求的信息资源，例如去除冗余内容或突出关键信息。</p><p>形式化地，给定当前对话上下文 $D$ 和检索得到的原始时间线集合 $\mathcal{T}$，LLM 将每一条时间线 $\tau \in \mathcal{T}$ 转换为细化后的时间线集合 $\mathcal{T}_{\Phi}$：</p><p>\[T_{\Phi} = \left\{ \arg\max_{\tau_{\Phi}} P_{\mathrm{LLM}}\left(\tau_{\Phi} \mid D, \tau\right) \mid \tau \in \mathcal{T} \right\}\]</p><p>最终，所有细化后的时间线 $\mathcal{T}_{\Phi}$ 将用于增强回复生成过程。</p><p>Phase II 的完整流程伪代码见算法2。</p><h3 id="时间线增强的回复生成（Timeline-augmented-Response-Generation-Phase-III）"><a href="#时间线增强的回复生成（Timeline-augmented-Response-Generation-Phase-III）" class="headerlink" title="时间线增强的回复生成（Timeline-augmented Response Generation, Phase III）"></a>时间线增强的回复生成（Timeline-augmented Response Generation, Phase III）</h3><p>在这一阶段，<strong>THEANINE</strong> 利用已经细化后的时间线来进行回复生成（RG）。</p><p>形式化地，给定当前对话上下文 \( D = \lbrace u_i \rbrace_{i=1}^{n} \) 以及细化后的时间线集合 \( \mathcal{T}_{\Phi} \)，大语言模型生成下一条回复 \( u_{n+1} \)：</p><p>$$<br>u_{n+1} &#x3D; \arg\max_{u_{n+1}} P_{\text{LLM}}(u_{n+1} \mid D, \mathcal{T}_{\Phi})<br>$$</p><p>也就是说，模型在生成回复时，不仅依赖当前对话内容 $D$，还融合了经过筛选与重构的历史事件时间线 $\mathcal{T}_{\Phi}$，从而能够更好地利用长期记忆中的关键上下文信息，提高回复的连贯性与一致性。</p><h2 id="实验设置"><a href="#实验设置" class="headerlink" title="实验设置"></a>实验设置</h2><h3 id="数据集"><a href="#数据集" class="headerlink" title="数据集"></a>数据集</h3><p>目前，用于长周期、多会话对话的公开数据集仍然较为有限。首先，Multi-Session Chat（MSC）（Xu et al., 2022a）是在 PersonaChat（Zhang et al., 2018）的基础上构建的，通过将原始对话扩展为多轮（五个）会话来实现长期对话建模。随后，DuLeMon（Xu et al., 2022b）和 CareCall（Bae et al., 2022）相继被提出，分别面向中文和韩文的长期对话场景。</p><p>近年来，Conversation Chronicles（CC）（Jang et al., 2023）作为一个新的数据集被发布。与 MSC 不同，CC 为对话参与者引入了明确的关系设定，例如“员工与上司”等，从而增强了对话的结构性与语境约束。</p><p>除了这些开放域数据集之外，Psychological QA⁸ 则关注于临床场景下的长期对话（以中文为主）。在本文中，我们选择 MSC 和 CC 作为评估数据集，以聚焦英文对话场景；而多语言及特定领域的数据集（如 DuLeMon、CareCall 和 Psychological QA）则留待未来工作进一步探索。</p><h3 id="基线方法（Baselines）"><a href="#基线方法（Baselines）" class="headerlink" title="基线方法（Baselines）"></a>基线方法（Baselines）</h3><p>为了评估 <strong>THEANINE</strong> 的性能，除了一些直接利用全部历史对话或记忆的朴素基线方法之外，我们还引入了以下具有代表性的设置与方法：</p><p><strong>Memory Retrieval（记忆检索）</strong><br>遵循 Xu et al. (2022a) 的做法，我们使用检索器从记忆库中提取与当前对话上下文相关的记忆，以增强回复生成（RG）。</p><p><strong>Memory Update（记忆更新）</strong><br>在每个对话会话结束时，我们利用大语言模型实现 Bae et al. (2022) 提出的常用记忆更新算法。该算法包括多种操作，如 <em>Change</em>（修改）、<em>Replace</em>（替换）、<em>Delete</em>（删除）、<em>Append</em>（追加）等（详见附录 H）。</p><p><strong>RSum-LLM</strong><br>一种纯生成式方法（Wang et al., 2023），完全依赖 LLM，通过递归地对记忆池进行摘要与更新来生成回复，而不使用显式的检索模块。</p><p><strong>MemoChat</strong><br>由 Lu et al. (2023) 提出，该方法利用 LLM 的链式思维（Chain-of-Thought, CoT）能力：（i）以“主题—摘要—对话”的结构从历史对话中提取重要记忆；（ii）选择相关记忆；（iii）生成回复。</p><p><strong>COMEDY</strong><br>由 Chen et al. (2024b) 提出，该方法使用 LLM 对每个会话级别的记忆进行总结，并将其压缩为简短的事件表示、用户画像（如行为模式、情绪等）以及用户-智能体关系信息。随后，从这些压缩记忆中选择相关内容用于增强回复生成。</p><h3 id="模型与实现细节（Models-and-Implementation-Details）"><a href="#模型与实现细节（Models-and-Implementation-Details）" class="headerlink" title="模型与实现细节（Models and Implementation Details）"></a>模型与实现细节（Models and Implementation Details）</h3><p><strong>大语言模型（Large language models）</strong><br>在所有实验中（包括各类基线方法），我们统一采用 gpt-3.5-turbo-0125（OpenAI, 2023）用于：（i）记忆摘要（见表6）、（ii）记忆更新，以及（iii）回复生成。温度参数（temperature）设定为 0.75。</p><p><strong>检索器（Retrievers）</strong><br>对于涉及检索的设置，我们使用 text-embedding-3-small（OpenAI, 2024b）来计算文本相似度。在关联记忆识别（Phase I-1）和 top-$k$ 记忆检索（Phase II）中，我们将 $j$ 和 $k$ 均设为 3。而在“Memory Retrieval”基线方法中，按照 Xu et al. (2022a) 的设置，将 $k &#x3D; 6$。</p><p><strong>对话会话（Dialogue sessions）</strong><br>在评估阶段，我们使用 MSC 和 CC 数据集的第 3 至第 5 个会话进行实验。这是因为在前两个会话（session 1–2）中，各方法表现几乎一致（由于尚未形成可更新的记忆）。</p><h2 id="评估方案一：自动评估与人工评估（Evaluation-Scheme-1-Automatic-and-Human-Evaluations）"><a href="#评估方案一：自动评估与人工评估（Evaluation-Scheme-1-Automatic-and-Human-Evaluations）" class="headerlink" title="评估方案一：自动评估与人工评估（Evaluation Scheme 1: Automatic and Human Evaluations）"></a>评估方案一：自动评估与人工评估（Evaluation Scheme 1: Automatic and Human Evaluations）</h2><p>为了评估 <strong>THEANINE</strong> 在长期对话中的回复质量，我们遵循常见做法，采用三类评估方式：（i）自动评估（Automatic evaluations）；（ii）G-Eval（Liu et al., 2023），一种基于大语言模型的生成评估框架；（iii）人工评估（human evaluation）。以下总结若干关键发现（具体细节、提示设计及评估界面见附录 E）：</p><hr><p><strong>（发现1）THEANINE 在回复生成任务中优于各类基线方法。</strong></p><table><thead><tr><th>Methods &#x2F; Metrics</th><th align="right">MSC Bleu-4</th><th align="right">MSC Rouge-L</th><th align="right">MSC Mauve</th><th align="right">MSC BertScore</th><th align="right">CC Bleu-4</th><th align="right">CC Rouge-L</th><th align="right">CC Mauve</th><th align="right">CC BertScore</th></tr></thead><tbody><tr><td>All Dialogue History</td><td align="right">1.65</td><td align="right">14.89</td><td align="right">9.06</td><td align="right">86.28</td><td align="right">4.90</td><td align="right">21.56</td><td align="right">26.47</td><td align="right">88.13</td></tr><tr><td>All Memories &amp; Current Context 𝒟</td><td align="right">1.56</td><td align="right">14.89</td><td align="right">10.62</td><td align="right">86.23</td><td align="right">4.41</td><td align="right">20.06</td><td align="right">38.16</td><td align="right">88.02</td></tr><tr><td>+ Memory Update (Bae et al., 2022)</td><td align="right">1.55</td><td align="right">14.77</td><td align="right">9.28</td><td align="right">86.20</td><td align="right">4.34</td><td align="right">20.34</td><td align="right">34.84</td><td align="right">88.03</td></tr><tr><td>Memory Retrieval (Xu et al., 2022a)</td><td align="right"><strong>1.92</strong></td><td align="right"><strong>15.49</strong></td><td align="right">11.16</td><td align="right">86.47</td><td align="right"><strong>4.93</strong></td><td align="right">20.63</td><td align="right">33.06</td><td align="right">88.07</td></tr><tr><td>+ Memory Update (Bae et al., 2022)</td><td align="right">1.67</td><td align="right">15.30</td><td align="right">13.71</td><td align="right">86.39</td><td align="right">4.46</td><td align="right">20.19</td><td align="right">34.28</td><td align="right">88.02</td></tr><tr><td>Rsum-LLM (Wang et al., 2023)</td><td align="right">0.75</td><td align="right">11.53</td><td align="right">2.45</td><td align="right">84.91</td><td align="right">0.98</td><td align="right">11.42</td><td align="right">2.28</td><td align="right">85.59</td></tr><tr><td>MemoChat (Lu et al., 2023)</td><td align="right">1.42</td><td align="right">13.51</td><td align="right">7.72</td><td align="right">85.96</td><td align="right">2.31</td><td align="right">15.87</td><td align="right">15.12</td><td align="right">87.08</td></tr><tr><td>COMEDY (Chen et al., 2024b)</td><td align="right">1.06</td><td align="right">12.79</td><td align="right">7.27</td><td align="right">85.29</td><td align="right">1.70</td><td align="right">13.57</td><td align="right">1.95</td><td align="right">85.90</td></tr><tr><td><strong>THEANINE (Ours)</strong></td><td align="right">1.80</td><td align="right">15.37</td><td align="right"><strong>18.62</strong></td><td align="right"><strong>86.70</strong></td><td align="right">6.85</td><td align="right"><strong>22.68</strong></td><td align="right"><strong>64.41</strong></td><td align="right"><strong>88.58</strong></td></tr></tbody></table><p align="center"><em>表 1：回复质量的自动评估（各会话平均）</em></p><p>表1展示了不同方法在回复生成（RG）任务中的性能，评估指标包括基于重叠的指标和基于语义嵌入的指标，例如：</p><ul><li>Bleu-4（Papineni et al., 2002）</li><li>Rouge-L（Lin, 2004）</li><li>Mauve（Pillutla et al., 2021）</li><li>BertScore（Zhang et al., 2020）</li></ul><p>在两个数据集上，THEANINE 在整体回复质量方面均优于多种基线方法。尽管在 MSC 数据集上，相较于“Memory Retrieval”方法，THEANINE 在部分基于重叠的指标（如 Bleu-4 和 Rouge-L）上略低，但在基于语义表示的指标（如 Mauve 和 BertScore）上表现显著更优。</p><p>值得注意的是，包括 THEANINE 在内，不采用记忆更新机制的方法通常获得更高的评估分数。这一现象从实验层面支持了本文的核心观点：对于终身对话智能体而言，<strong>无需更新或删除记忆的管理策略</strong>更有利于提升整体性能。</p><hr><p><strong>（发现2 &amp; 3）各阶段均对性能有贡献；整体检索“时间线”相比传统检索带来显著提升。</strong></p><table><thead><tr><th>Settings &#x2F; Metrics</th><th align="right">B-4</th><th align="right">R-L</th><th align="right">Mauve</th><th align="right">Bert</th></tr></thead><tbody><tr><td><strong>THEANINE (Ours)</strong></td><td align="right"><strong>4.32</strong></td><td align="right"><strong>19.03</strong></td><td align="right"><strong>41.52</strong></td><td align="right">87.64</td></tr><tr><td>w&#x2F;o Relation-aware Linking</td><td align="right">4.07</td><td align="right">18.58</td><td align="right">39.69</td><td align="right">87.57</td></tr><tr><td>w&#x2F;o Timeline Refinement</td><td align="right">4.03</td><td align="right">18.82</td><td align="right">41.34</td><td align="right"><strong>87.66</strong></td></tr><tr><td>Broken Down, Shuffled Timeline</td><td align="right">4.15</td><td align="right">18.70</td><td align="right">38.49</td><td align="right">87.61</td></tr><tr><td>Memory Retrieval</td><td align="right">3.43</td><td align="right">18.06</td><td align="right">22.11</td><td align="right">87.27</td></tr></tbody></table><p align="center"><em>表 2：消融实验性能（各数据集平均）。</em></p><p>为了深入分析模型设计的有效性，我们进一步研究了以下因素对 <strong>THEANINE</strong> 性能的影响：<br>（i）在记忆链接阶段（Phase I-2）中移除“关系感知”（relation-awareness）；<br>（ii）移除时间线细化（Phase II-2）；<br>（iii）为客观评估“时间线整体检索”的作用，我们设计了一种对照设置：将检索到的时间线打散为随机排序的事件序列，使其在回复生成（RG）时与传统的 top-$k$ 检索格式一致。</p><p>从表2的结果可以观察到，各组件对性能的贡献排序为：</p><blockquote><p><strong>关系感知记忆链接 &gt; 整体时间线检索 &gt; 时间线细化</strong></p></blockquote><p>这一结果表明：<br>首先，引入因果关系的记忆图构建（relation-aware linking）对于提升性能最为关键，验证了在记忆结构中显式建模事件间因果关系的有效性。<br>其次，基于该图结构对相关事件进行“时间线级别”的整体检索，相比传统的 top-$k$ 检索方法，能够显著提升回复生成质量——即使初始检索规模更小（$k&#x3D;3$ 对比 $k&#x3D;6$）。<br>最后，时间线细化（timeline refinement）虽然带来了性能提升，但增益相对较小，这表明其在实际应用于回复生成时仍存在优化空间，有待未来进一步研究。</p><hr><p><strong>（发现4）人工评估与 G-Eval 表明，THEANINE 在记忆检索的有效性与准确性方面均表现更优。</strong></p><p>除了评估智能体生成的回复质量之外，我们还进一步分析了不同记忆构建方法对<strong>记忆检索质量</strong>的影响。在相同的当前对话作为查询条件下，图5展示了方法之间的两两对比（ours vs. baselines），评估其检索到的记忆在多大程度上有助于提升回复生成（RG）。</p><p>结果表明，<strong>THEANINE</strong> 在所有对比中均取得了更高的胜率，尤其是在人工评估中优势更为明显。这说明该方法能够为回复生成提供更有帮助的记忆增强信息。</p><p>除了“有用性”（helpfulness），对检索“准确性”（accuracy）的客观评估同样至关重要。然而，现有的长期对话数据集并未提供对话上下文与记忆之间的“黄金映射”（即标准答案）。为此，我们筛选了 50 个需要依赖历史记忆进行回复生成的对话上下文（测试样本），并对不同方法的检索准确率进行人工标注评估。</p><p>表3的结果显示，THEANINE 及其消融变体在检索准确率方面均优于各类基线方法，并且其性能排序与表1中的结果以及表4中的成功率保持一致。这进一步验证了该方法在记忆检索与利用方面的整体优势。</p><hr><p><strong>（发现5）人工评估表明，THEANINE 能生成更好地蕴含（entail）历史交互信息的回复。</strong></p><p>在验证了 <strong>THEANINE</strong> 检索到的记忆具有较高“有用性”之后，我们进一步探究这些记忆是否能够促进可靠的长期人机交互。为此，我们组织标注人员通过多数投票的方式，对模型生成的回复进行判断：其与历史对话之间是“蕴含”（entail）、“矛盾”（contradict）还是“中立”（neutral）。</p><p>如图6所示，THEANINE 不仅将“矛盾”回复的比例降低至较低水平（4%），还在“蕴含”类别上取得了最高占比（68%）。这表明其生成的回复在很大程度上能够正确反映并延续历史对话内容，显著优于各类基线方法。</p><p>我们认为，这一优势源于其基于“时间线”的建模方式：通过组织相关记忆的演化过程，模型能够更好地刻画说话者之间的历史交互，从而生成与过去信息更加一致的回复。这种一致性对于对话智能体维持长期用户关系（例如建立亲密感）至关重要（Adiwardana et al., 2020）。</p><p>此外，这种高“蕴含性”与低“矛盾性”的特性也使 THEANINE 在特定应用场景中具有潜在价值。例如，在个性化医疗辅助等场景中，智能体需要确保其回复与用户历史信息（如电子健康记录或既往咨询记录）保持一致，这对于诊断决策具有重要意义（Tseng et al., 2024）。</p><p><strong>补充说明</strong>：Memory Update 方法在“矛盾”回复方面表现更低（2%），这表明在以下两者之间可能存在权衡关系：（i）通过删除过时记忆来避免矛盾；（ii）保留这些记忆以为回复生成（RG）提供更丰富的信息（Kim et al., 2024a）。</p><hr><p><strong>（发现6）人工评估表明，THEANINE 的中间处理过程具有较高合理性。</strong></p><p>如图7所示，评审人员在很大程度上认同 <strong>THEANINE</strong> 的中间步骤设计：</p><ul><li>在记忆链接阶段，92% 的评审认为模型能够正确地为记忆之间分配因果关系，这也解释了其在性能上的提升；</li><li>在时间线细化阶段，评审一致认为（100%，共100个样本）该过程能够有效提取出更有助于回复生成的信息。</li></ul><p>这些结果从人类评估角度进一步验证了 THEANINE 各个关键模块设计的有效性与合理性。更多关于各阶段处理过程及回复生成的具体示例见附录 G。</p><h2 id="评估方案二：TeaFarm-——-基于反事实驱动的长期对话评估框架"><a href="#评估方案二：TeaFarm-——-基于反事实驱动的长期对话评估框架" class="headerlink" title="评估方案二：TeaFarm —— 基于反事实驱动的长期对话评估框架"></a>评估方案二：TeaFarm —— 基于反事实驱动的长期对话评估框架</h2><p>（Evaluation Scheme 2: Counterfactual-driven Evaluation Pipeline）</p><p>在长期对话场景中，评估基于记忆增强的智能体具有较大挑战性，主要原因在于缺乏当前对话与“正确记忆”之间的<strong>标准映射（ground-truth mapping）</strong>。虽然可以借助 G-Eval，向评估用的 LLM（如 GPT-4）输入完整历史对话并判断回复是否正确引用过去信息，但这种方法的效果在很大程度上依赖于评估模型本身的能力（Kim et al., 2024b）。</p><p>为了解决这一问题，本文在提出 <strong>THEANINE</strong> 的同时，引入了 <strong>TeaFarm</strong> ——一种<strong>无需人工参与</strong>的、基于反事实（counterfactual）驱动的评估流程，用于衡量长期对话中记忆增强回复生成的效果。</p><hr><h3 id="基于反事实问题的记忆能力测试"><a href="#基于反事实问题的记忆能力测试" class="headerlink" title="基于反事实问题的记忆能力测试"></a>基于反事实问题的记忆能力测试</h3><p>（Testing Dialogue Agents’ Memory via Counterfactual Questions）</p><p>在 TeaFarm 中，我们通过“误导”对话智能体来测试其记忆能力：系统会构造<strong>与事实相反的陈述（反事实）</strong>，诱导模型生成错误回答，而只有正确引用历史对话信息的模型才能避免被误导。</p><p>具体而言，在与智能体对话时，我们会假装某些不真实的陈述为真（即反事实设定）。例如（见图8），问题可能基于错误前提提出，而模型需要识别并纠正这些前提。</p><p>在实践中，当评估一个已经与用户进行了多轮会话的智能体时，TeaFarm 的流程如下：</p><ol><li><strong>收集历史对话</strong>：整理所有过往会话，并按会话逐一进行摘要；</li><li><strong>输入摘要</strong>：将这些按时间排序的摘要输入到问题生成 LLM，使其能够理解各事件的当前状态（例如：“说话者B没有车”）；</li><li><strong>生成反事实问题</strong>：从双方视角生成反事实问题及其正确答案；</li><li><strong>模拟新会话</strong>：启动一个新的对话会话，与智能体进行自然交互；</li><li><strong>提出问题</strong>：在对话过程中自然地引入反事实问题；</li><li><strong>评估回答</strong>：根据模型是否能够抵抗误导、正确引用历史信息来评估其表现。</li></ol><p>该方法的核心优势在于：无需人工标注“正确记忆”，即可通过反事实机制自动评估模型是否真正掌握并使用了历史信息。TeaFarm 的整体流程图、提示设计及合成数据示例分别见附录 C、H 和 D。</p><img                           lazyload                       alt="image"                       data-src="https://img.102465.xyz/file/Y67lPCrm.png"                         alt="ChatGPT Image 2026年4月27日 13_26_27.png" width=100%                  ><h3 id="TeaFarm-评估结果（TeaFarm-Results）"><a href="#TeaFarm-评估结果（TeaFarm-Results）" class="headerlink" title="TeaFarm 评估结果（TeaFarm Results）"></a>TeaFarm 评估结果（TeaFarm Results）</h3><p>在表4中，<strong>THEANINE</strong> 在成功率（SR, Success Rate）上整体优于各类基线方法，尤其是在 Conversation Chronicles（CC）数据集上表现更为突出。消融实验的性能略低于完整模型，再次验证了<strong>关系感知记忆链接</strong>与<strong>时间线细化机制</strong>的有效性。</p><p>一个值得注意的现象是：所有方法的成功率整体较低。这表明 <strong>TeaFarm</strong> 作为评估框架具有较强的“压力测试”（stress-testing）能力，能够有效揭示长期对话中模型记忆能力的不足。</p><p>此外，一个有趣的发现是：采用检索机制的方法（与 THEANINE 类似）整体优于仅依赖 LLM 的方法（如 RSum-LLM、MemoChat 和 COMEDY）。这一结果在一定程度上支持了本文提出的观点——在大语言模型时代，<strong>构建结构化记忆管理机制（而非完全依赖生成能力）仍然至关重要</strong>。</p><p>最后，为了进一步分析模型的局限性，作者在附录 G 中提供了 THEANINE 在 TeaFarm 框架下失败案例的详细分析，以揭示对话智能体在某些复杂情境下仍面临的挑战。</p>]]>
    </content>
    <id>https://x4ai.cn/2026/04/27/THEANINE/</id>
    <link href="https://x4ai.cn/2026/04/27/THEANINE/"/>
    <published>2026-04-27T03:37:16.000Z</published>
    <summary>本文翻译了 NAACL 26 会议上发表的 THEANINE 论文，该论文提出的基于大语言模型的终身对话智能体框架 THEANINE 的设计与实现细节，包括其基于关联记忆图结构的记忆管理机制、时间线增强的回复生成方法，以及基于反事实驱动的评估方案 TeaFarm。</summary>
    <title>THEANINE论文翻译</title>
    <updated>2026-06-06T02:01:51.544Z</updated>
  </entry>
  <entry>
    <author>
      <name>迎风起降</name>
    </author>
    <category term="学习笔记" scheme="https://x4ai.cn/categories/%E5%AD%A6%E4%B9%A0%E7%AC%94%E8%AE%B0/"/>
    <category term="Memory for Dialogue Agents" scheme="https://x4ai.cn/tags/Memory-for-Dialogue-Agents/"/>
    <content>
      <![CDATA[<div align="center" style="font-size:2.2rem;font-weight:800;line-height:1.2;margin:0.6em 0 0.8em;">A Human-Inspired Reading Agent with Gist Memory</div><div style="display:flex;gap:8px;flex-wrap:wrap;align-items:center;"><a href="https://proceedings.mlr.press/v235/lee24c.html"><img                           lazyload                       alt="image"                       data-src="https://img.shields.io/badge/Conference-ICML%202024-0A66C2?style=for-the-badge"                         alt="Conference"                 ></a><a href="https://www.ccf.org.cn/Academic_Evaluation/NIS/"><img                           lazyload                       alt="image"                       data-src="https://img.shields.io/badge/CCF-A-F57C00?style=for-the-badge"                         alt="CCF-A"                 ></a><a href="https://raw.githubusercontent.com/mlresearch/v235/main/assets/lee24c/lee24c.pdf"><img                           lazyload                       alt="image"                       data-src="https://img.shields.io/badge/PDF-ReadAgent-B31B1B?style=for-the-badge"                         alt="PDF"                 ></a></div><img                           lazyload                       alt="image"                       data-src="https://img.102465.xyz/file/ROzU67QH.png"                         alt="image.png" width=100%                  ><h2 id="摘要"><a href="#摘要" class="headerlink" title="摘要"></a>摘要</h2><blockquote><p>Current Large Language Models (LLMs) are not only limited to some maximum context length, but also are not able to robustly consume long inputs. To address these limitations, we propose ReadAgent, an LLM agent system that increases effective context length up to 20× in our experiments. Inspired by how humans interactively read long documents, we implement ReadAgent as a simple prompting system that uses the advanced language capabilities of LLMs to (1) decide what content to store together in a memory episode, (2) compress those memory episodes into short episodic memories called gist memories, and (3) take actions to look up passages in the original text if ReadAgent needs to remind itself of relevant details to complete a task. We evaluate ReadAgent against baselines using retrieval methods, using the original long contexts, and using the gist memories. These evaluations are performed on three long-document reading comprehension tasks: QuALITY, NarrativeQA, and QMSum. ReadAgent outperforms the baselines on all three tasks while extending the effective context window by 3.5 −20×.</p></blockquote><blockquote><p>当前的大语言模型（LLM）不仅受到<strong>最大上下文长度</strong>的限制，还无法稳定地处理超长输入。为解决这些缺陷，我们提出<strong>ReadAgent</strong>——一套大语言模型智能体系统，在实验中可将<strong>有效上下文长度提升至原来的20倍</strong>。<br>受人类<strong>交互式阅读长文档</strong>的方式启发，我们将ReadAgent设计为一个简洁的提示系统，利用大语言模型强大的语言能力实现三项核心功能：1. 确定哪些内容应整合为一个记忆单元进行存储；2. 将这些记忆单元压缩为简短的<strong>主旨记忆（gist memory）</strong>；3. 当完成任务需要回忆相关细节时，主动执行检索操作，从原文中查找对应段落。我们将ReadAgent与多类基线方法进行对比，包括基于检索的方法、直接使用原始长上下文的方法，以及仅使用主旨记忆的方法。对比实验在三项长文档阅读理解任务上开展：QuALITY、NarrativeQA和QMSum。结果表明，ReadAgent在所有三项任务上<strong>均优于基线方法</strong>，同时将有效上下文窗口扩大了<strong>3.5至20倍</strong>。</p></blockquote><hr><h2 id="方法"><a href="#方法" class="headerlink" title="方法"></a>方法</h2><h3 id="摘要记忆-Gist-Memory"><a href="#摘要记忆-Gist-Memory" class="headerlink" title="摘要记忆 Gist Memory"></a>摘要记忆 Gist Memory</h3><p>摘要记忆（gist memory）是原始长上下文中文本片段的简短摘要的有序集合。构建摘要记忆包含两个步骤：分页和记忆摘要生成，如下文分别描述。</p><h4 id="剧情分页（Episode-Pagination）"><a href="#剧情分页（Episode-Pagination）" class="headerlink" title="剧情分页（Episode Pagination）"></a>剧情分页（Episode Pagination）</h4><p>当 ReadAgent 阅读长文本时，它会通过在何处暂停阅读来决定将哪些内容存储为剧情分页（Episode Pagination）。在每一步中，我们向大语言模型（LLM）提供一段文本，该文本从上一次暂停点开始，直到达到最大词数限制为止。我们提示 LLM 选择一个段落之间的自然停顿点，然后将上次暂停点到当前暂停点之间的内容视为一个剧集（也称为一页）。这就是剧集分页，我们通过以下提示来实现这一机制。</p><p>如提示中所示，段落之间插入了可能的中断点，以带编号的标签表示（例如 ⟨13⟩），这使得该问题成为针对大语言模型的多选题。我们仅在达到 <code>min words</code> 后才开始插入这些带编号的标签，以确保每页至少包含 <code>min words</code> 个词。</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br></pre></td><td class="code"><pre><span class="line">Pagination Prompt:</span><br><span class="line"></span><br><span class="line">You are given a passage that is taken from a larger text (article, book, ...) and some numbered labels between the paragraphs in the passage.  Numbered labels are in angle brackets. For example, if the label number is 19, it shows as ⟨19⟩ in text.  Please choose a label where it is natural to break reading.</span><br><span class="line"></span><br><span class="line">The label can be a scene transition, the end of a dialogue, the end of an argument, a narrative transition, etc.  Please answer with the break point label and explain.  </span><br><span class="line"></span><br><span class="line">For example, if ⟨57⟩ is a good point to break, answer with “Break point: ⟨57⟩\n Because ...”  </span><br><span class="line"></span><br><span class="line">Passage: </span><br><span class="line">&#123;...&#125; </span><br><span class="line">&#123;PARAGRAPH 5 TEXT&#125;</span><br><span class="line">⟨5⟩</span><br><span class="line">&#123;PARAGRAPH 6 TEXT&#125; </span><br><span class="line">⟨6⟩</span><br><span class="line">&#123;PARAGRAPH 7 TEXT&#125;</span><br><span class="line">&#123;...&#125;</span><br></pre></td></tr></table></figure><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br></pre></td><td class="code"><pre><span class="line">你得到了一段文本，这段文本是从更大的文本（文章、书籍等）中提取的，并且在段落之间有一些带编号的标签。带编号的标签用尖括号表示。例如，如果标签编号是19，则在文本中显示为⟨19⟩。请你选择一个标签，在那里自然地中断阅读。</span><br><span class="line"></span><br><span class="line">标签可以是场景转换、对话结束、论点结束、叙事转换等。请回答中断点标签并解释。</span><br><span class="line"></span><br><span class="line">例如，如果⟨57⟩是一个很好的中断点，请回答“中断点：⟨57⟩\n 因为...”</span><br><span class="line"></span><br><span class="line">段落：</span><br><span class="line">&#123;...&#125;</span><br><span class="line">&#123;段落5文本&#125;</span><br><span class="line">⟨5⟩</span><br><span class="line">&#123;段落6文本&#125;</span><br><span class="line">⟨6⟩</span><br><span class="line">&#123;段落7文本&#125;</span><br><span class="line">&#123;...&#125;</span><br></pre></td></tr></table></figure><h4 id="记忆概括-Memory-Gisting"><a href="#记忆概括-Memory-Gisting" class="headerlink" title="记忆概括 Memory Gisting"></a>记忆概括 Memory Gisting</h4><p>对于每一页，我们提示大型语言模型将确切内容缩短为一个摘要或要点，如下所示。</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line">Gisting Prompt:</span><br><span class="line"></span><br><span class="line">Please shorten the following passage.</span><br><span class="line"></span><br><span class="line">Just give me a shortened version. DO NOT explain your reason.</span><br><span class="line"></span><br><span class="line">Passage:</span><br><span class="line">&#123;PAGE TEXT&#125;</span><br></pre></td></tr></table></figure><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line">请缩短以下段落。</span><br><span class="line"></span><br><span class="line">只给我一个简化版本。不要解释你的理由。</span><br><span class="line"></span><br><span class="line">段落：</span><br><span class="line">&#123;PAGE TEXT&#125;</span><br></pre></td></tr></table></figure><p>随后，在每个摘要前添加一个页面标签（例如“⟨第2页⟩\n{摘要内容}”），以对其进行上下文标注（说明摘要来源），然后将所有摘要连接起来。这样就得到了摘要记忆。我们在提示中使用“缩短”一词来生成这些摘要，因为它有助于保持叙述流程的自然性，使连接后的结果更流畅。在我们的实验中，使用“总结”一词则倾向于生成结构化的摘要。</p><p>原始页面大小是决定摘要压缩程度的关键因素。假设我们将最小文本单位视为一个段落。直观上，一个段落与其相邻段落之间可能存在一定的互信息。因此，我们组合的文本块越大，可以去除的重复信息就越多。经验上，使用大语言模型压缩更大的文本块也往往会去除更多细节，这可能影响性能。我们通过调整分页中的最小字数和最大字数来控制页面大小。</p><p>这种权衡在4.4节中进行了研究，如下：</p><blockquote><p>压缩权衡表6展示了随着页面尺寸增大，压缩率提高的经验结果。随着压缩率的降低，摘要对于直接回答问题更有用。然而，对于使用查找功能的ReadAgent，当初始摘要压缩率过高时，准确性会受到影响。</p><img                           lazyload                       alt="image"                       data-src="https://img.102465.xyz/file/5ud80Ud7.png"                         alt="image.png" width=100%                  ></blockquote><blockquote><p>文章中各项任务使用的页面大小如下：</p><img                           lazyload                       alt="image"                       data-src="https://img.102465.xyz/file/Tr6J4RH1.png"                         alt="image.png" width=100%                  ></blockquote><h3 id="交互式查找与应答-Interactive-Look-Up-and-Response"><a href="#交互式查找与应答-Interactive-Look-Up-and-Response" class="headerlink" title="交互式查找与应答 Interactive Look-Up and Response"></a>交互式查找与应答 Interactive Look-Up and Response</h3><p>针对给定的长文档任务，我们希望 ReadAgent 在利用主旨记忆的基础上，进一步执行检索操作，从原文中查找相关细节。由于主旨记忆已通过页码完成上下文关联，我们只需对大语言模型进行提示，使其根据具体任务给出希望检索并重新阅读的页码。下文将讨论两种检索策略：并行一次性检索所有目标页面（ReadAgent‑P） 与串行逐次检索单页（ReadAgent‑S）。</p><h4 id="ReadAgent-P（并行检索）"><a href="#ReadAgent-P（并行检索）" class="headerlink" title="ReadAgent-P（并行检索）"></a>ReadAgent-P（并行检索）</h4><p>如下方所示的问答任务提示示例，我们通常会设定模型可检索的最大页面数量，同时要求模型尽可能少地选取页面，以避免不必要的计算开销与干扰信息。以下提示展示了并行检索的实现方式：模型仅需接收一次提示，即可同时请求多个页面。</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br></pre></td><td class="code"><pre><span class="line">The following text is what you remember from reading</span><br><span class="line">an article and a multiple choice question related to it.</span><br><span class="line"></span><br><span class="line">You may read 1 to 5 page(s) of the article again to refresh</span><br><span class="line">your memory to prepare yourself for the question.</span><br><span class="line">Please respond with which page(s) you would like to read.</span><br><span class="line"></span><br><span class="line">For example, if you only need to read Page 8, respond</span><br><span class="line">with “I want to look up Page [8] to ...”; if you would like</span><br><span class="line">to read Page 7 and 12, respond with “I want to look up</span><br><span class="line">Page [7, 12] to ...”; if you would like to read Page 2, 3, 7,</span><br><span class="line">15 and 18, respond with “I want to look up Page [2, 3, 7,</span><br><span class="line">15, 18] to ...”.</span><br><span class="line"></span><br><span class="line">DO NOT select more pages if you don’t need to.</span><br><span class="line"></span><br><span class="line">You don’t need to answer the question yet.</span><br><span class="line"></span><br><span class="line">Text:</span><br><span class="line">&#123;GIST MEMORY&#125;</span><br><span class="line">Question:</span><br><span class="line">&#123;QUESTION&#125;</span><br></pre></td></tr></table></figure><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br></pre></td><td class="code"><pre><span class="line">以下文本是你阅读一篇文章后记住的内容，以及一道与之相关的选择题。你可以重新阅读这篇文章的 1 至 5 页内容来唤醒记忆，以便作答。请回复你想要重新阅读的页码。</span><br><span class="line"></span><br><span class="line">示例：</span><br><span class="line">若只需阅读第 8 页，请回复：“I want to look up Page [8] to ...”</span><br><span class="line"></span><br><span class="line">若需阅读第 7 页和第 12 页，请回复：“I want to look up Page [7, 12] to ...”</span><br><span class="line"></span><br><span class="line">若需阅读第 2、3、7、15、18 页，请回复：“I want to look up Page [2, 3, 7, 15, 18] to ...”</span><br><span class="line"></span><br><span class="line">非必要不要选择多余页面。你暂时无需回答问题。</span><br><span class="line"></span><br><span class="line">文本：</span><br><span class="line">&#123;GIST MEMORY&#125;</span><br><span class="line">问题：</span><br><span class="line">&#123;QUESTION&#125;</span><br></pre></td></tr></table></figure><p>被选中的原始页面会替换记忆中对应位置的主旨内容，同时保留整体的叙事逻辑。随后我们将任务与更新后的记忆再次输入大模型，提示其完成任务（示例提示词见附录 F）。</p><h4 id="ReadAgent-S（串行检索）"><a href="#ReadAgent-S（串行检索）" class="headerlink" title="ReadAgent-S（串行检索）"></a>ReadAgent-S（串行检索）</h4><p>我们还研究了串行检索策略：模型每次只请求一页，最多可检索设定的最大页面数。在串行检索中，模型在决定下一页要展开的内容前，能够先看到已经展开的页面。这让模型比并行检索获得更多信息，因此在部分场景中表现可能更优。但与模型的交互次数大幅增加会提升计算成本，所以串行检索仅适用于能带来明显收益的任务。</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br></pre></td><td class="code"><pre><span class="line">The following text is what you remember from reading</span><br><span class="line">a meeting transcript, followed by a question about the</span><br><span class="line">transcript.</span><br><span class="line"></span><br><span class="line">You may read multiple pages of the transcript again to</span><br><span class="line">refresh your memory and prepare to answer the question.</span><br><span class="line"></span><br><span class="line">Each page that you re-read can significantly improve your</span><br><span class="line">chance of answering the question correctly.</span><br><span class="line"></span><br><span class="line">Please specify a SINGLE page you would like to read</span><br><span class="line">again or say ”STOP”.</span><br><span class="line"></span><br><span class="line">To read a page again, respond with “Page $PAGE NUM”,</span><br><span class="line">replacing $PAGE NUM with the target page number.</span><br><span class="line"></span><br><span class="line">You can only specify a SINGLE page in your response at</span><br><span class="line">this time.</span><br><span class="line"></span><br><span class="line">To stop, simply say “STOP”. DO NOT answer the ques-</span><br><span class="line">tion in your response.</span><br><span class="line"></span><br><span class="line">Text:</span><br><span class="line">&#123;GISTS WITH IN-LINE EXPANDED PAGES&#125;</span><br><span class="line"></span><br><span class="line">Pages re-read already (DO NOT ask to read them again):</span><br><span class="line">&#123;LIST OF PAGE NUMBERS ALREADY READ&#125;</span><br><span class="line"></span><br><span class="line">Question:</span><br><span class="line">&#123;QUESTION&#125;</span><br><span class="line"></span><br><span class="line">Specify a SINGLE page to read again, or say STOP:</span><br></pre></td></tr></table></figure><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br></pre></td><td class="code"><pre><span class="line">以下是你阅读一份会议记录后记住的内容，随后是一道与该记录相关的问题。</span><br><span class="line"></span><br><span class="line">你可以重新阅读这份记录中的多页内容，以唤醒记忆并准备回答问题。</span><br><span class="line"></span><br><span class="line">你重新阅读的每一页，都能显著提升你正确回答问题的概率。</span><br><span class="line"></span><br><span class="line">请指定一页你想要重新阅读的内容，或者回复STOP。</span><br><span class="line"></span><br><span class="line">若要重新阅读某一页，请以格式 Page 页码 回复，将 $PAGE NUM 替换为目标页码。</span><br><span class="line"></span><br><span class="line">你每次只能指定一页。若要停止检索，直接回复 STOP。请勿在回复中回答问题。</span><br><span class="line"></span><br><span class="line">记忆内容：</span><br><span class="line">&#123;GISTS WITH IN-LINE EXPANDED PAGES&#125;</span><br><span class="line"></span><br><span class="line">已重读页面（请勿重复请求阅读）：</span><br><span class="line">&#123;LIST OF PAGE NUMBERS ALREADY READ&#125;</span><br><span class="line"></span><br><span class="line">问题：</span><br><span class="line">&#123;QUESTION&#125;</span><br><span class="line"></span><br><span class="line">请指定一页重新阅读，或回复 STOP：</span><br></pre></td></tr></table></figure><h3 id="计算代价权衡与可扩展性-Computational-Trade-offs-and-Scalability"><a href="#计算代价权衡与可扩展性-Computational-Trade-offs-and-Scalability" class="headerlink" title="计算代价权衡与可扩展性 Computational Trade-offs and Scalability"></a>计算代价权衡与可扩展性 Computational Trade-offs and Scalability</h3><p>篇章分段、主旨压缩与交互式检索都需要迭代推理。正如后文所示，其额外开销受一个较小系数的线性约束，这使得本方法能够随输入长度良好扩展。</p><p><strong>分段</strong>：理论上，大语言模型可单次通读文档并直接完成分段，因此模型必须处理的最小词量等于文档长度。本文的分段算法将文档切分为不超过最大词数（<code>max_words</code>）的块，并保证每一步至少处理最小词数（<code>min_words</code>）。因此，<strong>最大词数与最小词数的比值</strong>，给出了模型使用该算法所需处理文档词数的倍数上限。</p><p><strong>主旨压缩</strong>：主旨压缩需要对原始输入再做一次完整遍历，因为每一页均独立压缩。</p><p><strong>检索</strong>：并行检索基于主旨而非全文，因此处理长度远短于原始输入的一次遍历。串行检索的每一步与并行检索类似，总开销受允许的最大检索次数限制。</p><p><strong>作答</strong>：最终的任务作答环节与并行检索的计算量相近。当然，提示词模板会带来少量额外开销。</p><p>另一方面，主旨生成是一次性开销，而检索与作答环节主要处理远短于原文的主旨。当同一上下文被用于多个任务时，这部分一次性开销可被均摊。因此在这类场景下，ReadAgent能够减少模型需处理的总token数。</p><p>具体而言，直接基于QuALITY开发集原文（230篇文章、2086个问题）作答时，模型需处理<strong>8,708,434个词</strong>；而使用ReadAgent：</p><ul><li>单页检索：6,499,856个词（节省25.4%）</li><li>最多2页检索：6,933,357个词（节省20.4%）</li><li>最多5页检索：7,503,084个词（节省13.8%）</li></ul><p>可以预期，<strong>压缩率越高，节省的计算量越显著</strong>。</p><h3 id="ReadAgent-的变体-ReadAgent-Variants"><a href="#ReadAgent-的变体-ReadAgent-Variants" class="headerlink" title="ReadAgent 的变体 ReadAgent Variants"></a>ReadAgent 的变体 ReadAgent Variants</h3><p>在附录 G 中，我们讨论了ReadAgent 的多种变体，这些变体可适用于不同的问题场景，包括在阅读长文档之前就已知晓目标任务的情况。在附录 E 中，我们介绍了如何将 ReadAgent 适配应用于网页导航场景。</p><blockquote><p>G.1 无条件 &#x2F; 有条件 ReadAgent</p></blockquote><p>在处理长文本时，用户有可能提前知道需要完成什么任务。这种情况下，我们可以在主旨生成环节的提示词中加入任务描述。这样一来，大语言模型就能更好地过滤掉与任务无关的信息，从而提升效率、减少干扰。这种方法被称为条件式 ReadAgent。</p><p>但更常见的情况是：生成主旨时并不知道具体任务，或者这些主旨需要用于多个不同任务（比如回答关于同一文本的多个问题）。因此，在主旨生成阶段不加入任务信息，模型可以生成通用性更强的主旨，代价是压缩率降低、干扰信息增多。这种设置被称为无条件式 ReadAgent。</p><p>本文仅对无条件式进行了实验验证，但我们认为在某些场景下，条件式会是更优选择。</p><blockquote><p>G.2 特定领域专用 ReadAgent</p></blockquote><p>与附录 G.1 相关，当将 ReadAgent 应用于特定领域时，提供领域专属指令往往会很有帮助。例如，若要使用 ReadAgent 理解程序库，可以向大语言模型给出更具体的指令，让其从每个文件中提取代码用途、功能、关键函数或类的签名等摘要信息作为主旨。</p><blockquote><p>G.3 迭代主旨压缩（Iterative Gisting）</p></blockquote><p>对于非常长的事件历史（例如一段对话），我们可以考虑通过迭代式主旨压缩对更早的记忆进行进一步压缩，从而支持更长的上下文，这与人类对久远记忆会变得模糊的特点相似。尽管这不在本文的研究范围内，但它对于智能助手等应用场景可能十分有用 —— 在这些场景中，随着用户与智能体持续交互，上下文长度会随时间无限增长。</p><h2 id="实验结果"><a href="#实验结果" class="headerlink" title="实验结果"></a>实验结果</h2><h3 id="QuALITY"><a href="#QuALITY" class="headerlink" title="QuALITY"></a>QuALITY</h3><blockquote><p>Quality: Question answering with long input texts, yes!</p></blockquote><p>QuALITY 是一个四选项多选题问答评测任务，其文本数据来源于多个不同领域。该任务使用准确率作为评估指标，随机猜测的正确率为 25%。</p><img                           lazyload                       alt="image"                       data-src="https://img.102465.xyz/file/6EgXukbh.png"                         alt="image.png" width=100%                  ><h3 id="NarrativeQA"><a href="#NarrativeQA" class="headerlink" title="NarrativeQA"></a>NarrativeQA</h3><blockquote><p>The NarrativeQA Reading Comprehension Challenge</p></blockquote><p>NarrativeQA是我们选用的三个阅读理解数据集中<strong>平均上下文长度最长</strong>的数据集。该数据集分为<strong>书籍（古登堡）<strong>和</strong>电影剧本</strong>两部分。其中，古登堡测试集平均长度为 70619 词，最长达 343910 词；电影剧本测试集平均长度为 29963 词。</p><img                           lazyload                       alt="image"                       data-src="https://img.102465.xyz/file/QixgGl1C.png"                         alt="image.png" width=100%                  ><h3 id="QMSum"><a href="#QMSum" class="headerlink" title="QMSum"></a>QMSum</h3><blockquote><p>QMSum: A New Benchmark for Query-based Multi-domain Meeting Summarization</p></blockquote><blockquote><p>SCROLLS: Standardized CompaRison Over Long Language Sequences</p></blockquote><p>QMSum 由各类主题的会议记录以及对应的问题或指令构成。我们使用了由 SCROLLS（Shaham 等人，2022）提供的拼接版 QMSum 数据集。这些会议记录通常篇幅较长，长度在 1000 到 26300 词之间，平均约为 10000 词。图 5 展示了 QMSum 训练集的词数分布直方图。该任务的答案为自由文本格式，因此标准评估指标是 ROUGE F 值。</p><img                           lazyload                       alt="image"                       data-src="https://img.102465.xyz/file/xDeoJtyb.png"                         alt="image.png" width=100%                  >]]>
    </content>
    <id>https://x4ai.cn/2026/04/06/ReadAgent%E8%AE%BA%E6%96%87%E9%98%85%E8%AF%BB/</id>
    <link href="https://x4ai.cn/2026/04/06/ReadAgent%E8%AE%BA%E6%96%87%E9%98%85%E8%AF%BB/"/>
    <published>2026-04-06T11:34:52.000Z</published>
    <summary>本文阅读了 ReadAgent 这篇论文，介绍了该论文提出的面向LLM智能体的阅读代理系统的设计和实现。</summary>
    <title>ReadAgent论文阅读</title>
    <updated>2026-06-06T02:01:51.544Z</updated>
  </entry>
  <entry>
    <author>
      <name>迎风起降</name>
    </author>
    <category term="学习笔记" scheme="https://x4ai.cn/categories/%E5%AD%A6%E4%B9%A0%E7%AC%94%E8%AE%B0/"/>
    <content>
      <![CDATA[<div align="center" style="font-size:2.2rem;font-weight:800;line-height:1.2;margin:0.6em 0 0.8em;">ES-Mem: Event Segmentation-Based Memory for Long-Term Dialogue Agents</div><div style="display:flex;gap:8px;flex-wrap:wrap;align-items:center;">  <a href="https://arxiv.org/abs/2601.07582"><img                           lazyload                       alt="image"                       data-src="https://img.shields.io/badge/arXiv-2601.07582-B31B1B?style=for-the-badge&logo=arxiv&logoColor=white"                         alt="arXiv"                 ></a></a></div><img                           lazyload                       alt="image"                       data-src="https://img.102465.xyz/file/nKPAMXn0.png"                         alt="image.png" width=100%                  ><h2 id="摘要"><a href="#摘要" class="headerlink" title="摘要"></a>摘要</h2><blockquote><p>Memory is critical for dialogue agents to maintain coherence and enable continuous adaptation in long-term interactions. While existing memory mechanisms offer basic storage and retrieval capabilities, they are hindered by two primary limitations: (1) rigid memory granularity often disrupts semantic integrity, resulting in fragmented and incoherent memory units; (2) prevalent flat retrieval paradigms rely solely on surface-level semantic similarity, neglecting the structural cues of discourse required to navigate and locate specific episodic contexts. To mitigate these limitations, drawing inspiration from Event Segmentation Theory, we propose ES-Mem, a framework incorporating two core components: (1) a dynamic event segmentation module that partitions long-term interactions into semantically coherent events with distinct boundaries; (2) a hierarchical memory architecture that constructs multi-layered memories and leverages boundary semantics to anchor specific episodic memory for precise context localization. Evaluations on two memory benchmarks demonstrate that ES-Mem yields consistent performance gains over baseline methods. Furthermore, the proposed event segmentation module exhibits robust applicability on dialogue segmentation datasets.</p></blockquote><blockquote><p>记忆对于对话智能体维持连贯性并在长期交互中实现持续适应至关重要。尽管现有的记忆机制提供了基本的存储与检索能力，但仍受到两个主要限制：（1）僵化的记忆粒度常常破坏语义完整性，导致记忆单元碎片化且缺乏连贯性；（2）主流的扁平化检索范式仅依赖表层语义相似性，忽视了话语结构线索，而这些线索对于定位和检索特定情境片段至关重要。为缓解上述问题，我们受到事件分割理论（Event Segmentation Theory）的启发，提出了 ES-Mem 框架。该框架包含两个核心组件：（1）动态事件分割模块，将长期交互划分为具有清晰边界的语义连贯事件；（2）层次化记忆架构，构建多层级记忆，并利用边界语义锚定特定的情景记忆，从而实现精确的上下文定位。在两个记忆基准数据集上的评估结果表明，ES-Mem 相较于基线方法取得了稳定的性能提升。此外，所提出的事件分割模块在对话分割数据集上也表现出良好的通用性。</p></blockquote><hr><h2 id="引言"><a href="#引言" class="headerlink" title="引言"></a>引言</h2><h3 id="现状"><a href="#现状" class="headerlink" title="现状"></a>现状</h3><p>当前的记忆机制存在两点问题：</p><p>1）记忆粒度是固定的，一般都是以一个 turn 为单位</p><p>2）检索的方法过于扁平，一般都是依赖于表层语义相似性（就是嵌入计算余弦相似度，然后召回 Top-K 这种）进行记忆检索，没有采用记忆 unit 之间的结构化关联。</p><h3 id="动机（故事）"><a href="#动机（故事）" class="headerlink" title="动机（故事）"></a>动机（故事）</h3><p>事件分段理论（Event Segmentation Theory, EST）是一种认知科学理论，用于解释人类在感知、理解和记忆连续事件时如何将其划分为有意义的片段。该理论由心理学家 Jeffrey Zacks 等人提出，对影视理解、记忆编码及预测行为具有重要影响。</p><p>简单来说就是输入对话流，然后通过事件分段模块将对话流划分为一个个事件单元（event unit），每个事件单元都是一个语义连贯的片段。然后在记忆检索阶段，利用事件边界的语义信息来定位和召回特定的情景记忆，从而实现更精确的上下文定位。</p><h3 id="贡献"><a href="#贡献" class="headerlink" title="贡献"></a>贡献</h3><ul><li>提出 ES-Mem，一个基于事件分割理论的新型认知启发式记忆框架。通过将记忆粒度从固定的话轮转变为动态事件，ES-Mem 解决了现有方法固有的语义碎片化问题，并确保了话语整体性的保持。</li><li>实现了一个动态分割模块，该模块根据主题连贯性和意图变化对连续对话流进行划分。这驱动了一种分层记忆架构，包含多层存储机制，从而支持一种以边界为锚点的精准上下文定位策略.</li><li>在两个长期记忆基准测试上系统地评估了 ES-Mem 的性能。实证结果表明，ES-Mem 持续优于各类记忆基线模型。此外，在小型模型场景下，我们的事件分割模块在对话分割任务中表现出强大的适应性.</li></ul><hr><h2 id="相关工作"><a href="#相关工作" class="headerlink" title="相关工作"></a>相关工作</h2><p>这里我们主要关注采用动态记忆颗粒度的</p><h2 id="通过EST理论来提升对话智能体记忆能力的相关工作。"><a href="#通过EST理论来提升对话智能体记忆能力的相关工作。" class="headerlink" title="通过EST理论来提升对话智能体记忆能力的相关工作。"></a>通过EST理论来提升对话智能体记忆能力的相关工作。</h2>]]>
    </content>
    <id>https://x4ai.cn/2026/04/03/ES-Mem%E8%AE%BA%E6%96%87%E9%98%85%E8%AF%BB/</id>
    <link href="https://x4ai.cn/2026/04/03/ES-Mem%E8%AE%BA%E6%96%87%E9%98%85%E8%AF%BB/"/>
    <published>2026-04-03T05:08:29.000Z</published>
    <summary>
      <![CDATA[<div align="center" style="font-size:2.2rem;font-weight:800;line-height:1.2;margin:0.6em 0 0.8em;">ES-Mem: Event Segmentation-Based Memory f]]>
    </summary>
    <title>ES-Mem论文阅读</title>
    <updated>2026-06-06T02:01:51.544Z</updated>
  </entry>
  <entry>
    <author>
      <name>迎风起降</name>
    </author>
    <category term="学习笔记" scheme="https://x4ai.cn/categories/%E5%AD%A6%E4%B9%A0%E7%AC%94%E8%AE%B0/"/>
    <category term="Memory for Dialogue Agents" scheme="https://x4ai.cn/tags/Memory-for-Dialogue-Agents/"/>
    <content>
      <![CDATA[<div align="center" style="font-size:2.2rem;font-weight:800;line-height:1.2;margin:0.6em 0 0.8em;">MemoryBank: Enhancing Large Language Models with Long-Term Memory</div><div style="display:flex;gap:8px;flex-wrap:wrap;align-items:center;">  <a href="https://ojs.aaai.org/index.php/AAAI/article/view/29946"><img                           lazyload                       alt="image"                       data-src="https://img.shields.io/badge/Conference-AAAI%202024-0A66C2?style=for-the-badge"                         alt="Conference"                 ></a>  <a href="https://www.ccf.org.cn/Academic_Evaluation/NIS/"><img                           lazyload                       alt="image"                       data-src="https://img.shields.io/badge/CCF-A-F57C00?style=for-the-badge"                         alt="CCF-A"                 ></a>  <a href="https://arxiv.org/abs/2305.10250"><img                           lazyload                       alt="image"                       data-src="https://img.shields.io/badge/arXiv-2305.10250-B31B1B?style=for-the-badge&logo=arxiv&logoColor=white"                         alt="arXiv"                 ></a>  <a href="https://github.com/zhongwanjun/MemoryBank-SiliconFriend"><img                           lazyload                       alt="image"                       data-src="https://img.shields.io/badge/GitHub-MemoryBank--SiliconFriend-181717?style=for-the-badge&logo=github&logoColor=white"                         alt="GitHub Repo"                 ></a></div><img                           lazyload                       alt="image"                       data-src="https://img.102465.xyz/file/DFjm9xg3.png"                         alt="image.png" width=100%                  ><p>MemoryBank 是一个围绕三个核心支柱构建的统一机制：</p><p>（1）作为主要数据存储库的记忆存储（memory storage），</p><p>（2）用于上下文相关记忆召回的记忆检索器（memory retriever），</p><p>（3）受 Hermann Ebbinghaus 遗忘曲线理论启发的记忆更新器（memory updater），该理论是关于记忆保持与遗忘的经典心理学原理。</p><hr><h2 id="记忆存储-Memory-Storage"><a href="#记忆存储-Memory-Storage" class="headerlink" title="记忆存储 Memory Storage"></a>记忆存储 Memory Storage</h2><p>记忆存储作为 MemoryBank 的“仓库”，是一个强大的数据存储库，包含精细组织的信息集合。如图 1 所示，它存储每日对话记录、过去事件的总结以及不断演化的用户人格评估，从而构建出一个动态且多层次的记忆结构。</p><h3 id="深度记忆存储-In-Depth-Memory-Storage"><a href="#深度记忆存储-In-Depth-Memory-Storage" class="headerlink" title="深度记忆存储 In-Depth Memory Storage"></a>深度记忆存储 In-Depth Memory Storage</h3><p>MemoryBank 的存储系统通过以详细的时间顺序记录多轮对话，捕捉 AI 与用户交互的丰富性。每一段对话都带有时间戳进行存储，形成有序的历史叙述。这种详细记录不仅有助于精确的记忆检索，也为后续的记忆更新提供支持，构成完整的对话历史索引。</p><h3 id="分层事件总结-Hierarchical-Event-Summary"><a href="#分层事件总结-Hierarchical-Event-Summary" class="headerlink" title="分层事件总结 Hierarchical Event Summary"></a>分层事件总结 Hierarchical Event Summary</h3><p>MemoryBank 不仅仅停留在细节记录层面，还模拟人类记忆的特性，对信息进行抽象和提炼。它将冗长的对话压缩为每日事件摘要，再进一步整合为全局摘要，从而形成分层记忆结构，使系统能够从宏观角度理解历史交互与关键事件。具体而言，我们将过去的每日对话或事件作为输入，通过如下提示让 LLM 进行总结：</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line">Summarize the events and key information in the content [dialog/events]</span><br><span class="line"></span><br><span class="line">概述内容[对话/事件]中的情节和关键信息</span><br></pre></td></tr></table></figure><h3 id="动态人格理解-Dynamic-Personality-Understanding"><a href="#动态人格理解-Dynamic-Personality-Understanding" class="headerlink" title="动态人格理解 Dynamic Personality Understanding"></a>动态人格理解 Dynamic Personality Understanding</h3><p>MemoryBank 关注用户人格的建模。它在长期交互过程中持续评估并更新用户特征，生成每日人格洞察，并进一步整合为全局人格认知。这种多层结构使 AI 能够学习、适应并针对不同用户特征进行个性化响应，从而提升用户体验。具体而言，我们使用如下提示:</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line">Based on the following dialogue, please summarize the user’s personality traits and emotions.[dialog]”</span><br><span class="line"></span><br><span class="line">请根据以下对话，概括该用户的性格特征和情绪。[对话]”</span><br></pre></td></tr></table></figure><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line">The following are the user’s exhibited personality traits and emotions throughout multiple days. Please provide a highly concise and general summary of the user’s personality[daily Personalities]。</span><br><span class="line"></span><br><span class="line">以下是用户在数天内表现出的性格特征和情绪。请对用户的性格进行高度简洁且概括性的总结[每日性格]。</span><br></pre></td></tr></table></figure><hr><h2 id="记忆检索-Memory-Retrieval"><a href="#记忆检索-Memory-Retrieval" class="headerlink" title="记忆检索 Memory Retrieval"></a>记忆检索 Memory Retrieval</h2><p>基于稳健的记忆存储结构，MemoryBank 的记忆检索机制类似于知识检索任务。我们采用类似于 Dense Passage Retrieval（Karpukhin et al., 2020）的双塔密集检索模型。在该框架中，每一轮对话及事件摘要都被视为一个记忆单元 m，并通过编码器模型 E(·) 预编码为向量表示 hm。由此，整个记忆集合 M 被表示为：<br>M &#x3D; {h₀ᵐ, h₁ᵐ, … , h|M|ᵐ}。</p><p>这些向量通过 FAISS 进行索引，以实现高效检索。同时，当前对话上下文 c 也通过同一编码器 E(·) 编码为 hc，作为查询向量，在 M 中检索最相关的记忆。在实际应用中，编码器 E(·) 可以替换为任意合适模型。</p><hr><h2 id="记忆更新机制-Memory-Updating-Mechanism"><a href="#记忆更新机制-Memory-Updating-Mechanism" class="headerlink" title="记忆更新机制 Memory Updating Mechanism"></a>记忆更新机制 Memory Updating Mechanism</h2><p>在持久化记忆存储与检索机制的基础上，LLM 的记忆能力可以显著增强。然而，在需要更具人类特征记忆行为的场景（如 AI 陪伴、虚拟 IP 等）中，引入记忆更新机制是必要的。例如，对长期未被访问且重要性较低的信息进行遗忘，可以使 AI 更加自然。</p><p>该机制受到 Ebbinghaus Forgetting Curve 启发，并遵循以下基本规律：</p><ul><li><strong>遗忘速率（Rate of Forgetting）</strong>：记忆保持随时间下降，如果不复习，信息会迅速遗忘。</li><li><strong>时间与记忆衰减（Time and Memory Decay）</strong>：遗忘曲线初期陡峭，随后趋缓。</li><li><strong>间隔效应（Spacing Effect）</strong>：重复学习可以减缓遗忘，使信息更容易被再次掌握。</li></ul><p>（注：虽然该理论还包括过度学习与材料意义性等因素，但本文仅建模上述三条原则。）</p><p>遗忘曲线可表示为指数衰减模型：</p><p>$$<br>R &#x3D; e^{(−t &#x2F; S)}，<br>$$</p><p>其中 R 表示记忆保留率，t 为学习后经过的时间，S 为记忆强度（受学习深度与重复次数影响）。为简化模型，我们将 S 设为离散值，并在首次出现时初始化为 1。当某一记忆被再次调用时，S 增加 1，同时 t 重置为 0，从而降低其被遗忘的概率。</p><p>需要注意的是，这是一种探索性且高度简化的建模方式。现实中的记忆过程更加复杂，并受到多种因素影响，不同个体与不同信息类型的遗忘曲线也会有所不同。</p>]]>
    </content>
    <id>https://x4ai.cn/2026/04/02/MemoryBank%E8%AE%BA%E6%96%87%E9%98%85%E8%AF%BB/</id>
    <link href="https://x4ai.cn/2026/04/02/MemoryBank%E8%AE%BA%E6%96%87%E9%98%85%E8%AF%BB/"/>
    <published>2026-04-02T06:50:02.000Z</published>
    <summary>本文阅读了 AAAI 2024 会议上发表的 MemoryBank 论文，介绍了该论文提出的增强大型语言模型长期记忆能力的机制，包括记忆存储、记忆检索和记忆更新机制，并分析了其基于 Ebbinghaus 遗忘曲线理论的记忆更新方法。</summary>
    <title>MemoryBank论文阅读</title>
    <updated>2026-06-06T02:01:51.544Z</updated>
  </entry>
  <entry>
    <author>
      <name>迎风起降</name>
    </author>
    <category term="学习笔记" scheme="https://x4ai.cn/categories/%E5%AD%A6%E4%B9%A0%E7%AC%94%E8%AE%B0/"/>
    <category term="程序性记忆" scheme="https://x4ai.cn/tags/%E7%A8%8B%E5%BA%8F%E6%80%A7%E8%AE%B0%E5%BF%86/"/>
    <category term="Self-Evolving" scheme="https://x4ai.cn/tags/Self-Evolving/"/>
    <content>
      <![CDATA[<div align="center" style="font-size:2.2rem;font-weight:800;line-height:1.2;margin:0.6em 0 0.8em;">G-Memory: Tracing Hierarchical Memory for Multi-Agent Systems</div><div style="display:flex;gap:8px;flex-wrap:wrap;align-items:center;">  <a href="https://neurips.cc/"><img                           lazyload                       alt="image"                       data-src="https://img.shields.io/badge/Conference-NeurIPS%202025-0A66C2?style=for-the-badge"                         alt="Conference"                 ></a>  <a href="https://www.ccf.org.cn/Academic_Evaluation/NIS/"><img                           lazyload                       alt="image"                       data-src="https://img.shields.io/badge/CCF-A-F57C00?style=for-the-badge"                         alt="CCF-A"                 ></a>  <a href="https://arxiv.org/abs/2506.07398"><img                           lazyload                       alt="image"                       data-src="https://img.shields.io/badge/arXiv-2506.07398-B31B1B?style=for-the-badge&logo=arxiv&logoColor=white"                         alt="arXiv"                 ></a>  <a href="https://github.com/bingreeky/GMemory"><img                           lazyload                       alt="image"                       data-src="https://img.shields.io/badge/GitHub-GMemory-181717?style=for-the-badge&logo=github&logoColor=white"                         alt="GitHub Repo"                 ></a></div><h2 id="瞎逼逼"><a href="#瞎逼逼" class="headerlink" title="瞎逼逼"></a>瞎逼逼</h2><h2 id="摘要"><a href="#摘要" class="headerlink" title="摘要"></a>摘要</h2><blockquote><p>Large language model (LLM)-powered multi-agent systems (MAS) have demonstrated cognitive and execution capabilities that far exceed those of single LLM agents, yet their capacity for self-evolution remains hampered by underdeveloped memory architectures. Upon close inspection, we are alarmed to discover that prevailing MAS memory mechanisms (1) are overly simplistic, completely disregarding the nuanced inter-agent collaboration trajectories, and (2) lack crosstrial and agent-specific customization, in stark contrast to the expressive memory developed for single agents. To bridge this gap, we introduce G-Memory, a hierarchical, agentic memory system for MAS inspired by organizational memory theory [1], which manages the lengthy MAS interaction via a three-tier graph hierarchy: insight, query, and interaction graphs. Upon receiving a new user query, G-Memory performs bi-directional memory traversal to retrieve both high-level, generalizable insights that enable the system to leverage cross-trial knowledge, and fine-grained, condensed interaction trajectories that compactly encode prior collaboration experiences. Upon task execution, the entire hierarchy evolves by assimilating new collaborative trajectories, nurturing the progressive evolution of agent teams. Extensive experiments across five benchmarks, three LLM backbones, and three popular MAS frameworks demonstrate that G-Memory improves success rates in embodied action and accuracy in knowledge QA by up to 20.89% and 10.12%, respectively, without any modifications to the original frameworks.</p></blockquote><blockquote><p>大语言模型（Large Language Models, LLM）驱动的多智能体系统（Multi-Agent Systems, MAS）在认知能力与执行能力方面已显著超越单一LLM智能体。然而，其自我演化能力仍受到记忆架构发展不足的制约。通过深入分析，我们发现当前主流的MAS记忆机制存在两大关键问题：（1）设计过于简单，<strong>完全忽视了多智能体协作过程中的复杂交互轨迹</strong>；（2）缺乏跨任务（cross-trial）与<strong>智能体个体层面</strong>的定制能力，这与单智能体系统中已发展出的高表达力记忆机制形成鲜明对比。<br>为弥补上述不足，本文提出了一种名为G-Memory的分层智能体记忆系统。该系统受到组织记忆理论（organizational memory theory）[1]的启发，通过一个三层图结构层级对MAS中的长程交互进行管理，包括：洞察图（insight graph）、查询图（query graph）以及交互图（interaction graph）。<br>在接收到新的用户查询时，G-Memory通过双向记忆遍历机制，同时检索：（i）高层次、具备泛化能力的抽象洞察，以支持跨任务知识迁移；以及（ii）细粒度、压缩表达的交互轨迹，用以高效编码历史协作经验。在任务执行过程中，该分层结构会通过吸收新的协作轨迹持续演化，从而促进智能体团队能力的渐进式提升。<br>在五个基准任务、三种LLM基础模型以及三种主流MAS框架上的大量实验表明，G-Memory在无需修改原有框架的前提下，可将具身行动任务的成功率提升最高达20.89%，并将知识问答任务的准确率提升最高达10.12%。</p></blockquote><img                           lazyload                       alt="image"                       data-src="https://img.102465.xyz/file/TMz6cqjC.png"                         alt="image.png" width=100%                  ><h2 id="方法"><a href="#方法" class="headerlink" title="方法"></a>方法</h2><h3 id="多智能体形式化定义"><a href="#多智能体形式化定义" class="headerlink" title="多智能体形式化定义"></a>多智能体形式化定义</h3><p>将多智能体系统（Multi-Agent System, MAS）定义为一个有向图 $G &#x3D; (\mathcal{V}, \mathcal{E})$，其中顶点集合 $\mathcal{V}$ 表示智能体的集合，$|\mathcal{V}|$ 表示智能体的数量；边集合 $\mathcal{E} \subseteq \mathcal{V} \times \mathcal{V}<br>$ 表示智能体之间交互通道（channel）的集合。</p><p>每个智能体节点 $\mathcal{C} \in \mathcal{V}$ 有一个四元组来描述：<br>$$<br>C_i &#x3D; (\mathrm{Base}_i,\ \mathrm{Role}_i,\ \mathrm{Mem}_i,\ \mathrm{Plugin}_i)<br>$$</p><table><thead><tr><th>符号</th><th>含义</th></tr></thead><tbody><tr><td>$\mathrm{Base}_i$</td><td>底层的大语言模型实例</td></tr><tr><td>$\mathrm{Role}_i$</td><td>智能体的角色或人设</td></tr><tr><td>$\mathrm{Mem}_i$</td><td>记忆状态，包括历史交互或外部知识库</td></tr><tr><td>$\mathrm{Plugin}_i$</td><td>辅助工具集合（如网页搜索引擎等）</td></tr></tbody></table><p>在接收到用户查询 ${Q}$ 后，系统经历了 ${T}$ 个同步通信周期。</p><p>在每个周期 ${t}$ 中，我们推导出节点的拓扑排序 $π &#x3D; [π1, . . . , πN ]$，使得如果存在从 $π_j$ 到 $π_k$ 的边，则 $j &lt; k$，这保证了每个智能体仅在所有前驱节点完成操作后才处理其输入。对于 $π$ 中的每个智能体 $\mathrm{C}_i$，其在第 $t$ 次迭代中的输出计算为：</p><p>$$<br>r_i^{(t)} &#x3D; C_i(P_{\text{sys}}^{(t)}, Q, { r_j^{(t)} : C_j \in \mathcal{N}^{-}(C_i) })<br>$$</p><table><thead><tr><th>符号</th><th>含义</th></tr></thead><tbody><tr><td>$r_i^{(t)}$</td><td>表示智能体 $C_i$ 在第 $t$ 轮生成的响应（包括推理步骤、中间分析或最终结果）</td></tr><tr><td>$P_{sys}^{(t)}$</td><td>全局系统提示，包含整体指令以及各个智能体的角色设定 $R_i$</td></tr><tr><td>$\mathcal{N}^{-}(C_i)$</td><td>智能体 $C_i$ 的入邻居集合，其输出作为当前输入上下文</td></tr></tbody></table><div style="margin:1rem 0;padding:1rem 1.1rem;border:1px solid #e6e8eb;border-radius:12px;background:linear-gradient(180deg,#fafbfc 0%,#f7f9fb 100%);">  <div style="font-weight:700;margin-bottom:0.6rem;">Tips: 这段定义可以这样理解</div>  <ul style="margin:0 0 0.8rem 1.1rem;padding:0;">    <li style="margin-bottom:0.35rem;">同步通信周期可以理解为整个多智能体系统完成了多少轮协作迭代。</li>    <li>例如查询 Q 输入给智能体 A，A 的输出分别流向 B 和 C，最后由 D 汇总，这就完成了一轮。</li>  </ul>  <div style="padding:0.65rem 0.8rem;border-radius:8px;background:#ffffff;border:1px dashed #d9dee5;font-family:ui-monospace,SFMono-Regular,Menlo,monospace;white-space:pre;line-height:1.35;">[Q] -> A -> B -> D      \          ^       -> C -----  </div>  <div style="margin-top:0.8rem;line-height:1.65;">    拓扑排序用于保证处理顺序正确。    合法示例：<code>π = [A, B, C, D]</code>、<code>π = [A, C, B, D]</code>；    非法示例：<code>π = [B, A, C, D]</code>、<code>π = [C, A, B, D]</code>。    非法的原因是 B、C 依赖 A 的输出，却在 A 之前被执行。  </div></div><p>当所有的智能体完成响应（acted）后，全局聚合算子 $\mathcal{A}$ 将响应集合融合为中间解 $a^{(t)}$。</p><p>$$<br>a^{(t)} &#x3D; \mathcal{A}(r_1^{(t)}, \ldots, r_N^{(t)})<br>$$</p><p>论文中还提到，聚合算子的常见实现有多数投票方案、通过专用聚合智能体进行分层摘要，或直接采用最终智能体的输出作为答案等。<br>这些迭代持续进行 $t &#x3D; {1, …, T}$ 次，直到达到预设限制或满足提前停止标准，从而生成对查询Q的最终响应 $a^{(T)}$。</p><h3 id="G-Memory-设计"><a href="#G-Memory-设计" class="headerlink" title="G-Memory 设计"></a>G-Memory 设计</h3><p>G-Memory 由三层图构成，分别是</p><ul><li>交互图 Interaction Graph (Utterance Graph)：记录原始多智能体交互的细粒度轨迹，包含每轮迭代中每个智能体的响应以及全局聚合结果。</li><li>查询图 Query Graph：历史查询组成的图，相关的历史查询之间会有边连接。</li><li>洞察图 Insight Graph：提取和总结从交互中获得的洞察经验（知识总结）。</li></ul><h4 id="交互图-Interaction"><a href="#交互图-Interaction" class="headerlink" title="交互图 Interaction"></a>交互图 Interaction</h4><p>👉 粒度最细：记录一次 query 内部的对话过程</p><p>$$<br>G_{\text{inter}}^{(Q)} &#x3D; \left( U^{(Q)}, E_u^{(Q)} \right)<br>$$</p><p>其中：</p><ul><li>节点集合：<br>$$<br>U^{(Q)} &#x3D; {u_i}, \quad u_i ≜ (\mathcal{A}_i, m_i)<br>$$</li></ul><table><thead><tr><th>符号</th><th>含义</th></tr></thead><tbody><tr><td>$Q$</td><td>查询，当前处理的任务</td></tr><tr><td>$u_i$</td><td>单个节点第 i 条发言</td></tr><tr><td>$\mathcal{A}_i$</td><td>发言者（哪个智能体说的），$\mathcal{A}_i \in \mathcal{V}$</td></tr><tr><td>$m_i$</td><td>发言内容（文本）</td></tr></tbody></table><ul><li>边集合：<br>$$<br>E_u^{(Q)} \subseteq U^{(Q)} \times U^{(Q)}<br>$$</li></ul><p>定义为：<br>$$<br>(u_j, u_k) \in E_u^{(Q)}<br>\iff u_{j}<br>$$<br>表示存在一条从 $u_j$ 到 $u_k$ 的边，当且仅当 $u_k$ 的产生依赖于 $u_j$。</p><h4 id="查询图-Query-Graph"><a href="#查询图-Query-Graph" class="headerlink" title="查询图 Query Graph"></a>查询图 Query Graph</h4><p>查询图存储了先前处理过的查询及其元数据，具体如下：</p><p>$$<br>G_{query} &#x3D; (Q, E_q) &#x3D; ( \lbrace Q_i, \Psi_i, G_{inter}^{(Q_i)} \rbrace _{i&#x3D;1}^{|Q|}, E_q )<br>$$</p><table><thead><tr><th>符号</th><th>含义</th></tr></thead><tbody><tr><td>$ \mathcal{Q} $</td><td>查询节点集合，$ \mathcal{Q} &#x3D; \lbrace q_i \rbrace $</td></tr><tr><td>$ q_i $</td><td>单个查询节点，$ q_i ≜ (Q_i, \Psi_i, G_{inter}^{(Q_i)} ) $</td></tr><tr><td>$ Q_i $</td><td>原始查询</td></tr><tr><td>$ \Psi_i $</td><td>任务状态（Failed &#x2F; Resolved）</td></tr><tr><td>$ {G}_{\text{inter}}^{(Q_i)} $</td><td>对应的交互图</td></tr><tr><td>$ {E}_q $</td><td>查询之间的关系边集合，$ {E}_q \subseteq {Q} \times {Q} $</td></tr></tbody></table><h4 id="洞察图-Insight-Graph"><a href="#洞察图-Insight-Graph" class="headerlink" title="洞察图 Insight Graph"></a>洞察图 Insight Graph</h4><p>$$<br>G_{insight} &#x3D; (I, E_i) &#x3D; ( \langle r_k, \Omega_k \rangle_{k&#x3D;1}^{|I|}, E_i )<br>$$</p><h2 id="实验"><a href="#实验" class="headerlink" title="实验"></a>实验</h2><h3 id="AutoGen"><a href="#AutoGen" class="headerlink" title="AutoGen"></a>AutoGen</h3><p>AutoGen 是一个由微软在 2023 年提出的一个多智能体框架，强调的是“允许开发者通过多个能够相互对话的智能体来构建大语言模型应用”。</p><img                           lazyload                       alt="image"                       data-src="https://img.102465.xyz/file/vmOCxKWK.png"                         alt="image.png" width=100%                  ><p>有点像群聊的感觉。这篇论文采用的是 上面的 A3 模式，一个 <code>Assistant</code> 和 一个 <code>Grounding Agent</code>，在这个论文的代码中叫 <code>solver</code> 和 <code>ground_truth</code>。</p><p>我看了代码，AutoGen 这个 MAS 的流程是这样的，当 solver 连续三次给出相同动作时，代码里会切换到 ground_truth 角色尝试打破循环。这个 MAS 就两个智能体，平时 solver 负责解题，ground_truth 负责当 solver 卡住时提供正确的答案或指导。</p><p>solver 的 system prompt 如下：</p><figure class="highlight text"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">You are a smart agent designed to solve problems.</span><br></pre></td></tr></table></figure><p>翻译</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">你是一个被设计用来解决问题的智能代理。</span><br></pre></td></tr></table></figure><p>ground_truth 的 system prompt 如下：</p><figure class="highlight text"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line">You are an agent designed to assist the solver agent. When you are called, it means the solver agent has repeatedly output the same incorrect content (It means that the solver agent is stuck in a loop of providing the same incorrect answer or approach).</span><br><span class="line"></span><br><span class="line">Your task is to carefully analyze the input and provide the correct answer or guidance to help the solver agent break out of the stuck state and proceed toward the correct solution.</span><br><span class="line"></span><br><span class="line">NOTE: ** Your approach must avoid being consistent with the previous output&#x27;s approach (as the previous output comes from a solver agent that has already fallen into a misconception, making it definitely wrong). **</span><br></pre></td></tr></table></figure><p>翻译</p><figure class="highlight text"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line">你是一个被设计用来辅助“求解代理（solver agent）”的代理。当你被调用时，意味着求解代理已经多次输出相同的错误内容（也就是说，它陷入了不断重复错误答案或错误方法的循环中）。</span><br><span class="line"></span><br><span class="line">你的任务是仔细分析输入内容，并提供正确的答案或指导，帮助求解代理摆脱这种卡住的状态，继续朝正确的解决方案推进。</span><br><span class="line"></span><br><span class="line">注意：</span><br><span class="line">你的方法必须避免与之前错误输出所采用的方法保持一致（因为之前的输出来自一个已经陷入误解的求解代理，因此其方法必然是错误的）。</span><br></pre></td></tr></table></figure><h3 id=""><a href="#" class="headerlink" title=""></a></h3><h2 id="实验结果"><a href="#实验结果" class="headerlink" title="实验结果"></a>实验结果</h2><h3 id="GPT-4o-mini"><a href="#GPT-4o-mini" class="headerlink" title="GPT-4o-mini"></a>GPT-4o-mini</h3><img                           lazyload                       alt="image"                       data-src="https://img.102465.xyz/file/tvovSItM.png"                         alt="image.png" width=100%                  ><h3 id="Qwen2-5-7B-Instruct"><a href="#Qwen2-5-7B-Instruct" class="headerlink" title="Qwen2.5-7B-Instruct"></a>Qwen2.5-7B-Instruct</h3><img                           lazyload                       alt="image"                       data-src="https://img.102465.xyz/file/RbrTVXlz.png"                         alt="image.png" width=100%                  ><h3 id="Qwen2-5-14B-Instruct"><a href="#Qwen2-5-14B-Instruct" class="headerlink" title="Qwen2.5-14B-Instruct"></a>Qwen2.5-14B-Instruct</h3><img                           lazyload                       alt="image"                       data-src="https://img.102465.xyz/file/LWUGN7dL.png"                         alt="image.png" width=100%                  >]]>
    </content>
    <id>https://x4ai.cn/2026/03/18/G-Memory%E8%AE%BA%E6%96%87%E9%98%85%E8%AF%BB/</id>
    <link href="https://x4ai.cn/2026/03/18/G-Memory%E8%AE%BA%E6%96%87%E9%98%85%E8%AF%BB/"/>
    <published>2026-03-18T04:08:50.000Z</published>
    <summary>本文阅读了 G-Memory 这篇论文，介绍了该论文提出的面向多智能体系统的分层智能体记忆系统 G-Memory 的设计和实现，以及在五个基准任务、三种LLM基础模型以及三种主流MAS框架上的实证实验结果。</summary>
    <title>G-Memory论文阅读</title>
    <updated>2026-06-06T02:01:51.544Z</updated>
  </entry>
  <entry>
    <author>
      <name>迎风起降</name>
    </author>
    <category term="学习笔记" scheme="https://x4ai.cn/categories/%E5%AD%A6%E4%B9%A0%E7%AC%94%E8%AE%B0/"/>
    <category term="Memory for Dialogue Agents" scheme="https://x4ai.cn/tags/Memory-for-Dialogue-Agents/"/>
    <content>
      <![CDATA[<div align="center" style="font-size:2.2rem;font-weight:800;line-height:1.2;margin:0.6em 0 0.8em;">A-Mem: Agentic Memory for LLM Agents</div><div style="display:flex;gap:8px;flex-wrap:wrap;align-items:center;">  <a href="https://neurips.cc/"><img                           lazyload                       alt="image"                       data-src="https://img.shields.io/badge/Conference-NeurIPS%202025-0A66C2?style=for-the-badge"                         alt="Conference"                 ></a>  <a href="https://www.ccf.org.cn/Academic_Evaluation/NIS/"><img                           lazyload                       alt="image"                       data-src="https://img.shields.io/badge/CCF-A-F57C00?style=for-the-badge"                         alt="CCF-A"                 ></a>  <a href="https://arxiv.org/abs/2502.12110"><img                           lazyload                       alt="image"                       data-src="https://img.shields.io/badge/arXiv-2502.12110-B31B1B?style=for-the-badge&logo=arxiv&logoColor=white"                         alt="arXiv"                 ></a>  <a href="https://github.com/WujiangXu/A-mem"><img                           lazyload                       alt="image"                       data-src="https://img.shields.io/badge/GitHub-A--mem-181717?style=for-the-badge&logo=github&logoColor=white"                         alt="GitHub Repo"                 ></a></div><img                           lazyload                       alt="image"                       data-src="https://img.102465.xyz/file/4fPzb60D.png"                         alt="image.png" width=100%                  ><h2 id="瞎逼逼"><a href="#瞎逼逼" class="headerlink" title="瞎逼逼"></a>瞎逼逼</h2><p>这篇文章最精彩的是用 Zettelkasten（卡片盒笔记法） 这个故事包装了一个由提示驱动的 agentic memory system for LLM agents（这里有点绕，就是这个记忆系统本身就是智能体型的，然后这个记忆系统还是专门设计给智能体用的），公式很清晰简洁（主要是我这种小白也能看懂），提示词设计也很有意思，尤其是那个笔记链接的提示词，感觉很有启发性，值得好好学习一下。</p><h2 id="摘要"><a href="#摘要" class="headerlink" title="摘要"></a>摘要</h2><blockquote><p>While large language model (LLM) agents can effectively use external tools for complex real-world tasks, they require memory systems to leverage historical experiences. Current memory systems enable basic storage and retrieval but lack sophisticated memory organization, <strong>despite recent attempts to incorporate graph databases</strong>. Moreover, these systems’ <strong>fixed operations and structures</strong> limit their adaptability across diverse tasks. To address this limitation, this paper proposes a novel agentic memory system for LLM agents that can <strong>dynamically organize memories in an agentic way</strong>. Following the basic principles of the <strong>Zettelkasten method</strong>, we designed our memory system to create interconnected knowledge networks through dynamic indexing and linking. When a new memory is added, we generate a comprehensive note containing multiple structured attributes, including contextual descriptions, keywords, and tags. The system then analyzes historical memories to identify relevant connections, establishing links where meaningful similarities exist. Additionally, this process <strong>enables memory evolution</strong> – as new memories are integrated, they can trigger updates to the contextual representations and attributes of existing historical memories, allowing the memory network to continuously refine its understanding. Our approach combines the structured organization principles of Zettelkasten with the flexibility of agent-driven decision making, allowing for more adaptive and context-aware memory management. Empirical experiments on six foundation models show superior improvement against existing SOTA baselines.</p></blockquote><blockquote><p>虽然大型语言模型（LLM）智能体能够有效利用外部工具来完成复杂的现实任务，但它们仍然需要 <strong>记忆系统</strong> 来利用历史经验。现有的记忆系统虽然能够实现基本的 <strong>存储与检索</strong> 功能，但在记忆组织方面仍然较为简单，<strong>尽管近期已有一些尝试将图数据库引入其中</strong>。此外，这些系统通常依赖 <strong>固定的操作方式和结构设计</strong>，这限制了它们在不同任务场景中的适应能力。<br>为了解决这一问题，本文提出了一种新的 <strong>面向 LLM 智能体的 agentic 记忆系统</strong>，该系统能够以 <strong>智能体驱动（agentic）的方式动态组织记忆</strong>。借鉴 <strong>Zettelkasten（卡片盒笔记法）</strong> 的基本思想，我们设计了一种记忆系统，通过 <strong>动态索引与链接机制</strong> 构建相互连接的知识网络。<br>当新的记忆被加入时，系统会生成一条包含多种结构化属性的完整笔记，例如 <strong>上下文描述、关键词以及标签</strong>。随后，系统会分析历史记忆以识别相关连接，并在存在有意义相似性的情况下建立记忆之间的链接。此外，这一过程还 <strong>支持记忆演化（memory evolution）</strong>：当新的记忆被整合进系统时，它们可能会触发对已有历史记忆的 <strong>上下文表示和属性进行更新</strong>，从而使整个记忆网络能够持续优化其理解。<br>我们的方法结合了 <strong>Zettelkasten 的结构化知识组织原则</strong> 与 <strong>智能体驱动决策的灵活性</strong>，从而实现更加 <strong>自适应且具备上下文感知能力的记忆管理机制</strong>。在六种基础模型上的实证实验表明，该方法相较于当前 <strong>最先进（SOTA）的基线方法</strong> 取得了显著性能提升。</p></blockquote><h2 id="方法"><a href="#方法" class="headerlink" title="方法"></a>方法</h2><h3 id="生成笔记-Note-Construction"><a href="#生成笔记-Note-Construction" class="headerlink" title="生成笔记 Note Construction"></a>生成笔记 Note Construction</h3><p>记忆 $M$ 由多个笔记 Note $m_i$ 组成，每个记忆单元由多个属性组成的结构化表示，包含以下元素：</p><p>$$<br>m_i &#x3D; {c_i, t_i, K_i, G_i, X_i, e_i, L_i}<br>$$</p><table><thead><tr><th>符号</th><th>描述</th></tr></thead><tbody><tr><td>$c_i$</td><td>原始交互内容（Original interaction content）</td></tr><tr><td>$t_i$</td><td>交互的时间戳（Timestamp）</td></tr><tr><td>$K_i$</td><td>由 LLM 生成的高频关键词，用于捕捉交互中的核心概念</td></tr><tr><td>$G_i$</td><td>由 LLM 生成的标签集合（tags），用于对记忆进行分类</td></tr><tr><td>$X_i$</td><td>由 LLM 生成的语境描述（contextual description），提供更丰富的语义理解</td></tr><tr><td>$e_i$</td><td>记忆内容的语义向量表示（embedding），用于计算记忆之间的语义相似度</td></tr><tr><td>$L_i$</td><td>与当前记忆具有语义关联的已链接记忆集合（linked memories）</td></tr></tbody></table><p>笔记构建过程使用精心设计提示模板 $P_{s1}$ 提示 LLM，输入为原始交互内容 $c_i$、时间戳 $t_i$ 和提示模板 $P_{s1}$，LLM 输出为结构化记忆单元的属性（$K_i, G_i, X_i$）：</p><p>$$<br>K_i,, G_i,, X_i \leftarrow \mathrm{LLM}(c_i \parallel t_i \parallel P_{s1})<br>$$</p><p>遵循 Zettelkasten 的原子性原则，每条笔记仅捕获一个自包含的知识单元。</p><p>为实现高效检索与关联，通过 文本编码器（嵌入模型） 计算一条稠密向量表征，该表征封装了笔记所有文本成分。</p><p>$$<br>e_i &#x3D; f_{\text{enc}}\ \left[ \operatorname{concat}(c_i, K_i, G_i, X_i) \right]<br>$$</p><h3 id="笔记链接-Link-Generation"><a href="#笔记链接-Link-Generation" class="headerlink" title="笔记链接 Link Generation"></a>笔记链接 Link Generation</h3><p>A-Mem 系统实现了一个自主链接生成机制，使新的记忆笔记能够在没有预定义规则的情况下形成有意义的连接。</p><p>当构建的记忆笔记 $m_n$ 被添加到系统中时，我们首先利用其语义嵌入进行基于相似性的检索。对于每个现有的记忆笔记 $m_j \in M$，我们计算一个相似性得分（其实就是余弦相似度Cosine Similarity）：</p><p>$$<br>s_{n,j} &#x3D; \frac{e_n \cdot e_j}{\lVert e_n \rVert \lVert e_j \rVert}<br>$$</p><p>系统识别出最相关的 top-k 个记忆笔记：</p><p>$$<br>M_{near}^{n} &#x3D; \lbrace m_j | rank(s_{n,j}) \le k,; m_j \in M \rbrace<br>$$</p><p>基于这些候选的最近记忆，我们提示大语言模型（LLM）分析它们之间可能存在的潜在共同属性相关的联系。形式化地，记忆 $m_n$ 的更新链接集如下：</p><p>$$<br>L_i \leftarrow \text{LLM}(m_n ||  M_{near}^n || P_{s2})<br>$$</p><p>基于这些候选最近记忆，我们提示大语言模型（LLM）分析它们可能共有的潜在属性。正式地，记忆 $m_n$ 的更新链接集如下：每个生成的链接 $l_i$ 的结构为：$L_i &#x3D; {m_i, \ldots, m_k}$。</p><p>通过<strong>将基于嵌入的检索作为初始过滤器</strong>，我们实现了高效的可扩展性，同时保持了语义相关性。A-MEM 能够快速识别大型记忆集合中的潜在连接，而无需进行详尽比较。更重要的是，<strong>由 LLM 驱动的解析能够实现对关系的细致理解，超越简单的相似性度量</strong>。该语言模型可以识别微妙的模式、因果关系和概念性关联，而这些可能无法仅从嵌入相似性中显现。我们实现了 Zettelkasten 原则中的灵活链接机制，同时借助现代语言模型。由此形成的网络自然地从记忆内容和上下文中涌现，从而实现自然的知识组织。（用语义嵌入先进行初筛选，然后提示大模型来进行最终的链接生成）</p><h3 id="记忆演化-Memory-Evolution"><a href="#记忆演化-Memory-Evolution" class="headerlink" title="记忆演化 Memory Evolution"></a>记忆演化 Memory Evolution</h3><p>在为新记忆建立链接之后，A-MEM 会根据其文本信息和与新记忆的关联关系来演化被检索到的记忆。</p><p>对于最近邻居集合 $\mathcal{M}_{\text{near}}^{n}$ 中的每个记忆 $m_j$，系统会决定是否需要更新其上下文、关键词和标签。该演化过程可以形式化地表示为：</p><p>$$<br>m_j^{*} \leftarrow \text{LLM}(m_n || (M_{near}^n - m_j) || m_j || P_{s3})<br>$$</p><p>经过演化的记忆 $m_j^{*}$ 随后取代集合 $\mathcal{M}$ 中原有的记忆 $m_j$。这种演化方法能够实现持续更新和新连接的建立，模仿人类学习过程。随着系统随时间处理更多记忆，它会发展出越来越复杂的知识结构，在多个记忆中发现更高阶的模式和概念。这为自主记忆学习奠定基础，使知识的组织通过新体验与已有记忆的持续互动而日益丰富。</p><h3 id="检索-Retrieve-Relative-Memory"><a href="#检索-Retrieve-Relative-Memory" class="headerlink" title="检索 Retrieve Relative Memory"></a>检索 Retrieve Relative Memory</h3><p>在每轮交互中，我们的 A-MEM 执行上下文感知的记忆检索，为智能体提供相关的历史信息。给定当前交互中的查询文本 $q$，我们首先使用与记忆笔记相同的文本编码器计算其密集向量表示：</p><p>$$<br>e_q &#x3D; f_{\mathrm{enc}}(q)<br>$$</p><p>系统随后使用<strong>余弦相似度</strong>计算查询嵌入与 $\mathcal{M}$ 中所有现有记忆笔记之间的相似度分数：</p><p>$$<br>s_{q,i} &#x3D; \frac{e_q \cdot e_i}{\lVert e_q \rVert \lVert e_i \rVert},<br>\quad \text{where } e_i \in m_i,\ \forall m_i \in \mathcal{M}<br>$$</p><p>然后我们从历史记忆存储中检索最相关的 $k$ 条记忆，以构建一个语境恰当的提示。</p><p>$$<br>M_{retrieved} &#x3D; \lbrace m_i  |  rank(s_{q,i}) \le k, m_i \in M \rbrace<br>$$</p><p>这些检索到的记忆提供了相关的历史背景，有助于智能体更好地理解和响应当前的交互。检索到的上下文通过将当前交互与记忆系统中存储的相关过去经验联系起来，丰富了智能体的推理过程。</p><h2 id="实验"><a href="#实验" class="headerlink" title="实验"></a>实验</h2><h3 id="实验结果"><a href="#实验结果" class="headerlink" title="实验结果"></a>实验结果</h3><h4 id="LoCoMo-数据集"><a href="#LoCoMo-数据集" class="headerlink" title="LoCoMo 数据集"></a>LoCoMo 数据集</h4><img                           lazyload                       alt="image"                       data-src="https://img.102465.xyz/file/3BX9CggA.png"                         alt="LoCoMo实验结果" width=100%                  ><hr><img                           lazyload                       alt="image"                       data-src="https://img.102465.xyz/file/BAB13b5q.png"                         alt="image.png" width=100%                  ><hr><img                           lazyload                       alt="image"                       data-src="https://img.102465.xyz/file/fqIS5y7t.png"                         alt="image.png" width=100%                  ><hr><img                           lazyload                       alt="image"                       data-src="https://img.102465.xyz/file/T8CMNbAJ.png"                         alt="image.png" width=100%                  ><h4 id="DialSim-数据集"><a href="#DialSim-数据集" class="headerlink" title="DialSim 数据集"></a>DialSim 数据集</h4><img                           lazyload                       alt="image"                       data-src="https://img.102465.xyz/file/G2wy22RC.png"                         alt="DialSim实验结果" width=100%                  ><h3 id="消融实验"><a href="#消融实验" class="headerlink" title="消融实验"></a>消融实验</h3><p>“w&#x2F;o”表示特定模块被移除的实验设置。LG和ME分别代表链路生成模块和记忆演化模块。</p><img                           lazyload                       alt="image"                       data-src="https://img.102465.xyz/file/AYM8cTYG.png"                         alt="image.png" width=100%                  ><h3 id="超参数敏感性分析"><a href="#超参数敏感性分析" class="headerlink" title="超参数敏感性分析"></a>超参数敏感性分析</h3><img                           lazyload                       alt="image"                       data-src="https://img.102465.xyz/file/HFmcmChS.png"                         alt="image.png" width=100%                  ><p>关于检索时候取 Top-K 的值</p><blockquote><p>这里是不是有点奇怪，为什么 GPT-4o-mini 和 GPT-4o 的 Top-K 都是 40，而其他模型的 Top-K 都是 10？<br>理论上 GPT-4o-mini 和 GPT-4o 的性能会更强，所以应该需要更加少的Top-K就可以达到相似的性能；还是说他性能强，增大Top-K还可以进一步取得更加好的效果，直到40才收敛。</p></blockquote><table><thead><tr><th>Model</th><th>Multi Hop</th><th>Temporal</th><th>Open Domain</th><th>Single Hop</th><th>Adversial</th></tr></thead><tbody><tr><td>GPT-4o-mini</td><td>40</td><td>40</td><td>50</td><td>50</td><td>40</td></tr><tr><td>GPT-4o</td><td>40</td><td>40</td><td>50</td><td>50</td><td>40</td></tr><tr><td>Qwen2.5-1.5b</td><td>10</td><td>10</td><td>10</td><td>10</td><td>10</td></tr><tr><td>Qwen2.5-3b</td><td>10</td><td>10</td><td>50</td><td>10</td><td>10</td></tr><tr><td>Llama3.2-1b</td><td>10</td><td>10</td><td>10</td><td>10</td><td>10</td></tr><tr><td>Llama3.2-3b</td><td>10</td><td>20</td><td>10</td><td>10</td><td>10</td></tr></tbody></table><h3 id="基线方法-Baselines"><a href="#基线方法-Baselines" class="headerlink" title="基线方法 Baselines"></a>基线方法 Baselines</h3><ul><li><p>LoCoMo：不使用记忆机制，直接将全部历史对话作为提示输入模型进行问答。</p></li><li><p>ReadAgent：通过“分页 → 记忆摘要 → 交互检索”三阶段流程处理长上下文文档。</p></li><li><p>MemoryBank：基于遗忘曲线动态更新记忆，并通过长期交互构建用户画像。</p></li><li><p>MemGPT：借鉴操作系统内存层级，通过“主上下文 + 外部上下文”管理长期信息.</p></li></ul><h3 id="数据集-Dataset"><a href="#数据集-Dataset" class="headerlink" title="数据集 Dataset"></a>数据集 Dataset</h3><h4 id="LoCoMo-数据集-1"><a href="#LoCoMo-数据集-1" class="headerlink" title="LoCoMo 数据集"></a>LoCoMo 数据集</h4><blockquote><p>Adyasha Maharana, Dong-Ho Lee, Sergey Tulyakov, Mohit Bansal, Francesco Barbieri, and Yuwei Fang. Evaluating very long-term conversational memory of llm agents. arXiv preprint arXiv:2402.17753, 2024.</p></blockquote><p>LoCoMo 是一个用于评估长对话理解能力的问答数据集，相比传统对话数据集，其对话长度显著更长。以往数据集通常包含约 1K tokens、4–5 个会话的对话，而 LoCoMo 的对话平均长度达到 约 9K tokens，最多可覆盖 35 个会话。因此，该数据集特别适合评估模型在长程依赖建模和跨会话一致性保持方面的能力。</p><p>LoCoMo 的问题类型设计较为多样，用于全面评估模型对长对话内容的理解能力，主要包括：</p><ul><li><p>Single-hop questions：可在单个会话中直接找到答案的问题</p></li><li><p>Multi-hop questions：需要跨多个会话整合信息才能回答的问题</p></li><li><p>Temporal reasoning questions：测试模型对时间相关信息的理解能力</p></li><li><p>Open-domain knowledge questions：需要结合对话上下文和外部知识进行回答的问题</p></li><li><p>Adversarial questions：用于测试模型识别不可回答问题的能力</p></li></ul><p>LoCoMo 数据集包含 7,512 个问答对，覆盖上述不同类型的问题。</p><h4 id="DialSim-数据集-1"><a href="#DialSim-数据集-1" class="headerlink" title="DialSim 数据集"></a>DialSim 数据集</h4><blockquote><p>Jiho Kim, Woosog Chay, Hyeonji Hwang, Daeun Kyung, Hyunseung Chung, Eunbyeol Cho, Yohan Jo, and Edward Choi. Dialsim: A real-time simulator for evaluating long-term multi-party dialogue understanding of conversational agents. arXiv preprint arXiv:2406.13144, 2024.</p></blockquote><p>DialSim 是一个用于评估长期对话记忆能力的问答数据集，来源于长期多角色对话场景。该数据集基于热门电视剧（如 Friends、The Big Bang Theory 和 The Office）构建，覆盖 约五年的剧情内容。</p><p>DialSim 数据集包含 1,300 个对话会话（sessions），约 350,000 个 tokens。每个会话包含 1000+ 个问题，数据集中的问题主要来源于：</p><ul><li>粉丝问答网站中的精炼问题</li><li>基于时间知识图谱生成的复杂问题</li></ul><p>由于对话跨度长、问题复杂，该数据集特别适合用于评估模型在长期对话记忆、跨时间推理以及多角色对话理解方面的能力。</p><h3 id="模型-foundation-models"><a href="#模型-foundation-models" class="headerlink" title="模型 foundation models"></a>模型 foundation models</h3><ul><li>Qwen-2.5 1.5B&#x2F;3B</li><li>Llama-3.2 1B&#x2F;3B</li><li>DeepSeek-R1-32B</li><li>GPT-4o-mini </li><li>Claude 3.0 Haiku</li><li>Claude 3.5 Haiku</li><li>文本嵌入 all-minilm-l6-v2</li></ul><h2 id="Prompt-设计"><a href="#Prompt-设计" class="headerlink" title="Prompt 设计"></a>Prompt 设计</h2><h3 id="生成笔记-附录B-1"><a href="#生成笔记-附录B-1" class="headerlink" title="生成笔记 附录B.1"></a>生成笔记 附录B.1</h3><p>提示$ P_{s1}$<br>原文：</p><figure class="highlight text"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br></pre></td><td class="code"><pre><span class="line">Generate a structured analysis of the following content by:</span><br><span class="line">1. Identifying the most salient keywords (focus on nouns, verbs, and key concepts)</span><br><span class="line">2. Extracting core themes and contextual elements</span><br><span class="line">3. Creating relevant categorical tags</span><br><span class="line"></span><br><span class="line">Format the response as a JSON object:</span><br><span class="line"></span><br><span class="line">&#123;</span><br><span class="line">  &quot;keywords&quot;: [</span><br><span class="line">    // several specific, distinct keywords that capture key concepts and terminology</span><br><span class="line">    // Order from most to least important</span><br><span class="line">    // Don’t include keywords that are the name of the speaker or time</span><br><span class="line">    // At least three keywords, but don’t be too redundant.</span><br><span class="line">  ],</span><br><span class="line">  &quot;context&quot;:</span><br><span class="line">    // one sentence summarizing:</span><br><span class="line">    // - Main topic/domain</span><br><span class="line">    // - Key arguments/points</span><br><span class="line">    // - Intended audience/purpose</span><br><span class="line">  ,</span><br><span class="line">  &quot;tags&quot;: [</span><br><span class="line">    // several broad categories/themes for classification</span><br><span class="line">    // Include domain, format, and type tags</span><br><span class="line">    // At least three tags, but don’t be too redundant.</span><br><span class="line">  ]</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line">Content for analysis:</span><br></pre></td></tr></table></figure><p>中文翻译：</p><figure class="highlight text"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br></pre></td><td class="code"><pre><span class="line">请对以下内容进行结构化分析，要求：</span><br><span class="line"></span><br><span class="line">1. 识别最重要的关键词</span><br><span class="line">- 重点关注名词、动词以及关键概念。</span><br><span class="line"></span><br><span class="line">2. 提取核心主题与上下文要素</span><br><span class="line"></span><br><span class="line">3. 生成相关的分类标签</span><br><span class="line"></span><br><span class="line">请将输出组织为如下 JSON 对象：</span><br><span class="line"></span><br><span class="line">&#123;</span><br><span class="line">  &quot;keywords&quot;: [</span><br><span class="line">    // 若干具体且彼此区分明显的关键词，用于概括核心概念与术语</span><br><span class="line">    // 按重要性从高到低排序</span><br><span class="line">    // 不要包含说话者姓名或时间信息</span><br><span class="line">    // 至少提供 3 个关键词，但避免过度重复</span><br><span class="line">  ],</span><br><span class="line">  &quot;context&quot;: &quot;用一句话总结以下内容：主题领域、关键观点以及预期用途或受众&quot;,</span><br><span class="line">  &quot;tags&quot;: [</span><br><span class="line">    // 若干较宽泛的分类标签</span><br><span class="line">    // 应包含领域、内容形式和类型等维度</span><br><span class="line">    // 至少提供 3 个标签，但避免过度重复</span><br><span class="line">  ]</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line">待分析内容：</span><br></pre></td></tr></table></figure><h3 id="笔记链接-附录B-2"><a href="#笔记链接-附录B-2" class="headerlink" title="笔记链接 附录B.2"></a>笔记链接 附录B.2</h3><p>提示$ P_{s2}$</p><p>原文：</p><figure class="highlight text"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br></pre></td><td class="code"><pre><span class="line">You are an AI memory evolution agent responsible for managing and evolving a knowledge base.</span><br><span class="line">Analyze the new memory note according to keywords and context, also with its several nearest neighbor memories.</span><br><span class="line"></span><br><span class="line">The new memory:</span><br><span class="line">- context: &#123;context&#125;</span><br><span class="line">- content: &#123;content&#125;</span><br><span class="line">- keywords: &#123;keywords&#125;</span><br><span class="line"></span><br><span class="line">The nearest neighbor memories:</span><br><span class="line">- &#123;nearest_neighbors_memories&#125;</span><br><span class="line"></span><br><span class="line">Based on this information, determine:</span><br><span class="line">Should this memory be evolved?</span><br><span class="line">Consider its relationships with other memories.</span><br></pre></td></tr></table></figure><p>中文翻译：</p><figure class="highlight text"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br></pre></td><td class="code"><pre><span class="line">你是一个 AI 记忆演化智能体，负责管理并持续演化一个知识库。</span><br><span class="line">请结合关键词、上下文以及若干条最近邻记忆，对新记忆笔记进行分析。</span><br><span class="line"></span><br><span class="line">新记忆信息：</span><br><span class="line">- context: &#123;context&#125;</span><br><span class="line">- content: &#123;content&#125;</span><br><span class="line">- keywords: &#123;keywords&#125;</span><br><span class="line"></span><br><span class="line">最近邻记忆：</span><br><span class="line">- &#123;nearest_neighbors_memories&#125;</span><br><span class="line"></span><br><span class="line">请基于以上信息判断：</span><br><span class="line">这条记忆是否应该被演化（更新）？</span><br><span class="line">请重点考虑它与其他记忆之间的关系。</span><br></pre></td></tr></table></figure><h3 id="记忆演化-附录B-3"><a href="#记忆演化-附录B-3" class="headerlink" title="记忆演化 附录B.3"></a>记忆演化 附录B.3</h3><p>提示$ P_{s3}$</p><p>原文：</p><figure class="highlight text"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br></pre></td><td class="code"><pre><span class="line">You are an AI memory evolution agent responsible for managing and evolving a knowledge base.</span><br><span class="line">Analyze the new memory note according to keywords and context, along with several nearest neighbor memories.</span><br><span class="line">Make decisions about its evolution.</span><br><span class="line"></span><br><span class="line">The new memory:</span><br><span class="line">- context: &#123;context&#125;</span><br><span class="line">- content: &#123;content&#125;</span><br><span class="line">- keywords: &#123;keywords&#125;</span><br><span class="line"></span><br><span class="line">The nearest neighbor memories:</span><br><span class="line">- &#123;nearest_neighbors_memories&#125;</span><br><span class="line"></span><br><span class="line">Based on this information, determine:</span><br><span class="line">1. What specific actions should be taken (strengthen, update_neighbor)?</span><br><span class="line">1.1 If choosing to strengthen, which memory should it connect to? Can you provide updated tags for this memory?</span><br><span class="line">1.2 If choosing to update_neighbor, you may update the context and tags of these memories based on improved understanding.</span><br><span class="line">Tags should reflect the content characteristics of these memories for future retrieval and categorization.</span><br><span class="line"></span><br><span class="line">All information should be returned in list order:</span><br><span class="line">[[new_memory], [neighbor_memory_1], ... [neighbor_memory_n]]</span><br><span class="line"></span><br><span class="line">These actions can be combined.</span><br><span class="line"></span><br><span class="line">Return your decision in JSON format with the following structure:</span><br><span class="line">&#123;</span><br><span class="line">  &quot;should_evolve&quot;: true/false,</span><br><span class="line">  &quot;actions&quot;: [&quot;strengthen&quot;, &quot;merge&quot;, &quot;prune&quot;],</span><br><span class="line">  &quot;suggested_connections&quot;: [&quot;neighbor_memory_ids&quot;],</span><br><span class="line">  &quot;tags_to_update&quot;: [&quot;tag_1&quot;, ..., &quot;tag_n&quot;],</span><br><span class="line">  &quot;new_context_neighborhood&quot;: [&quot;new context&quot;, ..., &quot;new context&quot;],</span><br><span class="line">  &quot;new_tags_neighborhood&quot;: [[&quot;tag_1&quot;, ..., &quot;tag_n&quot;], ..., [&quot;tag_1&quot;, ..., &quot;tag_n&quot;]]</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>中文翻译：</p><figure class="highlight text"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br></pre></td><td class="code"><pre><span class="line">你是一个 AI 记忆演化智能体，负责管理并持续演化一个知识库。</span><br><span class="line">请结合关键词、上下文以及若干条最近邻记忆，对新记忆笔记进行分析，并给出其演化决策。</span><br><span class="line"></span><br><span class="line">新记忆信息：</span><br><span class="line">- context: &#123;context&#125;</span><br><span class="line">- content: &#123;content&#125;</span><br><span class="line">- keywords: &#123;keywords&#125;</span><br><span class="line"></span><br><span class="line">最近邻记忆：</span><br><span class="line">- &#123;nearest_neighbors_memories&#125;</span><br><span class="line"></span><br><span class="line">请基于以上信息判断：</span><br><span class="line">1. 应采取哪些具体动作（strengthen、update_neighbor）？</span><br><span class="line">1.1 如果选择 strengthen，应连接到哪条记忆？并给出该记忆更新后的 tags。</span><br><span class="line">1.2 如果选择 update_neighbor，可基于新的理解更新邻居记忆的 context 和 tags。</span><br><span class="line">这些 tags 应体现记忆内容特征，以便后续检索和分类。</span><br><span class="line"></span><br><span class="line">所有信息按以下列表顺序返回：</span><br><span class="line">[[new_memory], [neighbor_memory_1], ... [neighbor_memory_n]]</span><br><span class="line"></span><br><span class="line">动作可以组合执行。</span><br><span class="line"></span><br><span class="line">请按以下 JSON 结构输出：</span><br><span class="line">&#123;</span><br><span class="line">  &quot;should_evolve&quot;: true/false,</span><br><span class="line">  &quot;actions&quot;: [&quot;strengthen&quot;, &quot;merge&quot;, &quot;prune&quot;],</span><br><span class="line">  &quot;suggested_connections&quot;: [&quot;neighbor_memory_ids&quot;],</span><br><span class="line">  &quot;tags_to_update&quot;: [&quot;tag_1&quot;, ..., &quot;tag_n&quot;],</span><br><span class="line">  &quot;new_context_neighborhood&quot;: [&quot;new context&quot;, ..., &quot;new context&quot;],</span><br><span class="line">  &quot;new_tags_neighborhood&quot;: [[&quot;tag_1&quot;, ..., &quot;tag_n&quot;], ..., [&quot;tag_1&quot;, ..., &quot;tag_n&quot;]]</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h3 id="一个具体使用记忆的例子"><a href="#一个具体使用记忆的例子" class="headerlink" title="一个具体使用记忆的例子"></a>一个具体使用记忆的例子</h3><p>原文：</p><figure class="highlight text"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br></pre></td><td class="code"><pre><span class="line">Example:</span><br><span class="line"></span><br><span class="line">Question 686: Which hobby did Dave pick up in October 2023?</span><br><span class="line">Prediction: photography</span><br><span class="line">Reference: photography</span><br><span class="line"></span><br><span class="line">talk start time: 10:54 am on 17 November, 2023</span><br><span class="line">memory content:</span><br><span class="line">Speaker Dave says: Hey Calvin, long time no talk! A lot has happened. I&#x27;ve taken up photography and it&#x27;s been great, been taking pics of the scenery around here which is really cool.</span><br><span class="line">memory context:</span><br><span class="line">The main topic is the speaker&#x27;s new hobby of photography, highlighting their enjoyment of capturing local scenery, aimed at engaging a friend in conversation about personal experiences.</span><br><span class="line">memory keywords: [&#x27;photography&#x27;, &#x27;scenery&#x27;, &#x27;conversation&#x27;, &#x27;experience&#x27;, &#x27;hobby&#x27;]</span><br><span class="line">memory tags: [&#x27;hobby&#x27;, &#x27;photography&#x27;, &#x27;personal development&#x27;, &#x27;conversation&#x27;, &#x27;leisure&#x27;]</span><br><span class="line"></span><br><span class="line">talk start time: 6:38 pm on 21 July, 2023</span><br><span class="line">memory content:</span><br><span class="line">Speaker Calvin says: Thanks, Dave! It feels great having my own space to work in. I&#x27;ve been experimenting with different genres lately, pushing myself out of my comfort zone. Adding electronic elements to my songs gives them a fresh vibe. It&#x27;s been an exciting process of self-discovery and growth!</span><br><span class="line">memory context:</span><br><span class="line">The speaker discusses their creative process in music, highlighting experimentation with genres and the incorporation of electronic elements for personal growth and artistic evolution.</span><br><span class="line">memory keywords: [&#x27;space&#x27;, &#x27;experimentation&#x27;, &#x27;genres&#x27;, &#x27;electronic&#x27;, &#x27;self-discovery&#x27;, &#x27;growth&#x27;]</span><br><span class="line">memory tags: [&#x27;music&#x27;, &#x27;creativity&#x27;, &#x27;self-improvement&#x27;, &#x27;artistic expression&#x27;]</span><br></pre></td></tr></table></figure><p>中文翻译：</p><figure class="highlight text"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br></pre></td><td class="code"><pre><span class="line">示例：</span><br><span class="line"></span><br><span class="line">问题 686：Dave 在 2023 年 10 月开始了哪项爱好？</span><br><span class="line">预测：photography</span><br><span class="line">参考答案：photography</span><br><span class="line"></span><br><span class="line">对话开始时间：2023 年 11 月 17 日 上午 10:54</span><br><span class="line">记忆内容：</span><br><span class="line">说话人 Dave：Hey Calvin，好久没聊了！最近发生了很多事。我开始玩摄影了，感觉很棒。我一直在拍这附近的风景，真的很酷。</span><br><span class="line">记忆上下文：</span><br><span class="line">这条记忆的主题是说话人开始了摄影这一新爱好，强调了其对拍摄本地风景的兴趣，意图是在与朋友交流中分享个人近况与体验。</span><br><span class="line">记忆关键词：[&#x27;photography&#x27;, &#x27;scenery&#x27;, &#x27;conversation&#x27;, &#x27;experience&#x27;, &#x27;hobby&#x27;]</span><br><span class="line">记忆标签：[&#x27;hobby&#x27;, &#x27;photography&#x27;, &#x27;personal development&#x27;, &#x27;conversation&#x27;, &#x27;leisure&#x27;]</span><br><span class="line"></span><br><span class="line">对话开始时间：2023 年 7 月 21 日 下午 6:38</span><br><span class="line">记忆内容：</span><br><span class="line">说话人 Calvin：谢谢你，Dave！有了自己的创作空间感觉太好了。最近我一直在尝试不同的音乐风格，逼自己走出舒适区。给歌曲加入电子元素让作品更有新鲜感。这是一个令人兴奋的自我探索与成长过程！</span><br><span class="line">记忆上下文：</span><br><span class="line">说话人讨论了其音乐创作过程，重点在于风格实验以及电子元素的引入，并体现了个人成长和艺术演进。</span><br><span class="line">记忆关键词：[&#x27;space&#x27;, &#x27;experimentation&#x27;, &#x27;genres&#x27;, &#x27;electronic&#x27;, &#x27;self-discovery&#x27;, &#x27;growth&#x27;]</span><br><span class="line">记忆标签：[&#x27;music&#x27;, &#x27;creativity&#x27;, &#x27;self-improvement&#x27;, &#x27;artistic expression&#x27;]</span><br></pre></td></tr></table></figure>]]>
    </content>
    <id>https://x4ai.cn/2026/03/15/A-Mem%E8%AE%BA%E6%96%87%E9%98%85%E8%AF%BB/</id>
    <link href="https://x4ai.cn/2026/03/15/A-Mem%E8%AE%BA%E6%96%87%E9%98%85%E8%AF%BB/"/>
    <published>2026-03-14T16:05:39.000Z</published>
    <summary>本文阅读了 A-Mem 这篇论文，介绍了该论文提出的面向LLM智能体的 agentic 记忆系统的设计和实现，以及在六种基础模型上的实证实验结果。</summary>
    <title>A-Mem论文阅读</title>
    <updated>2026-06-06T02:01:51.544Z</updated>
  </entry>
  <entry>
    <author>
      <name>迎风起降</name>
    </author>
    <category term="运维" scheme="https://x4ai.cn/categories/%E8%BF%90%E7%BB%B4/"/>
    <category term="HomeIDC" scheme="https://x4ai.cn/tags/HomeIDC/"/>
    <category term="Home-Assistant" scheme="https://x4ai.cn/tags/Home-Assistant/"/>
    <content>
      <![CDATA[<p>之前在小黄鱼淘入了一个APC的PDU，型号为AP7921，查看文档发现是支持每个插口单独控制开关的，于是想将这PDU接入HA控制。</p><p>这个PDU是支持 SNMP V1 和 V3 协议的，SNMP协议介绍如下：</p><blockquote><p>SNMP（简单网络管理协议）是一个<strong>用于在IP网络中监控和管理设备（如路由器、交换机、服务器和打印机）的标准协议</strong>。它允许网络管理员通过中心化的管理工作站收集设备状态、跟踪网络性能、响应故障，并实现自动化管理。SNMP包含管理器、代理和管理信息库（MIB）等组件，通过查询和轮询机制与代理交换数据，并支持三种版本：SNMPv1、SNMPv2c和更安全的SNMPv3。</p><p>SNMP中，每个可管理的设备信息都有一个唯一标识，称为 <strong>OID（对象标识符）</strong>。OID 就像设备内部信息的“地址”，用于标识 CPU 使用率、内存状态、端口流量等参数。例如，OID <code>1.3.6.1.2.1.1.5.0</code> 就表示设备的系统名称。网络管理系统通过 OID 访问设备的管理信息库（MIB），实现对设备状态的监控、故障告警和自动化管理。OID 的结构呈层次化，以点分十进制表示，每一层代表不同类别的管理对象。﻿</p></blockquote><p>主要区别是V3新增了用户认证的功能，但是我们家用局域网使用其实用V1就足够了。</p><h1 id="开启SNMP-V1"><a href="#开启SNMP-V1" class="headerlink" title="开启SNMP V1"></a>开启SNMP V1</h1><p>首先第一步就是先能够访问到PDU的Web管理页面，参考我之前写的<a class="link"   href="https://blog.fnas64.xin/archives/homeidc-apc-ap7921" >这篇博文<i class="fas fa-external-link-alt"></i></a>。</p><p>进入Web后，在Administration - Network - SNMPv1 - access 里，勾选 <code>Enable SNMPv1 access</code> ，点击<code>Apply</code> 。</p><img                           lazyload                       alt="image"                       data-src="https://img.102465.xyz/file/CO0lwdyY.png"                         alt="image.png" width=100%                  ><p>转到同级的<code>access control</code> 下，保证权限如图所示：</p><img                           lazyload                       alt="image"                       data-src="https://img.102465.xyz/file/iM1RjXRM.png"                         alt="image.png" width=100%                  ><p>即 <code>public</code>的权限为<code>Read</code>，<code>private</code>权限为<code>Write +</code> ；</p><p>**（可选）**然后，点击<code>private</code>，进入设置页面，将<code>NMS IP/Host Name</code>改为运行Home Assistant设备的IP</p><img                           lazyload                       alt="image"                       data-src="https://img.102465.xyz/file/upbuLzOe.png"                         alt="image.png" width=100%                  ><h1 id="HA接入"><a href="#HA接入" class="headerlink" title="HA接入"></a>HA接入</h1><p>Home Assistant 提供了 SNMP 设备的接入组件，文档在：<a class="link"   href="https://www.home-assistant.io/integrations/snmp" >https://www.home-assistant.io/integrations/snmp<i class="fas fa-external-link-alt"></i></a></p><p>这个组件只能通过配置文件<code>configuration.yaml</code> 手动接入，所以得先想办法访问到HA的配置文件。</p><p>一般来说部署HA容器时候有将config文件夹映射到宿主机里，这个每个人映射的位置都不一样了，我的是<code>/opt/homeassistant/config/configuration.yaml</code> 。</p><p>在<code>configuration.yaml</code> 后面追加下面内容：</p><ul><li><p><strong>⚠️</strong> <code>host</code>字段的值<code>192.168.123.50</code> <strong>是PDU的IP！每个人不同，根据实际情况更改</strong></p></li><li><p><strong>⚠️</strong> 在此感谢前辈提供的<code>oid</code> ，前辈经验贴：<a class="link"   href="https://bbs.hassbian.com/thread-4838-1-1.html" >https://bbs.hassbian.com/thread-4838-1-1.html<i class="fas fa-external-link-alt"></i></a></p></li></ul><figure class="highlight yaml"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br><span class="line">60</span><br><span class="line">61</span><br><span class="line">62</span><br><span class="line">63</span><br><span class="line">64</span><br><span class="line">65</span><br><span class="line">66</span><br><span class="line">67</span><br><span class="line">68</span><br><span class="line">69</span><br><span class="line">70</span><br><span class="line">71</span><br><span class="line">72</span><br><span class="line">73</span><br><span class="line">74</span><br><span class="line">75</span><br><span class="line">76</span><br><span class="line">77</span><br><span class="line">78</span><br><span class="line">79</span><br><span class="line">80</span><br><span class="line">81</span><br><span class="line">82</span><br><span class="line">83</span><br><span class="line">84</span><br><span class="line">85</span><br><span class="line">86</span><br><span class="line">87</span><br><span class="line">88</span><br><span class="line">89</span><br><span class="line">90</span><br><span class="line">91</span><br><span class="line">92</span><br><span class="line">93</span><br><span class="line">94</span><br><span class="line">95</span><br><span class="line">96</span><br></pre></td><td class="code"><pre><span class="line"><span class="attr">switch:</span></span><br><span class="line">  <span class="bullet">-</span> <span class="attr">platform:</span> <span class="string">snmp</span></span><br><span class="line">    <span class="attr">name:</span> <span class="string">&quot;APC Outlet 1&quot;</span></span><br><span class="line">    <span class="attr">host:</span> <span class="number">192.168</span><span class="number">.123</span><span class="number">.50</span></span><br><span class="line">    <span class="attr">community:</span> <span class="string">private</span></span><br><span class="line">    <span class="attr">version:</span> <span class="string">&quot;1&quot;</span></span><br><span class="line">    <span class="attr">baseoid:</span> <span class="number">1.3</span><span class="number">.6</span><span class="number">.1</span><span class="number">.4</span><span class="number">.1</span><span class="number">.318</span><span class="number">.1</span><span class="number">.1</span><span class="number">.4</span><span class="number">.4</span><span class="number">.2</span><span class="number">.1</span><span class="number">.3</span><span class="number">.1</span></span><br><span class="line">    <span class="attr">command_oid:</span> <span class="number">1.3</span><span class="number">.6</span><span class="number">.1</span><span class="number">.4</span><span class="number">.1</span><span class="number">.318</span><span class="number">.1</span><span class="number">.1</span><span class="number">.4</span><span class="number">.4</span><span class="number">.2</span><span class="number">.1</span><span class="number">.3</span><span class="number">.1</span></span><br><span class="line">    <span class="attr">payload_on:</span> <span class="number">1</span></span><br><span class="line">    <span class="attr">payload_off:</span> <span class="number">2</span></span><br><span class="line">    <span class="attr">command_payload_on:</span> <span class="number">1</span></span><br><span class="line">    <span class="attr">command_payload_off:</span> <span class="number">2</span></span><br><span class="line">  </span><br><span class="line">  <span class="bullet">-</span> <span class="attr">platform:</span> <span class="string">snmp</span></span><br><span class="line">    <span class="attr">name:</span> <span class="string">&quot;APC Outlet 2&quot;</span></span><br><span class="line">    <span class="attr">host:</span> <span class="number">192.168</span><span class="number">.123</span><span class="number">.50</span></span><br><span class="line">    <span class="attr">community:</span> <span class="string">private</span></span><br><span class="line">    <span class="attr">version:</span> <span class="string">&quot;1&quot;</span></span><br><span class="line">    <span class="attr">baseoid:</span> <span class="number">1.3</span><span class="number">.6</span><span class="number">.1</span><span class="number">.4</span><span class="number">.1</span><span class="number">.318</span><span class="number">.1</span><span class="number">.1</span><span class="number">.4</span><span class="number">.4</span><span class="number">.2</span><span class="number">.1</span><span class="number">.3</span><span class="number">.2</span></span><br><span class="line">    <span class="attr">command_oid:</span> <span class="number">1.3</span><span class="number">.6</span><span class="number">.1</span><span class="number">.4</span><span class="number">.1</span><span class="number">.318</span><span class="number">.1</span><span class="number">.1</span><span class="number">.4</span><span class="number">.4</span><span class="number">.2</span><span class="number">.1</span><span class="number">.3</span><span class="number">.2</span></span><br><span class="line">    <span class="attr">payload_on:</span> <span class="number">1</span></span><br><span class="line">    <span class="attr">payload_off:</span> <span class="number">2</span></span><br><span class="line">    <span class="attr">command_payload_on:</span> <span class="number">1</span></span><br><span class="line">    <span class="attr">command_payload_off:</span> <span class="number">2</span></span><br><span class="line">  </span><br><span class="line">  <span class="bullet">-</span> <span class="attr">platform:</span> <span class="string">snmp</span></span><br><span class="line">    <span class="attr">name:</span> <span class="string">&quot;APC Outlet 3&quot;</span></span><br><span class="line">    <span class="attr">host:</span> <span class="number">192.168</span><span class="number">.123</span><span class="number">.50</span></span><br><span class="line">    <span class="attr">community:</span> <span class="string">private</span></span><br><span class="line">    <span class="attr">version:</span> <span class="string">&quot;1&quot;</span></span><br><span class="line">    <span class="attr">baseoid:</span> <span class="number">1.3</span><span class="number">.6</span><span class="number">.1</span><span class="number">.4</span><span class="number">.1</span><span class="number">.318</span><span class="number">.1</span><span class="number">.1</span><span class="number">.4</span><span class="number">.4</span><span class="number">.2</span><span class="number">.1</span><span class="number">.3</span><span class="number">.3</span></span><br><span class="line">    <span class="attr">command_oid:</span> <span class="number">1.3</span><span class="number">.6</span><span class="number">.1</span><span class="number">.4</span><span class="number">.1</span><span class="number">.318</span><span class="number">.1</span><span class="number">.1</span><span class="number">.4</span><span class="number">.4</span><span class="number">.2</span><span class="number">.1</span><span class="number">.3</span><span class="number">.3</span></span><br><span class="line">    <span class="attr">payload_on:</span> <span class="number">1</span></span><br><span class="line">    <span class="attr">payload_off:</span> <span class="number">2</span></span><br><span class="line">    <span class="attr">command_payload_on:</span> <span class="number">1</span></span><br><span class="line">    <span class="attr">command_payload_off:</span> <span class="number">2</span></span><br><span class="line"></span><br><span class="line">  <span class="bullet">-</span> <span class="attr">platform:</span> <span class="string">snmp</span></span><br><span class="line">    <span class="attr">name:</span> <span class="string">&quot;APC Outlet 4&quot;</span></span><br><span class="line">    <span class="attr">host:</span> <span class="number">192.168</span><span class="number">.123</span><span class="number">.50</span></span><br><span class="line">    <span class="attr">community:</span> <span class="string">private</span></span><br><span class="line">    <span class="attr">version:</span> <span class="string">&quot;1&quot;</span></span><br><span class="line">    <span class="attr">baseoid:</span> <span class="number">1.3</span><span class="number">.6</span><span class="number">.1</span><span class="number">.4</span><span class="number">.1</span><span class="number">.318</span><span class="number">.1</span><span class="number">.1</span><span class="number">.4</span><span class="number">.4</span><span class="number">.2</span><span class="number">.1</span><span class="number">.3</span><span class="number">.4</span></span><br><span class="line">    <span class="attr">command_oid:</span> <span class="number">1.3</span><span class="number">.6</span><span class="number">.1</span><span class="number">.4</span><span class="number">.1</span><span class="number">.318</span><span class="number">.1</span><span class="number">.1</span><span class="number">.4</span><span class="number">.4</span><span class="number">.2</span><span class="number">.1</span><span class="number">.3</span><span class="number">.4</span></span><br><span class="line">    <span class="attr">payload_on:</span> <span class="number">1</span></span><br><span class="line">    <span class="attr">payload_off:</span> <span class="number">2</span></span><br><span class="line">    <span class="attr">command_payload_on:</span> <span class="number">1</span></span><br><span class="line">    <span class="attr">command_payload_off:</span> <span class="number">2</span></span><br><span class="line">  </span><br><span class="line">  <span class="bullet">-</span> <span class="attr">platform:</span> <span class="string">snmp</span></span><br><span class="line">    <span class="attr">name:</span> <span class="string">&quot;APC Outlet 5&quot;</span></span><br><span class="line">    <span class="attr">host:</span> <span class="number">192.168</span><span class="number">.123</span><span class="number">.50</span></span><br><span class="line">    <span class="attr">community:</span> <span class="string">private</span></span><br><span class="line">    <span class="attr">version:</span> <span class="string">&quot;1&quot;</span></span><br><span class="line">    <span class="attr">baseoid:</span> <span class="number">1.3</span><span class="number">.6</span><span class="number">.1</span><span class="number">.4</span><span class="number">.1</span><span class="number">.318</span><span class="number">.1</span><span class="number">.1</span><span class="number">.4</span><span class="number">.4</span><span class="number">.2</span><span class="number">.1</span><span class="number">.3</span><span class="number">.5</span></span><br><span class="line">    <span class="attr">command_oid:</span> <span class="number">1.3</span><span class="number">.6</span><span class="number">.1</span><span class="number">.4</span><span class="number">.1</span><span class="number">.318</span><span class="number">.1</span><span class="number">.1</span><span class="number">.4</span><span class="number">.4</span><span class="number">.2</span><span class="number">.1</span><span class="number">.3</span><span class="number">.5</span></span><br><span class="line">    <span class="attr">payload_on:</span> <span class="number">1</span></span><br><span class="line">    <span class="attr">payload_off:</span> <span class="number">2</span></span><br><span class="line">    <span class="attr">command_payload_on:</span> <span class="number">1</span></span><br><span class="line">    <span class="attr">command_payload_off:</span> <span class="number">2</span></span><br><span class="line">  </span><br><span class="line">  <span class="bullet">-</span> <span class="attr">platform:</span> <span class="string">snmp</span></span><br><span class="line">    <span class="attr">name:</span> <span class="string">&quot;APC Outlet 6&quot;</span></span><br><span class="line">    <span class="attr">host:</span> <span class="number">192.168</span><span class="number">.123</span><span class="number">.50</span></span><br><span class="line">    <span class="attr">community:</span> <span class="string">private</span></span><br><span class="line">    <span class="attr">version:</span> <span class="string">&quot;1&quot;</span></span><br><span class="line">    <span class="attr">baseoid:</span> <span class="number">1.3</span><span class="number">.6</span><span class="number">.1</span><span class="number">.4</span><span class="number">.1</span><span class="number">.318</span><span class="number">.1</span><span class="number">.1</span><span class="number">.4</span><span class="number">.4</span><span class="number">.2</span><span class="number">.1</span><span class="number">.3</span><span class="number">.6</span></span><br><span class="line">    <span class="attr">command_oid:</span> <span class="number">1.3</span><span class="number">.6</span><span class="number">.1</span><span class="number">.4</span><span class="number">.1</span><span class="number">.318</span><span class="number">.1</span><span class="number">.1</span><span class="number">.4</span><span class="number">.4</span><span class="number">.2</span><span class="number">.1</span><span class="number">.3</span><span class="number">.6</span></span><br><span class="line">    <span class="attr">payload_on:</span> <span class="number">1</span></span><br><span class="line">    <span class="attr">payload_off:</span> <span class="number">2</span></span><br><span class="line">    <span class="attr">command_payload_on:</span> <span class="number">1</span></span><br><span class="line">    <span class="attr">command_payload_off:</span> <span class="number">2</span></span><br><span class="line">  </span><br><span class="line">  <span class="bullet">-</span> <span class="attr">platform:</span> <span class="string">snmp</span></span><br><span class="line">    <span class="attr">name:</span> <span class="string">&quot;APC Outlet 7&quot;</span></span><br><span class="line">    <span class="attr">host:</span> <span class="number">192.168</span><span class="number">.123</span><span class="number">.50</span></span><br><span class="line">    <span class="attr">community:</span> <span class="string">private</span></span><br><span class="line">    <span class="attr">version:</span> <span class="string">&quot;1&quot;</span></span><br><span class="line">    <span class="attr">baseoid:</span> <span class="number">1.3</span><span class="number">.6</span><span class="number">.1</span><span class="number">.4</span><span class="number">.1</span><span class="number">.318</span><span class="number">.1</span><span class="number">.1</span><span class="number">.4</span><span class="number">.4</span><span class="number">.2</span><span class="number">.1</span><span class="number">.3</span><span class="number">.7</span></span><br><span class="line">    <span class="attr">command_oid:</span> <span class="number">1.3</span><span class="number">.6</span><span class="number">.1</span><span class="number">.4</span><span class="number">.1</span><span class="number">.318</span><span class="number">.1</span><span class="number">.1</span><span class="number">.4</span><span class="number">.4</span><span class="number">.2</span><span class="number">.1</span><span class="number">.3</span><span class="number">.7</span></span><br><span class="line">    <span class="attr">payload_on:</span> <span class="number">1</span></span><br><span class="line">    <span class="attr">payload_off:</span> <span class="number">2</span></span><br><span class="line">    <span class="attr">command_payload_on:</span> <span class="number">1</span></span><br><span class="line">    <span class="attr">command_payload_off:</span> <span class="number">2</span></span><br><span class="line">  </span><br><span class="line">  <span class="bullet">-</span> <span class="attr">platform:</span> <span class="string">snmp</span></span><br><span class="line">    <span class="attr">name:</span> <span class="string">&quot;APC Outlet 8&quot;</span></span><br><span class="line">    <span class="attr">host:</span> <span class="number">192.168</span><span class="number">.123</span><span class="number">.50</span></span><br><span class="line">    <span class="attr">community:</span> <span class="string">private</span></span><br><span class="line">    <span class="attr">version:</span> <span class="string">&quot;1&quot;</span></span><br><span class="line">    <span class="attr">baseoid:</span> <span class="number">1.3</span><span class="number">.6</span><span class="number">.1</span><span class="number">.4</span><span class="number">.1</span><span class="number">.318</span><span class="number">.1</span><span class="number">.1</span><span class="number">.4</span><span class="number">.4</span><span class="number">.2</span><span class="number">.1</span><span class="number">.3</span><span class="number">.8</span></span><br><span class="line">    <span class="attr">command_oid:</span> <span class="number">1.3</span><span class="number">.6</span><span class="number">.1</span><span class="number">.4</span><span class="number">.1</span><span class="number">.318</span><span class="number">.1</span><span class="number">.1</span><span class="number">.4</span><span class="number">.4</span><span class="number">.2</span><span class="number">.1</span><span class="number">.3</span><span class="number">.8</span></span><br><span class="line">    <span class="attr">payload_on:</span> <span class="number">1</span></span><br><span class="line">    <span class="attr">payload_off:</span> <span class="number">2</span></span><br><span class="line">    <span class="attr">command_payload_on:</span> <span class="number">1</span></span><br><span class="line">    <span class="attr">command_payload_off:</span> <span class="number">2</span></span><br></pre></td></tr></table></figure><h1 id="验证重启"><a href="#验证重启" class="headerlink" title="验证重启"></a>验证重启</h1><p>进入HA的Web页面，侧边栏 - 开发者工具 - 配置检查与重启 - 检查配置，检查配置无误后，点击 重新启动。</p><p>重启后来到，设置 - 设备与服务 - SNMP，应该就可以看到8个开关了。</p><img                           lazyload                       alt="image"                       data-src="https://img.102465.xyz/file/51ube6M8.png"                         alt="image.png" width=100%                  ><h1 id="结算画面"><a href="#结算画面" class="headerlink" title="结算画面"></a>结算画面</h1><img                           lazyload                       alt="image"                       data-src="https://img.102465.xyz/file/eWCvoa2J.png"                         alt="image.png" width=100%                  >]]>
    </content>
    <id>https://x4ai.cn/2025/09/08/APC-AP7921%E6%8E%A5%E5%85%A5HomeAssistant/</id>
    <link href="https://x4ai.cn/2025/09/08/APC-AP7921%E6%8E%A5%E5%85%A5HomeAssistant/"/>
    <published>2025-09-08T09:06:29.000Z</published>
    <summary>本文记录如何通过 SNMP 将 APC AP7921 的每个插口接入 Home Assistant，实现远程控制和状态监测的详细步骤和配置方法。</summary>
    <title>APC AP7921 接入Home Assistant（SNMP方式）</title>
    <updated>2026-06-06T02:01:51.544Z</updated>
  </entry>
  <entry>
    <author>
      <name>迎风起降</name>
    </author>
    <category term="🦐折腾" scheme="https://x4ai.cn/categories/%F0%9F%A6%90%E6%8A%98%E8%85%BE/"/>
    <category term="HomeIDC" scheme="https://x4ai.cn/tags/HomeIDC/"/>
    <category term="Home-Assistant" scheme="https://x4ai.cn/tags/Home-Assistant/"/>
    <content>
      <![CDATA[<h1 id="HA容器安装IPMITool"><a href="#HA容器安装IPMITool" class="headerlink" title="HA容器安装IPMITool"></a>HA容器安装IPMITool</h1><p>我的 HA 是 Docker 容器安装的，先找到 HA 容器的 ID，SSH 连接到宿主机，输入 <code>docker ps -a | grep homeassistant</code> ：</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line">&lt;yourusername&gt;@Docker:~# docker ps -a | grep homeassistant</span><br><span class="line"></span><br><span class="line">9f0a5e8d7de3 ghcr.nju.edu.cn/hasscc/hacn:stable &quot;/init&quot; 3 days ago Up 28 hours (healthy) homeassistant</span><br></pre></td></tr></table></figure><p>可以看到容器的 ID 为<code>9f0a5e8d7de3</code>，然后 <code>docker exec -it 9f0a5e8d7de3 /bin/bash</code> 进入容器的命令行。</p><p>然后根据 HA 基础镜像不同安装 IPMITool，<code>Alpine</code> 使用 <code>apk add ipmitool</code>，<code>Debian/Ubuntu</code> 使用 <code>apt install -y ipmitool</code>。</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br></pre></td><td class="code"><pre><span class="line">Docker:/config# apk add ipmitool</span><br><span class="line"></span><br><span class="line">fetch https://dl-cdn.alpinelinux.org/alpine/v3.21/main/x86_64/APKINDEX.tar.gz</span><br><span class="line"></span><br><span class="line">fetch https://dl-cdn.alpinelinux.org/alpine/v3.21/community/x86_64/APKINDEX.tar.gz</span><br><span class="line"></span><br><span class="line">(1/1) Installing ipmitool (1.8.19-r1)</span><br><span class="line"></span><br><span class="line">Executing busybox-1.37.0-r12.trigger</span><br><span class="line"></span><br><span class="line">OK: 184 MiB in 202 packages</span><br></pre></td></tr></table></figure><p>安装好 IPMITool 后，测试一下ipmi的命令 <code>ipmitool -I lanplus -H &lt;yourIP&gt; -U &lt;yourusername&gt; -P &lt;yourpassword&gt; dcmi power reading</code> （注意<code>&lt;&gt;</code>内的需要按你的实际情况填写），在 R730XD 上输出为：</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line">Instantaneous power reading:                    12 Watts</span><br><span class="line">Minimum during sampling period:                  2 Watts</span><br><span class="line">Maximum during sampling period:                296 Watts</span><br><span class="line">Average power reading over sample period:      141 Watts</span><br><span class="line">IPMI timestamp:                           09/04/25 15:10:37 CST    Sampling period:                          00000001 Seconds.</span><br><span class="line">Power reading state is:                   activated</span><br></pre></td></tr></table></figure><h1 id="编写IPMI命令"><a href="#编写IPMI命令" class="headerlink" title="编写IPMI命令"></a>编写IPMI命令</h1><p>需要将什么数据接入HA，就需要自己手动写出查询这些数据的命令，下面给出我的供参考：</p><ul><li>CPU温度（两个CPU中的最大值）</li></ul><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">ipmitool -I lanplus -H &lt;yourIP&gt; -U &lt;yourusername&gt; -P &lt;yourpassword&gt; sdr elist full | awk -F&#x27;|&#x27; &#x27;/Temp *\| 0E|Temp *\| 0F/ &#123;gsub(/degrees C/,&quot;&quot;,$5); gsub(/ /,&quot;&quot;,$5); if($5~/^[0-9]+$/ &amp;&amp; $5&gt;max) max=$5&#125; END &#123;if(max&gt;0) print max; else print 0&#125;&#x27;</span><br></pre></td></tr></table></figure><ul><li>查询风扇转速（所有风扇转速平均值，单位RPM）</li></ul><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">ipmitool -I lanplus -H &lt;yourIP&gt; -U &lt;yourusername&gt; -P &lt;yourpassword&gt; sdr elist full | grep &quot;Fan&quot; | awk -F&#x27;|&#x27; &#x27;&#123;gsub(/RPM/, &quot;&quot;, $5); gsub(/ /, &quot;&quot;, $5); if($5 ~ /^[0-9]+$/) &#123;sum+=$5; count++&#125;&#125; END &#123;if(count&gt;0) print sum/count; else print 0&#125;&#x27;</span><br></pre></td></tr></table></figure><ul><li>进气温度</li></ul><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">ipmitool -I lanplus -H &lt;yourIP&gt; -U &lt;yourusername&gt; -P &lt;yourpassword&gt; sdr elist full | grep &quot;Inlet Temp&quot; | awk -F&#x27;|&#x27; &#x27;&#123;print $5&#125;&#x27; | grep -o &#x27;[0-9]\+&#x27;</span><br></pre></td></tr></table></figure><ul><li>排气温度</li></ul><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">ipmitool -I lanplus -H &lt;yourIP&gt; -U &lt;yourusername&gt; -P &lt;yourpassword&gt; sdr elist full | grep &quot;Exhaust Temp&quot; | awk -F&#x27;|&#x27; &#x27;&#123;print $5&#125;&#x27; | grep -o &#x27;[0-9]\+&#x27;</span><br></pre></td></tr></table></figure><ul><li>功率</li></ul><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">ipmitool -I lanplus -H &lt;yourIP&gt; -U &lt;yourusername&gt; -P &lt;yourpassword&gt; sdr elist full | awk -F&#x27;|&#x27; &#x27;/Pwr Consumption/ &#123;gsub(/Watts/,&quot;&quot;,$5); gsub(/ /,&quot;&quot;,$5); print $5&#125;&#x27;</span><br></pre></td></tr></table></figure><ul><li>设置风扇自动调速（关）</li></ul><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">ipmitool -I lanplus -H &lt;yourIP&gt; -U &lt;yourusername&gt; -P &lt;yourpassword&gt; raw 0x30 0x30 0x01 0x00</span><br></pre></td></tr></table></figure><ul><li>设置风扇自动调速（开）</li></ul><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">ipmitool -I lanplus -H &lt;yourIP&gt; -U &lt;yourusername&gt; -P &lt;yourpassword&gt; raw 0x30 0x30 0x01 0x01</span><br></pre></td></tr></table></figure><ul><li>电源相关</li></ul><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br></pre></td><td class="code"><pre><span class="line"># 查询电源状态</span><br><span class="line">ipmitool -I lanplus -H &lt;yourIP&gt; -U &lt;yourusername&gt; -P password chassis power status</span><br><span class="line"></span><br><span class="line"># 开机</span><br><span class="line">ipmitool -I lanplus -H &lt;yourIP&gt; -U &lt;yourusername&gt; -P password chassis power on</span><br><span class="line"></span><br><span class="line"># 软关机</span><br><span class="line">ipmitool -I lanplus -H &lt;yourIP&gt; -U &lt;yourusername&gt; -P password chassis power soft</span><br><span class="line"></span><br><span class="line"># 重启</span><br><span class="line">ipmitool -I lanplus -H &lt;yourIP&gt; -U &lt;yourusername&gt; -P password chassis power cycle</span><br><span class="line"></span><br><span class="line"># 重置</span><br><span class="line">ipmitool -I lanplus -H &lt;yourIP&gt; -U &lt;yourusername&gt; -P password chassis power reset</span><br></pre></td></tr></table></figure><ul><li><p>设置风扇转速（%）</p></li><li><p>10%</p></li></ul><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">ipmitool -I lanplus -H &lt;yourIP&gt; -U &lt;yourusername&gt; -P &lt;yourpassword&gt; raw 0x30 0x30 0x02 0xff 0xa</span><br></pre></td></tr></table></figure><ul><li>20%</li></ul><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">ipmitool -I lanplus -H &lt;yourIP&gt; -U &lt;yourusername&gt; -P &lt;yourpassword&gt; raw 0x30 0x30 0x02 0xff 0x14</span><br></pre></td></tr></table></figure><ul><li>100%</li></ul><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">ipmitool -I lanplus -H &lt;yourIP&gt; -U &lt;yourusername&gt; -P &lt;yourpassword&gt; raw 0x30 0x30 0x02 0xff 0x64</span><br></pre></td></tr></table></figure><h1 id="接入HA"><a href="#接入HA" class="headerlink" title="接入HA"></a>接入HA</h1><ol><li>我这里使用<code>command_line</code>接入HA，打开HA的<code>configuration.yaml</code> ，加入下面的内容：</li></ol><ul><li><p>记得替换 <code>&lt;yourIP&gt;</code>、<code>&lt;yourusername&gt;</code>、<code>&lt;yourpassword&gt;</code> 为你自己的值。</p></li><li><p>我知道写<code>set_fan_00</code>、<code>set_fan_05</code>…这种很蠢，但是我用模板语法实在调试不通，请大佬们有更加好的方案指教。</p></li></ul><figure class="highlight yaml"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br><span class="line">60</span><br><span class="line">61</span><br><span class="line">62</span><br><span class="line">63</span><br><span class="line">64</span><br><span class="line">65</span><br><span class="line">66</span><br><span class="line">67</span><br><span class="line">68</span><br><span class="line">69</span><br><span class="line">70</span><br><span class="line">71</span><br><span class="line">72</span><br><span class="line">73</span><br><span class="line">74</span><br><span class="line">75</span><br><span class="line">76</span><br><span class="line">77</span><br><span class="line">78</span><br><span class="line">79</span><br><span class="line">80</span><br><span class="line">81</span><br><span class="line">82</span><br><span class="line">83</span><br><span class="line">84</span><br><span class="line">85</span><br><span class="line">86</span><br><span class="line">87</span><br><span class="line">88</span><br><span class="line">89</span><br><span class="line">90</span><br><span class="line">91</span><br><span class="line">92</span><br><span class="line">93</span><br><span class="line">94</span><br><span class="line">95</span><br><span class="line">96</span><br><span class="line">97</span><br><span class="line">98</span><br><span class="line">99</span><br><span class="line">100</span><br><span class="line">101</span><br><span class="line">102</span><br><span class="line">103</span><br><span class="line">104</span><br><span class="line">105</span><br><span class="line">106</span><br><span class="line">107</span><br><span class="line">108</span><br><span class="line">109</span><br><span class="line">110</span><br><span class="line">111</span><br><span class="line">112</span><br><span class="line">113</span><br><span class="line">114</span><br><span class="line">115</span><br><span class="line">116</span><br><span class="line">117</span><br><span class="line">118</span><br><span class="line">119</span><br><span class="line">120</span><br><span class="line">121</span><br><span class="line">122</span><br><span class="line">123</span><br><span class="line">124</span><br><span class="line">125</span><br><span class="line">126</span><br><span class="line">127</span><br><span class="line">128</span><br><span class="line">129</span><br><span class="line">130</span><br><span class="line">131</span><br><span class="line">132</span><br><span class="line">133</span><br><span class="line">134</span><br><span class="line">135</span><br><span class="line">136</span><br><span class="line">137</span><br><span class="line">138</span><br><span class="line">139</span><br><span class="line">140</span><br><span class="line">141</span><br><span class="line">142</span><br><span class="line">143</span><br><span class="line">144</span><br><span class="line">145</span><br><span class="line">146</span><br><span class="line">147</span><br><span class="line">148</span><br><span class="line">149</span><br><span class="line">150</span><br><span class="line">151</span><br><span class="line">152</span><br><span class="line">153</span><br><span class="line">154</span><br><span class="line">155</span><br><span class="line">156</span><br><span class="line">157</span><br><span class="line">158</span><br><span class="line">159</span><br><span class="line">160</span><br><span class="line">161</span><br><span class="line">162</span><br><span class="line">163</span><br><span class="line">164</span><br><span class="line">165</span><br><span class="line">166</span><br><span class="line">167</span><br><span class="line">168</span><br><span class="line">169</span><br><span class="line">170</span><br><span class="line">171</span><br><span class="line">172</span><br><span class="line">173</span><br><span class="line">174</span><br><span class="line">175</span><br><span class="line">176</span><br><span class="line">177</span><br><span class="line">178</span><br><span class="line">179</span><br><span class="line">180</span><br><span class="line">181</span><br><span class="line">182</span><br><span class="line">183</span><br><span class="line">184</span><br><span class="line">185</span><br><span class="line">186</span><br><span class="line">187</span><br><span class="line">188</span><br><span class="line">189</span><br><span class="line">190</span><br><span class="line">191</span><br><span class="line">192</span><br><span class="line">193</span><br><span class="line">194</span><br><span class="line">195</span><br><span class="line">196</span><br><span class="line">197</span><br><span class="line">198</span><br><span class="line">199</span><br><span class="line">200</span><br><span class="line">201</span><br><span class="line">202</span><br><span class="line">203</span><br><span class="line">204</span><br><span class="line">205</span><br><span class="line">206</span><br><span class="line">207</span><br><span class="line">208</span><br><span class="line">209</span><br><span class="line">210</span><br><span class="line">211</span><br><span class="line">212</span><br><span class="line">213</span><br><span class="line">214</span><br><span class="line">215</span><br><span class="line">216</span><br><span class="line">217</span><br><span class="line">218</span><br><span class="line">219</span><br><span class="line">220</span><br><span class="line">221</span><br><span class="line">222</span><br><span class="line">223</span><br><span class="line">224</span><br><span class="line">225</span><br><span class="line">226</span><br><span class="line">227</span><br><span class="line">228</span><br><span class="line">229</span><br><span class="line">230</span><br><span class="line">231</span><br><span class="line">232</span><br><span class="line">233</span><br><span class="line">234</span><br><span class="line">235</span><br><span class="line">236</span><br><span class="line">237</span><br><span class="line">238</span><br><span class="line">239</span><br><span class="line">240</span><br><span class="line">241</span><br><span class="line">242</span><br><span class="line">243</span><br><span class="line">244</span><br><span class="line">245</span><br><span class="line">246</span><br><span class="line">247</span><br><span class="line">248</span><br><span class="line">249</span><br><span class="line">250</span><br><span class="line">251</span><br><span class="line">252</span><br><span class="line">253</span><br><span class="line">254</span><br><span class="line">255</span><br><span class="line">256</span><br><span class="line">257</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment"># R730XD 远程管理命令配置</span></span><br><span class="line"><span class="attr">shell_command:</span></span><br><span class="line">  <span class="attr">r730xd_power_cycle:</span> <span class="string">&quot;ipmitool -I lanplus -H &lt;yourIP&gt; -U &lt;yourusername&gt; -P &lt;yourpassword&gt; chassis power cycle&quot;</span></span><br><span class="line">  <span class="attr">r730xd_power_reset:</span> <span class="string">&quot;ipmitool -I lanplus -H &lt;yourIP&gt; -U &lt;yourusername&gt; -P &lt;yourpassword&gt; chassis power reset&quot;</span></span><br><span class="line"></span><br><span class="line">  <span class="comment"># 风扇模式命令</span></span><br><span class="line">  <span class="attr">set_fan_auto:</span> <span class="string">&#x27;ipmitool -I lanplus -H &lt;yourIP&gt; -U &lt;yourusername&gt; -P &lt;yourpassword&gt; raw 0x30 0x30 0x01 0x01&#x27;</span></span><br><span class="line">  <span class="attr">set_fan_manual:</span> <span class="string">&#x27;ipmitool -I lanplus -H &lt;yourIP&gt; -U &lt;yourusername&gt; -P &lt;yourpassword&gt; raw 0x30 0x30 0x01 0x00&#x27;</span></span><br><span class="line">  </span><br><span class="line">  <span class="comment"># 预定义的风扇速度命令</span></span><br><span class="line">  <span class="attr">set_fan_00:</span> <span class="string">&#x27;ipmitool -I lanplus -H &lt;yourIP&gt; -U &lt;yourusername&gt; -P &lt;yourpassword&gt; raw 0x30 0x30 0x02 0xff 0x00&#x27;</span></span><br><span class="line">  <span class="attr">set_fan_05:</span> <span class="string">&#x27;ipmitool -I lanplus -H &lt;yourIP&gt; -U &lt;yourusername&gt; -P &lt;yourpassword&gt; raw 0x30 0x30 0x02 0xff 0x05&#x27;</span></span><br><span class="line">  <span class="attr">set_fan_10:</span> <span class="string">&#x27;ipmitool -I lanplus -H &lt;yourIP&gt; -U &lt;yourusername&gt; -P &lt;yourpassword&gt; raw 0x30 0x30 0x02 0xff 0x0a&#x27;</span></span><br><span class="line">  <span class="attr">set_fan_15:</span> <span class="string">&#x27;ipmitool -I lanplus -H &lt;yourIP&gt; -U &lt;yourusername&gt; -P &lt;yourpassword&gt; raw 0x30 0x30 0x02 0xff 0x0f&#x27;</span></span><br><span class="line">  <span class="attr">set_fan_20:</span> <span class="string">&#x27;ipmitool -I lanplus -H &lt;yourIP&gt; -U &lt;yourusername&gt; -P &lt;yourpassword&gt; raw 0x30 0x30 0x02 0xff 0x14&#x27;</span></span><br><span class="line">  <span class="attr">set_fan_25:</span> <span class="string">&#x27;ipmitool -I lanplus -H &lt;yourIP&gt; -U &lt;yourusername&gt; -P &lt;yourpassword&gt; raw 0x30 0x30 0x02 0xff 0x19&#x27;</span></span><br><span class="line">  <span class="attr">set_fan_30:</span> <span class="string">&#x27;ipmitool -I lanplus -H &lt;yourIP&gt; -U &lt;yourusername&gt; -P &lt;yourpassword&gt; raw 0x30 0x30 0x02 0xff 0x1e&#x27;</span></span><br><span class="line">  <span class="attr">set_fan_35:</span> <span class="string">&#x27;ipmitool -I lanplus -H &lt;yourIP&gt; -U &lt;yourusername&gt; -P &lt;yourpassword&gt; raw 0x30 0x30 0x02 0xff 0x23&#x27;</span></span><br><span class="line">  <span class="attr">set_fan_40:</span> <span class="string">&#x27;ipmitool -I lanplus -H &lt;yourIP&gt; -U &lt;yourusername&gt; -P &lt;yourpassword&gt; raw 0x30 0x30 0x02 0xff 0x28&#x27;</span></span><br><span class="line">  <span class="attr">set_fan_45:</span> <span class="string">&#x27;ipmitool -I lanplus -H &lt;yourIP&gt; -U &lt;yourusername&gt; -P &lt;yourpassword&gt; raw 0x30 0x30 0x02 0xff 0x2d&#x27;</span></span><br><span class="line">  <span class="attr">set_fan_50:</span> <span class="string">&#x27;ipmitool -I lanplus -H &lt;yourIP&gt; -U &lt;yourusername&gt; -P &lt;yourpassword&gt; raw 0x30 0x30 0x02 0xff 0x32&#x27;</span></span><br><span class="line">  <span class="attr">set_fan_55:</span> <span class="string">&#x27;ipmitool -I lanplus -H &lt;yourIP&gt; -U &lt;yourusername&gt; -P &lt;yourpassword&gt; raw 0x30 0x30 0x02 0xff 0x37&#x27;</span></span><br><span class="line">  <span class="attr">set_fan_60:</span> <span class="string">&#x27;ipmitool -I lanplus -H &lt;yourIP&gt; -U &lt;yourusername&gt; -P &lt;yourpassword&gt; raw 0x30 0x30 0x02 0xff 0x3c&#x27;</span></span><br><span class="line">  <span class="attr">set_fan_65:</span> <span class="string">&#x27;ipmitool -I lanplus -H &lt;yourIP&gt; -U &lt;yourusername&gt; -P &lt;yourpassword&gt; raw 0x30 0x30 0x02 0xff 0x41&#x27;</span></span><br><span class="line">  <span class="attr">set_fan_70:</span> <span class="string">&#x27;ipmitool -I lanplus -H &lt;yourIP&gt; -U &lt;yourusername&gt; -P &lt;yourpassword&gt; raw 0x30 0x30 0x02 0xff 0x46&#x27;</span></span><br><span class="line">  <span class="attr">set_fan_75:</span> <span class="string">&#x27;ipmitool -I lanplus -H &lt;yourIP&gt; -U &lt;yourusername&gt; -P &lt;yourpassword&gt; raw 0x30 0x30 0x02 0xff 0x4b&#x27;</span></span><br><span class="line">  <span class="attr">set_fan_80:</span> <span class="string">&#x27;ipmitool -I lanplus -H &lt;yourIP&gt; -U &lt;yourusername&gt; -P &lt;yourpassword&gt; raw 0x30 0x30 0x02 0xff 0x50&#x27;</span></span><br><span class="line">  <span class="attr">set_fan_85:</span> <span class="string">&#x27;ipmitool -I lanplus -H &lt;yourIP&gt; -U &lt;yourusername&gt; -P &lt;yourpassword&gt; raw 0x30 0x30 0x02 0xff 0x55&#x27;</span></span><br><span class="line">  <span class="attr">set_fan_90:</span> <span class="string">&#x27;ipmitool -I lanplus -H &lt;yourIP&gt; -U &lt;yourusername&gt; -P &lt;yourpassword&gt; raw 0x30 0x30 0x02 0xff 0x5a&#x27;</span></span><br><span class="line">  <span class="attr">set_fan_95:</span> <span class="string">&#x27;ipmitool -I lanplus -H &lt;yourIP&gt; -U &lt;yourusername&gt; -P &lt;yourpassword&gt; raw 0x30 0x30 0x02 0xff 0x5f&#x27;</span></span><br><span class="line">  <span class="attr">set_fan_100:</span> <span class="string">&#x27;ipmitool -I lanplus -H &lt;yourIP&gt; -U &lt;yourusername&gt; -P &lt;yourpassword&gt; raw 0x30 0x30 0x02 0xff 0x64&#x27;</span></span><br><span class="line"></span><br><span class="line"><span class="comment"># R730XD 传感器和开关配置</span></span><br><span class="line"><span class="attr">command_line:</span></span><br><span class="line">  <span class="bullet">-</span> <span class="attr">sensor:</span></span><br><span class="line">      <span class="attr">name:</span> <span class="string">R730XD</span> <span class="string">CPU</span> <span class="string">Temp</span></span><br><span class="line">      <span class="attr">command:</span> <span class="string">&quot;/config/scripts/ipmi_sdr_cache.sh cpu_temp&quot;</span></span><br><span class="line">      <span class="attr">unit_of_measurement:</span> <span class="string">&quot;°C&quot;</span></span><br><span class="line">      <span class="attr">command_timeout:</span> <span class="number">60</span></span><br><span class="line">      <span class="attr">scan_interval:</span> <span class="number">30</span></span><br><span class="line">      <span class="attr">value_template:</span> <span class="string">&quot;<span class="template-variable">&#123;&#123; value | int(0) &#125;&#125;</span>&quot;</span></span><br><span class="line">      <span class="attr">icon:</span> <span class="string">mdi:cpu-64-bit</span></span><br><span class="line"></span><br><span class="line">  <span class="bullet">-</span> <span class="attr">sensor:</span></span><br><span class="line">      <span class="attr">name:</span> <span class="string">R730XD</span> <span class="string">Fans</span> <span class="string">AVG</span> <span class="string">RPM</span></span><br><span class="line">      <span class="attr">command:</span> <span class="string">&quot;/config/scripts/ipmi_sdr_cache.sh fans_avg&quot;</span></span><br><span class="line">      <span class="attr">command_timeout:</span> <span class="number">60</span></span><br><span class="line">      <span class="attr">scan_interval:</span> <span class="number">30</span></span><br><span class="line">      <span class="attr">unit_of_measurement:</span> <span class="string">&quot;RPM&quot;</span></span><br><span class="line">      <span class="attr">value_template:</span> <span class="string">&quot;<span class="template-variable">&#123;&#123; value | int(0) &#125;&#125;</span>&quot;</span></span><br><span class="line">      <span class="attr">icon:</span> <span class="string">mdi:fan</span></span><br><span class="line"></span><br><span class="line">  <span class="bullet">-</span> <span class="attr">sensor:</span></span><br><span class="line">      <span class="attr">name:</span> <span class="string">R730XD</span> <span class="string">Inlet</span> <span class="string">Temp</span></span><br><span class="line">      <span class="attr">command:</span> <span class="string">&quot;/config/scripts/ipmi_sdr_cache.sh inlet_temp&quot;</span></span><br><span class="line">      <span class="attr">unit_of_measurement:</span> <span class="string">&quot;°C&quot;</span></span><br><span class="line">      <span class="attr">command_timeout:</span> <span class="number">60</span></span><br><span class="line">      <span class="attr">scan_interval:</span> <span class="number">30</span></span><br><span class="line">      <span class="attr">value_template:</span> <span class="string">&quot;<span class="template-variable">&#123;&#123; value | int(0) &#125;&#125;</span>&quot;</span></span><br><span class="line">      <span class="attr">icon:</span> <span class="string">mdi:thermometer-chevron-up</span></span><br><span class="line"></span><br><span class="line">  <span class="bullet">-</span> <span class="attr">sensor:</span></span><br><span class="line">      <span class="attr">name:</span> <span class="string">R730XD</span> <span class="string">Exhaust</span> <span class="string">Temp</span></span><br><span class="line">      <span class="attr">command:</span> <span class="string">&quot;/config/scripts/ipmi_sdr_cache.sh exhaust_temp&quot;</span></span><br><span class="line">      <span class="attr">command_timeout:</span> <span class="number">60</span></span><br><span class="line">      <span class="attr">scan_interval:</span> <span class="number">30</span></span><br><span class="line">      <span class="attr">unit_of_measurement:</span> <span class="string">&quot;°C&quot;</span></span><br><span class="line">      <span class="attr">value_template:</span> <span class="string">&quot;<span class="template-variable">&#123;&#123; value | int(0) &#125;&#125;</span>&quot;</span></span><br><span class="line">      <span class="attr">icon:</span> <span class="string">mdi:thermometer-chevron-down</span></span><br><span class="line"></span><br><span class="line">  <span class="bullet">-</span> <span class="attr">sensor:</span></span><br><span class="line">      <span class="attr">name:</span> <span class="string">R730XD</span> <span class="string">Power</span></span><br><span class="line">      <span class="attr">command:</span> <span class="string">&quot;/config/scripts/ipmi_sdr_cache.sh power&quot;</span></span><br><span class="line">      <span class="attr">command_timeout:</span> <span class="number">60</span></span><br><span class="line">      <span class="attr">scan_interval:</span> <span class="number">30</span></span><br><span class="line">      <span class="attr">unit_of_measurement:</span> <span class="string">&quot;W&quot;</span></span><br><span class="line">      <span class="attr">value_template:</span> <span class="string">&quot;<span class="template-variable">&#123;&#123; value | int(0) &#125;&#125;</span>&quot;</span></span><br><span class="line">      <span class="attr">icon:</span> <span class="string">mdi:flash</span></span><br><span class="line"></span><br><span class="line">  <span class="bullet">-</span> <span class="attr">switch:</span></span><br><span class="line">      <span class="attr">name:</span> <span class="string">R730XD</span> <span class="string">Power</span> <span class="string">Ctrl</span></span><br><span class="line">      <span class="attr">command_on:</span> <span class="string">&quot;ipmitool -I lanplus -H &lt;yourIP&gt; -U &lt;yourusername&gt; -P &lt;yourpassword&gt; chassis power on&quot;</span></span><br><span class="line">      <span class="attr">command_off:</span> <span class="string">&quot;ipmitool -I lanplus -H &lt;yourIP&gt; -U &lt;yourusername&gt; -P &lt;yourpassword&gt; chassis power soft&quot;</span></span><br><span class="line">      <span class="attr">command_state:</span> <span class="string">&quot;ipmitool -I lanplus -H &lt;yourIP&gt; -U &lt;yourusername&gt; -P &lt;yourpassword&gt; chassis power status | grep -qi &#x27;on&#x27;&quot;</span></span><br><span class="line">      <span class="attr">command_timeout:</span> <span class="number">60</span></span><br><span class="line">      <span class="attr">scan_interval:</span> <span class="number">30</span></span><br><span class="line">      <span class="attr">icon:</span> <span class="string">mdi:power</span></span><br><span class="line"></span><br><span class="line"><span class="comment"># R730XD 电源控制按钮配置</span></span><br><span class="line"><span class="attr">template:</span></span><br><span class="line">  <span class="bullet">-</span> <span class="attr">button:</span></span><br><span class="line">      <span class="bullet">-</span> <span class="attr">name:</span> <span class="string">&quot;R730XD Power Cycle&quot;</span></span><br><span class="line">        <span class="attr">icon:</span> <span class="string">mdi:restart</span></span><br><span class="line">        <span class="attr">press:</span></span><br><span class="line">          <span class="attr">service:</span> <span class="string">shell_command.r730xd_power_cycle</span></span><br><span class="line"></span><br><span class="line">      <span class="bullet">-</span> <span class="attr">name:</span> <span class="string">&quot;R730XD Power Reset&quot;</span></span><br><span class="line">        <span class="attr">icon:</span> <span class="string">mdi:restart-alert</span></span><br><span class="line">        <span class="attr">press:</span></span><br><span class="line">          <span class="attr">service:</span> <span class="string">shell_command.r730xd_power_reset</span></span><br><span class="line"></span><br><span class="line"><span class="comment"># R730XD 风扇控制配置</span></span><br><span class="line"><span class="attr">input_number:</span></span><br><span class="line">  <span class="attr">fan_speed:</span></span><br><span class="line">    <span class="attr">name:</span> <span class="string">R730XD</span> <span class="string">Fan</span> <span class="string">Speed</span> <span class="string">Ctrl</span></span><br><span class="line">    <span class="attr">initial:</span> <span class="number">15</span></span><br><span class="line">    <span class="attr">min:</span> <span class="number">0</span></span><br><span class="line">    <span class="attr">max:</span> <span class="number">100</span></span><br><span class="line">    <span class="attr">step:</span> <span class="number">5</span></span><br><span class="line">    <span class="attr">mode:</span> <span class="string">slider</span></span><br><span class="line"></span><br><span class="line"><span class="attr">script:</span></span><br><span class="line">  <span class="attr">set_server_fan_speed:</span></span><br><span class="line">    <span class="attr">alias:</span> <span class="string">R730XD</span> <span class="string">Set</span> <span class="string">Fan</span> <span class="string">Speed</span></span><br><span class="line">    <span class="attr">sequence:</span></span><br><span class="line">      <span class="bullet">-</span> <span class="attr">variables:</span></span><br><span class="line">          <span class="attr">fan_speed:</span> <span class="string">&quot;<span class="template-variable">&#123;&#123; speed | int &#125;&#125;</span>&quot;</span></span><br><span class="line">          </span><br><span class="line">      <span class="comment"># 设置风扇模式</span></span><br><span class="line">      <span class="bullet">-</span> <span class="attr">choose:</span></span><br><span class="line">          <span class="bullet">-</span> <span class="attr">conditions:</span></span><br><span class="line">              <span class="bullet">-</span> <span class="attr">condition:</span> <span class="string">template</span></span><br><span class="line">                <span class="attr">value_template:</span> <span class="string">&quot;<span class="template-variable">&#123;&#123; fan_speed == 0 &#125;&#125;</span>&quot;</span></span><br><span class="line">            <span class="attr">sequence:</span></span><br><span class="line">              <span class="bullet">-</span> <span class="attr">service:</span> <span class="string">shell_command.set_fan_auto</span></span><br><span class="line">        <span class="attr">default:</span></span><br><span class="line">          <span class="bullet">-</span> <span class="attr">service:</span> <span class="string">shell_command.set_fan_manual</span></span><br><span class="line">      </span><br><span class="line">      <span class="bullet">-</span> <span class="attr">delay:</span> <span class="string">&#x27;00:00:02&#x27;</span></span><br><span class="line">      </span><br><span class="line">      <span class="comment"># 设置风扇速度</span></span><br><span class="line">      <span class="bullet">-</span> <span class="attr">choose:</span></span><br><span class="line">          <span class="bullet">-</span> <span class="attr">conditions:</span></span><br><span class="line">              <span class="bullet">-</span> <span class="attr">condition:</span> <span class="string">template</span></span><br><span class="line">                <span class="attr">value_template:</span> <span class="string">&quot;<span class="template-variable">&#123;&#123; fan_speed == 0 &#125;&#125;</span>&quot;</span></span><br><span class="line">            <span class="attr">sequence:</span></span><br><span class="line">              <span class="bullet">-</span> <span class="attr">service:</span> <span class="string">shell_command.set_fan_00</span></span><br><span class="line">          <span class="bullet">-</span> <span class="attr">conditions:</span></span><br><span class="line">              <span class="bullet">-</span> <span class="attr">condition:</span> <span class="string">template</span></span><br><span class="line">                <span class="attr">value_template:</span> <span class="string">&quot;<span class="template-variable">&#123;&#123; fan_speed == 5 &#125;&#125;</span>&quot;</span></span><br><span class="line">            <span class="attr">sequence:</span></span><br><span class="line">              <span class="bullet">-</span> <span class="attr">service:</span> <span class="string">shell_command.set_fan_05</span></span><br><span class="line">          <span class="bullet">-</span> <span class="attr">conditions:</span></span><br><span class="line">              <span class="bullet">-</span> <span class="attr">condition:</span> <span class="string">template</span></span><br><span class="line">                <span class="attr">value_template:</span> <span class="string">&quot;<span class="template-variable">&#123;&#123; fan_speed == 10 &#125;&#125;</span>&quot;</span></span><br><span class="line">            <span class="attr">sequence:</span></span><br><span class="line">              <span class="bullet">-</span> <span class="attr">service:</span> <span class="string">shell_command.set_fan_10</span></span><br><span class="line">          <span class="bullet">-</span> <span class="attr">conditions:</span></span><br><span class="line">              <span class="bullet">-</span> <span class="attr">condition:</span> <span class="string">template</span></span><br><span class="line">                <span class="attr">value_template:</span> <span class="string">&quot;<span class="template-variable">&#123;&#123; fan_speed == 15 &#125;&#125;</span>&quot;</span></span><br><span class="line">            <span class="attr">sequence:</span></span><br><span class="line">              <span class="bullet">-</span> <span class="attr">service:</span> <span class="string">shell_command.set_fan_15</span></span><br><span class="line">          <span class="bullet">-</span> <span class="attr">conditions:</span></span><br><span class="line">              <span class="bullet">-</span> <span class="attr">condition:</span> <span class="string">template</span></span><br><span class="line">                <span class="attr">value_template:</span> <span class="string">&quot;<span class="template-variable">&#123;&#123; fan_speed == 20 &#125;&#125;</span>&quot;</span></span><br><span class="line">            <span class="attr">sequence:</span></span><br><span class="line">              <span class="bullet">-</span> <span class="attr">service:</span> <span class="string">shell_command.set_fan_20</span></span><br><span class="line">          <span class="bullet">-</span> <span class="attr">conditions:</span></span><br><span class="line">              <span class="bullet">-</span> <span class="attr">condition:</span> <span class="string">template</span></span><br><span class="line">                <span class="attr">value_template:</span> <span class="string">&quot;<span class="template-variable">&#123;&#123; fan_speed == 25 &#125;&#125;</span>&quot;</span></span><br><span class="line">            <span class="attr">sequence:</span></span><br><span class="line">              <span class="bullet">-</span> <span class="attr">service:</span> <span class="string">shell_command.set_fan_25</span></span><br><span class="line">          <span class="bullet">-</span> <span class="attr">conditions:</span></span><br><span class="line">              <span class="bullet">-</span> <span class="attr">condition:</span> <span class="string">template</span></span><br><span class="line">                <span class="attr">value_template:</span> <span class="string">&quot;<span class="template-variable">&#123;&#123; fan_speed == 30 &#125;&#125;</span>&quot;</span></span><br><span class="line">            <span class="attr">sequence:</span></span><br><span class="line">              <span class="bullet">-</span> <span class="attr">service:</span> <span class="string">shell_command.set_fan_30</span></span><br><span class="line">          <span class="bullet">-</span> <span class="attr">conditions:</span></span><br><span class="line">              <span class="bullet">-</span> <span class="attr">condition:</span> <span class="string">template</span></span><br><span class="line">                <span class="attr">value_template:</span> <span class="string">&quot;<span class="template-variable">&#123;&#123; fan_speed == 35 &#125;&#125;</span>&quot;</span></span><br><span class="line">            <span class="attr">sequence:</span></span><br><span class="line">              <span class="bullet">-</span> <span class="attr">service:</span> <span class="string">shell_command.set_fan_35</span></span><br><span class="line">          <span class="bullet">-</span> <span class="attr">conditions:</span></span><br><span class="line">              <span class="bullet">-</span> <span class="attr">condition:</span> <span class="string">template</span></span><br><span class="line">                <span class="attr">value_template:</span> <span class="string">&quot;<span class="template-variable">&#123;&#123; fan_speed == 40 &#125;&#125;</span>&quot;</span></span><br><span class="line">            <span class="attr">sequence:</span></span><br><span class="line">              <span class="bullet">-</span> <span class="attr">service:</span> <span class="string">shell_command.set_fan_40</span></span><br><span class="line">          <span class="bullet">-</span> <span class="attr">conditions:</span></span><br><span class="line">              <span class="bullet">-</span> <span class="attr">condition:</span> <span class="string">template</span></span><br><span class="line">                <span class="attr">value_template:</span> <span class="string">&quot;<span class="template-variable">&#123;&#123; fan_speed == 45 &#125;&#125;</span>&quot;</span></span><br><span class="line">            <span class="attr">sequence:</span></span><br><span class="line">              <span class="bullet">-</span> <span class="attr">service:</span> <span class="string">shell_command.set_fan_45</span></span><br><span class="line">          <span class="bullet">-</span> <span class="attr">conditions:</span></span><br><span class="line">              <span class="bullet">-</span> <span class="attr">condition:</span> <span class="string">template</span></span><br><span class="line">                <span class="attr">value_template:</span> <span class="string">&quot;<span class="template-variable">&#123;&#123; fan_speed == 50 &#125;&#125;</span>&quot;</span></span><br><span class="line">            <span class="attr">sequence:</span></span><br><span class="line">              <span class="bullet">-</span> <span class="attr">service:</span> <span class="string">shell_command.set_fan_50</span></span><br><span class="line">          <span class="bullet">-</span> <span class="attr">conditions:</span></span><br><span class="line">              <span class="bullet">-</span> <span class="attr">condition:</span> <span class="string">template</span></span><br><span class="line">                <span class="attr">value_template:</span> <span class="string">&quot;<span class="template-variable">&#123;&#123; fan_speed == 55 &#125;&#125;</span>&quot;</span></span><br><span class="line">            <span class="attr">sequence:</span></span><br><span class="line">              <span class="bullet">-</span> <span class="attr">service:</span> <span class="string">shell_command.set_fan_55</span></span><br><span class="line">          <span class="bullet">-</span> <span class="attr">conditions:</span></span><br><span class="line">              <span class="bullet">-</span> <span class="attr">condition:</span> <span class="string">template</span></span><br><span class="line">                <span class="attr">value_template:</span> <span class="string">&quot;<span class="template-variable">&#123;&#123; fan_speed == 60 &#125;&#125;</span>&quot;</span></span><br><span class="line">            <span class="attr">sequence:</span></span><br><span class="line">              <span class="bullet">-</span> <span class="attr">service:</span> <span class="string">shell_command.set_fan_60</span></span><br><span class="line">          <span class="bullet">-</span> <span class="attr">conditions:</span></span><br><span class="line">              <span class="bullet">-</span> <span class="attr">condition:</span> <span class="string">template</span></span><br><span class="line">                <span class="attr">value_template:</span> <span class="string">&quot;<span class="template-variable">&#123;&#123; fan_speed == 65 &#125;&#125;</span>&quot;</span></span><br><span class="line">            <span class="attr">sequence:</span></span><br><span class="line">              <span class="bullet">-</span> <span class="attr">service:</span> <span class="string">shell_command.set_fan_65</span></span><br><span class="line">          <span class="bullet">-</span> <span class="attr">conditions:</span></span><br><span class="line">              <span class="bullet">-</span> <span class="attr">condition:</span> <span class="string">template</span></span><br><span class="line">                <span class="attr">value_template:</span> <span class="string">&quot;<span class="template-variable">&#123;&#123; fan_speed == 70 &#125;&#125;</span>&quot;</span></span><br><span class="line">            <span class="attr">sequence:</span></span><br><span class="line">              <span class="bullet">-</span> <span class="attr">service:</span> <span class="string">shell_command.set_fan_70</span></span><br><span class="line">          <span class="bullet">-</span> <span class="attr">conditions:</span></span><br><span class="line">              <span class="bullet">-</span> <span class="attr">condition:</span> <span class="string">template</span></span><br><span class="line">                <span class="attr">value_template:</span> <span class="string">&quot;<span class="template-variable">&#123;&#123; fan_speed == 75 &#125;&#125;</span>&quot;</span></span><br><span class="line">            <span class="attr">sequence:</span></span><br><span class="line">              <span class="bullet">-</span> <span class="attr">service:</span> <span class="string">shell_command.set_fan_75</span></span><br><span class="line">          <span class="bullet">-</span> <span class="attr">conditions:</span></span><br><span class="line">              <span class="bullet">-</span> <span class="attr">condition:</span> <span class="string">template</span></span><br><span class="line">                <span class="attr">value_template:</span> <span class="string">&quot;<span class="template-variable">&#123;&#123; fan_speed == 80 &#125;&#125;</span>&quot;</span></span><br><span class="line">            <span class="attr">sequence:</span></span><br><span class="line">              <span class="bullet">-</span> <span class="attr">service:</span> <span class="string">shell_command.set_fan_80</span></span><br><span class="line">          <span class="bullet">-</span> <span class="attr">conditions:</span></span><br><span class="line">              <span class="bullet">-</span> <span class="attr">condition:</span> <span class="string">template</span></span><br><span class="line">                <span class="attr">value_template:</span> <span class="string">&quot;<span class="template-variable">&#123;&#123; fan_speed == 85 &#125;&#125;</span>&quot;</span></span><br><span class="line">            <span class="attr">sequence:</span></span><br><span class="line">              <span class="bullet">-</span> <span class="attr">service:</span> <span class="string">shell_command.set_fan_85</span></span><br><span class="line">          <span class="bullet">-</span> <span class="attr">conditions:</span></span><br><span class="line">              <span class="bullet">-</span> <span class="attr">condition:</span> <span class="string">template</span></span><br><span class="line">                <span class="attr">value_template:</span> <span class="string">&quot;<span class="template-variable">&#123;&#123; fan_speed == 90 &#125;&#125;</span>&quot;</span></span><br><span class="line">            <span class="attr">sequence:</span></span><br><span class="line">              <span class="bullet">-</span> <span class="attr">service:</span> <span class="string">shell_command.set_fan_90</span></span><br><span class="line">          <span class="bullet">-</span> <span class="attr">conditions:</span></span><br><span class="line">              <span class="bullet">-</span> <span class="attr">condition:</span> <span class="string">template</span></span><br><span class="line">                <span class="attr">value_template:</span> <span class="string">&quot;<span class="template-variable">&#123;&#123; fan_speed == 95 &#125;&#125;</span>&quot;</span></span><br><span class="line">            <span class="attr">sequence:</span></span><br><span class="line">              <span class="bullet">-</span> <span class="attr">service:</span> <span class="string">shell_command.set_fan_95</span></span><br><span class="line">          <span class="bullet">-</span> <span class="attr">conditions:</span></span><br><span class="line">              <span class="bullet">-</span> <span class="attr">condition:</span> <span class="string">template</span></span><br><span class="line">                <span class="attr">value_template:</span> <span class="string">&quot;<span class="template-variable">&#123;&#123; fan_speed == 100 &#125;&#125;</span>&quot;</span></span><br><span class="line">            <span class="attr">sequence:</span></span><br><span class="line">              <span class="bullet">-</span> <span class="attr">service:</span> <span class="string">shell_command.set_fan_100</span></span><br><span class="line">        <span class="attr">default:</span></span><br><span class="line">          <span class="bullet">-</span> <span class="attr">service:</span> <span class="string">system_log.write</span></span><br><span class="line">            <span class="attr">data:</span></span><br><span class="line">              <span class="attr">message:</span> <span class="string">&quot;不支持的风扇速度: <span class="template-variable">&#123;&#123; fan_speed &#125;&#125;</span>%&quot;</span></span><br><span class="line">              <span class="attr">level:</span> <span class="string">warning</span></span><br><span class="line"></span><br><span class="line"><span class="attr">automation:</span></span><br><span class="line">  <span class="bullet">-</span> <span class="attr">alias:</span> <span class="string">R730XD</span> <span class="string">Set</span> <span class="string">Fan</span> <span class="string">Speed</span> <span class="string">on</span> <span class="string">Change</span></span><br><span class="line">    <span class="attr">trigger:</span></span><br><span class="line">      <span class="bullet">-</span> <span class="attr">platform:</span> <span class="string">state</span></span><br><span class="line">        <span class="attr">entity_id:</span> <span class="string">input_number.fan_speed</span></span><br><span class="line">    <span class="attr">condition:</span></span><br><span class="line">      <span class="bullet">-</span> <span class="attr">condition:</span> <span class="string">template</span></span><br><span class="line">        <span class="attr">value_template:</span> <span class="string">&quot;<span class="template-variable">&#123;&#123; trigger.from_state is not none &#125;&#125;</span>&quot;</span></span><br><span class="line">      <span class="bullet">-</span> <span class="attr">condition:</span> <span class="string">template</span></span><br><span class="line">        <span class="attr">value_template:</span> <span class="string">&quot;<span class="template-variable">&#123;&#123; trigger.from_state.state != trigger.to_state.state &#125;&#125;</span>&quot;</span></span><br><span class="line">    <span class="attr">action:</span></span><br><span class="line">      <span class="bullet">-</span> <span class="attr">service:</span> <span class="string">script.set_server_fan_speed</span></span><br><span class="line">        <span class="attr">data:</span></span><br><span class="line">          <span class="attr">speed:</span> <span class="string">&quot;<span class="template-variable">&#123;&#123; states(&#x27;input_number.fan_speed&#x27;) | int &#125;&#125;</span>&quot;</span></span><br></pre></td></tr></table></figure><ol start="2"><li><strong>在</strong><code>config</code><strong>文件夹下新建一个文件夹</strong><code>scripts</code><strong>存放 Python 脚本：</strong></li></ol><ul><li><p>脚本的功能是创建一个<code>tmp</code>文件，每次将<code>ipmitool -I lanplus -H &lt;yourIP&gt; -U &lt;yourusername&gt; -P &lt;yourpassword&gt; sdr elist full</code>的结果 + 时间戳写入这个文件，然后其他命令每次都先读取tmp文件，如果<code>现在时间-文件时间戳 &gt; 60s</code>，则说明该缓存的<code>TTL</code>已经结束，再次执行<code>ipmitool -I lanplus -H &lt;yourIP&gt; -U &lt;yourusername&gt; -P &lt;yourpassword&gt; sdr elist full</code>重新创建缓存，这样可以减少对<code>ipmi</code>的压力，解决命令经常无响应的问题。</p></li><li><p>仍然要记得替换 <code>&lt;yourIP&gt;</code>、<code>&lt;yourusername&gt;</code>、<code>&lt;yourpassword&gt;</code> 为你自己的值。</p></li></ul><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br><span class="line">60</span><br><span class="line">61</span><br><span class="line">62</span><br><span class="line">63</span><br><span class="line">64</span><br><span class="line">65</span><br><span class="line">66</span><br><span class="line">67</span><br><span class="line">68</span><br><span class="line">69</span><br><span class="line">70</span><br><span class="line">71</span><br><span class="line">72</span><br><span class="line">73</span><br><span class="line">74</span><br><span class="line">75</span><br><span class="line">76</span><br><span class="line">77</span><br><span class="line">78</span><br><span class="line">79</span><br><span class="line">80</span><br><span class="line">81</span><br><span class="line">82</span><br><span class="line">83</span><br><span class="line">84</span><br><span class="line">85</span><br><span class="line">86</span><br><span class="line">87</span><br><span class="line">88</span><br><span class="line">89</span><br><span class="line">90</span><br><span class="line">91</span><br><span class="line">92</span><br><span class="line">93</span><br><span class="line">94</span><br><span class="line">95</span><br><span class="line">96</span><br></pre></td><td class="code"><pre><span class="line">#!/usr/bin/env bash</span><br><span class="line"># ipmi_sdr_cache.sh</span><br><span class="line"># Usage: ipmi_sdr_cache.sh &lt;cpu_temp|fans_avg|inlet_temp|exhaust_temp|power|full&gt;</span><br><span class="line"># Place under /config/ and chmod +x it. Adjust HOST/USER/PASS or use secrets.</span><br><span class="line"></span><br><span class="line">CACHE=&quot;/tmp/r730xd_sdr_cache&quot;</span><br><span class="line">TMP=&quot;$(mktemp /tmp/r730xd_sdr_cache.XXXXXX)&quot;</span><br><span class="line">HOST=&quot;&lt;yourIP&gt;&quot;</span><br><span class="line">USER=&quot;&lt;yourusername&gt;&quot;</span><br><span class="line">PASS=&quot;&lt;yourpassword&gt;&quot;</span><br><span class="line">TTL=30</span><br><span class="line"></span><br><span class="line">IPMITOOL_CMD=&quot;ipmitool -I lanplus -H $&#123;HOST&#125; -U $&#123;USER&#125; -P $&#123;PASS&#125;&quot;</span><br><span class="line"></span><br><span class="line">timestamp() &#123; date +%s; &#125;</span><br><span class="line"></span><br><span class="line"># ensure cache exists &amp; fresh</span><br><span class="line">now=$(timestamp)</span><br><span class="line">filetime=0</span><br><span class="line">if [ -f &quot;$CACHE&quot; ]; then</span><br><span class="line">  read -r filetime &lt; &quot;$CACHE&quot; || filetime=0</span><br><span class="line">fi</span><br><span class="line"></span><br><span class="line">age=$((now - filetime))</span><br><span class="line"></span><br><span class="line">if [ ! -f &quot;$CACHE&quot; ] || [ &quot;$age&quot; -gt &quot;$TTL&quot; ]; then</span><br><span class="line">  # try to fetch new data; write to temporary file then move atomically</span><br><span class="line">  if $IPMITOOL_CMD sdr elist full &gt; &quot;$&#123;TMP&#125;.data&quot; 2&gt;/dev/null; then</span><br><span class="line">    echo &quot;$now&quot; &gt; &quot;$&#123;TMP&#125;.tmp&quot;</span><br><span class="line">    cat &quot;$&#123;TMP&#125;.data&quot; &gt;&gt; &quot;$&#123;TMP&#125;.tmp&quot;</span><br><span class="line">    mv -f &quot;$&#123;TMP&#125;.tmp&quot; &quot;$CACHE&quot;</span><br><span class="line">    rm -f &quot;$&#123;TMP&#125;.data&quot;</span><br><span class="line">  else</span><br><span class="line">    # ipmitool failed: if cache exists, we&#x27;ll fall back to it; otherwise write a minimal cache</span><br><span class="line">    if [ ! -f &quot;$CACHE&quot; ]; then</span><br><span class="line">      echo &quot;$now&quot; &gt; &quot;$CACHE&quot;</span><br><span class="line">      echo &quot;NO_DATA_FROM_IPMITOOL&quot; &gt;&gt; &quot;$CACHE&quot;</span><br><span class="line">    fi</span><br><span class="line">  fi</span><br><span class="line">fi</span><br><span class="line"></span><br><span class="line"># helper: get raw cached body (skip first line timestamp)</span><br><span class="line">get_cached() &#123;</span><br><span class="line">  if [ -f &quot;$CACHE&quot; ]; then</span><br><span class="line">    tail -n +2 &quot;$CACHE&quot;</span><br><span class="line">  else</span><br><span class="line">    echo &quot;&quot;</span><br><span class="line">  fi</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line">case &quot;$1&quot; in</span><br><span class="line">  full)</span><br><span class="line">    get_cached</span><br><span class="line">    ;;</span><br><span class="line"></span><br><span class="line">  cpu_temp)</span><br><span class="line">    # choose sensors matching 0E or 0F (same AWK logic as you had)</span><br><span class="line">    get_cached | awk -F&#x27;|&#x27; &#x27;/Temp *\| 0E|Temp *\| 0F/ &#123;</span><br><span class="line">      gsub(/degrees C/,&quot;&quot;,$5); gsub(/ /,&quot;&quot;,$5);</span><br><span class="line">      if($5~/^[0-9]+$/ &amp;&amp; $5&gt;max) max=$5</span><br><span class="line">    &#125; END &#123; if(max&gt;0) print max; else print 0 &#125;&#x27;</span><br><span class="line">    ;;</span><br><span class="line"></span><br><span class="line">  fans_avg)</span><br><span class="line">    get_cached | awk -F&#x27;|&#x27; &#x27;/Fan/ &#123;</span><br><span class="line">      gsub(/RPM/,&quot;&quot;,$5); gsub(/ /,&quot;&quot;,$5);</span><br><span class="line">      if($5~/^[0-9]+$/)&#123; sum+= $5; cnt++ &#125;</span><br><span class="line">    &#125; END &#123;</span><br><span class="line">      if(cnt&gt;0) &#123;</span><br><span class="line">        avg = sum/cnt;</span><br><span class="line">        # print rounded int</span><br><span class="line">        printf &quot;%d\n&quot;, avg</span><br><span class="line">      &#125; else &#123; print 0 &#125;</span><br><span class="line">    &#125;&#x27;</span><br><span class="line">    ;;</span><br><span class="line"></span><br><span class="line">  inlet_temp)</span><br><span class="line">    # print first numeric from Inlet Temp</span><br><span class="line">    get_cached | awk -F&#x27;|&#x27; &#x27;/Inlet Temp/ &#123;print $5&#125;&#x27; | grep -o &#x27;[0-9]\+&#x27; | head -n1 | sed -e &#x27;s/^$/0/&#x27;</span><br><span class="line">    ;;</span><br><span class="line"></span><br><span class="line">  exhaust_temp)</span><br><span class="line">    get_cached | awk -F&#x27;|&#x27; &#x27;/Exhaust Temp/ &#123;print $5&#125;&#x27; | grep -o &#x27;[0-9]\+&#x27; | head -n1 | sed -e &#x27;s/^$/0/&#x27;</span><br><span class="line">    ;;</span><br><span class="line"></span><br><span class="line">  power)</span><br><span class="line">    get_cached | awk -F&#x27;|&#x27; &#x27;/Pwr Consumption/ &#123;</span><br><span class="line">      gsub(/Watts/,&quot;&quot;,$5); gsub(/ /,&quot;&quot;,$5); print $5; exit</span><br><span class="line">    &#125;&#x27; | sed -e &#x27;s/^[[:space:]]*//;s/[[:space:]]*$//&#x27; | awk &#x27;&#123; if($0~/^[0-9]+$/) print $0; else print 0 &#125;&#x27;</span><br><span class="line">    ;;</span><br><span class="line"></span><br><span class="line">  *)</span><br><span class="line">    echo &quot;Usage: $0 &lt;cpu_temp|fans_avg|inlet_temp|exhaust_temp|power|full&gt;&quot;</span><br><span class="line">    exit 2</span><br><span class="line">    ;;</span><br><span class="line">esac</span><br></pre></td></tr></table></figure><h1 id="最后成果"><a href="#最后成果" class="headerlink" title="最后成果"></a>最后成果</h1><img                           lazyload                       alt="image"                       data-src="https://img.102465.xyz/file/Ura0FXYF.png"                         alt="image.png" width=100%                  >]]>
    </content>
    <id>https://x4ai.cn/2025/09/05/R730XD-IDRAC8%E6%8E%A5%E5%85%A5HomeAssistant/</id>
    <link href="https://x4ai.cn/2025/09/05/R730XD-IDRAC8%E6%8E%A5%E5%85%A5HomeAssistant/"/>
    <published>2025-09-05T09:06:29.000Z</published>
    <summary>本文记录如何通过 IPMI 将 Dell R730XD 的传感器数据接入 Home Assistant，并实现远程电源控制和风扇调速功能的详细步骤和命令配置。</summary>
    <title>R730XD IDRAC8 接入 Home Assistant（IPMI方式）</title>
    <updated>2026-06-06T02:01:51.544Z</updated>
  </entry>
  <entry>
    <author>
      <name>迎风起降</name>
    </author>
    <category term="🦐折腾" scheme="https://x4ai.cn/categories/%F0%9F%A6%90%E6%8A%98%E8%85%BE/"/>
    <category term="HomeIDC" scheme="https://x4ai.cn/tags/HomeIDC/"/>
    <category term="AP" scheme="https://x4ai.cn/tags/AP/"/>
    <content>
      <![CDATA[<h1 id="介绍"><a href="#介绍" class="headerlink" title="介绍"></a>介绍</h1><h2 id="CR880X"><a href="#CR880X" class="headerlink" title="CR880X"></a>CR880X</h2><p>CR880X是运营商定制版的红米AX3000，硬件上CR880X和AX3000是几乎完全相同的，一般可以把运营商定制版系统刷为红米AX3000系统即可完美使用。</p><p>CR880X分为CR8806（中国联通定制版）、CR8808（中国移动定制版）、CR8809（中国电信定制版）；按主板版号来分的话有<strong>M81（IPQ5000）、M79 A版（IPQ5000）、M79 B版（IPQ5018）三种。</strong></p><p><strong>分辨方法如下：</strong></p><blockquote><p>来自恩山大佬帖子：<a class="link"   href="https://www.right.com.cn/forum/thread-8444159-1-1.html" >https://www.right.com.cn/forum/thread-8444159-1-1.html<i class="fas fa-external-link-alt"></i></a>（必看）</p><p>可以不拆机从顶部散热口处观察TTL位置来判断主板什么版本。具体操作就是用手机打开手电筒放在路由器底部，光线透过底部散热口照亮内部主板，眼睛通过顶部散热口查看内部的TTL位置<br>ttl在右上角的是5000，在底部的是5018（竖排是m81，横排是m79，板子左上角也有写）在uboot下均显示5018</p></blockquote><img                           lazyload                       alt="image"                       data-src="https://img.102465.xyz/file/bX51p6eM.png"                         alt="image.png" width=100%                  ><p><strong>⚠️ 需要注意的是：</strong></p><ol><li><p><strong>M79 B版不能刷红米AX3000固件会变砖，M79 A版刷了后2.4G wifi速率残缺</strong></p></li><li><p><strong>M79 B版刷了集客AP开机又是原厂固件</strong></p></li></ol><h2 id="集客"><a href="#集客" class="headerlink" title="集客"></a>集客</h2><h3 id="集客系统"><a href="#集客系统" class="headerlink" title="集客系统"></a>集客系统</h3><p>“集客系统”是一套无线接入点（AP）管理系统。它通常由以下两部分组成：</p><ol><li><p>AC（接入点控制器）<br>可以是刷了集客 AC 固件的路由器，也可以部署在 Docker 容器中的 AC，或者使用集客官方提供的云端 AC。AC 负责统一管理多个 AP，实现 Wi-Fi 管理、漫游优化等核心功能。</p></li><li><p>多个 AP（无线接入点）<br>分布在家中不同区域，为各个角落提供 Wi-Fi 信号覆盖。AP 则通过 AC 统一配置，通过同一 SSID 实现设备漫游和自动切换网络，<strong>本文就是把CR880X刷成集客AP</strong>。</p></li></ol><h3 id="AC-AP-优势"><a href="#AC-AP-优势" class="headerlink" title="AC + AP 优势"></a>AC + AP 优势</h3><p>相比常见的路由器 Mesh 组网方式，AC + AP 有几个显著优势：</p><ul><li><p>更强的设备承载能力：AC + AP 架构适用于更多终端连接，不容易因为过载导致性能下降。</p></li><li><p>无缝漫游体验：通过 AC 配置漫游阈值（如 RSSI），设备可以快速切换到信号最优的 AP，几乎不会断网，连接体验更轻柔流畅。</p></li><li><p>集中管理：AP 无需逐台单独配置，可以通过模板批量设置 SSID、信道、功率、VLAN、黑&#x2F;白名单等，大幅减少管理复杂度与设置时间。</p></li></ul><h3 id="集客AC-AP组网方法"><a href="#集客AC-AP组网方法" class="headerlink" title="集客AC + AP组网方法"></a>集客AC + AP组网方法</h3><ol><li><p>刷固件 &amp; 启动AC：将支持集客固件的设备（如小米路由、小米 AX 系列、K2T等，<strong>最好组网设备都是高通</strong>）刷成 AP固件，并部署AC（无论是Docker、虚拟机、云端还是刷路由器本身）。</p></li><li><p>配置模板：在AC控制器中配置SSID、信号功率、频段、漫游阈值、VLAN、安全设置等，并保存为模板。</p></li><li><p>AP 批量上架：AP插入电源或接入网线后，会自动连接AC并获取模板配置，实现“即插即用”。无需手动逐个配置。</p></li><li><p>漫游优化：启用KVR快速漫游、自动信道优化、5G优先等功能，提升终端在不同AP之间切换时的连接质量。</p></li></ol><h3 id="扫盲贴"><a href="#扫盲贴" class="headerlink" title="扫盲贴"></a>扫盲贴</h3><p>这里提供一篇恩山大佬的帖子学习：<a class="link"   href="https://www.right.com.cn/forum/thread-1501038-1-1.html" >https://www.right.com.cn/forum/thread-1501038-1-1.html<i class="fas fa-external-link-alt"></i></a></p><hr><h1 id="打开SSH权限"><a href="#打开SSH权限" class="headerlink" title="打开SSH权限"></a>打开SSH权限</h1><p>下面就正式开始刷机了！</p><h2 id="参考文献"><a href="#参考文献" class="headerlink" title="参考文献"></a>参考文献</h2><ol><li><p><a class="link"   href="https://www.right.com.cn/forum/thread-8444159-1-1.html" >https://www.right.com.cn/forum/thread-8444159-1-1.html<i class="fas fa-external-link-alt"></i></a></p></li><li><p><a class="link"   href="https://www.bilibili.com/video/BV1hntzzJEwJ" >https://www.bilibili.com/video/BV1hntzzJEwJ<i class="fas fa-external-link-alt"></i></a></p></li><li><p><a class="link"   href="https://www.bilibili.com/video/BV1KofUYKELP" >https://www.bilibili.com/video/BV1KofUYKELP<i class="fas fa-external-link-alt"></i></a></p></li><li><p><a class="link"   href="https://www.right.com.cn/forum/thread-8374973-1-1.html" >https://www.right.com.cn/forum/thread-8374973-1-1.html<i class="fas fa-external-link-alt"></i></a></p></li><li><p><a class="link"   href="https://github.com/openwrt-xiaomi/xmir-patcher/" >https://github.com/openwrt-xiaomi/xmir-patcher/<i class="fas fa-external-link-alt"></i></a></p></li></ol><h2 id="步骤记录"><a href="#步骤记录" class="headerlink" title="步骤记录"></a>步骤记录</h2><ol><li><p>从GitHub上将整个项目Clone到本地电脑 <code>git clone https://github.com/openwrt-xiaomi/xmir-patcher.git</code></p></li><li><p>准备好<code>python 3.8+</code> 和<code>openssl</code> 环境（MacOS会自带openssl）</p></li><li><p>运行脚本 <code>cd xmir-patcher &amp;  bash ./run.sh</code></p></li><li><p>脚本会自动安装pip的依赖！！！！（<strong>忘记conda开新环境了😠😠😠</strong>），然后显示主菜单：</p></li></ol><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br></pre></td><td class="code"><pre><span class="line">Xiaomi MiR Patcher  </span><br><span class="line"></span><br><span class="line"></span><br><span class="line"> 1 - Set IP-address (current value: 192.168.31.1)</span><br><span class="line"> 2 - Connect to device (install exploit)</span><br><span class="line"> 3 - Read full device info</span><br><span class="line"> 4 - Create full backup</span><br><span class="line"> 5 - Install EN/RU languages</span><br><span class="line"> 6 - Install permanent SSH</span><br><span class="line"> 7 - Install firmware (from directory &quot;firmware&quot;)</span><br><span class="line"> 8 - &#123;&#123;&#123; Other functions &#125;&#125;&#125;</span><br><span class="line"> 9 - [[ Reboot device ]]</span><br><span class="line"> 0 - Exit</span><br><span class="line"></span><br><span class="line">Select: </span><br></pre></td></tr></table></figure><ol start="5"><li><p>输入 <code>1</code> ，设置CR880X的IP地址，我这里是<code>192.168.123.7</code> （每个人不一样，同理下面命令的所有<code>192.168.123.7</code>都需要改成你自己的IP）</p></li><li><p>输入<code>2</code> ，MacOS需要<strong>允许终端查找附近设备</strong>，然后输入WEB管理页上的密码，看到输出<code>SSH server are activated!</code> 就是成功。</p></li></ol><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br></pre></td><td class="code"><pre><span class="line">Select: 2</span><br><span class="line"></span><br><span class="line">device_name = CR8806</span><br><span class="line">rom_version = 6.2.33 release</span><br><span class="line">mac_address = xx:xx:xx:xx:xx:xx</span><br><span class="line">CountryCode = CN</span><br><span class="line">Enter device WEB password: yourpassword</span><br><span class="line">WARN: Exploits &quot;arn_switch/start_binding/set_mac_filter&quot; not working!!!</span><br><span class="line">Enable smartcontroller scene executor ...</span><br><span class="line">Wait smartcontroller activation ...</span><br><span class="line">Unlock dropbear service ...</span><br><span class="line">Unlock SSH server ...</span><br><span class="line">Set password &quot;root&quot; for root user ...</span><br><span class="line">Enabling dropbear service ...</span><br><span class="line">Run SSH server on port 22 ...</span><br><span class="line">Test SSH connection to port 22 ...</span><br><span class="line"></span><br><span class="line">#### SSH server are activated! ####</span><br></pre></td></tr></table></figure><ol start="7"><li>固化SSH密码为<code>root</code> ，主菜单选<code>8</code>，然后选<code>2</code> ，输入需要固化的密码</li></ol><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br></pre></td><td class="code"><pre><span class="line">Select: 8</span><br><span class="line"></span><br><span class="line"></span><br><span class="line">----------------------------------------------------------</span><br><span class="line"></span><br><span class="line">Xiaomi MiR Patcher (extended functions) </span><br><span class="line"></span><br><span class="line"></span><br><span class="line">    1 - Set IP-address (current value: 192.168.123.7)</span><br><span class="line">    2 - Change root password</span><br><span class="line">    3 - Read dmesg and syslog</span><br><span class="line">    4 - Create a backup of the specified partition</span><br><span class="line">    5 - Uninstall EN/RU languages</span><br><span class="line">    6 - Set kernel boot address</span><br><span class="line">    7 - Install Breed bootloader</span><br><span class="line">    8 - __test__</span><br><span class="line">    9 - [[ Reboot device ]]</span><br><span class="line">    0 - Return to main menu</span><br><span class="line"></span><br><span class="line">Choice: 2</span><br><span class="line"></span><br><span class="line">Detect valid SSH server on port 22 (auth OK)</span><br><span class="line">Enter new password for root user: root</span><br><span class="line">The root password has been changed.</span><br></pre></td></tr></table></figure><ol start="8"><li>使用SSH连接到路由器 <code>ssh -o HostKeyAlgorithms=+ssh-rsa -o PubkeyAcceptedAlgorithms=+ssh-rsa root@192.168.123.7</code> ，输入密码<code>root</code>，看到下面的输出就是成功登陆。</li></ol><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br></pre></td><td class="code"><pre><span class="line">BusyBox v1.25.1 (2022-04-11 13:21:54 UTC) built-in shell (ash)</span><br><span class="line"></span><br><span class="line">    -----------------------------------------------------</span><br><span class="line">        Welcome to XiaoQiang!</span><br><span class="line">    -----------------------------------------------------</span><br><span class="line">    $$$$$$\  $$$$$$$\  $$$$$$$$\      $$\      $$\        $$$$$$\  $$\   $$\</span><br><span class="line">    $$  __$$\ $$  __$$\ $$  _____|     $$ |     $$ |      $$  __$$\ $$ | $$  |</span><br><span class="line">    $$ /  $$ |$$ |  $$ |$$ |           $$ |     $$ |      $$ /  $$ |$$ |$$  /</span><br><span class="line">    $$$$$$$$ |$$$$$$$  |$$$$$\         $$ |     $$ |      $$ |  $$ |$$$$$  /</span><br><span class="line">    $$  __$$ |$$  __$$&lt; $$  __|        $$ |     $$ |      $$ |  $$ |$$  $$&lt;</span><br><span class="line">    $$ |  $$ |$$ |  $$ |$$ |           $$ |     $$ |      $$ |  $$ |$$ |\$$\</span><br><span class="line">    $$ |  $$ |$$ |  $$ |$$$$$$$$\       $$$$$$$$$  |       $$$$$$  |$$ | \$$\</span><br><span class="line">    \__|  \__|\__|  \__|\________|      \_________/        \______/ \__|  \__|</span><br><span class="line"></span><br><span class="line"></span><br><span class="line">root@XiaoQiang:~# </span><br></pre></td></tr></table></figure><ol start="9"><li><p>输入<code>nvram get flag_last_success</code> 查看当前分区</p></li><li><p>若输出为<code>0</code> ，执行下面命令：</p></li></ol><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line">nvram set flag_last_success=1</span><br><span class="line">nvram set flag_boot_rootfs=1</span><br><span class="line">nvram commit</span><br><span class="line">reboot</span><br></pre></td></tr></table></figure><ol start="11"><li>等待路由器重启后，再次SSH登陆（步骤8），输入<code>nvram get flag_last_success</code>查看，若输出为<code>1</code>，则执行下一步，否则重复步骤10</li></ol><h1 id="刷入集客固件"><a href="#刷入集客固件" class="headerlink" title="刷入集客固件"></a>刷入集客固件</h1><h2 id="刷入7-0固件"><a href="#刷入7-0固件" class="headerlink" title="刷入7.0固件"></a>刷入7.0固件</h2><p>因为集客官网的8.0固件过大，无法直接刷入，需要先刷入7.0固件后再使用8.0固件进行升级</p><p>感谢 <a class="link"   href="https://space.bilibili.com/10720688/?spm_id_from=333.788.upinfo.detail.click" >@<i class="fas fa-external-link-alt"></i></a><a href="https://space.bilibili.com/10720688/?spm_id_from=333.788.upinfo.detail.click"><strong>乌客wuke</strong></a> 提供的7.0固件，下载链接为：<a class="link"   href="https://cloud.189.cn/web/share?code=6NFNbyniEFJr" >https://cloud.189.cn/web/share?code=6NFNbyniEFJr<i class="fas fa-external-link-alt"></i></a>，密码5q6s</p><p>这里提供一个从我博客服务器下载的链接，防止天翼云文件删除，<a href="/upload/ubi-JIKEAP_N3000.img">ubi-JIKEAP_N3000.img</a></p><ol><li>得到<a href="/upload/ubi-JIKEAP_N3000.img">ubi-JIKEAP_N3000.img</a>后，打开终端进入到<a href="/upload/ubi-JIKEAP_N3000.img">ubi-JIKEAP_N3000.img</a>同级目录下，利用SCP传输固件到路由器内 <code>scp -O -o HostKeyAlgorithms=+ssh-rsa -o PubkeyAcceptedAlgorithms=+ssh-rsa ubi-JIKEAP_N3000.img root@192.168.123.7:/tmp/</code></li></ol><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line">(base) fwm@MacBook-Air 集客固件 % ls</span><br><span class="line">GECOOS_N3000_IPQ50XX_8.0_2024081000.binubi-JIKEAP_N3000.img</span><br><span class="line">(base) fwm@MacBook-Air 集客固件 % scp -O -o HostKeyAlgorithms=+ssh-rsa -o PubkeyAcceptedAlgorithms=+ssh-rsa ubi-JIKEAP_N3000.img root@192.168.123.7:/tmp/</span><br><span class="line">root@192.168.123.7&#x27;s password: </span><br><span class="line">ubi-JIKEAP_N3000.img                          100%   14MB   3.9MB/s   00:03    </span><br><span class="line">(base) fwm@MacBook-Air 集客固件 % </span><br></pre></td></tr></table></figure><ol start="2"><li>重新SSH登陆到路由器，<code>ls /tmp | grep JIKEAP_N3000</code> ，有输出就是成功传输了</li></ol><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">root@XiaoQiang:/tmp# ls /tmp | grep JIKEAP_N3000</span><br><span class="line">ubi-JIKEAP_N3000.img</span><br></pre></td></tr></table></figure><ol start="3"><li>😁激动人心的时刻！！！正式开始刷机！！在SSH登陆后，输入<code>ubiformat /dev/mtd18 -y -f /tmp/ubi-JIKEAP_N3000.img</code></li></ol><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line">root@XiaoQiang:/tmp# ubiformat /dev/mtd18 -y -f /tmp/ubi-JIKEAP_N3000.img</span><br><span class="line">ubiformat: mtd18 (nand), size 37748736 bytes (36.0 MiB), 288 eraseblocks of 131072 bytes (128.0 KiB), min. I/O size 2048 bytes</span><br><span class="line">libscan: scanning eraseblock 287 -- 100 % complete  </span><br><span class="line">ubiformat: 288 eraseblocks have valid erase counter, mean value is 0</span><br><span class="line">ubiformat: flashing eraseblock 110 -- 100 % complete  </span><br><span class="line">ubiformat: formatting eraseblock 287 -- 100 % complete  </span><br></pre></td></tr></table></figure><ol start="4"><li>修改启动分区</li></ol><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line">nvram set flag_last_success=0</span><br><span class="line">nvram set flag_boot_rootfs=0</span><br><span class="line">nvram commit</span><br><span class="line">reboot</span><br></pre></td></tr></table></figure><ol start="5"><li>耐心等待系统重启，重启成功后进入主路由后台查看IP，WEB后台原始密码为<code>admin</code></li></ol><h2 id="刷入8-0固件"><a href="#刷入8-0固件" class="headerlink" title="刷入8.0固件"></a>刷入8.0固件</h2><p>在集客官网的<a class="link"   href="http://file.cnrouter.com/index.php/Index/index.html?model_id=69&device_type_id=4" >固件下载<i class="fas fa-external-link-alt"></i></a>页面，<strong>设备类型</strong><code>集客无线AP(高通)</code>、<strong>设备型号</strong><code>N3000</code></p><p>然后打开WEB后台，系统管理-系统升级-上传8.0固件-升级系统</p><h1 id="结算画面"><a href="#结算画面" class="headerlink" title="结算画面"></a>结算画面</h1><img                           lazyload                       alt="image"                       data-src="https://img.102465.xyz/file/ArNK9bJf.png"                         alt="image.png" width=100%                  >]]>
    </content>
    <id>https://x4ai.cn/2025/09/01/CR880X%E5%88%B7%E5%85%A5%E9%9B%86%E5%AE%A2AP%E7%B3%BB%E7%BB%9F/</id>
    <link href="https://x4ai.cn/2025/09/01/CR880X%E5%88%B7%E5%85%A5%E9%9B%86%E5%AE%A2AP%E7%B3%BB%E7%BB%9F/"/>
    <published>2025-09-01T09:56:22.000Z</published>
    <summary>本文记录如何将CR880X刷入集客AP系统，包括刷机前准备、刷机步骤和注意事项。</summary>
    <title>CR880X刷入集客AP系统</title>
    <updated>2026-06-06T02:01:51.544Z</updated>
  </entry>
  <entry>
    <author>
      <name>迎风起降</name>
    </author>
    <category term="运维" scheme="https://x4ai.cn/categories/%E8%BF%90%E7%BB%B4/"/>
    <category term="HomeIDC" scheme="https://x4ai.cn/tags/HomeIDC/"/>
    <category term="RouterOS" scheme="https://x4ai.cn/tags/RouterOS/"/>
    <content>
      <![CDATA[<h1 id="介绍"><a href="#介绍" class="headerlink" title="介绍"></a>介绍</h1><h2 id="网络情况介绍"><a href="#网络情况介绍" class="headerlink" title="网络情况介绍"></a>网络情况介绍</h2><p>我原来一直用的主路由是部署在aio小主机上的iKuai，功能能够完全满足我的需求 而且使用非常顺畅。</p><p>我现在的网络架构是电信的光纤入户到玄关处的弱电箱（弱电箱有网线通往各个房间），然后从光猫接出一根网线到我的房间里，最后在我的房间进行拨号上网的（即光猫桥接模式，只负责光电转换，由我房间的路由器拨号上网，然后全屋其他房间使用本人房间的AP上网）。</p><p>但是因为要在杂物房放置一台R730XD服务器作为冷备份服务器，需要也给那边接入网络；而杂物房的网口只连接到弱电箱，主路由本人房间情况下无法解决；所以决心购入一台新的小型路由器放在弱电箱内拨号上网，然后两个房间的网络接入到主路由到lan口上网。</p><h2 id="主路由配置"><a href="#主路由配置" class="headerlink" title="主路由配置"></a>主路由配置</h2><p>emmm，实话说 MikroTik 官方正版的 ax² 价格大概在500元上下，本人实在囊中羞涩，买的是 红米AX5 魔改的 ax²。</p><p>魔改版 ax² 配置如下：</p><ul><li><p>CPU：高通 IPQ6000 四核 <strong>1.8GHz（频率解锁版）</strong></p></li><li><p>内存：<strong>1G</strong>（原512M）</p></li><li><p>闪存&#x2F;硬盘：<strong>256M</strong>（原128M）</p></li><li><p>系统：RouterOS L4 授权版（加了授权小版，可以任意升级 非patch）</p></li><li><p>接口：4 * 千兆 + Reset 可用</p></li><li><p>外壳：铁壳</p></li></ul><p>以上配置购买价在小黄鱼210元左右（值不值就另说了，买改好的主要是图个省心吧）；<strong>什么都不改的原版红米AX5也是可以直接刷入patch版的RouterOS的</strong>，没刷机的价格在50元左右，刷好的在100元左右。</p><img                           lazyload                       alt="image"                       data-src="https://img.102465.xyz/file/yfc5tdYE.png"                         alt="image.png" width=100%                  ><img                           lazyload                       alt="image"                       data-src="https://img.102465.xyz/file/ftcyMA2y.png"                         alt="image.png" width=100%                  ><img                           lazyload                       alt="image"                       data-src="https://img.102465.xyz/file/xI2QwsOo.png"                         alt="image.png" width=100%                  ><hr><h1 id="配置系统"><a href="#配置系统" class="headerlink" title="配置系统"></a>配置系统</h1><h2 id="WinBox安装"><a href="#WinBox安装" class="headerlink" title="WinBox安装"></a>WinBox安装</h2><p>RouterOS实际上是有WEB管理页面的，但是据大佬说关闭网页管理转用WinBox这个软件进行配置可以更加节省系统资源。</p><p><del>但是很尴尬的是，WinBox这个软件只有Windows版本，在macOS上无法使用。不过有一个项目叫</del><a href="https://github.com/nrlquaker/winbox-mac"><del>winbox-mac</del></a><del>，结合了Wine在macOS上运行WinBox。</del></p><p>最新的WinBox 4.0已经支持跨平台使用！！！访问：<a class="link"   href="https://mikrotik.com/download" >https://mikrotik.com/download<i class="fas fa-external-link-alt"></i></a> 可以下载。</p><img                           lazyload                       alt="image"                       data-src="https://img.102465.xyz/file/gl0KWf59.png"                         alt="image.png" width=100%                  ><h2 id="配置PPPoE拨号"><a href="#配置PPPoE拨号" class="headerlink" title="配置PPPoE拨号"></a>配置PPPoE拨号</h2><p>先在原系统中记下宽带的账号密码、各个设备DHCP静态分配的地址、DDNS的MAC、KEY等等；</p><p>然后给路由器的WAN口接上光猫出来的网线，然后LAN口接入交换机，通电；</p><p>等待WinBox扫描出路由器：</p><img                           lazyload                       alt="image"                       data-src="https://img.102465.xyz/file/bPZgcR1W.png"                         alt="image.png" width=100%                  ><p>然后左侧输入图示地址，账号admin，密码空，点击connect按钮连接；</p><p>进入后点击左侧菜单上的Quick Set：</p><img                           lazyload                       alt="image"                       data-src="https://img.102465.xyz/file/0r8hL9DY.png"                         alt="image.png" width=100%                  ><p>选择Router模式、PPPoE、PPPoE的账号密码、设置DHCP、开启NAT</p><p>如果拨号出现<code>terminating... - failed to authenticate ourselves to peer</code>，手机拨打10001（中国电信），无法上网-宽带-提供宽带的电话-等待电信那边重置</p>]]>
    </content>
    <id>https://x4ai.cn/2025/09/01/MikroTik-hAP-ax%C2%B2%E9%85%8D%E7%BD%AEPPPoE%E6%8B%A8%E5%8F%B7%E4%B8%8A%E7%BD%91/</id>
    <link href="https://x4ai.cn/2025/09/01/MikroTik-hAP-ax%C2%B2%E9%85%8D%E7%BD%AEPPPoE%E6%8B%A8%E5%8F%B7%E4%B8%8A%E7%BD%91/"/>
    <published>2025-09-01T09:06:29.000Z</published>
    <summary>本文记录如何使用MikroTik hAP ax²路由器配置PPPoE拨号上网的详细步骤和配置方法。</summary>
    <title>MikroTik hAP ax² 配置PPPoE拨号上网</title>
    <updated>2026-06-06T02:01:51.544Z</updated>
  </entry>
  <entry>
    <author>
      <name>迎风起降</name>
    </author>
    <category term="运维" scheme="https://x4ai.cn/categories/%E8%BF%90%E7%BB%B4/"/>
    <category term="VPS运维" scheme="https://x4ai.cn/tags/VPS%E8%BF%90%E7%BB%B4/"/>
    <category term="Docker服务部署" scheme="https://x4ai.cn/tags/Docker%E6%9C%8D%E5%8A%A1%E9%83%A8%E7%BD%B2/"/>
    <content>
      <![CDATA[<p>最近终于入手了阿里云的200M服务器，记录一下利用、配置这台服务器的过程。</p><h2 id="DD-重装系统"><a href="#DD-重装系统" class="headerlink" title="DD 重装系统"></a>DD 重装系统</h2><p>新机到手，肯定是要重新安装系统的。先不说阿里云的系统模板可能会有问题，系统自带的监控也让人不舒服。</p><p>利用GitHub大佬的一键重装项目<a class="link"   href="https://github.com/bin456789/reinstall" >reinstall<i class="fas fa-external-link-alt"></i></a>。</p><p>采用命令记录如下：</p><h3 id="下载重装脚本"><a href="#下载重装脚本" class="headerlink" title="下载重装脚本"></a>下载重装脚本</h3><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment"># 海外VPS</span></span><br><span class="line">curl -O https://raw.githubusercontent.com/bin456789/reinstall/main/reinstall.sh || wget -O reinstall.sh <span class="variable">$_</span></span><br><span class="line"></span><br><span class="line"><span class="comment"># 大陆VPS</span></span><br><span class="line">curl -O https://cnb.cool/bin456789/reinstall/-/git/raw/main/reinstall.sh || wget -O reinstall.sh <span class="variable">$_</span></span><br></pre></td></tr></table></figure><h3 id="进行重装"><a href="#进行重装" class="headerlink" title="进行重装"></a>进行重装</h3><p>我比较喜欢用最新的Ubuntu LTS，重装命令如下：</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">bash reinstall.sh ubuntu 24.04 --password PASSWORD</span><br></pre></td></tr></table></figure><h2 id="跑分"><a href="#跑分" class="headerlink" title="跑分"></a>跑分</h2><p>重装系统后可以跑一下分了解VPS的性能情况，这里用到的是Github另一位大佬的<a class="link"   href="https://github.com/oneclickvirt/ecs" >融合怪测试脚本<i class="fas fa-external-link-alt"></i></a>，一键即可测试CPU、硬盘、内存、网络。</p><p>常用命令：</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment"># 海外VPS</span></span><br><span class="line"><span class="built_in">export</span> noninteractive=<span class="literal">true</span> &amp;&amp; curl -L https://raw.githubusercontent.com/oneclickvirt/ecs/master/goecs.sh -o goecs.sh &amp;&amp; <span class="built_in">chmod</span> +x goecs.sh &amp;&amp; bash goecs.sh <span class="built_in">env</span> &amp;&amp; bash goecs.sh install &amp;&amp; goecs</span><br><span class="line"></span><br><span class="line"><span class="comment"># 大陆VPS（CDN）</span></span><br><span class="line"><span class="built_in">export</span> noninteractive=<span class="literal">true</span> &amp;&amp; curl -L https://cdn.spiritlhl.net/https://raw.githubusercontent.com/oneclickvirt/ecs/master/goecs.sh -o goecs.sh &amp;&amp; <span class="built_in">chmod</span> +x goecs.sh &amp;&amp; bash goecs.sh <span class="built_in">env</span> &amp;&amp; bash goecs.sh install &amp;&amp; goecs</span><br><span class="line"></span><br><span class="line"><span class="comment"># 大陆VPS（CNB）</span></span><br><span class="line"><span class="built_in">export</span> noninteractive=<span class="literal">true</span> &amp;&amp; curl -L https://cnb.cool/oneclickvirt/ecs/-/git/raw/main/goecs.sh -o goecs.sh &amp;&amp; <span class="built_in">chmod</span> +x goecs.sh &amp;&amp; bash goecs.sh <span class="built_in">env</span> &amp;&amp; bash goecs.sh install &amp;&amp; goecs</span><br></pre></td></tr></table></figure><p>这里附上我的阿里云 200M-2C2G 轻量应用服务器评测<a class="link"   href="https://paste.spiritlhl.net/#/show/Jfua0.txt" >结果<i class="fas fa-external-link-alt"></i></a>。</p><h2 id="系统设置"><a href="#系统设置" class="headerlink" title="系统设置"></a>系统设置</h2><h3 id="挂载数据盘"><a href="#挂载数据盘" class="headerlink" title="挂载数据盘"></a>挂载数据盘</h3><p>我买的配置包含 100G 的数据盘，需要手动挂载</p><ul><li><p><code>sudo apt install parted e2fsprogs -y</code> 安装分区工具</p></li><li><p><code>fdisk -l</code> 查看服务器的磁盘情况</p></li></ul><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br></pre></td><td class="code"><pre><span class="line">Disk /dev/vda: 40 GiB, 42949672960 bytes, 83886080 sectors</span><br><span class="line">Units: sectors of 1 * 512 = 512 bytes</span><br><span class="line">Sector size (logical/physical): 512 bytes / 512 bytes</span><br><span class="line">I/O size (minimum/optimal): 512 bytes / 512 bytes</span><br><span class="line">Disklabel <span class="built_in">type</span>: gpt</span><br><span class="line">Disk identifier: D4F1FC6D-8843-414D-839C-A5EBB7DEF544</span><br><span class="line"></span><br><span class="line">Device      Start      End  Sectors  Size Type</span><br><span class="line">/dev/vda1    2048   206847   204800  100M EFI System</span><br><span class="line">/dev/vda2  206848 83886046 83679199 39.9G Linux filesystem</span><br><span class="line"></span><br><span class="line"></span><br><span class="line">Disk /dev/vdb: 100 GiB, 107374182400 bytes, 209715200 sectors</span><br><span class="line">Units: sectors of 1 * 512 = 512 bytes</span><br><span class="line">Sector size (logical/physical): 512 bytes / 512 bytes</span><br><span class="line">I/O size (minimum/optimal): 512 bytes / 512 bytes</span><br></pre></td></tr></table></figure><p>可以看到&#x2F;dev&#x2F;vdb是数据盘</p><ul><li><code>parted /dev/vdb</code> 开始分区</li><li><code>mklabel gpt</code> 设置GPT分区</li><li><code>Yes</code> 同意设置分区</li><li><code>mkpart primary 1 100%</code> 划分一个主分区，并设置分区的开始位置和结束位置（若有警告输入I）</li></ul><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line">(parted) mkpart primary 1 100%</span><br><span class="line">Warning: You requested a partition from 1000kB to 107GB (sectors 1953..209715199).</span><br><span class="line">The closest location we can manage is 1048kB to 1048kB (sectors 2047..2047).</span><br><span class="line">Is this still acceptable to you?</span><br><span class="line">Yes/No? <span class="built_in">yes</span></span><br><span class="line">Warning: The resulting partition is not properly aligned <span class="keyword">for</span> best performance: 2047s %</span><br><span class="line">2048s != 0s</span><br><span class="line">Ignore/Cancel? i</span><br></pre></td></tr></table></figure><ul><li><code>align-check optimal 1</code> 检查分区是否对齐</li></ul><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">(parted) align-check optimal 1                                            </span><br><span class="line">1 aligned</span><br></pre></td></tr></table></figure><ul><li><code>print</code> 查看分区表</li></ul><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br></pre></td><td class="code"><pre><span class="line">(parted) <span class="built_in">print</span> </span><br><span class="line">Model: Virtio Block Device (virtblk)</span><br><span class="line">Disk /dev/vdb: 107GB</span><br><span class="line">Sector size (logical/physical): 512B/512B</span><br><span class="line">Partition Table: gpt</span><br><span class="line">Disk Flags: </span><br><span class="line"></span><br><span class="line">Number  Start   End     Size   File system  Name     Flags</span><br><span class="line"> 2      1048kB  1049kB  512B                primary</span><br><span class="line"> 1      1049kB  107GB   107GB               primary</span><br></pre></td></tr></table></figure><ul><li><p><code>quit</code> 退出Parted工具</p></li><li><p><code>partprobe</code> 系统重读分区表</p></li><li><p><code>mkfs -t ext4 /dev/vdb1</code> 创建一个ext4文件系统</p></li><li><p><code>mkdir /mnt/data</code> 创建挂载点</p></li><li><p><code>echo `blkid /dev/vdb1 | awk &#39;{print $2}&#39; | sed &#39;s/\&quot;//g&#39;` /mnt/data ext4 defaults 0 0 &gt;&gt; /etc/fstab</code> 向 <code>/etc/fstab</code> 里写入新分区信息以便自动挂载</p></li><li><p><code>cat /etc/fstab</code> 查看是否出现了写入的新分区信息(UUID是新的)</p></li></ul><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line">root@ubuntu:~# <span class="built_in">cat</span> /etc/fstab</span><br><span class="line">LABEL=cloudimg-rootfs   /        ext4   discard,commit=30,errors=remount-ro     0 1</span><br><span class="line">LABEL=UEFI      /boot/efi       vfat    <span class="built_in">umask</span>=0077      0 1</span><br><span class="line">UUID=1403e86a-8d80-4be6-8e72-43817b0f6c1d /mnt/data ext4 defaults 0 0</span><br></pre></td></tr></table></figure><ul><li><code>systemctl daemon-reload &amp;&amp; mount -a</code> 配置生效</li><li><code>df -h</code> 查看目前磁盘空间和使用情况</li></ul><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br></pre></td><td class="code"><pre><span class="line">root@ubuntu:~# <span class="built_in">df</span> -h</span><br><span class="line">Filesystem      Size  Used Avail Use% Mounted on</span><br><span class="line">tmpfs           187M 1008K  186M   1% /run</span><br><span class="line">efivarfs        256K   17K  235K   7% /sys/firmware/efi/efivars</span><br><span class="line">/dev/vda2        39G  1.8G   36G   5% /</span><br><span class="line">tmpfs           935M     0  935M   0% /dev/shm</span><br><span class="line">tmpfs           5.0M     0  5.0M   0% /run/lock</span><br><span class="line">/dev/vda1       100M  6.2M   94M   7% /boot/efi</span><br><span class="line">tmpfs           187M   12K  187M   1% /run/user/0</span><br><span class="line">/dev/vdb1        98G   24K   93G   1% /mnt/data</span><br></pre></td></tr></table></figure><h3 id="安装Docker"><a href="#安装Docker" class="headerlink" title="安装Docker"></a>安装Docker</h3><p>我希望这台服务器上大部分服务都使用Docker部署，不会扰乱宿主机的环境。</p><p>官方的文档地址：<a class="link"   href="https://docs.docker.com/engine/install/ubuntu/" >https://docs.docker.com/engine/install/ubuntu/<i class="fas fa-external-link-alt"></i></a></p><h4 id="添加-Docker-官方的存储库"><a href="#添加-Docker-官方的存储库" class="headerlink" title="添加 Docker 官方的存储库"></a>添加 Docker 官方的存储库</h4><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br></pre></td><td class="code"><pre><span class="line"><span class="built_in">sudo</span> apt-get update</span><br><span class="line"><span class="built_in">sudo</span> apt-get install ca-certificates curl</span><br><span class="line"><span class="built_in">sudo</span> install -m 0755 -d /etc/apt/keyrings</span><br><span class="line"><span class="built_in">sudo</span> curl -fsSL https://download.docker.com/linux/ubuntu/gpg -o /etc/apt/keyrings/docker.asc</span><br><span class="line"><span class="built_in">sudo</span> <span class="built_in">chmod</span> a+r /etc/apt/keyrings/docker.asc</span><br><span class="line"></span><br><span class="line"><span class="built_in">echo</span> \</span><br><span class="line"><span class="string">&quot;deb [arch=<span class="subst">$(dpkg --print-architecture)</span> signed-by=/etc/apt/keyrings/docker.asc] https://download.docker.com/linux/ubuntu \</span></span><br><span class="line"><span class="string"><span class="subst">$(. /etc/os-release &amp;&amp; echo <span class="string">&quot;<span class="variable">$&#123;UBUNTU_CODENAME:-<span class="variable">$VERSION_CODENAME</span>&#125;</span>&quot;</span>)</span> stable&quot;</span> | \</span><br><span class="line"><span class="built_in">sudo</span> <span class="built_in">tee</span> /etc/apt/sources.list.d/docker.list &gt; /dev/null</span><br><span class="line"></span><br><span class="line"><span class="built_in">sudo</span> apt-get update</span><br></pre></td></tr></table></figure><h4 id="通过-APT-安装-Docker"><a href="#通过-APT-安装-Docker" class="headerlink" title="通过 APT 安装 Docker"></a>通过 APT 安装 Docker</h4><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="built_in">sudo</span> apt-get install docker-ce docker-ce-cli containerd.io docker-buildx-plugin docker-compose-plugin -y</span><br></pre></td></tr></table></figure><h4 id="将-Docker-迁移到数据盘"><a href="#将-Docker-迁移到数据盘" class="headerlink" title="将 Docker 迁移到数据盘"></a>将 Docker 迁移到数据盘</h4><p>上面提到数据盘挂载在 <code>/mnt/data</code>，需要将 Docker 的数据目录迁移到这里</p><p><strong>⚠️ 我这里是完全没有使用过的 Docker 环境，所以可以直接切换目录；如果是已经创建过容器或者 pull 过的，需要将旧数据迁移到新目录</strong></p><ul><li><code>mkdir /mnt/data/docker</code> 在数据盘上创建 Docker 目录</li><li><code>sudo systemctl stop docker</code> 停止 Docker 服务</li><li><code>sudo vim /etc/docker/daemon.json</code> 修改 Docker 配置文件</li></ul><p>将配置文件改为：</p><figure class="highlight json"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line"><span class="punctuation">&#123;</span></span><br><span class="line">    <span class="attr">&quot;data-root&quot;</span><span class="punctuation">:</span> <span class="string">&quot;/mnt/docker&quot;</span></span><br><span class="line"><span class="punctuation">&#125;</span></span><br></pre></td></tr></table></figure><ul><li><code>sudo systemctl daemon-reload</code></li><li><code>sudo systemctl restart docker</code> 重启 Docker 服务</li><li><code>docker info | grep Root</code> 查看 Docker 路径</li></ul><p>应该输出为设置的新路径</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">root@ubuntu:~# docker info | grep Root</span><br><span class="line"> Docker Root Dir: /mnt/data/docker</span><br></pre></td></tr></table></figure><h4 id="安装-Dockge"><a href="#安装-Dockge" class="headerlink" title="安装 Dockge"></a>安装 Dockge</h4><p>Dockge 是一个Docker-Compose 配置文件管理工具，项目地址：<a class="link"   href="https://github.com/louislam/dockge" >https://github.com/louislam/dockge<i class="fas fa-external-link-alt"></i></a></p><p>安装方法：</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment"># Create directories that store your stacks and stores Dockge&#x27;s stack</span></span><br><span class="line"><span class="built_in">mkdir</span> -p /opt/stacks /opt/dockge</span><br><span class="line"><span class="built_in">cd</span> /opt/dockge</span><br><span class="line"></span><br><span class="line"><span class="comment"># Download the compose.yaml</span></span><br><span class="line">curl https://raw.githubusercontent.com/louislam/dockge/master/compose.yaml --output compose.yaml</span><br><span class="line"></span><br><span class="line"><span class="comment"># Start the server</span></span><br><span class="line">docker compose up -d</span><br><span class="line"></span><br><span class="line"><span class="comment"># If you are using docker-compose V1 or Podman</span></span><br><span class="line"><span class="comment"># docker-compose up -d</span></span><br></pre></td></tr></table></figure><h3 id="网络优化"><a href="#网络优化" class="headerlink" title="网络优化"></a>网络优化</h3><h4 id="换源"><a href="#换源" class="headerlink" title="换源"></a>换源</h4><p>因为是阿里云的服务器嘛，肯定要用阿里源啦</p><p>阿里源文档地址：<a class="link"   href="https://developer.aliyun.com/mirror/ubuntu" >https://developer.aliyun.com/mirror/ubuntu<i class="fas fa-external-link-alt"></i></a></p><p>Ubuntu 24.04 先打开<code>/etc/apt/sources.list.d/ubuntu.sources</code></p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">vi /etc/apt/sources.list.d/ubuntu.sources   </span><br></pre></td></tr></table></figure><p>替换为下面的内容：</p><p>下面是<strong>Ubuntu 24.04</strong>的阿里源，其他请参考文档</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line">Types: deb deb-src</span><br><span class="line">URIs: https://mirrors.aliyun.com/ubuntu/</span><br><span class="line">Suites: noble noble-security noble-updates noble-proposed noble-backports</span><br><span class="line">Components: main restricted universe multiverse</span><br><span class="line">Signed-By: /usr/share/keyrings/ubuntu-archive-keyring.gpg</span><br></pre></td></tr></table></figure><hr><p>如果是<strong>24.04以下</strong>版本的，打开<code>/etc/apt/sources.list</code></p><p>以下为 22.04 LTS 的阿里源</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br></pre></td><td class="code"><pre><span class="line">deb https://mirrors.aliyun.com/ubuntu/ jammy main restricted universe multiverse</span><br><span class="line">deb-src https://mirrors.aliyun.com/ubuntu/ jammy main restricted universe multiverse</span><br><span class="line"></span><br><span class="line">deb https://mirrors.aliyun.com/ubuntu/ jammy-security main restricted universe multiverse</span><br><span class="line">deb-src https://mirrors.aliyun.com/ubuntu/ jammy-security main restricted universe multiverse</span><br><span class="line"></span><br><span class="line">deb https://mirrors.aliyun.com/ubuntu/ jammy-updates main restricted universe multiverse</span><br><span class="line">deb-src https://mirrors.aliyun.com/ubuntu/ jammy-updates main restricted universe multiverse</span><br><span class="line"></span><br><span class="line"><span class="comment"># deb https://mirrors.aliyun.com/ubuntu/ jammy-proposed main restricted universe multiverse</span></span><br><span class="line"><span class="comment"># deb-src https://mirrors.aliyun.com/ubuntu/ jammy-proposed main restricted universe multiverse</span></span><br><span class="line"></span><br><span class="line">deb https://mirrors.aliyun.com/ubuntu/ jammy-backports main restricted universe multiverse</span><br><span class="line">deb-src https://mirrors.aliyun.com/ubuntu/ jammy-backports main restricted universe multiverse</span><br></pre></td></tr></table></figure><h4 id="启用-BBR-拥塞控制算法"><a href="#启用-BBR-拥塞控制算法" class="headerlink" title="启用 BBR 拥塞控制算法"></a>启用 BBR 拥塞控制算法</h4><blockquote><p>BBR（Bottleneck Bandwidth and Round-trip propagation time） 是 Google 开发的一个 拥塞控制算法，它旨在最大化带宽利用率并减少网络拥塞延迟。<br>它是 Linux 内核中的一种 TCP 拥塞控制方式，用于替代传统的 Reno、CUBIC 等算法。传统的 TCP 拥塞控制算法基于丢包作为网络拥塞信号。而 BBR 是基于带宽和延迟的估算，不再依赖丢包。</p></blockquote><p>BBR 算法有一些魔改变体，在这篇博客中有进行对比评测：<a class="link"   href="https://roov.org/2020/03/bbr-bbrplus-bbr2/" >https://roov.org/2020/03/bbr-bbrplus-bbr2/<i class="fas fa-external-link-alt"></i></a></p><p>我个人还是比较喜欢原板的BBR算法，下面说明如何开启：</p><blockquote><p>参考文献：<a class="link"   href="https://www.sysgeek.cn/enable-bbr-on-ubuntu/" >https://www.sysgeek.cn/enable-bbr-on-ubuntu/<i class="fas fa-external-link-alt"></i></a></p></blockquote><p>查看目前的 TCP 控制算法</p><ul><li><code>sysctl net.ipv4.tcp_congestion_control</code></li></ul><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">root@ubuntu:~# sysctl net.ipv4.tcp_congestion_control</span><br><span class="line">net.ipv4.tcp_congestion_control = cubic</span><br></pre></td></tr></table></figure><p>查看系统是否支持 BBR，若支持则无任何输出</p><ul><li><code>sudo modprobe tcp_bbr</code></li></ul><p>使用下面命令设置 <code>fq</code>（Fair Queuing，公平排队）作为默认排队规则，BBR 作为拥塞控制算法.</p><ul><li><code>sudo sh -c &#39;echo &quot;net.core.default_qdisc=fq&quot; &gt;&gt; /etc/sysctl.conf&#39;</code></li><li><code>sudo sh -c &#39;echo &quot;net.ipv4.tcp_congestion_control=bbr&quot; &gt;&gt; /etc/sysctl.conf&#39;</code></li></ul><p>重新加载配置以生效：</p><ul><li><code>sudo sysctl -p</code></li></ul><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line">root@ubuntu:~# <span class="built_in">sudo</span> sysctl -p</span><br><span class="line">net.ipv4.ping_group_range = 0 2147483647</span><br><span class="line">net.core.default_qdisc = fq</span><br><span class="line">net.ipv4.tcp_congestion_control = bbr</span><br></pre></td></tr></table></figure><p>再次查看目前的 TCP 控制算法</p><ul><li><code>sysctl net.ipv4.tcp_congestion_control</code></li></ul><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">root@ubuntu:~# sysctl net.ipv4.tcp_congestion_control</span><br><span class="line">net.ipv4.tcp_congestion_control = bbr</span><br></pre></td></tr></table></figure><h3 id="开启虚拟内存SWAP"><a href="#开启虚拟内存SWAP" class="headerlink" title="开启虚拟内存SWAP"></a>开启虚拟内存SWAP</h3><p>因为 VPS 只有 2G 内存，需要开启 SWAP 保证内存不会溢出导致系统彻底卡死</p><ul><li><p>创建 swap 文件（4GB）：<code>sudo fallocate -l 4G /swapfile</code></p></li><li><p>设置合适的权限：<code>sudo chmod 600 /swapfile</code></p></li><li><p>格式化为 swap：<code>sudo mkswap /swapfile</code></p></li><li><p>启用 swap：<code>sudo swapon /swapfile</code></p></li><li><p>查看是否成功启用：<code>free -h</code></p>  <figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line">root@ubuntu:/opt# free -h</span><br><span class="line">            total        used        free      shared  buff/cache   available</span><br><span class="line">Mem:           1.8Gi       617Mi       236Mi       3.1Mi       1.2Gi       1.2Gi</span><br><span class="line">Swap:          4.0Gi          0B       4.0Gi</span><br></pre></td></tr></table></figure></li><li><p>开机自动挂载：<code>sudo nano /etc/fstab</code>，在最后追加：</p>  <figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">/swapfile none swap sw 0 0</span><br></pre></td></tr></table></figure></li></ul><h2 id="安装Web服务器"><a href="#安装Web服务器" class="headerlink" title="安装Web服务器"></a>安装Web服务器</h2><p>因为我的服务器希望可以通过不同的子域名访问不同的服务，所以需要部署一个Web服务器进行反向代理。</p><p>我这里选择的是 <a class="link"   href="https://caddyserver.com/docs/install" >Caddy<i class="fas fa-external-link-alt"></i></a>，不用 Nginx 是因为他需要搭配别的项目一起才能解决 HTTPS 证书续签的问题；而且 Nginx 本身的配置文件也较为复杂。</p><h3 id="安装Caddy"><a href="#安装Caddy" class="headerlink" title="安装Caddy"></a>安装Caddy</h3><p>命令如下：</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line"><span class="built_in">sudo</span> apt install -y debian-keyring debian-archive-keyring apt-transport-https curl</span><br><span class="line">curl -1sLf <span class="string">&#x27;https://dl.cloudsmith.io/public/caddy/stable/gpg.key&#x27;</span> | <span class="built_in">sudo</span> gpg --dearmor -o /usr/share/keyrings/caddy-stable-archive-keyring.gpg</span><br><span class="line">curl -1sLf <span class="string">&#x27;https://dl.cloudsmith.io/public/caddy/stable/debian.deb.txt&#x27;</span> | <span class="built_in">sudo</span> <span class="built_in">tee</span> /etc/apt/sources.list.d/caddy-stable.list</span><br><span class="line"><span class="built_in">sudo</span> apt update</span><br><span class="line"><span class="built_in">sudo</span> apt install caddy</span><br></pre></td></tr></table></figure><h3 id="设置Caddy配置文件"><a href="#设置Caddy配置文件" class="headerlink" title="设置Caddy配置文件"></a>设置Caddy配置文件</h3><h4 id="反向代理"><a href="#反向代理" class="headerlink" title="反向代理"></a>反向代理</h4><p>Caddy的配置文件在 <code>/etc/caddy/Caddyfile</code></p><p>以用的最多的普通反向代理为例，如果要将域名 <code>test.example.com</code> 反向代理到 <code>127.0.0.1:5000</code>，在 <code>Caddyfile</code> 中需要添加：</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line">test.example.com &#123;</span><br><span class="line">    reverse_proxy 127.0.0.1:5000</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p><code>sudo caddy reload</code> 加载新的配置</p><p>然后Caddy会自动帮你搞定证书的事情，直接访问 <code>https://test.example.com</code> 即可。</p><p><strong>备注：如果是特殊的项目，如 哪吒探针、Uptime Kuma 等项目，利用了 WebSocket 等协议的项目反向代理需要特殊设置。</strong></p><h4 id="重定向"><a href="#重定向" class="headerlink" title="重定向"></a>重定向</h4><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line">test.example.com &#123;</span><br><span class="line">    redir http://xx.xx.21.xx:80&#123;uri&#125; 301</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>同样需要 <code>sudo caddy reload</code> 加载新的配置。</p><h2 id="测速服务-Speedtest-x"><a href="#测速服务-Speedtest-x" class="headerlink" title="测速服务 Speedtest-x"></a>测速服务 Speedtest-x</h2><p>测速的开源项目有3个：</p><ul><li><a class="link"   href="https://github.com/librespeed/speedtest" >LibreSpeed<i class="fas fa-external-link-alt"></i></a></li><li><a class="link"   href="https://github.com/BadApple9/speedtest-x" >speedtest-x<i class="fas fa-external-link-alt"></i></a></li><li><a class="link"   href="https://github.com/openspeedtest/Speed-Test" >OpenSpeedTest<i class="fas fa-external-link-alt"></i></a></li></ul><p>speedtest-x 是在 LibreSpeed 基础上开发的，界面相对好看一丢丢</p><p>LibreSpeed可以自定义站点标题，知名测速网站 <a class="link"   href="https://test.ustc.edu.cn/" >中国科学技术大学测速网站<i class="fas fa-external-link-alt"></i></a> 是在 LibreSpeed 上修改的</p><p>我这里部署的是 speedtest-x，采用下面的 docker 命令进行部署：</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line">docker run -d \</span><br><span class="line">  --restart unless-stopped \</span><br><span class="line">  -p 23456:80 \</span><br><span class="line">  --name speedtest-x \</span><br><span class="line">  badapple9/speedtest-x</span><br></pre></td></tr></table></figure><p>然后可以使用 <code>IP:PORT</code> 进行访问测速，需要注意的是采用Caddy进行反向代理后可能会出现测速远远不如IP访问的问题，参考这个 <a class="link"   href="https://github.com/MortyFx/speedtest-x/issues/77" >issure<i class="fas fa-external-link-alt"></i></a>，原因是因为 <strong>当地运营商 QOS 限速所致，煞笔电信！！！</strong>。</p><h2 id="对象存储-MinIO"><a href="#对象存储-MinIO" class="headerlink" title="对象存储 MinIO"></a>对象存储 MinIO</h2><p>MinIO 已经叛变了开源社区，变成了一个闭源商业软件，不再推荐使用。</p><h2 id="MySQL-redis"><a href="#MySQL-redis" class="headerlink" title="MySQL + redis"></a>MySQL + redis</h2><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br></pre></td><td class="code"><pre><span class="line">networks:</span><br><span class="line">  infra-net:</span><br><span class="line">    name: infra-net</span><br><span class="line">    external: false</span><br><span class="line">services:</span><br><span class="line">  mysql:</span><br><span class="line">    image: mysql:8.2</span><br><span class="line">    container_name: mysql</span><br><span class="line">    restart: always</span><br><span class="line">    environment:</span><br><span class="line">      MYSQL_ROOT_PASSWORD: Fwm8848@</span><br><span class="line">      MYSQL_DATABASE: shared_db</span><br><span class="line">    ports:</span><br><span class="line">      - 3306:3306</span><br><span class="line">    networks:</span><br><span class="line">      - infra-net</span><br><span class="line">    healthcheck:</span><br><span class="line">      test:</span><br><span class="line">        - CMD</span><br><span class="line">        - mysqladmin</span><br><span class="line">        - ping</span><br><span class="line">        - -h</span><br><span class="line">        - localhost</span><br><span class="line">      interval: 10s</span><br><span class="line">      timeout: 5s</span><br><span class="line">      retries: 3</span><br><span class="line">    volumes:</span><br><span class="line">      - mysql-data:/var/lib/mysql # 数据持久化</span><br><span class="line">  redis:</span><br><span class="line">    image: redis:latest</span><br><span class="line">    container_name: redis</span><br><span class="line">    restart: always</span><br><span class="line">    ports:</span><br><span class="line">      - 6379:6379 # 映射物理机端口</span><br><span class="line">    networks:</span><br><span class="line">      - infra-net</span><br><span class="line">    healthcheck:</span><br><span class="line">      test:</span><br><span class="line">        - CMD</span><br><span class="line">        - redis-cli</span><br><span class="line">        - ping</span><br><span class="line">      interval: 10s</span><br><span class="line">      timeout: 5s</span><br><span class="line">      retries: 3</span><br><span class="line">    volumes:</span><br><span class="line">      - redis-data:/data # Redis 持久化挂载</span><br><span class="line">volumes:</span><br><span class="line">  mysql-data: null</span><br><span class="line">  redis-data: null</span><br></pre></td></tr></table></figure>]]>
    </content>
    <id>https://x4ai.cn/2025/08/26/VPS%E5%B8%B8%E7%94%A8%E6%9C%8D%E5%8A%A1%E9%83%A8%E7%BD%B2/</id>
    <link href="https://x4ai.cn/2025/08/26/VPS%E5%B8%B8%E7%94%A8%E6%9C%8D%E5%8A%A1%E9%83%A8%E7%BD%B2/"/>
    <published>2025-08-26T08:58:06.000Z</published>
    <summary>记录VPS常用服务的部署方法，包括系统重装、跑分、数据盘挂载、Docker安装及网络优化等。</summary>
    <title>VPS常用服务部署</title>
    <updated>2026-06-06T02:01:51.544Z</updated>
  </entry>
  <entry>
    <author>
      <name>迎风起降</name>
    </author>
    <category term="运维" scheme="https://x4ai.cn/categories/%E8%BF%90%E7%BB%B4/"/>
    <category term="HomeIDC" scheme="https://x4ai.cn/tags/HomeIDC/"/>
    <category term="PDU" scheme="https://x4ai.cn/tags/PDU/"/>
    <content>
      <![CDATA[<h1 id="介绍"><a href="#介绍" class="headerlink" title="介绍"></a>介绍</h1><h2 id="外观"><a href="#外观" class="headerlink" title="外观"></a>外观</h2><img                           lazyload                       alt="image"                       data-src="https://img.102465.xyz/file/mruhZVt3.png"                         alt="image.png" width=100%                  ><p><img                         lazyload                       alt="image"                       data-src="https://www.servethehome.com/wp-content/uploads/2021/04/Schneider-Electric-APC-7921B-Front.jpg"                                        ></p><h2 id="尺寸"><a href="#尺寸" class="headerlink" title="尺寸"></a>尺寸</h2><img                           lazyload                       alt="image"                       data-src="https://img.102465.xyz/file/5qsyE0Tr.png"                         alt="image.png" width=100%                  ><hr><h2 id="接口"><a href="#接口" class="headerlink" title="接口"></a>接口</h2><p>8个C13接口</p><p>空载功率为8W</p><h1 id="配置"><a href="#配置" class="headerlink" title="配置"></a>配置</h1><h2 id="网络配置"><a href="#网络配置" class="headerlink" title="网络配置"></a>网络配置</h2><p>通电后大概10s会听到断路器“咔”一声，就是开始上电；等待40s左右网口右下角的状态灯变成绿色，就是初始化完成了。</p><img                           lazyload                       alt="image"                       data-src="https://img.102465.xyz/file/iglSa5Ec.png"                         alt="image.png" width=100%                  ><p>因为不知道设备的前任对PDU做了什么配置，使用牙签或者回形针戳Reset小孔<strong>长按</strong>5s左右，可以看到状态灯开始黄绿交替闪烁；再快速<strong>短戳</strong>一下Reset，状态灯熄灭，系统开始初始化；再次亮起后重置成功。</p><p>根据<a class="link"   href="https://download.schneider-electric.com/files?p_enDocType=User+guide&p_File_Name=ASTE-6Z6KAM_R0_EN.pdf&p_Doc_Ref=SPD_ASTE-6Z6KAM_EN" >用户手册<i class="fas fa-external-link-alt"></i></a>说明，PDU有下面几种配置网络方法：</p><ul><li><p><strong>APC Device IP Configuration Wizard</strong></p><ul><li><p>在 Windows 电脑上运行<a class="link"   href="https://www.apc.com/us/en/download/document/MFOI-AXRPRY/" >网络配置工具<i class="fas fa-external-link-alt"></i></a>，自动发现并配置 PDU 的 IP、子网掩码和网关。</p></li><li><p>默认用户名&#x2F;密码是 <strong>apc &#x2F; apc</strong>。</p></li></ul></li><li><p><strong>DHCP&#x2F;BOOTP</strong></p><ul><li>如果网络里有 DHCP 服务器，PDU 插上网线后会自动获取 IP。</li></ul></li><li><p><strong>串口配置（本地直连）</strong></p><ul><li>用随机附带的配置线（RJ-11 → 串口）连到电脑，打开终端（9600bps，8-N-1），用 apc&#x2F;apc 登录，设置 IP、掩码、网关。</li></ul></li><li><p><strong>ARP + Ping 配置（远程）</strong></p><ul><li>在同一子网电脑上用 <code>arp -s</code> 命令绑定 MAC → IP，然后发一个 113 字节的 ping 包，PDU 会采用这个 IP。</li></ul></li></ul><p>但是实际上，APC Device IP Configuration Wizard、DHCP在我这都没有起效，然后我也没有串口线，最后是采用<strong>ARP + Ping 配置</strong>配置的，具体步骤如下：</p><h3 id="macOS-Linux"><a href="#macOS-Linux" class="headerlink" title="macOS &#x2F; Linux"></a>macOS &#x2F; Linux</h3><pre><code>sudo arp -s 192.168.123.50 00:c0:b7:8a:a2:b7ping 192.168.123.50 -s 113</code></pre><ul><li><p><code>192.168.123.50</code> 是你想分配给 PDU 的新 IP（改成你网段里空闲的）。</p></li><li><p><code>00:c0:b7:8a:a2:b7</code> 是 PDU 的 MAC 地址，必须改成你设备底部贴纸上的真实值。</p></li><li><p><code>ping -s 113</code> 的 <code>-s</code> 在 macOS&#x2F;Linux 表示 payload size（113 字节）。</p></li></ul><h3 id="Windows"><a href="#Windows" class="headerlink" title="Windows"></a>Windows</h3><pre><code>arp -s 192.168.1.50 00-c0-b7-63-9f-67ping 192.168.1.50 -l 113</code></pre><p>观察ping是否已经通，通了就访问上面设置的IP，我这里就是<code>192.168.123.50</code> ：</p><img                           lazyload                       alt="image"                       data-src="https://img.102465.xyz/file/pGLD2eGs.png"                         alt="image.png" width=100%                  ><p><strong>默认账密：apc&#x2F;apc</strong></p>]]>
    </content>
    <id>https://x4ai.cn/2025/08/25/APC-AP7921%E6%9C%BA%E6%9F%9CPDU%E9%85%8D%E7%BD%AE%E7%BD%91%E7%BB%9C/</id>
    <link href="https://x4ai.cn/2025/08/25/APC-AP7921%E6%9C%BA%E6%9F%9CPDU%E9%85%8D%E7%BD%AE%E7%BD%91%E7%BB%9C/"/>
    <published>2025-08-25T06:20:39.000Z</published>
    <summary>本文记录如何配置 APC AP7921 机柜 PDU 的网络，包括通过 ARP + Ping 配置 IP 的详细步骤。</summary>
    <title>APC AP7921 机柜 PDU 配置网络</title>
    <updated>2026-06-06T02:01:51.544Z</updated>
  </entry>
  <entry>
    <author>
      <name>迎风起降</name>
    </author>
    <category term="学习笔记" scheme="https://x4ai.cn/categories/%E5%AD%A6%E4%B9%A0%E7%AC%94%E8%AE%B0/"/>
    <category term="Spark" scheme="https://x4ai.cn/tags/Spark/"/>
    <content>
      <![CDATA[<h1 id="SSH-JDK部署"><a href="#SSH-JDK部署" class="headerlink" title="SSH &amp; JDK部署"></a>SSH &amp; JDK部署</h1><h2 id="互联互通"><a href="#互联互通" class="headerlink" title="互联互通"></a>互联互通</h2><p>在哥们(hhh)的帮助下，搞到四台阿里云服务器，不过挺寒碜的配置：</p><table><thead><tr><th align="center">编号</th><th align="center">IP</th><th align="center">用户名</th><th align="center">密码</th><th align="center">系统配置</th><th align="center">备注</th></tr></thead><tbody><tr><td align="center">1</td><td align="center">8.219.xx0.46</td><td align="center">root</td><td align="center">—-</td><td align="center">Ubuntu 22.04 | 2vCPU&#x2F;4GiB</td><td align="center">主节点</td></tr><tr><td align="center">2</td><td align="center">4x.236.2x.161</td><td align="center">root</td><td align="center">—-</td><td align="center">Ubuntu 22.04 | 2vCPU&#x2F;1GiB</td><td align="center">从节点</td></tr><tr><td align="center">3</td><td align="center">4x.236.15x.1x2</td><td align="center">root</td><td align="center">—-</td><td align="center">Ubuntu 22.04 | 2vCPU&#x2F;2GiB</td><td align="center">从节点</td></tr><tr><td align="center">4</td><td align="center">47.2x6.x15.x57</td><td align="center">root</td><td align="center">—-</td><td align="center">Ubuntu 22.04 | 2vCPU&#x2F;1GiB</td><td align="center">客户端</td></tr></tbody></table><p>写了一个shell脚本，在四台服务器上创建用户<code>dase-dis</code>（注意确保四台服务器的用户名和密码一致才可以使用）:</p><p>先<code>sudo apt install sshpass</code>，在Linux下执行脚本：</p><figure class="highlight sh"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">#!/bin/bash</span></span><br><span class="line"></span><br><span class="line"><span class="comment"># 服务器IP地址列表</span></span><br><span class="line">ip_list=(<span class="string">&quot;xxxx&quot;</span> <span class="string">&quot;xxxx&quot;</span> <span class="string">&quot;xxxx&quot;</span> <span class="string">&quot;xxxx&quot;</span>)</span><br><span class="line"></span><br><span class="line"><span class="comment"># 设置统一的密码</span></span><br><span class="line">password=<span class="string">&quot;admin&quot;</span></span><br><span class="line"></span><br><span class="line"><span class="comment"># 循环遍历IP地址列表</span></span><br><span class="line"><span class="keyword">for</span> ip <span class="keyword">in</span> <span class="string">&quot;<span class="variable">$&#123;ip_list[@]&#125;</span>&quot;</span></span><br><span class="line"><span class="keyword">do</span></span><br><span class="line">    <span class="built_in">echo</span> <span class="string">&quot;Connecting to <span class="variable">$ip</span>...&quot;</span></span><br><span class="line"></span><br><span class="line">    <span class="comment"># 连接服务器并创建用户</span></span><br><span class="line">    sshpass -p <span class="string">&quot;<span class="variable">$ssh_password</span>&quot;</span> ssh -o StrictHostKeyChecking=no root@<span class="variable">$ip</span> &lt;&lt; <span class="string">EOF</span></span><br><span class="line"><span class="string">        # 创建用户并设置密码</span></span><br><span class="line"><span class="string">        useradd -m -s /bin/bash dase-dis</span></span><br><span class="line"><span class="string">        echo &quot;dase-dis:$password&quot; | chpasswd</span></span><br><span class="line"><span class="string">        # 添加用户到sudo组</span></span><br><span class="line"><span class="string">        usermod -aG sudo dase-dis</span></span><br><span class="line"><span class="string">EOF</span></span><br><span class="line"></span><br><span class="line">    <span class="built_in">echo</span> <span class="string">&quot;User dase-dis created on <span class="variable">$ip</span> with password <span class="variable">$password</span>&quot;</span></span><br><span class="line"><span class="keyword">done</span></span><br></pre></td></tr></table></figure><p>执行结果：</p><p><img                         lazyload                       alt="image"                       data-src="https://cdn.jsdelivr.net/gh/fengwm64/imgs/imgs/202404282346549.png"                                        ></p><hr><p>实现四台服务器之间ssh免密登录</p><h3 id="安装openssh"><a href="#安装openssh" class="headerlink" title="安装openssh"></a>安装openssh</h3><p>在四台服务器上执行</p><ul><li><code>sudo apt-get install openssh-server</code> 安装openssh</li></ul><h3 id="更改主机名"><a href="#更改主机名" class="headerlink" title="更改主机名"></a>更改主机名</h3><p>在1号机（主节点，在文章开头编号1）执行：</p><ul><li><code>sudo hostnamectl set-hostname ecnu01</code> 更改主机名</li></ul><p>在2号机（从节点，在文章开头编号2）执行：</p><ul><li><code>sudo hostnamectl set-hostname ecnu02</code></li></ul><p>…以此类推</p><ul><li><p><code>sudo hostnamectl set-hostname ecnu03</code></p></li><li><p><code>sudo hostnamectl set-hostname ecnu04</code></p></li></ul><p>四台服务器都执行完毕后，断开ssh重新连接，观察到主机名字已经成功更改</p><p><img                         lazyload                       alt="image"                       data-src="https://cdn.jsdelivr.net/gh/fengwm64/imgs/imgs/202404292322849.png"                                        ></p><h3 id="更改hosts"><a href="#更改hosts" class="headerlink" title="更改hosts"></a>更改hosts</h3><p>原理：</p><blockquote><p><code>Hosts</code> 文件是本地计算机上的文本文件，用于将主机名与 <code>IP</code> 地址关联起来,绕过 <code>DNS</code> 解析。<code>Linux hosts</code> 文件的格式通常是：</p><p><code>IP地址    主机名    [别名...]</code></p><p>在 <code>/etc/hosts</code> 路径下，每行代表一个主机名到 <code>IP</code> 地址的映射。例如：</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line">127.0.0.1   localhost</span><br><span class="line">::1         localhost</span><br><span class="line">192.168.1.2 example.com</span><br></pre></td></tr></table></figure><p>其中，127.0.0.1 和 ::1 映射到 localhost，192.168.1.2 映射到 example.com。hosts 文件允许手动指定主机名与 IP 地址的对应关系，用于特定网络配置和测试。</p></blockquote><p><strong>开始修改：</strong></p><p>在四台机上执行以下操作：</p><ul><li><code>sudo vim /etc/hosts</code></li></ul><p>在<code>hosts</code>文件后追加（<strong>ip需要改成自己的哇</strong>）：</p><figure class="highlight sh"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment"># IP地址 主机名</span></span><br><span class="line">8.219.108.46 ecnu01</span><br><span class="line">47.236.20.161 ecnu02</span><br><span class="line">47.236.157.142 ecnu03</span><br><span class="line">47.236.115.157 ecnu04</span><br></pre></td></tr></table></figure><p><strong>!!!!!!!注意!!!!!!!!!</strong><br><strong>!!!!!!!注意!!!!!!!!!</strong><br><strong>!!!!!!!注意!!!!!!!!!</strong></p><p><strong>在云服务器配置时, 本机使用内网IP, 其余为公网IP</strong></p><p>查看内网IP:</p><p><img                         lazyload                       alt="image"                       data-src="https://cdn.jsdelivr.net/gh/fengwm64/imgs/imgs/202405021028165.png"                                        ></p><p>hosts数值示例:</p><p><img                         lazyload                       alt="image"                       data-src="https://cdn.jsdelivr.net/gh/fengwm64/imgs/imgs/202405021027955.png"                                        ></p><h3 id="拷贝ssh公钥"><a href="#拷贝ssh公钥" class="headerlink" title="拷贝ssh公钥"></a>拷贝ssh公钥</h3><p><strong>在所有机器依次执行下面命令：</strong></p><p>作用是将除主机外的三台机的ssh公钥拷贝到主中，实现其余三台机器到主机的ssh免密登录</p><ul><li><p><code>ssh-keygen -t rsa</code> 生成ssh密钥</p></li><li><p><code>ssh dase-dis@ecnu01 &#39;mkdir -p ~/.ssh &amp;&amp; cat &gt;&gt; ~/.ssh/authorized_keys&#39; &lt; ~/.ssh/id_rsa.pub</code> 发送公钥到主机</p></li><li><p><code>sudo service ssh restart &amp;&amp; chmod 700 ~/.ssh  &amp;&amp; chmod 600 ~/.ssh/authorized_keys</code> 重启本机ssh服务+解决ssh文件夹的权限问题</p></li></ul><p><img                         lazyload                       alt="image"                       data-src="https://cdn.jsdelivr.net/gh/fengwm64/imgs/imgs/202404292330151.png"                                        ></p><hr><p><strong>主机执行：</strong></p><p>作用是将主机的ssh认证拷贝到其余三台机中，实现其余三台机器之间的ssh免密登录</p><ul><li><p><code>scp ~/.ssh/authorized_keys dase-dis@ecnu02:/home/dase-dis/.ssh/authorized_keys</code></p></li><li><p><code>scp ~/.ssh/authorized_keys dase-dis@ecnu03:/home/dase-dis/.ssh/authorized_keys</code></p></li><li><p><code>scp ~/.ssh/authorized_keys dase-dis@ecnu04:/home/dase-dis/.ssh/authorized_keys</code></p></li></ul><p>上面的三条命令等价于命令：<code>for host in ecnu02 ecnu03 ecnu04; do scp ~/.ssh/authorized_keys dase-dis@$host:/home/dase-dis/.ssh/; done</code></p><p>然后主机执行：</p><p><code>sudo service ssh restart &amp;&amp; chmod 700 ~/.ssh  &amp;&amp; chmod 600 ~/.ssh/authorized_keys</code></p><p>运行结果：</p><p><img                         lazyload                       alt="image"                       data-src="https://cdn.jsdelivr.net/gh/fengwm64/imgs/imgs/202404292335551.png"                                        ></p><hr><p><strong>验证：</strong></p><p>互相ssh过去看看要不要输入密码</p><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line">ssh dase-dis@ecnu01</span><br><span class="line">exit</span><br><span class="line">ssh dase-dis@ecnu02</span><br><span class="line">exit</span><br><span class="line">ssh dase-dis@ecnu03</span><br><span class="line">exit</span><br><span class="line">ssh dase-dis@ecnu04</span><br><span class="line">exit</span><br></pre></td></tr></table></figure><h3 id="关闭防火墙"><a href="#关闭防火墙" class="headerlink" title="关闭防火墙"></a>关闭防火墙</h3><p>如果你是本地虚拟机:</p><ul><li><p><code>systemctl stop firewalld.service</code></p></li><li><p><code>systemctl disable firewalld.service </code></p></li></ul><p>如果你是云服务器:</p><p><strong>请确保你知道自己在干什么, 关闭防火墙(开放所有端口)可能导致服务器被入侵</strong></p><p><img                         lazyload                       alt="image"                       data-src="https://cdn.jsdelivr.net/gh/fengwm64/imgs/imgs/202405021144615.png"                                        ></p><h2 id="配置Java环境"><a href="#配置Java环境" class="headerlink" title="配置Java环境"></a>配置Java环境</h2><ul><li>JDK 1.8 <a class="link"   href="https://www.oracle.com/cn/java/technologies/javase/javase8-archive-downloads.html" >https://www.oracle.com/cn/java/technologies/javase/javase8-archive-downloads.html<i class="fas fa-external-link-alt"></i></a></li></ul><p>在四台机器上配置：</p><p><strong>可能你需要在上面oracle网站登陆后上手动找到下载地址，然后使用wget下载</strong></p><ul><li><p>下载：<code>wget https://download.oracle.com/otn/java/jdk/8u202-b08/1961070e4c9b4e26a04e7f5a083f551e/jdk-8u202-linux-x64.tar.gz</code></p></li><li><p>解压：<code>tar -zxvf jdk-8u202-linux-x64.tar.gz</code></p></li><li><p>环境变量配置：<code>sudo vi /etc/profile</code></p></li><li><p>添加以下内容：</p></li></ul><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta prompt_"># </span><span class="language-bash">路径自己配自己的</span></span><br><span class="line">export JAVA_HOME=/home/dase-dis/jdk1.8.0_202</span><br><span class="line">export CLASSPATH=.:$JAVA_HOME/lib/dt.jar:$JAVA_HOME/lib/tools.ja</span><br><span class="line">export PATH=$PATH:$JAVA_HOME/bin</span><br></pre></td></tr></table></figure><ul><li><p>刷新：<code>source /etc/profile</code></p></li><li><p>验证：<code>java -version</code></p></li></ul><p><img                         lazyload                       alt="image"                       data-src="https://cdn.jsdelivr.net/gh/fengwm64/imgs/imgs/202404301104527.png"                                        ></p><hr><h1 id="Hadoop-2-x部署"><a href="#Hadoop-2-x部署" class="headerlink" title="Hadoop 2.x部署"></a>Hadoop 2.x部署</h1><h2 id="下载"><a href="#下载" class="headerlink" title="下载"></a>下载</h2><p>地址：<a class="link"   href="https://archive.apache.org/dist/hadoop/common/hadoop-2.10.1/hadoop-2.10.1.tar.gz" >https://archive.apache.org/dist/hadoop/common/hadoop-2.10.1/hadoop-2.10.1.tar.gz<i class="fas fa-external-link-alt"></i></a></p><p><strong>在主节点上执行：</strong></p><ul><li><p>下载:<code>wget https://archive.apache.org/dist/hadoop/common/hadoop-2.10.1/hadoop-2.10.1.tar.gz</code></p></li><li><p>解压:<code>tar -zxvf hadoop-2.10.1.tar.gz</code></p></li></ul><p><img                         lazyload                       alt="image"                       data-src="https://cdn.jsdelivr.net/gh/fengwm64/imgs/imgs/202404301317432.png"                                        ></p><ul><li><p>进入文件夹:<code>cd ~/hadoop-2.10.1/</code></p></li><li><p>查看下载软件的版本:<code>./bin/hadoop version</code></p></li></ul><p><img                         lazyload                       alt="image"                       data-src="https://cdn.jsdelivr.net/gh/fengwm64/imgs/imgs/202404301319210.png"                                        ></p><h2 id="修改配置"><a href="#修改配置" class="headerlink" title="修改配置"></a>修改配置</h2><h3 id="修改slaves"><a href="#修改slaves" class="headerlink" title="修改slaves"></a>修改slaves</h3><p><strong>在主节点上执行：</strong></p><ul><li>修改 slaves 文件: <code>vim ~/hadoop-2.10.1/etc/hadoop/slaves</code></li></ul><p>修改为：</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">ecnu02</span><br><span class="line">ecnu03</span><br></pre></td></tr></table></figure><h3 id="修改core-site"><a href="#修改core-site" class="headerlink" title="修改core-site"></a>修改core-site</h3><ul><li>修改 core-site.xml: <code>vim ~/hadoop-2.10.1/etc/hadoop/core-site.xml</code></li></ul><figure class="highlight xml"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">&lt;?xml version=<span class="string">&quot;1.0&quot;</span> encoding=<span class="string">&quot;UTF-8&quot;</span>?&gt;</span></span><br><span class="line"><span class="meta">&lt;?xml-stylesheet type=<span class="string">&quot;text/xsl&quot;</span> href=<span class="string">&quot;configuration.xsl&quot;</span>?&gt;</span></span><br><span class="line"><span class="comment">&lt;!--</span></span><br><span class="line"><span class="comment">  Licensed under the Apache License, Version 2.0 (the &quot;License&quot;);</span></span><br><span class="line"><span class="comment">  you may not use this file except in compliance with the License.</span></span><br><span class="line"><span class="comment">  You may obtain a copy of the License at</span></span><br><span class="line"><span class="comment"></span></span><br><span class="line"><span class="comment">    http://www.apache.org/licenses/LICENSE-2.0</span></span><br><span class="line"><span class="comment"></span></span><br><span class="line"><span class="comment">  Unless required by applicable law or agreed to in writing, software</span></span><br><span class="line"><span class="comment">  distributed under the License is distributed on an &quot;AS IS&quot; BASIS,</span></span><br><span class="line"><span class="comment">  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.</span></span><br><span class="line"><span class="comment">  See the License for the specific language governing permissions and</span></span><br><span class="line"><span class="comment">  limitations under the License. See accompanying LICENSE file.</span></span><br><span class="line"><span class="comment">--&gt;</span></span><br><span class="line"></span><br><span class="line"><span class="comment">&lt;!-- Put site-specific property overrides in this file. --&gt;</span></span><br><span class="line"></span><br><span class="line"><span class="tag">&lt;<span class="name">configuration</span>&gt;</span></span><br><span class="line">  <span class="tag">&lt;<span class="name">property</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">name</span>&gt;</span>hadoop.tmp.dir<span class="tag">&lt;/<span class="name">name</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">value</span>&gt;</span>/home/dase-dis/hadoop-2.10.1/tmp<span class="tag">&lt;/<span class="name">value</span>&gt;</span></span><br><span class="line">  <span class="tag">&lt;/<span class="name">property</span>&gt;</span></span><br><span class="line">  <span class="comment">&lt;!--以下填写主节点主机名--&gt;</span></span><br><span class="line">  <span class="tag">&lt;<span class="name">property</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">name</span>&gt;</span>fs.defaultFS<span class="tag">&lt;/<span class="name">name</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">value</span>&gt;</span>hdfs://ecnu01:9999<span class="tag">&lt;/<span class="name">value</span>&gt;</span></span><br><span class="line">  <span class="tag">&lt;/<span class="name">property</span>&gt;</span></span><br><span class="line"><span class="tag">&lt;/<span class="name">configuration</span>&gt;</span></span><br></pre></td></tr></table></figure><p><img                         lazyload                       alt="image"                       data-src="https://cdn.jsdelivr.net/gh/fengwm64/imgs/imgs/202404301329297.png"                                        ></p><h3 id="修改hdfs-site"><a href="#修改hdfs-site" class="headerlink" title="修改hdfs-site"></a>修改hdfs-site</h3><ul><li>修改 hdfs-site.xml: <code>vim ~/hadoop-2.10.1/etc/hadoop/hdfs-site.xml</code></li></ul><figure class="highlight xml"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">&lt;?xml version=<span class="string">&quot;1.0&quot;</span> encoding=<span class="string">&quot;UTF-8&quot;</span>?&gt;</span></span><br><span class="line"><span class="meta">&lt;?xml-stylesheet type=<span class="string">&quot;text/xsl&quot;</span> href=<span class="string">&quot;configuration.xsl&quot;</span>?&gt;</span></span><br><span class="line"><span class="comment">&lt;!--</span></span><br><span class="line"><span class="comment">  Licensed under the Apache License, Version 2.0 (the &quot;License&quot;);</span></span><br><span class="line"><span class="comment">  you may not use this file except in compliance with the License.</span></span><br><span class="line"><span class="comment">  You may obtain a copy of the License at</span></span><br><span class="line"><span class="comment"></span></span><br><span class="line"><span class="comment">    http://www.apache.org/licenses/LICENSE-2.0</span></span><br><span class="line"><span class="comment"></span></span><br><span class="line"><span class="comment">  Unless required by applicable law or agreed to in writing, software</span></span><br><span class="line"><span class="comment">  distributed under the License is distributed on an &quot;AS IS&quot; BASIS,</span></span><br><span class="line"><span class="comment">  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.</span></span><br><span class="line"><span class="comment">  See the License for the specific language governing permissions and</span></span><br><span class="line"><span class="comment">  limitations under the License. See accompanying LICENSE file.</span></span><br><span class="line"><span class="comment">--&gt;</span></span><br><span class="line"></span><br><span class="line"><span class="comment">&lt;!-- Put site-specific property overrides in this file. --&gt;</span></span><br><span class="line"></span><br><span class="line"><span class="tag">&lt;<span class="name">configuration</span>&gt;</span></span><br><span class="line">  <span class="tag">&lt;<span class="name">property</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">name</span>&gt;</span>dfs.replication<span class="tag">&lt;/<span class="name">name</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">value</span>&gt;</span>2<span class="tag">&lt;/<span class="name">value</span>&gt;</span></span><br><span class="line">  <span class="tag">&lt;/<span class="name">property</span>&gt;</span></span><br><span class="line">  <span class="tag">&lt;<span class="name">property</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">name</span>&gt;</span>dfs.namenode.name.dir<span class="tag">&lt;/<span class="name">name</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">value</span>&gt;</span>file:/home/dase-dis/hadoop-2.10.1/tmp/dfs/name<span class="tag">&lt;/<span class="name">value</span>&gt;</span></span><br><span class="line">  <span class="tag">&lt;/<span class="name">property</span>&gt;</span></span><br><span class="line">  <span class="tag">&lt;<span class="name">property</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">name</span>&gt;</span>dfs.namenode.name.dir<span class="tag">&lt;/<span class="name">name</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">value</span>&gt;</span>file:/home/dase-dis/hadoop-2.10.1/tmp/dfs/name<span class="tag">&lt;/<span class="name">value</span>&gt;</span></span><br><span class="line">  <span class="tag">&lt;/<span class="name">property</span>&gt;</span></span><br><span class="line"><span class="tag">&lt;/<span class="name">configuration</span>&gt;</span></span><br></pre></td></tr></table></figure><h3 id="修改hadoop-env"><a href="#修改hadoop-env" class="headerlink" title="修改hadoop-env"></a>修改hadoop-env</h3><ul><li><p>修改 hadoop-env.sh: <code>vim ~/hadoop-2.10.1/etc/hadoop/hadoop-env.sh</code></p></li><li><p>将<code>JAVA_HOME</code>改为：</p></li></ul><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">export JAVA_HOME=/home/dase-dis/jdk1.8.0_202</span><br></pre></td></tr></table></figure><p><img                         lazyload                       alt="image"                       data-src="https://cdn.jsdelivr.net/gh/fengwm64/imgs/imgs/202404301434373.png"                                        ></p><h3 id="拷贝安装包"><a href="#拷贝安装包" class="headerlink" title="拷贝安装包"></a>拷贝安装包</h3><p>好了好了，终于改完了，接下来将改好的这份hadoop拷贝到其余三台机：</p><ul><li><p>拷贝到从节点1：<code>scp -r /home/dase-dis/hadoop-2.10.1 dase-dis@ecnu02:/home/dase-dis/</code></p></li><li><p>拷贝到从节点2：<code>scp -r /home/dase-dis/hadoop-2.10.1 dase-dis@ecnu03:/home/dase-dis/</code></p></li><li><p>拷贝到客户端：<code>scp -r /home/dase-dis/hadoop-2.10.1 dase-dis@ecnu04:/home/dase-dis/</code></p></li></ul><p>其实打包一下拷贝会更加好的，这里偷懒了</p><p><img                         lazyload                       alt="image"                       data-src="https://cdn.jsdelivr.net/gh/fengwm64/imgs/imgs/202404301352765.png"                                        ></p><h2 id="启动HDFS服务"><a href="#启动HDFS服务" class="headerlink" title="启动HDFS服务"></a>启动HDFS服务</h2><h3 id="格式化"><a href="#格式化" class="headerlink" title="格式化"></a>格式化</h3><blockquote><p><strong>注意:</strong> 仅在第一次启动 HDFS 时才需要格式化 NameNode，如果是重启HDFS那么跳过这步，直接执行下一步即可。<br>此外，在进行 NameNode 格式化之前，如果~&#x2F;hadoop-2.10.1&#x2F;tmp&#x2F;文件夹已存在，那么需要删除该文件夹后再执行以下格式化命令。</p><p><strong>如果启动时炸了，CTRL+C了，断电了，请参考后文解决办法，可能仍然需要格式化</strong></p></blockquote><ul><li>格式化命令: <code>~/hadoop-2.10.1/bin/hdfs namenode -format</code></li></ul><p><img                         lazyload                       alt="image"                       data-src="https://cdn.jsdelivr.net/gh/fengwm64/imgs/imgs/202404301427369.png"                                        ></p><hr><h3 id="启动"><a href="#启动" class="headerlink" title="启动"></a>启动</h3><ul><li>启动：<code>~/hadoop-2.10.1/sbin/start-dfs.sh</code></li></ul><p><img                         lazyload                       alt="image"                       data-src="https://cdn.jsdelivr.net/gh/fengwm64/imgs/imgs/202404301505902.png"                                        ></p><hr><h3 id="验证"><a href="#验证" class="headerlink" title="验证"></a>验证</h3><ul><li><p>验证：<code>jps</code></p></li><li><p><strong>主节点</strong></p></li></ul><p><img                         lazyload                       alt="image"                       data-src="https://cdn.jsdelivr.net/gh/fengwm64/imgs/imgs/202404301620529.png"                                        ></p><ul><li><strong>从节点</strong></li></ul><p><img                         lazyload                       alt="image"                       data-src="https://cdn.jsdelivr.net/gh/fengwm64/imgs/imgs/202404301509565.png"                                        ></p><hr><p>浏览器访问<code>http://主节点IP:50070/</code>，（<strong>如果主节点是云服务器记得把防火墙打开</strong>）</p><p>开防火墙：</p><p><img                         lazyload                       alt="image"                       data-src="https://cdn.jsdelivr.net/gh/fengwm64/imgs/imgs/202404301515606.png"                                        ></p><p>集群工作正常：</p><p><img                         lazyload                       alt="image"                       data-src="https://cdn.jsdelivr.net/gh/fengwm64/imgs/imgs/202404301622499.png"                                        ></p><p>查看节点信息：</p><p><img                         lazyload                       alt="image"                       data-src="https://cdn.jsdelivr.net/gh/fengwm64/imgs/imgs/202404301624181.png"                                        ></p><h3 id="集群异常解决"><a href="#集群异常解决" class="headerlink" title="集群异常解决"></a>集群异常解决</h3><ol><li>如果因为一些情况导致集群第一次没有启动成功，请在主、从节点：</li></ol><ul><li><p>在<strong>主节点</strong>, 停止集群：<code>~/hadoop-2.10.1/sbin/stop-dfs.sh</code></p></li><li><p>删除运行生成文件：<code>cd ~/hadoop-2.10.1/tmp/dfs &amp;&amp; rm -rf *</code></p></li><li><p>删除日志：<code>cd ~/hadoop-2.10.1/logs &amp;&amp; rm -rf *</code></p></li><li><p>解决端口占用：<code>sudo reboot</code></p></li><li><p>在<strong>主节点</strong>, 重新执行格式化命令：<code>~/hadoop-2.10.1/bin/hdfs namenode -format</code></p></li></ul><hr><ol start="2"><li><strong>云服务器可能会出现的错误</strong></li></ol><ul><li><strong>错误日志：</strong></li></ul><p><img                         lazyload                       alt="image"                       data-src="https://cdn.jsdelivr.net/gh/fengwm64/imgs/imgs/202404301613498.png"                                        ></p><ul><li><p>提示绑定错误或<code>2024-04-30 16:06:52,547 INFO org.apache.hadoop.util.ExitUtil: Exiting with status 1: java.net.BindException: Problem binding to [ecnu01:9000] java.net.BindException: Cannot assign requested address; For more details see:  http://wiki.apache.org/hadoop/BindException</code>的</p></li><li><p>检查文章<code>spark-1</code> 中提到的hosts设置是否正确, 设置好了不会出现这种情况</p></li><li><p><strong>参考：</strong></p></li></ul><ol><li><p><a class="link"   href="https://blog.csdn.net/xiaosa5211234554321/article/details/119627974" >https://blog.csdn.net/xiaosa5211234554321/article/details/119627974<i class="fas fa-external-link-alt"></i></a></p></li><li><p><a class="link"   href="https://cwiki.apache.org/confluence/display/HADOOP2/BindException" >https://cwiki.apache.org/confluence/display/HADOOP2/BindException<i class="fas fa-external-link-alt"></i></a></p></li></ol><h2 id="HDFS-Shell"><a href="#HDFS-Shell" class="headerlink" title="HDFS Shell"></a>HDFS Shell</h2><blockquote><p>注意:第一次使用 HDFS 时,需要首先在 HDFS 中创建用户目录</p></blockquote><ul><li><p>打开工作目录: <code>cd ~/hadoop-2.10.1</code></p></li><li><p>为当前 dase-dis 用户创建一个用户根目录: <code>./bin/hdfs dfs -mkdir -p /user/dase-dis</code></p></li></ul><p>HDFS Shell目录操作示例:</p><ul><li><p>显示 hdfs:&#x2F;&#x2F;&#x2F;user&#x2F;dase-dis 下的文件: <code>./bin/hdfs dfs -ls /user/dase-dis</code></p></li><li><p>新建 hdfs:&#x2F;&#x2F;&#x2F;user&#x2F;dase-dis&#x2F;input 目录: <code>./bin/hdfs dfs -mkdir /user/dase-dis/input</code></p></li><li><p>删除 hdfs:&#x2F;&#x2F;&#x2F;user&#x2F;dase-dis&#x2F;input 目录: <code>./bin/hdfs dfs -rm -r /user/dase-dis/input</code></p></li></ul><hr><h1 id="Spark部署"><a href="#Spark部署" class="headerlink" title="Spark部署"></a>Spark部署</h1><h2 id="修改配置文件"><a href="#修改配置文件" class="headerlink" title="修改配置文件"></a>修改配置文件</h2><h3 id="修改-bashrc文件"><a href="#修改-bashrc文件" class="headerlink" title="修改.bashrc文件"></a>修改.bashrc文件</h3><p><strong>客户端</strong>执行：</p><ul><li><p><code>vi ~/.bashrc</code></p></li><li><p>按<code>i</code>进入编辑模式，按方向键到文件最后一行，输入<code>export TERM=xterm-color</code></p></li></ul><p><img                         lazyload                       alt="image"                       data-src="https://cdn.jsdelivr.net/gh/fengwm64/imgs/imgs/202404292240144.png"                                        ></p><ul><li><p>按<code>Esc</code>键退出编辑模式，输入<code>:wq</code>保存退出</p></li><li><p>使<code>.bashrc</code>配置生效：<code>source ~/.bashrc</code></p></li></ul><h3 id="下载-spark"><a href="#下载-spark" class="headerlink" title="下载 spark"></a>下载 spark</h3><p>在<strong>主节点</strong>执行:</p><ul><li><p>启动HDFS服务(已经启动直接下一步):<code>~/hadoop-2.10.1/sbin/start-dfs.sh</code></p></li><li><p>下载Spark安装包：<code>wget http://archive.apache.org/dist/spark/spark-2.4.7/spark-2.4.7-bin-without-hadoop.tgz</code></p></li><li><p>解压安装包：<code>tar -zxvf spark-2.4.7-bin-without-hadoop.tgz</code></p></li><li><p>改名：<code>mv ~/spark-2.4.7-bin-without-hadoop ~/spark-2.4.7</code></p></li></ul><p>上述步骤完成后：<br><img                         lazyload                       alt="image"                       data-src="https://cdn.jsdelivr.net/gh/fengwm64/imgs/imgs/202404292257700.png"                                        ></p><h3 id="修改配置-1"><a href="#修改配置-1" class="headerlink" title="修改配置"></a>修改配置</h3><p>在<strong>主节点</strong>执行以下修改:</p><h4 id="spark-env"><a href="#spark-env" class="headerlink" title="spark-env"></a>spark-env</h4><ul><li><p><code>cp /home/dase-dis/spark-2.4.7/conf/spark-env.sh.template /home/dase-dis/spark-2.4.7/conf/spark-env.sh</code></p></li><li><p><code>vi /home/dase-dis/spark-2.4.7/conf/spark-env.sh</code></p></li></ul><p>修改为:</p><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta prompt_"># </span><span class="language-bash">因为下载的是Hadoop Free版本的Spark, 所以需要配置Hadoop的路径</span></span><br><span class="line">export HADOOP_HOME=/home/dase-dis/hadoop-2.10.1</span><br><span class="line">export SPARK_DIST_CLASSPATH=$($HADOOP_HOME/bin/hadoop classpath)</span><br><span class="line">export LD_LIBRARY_PATH=$HADOOP_HOME/lib/native:$LD_LIBRARY_PATH</span><br><span class="line"></span><br><span class="line">export SPARK_MASTER_HOST=ecnu01 #主节点主机名</span><br><span class="line">export SPARK_MASTERPORT=7077    #端口号</span><br></pre></td></tr></table></figure><p><img                         lazyload                       alt="image"                       data-src="https://cdn.jsdelivr.net/gh/fengwm64/imgs/imgs/202405020927048.png"                                        ></p><h4 id="slaves"><a href="#slaves" class="headerlink" title="slaves"></a>slaves</h4><ul><li><p><code>cp spark-2.4.7/conf/slaves.template spark-2.4.7/conf/slaves</code></p></li><li><p><code>vi spark-2.4.7/conf/slaves</code></p></li></ul><p>修改为:</p><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta prompt_"># </span><span class="language-bash">localhost</span></span><br><span class="line">ecnu02</span><br><span class="line">ecnu03</span><br></pre></td></tr></table></figure><p><img                         lazyload                       alt="image"                       data-src="https://cdn.jsdelivr.net/gh/fengwm64/imgs/imgs/202405020930661.png"                                        ></p><h4 id="spark-defaults"><a href="#spark-defaults" class="headerlink" title="spark-defaults"></a>spark-defaults</h4><ul><li><p><code>cp spark-2.4.7/conf/spark-defaults.conf.template spark-2.4.7/conf/spark-defaults.conf</code></p></li><li><p><code>vi spark-2.4.7/conf/spark-defaults.conf</code></p></li></ul><p>修改为:</p><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line">spark.eventLog.enabled=true</span><br><span class="line">spark.eventLog.dir=hdfs://ecnu01:9000/tmp/spark_history</span><br><span class="line">spark.history.fs.logDirectory=hdfs://ecnu01:9000/tmp/spark_history</span><br></pre></td></tr></table></figure><p><img                         lazyload                       alt="image"                       data-src="https://cdn.jsdelivr.net/gh/fengwm64/imgs/imgs/202405020941828.png"                                        ></p><h4 id="spark-config"><a href="#spark-config" class="headerlink" title="spark-config"></a>spark-config</h4><ul><li><code>vi spark-2.4.7/sbin/spark-config.sh</code></li></ul><p>追加:</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">export JAVA_HOME=/home/dase-dis/jdk1.8.0_202</span><br></pre></td></tr></table></figure><p><img                         lazyload                       alt="image"                       data-src="https://cdn.jsdelivr.net/gh/fengwm64/imgs/imgs/202405020945282.png"                                        ></p><h2 id="安装spark"><a href="#安装spark" class="headerlink" title="安装spark"></a>安装spark</h2><h3 id="拷贝"><a href="#拷贝" class="headerlink" title="拷贝"></a>拷贝</h3><p>本步骤将spark修改好的安装包拷贝到其他三台机:</p><ul><li><p><code>scp -r spark-2.4.7 dase-dis@ecnu02:~/</code></p></li><li><p><code>scp -r spark-2.4.7 dase-dis@ecnu03:~/</code></p></li><li><p><code>scp -r spark-2.4.7 dase-dis@ecnu04:~/</code></p></li></ul><h3 id="HDFS中建立日志目录"><a href="#HDFS中建立日志目录" class="headerlink" title="HDFS中建立日志目录"></a>HDFS中建立日志目录</h3><ul><li><code>~/hadoop-2.10.1/bin/hdfs dfs -mkdir -p /tmp/spark_history</code></li></ul><h3 id="启动-spark"><a href="#启动-spark" class="headerlink" title="启动 spark"></a>启动 spark</h3><p>千辛万苦, 终于到启动了<br><img                           lazyload                       alt="image"                       data-src="https://cdn.jsdelivr.net/gh/fengwm64/imgs/imgs/202405021003091.png"                         alt="" width="25%"                 ></p><p>在<strong>主节点</strong>执行:</p><ul><li><p>启动spark: <code>~/spark-2.4.7/sbin/start-all.sh</code></p></li><li><p>启动日志服务器: <code>~/spark-2.4.7/sbin/start-history-server.sh</code></p></li><li><p><strong>主节点:</strong></p></li></ul><p><img                         lazyload                       alt="image"                       data-src="https://cdn.jsdelivr.net/gh/fengwm64/imgs/imgs/202405021043271.png"                                        ></p><ul><li><strong>从节点:</strong></li></ul><p><img                         lazyload                       alt="image"                       data-src="https://cdn.jsdelivr.net/gh/fengwm64/imgs/imgs/202405021047418.png"                                        ></p><p><strong>错误处理:</strong></p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br></pre></td><td class="code"><pre><span class="line">dase-dis@ecnu01:~$ ~/spark-2.4.7/sbin/start-all.sh</span><br><span class="line">starting org.apache.spark.deploy.master.Master, logging to /home/dase-dis/spark-2.4.7/logs/spark-dase-dis-org.apache.spark.deploy.master.Master-1-ecnu01.out</span><br><span class="line">ecnu02: starting org.apache.spark.deploy.worker.Worker, logging to /home/dase-dis/spark-2.4.7/logs/spark-dase-dis-org.apache.spark.deploy.worker.Worker-1-ecnu02.out</span><br><span class="line">ecnu03: starting org.apache.spark.deploy.worker.Worker, logging to /home/dase-dis/spark-2.4.7/logs/spark-dase-dis-org.apache.spark.deploy.worker.Worker-1-ecnu03.out</span><br><span class="line">ecnu02: failed to launch: nice -n 0 /home/dase-dis/spark-2.4.7/bin/spark-class org.apache.spark.deploy.worker.Worker --webui-port 8081 spark://172.19.39.254:7077</span><br><span class="line">ecnu02:         at io.netty.channel.AbstractChannel.bind(AbstractChannel.java:248)</span><br><span class="line">ecnu02:         at io.netty.bootstrap.AbstractBootstrap$2.run(AbstractBootstrap.java:356)</span><br><span class="line">ecnu02:         at io.netty.util.concurrent.AbstractEventExecutor.safeExecute(AbstractEventExecutor.java:164)</span><br><span class="line">ecnu02:         at io.netty.util.concurrent.SingleThreadEventExecutor.runAllTasks(SingleThreadEventExecutor.java:472)</span><br><span class="line">ecnu02:         at io.netty.channel.nio.NioEventLoop.run(NioEventLoop.java:500)</span><br><span class="line">ecnu02:         at io.netty.util.concurrent.SingleThreadEventExecutor$4.run(SingleThreadEventExecutor.java:989)</span><br><span class="line">ecnu02:         at io.netty.util.internal.ThreadExecutorMap$2.run(ThreadExecutorMap.java:74)</span><br><span class="line">ecnu02:         at io.netty.util.concurrent.FastThreadLocalRunnable.run(FastThreadLocalRunnable.java:30)</span><br><span class="line">ecnu02:         at java.lang.Thread.run(Thread.java:748)</span><br><span class="line">ecnu02:   24/05/02 10:23:10 INFO util.ShutdownHookManager: Shutdown hook called</span><br><span class="line">ecnu02: full log in /home/dase-dis/spark-2.4.7/logs/spark-dase-dis-org.apache.spark.deploy.worker.Worker-1-ecnu02.out</span><br></pre></td></tr></table></figure><p><strong>请检查hosts文件设置, 文章<code>[大数据]Spark-1 SSH &amp; JDK部署</code></strong></p><h3 id="验证-1"><a href="#验证-1" class="headerlink" title="验证"></a>验证</h3><p>浏览器访问: <code>http://主节点IP:8080/</code>，（<strong>如果主节点是云服务器记得把防火墙打开</strong>）</p><p><img                         lazyload                       alt="image"                       data-src="https://cdn.jsdelivr.net/gh/fengwm64/imgs/imgs/202405021116322.png"                                        ></p><p>可以看到有两个worker在线, <strong>大功告成</strong></p><h2 id="运行spark应用程序"><a href="#运行spark应用程序" class="headerlink" title="运行spark应用程序"></a>运行spark应用程序</h2><p>好不容易搞好了, 来玩一下: </p><h3 id="创建文件夹-上传文件"><a href="#创建文件夹-上传文件" class="headerlink" title="创建文件夹&amp;上传文件"></a>创建文件夹&amp;上传文件</h3><ul><li><p>创建<code>spark_input</code>文件夹: <code>~/hadoop-2.10.1/bin/hdfs dfs -mkdir -p spark_input</code></p></li><li><p>上传文件<code>RELEASE</code>到<code>spark_input</code>: <code>~/hadoop-2.10.1/bin/hdfs dfs -put ~/spark-2.4.7/RELEASE spark_input/</code></p></li></ul><p>在<code>hadood</code>的页面可以看到, 文件<code>RELEASE</code>存储在两个节点中: </p><p><img                         lazyload                       alt="image"                       data-src="https://cdn.jsdelivr.net/gh/fengwm64/imgs/imgs/202405021152426.png"                                        ></p><h3 id="启动-Spark-Shell"><a href="#启动-Spark-Shell" class="headerlink" title="启动 Spark Shell"></a>启动 Spark Shell</h3><ul><li>启动<code>spark-shell</code>: <code>~/spark-2.4.7/bin/spark-shell --master spark://ecnu01:7077</code></li></ul><p><img                         lazyload                       alt="image"                       data-src="https://cdn.jsdelivr.net/gh/fengwm64/imgs/imgs/202405021309487.png"                                        ></p><ul><li>键入以下<code>Scala</code>代码:</li></ul><figure class="highlight scala"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">val</span> sc = spark.sparkContext</span><br><span class="line"><span class="keyword">val</span> textFile = sc.textFile(<span class="string">&quot;hdfs://ecnu01:9000/user/dase-dis/spark_input/RELEASE&quot;</span>)</span><br><span class="line"><span class="keyword">val</span> counts = textFile.flatMap(line =&gt; line.split(<span class="string">&quot; &quot;</span>)).map(word =&gt; (word, <span class="number">1</span>)).reduceByKey(_ + _)</span><br><span class="line">counts.collect().foreach(println)</span><br></pre></td></tr></table></figure><p>shell输出:</p><p><img                         lazyload                       alt="image"                       data-src="https://cdn.jsdelivr.net/gh/fengwm64/imgs/imgs/202405021317280.png"                                        ></p><p>可以在网页查看到正在运行的任务信息:</p><p><img                         lazyload                       alt="image"                       data-src="https://cdn.jsdelivr.net/gh/fengwm64/imgs/imgs/202405021310029.png"                                        ></p><p>到此Spark集群就已经顺利搭建完毕了</p><h2 id="停止集群"><a href="#停止集群" class="headerlink" title="停止集群"></a>停止集群</h2><p>如果你希望停止集群:</p><ul><li><p>停止<code>Spark</code>: <code>/spark-2.4.7/sbin/stop-all.sh</code></p></li><li><p>停止<code>Spark</code>日志服务: <code>/spark-2.4.7/sbin/stop-history-server.sh</code></p></li><li><p>停止<code>HDFS</code>服务: <code>/hadoop-2.10.1/sbin/stop-dfs.sh</code></p></li></ul><hr><h1 id="测试运行"><a href="#测试运行" class="headerlink" title="测试运行"></a>测试运行</h1><p>经典的<code>WordCount</code>程序源码如下:</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br><span class="line">60</span><br><span class="line">61</span><br><span class="line">62</span><br><span class="line">63</span><br><span class="line">64</span><br><span class="line">65</span><br><span class="line">66</span><br><span class="line">67</span><br><span class="line">68</span><br><span class="line">69</span><br><span class="line">70</span><br><span class="line">71</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">package</span> cn.edu.ecnu.spark.example.java.wordcount;</span><br><span class="line"></span><br><span class="line"><span class="keyword">import</span> org.apache.spark.SparkConf;</span><br><span class="line"><span class="keyword">import</span> org.apache.spark.api.java.JavaPairRDD;</span><br><span class="line"><span class="keyword">import</span> org.apache.spark.api.java.JavaRDD;</span><br><span class="line"><span class="keyword">import</span> org.apache.spark.api.java.JavaSparkContext;</span><br><span class="line"><span class="keyword">import</span> org.apache.spark.api.java.function.*;</span><br><span class="line"><span class="keyword">import</span> scala.Tuple2;</span><br><span class="line"></span><br><span class="line"><span class="keyword">import</span> java.util.Arrays;</span><br><span class="line"><span class="keyword">import</span> java.util.Iterator;</span><br><span class="line"></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">WordCount</span> &#123;</span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">static</span> <span class="keyword">void</span> <span class="title function_">run</span><span class="params">(String[] args)</span> &#123;</span><br><span class="line">        <span class="comment">/* 步骤1：通过SparkConf设置配置信息，并创建SparkContext */</span></span><br><span class="line">        <span class="type">SparkConf</span> <span class="variable">conf</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">SparkConf</span>();</span><br><span class="line">        conf.setAppName(<span class="string">&quot;WordCount&quot;</span>);</span><br><span class="line">        <span class="type">JavaSparkContext</span> <span class="variable">sc</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">JavaSparkContext</span>(conf);</span><br><span class="line"></span><br><span class="line">        <span class="comment">/* 步骤2：按应用逻辑使用操作算子编写DAG，其中包括RDD的创建、转换和行动等 */</span></span><br><span class="line">        <span class="comment">// 读入文本数据，创建名为lines的RDD</span></span><br><span class="line">        JavaRDD&lt;String&gt; lines = sc.textFile(args[<span class="number">0</span>]);</span><br><span class="line"></span><br><span class="line">        <span class="comment">// 将lines中的每一个文本行按空格分割成单个单词</span></span><br><span class="line">        JavaRDD&lt;String&gt; words = lines.flatMap(<span class="keyword">new</span> <span class="title class_">FlatMapFunction</span>&lt;String, String&gt;() &#123;</span><br><span class="line">            <span class="meta">@Override</span></span><br><span class="line">            <span class="keyword">public</span> Iterator&lt;String&gt; <span class="title function_">call</span><span class="params">(String line)</span> <span class="keyword">throws</span> Exception &#123;</span><br><span class="line">                <span class="keyword">return</span> Arrays.asList(line.split(<span class="string">&quot; &quot;</span>)).iterator();</span><br><span class="line">            &#125;</span><br><span class="line">        &#125;);</span><br><span class="line">        <span class="comment">// 将每个单词的频数设置为1，即将每个单词映射为[单词, 1]</span></span><br><span class="line">        JavaPairRDD&lt;String, Integer&gt; pairs = words.mapToPair(<span class="keyword">new</span> <span class="title class_">PairFunction</span>&lt;String, String, Integer&gt;() &#123;</span><br><span class="line">            <span class="meta">@Override</span></span><br><span class="line">            <span class="keyword">public</span> Tuple2&lt;String, Integer&gt; <span class="title function_">call</span><span class="params">(String word)</span> <span class="keyword">throws</span> Exception &#123;</span><br><span class="line">                <span class="keyword">return</span> <span class="keyword">new</span> <span class="title class_">Tuple2</span>&lt;String, Integer&gt;(word, <span class="number">1</span>);</span><br><span class="line">            &#125;</span><br><span class="line">        &#125;);</span><br><span class="line">        <span class="comment">// 按单词聚合，并对相同单词的频数使用sum进行累计</span></span><br><span class="line">        JavaPairRDD&lt;String, Integer&gt; wordCounts = pairs.groupByKey().mapToPair(<span class="keyword">new</span> <span class="title class_">PairFunction</span>&lt;Tuple2&lt;String, Iterable&lt;Integer&gt;&gt;, String, Integer&gt;() &#123;</span><br><span class="line">            <span class="meta">@Override</span></span><br><span class="line">            <span class="keyword">public</span> Tuple2&lt;String, Integer&gt; <span class="title function_">call</span><span class="params">(Tuple2&lt;String, Iterable&lt;Integer&gt;&gt; t)</span> <span class="keyword">throws</span> Exception &#123;</span><br><span class="line">                <span class="type">Integer</span> <span class="variable">sum</span> <span class="operator">=</span> Integer.valueOf(<span class="number">0</span>);</span><br><span class="line">                <span class="keyword">for</span> (Integer i : t._2) &#123;</span><br><span class="line">                    sum += i;</span><br><span class="line">                &#125;</span><br><span class="line">                <span class="keyword">return</span> <span class="keyword">new</span> <span class="title class_">Tuple2</span>&lt;String, Integer&gt;(t._1, sum);</span><br><span class="line">            &#125;</span><br><span class="line">        &#125;);</span><br><span class="line">        <span class="comment">// 合并机制</span></span><br><span class="line">        <span class="comment">/*</span></span><br><span class="line"><span class="comment">        JavaPairRDD&lt;String, Integer&gt; wordCounts =</span></span><br><span class="line"><span class="comment">        pairs.reduceByKey(</span></span><br><span class="line"><span class="comment">            new Function2&lt;Integer, Integer, Integer&gt;() &#123;</span></span><br><span class="line"><span class="comment">              @Override</span></span><br><span class="line"><span class="comment">              public Integer call(Integer t1, Integer t2) throws Exception &#123;</span></span><br><span class="line"><span class="comment">                return t1 + t2;</span></span><br><span class="line"><span class="comment">              &#125;</span></span><br><span class="line"><span class="comment">            &#125;);</span></span><br><span class="line"><span class="comment">         */</span></span><br><span class="line"></span><br><span class="line">        <span class="comment">// 输出词频统计结果</span></span><br><span class="line">        wordCounts.saveAsTextFile(args[<span class="number">1</span>]);</span><br><span class="line"></span><br><span class="line">        <span class="comment">/* 步骤3：关闭SparkContext */</span></span><br><span class="line">        sc.stop();</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">static</span> <span class="keyword">void</span> <span class="title function_">main</span><span class="params">(String[] args)</span> &#123;</span><br><span class="line">        run(args);</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><hr><h2 id="新建maven项目"><a href="#新建maven项目" class="headerlink" title="新建maven项目"></a>新建maven项目</h2><ul><li>在<code>idea</code>新建项目：</li></ul><p><img                         lazyload                       alt="image"                       data-src="https://cdn.jsdelivr.net/gh/fengwm64/imgs/imgs/202405021832587.png"                                        ></p><ul><li><code>pom.xml</code>内容如下：</li></ul><figure class="highlight xml"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">&lt;?xml version=<span class="string">&quot;1.0&quot;</span> encoding=<span class="string">&quot;UTF-8&quot;</span>?&gt;</span></span><br><span class="line"><span class="tag">&lt;<span class="name">project</span> <span class="attr">xmlns</span>=<span class="string">&quot;http://maven.apache.org/POM/4.0.0&quot;</span></span></span><br><span class="line"><span class="tag">         <span class="attr">xmlns:xsi</span>=<span class="string">&quot;http://www.w3.org/2001/XMLSchema-instance&quot;</span></span></span><br><span class="line"><span class="tag">         <span class="attr">xsi:schemaLocation</span>=<span class="string">&quot;http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd&quot;</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">modelVersion</span>&gt;</span>4.0.0<span class="tag">&lt;/<span class="name">modelVersion</span>&gt;</span></span><br><span class="line"></span><br><span class="line">    <span class="tag">&lt;<span class="name">groupId</span>&gt;</span>org.example<span class="tag">&lt;/<span class="name">groupId</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">artifactId</span>&gt;</span>spark-wordcount<span class="tag">&lt;/<span class="name">artifactId</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">version</span>&gt;</span>1.0-SNAPSHOT<span class="tag">&lt;/<span class="name">version</span>&gt;</span></span><br><span class="line"></span><br><span class="line">    <span class="tag">&lt;<span class="name">properties</span>&gt;</span></span><br><span class="line">        <span class="tag">&lt;<span class="name">spark.version</span>&gt;</span>1.2.0<span class="tag">&lt;/<span class="name">spark.version</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;/<span class="name">properties</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">dependencies</span>&gt;</span></span><br><span class="line">        <span class="tag">&lt;<span class="name">dependency</span>&gt;</span></span><br><span class="line">            <span class="tag">&lt;<span class="name">groupId</span>&gt;</span>org.apache.spark<span class="tag">&lt;/<span class="name">groupId</span>&gt;</span></span><br><span class="line">            <span class="tag">&lt;<span class="name">artifactId</span>&gt;</span>spark-core_2.11<span class="tag">&lt;/<span class="name">artifactId</span>&gt;</span></span><br><span class="line">            <span class="tag">&lt;<span class="name">version</span>&gt;</span>2.4.7<span class="tag">&lt;/<span class="name">version</span>&gt;</span></span><br><span class="line">        <span class="tag">&lt;/<span class="name">dependency</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;/<span class="name">dependencies</span>&gt;</span></span><br><span class="line"></span><br><span class="line"><span class="tag">&lt;/<span class="name">project</span>&gt;</span></span><br></pre></td></tr></table></figure><ul><li>更新依赖</li></ul><h2 id="新建-Java-代码"><a href="#新建-Java-代码" class="headerlink" title="新建 Java 代码"></a>新建 Java 代码</h2><ul><li>新建包<code>cn.edu.ecnu.spark.example.java.wordcount</code>，类<code>WordCount</code>:</li></ul><p><img                         lazyload                       alt="image"                       data-src="https://cdn.jsdelivr.net/gh/fengwm64/imgs/imgs/202405021833109.png"                                        ></p><h2 id="打包"><a href="#打包" class="headerlink" title="打包"></a>打包</h2><ul><li>打包为<code>.jar</code>:</li></ul><p><img                         lazyload                       alt="image"                       data-src="https://cdn.jsdelivr.net/gh/fengwm64/imgs/imgs/202405021759128.png"                                        ></p><p><img                         lazyload                       alt="image"                       data-src="https://cdn.jsdelivr.net/gh/fengwm64/imgs/imgs/202405021800838.png"                                        ></p><h2 id="传送到客户端"><a href="#传送到客户端" class="headerlink" title="传送到客户端"></a>传送到客户端</h2><ul><li>将打包好的<code>.jar</code>(位置<code>项目路径\out\artifacts\spark_wordcount_jar</code>)传到<strong>客户端</strong>的<code>/home/dase-dis/spark-2.4.7/myapp</code></li></ul><p><img                         lazyload                       alt="image"                       data-src="https://cdn.jsdelivr.net/gh/fengwm64/imgs/imgs/202405021803426.png"                                        ></p><h2 id="下载测试数据"><a href="#下载测试数据" class="headerlink" title="下载测试数据"></a>下载测试数据</h2><ul><li><p>下载：<code>wget https://github.com/ymcui/Chinese-Cloze-RC/archive/master.zip</code></p></li><li><p>解压：<code>unzip master.zip</code></p></li><li><p>解压：<code>unzip ~/Chinese-Cloze-RC-master/people_daily/pd.zip</code></p></li><li><p>拷贝到集群：<code>~/hadoop-2.10.1/bin/hdfs dfs -put ~/Chinese-Cloze-RC-master/people_daily/pd/pd.test spark_input/pd.test</code></p></li></ul><h2 id="提交jar任务"><a href="#提交jar任务" class="headerlink" title="提交jar任务"></a>提交jar任务</h2><ul><li><p>删除输出文件夹：<code>~/hadoop-2.10.1/bin/hdfs dfs -rm -r spark_output</code></p></li><li><p>提交任务：<code>~/spark-2.4.7/bin/spark-submit \ --master spark://ecnu01:7077 \ --class cn.edu.ecnu.spark.example.java.wordcount.WordCount \ /home/dase-dis/spark-2.4.7/myapp/spark-wordcount.jar hdfs://ecnu01:9000/user/dase-dis/spark_input hdfs://ecnu01:9000/user/dase-dis/spark_output</code></p></li></ul><p>正在运行</p><p><img                         lazyload                       alt="image"                       data-src="https://cdn.jsdelivr.net/gh/fengwm64/imgs/imgs/202405021816280.png"                                        ></p><p><strong>顺利跑完</strong></p><p>ssh：</p><p><img                         lazyload                       alt="image"                       data-src="https://cdn.jsdelivr.net/gh/fengwm64/imgs/imgs/202405021831459.png"                                        ></p><p>webui：</p><p><img                         lazyload                       alt="image"                       data-src="https://cdn.jsdelivr.net/gh/fengwm64/imgs/imgs/202405021830147.png"                                        ></p><p>查看<code>output</code>文件夹:</p><p><img                         lazyload                       alt="image"                       data-src="https://cdn.jsdelivr.net/gh/fengwm64/imgs/imgs/202405021931447.png"                                        ></p><p>查看<code>part01</code>运行结果：</p><ul><li><code>./hadoop-2.10.1/bin/hdfs dfs -cat /user/dase-dis/spark_output/part-00001</code></li></ul><p><img                         lazyload                       alt="image"                       data-src="https://cdn.jsdelivr.net/gh/fengwm64/imgs/imgs/202405021934024.png"                                        ></p>]]>
    </content>
    <id>https://x4ai.cn/2024/04/28/Spark%E9%9B%86%E7%BE%A4%E9%83%A8%E7%BD%B2/</id>
    <link href="https://x4ai.cn/2024/04/28/Spark%E9%9B%86%E7%BE%A4%E9%83%A8%E7%BD%B2/"/>
    <published>2024-04-28T11:48:24.000Z</published>
    <summary>记录在多台服务器上部署Spark集群的全过程，包括SSH互联互通、JDK配置、Hadoop安装与配置以及Spark安装与配置。</summary>
    <title>Spark集群部署</title>
    <updated>2026-06-06T02:01:51.544Z</updated>
  </entry>
  <entry>
    <author>
      <name>迎风起降</name>
    </author>
    <category term="学习笔记" scheme="https://x4ai.cn/categories/%E5%AD%A6%E4%B9%A0%E7%AC%94%E8%AE%B0/"/>
    <category term="机器学习" scheme="https://x4ai.cn/tags/%E6%9C%BA%E5%99%A8%E5%AD%A6%E4%B9%A0/"/>
    <content>
      <![CDATA[<h2 id="介绍"><a href="#介绍" class="headerlink" title="介绍"></a>介绍</h2><p>逻辑回归是一种广义上的线性回归模型，简单来看就是将线性回归的结果 $y$ 通过一个映射函数 sigmoid 映射到 0-1 区间，将 sigmoid 函数输出当做概率值，当概率值大于 0.5 时分类为正类，反之反类。</p><p><strong>Sigmoid函数</strong>:</p><p>$$<br>g(z) &#x3D; \frac{1}{1+e^{-z}}<br>$$</p><div align="center"><img                           lazyload                       alt="image"                       data-src="https://img.102465.xyz/file/1769422824613_image.png"                         alt="sigmoid曲线图" width=50%                  ></div><hr><h2 id="推导模型公式"><a href="#推导模型公式" class="headerlink" title="推导模型公式"></a>推导模型公式</h2><h3 id="模型公式"><a href="#模型公式" class="headerlink" title="模型公式"></a>模型公式</h3><p>$$<br>h_{\theta}(x) &#x3D; g(\theta^T X) &#x3D; \frac{1}{1 + e^{-\theta^T X}}<br>$$</p>$$\hat{y} = \begin{cases}1, & h_{\theta}(x) > 0.5 \\0, & h_{\theta}(x) < 0.5\end{cases}$$<p>⚠️ 注意： </p><ul><li>$\hat{y}$ 是预测的类别，</li><li>$y$ 是真实的类别，</li><li>$h_{\theta}(x)$ 视作概率值（这个模型就是这样设计的），</li><li>$\theta$ 是这个模型的 <strong>参数</strong>（实际上和线性回归的参数一样）</li></ul><hr><h3 id="推导损失函数"><a href="#推导损失函数" class="headerlink" title="推导损失函数"></a>推导损失函数</h3><h4 id="条件概率表示"><a href="#条件概率表示" class="headerlink" title="条件概率表示"></a>条件概率表示</h4><p>首先写出模型的条件概率表示（在给定样本 $x$ 下）：</p><p>给定输入样本 $x$，模型输出 $h_{\theta}(x)$ 表示预测类别为 $1$ 的概率，即：</p><p>$$<br>P_{\theta}(y&#x3D;1|x) &#x3D; h_{\theta}(x)<br>$$</p><p>同样，预测类别为 $0$ 的概率是：</p><p>$$<br>P_{\theta}(y&#x3D;0|x) &#x3D; 1 - h_{\theta}(x)<br>$$</p><p>令 $p &#x3D; h_{\theta}(x)$，上面的公式可以写为：</p><p>给定输入样本 $x$，预测类别为 $1$ 的概率：</p><p>$$<br>P_{\theta}(y&#x3D;1|x) &#x3D; p<br>$$</p><p>同样，预测类别为 $0$ 的概率是：</p><p>$$<br>P_{\theta}(y&#x3D;0|x) &#x3D; 1 - p<br>$$</p><hr><h4 id="最大似然函数"><a href="#最大似然函数" class="headerlink" title="最大似然函数"></a>最大似然函数</h4><p>这里实际上将sigmoid输出当做概率，我们目标追求 正确分类的概率 $P_{\theta}(y|x)$ 要越高越好 (注意这里的 $y$ 是真实类别，所以 $P_{\theta}(y|x)$ 就是正确分类的概率值)。</p><p>使用最大似然估计来求使 $P_{\theta}(y|x)$ 达到最大的参数 $\theta$，故可以得到参数 $\theta$ 的似然函数如下：</p><p>$$<br>L(\theta) &#x3D; P_{\theta}(Y|X) &#x3D; \prod_{j&#x3D;1}^{m} P_{\theta}(Y^j | X^j)<br>$$</p><p>©️ 符号说明：</p><ul><li>$m$: 样本数量</li><li>$d$: 为样本特征纬度</li><li>$x$: 单个样本的特征，是一个行向量</li><li>$X$: 每个样本特征组成的矩阵，形状为 $m * d$</li><li>$X^j$: 第 $j$ 个样本的特征</li><li>$y$: 单个样本的类别标签</li><li>$Y$: 样本类别组成的向量，形状为 $1 * m$</li><li>${Y^j}$: 第 $j$ 个样本的真实类别</li></ul><hr><h4 id="改写最大似然函数"><a href="#改写最大似然函数" class="headerlink" title="改写最大似然函数"></a>改写最大似然函数</h4><p>将 $P_{\theta}(y|x) $ 替代为 $p^{y} (1-p)^{1-y}$。</p><p>当 $y$ 为 $1$ ，$p^y$ 生效；$y&#x3D;0$，$p^y$ 失效，$(1-p)^{1-y}$ 生效。所以这种替代是等效的。</p><p>这其实是为什么要定义正类编码为 $1$ ，负类编码为 $0$ 的原因，这样做可以简洁表示，简化了后面的推导过程。如果定义为其他数字也是可以的，不过后续推导会很繁琐。</p><p>然后就可以得到下面这种形式：<br>$$<br>L(\theta) &#x3D; \prod_{j&#x3D;1}^{m} p^{Y^j} (1-p)^{1-Y^j}<br>$$</p><hr><h4 id="对数似然函数"><a href="#对数似然函数" class="headerlink" title="对数似然函数"></a>对数似然函数</h4><p>为了计算方便，在上式两边取对数：</p><p>$$<br>\ln L(\theta) &#x3D; \ln \left( \prod_{j&#x3D;1}^{m} p^{Y^j} (1-p)^{1-Y^j} \right)<br>&#x3D; \sum_{j&#x3D;1}^{m} \left( Y^j \ln p + (1 - Y^j) \ln(1 - p) \right)<br>$$</p><p><strong>最大化对数似然 $\ln L(\theta)$ 等价于 最大化似然 $L(\theta)$</strong></p><hr><h4 id="损失函数"><a href="#损失函数" class="headerlink" title="损失函数"></a>损失函数</h4><p>得到 对数似然 $\ln L(\theta)$ 后，我们需要最大化 对数似然 $\ln L(\theta)$ 得到参数 $\theta$。</p><p>我们知道求解一个模型是需要将模型的<strong>损失函数</strong>最小化，但是现在我们需要最大化对数似然，所以将对数似然 $\ln L(\theta)$取负，然后最小化$-\ln L(\theta)$ 即可。</p><p>故损失函数为：</p><p>$$<br>J(\theta) &#x3D; -\frac{1}{m} L(\theta) &#x3D; -\frac{1}{m} \sum_{j&#x3D;1}^{m} \left( y^j \ln p + (1 - y^j) \ln(1 - p) \right)<br>$$</p><p>📝 备注：<br>在逻辑回归的损失函数中，我们希望对所有训练样本的损失进行求和，然后再通过 $\frac{1}{m}$ 来平均，这样可以使损失函数对每个样本的贡献相等，避免训练数据量的大小对损失函数的影响过大。</p><hr><h4 id="梯度下降求解"><a href="#梯度下降求解" class="headerlink" title="梯度下降求解"></a>梯度下降求解</h4><p>先计算一些后面会用到的结果：</p><ul><li><strong>sigmoid函数的导数</strong>：</li></ul>$$\begin{aligned}\frac{\partial}{\partial z} g(z) &= \frac{\partial}{\partial z} \left( \frac{1}{1 + e^{-z}} \right) \\&= \frac{\partial}{\partial z} (1 + e^{-z})^{-1} \\&= -(1 + e^{-z})^{-2} \left( (1 + e^{-z})' \right) \\&= -(1 + e^{-z})^{-2} (-e^{-z}) \\&= (1 + e^{-z})^{-2} e^{-z}\end{aligned}$$<ul><li><strong>1 - sigmoid函数的导数</strong>:</li></ul><p>$$<br>\frac{\partial}{\partial z} \left( 1 - g(z) \right) &#x3D; -g(z) \left( 1 - g(z) \right)<br>$$</p><ul><li><strong>对数 sigmoid函数的导数</strong>:</li></ul>$$\begin{aligned}\frac{\partial}{\partial z} \ln g(z) &= \frac{\partial}{\partial z} \ln \left( \frac{1}{1 + e^{-z}} \right) \\&= (1 + e^{-z}) \left( (1 + e^{-z})^{-2} \right) \left( e^{-z} \right) \\&= \frac{e^{-z}}{1 + e^{-z}} \\&= 1 - \frac{1}{1 + e^{-z}} \\&= 1 - g(z)\end{aligned}$$<ul><li><strong>对数 1 - sigmoid函数的导数</strong>:</li></ul>$$\begin{aligned}\frac{\partial}{\partial z} \ln (1 - g(z)) &= \frac{\partial}{\partial z} \ln \left( 1 - \frac{1}{1 + e^{-z}} \right) \\&= - \frac{1}{1 - \frac{1}{1 + e^{-z}}} \left( (1 + e^{-z})^{-2} \right) (e^{-z}) \\&= - \frac{1 + e^{-z}}{e^{-z}} \left( (1 + e^{-z})^{-2} \right) \\&= -g(z)\end{aligned}$$<p>一通算后，终于可以开始计算损失函数的导数：</p>$$\begin{aligned}\frac{\partial}{\partial \theta_i} J(\theta) &= \frac{\partial}{\partial \theta_i} \left( -\frac{1}{m} \sum_{j=1}^{m} \left( y^j \ln p + (1 - y^j) \ln(1 - p) \right) \right) \\&= \frac{\partial}{\partial \theta_i} \left( -\frac{1}{m} \sum_{j=1}^{m} \left( y^j \ln g(\theta^T X^j) + (1 - y^j) \ln(1 - g(\theta^T X^j)) \right) \right) \\&= -\frac{1}{m} \sum_{j=1}^{m} \left( y^j \left( \ln g(\theta^T X^j) \right)' + (1 - y^j) \left( \ln(1 - g(\theta^T X^j)) \right)' \right) \\&= -\frac{1}{m} \sum_{j=1}^{m} \left( y^j (1 - g(\theta^T X^j)) x_i^j + (y^j - 1) g(\theta^T X^j) x_i^j \right) \\&= -\frac{1}{m} \sum_{j=1}^{m} \left( y^j - g(\theta^T X^j) \right) x_i^j\end{aligned}$$<p>中间计算详细过程：</p><ul><li><p>$\left( \ln g(\theta^T X) \right)’$:<br>$$<br>\left( \ln g(\theta^T X) \right)’ &#x3D; \frac{\partial}{\partial \theta_i} \ln g(\theta^T X)<br>$$</p></li><li><p>$\frac{\partial \ln g(z)}{\partial z} \frac{\partial z}{\partial \theta_i}$:</p></li></ul>$$\begin{aligned}\frac{\partial \ln g(z)}{\partial z} \frac{\partial z}{\partial \theta_i} &= (1 - g(z)) \frac{\partial}{\partial \theta_i} \theta^T X \\&= (1 - g(z)) x_i \\&= (1 - g(\theta^T X)) x_i\end{aligned}$$<ul><li><p>$\left( \ln(1 - g(\theta^T X)) \right)’$：<br>$$<br>\left( \ln(1 - g(\theta^T X)) \right)’ &#x3D; \frac{\partial}{\partial \theta_i} \ln(1 - g(\theta^T X))<br>$$</p></li><li><p>$\frac{\partial \ln(1 - g(z))}{\partial z} \frac{\partial z}{\partial \theta_i} $：</p></li></ul>$$\begin{aligned}\frac{\partial \ln(1 - g(z))}{\partial z} \frac{\partial z}{\partial \theta_i} &= -g(z) \frac{\partial}{\partial \theta_i} \theta^T X \\&= -g(z) x_i \\&= -g(\theta^T X) x_i\end{aligned}$$<hr><h4 id="迭代更新公式"><a href="#迭代更新公式" class="headerlink" title="迭代更新公式"></a>迭代更新公式</h4><p>梯度下降更新公式：<br>$$<br>\theta_i :&#x3D; \theta_i - \alpha \frac{1}{m} \sum_{j&#x3D;1}^{m} \left( g(\theta^T X^j) - y^j \right) x_i^j<br>$$</p>]]>
    </content>
    <id>https://x4ai.cn/2023/12/23/%E9%80%BB%E8%BE%91%E5%9B%9E%E5%BD%92%E6%8E%A8%E5%AF%BC/</id>
    <link href="https://x4ai.cn/2023/12/23/%E9%80%BB%E8%BE%91%E5%9B%9E%E5%BD%92%E6%8E%A8%E5%AF%BC/"/>
    <published>2023-12-23T06:46:38.000Z</published>
    <summary>逻辑回归是一种广义上的线性回归模型，本文推导了逻辑回归的模型公式、损失函数及梯度下降求解方法。</summary>
    <title>逻辑回归推导</title>
    <updated>2026-06-06T02:01:51.545Z</updated>
  </entry>
  <entry>
    <author>
      <name>迎风起降</name>
    </author>
    <category term="学习笔记" scheme="https://x4ai.cn/categories/%E5%AD%A6%E4%B9%A0%E7%AC%94%E8%AE%B0/"/>
    <category term="机器学习" scheme="https://x4ai.cn/tags/%E6%9C%BA%E5%99%A8%E5%AD%A6%E4%B9%A0/"/>
    <content>
      <![CDATA[<h2 id="原理"><a href="#原理" class="headerlink" title="原理"></a>原理</h2><h3 id="闭式求解"><a href="#闭式求解" class="headerlink" title="闭式求解"></a>闭式求解</h3><ul><li><p><strong>模型：</strong>$h_\theta(x)&#x3D;\theta^TX$</p></li><li><p><strong>损失函数：</strong>$J(\theta)&#x3D;\left|X\theta-Y\right|_2^2$</p></li><li><p><strong>目标：</strong>$\theta&#x3D;\arg\min J(\theta)$</p></li><li><p><strong>说明：</strong></p></li></ul>$$\begin{cases}\theta\in\mathbb{R}^{d\times1}\\[2ex]X\in\mathbb{R}^{m\times d}\\[2ex]Y\in\mathbb{R}^{m\times1}\end{cases}$$<p>正规方程形式求解，即为直接求 $J(\theta)$ 的最小值：</p><p>先展开 $J(\theta)$ ：</p>$$\begin{align*}J(\theta) &= \|X\theta - Y\|_{2}^{2} \\&= (X\theta - Y)^{T}(X\theta - Y) \\&= (X^{T}\theta^{T} - Y^{T})(X\theta - Y) \\&= X^{T}\theta^{T}X\theta - Y^{T}X\theta - Y^{T}X\theta + Y^{T}Y \\&= X^{T}\theta^{T}X\theta - 2Y^{T}X\theta + Y^{T}Y \end{align*}$$<p>对 $J(\theta)$ 进行求导：</p>$$\begin{aligned}\frac{\partial J(\theta)}{\partial\theta}& =\frac{\partial X^T\theta^TX\theta-2Y^TX\theta+Y^TY}{\partial\theta} \\&=2X^{T}X\theta-2Y^{T}X\end{aligned}$$<p>令 $J(\theta)&#x3D;0$ 得：</p>$$\begin{aligned}2X^{T}X\theta-2Y^{T}X& =0 \\X^{T}X\theta & =Y^{T}X \\\theta & =(X^TX)^{-1}Y^TX \end{aligned}$$<p>上述结果即为求解结果，需要说明的是：特征矩阵 $X$ 不满秩（即存在特征间的线性相关性），则正规方程求解过程中的矩阵求逆操作可能会导致数值不稳定性。</p><h3 id="梯度下降求解"><a href="#梯度下降求解" class="headerlink" title="梯度下降求解"></a>梯度下降求解</h3><ul><li><p><strong>模型：</strong>$h_\theta(x)&#x3D;\sum_{i&#x3D;1}^d\theta_ix_i$ </p><p>  注：$x_i$表示$x$的第$i$维</p></li><li><p><strong>损失函数：</strong>$J(\theta)&#x3D;\frac1{2m}\sum_{j&#x3D;0}^m\left(y^j-h_\theta(x^j)\right)^2$</p><p>  注：$x^j$表示第$j$个样本</p></li><li><p><strong>目标：</strong>$\theta&#x3D;\arg\min J(\theta)$</p></li><li><p><strong>说明：</strong></p></li></ul>$$\begin{cases}\theta\in\mathbb{R}^d\\[2ex]x\in\mathbb{R}^d\\[2ex]y\in\mathbb{R}^m\end{cases}$$<p>损失函数 $J(\theta)$ 是一个关于参数 $\theta$ 的二次型，对 $J(\theta)$ 进行展开：</p>$$\begin{aligned}J(\theta)& =\frac{1}{2m}\sum_{j=0}^{m}\Big(y^{j}-h_{\theta}(x^{j})\Big) \\&=\frac{1}{2m}\sum_{j=0}^{m}\Bigg(y^{j}-\sum_{i=1}^{d}\theta_{i}x_{i}^{j}\Bigg)^{2}\end{aligned}$$<p>对 $J(\theta)$ 进行偏微分求导运算得到：</p>$$\begin{aligned}\partial\frac{J(\theta)}{\partial\theta_i}& =\frac{\partial}{\partial\theta_{i}}\frac{1}{2m}\sum_{j=0}^{m}\Bigg(y^{j}-\sum_{i=1}^{d}\theta_{i}x_{i}^{j}\Bigg)^{2} \\&=\frac{1}{m}\sum_{j=0}^{m}\Bigg( y^{j}-\sum_{i=1}^{d}\theta_{i}x_{i}^{j}\Bigg)(-x_{i}^{j}) \\&=\frac{1}{m}\sum_{j=0}^{m}\Bigg(\sum_{i=1}^{d}\theta_{i}x_{i}^{j}-y^{j}\Bigg)x_{i}^{j}\end{aligned}$$<p>每次根据梯度更新参数：</p>$$\begin{aligned}\theta_{i}& =\theta_i-\alpha\partial\frac{J(\theta)}{\partial\theta_i} \\&=\theta_i-\alpha(\frac1m\sum_{j=0}^m\biggl(\sum_{i=1}^d\theta_ix_i^j-y^j\biggr)x_i^j) \\&=\theta_i+\alpha \frac{1}{m}\sum_{j=0}^m\Bigg( y^j-\sum_{i=1}^d\theta_ix_i^j\Bigg)x_i^j\end{aligned}$$<p>梯度下降法步骤：</p>$\text{Repeat until convergence } \{$$$\theta_i:=\theta_i+\alpha\:\frac{1}{m}\sum_{j=0}^m\Bigg(y^j-\sum_{i=1}^d\theta_ix_i^j\Bigg)x_i^j$$$\}$<h2 id="Python实现"><a href="#Python实现" class="headerlink" title="Python实现"></a>Python实现</h2><h3 id="导包"><a href="#导包" class="headerlink" title="导包"></a>导包</h3><figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment"># 导入所需的包</span></span><br><span class="line"><span class="keyword">import</span> numpy <span class="keyword">as</span> np</span><br><span class="line"><span class="keyword">import</span> pandas <span class="keyword">as</span> pd</span><br><span class="line"><span class="keyword">import</span> matplotlib.pyplot <span class="keyword">as</span> plt</span><br><span class="line"><span class="keyword">import</span> seaborn <span class="keyword">as</span> sns</span><br><span class="line"><span class="keyword">from</span> sklearn.impute <span class="keyword">import</span> SimpleImputer</span><br><span class="line"><span class="keyword">from</span> sklearn.preprocessing <span class="keyword">import</span> OneHotEncoder</span><br><span class="line"><span class="keyword">from</span> sklearn.model_selection <span class="keyword">import</span> train_test_split</span><br><span class="line"><span class="keyword">import</span> time</span><br><span class="line"></span><br><span class="line">%matplotlib inline</span><br><span class="line">%config InlineBackend.figure_format = <span class="string">&#x27;svg&#x27;</span></span><br></pre></td></tr></table></figure><h3 id="读取数据集"><a href="#读取数据集" class="headerlink" title="读取数据集"></a>读取数据集</h3><figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment"># 读取数据</span></span><br><span class="line">df = pd.read_csv(<span class="string">&quot;./housing.csv&quot;</span>)</span><br><span class="line"><span class="comment"># 预览数据</span></span><br><span class="line"><span class="built_in">print</span>(df.head())</span><br><span class="line"></span><br><span class="line"><span class="built_in">print</span>(df.info())</span><br></pre></td></tr></table></figure><h3 id="数据预处理"><a href="#数据预处理" class="headerlink" title="数据预处理"></a>数据预处理</h3><figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment"># 1）处理缺失值</span></span><br><span class="line"><span class="comment"># 取出有缺失值的列</span></span><br><span class="line"><span class="comment"># reshape是为了适应sklearn要求</span></span><br><span class="line">total_bedrooms = df.loc[:, <span class="string">&quot;total_bedrooms&quot;</span>].values.reshape(-<span class="number">1</span>, <span class="number">1</span>)  </span><br><span class="line"></span><br><span class="line"><span class="comment"># 复制一份不破坏原数据</span></span><br><span class="line">filled_df = df.copy()  </span><br><span class="line"></span><br><span class="line"><span class="comment"># 中位数填补</span></span><br><span class="line">filled_df.loc[:, <span class="string">&quot;total_bedrooms&quot;</span>] = SimpleImputer(strategy=<span class="string">&quot;median&quot;</span>).fit_transform(total_bedrooms)  </span><br><span class="line"></span><br><span class="line"><span class="comment"># 看一下效果</span></span><br><span class="line">filled_df.info()</span><br><span class="line"></span><br><span class="line"></span><br><span class="line"><span class="comment"># 2）编码</span></span><br><span class="line"><span class="comment"># 编码</span></span><br><span class="line">code = OneHotEncoder().fit_transform(filled_df.loc[:, <span class="string">&quot;ocean_proximity&quot;</span>].values.reshape(-<span class="number">1</span>, <span class="number">1</span>))</span><br><span class="line"></span><br><span class="line"><span class="comment"># 合并</span></span><br><span class="line">coded_df = pd.concat([filled_df, pd.DataFrame(code.toarray())], axis=<span class="number">1</span>)</span><br><span class="line"></span><br><span class="line"><span class="comment"># 删除原列</span></span><br><span class="line">coded_df.drop([<span class="string">&quot;ocean_proximity&quot;</span>], axis=<span class="number">1</span>, inplace=<span class="literal">True</span>)</span><br><span class="line"></span><br><span class="line"><span class="comment"># 改下表头</span></span><br><span class="line">coded_df.columns = <span class="built_in">list</span>(coded_df.columns[:-<span class="number">5</span>]) + [<span class="string">&quot;ocean_0&quot;</span>, <span class="string">&quot;ocean_1&quot;</span>, <span class="string">&quot;ocean_2&quot;</span>, <span class="string">&quot;ocean_3&quot;</span>, <span class="string">&quot;ocean_4&quot;</span>]</span><br><span class="line"><span class="comment"># coded_df.columns = coded_df.columns.astype(str)</span></span><br><span class="line"></span><br><span class="line"><span class="comment"># 看看效果</span></span><br><span class="line">coded_df.head(<span class="number">10</span>)</span><br></pre></td></tr></table></figure><h3 id="划分数据集"><a href="#划分数据集" class="headerlink" title="划分数据集"></a>划分数据集</h3><figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line">feature = coded_df.iloc[:, :<span class="number">8</span>].join(coded_df.iloc[:, -<span class="number">5</span>:])</span><br><span class="line">label = coded_df[<span class="string">&quot;median_house_value&quot;</span>]</span><br><span class="line"></span><br><span class="line">Xtrain,Xtest,Ytrain,Ytest = train_test_split(feature,label,test_size=<span class="number">0.3</span>)</span><br><span class="line"></span><br><span class="line">Xtrain.head()</span><br></pre></td></tr></table></figure><h3 id="求解模型"><a href="#求解模型" class="headerlink" title="求解模型"></a>求解模型</h3><h4 id="评价指标R-2"><a href="#评价指标R-2" class="headerlink" title="评价指标R^2"></a>评价指标R^2</h4><figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment"># 计算R^2</span></span><br><span class="line"><span class="keyword">def</span> <span class="title function_">R2</span>(<span class="params">y, y_pred</span>):</span><br><span class="line">    <span class="keyword">return</span> <span class="number">1</span> - (np.<span class="built_in">sum</span>((y - y_pred) ** <span class="number">2</span>) / np.<span class="built_in">sum</span>((y - np.mean(y)) ** <span class="number">2</span>))</span><br></pre></td></tr></table></figure><h4 id="数据标准化"><a href="#数据标准化" class="headerlink" title="数据标准化"></a>数据标准化</h4><figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment"># 数据标准化</span></span><br><span class="line"><span class="keyword">def</span> <span class="title function_">normalize</span>(<span class="params">X</span>):</span><br><span class="line">    sigma = np.std(X, axis=<span class="number">0</span>)</span><br><span class="line">    mu = np.mean(X, axis=<span class="number">0</span>)</span><br><span class="line">    X = (X - mu) / sigma</span><br><span class="line">    <span class="keyword">return</span> np.array(X)</span><br><span class="line"></span><br><span class="line">X = np.array(Xtrain).reshape(np.size(Xtrain, <span class="number">0</span>), -<span class="number">1</span>)</span><br><span class="line">y = np.array(Ytrain).T.reshape(-<span class="number">1</span>, <span class="number">1</span>)</span><br><span class="line"></span><br><span class="line"><span class="comment"># 标准化（闭式求解其实不需要，但梯度下降需要，为了对比统一都采用归一化）</span></span><br><span class="line">X = normalize(X)</span><br><span class="line">y = normalize(y)</span><br></pre></td></tr></table></figure><h4 id="闭合形式求解"><a href="#闭合形式求解" class="headerlink" title="闭合形式求解"></a>闭合形式求解</h4><figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment"># 1）线性回归模型的闭合形式参数求解</span></span><br><span class="line"><span class="comment"># 正规方程求解</span></span><br><span class="line"><span class="keyword">def</span> <span class="title function_">Normal_Equation</span>(<span class="params">X, y</span>):</span><br><span class="line">    <span class="keyword">return</span> np.linalg.inv(X.T @ X) @ X.T @ y</span><br><span class="line"></span><br><span class="line">start_time = time.time()</span><br><span class="line">theta_ne = Normal_Equation(X, y)</span><br><span class="line"></span><br><span class="line"><span class="built_in">print</span>(<span class="string">f&quot;花费时间：<span class="subst">&#123;time.time() - start_time&#125;</span>&quot;</span>)v</span><br><span class="line"><span class="built_in">print</span>(<span class="string">f&quot;R^2：<span class="subst">&#123;R2(y, X @ theta_ne)&#125;</span>&quot;</span>)</span><br><span class="line"></span><br><span class="line"><span class="comment"># 创建 DataFrame</span></span><br><span class="line">result_cf = pd.DataFrame(&#123;<span class="string">&quot;ColumnName&quot;</span>: <span class="built_in">list</span>(Xtrain.columns), <span class="string">&quot;Theta&quot;</span>: theta_ne.flatten()&#125;)</span><br><span class="line">result_cf</span><br></pre></td></tr></table></figure><h4 id="梯度下降求解-1"><a href="#梯度下降求解-1" class="headerlink" title="梯度下降求解"></a>梯度下降求解</h4><figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment"># 2）线性回归梯度下降参数求解</span></span><br><span class="line"><span class="comment"># 损失函数</span></span><br><span class="line"><span class="keyword">def</span> <span class="title function_">MSE_Loss</span>(<span class="params">y, y_pred</span>):</span><br><span class="line">    <span class="keyword">return</span> np.<span class="built_in">sum</span>((y_pred - y) ** <span class="number">2</span>) / (<span class="number">2</span> * np.size(y))</span><br><span class="line"></span><br><span class="line"><span class="comment"># 梯度下降</span></span><br><span class="line"><span class="keyword">def</span> <span class="title function_">GD</span>(<span class="params">X, y, lr=<span class="number">0.01</span>, epochs=<span class="number">5000</span></span>):</span><br><span class="line">    m, n = X.shape</span><br><span class="line"></span><br><span class="line">    <span class="comment"># 初始化参数为标准正态分布</span></span><br><span class="line">    theta = np.random.randn(n, <span class="number">1</span>)</span><br><span class="line">    <span class="comment"># 记录每代损失</span></span><br><span class="line">    loss = np.zeros(epochs)</span><br><span class="line"></span><br><span class="line">    <span class="keyword">for</span> epoch <span class="keyword">in</span> <span class="built_in">range</span>(epochs):</span><br><span class="line">        <span class="comment"># 计算梯度</span></span><br><span class="line">        gradient = (<span class="number">1</span> / m) * (X.T @ (X @ theta - y))</span><br><span class="line">        <span class="comment"># 更新参数</span></span><br><span class="line">        theta -= lr * gradient</span><br><span class="line">        <span class="comment"># 记录损失</span></span><br><span class="line">        loss[epoch] = MSE_Loss(y, X @ theta)</span><br><span class="line"></span><br><span class="line">    <span class="keyword">return</span> theta, loss</span><br><span class="line"></span><br><span class="line">start_time = time.time()</span><br><span class="line">[theta_gd, loss] = GD(X, y)</span><br><span class="line"></span><br><span class="line"><span class="built_in">print</span>(<span class="string">f&quot;花费时间：<span class="subst">&#123;time.time() - start_time&#125;</span>&quot;</span>)</span><br><span class="line"><span class="built_in">print</span>(<span class="string">f&quot;R^2：<span class="subst">&#123;R2(y, X @ theta_gd)&#125;</span>&quot;</span>)</span><br><span class="line"></span><br><span class="line"><span class="comment"># 创建 DataFrame</span></span><br><span class="line">result_gd = pd.DataFrame(&#123;<span class="string">&quot;ColumnName&quot;</span>: <span class="built_in">list</span>(Xtrain.columns), <span class="string">&quot;Theta&quot;</span>: theta_gd.flatten()&#125;)</span><br><span class="line">result_gd</span><br><span class="line"></span><br><span class="line"><span class="comment"># 绘制损失函数梯度下降曲线</span></span><br><span class="line">sns.lineplot(x=np.arange(<span class="number">5000</span>), y=loss.flatten(), label=<span class="string">&#x27;Loss Curve&#x27;</span>)</span><br><span class="line"></span><br><span class="line">plt.xlabel(<span class="string">&#x27;Epoch&#x27;</span>)</span><br><span class="line">plt.ylabel(<span class="string">&#x27;Loss&#x27;</span>)</span><br><span class="line">plt.title(<span class="string">&#x27;Gradient Descent Loss Curve&#x27;</span>)</span><br></pre></td></tr></table></figure><h2 id="实验结果"><a href="#实验结果" class="headerlink" title="实验结果"></a>实验结果</h2><div align="center">  <img                           lazyload                       alt="image"                       data-src="https://img.102465.xyz/file/1769423146511_202404241143950.png"                         alt="" width=70%                  ></div>]]>
    </content>
    <id>https://x4ai.cn/2023/11/06/%E7%BA%BF%E6%80%A7%E5%9B%9E%E5%BD%92%E6%8E%A8%E5%AF%BC/</id>
    <link href="https://x4ai.cn/2023/11/06/%E7%BA%BF%E6%80%A7%E5%9B%9E%E5%BD%92%E6%8E%A8%E5%AF%BC/"/>
    <published>2023-11-06T04:19:44.000Z</published>
    <summary>线性回归模型的推导过程，包括闭式求解和梯度下降法求解，并附有Python实现代码。</summary>
    <title>线性回归推导</title>
    <updated>2026-06-06T02:01:51.545Z</updated>
  </entry>
  <entry>
    <author>
      <name>迎风起降</name>
    </author>
    <category term="学习笔记" scheme="https://x4ai.cn/categories/%E5%AD%A6%E4%B9%A0%E7%AC%94%E8%AE%B0/"/>
    <category term="操作系统" scheme="https://x4ai.cn/tags/%E6%93%8D%E4%BD%9C%E7%B3%BB%E7%BB%9F/"/>
    <category term="Bochs" scheme="https://x4ai.cn/tags/Bochs/"/>
    <category term="环境配置" scheme="https://x4ai.cn/tags/%E7%8E%AF%E5%A2%83%E9%85%8D%E7%BD%AE/"/>
    <content>
      <![CDATA[<blockquote><p>WSL2 - Ubuntu 22.04 + VSCode + Bochs + XFCE4 + VcXsrv</p></blockquote><h2 id="笔者环境：WSL2-Ubuntu-22-04"><a href="#笔者环境：WSL2-Ubuntu-22-04" class="headerlink" title="笔者环境：WSL2 - Ubuntu 22.04"></a><strong>笔者环境：WSL2 - Ubuntu 22.04</strong></h2><img                           lazyload                       alt="image"                       data-src="https://img.102465.xyz/file/1769423822784_imgs202404282017347.png"                         width="75%"                 ><hr><h2 id="安装-WSL2-VSCode-终端"><a href="#安装-WSL2-VSCode-终端" class="headerlink" title="安装 WSL2 &amp; VSCode &amp; 终端"></a>安装 WSL2 &amp; VSCode &amp; 终端</h2><p>网上教程千千万，请自行查找：</p><ul><li><p><strong>WSL2</strong>：<a class="link"   href="https://blog.csdn.net/weixin_42888638/article/details/127129727" >WSL2 安装教程 - CSDN<i class="fas fa-external-link-alt"></i></a></p><ul><li><p>切换清华源：<a class="link"   href="https://mirrors.tuna.tsinghua.edu.cn/help/ubuntu/" >清华大学开源软件镜像站<i class="fas fa-external-link-alt"></i></a></p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line"><span class="built_in">sudo</span> <span class="built_in">cp</span> /etc/apt/sources.list /etc/apt/sources.bak</span><br><span class="line"><span class="built_in">sudo</span> vim /etc/apt/sources.list</span><br><span class="line"><span class="built_in">sudo</span> apt update</span><br></pre></td></tr></table></figure></li></ul></li><li><p><strong>VSCode</strong>：<a class="link"   href="https://code.visualstudio.com/" >Visual Studio Code 官网<i class="fas fa-external-link-alt"></i></a></p><ul><li>安装插件：WSL</li><li>点击左下绿色按钮，按提示连接 WSL。</li></ul></li></ul><img                           lazyload                       alt="image"                       data-src="https://img.102465.xyz/file/1769423830363_imgs202404282017839.png"                         width="75%"                 ><ul><li><strong>终端</strong>：Microsoft Store 提供下载。</li></ul><img                           lazyload                       alt="image"                       data-src="https://img.102465.xyz/file/1769423822263_imgs202404282018831.png"                         width="75%"                 ><hr><h2 id="安装软件包"><a href="#安装软件包" class="headerlink" title="安装软件包"></a>安装软件包</h2><p>以下命令以行为单位粘贴到终端运行：</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line"><span class="built_in">sudo</span> apt update</span><br><span class="line"><span class="built_in">sudo</span> apt upgrade</span><br><span class="line"><span class="built_in">sudo</span> apt-get install -y neofetch gcc vim build-essential g++ libgtk2.0-dev nasm gdb</span><br></pre></td></tr></table></figure><hr><h2 id="配置-WSL2-图形界面"><a href="#配置-WSL2-图形界面" class="headerlink" title="配置 WSL2 图形界面"></a>配置 WSL2 图形界面</h2><p>采用 <strong>XFCE4 + VcXsrv</strong>，实现图形化界面。</p><h3 id="安装-VcXsrv"><a href="#安装-VcXsrv" class="headerlink" title="安装 VcXsrv"></a>安装 VcXsrv</h3><p>下载地址：<a class="link"   href="https://sourceforge.net/projects/vcxsrv/files/latest/download" >VcXsrv 下载<i class="fas fa-external-link-alt"></i></a></p><ul><li><strong>安装过程：</strong></li></ul><img                           lazyload                       alt="image"                       data-src="https://img.102465.xyz/file/1769423821664_imgs202404282018380.png"                         width="75%"                 ><img                           lazyload                       alt="image"                       data-src="https://img.102465.xyz/file/1769423817770_imgs202404282018860.png"                         width="75%"                 ><ul><li>选择 <strong>One Large Window</strong>：</li></ul><img                           lazyload                       alt="image"                       data-src="https://img.102465.xyz/file/1769423821650_imgs202404282019190.png"                         width="75%"                 ><img                           lazyload                       alt="image"                       data-src="https://img.102465.xyz/file/1769423819754_imgs202404282019623.png"                         width="75%"                 ><ul><li>勾选 <strong>Disable Access Control</strong>：</li></ul><img                           lazyload                       alt="image"                       data-src="https://img.102465.xyz/file/1769423817770_imgs202404282018860.png"                         width="75%"                 ><ul><li>成功界面：</li></ul><img                           lazyload                       alt="image"                       data-src="https://img.102465.xyz/file/1769423838175_imgs202404282020653.png"                         width="75%"                 ><ul><li><strong>解决高 DPI 模糊问题</strong>：</li></ul><img                           lazyload                       alt="image"                       data-src="https://img.102465.xyz/file/1769423827362_imgs202404282020086.png"                         width="75%"                 ><img                           lazyload                       alt="image"                       data-src="https://img.102465.xyz/file/1769423828620_imgs202404282020064.png"                         width="75%"                 ><img                           lazyload                       alt="image"                       data-src="https://img.102465.xyz/file/1769423839497_imgs202404282021124.png"                         width="75%"                 ><hr><h3 id="安装-XFCE4"><a href="#安装-XFCE4" class="headerlink" title="安装 XFCE4"></a>安装 XFCE4</h3><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="built_in">sudo</span> apt install -y xfce4</span><br></pre></td></tr></table></figure><img                           lazyload                       alt="image"                       data-src="https://img.102465.xyz/file/1769423842349_imgs202404282021947.png"                         width="75%"                 ><hr><h3 id="配置"><a href="#配置" class="headerlink" title="配置"></a>配置</h3><ul><li>修改 <code>.bashrc</code> 文件：</li></ul><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="built_in">cd</span> ~ &amp;&amp; vim .bashrc</span><br></pre></td></tr></table></figure><ul><li>在文件末尾添加：</li></ul><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="built_in">export</span> DISPLAY=$(awk <span class="string">&#x27;/nameserver / &#123;print $2; exit&#125;&#x27;</span> /etc/resolv.conf 2&gt;/dev/null):0</span><br></pre></td></tr></table></figure><p>执行：</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="built_in">source</span> ~/.bashrc</span><br></pre></td></tr></table></figure><hr><h3 id="启动-XFCE4"><a href="#启动-XFCE4" class="headerlink" title="启动 XFCE4"></a>启动 XFCE4</h3><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="built_in">sudo</span> startxfce4</span><br></pre></td></tr></table></figure><ul><li>防火墙弹窗选择同意。</li></ul><img                           lazyload                       alt="image"                       data-src="https://img.102465.xyz/file/1769423836445_imgs202404282021946.png"                         width="75%"                 ><ul><li><strong>小技巧</strong>：使用多终端避免 log 阻塞命令。</li></ul><img                           lazyload                       alt="image"                       data-src="https://img.102465.xyz/file/1769423823202_imgs202404282021851.png"                         width="75%"                 ><ul><li><strong>解决锁屏卡死问题</strong>：</li></ul><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="built_in">sudo</span> apt purge xfce4-screensaver</span><br></pre></td></tr></table></figure><hr><h2 id="安装-Bochs"><a href="#安装-Bochs" class="headerlink" title="安装 Bochs"></a>安装 Bochs</h2><h3 id="下载与解压"><a href="#下载与解压" class="headerlink" title="下载与解压"></a>下载与解压</h3><ul><li>下载：</li></ul><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">wget https://sourceforge.net/projects/bochs/files/bochs/2.6.2/bochs-2.6.2.tar.gz</span><br></pre></td></tr></table></figure><img                           lazyload                       alt="image"                       data-src="https://img.102465.xyz/file/1769423836520_imgs202404282022435.png"                         width="75%"                 ><ul><li>解压：</li></ul><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">tar -zxvf bochs-2.6.2.tar.gz</span><br></pre></td></tr></table></figure><hr><h3 id="配置-1"><a href="#配置-1" class="headerlink" title="配置"></a>配置</h3><ul><li>进入目录并生成 Makefile：</li></ul><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br></pre></td><td class="code"><pre><span class="line"><span class="built_in">cd</span> bochs-2.6.2</span><br><span class="line">./configure \</span><br><span class="line">--prefix=/your_path/bochs \</span><br><span class="line">--enable-debugger \</span><br><span class="line">--enable-disasm \</span><br><span class="line">--enable-iodebug \</span><br><span class="line">--enable-x86-debugger \</span><br><span class="line">--with-x \</span><br><span class="line">--with-x11 \</span><br><span class="line">LDFLAGS=<span class="string">&#x27;-pthread&#x27;</span> \</span><br><span class="line">LIBS=<span class="string">&#x27;-lX11&#x27;</span></span><br></pre></td></tr></table></figure><ul><li>修改 <code>Makefile</code>：</li></ul><p>在第 92 行添加：</p><figure class="highlight makefile"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">IBS =-lm -lgtk-x11-2.0 -lgdk-x11-2.0 -latk-1.0 -lgio-2.0 -lpangoft2-1.0 -lgdk_pixbuf-2.0 -lpangocairo-1.0 -lcairo -lpango-1.0 -lfreetype -lfontconfig -lgobject-2.0 -lgmodule-2.0 -lglib-2.0 -lpthread</span><br></pre></td></tr></table></figure><hr><h3 id="编译安装"><a href="#编译安装" class="headerlink" title="编译安装"></a>编译安装</h3><ul><li>编译：</li></ul><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">make</span><br></pre></td></tr></table></figure><img                           lazyload                       alt="image"                       data-src="https://img.102465.xyz/file/1769423841201_imgs202404282023943.png"                         width="75%"                 ><ul><li>安装：</li></ul><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="built_in">sudo</span> make install</span><br></pre></td></tr></table></figure><img                           lazyload                       alt="image"                       data-src="https://img.102465.xyz/file/1769423828133_imgs202404282023112.png"                         width="75%"                 ><hr><h3 id="配置-Bochs"><a href="#配置-Bochs" class="headerlink" title="配置 Bochs"></a>配置 Bochs</h3><ul><li>创建 <code>bochsrc.disk</code> 文件：</li></ul><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="built_in">sudo</span> vim /home/fwm-0100/bochs/bin/bochsrc.disk</span><br></pre></td></tr></table></figure><ul><li>配置示例如下（路径需改为实际安装路径）：</li></ul><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">romimage: file=/home/fwm-0100/bochs/share/bochs/BIOS-bochs-latest</span><br><span class="line">vgaromimage: file=/home/fwm-0100/bochs/share/bochs/VGABIOS-lgpl-latest</span><br></pre></td></tr></table></figure><hr><h3 id="运行-Bochs"><a href="#运行-Bochs" class="headerlink" title="运行 Bochs"></a>运行 Bochs</h3><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line"><span class="built_in">cd</span> /home/fwm-0100/bochs/bin</span><br><span class="line">./bochs</span><br></pre></td></tr></table></figure><ul><li>界面截图：</li></ul><img                           lazyload                       alt="image"                       data-src="https://img.102465.xyz/file/1769423839174_imgs202404282023846.png"                         width="75%"                 ><hr><h2 id="参考文档"><a href="#参考文档" class="headerlink" title="参考文档"></a>参考文档</h2><ul><li>《操作系统真相还原》</li><li><a class="link"   href="https://www.cnblogs.com/blauendonau/p/14166062.html" >VcXsrv 图形化界面 - 博客园<i class="fas fa-external-link-alt"></i></a></li><li><a class="link"   href="https://blog.csdn.net/qq_45746571/article/details/129846595" >WSL2 + Bochs 安装指南 - CSDN<i class="fas fa-external-link-alt"></i></a></li></ul>]]>
    </content>
    <id>https://x4ai.cn/2023/04/22/Bochs%E7%8E%AF%E5%A2%83%E9%85%8D%E7%BD%AE%E8%AE%B0%E5%BD%95/</id>
    <link href="https://x4ai.cn/2023/04/22/Bochs%E7%8E%AF%E5%A2%83%E9%85%8D%E7%BD%AE%E8%AE%B0%E5%BD%95/"/>
    <published>2023-04-22T08:41:20.000Z</published>
    <summary>记录在WSL2环境下配置Bochs虚拟机的详细步骤，包括安装WSL2、配置图形界面、安装Bochs及其配置方法。</summary>
    <title>Bochs环境配置记录</title>
    <updated>2026-06-06T02:01:51.544Z</updated>
  </entry>
</feed>
