<?xml version="1.0" encoding="utf-8" standalone="yes"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:content="http://purl.org/rss/1.0/modules/content/">
  <channel>
    <title>Posts on LiuSG&#39;s Blog</title>
    <link>https://liushiguang.com/posts/</link>
    <description>Recent content in Posts on LiuSG&#39;s Blog</description>
    <image>
      <title>LiuSG&#39;s Blog</title>
      <url>https://liushiguang.com/%3Clink%20or%20path%20of%20image%20for%20opengraph,%20twitter-cards%3E</url>
      <link>https://liushiguang.com/%3Clink%20or%20path%20of%20image%20for%20opengraph,%20twitter-cards%3E</link>
    </image>
    <generator>Hugo -- 0.154.0</generator>
    <language>zh</language>
    <lastBuildDate>Wed, 14 Jan 2026 02:34:14 +0800</lastBuildDate>
    <atom:link href="https://liushiguang.com/posts/index.xml" rel="self" type="application/rss+xml" />
    <item>
      <title>VideoRefer Suite: Advancing Spatial-Temporal Object Understanding  with Video LLM 论文笔记</title>
      <link>https://liushiguang.com/posts/videorefer/</link>
      <pubDate>Sat, 10 Jan 2026 17:16:25 +0800</pubDate>
      <guid>https://liushiguang.com/posts/videorefer/</guid>
      <description>视频由动态序列构成，其不仅呈现视觉内容，还传递了不同事件与对象之间的时序和关联关系;现有的视频大语言模型（Video LLMs）主要侧重于整体场景理解,这些方法往往难以捕捉视频内容中细微的要素 Task Formulation 视频指代任务旨在理解用户在&amp;hellip;</description>
      <content:encoded><![CDATA[<h3 id="background">Background</h3>
<ol>
<li>视频由动态序列构成，其不仅呈现视觉内容，还传递了不同事件与对象之间的时序和关联关系;</li>
<li>现有的视频大语言模型（Video LLMs）主要侧重于整体场景理解,这些方法往往难以捕捉视频内容中细微的要素。</li>
</ol>
<h4 id="task-formulation">Task Formulation</h4>
<p>视频指代任务旨在理解用户在视频中指定时刻或时间段内提及的特定区域。基础视频指代任务侧重于描述性标注，而更复杂的任务则涉及推理物体间关系，并推断其未来状态或交互行为。</p>
<p>对于给定的视频 <code>$V \in \mathbb{R}^{N \times W \times H \times C}$</code>，其中 <code>$N, W, H, C$</code> 分别代表帧数、宽度、高度和通道数。将所有“对象”（<code>&lt;object&gt;</code>）定义为 <code>$\boldsymbol{R}$</code>，其中 <code>$\boldsymbol{R} = \{R_1, R_2, \dots, R_n\}$</code> 。这里，<code>$n$</code> 表示用户指定的对象总数 。<code>$R_j$</code> 表示为 <code>$R_j = \{r_{ij} \mid i \in \boldsymbol{T}\}$</code>，其中 <code>$r_{ij}$</code> 代表单个帧内的一个区域，而 <code>$\boldsymbol{T}$</code> 是包含一个或多个时间戳的集合。</p>
<p>对于视频大语言模型（Video LLM），模型优化过程旨在最大化在给定 <code>$V, \boldsymbol{R}$</code> 以及基于文本的提示 <code>$x$</code> 的条件下，在整个训练数据集中生成文本的对数似然，以产生所需的输出 ：
<code>$$\mathcal{L} = \sum_{(V, \boldsymbol{R}, x, y)} \log P(y \mid V, R_1, \dots, R_n, x)$$</code></p>
<p>其中 <code>$y$</code> 表示地面真实标签（Ground Truth Label）。</p>
<h3 id="contribution">Contribution</h3>
<p>
<div class="post-img-view">
<a data-fancybox="gallery" href="https://bucket.liushiguang.com/picturebed/VideoRefer-1.png">
<img src="https://bucket.liushiguang.com/picturebed/VideoRefer-1.png" alt="VideoRefer-1"  />
</a>
</div>

</p>
<ol>
<li>首先，本文引入多智能体数据引擎，精心构建了大规模高质量的对象级视频指令数据集VideoRefer-700K；</li>
<li>其次，本文提出VideoRefer模型，该模型配备多功能时空对象编码器，以捕捉精确的区域与序列表征</li>
<li>最后，本文细致创建了VideoRefer-Bench，从多维度全面评估视频大语言模型的时空理解能力。</li>
</ol>
<h3 id="architecture">Architecture</h3>
<p>本文在一个成熟的视频大语言模型 VideoLLaMA2.1 的基础上构建模型。本文的主要创新在于引入了一个通用且统一的空间-时间对象编码器，以获取跨视频场景的对象级表征。

<div class="post-img-view">
<a data-fancybox="gallery" href="https://bucket.liushiguang.com/picturebed/VideoRefer-2.png">
<img src="https://bucket.liushiguang.com/picturebed/VideoRefer-2.png" alt="VideoRefer-2"  />
</a>
</div>

</p>
<h4 id="a-versatile-spatial-temporal-object-encoder">A Versatile Spatial-Temporal Object Encoder</h4>
<p>为支持各类时空视频理解任务，本文提出的对象编码器不仅能在特定时间戳的单帧图像中捕捉掩码级空间特征，还能在持续时间段内聚合多帧间的时序信息。为此，本文为对象编码器设计了两种模式：单帧模式与多帧模式。</p>
<h5 id="single-frame">Single-Frame</h5>
<p>对于单帧模式，输入由一个随机选择的帧以及用户在该帧中指定的相应区域组成 。这里，<code>$\boldsymbol{T}$</code> 仅包含一个随机选择的时间戳 。为了生成对象级标记表示，本文提出了<strong>空间标记提取器 (Spatial Token Extractor)</strong> 。</p>
<p>详细而言，首先通过共享视觉编码器提取图像特征，生成全局图像特征 <code>$F_I \in \mathbb{R}^{1 \times H_I \times W_I \times D_I}$</code>，其中 <code>$H_I, W_I, D_I$</code> 分别代表图像特征的高度、宽度和维度 。每个对象的二值掩码 <code>$M$</code> 随后被调整大小以匹配图像特征的形状 。本文利用<strong>掩码池化 (Mask Pooling)</strong> 操作处理图像特征，为每个掩码提取对象级空间特征 <code>$F_O \in \mathbb{R}^{1 \times D_I}$</code>，该操作会池化区域 <code>$M$</code> 内的所有特征以生成对象级表示 。最后，采用一个 <strong>MLP 层</strong>进行适配，并为每个对象区域产生对象级标记 <code>$O \in \mathbb{R}^{1 \times C}$</code> 。</p>
<h5 id="multi-frame">Multi-Frame</h5>
<p>在多帧模式下，输入由视频中选定的一系列帧以及它们各自对应的对象区域组成，即 <code>$T$</code> 包含视频中的一组时间戳 。使用共享视觉编码器提取帧级特征，生成图像特征 <code>$\mathbf{F}_I \in \mathbb{R}^{k \times H_I \times W_I \times D_I}$</code>，其中 <code>$k$</code> 代表选定帧的数量。随后，本文利用<strong>空间标记提取器 (Spatial Token Extractor)</strong> 为每一帧生成对象级标记，得到对象标记 <code>$\mathbf{O} \in \mathbb{R}^{k \times C}$</code>。</p>
<p>为了在一段时间内跨多帧聚合不同的时空对象级表示，同时减少冗余标记，本文提出了<strong>时空标记融合模块 (Temporal Token Merge Module)</strong>，该模块旨在有效捕捉时空维度上的关键对象级标记。具体而言，从空间对象标记 <code>$\mathbf{O} \in \mathbb{R}^{k \times C}$</code> 开始，首先计算每对相邻标记之间的余弦相似度，公式如下：
<code>$$S_{m,m+1} = \frac{\mathbf{O}_m \cdot \mathbf{O}_{m+1}}{\|\mathbf{O}_m\| \cdot \|\mathbf{O}_{m+1}\|}, 0 \leq m &lt; k. [cite_start]$$</code></p>
<p>随后，从 <code>$\mathbf{S}$</code> 中选出前 <code>$k-u$</code> 个相似度得分，其中 <code>$u$</code> 是一个预定义常量。相应的标记对随后被合并为一个联合体，最终产生 <code>$u$</code> 个联合体。对于每个联合体，应用简单的平均池化（Average Pooling）来生成一个独特且具有代表性的标记。最终，在每个对象经过 MLP 层处理后，生成 <code>$u$</code> 个标记（表示为 <code>$\mathbf{O} \in \mathbb{R}^{u \times C}$</code>），这确保了在不破坏空间结构的情况下，兼顾空间完整性和时空一致性。</p>
<h3 id="dataset">Dataset</h3>
<p>
<div class="post-img-view">
<a data-fancybox="gallery" href="https://bucket.liushiguang.com/picturebed/VideoRefer-3.png">
<img src="https://bucket.liushiguang.com/picturebed/VideoRefer-3.png" alt="VideoRefer-3"  />
</a>
</div>

</p>
<h4 id="multi-agent-data-engine">Multi-agent Data Engine</h4>
<p>本文的数据构建流程包含五个组件：(i) 用于名词提取的分析器；(ii) 用于对象级描述生成的标注器；(iii) 用于掩码生成的分割器；(iv) 用于对应关系验证的审核器；(v) 用于总结与精炼的优化器。该多智能体数据引擎能有效消除噪声或无关语境，确保数据保持准确性与相关性。</p>
<p><strong>Analyzer for Noun Extraction</strong>： 鉴于现有视频数据集大多包含简短的场景级描述，本文首先<strong>分析原始描述以精确捕捉句子中的名词，即视频场景中出现的物体</strong>。为此采用一个分析器（Qwen2-Instruct-7B）来提取名词，包括主语及其他相关名词。</p>
<p><strong>Annotator for Object-level Caption Generation</strong>：为获取所提取名词的详细描述，本文采用通用视频理解模型（InternVL2-26B）作为标注工具，<strong>提示该模型专门针对物体本身提供全面描述，而非视频的整体叙事</strong>。为提高准确性与细节丰富度，本文对模型进行两次查询：分别强调动态行为与运动，以及突出静态外观与状态。</p>
<p><strong>Segmentor for Mask Generation</strong>：为获取每个提取名词的像素级掩码作为对象级区域表征，本文首先从视频中随机选择一帧，将提取的名词作为输入文本提示，通过开放集定位使用GroundingDINO提取边界框。随后，采用HQSAM基于对应的框提示生成高质量掩码。为适应多帧输入，进一步使用SAM 2为每一视频帧生成掩码。</p>
<p><strong>Reviewer for Correspondence Verification</strong>：为解决数据构建流程中可能存在的误差与不匹配问题，本文引入审查机制，通过&quot;评审模块&quot;验证掩码与文本描述的对应关系。具体流程如下：首先运用Osprey为特定帧生成区域级描述，随后评审模块（Qwen2-Instruct-7B）将判断Osprey生成的描述与标注器提供的描述是否指向同一对象。经过此筛选流程，我们仅保留40%的样本以确保数据准确性。</p>
<p><strong>Refiner for Summarization&amp;Refinement</strong>：最后，本文采用可靠的优化器GPT-4o对标注器生成的时空与外观描述进行总结和提炼。这一过程旨在进一步消除重复描述与幻觉内容，从而确保最终生成的对象级指令遵循数据集具备连贯性与准确性。</p>
<h4 id="data-characteristics">Data Characteristics</h4>
<p>通过运用multi-agent data engine，本文精心构建了三种主要类型的物体级视频指令数据：详细描述、简短描述以及多轮问答对。</p>
<p><strong>Object-level Detailed Caption</strong>：本文使用了大规模数据集Panda-70M的一个子集，该数据集为每个视频提供了简短描述。通过完整的多智能体数据引擎，生成了12.5万条高质量的对象级详细描述。</p>
<p><strong>Object-level Short Caption</strong>：为生成简短描述（主要用于预训练中对象级编码器与大语言模型的对齐），本文采用了pipeline中的分析器和分割器。具体而言，在分析器中，仅提取单数名词，从而能够复用原始描述来生成简短说明。通过这种方法，生成了50万条简短描述。</p>
<p><strong>Object-level QA</strong>：为生成明确指定特定对象或其关系的指令数据，本文收集了MeViS、Ref-YouTube-VOS 和A2DSentence数据集。这两类数据集均提供可靠的简短描述，并为每个对象区域提供掩码标注。利用这些简短描述和带掩码的视频，本文首先使用Annotator为每个区域生成对象级描述，随后通过多种提示词，调用Refiner生成与视频中对象相关的问答对。本文创建了三种基于区域的问答数据类型：（一）基础问题：涵盖对象类型、属性、动作、位置及随时间变化的交互关系。（二）推理问题：需要借助推理和背景知识来解释事件，而不依赖具体视觉细节。（三）未来预测：涉及对给定对象相关未来动作或事件的预测。共计生成7.5万组问答对。</p>
<h3 id="benchmark">Benchmark</h3>
<p>为全面评估模型在视频区域理解方面的能力，本文开发了一个名为VideoRefer-Bench的基准测试集。该基准从两个关键维度对模型进行评估：描述生成（对应<code>$\text{VideoRefer-Bench}^{\text{D}}$</code>）与多项选择问答（对应<code>$\text{VideoRefer-Bench}^{\text{Q}}$</code>）。

<div class="post-img-view">
<a data-fancybox="gallery" href="https://bucket.liushiguang.com/picturebed/VideoRefer-4.png">
<img src="https://bucket.liushiguang.com/picturebed/VideoRefer-4.png" alt="VideoRefer-4"  />
</a>
</div>

</p>
<h4 id="textvideorefer-benchtextd"><code>$\text{VideoRefer-Bench}^{\text{D}}$</code></h4>
<p><code>$\text{VideoRefer-Bench}^{\text{D}}$</code>是专门设计用于评估基于视频的指代模型在描述生成方面的性能。该基准共包含400条经人工筛选的数据条目。本文基于Panda-70M数据集，采用Multi-agent Data Engine中所述的流程构建测试集，并进行了细致的人工核查。此外，本文文开发了一套基于GPT-4o模型的评估流程，通过从0到5的分值范围，在以下四个维度对模型生成的预测结果进行严格评估：</p>
<p><strong>主体对应性（SC）</strong>：该维度评估生成描述的主体是否与真值中指定的主体准确对应。<br>
<strong>外观描述（AD）</strong>：该标准评估外观相关细节的准确性，包括颜色、形状、纹理及其他相关视觉属性。<br>
<strong>时序描述（TD）</strong>：该方面分析对物体运动的表征是否与实际运动保持一致。<br>
<strong>幻觉检测（HD）</strong>：该维度通过判断生成描述是否包含任何现实中不存在的事实、动作或元素（如想象性解读或错误推断）来识别差异。

<div class="post-img-view">
<a data-fancybox="gallery" href="https://bucket.liushiguang.com/picturebed/VideoRefer-5.png">
<img src="https://bucket.liushiguang.com/picturebed/VideoRefer-5.png" alt="VideoRefer-5"  />
</a>
</div>

</p>
<h4 id="textvideorefer-benchtextq"><code>$\text{VideoRefer-Bench}^{\text{Q}}$</code></h4>
<p><code>$\text{VideoRefer-Bench}^{\text{Q}}$</code>旨在评估多模态大语言模型在解析视频对象方面的能力。本文精心构建了一个包含198个视频的数据集，视频来源涵盖DAVIS-2017和MeViS测试集等多个数据集。为确保评估的严谨性，本文标注了1000道高质量选择题，这些问题设计用于检验不同维度的理解能力，包括基础问题、序列问题、关系问题、推理问题及未来预测。关键的是，每个问答对都必须明确关联到视频的特定区域，这确保多模态大语言模型无法在不实际分析视频或指定对象的情况下作出回答。</p>
]]></content:encoded>
    </item>
    <item>
      <title>Describe Anything: Detailed Localized Image and Video Captioning 论文笔记</title>
      <link>https://liushiguang.com/posts/describe-anything/</link>
      <pubDate>Sun, 04 Jan 2026 03:13:37 +0800</pubDate>
      <guid>https://liushiguang.com/posts/describe-anything/</guid>
      <description>图像描述长期以来一直是计算机视觉和自然语言处理领域中的一个挑战。大多数现有视觉语言模型（如GPT-4o）缺乏精确定位机制，近期赋予视觉语言模型（VLM）处理二维定位信息（如边界框）的研究方法通常仅生成简短短语，而非详细描述&amp;hellip;</description>
      <content:encoded><![CDATA[<h3 id="background">Background</h3>
<p>图像描述长期以来一直是计算机视觉和自然语言处理领域中的一个挑战。大多数现有视觉语言模型（如GPT-4o）缺乏精确定位机制，近期赋予视觉语言模型（VLM）处理二维定位信息（如边界框）的研究方法通常仅生成简短短语，而非详细描述；
尽管存在一些方法能够生成长描述，但其提供的细节有限，或混杂了来自其他区域的无关内容，如图所示：</p>
<p>
<div class="post-img-view">
<a data-fancybox="gallery" href="https://bucket.liushiguang.com/picturebed/Describe%20Anything-1.png">
<img src="https://bucket.liushiguang.com/picturebed/Describe%20Anything-1.png" alt="Describe Anything-1"  />
</a>
</div>

</p>
<p>本文确定了Detailed localized captioning所面临的三个关键障碍：</p>
<ol>
<li><strong>区域细节丢失：</strong> 如图所示，现有方法从全局图像表征中提取局部特征，这往往导致细粒度细节的丢失；裁剪感兴趣区域虽能增强细节表现，但可能丢失必要的上下文线索。

<div class="post-img-view">
<a data-fancybox="gallery" href="https://bucket.liushiguang.com/picturebed/Describe%20Anything-2.png">
<img src="https://bucket.liushiguang.com/picturebed/Describe%20Anything-2.png" alt="Describe Anything-2"  />
</a>
</div>

</li>
<li><strong>高质量数据集的稀缺性：</strong> 诸如RefCOCOs和Visual Genome 等数据集通常仅提供<strong>简短短语</strong>，不足以训练模型生成丰富、详细的描述。近期基于合成数据的方法依赖于边界框，这些边界框无法精确传达目标区域的具体范围，而依赖全局描述的方法则难以捕捉非显著性区域。</li>
<li><strong>基准测试的局限性：</strong> 现有的局部化描述基准测试通常采用基于语言的图像描述评估指标或基于大语言模型的评分方法，将生成的描述与参考描述进行比较。然而，此类技术并不完全适用于密集局部化描述任务。由于基准测试提供的参考描述往往缺乏对区域的全面细节，密集局部化描述模型常因正确描述了参考文本中未明确提及的细节而受到不公正的评分。</li>
</ol>
<h3 id="contribution">Contribution</h3>
<p>将本文的贡献总结如下：</p>
<ol>
<li><strong>Describe Anything Model（DAM）：</strong> 一种采用焦点提示与局部视觉主干网络的新型架构，用于多粒度区域图像与视频描述生成。</li>
<li><strong>SSL Data Pipeline（DLC-SDP）：</strong> 一种利用高质量分割标注与未标注网络图像的半监督数据流水线，用于可扩展且多样化的数据构建。</li>
<li><strong>DLC-Bench（DLC-Bench）：</strong> 一个设计用于在无参考描述条件下评估描述局部化控制（DLC）能力的基准测试集。</li>
</ol>
<h3 id="method">Method</h3>
<p>Describe Anything Model（DAM）能够针对用户在图像和视频中指定的区域生成详细的局部化描述。通过本文提出的焦点提示与局部化视觉主干网络，DAM有效平衡了局部细节与上下文信息。</p>
<h4 id="task-formulation">Task Formulation</h4>
<p>详细局部描述任务涉及生成专门针对图像或视频内指定区域的全面文本描述。形式上，给定 $N$ 个输入帧 $I^{(i)} \in \mathbb{R}^{H \times W \times 3}$ 以及指示每一帧中感兴趣区域的对应二值掩码 $M^{(i)} \in {0, 1}^{H \times W}$ ，目标是通过描述模型生成该区域内容的详细描述 $T$ ：
$$T = \text{CaptioningModel} \left( {I^{(i)}, M^{(i)}}_{i=1}^N \right)$$
本文重点使用二值掩码 $M^{(i)}$ 作为定位输入，因为其他形式的定位（例如点、涂鸦、框，或图像上的掩码以及视频帧子集上的掩码）可以通过分割模型（如 SAM 和 SAM 2 ）转换为掩码。</p>
<h4 id="architecture">Architecture</h4>
<p>
<div class="post-img-view">
<a data-fancybox="gallery" href="https://bucket.liushiguang.com/picturebed/Describe%20Anything-3.png">
<img src="https://bucket.liushiguang.com/picturebed/Describe%20Anything-3.png" alt="Describe Anything-3"  />
</a>
</div>

</p>
<h5 id="focal-prompt">Focal Prompt</h5>
<p>首先提取掩码$M$的边界框$B$，并在水平和垂直方向上按系数$\alpha$进行扩展，以包含额外的周边上下文：
$$
B^{&rsquo;} = \text{ExpandBox}(B, \alpha)
$$
随后，图像和掩码的焦点裁剪区域为：
$$
I^{&rsquo;} = I \vert B^{&rsquo;}, \quad M^{&rsquo;} = M \vert B^{&rsquo;}
$$
where $\vert B^{&rsquo;}$ denotes cropping to $B^{&rsquo;}$.
通过同时包含完整图像和局部裁剪图及其掩码，焦点提示既包含了全局上下文信息，又提供了感兴趣区域的详细视图。</p>
<h5 id="localized-vision-backbone">Localized Vision Backbone</h5>
<p>Handling Localization Inputs：完整图像 $I$ 及其掩码 $M$ 经过补丁嵌入层处理，随后通过全局视觉编码器 $f_{G}(\cdot)$ 获得全局视觉特征 $\mathbf{z}$。聚焦裁剪图像 $I^{&rsquo;}$ 及其掩码 $M^{&rsquo;}$ 则经过类似流程，由区域视觉编码器 $f_{R}(\cdot)$ 处理，不同之处在于 $f_{R}(\cdot)$ 同时将 $\mathbf{z}$ 作为上下文输入，以获得最终融合的视觉特征 $\mathbf{z}^{&rsquo;}$。具体表达式如下：
$$
\mathbf{x} = E_{\text{I}}(I) + E_{\text{M}}(M) + P, \quad \mathbf{z} = f_{\text{G}}(\mathbf{x})
$$
$$
\mathbf{x}&rsquo; = E_{\text{I}}(I&rsquo;) + E_{\text{M}}(M&rsquo;) + P, \quad \mathbf{z}&rsquo; = f_{\text{R}}(\mathbf{x}&rsquo;, \mathbf{z})
$$
Regional Feature Encoding with Gated Cross-Attention Adapters：为将全局上下文融入焦点提示，本文在区域视觉编码器$f_{R}$的每个Transformer模块中插入门控交叉注意力适配器。在自注意力层和前馈层之后，添加了一个门控交叉注意力机制，使局部特征能够关注全局特征：
$$\mathbf{h}^{(l)&rsquo;} = \mathbf{h}^{(l)} + \tanh\left(\gamma^{(l)}\right) \cdot \text{CrossAttn}\left(\mathbf{h}^{(l)}, \mathbf{z}\right)$$
$$\mathbf{h}_{\text{Adapter}}^{(l)} = \mathbf{h}^{(l)&rsquo;} + \tanh\left(\beta^{(l)}\right) \cdot \text{FFN}\left(\mathbf{h}^{(l)&rsquo;}\right)$$
其中，$\mathbf{h}^{(l)}$ 是 $f_R$ 中第 $l$ 个自注意力模块的输出，$\gamma^{(l)}$ 和 $\beta^{(l)}$ 是可学习的缩放参数（初始化为零）。为减少参数量，$f_R$ 与 $f_G$ 共享自注意力模块的权重。</p>
<h4 id="extension-to-videos">Extension to Videos</h4>
<p>由于图像可视为仅含单帧的视频，该模型自然能够通过处理帧序列及其对应掩码来扩展至视频处理。所有帧的视觉特征在序列维度上进行拼接，并输入语言模型以生成跨视频帧的详细定位描述，这与视觉语言模型预训练时处理视频的方式兼容。本文利用SAM 2 将稀疏定位信息转换为每帧的掩码。</p>
<h3 id="dlc-sdp-ssl-based-data-pipeline">DLC-SDP: SSL-based Data Pipeline</h3>
<h4 id="stage-1-leveraging-existing-annotations">Stage 1: Leveraging Existing Annotations</h4>
<p>DLC-SDP的第一阶段将数据生成问题重构为视觉基础的描述扩展任务。本文利用现有分割数据集中高质量的人工标注掩码和关键词（物体类别名称、部件名称、实体等），将视觉语言模型的查询任务重构为：在给定参考掩码的条件下，将每个区域关键词扩展为详细的描述性标题。</p>
<h4 id="stage-2-ssl-with-unlabeled-data">Stage 2: SSL with Unlabeled Data</h4>
<p>由于依赖高质量人工标注不具备可扩展性，DLC-SDP的第二阶段采用了基于自训练的半监督学习技术，本文自训练方法包含四个步骤：</p>
<ol>
<li>掩码生成。采用开放词汇分割模型从未标注的网络图像中提取物体掩码。</li>
<li>描述生成。基于标注分割数据集在DLC数据集上预训练的DAM模型，为这些区域生成详细的局部化描述。</li>
<li>置信度过滤。遵循自监督学习文献的方法，应用基于CLIP的置信度过滤机制，仅保留高质量样本。</li>
<li>数据扩展。新生成的（图像、掩码、描述）三元组被添加至训练数据集中。
此外，为支持DAM进行多粒度描述生成的能力，本文利用大型语言模型将详细描述总结为更简短的形式（如短语或短句），使DAM能够灵活地生成从简洁短语到多句子叙述的描述文本。</li>
</ol>
<h3 id="dlc-bench-benchmark-for-dlc">DLC-Bench: Benchmark for DLC</h3>
<p>本文推出了DLC-Bench，这是一个专为密集图像描述（DLC）设计的基准测试，旨在消除对完整参考描述的需求。DLC-Bench的核心思想在于：理想的描述应包含丰富的相关细节，同时严格避免事实性错误或涉及无关区域的信息。因此，我们通过为每个区域预定义一组正向与负向属性来评估预测结果。

<div class="post-img-view">
<a data-fancybox="gallery" href="https://bucket.liushiguang.com/picturebed/Describe%20Anything-4.png">
<img src="https://bucket.liushiguang.com/picturebed/Describe%20Anything-4.png" alt="Describe Anything-4"  />
</a>
</div>


如图所示，针对DAM类模型的评估过程包含两个步骤：1. 模型被要求为基准数据集中的每个掩码区域生成详细描述。2. 由一个大语言模型担任评估者，通过回答一组人工设计的关于区域细节的正面与负面问题，对生成的描述进行评判。</p>
<p>该方法提供了更为灵活和准确的评估，鼓励模型生成信息丰富且描述精准的文本，而无需受限于不完整的参考标注。</p>
]]></content:encoded>
    </item>
    <item>
      <title>Omni-RGPT: Unifying Image and Video Region-level Understanding via Token Marks 论文笔记</title>
      <link>https://liushiguang.com/posts/omni-rgpt/</link>
      <pubDate>Sun, 04 Jan 2026 03:13:03 +0800</pubDate>
      <guid>https://liushiguang.com/posts/omni-rgpt/</guid>
      <description>如何构建一种通用方法以有效解决跨图像与视频的特定区域任务，仍是一个待突破的挑战。一个关键挑战在于&lt;strong&gt;实现视频序列的可扩展性&lt;/strong&gt;。由于视频可能包含大量帧，依赖边界框坐标作为文本输入的方法面临扩展限制，因为输入的区域标记会随帧数线性增长。基于RoI的方法同样存在此问题，因为它们需要从空间区域重复提取视觉特征（如下图a所示）。而依赖单帧（例如初始帧）作为替代方案亦非最优选择，因为其在后续帧中缺乏对目标的可靠参照&amp;hellip;</description>
      <content:encoded><![CDATA[<h3 id="background">Background</h3>
<p>如何构建一种通用方法以有效解决跨图像与视频的特定区域任务，仍是一个待突破的挑战。</p>
<p>一个关键挑战在于<strong>实现视频序列的可扩展性</strong>。由于视频可能包含大量帧，依赖边界框坐标作为文本输入的方法面临扩展限制，因为输入的区域标记会随帧数线性增长。基于RoI的方法同样存在此问题，因为它们需要从空间区域重复提取视觉特征（如下图a所示）。而依赖单帧（例如初始帧）作为替代方案亦非最优选择，因为其在后续帧中缺乏对目标的可靠参照。</p>
<p>
<div class="post-img-view">
<a data-fancybox="gallery" href="https://bucket.liushiguang.com/picturebed/Omni-RGPT-1.png">
<img src="https://bucket.liushiguang.com/picturebed/Omni-RGPT-1.png" alt="Omni-RGPT-1"  />
</a>
</div>

</p>
<p>另一个挑战在于解决时序漂移问题。目前尚无标准化方法能够将不同帧中表示同一物体的多个向量（例如每帧中的边界框）统一为单一且一致的向量。这一问题在视频中尤为突出，因为目标物体在帧间常因运动、尺度变化和视角改变而发生外观变化。因此，将RoI区域特征合并为单一表征可能会引入不一致性，导致关键视觉细节的丢失。我们发现先前方法的一个关键局限在于其依赖的表征可能无法在帧间持续稳定地捕获目标区域，尤其是在追求图像与视频统一解决方案的背景下。</p>
<h3 id="contribution">Contribution</h3>
<p>本文提出了Omni-RGPT，这是一个多模态大语言模型，旨在实现对图像和视频的区域级理解。Omni-RGPT在基于图像和视频的常识推理基准测试中取得了最先进的性能，同时在描述性生成和指代表达理解任务中展现出强大能力。</p>
<ul>
<li>引入了Token Mark来保证模型在时空维度上保持一致的区域表征；</li>
<li>引入了一项辅助任务，进一步支持无需轨迹标注的鲁棒视频理解。该任务利用标记的一致性引导Token Mark，实现视频中稳定的区域解析；</li>
<li>构建了一个大规模区域级视频指令数据集（RegVID300k）。</li>
</ul>
<h3 id="method">Method</h3>
<p>本文基于 <strong>LLaVA</strong> 的核心设计，其中输入的图像或视频 <code>$X \in \mathbb{R}^{T \times 3 \times H_0 \times W_0}$</code>（图像的 <code>$T=1$</code>）由<strong>视觉编码器</strong> <code>$f(\cdot)$</code> 处理，产生视觉特征。通过一个<strong>投影层</strong>，这些视觉特征随后被投影为<strong>视觉标记</strong> <code>$V \in \mathbb{R}^{T \times D \times H \times W}$</code>，其中 <code>$D$</code> 是大语言模型（LLM）的输入维度。随后，视觉标记由 LLM <code>$\mathcal{F}_{LLM}(\cdot)$</code> 配合文本提示词进行处理，从而实现跨文本和视觉模态的<strong>联合推理</strong>。</p>
<p>模型的目标是通过引入 <code>$N$</code> 个输入<strong>区域提示</strong> <code>$\{m_i\}_{i=1}^N$</code>，使模型能够响应输入文本提示并理解特定的视觉元素，其中每个 <code>$m_i \in \{0, 1\}^{H_0 \times W_0}$</code> 定义了一个目标区域（例如<strong>边界框</strong>或<strong>掩码</strong>）。这些区域提示与文本提示中作为占位符的特殊标记 <code>&lt;region&gt;</code> 相对应，用于在<strong>时空维度</strong>上识别和推断指定区域。

<div class="post-img-view">
<a data-fancybox="gallery" href="https://bucket.liushiguang.com/picturebed/Omni-RGPT-2.png">
<img src="https://bucket.liushiguang.com/picturebed/Omni-RGPT-2.png" alt="Omni-RGPT-2"  />
</a>
</div>

</p>
<h4 id="token-mark">Token Mark</h4>
<p>将 <strong>令牌标记 (Token Mark)</strong> 定义为一组令牌 <code>$F \in \mathbb{R}^{N_F \times C}$</code>，其中 <code>$N_F$</code> 是令牌的总数，<code>$C$</code> 表示特征维度。为了使用 Token Mark 表示一个区域，从 <code>$[N_F]$</code> 中无放回地均匀采样 <code>$N$</code> 个索引，获得令牌集 <code>$R = \{r_i\}_{i=1}^N$</code> 。每个采样的令牌 <code>$r_i$</code> 随后与对应的区域提示 <code>$m_i$</code> 一一匹配，使得第 <code>$i$</code> 个 Token Mark 与第 <code>$i$</code> 个区域提示对齐。</p>
<p>这些令牌作为时空区域指示符，并被注入到相关视觉内容的语言侧输入中。具体而言使用一个线性层将 Token Mark 直接投影到词嵌入空间：<code>$\hat{R} = \mathcal{F}_{proj}(R) \in \mathbb{R}^{N \times D}$</code> 。</p>
<p>为了将采样的令牌 <code>$r_i$</code> 与其对应的区域 <code>$m_i$</code> 联系起来，将令牌嵌入到由区域提示定义的像素中。具体来说，每个像素位置 <code>$(h, w)$</code> 处的<strong>空间令牌标记 (Spatial Token Mark)</strong> <code>$S \in \mathbb{R}^{C \times H_0 \times W_0}$</code> 的计算公式为：
<code>$$S_{:,h,w} = \frac{\sum_{i=1}^{N} m_{i,h,w} \cdot r_i}{\epsilon + \sum_{i=1}^{N} m_{i,h,w}}$$</code></p>
<p>其中 <code>$\epsilon$</code> 是一个微小的正系数，用于防止在位置 <code>$(h, w)$</code> 没有激活掩码时出现除零情况 。接着，应用自适应平均池化来下采样 <code>$S$</code>，以匹配视觉令牌 <code>$V$</code> 的形状，从而获得更新后的空间令牌标记 <code>$\tilde{S}$</code> 。然后，使用共享投影层将其投影到词嵌入空间中，得到 <code>$\hat{S} = \mathcal{F}_{proj}(\tilde{S}) \in \mathbb{R}^{D \times H \times W}$</code> 。最后，我们将空间区域特定信息整合到视觉令牌中：<code>$\hat{V} = V + \hat{S}$</code> 。</p>
<p>该方法具有几个关键优势：</p>
<p><strong>i) 防止时间漂移 (Preventing temporal drift)</strong>：通过将目标区域编码为跨帧共享的唯一表示，该方法确保了在整个视频序列中区域分配的一致性；</p>
<p><strong>ii) 直接的区域-语言连接 (Direct region-language connection)</strong>：将 Token Mark 直接投影到词嵌入空间内，能够实现高效的区域-语言关系建模；</p>
<p><strong>iii) 保持视觉-语言全局对齐 (Preserving vision-language global alignment)</strong>：通过将区域信息作为<strong>残差特征 (residual features)</strong> 整合，该架构保持了与基础图像-文本对多模态框架（如 LLaVA）的对齐 。在没有区域提示的情况下，模型的功能与基础架构完全一致。</p>
<h4 id="temporal-region-guide-head">Temporal Region Guide Head</h4>
<p>对于视频输入，在训练过程中引入了一个辅助头，以增强跨帧的区域一致性，确保即使仅在第一帧提供了区域提示（region prompt），也能实现对区域的准确表示 。该辅助头对每个视觉标记（visual token）对应的令牌标记（Token Mark）进行分类，从而隐式地引导模型理解目标区域，而无需依赖来自轨迹片段（tracklets）的显式视频对象对应关系 。</p>
<p>令 <code>$V_t$</code> 表示第 <code>$t$</code> 帧的视觉标记，从而形成整个视频的视觉标记序列，记作 <code>$V_{vid} = (\hat{V}_1, V_2, \dots, V_T)$</code>，其中 <code>$\hat{V}_1$</code> 包含目标区域信息。序列 <code>$V_{vid}$</code> 随后由语言模型处理，该模型旨在为整个视频序列生成区域感知（region-aware）的预测。</p>
<p>辅助分类头 <code>$\mathcal{F}_{aux}$</code> 的执行过程如下：
<code>$$\mathcal{F}_{aux}(\mathcal{F}_{LLM}(V_{vid})) \in \mathbb{R}^{T \times H \times W \times (N_F + 1)}$$</code></p>
<p>其中 <code>$N_F + 1$</code> 是分类类别（包括 <code>$N_F$</code> 个令牌标记和背景）。</p>
<p>由于视觉标记是从原始输入分辨率下采样（downscaled）而来的，单个视觉标记内可能存在多个令牌标记。为了处理这种情况，本文应用了<strong>软标签分类</strong>（soft-label classification），为每个标记分配一个在 <code>$N_F + 1$</code> 个类别上的软标签分布，以反映每个标记属于多个区域或背景的比例。</p>
<h4 id="loss-function">Loss Function</h4>
<p>最终损失定义为 <code>$\mathcal{L} = \mathcal{L}_{LLM} + \alpha \mathcal{L}_{aux}$</code>，其中 <code>$\alpha$</code> 用于平衡辅助分类损失的贡献。语言模型损失 <code>$\mathcal{L}_{LLM}$</code> 计算为预测令牌与ground truth令牌之间的交叉熵损失。同时，辅助分类损失 <code>$\mathcal{L}_{aux}$</code> 定义为每个视觉标记的预测软标签分布与ground truth软标签分布之间的交叉熵损失。该区域引导头仅在训练期间使用，不会在推理（inference）过程中引入额外的延迟。</p>
<h3 id="dataset">Dataset</h3>
<p>由于目前尚不存在区域级的视频指令数据集，本文提出了 <strong>RegVID-300k</strong>，旨在增强多模态大语言模型 (MLLM) 的对话能力，并获得对视频中各区域的准确响应 。本文的方法包含三个步骤：</p>
<p>i) GPT4o 辅助的区域级详细字幕生成；
ii) 视觉幻觉减轻；
iii) 字幕引导的区域级指令样本生成 。</p>
<p>数据集构成如下：

<div class="post-img-view">
<a data-fancybox="gallery" href="https://bucket.liushiguang.com/picturebed/Omni-RGPT-4.png">
<img src="https://bucket.liushiguang.com/picturebed/Omni-RGPT-4.png" alt="Omni-RGPT-4"  />
</a>
</div>


数据集的关键特征包括：</p>
<ul>
<li><strong>i) 大规模</strong>：我们的数据集包含 9.8 万个独特视频、21.4 万个轨迹或掩码片段 (tracklets/masklets) 以及 29.4 万条指令（如区域级详细字幕和对话）。</li>
<li><strong>ii) 多样化</strong>：视频收集自 10 个用于不同任务的公共数据集 。</li>
<li><strong>iii) 细粒度问答 (QAs)</strong>：每个区域的描述约 60 个词，包含区域的上下文和时间信息，从而生成多样的指令样本 。</li>
<li><strong>iv) 高保真度</strong>：减轻了详细字幕中的视觉幻觉 。
<strong>数据收集</strong>：视频收集自 10 个包含标注区域（如掩码片段、轨迹片段或单帧边界框）及名词的公共数据集 。

<div class="post-img-view">
<a data-fancybox="gallery" href="https://bucket.liushiguang.com/picturebed/Omni-RGPT-3.png">
<img src="https://bucket.liushiguang.com/picturebed/Omni-RGPT-3.png" alt="Omni-RGPT-3"  />
</a>
</div>

</li>
</ul>
<h4 id="gpt4o-assisted-region-level-captions">GPT4o-Assisted Region-level Captions</h4>
<p>从视频和区域掩码片段对中，本文借鉴了 SoM 的视觉提示技术，在视频的每一帧中，将带有区域索引的物体掩码覆盖在每个掩码的中心。随后，我们将经过 SoM 处理的视频输入 GPT4o ，通过在文本提示词中加入名词（如“生成 [0]: 猫, [1]: 猫, [2]: 手 的详细描述”），请求生成包含每个掩码片段上下文和时间信息的丰富字幕 。</p>
<h4 id="visual-hallucination-mitigation">Visual Hallucination Mitigation</h4>
<p>受到 VFC 的启发，本文应用 LLM 和 MLLM 进行多阶段的视觉幻觉减轻 。首先，利用 LLM 将详细的区域级字幕分解为多个针对字幕内容的封闭式问题 。然后，将这些问题连同视频一起输入 MLLM，以验证内容是否正确 。在第三阶段，收集在前一步中未通过验证的问题，并要求 LLM 删除原始字幕中未经验证的内容并重新生成字幕 。</p>
<h4 id="gpt-assited-region-level-instruction-data">GPT-Assited Region-level Instruction Data</h4>
<p>在最后一步中，基于字幕引导的指令微调数据构建理念，本文进一步处理字幕以生成区域级视频指令 。利用纯文本 GPT4 从详细字幕中创建区域特定的问答对，涵盖字幕的各个方面 。样本包括详细描述、总结以及针对特定区域的通用问答 。本文提供了一些上下文示例 (in-context examples) 以提高样本生成的质量 。生成的指令涵盖了上下文方面（如颜色、空间位置）和时间方面（如动作、行为）。</p>
]]></content:encoded>
    </item>
    <item>
      <title>Perceive Anything: Recognize, Explain, Caption, and Segment Anything in Images and Videos 论文笔记</title>
      <link>https://liushiguang.com/posts/perceive-anything/</link>
      <pubDate>Sat, 03 Jan 2026 22:31:26 +0800</pubDate>
      <guid>https://liushiguang.com/posts/perceive-anything/</guid>
      <description>近期研究致力于通过视觉提示赋予视觉-语言模型区域级理解能力。现有方法可分为三种范式: 这些方法通常存在若干限制：(i) 它们通常仅产生有限的语义输出——往往只是类别标签或简短描述；(ii) 其设计是模态特定的，仅专注于单一的视觉模态（图像或视频），通用性有限；(iii) 它们依赖外部分割模型来提供掩码，这种串行设计增加了计算开销，并使整体性能对掩码质量敏感&amp;hellip;</description>
      <content:encoded><![CDATA[<h3 id="background">Background</h3>
<p>近期研究致力于通过视觉提示赋予视觉-语言模型区域级理解能力。现有方法可分为三种范式：</p>
<p>
<div class="post-img-view">
<a data-fancybox="gallery" href="https://bucket.liushiguang.com/picturebed/PAM-1.png">
<img src="https://bucket.liushiguang.com/picturebed/PAM-1.png" alt="PAM-1"  />
</a>
</div>

</p>
<p>这些方法通常存在若干限制：(i) 它们通常仅产生有限的语义输出——往往只是类别标签或简短描述；(ii) 其设计是模态特定的，仅专注于单一的视觉模态（图像或视频），通
用性有限；(iii) 它们依赖外部分割模型来提供掩码，这种串行设计增加了计算开销，并使整体性能对掩码质量敏感。</p>
<p>本文提出感知万物模型（PAM），这是一种端到端的区域级视觉语言模型，旨在实现对图像和视频的快速、全面的细粒度视觉理解，其能力包括<strong>预测类别、解释已识别区域元素的定义与上下文功能，并生成针对特定区域的详细描述</strong>。</p>
<h3 id="method">Method</h3>
<p>给定视觉提示（如点、框或掩码）以指定感兴趣区域，感知万物模型（PAM）能够<strong>同时</strong>实现：</p>
<p>（1）分割：在图像或整个视频中为指定区域生成精确的分割掩码。
（2）识别：判定指定区域或对象的类别。
（3）解释：清晰阐述该区域或对象在其给定语境中的定义、属性与功能。
（4）描述：为图像、视频及视频流中的区域生成简洁或详细的描述文本。

<div class="post-img-view">
<a data-fancybox="gallery" href="https://bucket.liushiguang.com/picturebed/PAM-2.png">
<img src="https://bucket.liushiguang.com/picturebed/PAM-2.png" alt="PAM-2"  />
</a>
</div>

</p>
<h4 id="semantic-perceiver">Semantic Perceiver</h4>
<p><strong>语义感知器</strong>的架构模仿了 SAM 2 的特征融合模块 (S2-FFM)，它接收两个主要输入：</p>
<ul>
<li>来自 S2-FFM 的<strong>增强掩码标记（Enhanced mask tokens）</strong>：结合了 IoU 和提示标记信息，作为精确掩码生成的唯一标识符；</li>
<li>S2-FFM 处理后的<strong>更新图像嵌入（Updated image embeddings）</strong>：捕获通用的视觉上下文，以及通过与掩码标记交互而增强的隐式特征。

<div class="post-img-view">
<a data-fancybox="gallery" href="https://bucket.liushiguang.com/picturebed/PAM-3.png">
<img src="https://bucket.liushiguang.com/picturebed/PAM-3.png" alt="PAM-3"  />
</a>
</div>


接着将 $N_s$ 个可学习的语义标记（Semantic tokens）与增强的掩码标记连接。最后，通过语义感知器内的进一步注意力机制，我们可以提取出富含通用视觉和物体级定位信息的视觉标记。给定 $N$ 帧的输入，语义感知器输出两组 256 维向量：$64^2 \times N$ 个视觉标记和 $N_s \times N$ 个<strong>语义标记</strong>（默认 $N_s = 16$）。</li>
</ul>
<h4 id="projector">Projector</h4>
<p>位于大语言模型 (LLM) 之前的<strong>投影层</strong>包含两部分：一个像素重组 (Pixel shuffle) 操作和一个 MLP 投影器：</p>
<ul>
<li><strong>对于图像输入：</strong> 在相邻的 $2\times2$ 特征块上应用像素重组操作，以对视觉标记的数量进行下采样（减少 Token 数量）；</li>
<li><strong>对于视频输入：</strong> 被提示的帧（Prompted frame）的处理方式与单张图像类似；而视频剪辑中的其余帧则经历更激进的 $4\times4$ 像素重组操作，以显著减少视觉标记，从而进一步提高语义解码器的处理效率。
随后，使用两个独立的 MLP 分别对<strong>视觉标记</strong>和<strong>语义标记</strong>进行空间投影对齐。</li>
</ul>
<h4 id="streaming-video-encode-and-decode">Streaming Video Encode and Decode</h4>
<p>基于 SAM 2 通过记忆模块在每帧中逐步引入历史信息的机制，本文提出了一种简单直接的策略，用于实现<strong>区域级流式视频字幕生成</strong>。</p>
<p>本文会对每个视频剪辑的<strong>最后一帧</strong>额外应用一次 $2\times2$ 的像素重组操作。这会产生更高密度的视觉标记，从而改善历史视觉信息的保存，这些标记随后作为下一个视频剪辑的“初始帧”。这种方法确保了每个剪辑的处理保持一致，并有效地将上一个剪辑的关键历史信息传递到下一个。 此外，本文将<strong>之前的文本描述</strong>纳入提示词中，以进一步增强上下文历史，提升模型对正在发生的事件的理解和描述准确性。</p>
<p>在实践中，本文的框架允许用户灵活指定解码时间戳。到达指定时间戳时，模型会描述当前时间戳与前一个时间戳之间的时间间隔内的指定区域。</p>
<h4 id="training-strategies">Training Strategies</h4>
<p>采用三阶段的<strong>课程学习 (Curriculum learning)</strong> 方法来构建训练过程，逐步增强 PAM 从图像到视频的区域级视觉理解能力：</p>
<ul>
<li><strong>阶段 1：图像预训练与对齐 (Image Pretraining and Alignment)</strong> 侧重于建立视觉标记、语义标记与语言模型嵌入空间之间的稳健对齐。 主要目标是使模型能有效理解区域级图像内容。在此阶段，<strong>仅训练语义感知器和投影层</strong>。</li>
<li><strong>阶段 1.5：视频增强预训练与对齐 (Video-Enhanced Pretraining and Alignment)</strong> 通过引入区域级视频字幕来扩展图像训练，使模型能够通过整合时空视觉信息来理解动态场景。 可训练模块与阶段 1 相同。</li>
<li><strong>阶段 2：多模态微调 (Multimodal Fine-Tuning)</strong> 采用监督微调 (SFT) 使模型能够执行多样化任务并生成所需的响应。 此阶段利用了经过细化和增强的高质量数据集。训练共同涉及<strong>语义感知器、投影层和语义解码器（LLM）</strong>。</li>
</ul>
<h3 id="dataset">Dataset</h3>
<p>本文开发了一套稳健的数据精炼与增强流程，用以构建高质量训练数据集。该数据集具备三个关键特征：</p>
<p>(1) 广泛的语义粒度。提供从粗粒度（类别、定义、上下文功能）到细粒度（细节描述）的多样化视觉语义标注；
(2) 区域流式描述标注。首个专门为流式视频区域描述任务构建的标注数据集）；
(3) 双语标注，同时支持英语与中文。

<div class="post-img-view">
<a data-fancybox="gallery" href="https://bucket.liushiguang.com/picturebed/PAM-4.png">
<img src="https://bucket.liushiguang.com/picturebed/PAM-4.png" alt="PAM-4"  />
</a>
</div>

</p>
<h4 id="image-dataset">Image Dataset</h4>
<p><strong>Regional Recognition, Explanation, and Caption：</strong> 对于区域识别，本文利用了多个实例检测、分割以及场景文本识别数据集。在这种背景下，边界框或掩码作为视觉提示输入，而标签则作为输出处理。</p>
<p>为了实现超越简单分类的深度、精细视觉理解，本文提出了一个增强流程，为每个特定区域生成：<strong>清晰的概念解释、上下文功能角色以及详细描述</strong> 。为实现这一目标，本文利用“Set of Mask”方法来识别感兴趣区域，并利用原始标注作为引导，促使模型产生所需的响应，随后进行人工质量保证 。</p>
<h4 id="video-dataset">Video Dataset</h4>
<p><strong>Region-level Video Caption：</strong> 为了将模型的区域描述能力扩展至视频领域，本文收集并分析了多个现有视频数据集，包括指代检测与分割数据集，以及近期为SAV数据集构建的Sa2VA标注。这些为基于文本描述检测、分割和描述视频中特定对象而设计的数据集，其描述往往过于粗略、简单、不准确，或主要关注静态信息，忽略了物体运动、交互关系及状态变化等贯穿视频的关键时序细节。</p>
<p>本文提出故事板驱动的描述扩展方法以克服局限 ：首先从视频均匀采样六帧关键帧 ，将其合成为高分辨率故事板 ，并使用SoM技术高亮目标对象 。接着，以原始标注为条件，提示GPT-4o结合多帧整合信息生成详尽且具时序感知的描述 。这种多帧机制比单帧分析更能增强上下文理解，从而获得更高质量的描述 。</p>
<p><strong>Region-level Streaming Video Caption：</strong> 为了将模型能力扩展至流式处理模式，本文首先采TRACE-Uni模型将输入视频分割为多个独立事件，每个事件均由其时间边界界定。随后，对每个分割后的视频片段，应用相同的“故事板驱动”处理方法。为生成精确且连续的事件描述，重新设计了GPT-4o输入提示，通过迭代方式将前序视频片段的描述作为上下文信息纳入当前片段的处理流程。</p>
]]></content:encoded>
    </item>
    <item>
      <title>FERRET: REFER AND GROUND ANYTHING ANYWHERE AT ANY GRANULARITY 论文笔记</title>
      <link>https://liushiguang.com/posts/ferret/</link>
      <pubDate>Sat, 03 Jan 2026 17:11:07 +0800</pubDate>
      <guid>https://liushiguang.com/posts/ferret/</guid>
      <description>本质上，referring和grounding需要同类型的知识：空间信息与语义的对齐。尽管如此，现有研究大多将指代表达与视觉grounding作为独立任务进行学习 本文旨在探讨以下三个核心问题：1. 如何在统一框架中整合referring与gro..</description>
      <content:encoded><![CDATA[<h3 id="background">Background</h3>
<p>本质上，referring和grounding需要同类型的知识：空间信息与语义的对齐。尽管如此，现有研究大多将指代表达与视觉grounding作为独立任务进行学习。</p>
<p>本文旨在探讨以下三个核心问题：</p>
<ol>
<li>如何在统一框架中整合referring与grouning，<strong>二者能否相互促进</strong>？</li>
<li><strong>如何表征人类referring时通常使用的多样化区域类型</strong>，例如点、方框、涂鸦乃至自由形状？</li>
<li>如何使referring与grounding具备开放词汇、指令跟随及鲁棒性，这些特性对实际应用至关重要？</li>
</ol>
<h3 id="contribution">Contribution</h3>
<ol>
<li>提出了Ferret模型，它采用了一种<strong>混合区域表示方法</strong>，并配备了一种新颖的空间感知视觉采样器，从而能够在多模态大语言模型中实现细粒度和开放词汇的referring与grounding。</li>
<li>构建了GRIT数据集，这是一个用于模型训练的大规模grounding与referring指令微调数据集。该数据集还包含了额外的空间负样本，以增强模型的鲁棒性。</li>
<li>引入了Ferret-Bench评估基准，用于综合评价需要同时处理referring/grounding、语义理解、知识运用和推理能力的任务。我们的模型在广泛的任务中展现出卓越的性能，并有效减少了物体幻觉现象。</li>
</ol>
<h3 id="method">Method</h3>
<h4 id="hybrid-region-representation">Hybrid Region Representation</h4>
<p>在指代特定区域时，通常采用三种主要格式：<strong>点状、框状和自由形状</strong>。点状和框状格式可通过坐标简洁表示，而通过坐标描述自由形状不仅计算成本高昂且表达晦涩，其复杂性还会阻碍模型学习建立所给坐标与对应区域之间的清晰关联。</p>
<p>为统一处理三种不同的区域表示格式，本文提出一种混合区域表示方法，该方法将<strong>离散坐标与连续视觉特征协同融合</strong>以指向特定区域。</p>
<ul>
<li>坐标处理方面：遵循之前的方法，我们将每个坐标量化为nbins个离散区间之一。默认情况下，nbins = 1000。该值具有输入不变性，这意味着对于任何输入图像尺寸，原始坐标都将被映射到新坐标。这使得模型能够适应不同的输入分辨率。</li>
<li>连续视觉特征提取方面：对于给定区域 $R$ ，我们首先构建与原始图像尺寸相同的二维二值掩码 $M$ ，在目标区域内标记为1，区域外标记为0。随后，将该二值掩码 $M$ 与提取的图像特征图 $Z$ 共同输入至我们提出的空间感知视觉采样器 $s(\cdot)$（详见第2.2节），以提取视觉连续特征 $f = s(M, Z)$ 。
最终，点被表示为 <code>$\{x, y, \mathbf{f}_{R_{p}}\}$</code> ，$R_{p}$是以 {x, y} 为中心的固定半径的圆。框和自由形状被表示为 <code>$\{x_{min}, y_{min}, x_{max}, y_{max}, \mathbf{f}_{R_{box}}\}$</code> （我的理解就是他的外界矩形框 + $R_{box}$）。</li>
</ul>
<h4 id="architecture">Architecture</h4>
<p>
<div class="post-img-view">
<a data-fancybox="gallery" href="https://bucket.liushiguang.com/picturebed/Ferret-1.png">
<img src="https://bucket.liushiguang.com/picturebed/Ferret-1.png" alt="Ferret-1"  />
</a>
</div>

</p>
<h5 id="input">Input</h5>
<p>图像输入到预训练的Image Encoder中（CLIP-ViT-L/14），提取image embeddings $Z \in R^{H \times W \times C}$；文本输入到分词器中分词并投影为 $T \in R^{L \times D}$ ；指代区域被表示为<code>&lt;区域名称&gt; &lt;坐标&gt; &lt;SPE&gt;</code>，SPE是一个特殊标记。通过这种方式，指代区域能够与普通文本充分融合，形成完整的句子。</p>
<h5 id="spatial-aware-visual-sampler">Spatial-aware Visual Sampler</h5>
<p>给定提取的图像特征图 $\mathbf{Z} \in \mathbb{R}^{H \times W \times C}$ 和二值区域掩码 $\mathbf{M}$ ，在 $\mathbf{M}$ 内随机采样 $N$ 个正样本点（其特征通过双线性插值得到）。将 $N$ 个点送入一系列级联的模块中，每个模块包含采样，聚合和池化。</p>
<ol>
<li>采样：利用<strong>最远点采样 (FPS)</strong> 算法从 $N$ 个点中采样出 $\frac{N}{r}$ 个点，这可以保证足够的覆盖范围。</li>
<li>聚合：对于每个采样点 $x_i$，从之前的 $N$ 个点池中搜索其 $k$ 个最近邻点，从而得到一组点 ${x_{i1}, x_{i2}, \dots, x_{ik}}$ 。对于每个点组，将采样点 $x_i$ 及其邻近点的特征按以下公式进行融合：
$$h_{ik} = \sigma([\theta([\mathbf{Z}(x_{ik}) - \mathbf{Z}(x_i); C(x_{ik}) - C(x_i)]); \mathbf{Z}(x_i); C(x_i)])$$
$C(x)$ 表示点 $x$ 的二维坐标，$\theta$ 是一个线性层，用于适应相对局部特征；$\sigma$ 也是一个线性层，用于将来自邻居的局部特征与采样点特征进行融合。</li>
<li>池化：执行最大池化 (Max Pooling)，将 $k$ 个邻居特征融合为一个特征，作为该采样点的表示：
$$h_i = \max_{k:(x_{ik}) \in \text{KNNs of } x_i} h_{ik}$$
经过这三个步骤，获得了更少的点，但拥有了更<strong>稠密</strong>的特征空间，因为它结合了局部邻居特征及其相对位置。这个模块最终输出 32 个点及其特征。将这些点特征展平为一个向量，并将其投影到 LLM 嵌入的维度。最终的特征将用于替换输入中的 $\langle \text{SPE} \rangle$ 标记。</li>
</ol>
<h3 id="datasets">Datasets</h3>
<blockquote>
<p>[!CITE]
In this section, we present GRIT, a <strong>G</strong>round-and-<strong>R</strong>efer <strong>I</strong>nstruction-<strong>T</strong>uning dataset containing around 1.1M multimodal dialogues for model training.</p>
</blockquote>
<p>GRIT包含三类数据：</p>
<ol>
<li>转换为指令遵循格式的公开数据集</li>
<li>通过ChatGPT和GPT-4生成的指令微调数据</li>
<li>来自空间负例挖掘的附加数据，用于增强模型鲁棒性</li>
</ol>
<h4 id="hierarchy">HIERARCHY</h4>
<p>空间理解可以通过不同的粒度级别和任务格式来表征 。在创建数据集期间，本文从以下两个维度进行考察：</p>
<ul>
<li><strong>就粒度而言</strong>，本文确定了四个主要类别：(i) 单个物体，(ii) 物体间的关系，(iii) 特定区域的描述，以及 (iv) 基于区域的复杂推理 。</li>
<li><strong>就任务格式而言</strong>，本文将数据进一步分为三种截然不同的类型：(i) Region in - Text out数据，(ii) Text out - Region out数据，以及 (iii) Text-Region结合数据 。</li>
</ul>
<p>各类别数据详情：</p>
<ul>
<li><strong>单个物体 (Individual objects)</strong>：本文选择了 Visual Genome、Object365 等目标检测数据集 以及 RefCOCOs 和 Flickr30k-Entities 等视觉定位数据集 。此外，为了让 Ferret 理解<strong>自由形状</strong>，本文利用 SAM 对 Visual Genome 物体数据获取分割掩码，并在训练期间将其输入视觉采样器 。该部分共有 67.8 万条数据 。</li>
<li><strong>物体间关系与区域描述 (Relationships &amp; descriptions)</strong>：本文从 Visual Genome 中选择了相关数据，均采用Region in - Text out格式，共获得 17.7 万条数据 。</li>
<li><strong>基于区域的复杂推理 (Region-based complex reasoning)</strong>：针对以特定区域为中心的复杂推理，本文在 <strong>ChatGPT/GPT-4</strong> 的帮助下构建了一个新颖的数据集 。它采用了Text-Region结合的格式 。</li>
</ul>
<h4 id="gpt-assisted-visual-instruction-data-generation">GPT-ASSISTED VISUAL INSTRUCTION DATA GENERATION</h4>
<p>对话指令微调数据对 MLLM 理解人类意图生成流利响应至关重要 。利用少样本提示，基于图像的文本描述让 ChatGPT/GPT-4 生成对话 。本文通过三个步骤强调基于区域的空间知识：(i) 包含物理关系和区域描述，(ii) 在对话中为可定位区域加入坐标，(iii) 再次利用 GPT-4 对生成的对话进行精炼优化 。共收集 3.4 万条对话 。此外，利用 GLIPv2 对 LLaVA 数据进行处理，形成伪定位标注数据供训练使用 。</p>
<h4 id="spatial-negative-mining">SPATIAL NEGATIVE MINING</h4>
<p>MLLM 容易在是非题中产生幻觉 。为了解决这一问题，本文通过以下两种方式进行负样本挖掘：(i) <strong>图像条件类别定位</strong>（随机选择图像中不存在的类别）和 (ii) <strong>语义条件类别定位</strong>（利用 GPT-4 寻找与原类别极其相似但不存在的实体，如“男”对比“女”，“蓝”对比“黄”） 。共收集 9.5 万条数据，以增强模型的鲁棒性并减少幻觉 。</p>
<h3 id="benchmarks">Benchmarks</h3>
<p>本文提出<strong>Ferret-Bench</strong>，它涵盖了三类评估指代和定位能力的区域相关问题：</p>
<ul>
<li><strong>(i) 指代描述 (Referring Description)</strong>：要求模型根据指代区域与其周围物体的交互来描述该区域。</li>
<li><strong>(ii) 指代推理 (Referring Reasoning)</strong>：模型需要在一个或多个指代区域的基础上进行正确的推理。</li>
<li><strong>(iii) 对话中的定位 (Grounding in Conversation)</strong>：模型需要进行正确推理，并准确地定位/找回推理所需的物体/区域。</li>
</ul>
<p>具体而言，本文针对每种类型的问题从 COCO 验证集中随机采样ferr 40 张图像，并按照 GPT-ASSISTED VISUAL INSTRUCTION DATA GENERATION 中的指令生成流程生成问题和 GPT-4 的答案。本文将问题和图像输入 MLLMs 以获取预测答案，然后提示 GPT-4 根据真实场景描述（包括物体、关系、区域说明、全局说明）对预测答案和来自 GPT-4 的伪答案进行评分。GPT-4 会评估指代理解的精准度、物体定位效果以及语义的正确性。评分范围为 1 到 10，分数越高表示表现越好。计算预测答案得分与 GPT-4 答案得分的比率，并以百分比形式呈现，以此衡量 MLLM 的表现。</p>
]]></content:encoded>
    </item>
    <item>
      <title>Cloudflare 教程</title>
      <link>https://liushiguang.com/posts/cloudflare/</link>
      <pubDate>Thu, 01 Jan 2026 14:50:17 +0800</pubDate>
      <guid>https://liushiguang.com/posts/cloudflare/</guid>
      <description>&lt;p&gt;Cloudflare的应用还是挺多的，赛博大善人这一块。开个笔记来总结一下我常用的一些用途。&lt;/p&gt;
&lt;h2 id=&#34;基本使用&#34;&gt;基本使用&lt;/h2&gt;
&lt;p&gt;我之前一直都是，在哪个网站买的域名，就在哪里做DNS的解析。后来发现首先是Cloudflare来做DNS解析的话，有很多额外free的功能很好用，比如说提供长期的、支持子域名的SSL证书，做缓存Always Online，免费CDN加速（不过在国内好像是反向加速&amp;hellip;&amp;hellip;），人机验证等等。
拿liushiguang.com这个域名来举例：&lt;/p&gt;
&lt;h2 id=&#34;邮件路由&#34;&gt;邮件路由&lt;/h2&gt;
&lt;p&gt;&lt;em&gt;用这个方法可以理论上获得无限的邮箱。&lt;/em&gt;
原理其实很简单，就是在解析域名的时候加上几条DNS记录，使得发送到该域名（比如 &lt;code&gt;xxx@liushiguang.com&lt;/code&gt;）的邮件会被转发到你设置好的邮箱里面。当然也可以把邮件转发到Cloudflare的Worker来做一些自动化的处理操作，但其实我主要还是转发到邮箱里面。&lt;/p&gt;
&lt;h2 id=&#34;cloudflare-r2&#34;&gt;Cloudflare R2&lt;/h2&gt;
&lt;h3 id=&#34;s3兼容api&#34;&gt;S3兼容API&lt;/h3&gt;
&lt;p&gt;Cloudflare R2支持通过S3兼容的API来访问，免费额度有10G的存储空间；A类操作（PUT、LIST等）每月100w次，B类操作（GET等）每月1000w次。&lt;em&gt;白嫖就是爽。&lt;/em&gt;
以CherryStudio的S3兼容存储备份为例，把流程列一下。

&lt;div class=&#34;post-img-view&#34;&gt;
&lt;a data-fancybox=&#34;gallery&#34; href=&#34;https://bucket.liushiguang.com/picturebed/Cloudflare-1.png&#34;&gt;
&lt;img src=&#34;https://bucket.liushiguang.com/picturebed/Cloudflare-1.png&#34; alt=&#34;Cloudflare-1&#34;  /&gt;
&lt;/a&gt;
&lt;/div&gt;


S3相关的要填的信息主要有：API地址、区域、存储桶、Access Key ID以及Secret Access Key这几个值。&lt;/p&gt;
&lt;h3 id=&#34;图床&#34;&gt;图床&lt;/h3&gt;
&lt;h2 id=&#34;cloudflare-turnstile&#34;&gt;Cloudflare Turnstile&lt;/h2&gt;</description>
      <content:encoded><![CDATA[<p>Cloudflare的应用还是挺多的，赛博大善人这一块。开个笔记来总结一下我常用的一些用途。</p>
<h2 id="基本使用">基本使用</h2>
<p>我之前一直都是，在哪个网站买的域名，就在哪里做DNS的解析。后来发现首先是Cloudflare来做DNS解析的话，有很多额外free的功能很好用，比如说提供长期的、支持子域名的SSL证书，做缓存Always Online，免费CDN加速（不过在国内好像是反向加速&hellip;&hellip;），人机验证等等。
拿liushiguang.com这个域名来举例：</p>
<h2 id="邮件路由">邮件路由</h2>
<p><em>用这个方法可以理论上获得无限的邮箱。</em>
原理其实很简单，就是在解析域名的时候加上几条DNS记录，使得发送到该域名（比如 <code>xxx@liushiguang.com</code>）的邮件会被转发到你设置好的邮箱里面。当然也可以把邮件转发到Cloudflare的Worker来做一些自动化的处理操作，但其实我主要还是转发到邮箱里面。</p>
<h2 id="cloudflare-r2">Cloudflare R2</h2>
<h3 id="s3兼容api">S3兼容API</h3>
<p>Cloudflare R2支持通过S3兼容的API来访问，免费额度有10G的存储空间；A类操作（PUT、LIST等）每月100w次，B类操作（GET等）每月1000w次。<em>白嫖就是爽。</em>
以CherryStudio的S3兼容存储备份为例，把流程列一下。

<div class="post-img-view">
<a data-fancybox="gallery" href="https://bucket.liushiguang.com/picturebed/Cloudflare-1.png">
<img src="https://bucket.liushiguang.com/picturebed/Cloudflare-1.png" alt="Cloudflare-1"  />
</a>
</div>


S3相关的要填的信息主要有：API地址、区域、存储桶、Access Key ID以及Secret Access Key这几个值。</p>
<h3 id="图床">图床</h3>
<h2 id="cloudflare-turnstile">Cloudflare Turnstile</h2>
]]></content:encoded>
    </item>
    <item>
      <title>Ubuntu24.04 GTK主题应用至Nautilus</title>
      <link>https://liushiguang.com/posts/gtk%E4%B8%BB%E9%A2%98%E5%BA%94%E7%94%A8%E5%88%B0nautilus/</link>
      <pubDate>Sun, 28 Dec 2025 00:13:35 +0800</pubDate>
      <guid>https://liushiguang.com/posts/gtk%E4%B8%BB%E9%A2%98%E5%BA%94%E7%94%A8%E5%88%B0nautilus/</guid>
      <description>&lt;h3 id=&#34;gtk主题应用到nautilus&#34;&gt;GTK主题应用到Nautilus&lt;/h3&gt;
&lt;p&gt;这个问题是很早之前的一个问题了，我早就解决了，今天想起来了来记录一下。&lt;/p&gt;
&lt;h4 id=&#34;问题描述&#34;&gt;问题描述&lt;/h4&gt;
&lt;p&gt;将系统更新到Ubuntu24.04之后（其实主要是因为Gnome更新到了46了），然后在gnome-tweaks中设置Legacy Applications的时候无法应用到Nautilus以及系统Settings的样式上。&lt;/p&gt;
&lt;p&gt;&lt;sup id=&#34;fnref:1&#34;&gt;&lt;a href=&#34;#fn:1&#34; class=&#34;footnote-ref&#34; role=&#34;doc-noteref&#34;&gt;1&lt;/a&gt;&lt;/sup&gt; 貌似是因为Gnome更新之后，不能这样了，得用下面的方法：&lt;/p&gt;
&lt;p&gt;在下载的xxx主题里面找到gtk-4.0文件夹，然后用它替换掉~/.config/gtk-4.0文件夹，就ok了。&lt;/p&gt;
&lt;p&gt;&lt;em&gt;感觉还挺麻烦的。。。&lt;/em&gt;&lt;/p&gt;
&lt;div class=&#34;footnotes&#34; role=&#34;doc-endnotes&#34;&gt;
&lt;hr&gt;
&lt;ol&gt;
&lt;li id=&#34;fn:1&#34;&gt;
&lt;p&gt;怎么把主题应用到 nautilus 和其他一些应用上 
&lt;a href=&#34;https://www.reddit.com/r/gnome/comments/1dzuomw/how_to_apply_theme_to_nautilus_and_some_other_apps/?tl=zh-hans&#34;
   target=&#34;_blank&#34;&gt;
 https://www.reddit.com/r/gnome/comments/1dzuomw/how_to_apply_theme_to_nautilus_and_some_other_apps/?tl=zh-hans&lt;/a&gt;
&amp;#160;&lt;a href=&#34;#fnref:1&#34; class=&#34;footnote-backref&#34; role=&#34;doc-backlink&#34;&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;/div&gt;</description>
      <content:encoded><![CDATA[<h3 id="gtk主题应用到nautilus">GTK主题应用到Nautilus</h3>
<p>这个问题是很早之前的一个问题了，我早就解决了，今天想起来了来记录一下。</p>
<h4 id="问题描述">问题描述</h4>
<p>将系统更新到Ubuntu24.04之后（其实主要是因为Gnome更新到了46了），然后在gnome-tweaks中设置Legacy Applications的时候无法应用到Nautilus以及系统Settings的样式上。</p>
<p><sup id="fnref:1"><a href="#fn:1" class="footnote-ref" role="doc-noteref">1</a></sup> 貌似是因为Gnome更新之后，不能这样了，得用下面的方法：</p>
<p>在下载的xxx主题里面找到gtk-4.0文件夹，然后用它替换掉~/.config/gtk-4.0文件夹，就ok了。</p>
<p><em>感觉还挺麻烦的。。。</em></p>
<div class="footnotes" role="doc-endnotes">
<hr>
<ol>
<li id="fn:1">
<p>怎么把主题应用到 nautilus 和其他一些应用上 
<a href="https://www.reddit.com/r/gnome/comments/1dzuomw/how_to_apply_theme_to_nautilus_and_some_other_apps/?tl=zh-hans"
   target="_blank">
 https://www.reddit.com/r/gnome/comments/1dzuomw/how_to_apply_theme_to_nautilus_and_some_other_apps/?tl=zh-hans</a>
&#160;<a href="#fnref:1" class="footnote-backref" role="doc-backlink">&#x21a9;&#xfe0e;</a></p>
</li>
</ol>
</div>
]]></content:encoded>
    </item>
    <item>
      <title>Ubuntu桌面壁纸被替换为白屏</title>
      <link>https://liushiguang.com/posts/ubuntu%E6%A1%8C%E9%9D%A2%E5%A3%81%E7%BA%B8%E6%9B%BF%E6%8D%A2%E4%B8%BA%E7%99%BD%E5%B1%8F/</link>
      <pubDate>Sat, 27 Dec 2025 23:44:35 +0800</pubDate>
      <guid>https://liushiguang.com/posts/ubuntu%E6%A1%8C%E9%9D%A2%E5%A3%81%E7%BA%B8%E6%9B%BF%E6%8D%A2%E4%B8%BA%E7%99%BD%E5%B1%8F/</guid>
      <description>&lt;p&gt;这个问题昨晚出现的吧，刚刚被我解决掉了，记录一下解决的过程。&lt;/p&gt;
&lt;h4 id=&#34;问题描述&#34;&gt;问题描述&lt;/h4&gt;
&lt;p&gt;就是下面这样：

&lt;div class=&#34;post-img-view&#34;&gt;
&lt;a data-fancybox=&#34;gallery&#34; href=&#34;https://bucket.liushiguang.com/picturebed/%E6%A1%8C%E9%9D%A2%E5%A3%81%E7%BA%B8%E6%9B%BF%E6%8D%A2%E4%B8%BA%E7%99%BD%E5%B1%8F-1.png&#34;&gt;
&lt;img src=&#34;https://bucket.liushiguang.com/picturebed/%E6%A1%8C%E9%9D%A2%E5%A3%81%E7%BA%B8%E6%9B%BF%E6%8D%A2%E4%B8%BA%E7%99%BD%E5%B1%8F-1.png&#34; alt=&#34;桌面壁纸替换为白屏-1.png&#34;  /&gt;
&lt;/a&gt;
&lt;/div&gt;


但是我打开应用面板的时候又是正常显示的：

&lt;div class=&#34;post-img-view&#34;&gt;
&lt;a data-fancybox=&#34;gallery&#34; href=&#34;https://bucket.liushiguang.com/picturebed/%E6%A1%8C%E9%9D%A2%E5%A3%81%E7%BA%B8%E6%9B%BF%E6%8D%A2%E4%B8%BA%E7%99%BD%E5%B1%8F-2.png&#34;&gt;
&lt;img src=&#34;https://bucket.liushiguang.com/picturebed/%E6%A1%8C%E9%9D%A2%E5%A3%81%E7%BA%B8%E6%9B%BF%E6%8D%A2%E4%B8%BA%E7%99%BD%E5%B1%8F-2.png&#34; alt=&#34;桌面壁纸替换为白屏-2.png&#34;  /&gt;
&lt;/a&gt;
&lt;/div&gt;

&lt;/p&gt;
&lt;h4 id=&#34;解决过程&#34;&gt;解决过程&lt;/h4&gt;
&lt;p&gt;最开始的时候是我的nautilus里面的icon不能按照我配置的GTK的主题来显示了，就显示的是很普通那种icon，然后我想着这种问题应该log out一下再回来就可以了，问题不大。&lt;/p&gt;
&lt;p&gt;结果我log out之后，就出现了上面这种问题，我一下不知道怎么弄了。理所当然的就去google: &amp;ldquo;ubuntu24.04 desktop show white&amp;rdquo;、&amp;ldquo;ubuntu24.04 wallpaper show whole white&amp;quot;这种搜索，然后到askubuntu包括reddite等论坛看了很多帖子，都没法解决我的问题。&lt;/p&gt;
&lt;p&gt;然后当然我也重启了很多次，也没用当然，但是重启后打开桌面会显示下面的这个东西：

&lt;div class=&#34;post-img-view&#34;&gt;
&lt;a data-fancybox=&#34;gallery&#34; href=&#34;https://bucket.liushiguang.com/picturebed/%E6%A1%8C%E9%9D%A2%E5%A3%81%E7%BA%B8%E6%9B%BF%E6%8D%A2%E4%B8%BA%E7%99%BD%E5%B1%8F-3.png&#34;&gt;
&lt;img src=&#34;https://bucket.liushiguang.com/picturebed/%E6%A1%8C%E9%9D%A2%E5%A3%81%E7%BA%B8%E6%9B%BF%E6%8D%A2%E4%B8%BA%E7%99%BD%E5%B1%8F-3.png&#34; alt=&#34;桌面壁纸替换为白屏-3.png&#34;  /&gt;
&lt;/a&gt;
&lt;/div&gt;


就是可以看到，有一个叫 &amp;ldquo;@!0,0;BDHF&amp;rdquo; 的东西把我的桌面壁纸给覆盖掉了，我昨天没有很在意这个，然后就是瞎折腾了半天解决不了。&lt;/p&gt;
&lt;p&gt;今天我又看到这个，我想我搜了那么多都搜不到，为啥不直接检索这个 &amp;ldquo;@!0,0;BDHF&amp;rdquo; 看看能不能搜索出来啥东西呢？但我说实话，这个东西我也不陌生，就是之前很莫名奇妙会出现的一个我的桌面icon的重影，我也不知道怎么关掉的，但是不影响我的正常使用，我也就一直没管。&lt;/p&gt;
&lt;p&gt;结果一搜还真给我搜出来了&lt;sup id=&#34;fnref:1&#34;&gt;&lt;a href=&#34;#fn:1&#34; class=&#34;footnote-ref&#34; role=&#34;doc-noteref&#34;&gt;1&lt;/a&gt;&lt;/sup&gt;，askubuntu上面的一个帖子，然后其实问题就出在Desktop Icons Next Generation (DING)这个gnome-shell extension上面，当然我也不知道原理是啥，我就把这个extension给disable了，一切就恢复正常了！&lt;/p&gt;
&lt;div class=&#34;footnotes&#34; role=&#34;doc-endnotes&#34;&gt;
&lt;hr&gt;
&lt;ol&gt;
&lt;li id=&#34;fn:1&#34;&gt;
&lt;p&gt;What is @!0,27;BDH and why does it keep me stuck in activites overview mode? 
&lt;a href=&#34;https://askubuntu.com/questions/1342245/what-is-0-27bdh-and-why-does-it-keep-me-stuck-in-activites-overview-mode&#34;
   target=&#34;_blank&#34;&gt;
 https://askubuntu.com/questions/1342245/what-is-0-27bdh-and-why-does-it-keep-me-stuck-in-activites-overview-mode&lt;/a&gt;
&amp;#160;&lt;a href=&#34;#fnref:1&#34; class=&#34;footnote-backref&#34; role=&#34;doc-backlink&#34;&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;/div&gt;</description>
      <content:encoded><![CDATA[<p>这个问题昨晚出现的吧，刚刚被我解决掉了，记录一下解决的过程。</p>
<h4 id="问题描述">问题描述</h4>
<p>就是下面这样：

<div class="post-img-view">
<a data-fancybox="gallery" href="https://bucket.liushiguang.com/picturebed/%E6%A1%8C%E9%9D%A2%E5%A3%81%E7%BA%B8%E6%9B%BF%E6%8D%A2%E4%B8%BA%E7%99%BD%E5%B1%8F-1.png">
<img src="https://bucket.liushiguang.com/picturebed/%E6%A1%8C%E9%9D%A2%E5%A3%81%E7%BA%B8%E6%9B%BF%E6%8D%A2%E4%B8%BA%E7%99%BD%E5%B1%8F-1.png" alt="桌面壁纸替换为白屏-1.png"  />
</a>
</div>


但是我打开应用面板的时候又是正常显示的：

<div class="post-img-view">
<a data-fancybox="gallery" href="https://bucket.liushiguang.com/picturebed/%E6%A1%8C%E9%9D%A2%E5%A3%81%E7%BA%B8%E6%9B%BF%E6%8D%A2%E4%B8%BA%E7%99%BD%E5%B1%8F-2.png">
<img src="https://bucket.liushiguang.com/picturebed/%E6%A1%8C%E9%9D%A2%E5%A3%81%E7%BA%B8%E6%9B%BF%E6%8D%A2%E4%B8%BA%E7%99%BD%E5%B1%8F-2.png" alt="桌面壁纸替换为白屏-2.png"  />
</a>
</div>

</p>
<h4 id="解决过程">解决过程</h4>
<p>最开始的时候是我的nautilus里面的icon不能按照我配置的GTK的主题来显示了，就显示的是很普通那种icon，然后我想着这种问题应该log out一下再回来就可以了，问题不大。</p>
<p>结果我log out之后，就出现了上面这种问题，我一下不知道怎么弄了。理所当然的就去google: &ldquo;ubuntu24.04 desktop show white&rdquo;、&ldquo;ubuntu24.04 wallpaper show whole white&quot;这种搜索，然后到askubuntu包括reddite等论坛看了很多帖子，都没法解决我的问题。</p>
<p>然后当然我也重启了很多次，也没用当然，但是重启后打开桌面会显示下面的这个东西：

<div class="post-img-view">
<a data-fancybox="gallery" href="https://bucket.liushiguang.com/picturebed/%E6%A1%8C%E9%9D%A2%E5%A3%81%E7%BA%B8%E6%9B%BF%E6%8D%A2%E4%B8%BA%E7%99%BD%E5%B1%8F-3.png">
<img src="https://bucket.liushiguang.com/picturebed/%E6%A1%8C%E9%9D%A2%E5%A3%81%E7%BA%B8%E6%9B%BF%E6%8D%A2%E4%B8%BA%E7%99%BD%E5%B1%8F-3.png" alt="桌面壁纸替换为白屏-3.png"  />
</a>
</div>


就是可以看到，有一个叫 &ldquo;@!0,0;BDHF&rdquo; 的东西把我的桌面壁纸给覆盖掉了，我昨天没有很在意这个，然后就是瞎折腾了半天解决不了。</p>
<p>今天我又看到这个，我想我搜了那么多都搜不到，为啥不直接检索这个 &ldquo;@!0,0;BDHF&rdquo; 看看能不能搜索出来啥东西呢？但我说实话，这个东西我也不陌生，就是之前很莫名奇妙会出现的一个我的桌面icon的重影，我也不知道怎么关掉的，但是不影响我的正常使用，我也就一直没管。</p>
<p>结果一搜还真给我搜出来了<sup id="fnref:1"><a href="#fn:1" class="footnote-ref" role="doc-noteref">1</a></sup>，askubuntu上面的一个帖子，然后其实问题就出在Desktop Icons Next Generation (DING)这个gnome-shell extension上面，当然我也不知道原理是啥，我就把这个extension给disable了，一切就恢复正常了！</p>
<div class="footnotes" role="doc-endnotes">
<hr>
<ol>
<li id="fn:1">
<p>What is @!0,27;BDH and why does it keep me stuck in activites overview mode? 
<a href="https://askubuntu.com/questions/1342245/what-is-0-27bdh-and-why-does-it-keep-me-stuck-in-activites-overview-mode"
   target="_blank">
 https://askubuntu.com/questions/1342245/what-is-0-27bdh-and-why-does-it-keep-me-stuck-in-activites-overview-mode</a>
&#160;<a href="#fnref:1" class="footnote-backref" role="doc-backlink">&#x21a9;&#xfe0e;</a></p>
</li>
</ol>
</div>
]]></content:encoded>
    </item>
    <item>
      <title>Linux字体配置</title>
      <link>https://liushiguang.com/posts/linux%E5%AD%97%E4%BD%93/</link>
      <pubDate>Mon, 15 Dec 2025 00:27:45 +0800</pubDate>
      <guid>https://liushiguang.com/posts/linux%E5%AD%97%E4%BD%93/</guid>
      <description>&lt;h3 id=&#34;字体&#34;&gt;字体&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;检查已安装字体：&lt;/strong&gt; &lt;code&gt;fc-list&lt;/code&gt;
字体文件夹：&lt;code&gt;/usr/share/fonts&lt;/code&gt;、&lt;code&gt;/home/username/.local/share/fonts&lt;/code&gt;、&lt;code&gt;/home/username/.fonts&lt;/code&gt;&lt;/p&gt;
&lt;h4 id=&#34;全局安装字体&#34;&gt;全局安装字体&lt;/h4&gt;
&lt;ol&gt;
&lt;li&gt;确保&lt;code&gt;/usr/share/fonts&lt;/code&gt;目录存在&lt;/li&gt;
&lt;li&gt;将已经下载的字体文件（ttf、otf）复制到&lt;code&gt;/usr/share/fonts&lt;/code&gt;目录中：&lt;code&gt;sudo cp *.ttf /usr/share/fonts&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;为了让系统识别新字体，运行以下命令更新字体缓存：&lt;code&gt;sudo fc-cache -f -v&lt;/code&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;h4 id=&#34;当前用户安装字体&#34;&gt;当前用户安装字体&lt;/h4&gt;
&lt;ol&gt;
&lt;li&gt;确保&lt;code&gt;~/.fonts&lt;/code&gt;目录存在&lt;/li&gt;
&lt;li&gt;将下载的字体文件复制到&lt;code&gt;~/.fonts&lt;/code&gt;目录中：&lt;code&gt;cp *.ttf ~/.fonts&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;为当前用户的字体更新字体缓存：&lt;code&gt;fc-cache -f -v&lt;/code&gt;&lt;/li&gt;
&lt;/ol&gt;</description>
      <content:encoded><![CDATA[<h3 id="字体">字体</h3>
<p><strong>检查已安装字体：</strong> <code>fc-list</code>
字体文件夹：<code>/usr/share/fonts</code>、<code>/home/username/.local/share/fonts</code>、<code>/home/username/.fonts</code></p>
<h4 id="全局安装字体">全局安装字体</h4>
<ol>
<li>确保<code>/usr/share/fonts</code>目录存在</li>
<li>将已经下载的字体文件（ttf、otf）复制到<code>/usr/share/fonts</code>目录中：<code>sudo cp *.ttf /usr/share/fonts</code></li>
<li>为了让系统识别新字体，运行以下命令更新字体缓存：<code>sudo fc-cache -f -v</code></li>
</ol>
<h4 id="当前用户安装字体">当前用户安装字体</h4>
<ol>
<li>确保<code>~/.fonts</code>目录存在</li>
<li>将下载的字体文件复制到<code>~/.fonts</code>目录中：<code>cp *.ttf ~/.fonts</code></li>
<li>为当前用户的字体更新字体缓存：<code>fc-cache -f -v</code></li>
</ol>
]]></content:encoded>
    </item>
    <item>
      <title>Nginx单域名部署多项服务</title>
      <link>https://liushiguang.com/posts/nginx%E5%8D%95%E5%9F%9F%E5%90%8D%E6%9C%8D%E5%8A%A1%E9%83%A8%E7%BD%B2/</link>
      <pubDate>Thu, 16 Oct 2025 01:55:14 +0800</pubDate>
      <guid>https://liushiguang.com/posts/nginx%E5%8D%95%E5%9F%9F%E5%90%8D%E6%9C%8D%E5%8A%A1%E9%83%A8%E7%BD%B2/</guid>
      <description>这个需求其实非常的自然而然，我刚学会nginx反向代理部署服务的时候，就有这个需求：我就这一个域名，我怎么在这个域名下，部署我的多项服务？二级访问目录并不是一个好方法，主要是因为直接对该域名下的目录进行了绑定，对绑定在根域名&amp;hellip;</description>
      <content:encoded><![CDATA[<p>这个需求其实非常的自然而然，我刚学会nginx反向代理部署服务的时候，就有这个需求：我就这一个域名，我怎么在这个域名下，部署我的多项服务？</p>
<h3 id="二级访问目录">二级访问目录</h3>
<p>二级访问目录并不是一个好方法，主要是因为直接对该域名下的目录进行了绑定，对绑定在根域名上的服务很不利，容易造成污染。</p>
<p>最开始我采用的是这种方法，好处就是做SSL的时候，实际上每项服务实际上都部署在同一个域名下，SSL无需绑定泛域名。</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-nginx" data-lang="nginx"><span class="line"><span class="cl"><span class="k">server</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">	<span class="c1"># 其他配置
</span></span></span><span class="line"><span class="cl">	<span class="kn">...</span>
</span></span><span class="line"><span class="cl">	
</span></span><span class="line"><span class="cl">	<span class="c1"># Nextcloud 网盘 为例
</span></span></span><span class="line"><span class="cl">	<span class="s">location</span> <span class="s">/nextcloud/</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">					<span class="c1"># 反向代理到nextcloud服务
</span></span></span><span class="line"><span class="cl">					<span class="kn">proxy_pass</span> <span class="s">http://127.0.0.1:8248/</span><span class="p">;</span> <span class="c1"># 最后的/很重要
</span></span></span><span class="line"><span class="cl">					<span class="kn">proxy_set_header</span> <span class="s">HOST</span> <span class="nv">$host</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">					<span class="kn">proxy_set_header</span> <span class="s">X-Forwarded-Proto</span> <span class="nv">$scheme</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">					<span class="kn">proxy_set_header</span> <span class="s">X-Real-IP</span> <span class="nv">$remote_addr</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">					<span class="kn">proxy_set_header</span> <span class="s">X-Forwarded-For</span> <span class="nv">$proxy_add_x_forwarded_for</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">	<span class="p">}</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span></code></pre></div><h3 id="子域名">子域名</h3>
<p>这种方式应该是常用的一种方式，比如：<code>https://www.163.com/</code>、<code>https://mail.163.com/</code>、<code>https://music.163.com/</code>用子域名的方式同时提供了多项服务。</p>
<p>我目前服务器上的服务基本都采用这种方式通过nginx反向代理，不方便的地方就是需要为子域名单独配置SSL，由于目前貌似只有 
<a href="/tutorial/cloudflare"
   target="_blank">
 Cloudflare</a>
 提供免费的泛域名SSL证书，所以其实我的主要域名都全面转向Cloudflare了😄。</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-nginx" data-lang="nginx"><span class="line"><span class="cl"><span class="k">server</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">	<span class="c1"># 其他配置
</span></span></span><span class="line"><span class="cl">	<span class="kn">server_name</span> <span class="s">cloud.example.com</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">	<span class="c1"># Nextcloud 网盘 为例
</span></span></span><span class="line"><span class="cl">	<span class="kn">location</span> <span class="s">/</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">					<span class="c1"># 反向代理到nextcloud服务
</span></span></span><span class="line"><span class="cl">					<span class="kn">proxy_pass</span> <span class="s">http://127.0.0.1:8248/</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">					<span class="kn">proxy_set_header</span> <span class="s">HOST</span> <span class="nv">$host</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">					<span class="kn">proxy_set_header</span> <span class="s">X-Forwarded-Proto</span> <span class="nv">$scheme</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">					<span class="kn">proxy_set_header</span> <span class="s">X-Real-IP</span> <span class="nv">$remote_addr</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">					<span class="kn">proxy_set_header</span> <span class="s">X-Forwarded-For</span> <span class="nv">$proxy_add_x_forwarded_for</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">	<span class="p">}</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span></code></pre></div>]]></content:encoded>
    </item>
    <item>
      <title>Nginx配置SSL</title>
      <link>https://liushiguang.com/posts/nginx%E9%85%8D%E7%BD%AEssl/</link>
      <pubDate>Thu, 16 Oct 2025 00:25:51 +0800</pubDate>
      <guid>https://liushiguang.com/posts/nginx%E9%85%8D%E7%BD%AEssl/</guid>
      <description>&lt;h3 id=&#34;1-下载ssl证书&#34;&gt;1. 下载SSL证书&lt;/h3&gt;
&lt;p&gt;在SSL服务提供商处，下载Nginx（适用大部分场景）（pem文件、crt文件、key文件）格式的SSL证书&lt;/p&gt;
&lt;h3 id=&#34;2-安装ssl证书到nginx服务器&#34;&gt;2. 安装SSL证书到Nginx服务器&lt;/h3&gt;
&lt;p&gt;&lt;em&gt;前置：为Nginx安装好SSL模块&lt;/em&gt;&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-bash&#34; data-lang=&#34;bash&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;nginx -V 2&amp;gt;&lt;span class=&#34;p&#34;&gt;&amp;amp;&lt;/span&gt;&lt;span class=&#34;m&#34;&gt;1&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;|&lt;/span&gt; grep -o -- &lt;span class=&#34;s1&#34;&gt;&amp;#39;--with-http_ssl_module&amp;#39;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;执行以下命令，若输出&lt;code&gt;--with-http_ssl_module&lt;/code&gt;则说明已安装 SSL 模块，否则需进行安装。&lt;/p&gt;
&lt;hr&gt;
&lt;p&gt;a. 将&lt;code&gt;domain.key&lt;/code&gt;和&lt;code&gt;domain_bundle.crt&lt;/code&gt;文件放在服务器&lt;code&gt;/etc/nginx/cert&lt;/code&gt;中
b. 添加监听443端口的server块&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-nginx&#34; data-lang=&#34;nginx&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;k&#34;&gt;server&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;kn&#34;&gt;listen&lt;/span&gt; &lt;span class=&#34;mi&#34;&gt;443&lt;/span&gt; &lt;span class=&#34;s&#34;&gt;ssl&lt;/span&gt; &lt;span class=&#34;s&#34;&gt;default_server&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;kn&#34;&gt;server_name&lt;/span&gt; &lt;span class=&#34;s&#34;&gt;yourdomain.com&lt;/span&gt; &lt;span class=&#34;s&#34;&gt;www.yourdomain.com&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;c1&#34;&gt;# ======================= 证书配置开始 =======================
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;c1&#34;&gt;# 指定证书文件（中间证书可以拼接至该pem文件中），请将 /etc/nginx/cert/ssl.crt 替换为您实际使用的证书文件的绝对路径
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;kn&#34;&gt;ssl_certificate&lt;/span&gt; &lt;span class=&#34;s&#34;&gt;/etc/nginx/cert/ssl.crt&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;c1&#34;&gt;# 指定私钥文档，请将 /etc/nginx/cert/ssl.key 替换为您实际使用的私钥文件的绝对路径
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;kn&#34;&gt;ssl_certificate_key&lt;/span&gt; &lt;span class=&#34;s&#34;&gt;/etc/nginx/cert/ssl.key&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;c1&#34;&gt;# 指定允许的 TLS 协议版本，TLS协议版本越高，HTTPS通信的安全性越高，但是相较于低版本TLS协议，高版本TLS协议对浏览器的兼容性较差
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;kn&#34;&gt;ssl_protocols&lt;/span&gt; &lt;span class=&#34;s&#34;&gt;TLSv1.2&lt;/span&gt; &lt;span class=&#34;s&#34;&gt;TLSv1.3&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;c1&#34;&gt;# 自定义设置使用的TLS协议的类型以及加密套件（以下为配置示例，请您自行评估是否需要配置）
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;kn&#34;&gt;ssl_ciphers&lt;/span&gt; &lt;span class=&#34;s&#34;&gt;ECDHE-RSA-AES128-GCM-SHA256:ECDHE:ECDH:AES:HIGH:!NULL:!aNULL:!MD5:!ADH:!RC4&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;c1&#34;&gt;# 优先使用服务端指定的加密套件
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;kn&#34;&gt;ssl_prefer_server_ciphers&lt;/span&gt; &lt;span class=&#34;no&#34;&gt;on&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;c1&#34;&gt;# 配置 SSL 会话缓存，提高性能
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;kn&#34;&gt;ssl_session_cache&lt;/span&gt; &lt;span class=&#34;s&#34;&gt;shared:SSL:1m&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;c1&#34;&gt;# 设置 SSL 会话超时时间
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;kn&#34;&gt;ssl_session_timeout&lt;/span&gt; &lt;span class=&#34;mi&#34;&gt;5m&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;c1&#34;&gt;# ======================= 证书配置结束 =======================
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;   
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;c1&#34;&gt;# 其它配置
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;kn&#34;&gt;...&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;err&#34;&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;c. 可选：设置 http 请求自动跳转到 https。在原有监听 80 端口的 &lt;code&gt;server&lt;/code&gt; 块中添加 &lt;code&gt;return&lt;/code&gt; 指令即可。&lt;/p&gt;</description>
      <content:encoded><![CDATA[<h3 id="1-下载ssl证书">1. 下载SSL证书</h3>
<p>在SSL服务提供商处，下载Nginx（适用大部分场景）（pem文件、crt文件、key文件）格式的SSL证书</p>
<h3 id="2-安装ssl证书到nginx服务器">2. 安装SSL证书到Nginx服务器</h3>
<p><em>前置：为Nginx安装好SSL模块</em></p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">nginx -V 2&gt;<span class="p">&amp;</span><span class="m">1</span> <span class="p">|</span> grep -o -- <span class="s1">&#39;--with-http_ssl_module&#39;</span>
</span></span></code></pre></div><p>执行以下命令，若输出<code>--with-http_ssl_module</code>则说明已安装 SSL 模块，否则需进行安装。</p>
<hr>
<p>a. 将<code>domain.key</code>和<code>domain_bundle.crt</code>文件放在服务器<code>/etc/nginx/cert</code>中
b. 添加监听443端口的server块</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-nginx" data-lang="nginx"><span class="line"><span class="cl"><span class="k">server</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="kn">listen</span> <span class="mi">443</span> <span class="s">ssl</span> <span class="s">default_server</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">    <span class="kn">server_name</span> <span class="s">yourdomain.com</span> <span class="s">www.yourdomain.com</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">    
</span></span><span class="line"><span class="cl">    <span class="c1"># ======================= 证书配置开始 =======================
</span></span></span><span class="line"><span class="cl">    <span class="c1"># 指定证书文件（中间证书可以拼接至该pem文件中），请将 /etc/nginx/cert/ssl.crt 替换为您实际使用的证书文件的绝对路径
</span></span></span><span class="line"><span class="cl">    <span class="kn">ssl_certificate</span> <span class="s">/etc/nginx/cert/ssl.crt</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">    <span class="c1"># 指定私钥文档，请将 /etc/nginx/cert/ssl.key 替换为您实际使用的私钥文件的绝对路径
</span></span></span><span class="line"><span class="cl">    <span class="kn">ssl_certificate_key</span> <span class="s">/etc/nginx/cert/ssl.key</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">    <span class="c1"># 指定允许的 TLS 协议版本，TLS协议版本越高，HTTPS通信的安全性越高，但是相较于低版本TLS协议，高版本TLS协议对浏览器的兼容性较差
</span></span></span><span class="line"><span class="cl">    <span class="kn">ssl_protocols</span> <span class="s">TLSv1.2</span> <span class="s">TLSv1.3</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">    <span class="c1"># 自定义设置使用的TLS协议的类型以及加密套件（以下为配置示例，请您自行评估是否需要配置）
</span></span></span><span class="line"><span class="cl">    <span class="kn">ssl_ciphers</span> <span class="s">ECDHE-RSA-AES128-GCM-SHA256:ECDHE:ECDH:AES:HIGH:!NULL:!aNULL:!MD5:!ADH:!RC4</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">    <span class="c1"># 优先使用服务端指定的加密套件
</span></span></span><span class="line"><span class="cl">    <span class="kn">ssl_prefer_server_ciphers</span> <span class="no">on</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">    
</span></span><span class="line"><span class="cl">    <span class="c1"># 配置 SSL 会话缓存，提高性能
</span></span></span><span class="line"><span class="cl">    <span class="kn">ssl_session_cache</span> <span class="s">shared:SSL:1m</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">    <span class="c1"># 设置 SSL 会话超时时间
</span></span></span><span class="line"><span class="cl">    <span class="kn">ssl_session_timeout</span> <span class="mi">5m</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">    <span class="c1"># ======================= 证书配置结束 =======================
</span></span></span><span class="line"><span class="cl">   
</span></span><span class="line"><span class="cl">    <span class="c1"># 其它配置
</span></span></span><span class="line"><span class="cl">    <span class="kn">...</span>
</span></span><span class="line"><span class="cl"><span class="err">}</span>
</span></span></code></pre></div><p>c. 可选：设置 http 请求自动跳转到 https。在原有监听 80 端口的 <code>server</code> 块中添加 <code>return</code> 指令即可。</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-nginx" data-lang="nginx"><span class="line"><span class="cl"><span class="c1"># 原有监听 80 端口的 server 块
</span></span></span><span class="line"><span class="cl"><span class="k">server</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="kn">listen</span> <span class="mi">80</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">    <span class="kn">server_name</span> <span class="s">yourdomain.com</span> <span class="s">www.yourdomain.com</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">        
</span></span><span class="line"><span class="cl">    <span class="c1"># 设置HTTP请求自动跳转到HTTPS
</span></span></span><span class="line"><span class="cl">    <span class="kn">return</span> <span class="mi">301</span> <span class="s">https://</span><span class="nv">$host$request_uri</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">    
</span></span><span class="line"><span class="cl">    <span class="c1"># 原有其它配置
</span></span></span><span class="line"><span class="cl">    <span class="kn">...</span>
</span></span><span class="line"><span class="cl"><span class="err">}</span>
</span></span></code></pre></div><h3 id="3-验证ssl证书是否安装成功">3. 验证SSL证书是否安装成功</h3>
<p>a. 请通过 HTTPS 访问您已绑定证书的域名（如 <code>https://yourdomain.com</code>，<code>yourdomain.com</code> 需替换为实际域名）。
b. 若浏览器地址栏显示安全锁图标，说明证书已成功部署。如访问异常或未显示安全锁，请先清除浏览器缓存或使用无痕（隐私）模式重试。</p>
]]></content:encoded>
    </item>
    <item>
      <title>循环神经网络</title>
      <link>https://liushiguang.com/posts/%E5%BE%AA%E7%8E%AF%E7%A5%9E%E7%BB%8F%E7%BD%91%E7%BB%9C/</link>
      <pubDate>Sat, 11 Oct 2025 02:22:34 +0800</pubDate>
      <guid>https://liushiguang.com/posts/%E5%BE%AA%E7%8E%AF%E7%A5%9E%E7%BB%8F%E7%BD%91%E7%BB%9C/</guid>
      <description>&lt;h3 id=&#34;rnn&#34;&gt;RNN&lt;/h3&gt;
&lt;p&gt;RNN&lt;sup id=&#34;fnref:1&#34;&gt;&lt;a href=&#34;#fn:1&#34; class=&#34;footnote-ref&#34; role=&#34;doc-noteref&#34;&gt;1&lt;/a&gt;&lt;/sup&gt;（Recurrent Neural Network）是一种循环神经网络，用于处理序列数据。与传统的前馈神经网络不同，RNN具有循环连接，使得它可以在处理序列时保持一种记忆状态。&lt;/p&gt;
&lt;p&gt;在 RNN 中，每个时间步都有一个隐藏状态（hidden state），它可以接收当前时间步的输入和上一个时间步的隐藏状态作为输入。隐藏状态的输出不仅取决于当前时间步的输入，还取决于之前所有时间步的输入。这种循环连接使得RNN可以处理变长序列，并且能够捕捉到序列中的时序信息。

&lt;div class=&#34;post-img-view&#34;&gt;
&lt;a data-fancybox=&#34;gallery&#34; href=&#34;https://bucket.liushiguang.com/picturebed/%E5%BE%AA%E7%8E%AF%E7%A5%9E%E7%BB%8F%E7%BD%91%E7%BB%9C-1.png&#34;&gt;
&lt;img src=&#34;https://bucket.liushiguang.com/picturebed/%E5%BE%AA%E7%8E%AF%E7%A5%9E%E7%BB%8F%E7%BD%91%E7%BB%9C-1.png&#34; alt=&#34;循环神经网络-1&#34;  /&gt;
&lt;/a&gt;
&lt;/div&gt;


RNN 的计算过程非常简单：
&lt;code&gt;$$ h_{t}=f(w_{hh}h_{t-1} + w_{xh}x_t)   $$&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;RNN 在自然语言处理（NLP）等领域有广泛的应用，例如语言建模、机器翻译、情感分析等任务。由于 RNN 能够处理变长序列，并且可以保持记忆状态，它在处理自然语言时可以考虑上下文的信息，捕捉到词语之间的依赖关系和语义信息。&lt;/p&gt;
&lt;p&gt;此外，RNN 也可以应用于时间序列预测，例如股票价格预测、天气预测等。RNN 可以根据过去的时间序列数据预测未来的趋势，对于具有时序依赖的数据具有一定的优势。&lt;/p&gt;
&lt;p&gt;然而，传统的RNN在处理长序列时存在&lt;strong&gt;梯度消失和梯度爆炸&lt;/strong&gt;的问题，这限制了其对长期依赖关系的建模能力。为了解决这个问题，出现了一些改进的 RNN 变体，如长短期记忆网络（LSTM）和门控循环单元（GRU），它们引入了门控机制来控制记忆状态的更新，改善了对长期依赖的建模能力。&lt;/p&gt;
&lt;h3 id=&#34;lstm&#34;&gt;LSTM&lt;/h3&gt;
&lt;p&gt;LSTM&lt;sup id=&#34;fnref:2&#34;&gt;&lt;a href=&#34;#fn:2&#34; class=&#34;footnote-ref&#34; role=&#34;doc-noteref&#34;&gt;2&lt;/a&gt;&lt;/sup&gt;（Long Short-Term Memory）是一种改进的循环神经网络（RNN）架构，旨在解决传统 RNN 中的梯度消失和梯度爆炸问题，以及增强对长期依赖关系的建模能力。&lt;/p&gt;
&lt;p&gt;LSTM 引入了一个记忆单元（memory cell），该单元可以存储和访问信息，并通过门控机制来控制信息的流动。LSTM 的关键部分包括&lt;strong&gt;输入门&lt;/strong&gt;（input gate）、&lt;strong&gt;遗忘门&lt;/strong&gt;（forget gate）、&lt;strong&gt;输出门&lt;/strong&gt;（output gate）。

&lt;div class=&#34;post-img-view&#34;&gt;
&lt;a data-fancybox=&#34;gallery&#34; href=&#34;https://bucket.liushiguang.com/picturebed/%E5%BE%AA%E7%8E%AF%E7%A5%9E%E7%BB%8F%E7%BD%91%E7%BB%9C-2.png&#34;&gt;
&lt;img src=&#34;https://bucket.liushiguang.com/picturebed/%E5%BE%AA%E7%8E%AF%E7%A5%9E%E7%BB%8F%E7%BD%91%E7%BB%9C-2.png&#34; alt=&#34;循环神经网络-2&#34;  /&gt;
&lt;/a&gt;
&lt;/div&gt;

&lt;/p&gt;
&lt;h4 id=&#34;遗忘门forget-gate&#34;&gt;遗忘门（forget gate）&lt;/h4&gt;
&lt;p&gt;
&lt;div class=&#34;post-img-view&#34;&gt;
&lt;a data-fancybox=&#34;gallery&#34; href=&#34;https://bucket.liushiguang.com/picturebed/%E5%BE%AA%E7%8E%AF%E7%A5%9E%E7%BB%8F%E7%BD%91%E7%BB%9C-3.png&#34;&gt;
&lt;img src=&#34;https://bucket.liushiguang.com/picturebed/%E5%BE%AA%E7%8E%AF%E7%A5%9E%E7%BB%8F%E7%BD%91%E7%BB%9C-3.png&#34; alt=&#34;循环神经网络-3&#34;  /&gt;
&lt;/a&gt;
&lt;/div&gt;


LSTM 的第一步就是决定我们要从单元状态中丢弃什么信息。这个决定由一个 sigmoid 层做出，称为“遗忘门层(forget gate layer)”。它查看 &lt;code&gt;$h_{t-1}$&lt;/code&gt; 和 &lt;code&gt;$x_t$&lt;/code&gt;，并且为单元状态 &lt;code&gt;$C_{t-1}$&lt;/code&gt; 中的每个数字输出一个 0 和 1 之间的数字。1 代表“完全保持这个数字”，而 0 表示“完全遗忘”。&lt;/p&gt;
&lt;p&gt;回到我们语言模型的例子，试图根据所有以前的单词来预测下一个单词。在这样的问题中，单元状态可能包括当前主题(subject)的性质，从而可以使用正确的代词。当我们看到一个新主题时，我们想要忘记老主题的性质。&lt;/p&gt;
&lt;h4 id=&#34;输入门input-gate&#34;&gt;输入门（input gate）&lt;/h4&gt;
&lt;p&gt;
&lt;div class=&#34;post-img-view&#34;&gt;
&lt;a data-fancybox=&#34;gallery&#34; href=&#34;https://bucket.liushiguang.com/picturebed/%E5%BE%AA%E7%8E%AF%E7%A5%9E%E7%BB%8F%E7%BD%91%E7%BB%9C-5.png&#34;&gt;
&lt;img src=&#34;https://bucket.liushiguang.com/picturebed/%E5%BE%AA%E7%8E%AF%E7%A5%9E%E7%BB%8F%E7%BD%91%E7%BB%9C-5.png&#34; alt=&#34;循环神经网络-5&#34;  /&gt;
&lt;/a&gt;
&lt;/div&gt;


下一步是决定我们要在单元状态下存储的新信息。这包含两部分。首先，一个称为“输入门层(input gate layer)”的 sigmoid 层决定我们将更新哪些值。然后，一个 tanh 层创建一个可以被添加到状态中的新候选值向量 &lt;code&gt;$\tilde{C_{t}}$&lt;/code&gt; 。在下一步中，结合这两个门来创建对状态的更新。

&lt;div class=&#34;post-img-view&#34;&gt;
&lt;a data-fancybox=&#34;gallery&#34; href=&#34;https://bucket.liushiguang.com/picturebed/%E5%BE%AA%E7%8E%AF%E7%A5%9E%E7%BB%8F%E7%BD%91%E7%BB%9C-4.png&#34;&gt;
&lt;img src=&#34;https://bucket.liushiguang.com/picturebed/%E5%BE%AA%E7%8E%AF%E7%A5%9E%E7%BB%8F%E7%BD%91%E7%BB%9C-4.png&#34; alt=&#34;循环神经网络-4&#34;  /&gt;
&lt;/a&gt;
&lt;/div&gt;


现在是将旧的单元状态 &lt;code&gt;$C_{t-1}$&lt;/code&gt; 更新为新的单元状态 &lt;code&gt;$C_t$&lt;/code&gt; 的时候了。以前的步骤已经决定了要做什么，我们只需要实际去做就行了。&lt;/p&gt;</description>
      <content:encoded><![CDATA[<h3 id="rnn">RNN</h3>
<p>RNN<sup id="fnref:1"><a href="#fn:1" class="footnote-ref" role="doc-noteref">1</a></sup>（Recurrent Neural Network）是一种循环神经网络，用于处理序列数据。与传统的前馈神经网络不同，RNN具有循环连接，使得它可以在处理序列时保持一种记忆状态。</p>
<p>在 RNN 中，每个时间步都有一个隐藏状态（hidden state），它可以接收当前时间步的输入和上一个时间步的隐藏状态作为输入。隐藏状态的输出不仅取决于当前时间步的输入，还取决于之前所有时间步的输入。这种循环连接使得RNN可以处理变长序列，并且能够捕捉到序列中的时序信息。

<div class="post-img-view">
<a data-fancybox="gallery" href="https://bucket.liushiguang.com/picturebed/%E5%BE%AA%E7%8E%AF%E7%A5%9E%E7%BB%8F%E7%BD%91%E7%BB%9C-1.png">
<img src="https://bucket.liushiguang.com/picturebed/%E5%BE%AA%E7%8E%AF%E7%A5%9E%E7%BB%8F%E7%BD%91%E7%BB%9C-1.png" alt="循环神经网络-1"  />
</a>
</div>


RNN 的计算过程非常简单：
<code>$$ h_{t}=f(w_{hh}h_{t-1} + w_{xh}x_t)   $$</code></p>
<p>RNN 在自然语言处理（NLP）等领域有广泛的应用，例如语言建模、机器翻译、情感分析等任务。由于 RNN 能够处理变长序列，并且可以保持记忆状态，它在处理自然语言时可以考虑上下文的信息，捕捉到词语之间的依赖关系和语义信息。</p>
<p>此外，RNN 也可以应用于时间序列预测，例如股票价格预测、天气预测等。RNN 可以根据过去的时间序列数据预测未来的趋势，对于具有时序依赖的数据具有一定的优势。</p>
<p>然而，传统的RNN在处理长序列时存在<strong>梯度消失和梯度爆炸</strong>的问题，这限制了其对长期依赖关系的建模能力。为了解决这个问题，出现了一些改进的 RNN 变体，如长短期记忆网络（LSTM）和门控循环单元（GRU），它们引入了门控机制来控制记忆状态的更新，改善了对长期依赖的建模能力。</p>
<h3 id="lstm">LSTM</h3>
<p>LSTM<sup id="fnref:2"><a href="#fn:2" class="footnote-ref" role="doc-noteref">2</a></sup>（Long Short-Term Memory）是一种改进的循环神经网络（RNN）架构，旨在解决传统 RNN 中的梯度消失和梯度爆炸问题，以及增强对长期依赖关系的建模能力。</p>
<p>LSTM 引入了一个记忆单元（memory cell），该单元可以存储和访问信息，并通过门控机制来控制信息的流动。LSTM 的关键部分包括<strong>输入门</strong>（input gate）、<strong>遗忘门</strong>（forget gate）、<strong>输出门</strong>（output gate）。

<div class="post-img-view">
<a data-fancybox="gallery" href="https://bucket.liushiguang.com/picturebed/%E5%BE%AA%E7%8E%AF%E7%A5%9E%E7%BB%8F%E7%BD%91%E7%BB%9C-2.png">
<img src="https://bucket.liushiguang.com/picturebed/%E5%BE%AA%E7%8E%AF%E7%A5%9E%E7%BB%8F%E7%BD%91%E7%BB%9C-2.png" alt="循环神经网络-2"  />
</a>
</div>

</p>
<h4 id="遗忘门forget-gate">遗忘门（forget gate）</h4>
<p>
<div class="post-img-view">
<a data-fancybox="gallery" href="https://bucket.liushiguang.com/picturebed/%E5%BE%AA%E7%8E%AF%E7%A5%9E%E7%BB%8F%E7%BD%91%E7%BB%9C-3.png">
<img src="https://bucket.liushiguang.com/picturebed/%E5%BE%AA%E7%8E%AF%E7%A5%9E%E7%BB%8F%E7%BD%91%E7%BB%9C-3.png" alt="循环神经网络-3"  />
</a>
</div>


LSTM 的第一步就是决定我们要从单元状态中丢弃什么信息。这个决定由一个 sigmoid 层做出，称为“遗忘门层(forget gate layer)”。它查看 <code>$h_{t-1}$</code> 和 <code>$x_t$</code>，并且为单元状态 <code>$C_{t-1}$</code> 中的每个数字输出一个 0 和 1 之间的数字。1 代表“完全保持这个数字”，而 0 表示“完全遗忘”。</p>
<p>回到我们语言模型的例子，试图根据所有以前的单词来预测下一个单词。在这样的问题中，单元状态可能包括当前主题(subject)的性质，从而可以使用正确的代词。当我们看到一个新主题时，我们想要忘记老主题的性质。</p>
<h4 id="输入门input-gate">输入门（input gate）</h4>
<p>
<div class="post-img-view">
<a data-fancybox="gallery" href="https://bucket.liushiguang.com/picturebed/%E5%BE%AA%E7%8E%AF%E7%A5%9E%E7%BB%8F%E7%BD%91%E7%BB%9C-5.png">
<img src="https://bucket.liushiguang.com/picturebed/%E5%BE%AA%E7%8E%AF%E7%A5%9E%E7%BB%8F%E7%BD%91%E7%BB%9C-5.png" alt="循环神经网络-5"  />
</a>
</div>


下一步是决定我们要在单元状态下存储的新信息。这包含两部分。首先，一个称为“输入门层(input gate layer)”的 sigmoid 层决定我们将更新哪些值。然后，一个 tanh 层创建一个可以被添加到状态中的新候选值向量 <code>$\tilde{C_{t}}$</code> 。在下一步中，结合这两个门来创建对状态的更新。

<div class="post-img-view">
<a data-fancybox="gallery" href="https://bucket.liushiguang.com/picturebed/%E5%BE%AA%E7%8E%AF%E7%A5%9E%E7%BB%8F%E7%BD%91%E7%BB%9C-4.png">
<img src="https://bucket.liushiguang.com/picturebed/%E5%BE%AA%E7%8E%AF%E7%A5%9E%E7%BB%8F%E7%BD%91%E7%BB%9C-4.png" alt="循环神经网络-4"  />
</a>
</div>


现在是将旧的单元状态 <code>$C_{t-1}$</code> 更新为新的单元状态 <code>$C_t$</code> 的时候了。以前的步骤已经决定了要做什么，我们只需要实际去做就行了。</p>
<p>我们将旧的状态乘以 ft，去忘记那些我们之前决定忘记的东西。然后我们向状态中添加 <code>$t_{t}*\tilde{C_t}$</code> 。这是新的候选值，并且按照我们决定更新每个状态值的程度来缩放。</p>
<p>对于语言模型来说，这就是我们实际删除旧主题性质的信息，并添加新信息的地方，正如我们在之前的步骤中决定的那样。</p>
<h4 id="输出门output-gate">输出门（output gate）</h4>
<p>
<div class="post-img-view">
<a data-fancybox="gallery" href="https://bucket.liushiguang.com/picturebed/%E5%BE%AA%E7%8E%AF%E7%A5%9E%E7%BB%8F%E7%BD%91%E7%BB%9C-6.png">
<img src="https://bucket.liushiguang.com/picturebed/%E5%BE%AA%E7%8E%AF%E7%A5%9E%E7%BB%8F%E7%BD%91%E7%BB%9C-6.png" alt="循环神经网络-6"  />
</a>
</div>


最后，我们需要决定输出的信息。此输出将基于我们的单元状态，但将是一个过滤后的版本。首先，我们运行一个 sigmoid 层，它决定了我们要输出单元状态的哪些部分。然后，我们让单元状态通过 tanh（将值变为 −1 和 1 之间），并将其乘以 sigmoid 门的输出，以便我们只输出我们决定输出的部分。</p>
<p>以语言模型为例，由于它只看到一个主题，如果后续是一个动词，它可能需要输出与动词相关的信息。例如，它可能会输出主题是单数还是复数，以便我们知道接下来的动词应该是什么形式。</p>
<h3 id="gru">GRU</h3>
<p>
<div class="post-img-view">
<a data-fancybox="gallery" href="https://bucket.liushiguang.com/picturebed/%E5%BE%AA%E7%8E%AF%E7%A5%9E%E7%BB%8F%E7%BD%91%E7%BB%9C-7.png">
<img src="https://bucket.liushiguang.com/picturebed/%E5%BE%AA%E7%8E%AF%E7%A5%9E%E7%BB%8F%E7%BD%91%E7%BB%9C-7.png" alt="循环神经网络-7"  />
</a>
</div>


LSTM 的一个稍微更显着的变种是由 Cho, et al. (2014) 提出的门控循环单元或者称为 GRU。它将忘记门和输入门组合成一个单一的“更新门”，它还合并了单元状态和隐藏状态，并进行了一些其他更改。所得到的模型比标准 LSTM 模型更简单，并且越来越受欢迎。</p>
<div class="footnotes" role="doc-endnotes">
<hr>
<ol>
<li id="fn:1">
<p>循环神经网络详解（RNN/LSTM/GRU） 
<a href="https://www.zhihu.com/tardis/zm/art/636756912?source_id=1003"
   target="_blank">
 https://www.zhihu.com/tardis/zm/art/636756912?source_id=1003</a>
&#160;<a href="#fnref:1" class="footnote-backref" role="doc-backlink">&#x21a9;&#xfe0e;</a></p>
</li>
<li id="fn:2">
<p>LSTM 网络原理：通过图解，一步一步“走过”LSTM 
<a href="https://xiaosheng.blog/2017/09/16/what-is-lstm"
   target="_blank">
 https://xiaosheng.blog/2017/09/16/what-is-lstm</a>
&#160;<a href="#fnref:2" class="footnote-backref" role="doc-backlink">&#x21a9;&#xfe0e;</a></p>
</li>
</ol>
</div>
]]></content:encoded>
    </item>
    <item>
      <title>Linux休眠机制</title>
      <link>https://liushiguang.com/posts/linux%E4%BC%91%E7%9C%A0/</link>
      <pubDate>Thu, 01 May 2025 01:39:40 +0800</pubDate>
      <guid>https://liushiguang.com/posts/linux%E4%BC%91%E7%9C%A0/</guid>
      <description>&lt;h3 id=&#34;基础&#34;&gt;基础&lt;/h3&gt;
&lt;p&gt;广义来讲，在Linux中支持
&lt;a href=&#34;https://wiki.archlinux.org/index.php/Power_management/Suspend_and_hibernate#Hibernation_into_swap_file&#34;
   target=&#34;_blank&#34;&gt;
 三种睡眠模式&lt;/a&gt;
，分别是:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Suspend to RAM，即狭义的挂起（Suspend），本文后面提到挂起都是指这种模式。&lt;/li&gt;
&lt;li&gt;Suspend to Disk，即我们常说的休眠（Hibernate）。&lt;/li&gt;
&lt;li&gt;Suspend to Both，也被称作Hybrid Suspend，是上面两种模式的混合，兼具两种模式的优点（和缺点）。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Ubuntu桌面版默认只有挂起（Suspend）模式，没有启用休眠（Hibernate）模式。&lt;/p&gt;
&lt;p&gt;挂起和休眠的目标都是保存冻结系统当前状态，后续需要时“快速”唤醒恢复，但是两者的实现方式是不同的。&lt;/p&gt;
&lt;p&gt;挂起时系统的运行数据仍然保存在内存（RAM，通常也叫MEM）中，所以系统还是会以较低的功耗消耗电池电量。这种状态下唤醒系统恢复非常快速，在普及SSD的现在是包括苹果在内很多操作系统的默认选项。&lt;/p&gt;
&lt;p&gt;休眠时系统的运行数据被写入磁盘（DISK），系统也会完全切断电源（大部分情况下），唤醒时需要先从硬盘读取数据到内存，因为恢复速度比挂起慢，实测甚至要慢于开机（20s vs 10s）。&lt;/p&gt;
&lt;p&gt;休眠的好处就是笔记本实际是关机状态，完全不耗电不发热，不用担心意外断电、进水和误触键盘唤醒系统，可以放心的携带和保存。&lt;/p&gt;
&lt;p&gt;Linux底层有两种实现来支持挂起和休眠，一种是内核（kernel）自带的swsusp，另一种是uswsusp(‘Userspace Software Suspend’) ，后者封装了前者，并且提供了更多的功能，通常swsusp已经够用了。&lt;/p&gt;
&lt;p&gt;swsusp的原理是向&lt;code&gt;/sys/power&lt;/code&gt;目录中的文件写入特定的状态字符串来操作系统的状态。&lt;/p&gt;
&lt;p&gt;最重要的是&lt;code&gt;/sys/power/state&lt;/code&gt;、&lt;code&gt;/sys/power/mem_sleep&lt;/code&gt;、&lt;code&gt;/sys/power/disk&lt;/code&gt;三个文件，分别保存了当前系统支持的睡眠模式、挂起方法、休眠方法，具体信息可以参考
&lt;a href=&#34;https://www.kernel.org/doc/html/latest/admin-guide/pm/sleep-states.html&#34;
   target=&#34;_blank&#34;&gt;
 官方文档&lt;/a&gt;
&lt;/p&gt;
&lt;p&gt;Systemd工具提供了睡眠管理的高级命令&lt;code&gt;systemctl suspend&lt;/code&gt;、&lt;code&gt;systemctl hibernate&lt;/code&gt;、&lt;code&gt;systemctl hybrid-sleep&lt;/code&gt;。&lt;/p&gt;
&lt;h3 id=&#34;配置&#34;&gt;配置&lt;/h3&gt;
&lt;ol&gt;
&lt;li&gt;配置&lt;code&gt;/swapfile&lt;/code&gt;，启用休眠需要比内存稍大的swap空间，推荐使用swapfile，从2.4内核开始， swapfile的性能已经不弱于swap分区，并且更容易调整大小，如果使用btrfs文件系统，需要内核升级到5.0以上才支持swapfile&lt;/li&gt;
&lt;/ol&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-shell&#34; data-lang=&#34;shell&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;c1&#34;&gt;# 先关闭已有的swap空间  &lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;sudo swapoff -a  
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;c1&#34;&gt;# 分配连续的磁盘空间，fallocate比dd命令更安全快速，空间大小参考后面的表格  &lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;sudo fallocate -l 20G /swapfile  
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;c1&#34;&gt;# 修改权限  &lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;sudo chmod &lt;span class=&#34;m&#34;&gt;600&lt;/span&gt; /swapfile  
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;c1&#34;&gt;# 启用swapfile  &lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;sudo mkswap /swapfile  
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;sudo swapon /swapfile  
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;c1&#34;&gt;# 确认结果  &lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;sudo swapon --show
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;free
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;swap文件的大小可以参考下面的表格：&lt;/p&gt;
&lt;table&gt;
  &lt;thead&gt;
      &lt;tr&gt;
          &lt;th&gt;内存&lt;/th&gt;
          &lt;th&gt;关闭休眠时swap空间大小&lt;/th&gt;
          &lt;th&gt;启用休眠时swap空间大小&lt;/th&gt;
          &lt;th&gt;最大swap空间大小&lt;/th&gt;
      &lt;/tr&gt;
  &lt;/thead&gt;
  &lt;tbody&gt;
      &lt;tr&gt;
          &lt;td&gt;4GB&lt;/td&gt;
          &lt;td&gt;2GB&lt;/td&gt;
          &lt;td&gt;6GB&lt;/td&gt;
          &lt;td&gt;8GB&lt;/td&gt;
      &lt;/tr&gt;
      &lt;tr&gt;
          &lt;td&gt;8GB&lt;/td&gt;
          &lt;td&gt;3GB&lt;/td&gt;
          &lt;td&gt;11GB&lt;/td&gt;
          &lt;td&gt;16GB&lt;/td&gt;
      &lt;/tr&gt;
      &lt;tr&gt;
          &lt;td&gt;12GB&lt;/td&gt;
          &lt;td&gt;3GB&lt;/td&gt;
          &lt;td&gt;15GB&lt;/td&gt;
          &lt;td&gt;24GB&lt;/td&gt;
      &lt;/tr&gt;
      &lt;tr&gt;
          &lt;td&gt;16GB&lt;/td&gt;
          &lt;td&gt;4GB&lt;/td&gt;
          &lt;td&gt;20GB&lt;/td&gt;
          &lt;td&gt;32GB&lt;/td&gt;
      &lt;/tr&gt;
      &lt;tr&gt;
          &lt;td&gt;24GB&lt;/td&gt;
          &lt;td&gt;5GB&lt;/td&gt;
          &lt;td&gt;29GB&lt;/td&gt;
          &lt;td&gt;48GB&lt;/td&gt;
      &lt;/tr&gt;
      &lt;tr&gt;
          &lt;td&gt;32GB&lt;/td&gt;
          &lt;td&gt;6GB&lt;/td&gt;
          &lt;td&gt;38GB&lt;/td&gt;
          &lt;td&gt;64GB&lt;/td&gt;
      &lt;/tr&gt;
      &lt;tr&gt;
          &lt;td&gt;64GB&lt;/td&gt;
          &lt;td&gt;8GB&lt;/td&gt;
          &lt;td&gt;72GB&lt;/td&gt;
          &lt;td&gt;128GB&lt;/td&gt;
      &lt;/tr&gt;
  &lt;/tbody&gt;
&lt;/table&gt;
&lt;ol start=&#34;2&#34;&gt;
&lt;li&gt;文件系统启动时挂载&lt;code&gt;/swapfile&lt;/code&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-shell&#34; data-lang=&#34;shell&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;nb&#34;&gt;echo&lt;/span&gt; &lt;span class=&#34;s1&#34;&gt;&amp;#39;/swapfile swap swap defaults 0 0&amp;#39;&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;|&lt;/span&gt; sudo tee -a /etc/fstab
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;ol start=&#34;3&#34;&gt;
&lt;li&gt;配置启动内核参数&lt;/li&gt;
&lt;/ol&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-shell&#34; data-lang=&#34;shell&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;c1&#34;&gt;# 查看swapfile的UUID  &lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;sudo findmnt -no UUID -T /swapfile  
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;c1&#34;&gt;# 查看swap_file_offset，忽略..符号  &lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;sudo filefrag -v /swapfile &lt;span class=&#34;p&#34;&gt;|&lt;/span&gt; awk &lt;span class=&#34;s1&#34;&gt;&amp;#39;{ if($1==&amp;#34;0:&amp;#34;){print substr($4, 1, length($4)-2)} }&amp;#39;&lt;/span&gt;  
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;c1&#34;&gt;# 编辑grub文件  &lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;sudo nano /etc/default/grub  
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;c1&#34;&gt;# 将grub文件中GRUB_CMDLINE_LINUX_DEFAULT参数修改为如下形式  &lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;c1&#34;&gt;# 其中UUID和resume_offset的值更换为上面两个命令的输出  &lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;nv&#34;&gt;GRUB_CMDLINE_LINUX_DEFAULT&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;=&lt;/span&gt;&lt;span class=&#34;s2&#34;&gt;&amp;#34;quiet splash resume=UUID=51f8eab4-d775-4020-aace-0e411ef5b8ed resume_offset=34816&amp;#34;&lt;/span&gt;  
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;c1&#34;&gt;# 保存退出，然后更新grub配置  &lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;sudo update-grub  
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;c1&#34;&gt;# 编辑initramfs  &lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;sudo nano /etc/initramfs-tools/conf.d/resume  
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;c1&#34;&gt;# 加入下面一行，UUID替换为实际值  &lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;nv&#34;&gt;resume&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;=&lt;/span&gt;&lt;span class=&#34;nv&#34;&gt;UUID&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;=&lt;/span&gt;51f8eab4-d775-4020-aace-0e411ef5b8ed  
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;c1&#34;&gt;# 保存退出，然后更新initramfs配置  &lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;sudo update-initramfs -u  
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;c1&#34;&gt;# 重启  &lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;reboot
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;重启后执行&lt;code&gt;sudo systemctl hibernate&lt;/code&gt;测试是否可以正常休眠。&lt;/p&gt;</description>
      <content:encoded><![CDATA[<h3 id="基础">基础</h3>
<p>广义来讲，在Linux中支持
<a href="https://wiki.archlinux.org/index.php/Power_management/Suspend_and_hibernate#Hibernation_into_swap_file"
   target="_blank">
 三种睡眠模式</a>
，分别是:</p>
<ul>
<li>Suspend to RAM，即狭义的挂起（Suspend），本文后面提到挂起都是指这种模式。</li>
<li>Suspend to Disk，即我们常说的休眠（Hibernate）。</li>
<li>Suspend to Both，也被称作Hybrid Suspend，是上面两种模式的混合，兼具两种模式的优点（和缺点）。</li>
</ul>
<p>Ubuntu桌面版默认只有挂起（Suspend）模式，没有启用休眠（Hibernate）模式。</p>
<p>挂起和休眠的目标都是保存冻结系统当前状态，后续需要时“快速”唤醒恢复，但是两者的实现方式是不同的。</p>
<p>挂起时系统的运行数据仍然保存在内存（RAM，通常也叫MEM）中，所以系统还是会以较低的功耗消耗电池电量。这种状态下唤醒系统恢复非常快速，在普及SSD的现在是包括苹果在内很多操作系统的默认选项。</p>
<p>休眠时系统的运行数据被写入磁盘（DISK），系统也会完全切断电源（大部分情况下），唤醒时需要先从硬盘读取数据到内存，因为恢复速度比挂起慢，实测甚至要慢于开机（20s vs 10s）。</p>
<p>休眠的好处就是笔记本实际是关机状态，完全不耗电不发热，不用担心意外断电、进水和误触键盘唤醒系统，可以放心的携带和保存。</p>
<p>Linux底层有两种实现来支持挂起和休眠，一种是内核（kernel）自带的swsusp，另一种是uswsusp(‘Userspace Software Suspend’) ，后者封装了前者，并且提供了更多的功能，通常swsusp已经够用了。</p>
<p>swsusp的原理是向<code>/sys/power</code>目录中的文件写入特定的状态字符串来操作系统的状态。</p>
<p>最重要的是<code>/sys/power/state</code>、<code>/sys/power/mem_sleep</code>、<code>/sys/power/disk</code>三个文件，分别保存了当前系统支持的睡眠模式、挂起方法、休眠方法，具体信息可以参考
<a href="https://www.kernel.org/doc/html/latest/admin-guide/pm/sleep-states.html"
   target="_blank">
 官方文档</a>
</p>
<p>Systemd工具提供了睡眠管理的高级命令<code>systemctl suspend</code>、<code>systemctl hibernate</code>、<code>systemctl hybrid-sleep</code>。</p>
<h3 id="配置">配置</h3>
<ol>
<li>配置<code>/swapfile</code>，启用休眠需要比内存稍大的swap空间，推荐使用swapfile，从2.4内核开始， swapfile的性能已经不弱于swap分区，并且更容易调整大小，如果使用btrfs文件系统，需要内核升级到5.0以上才支持swapfile</li>
</ol>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-shell" data-lang="shell"><span class="line"><span class="cl"><span class="c1"># 先关闭已有的swap空间  </span>
</span></span><span class="line"><span class="cl">sudo swapoff -a  
</span></span><span class="line"><span class="cl"><span class="c1"># 分配连续的磁盘空间，fallocate比dd命令更安全快速，空间大小参考后面的表格  </span>
</span></span><span class="line"><span class="cl">sudo fallocate -l 20G /swapfile  
</span></span><span class="line"><span class="cl"><span class="c1"># 修改权限  </span>
</span></span><span class="line"><span class="cl">sudo chmod <span class="m">600</span> /swapfile  
</span></span><span class="line"><span class="cl"><span class="c1"># 启用swapfile  </span>
</span></span><span class="line"><span class="cl">sudo mkswap /swapfile  
</span></span><span class="line"><span class="cl">sudo swapon /swapfile  
</span></span><span class="line"><span class="cl"><span class="c1"># 确认结果  </span>
</span></span><span class="line"><span class="cl">sudo swapon --show
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">free
</span></span></code></pre></div><p>swap文件的大小可以参考下面的表格：</p>
<table>
  <thead>
      <tr>
          <th>内存</th>
          <th>关闭休眠时swap空间大小</th>
          <th>启用休眠时swap空间大小</th>
          <th>最大swap空间大小</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>4GB</td>
          <td>2GB</td>
          <td>6GB</td>
          <td>8GB</td>
      </tr>
      <tr>
          <td>8GB</td>
          <td>3GB</td>
          <td>11GB</td>
          <td>16GB</td>
      </tr>
      <tr>
          <td>12GB</td>
          <td>3GB</td>
          <td>15GB</td>
          <td>24GB</td>
      </tr>
      <tr>
          <td>16GB</td>
          <td>4GB</td>
          <td>20GB</td>
          <td>32GB</td>
      </tr>
      <tr>
          <td>24GB</td>
          <td>5GB</td>
          <td>29GB</td>
          <td>48GB</td>
      </tr>
      <tr>
          <td>32GB</td>
          <td>6GB</td>
          <td>38GB</td>
          <td>64GB</td>
      </tr>
      <tr>
          <td>64GB</td>
          <td>8GB</td>
          <td>72GB</td>
          <td>128GB</td>
      </tr>
  </tbody>
</table>
<ol start="2">
<li>文件系统启动时挂载<code>/swapfile</code></li>
</ol>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-shell" data-lang="shell"><span class="line"><span class="cl"><span class="nb">echo</span> <span class="s1">&#39;/swapfile swap swap defaults 0 0&#39;</span> <span class="p">|</span> sudo tee -a /etc/fstab
</span></span></code></pre></div><ol start="3">
<li>配置启动内核参数</li>
</ol>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-shell" data-lang="shell"><span class="line"><span class="cl"><span class="c1"># 查看swapfile的UUID  </span>
</span></span><span class="line"><span class="cl">sudo findmnt -no UUID -T /swapfile  
</span></span><span class="line"><span class="cl"><span class="c1"># 查看swap_file_offset，忽略..符号  </span>
</span></span><span class="line"><span class="cl">sudo filefrag -v /swapfile <span class="p">|</span> awk <span class="s1">&#39;{ if($1==&#34;0:&#34;){print substr($4, 1, length($4)-2)} }&#39;</span>  
</span></span><span class="line"><span class="cl"><span class="c1"># 编辑grub文件  </span>
</span></span><span class="line"><span class="cl">sudo nano /etc/default/grub  
</span></span><span class="line"><span class="cl"><span class="c1"># 将grub文件中GRUB_CMDLINE_LINUX_DEFAULT参数修改为如下形式  </span>
</span></span><span class="line"><span class="cl"><span class="c1"># 其中UUID和resume_offset的值更换为上面两个命令的输出  </span>
</span></span><span class="line"><span class="cl"><span class="nv">GRUB_CMDLINE_LINUX_DEFAULT</span><span class="o">=</span><span class="s2">&#34;quiet splash resume=UUID=51f8eab4-d775-4020-aace-0e411ef5b8ed resume_offset=34816&#34;</span>  
</span></span><span class="line"><span class="cl"><span class="c1"># 保存退出，然后更新grub配置  </span>
</span></span><span class="line"><span class="cl">sudo update-grub  
</span></span><span class="line"><span class="cl"><span class="c1"># 编辑initramfs  </span>
</span></span><span class="line"><span class="cl">sudo nano /etc/initramfs-tools/conf.d/resume  
</span></span><span class="line"><span class="cl"><span class="c1"># 加入下面一行，UUID替换为实际值  </span>
</span></span><span class="line"><span class="cl"><span class="nv">resume</span><span class="o">=</span><span class="nv">UUID</span><span class="o">=</span>51f8eab4-d775-4020-aace-0e411ef5b8ed  
</span></span><span class="line"><span class="cl"><span class="c1"># 保存退出，然后更新initramfs配置  </span>
</span></span><span class="line"><span class="cl">sudo update-initramfs -u  
</span></span><span class="line"><span class="cl"><span class="c1"># 重启  </span>
</span></span><span class="line"><span class="cl">reboot
</span></span></code></pre></div><p>重启后执行<code>sudo systemctl hibernate</code>测试是否可以正常休眠。</p>
<ol start="4">
<li>配置Gnome界面
<strong>Option 1: The legacy method for For Ubuntu 22.04 and earlier, though it still works in Ubuntu 24.04.</strong></li>
</ol>
<p>1.) First, open terminal (Ctrl+Alt+T) and run command to create the config directory, that’s not exist by default in Ubuntu 24.04:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-shell" data-lang="shell"><span class="line"><span class="cl">sudo mkdir -p /etc/polkit-1/localauthority/50-local.d
</span></span></code></pre></div><p>2.) Thanks to @Joerg, Ubuntu 23.10 and Ubuntu 24.04 users need to install <code>polkitd-plka</code> package first:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-shell" data-lang="shell"><span class="line"><span class="cl">sudo apt install polkitd-pkla
</span></span></code></pre></div><p>3.) Then, run command to create &amp; edit a config file (replace gedit with gnome-text-editor for 24.04):</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-shell" data-lang="shell"><span class="line"><span class="cl">sudo gedit /etc/polkit-1/localauthority/50-local.d/com.ubuntu.enable-hibernate.pkla
</span></span></code></pre></div><p>When the file opens, paste following lines and save it.</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-text" data-lang="text"><span class="line"><span class="cl">[Re-enable hibernate by default in upower]
</span></span><span class="line"><span class="cl">Identity=unix-user:*
</span></span><span class="line"><span class="cl">Action=org.freedesktop.upower.hibernate
</span></span><span class="line"><span class="cl">ResultActive=yes
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">[Re-enable hibernate by default in logind]
</span></span><span class="line"><span class="cl">Identity=unix-user:*
</span></span><span class="line"><span class="cl">Action=org.freedesktop.login1.hibernate;org.freedesktop.login1.handle-hibernate-key;org.freedesktop.login1;org.freedesktop.login1.hibernate-multiple-sessions;org.freedesktop.login1.hibernate-ignore-inhibit
</span></span><span class="line"><span class="cl">ResultActive=yes
</span></span></code></pre></div><p>
<a href="https://www.wang7x.com/2021-01-28-ubuntu-20-04-hibernate/"
   target="_blank">
 原网站</a>
的这种方法是针对的Ubuntu22.04及之前的版本，新版的Ubuntu24.04参考
<a href="https://ubuntuhandbook.org/index.php/2021/08/enable-hibernate-ubuntu-21-10/"
   target="_blank">
 这个</a>
</p>
<p><strong>Option 2: Thanks to 
<a href="https://www.reddit.com/r/Kubuntu/comments/1c2frkd/enabling_hibernation_on_2404/?rdt=59532"
   target="_blank">
 this thread</a>
, since Ubuntu 24.04, the new method to add hibernate menu option is to create config file under <code>/etc/polkit-1/rules.d/</code> directory.</strong></p>
<p>First, run command to create a config file:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-shell" data-lang="shell"><span class="line"><span class="cl">sudo nano /etc/polkit-1/rules.d/10-enable-hibernate.rules
</span></span></code></pre></div><p>When file opens, paste the lines below and press Ctrl+S to save, finally press Ctrl+X to exit.</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-shell" data-lang="shell"><span class="line"><span class="cl">polkit.addRule<span class="o">(</span><span class="k">function</span><span class="o">(</span>action, subject<span class="o">)</span> <span class="o">{</span>
</span></span><span class="line"><span class="cl">    <span class="k">if</span> <span class="o">(</span>action.id <span class="o">==</span> <span class="s2">&#34;org.freedesktop.login1.hibernate&#34;</span> <span class="o">||</span>
</span></span><span class="line"><span class="cl">        action.id <span class="o">==</span> <span class="s2">&#34;org.freedesktop.login1.hibernate-multiple-sessions&#34;</span> <span class="o">||</span>
</span></span><span class="line"><span class="cl">        action.id <span class="o">==</span> <span class="s2">&#34;org.freedesktop.upower.hibernate&#34;</span> <span class="o">||</span>
</span></span><span class="line"><span class="cl">        action.id <span class="o">==</span> <span class="s2">&#34;org.freedesktop.login1.handle-hibernate-key&#34;</span> <span class="o">||</span>
</span></span><span class="line"><span class="cl">        action.id <span class="o">==</span> <span class="s2">&#34;org.freedesktop.login1.hibernate-ignore-inhibit&#34;</span><span class="o">)</span>
</span></span><span class="line"><span class="cl">    <span class="o">{</span>
</span></span><span class="line"><span class="cl">        <span class="k">return</span> polkit.Result.YES<span class="p">;</span>
</span></span><span class="line"><span class="cl">    <span class="o">}</span>
</span></span><span class="line"><span class="cl"><span class="o">})</span><span class="p">;</span>
</span></span></code></pre></div><p><strong>After doing either option above, your desktop (including MATE, XFCE, KDE, but exclude GNOME) should NOW have the “Hibernate” option in shutdown menu!</strong></p>
<p><strong>For GNOME, the default desktop in Ubuntu, you need to do one more step to make the menu option visible:</strong></p>
<p>For default GNOME desktop, go to the link button below and install the extension for the menu option: 
<a href="https://extensions.gnome.org/extension/755/hibernate-status-button/"
   target="_blank">
 Hibernate Status Button</a>
</p>
]]></content:encoded>
    </item>
    <item>
      <title>CLIP 详解</title>
      <link>https://liushiguang.com/posts/clip/</link>
      <pubDate>Mon, 18 Nov 2024 10:13:42 +0800</pubDate>
      <guid>https://liushiguang.com/posts/clip/</guid>
      <description>Alec Radford等人提出&lt;strong&gt;Contrastive Language-Image Pre-training (CLIP)&lt;/strong&gt;, 突破了文本-图像之间的限制。CLIP使用大规模的文本-图像配对预训练，并且可以直接迁移到Imagenet上，完全不需要图像标签微调即可实现zero-shot分类。CLIP模型或许会引导CV的发展走向&amp;hellip;</description>
      <content:encoded><![CDATA[<p>Alec Radford等人提出<strong>Contrastive Language-Image Pre-training (CLIP)</strong>, 突破了文本-图像之间的限制。CLIP使用大规模的文本-图像配对预训练，并且可以直接迁移到Imagenet上，完全不需要图像标签微调即可实现zero-shot分类。CLIP模型或许会引导CV的发展走向大规模预训练，文本-图像打通的时代。<sup id="fnref:1"><a href="#fn:1" class="footnote-ref" role="doc-noteref">1</a></sup></p>
<h3 id="预训练方法">预训练方法</h3>
<h4 id="文本-图像对">文本-图像对</h4>
<p>常规的图像分类模型往往都基于有类别标签的图像数据集进行全监督训练，这往往对于数据需求非常高，需要大量人工标注；同时限制了模型的适用性和泛化能力，不适于任务迁移。</p>
<p>而在我们的互联网上，可以轻松获取大批量的文本-图像配对数据。Open AI团队通过收集<strong>4亿（400 million)</strong> 个 <strong>文本-图像对((image, text) pairs)</strong>，以用来训练其提出的CLIP模型。

<div class="post-img-view">
<a data-fancybox="gallery" href="https://bucket.liushiguang.com/picturebed/CLIP%E7%B3%BB%E5%88%97-1.png">
<img src="https://bucket.liushiguang.com/picturebed/CLIP%E7%B3%BB%E5%88%97-1.png" alt="CLIP系列-1"  />
</a>
</div>

</p>
<h4 id="模型结构">模型结构</h4>
<p>CLIP的模型结构其实非常简单：包括两个部分，即<strong>文本编码器（Text Encoder）<strong>和</strong>图像编码器（Image Encoder)</strong>。Text Encoder选择的是<strong>Text Transformer</strong>模型；Image Encoder选择了两种模型，一是基于CNN的<strong>ResNet</strong>（对比了不同层数的ResNet），二是基于Transformer的
<a href="/posts/vit"
   target="_blank">
 ViT</a>
。

<div class="post-img-view">
<a data-fancybox="gallery" href="https://bucket.liushiguang.com/picturebed/CLIP%E7%B3%BB%E5%88%97-2.png">
<img src="https://bucket.liushiguang.com/picturebed/CLIP%E7%B3%BB%E5%88%97-2.png" alt="CLIP系列-2"  />
</a>
</div>

</p>
<h4 id="训练过程">训练过程</h4>
<p>CLIP在文本-图像对数据集上的训练过程如下：</p>
<ol>
<li>假设DataLoader中的一个batch包含了 <code>$N$</code> 个（文本-图像）对。将这 <code>$N$</code> 个文本先通过Text Encoder进行文本编码，假设Text Encoder将每条文本编码为一个长度为 <code>$d_t$</code> 的一维向量，那么这个batch的文本数据经Text Encoder的输出为<code>$[T_{1}, T_{2}, \dots , T_{N}]$</code> ,维度为 <code>$(N,d_{t})$</code> ; 同样的，将这 <code>$N$</code> 个图像先通过Image Encoder进行图像编码，假设Image Encoder将每条文本编码为一个长度为 <code>$d_{i}$</code> 的一维向量，那么这个batch的图像数据经Image Encoder的输出为<code>$[I_{1}, I_{2}, \dots , I_{N}]$</code>,维度为 <code>$(N,d_{i})$</code> 。</li>
<li>得到的<code>$[T_{1}, T_{2}, \dots , T_{N}]$</code>和中<code>$[I_{1}, I_{2}, \dots , I_{N}]$</code>，文本-图像是一一对应的，例如 <code>$T_{1}$</code> 与 <code>$I_{1}$</code> 对应，<code>$T_{2}$</code> 与 <code>$I_{2}$</code> 对应，&hellip;，<code>$T_{N}$</code>与 <code>$I_{N}$</code> 对应，这 <code>$N$</code> 个对应关系我们记为正样本；而原本并不对应的文本-图像我们标记为负样本，例如 <code>$T_1$</code> 与 <code>$I_2$</code> 不对应，<code>$T_N$</code> 与 <code>$I_{N-1}$</code> 不对应。这么一来，我们就有个 <code>$N$</code> 个正样本，<code>$N^2-N$</code> 个负样本。这样正负样本就可以作为正负标签，用来训练Text Encoder和Image Encoder了。</li>
<li>我们通过计算 <code>$I_i$</code> 与 <code>$T_j$</code> ( <code>$i,j \in [1,N]$</code> )之间的<strong>余弦相似度（cosine similarity）</strong>, <code>$I_{i} \cdot T_{j}$</code>用来度量相应的文本与图像之间的对应关系。余弦相似度越大，表明<code>$I_i$</code> 与<code>$T_j$</code> 的对应关系越强，反之越弱。那么训练的任务便很清晰地变成了：通过训练Text Encoder和Image Encoder的参数，最大化<code>$N$</code>个正样本的余弦相似度，最小化 <code>$N^2-N$</code>个负样本的余弦相似度。按上图所示，即最大化对角线中蓝色的数值，最小化其它非对角线的数值。优化目标可以写为：
<code>$$ min(\sum\limits_{i=1}^{N} \sum\limits_{i=1}^{N} (I_{i} \cdot T_{j})_{(i \neq j)} - \sum\limits_{i=1}^{N}(I_{i} \cdot T_i)) $$</code>
原论文中的伪代码实现如下：</li>
</ol>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="cl"><span class="c1"># image_encoder - ResNet or Vision Transformer</span>
</span></span><span class="line"><span class="cl"><span class="c1"># text_encoder - CBOW or Text Transformer</span>
</span></span><span class="line"><span class="cl"><span class="c1"># I[n, h, w, c] - minibatch of aligned images</span>
</span></span><span class="line"><span class="cl"><span class="c1"># T[n, l] - minibatch of aligned texts</span>
</span></span><span class="line"><span class="cl"><span class="c1"># W_i[d_i, d_e] - learned proj of image to embed</span>
</span></span><span class="line"><span class="cl"><span class="c1"># W_t[d_t, d_e] - learned proj of text to embed</span>
</span></span><span class="line"><span class="cl"><span class="c1"># t - learned temperature parameter</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c1"># 分别提取图像特征和文本特征</span>
</span></span><span class="line"><span class="cl"><span class="n">I_f</span> <span class="o">=</span> <span class="n">image_encoder</span><span class="p">(</span><span class="n">I</span><span class="p">)</span> <span class="c1">#[n, d_i]</span>
</span></span><span class="line"><span class="cl"><span class="n">T_f</span> <span class="o">=</span> <span class="n">text_encoder</span><span class="p">(</span><span class="n">T</span><span class="p">)</span> <span class="c1">#[n, d_t]</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c1"># 对两个特征进行线性投射，得到相同维度的特征，并进行l2归一化</span>
</span></span><span class="line"><span class="cl"><span class="n">I_e</span> <span class="o">=</span> <span class="n">l2_normalize</span><span class="p">(</span><span class="n">np</span><span class="o">.</span><span class="n">dot</span><span class="p">(</span><span class="n">I_f</span><span class="p">,</span> <span class="n">W_i</span><span class="p">),</span> <span class="n">axis</span><span class="o">=</span><span class="mi">1</span><span class="p">)</span>
</span></span><span class="line"><span class="cl"><span class="n">T_e</span> <span class="o">=</span> <span class="n">l2_normalize</span><span class="p">(</span><span class="n">np</span><span class="o">.</span><span class="n">dot</span><span class="p">(</span><span class="n">T_f</span><span class="p">,</span> <span class="n">W_t</span><span class="p">),</span> <span class="n">axis</span><span class="o">=</span><span class="mi">1</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c1"># 计算缩放的余弦相似度：[n, n]</span>
</span></span><span class="line"><span class="cl"><span class="n">logits</span> <span class="o">=</span> <span class="n">np</span><span class="o">.</span><span class="n">dot</span><span class="p">(</span><span class="n">I_e</span><span class="p">,</span> <span class="n">T_e</span><span class="o">.</span><span class="n">T</span><span class="p">)</span> <span class="o">*</span> <span class="n">np</span><span class="o">.</span><span class="n">exp</span><span class="p">(</span><span class="n">t</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c1"># 对称的对比学习损失：等价于N个类别的cross_entropy_loss</span>
</span></span><span class="line"><span class="cl"><span class="n">labels</span> <span class="o">=</span> <span class="n">np</span><span class="o">.</span><span class="n">arange</span><span class="p">(</span><span class="n">n</span><span class="p">)</span> <span class="c1"># 对角线元素的labels</span>
</span></span><span class="line"><span class="cl"><span class="n">loss_i</span> <span class="o">=</span> <span class="n">cross_entropy_loss</span><span class="p">(</span><span class="n">logits</span><span class="p">,</span> <span class="n">labels</span><span class="p">,</span> <span class="n">axis</span><span class="o">=</span><span class="mi">0</span><span class="p">)</span>
</span></span><span class="line"><span class="cl"><span class="n">loss_t</span> <span class="o">=</span> <span class="n">cross_entropy_loss</span><span class="p">(</span><span class="n">logits</span><span class="p">,</span> <span class="n">labels</span><span class="p">,</span> <span class="n">axis</span><span class="o">=</span><span class="mi">1</span><span class="p">)</span>
</span></span><span class="line"><span class="cl"><span class="n">loss</span> <span class="o">=</span> <span class="p">(</span><span class="n">loss_i</span> <span class="o">+</span> <span class="n">loss_t</span><span class="p">)</span><span class="o">/</span><span class="mi">2</span>
</span></span></code></pre></div><h4 id="损失函数">损失函数</h4>
<p>对比学习损失函数有多种，其中比较常用的一种是InfoNCE loss<sup id="fnref:2"><a href="#fn:2" class="footnote-ref" role="doc-noteref">2</a></sup>，何恺明的论文MoCo提出，我们可以把对比学习看成是一个字典查询的任务，即训练一个编码器从而去做字典查询的任务。</p>
<p>假设已经有一个编码好的query <code>$q$</code>（一个特征），以及一系列编码好的样本<code>$k_{0}, k_{1}, k_{2}, \dots$</code>那么<code>$k_{0}, k_{1}, k_{2}, \dots$</code>可以看作是字典里的key。假设字典里只有一个key即 <code>$k_+$</code> (称为 <code>$k$</code> positive）是跟<code>$q$</code>是匹配的，那么 <code>$q$</code> 和 <code>$k_+$</code> 就互为正样本对，其余的key为q的负样本。一旦定义好了正负样本对，就需要一个对比学习的损失函数来指导模型来进行学习。这个损失函数需要满足这些要求，即当query 和 <code>$q$</code> 唯一的正样本 <code>$k_+$</code> 相似，并且和其他所有负样本key都不相似的时候，这个loss的值应该比较低。反之，如果 <code>$q$</code> 和 <code>$k_+$</code> 不相似，或者 <code>$q$</code> 和其他负样本的key相似了，那么loss就应该大，从而惩罚模型，促使模型进行参数更新。

<div class="post-img-view">
<a data-fancybox="gallery" href="https://bucket.liushiguang.com/picturebed/CLIP%E7%B3%BB%E5%88%97-4.png">
<img src="https://bucket.liushiguang.com/picturebed/CLIP%E7%B3%BB%E5%88%97-4.png" alt="CLIP系列-4"  />
</a>
</div>


MoCo采用的对比学习损失函数就是InfoNCE loss，以此来训练模型，公式如下：
<code>$$ L_{q} = - \log \frac{exp(q \cdot k_{+} /_\mathcal{T})}{\sum_{i=0}^{k}exp(q \cdot k_{i} /_{\mathcal{T}})} $$</code>
CLIP 在两个方向上应用 InfoNCE Loss：</p>
<ul>
<li>对于每个图像 <code>$I_i$</code> ，目标是让 <code>$S_{i,i}$</code> (匹配的文本 <code>$T_i$</code> )的得分高于其他 (<code>$S_{i,j}(j \neq i)$</code>) (不匹配的文本)</li>
<li>对于每个文本 <code>$T_i$</code> ，目标是让 <code>$S_{i,i}$</code> (匹配的图像 <code>$I_i$</code> )的得分高于其他 (<code>$S_{i,j}(j \neq i)$</code>) (不匹配的图像)</li>
</ul>
<p>损失函数的形式为：</p>
<p><code>$$ \mathcal{L}_{image} = - \frac{1}{N}\sum\limits_{i=1}^{N} \log \frac{exp(S_{i,i}/\tau)}{\sum_{j=1}^{N} exp(S_{i,j}/\tau)} $$</code></p>
<p><code>$$ \mathcal{L}_{text} = - \frac{1}{N}\sum\limits_{i=1}^{N} \log \frac{exp(S_{i,i}/\tau)}{\sum_{j=1}^{N} exp(S_{j,i}/\tau)} $$</code></p>
<p>( <code>$\tau$</code> ) 是一个可学习的温度参数，用于缩放相似度得分。</p>
<p>总损失是两个方向的平均：
<code>$$ \mathcal{L} = \frac{1}{2}(\mathcal{L}_{image} + \mathcal{L}_{text})  $$</code>
由于InfoNCE的形式与CrossEntropy的形式完全一致，因此对于每一行<code>$S[i,:]$</code>(图像<code>$I_i$</code>对所有文本的相似度)，我们可以将其视为一个分类问题，目标是让模型正确预测第i个文本(正样本)</p>
<h3 id="零样本图像分类">零样本图像分类</h3>
<p>经过上一章的讲解，我们了解了CLIP如何在文本-图像对数据上训练，并且训练好的模型有能力判断给定的文本和图像是否匹配。<strong>这时CLIP已经完成了其全部训练过程，完全不需要Imagenet或其它数据集中的图像-类别标签，即可以直接做图像分类了，这也是CLIP这个模型最大的亮点：zero-shot图像分类</strong>。这是如何实现的呢，其实也非常简单：

<div class="post-img-view">
<a data-fancybox="gallery" href="https://bucket.liushiguang.com/picturebed/CLIP%E7%B3%BB%E5%88%97-3.png">
<img src="https://bucket.liushiguang.com/picturebed/CLIP%E7%B3%BB%E5%88%97-3.png" alt="CLIP系列-3"  />
</a>
</div>


步骤如下：</p>
<ol>
<li>根据所迁移的数据集将所有类别转换为文本。这里以Imagenet有1000类为例，我们得到了1000个文本：<code>A photo of {label}</code>。我们将这1000个文本全部输入 Text Encoder中，得到1000个编码后的向量 <code>$T_{i}(i = 1,2, \dots , N)(N=1000)$</code>,这被视作文本特征。</li>
<li>我们将需要分类的图像（单张图像）输入Image Encoder中，得到这张图像编码后的向量 <code>$I_1$</code> 。将<code>$I_1$</code>与得到的1000个文本特征分别计算余弦相似度。找出1000个相似度中最大的那一个（上图中对应的为 <code>$T_3$</code> ），那么评定要分类的图片与第三个文本标签（dog）最匹配，即可将其分类为狗。</li>
</ol>
<div class="footnotes" role="doc-endnotes">
<hr>
<ol>
<li id="fn:1">
<p>详解CLIP (一) | 打通文本-图像预训练实现ImageNet的zero-shot分类，比肩全监督训练的ResNet50/101 
<a href="https://zhuanlan.zhihu.com/p/521151393"
   target="_blank">
 https://zhuanlan.zhihu.com/p/521151393</a>
&#160;<a href="#fnref:1" class="footnote-backref" role="doc-backlink">&#x21a9;&#xfe0e;</a></p>
</li>
<li id="fn:2">
<p>InfoNCE 在 CLIP 中的应用原理（代码实现）：为什么用交叉熵F.cross_entropy来实现？ 
<a href="https://blog.csdn.net/shizheng_Li/article/details/146710821"
   target="_blank">
 https://blog.csdn.net/shizheng_Li/article/details/146710821</a>
&#160;<a href="#fnref:2" class="footnote-backref" role="doc-backlink">&#x21a9;&#xfe0e;</a></p>
</li>
</ol>
</div>
]]></content:encoded>
    </item>
    <item>
      <title>Transformer 详解</title>
      <link>https://liushiguang.com/posts/transformer/</link>
      <pubDate>Mon, 18 Nov 2024 10:13:28 +0800</pubDate>
      <guid>https://liushiguang.com/posts/transformer/</guid>
      <description>&lt;h3 id=&#34;整体结构&#34;&gt;整体结构&lt;/h3&gt;
&lt;p&gt;下图是 Transformer 用于中英文翻译的整体结构&lt;sup id=&#34;fnref:1&#34;&gt;&lt;a href=&#34;#fn:1&#34; class=&#34;footnote-ref&#34; role=&#34;doc-noteref&#34;&gt;1&lt;/a&gt;&lt;/sup&gt;：

&lt;div class=&#34;post-img-view&#34;&gt;
&lt;a data-fancybox=&#34;gallery&#34; href=&#34;https://bucket.liushiguang.com/picturebed/Transformer-1.png&#34;&gt;
&lt;img src=&#34;https://bucket.liushiguang.com/picturebed/Transformer-1.png&#34; alt=&#34;Transformer-1&#34;  /&gt;
&lt;/a&gt;
&lt;/div&gt;


可以看到 &lt;strong&gt;Transformer 由 Encoder 和 Decoder 两个部分组成&lt;/strong&gt;，Encoder 和 Decoder 都包含 6 个 block。Transformer 的工作流程大体如下：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;获取输入句子的每一个单词的表示向量 &lt;strong&gt;X&lt;/strong&gt;，&lt;strong&gt;X&lt;/strong&gt;由单词的 Embedding（Embedding就是从原始数据提取出来的Feature） 和单词位置的 Embedding 相加得到。

&lt;div class=&#34;post-img-view&#34;&gt;
&lt;a data-fancybox=&#34;gallery&#34; href=&#34;https://bucket.liushiguang.com/picturebed/Transformer-2.png&#34;&gt;
&lt;img src=&#34;https://bucket.liushiguang.com/picturebed/Transformer-2.png&#34; alt=&#34;Transformer-2&#34;  /&gt;
&lt;/a&gt;
&lt;/div&gt;

&lt;/li&gt;
&lt;li&gt;将得到的单词表示向量矩阵 (如上图所示，每一行是一个单词的表示 &lt;strong&gt;x&lt;/strong&gt;) 传入 Encoder 中，经过 6 个 Encoder block 后可以得到句子所有单词的编码信息矩阵 &lt;strong&gt;C&lt;/strong&gt;，如下图。单词向量矩阵用 &lt;code&gt;$X_{n\times d}$&lt;/code&gt; 表示， n 是句子中单词个数，d 是表示向量的维度 (论文中 d=512)。每一个 Encoder block 输出的矩阵维度与输入完全一致。

&lt;div class=&#34;post-img-view&#34;&gt;
&lt;a data-fancybox=&#34;gallery&#34; href=&#34;https://bucket.liushiguang.com/picturebed/Transformer-3.png&#34;&gt;
&lt;img src=&#34;https://bucket.liushiguang.com/picturebed/Transformer-3.png&#34; alt=&#34;Transformer-3&#34;  /&gt;
&lt;/a&gt;
&lt;/div&gt;

&lt;/li&gt;
&lt;li&gt;将 Encoder 输出的编码信息矩阵 &lt;strong&gt;C&lt;/strong&gt;传递到 Decoder 中，Decoder 依次会根据当前翻译过的单词 1~ i 翻译下一个单词 i+1，如下图所示。在使用的过程中，翻译到单词 i+1 的时候需要通过 &lt;strong&gt;Mask (掩盖)&lt;/strong&gt; 操作遮盖住 i+1 之后的单词。

&lt;div class=&#34;post-img-view&#34;&gt;
&lt;a data-fancybox=&#34;gallery&#34; href=&#34;https://bucket.liushiguang.com/picturebed/Transformer-4.png&#34;&gt;
&lt;img src=&#34;https://bucket.liushiguang.com/picturebed/Transformer-4.png&#34; alt=&#34;Transformer-4&#34;  /&gt;
&lt;/a&gt;
&lt;/div&gt;


上图 Decoder 接收了 Encoder 的编码矩阵 &lt;strong&gt;C&lt;/strong&gt;，然后首先输入一个翻译开始符 &amp;ldquo;&amp;lt;Begin&amp;gt;&amp;quot;，预测第一个单词 &amp;ldquo;I&amp;rdquo;；然后输入翻译开始符 &amp;ldquo;&amp;lt;Begin&amp;gt;&amp;rdquo; 和单词 &amp;ldquo;I&amp;rdquo;，预测单词 &amp;ldquo;have&amp;rdquo;，以此类推。这是 Transformer 使用时候的大致流程，接下来是里面各个部分的细节。&lt;/li&gt;
&lt;/ol&gt;
&lt;h3 id=&#34;transformer的输入&#34;&gt;Transformer的输入&lt;/h3&gt;
&lt;p&gt;Transformer 中单词的输入表示 &lt;strong&gt;x&lt;/strong&gt;由&lt;strong&gt;单词 Embedding&lt;/strong&gt; 和&lt;strong&gt;位置 Embedding&lt;/strong&gt; （Positional Encoding）相加得到。

&lt;div class=&#34;post-img-view&#34;&gt;
&lt;a data-fancybox=&#34;gallery&#34; href=&#34;https://bucket.liushiguang.com/picturebed/Transformer-5.png&#34;&gt;
&lt;img src=&#34;https://bucket.liushiguang.com/picturebed/Transformer-5.png&#34; alt=&#34;Transformer-5&#34;  /&gt;
&lt;/a&gt;
&lt;/div&gt;

&lt;/p&gt;</description>
      <content:encoded><![CDATA[<h3 id="整体结构">整体结构</h3>
<p>下图是 Transformer 用于中英文翻译的整体结构<sup id="fnref:1"><a href="#fn:1" class="footnote-ref" role="doc-noteref">1</a></sup>：

<div class="post-img-view">
<a data-fancybox="gallery" href="https://bucket.liushiguang.com/picturebed/Transformer-1.png">
<img src="https://bucket.liushiguang.com/picturebed/Transformer-1.png" alt="Transformer-1"  />
</a>
</div>


可以看到 <strong>Transformer 由 Encoder 和 Decoder 两个部分组成</strong>，Encoder 和 Decoder 都包含 6 个 block。Transformer 的工作流程大体如下：</p>
<ol>
<li>获取输入句子的每一个单词的表示向量 <strong>X</strong>，<strong>X</strong>由单词的 Embedding（Embedding就是从原始数据提取出来的Feature） 和单词位置的 Embedding 相加得到。

<div class="post-img-view">
<a data-fancybox="gallery" href="https://bucket.liushiguang.com/picturebed/Transformer-2.png">
<img src="https://bucket.liushiguang.com/picturebed/Transformer-2.png" alt="Transformer-2"  />
</a>
</div>

</li>
<li>将得到的单词表示向量矩阵 (如上图所示，每一行是一个单词的表示 <strong>x</strong>) 传入 Encoder 中，经过 6 个 Encoder block 后可以得到句子所有单词的编码信息矩阵 <strong>C</strong>，如下图。单词向量矩阵用 <code>$X_{n\times d}$</code> 表示， n 是句子中单词个数，d 是表示向量的维度 (论文中 d=512)。每一个 Encoder block 输出的矩阵维度与输入完全一致。

<div class="post-img-view">
<a data-fancybox="gallery" href="https://bucket.liushiguang.com/picturebed/Transformer-3.png">
<img src="https://bucket.liushiguang.com/picturebed/Transformer-3.png" alt="Transformer-3"  />
</a>
</div>

</li>
<li>将 Encoder 输出的编码信息矩阵 <strong>C</strong>传递到 Decoder 中，Decoder 依次会根据当前翻译过的单词 1~ i 翻译下一个单词 i+1，如下图所示。在使用的过程中，翻译到单词 i+1 的时候需要通过 <strong>Mask (掩盖)</strong> 操作遮盖住 i+1 之后的单词。

<div class="post-img-view">
<a data-fancybox="gallery" href="https://bucket.liushiguang.com/picturebed/Transformer-4.png">
<img src="https://bucket.liushiguang.com/picturebed/Transformer-4.png" alt="Transformer-4"  />
</a>
</div>


上图 Decoder 接收了 Encoder 的编码矩阵 <strong>C</strong>，然后首先输入一个翻译开始符 &ldquo;&lt;Begin&gt;&quot;，预测第一个单词 &ldquo;I&rdquo;；然后输入翻译开始符 &ldquo;&lt;Begin&gt;&rdquo; 和单词 &ldquo;I&rdquo;，预测单词 &ldquo;have&rdquo;，以此类推。这是 Transformer 使用时候的大致流程，接下来是里面各个部分的细节。</li>
</ol>
<h3 id="transformer的输入">Transformer的输入</h3>
<p>Transformer 中单词的输入表示 <strong>x</strong>由<strong>单词 Embedding</strong> 和<strong>位置 Embedding</strong> （Positional Encoding）相加得到。

<div class="post-img-view">
<a data-fancybox="gallery" href="https://bucket.liushiguang.com/picturebed/Transformer-5.png">
<img src="https://bucket.liushiguang.com/picturebed/Transformer-5.png" alt="Transformer-5"  />
</a>
</div>

</p>
<h4 id="单词embedding">单词Embedding</h4>
<p>单词的 Embedding 有很多种方式可以获取，例如可以采用 Word2Vec、Glove 等算法预训练得到，也可以在 Transformer 中训练得到。</p>
<h4 id="位置embedding">位置Embedding</h4>
<p>Transformer 中除了单词的 Embedding，还需要使用位置 Embedding 表示单词出现在句子中的位置。<strong>因为 Transformer 不采用 RNN 的结构，而是使用全局信息，不能利用单词的顺序信息，而这部分信息对于 NLP 来说非常重要。</strong> 所以 Transformer 中使用位置 Embedding 保存单词在序列中的相对或绝对位置。</p>
<p>位置 Embedding 用 <strong>PE</strong> 表示，<strong>PE</strong> 的维度与单词 Embedding 是一样的。PE 可以通过训练得到，也可以使用某种公式计算得到。在 Transformer 中采用了后者，计算公式如下：

<div class="post-img-view">
<a data-fancybox="gallery" href="https://bucket.liushiguang.com/picturebed/Transformer-6.png">
<img src="https://bucket.liushiguang.com/picturebed/Transformer-6.png" alt="Transformer-6"  />
</a>
</div>


其中，pos 表示单词在句子中的位置，d 表示 PE的维度 (与词 Embedding 一样)，2i 表示偶数的维度，2i+1 表示奇数维度 (即 2i≤d, 2i+1≤d)。使用这种公式计算 PE 有以下的好处：</p>
<ul>
<li>使 PE 能够适应比训练集里面所有句子更长的句子，假设训练集里面最长的句子是有 20 个单词，突然来了一个长度为 21 的句子，则使用公式计算的方法可以计算出第 21 位的 Embedding。</li>
<li>可以让模型容易地计算出相对位置，对于固定长度的间距 k，<strong>PE(pos+k)</strong> 可以用 <strong>PE(pos)</strong> 计算得到。因为 Sin(A+B) = Sin(A)Cos(B) + Cos(A)Sin(B), Cos(A+B) = Cos(A)Cos(B) - Sin(A)Sin(B)。
将单词的词 Embedding 和位置 Embedding 相加，就可以得到单词的表示向量 <strong>x</strong>，<strong>x</strong> 就是 Transformer 的输入。</li>
</ul>
<h3 id="self-attention自注意力机制">Self-Attention(自注意力机制)</h3>
<p>
<div class="post-img-view">
<a data-fancybox="gallery" href="https://bucket.liushiguang.com/picturebed/Transformer-7.png">
<img src="https://bucket.liushiguang.com/picturebed/Transformer-7.png" alt="Transformer-7"  />
</a>
</div>


上图是论文中 Transformer 的内部结构图，左侧为 Encoder block，右侧为 Decoder block。红色圈中的部分为 <strong>Multi-Head Attention</strong>，是由多个 <strong>Self-Attention</strong> 组成的，可以看到 Encoder block 包含一个 Multi-Head Attention，而 Decoder block 包含两个 Multi-Head Attention (其中有一个用到 Masked)。Multi-Head Attention 上方还包括一个 Add &amp; Norm 层，Add 表示残差连接 (Residual Connection) 用于防止网络退化，Norm 表示 Layer Normalization，用于对每一层的激活值进行归一化。</p>
<p>因为 <strong>Self-Attention</strong> 是 Transformer 的重点，所以我们重点关注 Multi-Head Attention 以及 Self-Attention，首先详细了解一下 Self-Attention 的内部逻辑。</p>
<h4 id="self-attention结构">Self-Attention结构</h4>
<p>
<div class="post-img-view">
<a data-fancybox="gallery" href="https://bucket.liushiguang.com/picturebed/Transformer-8.png">
<img src="https://bucket.liushiguang.com/picturebed/Transformer-8.png" alt="Transformer-8"  />
</a>
</div>


上图是 Self-Attention 的结构，在计算的时候需要用到矩阵<strong>Q(查询),K(键值),V(值)</strong>。在实际中，Self-Attention 接收的是输入(单词的表示向量x组成的矩阵X) 或者上一个 Encoder block 的输出。而<strong>Q,K,V</strong>正是通过 Self-Attention 的输入进行线性变换得到的。</p>
<h4 id="q-k-v的计算">Q, K, V的计算</h4>
<p>Self-Attention 的输入用矩阵X进行表示，则可以使用线性变阵矩阵<strong>WQ,WK,WV</strong>计算得到<strong>Q,K,V</strong>。计算如下图所示，<strong>注意 X, Q, K, V 的每一行都表示一个单词。</strong>

<div class="post-img-view">
<a data-fancybox="gallery" href="https://bucket.liushiguang.com/picturebed/Transformer-9.png">
<img src="https://bucket.liushiguang.com/picturebed/Transformer-9.png" alt="Transformer-9"  />
</a>
</div>

</p>
<h4 id="self-attention的输出">Self-Attention的输出</h4>
<p>得到矩阵 Q, K, V之后就可以计算出 Self-Attention 的输出了，计算的公式如下：

<div class="post-img-view">
<a data-fancybox="gallery" href="https://bucket.liushiguang.com/picturebed/Transformer-10.png">
<img src="https://bucket.liushiguang.com/picturebed/Transformer-10.png" alt="Transformer-10"  />
</a>
</div>


公式中计算矩阵<strong>Q</strong>和<strong>K</strong>每一行向量的内积，为了防止内积过大，因此除以 <code>$d_k$</code> 的平方根。<strong>Q</strong>乘以<strong>K</strong>的转置后，得到的矩阵行列数都为 n，n 为句子单词数，这个矩阵可以表示单词之间的 attention 强度。下图为<strong>Q</strong>乘以 <code>$K^T$</code> ，1234 表示的是句子中的单词。

<div class="post-img-view">
<a data-fancybox="gallery" href="https://bucket.liushiguang.com/picturebed/Transformer-11.png">
<img src="https://bucket.liushiguang.com/picturebed/Transformer-11.png" alt="Transformer-11"  />
</a>
</div>


得到 <code>$QK^T$</code> 之后，使用 Softmax 计算每一个单词对于其他单词的 attention 系数，公式中的 Softmax 是对矩阵的每一行进行 Softmax，即每一行的和都变为 1。

<div class="post-img-view">
<a data-fancybox="gallery" href="https://bucket.liushiguang.com/picturebed/Transformer-12.png">
<img src="https://bucket.liushiguang.com/picturebed/Transformer-12.png" alt="Transformer-12"  />
</a>
</div>


得到 Softmax 矩阵之后可以和<strong>V</strong>相乘，得到最终的输出<strong>Z</strong>。

<div class="post-img-view">
<a data-fancybox="gallery" href="https://bucket.liushiguang.com/picturebed/Transformer-13.png">
<img src="https://bucket.liushiguang.com/picturebed/Transformer-13.png" alt="Transformer-13"  />
</a>
</div>


上图中 Softmax 矩阵的第 1 行表示单词 1 与其他所有单词的 attention 系数，最终单词 1 的输出 <code>$Z_1$</code> 等于所有单词 i 的值 <code>$V_i$</code> 根据 attention 系数的比例加在一起得到，如下图所示：

<div class="post-img-view">
<a data-fancybox="gallery" href="https://bucket.liushiguang.com/picturebed/Transformer-14.png">
<img src="https://bucket.liushiguang.com/picturebed/Transformer-14.png" alt="Transformer-14"  />
</a>
</div>

</p>
<h4 id="multi-head-attention">Multi-Head Attention</h4>
<p>在上一步，我们已经知道怎么通过 Self-Attention 计算得到输出矩阵 Z，而 Multi-Head Attention 是由多个 Self-Attention 组合形成的，下图是论文中 Multi-Head Attention 的结构图。

<div class="post-img-view">
<a data-fancybox="gallery" href="https://bucket.liushiguang.com/picturebed/Transformer-15.png">
<img src="https://bucket.liushiguang.com/picturebed/Transformer-15.png" alt="Transformer-15"  />
</a>
</div>


从上图可以看到 Multi-Head Attention 包含多个 Self-Attention 层，首先将输入<strong>X</strong>分别传递到 h 个不同的 Self-Attention 中，计算得到 h 个输出矩阵<strong>Z</strong>。下图是 h=8 时候的情况，此时会得到 8 个输出矩阵<strong>Z</strong>。

<div class="post-img-view">
<a data-fancybox="gallery" href="https://bucket.liushiguang.com/picturebed/Transformer-16.png">
<img src="https://bucket.liushiguang.com/picturebed/Transformer-16.png" alt="Transformer-16"  />
</a>
</div>


得到 8 个输出矩阵 <code>$Z_1$</code>  到 <code>$Z_8$</code> 之后，Multi-Head Attention 将它们拼接在一起 <strong>(Concat)</strong>，然后传入一个<strong>Linear</strong>层，得到 Multi-Head Attention 最终的输出<strong>Z</strong>。

<div class="post-img-view">
<a data-fancybox="gallery" href="https://bucket.liushiguang.com/picturebed/Transformer-17.png">
<img src="https://bucket.liushiguang.com/picturebed/Transformer-17.png" alt="Transformer-17"  />
</a>
</div>


可以看到 Multi-Head Attention 输出的矩阵<strong>Z</strong>与其输入的矩阵<strong>X</strong>的维度是一样的。</p>
<h3 id="encoder结构">Encoder结构</h3>
<p>
<div class="post-img-view">
<a data-fancybox="gallery" href="https://bucket.liushiguang.com/picturebed/Transformer-18.png">
<img src="https://bucket.liushiguang.com/picturebed/Transformer-18.png" alt="Transformer-18"  />
</a>
</div>


上图红色部分是 Transformer 的 Encoder block 结构，可以看到是由 Multi-Head Attention, <strong>Add &amp; Norm, Feed Forward, Add &amp; Norm</strong> 组成的。刚刚已经了解了 Multi-Head Attention 的计算过程，现在了解一下 Add &amp; Norm 和 Feed Forward 部分。</p>
<h4 id="add--norm">Add &amp; Norm</h4>
<p>Add &amp; Norm 层由 Add 和 Norm 两部分组成，其计算公式如下：

<div class="post-img-view">
<a data-fancybox="gallery" href="https://bucket.liushiguang.com/picturebed/Transformer-19.png">
<img src="https://bucket.liushiguang.com/picturebed/Transformer-19.png" alt="Transformer-19"  />
</a>
</div>


其中 <strong>X</strong>表示 Multi-Head Attention 或者 Feed Forward 的输入，MultiHeadAttention(<strong>X</strong>) 和 FeedForward(<strong>X</strong>) 表示输出 (输出与输入 <strong>X</strong> 维度是一样的，所以可以相加)。</p>
<p><strong>Add</strong>指 <strong>X</strong>+MultiHeadAttention(<strong>X</strong>)，是一种残差连接，通常用于解决多层网络训练的问题，可以让网络只关注当前差异的部分，在 ResNet 中经常用到：

<div class="post-img-view">
<a data-fancybox="gallery" href="https://bucket.liushiguang.com/picturebed/Transformer-20.png">
<img src="https://bucket.liushiguang.com/picturebed/Transformer-20.png" alt="Transformer-20"  />
</a>
</div>


<strong>Norm</strong>指 Layer Normalization，通常用于 RNN 结构，Layer Normalization 会将每一层神经元的输入都转成均值方差都一样的，这样可以加快收敛。</p>
<h4 id="feed-forward">Feed Forward</h4>
<p>Feed Forward 层比较简单，是一个两层的全连接层，第一层的激活函数为 Relu，第二层不使用激活函数，对应的公式如下。

<div class="post-img-view">
<a data-fancybox="gallery" href="https://bucket.liushiguang.com/picturebed/Transformer-21.png">
<img src="https://bucket.liushiguang.com/picturebed/Transformer-21.png" alt="Transformer-21"  />
</a>
</div>


<strong>X</strong>是输入，Feed Forward 最终得到的输出矩阵的维度与<strong>X</strong>一致。</p>
<h4 id="组成encoder">组成Encoder</h4>
<p>通过上面描述的 Multi-Head Attention, Feed Forward, Add &amp; Norm 就可以构造出一个 Encoder block，Encoder block 接收输入矩阵  ，并输出一个矩阵  。通过多个 Encoder block 叠加就可以组成 Encoder。</p>
<p>第一个 Encoder block 的输入为句子单词的表示向量矩阵，后续 Encoder block 的输入是前一个 Encoder block 的输出，最后一个 Encoder block 输出的矩阵就是<strong>编码信息矩阵 C</strong>，这一矩阵后续会用到 Decoder 中。

<div class="post-img-view">
<a data-fancybox="gallery" href="https://bucket.liushiguang.com/picturebed/Transformer-22.png">
<img src="https://bucket.liushiguang.com/picturebed/Transformer-22.png" alt="Transformer-22"  />
</a>
</div>

</p>
<h3 id="decoder结构">Decoder结构</h3>
<p>
<div class="post-img-view">
<a data-fancybox="gallery" href="https://bucket.liushiguang.com/picturebed/Transformer-23.png">
<img src="https://bucket.liushiguang.com/picturebed/Transformer-23.png" alt="Transformer-23"  />
</a>
</div>


上图红色部分为 Transformer 的 Decoder block 结构，与 Encoder block 相似，但是存在一些区别：</p>
<ul>
<li>包含两个 Multi-Head Attention 层。</li>
<li>第一个 Multi-Head Attention 层采用了 Masked 操作。</li>
<li>第二个 Multi-Head Attention 层的<strong>K, V</strong>矩阵使用 Encoder 的<strong>编码信息矩阵C</strong>进行计算，而<strong>Q</strong>使用上一个 Decoder block 的输出计算。</li>
<li>最后有一个 Softmax 层计算下一个翻译单词的概率。</li>
</ul>
<h4 id="第一个multi-head-attention">第一个Multi-Head Attention</h4>
<p>Decoder block 的第一个 Multi-Head Attention 采用了 Masked 操作，因为在翻译的过程中是顺序翻译的，即翻译完第 i 个单词，才可以翻译第 i+1 个单词。通过 Masked 操作可以下面以 &ldquo;我有一只猫&rdquo; 翻译成 &ldquo;I have a cat&rdquo; 为例，了解一下 Masked 操作。</p>
<p>下面的描述中使用了类似 Teacher Forcing 的概念。在 Decoder 的时候，是需要根据之前的翻译，求解当前最有可能的翻译，如下图所示。首先根据输入 &ldquo;&lt;Begin&gt;&rdquo; 预测出第一个单词为 &ldquo;I&rdquo;，然后根据输入 &ldquo;&lt;Begin&gt; I&rdquo; 预测下一个单词 &ldquo;have&rdquo;。

<div class="post-img-view">
<a data-fancybox="gallery" href="https://bucket.liushiguang.com/picturebed/Transformer-24.png">
<img src="https://bucket.liushiguang.com/picturebed/Transformer-24.png" alt="Transformer-24"  />
</a>
</div>


Decoder 可以在训练的过程中使用 Teacher Forcing 并且并行化训练，即将正确的单词序列 (&lt;Begin&gt; I have a cat) 和对应输出 (I have a cat &lt;end&gt;) 传递到 Decoder。那么在预测第 i 个输出时，就要将第 i+1 之后的单词掩盖住，<strong>注意 Mask 操作是在 Self-Attention 的 Softmax 之前使用的，下面用 0 1 2 3 4 5 分别表示 &ldquo;&lt;Begin&gt; I have a cat &lt;end&gt;&quot;。</strong></p>
<p>Mask矩阵中可以用1表示有效字，0代表无效字（也可以用True/False）。</p>
<p><strong>第一步：</strong> 是 Decoder 的输入矩阵和 <strong>Mask</strong> 矩阵，输入矩阵包含 &ldquo;&lt;Begin&gt; I have a cat&rdquo; (0, 1, 2, 3, 4) 五个单词的表示向量，<strong>Mask</strong> 是一个 5×5 的矩阵。在 <strong>Mask</strong> 可以发现单词 0 只能使用单词 0 的信息，而单词 1 可以使用单词 0, 1 的信息，即只能使用之前的信息。

<div class="post-img-view">
<a data-fancybox="gallery" href="https://bucket.liushiguang.com/picturebed/Transformer-25.png">
<img src="https://bucket.liushiguang.com/picturebed/Transformer-25.png" alt="Transformer-25"  />
</a>
</div>


<strong>第二步：</strong> 接下来的操作和之前的 Self-Attention 一样，通过输入矩阵<strong>X</strong>计算得到<strong>Q,K,V</strong>矩阵。然后计算<strong>Q</strong>和 <code>$K^T$</code>  的乘积 <code>$QK^T$</code> 。

<div class="post-img-view">
<a data-fancybox="gallery" href="https://bucket.liushiguang.com/picturebed/Transformer-26.png">
<img src="https://bucket.liushiguang.com/picturebed/Transformer-26.png" alt="Transformer-26"  />
</a>
</div>


<strong>第三步：</strong> 在得到 <code>$QK^T$</code> 之后需要进行 Softmax，计算 attention score，我们在 Softmax 之前需要使用<strong>Mask</strong>矩阵遮挡住每一个单词之后的信息，遮挡操作如下：

<div class="post-img-view">
<a data-fancybox="gallery" href="https://bucket.liushiguang.com/picturebed/Transformer-27.png">
<img src="https://bucket.liushiguang.com/picturebed/Transformer-27.png" alt="Transformer-27"  />
</a>
</div>


Q和K在点积之后，需要先经过mask再进行softmax，因此，对于要屏蔽的部分，mask之后的输出需要为负无穷，这样softmax之后输出才为0。</p>
<p>得到 <strong>Mask</strong> <code>$QK^T$</code>  之后在 <strong>Mask</strong> <code>$QK^T$</code> 上进行 Softmax，每一行的和都为 1。但是单词 0 在单词 1, 2, 3, 4 上的 attention score 都为 0。</p>
<p><strong>第四步：</strong> 使用 <strong>Mask</strong> <code>$QK^T$</code> 与矩阵 <strong>V</strong>相乘，得到输出 <strong>Z</strong>，则单词 1 的输出向量 <code>$Z_1$</code> 是只包含单词 1 信息的。

<div class="post-img-view">
<a data-fancybox="gallery" href="https://bucket.liushiguang.com/picturebed/Transformer-28.png">
<img src="https://bucket.liushiguang.com/picturebed/Transformer-28.png" alt="Transformer-28"  />
</a>
</div>


<strong>第五步：</strong> 通过上述步骤就可以得到一个 Mask Self-Attention 的输出矩阵 <code>$Z_i$</code> ，然后和 Encoder 类似，通过 Multi-Head Attention 拼接多个输出 <code>$Z_i$</code> 然后计算得到第一个 Multi-Head Attention 的输出<strong>Z</strong>，<strong>Z</strong>与输入<strong>X</strong>维度一样。</p>
<h4 id="第二个multi-head-attention">第二个Multi-Head Attention</h4>
<p>Decoder block 第二个 Multi-Head Attention 变化不大， 主要的区别在于其中 Self-Attention 的 <strong>K, V</strong>矩阵不是使用 上一个 Decoder block 的输出计算的，而是使用 <strong>Encoder 的编码信息矩阵 C</strong> 计算的。</p>
<p>根据 Encoder 的输出 <strong>C</strong>计算得到 <strong>K, V</strong>，根据上一个 Decoder block 的输出 <strong>Z</strong> 计算 <strong>Q</strong> (如果是第一个 Decoder block 则使用输入矩阵 <strong>X</strong> 进行计算)，后续的计算方法与之前描述的一致。</p>
<p>这样做的好处是在 Decoder 的时候，每一位单词都可以利用到 Encoder 所有单词的信息 (这些信息无需 <strong>Mask</strong>)。</p>
<h4 id="softmax预测输出单词">Softmax预测输出单词</h4>
<p>Decoder block 最后的部分是利用 Softmax 预测下一个单词，在之前的网络层我们可以得到一个最终的输出 Z，因为 Mask 的存在，使得单词 0 的输出 Z0 只包含单词 0 的信息，如下：

<div class="post-img-view">
<a data-fancybox="gallery" href="https://bucket.liushiguang.com/picturebed/Transformer-29.png">
<img src="https://bucket.liushiguang.com/picturebed/Transformer-29.png" alt="Transformer-29"  />
</a>
</div>


Softmax 根据输出矩阵的每一行预测下一个单词：

<div class="post-img-view">
<a data-fancybox="gallery" href="https://bucket.liushiguang.com/picturebed/Transformer-30.png">
<img src="https://bucket.liushiguang.com/picturebed/Transformer-30.png" alt="Transformer-30"  />
</a>
</div>


这就是 Decoder block 的定义，与 Encoder 一样，Decoder 是由多个 Decoder block 组合而成。</p>
<h3 id="混淆点">混淆点</h3>
<p>Transformer使用了Teacher Forcing的方法，在模型训练的过程中是并行的，而在模型推理的过程中是串行的。</p>
<h3 id="transfomer总结">Transfomer总结</h3>
<ul>
<li>Transformer 与 RNN 不同，可以比较好地并行训练。</li>
<li>Transformer 本身是不能利用单词的顺序信息的，因此需要在输入中添加位置 Embedding，否则 Transformer 就是一个词袋模型了。</li>
<li>Transformer 的重点是 Self-Attention 结构，其中用到的 <strong>Q, K, V</strong>矩阵通过输出进行线性变换得到。</li>
<li>Transformer 中 Multi-Head Attention 中有多个 Self-Attention，可以捕获单词之间多种维度上的相关系数 attention score。</li>
</ul>
<div class="footnotes" role="doc-endnotes">
<hr>
<ol>
<li id="fn:1">
<p>Transformer模型详解（图解最完整版） 
<a href="https://zhuanlan.zhihu.com/p/338817680"
   target="_blank">
 https://zhuanlan.zhihu.com/p/338817680</a>
&#160;<a href="#fnref:1" class="footnote-backref" role="doc-backlink">&#x21a9;&#xfe0e;</a></p>
</li>
</ol>
</div>
]]></content:encoded>
    </item>
    <item>
      <title>MySQL 8.0数据库添加用户和授权</title>
      <link>https://liushiguang.com/posts/mysql-8.0%E6%95%B0%E6%8D%AE%E5%BA%93%E6%B7%BB%E5%8A%A0%E7%94%A8%E6%88%B7%E5%92%8C%E6%8E%88%E6%9D%83/</link>
      <pubDate>Thu, 05 Sep 2024 12:41:37 +0800</pubDate>
      <guid>https://liushiguang.com/posts/mysql-8.0%E6%95%B0%E6%8D%AE%E5%BA%93%E6%B7%BB%E5%8A%A0%E7%94%A8%E6%88%B7%E5%92%8C%E6%8E%88%E6%9D%83/</guid>
      <description>&lt;h3 id=&#34;1-创建新用户&#34;&gt;1. 创建新用户&lt;/h3&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-sql&#34; data-lang=&#34;sql&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;k&#34;&gt;create&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;k&#34;&gt;user&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;s1&#34;&gt;&amp;#39;username&amp;#39;&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;@&lt;/span&gt;&lt;span class=&#34;s1&#34;&gt;&amp;#39;host&amp;#39;&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;n&#34;&gt;identified&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;k&#34;&gt;by&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;s1&#34;&gt;&amp;#39;password&amp;#39;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;;&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;其中&lt;code&gt;username&lt;/code&gt;为自定义的用户名；&lt;code&gt;host&lt;/code&gt;为登录域名，&lt;code&gt;%&lt;/code&gt;代表任意IP，&lt;code&gt;localhost&lt;/code&gt;代表本机，或者填写指定的IP地址；&lt;code&gt;password&lt;/code&gt;为密码&lt;/p&gt;
&lt;h3 id=&#34;2-为用户授权&#34;&gt;2. 为用户授权&lt;/h3&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-sql&#34; data-lang=&#34;sql&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;k&#34;&gt;grant&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;k&#34;&gt;all&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;k&#34;&gt;privileges&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;k&#34;&gt;on&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;o&#34;&gt;*&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;*&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;k&#34;&gt;to&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;s1&#34;&gt;&amp;#39;username&amp;#39;&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;@&lt;/span&gt;&lt;span class=&#34;s1&#34;&gt;&amp;#39;host&amp;#39;&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;k&#34;&gt;with&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;k&#34;&gt;grant&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;k&#34;&gt;option&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;;&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;其中&lt;code&gt;*.*&lt;/code&gt;第一个&lt;code&gt;*&lt;/code&gt;表示所有数据库，第二个&lt;code&gt;*&lt;/code&gt;表示所有数据表，如果不想授权全部那就把对应的&lt;code&gt;*&lt;/code&gt;写成相应数据库或者数据表；&lt;code&gt;username&lt;/code&gt;为指定的用户；&lt;code&gt;%&lt;/code&gt;为该用户登录的域名&lt;/p&gt;
&lt;h3 id=&#34;3-授权之后刷新权限&#34;&gt;3. 授权之后刷新权限&lt;/h3&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-sql&#34; data-lang=&#34;sql&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;n&#34;&gt;flush&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;k&#34;&gt;privileges&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;;&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h3 id=&#34;4-撤销授权&#34;&gt;4. 撤销授权&lt;/h3&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-sql&#34; data-lang=&#34;sql&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;o&#34;&gt;#&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;err&#34;&gt;收回权限（不包含赋权权限）&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;k&#34;&gt;revoke&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;k&#34;&gt;all&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;k&#34;&gt;privileges&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;k&#34;&gt;on&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;o&#34;&gt;*&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;*&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;k&#34;&gt;from&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;n&#34;&gt;user_name&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;;&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;k&#34;&gt;revoke&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;k&#34;&gt;all&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;k&#34;&gt;privileges&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;k&#34;&gt;on&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;n&#34;&gt;user_name&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;*&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;k&#34;&gt;from&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;n&#34;&gt;user_name&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;;&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;o&#34;&gt;#&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;err&#34;&gt;收回赋权权限&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;k&#34;&gt;revoke&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;k&#34;&gt;grant&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;k&#34;&gt;on&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;k&#34;&gt;option&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;k&#34;&gt;on&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;o&#34;&gt;*&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;*&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;k&#34;&gt;from&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;n&#34;&gt;user_name&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;;&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;o&#34;&gt;#&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;err&#34;&gt;操作完成后重新刷新权限&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;n&#34;&gt;flush&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;k&#34;&gt;privileges&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;;&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;</description>
      <content:encoded><![CDATA[<h3 id="1-创建新用户">1. 创建新用户</h3>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-sql" data-lang="sql"><span class="line"><span class="cl"><span class="k">create</span><span class="w"> </span><span class="k">user</span><span class="w"> </span><span class="s1">&#39;username&#39;</span><span class="o">@</span><span class="s1">&#39;host&#39;</span><span class="w"> </span><span class="n">identified</span><span class="w"> </span><span class="k">by</span><span class="w"> </span><span class="s1">&#39;password&#39;</span><span class="p">;</span><span class="w">
</span></span></span></code></pre></div><p>其中<code>username</code>为自定义的用户名；<code>host</code>为登录域名，<code>%</code>代表任意IP，<code>localhost</code>代表本机，或者填写指定的IP地址；<code>password</code>为密码</p>
<h3 id="2-为用户授权">2. 为用户授权</h3>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-sql" data-lang="sql"><span class="line"><span class="cl"><span class="k">grant</span><span class="w"> </span><span class="k">all</span><span class="w"> </span><span class="k">privileges</span><span class="w"> </span><span class="k">on</span><span class="w"> </span><span class="o">*</span><span class="p">.</span><span class="o">*</span><span class="w"> </span><span class="k">to</span><span class="w"> </span><span class="s1">&#39;username&#39;</span><span class="o">@</span><span class="s1">&#39;host&#39;</span><span class="w"> </span><span class="k">with</span><span class="w"> </span><span class="k">grant</span><span class="w"> </span><span class="k">option</span><span class="p">;</span><span class="w">
</span></span></span></code></pre></div><p>其中<code>*.*</code>第一个<code>*</code>表示所有数据库，第二个<code>*</code>表示所有数据表，如果不想授权全部那就把对应的<code>*</code>写成相应数据库或者数据表；<code>username</code>为指定的用户；<code>%</code>为该用户登录的域名</p>
<h3 id="3-授权之后刷新权限">3. 授权之后刷新权限</h3>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-sql" data-lang="sql"><span class="line"><span class="cl"><span class="n">flush</span><span class="w"> </span><span class="k">privileges</span><span class="p">;</span><span class="w">
</span></span></span></code></pre></div><h3 id="4-撤销授权">4. 撤销授权</h3>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-sql" data-lang="sql"><span class="line"><span class="cl"><span class="o">#</span><span class="w"> </span><span class="err">收回权限（不包含赋权权限）</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="k">revoke</span><span class="w"> </span><span class="k">all</span><span class="w"> </span><span class="k">privileges</span><span class="w"> </span><span class="k">on</span><span class="w"> </span><span class="o">*</span><span class="p">.</span><span class="o">*</span><span class="w"> </span><span class="k">from</span><span class="w"> </span><span class="n">user_name</span><span class="p">;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="k">revoke</span><span class="w"> </span><span class="k">all</span><span class="w"> </span><span class="k">privileges</span><span class="w"> </span><span class="k">on</span><span class="w"> </span><span class="n">user_name</span><span class="p">.</span><span class="o">*</span><span class="w"> </span><span class="k">from</span><span class="w"> </span><span class="n">user_name</span><span class="p">;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="o">#</span><span class="w"> </span><span class="err">收回赋权权限</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="k">revoke</span><span class="w"> </span><span class="k">grant</span><span class="w"> </span><span class="k">on</span><span class="w"> </span><span class="k">option</span><span class="w"> </span><span class="k">on</span><span class="w"> </span><span class="o">*</span><span class="p">.</span><span class="o">*</span><span class="w"> </span><span class="k">from</span><span class="w"> </span><span class="n">user_name</span><span class="p">;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="o">#</span><span class="w"> </span><span class="err">操作完成后重新刷新权限</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="n">flush</span><span class="w"> </span><span class="k">privileges</span><span class="p">;</span><span class="w">
</span></span></span></code></pre></div>]]></content:encoded>
    </item>
    <item>
      <title>MySQL 修改用户</title>
      <link>https://liushiguang.com/posts/mysql-%E4%BF%AE%E6%94%B9%E7%94%A8%E6%88%B7/</link>
      <pubDate>Thu, 05 Sep 2024 12:41:37 +0800</pubDate>
      <guid>https://liushiguang.com/posts/mysql-%E4%BF%AE%E6%94%B9%E7%94%A8%E6%88%B7/</guid>
      <description>&lt;h2 id=&#34;具体操作&#34;&gt;具体操作&lt;/h2&gt;
&lt;p&gt;在&lt;strong&gt;5.7&lt;/strong&gt;之前修改密码：（使用update修改user表）&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-sql&#34; data-lang=&#34;sql&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;n&#34;&gt;use&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;n&#34;&gt;mysql&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;;&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;k&#34;&gt;update&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;k&#34;&gt;user&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;k&#34;&gt;set&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;n&#34;&gt;authentication_string&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;=&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;password&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;s1&#34;&gt;&amp;#39;123456&amp;#39;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;k&#34;&gt;where&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;k&#34;&gt;host&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;o&#34;&gt;=&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;s1&#34;&gt;&amp;#39;localhost&amp;#39;&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;k&#34;&gt;and&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;k&#34;&gt;user&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;=&lt;/span&gt;&lt;span class=&#34;s1&#34;&gt;&amp;#39;root&amp;#39;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;;&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;在&lt;strong&gt;5.7&lt;/strong&gt;修改密码：（废除了password字段，需要使用authentication_string）&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-sql&#34; data-lang=&#34;sql&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;n&#34;&gt;use&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;n&#34;&gt;mysql&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;;&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;k&#34;&gt;update&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;k&#34;&gt;user&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;k&#34;&gt;set&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;n&#34;&gt;password&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;=&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;password&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;s1&#34;&gt;&amp;#39;123456&amp;#39;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;k&#34;&gt;where&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;k&#34;&gt;host&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;=&lt;/span&gt;&lt;span class=&#34;s1&#34;&gt;&amp;#39;localhost&amp;#39;&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;k&#34;&gt;and&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;k&#34;&gt;user&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;o&#34;&gt;=&lt;/span&gt;&lt;span class=&#34;s1&#34;&gt;&amp;#39;root&amp;#39;&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;在&lt;strong&gt;8.0&lt;/strong&gt;中已经不能使用password函数和set&amp;hellip;语句，只能使用&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-sql&#34; data-lang=&#34;sql&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;n&#34;&gt;use&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;n&#34;&gt;mysql&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;;&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;k&#34;&gt;alter&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;k&#34;&gt;user&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;s1&#34;&gt;&amp;#39;root&amp;#39;&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;@&lt;/span&gt;&lt;span class=&#34;s1&#34;&gt;&amp;#39;localhost&amp;#39;&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;n&#34;&gt;identified&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;k&#34;&gt;with&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;n&#34;&gt;mysql_native_password&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;k&#34;&gt;by&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;s1&#34;&gt;&amp;#39;123456&amp;#39;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;;&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;o&#34;&gt;##&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;err&#34;&gt;如果遇报错，先执行&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;flush&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;k&#34;&gt;privileges&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h2 id=&#34;原因&#34;&gt;原因&lt;/h2&gt;
&lt;p&gt;mysql5.7.9版本之后废弃了password字段和password()函数，且默认加密方式不采用mysql_native_password.&lt;/p&gt;
&lt;p&gt;在mysql8.0以上版本中caching_sha2_password和sha256_password认证插件比mysql_native_password插件提供的密码加密更安全，并且前者加密性能更好。由于caching_sha2_password这样优秀的安全和性能特征，让他作为mysql8.0的默认首选认证插件，而并不是mysql_native_password.&lt;/p&gt;
&lt;p&gt;所以mysql8.0默认是caching_sha2_password加密,5.7.9版本后的默认是mysql_native_password.&lt;/p&gt;</description>
      <content:encoded><![CDATA[<h2 id="具体操作">具体操作</h2>
<p>在<strong>5.7</strong>之前修改密码：（使用update修改user表）</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-sql" data-lang="sql"><span class="line"><span class="cl"><span class="n">use</span><span class="w"> </span><span class="n">mysql</span><span class="p">;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="k">update</span><span class="w"> </span><span class="k">user</span><span class="w"> </span><span class="k">set</span><span class="w"> </span><span class="n">authentication_string</span><span class="o">=</span><span class="n">password</span><span class="p">(</span><span class="s1">&#39;123456&#39;</span><span class="p">)</span><span class="w"> </span><span class="k">where</span><span class="w"> </span><span class="k">host</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="s1">&#39;localhost&#39;</span><span class="w"> </span><span class="k">and</span><span class="w"> </span><span class="k">user</span><span class="o">=</span><span class="s1">&#39;root&#39;</span><span class="p">;</span><span class="w">
</span></span></span></code></pre></div><p>在<strong>5.7</strong>修改密码：（废除了password字段，需要使用authentication_string）</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-sql" data-lang="sql"><span class="line"><span class="cl"><span class="n">use</span><span class="w"> </span><span class="n">mysql</span><span class="p">;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="k">update</span><span class="w"> </span><span class="k">user</span><span class="w"> </span><span class="k">set</span><span class="w"> </span><span class="n">password</span><span class="o">=</span><span class="n">password</span><span class="p">(</span><span class="s1">&#39;123456&#39;</span><span class="p">)</span><span class="w"> </span><span class="k">where</span><span class="w"> </span><span class="k">host</span><span class="o">=</span><span class="s1">&#39;localhost&#39;</span><span class="w"> </span><span class="k">and</span><span class="w"> </span><span class="k">user</span><span class="w"> </span><span class="o">=</span><span class="s1">&#39;root&#39;</span><span class="w">
</span></span></span></code></pre></div><p>在<strong>8.0</strong>中已经不能使用password函数和set&hellip;语句，只能使用</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-sql" data-lang="sql"><span class="line"><span class="cl"><span class="n">use</span><span class="w"> </span><span class="n">mysql</span><span class="p">;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="k">alter</span><span class="w"> </span><span class="k">user</span><span class="w"> </span><span class="s1">&#39;root&#39;</span><span class="o">@</span><span class="s1">&#39;localhost&#39;</span><span class="w"> </span><span class="n">identified</span><span class="w"> </span><span class="k">with</span><span class="w"> </span><span class="n">mysql_native_password</span><span class="w"> </span><span class="k">by</span><span class="w"> </span><span class="s1">&#39;123456&#39;</span><span class="p">;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="o">##</span><span class="w"> </span><span class="err">如果遇报错，先执行</span><span class="n">flush</span><span class="w"> </span><span class="k">privileges</span><span class="w">
</span></span></span></code></pre></div><h2 id="原因">原因</h2>
<p>mysql5.7.9版本之后废弃了password字段和password()函数，且默认加密方式不采用mysql_native_password.</p>
<p>在mysql8.0以上版本中caching_sha2_password和sha256_password认证插件比mysql_native_password插件提供的密码加密更安全，并且前者加密性能更好。由于caching_sha2_password这样优秀的安全和性能特征，让他作为mysql8.0的默认首选认证插件，而并不是mysql_native_password.</p>
<p>所以mysql8.0默认是caching_sha2_password加密,5.7.9版本后的默认是mysql_native_password.</p>
]]></content:encoded>
    </item>
  </channel>
</rss>
