<?xml version="1.0" encoding="UTF-8"?><?xml-stylesheet href="/scripts/pretty-feed-v3.xsl" type="text/xsl"?><rss version="2.0" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:h="http://www.w3.org/TR/html4/"><channel><title>Linxii&apos;s Blog</title><description>一个清澈但愚蠢的学生</description><link>https://tyuou2.github.io</link><item><title>算法记录：数据结构</title><link>https://tyuou2.github.io/blog/algorithm-5-ds</link><guid isPermaLink="true">https://tyuou2.github.io/blog/algorithm-5-ds</guid><description>记录数据结构不会的题与做题思路</description><pubDate>Sun, 24 May 2026 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;import {Collapsible} from &quot;../../../components/advanced/index.js&quot;;
import { Lc3943Code } from &apos;../../snippets.js&apos;&lt;/p&gt;
&lt;h2&gt;1.分块&lt;/h2&gt;
&lt;h3&gt;1.1总体思考&lt;/h3&gt;
&lt;h3&gt;1.2 题目记录&lt;/h3&gt;
&lt;h4&gt;1.2.1 LC 3943 递增后的数对数量 &lt;a href=&quot;https://leetcode.cn/problems/number-of-pairs-after-increment/description/&quot;&gt;链接&lt;/a&gt;&lt;/h4&gt;
&lt;p&gt;  题目要实现两种操作&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;ol&gt;
&lt;li&gt;区间加x&lt;/li&gt;
&lt;/ol&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;ol start=&quot;2&quot;&gt;
&lt;li&gt;查询区间内x的数量&lt;/li&gt;
&lt;/ol&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;  对于区间操作来说，最容易想到的是线段树，但是线段树维护区间内每个数的数量非常麻烦，并且可能复杂度也不够好。分块在这种场景下较为合适，分块的区间加操作比线段树复杂度高一些，但是查询操作的复杂度更低，更加平衡。&lt;/p&gt;
&lt;p&gt;  对于区间加操作与查询操作的时间复杂度都是&lt;strong&gt;根号&lt;/strong&gt;级别的，分块的思想就是将数组划分为若干个大小为$\sqrt{m}$的块，每个块内维护一些额外的信息来支持快速的区间加和查询操作。
&amp;#x3C;Collapsible title={&quot;题解代码&quot;}&gt;

&lt;/p&gt;</content:encoded><h:img src="https://pic.linxii.top/blog/algorithm-5-DS-0-104817305.webp"/><enclosure url="https://pic.linxii.top/blog/algorithm-5-DS-0-104817305.webp"/></item><item><title>算法记录：字符串</title><link>https://tyuou2.github.io/blog/algorithm-4-string</link><guid isPermaLink="true">https://tyuou2.github.io/blog/algorithm-4-string</guid><description>记录字符串不会的题与做题思路</description><pubDate>Sun, 17 May 2026 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;import {Collapsible} from &quot;../../../components/advanced/index.js&quot;;&lt;/p&gt;
&lt;p&gt;import { TwoHash } from &apos;../../snippets.js&apos;&lt;/p&gt;
&lt;h2&gt;1.字符串哈希&lt;/h2&gt;
&lt;h3&gt;1.1总体思考&lt;/h3&gt;
&lt;h4&gt;1.1.1 双哈希&lt;/h4&gt;
&lt;p&gt;  双哈希的思想就是使用两组不同的哈希参数（基数和模数）来计算字符串的哈希值。降低哈希冲突的概率，同时使用随机base与随机mod防止被hack。&lt;/p&gt;
&lt;p&gt;  $h$数组用于存储前缀哈希值，$p$数组用于存储基数的幂。其中hash函数是
$ h[i] = a_{1}·base^{i-1} + a_{2}·base^{i-2} + ... + a_i·base^0 $&lt;/p&gt;
&lt;p&gt;  例子的直观理解：&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-cpp&quot;&gt;arr = [3, 1, 4, 1, 5],base=10

h[1] = 3
h[2] = 3*10 + 1 = 31
h[3] = 31*10 + 4 = 314
h[4] = 314*10 + 1 = 3141
h[5] = 3141*10 + 5 = 31415

//求子串hash
hash(l, r) = h[r+1] - h[l] × base^(r-l+1)
h[4] = 3141
h[1] = 3
len = 3 (r-l+1 = 3-1+1 = 3)
base^3 = 1000

hash = 3141 - 3×1000 = 141 ✅
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&amp;#x3C;Collapsible title={&quot;随机双hash模板&quot;}&gt;

&lt;/p&gt;</content:encoded><h:img src="https://pic.linxii.top/blog/algorithm-4-String-0-116783107.webp"/><enclosure url="https://pic.linxii.top/blog/algorithm-4-String-0-116783107.webp"/></item><item><title>算法记录：树</title><link>https://tyuou2.github.io/blog/algorithm-3-tree</link><guid isPermaLink="true">https://tyuou2.github.io/blog/algorithm-3-tree</guid><description>记录“树论”不会的题与做题思路</description><pubDate>Mon, 04 May 2026 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;import {Collapsible} from &quot;../../../components/advanced/index.js&quot;;
import {  NCWeek142F } from &apos;../../snippets.js&apos;&lt;/p&gt;
&lt;h2&gt;1.DFS&lt;/h2&gt;
&lt;h3&gt;1.1总体思考&lt;/h3&gt;
&lt;h3&gt;1.2 题目记录&lt;/h3&gt;
&lt;h4&gt;1.2.1 nowCoder 周赛142F 小苯的DFS &lt;a href=&quot;https://ac.nowcoder.com/acm/contest/133790/F&quot;&gt;链接&lt;/a&gt;&lt;/h4&gt;
&lt;p&gt;  题目要求在树上进行随机DFS时，得到dfn非递减的概率是多少，概率使用逆元来表示。&lt;/p&gt;
&lt;p&gt;  这个题目虽然是DFS，但是都是思维！！！被限制的不是DFS怎么写，而是怎么想到一些处理方法。直接看代码以及写的注释吧，也是看苯环gg的题解视频写的。&lt;/p&gt;
&lt;p&gt;&amp;#x3C;Collapsible title={&quot;题解代码&quot;}&gt;

&lt;/p&gt;</content:encoded><h:img src="https://pic.linxii.top/blog/algorithm-3-Tree-0116782825.webp"/><enclosure url="https://pic.linxii.top/blog/algorithm-3-Tree-0116782825.webp"/></item><item><title>算法记录：图论</title><link>https://tyuou2.github.io/blog/algorithm-2-graph</link><guid isPermaLink="true">https://tyuou2.github.io/blog/algorithm-2-graph</guid><description>记录图论不会的题与做题思路</description><pubDate>Fri, 24 Apr 2026 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;import {Collapsible} from &quot;../../../components/advanced/index.js&quot;;&lt;/p&gt;
&lt;h2&gt;1.生成树&lt;/h2&gt;
&lt;h3&gt;1.1总体思考&lt;/h3&gt;
&lt;h3&gt;1.2 题目记录&lt;/h3&gt;
&lt;h4&gt;1.2.1 nowCoder 周赛140G 小红的生成树构造 &lt;a href=&quot;https://ac.nowcoder.com/acm/contest/132940/G&quot;&gt;链接&lt;/a&gt;&lt;/h4&gt;
&lt;p&gt;  整场周赛都是赛后补做的，这个没写出来。然后去题解讨论看了&lt;a href=&quot;https://www.nowcoder.com/discuss/875556533450399744?sourceSSR=users&quot;&gt;文字题解&lt;/a&gt;，理解思路后，感觉不是很难，然后开始写代码，but在合并过程中这个&quot;ABCD&quot;的类别统计碰到了麻烦，想着用set来做统计，结果越写越麻烦，遂去看&lt;a href=&quot;%E3%80%90%E7%89%9B%E5%AE%A2%E5%91%A8%E8%B5%9B140%E9%A2%98%E7%9B%AE%E8%AE%B2%E8%A7%A3%E3%80%91https://www.bilibili.com/video/BV1fHdsB6Euw?vd_source=7cf9737027fcb723c75b8962d21a5bf9&quot;&gt;官方视频题解&lt;/a&gt;。&lt;/p&gt;
&lt;p&gt;  然后在这个统计每个连通块的类别的时候，苯环gg用了mask来进行统计，使用数字的二进制位来表示每个类别是否存在，这样就可以通过位运算来快速地统计和合并类别，非常巧妙。&lt;/p&gt;
&lt;p&gt;  这个题目学到了二进制mask统计类别，总感觉似曾相识，但是就是自己做的时候想不到！！！&lt;/p&gt;
&lt;p&gt;&amp;#x3C;Collapsible title={&quot;题解代码&quot;}&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-cpp&quot;&gt;struct DSU
{
    vector&amp;#x3C;int&gt; f, siz, mask;
    DSU(int n)
    {
        init(n);
    }
    void init(int n)
    {
        f.resize(n);
        iota(f.begin(), f.end(), 0);
        siz.assign(n, 1);
        mask.assign(n, 0);
    }

    int find(int x)
    {
        if (x == f[x])
        {
            return x;
        }

        f[x] = find(f[x]);
        return f[x];
    }

    bool same(int x, int y)
    {
        return find(x) == find(y);
    }

    bool merge(int x, int y)
    {
        x = find(x);
        y = find(y);
        if (x == y)
        {
            return false;
        }

        siz[x] += siz[y];
        f[y] = x;
        mask[x] |= mask[y];
        return true;
    }

    int size(int x)
    {
        return siz[find(x)];
    }
};
void solve()
{
    int n, m;
    string s;
    cin &gt;&gt; n &gt;&gt; m &gt;&gt; s;
    DSU dsu(n);
    for (int i = 0; i &amp;#x3C; n; i++)
    {
        dsu.mask[i] = (1 &amp;#x3C;&amp;#x3C; (s[i] - &apos;A&apos;));
    }
    auto check = [&amp;#x26;](char a, char b)
    {
        if (a &gt; b)
        {
            swap(a, b);
        }

        return (abs(a - b) &amp;#x3C;= 1) &amp;#x26;&amp;#x26; !(a == &apos;B&apos; &amp;#x26;&amp;#x26; b == &apos;C&apos;);
    };

    vector&amp;#x3C;pii&gt; ans, ext;
    for (int i = 0; i &amp;#x3C; m; i++)
    {
        int u, v;
        cin &gt;&gt; u &gt;&gt; v;
        u--, v--;
        if (check(s[u], s[v]))
        {
            if (!dsu.same(u, v))
            {
                dsu.merge(u, v);
                ans.push_back({u, v});
            }
        }
        else
        {
            ext.push_back({u, v});
        }
    }

    for (int i = 0; i &amp;#x3C; n; i++)
    {
        if (dsu.find(i) != i)
        {
            continue;
        }
        if (__builtin_popcount(dsu.mask[i]) != 2)
        {
            cout &amp;#x3C;&amp;#x3C; &quot;No&quot; &amp;#x3C;&amp;#x3C; endl;
            return;
        }
    }

    for (auto [u, v] : ext)
    {
        if (!dsu.same(u, v))
        {
            dsu.merge(u, v);
            ans.push_back({u, v});
        }
    }

    if (dsu.size(0) != n)
    {
        cout &amp;#x3C;&amp;#x3C; &quot;No&quot; &amp;#x3C;&amp;#x3C; endl;
        return;
    }
    else
    {
        cout &amp;#x3C;&amp;#x3C; &quot;Yes&quot; &amp;#x3C;&amp;#x3C; endl;

        for (auto [u, v] : ans)
        {
            cout &amp;#x3C;&amp;#x3C; u + 1 &amp;#x3C;&amp;#x3C; &quot; &quot; &amp;#x3C;&amp;#x3C; v + 1 &amp;#x3C;&amp;#x3C; endl;
        }
    }
}
int main()
{
    ios::sync_with_stdio(false);
    cin.tie(nullptr);
    cout.tie(nullptr);
    mainInit();
    i64 t = 1;
    // cin &gt;&gt; t;
    while (t--)
    {
        solve();
    }
    return 0;
}

&lt;/code&gt;&lt;/pre&gt;</content:encoded><h:img src="https://pic.linxii.top/blog/algorithm-2-Graph-0-140220427.webp"/><enclosure url="https://pic.linxii.top/blog/algorithm-2-Graph-0-140220427.webp"/></item><item><title>VLM 基础</title><link>https://tyuou2.github.io/blog/vlm-base</link><guid isPermaLink="true">https://tyuou2.github.io/blog/vlm-base</guid><description>视觉语言模型近年来主要贡献论文</description><pubDate>Fri, 24 Apr 2026 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;import { ArxivRating, RatingCriteria } from &apos;@/components/advanced&apos;&lt;/p&gt;
&lt;h2&gt;1.CLIP&lt;/h2&gt;
&lt;p&gt;&amp;#x3C;ArxivRating id={&apos;2103.00020&apos;} tldr=&apos;CLIP通过对比学习将图像和文本映射到同一特征空间，使得模型能够理解和关联视觉和语言信息&apos; rank={5}&gt;
&lt;/p&gt;
&lt;h3&gt;1.1 CLIP架构&lt;/h3&gt;
&lt;p&gt;&lt;img src=&quot;https://pic.linxii.top/blog/vlm-base-1-ar.webp&quot; alt=&quot;CLIP架构&quot;&gt;
  图中左侧部分展示CLIP的架构，CLIP由两个独立的编码器组成：一个用于处理图像输入，另一个用于处理文本输入。论文中提到对于图像编码器，CLIP使用了ResNet-50和Vision Transformer (ViT)两种架构进行实验，而文本编码器则使用了Transformer架构。这两个编码器将图像和文本分别映射到同一特征空间中，使得它们可以进行&lt;strong&gt;对比学习&lt;/strong&gt;，主要目标就是让相关的图像和文本在特征空间中更接近，而不相关的图像和文本则更远离，&lt;/p&gt;
&lt;h3&gt;1.2损失函数&lt;/h3&gt;
&lt;p&gt;  CLIP使用&lt;strong&gt;对比损失函数（Contrastive Loss）&lt;/strong&gt;，具体来说是&lt;strong&gt;InfoNCE损失&lt;/strong&gt;，来训练模型。&lt;/p&gt;
&lt;p&gt;  论文中从行和列两个方向定义了损失函数，行方向的损失函数（$\mathcal{L}&lt;em&gt;{\text{image-to-text}}$）鼓励正确的图像-文本对在特征空间中更接近，而列方向的损失函数（$\mathcal{L}&lt;/em&gt;{\text{text-to-image}}$）则鼓励正确的文本-图像对在特征空间中更接近。最终的总损失函数是这两部分损失的和：
$$
\begin{align}
\mathcal{L} &amp;#x26;= \mathcal{L}&lt;em&gt;{\text{image-to-text}} + \mathcal{L}&lt;/em&gt;{\text{text-to-image}} \[10pt]
\mathcal{L}&lt;em&gt;{\text{i2t}} &amp;#x26;= -\mathbb{E}\left[ \log \frac{e^{\mathrm{sim}(I_i,T_i)/\tau}}{\sum_j e^{\mathrm{sim}(I_i,T_j)/\tau}} \right] \[18pt]
\mathcal{L}&lt;/em&gt;{\text{t2i}} &amp;#x26;= -\mathbb{E}\left[ \log \frac{e^{\mathrm{sim}(T_i,I_i)/\tau}}{\sum_j e^{\mathrm{sim}(T_i,I_j)/\tau}} \right]
\end{align}
$$&lt;/p&gt;
&lt;p&gt;对比损失函数的代码实现：&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-python&quot;&gt;import torch
import torch.nn.functional as F

def clip_contrastive_loss(image_features, text_features, temperature=0.07):
    &quot;&quot;&quot;
    实现 CLIP 原论文的对称 InfoNCE 损失
    :param image_features: 图像特征 [batch_size, dim]
    :param text_features: 文本特征 [batch_size, dim]
    :param temperature: 温度系数 τ
    :return: 总损失 L = (L_i2t + L_t2i) / 2
    &quot;&quot;&quot;
    batch_size = image_features.shape[0]

    # 1. 计算相似度矩阵 sim(I_i, T_j) [N, N]
    sim_matrix = torch.matmul(image_features, text_features.T) / temperature

    # 2. 图像→文本损失 (image-to-text)
    # 对每一张图，在所有文本里找匹配的正样本（对角线）
    labels = torch.arange(batch_size, device=image_features.device)
    loss_i2t = F.cross_entropy(sim_matrix, labels)

    # 3. 文本→图像损失 (text-to-image)
    # 对每一段文本，在所有图像里找匹配的正样本（转置矩阵后对角线）
    loss_t2i = F.cross_entropy(sim_matrix.T, labels)

    # 4. 总损失（论文：取平均）
    loss = (loss_i2t + loss_t2i) / 2
    return loss
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;  &lt;strong&gt;补充&lt;/strong&gt;
F.cross_entropy具体实现&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-python&quot;&gt;def cross_entropy(input, target):
    &quot;&quot;&quot;
    计算交叉熵损失
    :param input: 预测的相似度矩阵 [N, N]
    :param target: 真实标签 [N]，每个元素是正确匹配
    :return: 平均交叉熵损失
    &quot;&quot;&quot;
    # 1. 计算 log-softmax
    log_probs = F.log_softmax(input, dim=1)
    # 2. 选择正确匹配的 log-probabilities
    selected_log_probs = log_probs[torch.arange(input.size(0)), target]
    # 3. 计算平均损失
    loss = -selected_log_probs.mean()
    return loss
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;2.BLIP&lt;/h2&gt;
&lt;p&gt;&amp;#x3C;ArxivRating id={&apos;2201.12086&apos;} tldr=&apos;BLIP 用一套模型，同时带文本编码器 + 解码器，既能做图文匹配检索，又能直接看图生成文字，打通理解和生成。靠三种跨模态损失联合训练，再加自动清洗劣质图文数据，让图文特征对齐更准，实际效果全面变强。&apos; rank={5}&gt;
&lt;/p&gt;
&lt;h3&gt;2.1 BLIP架构&lt;/h3&gt;
&lt;p&gt;&lt;img src=&quot;https://pic.linxii.top/blog/vlm-base-2-blip.webp&quot; alt=&quot;BLIP架构&quot;&gt;&lt;/p&gt;
&lt;p&gt;  在BLIP出现之前，视觉语言模型通常不能兼顾&lt;strong&gt;理解&lt;/strong&gt;和&lt;strong&gt;生成&lt;/strong&gt;两大功能。CLIP在图文匹配上表现很好（理解），但是不能够直接生成对于图像的文本描述（生成），因此BLIP的第一个核心目标就是用一个模型，同时搞定理解与生成。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;视觉编码器&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;主干:ViT&lt;/li&gt;
&lt;li&gt;输入：一张图片&lt;/li&gt;
&lt;li&gt;输出：图像特征（分为局部图像patch特征和全局图像表征img_feat（cls token））&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;文本编码器&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;主干:Transformer编码器&lt;/li&gt;
&lt;li&gt;输入：一段文本&lt;/li&gt;
&lt;li&gt;输出：全局文本特征text_feat&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;给理解任务使用的，ITC、ITM&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;文本解码器&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;主干:带交叉注意力的Transformer解码器&lt;/li&gt;
&lt;li&gt;输入：图像特征（局部patch特征和全局img_feat）和已生成的文字&lt;/li&gt;
&lt;li&gt;输出：下一个token&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;给生成任务使用的，LM&lt;/p&gt;
&lt;h3&gt;2.2 三大预训练任务&lt;/h3&gt;
&lt;p&gt;  BLIP设计了三大预训练任务，分别是&lt;strong&gt;图文对比（ITC）&lt;/strong&gt;、&lt;strong&gt;图文匹配（ITM）&lt;strong&gt;和&lt;/strong&gt;语言建模（LM）&lt;/strong&gt;，&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;图文对比（ITC）&lt;/strong&gt;：&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;  这部分与CLIP基本完全相同，只是更新逻辑不是单纯由ITC损失来更新，而是由ITC、ITM和LM三种损失共同更新。BLIP的ITC损失也是对比损失，鼓励正确的图文对在特征空间中更接近，而不相关的图文对则更远离。&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;图文匹配（ITM）&lt;/strong&gt;：&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;  ITM是一个二分类任务，输入是一对图文（图像和文本），模型需要判断它们是否匹配。正样本是正确匹配的图文对，负样本是随机配对的图文对。&lt;/p&gt;
&lt;p&gt;  是用视觉局部特征+文本token特征，同时使用跨模态交叉注意力做深度融合，最后通过一个MLP分类头输出2维的logits。&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;语言建模（LM）&lt;/strong&gt;：&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;  LM的目的是生成人物，给一张图，然后自动生成一段描述这张图的文本。BLIP使用了一个带交叉注意力的Transformer解码器来实现这个功能，输入是图像特征（局部patch特征和全局img_feat）和已生成的文字，输出是下一个token。&lt;/p&gt;
&lt;h3&gt;2.3 CapFilter&lt;/h3&gt;
&lt;p&gt;&lt;img src=&quot;https://pic.linxii.top/blog/vlm-base-3-CapFilter.webp&quot; alt=&quot;CapFilter架构&quot;&gt;
  互联网上的图文对数据质量参差不齐，&lt;strong&gt;有很多脏数据&lt;/strong&gt;，直接训练会导致模型学错。因此BLIP先用一个弱模型对网络爬取的图生成文本描述，然后现在每张图有两个文本描述：一个是原始的网络爬取的文本描述，一个是弱模型生成的文本描述。然后利用模型自身的多模态打分的能力（ITC与ITM），对上面说的这两个文本进行打分，选分数更高的文本作为最终的训练文本。这个过程就是CapFilter，BLIP通过这个自动清洗数据的机制，提升了训练数据的质量，从而提升了模型的性能。&lt;/p&gt;
&lt;h2&gt;3. BLIP2&lt;/h2&gt;
&lt;p&gt;&amp;#x3C;ArxivRating id={&apos;2301.12597&apos;} tldr=&apos;BLIP2 通过引入一个轻量级的Q-Former模块，使用预训练的语言模型作为解码器，并设计了新的预训练任务，实现了更高效的视觉语言理解和生成能力。&apos; rank={5}&gt;
&lt;/p&gt;
&lt;h3&gt;3.1 BLIP2架构&lt;/h3&gt;
&lt;p&gt;&lt;img src=&quot;https://pic.linxii.top/blog/vlm-base-4-blip2-ar.webp&quot; alt=&quot;BLIP2架构&quot;&gt;
  BLIP2的核心创新是引入了一个轻量级的&lt;strong&gt;Q-Former模块&lt;/strong&gt;，它位于视觉编码器和语言模型之间，负责将视觉特征转换为适合语言模型处理的形式。BLIP2使用预训练的语言模型作为解码器，这样可以利用大规模预训练语言模型的强大生成能力。&lt;strong&gt;Q-Former就是模态转换器&lt;/strong&gt;。&lt;/p&gt;
&lt;h3&gt;3.2 第一阶段——训练Q-Former&lt;/h3&gt;
&lt;p&gt;&lt;img src=&quot;https://pic.linxii.top/blog/vlm-base-4-blip2.webp&quot; alt=&quot;Q-Former&quot;&gt;
  在第一阶段，冻结视觉编码器，只训练Q-Former。Q-Former最下层有一个可学习的查询向量Learned Queries,这是Q-Former的核心。然后Q-Former是一个双Transformer设计，左侧带有Cross Attention，Learned Queries通过这个与图像特征进行交互，同时可以通过共享的自注意力层与右侧文本特征进行交互，右侧是一个标准的Transformer，接收输入文本。&lt;/p&gt;
&lt;p&gt;  训练时，同时优化ITC、ITM、ITG三个损失，倒逼可学习 Queries，提取出和文本语义高度相关的视觉特征。图中右侧部分展示的是在训练时，BLIP2使用了不同的掩码策略&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;双向自注意力掩码&lt;/strong&gt;（对应ITM图文匹配）&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;所有的Q、T之间全部相互可见，做完整的图文语义匹配判断&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;多模态因果掩码&lt;/strong&gt;（对应ITG图文生成）&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Q之间全程双向可见，文本T只能看向之前的位置，看不到未来的token(就是为了防止文本生成时作弊，原始transformer解码器的掩码就是这样的)&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;单模态掩码&lt;/strong&gt;（对应ITC图文对比）&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Q与T完全隔离，互不可见，只允许模态内部做注意力交互，让图文各自独立地学习自己的特征表示，从而进行图文对比学习。&lt;/p&gt;
&lt;h3&gt;3.3 第二阶段——视觉→语言生成对齐&lt;/h3&gt;
&lt;p&gt;&lt;img src=&quot;https://pic.linxii.top/blog/vlm-base-5-blip2-2.webp&quot; alt=&quot;视觉语言生成对齐&quot;&gt;
  在第二阶段中，视觉编码器与LLM都冻结，仅训练：Q-Former + 末尾全连接投影层，其中全连接投影层是为了把 Q-Former 输出的 Query 特征维度，映射匹配成 LLM 能接收的词嵌入维度，总的就是让 Q-Former 输出的视觉前缀特征，完美适配冻结 LLM 的语言空间，让 LLM 看懂图像、直接做图文生成。&lt;/p&gt;
&lt;p&gt;  图中是两种LLM大模型架构，当前主流的大模型均使用Decoder架构（如GPT系列）。BLIP2的设计使得它能够兼容不同架构的LLM。ps：当前的通用大模型几乎没有Encoder-Decoder架构，然后当前的通用大模型大多都是Decoder+MoE。&lt;/p&gt;
&lt;h2&gt;4.LLaVA&lt;/h2&gt;
&lt;p&gt;&amp;#x3C;ArxivRating id={&apos;2304.08485&apos;} tldr=&apos;LLaVA 通过引入一个轻量级的视觉前缀模块，将图像特征转换为语言模型可理解的形式，实现了更高效的视觉语言理解和生成能力。&apos; rank={5}&gt;
&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;LLaVA堪称大道至简&lt;/strong&gt;&lt;/p&gt;</content:encoded><h:img src="https://pic.linxii.top/blog/vlm-base-0-115654853.webp"/><enclosure url="https://pic.linxii.top/blog/vlm-base-0-115654853.webp"/></item><item><title>算法记录：动态规划</title><link>https://tyuou2.github.io/blog/algorithm-1-dp</link><guid isPermaLink="true">https://tyuou2.github.io/blog/algorithm-1-dp</guid><description>记录DP不会的题与做题思路</description><pubDate>Mon, 20 Apr 2026 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;import {Collapsible} from &quot;../../../components/advanced/index.js&quot;;&lt;/p&gt;
&lt;p&gt;import {  Lc3906Code,Lc3920Code } from &apos;../../snippets.js&apos;&lt;/p&gt;
&lt;h2&gt;1.数位DP&lt;/h2&gt;
&lt;h3&gt;1.1总体思考&lt;/h3&gt;
&lt;h3&gt;1.2 题目记录&lt;/h3&gt;
&lt;h4&gt;1.2.1 LC 3906 统计网格路径中好整数的数目  &lt;a href=&quot;https://leetcode.cn/problems/count-good-integers-on-a-grid-path/&quot;&gt;链接&lt;/a&gt;&lt;/h4&gt;
&lt;p&gt;  对于这个题目，题目要求找[l,r]范围内符合要求的数字个数，而且数据范围很大，[1,9e15],遍历是肯定不可能的，所以需要用数位DP来做。数位DP的核心思想是将问题转化为对数字的每一位进行状态转移，从而避免了暴力枚举所有数字。&lt;/p&gt;
&lt;p&gt;  同时，对于这种求[l,r]范围内的的答案来说，我们可以把题目转换成求[0,r]范围内的答案减去[0,l-1]范围内的答案。这样就可以通过数位DP来分别计算这两个范围内的符合要求的数字个数，最后得到最终结果。这样在一定的情况下更方便处理。&lt;/p&gt;
&lt;p&gt;  这题目给的两个限制条件是：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;（1）题目给了一个字符串directions，这个字符串就确定了数字的哪些位置是有限制的，对于有限制的位置，要确保当前的限制的位置大于等于上一个被限制的位置的数字，因此要记录上一个被限制的数字，即prev。&lt;/li&gt;
&lt;li&gt;（2）同时，对于每一位数字，我们还要记录当前是否已经超过了题目所给的上界r的限制，即isHigh，这样就可以在状态转移时正确地处理边界情况。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;  然后就是DP，DP就要知道DP数组是是什么样子的，各个维度的含义，以及状态转移方程是怎样的。&lt;/p&gt;
&lt;p&gt;  使用记忆化搜索（Python可以直接擦车（@Cache）），我们可以定义一个递归函数dfs(i, isHigh, prev)，其中i表示当前处理的数字位，isHigh表示当前是否已经超过了上界r的限制，prev表示上一个被限制的位置的数字。这个函数的返回值就是从当前状态出发，满足条件的数字个数。然后使用一个记忆化数组memo来存储已经计算过的状态，以避免重复计算。&lt;/p&gt;
&lt;p&gt;  然后我们看转移方程，首先要确定当前位的上界，如果isHigh为true，那么当前位的上界就是s[i]（s是r转换成字符串后的结果），否则当前位的上界就是9。然后对于每个可能的数字d（从0到当前位的上界），我们需要判断是否满足限制条件，如果当前位没有被限制，那么直接递归调用dfs(i + 1, isHigh &amp;#x26;&amp;#x26; (d == upper), prev)；如果当前位被限制，那么需要判断d是否大于等于prev，如果满足条件，则递归调用dfs(i + 1, isHigh &amp;#x26;&amp;#x26; (d == upper), d)。最后将所有满足条件的数字个数累加起来，并存储在memo数组中。&lt;/p&gt;
&lt;p&gt;&amp;#x3C;Collapsible title={&quot;题解代码&quot;}&gt;

&lt;/p&gt;
&lt;h2&gt;2.最长递增子序列（LIS） &lt;a href=&quot;https://leetcode.cn/problems/longest-increasing-subsequence/solutions/2147040/jiao-ni-yi-bu-bu-si-kao-dpfu-o1-kong-jia-4zma/&quot;&gt;链接&lt;/a&gt;&lt;/h2&gt;
&lt;h3&gt;2.1总体思考&lt;/h3&gt;
&lt;p&gt;  最长递增子序列就是一个数组arr中满足下标$i&amp;#x3C;j$ 并且 $nums[i]&amp;#x3C;nums[j]$的子序列的最大长度。&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;只求最大长度&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;  DP方法，其中$f[i]$表示以$nums[i]$结尾的最长递增子序列（LIS）的长度。时间复杂度$O(n^{2})$&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-cpp&quot;&gt;class Solution {
public:
    int lengthOfLIS(vector&amp;#x3C;int&gt;&amp;#x26; nums) {
        int n = nums.size();
        vector&amp;#x3C;int&gt; f(n);
        for (int i = 0; i &amp;#x3C; n; i++) {
            f[i] = 0;
            for (int j = 0; j &amp;#x3C; i; j++) { //从前面找
                if (nums[j] &amp;#x3C; nums[i]) {
                    f[i] = max(f[i], f[j]);
                }
            }
            f[i]++;//以nums[i]结尾，所以要+1
        }
        return ranges::max(f);
    }
};
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;  当我们只求最大长度时，存在$O(nlogn)$的算法。这里$g$数组维护的是到遍历的位置为止，当长度为$g.size()$时，最后一个数字最小是多少。在遍历完成之后，$g[i]$就是整个数组里面长度为$i+1$的递增子序列的最后一个数字的最小值。&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-cpp&quot;&gt;class Solution {
public:
    int lengthOfLIS(vector&amp;#x3C;int&gt;&amp;#x26; nums) {
        vector&amp;#x3C;int&gt; g;
        for (int x : nums) {
            auto it = ranges::lower_bound(g, x);//求的是严格递增的，然后这里就是使用&gt;=，如果求的是非递减，这里就是&gt;了
            if (it == g.end()) {
                g.push_back(x); // &gt;=x 的 g[j] 不存在
            } else {
                *it = x;
            }
        }
        return g.size();
    }
};
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;2.2 题目记录&lt;/h3&gt;
&lt;h4&gt;2.2.1 LC 3920 删除元素后最大固定点数目 &lt;a href=&quot;https://leetcode.cn/problems/maximize-fixed-points-after-deletions/description/&quot;&gt;链接&lt;/a&gt;&lt;/h4&gt;
&lt;p&gt;  题目要求在一个数组中删除任意个元素，使得剩下的元素中满足$nums[i] = i$的元素个数最大。那对于选的两个元素下标分别是$i$和$j$，就要保证：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;$i &amp;#x3C; j$，因为$i$和$j$都是选的元素的下标，直接假定$i &amp;#x3C; j$&lt;/li&gt;
&lt;li&gt;$j$前面删除的次数要大于等于$i$前面删除的次数，即$i-nums_i&amp;#x3C;=j-nums_j$&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;  然后就可以得到二元组$(nums[i],i-nums[i])$，然后这就是二维偏序问题。根据刚刚的两点，也就是这个二元组的第一个元素要递增，第二个元素要单调不降。把按照第一维元素排序后，第一维度的元素就满足了递增的要求了，然后第二维度的元素就要满足单调不降的要求了。然后这个问题就转化成了求第二维度元素的最长非递减子序列的长度了。&lt;/p&gt;
&lt;p&gt;  这里最长非递减子序列的算法和最长递增子序列的算法是一样的，对于$O(nlong)$的算法中唯一的区别就是在求下标的时候，最长递增子序列是求的是lower_bound，而最长非递减子序列是求的是upper_bound。&lt;/p&gt;
&lt;p&gt;&amp;#x3C;Collapsible title={&quot;题解代码&quot;}&gt;

&lt;/p&gt;
&lt;h2&gt;3.最长公共子序列（LCS)&lt;a href=&quot;https://leetcode.cn/problems/longest-common-subsequence/solutions/2133188/jiao-ni-yi-bu-bu-si-kao-dong-tai-gui-hua-lbz5/&quot;&gt;链接&lt;/a&gt;&lt;/h2&gt;
&lt;h3&gt;3.1 总体思考&lt;/h3&gt;
&lt;p&gt;  最长公共子序列的朴素时间复杂度就是$O(n^2)$,然后$f[i][j]$代表的就是到$s[i]$和$t[j]$为止最长公共子序列的长度。&lt;/p&gt;
&lt;p&gt;  这里可以从网格图DP的角度去理解，更好理解一些。&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-cpp&quot;&gt;class Solution {
public:
    int longestCommonSubsequence(string s, string t) {
        int n = s.size(), m = t.size();
        vector f(n + 1, vector&amp;#x3C;int&gt;(m + 1));
        for (int i = 0; i &amp;#x3C; n; i++) {
            for (int j = 0; j &amp;#x3C; m; j++) {
                f[i + 1][j + 1] = s[i] == t[j] ? f[i][j] + 1 :max(f[i][j + 1], f[i + 1][j]);
            }
        }
        return f[n][m];
    }
};
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;3.2 题目记录&lt;/h3&gt;</content:encoded><h:img src="https://pic.linxii.top/blog/algorithm-1-DP-0-116544406.webp"/><enclosure url="https://pic.linxii.top/blog/algorithm-1-DP-0-116544406.webp"/></item><item><title>四月记事</title><link>https://tyuou2.github.io/blog/monthjournal-26-4</link><guid isPermaLink="true">https://tyuou2.github.io/blog/monthjournal-26-4</guid><description>四月记事</description><pubDate>Wed, 01 Apr 2026 00:00:00 GMT</pubDate><content:encoded>&lt;h2&gt;四月记事&lt;/h2&gt;
&lt;p&gt;  beginning&lt;/p&gt;</content:encoded><h:img src="https://pic.linxii.top/blog/MonthJournal-26-4-0-115649287_p0.webp"/><enclosure url="https://pic.linxii.top/blog/MonthJournal-26-4-0-115649287_p0.webp"/></item><item><title>魔法世界的小蜜蜂</title><link>https://tyuou2.github.io/blog/story-a-1</link><guid isPermaLink="true">https://tyuou2.github.io/blog/story-a-1</guid><description>随笔</description><pubDate>Tue, 31 Mar 2026 00:00:00 GMT</pubDate><content:encoded>&lt;h2&gt;沉默是金&lt;/h2&gt;</content:encoded><h:img src="https://pic.linxii.top/blog/novel-A-1-115656849.webp"/><enclosure url="https://pic.linxii.top/blog/novel-A-1-115656849.webp"/></item><item><title>NLP课程学习</title><link>https://tyuou2.github.io/blog/nlp-course</link><guid isPermaLink="true">https://tyuou2.github.io/blog/nlp-course</guid><description>学期内课程NLP学习记录</description><pubDate>Tue, 24 Mar 2026 00:00:00 GMT</pubDate><content:encoded>&lt;h2&gt;1.隐马尔可夫模型&lt;/h2&gt;
&lt;p&gt;  隐马尔可夫模型主要解决三类问题：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;评估问题&lt;/strong&gt;：给定模型参数和观察序列，计算该观察序列的概率。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;解码/预测问题&lt;/strong&gt;：给定模型参数和观察序列，找到最可能的状态序列。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;学习问题&lt;/strong&gt;：给定观察序列，估计模型参数。&lt;/li&gt;
&lt;/ol&gt;
&lt;h3&gt;1.1前向算法&lt;/h3&gt;
&lt;p&gt;  用来解决评估问题，计算给定模型参数和观察序列的概率。联合概率可以表示为：
$$
p(x,y)=p(y)p(x|y) = p(y_{1})\prod_{t=2}^{T} p(y_t|y_{t-1})\prod_{t=1}^{T}p(x_t|y_t)
$$
  其中，$p(y_{1})$是初始状态的概率，$\prod_{t=2}^{T} p(y_t|y_{t-1})$是状态转移概率的乘积，$\prod_{t=1}^{T}p(x_t|y_t)$是观察概率的乘积。&lt;/p&gt;
&lt;p&gt;  就中文分词任务而言，$y_{t}$代表BMES标签中的一个标签，$x_{t}$代表中文的一个字。&lt;/p&gt;
&lt;h3&gt;1.2 维特比算法&lt;/h3&gt;
&lt;p&gt;  用来解决解码/预测问题，找到给定模型参数和观察序列的最可能的状态序列。维特比算法通过动态规划的方法，计算每个时间步的最优路径概率，并记录路径以便回溯。&lt;/p&gt;
&lt;p&gt;  对于[湿度为1、湿度为2、湿度为3]、[晴天、雨天、多云]的例子，维特比算法会计算每个时间步的最优路径概率，并最终找到最可能的天气序列。&lt;/p&gt;</content:encoded><h:img src="https://pic.linxii.top/blog/nlp-course-0-97161689.webp"/><enclosure url="https://pic.linxii.top/blog/nlp-course-0-97161689.webp"/></item><item><title>过年ing</title><link>https://tyuou2.github.io/blog/daily-2-newyear</link><guid isPermaLink="true">https://tyuou2.github.io/blog/daily-2-newyear</guid><description>过年了！！！</description><pubDate>Mon, 16 Feb 2026 00:00:00 GMT</pubDate><content:encoded>&lt;h2&gt;过年ing&lt;/h2&gt;
&lt;p&gt;  2025年除夕了，晚上看到了许多许多的烟花，而且天上还有很多星星，感觉在学校好久都看不大到星星。&lt;/p&gt;
&lt;p&gt;  过年在家可以吃更多好吃的了，吃总是能够让人开心的，哈哈哈哈！！！！放假之后在家里玩的也很开心，与发小们一起打够级等等。&lt;/p&gt;
&lt;p&gt;  期待新的一年吧，期待新的开始，期待新的挑战，期待新的成长，期待新的收获！！！&lt;/p&gt;</content:encoded><h:img src="https://pic.linxii.top/blog/daily-2-newYear-0-90436399.webp"/><enclosure url="https://pic.linxii.top/blog/daily-2-newYear-0-90436399.webp"/></item><item><title>Paper Reading 1 :transformer In CV</title><link>https://tyuou2.github.io/blog/paper-reading-2-transformerincv</link><guid isPermaLink="true">https://tyuou2.github.io/blog/paper-reading-2-transformerincv</guid><pubDate>Sat, 31 Jan 2026 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;import { ArxivRating, RatingCriteria } from &apos;@/components/advanced&apos;&lt;/p&gt;
&lt;h2&gt;1.VIT&lt;/h2&gt;
&lt;p&gt;&amp;#x3C;ArxivRating id={&apos;2010.11929&apos;} tldr=&apos;ViT将图像划分为固定大小的patches，把每个patch当成token来处理，类似于NLP中的词嵌入。ViT使用Transformer架构，仅使用transformer的编码器，使用多头自注意力机制&apos; rank={5}&gt;
&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://pic.linxii.top/blog/base-learning-2-CV-vit.webp&quot; alt=&quot;vit架构&quot;&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;vit的基本流程：&lt;/strong&gt;&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;图像被划分为16x16的patches，每个patch被展平并映射到一个固定维度的向量空间，即每个patch对应一个patch embedding。（这一映射过程通过可训练的线性投影（trainable linear projection）实现），然后加上一个分类令牌(class token)，用于最终的分类任务。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;然后进行位置编码(Position Encoding)，vit中的位置编码是通过可学习的位置嵌入实现的。先初始化一个与patch数量相同的可学习位置嵌入矩阵。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;位置编码被添加到patch embeddings中，以保留空间信息。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;这些patch embeddings被输入到标准的Transformer编码器中进行处理。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;最终的分类是通过在Transformer输出上添加一个MLP分类头来实现的。&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;&lt;strong&gt;ViT 输入输出尺寸（ViT-Base/16）&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;原始图像（224×224×3）→ Patches（196×768）→ Patch Embeddings（196×768）→ 加分类令牌（197×768）→ 加位置嵌入（197×768）→ 编码器输出（197×768）→ 全局表示（1×768）→ 分类结果（1×1000）&lt;/p&gt;
&lt;h2&gt;2.Swin Transformer&lt;/h2&gt;
&lt;p&gt;&amp;#x3C;ArxivRating id={&apos;2103.14030&apos;} tldr=&apos;Swin Transformer 引入了层次化的结构和滑动窗口机制，有效地捕捉了图像的局部和全局特征，提升了计算效率和性能，广泛应用于各种计算机视觉任务。&apos; rank={5}&gt;
&lt;/p&gt;</content:encoded><h:img src="https://pic.linxii.top/blog/paper-reading-2-transformerInCV-0-138941727.webp"/><enclosure url="https://pic.linxii.top/blog/paper-reading-2-transformerInCV-0-138941727.webp"/></item><item><title>ML课程内容复习笔记基础</title><link>https://tyuou2.github.io/blog/base-learning-5-ml1</link><guid isPermaLink="true">https://tyuou2.github.io/blog/base-learning-5-ml1</guid><description>稍微复习一下机器学习的基础内容</description><pubDate>Thu, 15 Jan 2026 00:00:00 GMT</pubDate><content:encoded>&lt;h2&gt;1. 一些基本概念&lt;/h2&gt;
&lt;h3&gt;1.1 PAC&lt;/h3&gt;
&lt;p&gt;  PAC（Probably Approximately Correct）概率近似正确: $$P(\vert f(x)-y \vert \le \epsilon )\ge 1-\delta $$&lt;/p&gt;
&lt;p&gt;  假设有一个变量$x$,一个$y$表示真实的函数值，假设有一个学习算法$A$，它从样本集中学习一个假设函数$f$来近似$y$。我们希望在给定的误差范围$\epsilon$和置信度$\delta$下，算法$A$能够以高概率（至少$1-\delta$）输出一个假设函数$f$，使得其与真实函数值$y$的误差不超过$\epsilon$。&lt;/p&gt;
&lt;p&gt;  总的来说就是我们希望能够&lt;strong&gt;以很高的概率得到一个很好的模型！！！&lt;/strong&gt;&lt;/p&gt;
&lt;h3&gt;1.2 基本术语&lt;/h3&gt;
&lt;p&gt;&lt;img src=&quot;https://pic.linxii.top/blog/base-learning-5-ML1-terms.webp&quot; alt=&quot;基本术语&quot;&gt;&lt;/p&gt;
&lt;p&gt;  &lt;strong&gt;归纳偏好(Inductive Bias):&lt;/strong&gt; 机器学习算法在学习过程中对某种类型假设的偏好，使用&lt;strong&gt;奥卡姆剃刀准则(Occam&apos;s Razor Principle)&lt;/strong&gt;，即在多个假设中，选择&lt;strong&gt;最简单的&lt;/strong&gt;假设。&lt;/p&gt;
&lt;p&gt;  &lt;strong&gt;NFL定理(No Free Lunch Theorem):&lt;/strong&gt; 没有一种学习算法在所有可能的问题上都表现最好。不同的算法在不同的问题上可能表现不同，因此选择合适的算法需要考虑具体问题的特点,脱离具体问题谈算法没意义。&lt;/p&gt;
&lt;h3&gt;1.3模型评估&lt;/h3&gt;
&lt;p&gt;  &lt;strong&gt;泛化误差(Generalization Error):&lt;/strong&gt; 衡量模型在未见过的数据上的表现。&lt;/p&gt;
&lt;p&gt;  &lt;strong&gt;经验误差(Empirical Error):&lt;/strong&gt; 衡量模型在训练数据上的表现。&lt;/p&gt;
&lt;p&gt;  &lt;strong&gt;过拟合(Overfitting):&lt;/strong&gt; 模型在训练数据上表现很好，但在测试数据上表现较差。&lt;/p&gt;
&lt;p&gt;  &lt;strong&gt;欠拟合(Underfitting):&lt;/strong&gt; 模型在训练数据和测试数据上都表现较差。&lt;/p&gt;
&lt;p&gt;  这个算法是怎么缓解过拟合的？这种缓解的技术在什么时候会失效？&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;怎么获得测试集？&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;留出法(Hold-out Method):&lt;/strong&gt; 将数据集&lt;strong&gt;划分&lt;/strong&gt;为训练集和测试集。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;交叉验证(Cross-Validation):&lt;/strong&gt; 将数据集划分为多个子集，&lt;strong&gt;轮流&lt;/strong&gt;使用一个子集作为测试集，其余子集作为训练集。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;自助法(Bootstrap Method):&lt;/strong&gt; 从数据集中有&lt;strong&gt;放回地&lt;/strong&gt;抽取样本，构建多个训练集和测试集。但是这种方法改变了数据分布。包外估计。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;调参&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;权重参数是通过训练数据学习得到的，而超参数则需要通过验证集来调节。那这么看来验证集好像是必要的。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;性能度量&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;回归任务：均方误差(MSE)、平均绝对误差(MAE)等。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;分类任务：准确率(Accuracy)、精确率(Precision)、召回率(Recall)、F1分数等。F1 分数&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;混淆矩阵(Confusion Matrix)可以更好地理解分类模型的性能。，表示如下：&lt;/p&gt;
&lt;p&gt;|               | Predicted Positive | Predicted Negative |
|---------------|--------------------|--------------------|
| Actual Positive | True Positive (TP)  | False Negative (FN) |
| Actual Negative | False Positive (FP) | True Negative (TN)  |&lt;/p&gt;
&lt;p&gt;$$Accuracy = \frac{TP + TN}{TP + TN + FP + FN}$$  
$$Precision = \frac{TP}{TP + FP}$$  
$$Recall = \frac{TP}{TP + FN}$$&lt;/p&gt;
&lt;p&gt;$$F1 Score = 2 * \frac{P \times R}{P  + R }$$   等价表示为$$  \frac{1}{F1}=\frac{1}{2} \times (\frac{1}{P}+\frac{1}{R})  $$&lt;/p&gt;
&lt;p&gt;  当我们对精确率与召回率有不同偏好的时候，可以使用加权的Fβ分数来调整它们的权重：&lt;/p&gt;
&lt;p&gt;$$ F_{\beta} = (1 + \beta^2) \times \frac{P \times R}{\beta^2 \times P + R} $$  
等价表示为$$ \frac{1}{F_{\beta}} = \frac{1}{1+\beta^2} \times (\frac{1}{P}+\frac{\beta^2}{R})$$&lt;/p&gt;
&lt;p&gt;  当$\beta &gt;1$时，召回率的权重更大，此时我的模型更加希望能够捕捉到更多的正样本; 当$\beta&amp;#x3C;1$时，精确率的权重更大，此时我的模型更加希望预测为正样本的样本中真正为正样本的比例更高。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;比较检验&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;  假设检验（Hypothesis Testing）是一种统计方法，用于比较两个或多个模型的性能是否存在显著差异。常用的方法包括t检验（t-test）、McNemar检验（基于列联表，卡方检验）。&lt;/p&gt;
&lt;h2&gt;2.线性模型&lt;/h2&gt;
&lt;p&gt;  找到一个线性函数$h(X)=W^{T}X+b$来使得$h(X_{i}) \approx y_{i}$。&lt;/p&gt;
&lt;p&gt;  最小二乘法(Ordinary Least Squares, OLS): 通过最小化预测值与真实值之间的平方误差来估计模型参数。&lt;/p&gt;
&lt;p&gt;损失函数为：$$J(W,b)=\frac{1}{2m}\sum_{i=1}^{m}(h(X^{(i)})-y^{(i)})^{2}$$，&lt;/p&gt;
&lt;p&gt;其中$m$为样本数量，$X^{(i)}$为第$i$个样本的特征向量，$y^{(i)}$为第$i$个样本的真实值。&lt;/p&gt;
&lt;p&gt;  找到一个极值点，这个极值点意味着可能为极大值点/极小值点，而在线性回归中，可以无限大，因此这个极值点就是极小值点。（哇哦！！！）&lt;/p&gt;
&lt;p&gt;  $$W=\left(X^{T}X\right)^{-1}X^{T}y$$    $$b=\bar{y}-W^{T}\bar{X}$$&lt;/p&gt;
&lt;p&gt;若$X^{T}X$不可逆，可以使用正则化方法来解决。&lt;/p&gt;
&lt;h2&gt;3.决策树&lt;/h2&gt;
&lt;p&gt;  决策树是一种树状结构的模型，用于分类和回归任务。它通过递归地将数据划分为更小的子集来构建树状结构，每个节点表示一个特征的测试，每个分支表示测试结果的不同取值，每个叶节点表示最终的预测结果。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;划分条件：&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;  &lt;strong&gt;信息增益(Information Gain)&lt;/strong&gt;: 选择能够最大化信息增益的特征进行划分。信息增益衡量了通过划分数据集所获得的信息量。划分公式如下：
$$
IG(D, A) = H(D) - \sum_{v \in Values(A)} \frac{|D_v|}{|D|} H(D_v)
$$&lt;/p&gt;
&lt;p&gt;其中$H(D)$表示数据集$D$的熵，$A$表示特征，$Values(A)$表示特征$A$的所有可能取值，$D_v$表示在特征$A$上取值为$v$的子集。（老师讲过，没复习之前竟然一点印象都没有，看到才例子想起来讲过，悲~）&lt;/p&gt;
&lt;p&gt;  &lt;strong&gt;增益率(Gain Ratio)&lt;/strong&gt;:选择能够最大化增益率的特征进行划分。增益率是信息增益与特征固有值的比值，是为了解决信息增益偏向于选择取值较多的特征的问题。（比如有一个手机号特征，这肯定不一样，决策树就可能变成了一个菊花图）
划分公式如下
$$
GR(D, A) = \frac{IG(D, A)}{IV(A)}
$$
其中$IV(A)$表示特征$A$的固有值，计算公式为：&lt;/p&gt;
&lt;p&gt;$$
IV(A) = -\sum_{v \in Values(A)} \frac{|D_v|}{|D|} \log_2 \frac{|D_v|}{|D|}
$$&lt;/p&gt;
&lt;p&gt;  &lt;strong&gt;基尼指数(Gini Index)&lt;/strong&gt;: 选择能够最小化基尼指数的特征进行划分。基尼指数衡量了数据集的纯度，值越小表示数据集越纯。划分公式如下：
$$
Gini(D) = 1 - \sum_{k=1}^{C} p_k^2
$$
其中$C$表示类别的数量，$p_k$表示类别$k$在数据集$D$中的比例。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;剪枝&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;  剪枝是为了防止过拟合，通过减少决策树的复杂度来提高模型的泛化能力。常用的剪枝方法包括预剪枝和后剪枝。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;缺失值处理&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;  样本赋权，权重划分&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;停止条件：&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;所有样本属于同一类别。&lt;/li&gt;
&lt;li&gt;达到预设的树的最大深度。&lt;/li&gt;
&lt;li&gt;当前节点包含的样本为空或少于预设的最小样本数。&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;4.支持向量机(SVM,上课未讲)&lt;/h2&gt;
&lt;p&gt;  支持向量机是一种用于分类和回归任务的监督学习算法。它通过寻找一个&lt;strong&gt;最优的超平面&lt;/strong&gt;来将不同类别的样本进行分离。支持向量机的目标是&lt;strong&gt;最大化类别之间的间隔&lt;/strong&gt;。&lt;/p&gt;
&lt;p&gt;  如果原始空间是有限维，则可以通过核函数将数据映射到高维空间，从而使得数据在&lt;strong&gt;高维空间中线性可分&lt;/strong&gt;。&lt;/p&gt;
&lt;h2&gt;5.神经网络基础&lt;/h2&gt;
&lt;p&gt;  略&lt;/p&gt;
&lt;h2&gt;6.贝叶斯分类器&lt;/h2&gt;
&lt;p&gt;  贝叶斯分类器是一种基于贝叶斯定理的概率分类算法。贝叶斯决策理论为了最小化分类错误率，应该选择后验概率最大的类别作为预测结果。给定$N$个类别$C_{1},C_{2},...,C_{N}$，对于一个新的样本$x$，贝叶斯分类器通过计算每个类别的后验概率$P(C_{i}|x)$来进行分类。根据贝叶斯定理，后验概率可以表示为：$$P(C_{i}|x) = \frac{P(x|C_{i})P(C_{i})}{P(x)}$$，其中$P(x|C_{i})$是似然概率，表示在类别$C_{i}$下观察到样本$x$的概率；$P(C_{i})$是先验概率，表示类别$C_{i}$在总体中的比例；$P(x)$是样本$x$的边际概率。&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;判别式模型与生成式模型&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;  判别式模型直接建模后验概率$P(C|X)$，如决策树；生成式模型则建模联合概率$P(X,C)$，如朴素贝叶斯分类器。
&lt;img src=&quot;https://pic.linxii.top/blog/base-learning-5-ML1-DGmodel.webp&quot; alt=&quot;生成式与判别式&quot;&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;极大似然估计&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;  先假设某种概率分布，然后基于训练数据来估计该分布的参数，$\theta_c$ 对与训练集$D$中类别$c$的样本，极大似然估计的目标是找到参数$\theta_{c}$，使得在给定类别$c$的条件下，观察到训练数据的概率最大化。数学表达式如下：$$\theta_{c}^{&lt;em&gt;} = \arg\max_{\theta_{c}} P(D_{c}|\theta_{c})$$，其中的$$P(D_{c}|\theta_{c})= \prod_{x_{i} \in D_{c}} P(x_{i}|\theta_{c})$$，其中$D_{c}$表示训练集中属于类别$c$的样本集合。但是在实际应用中，直接计算似然函数可能会导致数值下溢，因此通常使用对数似然函数进行优化，数学表达式如下：$$\theta_{c}^{&lt;/em&gt;} = \arg\max_{\theta_{c}} \sum_{x_{i} \in D_{c}} \log P(x_{i}|\theta_{c})$$。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;朴素贝叶斯分类器(Naive Bayes Classifier)&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;  朴素贝叶斯分类器是一种基于贝叶斯定理的简单而有效的分类算法。它假设特征之间是条件独立的，即在给定类别的条件下，特征之间相互独立。尽管这个假设在实际应用中可能不成立，但朴素贝叶斯分类器在许多任务中表现良好。&lt;/p&gt;
&lt;p&gt;  重点在于计算似然概率$P(X|C_{i})$，根据条件独立性假设，可以将其分解为各个特征的条件概率的乘积：$$P(X|C_{i}) = \prod_{j=1}^{n} P(x_{j}|C_{i})$$，其中$n$表示特征的数量，$x_{j}$表示第$j$个特征。&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;估计&lt;strong&gt;先验概率$P(C_{i})$&lt;/strong&gt;：可以通过计算训练集中每个类别的样本数量与总样本数量的比例来估计先验概率。数学表达式：$$P(C_{i}) = \frac{N_{i}}{N}$$，其中$N_{i}$表示类别$C_{i}$的样本数量，$N$表示训练集中的总样本数量。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;估计&lt;strong&gt;条件概率$P(x_{j}|C_{i})$&lt;/strong&gt;：对于离散特征，就是好瓜中根蒂是蜷缩的概率，就是这种特征在该类别下出现的频率。数学表达式：$$P(x_{j}|C_{i}) = \frac{N_{i,j}}{N_{i}}$$，其中$N_{i,j}$表示在类别$C_{i}$下特征$x_{j}$取值的样本数量，$N_{i}$表示类别$C_{i}$的样本数量。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;拉普拉斯修正&lt;/strong&gt;：为了解决零概率问题，可以使用拉普拉斯修正（Laplace Smoothing）来调整条件概率的估计。数学表达式：$$P(x_{i}|C) = \frac{D_{c,x_i} + 1}{D_{c} + N_{i}}$$,其中$D_{c,x_i}$表示在类别$C$下特征$x_{i}$取值的样本数量，$D_{c}$表示类别$C$的样本数量，$N_{i}$表示特征$x_{i}$的可能取值数量。&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;7.集成学习&lt;/h2&gt;
&lt;p&gt;  &lt;strong&gt;好而不同&lt;/strong&gt;!!!&lt;/p&gt;
&lt;p&gt;  同质的主要是要有差异性，异质的输出做比较时要做Alignment(配准)。(这里说的是模型类型是否相同，比如三个CNN属于同质的)&lt;/p&gt;
&lt;h3&gt;7.1 Boosting&lt;/h3&gt;
&lt;p&gt;  Boosting是确定一个基学习方法，多个基学习器，使用之前的训练集进行测试，根据测试结果调整样本权重，&lt;strong&gt;错误分类的样本权重增加&lt;/strong&gt;，&lt;strong&gt;正确分类的样本权重减少&lt;/strong&gt;，然后使用调整后的样本权重重新训练基学习器。最终将多个基学习器的预测结果进行加权投票或加权平均，得到最终的预测结果。（背后的统计学-残差最小化）&lt;/p&gt;
&lt;h3&gt;7.2 Bagging&lt;/h3&gt;
&lt;p&gt;  Bagging是Bootstrap Aggregating的缩写，是一种通过&lt;strong&gt;有放回地抽样&lt;/strong&gt;来构建多个训练集的方法。每个基学习器在不同的训练集上进行训练，最终将多个基学习器的预测结果进行投票或平均，得到最终的预测结果。&lt;/p&gt;
&lt;p&gt;  这个的改进版本就是随机森林了。&lt;/p&gt;
&lt;h2&gt;8.聚类&lt;/h2&gt;
&lt;p&gt;  聚类是一种无监督学习方法，用于将数据集划分为多个簇，使得同一簇内的数据点相似度较高，而不同簇之间的数据点相似度较低。常用的聚类算法包括K均值聚类(K-Means Clustering)、层次聚类(Hierarchical Clustering)和密度聚类(Density-Based Clustering)等。&lt;/p&gt;
&lt;h3&gt;8.1距离度量&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;非负性:&lt;/strong&gt; 距离度量的值必须是非负的，即$d(x, y) \geq 0$，对于任意的$x$和$y$。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;对称性:&lt;/strong&gt; 距离度量必须满足对称性，即$d(x, y) = d(y, x)$，对于任意的$x$和$y$。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;三角不等式:&lt;/strong&gt; 距离度量必须满足三角不等式，即$d(x, z) \leq d(x, y) + d(y, z)$，对于任意的$x$、$y$和$z$。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;同一性：&lt;/strong&gt; 距离度量必须满足同一性，即$d(x, y) = 0$当且仅当$x = y$。&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;8.2原型聚类&lt;/h3&gt;
&lt;p&gt;  原型聚类是一种基于原型的聚类方法，通过定义每个簇的中心点（原型）来进行聚类。常用的原型聚类算法包括K均值聚类(K-Means Clustering)和高斯混合模型(Gaussian Mixture Model, GMM)等。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;K均值聚类(K-Means Clustering)&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;  &lt;/p&gt;
&lt;h3&gt;8.3层次聚类&lt;/h3&gt;
&lt;h3&gt;8.4密度聚类&lt;/h3&gt;</content:encoded><h:img src="https://pic.linxii.top/blog/base-learning-5-ML1-0-111292196.webp"/><enclosure url="https://pic.linxii.top/blog/base-learning-5-ML1-0-111292196.webp"/></item><item><title>Paper Reading 1 :transformer</title><link>https://tyuou2.github.io/blog/paper-reading-1</link><guid isPermaLink="true">https://tyuou2.github.io/blog/paper-reading-1</guid><pubDate>Thu, 15 Jan 2026 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;import { ArxivRating, RatingCriteria } from &apos;@/components/advanced&apos;&lt;/p&gt;
&lt;h2&gt;Transformer&lt;/h2&gt;
&lt;p&gt;&amp;#x3C;ArxivRating id={&apos;1706.03762&apos;} tldr=&apos;Transformer架构基于自注意力机制，能够高效地处理序列数据。它由编码器和解码器组成，广泛应用于自然语言处理和计算机视觉等领域。&apos; rank={5}&gt;&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;https://www.bilibili.com/video/BV1pu411o7BE?vd_source=7cf9737027fcb723c75b8962d21a5bf9&quot;&gt;Transformer论文逐段精读【论文精读】&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;https://www.bilibili.com/video/BV16f1mB7Ebj?vd_source=7cf9737027fcb723c75b8962d21a5bf9&quot;&gt;All in Transformer | 一次讲透注意力机制：看完彻底听懂！（学不会跟我爆了）(极其详细简单的教程)&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;  本篇blog的部分图片参考自上述第二个视频，已经询问过视频原作者了。&lt;/p&gt;
&lt;h3&gt;1.Transformer架构以及原理&lt;/h3&gt;
&lt;h4&gt;1.1.Transformer架构&lt;/h4&gt;
&lt;p&gt;  Transformer架构最初用于自然语言处理任务。Transformer的核心组件是&lt;strong&gt;自注意力机制&lt;/strong&gt;（Self-Attention Mechanism），它允许模型在处理输入序列时动态地关注不同位置的信息。Transformer架构主要由编码器（Encoder）和解码器（Decoder）两部分组成，每部分包含多个相同的层（Layer）。每个编码器层包括多头自注意力机制（Multi-Head Self-Attention）和前馈神经网络（Feed-Forward Neural Network），而解码器层则在此基础上增加了对编码器输出的注意力机制。
&lt;img src=&quot;https://pic.linxii.top/blog/paper-reading-1-model.webp&quot; alt=&quot;transformer架构&quot;&gt;&lt;/p&gt;
&lt;h4&gt;1.2.缩放点积自注意力机制(Scaled Dot-Product Self-Attention)&lt;/h4&gt;
&lt;p&gt;  自注意力机制允许模型在处理序列数据时动态地关注不同位置的信息。它通过计算输入序列中每个位置与其他位置的相关性来生成新的表示。自注意力机制的计算过程包括以下几个步骤：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;线性变换&lt;/strong&gt;：将输入序列的每个token通过三个不同的线性变换映射到查询（Query）、键（Key）和值（Value）向量。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;计算注意力分数&lt;/strong&gt;：通过计算查询向量与键向量的点积来获得注意力分数，表示每个位置对其他位置的关注程度,通常会除以一个缩放因子以稳定梯度。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;归一化&lt;/strong&gt;：使用Softmax函数对注意力分数进行归一化，得到注意力权重。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;加权求和&lt;/strong&gt;：将值向量与注意力权重相乘并求和，得到新的表示。
  缩放点积自注意力机制的数学表达式如下：&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;$$Attention(Q, K, V) = softmax\left(\frac{QK^T}{\sqrt{d_k}}\right)V$$&lt;/p&gt;
&lt;p&gt;其中，$Q$、$K$、$V$分别表示查询、键和值矩阵，$d_k$是键向量的维度。&lt;/p&gt;
&lt;p&gt;  在实际计算中，$Q$与$K$的维度是$(seq_len, d_{k})$，$V$的维度是$(seq_len, d_{v})$，最终输出的维度是$(seq_len, d_{v})$。
&lt;img src=&quot;https://pic.linxii.top/blog/paper-reading-1-2-Attention.webp&quot; alt=&quot;Scaled Dot-Product Self-Attention And multi-head self-attention机制&quot;&gt;&lt;/p&gt;
&lt;h4&gt;1.3.多头自注意力机制(Multi-Head Self-Attention)&lt;/h4&gt;
&lt;p&gt;  多头自注意力机制通过并行地计算多个自注意力头来增强模型的表达能力。每个头独立地学习不同的表示，然后将它们拼接在一起并通过线性变换得到最终的输出。&lt;/p&gt;
&lt;p&gt;  多头自注意力机制的数学表达式如下：&lt;/p&gt;
&lt;p&gt;$$MultiHead(Q, K, V) = Concat(head_1, head_2, ..., head_h)W^O$$&lt;/p&gt;
&lt;p&gt;$$head_i = Attention(QW_i^Q, KW_i^K, VW_i^V)$$&lt;/p&gt;
&lt;p&gt;其中，$h$表示头的数量，$W_i^Q$、$W_i^K$、$W_i^V$是每个头的线性变换矩阵，$W^O$是输出的线性变换矩阵。&lt;/p&gt;
&lt;p&gt;  在使用多头自注意力机制时，最后的输出维度是通过拼接所有头的输出并进行线性变换得到的，因此需要确保每个头的输出维度之和等于模型的隐藏维度。于是这里维度相关的公式就是：
$$d_{k}=d_{v}=d_{model}/h $$&lt;/p&gt;
&lt;p&gt;  此次$d_{k}=d_{v}$，是为了简化计算，实际中可以不相等。&lt;/p&gt;
&lt;h4&gt;1.4.位置编码(Position Encoding)&lt;/h4&gt;
&lt;p&gt;  由于Transformer架构不包含循环或卷积结构，因此无法直接捕捉序列中的位置信息。为了解决这个问题，Transformer引入了位置编码（Position Encoding），它为输入序列中的每个token添加了位置信息。位置编码可以是固定的（如正弦和余弦函数）或可学习的,论文中提到固定的与可学习的效果差不多，因此就采用了固定的位置编码。&lt;/p&gt;
&lt;p&gt;$$
PE_{(pos, 2i)} = sin\left(\frac{pos}{10000^{2i/d_{model}}}\right)
$$&lt;/p&gt;
&lt;p&gt;$$
PE_{(pos, 2i+1)} = cos\left(\frac{pos}{10000^{2i/d_{model}}}\right)
$$&lt;/p&gt;
&lt;p&gt;  $$pos$$表示位置，$$i$$表示维度索引，$$d_{model}$$是每个token的特征维度。&lt;/p&gt;
&lt;p&gt;  这里使用$\frac{2i}{d}$其中的2是为了区分偶数维和奇数维，偶数维使用正弦函数，奇数维使用余弦函数。这样设计的位置编码具有周期性，可以帮助模型捕捉不同位置之间的关系。&lt;/p&gt;
&lt;h4&gt;1.5层归一化(Layer Normalization)&lt;/h4&gt;
&lt;p&gt;  首先什么是batch normalization？Batch Normalization是特征归一化的一种方法，它通过对每个mini-batch的数据进行归一化处理，&lt;strong&gt;使得每个特征的均值为0，方差为1&lt;/strong&gt;，从而减少了内部协变量偏移。Batch Normalization通常应用于卷积层或全连接层之后。&lt;/p&gt;
&lt;p&gt;  而Layer Normalization是另一种归一化方法，它&lt;strong&gt;对每个样本的所有特征进行归一化&lt;/strong&gt;，使得每个样本的特征均值为0，方差为1，与Batch Normalization不同。Layer Normalization通常应用于Transformer中的自注意力机制和前馈神经网络之后。&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://pic.linxii.top/blog/paper-reading-1-normC.webp&quot; alt=&quot;手绘norm化对比图&quot;&gt;&lt;/p&gt;
&lt;p&gt;  图中蓝色部分为Batch Normalization，橙色部分为Layer Normalization。下面是二维的例子，上面是三维的例子。&lt;/p&gt;
&lt;p&gt;  Layer Normalization更好的解释是，图中右侧部分的内容。Batch Normalization是对每个特征进行归一化，这个时候每个样本有长有短，而且在进行预测的时候需要存储全局的均值和方差，如果在进行预测的时候碰到一个非常长的样本，可能就处理不好。而Layer Normalization是对每个样本进行归一化，与全局的均值和方差无关。&lt;/p&gt;
&lt;h3&gt;2.Transformer例子（来自Second视频）&lt;/h3&gt;
&lt;p&gt;  假设我们有3个简单的句子，分别是：“史蒂夫  建造  房子”、“苦力怕  在  玩家  身边  爆炸”、“挖 钻石 很 有趣”。&lt;/p&gt;</content:encoded><h:img src="https://pic.linxii.top/blog/paper-reading-1-0-137188632.webp"/><enclosure url="https://pic.linxii.top/blog/paper-reading-1-0-137188632.webp"/></item><item><title>CV基础学习笔记1</title><link>https://tyuou2.github.io/blog/base-learning-2-cv</link><guid isPermaLink="true">https://tyuou2.github.io/blog/base-learning-2-cv</guid><description>计算机视觉的基础知识，下游任务、卷积的原理，经典模型（如AlexNet、VGG、ResNet）、Transformer在计算机视觉中的应用（如ViT）等内容。</description><pubDate>Wed, 14 Jan 2026 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;import { ArxivRating, RatingCriteria } from &apos;@/components/advanced&apos;&lt;/p&gt;
&lt;h2&gt;计算机视觉&lt;/h2&gt;
&lt;h3&gt;1.Intro&lt;/h3&gt;
&lt;h4&gt;1.1.计算机视觉任务&lt;/h4&gt;
&lt;ul&gt;
&lt;li&gt;图像分类(Image Classification)：将图像分配到预定义的类别中。&lt;/li&gt;
&lt;li&gt;目标检测(Object Detection)：识别图像中的物体并确定其位置。&lt;/li&gt;
&lt;li&gt;语义分割(Semantic Segmentation)：将图像中的每个像素分配到特定的类别。&lt;/li&gt;
&lt;li&gt;实例分割(Instance Segmentation)：不仅区分不同类别，还区分同一类别的不同实例。&lt;/li&gt;
&lt;li&gt;图像生成(Image Generation)：生成新的图像。&lt;/li&gt;
&lt;/ul&gt;
&lt;h4&gt;1.2.计算机视觉发展&lt;/h4&gt;
&lt;p&gt;  计算机视觉的发展经历了从传统方法到深度学习的转变。早期的方法依赖于&lt;strong&gt;手工设计的特征提取技术&lt;/strong&gt;，如SIFT和HOG。然而，随着深度学习的兴起，&lt;strong&gt;卷积神经网络&lt;/strong&gt; &lt;strong&gt;(&lt;/strong&gt; &lt;strong&gt;CNN&lt;/strong&gt; &lt;strong&gt;)&lt;/strong&gt; 成为主流，如AlexNet、ResNet ，显著提升了计算机视觉任务的性能。再后来，&lt;strong&gt;Transformer架构&lt;/strong&gt;也被引入计算机视觉领域，进一步推动了该领域的发展。&lt;/p&gt;
&lt;h3&gt;2.CNN&lt;/h3&gt;
&lt;h4&gt;2.1.卷积操作(Convolution Operation)&lt;/h4&gt;
&lt;p&gt;  卷积操作是CNN的核心，通过在输入图像上应用滤波器（卷积核）来提取特征。卷积操作是捕捉局部空间关系的有效方法，能够识别图像中的边缘、纹理等基本特征，低层卷积层通常捕捉简单特征，而高层卷积层则捕捉更复杂的模式和语义信息。
  卷积操作的数学表达式如下：
$$
S(i,j) = (I * K)(i,j) = \sum_{m}\sum_{n} I(i-m,j-n)K(m,n)
$$
  其中，$I$表示输入图像，$K$表示卷积核，$S$表示输出特征图，$(i,j)$表示输出特征图中的位置，$(m,n)$表示卷积核的位置。
  在这里，卷积操作可以利用&lt;strong&gt;快速傅里叶变换（FFT）&lt;/strong&gt; 进行加速，特别是在处理大尺寸图像时。此外，卷积操作还可以通过调整步幅（stride）和填充（padding）来控制输出特征图的尺寸。&lt;/p&gt;
&lt;h4&gt;2.2.池化操作(Pooling Operation)&lt;/h4&gt;
&lt;p&gt;  池化操作用于降低特征图的空间维度，同时保留重要信息。常见的池化方法包括最大池化（Max Pooling）和平均池化（Average Pooling）。池化有助于减少计算量，防止过拟合。&lt;/p&gt;
&lt;h3&gt;3.经典CNN架构&lt;/h3&gt;
&lt;h4&gt;3.1.AlexNet&lt;/h4&gt;
&lt;h4&gt;3.2.VGG&lt;/h4&gt;
&lt;h4&gt;3.3.ResNet&lt;/h4&gt;
&lt;h3&gt;4.Transformer在计算机视觉中的应用&lt;/h3&gt;
&lt;h4&gt;4.1.ViT (Vision Transformer)&lt;/h4&gt;
&lt;p&gt;&lt;img src=&quot;https://pic.linxii.top/blog/base-learning-2-CV-vit.webp&quot; alt=&quot;vit架构&quot;&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;vit的基本流程：&lt;/strong&gt;&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;图像被划分为16x16的patches，每个patch被展平并映射到一个固定维度的向量空间，即每个patch对应一个patch embedding。（这一映射过程通过可训练的线性投影（trainable linear projection）实现），然后加上一个分类令牌(class token)，用于最终的分类任务。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;然后进行位置编码(Position Encoding)，vit中的位置编码是通过可学习的位置嵌入实现的。先初始化一个与patch数量相同的可学习位置嵌入矩阵。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;位置编码被添加到patch embeddings中，以保留空间信息。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;这些patch embeddings被输入到标准的Transformer编码器中进行处理。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;最终的分类是通过在Transformer输出上添加一个MLP分类头来实现的。&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;&lt;strong&gt;ViT 输入输出尺寸（ViT-Base/16）&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;原始图像（224×224×3）→ Patches（196×768）→ Patch Embeddings（196×768）→ 加分类令牌（197×768）→ 加位置嵌入（197×768）→ 编码器输出（197×768）→ 全局表示（1×768）→ 分类结果（1×1000）&lt;/p&gt;</content:encoded><h:img src="https://pic.linxii.top/blog/base-learning-2-CV-0-105463056.webp"/><enclosure url="https://pic.linxii.top/blog/base-learning-2-CV-0-105463056.webp"/></item><item><title>回家ing</title><link>https://tyuou2.github.io/blog/daily-1-gohome</link><guid isPermaLink="true">https://tyuou2.github.io/blog/daily-1-gohome</guid><description>回家路上随手拍与随脑想！！！</description><pubDate>Thu, 29 Jan 2026 00:00:00 GMT</pubDate><content:encoded>&lt;h2&gt;回家ing&lt;/h2&gt;
&lt;p&gt;  从青岛回家，暂时还没有直达的高铁，怕转车赶不上回家的公交车，于是还是直接选择了大巴车（下学期回学校的时候一定要做一次火车/高铁，体验+学习一下做高铁的流程，说实话还没做过高铁呢~）&lt;/p&gt;
&lt;p&gt;  然后大巴车也是走走停停&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-python&quot;&gt;上车-&gt;发车-&gt;停车接人 for i in range(n) -&gt;发车-&gt;服务区休息-&gt;继续发车-&gt;到达!
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;  刚刚出青岛的时候，拍了一下小山与山脚的乡村(不擅长拍照，而且像素有点差了~)，单纯记录一下吧！！！
&lt;img src=&quot;https://pic.linxii.top/blog/daily-1-gohome-1mountain.webp&quot; alt=&quot;&quot;&gt;&lt;/p&gt;
&lt;p&gt;  经过漫漫漫漫漫漫...长的路程，终于到家了！！！天气也不太好，灰蒙蒙的~不过回家就是好！！！&lt;/p&gt;
&lt;p&gt;  这里灰蒙蒙的，别的地方也是灰蒙蒙的&lt;/p&gt;
&lt;p&gt;  over~
&lt;img src=&quot;https://pic.linxii.top/blog/daily-1-gohome-2griy.webp&quot; alt=&quot;&quot;&gt;&lt;/p&gt;</content:encoded><h:img src="https://pic.linxii.top/blog/daily-1-gohome-0-92642907.webp"/><enclosure url="https://pic.linxii.top/blog/daily-1-gohome-0-92642907.webp"/></item><item><title>CS336-3-scaling-law</title><link>https://tyuou2.github.io/blog/cs336-3-scaling-law</link><guid isPermaLink="true">https://tyuou2.github.io/blog/cs336-3-scaling-law</guid><pubDate>Tue, 27 Jan 2026 00:00:00 GMT</pubDate><content:encoded>&lt;h2&gt;9.Scaling Law basics&lt;/h2&gt;
&lt;h3&gt;9.1 data与performance的关系&lt;/h3&gt;
&lt;p&gt;  可以用一个公式来表示数据量与模型性能的关系：
&lt;img src=&quot;https://pic.linxii.top/blog/cs336-3-scaling-law-1DataVsPerformance.webp&quot; alt=&quot;Data vs Performance&quot;&gt;
  假设有$N$个样本，然后这些样本服从高斯分布，即$x_i \sim N(\mu, \sigma^2)$，如果使用$\hat{\mu} = \frac{1}{N} \sum_{i=1}^{N} x_i$来估计$\mu$，那么估算的均方误差$\mathbb{E}[(\hat{\mu}-\mu)^2]=\frac{\sigma^2}{N}$。
然后两边取个log,于是$log(error)=-log(N)+2log(\sigma)$，误差的对数与数据量的对数成线性关系。这就是一种scaling law。从这里可以认识到任何像$\frac{1}{N^{\alpha}}$这样的关系都可以被看作是一种scaling law。&lt;/p&gt;
&lt;h3&gt;9.2 data 与 model size的关系&lt;/h3&gt;
&lt;p&gt;  在进行模型构建时选择哪些呢？&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;架构&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;对比不同的架构，发现transformer与LSTM,可以从下面的图中看到结果
&lt;img src=&quot;https://pic.linxii.top/blog/cs336-3-scaling-law-2ArchitectureComparison.webp&quot; alt=&quot;Architecture Comparison&quot;&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;优化器&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;  对比不同的优化器，如Adam与SGD,可以从下面的图中看到结果，
&lt;img src=&quot;https://pic.linxii.top/blog/cs336-3-scaling-law-3OptimizerComparison.webp&quot; alt=&quot;Optimizer Comparison&quot;&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;width vs depth&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;  对比不同的宽度与深度，可以从下面的图中看到结果，当然在实际使用中还需要考虑计算成本与时间成本等因素，
&lt;img src=&quot;https://pic.linxii.top/blog/cs336-3-scaling-law-4WidthVsDepth.webp&quot; alt=&quot;Width vs Depth&quot;&gt;&lt;/p&gt;
&lt;h3&gt;9.3 hyper-parameter与performance的关系&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;batch size&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;  batch size的影响如下图所示：
&lt;img src=&quot;https://pic.linxii.top/blog/cs336-3-scaling-law-5BatchSize.webp&quot; alt=&quot;Batch Size&quot;&gt;
  通过左图可以看出来小 batch 的梯度方向不稳定，路径带有更多噪声，大 batch 的梯度更接近真实方向，路径更直，更稳定。然后但每一步计算成本更高。&lt;/p&gt;
&lt;p&gt;  右图中的Noise Scale可以认为是训练中 “噪声刚好被压到可接受水平” 的那个最小批量大小。于是右图的含义就是训练速度与batch size的关系。&lt;/p&gt;
&lt;p&gt;  然后可以定义&lt;strong&gt;临界批量大小 = 达到目标损失所需的最小样本数 / 达到目标损失所需的最小步数&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;learning rate&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;  当模型宽度缩放时，最优学习率也会变化。，因此可以采用mup等方法来进行，当模型缩放时，学习率按固定规律缩放。&lt;/p&gt;
&lt;h3&gt;9.4 data与model size的数学关系&lt;/h3&gt;
&lt;p&gt;  Joint data-model scaling law可以表示为：
$$
Error(N, M) = N^{-\alpha}+M^{-\beta}+C
$$
也有研究表明可以表示为下面的式子，基本上是等价的，差了一个常数项，这个代表某个不可降低的最低误差C：
$$
Error(N, M) =(M^{-\alpha}+n^{-1})^{\beta}
$$&lt;/p&gt;</content:encoded><h:img src="https://pic.linxii.top/blog/cs336-3-scaling-law-0-120301073.webp"/><enclosure url="https://pic.linxii.top/blog/cs336-3-scaling-law-0-120301073.webp"/></item><item><title>CS336-2-system</title><link>https://tyuou2.github.io/blog/cs336-2-system</link><guid isPermaLink="true">https://tyuou2.github.io/blog/cs336-2-system</guid><description>LLM的底层，主要是GPU部分</description><pubDate>Sat, 24 Jan 2026 00:00:00 GMT</pubDate><content:encoded>&lt;h2&gt;4.MoE架构&lt;/h2&gt;
&lt;p&gt;  Mixture of Experts(MoE)是一种通过引入多个专家模型来提升模型容量和性能的架构。MoE模型通常包含一个路由器（Router）和多个专家（Experts）。这里的多个专家可以认为是FFN层的集合，每个专家都是一个独立的神经网络模块。路由器根据输入数据的特征动态选择最合适的专家进行处理。
&lt;img src=&quot;https://pic.linxii.top/blog/cs336-2-system-1DenseAndMoE.webp&quot; alt=&quot;Dense与MoE架构示意图&quot;&gt;&lt;/p&gt;
&lt;h3&gt;4.1Router机制&lt;/h3&gt;
&lt;p&gt;  路由器的主要任务是根据输入数据的特征动态选择最合适的专家进行处理。路由器通常使用一个轻量级的神经网络来计算每个专家的权重分布，然后根据这些权重选择最合适的专家。常见的路由策略包括Top-K选择、强化学习等。&lt;/p&gt;
&lt;p&gt;  在Top-K选择中，常见的有三种方式：Token chooses experts、Expert chooses  tokens、全局选择。现在大部分模型都采用Token chooses experts的方式，即&lt;strong&gt;每个输入token独立选择Top-K个专家进行处理&lt;/strong&gt;。
&lt;img src=&quot;https://pic.linxii.top/blog/cs336-2-system-2Router.webp&quot; alt=&quot;Router&quot;&gt;&lt;/p&gt;
&lt;h3&gt;4.2 shared expert&lt;/h3&gt;
&lt;p&gt;  DeepSeek V3 证明了&lt;strong&gt;参数量少，数量更多&lt;/strong&gt;的专家模型+&lt;strong&gt;一个共享的&lt;/strong&gt;专家模型是非常有效的，能在多个Benchmark 的表现变得更好。
&lt;img src=&quot;https://pic.linxii.top/blog/cs336-2-system-3SharedExpert.webp&quot; alt=&quot;shared expert&quot;&gt;&lt;/p&gt;
&lt;h3&gt;4.3 如何训练MoE模型&lt;/h3&gt;
&lt;p&gt;  如何训练MoE模型是一个关键问题。我们不能够简单地将所有专家都训练一遍，因为这样会导致计算资源的浪费。然后就是对于Router的训练，这个是不可微分的。&lt;/p&gt;
&lt;p&gt;  目前常用的训练方法包括以下几种：使用RL进行训练，随机扰动，启发式平衡损失(Heuristic balencing loss)&lt;/p&gt;
&lt;h3&gt;4.4 MoE总结&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;MoE利用了稀疏性特征，不是每个专家都参与计算，从而节省了计算资源。&lt;/li&gt;
&lt;li&gt;离散路由使得选择专家的过程不可微分，但是在实践中Top-K选择已经被证明是有效的。&lt;/li&gt;
&lt;li&gt;MoE是有效且性价比高的，现在许多顶级模型基本都采用这种架构。&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;5.GPU&lt;/h2&gt;
&lt;h3&gt;5.1 学习GPU&lt;/h3&gt;
&lt;p&gt;  CPU与GPU的设计目标的区别，CPU优化的是latency(每个线程都要尽快完成任务，这个在操作系统中详细讲过各种调度算法等等知识)，而GPU优化的是throughput（总的处理的数据量要大)。GPU的设计是高并行度，适合处理大量相似的计算任务，比如图形渲染和深度学习中的矩阵运算。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;架构组成&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;execution units：GPU的核心计算单元，负责执行各种计算任务。一个GPU包含多个SM（Streaming Multiprocessors），每个SM包含多个SP（Streaming Processors）,这些SP就是执行单元，这些可以并行处理大量的线程，进行并行运算。&lt;/li&gt;
&lt;li&gt;memory：GPU的内存层次结构，L1 Cache、共享内存位于SM内部，L2 Cache位于GPU芯片上，显存（VRAM）位于GPU外部。存取速度与CPU的内存层次结构类似，距离SM越近的内存层次，存取速度越快。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;执行&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;  在GPU中，执行的时候有三个重要的概念：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;thread：线程以并行方式运行，所有线程执行相同的指令，但是处理不同的数据。（单指令多线程，SIMT）&lt;/li&gt;
&lt;li&gt;warp：warp是GPU中线程的基本调度单位，通常一个warp包含32个线程，这些线程同时执行相同的指令。&lt;/li&gt;
&lt;li&gt;block：block是线程的集合，每个block在一个SM上执行，而且拥有自己的共享内存。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;  在执行过程中的内存访问与传输
&lt;img src=&quot;https://pic.linxii.top/blog/cs336-2-system-4MemoryAccessAndTransfer.webp&quot; alt=&quot;GPU内存访问与传输&quot;&gt;&lt;/p&gt;
&lt;h3&gt;5.2 加速GPU的Trick&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;控制差异&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;  在GPU中，线程是以warp为单位进行调度的，每个warp的线程是运行相同指令的，如果一个warp中的线程执行不同的分支路径，就会导致控制差异（Control Divergence），不能够并行执行，从而降低并行效率。因此，在编写GPU代码时，&lt;strong&gt;尽量避免在同一个warp内出现分支语句&lt;/strong&gt;。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;低精度计算&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;  在深度学习中，通常使用低精度的数据类型（如FP16、INT8）来进行计算，虽然计算的次数不变，但是在进行读写存储器时，传输的数据量减少了，提升了整体的效率。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;操作融合&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;  把多个操作融合到一个CUDA kernel中执行，减少内存读写次数，例如，把卷积操作和激活函数融合到一个kernel中执行。算子融合。
&lt;img src=&quot;https://pic.linxii.top/blog/cs336-2-system-5OperatorFusion.webp&quot; alt=&quot;操作融合示意图&quot;&gt;
&lt;strong&gt;recomputation&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;  在训练过程中，某些中间结果可以通过重新计算得到，而不是存储在显存中。这样可以节省显存空间，允许使用更大的模型或者更大的batch size进行训练。通过在前向传播时不保存某些中间结果，在反向传播时&lt;strong&gt;重新计算&lt;/strong&gt;这些结果，从而减少显存的使用。
&lt;img src=&quot;https://pic.linxii.top/blog/cs336-2-system-6Recomputation.webp&quot; alt=&quot;recomputation示意图&quot;&gt;
&lt;strong&gt;Memory coalescing and DRAM&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;  内存合并是指多个线程同时访问连续的内存地址，从而提高内存访问效率。DRAM的访问延迟较高，因此通过内存合并可以减少访问次数，提高整体性能。在编写GPU代码时，尽量让线程访问连续的内存地址，避免随机访问。仍然是利用了&lt;strong&gt;局部性原理&lt;/strong&gt;，主要是空间局部性。实现的时候也是通过一次性传输更多数据来减少访问次数。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;tiling&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;  Tiling是一种将大规模数据划分为较小块进行处理的技术。通过将矩阵划分为块，重复读取shared memory，而不是更慢的global memory。在实现时，可以将矩阵划分为较小的子矩阵（tiles），然后在GPU上并行处理这些子矩阵。然后这里也可能出现一些问题，当矩阵的维度不是tile大小的整数倍时，可能会导致一些线程处理不完整的tile，从而影响性能。因此，在设计tile大小时，需要考虑矩阵的维度，选择合适的tile大小以最大化利用率。&lt;/p&gt;
&lt;h3&gt;5.3 Flash Attention&lt;/h3&gt;
&lt;p&gt;  Flash Attention是一种高效的注意力机制实现。Flash Attention的核心思想是将注意力计算划分为多个小块（tiles），并在GPU的共享内存中进行计算，避免了频繁访问全局内存。
对于矩阵的操作可以运用前面的，而在注意力机制里面有softmax操作，Flash Attention通过分块计算softmax。具体来说，Flash Attention在计算softmax时，将输入矩阵划分为多个小块（tiles），先在每个 tile 内计算局部中间参数（块内最大值、块内指数和），再通过跨 tile 的累积计算更新整行的全局最大值和全局指数和，最终基于全局参数完成 Softmax 的全局归一化。
&lt;img src=&quot;https://pic.linxii.top/blog/cs336-2-system-7FlashAttention.webp&quot; alt=&quot;Flash Attention示意图&quot;&gt;&lt;/p&gt;
&lt;h2&gt;6.GPU运行与CUDA kernel&lt;/h2&gt;
&lt;p&gt;  首先是一些常用运算在GPU运行时的CPU与GPU上的时间开销。&lt;/p&gt;
&lt;h3&gt;6.1 CUDA kernel&lt;/h3&gt;
&lt;p&gt;  可以自己实现CUDA kernel来加速特定的计算任务。但是一般的常用操作，pytorch等深度学习框架已经实现了高度优化的CUDA kernel，可以直接使用。(而且对于常用操作，往往自己实现的kernel不如框架自带的更快)&lt;/p&gt;
&lt;p&gt;  然后进行CUDA kernel编程的主要语言有两种CUDA C/C++、Triton。C/C++是NVIDIA官方提供的CUDA编程语言，适合底层优化。Triton语法接近Python，基于Python调用，Triton的编译器会自动生成高效的 CUDA kernel，语法简洁、开发效率高。&lt;/p&gt;
&lt;h2&gt;7.Parallelism basic&lt;/h2&gt;
&lt;h3&gt;7.1 LLM的Networking&lt;/h3&gt;
&lt;p&gt;  对于大语言模型来说，单GPU的显存以及计算速度是无法满足的，因此需要多个GPU协同。下图是多个GPU协同工作的示意图。
&lt;img src=&quot;https://pic.linxii.top/blog/cs336-2-system-8LLMNetworking.webp&quot; alt=&quot;LLM的Networking&quot;&gt;
&lt;strong&gt;collective communication&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;All-Reduce：将所有节点的数据进行归约操作（如求和、最大值等），并将结果分发给所有节点。&lt;/li&gt;
&lt;li&gt;Broadcast：将一个节点的数据广播到所有其他节点。&lt;/li&gt;
&lt;li&gt;Reduce：将所有节点的数据进行归约操作，并将结果发送到指定的节点。&lt;/li&gt;
&lt;li&gt;All Gather：将所有节点的数据收集到每个节点（这里并不进行归约操作）。&lt;/li&gt;
&lt;li&gt;Reduce Scatter：将所有节点的数据进行归约操作，并将结果分散到各个节点。
&lt;img src=&quot;https://pic.linxii.top/blog/cs336-2-system-9CollectiveCommunication.webp&quot; alt=&quot;Collective Communication basic示意图&quot;&gt;
  然后其中的All Reduce可以由Reduce Scatter + All Gather实现。&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;7.2 不同形式的并行&lt;/h3&gt;
&lt;h4&gt;7.2.1 Data Parallelism&lt;/h4&gt;
&lt;p&gt;  最初的数据并行是将模型复制到多个GPU上，然后将输入数据划分为B个batch，然后把这B个batch分给M个GPU进行计算，每个GPU计算完之后，对梯度进行All Reduce操作，最后更新模型参数。这样做的好处是实现简单，缺点是对与Memory非常不友好，因为每个GPU都要存一个完整的模型参数，当模型非常大时，单个GPU无法容纳整个模型。&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-python&quot;&gt;def data_parallelism_main(rank: int, world_size: int, data: torch.Tensor, num_layers: int, num_steps: int):
    for step in range(num_steps):
        optimizer.zero_grad()  # 清空梯度
        x = data
        for param in params:
            x = x @ param
            x = F.gelu(x)
        loss = x.square().mean()  # Loss function is average squared magnitude
        loss.backward()
        for param in params:
            dist.all_reduce(tensor=param.grad, op=dist.ReduceOp.AVG, async_op=False)
        optimizer.step()
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;  此处的代码就是Parallelism的简易实现， all-reduce操作的位置在计算梯度后，optimizer 更新前的，然后每个GPU计算的loss不同，梯度被 all-reduced 到不同的GPU，上面的代码中使用的是平均值（AVG），因此通过这种方式保持每个GPU的参数同步。&lt;/p&gt;
&lt;p&gt;  然后ZeRO提出了一种优化的数据并行方法，其有三个主要的优化阶段&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;优化器状态分区：将优化器的状态（Adam的动量等）分区到多个GPU上，每个GPU只更新自己负责的参数部分，然后通过&lt;strong&gt;通信&lt;/strong&gt;进行同步。&lt;/li&gt;
&lt;li&gt;梯度分区：在前面基础上，梯度也按照相同方式分区。每个GPU只保留自己负责的参数的梯度，然后All-Reduce变为Reduce-Scatter+All-Gather。&lt;/li&gt;
&lt;li&gt;参数分区：在前面基础上，模型参数也进行分区，在进行前向传播/反向传播时，通过All-Gather收集参数，计算完成后再丢弃非本地部分，这样单卡的显存占用变成1/M，但是通信开销增加了。
&lt;img src=&quot;https://pic.linxii.top/blog/cs336-2-system-10ZeRO.webp&quot; alt=&quot;ZeRO示意图&quot;&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h4&gt;7.2.2 Model Parallelism&lt;/h4&gt;
&lt;p&gt;  模型并行是将模型划分为多个部分，主要有两种方式：Pipeline Parallelism和Tensor Parallelism。
&lt;strong&gt;Poipeline Parallelism&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;  Pipeline Parallelism是将模型划分为多个阶段，每个阶段放在不同的GPU上，像工业流水线一样进行处理。输入数据经过第一个阶段处理后，输出结果传递给下一个阶段，依次类推。
&lt;img src=&quot;https://pic.linxii.top/blog/cs336-2-system-11PipelineParallelism.webp&quot; alt=&quot;Pipeline Parallelism示意图&quot;&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Tensor Parallelism&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;  Tensor Parallelism是将模型的张量（如权重矩阵）划分为多个部分，分布在不同的GPU上进行计算。这种方法适用于大规模矩阵运算，避免了单个GPU的内存瓶颈。
&lt;img src=&quot;https://pic.linxii.top/blog/cs336-2-system-12TensorParallelism.webp&quot; alt=&quot;Tensor Parallelism示意图&quot;&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-python&quot;&gt;def tensor_parallelism_main(rank: int, world_size: int, data: torch.Tensor, num_layers: int):
    local_num_dim = int_divide(num_dim, world_size)
    params = [get_init_params(num_dim, local_num_dim, rank) for i in range(num_layers)]
    x = data
    for i in range(num_layers):
        x = x @ params[i]
        x = F.gelu(x)
        activations = [torch.empty(batch_size, local_num_dim, device=get_device(rank)) for _ in range(world_size)]
        dist.all_gather(tensor_list=activations, tensor=x, async_op=False)
        x = torch.cat(activations, dim=1)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;  Tensor Parallelism是每个GPU获取部分 layer（或者子矩阵），通信时传输所有的data和 activation。&lt;/p&gt;
&lt;h4&gt;7.2.3 Sequence Parallelism&lt;/h4&gt;
&lt;p&gt;  Sequence Parallelism是针对序列数据（如文本、时间序列等）进行并行处理的方法。它将长序列划分为多个子序列，分布在不同的GPU上进行计算。每个设备处理序列的一部分。在需要全序列信息的操作（如注意力机制）时，进行&lt;strong&gt;通信&lt;/strong&gt;交换信息。图中的$g$表示All gather操作,$\bar{g}$表示Reduce scatter操作。
&lt;img src=&quot;https://pic.linxii.top/blog/cs336-2-system-13SequenceParallelism.webp&quot; alt=&quot;Sequence Parallelism示意图&quot;&gt;&lt;/p&gt;
&lt;h2&gt;8.Parallelism 2&lt;/h2&gt;
&lt;p&gt;  综合到7里面了&lt;/p&gt;</content:encoded><h:img src="https://pic.linxii.top/blog/cs336-2-system-0-111292196.webp"/><enclosure url="https://pic.linxii.top/blog/cs336-2-system-0-111292196.webp"/></item><item><title>RL的原理-4-值迭代与策略迭代</title><link>https://tyuou2.github.io/blog/rl-learning-4-vpit</link><guid isPermaLink="true">https://tyuou2.github.io/blog/rl-learning-4-vpit</guid><description>值迭代与策略迭代的原理与算法，有点懵，review++</description><pubDate>Thu, 22 Jan 2026 00:00:00 GMT</pubDate><content:encoded>&lt;h2&gt;1.值迭代(Value Iteration)&lt;/h2&gt;
&lt;p&gt;  值迭代是一种通过反复应用贝尔曼最优公式来计算最优状态值函数和最优策略的方法。就是在rl-3中提到的贝尔曼最优公式的求解。&lt;/p&gt;
&lt;p&gt;  值迭代的基本思想是从一个初始的状态值函数$v_0$开始，反复应用贝尔曼最优公式，得到一系列的状态值函数$v_1, v_2, ...$。随着迭代次数的增加，这些状态值函数将逐渐逼近最优状态值函数$v_{*}$。
$$
v_{k+1} =f(v_k) = \max_{\pi} \left( r_{\pi} + \gamma \mathbf{P_{\pi}} v_k \right)
$$&lt;/p&gt;
&lt;h3&gt;1.1 算法步骤（Matrix form）&lt;/h3&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;策略更新(policy update)&lt;/strong&gt;：对于当前的状态值函数$v_k$，计算对应的策略$\pi_k$。
$$
\pi_{k+1}= argmax_{\pi}(r_{\pi} + \gamma P_{\pi} v_k)
$$&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;值更新(value update)&lt;/strong&gt;：使用贝尔曼最优公式更新状态值函数$v_{k+1}$。
$$
v_{k+1} = r_{\pi_{k+1}} + \gamma \mathbf{P_{\pi_{k+1}}} v_k
$$&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;h3&gt;1.2 算法步骤（Element-wise form）&lt;/h3&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;策略更新(policy update)
$$
\pi_{k+1}(s) = argmax_{a} \sum \pi(a|s) (\sum_{r} p(r | s, a)r+ \gamma \sum_{s&apos;} p(s&apos; | s, a) v_k(s&apos;)), \forall s \in S
$$
  然后最大的动作就是当前状态下的最优动作,这是一个贪心的过程。
$$
\pi_{k+1}(s) = argmax_{a} q_{k}(a, s), \forall s \in S
$$
$$
\pi_{k+1}(a|s)=
\begin{cases}
1 &amp;#x26; a=a^&lt;em&gt;_{k}(s) \
0 &amp;#x26; a \ne a^&lt;/em&gt;_{k}(s)
\end{cases}
$$&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;值更新(value update)
$$
v_{k+1}(s) = \sum_{a} \pi_{k+1}(a|s) (\sum_{r} p(r | s, a)r+ \gamma \sum_{s&apos;} p(s&apos; | s, a) v_k(s&apos;)), \forall s \in S
$$
$$
v_{k+1}(s) = \max_{a} q_{k}(a,s)
$$&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;h3&gt;1.3例子&lt;/h3&gt;
&lt;p&gt;  我们会有一个q-value的表格，然后通过上面的步骤不断更新q-value表格，直到收敛为止。
| q-value    | $a_{1}$  | $a_{2}$  | $a_{3}$ |...  |
|------------|----------|----------|---------|-----|
| $S_{1}$    | $-1+\gamma v({s1})$  |  $0+\gamma v({s3})$        |  $0+\gamma v({s1})$       |     |
| $S_{2}$    |  ...     |          |         |     |
| $S_{3}$    |  ...     |          |         |     |
|  ...       |  ...     |          |         |     |&lt;/p&gt;
&lt;p&gt;  通过之前的式子知道$
v_{k+1}(s) = \max_{a} q_{k}(a,s)
$，所以每次更新v-value的时候，就是取q-value表格中每一行的最大值。这里的每次更新是所有状态都会进行更新。&lt;/p&gt;
&lt;p&gt;  在执行的时候让$v_{k}=0$，设定个初值上面的表格就是具体的数了，然后就可以不断迭代，直到收敛为止。&lt;/p&gt;
&lt;h2&gt;2.策略迭代(Policy Iteration)&lt;/h2&gt;
&lt;p&gt;  最开始有一个策略$\pi_0$，任意的就行，然后通过策略评估计算出对应的状态值函数$v_{\pi_0}$，然后进行策略迭代。&lt;/p&gt;
&lt;h3&gt;2.1 算法步骤（Matrix form）&lt;/h3&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;策略评估(Policy Evaluation)&lt;/strong&gt;：对于当前的策略$\pi_k$，计算对应的状态值函数$v_{\pi_k}$。
$$
v_{\pi_k} = r_{\pi_k} + \gamma \mathbf{P_{\pi_k}} v_{\pi_k}
$$&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;策略改进(Policy Improvement)&lt;/strong&gt;：使用贝尔曼最优公式更新策略$\pi_{k+1}$。
$$
\pi_{k+1}= argmax_{\pi}(r_{\pi} + \gamma P_{\pi} v_{\pi_k})
$$&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;h3&gt;2.2 算法步骤（Element-wise form）&lt;/h3&gt;
&lt;p&gt;  在这里每次迭代都是更新所有状态选择的策略。&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;策略评估(Policy Evaluation)&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;  这里也是迭代的过程，通过不断迭代计算出对应的状态值函数$v_{\pi_k}$。
$$
v_{\pi_k}^{(j+1)}(s) = \sum_{a} \pi_k(a|s) (\sum_{r} p(r | s, a)r+ \gamma \sum_{s&apos;} p(s&apos; | s, a) v_{\pi_k}^{(j)}(s&apos;)), \forall s \in S
$$&lt;/p&gt;
&lt;ol start=&quot;2&quot;&gt;
&lt;li&gt;策略改进(Policy Improvement)
$$
\pi_{k+1}(s) = argmax_{a} \sum \pi(a|s) (\sum_{r} p(r | s, a)r+ \gamma \sum_{s&apos;} p(s&apos; | s, a) v_{\pi_k}(s&apos;)), \forall s \in S
$$&lt;/li&gt;
&lt;/ol&gt;
&lt;h2&gt;3.截断迭代（Truncated Iteration）&lt;/h2&gt;
&lt;p&gt;  当$j=1$时，就会变成值迭代(Value Iteration)，所以值迭代可以看作是策略迭代的一种特殊情况，而在实际中，策略迭代中的策略评估过程不可能进行无限次的策略评估，所以实际应用时也是截断的。&lt;/p&gt;</content:encoded><h:img src="https://pic.linxii.top/blog/rl-learning-4-VPIt-0-100428246.webp"/><enclosure url="https://pic.linxii.top/blog/rl-learning-4-VPIt-0-100428246.webp"/></item><item><title>CS336-1-基础部分</title><link>https://tyuou2.github.io/blog/cs336-1-basic</link><guid isPermaLink="true">https://tyuou2.github.io/blog/cs336-1-basic</guid><description>LLM的基础知识，包括Tokenization、PyTorch资源计算、LLM架构与超参数等内容。</description><pubDate>Thu, 22 Jan 2026 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;import {Collapsible} from &quot;../../../components/advanced/index.js&quot;;&lt;/p&gt;
&lt;h2&gt;1.Tokenization&lt;/h2&gt;
&lt;p&gt;  Tokenization是将文本分割成更小的单位（tokens）的过程，这些单位可以是单词、子词或字符。&lt;/p&gt;
&lt;h3&gt;BPE&lt;/h3&gt;
&lt;p&gt;  BPE（Byte Pair Encoding）是一种基于频率的子词分割方法。它通过迭代地合并最频繁出现的字节对来构建子词表。很古老的算法（94年），但是效果还不错。&lt;/p&gt;
&lt;p&gt;  字符到字节数字直接映射的代码实现&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-python&quot;&gt;def char_to_bytes(text):
    byte_array = text.encode(&apos;utf-8&apos;)
    byte_list = list(byte_array)
    return byte_list
&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;Original text: Hello, World!
Byte representation: [72, 101, 108, 108, 111, 44, 32, 87, 111, 114, 108, 100, 33]
Original text: 你好，世界！
Byte representation: [228, 189, 160, 229, 165, 189, 239, 188, 140, 228, 184, 150, 231, 149, 140, 239, 188, 129]
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;  然后BPE会基于这些字节进行子词的合并。&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;训练阶段：
1. 将所有文本用 char_to_bytes 转换为字节列表
2. 初始化为单个字节的token
3. 重复直到词汇表达到目标大小：
   a. 统计所有相邻token pair的频率
   b. 合并频率最高的pair
   c. 更新整个语料

编码阶段：(这个在应用的时候用到)
1. 用 char_to_bytes 将输入文本转为字节
2. 应用所有训练好的合并规则
3. 返回token IDs和对应的字节对象
&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code&gt;@staticmethod
def char_to_bytes(text):
    &quot;&quot;&quot;将文本转换为UTF-8字节列表&quot;&quot;&quot;
    return list(text.encode(&apos;utf-8&apos;))

@staticmethod
def bytes_to_text(byte_list):
    &quot;&quot;&quot;将字节列表转换回文本&quot;&quot;&quot;
    return bytes(byte_list).decode(&apos;utf-8&apos;, errors=&apos;replace&apos;)

def __init__(self, vocab_size=1000):
    &quot;&quot;&quot;
    初始化BPE编码器

    Args:
        vocab_size: 目标词汇表大小（包括基础的256字节）
    &quot;&quot;&quot;
    # 基础词汇表：256个字节
    self.base_vocab = {bytes([i]): i for i in range(256)}
    self.reverse_base = {i: bytes([i]) for i in range(256)}

    # BPE合并规则
    self.merges = []  # 存储(byte1, byte2)合并规则
    self.vocab = self.base_vocab.copy()  # 完整词汇表
    self.reverse_vocab = self.reverse_base.copy()

    self.vocab_size = vocab_size
    self.next_token_id = 256  # 新token从256开始

def train(self, corpus, verbose=False):
    &quot;&quot;&quot;
    在语料上训练BPE

    Args:
        corpus: 文本列表
        verbose: 是否显示训练过程
    &quot;&quot;&quot;
    if verbose:
        print(f&quot;开始BPE训练，目标词汇表大小: {self.vocab_size}&quot;)
        print(f&quot;基础词汇表大小: 256&quot;)
        print(f&quot;需要学习 {self.vocab_size - 256} 个合并规则&quot;)

    # 将语料转换为字节序列 - 使用类方法
    tokenized_corpus = []
    for text in corpus:
        byte_list = self.char_to_bytes(text)  # 改为self.char_to_bytes
        # 转换为字节对象列表
        tokens = [bytes([b]) for b in byte_list]
        tokenized_corpus.append(tokens)

    # BPE训练：迭代合并
    merge_count = 0
    while len(self.vocab) &amp;#x3C; self.vocab_size:
        # 统计所有相邻pair的频率
        pair_freq = {}

        for tokens in tokenized_corpus:
            for i in range(len(tokens) - 1):
                pair = (tokens[i], tokens[i+1])
                pair_freq[pair] = pair_freq.get(pair, 0) + 1

        if not pair_freq:
            if verbose:
                print(f&quot;没有更多可合并的pair，提前停止&quot;)
            break

        # 找到最常见的pair
        best_pair = max(pair_freq.items(), key=lambda x: x[1])[0]
        self.merges.append(best_pair)

        # 创建新token
        new_token = best_pair[0] + best_pair[1]
        token_id = self.next_token_id
        self.vocab[new_token] = token_id
        self.reverse_vocab[token_id] = new_token
        self.next_token_id += 1

        if verbose and merge_count % 10 == 0:
            print(f&quot;合并 #{merge_count+1}: {best_pair} -&gt; ID:{token_id}&quot;)
            print(f&quot;  新token: {new_token} (长度:{len(new_token)}字节)&quot;)

        # 在语料中应用这个合并
        new_tokenized_corpus = []
        for tokens in tokenized_corpus:
            new_tokens = []
            i = 0
            while i &amp;#x3C; len(tokens):
                if (i &amp;#x3C; len(tokens) - 1 and
                    tokens[i] == best_pair[0] and
                    tokens[i+1] == best_pair[1]):
                    # 合并这一对
                    new_tokens.append(new_token)
                    i += 2  # 跳过已合并的第二个元素
                else:
                    new_tokens.append(tokens[i])
                    i += 1
            new_tokenized_corpus.append(new_tokens)

        tokenized_corpus = new_tokenized_corpus
        merge_count += 1

    if verbose:
        print(f&quot;训练完成！共学习 {len(self.merges)} 个合并规则&quot;)
        print(f&quot;最终词汇表大小: {len(self.vocab)}&quot;)

    return self.merges

def encode(self, text):
    &quot;&quot;&quot;
    使用训练好的BPE编码文本

    Args:
        text: 输入文本

    Returns:
        token_ids: token ID列表
        tokens: 对应的字节对象列表
    &quot;&quot;&quot;
    # 1. 转换为字节列表 - 使用类方法
    byte_list = self.char_to_bytes(text)  # 改为self.char_to_bytes

    # 2. 初始化为单个字节的token
    tokens = [bytes([b]) for b in byte_list]

    # 3. 按顺序应用所有合并规则
    for pair in self.merges:
        new_tokens = []
        i = 0
        while i &amp;#x3C; len(tokens):
            if (i &amp;#x3C; len(tokens) - 1 and
                tokens[i] == pair[0] and
                tokens[i+1] == pair[1]):
                # 合并这一对
                new_token = pair[0] + pair[1]
                new_tokens.append(new_token)
                i += 2
            else:
                new_tokens.append(tokens[i])
                i += 1
        tokens = new_tokens

    # 4. 转换为token ID
    token_ids = [self.vocab[token] for token in tokens]

    return token_ids, tokens

def decode(self, token_ids):
    &quot;&quot;&quot;
    将token IDs解码回文本

    Args:
        token_ids: token ID列表

    Returns:
        text: 解码后的文本
    &quot;&quot;&quot;
    # 1. 将IDs转换回字节
    byte_sequence = b&apos;&apos;
    for token_id in token_ids:
        if token_id in self.reverse_vocab:
            byte_sequence += self.reverse_vocab[token_id]
        else:
            # 如果ID不在词汇表中，使用基础字节
            byte_sequence += bytes([token_id % 256])

    # 2. 解码为文本
    try:
        text = byte_sequence.decode(&apos;utf-8&apos;)
    except UnicodeDecodeError:
        # 如果有无效序列，使用错误处理
        text = byte_sequence.decode(&apos;utf-8&apos;, errors=&apos;replace&apos;)

    return text

def get_vocab_size(self):
    &quot;&quot;&quot;获取当前词汇表大小&quot;&quot;&quot;
    return len(self.vocab)

def print_vocab_sample(self, n=20):
    &quot;&quot;&quot;打印词汇表示例&quot;&quot;&quot;
    print(&quot;\n词汇表示例:&quot;)
    print(&quot;-&quot; * 40)
    print(f&quot;{&apos;Token ID&apos;:&amp;#x3C;10} {&apos;字节长度&apos;:&amp;#x3C;10} {&apos;内容(可打印部分)&apos;}&quot;)
    print(&quot;-&quot; * 40)

    # 按ID排序
    sorted_items = sorted(self.reverse_vocab.items(), key=lambda x: x[0])

    for i, (token_id, token_bytes) in enumerate(sorted_items):
        if i &gt;= n:
            print(f&quot;... 还有 {len(self.vocab) - n} 个token&quot;)
            break

        # 尝试解码为可打印字符
        try:
            content = token_bytes.decode(&apos;utf-8&apos;)
            display = repr(content)
        except:
            display = repr(token_bytes)

        print(f&quot;{token_id:&amp;#x3C;10} {len(token_bytes):&amp;#x3C;10} {display}&quot;)
    print(&quot;-&quot; * 40)
&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code&gt;&amp;#x3C;/Collapsible&gt;

### 疑惑

&amp;#x26;emsp;&amp;#x26;emsp; **Q1: 汉字占用三个字节，其中的单个字节会被当做一个原来256范围内的token吗？**

&amp;#x26;emsp;&amp;#x26;emsp;答案肯定是不会的。

&amp;#x26;emsp;&amp;#x26;emsp;首先，要搞懂字符到字节的映射关系，在UTF-8的编码中，ASCII字符使用单个字节表示，而非ASCII字符使用多个字节表示,比如汉字使用3个字节表示，即一个汉字使用三个数字表示。
具体的映射逻辑如下：
```text
第一个字节的范围         含义
0xxxxxxx (0-127)       ASCII字符（单字节）
110xxxxx (192-223)     双字节字符的起始字节
1110xxxx (224-239)     三字节字符的起始字节
11110xxx (240-247)     四字节字符的起始字节

10xxxxxx (128-191)     多字节字符的后续字节
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;  这里类似哈夫曼编码。&lt;/p&gt;
&lt;p&gt;  大语言模型的tokenizer会有海量的训练数据，BPE会基于这些数据学习到常见的字节组合，从而形成子词token。因此，汉字的三个字节会被BPE合并成一个token，而不是单独作为三个token处理。这里由于是&lt;strong&gt;大量的数据&lt;/strong&gt;，因此也不用担心学不到。&lt;/p&gt;
&lt;h2&gt;2.PyTorch, resource accounting&lt;/h2&gt;
&lt;p&gt;  &lt;strong&gt;全连接神经网络的粗略估算&lt;/strong&gt;：前向传播的计算量是参数量的2倍左右，反向传播的计算量是前向传播的2倍左右，即参数量的4倍左右，总的计算量大约是参数量的6倍左右。还需要乘以输入的长度。因此在一个step中，FLOPs的计算公式大约是：
$$
\text{FLOPs} \approx 6 \times \text{参数量} \times \text{输入长度}
$$&lt;/p&gt;
&lt;h2&gt;3.Architecture of LLMs and hyperparameters&lt;/h2&gt;
&lt;h3&gt;3.1 LLM的组件&lt;/h3&gt;
&lt;p&gt;&lt;img src=&quot;https://pic.linxii.top/blog/cs336-1-basic-1ArchNow.webp&quot; alt=&quot;大语言模型架构的趋势&quot;&gt;&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;pre norm  vs post norm&lt;/strong&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;   现在大多数模型都采用pre-norm结构，因为它在训练深层网络时更稳定，让梯度直接回传到原始输入，不经过 norm的干扰。
&lt;img src=&quot;https://pic.linxii.top/blog/cs336-1-basic-2PreAndPostNorm.webp&quot; alt=&quot;pre-norm vs post-norm&quot;&gt;
2. &lt;strong&gt;LayerNorm vs RMSNorm&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;  LayerNorm是对每个样本的所有特征进行归一化，而RMSNorm只使用均方根（RMS）来归一化，不减去均值,并且不加偏置。RMSNorm计算更简单，更快，节省内存，而且效果基本相当。
$$
\text{RMSNorm}(x) = \frac{x}{\sqrt{\frac{1}{d} \sum_{i=1}^{d} x_i^2 + \epsilon}} \cdot \gamma
$$
$$
\text{LayerNorm}(x) = \frac{x - E[x]} { \sqrt{Var[x] + \epsilon }} \cdot \gamma + \beta
$$
3. &lt;strong&gt;激活函数&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;ReLU
$$
Relu(x) = max(0, x)
$$&lt;/li&gt;
&lt;li&gt;GeLU
$$
GeLU(x) = 0.5x(1 + tanh(\sqrt{2/\pi}(x + 0.044715x^3)))
$$&lt;/li&gt;
&lt;li&gt;GeGLU
$$
GeGLU(x) = x_1 \otimes GeLU(x_2)
$$&lt;/li&gt;
&lt;li&gt;SwiGLU
$$
SwiGLU(x) = x_1 \otimes swish(x_2) = x_1 \otimes \frac{x_2}{1 + e^{-x_2}}
$$
  GeLU是ReLU的平滑版本，然后其中的 $\otimes$ 表示逐元素相乘。GeGLU和SwiGLU是Gated Linear Unit的变体，通过引入门控机制来增强模型的表达能力。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;  现在大多数LLM都使用SwiGLU作为激活函数，它在实践中表现出更好的性能。&lt;/p&gt;
&lt;ol start=&quot;4&quot;&gt;
&lt;li&gt;&lt;strong&gt;Serial vs Parallel&lt;/strong&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;  这里的串行与并行指的是两个线性层的计算逻辑，并非是CUDA加速计算的并行。串行结构是传统的Transformer架构，层与层之间是顺序连接的。而并行结构则将注意力机制和前馈网络并行处理，然后将它们的输出进行融合。当前大模型大多采用串行结构，&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-python&quot;&gt;#串行方式
h = activation(W1 * x + b1)  # 第一层
output = W2 * h + b2         # 第二层（必须等第一层算完）

#并行方式
h1 = W1 * x + b1  # 第一层权重
h2 = W2 * x + b2  # 第二层权重（与第一层同时计算）
output = activation(h1) * h2  # 然后组合
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;  现在LLM采用串行，主要是Serial的稳定性优势 &gt; Parallel的微小延迟优势，&lt;/p&gt;
&lt;ol start=&quot;5&quot;&gt;
&lt;li&gt;&lt;strong&gt;位置编码&lt;/strong&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;ul&gt;
&lt;li&gt;绝对位置编码（Absolute Position Encoding）：为每个位置分配一个唯一的编码，模型通过这些编码来识别序列中的位置关系。常见的方法有正弦-余弦位置编码（Sinusoidal Position Encoding）和可学习的位置编码（Learnable Position Encoding）。&lt;/li&gt;
&lt;li&gt;相对位置编码（Relative Position Encoding）：相对位置编码关注的是序列中元素之间的相对位置关系。&lt;/li&gt;
&lt;li&gt;Rotary Position Embedding (RoPE)：RoPE通过旋转嵌入向量来编码位置关系，使得模型能够更好地捕捉序列中元素之间的相对位置关系。RoPE在实践中表现很好，已经被广泛应用于各种大语言模型中。
&lt;img src=&quot;https://pic.linxii.top/blog/cs336-1-basic-3position.webp&quot; alt=&quot;RoPE例子&quot;&gt;
  RoPE的处理是在高维空间中的旋转是通过切分成多个二维平面来实现的。每个二维平面对应嵌入向量的两个维度，通过在这些二维平面上应用旋转矩阵来实现位置编码。
&lt;img src=&quot;https://pic.linxii.top/blog/cs336-1-basic-4RoPE.webp&quot; alt=&quot;RoPE原理&quot;&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;3.2 超参数&lt;/h3&gt;
&lt;ol&gt;
&lt;li&gt;$d_{ff}$与$d_{model}$&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;  前馈网络的隐藏层维度 $d_{ff}$ 通常设置为模型维度 $d_{model}$ 的4倍，即 $d_{ff} = 4 \times d_{model}$。这是对于Relu等激活函数的常见设置。然后如果使用SwiGLU等激活函数，通常设置为 $d_{ff} = 8/3 \times d_{model}$,这是让参数量相等的数学结果。&lt;/p&gt;
&lt;ol start=&quot;2&quot;&gt;
&lt;li&gt;注意力头数 $h$ 、每个头的维度 $d_k$与模型维度 $d_{model}$&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;  模型维度 $d_{model}$ 通常被划分为多个注意力头，每个头的维度为 $d_k$。头数 $h$ 和每个头的维度 $d_k$ 之间存在一定的关系。&lt;/p&gt;
&lt;p&gt;  定义$Ratio= d_k * h / d_{model}$,通常这个比率为1（当然也有不为1的情况）,即
$$
d_{model} = h \times d_k
$$&lt;/p&gt;
&lt;ol start=&quot;3&quot;&gt;
&lt;li&gt;$d_{model}$与层数 $n_{layers}$&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;  模型维度 $d_{model}$ 和层数 $n_{layers}$ 之间存在一定的经验关系。通常，较大的模型维度需要更多的层数来充分利用其表达能力。
&lt;img src=&quot;https://pic.linxii.top/blog/cs336-1-basic-5model-layer.webp&quot; alt=&quot;模型维度与层数关系&quot;&gt;&lt;/p&gt;
&lt;ol start=&quot;4&quot;&gt;
&lt;li&gt;词汇表大小 $V$&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;  词汇表大小 $V$ 与模型是否为多语言模型有关。对于单语言模型，词汇表大小通常在30,000到50,000之间。而对于多语言模型，词汇表大小可能需要更大，以覆盖更多的语言和字符集，通常在100,000以上。&lt;/p&gt;
&lt;ol start=&quot;5&quot;&gt;
&lt;li&gt;正则化&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;  正则化技术如Dropout和权重衰减在大语言模型中也很重要。Dropout通常设置在0.1到0.3之间，而权重衰减的值通常在1e-5到1e-2之间。而且大多数LLM都不使用Dropout，使用权重衰减。&lt;/p&gt;
&lt;h3&gt;3.3 最新的训练稳定的trick&lt;/h3&gt;
&lt;p&gt;  后续ing&lt;/p&gt;</content:encoded><h:img src="https://pic.linxii.top/blog/cs336-1-basic-0-118544049.webp"/><enclosure url="https://pic.linxii.top/blog/cs336-1-basic-0-118544049.webp"/></item><item><title>RL的原理-3-贝尔曼最优公式</title><link>https://tyuou2.github.io/blog/rl-learning-3-opbellman</link><guid isPermaLink="true">https://tyuou2.github.io/blog/rl-learning-3-opbellman</guid><description>贝尔曼最优公式求解方法以及性质</description><pubDate>Wed, 21 Jan 2026 00:00:00 GMT</pubDate><content:encoded>&lt;h2&gt;1.贝尔曼最优公式&lt;/h2&gt;
&lt;p&gt;  贝尔曼最优公式（Bellman Optimality Equations）描述了在最优策略下，状态值函数和动作值函数之间的关系。&lt;/p&gt;
&lt;p&gt;  对于状态值函数$v_{&lt;em&gt;}(s)$，贝尔曼最优公式表示为：
$$
v_{&lt;/em&gt;}(s) =\max_{\pi} \sum_{a} \pi(a|s) \sum_{r} p(r | s, a)r+ \gamma \sum_{s&apos;} p(s&apos; | s, a) v_{&lt;em&gt;}(s&apos;), \forall s \in S
$$
$$
=max_{pi} \sum_{a} \pi(a|s) q_{&lt;/em&gt;}(s, a), \forall s \in S
$$
  其向量形式为：
$$
\mathbf{v_{&lt;em&gt;}} = \max_{\pi} \left( \mathbf{R^{\pi}} + \gamma \mathbf{P^{\pi}} \mathbf{v_{&lt;/em&gt;}} \right)
$$&lt;/p&gt;
&lt;h2&gt;2. 不动点与Contraction Mapping theorem&lt;/h2&gt;
&lt;h3&gt;2.1 不动点&lt;/h3&gt;
&lt;p&gt;  在数学中，不动点（fixed point）是指在某个映射下保持不变的点。具体来说，给定一个映射$f: X \to X$，如果存在一个点$x^* \in X$，使得$f(x^&lt;em&gt;) = x^&lt;/em&gt;$，那么$x^*$就是$f$的不动点。&lt;/p&gt;
&lt;p&gt;  简单表示就是$f(x)=x$的解。&lt;/p&gt;
&lt;h3&gt;2.2 收缩映射&lt;/h3&gt;
&lt;p&gt;  收缩映射（contraction mapping）是指在一个度量空间中，将任意两个点之间的距离缩小的映射。具体来说，给定一个度量空间$(X, d)$，如果存在一个常数$0 \leq k &amp;#x3C; 1$，使得对于任意的$x, y \in X$，都有$d(f(x), f(y)) \leq k \cdot d(x, y)$，那么$f$就是一个收缩映射。&lt;/p&gt;
&lt;p&gt;  可以举个例子，比如函数$f(x) = \frac{1}{2}x$在实数集上是一个收缩映射，因为对于任意的$x, y \in \mathbb{R}$，都有：
$$
|f(x) - f(y)| = \left|\frac{1}{2}x - \frac{1}{2}y\right| = \frac{1}{2}|x - y|
$$&lt;/p&gt;
&lt;h3&gt;2.3 Contraction Mapping Theorem&lt;/h3&gt;
&lt;p&gt;  收缩映射定理（Contraction Mapping Theorem）指出，在一个完备度量空间中，任何收缩映射都有且只有一个不动点。换句话说，如果$f: X \to X$是一个收缩映射，那么存在唯一的$x^* \in X$，使得$f(x^&lt;em&gt;) = x^&lt;/em&gt;$。&lt;/p&gt;
&lt;h2&gt;3.贝尔曼最优公式求解&lt;/h2&gt;
&lt;p&gt;  由于贝尔曼最优公式是一个收缩映射，根据收缩映射定理，我们可以保证通过反复应用贝尔曼最优公式，最终会收敛到唯一的最优状态值函数$v_{*}(s)$。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;迭代算法&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;  具体来说，我们可以从一个初始的状态值函数$v_0(s)$开始，反复应用贝尔曼最优公式，得到一系列的状态值函数$v_1(s), v_2(s), ...$。随着迭代次数的增加，这些状态值函数将逐渐逼近最优状态值函数$v_{*}(s)$。
$$
v_{k+1}(s) = f(v_k(s)) = \max_{\pi} \left( \mathbf{R^{\pi}} + \gamma \mathbf{P^{\pi}} \mathbf{v_k} \right)
$$&lt;/p&gt;
&lt;h2&gt;4.一些性质&lt;/h2&gt;
&lt;p&gt;  在公式中什么是已知的呢？&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Reward : $$r$$&lt;/li&gt;
&lt;li&gt;System model : $$p(s&apos; | s, a),p(r|s,a) $$&lt;/li&gt;
&lt;li&gt;Discount factor : $$\gamma$$&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;  我们得到的最优策略与设置的reward、discount factor（即公式中的$\gamma$）等有关。&lt;/p&gt;
&lt;p&gt;  当$$\gamma \to 1$$时，智能体更加关注长期奖励；当$$\gamma \to 0$$时，智能体更加关注短期奖励。而且$$\gamma$$也会使得智能体选择最短路径。&lt;/p&gt;</content:encoded><h:img src="https://pic.linxii.top/blog/rl-learning-3-opBellman-0-98149232.webp"/><enclosure url="https://pic.linxii.top/blog/rl-learning-3-opBellman-0-98149232.webp"/></item><item><title>RL的原理-2-贝尔曼公式</title><link>https://tyuou2.github.io/blog/rl-learning-2-bellman</link><guid isPermaLink="true">https://tyuou2.github.io/blog/rl-learning-2-bellman</guid><description>贝尔曼公式</description><pubDate>Tue, 20 Jan 2026 00:00:00 GMT</pubDate><content:encoded>&lt;h2&gt;1.return的重要性&lt;/h2&gt;
&lt;p&gt;  在强化学习中，智能体的目标是最大化其在环境中获得的累积奖励（return 或者 discounted return）。&lt;/p&gt;
&lt;p&gt;  四宫格循环走的情况：&lt;/p&gt;
&lt;p&gt;$$v_{1}=r_{1}+\gamma r_{2}+\gamma^{2} r_{3}+...=r_{1}+\gamma v_{2}$$&lt;/p&gt;
&lt;p&gt;$$v_{2}=r_{2}+\gamma r_{3}+\gamma^{2} r_{4}+...=r_{2}+\gamma v_{3}$$&lt;/p&gt;
&lt;p&gt;$$v_{3}=r_{3}+\gamma r_{4}+\gamma^{2} r_{1}+...=r_{3}+\gamma v_{4}$$&lt;/p&gt;
&lt;p&gt;$$v_{4}=r_{4}+\gamma r_{1}+\gamma^{2} r_{2}+...=r_{4}+\gamma v_{1}$$&lt;/p&gt;
&lt;h1&gt;  上面的公式展示了return的递归性质，可以写成矩阵的形式
$$
\begin{bmatrix} v_{1} \ v_{2} \ v_{3} \ v_{4} \end{bmatrix}&lt;/h1&gt;
&lt;p&gt;\begin{bmatrix} r_{1} \ r_{2} \ r_{3} \ r_{4} \end{bmatrix}
+
\gamma
\begin{bmatrix} 0 &amp;#x26; 1 &amp;#x26; 0 &amp;#x26; 0 \ 0 &amp;#x26; 0 &amp;#x26; 1 &amp;#x26; 0 \ 0 &amp;#x26; 0 &amp;#x26; 0 &amp;#x26; 1 \ 1 &amp;#x26; 0 &amp;#x26; 0 &amp;#x26; 0 \end{bmatrix}
\begin{bmatrix} v_{1} \ v_{2} \ v_{3} \ v_{4} \end{bmatrix}
$$
  上面的矩阵形式可以写成简化的形式，通过下面这种简化的形式可以很方便的求解
$$
\mathbf{v} = \mathbf{r} + \gamma \mathbf{P} \mathbf{v}
$$&lt;/p&gt;
&lt;h2&gt;2.State Value&lt;/h2&gt;
&lt;p&gt;$$
S_t \xrightarrow{A_t} R_{t+1}, S_{t+1} \xrightarrow{A_{t+1}} R_{t+2}, S_{t+2} \xrightarrow{A_{t+2}} R_{t+3} ...
$$
$$
G_t = R_{t+1} + \gamma R_{t+2} + \gamma^2 R_{t+3} + ...
$$&lt;/p&gt;
&lt;p&gt;  状态值函数（State Value Function）表示在给定状态下，智能体在未来能够获得的累积奖励的期望值。状态值函数通常表示为$v_{\pi}(s)$，其中$s$表示状态，$\pi$表示策略。
$$
v_{\pi}(s) = \mathbb{E}_{\pi}[G_t | S_t = s]
$$&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;return 与 state value的关系&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;  return是针对&lt;strong&gt;单个&lt;/strong&gt;trajestory而言的，而state value是对&lt;strong&gt;所有&lt;/strong&gt;trajestory而言的。state value表示在给定状态下，智能体在未来能够获得的累积奖励的&lt;strong&gt;期望值&lt;/strong&gt;。&lt;/p&gt;
&lt;h2&gt;3.贝尔曼公式&lt;/h2&gt;
&lt;h3&gt;3.1 如何推导贝尔曼公式&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;贝尔曼公式：&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;$$ v_{\pi} (s) = \mathbb{E}_{\pi}[G_t | S_t = s]$$&lt;/p&gt;
&lt;p&gt;   $$=\mathbb{E} [R_{t+1} | S_t = s]+ \gamma \mathbb{E}&lt;em&gt;{\pi}[G&lt;/em&gt;{t+1} | S_t = s]$$&lt;/p&gt;
&lt;p&gt;   $$=\sum_{a} \pi(a|s) \sum_{r} p(r|s,a)r + \gamma \sum_{a} \pi(a|s) \sum_{s&apos;} p(s&apos;|s,a)v_{\pi}(s&apos;)$$&lt;/p&gt;
&lt;p&gt;   $$=\sum_{a} \pi(a|s) [\sum_{r}p(r|s,a)r + \gamma \sum_{s&apos;} p(s&apos;|s,a)v_{\pi}(s&apos;) ]$$&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;推导流程：&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;（1）s状态的state value是当前状态的所有return的期望值：&lt;/p&gt;
&lt;p&gt;   $$v_{\pi}(s)=\mathbb{E}[G_{t}|S_{t}=s]$$&lt;/p&gt;
&lt;p&gt;      $$=\mathbb{E}[R_{t+1}+\gamma G_{t+1}|S_{t}=s]$$&lt;/p&gt;
&lt;p&gt;      $$=\mathbb{E}[R_{t+1}|S_{t}=s]+\gamma \mathbb{E}[G_{t+1}|S_{t}=s]$$&lt;/p&gt;
&lt;p&gt;（2）首先看第一项，第一项就是当前状态下，所有可能动作下的奖励（reward）的期望值：&lt;/p&gt;
&lt;p&gt;   $$\mathbb{E}[R_{t+1}|S_{t}=s]=\sum_{a}\pi (a|s) \mathbb{E}[R_{t+1}|S_{t}=s,A_{t}=a]$$&lt;/p&gt;
&lt;p&gt;           $$=\sum_{a}\pi (a|s) \sum_{r} p(r|s,a)r$$&lt;/p&gt;
&lt;p&gt;  其中，$\pi(a|s)$表示在状态$s$下选择动作$a$的概率，$p(r|s,a)$表示在状态$s$下选择动作$a$后获得奖励$r$的概率。&lt;/p&gt;
&lt;p&gt;(3)再看第二项，第二项是当前状态下，所有可能动作下，所有可能转移到的下一个状态的state value的期望值：&lt;/p&gt;
&lt;p&gt;   $$\mathbb{E}[G_{t+1}|S_{t}=s]=\sum_{s&apos;} \mathbb{E}[G_{t+1}|S_t=s,S_{t+1}=s&apos;]p(s&apos;|s)$$&lt;/p&gt;
&lt;p&gt;           $$=\sum_{s&apos;} \mathbb{E}[G_{t+1}|S_{t+1}=s&apos;]p(s&apos;|s)$$&lt;/p&gt;
&lt;p&gt;           $$=\sum_{s&apos;} v_{\pi}(s&apos;)p(s&apos;|s)$$&lt;/p&gt;
&lt;p&gt;           $$=\sum_{s&apos;}v_{\pi}(s&apos;) \sum_{a}p(s&apos;|s,a)  \pi(a|s)$$&lt;/p&gt;
&lt;h3&gt;3.2贝尔曼公式的矩阵与向量形式&lt;/h3&gt;
&lt;p&gt;  首先把第一项中的部分组合一下，这部分就是当前状态下执行不同的Action得到的reward的期望值：
$$
r_{\pi}(s) = \sum_{a} \pi(a|s) \sum_{r} p(r|s,a)r
$$&lt;/p&gt;
&lt;p&gt;  然后把第二项中的部分组合一下，这部分就是从$$s$$到$$s&apos;$$的概率：
$$
P_{\pi}(s,s&apos;) = \sum_{a} \pi(a|s) p(s&apos;|s,a)
$$&lt;/p&gt;
&lt;p&gt;  于是贝尔曼公式可以写成下面的形式：
$$
v_{\pi}(s) = r_{\pi}(s) + \gamma \sum_{s&apos;} P_{\pi}(s&apos;|s) v_{\pi}(s&apos;)
$$&lt;/p&gt;
&lt;p&gt;  上面的公式可以写成矩阵与向量的形式：
$$
\mathbf{v}&lt;em&gt;{\pi} = \mathbf{r}&lt;/em&gt;{\pi} + \gamma \mathbf{P}&lt;em&gt;{\pi} \mathbf{v}&lt;/em&gt;{\pi}
$$&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;求解&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;  根据矩阵的形式，可以使用逆矩阵进行求解，不过实际应用中并不使用这种解法，因为状态空间通常非常大，计算逆矩阵的开销很大。通常使用迭代方法来近似求解。
$$
v_{k+1} \rightarrow r_{\pi} + \gamma P_{\pi} v_{k}
$$
  当$k \rightarrow \infty$时，$v_{k}$会收敛到$v_{\pi}$。&lt;/p&gt;
&lt;h2&gt;4.Action Value&lt;/h2&gt;
&lt;p&gt;$$q_{\pi}(s,a) = \mathbb{E}&lt;em&gt;{\pi}[G_t | S_t = s, A_t = a]$$
$$= \sum&lt;/em&gt;{r} p(r|s,a) r + \gamma \sum_{s&apos;} p(s&apos;|s,a) v_{\pi}(s&apos;)$$&lt;/p&gt;
&lt;p&gt;  Action Value 与 State Value的关系：
$$
v_{\pi}(s) = \sum_{a} \pi(a|s) q_{\pi}(s,a)
$$&lt;/p&gt;</content:encoded><h:img src="https://pic.linxii.top/blog/rl-learning-2-bellman-0-101586094.webp"/><enclosure url="https://pic.linxii.top/blog/rl-learning-2-bellman-0-101586094.webp"/></item><item><title>RL的原理-1-基本概念</title><link>https://tyuou2.github.io/blog/rl-learning-1-basic-concept</link><guid isPermaLink="true">https://tyuou2.github.io/blog/rl-learning-1-basic-concept</guid><description>强化学习（Reinforcement Learning, RL）中的一些基本概念，包括状态、动作、奖励、策略等。</description><pubDate>Sat, 17 Jan 2026 00:00:00 GMT</pubDate><content:encoded>&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;状态（State）&lt;/strong&gt;: 环境在某一时刻的描述，通常用一个向量表示。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;状态空间（State Space）&lt;/strong&gt;: 所有可能状态的集合，表示为$S={s_{i}}$。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;动作（Action）&lt;/strong&gt;: 在每个状态下，智能体可以执行的操作。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;动作空间（Action Space）&lt;/strong&gt;: 所有可能动作的集合，表示为$A(s_{i})={a_{j}}$。动作空间与状态是相关的，不同的状态可能有不同的可用动作。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;状态转移（state transition）&lt;/strong&gt;: 智能体执行某个动作，环境状态发生变化的过程。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;forbidden area&lt;/strong&gt;: 智能体进入这个区域后通常会导致负奖励或任务失败。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Tabular形式&lt;/strong&gt;: 当状态空间和动作空间较小时，可以使用表格来存储状态-动作值函数（Q值）。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;State transition Probability&lt;/strong&gt;: 状态转移概率，表示在给定当前状态和动作的情况下，转移到下一个状态的概率，通常表示为$P(s&apos;|s,a)$。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;策略（Policy）&lt;/strong&gt;: 智能体在每个状态下选择动作的规则，通常表示为$\pi(a|s)$，即在状态$s$下选择动作$a$的概率。（在实际coding时采用数组/矩阵进行存储）&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;奖励（Reward）&lt;/strong&gt;: 智能体在执行动作后从环境中获得的反馈信号，通常表示为$r(s,a)$，即在状态$s$下执行动作$a$后获得的奖励。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Trajestory&lt;/strong&gt;: 智能体在环境中经历的一系列状态、动作和奖励的序列，通常表示为$(s_0, a_0, r_1, s_1, a_1, r_2, ...)$。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;回报(return)&lt;/strong&gt;: 智能体在某一时刻开始，未来所有奖励的累积和，通常表示为$G_t = r_{t+1} + r_{t+2} + ... + r_{T}$，其中$T$是终止时间步。return是针对整个trajestory而言的。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;折扣回报(discounted return)&lt;/strong&gt;:考虑未来奖励的时间价值，通常表示为$G_t = r_{t+1} + \gamma r_{t+2} + \gamma^2 r_{t+3} + ...$，其中$\gamma$是折扣因子，取值范围为$[0,1]$。当$\gamma$接近1时，智能体更关注长期奖励；当$\gamma$接近0时，智能体更关注短期奖励。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;episode&lt;/strong&gt;: 智能体从初始状态开始，经过一系列状态转移，直到达到终止状态的过程称为一个episode。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;马尔可夫性质（markov property）&lt;/strong&gt;: 表示当前状态包含了所有必要的信息，未来状态的转移只依赖于当前状态和动作，而与过去的状态和动作无关。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;MDP(Markov Decision Process)&lt;/strong&gt;:三个集合（状态空间S，动作空间A，奖励函数R）和两个Probability Distribution(状态的probability distribution和奖励的probability distribution)以及策略π（a|s）,再加上memoryless property（与历史无关的特性）。&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;</content:encoded><h:img src="https://pic.linxii.top/blog/rl-learning-1-basic-concept-0-133464082.webp"/><enclosure url="https://pic.linxii.top/blog/rl-learning-1-basic-concept-0-133464082.webp"/></item><item><title>ML-DL-RL的信息论基础</title><link>https://tyuou2.github.io/blog/base-learning-1-math-it</link><guid isPermaLink="true">https://tyuou2.github.io/blog/base-learning-1-math-it</guid><description>信息论的基础知识，包括自信息、信息熵、最大熵原理、KL散度、交叉熵、联合熵、条件熵和互信息等内容。 </description><pubDate>Fri, 16 Jan 2026 00:00:00 GMT</pubDate><content:encoded>&lt;h2&gt;1.信息论基础概念&lt;/h2&gt;
&lt;p&gt;&lt;strong&gt;自信息&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;  自信息（Self-Information）可以理解为时间发生之前我们对事件发生的不确定性的度量。对于一个离散随机变量$X$，其取值$x_i$的自信息$I(x_i)$定义为：$$ I(x_i) = -\log_2 P(x_i) $$
  其中，$P(x_i)$是随机变量$X$取值$x_i$的概率。自信息越大，表示事件发生的概率越小，即事件越不确定。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;信息熵&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;  信息熵（Entropy）也是用于衡量随机变量的不确定性。对于一个离散随机变量$X$，其熵$H(X)$定义为：
$$ H(X) = -\sum_{i} P(x_i) \log_2 P(x_i) $$
  熵越大，表示随机变量的不确定性越高。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;最大熵原理&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;  最大熵原理就是指在所有满足已知约束条件的概率分布中，选择熵最大的那个分布。这个原理反映了在缺乏额外信息的情况下，我们应该选择最不确定的分布，&lt;strong&gt;最不确定的分布就是均匀分布，均匀分布要比非均匀分布的熵大&lt;/strong&gt;，概率均匀分布，预测风险最小。&lt;/p&gt;
&lt;p&gt;|&lt;strong&gt;约束条件&lt;/strong&gt;|&lt;strong&gt;适用变量的类型&lt;/strong&gt;|&lt;strong&gt;最大熵分布&lt;/strong&gt;|
|---|---|---|
|无约束|离散变量|均匀分布|
|无约束|连续变量|均匀分布|
|已知均值和方差|连续变量|正态分布|
|非负取值，给定均值|连续变量|指数分布|
|非负整数取值，给定均值|离散变量|泊松分布|&lt;/p&gt;
&lt;p&gt;  &lt;strong&gt;不要把鸡蛋放在一个笼子里！&lt;/strong&gt; 感觉高中地理中学的多个原材料厂家就有这个想法，防止某个地方出问题导致原材料断供。好像投资也是这个道理。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;KL散度&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;  KL散度（Kullback-Leibler Divergence）用于衡量两个概率分布之间的差异。对于两个离散概率分布$P$和$Q$，其KL散度$D_{KL}(P||Q)$定义为：
$$ D_{KL}(P||Q) = \sum_{i} P(x_i) \log_2 \frac{P(x_i)}{Q(x_i)} $$&lt;/p&gt;
&lt;p&gt;  根据公式可以看出来，$$D_{KL}(P||Q) =D_{KL}(Q||P)$$&lt;strong&gt;并不一定存在&lt;/strong&gt;，KL散度不是一个对称的度量。然后，KL散度越大，表示两个分布之间的差异越大，当$P=Q$时，KL散度为0。而且，KL散度总是非负的，即$D_{KL}(P||Q) \geq 0$，数学证明看了但是不写了，哈哈哈。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;交叉熵&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;  交叉熵（Cross-Entropy）用于衡量两个概率分布之间的差异。对于两个离散概率分布$P$和$Q$，其交叉熵$H(P, Q)$定义为：
$$ H(P, Q) = -\sum_{i} P(x_i) \log_2 Q(x_i) $$&lt;/p&gt;
&lt;p&gt;  当概率分布$P(x)$确定时，信息熵$H(P)$也是确定的，因此交叉熵$H(P, Q)$与KL散度$D_{KL}(P||Q)$之间存在以下关系：
$$ H(P, Q) = H(P) + D_{KL}(P||Q) $$&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;联合熵&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;  联合熵（Joint Entropy）用于衡量多个随机变量的联合不确定性。对于两个离散随机变量$X$和$Y$，其联合熵$H(X, Y)$定义为：
$$ H(X, Y) = -\sum_{i} \sum_{j} P(x_i, y_j) \log_2 P(x_i, y_j) $$
  联合熵表示随机变量$X$和$Y$的联合不确定性。联合熵满足以下性质：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;$H(X, Y) \leq H(X) + H(Y)$，等号成立当且仅当$X$和$Y$独立。&lt;/li&gt;
&lt;li&gt;$H(X, Y) = H(X) + H(Y|X) = H(Y) + H(X|Y)$，其中$H(Y|X)$和$H(X|Y)$分别是条件熵。&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;&lt;strong&gt;条件熵&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;  条件熵（Conditional Entropy）用于衡量在已知另一个随机变量的情况下，随机变量的不确定性。对于两个离散随机变量$X$和$Y$，其条件熵$H(X|Y)$定义为：
$$ H(X|Y) = -\sum_{j} P(y_j) \sum_{i} P(x_i|y_j) \log_2 P(x_i|y_j) $$&lt;/p&gt;
&lt;p&gt;  条件熵表示在已知随机变量$Y$的情况下，随机变量$X$的不确定性。条件熵越大，表示在已知$Y$的情况下，$X$的不确定性越高。条件熵满足以下性质：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;$H(X|Y)=H(X,Y)-H(Y)$,此次的$H(X,Y)$是联合熵。&lt;/li&gt;
&lt;li&gt;$H(X|Y) \leq H(X)$，等号成立当且仅当$X$和$Y$独立。&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;&lt;strong&gt;互信息&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;  互信息（Mutual Information）用于衡量两个随机变量之间的依赖关系。对于两个离散随机变量$X$和$Y$，其互信息$I(X; Y)$定义为：
$$ I(X; Y) = \sum_{i} \sum_{j} P(x_i, y_j) \log_2 \frac{P(x_i, y_j)}{P(x_i) P(y_j)} $$&lt;/p&gt;
&lt;p&gt;  互信息表示随机变量$X$和$Y$之间的依赖关系。互信息越大，表示两个随机变量之间的依赖关系越强。当$X$和$Y$独立时，互信息为0。互信息满足以下性质：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;$I(X; Y) = H(X) - H(X|Y) = H(Y) - H(Y|X)$。&lt;/li&gt;
&lt;li&gt;$I(X; Y) \geq 0$，等号成立当且仅当$X$和$Y$独立。&lt;/li&gt;
&lt;/ol&gt;</content:encoded><h:img src="https://pic.linxii.top/blog/base-learning-1-math-IT-0-128657161.webp"/><enclosure url="https://pic.linxii.top/blog/base-learning-1-math-IT-0-128657161.webp"/></item><item><title>pytorch coding笔记</title><link>https://tyuou2.github.io/blog/base-learning-6-pytorch</link><guid isPermaLink="true">https://tyuou2.github.io/blog/base-learning-6-pytorch</guid><description>pytorch的基础使用，包括模型创建、数据加载、训练与评估等内容。</description><pubDate>Fri, 16 Jan 2026 00:00:00 GMT</pubDate><content:encoded>&lt;h2&gt;1.入门&lt;/h2&gt;
&lt;h3&gt;1.1 数据加载到模型训练与评估example&lt;/h3&gt;
&lt;h4&gt;1.1.1 模型创建&lt;/h4&gt;
&lt;p&gt;  一般来说，创建一个模型，首先继承&lt;code&gt;nn.Module&lt;/code&gt;类，然后在&lt;code&gt;__init__&lt;/code&gt;方法中定义网络层，在&lt;code&gt;forward&lt;/code&gt;方法中定义前向传播逻辑。&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-python&quot;&gt;# model.py

import torch
import torch.nn as nn

class SimpleNN(nn.Module):
    def __init__(self):
        super(SimpleNN, self).__init__()
        self.fc1 = nn.Linear(28 * 28, 128)
        self.fc2 = nn.Linear(128, 10)

    def forward(self, x):
        x = torch.flatten(x, 1)
        x = torch.relu(self.fc1(x))
        x = self.fc2(x)
        return x
&lt;/code&gt;&lt;/pre&gt;
&lt;h4&gt;1.1.2 数据加载&lt;/h4&gt;
&lt;p&gt;  使用&lt;code&gt;Dataset&lt;/code&gt;来定义自定义数据集，并使用&lt;code&gt;DataLoader&lt;/code&gt;来加载数据，其中的&lt;code&gt;transform&lt;/code&gt;用于对图像进行预处理。总的来说是&lt;code&gt;Dataloader&lt;/code&gt;封装了&lt;code&gt;Dataset&lt;/code&gt;，提供了批量加载、打乱数据等功能,同时&lt;code&gt;Dataset&lt;/code&gt;负责数据的读取并利用&lt;code&gt;transform&lt;/code&gt;进行预处理。&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-python&quot;&gt;# data_loader.py
from torch.utils.data import Dataset, DataLoader
from torchvision import transforms
from PIL import Image
import os


class MyCustomDataset(Dataset):
    def __init__(self, image_dir, labels_file, transform=None):
        self.image_dir = image_dir
        self.transform = transform
        # 假设标签文件格式：image_name label
        with open(labels_file, &apos;r&apos;) as f:
            self.labels = [line.strip().split() for line in f.readlines()]

    def __len__(self):
        return len(self.labels)

    def __getitem__(self, idx):
        img_name, label = self.labels[idx]
        img_path = os.path.join(self.image_dir, img_name)
        image = Image.open(img_path).convert(&quot;RGB&quot;)
        label = int(label)

        if self.transform:
            image = self.transform(image)

        return image, label


def get_data_loaders(batch_size=64):
    transform = transforms.Compose([
        transforms.Resize((28, 28)),
        transforms.Grayscale(num_output_channels=1),
        transforms.ToTensor(),
        transforms.Normalize((0.5,), (0.5,))
    ])

    # 训练数据集加载
    train_dataset = MyCustomDataset(
        image_dir=&apos;./data/train&apos;,
        labels_file=&apos;./data/train_labels.txt&apos;,
        transform=transform
    )

    # 测试数据集加载
    test_dataset = MyCustomDataset(
        image_dir=&apos;./data/test&apos;,
        labels_file=&apos;./data/test_labels.txt&apos;,
        transform=transform
    )

    train_loader = DataLoader(train_dataset, batch_size=batch_size, shuffle=True)
    test_loader = DataLoader(test_dataset, batch_size=batch_size, shuffle=False)

    return train_loader, test_loader
&lt;/code&gt;&lt;/pre&gt;
&lt;h4&gt;1.1.3 训练&lt;/h4&gt;
&lt;p&gt;  训练过程包括前向传播、计算损失、反向传播和优化模型参数，保存训练好的模型。然后超参数如学习率、批量大小、训练轮数等可以通过命令行参数进行配置，方便调整。&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-python&quot;&gt;# train.py
import torch
import torch.nn as nn
import torch.optim as optim
import argparse
from model import SimpleNN
from data_loader import get_data_loaders


def train(args):

    # 配置设备
    device = torch.device(&quot;cuda&quot; if torch.cuda.is_available() else &quot;cpu&quot;)

    # 获取数据加载器，使用命令行参数 batch_size
    train_loader, _ = get_data_loaders(batch_size=args.batch_size)

    model = SimpleNN().to(device)
    criterion = nn.CrossEntropyLoss()
    # 使用命令行参数 lr
    optimizer = optim.AdamW(model.parameters(), lr=args.lr)

    model.train()
    # 使用命令行参数 epochs
    for epoch in range(args.epochs):
        for batch_idx, (data, target) in enumerate(train_loader):
            data, target = data.to(device), target.to(device)
            optimizer.zero_grad()
            output = model(data)
            loss = criterion(output, target)
            loss.backward()
            optimizer.step()

            if batch_idx % 100 == 0:
                print(f&quot;Epoch {epoch + 1}/{args.epochs}, Batch {batch_idx}, Loss: {loss.item():.4f}&quot;)

    # 使用命令行参数 save_path
    torch.save(model.state_dict(), args.save_path)
    print(f&quot;Model saved to `{args.save_path}`&quot;)


if __name__ == &quot;__main__&quot;:
    parser = argparse.ArgumentParser(description=&quot;PyTorch MNIST Training&quot;)

    # 定义超参数
    parser.add_argument(&quot;--batch-size&quot;, type=int, default=64, help=&quot;input batch size for training (default: 64)&quot;)
    parser.add_argument(&quot;--epochs&quot;, type=int, default=2, help=&quot;number of epochs to train (default: 2)&quot;)
    parser.add_argument(&quot;--lr&quot;, type=float, default=0.001, help=&quot;learning rate (default: 0.001)&quot;)
    parser.add_argument(&quot;--save-path&quot;, type=str, default=&quot;simple_nn.pth&quot;,
                        help=&quot;path to save the model (default: simple_nn.pth)&quot;)

    args = parser.parse_args()
    train(args)
&lt;/code&gt;&lt;/pre&gt;
&lt;h4&gt;1.1.4 评估&lt;/h4&gt;
&lt;p&gt;  评估模型的性能，计算在测试集上的准确率，并加载之前保存的模型权重进行评估，此时不需要计算梯度，因此使用&lt;code&gt;torch.no_grad()&lt;/code&gt;来节省内存和计算资源。&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-python&quot;&gt;# evaluate.py

import torch
from model import SimpleNN
from data_loader import get_data_loaders


def evaluate():
    device = torch.device(&quot;cuda&quot; if torch.cuda.is_available() else &quot;cpu&quot;)
    _, test_loader = get_data_loaders()

    model = SimpleNN().to(device)
    try:
        model.load_state_dict(torch.load(&quot;simple_nn.pth&quot;, weights_only=True))
        model.eval()
    except FileNotFoundError:
        print(&quot;Error: `simple_nn.pth` not found. Please run `train.py` first.&quot;)
        return

    correct = 0
    with torch.no_grad():
        for data, target in test_loader:
            data, target = data.to(device), target.to(device)
            output = model(data)
            pred = output.argmax(dim=1, keepdim=True)
            correct += pred.eq(target.view_as(pred)).sum().item()

    print(f&quot;Test Accuracy: {100. * correct / len(test_loader.dataset):.2f}%&quot;)


if __name__ == &quot;__main__&quot;:
    evaluate()
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;1.2 自定义层&lt;/h3&gt;
&lt;p&gt;  在&lt;code&gt;__init__&lt;/code&gt;方法中定义层,在pytorch中提供了很多常用的层，如&lt;code&gt;nn.Linear&lt;/code&gt;、&lt;code&gt;nn.Conv2d&lt;/code&gt;、&lt;code&gt;nn.ReLU&lt;/code&gt;等。&lt;/p&gt;
&lt;p&gt;  常用的层包括：&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-python&quot;&gt;# 1. 线性层 (Fully Connected Layer)
# in_features: 输入神经元个数, out_features: 输出神经元个数
fc = nn.Linear(in_features=784, out_features=128)

# 2. 卷积层 (Convolutional Layer)
# 参数: 输入通道数, 输出通道数, 卷积核尺寸, 步长, 填充
conv = nn.Conv2d(in_channels=3, out_channels=16, kernel_size=3, stride=1, padding=1)

# 3. 池化层 (Pooling Layer)
max_pool = nn.MaxPool2d(kernel_size=2, stride=2)
avg_pool = nn.AvgPool2d(kernel_size=2, stride=2)

# 4. 激活函数 (Activation Functions)
relu = nn.ReLU()
sigmoid = nn.Sigmoid()
tanh = nn.Tanh()

# 5. 批归一化层 (Batch Normalization)
bn1d = nn.BatchNorm1d(num_features=128)
bn2d = nn.BatchNorm2d(num_features=16)

# 6. 丢弃层 (Dropout Layer)
dropout = nn.Dropout(p=0.5)

# 7. 循环神经网络层 (RNN/LSTM/GRU)
rnn = nn.RNN(input_size=10, hidden_size=20, num_layers=2)
lstm = nn.LSTM(input_size=10, hidden_size=20, num_layers=2)
gru = nn.GRU(input_size=10, hidden_size=20, num_layers=2)

# 8. Transformer 与 注意力机制
transformer = nn.Transformer(d_model=512, nhead=8)
attention = nn.MultiheadAttention(embed_dim=512, num_heads=8)

# 9. Sequential 容器: 用于按顺序包装多个层
model = nn.Sequential(
    nn.Linear(784, 128),
    nn.ReLU(),
    nn.Linear(128, 10)
)
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;1.3 Dataset与DataLoader&lt;/h3&gt;
&lt;p&gt;  Dataset是一个抽象类，在实现自己的数据集类的时候需要继承它并实现__init__、__len__和__getitem__方法(这里可以看最开始的例子)&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-python&quot;&gt;class CustomImageDataset(Dataset):
    def __init__(self, annotations_file, img_dir, transform=None, target_transform=None):
        self.img_labels = pd.read_csv(annotations_file)
        self.img_dir = img_dir
        self.transform = transform
        self.target_transform = target_transform

    def __len__(self):
        return len(self.img_labels)

    def __getitem__(self, idx):
        img_path = os.path.join(self.img_dir, self.img_labels.iloc[idx, 0])
        image = read_image(img_path)
        label = self.img_labels.iloc[idx, 1]
        if self.transform:
            image = self.transform(image)
        if self.target_transform:
            label = self.target_transform(label)
        return image, label
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;  DataLoader是一个迭代器，用于批量加载数据集，并支持多线程加载、数据打乱等功能,其中有很多参数&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-python&quot;&gt;train_loader = DataLoader(
    dataset=train_dataset,  # 数据集对象
    batch_size=64,         # 每个批次的样本数量
    shuffle=True,          # 是否在每个epoch开始时打乱数据
    num_workers=4,        # 用于数据加载的子进程数量
    pin_memory=True       # 是否将数据加载到CUDA固定内存中
)
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;1.4 损失函数与优化器&lt;/h3&gt;
&lt;h4&gt;1.4.1 损失函数&lt;/h4&gt;
&lt;p&gt;  损失函数用于衡量模型预测值与真实值之间的差距，常用的损失函数有：&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-python&quot;&gt;# 1. 均方误差损失 (Mean Squared Error Loss)
mse_loss = nn.MSELoss()
# 2. 交叉熵损失 (Cross Entropy Loss)
cross_entropy_loss = nn.CrossEntropyLoss()
# 3. 二元交叉熵损失 (Binary Cross Entropy Loss)
bce_loss = nn.BCELoss()
# 4. 平滑L1损失 (Smooth L1 Loss)
smooth_l1_loss = nn.SmoothL1Loss()
# 5. KL散度损失 (Kullback-Leibler Divergence Loss)
kl_div_loss = nn.KLDivLoss()
&lt;/code&gt;&lt;/pre&gt;
&lt;h4&gt;1.4.2 优化器&lt;/h4&gt;
&lt;p&gt;  优化器用于更新模型参数以最小化损失函数，常用的优化器有：&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-python&quot;&gt;# 1. 随机梯度下降 (Stochastic Gradient Descent)
sgd_optimizer = optim.SGD(model.parameters(), lr=0.01, momentum=0.9)
# 2. Adam 优化器
adam_optimizer = optim.Adam(model.parameters(), lr=0.001)
# 3. RMSprop 优化器
rmsprop_optimizer = optim.RMSprop(model.parameters(), lr=0.001)
# 4. AdamW 优化器
adamw_optimizer = optim.AdamW(model.parameters(), lr=0.001)
&lt;/code&gt;&lt;/pre&gt;</content:encoded><h:img src="https://pic.linxii.top/blog/base-learning-6-pytorch-0-107598280.webp"/><enclosure url="https://pic.linxii.top/blog/base-learning-6-pytorch-0-107598280.webp"/></item><item><title>ML-DL-RL的概率论基础</title><link>https://tyuou2.github.io/blog/base-learning-1-math-pt</link><guid isPermaLink="true">https://tyuou2.github.io/blog/base-learning-1-math-pt</guid><description>概率论的基础知识，包括基本概念、概率分布（离散和连续）、联合概率分布、边缘概率、条件概率与独立性、贝叶斯定理、期望、方差和协方差等内容。 </description><pubDate>Wed, 14 Jan 2026 00:00:00 GMT</pubDate><content:encoded>&lt;h2&gt;1.基础概念&lt;/h2&gt;
&lt;p&gt;  概率：描述事件发生的不确定性，取值范围为0到1。&lt;/p&gt;
&lt;p&gt;  随机变量：表示随机现象结果的变量，可以是离散或连续的。例如：掷骰子的结果是一个离散随机变量，而测量温度则是一个连续随机变量。&lt;/p&gt;
&lt;h2&gt;2.概率分布&lt;/h2&gt;
&lt;p&gt;  概率分布描述随机变量可能取值的范围及其对应的概率。&lt;/p&gt;
&lt;h3&gt;2.1.离散概率分布&lt;/h3&gt;
&lt;p&gt;  离散概率分布适用于离散随机变量，如掷骰子。&lt;/p&gt;
&lt;p&gt;  &lt;strong&gt;需满足的条件包括：&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;A.P的定义域为随机变量的所有可能取值；&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;B.每个取值的概率有$ 0\le P(x) \le 1$；&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;C.所有取值的概率和为1,即$ P(X=x_{1})+...+P(X=x_{n})=1$。&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;  &lt;strong&gt;常见的离散概率分布包括：&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;伯努利分布(Bernoulli Distribution)：描述只有两种结果（成功或失败）的实验。&lt;/li&gt;
&lt;li&gt;二项分布(Binomial Distribution)：描述在n次独立的伯努利试验中成功的次数。&lt;/li&gt;
&lt;li&gt;泊松分布(Poisson Distribution)：描述在固定时间或空间内事件发生的次数。&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;2.2.连续概率分布&lt;/h3&gt;
&lt;p&gt;  连续概率分布适用于连续随机变量，如测量温度。&lt;/p&gt;
&lt;p&gt;  &lt;strong&gt;需满足的条件包括：&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;A.概率密度函数(PDF)的定义域为随机变量的所有可能取值范围；&lt;/li&gt;
&lt;li&gt;B.概率密度函数的值有$ f(x) \ge 0$；&lt;/li&gt;
&lt;li&gt;C.概率密度函数在整个定义域上的积分为1,即$ \int_{-\infty}^{+\infty} f(x) dx = 1$。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;  &lt;strong&gt;常见的连续概率分布包括：&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;正态分布(Normal Distribution)：也称为高斯分布，描述数据围绕均值对称分布的情况。&lt;/li&gt;
&lt;li&gt;指数分布(Exponential Distribution)：描述事件发生的时间间隔。&lt;/li&gt;
&lt;li&gt;均匀分布(Uniform Distribution)：描述在某个区间内所有值出现的概率相等。&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;2.3联合概率分布&lt;/h3&gt;
&lt;p&gt;  联合概率分布描述多个随机变量同时发生的概率。例如，对于两个随机变量X和Y的联合概率分布$P(X=x,Y=y)$，表示X和Y同时取某个值的概率。&lt;/p&gt;
&lt;h2&gt;3.边缘概率&lt;/h2&gt;
&lt;p&gt;  边缘概率是指在联合概率分布中，通过对其他变量进行求和或积分，得到某个变量的概率分布。例如，对于两个随机变量X和Y的联合概率分布P(X,Y)，X的边缘概率P(X)可以通过对Y进行求和或积分得到：&lt;/p&gt;
&lt;p&gt;$$ P(X=x) = \sum_{y} P(X=x,Y=y) \quad \text{(离散情况)} $$&lt;/p&gt;
&lt;p&gt;$$ P(X=x) = \int P(X=x,Y=y) dy \quad \text{(连续情况)} $$&lt;/p&gt;
&lt;h2&gt;4.条件概率与独立性&lt;/h2&gt;
&lt;h3&gt;4.1条件概率&lt;/h3&gt;
&lt;p&gt;  条件概率描述在已知某个事件发生的情况下，另一个事件发生的概率。条件概率的定义如下：&lt;/p&gt;
&lt;p&gt;$$ P(A|B) = \frac{P(A \cap B)}{P(B)} $$&lt;/p&gt;
&lt;p&gt;  其中，$P(A|B)$表示在事件B发生的条件下事件A发生的概率，$P(A \cap B)$表示事件A和事件B同时发生的概率，$P(B)$表示事件B发生的概率。&lt;/p&gt;
&lt;p&gt;  在ML中表示为
$$ P(Y=y|X=x) = \frac{P(X=x,Y=y)}{P(X=x)} $$&lt;/p&gt;
&lt;h3&gt;4.2独立性&lt;/h3&gt;
&lt;p&gt;  独立性：如果两个事件A和B独立，则有$P(A|B)=P(A)$，即事件B的发生不影响事件A的概率。若果$P(A \cap B)=P(A)P(B)$，则A和B独立，反之也是成立的，这是充要条件。&lt;/p&gt;
&lt;p&gt;  条件独立性：如果在给定第三个事件C的条件下，事件A和B独立，则称A和B在条件C下条件独立。即$P(A|B,C)=P(A|C)$。&lt;/p&gt;
&lt;h2&gt;5.贝叶斯定理&lt;/h2&gt;
&lt;p&gt;  贝叶斯定理是根据已知的条件概率来计算另一个条件概率。贝叶斯定理的公式如下：&lt;/p&gt;
&lt;p&gt;$$ P(A|B) = \frac{P(B|A)P(A)}{P(B)} $$&lt;/p&gt;
&lt;p&gt;  在机器学习中，贝叶斯定理常用于分类任务，如朴素贝叶斯分类器。通过计算后验概率$P(Y|X)$，可以根据输入特征X预测类别Y。&lt;/p&gt;
&lt;h2&gt;6.期望、方差和协方差&lt;/h2&gt;
&lt;h3&gt;6.1期望&lt;/h3&gt;
&lt;p&gt;  期望（数学期望）是随机变量取值的加权平均值。对于离散随机变量X，其期望定义为：&lt;/p&gt;
&lt;p&gt;$$ E[X] = \sum_{x} x P(X=x) $$&lt;/p&gt;
&lt;p&gt;  对于连续随机变量X，其期望定义为：&lt;/p&gt;
&lt;p&gt;$$ E[X] = \int_{-\infty}^{+\infty} x p(x) dx $$&lt;/p&gt;
&lt;h3&gt;6.2方差&lt;/h3&gt;
&lt;p&gt;  方差衡量随机变量取值的离散程度。对于离散随机变量X，其方差定义为：
$$ Var(X) = E[(X - E[X])^2] = \sum_{x} (x - E[X])^2 P(X=x) $$&lt;/p&gt;
&lt;p&gt;  对于连续随机变量X，其方差定义为：
$$ Var(X) = E[(X - E[X])^2] = \int_{-\infty}^{+\infty} (x - E[X])^2 p(x) dx $$&lt;/p&gt;
&lt;p&gt;  标准差即为方差的平方根，表示随机变量取值的平均偏离程度。&lt;/p&gt;
&lt;h3&gt;6.3协方差&lt;/h3&gt;
&lt;p&gt;  协方差衡量两个随机变量之间的线性关系。对于随机变量X和Y，其协方差定义为：&lt;/p&gt;
&lt;p&gt;$$ Cov(X,Y)$$&lt;/p&gt;
&lt;p&gt;$$ =E[(X - E[X])(Y - E[Y])] $$&lt;/p&gt;
&lt;p&gt;$$= \sum_{x}\sum_{y} (x - E[X])(y - E[Y]) P(X=x,Y=y) $$
$$= E[XY]-E[x]E[y] $$&lt;/p&gt;
&lt;p&gt;  协方差的值可以是正数、负数或零，分别表示正相关、负相关和无相关关系。&lt;/p&gt;
&lt;h2&gt;7.ML、RL中常见概率分布的期望和方差&lt;/h2&gt;
&lt;p&gt;| &lt;strong&gt;分布类型&lt;/strong&gt;       | &lt;strong&gt;期望&lt;/strong&gt; (E[X]) | &lt;strong&gt;方差&lt;/strong&gt;(Var(X))    |
|----------------|----------------|--------------------|
| 伯努利分布     | p              | p(1 - p)           |
| 二项分布       | np             | np(1 - p)          |
| 泊松分布       | λ              | λ                  |
| 正态分布       | μ              | σ²                 |
| 指数分布       | 1/λ            | 1/λ²               |
| 均匀分布       | (a + b) / 2  | (b - a)² / 12      |&lt;/p&gt;
&lt;p&gt;  其中的正态分布又称为高斯分布，概率密度函数为:   $$f(x; \mu, \sigma) = \frac{1}{\sigma \sqrt{2\pi}} \exp \left( -\frac{(x - \mu)^2}{2\sigma^2} \right) $$&lt;/p&gt;
&lt;p&gt;  标准正态分布的期望为0，方差为1，&lt;/p&gt;
&lt;p&gt;  在coding时，许多数据都比较接近正态分布；而且相同方差的所有可能分布中，正态分布有最大的不确定性，所以正太分布是先验知识最少的分布。如果模型表现较好，那么说明模型的鲁棒性是较高的。&lt;/p&gt;
&lt;p&gt;  当把正太分布推广到多维空间的时候，就有多维正态分布&lt;/p&gt;</content:encoded><h:img src="https://pic.linxii.top/blog/base-learning-1-math-PT-0-128675848.webp"/><enclosure url="https://pic.linxii.top/blog/base-learning-1-math-PT-0-128675848.webp"/></item><item><title>NLP基础学习笔记1</title><link>https://tyuou2.github.io/blog/base-learning-3-nlp</link><guid isPermaLink="true">https://tyuou2.github.io/blog/base-learning-3-nlp</guid><description>NLP的基础知识，包括基本概念、词嵌入、经典NLP模型（如Word2Vec、RNN、LSTM、GRU）、Transformer在NLP中的应用（如BERT、GPT）等内容。</description><pubDate>Wed, 14 Jan 2026 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;import { ArxivRating, RatingCriteria } from &apos;@/components/advanced&apos;&lt;/p&gt;
&lt;h2&gt;自然语言处理&lt;/h2&gt;
&lt;h3&gt;1.Intro&lt;/h3&gt;
&lt;h4&gt;1.1.自然语言处理任务&lt;/h4&gt;
&lt;ul&gt;
&lt;li&gt;语言建模(Language Modeling)：预测下一个词或填补句子中的空白。&lt;/li&gt;
&lt;li&gt;机器翻译(Machine Translation)：将文本从一种语言翻译成另一种语言。&lt;/li&gt;
&lt;li&gt;情感分析(Sentiment Analysis)：识别文本中的情感倾向。&lt;/li&gt;
&lt;li&gt;命名实体识别(Named Entity Recognition, NER)：识别文本中的实体，如人名、地名等。&lt;/li&gt;
&lt;li&gt;问答系统(Question Answering)：根据给定的问题从文本中提取答案。&lt;/li&gt;
&lt;/ul&gt;
&lt;h4&gt;1.2.自然语言处理发展&lt;/h4&gt;
&lt;p&gt;  自然语言处理的发展经历了从&lt;strong&gt;基于规则的方法&lt;/strong&gt;到&lt;strong&gt;统计方法&lt;/strong&gt;，再到&lt;strong&gt;深度学习&lt;/strong&gt;的方法。早期的方法依赖于手工设计的规则和特征提取技术。随着统计方法的发展，基于&lt;strong&gt;概率模型&lt;/strong&gt;的方法如隐马尔可夫模型（HMM）和条件随机场（CRF）被广泛应用。近年来，深度学习方法，特别是&lt;strong&gt;基于Transformer架构&lt;/strong&gt;的方法，如BERT和GPT，显著提升了自然语言处理任务的性能。&lt;/p&gt;
&lt;h3&gt;2.词嵌入&lt;/h3&gt;
&lt;h4&gt;2.1.词袋模型(Bag of Words)&lt;/h4&gt;
&lt;p&gt;  词袋模型是一种简单的文本表示方法，将文本表示为词的无序集合，忽略了词的顺序和语法结构。每个文本被表示为一个向量，向量的每个维度对应一个词汇表中的词，值表示该词在文本中出现的频率或存在与否。&lt;/p&gt;
&lt;h4&gt;2.2.词向量(Word Embeddings)&lt;/h4&gt;
&lt;p&gt;  词向量是一种将词映射到连续向量空间的方法，捕捉词之间的语义关系。常见的词向量方法包括Word2Vec和GloVe。词向量通过训练神经网络模型，使得语义相似的词在向量空间中距离较近。&lt;/p&gt;
&lt;h3&gt;3.经典NLP模型&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;注&lt;/strong&gt;：此部分内容图片来自&lt;a href=&quot;https://blog.csdn.net/mary19831/article/details/129570030&quot;&gt;LSTM从入门到精通（形象的图解，详细的代码和注释，完美的数学推导过程）&lt;/a&gt;和&lt;a href=&quot;https://blog.csdn.net/baidu_38963740/article/details/117197619&quot;&gt;pytorch中LSTM参数详解（一张图帮你更好的理解每一个参数）&lt;/a&gt;。&lt;/p&gt;
&lt;h4&gt;3.1.Word2Vec&lt;/h4&gt;
&lt;h4&gt;3.2 RNN&lt;/h4&gt;
&lt;h5&gt;3.2.1 RNN的结构&lt;/h5&gt;
&lt;p&gt;&lt;img src=&quot;https://pic.linxii.top/blog/base-learning-3-NLP-2-RNN.webp&quot; alt=&quot;RNN的结构&quot;&gt;&lt;/p&gt;
&lt;p&gt;  对于中文分词任务而且，其中的$X_{t}$代表中文的一个字，然后$O_{t}$即代表BMES标签中的一个标签，$S_{t}$代表RNN的隐藏状态。RNN通过循环连接来处理序列数据，能够捕捉序列中的时间依赖关系。然而，传统的RNN存在梯度消失和梯度爆炸问题,以及串行的运行方式，限制了其在长序列上的表现。&lt;/p&gt;
&lt;h5&gt;3.2.2 RNN的缺点&lt;/h5&gt;
&lt;p&gt;&lt;strong&gt;（1）梯度消失与梯度爆炸问题&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;   在训练过程中，RNN的梯度可能会随着时间步的增加而迅速变小（消失）或变大（爆炸），导致模型难以学习长距离依赖关系。而这个梯度消失和爆炸问题主要是由于RNN在反向传播过程中需要计算多个时间步的梯度乘积，
&lt;img src=&quot;https://pic.linxii.top/blog/base-learning-3-1-Gradient.webp&quot; alt=&quot;梯度消失与梯度爆炸&quot;&gt;&lt;/p&gt;
&lt;p&gt;  通过上图的推导可以看到，$w_{s}^{k-1}$是一个&lt;strong&gt;指数函数&lt;/strong&gt;，递增或递减的速度取决于权重矩阵$w_{s}$的值。如果$w_{s}$的值小于1，那么随着时间步的增加，$w_{s}^{k-1}$会迅速趋近于0，导致&lt;strong&gt;梯度消失&lt;/strong&gt;；如果$w_{s}$的值大于1，那么随着时间步的增加，$w_{s}^{k-1}$会迅速增大，导致&lt;strong&gt;梯度爆炸&lt;/strong&gt;。&lt;/p&gt;
&lt;h4&gt;3.3 LSTM&lt;/h4&gt;
&lt;h5&gt;3.3.1 LSTM的结构&lt;/h5&gt;
&lt;p&gt;&lt;img src=&quot;https://pic.linxii.top/blog/base-learning-3-NLP-3-LSTM.webp&quot; alt=&quot;LSTM的结构&quot;&gt;&lt;/p&gt;
&lt;p&gt;  LSTM的核心是引入了一个单独的记忆单元（Cell State）和三个门控机制,输入门（Input Gate）、遗忘门（Forget Gate）和输出门（Output Gate）。记忆单元能够在时间步之间传递信息，而门控机制则控制信息的流动，允许模型选择性地保留或丢弃信息，从而有效地捕捉长时间的依赖关系。&lt;/p&gt;
&lt;h5&gt;3.3.1 LSTM的Pytorch应用与结构对应&lt;/h5&gt;
&lt;p&gt;&lt;img src=&quot;https://pic.linxii.top/blog/base-learning-3-NLP-4-LSTM-torch1.webp&quot; alt=&quot;LSTM的Pytorch应用与结构对应1&quot;&gt;
&lt;img src=&quot;https://pic.linxii.top/blog/base-learning-3-NLP-5-LSTM-torch2.webp&quot; alt=&quot;LSTM的Pytorch应用与结构对应2&quot;&gt;
  &lt;/p&gt;
&lt;h4&gt;3.4 GRU&lt;/h4&gt;
&lt;h3&gt;4.Transformer在自然语言处理中的应用&lt;/h3&gt;
&lt;h4&gt;4.1.BERT (Bidirectional Encoder Representations from Transformers)&lt;/h4&gt;
&lt;h4&gt;4.2.GPT 1 (Generative Pre-trained Transformer)&lt;/h4&gt;</content:encoded><h:img src="https://pic.linxii.top/blog/base-learning-3-NLP-0-105463056.webp"/><enclosure url="https://pic.linxii.top/blog/base-learning-3-NLP-0-105463056.webp"/></item><item><title>GNN基础学习笔记1</title><link>https://tyuou2.github.io/blog/base-learning-4-gnn</link><guid isPermaLink="true">https://tyuou2.github.io/blog/base-learning-4-gnn</guid><description>图神经网络（GNN）的基础知识，包括基本概念、常见模型（如GCN、GAT）、E(n)等变图神经网络以及过平滑问题的介绍。</description><pubDate>Tue, 13 Jan 2026 00:00:00 GMT</pubDate><content:encoded>&lt;h2&gt;GNN基础&lt;/h2&gt;
&lt;h3&gt;1.Intro&lt;/h3&gt;
&lt;h4&gt;1.1.图相关任务&lt;/h4&gt;
&lt;ul&gt;
&lt;li&gt;节点分类(Node Classification)：预测图中节点的类别标签。&lt;/li&gt;
&lt;li&gt;节点位置优化(Node Position Optimization)：优化节点在图中的位置以提高图的表示能力。&lt;/li&gt;
&lt;li&gt;边预测(Edge Prediction)：预测图中节点之间是否存在边，即预测节点对之间的关系。&lt;/li&gt;
&lt;li&gt;图分类(Graph Classification)：对整个图进行分类，预测图的类别标签。&lt;/li&gt;
&lt;li&gt;图生成(Graph Generation)：生成新的图结构，通常用于模拟复杂网络或生成特定类型的图。&lt;/li&gt;
&lt;li&gt;图嵌入(Graph Embedding)：将图中的节点或子图映射到 &lt;strong&gt;低维向量空间&lt;/strong&gt;，以便进行后续的机器学习任务。&lt;/li&gt;
&lt;/ul&gt;
&lt;h4&gt;1.2图神经网络&lt;/h4&gt;
&lt;p&gt;  GNN输入输出都是图结构数据，同时输出图的顶点、边、全局的Embedding向量有更加丰富的信息表达能力，可以用于下游任务。&lt;/p&gt;
&lt;h3&gt;2.GCN&lt;/h3&gt;
&lt;h4&gt;2.1.消息传递机制(Message Passing)&lt;/h4&gt;
&lt;p&gt;  图神经网络的核心思想是通过节点之间的信息传递来更新节点的表示。每个节点通过聚合其邻居节点的信息来更新自己的表示。这种信息传递机制使得节点能够捕捉到其局部结构和属性信息。类似于卷积神经网络（CNN）中的卷积操作，GNN通过&lt;strong&gt;邻居节点的信息聚合&lt;/strong&gt;来实现节点表示的更新。
  消息聚合能够发生在节点与节点直接，消息聚合也可以在边与节点之间进行等等。&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;k层GNN能够捕捉到k跳邻居的信息。消息传递的过程可以迭代进行，每一层GNN都会更新节点的表示。同时也可以在过程中聚合全局信息（利用全局Embedding）。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;  最后再加一层MLP进行预测，这样就可以变成低维向量表示，用于下游任务。&lt;/p&gt;
&lt;h3&gt;3.GAT&lt;/h3&gt;
&lt;h4&gt;3.1.GAT中的注意力机制(Attention Mechanism)&lt;/h4&gt;
&lt;p&gt;  GAT引入了注意力机制，使得节点在聚合邻居信息时能够动态地分配不同的权重。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;注意力分数：&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;$$  a_{ij}=softmax(\sigma(\vec a^T [ W \vec h_{i}||W \vec h_{j}] ))=\frac{exp(LeakyReLU(\vec a^T [ W \vec h_{i}||W \vec h_{j}] ))}{ \sum_{k \in N_{i}}(exp(LeakyReLU(\vec a^T [ W \vec h_{i}||W \vec h_{k}] ))) } $$&lt;/p&gt;
&lt;p&gt;  在这里，$||$表示向量的连接操作，$\vec a$是可学习的注意力权重向量，$W$是线性变换矩阵，$\vec h_{i}$和$\vec h_{j}$分别是节点$i$和节点$j$的特征向量，$N_{i}$表示节点$i$的邻居节点集合。&lt;/p&gt;
&lt;p&gt;  其中这个$||$向量的连接操作可以实现非对称的注意力机制，也就是说节点$i$对节点$j$的关注程度可能与节点$j$对节点$i$的关注程度不同。
比较恰当的例子就是演讲者与听众的关系，一个演讲者对一个听众的关注点与一个听众对一个演讲者的关注是不同的。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;加权聚合：&lt;/strong&gt;
$$ \vec h_{i}^{&apos;}=\sigma(\sum_{j \in N_{i}} a_{ij} W \vec h_{j}) $$&lt;/p&gt;
&lt;h4&gt;3.2GAT中的多头注意力(Multi-Head Attention)&lt;/h4&gt;
&lt;p&gt;  GAT通过使用多个注意力头（即使用多套$W$和$a^T$）。&lt;/p&gt;
&lt;h2&gt;4.E(n)等变图神经网络&lt;/h2&gt;
&lt;h3&gt;4.1.等变与不变&lt;/h3&gt;
&lt;p&gt;  图像的卷积操作对平移是等变的，即图像平移后再进行卷积操作，结果与先进行卷积再平移是相同的。&lt;/p&gt;
&lt;h3&gt;4.2 E(n)等变图神经网络&lt;/h3&gt;
&lt;p&gt;  E(n)等变图神经网络设计用于处理具有欧几里得对称性的图数据。在处理物理系统、分子结构等领域中表现出色，因为这些系统通常具有旋转、平移和反射等对称性。E(n)等变图神经网络通过确保其操作在这些对称变换下保持不变，从而捕捉到数据的本质特征，提高模型的泛化能力和性能。恰当的例子是比如氧气分子，无论如何旋转和平移，它的化学性质是不变的。&lt;/p&gt;
&lt;h2&gt;5.GNN的过平滑问题&lt;/h2&gt;
&lt;p&gt;  随着GNN层数的增加，处于同一个连通分支的节点的表示趋于相似，导致节点之间的区分度降低。这种现象被称为过平滑问题，可能会影响模型的性能。&lt;/p&gt;
&lt;p&gt;  问题的原因是GNN在聚合邻居的信息，当层数增加时，聚合的节点信息越多，然后最后如果$k=$连通块的直径，那么所有节点的表示都会趋于相同，因为这样的话整个图的信息就都聚合了。&lt;/p&gt;
&lt;h3&gt;5.1缓解措施&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;限制GNN的层数：通过限制GNN的层数，可以减少信息的过度聚合，从而缓解过平滑问题。&lt;/li&gt;
&lt;li&gt;正则化技术：引入正则化项，如DropEdge，可以帮助模型保持节点表示的多样性，防止过度平滑。&lt;/li&gt;
&lt;/ul&gt;</content:encoded><h:img src="https://pic.linxii.top/blog/base-learning-4-GNN-0-105463056.webp"/><enclosure url="https://pic.linxii.top/blog/base-learning-4-GNN-0-105463056.webp"/></item><item><title>linux基础</title><link>https://tyuou2.github.io/blog/tech-note-3-linux</link><guid isPermaLink="true">https://tyuou2.github.io/blog/tech-note-3-linux</guid><description>记一些到现在不熟悉的linux命令与操作，方便查阅，不定时更新</description><pubDate>Sat, 10 Jan 2026 00:00:00 GMT</pubDate><content:encoded>&lt;h2&gt;&lt;strong&gt;1.文件读写&lt;/strong&gt;&lt;/h2&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;cat filename          # 查看整个文件内容
nano filename         # 使用nano编辑器打开文件进行编辑
vim filename          # 使用vim编辑器打开文件进行编辑
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;  在nano中相关的命令&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;Ctrl + O             # 保存文件
Ctrl + X             # 退出nano编辑器
Ctrl + K             # 剪切当前行
Ctrl + U             # 粘贴剪切的内容
Ctrl + W	         # 搜索内容
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;  在vim中相关的命令&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;i                    # 进入插入模式
Esc                  # 退出插入模式
:w                   # 保存文件
:q                   # 退出vim
:wq                  # 保存并退出vim
:q!                  # 强制退出不保存
dd                   # 剪切（删除）当前行（实际是放到寄存器，可用p粘贴）
yy                   # 复制当前行
p                    # 粘贴复制的内容
/x                   # 搜索内容
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;&lt;strong&gt;2.授权&lt;/strong&gt;&lt;/h2&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;chmod 755 filename      # 赋予文件所有者读写执行权限，组用户和其他用户读执行权限
chmod -R 755 directory  # 递归赋予目录及其子文件/目录权限
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;  权限说明：&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;r: 读权限 (read)
w: 写权限 (write)
x: 执行权限 (execute)

数字表示法：
4: 读权限
2: 写权限
1: 执行权限

7: 读写执行权限 (4+2+1)
&lt;/code&gt;&lt;/pre&gt;</content:encoded><h:img src="https://pic.linxii.top/blog/tech-note-3-linux-0-94516796.webp"/><enclosure url="https://pic.linxii.top/blog/tech-note-3-linux-0-94516796.webp"/></item><item><title>git配置与使用</title><link>https://tyuou2.github.io/blog/tech-note-2-git</link><guid isPermaLink="true">https://tyuou2.github.io/blog/tech-note-2-git</guid><description>git的使用，基于命令行的操作</description><pubDate>Sat, 10 Jan 2026 00:00:00 GMT</pubDate><content:encoded>&lt;h2&gt;&lt;strong&gt;1.创建Github仓库&lt;/strong&gt;&lt;/h2&gt;
&lt;p&gt;  网页端操作，创建空仓库即可&lt;/p&gt;
&lt;h2&gt;&lt;strong&gt;2. 项目git初始化&lt;/strong&gt;&lt;/h2&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;git init
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;&lt;strong&gt;3.关联我的仓库与项目&lt;/strong&gt;&lt;/h2&gt;
&lt;p&gt;  其中仓库地址为1中创建的&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;#填写仓库地址
git remote add origin [https://github.com/tyuou2/knowledgeGraph.git]
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;&lt;strong&gt;4.不断提交、推送（多次使用）&lt;/strong&gt;&lt;/h2&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;#切换到 gh-pages 分支
git checkout master
#加入到Git中
git add .

#提交到本地仓库
git commit -m &quot;xxx&quot;

#确保当前分支名为 master (防止本地是 main 而远程是 master 造成混乱) 【这一步也可以省略】
git branch -M master

#开大缓存，防止推送失败
git config --global http.postBuffer 524288000

#推送代码到远程仓库
git push -u origin master
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;&lt;strong&gt;5.撤销上次提交&lt;/strong&gt;&lt;/h2&gt;
&lt;p&gt;  本地git撤销&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;#只撤销提交
git reset --soft HEAD^

#连同内容都撤销
git reset --hard HEAD^
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;  已同步的远程仓库撤销&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;#就是强制同步一遍本地的记录
git push -f origin 你的分支名  # 比如 git push -f origin main
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;&lt;strong&gt;6.合并分支的最新代码&lt;/strong&gt;&lt;/h2&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;git checkout gh-pages

git merge master -m &quot;同步 master 到 gh-pages&quot;
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;&lt;strong&gt;7.总结&lt;/strong&gt;&lt;/h2&gt;
&lt;p&gt;  这些大部分都可以通过IDE的可视化页面进行操作，命令行有时候更加方便。&lt;/p&gt;</content:encoded><h:img src="https://pic.linxii.top/blog/tech-note-2-git-0-96440584.webp"/><enclosure url="https://pic.linxii.top/blog/tech-note-2-git-0-96440584.webp"/></item><item><title>云服务器配置</title><link>https://tyuou2.github.io/blog/tech-note-1-cloudserver</link><guid isPermaLink="true">https://tyuou2.github.io/blog/tech-note-1-cloudserver</guid><description>一个全新的云服务器（Ubuntu系统）初始配流程-部署项目使用时</description><pubDate>Sat, 10 Jan 2026 00:00:00 GMT</pubDate><content:encoded>&lt;h2&gt;&lt;strong&gt;1.初次启动配置&lt;/strong&gt;&lt;/h2&gt;
&lt;h3&gt;&lt;strong&gt;1.1  更新系统软件包&lt;/strong&gt;&lt;/h3&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;sudo apt update &amp;#x26;&amp;#x26; sudo apt upgrade -y
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;&lt;strong&gt;1.2 Docker安装&lt;/strong&gt;&lt;/h3&gt;
&lt;p&gt;  本次使用的是&lt;strong&gt;华为云服务器&lt;/strong&gt;，因此仓库等都是华为云的配置，其他云服务器可以进行更改后使用。&lt;/p&gt;
&lt;p&gt;  添加华为云 Docker GPG 密钥&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;# 第一步：下载华为云 Docker 源的 GPG 密钥，保存为临时文件
curl -fsSL https://repo.huaweicloud.com/docker-ce/linux/ubuntu/gpg -o /tmp/docker-key.gpg
# 第二步：将下载的 GPG 密钥转换为 dearmor 格式，并存入系统可信密钥目录
sudo gpg --dearmor /tmp/docker-key.gpg -o /etc/apt/trusted.gpg.d/docker.gpg
# 可选：删除临时文件（清理冗余）
rm /tmp/docker-key.gpg
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;  配置华为云 Docker 稳定版仓库&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;echo &quot;deb [arch=$(dpkg --print-architecture) signed-by=/etc/apt/trusted.gpg.d/docker.gpg] https://repo.huaweicloud.com/docker-ce/linux/ubuntu $(lsb_release -cs) stable&quot; | sudo tee /etc/apt/sources.list.d/docker.list &gt; /dev/null
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;  更新包索引&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;sudo apt update
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;  安装 Docker Engine 核心组件&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;sudo apt install -y docker-ce docker-ce-cli containerd.io
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;  验证 Docker 安装并设置开机自启&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;# 启动Docker服务
sudo systemctl start docker
# 设置开机自动启动
sudo systemctl enable docker
&lt;/code&gt;&lt;/pre&gt;
&lt;h4&gt;&lt;strong&gt;（额外）配置国内镜像源&lt;/strong&gt;&lt;/h4&gt;
&lt;p&gt;  写入镜像加速器配置&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;sudo tee /etc/docker/daemon.json &amp;#x3C;&amp;#x3C;-&apos;EOF&apos;
{
  &quot;registry-mirrors&quot;: [&quot;https://docker.xuanyuan.me&quot;]
}
EOF
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;  &lt;strong&gt;重启 Docker 相关服务，使配置生效&lt;/strong&gt;，必须执行这两步，加速器配置才能正常工作。&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;# 重新加载系统服务配置
sudo systemctl daemon-reload
# 重启 Docker 服务
sudo systemctl restart docker
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;&lt;strong&gt;1.3 Redis配置&lt;/strong&gt;&lt;/h3&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;# 启动 Redis 容器，设置密码123456，暴露6379端口，开机自启
docker run -d \
  --name redis \
  --restart always \
  -p 6379:6379 \
  redis:latest \
  redis-server --requirepass 123456
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;&lt;strong&gt;1.4RabbitMQ 配置&lt;/strong&gt;&lt;/h3&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;# 启动 RabbitMQ 容器（默认开启5672端口，带管理界面15672端口）
docker run -d \
  --name rabbitmq \
  --restart always \
  -p 5672:5672 \
  -p 15672:15672 \
  rabbitmq:3-management
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;&lt;strong&gt;1.5 MySQL容器&lt;/strong&gt;&lt;/h3&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;# 启动 MySQL 8.0 容器，设置 root 密码为your_password，暴露 3306 端口
#（先创建目录，再启动新容器）
mkdir -p /usr/local/mysql/data
docker run -d \
  --name mysql \
  --restart always \
  -p 3306:3306 \
  -v /usr/local/mysql/data:/var/lib/mysql \
  -e MYSQL_ROOT_PASSWORD=your_password \
  -e TZ=Asia/Shanghai \
  -e MYSQL_INITDB_SKIP_TZINFO=1 \
  mysql:8.0 \
  --character-set-server=utf8mb4 \
  --collation-server=utf8mb4_unicode_ci
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;&lt;strong&gt;1.6Minio配置&lt;/strong&gt;&lt;/h3&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;# 创建主机本地存储目录
mkdir -p /usr/local/minio/data

# 带数据挂载的 MinIO 启动命令（其他参数不变，新增 -v 映射）
docker run -d \
  --name minio \
  --restart always \
  -p 9005:9000 \
  -p 9006:9001 \
  -v /usr/local/minio/data:/data \
  -e MINIO_ROOT_USER=username \
  -e MINIO_ROOT_PASSWORD=password \
  minio/minio server /data --console-address &quot;:9001&quot;
&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;# 步骤 1：使用新版本正确命令配置 MinIO 别名（凭证关联）
# 核心命令：mc alias set + 别名 + 服务地址 + accessKey + secretKey
mc alias set local http://127.0.0.1:9000 name password
# 此时再执行服务信息查询，即可正常返回结果，不再报 Access Denied
mc admin info local
# 创建存储桶（匹配你的配置）
mc mb local/knowledgegraph
# 成功提示：Bucket created successfully `local/knowledgegraph`.
# 验证存储桶是否存在
mc ls local
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;&lt;strong&gt;1.7Nginx初始配置&lt;/strong&gt;&lt;/h3&gt;
&lt;p&gt;  单项目可以优先使用&lt;strong&gt;云服务器本地部署&lt;/strong&gt;，操作简单&lt;/p&gt;
&lt;p&gt;  (1)Docekr部署&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;#待更新

&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;  (2)云服务器本地部署&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;# 1. 更新系统软件包索引（确保安装最新版本 Nginx）
sudo apt update -y

# 2. 安装 Nginx
sudo apt install -y nginx

# 3. 启动 Nginx 服务
sudo systemctl start nginx

# 4. 设置 Nginx 开机自启（服务器重启后自动运行）
sudo systemctl enable nginx

# 5. 验证 Nginx 启动状态（可选，查看是否正常运行）
sudo systemctl status nginx
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;&lt;strong&gt;1.8 node.js安装&lt;/strong&gt;&lt;/h3&gt;
&lt;p&gt;  使用nvm进行安装,首先换源安装nvm：&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;# 临时设置nvm镜像（国内淘宝源）
export NVM_NODEJS_ORG_MIRROR=https://npmmirror.com/mirrors/node
export NVM_INSTALL_GITHUB_REPO=coreybutler/nvm-windows
export NVM_SOURCE=https://gitee.com/mirrors/nvm.git

# 用镜像地址安装nvm
curl -o- https://gitee.com/mirrors/nvm/raw/v0.39.7/install.sh | bash
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;  安装完成后，执行以下命令生效 nvm,并验证是否安装成功&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;source ~/.bashrc
nvm -v
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;  安装 node.js 长期支持版本：&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;# 安装指定版本（自动用国内镜像）
nvm install 22.20.0
# 设为默认版本
nvm alias default 22.20.0
# 验证版本
node -v  # 输出 v22.20.0 即可
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;&lt;strong&gt;2.nginx 反向代理配置&lt;/strong&gt;&lt;/h2&gt;
&lt;p&gt;  假设部署在云服务器的7777端口，配置反向代理到80/443端口。
  首先进入nginx配置目录，创建一个新的配置文件xxx.conf：&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;#创建nginx配置文件
sudo touch /etc/nginx/conf.d/xxx.conf
#编辑xxx.conf文件
sudo nano /etc/nginx/conf.d/xxx.conf
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;  在文件中添加内容,其中日志文件路径可根据需要修改：
  保存并退出编辑器后，测试 Nginx 配置是否正确,并重启Nginx服务使配置生效：&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;sudo nginx -t
sudo systemctl restart nginx
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;  &lt;strong&gt;有点懵，AI带着部署的，后面再更新吧！！！&lt;/strong&gt;&lt;/p&gt;</content:encoded><h:img src="https://pic.linxii.top/blog/tech-note-1-CloudServer-0-92765703.webp"/><enclosure url="https://pic.linxii.top/blog/tech-note-1-CloudServer-0-92765703.webp"/></item></channel></rss>