# Somnus的博客 > 记录学习、项目、实验和日常。 Public Ghost content for AI and LLM tooling. This file includes a bounded export of public pages first, then recent public posts. Append `.md` to any post or page URL to get the content in Markdown (for example, `/example-post.md`). ## Pages ### LaTeX 数学公式速查表 URL: https://blog.somnus.wiki/latex/ Last updated: 2026-07-23T01:50:37.000Z **在线预览**输入文字/LaTeX,右侧即时渲染当 $a \\ne 0$ 时: $$ x=\\frac{-b\\pm\\sqrt{b^2-4ac}}{2a} $$ 这是一份偏博客写作场景的 Markdown LaTeX 数学公式速查表,适用于 MathJax/KaTeX/Typora/Obsidian/Jupyter/知乎/CSDN 等常见环境。内容合并了我常用写法、易踩坑规则,以及 [科学空间的 MathJax/LaTeX 参考页](https://spaces.ac.cn/latex.html?ref=blog.somnus.wiki) 中的常见命令分类。 ## 一、公式环境 | 场景 | 写法 | 说明 | | ----- | ---------------------- | ----------------------------- | | 行内公式 | \`$x^2+y^2=z^2$\` | 放在正文中,尺寸较紧凑:$x^2+y^2=z^2$ | | 行内公式 | \`\\(x^2+y^2=z^2\\)\` | MathJax 推荐写法之一,显示效果同上 | | 独立公式 | \`$$...$$\` | 单独占一行,适合较长公式 | | 独立公式 | \`\\\[...\\\]\` | MathJax 推荐写法之一 | | 多行公式 | aligned | 用 \\\\ 换行,用 & 对齐 | | 复杂块公式 | {% math() %}…{% end %} | 本站专用:绕开 Markdown,直接交给 MathJax | ``` 当 $a \ne 0$ 时,方程 $ax^2+bx+c=0$ 的解为 $$ x = \frac{-b \pm \sqrt{b^2-4ac}}{2a}. $$ ``` 渲染效果: 当 $a \\ne 0$ 时,方程 $ax^2+bx+c=0$ 的解为 $$ x = \\frac{-b \\pm \\sqrt{b^2-4ac}}{2a}. $$ 本站写长公式时,推荐用 `math` shortcode 包住原生 MathJax/TeX 内容,避免 Markdown 先把 `_`、`*`、`\{`、`\\` 等符号解析掉: ``` \begin{aligned} \pi^*(y|x) &= \frac{1}{Z(x)} \pi_{\text{ref}}(y|x) \\ \mathbb{D}_{KL}(P\|Q) &= \mathbb{E}_{x\sim P}\left[\log\frac{P(x)}{Q(x)}\right] \end{aligned} ``` ## 二、上下标与正下方 | 需求 | 写法 | 效果 | | ---------- | ----------------------------------------------------- | ----------------------------------------------------- | | 上标 | $x^2$ | $x^2$ | | 下标 | $x\_i$ | $x\_i$ | | 多字符上标 | $x^{n+1}$ | $x^{n+1}$ | | 多字符下标 | $a\_{ij}$ | $a\_{ij}$ | | 上下标同时出现 | $x\_i^2$ | $x\_i^2$ | | 撇号 | $f^\\prime(x), f^{\\prime\\prime}(x)$ | $f^\\prime(x), f^{\\prime\\prime}(x)$ | | 行内强制下标在正下方 | $\\max\\limits\_{\\theta} f(\\theta)$ | $\\max\\limits\_{\\theta} f(\\theta)$ | | 整体正下方标注 | $\\underset{\\theta}{\\arg\\max}\\, P(x\\mid\\theta)$ | $\\underset{\\theta}{\\arg\\max}\\, P(x\\mid\\theta)$ | | 整体正上方标注 | $\\overset{\*}{x}$ | $\\overset{\*}{x}$ | 要点: - 行内公式里,`\sum`、`\max`、`\lim` 的上下标通常会挤在右侧;需要正下方时加 `\limits`。 - 独立公式里,很多大算子默认会把上下标放到正上方/正下方。 - `\arg\max\limits_{\theta}` 的 `\theta` 更靠近 `max`;`\underset{\theta}{\arg\max}` 会在整个 `argmax` 下方居中。 ## 三、希腊字母 ### 小写 | 英文 | 读音 | 写法 | 效果 | 英文 | 读音 | 写法 | 效果 | | ------- | ---- | ----------- | ----------- | ------- | ---- | ----------- | ----------- | | alpha | 阿尔法 | $\\alpha$ | $\\alpha$ | beta | 贝塔 | $\\beta$ | $\\beta$ | | gamma | 伽马 | $\\gamma$ | $\\gamma$ | delta | 德尔塔 | $\\delta$ | $\\delta$ | | epsilon | 艾普西隆 | $\\epsilon$ | $\\epsilon$ | zeta | 泽塔 | $\\zeta$ | $\\zeta$ | | eta | 伊塔 | $\\eta$ | $\\eta$ | theta | 西塔 | $\\theta$ | $\\theta$ | | iota | 约塔 | $\\iota$ | $\\iota$ | kappa | 卡帕 | $\\kappa$ | $\\kappa$ | | lambda | 兰布达 | $\\lambda$ | $\\lambda$ | mu | 缪 | $\\mu$ | $\\mu$ | | nu | 纽 | $\\nu$ | $\\nu$ | xi | 克西 | $\\xi$ | $\\xi$ | | omicron | 奥密克戎 | $o$ | $o$ | pi | 派 | $\\pi$ | $\\pi$ | | rho | 柔 | $\\rho$ | $\\rho$ | sigma | 西格玛 | $\\sigma$ | $\\sigma$ | | tau | 陶 | $\\tau$ | $\\tau$ | upsilon | 宇普西隆 | $\\upsilon$ | $\\upsilon$ | | phi | 斐 | $\\phi$ | $\\phi$ | chi | 希 | $\\chi$ | $\\chi$ | | psi | 普西 | $\\psi$ | $\\psi$ | omega | 欧米伽 | $\\omega$ | $\\omega$ | 常见变体: | 名称 | 读音 | 写法 | 效果 | 名称 | 读音 | 写法 | 效果 | | ---------- | ------ | -------------- | -------------- | -------- | ---- | ------------ | ------------ | | varepsilon | 艾普西隆变体 | $\\varepsilon$ | $\\varepsilon$ | vartheta | 西塔变体 | $\\vartheta$ | $\\vartheta$ | | varpi | 派变体 | $\\varpi$ | $\\varpi$ | varrho | 柔变体 | $\\varrho$ | $\\varrho$ | | varsigma | 西格玛词尾形 | $\\varsigma$ | $\\varsigma$ | varphi | 斐变体 | $\\varphi$ | $\\varphi$ | | digamma | 双伽马 | $\\digamma$ | $\\digamma$ | | | | | ### 大写 大写字母读音同对应小写;常用大写命令如下。 | 名称 | 读音 | 写法 | 效果 | 名称 | 读音 | 写法 | 效果 | | ----- | --- | --------- | --------- | ------- | ---- | ----------- | ----------- | | Gamma | 伽马 | $\\Gamma$ | $\\Gamma$ | Delta | 德尔塔 | $\\Delta$ | $\\Delta$ | | Theta | 西塔 | $\\Theta$ | $\\Theta$ | Lambda | 兰布达 | $\\Lambda$ | $\\Lambda$ | | Xi | 克西 | $\\Xi$ | $\\Xi$ | Pi | 派 | $\\Pi$ | $\\Pi$ | | Sigma | 西格玛 | $\\Sigma$ | $\\Sigma$ | Upsilon | 宇普西隆 | $\\Upsilon$ | $\\Upsilon$ | | Phi | 斐 | $\\Phi$ | $\\Phi$ | Psi | 普西 | $\\Psi$ | $\\Psi$ | | Omega | 欧米伽 | $\\Omega$ | $\\Omega$ | nabla | 纳布拉 | $\\nabla$ | $\\nabla$ | ## 四、关系运算符 | 描述 | 写法 | 效果 | 描述 | 写法 | 效果 | | -------- | ---------------- | ------------ | -------- | ---------------- | ------------ | | 等于 | $=$ | $=$ | 不等于 | $\\ne$ 或 $\\neq$ | $\\ne$ | | 小于 | $<$ 或 $\\lt$ | $\\lt$ | 大于 | $>$ 或 $\\gt$ | $\\gt$ | | 小于等于 | $\\le$ 或 $\\leq$ | $\\leq$ | 大于等于 | $\\ge$ 或 $\\geq$ | $\\geq$ | | 更美观的小于等于 | $\\leqslant$ | $\\leqslant$ | 更美观的大于等于 | $\\geqslant$ | $\\geqslant$ | | 约等于 | $\\approx$ | $\\approx$ | 渐近相等 | $\\simeq$ | $\\simeq$ | | 同构/全等 | $\\cong$ | $\\cong$ | 恒等/等价 | $\\equiv$ | $\\equiv$ | | 相似/同阶 | $\\sim$ | $\\sim$ | 正比于 | $\\propto$ | $\\propto$ | | 远小于 | $\\ll$ | $\\ll$ | 远大于 | $\\gg$ | $\\gg$ | | 垂直 | $\\perp$ | $\\perp$ | 平行 | $\\parallel$ | $\\parallel$ | | 整除 | $\\mid$ | $\\mid$ | 不整除 | $\\nmid$ | $\\nmid$ | 否定写法常用 `\not`: | 写法 | 效果 | | ----------------- | ----------------- | | $\\not\\equiv$ | $\\not\\equiv$ | | $\\not\\sim$ | $\\not\\sim$ | | $\\not\\subseteq$ | $\\not\\subseteq$ | ## 五、集合与逻辑 | 描述 | 写法 | 效果 | 描述 | 写法 | 效果 | | ----- | ------------------ | ------------------ | ----- | ------------------ | ------------------ | | 属于 | $\\in$ | $\\in$ | 不属于 | $\\notin$ | $\\notin$ | | 包含元素 | $\\ni$ | $\\ni$ | 空集 | $\\emptyset$ | $\\emptyset$ | | 子集 | $\\subset$ | $\\subset$ | 真超集 | $\\supset$ | $\\supset$ | | 子集或等于 | $\\subseteq$ | $\\subseteq$ | 超集或等于 | $\\supseteq$ | $\\supseteq$ | | 并集 | $\\cup$ | $\\cup$ | 交集 | $\\cap$ | $\\cap$ | | 大并 | $\\bigcup\_i A\_i$ | $\\bigcup\_i A\_i$ | 大交 | $\\bigcap\_i A\_i$ | $\\bigcap\_i A\_i$ | | 差集 | $A\\setminus B$ | $A\\setminus B$ | 笛卡尔积 | $A\\times B$ | $A\\times B$ | | 任意 | $\\forall$ | $\\forall$ | 存在 | $\\exists$ | $\\exists$ | | 不存在 | $\\nexists$ | $\\nexists$ | 因此 | $\\therefore$ | $\\therefore$ | | 因为 | $\\because$ | $\\because$ | 蕴含 | $\\Rightarrow$ | $\\Rightarrow$ | | 当且仅当 | $\\Leftrightarrow$ | $\\Leftrightarrow$ | 逻辑非 | $\\neg$ | $\\neg$ | | 逻辑与 | $\\land$ | $\\land$ | 逻辑或 | $\\lor$ | $\\lor$ | 常用数集: | 描述 | 写法 | 效果 | | --- | ------------- | ------------- | | 自然数 | $\\mathbb{N}$ | $\\mathbb{N}$ | | 整数 | $\\mathbb{Z}$ | $\\mathbb{Z}$ | | 有理数 | $\\mathbb{Q}$ | $\\mathbb{Q}$ | | 实数 | $\\mathbb{R}$ | $\\mathbb{R}$ | | 复数 | $\\mathbb{C}$ | $\\mathbb{C}$ | ## 六、二元运算符 | 描述 | 写法 | 效果 | 描述 | 写法 | 效果 | | -- | --------- | --------- | ------- | ----------- | ----------- | | 加减 | $\\pm$ | $\\pm$ | 减加 | $\\mp$ | $\\mp$ | | 乘号 | $\\times$ | $\\times$ | 点乘 | $\\cdot$ | $\\cdot$ | | 星号 | $\\ast$ | $\\ast$ | 卷积 | $\\star$ | $\\star$ | | 除号 | $\\div$ | $\\div$ | 分式斜线 | $a/b$ | $a/b$ | | 圆加 | $\\oplus$ | $\\oplus$ | 圆乘 | $\\otimes$ | $\\otimes$ | | 圆点 | $\\odot$ | $\\odot$ | 菱形 | $\\diamond$ | $\\diamond$ | | 楔积 | $\\wedge$ | $\\wedge$ | 逻辑或/vee | $\\vee$ | $\\vee$ | | 交 | $\\cap$ | $\\cap$ | 并 | $\\cup$ | $\\cup$ | ## 七、分式、根号与常用结构 | 描述 | 写法 | 效果 | | ------ | -------------------------------- | -------------------------------- | | 分式 | $\\frac{a+b}{c+d}$ | $\\frac{a+b}{c+d}$ | | 行内较小分式 | $\\tfrac{a}{b}$ | $\\tfrac{a}{b}$ | | 展示较大分式 | $\\dfrac{a}{b}$ | $\\dfrac{a}{b}$ | | 平方根 | $\\sqrt{x}$ | $\\sqrt{x}$ | | n 次根 | $\\sqrt\[n\]{x}$ | $\\sqrt\[n\]{x}$ | | 上划线 | $\\overline{AB}$ | $\\overline{AB}$ | | 下划线 | $\\underline{AB}$ | $\\underline{AB}$ | | 上花括号 | $\\overbrace{a+\\cdots+a}^{n}$ | $\\overbrace{a+\\cdots+a}^{n}$ | | 下花括号 | $\\underbrace{x+\\cdots+x}\_{n}$ | $\\underbrace{x+\\cdots+x}\_{n}$ | | 上箭头 | $\\overrightarrow{AB}$ | $\\overrightarrow{AB}$ | | 左箭头 | $\\overleftarrow{AB}$ | $\\overleftarrow{AB}$ | | 帽子 | $\\widehat{ABC}$ | $\\widehat{ABC}$ | | 波浪帽 | $\\widetilde{ABC}$ | $\\widetilde{ABC}$ | ## 八、括号与分隔符 | 描述 | 写法 | 效果 | | ------ | ---------------------------------------------- | ---------------------------------------------- | | 小括号 | $(x)$ | $(x)$ | | 中括号 | $\[x\]$ | $\[x\]$ | | 花括号 | $\\{x\\}$ | ${x}$ | | 绝对值 | $\\lvert x\\rvert$ | $\\lvert x\\rvert$ | | 范数 | $\|x|$ 或 $\\lVert x\\rVert$ | $\\lVert x\\rVert$ | | 角括号 | $\\langle x,y\\rangle$ | $\\langle x,y\\rangle$ | | 向上取整 | $\\lceil x\\rceil$ | $\\lceil x\\rceil$ | | 向下取整 | $\\lfloor x\\rfloor$ | $\\lfloor x\\rfloor$ | | 自适应大小 | $\\left\[ \\frac{a}{b} \\right\]$ | $\\left\[ \\frac{a}{b} \\right\]$ | | 只有左分隔符 | $\\left. \\frac{dy}{dx} \\right\\rvert\_{x=0}$ | $\\left. \\frac{dy}{dx} \\right\\rvert\_{x=0}$ | | 手动放大 | $\\Bigl( \\bigl( x \\bigr) \\Bigr)$ | $\\Bigl( \\bigl( x \\bigr) \\Bigr)$ | 成对括号常用模板: ``` \left( ... \right) \left[ ... \right] \left\{ ... \right\} \left| ... \right| \left\langle ... \right\rangle ``` ## 九、箭头 | 描述 | 写法 | 效果 | 描述 | 写法 | 效果 | | ----- | ----------------------------- | ----------------------------- | ----- | ------------------------ | ------------------------ | | 左箭头 | $\\leftarrow$ | $\\leftarrow$ | 右箭头 | $\\rightarrow$ | $\\rightarrow$ | | 赋值箭头 | $\\gets$ | $\\gets$ | 映射到 | $\\mapsto$ | $\\mapsto$ | | 长左箭头 | $\\longleftarrow$ | $\\longleftarrow$ | 长右箭头 | $\\longrightarrow$ | $\\longrightarrow$ | | 双线左箭头 | $\\Leftarrow$ | $\\Leftarrow$ | 双线右箭头 | $\\Rightarrow$ | $\\Rightarrow$ | | 左右箭头 | $\\leftrightarrow$ | $\\leftrightarrow$ | 当且仅当 | $\\Leftrightarrow$ | $\\Leftrightarrow$ | | 长双线箭头 | $\\Longrightarrow$ | $\\Longrightarrow$ | 长当且仅当 | $\\Longleftrightarrow$ | $\\Longleftrightarrow$ | | 上箭头 | $\\uparrow$ | $\\uparrow$ | 下箭头 | $\\downarrow$ | $\\downarrow$ | | 上下箭头 | $\\updownarrow$ | $\\updownarrow$ | 向量箭头 | $\\vec{x}$ | $\\vec{x}$ | | 上方带字 | $\\xrightarrow{n\\to\\infty}$ | $\\xrightarrow{n\\to\\infty}$ | 上下带字 | $\\xleftarrow\[下方\]{上方}$ | $\\xleftarrow\[下方\]{上方}$ | ## 十、标准函数 数学函数应该用直立体命令,不要直接写成斜体字母。例如 `\sin x` 比 `sin x` 更规范。 | 类型 | 命令 | | ----- | ----------------------------------- | | 三角函数 | \\sin \\cos \\tan \\cot \\sec \\csc | | 反三角函数 | \\arcsin \\arccos \\arctan | | 双曲函数 | \\sinh \\cosh \\tanh \\coth | | 对数/指数 | \\log \\ln \\lg \\exp | | 极值 | \\max \\min \\sup \\inf | | 极限 | \\lim \\limsup \\liminf | | 代数/概率 | \\det \\dim \\ker \\hom \\gcd \\Pr | | 角度 | \\deg | 常用示例: | 需求 | 写法 | 效果 | | ----------- | ----------------------------------------------- | ----------------------------------------------- | | 正弦函数 | $\\sin x$ | $\\sin x$ | | 对数 | $\\log\_2 n$ | $\\log\_2 n$ | | 极限 | $\\lim\_{x\\to 0}\\frac{\\sin x}{x}=1$ | $\\lim\_{x\\to 0}\\frac{\\sin x}{x}=1$ | | 行内极限正下方 | $\\lim\\limits\_{x\\to 0}\\frac{\\sin x}{x}=1$ | $\\lim\\limits\_{x\\to 0}\\frac{\\sin x}{x}=1$ | | argmax 右下角 | $\\arg\\max\_{\\theta} f(\\theta)$ | $\\arg\\max\_{\\theta} f(\\theta)$ | | argmax 居中下方 | $\\underset{\\theta}{\\arg\\max}\\, f(\\theta)$ | $\\underset{\\theta}{\\arg\\max}\\, f(\\theta)$ | ## 十一、求和、积分与微积分 | 描述 | 写法 | 效果 | | ------- | -------------------------------------- | -------------------------------------- | | 求和 | $\\sum\_{i=1}^{n} i$ | $\\sum\_{i=1}^{n} i$ | | 求积 | $\\prod\_{i=1}^{n} x\_i$ | $\\prod\_{i=1}^{n} x\_i$ | | 并和 | $\\coprod\_i A\_i$ | $\\coprod\_i A\_i$ | | 一重积分 | $\\int\_a^b f(x)\\,dx$ | $\\int\_a^b f(x)\\,dx$ | | 二重积分 | $\\iint\_D f(x,y)\\,dx\\,dy$ | $\\iint\_D f(x,y)\\,dx\\,dy$ | | 三重积分 | $\\iiint\_\\Omega f\\,dV$ | $\\iiint\_\\Omega f\\,dV$ | | 曲线/曲面积分 | $\\oint\_C f(z)\\,dz$ | $\\oint\_C f(z)\\,dz$ | | 无穷 | $\\infty$ | $\\infty$ | | 导数 | $\\frac{dy}{dx}$ | $\\frac{dy}{dx}$ | | 偏导 | $\\frac{\\partial f}{\\partial x}$ | $\\frac{\\partial f}{\\partial x}$ | | 二阶偏导 | $\\frac{\\partial^2 f}{\\partial x^2}$ | $\\frac{\\partial^2 f}{\\partial x^2}$ | | 梯度 | $\\nabla\_{\\theta}J(\\theta)$ | $\\nabla\_{\\theta}J(\\theta)$ | | 拉普拉斯算子 | $\\nabla^2 f$ | $\\nabla^2 f$ | | 微分间距 | $\\int f(x)\\,dx$ | $\\int f(x)\\,dx$ | ## 十二、向量、矩阵与线性代数 | 描述 | 写法 | 效果 | | ---- | ------------------------- | ------------------------- | | 向量 | $\\vec{x}$ | $\\vec{x}$ | | 粗体向量 | $\\mathbf{x}$ | $\\mathbf{x}$ | | 单位向量 | $\\hat{x}$ | $\\hat{x}$ | | 转置 | $A^\\top$ | $A^\\top$ | | 逆矩阵 | $A^{-1}$ | $A^{-1}$ | | 行列式 | $\\det(A)$ | $\\det(A)$ | | 范数 | $\\lVert x\\rVert\_2$ | $\\lVert x\\rVert\_2$ | | 内积 | $\\langle x,y\\rangle$ | $\\langle x,y\\rangle$ | | 点乘 | $x\\cdot y$ | $x\\cdot y$ | | 叉乘 | $x\\times y$ | $x\\times y$ | | 矩阵迹 | $\\operatorname{tr}(A)$ | $\\operatorname{tr}(A)$ | | 秩 | $\\operatorname{rank}(A)$ | $\\operatorname{rank}(A)$ | 矩阵模板: ``` $$ A = \begin{bmatrix} 1 & 2 \\ 3 & 4 \end{bmatrix} $$ ``` 渲染效果: $$ A = \\begin{bmatrix} 1 & 2 \\\\ 3 & 4 \\end{bmatrix} $$ 常见矩阵环境: | 环境 | 分隔符 | 示例 | | ------- | --- | ----------------------------------- | | matrix | 无括号 | \\begin{matrix} ... \\end{matrix} | | pmatrix | 小括号 | \\begin{pmatrix} ... \\end{pmatrix} | | bmatrix | 中括号 | \\begin{bmatrix} ... \\end{bmatrix} | | Bmatrix | 花括号 | \\begin{Bmatrix} ... \\end{Bmatrix} | | vmatrix | 单竖线 | \\begin{vmatrix} ... \\end{vmatrix} | | Vmatrix | 双竖线 | \\begin{Vmatrix} ... \\end{Vmatrix} | ## 十三、分段函数与方程组 分段函数: ``` $$ f(x)= \begin{cases} x^2, & x \ge 0, \\ -x, & x < 0. \end{cases} $$ ``` $$ f(x)= \\begin{cases} x^2, & x \\ge 0, \\\\ -x, & x < 0\. \\end{cases} $$ 方程组: ``` $$ \begin{cases} x+y=1, \\ 2x-y=3. \end{cases} $$ ``` $$ \\begin{cases} x+y=1, \\\\ 2x-y=3\. \\end{cases} $$ ## 十四、多行公式与对齐 在公式中直接回车通常不会换行;使用 `aligned`,用 `\\` 换行,用 `&` 指定对齐点。 ``` $$ \begin{aligned} \theta &\leftarrow \theta - \eta \nabla_{\theta}J(\theta) \\ \Delta\theta &\propto F^{-1}g \\ \hat{\theta} &= \underset{\theta}{\arg\max}\,P(x\mid\theta) \end{aligned} $$ ``` $$ \\begin{aligned} \\theta &\\leftarrow \\theta - \\eta \\nabla\_{\\theta}J(\\theta) \\\\ \\Delta\\theta &\\propto F^{-1}g \\\\ \\hat{\\theta} &= \\underset{\\theta}{\\arg\\max}\\,P(x\\mid\\theta) \\end{aligned} $$ 多列对齐: ``` $$ \begin{array}{rcl} a+b &=& c \\ x-y &=& z \end{array} $$ ``` $$ \\begin{array}{rcl} a+b &=& c \\\\ x-y &=& z \\end{array} $$ ## 十五、字体与字母样式 | 描述 | 写法 | 效果 | | ------- | ----------------- | ----------------- | | 罗马体 | $\\mathrm{d}x$ | $\\mathrm{d}x$ | | 粗体 | $\\mathbf{x}$ | $\\mathbf{x}$ | | 斜体 | $\\mathit{ABC}$ | $\\mathit{ABC}$ | | 无衬线 | $\\mathsf{ABC}$ | $\\mathsf{ABC}$ | | 打字机体 | $\\mathtt{ABC}$ | $\\mathtt{ABC}$ | | 花体 | $\\mathcal{D}$ | $\\mathcal{D}$ | | 黑板粗体 | $\\mathbb{R}$ | $\\mathbb{R}$ | | Fraktur | $\\mathfrak{g}$ | $\\mathfrak{g}$ | | 文本 | $\\text{if } x>0$ | $\\text{if } x>0$ | 常用机器学习记号: | 描述 | 写法 | 效果 | | ---- | ------------------------------------ | ------------------------------------ | | 数据集 | $\\mathcal{D}$ | $\\mathcal{D}$ | | 损失函数 | $\\mathcal{L}(\\theta)$ | $\\mathcal{L}(\\theta)$ | | 期望 | $\\mathbb{E}\_{x\\sim p(x)}\[f(x)\]$ | $\\mathbb{E}\_{x\\sim p(x)}\[f(x)\]$ | | 概率 | $\\Pr(X=x)$ | $\\Pr(X=x)$ | | 正态分布 | $\\mathcal{N}(\\mu,\\sigma^2)$ | $\\mathcal{N}(\\mu,\\sigma^2)$ | ## 十六、重音、修饰与标记 | 描述 | 写法 | 效果 | 描述 | 写法 | 效果 | | --- | ----------------- | ----------------- | ---- | ------------------------ | ------------------------ | | 帽子 | $\\hat{x}$ | $\\hat{x}$ | 宽帽子 | $\\widehat{xyz}$ | $\\widehat{xyz}$ | | 波浪 | $\\tilde{x}$ | $\\tilde{x}$ | 宽波浪 | $\\widetilde{xyz}$ | $\\widetilde{xyz}$ | | 横线 | $\\bar{x}$ | $\\bar{x}$ | 上划线 | $\\overline{x+y}$ | $\\overline{x+y}$ | | 点 | $\\dot{x}$ | $\\dot{x}$ | 双点 | $\\ddot{x}$ | $\\ddot{x}$ | | 向量 | $\\vec{x}$ | $\\vec{x}$ | 反向向量 | $\\overleftarrow{AB}$ | $\\overleftarrow{AB}$ | | 上标注 | $\\overset{!}{=}$ | $\\overset{!}{=}$ | 下标注 | $\\underset{x=0}{\\lim}$ | $\\underset{x=0}{\\lim}$ | ## 十七、空格、换行与文本混排 LaTeX 会自动处理大部分数学间距;需要手动调整时用这些命令。 | 写法 | 效果 | 说明 | | ---------------- | ---------------- | ------------- | | $a\\,b$ | $a\\,b$ | 小空格,常用于 \\,dx | | $a\\:b$ | $a\\:b$ | 中小空格 | | $a\\;b$ | $a\\;b$ | 中空格 | | $a\\quad b$ | $a\\quad b$ | 一个 quad | | $a\\qquad b$ | $a\\qquad b$ | 两个 quad | | $a\\!b$ | $a\\!b$ | 负空格 | | $\\text{若 } x>0$ | $\\text{若 } x>0$ | 公式中插入中文/文本 | 推荐写法: ``` \int_a^b f(x)\,dx \mathbb{E}_{x\sim p(x)}\left[ f(x) \right] f(x)=0,\quad x\ne 0 ``` ## 十八、颜色、取消线与盒子 这些命令在 MathJax 中常见,但不同平台支持程度不完全一致。 | 描述 | 写法 | 备注 | | --- | ----------------------- | ------------ | | 颜色 | $\\color{red}{x+y}$ | MathJax 常见支持 | | 盒子 | $\\boxed{x=1}$ | 核心命令,较稳 | | 背景色 | $\\bbox\[yellow\]{x+y}$ | 平台相关 | | 删除线 | $\\cancel{x}$ | 平台相关,可能需要扩展 | 较稳的渲染示例: $$ \\boxed{x=1} $$ 如果某个平台不显示 `\cancel` 或 `\bbox`,通常是该平台没有加载对应扩展;写博客时尽量少依赖这些增强命令。 ## 十九、常用模板 ### 梯度下降 ``` \theta \leftarrow \theta - \eta \nabla_{\theta}J(\theta) ``` $$ \\theta \\leftarrow \\theta - \\eta \\nabla\_{\\theta}J(\\theta) $$ ### 最大似然估计 ``` \hat{\theta} = \underset{\theta}{\arg\max}\,P(x\mid\theta) ``` $$ \\hat{\\theta} = \\underset{\\theta}{\\arg\\max}\\,P(x\\mid\\theta) $$ ### 贝叶斯公式 ``` P(A\mid B)=\frac{P(B\mid A)P(A)}{P(B)} ``` $$ P(A\\mid B)=\\frac{P(B\\mid A)P(A)}{P(B)} $$ ### softmax ``` \operatorname{softmax}(z_i) = \frac{e^{z_i}}{\sum_{j=1}^{K} e^{z_j}} ``` $$ \\operatorname{softmax}(z\_i) = \\frac{e^{z\_i}}{\\sum\_{j=1}^{K} e^{z\_j}} $$ ### 交叉熵 ``` \mathcal{L} = -\sum_{i=1}^{n} y_i \log \hat{y}_i ``` $$ \\mathcal{L} = -\\sum\_{i=1}^{n} y\_i \\log \\hat{y}\_i $$ ### 高斯分布 ``` \mathcal{N}(x;\mu,\sigma^2) = \frac{1}{\sqrt{2\pi\sigma^2}} \exp\left(-\frac{(x-\mu)^2}{2\sigma^2}\right) ``` $$ \\mathcal{N}(x;\\mu,\\sigma^2) = \\frac{1}{\\sqrt{2\\pi\\sigma^2}} \\exp\\left(-\\frac{(x-\\mu)^2}{2\\sigma^2}\\right) $$ ### 矩阵乘法 ``` C_{ij}=\sum_{k=1}^{n} A_{ik}B_{kj} ``` $$ C\_{ij}=\\sum\_{k=1}^{n} A\_{ik}B\_{kj} $$ ## 二十、避坑清单 | 问题 | 推荐做法 | | -------------------- | ---------------------------------------------------- | | 行内公式太高 | 换成独立公式,或使用 \\tfrac | | 下标没有在正下方 | 行内加 \\limits,或用 \\underset | | 多字符上下标只显示第一个字符 | 用 {},例如 x\_{n+1} | | 公式中回车不换行 | 使用 aligned 或 array,并用 \\\\ | | 多行公式没有对齐 | 在等号/箭头前加 & | | 绝对值和范数太矮 | 用 \\left\\lvert...\\right\\rvert 或 \\lVert...\\rVert | | 竖线符号语义不清 | 条件概率推荐 \\mid,范数推荐 \\lVert...\\rVert | | 函数名变成斜体 | 用 \\sin、\\log、\\max、\\operatorname{rank} | | 中文或普通文字在公式中显示异常 | 用 \\text{...} | | Markdown 表格里写公式被竖线拆开 | 避免裸竖线,改用 \\mid、\\lvert、\\rvert | | 平台不支持某命令 | 优先使用 MathJax/KaTeX 常见核心命令,少用增强扩展 | ### 本站 Markdown + MathJax 特别注意 本站页面的公式流程是:Markdown 先被 Zola 渲染成 HTML,浏览器里 MathJax 再渲染公式。因此某些 TeX 写法本身没错,但可能在 MathJax 看到之前已经被 Markdown 改坏。复杂公式优先用 `{% math() %}…{% end %}`。 | 容易踩坑的写法 | 稳定写法 | 原因 | | --------------------- | ---------------------------------------- | --------------------------- | | \\bigg\\{...\\bigg\\} | \\bigg\\lbrace...\\bigg\\rbrace | \\{ 可能被 Markdown 当作转义处理 | | \\pi^\* | \\pi^{\\ast} | \* 可能触发 Markdown 斜体 | | P\\\|Q 或 P||Q | P\\Vert Q | 竖线容易被 Markdown 或表格语法干扰 | | \\\|\\Delta\\theta\\| | \\lVert\\Delta\\theta\\rVert | 范数语义更清楚,也更稳 | | 行尾 \\\\ | 行尾 \\\\\\\\,或使用 math shortcode 后写正常 \\\\ | Markdown 行尾反斜杠可能被当作硬换行 | | \_ 多的长公式 | 用 math shortcode | 下标里的 \_ 可能和 Markdown 强调语法打架 | 如果写在 `math` shortcode 里,上面这些可以按更接近原生 MathJax 的习惯写;如果直接写在普通 Markdown 里,就优先使用右侧稳定写法。 ## 二十一、快速索引 | 想查 | 关键词 | | -------------------- | ---------------------------------- | | 正下方、argmax、max 下标 | \\limits、\\underset | | 约等于、正比、不等于 | \\approx、\\propto、\\ne | | 希腊字母 theta、tau、Delta | \\theta、\\tau、\\Delta | | 梯度、偏导、拉普拉斯 | \\nabla、\\partial、\\nabla^2 | | 多行公式、换行、对齐 | aligned、\\\\、& | | 矩阵、方程组、分段函数 | bmatrix、cases、array | | 花体、黑板粗体、实数集 | \\mathcal、\\mathbb | | 括号自动变大 | \\left、\\right | | 箭头、映射、带字箭头 | \\leftarrow、\\mapsto、\\xrightarrow | 参考: - [科学空间:MathJax 在线 / LaTeX 参考](https://spaces.ac.cn/latex.html?ref=blog.somnus.wiki) - 本站 Markdown 数学公式写作习惯与常用机器学习公式模板 ### 友链 URL: https://blog.somnus.wiki/you-lian/ Last updated: 2026-07-21T08:13:19.000Z 欢迎来到我的友链页面! - [somnus 的 GitHub](https://github.com/somnus4214?ref=blog.somnus.wiki):我的个人代码仓库,主要分享计算机视觉、深度学习、ROS 开发以及 C++/Rust 相关的项目与学习记录。 - [leon 的 X (Twitter)](https://x.com/HaitangM38838?ref=blog.somnus.wiki):分享技术日常与生活动态,欢迎来找我交流互动! ### Privacy Policy for ripfullpage URL: https://blog.somnus.wiki/ripfullpage/ Last updated: 2026-07-21T07:57:13.000Z # Privacy Policy for ripfullpage Effective Date: June 12, 2026 ripfullpage is a Chrome extension for capturing, cropping, and saving webpage screenshots. ## Data Collection ripfullpage does not collect, store, sell, rent, or share any personal information. ## Screenshot Processing All screenshot capture, cropping, and image processing are performed locally in the user’s browser. ripfullpage does not upload screenshots, webpage content, browsing history, or any other user data to external servers. ## Permissions ripfullpage may request browser permissions such as `activeTab`, `scripting`, `storage`, and `downloads`. These permissions are used only to: - Capture the current webpage - Process screenshots locally in the browser - Save screenshots to the user’s device - Store extension preferences locally ## Third-party Services ripfullpage does not use third-party analytics, advertising SDKs, tracking services, or remote code. ## Data Security Since ripfullpage does not collect or transmit user data, no personal data is stored on external servers. ## Changes to This Policy This privacy policy may be updated from time to time. Any changes will be posted on this page. ## Contact If you have any questions about this privacy policy, please contact: chen1921460502@gmail.com ## Posts ### 采购清单 URL: https://blog.somnus.wiki/p/cai-gou-qing-dan/ Last updated: 2026-09-17T07:07:41.000Z ## 仪器列表--陈磊2026E8013282001 1. TB-3060 标准光学面包板,尺寸:600 mm × 300 mm × 12.7 mm,M6螺纹贯穿孔,材质:6061-T6铝合金 1558¥ ![image.png](https://img.somnus.wiki/file/1789616040650_image.png) 1. MKB-50A 12.7 mm接杆支架套装A,包含12.7 mm标准接杆支架、12.7 mm接杆(50 mm长)、标准支架底座(无磁)和叉式压块 2个 217¥\*2 ![image.png](https://img.somnus.wiki/file/1789628732998_image.png) 1. MKB-100A 12.7 mm接杆支架套装A,包含12.7 mm标准接杆支架、12.7 mm接杆(100 mm长)、标准支架底座(无磁)和叉式压块 2个 232¥\*2 ### 理财方案 URL: https://blog.somnus.wiki/p/li-cai-fang-an/ Last updated: 2026-09-16T09:05:42.000Z **你的整体思路可以定为:先保障生活和备用金,再为旅游存钱,最后把长期不用的钱分成短债和股票指数基金。用不同产品区分用途,不需要每天记账。** 按你每月收入 **4300 元、日常花销 2000 元**计算,每月可以安排的结余是 **2300 元**。这以收入正常到账、没有额外大额支出为前提。 钱固定分成这五部分: | 用途 | 放在哪里 | 使用规则 | | --------- | ------------------- | ----------------------- | | 日常生活 | 招行二类消费卡 | 每月预算 2000 元 | | 备用金 | 一类卡的朝朝宝 | 目标 10000 元,用于突发支出、补贴延迟等 | | 旅游及其他计划支出 | 单独一只货币基金 | 按预算积攒,需要付款时取用 | | 保守投资 | 一只短债或超短债纯债基金 | 放预计至少半年至一年不用、能接受净值波动的钱 | | 激进投资 | 一只沪深300等宽基指数基金或联接基金 | 放预计 5 年以上不用、能承受较大亏损的钱 | 这些期限是资金安排建议,**不代表持有到期就一定赚钱**。 你现在的 **8200 元**,先留出本月剩余生活费 **1000 元**。如果其余 **7200 元尚未投资**,考虑到还要准备寒假旅游,我建议先放入朝朝宝建立备用金。已经买入的基金不必为了凑这个数字专门卖掉,按实际余额继续安排即可。 从下一个完整月份开始,收入到账后这样转: - 家里给的 **1500 元**:汇总到一类卡,再转入二类消费卡。 - 补贴合计 **2800 元**:从其他银行汇总到一类卡,其中 **500 元**补到消费卡。 - 剩余 **2300 元**,按下面对应阶段分配。补贴没到账,就暂缓当月投资。 | 每月资金去向 | 备用金未满,正在攒旅游钱 | 备用金已满,正在攒旅游钱 | 备用金已满,恢复日常安排 | | ------- | ------------ | ------------ | ------------ | | 日常生活 | 2000 | 2000 | 2000 | | 旅游/计划支出 | 1000 | 1000 | 500 | | 补充备用金 | 1300 | 0 | 0 | | 短债基金 | 0 | 600 | 800 | | 宽基指数基金 | 0 | 700 | 1000 | | 合计 | 4300 | 4300 | 4300 | 这里的旅游 **1000 元/月**,仍然沿用“预算 3000 元、分三个月存”的例子。实际应按你的预算和**首次订票付款前还剩几次收入到账**来调整。备用金达到 10000 元的当月,只补足差额,剩余部分再安排投资。 最后一列的 **500 元计划支出**,可以用于下一次旅游、换设备等。已经确定近期要花的钱多于这个额度,就临时减少投资额,不挪用备用金。 结合你的截图,这套方案主要用两个入口: - **「朝朝宝」**:管理备用金。 - **「基金」**:购买旅游用的货币基金、保守部分的短债基金、激进部分的指数基金。 旅游货币基金可以先搜索之前提到的 **招商招钱宝货币 B(000607)**作为候选,官网标示为低风险货币型;是否能买、到账时间,以你的招行页面为准。[官方产品介绍](https://www.cmfchina.com/web/fundDetail/000607/index.html?ref=blog.somnus.wiki)⁠ 截图里的“周周宝、月月宝”等暂时不用加入方案。仅凭首页展示的历史年化数字,无法判断具体产品的风险和取用条件,也不能把它们当成承诺利率。 **之后只执行几个简单规则:** - **旅游钱按需赎回**:订票、订酒店前按到账规则提前取出,不等收益达到某个数字。 - **短债和指数基金没有必须定期“收回来”的要求**:没有用钱需求、产品仍适合,就可以继续持有;确定未来要用的钱,提前逐步转入货币基金等低风险安排。 - **备用金用掉后优先补回**:暂停新增投资;旅行等可调整的支出也相应缩减。 - **每月分一次钱,每半年检查一次配置**:平时看消费卡余额控制开销,看各产品余额区分用途。 朝朝宝和这些基金都应按非保本产品理解;货币基金风险较低,也仍存在亏损可能。[货币基金风险说明](https://static.cmfchina.com/fundarticle/20260324/20021174543596.pdf?ref=blog.somnus.wiki)⁠ ### 研一选课计划 URL: https://blog.somnus.wiki/p/yan-1xuan-ke-ji-hua/ Last updated: 2026-09-02T08:26:22.000Z ## 秋季最终选课单 ### 1\. 085410本专业学位课:10学分 以下四门全部设置为“学位课”: | 课程 | 学分 | 属性 | | ----------- | ------ | --------- | | 算法设计与分析 | 3 | 人工智能专业核心课 | | 人工智能与机器学习方法 | 3 | 人工智能专业核心课 | | 高等数值分析 | 2 | 人工智能专业课 | | 图像分析及应用 | 2 | 人工智能专业课 | | **合计** | **10** | 完成2核心+2专业 | ### 2\. 公共必修:7学分 | 课程 | 学分 | 选班建议 | | ---------------- | ----- | ------ | | 新时代中国特色社会主义理论与实践 | 2 | **2班** | | 学术道德与学术写作规范 | 1 | **2班** | | 工程伦理 | 1 | 直接选 | | 硕士学位英语 | 3 | 选择慕课 | | **合计** | **7** | | 这里建议选《学术道德》2班,是因为1班周一下午与瑜伽、排球冲突。 ### 3\. 公共选修:1.5学分 | 课程 | 学分 | 说明 | | ----------------- | ------- | ----------- | | 人工智能发展历程及其伦理、风险议题 | 1 | AI相关公共选修 | | 瑜伽或排球 | 0.5 | 二选一 | | **合计** | **1.5** | 春季再修0.5学分足球 | 这样春季修0.5学分足球以后,两学期公共选修正好2学分,没有多修。 ## 秋季周课表 | 节次/时间 | 周一 | 周二 | 周三 | 周四 | 周五 | 周六 | 周日 | | ------------------ | -------------------------------- | ---------------------------------- | ------------------------------- | ---------------------- | --------------------------------------------------- | --------------------------- | -------------------------------- | | **1节**08:30–09:15 | | | | | | | | | **2节**09:20–10:05 | | **高等数值分析**2~9周A101 | **算法设计与分析**2~20周A101 | | | **图像分析及应用**2~3、7~17、19周B203 | **人工智能与机器学习方法**2、6~12、14、16周B203 | | **3节**10:25–11:10 | | **高等数值分析**2~5、7~9周A101 | **算法设计与分析**2~20周A101 | | | **图像分析及应用**2~3、7~17、19周B203 | **人工智能与机器学习方法**2、6~12、14、16周B203 | | **4节**11:15–12:00 | | **高等数值分析**2~5、7~9周A101 | **算法设计与分析**2~20周A101 | | | **图像分析及应用**2~3、7~17、19周B203 | **人工智能与机器学习方法**2、6~12、14、16周B203 | | **5节**13:30–14:15 | | **新时代中国特色社会主义理论与实践2班**2~13周图书馆A407 | **人工智能发展历程及其伦理、风险议题**14~20周A101 | | **人工智能发展历程及其伦理、风险议题**14~20周A101 | | | | **6节**14:20–15:05 | | **新时代中国特色社会主义理论与实践2班**2~13周图书馆A407 | **人工智能发展历程及其伦理、风险议题**14~20周A101 | | **人工智能发展历程及其伦理、风险议题**14~20周A101 | | | | **7节**15:25–16:10 | **瑜伽或排球**2~5、7~17周 | **新时代中国特色社会主义理论与实践2班**2~13周图书馆A407 | **人工智能发展历程及其伦理、风险议题**14~20周A101 | **高等数值分析**2~4、6~8周A101 | **人工智能发展历程及其伦理、风险议题**14~20周A101 | | | | **8节**16:15–17:00 | **瑜伽或排球**2~5、7~17周 | | | **高等数值分析**2~4、6~8周A101 | | | | | **9节**17:05–17:50 | | | | **高等数值分析**2~4、6~8周A101 | | | | | **10节**18:30–19:15 | **学术道德与学术写作规范2班**2~5、7~9周图书馆A407 | | **工程伦理**2~8周图书馆A421 | | | | | | **11节**19:20–20:05 | **学术道德与学术写作规范2班**2~5、7~9周图书馆A407 | | **工程伦理**2~8周图书馆A421 | | **人工智能与机器学习方法**2~3、6~12、14周B203**算法设计与分析**仅20周,A101 | | | | **12节**20:15–21:00 | **学术道德与学术写作规范2班**2~5、7~9周图书馆A407 | | **工程伦理**2~8周图书馆A421 | | **人工智能与机器学习方法**2~3、6~12、14周B203**算法设计与分析**仅20周,A101 | | | | **13节**21:05–21:50 | | | | | **人工智能与机器学习方法**2~3、6~12、14周B203**算法设计与分析**仅20周,A101 | | | ## 明确不选 秋季不再选择: - **云计算技术与工程** - **计算机体系结构** - 其他只为凑学分的课程 原因是春季仍要满足至少10学分,提前修这两门不能抵扣春季最低学分,只会增加秋季压力。 ## 选课当天的顺序 建议按照这个优先级操作: 1. 人工智能与机器学习方法 2. 算法设计与分析 3. 图像分析及应用 4. 高等数值分析 5. 新时代中国特色社会主义理论与实践2班 6. 学术道德与学术写作规范2班 7. 工程伦理 8. 硕士学位英语慕课 9. 人工智能发展历程及其伦理、风险议题 10. 瑜伽或排球二选一 最终结果: > **秋季18.5学分,春季10学分,两学期共28.5学分。** 秋季直接完成085410的“2核心+2专业”,春季再用《智能博弈》把专业学位课补到12.5学分。这是目前学分结构最干净、又能兼顾春季实习准备的方案。 ### 2026-08-22|拔智齿 URL: https://blog.somnus.wiki/diary/2026-08-22-ba-zhi-chi/ Last updated: 2026-08-22T14:46:42.000Z > omg,为什么要有智齿,拔牙拔了快一个小时,疼死我了。 ## 情况介绍 我在今年寒假的时候因为左下智齿萌出导致藏污纳垢引起发炎,然后当时就决定为了根治这个毛病,就把那颗智齿拔掉,医生给我拍了牙片(如下),当时医生推荐我把图片中右下右上的智齿都一口气拔掉,当时我带着上面的智齿还算正,而且也不疼就拒绝了(现在非常后悔😇),于是当时就把那颗该死的智齿拔掉了,爽爽过了半年。但是从图中看到,图片中左下角的那个智齿非常致命是一个斜着长的智齿。 ![IMG_5953.jpeg](https://img.somnus.wiki/file/1787408251843_IMG_5953.jpeg) 这个智齿就是阻生智齿,会在生长的过程中逐渐挤压前面的一颗板牙。一方面容易导致一系列的牙被挤歪,另一方面还会与前面的板牙融合导致出现更大的问题。 于是我一直想着找机会把这个智齿也拔了,经历了上次的拔牙痛苦,这次索性就一次拔两颗,少经历一次痛苦也算好。 ## 拔牙过程 今天早上7点我就出门去吃早饭并在8点就到了诊所,、从9点开始打麻药并拔牙一直拔到10点,虽然麻药挺有效果的,减少了很多疼痛,但是还是会有很多难受的地方,比如嘴一直张着,牙医拿一个东西硬撬,会有种胀痛感,然后我的上面的智齿特别难拔据说是牙根和骨头连的太紧了。 于是只能把牙齿做一下切分,用那个超声波钻头把牙齿开,然后在松动后一点点拔下来。这一段真的巨痛😭。 ## 保养注意 ### 饮食 - 在拔完牙的两个小时内,不要吃任何的东西喝任何的东西,此时刚形成的血凝块特别特别脆弱。 - 2~24h:此时主要以**冷的、凉的**流食为主,比如*藕粉*(泡完冷却)、*酸奶、牛奶、冷水*,一定不能吃热的,容易导致血液流动加快,影响血凝块形成。 - 24~72h:此时和上面差不多,最好还是冷凉的流食,但可以增加一点能量的摄入,为昨天没有足够的蛋白质摄入,可以考虑*鸡蛋羹、酸奶、牛奶*。同样一定要是冷的或者凉的。 - 72~ 7天:如果疼痛和肿胀逐渐下降,可以慢慢接近正常饮食,比如软米饭、面条、馄饨、蒸鱼、炖得很软的肉、鸡蛋和软蔬菜。仍然尽量用另一侧咀嚼,尤其不要让坚硬碎屑反复进入下颌拔牙窝。 **一周以后**:如果伤口恢复顺利,就可以根据舒适程度逐渐恢复正常饮食。复杂阻生智齿的拔牙窝完全长好需要更久,所以即使已经不疼,也不用急着拿拔牙侧啃很硬的东西。 ## ### 国科大南京学院 ↔ 金马路班车速查表 URL: https://blog.somnus.wiki/p/banche/ Last updated: 2026-08-28T13:37:10.000Z _This post is for paying subscribers only._ ### 从王虹爆火到媒体失格:左派叙事遇到女性 URL: https://blog.somnus.wiki/essays/wanghong/ Last updated: 2026-08-04T10:02:09.000Z ### 传统媒体的“路径依赖”:用道德纯度代偿专业门槛 在过去几十年里,我国媒体形成了一套固定的宣传公式:**用极端的“苦难”和“自我牺牲”来论证一个人的伟大。** - **逻辑自洽的年代:** 当年大众的受教育水平有限,听不懂哥德巴赫猜想,也搞不清GDP和板块构造。为了降低传播门槛,媒体选择了一条捷径:通过描绘科学家和干部的“生活白痴”或“苦行僧”形象(如陈景润撞电线杆、焦裕禄忍痛工作、李四光穿补丁衣服),来向大众传递一个简单粗暴的逻辑——**他生活极其艰苦、对物质毫无所求,说明他是个无私的好人;一个无私的好人,他的学术成果或工作成绩必定是顶级的。** - **极致奉献等于最高赞美:** 在这种左翼叙事下,发高烧不回家、拿几百块工资住6平米宿舍,就是对劳动者最高的荣誉加冕。你越像个没有任何世俗欲望的“圣徒”,你的社会评价就越高。 ### 新时代的叙事崩塌:科技新贵不按套路出牌 到了2026年,大众的受教育程度早已今非昔比,完全能听懂数据、指标和实际贡献。然而,媒体依然深陷在这套老旧的模板里无法自拔,这直接导致了近两年科技与互联网报道的全面脱节。 - **套不进模板的现代精英:** 面对中美在AI领域的激烈角逐,国内媒体集体失语。原因很简单:像DeepSeek、Kimi这样的AI团队,或者米哈游这样的百亿创业者,他们可能玩摇滚、逛夜店、看动漫、财务自由,这完全粉碎了媒体“馒头配咸菜”的艰苦奋斗滤镜。媒体不知道该怎么报道不吃苦的成功者。 - **韦神现象的本质:** 韦神之所以能获得极高的媒体热度,并不是他需要媒体,而是**媒体太需要韦神了**。他是这个时代极其罕见的、完美嵌合“衣着朴素、吃馒头喝开水、钻研世界难题”这一传统模板的人物。媒体不过是拿着几十年的旧模具,终于在沙滩上找到了一个能扣进去的贝壳。 ### 火星撞地球:道德叙事与当代女性主义的决裂 这种叙事危机在遭遇现代女性主义时,爆发了最剧烈的冲突(例如此次关于王虹以及卡地亚首饰的报道争议)。 - **两种价值观的对撞:** 传统宣传极度强调“奉献与无私”,而当代女性主义极度重视“权益与回报”。在年轻男性群体中,这套艰苦朴素的叙事可能只会一笑而过;但在年轻女性群体中,要求一位顶尖女科学家“艰苦朴素”,会被立刻视为职场剥削和性别打压。 - **“卡地亚争议”的根源:** 媒体并不是在故意歧视女性,而是他们的大脑依然运行着那套旧系统。在他们的惯性思维里,表扬一个科学家的标准流程就是无视她的物质享受,去挖掘她“朴素、奉献”的一面。但当他们试图把这种“贫困户”式的赞美强加给一位佩戴名贵首饰、成就斐然的新时代女学者时,系统直接“宕机”了。 ### 总结 从女脱口秀演员嘲笑保尔·柯察金,到大众反感“大国工匠挤宿舍”,社会思潮已经发生了不可逆的转向。现代年轻人拒绝接受付出与收益的严重倒挂,更拒绝将“自我牺牲”作为评价专业能力的唯一标尺。 传统媒体如果不能摒弃这套没苦硬吃的“道德叙事”,重新建立起以事实、成果和专业度为核心的“绩效叙事”,他们在面对未来那些个性鲜明、追求现世回报的新生代科学家和各界领军人物时,只会陷入更加难堪的失语与翻车境地。 ### 为 GitHub Actions 配置腾讯云专用部署用户 URL: https://blog.somnus.wiki/p/githubactions-for-tencentcloud/ Last updated: 2026-07-29T03:26:58.000Z > 最近在公司实习的期间,在我的腾讯云轻量服务器上跑着的一个[项目](https://github.com/somnus0917/luopanhacker?ref=blog.somnus.wiki),然后我算真正意义的完整尝试了 CICD 的流程,感觉非常震撼,好用,同样也遇到了今天的问题 ## 前情提要 我在 codex 的辅助下设计了一套工作流,提交到了 github 都会触发一次 actions,具体 actions 内容如下。 ```yml name: Deploy production on: push: branches: [main] workflow_dispatch: concurrency: group: luopan-production cancel-in-progress: false permissions: contents: read jobs: ci: name: Validate code and deployment configuration runs-on: ubuntu-latest steps: - name: Check out committed source uses: actions/checkout@v6 - name: Set up Python uses: actions/setup-python@v6 with: python-version: '3.11' - name: Install uv run: | curl -LsSf https://astral.sh/uv/0.11.29/install.sh | \ env UV_UNMANAGED_INSTALL="$HOME/.local/bin" sh echo "$HOME/.local/bin" >> "$GITHUB_PATH" - name: Sync locked Python environment run: uv sync --locked --no-dev - name: Run Python syntax and unit checks run: | uv run --locked python -m compileall -q apps tests uv run --locked python -m unittest discover -s tests -v - name: Run Rust tests run: cargo test --workspace - name: Set up Node uses: actions/setup-node@v6 with: node-version: '20' - name: Set up pnpm uses: pnpm/action-setup@v4 with: version: 10.24.0 - name: Install frontend dependencies working-directory: apps/web run: pnpm install --frozen-lockfile - name: Check and build frontend working-directory: apps/web run: | pnpm check pnpm build - name: Check JavaScript and shell scripts run: | node --check web/static/app.js bash -n ops/deploy.sh ops/backup.sh ops/ssh-deploy-wrapper.sh \ docker/start.sh docker/cron_scrape.sh docker/cron_inventory_sync.sh \ docker/run_daily.sh docker/test-deploy.sh \ scripts/run_daily.sh scripts/run_dashboard.sh scripts/run_inventory_sync.sh \ scripts/deploy_from_github.sh - name: Validate Docker Compose production mounts run: | docker compose config >/dev/null LUOPAN_DATA_DIR=/srv/luopan-data docker compose config | \ grep -q '/srv/luopan-data/state' grep -q '^STORAGE_SYNC_AFTER_SCRAPE=true$' docker/crontab grep -q 'STORAGE_SYNC_AFTER_SCRAPE:-true' docker/cron_scrape.sh for runtime_dir in config logs output session state; do grep -q -- "--exclude='${runtime_dir}/'" ops/deploy.sh done grep -q -- '--alias douyin-compass-collector' ops/deploy.sh deploy: needs: ci runs-on: ubuntu-latest steps: - name: Configure restricted deployment key env: DEPLOY_KEY: ${{ secrets.LUOPAN_DEPLOY_SSH_KEY }} KNOWN_HOSTS: ${{ secrets.LUOPAN_DEPLOY_KNOWN_HOSTS }} run: | install -m 700 -d ~/.ssh printf '%s\n' "$DEPLOY_KEY" > ~/.ssh/id_ed25519 chmod 600 ~/.ssh/id_ed25519 printf '%s\n' "$KNOWN_HOSTS" > ~/.ssh/known_hosts chmod 600 ~/.ssh/known_hosts - name: Deploy committed main branch env: DEPLOY_HOST: ${{ secrets.LUOPAN_DEPLOY_HOST }} DEPLOY_USER: ${{ secrets.LUOPAN_DEPLOY_USER }} run: | ssh -i ~/.ssh/id_ed25519 -o BatchMode=yes -o StrictHostKeyChecking=yes \ -o ServerAliveInterval=30 -o ServerAliveCountMax=20 \ "${DEPLOY_USER}@${DEPLOY_HOST}" "deploy ${GITHUB_SHA}" ``` 其实对于我来说这个项目的 CI 基本上都是走个流程,因为如果本地运行不通过的话,我都不会提交上去。所以主要看 CD 部分。 ```yml deploy: needs: ci runs-on: ubuntu-latest steps: - name: Configure restricted deployment key env: DEPLOY_KEY: ${{ secrets.LUOPAN_DEPLOY_SSH_KEY }} KNOWN_HOSTS: ${{ secrets.LUOPAN_DEPLOY_KNOWN_HOSTS }} run: | install -m 700 -d ~/.ssh printf '%s\n' "$DEPLOY_KEY" > ~/.ssh/id_ed25519 chmod 600 ~/.ssh/id_ed25519 printf '%s\n' "$KNOWN_HOSTS" > ~/.ssh/known_hosts chmod 600 ~/.ssh/known_hosts - name: Deploy committed main branch env: DEPLOY_HOST: ${{ secrets.LUOPAN_DEPLOY_HOST }} DEPLOY_USER: ${{ secrets.LUOPAN_DEPLOY_USER }} run: | ssh -i ~/.ssh/id_ed25519 -o BatchMode=yes -o StrictHostKeyChecking=yes \ -o ServerAliveInterval=30 -o ServerAliveCountMax=20 \ "${DEPLOY_USER}@${DEPLOY_HOST}" "deploy ${GITHUB_SHA}" ``` 注意,需要在本地生成一对公钥和私钥,公钥放到服务器,私钥则放到 GitHub 的 actions 的 env 中。通过以下命令来生成密钥对: ```zsh ssh-keygen -t ed25519 -f ~/.ssh/key_name -C "comment_message" ``` ![截屏2026-07-27 18.23.11.png](https://img.somnus.wiki/file/1785147813532_%E6%88%AA%E5%B1%8F2026-07-27_18.23.11.png) 公钥要放到服务器的\~/.ssh/authorized\_keys,然后就可以免密登录了,也可以成功 CD 了。 ## 问题描述 现在是能成功部署的,但是问题是,每次提交触发 actions 都给我弹这样的消息。 ![mosaic-tskau.png](https://img.somnus.wiki/file/1785148486603_mosaic-tskau.png) 原因也很明显,GitHub actions 的部署是在 Azure 的服务器上,而这些服务器又是美国的,我的部署命令也是通过ssh访问服务器来实现的。频繁的部署就会频繁的发,这样还是有点烦的。 ## 问题解决 这个问题如何解决呢,我问过很多的 ai,给出的方案都是差不多的,一个是指定CICD 使用的用户名(腾讯云上给这个用户添加到白名单),另一个就是通过 cloudflare tunnel 连接,本文中使用的是方法一。 ### 确认报警源 第一步,你收到任何的报警信息要先确定是不是自己触发的,通过核对登录时间和 actions 操作时间来判断。 不要把陌生人的恶意访问当成 actions。 ### 创建专用账号 连接到服务器,通过以下命令来创建一个全新的用户: ```bash sudo adduser \ --disabled-password \ --gecos "" \ user-name sudo passwd -l uaer-name ``` 值得注意的是,由于我们之后要将这个用户加到白名单,所有要放行一切登录行为,为了避免被恶意攻击,所以我们关闭密码登录功能,只能通过密钥来登录。 ### 单独创建密钥 为了避免因为密钥泄露导致的无限制恶意访问,所以最好单独创建一个新的密钥。 在自己的电脑本地: ```bash ssh-keygen \ -t ed25519 \ -f ~/.ssh/github_actions \ -C "github-actions" \ -N "" ``` `-f` 是位置和密钥名字;`-t` 是密钥类型,通常使用`ed25519` ;`-C` 是备注信息。 和上面一样会得到两个密钥 - 私钥 → GitHub Actions Secret - 公钥 → 服务器的 authorized\_keys ![image.png](https://img.somnus.wiki/file/1785232594385_image.png) 注意这里的 secrets 的 name 要和上面 actions.yml 中对应。 ### 给新建用户提权 新建的用户默认是不可能有权限去运行另一个用户文件夹内的文件的,所以我们需要给这个用户赋予一定的权限。 有两种方式,一种是给他赋项目文件夹和 docker(我的项目需要启动 docker) 的权。通过下面这两个命令。 ```bash sudo usermod -aG docker user-name sudo chown -R username /home/ubuntu/luopan-app ``` 但是这样的话权限就会有点过于大了,因为 docker 本身就是一个很容易越权的服务。 所以更推荐第二种方式,就是只给这个用户赋予deploy.sh的权限。 幸好我的项目是有单独的部署脚本,所以只需要用户有权利运行deploy.sh就可以完成部署。 #### deploy.sh的两种模式 deploy.sh默认会有接受 SHA 和不接受 SHA 的两种情况,在有 SHA 的时候会下载并部署指定 commit,而没有的时候会git fetch origin main → git reset --hard origin/main。这样的设计为的是方便两种部署方式,一种是 actions,另一种直接 ssh 连接部署。 于是我们的权限设计也可以照顾这样的两种模式(实际上不照顾也行,因为默认actions 是会自带 SHA 的,而常规情况也不会通过这个用户来登录服务器)。 #### 权限赋予 我们可以通过这样的命令来赋予权限(必须带 SHA): ```bash sudo tee /usr/local/sbin/luopan-ssh-gateway >/dev/null <<'EOF' #!/usr/bin/env bash set -Eeuo pipefail readonly DEPLOY_SCRIPT="/home/ubuntu/luopan-app/ops/deploy.sh" readonly RECEIVED_COMMAND="${SSH_ORIGINAL_COMMAND:-}" if [[ "${RECEIVED_COMMAND}" =~ ^deploy\ ([0-9a-f]{40})$ ]]; then readonly DEPLOY_SHA="${BASH_REMATCH[1]}" exec sudo -n -u ubuntu -- "${DEPLOY_SCRIPT}" "${DEPLOY_SHA}" fi echo 'This SSH key only permits:' >&2 echo ' deploy <40-character lowercase Git SHA>' >&2 exit 126 EOF sudo chown root:root /usr/local/sbin/luopan-ssh-gateway sudo chmod 755 /usr/local/sbin/luopan-ssh-gateway ``` 这段代码的意思是在`/usr/local/sbin` 文件夹下创建luopan-ssh-gateway这样的网关脚本 ``` set -Eeuo pipefail ``` 这句表明使用严格模式,有任何错误都会尽快退出。 ```bash readonly DEPLOY_SCRIPT="/home/ubuntu/luopan-app/ops/deploy.sh" readonly RECEIVED_COMMAND="${SSH_ORIGINAL_COMMAND:-}" ``` 这两句则是定义了`DEPLOY_SCRIPT` 和`RECEIVED_COMMAND` ,前面的`readonly` 表明定义完之后不可变。 第二句中的`SSH_ORIGINAL_COMMAND` 就是在运行 openssh 的时候如果命令被截断(我会在下面定义 authorized\_keys来截断),原本要执行的 command 都会注入到这个变量里面。 {变量:-}表示: - 如果变量存在,使用它的值; - 如果变量不存在或为空,使用空字符串。 比如有人尝试直接打开交互式 Shell: ``` ssh luopan-deploy@服务器 ``` 此时没有提交具体命令,`RECEIVED_COMMAND` 就会是空字符串,随后被拒绝。 ```bash if [[ "${RECEIVED_COMMAND}" =~ ^deploy\ ([0-9a-f]{40})$ ]]; then readonly DEPLOY_SHA="${BASH_REMATCH[1]}" exec sudo -n -u ubuntu -- "${DEPLOY_SCRIPT}" "${DEPLOY_SHA}" fi ``` 这一步则是正则匹配,并把匹配到的 SHA 存到`DEPLOY_SHA` ,然后则**以 ubuntu(默认用户)**来运行这个deploy.sh。这里就是**提权**的核心。 ```bash echo 'This SSH key only permits:' >&2 echo ' deploy <40-character lowercase Git SHA>' >&2 ``` 最后这两句就是拒绝非法指令。 ```bash sudo chown root:root /usr/local/sbin/luopan-ssh-gateway sudo chmod 755 /usr/local/sbin/luopan-ssh-gateway ``` 这两句大致是一个意思,就是让普通用户有权读取这个脚本,但是没权利修改。避免被用户不小心改掉。 ### 授权执行部署脚本 上面提到的最后一步就是`sudo -n -u ubuntu – "${DEPLOY_SCRIPT}" "${DEPLOY_SHA}`,而默认一个用户是**无权**以另一个用户的身份来执行任务的。 ``` sudo visudo -f /etc/sudoers.d/luopan-deploy ``` 写入以下内容: ``` Cmnd_Alias LUOPAN_DEPLOY_COMMANDS = /home/ubuntu/luopan-app/ops/deploy.sh, /home/ubuntu/luopan-app/ops/deploy.sh * luopan-deploy ALL=(ubuntu) NOPASSWD: LUOPAN_DEPLOY_COMMANDS ``` 赋权 ``` sudo visudo -cf /etc/sudoers.d/luopan-deploy sudo chmod 440 /etc/sudoers.d/luopan-deploy sudo -l -U luopan-deploy ``` ### 服务器authorized\_keys添加。 #### 创建`.ssh` 目录 ``` sudo install \ -d \ -m 700 \ -o luopan-deploy \ -g luopan-deploy \ /home/luopan-deploy/.ssh ``` 700权限就是`rwx------` ,也就是只有当前用户有权限。`-o`和`-g`就是设定所有者和所属组,后面都是跟用户名。 最后得到的是这个: ``` drwx------ 2 luopan-deploy luopan-deploy ... /home/luopan-deploy/.ssh ``` #### 生成受限制的`authorized_keys` ``` { printf 'restrict,command="/usr/local/sbin/luopan-ssh-gateway" ' cat /tmp/luopan_github_actions.pub } | sudo tee /home/luopan-deploy/.ssh/authorized_keys >/dev/null ``` 这一段会创建以下文件 ``` /home/luopan-deploy/.ssh/authorized_keys ``` 文件内容是: ``` restrict,command="/usr/local/sbin/luopan-ssh-gateway" ssh-ed25519 AAAA-----------------AAAA... github-actions-luopanhacker ``` 这里表明让用户登录之后,强制运行`luopan-ssh-gateway`。 #### 添加权限 ``` sudo chown -R luopan-deploy:luopan-deploy /home/luopan-deploy/.ssh sudo chmod 700 /home/luopan-deploy/.ssh sudo chmod 600 /home/luopan-deploy/.ssh/authorized_keys ``` ### 本地连接测试 ``` ssh \ -i ~/.ssh/luopan_github_actions \ -o IdentitiesOnly=yes \ luopan-deploy@服务器IP \ "whoami" ``` 应该就在返回: ``` This SSH key only permits: deploy deploy <40-character lowercase Git SHA> ``` ### 腾讯云添加白名单 ![image.png](https://img.somnus.wiki/file/1785295188040_image.png) 在白名单管理中添加指定用户的放行规则 ![image.png](https://img.somnus.wiki/file/1785295266795_image.png) ## 总结 按照上面的步骤走完,就不会再提示报警信息了,而且同时安全性也能相当高。 其实今天我才真正理解了 linux 不同用户的设计是为什么了,之前我常常都是只使用一个用户或者 root,但是为了安全可靠,多用户设计是相当先进的。🥹 ### 检索增强!!基础流程 URL: https://blog.somnus.wiki/notes/jian-suo-zeng-qiang-ji-chu-liu-cheng/ Last updated: 2026-07-27T07:15:49.000Z 即检索增强技术(retrieval argumented generation)。是一种结合信息检索和文本生成的技术。旨在通过引入外部知识库来提升模型的事实准确性、相关性和多样性。 ## 使用场景 在做一些偏企业内部的知识助手和电商客服的时候,内部的手册和知识库往往会超过大模型的上下文。通常会有以下问题: - 模型无法读取所有内容 - 模型推理成本高 - 模型推理慢 于是 rag 便有他的意义了。 ## 整体流程 #### 建库 首先第一步是收集数据,往往在现实工作中不会有提供好的现成的数据用,我们需要去把不同格式的数据(pdf、网站、数据库、api)。然后 就是需要对数据进行分割,把数据分成指定大小的块,通常会有按字数分割、按章节分割。再接着使用预训练模型(BGE、BERT)对原始数据进行 embedding 表示,这些向量表明了文本的语义。具体的可以参见[这篇博客](https://blog.somnus.wiki/notes/embedding/)。最后再把向量和文本都存在数据库中,建立**索引关系**,来实现快速检索。 #### 检索 当用户提交查询时,对待查询的文本采用相同的编码器,然后根据得到的向量来进行**相似度计算**(余弦相似度、欧式距离等方法),然后选择出相似度最高的 n 个选择,作为查询的参考;然后再对选择进行**重排(rerank)**,选出最终的 m 个选择(n>m)。 #### 生成 对于用户的问题和上面的 m 个选择进行整合,一块发送给大模型,最后生成答案。然后可以添加一些合理的后处理,比如置信度评估,候选答案评选。 ![image.png](https://img.somnus.wiki/file/1784798869380_image.png) 上图中可以比较清晰的知道 rag 整个的流程。 ## 具体技术 下面介绍一下具体的技术细节 ### query transition 属于rag pipeline的第一阶段,目的是为了将question变成更容易检索的形式,提升retrieval的效果。 #### multi query 将 question 按多个角度重写例如同义词替换、相似语义拓展、删除冗余词、纠正错误和标准化表述等。然后再对每个问题都进行 retrieve 将得到,再对得到的结果取并集,然后把并集都丢给 llm。 ![image.png](https://img.somnus.wiki/file/1784863772863_image.png) #### rag fusion 对于检索得到的文档进行排名,通过加权各个question在文档中的排名得到一个综合的排名。 ![image.png](https://img.somnus.wiki/file/1784865628505_image.png) #### decomposition 把初始问题切分成n个问题,然后按照以下;两种方式处理 - 前一个问题的答案输入到下一个问题。 - n 个问题互相独立,最终拼接成最终答案。 ![image.png](https://img.somnus.wiki/file/1784865785931_image.png) ![image.png](https://img.somnus.wiki/file/1784865821754_image.png) #### stepback 从一个具体的问题出发,通过给一些few-shot的方式,生成一个更高层次、更抽象的问题,以便于检索到相关文档。例如从问某人的学习成绩到问对某人的评价。 #### HyDE 与以上不同,HyDE根据用户输入的question生成一些假设的doc,这些doc与文档更接近,利用这些doc检索相关的知识。这样做的直觉是:由模型生成的回答会包含与查询语义相关的词汇和信息,可以作为查询的丰富语义表示,从而找到那些没有直接关键词匹配但语义相关的文档。 > 比如对于用户查询“保险销售技巧”。传统检索可能只针对“销售”或“保险”检索,结果不一定全面。应用 HyDE 时,我们先让 LLM 根据这一查询生成一段**假设回答**,例如:“保险销售技巧包括了解客户需求、建立信任、提供专业建议”,假设回答丰富了查询关键词,有更大概率找到相关的文档。 ### routing 路由,顾名思义,就是将不同的 query 按照一定的规则分流到不同的流程。 routing 属于 rag pipeline 的第二阶段,目的是为了让 query 能够查询到争取的数据源(数据源都不对的话,那就不要扯其他的了)。 通常会有以下几种 routing 方式 1. 基于规则的方法 在现实工作场景中,我们可以人为的给项目设定路由规则,比如如果用户 query 中包含“报销”“费用”等关键词,判定为报销流程查询;如果包含“销售”“技巧”等词,判定为保险产品销售技巧。 1. 基于机器学习的方法 收集大量的样本,对于一个预训练模型(bert)进行微调,来让这个模型判断分流,而不是简单的根据一些字词来判断。 1. 基于提示词工程的方法 通过精心设计的Prompt直接让大模型判断意图类别。可以提供若干意图类别描述,让模型选择最适合的类别。此方法不需要额外训练数据,在零样本或少样本场景下效果好。 #### logical routing 有多个数据库(知识库),将问题 route 到不同的数据库来查询。 ![image.png](https://img.somnus.wiki/file/1785135141537_image.png) #### semantic routing 根据**语义相似度**来 route,就是在对 query 进行 embedding 之后得到对应的语义向量,然后用这个语义向量去匹配对应的数据库。 一种特别有用的技术是使用embeddings将query路由到最相关的prompt。 ![image.png](https://img.somnus.wiki/file/1785135296416_image.png) ### Query Construction 这个是 ragpipeline 的第三部分,利用命名实体识别技术,将 query 转化成适合用于搜索的keywords 或者参数。比如: > 用户query:how to use multi-modal models in an agent, only videos under 5 minutes输出:content\_search: multi-modal models agent、title\_search: multi-modal models agent、max\_length\_sec: 300 ![image.png](https://img.somnus.wiki/file/1785135596383_image.png) ### Indexing rag pipeline的第四部分,将文档拆成 vector 形式,并建立索引。 #### chunking 分块(chunking)是将大块文本分解成小段的过程,直接影响系统的检索质量和生成效果: - **检索精度和相关性**:合理的切分能保证每个chunk的**语义完整性**,更准确的匹配用户意图,降低不相关内容被一同检索的可能性。 - **向量表示与相似度计算**:能产生更**精确的语义向量表示**,使相似度计算更加准确,并且可以加速相似度计算和索引查找过程 - **生成质量优化**:合理的切分能在不超过模型最大输入限制的情况下提供足够上下文,**提高输入给模型的信息密度和质量**,**减轻模型幻觉** - **实际应用考量**:不同领域可能有不同的切分策略,保留和添加适当的元数据可以提升检索效果。 现有的 chunking 策略有:FastGPT、Langchian-Chat、 Baidu千帆知识库、星火知识库、Jina。 基于**Langchain**的分块方案: - **Character**:按固定字符数分割。 可以设置重叠字符来保持上下文 - **Recursive**:Langchain的默认文本分割器,它按不同的字符递归地分割文档(默认使用\[“\\n\\n” ,"\\n" ," ",""\]),按照顺序逐个遍历列表中的分隔符直到块足够小为止,可以实现结构化的文档切分。 可以设置重叠字符来保持上下文。 - **Token**:按照token数量进行分块。常用的分词器有BPE、tiktoken等 - **Document**:使用特定的分隔符或规则进行分割,如markdown中的标题符号、Python代码中的类和函数等。 - **Semantic**:通过计算句子间embedding距离,把具有相似主题或内容的句子分为一块。 - **Agentic**:最高级别的chunking方法,通过LLM做决策,将文本分块为独立的命题。 chunking 这一块非常有说法,下一篇博客具体讲一下。 ### Retrieval 检索,即根据上面 已经过 construction 的query 和经过 chunking 的数据库(知识库)来检索得到匹配的内容。主要有以下知识点,以后会专门介绍。 1. 混合检索 2. reranking 3. 检索评估指标 ### Generation 将检索到的相关文档与原始查询合并,形成更丰富的上下文信息,作为生成模型的输入,生成连贯、准确且信息丰富的回答或文本。 ```python from langchain.prompts import ChatPromptTemplate # 一个参考模板 template = """你是一个xx领域的专家,请结合从知识库中检索到的相关文档,回答用户问题: ### 问题: {question} ### 上下文: {context} ### 答案: """ prompt = ChatPromptTemplate.from_template(template) ``` 在现实的生产环境中,经常容易出现以下三个问题: 1. **多轮对话的语义连贯性不足**:在用户多轮提问时,如果新问题是对上一轮回答的跟进(例如用户问:“这个怎么申请?”),系统需要理解“这个”指代什么。缺少对话上下文的关联可能导致LLM误解提问,给出不相关或幻觉的答案。 2. **多模态知识的利用困难**:金融保险领域的知识库包含PDF手册、PPT演示、文本说明、视频讲解等多种形式。如果不对这些不同格式的数据进行预处理和结构化,检索时可能遗漏关键信息,导致答案不全面。 3. **缺少来源引用降低可解释性**:用户希望了解答案出处以建立信任。如果生成的答案没有标注来源,用户无法追溯信息真实性。特别是从长文档提取内容时,不注明具体出处会降低答案的可信度和可检查性。 具体解决也可以参考以下方法: 1. **维护对话上下文,确保连贯**:在每次用户提问时,检测问题是否包含代词或省略(如“这个”“它”等)以判断是否为跟进问答。如果是跟进问题,将**之前的相关问答摘要**或**关键术语**添加到当前查询中。一种常见做法是**问题重写**:将用户的新问题与上下文合并重写成完整问题,再送入检索和LLM。与此同时,系统应维护一个对话历史状态,让LLM参考之前的问答或已检索的知识,避免因缺少背景造成误解。 2. **整合多模态知识,提高答案全面性**:预先对PDF、PPT、文本、视频等资料进行解析和结构化处理,存入统一的向量数据库以便检索。比如: 1. **PDF/PPT**:提取文字内容,保留章节标题、表格数据等结构信息,将长文档按段落或页面切分成知识片段(chunks),并为每个片段添加文档名称、页码/幻灯片编号等元数据。 2. **视频**:对讲解视频执行语音转文本(ASR),获得字幕稿。根据时间戳将字幕稿切分成短段,并存储视频ID和时间段元数据。必要时可结合视频说明文字或关键帧截图的文字说明。 3. 将不同模态的数据转换为统一的文本嵌入向量,以便用同一种检索方式获取相关片段(例如使用同一嵌入模型表示文本和语音转文本)。或者采用**多模态检索**策略:分别在文本库、图像/视频库中检索,再融合结果。 4. 在生成答案时,允许LLM综合多个来源的片段。例如同时引用保单PDF中的条款和培训视频中的说明,以形成完整答案。 3\. **在答案中加入来源引用,增强可解释性**:设计提示(Prompt)要求LLM在给出答案时**标注信息来源**。例如,让模型在句末用括号注明来源文档名称或索引编号。实现方法可以是:在将检索到的文档片段传递给LLM时,附加标记(如【1】、【2】)或者直接提供“引用格式”的文本,让模型仿照引用格式回答。对于长文档的引用,如果答案来自同一资料的不同部分,可以拆分引用为【文档A,第10页】、【文档A,第15页】等,精确指明出处。生成策略上,可以使用RAG的**引用增强模式**,即模型严禁脱离提供的知识片段编造答案,确保每句都有据可依。最终答案输出时,将源文件名称或链接映射为用户可查看的引用,以便用户点开核实内容。这种动态插入引用的方式保证了答案的可溯源性,减少幻觉,增加用户信任。 ## 面临问题 1. **内容缺失**:知识库中缺失上下文,rag只能提供不精确、甚至是错误的答案。 - 解决方案:可以通过**数据清洗**和**prompt优化**(比如在prompt中加入“如果你不确定答案是什么,就告诉我你不知道”的提示,防止模型胡说八道)缓解。 1. **错过排名靠前的文档**:由于检索时缺乏上下文,导致检索到的关键的文档排名靠后,没有返回给用户。 - 解决方案:可以通过**chunk\_size 和 similarity\_top\_k**平衡计算效率和质量,以及采用**Rerank算法**。 1. **不在上下文中**:无法将检索到的文档全部放在输入模型的上下文中,尤其是检索到大量文档时。 - 解决方案:使用**长上下文**的方法,例如需要对文档进行合并、插值等。 1. **未提取**:LLM倾向于检索近似值而不是精确值,导致包含很多不相关的甚至互相矛盾的信息,可能因为这些噪音损害响应质量。 - 解决方案:**数据清洗,压缩prompt**,避免**中部丢失问题**(模型对输入上下文开头和结尾的信息理解能力更强,应避免将关键的信息放在中部)。 1. **格式错误**: LLM 忽视了提取特定格式的信息(如表格或列表)的指令。 - 解决方案:prompt中给出**示例**,简化清晰prompt,对**输出进行解析**。 1. **特异度不正确**:输出的粒度与输入不一致,比如用户问题很具体,模型回答很宏观;或者用户问题包含详细的上下文,模型生成简短的回复。 - 解决方案:改进检索策略,如从**小到大检索、句子窗口检索、递归检索;**在prompt中引导模型生成特定粒度的内同。 1. **不完整**:输出只回答了输入的部分问题。 - 解决方案:之前RAG的文章中提到的一些方法,如**routing**到最相关的文档库,用户查询重写和分解成子问题。 ## 效果评估 ##### 准确率/召回率评估 评估RAG系统回答问题的正确性和检索相关性,更多指标详见3.1.6 Retrieval 检索评估指标。主要包括: - **答案准确率**:比较生成的答案与标准答案的匹配程度,可使用自然语言处理中的评价指标如 **BLEU**(衡量n元语法匹配程度)、**ROUGE**(衡量召回的n元语法覆盖率)等来量化答案与参考答案的相似度。 - **检索召回率**:评估检索模块是否找到了包含正确答案的文档。例如计算 **Top-k召回率**(正确答案所在文档是否出现在前k个检索结果中)以及 **MRR(平均倒数排名)、NDCG(归一化折损累计增益)**,以衡量正确文档在检索结果中的位置(MRR越高表示相关文档排名越靠前)。 ##### 可信度评估 衡量生成的答案在多大程度上有文档支持,以及答案内容和检索到的文档是否一致、可靠。具体包括: - **答案与支持文档匹配度**:验证生成答案中的关键信息是否能在检索文档中找到。可以计算答案和支持文档之间的相似度或重合率,例如关键词重叠度。 - **文档覆盖率**:检查检索到的文档是否覆盖了回答所需的所有要点。如果答案涉及多个要点,评估这些要点是否均能在提供的文档集合中找到依据。 ##### 响应速度评估 评估RAG系统处理查询的速度,包括: - **平均响应时间**:系统处理单个查询的平均用时。 - **P95/P99 延迟**:95%和99%的请求在多少时间内完成(尾部延迟),用于评估最慢响应的情况。 - **整体响应分布**:可以绘制响应时间分布图(如直方图)来了解大部分查询的延迟范围。 ##### 可扩展性评估 测试RAG系统在不同数据规模和负载下的性能表现,包括: - **数据规模扩展**:增大知识库或文档集规模,观察检索和生成性能的变化(如响应时间是否随数据量线性增长,检索准确率是否保持稳定)。 - **吞吐量**:衡量系统每秒可处理的查询数(QPS),以及在高并发情况下的性能表现。 ##### 用户体验评估 系统给用户带来的主观感受和易用性,包括: - **人工满意度评价**:通过人工评估或用户反馈来打分,衡量用户对答案的满意度。例如收集用户评分(1-5分)或对答案是否解决问题的二元反馈,以计算平均满意度分或满意率。 - **答案可读性**:评价生成答案表述的清晰易懂程度。可以使用**可读性评分**(如基于句子长度和词汇复杂度的指标)来定量分析答案文本的可读性,确保答案语言简洁明了,便于用户理解。 ### jujutsu 使用指南 URL: https://blog.somnus.wiki/notes/jujutsu-shi-yong-zhi-nan/ Last updated: 2026-07-27T06:26:29.000Z 最近关注了一个 b 站博主,[下划线自然选择下划线](https://space.bilibili.com/369690371?spm%5Fid%5Ffrom=333.788.upinfo.head.click&ref=blog.somnus.wiki)lol😅。就是一个 rust 区的博主,然后在他的账号里面我注意到他介绍了 jujutsu,当时他声称是下一代的版本管理工具,直接对位 git 的。 然后今天我在 vibecoding 我的博客网站后台的时候,我的 codex 的最后一次重置卡已经用完了,我想省着留着工作的时候用,于是我去买了火山的 coding plan lite,然后在 opencode 中接入了,一开始使用的 glm5.2,但是 glm5.2 经常容易卡住,于是我换成了 kimi2.7code,但是我没注意到五小时额度(此处有伏笔),我直接让它帮我整理一下博客仓库的文件结构,然后运行到一半,额度用完了☹️,此时我的工作空间已经被搞乱了,虽然我有 git 的习惯,当时也立刻`git reset --hard HEAD && git clean -fd`但是我还是会担心我的 gitignore 的文件还是会被修改,于是我当时就在考虑有没有比 git 更好的管理工具,后来我去了解了一下jujutsu,虽然不知道能不能解决我上面的问题,但是感觉确实很好用,很有自己的特点。于是便有了这篇博客。(早知道用 mimo 了😭) jujutsu是在前辈 git、mercurial 等版本管理工具中分别取其长处得到的。 ## 优势 在 20 年前 linus 一个人花了 10 天敲出了 git 的初版原型,彻底改变了软件行业的工作流程,在这 20 年间,linus 和他的团队,对 git 陆陆续续做了很多修改,那为什么还要选择 jujutsu 呢,下面介绍一下jujutsu 的优势。 ### 适应ai 时代 不论是否同意,这个时代的编程方式已经发生了天翻地覆的变化,ai 已经进入了每一个合格的程序员的工作流程中,甚至接管了程序员很多的的工作,和 ai 共同编程就是这个时代的程序员的主旋律。 在日常的工作流程中,我们肯定少不了和让 ai 聚焦需求,并验证 ai 写的代码,在逐步验证的过程中,最好的时间方式就是验证一步就隔离一步,大家肯定都经历过 ai 偷偷改文件删文件,然后美美“跑路”的情况,然而 git 在这样的工作流中就显得有点力不从心了,频繁的 git add 让工作变得繁琐。而且 ai 带来的工作提效往往需要我们测试不同的方案,而 git 如果需要测试不同的方案就需要频繁的清理 worktree,并来回 checkout。 ## 使用 ### 安装 如果有rust环境可以直接 ```bash cargo binstall --strategies crate-meta-data jj-cli ``` 如果没有安装 binstall 可以先更新 rust 工具链并安装 binstall ```bash rustup update cargo install cargo-binstall #然后再安装 cargo binstall --strategies crate-meta-data jj-cli ``` binstall是新版本的 rust 推出的二进制安装工具,之前的 cargo install 都是需要从源码编译,下载各种各样的库,花费的时间会比较长,但是 binstall 就会直接下载 GitHub 的编译好的二进制程序直接安装,会快很多。 如果是archlinux 系,还推荐通过 pacman 来安装。执行以下命令 ``` sudo pacman -S jujutsu ``` ### 基本命令 假设处于一个真实的工作流中: 首先我们第一步是进入仓库,那么进入仓库分为两种情况,一种是本地创建自己的仓库;另一种是 clone 远程的仓库。 对于创建自己的仓库,类似于 git,可以直接运行 ```bash jj git init --colocate . ``` 意思是从 jj 和 git兼容的方式创建库,并不推荐单纯的通过\` jj init \` ,因为目前 jj 的功能还不够完善,类似于 tag 还没有,所以从兼容的方式创建是比较中庸的手段。 对于远程仓库,可以直接运行 ``` jj git clone https://github.com/user/name.git # 或者fetch更新本地库 jj git fetch ``` 对于已经创建好的库,我们可以通过下面指令来查看其状态。 ``` jj status / jj st # 查看当前的提交状态,类似于git status jj log # 查看历史的工作树,类似于git log ``` ![截屏2026-07-27 14.06.23.png](https://img.somnus.wiki/file/1785132484910_%E6%88%AA%E5%B1%8F2026-07-27_14.06.23.png) 下面我聊一些我在使用过程中认识到的点: 💡 working copy 是你当前工作的区,如果是 empty,那么你的工作区就是干净的;如果是有更改或者提交过更改,那么工作区就不是干净的. 听上去有点绕口令,我们在使用 git 的时候,通过 commit 就能直接把当前的工作区“弄干净”,而在 jj 中,虽然也可以用 commit(类似于 git),但是更多还可以使用 describe(desc)这样就是给当前工作区的更改添加一个描述,但是还是处于当前工作区,如果要清空,就需要new,这样的好处是,方便随时对修改的内容进行 desc,随着更改,持续的增删 desc 的内容。 ``` git commit -m "message" 等价于 jj commit -m "message" 等价于 jj describe -m "message" /jj desc -m "message" jj new ``` 💡 jj 能更方便的修改过去提交的 message 由上面可见,jj 能随时修改正在工作的工作区的 message,同样的,他也可以修改过去已经提交的更改 ![截屏2026-07-27 14.19.43.png](https://img.somnus.wiki/file/1785133197457_%E6%88%AA%E5%B1%8F2026-07-27_14.19.43.png) 比如要修改xplzknov(粉色可以直接用 x来代替),可以直接运行 ``` jj desc -r @- # 或者 jj desc -r x ``` 然而 git 的话就是先 rebase,再布拉布拉修改。 💡 对于未完成的代码(半成品),jj 相对 git 也能更方便的处理 在 git 中,如果有未完成的代码,同时又想切换分支去测试新的东西,只能先 stash,然后再切换;而 jj 中未提交 `@` 是标准变更,支持:改 desc、部分提交、与历史变更合并、直接基于当前半成品切新变更,不用 stash。 ### 曾许人间第一流 URL: https://blog.somnus.wiki/essays/ceng-xu-ren-jian-di-yi-liu/ Last updated: 2026-07-27T07:18:17.000Z 今天早上看了科隆major决赛,见证了niko夺冠,真的太不容易了,放一张老尼的帅图。 ![image.png](https://img.somnus.wiki/file/1784860492910_image.png) niko在科隆major捧起冠军奖杯,是他十一年职业生涯迟到八年完成的梦。 八年足够让少年将胡须长满脸庞,足够让故事从波士顿写到科隆,足够让分道扬镳的老友重聚。这届场均rating1.22,决赛用1.43的rating率队以三比零击败黑豹。niko在29岁的年纪,亲手合上了差一点的书页。 十一年生涯里,他得到过许多,也失去过更多。人们因此将遗憾的标签贴满他的名字,直到今晚,niko给了我们一个重新认识他的机会,所有的等待在这里找到了答案。 > “Listen here ,this is my time” 站在niko身边的是21岁的m0NESY,少年曾许诺,要和niko一起赢下major冠军。从G2到猎鹰,从连续八次决赛折起,到majorMVP,这一路有多坎坷,这一刻就有多绚烂。所以你会看见m0NESY在捧杯后喜极而泣,面对奖杯久久难以平静,以及那个与十二年前Get\_RiGht极其相似的画面。 关于这次夺冠,猎鹰还有太多故事可以讲述。关于他们如何从魔鬼半区中杀出重围,关于18岁的京介如何在“百万质疑”中站稳脚跟,关于25岁的龙哥如何从小人物成长为丹麦真龙,还有36岁的karrigan如何在两个月之内,从最不被看好的转会交易,成为最年长的major冠军。 这支队伍已经向世人证明,他们并不只是富有,而这群人给我们传递的精神,叫永不放弃。 > “Never give up your dreams. Never give up your life.” 在冠军的背面,科隆夜空的另一个主题是遗憾。“教父”的最后一次科隆之旅,没能与兄弟们圆满。破碎的王朝,止步的黑马,执着的旅人,心碎的土耳其。无论成败,这舞台上的每一个人都值得尊重。此番以感谢所有人,让这一切变得如此特别。 但电子竞技残酷的地方就在于,能承载数万人的体育馆,却容得下一对赢家。 附一张竞猜之王的证据(忽略stage2和stage3) ![IMG_7346.jpeg](https://img.somnus.wiki/file/1784860753004_IMG_7346.jpeg) ### 理科筑骨,文科何以塑魂? URL: https://blog.somnus.wiki/essays/liberal-and-science/ Last updated: 2026-07-27T07:24:01.000Z 前几天,我和女朋友聊文科和理科,到底谁更有用? 我是工科生,东西做出来了,效果就在眼前。她的看法不一样。她觉得人文社科的价值没那么容易算出来,但它会影响一个社会怎么理解自己,人与人怎么相处,大家又想往哪里走。 那晚我们谁也没说服谁。不过我后来一直在想:现在很多人不太看得起文科,这不一定只是理科生的傲慢,也不是凭空出现的情绪。文科领域这些年确实有一些让人失望的表现,给了这种情绪生长的空间。 但要是把问题一股脑归到“文科没用”,也太简单了。**真正该问的不是文科有没有用,而是它有没有把自己的价值做出来。** ## 建构与解构的巨大反差 先看我们身边的现实。过去几十年,理工科做成的事太多了:从两弹一星、北斗导航,到无人机、福建舰、5G、天宫空间站。钢铁、代码、公式听起来很硬,可正是这些东西,把生产力往前推,也把我们的物质生活一点点搭起来。 理工科有个很直接的特点:总得过现实这一关。桥能不能通车,芯片能不能量产,产品有没有人用,躲不开。做错了,实验、市场和用户很快就会给答案。这种反馈有时很残酷,但至少够诚实。 > **理工科解决的是“能不能做”,而人文更常追问“该不该做、为了谁做”。** 物质的骨架搭起来之后,文科本该接着做另一部分事:帮我们理解这个社会正在发生什么,帮我们把中国的经验讲成别人愿意听的故事,也让人们知道自己为什么会站在一起。人不是只靠效率活着。一个社会能不能走稳,也不只看 GDP、专利和基础设施,还要看大家是否愿意相信彼此,是否知道自己从哪里来、要往哪里去。 可现实常常有点别扭。理工科在解决一个个具体问题时,一部分文科工作者却更习惯先拆解、先否定;有人拿数据说明我们做成了什么,也有人急着从中找出各种“劣根性”;谈文化出海时,还有人会下意识觉得西方的标准才算标准。 批评没有错,自我反省当然也没有错。怕的是只剩下嘲讽和否定,却没人愿意认真说明:我们做成的事为什么值得理解,我们的生活为什么值得被好好讲述。到最后,丢掉的不只是表达的主动权,也会是大家对共同生活的信心。 ## 错位的生态与失语的“正规军” 有意思的是,今天一些很出圈的中国文化作品,主创还真不一定来自传统意义上的文科专业。刘慈欣学的是水电工程;《哪吒》系列导演饺子毕业于华西药学院;《黑神话:悟空》的冯骥学的是生物医学工程;米哈游的三位创始人来自交大计算机系;李子柒则是一路自己摸索出来的民间创作者。 这当然不能推出“理工科更懂文化”,更不能说明文科生不行。但它多少暴露了一个尴尬:有些真正想讲故事、做作品的人,已经绕开了学院体系。技术背景的人去做游戏、拍动画、写科幻,也未必是他们比文学专业的人更会写,而是他们愿意从一个产品、一个用户、一个具体的中国经验开始做。 > *文化作品最后还是要回到具体的人、具体的生活和具体的经验里。* 再看看一些学术论文,问题或许也能看出一点端倪。不是说题目冷门就没价值,也不是说借鉴西方理论就不对。比如研究“后女性主义文化”,或者讨论“[高速公路建设与拐卖儿童的关系](https://www.sciencedirect.com/science/article/abs/pii/S0167268124003792?ref=blog.somnus.wiki)”,本身都可以有意义。关键是:这个问题从哪里来,最后要回到哪里去?它能不能解释中国社会里真实发生的事,能不能帮助普通人理解自己的生活? 如果一篇论文只是为了套模型、赶发表、评职称,再时髦的概念也很难和现实真正碰上。更麻烦的是,文学课若把西方经典当成唯一标尺,SSCI 数量若成了最重要的指标,研究就很容易慢慢困在一套外来的话语里,忘了脚下的土地。 ![截屏2026-07-15 13.59.39.png](https://img.somnus.wiki/file/1784095206775_%E6%88%AA%E5%B1%8F2026-07-15_13.59.39.png) 还有一点,文科的问题往往不会马上“报错”。理工科路线走偏了,产品做不出来,现实很快会告诉你;文科路线走偏了,现有的评价体系却可能还在鼓掌。久而久之,研究离实践远了,表达离普通人远了,对这片土地和生活在这里的人,也就越来越缺少真切的共情。 所以很多人反感某些脱离常识的公共表达。他们不一定是在反感复杂理论,而是在反感那种居高临下的口气:不了解普通人的处境,却急着教别人该怎么感受;没有真正去过一线,却把真实生活当成概念游戏的素材。 ## “无用之用”的真正使命 说到这里,反而更能说明:文科一点都不是没用。很多看起来“暂时没用”的积累,到了关键时刻会变得特别重要,甚至会影响一个国家的判断。平时冷门的琉球学、西夏学,放到外交谈判桌上,就可能成为很有分量的依据。 更重要的是,有些问题本来就不是理工科擅长回答的:技术到底该服务谁?效率和公平怎么平衡?一个新产品进入社会,会怎样改变人的生活?算法越来越会推送,人又怎么不被它推着走?**这些问题没标准答案,但不能没人问。** 今天中国的物质积累,已经到了精神世界也该跟上的时候。我们有完整的工业体系,新能源车走向全球,企业也在大步出海。越是这样,越需要文科发挥作用。 > *出海不只是把货卖出去,也要让别人听懂:我们为什么这么做,我们相信什么,我们的普通人过着怎样的生活。* 我们需要美术生为中国汽车画出自己的《头文字 D》,让工业产品也能成为年轻人的文化图腾;需要政治学者用中国自己的理论回应中国的问题;需要历史学者从故纸堆里找到应对当下变局的智慧;需要有人把中国企业家的创业故事,写成全世界商学院都会研究的管理案例;更需要有人把最普通的中国劳动者,拍成银幕上会发光的英雄。 这些事听起来很“软”,其实一点也不轻。好的文学、电影、设计和社会研究,能把原本互不相识的人连起来,让大家对共同的生活有感觉、有判断。它们不一定马上变成产值,却会在很长时间里塑造一个社会的气质。 ## 不是把文科变成理科 当然,文科的出路不是把自己变成理科的附庸。不是每门课都要做成可量化的实验,也不是每篇文章都必须立刻转成产品。人文的价值本来就有“慢”的一面,有不那么功利的一面,也需要面对复杂问题的耐心。 但“慢”不等于飘在天上,“不功利”也不等于不用负责。研究《红楼梦》有价值,研究性别议题有价值,讨论西方思想也有价值。前提是研究者真的理解研究对象,也愿意回应脚下的社会,而不是拿一套现成概念去硬套现实。 文科不需要放弃批判精神。它需要的是更扎实的批判:**看得见问题,讲得清问题,也愿意参与解决问题。** ## 破局之路:到泥土中去,到人民中去 文科想走出现在的困境,靠的不是多发几篇能被外部体系认可的论文,也不是在网上制造对立、贩卖立场。它的路,还是在泥土里、在车间里、在普通人的认可里。 教育也该换个思路:少迷信单一指标,多建立自己的评价标准。论文当然要写,但一份扎实的田野调查、一次长期的社区服务、一个真正被观众喜欢的作品,也该得到相应的尊重。大学可以少一些只在会议室里完成的课题,多给学生一些机会,去企业、农田、医院、社区和生产线里待几个月。 文科生也该多走出书斋,去看看现代技术,去农田和生产线转转。没摸过中国社会的脉搏,没在实践里摔打过,很难长出真正属于中国的理论。反过来,理工科的人也不妨多读点历史、文学和社会学。技术越强,越要知道它会落到谁身上,又会把人带到哪里去。 我和女朋友后来也没有给“文理谁更重要”下结论。这个问题本来就没有标准答案。 > **理工科筑骨,文科塑魂;骨架和灵魂从来不是竞争关系。** 一个国家不能只有硬邦邦的钢铁,也不能只有飘在空中的词语。 理工科已经为这个国家铺出了经济和工业的轨道。舞台搭好了,灯光也亮了。接下来,该文科上场讲故事:把真实的中国讲清楚,把普通人的尊严写出来,也把这个时代的问题看得更深一点。 如果在这样的时代,还是只会拆台、不会建设,还是离人民的期待越来越远,只顾着迎合别人的目光,被时代轻视也就不奇怪了。 > *历史不会一直等人,但它总会给愿意回到现实、认真说话的人留一个位置。* ### ppo vs dpo vs grpo URL: https://blog.somnus.wiki/notes/ppovsdpovsgrpo/ Last updated: 2026-07-28T09:43:14.000Z > 这一节主要讲的就是大模型的强化学习策略,现在主流的方案就包括ppo、dpo、grpo。 ## 背景介绍 一个传统的完整的rlhf(reinforcement learning from human feedback,人类反馈强化学习)要包括三部分:sft(监督微调)、rm(奖励模型训练)、rl(强化学习微调)。 而今天提到的三种方法,都是第三部中强化学习微调的时候,使用的不同的算法。 ## 过去的方法 在ppo之前策略是有一个发展的过程的,具体可以参考这个[知乎](https://zhuanlan.zhihu.com/p/614115887?ref=blog.somnus.wiki),讲的非常具体(公式太多了😩)。 ### VPG 即传统策略梯度(vanilla policy gradiant) #### 基本原理 他的核心原理就是把你的目标函数$J(\\theta)$量化出来: $$ \\max\\limits\_{\\theta}(J(\\theta))=\\max\\limits\_{\\theta}(\\mathbb{E}*{\\tau\\sim\\pi*\\theta}\[R(\\tau)\])=\\max\\limits\_{\\theta}\\sum\_\\tau P(\\tau;\\theta) R(\\tau) $$ 上述函数中其中$\\tau$就是agent与环境交互的动作轨迹,对$\\tau$求和就是表明与环境交互的所有可能,每一种可能乘上对应的$R$(reward函数),然后计算得到目标函数$J(\\theta)$,那么整个函数想表达的就是通过$\\theta$得到最大的目标函数。 和一般的函数一样,我们想要得到最大值,都要算这样的函数的梯度,最终经过一系列的化简、对数导数技巧等操作,得到: $$ \\nabla\_\\theta J(\\theta)\\approx\\frac{1}{n}\\sum\_{i=1}^m(\\nabla\_\\theta \\log\\pi\_\\theta(a\_{t^{(i)}}|s\_{t^{(i)}}))R(t^{(i)}) $$ Williams提出的REINFORCE算法是经典的策略梯度算法之一。 #### 存在缺陷 我们在VPG中定义了步长$\\alpha$来更新$\\theta$,但是很容易出现overshooting(过冲)和undershooting(下冲)的情况 ![image.png](https://img.somnus.wiki/file/1782875592475_image.png) 如上图所示,一旦函数过冲进入低梯度的次优区域,很容易出现目标函数无法逃逸的现象。 ### NPG 面对上面的问题,我们很自然的会想到,通过限制步长来解决,即自然策略梯度(natural policy gradiant)。 $$ \\Delta\\theta^\*=\\underset{\\lVert\\Delta\\theta\\rVert\\le\\epsilon}{\\arg\\max} J(\\theta+\\Delta\\theta) $$ 但是效果也不如预期,如下图中 ![image.png](https://img.somnus.wiki/file/1782875809661_image.png) 不同参数分布,对于相同的步长的敏感度是不同的。所以我们不应该限制步长的大小,而是限制因为步长变化,策略导致的分布变化了多少。 #### 核心机制 引入了KL散度(KL Divergence)与黎曼空间。NPG不再使用普通的欧几里得距离来定义梯度的方向,而是将目标函数放到了一个由分布构成的黎曼流形上,为了在这样的流形上找准方向,它还引入了费舍尔信息矩阵(Fisher Information Matrix, FIM)。 于是新的策略更新为: $$ \\begin{aligned} \\Delta\\theta^\*&=\\underset{\\mathcal{D}*{KL}(\\pi*\\theta\\Vert\\pi\_{\\Delta\\theta})\\le\\epsilon}{\\arg\\max}J(\\theta+\\Delta\\theta)\\ \\end{aligned} $$ #### 数学魔法 KL 散度是一个非常复杂的非线性积分,直接把它放进神经网络里求导是极其困难的。 NPG 在这里引入了微积分里的泰勒展开,对 KL 散度进行二阶近似。奇妙的事情发生了,KL 散度的二阶导数(海森矩阵)正好等于统计学中鼎鼎大名的**费雪信息矩阵 $F$**。 你可以把费雪矩阵 $F$ 理解为当前策略空间的“曲率地图”或“敏感度矩阵”。它精确地记录了:此时此刻,改变哪几个参数会导致动作分布发生剧烈变化。 $$F = \\mathbb{E}*{\\pi*\\theta} \\left\[ \\nabla\_\\theta \\log \\pi\_\\theta(a|s) \\nabla\_\\theta \\log \\pi\_\\theta(a|s)^T \\right\]$$ $$ \\Delta\\theta^\*=\\underset{\\frac{1}{2}\\Delta\\theta^TF\\Delta\\theta\\le\\epsilon}{\\arg\\max}\\nabla\_\\theta J(\\theta)^T\\Delta\\theta $$ 该方案的强大之处就在于,无论分布的表示如何,它总能以**相同的幅度**来改变策略 #### 存在缺陷 **算力需求高**,假设神经网络有N个参数,费舍尔矩阵就有$N\\times N$个参数,更要命的是还要求费舍尔矩阵的逆矩阵$F^{-}$ ,线性代数中告诉我们,计算逆矩阵的时间复杂度是$O(n ^3)$ ,此时算力需求会爆炸。 ### TRPO 刚刚上面说到了,自然策略梯度的问题就是理论非常完美,但是就是算不动。而trpo解决的就是这个算不动的问题。 #### trust region是什么 上一个推导中,我们把极其复杂的原问题,变成了泰勒展开后的近似问题: - 目标:$\\max g^T\\Delta\\theta=\\max\\nabla\_{\\theta}J(\\theta)^-\\Delta\\theta$ - 约束:$\\frac{1}{2}\\Delta\\theta^TF\\Delta\\theta\\le\\epsilon$ 但是此处有一个很关键的问题,泰勒展开只有在“局部”的时候才精准,如果走的太远$\\Delta\\theta$太大,就会产生**很大的偏差**。也就是说,只有在满足上面提到的这个约束条件的椭圆形范围内,泰勒展开才是靠谱的,这就是所谓的信赖阈(trust region)。 #### 共轭梯度法(conjugate gradiant) 上述提到,NPG的最终更新公式是$\\Delta\\theta\\propto F^{-1}g$,此处的逆矩阵会因为现代大模型上百万参数的情况而计算量指数级暴增,会瞬间撑爆GPU。 但是实际上,我们并不关注$F^{-1}$这个逆矩阵本身,cg就把原本的$x=F^{-1}g$转化成了$Fx=g$,把原本的求逆问题转化成了解方程问题。 - 利用深度学习框架的“自动求导黑魔法(海森向量积 HVP)”,不需要在内存中生成 $F$ 矩阵,就能算出 $F$ 与向量的乘积。 - 在极其狭长的曲率空间中,只用 10 到 20 步迭代,就能精准找到不引起策略突变的**最优更新方向**。 #### 回溯线性搜索 cg虽然能求到方向和最大允许步长,但是trpo仍然不放心,因为上面提到的第一步泰勒展开已经是一个近似,如果第一步走出来,最终的KL散度就超标了,就完了。 于是,回溯线性搜索就是最后这一道保险: - 先尝试迈出计算好的最大步伐。 - 把新参数放进**真实**的(未经过泰勒展开近似的)环境和模型中测一下。 - 如果发现真实的 KL 散度突破了 $\\delta$,或者真实的收益下降了,就立刻把步长打个折(比如缩小一半),再重新测。 - 一直退缩,直到完全满足真实的安全条件,才正式拍板更新。 #### 存在缺陷 上面说了这么多trpo的原理,很明显让人感觉到的缺点就是,**复杂**,我们在工程实践中,往往是不能允许有这么复杂的逻辑的,因为复杂就意味着难以调试,意味着问题出现难以修复;此外trpo的计算开销也会很大。 --- ## PPO 在面对上面这些问题,2017年openai团队提出了自己的[解决方案](https://arxiv.org/abs/1707.06347?ref=blog.somnus.wiki)。 Proximal Policy Optimization,近端策略优化算法。PPO主要有两个变体,PPO penalty和PPO clip。两者都在论文中有提及。 ### PPO Penalty 这个方法在trpo的基础上,把原本的绝对不能跨越的硬性边界($KL散度\\le \\delta$)变成了软惩罚(拉格朗日松弛)。 举个例子,原本是禁止超速,绝对不能超过120,但是现在超过120会罚款,超得越多罚的越狠。 #### 公式表达 $$ J^{pen}(\\theta)=\\mathbb{E}\\Bigg\[\\frac{\\pi\_\\theta(a\_t|s\_t)}{\\pi\_{\\theta\_{\\mathrm{old}}}(a\_t|s\_t)}\\hat{A}*t-\\beta \\mathrm{KL}(\\pi*{\\theta\_{\\mathrm{old}}}\\Vert\\pi\_\\theta)\\Bigg\] $$ - 前者是原本的公式,也就是重要性采样 - 后者是增加的“罚款”:新旧策略的KL散度越大,这个值就越大,表明扣的分越多。 - $\\beta$是罚款系数,表明惩罚的力度 核心点是这个公式中的$\\beta$并不是固定的,而是有一个动态调控机制。 1. 先设定一个目标KL值$d\_0$ 2. 跑完1个batch的数据后,算一下真实的KL值d。 3. 如果$d>1.5\\times d\_0$,那么就加大惩罚力度,$\\beta \\leftarrow 2\\beta$ 。 4. 如果$d 不要算KL散度了,直接对概率比值一刀切了。 #### 核心公式 $$ L^{clip}(\\theta)=\\hat{\\mathbb{E}}\\Bigg\[ \\min\\Big\[ r\_t(\\theta)\\hat{A}\_t,\\text{clip}(r\_t(\\theta),1-\\epsilon,1+\\epsilon)\\hat{A}\_t\\Big\]\\Bigg\] $$ 其中: $$ r\_t(\\theta) = \\frac{\\pi\_\\theta(a\_t|s\_t)}{\\pi\_{\\theta\_{old}}(a\_t|s\_t)} $$ 是新旧概率比值。$r\_t(\\theta)$表明的是当前的策略是否喜欢这个动作,$r\_t(\\theta)> 1$表明策略喜欢这个工作,以后会继续增强这个动作。反之,$r\_t(\\theta)$表明不喜欢,以后会有削减他的动作。 $\\hat A$是优势函数,表明这个动作是“好”还是“坏”。$\\hat A>0$表明这个动作比平均动作好,反之则表示坏。 $\\text{clip}$是截断函数,假设$\\epsilon=0.2$,它会强行把$r\_t(\\theta)$锁在0.8到1.2之间,大于或者小于这个范围都会被截断。 $\\min$是取最小值,也是整个公式*最精妙*的地方。 取最小值的对象只有两个,一个是不截断的原始项$r\_t(\\theta)\\hat A$,另一个是截断项$\\text{clip}$。这样的非堆成设计表明了整体的更新逻辑就是**见好就收,小错意识到了轻罚,犯错没意识到就重罚**。下面的例子中假设$\\epsilon=0.2$ 1. 当$\\hat A=1$,动作很好、而且$r\_t(\\theta)=1.5$新策略也知道这个动作是好动作,则“见好就收”。取min得到的结果是1.2,**不会让她无限制的增长** 2. 当$\\hat A=-1$,动作很坏、但是$r\_t(\\theta)=0.5$新策略知道这个动作不好,也在尝试减少他的时候,取min得到的结果是-0.8,会**轻微的惩罚**这个,但是不会一棒子打死。 3. 当$\\hat A=-1$,动作很坏、但是$r\_t(\\theta)=1.5$,在动作坏的同时,新策略反而没意识到,还认为它是好动作想增加他,此时取min得到的结果就是-1.5,会狠狠地惩罚它。注意到:此时min没有选择那个被“柔和化处理”的clip的那个项,而是让**梯度全量通过**,强行把模型拽回来。 ![image.png](https://img.somnus.wiki/file/1782898689792_image.png) --- ## DPO ### PPO的问题 前提提要中提到的,rlhf中为了让大模型的回答符合人类喜好,都会走过三个部分,sft、rm、rl。其中ppo就是rl部分使用的算法。 在rl(PPO)部分需要同时读取四个模型:要训练的原模型、计算KL散度需要的旧模型、评价模型、奖励模型,问题很明显,就是模型太多。稍微大一点的模型,即使使用了显存优化技术,单卡也根本没办法跑。 ### 数学推导 即direct preference optimization。无论是PPO还是DPO,想要完成的目标都是一模一样的,就是让模型$\\pi$尽可能得到更高的奖励$r(x,y)$,同时又不能离原始参考模型$\\pi\_{ref}$太远(不能让KL散度太大)。 数学表达如下: $$ \\underset{\\pi}\\max \\bigg\\lbrace\\mathbb{E}\_*{x\\sim\\mathcal{D},y\\sim\\pi}\[r(x,y)\]-\\beta\\mathbb{D}*{KL}(\\pi(y|x)\\Vert\\pi\_{ref}(y|x))\\bigg\\rbrace $$ 其中$\\beta$是KL散度的惩罚系数,为了控制模型不能偏离老模型太远。 对于上面的公式,进行展开KL散度。得到: $$ \\underset{\\pi}\\max\\mathbb{E})*{x\\sim\\mathcal{D}}\\mathbb{E}\_*{y\\sim\\pi(y|x)}\\bigg\[r(x,y)-\\beta\\log \\frac{\\pi(y|x)}{\\pi\_{ref}(y|x)} \\bigg\] $$ 这一步的推导是值得仔细说一下的。对于前一项即奖励项$\\mathbb{E}\_{x\\sim\\mathcal{D},y\\sim\\pi}\[r(x,y)\]$。 > 根据概率论的全期望公式(或者联合概率分布拆解$P(x,y)=P(x)P(y|x)$)。 这一项就完全等于两层嵌套的期望即$\\mathbb{E}\_{x\\sim\\mathcal{D}}\\mathbb{E}*\_*{y\\sim\\pi(y|x)}r(x,y)$. 对于后一项即惩罚项$\\beta\\mathbb{D}*{KL}(\\pi(y|x)\\Vert\\pi*{ref}(y|x))$ > KL散度的计算基本公式是这样的:$\\mathbb{D}*{KL}(P\\Vert Q)=\\mathbb{E}*{x\\sim P}\\big\[\\log\\frac{P(x)}{Q(x)}\\big\]$ 直接使用上面KL散度的公式带入,得到:$\\beta\\mathbb{E}\_*{y\\sim\\pi(y|x)}\[\\log\\frac{\\pi(y|x)}{\\pi*{ref}(y|x)}\]$ > 此处为什么是对y求期望呢,是因为KL散度关注的就是对于生成的结果y在不同策略下概率分布的差异,我们的策略P就是正在训练的新策略$\\pi(y|x)$,而策略Q就是原始的参考模型$\\pi\_{ref}(y|x)$。 此时值得注意的是,我们在训练大模型的时候,不可能不基于输入x和数据$\\mathcal{D}$训练,而我们这个公式中就没有对x求期望,所以这里是原论文中一个不严谨的地方(或者说原论文为了公式简洁),此处省略掉了对于x的期望 我们把省略的期望加上去,得到:$\\beta\\mathbb{E}\_{x\\sim\\mathcal{D}}\\mathbb{E}*\_*{y\\sim\\pi(y|x)}\[\\log\\frac{\\pi(y|x)}{\\pi\_{ref}(y|x)}\]$ 然后把左右两项都都替换,然后合并同类项得到: $$ \\begin{aligned} 原式&= \\underset{\\pi}\\max\\mathbb{E}\_{x\\sim\\mathcal{D}}\\mathbb{E}*\_*{y\\sim\\pi(y|x)}r(x,y)-\\underset{\\pi}\\max\\mathbb{E}\_{x\\sim\\mathcal{D}}\\mathbb{E}*\_*{y\\sim\\pi(y|x)}\\beta\\log \\frac{\\pi(y|x)}{\\pi\_{ref}(y|x)} \\\\ &=\\underset{\\pi}\\max\\mathbb{E}\_{x\\sim\\mathcal{D}}\\mathbb{E}*\_*{y\\sim\\pi(y|x)}\\bigg\[r(x,y)-\\beta\\log \\frac{\\pi(y|x)}{\\pi\_{ref}(y|x)} \\bigg\] \\end{aligned} $$ 对于这个公式我们再把中括号中的两项调换一下顺序,并除以一个$\\beta$。 > 我们想求的是能让这个式子得到最大对应的$\\pi$,于是除以$\\beta$是没有影响的,调换顺序,就从原本的求max变成求min $$ \\underset{\\pi}\\min\\mathbb{E}\_{x\\sim\\mathcal{D}}\\mathbb{E}*\_*{y\\sim\\pi(y|x)}\\bigg\[\\log\\frac{\\pi(y|x)}{\\pi\_{ref}(y|x)}-\\frac{r(x,y)}{\\beta} \\bigg\] $$ 此时来到**最关键的一步**,我们把后面的$\\frac{r(x,y)}{\\beta}$也放到log中,根据高中知识可以得到: $$ \\begin{aligned} \\underset{\\pi}\\min\\mathbb{E}\_{x\\sim\\mathcal{D}}\\mathbb{E}*\_*{y\\sim\\pi(y|x)}\\bigg\[\\log\\frac{\\pi(y|x)}{\\pi\_{ref}(y|x)}-\\log \\exp(\\frac{r(x,y)}{\\beta}) \\bigg\] \\\\ =\\underset{\\pi}\\min\\mathbb{E}\_{x\\sim\\mathcal{D}}\\mathbb{E}*\_*{y\\sim\\pi(y|x)}\\bigg\[\\log\\frac{\\pi(y|x)}{\\pi\_{ref}(y|x)\\exp(\\frac{r(x,y)}{\\beta})}\\bigg\] \\end{aligned} $$ 看到这个式子我们能想到,如果能把log后面的的式子凑成满足KL散度的格式就好了, > 满足KL散度有两个硬性的要求,即P和Q分别要满足合法的概率分布,即两者所有可能的y加起来要等于1!!$\\sum\_y P(y)=1$ 我们可以观察到,分子是能满足的,但是分母是绝对不满足的,于是我们便可以想办法凑出他,假设分母的概率分布总和为Z,要让他满足KL散度,就可以直接除掉Z。此时Z的计算公式如下:$Z(x) = \\sum\_{y’} \\pi\_{\\text{ref}}(y’|x) \\exp\\left(\\frac{r(x,y’)}{\\beta}\\right)$,带入得到: $$ \\begin{aligned} 原式 &=\\underset{\\pi}\\min\\mathbb{E}\_{x\\sim\\mathcal{D}}\\mathbb{E}*\_*{y\\sim\\pi(y|x)}\\bigg\[\\log\\frac{\\pi(y|x)}{\\frac{1}{\\displaystyle Z(x)}\\pi\_{ref}(y|x)\\exp(\\frac{r(x,y)}{\\beta}) \\displaystyle Z(x)}\\bigg\] \\\\ &=\\underset{\\pi}\\min\\mathbb{E}\_{x\\sim\\mathcal{D}}\\mathbb{E}*\_*{y\\sim\\pi(y|x)}\\bigg\[\\log\\frac{\\pi(y|x)}{\\frac{1}{\\displaystyle Z(x)}\\pi\_{ref}(y|x)\\exp(\\frac{r(x,y)}{\\beta})}-\\log \\displaystyle Z(x)\\bigg\] \\end{aligned} $$ 设: $$\\pi^{\\ast}(y|x) = \\frac{1}{Z(x)} \\pi\_{\\text{ref}}(y|x) \\exp\\left( \\frac{r(x,y)}{\\beta} \\right)$$ 带入得到: $$= \\min\_\\pi \\mathbb{E}*{x \\sim \\mathcal{D}} \\bigg\[ \\mathbb{D}*{\\text{KL}} \\Big( \\pi(y|x) \\Vert \\pi^{\\ast}(y|x) \\Big) - \\log Z(x) \\bigg\]$$ > 此处的$\\mathbb{E}\_{y \\sim \\pi(y|x)} \\left\[ \\log Z(x) \\right\]$,由于$Z(x)$上面的定义,所有的$y’$都被遍历并加起来了,于是这个变量就和y一点关系也没有了,对于“对y求期望”这一项就相当于常数,然后常数的期望就等于常数本身,于是直接就等于$\\log Z(x)$ 此时我们的问题关键就到了,找到一个策略,让上面这个式子最小化!!根据*吉布斯不等式*,两个概略分布的KL散度永远大于等于0,只有当两个分布完全一模一样的时候,散度才能等于0,为了让上面的式子最小,我们就得到: $$ \\pi(y|x)=\\pi^{\\ast}(y|x)=\\frac{1}{Z(x)}\\pi\_{ref}(y|x)\\exp(\\frac{r(x|y)}{\\beta}) $$ 至此,传统的PPO方法推导就会结束,传统的方法就是:既然我知道了最优解长这个样子,那我就去训练一个奖励模型 $r(x,y)$,然后用强化学习逼近这个 $\\pi^{\\ast}$。 但是DPO不一样。🥹 ### 神来之笔 DPO作者提出我们既然已经知道最优策略$\\pi^{\\ast}$长这个样子了,那么我们为什么还要花功夫去把$r(x,y)$求出来呢,我们为什么不直接**把 $r(x,y)$ 用 $\\pi^{\\ast}$ 表达出来**?。 两边同除$\\pi\_{ref}(y|x)$: $$\\frac{\\pi^{\\ast}(y|x)}{\\pi\_{\\text{ref}}(y|x)} = \\frac{1}{Z(x)} \\exp\\left( \\frac{1}{\\beta} r(x,y) \\right)$$ 两边同时取自然对数 $\\log$: $$\\log \\frac{\\pi^{\\ast}(y|x)}{\\pi\_{\\text{ref}}(y|x)} = \\log \\left( \\frac{1}{Z(x)} \\exp\\left( \\frac{1}{\\beta} r(x,y) \\right) \\right)$$ $$\\log \\frac{\\pi^{\\ast}(y|x)}{\\pi\_{\\text{ref}}(y|x)} = -\\log Z(x) + \\frac{1}{\\beta} r(x,y)$$ 表示$r(x,y)$: $$r(x,y) = \\beta \\log \\frac{\\pi^{\\ast}(y|x)}{\\pi\_{\\text{ref}}(y|x)} + \\beta \\log Z(x)$$ 新的问题又出现了,这里的$Z(x)$是所有可能回答的积分,计算会非常复杂,怎么办? 此时DPO又回归数据的本质了,我们不需要关注一个回答的绝对分数是多少多少,我们**只需要关注偏好**即(*preference*,DPO里面的P),换句话说就是,我们只关注哪个回答更好,哪个更坏。 根据Bradley-Terry 模型,人类觉得 $y\_w$ 比 $y\_l$ 好的概率,取决于它们的**分数差**: $$r(x, y\_w) - r(x, y\_l)$$ $$ \\begin{aligned} r(x, y\_w) - r(x, y\_l) &= \\left\[ \\beta \\log \\frac{\\pi^{\\ast}(y\_w|x)}{\\pi\_{\\text{ref}}(y\_w|x)} + \\color{red}{\\beta \\log Z(x)} \\right\] - \\left\[ \\beta \\log \\frac{\\pi^{\\ast}(y\_l|x)}{\\pi\_{\\text{ref}}(y\_l|x)} + \\color{red}{\\beta \\log Z(x)} \\right\]\\\\ &= \\beta \\log \\frac{\\pi^{\\ast}(y\_w|x)}{\\pi\_{\\text{ref}}(y\_w|x)} - \\beta \\log \\frac{\\pi^{\\ast}(y\_l|x)}{\\pi\_{\\text{ref}}(y\_l|x)} \\end{aligned} $$ > 按照 Bradley-Terry 模型,$y\_w$ 战胜 $y\_l$ 的概率是 : $$P(y\_w \\succ y\_l) = \\sigma(r\_w - r\_l)$$ 我们在深度学习里,想要最大化这个获胜概率,也就是要**最小化它的负对数似然 (Negative Log-Likelihood)**。最后,我们把理论上的最优解 $\\pi^{\\ast}$,替换成我们正在用 PyTorch 训练的神经网络参数 $\\pi\_\\theta$。 这就自然而然地得到了在文档里看到的、大名鼎鼎的 **DPO 损失函数**: $$\\mathcal{L}*{DPO} = - \\mathbb{E}*{x, y\_w, y\_l} \\left\[ \\log \\sigma \\left( \\beta \\log \\frac{\\pi\_\\theta(y\_w|x)}{\\pi\_{\\text{ref}}(y\_w|x)} - \\beta \\log \\frac{\\pi\_\\theta(y\_l|x)}{\\pi\_{\\text{ref}}(y\_l|x)} \\right) \\right\]$$ > 至于最后为什么要用**负对数似然**呢,这里纯粹是**工程实践**的问题。我们实际情况肯定不是一条数据,如果有很多数据,概率都是0到1之间,直接乘会导致计算机出现浮点数下溢出(underflow),于是便增加了一个求对数,把乘积转变成累加。 为什么是负呢,纯粹是因为目前深度学习框架的几乎所有的优化器(adam,sgd)在设计的时候都默认用来寻找“山谷”的最低点,我们最大化一个东西,就直接取负就行了。 而最后套的这个求期望,就是表达深度学习中最基础的一个动作:**“在整个训练数据集上求平均 Loss”**。 至此DPO的核心公式就推导完了!! ### 个人小结 DPO的最大创新就是我上面提到的那个神来之笔,即**无须显式的拟合奖励模型**的情况下,高效的学习出与人类偏好一致的最优策略。 “DPO 是把 RM 和 RL 揉在了一起,用一步分类 Loss 直接干完了两步的活”。 ## GRPO 如果说DPO是通过数学魔法逃避了强化学习,而GRPO就是正面硬刚,真正把强化学习做到了极致的轻量化。 ### 背景介绍 GRPO是在2024年由deepseek团队提出来,即group relative policy optimization。在deepseek-math和deepseek-r1中使用。 传统ppo采用的还是actor-critic(演员-评论家)架构。 - actor模型:负责生成回答(70B参数)。 - critic模型:负责给actor生成的答案打分。(通常会和actor模型一样大70B参数) 于是为了更新一个70B的模型,gpu显存中必须常驻着两个同样大的模型。 ### 底层原理 ![image.png](https://img.somnus.wiki/file/1783484297460_image.png) 从[论文](https://arxiv.org/pdf/2402.03300?ref=blog.somnus.wiki)中的可以得到这个图片,可以直观的看出来ppo和grpo的区别。 grpo是对ppo的一次架构重构,他的核心是,**采用组内采样的统计量作为baseline代替原本的crtical(value model)预测baseline**。 第一步:群体采样 > 对于每次训练迭代,我们从训练集中取出一个关键词q,使用当前的训练策略(policy model)生成一组G个输出${o\_1,o\_2\\dots o\_G}$ ,在deepseek的实践中,这个G常常设置为4\~8 个。 第二步:计算绝对奖励 > 将这 $G$ 个输出 ${o\_1, o\_2, \\dots, o\_G}$ 分别送入奖励函数中进行评估。 这个奖励函数可以是一个训练好的奖励模型(Reward Model),也可以是一个基于代码运行结果、数学验证器的**确定性规则(Rule-based Reward)**。评估后,我们得到一组标量分数:${r\_1, r\_2, \\dots, r\_G}$。 3.第三步:计算相对优势 (Advantage Estimation): 通过 Z-Score 归一化消除方差,取代 Critic 网络。 这是 GRPO 的核心。PPO 使用 Critic 模型来预测一个状态的期望价值 $V(s)$ 作为基线。而 GRPO 直接在刚刚生成的这组数据内部计算统计量: 1. 计算这 $G$ 个分数的均值 $\\mu = \\frac{1}{G} \\sum\_{i=1}^G r\_i$ 2. 计算标准差 $\\sigma = \\sqrt{\\frac{1}{G} \\sum\_{i=1}^G (r\_i - \\mu)^2}$ 然后,对每一个输出 $o\_i$ 的得分进行标准化,得到**优势函数(Advantage)**: $$A\_i = \\frac{r\_i - \\mu}{\\sigma}$$ **数学意义:** $A\_i > 0$ 表示该回答在当前策略的生成空间中好于平均水平;$A\_i < 0$ 表示低于平均水平。通过除以标准差 $\\sigma$,使得奖励信号的分布始终保持稳定,极大降低了训练过程中的梯度方差。 4.第四步:计算目标函数并更新参数 (Policy Optimization):结合 PPO 的 Clipping 和 KL 散度正则化。 最后,GRPO 要最大化它的目标函数(Objective Function)。这个公式包含了两个核心部分:**收益最大化项** 和 **KL 散度惩罚项**。 $$J\_{GRPO}(\\theta) = \\mathbb{E}*{q, {o\_i}*{i=1}^G} \\left\[ \\frac{1}{G} \\sum\_{i=1}^G \\left( \\mathcal{L}*{clip}^{(i)} - \\beta \\mathbb{D}*{KL}(\\pi\_\\theta || \\pi\_{ref}) \\right) \\right\]$$ 我们将括号里的两项拆开看: **1\. 裁剪损失(Clip Loss, 继承自 PPO):** $$\\mathcal{L}*{clip}^{(i)} = \\min \\left( \\frac{\\pi*\\theta(o\_i|q)}{\\pi\_{\\theta\_{old}}(o\_i|q)} A\_i, \\text{clip}\\left(\\frac{\\pi\_\\theta(o\_i|q)}{\\pi\_{\\theta\_{old}}(o\_i|q)}, 1-\\epsilon, 1+\\epsilon\\right) A\_i \\right)$$ 这里 $\\frac{\\pi\_\\theta}{\\pi\_{\\theta\_{old}}}$ 是新旧策略的概率比值。Clip 函数将其限制在 $\[1-\\epsilon, 1+\\epsilon\]$(通常 $\\epsilon = 0.2$)之间。**原理在于:** 即使 $A\_i$ 非常大(某个回答特别好),也不允许模型一次性把这个回答的生成概率调得过高,防止策略更新步子迈得太大导致“破坏性遗忘”。 **2\. KL 散度惩罚(KL Penalty):** $$\\beta \\mathbb{D}*{KL}(\\pi*\\theta || \\pi\_{ref}) = \\beta \\frac{\\pi\_{ref}(o\_i|q)}{\\pi\_\\theta(o\_i|q)} - \\log \\frac{\\pi\_{ref}(o\_i|q)}{\\pi\_\\theta(o\_i|q)} - 1$$ *注:这是 DeepSeek 论文中使用的精确 KL 估计器,比传统的 $\\log$ 差值更无偏。* 这一项强制正在训练的模型 $\\pi\_\\theta$ 不能偏离初始的参考模型 $\\pi\_{ref}$ 太远。如果模型为了迎合高奖励而输出了乱码或极端重复的文本,KL 散度会迅速飙升并将其扣分扣回来。 ### 为什么这种“粗暴”的数学替换有效? 从算法工程的角度看,GRPO 解决了一个核心的资源死锁问题: 1. **同策略(On-Policy)的必要性:** RLHF 必须是 On-policy 的(模型必须对自己刚刚生成的文本进行评估和更新),以防止策略漂移。 2. **Actor-Critic 的显存灾难:** 为了实现 On-policy 的基线预测,PPO 必须保留一个同等参数规模的 Critic。70B 的 Actor 就需要 70B 的 Critic。 3. **GRPO 的无偏估计:** 数学上已经证明,在相同状态 $q$ 下生成多条轨迹,其组内均值是一个针对该状态的**无偏基线估计(Unbiased Baseline Estimator)**。只要组大小 $G$ 不极度小,它的作用与 Critic 完全等价。 通过将复杂的神经网络推断(Critic 前向传播)替换为极简的标量数学运算(求均值和方差),GRPO 在理论严密性不受损的前提下,将强化学习的显存开销直接砍掉了将近一半。 ### 2026-06-25 URL: https://blog.somnus.wiki/diary/2026-06-25/ Last updated: 2026-07-22T01:39:53.000Z > 之前对于 ssh 做了一点初步的了解,但是还只是关于指令的了解,并不够深入,今天在尝试无密码连接服务器,或者树莓派终端的时候有了更深入的了解。 ## ssh 连接方式 在使用服务器的时候,肯定会使用 ssh 指令来连接,详情可以参见这篇[博客](https://blog.somnus.top/p/%E6%9C%8D%E5%8A%A1%E5%99%A8%E5%9F%BA%E7%A1%801/?ref=blog.somnus.wiki),但是上面提到的连接方式都是最传统的 ssh 用户名@ip 地址,然后输入用户名的密码。 一方面这种方式效率很慢,如果密码是自己设置的常用的密码,可能连接还会比较顺手,但如果像我之前通过 vaultwarden 设置的强密码,每次连接服务器都要通过打开 vaultwarden 复制粘贴,那这样就麻烦很多了;另外,还有一个很明显的问题就是,这样通过输入用户名密码的方式,给人的感觉就是安全性很低,虽然 ssh 是加密传输的,但是直接把明文密码输入到终端,总感觉是容易被抓取到并破坏服务器的。于是便有了密钥连接。 ### 使用方式 密钥,是通过 ssh-keygen生成的存储在本地的一对密钥值。比如通过以下指令: ``` ssh-keygen -t ed25519 -C "somnusmac" ``` 这样便会在当前用户的.ssh文件夹内生成以下文件夹的两个文件。 ``` ➜ somnus-blog git:(main) ✗ ls -l ~/.ssh/id_ed25519* -rw------- 1 somnus staff 419 4月 27 11:06 /Users/somnus/.ssh/id_ed25519 -rw-r--r-- 1 somnus staff 108 4月 27 11:06 /Users/somnus/.ssh/id_ed25519.pub ➜ somnus-blog git:(main) ✗ ``` 其中-t(type)后面的参数表明的是两个密钥的加密方式,主要有以下几种加密方式: | 类型 | 生成命令 | 现在是否推荐 | 说明 | | ---------------------------------------------------------------------------------------------------------- | -------------------------- | ------------ | -------------------------------------------------------------------------------------------------------------------------------------------------------- | | **Ed25519** | ssh-keygen -t ed25519 | **最推荐** | 现代、快、安全、密钥短,OpenSSH 现在默认也是它。([OpenBSD Manual Pages](https://man.openbsd.org/ssh-keygen?utm%5Fsource=chatgpt.com "ssh-keygen(1) - OpenBSD manual pages")) | | **RSA** | ssh-keygen -t rsa -b 4096 | 可用,但不是首选 | 兼容性最好,老服务器也支持。建议至少 3072/4096 位。 | | **ECDSA** | ssh-keygen -t ecdsa -b 256 | 可用 | 也是椭圆曲线算法,但一般现在不如直接用 Ed25519 简单。 | | **DSA** | ssh-keygen -t dsa | **不推荐/基本淘汰** | 老算法,不建议再用。 | | **ed25519-sk** | ssh-keygen -t ed25519-sk | 高安全场景可用 | 需要硬件安全密钥,比如 YubiKey/FIDO2。 | | **ecdsa-sk** | ssh-keygen -t ecdsa-sk | 高安全场景可用 | 也是硬件安全密钥类型。 | | 然后生成的两个文件呢,id\_ed25519是私钥,id\_ed25519.pub是公钥,前者是放在自己的电脑上,相当于“身份证”,后者则是你身份的登录信息,要放到服务器、树莓派、github、aur 的远程上。 | | | | ``` 你的电脑:我想用这把公钥登录 GitHub:我查一下这把公钥属于哪个账号 GitHub:给你一个随机挑战,你用私钥签名给我看 你的电脑:用私钥签名,但私钥本身不发出去 GitHub:用刚才那把公钥验证签名 GitHub:验证通过,确认你是这个 key 的持有者 ``` 至于我提到的,会不会出现公钥很多导致查找速度慢,这个基本是不会的,因为在上传时会把公钥上传到服务器,先找到对应的公钥,然后服务器发一个数值,本地通过私钥对这个数值进行签名,然后把签名原数据发给服务器,再验证。 ## tailscale 记得当时在搭建那个树莓派上基于 jellyfin 的流媒体网站的时候,当时用的内网穿透方式是 cloudflare 的 tunnel。然后我在尝试在公网环境访问我的树莓派的时候第一时间也想到了使用 cloudflare 的 tunnel,然后也很顺利的连接上了,但是连接的延迟有点不堪入目,所以我把方案换成了另一个 tailscale,感觉这个效果也是非常之好,于是记录一下。 ### 基本原理 tailscale是给所有设备建了一个独属于自己的虚拟局域网。标志就是这些以 100 开头的 ipv4 地址。 ![截屏2026-06-25 16.53.12.png](https://img.somnus.top/file/1782377627413_%E6%88%AA%E5%B1%8F2026-06-25_16.53.12.png) 其大致的流程是这样的: ``` 你的 Mac 登录 Tailscale ↓ 树莓派也登录同一个 Tailscale 账号 / tailnet ↓ Tailscale 控制服务器告诉双方: 你们彼此是谁、公钥是什么、当前可能的网络地址是什么 ↓ Mac 和树莓派尝试直接建立 WireGuard 加密连接 ↓ 成功后,你就可以 ssh 100.71.172.51 ``` tailscale 是基于 wireguard的自动组网工具,它能把 Mac、树莓派、服务器组成一个“私人虚拟局域网”,并自动处理密钥、IP、DNS、权限、NAT 穿透和中继。 通常来说,想要两个设备(非局域网)连接,要知道*公网 ip、路由器端口有无转发、防火墙是否放行、ip 会不会变这些问题*,但是 tailscale 把这些问题都抽象掉了,只需要你保证你的设备都连接进了 tailnet,这些问题都会由 tailscale 来解决。 ### 层级划分 tailscale分为控制平面和数据平面, ``` 控制平面 Tailscale coordination server 登录 / 设备列表 / 公钥分发 / ACL / DNS / NAT 协调 │ │ 只负责协调 │ MacBook ←──────── 数据平面 ────────→ 树莓派 WireGuard 加密通信,优先点对点直连 ``` 控制层面负责“谁是谁”、“谁能访问谁”。而数据层面则是真正负责两个设备之间的加密通信的。 ### 具体流程 当你执行连接的时候,tailscale 经历了一下过程: 1. SSH 客户端准备连接 100.71.172.51:22 2. 操作系统查路由表: 100.71.172.51 是 Tailscale 网络地址 所以这包流量交给 Tailscale 虚拟网卡 3. Tailscale 客户端发现: 目标设备是 raspberrypi 你有权限访问它 对方的 WireGuard 公钥是 xxx 4. Mac 和树莓派尝试建立 WireGuard 加密隧道 5. 优先 NAT 穿透直连: Mac ←──── UDP 加密隧道 ────→ 树莓派 6. 如果直连失败: Mac ←──── DERP 中继 ────→ 树莓派 但中继看到的仍然是加密包 7. 树莓派收到包后解密 8. 包被交给树莓派本机的 sshd 服务,也就是 22 端口 9. 然后才进入正常 SSH 登录流程: 密码登录 / SSH key 登录 / 权限检查 ### 使用 登录 ``` # 登录(浏览器弹出授权页面) tailscale up # 登录并生成二维码(适合无桌面环境) tailscale up --qr # 使用 Auth Key 静默登录(适合服务器/自动化) tailscale up --authkey=tskey-auth-xxxxx # 登出(断开但保留账号) tailscale down # 完全登出并移除账号 tailscale logout ``` 状态 ``` # 查看所有节点状态 tailscale status # 只看活跃连接 tailscale status --active # JSON 格式输出 tailscale status --json # 查看本机 Tailscale IP tailscale ip tailscale ip -4 # 只看 IPv4 tailscale ip -6 # 只看 IPv6 # 查看某个 IP 对应的设备/用户 tailscale whois 100.x.x.x ``` 启用 tailscale ``` # 启动/停止/重启 daemon sudo systemctl start tailscaled sudo systemctl stop tailscaled sudo systemctl restart tailscaled # 查看 daemon 日志 tailscale debug daemon-logs journalctl -u tailscaled --since="1 hour ago" ``` 安装 ``` # linux curl -fsSL https://tailscale.com/install.sh | sh # macos brew install tailscale ``` 安装后的流程 ``` curl -fsSL https://tailscale.com/install.sh | sh sudo systemctl enable --now tailscaled sudo tailscale up ``` ## cloudflare tunnel 对于 tailscale 你想要连接的每一个设备都需要登录自己的账号,但如果想要有一个公用的服务器(大家都能登录的),这时候 cloudflare 就会更方便。 ### 使用方法 首先要有一个 cloudflare 账号,然后创建连接器(tunnel)💩翻译,我找了好久都没有找到 tunnel。 ![image.png](https://img.somnus.top/file/1782381993827_image.png) 然后按照他的安装指令在服务器上安装对应的包: ![image.png](https://img.somnus.top/file/1782383540944_image.png) ### 2026-06-24 URL: https://blog.somnus.wiki/diary/2026-06-24/ Last updated: 2026-07-21T07:57:07.000Z ![截屏2026-06-24 11.59.12.png](https://img.somnus.top/file/1782273637012_%E6%88%AA%E5%B1%8F2026-06-24_11.59.12.png) ## 1\. 开场 下面结合你提到的大数据、物联网、人工智能、具身智能和机电电子等方向,聊聊我自己的理解。我的本科专业是智能制造,是机械和计算机的交叉学科;研究生读人工智能,导师的课题是具身智能。身边也有不少电子信息、计算机专业的同学,所以这些内容主要来自个人经历和身边样本,供你参考。 > 先说结论:填志愿时,与其执着于一个听起来最新的专业名称,不如优先保证自己处在**接近目标方向、后续选择更多**的轨道上。 ### 1.1 简单聊专业 你提到的方向都不错,也能看出来你已经在关注比较前沿的行业了。机电电子通常更常被归到电子信息大类;具身智能则是一个相对新的方向,本科直接开设这个专业的学校目前并不多。 从我了解到的情况看,人工智能、具身智能等方向如果想做更核心的研发,**读研通常会更有优势**;电子信息、计算机、软件工程等专业则相对拥有更多本科直接就业的岗位。当然,这不代表其他专业本科就没有机会,只是不同人的就业结果会差很多。 如果你的目标是将来读人工智能,并不一定非要本科就读人工智能。大数据、软件工程、计算机,甚至数学等相近专业,都可能在考研或保研时转向人工智能。我研究生同学中,就有本科读软件工程保研过来的,也有数学系考研过来的。 所以本科填志愿时,不必把专业卡得太死。更重要的是优先保证自己**不偏离目标方向**:例如本科读电子信息、研究生转向具身智能,路径很自然;但如果本科专业和目标领域距离很远,后续转向的门槛通常会更高。像土木、环境等专业,想通过保研大幅跨到新兴工科方向,往往就需要付出更多努力。 我有一位高中同学,本科读人工智能,因为担心本科直接就业竞争力不足,现在正在准备二战考研。相较之下,他所在学院的计算机专业本科就业岗位会更多一些。这个例子也说明,选择“热门专业”之外,还应考虑学校的培养质量、课程设置和自己的升学计划。 ### 1.2 中外合办 如果家庭条件允许,中外合办可以纳入志愿比较。它的优势往往是以更高的学费,换取更高层次的学校平台或更集中的优质专业;但前提是认真核实办学主体、培养模式、毕业证书、学费、是否有海外培养要求,以及后续升学和就业情况。 > 中外合办不是“天然更好”,而是一个*可能用成本换取平台*的选项。信息一定要向招生办逐项确认。 我高中时也担心过中外合办的认可度,所以没有填报,现在想来多少有些遗憾。我的看法是:如果项目本身靠谱、毕业证书和普通项目的差异可接受、专业也符合自己的规划,同时学校层次能有明显提升,那就值得认真考虑。报之前最好直接向招生办确认细节,不要只看宣传材料。 ### 1.3 保研 优先保证专业方向的想法很好,但也别忽略学校本身的情况。比如学校是否有推免资格、推免比例大概多少,这些都是值得比较的信息。 不是说上大学就一定要保研,而是有这样一条路,未来就多一个选择。推免可以减少**一次性大考的风险**,也让你有更多时间了解导师和课题;不少学校也会为推免生提供覆盖学费的奖学金。不过具体政策每年、每校都可能不同,填报前还是要看学校公布的最新信息。 至于具体怎么准备保研,这又是一个很长的话题,今天先不展开。 ## 2\. 回答你的问题 ### 2.1 行业前景 我身边本科毕业后直接就业的人不算多,所以对整体薪资没有特别完整的数据。以我参加全国大学生机器人大赛 Robocon 的经历来看,机器人队里不少电子信息、电气专业的队友已经就业,有人拿到了华为、大疆、影石等公司的研发岗 offer。这些结果除了专业基础,也和比赛、项目、科研、实习等经历有关。 其中一位去华为成都的本科同学,月薪大约是 18k;以本科就业、成都的生活成本来看,我觉得这是一个不错的 offer。但同班同学中,也有人拿到的 offer 不太理想,薪资不到 10k,工作强度却不低。 我想表达的是:即使学校和专业相同,不同人的去向也可能差很多。工科就业除了学校和专业提供的基础,更看**比赛、科研、实习和项目能力**。学校的名头很重要,但单靠它通常不足以拿到理想的 offer。 ### 2.2 薪资情况 > 以下薪资只来自我身边同学的样本,*不代表普遍市场水平*;具体情况还会受到城市、岗位、行业和毕业年份影响。 我本科学校属于中上游 211,工科实力还不错;电子信息本科就业较好的同学,月薪大约能到 **20k**、年总包约 **28 万**,较低的也可能在 **10k 左右**。人工智能方向的本科同学大多选择读研,所以我没有足够的样本给出判断。 薪资增长也很依赖具体公司、岗位和个人选择。以前面提到的华为为例,我那位在成都入职的队友所在岗位,职业流动性相对更高,后续可以通过积累经验、转换岗位或跳槽来争取薪资提升;这当然只是我从身边案例得到的观察。相比之下,我室友曾拿到中铁建的 offer,他更在意薪资、工作强度和长期发展空间,最后选择继续考研。央国企也有稳定性和福利等优势,只是对他个人而言,这份 offer 并不符合当时的期待。 ### 2.3 女生适配 我理解你的担心主要有两方面:一是担心自己能不能学好,二是担心未来的工作环境。 学习上,**性别不是决定因素**。大学更看自驱力、学习习惯和能否持续投入。我们班一共 5 个女生、20 个男生,6 个保研名额里有 3 位女生拿到了不错的去向,比如浙大机械、天大机械和哈工威;这只是一个小样本,但也足以说明女生完全可以在工科里做得很出色。 就业上,不同专业和岗位的工作环境确实差异很大。若你更在意工作地点、出差频率、现场作业等因素,可以在填志愿时重点了解培养方向和常见去向。土木、环境、地质等传统行业中,部分岗位的现场工作比例会更高;电子信息、计算机、人工智能等方向则通常有更多研发、算法、软件和产品类岗位。最终还是建议结合自己的**兴趣、能力和对生活方式的期待**来选,而不必因为性别先把某个方向排除。 ## 3\. 结束 我的分享就到这里了。你还有什么想问的吗? ···· 最后祝你读到理想的专业和学校,未来一帆风顺。 ### 模型微调 URL: https://blog.somnus.wiki/notes/mo-xing-wei-diao/ Last updated: 2026-07-21T07:57:10.000Z > 模型后处理,对于大模型的应用,对于不同的特定场景,往往会有不同的能力需求,此时后处理(微调)就显得很有必要了。 ## LoRA 对于每一个任务,如果都需要从头训练大模型,那产生的**成本**往往是过量的,不光是训练的算力和时间成本,为了防止出现过拟合,从头训练大模型也需要大量的数据。于是便有人提出了这个方法LoRA,低成本的微调大模型。 **Prompt的质量和多样性**远比数量重要,全量微调一个30B量级的base model只需要10w条数据即可,lora微调一个30B的大约需要2w条。 ### 方法介绍 LoRA是在模型的关键层(多头注意力或者forward feed)上添加一个低秩矩阵,将其添加到原始权重矩阵上,该方法不需要改变整个模型的结构,在推理的时候也**不需要额外的计算量**,并能**保持原始的性能**。 $$ W=W\_0+BA $$ 其中$B \\in \\mathbb{R}^{d\\times r}$,$A\\in \\mathbb{R}^{r\\times k}$,可以得到BA的$rank(BA) \\leqslant r$ 。通过训练A和B矩阵来对模型进行微调。 ![LoRA 在冻结原权重上添加低秩补丁](https://img.somnus.top/file/1782116667201_01-lora-low-rank-patch.png) ![image.png](https://img.somnus.top/file/1781763468065_image.png) ### 为什么需要参数少 将设W是4096\* 4096的矩阵,设定上面的r为8,如果要训练完整的一层,则需要16777216个参数,而通过LoRA来微调,就只需要训练那个添加的低秩矩阵。总参数为$4096\\times 9+9\\times 4096=65536$个参数。参数直接锐减了很多。 ![LoRA 用更少的可训练参数完成微调](https://img.somnus.top/file/1782116667473_02-fewer-trainable-parameters.png) 完整的公式应该是这样的: $$W=W\_0+\\frac{\\alpha}{r}BA$$ 其中r是上面的低秩矩阵的秩,而$\\alpha$是超参数,用于控制LoRA对模型的*修改力度*。 这也就是LoRA的巧妙之处。 就是可以**使用少的多的参数构造一个尺寸和原权重一样的,但是自由度被限制在低秩空间的更新矩阵**。 ![image.png](https://img.somnus.top/file/1781763988077_image.png) 这个表中可以看到,哪怕r取得很小,模型的准确率保持的还是很高的。 ### r如何设置 r控制的是LoRA的**表达能力**,r 越大,LoRA 能学的变化越复杂,但参数更多、显存更高、也更容易过拟合。r 越小,参数更少、更省,但表达能力受限。 总结就是一句话,模型需要解决的任务越复杂就需要越大的r,反之则越小的r。 最常用的参数就是: $$ r=8,\\alpha=16 $$ ![LoRA rank r 的表达力与开销取舍](https://img.somnus.top/file/1782116661992_03-rank-tradeoff.png) --- ## AdaLoRA [AdaLoRA: Adaptive Budget Allocation for Parameter-Efficient Fine-Tuning](https://arxiv.org/abs/2303.10512?ref=blog.somnus.wiki) 即Adaptive LoRA,原本的LoRA的r都是由人手动设置的,但是对于每个层可能需要的r都不一样。有些层相对重要,就应该分配更大的r,有些层则应该分配更小的r。 ### 方法介绍 普通的LoRA计算公式是这样的:$W=W\_0+BA$,但是AdaLoRA是这样的: $$ \\begin{aligned} W&=W\_0+P\\Lambda Q \\\\ P&\\in \\mathbb{R}^{d\_1\\times r} \\\\ Q&\\in \\mathbb{R}^{r\\times d\_1} \\\\ \\Lambda \\in \\mathbb{R}^{r\\times r} \\end{aligned} $$ 其中$\\Lambda$是: $$ \\Lambda =diag(\\lambda\_1,\\lambda\_2 \\dots \\lambda\_r) $$ 此处使用了奇异值分解(SVD),AdaLoRA并没有说觉得哪个层的权重不重要,就把那一层删掉,而是把其中的$\\Lambda$对应行的$\\lambda$置零,后续如果发现被误判了重要性,还可以把它恢复过来。 奇异值分解(Singular Value Decomposition,简称 SVD)是线性代数中**最重要**的矩阵分解方法之一。可以把它理解为“**矩阵的极坐标表示**”——任何矩阵都能被**分解成三个简单矩阵的乘积。** $$ \\begin{aligned} P\\Lambda Q&=\\sum\_{i=1}^{r}\\lambda\_i P\_{\\ast,i}Q\_{i,\\ast}\\\\ &=\\lambda\_1P\_{\\ast,1}Q\_{1,\\ast}+\\lambda\_2 P\_{\\ast,2}Q\_{2,\\ast}+\\dots +\\lambda\_rP\_{\\ast,r}Q\_{r,\\ast} \\end{aligned} $$ 在原论文中这样的三个变量组合起来,叫做triplet。如下: $$ G\_i={P\_{\\ast,i},\\lambda\_i,Q\_{i,\\ast}} $$ 就是一个奇异值,左奇异向量,右奇异向量的组合。 然后如果要降低rank,就把某个$\\lambda\_i$置0。 ### 降低rank方法 就如上面所说,adalora要降低rank,就是将不那么重要的triplet对应的lambda去掉。将某个lambda置0,则反映到$P\\Lambda Q$是降低了他的rank(秩)。 该方法没有直接把P或者Q删除,而是将其对应方向的$\\lambda$设置为0,AdaLoRA 只 **mask 掉奇异值**,**保留奇异向量**,这样被误剪掉的方向后面还**有恢复可能**;相比直接剪掉 LoRA 的 doublet,训练会**更稳定**。 ![AdaLoRA 将 lambda 置零但保留奇异向量以便恢复](https://img.somnus.top/file/1782116666854_04-adalora-reversible-pruning.png) ### 评价方法 那么我们应该如何评价这样的参数对于原本的权重矩阵有没有影响呢,如何衡量呢。 最简单的方法就是直接看对应的奇异值大小,如果奇异值越大,则认为该triplet越重要。公式如下: $$ S\_{k,i}=|\\lambda\_{k,i}| $$ 但这样很明显是不准确的。 原论文中给了这样的公式: $$ S\_{k,i}=S(\\lambda\_{k,i})+\\frac{1}{d\_1}\\sum\_{j=1}^{d\_1}S(P\_{k,ji})+\\frac{1}{d\_2}\\sum\_{j=1}^{d\_2}S(Q\_{k,ij}) $$ 其中: $$ \\begin{aligned} S(\\omega\_{ij})&=\\overline{I}(\\omega\_{ij})U(\\omega\_{ij})\\\\ &=|\\omega\_{ij}\\nabla\_\\omega \\mathcal{L}|U(\\omega\_{ij}) \\end{aligned} $$ 逐步解释一下,其中的$\\nabla\\mathcal{L}$是loss对$\\omega$的梯度,表明如果把这个参数删掉,**loss 大概会变化多少**。AdaLoRA 借鉴了剪枝里的思想: 如果把某个参数置 0,会让 loss 变化很大,那这个参数就重要。 而$\\overline{I}$表明此处做的是平滑敏感度,即 $$\\overline{I}(t)(w)=β1​\\overline{I}(t−1)(w)+(1−β1​)I(t)(w)$$ 如果: $$\\beta\_1=0.85$$ 那么新的平滑分数大概是: $$ 85%旧历史+15%当前 batch $$ 这样可以避免因为某一个 batch 的偶然性而误判重要性。 而$U(\\omega\_{ij})$是uncertainty term,用局部变化来量化不确定性。 ![截屏2026-06-18 16.28.41.png](https://img.somnus.top/file/1781771339194_%E6%88%AA%E5%B1%8F2026-06-18_16.28.41.png) 两者相乘,表明了我们这个式子是想得到**既敏感,又值得关注的参数**。 ``` flowchart TD A[训练一步] --> B[前向传播
ΔW_k = P_k Λ_k Q_k] B --> C[计算 Loss 并反向传播] C --> D[获得参数梯度] D --> E["单参数敏感度
I(w)=|w·∇wL|"] E --> F[指数滑动平均
平滑敏感度] F --> G[计算不确定性] G --> H["单参数重要性
s(w)=平滑敏感度 × 不确定性"] H --> I["聚合成 triplet 重要性
G_{k,i}={P_{k,*i}, λ_{k,i}, Q_{k,i*}}"] I --> J["S_{k,i}=s(λ)+avg s(P列)+avg s(Q行)"] J --> K[所有 triplet 全局排序] K --> L{是否在当前预算
Top-b 内?} L -- 是 --> M[保留 λ] L -- 否 --> N[λ 置 0] M --> O[得到动态 rank 分配] N --> O ``` ### 全局分配器 adalora原论文标题中还强调了*BUDGET ALLOCATION*,他是如何实现的呢。就是通过其独特的全局分配器。 全局分配器负责决定:当前训练阶段,全模型一共允许保留多少个低秩方向;Top-b 负责决定:具体保留哪些方向。 top-b的b就是从所有矩阵的所有triplet中选择最大的b个triplet保留下来。 全局分配器,调度的就是这个$b^{t}$,也就是当前训练step 为t的时候,允许保留的b个triplet。 AdaLoRA 通常是: 一开始预算比较大,允许保留更多 triplet; 中间**逐渐减少预算**,慢慢压缩 rank; 最后到达目标预算,固定下来继续训练。 ![AdaLoRA 全局预算逐步收紧并保留 Top-b 方向](https://img.somnus.top/file/1782116661964_05-global-budget-scheduler-v2.png) ``` 为什么要这样设计呢?因为一开如果设置很小的triplet,很容易误删很多后面有用的triplet。 ``` 整个AdaLoRA的流程如下: ``` flowchart TD A[预训练模型参数
W0 冻结不更新] --> B[选择需要适配的权重矩阵
如 Wq / Wv / Wo / FFN] B --> C[为每个权重矩阵加入 AdaLoRA 增量] C --> D["普通 LoRA: ΔW = BA"] C --> E["AdaLoRA: ΔW = PΛQ"] E --> F["展开为多个 rank-1 方向
ΔW = Σ λ_i P_*i Q_i*"] F --> G["每个方向是一个 triplet
G_i = {P_*i, λ_i, Q_i*}"] G --> H[前向传播
W = W0 + PΛQ] H --> I[计算任务 Loss] I --> J[加入正交正则
约束 P 和 Q 接近正交] J --> K[反向传播
得到 P / Λ / Q 的梯度] K --> L["计算单参数敏感度
I(w)=|w · ∇wL|"] L --> M[平滑敏感度
减少 mini-batch 噪声] M --> N[计算不确定性
衡量重要性波动] N --> O["得到单参数重要性
s(w)=平滑敏感度 × 不确定性"] O --> P["聚合为 triplet 重要性
S_i = s(λ_i) + avg s(P_*i) + avg s(Q_i*)"] P --> Q[收集所有层 / 所有矩阵 / 所有 triplet 的重要性分数] Q --> R[全局排序 triplet] R --> S[Global Budget Scheduler
当前允许保留 b_t 个方向] S --> T{triplet 是否在
全局 Top-b_t 内?} T -- 是 --> U["保留 λ_i
该 rank-1 方向继续生效"] T -- 否 --> V["将 λ_i 置 0
关闭该 rank-1 方向"] U --> W[得到当前 step 的动态 rank 分配] V --> W W --> X[继续训练] X --> H S --> Y[预算随训练逐渐减少
从较大 rank 逐步压缩到目标预算] Y --> R ``` ### 优缺点 优点: 1. rank分配更合理 2. 同样参数量下,效果更好 3. 可以自动发现哪些层更重要 缺点: 4. 训练流程复杂很多 5. 超参数多 6. 训练开销更大 --- ## QLoRA QLoRA即Quant LoRA,Quant就是量化的意思,顾名思义,这个方法是通过量化的手段在LoRA的基础上继续降低占用。 ### 模型量化 模型量化就是模型的低精度表示,即在不降低模型效果的前提下,用更低的精度来来表示模型的参数,从而缩减模型体积和训练模型时候的内存占用。 通过QLoRA能实现本地(单张48GB GPU)训练65B的模型,65B的模型光权重就要130GB。 | 精度 | 每参数 | 7B 权重大致占用 | | --------- | -------- | --------- | | FP32 | 4 bytes | 28 GB | | FP16/BF16 | 2 bytes | 14 GB | | INT8 | 1 byte | 7 GB | | INT4/NF4 | 0.5 byte | 3.5 GB 左右 | 原本模型可能是使用FP32存的,但是可以把它讲到INT8来存,这样就能降低很大的模型内存占用。 我的本科毕设事实上也就是一个模型量化的项目,把原本的RAFT-stereo模型从FP32量化成FP16。参见[这个项目](https://github.com/somnus0917/RAFT-Stereo?ref=blog.somnus.wiki)和[这个文章](https://blog.somnus.top/p/raft%E8%AE%AD%E7%BB%83%E6%97%A5%E5%BF%972/?ref=blog.somnus.wiki) ### 问题背景 为什么需要QLoRA呢,原本LoRA不是已经只关心那个新加的低秩矩阵吗? 回顾刚才上面的LoRA。 $$W=W\_0+BA$$ 因为随着现代模型越来越大,$W$这个权重已经变得非常大,比如上面提到的65B,模型权重体积就能达到130GB,家用显卡根本不可能能装下,连基础权重模型文件都装不下的话就别提其他的了。 ### 核心公式 QLoRA的核心公式就是如下: $$ \\begin{aligned} W\_q&={Quant}(W) \\\\ \\hat{W}&=Dequant(W\_q) \\end{aligned} $$ 在QLoRA中将冻结的原始权重W量化成,压缩得到$W\_q$,在真正计算的时候再将其解量化。 - 基座模型:冻结,并且用**4-bit**存储 - LoRA:用**FP16或者BF16**存储。 ![QLoRA 将冻结基座以 4-bit 存储,并在计算时临时展开](https://img.somnus.top/file/1782207735115_06-qlora-quantized-base.png) ### 关键设计 如果只是直接的使用我的毕业设计的量化方式(直接`new_state_dict[key] = value.half()`😅),那样的话整个模型的效果肯定不好,很多有效的信息都会被丢失掉,于是论文作者提出了下面三项关键设计。 #### NF4 解决的问题是,怎样让4-bit量化得尽量不伤模型,也就是尽量保证有效信息的传递。 普通的INT4会把一个数值范围均匀的分成16段,然而大模型的权重并不是均匀分布的,而是接近于**正态分布的**,大部分权重集中在0左右。 若依旧均匀量化,就会发生: - 0 附近最密集、最重要的数值,分辨率不够; - 两端稀少的极端值,反而浪费很多编码格子。 实际的量化过程会按照block来实现,每64个权重会共享一个缩放值c: $$W\_{\\text{block}} \\xrightarrow{\\text{absmax normalize}} \\frac{W\_{\\text{block}}}{c} \\xrightarrow{\\text{NF4}} q$$ 存储时保存: - 每个权重的 4-bit NF4 索引; - 每个 block 的 scale c。 例子: 论文中是通过这样的方式来得到这样的正态分布NF4表Q。 ``` from scipy.stats import norm import torch def create_normal_map(offset=0.9677083, use_extra_value=True): if use_extra_value: # one more positive value, this is an asymmetric type v1 = norm.ppf(torch.linspace(offset, 0.5, 9)[:-1]).tolist() # 正数部分 v2 = [0]*(256-15) ## we have 15 non-zero values in this data type v3 = (-norm.ppf(torch.linspace(offset, 0.5, 8)[:-1])).tolist() #负数部分 v = v1 + v2 + v3 else: v1 = norm.ppf(torch.linspace(offset, 0.5, 8)[:-1]).tolist() v2 = [0]*(256-14) ## we have 14 non-zero values in this data type v3 = (-norm.ppf(torch.linspace(offset, 0.5, 8)[:-1])).tolist() v = v1 + v2 + v3 values = torch.Tensor(v) values = values.sort().values values /= values.max() assert values.numel() == 256 return values Q = create_normal_map() Q ``` 得到的Q如下, ``` -1.0000000000 -0.9738519258 -0.9497979294 ... 0.9497979294 0.9738519258 1.0000000000 ``` 对于输入的权重: ``` input_blocked_tensor = [[-1.28645003578589, -1.817660483275528, 9.889441349505042, 0.010208034676132627], [ -15.009014631551885, 1.4136255086268115, -7.815595761491153, 10.766760590950263], [-0.731406153917959, 3.468224595908726, 2.445252541840315, -8.970824523299282], [-9.641638854625175, 7.696158363188889, -5.323939281255154, 5.97160401402024]] ``` 根据每一块的特征值的绝对值的最大值,保存为量化常数。 ``` c1 = max(|-1.28645003578589|, |-1.817660483275528|, |9.889441349505042|, |0.010208034676132627|) = 9.889441349505042 c2 = max(|-15.009014631551885|, |1.4136255086268115|, |-7.815595761491153|, |10.766760590950263|) = 15.009014631551885 c3 = max(|-0.731406153917959|, |3.468224595908726|, |2.445252541840315|, |-8.970824523299282|) = 8.970824523299282 c4 = max(|-9.641638854625175|, |7.696158363188889|, |-5.323939281255154|, |5.97160401402024|) = 9.641638854625175 ``` 对于第一个权重参数`-1.28645003578589`让他除以这一块的权重参数`9.889441349505042`,得到`-0.13008318572517502`,再在Q中找到最接近的值`-0.09105003625154495`,得到其在Q中的索引为6(假设)。然后权重参数矩阵就可以转换成索引值矩阵。 ``` [[6, 5, 15, 7], [0, 8, 2, 14], [6, 11, 10, 0], [0, 14, 2, 13]] ``` 在反量化(解量化时),再根据这个索引找到对应的值矩阵,再乘上每一块对应的权重参数得到反量化后的权重参数矩阵。 ``` [[-0.9004339933799617, -1.8273060011889755, 9.889441349505042, 0.0], [-15.009014631551885, 1.1944218804231184, -7.880829111886221, 10.850869732860506], [-0.816793898052648, 3.0313783372030603, 2.2078302737800004, -8.970824523299282], [-9.641638854625175, 6.970488722350373, -5.062564734402345, 5.424549965245643]] ``` 可以看到,参数是肯定不一样的,这就是必然的精度损失,但是只要不影响模型效果,就可以。 我们在这个过程中,保存的就是索引值矩阵,和对应每个块的权重参数`cn`。可以注意到索引值矩阵是4-bit但是这个c权重参数是FP32的。 #### 双重量化 按照上面所说,我们如果按照FP32保存权重参数,在QLoRA中每个块的大小是64,每个块中的每个值是4比特,那么为了存这个权重参数就需要额外占用$32/(64\\times 4) = 12.5%$ 的显存。若每 64 个权重共享一个 FP32 scale(权重参数),则 scale 的平均额外成本是: $$ \\frac{32}{64}=0.5\\text{ bit/parameter} $$ 作者在此处提出了对这个scale数再进行一次8bit的量化,QLoRA以每256个量化常数为一组再做一次量化,此时他产生的占用就有两部分组成,第一次的scale经过8bit得到的常数和对scale进行量化得到的new scale。此时需要**额外占用**$8/(64\\times 4)+32/(256\\times 64\\times 4)=3.174 %$,论文给出的平均节省约为 $0.373 \\text{bit/parameter}$。65B 模型上,这仍然是数 GB 的差距。 因为使用了双重量化,在进行反量化时我们也需要进行**两次反量化**才能把量化后的值还原。 #### Paged Optimizer(分页优化) 训练显存不只是权重,还包括: - 激活值; - 梯度; - LoRA 的 optimizer states; - 临时张量; - 不同长度样本造成的显存波动。 尤其是长上下文、长指令样本进来时,显存可能突然高一截,导致训练直接 **OOM**。 Paged Optimizer 使用 *NVIDIA Unified Memory*,把一部分不那么急需驻留在显存中的优化器状态换出到 CPU 内存,需要时再调回。它更像一个*显存缓冲机制*: - 正常时尽量在 GPU 内; - 显存峰值时向 CPU 内存借空间; - 代价是发生换页时训练可能变慢。 它不是 QLoRA 质量提升的主要来源,却让实际**训练稳定很多**。 ![Paged Optimizer 在显存峰值时临时借用 CPU 内存](https://img.somnus.top/file/1782207734593_07-qlora-paged-optimizer.png) ### 与LoRA对比 | 方面 | LoRA | QLoRA | | ------------ | ------------ | ------------ | | 基座权重存储 | 通常 FP16/BF16 | 通常 NF4 4-bit | | 基座模型是否更新 | 不更新 | 不更新 | | LoRA adapter | BF16/FP16 训练 | BF16/FP16 训练 | | 显存瓶颈 | 仍须放下完整基座模型 | 基座权重显存大幅降低 | | 适合场景 | 7B/13B 或显存充足 | 显存紧张、想微调更大模型 | | 训练速度 | 通常更直接 | 需量化/解量化,未必更快 | ### 为什么质量还能接近全参数微调? 1. QLoRA的LoRA本质没有变,还是在原始模型权重的基础上增加一个相对低秩的任务增量。 2. NF4 带来了量化误差,但这个误差没有大到让原模型能力崩掉。 ## 其他LoRA变种 ### X-LoRA X-LoRA:采用了MOE的思路,对每个token经过多个expert,额外训练多一个scaling network,通过输出的scaling对每个expert的输出进行加权 ![general_arch_v5.png](https://img.somnus.top/file/1782292928556_general_arch_v5.png) [原论文](https://arxiv.org/abs/2402.07148?ref=blog.somnus.wiki) X-LoRA相当于调用不同的多个LoRA来处理同一个token,普通的LoRA如下: $$ W=W\_0+\\Delta W $$ 而X-LoRA则是这样的: $$ W=W\_0+\\sum\_{i=1}^ns\_i(x,l,t)\\Delta W\_i $$ 其中,$s\_i(x,l,t)$是指模型针对$x$的输入,第$l$层,第$t$个token动态算出来的权重。 优点:能让模型有更强的组合能力,不同的LoRA会增强气不同领域的能力。同样的,相对于重新训练模型,X-LoRA还是更节省的方案。 缺点:毕竟有多个专家LoRA,推理速度自然就比单LoRA慢,显存和带宽压力也会变大。不同 LoRA 若基座、训练方式、目标冲突严重,混用可能互相干扰。 ### LoHa 在LoRA的基础上引入了hadamard product,可以理解为对LoRA的更有创造力的改造。 LoRA的公式如下: $$ W=W\_0+BA $$ 其中的BA是两个低秩矩阵相乘。而LoHa则是: $$W=W\_0+(B\_1A\_1)\\odot(B\_2A\_2)$$ 其中的$\\odot$就是hadamard product,即同一位置的元素相乘。 原本的LoRA计算的有效秩是$r$,经过哈马达乘积之后的有效秩就是$r^2$。 很明显,我们可以注意到,经过这样的乘积,微调需要的计算量肯定会变大,而且变大不少,但是相对于原本基础模型仍然不算大。 > LoHa 仍坚持“冻结大模型、只训练少量参数”的 PEFT 本质;但放弃了 LoRA 最漂亮的性质之一:更新本身低秩、且可用两次小矩阵乘法高效计算。它用**更多训练时计算**,换取更强的**更新表达能力**。 ## Q&A ### LoRA - Full fine-tuning 和 PEFT 的区别是什么? fullfine-tuning是全量微调,PEFT是高效微调,区别是修改的参数量的多少 - LoRA 为什么叫 low-rank adaptation? low-rank adaptation是低秩调节,是指在模型的关键层添加一个低秩矩阵来对模型进行微调 - LoRA 中原始权重 `W` 会不会训练? 不会 - `ΔW = BA` 里的 `A / B / r` 分别是什么? A和B就是两个低秩举证,r是他们的秩 - LoRA 通常插在 Transformer 的哪些层? 多头注意力层和前向传播层 - 为什么 LoRA 训练后可以合并回原模型权重? 因为他的BA的格式和W是一致的 - LoRA 的优点和局限分别是什么? 优点:训练高效,算力需求小。 缺点:参数r需要自己设置,而且每一层的r默认相同(adalora解决了这个问题) --- - Q2:不是“添加一个低秩矩阵”,更准确是添加两个小矩阵 `A` 和 `B`,用它们的乘积近似一个低秩增量: ``` W' = W + ΔW ΔW = B A ``` - Q4:`A/B` 不是“两个低秩矩阵”。通常 `A` 和 `B` 本身是小矩阵,关键是它们的乘积 `BA` 的秩最多为 `r`,所以 `ΔW` 是低秩的。`r` 是 LoRA rank,控制新增参数量和适配能力。 - Q5:对。常见是 attention 里的 `q_proj / k_proj / v_proj / o_proj`,也可以放到 FFN 的 `up_proj / down_proj / gate_proj` 等线性层。 - Q6:对,因为 `BA` 的形状和原权重 `W` 一样,所以推理前可以做: ``` W_merged = W + BA ``` - Q7:你提到 AdaLoRA 很好。再补两个常见局限: - 不是所有任务都能用很小的 rank 适配好,复杂任务可能需要更高 rank 或更多插入层。 - 对数据质量仍然敏感,LoRA 只降低训练成本,不自动解决过拟合、灾难性遗忘或指令数据质量问题。 ### QLoRA - QLoRA 和 LoRA 的核心区别是什么? 核心区别是QLoRA在LoRA的基础上还实现了对原始参数的量化,方便大模型在小显存机器上进行训练 - QLoRA 里 base model 会不会训练? 不会训练,本质和LoRA一样,都是通过添加低秩矩阵对模型进行微调。 - 为什么 4-bit 量化能显著降低显存? 因为4-bit存储就是会比原本的FP32和FP16节约很多存储空间 - NF4 是什么?为什么适合模型权重? NF4是一种4-bit量化的数据结构方式,因为权重参数据观察大部分都是符合正态分布的,也就是靠近0参数越来越多,而NF4同样也是靠近0的量化点越来越多,这样可以尽量保留有效信息。 - Double Quantization 解决什么问题? 解决的是量化本身的scale也就是量化常数的存储问题,也就是在原本的量化的基础上,对量化常数在进行一次Quant。 - Paged Optimizer 解决什么问题? 防止一些长上下文等参数进入显存导致oom,他利用的是临时借用cpu内存。 - QLoRA 的优点和局限分别是什么? 优点,让大模型的微调的硬件成本进一步降低。 缺点,多少会有点信息丢失。 --- - Q1 对。更精确说:**QLoRA = 4-bit quantized frozen base model + trainable LoRA adapters**。它不是把 LoRA 参数也全都 4-bit 训练,LoRA adapter 通常仍以较高精度训练。 - Q3 对。补个比例:FP16 是 16 bit,4-bit 权重理论上只有它的 `1/4`;FP32 则是 `1/8`。实际还会有量化常数、optimizer states、activation 等开销,所以不是总显存严格降到 1/4。 - Q5 很好。“再进行一次 Quant”可以写成:对第一次量化用到的 scale/quantization constants 再量化,减少额外存储开销。 - Q6 对,不过“长上下文等参数进入显存”可以更准一点:Paged Optimizer 主要解决 **optimizer states / 梯度检查点等训练中显存峰值突然升高** 的问题,通过 unified memory 在 GPU/CPU 间分页,降低 OOM 风险。 - Q7 补两个局限: - 量化会带来一定精度损失,极端任务或高精度场景可能不如 FP16/BF16 LoRA。 - 训练速度不一定更快,有时量化/反量化和分页会带来额外开销。 - 硬件和框架支持也会影响体验,比如 bitsandbytes、CUDA、显卡架构。 ### 简单总结基础知识 URL: https://blog.somnus.wiki/notes/jian-dan-zong-jie-ji-chu-zhi-shi/ Last updated: 2026-07-21T07:57:09.000Z > 经过快一个月的学习,我对于大模型已经有了相当初步的了解,从最基础的self-attention到整体的transformer,从预处理的tokenizer到后处理的normalization。温故而知新,今天小结一下所有学到的知识。 ``` flowchart LR %% 全局主线:横向流动 A[文本] --> TK[Tokenizer] TK --> B[token id] B --> EB[Embedding Layer] EB --> C[embedding vector] C --> SubBlock %% Tokenizer 的方法框:挂在 Tokenizer 节点下方(垂直排列) TK -.-> Methods1 subgraph Methods1 [分词方式] direction TB T1[Word-based] T2[Subword-based] T3[Char-based] end %% Embedding 的方法框:挂在 Embedding Layer 节点下方(垂直排列) EB -.-> Methods2 subgraph Methods2 [词向量方式] direction TB E1[One-Hot] E2[Word2Vec] E3[FastText] end %% Transformer 核心块:内部横向紧凑排列 subgraph SubBlock [Transformer Block] direction LR D[Self Attention] --> E[Add & Norm] E --> F[Feed Forward] F --> G[Add & Norm] end %% 输出主线 SubBlock --> H[Logits] --> I[Softmax] --> J[Output] ``` ## self-attention ### 基础介绍 自注意力机制是现代大模型的核心,他是将原始的输入$X$向量分别乘上对应的$W\_q、W\_k、W\_v$(这三个都是通过训练得到的权重值)得到对应的$Q、K、V$。 最终的输出ouput: $$ \\begin{aligned} output &= softmax(score)V \\\\ &=softmax(\\frac{Q K^T}{\\sqrt{d\_k}}) \\end{aligned} $$ 自注意力机制的本质就是**让序列中所有的位置,都会根据自身的内容,动态的从序列中其他位置获取信息。** 其中的$\\sqrt{d\_k}$就是为了防止softmax接受到的值过于尖锐。如果没有除这个$\\sqrt{d\_k}$,很容易出现,softmax接收到要么过大的数字,要么过小的数字,导致有些位置无限接近于1,有些无限接近于0,导致梯度消失,而且自注意力机制更新网络参数就是通过梯度来实现的。 ### 存在问题 很明显,现在的self-attention不会关注某个向量的位置信息,导致位置信息的丢失,所以需要改进的点之一就是添加位置编码,这个在后面会详细介绍。 ## transformer 下面是手绘tranformer架构图: ![IMG_7304.jpeg](https://img.somnus.top/file/1781517120457_IMG_7304.jpeg) 以上中的多头注意力+叠加归一化+前向传递+叠加归一化,不是执行一次,而是N次。 - Tokenizer 把文本切成 token,并映射成 token id。 - Token id 只是编号,必须通过 embedding matrix 变成向量。 - Self-attention 让每个 token 根据上下文动态聚合信息。 - Q/K/V 分别表示查询、匹配特征和被汇总内容。 - Multi-head attention 是多个 attention 视角并行学习。 - Causal attention 防止生成模型看到未来 token。 - Position encoding / RoPE 给模型提供顺序信息。 - FFN 对每个 token 的表示做非线性变换。 - Normalization 和 residual 让深层 Transformer 更稳定训练。 - Decoding 决定模型如何从概率分布里选择下一个 token ## Q&A 1. 从文本输入到 next token 输出,完整流程是什么? 先经过tokenizer得到token id,再经过embedding layer得到embedding vector,输入到transformer,再经过logits,经过softmax和decoding得到下一个token。 2. Q、K、V 分别是什么角色? Q是当前token想要查找什么信息,K是当前token能提供什么匹配特征,V是token能提供的内容 3. 为什么 GPT 要用 causal mask? 因为对于生成式的模型,如果提前知道后续的内容,就是导致模型训练效果变差 4. multi-head 和 causal 是什么关系? 多头注意力是指模型从多个角度去关注问题,causal是标志模型是否需要mask 5. token embedding 和 RAG embedding 有什么区别? token embedding是让token能在模型内部之间理解,RAG embedding是用于对知识库生成向量并进行检索。 6. 为什么 Transformer 需要位置编码? 因为自注意力机制本身是不关注每个token的位置的,但是语句本身不同顺序表明的语义是不同的。 7. PreNorm 为什么比 PostNorm 更容易训练深层模型? 8. top-k、top-p、temperature 分别控制生成的什么特性? 不知道 1-6 都可以过。小修 Q3: 不是“提前知道后续内容会导致训练效果变差”,而是: > 如果训练时能看到未来 token,模型会偷看答案,loss 会虚假变好;但推理生成时没有未来 token 可看,导致训练和推理条件不一致。 Q7 可以这样答: > PreNorm 把 Norm 放在 Attention/FFN 前面,残差连接更接近一条直接的 identity path:`x -> x + sublayer(norm(x))`。这样梯度可以更顺畅地沿着残差路径从深层传回浅层,所以深层 Transformer 更容易稳定训练。PostNorm 是 `norm(x + sublayer(x))`,残差路径每层都被 Norm 包住,对初始化和学习率更敏感。 Q8 记这一版就够用: - **temperature**:控制概率分布的尖锐程度。 - 低温:更保守,更容易选高概率 token。 - 高温:更随机,更有创造性,也更容易跑偏。 - **top-k**:只保留概率最高的 `k` 个 token,再从里面采样。 - `k` 小:选择范围窄,更稳定。 - `k` 大:选择范围宽,更多样。 - **top-p**:保留累计概率达到 `p` 的最小 token 集合,再从里面采样。 - `p` 小:只保留最核心候选。 - `p` 大:允许更多低概率候选。 - 它比 top-k 更动态,因为候选数量会随概率分布变化。 一句话: > temperature 调整概率分布形状,top-k 固定候选数量,top-p 固定候选累计概率。 ### 2026-06-15 URL: https://blog.somnus.wiki/diary/2026-06-15/ Last updated: 2026-07-21T07:57:07.000Z ![截屏2026-06-15 12.53.46.png](https://img.somnus.top/file/1781500989491_%E6%88%AA%E5%B1%8F2026-06-15_12.53.46.png) > 最近办好了招商行的visa信用卡,迫不及待的试了一下cloudflare的r2存储,然后关注到网上有一个cloudflare-imgbed的项目,然后我想试试看他这个(可恶的阿里云oss,访问流量也需要收费),然后我会逐渐尝试使用cloudflare的这个来作为我的图床。 ### 部署 整个部署的流程还是挺清晰的,但是我也遇到了不少的坑,所以是值得一提的。 可以直接看官方的这个[链接](https://cfbed.sanyue.de/guide/quick-start.html?ref=blog.somnus.wiki) 我没注意到的问题是,其实之前也遇到过,就是在创建pages的时候,又不小心创建成workers了 ![image.png](https://img.somnus.top/file/1781506794076_image.png) 可恶,pages在最小面的小小的一行上,真的怕被我看见。 然后按照他的步骤一步步来,就可以创建出[图床网站](https://img.somnus.top/?ref=blog.somnus.wiki)了,但是存在另一个问题,就是需要提前在pages里面添加`AUTH_CODE`的环境变量,这个就是在进入网站的时候需要添加的认证码。 如果需要管理员账户,也需要提前添加。 ![截屏2026-06-15 15.06.46.png](https://img.somnus.top/file/1781507229800_%E6%88%AA%E5%B1%8F2026-06-15_15.06.46.png) ### 测试 部署完成后,我比较关系cloudflare在国内连接的稳定性,所以我测试了一下 ![截屏2026-06-15 15.10.17.png](https://img.somnus.top/file/1781507448163_%E6%88%AA%E5%B1%8F2026-06-15_15.10.17.png) 其实效果还可以,部分地区会有断链,但是大部分地区都还可以。出于好奇,我也测试了一下阿里云oss。 ![截屏2026-06-15 15.09.10.png](https://img.somnus.top/file/1781507450963_%E6%88%AA%E5%B1%8F2026-06-15_15.09.10.png) hhhh,😅好吧,国内访问稳定性,还是得看阿里云,但是他访问流量付费,拉完了 ### 第一次打包aur URL: https://blog.somnus.wiki/p/di-yi-ci-da-bao-aur/ Last updated: 2026-07-21T07:57:13.000Z > 最近组里面给了一个图像标注的任务,但是我现在的主力电脑是mac,我在网上搜罗好用的mac上的标注软件,像很出名的开源软件labelimg和labeling都没有对应的mac版本,mac的appstore上的rectlabel,又是付费软件,就是会一直弹广告,虽然在标注的时候忍忍就过来了,但是我还是想做一个免费开源跨平台的标注软件,最好还能基于rust,不过这篇博客不介绍我的superlabel,而是介绍把软件发布到archlinux的aur中的教程。 ## 初步介绍 我们在aur上找软件的时候会发现,在软件名后面,通常会有-bin -git的后缀。 ![](https://somnusblog.oss-cn-shanghai.aliyuncs.com/images/%E6%88%AA%E5%B1%8F2026-06-10%2014.57.32.png) 在这里介绍一些常见的后缀代表的含义: - \-bin:表示安装的是作者已经编译好的二进制文件。 - \-git:表示从 Git 仓库直接获取最新开发代码并编译。 - \-svn:从 SVN 仓库获取最新开发代码。类似于git - \-nightly:表示每日构建版本。通常是项目自动构建的最新版本,可能比稳定版更新,也可能不稳定。 - \-beta:测试版,比稳定版新,但通常已经进入公开测试阶段。 - \-alpha:早期测试版,比 beta 更不稳定。 \-rc:Release Candidate,发布候选版。 我的superlabel,暂时在aur上是发布的-bin版本,也就是通过下载我的github仓库的.appimage。 之后我也会尝试添加-git版本,因为问题很明显appimage为了保证能运行,通常会包含更多的库,会导致其体积膨胀。 ![](https://somnusblog.oss-cn-shanghai.aliyuncs.com/images/%E6%88%AA%E5%B1%8F2026-06-10%2015.09.17.png) ## 发布流程 ### 注册账号 要发布aur软件,需要现在archlinux的官网注册一个aur账号,在注册完账号之后,在本体通过ssh-keygen生成密钥,将密钥添加到aur账号里面, ``` ssh-keygen -t ed25519 -f ~/.ssh/aur cat ~/.ssh/aur.pub ``` 配置config: ``` nano ~/.ssh/config 添加以下内容 Host aur.archlinux.org User aur IdentityFile ~/.ssh/aur IdentitiesOnly yes ``` 然后测试一下连接: ``` ssh aur@aur.archlinux.org ``` 通常会返回Welcome to AUR ,XXX ### 创建仓库 先根据上面的逻辑,来创建一个属于自己项目的包名,比如superlabel-bin。记得查一下aur仓库中有没有重名的。 ``` git clone ssh://aur@aur.archlinux.org/superlabel-bin.git cd superlabel-bin ``` 直接clone,便会在远程仓库中创建仓库。 然后就是编写PKGBUILD,这个是核心,记得我们每次在下载aur包的时候都会审阅的那个文件吗,就是PKGBUILD,对于这样的一个-bin的包,大致的文件内容如下: ``` # Maintainer: somnus0917 pkgname=superlabel-bin pkgver=0.1.2 pkgrel=2 pkgdesc="Lightweight desktop annotation tool for object detection datasets" arch=('x86_64') url="https://github.com/somnus0917/superlabel" license=('unknown') depends=('fuse2' 'webkit2gtk-4.1' 'gtk3' 'cairo' 'gdk-pixbuf2' 'glib2' 'libsoup3' 'pango') provides=('superlabel') conflicts=('superlabel') options=('!strip') source=( "superlabel-${pkgver}.AppImage::https://github.com/somnus0917/superlabel/releases/download/v${pkgver}/superlabel_${pkgver}_amd64.AppImage" "superlabel.sh" "superlabel.desktop" "superlabel.png::https://github.com/somnus0917/superlabel/raw/v${pkgver}/src-tauri/icons/icon.png" ) sha256sums=( '846fd8b0209f208afb46d2dbc0a95424ede66f3f2201cff48f510dec1763c68b' 'SKIP' 'SKIP' 'SKIP' ) package() { install -Dm755 "superlabel-${pkgver}.AppImage" "${pkgdir}/opt/superlabel/superlabel.AppImage" install -Dm755 "superlabel.sh" "${pkgdir}/usr/bin/superlabel" install -Dm644 "superlabel.desktop" "${pkgdir}/usr/share/applications/superlabel.desktop" install -Dm644 "superlabel.png" "${pkgdir}/usr/share/icons/hicolor/512x512/apps/superlabel.png" } ``` 其中: ``` pkgver=0.1.3 pkgrel=1 ``` pkgver是上游的版本号,而pkgrel是指下游即aur的修订次数,就是在上游依赖不变的前提下的**子版本**。 其中: ``` sha256sums=( '846fd8b0209f208afb46d2dbc0a95424ede66f3f2201cff48f510dec1763c68b' 'SKIP' 'SKIP' 'SKIP' ) ``` 是校验值,一开始可以全填skip,然后通过`updpkgsums`来下载source并自动填写校验值。 ### 本地测试 先清除本地的缓存 ``` rm -rf src pkg ``` 然后`makepkg -fsc`, ``` -f 覆盖已有构建结果 -s 自动安装缺少的依赖 -c 构建完成后清理临时文件 ``` ### 发布 在发布前必须生成.SRCINFO ``` makepkg --printsrcinfo > .SRCINFO ``` `.SRCINFO` 是 AUR 网页和工具读取的软件包元数据。修改 `PKGBUILD` 后如果忘记重新生成它,AUR 页面可能仍显示旧版本、旧依赖或旧下载地址。 然后,在上传前通过`git status`查看一下上传的内容,确定只包含了该上传的内容,特别是别上传刚刚updpkgsums下载下来的appimage文件,因为不需要!!! ``` git add PKGBUILD .SRCINFO superlabel.desktop superlabel.png git commit -m "Initial import: superlabel-bin 0.1.3-1" git push ``` 建议.gitignore添加一下内容 ``` /src/ /pkg/ *.pkg.tar.* *.AppImage ``` ### 版本升级 如果是大的版本升级,比如0.1.3->0.1.4,那么先进行git pull,修改PKGBUILD中的版本号。然后生成校验值,重新测试并生成.SRCINFO,提交并push。 ``` superlabel-bin/ ├── .git/ ├── .gitignore ├── .SRCINFO ├── PKGBUILD ├── superlabel.desktop └── superlabel.png ``` ## 工作流程 ``` flowchart TD A[开发代码] --> B[GitHub Actions 构建 AppImage] B --> C[创建 GitHub Release] C --> D[上传 superlabel-0.1.4.AppImage] D --> E[更新 AUR PKGBUILD 的 pkgver 和 sha256sums] E --> F[更新 .SRCINFO] F --> G[推送 AUR] ``` ### archlinux kernel Panic(滚动更新的福报) URL: https://blog.somnus.wiki/p/archlinux-kernel-panic-gun-dong-geng-xin-de-fu-bao/ Last updated: 2026-07-21T07:57:12.000Z > 早有耳闻archlinux系统的滚动更新,可能会有突然崩溃的情况(无违规操作)。自从回学校参加毕业典礼,大概有了三四周没有用我的惠普笔记本,回来之后我对这个笔记本进行了全面的更新,结果突然就蓝屏了,以下是我在克劳德老师的指导下的修复过程 ![蓝屏情况](https://somnusblog.oss-cn-shanghai.aliyuncs.com/images/20260605165921760.png) ## 原因分析 首先从这个蓝屏的报错信息中可以看到: ``` VFS: Cannot open root device "UUID=199aa68c-5125-4216-a220-6d38167d0f96" or unknown-block(0,0): error -6 Kernel panic - not syncing: VFS: Unable to mount root fs on unknown-block(0,0) ``` UUID=199aa68c-5125-4216-a220-6d38167d0f96是我的nvme0n1p9,也就是分配给双系统的cachyos使用的盘分区,他提示无法open,也就是当前没办法找到这个盘分区。内核启动的时候是需要initramfs来加载驱动包括nvme驱动btrfs驱动,如果initramfs损坏了,就会出现上面这种,知道指定的UUID(UUID信息是通过grub记录的)但是没办法打开。 ``` flowchart A[initramfs损坏]-->B[nvme驱动未加载]-->C[硬盘不可见]-->D[根分区的UUID不可见]-->E[Kernel Panic] ``` 当然后面发现问题其实不止这一个,我们就先从这个问题引入吧! ## 问题解决1 不管搜哪里的方案,如果你没办法进入系统终端,那么第一步永远都是找到(制作)启动盘。回忆一下之前安装的步骤,我用的是ventory制作的启动盘,选择对应的系统iso,进入live模式,然后通过`lsblk -f` 确认分区结构。 ![](https://somnusblog.oss-cn-shanghai.aliyuncs.com/images/20260605172138839.png) 找到和上面的报错信息对应的UUID及其对应的盘符名字。 从中可以得到根分区是nvme0n1p9,efi分区是nvme0n1p7,然后将这两个盘挂载到当前的live系统中。 ``` sudo mount -o subvol=@ /dev/nvme0n1p9 /mnt sudo mount -o subvol=@home /dev/nvme0n1p9 /mnt/home sudo mount /dev/nvme0n1p7 /mnt/boot/efi ``` 挂载根分区是因为问题肯定主要出现在跟分区内,挂载efi是为了修复完成后更新grub。 然后通过chroot进入系统。 ``` sudo arch-chroot /mnt ``` 在接着就是重建initramfs,通过下面的指令来实现 ``` mkinitcpio -P ``` 但是问题又出现了 ![](https://somnusblog.oss-cn-shanghai.aliyuncs.com/images/20260605173742387.png) 用于重建的preset文件是空的。由于本地存在这个文件,那么在通过`pacman -S cachyos-linux`的时候,不会更新(我因为更新cachyos-linux这个等了好久,但是更新完还是空的)。所以如果要更新,要把原本的preset文件删除,这样才会重新构建这个文件。 或者简单一点,直接手动写入内容: ``` cat > /etc/mkinitcpio.d/linux-cachyos.preset << 'EOF' ALL_config="/etc/mkinitcpio.conf" ALL_kver="/boot/vmlinuz-linux-cachyos" PRESETS=('default' 'fallback') default_image="/boot/initramfs-linux-cachyos.img" fallback_image="/boot/initramfs-linux-cachyos-fallback.img" fallback_options="-S autodetect" EOF ``` linux-cachyos-lts一样: ``` cat > /etc/mkinitcpio.d/linux-cachyos-lts.preset << 'EOF' ALL_config="/etc/mkinitcpio.conf" ALL_kver="/boot/vmlinuz-linux-cachyos-lts" PRESETS=('default' 'fallback') default_image="/boot/initramfs-linux-cachyos-lts.img" fallback_image="/boot/initramfs-linux-cachyos-lts-fallback.img" fallback_options="-S autodetect" EOF ``` 然后`mkinitcpio -P`,这样就重新生成了包含 NVMe 驱动和 btrfs 模块的 initramfs,内核启动时才能识别硬盘。 然后更新GRUB,保证启动项指向正确的文件。 ``` grub-mkconfig -o /boot/grub/grub.cfg ``` 然后就能成功进入系统了。 ## 小结1 所以问题的根源又进一步向上回溯了。 ``` flowchart preset文件为空-->mkinitcpio无事可做-->initramfs损坏 ``` 那么我们再向上刨根问底,为什么好端端的preset会为空呢? 根据claude老师的推断,应该是在更新内核的过程中,preset文件刚被创建,还没写入东西,更新程序被某个原因打断了,然后锁屏程序就崩溃了重启!然后就进不去了。 看上去这一整套链条还挺扯淡的,怎么会刚好刚创建就被打断呢,这个我也不太清楚了,可能会是什么条件触发了打断,这些对于我来说都是黑箱,没办法探究其更深层次的原因了。😩 这个据说是pacman的设计缺陷,就是没有原子性写入保证,就是会出现写了一半的中间态。 ``` ✅ 开始写入 linux-cachyos 包的文件 ✅ vmlinuz 内核文件写入完成 ❌ preset 文件只创建了空文件,内容没写进去 ❌ mkinitcpio hook 没有被触发 ❌ initramfs 没有重建 ``` --- ## 问题解决2 成功进入系统了,问题完全解决了吗??实际上并没有,还有一个问题,就是卡在了锁屏界面,我在kde plasma登录我的somnus账号,输入密码,没有报错,但是加载了一会又给我退了出来。再试依旧退出来。然后通过`ctrl alt f1`切换到终端登录,输入somnus及密码,再次退出⏏️,又要输入用户名。由此循环往复。 后来我主动尝试了root用户,竟然可以进入。然后在root中通过这个指令来查看日志: ``` journalctl -xe --no-pager | tail -50 ``` ![](https://somnusblog.oss-cn-shanghai.aliyuncs.com/images/20260605181825443.png) 从报错信息中可以看到: ``` plasmashell[119953]: error: "■ ■ ■ ■ ■ PipeWire" 0 received error while creating the stream - Media monitor will not work. ``` 也许是pipewire一直在报错导致进入桌面后,但是在重装pipewire后发现还是进不去。 然后在root用户中通过`su - somnus`来进入somnus用户。 ![](https://somnusblog.oss-cn-shanghai.aliyuncs.com/images/20260605181001962.png) 由此便可以发现问题了,图片中可以看到,虽然是乱码,但是大致可以猜出来`No such file`或者`permission denied`,也就是zsh出问题了,然后通过`ls -la /usr/bin/zsh`来验证一下。 ![](https://somnusblog.oss-cn-shanghai.aliyuncs.com/images/20260605181328064.png) 文件大小竟然是0!!输出的一行应该是: ``` -rwxr-xr-x 2 root root 0 6月 2 02:44 /usr/bin/zsh ``` 直接`pacman -S zsh`呢,发现并不会改变这个文件夹,文件夹的大小还是0,也就是和上面**情况一样**,安装的时候判断文件夹已经存在,就可以跳过这个文件的安装。只能执行这个`pacman -S --overwrite '*' zsh`,这会文件夹大小就正常了,`然后可以通过su - somnus`来进入somnus。此时,进入了somnus用户后,准备查看一下pipewire服务的状态, ``` systemctl --user status pipewire wireplumber ``` 发现问题是 ``` failed to connect to user scope bus via local transport: $DBUS_SESSION_BUS_ADDRESS and $XDG_RUNTIME_DIR not define ``` 因为这是通过su进入的用户,所以用户的会话环境并不完整,解决方法就是通过以下来进入somnus: ``` machinectl shell somnus@ ``` 然后再把该启动的服务都启动 ``` systemctl --user enable --now pipewire pipewire-pulse wireplumber ``` ![](https://somnusblog.oss-cn-shanghai.aliyuncs.com/images/20260605182422027.png) 最终成功进去KDE plasma!!! ## 小结2 直接导致无法进入的原因是zsh出问题了,也就是zsh在更新过程中变空了。以下是用户登录的流程。 ``` flowchart 输入用户名密码 --> PAM验证通过 --> 启动用户的默认shell ``` somnus 的默认 shell 是 zsh,验证密码通过后系统尝试执行 `/usr/bin/zsh`,但这个文件是空的,进程立即退出,系统就认为“登录会话结束了“,于是直接回到登录提示符,看起来就像密码输错了一样,实际上是 shell 启动失败了。 1. 为什么 root 能登录 root 的默认 shell 是 `/bin/bash`,bash 没有损坏,所以 root 登录完全正常。 2. 为什么 zsh 会变成空文件 更新时 pacman 的写入流程大致是: - 下载新版本 zsh - **先把旧文件清空/删除** - 写入新文件内容 更新在第 2 步和第 3 步之间被中断,新内容还没写进去,文件就已经被清空了,所以留下了一个 0 字节的空壳。 看起来又是因为没有原子级写入保证的问题。 ## 问题3 实际上问题并没有完全解决,lol,但是感觉确实还是学到很多了,所以最后这个问题暂时没有去解决的,我的clash verge rev出问题了,就是软件显示的画面一直都是空白的,我感觉的话,应该卸载一下重装就好了。 实际上问题复杂很多,我在尝试修这个问题的时候,试了以下指令: ``` GDK_BACKEND=x11 clash-verge ``` 导致我的hp的屏幕直接卡住了,就是开机之后会闪几下clash verge rev的界面(全黑的界面),然后整个页面都变黑了。彻底卡住了,然后一直拍照给claude,然后尝试修复。但是问题一直没办法解决。后来都给我整生气了,因为一直只能通过拍照给claude,然后手写复制他给的解决方案,非常麻烦,但是后来突然脑子活路了,想到这个问题并不是刚才的系统级的内核问题,电脑其他的功能可能是能正常运行的,因为这个问题只是plasma桌面的问题,所以我想到可以通过ssh连接,然后codex连接到这个电脑(记得打开防火墙),让codex帮我分析解决问题。然后就能美美解决了😅,codex真的很强。 后来他帮我分析问题,问题是在于: - 你的桌面不是显卡黑屏,也不是 KWin 没起来;真正崩的是 `/usr/bin/plasmashell`。 - `coredumpctl` 里有多次连续崩溃:13:50 和 14:03,都是 `plasmashell SIGSEGV`。 - 崩溃栈落在 `fontconfig` 和 Qt 文本渲染: `FcCharSetHasChar -> QFontEngineMulti -> QQuickText -> Klipper` - 当时 `fc-match sans-serif` / `monospace` 返回过异常结果,匹配到了 `JetBrainsMono-Bold.woff2`,还显示成错乱的 family/style。 - 重建字体缓存后,`fc-match` 恢复正常,再登录时 `plasmashell` 没有再崩,桌面恢复。 没想到的竟然是**字体缓存导致的问题**。 用户目录或系统里的字体缓存坏了,或者某个字体条目/Windows 字体/自定义字体让 fontconfig 生成了异常缓存。Plasma 启动托盘/剪贴板 Klipper 时要渲染文字,Qt 走到 fontconfig 字符集检查,然后 `plasmashell` 段错误,表现出来就是登录后闪几下再黑屏。 ### embedding URL: https://blog.somnus.wiki/notes/embedding/ Last updated: 2026-07-21T07:57:09.000Z > 我对embedding的印象还是在做dify和fastgpt的客服时,有数据库rag解析的需求时,你需要接入一个embedding模型api,比如BGE或者text-embedding-v4,那时候对于embedding有一定初步的认识,今天详细了解一下。 ## 基础介绍 embedding即嵌入(我印象中一直记得嵌入式工程师的英文🧑‍💻),是将经过tokenizer得到的编号转变成向量矩阵。 ## 静态编码 ### 独热编码(one-hot encoding) 最简单,即用向量中的一个数字1来代表该词,于是对于一个N个的词汇表,独热编码对应的向量维度为N。 缺点如下: - 语义鸿沟:很明显无法表明两个词向量之间的距离关系,因为任意的两个向量之间的距离都是固定的。 - 维度灾难:高维情况下将导致数据样本稀疏,距离的计算困难,对于下游模型的负担很重。 ### Word2Vec 我先按照我的理解把这个说一下,Word2Vec就是根据相邻近的词的关系,然后得到对应的向量矩阵,其中主要有两个算法SG和CBOW,前者是根据目标词target,预测临近的context词,后者是根据上下文的context,预测得到target。 最开始,词对应的向量矩阵是随机生成的,然后通过不断投喂数据,让他学习改进,由于猫和狗经常出现在同一个情况的上下文中,导致其两者的向量距离越来越近。表明其语义也就很近。 对于SG和CBOW,他们的核心区别其实并不是从target推context或者从context推target,而是**取平均数**,就是CBOW会对context取一个平均再进行运算。 ![](https://somnusblog.oss-cn-shanghai.aliyuncs.com/images/%E6%88%AA%E5%B1%8F2026-06-09%2017.21.20.png) 取平均这个操作会产生以下影响: - 训练速度不同:倘若有n个target,窗口大小为w,那么SG的样本数量接近于$2*w*n$个,但是CBOW只有近似于n个参数。 - 训练效果不同:SG适用于相对少量的数据,对于稀有词的效果更好,CBOW比SG快了很多,但是常用词的表征效果也比SG好一点。 Word2Vec的核心缺点: - 一个词永远只有一个向量; `苹果很好吃` `苹果发布了新手机` 这里的“苹果”一个是水果,一个是公司,但传统 Word2Vec 只会给它同一个向量。 - 高频但意义不大的stop word(比如the)会充斥训练样本, ### FastText Word2Vec将每个单词作为最小单位,为每个单词生成一个向量,对于中文没什么,但是对于英文,会忽略其单词内部的形态特征,比如Apple和Apples。 FastText是使用n-gram来构建文本特征的,n-gram是通过滑动窗口n对文本进行处理得到一系列长度为n的字节片段序列,例如当n等于3 $$$$ 其中<表示前缀,>表示后缀。 其好处是: 1. 对于低频次生成的词向量效果会更好,因为他们的n-gram可以和其他词共享。 2. 对于训练词库外的单词,仍然可以构建他的词向量,可以叠加得到他的字符级n-gram词向量。 ### Glove 根据语料库构建出一个共现矩阵,矩阵中每个元素$X\_{ij}$代表单词$i$和$j$在特定大小的上下文窗口中共同出现的次数,然后使用神经网络来拟合共现矩阵。 ![697](https://somnusblog.oss-cn-shanghai.aliyuncs.com/images/20260609180113919.png) ![](https://somnusblog.oss-cn-shanghai.aliyuncs.com/images/20260609180135862.png) $$x\_{max} = 100, \\alpha = 0.75$$向量v是要学习的参数,本质上与监督学习的训练方法一样,采用了AdaGrad的梯度下降算法,对矩阵X中的所有非零元素进行随机采样,学习曲率(learning rate)设为0.05,在vector size小于300的情况下迭代了50次,其他大小的vectors上迭代了100次,直至收敛。最后我们对于一个词w的target embedding和context embedding,就是它对应的$v\_i$和 $v\_j$求和。 Glove对比Word2vec - **word2vec是局部语料库训练的,其特征提取是基于滑窗的**;**glove的滑窗是为了构建共现 matrix**,是基于全局语料的,可见glove需要事先统计共现概率;因此,word2vec可以进行在线学习,glove则需要统计固定语料信息。并且**Glove训练时收敛更快**。 - word2vec是无监督学习,同样由于不需要人工标注;glove通常被认为是无监督学习,但实际上glove还是有label的,即共现次数log(Xij)。 - word2vec损失函数实质上是带权重的**交叉熵**,权重固定;glove的损失函数是**最小平方损失函数**,权重可以做映射变换。 - **Glove可拓展性好**,对于很小或很大的corpus都可以有效地训练;另外,对于**限制embedding维度更低的情况,Glove也表现很好。** ## 动态编码 静态编码每次单词只能得到一个词向量,但是在nlp的项目中,一个词很多时候都不止一个意思。而动态编码就可以从**一个单词中学到多个词向量**。以下方法预训练阶段是无监督的,下游任务一般是有监督的。 ### ELMo 是指先用语言模型训练得到一个embedding向量,此时也只有的单词也只有一个意思,没办法多义词区分,在实际使用word embedding的时候,单词有了特定的上下文,这个时候再根据上下文的语义去调整word embedding,此时该词向量就有了多语义功能。 ELMO采用了典型的两阶段过程,**第一个阶段是利用语言模型进行预训练;第二个阶段是在做下游任务时,从预训练网络中提取对应单词的网络各层的Word Embedding作为新特征补充到下游任务中。** - 预训练ELMo采用的是双层双向LSTM,训练任务是根据上下文预测目标单词。左端的前向双层LSTM代表正方向编码器,输入的是从左到右的除了预测单词以外的上文context before,右端则是逆向双层编码器,输入的是从右往左的context after。每个编码器都是双层,通过这样的结构,得到的是三个embedding,一个是表示单词的embedding,一个是表示句法特征的embedding,一个是表示语义特征的embedding。 - 第二个阶段,比如下游任务是QA,将用户的query和回复的三个embedding加权整合,作为补充的新特征给下游任务,然后再进行下游任务的训练,ELMO给下文提供的是每个单词的特征形式,所以这一类预训练方法叫做“Feature-based Pre training“。 但是缺点是:LSTM的**特征提取能力弱于**Transformer;ELMO采取双向拼接这种融合特征的能力可能比Bert一体化的融合特征方式弱。 ### GPT 同样也是两阶段训练,但是和ELMO不同的是,GPT在第一阶段训练时使用的是**transformer的decoder**,而且是单向训练,即只用上文不用下文。在微调阶段GPT要考虑**语言模型的损失**,此外ELMO可以用于其它任务的模型,但是GPT要求任务必须用自己的框架。 ### Bert 采用和GPT一样的训练方式,但是区别是采用双向语言模型,用MLM和NSP任务预训练。 - **MLM**:Mask language model,随机mask掉15%的单词,让语言模型去预测这个单词。为了弥补预训练和下游任务的差距(下游任务没有mask),这些mask的单词有10%的概率替换成随机的一个词,10%的概率替换成它本身,这样就能强迫模型在编码当前时刻的时候不能太依赖于当前的词,而要考虑它的上下文,甚至更加上下文进行”纠错”。 **NSP**: Next Sentence Prediction,输入是A和B两个句子,判断B是否是A后面的句子。 Bert模型的输入包括三部分:词embedding,位置编码embedding,句子segment。 ![](https://somnusblog.oss-cn-shanghai.aliyuncs.com/images/20260610175128376.png) 分别区分出词属于第几个句子,处于什么位置。 ### 总结 | 模型 | 特征提取器 | 预训练阶段的任务 | 将预训练用于下游的策略 | | ---- | ------------------- | -------- | ------------- | | ELMo | LSTM | biLM(双向) | feature-based | | GPT | transformer decoder | LM | fine-tuning | | Bert | transformer encoder | MLM, NSP | fine-tuning | ## RAG 中常用的文本编码与检索模型 ### Embedding 模型架构 **双编码器,Bi-Encoder:** 双编码器的思想是使用两个相同的encoder来处理query和doc,然后把它们嵌入到相同的向量空间中,在检索阶段,query和doc会被转化成固定的长度向量表示,然后通过计算两者的相似度来匹配。 - **优点**:这种方法的优势在于它具有较高的计算效率,因为查询和文档的编码是独立进行的,适合用于大规模数据集。通常,使用双编码器进行检索时,检索过程会非常快速。 **稀疏嵌入模型,Sparse Embedding Model**: 不同于密集嵌入模型,通常基于传统的词袋模型(如TF-IDF)或稀疏编码技术。这些模型生成的嵌入是稀疏的,意味着嵌入向量中大多数元素的值是零,仅有少量非零元素。 - **优点**:稀疏嵌入模型往往计算**效率较高**,并且可以避免高维密集向量所带来的**计算开销**,特别是在大型文档库的检索中。此外,稀疏表示有时能捕捉到更加显著的词汇特征,适用于特定的检索任务,如关键词匹配等。 --- 以下是两个经典的Bi-Encoder模型 ### BGE v1 BGE,全称BAAI General Embedding,是智源研究院提出的开源通用向量模型,在过去短短一年时间内,在huggingface上总下载量已超数亿次,是目前下载量最多的国产AI系列模型。 之前我在跑一个fastgpt的项目的时候,也调用过智源的这个模型,作为数据库的embedding模型,没想到今天就学到了这个模型。真的与时俱进。 原论文:[C-Pack: Packed Resources For General Chinese Embeddings](https://arxiv.org/abs/2309.07597?ref=blog.somnus.wiki) 首先,BGE模型的训练分为三部分,第一部分是在通用文本上预训练,第二部分是在通用文本上进行finetinue(使用未标注数据),第三部分是在特定文本上进行finetinue(使用标注数据)。 #### 预训练 在预训练阶段,作者使用的是智源自己的wudao数据集,采用的模型架构是RetroMAE,RetroMAE由两个主要的部分组成,一个12层的类bert的encoder和一个1层one-layer的decoder、这样的encoder-decoder不对称架构。 首先在encoder阶段,对sentence进行15%到30%的mask,然后在encoder阶段,尝试根据这个句向量恢复出原句。在decoder部分会加大噪声,mask提升到50%~70%。 encoder是来将原本的句子压缩压缩成\[CLS\]向量,decoder是用来检测这样的向量是否有足够的信息恢复成原文。 > 为什么decoder要设计的更高的mask:encoder不能设计的过高的mask,不然的话,语义会被过分压缩,decoder需要设计较高的mask,因为更高的mask能让他更依赖encoder得到的向量来推断出句子,避免仅仅靠自己来推断。–》逼 encoder 的 `[CLS]` 表示承载更多全局语义。 #### 通用文本fine-tune 经过预训练之后,模型只是学会了文本表示的基础,还不能用于相似度检索。 [BAAI](https://huggingface.co/BAAI/bge-large-zh?utm%5Fsource=chatgpt.com):预训练的目标只是为了重构文本,需要进一步fine-tune才能用于检索 **对比学习** 刚才说了第二部分是对于unlabeled数据进行训练,那么非标注数据怎么进行对比学习呢,实际上这些unlabeled数据是采用的**伪标签**,先收集大量的pair数据,如title-passage,然后用text2vec-chinese计算文本的相似度,再卡阈值(paper中采用0.43作为阈值),过滤掉置信度比较低的pair;最后形成了100 million pairs这样庞大的数据集。 目标损失函数: $$ L=-log\\frac{\\exp(sim(e\_p,e\_q)/\\tau)}{\\exp(sim(e\_p,e\_q)/\\tau)+\\exp(sim(e\_p+e\_{q’})/\\tau)} $$ 其中对于p有正样本q和负样本${q}’$,sim是用来表示样本和p的相似度,通常用点积或者余弦相似度来计算,$\\tau$是温度系数,来控制平滑度,$e\_p$和$e\_q$表示p和q的嵌入向量。 在对比学习中,负样本采样的质量对模型性能至关重要,特别是**难负样本**的采样非常重要,因为如果所有负样本都很容易区分,那么模型的损失会很小,梯度也会很小,导致模型收敛慢,且在复杂语义场景下,学习到的表示可能无法有效地区分正样本和难负样本。 #### 特定任务finetune 一对pair在某个任务上是相似的,而在另一个任务上可能就不相似了。争对这个问题,作者提出了两点解决方案:1\. 不同的任务加上不同的指令;2.进行难负例挖掘;最后再在进行改造后的数据集上进行finetune。 第三阶段是**有监督的微调**。 ``` 第二阶段: 从大量弱标注数据中学通用匹配能力。 第三阶段: 从高质量监督数据中学真实检索判断能力。 ``` ## Q&A - 为什么 token id 不能直接作为模型输入? 因为token id是一个数字没办法表明两个词之间的关系,比如谁和谁意思相近,没办法表征出来。 - embedding matrix 的形状通常是什么? 是一个长向量 - token embedding 和 position embedding 分别表达什么? token embedding像表征一个token的意思,他的词义,而pe是表达该token处于的位置 - RAG 里的 embedding 和 LLM 输入层 embedding 是一回事吗? 原理很像,都是将离散的文本变成向量表示,但是RAG中是为了生成用于检索的向量,而LLM中的是生成模型内部理解用的向量 - 为什么 embedding 可以用来做相似度检索? embedding可以通过空间距离来表征token的词义,于是可以用来了进行检索 - cosine similarity 在 embedding 检索里表示什么? 表示embedding检索的两个token词义有多么相近,越接近1就意思越相近,接近0就是不相关,接近-1就是反义。 这节整体可以过,几个点修准就行。 1. **为什么 token id 不能直接作为输入** 你的答案对。再补一句:token id 只是编号,编号大小没有语义。比如 id `100` 和 id `101` 不代表它们语义更近。 2. **embedding matrix 的形状** 这里要改。不是“一个长向量”,而是一个矩阵: ``` vocab_size x hidden_size ``` 比如词表 100000,hidden size 4096: ``` 100000 x 4096 ``` 每一行才是某个 token 的 embedding vector。 1. **token embedding vs position embedding** 对。token embedding 表示“这个 token 是什么”,position embedding / RoPE 表示“它在什么位置”。 2. **RAG embedding vs LLM 输入层 embedding** 你答得很好。再精确一点:RAG 里的 embedding 通常表示的是一句话、一段文本、一个 chunk 的语义;LLM 输入层 embedding 通常是 token 级别的内部表示。 3. **为什么可以做相似度检索** 对,但注意 RAG 检索通常不是检索单个 token 的词义,而是检索文本片段的语义相似度。 4. **cosine similarity** 前半句对。小修:接近 `-1` 不一定表示“反义”,更准确是“方向相反”。在真实 embedding 空间里,反义词有时也可能因为上下文相似而靠得近,比如“好”和“坏”都常出现在情感评价语境里。 ### fk gofullpage(ai时代对于软件的思考) URL: https://blog.somnus.wiki/p/fk-gofullpage-aishi-dai-dui-yu-ruan-jian-de-si-kao/ Last updated: 2026-07-21T07:57:12.000Z ![](https://somnusblog.oss-cn-shanghai.aliyuncs.com/images/%E6%88%AA%E5%B1%8F2026-06-04%2010.21.17.png) > 前情提要:我今天上午原本是在准备做一点公司的活,想办法能不能做一些比如商品价格爬虫之类的方案,然后GPT推荐我商品价格数据获取可以从京东万象数据中获取,然后我就到了京东云的官网,但是京东万象数据好像关闭服务了??但是我注意到京东云轻量级服务器专区,有点小心动💓,但是不同型号不同标准,也不知道哪个更适合我。我准备截图发给GPT,很明显这个页面超过一页,然后mac上我也不知道怎么缩小chrome页面,然后我想到了我之前安装的gofullpage插件,于是我便使用了,他截图功能是免费的,但是编辑和截取部分页面是付费的,fk🤬于是我当时就想到可以试试自己做一个插件。 ## 前期准备 说干就干,我先去问GPT了解了一下chrome浏览器插件的基础信息。 首先,现代的浏览器有着统一的浏览器配置规范,即manifest v3(v2已经几乎被全面弃用),在每个插件的根目录下都会有一个manifest.json。如果要发布到chrome插件商城,需要去开发者平台注册一个账号,还要充值5$,fffffk。暂时先不上架了。 ## 工作流程 这个项目整体是基于原生的js、css、html和chrome extension api来实现的,工作流程如下图: ``` flowchart TD A[popup] B[background] C[content script] G[滚动页面 / 获取区域] H[拼接截图] F{是否截取完成?} D[editor] E[end] A -- 发送截图命令 --> B B -- 调用截图 API --> H H --> F F -- 是 --> D F -- 否 --> C C --> G G -- 请求下一屏截图 --> B D -- canvas 编辑
复制 / 下载 --> E ``` ## 技术细节 ### 全页截图拼接 由于chrome并不提供获取全页的api,于是无法通过直接调用来实现截图,所以每次的全页截图都是通过模拟拼接出来的。 通过content script获取`scrollHeight、scrollWidth、clientHeight、clientWidth`,再保存当前位置,计算每次需要停留的位置。 每次滚动并不是完全贴合,而是留出重叠部分`TILE_OVERLAP_CSS_PX`,因为如果没有重叠,每个拼接处都会有个裂缝。然后就是不断执行滚动、delay `CAPTURE_DELAY_MS = 300;`(等待渲染完成)。截图,保存子图。 最后通过canvas拼接起来。 ### 过滤元素 除了拼接,还有一个很重要的点就是要过滤一些不该被重复截取的元素。比如标题栏、广告、悬浮球等等。 主要分成两个策略: ``` flowchart A[element] B[alwaysHide] C[pageChrome] A--广告、翻译浮球、右侧小悬浮按钮、扩展 iframe-->B A--fixed / sticky 的页面导航、目录、侧栏-->C ``` ### 自定义截图 这个很简单,通过content script注入全屏遮罩,鼠标拖动生成选框,松开鼠标便得到viewpoint。然后使用background获取制定的可见区域。 ### 元素截图 在截屏时为了防止误触页面,会添加一个遮罩来阻挡鼠标点击,那么怎么获取鼠标到底点击的是什么元素呢。 ``` overlay.style.pointerEvents = "none"; const element = document.elementFromPoint(event.clientX, event.clientY); overlay.style.pointerEvents = "auto"; ``` 在获取元素的时候把遮罩取消,获取完再**立刻**恢复。如果注释上面的none这一行。 ![](https://somnusblog.oss-cn-shanghai.aliyuncs.com/images/%E6%88%AA%E5%B1%8F2026-06-04%2016.56.50.png) 程序就会失效了。 ## 难点 **这类插件最难的地方** 不是“截一张图”,而是下面这些边界: - Retina / 缩放比例导致坐标偏移 - captureVisibleTab 频率限制 - 页面懒加载导致截图内容变化 - sticky/fixed 元素重复 - 无限滚动页面没有终点 - 超长截图 Canvas 内存爆 - 广告/浮球/扩展 iframe 难以统一识别 - chrome://、扩展页面、商店页面无法截图 - 剪贴板/分享 API 在不同浏览器支持不一致 所以这个项目的技术核心其实是: ``` 浏览器扩展权限模型 + content/background 消息编排 + 滚动分块截图 + Canvas 坐标映射和图像处理 + 大量网页兼容性兜底 ``` ## 思考回顾 从前情提要开始,脑子中灵感的迸发,让我感觉在这个时代真正重要的,也许真的不再是你能写怎样的代码,也许更应该关注于,你要写什么,你想要得到什么。就像我这个项目一样,从有想法,到落地,不过就是一天的时间,如果没有这种工具思维,在面对需要付费的小工具的时候,我可能就会直接掏钱了(虽然很便宜,好像就1$一个月),但是这样一段小的工具项目,也能让我知道不少东西,比如插件的发布流程,js、html、css传统三件套的配合、canvas的用处、chrome的api式的架构等等。 所以我觉得在这个ai爆发的时代,一定要多去想,多去做,多去了解,不管怎样的架构,都去了解一下,这样在面对真正的问题的时候,曾经项目的灵感也会救你(我)一命。 共勉! ### 什么是归一化normalization URL: https://blog.somnus.wiki/notes/shi-yao-shi-gui-yi-hua-normalization/ Last updated: 2026-07-21T07:57:09.000Z ## normalization 即归一化,即把数值缩放到**正态分布**的范围内,通常是为了消除不同特征的量纲差异,使得数据更适合进行后续的分析和处理。 ### batch normalization 是指在神经网络的每一层,对mini-batch都进行归一化处理。 优点:加速网络训练;防止梯度问题;优化正则化效果。 缺点:BN对batch-size的大小很敏感,要求数据长度一致,且受离群数据的影响很严重。 ### layer normalization 在神经网络的每一层,对每个样本的特征通道进行归一化处理。 计算过程如下: 1. 首先计算每个样本的特征值的均值和方差 $$ \\begin{aligned} \\mu&=\\frac{1}{d}\\sum\_{i=1}^dx\_i\\\\ \\sigma&=\\sqrt{\\frac{1}{d}\\sum\_{i=1}^d(x\_i-\\mu)^2} \\end{aligned} $$ 2. 使用均值和方差对输入X进行归一化 $$ X\_{norm}=\\frac{X-\\mu}{\\sigma+\\epsilon} $$ 3. 最后将归一化之后的$X\_{norm}$乘以权重$w$,加上偏置 $$ Y=X\_{norm}w+b $$ 优点:训练样本小,样本间的影响较大的情况下更稳定。主要应用于RNN。 ``` class LayerNorm(nn.Module): # features: (bsz, max_len, hidden_dim) def __init__(self, features, eps=1e-6): super(LayerNorm, self).__init__() self.a_2 = nn.Parameter(torch.ones(features)) self.b_2 = nn.Parameter(torch.zeros(features)) self.eps = eps def forward(self, x): # 就是在统计每个样本所有维度的值,求均值和方差,所以就是在hidden dim上操作 # 相当于变成[bsz*max_len, hidden_dim], 然后再转回来, 保持是三维 mean = x.mean(-1, keepdim=True) # mean: [bsz, max_len, 1] std = x.std(-1, keepdim=True) # std: [bsz, max_len, 1] # 注意这里也在最后一个维度发生了广播 return self.a_2 * (x - mean) / (std + self.eps) + self.b_2 ``` ### instant normalization 对于每个样本的每个特征通道进行归一化。 **优点: 更适用于图像生成等任务中,每个样本的特征通道独立于其他样本的情况** ### group normalization IN和LN的融合,在神经网络的每一层中,**将特征分成若干组,对每个组的特征进行归一化处理。** **优点: 适用于样本较小、样本间相互影响较大,但又不需要对整个mini-batch进行归一化的情况。** ### RMSNorm LayerNorm每次都需要计算均值和方差,而RMSNorm没有去中心化的操作,只有缩放的操作,**只需要计算方差计算量更小**。这也是Llama模型使用的Normalization方法。 对于给定的输入$X$(其中 $X$是一个$n\\times d$的矩阵,$n$是批次大小,$d$是特征维度),RMSNorm 的计算可以表示为: 1. 计算均方根,同时加上一个小的常数$\\sigma$以避免除以零: $$RMS(x)= \\sqrt {\\frac{1}{d} \\sum\_{i=1} ^d x\_i^2 +\\sigma}$$ 1. 最后,使用得到的 RMS 值对输入 $X$ 进行归一化,并乘以可学习的权重参数 $w$: $$Y = X \* \\frac{1}{RMS(x)} \* w$$ ``` class RMsNorm(torch.nn.Module): def __init__(self, dim:int, eps:float =1e-6): super()._init_() self.eps=eps self.weight =nn.Parameter(torch.ones(dim)) def _norm(self,x): return x*torch.rsqrt(x.pow(2).mean(-1,keepdim=True)+ self.eps) def forward(self,x): output =self._norm(x.float()).type_as(x) return output* self.weight ``` ### 为什么使用layer normalization layer normalization和其他所有normalize的方法一样,目的都是为了防止因为训练后续的参数爆炸(数字变得特别大),导致训练的梯度消失(权重更新就是根据梯度来得到的)。反之也是,如果参数过小,就很容易导致梯度爆炸。总的来说,归一化的目的就是为了**保证训练的稳定性**。 其次,归一化还能**加速模型收敛**。模型的每一层都在拟合一个数据,如果不进行归一化,每次数据分布都会发生变化,导致模型的学习会变得困难。 还有一个值得一提的好处,归一化有助于让模型训练**不再依赖**权重的初始化。 ### 为什么不用batch normalization batch normalization是很早提出来的,常用于CV领域,但是在NLP不常用,主要是有以下原因。 1. batch要大,小的batch往往会导致模型训练不稳定。 2. batch在训练时可能会出现一些跨设备通信的问题,在现代算力集群中,同一个batch可能会被分到不同的机器上,这样不同机器之间的通信也要被计算到训练消耗之中。通过mini-batch或者syncbatch normalization可以“解决”这个问题,但是不管怎样,在模型足够大的时候,额外的通信开销是个问题 3. 训练和预测会不一致:在训练时会有大批量的数据组成batch,但是在预测的时候只有一个样本数据,batch norm就废了。 4. 并不适合主流NLP框架 5. 并不适合长度不固定的NLP序列。 ### why post norm > pre norm > 经典问题回归,在通过[苏建林的解释](https://kexue.fm/archives/9009?ref=blog.somnus.wiki)之后,才对这个问题有了更深层次的理解。🤔 在他的博客中说到,pre norm的层数是**有水分**的!也就是说相同的层数的post norm和pre norm,前者的实际层数就会比后者多,因此性能也肯定会更强。 解释原因如下: 首先pre norm和post norm的公式分别如下: $$ \\begin{aligned} pre\_norm: x\_{t+1}=x\_t+F\_t(Norm(x\_t))\\\\ post\_norm: x\_{t+1}=Norm(x\_t+F\_t(x\_t)) \\end{aligned} $$ 知乎上 [@唐翔昊](https://www.zhihu.com/question/519668254/answer/2371885202?ref=blog.somnus.wiki) 给出的答案是:**Pre Norm的深度有“水分”**!也就是说,一个L层的Pre Norm模型,其实际等效层数不如L层的Post Norm模型,而层数少了导致效果变差了。 我们对原始公式进行递归: $$ \\begin{aligned} x\_{t+1}&=x\_t+F\_t(Norm(x\_t))\\\\ &=x\_{t-1}+F\_{t-1}(Norm(x\_{t-1}))+F\_t(Norm(x\_t))\\\\ &=\\cdots \\\\ &=x\_0+F\_0(Norm(x\_0))+\\cdots+F\_{t-1}(Norm(x\_{t-1}))+F\_t(Norm(x\_t)) \\end{aligned} $$ 假设每一层的餐叉增量都是同一量级,比如都是$O(1)$,原公式就是: $$ x\_{t+1}\\approx x\_0+O(t+1) $$ 那么$x\_{t+1}$和$x\_t$之间的差距就相当于$t+1$和$t$之间的差别,当t相当大的时候,两者的相对差别就减小了很多。 这个意思是说,当t比较大时,$x\_t$,$x\_{t+1}$相差较小,所以$F\_{t+1}(Norm(x\_t+1))$与$F\_{t+1}(Norm(x\_t))$很接近,因此原本一个t层的模型与t+1层和,近似等效于一个更宽的t层模型,所以在Pre Norm中多层叠加的结果更多是增加宽度而不是深度,层数越多,这个层就越“虚”。 也就是说pre norm的结构会无形之间增加模型的宽度而降低模型的深度,而明显模型深度会更重要,等效导致模型性能降低变差;而post norm则相反,他的每次norm会削弱恒等分支的权重,更突出残差分支,效果会更好。可以参见[这篇博客](https://kexue.fm/archives/8620?ref=blog.somnus.wiki)。 ## Q&A 1. Normalization 在深层网络里解决什么问题? 是为了消除不同分布的数值会导致的梯度爆炸或消失的问题,保证模型训练的稳定性 2. BatchNorm 和 LayerNorm 的统计维度有什么不同? 一个是横向的,一个是纵向的 3. 为什么 Transformer 更常用 LayerNorm 而不是 BatchNorm? batchsize通常会要求比较大,过小的batchsize训练效果不好,而且可能会出现跨设备通信的问题,batch norm不适合nlp框架;batchnorm训练和预测的size会不一致。 4. PreNorm 和 PostNorm 的结构差异是什么? 一个是先进行归一再累加残差,另一个是先累加再归一。 5. 为什么现代大模型更偏向 PreNorm? 大模型层数并不大,所以pre norm的性能差异不会很明显,而pre norm相对post norm更容易训练。 6. RMSNorm 相比 LayerNorm 少做了什么?为什么它更高效? LayerNorm每次都需要计算均值和方差,而RMSNorm没有去中心化的操作,只有缩放的操作,**只需要计算方差计算量更小**。 Q2 “横向/纵向”这个说法太模糊。建议这样记: - **BatchNorm**:对同一个特征维度,在 batch 维度上统计均值/方差。 - **LayerNorm**:对单个 token / 单个样本,在 hidden dimension 上统计均值/方差。 在 Transformer 里,某个 token 的 hidden vector 形状类似: `[h1, h2, h3, ..., hd]` Q4 你的描述接近,但要把 sublayer 放进去: `PostNorm: x -> Sublayer -> Add residual -> Norm `PreNorm: x -> Norm -> Sublayer -> Add residual\` 不是简单“归一再累加”,而是 **Norm 放在 Attention/FFN 前面还是后面**。 Q5 这里有个明显要改的点:现代大模型层数其实很深,不是“层数并不大”。PreNorm 更常用的核心原因是: > residual path 更干净,梯度可以更稳定地从深层传回浅层,所以深层 Transformer 更容易训练。 PostNorm 在浅层或原始 Transformer 里可用,但模型加深后更容易训练不稳定。 标准答案如下: > 现代大模型常用 PreNorm,因为它让残差路径上的梯度传播更稳定,深层 Transformer 更容易训练。PostNorm 在一些设置下可能有更好的最终效果,但对初始化、学习率、warmup 和结构设计更敏感,直接训练较深模型更困难。因此主流大模型通常优先选择 PreNorm 或 RMSNorm + PreNorm 的组合。 Q6 你说对了“没有去中心化”,但最后一句要改:RMSNorm 不是“只需要计算方差”,而是计算 **均方根 RMS**: `RMS(x) = sqrt(mean(x^2))` 它不计算均值,也不做 x - mean(x),所以比 LayerNorm 少一步中心化,计算更简单。 ### 2026-05-27 URL: https://blog.somnus.wiki/diary/2026-05-27/ Last updated: 2026-07-21T07:57:07.000Z > 昨天捣鼓了一下一些网站矩阵,还是挺有意思的,比如搭建了基于somnus.top的子网站,比如docs.somnus.top、paper.somnus.top,lab.somnus.top,status.somnus.top 现在最清晰的就是这样一套搭建的逻辑,除了status这个需要定时执行的网站以外,其他都是静态网页,通过GitHub actions部署然后走GitHub pages发布,再自定义域名转接到对应的pages上。分别介绍一下这些网站的原理。 ### status 先介绍最特殊的这个状态监测网站,是用来检测网站是否能正常工作,其实那些静态发布的网站几乎不可能出现问题,因为他们都是不会有什么变化的,除非做出一些commit更改,而每次commit都是可控的(通常不会改他的逻辑,只是添加内容),最容易出问题的还是那个部署在火山云服务器的vaultwarden密码管理器,但是幽默的地方来了,这个status和vaultwarden部署在同一个服务器,所以除非**vaultwarden的docker容器自己出问题**,不然的话这个status根本没办法起到作用,我估计这两个服务应该分开放,不然的话,是不是没有**容灾性**😅(我猜的,不知道这个词是否合适)。 技术细节:status使用的是现成的开源方案[uptime-kuma](https://github.com/louislam/uptime-kuma?ref=blog.somnus.wiki),不光简洁又好看,效果还挺好的。 ![](https://somnusblog.oss-cn-shanghai.aliyuncs.com/images/%E6%88%AA%E5%B1%8F2026-05-27%2012.17.42.png) ![](https://somnusblog.oss-cn-shanghai.aliyuncs.com/images/%E6%88%AA%E5%B1%8F2026-05-27%2012.18.20.png) 如图,就是uptime-kuma的效果,对于我的这些网站服务,他是直接通过https来检测网站是否正常运行的。 ![](https://somnusblog.oss-cn-shanghai.aliyuncs.com/images/%E6%88%AA%E5%B1%8F2026-05-27%2012.20.02.png) 如果返回的状态码属于设定范围,则认定为正常。对于那些静态网页,我设定的状态码范围是`200-299`,而对于vaultwarden,我设定的范围是`200-399`。 ### 2026-05-21 URL: https://blog.somnus.wiki/diary/2026-05-21/ Last updated: 2026-07-21T07:57:07.000Z ## 智慧城市与交通学院本科毕业设计(论文)数字化档案检查清单 - 专业:智能制造工程 - 学号:2022116717 - 姓名:陈磊 - 建议根文件夹名:`智能制造工程_2022116717_陈磊` ### 必须归档材料(1-10) - 1\. 本科毕业设计(论文)Word:`智能制造工程_2022116717_陈磊_基于深度学习的双目立体匹配优化与轻量化方法研究.docx` - 1\. 本科毕业设计(论文)PDF:`智能制造工程_2022116717_陈磊_基于深度学习的双目立体匹配优化与轻量化方法研究.pdf` - 2\. 外文翻译 Word:`智能制造工程_2022116717_陈磊_外文翻译.docx` - 2\. 外文翻译 PDF:`智能制造工程_2022116717_陈磊_外文翻译.pdf` - 3\. 毕设评分表(竖版)PDF:`智能制造工程_2022116717_陈磊_毕设评分表.pdf` - 4\. 答辩记录表 PDF:`智能制造工程_2022116717_陈磊_答辩记录表.pdf` - 5\. 评审意见表 PDF:`智能制造工程_2022116717_陈磊_评审意见表.pdf` - 6\. 查重报告 PDF:`智能制造工程_2022116717_陈磊_查重报告.pdf` - 7\. AIGC 检测报告 PDF:`智能制造工程_2022116717_陈磊_AIGC 检测报告.pdf` - 8\. 开题报告 Word:`智能制造工程_2022116717_陈磊_开题报告.doc` - 8\. 开题报告 PDF:`智能制造工程_2022116717_陈磊_开题报告.pdf` - 9\. 中期检查报告 Word:`智能制造工程_2022116717_陈磊_中期检查报告.doc` - 9\. 中期检查报告 PDF:`智能制造工程_2022116717_陈磊_中期检查报告.pdf` - 10\. 中期检查评分表 PDF:`智能制造工程_2022116717_陈磊_中期检查评分表.pdf` ### 限制性归档材料(如有则提交) - 11\. 题目修改表 PDF:`智能制造工程_2022116717_陈磊_题目修改表.pdf` - 12\. 缓答辩申请表 PDF:`智能制造工程_2022116717_陈磊_缓答辩申请表.pdf` - 13\. 图纸类文件:`智能制造工程_2022116717_陈磊_图纸名称` - 14\. 其它附件:`智能制造工程_2022116717_陈磊_材料名称` ## 备注 - 清单要求 1-10 为必须归档材料,当前必需归档材料已齐全。 - 第 11-14 项为限制性归档材料,仅在实际存在相关材料时提交。 - 查重报告和 AIGC 检测报告当前仅发现 PDF 文件;若学校系统另行导出了非 PDF 原文件,也需要一并补充。 ## 智慧城市与交通学院本科毕业设计(论文)纸质档案袋检查清单 - 学院:智慧城市与交通学院 - 专业:智能制造工程 - 学号:2022116717 - 姓名:陈磊 - 论文题目:基于深度学习的双目立体匹配优化与轻量化方法研究 ### 必须归档材料(1-11) - 1\. 本科毕业设计(论文):1 本,A4 双面打印,胶装,带学校毕业论文封面,格式按《西南交通大学本科毕业设计(论文)撰写规范》。 - 2\. 外文翻译:1 本,A4 双面打印,带学校外文翻译封面,按扉页、中文、英文顺序排版。 - 3\. 毕设评分表(竖版):1 页,原件,需有评审成绩(含单项评分)及评审组组长签字,时间填写答辩时间。 - 4\. 答辩记录表:1 页,原件,需完整填写,所有答辩评委需签字。 - 5\. 评审意见表:1 页,由论文指导老师和论文评阅人填写评审意见并签字,时间需在答辩之前。 - 6\. 查重报告:1 页,打印查重报告首页即可。 - 7\. AIGC 检测报告:1 页,打印 AIGC 检测报告首页即可。 - 8\. 毕业设计(论文)指导纪要:1 本,完整填写指导内容,指导教师需签字。 - 9\. 毕业设计(论文)开题报告:1 本,完整填写指导内容,指导教师需签字。 - 10\. 毕业设计(论文)中期检查报告:1 本,完整填写指导内容,需由指导教师和系室负责人或中期检查答辩组组长签字。 - 11\. 毕业设计(论文)中期检查评分表:1 页,原件,需有评审成绩及评审组组长签字,时间填写中期检查答辩时间。 ### 限制性归档材料(如有则提交) - 12\. 毕业设计(论文)题目修改表:1 页,原件,需完整填写,由指导老师、系室负责人、学院教学负责人签字。 - 13\. 毕业设计(论文)缓答辩申请表:1 页,如有缓答辩则需提供,原件需完整填写,由指导老师、系室负责人、学院教学负责人签字。 - 14\. 图纸类文件:若干,如有则需提供,折叠为 A4 幅面。 - 15\. 其它附件:若干,由指导教师指定,A4 幅面。 ### 纸质版备注 - 纸质清单要求 1-11 为必须归档材料,当前必需归档材料已齐全。 - 第 12-15 项为限制性归档材料,如有则必须提供;已存在题目修改表,因此第 12 项需要放入袋内。 - 档案袋封面需完整填写:学院写“智慧城市与交通学院”,专业写“智能制造工程”,指导老师、题目、学号、姓名需与教务网和纸质材料一致。 - 本人材料属于毕业论文类型,涉及第 1、9、10、11、12 项时,页眉页脚等相关格式处应使用“毕业论文”,不要保留“毕业设计(论文)”。 ## other 今日女盆友来成都,有没有懂的。 ### 2026-05-19 URL: https://blog.somnus.wiki/diary/2026-05-19/ Last updated: 2026-07-21T07:57:07.000Z - 改进 文献格式一致 - 不足和展望不需要引用文献 - 页底距统一 - 有一个公式要居中和添加编号 - 第五章标题详细一点 - 柱状图表封起来 - 第一张每个方法都要有一个文献 ### 位置编码完全解读(espeacially RoPE) URL: https://blog.somnus.wiki/notes/wei-zhi-bian-ma-wan-quan-jie-du-espeacially-rope/ Last updated: 2026-07-21T07:57:09.000Z ## 位置编码 卷积具有局部性,天然地会注意元素之间的相对位置,但是基于自注意力的transformer模型则对位置不敏感,因此必须要把元素的位置信息在embedding阶段传给元素。 比如: ``` 我 爱 你 你 爱 我 ``` 在自注意力的理论中,这两个句子的表意是一样的,但是很明显这两者是天差地别的。 ### 绝对位置编码 在早期的BERT、GPT2等模型中,会直接将绝对位置编码加入到embedding中,但是绝对位置编码又分为两种 1. 可学习的绝对位置编码:直接对不同位置随机初始化一个position embedding,将其加入到文本的embedding中,但是这样的方法引入了大量可学习的参数,需要大量数据才能训练。 2. 固定绝对位置编码:attention is all you need中使用的三角位置编码 公式如下 $$ \\begin{aligned} PE\_{(pos,2i)}=\\sin(\\frac{pos}{10000^{\\frac{2i}{d\_{model}}}}) \\\\ PE\_{(pos,2i+1)}=\\cos(\\frac{pos}{10000^{\\frac{2i}{d\_{model}}}}) \\end{aligned} $$ 其中$d\_{model}$是位置编码的长度,$i \\in \[0,1,…,(d\_{model}-1)/2\]$。 采用这种设计,对于pos+k的位置的位置编码可以通过pos位置的位置编码线性表示得到。 ### 相对位置编码 是指将两个token的相对位置信息添加到对应的attention值中。 要对于相对距离远的进行惩罚。 ![](https://somnusblog.oss-cn-shanghai.aliyuncs.com/images/20260601165711906.png) #### [Attention with linear biases enables input length extrapolation(ALiBi)](https://arxiv.org/abs/2108.12409?ref=blog.somnus.wiki) 上图中就是ALiBi,传统的绝对位置编码会在训练时添加一个固定的向量,模型可能会**过拟合**这些特定长度的向量,于是ALiBi在分数计算的时候就添加了一个线性偏置,减少了模型对特定长度序列的依赖,提升了其对于未知长度序列的**泛化性**。 - ALiBi的位置偏差会随token距离线性增加。 - ALiBi鼓励模型关注更近的位置,但又不会完全排除远处的依赖。 #### XLNET #### T5 #### DeBERTa ### 旋转位置编码RoPE RoPE实现了绝对位置编码和相对位置编码的统一,(*通过绝对编码的形式实现了相对编码的效果*)。RoPE最大意义就是在**保留token位置信息**的同时**没有**对token的语义**产生污染**。 RoPE将输入的序列位置信息通过旋转操作,嵌入到self-attention的计算中。 [这个视频](https://www.bilibili.com/video/BV1FjrCBdESo?spm%5Fid%5Ffrom=333.788.videopod.sections&vd%5Fsource=5a4bb483003fcc440c31e2dedcbcb744&ref=blog.somnus.wiki)讲的很清楚,分别讲了为什么要用位置编码和RoPE如何推导。 #### 旋转矩阵 $$ \\left{ \\begin{matrix} \\cos \\alpha & -\\sin\\alpha\\\\ \\sin\\alpha & \\cos \\alpha \\end{matrix}\\right} $$ 上面这个公式就是一个旋转矩阵,他乘上$\\left{ \\begin{matrix} 0 \\\\ 1\\end{matrix} \\right}$,就表明让这个向量在坐标轴沿逆时针旋转$\\alpha$的角度。 ![](https://somnusblog.oss-cn-shanghai.aliyuncs.com/images/%E6%88%AA%E5%B1%8F2026-06-01%2018.30.08.png) 如图,图中就是逆时针旋转30度。我们可以观察到表明语义的长度没有变化的同时,添加了表明位置信息的旋转角度$\\alpha$。 为了表明,于是便可以通过对原矩阵乘上一个旋转矩阵来添加其位置信息。如下: $$ \\begin{aligned} q’&=R(m\\theta)q \\\\ k’&=R(n\\theta)k \\end{aligned} $$ 对于p和q就分别乘上两个旋转矩阵,来表明其位置信息。 $$ \\begin{aligned} Score=(q’)^T \\cdot k’&=(R(m\\theta)\\cdot q)^T \\cdot R(n\\theta)\\cdot k \\\\ &=q^T \\cdot R(m\\theta)^T\\cdot R(n\\theta) \\cdot k\\\\ &=q^T\\cdot R((n-m)\\theta)\\cdot k \\end{aligned} $$ #### 拓展到普遍情况 上述是基于两维矩阵举的例子,实际情况中的token都是上千维,对于这种情况,RoPE采用了分治法。对于4096维度的矩阵,就给他拆成2048对。对于每一对都乘上旋转矩阵、添加位置信息。 ![](https://somnusblog.oss-cn-shanghai.aliyuncs.com/images/%E6%88%AA%E5%B1%8F2026-06-02%2016.05.48.png) #### 问题 旋转的话,10度和370度表示的意思就可能重叠,这样的话根本没办法表明独特的位置信息。为了解决这个问题,就可以利用刚刚上面的基本角度$\\theta$,对于不同维度的矩阵,取用不一样大小的$\\theta$,维度越低,$\\theta$越大;反之,则越小。 ![](https://somnusblog.oss-cn-shanghai.aliyuncs.com/images/%E6%88%AA%E5%B1%8F2026-06-02%2016.09.20.png) 也就是说,维度越低的矩阵,对于位置变化会更**敏感**,维度越高则越**钝感**。 RoPE的基本角度计算公式也是参考三角函数编码的。 $$ \\theta\_i=10000^{-\\frac{2(i-1)}{d\_{model}}} $$ 因此就不需要担心重叠的问题,因为一个token的位置信息是由**所有的旋转矩阵**决定的,虽然低维度可能出现重叠,但是高维度还是能表明其**独特性**的。 > 准确说是很难重复,实际上也是可能重复的,但是重复周期会很长。 #### 相对正余弦编码 正余弦编码虽然也包含相对位置信息,如下 $$ \\begin{aligned} Q &=W\_q(X\_m+P\_m) \\\\ K&=W\_k(X\_n+P\_n) \\\\ Score&=(X\_m+P\_m) (X\_n+P\_n)^T \\end{aligned} $$ 注意力分数计算已经简化了。展开后如下 $$ \\begin{aligned} Score &=(X\_m+P\_m)(X\_n^T+P\_n^T) \\\\ &=X\_mX\_n+P\_mX\_n^T+X\_mP\_n^T+P\_mP\_n^T \\end{aligned} $$ 其中$X\_mX\_n$是有效语义信息,而$P\_mP\_n^T$是有效位置信息,其余都是无效信息(噪声)。RoPE则如下: $$ \\begin{aligned} Score&=QK^T\\\\ &=(R\_mX\_m)(R\_nX\_n)^T\\\\ &=X\_mR\_{n-m}X\_n^T \\end{aligned} $$ 很明显全是有效信息。 信息的有效性直接关系到**模型的质量和训练成本**(噪声大的模型需要更多的训练数据)。 这也就是RoPE能被广泛使用的原因。 ### 2026-05-15 URL: https://blog.somnus.wiki/diary/2026-05-15/ Last updated: 2026-07-21T07:57:07.000Z > 最近被毕设论文的格式问题折磨的很难受,因为我用的是typst模版,只能导出pdf,不支持原生导出docx,但是毕设论文提交强制要求docx或者doc。 ## 1解决方案 ### 1.1pdf2docx 我女朋友有wps会员(顺便一提这个wps会员还走了我们学校的报销途径),然后wps会员支持pdf转word,于是我便使用了他的这个功能。其实效果还是可以的,大部分该转换的图表都可以正常转换,但是也存在很多问题,比如: - 过多的分页符,似乎是因为wps的某种机制,他会把pdf某一页下面留空(哪怕是一点点的空)都判定为分页符,实际上可能是因为图片导致的留空 - 字体无法主动识别,wps默认转word,会把字体提取成默认字体,macbook上就是苹方,Windows上就是微软雅黑(我女朋友在她电脑上测试的)。 - 图片排版错乱,这个情况只是会部分出现。 ### 1.2排版优化 针对上面的问题,如果要手动改,其实也可以,但我是真的懒😭,原本听同学建议,准备去淘宝上找一个代排版的,后来在加上那个人微信之后,他要我发论文和格式规范要求,我又有点担心会因为学校信息泄露,被勒索威胁什么。 于是我准备codex来辅助我对这个论文进行排版。 我先是在codex的要求下,安装了libreoffice,因为codex在渲染word文档的时候需要使用soffice。然后我就在一步步指令让他对我的word文档进行格式修改。 效果还是意料之外的挺好的。 ![](https://somnusblog.oss-cn-shanghai.aliyuncs.com/images/%E6%88%AA%E5%B1%8F2026-05-15%2015.52.07.png) ## 2 SWJTU 130周年 今天是西南交的120周年,还是祝西南交生日快乐,等会去看看有没有什么好玩的活动。 ### 2026-05-14 URL: https://blog.somnus.wiki/diary/2026-05-14/ Last updated: 2026-07-21T07:57:06.000Z > 女朋友要来成都玩,最近使用qwen和chatgpt image2做了旅游规划,别说,效果还挺好的。 ``` flowchart LR A[双流机场] B[酒店1] C[景点1] D[西南交大] E[景点2] F[成都西站] G[酒店2] H[九寨沟] I[酒店3] J[天府机场] A-->B-->C-->D-->B-->E-->F-->G-->H-->I-->J ``` ![](https://somnusblog.oss-cn-shanghai.aliyuncs.com/images/travelplan.png) 以下是我的答辩记录: 学生陈磊围绕《基于深度学习的双目立体匹配优化与轻量化方法研究》进行汇报,介绍了双目立体匹配的研究背景、RAFT-Stereo 基线模型、精度优化探索及 FP16 半精度轻量化实验结果。 评委主要提出以下意见:一是论文格式仍存在少量不规范之处;二是答辩 PPT 中轻量化部分文字较多,缺少原理示意图;三是论文中轻量化改进主要为 FP32 转 FP16,方案相对简单;四是精度优化工作应在论文中独立展开。学生回答认为,正因轻量化方案较简单,才补充了视差细化、边缘损失和局部解冻等精度优化工作,并表示后续将按评委意见完善格式、补充图示,调整论文结构。 ### 2026-05-13 URL: https://blog.somnus.wiki/diary/2026-05-13/ Last updated: 2026-07-21T07:57:06.000Z > 今日毕设答辩 ## 陈磊毕设答辩讲稿 2000字逐页口语版 ### 第1页 各位老师好,我是陈磊。我的毕设题目是《基于深度学习的双目立体匹配优化与轻量化方法研究》。这项工作主要围绕两个问题:一个是提升双目匹配在边缘、遮挡等困难区域的精度,另一个是降低模型部署时的计算和显存开销。 ### 第2页 我的汇报分为五部分:研究背景、相关理论与基线模型、精度优化方法、模型轻量化实现,以及最后的实验结果和总结。 ### 第3页 首先介绍研究背景与意义,主要说明为什么要研究双目立体匹配,以及它目前还存在哪些实际问题。 ### 第4页 深度信息是机器理解三维环境的基础。比如机器人避障、自动驾驶感知、三维重建,都需要知道物体离相机有多远。双目立体匹配通过左右图像的视差来恢复深度,相比激光雷达和结构光,成本更低、结构更简单。但它也容易受到遮挡、深度突变、弱纹理和光照变化影响,所以提高精度和效率都很重要。 ### 第5页 现有深度学习方法已经提升了整体精度,但还有两个瓶颈。第一,边缘、细小结构和遮挡区域仍然容易出错,视差图会有模糊现象。第二,高性能模型通常推理慢、显存占用高,不方便部署。因此本文以 RAFT-Stereo 为基础,分别做局部精度优化探索和轻量化部署分析。 ### 第6页 接下来介绍相关理论与基线模型,也就是本文采用的 RAFT-Stereo。 ### 第7页 RAFT-Stereo 的核心流程有三步。第一步是多尺度特征编码,提取左右图像的上下文和纹理信息。第二步是构建全对场相关体,计算左右图像特征之间的相似度。第三步是通过 GRU 循环更新视差,多轮迭代修正结果。它的优势是精度高,但计算量也比较大。 ### 第8页 在 Middlebury 2014 数据集上,基线模型的 EPE 是 2.3758,异常点率 D1 是 12.0526。EPE 表示预测视差和真实视差的平均误差,D1 表示异常点比例,都是越小越好。从图中 backpack 场景可以看到,整体视差结构能恢复出来,但局部边缘和细节仍有优化空间。 ### 第9页 下面进入精度优化方法探索。这部分主要想验证:在 RAFT-Stereo 这种强基线模型上,简单的结构和损失改进是否还能带来明显提升。 ### 第10页 第一种方法是输出端视差细化模块。我的想法是,基线模型已经给出比较完整的视差图,但边缘和细节不够精细,所以在输出端加入残差细化头,让它结合图像纹理和轮廓信息,对原始视差进行局部修正。 ### 第11页 第二种方法是边缘感知联合损失。普通损失对所有像素比较平均,但实际更容易出错的是边缘和深度突变位置。因此我根据图像梯度提取边缘响应,构建权重图,让边缘区域的误差在训练时受到更大惩罚,从而引导模型关注边界模糊问题。 ### 第12页 第三种方法是局部解冻微调。由于数据规模有限,如果全参数微调,容易破坏预训练特征。所以我冻结特征提取骨干,只对部分模块训练。方案 A 是开放相关体和 GRU 更新模块,方案 B 是只训练新增细化头,用来对比不同局部训练策略的效果。 ### 第13页 接下来是模型轻量化实现。这部分关注模型在实际部署时的速度、显存和存储体积。 ### 第14页 高精度立体匹配模型的部署压力主要有三点:相关体和迭代机制会占用较多显存;前向计算复杂,推理时间较长;模型权重文件较大,不利于边缘设备部署。因此轻量化的目标不是单纯压缩模型,而是在精度、速度和资源占用之间取得平衡。 ### 第15页 我比较了几种轻量化路线。结构重设计和知识蒸馏需要重新训练,开发成本较高;剪枝可能损害边缘细节。本文最终选择 FP16 半精度推理,因为它不改变原网络结构,实现简单,可以利用 GPU 张量核心加速,并且通常精度损失较小。 ### 第16页 FP16 的基本原理是把推理中的权重、偏置、输入张量和中间特征,从 FP32 转换为 FP16。由于这里只用于推理阶段,不涉及反向传播和梯度更新,所以数值稳定性风险相对较低。同时现代 GPU 对半精度计算有硬件支持,能够提升吞吐并降低显存。 ### 第17页 半精度推理流程是:先加载原 FP32 模型,再转换模型参数和张量,之后执行 FP16 前向推理,最后输出视差图。评价时我从四方面看效果:EPE 和 D1 看精度,平均耗时和 FPS 看速度,最大 CUDA 显存看资源占用,权重体积看部署成本。 ### 第18页 下面进入实验结果与总结,先看精度优化结果,再看轻量化结果。 ### 第19页 精度优化的结果显示,基线 EPE 为 2.3758,D1 为 12.0526。只加细化头后,EPE 为 2.376,D1 为 12.0609,基本没有提升。加入边缘损失后,EPE 为 2.3757,变化也很小。局部解冻后 EPE 变为 2.4377,反而下降。说明简单后端修正很难突破强基线。 ### 第20页 这一页是 Jadeplant 场景的可视化对比。可以重点看叶片边缘、前景背景交界处和细小结构。不同方案整体差异并不明显,和前面的定量结果一致,说明输出端细化对局部可能有轻微影响,但没有稳定带来明显提升。 ### 第21页 轻量化结果更明显。FP16 后,EPE 从 2.376 变为 2.353,精度基本稳定;推理时间从 209.8 毫秒降到 173.6 毫秒,下降约 17.2%;显存从 845MB 降到 662MB,下降约 21.6%;模型体积从 43MB 降到 22MB,接近压缩一半。 ### 第22页 本文结论主要有三点。第一,构建了从细化头、边缘损失到局部解冻的评估链路。第二,实验说明强基线的精度瓶颈不能只靠输出端修补,更需要改进特征融合和遮挡建模。第三,FP16 在保持精度稳定的同时,显著降低了推理时间、显存和模型体积。 ### 第23页 不足和展望方面,后续可以从三点继续做:算法上加入多尺度特征融合和注意力机制;数据集上扩展到 Scene Flow、KITTI 等更多场景;部署上继续探索 INT8 量化和剪枝,并在 Jetson 这类边缘设备上测试实时性和功耗。 ### 第24页 以上就是我的全部汇报。本文围绕 RAFT-Stereo,从精度优化和轻量化部署两个角度进行了实验分析。恳请各位老师批评指正,谢谢各位老师。 ## 改进建议 1. 不要因为图片留出大范围的空白。 2. 把第二章中的匹配优化部分独立出来。 3. 修改几个老师提到的格式问题。 4. 缺少卷号的参考文献补充一下卷号 5. 第四章添加一个引言 ### 2026-05-11 URL: https://blog.somnus.wiki/diary/2026-05-11/ Last updated: 2026-07-21T07:57:06.000Z 最近捯饬这个破博客网站就捯饬了好久,现在的效果还算挺满意的,之前就感觉走的优点太偏了,过分追求美术风格,导致已经偏离了我的初心。 原本是想做一些技术分享,现在已经变成了家豪风格,现在准备返璞归真。 找了一个新的主题,感觉很简洁。 ## 小项目 今天关注了一个小项目,就是这个[greenwall刷绿墙](https://github.com/zmrlft/GreenWall?ref=blog.somnus.wiki)的小项目,原本我是准备自己也刷一下我的仓库绿墙的,因为之前真的太空白了,好丑,好丑。 ![](https://somnusblog.oss-cn-shanghai.aliyuncs.com/images/%E6%88%AA%E5%B1%8F2026-05-11%2018.20.41.png) 然后就关注到这个项目了。 ### 项目原理 这个项目其实我第一眼看到我就大概知道他的原理了,首先我们要知道git和github是两个东西。 git是一个用来进行代码管理的工具,而github最基本的功能就是一个放置代码的平台。github的绿墙是根据git的history来生成的,而你的项目什么时候commit,commit了什么东西,都是你自己可控的。同样你也可以指定commit的时间,不一定要是当下。至于我看到issue中有人问会不会被github官方封禁。 这个是肯定不会的,要知道github和git并不是捆绑的,我可以将两者连起来,但是同样也可以不连起来,只使用本地的git。所以我添加什么项目,项目之前有怎样的commit,都和github没关系。 ### 项目问题 这个项目是一个中国人做的,挺好的,现在已经有1.3k★,现在已经支持很多功能了,比如画笔功能,图片转绿墙形状,粘贴字母状绿墙: ![](https://somnusblog.oss-cn-shanghai.aliyuncs.com/images/%E6%88%AA%E5%B1%8F2026-05-11%2018.31.21.png) 然后这个仓库里面之前有人提交过一个[pr](https://github.com/zmrlft/GreenWall/pull/70?ref=blog.somnus.wiki),但是后来作者就没管了。 ![](https://somnusblog.oss-cn-shanghai.aliyuncs.com/images/%E6%88%AA%E5%B1%8F2026-05-11%2023.34.29.png) 我准备自己vibe-coding一个fr,也实现同样的功能,希望能被merge,不过一定要写清楚fr的提交内容。 ## 端到端加密 最近捣鼓了一个博客网站的端到端加密,觉得还挺好玩的。 ``` flowchart A[本地客户端明文markdown] B[本地密文] ``` ### word2token的奥秘 URL: https://blog.somnus.wiki/notes/word2tokende-ao-mi/ Last updated: 2026-07-21T07:57:09.000Z ## 为什么要有tokenizer tokenizer的作用是把文本序列转换成数字序列,即token编号,作为transformer的输入。 ``` graph LR A[word] --tokenizer-->B[token id] B --embedding-->C[vector] C-->D[...] ``` ## word-based tokenizer 将文本分成一个个词,优点是表达意思准确,但是问题是很容易把同一个意思的词分成很多类,比如cat和cats就会被分成两类,按照这样的编码方式,就会导致词表巨大,因此就需要巨大的embedding\_matrix,导致空间复杂度和时间复杂度大幅上升。 如果限制词表的大小,比如把词表上限设为10000,就会出现很多词都无法覆盖的情况,模型性能会很差。 ## character-based tokenizer 按照每个字母来分词,比如`"cat"`就被分为`'c','a','t'`,优点是很容易表示英文,对于英文总共可能只需要256个序号来表示,对于任意文本都不会出现unknown的现象。 缺点也是显著的, 1. 每个字母没办法代表很多的含义,信息量太低了,导致模型性能也会很差; 2. 对于中文还是需要很大的词表; 3. 相对于word-based,token序列会过于长。 ## subword-based tokenizer ``` flowchart A["`word-based - 词表过大(5万~10万) - 运算量大`"] B["character-based - token序列过长 - 信息量过低 - 语义捕获能力弱"] C[subword-based] A ----> C B ----> C ``` 可以看到上述两种方法都有自己的缺点,而subword就是一种折中的方法。 subword划分更符合英文词群,能充分表达词意。 ``` flowchart A[dogs] B[dog] C[s] A---->B A---->C D[tokenizer] E[token] F[izer] D---->E D---->F ``` ### BPE 即byte-pair encoding,主要分成两部分,词频统计和词表合并。 首先先把所有的词按character切分,得到单词表,再所有词中的两两组合的单词组合统计频率,根据频率从高到低排,取出频率最高的那个两个单词的组合,把它加到词表中。然后再按照新的词表的词两两组合(此时会有三个单词组成词组),再统计频率,再将最高频率的词组加到词表中,以此循环,知道达到超参数设定的最大循环次数。 具体流程可以看这个[视频](https://www.bilibili.com/video/BV1Fc411C7sz/?ref=blog.somnus.wiki)。 BPE的超参数是设定最大循环次数。 - BBPE即byte level BPE,即把每个字节视作基本的token,两个字节就可以合并表示Unicode,比如中文,阿拉伯文,表情符号。 ### WordPiece Tokenization 大体是和BPE类似的,但是除了第一个字母,会添加##作为前缀 ``` flowchart LR A[word]-->B["w,##o,##r,##d"] ``` 使用联合概率来计算每个pair的得分。 $$pair得分=\\frac{pair出现的次数}{token1出现的次数 \\times token2出现的次数}$$ 证明这个wordpiece方法他会优先考虑单一token在词表中**并不频繁**的,将其合并起来。 比如unable的`un`、`##able`和hugging的`hu`、`##gging`,后者会更容易合并起来。 ### Unigram tokenization 首先是获得一个很大的词表,然后在尝试删除这个词表中的某个词,然后计算他的unigram loss。删除p%使得loss增加最少的token。 - 前提:假设每个词的出现都是独立的。 unigram loss计算公式 $$ Unigramloss=\\sum frec(-log(P(word))) $$ 其中的frec是这个词出现的频率,P(word)是这个词出现的概率。 - unigram是通过不断的迭代知道词表缩小到预设值。 - 为单词的每种划分算法非常耗时,采用更高效的维特比算法。 ### sentencePiece tokenization - BPE、WordPiece、Unigram的缺点 中文,韩文,日文等语言并不是通过空格来分词的。 对于特定语言可以使用pre-tokenization,但是不通用。 - SentencePiece tokenization 将输入视为输入字节流,包括空格。 然后再使用byte-level BPE或unigram算法来构建适当的词汇表。 - unigram算法经常在sentence piece中使用,是AIBERT、T5、mBART、BigBird和XLNet使用的算法。 ## Q&A 1. token是一个词,token\_id是这个词对应的数字,vocabulary是词表,就是所有词的汇总。 2. 因为如果直接按词处理的话,会出现太多的意思相近的词,比如dog和dogs就会被认为是两个词,词表会过大 3. 按字符划分的话,词表的大小会小很多,但是每个字符的表意并不够丰富,导致模型效果会不好。 4. BPE的基本思想是词频统计和词表划分。 5. 不知道 你这节已经有骨架了,差的是把几个概念说得更精确。逐题修一下。 1. **token / token id / vocabulary** token 不一定是“一个词”。它可以是一个字、一个词、一个词片段、一个标点、一个空格片段,甚至代码里的符号片段。更准确地说: - `token`:tokenizer 切出来的最小处理单元 - `token id`:这个 token 在词表里的整数编号 - `vocabulary`:token 到 id 的映射表 2. **为什么不直接按词处理** 你说的 `dog/dogs` 是对的,但还要补两个更关键的问题: - 词表会巨大,尤其中文、英文变形、专有名词、拼写错误、代码、URL 混在一起时。 - 会有 OOV,也就是词表外词。遇到没见过的新词、人名、产品名,就没法稳定表示。 3. **为什么不完全按字符处理** 对。再补一个核心代价:序列会变得很长。比如 `transformerization` 按字符可能十几个 token,按 subword 可能 3-4 个 token。序列越长,attention 成本越高,上下文窗口也越容易被占满。 4. **tokenizer 怎么影响上下文长度和计费** 大模型按 token 处理上下文,很多 API 也是按 token 计费。所以 tokenizer 会影响:例子:如果一个 tokenizer 对中文切得很碎,同样一篇中文文章 token 数更多,就更贵,也更容易超过上下文长度。 - 同一段文字会被切成多少 token - 能塞进上下文窗口的内容有多少 - 输入/输出成本是多少 - 长文本、中文、代码、多语言混合时的实际效率 **BPE 的基本思想** 你的答案方向对,但还不够具体。BPE 可以这样理解: > 从字符级开始,反复统计最常一起出现的相邻片段,把它们合并成新的 token,直到达到目标词表大小。 举例: ``` l o w l o w e r n e w e s t ``` 如果 `l` 和 `o` 经常相邻,就合并成 `lo`;如果 `lo` 和 `w` 经常相邻,就合并成 `low`。 **subword tokenizer 的核心折中** 这题可以这样答: > 高频词或常见片段尽量作为完整 token,低频词、生造词、专有名词拆成更小的子词片段。 好处是:词表不会无限大,同时也不容易 OOV,序列长度也比字符级短。 ### fk transformer URL: https://blog.somnus.wiki/notes/fk-transformer/ Last updated: 2026-07-21T07:57:09.000Z # transformer transformer是在这篇[attention is all you need](https://arxiv.org/abs/1706.03762?ref=blog.somnus.wiki)中提出来的。 ![Transformer 模型架构图](https://somnusblog.oss-cn-shanghai.aliyuncs.com/images/20260508113056125.png) 上图就是该论文最核心的模型架构图,本文也是围绕着这个架构图来展开的。 ### embedding 由input embedding和位置编码相加得到,后面的文章详细介绍。 - input embedding可以通过**word2Vec,bert,OpenAI Embedding API**等方式获取,目的是将文本映射到连续的向量空间(把文本变成模型能处理的向量)。 - 位置编码是为了捕捉输入中token的顺序信息,常用的有rope,绝对位置编码等 ![Embedding 示意图](https://somnusblog.oss-cn-shanghai.aliyuncs.com/images/%E6%88%AA%E5%B1%8F2026-05-08%2011.24.59.png) ### encoder(编码器) 图片中的红色框内,就是encoder。 ![Encoder 示意图](https://somnusblog.oss-cn-shanghai.aliyuncs.com/images/%E6%88%AA%E5%B1%8F2026-05-08%2011.32.35.png) 可以看到encoder是有N个\`multi-head attention\`+\`add&norm\`+\`feed forward\`+\`add&norm\` 组成的。\`multi-head attention\`是多头注意力机制,在这个文章中有详细介绍。\[self-attention\](https://affine.somnus.top/workspace/09e2838e-0333-4d5c-80d5-bcae278e8e50/qQsakBqRrL2JR41Rk7p2h),\`add&norm\`是两个步骤,add是指把源数据和经过运算输出的数据叠加起来,norm则是对其进行归一化操作。公式如下: $$ LayerNormalization(X+Multi-Head Attention(X)) $$ 此处公式中用的是LayerNormalization,而对于norm的位置,也分成了pre-norm和post-norm,分别是指norm的操作在残差连接之前和之后。上图中就是post-norm。 `feed forward`即两个简单的全连接层。公式如下: $$ max(0,XW\_1+b\_1)X+b\_2 $$ **Attention 负责\*\*** token 之间的信息交互\*\*,让每个 token 根据上下文更新自己的表示;\*\*FFN 负责对每个 token 各自做非线性变换\*\*,提升表达能力。FFN 虽然看起来只是两层全连接,但它通常占 Transformer 参数量很大,不是“简单附属模块”。 ### decoder(解码器) ![Decoder 示意图](https://somnusblog.oss-cn-shanghai.aliyuncs.com/images/%E6%88%AA%E5%B1%8F2026-05-08%2013.45.57.png) 可以看出来decoder相对于encoder是要复杂一点的,除了一个N次处理循环block以外,最后还有线形变换和softmax处理。 在循环处理的block中也有不同,我们可以看到第一个多头注意力增加了一个masked,表明这个注意力机制有一个掩码矩阵。为什么要有掩码矩阵可以看self- attention这篇文章[self-attention](https://affine.somnus.top/workspace/09e2838e-0333-4d5c-80d5-bcae278e8e50/qQsakBqRrL2JR41Rk7p2h?ref=blog.somnus.wiki)。上图中的第二个多头注意力机制是cross-attention,是指decoder层用来看encoder输出的注意力层。对于decoder-only是没有这个cross-attention的。 最后还有一个softmax,根据前面的知识,在self-attention的模块中是存在softmax,而这两个softmax很明显负责的功能是不一样的。其中self-atttention中的softmax是用来告诉当前的位置应该关注哪些位置,而这里的softmax是表明下一个token应该表示词表里的哪个词。 ## only模块 ### encoder-only 常见的模型是:BERT,RoBERTa。 它通常是双向注意力机制,即不需要要掩码,可以看见未来token。 侧重于理解,所以它很适合理解完整输入,比如: - 文本分类 - 情感分析 - 句子匹配 ### decoder-only 常见模型是:GPT,Mistral,LLaMa。 它使用的是单向注意力机制,需要给未来token添加掩码,不可以可以看见未来token。 侧重于生成,所以它天然适合生成: - 聊天 - 写作 - 代码生成 > 值得注意的一点是,现代的decoder-only大模型也可以作为理解任务,因为在模型训练的时候会投喂很多理解型的数据,因此很多的理解任务都被转成了生成任务。 Decoder-only 模型通过预测下一个 token 学到了上下文表示;在回答时完整输入都位于它左侧,所以它能读完整问题,再把理解任务转化为生成答案。它不是像 BERT 那样双向编码全文,但足够大、数据足够多、训练方式足够好时,生成过程本身就承载了理解。 ## 解码策略 在自然语言生成的任务中,我们通常使用一个预先训练好的大模型来根据我们的输入生成输出文本,而大模型给的输出是下一个token的概率分布,而如何在这些概率分布中选择,便是解码策略。 目前主流的解码策略有以下几种: - 贪心解码:即直接选择最大概率的token - 随机采样:按照概率分布随机选择一个单词 - beam search:维护一个大小为 k 的候选序列集合,每一步从每个候选序列的概率分布中选择概率最高的 k 个单词,然后保留总概率最高的 k 个候选序列。 以上的方法各有各的问题,而top-k和top-p采样就是介于贪心和随机采样之间的策略,也是目前大模型最常用的解码策略。 ### top-k采样 为什么不能直接用贪心解码,想象我们智能手机的输入法推荐下一个字,他就是贪心解码策略,直接给你按照从高到低概率排列,但是如果我们一直按第一个,会发现句子逐渐越来越重复。而top-k就是从概率最高的k个单词中,采用随机采样来选一个单词。 ![Top-k 采样示意图](https://somnusblog.oss-cn-shanghai.aliyuncs.com/images/20260508145129764.png) 上图中,紫色部分便是被选中的,可能被选择的词。 Top-k 的优势是简单、稳定、能过滤低概率噪声,相对更多样;劣势是固定 k 太机械,容易在模型很确定时放得太宽、在模型不确定时收得太窄。 ### top-p采样 top-k有个很明显的问题,就是这个k如何确定,如何选择才更合理,于是出现了动态设置 token 候选列表大小策略——即核采样(Nucleus Sampling)。 ![Top-p 采样示意图](https://somnusblog.oss-cn-shanghai.aliyuncs.com/images/20260508145844565.png) 由上图中可以看出来,top-p的策略是设定一个p,只保留从高到低累计概率小于p的token,这样就可以避免上面top-k的一些问题。 Top-p 的优点是自适应、灵活、能过滤长尾,特别适合开放式生成;缺点是 p 需要调,分布平坦时候选可能过多,而且随机采样仍可能带来不稳定。 在现代的大模型中,top-k和top-p往往都是同时采用的。即先进行top-k采样缩小候选范围,再在这个候选范围中采用top-p采样来保留高概率的候选集合。 同时启用 top-k 和 top-p,是为了让 top-k 提供候选数量上限,让 top-p 根据概率分布动态收缩候选集合;两者结合能减少低质量长尾,又避免固定 k 过于机械。 ### temperature采样 temperature采样的本质就是先对logits做一些缩放,再进行softmax。 $$ p\_i = \\frac {exp(z\_i / T)}{\\sum\_j exp(z\_j / T) } $$ $z\_i$ = 第 i 个 token 的 logit,T = temperature,$p\_i$ = 第 i 个 token 的采样概率 ![Temperature 采样示意图一](https://somnusblog.oss-cn-shanghai.aliyuncs.com/images/20260508160013393.png) ![Temperature 采样示意图二](https://somnusblog.oss-cn-shanghai.aliyuncs.com/images/20260508160049711.png) 结论就是,温度越高随机性越大,分布越均匀,越发散;温度越低,随机性越低,分布更尖锐,越保守。 ## **联合采样(top-k & top-p & Temperature)** 通常我们是将 top-k、top-p、Temperature 联合起来使用。使用的先后顺序是 top-k->top-p->Temperature。 我们还是以前面的例子为例。 首先我们设置 top-k = 3,表示保留概率最高的3个 token。这样就会保留女孩、鞋子、大象这3个 token。 - 女孩:0.664 - 鞋子:0.199 - 大象:0.105 接下来,我们可以使用 top-p 的方法,保留概率的累计和达到 0.8 的单词,也就是选取女孩和鞋子这两个 token。接着我们使用 Temperature = 0.7 进行归一化,变成: - 女孩:0.660 - 鞋子:0.340 接着,我们可以从上述分布中进行随机采样,选取一个单词作为最终的生成结果。 ## Q&A Q1:Transformer block 里 attention 和 FFN 分别负责什么? A1:attention是主要的模块,是用来训练模型能关注更重要的信息,而FNN是两个简单的全连接层。 Q2:Encoder 和 Decoder 最大区别是什么? A2:我认为最大的区别是encoder的输出可以输入到decoder中,decoder因此具有cross-attention功能,除此以外,还有一些区别,比如encoder的注意力是不需要掩码的。 Q3:为什么 GPT 用 Decoder-only 架构? A3:GPT是generative pretrain transformer特指生成式的大模型,而decoder天生就更适合生成式的任务,因为他的单向注意力机制等等特性。 Q4:logits、softmax、probability 三者是什么关系? A4:**logits 是“还没正规化的偏好分数”;softmax 是“归一化成概率分布”;probability 是“最终每个 token 的概率”。** Q5:temperature 变大/变小分别会怎样? A5:温度越高随机性越大,分布越均匀,越发散;温度越低,随机性越低,分布更尖锐,越保守。 Q6:top-k 和 top-p 的区别是什么? A6:top-k是指定k个候选项,按照概率从高到低选择,而top-p是指定累计概率p,按照概率额从高到低选择累计概率小于p的候选。 整体过了,尤其 Q4/Q5 很稳。帮你把几个点修准一点: Q1 需要补上关键分工:**Attention 负责 token 之间的信息交互**,让每个 token 根据上下文更新自己的表示;**FFN 负责对每个 token 各自做非线性变换**,提升表达能力。FFN 虽然看起来只是两层全连接,但它通常占 Transformer 参数量很大,不是“简单附属模块”。 Q2 你的理解基本对,但“最大区别”可以更直接:**Encoder 是双向理解,Decoder 是因果生成**。在原始 Encoder-Decoder Transformer 里,Decoder 还有 cross-attention 去读 Encoder 输出;但 GPT 这种 decoder-only 没有 encoder,所以也没有 cross-attention。 Q3 对。更完整说法:GPT 用 decoder-only 是因为预训练目标是 next-token prediction,天然要求当前位置只能依赖历史 token。Decoder-only 结构简单、训练目标和推理方式一致,很适合规模化生成。 Q6 小修:top-k 不是“按照概率从高到低选择”,而是**只保留概率最高的 k 个 token,然后在这 k 个里面重新归一化并采样**。top-p 是**保留从高到低累计概率达到 p 的最小候选集合,然后重新归一化并采样**。你说的“小于 p”接近,但通常是“直到累计概率达到/超过 p”。 ### 自注意力,万恶之源(不是) URL: https://blog.somnus.wiki/notes/zi-zhu-yi-li-mo-e-zhi-yuan-bu-shi/ Last updated: 2026-07-21T07:57:08.000Z ## attention 注意力机制,最早是在机器翻译论文\*\*[Neural Machine Translation by Jointly Learning to Align and Translate](https://arxiv.org/abs/1409.0473?ref=blog.somnus.wiki)\*\*中提出来的,他的核心是,用一个东西产生的query,去key/value中查询需要的东西。在上述论文中encoder-decoder attention的结构中。 ``` Q来自于decoder K,V来自于encoder ``` decoder 当前正在生成目标语言词,它用自己的 query 去源语言句子的 encoder 表示里查找相关信息。 只要Q、K、V来自于不同的地方,就是attention机制。 ### attention的本质 实际上attention就是根据相关性对信息进行**分配权重**,在**加权汇总**。 他不是让模型一整个平均的去看信息,而是让模型知道什么更重要,什么没那么重要,重要的多看一点。 数学上就是这样表示: ``` 1. 用 Query 和 Key 算相关性分数 score = QK^T 2. 用 softmax 把分数变成权重 weights = softmax(score) 3. 用这些权重对 Value 做加权求和 output = weights · V ``` ## self-attention 区别于attention,自注意力机制的Q、K、V都是来自于同一个source。 $$ \\begin{aligned} Q&=X W\_q \\\\ K&=X W\_k \\\\ V=X W\_v \\end{aligned} $$ 上面公式中的X就是source,Q、K、V都是从X得到的。 ### self-attention的本质 **让序列中的每个位置,根据自身内容,动态地从同一序列的所有位置中取信息。** 根据上面计算得到的Q、K、V,让每一个token的Q和其他所有的token的K都计算一遍相似度 $$ score = \\frac{Q K^T} {\\sqrt{d\_k}} $$ 再根据权重值,得到输出: $$ output=softmax(score)V $$ 上述公式是论文[*attention is all you need*](https://arxiv.org/abs/1706.03762?ref=blog.somnus.wiki)中的,其中的$d\_k$分别是是K的维度(dimension)。作用是防止计算得到的score过于大,导致经过softmax之后过分尖锐(一个位置的权重无限接近于1,其余位置无限接近于0,训练效果差),或者说,除以$d\_k$是为了防止进入softmax的饱和区,导致梯度消失。神经网络更新参数是需要根据梯度变化来更新的。 $$ \\frac{\\partial p\_i}{\\partial z\_i} = p\_i(1 - p\_i) $$ ### causal self attention 是一种带因果罩的self-attention。即判断当前token是否能看见,未来token,比如`我 喜欢 大 模型` 对于`喜欢`这个token,就无法看见`大` `模型`这两个token,在处理`喜欢`这个token的时候,就会给他后面的token添加一个causal mask,把未来的attention score设置为-inf,因为此处的softmax是0。 对于生成式(GPT)的任务,通常需要从左往右输出下一个token,如果在训练时没有遮蔽未来token,相当于就是让模型提前知道了答案,训练loss很好看,但是模型能力并没有得到训练,所以对于GPT这种自回归语言模型必须要有causal mask。 而对于理解型任务,比如情感分析,文本填空,则需要双向self attention,要理解整句话,比如下面的例子 ``` 这部电影前半段无聊,但结尾非常震撼。 ``` 判断情感时,“无聊”应该能看到后面的“但结尾非常震撼”,否则理解会偏。BERT 这类模型就通常用普通双向 self-attention,可以看左右两边。 ### 添加位置编码 self-attention虽然考虑了所有的输入变量,但是并没有考虑每个变量处于**序列中的位置**。 比如,`我 爱 你`和`你 爱 我`就是两组序列,原本的self- attention只关心他们每个词的相关性,不在乎顺序,但很明显这两组序列想表达的意思是绝对不一样的。 于是便有了添加位置编码来明确每个输入变量(token)的位置。图片中的e就是位置编码。 ![位置编码示意图](https://somnusblog.oss-cn-shanghai.aliyuncs.com/images/20260507160408773.png) - 注意:位置编码的计算不是直接根据token的位置1,2,3,4得到的,主要有两种逻辑,一种是根据公式计算,像[*attention is all you need*](https://arxiv.org/abs/1706.03762?ref=blog.somnus.wiki)中的sin/cos位置编码就是通过公式计算到的;另一种是可学习的位置编码,比如:$P\_{position} ∈ R^{(max\_{len} × d\_{model})}$,训练不光得到Wq、Wk、Wv,还会得到这个位置编码的计算方式。 ### multi-head attention 是指不是指用一套Q、K、V来去看上下文,而是用多套Q、K、V去看。 比如有8个head: $$ \\begin{aligned} head\_1&: Wq\_1, Wk\_1, Wv\_1\\\\ head\_2&: Wq\_2, Wk\_2, Wv\_2\\\\ \\dots\\\\ head\_8&: Wq\_8, Wk\_8, Wv\_8 \\end{aligned} $$ 对于每个head,都回去计算一遍output: $$ \\begin{aligned} Q\_i&=XWq\_i\\\\ K\_i&=XWk\_i\\\\ V\_i&=XWv\_i\\\\ head\_i&=softmax(Q\_i K\_i^T \\sqrt{(d\_k)})V\_i \\end{aligned} $$ 再把每个head都concat起来。 $$ \\begin{aligned} concat&=\[head\_1; head\_2; \\dots; head\_h\]\\\\ output&=concat \* W\_o \\end{aligned} $$ 最终的到新的输出。 - 为什么要有多头注意力:因为一个 attention head 的表达能力有限。多头相当于让模型同时提出不同问题。 比如:小明把苹果放进书包,因为它很甜。对于不同的head就会关注不同的关系 ``` head 1:关注“它”指代谁 head 2:关注形容词“甜”和名词的关系 head 3:关注局部相邻词 head 4:关注主谓宾结构 ``` 下图中的h就是head的数量。 ![多头注意力示意图](https://somnusblog.oss-cn-shanghai.aliyuncs.com/images/20260507164008940.png) 对于多头注意力机制,训练得到的参数在前面的基础上,又增加了$W\_o$,它负责把各个 head 看到的东西怎么合并。 $$ W\_o ∈ R^{(h \* d\_v × d\_{model})} $$ ``` graph LR A--->B[attention] A--->C[self-attention] C--->D[single-head] C--->E[multi-head] D--->F[normal] D--->G[causal] E--->H[normal] E--->I[causal] ``` --- ## test: Q1:Q、K、V 分别来自哪里?为什么不是直接用输入向量两两算相似度? A1:Q、K、V都是通过对embedding的数据分别其乘上其对应的权重系数得到的,比如Q=Wq\*X;因为attention机制就是为了根据相关性对信息来分配权重,而直接两两相乘,忽略了相关性,效果不好。 Q2:attention score 为什么要除以sqrt(d\_k) A2:因为如果不除去这个K的维度开方,权重值会偏大,导致softmax容易进入饱和区,从而导致梯度消失,而神经网络的训练都是根据梯度来变化来更新参数的。 Q3:softmax 后的 attention weights 表示什么? A3:每个token的Q和K求得的相似度,也就是这个token在全文中的权重。 Q4:self-attention 相比 RNN/CNN,核心优势和代价分别是什么? A4:参见[Self-Attention、RNN、CNN 核心对比](https://affine.somnus.top/workspace/09e2838e-0333-4d5c-80d5-bcae278e8e50/BPPtOP8gag7NYk-SFtleQ?ref=blog.somnus.wiki) Q5:causal self-attention 和普通 self-attention 差在哪里? A5:causal self-attention 没有学到 --- A1:小修正:不是“直接两两相乘忽略了相关性”,直接用输入向量做相似度其实也能算相关性,但表达能力弱。引入Wq/Wk/Wv的意义是让模型学会三种不同角色: - Q:当前 token 想找什么信息 - K:每个 token 能被别人匹配的特征 - V:真正被加权汇总的信息内容 A3:Q3 需要再精确一点。softmax 后的 attention weights 表示:**对某一个 query token 来说,它应该从各个 key/value token 那里取多少信息**。不是“这个 token 在全文中的权重”,而是“这个 token 看其他 token 的分配比例”。 A4:补上: - 相比 RNN:self-attention 可以并行处理序列,而且任意两个 token 的交互路径更短;RNN 要一步步传递,长距离依赖容易衰减。 - 相比 CNN:self-attention 可以直接建模任意位置之间的关系;CNN 依赖卷积核和层数扩大感受。 - 代价:标准 self-attention 的计算和显存复杂度是O(n^2),序列越长越贵。 A5:causal self-attention 是 decoder / 自回归生成里用的。它和普通 self-attention 的区别是:**当前 token 只能看自己和它左边的 token,不能看未来 token**。 比如句子: `我 喜欢 大 模型` 生成“喜欢”时,只能看“我”和“喜欢”,不能偷看后面的“大 模型”。实现上会加一个 causal mask,把未来位置的 attention score 设成-inf,softmax 后权重就是 0。 ### 2026-05-06 URL: https://blog.somnus.wiki/diary/2026-05-06/ Last updated: 2026-07-21T07:57:06.000Z 这里写日记… 以后的日记还是在appflowy上面更新了,但是他的跨设备功能很方便,但是我现在还没找到导出markdown的按钮,所以现在不太能连载到我的博客网站上面了 --- 取消了一个appflowy,fuck,因为他不但没有导出markdown function,而且还没有ai接入功能,在他足够完善之前,我都会使用obsidian的sync方案。虽然有点贵,一个月要37块钱左右,就当成知识付费了,但是部署appflowy的经历还是有不少可以总结的。 --- 最近部署了一下 appflowy 在我的火山云服务器上,感觉虽然有点麻烦但是还是有点意思的\~(¯▽¯) ## 部署遇到的问题 ### dns无法解析域名 我在给阿里云的域名后台添加了appflowy.somnus.top的解析后,发现在服务器端还是没办法解析到这个域名的ip, ``` root@iv-yef9e14c8wxjd1vkt7di:~/AppFlowy-Cloud# nslookup appflowy.somnus.top 8.8.8.8Server: 8.8.8.8Address: 8.8.8.8#53** server can't find appflowy.somnus.top: NXDOMAIN ``` 后台在claude一步步帮助下,我通过这个指令。 ``` root@iv-yef9e14c8wxjd1vkt7di:~/AppFlowy-Cloud# nslookup -type=NS somnus.top 8.8.8.8Server: 8.8.8.8Address: 8.8.8.8#53Non-authoritative answer:somnus.top nameserver = zariyah.ns.cloudflare.com.somnus.top nameserver = alan.ns.cloudflare.com.Authoritative answers can be found from:root@iv-yef9e14c8wxjd1vkt7di:~/AppFlowy-Cloud# ``` 才知道了原来我的阿里云已经把解析的权力给了cloudflare。 > 因为这个域名的主域名是用于博客网站的搭建,即somnus.top,而为了便宜,这个博客网站是我通过github actions搭建的,为了方便国内能比较快的访问这个博客网站,我便把解析权给了cloudflare。 于是我在cloudflare后台,在添加了一条新的A解析,这个问题就解决了。 其实这个问题,之前就遇到过,在我搭建vaultwarden的时候,就遇到过,但是这次又忘记😅了。 ## 部署体验 整体体验下来,效果还是很好的,感觉同步也很流畅,不再依赖于国外的服务器,ios端也不会出现报错。 但是最大的问题就是两个 ### 无法本地使用deepseek或者其他api appflowy原生支持的是openai的api调用,来实现其ai功能。但是openai很贵!!!于是我想着使用deepseek的api调用来实现ai功能,发现并不能原生支持,在deploy.env中,根本没有baseurl的配置项 ``` AI_OPENAI_API_KEY=# If no summary model is provided, there will be no search summary when using AI search.AI_OPENAI_API_SUMMARY_MODEL= ``` 然后去这个项目的issues里面找,发现早在24年就有人提出了,要添加其他模型支持的fr。 ![](https://somnusblog.oss-cn-shanghai.aliyuncs.com/images/%E6%88%AA%E5%B1%8F2026-05-06%2016.38.07.png) 然后我fork了开源的appflowy和appflowy\_cloud两个项目到本地,问codex发现ai功能是在appflowy\_ai这个组件中执行的。而这个组件是一个docker的images,并没有开源出来。 ![](https://somnusblog.oss-cn-shanghai.aliyuncs.com/images/%E6%88%AA%E5%B1%8F2026-05-06%2016.40.28.png) 😡草了,没招了,只能等官方推进这个吧,或者等官方开源这个组件。详情可以关注一下这个[issues](https://github.com/AppFlowy-IO/AppFlowy-Cloud/issues/943?ref=blog.somnus.wiki)的讨论。 ### 无法单个页面导出markdown 具体bu za ### 无法单个页面导出markdown 具体不再多说了。 --- 我会继续找一些其他的可以方便部署的类notion,在此之前,我先用obsidian sync了。 ### 2026-05-04 URL: https://blog.somnus.wiki/diary/2026-05-04/ Last updated: 2026-07-21T07:57:06.000Z > 临时写一篇日记,昨天晚上看了一篇koji关于阿里百炼技术负责人的于文渊的[视频播客](https://www.bilibili.com/video/BV1wQXPBUEkE/?spm%5Fid%5Ffrom=333.1387.homepage.video%5Fcard.click&ref=blog.somnus.wiki),略有感想。 ## 总结播客 于文渊提到, - ai的应用场景已经从闲聊转向了生产力的场景,从而带动了高质量token消耗量**指数级提升**。 - 阿里云百炼的核心使命之一是实现算力资源的最优化配置,确保每一块 GPU 都能被物尽其用 - 对于计算机专业的学生,他建议应利用 AI 提升效率,但核心竞争力在于能够识别 AI 产出的潜在缺陷,坚守那 1% 的深度判断力 - 算力短缺如同工业领域的“石油”匮乏,中国 AI 发展的关键在于算力供给的稳定性与自主可控程度 ## 个人思考 其实其他方面我也没怎么关注,但是我比较关心的就是,作为cs学生,如何在ai时代下正确的处理ai辅助接替工作的情况😅,他给了比较明确的观点,就是不要让ai去完全地接替你的工作。 他在播客中举了一个张文宏的例子,如果你作为一个资深的医生,去使用ai来辅助你工作,这没问题\~但是你如果作为小白医生,第一天就开始使用ai来做一些诊断,这样就完全乱套了,后续ai如果除了什么问题,就也根本没有什么判断的能力了。 我觉得这个例子举得很好了,我在享受ai编程带来的便利的同时,也忽略了他的危害,他让我在脱离ai的情况下,几乎一个简单的程序都写不出来😇,这些是值得警惕的。 ### 2026-04-30 URL: https://blog.somnus.wiki/diary/2026-04-30/ Last updated: 2026-07-21T07:57:06.000Z ## rust学习 > 今天的rust的学习主要是学了`trait`,我原本对`trait`的印象就是一个放在`#[derive()]`里面的,比如增加了`Display`或者`Debug`的trait之后,就可以直接在println里面打印结构体。 我今天主要学的就是重载运算符、`Drop`和`trait`的特性。简单总结一下 ### trait `trait`是什么,顾名思义,`trait`的中文是特征,即一种**定义共享行为**的机制,不管是什么数据类型,只要定义了这个`trait`,就一定要完成某个共享行为。比如说:`Display`、`Debug`、`Drop`…..对于绝大多数数据类型,这些trait都是满足的。 为什么要有trait?我自己总结下来有三点原因: #### 代码复用: 对于任何数据类型,他的同一个`trait`都可以有自己的实现方法,但是如果该数据类型没有自定义实现方法,可以通过`trait`得到一个默认的方法。 ``` trait Greet { fn name(&self) -> String; fn greet(&self) -> String { // 默认实现 format!("Hello, {}!", self.name()) } } ``` #### 泛型约束: 让函数接受“任何实现了某 `trait` 的类型“,实现**静态多态**。 ``` fn make_sound(animal: &T) { println!("{} says {}", animal.name(), animal.speak()); } // 或用 impl Trait 语法(更简洁) fn make_sound(animal: &impl Animal) { ... } ``` 编译器在编译之后就单态化他了。 #### 动态分发: 使用dyn trait实现运行时的多态,可以在`box`里面存放不同的类型。 ``` let animals: Vec> = vec![ Box::new(Dog), Box::new(Cat), ]; for a in &animals { println!("{}", a.speak()); // 运行时决定调用哪个实现 } ``` ### dyn 具体说一下trait的动态分发的例子: ``` fn main() { let rn = 0.63; println!("{}", random_animal(rn).noise()); println!("helloworld") } struct Sheep {} struct Cow {} trait Animal { fn noise(&self) -> &'static str; } impl Animal for Sheep { fn noise(&self) -> &'static str { "mie ~~~" } } impl Animal for Cow { fn noise(&self) -> &'static str { "mou ~~~" } } fn random_animal(rn: f64) -> Box { if rn > 0.5 { Box::new(Sheep {}) } else { Box::new(Cow {}) } } ``` 上面代码中,对于`random_animal`的返回,就使用了动态分发,不需要提前预设好类型,不需要指定是`Sheep`还是`Cow`。 ### 2026-04-29 URL: https://blog.somnus.wiki/diary/2026-04-29/ Last updated: 2026-07-21T07:57:05.000Z > 最近刘总分配了一个新任务,就是从抖店后台抓取抖店的订单数据,保存到本地的excel,还是有所经验可以总结的 先介绍方案,先通过影刀来抓取网页数据,并保存到本地,在将其本地数据通过调用大模型进行清理,再通过python将其写入到中excel。 ## 自动获取数据 ### 为什么使用影刀 相较于影刀,我更多关注了哪些原生支持cli和开源的方案,一方面,支持cli就可以通过agent来自动化获取数据;另一方面,开源方案通常来说本地部署是不需要钱的。 但是后来我在试着根据这个[视频](https://www.bilibili.com/video/BV1ooDyBmE6v/?spm%5Fid%5Ffrom=333.337.search-card.all.click&ref=blog.somnus.wiki)来通过claude code调用playwrightcli,但是效果非常差,经常莫名其妙要求人工验证,也不知道哪里出问题了,之后有时间我准备在codex上再试试。 影刀使用还是很方便的,全是拖动节点,手动捕获目标区域。整个就是模拟人类点击滚动等操作,很不容易被**封禁**。关于费用方面,我目前也没发现他有什么收费的点,好像是多人合作使用一个的时候要付费,等于免费😆 ![](https://somnusblog.oss-cn-shanghai.aliyuncs.com/images/%E6%88%AA%E5%B1%8F2026-04-29%2011.35.23.png) 其实可以看到,我这个获取数据还是不是直接整个页面获取的,而是分成了两个获取的步骤,第一个获取相似元素:因为每个商品卡没办法捕获为整体, 而每个商品的标题栏,也就是订单编号和下单时间这一栏,可以捕获,于是我对每个订单的标题栏都采用了获取相似元素列表,最终保存结果为order\*heads.csv,这个数据是相对==\*干净整洁\_==的 ![](https://somnusblog.oss-cn-shanghai.aliyuncs.com/images/%E4%B8%8B%E8%BD%BD.png) 但是效果还是不够好,还是会识别出一些乱的文字,如下图 ![](https://somnusblog.oss-cn-shanghai.aliyuncs.com/images/%E6%88%AA%E5%B1%8F2026-04-29%2016.40.19.png) 于是我最终还是决定调用大模型来分析和处理这些数据。输入的文件就是这个heads,和fullpage的文件。 ### 本地大模型调用 我原本是在使用fastgpt来处理这个数据的,因为fastgpt可以显式的制定大模型的提示词,而且之前那个净水客服我也配置好了相应的大模型供应商。但是后来发现搞来搞去,最后就一个节点😅,于是fastgpt就显得没什么很大的必要了。我最后决定使用openai库来调用大模型处理,话说这应该是我第一次通过代码来本地调用大模型。先看一下main函数: ``` def main() -> int: log("加载环境变量") load_dotenv() parser = argparse.ArgumentParser(description="Call DashScope to generate output.json") parser.add_argument("--prompt", default="prompt.txt", help="prompt file path") parser.add_argument("--heads", default="data/order_heads.csv", help="order heads csv path") parser.add_argument("--full", default="data/order_full.txt", help="full order text path") parser.add_argument("--out", default="data/output.json", help="output JSON path") parser.add_argument("--model", default=os.getenv("DASHSCOPE_MODEL", DEFAULT_MODEL)) parser.add_argument("--base-url", default=os.getenv("DASHSCOPE_BASE_URL", DEFAULT_BASE_URL)) parser.add_argument("--api-key", default=os.getenv("DASHSCOPE_API_KEY")) parser.add_argument( "--no-json-mode", action="store_true", help="do not send response_format=json_object to the model API", ) args = parser.parse_args() if not args.api_key: print("缺少 DASHSCOPE_API_KEY。请先创建 .env,或用 --api-key 传入。", file=sys.stderr) return 2 log("开始生成 output.json") result = call_model( prompt_path=Path(args.prompt), order_heads_path=Path(args.heads), order_full_path=Path(args.full), model=args.model, base_url=args.base_url, api_key=args.api_key, use_json_mode=not args.no_json_mode, ) out_path = Path(args.out) log(f"写入结果文件: {out_path}") out_path.write_text(json.dumps(result, ensure_ascii=False, indent=2), encoding="utf-8") order_count = len(result.get("orders", [])) if isinstance(result, dict) else 0 log(f"已生成: {out_path}") log(f"订单数量: {order_count}") return 0 ``` 首先这个main函数很明显的一点就是使用了argparser来解析命令行,分别获取数据文件位置,大模型相关信息(base\_url、model、dashborad、api,promot),然后调用call\_model这个函数来调用大模型。 在call\_model函数中,核心就这些 ``` log(f"初始化模型客户端: model={model}, base_url={base_url}") client = OpenAI(api_key=api_key, base_url=base_url) kwargs: dict[str, Any] = { "model": model, "messages": [ { "role": "system", "content": "你是订单文本结构化助手。只返回严格 JSON,不要返回 Markdown 或额外说明。", }, {"role": "user", "content": user_message}, ], "temperature": 0, } if use_json_mode: kwargs["response_format"] = {"type": "json_object"} log("开始请求大模型,等待返回...") start = time.perf_counter() response = client.chat.completions.create(**kwargs) elapsed = time.perf_counter() - start log(f"模型已返回,耗时 {elapsed:.1f} 秒") ``` 然后再对这个输出的json进行一些格式检查,就直接保存到本地了。 输出的json文件如下: ![](https://somnusblog.oss-cn-shanghai.aliyuncs.com/images/pixelated-image_1777453634494.png) ### 写入excel 要求写入的表格如下: ![](https://somnusblog.oss-cn-shanghai.aliyuncs.com/images/%E6%88%AA%E5%B1%8F2026-04-29%2017.04.23.png) xlsx文件本质是一个**压缩包**,把不同的sheet压缩到一块。通过python的zipfile可以对xlsx文件进行解压并读取,写入完成后,再通过zipfile对其重新压缩。 对于文件写入,是通过`xml.etree.ElementTree`这个模块来获得的,展开后的xlsx,本质都是xml结构,即openxml协议。 通过手动查找、增加和修改 XML 标签(例如表示行的 `` 和表示单元格的 `` 以及表示值的 ``)来实现对工作表内容的编辑。 %% 该说不说,在此之前我对xlsx一直都有刻板印象,是一个微软(肯定不是)的封闭的打开很麻烦的一种文件格式,没想到竟然内部也是朴实无华的。 %% 通过这样一套逻辑,也可以成功的把数据写入到指定的行和列。 ``` graph TD a[开始] --定时启动--> b[影刀] b--数据-->c[大模型] c-->d[写入xlsx] ``` ## 生活 最近把公司配的macbook熟络了一下,真香啊,感觉这个屏幕看着就很舒服,其实这个屏幕也不是高刷屏,但好像是什么hdr,反正看着就是很舒服。 除此以外,macos的生态也很好,很多我的linux上没有的软件,macos都支持。 希望以后能有钱买这个电脑 ### 服务器基础1 URL: https://blog.somnus.wiki/p/fu-wu-qi-ji-chu-1/ Last updated: 2026-07-21T07:57:13.000Z > 我在没有这个台式机之前只有一个带集显的笔记本,所以之前的训练任务只能通过租服务器来训练,不可避免的去学一下操作远程服务器的指令。避免每次都要google或者问ai,于是写成这篇博客。 感觉这样的操作就像现在很火的一个词:skill 😄,总结成经验方便以后调用。 ## ssh连接 首先是连接 ``` ssh -p 44012 root@connect.bjb2.seetacloud.com ``` 然后输入yes,再输入密码。 这个很简单,也没有什么好说的。 ## 从远程服务器复制 ### scp指令 这个是最常用的从远程复制文件到本地的指令。 ``` scp -P 8888 用户名@服务器IP:远程文件绝对路径 本地保存路径 ``` 同样对于上面那个服务器地址。想要复制他的文件或者文件夹就执行 ``` scp -P 44012 -r root@connect.bjb2.seetacloud.com:/root/autodl-fs/middlebury ~/work/bishe/ ``` 对于一个文件夹,需要加上`-r`。 但是这个指令的优点也是唯一的优点就是用法简单;缺点很多:不支持断点续传,不支持效率低。 ![](https://somnusblog.oss-cn-shanghai.aliyuncs.com/images/%E5%B1%8F%E5%B9%95%E6%88%AA%E5%9B%BE_20260421_234255.png) ### sftp指令 - 特点:交互式操作、类似FTP的操作 ``` sftp user@remote_host # 进入 sftp 交互界面后: sftp> cd /logs/nginx/ # 切换远程目录 sftp> ls -lh # 查看远程文件大小 sftp> get access.log.1.gz # 下载指定文件 sftp> exit # 退出 ``` ![](https://somnusblog.oss-cn-shanghai.aliyuncs.com/images/20260422002927448.png) - 优点:操作方便,直观;支持断点续传。 - 缺点:传输效率一般。 ### rsync指令 基于增量传输算法,是目前公认的最强的文件同步工具。 普及率也还行,目前大部分主流linux发行版都会预装rsync,比如unbunt,centos,fedora。 ![](https://somnusblog.oss-cn-shanghai.aliyuncs.com/images/20260422003757409.png) rsync最常用的参数组合就是-avz - \-a(archieve):保留权限,软链接,时间戳等属性。 - \-v(verbose):显示详细进度。 - \-z(compress):传输时压缩节约流量,传的更快。 如果需要添加服务器端口信息,则需要像上面一个-e “-P 端口号” 在rsync中,源路径结尾是否加斜杠区别很大: - my\_site/(带斜杠):同步目录下的内容。 - my\_site(不带斜杠):同步目录本身及其内容。 ### Cachyos安装 URL: https://blog.somnus.wiki/p/cachyosan-zhuang/ Last updated: 2026-07-21T07:57:13.000Z > 前言: 刚刚入职一周左右,我现在也算在玄武**安定下来**了,于是我便让我家里人把我寒假组装的台式机从老家寄过来,然后我还买了一块 **1TB 的固态硬盘** 💾,专门用于台式机的 Linux 系统。 最近这段时间,Linux 系统也是经历了*频繁更换* 😅 -> `ubuntu` \-> `archlinux` \-> `fedora` \-> `cachyos`。最终决定**长期使用**的就是 `CachyOS` 🎉。 ## 简要介绍 🌟 **CachyOS** 是由 CDN77 和 CLOUDFLARE 赞助的、基于 Arch Linux 的开源 Linux 发行版,主打的特点是**极致的性能** 🚀!它的每个封装都编译了 `x86-64-v3`、`x86-64-v4` 和 `Zen4` 指令集以及 LTO。核心封装还会获得额外的 **PGO** 和 **BOLT** 优化——*完全无需手动重建*。 为了方便能释放出我电脑的**所有性能** 💪,于是我便选择了 CachyOS,不太喜欢像 Arch Linux 那种*完全自定义*,实在是太麻烦了 😢 ## 安装 🔧 安装直接可以看 **shorin** 的 wiki。 通过这个连接访问 🔗 [Shorin-ArchLinux-Guide/wiki/CachyOS](https://github.com/SHORiN-KiWATA/Shorin-ArchLinux-Guide/wiki/CachyOS?ref=blog.somnus.wiki) 我就写一个注意的点。我在我的 HP 笔记本上装双系统,按照这个流程几乎**一点问题都没有**(原本别人说的在 ISO 中要提前改源的点似乎已经被修复了 ✅)。 但是在我的**台式机** 🖥️ 上,在两个不同的硬盘上安装双系统就出现问题了 ⚠️。 ### 问题 1 ❓ #### 表现 👀 在配置安装的时候会发现选择引导 (grub) 界面会和 shorin 的教程中不太一样,只剩下两个选择,就是 `grub` 和另一个什么,此时就该发现不对了 🙅。然后在后面安装的过程中也会出现报错,直接终止安装 ❌。 ``` The bootloader could not be installed. The installation command
grub-install --target=i386-pc --recheck --force /dev/nvme0n1
returned error code 1. ``` #### 解决 🛠️ 这个问题其实很好解决。在安装的时候,我起初没有注意到 **bootloader** 的位置。在选择分区的时候左下角有一个选择 bootloader,将这个要设置成和要装 **CachyOS** 的盘一致 :disk:。此后就安装成功了 🎉,但紧接着就是第二个问题。 ### 问题 2 ❓ #### 表现 👀 按照 **CachyOS Hello** 的指引,选择了 *reboot*,我们会发现电脑直接进入了 **Windows** 而非 `grub` 😭。 根据网上的说法,可能是主板 BIOS 启动设置的问题,比如什么 **secure boot** 模式。这一部分问题我全部都排查过了,可以从 *shorin 的 wiki* 中提前做好设置。 ![](https://somnusblog.oss-cn-shanghai.aliyuncs.com/images/20260420175939731.png) #### 解决 🛠️ 我首先去主板里把启动项设置成我装 Linux 的**固态硬盘**,然后才可以正常进入 `grub`。但是问题是没能够在 `grub` 中找到 Windows 的启动选项 💭。后来逐渐摸排,才发现问题出现在主板上:主板处于 **UEFI 和 Legacy 的兼容模式** (CSM),导致开机会自动进入主板的兼容选择模式,此时便会根据在 BIOS 中的选择来启动,而 `grub` 没办法兼容两种启动方式。如果你强行在 `grub` 里面添加 Windows 的启动项,就会出现下面的报错情况: ![](https://somnusblog.oss-cn-shanghai.aliyuncs.com/images/20260420181352693.png) 为了解决这个问题,我们*直接把主板的 CSM 兼容模式关闭* 🚫,然后重新安装一遍,便大功告成 🥰 🎊! ### 问题 3 (可能存在) ⚠️ #### 表现 👀 由于网络原因,安装的下载过程**奇慢无比** 🐌。 #### 解决 🛠️ **手动把源换成中国的源** 🇨🇳。 ``` # 切换目录 cd /etc/pacman.d/ # 替换CachyOS本体源为中科大国内源 sudo tee cachyos-mirrorlist < 最近正在准备写本科毕业设计论文,不想用微软的word写,因为感觉不够极客 😧 ,也不想使用LaTex,因为LaTex太笨重了,编译速度太慢了,在线服务overleaf又是收费的(帮黄学长报销这个来着,好像一个月要三百多 🙀) 在网上搜罗搜罗发现了typst,一个基于rust内核的开源的标记语言排版系统,速度非常快,功能我觉得的也足够强(公式什么的都能很好地显示),同时我在他的模板库也找到了其他大学的毕业论文模板(nju),于是基于这个模板,我便衍生做一个swjtu的版本,最后,感谢nju-lug。 ## 基础介绍 先简单介绍一下typst,是一款可用于出版的可编程标记语言,拥有变量、函数与包管理等现代编程语言特点,同时又注重于科学写作领域,与LaTex相似。 - 语法简洁:上手难度与markdown相当,文本的源码阅读性很高 - 编译速度快:typst用的是rust编写的,即typ(esetting+ru)st,运行平台是WASM, - 环境搭建简单:不需要像LaTex折腾几个G的开发环境,可以在Web运行,还可以用vscode安装插件开发。 ## Typst 简明语法指南 Typst 是一款现代、高效的排版系统,旨在提供比 LaTeX 更简单的语法和更快的渲染速度。其语法主要分为:**标记模式 (Markup)**、**数学模式 (Math)** 和 **脚本模式 (Scripting)**。 ### 基础文本排版 (Markup Mode) 标记模式用于文档的正文结构,类似于 Markdown。 | 功能 | 语法示例 | 说明 | | -------- | ----------------------------------- | ---------------- | | **标题** | \= 一级标题, \== 二级标题 | \= 越多层级越深,等号后需空格 | | **加粗** | \*加粗内容\* | 使用星号包裹 | | **斜体** | \_斜体内容\_ | 使用下划线包裹 | | **代码块** | \` \`行内代码\` \` 或 \`\`\` 块状代码 \`\`\` | 支持多种语言高亮 | | **无序列表** | \- 列表项 | 自动层级缩进 | | **有序列表** | \+ 列表项 | 自动编号 | | **术语列表** | / 术语: 定义描述 | 常用于名词解释 | | **链接** | https://google.com | 自动识别或使用 #link 函数 | | **引用** | \> 引用文本 | 用于长段引用 | | **注释** | // 单行 或 /\* 多行 \*/ | 不会显示在输出文件中 | --- ### 数学公式 (Math Mode) 使用美元符号 `$` 进入数学模式。 - **行内公式**: `$a + b = c$` - **块级公式**: `$ v = s / t $` (在 `$` 符号内侧保留空格) #### 常用数学语法: - **上下标**: `x^2`, `y_i` - **分式**: `1/2` 或 `fract(a, b)` - **根号**: `sqrt(x)` - **希腊字母**: 直接输入名称,如 `alpha`, `beta`, `phi`, `Delta` - **矩阵**: `mat(1, 2; 3, 4)` (分号分隔行) - **求和/积分**: `sum`, `integral`, `product` - **限制范围**: `sum_(i=0)^n x_i` --- ### 函数与脚本 (Scripting Mode) 在 Typst 中,所有以 `#` 开头的标识符都代表函数调用或代码逻辑。 #### 常用内置函数 - **图片**: `#image("logo.png", width: 40%)` - **形状与间隔**: `#rect[]`, `#circle[]`, `#h(1cm)` (水平间距), `#v(2pt)` (垂直间距) - **换页**: `#pagebreak()` **表格**: ``` #table( columns: (1fr, auto, 1fr), [左], [中], [右], [1], [2], [3], ) ``` --- ### 样式设置 (Set & Show Rules) 这是 Typst 实现自动化排版的核心。 #### Set 规则 (全局状态设置) 用于修改元素的默认属性: ``` #set page(paper: "a4", margin: 2cm, numbering: "1") // 纸张、边距、页码 #set text(font: "SimSun", size: 12pt, lang: "zh") // 字体、字号、语言 #set heading(numbering: "1.1 ") // 标题自动编号 #set par(justify: true, first-line-indent: 2em) // 段落两端对齐、首行缩进 ``` #### Show 规则 (元素重定义) 用于改变特定元素的表现方式(类似于 CSS): ``` // 将所有一级标题设为红色 #show heading.where(level: 1): set text(red) // 在每个表格前自动加个加粗的提示 #show table: it => [ *数据表:* #it ] ``` --- ### 变量、编程与逻辑 Typst 是一门完整的编程语言。 - **定义变量**: `#let name = "Typst"` - **条件判断**: `#if 1 < 2 [Yes] else [No]` - **循环**: `#for i in range(3) [ #i ]` **定义函数**: ``` #let notice(body) = rect(fill: gray.lighten(80%))[ *注意:* #body ] #notice[这是一个自定义提醒框] ``` --- ### 标签与引用 - **定义标签**: 在元素后面紧跟 ``。 - **引用标签**: 使用 `@label_name`。 ``` = 核心介绍 如章节 @intro 所述... #figure( image("graph.png"), caption: [实验结果], ) 见图 @fig_result 。 ``` --- ### 常用快捷键/对比 (vs LaTeX) | 需求 | Typst | LaTeX | | -------- | -------------------------- | --------------------------- | | **字体颜色** | #text(fill: blue)\[文本\] | \\textcolor{blue}{文本} | | **环境包裹** | \[...\] | \\begin{...} ... \\end{...} | | **加粗** | \*text\* | \\textbf{text} | | **导入模块** | #import "template.typ": \* | \\usepackage{...} | ## 个人使用经验 > 最近在完善毕设论文,亲自使用了这个模板,一边发现问题一边修改问题hhhhh,感觉效率非常高。同样也总结了一些工作的经验。 ### 流程图 任何的论文都**一定一定**会有流程图,不管说是方法的流程,还是模型的结构,都需要流程图来让表达更加具体,让读者更容易,引用一个著名的理论*论文就是要讲好一个故事*,衡量论文,不光要从学术的角度看,也许要关注他的表述,怎么让读者看懂。 对于typst的流程图,我在我本人的毕设里面采用了两种方案,一种是通过drawio(现在叫digrams)来绘制,并导出pdf后再导入论文中。 typst插入论文图片使用以下的语法 ``` #figure( image("image_path",width=80%), caution:[图片的描述] ) ``` 第二种方案是使用typst嵌入的流程图绘制方式,typst作为开源项目,虽然原生的功能可能存在缺乏现象,但是会有很多开源社区的贡献来弥补,流程图的绘制,就会用到cetz这个库,首先在typ文件开头引用cetz ``` #import "@preview/cetz:0.3.4" ``` 然后就可以通过函数调用的方式来绘制具体的流程图,下面可以看到,真的非常之函数。😁 ``` #figure( align(center)[ #cetz.canvas({ import cetz.draw: * let box-stroke = (paint: rgb("#3E4A61"), thickness: 0.9pt) let box-fill = rgb("#F5F7FA") let arrow-stroke = (paint: rgb("#4B5563"), thickness: 0.9pt) let x1 = 0 let x2 = 6.4 let ys = (8.4, 7.2, 6.0, 4.8, 3.6, 2.4, 1.2) let labels = ( [加载 FP32 权重], [构建 RAFT-Stereo 网络], [模型参数转换为 FP16], [输入图像预处理并转换为 FP16], [执行半精度前向推理], [输出视差图], [统计指标并与 FP32 基线对比], ) for i in range(0, 7) { let y = ys.at(i) rect((x1, y - 0.35), (x2, y + 0.35), radius: 0.08, fill: box-fill, stroke: box-stroke) content(((x1 + x2) / 2, y), labels.at(i)) if i < 6 { line(((x1 + x2) / 2, y - 0.35), ((x1 + x2) / 2, ys.at(i + 1) + 0.35), mark: (end: ">"), stroke: arrow-stroke) } } }) ], caption: [基于 FP16 的半精度推理流程图], ) ``` 编译出来的效果如下: ![](https://somnusblog.oss-cn-shanghai.aliyuncs.com/images/20260424131446037.png) ### 专业绘图 同样的专业绘图也可以交给cetz,其实他的专业绘图这么强,我也是没想到的。这个双目摄像头成像示意图就是完全由Chatgpt 5.4生成的,没想到效果这么惊艳。 ![](https://somnusblog.oss-cn-shanghai.aliyuncs.com/images/20260424131915278.png) 代码如下 ``` #figure( align(center)[ #cetz.canvas({ import cetz.draw: * // 定义核心几何参数 let b = 5 // 基线长度 b let f = 1.5 // 相机焦距 f let Z = 6 // 目标点深度 Z let Ol = (0, 0) // 左相机光心 let Or = (b, 0) // 右相机光心 let P = (2, Z) // 空间目标点 P(X, Y, Z) // 绘制基线与光心 line((-1, 0), (b + 1, 0), stroke: (dash: "dashed", paint: luma(150)), name: "baseline") circle(Ol, radius: 0.06, fill: black) content((0, -0.4), [$O_L$]) circle(Or, radius: 0.06, fill: black) content((b, -0.4), [$O_R$]) // 标注基线 b line((0, -0.8), (b, -0.8), mark: (start: ">", end: ">"), stroke: 0.8pt) content((b / 2, -1.2), [$b$]) // 绘制图像平面 (高度为 f) line((-1.5, f), (1.5, f), stroke: 1.2pt) line((b - 1.5, f), (b + 1.5, f), stroke: 1.2pt) // 标注焦距 f line((-2, 0), (-2, f), mark: (start: ">", end: ">"), stroke: 0.8pt) content((-2.4, f / 2), [$f$]) // 绘制空间点 P 及其到基线的垂线 circle(P, radius: 0.06, fill: black) content((P.at(0), P.at(1) + 0.4), [$P(X,Y,Z)$]) line((P.at(0), 0), P, stroke: (dash: "dotted", paint: luma(100))) // 标注深度 Z line((b + 2, 0), (b + 2, Z), mark: (start: ">", end: ">"), stroke: 0.8pt) content((b + 2.4, Z / 2), [$Z$]) // 绘制光线 (P 到光心) line(P, Ol, stroke: (paint: blue.darken(20%), thickness: 0.8pt)) line(P, Or, stroke: (paint: blue.darken(20%), thickness: 0.8pt)) // 计算投影点位置 (利用相似三角形严谨计算) let xL_pos = P.at(0) * (f / Z) let xR_pos = b + (P.at(0) - b) * (f / Z) let pL = (xL_pos, f) let pR = (xR_pos, f) // 绘制并标注左侧投影点 x_L circle(pL, radius: 0.05, fill: red) content((xL_pos - 0.4, f + 0.3), text(fill: red)[$x_L$]) // 绘制并标注右侧投影点 x_R circle(pR, radius: 0.05, fill: red) content((xR_pos + 0.4, f + 0.3), text(fill: red)[$x_R$]) }) ], caption: [双目视觉立体测距原理示意图], ) ``` > 我将永远拥护Chatgpt和typst(如果可以的话),听说那些专业期刊会要求提供tex格式的文件,这不就炸了,不过本科毕设肯定不会要求的,我感觉我同学百分之九十五都不会使用latex来写毕业论文,大部分估计都是word系。 同样对于柱状图,我们也可以直接使用cetz来绘制。 代码如下: ``` #figure( align(center)[ #cetz.canvas({ import cetz.draw: * let axis-stroke = (paint: rgb("#4B5563"), thickness: 0.9pt) let grid-stroke = (paint: luma(220), thickness: 0.5pt) let fp32-fill = rgb("#8FB7E8") let fp16-fill = rgb("#F2A978") // 坐标轴与网格 line((1.2, 0.8), (1.2, 6.0), stroke: axis-stroke) line((1.2, 0.8), (8.6, 0.8), stroke: axis-stroke) line((1.2, 1.8), (8.6, 1.8), stroke: grid-stroke) line((1.2, 2.8), (8.6, 2.8), stroke: grid-stroke) line((1.2, 3.8), (8.6, 3.8), stroke: grid-stroke) line((1.2, 4.8), (8.6, 4.8), stroke: grid-stroke) line((1.2, 5.8), (8.6, 5.8), stroke: grid-stroke) // 纵轴刻度 content((0.7, 0.8), [0]) content((0.55, 1.8), [50]) content((0.45, 2.8), [100]) content((0.45, 3.8), [150]) content((0.45, 4.8), [200]) content((0.45, 5.8), [250]) content((0.6, 6.35), [时间/ms]) // 柱状图 rect((2.3, 0.8), (4.2, 5.0), fill: fp32-fill, stroke: none) rect((5.4, 0.8), (7.3, 4.27), fill: fp16-fill, stroke: none) // 数值与标签 content((3.25, 5.28), [209.80]) content((6.35, 4.55), [173.59]) content((3.25, 0.35), [FP32]) content((6.35, 0.35), [FP16]) }) ], caption: [FP32 与 FP16 速度对比柱状图], ) ``` 效果如图所示 ![](https://somnusblog.oss-cn-shanghai.aliyuncs.com/images/20260424140716277.png) ### 表格 typst原生支持表格,通过使用table可以来创建表格。 代码如下: ``` #figure( table( columns: (22%, 28%, 50%), stroke: none, align: (x, y) => ( if y == 0 { center + horizon } else if x <= 1 { center + horizon } else { left + horizon } ), table.hline(y: 0, stroke: 1.5pt), table.header([*指标类别*], [*统计指标*], [*说明*]), table.hline(y: 1, stroke: 0.5pt), [精度指标], [EPE、D1], [衡量预测视差与真实视差之间的误差,用于判断半精度转换是否影响匹配精度。], table.hline(y: 2, stroke: 0.5pt), [速度指标], [AvgTime、FPS], [衡量单张图像推理耗时和连续处理能力,用于评价推理效率。], table.hline(y: 3, stroke: 0.5pt), [资源指标], [Max CUDA Memory], [统计推理过程中的 GPU 显存峰值,用于分析显存占用变化。], table.hline(y: 4, stroke: 0.5pt), [存储指标], [Model Size], [统计模型权重文件大小,用于评价模型部署和分发成本。], table.hline(y: 5, stroke: 1.5pt), ), caption: [FP16 轻量化实验评价指标说明], kind: table ) ``` ![](https://somnusblog.oss-cn-shanghai.aliyuncs.com/images/20260424141016931.png) ### 引用文献 只要是论文,必然会用引用文献,我们每个人都是站在巨人肩上修修补补,所以typst必然需要有引用文献的功能。😀 在thesis.typ的同目录下会有ref.bib,在其中可以添加参考文献目录。然后再文章中可以通过`@文献名`来引用,注意,不光要创建这样的ref.bib文件,并按照指定格式输入,还需要在thesis.typ中显式的使用它,具体可以看以下: #### 文献参考格式 大概可以按照这样的格式来写 ``` @article{ref02_Hirschmuller2008SGM, //文章中引用就@这个名字 author = {Hirschmuller, Heiko}, title = {Stereo Processing by Semi-Global Matching and Mutual Information}, journal = {IEEE Transactions on Pattern Analysis and Machine Intelligence}, year = {2008}, volume = {30}, number = {2}, pages = {328-341} } @inproceedings{ref03_Scharstein2014Middlebury, author = {Scharstein, Daniel and Hirschmuller, Heiko and Kitajima, Yukihiro and Krathwohl, Greg and Ne{\v{s}}i{\'c}, Nera and Wang, Xi and Westling, Porter}, title = {High-Resolution Stereo Datasets with Subpixel-Accurate Ground Truth}, booktitle = {German Conference on Pattern Recognition (GCPR)}, year = {2014} } ``` #### 全局定义 ``` #let ( functions ) = documentclass( fonts: ( font-settings ), info: ( info.... ), bibliography: bibliography.with("ref.bib"), ) ``` 要在bibliography中改成自己的文献参考集的名字。 #### 文章整体引用 只在前面的全局定义中写完了还不够,还需要在文章主体中使用文献参考集。 ``` #bilingual-bibliography(full: true) ``` 要把这一行放在文末。 之后就可以直接通过@来引用了。 --- 发发发 😠,学校毕业终稿不支持pdf提交,发发发,气死了,最后还要调格式。只有最迂腐最笨的学校才会有这样的规定。 ### Fastgpt初体验1 URL: https://blog.somnus.wiki/p/fastgptchu-ti-yan-1/ Last updated: 2026-07-21T07:57:11.000Z > 最近刚刚入职了某小厂,作为ai实习生,也是开启了自己的牛马生涯。 公司的第一个任务就是做一个电商客服,我在网络上搜罗了一下可能比较成形的方案,最终决定选择使用fastgpt框架调用qwen api+千牛后台。 ## 介绍 fastgpt是是一个ai agent搭建平台,提供了开箱即用的数据处理和模型调用能力,可以方便的本地化部署工作流。而且这个项目好像是中文项目,感觉ai agent领域很多都是中文(比如还有dify)。 ## 工作流介绍 我的智能客服工作流是在他在线方案的模板下,简单改了一下,模板可以通过[这个网站](https://cloud.fastgpt.io/dashboard/templateMarket?ref=blog.somnus.wiki)访问。 下面为流程图 ![](https://somnusblog.oss-cn-shanghai.aliyuncs.com/images/20260414180732203.png) ### 系统配置 设置开场白,如: ``` 您好!欢迎光临云米净水旗舰店!❤️ 我是您的专属顾问。 想要咨询一些什么问题呢 ``` ![](https://somnusblog.oss-cn-shanghai.aliyuncs.com/images/20260414173546121.png) ### 流程开始 用户输入问题string,会直接进入安抚程序。 ### 用户意图解析 通过问题分类将用户的问题string输入到qwen flash,可以对问题进行大致分类,比如分成产品问题售后类,其他类(打招呼,情绪宣泄)。对于不同的问题走不一样的后续流程。 ### 产品品类解析 同样还是通过调用qwen flash,来判断问题有关的产品是什么。方便调用对应的知识库来查询。 ### 知识库查询 知识库使用的embedding模型也是阿里旗下的text-embedding-v4(因为阿里有免费额度 😄) 💯,知识库检索的重排模型使用的是gte-rerank-v2 。 ### 查询融合 因为可能会走不同的知识库查询,为了方便下文中调用,通过这个查询融合模块就可以不用管走哪个数据库,住模型都使用相同的数据库变量。 ### 主模型 根据用户问题和知识库查询结果,输出客服回答。 ## 遇到的问题 - 一开始我虽然知道对于客服这种对回复效率有一定要求的agent,不能使用思考类模型,于是我一开始特地使用的就是带flash(🤗)的模型即qwen3.5-flash,但是还是不行还是会存在思考过程。导致输出非常地慢,会要一分多钟。后来选择了qwen-flash,就没有这个问题了,效果也还可以。 - 我一开始就是白嫖的阿里的免费额度,阿里是每个模型都有独立的免费额度,我原本以为我会先把qwen-flash用完,但是我没想到的,最先用完的是embedding模型(好像据说每次知识库搭建和调用都会使用embedding模型),后来为了方便测试含泪😢爆了10块大洋,后续部署的话希望公司买点api吧。 --- 过了一段时间,我对原本的智能客服进行了迭代升级,因为我们有对问题要求有了更精细的划分,比如:售前,售后;净水类,厨卫类,于是进行了迭代,但是再次不做过多赘述,因为后面我的任务被分配为针对云米净水旗舰店去做这样的ai客服,下面让我继续聊我的工作进展 ## Q:问题汇总 ### failed to upload error 据ai解释好像是因为脚本默认推荐的ip是192.18.0.1,这是一个docker内部虚拟的网桥地址,浏览器和minio没办法访问到,所以在开始设置时可以直接设置自己在局域网的ip,这样的话不太会出现上传不了的现象。 但是很明显,局域网的ip很有可能会变化,因为dhcp协议会自动分配ip地址,如果你可以访问到路由器管理员页面,可以在路由器里面设置静态ip,将ip和设备mac捆绑起来。 但是如果你没办法访问后台的话,可以每次开始工作前,去docker-compose.yml中重新设置一下, 通过以下命令得到ip地址 ``` ip addr show ``` 设置成局域网ip。 ``` STORAGE_EXTERNAL_ENDPOINT: http://192.168.2.190:9000 # 一个服务器和客户端均可访问到存储桶的地址,可以是固定的宿主机 IP 或者域名,注意不要填写成 127.0.0.1 或者 localhost 等本地回环地址(因为容器里无法使用) ``` 之后每次出现重新开始工作如果局域网ip🖇️变了,就执行一下上面的更改,再重新通过当前的ip:3000来访问这个前端网页🕸️。 ### Rust学习01-所有权 URL: https://blog.somnus.wiki/notes/rustxue-xi-01-suo-you-quan/ Last updated: 2026-07-21T07:57:10.000Z ## Trait trait(特征)是一种**定义共享行为**的机制,可以理解成其他语言中的接口,相当于是不管你底层是什么数据类型,只要定义了这个trait,就一定要实现这个trait的功能。 ### 定义和实现 trait本质上是一组方法签名的集合,你可以定义一个trait并为不同的结构体去实现这个trait,代码实例如下 ``` // 1. 定义一个名为 `MakeSound` 的 Trait,规定了必须有一个 `sound` 方法 pub trait MakeSound { fn sound(&self) -> String; } // 2. 定义两个完全不同的结构体 struct Dog; struct Cat; // 3. 为 Dog 实现 `MakeSound` 契约 impl MakeSound for Dog { fn sound(&self) -> String { String::from("汪汪!") } } // 4. 为 Cat 实现 `MakeSound` 契约 impl MakeSound for Cat { fn sound(&self) -> String { String::from("喵喵!") } } ``` ### 主要作用 trait主要的有下面几个特点 - 多态:允许编写处理多种类型的通用代码,你可以写一个函数,不关心她是`Dog`还是`Cat`,只关心传入的类型能否`make_sound`。 ``` // 这个函数接收任何实现了 MakeSound 的类型 fn play_sound(animal: &impl MakeSound) { println!("听到声音: {}", animal.sound()); } ``` - 泛型约束:trait可以来限制泛型能够接受的类型。例如``表示T可以是任何类型,但是必须实现了MakeSound这个trait。 - 代码复用:trait中的方法可以有默认的实现逻辑,如果某个类型实现了trait但是没有重写方法,则会自动获得默认行为。 ### rust内置的trait rust极其依赖trait,这也是rust设计优雅的原因,有很多理所当然的行为,实际上都是trait: - Debug和Display:可以使用`println("{:?}",x)`来打印变量。 - Clone和Copy:定义了数据的复制 - Add和Sub:定义了`+`和`-`如何对*非基础类型*进行数学运算 rust的trait还符合**开闭原则**,即倘若要新增一个动物,无需重写`playsound`这个函数,而是直接新建一个动物结构体,并且为他`impl MakeSound`就可以了。 还体现了职责分离这一特点,MakeSound只负责发出声音,动物的逻辑只在动物的部分内,不同的模块就被安全的隔开了。 --- ### dyn ### Vaultwarden使用教程 URL: https://blog.somnus.wiki/p/vaultwardenshi-yong-jiao-cheng/ Last updated: 2026-07-21T07:57:13.000Z > 最近正在因为换了浏览器,进行过一次密码迁移,在迁移的过程中总感觉有种隐忧,就是密码泄露或者丢失的风险 其实密码泄露给这些大型的公司比如Microsoft/Google,其实对我没影响,但是如果片丢失了,就影响大了,因为我有很多的网络站点都是使用浏览器生成的强密码,此前很多密码管理工具也出现过数据丢失的问题。 所以我想着自己搭建属于自己的服务来实现,更安全(但不一定更可靠)的密码服务。 ## 调研 我简单调查了市面上所有的密码管理器,主要分为两部分: - a、云服务:除了依赖浏览器厂商,还有一些专业的密码服务厂商,比如被广泛使用的1password和以隐私保护著称的proton pass,但是这两个的服务都不是免费的,而且都不便宜,国内的连接也不够稳定。 - b、自搭建服务:使用大名鼎鼎的bitwarden或者keepassxc,后者很安全,因为数据文件只能保存在本地,但问题也就是他没有任何的云服务,非常不方便。 最终我决定使用vaultwarden,一个基于bitwarden的rust重写版(rust信徒狂喜),使用我之前的买的一个火山云的服务器2g2核,足够运行。当时这个服务器还是用来跑openclaw的,后来没弄成,就闲置下来了。 ## 部署 ### 部署要求 - 云服务器:由于rust底层,导致占用很低,2gb2核绝对够用 - 域名:需要一个域名,因为需要ios自动填充和chrome插件都需要HTTPS环境,所以需要把域名解析到我的个人服务器上,并且需要SSL证书。 - 环境要求:服务器最好有docker环境 ### 部署过程 #### 配置docker 这是第一步,对于国内来说也是最难的一步,因为服务器不同于个人网络,不可以部署vpn服务,所以要配置要准备好可以连接的docker是最麻烦的,首先是docker安装,可以在部署服务器的时候,可以选择自带并且安装好docker的环境。对于不同的服务器厂商,可以直接搜各自[指定的文档](https://www.volcengine.com/docs/6396/75300?lang=zh&ref=blog.somnus.wiki)来安装docker。 安装docker,通常不会出现问题,问题主要出现在拉取镜像上,网上对于这方面的解决措施纷繁复杂,什么配置镜像源,什么本地pull再传到服务器上,我这里之给出一种解决我的问题的方法 - 1ms:一毫秒,是一个国内可以访问的docker代理网站 ``` docker pull docker.1ms.run/vaultwarden/server:latest docker pull docker.1ms.run/library/caddy:2 ``` 此处下载了两个镜像,一个是vaultwarden本地,另一个是caddy,用来反向代理的 #### 安装环境 首先创建文件夹 ``` mkdir -p /home/somnus/vaultwarden cd /home/somnus/vaultwarden ``` 接着就是创建Caddyfile,他是用来全自动申请和续签Let’s Encrypt 的 HTTPS 证书,完全不需要手动配置。 ``` nano Caddyfile ``` 并输入以下内容 ``` pwd.yourdomain.com { #此处替代为自己的域名 reverse_proxy vaultwarden:80 } ``` 并保存退出 #### 创建docker-compose.yaml 创建该yaml文件 ``` nano docker-compose.yaml ``` 并且输入以下内容 ``` version: '3' services: vaultwarden: image: vaultwarden/server:latest container_name: vaultwarden restart: always environment: - WEBSOCKET_ENABLED=true - SIGNUPS_ALLOWED=true # 第一次启动设为 true,注册完后改为 false volumes: - ./vw-data:/data caddy: image: caddy:2 container_name: caddy restart: always ports: - 80:80 # 必须映射 80 和 443 端口 - 443:443 volumes: - ./Caddyfile:/etc/caddy/Caddyfile:ro - ./caddy-data:/data - ./caddy-config:/config depends_on: - vaultwarden ``` 保存并退出,执行以下命令 ``` sudo docker compose up -d ``` 此时如果一切正常,你在访问自己的域名的时候就能进入vaultwarden的登陆界面了。 ### 备案 如果你选择的是国内的运营商的服务器,那么不可避免的你都需要进行备案,可以直接按照对应公司的备案流程来。 ![](https://somnusblog.oss-cn-shanghai.aliyuncs.com/images/20260401181254590.png) 备案时间真的很长,至少需要一周左右。所以现在都再等待备案通过。 --- 过了大概三天,工信部的备案就通过了,再过不到24hour,就可以直接访问这个网站了。 网站页面如下: ![](https://somnusblog.oss-cn-shanghai.aliyuncs.com/images/20260403222851908.png) 点击下方的注册,创建账号,输入邮箱和主密码,**注意注意注意**,这个主密码一定要记住,一定要存在一个你记得的地方。因为主密码忘记之后无法恢复。 注册完账号之后就可以登陆并导入浏览器的密码本。 ### 260328 URL: https://blog.somnus.wiki/p/secret-111-share/ Last updated: 2026-07-21T07:57:12.000Z ## 一、轻量化部署方向工作总结 ### 1、研究动机 在前期实验中,基于 RAFT-Stereo 的 refinement head、边缘感知损失以及 partial unfreeze 等改进方法,并未在 Middlebury 数据集上取得稳定、显著的精度提升,说明原始 `raftstereo-middlebury` 模型本身已经具有较强性能,小规模真实域数据下难以通过小幅结构改动进一步提升整体指标。 因此,后续工作将研究重点由“继续提升精度”转向“在尽量保持精度的前提下降低模型部署开销”,即探索 RAFT-Stereo 在边缘设备上的轻量化部署方案。 --- ### 2、轻量化研究思路 本阶段主要围绕以下两个方向展开: #### (1)、官方轻量模型对比:realtime 版本 首先,选择官方提供的 `raftstereo-realtime.pth` 作为轻量化基线模型,与高精度的 `raftstereo-middlebury.pth` 进行对比。该模型通过减少网络规模、降低迭代次数、共享部分骨干结构等方式提升实时性,目标是在更低资源消耗下完成双目匹配任务。 这一实验的目的在于验证:**直接采用官方轻量模型,是否能够在保证可接受精度的前提下实现部署端优化。** #### (2)、半精度部署:FP16 在发现 realtime 模型精度下降较明显后,进一步尝试 **FP16 半精度部署方案**。该方案不改变原始 RAFT-Stereo 网络结构,也不重新训练模型,而是直接将高精度的 `raftstereo-middlebury.pth` 转换为半精度权重模型,并在推理与评测阶段采用 FP16 进行部署测试。 这一方案的核心思想是: - 保留原始高精度模型结构; - 避免重新训练带来的额外成本; - 通过降低数值精度减少模型文件大小、显存占用和推理时间; - 在尽量保持原始精度的前提下,实现更适合边缘设备的部署形式。 --- ### 3、实验设置 #### (1)、模型与环境 | 项目 | 设置 | | ----------- | ------------------------------------------------- | | 高精度基线模型 | RAFT-Stereo (raftstereo-middlebury.pth) | | 轻量化对照模型 | RAFT-Stereo Realtime (raftstereo-realtime.pth) | | 半精度模型 | RAFT-Stereo FP16 (raftstereo-middlebury-fp16.pth) | | 推理设备 | GPU:NVIDIA 4090 | | correlation | reg | #### (2)、对比方案 本阶段主要设置了三组对比: - **Baseline FP32**:原始 `raftstereo-middlebury.pth` - **Realtime**:官方轻量化实时模型 `raftstereo-realtime.pth` - **FP16**:由 `raftstereo-middlebury.pth` 转换得到的半精度模型 `raftstereo-middlebury-fp16.pth` #### (3)、评价维度 为了从部署角度评价模型轻量化效果,本阶段不仅关注匹配精度,还额外统计了以下指标: - **EPE**:平均像素误差 - **D1**:离群点比例 - **模型大小**:模型文件存储开销 - **Average Inference Time**:平均推理时间 - **FPS**:每秒处理帧数 - **Max CUDA Memory Allocated**:推理阶段显存占用峰值 --- ### 4、实验结果 #### (1)、官方 realtime 模型与高精度模型对比 | 模型 | 参数量 | EPE | D1 | | ---------------------- | ------ | ------- | ------- | | RAFT-Stereo Middlebury | 11.14M | 2.3758 | 12.0526 | | RAFT-Stereo Realtime | 9.89M | 11.1557 | 28.5460 | 从结果可以看出,官方 realtime 模型参数量相较高精度 Middlebury 模型有所减少,但在 Middlebury2014 数据集上的精度下降非常明显: - EPE 由 **2.3758** 上升到 **11.1557** - D1 由 **12.0526** 上升到 **28.5460** 这说明:**直接采用官方轻量模型虽然能够降低模型规模,但会带来较大的精度损失,不适合作为当前任务的最终部署方案。** --- #### (2)、FP16 半精度部署结果 | 模型 | EPE | D1 | 平均推理时间 | FPS | 最大显存占用 | 模型大小 | | ------------- | ------ | ------- | --------- | ---- | --------- | ----- | | Baseline FP32 | 2.3758 | 12.0526 | 209.80 ms | 4.77 | 844.91 MB | 43 MB | | FP16 | 2.3535 | 12.0593 | 173.59 ms | 5.76 | 662.34 MB | 22 MB | 进一步计算可得: - **模型大小**:43 MB → 22 MB,下降约 **48.8%** - **显存占用**:844.91 MB → 662.34 MB,下降约 **21.6%** - **平均推理时间**:209.80 ms → 173.59 ms,缩短约 **17.3%** - **FPS**:4.77 → 5.76,提升约 **20.8%** 而在精度方面: - **EPE**:2.3758 → 2.3535,基本保持一致 - **D1**:12.0526 → 12.0593,几乎不变 这说明:**将高精度 Middlebury 模型直接转换为 FP16 半精度模型后,模型精度基本保持稳定,同时明显降低了模型存储、显存占用和推理时间。** | 原图 | baseline | fp16 | | ------------------------------------------------------------------- | -------------------------------------------------------------------------------- | -------------------------------------------------------------------------------- | | ![](https://somnusblog.oss-cn-shanghai.aliyuncs.com/images/im0.png) | ![](https://somnusblog.oss-cn-shanghai.aliyuncs.com/images/Bicycle1-perfect.png) | ![](https://somnusblog.oss-cn-shanghai.aliyuncs.com/images/Bicycle1-perfect.png) | --- ### 5、结果分析 通过本阶段实验,可以得到如下分析: #### a、官方 realtime 轻量模型存在明显精度代价 虽然 realtime 模型在参数量上比高精度 Middlebury 模型更小,但在 Middlebury2014 数据集上的 EPE 和 D1 均大幅恶化,说明这种结构级轻量化方式是以显著牺牲匹配精度为代价实现的。 #### b、FP16 半精度部署是一种更稳妥的轻量化方式 与直接更换轻量模型不同,FP16 方案不改变原始网络结构,因此保留了高精度模型已经学习到的匹配能力。实验表明,在几乎不损失精度的情况下,FP16 能够显著减小模型文件体积,并降低显存和时间开销。 #### c、对于当前任务,部署优化比结构替换更合适 结合前面对 refinement 系列方法和 realtime 模型的实验可以看出,对于 Middlebury 这种偏高精度需求的双目匹配任务,直接替换为更小的轻量结构往往会带来较大精度损失;相比之下,在保持主模型结构不变的基础上做半精度部署,更适合作为当前阶段的轻量化方案。 --- ### 6、实验结论 通过本次轻量化部署实验,可以得到以下结论: - 官方 `raftstereo-realtime` 模型虽然在参数规模上更小,但在 Middlebury2014 数据集上的精度下降明显,难以满足高精度双目匹配任务需求; - 基于 `raftstereo-middlebury.pth` 的 **FP16 半精度部署方案**,在不改变原始模型结构、也不重新训练的前提下,实现了模型体积、显存占用和推理时间的有效下降; - FP16 模型在 EPE 和 D1 指标上与原始 FP32 模型基本保持一致,说明其能够在较好保留模型性能的同时降低部署开销; - 相较于直接采用官方轻量化结构,**FP16 半精度部署是一种更适合当前任务场景的轻量化方法**; - 该方法工程实现成本较低,部署路径清晰,具有进一步面向边缘设备应用的可行性。 --- ### 7、后续规划 后续可以继续围绕以下方向展开: - 在 FP16 部署基础上,进一步补充不同输入分辨率下的推理速度与显存测试; - 尝试在 Jetson 等边缘硬件平台上验证实际部署效果; - 进一步探索剪枝、蒸馏等轻量化方法,与 FP16 方案形成对比; - 在保持精度的前提下,研究更适合嵌入式或低功耗场景的双目匹配部署策略。 ### Eulerproj01 URL: https://blog.somnus.wiki/notes/eulerproj01/ Last updated: 2026-07-21T07:57:08.000Z ### p002 #### 题目 Problem 2: Even Fibonacci numbers [题目链接](https://projecteuler.net/problem=2?ref=blog.somnus.wiki) Each new term in the Fibonacci sequence is generated by adding the previous two terms. By starting with $1$ and $2$, the first $10$ terms will be: $$1, 2, 3, 5, 8, 13, 21, 34, 55, 89, \\dots$$ By considering the terms in the Fibonacci sequence whose values do not exceed four million, find the sum of the even-valued terms. 这道题目的要求是算出Fibonacci数列的前4000000个数字中的偶数之和。 #### 解答 这题如果直接做,可以直接根据Fibonacci数列的规律,直接while循环判断。 $$ F(n) = F(n-1)+F(n-2) $$ 代码如下 ``` fn solve2() -> u64 { let mut a = 1; let mut b = 2; let mut sum = 0; while a <= 4_000_000 { if a % 2 == 0 { sum += a; } let next = a + b; a = b; b = next; } sum } ``` 很明显,这样是能算到最终结果的,最终的结果就是4613732,但这个太暴力很明显不值得我特地写一篇博客。 此时,我们好好回顾一下这个问题,要求一个fibonacci数列的所有偶数,我们在写这个数列的时候会发现数列中的偶数是隔着两个奇数就有一个偶数,如果把所有偶数剔出来,会发现满足一下的规律。 $$ F(n) = 4\*F(n-1) + F(n-2) $$ 自此可以看出来时间复杂度从O(n)->O(n/3)。 rust代码如下 ``` fn solve1() -> u64 { let mut a = 2; let mut b = 8; let mut sum = 0; while a <= 4_000_000 { sum += a; let next = 4 * b + a; a = b; b = next; } sum } ``` 计算时间上面呢也就减少了20ns,但是这样的降低时间复杂度的操作还是很有意义的。从中我们也可以得到启发,当程序无法进行优化时,可以从**数学的角度**入手。下面是运行时间对比图片(我把测试增加了)。 ![](https://somnusblog.oss-cn-shanghai.aliyuncs.com/images/20260329211758117.png) ### p004 #### 题目 Problem 4: Largest Palindrome Product [题目链接](https://projecteuler.net/problem=4?ref=blog.somnus.wiki) 题目描述: A palindromic number reads the same both ways. The largest palindrome made from the product of two $2$-digit numbers is $9009 = 91 \\times 99$. 问题: Find the largest palindrome made from the product of two $3$-digit numbers. #### 解答a 同样,这题可以直接设定i和j代表两个数。直接开始循环,然后通过一个函数判断乘积是否是回文数。代码如下: ``` fn is_palindrome(n: u64) -> bool { let s = n.to_string(); s.chars().eq(s.chars().rev()) } fn baolisolve() -> u64 { let mut max_palindrome = 0; for i in 100..999 { for j in 100..999 { let x = i * j; if is_palindrome(x) && x >= max_palindrome { max_palindrome = x; } } } max_palindrome } ``` 最后得到的计算时间也是相当可观了(计算时间很长) ![](https://somnusblog.oss-cn-shanghai.aliyuncs.com/images/20260329230307618.png) #### 解答b 此时可以观察到,要求出最大的回文数,往往两个数字都需要大概900多的样子,我们可以直接从后向前循环,这样的话时间就能减少很多。 ![](https://somnusblog.oss-cn-shanghai.aliyuncs.com/images/20260329230525808.png) #### 解答c 首先引入一个结论,任何一个偶数位数的回文数都可以被11整除。在本题目中,要求最大的六位回文数,则该回文数必然有一个因数也可以被11整除。于是便可以减少部分循环次数,进一步降低时间消耗。 代码如下 ``` fn mathsolve() -> u64 { let mut max_palindrome = 0; for i in (100..999).rev() { let start = if i % 11 == 0 { 999 } else { 990 }; let step = if i % 11 == 0 { 1 } else { 11 }; let mut j = start; while j >= 100 { let n = i * j; if n <= max_palindrome { break; } if is_palindrome(n) { max_palindrome = n; } j -= step; } } max_palindrome } ``` 最后可以看到消耗时间被降低到了微秒级别 ![](https://somnusblog.oss-cn-shanghai.aliyuncs.com/images/20260329230845620.png) 简单证明一下上面这个结论。 设一个 6 位回文数写成: \[ N = \\overline{abccba} \] 其中 (a,b,c) 都是数字,且 (a \\neq 0)。 把它按十进制展开: \[ N = 100000a + 10000b + 1000c + 100c + 10b + a \] 整理同类项可得: \[ N = 100001a + 10010b + 1100c \] 接下来把每一项提取出 11: \[ 100001 = 11 \\times 9091 \] \[ 10010 = 11 \\times 910 \] \[ 1100 = 11 \\times 100 \] 所以: \[ N = 11(9091a + 910b + 100c) \] 因此,(N) 一定能被 11 整除。 任意 6 位回文数都可以写成 11 的倍数,因此: \[ \\boxed{\\text{任意 6 位回文数一定能被 11 整除}} \] 数学博大精深~~! ### p005 #### 题目 Problem 5: Smallest Multiple [题目链接](https://projecteuler.net/problem=5?ref=blog.somnus.wiki) 题目描述: $2520$ is the smallest number that can be divided by each of the numbers from $1$ to $10$ without any remainder. 问题: What is the smallest positive number that is **evenly divisibledivisible with no remainder** by all of the numbers from $1$ to $20$? #### 解答 这题需要求出1-20所有数字的lcm(最小公倍数)。 $$ lcm(a,b) = \\frac{a \* b}{gcd(a , b)} $$ 代码如下 ``` fn gcd(mut a: u64, mut b: u64) -> u64 { while b != 0 { let temp = b; b = a % b; a = temp; } a } fn lcm(a: u64, b: u64) -> u64 { a / gcd(a, b) * b } fn solve() -> u64 { (1..20).reduce(|acc, c| lcm(acc, c)).unwrap() } ``` ![](https://somnusblog.oss-cn-shanghai.aliyuncs.com/images/20260330142434183.png) ### Raft训练日志2 URL: https://blog.somnus.wiki/p/raftxun-lian-ri-zhi-2/ Last updated: 2026-07-21T07:57:11.000Z ## 前情提要 我上一个训练日志中,尝试对RAFT-Stereo模型进行优化,通过添加refinement层和边缘损失感知,然后训练完的模型的EPE和D1几乎没有产生变化。因为这个层结构对整个模型来说都没什么影响。接着我在改完的这个模型后,对模型进行了partial unfreezement,然后在此基础上对middlebury数据集进行训练。最后得到的结果EPE和D1反而变差了,这个大概率是因为我本地的数据集太小,在此基础上进行训练会很容易导致过拟合的现象。自此,我便将研究重心转移到模型轻量化方面来。 --- ## 模型设计 首先,我先去官方库上找到了不同版本模型的[下载链接](https://drive.google.com/drive/folders/1booUFYEXmsdombVuglatP0nZXb5qI89J?ref=blog.somnus.wiki)。我原本一直都是使用他的middlebury版本,因为我本地的数据集也是middelbury,得到的效果也很好,然后我注意到除了middlebury,以外还有两个以数据集命名的预训练模型eth3d和sceneflow。还有一个模型就是raftstereo-realtime.pt,我后来调查得到,这个模型就是官方训练出来的**轻量化版本**,然,我就想着先拿这个模型测试一下。 除此以外,我还想了通过转换精度来实现轻量化,具体就是不改变模型原始网络结构,也不用重新训练模型,将原本的**Float32转换成Float16**,这样便可以大幅度降低模型的大小和显存占用,对于低显存的边缘设备,能提升帧率和处理速度。以下为处理代码,处理逻辑实际上`value.half()`这一句。 ``` def convert_to_fp16(input_path, output_path): print(f"Reading original checkpoint from: {input_path}") state_dict = torch.load(input_path, map_location='cpu') new_state_dict = {} for key, value in state_dict.items(): if isinstance(value, torch.Tensor) and value.is_floating_point(): new_state_dict[key] = value.half() # Convert to float16 else: new_state_dict[key] = value # Keep non-floating-point parameters unchanged print(f"Saving FP16 checkpoint to: {output_path}") torch.save(new_state_dict, output_path) print("Conversion completed successfully!") ``` 这一方案的核心思想就是: - 保留原始高精度模型结构。 - 避免重新训练带来的额外成本。 --- ## 训练结果 ### 官方 realtime 模型与高精度模型对比 | 模型 | 参数量 | EPE | D1 | | ---------------------- | ------ | ------- | ------- | | RAFT-Stereo Middlebury | 11.14M | 2.3758 | 12.0526 | | RAFT-Stereo Realtime | 9.89M | 11.1557 | 28.5460 | 从结果可以看出,官方 realtime 模型参数量相较高精度 Middlebury 模型有所减少,但在 Middlebury2014 数据集上的精度下降非常明显: - EPE 由 **2.3758** 上升到 **11.1557** - D1 由 **12.0526** 上升到 **28.5460** 这说明:**直接采用官方轻量模型虽然能够降低模型规模,但会带来较大的精度损失,不适合作为当前任务的最终部署方案。** ### FP16 半精度部署结果 | 模型 | EPE | D1 | 平均推理时间 | FPS | 最大显存占用 | 模型大小 | | ------------- | ------ | ------- | --------- | ---- | --------- | ----- | | Baseline FP32 | 2.3758 | 12.0526 | 209.80 ms | 4.77 | 844.91 MB | 43 MB | | FP16 | 2.3535 | 12.0593 | 173.59 ms | 5.76 | 662.34 MB | 22 MB | 进一步计算可得: - **模型大小**:43 MB → 22 MB,下降约 **48.8%** - **显存占用**:844.91 MB → 662.34 MB,下降约 **21.6%** - **平均推理时间**:209.80 ms → 173.59 ms,缩短约 **17.3%** - **FPS**:4.77 → 5.76,提升约 **20.8%** 而在精度方面: - **EPE**:2.3758 → 2.3535,基本保持一致 - **D1**:12.0526 → 12.0593,几乎不变 这说明:**将高精度 Middlebury 模型直接转换为 FP16 半精度模型后,模型精度基本保持稳定,同时明显降低了模型存储、显存占用和推理时间。** ## 小结 虽然我做出的改进非常小,但是得到的效果我觉得还是可以的,应该是足够支撑去作为本科毕业设计的成果的,但不知道胡老师会怎么看,希望能美美通关。 ### Ml Launcher开发 URL: https://blog.somnus.wiki/p/ml-launcherkai-fa/ Last updated: 2026-07-21T07:57:12.000Z > 最近做一些模型训练的项目,每次在训练的时候,长长的后续参数都让人很头大(每次都要问a:怎么让这个项目跑起来),于是我想是否可以弄一个项目,方便的设置参数,便有了这个项目。 ## 简单介绍 首先,为了保证模型训练的时候不能产生太多的内存和性能消耗,所以这个项目使用了rust来编写,实测这个项目的后台占用大概在20MB左右,几乎不会对模型训练产生影响。 其次,这个项目为了方便在终端运行,所以没有选用带GUI的前端,而是直接使用rust的终端显示库,ratatui,实际体验下来,这个库还是非常好用的,无论是api调用的**方便性**,还是**便捷内存占用**都是十分出色,非常适合我这个项目。 ## install 这个工具我已经上传crate.io,所以可以直接通过cargo install(没有安装rust环境的可以去装了:LOL ) ``` cargo install ml-launcher ``` ## 使用 下载到本地后会自动安装到bin目录,可以直接在终端中调用。 ``` ml-launcher ``` 之后就可以打开这样的界面 ![](https://somnusblog.oss-cn-shanghai.aliyuncs.com/images/20260327230007572.png) 按下p可以编辑想要识别python 参数的位置,按下c可以设置config.json的位置(这个一般可以通过进入程序后save config获取到) 随意选择一个进入方式,便进入了这样的界面 首先,第一个table,Parameters:就是主界面,用来对每个参数(可以选择部分参数)设置对应的值,同时右边也可以看见该参数需要的类型和描述。下方的一排是在当前table可以使用的快捷键。 ![](https://somnusblog.oss-cn-shanghai.aliyuncs.com/images/20260327230351888.png) 其次,第二个table,filter:可以通过选择某个参数按t,来改变是否需要手动设置值(因为在大部分情况下,很多参数是不需要手动输入的,直接使用默认值就行)左边的Filter可以进行滚动显示。 ![](https://somnusblog.oss-cn-shanghai.aliyuncs.com/images/20260327231856599.png) 最后,第三个table,actions:主要有下面这些可以执行的动作(当前版本直接run code好像是有问题的,而且不能选择conda环境或者uv环境中的python,所以建议复制指令使用)。 ![](https://somnusblog.oss-cn-shanghai.aliyuncs.com/images/20260327231020211.png) ## 代码结构讲解 当前版本是“0.1.0“,所以后续如果有改动,该逻辑可能不准 ### modeselector 首先,刚进入程序会进入到模式选择,ModelSelector这个结构体是由以下元素组成的 ``` pub struct ModeSelector { // 主菜单状态 selected: usize, //作为选择的标志位。 // 路径输入 script_path: String, //输入的py文件地址,默认为当前目录下 config_path: String, editing_script: bool, //设置一个bool值,方便表明该参数处于修改状态,会有不一样的颜色显示 editing_config: bool, // 错误信息 error_msg: String, // 退出标志 should_quit: bool, // 继承配置对话框 show_inherit_dialog: bool, inherit_selected: usize, inherit_config_path: String, editing_inherit_path: bool, // 合并结果展示 show_merge_result: bool, pending_merge_result: Option, pending_new_config: Option, } ``` 此处特地增加了一个should\_quit的标志位,因为在没有这个标志位的时候,可能会因为两种不同的退出方式而产生冲突的bug。 ratatui设计的程序主要还有一个逻辑就是需要handle不同界面上的按键,分别有handle\_main\_menu\_key,handle\_inherit\_dialog\_key,handle\_merge\_result\_key,handle\_editing\_script\_key,handle\_editing\_config\_key,handle\_editing\_inherit\_path\_key,分别对应不同的界面。 这个是第一部分的流程图。 ![](https://somnusblog.oss-cn-shanghai.aliyuncs.com/images/ml-launcher-modelselector.drawio.png) ### 主程序 如上面所介绍,主要分成三个table,其中,比较值得一说的是,主要有四个输入模式, ``` #[derive(Default, Clone, PartialEq)] pub enum InputMode { #[default] Normal, Editing, SaveDialog, LoadDialog, } ``` 对于每个输入模式也有各自handle input的逻辑。每个handle input逻辑都需要处理不同按键和不同选择下的enter键。 每个参数都会有一个usedefault的布尔值,如果为ture,则不会再parameter中显示出来,反之则会显示,并且可以编辑对应的值。 ``` let active_params: Vec<(usize, &ParamField)> = self .config .params .iter() .enumerate() .filter(|(_, p)| !p.use_default) .collect(); ``` 原本因为一个程序文件的总参数个数太多了,导致正常的终端的filter table根本没办法全部显示完,所以在这里改用了ListState来处理滚动显示逻辑。 ``` let mut state = ListState::default(); state.select(Some(self.selected_param)); ``` ## 小结 哎哎哎哎,其实还是有挺多话之的说的,但是不太想写了,我最近再做另一个todolist的app,希望那个能纯手搓出来,而不要用vide coding,hhhhhh,加油吧~~~。 ### Tmux使用教程 URL: https://blog.somnus.wiki/p/tmuxshi-yong-jiao-cheng/ Last updated: 2026-07-21T07:57:13.000Z > 为什么用tmux:首先,因为我最近换了一个终端模拟器alacritty,这个模拟器不支持原生的分屏,需要通过tmux辅助。别问为什么非要用alacritty而不是自带分屏的ghostty,因为一个是rust一个是zig。 另外,我在使用服务器训练raft视觉模型的时候,偶尔因为网络断开连接,导致远程ssh关闭,自动的结束了训练,虽然我知道了可以使用nohup 在>输出到log文件中,但是还是觉得如果可以用tmux这种会更方便。 ## tmux简介 为了解决上面提到的很常见的问题(关闭终端窗口&断开连接会导致任务(会话)终止),tmux的任务就是把session和窗口解除了绑定。 因此,tmux有三层结构,这样既解决了上面的问题,也推出了方便的分屏功能。 ``` session(会话) └── window(窗口) └── pane(面板/分屏) ``` ![](https://somnusblog.oss-cn-shanghai.aliyuncs.com/images/20260324213922252.png) 上面这个例子中就是在seesion:s1中的window:0。 ## tmux安装 ### 安装本体 ``` #Ubuntu & Debian sudo apt install tmux #CentOS & Fedora sudo yum install tmux # Mac brew install tmux ``` ### 安装tpm(tmux插件管理工具) ``` git clone https://github.com/tmux-plugins/tpm ~/.tmux/plugins/tpm ``` ### 配置conf文件 ``` nano ~/.tmux.conf ``` ### 写入配置 ``` # ========================= # 基础配置 # ========================= set -g prefix C-a unbind C-b bind C-a send-prefix set -g mouse on # ========================= # 插件管理 # ========================= set -g @plugin 'tmux-plugins/tpm' # Catppuccin主题 set -g @plugin 'catppuccin/tmux' # 可选:增强体验插件 set -g @plugin 'tmux-plugins/tmux-sensible' # ========================= # Catppuccin配置(可选优化) # ========================= set -g @catppuccin_flavour 'mocha' # latte / frappe / macchiato / mocha # 状态栏位置 set -g status-position bottom # ========================= # 初始化 TPM(必须放最后) # ========================= run '~/.tmux/plugins/tpm/tpm' ``` ### 安装插件 进入tmux`tmux`,然后`Ctrl + a ->Shift + I` - 等待自动安装 - 大写I ### 加载配置 ``` tmux source-file ~/.tmux.conf ``` ## tmux使用 ### 🟢 会话(session) ``` tmux new -s name # 创建会话 tmux ls # 列出会话 tmux attach -t name # 进入会话 tmux kill-session -t name # 删除会话 tmux rename-session -t old new ``` ### 🔵 基本快捷键(前缀 Ctrl + a) ``` Ctrl + a d # detach(后台运行) Ctrl + a r # 重载配置(需绑定) Ctrl + a : # 命令模式 ``` ### 🟡 窗口(window) ``` Ctrl + a c # 新建窗口 Ctrl + a n # 下一个窗口 Ctrl + a p # 上一个窗口 Ctrl + a 数字 # 切换窗口 Ctrl + a , # 重命名窗口 Ctrl + a w # 列出窗口 Ctrl + a & # 关闭窗口 ``` ### 🟣 分屏(pane) ``` Ctrl + a % # 垂直分屏 Ctrl + a " # 水平分屏 Ctrl + a 方向键 # 切换 pane Ctrl + a x # 关闭 pane Ctrl + a z # 最大化/还原 pane ``` ### 🟠 调整大小 ``` Ctrl + a Ctrl + ←/→/↑/↓ # 调整 pane 大小 ``` ### 🟤 布局 ``` Ctrl + a 空格 # 切换布局 Ctrl + a Alt + 1 # even-horizontal Ctrl + a Alt + 2 # even-vertical Ctrl + a Alt + 3 # main-horizontal Ctrl + a Alt + 4 # main-vertical ``` ### ⚫ 复制模式(vi) ``` Ctrl + a [ # 进入 copy-mode v # 开始选择 y # 复制 q # 退出 ``` ### 🎯 一句话速记 ``` session:tmux new -s 退出不关:Ctrl+a d 分屏:% 和 " 切换:方向键 ``` ### Zsh开箱即用 URL: https://blog.somnus.wiki/p/zshkai-xiang-ji-yong/ Last updated: 2026-07-21T07:57:13.000Z > why zsh: 本人长期使用bash,其实早有耳闻zsh的方便快捷,之前黄学长给我的使用的服务器里面就配置了zsh,当时感觉自动补齐功能很惊艳,但是受限于我原本在机器人队打工,很多网上开源雷达点云算法(pointlio,fastlio)都需要通过source setup.bash来配置环境,如果用zsh可能会有些问题,但最近很高兴去尝试一些新的东西,于是就有了这篇博客 ## zsh的特点 - zsh,即z-shell,是在bourne-shell的基础上,作出许多改进的一种**命令解释器**,其实就是shell就是linux或者类linux架构中,起到和kernel内核传达指令的“**壳**”(shell)。 zsh是保罗(Paul Falstad)在普林斯顿大学求学期间编写的,当时只是初版,而zsh的名字是来源于他的一个老师邵中(zhong shao),保罗把他老师的名字zsh作为了shell的名称。 此后,zsh一直作为bash替代,不稳不火。2019年,由于macos上的bash版本太旧了,但是新版的bash采用GPLv3授权,这让苹果无法接受,自此苹果改成了zsh作为默认shell(依旧果子重拳出击)。 zsh有着更好的扩展性,同时可以定制更多的主题,让平平无奇的终端变得赏心悦目。 - oh-my-zsh:一个免费好用,非常好用的**开源zsh框架**,由开源社区推动,现在已经有了很多非常好用的插件。比如git,zsh-autosuggestions,建议每一个人都无脑使用oh-my-zsh。 - alacritty:一个使用rust编写基于opengl的终端模拟器,**快、轻便、跨平台**是他的特点,他相对于ghostty的缺点就是不能支持原生分屏,但这一点可以通过tmux弥补。其他全是优点。 ## 安装zsh和oh-my-zsh ### 下载 在ubuntu安装zsh可以直接用apt来安装 ``` sudo apt update sudo apt install -y zsh git curl #顺便检查一下git和curl的安装 ``` 在archlinux系列可以通过pacman安装 ``` sudo pacman -S zsh ``` 安装完可以检查一下 ``` which zsh zsh --verstion ``` ### 配置 如果有需要,可以把zsh设置为默认的shell 通过执行这个命令可以完成设置 ``` chsh -s "$(which zsh)" ``` 会要求你输入密码,然后退出重新打开终端,通过下面这个指令来确认是否切换成功。 ``` echo $SHELL ``` 如果改完之后还是显示是原始的shell,bash或者fish,建议重启一下。 ### 安装oh-my-zsh 直接用[官方仓库](https://github.com/ohmyzsh/ohmyzsh?ref=blog.somnus.wiki)(omz竟然有185.6k stars)给的脚本来安装。 ``` sh -c "$(curl -fsSL https://raw.githubusercontent.com/ohmyzsh/ohmyzsh/master/tools/install.sh)" ``` 然后可以使用下面的指令来安装两个最常用的插件 ``` git clone https://github.com/zsh-users/zsh-autosuggestions \ ${ZSH_CUSTOM:-~/.oh-my-zsh/custom}/plugins/zsh-autosuggestions git clone https://github.com/zsh-users/zsh-syntax-highlighting.git \ ${ZSH_CUSTOM:-~/.oh-my-zsh/custom}/plugins/zsh-syntax-highlighting ``` clone完之后,还要去\~/.zshrc中把plugins后面加上zsh-autosuggestions和zsh-syntax-highlighting,如下: ``` plugins=( git zsh-autosuggestions zsh-syntax-highlighting ) ``` 还要执行一下下面这两行,这两行是用来是zsh的自动补齐生效的。 ``` autoload -Uz compinit compinit ``` ### 安装[starship](https://github.com/starship/starship?ref=blog.somnus.wiki)(可选) 这是一个终端美化工具,如果你本地有rust和cargo环境,可以直接用cargo来安装(如果没有rust,建议尝试一下安装rust,你不会后悔的)。 ``` cargo install starship --locked ``` 如果没有也不用着急,starship官方还给了脚本安装的方式,`curl -sS https://starship.rs/install.sh | sh` [这个网址](https://starship.rs/presets/?ref=blog.somnus.wiki)里面有很多别人配置好的预设,你如果有兴趣也可以自己配置。直接把别人的预设复制粘贴到下面这个toml文件内。 ``` mkdir -p ~/.config nano ~/.config/starship.toml ``` 最后可以通过`source ~/.zshrc`来让自己的配置生效。 ### 安装主题: 如果厌倦了原始简洁主题,可以尝试使用一下这个[powerlevel10k](https://github.com/romkatv/powerlevel10k?ref=blog.somnus.wiki),很好看的。安装方法是还是推荐clone到oh my zsh的目录, ``` git clone --depth=1 https://github.com/romkatv/powerlevel10k.git ${ZSH_CUSTOM:-$HOME/.oh-my-zsh/custom}/themes/powerlevel10k ``` 然后在.zshrc中找到ZSH\_THEME那一行,改成这个 ``` ZSH_THEME="powerlevel10k/powerlevel10k" ``` 然后再source ``` source ~/.zshrc ``` 此时就会进入p10k的配置向导,如果没有自动进入可以手动进入 ``` p10k configure ``` 在配置的过程中他会问你能否正常显示一些表情,所以在安装这个包之前最好先准备好支持等宽中文的表情字体,比如Maple Mono NF CN。 - Maple Mono NF CN 可以通过查看[这个链接](https://font.subf.dev/zh-cn/download/?ref=blog.somnus.wiki),我们从下图中可以看到,他会推荐使用ArchlinuxCN,我也去问了一下如何添加这个CN到paru源里面。 - 总共三步,编辑pacman:`sudo nano /etc/pacman.conf`,其次在文件最末尾添加, ``` [archlinuxcn] Server = https://mirrors.tuna.tsinghua.edu.cn/archlinuxcn/$arch ``` ,最后在保存文件之后,通过这样的命令, ``` sudo pacman -Sy archlinuxcn-keyring ``` 此时就可以正常下载这个字体了。 ## 可能存在的问题 ### 可能在安装并且设置好默认shell后发现没有改。 这个问题好像是ubuntu自带的终端一直存在的问题,就是没有办法去真正的改变他的shell,我自己也试过很多方法,最后都不行。 所以我给出的权宜之计就是使用其他的终端模拟器,就是我上面介绍的两个,anticritty或者ghostty,这两者相对来说,功能更完善的还是使用zig编写的ghostty,但是我还是推荐你使用anticritty,(别问为什么,问就是他是使用rust编写的,我是rust信徒。) 还有一种可能,就是需要logout之后在重新进入才能正确设置成zsh。 ### Dify初体验1 URL: https://blog.somnus.wiki/p/difychu-ti-yan-1/ Last updated: 2026-07-21T07:57:11.000Z ## 工作流介绍。 ![](https://somnusblog.oss-cn-shanghai.aliyuncs.com/images/%E6%94%BF%E5%8A%A1%E5%88%86%E6%9E%90-whole-workflow%20(1).png) 工作流整体如图所示。 - 首先是用户输入,对于用户输入的问题,使用一个参数提取器,调用glm-4.7模型对其进行参数提取,参数提取为year,region,task\_type,keywords。为了方便的其他操作。 - 接着呢,就是一段代码执行块,执行了下面这一段python代码。主要实现的逻辑是,对于没有发布指定法规的年份,使用上一个最近的发布的法规,即对于2022徐州的问题,就返回2021年发布的支出标准。对于2026徐州就返回2025年发布的支出标准。 ``` def main(year: int, region: str, keywords: str): REGULATIONS = [ {"region": "徐州", "year": 2021, "filename": "徐州市政务信息化项目运维服务支出标准(徐财评〔2021〕5号).pdf"}, {"region": "徐州", "year": 2025, "filename": "徐州市政务信息化项目运维服务支出标准(徐财评〔2025〕13号).pdf"}, {"region": "无锡", "year": 0, "filename": "《无锡市市级政务信息化建设及运行维护项目预算支出标准(试行)》.pdf"}, {"region": "江苏省", "year": 2021, "filename": "省政府办公厅关于印发江苏省省级政务信息化项目建设管理办法的通知(苏政办发〔2021〕24号).pdf"}, {"region": "辽宁", "year": 0, "filename": "《辽宁省省级政务信息化建设项目预算支出标准规定(试行)》.pdf"}, {"region": "连云港", "year": 0, "filename": "《连云港市市级政务信息化项目运维方案编制指南和支出预算标准(试行)》.pdf"}, ] FALLBACK_REGION = "江苏省" # 1. 匹配地区 candidates = [r for r in REGULATIONS if region in r["region"] or r["region"] in region] if not candidates: candidates = [r for r in REGULATIONS if FALLBACK_REGION in r["region"]] # 2. 匹配年份 if candidates: eligible = [r for r in candidates if r["year"] <= year] best = max(eligible, key=lambda r: r["year"]) if eligible else None else: best = None # 3. 拼接 query if best: filename_stem = best["filename"].replace(".pdf", "") query = f"{filename_stem} {keywords}" else: # 无匹配时用基础信息兜底 query = f"{region} {year}年 {keywords}" return { "year": str(year), "filename": best["filename"] if best else None, "query": query, } ``` - 接着,是一个llm节点,调用deepseekv3,根据得到的信息实现rag查询重写,为了方便在知识库中查找。以下为system提示词: ``` 你是一个面向向量检索(RAG)的查询重写专家。用户当前的需求是估算政务软件项目的造价。 请根据用户的原始 query,将其拆解并改写为 2 到 3 个具体的【自然语言问句或短句】,以便在知识库中精准匹配到长文档中的计算公式和费率表。 改写规则与约束: 1. 严禁堆砌关键词:绝对不要输出用空格隔开的零散词汇组合,必须是结构完整的自然语言。 2. 剥离业务噪音:将用户原始 query 中具体的业务场景(如“录入表单”、“财政业务”)抽象为通用的造价术语(如“定制软件开发”、“功能点测算”)。 3. 必须包含多路检索意图: - 意图 A(找公式):生成一句专门针对底层计算公式的检索词(如:“定制软件开发服务费用的计算公式是什么?”) - 意图 B(找参数):生成一句专门针对费率和取费标准的检索词(如:“软件开发生产率基准、人月折算系数和基准人月费率是多少?”) 不要输出任何解释或思考过程,请直接分行输出优化后的自然语言查询句子。要明确年份和地区 ``` 接下来就是知识检索,我将文件分成了两个知识库,一个是行业基准数据,一个是地方政策。索引方式都是高质量,使用zhipu的embedding-3模型,检索方式采用混合检索,使用有道的bcererankbaseV1模型,top-K设置为7。 ![](https://somnusblog.oss-cn-shanghai.aliyuncs.com/images/20260323192001579.png) ![](https://somnusblog.oss-cn-shanghai.aliyuncs.com/images/20260323192036641.png) 接下来是变量聚合器,将上面查询两个数据库的结果合并起来。 ![](https://somnusblog.oss-cn-shanghai.aliyuncs.com/images/20260323192446645.png) 最后,整合上面主要的信息,输入给llm节点,让他来进行最后的计算。该节点调用的是glm4.7,下面是他的提示词 ``` # Role 你是一位资深的政府信息化项目造价评估与审计专家。你的任务是根据提供的官方政策文件和行业基准数据,为软件开发或系统运维项目提供极其严谨、准确的成本估算。 # Rules 1. **绝对忠实于上下文**:你必须【仅根据】提供的上下文(Context)中的公式、基准价格、系数进行计算。严禁使用任何外部知识或自行编造单价。 2. **处理信息缺失**:如果上下文中缺乏计算必需的参数(如某个特定表单的复杂度权重、特定级别工程师的单价等),请明确指出缺失的内容,并根据已有信息给出一个合理的估算范围,或仅列出计算公式。 3. **展现思维链(CoT)**:所有的计算必须有明确的推导过程,不能直接抛出结果。必须先列出公式,再代入数据,最后得出结果。 4. **单位统一**:请在计算过程中注意单位的统一(如“元”、“万元”、“人月”、“人年”等),并在最终结果中清晰标注。 # Output Format 请严格按照以下四个部分输出你的解答: **1. 需求解析**:简述项目年份、所属地域、项目类型(开发/运维)及关键规模参数(如表单数、接口数、驻场人数等)。 **2. 评估依据**:明确列出你从上下文中提取到的用于计算的具体文件名称、计算公式、基准单价和相关系数。 **3. 计算过程**:将需求参数代入公式,一步步进行数学运算。 **4. 最终估算**:给出一个明确的总价(或价格区间),并附上一句简短的专业风险提示或建议。 ``` ## 运行测试 ### 输入问题 2022年3月,徐州市财政局要开发一套财政业务管理系统,包括3个录入查询表单,每个表单分别对应3个数据库的表和一个外部接口,请估算合理的价格范围; ### 输出结果 ``` **1. 需求解析** - **项目年份**:2022年3月(依据上下文规则,采用2021年度基准数据)。 - **所属地域**:徐州市(注:上下文提供的人月费率基准为无锡市数据,计算中将暂用此基准,并在建议中提示地域差异)。 - **项目类型**:新建软件开发项目(财政业务管理系统)。 - **规模参数**:包含3个录入查询表单。每个表单对应3个数据库表(共计9个内部逻辑文件参考对象)和1个外部接口(共计3个外部接口参考对象)。 **2. 评估依据** 根据提供的上下文(《江苏省省级财政投资评审项目支出预算标准》及相关引用文件),本次估算依据如下: - **计算模型**:采用功能点法进行成本估算。 - **人月折算系数**:174 人·时/月。 - **软件开发生产率基准(P50)**:6.32 人·时/功能点(电子政务领域)。 - **生产率浮动范围**:根据实际情况可上下浮动 20%(即 5.056 ~ 7.584 人·时/功能点)。 - **复用度调整系数**:新建项目预算阶段默认取值范围为 0.75 - 1(复用度低)。 - **软件开发基准人月费率**:24,000 元/人·月(参考上下文中无锡市平均基准)。 - **缺失参数说明**:上下文中未提供具体的“功能点计数规则”(即未定义表单、数据库表、接口分别对应多少功能点),因此无法直接将业务需求数量转换为功能点总数。以下计算将基于**“单功能点单价”**进行推导。 **3. 计算过程** **步骤一:计算基准工时单价** 根据人月费率和人月折算系数,计算每小时的直接人力成本单价: $$ \begin{aligned} \text{基准工时单价} &= \frac{\text{软件开发基准人月费率}}{\text{人月折算系数}} \\\\ &= \frac{24,000 \text{ 元/人月}}{174 \text{ 人·时/人月}} \\\\ &\approx 137.931 \text{ 元/人·时} \end{aligned} $$ **步骤二:计算单功能点成本范围(未考虑复用度)** 结合生产率基准及浮动范围(±20%),计算单个功能点的开发成本: - **生产率下限(效率最高)**:$6.32 \times (1 - 20\%) = 5.056 \text{ 人·时/FP}$ - **生产率上限(效率最低)**:$6.32 \times (1 + 20\%) = 7.584 \text{ 人·时/FP}$ - **单功能点成本下限**: $$ 137.931 \text{ 元/人·时} \times 5.056 \text{ 人·时/FP} \approx 697.36 \text{ 元/FP} $$ - **单功能点成本上限**: $$ 137.931 \text{ 元/人·时} \times 7.584 \text{ 人·时/FP} \approx 1,046.03 \text{ 元/FP} $$ **步骤三:应用复用度调整系数** 针对新建项目,复用度调整系数范围为 0.75 - 1。我们需要计算最终的单功能点综合单价区间。 - **最低单价**(生产率下限 $\times$ 最低复用度): $$ 697.36 \times 0.75 \approx 523.02 \text{ 元/FP} $$ - **最高单价**(生产率上限 $\times$ 最高复用度): $$ 1,046.03 \times 1.00 = 1,046.03 \text{ 元/FP} $$ **步骤四:项目总规模估算(功能点推导)** _由于上下文缺乏具体的“功能点计数表”,无法精确计算规模。以下为基于行业常见理解(非上下文数据,仅供参考逻辑)的公式化表述:_ 若设该项目的总功能点数为 $S$,则总价计算公式为: $$ \text{项目总费用} = S \times \text{单功能点综合单价} $$ **4. 最终估算** 由于提供的上下文中**缺失功能点计数标准**(即无法确定“3个表单”、“9个数据库表”、“3个接口”具体折算多少功能点),无法直接得出具体的总价。 基于上下文数据,该项目的**单功能点综合单价应在 523.02 元 至 1,046.03 元 之间**。 _若假设_(仅为举例,非依据上下文)该项目总规模为 100 功能点,则总价估算范围为: $$ 52,302 \text{ 元} \leq \text{总价} \leq 104,603 \text{ 元} $$ **专家风险提示与建议:** 1. **地域差异**:计算依据使用了上下文中的“无锡市”平均基准人月费率(2.4万元/人月)。徐州市的实际费率可能与此存在差异,建议查询徐州市当地发布的最新信息化项目造价标准进行替换。 2. **标准缺失**:目前最关键的是缺乏《软件工程 功能规模测量》等具体的计数规则文件,建议补充“表单”、“数据表”与“功能点”的对应换算表,以完成精确的规模测算。 3. **非人力成本**:上述估算仅包含软件开发的人力及间接成本,未包含直接非人力成本(如软硬件采购、差旅费等),如有此类支出需另行计算。 ``` ### 输入问题 2026年,徐州公安局要开发一套警察人员管理系统,包括3个录入查询表单,每个表单分别对应3个数据库的表和一个外部接口,请估算合理的价格范围; ### 输出结果 ``` * **1. 需求解析:** * 年份:2026年。 * 地点:徐州(江苏)。*注:上下文使用的是辽宁费率。* * 类型:完全定制开发(J0401)。 * 规模:3个表单,3个数据库表,1个外部接口。 * **2. 评估依据:** * 文件:“5.软件开发费”,表1。 * 方法:完全定制开发。 * 公式:定制开发费用 = 人月费率 × 开发工作量。 * 费率:1.87万元/人月。 * 注意:原则上应使用功能点法,但上下文缺少功能点计数规则和基准生产率。 * **3. 计算过程:** * 假设“开发工作量” = X 人月。 * 计算:1.87 * X。 * 由于我无法计算X(需要“功能点数”和转换系数),计算停在这里。 * 确立人月单价。 * **4. 最终估算:** * 结果:无法确定总价。 * 确定费率:**18,700元/人月**。 * 建议:建议在徐州当地重新评估费率,并确定工作量(可能通过功能点分析)。 - _等等,再看“用户的具体项目需求”。_ “请估算合理的价格范围。” - 如果用户*期待*一个数字,并且我提供不了,我需要非常有条理。 - 有*任何*可能“接口”或“表单”有隐含成本吗?不。 - _修正:_ 我将提供计算**如果**工作量已知的结构,并强调1.87万这个数字。 - _让我们最后一次检查提示指令。_ “如果上下文缺乏... 给出合理的估算范围,或仅列出公式。” - 既然我不能编造范围,我将“仅列出公式”并提供“单价”。 - _完善后的输出结构:_ - **1. 需求解析**:清楚地陈述徐州2026年项目的细节。提到地域不匹配(徐州 vs 辽宁)。 - **2. 评估依据**:引用“完全定制开发”方法和具体的“1.87万元/人月”费率。引用功能点优先但数据缺失。 - **3. 计算过程**: - 公式:`总价 = 人月费率 × 开发工作量`。 - 已知:`人月费率 = 1.87万元/人月`。 - 未知:`开发工作量`。解释为什么未知(没有功能点表/工作量表)。 - 数学:展示单价是18,700。 - **4. 最终估算**: - 由于变量缺失,无法计算总价。 - **有效单价:** 18,700元/人月。 - **风险/说明:** 地域差异(辽宁费率应用于江苏),以及缺少工作量估算参数。 - _自我修正/完善:_ 提示要求我成为“资深专家”。资深专家不仅仅是说“我不能”。他们会说“基于提供的数据,我们建立了单位成本模型。然而,要得出总价,我们需要通过[缺失方法]确定工作量。基于现有数据,单位成本如下...” - 这是最稳健的回答方式。它遵循规则(不编造工作量),但回答了请求的“精神”(估算成本 = 确立费率)。 - _再检查一次:_ “接口”算作“商品软件”吗?不,通常是定制。“表单”呢?定制。 - _等等_,“商品软件费用”提到“∑商品软件单价×数量”。项目没有提到购买商品软件(如Oracle许可证或成品系统)。所以“商品软件费用”为0。 - _等等_,“分级部署费用”?文本说“完全定制开发... — — 定制开发费用”。所以不涉及分级部署费用或商品软件费用。 - _逻辑的最终检查:_ 1. **项目**:完全定制开发。 2. **费用公式**:人月费率 × 开发工作量。 3. **费率**:1.87万元/人月(根据提供的文本)。 4. **工作量**:未知/缺失。 5. **结论**:成本 = 1.87 \* 工作量。 - _格式:_ 按要求使用四个部分。 - _关于“3年”条款的一个细节?_ 不,提示说“开发一个系统”。这不是租赁或运维项目(尽管提到“1年维保”)。合同“可能”是3年(租赁),但这里“开发”意味着建设。 - _输出语言:_ 中文,专业语气。 **1. 需求解析** - **项目年份**:2026年 - **所属地域**:江苏省徐州市(注意:参考文件数据基于辽宁省,存在地域基准差异) - **项目类型**:定制软件开发服务(完全定制开发,J0401) - **关键规模参数**:包含3个录入查询表单、3个数据库表、1个外部接口。 **2. 评估依据** 根据提供的参考上下文,提取以下计算标准: - **服务类别**:完全定制开发。 - **费用构成公式**:`定制开发费用 = 人月费率 × 开发工作量`(见上下文第3条“完全定制开发”计算方法及(1)工作量估算法)。 - **人月费率标准**:根据上下文,软件开发人员的人月费率标准为 **1.87 万元/人月**。 - **功能点法补充说明**:上下文提到“原则上按功能点法估算”,且提供了功能点法的复杂公式,但未提供具体的“功能点数对应表”、“基准生产率”、“人月折算系数”或“应用类型调整因子”的具体数值。因此,无法采用功能点法进行直接计算,只能采用工作量估算法列出公式。 - **其他费用**:项目无商品软件购置或分级部署费用,仅计算定制开发费用。 **3. 计算过程** 由于上下文中未提供“录入查询表单”、“数据库表”或“外部接口”对应的“开发工作量”(人月)折算标准,无法直接计算最终总价。以下基于已知参数建立费用模型: **第一步:确定基础单价** 根据上下文中辽宁省的数据标准: - 人月费率 = 1.87 万元/人月 **第二步:建立计算公式** 根据完全定制开发的费用计算规则: - 定制开发费用 = 1.87 万元/人月 × 开发工作量(人月) **第三步:分析缺失参数** 要得到具体总价,需要确定“开发工作量”。 - 若采用工作量估算法:需要知道3个表单、3个数据库表、1个接口具体需要多少个人月来完成。上下文中未提供这些组件的工时基准(如:每个表单开发需要0.5人月等)。 - 若采用功能点估算法:需要先计算功能点数,并代入公式: `费用 = 功能点数 × 规模变更调整因子 × 基准生产率 / 人月折算系数 × 应用类型调整因子 × 人月费率` 但上下文未提供上述各调整因子的具体数值,且未提供“表单”与“功能点”的转换规则,故此路径无法计算。 **4. 最终估算** 由于上下文缺失关键参数(组件与工作量/功能点的对应关系及调整系数),无法给出具体的总价数值。 **估算结论**: 该项目每投入 1 个开发人月,成本为 **1.87 万元**。 建议按照以下公式进行最终测算: > **总价(万元) = 1.87 × 预估开发人月数** **专家风险提示**: 1. **地域差异风险**:提供的人月费率(1.87万元/人月)明确基于辽宁省统计数据,而项目位于江苏徐州。通常江苏省的人工成本高于辽宁省,直接使用该费率可能导致估算偏低,建议结合江苏省或徐州市当地的同类项目标准调整费率。 2. **工作量测算缺失**:在缺乏具体功能点计费规则的情况下,建议通过专家评审法(如德尔菲法)对3个表单和1个接口的开发工作量进行人天评估,再代入上述公式计算。 ``` ### Raft训练日志1 URL: https://blog.somnus.wiki/p/raftxun-lian-ri-zhi-1/ Last updated: 2026-07-21T07:57:11.000Z ## 前情提要 ### 什么是RAFT RAFT-stereo是基于RAFT的扩展。RAFT即Recurrent All-Pairs Field Transforms,是一个很出名的光流估计模型,他的特点是先建立全局匹配关系,再通过循环迭代不断细化结果,每一步更新都会变得更准,而RAFT-stereo是基于这个模型的双目改造。 ![](https://somnusblog.oss-cn-shanghai.aliyuncs.com/images/20260322114328149.png) ### RAFT-stereo的原理 先用卷积网络提取左右图的高维特征,而不是拿RGB去比较。因为原始RGB容易受到光照,纹理,噪声影响。 接着就是构建相关性体,简单说就是对于左图中的每一个位置,它都会和右图的一整条去进行相似度比较,形成一种“匹配代价”。 再接着就是**不断迭代**,他会先给个初始估计,然后通过**循环更新模块**来反复修正。 ### 和其他立体匹配有什么不同之处 传统方法,比如SGM、Blocking Matching,是先算匹配代价,再代价聚合,再做视觉选择,最后再做左右一致性检查。 而RAFT-stereo,属于端到端的学习方法,他会自动学习,什么特征适合匹配,什么区域应该相信上下文,哪些遮挡、弱纹理、重复纹理区域该怎么处理。 ### 为什么做这个 我的本科毕业设计做的是双目摄像头的,通过资料搜集,我发现网上感觉最新(2021年提出)的双目摄像头立体匹配算法就是这个RAFT-Stereo,于是我便接触到了这个项目。 ## 训练过程 首先,这个项目已经被开源到Github,而且readme文档有较为详细的指令包括下载数据集,demo输出,train,evaluate等等。 说实话,这是我第一次进行正式的模型训练和微调项目,之前做过的模型训练是[yolov5](https://github.com/ultralytics/yolov5.git?ref=blog.somnus.wiki),之前g308(西南交通大学robocon校队,我梦开始的地方,没有这个机会,我根据不会接触技术路线,这个博客也不会诞生)的入队考核需要通过这个进行蓝球,红球,球框额的识别(实际上就是[robocon](http://robocon.org.cn/?ref=blog.somnus.wiki)2024的麦穗题目),每个人都会以自己的方式接触到yolo,都是回忆啊,在这个之后我就没有做过需要模型训练的项目了,而且就算是那个yolo项目,我也没有进行模型微调,毕竟是入队考核,还是以基础为主。 通过这个项目,chatgpt辅助我,让我也学会了大致的模型微调流程,以下是我的总结 - 首先要对原始模型进行推理和评测,得到baseline,也就是整个微调的基线。 - 然后控制变量,每次只改一部分,而且要保留原模型,改动要作为一个选择,也就是作为运行程序的一个可选参数,而不能直接在原模型上改,不然的话,感觉就没有回头路了。 - 再然后就是根据评价指标,对得到的模型进行评价,和baseline对比。 ## 模型改进改动 对于模型改变,我只是根据chatgpt给的提示作了一些修改,代码全部上传到我自己fork的[github](https://github.com/somnus4214/RAFT-Stereo?ref=blog.somnus.wiki)上, ### refinement 我首先尝试了增加一个refinement层,这是一个很微小的改变,以下为refinement层 ``` import torch import torch.nn as nn import torch.nn.functional as F class RefinementHead(nn.Module): def __init__(self, in_channels=4, hidden_dim=32): super(RefinementHead, self).__init__() self.conv1 = nn.Conv2d(in_channels, hidden_dim, 3, padding=1) self.conv2 = nn.Conv2d(hidden_dim, hidden_dim, 3, padding=1) self.conv3 = nn.Conv2d(hidden_dim, hidden_dim, 3, padding=1) self.conv4 = nn.Conv2d(hidden_dim, 16, 3, padding=1) self.conv5 = nn.Conv2d(16, 1, 3, padding=1) def forward(self, image1, disp): x = torch.cat((image1, disp), dim=1) x = F.relu(self.conv1(x)) x = F.relu(self.conv2(x)) x = F.relu(self.conv3(x)) x = F.relu(self.conv4(x)) res = self.conv5(x) return disp + res ``` 实际上RAFT-Stereo最核心的组件就是**内置的精炼层––多层卷积GRU**,这样的设计本身就是在精炼,所以第一步的视差估计都是粗糙的,每一步都在上一步的基础上迭代、修正,最后一轮的输出就是经过了32次迭代的输出。 refinement是什么,本质上是一个输出端的残差修正模块,在RAFT-Stereo经过多次迭代后,会输出一个目前比较好的视差图,然后再和输入的左图拼接, - 左图:3通道 - 视差图:1通道 得到了一个4通道的输入,将其输入到refinement head模块中,就是上面的代码块中,最终得到一个1通道残差图,每个像素点表示此处应该对视差作出的更改。 ``` if hasattr(self.args, 'use_refinement') and self.args.use_refinement: disp0 = flow_predictions[-1] disp_refined = self.refinement_head(image1_raw / 255.0, disp0) else: disp_refined = flow_predictions[-1] ``` 我的refinement主要想解决的问题是物体边界存在模糊、细节构区域。所以这个模块更像是图像引导的局部视差修正器。 ### 边缘感知损失 边缘感知损失本质上让模型在图像边缘附近犯错时付出更大的代价。将普通的*L1 loss*计算从$L= |d\_{pred}-d\_{gt}|$ 变成 $L\_{edge}=weight\*|d\_{pred}-d{gt}|$ ``` if args.use_edge_loss: loss_edge = edge_aware_loss(disp_refined, flow, valid, image1) loss += args.lambda_edge * loss_edge metrics["loss_edge"] = loss_edge.item() ``` 那么问题是,如何找到图像边缘位置呢,按照以下步骤:把RGB转成灰度图->算出图像梯度->归一化->构造最终权重。 最终权重计算公式: $$ weight = 1.0 + \\alpha \*grad $$ 该方法的原理是,视觉误差并不是所有位置都一样重要,在平坦区域,误差稍微大一点,视觉上可能还不明显。但在边界区域,只要错一点点,就会造成: - 轮廓糊掉 - 深度断层错位 - 物体边缘拉扯 - 细结构消失 我的代码**整个总损失是由三部分组成**,首先是原始的sequence loss,是RAFT-Stereo自带的损失,其次是refined loss,是最终的视差图和gt的普通误差监督,最后是edge-aware loss,这个是对 disp\_refined 在边缘区域加权后的误差监督。 ### partial unfreeze 之前的训练都是冻结整个RAFT-Stereo主干,之训练了新增的,refinement head,但是partial unfreeze呢,仍然保留大部分主干冻结,额外解冻update\_block,让refinement\_head和update\_block一块训练。 ![](https://somnusblog.oss-cn-shanghai.aliyuncs.com/images/20260323033429711.png) ## 实验结果 | | EPE | D1 | | ---------------------- | ------ | ------- | | Baseline | 2.3758 | 12.0526 | | Refinement only | 2.3760 | 12.0609 | | Refinement + Edge Loss | 2.3757 | 12.0588 | | Partial Unfreeze | 2.4376 | 12.5331 | 从结果可以看出,仅增加 refinement head 或加入边缘感知损失后,模型整体性能与 baseline 基本持平,**未出现明显提升**,但也没有显著破坏原始模型性能。这说明输出端的轻量级局部修正结构在当前小规模真实域数据集设置下,对整体 EPE 和 D1 指标的**影响较为有限**。 相比之下,partial unfreeze 策略使模型性能出现明显下降。这说明在仅使用 Middlebury 小规模数据集进行微调时,解冻 update block 会显著增加**过拟合风险**,并可能破坏原始预训练模型已经形成的匹配更新能力,导致整体**泛化能力下降**。 ## 后续 在尝试提升模型性能无望后,我和导师商量过后决定把研究中心转移到对模型的轻量化上面来,当前模型大概有11.14m个参数,可以尝试剪枝和蒸馏,对模型进行“减重”,方便模型部署到低算力的边缘设备上。 ## 碎碎念 ### 服务器 我这次训练都是使用autodl的服务器(不是广告),原本我以后这种租服务器,每次都是要把数据上传到服务器端,然后再开始跑模型,受限于网速,每次部署都会很浪费时间,如果一直挂着服务器,钱包又会受不了。实际上,这个服务器端,有三种存储形式,**系统盘,数据盘和文件存储**,其中系统盘就是镜像装配的盘,也就是整个系统配置所在的盘,你可以把一些数据文件放在里面,但是这个盘的大小通常不大,所以大数据不适合放在里面,但是这个有一个很大的有时就是,你可以上传自己的容器示例。 ![](https://somnusblog.oss-cn-shanghai.aliyuncs.com/images/20260323140524892.png) 上传的实例可以非常方便的部署到同型号或者近似型号的显卡上。 ### 训练命令 我原本是直接使用python train.py 布拉布拉的配置信息,但是这个有一个问题,就是一旦我的网络不稳定,与服务器断开连接,运行的程序会因为终端的关闭而直接终止,因为这种连接本质都是ssh,而ssh就是将服务器的终端直接投到本地,倘若连接关闭,ssh会自动杀死服务器终端。当时的解决方案就是使用**nohup**(no hang up,不挂起)是告诉linux系统,后台不挂断地运行命令,此时退出终端也不会影响程序进行。 ``` nohup python train_stereo.py \ --name raftstereo_partial_unfreeze_500 \ --restore_ckpt models/raftstereo-middlebury.pth \ --batch_size 1 \ --train_iters 16 \ --valid_iters 16 \ --num_steps 500 \ --mixed_precision \ --use_refinement \ --use_edge_loss \ --partial_unfreeze \ --lr_refine 2e-4 \ --lr_update_block 2e-5 \ --lambda_ref 1.0 \ --lambda_edge 0.2 \ --train_dataset middlebury >111.logs 2>&1 & ``` 希望以后能记住这个指令。更希望自己能擅长使用tmux。 总算可以好好休息一下,前段时间还是有点太忙了。 ### 我的第一篇博客 URL: https://blog.somnus.wiki/diary/wo-de-di-yi-pian-bo-ke/ Last updated: 2026-07-21T07:57:05.000Z 你好,这是我的博客 🚀 放一张linus的照片 ![linus](https://somnusblog.oss-cn-shanghai.aliyuncs.com/images/9723046.jpg)