<?xml version="1.0" encoding="utf-8"?>
<feed xmlns="http://www.w3.org/2005/Atom" xml:lang="zh_CN">
  <title>日勿の部落格</title>
  <subtitle>知识在于内化，而非存储</subtitle>
  <link href="https://blog.085404.xyz/" rel="alternate" type="text/html"/>
  <link href="https://blog.085404.xyz/atom.xml" rel="self" type="application/atom+xml"/>
  <id>https://blog.085404.xyz/</id>
  <updated>2026-09-13T00:00:00.000Z</updated>
  <entry>
    <title>CLI Proxy API 部署</title>
    <link href="https://blog.085404.xyz/posts/cli-proxy-api-%E9%83%A8%E7%BD%B2/" rel="alternate" type="text/html"/>
    <id>https://blog.085404.xyz/posts/cli-proxy-api-%E9%83%A8%E7%BD%B2/</id>
    <published>2026-08-25T00:00:00.000Z</published>
    <updated>2026-08-25T00:00:00.000Z</updated>
    <summary></summary>
    <content type="html"><![CDATA[<p>鉴于 CPA 的<a href="https://help.router-for.me/">文档</a>在 Docker 部署和反向代理的部分过于简陋，且配合 CPAMP 使用时部署存在一些问题，为避免额度盗刷，这里通过实测案例进行一些补充部署记录。</p>
<blockquote>
<p>[!warning]</p>
<ol>
<li>本文档相关配置以最新版 <a href="https://github.com/router-for-me/CLIProxyAPI">CPA</a> 和 <a href="https://github.com/seakee/CPA-Manager-Plus">CPAMP</a> 为准。</li>
<li>NGINX 默认部署在宿主机。</li>
</ol>
</blockquote>
<h2>Docker 统一部署</h2>
<pre><code>services:
  cli-proxy-api:
    image: eceasy/cli-proxy-api:latest
    container_name: cli-proxy-api
    ports:
      - "127.0.0.1:8317:8317"
    volumes:
      # CPA 配置文件
      - ./config.yaml:/CLIProxyAPI/config.yaml
      # 凭证目录
      - ./auth-dir:/root/.cli-proxy-api
      # 插件
      - ./plugins:/CLIProxyAPI/plugins
    restart: unless-stopped

  cpa-manager-plus:
    image: seakee/cpa-manager-plus:latest
    restart: unless-stopped
    ports:
      - '127.0.0.1:18317:18317'
    volumes:
      - ./cpa-manager-plus-data:/data
    depends_on:
      - cli-proxy-api
</code></pre>
<p>上述 docker compose 文件中，映射端口全部指定了 host 为 <code>127.0.0.1</code>，防止通过 <code>公网IP:Port</code> 访问到相关服务。</p>
<h2>NGINX 反向代理</h2>
<blockquote>
<p>[!note]
该部分参考<a href="https://seakee.github.io/CPA-Manager-Plus/docs/deployment/reverse-proxy.html">反向代理 | CPA Manager Plus</a></p>
</blockquote>
<p>首先，在 NGINX 的 http 块中加入如下配置，以支持正确处理 WebSocket 等需要 HTTP 协议升级（Upgrade）的反向代理请求：</p>
<pre><code># 其他配置
...

http {
    # 其他配置
    ...
    
    # 添加如下部分
    map $http_upgrade $connection_upgrade {
            default upgrade;
            ''      close;
    }
}
</code></pre>
<p>然后，在 NGINX 配置文件目录 <code>conf.d</code> 下新增 <code>cpa.conf</code>：</p>
<pre><code># 指定后端别名
upstream cpa_api {
    server 127.0.0.1:8317;
}

upstream cpamp {
    server 127.0.0.1:18317;
}

# http 默认跳转
server {
    listen 80;
    server_name your.cpa.domain;

    return 301 https://$host$request_uri;
}

server {
    listen 443 ssl;
    server_name your.cpa.domain;
    http2 on;
	
	# SSL 证书配置
    ssl_certificate xxx;
    ssl_certificate_key xxx;

    ssl_protocols TLSv1.2 TLSv1.3;

    client_max_body_size 64m;

    proxy_http_version 1.1;
    proxy_read_timeout 3600s;
    proxy_send_timeout 3600s;
    proxy_buffering off;

    proxy_set_header Host              $host;
    proxy_set_header X-Real-IP         $remote_addr;
    proxy_set_header X-Forwarded-For   $proxy_add_x_forwarded_for;
    proxy_set_header X-Forwarded-Proto $scheme;
    proxy_set_header Upgrade           $http_upgrade;
    proxy_set_header Connection        $connection_upgrade;

    # 默认进入 CPAMP 面板
    location = / {
        return 302 /management.html;
    }

    # ===== CPA Manager Plus =====

    location = /management.html { proxy_pass http://cpamp; }
    location = /health          { proxy_pass http://cpamp; }
    location = /status          { proxy_pass http://cpamp; }
    location = /setup           { proxy_pass http://cpamp; }

    # CPAMP 兼容接口与运行时接口
    location ^~ /usage-service/ { proxy_pass http://cpamp; }

    # /v0/management/* 先进入 CPAMP：
    # - 用量 / 模型价格 / API 密钥别名 / 仪表盘 / 请求监控 /
    #   Codex 账号巡检 / 导入导出 等由 CPAMP 自己处理
    # - 其他 CPA 管理接口由 CPAMP 使用服务端保存的 CPA Management Key 继续代理到 CPA
    #
    # 注意：不要只配置 location = /v0/management
    # 应使用带尾斜杠的前缀匹配 /v0/management/
    location ^~ /v0/management/ { proxy_pass http://cpamp; }

    # CPAMP 插件页面资源。缺少此规则时，插件页面可能打开空白或资源 404。
    location ^~ /v0/resource/plugins/ { proxy_pass http://cpamp; }

    # /models 由 CPAMP 提供兼容代理
    # 如果 CPAMP 尚未完成 setup，访问此路径可能返回 412
    location = /models          { proxy_pass http://cpamp; }

    # ===== CPA / CLI Proxy API =====

    # OpenAI / Claude Code / Codex 等实际 API 请求应直接走 CPA
    location ^~ /v1/                 { proxy_pass http://cpa_api; }
    location ^~ /v1beta/             { proxy_pass http://cpa_api; }
    location ^~ /backend-api/codex/  { proxy_pass http://cpa_api; }
    location ^~ /api/                { proxy_pass http://cpa_api; }

    # CPA 特殊路由与 OAuth 回调
    location = /v1internal:method    { proxy_pass http://cpa_api; }
    location = /healthz              { proxy_pass http://cpa_api; }
    location = /anthropic/callback   { proxy_pass http://cpa_api; }
    location = /codex/callback       { proxy_pass http://cpa_api; }
    location = /google/callback      { proxy_pass http://cpa_api; }
    location = /antigravity/callback { proxy_pass http://cpa_api; }

    # 兜底给 CPA
    # 用于 CPA 根路径、Amp 路由以及未来新增接口
    location / {
        proxy_pass http://cpa_api;
    }
}
</code></pre>
<p>上述配置完成后，无需在防火墙开放 <code>8317/18317</code> 端口，保持关闭即可。</p>
<h2>配置连接信息</h2>
<p>首次启动后，在 <code>docker logs &lt;container_id&gt;</code> 中可以看到 <code>camp_xxx</code> 字样的密钥，复制保存。该密钥为登录 CPAMP 面板的密钥。CPA 的密钥根据面板提示逐步设置。如果已经存在正在使用的 CPA，会自动接入对应信息。</p>
<p>需注意，CPA 地址在本文档的部署情况下，即 CPA 和 CPAMP 部署于同一 Docker 网络下，请填写 NGINX 中配置的后端名称：<code>http://cli-proxy-api:8317</code></p>
]]></content>
    <author><name>Sun2ot</name></author>
    <category term="教程"/>
  </entry>
  <entry>
    <title>《活着》</title>
    <link href="https://blog.085404.xyz/posts/%E6%B4%BB%E7%9D%80/" rel="alternate" type="text/html"/>
    <id>https://blog.085404.xyz/posts/%E6%B4%BB%E7%9D%80/</id>
    <published>2026-07-12T00:00:00.000Z</published>
    <updated>2026-09-13T00:00:00.000Z</updated>
    <summary></summary>
    <content type="html"><![CDATA[<blockquote>
<p>[!note]
"人是为活着本身而活着的，而不是为了活着之外的任何事物所活着。"</p>
</blockquote>
<p>余华在《活着》的韩文版自序中写下了这句话。它不是小说正文中的台词，而是余华对整部作品精神内核的提炼。</p>
<h2>为什么这样说？</h2>
<p>福贵的一生，是"剥去"的过程。小说中的福贵，本是地主家的少爷，年轻时挥霍无度。此后，他的人生就像被命运一层层地"剥皮"——家产败光、父亲气死、母亲病逝、儿子有庆被抽血致死、女儿凤霞难产而亡、妻子家珍病故、女婿二喜工地事故、外孙苦根吃豆子撑死……所有"活着之外"的东西——财富、地位、亲情、希望——都被一一夺走。</p>
<p>到最后，福贵只剩一头也叫"福贵"的老牛相伴。然而他并没有崩溃，没有自杀，没有怨天尤人，而是平静地牵着老牛在田间劳作，平静地给路人讲述自己的一生。</p>
<p>《活着》用这种极致的"剥夺"告诉我们：当所有外在的支撑都被拿走之后，福贵依然在活着。 这说明，活着本身不需要任何外在的理由和附加的意义。</p>
<h2>对"意义焦虑"的反叛</h2>
<p>我们习惯为活着寻找理由——为了理想、为了爱情、为了家人、为了成就。这些当然美好，但余华提出了一个尖锐的问题：如果这些理由全部消失了呢？你还活不活？</p>
<p>福贵的回答是：活。不是因为勇敢，不是因为坚强，而是因为活着本身就是生命最本能的状态。余华借此打破了我们对"痛苦使人崇高"的浪漫想象——苦难就是苦难，它不带来升华，不带来顿悟，福贵的忍受是沉默的、朴素的、近乎植物般的韧性。</p>
<p>余华曾说："活着的力量不是来自于呐喊，也不是来自于进攻，而是忍受。" 福贵的活着，不是英雄式的抗争，而是一种中国人特有的、与土地相连的生存哲学——像庄稼一样，接受风雨，接受命运，默默扎根，静静生长。这种"忍受"的中国式生存智慧不是懦弱，而是一种对生命本身最深沉的尊重。这句话初读似乎有些"虚无"，仿佛否定了生活的一切追求。但细想之后，我认为余华想表达的恰恰相反：它不是让你放弃追求，而是让你在失去一切追求之后，依然能活下去。在顺境中，我们为理想、为爱人、为事业而活，这很好。但人生无常，命运不会按照我们的剧本走。当理想破灭、亲人离散、一切归零的时候，人靠什么撑下去？余华的回答是：靠活着本身。 生命不需要被"意义"来拯救，呼吸本身就是奇迹，心跳本身就是尊严。</p>
<p>这其实是一种极其温暖的思想。它告诉我们：你不需要功成名就才配活着，不需要被爱才有资格存在，不需要"活出意义"才算不虚此行。你活着，这件事本身，就已经足够了。在当下这个充满焦虑的时代——我们被要求"活出精彩""实现自我""成为更好的自己"——余华用福贵的一生提醒我们：平凡地、安静地、甚至孤零零地活着，本身就是一种了不起的力量。</p>
]]></content>
    <author><name>Sun2ot</name></author>
    <category term="思绪"/>
  </entry>
  <entry>
    <title>LLM 与 Coding Agent</title>
    <link href="https://blog.085404.xyz/posts/llm-%E4%B8%8E-coding-agent/" rel="alternate" type="text/html"/>
    <id>https://blog.085404.xyz/posts/llm-%E4%B8%8E-coding-agent/</id>
    <published>2026-04-18T00:00:00.000Z</published>
    <updated>2026-04-18T00:00:00.000Z</updated>
    <summary>简单地科普一下大语言模型和AI编程之间的关系，并介绍现有的套餐计费模式。</summary>
    <content type="html"><![CDATA[<h2>摘要</h2>
<p>今天大家口中的“AI 编程”，其实常把三层东西混在一起说。最底层是 LLM 这样的基础模型，中间层是把模型放进“能看文件、能跑命令、能调用工具、能维护状态”的 Agent；最上层才是开发者真正天天接触的入口，例如 CLI、IDE 插件、GitHub 上的云端代理。</p>
<p>在 LLM 的语境里，广义的 Agent 不是“更大的模型”，而是以模型为决策核心、能规划、能调用工具、能维护上下文、能完成多步任务的软件系统。Coding Agent 则是广义 Agent 在软件工程领域的特化版本，它面向的是代码仓库、终端、测试、Git、Issue/PR、设计文档和第三方开发工具等环境。</p>
<p>从计算成本上也需要分开看。从技术上来说，这类系统都底层都在处理 token。但从商业上说，开发者显然未必总是按 token 原样付费。目前的计费方式包括：按输入/输出 token 计费、缓存 token 计费、隐藏 thinking token 计费、按 premium requests 计费，以及按“每 5 小时请求额度/每周请求额度/每月请求额度”订阅的混合模式。</p>
<h2>LLM 如何走到 Coding Agent</h2>
<p>从技术起点看，现代 LLM 的底层仍然是“对 token 序列做条件生成”的模型。2017 年的 Transformer 论文奠定了注意力机制这条主线，GPT-3 则证明了大规模参数与数据扩展可以显著提升 few-shot 能力。因此今天的大多数编程助手，表面上像在“帮你写代码”，底层本质仍然是在做高维度的 token 预测与条件生成。</p>
<p>面向编程的能力随后沿着两条路线演进。第一条路线，是在代码数据上进一步训练的专用模型。第二条路线，则是把更通用的前沿模型做成“能处理开发任务”的系统能力：更长的上下文、更好地推理、更强的多模态能力、更强的工具调用能力。再往前一步，就是从“模型回答问题”进入“模型参与执行工作”。各家大模型厂商近两年的官方平台就是把这种模式产品化，如 Claude Code 和 Codex。</p>
<p>如果把 LLM 当作文字推理引擎，那么广义 Agent 就是让这个引擎<strong>在环境中持续做事的应用形态</strong>。OpenAI 的定义很直接：</p>
<blockquote>
<p>Agent 是计划、调用工具、跨专家协作并保持足够状态以完成多步工作的应用程序。</p>
</blockquote>
<p>Anthropic 的表述则更工程化：</p>
<blockquote>
<p>Workflow 通过预定义代码路径把 LLM 与工具串起来，Agent 则允许 LLM 动态决定自己的过程与工具使用。</p>
</blockquote>
<p>简单来说，智能体可以表述为“模型 + 工具 + 循环”，直到达到停止条件为止。这一区分非常关键，因为很多开发者口中的“Agent”其实并不是完全自治体，而是“带一点自主性的工作流”。比如：</p>
<ul>
<li>“读取报错日志 → 搜索项目文件 → 给出修复建议”的脚本：Workflow</li>
<li>一个会自己决定先读哪些文件、要不要运行测试、需不需要继续搜索网络资料、何时请求你批准写入代码的系统：Agent</li>
</ul>
<p>因此，今天的 AI 编程能力已经不适合只按模型排行榜来理解。更准确的理解方式是：<strong>模型能力决定上限，Agent Loop 决定能否把上限转化成真实开发产出</strong>。同一个模型，放进不同的权限边界、不同的工具集和不同的上下文管理策略里，开发体验会差非常多。OpenAI 专门写了 Codex agent loop 的工程拆解，Anthropic 也在 Claude Code 最佳实践里强调“先探索、再计划、再编码”。这说明行业竞争的重点，已经部分从“谁更会回答”转向“谁更会工作”。换言之，Coding Agent 与广义 Agent 的关系，本质上是“子集”关系。Coding Agent 是把广义 Agent 的规划、工具调用、状态管理和多步执行，放进了软件工程环境里：代码库、文件系统、终端、测试框架、lint、版本控制、Issue/PR、设计文档、MCP 工具、CI/CD 等，都是它的“世界”。</p>
<p>但是，也需要明确指出，Agent 的核心收益主要来自上下文管理、专业分工和并行化，但代价通常是更多 model calls、更高的 token 消耗与更复杂的调度。</p>
<h2>为什么是 CLI</h2>
<p>CLI（Command Line Interface，命令行界面）是一种通过文字命令与计算机操作系统或软件进行交互的界面，通俗的理解就是俗称的“终端”。为什么现有的顶级 Agent 都会采取这种形式？原因很简单，应用程序随着框架的变化，架构设计是会改变的。而操作系统的接口无论如何都是不会变的，至少不会短期内剧烈变化。</p>
<p>举个简单的例子，早期的 AI IDE 大多基于 vscode，采用插件的形式进行辅助编码。但结果呢？受限于 vscode 自身的限制，文件的读取、写入、搜索都需要 vscode 提供底层支持才能进行。而 cursor 的横空出世告诉人们：可以 All in AI。CLI Coding Agent 就是秉持这一观念，你可以通过对话命令 CLI 调用系统接口，例如 Windows 的 Powershell 或是 Linux 下的 Bash，来实现任意的文件操作，进而进行编码。相对应的，也可以命令 Agent 执行指令。于是你会发现，“编码 + 指令”，已经构成了完整的 develop 过程。</p>
<h2>复杂的 Token 计费模式</h2>
<p>天下熙熙皆为利来，天下攘攘皆为利往。互联网企业在 21 世纪已经对产品推广模型形成了一种路径依赖，这也是各位能够免费使用 GPT、Gemini、Qwen、豆包等模型的原因。</p>
<p>从技术定义上说，token 是模型内部处理信息的最小单位之一，它可能是一个单词、一个字符，甚至是字节/Unicode 级别的片段（例如图片、音频）。对于模型接口，上下文窗口、输出上限、缓存命中、并行工具调用成本，最后都会落在 token 上。而 tokenization 指的就是把原始文本切成 token 的过程，针对这一过程，各大厂商使用的 tokenizer 当然不同。因此，不同 tokenizer 会影响词表大小、效率和 OOV 处理方式。</p>
<p>从计费模式上说，在 API 世界里，只需以输入 token 与输出 token 为基本计费单位，然后叠加缓存、批处理、工具和长上下文的差异化收费，通过简单地相乘再相加即可得到资费详情。但除此之外，还有两类“看不见但会付钱”的 token。第一类是<strong>工具开销</strong>：在 Anthropic 文档里，<code>tools</code> 参数、<code>tool_use</code>、<code>tool_result</code>，乃至自动插入的 tool-use system prompt，都会进入 token 计费。第二类是<strong>思考或推理开销</strong>：Gemini 定价页直接把输出价格标注为 “including thinking tokens”；Anthropic 也写明即使你最后只看到了被总结后的内容，extended thinking 也是按完整 thinking tokens 计费的。这意味着，对于 Agent 来说，“让模型多想一步”往往确实能提升质量，但它并不免费。</p>
<p>而在 LLM API 之外，，一个令人头疼的现状是，<strong>计费和 token 的关系不再是一条直线</strong>。在计费方式上，由于 token 的开销急剧增长，计费和 token 直接划等号的计算方式似乎有点令人难以接受。于是，秉持着太阳底下没有新鲜事的原则，各大厂商火速推出了自家的 Coding Plan。通常来说，是按照“每 5 小时、每周、每月”的请求额度管理。具体到各个厂商，有的是背后是按照消耗的 tokens 进行统计，有的是按照请求数进行统计。例如单次提问会按实际模型调用次数扣额度，简单任务大约消耗 5–10 次，复杂任务可能 10–30+ 次等。这意味着，哪怕你 prompt 很短，只要 Agent 在底层反复读文件、调工具、重试验证，额度照样会快速下降。</p>
<p>这种计费方式的转变直接带来几个问题。首先是资源消耗不再透明，从用户端很难控制模型的请求次数，绝大多数操作都由 Agent 接管，在 LLM 引导 Agent 犯傻时，甚至会空跑额度去做大量无意义的工作。其次是，当即用即停的权力被“套餐”被包裹时，购买 Coding Plan 的决策很难说是一种便宜还是一个陷阱。就现状而言，各大厂商的 Coding Plan 或类似产品朝令夕改、霸王条款已经屡见不鲜。尤其是某些地方决策层莫名其妙地联合企业推行龙虾这种东西，导致国产算力不足的缺陷被进一步放大。一键式部署龙虾意味着一大批人将轻松的挤兑甚至挥霍本就捉襟见肘的算力，进而引发厂商的极端化应急处理，比如粗旷的封杀账号。</p>
<p>当然，从商业角度看，Coding Plan 必然是赔本赚吆喝的前期战略举措，而也有企业反其道而行之，不愿意随大流。小米推出的 Token Plan 在 Coding Plan 的基础上，不做花里胡哨的限制，直接按照 tokens 数量进行套餐售卖。但代价就是该套餐属于盈利性产品，因此价格相比其他厂家套餐会极其昂贵（因为其他厂家真的是在亏本卖）。</p>
]]></content>
    <author><name>Sun2ot</name></author>
    <category term="开发"/>
  </entry>
  <entry>
    <title>MathType Docs</title>
    <link href="https://blog.085404.xyz/posts/mathtype-docs/" rel="alternate" type="text/html"/>
    <id>https://blog.085404.xyz/posts/mathtype-docs/</id>
    <published>2026-03-24T00:00:00.000Z</published>
    <updated>2026-03-24T00:00:00.000Z</updated>
    <summary>本文提供了mathtype的安装方式，及一些需要注意的安装事项。</summary>
    <content type="html"><![CDATA[<blockquote>
<p>[!tip]
在 MS Office 已经支持 LaTeX 的今天，某种意义上说，这不是一个好的工具，使用它只是一个过时的、荒诞的、无奈的妥协。</p>
</blockquote>
<p>本文基于 <a href="https://pan.baidu.com/s/1u2WSn4NQZUMQRiRdDFVw4A">MathType 7.4.9.49</a>，提取码：<code>koop</code></p>
<h2>安装注意事项</h2>
<ol>
<li>🌟🌟🌟 在新电脑安装时，发现了一个非常现代化的问题。对于新款设备，基本都是高分辨率屏幕，而这会导致 MathType 的 exe 安装程序，在双击后可以加载，但无法显示选择安装路径等后续操作。
解决方案：右键安装程序 - <code>属性</code>，设置为低分辨率（640x480）下运行即可。</li>
<li>如果成功安装，并且软件一切正常，但就是无法将窗口显示到前台（即只能从任务栏缩略图看见），尝试修改注册表 <code>HKEY_CURRENT_USER\Control Panel\Desktop\ForegroundLockTimeout</code> 为 3，然后重启电脑。该问题极其少见，请谨慎处理注册表。（<s>也可能是多显示器，窗口超出界面了</s>）</li>
</ol>
<h2>分辨率问题</h2>
<p>https://zhuanlan.zhihu.com/p/650006277</p>
<h2>启动慢</h2>
<p>我很抵触 MathType 的一个原因就是，这玩意的操作逻辑十分丑陋，每次插入公式都是启动一个新的进程来完成，可以类比为每次发送一条 QQ 消息都要重新启动一次 QQ😅。</p>
<p>对于这个问题，有两种方案：</p>
<ol>
<li>如果安装的 MathType 有服务器模式，打开它。你的右下角任务栏会多出糊得跟马赛克一样的图标 <code>MathType server/服务器</code>。让这玩意驻留后台就可以避免频繁冷启动。</li>
<li>也可以开启一个 MathType 进程但不关闭，也就是插入一条公式，但 ctrl + s 保存后<strong>不退出编辑窗口</strong>。这样也能实现秒启动 MathType。</li>
</ol>
<h2>调整公式大小</h2>
<p>上方导航栏 - 大小 - 定义</p>
<h2>公式编号&amp;右对齐</h2>
<p>https://www.mathtype.cn/jiqiao/mathtype-jdxfz.html</p>
<p>编号右对齐后，如果还需要公式居中：</p>
<ol>
<li>在公式左边再按一下 Tab，此时是按照上述右对齐的方式设置的 Tab</li>
<li>双击左边的 Tab 会灰色高亮选中，然后右键 <code>段落</code>，将这个制表符修改为栏宽/2。例如栏宽 26 字符，这里就设置 <code>居中对齐</code>，无 <code>引导符</code>，位置 <code>13字符</code></li>
</ol>
<h2>修改公式编号字体</h2>
<p>word 中搜索 <code>应用样式</code>，下拉菜单找到 <code>MTDisplayEquation</code>，修改即可</p>
<h2>公式引用</h2>
<p>MathType 的公式引用 bug 有点多，还是 MS Word 域代码吧。</p>
]]></content>
    <author><name>Sun2ot</name></author>
    <category term="教程"/>
  </entry>
  <entry>
    <title>LaTeX 使用教程</title>
    <link href="https://blog.085404.xyz/posts/latex-%E4%BD%BF%E7%94%A8%E6%95%99%E7%A8%8B/" rel="alternate" type="text/html"/>
    <id>https://blog.085404.xyz/posts/latex-%E4%BD%BF%E7%94%A8%E6%95%99%E7%A8%8B/</id>
    <published>2026-03-19T00:00:00.000Z</published>
    <updated>2026-03-19T00:00:00.000Z</updated>
    <summary>介绍了 TexLive 的多平台安装部署方式，以及如何与 vscode 进行联动。</summary>
    <content type="html"><![CDATA[<h2>LaTex 发行版部署 (Windows)</h2>
<p>下载 <a href="https://mirrors.tuna.tsinghua.edu.cn/CTAN/systems/texlive/Images/">texlive</a> 并装载。装载 ISO 完成后，执行批处理文件安装。</p>
<p><img src="https://img.085404.xyz/images/2d24ff911eb9c033c47be6ee8983aeaf.webp" alt="image.png" /></p>
<hr />
<p>考虑到一些人可能对 ISO 文件没有概念，这里详细说一下具体的操作流程。<strong>如果你能顺利的完成上一步骤，分割线内的操作可以略过。</strong></p>
<p>点击上面的下载地址，选择下载最新的（你看到的时候不一定是 2024 了）<code>texlive$year.iso</code> 文件即可。</p>
<p><img src="https://img.085404.xyz/images/a2c6cbf12a79abfe0c67610ce831f668.webp" alt="image.png" /></p>
<p>找到下载的文件，鼠标右键，找到 <code>装载</code> 选项，点击它，完成后你会发现有一个新的 DVD 驱动器装载到了你的电脑（如下图）。ISO 文件是一种光盘镜像文件，常用于存储光盘的内容，如 CD 或 DVD 的数据，这里你可以理解为 <code>texlive</code> 的安装包通过一个虚拟的 U 盘插在了你的电脑上，现在你只需要进去找到安装文件执行就 OK 了。</p>
<p><img src="https://img.085404.xyz/images/90d870bcc90c110b8792962882f4aeea.webp" alt="image.png" /></p>
<p>右键这个驱动器，打开它，你就可以成功找到 <a href="#LaTex%20%E5%8F%91%E8%A1%8C%E7%89%88%E9%83%A8%E7%BD%B2%20(Windows)">文章开头</a> 提到的<strong>批处理安装文件</strong>。</p>
<blockquote>
<p>或者你直接双击上方图片的这个驱动器也行，会自动弹出安装界面的，all right？</p>
</blockquote>
<hr />
<p>可<strong>酌情</strong>修改安装位置。</p>
<p><img src="https://img.085404.xyz/images/e74aa2342795b7004fb7ed383fffceb2.webp" alt="image.png" /></p>
<p>注意，他的修改很有意思，子路径是分割开的进行设置的，所以一般只需要修改盘符即可。</p>
<p><img src="https://img.085404.xyz/images/c2d7139b9fad54a0d0282043be7169f0.webp" alt="image.png" /></p>
<p>点击 <code>Advanced</code> 进入高级设置菜单，这里还可以设置一些东西。</p>
<p><img src="https://img.085404.xyz/images/b73ca149f7a21781528a1ff76552052f.webp" alt="image.png" /></p>
<p>首先是语言。通常情况下中文 + 英文即可。在 <code>Customize</code> 菜单中，勾选 “<code>Chinese</code>”、“C <code>hinese/Japanese/Korean (base)</code>”，再往下拉，勾选 “<code>US and UK English</code>”，然后点击 “<code>确定</code>”。</p>
<p>准备就绪后，点击右下角“安装”。</p>
<p><img src="https://img.085404.xyz/images/583214f8169e8daa80978b5a6a60b28d.webp" alt="image.png" /></p>
<blockquote>
<p>安装过程涉及大量 4K 小文件读写，硬盘拉跨可能会很慢，请耐心等待。</p>
</blockquote>
<h2>LaTex 发行版部署 (Linux)</h2>
<p>安装来源同 Windows 一致，仍然需下载 <a href="https://mirrors.tuna.tsinghua.edu.cn/CTAN/systems/texlive/Images/">texlive</a> ISO。</p>
<blockquote>
<p>[!tip]
如果使用 WSL 进行部署，无需迁移复制，直接跨文件系统读取即可。</p>
</blockquote>
<h3>挂载镜像</h3>
<pre><code>sudo mkdir /mnt/texlive
sudo mount /mnt/d/Downloads/texlive2026.iso /mnt/texlive
</code></pre>
<h3>启动安装程序</h3>
<pre><code>cd /mnt/texlive
sudo ./install-tl
</code></pre>
<h3>安装配置</h3>
<blockquote>
<p>[!warning]
本节为可选内容，你可以在启动安装程序后直接输入 <code>I</code> 进行安装。</p>
</blockquote>
<p>启动安装程序后，可以在终端看到如下内容：
<img src="https://img.085404.xyz/images/e35bcae1060c85d64349969392b0cdb8.webp" alt="image.png|300*400" /></p>
<p>输入 <code>C</code> 进行安装组件配置：</p>
<pre><code>===============================================================================
Select collections:

 a [X] Essential programs and files      w [X] Italian
 b [X] BibTeX additional styles          x [X] Japanese
 c [X] TeX auxiliary programs            y [X] Korean
 d [X] ConTeXt and packages              z [X] Other languages
 e [X] Additional fonts                  A [X] Polish
 f [X] Recommended fonts                 B [X] Portuguese
 g [X] Graphics and font utilities       C [X] Spanish
 h [X] Additional formats                D [X] LaTeX fundamental packages
 i [X] Games typesetting                 E [X] LaTeX additional packages
 j [X] Humanities packages               F [X] LaTeX recommended packages
 k [X] Arabic                            G [X] LuaTeX packages
 l [X] Chinese                           H [X] MetaPost and Metafont packages
 m [X] Chinese/Japanese/Korean (base)    I [X] Music packages
 n [X] Cyrillic                          J [X] Graphics, pictures, diagrams
 o [X] Czech/Slovak                      K [X] Plain (La)TeX packages
 p [X] US and UK English                 L [X] PSTricks
 s [X] Other European languages          M [X] Publisher styles, theses, etc.
 t [X] French                            N [ ] Windows-only support programs
 u [X] German                            O [X] XeTeX and packages
 v [X] Greek
 P [X] Mathematics, natural sciences, computer science packages
 S [X] TeXworks editor; TL includes only the Windows binary

Actions: (disk space required: 9388 MB)
 &lt;-&gt; deselect all
 &lt;+&gt; select all
 &lt;R&gt; return to main menu
 &lt;Q&gt; quit

Enter letter(s) to (de)select collection(s):
</code></pre>
<p>每个配置项通过字母进行控制，输入对应的字母并回车即可进行选中/取消选中。<strong>该过程可以反复操作</strong>，因此不必力求一次性输入所有字母。这里给出一个较为保守的取消配置方案：</p>
<pre><code>knostuvxyzABC # 语言类 (保留了中/英)
iI # 游戏和音乐排版，极其小众的需求
S # 编辑器, 由于使用vscode, 因此不需要
</code></pre>
<blockquote>
<p>[!tip]
完整安装其实也就 9G，上面一顿操作也就省 1G 左右空间，代价则是编译某个模板报错或者某个 Journal 模板炸了。
但是，即使有些宏包在上面精简了，后期仍然可以使用 <code>tlmgr</code> 工具来<strong>单独安装</strong>。因此不用太担心。</p>
</blockquote>
<p>确认配置完成后，输入 <code>R</code> 并回车，返回上一级菜单。</p>
<h3>开始安装</h3>
<p>输入 <code>I</code> 并回车，开始安装。由于安装过程涉及大量小文件读写，因此安装时间取决于机器的硬件性能，耐心等待即可。</p>
<p>安装完成后，配置环境变量：</p>
<pre><code>export PATH="/usr/local/texlive/2026/bin/x86_64-linux:$PATH"
export MANPATH="/usr/local/texlive/2026/texmf-dist/doc/man:$MANPATH"
export INFOPATH="/usr/local/texlive/2026/texmf-dist/doc/info:$INFOPATH"
</code></pre>
<p>然后注册 texlive 字体：</p>
<pre><code>sudo cp /usr/local/texlive/2026/texmf-var/fonts/conf/texlive-fontconfig.conf /etc/fonts/conf.d/09-texlive.conf
sudo fc-cache -fsv
</code></pre>
<p>此外，Linux 上需要额外下载 <code>ghostscript</code>，否则 eps 转 pdf 时会失败：</p>
<pre><code>sudo apt update
sudo apt install ghostscript
</code></pre>
<p>上述步骤做完后，即可卸载 texlive 镜像：</p>
<pre><code>sudo umount /mnt/texlive
</code></pre>
<h2>vscode 配置</h2>
<p>首先声明，在 texlive 安装完成后，默认会自带一个编辑器 <code>TeXworks editor</code>，是可以正常使用的。但谁能拒绝宇宙第一开源编辑器 vscode 呢🤣。</p>
<p>只需要安装插件 <code>LaTex Workshop</code> 即可让 vscode 支持 LaTeX 编辑。此外，还需要添加如下配置文件。</p>
<blockquote>
<p>注意，该配置仅需工作区生效即可。</p>
<p>如果不清楚 vscode 的分区管理，那就在项目根目录下新建一个 <code>.vscode/settings.json</code>，然后将下面配置复制粘贴进去即可。</p>
</blockquote>
<pre><code>{
    //输出文件的保存目录(会自动在当前路径下创建build文件夹)
    "latex-workshop.latex.outDir": "%DIR%/build",
    //光标在公式上时会自动悬浮渲染
    "latex-workshop.mathpreviewpanel.cursor.enabled": true,
    //使用默认的(第一个)编译链自动构建 LaTeX 项目
    "latex-workshop.latex.autoBuild.run": "onSave",
    "latex-workshop.showContextMenu": true,
    //从使用的宏包中自动提取命令和环境，补全正在编写的代码
    "latex-workshop.intellisense.package.enabled": true,
    //文档编译错误时是否弹出显示出错和警告的弹窗(都可从终端获取)
    "latex-workshop.message.error.show": false,
    "latex-workshop.message.warning.show": false,
    //定义编译工具
    "latex-workshop.latex.tools": [
        {
            "name": "xelatex",
            "command": "xelatex",
            "args": [
                "-synctex=1",
                "-interaction=nonstopmode",
                "-file-line-error",
                "--output-directory=%OUTDIR%",
                "%DOCFILE%" //%DOC%
            ]
        },
        {
            "name": "pdflatex",
            "command": "pdflatex",
            "args": [
                "-synctex=1",
                "-interaction=nonstopmode",
                "-file-line-error",
                "--output-directory=%OUTDIR%",
                "%DOCFILE%"
            ]
        },
        {
            "name": "latexmk",
            "command": "latexmk",
            "args": [
                "-synctex=1",
                "-interaction=nonstopmode",
                "-file-line-error",
                "-pdf",
                "-outdir=%OUTDIR%",
                "%DOCFILE%"
            ]
        },
        {
            "name": "bibtex",
            "command": "bibtex",
            "args": [
                "build/%DOCFILE%"
            ]
        }
    ],
    //定义编译链
    "latex-workshop.latex.recipes": [
        {
            "name": "XeLaTeX",
            "tools": [
                "xelatex"
            ]
        },
        {
            "name": "PDFLaTeX",
            "tools": [
                "pdflatex"
            ]
        },
        {
            "name": "BibTeX",
            "tools": [
                "bibtex"
            ]
        },
        {
            "name": "LaTeXmk",
            "tools": [
                "latexmk"
            ]
        },
        {
            "name": "xelatex -&gt; bibtex -&gt; xelatex*2",
            "tools": [
                "xelatex",
                "bibtex",
                "xelatex",
                "xelatex"
            ]
        },
        {
            "name": "pdflatex -&gt; bibtex -&gt; pdflatex*2",
            "tools": [
                "pdflatex",
                "bibtex",
                "pdflatex",
                "pdflatex"
            ]
        },
    ],
    //编译完成后要清除掉的辅助文件类型(如无需求，请勿修改)
    "latex-workshop.latex.clean.fileTypes": [
        "*.aux",
        "*.bbl",
        "*.blg",
        "*.idx",
        "*.ind",
        "*.lof",
        "*.lot",
        "*.out",
        "*.toc",
        "*.acn",
        "*.acr",
        "*.alg",
        "*.glg",
        "*.glo",
        "*.gls",
        "*.ist",
        "*.fls",
        "*.log",
        "*.fdb_latexmk"
    ],
    //何时对上文设置的辅助文件进行清除
    "latex-workshop.latex.autoClean.run": "onFailed",
    //设置默认编译链
    "latex-workshop.latex.recipe.default": "pdflatex -&gt; bibtex -&gt; pdflatex*2",
    //反向同步快捷键绑定
    "latex-workshop.view.pdf.internal.synctex.keybinding": "double-click",

    "[latex]": {
        "editor.wordWrap": "on",  //自动换行
        "editor.wordWrapColumn": 80
    },
}
</code></pre>
<p>理论上说，安装过程中会自动配置环境变量，从而让 <code>xelatex</code> 等命令全局生效。但如果编译时一直检测不到，可以换成绝对路径引用，或者尝试<strong>重启电脑</strong>。</p>
<p><strong>看不懂上面的内容就看这里</strong>。你要做的是三件事：</p>
<ol>
<li>下载安装 vscode</li>
<li>下载一个叫做 <code>LaTex Workshop</code> 的插件，图标是一个钢笔一样的东西</li>
<li>在 vscode 中打开你的 LaTeX 模板文件夹</li>
<li>在这个文件夹的根目录下，新建一个文件夹 <code>.vscode</code>，然后在 <code>.vscode</code> 文件夹里再新建一个 <code>settings.json</code> 文件</li>
<li>把上面的配置文件内容复制粘贴到 <code>settings.json</code>，保存</li>
<li>你的 vscode 右侧会多出一个 LaTeX 的操作面板，点进去试吧</li>
<li><strong>还有任何不会用的就百度去</strong>，只要你投的不是什么十分野鸡的期刊，网上很容易就能找到 LaTeX 模板的使用方法</li>
</ol>
<h3>LaTex Workshop 占位符说明</h3>
<p>参考 https://github.com/James-Yu/LaTeX-Workshop/wiki/Compile#placeholders</p>
<p>首先在配置项 <code>outDir</code> 中指定全局输出路径，这里 <code>%DIR</code> 指工作区根路径：</p>
<pre><code>"latex-workshop.latex.outDir": "%DIR%/build",
</code></pre>
<blockquote>
<p>注意编译前请提前创建好对应路径 <code>build/</code></p>
</blockquote>
<p>然后，在各编译器中，即可使用占位符 <code>%OUTDIR%</code> 表示输出路径。</p>
<blockquote>
<p>参考官方文档：“<code>%OUTDIR%</code> is the output directory configured in <a href="https://github.com/James-Yu/LaTeX-Workshop/wiki/View#latex-workshoplatexoutdir"><code>latex-workshop.latex.outDir</code></a>”</p>
</blockquote>
<p>以 <code>xelatex</code> 编译器为例，可以看见其输出参数为 <code>-output-directory</code>：</p>
<pre><code>xelatex -help

-output-directory=DIR   use existing DIR as the directory to write files in
</code></pre>
<p>因此配置编译工具 <code>latex-workshop.latex.tools</code>：</p>
<pre><code>// 使用占位符 %OUTDIR%
{
    "name": "xelatex",
    "command": "xelatex",
    "args": [
        "-synctex=1",
        "-interaction=nonstopmode",
        "-file-line-error",
        "--output-directory=%OUTDIR%",
        "%DOCFILE%"
    ]
}
</code></pre>
<p>类似的，为 <code>latexmk</code> 添加参数：</p>
<pre><code>{
    "name": "latexmk",
    "command": "latexmk",
    "args": [
        "-synctex=1",
        "-interaction=nonstopmode",
        "-file-line-error",
        "-pdf",
        "-outdir=%OUTDIR%",
        "%DOCFILE%"
    ]
}
</code></pre>
<p>但注意，<code>bibtex</code> 没有该参数，直接通过 <code>bibtex xxx</code> 进行编译，因此修改为:</p>
<pre><code>{
    "name": "bibtex",
    "command": "bibtex",
    "args": [
        "build/%DOCFILE%"
    ]
}
</code></pre>
<blockquote>
<p>[!caution]
注意这里 bibtex 的配置，需指定参数为 <code>build/</code> 而不是用占位符 <code>%OUTDIR%</code> 代替，这是由于 LaTeX 的安全考虑，可参考如下链接：</p>
<ul>
<li><a href="https://blog.csdn.net/weixin_43959160/article/details/139213483">解决问题：bibtex.exe: Not writing to xxx/xxx.blg</a></li>
<li><a href="https://tex.stackexchange.com/questions/223870/bibtex-error-not-writing-to-book-blg-openout-any-p-mactex-2014">bibtex error: Not writing to book.blg (openout_any = p)</a></li>
</ul>
</blockquote>
<h2>中文支持</h2>
<p>参考 <a href="https://cn.overleaf.com/learn/latex/Chinese">overleaf latex Chinese</a></p>
<p>推荐使用 XeLaTeX 和 LuaLaTeX 来编译含有中文字符的 <code>.tex</code> 文件：</p>
<ol>
<li>直接使用 <code>ctexart</code> 文档类即可支持中文</li>
<li>或者使用 <code>ctex</code> 包来支持中文</li>
</ol>
<pre><code>\documentclass{ctexart}

\begin{document}
\tableofcontents

\begin{abstract}
这是简介及摘要。
\end{abstract}

\section{前言}

\section{关于数学部分}
数学、中英文皆可以混排。You can intersperse math, Chinese and English (Latin script) without adding extra environments.

這是繁體中文。
\end{document}
</code></pre>
<p>或者</p>
<pre><code>\documentclass{xxx}
\usepackage{ctex}
</code></pre>
<h2>latexdiff</h2>
<blockquote>
<p>[!tip]
本节内容需要你在撰写文档时，使用 Git 进行了版本控制。</p>
<p>换言之，如果你对工程项目的管理使用的是复制粘贴大法，恕我爱莫能助。</p>
</blockquote>
<h3>1. 确认环境</h3>
<p>在命令行（cmd 或 PowerShell）里输入：</p>
<pre><code>latexdiff --version
</code></pre>
<p>如果能看到版本号，说明 TeXLive 已经带有 <code>latexdiff</code>。</p>
<p>如果报错，就需要安装（TeXLive 通常自带）。</p>
<h3>2. 使用 git 导出不同 commit 的文件版本</h3>
<p>假设论文主文件叫 <code>paper.tex</code>，你有两个 commit：</p>
<ul>
<li>老版本：<code>OLD_COMMIT</code></li>
<li>新版本：<code>NEW_COMMIT</code></li>
</ul>
<p>可以这样导出：</p>
<pre><code>git show OLD_COMMIT:paper.tex &gt; old.tex
git show NEW_COMMIT:paper.tex &gt; new.tex
</code></pre>
<p>这样你就得到了 <code>old.tex</code> 和 <code>new.tex</code> 两个版本。通常情况下，<code>new.tex</code> 就是当前修改完成的版本，因此只需要根据 commit 记录导出修订前的版本即可。</p>
<h3>3. 用 latexdiff 生成标记文档</h3>
<blockquote>
<p>[!caution]
<code>latexdiff</code> 本质是“文本级 diff”，它<strong>不理解 LaTeX 结构</strong>，所以 99% 的情况下，生成的标记文档是无法编译成功了。请耐心排查问题，虽然这很麻烦，但相比于你手动修订还是快很多的。</p>
<p>当然，你也可以选择 WORD 投稿 🤝</p>
</blockquote>
<p>如果你能理解 latexdiff 的风险，那么可执行以下指令：</p>
<pre><code>latexdiff --math-markup=1 \
          --disable-citation-markup \
          --config="PICTUREENV=(?:figure|table|table*|tabular|tabular*)" \
          --exclude-textcmd="section,subsection,subsubsection,caption" \
          old.tex new.tex &gt; diff.tex
</code></pre>
<p>上述命令相比于默认的 <code>latexdiff</code> 操作，做了如下几件事情：</p>
<ol>
<li><strong>保留默认对数学环境的标记。</strong></li>
<li><strong>禁止对引用命令的内容做差异标记</strong>：即使两个版本中 <code>\cite{A}</code> 变成了 <code>\cite{B}</code>，也不会显示为“删除 A、新增 B”，而是整体视为未变或原样保留。</li>
<li>扩展 <code>latexdiff</code> 认为“不应拆分内部内容”的<strong>浮动体/表格环境</strong>列表：对声明的环境视为一个不可分割的单元，不在内部插入 <code>\DIFadd</code> 或者 <code>\DIFdel</code></li>
<li>指定部分命令的参数<strong>不参与字词级差异分析</strong>，而是作为整体处理：包括各级标题、图表名。</li>
</ol>
<p>需注意，上述指令关闭了一些内容的对比生成，但不代表你应该忽视。对于一些重要的改变，你应当手动注明修改：添加 <code>DIFadd/DIFdel</code> 标记等。此外，如存在大量复杂公式，则 <code>--math-markup=1</code> 仍可能导致编译失败，可尝试置其为 <code>0</code> 以避免数学公式的嵌套标记生成。</p>
<p>如果碰上字符集报错，例如：</p>
<pre><code>latexdiff .\origin.tex .\sn-article.pdf &gt; major_diff.tex

utf8 "\xD0" does not map to Unicode at f:\texlive\2024\texmf-dist\scripts\latexdiff\latexdiff.pl line 1826, &lt;FILE&gt; chunk 1.
Either both texts must have preamble or neither text must have the preamble.
</code></pre>
<p>可通过手动指定字符集解决：</p>
<pre><code>latexdiff --encoding=utf8 .\origin.tex .\sn-article.tex &gt; major_diff.tex
</code></pre>
<p>然后编译生成的对比文档即可获得带修订版本的稿件。</p>
<h2>参考</h2>
<ul>
<li><a href="https://github.com/shinyypig/latex-vscode-config">shinyypig/latex-vscode-config</a></li>
<li><a href="https://blog.iw17.cc/install-latex/">blog.iw17.cc/install-latex</a></li>
<li><a href="https://blog.iw17.cc/vscode-latex/">blog.iw17.cc/vscode-latex</a></li>
<li><a href="https://zhuanlan.zhihu.com/p/166523064">知乎：Visual Studio Code (vscode)配置LaTeX</a></li>
</ul>
]]></content>
    <author><name>Sun2ot</name></author>
    <category term="教程"/>
  </entry>
  <entry>
    <title>图片转换专题</title>
    <link href="https://blog.085404.xyz/posts/%E5%9B%BE%E7%89%87%E8%BD%AC%E6%8D%A2%E4%B8%93%E9%A2%98/" rel="alternate" type="text/html"/>
    <id>https://blog.085404.xyz/posts/%E5%9B%BE%E7%89%87%E8%BD%AC%E6%8D%A2%E4%B8%93%E9%A2%98/</id>
    <published>2026-03-18T00:00:00.000Z</published>
    <updated>2026-03-18T00:00:00.000Z</updated>
    <summary>介绍了一些少见特殊格式的图片转换方式</summary>
    <content type="html"><![CDATA[<h2>一、写在前面</h2>
<p>主要工具包括：<a href="https://inkscape.org/">Inkscape</a>，<a href="https://ghostscript.com/">Ghostscript</a>，<a href="https://imagemagick.org/script/download.php#gsc.tab=0">Image Magick</a>。搜索 <code>格式a-格式b</code> 可快速检索，例如 <code>PDF-EMF</code>。</p>
<blockquote>
<p>[!tip]</p>
<ol>
<li>并不是说每一类工具只能进行如下列举的格式转换，但是不同工具各有所长，因此只给出推荐的格式转换。</li>
<li>本文不定时更新。</li>
</ol>
</blockquote>
<h2>二、Inkscape</h2>
<pre><code># PDF-EMF: latex论文图片转word
inkscape input.pdf --export-type=emf --export-filename=output.emf

# SVG-EMF: matplotlib输出矢量图转word
inkscape input.svg --export-type=emf --export-filename=output.emf
</code></pre>
<p>注意，如果 PDF 是 matplotlib 绘制的，需确保代码中设置 Type1 或 TrueType 字体，否则默认的 Type3 字体将导致 Inkscape 解析失败：</p>
<pre><code>plt.rcParams.update({
    "pdf.fonttype": 42,      # 使用 TrueType 字体（Type42），兼容性最好
    "ps.fonttype": 42,       # 同时设置 PostScript 字体类型（以防转 EPS）
})
</code></pre>
<h2>三、Ghostscript</h2>
<pre><code># PDF-EPS: 矢量图转上古期刊图片
gswin64c.exe -sDEVICE=eps2write -dPDFSETTINGS=/prepress -dNOPAUSE -dQUIET -o "taxonomy.eps" "taxonomy.pdf"
</code></pre>
<ul>
<li><code>*.eps</code> 文件是输出文件，<code>*.pdf</code> 文件是输入文件</li>
<li><code>-dPDFSETTINGS</code> 可选：
<ul>
<li><code>/screen</code>：适用于屏幕显示，压缩图像，适当降低质量，减小文件大小。</li>
<li><code>/ebook</code>：适合电子书，图像质量适中。</li>
<li><code>/printer</code>：适合打印，高质量的图像。</li>
<li><code>/prepress</code>：用于出版行业，最高质量输出 (通常用这个就行)。</li>
</ul>
</li>
</ul>
<h2>四、Image Magick</h2>
<pre><code># 批量转换-PDF: 通常情况下，通过代码/程序可以直接输出 PDF 格式图片。但如果存在部分位图需要转换，可通过 image magick 实现
magick mogrify -format pdf -compress zip *.png

# 单一转换-PDF
magick input.jpg -compress zip output.pdf
</code></pre>
]]></content>
    <author><name>Sun2ot</name></author>
    <category term="教程"/>
  </entry>
  <entry>
    <title>Office Word Docs</title>
    <link href="https://blog.085404.xyz/posts/office-word-doc/" rel="alternate" type="text/html"/>
    <id>https://blog.085404.xyz/posts/office-word-doc/</id>
    <published>2026-03-17T00:00:00.000Z</published>
    <updated>2026-03-17T00:00:00.000Z</updated>
    <summary>适用于论文等极度精密文档的格式控制教程</summary>
    <content type="html"><![CDATA[<h2>不自动选中换行符</h2>
<p><code>文件</code> - <code>选项</code> - <code>高级</code>，取消勾选 <code>智能段落选择</code></p>
<h2>页眉/页脚</h2>
<p><code>插入</code> - <code>页眉/页脚</code></p>
<h3>底部横线</h3>
<p>选中页眉/页脚中的内容（空白就选中回车符），点击 <code>开始</code> - <code>边框</code> - <code>边框和底纹</code></p>
<p>调节颜色、样式、宽度等，然后点击设置 <code>下框线</code>。</p>
<blockquote>
<p>[!tip]
注意，如果是<strong>上粗下细</strong>的文武线，要选择显示效果<strong>上细下粗</strong>的那个样式，反之亦然。不知道为什么是反过来的。</p>
</blockquote>
<h3>奇偶页不同</h3>
<p>双击页眉/页脚后，进入 <code>页眉和页脚</code> ，勾选 <code>奇偶页不同</code></p>
<blockquote>
<p>[!tip]
设置页码奇偶页不同：同上，勾选 <code>奇偶页不同</code> ，添加页码后，复制奇数页生成的页码，<strong>复制粘贴</strong>到偶数页，切记不能手动输入！</p>
</blockquote>
<h3>内部内容对其</h3>
<p><code>页眉和页脚</code> - <code>插入对齐制表符位</code></p>
<blockquote>
<p>[!warning]
这个对其制表符可以插入多个，所以如果页码和内容由间隔，例如页码居左 &amp; 内容居中，只需要多次插入即可</p>
</blockquote>
<h3>页眉插入章节标题</h3>
<p><code>插入</code> - <code>文档部件</code> - <code>域</code> - <code>StyleRef (倒数往下找)</code>，选择 <code>域属性</code>（就是你要引用哪个样式，通常是标题 1），勾选右侧 <code>插入段落编号</code>。</p>
<h2>脚注</h2>
<p>脚注生成的文本是可以直接调整格式的，正常调整即可。</p>
<p>调整脚注上方的“脚注分隔符”：先切换到草稿视图 <code>视图</code> - <code>草稿</code>，然后在 <code>引用</code> 中，点击 <code>显示备注</code>。在下方下拉菜单中，选中 <code>脚注分隔符</code>，即可进行调整。完成后切换回 <code>页面视图</code> 即可。</p>
<h2>页面布局</h2>
<h3>分栏</h3>
<p><code>布局</code> - <code>栏</code> ，<strong>框选需要分栏的部分</strong>，选择 <code>两栏</code></p>
<blockquote>
<p>[!caution]
左边栏没有满，就跑到右边栏，即左右栏均匀分布的问题：主要是可能在中间部分引入了非分栏的表格等结构，导致插入了 <code>连续分节符</code>。默认情况下，分栏是左边满了再去右边，因此请核实 <code>分节符</code> 位置，尤其是<strong>审阅模式下隐藏内容里的分节符！！！</strong></p>
</blockquote>
<h2>西文换行</h2>
<p><code>段落</code> - <code>中文版式</code> - 勾选 <code>允许西文单词在中间换行</code></p>
<blockquote>
<p>[!tip]
参考文献两端对齐后大面积空白，可用此法解决</p>
</blockquote>
<h2>上标不够靠上</h2>
<p><code>字体</code> - <code>高级</code> - <code>位置</code> 改为 <code>上升</code> - <code>磅值</code> 按需设置即可</p>
<blockquote>
<p>[!warning]
<s>如果磅值设置后导致行距变大，请在正文的 <code>段落</code> 中，取消勾选 <code>对其文档网格</code></s></p>
<p>好像把行间距设置为<strong>固定值</strong>就行了</p>
</blockquote>
<h2>样式自动更新</h2>
<ul>
<li>开启后，如果修改了某一标题的<strong>全部内容</strong>，则所有与之样式相同的部分都会变化</li>
<li>若关闭，则不会</li>
</ul>
<blockquote>
<p>[!tip]
关于自动更新，如果此前已经修改过某些使用了该样式的部分，则样式自动更新似乎不会作用于这些部分上</p>
</blockquote>
<h2>公式</h2>
<p><a href="https://zyxin.xyz/blog/2017-08/word-math/">Word - 快速输入数学公式 | JacobZ (zyxin.xyz)</a></p>
<p><a href="https://support.microsoft.com/zh-cn/office/word-%E4%B8%AD%E4%BD%BF%E7%94%A8-unicodemath-%E5%92%8C-latex-%E7%9A%84%E7%BA%BF%E6%80%A7%E6%A0%BC%E5%BC%8F%E5%85%AC%E5%BC%8F-2e00618d-b1fd-49d8-8cb4-8d17f25754f8?ui=zh-cn&amp;rs=zh-cn&amp;ad=cn">Word 中使用 UnicodeMath 和 LaTeX 的线性格式公式 - Microsoft 支持</a></p>
<p>已知 office word 中与 LaTeX 不一致的地方：</p>
<pre><code># 下方约束条件
latex:  \lim\limits_{constrain}
word:  \lim\below{constrain}
</code></pre>
<h3>公式编号</h3>
<p>有两种方法：使用 word 进行编号，或者依赖 MathType 进行编号。</p>
<h4>word 编号 (非 MathType)</h4>
<p>首先说 word 明面上一种自带的公式编辑方法，即 <code>插入</code> 选项卡中的 <code>编号</code>。</p>
<pre><code># 在公式框内部的最右侧通过 #(编号)，然后回车
E=mc^2#(1)
</code></pre>
<p>生成效果与下方相同</p>
<p>$$
E=mc^2\tag{1}
$$</p>
<p>这种方式的主要优点在于<strong>会将生成的编号自动进行右对齐</strong>，但存在一个问题：如果需要对公式进行交叉引用，在引用界面是看不到这个编号效果的。</p>
<p>类似这种方案，网上还可以搜索到通过 <code>SEQ</code> 域代码进行编号的，但不仅效果一致，而且存在的问题也一样。</p>
<blockquote>
<p>参考官方文档 <a href="https://support.microsoft.com/zh-cn/office/%E5%AD%97%E6%AE%B5%E4%BB%A3%E7%A0%81-seq-%E5%BA%8F%E5%88%97-%E5%AD%97%E6%AE%B5-062a387b-dfc9-4ef8-8235-29ee113d59be">Seq (序列) 字段</a>。</p>
</blockquote>
<hr />
<p>下面介绍 word 第二种编号方法：域。</p>
<p>https://zhuanlan.zhihu.com/p/109212246</p>
<ol>
<li>公式完成后，输入 <code>#()</code></li>
<li>新建一个多级列表，在 <code>更多</code> 选项卡内，输入 <code>ListNum</code> 名称 (随便自定义)</li>
<li>在括号内， <code>插入</code> - <code>文档部件</code> - <code>域</code></li>
<li><code>ListNum</code> - 选中自定义名称的列表</li>
<li>回车，即可实现自动编号 + 右对齐</li>
</ol>
<p>上述过程也可以通过域代码的方式进行：</p>
<ol>
<li>公式完成后，输入 <code>#()</code></li>
<li>新建一个多级列表，在 <code>更多</code> 选项卡内，输入 <code>ListNum</code> 名称 (随便自定义，例如 <code>eq</code>)</li>
<li>在括号内，按 <code>ctrl + F9</code> 添加域，输入 <code>listnum eq</code></li>
<li>按 <code>F9</code> 渲染域</li>
<li>回车，即可实现自动编号 + 右对齐</li>
</ol>
<h4>MathType 编号</h4>
<p>太傻逼了这个，算了不写了。</p>
<h4>最佳实践</h4>
<p>经过较长时间的使用，总结出最佳实现方式如下：</p>
<ol>
<li>使用 <code>SEQ</code> 域代码进行自动编号</li>
<li>框选生成编号，<code>ctrl + shift + F5</code> 进行书签生成。
这一步可以根据公式含义自行制定书签名称，便于后续交叉引用。</li>
<li>使用 <code>REF</code> 域代码进行公式引用，例如 <code>REF all_loss</code>，其中 <code>all_loss</code> 为<strong>书签</strong>，对应第 2 步添加的公式书签。</li>
</ol>
<h3>MathType 公式居中，编号右对齐</h3>
<ol>
<li>首先打开 <code>布局</code> - <code>栏</code> - <code>更多栏</code>，记录下栏宽（例如 x 字符）</li>
<li>先将公式前的空格删除，移动到最左侧</li>
<li>右键 <code>段落</code> - <code>制表位</code></li>
<li>制表位位置 = x 字符，对齐方式：右对齐，引导符：无</li>
<li>在编号前按下 <code>Tab</code>，即可实现编号右对齐</li>
<li>复制这个制表符，粘贴到公式左侧</li>
<li>继续右键 <code>段落</code> - <code>制表位</code></li>
<li>制表位位置 = x/2 字符，对齐方式：居中对齐，引导符：无</li>
<li>即可实现公式居中</li>
</ol>
<blockquote>
<p>经过实测，插入公式时光标顶格，保存公式后按一下 <code>Tab</code> 似乎可以自动跳转到当前栏的最右端。暂不清楚是否跟 MathType 的版本有关。</p>
</blockquote>
<h3>注意事项</h3>
<ol>
<li>无论是 MS 还是 WPS，通过<strong>题注</strong>进行公式编号时，一旦删除了中间某个公式，剩余的公式虽然会重新自动编号，但是，从修改位置之后的<strong>所有题注引用会报错找不到书签</strong>。</li>
<li>如果通过 <code>SEQ</code> 编号然后手动添加书签，可以使得其余位置公式正常重排，只会丢失被删除公式的书签。</li>
</ol>
<h2>表格</h2>
<h3>列表第二行内容顶头</h3>
<p>在列表段落右键 - <code>调整列表缩进</code> - <code>文本缩进</code> 设置为 0 即可</p>
<h3>制表符问题</h3>
<p>如果碰上那种在文中使用外侧框线表格，将文章划分为一个一个块块的文档（没错，傻逼学校文档，说的就是你），如果要在里面使用公式，按 <code>Tab</code> 进行对齐是不好使的，因为表格中的 <code>Tab</code> 默认是移动到下一格子，因此需要使用 <code>Ctrl + Tab</code>。</p>
<h3>找不到“段落”设置</h3>
<p>可以尝试以下方法：</p>
<ol>
<li>100% 可行，在开始选项卡点段落右下角的按钮即可</li>
<li>框选表格里的一段文字，再右键点击段落</li>
</ol>
<h3>设置表边框不生效</h3>
<p>设置三线表时，可能会出现明明设置了边框宽度，但是不生效的情况。因为边框设置生效是有顺序要求的。执行以下步骤即可：</p>
<ol>
<li>先<strong>取消</strong>对应位置表边框（例如下框线）</li>
<li>然后设置框线厚度</li>
<li>最后重新勾选生效对应位置的框线，即可生效。</li>
</ol>
<h3>修改表格内容字体</h3>
<p>在面对某些特定表格，例如实验结果时，我们可能希望表格中的文本可以像正文一样，通过样式统一设置。但当我们新建一个样式后，例如：中文宋体、英文 Times New Roman，然后选中/框选整个表格，点击样式会发现无法生效。表格中文会锁定宋体，英文会锁定 Calibri。只有单独选中表格中的部分内容进行字体修改才能生效。</p>
<p>这是因为表格内容的样式并不是通过外部设置的，而是单独的表格样式：
<code>选中表格</code> - <code>表设计</code> - <code>表格样式下拉菜单</code> - <code>修改表格样式</code></p>
<p>在这里面就可以对内容的字体进行修改了。</p>
<h3>表格内容无法垂直居中</h3>
<p>即使在表格属性中设置了单元格垂直居中，会发现文本仍然存在下边距（等效视觉为整体靠上）。解决方案为：<code>文件</code> - <code>选项</code> - <code>高级</code> - <code>以下对象的布局选项</code> - 勾选 <code>在表格中将行高调至网格高度</code>。</p>
<h3>三线表横线中间断开</h3>
<p><code>边框和底纹</code> 中设置框线时，<code>作用于</code> 下拉菜单设置为 <code>段落</code> 即可。注意，不同作用于的框线可以单独设置，所以取消其中的框线不会影响其他作用域的样式。</p>
<h2>插图</h2>
<blockquote>
<p>图注写在图下面</p>
</blockquote>
<h3>图编号</h3>
<p>通过 <code>引用</code> - <code>插入题注</code> 来进行图编号，注意以下几点：</p>
<ol>
<li><code>标签</code> 指的是图标号前面的内容，例如 <code>图1</code> ， <code>Fig.1</code> ， <code>标签</code> 就是 <code>图</code> 和 <code>Fig</code></li>
<li>你想使用的 <code>标签</code> 不存在时，可以 <code>新建标签</code></li>
<li>生成的题注格式为 <code>标签&lt;空格&gt;序号</code> ，其中的 <code>空格</code> 属于 word 本身规定，只能手动删除</li>
<li><code>插入题注</code> 不具备样式链接功能，需要手动调整样式</li>
</ol>
<h3>设置图片大小</h3>
<p>右键图片 - <code>大小和位置</code> 可以精确设置图片大小。如果有多张图片需要调整为一致的大小，在调整好第一张后，<strong>直接选中</strong>其余图片，按 <code>F4</code> 即可重复刚刚的调整操作。</p>
<h2>参考文献</h2>
<p>zotero 插入的参考文献，是不支持自动交叉引用的。目前测试的一种可行方案如下：</p>
<ol>
<li>新建 <code>编号</code> 样式（不是多级列表），设置字体等样式</li>
<li>框选所有 zotero 生成的参考文献，使用 <code>编号</code></li>
<li>删除 zotero 生成的 <code>[数字]</code> （其实也可以在 citation 文件中修改，待评估）</li>
<li>修改原文中的参考文献引用即可</li>
</ol>
]]></content>
    <author><name>Sun2ot</name></author>
    <category term="教程"/>
  </entry>
  <entry>
    <title>甲骨文抢机教程</title>
    <link href="https://blog.085404.xyz/posts/%E7%94%B2%E9%AA%A8%E6%96%87%E6%8A%A2%E6%9C%BA%E6%95%99%E7%A8%8B/" rel="alternate" type="text/html"/>
    <id>https://blog.085404.xyz/posts/%E7%94%B2%E9%AA%A8%E6%96%87%E6%8A%A2%E6%9C%BA%E6%95%99%E7%A8%8B/</id>
    <published>2026-03-12T00:00:00.000Z</published>
    <updated>2026-03-12T00:00:00.000Z</updated>
    <summary>本文介绍了利用R探长小助手进行脚本枪机的方法和注意事项。</summary>
    <content type="html"><![CDATA[<h2>创建 OCI 密钥</h2>
<p><img src="https://img.085404.xyz/images/52d45a5c5a43466bbcb03bf7f79a55d8.webp" alt="image.png" /></p>
<p><img src="https://img.085404.xyz/images/8b38737ae7ff517c4e7e1f5d13cd45ea.webp" alt="image.png" /></p>
<p><img src="https://img.085404.xyz/images/80761b0ac3af23d609a9f8be7a007b73.webp" alt="image.png" /></p>
<blockquote>
<p>[!note]
注意保存好这里下载的密钥文件</p>
</blockquote>
<p><img src="https://img.085404.xyz/images/cef3194c5ad2961450c3b1b4bec72190.webp" alt="image.png" /></p>
<h2>安装脚本</h2>
<p>在服务器上创建脚本运行路径：</p>
<pre><code>mkdir /opt/rbot
</code></pre>
<p>获取并运行脚本：</p>
<pre><code>cd /opt/rbot
# 下载脚本
wget -O sh_client_bot.sh https://github.com/semicons/java_oci_manage/releases/latest/download/sh_client_bot.sh
# 给执行权限
chmod +x sh_client_bot.sh
# 运行脚本
bash sh_client_bot.sh
</code></pre>
<blockquote>
<p>[!note]
rbot 会自动注册服务，<code>systemctl status bot</code> 可查看运行状态。</p>
</blockquote>
<p><strong>提示运行失败是正常的</strong>，直接 <code>Ctrl + C</code> 退出即可。打开产生的 <code>client_config</code> 文件，将 Oracle 控制台得到的 OCI 密钥按提示粘贴到指定位置：</p>
<pre><code>#在oci=begin和oci=end之间放入你的API配置信息 支持多个配置文件 机器人切换profile可更换操作配置
oci=begin
[DEFAULT]
user=xxx
fingerprint=xxx
tenancy=xxx
region=xxx
key_file=./Oracle.pem
oci=end
</code></pre>
<p>找到刚刚下载的两个密钥文件，将其中的私钥（后缀为 <code>.pem</code>，不是 <code>xxx_public.pem</code>）重命名为 <code>Oracle.pem</code>，然后将其上传到 <code>rbot</code> 路径下即可。</p>
<p>然后打开 TG，执行以下步骤：</p>
<ol>
<li>首先关注 <a href="https://t.me/agentONE_R">频道</a></li>
<li>添加 <a href="https://t.me/radiance_helper_bot">机器人</a></li>
</ol>
<blockquote>
<p>[!warning]
关注频道是必选项，否则无法使用机器人功能。</p>
</blockquote>
<ol>
<li>
<p>向机器人发送指令 <code>/raninfo</code> ，并根据提示在一分钟内重复发送一次，获得配置文件信息：</p>
<pre><code>username=xxx
password=xxx
</code></pre>
</li>
<li>
<p>继续打开 <code>client_config</code> 文件，将上面得到的 <code>username</code> 和 <code>password</code> 填入其中：</p>
<pre><code>#用户信息 从 https://t.me/radiance_helper_bot 配置(bot可使用/raninfo命令随机生成)
#必传
username=xxx
#必传
password=xxx
</code></pre>
</li>
<li>
<p>启动客户端：</p>
<pre><code>bash sh_client_bot.sh # 默认端口9527
# 或者
bash sh_client_bot.sh 8888 # 可更换默认9527为8888端口
</code></pre>
</li>
</ol>
<h2>执行开机操作</h2>
<p>向机器人发送 <code>/oracle</code>，选择 <code>1. 开机</code>，配置按需求进行选择。注意 <code>确定开机</code> 按钮需要点两次，第一次是确认，第二次是提交。</p>
]]></content>
    <author><name>Sun2ot</name></author>
    <category term="教程"/>
  </entry>
  <entry>
    <title>浅探 Openclaw</title>
    <link href="https://blog.085404.xyz/posts/%E6%B5%85%E6%8E%A2-openclaw/" rel="alternate" type="text/html"/>
    <id>https://blog.085404.xyz/posts/%E6%B5%85%E6%8E%A2-openclaw/</id>
    <published>2026-03-10T00:00:00.000Z</published>
    <updated>2026-03-10T00:00:00.000Z</updated>
    <summary>记录了关于 openclaw (龙虾) 在 Windows 操作系统下的部署方式，并给出了对接到 QQ 机器人的方法。</summary>
    <content type="html"><![CDATA[<h2>写在前面</h2>
<blockquote>
<p>[!note]<br />
<strong>2026-03-10</strong>：这个东西体验了几天，发现属实没什么卵用。远程操控有 UU，coding 有 codex/cc 等一大堆 CLI Agent（不会真有人写代码对着 IM 窗口狂敲吧？Diff 都不需要的吗？），其他的功能也完全可以使用 cherrystudio 等成熟客户端平替。</p>
<p>你可能会说，对着一个 IM 就可以完成上面的所有操作，不是更方便吗？一方面，权限问题目前没有很好的解决方案；另一方面，各家 IM 的适配有限，响应速度和 tokens 开销都是一笔不菲的成本。最重要的是，即使忽略上述两个问题，openclaw 的实际体验感也不好用啊！它不好用啊，不好用。</p>
</blockquote>
<h2>安装</h2>
<blockquote>
<p>[!warning]</p>
<ol>
<li>需 Node.js &gt;= 22</li>
<li>如果运行在服务器上，2G 内存是不够的！具体多少不清楚，我手头也没有那么大内存的服务器。</li>
</ol>
</blockquote>
<blockquote>
<p>[!caution]
Windows 下安装需要用管理员模式启动终端！</p>
</blockquote>
<p>安装 openclaw</p>
<pre><code># Linux
curl -fsSL https://openclaw.ai/install.sh | bash
# Windows
iwr -useb https://openclaw.ai/install.ps1 | iex
</code></pre>
<p>选择对应的后端 LLM 接入，启用需要的 skills 即可。或者也可以跳过该步骤，后续手动修改。</p>
<h3>反向代理</h3>
<pre><code>server {
    listen 80;
    server_name your_domain; # 替换域名

    return 301 https://$host$request_uri;
}

server {
    listen 443 ssl;
    server_name your_domain; # 替换域名

    ssl_certificate /path/to/your/fullchain.pem; # 证书文件
    ssl_certificate_key /path/to/your/privkey.pem; # 私钥文件

    ssl_protocols TLSv1.2 TLSv1.3;
    ssl_ciphers HIGH:!aNULL:!MD5;
    ssl_prefer_server_ciphers on;

    location / {
        # 代理到 OpenClaw 本地端口
        proxy_pass http://127.0.0.1:18789;
        
        # 传递客户端真实 IP 与协议等基础头信息
        proxy_set_header Host $host;
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header X-Forwarded-Proto $scheme;

        # 启用 WebSocket 支持
        proxy_set_header Upgrade $http_upgrade;
        proxy_set_header Connection "upgrade";
        proxy_http_version 1.1;
        
        # 延长超时时间
        proxy_read_timeout 86400;
        proxy_send_timeout 86400;
    }
}
</code></pre>
<h2>接入 QQ</h2>
<pre><code>openclaw plugins install @sliverp/qqbot@latest
openclaw channels add --channel qqbot --token "ID:Secret"
openclaw restart gateway
</code></pre>
<blockquote>
<p>[!note]
执行 <code>plugins install</code> 时，如果发生 npm run failed，除了常见的网络原因之外，还有一个可能是 OOM...别问我为什么知道...</p>
</blockquote>
<h2>FAQ</h2>
<h3>关于自启动</h3>
<p>在 Windows 平台下，openclaw 的自启动是写在 <code>任务计划程序</code> 里的，在任何用户登录时，启动脚本 <code>C:\Users\&lt;user&gt;\.openclaw\gateway.cmd</code></p>
]]></content>
    <author><name>Sun2ot</name></author>
    <category term="教程"/>
  </entry>
  <entry>
    <title>WSL 部署 CosyVoice3</title>
    <link href="https://blog.085404.xyz/posts/cosyvoice3/" rel="alternate" type="text/html"/>
    <id>https://blog.085404.xyz/posts/cosyvoice3/</id>
    <published>2026-03-02T00:00:00.000Z</published>
    <updated>2026-03-02T00:00:00.000Z</updated>
    <summary>介绍了如何在Windows系统的WSL环境下配置音频大模型CosyVoice3及其使用方式。</summary>
    <content type="html"><![CDATA[<h2>环境配置</h2>
<blockquote>
<p>[!tip]
大体过程可以完全参考官方的 README <a href="https://github.com/FunAudioLLM/CosyVoice?tab=readme-ov-file#install">文档</a></p>
</blockquote>
<p>首先克隆官方仓库：</p>
<pre><code>git clone https://github.com/FunAudioLLM/CosyVoice.git
</code></pre>
<p>如果网络问题导致克隆中断，可以进入项目路径后重新执行：</p>
<pre><code>cd CosyVoice
git submodule update --init --recursive
</code></pre>
<p>配置虚拟环境的工具，官方给出的是 conda，我这里使用 pixi 替代：</p>
<pre><code>pixi init
pixi add python=3.10
</code></pre>
<p><code>pixi.toml</code> 和 <code>pixi.lock</code> 参见 https://gist.github.com/sun2ot/c3e0802ccc0c706664a7a501b4d7251e</p>
<blockquote>
<p>[!warning]
我的环境为：Windows 11 23H2，WSL Ubuntu-24.04 LTS，Nvidia Driver 591.86，CUDA 13.1
依赖版本仅供参考</p>
</blockquote>
<p>将环境配置文件内容复制替换后，执行 <code>pixi install</code> 即可。</p>
<h2>预训练模型下载</h2>
<p>参考官方文档中的 <code>Model download</code> 部分，按照国内外不同网络环境使用脚本下载即可。例如国内环境下，创建 <code>model_dl.py</code> 文件：</p>
<pre><code>from modelscope import snapshot_download
snapshot_download('FunAudioLLM/Fun-CosyVoice3-0.5B-2512', local_dir='pretrained_models/Fun-CosyVoice3-0.5B')
snapshot_download('iic/CosyVoice2-0.5B', local_dir='pretrained_models/CosyVoice2-0.5B')
snapshot_download('iic/CosyVoice-300M', local_dir='pretrained_models/CosyVoice-300M')
snapshot_download('iic/CosyVoice-300M-SFT', local_dir='pretrained_models/CosyVoice-300M-SFT')
snapshot_download('iic/CosyVoice-300M-Instruct', local_dir='pretrained_models/CosyVoice-300M-Instruct')
snapshot_download('iic/CosyVoice-ttsfrd', local_dir='pretrained_models/CosyVoice-ttsfrd')
</code></pre>
<p>然后执行 <code>python model_dl.py</code> 即可。所有模型总共需占用 32G，如果仅需要音色克隆，则只需下载 <code>FunAudioLLM/Fun-CosyVoice3-0.5B-2512</code> 即可（可能是的，我没试过）。</p>
<h2>代码修改</h2>
<p>由于 <code>torchaudio</code> 与官方版本不一致，因此导致 <code>.info()</code> API 存在不兼容。修改 <code>webui.py</code> 如下内容即可：</p>
<pre><code>if mode_checkbox_group in ['3s极速复刻', '跨语种复刻']:
    if prompt_wav is None:
        gr.Warning('prompt音频为空，您是否忘记输入prompt音频？')
        yield (cosyvoice.sample_rate, default_data)
    waveform, sr = torchaudio.load(prompt_wav)
    # if torchaudio.info(prompt_wav).sample_rate &lt; prompt_sr:
    if sr &lt; prompt_sr:
        # gr.Warning('prompt音频采样率{}低于{}'.format(torchaudio.info(prompt_wav).sample_rate, prompt_sr))
        gr.Warning(f'prompt音频采样率{sr}低于{prompt_sr}')
        yield (cosyvoice.sample_rate, default_data)
</code></pre>
<h2>启动</h2>
<p>终端执行 <code>python webui.py --model_dir pretrained_models/Fun-CosyVoice3-0.5B</code>，启动后访问 <code>http://localhost:8000</code> 即可看到 webui 界面。
<img src="https://img.085404.xyz/images/365f5c319d0e1866cd4b10e55633028c.webp" alt="image.png" /></p>
<p>注意两个问题：</p>
<ol>
<li>音色克隆除了音源以外，还需要对应的标签，即与音频对应的文本。但由于是零样本生成，因此准备几秒的音频即可，手动录入文本标签并不会很困难。</li>
<li><code>prompt文本</code> 可能存在 bug，参考 <a href="https://github.com/FunAudioLLM/CosyVoice/issues/1703#issuecomment-3678473294">issue1703</a>，要在你的提示词前面加上 <code>You are a helpful assistant.&lt;|endofprompt|&gt;</code>，否则亲测会报错。</li>
</ol>
]]></content>
    <author><name>Sun2ot</name></author>
    <category term="开发"/>
  </entry>
  <entry>
    <title>WSL Docs</title>
    <link href="https://blog.085404.xyz/posts/wsl-docs/" rel="alternate" type="text/html"/>
    <id>https://blog.085404.xyz/posts/wsl-docs/</id>
    <published>2026-03-02T00:00:00.000Z</published>
    <updated>2026-03-02T00:00:00.000Z</updated>
    <summary>Windows 平台部署 WSL 子系统</summary>
    <content type="html"><![CDATA[<h1>WSL Docs</h1>
<h2>安装</h2>
<p>Windows 11 内置了 <code>wsl.exe</code>，可以在 shell 中直接调用</p>
<pre><code># 查看帮助信息
wsl --help

# 查看当前有效的分发列表
wsl --list --online

# 安装发行版
wsl --install -d Ubuntu-24.04
</code></pre>
<h2>基本操作</h2>
<p><a href="https://learn.microsoft.com/zh-cn/windows/wsl/basic-commands">wsl basic commands</a></p>
<pre><code># 关机
wsl --shutdown

# 查看版本
wsl --version
</code></pre>
<h2>wslconfig</h2>
<p><a href="https://learn.microsoft.com/zh-cn/windows/wsl/wsl-config#wslconfig">官方文档</a></p>
<p>默认情况下，通过 <code>wsl</code> 安装的是 WSL2，WSL（或者说 WSL1 已经停止维护）。<code>.wslconfig</code> 即为 WSL2 使用的配置文件。</p>
<p><code>.wslconfig</code> 默认情况下不存在，需手动在 <code>C:\Users\&lt;UserName&gt;</code> 下新建一个。</p>
<blockquote>
<p>[!warning]
修改配置后，必须关闭 wsl 然后重启才能生效</p>
</blockquote>
<h3>主要配置项</h3>
<p><a href="https://learn.microsoft.com/zh-cn/windows/wsl/wsl-config#main-wsl-settings">官方文档</a></p>
<h2>WSL 网络</h2>
<h3>NAT（默认）</h3>
<p>默认情况下，WSL 使用基于 NAT（网络地址转换）的网络体系结构。按照 <a href="https://learn.microsoft.com/zh-cn/windows/wsl/networking">官方文档</a> 的描述，从 Windows 访问 WSL 应用，居然是访问 <code>localhost</code>？？这太他妈迷惑了。</p>
<p>而从 WSL 访问 Windows，按照 WSL 的网络结构，显然可以通过 Windows 的 IP 地址访问，但也可以通过 <code>ip route show | grep -i default | awk '{ print $3}'</code> 获取的 IP 进行访问，而且这个 IP 和 Windows 真正的局域网 IP 并不一致。</p>
<h3>镜像（推荐）</h3>
<blockquote>
<p>[!tip]
官方现在有图形化界面，直接在网络模块中设置为 <code>Mirrored</code> 即可。</p>
</blockquote>
<p>Windows 22H2 以后支持一种更加优雅的网络解决方案：“<a href="https://learn.microsoft.com/zh-cn/windows/wsl/networking#mirrored-mode-networking">镜像网络</a>”。启用此功能会将 WSL 更改为全新的网络体系结构，其目标是将 Windows 上的网络接口“镜像”到 wsl 中，以添加新的网络功能并提高兼容性。</p>
<p>开启该模式需要在 <code>.wslconfig</code> 中配置：</p>
<pre><code>[wsl2]
networkingMode = mirrored
</code></pre>
<p>然后使用管理员权限在 PowerShell 窗口中运行以下命令，以 <a href="https://learn.microsoft.com/zh-cn/windows/security/operating-system-security/network-security/windows-firewall/hyper-v-firewall">配置 Hyper-V 防火墙</a> 设置，从而允许入站连接：</p>
<pre><code>Set-NetFirewallHyperVVMSetting -Name '{40E0AC32-46A5-438A-A0B2-2B479E8F2E90}' -DefaultInboundAction Allow
</code></pre>
<p><strong>或者</strong></p>
<pre><code>New-NetFirewallHyperVRule -Name "MyWebServer" -DisplayName "My Web Server" -Direction Inbound -VMCreatorId '{40E0AC32-46A5-438A-A0B2-2B479E8F2E90}' -Protocol TCP -LocalPorts 80
</code></pre>
<h2>WSL 存储</h2>
<blockquote>
<p>[!tip]
尽量不要在 WSL 里去频繁跨系统读写 Windows 磁盘中的内容！速度相对而言非常慢</p>
</blockquote>
<p>Windows 中的磁盘，即 <code>C/D/...</code> 等磁盘，在 WSL 中以挂载形式呈现，即 <code>/mnt/c,d...</code> 等。此外，你可以在 Windows 的文件资源管理器中看到 WSL 的存储位置。但是，这一切都是通过网络连接的，而一张千兆网卡的速度也就是 100MB/s 左右。所以你会看到，一张 pice4.0 顺序读写 7000 MB/s 的磁盘，从磁盘写入 WSL 的速度只有可怜的 100 MB/s。只能说方便的代价就是极度的性能损失。</p>
<h2>FAQ</h2>
<h3>应该使用 WSL 吗？</h3>
<ul>
<li>如果只是为了包管理器，scoop 足矣</li>
<li>如果写轻量项目，Windows 也完全足够</li>
<li>但如果有 Linux 的专属需求，例如 python 深度学习的某个包不支持 Windows，那不妨来试试 WSL，确实挺好用</li>
</ul>
<h3>代理</h3>
<p>使用 WSL2 时，可能会遇到报错：</p>
<pre><code>wsl: 检测到 localhost 代理配置，但未镜像到 WSL。NAT 模式下的 WSL 不支持 localhost 代理。
</code></pre>
<p>在 WSL1 时代，由于 Linux 子系统和 Windows 共享了网络端口，所以访问 Windows 的代理非常简单。例如 Windows 的代理客户端监听了 8000 端口，那么只需要在 Linux 子系统中执行如下命令，就可以让当前 session 中的请求通过代理访问互联网。</p>
<pre><code>export ALL_PROXY="http://127.0.0.1:8000"
</code></pre>
<p>但是 WSL2 基于 Hyper-V 运行，导致 Linux 子系统和 Windows <strong>在网络上是两台各自独立的计算机</strong>，从 Linux 子系统访问 Windows 首先需要找到 Windows 的 IP。</p>
<p>所以解决该问题，可以在 <code>.wslconfig</code> 中关闭自动代理。注意，按照微软 <a href="https://learn.microsoft.com/zh-cn/windows/wsl/wsl-config#main-wsl-settings">官方文档</a> 的最新版，<code>autoProxy</code> 已经不再属于实验性设置 <code>[experimental]</code>，而是属于主要设置 <code>[wsl2]</code>：</p>
<pre><code>[wsl2]
# 在该节下新增配置
autoProxy = false
</code></pre>
<blockquote>
<p>当然，你也可以按照 <a href="https://github.com/microsoft/WSL/issues/10753#issuecomment-1814839310">issue</a> 让自动代理正常工作，而不是关闭。</p>
</blockquote>
<h3>nvidia 驱动与 cuda 问题</h3>
<p>如果 windows 主系统中已安装了 nvidia 驱动，ubuntu 中不要再额外安装了，可以直接通过 <code>nvidia-smi</code> 查看。</p>
<h3>移动 WSL</h3>
<blockquote>
<p>[!tip]
2026.03.02 更新：wsl <code>--manage</code> 可以直接移动发行版位置。在 <code>--shutdown</code> 后，直接通过 <code>wsl --manage Ubuntu-24.04 --move "F:\wsl\ubuntu2404"</code></p>
</blockquote>
<p>默认情况下，wsl 会安装在 C 盘。如果追求极致的稳定性，其实是不推荐移动的。但如果确实要移动，也有办法：</p>
<pre><code># 查看当前子系统状态
wsl -l -v

# 需要先停止子系统运行
wsl --shutdown

# 导出子系统
wsl --export Ubuntu-20.04 D:\ubuntu20-04.tar
</code></pre>
<p>注意，这里导出的发行版名称严格与 <code>wsl -l -v</code> 的结果相对应，即：</p>
<pre><code>NAME            STATE           VERSION
* Ubuntu-20.04    Exporting       2
</code></pre>
<p>完成上述操作后，原有子系统仍然存在，且导出目录下会多出一个对应的归档文件。接下来需要删除原有系统：</p>
<pre><code>wsl --unregister Ubuntu-20.04
</code></pre>
<p>这一步完成后，原系统占据空间将被释放。然后导入归档文件：</p>
<pre><code># --import 发行版 安装位置 文件名 [选项]
wsl --import Ubuntu-20.04 D:\Ubuntu-20.04-wsl  D:\ubuntu20-04.tar --version 2
</code></pre>
<p>重新安装子系统后，默认登录用户会重置为 root，所以还需要修改默认登录用户：</p>
<pre><code>ubuntu2004.exe config --default-user root
</code></pre>
]]></content>
    <author><name>Sun2ot</name></author>
    <category term="开发"/>
  </entry>
  <entry>
    <title>zju-connect 代替 aTrust 客户端</title>
    <link href="https://blog.085404.xyz/posts/zju-connect-%E4%BB%A3%E6%9B%BF-atrust-%E5%AE%A2%E6%88%B7%E7%AB%AF/" rel="alternate" type="text/html"/>
    <id>https://blog.085404.xyz/posts/zju-connect-%E4%BB%A3%E6%9B%BF-atrust-%E5%AE%A2%E6%88%B7%E7%AB%AF/</id>
    <published>2026-02-22T00:00:00.000Z</published>
    <updated>2026-02-22T00:00:00.000Z</updated>
    <summary>本文介绍了如何使用 zju-connect 替代 aTrust 客户端，并提供了下载、配置文件设置以及代理配置的详细步骤。</summary>
    <content type="html"><![CDATA[<h2>背景</h2>
<p>继上一次 <a href="https://blog.085404.xyz/posts/%E8%A7%A3%E5%86%B3%E6%AF%92%E7%98%A4%E6%B7%B1%E4%BF%A1%E6%9C%8Deasyconnect/">解决毒瘤深信服easyconnect</a>，校方随大流已将 VPN 升级为深信服的 aTrust。当然，🐶改不了吃💩，软件换皮不能改变其强兼你电脑的本质。好在互联网冲浪时发现浙江大学的同学提供了 ZJU RVPN 客户端的 Go 语言实现，经实测其可完全使用与常规 aTrust VPN 的使用场景。</p>
<h2>下载</h2>
<p>下载 <a href="https://github.com/Mythologyli/zju-connect">zju-connect</a> 的 beta 或者 nightly 版本。由于是独立二进制文件，因此解压后即可使用。如果是生产环境上需要，也可以考虑 Docker 部署。</p>
<h2>配置文件</h2>
<p>zju-connect 以 toml 作为配置文件，配置字段可参考 <a href="https://github.com/Mythologyli/zju-connect?tab=readme-ov-file#%E5%8F%82%E6%95%B0%E8%AF%B4%E6%98%8E">参数说明</a>。下面给出一个基本配置文件：</p>
<pre><code>protocol = "atrust"
# 修改为学校的 vpn 地址
server_address = "vpn.nmu.edu.cn"
server_port = 443
username = "账号"
password = "密码"

# 代理端口
socks_bind = ":1090"
http_bind = ":1091"

# 非浙大人员请将以下两项置为 true 以避免冲突
disable_zju_config = true
disable_zju_dns = true

dns_ttl = 3600
disable_keep_alive = false
zju_dns_server = "auto"
secondary_dns_server = "114.114.114.114"
debug_dump = false

port_forwarding = []
custom_dns = []

# aTrust 设置
auth_type = "auth/psw"
login_domain = "BMD"

client_data_file = "client_data.json"
graph_code_file = "graph_code.jpg"
update_best_nodes_interval = 300
</code></pre>
<p>关于上述配置文件中的 aTrust 设置，需要根据实际情况进行配置。具体来说，当启动 zju-connect 后，终端显示如下：</p>
<pre><code>❯ .\zju-connect.exe -config .\config.toml
2026/02/22 09:58:02 Start ZJU Connect v1.0.0-beta.4-b593140
2026/02/22 09:58:02 Read client data file error: open client_data.json: The system cannot find the file specified.
2026/02/22 09:58:02 Will create a new client data file if log in successfully
2026/02/22 09:58:02 VPN protocol: atrust
2026/02/22 09:58:02 Perform GET /passport/v1/public/authConfig
# --------------注意这一行-----------------
2026/02/22 09:58:02 Available authentication methods: [{LoginDomain:BMD AuthType:auth/psw AuthName:师生用户 LoginURL:} {LoginDomain:local AuthType:auth/psw AuthName:非师生用户 LoginURL:}]
# ------------------------------
2026/02/22 09:58:02 Login error: auth type/login domain combination not found: auth type: auth/psw, login domain: Radius
2026/02/22 09:58:02 VPN client setup error: auth type/login domain combination not found: auth type: auth/psw, login domain: Radius
</code></pre>
<p>首次启动失败后的日志中会显示 <code>Available authentication methods</code>，将其中的 <code>LoginDomain</code> 和 <code>AuthType</code> 分别对应地写入 toml 配置文件中即可。然后重新启动 zju-connect 即可。</p>
<h2>代理配置</h2>
<blockquote>
<p>[!note]
此处以 clash 系列为例，v2ray 等其他代理内核的原理一致。</p>
</blockquote>
<p>根据上述配置文件中设置的代理端口，需要给 clash 添加新的代理节点、策略组和分流规则。</p>
<pre><code># 代理端口
socks_bind = ":1090"
http_bind = ":1091"
</code></pre>
<p>clash 配置文件变动如下：</p>
<pre><code># 注意: 新增的分流规则添加在顶部
rules:
  # vpn 地址保持直连
  - DOMAIN,vpn.nmu.edu.cn,🎯 全球直连
  # 内网域名和IP段走策略组
  - DOMAIN-SUFFIX,nmu.edu.cn,🏫 校园网
  - IP-CIDR,10.0.0.0/8,🏫 校园网,no-resolve
  - IP-CIDR,172.16.0.0/12,🏫 校园网,no-resolve

proxies:
- name: zju-local
  type: socks5
  server: 127.0.0.1
  port: 1090
  udp: true
  
proxy-groups:
  - type: select
    name: 🏫 校园网
    proxies:
      - 🚀 节点选择
      - 🎯 全球直连
      - zju-local
      - DIRECT
  # 学术科研的策略组根据实际情况添加
  # 我这里是因为对文献库做了分流
  - type: select
    name: 📚 学术科研
    proxies:
      - 🚀 节点选择
      - 🎯 全球直连
      - 🏫 校园网
      - DIRECT
</code></pre>
<blockquote>
<p>[!warning]
出于特殊原因，我无需使用脚本完成配置文件修改，因此本文不提供 clash 桌面端的脚本。</p>
</blockquote>
]]></content>
    <author><name>Sun2ot</name></author>
    <category term="教程"/>
  </entry>
  <entry>
    <title>fail2ban</title>
    <link href="https://blog.085404.xyz/posts/fail2ban/" rel="alternate" type="text/html"/>
    <id>https://blog.085404.xyz/posts/fail2ban/</id>
    <published>2026-01-17T00:00:00.000Z</published>
    <updated>2026-01-17T00:00:00.000Z</updated>
    <summary>fail2ban使用说明，防止服务器被爆破</summary>
    <content type="html"><![CDATA[<h2>检查 SSH 端口是否被爆破</h2>
<pre><code>journalctl -u ssh
</code></pre>
<p>如果在日志中看到了大量的不明 IP 地址在进行尝试，就说明有人在尝试爆破了。</p>
<h2>安装 Fail2Ban</h2>
<p>在基于 Debian/Ubuntu 的系统中执行：</p>
<pre><code>sudo apt update
sudo apt install fail2ban -y
</code></pre>
<p>在基于 RHEL/CentOS 的系统中执行：</p>
<pre><code>sudo yum install epel-release
sudo yum install fail2ban -y
</code></pre>
<h2>配置文件管理原则</h2>
<p>Fail2Ban 默认配置文件为 <code>/etc/fail2ban/jail.conf</code>。为防止软件更新覆盖配置，禁止直接修改该文件。所有自定义配置应写入 <code>/etc/fail2ban/jail.local</code>。</p>
<p>创建并编辑配置文件：</p>
<pre><code>sudo touch /etc/fail2ban/jail.local
sudo nano /etc/fail2ban/jail.local
</code></pre>
<h2>标准配置方案</h2>
<p>将以下内容写入 <code>/etc/fail2ban/jail.local</code>：</p>
<pre><code>[DEFAULT]
# 基础封禁参数
bantime  = 24h
findtime = 1h
maxretry = 3

# 启用阶梯式封禁（针对多次被封禁的 IP 延长封禁时间）
bantime.increment = true
bantime.factor = 1
bantime.maxtime = 5w

# 指定防火墙后端（推荐使用 nftables）
banaction = nftables-multiport
backend = systemd

[sshd]
enabled = true
# 指定实际使用的 SSH 端口
port    = 4399
# 开启激进模式以捕获协议错误、预认证阶段断开等恶意探测
filter  = sshd[mode=aggressive]
</code></pre>
<h2>关键参数说明</h2>
<ul>
<li><strong>mode = aggressive</strong>：此模式不仅监控密码失败，还会监控密钥交换失败（kex_protocol_error）、连接重置（Connection reset by... [preauth]）等行为。适用于已禁用密码登录、仅使用证书登录的服务器。</li>
<li><strong>bantime.increment</strong>：开启后，如果同一 IP 重复触发封禁，封禁时间将按指数级增长，最高可达 <code>bantime.maxtime</code>。</li>
<li><strong>findtime = 1h</strong>：将监控窗口延长至 1 小时，用于捕获低频尝试的扫描脚本。</li>
</ul>
<h2>服务管理命令</h2>
<h3>启动与重新加载</h3>
<pre><code># 启动服务并设置开机自启
sudo systemctl enable --now fail2ban

# 每次修改 jail.local 后重新加载配置
sudo systemctl restart fail2ban
</code></pre>
<h3>查看状态</h3>
<pre><code># 查看 Fail2Ban 整体运行状态
fail2ban-client status

# 查看 SSH 监狱的具体封禁列表和统计
fail2ban-client status sshd
</code></pre>
<h3>验证配置生效情况</h3>
<pre><code># 查询当前 sshd 监狱生效的 maxretry 值
fail2ban-client get sshd maxretry

# 查询当前 sshd 监狱生效的模式
fail2ban-client get sshd filter
</code></pre>
<h3>手动操作</h3>
<pre><code># 手动封禁一个 IP
fail2ban-client set sshd banip &lt;IP地址&gt;

# 手动解封一个 IP
fail2ban-client set sshd unbanip &lt;IP地址&gt;
</code></pre>
<h2>日志监控</h2>
<p>通过查看日志确认 Fail2Ban 是否正常识别并过滤恶意请求：</p>
<pre><code># 查看 Fail2Ban 运行日志
sudo tail -f /var/log/fail2ban.log

# 查看 SSH 登录尝试日志
sudo journalctl -u ssh -f
</code></pre>
]]></content>
    <author><name>Sun2ot</name></author>
    <category term="教程"/>
  </entry>
  <entry>
    <title>回源配置</title>
    <link href="https://blog.085404.xyz/posts/%E5%9B%9E%E6%BA%90%E9%85%8D%E7%BD%AE/" rel="alternate" type="text/html"/>
    <id>https://blog.085404.xyz/posts/%E5%9B%9E%E6%BA%90%E9%85%8D%E7%BD%AE/</id>
    <published>2026-01-03T00:00:00.000Z</published>
    <updated>2026-01-03T00:00:00.000Z</updated>
    <summary>nginx配合脚本实现自动化管理回源IP列表</summary>
    <content type="html"><![CDATA[<h2>场景需求</h2>
<p>CDN 或者全站加速等场景，可以设置 HTTP 回源，然后配合 IP 地址列表，实现仅允许对应 IP 访问源站，阻断非法访问。</p>
<h2>EdgeOne</h2>
<blockquote>
<p>[!info]
免费版不支持控制台操作，所以这里借助脚本 + API</p>
</blockquote>
<p>nginx 配置如下：</p>
<pre><code>server {
    # default_server 表示如果有其他域名指向这个IP，也都由这个配置处理
    # 只要IP白名单通过，这里就无脑响应
    listen 80 default_server; 
    
    # _ 是一个常见的习惯用法，代表“匹配所有域名”或“不关心域名”
    server_name _; 

    root /var/www/files;

    # 引入之前的 IP 白名单脚本生成的配置文件
    include /etc/nginx/edgeone_ips.conf;

    location / {
        autoindex on;
    }
}
</code></pre>
<p>编写更新脚本 <code>/usr/local/bin/update_edgeone_ips.sh</code>：</p>
<pre><code>#!/bin/bash

# 定义文件路径
API_URL="https://api.edgeone.ai/ips"
CONF_FILE="/etc/nginx/edgeone_ips.conf"
TEMP_FILE="/tmp/edgeone_ips.tmp"

echo "1. 开始下载 IP 列表..."
# --http1.1: 强制使用旧协议，防止卡死
# -s: 静默模式 (不显示进度条)
# -S: 如果报错则显示错误信息
curl -sS --http1.1 "$API_URL" &gt; "$TEMP_FILE"

# 检查是否下载到了内容（文件是否存在且大小大于 0）
if [ ! -s "$TEMP_FILE" ]; then
    echo "❌ 下载失败：文件为空。"
    exit 1
fi

echo "2. 格式化并生成 Nginx 配置..."
# 使用 awk 提取非空行的第一列，拼接成 allow 语句
awk 'NF {print "allow " $1 ";"}' "$TEMP_FILE" &gt; "$CONF_FILE"

# 追加 deny all
echo "" &gt;&gt; "$CONF_FILE"
echo "deny all;" &gt;&gt; "$CONF_FILE"

echo "3. 重载 Nginx..."
# 检查配置无误后重载
nginx -t &amp;&amp; systemctl reload nginx

echo "✅ 完成！"
# 清理临时文件
rm -f "$TEMP_FILE"
</code></pre>
<p>赋予执行权限后，设置定时任务：</p>
<pre><code>0 3 */3 * * /usr/local/bin/update_edgeone_ips.sh
</code></pre>
]]></content>
    <author><name>Sun2ot</name></author>
    <category term="开发"/>
  </entry>
  <entry>
    <title>🔒 自我介绍</title>
    <link href="https://blog.085404.xyz/posts/%E8%87%AA%E6%88%91%E4%BB%8B%E7%BB%8D/" rel="alternate" type="text/html"/>
    <id>https://blog.085404.xyz/posts/%E8%87%AA%E6%88%91%E4%BB%8B%E7%BB%8D/</id>
    <published>2025-12-09T00:00:00.000Z</published>
    <updated>2025-12-09T00:00:00.000Z</updated>
    <summary>该文章内容已加密，请输入密码后查看。</summary>
    <content type="html"><![CDATA[<p><em>🔒 本文已加密保护，请访问博客网站输入密码阅读全文。</em></p>]]></content>
    <author><name>Sun2ot</name></author>
    <category term="日常"/>
  </entry>
  <entry>
    <title>pixi</title>
    <link href="https://blog.085404.xyz/posts/pixi/" rel="alternate" type="text/html"/>
    <id>https://blog.085404.xyz/posts/pixi/</id>
    <published>2025-08-22T00:00:00.000Z</published>
    <updated>2025-08-22T00:00:00.000Z</updated>
    <summary></summary>
    <content type="html"><![CDATA[<h2>安装</h2>
<pre><code>curl -fsSL https://pixi.sh/install.sh | sh
</code></pre>
<p>上述调用将自动下载最新版本的 <code>pixi</code> 并解压，然后将 <code>pixi</code> 二进制文件移动到 <code>~/.pixi/bin</code> 。该脚本还会扩展 shell 启动脚本中的 <code>PATH</code> 环境变量，使其包含 <code>~/.pixi/bin</code> 。</p>
<p>完成后可添加 shell <a href="https://pixi.sh/latest/installation/#autocompletion">自动补全</a>：</p>
<pre><code># bash
eval "$(pixi completion --shell bash)"

# zsh
autoload -Uz compinit &amp;&amp; compinit # redundant with Oh My Zsh
eval "$(pixi completion --shell zsh)"

# powershell
(&amp; pixi completion --shell powershell) | Out-String | Invoke-Expression
</code></pre>
<h2>初始化</h2>
<ul>
<li>如果是新建项目，可执行 <code>pixi init project_name</code></li>
<li>如果是已有的项目，可执行 <code>pixi init</code></li>
</ul>
<p>两者都会在项目路径下生成 <code>.gitattributes</code>，<code>.gitignore</code>，<code>pixi.lock</code> 和 <code>pixi.toml</code>。</p>
<h2>安装与卸载包</h2>
<p>可通过 <code>add</code> 命令添加包：</p>
<pre><code>pixi add python=3.10
</code></pre>
<p>默认情况下，pixi 会使用 <code>conda-forge</code> 作为安装渠道。对于只提供 <code>pypi</code> 渠道的包，可以通过：</p>
<pre><code>pixi add xxx --pypi
</code></pre>
<p>上述操作均会更新 <code>pixi.toml</code>，在 <code>dependencies</code> 表中添加键值对，例如：</p>
<pre><code>[dependencies]
python = "3.10.*"
rasterio = "&gt;=1.4.3,&lt;2"
nvitop = "&gt;=1.5.3,&lt;2"
</code></pre>
<p>卸载通过如下方式：</p>
<pre><code>pixi remove numpy
</code></pre>
<h2>环境与激活</h2>
<p>需要声明的是，在 pixi 中，每个项目的环境都是独立的，依赖于配置文件。但包本身只会被下载一次，并缓存在中央存储中，所有项目的环境通过硬链接产生，存储在<strong>工作区</strong>下的 <code>.pixi</code> 中。区别于 conda，pixi 支持对于同一个项目产生不同的环境。默认的环境为 <code>.pixi/envs/default</code>。</p>
<p>类似于 <code>conda activate</code> ，pixi 通过 <code>pixi shell</code> 来激活当前环境，这取决于在哪个工作区下的 <code>pixi.toml</code>。</p>
<p>当然，也可以选择不激活环境，而是直接通过 <code>pixi run python xxx</code> 来执行任务。</p>
<p>更多的区别，推荐阅读 <a href="https://pixi.sh/latest/switching_from/conda/#key-differences-at-a-glance">官方文档</a>，这一节很清楚的讲明了 pixi 的工作方式去其他工具有何不同。</p>
<h2>缓存</h2>
<p>pixi 会将下载的所有包存储在中央缓存中，不同操作系统对应的路径如下：</p>
<ul>
<li>Linux： <code>$XDG_CACHE_HOME/rattler</code> 或 <code>$HOME/.cache/rattler</code></li>
<li>macOS: <code>$HOME/Library/Caches/rattler</code></li>
<li>Windows: <code>%LOCALAPPDATA%\rattler</code></li>
</ul>
<p>而工作区中的 <code>.pixi</code> 里的包通过硬链接共享中央缓存。可以通过设置 <code>PIXI_CACHE_DIR</code> 或 <code>RATTLER_CACHE_DIR</code> 环境变量来配置此位置。</p>
<h2>pixi 配置</h2>
<p><a href="https://pixi.sh/latest/reference/pixi_configuration">官方文档</a></p>
<h3>配置文件</h3>
<p>pixi 的配置与工作区的配置是两个东西。pixi 的配置优先级可参考 <a href="https://pixi.sh/dev/reference/pixi_configuration/#the-configuration-of-pixi-itself">The Configuration of Pixi Itself</a>。</p>
<p>简单来说，通常情况下在 <code>$HOME/.pixi/config.toml</code> 中添加 pixi 配置即可，可保证相对较高的优先级且与工作区配置解耦。</p>
<h3>镜像与代理</h3>
<p>先说代理的问题，pixi 会优先考虑 <code>https_proxy</code> 等代理环境，并赋予其最高优先级。所以建议直接配置即可。</p>
<p>pixi 的常用镜像包括 pypi 和 conda 两部分，<code>config.toml</code> 的配置方式如下：</p>
<pre><code>[mirrors]
"https://conda.anaconda.org/conda-forge" = [
  "https://mirrors.tuna.tsinghua.edu.cn/anaconda/cloud/conda-forge/",
]
"https://repo.anaconda.com/pkgs/main" = [
  "https://mirrors.tuna.tsinghua.edu.cn/anaconda/pkgs/main/",
  "https://mirrors.ustc.edu.cn/anaconda/pkgs/main",
]
"https://repo.anaconda.com/pkgs/r" = [
  "https://mirrors.tuna.tsinghua.edu.cn/anaconda/pkgs/r",
  "https://mirrors.ustc.edu.cn/anaconda/pkgs/r",
]
"https://repo.anaconda.com/pkgs/msys2" = [
  "https://mirrors.tuna.tsinghua.edu.cn/anaconda/pkgs/msys2",
  "https://mirrors.ustc.edu.cn/anaconda/pkgs/msys2",
]
"https://repo.anaconda.com/pkgs/free" = [
  "https://mirrors.sustech.edu.cn/anaconda/pkgs/free"
]
"https://repo.anaconda.com/pkgs/pro" = [
  "https://mirrors.sustech.edu.cn/anaconda/pkgs/pro"
]


[pypi-config]
index-url = "https://mirrors.aliyun.com/pypi/simple"
extra-index-urls = ["https://pypi.tuna.tsinghua.edu.cn/simple"]
</code></pre>
<blockquote>
<p>由于 Pytorch 官方已不再对 Anaconda 的 pytorch channel 提供支持，因此上述镜像没有添加清华与南科大的 pytorch/nvidia 渠道。</p>
</blockquote>
<h2>系统需求</h2>
<p><code>system-requirements</code> 可以告诉 pixi 安装和运行工作区环境所需的系统规格。确保依赖项与计算机的操作系统和硬件相匹配。</p>
<pre><code>[system-requirements]
linux  = "4.18"
libc   = { family = "glibc", version = "2.28" }
cuda   = "12"
macos  = "13.0"
</code></pre>
<p>默认情况下，Linux 环境的系统需求如下：</p>
<pre><code>[system-requirements]
linux = "4.18"
libc = { family = "glibc", version = "2.28" }
</code></pre>
<p>所以如果使用 Ubuntu 18.04 等 old distribution，注意修改 libc 的版本。</p>
<h2>Pytorch 的安装</h2>
<p><a href="https://pixi.sh/latest/python/pytorch/">官方文档</a></p>
<p>官方建议使用 conda-forge 通道来安装 pytorch。至于 Anaconda 的 pytorch 渠道，目前不再支持，参考 <a href="https://github.com/pytorch/pytorch/issues/138506">Deprecating PyTorch’s official Anaconda channel</a>。当然，使用 pypi 来安装 pytorch 永远是可行的。</p>
<p>需要注意的是，pixi 安装 pytorch 时，需要指定 system requirements（即 CUDA），否则将安装 CPU 版本：</p>
<pre><code>[system-requirements]
cuda = "11"
</code></pre>
<blockquote>
<p>[!warning]
<code>system-requirements</code> 仅用于确保依赖 <code>__cuda &gt;= {version}</code> 的软件包能够被正确解析，但无法指定特定的 CUDA 运行时版本。</p>
</blockquote>
<p>如果需要限制 CUDA 的具体版本，可以通过 <code>cuda-version</code> 包来限制：</p>
<pre><code>[dependencies]
pytorch-gpu = "2.0.0"
cuda-version = "11.8"
</code></pre>
<p>这会直接影响安装的 <code>cudatoolkit</code> 的版本。</p>
<p>将上述内容添加到工作区的 <code>pixi.toml</code> 后，使用 <code>pixi install</code> 即可更新环境依赖。</p>
<h2>多环境</h2>
<p>在 pixi 中，有三个层级概念：<code>environment &gt; feature &gt; dependencies</code>。dependencies 表示依赖包，若干个依赖包可以形成一个 feature，若干个 feature 可以组合成一个 environment。</p>
<p>项目初始化所产生的 <code>.pixi/envs/default</code> 本身就是一个名为 <code>default</code> 的 <code>feature</code>，换言之：</p>
<pre><code># 根据feature创建环境

[environments]
# implicit: default = ["default"]
default = ["py39"] # implicit: default = ["py39", "default"]
</code></pre>
<p>如果需要创建一个新的 feature，可通过：</p>
<pre><code>pixi add --feature dev --pypi matplotlib==3.8 ipykernel
</code></pre>
<p>即创建了一个名为 <code>dev</code> 的 feature，其具有两个 dependencies：matplotlib 和 ipykernel。这会在 <code>pixi.toml</code> 中添加如下内容：</p>
<pre><code>[feature.dev.pypi-dependencies]
matplotlib = "==3.8"
ipykernel = "*"
</code></pre>
<p>基于上述过程，可以将若干个 feature 组合成一个 environment：</p>
<pre><code>[environments]
dev = {features = ["default", "dev"]}
</code></pre>
<p>然后执行 <code> pixi install -e dev</code> 即可创建新的环境。其中 <code>dev</code> 环境包含了 <code>default feature</code> 中的所有 <code>dependencies</code>，还额外具有 matplotlib 和 ipykernel 两个包。</p>
<h3>solve-group</h3>
<p>当 pixi 解析依赖关系时，它会为每个独立的环境计算出一组满足所有版本约束的、具体的包版本，并将结果写入 <code>pixi.lock</code> 文件。通常情况下，每个环境的解析是独立进行的。</p>
<p>然而，在某些情况下，不同环境之间可能会因为各自特有的依赖而导致解析器为同一个共享依赖选择了不同的版本。这在生产/测试环境下可能是个问题。<code>solve-group</code> 的可以<strong>确保多个不同环境中的共享依赖包版本完全一致</strong>。通过将多个环境放入同一个 <code>solve-group</code>，等于在告诉 Pixi：“请把这些环境看作一个整体来解析它们的依赖关系，并确保它们共有的那些包版本是完全相同的。”</p>
<pre><code>[environments]
# 为 default 环境和 dev 环境定义 solve-group
default = {features = ["default"], solve-group = "prod"}
dev = {features = ["default", "dev"], solve-group = "prod"}
</code></pre>
]]></content>
    <author><name>Sun2ot</name></author>
    <category term="教程"/>
  </entry>
  <entry>
    <title>多模态推荐系统研究进展</title>
    <link href="https://blog.085404.xyz/posts/%E5%A4%9A%E6%A8%A1%E6%80%81%E6%8E%A8%E8%8D%90%E7%B3%BB%E7%BB%9F%E7%A0%94%E7%A9%B6%E8%BF%9B%E5%B1%95/" rel="alternate" type="text/html"/>
    <id>https://blog.085404.xyz/posts/%E5%A4%9A%E6%A8%A1%E6%80%81%E6%8E%A8%E8%8D%90%E7%B3%BB%E7%BB%9F%E7%A0%94%E7%A9%B6%E8%BF%9B%E5%B1%95/</id>
    <published>2025-07-21T00:00:00.000Z</published>
    <updated>2025-07-21T00:00:00.000Z</updated>
    <summary></summary>
    <content type="html"><![CDATA[<blockquote>
<p>[!Tip]
学业生涯的最后一篇论文笔记。你与我最后的战场，亦或是世界起始的圣战。</p>
</blockquote>
<h2>引言</h2>
<p>推荐系统的主要功能是预测用户对各种项目的评分或偏好，并根据历史交互数据和公开信息推荐最可能和最相关的项目。然而，传统的基于 ID 的推荐方法基于用户倾向于选择与他们以前喜欢的项目类似的项目的原则，通常强烈依赖于足够的用户项目交互。尽管取得了成功，推荐系统仍面临两大挑战：数据稀疏性和冷启动问题。数据稀疏性源于用户和产品之间交互数据的自然稀疏性，这使得准确预测用户的偏好变得困难。这种稀疏性可能会导致不可靠的推荐，特别是在商品目录较大但用户交互相对较少的系统中。<strong>冷启动问题</strong>的发生是因为传统的推荐系统模型严重依赖 ID 嵌入，在几乎没有历史交互数据的情况下，很难对新用户或产品做出令人满意的预测。在不断引入新项目和用户的动态环境中，这一挑战尤为明显。</p>
<p>为了缓解这些问题，多模式信息正越来越多地被整合到推荐系统中。MRS 利用辅助多模式信息，如文本、图像、视频和音频，来补充用户和项目之间的历史交互。这种方法通过更丰富、更全面地了解用户偏好来提高推荐性能。推荐系统的基本目标是迎合人们的偏好，由于人类对世界的感知本质上是多模态的，因此结合不同的模态信息可以在更精细的粒度上捕捉偏好。这导致了更准确和个性化的推荐，从而提高了用户满意度和参与度。</p>
<h2>技术分类</h2>
<p>基于对当前 SOTA MRS 工作的总结和组织，将 MRS 中的技术分为四个部分：<strong>特征提取、编码器、多模态融合和损失函数</strong>。</p>
<h3>特征提取</h3>
<p>不同的应用场景包含不同类型的模态信息，导致具有不同多模态特征的不同数据集。然而，<strong>大多数数据集至少包括三种主要模式：交互、视觉和文本</strong>。例如，亚马逊、Netflix 和 TikTok 等大型平台提供了丰富的图像和文本信息数据集，从而涵盖了视觉和文本形式。具体来说，TikTok 数据集通常包括其他模式，如音频和视频。此外，来自专业领域的数据集有时具有罕见的模式。例如，时尚和医疗保健等热门领域的数据集通常包括各种专业模式。</p>
<p>特征提取是一个关键过程，旨在使用嵌入技术表示低维、可解释的信道特征。不同的模态采用不同的预提取方法。对于<strong>视觉模态</strong>，使用 ResNet 和 ViT 等模型来提取特征。在<strong>文本模态</strong>的情况下，使用 BERT 和 Sentence-Transformer 等模型来推导特征。<strong>音频特征</strong>通常使用 LSTM 和 GRU 等模型提取。</p>
<h3>编码器</h3>
<p>编码器利用从多模态信息和历史交互数据中提取的特征来推断用户偏好表示，随后用于预测用户 - 项目交互以进行推荐。与传统的推荐系统类似，<strong>用于多模态推荐的编码器</strong>可以大致分为<strong>基于矩阵分解</strong>（MF）和<strong>基于图卷积网络</strong>（Graph）的方法。基于 MF-based 的方法以其简单性和有效性而闻名，而基于图的方法利用用户项目交互中固有的二分图来学习高阶邻居特征。</p>
<p>随着 MRS 的快速发展，人们提出并利用了更复杂的编码器来充分利用丰富的多模态信息，从而提高了推荐性能。这些先进的编码器能够集成各种多模态数据，从而提供更准确和个性化的建议。</p>
<h3>多模态融合</h3>
<p>多模态融合是 MRS 的重点之一。最近的研究表明，模态融合的时机会显著影响建议的有效性。多模态融合涉及在不同阶段整合来自不同模态的信息，而这个时机对于实现最佳性能至关重要。</p>
<ul>
<li><strong>Early Fusion：</strong> 早期融合涉及在编码器处理之前组合不同的模态特征。这种方法可以有效地揭示模态之间的隐藏关系，因为集成的多模态特征允许编码器学习更丰富、更高质量的表示。早期融合可以捕捉不同类型数据（如文本、图像和音频）之间的复杂交互，从而更全面地了解用户偏好。早期融合技术通常包括连接、注意力机制和基于神经网络的集成方法，旨在创建多模态数据的统一表示。</li>
<li><strong>Late Fusion：</strong> 后期融合在单个模态特定编码器处理后，将每种模态的分数或预测结合起来。这种方法侧重于利用每种特定模态模型的优势，然后结合它们的输出来做出最终建议。在某些模式比其他模式更有信息或更可靠的情况下，后期融合可能特别有效。通过将融合过程推迟到预测阶段之后，后期融合允许更有针对性和更精确地提取特定的模态信息，从而提高了整体推荐的准确性。</li>
</ul>
<blockquote>
<p>后期融合好像还挺少见的？</p>
</blockquote>
<h3>损失函数</h3>
<p>MRS 利用损失函数，损失函数可大致分为两个部分：主要任务和辅助任务。主要任务是监督学习，通常涉及明确定义的标签来指导模型的学习过程。这些任务确保模型能够根据标记的数据学习做出准确的预测。辅助任务是自监督学习（SSL）。SSL 根据数据本身的固有结构或模式生成监督信号，而不是仅依赖外部标记的数据。这种方法允许推荐系统有效地利用未标记的数据，提取有意义的表示，即使在数据稀疏的情况下也能做出准确的预测。</p>
<p><strong>监督学习</strong>可以进一步细分为逐点损失（<strong>Pointwise Loss</strong>）和成对损失（<strong>Pairwise Loss</strong>）。</p>
<ul>
<li>Pointwise Loss：此损失是通过将每个单独项目的预测得分与其实际标签进行比较来计算的。常见的逐点损失函数包括均方误差（MSE）和交叉熵损失（CE），用于直接评估单个预测的准确性。</li>
<li>Pairwise Loss：这种损失集中在项目的相对排名上。它评估模型根据用户偏好正确排序每对项目的能力。常见的成对损失函数包括贝叶斯个性化排名（BPR）和 Hinge Loss，旨在优化项目的排名顺序，而不是其绝对得分。</li>
</ul>
<p><strong>自监督学习</strong>可分为<strong>基于特征</strong>和<strong>基于结构</strong>的方法。</p>
<ul>
<li>Feature-based SSL：这种方法涉及创建辅助任务来预测或重建数据的某些特征。例如，可以训练模型以基于可用数据预测项目或用户的缺失特征，从而学习更稳健的表示。</li>
<li>Structure-based SSL：这种方法利用了数据的结构属性，例如用户和项目之间的关系和交互。例如，基于图的方法可能会使用节点相似性或子图模式来生成监督信号，从而增强模型捕获复杂依赖关系和交互的能力。</li>
</ul>
<h2>特征提取</h2>
<p>在<strong>视觉领域</strong>，早期的研究主要利用卷积架构，如 CNN，以及 VGG、Inception、Caffe 和 ResNet 等特定模型，这些模型在处理各种视觉识别任务方面表现出了显著的有效性。这些模型因其深度学习能力而备受推崇，该能力允许从原始图像中提取高级、复杂的特征。此外，在<strong>文本领域</strong>，已经采用了多种技术进行特征提取。这些方法包括 TF-IDF 等传统方法，以及 GRU、PV-DM（PV-DBOW）和 Glove 等更复杂的神经网络方法。注意力机制的引入，以及 BERT、Word2Vec、Sentence-Transformer 和 Sentence2Vec 等模型，通过允许上下文丰富的文本表示，进一步彻底改变了理解和处理语言的能力。</p>
<p>随着该领域的发展，<a href="https://github.com/enoche/MMRec.git">MMRec 开源框架</a> 通过标准化视觉和文本模式的特征提取方法来体现这一趋势，从而促进了更可控和可重复的实验设置。此外，数据质量也带来了挑战，例如亚马逊数据集中的一些损坏和缺失的图像。因此，最近的方法已经转向利用视觉模态中预先提供的特征来规避与手动处理相关的问题。同样，在文本模态中，对预训练模型的依赖已经变得司空见惯。这些模型为特征提取提供了坚实的基础，<strong>利用大量预先存在的知识库来提高文本分析的准确性和深度</strong>。</p>
<p>此外，<strong>并非所有研究都全面涉及视觉和文本模式</strong>。一些作品主要关注视觉方面作为辅助信息的一种形式。例如，VBPR 和 DVBPR 强调了视觉特征在增强推荐系统中的重要性，而忽略了文本数据。同样，VMCF 和 ACF 结合了视觉信息来提高其模型的准确性，但它们没有整合文本见解，而文本见解可能会丰富对数据的上下文理解。在对抗方面，AMR 利用视觉模态来增强其模型对对抗攻击的鲁棒性，但文本模态仍未得到探索。相反，ADDVAE 只关注文本形态，从而提供了对文本数据的细致入微的理解，但省略了视觉信息丰富的描述性。</p>
<h2>编码器</h2>
<p>在推荐系统中，编码器通常用于提取用户或项目的特征表示。<strong>编码器可以是多种类型的模型，从简单的线性模型到复杂的深度神经网络</strong>。主要目的是将原始数据（如用户行为数据、项目属性等）转换为固定大小的嵌入，以捕获输入数据的核心特征。然而，在多模态推荐中，编码器起着相同的作用，但由于特征的多样性，已经提出了更多样化和专门设计的编码器，以更好地利用多模态信息和用户交互数据进行更准确的推荐。从技术角度来看，我们将所有编码器大致分为 <strong>MF-based 编码器和 Graph-based 编码器</strong>。</p>
<p><strong>定义 1</strong>：设用户集合 $\mathcal{U} \in \mathbb{R}^{|\mathcal{U}| \times d}$ 和物品集合 $\mathcal{I} \in \mathbb{R}^{|\mathcal{I}| \times d}$，其中 $d$ 是隐藏维度。我们用 $\mathbf{R} = [r_{u,i}]^{|\mathcal{U}| \times |\mathcal{I}|}$ 来表示用户 - 物品交互矩阵。对于基于图的编码器 $\mathcal{G} = (\mathcal{V}, \mathcal{E})$，它是一个给定的图，节点集为 $\mathcal{V}$，边集为 $\mathcal{E}$，其中 $|\mathcal{V}| = |\mathcal{U}| + |\mathcal{I}|$。$\mathbf{U}$ 和 $\mathbf{I}$ 分别表示用户和物品的隐藏嵌入。$\mathbf{E} = {\mathbf{U}|\mathbf{I}}$ 表示 $\mathcal{V}$ 的嵌入。</p>
<p><strong>定义 2</strong>：重述项目集 $\mathcal{I}_m \in \mathbb{R}^{|\mathcal{I}| \times d_m}$，其中 $m \in \mathcal{M}$，$\mathcal{M}$ 是模态集合，$d_m$ 是模态 $m$ 的隐藏维度。此外，$\mathcal{G}_m = (\mathcal{V}<em>m, \mathcal{E})$ 是一个给定的图，节点集 $\nu</em>{m}$ 和边集 $\mathcal{E}$，其中 $|\mathcal{V}_m| = |\mathcal{U}| + |\mathcal{I}_m|$。$\mathbf{I}_m$ 表示模态 $m$ 的项目隐藏嵌入。$\mathbf{E}_m = {\mathbf{U}|\mathbf{I}<em>m}$ 表示 $\nu</em>{m}$ 的嵌入。</p>
<h3>MF-based Encoder</h3>
<p>基于 MF 的编码器的核心思想是将用户项目评级矩阵 R 分解为两个低阶隐藏嵌入 U 和 I。评分矩阵 R 的近似值可以表示为：
$$
\mathbf{R}\approx \hat{\mathbf{R}} = \mathbf{U}\mathbf{I}^T, \tag{1}
$$</p>
<p>损失函数可以定义为：
$$
\min_{\mathbf{U},\mathbf{I}}| \mathbf{R} - \hat{\mathbf{R}}| _F^2 +\lambda (| \mathbf{E}| _F^2), \tag{2}
$$</p>
<p>由于该项目的多样化特征，粗略地说，MRS 的评级矩阵 R 的近似值可以表示为：
$$
\begin{array}{r}\mathbf{R}\approx \hat{\mathbf{R}} = \mathbf{U}\mathbf{I}^T,\quad \mathbf{I} = \mathrm{Aggr}(\mathbf{I}_m),\ \mathbf{R}\approx \hat{\mathbf{R}} = \mathrm{Aggr}(\mathbf{U}\mathbf{I}_m^T), \end{array} \tag{3}
$$</p>
<p>其中 Aggr（·）表示多模态融合。损失函数可以定义为：
$$
\min_{\mathbf{U},\mathbf{I}}| \mathbf{R} - \hat{\mathbf{R}}| <em>F^2 +\lambda (\sum</em>{m\in M}| \mathbf{E}_m| _F^2). \tag{5}
$$</p>
<blockquote>
<p>[!tip]
所以说，矩阵分解的 MRS 是让分解后的评分矩阵和原始评分矩阵尽可能相似。至于多模态，就是不同的融合方式而已。</p>
</blockquote>
<h3>Graph-based Encoder</h3>
<p>图基编码器的核心思想是利用节点的特征和图的结构信息来学习节点的表示。我们介绍常用的图基编码器范式图卷积网络（GCN）。对于一个图 $\mathcal{G}$ 及其邻接矩阵 $\mathbf{A}$，GCN 的一层传播可以表示为：
$$
\mathbf{E}^{(l)} = \sigma (\tilde{\mathbf{D}}^{-\frac{1}{2}}\tilde{\mathbf{A}}\tilde{\mathbf{D}}^{-\frac{1}{2}}\mathbf{E}^{(l - 1)}\mathbf{W}^{(l - 1)}), \tag{6}
$$
其中 $\mathbf{E}^{(l)}$ 是 $\mathcal{V}$ 的第 $l$ 层隐藏嵌入，$\tilde{\mathbf{A}} = \mathbf{A} + \mathbf{I}$。$\tilde{\mathbf{D}}$ 是 $\tilde{\mathbf{A}}$ 的度矩阵，其中 $\tilde{\mathbf{D}}<em>{ii} = \sum</em>{j}\tilde{\mathbf{A}}<em>{ij}$。$\mathbf{W}^{(l - 1)}$ 是第 $l - 1$ 层的权重矩阵，$\sigma (\cdot)$ 是激活函数。在推荐领域，LightGCN 证明了 GCN 中的权重矩阵和激活函数是无用的，甚至增加了训练难度。为此，广泛使用的简化 GCN 可以表示为：
$$
\mathbf{E}^{(l)} = \tilde{\mathbf{D}}^{-\frac{1}{2}}\tilde{\mathbf{A}}\tilde{\mathbf{D}}^{-\frac{1}{2}}\mathbf{E}^{(l - 1)}, \tag{7}
$$
然后它通过 $\tilde{\mathbf{E}} = \mathrm{Stack}</em>{l\in L}(\mathbf{E}^{(l)})$ 堆叠多个层，其中 $L$ 是 GCN 的总层数。我们简单地将整个基于图的编码器定义为 $\tilde{\mathbf{E}} = \mathrm{GCN}(\mathbf{U},\mathbf{I})$ 。整个表示 $\tilde{\mathbf{E}}$ 可以通过 $\tilde{\mathbf{U}},\tilde{\mathbf{I}} = \mathrm{Sp}(\tilde{\mathbf{E}})$ 分成用户和项目部分。为了更好地挖掘用户和项目表示，提出了两种<strong>同构类型图</strong>，用户 - 用户和项目 - 项目。一部分基于图的编码器将使用其中一个或全部来更好地学习表示。同构图首先按相似性保留前 $k$ 个项目/用户：
$$
\begin{array}{r}\mathbf{S}<em>{i,i^{\prime}}^{I} = \left{ \begin{array}{ll}1, &amp; \mathbf{S}</em>{i,i^{\prime}}^{I}\in \mathrm{top - }k(\mathbf{S}<em>{i,i^{\prime}}^{I}),\ 0, &amp; \mathrm{otherwise} \end{array} \right., \
\mathbf{S}</em>{u,u^{\prime}}^{U} = \left{ \begin{array}{ll}1, &amp; \mathbf{S}<em>{u,u^{\prime}}^{U}\in \mathrm{top - }k(\mathbf{S}</em>{u,u^{\prime}}^{U}),\ 0, &amp; \mathrm{otherwise} \end{array} \right.. \end{array} \tag{8-9}
$$</p>
<p>为缓解图结构数据中高阶顶点与低阶顶点之间特征分布的显著差异问题，通常对邻接矩阵应用对称归一化，表示为 $\mathbf{S}^{U} = (\mathbf{D}^{U})^{- 1 / 2}\mathbf{S}^{U}(\mathbf{D}^{U})^{- 1 / 2}$ 和 $\mathbf{S}^{I} = (\mathbf{D}^{I})^{- 1 / 2}\mathbf{S}^{I}(\mathbf{D}^{I})^{- 1 / 2}$，其中 $\mathbf{D}^{U}$ 和 $\mathbf{D}^{I}$ 分别表示 $\mathbf{S}^{U}$ 和 $\mathbf{S}^{I}$ 的对角度矩阵。此归一化过程至关重要，因为它根据每个顶点的连通性调整其影响，从而防止高阶顶点在特征表示中占据不成比例的优势。然后通过传播 $\tilde{\mathbf{U}} /\tilde{\mathbf{I}}$：</p>
<p>$$
\begin{array}{r}\hat{\mathbf{U}} = (\mathbf{S}^U)^{L_u}\bar{\mathbf{U}},\hat{\mathbf{I}} = (\mathbf{S}^I)^{L_i}\bar{\mathbf{I}}, \end{array} \tag{10}
$$
其中 $L_{u}$ 和 $L_{i}$ 分别是用户-用户图和物品-物品图的层数。这两种表示可以通过 $\tilde{\mathbf{U}} = \tilde{\mathbf{U}} +\tilde{\mathbf{U}}$ 和 $\tilde{\mathbf{I}} = \hat{\mathbf{I}} +\bar{\mathbf{I}}$ 可选地增强用户和物品表示。</p>
<p>为简化起见，我们将包含可选用户-用户和物品-物品图的复合图编码器定义为：
$$
\tilde{\mathbf{U}},\tilde{\mathbf{I}} = \mathrm{C - GCN}(\mathbf{U},\mathbf{I})) \tag{11}
$$</p>
<p>MRS的评分矩阵 $\mathbf{R}$ 的近似可以表示为：
$$
\mathbf{R}\approx \hat{\mathbf{R}} = \tilde{\mathbf{U}}\tilde{\mathbf{I}}^T,\quad \tilde{\mathbf{U}},\tilde{\mathbf{I}} = \mathrm{C - GCN}(\mathbf{U},\mathbf{I})). \tag{12}
$$</p>
<p>损失函数可以定义为公式1。粗略地说，MRS的评分矩阵 $\mathbf{R}$ 的近似可以表示为：
$$
\begin{array}{r}\mathbf{R}\approx \hat{\mathbf{R}} = \tilde{\mathbf{U}}\tilde{\mathbf{I}}^T,\quad \tilde{\mathbf{U}},\tilde{\mathbf{I}} = \mathrm{C - GCN}(\mathbf{U},\mathrm{Aggr}(\mathbf{I}_m)),\ \mathbf{R}\approx \hat{\mathbf{R}} = \mathrm{Aggr}(\tilde{\mathbf{U}}\tilde{\mathbf{I}}_m^T),\quad \tilde{\mathbf{U}},\tilde{\mathbf{I}}_m = \mathrm{C - GCN}(\mathbf{U},\mathbf{I}_m). \end{array} \tag{13}
$$</p>
<h2>多模态融合</h2>
<p>多模态融合是MRS中的一个关键研究领域。多模态整合的效果在很大程度上受到不同模态融合时间以及用于执行这种融合的策略的影响。因此，基于这两个视角：时间与策略，对现有工作进行系统分析和分类变得至关重要。</p>
<p><img src="https://img.085404.xyz/images/98712ff1beee8f2d62cf12b804327faa.webp" alt="image.png" /></p>
<h3>时间</h3>
<p>我们首先从时间角度对MRS中的所有近期工作进行分类。具体来说，可以分为早期融合和晚期融合策略。形式上，
$$
\mathbf{E} = \mathrm{Encoder}(\mathbf{U},\mathrm{Aggr}(\mathbf{I}_m)), \tag{15}
$$
$$
\mathbf{\bar{E}} = \mathrm{Aggr}(\mathrm{Encoder}(\mathbf{U},\mathbf{I}_m)), \tag{16}
$$
早期融合表示在<strong>图消息传播和聚合之前</strong>融合所有模态。晚期融合表示在<strong>图消息传播和聚合之后</strong>融合所有模态。尽管早期和晚期融合时机在各种场景中都表现出色，但每种方法都表现出不同的局限性。具体来说，早期融合倾向于在早期阶段整合模态，这可能导致模态特定特征未能被充分挖掘，因为过早整合。另一方面，后期融合策略在较晚阶段结合模态，面临充分捕捉和利用不同模态之间相关性的挑战。这可能会限制模型提取不同模态之间相关性的能力，从而可能降低利用多模态信息的整体效果。</p>
<blockquote>
<p>[!note]
其实还有一个问题，如果模型计算比较复杂，早期融合的效率会比晚期融合高很多。因为现“融成一坨”之后，你就只需要对这一个 tensor 做计算，否则你需要对每个模态分别处理，即复杂度线性增加（取决于模态数量）。</p>
</blockquote>
<h3>策略</h3>
<p>从策略角度看，所有近期的工作可以从两个维度进行细粒度分类，即<strong>逐元素或拼接</strong>以及<strong>注意力或启发式</strong>。MRS中的许多工作都对融合策略进行了细微调整。尽管存在这些变化，但可以根据推动其使用的潜在动机，以一种广泛的方式对这些方法进行分类，如表IV所述。</p>
<table>
<thead>
<tr>
<th>Fusion Strategy</th>
<th>Concatenation</th>
<th>Element-wise</th>
</tr>
</thead>
<tbody>
<tr>
<td>启发式</td>
<td>$E_v|E_t$</td>
<td>$E_v+E_t$</td>
</tr>
<tr>
<td>注意力</td>
<td>$\alpha_vE_v|\alpha_tE_t$</td>
<td>$\alpha_vE_v+\alpha_tE_t$</td>
</tr>
<tr>
<td>这里只用了视觉（Visual）和文本（Textual）两种模态做示例。</td>
<td></td>
<td></td>
</tr>
</tbody>
</table>
<p>元素级融合方法相比拼接方法提供了更深入的不同模态整合。然而，这种更深层次的整合可能会无意中放大模态数据中存在的固有噪声。另一方面，注意力方法相比于启发式方法，提供了更动态的模态权重分配，使得可以根据输入特征与特定任务的相关性，更适应性和响应性地处理输入特征。尽管有其优势，但注意力机制会显著增加计算成本，并增加训练阶段的复杂性。</p>
<p>融合策略通常与融合时机协同作用，从而显著影响模型性能。认识到这种相互作用，我们对MRS中的近期融合策略和时机进行了全面总结，如图5所示。我们的目标是为研究人员提供一个更清晰的视角，了解这些观点如何共同作用以影响模型性能。</p>
<blockquote>
<p>啧，要不说高手写的论文就是 8 一样呢。两个不同的 entity tensor 要么加要么拼，要么加权之后再加/再拼。硬是能单开出一节 Strategy 来写，6</p>
</blockquote>
<h2>分类法</h2>
<p>Taxonomy 在论文的每一章都进行了分类，就是字面意义上的归个类，然后简要的写几笔。有需要的可以看下原文。这里只放一个模型时序的分类图足以。
<img src="https://img.085404.xyz/images/138c6beab5ace7305aba5a3749a90703.webp" alt="image.png" /></p>
<blockquote>
<p>话又说回来了，22 年以后基本就很少见到矩阵分解的做法了，大多都是基于 GCN 的。猜测可能是 MF 对数学理论功底要求比较高，想水还真不太好水，GCN 就不一样了。。。</p>
</blockquote>
<h2>损失函数</h2>
<p>MRS使用由两个关键类型组成的损失函数：<strong>主要任务</strong>和<strong>辅助任务</strong>，如图7所示。主要任务涉及监督学习，其中明确的标签指导模型的学习，确保从标记数据中进行准确预测。相反，辅助任务采用自监督学习（SSL），它从数据的内在结构生成监督信号，而无需外部标签。这种方法能够有效利用未标记的数据，使系统即使在标记数据稀疏的情况下也能提取有意义的表示并保持准确性。我们详细介绍了这两种类型的损失函数。</p>
<p><img src="https://img.085404.xyz/images/b842000fbc0445f923a6d6fd68bb7103.webp" alt="image.png" /></p>
<h3>监督学习</h3>
<p>初步：在RS场景中，我们将推荐模型简化定义为 $f(\cdot)$ ，它可以通过以下方式预测用户 $u$ 和物品 $i$ 之间的评分：
$$
y_{u,i} = f(\mathbf{R},u,i), \tag{17}
$$
其中 $\mathbf{R} = [r_{u,i}]^{|U|\times |Z|}$ 表示用户-物品交互矩阵。在MRS场景中，多模态推荐模型将扩展为 $f_{m}(\cdot)$ ，它可以通过多模态信息预测用户 $u$ 和物品 $i$ 之间的评分：
$$
y_{u,i} = f_{m}(\mathbf{R},u,i,\mathbf{M}), \tag{18}
$$</p>
<p>监督学习任务是将任何用户 $u$ 和项目 $i$ 之间的预测得分拉近到真实标签。它可以分为<strong>点损失</strong>和<strong>成对损失</strong>。</p>
<p>对于逐点损失，我们引入了两种常见的损失函数，这些函数在许多机器学习下游任务中也被广泛使用，例如计算机视觉和自然语言处理。1）均方误差（MSE） 和 2）交叉熵损失（CE）。</p>
<p>MSE可以定义为：
$$
\frac{1}{|\mathcal{D}|}\sum_{(u,i)\in \mathcal{C}}(r_{u,i} - y_{u,i})^2, \tag{19}
$$
其中 $\mathcal{D}$ 表示训练集。这个损失函数将交互的用户-物品对的预测得分更接近1，非交互的用户-物品对的预测得分更接近0。</p>
<p>CE 可以定义为：
$$
-\frac{1}{|\mathcal{D}|}\sum_{(u,i)\in \mathcal{D}}[r_{u,i}\log (y_{u,i}) + (1 - r_{u,i})\log (1 - y_{u,i})]. \tag{20}
$$
在RS（MRS）领域中利用CE，将预测函数视为一个两分类任务，将用户-物品对分类为已交互和未交互类别。这通过最小化预测概率分布与真实标签分布之间的差异来提高分类精度，使模型能够更好地学习分类标签的概率分布。然而，由于用户-物品交互数据的天然稀疏性，我们需要更加重视稀缺的标记数据，因此进一步提出了成对损失。</p>
<p>对于点损失，学习目标是拉近正样本对，同时推开负样本对。我们引入了两种常用的损失函数，它们也是广泛使用的损失函数。1）贝叶斯个性化排序（BPR） 和 2）Hinge 损失。</p>
<p>BPR 可以定义为：
$$
\sum_{(u,i^+,i^-)\in \mathcal{D}} - \log (\sigma (y_{u,i^+} - y_{u,i^-})), \tag{21}
$$
其中 $y_{u,i^+}$ 和 $y_{u,i^- }$ 是用户 $u$ 对正样本项 $i^+$ 和负样本项 $i^-$ 的评分。$\sigma$ 是激活函数。</p>
<p>Hinge Loss 可以定义为：
$$
\max (0,1 - r_{u,i}\cdot y_{u,i}). \tag{22}
$$
Hinge Loss 旨在不仅正确分类数据点，而且最大化分类决策边界的间距。</p>
<blockquote>
<p>Hinge Loss? 你还别说，真没见过😅</p>
</blockquote>
<h3>自监督学习</h3>
<p>在 RS 场景中，自监督学习首先为对比学习创建两个不同的视图，我们简单地将这个视图生成器定义为：
$$
w = \mathcal{C}(\mathbf{R},\mathbf{E}), \tag{23}
$$
其中 $\mathcal{C}(\cdot)$ 是一个视图创建者。$\mathbf{R}$ 和 $\mathbf{E}$ 使用与第四节相同的定义。在MRS场景中，视图创建者可以进一步定义为
$$
w = \mathcal{C}(\mathbf{R},\mathbf{E},\mathbf{M}). \tag{24}
$$</p>
<blockquote>
<p>[!warning]
我倒是不否定目前 RS 中的 SSL 基本就是对比学习了，但是直接把自监督特指为对比学习多少还是有点不合适吧。。。</p>
</blockquote>
<p>视图创建者可分为两种类型：1）基于特征的SSL和2）基于结构的SSL。</p>
<p><strong>基于特征的SSL</strong>旨在通过扰动特征生成同一数据实例的多个视图。这种方法被战略性地用于增强学习表示的鲁棒性。通过在输入特征中引入变化，模型被迫关注这些修改中的不变方面，从而获得对用户-项目交互更深入、更通用的理解。它可以简化表示为：
$$
\omega = \mathcal{C}_{feature}(\mathbf{R},\mathbf{E},\mathbf{M}) = (\mathbf{R},\mathrm{Perturb}(\mathbf{E}),\mathbf{M}), \tag{25}
$$
其中 Perturb 可以是 MLP、特征丢弃、添加随机噪声等。值得注意的是，不同的模态可以自然地被视为两种基于特征的视图。对两种不同模态表示应用基于特征的自监督学习可以看作是模态对齐。</p>
<p><strong>基于结构的 SSL</strong>旨在通过扰动图结构为同一数据实例生成多个视图。这种方法被用来细致地捕捉图结构中固有的复杂依赖关系和交互作用，这对于提高基于图的学习模型的鲁棒性和性能至关重要。这种操作使学习算法能够从数据的本质特征中辨识并进行泛化，从而在图数据普遍的领域中可能产生更有效和有见地的表示。它可以简化表示为：
$$
\omega = \mathcal{C}_{structure}(\mathbf{R},\mathbf{E},\mathbf{M}) = (\mathrm{Perturb}(\mathbf{R}),\mathbf{E},\mathbf{M}), \tag{26}
$$
其中 Perturb $(\cdot)$ 可以是 MLP、节点/边丢弃、添加随机噪声等。</p>
<p>自监督学习任务是强制生成的视图更接近，以增强MRS模型的表示能力。我们引入了两种广泛使用的自监督损失函数。1）InfoNCE 和 2）Jensen- Shannon散度（JS）。</p>
<p>InfoNCE 是<strong>噪声对比估计</strong>的一种变体，作为推荐系统中的自监督学习损失函数得到了广泛采用。它可以表示为：
$$
\mathbb{E}[-\log \frac{\exp(f(\omega_i',\omega_i''))}{\sum_{\forall i,j}\exp(f(\omega_i',\omega_j''))}], \tag{27}
$$
其中 $f(\cdot)$ 表示一个评判函数，用于计算两个视图之间的相似性得分。项 $\exp f(\omega_i',\omega_i'')$ 对应正对的得分，而项 $\sum \exp (f(\omega_i',\omega_j''))$ 包含分子和所有负对的得分。</p>
<p>除了使用InfoNCE估计互信息外，下界也可以通过Jensen-Shannon（JS）散度进行估计。推导出的学习目标类似于将InfoNCE与标准二元交叉熵损失相结合，应用于正样本对和负样本对。
$$
\mathbb{E}[-\log \sigma (f(\omega_i',\omega_i''))] - \mathbb{E}[\log (1 - \sigma (f(\omega_i',\omega_j'')))], \tag{28}
$$
其中σ表示用于归一化判别函数输出的sigmoid函数。这种优化的主要思想是将标签1分配给正样本对，将标签0分配给负样本对，从而提高正样本对的预测值并增强它们之间的相似性。</p>
]]></content>
    <author><name>Sun2ot</name></author>
    <category term="教程"/>
  </entry>
  <entry>
    <title>多Git用户管理</title>
    <link href="https://blog.085404.xyz/posts/%E5%A4%9Agit%E7%94%A8%E6%88%B7%E7%AE%A1%E7%90%86/" rel="alternate" type="text/html"/>
    <id>https://blog.085404.xyz/posts/%E5%A4%9Agit%E7%94%A8%E6%88%B7%E7%AE%A1%E7%90%86/</id>
    <published>2025-06-18T00:00:00.000Z</published>
    <updated>2025-06-18T00:00:00.000Z</updated>
    <summary></summary>
    <content type="html"><![CDATA[<h2>需求背景</h2>
<p>当用户具有多个 Git 账户时，就会出现 Git 权限的冲突问题。例如 A 账户无法向 B 账户下的仓库提交 commit。为此，可以通过多密钥的方式来解决。</p>
<h2>操作步骤</h2>
<p>首先为不同的账户创建密钥：</p>
<pre><code>ssh-keygen -t ed25519 -C "sun2ot" -f ~/.ssh/id_ed25519_sun2ot
</code></pre>
<p>这里的 <code>-C</code> 用于在密钥中插入注释，用于区分不同的密钥。<code>-f</code> 指定了你要文件的名称和路径。</p>
<p>类似的，可以用 <code>-C "Account of B"</code> 来创建其他账户的密钥。</p>
<p>然后，在 <code>~/.ssh/config</code> 中添加对应的 SSH 配置：</p>
<pre><code>Host github-sun2ot
  Hostname ssh.github.com
  User git
  Port 443
  IdentityFile ~/.ssh/id_ed25519_sun2ot
</code></pre>
<p>这里的 <code>IdentityFile</code> 注意要设置成私钥（即不带 <code>.pub</code> 后缀）。此外，SSH 的默认端口本来是 22，但有的时候由于代理或者防火墙等原因，实测换成 https 的 443 端口会更好。</p>
<p><code>Host</code> 字段用于区分不同的配置名称，可以设置成任意你觉得便于记忆的样式。</p>
<p>设置完成后，即可进行 Git 仓库的配置：</p>
<pre><code># 原始
git remote add origin git@github.com:sun2ot/AAA.git
# 修改
git remote add origin git@github-sun2ot:sun2ot/AAA.git
</code></pre>
<p>对于默认的 Git SSH 配置，<code>@</code> 后的域名是 github.com。但这里为了启用我们自定义的 SSH 配置，所以修改为了 <code>~/.ssh/config</code> 中对应的 Host 字段。</p>
<h2>注意事项</h2>
<p>请检查你的家目录下是否存在 <code>.gitconfig</code>（好像是叫这个，我记不太清了）文件。该文件会在你执行 <code>git config --global user.name "sun2ot"</code> 等类似的全局操作后产生。</p>
<p>如果存在全局配置文件，则会覆盖你在仓库路径下设置的局部配置。从而产生无论你怎么修改提交的用户，你会发现始终是一个用户（全局用户）提交的。你可以直接删除这个文件，或者从中删除对应的鉴权信息即可。</p>
]]></content>
    <author><name>Sun2ot</name></author>
    <category term="教程"/>
  </entry>
  <entry>
    <title>二进制部署 MySQL</title>
    <link href="https://blog.085404.xyz/posts/%E4%BA%8C%E8%BF%9B%E5%88%B6%E9%83%A8%E7%BD%B2-mysql/" rel="alternate" type="text/html"/>
    <id>https://blog.085404.xyz/posts/%E4%BA%8C%E8%BF%9B%E5%88%B6%E9%83%A8%E7%BD%B2-mysql/</id>
    <published>2025-04-30T00:00:00.000Z</published>
    <updated>2025-04-30T00:00:00.000Z</updated>
    <summary></summary>
    <content type="html"><![CDATA[<h2>下载</h2>
<p>官网下载 <a href="https://dev.mysql.com/downloads/mysql/">MySQL Community Server</a></p>
<p><img src="https://img.085404.xyz/images/f92e6cabf60e9147327880b713bf1107.webp" alt="1745995346881.png" /></p>
<p>这里的 glibc 版本视情况选择合适的版本。工作站系统更新过，可以直接部署 glibc 2.28 版本。云服务器系统过于老旧，请使用 glibc 2.17 版本。</p>
<p><img src="https://img.085404.xyz/images/e9021f010d0baacb78fd0cc3736d2174.webp" alt="1745995480809.png" /></p>
<p>下载对应的压缩包。如无特殊需求，直接选体积最大的那个拉满即可。</p>
<h2>解压</h2>
<p>解压压缩包到 <code>/usr/local</code> 下</p>
<pre><code>sudo tar -xJvf mysql-8.4.5-linux-glibc2.28-x86_64.tar.xz -C /usr/local
# 重命名一下文件名，方便后续操作
sudo mv /usr/local/mysql-8.4.5-linux-glibc2.28-x86_64 mysql
</code></pre>
<p>为了便于后续操作，先配置环境变量</p>
<pre><code>export "/usr/local/mysql/bin" &gt;&gt; ~/.bashrc
source ~/.bashrc
</code></pre>
<h2>初始化</h2>
<p>创建系统用户和数据目录，为启动 MySQL 做准备</p>
<pre><code># 新建 mysql 用户（无登录 shell）
sudo useradd -r -s /bin/false mysql

# 建立数据目录并授权
sudo mkdir -p /mnt/mysql/data
sudo mkdir -p /mnt/mysql/log
sudo chown -R mysql:mysql /mnt/mysql
</code></pre>
<blockquote>
<p>工作站的 MySQL，数据目录是一个 512G 的逻辑卷，挂载在 <code>/mnt/mysql</code> 下。</p>
</blockquote>
<p>初始化数据目录（只需要执行一次）。这里只是初始化，还没有启动 MySQL。</p>
<pre><code>sudo /usr/local/mysql/bin/mysqld \
  --initialize-insecure \
  --basedir=/usr/local/mysql \
  --datadir=/mnt/mysql/data \
  --user=mysql
</code></pre>
<blockquote>
<p>这样 root 初始无密码，后续可用 ALTER USER 'root'@'localhost' IDENTIFIED BY 'YourStrong!Pass'; 设置密码。</p>
</blockquote>
<p>编写 MySQL 配置文件：在 <code>/mnt/mysql</code> 下新建一个 <code>my.cnf</code>，插入以下内容并保存。</p>
<pre><code>[client]
port            = 3306

[mysqld]
user            = mysql
port            = 3306
# 开启局域网访问
bind-address    = 0.0.0.0
basedir         = /usr/local/mysql
datadir         = /mnt/mysql/data

# 日志
log-error       = /mnt/mysql/log/mysql.err

# 国际化与插件目录（解决找不到 errmsg.sys、.so 问题）
lc-messages-dir = /usr/local/mysql/share
plugin-dir      = /usr/local/mysql/lib/plugin

# 可根据需要添加优化项，例如：
# max_connections = 200
# innodb_buffer_pool_size = 1G
</code></pre>
<h2>配置 MySQL 服务</h2>
<p>在 <code>/etc/systemd/system</code> 下新建一个 systemd 单元文件 <code>mysqld.service</code></p>
<pre><code>sudo vim /etc/systemd/system/mysqld.service
</code></pre>
<p>插入以下内容：</p>
<pre><code># /etc/systemd/system/mysqld.service

[Unit]
Description=MySQL Community Server
After=network-online.target
Wants=network-online.target

[Service]
Type=simple
User=mysql
Group=mysql

# 启动命令
ExecStart=/usr/local/mysql-8.4.5-linux-glibc2.28-x86_64/bin/mysqld --defaults-file=/mnt/mysql/my.cnf

# 停止命令
ExecStop=/usr/local/mysql-8.4.5-linux-glibc2.28-x86_64/bin/mysqladmin \
  --defaults-file=/mnt/mysql/my.cnf \
  shutdown

# 异常时自动重启
Restart=on-failure
# 对于“正常退出”（exit code 0）不重启
RestartPreventExitStatus=0

# 打开文件句柄数上限
LimitNOFILE=5000

[Install]
WantedBy=multi-user.target
</code></pre>
<p>保存后执行：</p>
<pre><code>sudo systemctl daemon-reload
sudo systemctl enable mysqld
</code></pre>
<h2>启动 MySQL</h2>
<pre><code># 启动
sudo systemctl start mysqld

# 查看状态
sudo systemctm status mysqld

# 停止
sudo systemctl stop mysqld

# 重启
sudo systemctl restart mysqld
</code></pre>
<p>启动 MySQL 后，测试是否能够正常连接</p>
<pre><code>mysql -uroot -p
</code></pre>
<p>由于上面通过 <code>--initialize-insecure</code> 参数初始化 MySQL，因此 root 用户默认密码为空，直接回车登录即可。</p>
<p>确认登录成功后，重新设置 root 密码（强密码）：</p>
<pre><code>ALTER USER 'root'@'localhost' IDENTIFIED BY 'YourStrong!Pass';
</code></pre>
<h2>权限配置</h2>
<p>按照上述操作启动 MySQL 后，在终端可以通过 root 进行登录，但是通过远程连接是无法登录的。因为 MySQL 的用户是<strong>用户名 + 主机</strong>两元组控制的：</p>
<table>
<thead>
<tr>
<th>用户</th>
<th>允许登录来源</th>
</tr>
</thead>
<tbody>
<tr>
<td>root@localhost</td>
<td>仅限本机（localhost/127.0.0.1）</td>
</tr>
<tr>
<td>root@IP</td>
<td>允许从 IP 访问</td>
</tr>
<tr>
<td>root@%</td>
<td>允许从任意主机访问</td>
</tr>
</tbody>
</table>
<p>如果是自己本机部署 MySQL 开发，root 用户裸奔无所谓。但是服务器部署就需要考虑权限问题，十几个人通过一个 root 账户进行开发是很傻逼的行为。</p>
<p>对于每个用户，按照最小权限原则，只需要授予其需要使用的数据库权限即可。一般一人一个即可，如果有协作开发场景，那就授予多人这个库的权限。</p>
<p>首先，创建数据库。</p>
<pre><code>CREATE DATABASE dev_yzh;
</code></pre>
<p>然后，创建需要使用这个库的用户（这个一次就够了，后续都是这几个用户）。</p>
<pre><code>CREATE USER 'yzh'@'172.26.%' IDENTIFIED BY '用户yzh的密码';
</code></pre>
<blockquote>
<p>这里使用 <code>%</code> 通配符对 IP 进行了限制，即分配 IP 的 172.26 网段。</p>
</blockquote>
<p>授予用户 <code>yzh</code> 数据库 <code>dev_yzh</code> 的所有权限。</p>
<pre><code>GRANT ALL PRIVILEGES ON dev_yzh.* TO 'yzh'@'172.26.%';
FLUSH PRIVILEGES;
</code></pre>
<p>也可以对权限进行限制，只授予表结构变更和数据操作的权限，保留全局管理操作的权限：</p>
<pre><code>GRANT SELECT, INSERT, UPDATE, DELETE, CREATE, DROP, ALTER
  ON dev_yzh.* TO 'yzh'@'172.26.%';
</code></pre>
<p>还可以通过 MySQL 8.0+ 的角色模板进行管理。</p>
]]></content>
    <author><name>Sun2ot</name></author>
    <category term="教程"/>
  </entry>
  <entry>
    <title>Linux 反病毒软件 ClamAV</title>
    <link href="https://blog.085404.xyz/posts/linux-%E5%8F%8D%E7%97%85%E6%AF%92%E8%BD%AF%E4%BB%B6-clamav/" rel="alternate" type="text/html"/>
    <id>https://blog.085404.xyz/posts/linux-%E5%8F%8D%E7%97%85%E6%AF%92%E8%BD%AF%E4%BB%B6-clamav/</id>
    <published>2025-03-03T00:00:00.000Z</published>
    <updated>2025-03-03T00:00:00.000Z</updated>
    <summary>Linux系统下的一种反病毒软件，可更新病毒库。</summary>
    <content type="html"><![CDATA[<h2>介绍</h2>
<p>Linux 操作系统以其强大的权限管理和开源特性而闻名，相比其他操作系统（如 Windows），它确实更不容易受到病毒和恶意软件的攻击。但某些时刻的确需要一个防病毒软件，可以考虑使用 <a href="https://www.clamav.net/">ClamAV：一款开源的防病毒引擎</a>。</p>
<h2>安装</h2>
<p>安装 clamav 及其守护进程</p>
<pre><code>sudo apt update
sudo apt install clamav clamav-daemon
</code></pre>
<p>执行 <code>clamscan --version</code>，显示版本信息说明安装成功。</p>
<h3>官方二进制安装</h3>
<p>如果本地的软件源过于老旧，可能导致下载的 clamav 在更新病毒库时被拦截，此时可以直接从<a href="https://www.clamav.net/downloads">官方源</a>安装最新版：</p>
<pre><code>wget https://www.clamav.net/downloads/production/clamav-1.5.1.linux.x86_64.deb
sudo apt install ./clamav-1.5.1.linux.x86_64.deb
</code></pre>
<h2>使用</h2>
<h3>配置</h3>
<blockquote>
<p>[!warning]
当且仅当通过<strong>官方二进制安装</strong>时，可能需要这一步。否则请直接前往下一步<a href="#%E6%9B%B4%E6%96%B0%E7%97%85%E6%AF%92%E5%BA%93">更新病毒库</a></p>
</blockquote>
<pre><code># 查找配置文件
ls /usr/local/etc | grep clam
</code></pre>
<p>会看到</p>
<pre><code>clamd.conf.sample
freshclam.conf.sample
</code></pre>
<p>将<strong>这两个文件</strong>重命名为无 <code>.sample</code> 后缀的形式并编辑，在文件开头找到如下内容：</p>
<pre><code>...
# Comment or remove the line below.
Example
...
</code></pre>
<p>注释掉 <code>Example</code> 这一行，即修改为 <code>#Example</code>，然后保存关闭即可。</p>
<h3>更新病毒库</h3>
<p>第一次安装后，需要手动更新病毒库：</p>
<pre><code># 先暂停病毒库后台服务，否则无法更新
sudo systemctl stop clamav-freshclam

# 更新病毒库数据
sudo freshclam

# 启动 clamav 服务
sudo systemctl enable clamav-freshclam --now
</code></pre>
<h3>查看病毒数据库目录及日期</h3>
<pre><code>ls -l /var/lib/clamav
</code></pre>
<h3>扫描文件</h3>
<p>默认情况下，clamav 需要手动调用 <code>clamscan</code> 以扫描文件/目录：</p>
<pre><code># -r 递归扫描
clamscan [-r] /path/to/file_or_path
</code></pre>
<p>clamav 会在终端显示扫描过程和结果，如果需要记录到日志，使用 <code>-l</code> 参数：</p>
<pre><code>clamscan /path/to/scan -l /path/to/logfile
</code></pre>
<p>其他常用参数：</p>
<ul>
<li><code>-i</code>：<code>inject</code>，仅显示（病毒）感染文件，避免大量刷 Log</li>
<li><code>--remove</code>：扫描到感染文件后删除该文件</li>
<li><code>--help</code>：帮助信息</li>
</ul>
<h3>脚本示例</h3>
<p>以家目录为例，扫描是否存在感染文件。可以添加到 crontab 定时执行。</p>
<pre><code>#!/bin/bash

SCAN_DIR="/home"
EXCLUDE_LIST=(
    "^/home/user1(/.*)?$"
    "^/home/[^/]+/miniforge3(/.*)?$"
    "^/home/[^/]+/\.conda(/.*)?$"
)

EXCLUDE_ARGS=""
for item in "${EXCLUDE_LIST[@]}"; do
    if [[ "$item" =~ \(/.*\)\?$ ]]; then
        EXCLUDE_ARGS+=" --exclude-dir='/$item/' "
    else
        EXCLUDE_ARGS+=" --exclude='/$item/' "
    fi
done

LOG_FILE="/var/log/clamav_home_scan.log"
echo "Scan started at $(date)" &gt;&gt; "$LOG_FILE"
clamscan -r $EXCLUDE_ARGS "$SCAN_DIR" &gt;&gt; "$LOG_FILE" 2&gt;&amp;1
echo "Scan completed at $(date)" &gt;&gt; "$LOG_FILE"
</code></pre>
<h2>参考文献</h2>
<ul>
<li><a href="https://www.sysgeek.cn/install-clamav-on-ubuntu/">系统极客：如何安装、配置和使用 ClamAV，守护 Ubuntu 系统安全</a></li>
<li><a href="https://aws.amazon.com/cn/blogs/china/deploy-clamav-on-ec2-with-realtime-scan-and-centralized-alarm/">亚马逊 AWS 官方博客：在 EC2 Linux 操作系统上部署 ClamAV 并开启实时防护、集中日志采集和统一告警</a></li>
</ul>
]]></content>
    <author><name>Sun2ot</name></author>
    <category term="教程"/>
  </entry>
  <entry>
    <title>解决毒瘤深信服easyconnect</title>
    <link href="https://blog.085404.xyz/posts/%E8%A7%A3%E5%86%B3%E6%AF%92%E7%98%A4%E6%B7%B1%E4%BF%A1%E6%9C%8Deasyconnect/" rel="alternate" type="text/html"/>
    <id>https://blog.085404.xyz/posts/%E8%A7%A3%E5%86%B3%E6%AF%92%E7%98%A4%E6%B7%B1%E4%BF%A1%E6%9C%8Deasyconnect/</id>
    <published>2025-02-20T00:00:00.000Z</published>
    <updated>2025-02-20T00:00:00.000Z</updated>
    <summary></summary>
    <content type="html"><![CDATA[<h2>简介</h2>
<p>深信服的恶名由来已久，我校不出意外也是使用该公司的 EasyConnect 进行 VPN 连接。由于全局 VPN 走校园网会导致网速缓慢（因为校园网慢啊），且校方当地的网络🚔似乎挺缺业绩的，因此需采取一定的手段封印这个毒瘤软件。</p>
<h2>部署 docker-easyconnect</h2>
<p><a href="https://github.com/docker-easyconnect/docker-easyconnect">docker-easyconnect: 使深信服（Sangfor）开发的非自由的 VPN 软件 EasyConnect 和 aTrust 运行在 docker 或 podman 中，并作为网关和/或提供 socks5、http 代理服务</a>。</p>
<p>按照官方文档给出的案例启动即可，这里为了方便后期维护，将其改写为 <code>docker-compose.yaml</code> 的形式以便管理：</p>
<pre><code>services:
  easyconnect:
    image: hagb/docker-easyconnect:cli
    container_name: easyconnect
    devices:
      - /dev/net/tun
    cap_add:
      - NET_ADMIN
    volumes:
      - $HOME/.easyconn:/root/.easyconn
    ports:
      - "127.0.0.1:1080:1080"
      - "127.0.0.1:8888:8888"
    environment:
      - EC_VER=7.6.7
      - EXIT=1
      - CLI_OPTS=-d xxx.edu.cn -u xxx -p xxx
</code></pre>
<p>关于各参数的作用，参考 <a href="https://github.com/docker-easyconnect/docker-easyconnect/blob/master/doc/usage.md">doc/usage</a> 即可，这里只对几个关键参数做出说明。</p>
<p>docker-easyconnect 有命令行与图形化界面两种部署形式。由于我校的 VPN 仅需学号 + 密码即可登录，因此只需要命令行部署即可。</p>
<p>在上述的配置文件中：</p>
<ul>
<li><code>ports</code>：端口映射，其中 <code>1080</code> 为 http 端口，<code>8888</code> 为 socks 端口</li>
<li><code>EC_VER</code>：easyconnect 的版本，可选 7.6.7/7.6.3，参考 <a href="https://github.com/docker-easyconnect/docker-easyconnect/blob/master/doc/usage.md#easyconnect-%E7%89%88%E6%9C%AC%E9%80%89%E6%8B%A9">EasyConnect 版本选择</a></li>
<li><code>CLI_OPTS</code>：配置登录信息，其中 <code>-d</code> 指定 VPN 域名，<code>-u</code> 指定用户名，<code>-p</code> 指定密码</li>
</ul>
<h2>使用方式</h2>
<p>编辑好镜像文件后，即可启动容器：</p>
<pre><code>docker compose -f xxx.yaml up -d
</code></pre>
<p>由于我是在 <strong>WSL2</strong> 下配置镜像模式使用的，因此容器的网络与宿主机网络完全一致，因此只需要在需要代理的软件中进行配置即可。</p>
<blockquote>
<p>关于部署 WSL，可参考 <a href="https://blog.085404.xyz/method/wsl.html">Blog: WSL Docs</a></p>
</blockquote>
<p>例如 ssh 连接内网服务器，只需要设置 proxy （假设使用 socks 代理）为 <code>127.0.0.1:8888</code> 即可登录。</p>
<h2>存在的问题</h2>
<p><a href="https://github.com/docker-easyconnect/docker-easyconnect/issues/423">issue 423</a></p>
<p><s>暂不清楚什么原因，虽然成功登录，并且可以连接内网服务器，但浏览器设置代理时会访问失败，这导致一些需要校园网络环境的在线服务（如文献）等无法使用容器代理。该问题尚未解决，有待后续补充。</s></p>
<p>上述问题已解决，原因可参考 <a href="https://github.com/docker-easyconnect/docker-easyconnect/issues/154">issue 154</a>，主要是容器内的 dns 存在问题。按照作者给出的 <code>iptables</code> 方法无法解决，经测试可以通过以下方式解决该问题。</p>
<p>由于 dns 故障，因此对于域名无法解析，此处需分为三种情况：</p>
<ol>
<li><strong>内网域名可被公开 DNS 解析</strong>：此时只需要让代理请求能够在不依赖容器代理的情况下获得该域名的 IP 地址即可，参考下文；</li>
<li><strong>内网域名且只能被私有 DNS 解析</strong>：这种情况我暂未遇到，故暂不做考虑。但解决方式可能与情况 1 的一致；</li>
<li><strong>内网页面直接通过 IP 访问</strong>，无需域名：这种情况将不会受到 DNS 的干扰，因为无需解析域名获得 IP。</li>
</ol>
<p>针对情况 1，可以借助 Clash 来完成域名的解析，这里我使用的是 <a href="https://github.com/clash-verge-rev/clash-verge-rev">clash-verge-rev</a>，其基于 <a href="https://github.com/MetaCubeX/mihomo">Meta 内核</a> 构建。主要是需要代理内核支持 <code>hosts</code> 设定即可。</p>
<p>在 Clash 的配置文件中，添加如下内容：</p>
<pre><code>hosts:
  xxx.edu.cn: 1.2.3.4  # 更换为对应的 IP 地址
</code></pre>
<p>通过在代理内核中增加 hosts 映射，然后在浏览器中设置代理请求转发到 Clash，即可在容器代理之外获得域名对应的 IP 地址，从而绕过项目原有的 DNS Bug。</p>
]]></content>
    <author><name>Sun2ot</name></author>
    <category term="教程"/>
  </entry>
  <entry>
    <title>nvidia-smi 常用命令</title>
    <link href="https://blog.085404.xyz/posts/nvidia-smi-%E5%B8%B8%E7%94%A8%E6%8C%87%E4%BB%A4/" rel="alternate" type="text/html"/>
    <id>https://blog.085404.xyz/posts/nvidia-smi-%E5%B8%B8%E7%94%A8%E6%8C%87%E4%BB%A4/</id>
    <published>2025-02-18T00:00:00.000Z</published>
    <updated>2025-02-18T00:00:00.000Z</updated>
    <summary></summary>
    <content type="html"><![CDATA[<h2>显示当前显卡支持的频率</h2>
<pre><code>nvidia-smi -q -d SUPPORTED_CLOCKS
</code></pre>
<p>16 系往后的档位基本都在 15MHz 一档，该命令能够列出当前 GPU 支持的所有档位，便于后续的调整。</p>
<h2>设定显卡功耗上限</h2>
<pre><code>nvidia-smi -pl [功耗]
</code></pre>
<p>单位是瓦特，可以精确到小数点后两位。对笔记本电脑<strong>大概率不会生效</strong>，需借助 OEM 的控制台实现功耗控制，或者借助下文的频率设定变相实现功耗限制。</p>
<h2>锁定核心频率</h2>
<pre><code>nvidia-smi -lgc [频率]
</code></pre>
<p><strong>固定</strong>核心频率。频率需处于显卡支持的档位，并且要能够实现（总不能让你超 10G 吧😅）。</p>
<pre><code>nvidia-smi -lgc a,b
</code></pre>
<p><strong>限制核心频率区间</strong>（大于 a 小于 b）。打游戏的时候用这个多一点，比较好用。</p>
<pre><code>nvidia-smi -rgc
</code></pre>
<p>恢复对频率的任何修改。</p>
<blockquote>
<p>重启计算机也是可以的。</p>
</blockquote>
<h2>锁定显存频率</h2>
<p>操作方法与核心频率类似，只不过参数变为 <code>-lmc</code></p>
<pre><code># 固定显存频率
nvidia-smi -lmc [频率]

# 固定显存频率区间
nvidia-smi -lmc a,b

# 恢复显存频率
nvidia-smi -rmc
</code></pre>
]]></content>
    <author><name>Sun2ot</name></author>
    <category term="教程"/>
  </entry>
  <entry>
    <title>Windows CPU 睿频设置</title>
    <link href="https://blog.085404.xyz/posts/windows-cpu-%E7%9D%BF%E9%A2%91%E8%AE%BE%E7%BD%AE/" rel="alternate" type="text/html"/>
    <id>https://blog.085404.xyz/posts/windows-cpu-%E7%9D%BF%E9%A2%91%E8%AE%BE%E7%BD%AE/</id>
    <published>2025-02-18T00:00:00.000Z</published>
    <updated>2025-02-18T00:00:00.000Z</updated>
    <summary></summary>
    <content type="html"><![CDATA[<h2>关闭睿频</h2>
<p>Windows 10 可以通过设置电源模式为节能模式实现关闭睿频，Windows 11 需借助电源计划实现。</p>
<p>打开“控制面板 - 硬件和声音 - 电源选项”，默认的电源计划为 <code>平衡</code>。为使修改的电源计划在重启后仍然生效，需 <code>创建电源计划</code>：</p>
<p><img src="https://img.085404.xyz/images/8cdeeb910973be267815a47a1bd66d28.webp" alt="image.png" /></p>
<p>电源计划名字任意，点击 <code>下一页</code> 即可创建完成。</p>
<p>点击创建的电源计划右侧的 <code>更改计划设置</code>，然后 <code>更改高级电源设置</code>:</p>
<p><img src="https://img.085404.xyz/images/91391b76155c1a5a0779e355a36893a4.webp" alt="image.png" /></p>
<p>在弹出的电源选项中，找到 <code>最大处理器状态</code>，将下方的两个值从 100% 修改为 99% 即可关闭 CPU 睿频：</p>
<p><img src="https://img.085404.xyz/images/66a506aedf1ebd0823bdff8be03179ed.webp" alt="image.png" /></p>
<h2>设置处理器频率</h2>
<p>默认情况下，电源计划中“处理器电源管理”是没有 <code>处理器最大频率</code> 的设置项的，需要在注册表中手动开启。</p>
<p>找到注册表项：</p>
<pre><code>计算机\HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Control\Power\PowerSettings\54533251-82be-4824-96c1-47b60b740d00\75b0ae3f-bce0-45a7-8c89-c9611c25e100
</code></pre>
<p>修改其中的 <code>Attributes</code> 值为 2 即可。</p>
<p><img src="https://i-blog.csdnimg.cn/blog_migrate/cc8624ebf7710315db96c8cabbe98dbf.png" alt="" /></p>
]]></content>
    <author><name>Sun2ot</name></author>
    <category term="教程"/>
  </entry>
  <entry>
    <title>中文计算机EI期刊</title>
    <link href="https://blog.085404.xyz/posts/%E4%B8%AD%E6%96%87%E8%AE%A1%E7%AE%97%E6%9C%BAei%E6%9C%9F%E5%88%8A/" rel="alternate" type="text/html"/>
    <id>https://blog.085404.xyz/posts/%E4%B8%AD%E6%96%87%E8%AE%A1%E7%AE%97%E6%9C%BAei%E6%9C%9F%E5%88%8A/</id>
    <published>2025-01-15T00:00:00.000Z</published>
    <updated>2025-01-15T00:00:00.000Z</updated>
    <summary></summary>
    <content type="html"><![CDATA[<h2>说明</h2>
<ol>
<li>以下期刊排名不分先后，以本文编辑时间当期最新的 <a href="https://www.elsevier.com/products/engineering-village/databases/compendex">Compendex</a> Source List 为准</li>
<li>CCF 评级暂只参考<a href="https://www.ccf.org.cn/ccftjgjxskwml/">计算领域高质量科技期刊分级目录</a></li>
<li>内容不定时更新</li>
</ol>
<h2>软件学报</h2>
<table>
<thead>
<tr>
<th>属性</th>
<th>值</th>
</tr>
</thead>
<tbody>
<tr>
<td>英文名</td>
<td>Journal of Software</td>
</tr>
<tr>
<td>CCF 评级</td>
<td>🚀 T1</td>
</tr>
<tr>
<td>刊期</td>
<td>月刊</td>
</tr>
<tr>
<td>模板</td>
<td>Word ✅ LaTeX ❌</td>
</tr>
<tr>
<td>官网</td>
<td>https://www.jos.org.cn/jos/home/</td>
</tr>
</tbody>
</table>
<h2>自动化学报</h2>
<table>
<thead>
<tr>
<th>属性</th>
<th>值</th>
</tr>
</thead>
<tbody>
<tr>
<td>英文名</td>
<td>Acta Automatica Sinica</td>
</tr>
<tr>
<td>CCF 评级</td>
<td>🚀 T1</td>
</tr>
<tr>
<td>刊期</td>
<td>月刊</td>
</tr>
<tr>
<td>模板</td>
<td>Word ❌ LaTeX ✅</td>
</tr>
<tr>
<td>官网</td>
<td>http://www.aas.net.cn/</td>
</tr>
</tbody>
</table>
<h2>电子学报</h2>
<table>
<thead>
<tr>
<th>属性</th>
<th>值</th>
</tr>
</thead>
<tbody>
<tr>
<td>英文名</td>
<td>Acta Electronica Sinica</td>
</tr>
<tr>
<td>CCF 评级</td>
<td>🚀 T1</td>
</tr>
<tr>
<td>刊期</td>
<td>月刊</td>
</tr>
<tr>
<td>模板</td>
<td>Word ✅ LaTeX ❌</td>
</tr>
<tr>
<td>官网</td>
<td>https://www.ejournal.org.cn/CN/home/</td>
</tr>
</tbody>
</table>
<h2>工程科学与技术</h2>
<table>
<thead>
<tr>
<th>属性</th>
<th>值</th>
</tr>
</thead>
<tbody>
<tr>
<td>英文名</td>
<td>Advanced Engineering Science</td>
</tr>
<tr>
<td>CCF 评级</td>
<td>😅 无</td>
</tr>
<tr>
<td>刊期</td>
<td>双月刊</td>
</tr>
<tr>
<td>模板</td>
<td>Word ✅ LaTeX ❌</td>
</tr>
<tr>
<td>官网</td>
<td>https://jsuese.scu.edu.cn/zh/home/</td>
</tr>
<tr>
<td>主办单位</td>
<td>四川大学</td>
</tr>
<tr>
<td>注：</td>
<td></td>
</tr>
</tbody>
</table>
<ol>
<li>收录计算机科学与技术、人工智能领域的论文，但或许偏应用。请酌情参考。</li>
</ol>
<h2>计算机学报</h2>
<table>
<thead>
<tr>
<th>属性</th>
<th>值</th>
</tr>
</thead>
<tbody>
<tr>
<td>英文名</td>
<td>Chinese Journal of Computers</td>
</tr>
<tr>
<td>CCF 评级</td>
<td>🚀 T1</td>
</tr>
<tr>
<td>刊期</td>
<td>月刊</td>
</tr>
<tr>
<td>模板</td>
<td>Word ✅ LaTeX ✅</td>
</tr>
<tr>
<td>官网</td>
<td>http://cjc.ict.ac.cn/</td>
</tr>
</tbody>
</table>
<h2>科学通报（中文）</h2>
<table>
<thead>
<tr>
<th>属性</th>
<th>值</th>
</tr>
</thead>
<tbody>
<tr>
<td>英文名</td>
<td><a href="https://www.sciengine.com/CSB/home">Chinese Science Bulletin</a></td>
</tr>
<tr>
<td>CCF 评级</td>
<td>🚀 T1</td>
</tr>
<tr>
<td>刊期</td>
<td>旬刊</td>
</tr>
<tr>
<td>模板</td>
<td>Word ✅ LaTeX ❌ EndNote ✅</td>
</tr>
<tr>
<td>官网</td>
<td>http://cjc.ict.ac.cn/</td>
</tr>
<tr>
<td>主办方</td>
<td>中国科学院</td>
</tr>
</tbody>
</table>
<p>注：</p>
<ol>
<li>中文官网极其简陋（各种意义上）</li>
<li>模板需从英文官网下载</li>
</ol>
<h2>计算机集成制造系统</h2>
<table>
<thead>
<tr>
<th>属性</th>
<th>值</th>
</tr>
</thead>
<tbody>
<tr>
<td>英文名</td>
<td>Computer Integrated Manufacturing Systems（CIMS）</td>
</tr>
<tr>
<td>CCF 评级</td>
<td>👌 T3</td>
</tr>
<tr>
<td>刊期</td>
<td>月刊</td>
</tr>
<tr>
<td>模板</td>
<td>Word ✅ LaTeX ❌</td>
</tr>
<tr>
<td>官网</td>
<td>http://www.cims-journal.cn/CN/1006-5911/home.shtml</td>
</tr>
<tr>
<td>主办方</td>
<td>中国兵器工业集团第二一○研究所</td>
</tr>
</tbody>
</table>
<h2>计算机研究与发展</h2>
<table>
<thead>
<tr>
<th>属性</th>
<th>值</th>
</tr>
</thead>
<tbody>
<tr>
<td>英文名</td>
<td>Computer Research and Development</td>
</tr>
<tr>
<td>CCF 评级</td>
<td>🚀 T1</td>
</tr>
<tr>
<td>刊期</td>
<td>月刊</td>
</tr>
<tr>
<td>模板</td>
<td>Word ✅ LaTeX ❌</td>
</tr>
<tr>
<td>官网</td>
<td>https://crad.ict.ac.cn/</td>
</tr>
<tr>
<td>主办方</td>
<td>中国科学院计算技术研究所，中国计算机学会</td>
</tr>
</tbody>
</table>
<h2>武汉大学学报（信息科学版）</h2>
<table>
<thead>
<tr>
<th>属性</th>
<th>值</th>
</tr>
</thead>
<tbody>
<tr>
<td>英文名</td>
<td>Computer Research and Development</td>
</tr>
<tr>
<td>CCF 评级</td>
<td>😅 无</td>
</tr>
<tr>
<td>刊期</td>
<td>月刊</td>
</tr>
<tr>
<td>模板</td>
<td>Word ✅ LaTeX ❌</td>
</tr>
<tr>
<td>官网</td>
<td>http://ch.whu.edu.cn/qikanjieshao/</td>
</tr>
<tr>
<td>主办方</td>
<td>武汉大学</td>
</tr>
</tbody>
</table>
<p>注：</p>
<ol>
<li>计算机领域跟地理、遥感、图形图像学相关的可投</li>
</ol>
<h2>通信学报</h2>
<table>
<thead>
<tr>
<th>属性</th>
<th>值</th>
</tr>
</thead>
<tbody>
<tr>
<td>英文名</td>
<td>Journal on Communications</td>
</tr>
<tr>
<td>CCF 评级</td>
<td>🚀 T1</td>
</tr>
<tr>
<td>刊期</td>
<td>月刊</td>
</tr>
<tr>
<td>模板</td>
<td>Word ✅ LaTeX ❌</td>
</tr>
<tr>
<td>官网</td>
<td>https://www.infocomm-journal.com/txxb/CN/1000-436X/home.shtml</td>
</tr>
<tr>
<td>主办方</td>
<td>中国通信学会</td>
</tr>
</tbody>
</table>
<p>注：</p>
<ol>
<li>收录范围包括计算机网络、通信等</li>
</ol>
<h2>计算机辅助设计与图形学学报</h2>
<table>
<thead>
<tr>
<th>属性</th>
<th>值</th>
</tr>
</thead>
<tbody>
<tr>
<td>英文名</td>
<td>Journal of ComputerAided Design and Computer</td>
</tr>
<tr>
<td>CCF 评级</td>
<td>🚀 T1</td>
</tr>
<tr>
<td>刊期</td>
<td>月刊</td>
</tr>
<tr>
<td>模板</td>
<td>Word ✅ LaTeX ❌</td>
</tr>
<tr>
<td>官网</td>
<td>https://www.jcad.cn/</td>
</tr>
<tr>
<td>主办方</td>
<td>中国计算机学会，北京中科期刊出版有限公司（科学出版社）</td>
</tr>
</tbody>
</table>
<h2>模式识别与人工智能</h2>
<table>
<thead>
<tr>
<th>属性</th>
<th>值</th>
</tr>
</thead>
<tbody>
<tr>
<td>英文名</td>
<td>Pattern Recognition and Artificial Intelligence</td>
</tr>
<tr>
<td>CCF 评级</td>
<td>✈️ T2</td>
</tr>
<tr>
<td>刊期</td>
<td>月刊</td>
</tr>
<tr>
<td>模板</td>
<td>任意 😇</td>
</tr>
<tr>
<td>官网</td>
<td>http://manu46.magtech.com.cn/Jweb_prai/CN/volumn/home.shtml</td>
</tr>
<tr>
<td>主办方</td>
<td>中国自动化学会，国家智能计算机研究开发中心，中国科学院合肥智能机械研究所</td>
</tr>
</tbody>
</table>
<p>注：</p>
<ol>
<li>虽然这个官网看起来像盗版一样，但就是这么草台班子</li>
<li>以下摘录自期刊模板的原话：
<blockquote>
<p>“因为录用稿件在发表前需要专用软件排版，因此<strong>稿件没有明确模板要求</strong>。可<strong>参阅期刊已发表论文进行排版</strong>，包括题目、作者单位、中英文摘要、正文、参考文献和作者信息等。另外，已发表论文正文采用双栏排列，为方便审稿专家审阅，<strong>作者可单栏排列</strong>。”</p>
</blockquote>
</li>
</ol>
<h2>计算机工程与应用</h2>
<table>
<thead>
<tr>
<th>属性</th>
<th>值</th>
</tr>
</thead>
<tbody>
<tr>
<td>英文名</td>
<td>Computer Engineering and Applications</td>
</tr>
<tr>
<td>CCF 评级</td>
<td>✈️ T2</td>
</tr>
<tr>
<td>刊期</td>
<td>月刊</td>
</tr>
<tr>
<td>模板</td>
<td>Word ✅ LaTeX ❌</td>
</tr>
<tr>
<td>官网</td>
<td>http://cea.ceaj.org/CN/1002-8331/home.shtml</td>
</tr>
<tr>
<td>主办方</td>
<td>华北计算技术研究所</td>
</tr>
</tbody>
</table>
<p>注：</p>
<ol>
<li>截止 2025-01-17，尚未收录到 Elsevier Compendex Source List（文件尚未更新）</li>
</ol>
<h2>机器人</h2>
<table>
<thead>
<tr>
<th>属性</th>
<th>值</th>
</tr>
</thead>
<tbody>
<tr>
<td>英文名</td>
<td>Robot</td>
</tr>
<tr>
<td>CCF 评级</td>
<td>👌 T3</td>
</tr>
<tr>
<td>刊期</td>
<td>双月刊</td>
</tr>
<tr>
<td>模板</td>
<td>Word ✅ LaTeX ✅ Overleaf ✅</td>
</tr>
<tr>
<td>官网</td>
<td>http://cea.ceaj.org/CN/1002-8331/home.shtml</td>
</tr>
<tr>
<td>主办方</td>
<td>中国科学院沈阳自动化研究所，中国自动化学会</td>
</tr>
</tbody>
</table>
<p>注：</p>
<ol>
<li>🤖 支持 Overleaf，好文明！</li>
</ol>
<h2>北京航空航天大学学报</h2>
<table>
<thead>
<tr>
<th>属性</th>
<th>值</th>
</tr>
</thead>
<tbody>
<tr>
<td>英文名</td>
<td>Journal of Beijing University of Aeronautics and Astronautics</td>
</tr>
<tr>
<td>CCF 评级</td>
<td>😅 无</td>
</tr>
<tr>
<td>刊期</td>
<td>月刊</td>
</tr>
<tr>
<td>模板</td>
<td>Word ✅ LaTeX ❌</td>
</tr>
<tr>
<td>官网</td>
<td>https://bhxb.buaa.edu.cn/bhzk/</td>
</tr>
<tr>
<td>主办方</td>
<td>北京航空航天大学</td>
</tr>
</tbody>
</table>
<p>注：</p>
<ol>
<li><strong>自然科学版</strong>收录信息与电子技术、计算机科学及应用技术领域文章</li>
</ol>
<h2>北京邮电大学学报</h2>
<table>
<thead>
<tr>
<th>属性</th>
<th>值</th>
</tr>
</thead>
<tbody>
<tr>
<td>英文名</td>
<td>Journal of Beijing University of Posts and Telecommunications</td>
</tr>
<tr>
<td>CCF 评级</td>
<td>😅 无</td>
</tr>
<tr>
<td>刊期</td>
<td>双月刊</td>
</tr>
<tr>
<td>模板</td>
<td>Word ❌ LaTeX ❌ PDF ✅</td>
</tr>
<tr>
<td>官网</td>
<td>https://journal.bupt.edu.cn/CN/1007-5321/home.shtml</td>
</tr>
<tr>
<td>主办方</td>
<td>北京邮电大学</td>
</tr>
</tbody>
</table>
<p>注：</p>
<ol>
<li>收录的还挺杂，点开热点研究，深度学习、知识图谱、情感分析之类的都有</li>
</ol>
<h2>电子与信息学报</h2>
<table>
<thead>
<tr>
<th>属性</th>
<th>值</th>
</tr>
</thead>
<tbody>
<tr>
<td>英文名</td>
<td>Journal of Electronics and Information Technology</td>
</tr>
<tr>
<td>CCF 评级</td>
<td>✈️ T2</td>
</tr>
<tr>
<td>刊期</td>
<td>月刊</td>
</tr>
<tr>
<td>模板</td>
<td>Word ✅ LaTeX ❌</td>
</tr>
<tr>
<td>官网</td>
<td>https://jeit.ac.cn/</td>
</tr>
<tr>
<td>主办方</td>
<td>中国科学院空天信息创新研究院</td>
</tr>
</tbody>
</table>
<p>注：</p>
<ol>
<li>收录范围：图像与智能信息处理、物联网</li>
</ol>
<h2>大学学报汇总</h2>
<p>大学弄的学报太多了，就不一一罗列了，反正自己看吧。</p>
<ul>
<li>中南大学学报（自然科学版）</li>
<li>中国矿业大学学报</li>
<li>中国石油大学学报</li>
<li>哈尔滨工程大学学报</li>
<li>哈尔滨工业大学学报</li>
<li>华中科技大学学报（自然科学版）</li>
<li>湖南大学学报</li>
<li>吉林大学学报（工学班）</li>
<li>国防科技大学学报</li>
<li>东北大学学报</li>
<li>西北工业大学学报</li>
<li>上海交通大学学报</li>
<li>华南理工大学学报（自然科学版）</li>
<li>东南大学学报（自然科学版）</li>
<li>西南交通大学学报</li>
<li>电子科技大学学报</li>
<li>天津大学学报</li>
<li>同济大学学报（自然科学版）</li>
<li>清华大学学报</li>
<li>西安交通大学学报</li>
<li>西安电子科技大学学报</li>
<li>浙江大学学报（工学版）</li>
<li>北京理工大学学报</li>
</ul>
]]></content>
    <author><name>Sun2ot</name></author>
    <category term="教程"/>
  </entry>
  <entry>
    <title>反向ssh隧道搭建</title>
    <link href="https://blog.085404.xyz/posts/%E5%8F%8D%E5%90%91ssh%E9%9A%A7%E9%81%93%E6%90%AD%E5%BB%BA/" rel="alternate" type="text/html"/>
    <id>https://blog.085404.xyz/posts/%E5%8F%8D%E5%90%91ssh%E9%9A%A7%E9%81%93%E6%90%AD%E5%BB%BA/</id>
    <published>2025-01-12T00:00:00.000Z</published>
    <updated>2025-01-12T00:00:00.000Z</updated>
    <summary></summary>
    <content type="html"><![CDATA[<h2>问题背景</h2>
<p>存在公网服务器 A 和内网服务器 B。由于不想仅仅为了连接 B 而开启 VPN，因此采用 A 来中转到 B 的 ssh 请求。</p>
<h2>操作方式</h2>
<p>基础的 ssh 已经足够使用，但如果 A 和 B 的连接不稳定（例如 A 为海外服务器），可能导致 ssh 连接断开。所以这里采用 <code>autossh</code> 来自动维护连接。</p>
<pre><code>sudo apt update
sudo apt install autossh
</code></pre>
<p>在<strong>内网</strong>服务器 B 上执行反向隧道开启命令并使用 <code>autossh</code> 代替 <code>ssh</code>：</p>
<pre><code>autossh -f -M 0 -N -R 4410:localhost:22 -p 2222 sshp@206.237.1.69
</code></pre>
<p>参数说明如下：</p>
<ul>
<li><code>-f</code>：后台运行。</li>
<li><code>-M 0</code>：禁用 <code>autossh</code> 默认的端口监控功能（推荐使用 <code>ServerAliveInterval</code>）。</li>
<li><code>-N</code>：不执行远程命令，仅用于转发。</li>
<li><code>-R</code>：定义反向隧道。</li>
<li><code>-p 2222</code>：指定公网服务器的 SSH 端口。</li>
</ul>
<p>上述指令将公网服务器 A 的 4410 端口，映射到内网服务器的 22 端口，该映射关系通过 ssh 连接建立。其中，<code>sshp</code> 是一个公网服务器上已经存在的用户（<strong>没有的话需创建</strong>）。</p>
<p>因此，对于内网服务器 B，现在可以通过公网服务器 A 来连接：</p>
<pre><code>ssh -p 4410 local_user@A_ip
</code></pre>
<p>这里的 <code>local_user</code> 是内网服务器 B 上的用户。至此，通过公网服务器 A 即可连接到内网服务器 B。</p>
]]></content>
    <author><name>Sun2ot</name></author>
    <category term="教程"/>
  </entry>
  <entry>
    <title>Ubuntu 编译 FFmpeg</title>
    <link href="https://blog.085404.xyz/posts/ubuntu-%E7%BC%96%E8%AF%91-ffmpeg/" rel="alternate" type="text/html"/>
    <id>https://blog.085404.xyz/posts/ubuntu-%E7%BC%96%E8%AF%91-ffmpeg/</id>
    <published>2024-11-19T00:00:00.000Z</published>
    <updated>2024-11-19T00:00:00.000Z</updated>
    <summary></summary>
    <content type="html"><![CDATA[<h2>安装依赖</h2>
<pre><code>sudo apt-get update -qq &amp;&amp; sudo apt-get -y install \
  autoconf \
  automake \
  build-essential \
  cmake \
  git-core \
  libass-dev \
  libfreetype6-dev \
  libgnutls28-dev \
  libmp3lame-dev \
  libsdl2-dev \
  libtool \
  libva-dev \
  libvdpau-dev \
  libvorbis-dev \
  libxcb1-dev \
  libxcb-shm0-dev \
  libxcb-xfixes0-dev \
  meson \
  ninja-build \
  pkg-config \
  texinfo \
  wget \
  yasm \
  zlib1g-dev
</code></pre>
<p>在 Ubuntu 20.04 上，额外安装：</p>
<pre><code>sudo apt install libunistring-dev libaom-dev libdav1d-dev
</code></pre>
<h2>部署</h2>
<p>首先拉取源码：</p>
<pre><code>git clone https://git.ffmpeg.org/ffmpeg.git ffmpeg_src
</code></pre>
<p>配置：</p>
<pre><code>./configure \
--enable-nonfree \
--enable-cuda-nvcc \
--enable-libnpp \
--enable-nvenc \
--bindir="$HOME/app/ffmpeg/bin"
</code></pre>
<p>关于 <code>--extra-cflags</code> 和 <code>--extra-ldflags</code> 参数，适用于你的依赖不在默认路径的情况。所以网上很多教程加了这个参数，但通常其实是不需要的。</p>
<p>编译安装：</p>
<pre><code>make -j 32
sudo make install
</code></pre>
<p>检查是否部署成功：</p>
<blockquote>
<p>记得给生成的 binaries 配置环境变量</p>
</blockquote>
<pre><code>ffmpeg -hwaccels
# 输出
Hardware acceleration methods:                                            
vdpau                                                                     
cuda                                                                      
vaapi

ffmpeg -codecs | grep nvenc
# 输出结果中应包含
av1_nvenc, h264_nvenc, hevc_nvenc
</code></pre>
<h2>排障</h2>
<p>编译过程中可能遇到一些缺失依赖问题。可以通过按照 <a href="https://trac.ffmpeg.org/wiki/CompilationGuide/Ubuntu">官方文档</a> 的步骤提前安装以规避，也可以在中途按需处理，这里我选择后者。</p>
<h3>nasm</h3>
<p>报错：<code>nasm not found or too old. Please install/update nasm or use --disable-x86asm for a build without hand-optimized assembly.</code></p>
<p>参考官方文档 <a href="https://trac.ffmpeg.org/wiki/CompilationGuide/Ubuntu#NASM">NASM</a> 章节。</p>
<h3>nvenc</h3>
<p>报错：<code>ERROR: nvenc requested, but not all dependencies are satisfied: ffnvcodec</code></p>
<p>参考官方文档 <a href="https://trac.ffmpeg.org/wiki/HWAccelIntro">Hardware acceleration introduction with FFmpeg</a> 的 <a href="https://trac.ffmpeg.org/wiki/HWAccelIntro#NVENC">NVENC</a> 章节。</p>
<blockquote>
<p>这个库本质上只是修改一些文件，速度非常快，无需 <code>make -j</code></p>
</blockquote>
<p>请注意，安装时切记查看 <a href="https://github.com/FFmpeg/nv-codec-headers">nv-codec-headers</a> 项目主页的 README。<strong>不同驱动版本需要切换到不同的 git 分支</strong>。</p>
]]></content>
    <author><name>Sun2ot</name></author>
    <category term="教程"/>
  </entry>
  <entry>
    <title>LaTex 语法</title>
    <link href="https://blog.085404.xyz/posts/latex-%E8%AF%AD%E6%B3%95/" rel="alternate" type="text/html"/>
    <id>https://blog.085404.xyz/posts/latex-%E8%AF%AD%E6%B3%95/</id>
    <published>2024-11-06T00:00:00.000Z</published>
    <updated>2024-11-06T00:00:00.000Z</updated>
    <summary></summary>
    <content type="html"><![CDATA[<blockquote>
<p>[!tip]
如果你有过 markdown 的写作经历，上手 LaTeX 会特别快</p>
</blockquote>
<h2>基础说明</h2>
<p>LaTeX 的写作本质上是一种<strong>非所见即所得的标签化的</strong>语言。所有在 Word/WPS 中需要通过鼠标键盘控制样式的部分，只需要通过对应的标签即可控制。</p>
<p>但是，LaTeX 是编辑部用来最终排版的，因此 LaTeX 的 bst 和 cls 文件就控制了整个文档的样式。也就是说，只需要用标签进行样式控制，但不需要对具体的细节进行排版（行高、字体等等）。这些样式也都是编辑部给的模板写死的，<strong>严禁修改</strong>。部分期刊会对修改了样式的稿件<strong>直接退稿</strong>，认定为学术不端。</p>
<p>下面会对各种常见的样式进行说明。其实相应的介绍，期刊/会议提供的模板中通常会有一个 PDF 文件，那里面也会详细介绍各种样式的使用说明。不过大体上是类似的，只是不同期刊的部分样式略有不同。</p>
<h2>标题</h2>
<pre><code>\section{一级标题}
\subsection{二级标题}
\subsubsection{三级标题}
</code></pre>
<h2>换行</h2>
<p>LaTeX 不比 markdown，切记不要随便空行：</p>
<ol>
<li>文字与公式之间。空行会导致默认创建一个新段落</li>
<li>文字与图片之间，空行不影响段落间距</li>
</ol>
<p>如果想在编辑器中换行但又不影响渲染效果，有以下几种方式：</p>
<ol>
<li>借助编辑器插件设置自动换行</li>
<li>在行尾添加 <code>%</code>，这样即使源码中换行也会被忽略</li>
</ol>
<h2>列表</h2>
<p>与 markdown 和 Word 类似，LaTeX 的列表也有无序列表和有序列表之分。例如引言中，总结本文的主要贡献时，会使用无序列表：</p>
<pre><code>The main contributions of this work are summarized as follows:
\begin{itemize}
  \item We propose a ...
  \item We design a .
  \item Extensive experiments ...
\end{itemize}
</code></pre>
<p>无序列表的样式类似于：</p>
<ul>
<li>We propose a ...</li>
<li>We design a .</li>
<li>Extensive experiments ...</li>
</ul>
<p>第二种是有序列表，用的相对较少：</p>
<pre><code>From Tab.1, we can draw some conclusion:
\begin{enumerate}
  \item Text1
  \item Text2
  \item Text3
\end{enumerate}
</code></pre>
<p>显示的效果类似于：</p>
<ol>
<li>Text1</li>
<li>Text2</li>
<li>Text3</li>
</ol>
<p>不同的期刊对于有序列表的样式控制可能有所不同，主要是前面的序号，可能是 <code>1.</code>，也可能是 <code>1)</code>，或者类似的。因此<strong>请参考模板中提供的 PDF 说明文件</strong>！</p>
<h2>图片</h2>
<p>在 LaTeX 中，有几种不同的方式可以插入图像，包括单栏图像、跨两栏图像以及子图等。</p>
<h3>单栏图像</h3>
<p>单栏图像 <code>figure</code> 是最基本的插入图像的方式：</p>
<pre><code>\begin{figure}[ht]
    \centering  % 控制图片居中
    % []表示参数, width定义了图片的宽度. \textwidth 表示使用单栏的宽度, 0.8 就是这个宽度的 80%
    % {}表示要插入的图像文件
    \includegraphics[width=0.8\textwidth]{image-file.pdf}
    % 图像标题, 注意对于图片来说, 标题在下方, 所以相应的 LaTeX 代码也要写在下面
    \caption{Caption for the figure.} % 根据不同的期刊要求, 决定句末是否要加句点
    \label{fig:label}  % 定义标签, 用于交叉引用. 如果不需要也可以不写
\end{figure}
</code></pre>
<h3>双栏图像</h3>
<p>对于双栏文档，可以使用 <code>figure*</code> 环境来让图像跨越两栏：</p>
<pre><code>\begin{figure*}[ht]
    \centering
    % 对于两栏而言, \textwidth 自动会变成两栏的宽度. 所以与单栏同理，可以通过 0.8\textwidth 来自定义宽度
    \includegraphics[width=\textwidth]{image-file}
    \caption{Caption for the wide figure.}
    \label{fig:wide-label}
\end{figure*}
</code></pre>
<h3>子图</h3>
<p>如果你想在一个图中包含多个子图，可以使用 <code>subfigure</code> 环境（需要 <code>subcaption</code> 包或 <code>subfig</code> 包）。</p>
<blockquote>
<p>不同期刊用的包不太一样，注意看一下</p>
</blockquote>
<p>使用 <code>subcaption</code> 包的例子如下：</p>
<pre><code>% 所有的 \usepackage 都在 tex 文档的顶部
% 你也可以自行添加其他的包
% 与软件开发的依赖做区分，Latex 的所有包在你安装的时候就部署好了
% 所以基本都是通用的，不用担心你有但编辑部没有
\usepackage{subcaption}
...
\begin{figure}[ht]
    \centering
    \begin{subfigure}[b]{0.45\textwidth}
        \includegraphics[width=\textwidth]{image-file1}
        \caption{Caption of sub-figure 1.}  % 子图标题
        \label{fig:sub1}
    \end{subfigure}
    \hfill % 填充子图之间的空隙, 可以视情况添加
    \begin{subfigure}[b]{0.45\textwidth}
        \includegraphics[width=\textwidth]{image-file2}
        \caption{Caption of sub-figure 2.}  % 子图标题
        \label{fig:sub2}
    \end{subfigure}
    \caption{Overall caption for both sub-figures.}  % 若干个子图的总标题
    \label{fig:both}
\end{figure}
</code></pre>
<p>这里演示的是一栏两个子图的情况，所以假设栏宽为 1，就控制每个子图的宽度是 0.45，这样加起来是 0.9 &lt; 1。同理，如果一栏有三个子图，就设置为 0.3。</p>
<p>许注意，<strong>不是说加起来小于 1 就一定可以</strong>。因为图片本身存在一些不可见的边距，也是要占据宽度的。所以假如一栏三个图，你设置宽度 0.33，那大概率是会被挤下来的。</p>
<p>如果你一栏需要更多的图，就继续添加 <code>\begin{subfigure} ... \end{subfigure}</code> 就好了。</p>
<h2>表格</h2>
<h3>基本表格（三线表）</h3>
<blockquote>
<p>[!tip]
这里我只对基础表格进行一个比较详细的介绍，后面的都可以参照基础表格进行理解。不然太多了，写的累 😅。</p>
</blockquote>
<pre><code>\begin{table}[ht]
    \centering
    % 与图片类似，表格的标题因为在上方，所以\caption要写在上面
    \caption{基本表格示例}
    \label{tab:basic}
    \begin{tabular}{|c|c|c|}
        \toprule
        列1 &amp; 列2 &amp; 列3 \\
        \midrule
        数据1 &amp; 数据2 &amp; 数据3 \\
        数据4 &amp; 数据5 &amp; 数据6 \\
        \bottomrule
    \end{tabular}
\end{table}
</code></pre>
<p>这个表格渲染的效果类似于：</p>
<table>
<thead>
<tr>
<th>列 1</th>
<th>列 2</th>
<th>列 3</th>
</tr>
</thead>
<tbody>
<tr>
<td>数据 1</td>
<td>数据 2</td>
<td>数据 3</td>
</tr>
<tr>
<td>数据 4</td>
<td>数据 5</td>
<td>数据 6</td>
</tr>
</tbody>
</table>
<p>首先看到 <code>\begin{tabular}</code> 后面的几个 c：</p>
<ol>
<li>这里的 c 表示 center，即居中。类似地，左对齐用 l (left)，右对齐用 r (right)</li>
<li>表格一共有 3 列，所以有 3 个 c</li>
<li>每个 c 的左右都有一个 <code>|</code>，这个表示表格竖向的分割线。换言之，如果写成 <code>c c c</code>，就是<strong>三线表</strong>了</li>
</ol>
<p>其次，三线表是一个统称。有时并不是真的只有三条线。控制这些横向的线，靠的是三个 <code>*rule</code>：</p>
<ol>
<li><code>\toprule</code> 表示最上方的横线</li>
<li><code>\midrule</code> 表示表格中间的横线，视情况可以有多条</li>
<li><code>\bottomrule</code> 表示最下方的线</li>
<li>请不要随意使用 <code>\hrule</code>，这玩意生成的线看起来和三线表一样，但是会缺少三线表上下粗、中间细的特点</li>
</ol>
<p>然后来看分隔符：</p>
<ol>
<li><code>&amp;</code> 用于区分两个列。如果某一列的值为空，保持空值即可。</li>
<li><code>\\</code> 表示表格换行</li>
</ol>
<p>最后，也是最重要的一点。LaTeX 的表格代码编写（阅读）方式是<strong>逐行进行</strong>的。切记是逐行的，理解这一点，多行/多列表格就很简单了。</p>
<h3>浮动表格（跨栏）</h3>
<p>与图片类似，单栏的基本表格是 <code>table</code>，跨栏的就是 <code>table*</code>。其余的跟单栏表格一致，不做赘述。</p>
<pre><code>\begin{table*}[ht]
    \centering
    \caption{跨两栏表格示例}
    \label{tab:twocolumn}
    \begin{tabular}{|c|c|c|}
        \hline
        列1 &amp; 列2 &amp; 列3 \\
        \hline
        数据1 &amp; 数据2 &amp; 数据3 \\
        数据4 &amp; 数据5 &amp; 数据6 \\
        \hline
    \end{tabular}
\end{table*}
</code></pre>
<h3>跨行/跨列表格</h3>
<p>跨行/跨列指的是类似于 Excel 中的合并单元格操作。LaTeX 中也有对应的控制方法。这里直接上一个复杂表格，一次性讲清这个问题。</p>
<pre><code>\begin{table}[htb]
\caption{Statistics of benchmark datasets}
\label{tab:dataset}
  \begin{tabular}{cccccccc}
    \toprule
    Dateset &amp; \multicolumn{2}{c}{Yelp} &amp; \multicolumn{2}{c}{Sports} &amp; \multicolumn{3}{c}{Tiktok} \\
    \midrule
    Modality &amp; V &amp; T &amp; V &amp; T &amp; V &amp; T &amp; A \\
    Dim &amp; 512 &amp; 1024 &amp; 4096 &amp; 1024 &amp; 128 &amp; 768 &amp; 128 \\
    \cmidrule(lr){2-3} \cmidrule(lr){4-5} \cmidrule(lr){6-8}
    Users &amp; \multicolumn{2}{c}{37,397} &amp; \multicolumn{2}{c}{35,598} &amp; \multicolumn{3}{c}{9,308} \\
    Items &amp; \multicolumn{2}{c}{32,491} &amp; \multicolumn{2}{c}{18,357} &amp; \multicolumn{3}{c}{6,710} \\
    Interactions &amp; \multicolumn{2}{c}{235,735} &amp; \multicolumn{2}{c}{296,337} &amp; \multicolumn{3}{c}{68,722} \\
    \midrule
    Sparsity &amp; \multicolumn{2}{c}{99.98\%} &amp; \multicolumn{2}{c}{99.95\%} &amp; \multicolumn{3}{c}{99.89\%} \\
    \bottomrule
  \end{tabular}
\end{table}
</code></pre>
<p>表格渲染效果如下：
<img src="https://img.085404.xyz/images/528378dbbca9bec72bfa93e9dca9b859.webp" alt="image.png" /></p>
<p>首先注意，表格第一行乍一看只有 4 列，但是每个数据集本质上占据了 2-3 列，所以 <code>\begin{tabular}</code> 后面要列出最多的列数，即 8 个 c。</p>
<p>然后，一个数据集占据了 2-3 列，这个就叫跨列，通过 <code>\multicolumn{列数}{c/l/r =&gt; 居中/左/右}{列名}</code>。之前说的逐行阅读就是这个原因，跨列的元素是在一行的编辑中完成的。</p>
<p>类似地，如果是跨行的表格，使用 <code>\multirow{行数}{*}{列名}</code>：</p>
<blockquote>
<p>[!warning]
跨行需要添加包 <code>\usepackage{multirow}</code></p>
</blockquote>
<pre><code>\begin{table*}[t]
\centering
\caption{Overall performance comparison}
\label{tab:performance-comparison}
% \resizebox{\textwidth}{!}{
  \begin{tabular}{lcccccccccc}
  \toprule
  \multirow{2}{*}{Method} &amp; \multirow{2}{*}{Venue} &amp; \multicolumn{3}{c}{Yelp} &amp; \multicolumn{3}{c}{Sports} &amp; \multicolumn{3}{c}{Tiktok} \\
  \cmidrule(lr){3-5} \cmidrule(lr){6-8} \cmidrule(lr){9-11}
   &amp; &amp; R@20 &amp; N@20 &amp; P@20 &amp; R@20 &amp; N@20 &amp; P@20 &amp; R@20 &amp; N@20 &amp; P@20 \\
  \midrule
  LightGCN &amp; SIGIR'20 &amp; 0.0599 &amp; 0.0255 &amp; 0.0040
                      &amp; 0.0754 &amp; 0.0335 &amp; 0.0040
                      &amp; 0.0821 &amp; 0.0369 &amp; 0.0041 \\
</code></pre>
<p><img src="https://img.085404.xyz/images/6799875a7cc6974f926433bd13df6c3e.webp" alt="image.png" /></p>
<p>参考上图，<code>Method</code> 占据了 1-2 行，所以是 <code>\multirow{2}{*}{Method}</code>。需要注意，中间的 <code>*</code> 最好不要改，它可以自动调整。</p>
<p>在编写时可以看到，Method 和 Venue 虽然占据了两行，但是我们在第一行就写了，因此第二行的对应位置需要空出来 <code>空白 &amp; 空白 &amp; R@20 &amp; ...</code></p>
<h3>缩放表格</h3>
<p>这是一个很有用的包。有时表格过大会导致即使跨栏分布，表格也摆不下。这个使用可以使用 <code>\resizebox</code> 根据页面大小自动缩放表格。使用方式如下：</p>
<blockquote>
<p>[!warning]
使用 <code>\resizebox</code> 需要添加包 <code>\usepackage{graphicx}</code>。</p>
</blockquote>
<pre><code>\usepackage{graphicx}

\begin{table}[ht]
    \centering
    \resizebox{\textwidth}{!}{
        % 在原本的 tabular 外面包一层 resizebox 即可
        \begin{tabular}{|c|c|c|c|c|}
            \hline
            列1 &amp; 列2 &amp; 列3 &amp; 列4 &amp; 列5 \\
            \hline
            数据1 &amp; 数据2 &amp; 数据3 &amp; 数据4 &amp; 数据5 \\
            数据6 &amp; 数据7 &amp; 数据8 &amp; 数据9 &amp; 数据10 \\
            % 更多行...
            \hline
        \end{tabular}
    }
    \caption{使用 resizebox 缩小的表格示例}
    \label{tab:resized_table}
\end{table}
</code></pre>
<p>resizebox 接受的参数是 <code>\resizebox{宽度}{高度}{内容}</code>，其中内容指的是表格。而宽度和高度的设置通常如上表所示，我们只需要设置宽度为页面宽度 <code>\textwidth</code>，高度设置为 <code>!</code>，LaTeX 就能帮我们自动调整表格大小。</p>
<h2>浮动参数</h2>
<p>在上文中，可以经常看到在图表后面有 <code>[ht]</code> 这样的标记。这在 LaTeX 中被称为“浮动参数”，它们用于指定浮动体（如图像、表格等）在文档中的<strong>理想放置位置</strong>。这些参数通常紧跟在浮动环境的开始标签（<code>\begin{figure}</code> 和 <code>\begin{table}</code> 等）之后。</p>
<blockquote>
<p>[!warning]
浮动参数只能指定<strong>理想位置</strong>，这就是本文开头我所说的，LaTeX 是一种非所见即所得的排版方式。也许你想让某个表格在当前页的顶部，但由于位置不够，它就可能会被挤到下一页的顶部。</p>
</blockquote>
<p>下面对每一种浮动参数进行解释：</p>
<ul>
<li>h：代表 "here"，即尽量将浮动体放置在其出现的文本位置附近。</li>
<li>t：代表 "top"，表示可以放置在页面的顶部。</li>
<li>b：代表 "bottom"，意味着可以放在页面的底部。</li>
<li>p：表示可以单独放在浮动体页上，即该页只包含浮动体。</li>
<li>!：这个符号意味着 LaTeX 应尽可能地遵循上述位置指示，而不是优先考虑其默认的排版规则。</li>
</ul>
<p>你可以<strong>组合使用这些字母来提供多个位置选项</strong>给 LaTeX。例如，<code>[ht]</code> 表示首先尝试在当前位置 (h) 插入浮动体，如果不行，则尝试在页面顶部 (t)。</p>
<blockquote>
<p>[!tip]
在 LaTeX 中，图表的位置需要结合浮动参数进行<strong>反复尝试、反复调整</strong></p>
</blockquote>
<h2>交叉引用（非参考文献）</h2>
<p>这里的交叉引用指的是图、表、章节标题之类的交叉引用。之所以单独写一节，是因为所有的引用方式都是一样的。</p>
<p>在上面的 <a href="#%E5%9B%BE%E7%89%87">图片</a>、<a href="#%E8%A1%A8%E6%A0%BC">表格</a> 章节中，可以看到在 <code>\caption{}</code> 的旁边有一个 <code>\label{}</code>，它能够生成一个标记。</p>
<p>你可以在 tex 文档的任意地方对这个标记进行引用 <code>\ref{}</code>，就可以生成一个可跳转的标记。引用方式如下：</p>
<pre><code>% 假设你插入了一张图
\begin{figure}[ht]
    \centering
    \includegraphics[width=0.8\textwidth]{image-file.pdf}
    \caption{Caption for the figure.}
    \label{fig:framework}
\end{figure}
% 中间可能有一堆内容
Fig.\ref{fig:framework} shows that the modal can effectively learn node features.
</code></pre>
<p>LaTeX 会自动对所有的图表编号，所以上面的内容会直接编译为 <code>Fig.1</code> 类似的。</p>
<p>表格的交叉引用和图片是一模一样的。而对于章节，只需要在 <code>\section{}</code> 的旁边也通过 <code>\label{}</code> 创建一个标签即可引用，方法和图表也是完全一样的。</p>
<blockquote>
<p>[!tip]</p>
<ol>
<li>label 的名称没有统一格式，你可以任意设置，但最好具有一定的规律。</li>
<li>不要把 label 设置为 fig1, fig2 这种。你能确保 fig1 就永远是第一张图吗？直接以图表的含义作为名称即可。</li>
</ol>
</blockquote>
<h2>公式</h2>
<p>首先说明，LaTeX 的公式说简单也简单，说复杂也复杂（但其实真的很简单）。这里我只写怎么进行排版，具体的公式应该怎么写，建议自学一下。</p>
<p>关于有一些符号如何输出，给你们提供一个很方便的工具文档 <a href="https://katex.org/docs/supported.html">KaTex</a>。基本上所有的符号都可以从上面找到。</p>
<p>此外，如果你此前是用的 mathtype 写的公式，是可以无缝衔接到 LaTeX 的。只需要改一下设置，让 mathtype 复制的内容变为 LaTeX 即可。自行百度一下。</p>
<h3>单行公式</h3>
<p>先来说单行公式 <code>equation</code>，这个是最简单的：</p>
<pre><code>\begin{equation}
    \mathbf{W}_\text{g} &amp; = \sigma(\mathbf{W}_1\mathbf{P}_m + \mathbf{b}_1)
\end{equation}
</code></pre>
<p>$$ \mathbf{W}_\text{g} = \sigma(\mathbf{W}_1\mathbf{P}_m + \mathbf{b}_1) \tag{1}$$</p>
<p>和图表一致，LaTeX 会自动对公式进行编号。</p>
<h3>多行公式</h3>
<p>如果需要多行公式，通常建议使用 <code>align</code>。使用两个 <code>equation</code> 会让行间距变的很大。</p>
<pre><code>\begin{align}
  \omega_m &amp;= \text{Softmax}(\mathbf{W}_m) \\
  \bar{\mathbf{E}} &amp;= \sum_{m \in \mathcal{M}} \omega_m \cdot \hat{\mathbf{E}}^m
\end{align}
</code></pre>
<p>$$
\begin{align}
\omega_m &amp;= \text{Softmax}(\mathbf{W}<em>m) \tag{1} \
\bar{\mathbf{E}} &amp;= \sum</em>{m \in \mathcal{M}} \omega_m \cdot \hat{\mathbf{E}}^m \tag{2}
\end{align}
$$</p>
<p>多行公式也会自动编号。若干个公式之间通过 <code>\\</code> 进行换行。可以在公式中添加符号 <code>&amp;</code>，这可以指定每一行的公式应该在什么地方对齐。例如上面的例子是在等号 <code>=</code> 处对齐，你也可以放在开头进行左对齐：</p>
<pre><code>\begin{align}
  &amp; \omega_m = \text{Softmax}(\mathbf{W}_m) \\
  &amp; \bar{\mathbf{E}} = \sum_{m \in \mathcal{M}} \omega_m \cdot \hat{\mathbf{E}}^m
\end{align}
</code></pre>
<p>$$
\begin{align}
&amp;\omega_m = \text{Softmax}(\mathbf{W}<em>m) \tag{1} \
&amp; \bar{\mathbf{E}} = \sum</em>{m \in \mathcal{M}} \omega_m \cdot \hat{\mathbf{E}}^m \tag{2}
\end{align}
$$</p>
<h3>多行公式特例</h3>
<p>有时你可能希望多个公式在一起，但是又不要对它们进行多个编号（例如三个公式共用一个编号），那么可以使用 <code>split</code> 环境：</p>
<pre><code>\begin{equation}
  \begin{split}
    \mathbf{H} &amp;= (1+\delta) \cdot (\mathbf{A}_m + \mathbf{I}) \bar{\mathbf{E}} \\
    &amp;= (1+\delta) \cdot \mathbf{G}_m \bar{\mathbf{E}}
  \end{split}
\end{equation}
</code></pre>
<p>$$
\begin{equation}
\begin{split}
\mathbf{H} &amp;= (1+\delta) \cdot (\mathbf{A}_m + \mathbf{I}) \bar{\mathbf{E}} \
&amp;= (1+\delta) \cdot \mathbf{G}_m \bar{\mathbf{E}}
\end{split}
\tag{1}
\end{equation}
$$</p>
<p>这种多行公式排版方式<strong>和普通的多行公式完全一致</strong>，例如 <code>\\</code> 和 <code>&amp;</code> 的使用，只不过是包裹了一个 <code>split</code> 环境而已。同样支持自动编号</p>
<h3>公式引用</h3>
<p>公式的引用和上一节的 <a href="#%E4%BA%A4%E5%8F%89%E5%BC%95%E7%94%A8-%E9%9D%9E%E5%8F%82%E8%80%83%E6%96%87%E7%8C%AE">交叉引用</a> 完全一致。只需要添加一个 <code>\label{}</code> 在 <code>\ref{}</code> 即可：</p>
<pre><code>\begin{equation}
    \label{eq:a_eq}
    \mathbf{W}_\text{g} &amp; = \sigma(\mathbf{W}_1\mathbf{P}_m + \mathbf{b}_1)
\end{equation}
% 正文
Eq.\ref{eq:a_eq} is the formulation that ...
</code></pre>
]]></content>
    <author><name>Sun2ot</name></author>
    <category term="教程"/>
  </entry>
  <entry>
    <title>NVIDIA Container Toolkit</title>
    <link href="https://blog.085404.xyz/posts/nvidia-container-toolkit/" rel="alternate" type="text/html"/>
    <id>https://blog.085404.xyz/posts/nvidia-container-toolkit/</id>
    <published>2024-09-11T00:00:00.000Z</published>
    <updated>2024-09-11T00:00:00.000Z</updated>
    <summary></summary>
    <content type="html"><![CDATA[<h2>问题描述</h2>
<p>在早期深度学习尚未普及时，Docker 作为一种容器技术是不支持调用宿主机 GPU 的，后面在 Docker 官方以及 NVIDIA 的推动下才得以支持，这其中的关键就在于 NVIDIA Container Toolkit。</p>
<p>通常有两种情况可能会需要 NVIDIA Container Toolkit：</p>
<ol>
<li>主动：需要 Docker 容器调用 GPU</li>
<li>被动：遇到报错 <code># docker: Error response from daemon: could not select device driver ““ with capabilities: [[gpu]].</code></li>
</ol>
<h2>安装</h2>
<p><a href="https://docs.nvidia.com/datacenter/cloud-native/container-toolkit/latest/install-guide.html">官方文档</a></p>
<p>首先，需要装好 <a href="https://www.nvidia.com/en-us/drivers/"> NVIDIA 显卡驱动</a>。然后配置仓库：</p>
<pre><code>curl -fsSL https://nvidia.github.io/libnvidia-container/gpgkey | sudo gpg --dearmor -o /usr/share/keyrings/nvidia-container-toolkit-keyring.gpg

curl -s -L https://nvidia.github.io/libnvidia-container/stable/deb/nvidia-container-toolkit.list | sed 's#deb https://#deb [signed-by=/usr/share/keyrings/nvidia-container-toolkit-keyring.gpg] https://#g' | sudo tee /etc/apt/sources.list.d/nvidia-container-toolkit.list
</code></pre>
<blockquote>
<p>上述指令切记分两条分别执行，否则会出现看似添加了源但其实没有添加上的尴尬情况。</p>
</blockquote>
<p>完成后，更新并安装 NVIDIA Container Toolkit 软件包：</p>
<pre><code>sudo apt update
sudp install -y nvidia-container-toolkit
</code></pre>
<p>安装完成后，记得重启 Docker：</p>
<pre><code>sudo systemctl restart docker
</code></pre>
]]></content>
    <author><name>Sun2ot</name></author>
    <category term="教程"/>
  </entry>
  <entry>
    <title>Ollama 玩转本地大模型</title>
    <link href="https://blog.085404.xyz/posts/ollama-%E7%8E%A9%E8%BD%AC%E6%9C%AC%E5%9C%B0%E5%A4%A7%E6%A8%A1%E5%9E%8B/" rel="alternate" type="text/html"/>
    <id>https://blog.085404.xyz/posts/ollama-%E7%8E%A9%E8%BD%AC%E6%9C%AC%E5%9C%B0%E5%A4%A7%E6%A8%A1%E5%9E%8B/</id>
    <published>2024-09-11T00:00:00.000Z</published>
    <updated>2024-09-11T00:00:00.000Z</updated>
    <summary></summary>
    <content type="html"><![CDATA[<h2>Ollama</h2>
<p><a href="https://ollama.com/">Ollama</a> 是一款 <a href="https://github.com/ollama/ollama">开源</a> 框架，专为本地部署和运行大语言模型（LLM）而设计。其特性与功能有很多，这里只列举几个核心点：</p>
<ol>
<li>简化部署：Ollama 可以让你用 Docker 的方式部署 LLM，这是什么概念不用我多说了吧</li>
<li>API 支持：无需额外的 coding 操作，Ollama 本身就支持 API 调用 LLM</li>
<li>跨平台：主要针对 macOS 和 Linux，Windows 目前属于预览性支持</li>
<li>Python SDK 支持</li>
</ol>
<p>Ollama 支持的模型可以在 <a href="https://ollama.com/library">Ollama Library</a> 中检索。</p>
<h3>GPU 支持</h3>
<p>我没玩过 A 卡，所以这里只记录 N 卡的情况。更多内容可参考 <a href="https://github.com/ollama/ollama/blob/main/docs/gpu.md">Ollama GPU</a>。</p>
<blockquote>
<p>大模型不坑穷人，所以请注意：运行 7B 模型需要至少 8GB 的显存，13B 模型需要至少 16GB，33B 模型需要至少 32GB。</p>
</blockquote>
<p>Ollama 支持 compute capability (计算能力) 5.0+ 的 Nvidia GPU，如下表所示。如果不清楚你的 GPU 计算能力是多少，可在 <a href="https://developer.nvidia.com/cuda-gpus">cuda-gpus</a> 查询。</p>
<table>
<thead>
<tr>
<th>Compute Capability</th>
<th>Family</th>
<th>Cards</th>
</tr>
</thead>
<tbody>
<tr>
<td>9.0</td>
<td>NVIDIA</td>
<td><code>H100</code></td>
</tr>
<tr>
<td>8.9</td>
<td>GeForce RTX 40xx</td>
<td><code>RTX 4090</code> <code>RTX 4080 SUPER</code> <code>RTX 4080</code> <code>RTX 4070 Ti SUPER</code> <code>RTX 4070 Ti</code> <code>RTX 4070 SUPER</code> <code>RTX 4070</code> <code>RTX 4060 Ti</code> <code>RTX 4060</code></td>
</tr>
<tr>
<td></td>
<td>NVIDIA Professional</td>
<td><code>L4</code> <code>L40</code> <code>RTX 6000</code></td>
</tr>
<tr>
<td>8.6</td>
<td>GeForce RTX 30xx</td>
<td><code>RTX 3090 Ti</code> <code>RTX 3090</code> <code>RTX 3080 Ti</code> <code>RTX 3080</code> <code>RTX 3070 Ti</code> <code>RTX 3070</code> <code>RTX 3060 Ti</code> <code>RTX 3060</code> <code>RTX 3050 Ti</code> <code>RTX 3050</code></td>
</tr>
<tr>
<td></td>
<td>NVIDIA Professional</td>
<td><code>A40</code> <code>RTX A6000</code> <code>RTX A5000</code> <code>RTX A4000</code> <code>RTX A3000</code> <code>RTX A2000</code> <code>A10</code> <code>A16</code> <code>A2</code></td>
</tr>
<tr>
<td>8.0</td>
<td>NVIDIA</td>
<td><code>A100</code> <code>A30</code></td>
</tr>
<tr>
<td>7.5</td>
<td>GeForce GTX/RTX</td>
<td><code>GTX 1650 Ti</code> <code>TITAN RTX</code> <code>RTX 2080 Ti</code> <code>RTX 2080</code> <code>RTX 2070</code> <code>RTX 2060</code></td>
</tr>
<tr>
<td></td>
<td>NVIDIA Professional</td>
<td><code>T4</code> <code>RTX 5000</code> <code>RTX 4000</code> <code>RTX 3000</code> <code>T2000</code> <code>T1200</code> <code>T1000</code> <code>T600</code> <code>T500</code></td>
</tr>
<tr>
<td></td>
<td>Quadro</td>
<td><code>RTX 8000</code> <code>RTX 6000</code> <code>RTX 5000</code> <code>RTX 4000</code></td>
</tr>
<tr>
<td>7.0</td>
<td>NVIDIA</td>
<td><code>TITAN V</code> <code>V100</code> <code>Quadro GV100</code></td>
</tr>
<tr>
<td>6.1</td>
<td>NVIDIA TITAN</td>
<td><code>TITAN Xp</code> <code>TITAN X</code></td>
</tr>
<tr>
<td></td>
<td>GeForce GTX</td>
<td><code>GTX 1080 Ti</code> <code>GTX 1080</code> <code>GTX 1070 Ti</code> <code>GTX 1070</code> <code>GTX 1060</code> <code>GTX 1050 Ti</code> <code>GTX 1050</code></td>
</tr>
<tr>
<td></td>
<td>Quadro</td>
<td><code>P6000</code> <code>P5200</code> <code>P4200</code> <code>P3200</code> <code>P5000</code> <code>P4000</code> <code>P3000</code> <code>P2200</code> <code>P2000</code> <code>P1000</code> <code>P620</code> <code>P600</code> <code>P500</code> <code>P520</code></td>
</tr>
<tr>
<td></td>
<td>Tesla</td>
<td><code>P40</code> <code>P4</code></td>
</tr>
<tr>
<td>6.0</td>
<td>NVIDIA</td>
<td><code>Tesla P100</code> <code>Quadro GP100</code></td>
</tr>
<tr>
<td>5.2</td>
<td>GeForce GTX</td>
<td><code>GTX TITAN X</code> <code>GTX 980 Ti</code> <code>GTX 980</code> <code>GTX 970</code> <code>GTX 960</code> <code>GTX 950</code></td>
</tr>
<tr>
<td></td>
<td>Quadro</td>
<td><code>M6000 24GB</code> <code>M6000</code> <code>M5000</code> <code>M5500M</code> <code>M4000</code> <code>M2200</code> <code>M2000</code> <code>M620</code></td>
</tr>
<tr>
<td></td>
<td>Tesla</td>
<td><code>M60</code> <code>M40</code></td>
</tr>
<tr>
<td>5.0</td>
<td>GeForce GTX</td>
<td><code>GTX 750 Ti</code> <code>GTX 750</code> <code>NVS 810</code></td>
</tr>
<tr>
<td></td>
<td>Quadro</td>
<td><code>K2200</code> <code>K1200</code> <code>K620</code> <code>M1200</code> <code>M520</code> <code>M5000M</code> <code>M4000M</code> <code>M3000M</code> <code>M2000M</code> <code>M1000M</code> <code>K620M</code> <code>M600M</code> <code>M500M</code></td>
</tr>
</tbody>
</table>
<h3>部署方式</h3>
<blockquote>
<p>下文以 Linux 为例</p>
</blockquote>
<p>推荐使用脚本部署：</p>
<pre><code>curl -fsSL https://ollama.com/install.sh | sh
</code></pre>
<p><a href="https://github.com/ollama/ollama/blob/main/docs/linux.md#manual-install">手动部署</a> 也不麻烦，如果有需要参考官方文档即可。</p>
<h3>最佳实践</h3>
<p>以最新的 Llama3.1 模型为例，部署该模型只需要：</p>
<pre><code>ollama run llama3.1
</code></pre>
<p>搞定。</p>
<h3>关于 API 的使用</h3>
<p>这里纠正一个误区：执行 <code>ollama run model</code> 命令会进入 CLI 交互模式，此时可以与大模型进行对话。</p>
<p>但如果只是需要调用模型 API，执行 <code>ollama serve</code> 即可。此时 ollama 会监听本地的 11434 端口，并提供 RESTful API，此时 ollama 对系统占用可忽略不计。</p>
<p>可参考 <a href="https://github.com/ollama/ollama/blob/main/docs/api.md">API 文档</a> 直接发起请求，ollama 会自动加载模型到 GPU 进行计算。如果一段时间内没有继续请求，<strong>模型会自动卸载</strong>。</p>
<h3>环境变量</h3>
<p>这里主要说明两个，一个是多 GPU 环境，另一个是局域网访问，这应该也是最常见的问题。</p>
<p>如果具有多 GPU 环境，ollama 会自动调用多 GPU 加速计算（前提是模型足够大）。但我相信富哥还是少数😭，更多的需求是指定 ollama 使用一个或几个特定的 GPU。</p>
<p>此外，ollama 默认工作在 <code>127.0.0.1</code> 上，可以通过 nginx 做反代访问，也可以通过环境变量 <code>OLLAMA_HOST</code> 设置。</p>
<p>常规情况下，安装 Ollama 后，可通过 systemd 管理其服务。因此可在 <code>/etc/systemd/system/ollama.service</code> 中添加上述环境变量，如下：</p>
<pre><code>[Unit]
Description=Ollama Service
After=network-online.target

[Service]
ExecStart=/usr/local/bin/ollama serve
User=ollama
Group=ollama
Restart=always
RestartSec=3
Environment="CUDA_VISIBLE_DEVICES=1"
Environment="OLLAMA_HOST=0.0.0.0"

[Install]
WantedBy=default.target
</code></pre>
<p>但如果处于容器环境，尤其是没有使用 systemd 的容器环境，默认情况下，ollama 完成后是不会自动启动的，如：</p>
<pre><code>ollama list

Error: could not connect to ollama app, is it running?
</code></pre>
<p>此时可以在 shell 配置文件中添加环境变量，实现上述效果：</p>
<pre><code>vim ~/.bashrc # ~/.zshrc

# 添加如下内容
export OLLAMA_HOST=0.0.0.0:11434
export OLLAMA_MODELS=/home/user/ollama
</code></pre>
<p>然后手动启动 ollama：</p>
<pre><code>nohup ollama serve &gt; ollama.log 2&gt;&amp;1 &amp;
# 如有需要，可执行
disown
</code></pre>
<h2>Open WebUI</h2>
<p>如果只使用 Ollama，那么跟模型的交互仅限于 CLI 和 API 两种方式。如果想实现 chatgpt 类型的页面交互模式，需要借助额外的前端框架，可参考官方提供的 <a href="https://github.com/ollama/ollama?tab=readme-ov-file#web--desktop">Web&amp;Desktop</a> 目录。</p>
<p>下面以 Open WebUI 为例，推荐使用 Docker 进行部署：</p>
<pre><code>docker run -d -p 3000:8080 --gpus all --add-host=host.docker.internal:host-gateway -v open-webui:/app/backend/data --name open-webui --restart always ghcr.io/open-webui/open-webui:cuda
</code></pre>
<blockquote>
<p>注意：启动容器之前，需要先部署 <a href="https://blog.085404.xyz/method/nvidia-container-toolkit.html">NVIDIA Container Toolkit</a></p>
</blockquote>
<p>这里不推荐本地的映射卷路径，实测会出现一些问题，例如：</p>
<pre><code>huggingface_hub.utils._errors.LocalEntryNotFoundError: Cannot find an appropriate cached snapshot folder for the specified revision on the local disk and outgoing traffic has been disabled. To enable repo look-ups and downloads online, pass 'local_files_only=False' as input.
</code></pre>
<p>大概率是因为修改了映射卷位置所导致的，见 <a href="https://github.com/open-webui/open-webui/issues/2758">open-webui issue: 2758</a></p>
<h2>Ollama + LangChain</h2>
<p>先画个饼，后面再写。</p>
]]></content>
    <author><name>Sun2ot</name></author>
    <category term="教程"/>
  </entry>
  <entry>
    <title>generate_204 服务</title>
    <link href="https://blog.085404.xyz/posts/generate_204-%E6%9C%8D%E5%8A%A1/" rel="alternate" type="text/html"/>
    <id>https://blog.085404.xyz/posts/generate_204-%E6%9C%8D%E5%8A%A1/</id>
    <published>2024-08-26T00:00:00.000Z</published>
    <updated>2024-08-26T00:00:00.000Z</updated>
    <summary></summary>
    <content type="html"><![CDATA[<p>关于这个东西，我不止一次在 Clash 上看到过，但并没有去深究这是什么，只当做是一个类似于测试延迟的 API。无意中了解到关于它的具体作用，这里记录一下。</p>
<p>"Generate_204" 是一种网络连通性测试服务，广泛应用于检查网络连接状态。它通过访问一个返回 HTTP 204 状态码的 URL 来判断当前网络是否畅通，所以叫做 <code>generage_204</code>。</p>
<p>由于 HTTP 204 表示请求已成功处理且没有返回内容，访问这些 URL 可以有效减少网络开销，并且迅速判断网络连接情况。</p>
<p>许多知名的互联网公司提供这些服务，包括 Google、Apple、Cloudflare 等。根据互联网的检索信息，国内的 <code>华米OV</code> 中前三家似乎也提供了这种服务。这里统计一下常见的 <code>generate_204</code> URL：</p>
<table>
<thead>
<tr>
<th>服务提供者</th>
<th>链接</th>
<th>http/https</th>
<th>IP Version</th>
</tr>
</thead>
<tbody>
<tr>
<td>Google</td>
<td><a href="http://www.gstatic.com/generate_204">http://www.gstatic.com/generate_204</a></td>
<td>204/204</td>
<td>4+6</td>
</tr>
<tr>
<td>Google</td>
<td><a href="http://www.google-analytics.com/generate_204">http://www.google-analytics.com/generate_204</a></td>
<td>204/204</td>
<td>4+6</td>
</tr>
<tr>
<td>Google</td>
<td><a href="http://www.google.com/generate_204">http://www.google.com/generate_204</a></td>
<td>204/204</td>
<td>4+6</td>
</tr>
<tr>
<td>Google</td>
<td><a href="http://connectivitycheck.gstatic.com/generate_204">http://connectivitycheck.gstatic.com/generate_204</a></td>
<td>204/204</td>
<td>4+6</td>
</tr>
<tr>
<td>Apple</td>
<td><a href="http://captive.apple.com/">http://captive.apple.com</a></td>
<td>200/200</td>
<td>4+6</td>
</tr>
<tr>
<td>Apple</td>
<td><a href="http://www.apple.com/library/test/success.html">http://www.apple.com/library/test/success.html</a></td>
<td>200/200</td>
<td>4+6</td>
</tr>
<tr>
<td>Microsoft👍</td>
<td><a href="http://www.msftconnecttest.com/connecttest.txt">http://www.msftconnecttest.com/connecttest.txt</a></td>
<td>200/error</td>
<td>4</td>
</tr>
<tr>
<td>Cloudflare</td>
<td><a href="http://cp.cloudflare.com/">http://cp.cloudflare.com/</a></td>
<td>204/204</td>
<td>4+6</td>
</tr>
<tr>
<td>Firefox</td>
<td><a href="http://detectportal.firefox.com/success.txt">http://detectportal.firefox.com/success.txt</a></td>
<td>200/200</td>
<td>4+6</td>
</tr>
<tr>
<td>V2ex</td>
<td><a href="http://www.v2ex.com/generate_204">http://www.v2ex.com/generate_204</a></td>
<td>204/301</td>
<td>4+6</td>
</tr>
<tr>
<td>小米</td>
<td><a href="http://connect.rom.miui.com/generate_204">http://connect.rom.miui.com/generate_204</a></td>
<td>204/204</td>
<td>4</td>
</tr>
<tr>
<td>华为</td>
<td><a href="http://connectivitycheck.platform.hicloud.com/generate_204">http://connectivitycheck.platform.hicloud.com/generate_204</a></td>
<td>204/204</td>
<td>4</td>
</tr>
<tr>
<td>Vivo</td>
<td><a href="http://wifi.vivo.com.cn/generate_204">http://wifi.vivo.com.cn/generate_204</a></td>
<td>204/204</td>
<td>4</td>
</tr>
</tbody>
</table>
<p>一般情况下，使用微软的就不错，因为 Windows 的联网判断方法用的就是这个。</p>
]]></content>
    <author><name>Sun2ot</name></author>
    <category term="教程"/>
  </entry>
  <entry>
    <title>sd 人像转二次元</title>
    <link href="https://blog.085404.xyz/posts/sd-%E4%BA%BA%E5%83%8F%E8%BD%AC%E4%BA%8C%E6%AC%A1%E5%85%83/" rel="alternate" type="text/html"/>
    <id>https://blog.085404.xyz/posts/sd-%E4%BA%BA%E5%83%8F%E8%BD%AC%E4%BA%8C%E6%AC%A1%E5%85%83/</id>
    <published>2024-08-06T00:00:00.000Z</published>
    <updated>2024-08-06T00:00:00.000Z</updated>
    <summary></summary>
    <content type="html"><![CDATA[<h1>sd 人像转二次元</h1>
<h2>提示词反推</h2>
<p>文生图的流程是给提示词然后生成对应图片，而如果要将一个人像转为二次元，一个靠谱的方案是先获得这个人像对应的提示词，这能帮助 sd 模型生成的图像尽可能贴近原图像。</p>
<p>对于 sd webui，这需要提示词反推插件来实现：</p>
<pre><code>https://github.com/67372a/stable-diffusion-webui-wd14-tagger.git
</code></pre>
<p>原本的 <a href="https://github.com/toriato/stable-diffusion-webui-wd14-tagger">wd14 tagger</a>（也就是你在网上各种教程、视频里看到的那个插件）已经 public archive 了，所以上面提供的是一个近期更新过的可用版本。</p>
<p><img src="http://img.085404.xyz/images/8eac1b85e78226f28cb91d6e866ede1e.webp" alt="image.png" /></p>
<p>安装完成后进入 Tagger 选项卡，按照上图所示操作，会自动下载反推所需模型。</p>
<blockquote>
<p>自动从 hugging face 下载，需要代理环境。</p>
</blockquote>
<p>将生成的正向提示词 Send to txt2img 后，点击 Unload all interrogate models 卸载反推模型以节省显存占用。</p>
<h2>controlnet 模型</h2>
<p>具体的操作流程参考教程： https://www.uisdc.com/stable-diffusion-27</p>
<p>下面主要给出这个教程涉及到的模型资源（涉及下载部分需代理环境）：</p>
<ol>
<li>Preprocessor 1：
<ul>
<li>openpose_full：下拉菜单选中预处理器后，点击右侧按钮自动下载
<img src="http://img.085404.xyz/images/cb23d40373b3e284adeb1f7a38dd3eb1.webp" alt="image.png" /></li>
<li>对应的 Model：<a href="https://huggingface.co/lllyasviel/ControlNet/blob/main/models/control_sd15_openpose.pth">control_sd15_openpose.pth</a></li>
</ul>
</li>
<li>Preprocessor 2：
<ul>
<li>softedge_pidinet：与 openpose_full 相同，选中后点击自动下载</li>
<li>对应的 Model：<a href="https://huggingface.co/lllyasviel/ControlNet-v1-1/blob/main/control_v11p_sd15_softedge.pth">control_v11p_sd15_softedge.pth</a> 和 <a href="https://huggingface.co/lllyasviel/ControlNet-v1-1/blob/main/control_v11p_sd15_softedge.yaml">control_v11p_sd15_softedge.yaml</a></li>
</ul>
</li>
</ol>
<blockquote>
<p>基底大模型用的是<a href="https://civitai.com/models/7371/rev-animated">ReV Animated</a></p>
</blockquote>
<h2>最终效果</h2>
<p><img src="http://img.085404.xyz/images/f135e8e6170f8669016f0c1af2778412.webp" alt="1722849030495.png" /></p>
]]></content>
    <author><name>Sun2ot</name></author>
    <category term="教程"/>
  </entry>
  <entry>
    <title>stable diffusion webui 部署</title>
    <link href="https://blog.085404.xyz/posts/stable-diffusion-webui-%E9%83%A8%E7%BD%B2/" rel="alternate" type="text/html"/>
    <id>https://blog.085404.xyz/posts/stable-diffusion-webui-%E9%83%A8%E7%BD%B2/</id>
    <published>2024-08-05T00:00:00.000Z</published>
    <updated>2024-08-05T00:00:00.000Z</updated>
    <summary></summary>
    <content type="html"><![CDATA[<h1>stable diffusion webui 部署</h1>
<blockquote>
<p>Windows 的就不介绍了，一是因为使用 Windows 大部分是个人机，具有完全操作权限，因此没什么操作难度；二是相关的资料实在太多。</p>
</blockquote>
<h2>准备工作</h2>
<ol>
<li>代理环境（没有也不是不行，不过稍微麻烦点，下文会给出解决方案）</li>
<li><code>git</code></li>
<li>具有<strong>较新</strong>版本驱动的 NVIDIA GPU（我的服务器使用的 <code>Driver Version: 535.183.01   CUDA Version: 12.2</code>）
<ul>
<li>不要用最新的，因为大概率不兼容</li>
</ul>
</li>
<li><code>conda/mamba</code> 环境</li>
</ol>
<h2>部署过程</h2>
<p>首先创建一个 Python 3.10.6 版本的虚拟环境：</p>
<blockquote>
<p>必须是这个版本</p>
</blockquote>
<pre><code>conda create -n sdw python=3.10.6
conda activate sdw
</code></pre>
<p>然后克隆仓库（这是原版的 sdw，不是第三方的）：</p>
<pre><code>git clone https://github.com/AUTOMATIC1111/stable-diffusion-webui.git
</code></pre>
<p>下载依赖和模型：</p>
<pre><code>cd stable-diffusion-webui
./webui.sh
</code></pre>
<h2>问题说明</h2>
<p>理论上说，在网络和驱动没有问题的情况下，按照上述部署过程，等一段时间后就可以正常启动 sd webui。但多数情况下不会如此顺利，所以这里对几个常见的问题做出说明。</p>
<h3>关于代理</h3>
<p>如果你知道如何在 Linux 部署 clash 等代理客户端，那么只需要在执行依赖安装前，执行</p>
<pre><code>export https_proxy=http://127.0.0.1:7890 http_proxy=http://127.0.0.1:7890;
</code></pre>
<p>此后无论是从 GitHub 还是 hugging face 下载，都会走代理环境。</p>
<p>如果你不清楚这是什么，那就按照“依赖安装”一节中的内容逐步操作。</p>
<h3>依赖安装</h3>
<p>从事深度学习的可能会发现一个问题，sd webui 使用的是 Python 自带的 venv 虚拟环境，这玩意显然是没有 conda/mamba 好用的，那可不可以将依赖安装在后者然后启动呢？</p>
<p>经过实测以及检索各方信息，我的建议是别这么干，原因如下：</p>
<ol>
<li>代码能跑时，就不要去改它，这个道理 coder 应该都懂</li>
<li>我试图通过 <code>pip install -r requirements.txt</code> 在 conda 环境中预先下载环境，也确实能跑起来，但就本质来说，隔离环境这一点跟 venv 没有区别啊</li>
<li>此外，许多功能官方都封装在了 <code>webui-user.sh/bat</code> 的参数中。如果选择 conda 环境，将舍弃这一便利性，需要自行修改代码中相关参数。</li>
</ol>
<p>综上，与其不断试错，不如配置好镜像和代理，使用 <code>./webui.sh</code> 安装。</p>
<pre><code># pip 镜像
pip config set global.index-url https://pypi.tuna.tsinghua.edu.cn/simple
</code></pre>
<p>由于该模型需要从 hugging face 下载，所以这里建议手动从 <a href="https://hf-mirror.com/runwayml/stable-diffusion-v1-5/blob/main/v1-5-pruned-emaonly.safetensors">镜像站</a> 下载，然后移动到项目路径下的 <code>models/Stable-diffusion</code>。</p>
<blockquote>
<p>由于模型文件较大，下载完成后记得校验 sha256 以确保文件未损坏。</p>
</blockquote>
<p>上述工作完成后，执行 <code>./webui.sh</code> 等待即可。</p>
<p>如果依赖安装完成后，启动时报错 torch 相关错误，请检查以下两点：</p>
<ol>
<li>驱动版本是否合适（驱动版本太老，你又不是管理员，那还是歇了吧）</li>
<li>参考下文“torch 版本”一节</li>
</ol>
<h3>局域网访问</h3>
<p>默认情况下，sd webui 运行在 <code>127.0.0.1:7860</code>，不支持本机以外设备访问。可以修改 <code>webui-user.sh</code> 中的 <code>COMMANDLINE_ARGS</code> 解决：</p>
<pre><code>export COMMANDLINE_ARGS="--listen"
</code></pre>
<p>修改后重启 sd webui，将会在 <code>0.0.0.0:7860</code> 上启动。</p>
<h3>关于 GPUs</h3>
<p>稳定扩散模型 stable diffusion 本质上<strong>不支持跨多个 GPU 的并行工作</strong>，见 <a href="https://github.com/AUTOMATIC1111/stable-diffusion-webui/issues/11646">issue 11646</a>。但如果你有多个 GPU，想运行多个 sd webui 实例，或者想让 sd webui 运行在某个指定的 GPU 上，可以通过 <code>--device-id</code> 参数实现：</p>
<pre><code>export COMMANDLINE_ARGS="--device-id 1"
</code></pre>
<blockquote>
<p>注意，<code>export COMMANDLINE_ARGS="--device-id 0,1"</code> 是错误的，不可识别。</p>
</blockquote>
<h3>torch 版本</h3>
<p>sd webui 所需要的 pytorch 版本，按照 <code>nvidia-smi</code> 显示的 cuda 版本去下载，而不是 <code>nvcc -V</code> 所显示的 Runtime API 版本。</p>
<p>至于哪个版本可用，这是一个经验论问题，反复试错即可。</p>
<h2>所有参数</h2>
<p>https://github.com/AUTOMATIC1111/stable-diffusion-webui/wiki/Command-Line-Arguments-and-Settings</p>
]]></content>
    <author><name>Sun2ot</name></author>
    <category term="教程"/>
  </entry>
  <entry>
    <title>sd 美化二维码</title>
    <link href="https://blog.085404.xyz/posts/sd-%E7%BE%8E%E5%8C%96%E4%BA%8C%E7%BB%B4%E7%A0%81/" rel="alternate" type="text/html"/>
    <id>https://blog.085404.xyz/posts/sd-%E7%BE%8E%E5%8C%96%E4%BA%8C%E7%BB%B4%E7%A0%81/</id>
    <published>2024-08-01T00:00:00.000Z</published>
    <updated>2024-08-01T00:00:00.000Z</updated>
    <summary></summary>
    <content type="html"><![CDATA[<h1>sd 美化二维码</h1>
<h2>二维码生成</h2>
<p>这一步借助 webui 的插件 <a href="https://github.com/antfu/sd-webui-qrcode-toolkit">qrcode-toolkit</a> 实现。</p>
<h2>controlnet 模型</h2>
<p>先下载 <a href="https://github.com/Mikubill/sd-webui-controlnet">webui controlnet</a> 插件，然后下载两个控制模型：</p>
<ol>
<li>qrcode moster：
<ul>
<li><a href="https://hf-mirror.com/monster-labs/control_v1p_sd15_qrcode_monster/blob/main/control_v1p_sd15_qrcode_monster.yaml">control_v1p_sd15_qrcode_monster.yaml</a></li>
<li><a href="https://hf-mirror.com/monster-labs/control_v1p_sd15_qrcode_monster/blob/main/control_v1p_sd15_qrcode_monster.safetensors">control_v1p_sd15_qrcode_monster.safetensors</a></li>
</ul>
</li>
<li>latentcat/latentcat-controlnet：
<ul>
<li><a href="https://hf-mirror.com/latentcat/latentcat-controlnet/blob/main/models/control_v1p_sd15_brightness.safetensors">control_v1p_sd15_brightness.safetensors</a></li>
</ul>
</li>
</ol>
<p>这三个文件都放在 <code>extensions/sd-webui-controlnet/models</code> 下。</p>
<blockquote>
<p>给出的链接都是 hugging face 的镜像站，直接下载即可。</p>
</blockquote>
<p>然后按照下文使用 API  Key 下载基底大模型 <a href="https://civitai.com/models/7371/rev-animated">ReV Animated</a>。注意，Overview 中推荐搭配 VAE 和 Negative Prompt Embeddings 使用，我这里仅加载了一个 <a href="https://huggingface.co/WarriorMama777/OrangeMixs/blob/main/VAEs/orangemix.vae.pt">orangemix.vae.pt</a>。</p>
<p>关于 controlnet 的参数说明，可以参考如下视频，这里不过多赘述：</p>
<p>&lt;iframe width="560" height="315" src="https://www.youtube.com/embed/HyQxGnQNAHI?si=gQq49tUCN4iX-l7K" title="YouTube video player" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share" referrerpolicy="strict-origin-when-cross-origin" allowfullscreen&gt;&lt;/iframe&gt;</p>
<p>如果只是想关注如何用 stable diffusion webui 实现二维码生成，那建议直接看这个视频：</p>
<p>&lt;iframe width="560" height="315" src="https://www.youtube.com/embed/4CCZlwF4mro?si=plUcnKaspeHFUAfJ" title="YouTube video player" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share" referrerpolicy="strict-origin-when-cross-origin" allowfullscreen&gt;&lt;/iframe&gt;</p>
<h3>使用 civitai API</h3>
<p>有的模型 C 站要求登录后才能下载，因此直接 download 直链是不 work 的，需要使用 API Key。</p>
<p>按照路径：右上角头像 ➡️ Account settings ➡️ API Keys，新增一个 API Key。</p>
<blockquote>
<p>[!caution] 警告
虽然约定俗成但还是说一句：注意页面上的提示，弹出的 API Key 只会出现一次，记得保存下来。</p>
</blockquote>
<p>按照如下格式在下载链接中添加 <code>token</code> 参数即可下载：</p>
<pre><code>wget "https://civitai.com/api/download/models/425083?type=Model&amp;format=SafeTensor&amp;size=full&amp;fp=fp32&amp;token=xxx" --content-disposition
</code></pre>
<blockquote>
<p>主要是 <code>&amp;token=xxx</code></p>
</blockquote>
<h2>FAQ</h2>
<p>报错 <code>Expecting value: line 1 column 1 (char 0)</code></p>
<p>该问题与代理有关，关闭代理后重启即可。如果不想关闭代理，可修改 <code>webui-user.bat</code>，添加参数 <code>--no-gradio-queue</code></p>
<pre><code>set COMMANDLINE_ARGS=--no-gradio-queue
</code></pre>
<blockquote>
<p>Linux 类似地修改 <code>webui-user.sh</code> 即可。</p>
</blockquote>
]]></content>
    <author><name>Sun2ot</name></author>
    <category term="教程"/>
  </entry>
  <entry>
    <title>部署 Neo4j</title>
    <link href="https://blog.085404.xyz/posts/%E9%83%A8%E7%BD%B2-neo4j/" rel="alternate" type="text/html"/>
    <id>https://blog.085404.xyz/posts/%E9%83%A8%E7%BD%B2-neo4j/</id>
    <published>2024-07-11T00:00:00.000Z</published>
    <updated>2024-07-11T00:00:00.000Z</updated>
    <summary></summary>
    <content type="html"><![CDATA[<h1>部署 Neo4j</h1>
<h2>下载安装</h2>
<p>进入 <a href="https://neo4j.com/deployment-center/">neo4j deployment center</a>，页面往下拉，找到 <strong>Graph Database Self-Managed</strong>，选择对应配置下载压缩包。</p>
<blockquote>
<p>[!tip]
注意，选择社区版(community)，企业版是收费的。</p>
</blockquote>
<p>然后解压出来：</p>
<pre><code>tar -zxvf neo4j-community-4.4.35-unix.tar.gz
</code></pre>
<p>进入解压出的路径 <code>neo4j-community-4.4.35</code> 后，主要关注两个地方：</p>
<ol>
<li><code>bin</code>：neo4j 的二进制执行文件</li>
<li><code>conf</code>：neo4j 的配置文件</li>
</ol>
<p>通常情况下，需要使用 <code>bin/neo4j</code> 来管理 Neo4j 服务，所以还需要配置一个环境变量方便使用：</p>
<pre><code>vim ~/.bashrc # 或者 ~/.zshrc

# 在最下方插入如下内容(注意替换)
export PATH="path/to/your/neo4j-community-4.4.35/bin:$PATH"

# 保存退出文件

# 生效环境变量
source ~/.bashrc
</code></pre>
<p>然后执行 <code>neo4j status</code>，查看是否正常执行，输出 <code>Neo4j is not running.</code> 表示配置成功。</p>
<h2>配置 jdk11 环境</h2>
<p><a href="https://neo4j.com/docs/upgrade-migration-guide/current/version-4/migration/surface-changes/java-api/#_jdk_11">Neo4j 4.X 依赖于 jdk11</a>，所以还需要配置一个 Java 环境。</p>
<p>服务器已经在 <code>/opt</code> 下部署了常用环境的二进制包，因此直接配置环境变量即可。</p>
<pre><code>vim ~/.bashrc # 或者 ~/.zshrc

# 在最下方插入如下内容(注意替换)
export JAVA_HOME=/opt/jdk-11.0.22
export PATH=$JAVA_HOME/bin:$PATH
export JRE_HOME=$JAVA_HOME/jre
export CLASSPATH=.:$JAVA_HOME/lib

# 保存退出文件

# 生效环境变量
source ~/.bashrc
</code></pre>
<p>完成后输入 <code>java --version</code>，返回如下信息表示配置成功：</p>
<pre><code>java 11.0.22 2024-01-16 LTS
Java(TM) SE Runtime Environment 18.9 (build 11.0.22+9-LTS-219)
Java HotSpot(TM) 64-Bit Server VM 18.9 (build 11.0.22+9-LTS-219, mixed mode)
</code></pre>
<blockquote>
<p>[!tip]
注意是 java，不是 openjdk 😅</p>
</blockquote>
<h2>配置 Neo4j</h2>
<p>由于 Neo4j 社区版只支持单实例，无法做集群，所以多用户使用时，要修改默认端口防止冲突。</p>
<blockquote>
<p>[!tip]
可以简单理解为部署一个 neo4j 只能给一个人用，不像数据库，部署一次可以服务多个用户。</p>
</blockquote>
<p>Neo4j 的配置通过编辑文件 <code>conf/neo4j.conf</code> 进行。</p>
<p><strong>首先修改 http 端口</strong>。在配置文件中找到如下内容，删除前面的井号 <code>#</code> 取消注释，并修改对应的端口：</p>
<pre><code># 修改前
#dbms.connector.http.listen_address=:7474
#dbms.connector.http.advertised_address=:7474

# 修改后
dbms.connector.http.listen_address=:7475
dbms.connector.http.advertised_address=:7475
</code></pre>
<p><strong>然后修改 bolt 协议的端口</strong>。</p>
<pre><code># 修改前
#dbms.connector.bolt.listen_address=:7687
#dbms.connector.bolt.advertised_address=:7687

# 修改后
dbms.connector.bolt.listen_address=:7688
dbms.connector.bolt.advertised_address=:7688
</code></pre>
<p><strong>最后修改数据库默认的监听地址</strong>。如果你只需要在本机使用 Neo4j，没有局域网访问的需求，可以不做这一步。</p>
<pre><code>dbms.default_listen_address=0.0.0.0
</code></pre>
<h2>使用 Neo4j</h2>
<pre><code># 启动
neo4j start

# 停止
neo4j stop

# 重启
neo4j restart

# 检查状态
neo4j status
</code></pre>
]]></content>
    <author><name>Sun2ot</name></author>
    <category term="教程"/>
  </entry>
  <entry>
    <title>智能优化算法4：粒子群算法</title>
    <link href="https://blog.085404.xyz/posts/%E7%B2%92%E5%AD%90%E7%BE%A4%E7%AE%97%E6%B3%95/" rel="alternate" type="text/html"/>
    <id>https://blog.085404.xyz/posts/%E7%B2%92%E5%AD%90%E7%BE%A4%E7%AE%97%E6%B3%95/</id>
    <published>2024-06-24T00:00:00.000Z</published>
    <updated>2024-06-24T00:00:00.000Z</updated>
    <summary></summary>
    <content type="html"><![CDATA[<h1>智能优化算法 4：粒子群算法</h1>
<h2>基本原理</h2>
<p>粒子群算法（Particle Swarm Optimization, PSO）是一种基于群体智能的优化算法。它通过模拟鸟群或鱼群等社会动物的群体行为来寻找最优解。可以假设这样一个场景：在一个区域里只有一个食物源，有一群鸟在觅食。所有鸟都不知道食物在哪里，但是它们知道当前的位置离食物还有多远。那么找到食物的最佳策略是什么呢？最简单有效的方法是<strong>搜索目前离食物最近的鸟的周围区域</strong>并根据自身经验判断食物的位置。</p>
<p>鸟群在寻找食物的过程中，个体之间存在信息交流和共享，每个成员可以从其他成员的搜索经验中获益，并调整自身的搜索行为。当食物源不可预测其分布状态时，这种个体之间的协作所产生的优势是巨大的。</p>
<ul>
<li>在 PSO 中，把一个优化问题看作是在空中觅食的鸟群，那么“食物”就是优化问题的最优解，而在空中飞行的每一只觅食的“鸟”就是 PSO 算法中在解空间中进行搜索的一个“<strong>粒子</strong>”(Particle)。</li>
<li>粒子在搜索空间中以一定的<strong>速度</strong>飞行，这个速度根据它本身的飞行经验和同伴的飞行经验来动态调整。所有的粒子都有一个被目标函数决定的适应值（fitness value），这个适应值用于评价粒子的“好坏”程度。</li>
<li>每个粒子知道自己到目前为止发现的<strong>最好位置</strong>（particle best，记为 <code>pbest</code>）和<strong>当前的位置</strong>，<code>pbest</code> 就是粒子本身找到的最优解，这个可以看作是粒子自己的飞行经验。</li>
<li>除此之外，每个粒子还知道到目前为止整个群体中所有粒子发现的最好位置 (global best，记为 <code>gbest</code>)，<code>gbest</code> 是在 <code>pbest</code> 中的最好值，即是<strong>全局最优解</strong>，这个可以看作是整个群体的经验。</li>
</ul>
<h2>数学模型</h2>
<p>粒子群算法（Particle Swarm Optimization, PSO）是通过模拟群体行为来解决优化问题的算法。它的核心在于每个粒子在搜索空间中不断调整其位置和速度，以接近全局最优解。下面介绍粒子群算法的数学模型，包括位置和速度的更新公式及其相关参数。</p>
<h3>初始化</h3>
<p>首先，初始化粒子群，包括每个粒子的初始位置和速度。假设粒子群由 $N$ 个粒子组成，每个粒子在 $D$ 维空间中。</p>
<ul>
<li>粒子 $i$ 的<strong>位置</strong>： $\mathbf{x}<em>i = (x</em>{i1}, x_{i2}, \ldots, x_{iD})$</li>
<li>粒子 $i$ 的<strong>速度</strong>： $\mathbf{v}<em>i = (v</em>{i1}, v_{i2}, \ldots, v_{iD})$</li>
<li>粒子 $i$ 的<strong>个体最优位置</strong>（个体最优解）：$\mathbf{pbest}<em>i = (p</em>{i1}, p_{i2}, \ldots, p_{iD})$</li>
<li><strong>全局最优位置</strong>（全局最优解）：$\mathbf{gbest} = (g_1, g_2, \ldots, g_D)$</li>
</ul>
<h3>速度更新公式</h3>
<p>粒子 $i$ 在第 $t+1$ 代的速度由以下公式更新：</p>
<p>$$
\mathbf{v}_i(t+1) = w \cdot \mathbf{v}_i(t) + c_1 \cdot r_1 \cdot (\mathbf{p}_i(t) - \mathbf{x}_i(t)) + c_2 \cdot r_2 \cdot (\mathbf{g}(t) - \mathbf{x}_i(t))
$$</p>
<p>其中：</p>
<ul>
<li>$w$ 是<strong>惯性权重</strong>，控制粒子的惯性对速度的影响。</li>
<li>$c_1$ 是<strong>个体学习因子</strong>，衡量粒子自身经验的重要性。</li>
<li>$c_2$ 是<strong>社会学习因子</strong>，衡量粒子借鉴群体经验的重要性。</li>
<li>$r_1$ 和 $r_2$ 是两个独立的均匀分布的随机数，范围在 [0, 1] 之间，增加搜索的随机性。</li>
</ul>
<blockquote>
<p>式子的三个部分也可以看作：粒子先前的速度 + 自身认知项（认知组件） + 群体认知项（社交组件）</p>
</blockquote>
<h3>位置更新公式</h3>
<p>粒子 $i$ 在第 $t+1$ 代的位置由以下公式更新：</p>
<p>$$
\mathbf{x}_i(t+1) = \mathbf{x}_i(t) + \mathbf{v}_i(t+1)
$$</p>
<blockquote>
<p>位置是一个空间坐标，所以位置更新要加上整个空间内的位移</p>
</blockquote>
<h3>适应度评估</h3>
<p>每个粒子的适应度值 $f(\mathbf{x}_i)$ 是其位置 $\mathbf{x}_i$ 对应的目标函数值。适应度评估用于更新个体最优位置和全局最优位置：</p>
<ul>
<li>如果 $f(\mathbf{x}_i(t+1))$ 优于 $f(\mathbf{pbest}_i(t))$，则<strong>更新个体最优位置</strong>：$\mathbf{pbest}_i(t+1) = \mathbf{x}_i(t+1)$</li>
<li>如果 $f(\mathbf{pbest}_i(t+1))$ 优于 $f(\mathbf{gbest}(t))$，则<strong>更新全局最优位置</strong>： $\mathbf{gbest}(t+1) = \mathbf{pbest}_i(t+1)$</li>
</ul>
<h3>终止条件</h3>
<p>算法反复进行速度和位置的更新，直到满足以下停止条件之一：</p>
<ul>
<li>达到预设的最大迭代次数。</li>
<li>全局最优位置的适应度值在若干次迭代中没有显著改善。</li>
<li>全局最优位置的适应度值达到了预设的阈值。</li>
</ul>
<h3>参数设置</h3>
<p>粒子群算法的性能在很大程度上依赖于参数的设置：</p>
<ul>
<li><strong>惯性权重 ( w )</strong>：一般在 [0.4, 0.9] 范围内选择，通常随时间递减。</li>
<li><strong>个体学习因子 ( c_1 ) 和社会学习因子 ( c_2 )</strong>：通常都设置为 2。</li>
<li><strong>速度限制 $v_{\text{max}}$</strong>：限制粒子的速度以防止其移动过快，一般取值为位置范围的 10% 到 20%。</li>
</ul>
<h2>粒子群算法解决函数优化问题</h2>
<p><strong>Rosenbrock 函数</strong>是一个经典的测试函数，用于验证优化算法的性能，其定义如下：</p>
<p>$$
f(x,y)=(a-x)^2+b \cdot (y-x^2)^2
$$</p>
<p>该函数有一个全局最小值点，当 $a=1, b=100$ 时，全局最小值点位于 $(x,y)=(1,1)$，此时函数值为 0。函数在该点附近呈现一个狭长的谷底，这使得优化问题具有挑战性。</p>
<p><a href="https://github.com/sun2ot/algorithm-learning/blob/master/ioa/PSO.py">代码实现</a></p>
]]></content>
    <author><name>Sun2ot</name></author>
    <category term="笔记"/>
  </entry>
  <entry>
    <title>智能优化算法3：模拟退火算法</title>
    <link href="https://blog.085404.xyz/posts/%E6%A8%A1%E6%8B%9F%E9%80%80%E7%81%AB%E7%AE%97%E6%B3%95/" rel="alternate" type="text/html"/>
    <id>https://blog.085404.xyz/posts/%E6%A8%A1%E6%8B%9F%E9%80%80%E7%81%AB%E7%AE%97%E6%B3%95/</id>
    <published>2024-06-23T00:00:00.000Z</published>
    <updated>2024-06-23T00:00:00.000Z</updated>
    <summary></summary>
    <content type="html"><![CDATA[<h1>智能优化算法 3：模拟退火算法</h1>
<h2>基本原理</h2>
<p>模拟退火算法（Simulated Annealing，SA）的基本思想来源于固体退火过程。在退火过程中，固体被加热到高温，使得原子可以自由移动，然后逐渐降低温度，系统逐渐冷却并达到稳定状态，最终原子排列在能量最低的基态。</p>
<p>具体来说，模拟退火算法通过在解空间中随机采样，并允许一定的“退步”来避免陷入局部最优解，从而接近全局最优解。算法的关键在于“接受概率”，即是否接受一个较差解的概率随着温度的降低而降低。</p>
<blockquote>
<p>把问题看作固体内部粒子，把最优解看作平衡态，用程序模拟退火的过程，就是模拟退火算法。</p>
</blockquote>
<h2>数学模型</h2>
<h3>热力学定义与 Boltzmann 分布</h3>
<p>在统计物理学中，Boltzmann 分布描述了一个系统在温度 T 下，系统在不同能量状态 $E_i$ ​ 的概率分布。具体形式为：</p>
<p>$$
P_i(T)=\frac{exp(\frac{-E_i}{k_B T})}{Z}
$$</p>
<ul>
<li>$P_i(T)$ 是系统在温度 T 下处于状态 i 的概率</li>
<li>$k_B$ 是 Boltzmann 常数</li>
<li>$Z$ 是配分函数，表示系统所有可能状态（共 n 个）的概率之和，确保概率总和为 1，即
$$
Z=\sum_j exp(\frac{-E_j}{k_B T})
$$</li>
</ul>
<h3>模拟退火算法中的应用</h3>
<p>假设热力学系统 S 有 n 个离散的状态，其中状态 i 的能量表示为 $E_i$。设在温度 $T_k$ 下系统达到热平衡，此时处于状态 i 的概率为：</p>
<p>$$
P_i(T_k)=C_k exp(\frac{-E_i}{T_k})
$$</p>
<p>上式可以被理解为对 Boltzmann 分布的<strong>简化形式</strong>，其中 $C_k$ 是一个归一化常数（类似于配分函数的倒数），确保所有状态概率之和为 1，即</p>
<p>$$
C_k=\frac{1}{\sum_{i=1}^n exp(\frac{-E_i}{T_k})}
$$</p>
<p>在模拟退火算法中，该方程用于<strong>计算接受一个较差解的概率</strong>，这个概率决定了算法是否接受一个<strong>能量较高</strong>的新解，以便跳出局部最优解并朝向全局最优解。</p>
<blockquote>
<p>也就是智能优化算法中的从当前状态转移到新状态的转移（接受）概率。</p>
</blockquote>
<h3>温度对概率的影响</h3>
<p>假设在同一温度 T 下，有两个不同的能量状态 $E_1 &lt; E_2$，那么</p>
<p>$$
\frac{P_1(T)}{P_2(T)}=exp(-\frac{E_2-E_1}{T})
$$</p>
<p>因为 $E_1 &lt; E_2$，所以 $exp(-\frac{E_2-E_1}{T})&lt;1$，即 $P_1(T)&lt;P_2(T)$。这说明<strong>在相同温度下，系统处于能量小状态的概率比处于能量大状态的概率要大</strong>。此时，模拟退火算法可以在部分高质量解区域进行重点搜索，提高搜索效率。</p>
<p>当 $T_k$ 很大，即<strong>温度高</strong>时，$\frac{E_i}{T_k} \rightarrow 0$，因此</p>
<p>$$
P_i(T_k)=C_k exp(\frac{-E_i}{T_k}) \rightarrow C_k = \frac{1}{\sum_{i=1}^n exp(\frac{-E_i}{T_k})} = \frac {1} n
$$</p>
<p>这表示在温度高时，<strong>系统处于任意能量状态的概率基本相同</strong>，接近 1/n。此时，模拟退火算法可以在解空间任何区域进行搜索（广域搜索），避免早熟收敛。</p>
<p>当 $T_k \rightarrow 0$ 时，$\frac{E_i}{T_k} \rightarrow \infty$，此时<strong>系统将无限接近能量最低状态</strong>，且低温下对能量差异非常敏感，即 $E_i$ 和 $E_j$ 的小差别会带来 $P_i(T_k)$ 和 $P_j(T_k)$ 的巨大差异。此时，模拟退火算法无限接近全局最优解（能量最低状态）。</p>
<h2>算法模拟</h2>
<p>模拟退火算法（SA）的模拟有三个要求：</p>
<ol>
<li>初始温度足够高</li>
<li>降温过程足够慢</li>
<li>终止温度足够低</li>
</ol>
<p>SA 通过接受较差解的策略来避免陷入局部最优解，最终收敛到全局最优解。这种接受较差解的概率由 Boltzmann 分布控制，其公式为：</p>
<p>$$
P(E,E',T)=exp(\frac{-\Delta E}{T})
$$</p>
<p>其中：</p>
<ul>
<li>$E$ 是当前解的目标函数值</li>
<li>$E'$ 是新解的目标函数值</li>
<li>$\Delta E = E'-E$ 是目标函数值的变化</li>
<li>$T$ 是当前温度</li>
</ul>
<h3>模拟退火算法的构造</h3>
<p>算法主要分为以下三个部分：</p>
<ol>
<li><strong>初始解和初始温度</strong>：
<ul>
<li>选择一个初始解 $S_0$。</li>
<li>设定一个初始温度 $T_0$（足够高）</li>
</ul>
</li>
<li><strong>退火调度</strong>：
<ul>
<li>设定温度下降的策略（退火调度），如线性降温、指数降温等。</li>
</ul>
</li>
<li><strong>终止条件</strong>：
<ul>
<li>设定算法的终止条件，如温度降到一定阈值或者经过一定次数的迭代后停止。</li>
</ul>
</li>
</ol>
<p>具体的算法步骤如下：</p>
<ol>
<li><strong>初始化</strong>：
<ul>
<li>选择初始解 $S$</li>
<li>设定初始温度 $T$</li>
<li>设定温度下降的策略，例如 $T=rate*T$</li>
<li>设定初始迭代次数 $k=0$</li>
</ul>
</li>
<li><strong>迭代过程</strong>：
<ul>
<li><strong>生成新解</strong>：从当前解 $S$ 的邻域中随机选择一个新解 $S'$</li>
<li><strong>计算能量差</strong>：计算新解与当前解的能量差 $\Delta E = E(S')-E(S)$</li>
<li><strong>接受概率</strong>：根据 Boltzmann 分布计算接受新解的概率 $P(E,E',T)=exp(\frac{-\Delta E}{T})$
<ul>
<li>如果 $\Delta E &lt; 0$（温度降低），接受新解（即 $S \leftarrow S'$）</li>
<li>如果 $\Delta E \ge 0$，以概率 $P$ 接受新解</li>
</ul>
</li>
<li><strong>更新温度</strong>：根据退火调度函数更新温度 $T$</li>
<li><strong>更新迭代次数</strong>：$k \leftarrow k+1$</li>
</ul>
</li>
<li><strong>终止条件</strong>：
<ul>
<li>如果达到终止条件，输出当前解 $S$ 作为最终结果</li>
<li>否则，返回步骤 2 继续迭代</li>
</ul>
</li>
</ol>
<h2>SA 算法解决调度问题</h2>
<h3>问题定义</h3>
<p><strong>调度问题</strong>（Scheduling Problem）通常涉及将一组任务（作业）分配给有限的资源（例如机器、工人或时间段），以优化某个目标。常见的目标包括最小化总完成时间、最小化延迟或最大化资源利用率。</p>
<ul>
<li>有 n 个作业，每个作业需要一定的处理时间。</li>
<li>有 m 台机器，每台机器可以并行处理一个作业。</li>
</ul>
<p><strong>目标</strong>：将这些作业分配到机器上，使得所有机器的总完成时间（Makespan）最小化。</p>
<p><strong>Makespan</strong>：所有机器的最大完成时间，即最后一个完成作业的时间。</p>
<ul>
<li>假设有两个机器和 4 个作业，执行时间分别为 [2, 4, 6, 8]。一种可能的分配是：
<ul>
<li>机器 1：作业 1 (2), 作业 3 (6)</li>
<li>机器 2：作业 2 (4), 作业 4 (8)</li>
</ul>
</li>
<li>机器 1 的总完成时间是 2 + 6 = 8</li>
<li>机器 2 的总完成时间是 4 + 8 = 12</li>
<li>Makespan 是两者中的最大值，即 12</li>
</ul>
<p>调度问题的目标就是找到一种分配，使得这个最大值最小。</p>
<h3>算法实现</h3>
<p><a href="https://github.com/sun2ot/algorithm-learning/blob/master/ioa/SA.py">完整代码</a></p>
]]></content>
    <author><name>Sun2ot</name></author>
    <category term="笔记"/>
  </entry>
  <entry>
    <title>智能优化算法2：蚁群算法</title>
    <link href="https://blog.085404.xyz/posts/%E8%9A%81%E7%BE%A4%E7%AE%97%E6%B3%95/" rel="alternate" type="text/html"/>
    <id>https://blog.085404.xyz/posts/%E8%9A%81%E7%BE%A4%E7%AE%97%E6%B3%95/</id>
    <published>2024-06-22T00:00:00.000Z</published>
    <updated>2024-06-22T00:00:00.000Z</updated>
    <summary></summary>
    <content type="html"><![CDATA[<h1>智能优化算法 2：蚁群算法</h1>
<h2>基本原理</h2>
<p>蚁群优化（ant colony optimization, ACO）算法是一种源自大自然生物世界的仿生类算法，其思想充分吸收了蚁群在觅食过程中的行为特性。</p>
<p>蚁群觅食，每只蚂蚁在经过的路上会留下一种化学物质，称为信息素，<strong>信息素会随着时间逐渐挥发</strong>，也就是说找到食物越慢，这条路上的信息素残留越少。其他蚂蚁可以感受到信息素的存在，并且能测量信息素的浓度。</p>
<p><strong>通常蚂蚁会沿着信息素最浓的方向行走</strong>，毕竟是前人经验嘛，这样这条路上的信息素越来越浓；但偶尔也会突发奇想，试着走走信息素没那么浓，甚至没有信息素的路径，可能更快找到了食物。那么新路径信息素浓度很大，但也可能找不到食物，直到信息素挥发完毕。</p>
<p>如此反复，经过大量蚂蚁重复多次的探索，最终摸索出一条最短的路径；当然 <strong>不一定是全局最短</strong>，这点就不解释了。</p>
<h2>数学模型</h2>
<p>蚁群算法的基本迭代过程如下：</p>
<ol>
<li><strong>初始化信息素</strong>：在所有路径上初始化一个较小的初始信息素值。</li>
<li><strong>每只蚂蚁构建路径</strong>：
<ul>
<li>每只蚂蚁根据当前的信息素和启发式信息（如路径距离、时间等）选择路径，从起点到达终点。</li>
<li>在路径选择过程中，蚂蚁会逐步走完整个路径，<strong>但此时并不会更新信息素</strong>。</li>
</ul>
</li>
<li><strong>路径评估与信息素更新</strong>：
<ul>
<li>在所有蚂蚁都走完路径后，收集每只蚂蚁的路径信息。</li>
<li>计算每只蚂蚁在其路径上留下的信息素量​。</li>
<li>叠加所有蚂蚁在每条路径上留下的信息素，更新全局信息素矩阵。</li>
</ul>
</li>
<li><strong>挥发信息素</strong>：应用信息素挥发公式，使信息素浓度逐步减小，保持探索能力。</li>
</ol>
<h3>状态转移概率</h3>
<p>这玩意决定了蚂蚁选择走哪条路。</p>
<p><img src="http://img.085404.xyz/images/7060f4cc6c489098c35726c5e03ee65a.webp" alt="image.png" /></p>
<ul>
<li>$P^k_{i,j}(t)$ 是状态转移概率，代表 t <strong>时刻</strong>，第 k 只蚂蚁在 i 点选择往 j 点走的概率；</li>
<li>allowed 表示蚂蚁 k 下一步允许选择的城市；</li>
<li>$\tau_{ij}$ 表示 i，j 两点之间的信息素浓度</li>
<li>$\alpha$ 是信息素启发因子，控制着 $\tau_{i,j}$ 对路径选择的影响程度：
<ul>
<li>值越大，越依赖信息素，探索性降低；</li>
<li>值越小，蚁群搜索的范围减少；</li>
</ul>
</li>
<li>$\eta_{ij}$ 表示 i，j 之间的能见度，反映了由 i 到 j 的启发程度，因此也叫启发函数
<ul>
<li>通常取 i，j 距离 $d_{ij}$ 的倒数</li>
<li>距离越短，启发函数值越大，因此表示蚂蚁在两点间转移的期望程度</li>
</ul>
</li>
<li>$\beta$ 是期望值启发因子，表示能见度的相对重要性，反映蚂蚁在运动过程中启发信息在蚂蚁选择路径中的受重视程度：
<ul>
<li>值越大，则状态转移概率越接近于贪心规则（距离越短我越走嘛）；</li>
</ul>
</li>
</ul>
<blockquote>
<p>注意这里的 t 虽然译为“时刻”，但其实是迭代的次数，即 m 只蚂蚁全部走完一次后，才会加一。</p>
</blockquote>
<h3>信息素更新公式</h3>
<h4>初始化信息素浓度</h4>
<p>$$
\tau_{ij} = O
$$</p>
<p>如果 O 太大，算法容易早熟，蚂蚁会很快的全部集中到一条局部最优的路径上。反之，如果 O 太小，信息素对搜索方向的指导作用太低，也会影响算法性能。</p>
<h4>信息素更新规则</h4>
<p>$$\tau_{ij}(t + 1) = (1 - \rho) \tau_{ij}(t) + \sum_{k=1}^{m} \Delta \tau_{ij}^k(t)$$</p>
<p>其中：</p>
<ul>
<li>$\tau_{ij}(t)$ 表示在时刻 t 路径 (i, j) 上的信息素浓度。</li>
<li>$\rho$ 是信息素挥发系数，$0 &lt; \rho &lt; 1$。
<ul>
<li>信息素随时间的衰减，能避免信息素无限累积，使得算法能不断探索新的路径，防止陷入局部最优。</li>
</ul>
</li>
<li>$\Delta \tau_{ij}^k(t)$ 是第 k 只蚂蚁在时刻 t 经过路径 (i, j) 时留下的信息素量，因此<strong>信息素增量</strong>是整个蚁群产生的增量。
<ul>
<li>信息素的增量通常与路径的质量相关，路径越短（即质量越好），新增的信息素越多。</li>
</ul>
</li>
</ul>
<blockquote>
<p>始终记得，在蚁群算法中，信息素更新是基于一次完整迭代中的<strong>所有蚂蚁</strong>行动结果进行的，而不是在每只蚂蚁行动之后立即更新。具体地说，公式中的 m 指的是当前迭代中参与路径构建的所有蚂蚁，即蚁群数量。</p>
</blockquote>
<h3>三种基本蚁群算法模型</h3>
<p>$\Delta \tau_{ij}$ 的计算方法取决于具体的蚁群算法实现，常见的方法包括以下 3 种。这些模型的区别在于蚂蚁在路径上留下信息素的方式。</p>
<h4>蚂蚁圈模型（Ant Cycle Model）</h4>
<p>在蚂蚁圈模型中，<strong>信息素的更新发生在所有蚂蚁完成路径之后</strong>，综合每只蚂蚁的路径质量进行更新（<strong>全局更新</strong>）。<strong>这是最常用的蚂蚁算法模型</strong>。</p>
<p>其中 $\Delta \tau_{ij}^k$ 的一个常见的选择是：</p>
<p>$$\Delta \tau_{ij}^k = \begin{cases}
\frac{Q}{L_k} &amp; \text{如果蚂蚁 } k \text{ 经过了路径 } (i, j) \
0 &amp; \text{否则}
\end{cases}$$</p>
<p>其中 Q 是一个常数， $L_k$ 是第 k 只蚂蚁在本次迭代所走路径的总长度。</p>
<h4>蚂蚁数量模型（Ant Quantity Model）</h4>
<p>在蚂蚁数量模型中，蚂蚁在经过路径时留下的信息素为<strong>变量</strong>。属于<strong>局部更新</strong>，即信息素的更新是沿路径在每个节点处逐步进行的，而不是等所有蚂蚁完成路径后再统一更新。</p>
<p>$$
\Delta \tau_{ij}^k = \begin{cases}
\frac{Q}{d_{ij}} &amp; \text{如果蚂蚁 } k \text{ 经过了路径 } (i, j) \
0 &amp; \text{否则}
\end{cases}
$$</p>
<h4>蚂蚁密度模型（Ant Density Model）</h4>
<p>在蚂蚁数量模型中，蚂蚁在经过路径时留下的信息素为<strong>常量</strong>。属于<strong>局部更新</strong>。</p>
<p>$$\Delta \tau_{ij}^k = Q$$</p>
<p>Q 是一个常数。</p>
<h2>蚁群算法求解 TSP 问题</h2>
<p>旅行商问题，即 TSP 问题（Traveling Salesman Problem），给定一组城市和每对城市之间的距离，商家从其中一个城市出发，要求每个城市都要经过且只能经过一次，最后回到起点城市，求解商家访问这些城市的<strong>最短路径</strong>。</p>
<blockquote>
<p>在经典的 TSP 中，假设所有城市之间都存在路径，即形成一个<strong>完全图</strong>。也就是说，每对城市之间都有直接的边连接。</p>
</blockquote>
<p><a href="https://github.com/sun2ot/algorithm-learning/blob/master/ioa/ACO.py">完整代码</a></p>
]]></content>
    <author><name>Sun2ot</name></author>
    <category term="笔记"/>
  </entry>
  <entry>
    <title>Python logging 模块</title>
    <link href="https://blog.085404.xyz/posts/python-logging-%E6%A8%A1%E5%9D%97/" rel="alternate" type="text/html"/>
    <id>https://blog.085404.xyz/posts/python-logging-%E6%A8%A1%E5%9D%97/</id>
    <published>2024-06-14T00:00:00.000Z</published>
    <updated>2024-06-14T00:00:00.000Z</updated>
    <summary></summary>
    <content type="html"><![CDATA[<h2>基本使用</h2>
<pre><code>import logging

logging.info('this is an info msg')
logging.warning('this is a warning msg')
</code></pre>
<h2>显示时间</h2>
<p>要在日志中显示时间信息，可以通过格式字符串 <code>%(asctime)s</code> 控制：</p>
<pre><code>import logging
logging.basicConfig(format='%(asctime)s %(message)s')
logging.warning('is when this event was logged.')
</code></pre>
<p>输出结果如下：</p>
<pre><code>2010-12-12 11:41:42,612 is when this event was logged.
</code></pre>
<p>按照最新的 <a href="https://docs.python.org/zh-cn/3/howto/logging.html#displaying-the-date-time-in-messages">Python 3.12.5 official docs</a>，
但一般情况下，我们不需要如此精细的时间。如果需要自定义时间格式，可以通过给 <code>basicConfig</code> 添加 <code>datafmt</code> 参数实现：</p>
<pre><code>import logging
logging.basicConfig(format='%(asctime)s %(message)s', datefmt='%m/%d/%Y %I:%M:%S %p')
logging.warning('is when this event was logged.')
</code></pre>
<p>输出结果如下：</p>
<pre><code>12/12/2010 11:46:36 AM is when this event was logged.
</code></pre>
<blockquote>
<p>关于 <code>datefmt</code> 的格式，参见 <a href="https://docs.python.org/zh-cn/3/library/time.html#time.strftime">time.strftime()</a>。</p>
</blockquote>
<h2>进阶日志使用</h2>
<h3>记录器</h3>
<p>高级的日志使用方法与基础的区别主要在于模块化记录。关于具体的区别可以参考截止目前最新的 <a href="https://docs.python.org/zh-cn/3/howto/logging.html#advanced-logging-tutorial">Python 3.12.5 official docs</a>。这里仅对 coding 做出介绍。</p>
<p>一个良好的习惯是：<strong>对于不同的模块，使用不同的日志记录器</strong>。</p>
<pre><code>import logging
logger = logging.getLogger('example')
</code></pre>
<p>对于一个记录器，首先需要指定要处理的 <a href="https://docs.python.org/zh-cn/3/library/logging.html#levels">日志级别</a>。需注意，这里指定的是<strong>日志处理的最低严重性级别</strong>，例如 INFO 表示仅处理 INFO 、 WARNING 、 ERROR 和 CRITICAL 消息，而忽略 DEBUG 消息。</p>
<pre><code>logger.setLevel(logging.INFO)
</code></pre>
<p>请注意，<code>setLevel()</code> 是很重要的步骤，如果遗漏，日志级别将被默认置为 <code>NOTEST</code>。这意味着对于一条日志信息：</p>
<ol>
<li>如果记录器<strong>是根记录器</strong>时，将处理所有消息</li>
<li>如果记录器<strong>不是根记录器</strong>，则将委托给父级</li>
</ol>
<p>这往往会导致一些 exception 出现，因为根记录器默认的日志级别为 <code>WARNING</code>，这意味着你记录的 <code>DEBUG &amp; INFO</code> 日志都不会输出。</p>
<h3>处理器</h3>
<p>上面说过，对于一个模块，采用一个独立的记录器是合适的。但对于同一个模块，不同类型的日志信息，也许需要不同的处理方式。例如正常的日志信息在 CLI 输出，而异常信息持久化到文件存储，此时就需要<strong>对一个日志记录器添加不同的处理器</strong>。Python 标准库中已经封装好了许多 <a href="https://docs.python.org/zh-cn/3/howto/logging.html#useful-handlers">常用的处理器类型</a>。下面以 <code>StreamHandler</code> 为例，它可以发送消息到流（一般用于输出到 <code>stdout</code> 或 <code>stderr</code>）。</p>
<pre><code>exp_stream = logging.StreamHandler()
# WARNING &gt; INFO
exp_stream.setLevel(logging.WARNING)
</code></pre>
<p>显然，一个记录器添加了若干个处理器后，<strong>每个处理器可以设置不同的日志级别</strong>。但请注意，上面通过 <code>logger.setLevel(logging.INFO)</code> 已经设置了 <code>logger</code> 的级别。理论上说，<code>handler</code> 的级别应当<strong>高于</strong> <code>logger</code> 的级别，因为后者决定了整个模块的日志准入级别，如果 <code>handler</code> 的级别比 <code>logger</code> 更低，会直接被忽略。</p>
<h3>格式器</h3>
<p>上文“显示时间”章节提到过，可以使用 <code>logging.basicConfig()</code> 来设置日志格式。但这是基于全局的格式控制，用于模块化日志记录显然是不合适的。<strong>不同的日志记录器应该可以具有不同的日志格式</strong>，这就需要借助格式器来完成。</p>
<p>一种常用的格式如下，这表示 <code>时间 - 记录器名字 - 日志级别 - 日志信息</code>。</p>
<pre><code>formatter = logging.Formatter('%(asctime)s - %(name)s - %(levelname)s - %(message)s')
exp_stream.setFormatter(formatter)
</code></pre>
<h3>使用</h3>
<p>配置完成后，将 <code>handler</code> 添加到 <code>logger</code> 后，即可像基本使用那样记录日志信息了：</p>
<pre><code># 添加处理器
logger.addHandler(exp_stream)
# 记录日志
logger.info('this is an info msg')
logger.warning('this is a warning msg')
...
</code></pre>
<p>不过，上面示例中的两条日志，只有 <code>WARNING</code> 会被输出，而 <code>INFO</code> 不会。这是因为，只有一个名为 <code>exp_stream</code> 的 <code>handler</code> 被定义处理 <code>WARNING</code> 日志信息，而虽然 <code>logger</code> 的级别是 <code>INFO</code>，但并没有对应的 <code>handler</code> 来处理。</p>
<p>因此，在 Python 的 <code>logging</code> 模块中，默认情况下，如果没有显式地为 <code>logger</code> 添加任何 <code>handler</code>，那么 <code>logger</code> 将不会自动输出任何日志到终端或其他地方。</p>
<h3>配置文件</h3>
<p>上文的进阶日志使用部分，也可以通过一个日志配置文件统一定义。</p>
<p>有空再写。</p>
<h2>FAQ</h2>
<h3>配置不生效</h3>
<p>logging 模块是不支持追加配置的，就是说如果其他部分代码已经使用了 <code>logging.basicConfig()</code>，那么配置就固定了，后续再调用该函数是无法生效的。</p>
]]></content>
    <author><name>Sun2ot</name></author>
    <category term="教程"/>
  </entry>
  <entry>
    <title>Termux Docs</title>
    <link href="https://blog.085404.xyz/posts/termux-docs/" rel="alternate" type="text/html"/>
    <id>https://blog.085404.xyz/posts/termux-docs/</id>
    <published>2024-06-14T00:00:00.000Z</published>
    <updated>2024-06-14T00:00:00.000Z</updated>
    <summary></summary>
    <content type="html"><![CDATA[<h1>Termux Docs</h1>
<h2>安装</h2>
<p><a href="https://github.com/termux/termux-app#github">Termux-App GitHub</a></p>
<h2>初始化</h2>
<p><strong>修改镜像源：</strong></p>
<pre><code>termux-change-repo
</code></pre>
<p>屏幕下方的上下左右选中修改 <code>Main Repo</code>，然后在二级菜单中选中 <code>tuna</code> 字样的源（清华源）</p>
<blockquote>
<p>使用代理就不需要设置</p>
</blockquote>
<p><strong>获取 root 权限（可选）：</strong></p>
<pre><code>su
</code></pre>
<p>会弹出授权提示框，允许即可。</p>
<p><strong>安装 ssh 套件：</strong></p>
<pre><code>pkg install openssh
</code></pre>
<p>如果安装后使用 <code>ssh</code> 指令报错，请继续安装 <code>openssl</code></p>
<pre><code>pkg install openssl
</code></pre>
<p><strong>安装 vim：</strong></p>
<pre><code>pkg install vim
</code></pre>
<p><strong>访问 Android 外置存储：</strong></p>
<pre><code>pkg upgrade
# Android &gt;= 11 务必执行这一步
pkg install termux-am
termux-setup-storage
</code></pre>
<p>完成后 <code>~/</code> 目录下会多出一个 <code>storage</code> 文件夹，里面有几个软链接，分别链接到相册，下载等路径，其中的 <code>~/storage/shared</code> 对应 <code>storage/emulated/0/</code>。</p>
<h2>修改字体</h2>
<p>对于使用了 prompt 的 shell，往往需要额外字体的支持（例如 <a href="https://www.nerdfonts.com/font-downloads">NerdFont</a>）。下面说明如何更换 Termux 的字体。</p>
<ol>
<li>下载所需要的字体文件，例如 <code>FiraCode.ttf</code></li>
<li>将该字体重命名为 <code>font.ttf</code> 并上传到 <code>/data/data/com.termux/files/home/.termux/font.ttf</code></li>
<li>重启 Termux 或者新建 session 即可生效</li>
</ol>
<h2>注意事项</h2>
<blockquote>
<p>仅针对 Android 14</p>
</blockquote>
<ol>
<li>termux 作为安卓 App，默认只能访问自有路径，即 <code>/data/data/com.termux/files/home</code>，等价于 Linux 下的 <code>/home/user</code>，故显示为 <code>~</code>。而 <code>/</code> 作为系统根路径，无 root 权限时无法访问。</li>
<li>安卓外置存储，或者说常规模式下用户可以访问的路径，从 <code>/storage/emulated/0</code> 开始，其 shortcut 为 <code>/sdcard</code></li>
</ol>
]]></content>
    <author><name>Sun2ot</name></author>
    <category term="教程"/>
  </entry>
  <entry>
    <title>智能优化算法1：遗传算法</title>
    <link href="https://blog.085404.xyz/posts/%E9%81%97%E4%BC%A0%E7%AE%97%E6%B3%95/" rel="alternate" type="text/html"/>
    <id>https://blog.085404.xyz/posts/%E9%81%97%E4%BC%A0%E7%AE%97%E6%B3%95/</id>
    <published>2024-06-14T00:00:00.000Z</published>
    <updated>2024-06-14T00:00:00.000Z</updated>
    <summary></summary>
    <content type="html"><![CDATA[<h1>智能优化算法 1：遗传算法</h1>
<h2>考试范围</h2>
<ul>
<li>五个优化算法（遗传、蚁群、粒子群、退火 + 自定义）的<strong>基本原理</strong>（数学公式，解释清楚）和<strong>算法过程</strong>（第一步、第二步...），包含老师讲的四个，以及另外一个随便的。<code>15分</code></li>
<li>给出一个用智能优化算法解决本领域的案例</li>
<li>给一个问题（他讲过的：退火解决调度问题，粒子群解 TSP 问题，遗传算法解 01 背包），给出智能优化算法求解该问题的过程</li>
</ul>
<h2>遗传算法</h2>
<h3>基本原理</h3>
<p>遗传算法是一种模拟自然进化过程的优化方法，它从一个称为种群的候选解集合开始，通过不断地产生新的种群来寻找更优的解。</p>
<p>遗传算法的核心是<strong>选择、交叉和变异</strong>三种操作。</p>
<ul>
<li>选择是根据解的适应度来从旧种群中挑选出一些解作为双亲，适应度越高，被选中的概率越大。</li>
<li>交叉是将两个双亲的部分特征进行组合，产生新的解作为后代。</li>
<li>变异是对某些解的某些特征进行随机改变，增加种群的多样性。</li>
</ul>
<p>这个迭代产生新种群的过程一直持续到遗传算法达到预设的终止条件为止。这就是遗传算法的基本原理。</p>
<p><img src="http://img.085404.xyz/images/69dbedc56fc450e89197e6e489ea6149.webp" alt="image.png" /></p>
<p>对每个个体都进行上述的优化，然后择优录取。</p>
<p>选择、交叉和变异的方式多种多样。以选择为例：</p>
<ul>
<li><strong>轮盘赌法</strong>：将个体适应度大小映射为概率进行复制，适应度高的个体有更大概率复制，且复制的份数越多</li>
<li><strong>精英产生精英</strong>：对适应度高的前 N/4 的个体进行复制，然后用这些个体把后 N/4 的个体替换掉</li>
<li>选择个体时，不一定要是有明确指标的，随机的方式也可以</li>
<li>甚至不一定总是适应度高的替换低的，用低的随机替换高的也行</li>
<li>......</li>
</ul>
<p>综上所述，给出遗传算法的主要步骤：</p>
<ol>
<li>产生初始群体。</li>
<li>计算每个个体适应度函数值。</li>
<li>利用轮盘赌算法选择进入下一代群体中的个体。</li>
<li>两两配对的个体进行交叉操作以产生新个体。</li>
<li>新个体进行变异操作。</li>
<li>将群体中迄今出现的最好个体直接复制到下一代中（精英保留策略）。</li>
<li>反复执行步骤 2 到步骤 6,直到满足算法终止条件</li>
</ol>
<h3>遗传算法解 01 背包</h3>
<p><a href="https://github.com/sun2ot/algorithm-learning/blob/master/ioa/GA.py">代码实现</a></p>
<blockquote>
<p>遗传算法是一种启发式的搜索方法，通常无法保证找到全局最优解。它通过模拟自然选择和遗传变异来逐步改进解，但因为它依赖随机过程，因此<strong>每次运行可能会得到不同的结果</strong>。为了增加找到最优解的概率，可以多次运行算法，并选择其中最好的结果。</p>
</blockquote>
<p>在 01 背包问题中，因为物品本身有 n 种，所以染色体长度设为 n 即可，天然属于二进制编码。</p>
<p><strong>产生初始群体</strong></p>
<p>例如物品列表如下：</p>
<table>
<thead>
<tr>
<th>item</th>
<th>weight</th>
<th>value</th>
</tr>
</thead>
<tbody>
<tr>
<td>三级头</td>
<td>15</td>
<td>11</td>
</tr>
<tr>
<td>三级甲</td>
<td>18</td>
<td>12</td>
</tr>
<tr>
<td>机枪</td>
<td>17</td>
<td>9</td>
</tr>
</tbody>
</table>
<p>那么对于以下两种选择方案：</p>
<table>
<thead>
<tr>
<th>item</th>
<th>weight</th>
<th>value</th>
</tr>
</thead>
<tbody>
<tr>
<td>三级头</td>
<td>15</td>
<td>11</td>
</tr>
<tr>
<td>三级甲</td>
<td>18</td>
<td>12</td>
</tr>
<tr>
<td>总计</td>
<td>33</td>
<td>23</td>
</tr>
</tbody>
</table>
<table>
<thead>
<tr>
<th>item</th>
<th>weight</th>
<th>value</th>
</tr>
</thead>
<tbody>
<tr>
<td>三级头</td>
<td>15</td>
<td>11</td>
</tr>
<tr>
<td>机枪</td>
<td>17</td>
<td>9</td>
</tr>
<tr>
<td>总计</td>
<td>32</td>
<td>20</td>
</tr>
</tbody>
</table>
<p>可分别表示为染色体 <code>A = [110]</code> 和 <code>B = [101]</code>，其中 1 表示选中对应物品。假设背包容量为 40，对于上述两个染色体，A 的适应度就高于 B（因为 value 更大）。</p>
<blockquote>
<p>注意：这里的初始种群个数的选择要视具体情况而定，如果初始数量太少可能会导致在向量空间中覆盖面积过小而导致收敛到了非最优解就终止了算法。</p>
</blockquote>
<hr />
<p><strong>选择</strong></p>
<ol>
<li>计算 n 个物品的<strong>适应度值</strong> $f_i$</li>
<li>计算出轮盘赌选择概率
$$
p_i=f_i/\sum f_i=f_i/f_{sum}
$$</li>
<li>根据概率选择出下一代种群（注意判断背包容量）</li>
</ol>
<hr />
<p><strong>交叉/变异</strong></p>
<p>交叉方式有很多种，这里可以选择一个交叉点位置，然后从此处断开，两者交换即可。变异的话可以随机选择一个点位变异（取反）。</p>
]]></content>
    <author><name>Sun2ot</name></author>
    <category term="笔记"/>
  </entry>
  <entry>
    <title>nvidia-smi</title>
    <link href="https://blog.085404.xyz/posts/nvidia-smi/" rel="alternate" type="text/html"/>
    <id>https://blog.085404.xyz/posts/nvidia-smi/</id>
    <published>2024-05-17T00:00:00.000Z</published>
    <updated>2024-05-17T00:00:00.000Z</updated>
    <summary></summary>
    <content type="html"><![CDATA[<h1>nvidia-smi</h1>
<h2>介绍与使用</h2>
<p>nvidia-smi，全称为 NVIDIA System Management Interface，是一个跨平台的命令行工具，<strong>用于监控和管理 NVIDIA GPU 的状态和性能</strong>。它可以显示 GPU 的详细信息，包括型号、驱动版本、内存使用情况、温度、功耗等。此外，它还可以用于控制 GPU 的行为，如调整频率、设置风扇转速等。</p>
<p>在日常的 DL 过程中，应首先关注 GPU 资源的使用情况，否则当资源不足时可能出现 <code>cuda out of memory</code> 等错误。</p>
<p><code>nvidia-smi</code> 的使用方式就是其本身：</p>
<pre><code>nvidia-smi
</code></pre>
<p>显示信息如下：</p>
<pre><code>Fri May 17 16:16:12 2024       
+-----------------------------------------------------------------------------+
| NVIDIA-SMI 470.129.06   Driver Version: 470.129.06   CUDA Version: 11.4     |
|-------------------------------+----------------------+----------------------+
| GPU  Name        Persistence-M| Bus-Id        Disp.A | Volatile Uncorr. ECC |
| Fan  Temp  Perf  Pwr:Usage/Cap|         Memory-Usage | GPU-Util  Compute M. |
|                               |                      |               MIG M. |
|===============================+======================+======================|
|   0  NVIDIA TITAN V      Off  | 00000000:2D:00.0  On |                  N/A |
| 28%   43C    P8    30W / 250W |     16MiB / 12058MiB |      0%      Default |
|                               |                      |                  N/A |
+-------------------------------+----------------------+----------------------+
|   1  NVIDIA TITAN V      Off  | 00000000:99:00.0 Off |                  N/A |
| 28%   42C    P8    24W / 250W |      5MiB / 12066MiB |      0%      Default |
|                               |                      |                  N/A |
+-------------------------------+----------------------+----------------------+
                                                                               
+-----------------------------------------------------------------------------+
| Processes:                                                                  |
|  GPU   GI   CI        PID   Type   Process name                  GPU Memory |
|        ID   ID                                                   Usage      |
|=============================================================================|
|    0   N/A  N/A      2367      G   /usr/lib/xorg/Xorg                 14MiB |
|    1   N/A  N/A      2367      G   /usr/lib/xorg/Xorg                  4MiB |
+-----------------------------------------------------------------------------+
</code></pre>
<p>从上至下分为两个表格，先看<strong>下面的表格</strong>，主要显示占用 GPU 资源的进程信息，从左到右分别为：</p>
<ul>
<li><code>GPU</code>：GPU 编号，与数组下标类似，从 0 开始</li>
<li><code>GI ID</code>： GPU 实例 ID（用于 MIG 模式），在非 MIG 模式下为 <code>N/A</code></li>
<li><code>CI ID</code>：计算实例 ID（用于 MIG 模式），在非 MIG 模式下为 <code>N/A</code></li>
<li><code>PID</code>：进程 ID</li>
<li><code>Type</code>：进程类型
<ul>
<li><code>G</code>：表示图形进程</li>
<li><code>C</code>：表示计算进程</li>
</ul>
</li>
<li><code>Process name</code>：进程的名称和路径</li>
<li><code>GPU Memory Usage</code>：进程占用 GPU 的内存量</li>
</ul>
<blockquote>
<p>[!note] 总结
通常情况下，根据 Processes 表中的信息，即可判断出：谁的哪个进程占用了某张卡的多少资源。还可以在<s>拳头够大</s>友好交流的前提下，根据 PID 杀死该进程。</p>
</blockquote>
<hr />
<p>再来<strong>看上面的表格</strong>，首先是第一行：</p>
<ul>
<li><code>NVIDIA-SMI</code>：工具版本</li>
<li><code>Driver Version</code>：驱动版本</li>
<li><code>CUDA Version</code>：Driver API 对应的 CUDA 版本（这是一个略复杂的问题，有空再写）</li>
</ul>
<p><strong>第二行是表头</strong>，的每个格子内分为若干行，每行有若干字段。这个表格的正确读取方式为：<strong>每个单词都代表一个独立值</strong>。例如第二行第一个格子内实际上有 7 个值，分别为：</p>
<ul>
<li><code>GPU</code>：GPU 编号</li>
<li><code>Name</code>：GPU 名称</li>
<li><code>Persistence-M</code>：是否开启持久模式</li>
<li><code>Fan</code>：GPU 风扇的当前速度百分比</li>
<li><code>Temp</code>：GPU 的当前温度（摄氏度）</li>
<li><code>Perf</code>：当前的性能状态
<ul>
<li><code>P8</code>：表示<strong>最低</strong>功耗状态</li>
<li><code>P0</code>：表示<strong>最高</strong>性能状态</li>
</ul>
</li>
<li><code>Pwr:Usage/Cap</code>：当前功耗和最大功耗（瓦特）</li>
</ul>
<p>第二行第二列：</p>
<ul>
<li><code>Bus-Id</code>：GPU 在 PCIe 总线上的标识符</li>
<li><code>Disp.A</code>：显示设备是否正在使用 GPU（<code>on/off</code>）</li>
<li><code>Memory-Usage</code>：GPU 内存使用情况</li>
</ul>
<p>第二行第三列：</p>
<ul>
<li><code>Volatile Uncorr. ECC</code>：易失性不可纠正错误计数</li>
<li><code>GPU-Util</code>：GPU 利用率百分比</li>
<li><code>Compute M.</code>：计算模式
<ul>
<li><code>Default</code>：默认计算模式</li>
<li><code>Exclusive Thread</code>：每个 GPU 只能被一个线程访问</li>
<li><code>Prohibited</code>：禁止使用 CUDA</li>
</ul>
</li>
<li><code>MIG M.</code>：多实例 GPU（MIG）模式</li>
</ul>
<h2>Memory-Usage 与 GPU-Util</h2>
<p>以上表为例，不难发现 <code>Memory-Usage</code> 存在 GPU 内存占用时，<code>GPU-Util</code> 百分比为 0。</p>
<p>因为当前 GPU 并没有进行计算任务（如深度学习训练或推理），但仍然有内存被分配和占用。</p>
<h2>👻幽灵占用</h2>
<p>“幽灵占用”问题与上一节类似，主要体现在 <code>Memory-Usage</code> 大量占用，但不仅 <code>GPU-Util</code> 百分比为 0，而且 Processes 表中没有进程或进程占用量与上面显示的不符。</p>
<blockquote>
<p>[!tip]
“幽灵占用”这名字是我随口起的，不确定这个问题的学名叫什么。。。</p>
</blockquote>
<p>这种情况多是由于用户运行了一些自己都不知道是什么玩意的代码，自然也就不知道产生的进程究竟有没有结束，见下图：</p>
<p><img src="https://img.085404.xyz/images/0b6471356cb82e36c38d27ec6ce45711.webp" alt="Linus-garbage-code.webp" /></p>
<p>针对此类问题，可以尝试以下几步：</p>
<pre><code># 先查看哪些进程在使用 GPU 设备文件
sudo fuser -v /dev/nvidia*
</code></pre>
<p>如果能找到可疑进程，直接根据输出的 PID 杀死即可。</p>
<p>如果这一招也不行，那就采用一个更无脑但有效的方案：</p>
<pre><code>ps aux | grep python
</code></pre>
<p>直接找 Python 相关的进程，一个个杀。</p>
]]></content>
    <author><name>Sun2ot</name></author>
    <category term="教程"/>
  </entry>
  <entry>
    <title>高级 GPU 资源监控 nvitop</title>
    <link href="https://blog.085404.xyz/posts/%E9%AB%98%E7%BA%A7-gpu-%E8%B5%84%E6%BA%90%E7%9B%91%E6%8E%A7-nvitop/" rel="alternate" type="text/html"/>
    <id>https://blog.085404.xyz/posts/%E9%AB%98%E7%BA%A7-gpu-%E8%B5%84%E6%BA%90%E7%9B%91%E6%8E%A7-nvitop/</id>
    <published>2024-05-17T00:00:00.000Z</published>
    <updated>2024-05-17T00:00:00.000Z</updated>
    <summary></summary>
    <content type="html"><![CDATA[<h1>高级 GPU 资源监控 nvitop</h1>
<h2>介绍</h2>
<p><a href="https://github.com/XuehaiPan/nvitop">nvitop</a>：交互式的 NVIDIA-GPU 监控。略多版介绍可见 <a href="https://docs.hpc.sjtu.edu.cn/app/compilers_and_languages/nvitop.html">上海交大超算平台用户手册-nvitop</a>。</p>
<h2>部署</h2>
<p>请务必使用 root 权限进行全局安装，否则会被安装到 conda 隔离环境中。</p>
<pre><code>sudo pip3 install --upgrade nvitop
</code></pre>
<h2>参数列表</h2>
<blockquote>
<p>[!tip] 只列出的部分常用参数，具体见官方文档，或者 <code>nvitop --help</code></p>
</blockquote>
<pre><code>可选参数:
  --help, -h            帮助文档
  --version, -V         查看版本号
  --once, -1            只显示一次信息
  --monitor [{auto,full,compact}], -m [{auto,full,compact}]
                        持续监控,如果省略-m参数,默认采用auto
  --interval SEC        进程状态更新间隔(单位: 秒) (默认: 2s)

颜色:
  --colorful            使用渐变色显示条形图
  --light               使用亮色主题显示(NVITOP_MONITOR_MODE="light")

设备过滤:
  --only idx [idx ...], -o idx [idx ...]
                        只显示指定设备
  --only-visible, -ov   只显示 `CUDA_VISIBLE_DEVICES` 环境变量对应的设备

进程过滤:
  --compute, -c         显示具有计算上下文的GPU进程 (type: 'C' or 'C+G')
  --only-compute, -C    显示只具有计算上下文的GPU进程 (type: 'C' only)
  --graphics, -g        显示具有图形处理上下文的GPU进程 (type: 'G' or 'C+G')
  --only-graphics, -G   显示只具有图形处理上下文的GPU进程 (type: 'G' only)
  --user [USERNAME [USERNAME ...]], -u [USERNAME [USERNAME ...]]
                        显示指定用户的进程 (or `$USER` for no argument).
  --pid PID [PID ...], -p PID [PID ...]
                        显示置顶PID的进程
</code></pre>
<h2>快捷键</h2>
<p>默认情况下，调用 <code>nvitop</code> 会进入 monitor 模式，类似于 <code>top</code>，而 <code>nvitop -1</code> 才是与原版 <code>nvidia-smi</code> 类似的单次显示。</p>
<p>在 monitor 模式下，存在一些快捷键：</p>
<blockquote>
<p>太多了，只摘取了一些常用的，懒得翻译了，就这样吧</p>
</blockquote>
<table>
<thead>
<tr>
<th>Key</th>
<th>Binding</th>
</tr>
</thead>
<tbody>
<tr>
<td><code>q</code></td>
<td>Quit and return to the terminal.</td>
</tr>
<tr>
<td><code>h</code> / <code>?</code></td>
<td>Go to the help screen.</td>
</tr>
<tr>
<td><code>a</code> / <code>f</code> / <code>c</code></td>
<td>Change the display mode to <em>auto</em> / <em>full</em> / <em>compact</em>.</td>
</tr>
<tr>
<td><code>r</code> / <code>&lt;C-r&gt;</code> / <code>&lt;F5&gt;</code></td>
<td>Force refresh the window.</td>
</tr>
<tr>
<td><code>&lt;Up&gt;</code> / <code>&lt;Down&gt;</code></td>
<td>Select and highlight a process.</td>
</tr>
<tr>
<td><code>&lt;Left&gt;</code> / <code>&lt;Right&gt;</code></td>
<td>Scroll the host information of processes.</td>
</tr>
<tr>
<td><code>&lt;Home&gt;</code></td>
<td>Select the first process.</td>
</tr>
<tr>
<td><code>&lt;End&gt;</code></td>
<td>Select the last process.</td>
</tr>
<tr>
<td><code>&lt;C-a&gt;</code>  &lt;br&gt;<code>^</code></td>
<td>Scroll left to the beginning of the process entry (i.e. beginning of line).</td>
</tr>
<tr>
<td><code>&lt;C-e&gt;</code>  &lt;br&gt;<code>$</code></td>
<td>Scroll right to the end of the process entry (i.e. end of line).</td>
</tr>
<tr>
<td><code>&lt;PageUp&gt;</code> / <code>&lt;PageDown&gt;</code></td>
<td>scroll entire screen (for large amounts of processes).</td>
</tr>
<tr>
<td><code>&lt;Space&gt;</code></td>
<td>Tag/untag current process.</td>
</tr>
<tr>
<td><code>&lt;Esc&gt;</code></td>
<td>Clear process selection.</td>
</tr>
<tr>
<td><code>&lt;C-c&gt;</code>  &lt;br&gt;<code>I</code></td>
<td>Send <code>signal.SIGINT</code> to the selected process (interrupt).</td>
</tr>
<tr>
<td><code>T</code></td>
<td>Send <code>signal.SIGTERM</code> to the selected process (terminate).</td>
</tr>
<tr>
<td><code>K</code></td>
<td>Send <code>signal.SIGKILL</code> to the selected process (kill).</td>
</tr>
<tr>
<td><code>e</code></td>
<td>Show process environment.</td>
</tr>
<tr>
<td><code>t</code></td>
<td><strong>Toggle tree-view screen.</strong></td>
</tr>
<tr>
<td><code>&lt;Enter&gt;</code></td>
<td>Show process metrics.</td>
</tr>
<tr>
<td><code>,</code> / <code>.</code></td>
<td>Select the sort column.</td>
</tr>
<tr>
<td><code>/</code></td>
<td>Reverse the sort order.</td>
</tr>
</tbody>
</table>
]]></content>
    <author><name>Sun2ot</name></author>
    <category term="教程"/>
  </entry>
  <entry>
    <title>麻雀搜索算法</title>
    <link href="https://blog.085404.xyz/posts/%E9%BA%BB%E9%9B%80%E6%90%9C%E7%B4%A2%E7%AE%97%E6%B3%95/" rel="alternate" type="text/html"/>
    <id>https://blog.085404.xyz/posts/%E9%BA%BB%E9%9B%80%E6%90%9C%E7%B4%A2%E7%AE%97%E6%B3%95/</id>
    <published>2024-05-16T00:00:00.000Z</published>
    <updated>2024-05-16T00:00:00.000Z</updated>
    <summary></summary>
    <content type="html"><![CDATA[<h1>麻雀搜索算法</h1>
<h2>sparrow search algorithm</h2>
<pre><code>XUE J, SHEN B. A novel swarm intelligence optimization approach: sparrow search algorithm[J]. Systems Science &amp; Control Engineering, 2020, 8(1): 22-34.
</code></pre>
<h3>Abstract</h3>
<blockquote>
<p>[!quote] 受麻雀群体智慧、觅食和反捕食行为的启发，本文提出了一种新的群体优化方法，即麻雀搜索算法（SSA）。在19个基准函数上进行了实验，测试了SSA的性能，并将其与灰狼优化器（GWO）、引力搜索算法（GSA）和粒子群优化（PSO）等其他算法的性能进行了比较。仿真结果表明，所提出的SSA在精度、收敛速度、稳定性和鲁棒性方面优于GWO、PSO和GSA。最后，通过两个工程实例验证了所提出的SSA的有效性。</p>
</blockquote>
<h3>Introduction</h3>
<ol>
<li>受麻雀种群觅食和反捕食行为的启发，提出了一种新的SI技术，即SSA；</li>
<li>通过使用所提出的SSA，在一定程度上提高了优化搜索空间的探索和利用率；</li>
<li>所提出的SSA在两个实际工程问题中得到了成功的应用。</li>
</ol>
<h4>Biological characteristics</h4>
<p>麻雀通常是群居鸟类，种类繁多。它们分布在世界大部分地区，喜欢生活在人类生活的地方。此外，它们是杂食性鸟类，主要以谷物或杂草的种子为食。众所周知，麻雀是常见的留鸟。与许多其他小鸟相比，麻雀非常聪明，记忆力也很强。请注意，有两种不同类型的圈养麻雀，既有<strong>生产者</strong>，也有<strong>觅食者</strong>。生产者积极寻找食物来源，而觅食者则由生产者获取食物。此外，有证据表明，鸟类通常灵活地使用行为策略，并<strong>在生产和觅食之间切换</strong>。也可以说，为了寻找食物，麻雀通常使用生产者和觅食者的策略。</p>
<p>研究表明，<strong>个体会监控群体中其他人的行为</strong>。同时，鸟群中的攻击者想要提高自己的捕食率，他们被用来与高摄入量的同伴争夺食物资源。此外，当麻雀选择不同的觅食策略时，个体的能量储备可能会发挥重要作用，而<strong>能量储备低的麻雀会觅食更多</strong>。值得一提的是，这些位于<strong>种群边缘的鸟类更容易受到捕食者的攻击，并不断试图获得更好的位置</strong>。位于中心的动物可能会向邻居靠近，以尽量减少其危险范围。我们也知道，所有的麻雀都表现出对一切事物好奇的本能，同时它们总是保持警惕。例如，当<strong>一只鸟确实探测到捕食者时</strong>，一个或多个个体发出啁啾声，整个群体就会飞走。</p>
<h4>Mathematical model and algorithm</h4>
<p>根据之前对麻雀的描述，我们可以建立数学模型来构建麻雀搜索算法。为了简单起见，我们将麻雀的以下行为理想化，并制定了相应的规则：</p>
<ol>
<li>生产者通常拥有高水平的能量储备，并<strong>为所有觅食者提供觅食区域或方向</strong>。它负责确定可以找到丰富食物来源的地区。能量储备的水平取决于对个体适应度值的评估。</li>
<li>一旦麻雀发现捕食者，<strong>个体就会开始鸣叫</strong>，发出警报信号。当警报值大于安全阈值时，生产者需要将所有搜寻者<strong>带到安全区域</strong>。</li>
<li>只要寻找更好的食物来源，每只麻雀都可以成为生产者，但生产者和觅食者在整个种群中的<strong>比例没有变化</strong>。</li>
<li><strong>能量较高的麻雀将充当生产者</strong>。为了获得更多的能量，一些饥饿的觅食者更有可能飞往其他地方觅食。</li>
<li>觅食者<strong>跟随</strong>能够提供最好食物的生产者寻找食物。与此同时，一些觅食者可能会不断<strong>监视</strong>生产者并争夺食物，以提高自己的捕食率。</li>
<li>处于<strong>群体边缘</strong>的麻雀在意识到危险时迅速向安全区域移动以获得更好的位置，而处于<strong>群体中间</strong>的麻雀则随机行走以靠近他人。</li>
</ol>
<p>在模拟实验中，我们需要使用虚拟麻雀来寻找食物。<em>麻雀的位置</em>可以用以下矩阵表示：</p>
<p><img src="https://img.085404.xyz/images/728e713f4aba14ff258fc0ccf50164f2.webp" alt="image.png" /></p>
<p>其中 n 是麻雀的数量，d 表示要优化的变量的维度。那么，<em>所有麻雀的适应度值</em>可以用以下向量表示：</p>
<p><img src="https://img.085404.xyz/images/affd6e0663aecd5ddadf73befa2dd2cb.webp" alt="image.png" /></p>
<p>其中 n 表示麻雀的数量，$F_X$ 中每一行的值表示个体的适应度值。在SSA中，具有<strong>更好适应度值的生产者在搜索过程中优先获得食物</strong>。此外，因为生产者负责寻找食物和引导整个“鸟口”的流动。所以，生产者可以在比觅食者更广泛的地方寻找食物。根据规则（1）和（2），在每次迭代过程中，<em>生产者的位置更新</em>如下：</p>
<p><img src="https://img.085404.xyz/images/33707ecfba585be244ab12196b4d1bd9.webp" alt="image.png" /></p>
<p>其中t表示当前迭代，$j=1,2,...,d.X^t_{i,j}$表示迭代t时第i个麻雀的第j个维度的值。$iter_{max}$是迭代次数最多的常数。$\alpha \in (0,1]$是一个随机数。$R_2 \in [0,1]$和$ST \in [0.5,1.0]$分别表示报警值和安全阈值。Q 是服从正态分布的随机数。L表示$1 \times d$ 的矩阵，其中内部的每个元素都是1。</p>
<p>当$R2 \lt ST$时，这意味着周围没有捕食者，组群会逐渐聚集，如下图。如果$R2 \ge ST$，则意味着一些麻雀已经发现了捕食者，所有麻雀都需要迅速飞往其他安全区域，即<strong>按正态分布随机移动到当前位置附近</strong>。</p>
<p><img src="https://img.085404.xyz/images/309ffdfc529f62fb544f0eb1d8a6856e.webp" alt="image.png" /></p>
<p>至于觅食者，他们需要执行规则（4）和（5）。如上所述，一些觅食者更频繁地监视生产者。一旦他们发现生产者找到了好的食物，他们就会立即离开目前的位置去争夺食物。如果他们赢了，他们可以立即获得生产者的食物，否则他们将继续执行规则（5）。<em>觅食者的位置更新</em>公式如下所述：</p>
<p><img src="https://img.085404.xyz/images/a85d513b27c484d8e3fea0a18a124727.webp" alt="image.png" /></p>
<p>其中$X_P$是生产者所占据的最佳位置。$X_{worst}$表示当前全局最差位置。A 表示一个 $1 \times d$ 的矩阵，其中每个元素随机分配1或−1，$A^+=A^T(AA^T)^{-1}$。当$i \gt n/2$时，这表明适应度值较差的第 i 个觅食者最有可能正在挨饿。</p>
<blockquote>
<p>[!quote]
从公式可以看出，若i&gt;n/2时，其值为一个标准正态分布随机数与一个以自然对数为底数的指数函数的积，当种群收敛时其取值符合标准正态分布随机数。 (其值会收敛于0)。</p>
<p>若i&lt;=n/2时，其取值为当前最优的麻雀的位置加上该麻雀与最优位置每一维距离随机加减后，将总和均分到每一维上。过程可以描述为在当前最优位置附近随机找一个位置，且每一维距最优位置的方差将会变得更小，即不会出现在某一维上与最优位置相差较大，而其他位置相差较小。（其值收敛于最优位置）。</p>
</blockquote>
<blockquote>
<p>[!tip]
在麻雀搜索算法中，觅食者是指除了生产者（producer）之外的其他麻雀。在觅食者位置更新的逻辑中，(i &gt; n/2) 和 otherwise 分别对应了两种情况，表示了觅食者在更新位置时的不同策略：</p>
<ol>
<li>
<p>当 (i &gt; n/2) 时：这表示觅食者的编号 (i) 大于种群大小 (n) 的一半。在这种情况下，觅食者更倾向于向生产者靠近，以便更好地利用生产者的信息和种群中心的指引。因此，觅食者在更新位置时会采取更加向种群中心和生产者靠拢的策略，以加速搜索过程。</p>
</li>
<li>
<p>当 (i &lt;= n/2) 时（即 otherwise）：这表示觅食者的编号 (i) 小于等于种群大小 (n) 的一半。在这种情况下，觅食者可能更倾向于探索种群中的其他区域，而不是过于依赖生产者和种群中心的信息。因此，觅食者在更新位置时可能会更多地进行探索，以寻找潜在的更优解。</p>
</li>
</ol>
<p>总体逻辑是，在麻雀搜索算法中，觅食者根据自身的编号与种群大小的关系，调整其更新位置的策略。这种策略旨在平衡利用生产者和种群中心的信息，同时保持一定的探索性，以更有效地搜索解空间中的潜在解。</p>
</blockquote>
<p><strong>侦查预警行为</strong></p>
<p>在模拟实验中，我们假设这些意识到危险的麻雀占总种群的10%到20%。这些麻雀的初始位置是在种群中随机产生的。根据规则（6），该数学模型可以表示如下：</p>
<p><img src="https://img.085404.xyz/images/d8d4f618268288a7fd8af025973b4f2c.webp" alt="image.png" /></p>
<p>其中$X_{best}$是当前的全局最优位置。$\beta$是步长控制参数，$\varepsilon$ 是一个平均值为0且方差为1的服从正态分布的随机数。$K \in [−1, 1]$是一个随机数。这里 $f_i$ 是当前麻雀的适应度值。$f_g$和$f_w$分别是当前全局最佳和最差适应度值。$\epsilon$是最小的常数，以避免零除法误差。</p>
<p>为了简单起见，当$f_i \gt f_g$表示麻雀处于组的边缘时。$X_{best}$代表种群中心的位置，在它周围是安全的。$f_i = f_g$表明，处于种群中间的麻雀意识到了危险，需要靠近其他麻雀。$K$表示麻雀移动的方向，也是步长控制参数。</p>
<blockquote>
<p>[!tip] 关于 $f_i, f_g$ 大小关系的含义
在麻雀搜索算法中，$f_i$表示第$i$只麻雀的适应度值，$f_g$表示种群中适应度值最高的麻雀的适应度值。</p>
<p>当某只麻雀的适应度值$f_i$<strong>大于</strong>种群中适应度值最高的麻雀的适应度值$f_g$时，这意味着该麻雀的适应度比整个种群中的其他麻雀都要高，因此该麻雀被认为处于种群的边缘，即在搜索空间中处于一个比较优越的位置，可能是一个局部最优解或者接近全局最优解。</p>
<p>当某只麻雀的适应度值$f_i$<strong>等于</strong>种群中适应度值最高的麻雀的适应度值$f_g$时，这表示该麻雀的适应度与整个种群中适应度最高的麻雀的适应度相同，即达到了种群的最高适应度水平。在这种情况下，可以理解为中心麻雀意识到了危险，因为虽然它的适应度与种群中最好的麻雀相同，但仍然可能存在更好的解决方案，因此需要继续搜索以寻找可能的更优解。</p>
</blockquote>
<p>基于上述模型的理想化和可行性，SSA的基本步骤可以概括为算法1中所示的伪代码。</p>
<p><img src="https://img.085404.xyz/images/34347d39c77bbda4953b40b71fb8b074.webp" alt="image.png" /></p>
<p><img src="https://img.085404.xyz/images/268d8382ad32691b314b6a0e3458f2af.webp" alt="image.png" /></p>
<h2>应用</h2>
<pre><code>WANG J, HAO S, ZHAO G. CSSA-based collaborative optimization recommendation of users in mobile crowdsensing[J]. PEER-TO-PEER NETWORKING AND APPLICATIONS, 2023, 16(2): 803-817.
</code></pre>
<h3>Abstract</h3>
<p>移动人群感知（MCS）作为一种新的感知范式，将人工智能与物联网相结合，利用具有智能设备（包括手机、智能汽车、可穿戴设备等）的移动用户来执行任务请求者请求的感知任务。移动用户固有的移动性使移动众感成为一个通用的平台，可以取代或补充现有的静态感知基础设施[1]。通过这种数据采集，MCS在交通监测[2]、环境现象观测[3]、移动医疗[4]等领域发挥着举足轻重的作用。传感系统由平台、任务发布者和参与者用户三部分组成。</p>
<p>任务发布者可以是一台机器，也可以是一个人。参与感测任务需要参与者的时间以及设备的感测、计算和通信资源。为了刺激广泛的用户参与，一些MCS系统为用户提交的有价值的传感器报告提供奖励[5]。激励可以采取不同的形式[6]，包括金钱、通过游戏获得的娱乐或系统提供的服务。这些任务预算分配给根据传感数据花费的时间或提供的传感报告的质量完成传感任务的参与者[7]。因此，为了提高感知任务的数据质量[8，9]并降低任务成本[10-12]，选择合适的用户是一个非常重要的先决条件。</p>
<p>移动人群感知系统通过用户参与任务，利用移动感知设备采集上传到平台的数据，完成数据采集。如何正确的用户，对于完成整个任务有着关键的影响。在之前的任务分配中，用户选择过程是通过分析用户属性与任务之间的匹配度来完成的。通常主要目标是提高感知数据的质量，用户选择基于可信用户选择[13]、质量推理方法[8]、用户信息质量分析[14]等方法。这些方法适用于收集单一数据类型的感知任务。当传感任务需要各种传感数据时，例如城市环境的数据收集，包括温度、湿度、图像和其他信息，并且用户设备上的传感器类型不同。与数据采集位置的距离不同，因此很难找到多个合适的用户来完成任务。</p>
<p>面对复杂的任务需求，仅考虑单个用户选择的优化很难满足需求。简单地增加具有相似属性的用户数量不仅会增加成本，还会造成数据冗余。因此，针对在这种复杂环境中寻找最优用户选择方案的问题[15]，本文提出了一种新的感知任务用户选择框架——群感知麻雀搜索算法（CSSA）。分析了用户属性与任务之间的拟合程度，如图1所示。通过多个用户之间的协作，由他们自己的传感设备收集不同的数据，共同完成传感任务。为了解决复杂环境中的优化问题，智能优化算法中的麻雀搜索算法（SSA）[16]被用于解决该问题。该算法具有搜索精度高、收敛速度快、稳定性强的特点。</p>
<p>本文的贡献如下：</p>
<ol>
<li>本文考虑了用户不同因素对任务的影响，提出了用户适应度的概念，通过计算用户的位置、设备状态和用户的历史信誉[17]，分析了用户与任务的匹配程度、用户上传数据的质量可靠性，并根据对用户对任务优先级的分析，认为影响因素越全面，结论越可靠。</li>
<li>本文提出的方法是基于麻雀搜索算法。利用用户的适应度来划分用户的身份，然后通过混沌序列映射来初始化用户群体。模拟了麻雀搜索算法的迭代优化过程，求解了最优用户选择方案。与其他群智能优化算法相比，它具有优化能力强、收敛速度快的优点。</li>
<li>基于任务所需的不同类型的感知数据，采用了多用户协作的方法。每个用户使用当前设备的传感器收集传感数据，然后将多个用户上传的信息进行整合，完成传感任务。与非合作用户相比，结果数据质量更高。</li>
</ol>
<p><img src="https://img.085404.xyz/images/9b935bfa433d5171dac5c00f892da49e.webp" alt="image.png" /></p>
<h3>Related work</h3>
<p>任务分配是MCS的一个主要研究方向。它收集来自移动用户的感知数据，并获得感知结果以完成任务。一般来说，影响任务质量的因素有很多，包括数据收集量、任务持续时间和任务空间-时间覆盖率[18]。收集的传感器数据越多，任务的质量就越高。适当的任务空间-时间覆盖通常也意味着更高的任务质量。影响任务成本的因素还有很多[19]，包括用户招募成本、用户移动成本和数据传输成本。</p>
<p>本文的用户推荐方法综合考虑了以上三个方面。用户的信息分为位置、权力、信誉等，通过多方面的信息综合考虑用户，确保任务的数据质量。并通过用户设备传感器类型和任务匹配程度来满足任务数据类型的各种需求。与以前的任务分配方法相比，CSSA方法更适合于需要多种数据类型的复杂情况。此外，根据群体智能优化算法。</p>
<h3>Problem description</h3>
<p>由于现有的研究方法侧重于单一类型的感知数据收集，在面对复杂的任务要求时效果并不理想。而且，面对复杂的任务需求，仅考虑单个用户选择的优化很难满足需求。简单地增加具有相似属性的用户数量，不仅会增加任务成本，还会造成数据冗余。</p>
<p>为了解决这个问题，本文提出了一个用户选择框架，该框架综合考虑了各种用户属性对任务感知数据质量的影响以及多用户协作问题。该策略的目标是在确保任务成本的同时提高感知数据的质量，同时考虑用户选择的时间。本文将用户对任务的影响因素主要归结为以下几个方面：用户与数据采集地点的距离；用户设备的传感器类型与任务所需的感测数据类型之间的一致程度；感测装置的剩余功率；基于历史数据的用户信誉。</p>
<p>由于待解决问题的复杂性很高，针对上述影响因素提出了适合度的概念，并以适合度值作为衡量用户优先级和匹配度的参考。适合度计算所需的信息包括用户通过定位的位置信息、用户设备上传的设备剩余电量和传感器类型信息，以及基于用户历史任务完成记录获得的信誉信息。通过对这些值的计算，完成了对采集到的任务数据的质量控制，分析用户是否适合当前任务。这些适应度值是智能优化算法的重要参考标准，如图2所示。</p>
<p><img src="https://img.085404.xyz/images/7d6bbb9fa9a9b43e818263d0f6ced82a.webp" alt="image.png" /></p>
<p>移动众感知平台由许多感知用户组成。U表示任务发布时可以参与系统的用户集合，$F=(f_1, f_2, f_3, f_n)$ 表示当前所有用户对该任务的适应度值，用户集为$U = (u_1, u_2, u_3, u_n)$ 的顺序。按比例将用户分为<strong>探索者</strong>（初级传感数据采集任务适应度高）和<strong>跟随者</strong>（适应度低，负责补充数据和改进传感数据类型），并分别根据不同的职责进行任务感知。最后，根据CSSA算法筛选出合适的感知用户。</p>
<p>基于上述用户选择模型，在考虑数据质量、任务成本和分配时间的前提下，将数据质量最大化的用户选择问题定义如下：</p>
<p><img src="https://img.085404.xyz/images/f381944d917612aa30a012cf5c76ccd3.webp" alt="image.png" /></p>
<p>其中 $I$ 是$1 \times j$的全1矩阵，用于控制所有用户传感器类型的总和以满足任务所需的类型，其中$j$是任务所需类型的数量。$d_i$是用户和任务感知位置之间的距离，通过选择靠近用户的位置，从而降低成本。$UserSen_i \cap TaskSen_i \ne \emptyset$确保用户的设备类型满足至少一个任务要求。目标函数V是表示用户通过设备的剩余功率和设备的适合度来完成任务的数据质量，然后通过乘以信誉适合度来表示最终的完成效果。</p>
<p>同时，为了提高用户选择效率和数据质量，本文采用<strong>参与者协作</strong>的方式来完成任务。分析当前任务所需的数据类型，并要求用户根据不同的传感器完成他们可以提供的信息类型。然后，平台对多个用户上传的内容进行汇总分析，完成传感数据的采集。该算法利用了群体间协作的特点，有利于移动众感知中参与者和用户群体的协作，完成感知任务。同时，综合分析感知用户与用户的位置、设备等多个属性的匹配程度。在众感中，通过分析多种影响因素的用户选择策略和用户之间的合作，可以提高感知数据的质量和任务分配时间，并显著提高众感平台的性能。表1总结了本文中使用的重要符号。</p>
<h3>User collaborative recommendation method</h3>
<h4>Calculation of user fitness value</h4>
<h5>Position fitness</h5>
<p>通过计算任务位置与用户的匹配关系，<strong>当用户远离任务时，用户需要移动的距离会增加，从而增加任务完成时间和更高的任务成本</strong>。鉴于这种情况，本文引入了位置适合度的概念，并将用户和任务之间的距离作为衡量适合度的指标进行了比较。计算公式如下：</p>
<p><img src="https://img.085404.xyz/images/33fdbde36f05633d4c2cc2ff26104db4.webp" alt="image.png" /></p>
<p>其中$d_{max}$是所有用户与任务之间的最大距离，$d_i$是用户$i$与任务之间距离。根据公式（1）的计算，用户与任务之间的<strong>距离越小，位置适应度越大</strong>；当用户和任务之间的距离变长时，适合度逐渐降低。<strong>当适应度等于1时，它达到最大值</strong>，这意味着用户接近任务。从位置的角度来看，用户具有最高的优先级。上述公式将用户与任务之间的位置关系转换为距离，其中<strong>欧几里德距离</strong>用于计算用户ui与任务s之间的距离，用户的位置为（xi，yi），任务的位置为为（x，y），公式如下：</p>
<p><img src="https://img.085404.xyz/images/96d95a0d50413e2f6845590e1d0b3d9b.webp" alt="image.png" /></p>
<p>原来路径任务在用户感知区域时，随着任务之间的距离越来越近，用户与任务之间的适应度位置也会提高，用户想要执行的任务意图越来越大。因此用户可以主动加快速度，减少移动时间，降低任务完成的速度，提高整体效率。</p>
<h5>Electric quantity fitness</h5>
<p>电量适合度被认为是移动设备的剩余电量，因为在完成任务的过程中，传感器的感知数据需要不断利用，感知数据需要通过通信网络实时上传到平台。这个过程需要不断消耗移动设备的电量。因此，电量适合度也是一个重要的考虑参数，它决定了移动设备是否有能力完成感知数据收集。当电量较低时（根据调查，当设备电池电量低于30%时，用户参与任务的意愿非常低，因此本文将电量设置为30%），用户很可能不愿意参与任务。电量适应度的计算根据电量剩余百分比[0,1]中的数据表示，公式如下：</p>
<p><img src="https://img.085404.xyz/images/a97c70d14ab4b6c9897590a480874c94.webp" alt="image.png" /></p>
<p>其中$CE_i$和$E_i$分别表示当前用户的设备的剩余可用功率和总功率。</p>
<h5>Equipment fitness</h5>
<p>根据任务需要感知的数据类型不同，对感知用户设备的要求也不同。因此，需要考虑候选参与者的传感设备上所需传感器的可用性，这将显著影响数据的可靠性和传感结果的质量。在不同要求的传感任务中，每个人的设备都必须配备所有必要的传感器，当一些必要的传感器缺失时，平台会认为这些用户没有资格选择。本文列出了一些可能的传感器，如表2所示。然而，由于用户2的信号质量较低，数据上传过程可能不稳定，从而影响任务的数据质量。因此，<strong>采用用户合作的方法</strong>，由多个用户收集不同类型的数据，并合作完成感知任务。例如，用户3和用户5分别具有位置传感器和温度传感器，并且具有更高的信息质量，因此可以通过用户之间的合作更好地完成感知任务。为了量化匹配程度，使用设备适应度来表示用户和任务之间的硬件能力匹配程度，并将任务依次分配给最好的用户，这不仅可以降低任务所需的成本，还可以提高数据质量。设备信息与传感内容的匹配程度如图3所示。</p>
<p><img src="https://img.085404.xyz/images/23b87c65b0e46304310ad9277a751d8e.webp" alt="image.png" /></p>
<p>其中$SameSen_i=UserSen_i \cap TaskSen$是传感器重组集，表示用户设备的传感器类型集与任务感知内容集的重叠部分。其中一些是必不可少的传感器，其他非关键类型的传感器越来越好，因此传感器的符合度为：</p>
<p><img src="https://img.085404.xyz/images/0d771f5b46ccbf7d7b9ff95b5dd192b0.webp" alt="image.png" /></p>
<p>其中$Sig_i$和$Sig_{max}$分别表示设备当前信号质量和最高信号质量，并以分数的方式分析用户当前信号质量强度。$SameSen_i$和$TaskSen$的比值表示当前设备类型与任务需求类型之间的<strong>拟合程度</strong>。$\eta_1$是信号强度系数，$\eta_2$是传感器符合度系数，两者之间的关系为$\eta_1 + \eta_2 = 1$，控制着不同变量的权重。</p>
<p>然而，每个感知用户不需要拥有所需的所有感知传感器类型，而是可以通过多个用户之间的合作来完成多种类型感知数据的收集。如上所述，用户3和5一起工作以获得比单独的用户2更好的结果。因此，1×j的矩阵Mi用于记录当前用户i的传感器类型，j是任务所需的传感器类型。用户携带的设备所拥有的传感器类型设置为1，如果没有传感器，则设置为0。</p>
<h5>Reputation fitness</h5>
<p>由于声誉本身很难直接评估，因此用户的声誉[38]是基于他们之前参与感知任务来评估的。评估标准包括他们是否致力于完成分配给他们的任务以及完成这些任务的能力。以下公式（7）和（8）分别用于评估每个参与者的信誉参数：</p>
<p><img src="https://img.085404.xyz/images/a078bd5f840c3b001d341074ee880f62.webp" alt="image.png" /></p>
<p>其中Spti表示用户<strong>参与</strong>的任务集，Cti表示用户被选择的任务集；比率Cti表示用户将<strong>参与该任务的概率</strong>；Scti表示用户<strong>完成</strong>的一组任务，Fti表示由用户完成的任务集，比率Scti表示<strong>用户完成任务的概率</strong>。由于这两个参数相对不同，因此使用它们的集合平均值来给出参与者的总体声誉。因此，每个用户的个人信誉适合度由以下公式计算：</p>
<p><img src="https://img.085404.xyz/images/4105488d80f46a2e8b8d3d49a25fc3f0.webp" alt="image.png" /></p>
<p>因此，根据上述方法，每个用户的适应度计算如下：</p>
<p><img src="https://img.085404.xyz/images/f80585bb33fe702789d6274cb84d4112.webp" alt="image.png" /></p>
<p>根据感测服务的性质，可以给予某个参数比其他参数更高的权重。</p>
<h4>Selection of sensing users</h4>
<p>当感知任务发布时，愿意参与任务的感知用户向平台报告，参与者集为U=（u1，u2，u3…un）。平台首先根据用户属性和历史数据，通过公式（8）计算出每个用户的（信誉？）适应度 Fi。</p>
<p>初始化所有用户在优化算法中的位置。为了提高算法的全局搜索能力，避免迭代后期种群多样性的降低，采用逻辑混沌映射代替随机数生成，使位置更加随机、规则。公式如下：</p>
<p><img src="https://img.085404.xyz/images/eaab45c4ee9611a76011a10e006f3b0e.webp" alt="image.png" /></p>
<p>其中μ是Logistic参数，当μ的值满足3.5699456&lt;μ&lt;=4的条件时，迭代生成的值处于<strong>伪随机分布状态</strong>。通过上述公式的迭代计算，n个参与者在维度d下的位置由矩阵表示：</p>
<p><img src="https://img.085404.xyz/images/1724caf0b98a6c60ddfd9181f20dfcc8.webp" alt="image.png" /></p>
<p>参与者的适应度值可以表示为：</p>
<p><img src="https://img.085404.xyz/images/51dc7252f4c6a9c943184117e361ffb2.webp" alt="image.png" /></p>
<p>其中n表示参与者的数量，d表示优化变量的维度。通过计算上述矩阵，按适合度值对用户进行排序，并通过设置值$P_r$将用户划分为<strong>探索者</strong>和<strong>追随者</strong>的两种不同身份，$P_r$表示<strong>探索者占所有参与者的比例</strong>。当前用户由数组 <code>ID[n]</code>标识，0表示follower，1表示explorer。其中，探索者由于任务适应度高，负责主要的传感数据采集，是传感任务的主要成分。追随者与探索者的身份相对应。</p>
<p>由于适应度低，追随者跟随探索者收集数据，并负责补充数据和改进传感数据类型。例如，当Pr设置为0.2时，探索者与跟随者的比例为2:8。探索者的位置更新如下：</p>
<p><img src="https://img.085404.xyz/images/e68e71cf4282bcc4b473c75c8f308978.webp" alt="image.png" /></p>
<p>其中t表示当前迭代次数，$j=1,2,...,d$表示当前维度，$U^t_{i,j}$表示迭代t时第i个感知用户的第j个维度的位置，$iter_{max}$是迭代次数最多的常数。$\alpha \in (0,1)$是一个随机数，$P_n \in [0,1]$和$CT \in [0.5,1.0]$分别表示子区域的<strong>任务完成度</strong>和<strong>完成阈值</strong>。Q是遵循正态分布的随机数。</p>
<p>当 $P_n \lt CT$ 时，表示当前区域的任务完成次数未达到阈值，当前搜索者可以继续完成子区域的任务。当使用$P_n \ge CT$时，意味着当前区域的参与者数量满足任务完成的要求，所有探索者都需要完成其他子区域的感知任务。</p>
<p>跟随者跟随在探索者附近，负责部分传感数据的收集，随着探索者完成任务，两者的适应度值会动态变化，当跟随者发现探索者的适应度低于自己时，他们会产生竞争关系，两者的身份也会发生变化，当前用户<code>ID[i]</code>的值也会发生反转。<strong>职位更新公式</strong>如下：</p>
<p><img src="https://img.085404.xyz/images/d30e4f41b1ced20144e4508558585375.webp" alt="image.png" /></p>
<p>其中$U_p$是探索者最佳位置，$U_{worst}$是全局最差的位置。D表示维度值。当跟随者的适应度值合适时，移动到当前探险家的位置附近，随机到最佳位置附近的位置，距离最佳位置的每个维度的方差变小；当 $i \gt n/2$时，这表明跟随者在当前感知用户队列中的适应度值非常低，无法完成当前任务，然后在其他区域中寻找感知任务。</p>
<p>此外，所有任务参与者中的一部分被选为监控器，这些监控器在人群中随机生成，负责防止用户集中在最佳感知位置并陷入局部最优。选择10%~20%的感应用户作为监视器，其位置表达式更新如下：</p>
<p><img src="https://img.085404.xyz/images/3ea3b194e7447c227f8ee6144911f886.webp" alt="image.png" /></p>
<p>其中$U_{best}$和$U_{worst}$分别表示当前全局最优位置和最差位置；$\beta$作为步长控制参数，是一个平均值为0、方差为1的正态分布随机数；$f_i$是当前个体用户的适应度值；$f_g$是当前的全局最优适应度值。为了避免陷入局部最优，如果用户处于最佳位置，他应该逃到最佳位置和最差位置之间的随机位置；否则，他应该逃到他自己和随机位置之间的随机位置。</p>
<p>优化算法的目的是找到目标函数的最大值，即找到最符合任务要求的用户，从而提高数据质量。</p>
<h3>Experimental results and analysis</h3>
<p>模拟实验的数据来自Geolife[39]数据集，该数据集包含182个用户的GPS轨迹，由一系列时间戳表示。每个点包含经度、纬度和高度的坐标信息，包括17621个轨迹。本文主要使用经纬度坐标信息。传感器的可用类别由用户携带，剩余功率等随机分配给用户。</p>
<p>为了使实验结论更加可靠，本文还使用真实的数据集进行验证，通过众包平台“猪八戒”上的数据采集工具进行抓取，筛选出交易记录最多的前200名用户。每个传感用户的信息包括位置、价格、历史完工数据和其他信息。本文使用的仿真工具是MATLAB，它是在Windows10的64位平台上实现的。处理器为AMD R7-4800 h，主频为2.9 GHz，内存为16.00 GB。</p>
<p>在本文中，首先使用测试函数来调整算法。调整ED和CT两个参数以优化算法的优化性能，并通过1000次迭代获得最快的迭代率。如图4、5、6和7所示，区域完成度阈值设置为0.8，迭代次数设置为1000，以调试探索者和跟随者的比例。图4和图5中探索者和追随者的比例分别为0.2:0.8和0.3:0.7。当任务中探索者的比例增加时，迭代速度略慢，但在有限的迭代次数中，适应度值的准确性显著提高。因此，<strong>适当增加探索者在参与者群体中的比例有利于提高算法的性能</strong>。</p>
<p><img src="https://img.085404.xyz/images/955f01eb14e208dc23c957b34ee82b6d.webp" alt="image.png" /></p>
<p>当探索者和跟随者的数量之比固定为0.3:0.7时，设置区域完成阈值，如图6和图7所示。<strong>在完成阈值降低后，迭代曲线在快速下降后将呈现出明显的平坦期</strong>。由于任务完成设置的程度降低，参与者可能会重复改变感知区域，导致适应度准确性在一段时间内几乎没有提高，从而降低任务完成效率。图6和图7中转换曲线的比较表明，完成阈值设置得越低，曲线的平坦化周期就越长。经过反复测试，发现0.8的完成阈值CT表现最好。因此，ED参数设置为0.3，CT参数设置为0.8。</p>
<p><img src="https://img.085404.xyz/images/49150c9cb94f3f3d0767f8baaa6b730a.webp" alt="image.png" /></p>
]]></content>
    <author><name>Sun2ot</name></author>
    <category term="笔记"/>
  </entry>
  <entry>
    <title>TCCLI更新腾讯云CDN证书</title>
    <link href="https://blog.085404.xyz/posts/tccli-%E6%9B%B4%E6%96%B0%E8%85%BE%E8%AE%AF%E4%BA%91-cdn-%E8%AF%81%E4%B9%A6/" rel="alternate" type="text/html"/>
    <id>https://blog.085404.xyz/posts/tccli-%E6%9B%B4%E6%96%B0%E8%85%BE%E8%AE%AF%E4%BA%91-cdn-%E8%AF%81%E4%B9%A6/</id>
    <published>2024-05-03T00:00:00.000Z</published>
    <updated>2024-05-03T00:00:00.000Z</updated>
    <summary></summary>
    <content type="html"><![CDATA[<h1>TCCLI 更新腾讯云 CDN 证书</h1>
<h2>背景</h2>
<p>经查，由于上游服务商相关政策，针对免费 ssl 的有效时间从 1 年缩减至了 90 天，国内腾讯云及阿里云等平台均受影响。</p>
<p>如此一来，这些云服务大厂的免费证书按照麻烦程度真的不如 <a href="https://github.com/acmesh-official/acme.sh">acme.sh</a> 签 90 天的泛域名了。不过不知道处于什么原因，腾讯跟阿里都是有云计算平台的 CLI 工具的，但搜索了一圈发现只有阿里云平台的第三方教程，而腾讯云的大多是调用的 web api，所以这里就自己折腾一下吧。</p>
<blockquote>
<p>官方的文档只能当手册看，拿来当教程用略有些折磨。</p>
</blockquote>
<h2>安装 TCCLI</h2>
<blockquote>
<p>以下内容节选自官方文档，由于文档链接可能会变动，这里就不放了，反正它们钱多，搜索引擎排名向来都是“名列前茅”的，所以随便搜一搜就有了。</p>
</blockquote>
<p><code>TCCLI</code> 没有独立的二进制文件，需要一来 Python 环境，所以安装也是依靠 pip 进行：</p>
<pre><code>pip install tccli
</code></pre>
<p>查看是否安装成功：</p>
<pre><code>tccli --version

3.0.1099.1
</code></pre>
<p>在 Linux 环境下执行以下命令可开启自动补全功能，支持大小写自动纠错：</p>
<pre><code>complete -C 'tccli_completer' tccli
</code></pre>
<blockquote>
<p>将其写入 <code>.bashrc/.zshrc</code> 可持久化生效。</p>
</blockquote>
<h2>配置 TCCLI</h2>
<p>通过交互模式进行配置：</p>
<pre><code>tccli configure
</code></pre>
<p>由于安全原因，不建议为腾讯云主账户设置 API Token，通常是建立子用户来设置权限，使用子用户的 <code>secrectKey/Id</code> 进行操作。因此，这里<strong>建议使用 TCCLI 的多账户模式</strong>：</p>
<pre><code>tccli configure --profile any_name_you_like
</code></pre>
<p>后续使用 <code>tccli</code> 指令时，在最后加上 <code>--profile any_name_you_like</code> 即可使用对应的账户配置。</p>
<h2>上传证书</h2>
<pre><code>tccli ssl UploadCertificate --cli-unfold-argument --CertificatePublicKey '-----BEGIN CERTIFICATE-----
***
-----END CERTIFICATE-----' --CertificatePrivateKey '-----BEGIN EC PRIVATE KEY-----
***
-----END EC PRIVATE KEY-----' --Alias 'acme-085404.xyz'
</code></pre>
<table>
<thead>
<tr>
<th>参数</th>
<th>必填</th>
<th>类型</th>
<th>描述</th>
</tr>
</thead>
<tbody>
<tr>
<td>CertificatePublicKey</td>
<td>是</td>
<td>String</td>
<td>证书内容</td>
</tr>
<tr>
<td>CertificatePrivateKey</td>
<td>否</td>
<td>String</td>
<td>私钥内容，证书类型为 SVR 时必填，为 CA 时可不填</td>
</tr>
<tr>
<td>Alias</td>
<td>否</td>
<td>String</td>
<td>备注</td>
</tr>
</tbody>
</table>
<p>响应结果：</p>
<pre><code>{ 
    "Response": {
        "CertificateId": "***", 
        "RepeatCertId": "", 
        "RequestId": "***" 
    }
}
</code></pre>
<p>这里的 <code>CertificateId</code> 就是腾讯云控制台的证书 id。</p>
<h2>更新 CDN 证书</h2>
<pre><code>tccli cdn UpdateDomainConfig --cli-unfold-argument --Domain 'blog.085404.xyz' --Origin.AdvanceHttps.CertInfo.CertId LE-dsfdsf --Origin.AdvanceHttps.CertInfo.Certificate '不展示' --Origin.AdvanceHttps.CertInfo.PrivateKey '不展示' --Origin.AdvanceHttps.CertInfo.Message '备注' --Origin.AdvanceHttps.CertInfo.From 'acme.sh-upload'
</code></pre>
<pre><code>#!/bin/bash

tccli cdn ModifyDomainConfig --cli-unfold-argument --Domain 'blog.085404.xyz' --Route 'Https.CertInfo.CertId' --Value '{\"update\":\"0VpOXiPz\"}'

echo "cdn证书更新脚本执行完成"
</code></pre>
<h2>脚本示例</h2>
<p>使用下述脚本前，先执行 <code>sudo apt install jq</code>，需要 <code>jq</code> 这个库来解析命令行输出的 json 数据。</p>
<blockquote>
<p>你用 <code>sed</code> 当然也行</p>
</blockquote>
<pre><code>#!/bin/bash

# 读取acme.sh证书，并上传腾讯云，返回证书id

# 从文件中读取文本内容并存储到变量中
SSLPub=$(cat "/root/.acme.sh/085404.xyz_ecc/fullchain.cer")
SSLPri=$(cat "/root/.acme.sh/085404.xyz_ecc/085404.xyz.key")

response=$(tccli ssl UploadCertificate --cli-unfold-argument --CertificatePublicKey "$SSLPub" --CertificatePrivateKey "$SSLPri" --Alias "acme-085404.xyz" --profile obsidian)

echo $response

CertificatedId=$(echo $response | jq -r '.CertificateId')

echo "cerd id: $CertificatedId"
cert_id='{"update":"'$CertificatedId'"}'

tccli cdn ModifyDomainConfig --cli-unfold-argument --Domain 'your_cdn_domain.com' --Route 'Https.CertInfo.CertId' --Value "$cert_id" --profile obsidian
</code></pre>
]]></content>
    <author><name>Sun2ot</name></author>
    <category term="教程"/>
  </entry>
  <entry>
    <title>VSCode 美化</title>
    <link href="https://blog.085404.xyz/posts/vscode-%E7%BE%8E%E5%8C%96/" rel="alternate" type="text/html"/>
    <id>https://blog.085404.xyz/posts/vscode-%E7%BE%8E%E5%8C%96/</id>
    <published>2024-04-28T00:00:00.000Z</published>
    <updated>2024-04-28T00:00:00.000Z</updated>
    <summary></summary>
    <content type="html"><![CDATA[<h1>VSCode 美化</h1>
<h2>前情提要</h2>
<p>最近被学弟安利了一个开源项目 <a href="https://github.com/SAWARATSUKI/ServiceLogos">ServiceLogos</a>，是一个各类开发语言与 IDE 的 Logo 设计仓库。</p>
<p>所以想了想，老登也要跟上时代！美化的尽头是默认，但首先要开始美化😁</p>
<h2>插件安装</h2>
<p>众所周知，VSCode 的本质就是一个<s>浏览器</s> IDE，你可以在里面写代码、<s>看动漫</s>、<s>看小说</s>，而这些的前提都是基于各种各样的插件。因此，首先安装以下两个插件：</p>
<pre><code>名称: Custom CSS and JS Loader
ID: be5invis.vscode-custom-css
说明: Custom CSS and JS for Visual Studio Code
版本: 7.2.1
发布者: be5invis
VS Marketplace 链接: https://marketplace.visualstudio.com/items?itemName=be5invis.vscode-custom-css
</code></pre>
<pre><code>名称: Background
ID: Katsute.code-background
说明: The most advanced background image extension for VSCode
版本: 2.10.2
发布者: Katsute
VS Marketplace 链接: https://marketplace.visualstudio.com/items?itemName=Katsute.code-background
</code></pre>
<h2>美化设置</h2>
<h3>背景</h3>
<p><code>Ctrl + Shift + P</code>，搜索 <code>Background: Configuration</code>，有四个组件的背景可以设置：</p>
<ol>
<li>Window（整个窗口）</li>
<li>Editor（编辑器）</li>
<li>Sidebar（侧边栏）</li>
<li>Panel（各种弹出窗口）</li>
</ol>
<p>选中你要修改的组件，然后选择一张图片即可。</p>
<h3>Logo</h3>
<p><code>Ctrl + Shift + P</code> - <code>Open User Settings</code></p>
<p>在 <code>settings.json</code> 中插入一行：</p>
<pre><code>"vscode_custom_css.imports": ["file:///D:/snippets/vscode-custom/logo.css"]
</code></pre>
<p>这里的 <code>file:///</code> 是文件协议，指向你的自定义 css 文件，该文件如下：</p>
<pre><code>.editor-group-watermark &gt; .letterpress{
  background-image: url("image.link") !important;
  opacity: .75;
  aspect-ratio: 3/2 !important;
}
</code></pre>
<p>将其中的 <code>image.link</code> 替换为 Logo 图片的链接，可以是 <a href="https://github.com/SAWARATSUKI/ServiceLogos">ServiceLogos</a> 中的 raw link，也可以将图片转为 <a href="https://www.jyshare.com/front-end/59/">base64</a> 后替换进去。如果网络环境无法直连 github，建议选择 base64 方案。</p>
<h2>最终效果</h2>
<p><img src="https://img.085404.xyz/images/344c5bc10a13e84ac969f714c2fc749b.webp" alt="image.png" /></p>
]]></content>
    <author><name>Sun2ot</name></author>
    <category term="教程"/>
  </entry>
  <entry>
    <title>JupyterHub 部署</title>
    <link href="https://blog.085404.xyz/posts/jupyterhub-%E9%83%A8%E7%BD%B2/" rel="alternate" type="text/html"/>
    <id>https://blog.085404.xyz/posts/jupyterhub-%E9%83%A8%E7%BD%B2/</id>
    <published>2024-04-15T00:00:00.000Z</published>
    <updated>2024-04-15T00:00:00.000Z</updated>
    <summary></summary>
    <content type="html"><![CDATA[<h2>什么是 JupyterHub</h2>
<p><a href="https://github.com/jupyterhub/jupyterhub">JupyterHub</a> is the best way to serve <a href="https://jupyter-notebook.readthedocs.io/en/latest/">Jupyter notebook</a> for <strong>multiple users</strong>. Because JupyterHub manages a separate Jupyter environment for each user, it can be used in a class of students, a corporate data science group, or a scientific research group. It is a multi-user <strong>Hub</strong> that spawns, manages, and proxies multiple instances of the single-user <a href="https://jupyter-notebook.readthedocs.io/en/latest/">Jupyter notebook</a> server.</p>
<h2>前置环境</h2>
<h3>安装 Python 和 pip</h3>
<p>大多数发行版 Unix 系统都自带 python 和 pip：</p>
<pre><code>apt install python3 python3-pip
</code></pre>
<p>注意这里用 <code>pip3 -V</code> 看一下版本。太老的版本（例如 python 3.6）基本是会出问题的。所以如果需要更新 <code>pip3</code>：</p>
<pre><code>sudo -H pip3 install -U pip
</code></pre>
<p>如果提示 <code>Cache entry deserialization failed, entry ignored</code>，可能是由于某些缓存文件损坏或不完整所致。这个问题通常可以通过清理 <code>pip</code> 的缓存来解决：</p>
<pre><code>pip3 cache purge
</code></pre>
<p>如果你的 <code>pip3</code> 版本太老，也可能提示 <code>ERROR: unknown command "cache" - maybe you meant "check"</code>，那就只能手动清楚缓存了：</p>
<pre><code># Linux下的pip缓存通常在 `~/.cache/pip` 或 `/var/cache/pip`
rm -rf ~/.cache/pip
rm -rf /var/cache/pip
</code></pre>
<p>然后重新执行 <code>sudo -H pip3 install -U pip</code>。</p>
<h3>安装 JupyterHub</h3>
<pre><code>sudo -H pip3 install -U jupyterhub
</code></pre>
<ul>
<li><code>-H</code> 选项告诉 <code>pip</code> 使用 HOME 环境变量指定的目录作为家目录。这在使用 <code>sudo</code> 时很重要，因为默认情况下，<code>sudo</code> 会重置 HOME 环境变量为 <code>/root</code>。使用 <code>-H</code> 选项可以确保 <code>pip</code> 将包安装到当前用户的家目录下，而不是 root 用户的家目录下。这样做是为了避免因为权限问题导致的包安装到错误的位置。</li>
<li><code>-U</code> 选项是 <code>pip</code> 的一个标志，代表 <code>--upgrade</code>。它告诉 <code>pip</code> 安装包之前先尝试升级已安装的包到最新版本。如果已经安装了某个包，<code>-U</code> 选项会使 <code>pip</code> 尝试升级到新版本，如果没有指定包，则会安装最新版本。</li>
</ul>
<h3>安装 configurable-http-proxy</h3>
<p>这是一个 nodejs 模块，npm 安装的（官方要求的）。理论上说，安装 nodejs 用 <code>apt install</code> 就行。不过版本实在太老了，所以还是建议手动 install 吧，反正也不费什么事。</p>
<pre><code>npm install -g configurable-http-proxy
</code></pre>
<blockquote>
<p>去官网找<strong>契合操作系统版本</strong>的 Node.js LTS 版本即可。</p>
</blockquote>
<h2>配置 JupyterHub</h2>
<blockquote>
<p>基于 <code>JupyterHub version 2.3.1</code>。移除网络上抄来抄去的过时配置。</p>
</blockquote>
<p><a href="https://jupyterhub.readthedocs.io/en/stable/tutorial/getting-started/config-basics.html#generate-a-default-config-file">生成默认配置文件</a> 并移动到公共目录：</p>
<pre><code>jupyterhub --generate-config
mkdir -p /etc/jupyterhub
mv jupyterhub_config.py /etc/jupyterhub/
</code></pre>
<blockquote>
<p>可能会报错 python 版本不受支持之类的，可以忽略不用管。</p>
</blockquote>
<p>接下来修改配置文件：</p>
<pre><code># 使用 JupyterLab 而非 Jupyter Notebook（可选）
c.Spawner.default_url = "/lab"

c.JupyterHub.ip = "0.0.0.0"
c.JupyterHub.port = 8000

# 设置 Jupyterhub 的管理员账户
c.Authenticator.admin_users = {"yzh","hml"}
#如果未设置此配置值，则所有经过身份验证的用户都将被允许进入您的集线器。
c.Authenticator.allowed_users = {'yzh'}
</code></pre>
<p>上述过程完成后，即可启动 JupyterHub：</p>
<pre><code>jupyterhub -f /etc/jupyterhub/jupyterhub_config.py
</code></pre>
<p>注意，会在启动 JupyterHub 的路径下生成 <code>jupyterhub.sqlite</code> 和 <code>jupyterhub_cookie_secret</code> 文件，如有必要可以进行定时备份。</p>
<h2>切换 kernel</h2>
<p>在 Jupyter 中，每个 kernel 对应一个运行环境。对于一个 conda 环境，只需要安装 <code>ipykernel</code>，即可被 Jupyter 识别到：</p>
<pre><code># 激活要使用的环境
conda activate jtest

# 安装 ipykernel
conda install ipykernel

# 添加到 Jupyter kernel list
## --name: conda 环境的名称
## --display-name: Jupyter 中显示的名称
python -m ipykernel install --user --name jtest --display-name "jtest"
</code></pre>
<p>重启当前用户的 Jupyter Server，即可在 kernel 菜单中发现 <code>jtest</code>。</p>
<blockquote>
<p>其他用户登录 JupyterHub 时，无法看到该 kernel。</p>
</blockquote>
<blockquote>
<p>[!warning] 禁止使用（污染）JupyterHub 的默认 kernel！请自行创建 conda 环境以使用。</p>
</blockquote>
]]></content>
    <author><name>Sun2ot</name></author>
    <category term="教程"/>
  </entry>
  <entry>
    <title>私有化Bitwarden部署</title>
    <link href="https://blog.085404.xyz/posts/%E7%A7%81%E6%9C%89%E5%8C%96bitwarden%E9%83%A8%E7%BD%B2/" rel="alternate" type="text/html"/>
    <id>https://blog.085404.xyz/posts/%E7%A7%81%E6%9C%89%E5%8C%96bitwarden%E9%83%A8%E7%BD%B2/</id>
    <published>2024-04-14T00:00:00.000Z</published>
    <updated>2024-04-14T00:00:00.000Z</updated>
    <summary></summary>
    <content type="html"><![CDATA[<h1>私有化 Bitwarden 部署</h1>
<blockquote>
<p><a href="https://github.com/dani-garcia/vaultwarden">vaultwarden</a> is an <strong>unofficial</strong> Bitwarden compatible server written in Rust, formerly known as bitwarden_rs.</p>
</blockquote>
<h2>拉取镜像</h2>
<p>按理说部署 docker 需要先拉取镜像，不过这里使用 <code>docker-compose</code> 来启动，所以只需要准备好对应的 <code>docker-compose.yml</code> 文件然后启动即可，拉取镜像会自动执行。所以直接从配置项入手。</p>
<h2>配置项</h2>
<h3>禁止新用户注册</h3>
<p>默认情况下，任何能够访问后端的人都可以在此注册新账户，该功能可通过设置环境变量 <code>SIGNUPS_ALLOWED</code> 为 <code>false</code> 进行关闭：</p>
<pre><code>docker run -d --name bitwarden \
  -e SIGNUPS_ALLOWED=false \
  -v /vw-data/:/data/ \
  -p 80:80 \
  vaultwarden/server:latest
</code></pre>
<p>需要注意的是, 现在注册和邀请功能仍然显示在页面上, 但是实际尝试注册和邀请会报错误消息。因为官方 <strong>Bitwarden server API</strong> 是一个开放平台, 所以无法完全禁用。</p>
<h3>禁止组织邀请</h3>
<p>即使 <code>SIGNUPS_ALLOWED=false</code> ，作为组织所有者或管理员的现有用户仍然可以邀请新用户。可以通过将 <code>INVITATIONS_ALLOWED</code> 环境变量设置为 <code>false</code> 来完全禁用此功能：</p>
<pre><code>docker run -d --name bitwarden \
  -e SIGNUPS_ALLOWED=false \
  -e INVITATIONS_ALLOWED=false \
  -v /vw-data/:/data/ \
  -p 80:80 \
  vaultwarden/server:latest
</code></pre>
<blockquote>
<p>无论如何限制，Vaultwarden 管理员都可以通过 admin page 邀请任何人。</p>
</blockquote>
<h3>限制注册邮箱域名</h3>
<p>可以通过相应设置 <code>SIGNUPS_DOMAINS_WHITELIST</code> 将注册限制为来自某些域的电子邮件地址。例如：</p>
<ul>
<li><code>SIGNUPS_DOMAINS_WHITELIST=example.com</code> (single domain)</li>
<li><code>SIGNUPS_DOMAINS_WHITELIST=example.com,example.net,example.org</code> (multiple domains)</li>
</ul>
<blockquote>
<p>[!warning] 如果设置了 <code>SIGNUPS_DOMAINS_WHITELIST</code> ，则会忽略 <code>SIGNUPS_ALLOWED</code> 的值。</p>
</blockquote>
<h3>开启邮箱验证</h3>
<p>设置 <code>SIGNUPS_VERIFY=true</code> 可以让新注册用户成功登录之前进行电子邮件验证。这将防止有人使用具有正确域的虚假电子邮件地址进行注册。</p>
<h3>admin page</h3>
<h4>启用</h4>
<blockquote>
<p>强烈建议在启用此功能之前激活 HTTPS，以避免可能的 MITM 攻击。</p>
</blockquote>
<p>该页面允许服务器管理员查看所有注册用户并删除它们。即使注册被禁用，它也允许邀请新用户。</p>
<p>要<strong>启用管理页面</strong>，您需要使用 <code>ADMIN_TOKEN</code> 设置身份验证令牌并<strong>将其保密</strong>：</p>
<pre><code>docker run -d --name vaultwarden \
  -e ADMIN_TOKEN=some_random_token_as_per_above_explanation \
  -v /vw-data/:/data/ \
  -p 80:80 \
  vaultwarden/server:latest
</code></pre>
<p>令牌可以是任何内容，但建议使用随机生成的长字符串，例如运行 <code>openssl rand -base64 48</code> 。</p>
<p>通过 <code>/admin</code> 可以进入 admin page。</p>
<p>首次在管理页面中保存设置时，将在 <code>DATA_FOLDER</code> 中生成 <code>config.json</code> 。<strong>该文件中的值将优先于相应的环境变量</strong>。但请注意，管理页面中的配置更改只有在您单击 <code>Save</code> 按钮后才会生效。</p>
<blockquote>
<p>[!warning] 更改 <code>ADMIN_TOKEN</code> 后，当前登录的任何管理员仍然可以使用其现有的登录会话，直到过期。管理会话生命周期是可配置的，默认值为 20 分钟。</p>
</blockquote>
<h4>禁用</h4>
<p>如果需要禁用 admin page，需要取消设置 <code>ADMIN_TOKEN</code> 并重新启动 Vaultwarden。</p>
<p>但如果环境变量 <code>ADMIN_TOKEN</code> 的值保留在上述 <code>config.json</code> 文件中，则删除该值不会禁用管理页面。要禁用管理页面，请确保未设置 <code>ADMIN_TOKEN</code> 环境变量，并且 <code>config.json</code> 中不存在 <code>"admin_token"</code> 键（如果该文件存在）。</p>
<h4>保护令牌</h4>
<p>按照 <a href="https://github.com/dani-garcia/vaultwarden/wiki/Enabling-admin-page#secure-the-admin_token">官方说明</a>，有两种方式可以生成加密的 Argon2 PHC 字符串，这里我们用 vaultwarden 内置的 PHC 生成器对 <code>ADMIN_TOKEN</code> 进行 hash 加密：</p>
<pre><code>docker exec -it 容器名称 /vaultwarden hash
</code></pre>
<p>Vaultwarden CLI 将要求输入密码两次，如果两次相同，它将输出生成的 PHC 字符串，如下：</p>
<pre><code>ADMIN_TOKEN='$argon2id$****$****$****$****'
</code></pre>
<p>同样，对于生成的加密字符串，有三种方式可以进行配置：</p>
<ol>
<li>使用 <code>docker-compose.yml</code>，在 <code>enviroment</code> 中引入 <code>ADMIN_TOKEN</code> 即可：<pre><code>environment:
 ADMIN_TOKEN: $$argon2id$$****$$****$$****$$****
</code></pre>
<blockquote>
<p>注意，这里的 Argon2 PHC 跟生成的略有不同，不仅没有引号包裹，且原本的每个 <code>$</code> 必须用两个连续的 <code>$$</code> 进行转义。</p>
</blockquote>
</li>
<li>直接在 CLI 中通过 <code>-e ADMIN_TOKEN</code> 参数引入。</li>
<li>通过 <code>.env</code> 文件：<pre><code> # 在.env文件中如下配置
 VAULTWARDEN_ADMIN_TOKEN='$argon2id$v=19$m=65540,t=3,p=4$MmeK.....'

 # 然后在docker-compose.yml中引入
 environment:
   - ADMIN_TOKEN=${VAULTWARDEN_ADMIN_TOKEN}
</code></pre>
</li>
<li>也可以在 <code>/admin</code> 界面中进行修改，不过官方似乎不建议这么干。</li>
</ol>
<p>如果加密字符串不合法，会给出警告：</p>
<pre><code>WARNING: The argon2id variable is not set. Defaulting to a blank string.
WARNING: The v variable is not set. Defaulting to a blank string.
WARNING: The m variable is not set. Defaulting to a blank string.
...
</code></pre>
<h4>禁用令牌</h4>
<p>将 <code>DISABLE_ADMIN_TOKEN</code> 变量设置为 true 可以禁用用于身份验证的内置 <code>ADMIN_TOKEN</code> ，同时启用管理面板。任何有权访问该 URL 的人都可以访问管理面板。您将需要采取额外的步骤来保护它。这包括外部和本地。</p>
<h2>代理设置</h2>
<p>官方给出了一些用户的代理 <a href="https://github.com/dani-garcia/vaultwarden/wiki/Proxy-examples">案例</a>，包括 caddy，nginx 等，这里笔者习惯于 nginx，下面给出自用的配置。</p>
<pre><code># vaultwarden proxy config
# The `upstream` directives ensure that you have a http/1.1 connection
# This enables the keepalive option and better performance
#
# Define the server IP and ports here.
upstream vaultwarden-default {
  zone vaultwarden-default 64k;
  server 127.0.0.1:9988;
  keepalive 2;
}

# Needed to support websocket connections
# See: https://nginx.org/en/docs/http/websocket.html
# Instead of "close" as stated in the above link we send an empty value.
# Else all keepalive connections will not work.
map $http_upgrade $connection_upgrade {
    default upgrade;
    ''      "";
}

# Redirect HTTP to HTTPS
server {
    listen 80;
    server_name your_vaultwarden.com;

    if ($host = your_vaultwarden.com) {
        return 301 https://$host$request_uri;
    }
    return 404;
}

server {
    # For older versions of nginx appened http2 to the listen line after ssl and remove `http2 on`
    listen 443 ssl;
    # http2 on;
    server_name your_vaultwarden.com;

    # Specify SSL Config when needed
    ssl_certificate /path/to/your/fullchain.cer;
    ssl_certificate_key /path/to/your/domain.key;
    client_max_body_size 525M;

    location / {
      proxy_http_version 1.1;
      proxy_set_header Upgrade $http_upgrade;
      proxy_set_header Connection $connection_upgrade;

      proxy_set_header Host $host;
      proxy_set_header X-Real-IP $remote_addr;
      proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
      proxy_set_header X-Forwarded-Proto $scheme;

      proxy_pass http://127.0.0.1:9988;
    }
}
</code></pre>
<h2>部署容器</h2>
<p>这里给出一个自用的 <code>docker-compose.yml</code>：</p>
<pre><code>version: '3'

services:
  vaultwarden:
    image: vaultwarden/server:latest
    container_name: vaultwarden
    restart: unless-stopped
    volumes:
      - /deploy/vaultwarden/data:/data
    ports:
      - 9988:80
    environment:
      DOMAIN: "https://your_vaultwarden.com"
      LOG_FILE: "/data/access.log"
      SIGNUPS_ALLOWED: true
      INVITATIONS_ALLOWED: false
      ADMIN_TOKEN: $$...$$...$$....$$...$$...
</code></pre>
<p>拉取镜像并启动容器：</p>
<pre><code># 进入项目根目录
cd /deploy/vaultwarden

# 创建并启动容器
docker-compose up -d
</code></pre>
<h2>自动备份</h2>
<p>使用脚本对 vaultwarden 数据打包，然后借助阿里云对象存储 CLI 工具 ossutil 上传至对象存储 bucket，将该过程设置定时任务以完成自动备份。</p>
<p>备份脚本 <code>oss-upload.sh</code> 如下：</p>
<pre><code>#!/bin/bash

# 检查是否传入路径参数
if [ $# -eq 0 ]; then
    echo "Error: Please provide a directory path as an argument."
    exit 1
fi

path=$1
echo "target is $path"

# 检查传入的路径是否存在
if [ ! -d $path ]; then
    echo "Error: The specified directory does not exist."
    exit 1
fi

timestamp=$(date +"%Y-%m-%d")

# 创建压缩文件
tar -zcPf "${timestamp}.tar.gz" $path &gt;&gt; output.txt

# 上传压缩文件到oss
ossutil cp -f "${timestamp}.tar.gz" oss://vault-warden/ &gt;&gt; output.txt

# 删除压缩文件
rm "${timestamp}.tar.gz"

echo "Script executed at $(date)" &gt;&gt; output.txt
</code></pre>
<p>执行完成后，可手动执行进行测试：</p>
<pre><code>chmod +x oss-upload.sh
./oss-upload.sh /path/to/vault-warden
</code></pre>
<p>脚本执行后， <code>output.txt</code> 中存有过程日志记录，bucket 中会出现 <code>YYYY-MM-DD.tar.gz</code> 文件。由于这里是每天备份一次，所以名称精确到天即可，如有进一步需求可自行修改。</p>
<p>设置定时任务方式如下：</p>
<pre><code>crontab -e
</code></pre>
<p>在文件中添加一行：</p>
<pre><code>0 0 * * * /path/to/oss-upload.sh /path/to/vault-warden
</code></pre>
<p>保存并退出即可。</p>
]]></content>
    <author><name>Sun2ot</name></author>
    <category term="教程"/>
  </entry>
  <entry>
    <title>GitHub添加SSH密钥</title>
    <link href="https://blog.085404.xyz/posts/github%E6%B7%BB%E5%8A%A0ssh%E5%AF%86%E9%92%A5/" rel="alternate" type="text/html"/>
    <id>https://blog.085404.xyz/posts/github%E6%B7%BB%E5%8A%A0ssh%E5%AF%86%E9%92%A5/</id>
    <published>2024-04-08T00:00:00.000Z</published>
    <updated>2024-04-08T00:00:00.000Z</updated>
    <summary></summary>
    <content type="html"><![CDATA[<h2>生成密钥</h2>
<pre><code> ssh-keygen -t ed25519 -C "sun2ot@qq.com" -f ~/.ssh/your-key
</code></pre>
<ul>
<li><code>-t</code>：加密方式，rsa，ed25519，或者别的 GitHub 支持的都可以。</li>
<li><code>-C</code>：GitHub 邮箱，用于注释密钥。</li>
<li><code>-f</code>：指定生成密钥的位置和名称。建议存放在 <code>~/.ssh/</code> 下，但名称可以自定义以避免重复。无需指定密钥文件的后缀，openssh 会自动生成。</li>
</ul>
<p>生成密钥时会提示是否需要设置 <code>passphrase</code>，这个可以置空，不影响使用。</p>
<p>生成完成后，会在 <code>~/.ssh/</code> 下生成 <code>your-key</code>（私钥）和 <code>your-key.pub</code>（公钥）。接下来，需要将公钥上传到 GitHub。</p>
<h2>添加公钥到 GitHub</h2>
<p>官方教程：<a href="https://docs.github.com/en/authentication/connecting-to-github-with-ssh/adding-a-new-ssh-key-to-your-github-account">英文</a> <a href="https://docs.github.com/zh/authentication/connecting-to-github-with-ssh/adding-a-new-ssh-key-to-your-github-account">中文</a></p>
<ol>
<li>点击右上角 GitHub 头像</li>
<li>点击 <code>Settings</code></li>
<li>左侧菜单列表找到 <code>SSH and GPG keys</code></li>
<li>点击绿色的 <code>New SSH key</code>
<ol>
<li><code>Title</code> 是用以区分的密钥别名，随意指定即可</li>
<li><code>Key Type</code> 保持 Authentication Key 不变</li>
<li><code>Key</code> 中填入刚刚生成的 <code>your-key.pub</code> 中的内容，例如
<code>ssh-ed25519 123456abcd your@email.com</code></li>
<li>点击 <code>Add SSH key</code> 即可</li>
</ol>
</li>
</ol>
<h2>通过 SSH 进行克隆</h2>
<p>首先测试与 GitHub 的 SSH 连通性：</p>
<pre><code>ssh -T git@github.com
</code></pre>
<p>如果一切正常应该会输出如下内容：</p>
<pre><code>Hi your-github-name! You've successfully authenticated, but GitHub does not provide shell access.
</code></pre>
<blockquote>
<p>可能还有一些 Warning，或者提示写入 fingerprint 的提示，这都是正常的。</p>
</blockquote>
<p>但如果报错 <code>kex_exchange_identification: read: Connection reset by peer</code>，请检查网络环境。大概率是🧱的问题，需要🪜来代理一下，见 <a href="https://blog.085404.xyz/metohd/github-ssh.html">git 代理</a>。</p>
<p>如果生成密钥时指定了自定义名称，建议在 ssh 配置文件中写明，防止出现 <code>Permission denied (publickey).</code> 错误：</p>
<pre><code>vim ~/.ssh/config

# 在其中添加
Host github.com
  Hostname ssh.github.com
  IdentityFile /path/to/your/private-key  # 自定义密钥路径
  User git
</code></pre>
<p>处理完上述问题后，便可以正常访问仓库：</p>
<pre><code>git clone git@github.com:&lt;github-name&gt;/&lt;repo-name&gt;.git
</code></pre>
<p>这个克隆地址与 HTTP 类似，都可以在 GitHub 的网页复制下来。</p>
]]></content>
    <author><name>Sun2ot</name></author>
    <category term="教程"/>
  </entry>
  <entry>
    <title>ss订阅链接加密解析</title>
    <link href="https://blog.085404.xyz/posts/ss%E8%AE%A2%E9%98%85%E9%93%BE%E6%8E%A5%E5%8A%A0%E5%AF%86%E8%A7%A3%E6%9E%90/" rel="alternate" type="text/html"/>
    <id>https://blog.085404.xyz/posts/ss%E8%AE%A2%E9%98%85%E9%93%BE%E6%8E%A5%E5%8A%A0%E5%AF%86%E8%A7%A3%E6%9E%90/</id>
    <published>2024-03-30T00:00:00.000Z</published>
    <updated>2024-03-30T00:00:00.000Z</updated>
    <summary></summary>
    <content type="html"><![CDATA[<p>对于一个已经部署好的 Shadowsocks 节点，在 Clash 中的配置参数如下：</p>
<pre><code>proxies:
  - {name: node, server: 192.168.1.1, port: 8388, type: ss, cipher: chacha20-ietf-poly1305, password: 123456, udp: true}
</code></pre>
<p>我们通常看到的 ss 订阅链接如下：</p>
<pre><code>ss://Y2hhY2hhMjAtaWV0Zi1wb2x5MTMwNToxMjM0NTZAMTkyLjE2OC4xLjE6ODM4OA
</code></pre>
<p>这其实是一个经过 urlsafe_base64 加密后得到的字符串：</p>
<pre><code>Encode bytes using the URL- and filesystem-safe Base64 alphabet.

Argument s is a bytes-like object to encode. The result is returned as a bytes object. The alphabet uses '-' instead of '+' and '_' instead of '/'.
</code></pre>
<p>换言之，只要先对 ss 链接进行 base64 解码，即可得到相关参数：</p>
<blockquote>
<p>注意：只解码 <code>ss://</code> 后面的内容</p>
</blockquote>
<pre><code>chacha20-ietf-poly1305:123456@192.168.1.1:8388
</code></pre>
<p>这也就是上文我们给出的 Clash 节点配置信息。而 ss 链接在没有进行 base64 加密前的格式，正是：</p>
<pre><code>ss://method:password@server:port  
</code></pre>
<p>显然，这存在一个问题：<code>name</code> 和 <code>udp</code> 参数不在其中。换言之，如果你将自己的 Shadowsocks 节点配置按照如上方式加密，得到的 ss 订阅链接是无法解析出 <code>name</code> 和 <code>udp</code> 参数的。</p>
<p>通过联合 subconverter 进行研究，我发现一个适用于 Clash 的 ss 订阅链接格式如下：</p>
<pre><code>ss://url_base64(method:password@server:port?udp=1)#name
</code></pre>
<ul>
<li>url_base64 只加密 <code>ss://</code> 与 <code>#name</code> 之间的内容</li>
<li>带有 <code>?udp=1</code> 则会解析出 <code>udp=true</code>；反之则不会</li>
<li><code>#name</code> 直接添加在加密完成后的完整 ss 链接后</li>
</ul>
<p>当然，<code>@server:port</code> 部分也可以不加密：</p>
<pre><code>ss://Y2hhY2hhMjAtaWV0Zi1wb2x5MTMwNTpwYXNzd29yZA@www.example.com:1080#name
</code></pre>
]]></content>
    <author><name>Sun2ot</name></author>
    <category term="教程"/>
  </entry>
  <entry>
    <title>Tmux Docs</title>
    <link href="https://blog.085404.xyz/posts/tmux-docs/" rel="alternate" type="text/html"/>
    <id>https://blog.085404.xyz/posts/tmux-docs/</id>
    <published>2024-03-30T00:00:00.000Z</published>
    <updated>2024-03-30T00:00:00.000Z</updated>
    <summary></summary>
    <content type="html"><![CDATA[<h1>Tmux Docs</h1>
<h2>Tmux 是什么？</h2>
<p>命令行的典型使用方式是，打开一个终端窗口（terminal window，以下简称 " 窗口 "），在里面输入命令。<strong>用户与计算机的这种临时的交互，称为一次 " 会话 "（session）</strong> 。</p>
<p>会话的一个重要特点是，窗口与其中启动的进程是连在一起的：</p>
<ul>
<li>打开窗口，会话开始；</li>
<li>关闭窗口，会话结束，会话内部的进程也会随之终止，不管有没有运行完。</li>
</ul>
<p>一个典型的例子就是，SSH 登录远程计算机，打开一个远程窗口执行命令。这时，网络突然断线，再次登录的时候，是找不回上一次执行的命令的。因为上一次 SSH 会话已经终止了，里面的进程也随之消失了。</p>
<p>为了解决这个问题，会话与窗口可以 " 解绑 "：窗口关闭时，会话并不终止，而是继续运行，等到以后需要的时候，再让会话 " 绑定 " 其他窗口。</p>
<p><strong>Tmux 就是会话与窗口的 " 解绑 " 工具，将它们彻底分离。</strong></p>
<p>Tmux 的标准定义是：</p>
<pre><code>一个在 Unix 和类 Unix 系统上运行的终端复用器（terminal multiplexer）。它允许用户在单个终端窗口中同时访问多个终端会话。使用 Tmux，您可以创建、管理和控制多个终端会话，这些会话可以在后台运行，即使您断开了与服务器的连接也可以保持运行。这使得在服务器上同时运行多个终端会话变得更加高效和方便。
</code></pre>
<h2>Tmux 基本组件概念</h2>
<p>Tmux 的核心组件分为三个：</p>
<ul>
<li>会话 session</li>
<li>窗口 window</li>
<li>窗格 pane</li>
</ul>
<p><img src="https://img.085404.xyz/images/705e3594e044f430e9802ea22e3ad20c.webp" alt="image.png" /></p>
<p>下图是一个布局示意图</p>
<p><img src="https://img.085404.xyz/images/f4267dcf8a41e09829e248afb9965684.webp" alt="image.png" /></p>
<p>通常来说，一个用户只需要创建一个 session 即可。不同的任务可以在不同的 window 下完成。每个 window 可以按需要划分为若干个 pane 来辅助完成任务。</p>
<h2>部署 Tmux</h2>
<p>最简单的当然是 <code>apt install tmux</code>，不过这样安装的版本并不是最新版（但是完全不影响使用）。我个人喜欢手动编译，反正要不了多久。</p>
<p>首先准备编译环境：</p>
<pre><code>sudo apt update
sudo apt install libevent-dev ncurses-dev build-essential bison pkg-config automake
</code></pre>
<p><a href="https://github.com/tmux/tmux/wiki/Installing">Tmux wiki</a> 中没有提到 <code>automake</code>，但不排除有的机器，例如容器环境，没有该工具，所以多装一个保险。</p>
<p>然后编译构建：</p>
<pre><code>git clone https://github.com/tmux/tmux.git
cd tmux
sh autogen.sh
./configure &amp;&amp; make
</code></pre>
<blockquote>
<p>这里是 git clone 的源码，而不是 release 发布的 <code>tmux-xx.tar.gz</code>，否则找不到 <code>autogen.sh</code></p>
</blockquote>
<h2>如何使用 Tmux</h2>
<h3>关于快捷键</h3>
<p>Tmux 有大量的快捷键。所有快捷键都要通过<strong>前缀键</strong>唤起。默认的前缀键是 <code>Ctrl + b</code>，即先按下 <code>Ctrl + b</code>，快捷键才会生效。</p>
<p>举例来说，帮助命令的快捷键是 <code>Ctrl + b + ?</code>。它的用法是，在 Tmux 窗口中，先按下 <code>Ctrl+b</code>，再按下 <code>?</code>，就会显示帮助信息。</p>
<p>然后，按下 <code>ESC</code> 键或 <code>q</code> 键，就可以退出帮助。</p>
<blockquote>
<p>由于快捷键的数量太多，全部记住未必就真的“快捷”，个人认为部分指令的缩写也足够简洁明了（除非英语不太行），所以下文只会提到常用的快捷键。</p>
</blockquote>
<p>一些 Reference：</p>
<ul>
<li>https://gist.github.com/ryerh/14b7c24dfd623ef8edc7</li>
</ul>
<h3>会话</h3>
<h4>创建会话</h4>
<pre><code># 创建一个名为 wym 会话
tmux new -s wym
</code></pre>
<p>执行后，会自动进入 wym session，并在终端下方进行亮绿色显示 <code>[wym]</code>。</p>
<p>在这个 session 中执行的命令，产生的任何输出都不会随着 ssh 连接的断开而丢失，即直接关闭终端程序或者网络突然断开，都不会影响当前的会话。</p>
<h4>连接会话</h4>
<p>当我们登录服务器并需要重新连接到之前的会话时，可以通过如下方式：</p>
<pre><code># 查看当前正在运行的会话
tmux ls
# -----输出如下-----
wym: 1 windows (created Mon Apr  1 12:29:03 2024)
yzh: 1 windows (created Fri Mar 29 09:52:33 2024)
</code></pre>
<p>可以看见，当前有两个 tmux 会话，一个叫 <code>wym</code>，一个叫 <code>yzh</code>。我们需要重新连接到 <code>wym</code> 以继续之前的工作，执行：</p>
<pre><code># tmux attach (附上) -t (target, 目标) session_name
tmux attach -t wym

# 也可以将 attach 简写为 at
tmux at -t wym
</code></pre>
<ul>
<li>如果退出会话前，有进程正在输出，则你会看到输出仍在进行或者已停止</li>
<li>之前执行的指令记录仍然存在</li>
</ul>
<h4>退出会话</h4>
<blockquote>
<p>其实“退出”叫做“分离/卸载”更合适，因为关键字是 <code>detach</code></p>
</blockquote>
<p>退出会话有两种方式：快捷键和指令</p>
<pre><code># 指令
tmux detach

# 快捷键
ctrl + b + d
</code></pre>
<p>综上，一般的工作流程是：创建会话摸摸鱼，然后退出会话恰个饭，接着连接会话继续摸。。。</p>
<h4>杀死会话</h4>
<p>除非你确定这个 session 已经没用了，否则不要轻易这么做。</p>
<pre><code>tmux kill-session -t &lt;session-name&gt;
</code></pre>
<h3>窗口</h3>
<p>多个不同的任务，可以分别在不同的窗口内执行。例如 Window1 用来跑模型，Window2 用来看日志。</p>
<h4>创建窗口</h4>
<p>输入 <code>tmux new</code> 后按 <code>Tab</code>，就可以得到相应的提示信息。</p>
<pre><code># 指令(可以指定窗口的名称)
tmux new-windows -n &lt;window-name&gt;

# 快捷键
ctrl + b + c  # c = create
</code></pre>
<p>快捷键创建的窗口会使用当前的 <code>shell</code> 作为名称，例如 <code>bash</code>，<code>zsh</code> 等，无法自定义名称。</p>
<h4>切换窗口</h4>
<pre><code># 查看当前开启的窗口
tmux lsw  # lsw = list window
# -----输出如下-----
0: zsh (1 panes) [120x29] [layout cafd,120x29,0,0,0] @0
1: 2- (1 panes) [120x29] [layout cb00,120x29,0,0,3] @3
2: zsh* (1 panes) [120x29] [layout cb02,120x29,0,0,5] @4 (active)
</code></pre>
<p>可以看见终端下方的高亮提示条内，其实是有相关辅助信息的，例如：</p>
<pre><code>[wym]   0:zsh   1:zsh-   2:zsh*
</code></pre>
<p>其中，<code>[wym]</code> 是当前的 session 名称，0,1,2 是三个 window 的编号。<code>*</code> 表示当前所处的时候 2 号 window，这和 <code>tmux lsw</code> 输出中的 <code>active</code> 所在行是对应的。<code>-</code> 表示上一个 window。</p>
<p>如果要切换窗口，也有两种方式：</p>
<pre><code># 指令：切换到0
tmux select-window -t &lt;target-window&gt;  # tmux select-window -t 0

# 快捷键
ctrl + b + n  # n = next，下一个窗口
ctrl + b + p  # p = previous，上一个窗口
ctrl + b + w # w = window，可以在列表中选择要切换的窗口（推荐）
</code></pre>
<h4>杀死窗口</h4>
<blockquote>
<p>杀死的命令其实都差不多的，可以 <code>tmux kill</code> 然后按 <code>Tab</code> 提示。</p>
</blockquote>
<pre><code># 指令
tmux kill-window -t &lt;target-window&gt;

# 快捷键
ctrl + b + &amp;  # 杀死当前窗口，会问你 yes or no
</code></pre>
<h3>窗格</h3>
<h4>创建窗格</h4>
<p>窗格其实就是把一个窗口（window）分割为多个小块，每个块就是一个 pane。</p>
<blockquote>
<p>屏幕够大的话，非常 nice👍</p>
</blockquote>
<p>那么怎么分割呢？很显然，水平方向可以分成左右两块，垂直方向可以分成上下两块。如果要分多块，就是在每个 pane 里继续上下左右分割，行成不规则形状。</p>
<pre><code># 水平分割
tmux split-window -h  # h = horizontally

# 垂直分割
tmux split-window -v  # v = vertically
</code></pre>
<p>例如先后执行上面两句指令的话，你会得到这样一个布局：</p>
<p><img src="https://img.085404.xyz/images/74df984629aef1f04f45bcbf1c97aca3.webp" alt="image.png" /></p>
<h4>切换窗格</h4>
<p>显然，屏幕上有这么多块，需要来回切换。这里有若干种方式：</p>
<ol>
<li>参见文末的<strong>开启鼠标控制</strong>一节。开启后，就可以进行符合直觉的鼠标点击切换了。</li>
<li>指令</li>
<li>快捷键</li>
</ol>
<p>下面针对 2,3 两点进行介绍。</p>
<p>指令切换</p>
<pre><code># 查看当前 pane
tmux lsp  # lsp = list pane

# 类似于上面的 select-window, pane 也可以 select
tmux select-pane -t &lt;target-pane&gt;
</code></pre>
<p>快捷键</p>
<pre><code>ctrl + b + %  垂直分割
ctrl + b +  "  水平分割
ctrl + b + q 显示每个窗格是第几个，当数字出现的时候按数字几就选中第几个窗格
</code></pre>
<h4>杀死窗格</h4>
<pre><code># 指令
tmux kill-pane -t &lt;target-pane&gt;

# 快捷键
ctrl + b + x  关闭窗格
</code></pre>
<h2>配置 Tmux</h2>
<h3>开启鼠标控制</h3>
<pre><code># 创建Tmux配置文件
vim ~/.tmux.conf

# 添加以下内容开启鼠标控制
set -g mouse on

# 重载Tmux配置
tmux source-file ~/.tmux.conf
</code></pre>
<p>即时生效，无需重启 Tmux</p>
<h3>中文显示异常</h3>
<p>当 Tmux 的中文显示异常时（例如全部变为了下划线 <code>_</code>），可以尝试如下方式：</p>
<pre><code>vim ~/.bashrc # 或者 vim ~/.zshrc
# 添加如下内容
export LANG=en_US.UTF-8 
# 生效环境变量
source ~/.bashrc # 或者 source ~/.zshrc
</code></pre>
<blockquote>
<p>这个选项 <code>.zshrc</code> 默认就有，只不过被注释掉了</p>
</blockquote>
<p>也可以采用 <code>-u</code> 参数强制 Tmux 使用 utf8：</p>
<pre><code># 使用 tmux 时添加 -u 参数
tmux -u new -s xxx
</code></pre>
<p>或者干脆使用 alias：</p>
<pre><code>alias tmux='tmux -u'
</code></pre>
]]></content>
    <author><name>Sun2ot</name></author>
    <category term="教程"/>
  </entry>
  <entry>
    <title>Linux 部署 MATLAB</title>
    <link href="https://blog.085404.xyz/posts/linux-%E9%83%A8%E7%BD%B2-matlab/" rel="alternate" type="text/html"/>
    <id>https://blog.085404.xyz/posts/linux-%E9%83%A8%E7%BD%B2-matlab/</id>
    <published>2024-03-25T00:00:00.000Z</published>
    <updated>2024-03-25T00:00:00.000Z</updated>
    <summary></summary>
    <content type="html"><![CDATA[<h2>Warning</h2>
<ol>
<li>MATLAB 是收费软件，且学校没有购买授权，因此只能安装破解版。</li>
<li>本教程全过程需 root 权限，但完成后普通用户可以使用。</li>
</ol>
<h2>下载 MATLAB 镜像及破解补丁</h2>
<p>这里提供 R2019b 和 R2020a 两个版本。其中前者为服务器已经部署的版本，后者为网络资源，可用性未知，仅作参考。</p>
<pre><code># R2019b
链接：https://pan.baidu.com/s/1AZ_LmyzfTUJ4nGzOBaTghg 
提取码：7pso 
--来自百度网盘超级会员V6的分享

# R2020a
链接：https://pan.baidu.com/s/1LHSmd7nC8x6QmziyYt6whQ 
提取码：cmmd 
--来自百度网盘超级会员V6的分享
</code></pre>
<p>由于属于盗版资源，因此无直链可以提供。但可以通过 Alist 中转请求的方式满速下载到服务器（前提是你有百度网盘的会员）。</p>
<pre><code>curl -L -X GET "https://xxx/d/xxx/R2019b_Linux.iso" -H "User-Agent: pan.baidu.com" -o R2019b_Linux.iso
</code></pre>
<blockquote>
<p>之所以不先下载再上传，是因为司马校园网。希望后来者的实验条件不要如此艰苦。。。</p>
</blockquote>
<h2>安装 MATLAB</h2>
<pre><code># 创建临时挂载路径
mkdir /mnt/matlab

# 挂载 iso 镜像
## -o loop: 将文件视为块设备。这样可以让你像挂载硬盘分区一样挂载一个文件。
mount -o loop R2019b_Linux.iso /mnt/matlab

# 退出挂载路径后再安装
cd ~
# 执行指令静默安装
## 注意替换参数：
## - destinationFolder: 安装路径
## - activationPropertiesFile: 破解补丁路径
/mnt/matlab/install -mode silent -fileInstallationKey 09806-07443-53955-64350-21751-41297 -agreeToLicense yes -activationPropertiesFile Matlab-R2019b-Linux64-Crack/license_standalone.lic -destinationFolder /opt/matlab2019b/

# 也可以直接 /mnt/matlab/install，不过需要打开 X11 显示，然后对照着上面的信息在 GUI 中填进去。听不懂的话就略过这一段吧。

# 等待安装...很久...大概10min?
</code></pre>
<p>输出如下结果即安装完成：</p>
<pre><code>(三月 25, 2024 19:00:27) Exiting with status 0
(三月 25, 2024 19:00:29) End - Successful.
Finished
</code></pre>
<h2>注入破解补丁</h2>
<p>注意，是破解补丁包里的文件往安装路径下复制。</p>
<pre><code># 解压破解补丁包
tar -zxvf Matlab-R2019b-Linux64-Crack.tar.gz

# 注入启动组件破解补丁
cp Matlab-R2019b-Linux64-Crack/R2019b/bin/glnxa64/* /opt/matlab2019b/bin/glnxa64/ -r

# 注入破解许可证
cp Matlab-R2019b-Linux64-Crack/license_standalone.lic /opt/matlab2019b/licenses
</code></pre>
<p>至此，root 用户应该可以执行 <code>/opt/matlab2019b/bin/matlab</code> 启动MATLAB(需开启 X11)。但普通用户尚且无法启动，仍会提示需要激活，原因是相关破解补丁权限为 root，普通用户无法读取，因此需要修改相关权限。</p>
<pre><code># 新增 matlab 用户组
groupadd matlab
# 将需要使用 matlab 的用户添加进组
usermod -aG matlab u_23_1,yzh
# 修改 matlab 文件的所属组为 matlab
chown root:matlab ./matlab2019b/ -R
# 修改 matlab 文件的权限为 775
chmod 775 matlab2019b/ -R
</code></pre>
<p>至此，普通用户可通过 <code>/opt/matlab2019b/bin/matlab</code> 顺利启动 MATLAB。如需配置环境变量，在 <code>.bashrc</code> 或 <code>.zshrc</code> 中添加<code>export PATH=/opt/matlab2019b/bin:$PATH</code> 即可。</p>
]]></content>
    <author><name>Sun2ot</name></author>
    <category term="教程"/>
  </entry>
  <entry>
    <title>通过kaggle api下载数据集</title>
    <link href="https://blog.085404.xyz/posts/%E9%80%9A%E8%BF%87kaggle-api%E4%B8%8B%E8%BD%BD%E6%95%B0%E6%8D%AE%E9%9B%86/" rel="alternate" type="text/html"/>
    <id>https://blog.085404.xyz/posts/%E9%80%9A%E8%BF%87kaggle-api%E4%B8%8B%E8%BD%BD%E6%95%B0%E6%8D%AE%E9%9B%86/</id>
    <published>2024-03-24T00:00:00.000Z</published>
    <updated>2024-03-24T00:00:00.000Z</updated>
    <summary></summary>
    <content type="html"><![CDATA[<h2>获取 Token</h2>
<p>点击右上角头像，菜单中进入 <code>Settings</code></p>
<p><img src="https://img.085404.xyz/images/c0e351ceeccdd0343f7bb5fc2ac237d4.webp" alt="image.png|200x250" /></p>
<p>点击 <code>Create New Token</code> 生成新的 Token，浏览器会自动下载鉴权文件：</p>
<p><img src="https://img.085404.xyz/images/452f1905933f89f5511b7e7ef3f48ce3.webp" alt="image.png" /></p>
<p><img src="https://img.085404.xyz/images/bf71c933b12876accbfeb6efc6202271.webp" alt="image.png" /></p>
<p>注意这句话：<code>Ensure kaggle.json is in the location ~/.kaggle/kaggle.json to use the API.</code>，意思是需要将下载的 <code>kaggle.json</code> 放到 Linux 服务器的 <code>~/.kaggle/</code> 下。</p>
<pre><code>cd ~
mv -r kaggle.json ~/.kaggle/
</code></pre>
<h2>下载数据集</h2>
<p>首先下载 kaggle 指令：</p>
<pre><code>pip install kaggle
</code></pre>
<p>在 kaggle 上找到要下载的数据集首页，然后点击右上角菜单，复制 API 指令：</p>
<p><img src="https://img.085404.xyz/images/6e42b7021108d4ed9ef313330b930eb2.webp" alt="image.png" /></p>
<p>复制的指令如下：</p>
<pre><code>kaggle datasets download -d yelp-dataset/yelp-dataset
</code></pre>
<h3>下载指定版本数据集</h3>
<p>下拉 kaggle 数据集页面，在页面的右侧会看到 <code>Data Explorer</code>。默认情况下下载的是最新版数据集。</p>
<p><img src="https://img.085404.xyz/images/64aaa23b0d1a943960c9493c81634dc8.webp" alt="image.png" /></p>
<p>以图中的 <code>yelp</code> 数据集为例，我并不需要最新的 <code>yelp</code> 数据集，而是 <code>yelp2018</code>。因此点击图中的 <code>Version 4</code>，在弹出窗口中选择想要的版本即可。</p>
<p>但比较蛋疼的是，经过本人的不屑研究，没有找到通过 kaggle api 下载历史版本数据集的方法（我指的是 kaggle CLI）。因此迫不得已，只能使用邪术了。</p>
<ol>
<li>在服务器<a href="https://blog.085404.xyz/method/aria2.html">部署 aria2</a></li>
<li><code>F12</code> 大法进入浏览器开发者页面，然后点击下载，从 <code>网络</code> 选项卡找到下载链接并复制（如果有 idm 等插件直接嗅探就行了）</li>
<li>用服务器通过 aria2 下载</li>
</ol>
]]></content>
    <author><name>Sun2ot</name></author>
    <category term="教程"/>
  </entry>
  <entry>
    <title>AI 工具收集贴</title>
    <link href="https://blog.085404.xyz/posts/ai-%E5%B7%A5%E5%85%B7%E6%94%B6%E9%9B%86%E8%B4%B4/" rel="alternate" type="text/html"/>
    <id>https://blog.085404.xyz/posts/ai-%E5%B7%A5%E5%85%B7%E6%94%B6%E9%9B%86%E8%B4%B4/</id>
    <published>2024-03-22T00:00:00.000Z</published>
    <updated>2024-03-22T00:00:00.000Z</updated>
    <summary></summary>
    <content type="html"><![CDATA[<h2>对话生成</h2>
<ol>
<li>以下排名不分先后，脑子想到哪写到哪</li>
<li>背景不是<strong>大资本</strong> = 没钱 + 限制多 + 不好用，故不予收录</li>
</ol>
<h3>国内</h3>
<h4>文心一言</h4>
<ul>
<li>级别：臭🐟烂🦐</li>
<li>背景：百度</li>
<li>地址： https://yiyan.baidu.com/</li>
<li>简评：v3.0 难用的一比，v4.0 收费贼贵，还不能免费试用，宏颜获水。</li>
</ul>
<h4>通义千问</h4>
<ul>
<li>级别：🌟能用</li>
<li>背景：阿里</li>
<li>地址： https://tongyi.aliyun.com/</li>
<li>简评：毕竟是码农心里的白月光，对话一般，写代码还凑活。</li>
<li>相关产品：
<ul>
<li><a href="https://tongyi.aliyun.com/tingwu">通义听悟</a>：语音转文字 yyds</li>
<li><a href="https://tongyi.aliyun.com/lingma">通义灵码</a>：支持 vscode 插件，类似 copilot，不用我多说了吧</li>
</ul>
</li>
</ul>
<h4>智谱清言</h4>
<ul>
<li>级别：🎁好用</li>
<li>背景：清华</li>
<li>地址： https://chatglm.cn/</li>
<li>简评：4.0 免费使用，而且支持检索互联网内容，堪称低配版 gpt4.0 + copilot</li>
<li>备注：善用智能体有奇效。这个智能体不就是 prompt 市场吗，不过能铺开确实很舒服，不用自己编写 prompt 了。</li>
</ul>
<h4>腾讯混元</h4>
<ul>
<li>级别：臭🐟烂🦐</li>
<li>背景：腾讯</li>
<li>地址： https://hunyuan.tencent.com/</li>
<li>简评：是个小程序，这一点就够判它死刑了。更何况腾讯自己内部都觉得不好用，也好意思推出来？</li>
</ul>
<h4>讯飞星火</h4>
<ul>
<li>级别：❓</li>
<li>背景：科大讯飞</li>
<li>地址： https://xinghuo.xfyun.cn/</li>
<li>简评：按理说讯飞输入法这么大的招牌，大模型语料数据应该不缺的。不过没有深度使用过，故级别未知。</li>
</ul>
<h4>豆包</h4>
<ul>
<li>级别：❓</li>
<li>背景：字节跳动</li>
<li>地址： https://www.doubao.com/chat/</li>
<li>简评：可惜了，我不刷抖音，这玩意降智。</li>
</ul>
<h4>Kimi</h4>
<ul>
<li>级别：❓</li>
<li>背景：中信建投</li>
<li>地址： https://kimi.moonshot.cn/</li>
<li>简评：Tg 上看到的，据称是国产大模型的神。主要是长文本相当牛逼（200W）。而且背景是中信建投。。。</li>
</ul>
<h4>天工</h4>
<ul>
<li>级别：❓</li>
<li>背景：昆仑万维（什么领域都想横插一脚的<strong>投资公司</strong>）</li>
<li>地址： https://work.tiangong.cn/</li>
<li>简评：大模型一吹，股价翻倍。</li>
</ul>
<h3>国外</h3>
<blockquote>
<p>请在富强的网络条件下使用。</p>
</blockquote>
<h4>ChatGPT</h4>
<ul>
<li>级别：🎁好用</li>
<li>背景：OpenAI</li>
<li>地址： https://chat.openai.com/</li>
<li>简评：鼻祖。不过只要一用反问语气模型就立马变卦，对中文有点不太友好，建议英文对话。</li>
</ul>
<h4>Gemini</h4>
<ul>
<li>级别：❓</li>
<li>背景：Google</li>
<li>地址： https://gemini.google.com/</li>
<li>简评：不支持香港节点，对分流策略有点要求。</li>
</ul>
<h2>OpenAI Api 站点</h2>
<blockquote>
<p>强烈推荐自建前端 + Api 的方式使用。</p>
</blockquote>
<ul>
<li>https://api.aigcbest.top/</li>
<li>https://home.nextapi.fun/</li>
</ul>
]]></content>
    <author><name>Sun2ot</name></author>
    <category term="日常"/>
  </entry>
  <entry>
    <title>北民大校园网对抗手册</title>
    <link href="https://blog.085404.xyz/posts/%E5%8C%97%E6%B0%91%E5%A4%A7%E6%A0%A1%E5%9B%AD%E7%BD%91%E5%AF%B9%E6%8A%97%E6%89%8B%E5%86%8C/" rel="alternate" type="text/html"/>
    <id>https://blog.085404.xyz/posts/%E5%8C%97%E6%B0%91%E5%A4%A7%E6%A0%A1%E5%9B%AD%E7%BD%91%E5%AF%B9%E6%8A%97%E6%89%8B%E5%86%8C/</id>
    <published>2024-03-18T00:00:00.000Z</published>
    <updated>2024-03-18T00:00:00.000Z</updated>
    <summary></summary>
    <content type="html"><![CDATA[<h2>DNS</h2>
<p>校园网给出的三个 DNS，经反查后对应信息为</p>
<pre><code>202.201.112.1    dns.nmu.edu.cn
202.100.96.68  宁夏银川市 电信DNS服务器
221.199.12.157   宁夏银川市 大博金网吧(胜利南街永春巷27号)
</code></pre>
<p>校园网服务商为电信，连接校园网需要校内验证，综上前两个 DNS 问题不大。第二个视情况可以挑选一个西夏区的电信 DNS。不过第三个就有点抽象了。。。</p>
<h2>Against NUN</h2>
<p><code>2024年03月18日 18:58:53</code>，经运维人员证实，校园网锁死 10Mbps，特此启动<strong>Against NUN</strong>计划。</p>
<h3>双网卡策略</h3>
<h4>ssh 连接问题</h4>
<p>如果去搜索指定网卡进行 ssh 连接，大概率会从 CSDN 上看到一个互相抄来抄去的解决方案：通过 <code>ssh -B/-b</code> 绑定建立连接的 IP/网卡。但是情况是 Windows 并不支持这种方式。</p>
<pre><code>ssh -B -h
usage: ssh [-46AaCfGgKkMNnqsTtVvXxYy] [-B bind_interface]
           [-b bind_address] [-c cipher_spec] [-D [bind_address:]port]
           [-E log_file] [-e escape_char] [-F configfile] [-I pkcs11]
           [-i identity_file] [-J [user@]host[:port]] [-L address]
           [-l login_name] [-m mac_spec] [-O ctl_cmd] [-o option] [-p port]
           [-Q query_option] [-R address] [-S ctl_path] [-W host:port]
           [-w local_tun[:remote_tun]] destination [command]

BindInterface not supported on this platform.
</code></pre>
<p>所以只能通过修改路由表解决。首先找到连接校园网的网卡的 IP 地址：</p>
<pre><code>ipconfig /all

# 以下内容省略了不重要的部分

无线局域网适配器 WLAN 2:
   连接特定的 DNS 后缀 . . . . . . . :
   描述. . . . . . . . . . . . . . . : MERCURY Wireless N Adapter
   物理地址. . . . . . . . . . . . . : D4-84-09-5C-ED-6F
   IPv4 地址 . . . . . . . . . . . . : 172.26.15.89(首选)
</code></pre>
<p>接着，在管理员模式下，添加路由表记录：</p>
<pre><code>route add 172.16.16.242 mask 255.255.255.255 172.26.15.89
</code></pre>
<p>这会将前往 <code>172.16.16.242</code> 的请求路由到 <code>172.26.15.89</code>，也就是校园网给网卡 2 分配的 IP 地址。</p>
<p>当然，可能你会发现路由表中，新增的这条记录跃点数较低：</p>
<pre><code>route print


IPv4 路由表
===========================================================================
活动路由:
网络目标        网络掩码          网关       接口   跃点数
0.0.0.0          0.0.0.0    192.168.15.58   192.168.15.141     30
0.0.0.0          0.0.0.0   172.26.255.254     172.26.15.89     55
127.0.0.0        255.0.0.0            在链路上         127.0.0.1    331
127.0.0.1         255.255.255.255            在链路上         127.0.0.1    331
127.255.255.255  255.255.255.255            在链路上         127.0.0.1    331
172.16.16.242       255.255.255.255            在链路上      172.26.15.89     56
</code></pre>
<p>但路由选择的过程是这样的：</p>
<ol>
<li>路由器查找路由表，寻找与目标 IP 地址最匹配的条目（与掩码进行与运算）。</li>
<li>如果找到精确匹配（如 /32 或 255.255.255.255），则直接发送到那个地址，因为目标就在本地网络上。</li>
<li>如果没有精确匹配，路由器会查找具有最长网络前缀匹配的条目（即最具体的网络掩码）。</li>
<li>如果没有找到具体的匹配项，路由器会使用默认路由（0.0.0.0/0）来路由数据包，并且在这种情况下，会选择跃点数最低的默认路由。</li>
</ol>
<p>删除路由：</p>
<pre><code>route delete 172.16.16.242 mask 255.255.255.255
</code></pre>
<p>添加或修改路由（如果需要指定新的下一跳地址）：</p>
<pre><code>route add 172.16.16.242 mask 255.255.255.255 172.26.15.90
</code></pre>
<h4>内网访问</h4>
<pre><code>网络目标        网络掩码          网关       接口   跃点数
0.0.0.0          0.0.0.0    192.168.15.58   192.168.15.141     30
0.0.0.0          0.0.0.0   172.26.255.254     172.26.15.89     55
</code></pre>
<p>根据路由表能看出，校园网路由的跃点为 55，大于手机热点的跃点。即使断开热点再连上，也不过是两个路由记录的顺序调换一下，跃点数仍然一大一小。这导致热点的默认网关优先级更高，导致内网无法访问。</p>
<p>不过在翻了翻 <a href="https://wiki.metacubex.one/config/general/#_12">虚空终端 Docs</a> 之后，找到了出站接口的设置参数。因此，只需要在 mihomo 的配置文件指定流量出口的网卡即可解决。</p>
<pre><code>interface-name: WLAN
</code></pre>
<p>这里的网卡名称通过 <code>ipconfig /all</code> 可以查到，“无线局域网适配器”后面的内容就是网卡名称：</p>
<pre><code>ipconfig /all

无线局域网适配器 WLAN
......
</code></pre>
<p>当然，直接通过控制面板或者 powershell 去修改对应网络的跃点数也行，不过这样更麻烦不是吗。</p>
]]></content>
    <author><name>Sun2ot</name></author>
    <category term="日常"/>
  </entry>
  <entry>
    <title>Word 插件兼容性解决方案</title>
    <link href="https://blog.085404.xyz/posts/word-%E6%8F%92%E4%BB%B6%E5%85%BC%E5%AE%B9%E6%80%A7%E8%A7%A3%E5%86%B3%E6%96%B9%E6%A1%88/" rel="alternate" type="text/html"/>
    <id>https://blog.085404.xyz/posts/word-%E6%8F%92%E4%BB%B6%E5%85%BC%E5%AE%B9%E6%80%A7%E8%A7%A3%E5%86%B3%E6%96%B9%E6%A1%88/</id>
    <published>2024-03-16T00:00:00.000Z</published>
    <updated>2024-03-16T00:00:00.000Z</updated>
    <summary></summary>
    <content type="html"><![CDATA[<h1>Word 插件兼容性解决方案</h1>
<h2>WPS 个人版启用 VBA 环境</h2>
<blockquote>
<p>[!bug] 正常情况下企业版内置，个人版需要购买授权才能用。</p>
</blockquote>
<p>虽然 WPS 提示要安装 VBA，但实测安装了 VBA7.1 后不起作用，后来看了下网上说的不是 VBA，而是 <strong>WPS VBA 插件</strong>。然后就去下了一个试试：</p>
<p>链接： https://pan.baidu.com/s/16GKbt95d9iCAviKMj2OcOg
提取码：e27x</p>
<p>果不其然，并不是 VBA 的 msi 程序，而是一个 exe，运行后出现的是 <code>kingsoft</code> 界面，看来是给 WPS 软件打的补丁。</p>
<p>安装完成后，即可正常使用 zotero 等软件。</p>
<h2>WPS 中启用 MathType</h2>
<blockquote>
<p>[!warning] 适用于较新的 WPS 12.0+ 以及 MathType 7.0+，其他版本未做测试！</p>
</blockquote>
<p>与 Zotero 不同，MathType 这玩意即使安装完成也不会自动配置 WPS，具体体现在两个问题：</p>
<ol>
<li>WPS 启动后没有 MathType 选项卡（缺少了相应的 <code>dotm</code>）</li>
<li>加载 <code>dotm</code> 以显示选项卡后，插入公式会报错找不到 <code>MathPage.wll</code></li>
</ol>
<p>下面分别介绍如何解决。</p>
<h3>让 WPS 显示   MathType 选项卡</h3>
<p>对于 <code>dotm</code> 文件的问题，不要试图在<strong>工具-加载项</strong>这里添加。在这里添加虽然可以正常显示选项卡，但不会随着 WPS 自启动。所以你需要做的是：</p>
<ol>
<li>找到 MathType 的安装路径
<blockquote>
<p>什么？你不知道怎么找？右键 MathType 的快捷方式，点击 <code>打开文件所在位置</code>。你要是连快捷方式也不知道怎么找的话，建议 remake</p>
</blockquote>
</li>
<li>进入 <code>\Office Support\32</code> 这个路径，这里面存放着适用于 32 位 office 的 <code>dotm</code> 模板文件
<blockquote>
<p>至于为什么是 32 位，那得问问金山为什么 2024 年还在生产 32 位的电子垃圾了。别扯什么兼容性，提供 32/64 两种安装包就能解决的问题。</p>
</blockquote>
</li>
<li>找到 <code>MathType Commands 2016.dotm</code> 这个文件，复制粘贴到 <code>*\WPS Office\12.1.0.16412\office6\startup</code> 路径下。
<blockquote>
<p>这个路径是你 WPS 的安装路径，切记<strong>粘贴到版本号最新的那个路径下</strong>，因为我们的 WPS 居然把占用庞大的历史版本软件也保存在了用户机上，真是国产之光呢^_^</p>
</blockquote>
</li>
</ol>
<p>至此，重启 WPS，应该就可以看到 MathType 的选项卡了，不过应该是无法使用的。</p>
<h3>WPS 中使用 MathType 报错找不到 mathpage.wll 文件</h3>
<p>解决步骤如下：</p>
<ol>
<li>找到 MathType 安装路径，进入 <code>\MathPage\32</code></li>
<li>里面只有一个 <code>MathPage.wll</code>，依然复制粘贴到 <code>*\WPS Office\12.1.0.16412\office6\startup</code> 路径下</li>
</ol>
<blockquote>
<p>[!warning] <code>dotm</code> 文件和 <code>MathPage.wll</code> 文件需要一一对应，要么全是 32 位，要么全是 64 位！</p>
</blockquote>
<h2>WPS 启用 Zotero</h2>
<p>正常情况下，WPS 会自动加载属于 Office Word 的 <code>Zotero.dotm</code>，即 <code> </code> <code>C:\Users\y2pub\AppData\Roaming\Microsoft\Word\Startup\Zotero.dotm</code>。但此时可能由于软件升级或别的什么原因，导致 WPS 出现异常，即可见选项卡但无法使用 Zotero。</p>
<p>因此，找到 <code>Zotero安装路径/extensions</code>，打开文件夹，里面有一个 <code>Zotero.dotm</code></p>
<blockquote>
<p>如果是 Zotero7 的测试版，该文件在 <code>Zotero安装路径/integ ration/word-for-windows/</code> 下。</p>
</blockquote>
<p>将其<strong>复制</strong>到 <code>C:\Users\y2pub\AppData\Roaming\Microsoft\Word\Startup\</code>。然后重启 WPS 即可。</p>
<p>当然，你也可以选择给两个软件分别配置 <code>Zotero.dotm</code>，注意事项，见上文<a href="#%E8%AE%A9%20WPS%20%E6%98%BE%E7%A4%BA%20MathType%20%E9%80%89%E9%A1%B9%E5%8D%A1">让 WPS 显示 MathType 选项卡</a>。</p>
<h2>关于 Office Word</h2>
<ol>
<li>开启 MathType 选项卡的方式与 WPS 类似，只是路径不同，为：<code>C:\Program Files (x86)\Microsoft Office\root\Office16\STARTUP</code></li>
<li>报错 <code>mathpage.wll</code> 时，相关路径为：<code>C:\Program Files (x86)\Microsoft Office\root\Office16</code></li>
</ol>
<blockquote>
<p>有的是 x86 路径，有的是 x64 路径，都看看</p>
</blockquote>
<p>注意，office 365 似乎无需手动配置，默认启动。</p>
]]></content>
    <author><name>Sun2ot</name></author>
    <category term="教程"/>
  </entry>
  <entry>
    <title>Windows 11 Dev Drive</title>
    <link href="https://blog.085404.xyz/posts/windows-11-dev-drive/" rel="alternate" type="text/html"/>
    <id>https://blog.085404.xyz/posts/windows-11-dev-drive/</id>
    <published>2024-03-11T00:00:00.000Z</published>
    <updated>2024-03-11T00:00:00.000Z</updated>
    <summary></summary>
    <content type="html"><![CDATA[<h1>Windows 11 Dev Drive</h1>
<blockquote>
<p>在 2023 年的微软开发者大会上，推出了 Dev Drive 新存储卷。从今天开始，Dev Drive 将面向所有使用最新 Windows 11 的 Windows 开发者开放。</p>
</blockquote>
<p>对于这个巨硬推出的新玩具，网上相关资料较少，因此这里做一个记录。</p>
<p><a href="https://learn.microsoft.com/zh-cn/windows/dev-drive/">About Dev Drive on Windows 11 by Microsoft Official</a></p>
<h2>受信与反病毒策略</h2>
<p>管理员模式运行 powershell：</p>
<pre><code># 将开发驱动器指定为受信任的驱动器
fsutil devdrv trust &lt;drive-letter&gt;:

# 查询开发驱动器是否受信任
fsutil devdrv query &lt;drive-letter&gt;:
</code></pre>
<blockquote>
<p>不能将计算机上的 C: 驱动器指定为开发驱动器。开发人员工具（如 Visual Studio、MSBuild、.NET  SDK、Windows SDK）应存储在 C:/ 驱动器上，而不是存储在开发驱动器中！</p>
</blockquote>
<p>默认情况下，<a href="https://learn.microsoft.com/zh-cn/windows-hardware/drivers/ifs/filter-manager-concepts">筛选器管理器</a> 将关闭开发驱动器上的所有筛选器，防病毒筛选器除外。当然，我们也可以关闭。</p>
<p>如果你的电脑上运行了 Windows Defender 或者 360 之类的安全软件，执行 <code>fsutil devdrv query</code> 大概率会看到有相关字样。由于个人小鸡基本不存在病毒攻击的问题，因此这里我们可以将防病毒策略完全关闭以提高开发驱动器的性能：</p>
<pre><code>fsutil devdrv enable /disallowAv
</code></pre>
<p>然后检查效果：</p>
<pre><code># 再次查询
fsutil devdrv query &lt;drive-letter&gt;:

# 结果如下
This isNo filters are currently attached to this developer volume.
</code></pre>
<h2>自动挂载</h2>
<p>由于新电脑有两个硬盘位，所以在从盘位划分了一块作为 Dev driver。但奇怪的是，虽然正常使用，但开机、重启时无法自动挂载，必须手动找到 <code>VHDX</code> 文件进行挂载。暂不清楚是什么原因导致的，但总之，在微软 devhome 项目的 <a href="https://github.com/microsoft/devhome/issues/1903#issuecomment-1916919629">issue #1903</a> 找到了解决方案。</p>
<p>简单来说就是新建一个计划任务，然后用 <code>diskpart.exe</code> 执行一段磁盘挂载的脚本即可。</p>
]]></content>
    <author><name>Sun2ot</name></author>
    <category term="教程"/>
  </entry>
  <entry>
    <title>Windows 计划任务备份方案</title>
    <link href="https://blog.085404.xyz/posts/windows-%E8%AE%A1%E5%88%92%E4%BB%BB%E5%8A%A1%E5%A4%87%E4%BB%BD%E6%96%B9%E6%A1%88/" rel="alternate" type="text/html"/>
    <id>https://blog.085404.xyz/posts/windows-%E8%AE%A1%E5%88%92%E4%BB%BB%E5%8A%A1%E5%A4%87%E4%BB%BD%E6%96%B9%E6%A1%88/</id>
    <published>2024-03-11T00:00:00.000Z</published>
    <updated>2024-03-11T00:00:00.000Z</updated>
    <summary></summary>
    <content type="html"><![CDATA[<h2>任务需求</h2>
<p>需要对位于 <code>D:\</code> 下的 obsidian 文件夹中的所有文件进行定时全量备份。</p>
<h2>操作步骤</h2>
<p>快捷键按 Win+R 打开运行输入 taskschd.msc 打开 “任务计划程序”，创建一个基本任务。</p>
<p><img src="https://img.085404.xyz/images/43716f61f311931735ac142f0648f95e.webp" alt="image.png" /></p>
<p>名称可以自己任意填，描述可不填。</p>
<p><img src="https://img.085404.xyz/images/b25441b33b4df4ec8617e7b5bca938b4.webp" alt="image.png" /></p>
<p>点击下一页，任务触发器选择每天</p>
<p><img src="https://img.085404.xyz/images/ef192ec16a77db9c953c7ddf873605da.webp" alt="image.png" /></p>
<p>设置每天自动备份的具体时间，设置完成后点击 “下一步” 按钮。</p>
<p><img src="https://img.085404.xyz/images/f2ea62e165cc9158492aceaee92ed2fb.webp" alt="image.png" /></p>
<p>选择要启动的程序，填入相应参数，然后点击 “下一步” 按钮。这里我设置的任务是调用 powershell 执行对应<a href="https://gist.github.com/sun2ot/9d27ebeaae4a4294b335f0b8349de5f1">脚本</a>，实现将路径下文件打成压缩包后移动到另一个盘实现备份效果。</p>
<p><img src="https://img.085404.xyz/images/6ec1b3189c0962aa746b9b5e16aa2eff.webp" alt="image.png" /></p>
<blockquote>
<p>注意这里的程序路径如果有空格，切记加引号。或者点击 <code>浏览</code> 一步一步选中目标程序即可。</p>
</blockquote>
<p>填好后点击 “下一步”，然后点击完成按钮，保存任务计划。</p>
<p><img src="https://img.085404.xyz/images/c520c023a1a165bcdf9598fffcb5ae08.webp" alt="image.png" /></p>
<p><img src="https://img.085404.xyz/images/04f40342c3413a302f4a3a0216ff4a6a.webp" alt="image.png" /></p>
<p>点击 <code>更改用户或组</code></p>
<p><img src="https://img.085404.xyz/images/be447e5cab740790267a008c4575bc3e.webp" alt="image.png" /></p>
<p>注意，如果前面的设置有误，或者想要修改，这里是可以更改的（上方的菜单栏）</p>
<p><img src="https://img.085404.xyz/images/b74fc94615f76d26243024efb43bcc7d.webp" alt="image.png" /></p>
<p>在条件中，取消勾选电源选项</p>
<p><img src="https://img.085404.xyz/images/35da9641b0b8441a0c10b25ed8dc8d6f.webp" alt="image.png" /></p>
<p>在设置中，勾选如上选项</p>
<p>设置完成后，右键任务尝试运行</p>
<p><img src="https://img.085404.xyz/images/36241eeeee479aa8d9181482ce7f46d1.webp" alt="image.png" /></p>
]]></content>
    <author><name>Sun2ot</name></author>
    <category term="教程"/>
  </entry>
  <entry>
    <title>git push 密码验证失败</title>
    <link href="https://blog.085404.xyz/posts/git-push-%E5%AF%86%E7%A0%81%E9%AA%8C%E8%AF%81%E5%A4%B1%E8%B4%A5/" rel="alternate" type="text/html"/>
    <id>https://blog.085404.xyz/posts/git-push-%E5%AF%86%E7%A0%81%E9%AA%8C%E8%AF%81%E5%A4%B1%E8%B4%A5/</id>
    <published>2024-03-10T00:00:00.000Z</published>
    <updated>2024-03-10T00:00:00.000Z</updated>
    <summary></summary>
    <content type="html"><![CDATA[<h1>git push 密码验证失败</h1>
<h2>原因</h2>
<p>通过 <code>git push</code> 提交时可能会碰到如下错误。字面意思很清楚，密码验证的方法已经在 2021 年 8 月 13 号后就失效了，因此我们需要改用 token 鉴权。</p>
<pre><code>git push

# 输出
Username for 'https://github.com': sun2ot
Password for 'https://sun2ot@github.com':
remote: Support for password authentication was removed on August 13, 2021.
remote: Please see https://docs.github.com/en/get-started/getting-started-with-git/about-remote-repositories#cloning-with-https-urls for information on currently recommended modes of authentication.
fatal: Authentication failed for 'https://github.com/sun2ot/xxx.git/'
</code></pre>
<h2>解决方案</h2>
<h3>方式一：使用 token</h3>
<p>github 头像下拉菜单——settings——新页面左侧导航栏最后一项 developer settings</p>
<p>personal access token——Tokens (classic)</p>
<blockquote>
<p>经典模式的 token 可以设置<strong>永不过期</strong>，用起来比较方便，当然，被盗了就很不安全，所以自行取舍。</p>
</blockquote>
<p>生成的 token 作用范围勾选 <code>repo</code> 就足够了，你也可以全勾上，当做万能 token。</p>
<p>然后进入项目根目录</p>
<pre><code>git remote set-url origin https://&lt;your token&gt;@github.com/&lt;your name&gt;/&lt;repo&gt;.git
</code></pre>
<p>以后就可以直接 <code>git push</code> 了。</p>
<h3>方式二：使用 ssh</h3>
<blockquote>
<p>使用该方法的前提是已经配置了 GitHub ssh 密钥！</p>
</blockquote>
<pre><code># 查看远程地址别名及路径
git remote -v

origin  https://github.com/kkeeliu/doubao_community_frontend.git (fetch)
origin  https://github.com/kkeeliu/doubao_community_frontend.git (push)

# 修改远程地址
git remote set-url origin git@github.com:&lt;your name&gt;/&lt;repo&gt;.git

# 查看修改是否成功
git remote -v
</code></pre>
]]></content>
    <author><name>Sun2ot</name></author>
    <category term="教程"/>
  </entry>
  <entry>
    <title>git 代理</title>
    <link href="https://blog.085404.xyz/posts/git-%E4%BB%A3%E7%90%86/" rel="alternate" type="text/html"/>
    <id>https://blog.085404.xyz/posts/git-%E4%BB%A3%E7%90%86/</id>
    <published>2024-03-10T00:00:00.000Z</published>
    <updated>2024-03-10T00:00:00.000Z</updated>
    <summary></summary>
    <content type="html"><![CDATA[<h1>git 代理</h1>
<pre><code># 设置
git config --global http.proxy "socks5://127.0.0.1:7890"  # clash的socks5端口是7890
git config --global https.proxy "socks5://127.0.0.1:7890" # 根据实际情况做修改
</code></pre>
<pre><code># 恢复
git config --global --unset http.proxy
git config --global --unset https.proxy
</code></pre>
<p>git 的配置范围有三种，一般情况下可以选择不加 <code>--global</code> 参数，实现局部（也就是当前仓库）配置，毕竟不是每一个库都需要代理吧。</p>
<pre><code># 查看当前git config
# 系统
git config --system --list
# 全局
git config --global --list
# 当前仓库
git config --local --list
</code></pre>
<p>如果是在克隆时需要临时使用代理，也可以使用命令行参数实现：</p>
<pre><code># http/https 代理
git clone --config http.proxy=http://&lt;proxy_address&gt;:&lt;port&gt; &lt;repository_url&gt;

# socks5 代理
git clone --config http.proxy=socks5://&lt;proxy_address&gt;:&lt;port&gt; &lt;repository_url&gt;
</code></pre>
<p>注意，SSH 默认不会走 Windows 下的系统代理，更不会走 Linux 的后台代理，因此，如果发现通过 SSH 进行 <code>git clone</code> 时报错，可以排查是否为网络问题。如果是，需要配置 ssh config 并指定代理。以下是 Linux 的 SSH 代理设置方式，需借助 <code>netcat</code> 实现。</p>
<pre><code>Host github.com
  Hostname ssh.github.com
  IdentityFile /home/yzh/.ssh/whr-yzh-git
  User git
  Port 443
  ProxyCommand nc -X 5 -x 127.0.0.1:7890 %h %p
</code></pre>
<p>Windows 的 ssh 代理设置方式，见 <a href="../Shell/SSH/Windows%E5%B9%B3%E5%8F%B0%E4%BB%A3%E7%90%86OpenSSH.md">Windows平台代理OpenSSH</a></p>
]]></content>
    <author><name>Sun2ot</name></author>
    <category term="教程"/>
  </entry>
  <entry>
    <title>Linux 二进制部署 nodejs</title>
    <link href="https://blog.085404.xyz/posts/linux-%E4%BA%8C%E8%BF%9B%E5%88%B6%E9%83%A8%E7%BD%B2-nodejs/" rel="alternate" type="text/html"/>
    <id>https://blog.085404.xyz/posts/linux-%E4%BA%8C%E8%BF%9B%E5%88%B6%E9%83%A8%E7%BD%B2-nodejs/</id>
    <published>2024-03-10T00:00:00.000Z</published>
    <updated>2024-03-10T00:00:00.000Z</updated>
    <summary></summary>
    <content type="html"><![CDATA[<h2>下载</h2>
<p><a href="https://nodejs.org/en/download">Download | Node.js (nodejs.org)</a></p>
<p>下载 Linux Binaries (x64)并解压</p>
<pre><code>wget https://nodejs.org/dist/v18.18.0/node-v18.18.0-linux-x64.tar.xz
tar -xJf node-v18.18.0-linux-x64.tar.xz -C /usr/local/
</code></pre>
<h2>配置环境变量</h2>
<p>修改 <code>~/.bashrc</code></p>
<pre><code>export PATH=/usr/local/node-v18.18.0-linux-x64/bin:$PATH
</code></pre>
<p>执行 <code>source ~/.bashrc</code> 生效</p>
<p>查看效果</p>
<pre><code>node -v
npm -v
</code></pre>
<blockquote>
<p>[!warning]
如果你是非 root 用户，切记 export 的路径不要用 <code>~</code> ，否则会出现 <code>node -v</code> 正常但 <code>npm -v</code> 失败的奇怪情况。原因是 <code>npm</code> 默认情况下是使用 <code>node</code> 去执行的，而你导出的路径为 <code>~</code> ，因此系统会去 <code>/usr/bin</code> 下寻找 <code>node</code> 的。而一旦管理员没有安装 <code>node</code> 环境的话，那你是无法使用 <code>node</code> 去调用 <code>npm</code> 的。</p>
</blockquote>
]]></content>
    <author><name>Sun2ot</name></author>
    <category term="教程"/>
  </entry>
  <entry>
    <title>Linux后台运行的几种方式</title>
    <link href="https://blog.085404.xyz/posts/linux%E5%90%8E%E5%8F%B0%E8%BF%90%E8%A1%8C%E7%9A%84%E5%87%A0%E7%A7%8D%E6%96%B9%E5%BC%8F/" rel="alternate" type="text/html"/>
    <id>https://blog.085404.xyz/posts/linux%E5%90%8E%E5%8F%B0%E8%BF%90%E8%A1%8C%E7%9A%84%E5%87%A0%E7%A7%8D%E6%96%B9%E5%BC%8F/</id>
    <published>2024-03-10T00:00:00.000Z</published>
    <updated>2024-03-10T00:00:00.000Z</updated>
    <summary></summary>
    <content type="html"><![CDATA[<h1>Linux后台运行的几种方式</h1>
<h2>使用进程守护工具</h2>
<blockquote>
<p>这里以 node.js 的 <a href="https://pm2.keymetrics.io/">pm2</a> 为例。Deep Leaners 建议跳过这一节。</p>
</blockquote>
<h3>部署</h3>
<pre><code># 全局安装pm2
npm i pm2 -g  

# 进入项目目录下
pm2 start index.js  --name your-project-any-name
</code></pre>
<p><img src="https://img.085404.xyz/images/cc4cc59aebdce5733aab01c9f28f1813.png" alt="image.png" /></p>
<p><strong>利用该方式可以在服务器上运行多个 Node.js 项目</strong></p>
<h3>pm2 命令</h3>
<table>
<thead>
<tr>
<th>命令</th>
<th>解释</th>
</tr>
</thead>
<tbody>
<tr>
<td><code>pm2 start index.js --name my-server</code></td>
<td>启动并命名进程</td>
</tr>
<tr>
<td><code>pm2 list</code></td>
<td>显示所有进行</td>
</tr>
<tr>
<td><code>pm2 stop my-server</code></td>
<td>停止 my-server 这个进程</td>
</tr>
<tr>
<td><code>pm2 restart all</code></td>
<td>启动所有进程</td>
</tr>
<tr>
<td><code>pm2 delete my-server</code></td>
<td>删除某个进程</td>
</tr>
<tr>
<td><code>pm2 show my-server</code></td>
<td>查看某个进程的详情信息</td>
</tr>
<tr>
<td><code>pm2 logs</code></td>
<td>查看日志信息</td>
</tr>
<tr>
<td><code>pm2 start &lt;programe&gt; -- &lt;command&gt;</code></td>
<td>守护可执行程序</td>
</tr>
</tbody>
</table>
<h2>nohup</h2>
<h3>什么是 nohup</h3>
<p>在 Unix 和类 Unix 操作系统中，信号是进程间通信的一种方式，用于通知接收进程某个事件已经发生。挂起信号（SIGHUP）是其中的一种。</p>
<p>当终端或控制进程断开时，操作系统会向由该终端启动的进程发送 SIGHUP 信号。默认情况下，大多数进程在接收到 SIGHUP 信号时会终止。这是因为，在早期 Unix 系统中，终端断开通常意味着用户已经“挂断”了电话线，因此与终端相关的进程不再有用。</p>
<p>然而，有时希望<strong>即使在终端断开的情况下，进程也能继续运行</strong>。这就是 <code>nohup</code> 命令的用途。<code>nohup</code> 命令会忽略 SIGHUP 信号，使得启动的进程在终端断开后继续运行。此外，<code>nohup</code> 还会自动将进程的标准输出和标准错误输出重定向到 <code>nohup.out</code> 文件，以防止输出丢失。</p>
<h3>使用方式</h3>
<p>这里给出三种指令模板，按需使用：</p>
<pre><code># 忽略挂起信号, 错误输出和标准输出都被重定向到 `nohup.out`
nohup python your_script.py &gt; output.log 2&gt;&amp;1 &amp;

# 忽略挂起信号, 标准输出都被重定向到 `nohup.out`
nohup python your_script.py &gt; output.log &amp;

# 忽略挂起信号, 但没有指定输出重定向
nohup python your_script.py &amp;
</code></pre>
<p>这里的 <code>output.log</code> 可以是任意文本文件，例如 <code>a.txt</code>，<code>b</code>，<code>log.aaa</code> 等，随你喜欢。所以显然，第二种和第三种除了指定输出文件名的区别外，没有其他差异。</p>
<p>有时即使使用了 <code>nohup</code> 和 <code>&amp;</code> 让命令在后台运行，退出 SSH 连接时仍然可能会收到 <code>"you have running jobs"</code> 的提示（实测会发生在 <code>zsh</code> 中）。这是因为退出 shell 会话时，shell 会检测到仍然有在当前 shell 会话中启动的进程（即使它们是后台进程）。</p>
<p>要避免这个问题，可以使用 <code>disown</code> 命令来从当前 shell 的作业列表中删除这些作业，使它们不受当前 shell 会话的影响。例如：</p>
<pre><code># 执行了某个作业
nohup ./frps -c frps.toml &amp;

# 删除作业记录
disown
</code></pre>
<p>这并不影响后台作业的执行。当然，也可以直接关闭 shell 窗口以退出用户登陆。</p>
<h2>Tmux</h2>
<p><a href="https://blog.085404.xyz/method/tmux-docs.html">Tmux Docs</a></p>
]]></content>
    <author><name>Sun2ot</name></author>
    <category term="教程"/>
  </entry>
  <entry>
    <title>SSH 免密连接服务器</title>
    <link href="https://blog.085404.xyz/posts/ssh-%E5%85%8D%E5%AF%86%E8%BF%9E%E6%8E%A5%E6%9C%8D%E5%8A%A1%E5%99%A8/" rel="alternate" type="text/html"/>
    <id>https://blog.085404.xyz/posts/ssh-%E5%85%8D%E5%AF%86%E8%BF%9E%E6%8E%A5%E6%9C%8D%E5%8A%A1%E5%99%A8/</id>
    <published>2024-03-10T00:00:00.000Z</published>
    <updated>2024-03-10T00:00:00.000Z</updated>
    <summary></summary>
    <content type="html"><![CDATA[<h2>在 Windows 端创建密钥</h2>
<blockquote>
<p>[!tip]
Win10 已经内置了 openssh 组件，可以直接使用。其他版本自行搜索。</p>
</blockquote>
<pre><code>ssh-keygen
</code></pre>
<p>回车后会让你设置密钥生成路径以及 phrase 等内容，默认为空即可，一路回车。</p>
<p>然后可以在 <code>C:\Users\你的win用户名\.ssh</code> 下看到 <code>id_rsa</code> 和 <code>id_rsa.pub</code> 两个文件，其中前者为私钥，后者为公钥。</p>
<h2>上传公钥</h2>
<h3>仅 Linux 可用</h3>
<pre><code>ssh-copy-id -i ~/.ssh/id_rsa.pub user@host
</code></pre>
<p>这里的 <code>user</code> 和 <code>host</code> 对应需要免密连接的服务器的用户和 IP 地址。</p>
<h3>通用方法</h3>
<p>通过 scp，或者 sftp，或者其他什么方式，将公钥 <code>id_rsa.pub</code> <strong>文件的内容</strong>粘贴到服务器的 <code>~/.ssh/authorized_keys</code> 文件中。</p>
<blockquote>
<p>[!warning]
如果你想在服务器上存放多个公钥文件，请保证每个公钥在 authorized_keys 文件中单独占一行。如果使用 <code>ssh-copy-id</code> 则可以忽略此处。</p>
</blockquote>
<h2>配置本地 ssh</h2>
<p>ssh 配置文件地址：</p>
<pre><code># windows
C:\Users\你的win用户名\.ssh\config

# Linux
~/.ssh/config
</code></pre>
<p>在配置文件中添加如下内容：</p>
<pre><code>Host 自定义名称
  HostName 服务器的IP/域名
  User 登录用户名
  IdentityFile C:\Users\你的win用户名\.ssh\id_rsa
</code></pre>
<blockquote>
<p>[!tip]
此处的 IdentityFile 为私钥路径，即 <code>id_rsa</code></p>
</blockquote>
<p>然后就可以在 Terminal 中实现免密 ssh 访问服务器</p>
<pre><code>ssh 自定义名称
</code></pre>
<h3>备注</h3>
<p>也许你会发现，通过 <code>ssh-copy-id</code> 命令或者别的什么方式，将公钥添加到远程服务器的 <code>authorized_keys</code> 文件中后，即使并且没有在本地 SSH 配置文件中显式指定 <code>IdentityFile</code> 字段，但仍然可以成功进行免密连接。</p>
<p>这是因为 SSH 客户端会默认使用 <code>~/.ssh/id_rsa</code>（默认私钥文件）来进行身份验证。</p>
<p>SSH 客户端在尝试连接时会按照一定的顺序搜索可用的私钥文件，其中包括默认的 <code>~/.ssh/id_rsa</code> 文件。因此，如果你的私钥文件恰好是默认的 <code>~/.ssh/id_rsa</code>，那么在本地 SSH 配置文件中不显式指定 <code>IdentityFile</code> 字段也可以进行免密连接。</p>
<p>虽然在某些情况下可以省略 <code>IdentityFile</code> 字段，但为了确保连接的稳定性和安全性，建议在本地 SSH 配置文件中明确指定要使用的私钥文件路径。这样可以避免混淆和不必要的问题，并确保连接设置是清晰明了的。</p>
]]></content>
    <author><name>Sun2ot</name></author>
    <category term="教程"/>
  </entry>
  <entry>
    <title>Windows平台代理OpenSSH</title>
    <link href="https://blog.085404.xyz/posts/windows%E4%BB%A3%E7%90%86openssh/" rel="alternate" type="text/html"/>
    <id>https://blog.085404.xyz/posts/windows%E4%BB%A3%E7%90%86openssh/</id>
    <published>2024-03-10T00:00:00.000Z</published>
    <updated>2024-03-10T00:00:00.000Z</updated>
    <summary></summary>
    <content type="html"><![CDATA[<p>百度搜到的很多给 SSH 设置代理的方法，都是抄来抄去的，就是 <code>ProxyCommand nc -v -x 127.0.0.1:1080 %h %p</code> 。</p>
<p>但是这分明是给 Linux 用的， Windows 上哪里来的 nc 程序？</p>
<p>所以以下操作才是给 Windows 用的。</p>
<p>在自己的用户文件夹找到 <code>.ssh</code> 文件夹，在里面新建一个空白文件，取名 config，如果已经有了就不用创建了。</p>
<p>在 config 文件加上：</p>
<pre><code>Host 自定义名称
  HostName 服务器的IP/域名
  User 登录用户名
  IdentityFile C:\Users\你的win用户名\.ssh\id_rsa
  ProxyCommand "D:\environment\Git\mingw64\bin\connect.exe" -S 127.0.0.1:7890 %h %p  
</code></pre>
<p>这里 git 的安装路径和后面的代理自己看着填。后面的代理， <code>-S</code> 指是 socks 代理，默认是 socks5， <code>127.0.0.1:7890</code> 就是你本地的代理地址，后面的 <code>%h %p</code> 意思是 Host 和 Port。如果要使用 HTTP 代理，就写 <code>-H</code> 。</p>
<p>当然，不难发现这里其实调用的是 Git 中的 connect.exe 程序，如果没有或者不想安装 Git，完全可以用独立程序代替，下载方式如下：</p>
<pre><code>scoop install main/connect
</code></pre>
<p>由于这个程序已经相当古老了，因此只能在<a href="https://web.archive.org/web/20080516100455/http://www.meadowy.org/~gotoh/projects/connect">互联网存档</a>中找到。</p>
]]></content>
    <author><name>Sun2ot</name></author>
    <category term="教程"/>
  </entry>
  <entry>
    <title>腾讯、阿里对象存储价格对比</title>
    <link href="https://blog.085404.xyz/posts/%E8%85%BE%E8%AE%AF%E9%98%BF%E9%87%8C%E5%AF%B9%E8%B1%A1%E5%AD%98%E5%82%A8%E4%BB%B7%E6%A0%BC%E5%AF%B9%E6%AF%94/" rel="alternate" type="text/html"/>
    <id>https://blog.085404.xyz/posts/%E8%85%BE%E8%AE%AF%E9%98%BF%E9%87%8C%E5%AF%B9%E8%B1%A1%E5%AD%98%E5%82%A8%E4%BB%B7%E6%A0%BC%E5%AF%B9%E6%AF%94/</id>
    <published>2024-03-10T00:00:00.000Z</published>
    <updated>2024-03-10T00:00:00.000Z</updated>
    <summary></summary>
    <content type="html"><![CDATA[<h2>中国大陆内</h2>
<table>
<thead>
<tr>
<th>计费项</th>
<th>腾讯</th>
<th>阿里</th>
</tr>
</thead>
<tbody>
<tr>
<td>标准存储</td>
<td>0.099 元/GB/月</td>
<td>0.12 元/GB/月</td>
</tr>
<tr>
<td>外网下行流量</td>
<td>0.5 元/GB</td>
<td>00:00 - 08:00 (闲时)：0.25 元/GB&lt;br&gt;08:00 - 24:00 (忙时)：0.50 元/GB</td>
</tr>
<tr>
<td>CDN 回源</td>
<td>0.15 元/GB</td>
<td>0.15 元/GB</td>
</tr>
<tr>
<td>读写请求</td>
<td>0.01 元/万次</td>
<td>PUT 类型请求：每月 0 - 500 万次：免费  &lt;br&gt;&gt;500 万次：0.01 元/万次&lt;br&gt;GET 类型请求：每月 0 - 2000 万次：免费  &lt;br&gt;&gt;2000 万次：0.01 元/万次</td>
</tr>
</tbody>
</table>
<p>双方的最低计费门槛为 0.01 元，即低于 0.01 元的消费直接算作 0 元(免费)。双方<strong>按量付费</strong>规则如下：</p>
<ul>
<li>阿里云：
<ul>
<li>按小时计费</li>
<li>存储费用=存储量（GB）×月单价÷30（天）÷24（小时）</li>
</ul>
</li>
<li>腾讯云：
<ul>
<li>按天计费，日存储容量费用 = 存储容量月单价 / 30 * 日存储容量</li>
<li>日存储容量 = 当日“每5分钟存储容量”之和 / 288（采样点数）</li>
</ul>
</li>
</ul>
<p>综上可计算出双方触发计费门槛时的最低存储容量：</p>
<blockquote>
<p>也就是你最多可以白嫖这么大的空间（只是存储不计费，流量还是贵）</p>
</blockquote>
<ul>
<li>阿里云：$0.01 \div (0.12 \div 30 \div 24) = 59.9999 \approx 60(GB)$</li>
<li>腾讯云：$0.01 \div (0.099 \div 30) = 3.0303 \approx 3.03(GB)$</li>
</ul>
<p>我自认为算的没错，但没想到相差会这么大。。。</p>
<h3>关于腾讯云采样点的解释</h3>
<p>假设你存了 $x$ GB 的数据，并且在一天时间内保持不变，那么你的日存储容量为：</p>
<p>$$
StoragePerDay = (1 \times 24 \times 60 \div 5)x \div 288 =  x
$$</p>
<p>也就是“每 5 分钟采样一次”正好对应 288 个采样点。之所以这么计算是为了当你的存储容量发生变动时，能够快速的调整之后的存储费用。</p>
<h2>中国大陆外</h2>
<blockquote>
<p>以香港为例</p>
</blockquote>
<table>
<thead>
<tr>
<th>计费项</th>
<th>腾讯</th>
<th>阿里</th>
</tr>
</thead>
<tbody>
<tr>
<td>标准存储</td>
<td>0.156元/GB/月</td>
<td>0-5GB 免费，超出：0.136元/GB/月</td>
</tr>
<tr>
<td>外网下行流量</td>
<td>0.75 元/GB</td>
<td>0-5GB 免费，5GB-10TB：0.75 元/GB</td>
</tr>
<tr>
<td>CDN 回源流量（元/GB）</td>
<td>0.75</td>
<td>0.75</td>
</tr>
<tr>
<td>读写请求（元/万次）</td>
<td>0.01</td>
<td>PUT：0.1，GET：0.01</td>
</tr>
</tbody>
</table>
<h2>CDN</h2>
<h3>按量计费</h3>
<blockquote>
<p>这里因为是自用，所以只考虑中国内地</p>
</blockquote>
<p>阿里</p>
<p><img src="https://img.085404.xyz/images/5638de784e60283400e7de3924efda74.webp" alt="image.png|300x200" /></p>
<p>腾讯</p>
<p><img src="https://img.085404.xyz/images/3a482ab6b058dc5581578a87eb166d94.webp" alt="image.png|200x200" /></p>
<p>双方都是阶梯计费，但腾讯云最高档位也比阿里云便宜。</p>
<h3>关于 CDN 计费规则</h3>
<p><img src="https://img.085404.xyz/images/1945f67af13f9638c47a0de210beadf8.webp" alt="image.png" /></p>
<blockquote>
<p>cdn 计费图示意，OSS/COS 通用</p>
</blockquote>
<p>从上图可知，对象存储加上 CDN 后，主要产生的计费项目变化如下：</p>
<ol>
<li>用户上传到 OSS/COS 依然免费</li>
<li>用户从对象存储下载资源时，访问路径从 <code>用户-OSS/COS</code> 变成了 <code>用户-CDN-OSS/COS</code>，因此：
<ol>
<li>加上 CDN 后肯定会把对象存储设置为私有读写，此时原来从 OSS/COS 直接流向用户(<strong>公网</strong>)的流量就变成了 CDN 的<strong>回源</strong>流量（0.5 元/GB -&gt; 0.15 元/GB）</li>
<li>用户只会和 CDN 交互，因此会产生 CDN 的下行流量费用（0.21 元/GB）</li>
</ol>
</li>
</ol>
<p>综上所述，假设同样要从对象存储下载 1GB 数据（这里忽略请求等费用，只考虑流量费用）：</p>
<ul>
<li>不使用 CDN：0.5 元</li>
<li>使用 CDN 但未命中 CDN 缓存：0.21+0.15=0.36 元</li>
<li>使用 CDN 且命中 CDN 缓存：0.21</li>
</ul>
<p>使用 CDN 除了流量费用变得便宜，更重要的是以下两点：</p>
<ol>
<li>对象存储本质上相当于你的一个私有存储，公开对外访问本就是存在严重安全隐患的</li>
<li>各大厂商针对 CDN 都有比对象存储更完善的安保报警机制，当发生盗刷时能够及时通知并下线 CDN，因此更安全
<blockquote>
<p>及不及时不好说，但起码能够下线 CDN，个人用户设置几个 G 的阈值，真盗刷了也用不了几个钱。更何况还有防盗链之类的，虽然 ob 不支持 hhh。</p>
</blockquote>
</li>
</ol>
<blockquote>
<p>[!warning] COS 重大发现
<s>他妈的，人家腾讯 COS 不用设置自定义域名就可以实现在线预览，这 TM 才叫图床！</s>
已经不能用了 hhh</p>
</blockquote>
<h2>当前使用方案总结</h2>
<ol>
<li>腾讯云 CDN + 阿里云 OSS （肯定挑便宜大碗的用，没毛病）</li>
<li>CDN 设置单日封顶以及5分钟内封顶，触发报警后 CDN 永久离线</li>
</ol>
<h2>TODO</h2>
<p>根据官方文档，有一种模式也许可以进一步节省流量。将对象存储挂载到厂商同地域的云服务器，这样对象存储的流量传输全部走内网，属于免费。然后对服务器套上（全站）CDN，如此一来只会产生 CDN 下行流量。</p>
<p>不过受限于国内价比黄金的带宽，如果访问量较高且触发回源的话，可能会因为小水管的原因收到阻塞。</p>
]]></content>
    <author><name>Sun2ot</name></author>
    <category term="教程"/>
  </entry>
  <entry>
    <title>Clash Core 部署</title>
    <link href="https://blog.085404.xyz/posts/clash-core-%E9%83%A8%E7%BD%B2/" rel="alternate" type="text/html"/>
    <id>https://blog.085404.xyz/posts/clash-core-%E9%83%A8%E7%BD%B2/</id>
    <published>2024-02-25T00:00:00.000Z</published>
    <updated>2024-02-25T00:00:00.000Z</updated>
    <summary></summary>
    <content type="html"><![CDATA[<h2>写在前面</h2>
<p>理论上说，学校是可以给教职工服务器开通外网访问权限的。。。咱不敢问，也不敢说，所以还是自己动手丰衣足食吧，反正对于法外狂徒来说，代理只是家常便饭了。</p>
<p>clash dashboard 面板密码：<code>yzh112358</code></p>
<p>代理程序的二进制文件位于服务器的 <code>/usr/local/bin/mihomo</code>。</p>
<p>配置文件位于 <code>/etc/mihomo</code>，结构如下：</p>
<pre><code>mihomo/
├── cache.db  缓存
├── config.yaml  配置文件
├── Country.mmdb  geoip数据库
├── providers/  规则集
└── ui/  web面板
</code></pre>
<h2>你需要知道</h2>
<ol>
<li><a href="https://www.kaspersky.com.cn/resource-center/preemptive-safety/vpn-vs-proxy-server">什么是代理？和 VPN 有什么区别？</a></li>
<li><a href="https://www.vpn-china.org/the-difference-between-airport-and-vpn/">什么是“机场”？</a></li>
</ol>
<blockquote>
<p>为了避免上述链接失效或无法直连访问，已将对应页面归档为 <code>.WACZ</code>。打开该文件，可以通过 <code>Webrecorder ArchiveWeb.page</code> 浏览器插件，见 <code>webarchive.zip</code></p>
</blockquote>
<p><code>/root/release</code>下是一些公共服务的二进制文件</p>
<h2>部署 clash</h2>
<ol>
<li>
<p>下载二进制文件(自行根据最新版本修改指令)，并解压</p>
<pre><code>wget https://ghproxy.085404.xyz/https://github.com/MetaCubeX/mihomo/releases/download/Prerelease-Alpha/mihomo-linux-amd64-alpha-1d3e9f4.gz
gzip -d mihomo-linux-amd64-alpha-1d3e9f4.gz
</code></pre>
</li>
<li>
<p>将下载的二进制可执行文件重名名为 mihomo 并移动到 <code>/usr/local/bin/</code></p>
<pre><code>mv mihomo-linux-amd64-alpha-1d3e9f4.gz mihomo
cp mihomo /usr/local/bin
</code></pre>
</li>
<li>
<p>下载配置文件并复制到对应路径下</p>
<pre><code>curl -o config.yaml "你的订阅链接"
cp config.yaml /etc/mihomo
</code></pre>
</li>
<li>
<p>授予合适的权限后，启动 clash</p>
<pre><code># -d 是配置文件路径，-h 可查看完整文档
nohup mihomo -d /etc/mihomo &gt; clash.log 2&gt;&amp;1 &amp;
</code></pre>
</li>
</ol>
<h3>说明</h3>
<ol>
<li><code>config.yaml</code>, <code>cache.db</code>, <code>Country.mmdb</code> 授予组读写权限</li>
<li><code>mihomo</code> 授予组执行权限</li>
<li>通过 <code>nohup</code> 挂起后，日志会写入 <code>clash.log</code> 文件，而 dashboard 中无法查看
<blockquote>
<p>想了想，反正 dashboard 的日志界面说实话很一般，真要分析还是看日志文件吧。。。主要是非root启动更安全。如果实在无法接受，通过 systemd 启动服务即可正常使用，见 <a href="https://wiki.metacubex.one/startup/service/">虚空终端</a></p>
</blockquote>
</li>
</ol>
<h2>部署 clash dashboard</h2>
<pre><code>git clone https://github.com/metacubex/metacubexd.git -b gh-pages /etc/mihomo/ui
</code></pre>
<p>更新方式：</p>
<pre><code>cd /etc/mihomo/ui
git pull -r
# 或者
git -C /etc/mihomo/ui pull -r
</code></pre>
<p>而后通过 nginx 反向代理，或者访问 <code>http://ip:9090/ui</code>都可以。</p>
<blockquote>
<p>这个端口具体看配置文件的 <code>external-controller</code></p>
</blockquote>
]]></content>
    <author><name>Sun2ot</name></author>
    <category term="教程"/>
  </entry>
  <entry>
    <title>frp内网穿透alist</title>
    <link href="https://blog.085404.xyz/posts/frp%E5%86%85%E7%BD%91%E7%A9%BF%E9%80%8Falist/" rel="alternate" type="text/html"/>
    <id>https://blog.085404.xyz/posts/frp%E5%86%85%E7%BD%91%E7%A9%BF%E9%80%8Falist/</id>
    <published>2024-02-25T00:00:00.000Z</published>
    <updated>2024-02-25T00:00:00.000Z</updated>
    <summary></summary>
    <content type="html"><![CDATA[<h2>相关资源</h2>
<p><a href="https://github.com/fatedier/frp">frp GitHub</a></p>
<p><a href="https://gofrp.org/zh-cn/">frp 中文文档</a></p>
<h2>事件背景</h2>
<p>公元 2024 年，傻逼校园网屏蔽了内网不同网段设备的访问，导致我的所有局域网解决方案全部失效（例如 alist）。至此，我无法躺在宿舍的床上看实验室里电脑的视频，这极大的影响了我的科研热情。为此，决定进行内网穿透。</p>
<p><strong>本次进行的内网穿透主要针对 http 服务（alist）。</strong></p>
<h2>服务端配置</h2>
<blockquote>
<p>下述内容也可以参考官方示例 <a href="https://gofrp.org/zh-cn/docs/examples/vhost-http/">vhost-http</a></p>
</blockquote>
<pre><code># 下载 frp
wget https://github.com/fatedier/frp/releases/download/v0.54.0/frp_0.54.0_linux_amd64.tar.gz
# 解压
tar -zxvf frp_0.54.0_linux_amd64.tar.gz
</code></pre>
<p>注意，frp 的 Github Release 内包括客户端与服务端两部分，分别对应 <code>frps*</code> 于 <code>frpc*</code>。所以服务端配置可以删除 <code>frpc*</code>。</p>
<blockquote>
<p>此外，麻烦还在无脑搬运的看看这个："从 v0.52.0 版本开始，frp 开始支持 TOML、YAML 和 JSON 作为配置文件格式。请注意，<strong>INI 已被弃用，并将在未来的发布中移除</strong>。新功能<strong>只能</strong>在 TOML、YAML 或 JSON 中使用。希望使用这些新功能的用户应相应地切换其配置格式。"—— <a href="https://gofrp.org/zh-cn/docs/features/common/configure/">frps docs</a></p>
</blockquote>
<p>服务端 <code>frps.toml</code> 内容如下：</p>
<pre><code>bindPort = 7000 # frp 服务端口
vhostHTTPPort = 5245 # 监听 HTTP 的端口
</code></pre>
<p>请确保关闭这两个端口的防火墙。</p>
<h2>客户端配置</h2>
<pre><code>serverAddr = "172.16.16.242" # 服务端 IP
serverPort = 7000 # 服务端 frp 服务端口

[[proxies]] # 可写可不写，不影响
name = "alist" # 需唯一
type = "http"
localPort = 5244  # 本地 http 服务的端口
customDomains = ["your_domain.com"]
</code></pre>
<p>针对该配置文件做出补充介绍：</p>
<ol>
<li>不难发现我这里的 <code>serverAddr</code> 并不是一个公网 IP，而是一个内网 IP。因为校园网屏蔽了外网 IP，导致我的新加坡服务器无法作为内网穿透的服务端，因此退而求其次使用了一台内网服务器作为服务端。因此严格来说，本次实践是进行的<strong>内网的内网穿透</strong>。</li>
<li><code>customDomains</code> 是一个 A 记录解析到 <code>serverAddr</code> 的域名。参考官方文档，DomainConfig 有 <code>customDomains</code> 和 <code>subdomain</code> 两个配置项。实测必须指定一个，否则会报错 <code>proxy xxx: subdomain and custom domains should not be both empty</code></li>
</ol>
<h2>启动 frp 服务</h2>
<p><strong>启动服务端</strong>：</p>
<pre><code>./frps -c frps.toml
</code></pre>
<p>服务端成功启动后，会看到如下类似信息：</p>
<pre><code>2024/02/25 19:41:14 [I] [root.go:105] frps uses config file: frps.toml
2024/02/25 19:41:15 [I] [service.go:225] frps tcp listen on 0.0.0.0:7000
2024/02/25 19:41:15 [I] [service.go:292] http service listen on 0.0.0.0:5245
2024/02/25 19:41:15 [I] [root.go:114] frps started successfully
</code></pre>
<p><strong>启动客户端</strong>：</p>
<pre><code>.\frpc.exe -c .\frpc.toml
</code></pre>
<p>客户端启动成功后，会看到如下类似信息：</p>
<pre><code>2024/02/25 19:42:52 [I] [root.go:142] start frpc service for config file [.\frpc.toml]
2024/02/25 19:42:52 [I] [service.go:287] try to connect to server...
2024/02/25 19:42:52 [I] [service.go:279] [7e047f197c32f772] login to server success, get run id [7e047f197c32f772]
2024/02/25 19:42:52 [I] [proxy_manager.go:173] [7e047f197c32f772] proxy added: [alist]
2024/02/25 19:42:52 [I] [control.go:170] [7e047f197c32f772] [alist] start proxy success
</code></pre>
<p>此时服务端也会同步显示客户端连接情况：</p>
<pre><code>2024/02/25 19:42:54 [I] [service.go:563] [7e047f197c32f772] client login info: ip [172.26.109.89:1557] version [0.54.0] hostname [] os [windows] arch [amd64]
2024/02/25 19:42:54 [I] [http.go:110] [7e047f197c32f772] [alist] http proxy listen for host [your_domain.com] location [] group [], routeByHTTPUser []
2024/02/25 19:42:54 [I] [control.go:401] [7e047f197c32f772] new proxy [alist] type [http] success
</code></pre>
<h2>访问内网服务</h2>
<p>通过 <code>http://your_domain.com:5245</code> 即可访问内网设备 <code>127.0.0.1</code> 下端口 <code>5244</code> 的服务。</p>
<blockquote>
<p>客户端配置中，<code>localIP</code> 默认为 <code>127.0.0.1</code>，故省略。如有需要请自行修改。</p>
</blockquote>
]]></content>
    <author><name>Sun2ot</name></author>
    <category term="教程"/>
  </entry>
  <entry>
    <title>Neo4j Cypher</title>
    <link href="https://blog.085404.xyz/posts/neo4j-cypher/" rel="alternate" type="text/html"/>
    <id>https://blog.085404.xyz/posts/neo4j-cypher/</id>
    <published>2024-01-28T00:00:00.000Z</published>
    <updated>2024-01-28T00:00:00.000Z</updated>
    <summary></summary>
    <content type="html"><![CDATA[<p><a href="https://www.youtube.com/watch?v=xKVA2gL8WHs&amp;list=PL6UwySlcwEYJ9BKIiCk2bMfd_JKXwDPQJ&amp;index=1">About Neo4j</a></p>
<h2>核心概念</h2>
<h3>Node</h3>
<p>Neo4j 图形数据库中的数据实体称为节点。 Cypher 中使用括号 <code>( )</code> 引用节点。</p>
<pre><code>MATCH (n:Person {name:'Anna'})
RETURN n.born AS birthYear
</code></pre>
<p>在上述例子中：</p>
<ul>
<li><code>Person</code> 是一个 <code>label</code>（标签）。标签就像标记一样，用于在数据库中查询特定节点。一个节点可以有多个标签，例如张三可以是 <code>Person</code>，也可以是 <code>Actor</code></li>
<li>节点的 <code>name</code> 属性设置为 <code>Anna</code> 。属性在大括号 <code>{ }</code> 内定义，用于为节点提供特定信息，也可以查询这些信息并进一步提高精确定位数据的能力</li>
<li>赋予该节点变量 <code>n</code> 。变量允许在后续子句中引用指定的节点</li>
</ul>
<hr />
<h3>Relationship</h3>
<p>图中的节点可以通过关系连接起来。关系必须具有起始节点、结束节点和一种类型（<strong>三元组</strong>）。关系在 Cypher 中用箭头（例如 <code>--&gt;</code> ）表示，指示关系的方向。</p>
<pre><code>MATCH (:Person {name: 'Anna'})-[r:KNOWS WHERE r.since &lt; 2020]-&gt;(friend:Person)
RETURN count(r) As numberOfFriends
</code></pre>
<p>与节点不同，关系模式中的信息必须用方括号 <code>[ ]</code> 括起来。</p>
<blockquote>
<p>所以一个关系应该形如 <code>node1-[ var:type ]-&gt;node2</code> 或者 <code>node1&lt;-[ var:type ]-node2</code>，
即用一个 <code>[ ]</code> 插在 <code>--&gt;</code> 的两个横线中间</p>
</blockquote>
<p>上面的查询示例表示：匹配 <code>type</code> (类型)为 <code>KNOWS</code> 且属性 <code>since</code> 小于 <code>2020</code> 的关系。并且该关系应该从名为 <code>Anna</code> 的 <code>Person</code> 节点指向任何其他的 <code>Person</code> 节点（赋予变量 <code>friend</code> ）。</p>
<p>count() 函数在 <code>RETURN</code> 子句中用于统计前面 <code>MATCH</code> 子句中 <code>r</code> 变量绑定的所有关系（即有多少个朋友在 <code>2020</code> 之前就已经认识了）。</p>
<blockquote>
<p><code>label</code> 和 <code>type</code> 从作用上都可以近似理解为“类型”。
但请注意：虽然节点可以有多个标签，但关系只能有一种类型。</p>
</blockquote>
<hr />
<h3>Path</h3>
<p>图中的路径由连接的节点和关系组成。探索这些路径是 Cypher 的核心。</p>
<pre><code>MATCH (n:Person {name: 'Anna'})-[:KNOWS]-{1,5}(friend:Person WHERE n.born &lt; friend.born)
RETURN DISTINCT friend.name AS olderConnections
</code></pre>
<p>此示例使用量化关系来查找距离 <code>5-hops</code> 的所有路径，仅遍历从起始节点 <code>Anna</code> 到其他旧 <code>Person</code> 类型（由 WHERE 子句定义）的 <code>KNOWS</code> 关系。DISTINCT 运算符用于确保 <code>RETURN</code> 子句仅返回唯一节点。</p>
<h2>Match</h2>
<h3>获取所有节点</h3>
<p>通过指定具有单个节点且没有标签的模式，将返回图中的所有节点。</p>
<pre><code>MATCH (n)
RETURN n
</code></pre>
<p>查找具有特定标签的所有节点：</p>
<pre><code>MATCH (movie:Movie)
RETURN movie.title
</code></pre>
<h3>查找相关节点</h3>
<p>相关节点即具有关系连接的节点。符号 <code>--</code> 表示与关系相关，而<strong>不考虑关系的类型或方向</strong>。</p>
<pre><code>MATCH (director {name: 'Oliver Stone'})--(movie)
RETURN movie.title
</code></pre>
<p>返回 <code>Oliver Stone</code> 执导的所有电影</p>
<h3>通过标签约束查找</h3>
<p>匹配名为 <code>Oliver Stone</code> 的 <code>Person</code> 的连接的所有 <code>Movie</code> 节点。</p>
<pre><code>MATCH (:Person {name: 'Oliver Stone'})--(movie:Movie)
RETURN movie.title
</code></pre>
<p>也可以通过标签表达式进行约束</p>
<pre><code>MATCH (n:Movie|Person)
RETURN n.name AS name, n.title AS title
</code></pre>
<p>返回 <code>Movie</code> or <code>Person</code> 节点。</p>
<h3>指向性查找</h3>
<p>使用 <code>--&gt;</code> 或者 <code>&lt;--</code> 可以查找 <strong>outgoing relationship</strong>（出链、出关系）：</p>
<pre><code>MATCH (:Person {name: 'Oliver Stone'})--&gt;(movie)
RETURN movie.title
</code></pre>
<p>返回通过出关系连接到 <code>Person</code> 节点且 <code>name</code> 属性设置为 <code>Oliver Stone</code> 的任何节点。</p>
<h3>关系变量</h3>
<p>可以向模式引入变量，用于过滤关系属性或返回关系。例如：</p>
<pre><code>MATCH (:Person {name: 'Oliver Stone'})-[r]-&gt;(movie)
RETURN type(r)
</code></pre>
<p>返回 <code>Oliver Stone</code> 中每个传出关系的类型。</p>
<h3>匹配无向关系</h3>
<p>当模式包含绑定关系，并且该关系模式未指定方向时，Cypher 将尝试在两个方向上匹配该关系。</p>
<pre><code>MATCH (a)-[:ACTED_IN {role: 'Bud Fox'}]-(b)
RETURN a, b
</code></pre>
<h2>Create</h2>
<h3>创建节点</h3>
<pre><code>CREATE (charlie:Person:Actor {name: 'Charlie Sheen'}), (oliver:Person:Director {name: 'Oliver Stone'})
</code></pre>
<p>创建两个节点，绑定到变量 <code>charlie</code> 和 <code>oliver</code>，每个节点都有一个 <code>Person</code> 标签和 <code>name</code> 属性。此外，charlie 节点还具有标签 <code>Actor</code>，而 oliver 节点则分配有标签 <code>Director</code>。</p>
<blockquote>
<p>点击 ▶ 执行或 <code>Ctrl + Enter</code>
<code>shift + enter</code> 换行输入</p>
</blockquote>
<hr />
<h3>创建关系</h3>
<p>使用 <code>CREATE</code> 子句创建关系。与节点不同，关系始终只需要一种关系类型和方向。与节点类似，可以为关系分配属性和关系类型，并绑定到变量。</p>
<pre><code>CREATE (charlie:Person:Actor {name: 'Charlie Sheen'})-[:ACTED_IN {role: 'Bud Fox'}]-&gt;(wallStreet:Movie {title: 'Wall Street'})&lt;-[:DIRECTED]-(oliver:Person:Director {name: 'Oliver Stone'})
</code></pre>
<p>此查询为 Charlie Sheen 和 Oliver Stone 创建 <code>Person</code> 节点，为 Wall Street 创建 <code>Movie</code> 节点。它还创建了它们之间的类型 <code>ACTED_IN</code> 和 <code>DIRECTED</code> 的关系。</p>
<blockquote>
<p>注意两个箭头的指向，分别是 <code>-[ ]-&gt;</code> 和 <code>&lt;-[ ]-</code></p>
</blockquote>
<h4>对已存在节点创建关系</h4>
<pre><code>match (a:`品种`),(b:area) where a.name="爆裂葡萄" and b.location="(114,514)" create (a)-[r:plant_in]-&gt;(b) return r
</code></pre>
<p>试想，对已存在的节点添加关系，需要经历三步：</p>
<ol>
<li>找出头节点、尾节点 <code>match (a),(b) where ...</code></li>
<li>确定两者之间的关系 <code>relation type</code></li>
<li>建立关系 <code>create (a)-[r:type]-&gt;(b)</code></li>
</ol>
<blockquote>
<p>[!question] 如果直接节点、关系一把抓，一起创建，会发生什么？</p>
</blockquote>
<pre><code>create (:`品种`{name:"爆裂葡萄"})-[:plant_in]-&gt;(:area{location:"(114,514)"})
</code></pre>
<p>执行上述操作后，会生成两个新节点与它们之间的关系，即使目标品种与位置已经存在，仍会执行节点创建操作，这很可能是我们不想要的。</p>
<h2>Delete</h2>
<h3>删除单个节点</h3>
<pre><code>MATCH (n:Person {name: 'Tom Hanks'})
DELETE n
</code></pre>
<p>这将删除 <code>Person</code> 节点 <code>Tom Hanks</code> </p>
<blockquote>
<p>该查询只能在没有任何关系连接的节点上运行</p>
</blockquote>
<p>还可以使用 <code>NODETACH DELETE</code> 子句删除单个节点。使用 <code>NODETACH</code> 关键字显式定义关系不会从节点分离并删除。 <code>NODETACH</code> 关键字是现有关键字 DETACH 的镜像。包含它在功能上与使用简单的 <code>DELETE</code> 相同。</p>
<pre><code>MATCH (n:Person {name: 'Tom Hanks'})
NODETACH DELETE n
</code></pre>
<blockquote>
<p><code>NODETACH</code> 在 Neo4j 5.14 中引入</p>
</blockquote>
<h3>仅删除关系</h3>
<p>删除关系的同时保持连接到该关系的节点不受影响。</p>
<pre><code>MATCH (n:Person {name: 'Laurence Fishburne'})-[r:ACTED_IN]-&gt;()
DELETE r
</code></pre>
<p>这将从 <code>Person</code> 节点 <code>Laurence Fishburne</code> 删除所有传出的 <code>ACTED_IN</code> 关系，而不删除该节点。</p>
<h3>删除节点及其所有关系</h3>
<p>要删除节点以及连接它们的任何关系，需使用 <code>DETACH DELETE</code> 子句。</p>
<pre><code>MATCH (n:Person {name: 'Carrie-Anne Moss'})
DETACH DELETE n
</code></pre>
<p>这将删除 <code>Person</code> 节点 <code>Carrie-Anne Moss</code> 以及与其连接的所有关系。</p>
<blockquote>
<p>安全权限受限的用户可能不允许使用 <code>DETACH DELETE</code> 子句。有关详细信息，RTFM -&gt; <a href="https://neo4j.com/docs/operations-manual/5/tutorial/access-control/#detach-delete-restricted-user">细粒度访问控制</a></p>
</blockquote>
<h3>删除所有节点和关系</h3>
<pre><code>MATCH (n)
DETACH DELETE n
</code></pre>
<blockquote>
<p>不适用于删除大量数据，但在试验小型示例数据集时很有用。当删除大量数据时，请使用 <a href="https://neo4j.com/docs/cypher-manual/current/subqueries/call-subquery/#delete-with-call-in-transactions">CALL { …​ } IN TRANSACTIONS</a></p>
</blockquote>
<h2>Load CSV</h2>
<h3>介绍</h3>
<ul>
<li>CSV 文件的 URL 是通过使用 <code>FROM</code> 指定的，后跟计算相关 URL 的任意表达式</li>
<li>需要使用 <code>AS</code> 为 CSV 数据指定变量</li>
<li>CSV 文件可以存储在数据库服务器上，然后可以使用 <code>file:///</code> URL 进行访问。另外， <code>LOAD CSV</code> 还支持通过 HTTPS、HTTP 和 FTP 访问 CSV 文件</li>
<li><code>LOAD CSV</code> 支持使用 gzip 和 Deflate 压缩的资源。此外， <code>LOAD CSV</code> 支持本地存储的使用 ZIP 压缩的 CSV 文件</li>
<li><code>LOAD CSV</code> 将遵循 HTTP 重定向，但出于安全原因，它不会遵循更改协议的重定向，例如，从 HTTPS 转到 HTTP</li>
<li><code>LOAD CSV</code> 通常与子查询 <a href="https://neo4j.com/docs/cypher-manual/current/subqueries/call-subquery/#delete-with-call-in-transactions">CALL { …​ } IN TRANSACTIONS</a> 结合使用</li>
<li><code>LOAD CSV</code> 由加载权限 <a href="https://neo4j.com/docs/operations-manual/5/authentication-authorization/load-privileges/">Load Privileges</a> 调节</li>
</ul>
<h3>文件 URL 的配置</h3>
<p><a href="https://neo4j.com/docs/operations-manual/5/configuration/configuration-settings/#config_dbms.security.allow_csv_import_from_file_urls">dbms.security.allow_csv_import_from_file_urls</a></p>
<p>使用 <code>LOAD CSV</code> 加载数据时 Cypher 是否允许使用 <code>file:///</code> URL 加载数据库服务器文件系统上的文件。<strong>默认为 true</strong>。</p>
<p>设置 <code>dbms.security.allow_csv_import_from_file_urls=false</code> 将完全禁用 <code>LOAD CSV</code> 对文件系统的访问。</p>
<hr />
<p><a href="https://neo4j.com/docs/operations-manual/5/configuration/configuration-settings#config_server.directories.import">server.directories.import</a></p>
<p>设置 <code>file:///</code> URL 的根目录。应将其设置为<strong>相对于</strong>数据库服务器上 Neo4j 安装路径的单个目录。所有从 <code>file:///</code> URL 加载的请求都将相对于指定的目录。</p>
<p>配置设置中设置的默认值是 <code>import</code>。这是一种安全措施，可防止数据库访问标准导入目录之外的文件，类似于 Unix <code>chroot</code> 的操作方式。将其设置为空字段将允许访问 Neo4j 安装文件夹中的所有文件。注释掉此设置将禁用安全功能，从而允许导入本地系统中的所有文件。<strong>这绝对不推荐</strong>。</p>
<p>文件 URL 将相对于 <code>server.directories.import</code> 目录进行解析。例如，文件 URL 通常类似于 <code>file:///myfile.csv</code> 或 <code>file:///myproject/myfile.csv</code>。请注意：</p>
<ul>
<li>使用 <code>file:///</code> URL 时，空格和其他非字母数字字符需要进行 URL 编码</li>
<li>如果 <code>server.directories.import</code> 设置为默认值 <code>import</code>，则在 <code>LOAD CSV</code> 中使用上述 URL 将分别从 <code>&lt;NEO4J_HOME&gt;/import/myfile.csv</code> 和 <code>&lt;NEO4J_HOME&gt;/import/myproject/myfile</code> 读取.csv</li>
<li>如果设置为 /data/csv，则在 <code>LOAD CSV</code> 中使用上述 URL 将分别从 <code>&lt;NEO4J_HOME&gt;/data/csv/myfile.csv</code> 和 <code>&lt;NEO4J_HOME&gt;/data/csv/myproject/myfile.csv</code> 读取</li>
</ul>
<blockquote>
<p><code>server.directories.import</code> 仅适用于本地磁盘，不适用于远程 URL</p>
</blockquote>
<h3>csv 文件格式</h3>
<ul>
<li>字符编码为 UTF-8</li>
<li>结束行终止取决于系统，例如，在 UNIX 上为 <code>\n</code> ，在 Windows 上为 <code>\r\n</code></li>
<li>默认字段终止符是 <code>,</code>。可使用 <code>LOAD CSV</code> 命令中的选项 <code>FIELDTERMINATOR</code> 更改字段终止符</li>
<li>CSV 文件中允许带引号的 <code>STRING</code> 值，并且在读取数据时会删除引号；</li>
<li><code>STRING</code> 引号的字符是双引号 <code>"</code> </li>
<li>如果 <code>dbms.import.csv.legacy_quote_escaping</code> 设置为 <code>true</code> 的默认值，则 <code>\</code> 用作转义字符</li>
<li>双引号必须位于带引号的 <code>STRING</code> 中并使用转义字符或第二个双引号进行转义</li>
</ul>
<hr />
<ul>
<li>headers 最好设置英文，反正 <code>line.'名称'</code> 这种写法是会报错的</li>
</ul>
<h3>从 csv 导入数据</h3>
<h4>无标题 csv</h4>
<p>使用 <code>LOAD CSV</code> 将数据获取到查询中。然后使用 Cypher 的正常更新子句将其写入数据库。</p>
<pre><code>1,ABBA,1992
2,Roxette,1986
3,Europe,1979
4,The Cardigans,1992
</code></pre>
<p>为 CSV 文件中的每一行创建一个带有 <code>Artist</code> 标签的新节点，CSV 文件中的两列被设置为节点上的属性：</p>
<pre><code>LOAD CSV FROM 'file:///artists.csv' AS line
CREATE (:Artist {name: line[1], year: toInteger(line[2])})
</code></pre>
<p>也可以导入<strong>远程 csv</strong>：</p>
<pre><code>LOAD CSV FROM 'https://data.neo4j.com/bands/artists.csv' AS line
CREATE (:Artist {name: line[1], year: toInteger(line[2])})
</code></pre>
<h4>有标题 csv</h4>
<pre><code>Id,Name,Year
1,ABBA,1992
2,Roxette,1986
3,Europe,1979
4,The Cardigans,1992
</code></pre>
<p>使用 <code>WITH HEADERS</code> 表示文件首行为列名，可以通过相应的列名称访问特定字段：</p>
<pre><code>LOAD CSV WITH HEADERS FROM 'file:///artists-with-headers.csv' AS line
CREATE (:Artist {name: line.Name, year: toInteger(line.Year)})
</code></pre>
<h4>自定义 csv 分隔符</h4>
<p>有时，CSV 文件使用逗号以外的其他分隔符。此时可以使用 <code>FIELDTERMINATOR</code> 指定文件使用的分隔符。</p>
<blockquote>
<p>FIELDTERMINATOR = field terminator</p>
</blockquote>
<p>如果前面加上 <code>\u</code>，则可以使用 unicode 字符编码的十六进制表示形式。编码必须用四位数字书写。例如，<code>\u003B</code> 相当于 <code>;</code> (分号)。</p>
<pre><code>1;ABBA;1992
2;Roxette;1986
3;Europe;1979
4;The Cardigans;1992
</code></pre>
<pre><code>LOAD CSV FROM 'file:///artists-fieldterminator.csv' AS line FIELDTERMINATOR ';'
CREATE (:Artist {name: line[1], year: toInteger(line[2])})
</code></pre>
<h3>导入大量数据</h3>
<p>如果 CSV 文件包含大量行（接近数十万或数百万），则可以使用 <code>CALL { ... } IN TRANSACTIONS</code> 指示 Neo4j 在多行之后提交事务。这可以减少事务状态的内存开销。</p>
<p>也可以设置事务的速率，例如加上 <code>OF 500 ROWS</code> 表示 500 行为一个事务。</p>
<pre><code>LOAD CSV FROM 'file:///artists.csv' AS line
CALL {
  WITH line
  CREATE (:Artist {name: line[1], year: toInteger(line[2])})
} IN TRANSACTIONS OF 500 ROWS
</code></pre>
<h3>导入包含转义字符的数据</h3>
<p>当数据周围都有额外的引号，甚至值内还有转义引号时：</p>
<pre><code>"1","The ""Symbol""","1992"
</code></pre>
<blockquote>
<p><code>"The ""Symbol"""</code> = <code>The "Symbol"</code></p>
</blockquote>
<pre><code>LOAD CSV FROM 'file:///artists-with-escaped-char.csv' AS line
CREATE (a:Artist {name: line[1], year: toInteger(line[2])})
RETURN
  a.name AS name,
  a.year AS year,
  size(a.name) AS size
</code></pre>
<p>返回结果如下：</p>
<table>
<thead>
<tr>
<th>name</th>
<th>year</th>
<th>size</th>
</tr>
</thead>
<tbody>
<tr>
<td>'The "Symbol"'</td>
<td>1992</td>
<td>12</td>
</tr>
</tbody>
</table>
<blockquote>
<p>请注意，输出中的 <code>STRING</code> 值用引号 <code>'</code> 包裹</p>
</blockquote>
<h3>获取行号</h3>
<p>对于某些情况，例如调试 csv 文件时，获取 <code>LOAD CSV</code> 正在运行的当前行号可能很有用。 </p>
<p><code>linenumber()</code> 函数提供了该功能，或者如果在没有 <code>LOAD CSV</code> 上下文的情况下调用，则返回 <code>null</code> 。</p>
<pre><code>LOAD CSV FROM 'file:///artists.csv' AS line
RETURN linenumber() AS number, line
</code></pre>
<pre><code>+---------------------------------------+
| number | line                         |
+---------------------------------------+
| 1      | ["1","ABBA","1992"]          |
| 2      | ["2","Roxette","1986"]       |
| 3      | ["3","Europe","1979"]        |
| 4      | ["4","The Cardigans","1992"] |
+---------------------------------------+
4 rows
</code></pre>
<h3>获取绝对路径</h3>
<p>与 <code>linenumber()</code> 类似，如果在没有 <code>LOAD CSV</code> 上下文的情况下调用，则返回 <code>null</code>。</p>
<pre><code>LOAD CSV FROM 'file:///artists.csv' AS line
RETURN DISTINCT file() AS path
</code></pre>
<p>由于 <code>LOAD CSV</code> 可以临时下载文件来处理，因此需要注意的是 <code>file()</code> 将始终返回<strong>磁盘上的路径</strong>。如果使用指向磁盘的 <code>file:///</code> URL 调用 <code>LOAD CSV</code>，<code>file()</code> 将返回相同的路径。</p>
<h2>FAQ</h2>
<h3>导出 cypher</h3>
<pre><code>:history
</code></pre>
<p>然后导出即可</p>
]]></content>
    <author><name>Sun2ot</name></author>
    <category term="教程"/>
  </entry>
  <entry>
    <title>Linux 用户管理</title>
    <link href="https://blog.085404.xyz/posts/linux-%E7%94%A8%E6%88%B7%E7%AE%A1%E7%90%86/" rel="alternate" type="text/html"/>
    <id>https://blog.085404.xyz/posts/linux-%E7%94%A8%E6%88%B7%E7%AE%A1%E7%90%86/</id>
    <published>2023-12-29T00:00:00.000Z</published>
    <updated>2023-12-29T00:00:00.000Z</updated>
    <summary></summary>
    <content type="html"><![CDATA[<h2>最小权限原则</h2>
<p>最小权限原则（Principle of Least Privilege, POLP）是一种在计算机安全领域广泛遵循的原则，其核心理念是：任何用户、进程、程序或系统组件都应仅拥有执行其任务所必需的最小范围内的权限。换句话说，不应授予比完成工作所需的更多访问权或特权。</p>
<p>具体来说：</p>
<ul>
<li>每个用户账号应当只被赋予完成其职责所必要的账户权限，例如，如果一个用户只需要读取特定文件，则不应授予其写入或删除该文件的权限。</li>
<li>应用程序和服务应当以非管理员或最少特权用户身份运行，除非它们确实需要进行系统级别的操作。</li>
<li>系统配置和策略应当限制不必要的资源访问，确保即使有恶意软件或内部误操作发生时，攻击面也能保持最小化。</li>
</ul>
<p>通过实施最小权限原则，可以有效减少潜在的安全威胁，因为即便某个部分遭到破坏，其损害也会受限于其所拥有的权限级别。这有助于防止恶意活动扩散，并保护敏感信息不被未经授权的访问或修改。</p>
<h2>用户</h2>
<h3>切换到其他用户</h3>
<p>变更帐号为 root 并在执行 ls 指令后退出变回原使用者：</p>
<pre><code>su -c ls root
</code></pre>
<p>变更帐号为 test 并改变工作目录至 test 的家目录：</p>
<pre><code>su - test
</code></pre>
<blockquote>
<p>从 root 用户切换为之前的普通用户只需要 <code>exit</code> 即可。</p>
</blockquote>
<h3>修改密码</h3>
<p>普通用户修改自己的密码</p>
<pre><code>passwd
</code></pre>
<p>root 用户修改其他用户密码</p>
<pre><code>passwd user_name
</code></pre>
<blockquote>
<p>如果修改密码过程中不想修改了，可以尝试 <code>ctrl + c</code> 或者 <code>ctrl + d</code> 退出</p>
</blockquote>
<p>锁定某个用户的密码（让他不能修改）：</p>
<pre><code># 锁定
passwd -l user_name
# 解除锁定
passwd -u user_name
</code></pre>
<p>删除用户密码（则登录时无需密码）：</p>
<pre><code>passwd -d user_name
</code></pre>
<h2>用户组</h2>
<blockquote>
<p>将用户归为特定的组并设置路径权限</p>
</blockquote>
<h3>创建组</h3>
<p>首先，如果A组和B组还不存在，你可以使用 <code>groupadd</code> 命令来创建它们：</p>
<pre><code>sudo groupadd groupA
sudo groupadd groupB
</code></pre>
<h3>将用户添加到组中</h3>
<p>使用 <code>usermod</code> 命令将用户添加到所需的组中。例如，将用户 <code>user1</code> 添加到 <code>groupA</code>：</p>
<pre><code>sudo usermod -aG groupA user1
</code></pre>
<p>同样地，将用户 <code>user2</code> 添加到 <code>groupB</code>：</p>
<pre><code>sudo usermod -aG groupB user2
</code></pre>
<p>在这个命令 <code>sudo usermod -aG groupA user1</code> 中：</p>
<ul>
<li><code>-a</code>: 这个选项是 "append" 的缩写。使用 <code>-a</code> 选项意味着你想要向用户的现有组（以及其他组）中添加一个新的组，<strong>而不是替换掉现有的组</strong>。</li>
<li><code>G</code>: 这个选项告诉 <code>usermod</code> 命令你要修改用户的组关联。</li>
</ul>
<p>因此，<code>-aG</code> 组合选项的作用是将指定用户添加到指定的组（在此例中是 <code>groupA</code>）中，而不会影响该用户的其他组关联。</p>
<blockquote>
<p>如果想要让某个文件/路径A组可读写，B组可读，单纯通过 <code>chmod</code> 无法实现，需要借助更高级的权限管理工具。因此一种常规的做法是，将同一个用户添加到多个组中，通过对组的权限进行设置，实现复杂权限控制。</p>
</blockquote>
<p>如果你想要将当前用户（即你正在使用的用户）<strong>移动到一个特定的组</strong>中，你可以使用 <code>usermod</code> 命令。但是需要小心，这样做可能会导致一些权限问题，因为用户现有的文件和目录可能是基于之前的主组创建的。所以，在更改用户的主组后，确保适当地调整文件和目录的权限，<strong>以确保用户在新的主组中有所需的权限</strong>。</p>
<p>要将当前用户（例如，假设当前用户是 <code>user1</code>）移动到 <code>groupA</code> 组，你可以执行以下命令：</p>
<pre><code>sudo usermod -g groupA user1
</code></pre>
<h3>设置路径权限</h3>
<p>使用 <code>chmod</code> 和 <code>chown</code> 命令来设置路径的权限。例如，如果你有一个名为 <code>/path/to/your/directory</code> 的目录，并且你想让 <code>groupA</code> 的用户有对该目录的读写权限，而 <code>groupB</code> 的用户只有读权限，你可以执行以下操作：</p>
<ol>
<li>设置目录 <code>所有者:组</code>：</li>
</ol>
<pre><code>sudo chown user1:groupA /path/to/your/directory
</code></pre>
<blockquote>
<p>所有者和组没有必然联系，所有者未必是属于这个组的。</p>
</blockquote>
<ol>
<li>设置目录的权限为允许 <code>groupA</code> 读写，但只允许 <code>groupB</code> 读：</li>
</ol>
<pre><code>sudo chmod 770 /path/to/your/directory
</code></pre>
<p>现在，只有属于 <code>groupA</code> 的用户可以读取、写入和执行 <code>/path/to/your/directory</code>。其他用户（<strong>不是所有者</strong>也不属于 <code>groupA</code> 的用户）则不能访问这个目录。</p>
<ol>
<li><strong>设置ACL (可选)</strong>：
如果你想为特定的组或用户设置更细粒度的权限控制，可以考虑使用ACL（访问控制列表）。这允许你设置文件或目录的详细权限规则。</li>
</ol>
<h3>修改所属组</h3>
<pre><code># 修改所属用户
sudo chown 新所有者用户名 文件或目录路径
# 修改所属组
sudo chown :新组名 文件或目录路径
# 同时修改所属用户和所属组
sudo chown 新所有者用户名:新组名 文件或目录路径
</code></pre>
<h2>Linux 权限说明</h2>
<p>在Linux和UNIX系统中，文件和目录的权限可以使用数字形式表示。这些数字是通过组合不同的权限标志来构成的。</p>
<p>每个权限标志对应一个特定的数值：</p>
<ul>
<li><code>r</code>（读取权限）：对应数值 <code>4</code></li>
<li><code>w</code>（写入权限）：对应数值 <code>2</code></li>
<li><code>x</code>（执行权限）：对应数值 <code>1</code></li>
</ul>
<p>为了设置文件或目录的权限，你可以为所有者、组和其他用户分别分配上述的权限数值。当你将这些数值相加，就可以得到完整的三位数字表示权限。</p>
<p>举例来说，如果你看到一个权限设置是 <code>770</code>，它可以被分解为：</p>
<ul>
<li>第一个数字 <code>7</code>：代表所有者的权限。这是通过将 <code>rwx</code> 转换为对应的数值进行计算得到的，即 <code>4 (读) + 2 (写) + 1 (执行) = 7</code>。</li>
<li>第二个数字 <code>7</code>：代表所属组（即<strong>属于该组的</strong>其他用户）的权限。同样，它也是 <code>rwx</code>，即 <code>4 (读) + 2 (写) + 1 (执行) = 7</code>。</li>
<li>第三个数字 <code>0</code>：代表其他用户（不是文件或目录的所有者或同组的用户）的权限。这里为 <code>0</code> 意味着没有任何权限。</li>
</ul>
<h2>授权指令chmod</h2>
<p>https://www.runoob.com/linux/linux-comm-chmod.html</p>
<h4>语法</h4>
<pre><code>chmod [-cfvR] [--help] [--version] mode file...
</code></pre>
<h4>参数说明</h4>
<p>mode : 权限设定字串，格式如下 :</p>
<pre><code>[ugoa...][[+-=][rwxX]...][,...]
</code></pre>
<p>其中：</p>
<ul>
<li><code>u</code> 表示该文件的拥有者，<code>g</code> 表示与该文件的拥有者属于同一个群体(group)者，<code>o</code> 表示其他以外的人，<code>a</code> 表示这三者皆是。</li>
<li><code>+</code> 表示增加权限、<code>-</code> 表示取消权限、<code>=</code> 表示唯一设定权限。</li>
<li><code>r</code> 表示可读取，<code>w</code> 表示可写入，<code>x</code> 表示可执行，<code>X</code> 表示只有当该文件是个子目录或者该文件已经被设定过为可执行。</li>
</ul>
<p>其他参数说明：</p>
<ul>
<li><code>-c</code> : 若该文件权限确实已经更改，才显示其更改动作</li>
<li><code>-f</code> : 若该文件权限无法被更改也不要显示错误讯息</li>
<li><code>-v</code> : 显示权限变更的详细资料</li>
<li><code>-R</code> : 对目前目录下的所有文件与子目录进行相同的权限变更(即以递归的方式逐个变更)</li>
<li><code>--help</code> : 显示辅助说明</li>
<li><code>--version</code> : 显示版本</li>
</ul>
<h4>示例</h4>
<pre><code># 给 /test/a 的所有者添加执行权限
chmod u+x /test/a
# 所有人可读、写、执行 /test/a
chmod 777 /test/a
</code></pre>
<h2>查看当前系统的用户/组情况</h2>
<p>要查看当前系统上的所有组和用户，你可以使用以下命令：</p>
<ol>
<li><strong>查看所有组</strong>：</li>
</ol>
<p>你可以查看 <code>/etc/group</code> 文件来列出所有的组。这个文件包含了系统上的所有组的信息。</p>
<pre><code>cat /etc/group
</code></pre>
<p>或者使用以下命令，它会输出更加简洁的组列表：</p>
<pre><code>getent group
</code></pre>
<ol>
<li><strong>查看所有用户</strong>：</li>
</ol>
<p>你可以查看 <code>/etc/passwd</code> 文件来列出所有的用户。这个文件包含了系统上的所有用户的信息。</p>
<pre><code>cat /etc/passwd
</code></pre>
<p>但是，如果你只想查看系统用户而不包括系统服务用户（通常UID &lt; 1000的用户），你可以使用以下命令：</p>
<pre><code>awk -F':' '$3 &gt;= 1000 &amp;&amp; $3 != 65534 { print $1 }' /etc/passwd
</code></pre>
<p>这个命令使用了 <code>awk</code> 工具来过滤 UID 在 1000 或以上的用户，并排除了通常用于系统服务的用户（如 <code>nobody</code>）。</p>
<ol>
<li><strong><code>awk</code></strong>：这是一个文本处理工具，用于对文本文件进行数据提取和处理。</li>
<li><strong><code>-F':'</code></strong>：这是 <code>awk</code> 命令的一个选项，它告诉 <code>awk</code> 使用 <code>:</code> 作为字段分隔符。在这里，我们正在处理 <code>/etc/passwd</code> 文件，该文件使用 <code>:</code> 来分隔不同的字段。</li>
<li><strong><code>'$3 &gt;= 1000 &amp;&amp; $3 != 65534 { print $1 }'</code></strong>：
<ul>
<li><strong><code>'$3 &gt;= 1000</code></strong>：这个条件检查第三个字段（即用户 ID，UID）是否大于或等于 1000。</li>
<li><strong><code>&amp;&amp;</code></strong>：这是一个逻辑与操作符，用于结合多个条件。</li>
<li><strong><code>$3 != 65534</code></strong>：这个条件确保 UID 不是 65534。通常，这个 UID 被 <code>nobody</code> 用户使用，所以我们排除它。</li>
<li><strong><code>{ print $1 }</code></strong>：当上述条件都满足时，执行这个动作，打印出第一个字段，也就是用户名。</li>
</ul>
</li>
<li><strong><code>/etc/passwd</code></strong>：这是我们想要处理的文件路径，它通常包含系统上所有用户的列表及其相关信息。</li>
</ol>
<h2>查看当前用户进程</h2>
<pre><code># u: 显示用户、启动进程的时间和命令, x: 显示没有控制终端的进程
ps ux
# 筛选
ps ux | grep 'python main.py'
</code></pre>
]]></content>
    <author><name>Sun2ot</name></author>
    <category term="教程"/>
  </entry>
  <entry>
    <title>算法-分支限界法</title>
    <link href="https://blog.085404.xyz/posts/%E7%AE%97%E6%B3%95-%E5%88%86%E6%94%AF%E9%99%90%E7%95%8C/" rel="alternate" type="text/html"/>
    <id>https://blog.085404.xyz/posts/%E7%AE%97%E6%B3%95-%E5%88%86%E6%94%AF%E9%99%90%E7%95%8C/</id>
    <published>2023-12-25T00:00:00.000Z</published>
    <updated>2023-12-25T00:00:00.000Z</updated>
    <summary></summary>
    <content type="html"><![CDATA[<p>完整可执行代码见 <a href="https://github.com/sun2ot/algorithm-learning/tree/master/branch-and-bound">sun2ot/algorithm-learning</a></p>
<h2>基本思想</h2>
<p>分支限界法类似于回溯法，也是在问题的解空间上搜索问题解的算法。一般情况下，分支限界法与回溯法的求解目标不同。回溯法的求解目标是找出解空间中满足约束条件的<strong>所有解</strong>，而分支限界法的求解目标则是找出满足约束条件的<strong>一个解</strong>，或是在满足约束条件的解中找出使某一目标函数值达到极大或极小的解，即<strong>在某种意义下的最优解</strong>。</p>
<p>由于求解目标不同，导致分支限界法与回溯法对解空间的搜索方式也不相同。回溯法以<strong>深度优先</strong>的方式搜索解空间，而分支限界法则以<strong>广度优先</strong>或以<strong>最小耗费优先</strong>的方式搜索解空间。</p>
<p>分支限界法的搜索策略是，在扩展结点处，先生成其所有的儿子结点（分支），然后再从当前的活结点表中选择下一个扩展结点。为了有效地选择下一扩展结点，加速搜索的进程，在每一活结点处，计算一个<strong>函数值（限界）</strong>，并根据函数值，从当前活结点表中选择一个最有利的结点作为扩展结点，<strong>使搜索朝着解空间上有最优解的分支推进</strong>，以便尽快地找出一个最优解。这种方法称为分支限界法。</p>
<p>在搜索问题的解空间树时，分支限界法与回溯法的主要区别在于它们对当前扩展结点所采用的扩展方式不同。在分支限界法中，<strong>每一个活结点只有一次机会成为扩展结点</strong>。活结点一旦成为扩展结点，就一次性产生其所有儿子结点。在这些儿子结点中，导致不可行解或导致非最优解的儿子结点被舍弃，其余儿子结点被加入活结点表中。此后，从活结点表中取下一结点成为当前扩展结点，并重复上述结点扩展过程。这个过程一直持续到找到所需的解或活结点表为空时为止。</p>
<p>从活结点表中选择下一扩展结点的不同方式导致不同的分支限界法。最常见的有以下两种方式:</p>
<ol>
<li><strong>队列式(FIFO)分支限界法</strong>：队列式分支限界法将活结点表组织成一个队列，并按队列的先进先出原则选取下一个结点为当前扩展结点。</li>
<li><strong>优先队列式分支限界法</strong>：优先队列式的分支限界法将活结点表组织成一个优先队列，并按优先队列中规定的结点优先级选取优先级最高的下一个结点成为当前扩展结点。</li>
</ol>
<h2>布线问题</h2>
<h3>问题定义</h3>
<p>印刷电路板将布线区域划分成 $n\times m$ 个方格阵列，如图(a)所示。精确的电路布线问题要求确定连接方格 a 的中点到方格 b 的中点的最短布线方案。在布线时，<strong>电路只能沿直线或直角布线</strong>，如图(b)所示。为了避免线路相交，已布了线的方格做了封锁标记，<strong>其他线路不允许穿过被封锁的方格</strong>。</p>
<p><img src="https://img.085404.xyz/images/7e115db12e895da281f2fb067afead1d.webp" alt="image.png|300*400" /></p>
<h3>算法思想</h3>
<p>布线问题的解空间是一个<strong>图</strong>。解此问题的队列式分支限界法从起始位置 a 开始将它作为第一个扩展结点。与该扩展结点<strong>相邻并且可达</strong>的方格成为可行结点被加入到活结点队列中，并且将这些方格标记为1，即从起始方格 a 到这些方格的距离为1。接着，从活结点队列中取出<strong>队首结点</strong>作为下一个扩展结点，并将与当前扩展结点<strong>相邻且未标记过</strong>的方格标记为2，并存入活结点队列。这个过程一直继续到算法搜索到目标方格 b 或活结点队列为空时为止。</p>
<h3>代码实现</h3>
<pre><code>#define LENGTH 6  // 网格的边长
#define MAX_LENGTH (LENGTH + 2)  // 网格的边长加上边界

int grid[MAX_LENGTH][MAX_LENGTH];  // 网格

typedef struct {  // 网格位置
    int row, col;
} Position;

/**
 * 在给定的起点和终点之间查找路径。
 *
 * @param start 起点位置
 * @param finish 终点位置
 * @param PathLen 路径长度的指针
 * @param path 路径的指针
 * @return 如果找到路径，则返回true；否则返回false。
 */
bool FindPath(Position start, Position finish, int *PathLen, Position **path) {
    Position nextStep[4] = {
        {0, 1},  // 右
        {1, 0},  // 下
        {0, -1}, // 左
        {-1, 0}  // 上
    };

    Position here, next;  // 当前位置和下一个位置
    // 将起点位置赋值给当前位置
    here.row = start.row;  
    here.col = start.col;
    grid[start.row][start.col] = 1;  // 起始点置为1表示已访问
    Queue Q;
    initQueue(&amp;Q);

    while (true) {
        for (int i = 0; i &lt; 4; i++) {  // 检查当前位置的四个方向
            // 四次循环分别检查右、下、左、上四个方向
            next.row = here.row + nextStep[i].row;  
            next.col = here.col + nextStep[i].col;
            // 该位置未被访问过且不是障碍物/边界
            if (grid[next.row][next.col] == 0 &amp;&amp; next.row != -1 &amp;&amp; next.col != -1) {  
                grid[next.row][next.col] = grid[here.row][here.col] + 1;  // 更新到达该位置的路径长度
                if (next.row == finish.row &amp;&amp; next.col == finish.col)   // 到达目标终点
                    break;

                enqueue(&amp;Q, next);  // 将新探测的布线位置(活结点)入队
            }
        }

        if (next.row == finish.row &amp;&amp; next.col == finish.col)   // 对于目标终点，不再探测
            break;
        if (isEmpty(&amp;Q)) return false;  // 队列为空，说明没有路径

        here = dequeue(&amp;Q);  // 取出队首元素，作为新的当前位置
    }

    *PathLen = grid[finish.row][finish.col];  // grid终点存储了到达此处的路径长度
    *path = (Position *)malloc(*PathLen * sizeof(Position));  // 分配存储路径轨迹的空间
    here = finish;  // 从终点开始回溯

    for (int j = *PathLen - 1; j &gt;= 0; j--) {  
        (*path)[j] = here;  // 将当前位置存入路径轨迹
        for (int i = 0; i &lt; 4; i++) {  // 检查当前位置的四个方向
            next.row = here.row + nextStep[i].row;  
            next.col = here.col + nextStep[i].col;
            // 回溯时路径长度j逐渐减小，因此若当前位置四周有路径长度为j的点，则说明是上一个点
            if (grid[next.row][next.col] == j)  
                break;
        }
        here = next;  // 将回溯的点作为新的当前位置
    }

    return true;
}
</code></pre>
<blockquote>
<p>关于 <code>grid</code> 的论述</p>
</blockquote>
<p><code>grid</code> 数组存储了到达当前位置的最短路径长度。</p>
<ul>
<li>为 -1 时，由于不可能存在比它更小的值，因此作为<strong>边界/障碍物</strong>使用</li>
<li>为 0 时，表示尚未记录到达此处的距离，因为表示该点<strong>没有被访问过</strong></li>
<li>为 &gt;0 的值时，表示<strong>到达此处的当前最短距离</strong>。</li>
</ul>
]]></content>
    <author><name>Sun2ot</name></author>
    <category term="笔记"/>
  </entry>
  <entry>
    <title>算法-回溯算法</title>
    <link href="https://blog.085404.xyz/posts/%E7%AE%97%E6%B3%95-%E5%9B%9E%E6%BA%AF%E6%B3%95/" rel="alternate" type="text/html"/>
    <id>https://blog.085404.xyz/posts/%E7%AE%97%E6%B3%95-%E5%9B%9E%E6%BA%AF%E6%B3%95/</id>
    <published>2023-12-22T00:00:00.000Z</published>
    <updated>2023-12-22T00:00:00.000Z</updated>
    <summary></summary>
    <content type="html"><![CDATA[<p>完整可执行代码见 <a href="https://github.com/sun2ot/algorithm-learning/tree/master/backtracking">sun2ot/algorithm-learning</a></p>
<h2>n 后问题</h2>
<h3>问题定义</h3>
<p>在 $n \times n$ 格的棋盘上放置彼此不受攻击的 $n$ 个皇后。按照国际象棋的规则，皇后可以攻击与之处在<strong>同一行</strong>或<strong>同一列</strong>或<strong>同一斜线</strong>上的棋子。$n$ 后问题等价于在 $n \times n$ 格的棋盘上放置 $n$ 个皇后，任何 2 个皇后不放在同一行或同一列或同一斜线上。</p>
<h3>算法思想</h3>
<p>用 $n$ 元组 $x[1:n]$ 表示 $n$ 后问题的解。其中，$x[i]$ 表示皇后 $i$ 放在第 $i$ 行的第 $x[i]$ 列，则存在以下要求：</p>
<ul>
<li>不在同一行：$i$ 不同就可以保证</li>
<li>不在同一列：$x[i]$ 表示第 $x[i]$ 列，因此 $x[i]$ 也互不相同</li>
</ul>
<blockquote>
<p>[!info] 对于不在同一斜线的情况，做出以下论述。</p>
</blockquote>
<p>假设 $n \times n$ 格的棋盘的行号从左到右、列号从上到下分别为 $1,2,...,n$，则：</p>
<ul>
<li>
<p>斜率为 $-1$ 的直线（有若干条，且互相平行）所对应的格子，例如下图中黄色方格的坐标分别为 $(1,2),\ (2,3)$，其横纵坐标的<strong>差值</strong>都为 $-1$。同理，红色、黄色对应方格的坐标差值 $0$ 和 $-2$，也是分别相同的。</p>
<p>因此，假设两个皇后放置的位置分别为 $(i,j),\ (k,l)$，若 $i-j=k-l$，则说明两者同处于斜率为 $-1$ 的斜线上。
<img src="https://img.085404.xyz/images/9224f0ea2bd122b7e96698f0908a519e.webp" alt="未命名文件.svg" /></p>
</li>
<li>
<p>斜率为 $+1$ 的直线所对应的格子，例如下图中宇宙超级无敌绝绝魅惑紫色方格的坐标分别为 $(2,1),\ (1,2)$，其横纵坐标的<strong>和</strong>都为 $3$。同理，青色与褐色对应的方格的坐标和也是分别相同的。</p>
<p>因此，假设两个皇后放置的位置分别为 $(i,j),\ (k,l)$，若 $i+j=k+l$，则说明两者同处于斜率为 $+1$ 的斜线上。
<img src="https://img.085404.xyz/images/bf7c41179f5baab389cb831ed3c0d3c7.webp" alt="未命名文件.png" /></p>
</li>
</ul>
<p>综上所述，我们可以给出 N 后问题的所有约束。</p>
<h3>代码实现</h3>
<blockquote>
<p>[!info] 关于 N 后问题，有一个未按照上述思路优化的原始版本，见仓库代码 <code>NQueen.c</code></p>
</blockquote>
<pre><code>/**
 * 判断在指定位置放置皇后是否可行
 *
 * @param solution 存放皇后位置的数组
 * @param row      当前行数
 * @param col      当前列数
 * @return         如果安全放置皇后，则返回 true；否则返回 false
 */
bool isSafe(int* solution, int row, int col) {
    for (int i = 0; i &lt; row; i++) {
        // 检查是否在同一列或同一斜线上
        if (solution[i] == col || abs(i - row) == abs(solution[i] - col)) {
            return false;
        }
    }
    return true;
}

/**
 * 解决N皇后问题的辅助函数
 * 
 * @param solution 存放皇后位置的数组
 * @param row 当前处理的行
 * @param n 皇后的数量
 */
void solveNQueensUtil(int* solution, int row, int n) {
    if (row == n) {
        // 处理完最后一行，表示找到一个解
        printSolution(solution, n);
        return;
    }

    for (int col = 0; col &lt; n; col++) {  // 遍历当前行的每一列
        if (isSafe(solution, row, col)) {
            // 当前位置安全，将皇后放置在该位置
            solution[row] = col;

            // 递归地尝试放置下一行的皇后
            solveNQueensUtil(solution, row + 1, n);

            // 没有显式地撤销当前皇后的位置，这是因为我们使用了一个一维数组 solution 来存储每行皇后的列位置
            // 回溯：当递归返回到当前行时，我们会继续在其他列尝试放置皇后。在这个过程中，solution[row] 的值会被新的列位置覆盖，这实际上就是撤销了之前放置在该行的皇后。

        }
    }
}

/**
 * 解决N皇后问题
 * 
 * @param n 皇后的数量
 */
void solveNQueens(int n) {
    int* solution = (int*)malloc(n * sizeof(int));  // 申请分配用于存放皇后位置的数组

    solveNQueensUtil(solution, 0, n);  // 从第0行开始处理

    free(solution);  // 释放内存
}
</code></pre>
<blockquote>
<p>[!question] 为什么 <code>isSafe()</code> 中检查冲突时，只需要检查前 row 行？</p>
</blockquote>
<p>在 N 后问题中，我们通常会按行顺序，每一次放置一个皇后。也就是说，当我们在检查是否可以在第 <code>row</code> 行和第 <code>col</code> 列放置皇后时，我们知道所有在第 <code>row</code> 行之前的行已经放置了皇后，而所有在第 <code>row</code> 行之后的行还没有放置皇后。</p>
<h2>01 背包问题</h2>
<h3>问题定义</h3>
<p>01 背包问题是背包问题的一个特例，即装入物品时，<strong>不能装入物品的一部分</strong>，要么全部装入、要么不装入，因为这种非此即彼的选择方式，得名 <code>01</code> 背包问题。</p>
<h3>算法思想</h3>
<p>在背包问题的解决过程中，我们需要尝试所有可能的物品组合，以找到最优解。为了实现这一点，我们对每个物品都有两种选择：<strong>选择它</strong>或者<strong>不选择它</strong>，分别对应解空间的左右子树。当我们选择了一个物品（即 <code>selected[index] = true;</code>）并递归地处理了所有后续的物品后，我们需要撤销对当前物品的选择，以便我们可以在不选择当前物品的情况下，尝试所有可能的后续选择。</p>
<p>但是，当不选择当前物品时，我们可以借助剪枝函数提前回溯：若不选择当前物品 <code>index</code>，则假设尽力装入从下一物品 <code>index+1</code> 开始的剩余物品（按背包问题的方式，即可以装入部分），可以求出一个<strong>价值上界</strong>。那么：</p>
<ul>
<li>如果这个价值上界<strong>小于</strong>当前的最高价值，则无需继续考虑从 <code>index+1</code> 开始的该分枝。原因很简单，你 TM 把剩下的破玩意都塞进去也不能达到新的最大价值，那我还装你干什么。</li>
<li>反之，如果这个价值上界<strong>大于</strong>当前的最高价值，那才有考虑该分枝的价值。</li>
</ul>
<h3>代码实现</h3>
<pre><code>// 定义物品结构体
typedef struct {
    int id;
    int weight;
    int value;
} Item;

/**
 * 计算选择剩余所有物品后的价值上界
 * 
 * @details 用于剪枝，如果价值上界小于当前最大价值，则不再继续搜索
 * 
 * @param items 物品数组
 * @param n 物品数量
 * @param W 背包容量
 * @param index 当前物品索引
 * @param currentWeight 当前背包重量
 * @param currentValue 当前背包价值
 * @return 价值上界
 */
int upperBound(Item* items, int n, int W, int index, int currentWeight, int currentValue) {
    int remainingWeight = W - currentWeight;  // 剩余容量
    int upperValue = currentValue;  // 上界价值：初始值为当前已选中物品的总价值

    // 从index开始，尽可能多地装入物品
    while (index &lt; n &amp;&amp; items[index].weight &lt;= remainingWeight) {  // 不是最后一个物品，且剩余容量足够装下当前物品
        remainingWeight -= items[index].weight;
        upperValue += items[index].value;
        index++;
    }

    if (index &lt; n) {  // 剩余容量不足以装下当前物品
        upperValue += (remainingWeight * (items[index].value/items[index].weight));  // 装入部分当前物品，更新上界价值
    }

    return upperValue;
}

/**
 * 回溯搜索函数
 *
 * @param items 物品数组
 * @param n 物品数量
 * @param W 背包容量
 * @param selected 选中的物品数组
 * @param currentWeight 当前背包重量
 * @param currentValue 当前背包价值
 * @param maxValue 最大背包价值
 * @param index 当前物品索引
 */
void backtrack(Item* items, int n, int W, bool* selected, int* currentWeight, int* currentValue, int* maxValue, int index) {
    if (index == n) {  // 处理完所有物品
        if (*currentWeight &lt;= W &amp;&amp; *currentValue &gt; *maxValue) {  // 找到一个更优解
            *maxValue = *currentValue;
            printf("The max value is: %d\n", *maxValue);
            printf("The best choice is as follows:\n");
            for (int i = 0; i &lt; n; i++) {
                if (selected[i]) {
                    printf("item %d\n", items[i].id);
                }
            }
            printf("----------\n");
        }
        return;
    }

    if (*currentWeight + items[index].weight &lt;= W) {  // 当前物品可以装入背包
        selected[index] = true;  // 选中当前物品
        // 更新当前总重量和总价值
        *currentWeight += items[index].weight;
        *currentValue += items[index].value;
        // 选择当前物品的情况下，递归处理下一个物品
        backtrack(items, n, W, selected, currentWeight, currentValue, maxValue, index + 1);

        selected[index] = false;  // 撤销选择当前的物品
        // 恢复不选择这个物品时的总重量和总价值
        *currentWeight -= items[index].weight;
        *currentValue -= items[index].value;
    }
    
    // 不选择当前物品(从index+1开始)的情况下，尝试所有可能的后续选择
    // 如果选择剩余物品的上界价值大于当前最大价值，则递归处理下一个物品
    if (upperBound(items, n, W, index + 1, *currentWeight, *currentValue) &gt; *maxValue) {
        backtrack(items, n, W, selected, currentWeight, currentValue, maxValue, index + 1);  
    }
}

/**
 * 解决01背包问题
 * 
 * @param items 物品数组
 * @param n 物品数量
 * @param W 背包容量
 */
void knapsack(Item* items, int n, int W) {
    // 初始化
    bool selected[n];
    int currentWeight = 0;
    int currentValue = 0;
    int maxValue = 0;
    int index = 0;

    // 按照单位重量价值进行降序排序
    qsort(items, n, sizeof(Item), compare);
    printf("Sort by unitValue:\n");
    for (int i = 0; i &lt; n; i++)
    {
        printf("item %d: weight = %d, value = %d, unitValue = %d\n", items[i].id, items[i].weight, items[i].value, items[i].value/items[i].weight);
    }
    printf("----------\n");
    

    backtrack(items, n, W, selected, &amp;currentWeight, &amp;currentValue, &amp;maxValue, index);

    printf("The final best value is: %d\n", maxValue);
}
</code></pre>
]]></content>
    <author><name>Sun2ot</name></author>
    <category term="笔记"/>
  </entry>
  <entry>
    <title>算法-贪心算法</title>
    <link href="https://blog.085404.xyz/posts/%E7%AE%97%E6%B3%95-%E8%B4%AA%E5%BF%83%E7%AE%97%E6%B3%95/" rel="alternate" type="text/html"/>
    <id>https://blog.085404.xyz/posts/%E7%AE%97%E6%B3%95-%E8%B4%AA%E5%BF%83%E7%AE%97%E6%B3%95/</id>
    <published>2023-12-18T00:00:00.000Z</published>
    <updated>2023-12-18T00:00:00.000Z</updated>
    <summary></summary>
    <content type="html"><![CDATA[<p>完整可执行代码见 <a href="https://github.com/sun2ot/algorithm-learning/tree/master/greedy">sun2ot/algorithm-learning</a></p>
<h2>基本思想</h2>
<p>当一个问题具有最优子结构性质时，可以用动态规划法求解。但有时会有更有效的方法，例如贪心算法。顾名思义，贪心算法总是做出当前看来最好的选择。也就是说贪心算法并<strong>不从整体最优上加以考虑</strong>，它所做出的选择只是<strong>在某种意义上的局部最优选择</strong>。当然，我们希望贪心算法得到的最终结果也是整体最优的。</p>
<p>虽然贪心算法不是对所有问题都能得到整体最优解，但对范围相当广的许多问题它能产生整体最优解。如图的单源最短路径问题、最小生成树问题等。在一些情况下，即使贪心算法不能得到整体最优解，但其最终结果却是最优解的很好的近似解。</p>
<h2>活动安排问题</h2>
<h3>问题定义</h3>
<p>设有 $n$ 个活动的集合 $E={1,2,...,n}$，其中每个活动都要求使用同一资源，如演讲会场等，而在同一时间内只有一个活动能使用这一资源。每个活动 i 都有一个要求使用该资源的起始时间 $s_i$ 和一个结束时间 $f_i$，且 $s_i \lt f_i$。如果选择了活动 $i$，则它在半开时间区间 $[s_i\ f_i)$ 内占用资源。若区间 $[s_i,\ f_i)$ 与区间 $[s_j,\ f_j)$ 不相交，则称活动 $i$ 与活动 $j$ 是相容的。也就是说，当 $s_i \geq f_j$ 或 $s_j \geq f_i$ 时，活动 $i$ 与活动 $j$ 相容。活动安排问题就是要在所给的活动集合中选出<strong>最大的相容活动子集合</strong>。</p>
<h3>算法思想</h3>
<p>首先对活动按照结束时间进行排序。然后，我们选择第一个活动，并从第二个活动开始遍历，如果当前活动的开始时间大于等于前一个所选活动的结束时间，则选择该活动，并输出其信息。</p>
<p>由于算法每次总是选择具有最早完成时间的相容活动。直观上，按这种方法选择相容活动为未安排活动留下尽可能多的时间。也就是说，该算法的贪心选择的意义是<strong>使剩余的可安排时间段极大化</strong>，以便安排尽可能多的相容活动。</p>
<h3>代码实现</h3>
<pre><code>// 活动结构体
typedef struct {
    int start;
    int finish;
} Activity;

// 比较函数，用于排序活动
int compare(const void* a, const void* b) {
    // 类型转换的优先级低，因此需要加两重括号
    return ((Activity*)a)-&gt;finish - ((Activity*)b)-&gt;finish;
}

// 贪心算法解决活动安排问题
void activityScheduling(Activity activities[], int n) {

    // 按照结束时间对活动进行排序
    qsort(activities, n, sizeof(Activity), compare);

    // 输出排序后的activities数组
    printf("Sorted activities, format is 'id: (start time, end time)': \n");
    for (int i = 0; i &lt; n; i++) {
        printf("%d: (%d, %d)\n", i + 1, activities[i].start, activities[i].finish);
    }

    printf("selected activity, format is 'id: (start time, end time)': \n");

    // 选择第一个活动
    int i = 0;
    printf("%d: (%d, %d)\n", i + 1, activities[i].start, activities[i].finish);

    // 选择其他活动
    for (int j = 1; j &lt; n; j++) {
        if (activities[j].start &gt;= activities[i].finish) {  // 活动j与活动i不冲突
            i = j;  // 选择活动j
            printf("%d: (%d, %d)\n", i + 1, activities[i].start, activities[i].finish);
        }
    }
}
</code></pre>
<h3>全局最优解证明</h3>
<p>贪心算法并不总能求得问题的整体最优解。但对于活动安排问题，贪心算法却总能求得的整体最优解。这个结论可以用数学归纳法证明。</p>
<p>假设活动集合 $E={1,2,...,n}$ 是一个已经<strong>以结束时间非递减顺序</strong>排好的集合，那么活动 1 的完成时间是最早的。</p>
<hr />
<p>首先证明存在<strong>第一个活动就是贪心算法选择的活动</strong>的最优解。假设 $A \subseteq E$ 是一个最优解，且 $A$ 中的活动也是以结束时间非递减顺序排好的。假设 $A$ 中的第一个活动是 $k$：</p>
<ul>
<li>当 $k=1$ 时，最优解的第一个活动就是贪心选择的活动。</li>
<li>当 $k \geq 1$ 时，假设 $B=A-{k}\ \cup {1}$ ，则 $B$ 也是相容的，并且个数与 $A$ 相同，所以 $B$ 也是最优的，且也是以贪心选择活动 1 开始的。</li>
</ul>
<p>{% note info modern %}
$B=A-{k}\ \cup {1}$ 就是从 $A$ 中把第一个活动从 $k$ 替换为 1。显然，1 作为结束时间最早的活动，你一个结束的不比我早的 $k$ 放进去都相容，那换成 1 必定也相容。
{% endnote %}</p>
<p>综上，总是存在以贪心选择开始的最优解。</p>
<hr />
<p>在选择了活动 1 后，剩下的任务就是从 $E' = E-{1}$ 中继续选择活动。若 $A$ 是最优解，则 $A'=A-{1}$ 也是 $E'$ 的最优解。下面给出证明。</p>
<p>假设能找到 $E'$ 的另一个最优解 $B'$，其包含的活动数量比 $A'$ 更多，那就八达鸟了。如果说最优解 $A$ 里包含 $m$ 个活动，那么 $A'$ 比它少一个，也就是 $m-1$ 个。那作为比 $A'$ 数量更多的 $B'$，至少得是 $m$ 个吧，并且这 $m$ 个活动里<strong>不包括活动 1</strong>（因为活动 1 已经被选择了，all right？）。还是那个道理，活动 1 作为结束最早的活动，把它加入 $B'$ 显然也是相容的，此时 $B'$ 至少包含 $m+1$ 个活动。显然，最优解也才 $m$ 个，那 $B'$ 直接荣登最优解，这与假设矛盾。</p>
<p>综上，在活动安排问题中，贪心算法得出的解就是全局最优解。</p>
<h2>背包问题</h2>
<h3>问题定义</h3>
<p>给定 $n$ 种物品和一个背包。物品 $i$ 的重量是 $w_i$，其价值为 $v_i$，背包的容量为 $c$。假设装入物品时，<strong>可以选择物品 $i$ 的一部分</strong>，那么如何选择装入背包中的物品，使得装入背包中的物品总价值最大？</p>
<h3>算法思想</h3>
<p>首先计算每种物品单位重量的价值 $\frac {v_i} {w_i}$，然后依据贪心策略，将尽可能多的单位价值最高的物品装入背包。若装入后总重量未超过背包容量 $c$，则继续装入单位重量次高的物品，以此类推，直到<strong>背包装满</strong>为止。</p>
<p>{% note warning modern %}
与 01 背包区分，背包问题（分数背包问题）由于可以装入物品的一部分，因此是可以全部装满的。
{% endnote %}</p>
<h3>代码实现</h3>
<pre><code>struct Item {
    int weight;
    int value;
    char *name;  // 物品名称
};

/**
 * 贪心算法解决背包问题
 *
 * @param capacity 背包的容量
 * @param items 物品数组
 * @param numItems 物品的数量
 */
void knapsackGreedy(int capacity, struct Item items[], int numItems) {
    qsort(items, numItems, sizeof(struct Item), compare);  // 按照单位价值从大到小排序

    double totalValue = 0.0;  // 背包中物品的总价值
    int currentWeight = 0;  // 背包中物品的总重量
    int i;

    for (i = 0; i &lt; numItems; i++) {
        if (currentWeight + items[i].weight &lt;= capacity) {  // 物品可以全部装入背包
            totalValue += items[i].value;
            currentWeight += items[i].weight;
            printf("selected all: %s, weight: %d, value: %d\n", items[i].name, items[i].weight, items[i].value);
        } else {  // 物品只能部分装入背包
            int remain = capacity - currentWeight;
            totalValue += items[i].value * ((double)remain / items[i].weight);
            printf("selected part: %s, weight: %d, value: %.2f\n", items[i].name, remain, items[i].value * ((double)remain / items[i].weight));
            break;  // 背包已经装满，退出循环
        }
    }

    printf("max value is: %f\n", totalValue);
}
</code></pre>
]]></content>
    <author><name>Sun2ot</name></author>
    <category term="笔记"/>
  </entry>
  <entry>
    <title>算法-动态规划</title>
    <link href="https://blog.085404.xyz/posts/%E7%AE%97%E6%B3%95-%E5%8A%A8%E6%80%81%E8%A7%84%E5%88%92/" rel="alternate" type="text/html"/>
    <id>https://blog.085404.xyz/posts/%E7%AE%97%E6%B3%95-%E5%8A%A8%E6%80%81%E8%A7%84%E5%88%92/</id>
    <published>2023-12-10T00:00:00.000Z</published>
    <updated>2023-12-10T00:00:00.000Z</updated>
    <summary></summary>
    <content type="html"><![CDATA[<p>完整可执行代码见 <a href="https://github.com/sun2ot/algorithm-learning/tree/master/dynamic-programming">sun2ot/algorithm-learning</a></p>
<h2>基本思想</h2>
<p>与分治法类似，其基本思想也是将待求解问题分解成若干个子问题，先求解子问题，然后从这些子问题的解得到原问题的解。</p>
<p>与分治法不同的是，适合于用动态规划法求解的问题，经分解得到的<strong>子问题往往不是互相独立的</strong>，有些子问题被重复计算了许多次。如果我们能够保存已解决的子问题的答案，而在需要时再找出已求得的答案，这样就可以避免大量的重复计算，从而得到多项式时间算法。</p>
<p>为了达到此目的，可以用一个表来记录所有已解决的子问题的答案。不管该子问题以后是否被用到，只要它被计算过，就将其结果填入表中。这就是动态规划法的基本思想。</p>
<h2>矩阵连乘问题</h2>
<h3>问题定义</h3>
<p>矩阵连乘算法问题是一个经典的优化问题，其目标是找到一种最优的计算顺序，以最小化矩阵连乘操作的总次数。</p>
<p>假设有一系列矩阵 $A_1, A_2, A_3, ..., A_n$ ，其中矩阵 $A_i$ 的维度为 $p_{i-1} \times p_i$ ，我们想要计算它们的连乘积 $A_1 \cdot A_2 \cdot A_3 \cdot ... \cdot A_n$ 。注意，矩阵连乘是一个不可交换的操作，即矩阵的乘法顺序会影响最终结果。</p>
<p>矩阵连乘问题的目标是找到一种最优的计算顺序，使得计算矩阵连乘的总次数最小。这个问题可以使用动态规划的方法来解决，其中关键是定义一个递归的状态和状态转移方程。</p>
<h3>分析最优子结构</h3>
<p>将矩阵连乘积 $A_iA_{i+1}...A_j$ 记为 $A[i:j]$ 。假设最优计算次序是在 $A_k$ 和 $A_{k+1}$ 之间断开，则原问题的解即为 $A[1:k]$ 的计算量加上 $A[k+1:n]$ 的计算量再加上 $A[1: k]\cdot A[k+1:n]$ 的计算量。</p>
<p>那么这个拆解过程存在一个前提，就是计算 $A[1:n]$ 的最优次序所包含的矩阵子链 $A[1:k]$ 和 $A[k+1:n]$ 的次序也是最优的。我们可以用反证法来证明这一点是成立的。</p>
<p><strong>假设</strong>当 $A[1:k], A[k+1:n]$ 这种计算次序<strong>最优</strong>时， $A[1:k]$ 的计算次序不是最优的，即存在 $k'$ ，使得 $A[1:k']$ 的计算量小于 $A[1:k]$ 。那么显然， $A[1: k'], A[k'+1:n]$ 这种拆解方式的计算量一定小于原先的 $A[1:k]$ 和 $A[k+1:n]$ ，即  $A[1:k], A[k+1:n]$ 的计算次序<strong>不是最优</strong>，这与假设矛盾。</p>
<p>{% note info modern %}
同理可证 $A[1: n]$ 所包含的矩阵子链 $A[k+1:n]$ 也是最优的。
{% endnote %}</p>
<p>综上，矩阵连乘问题的最优解包含着其子问题的最优解，这种性质称为<strong>最优子结构性质</strong>。</p>
<p>{% note warning modern %}
问题的最优子结构性质是该问题可用动态规划算法求解的显著特征。
{% endnote %}</p>
<h3>算法思想</h3>
<p>在确定了原问题具有最优子结构性质后，我们需要<strong>递归地定义最优值</strong>。假设计算 $A[i:j]$ 所需的最少数乘次数为 $m[i][j]$ ，则原问题 $A[1:n]$ 的最优值为 $m[1][n]$ 。</p>
<ul>
<li>当 $i=j$ 时， $A[i:j]$ 就是一个单独的矩阵，所以 $m[i][j]=0$</li>
<li>当 $i \lt j$ 时， $m[i][j]=m[i][k]+m[k+1][j]+p_{i-1}p_kp_j$</li>
</ul>
<p>{% note info modern %}
$i&lt;j$ 时的解释
关于 $p$ ，我们将矩阵 $A_i$ 的维数记为 $p_{i-1} \times pi$ 。此处有一个问题，就是<strong>对于两个矩阵相乘，总共的数乘次数为多少</strong>？若 $A$ 是 $p \times q$ 矩阵， $B$ 是 $q\times r$ 矩阵，则计算 $AB$ 总共需要 $pqr$ 次数乘。</p>
<blockquote>
<p>p 行，每行都要乘 r 列，一共就是 pr 次乘法。每一次乘法都是 q 个数和 q 个数相乘，因此总共为 prq=pqr 次。</p>
</blockquote>
<p>因此， $m[i][k]$ 表示从 $A_i$ 乘到 $A_k$ ，其中 $A_i$ 是 $(i-1) \times i$ 矩阵， $A_k$ 是 $(k-1) \times k$ 矩阵，由于矩阵连乘的前提一定是<strong>矩阵链是可乘的</strong>，因此 $A[i:k]$ 的结果是一个 $(i-1) \times k$ 矩阵。同理， $A[k+1:j]$ 的结果是 $k \times j$ 矩阵。所以这两个矩阵相乘的数乘次数为 $p_{i-1}p_kp_j$ 。
{% endnote %}</p>
<p>那么 $k$ 取多少呢？我们无法确定，因为 $i \leq k \lt j$ 的位置有 $i,i+1,...,j-1$ 共 $j-i$ 种可能，因此 $m[i][j]$ 递归地定义为：</p>
<p>$$
m[i][j] = \begin{cases}
0 &amp; i=j \
\min\limits_{i \leq k \lt j}{m[i][j]=m[i][k]+m[k+1][j]+p_{i-1}p_kp_j} &amp; i \lt j
\end{cases}
$$</p>
<p>上式给出了两个信息，一是<strong>最优值</strong> $m$ ，即矩阵连乘所需的最小次数；二是<strong>最优解</strong> $k$ ，即应该怎么划分才能获得最优值。</p>
<h3>代码实现</h3>
<pre><code>/**
 * 使用动态规划解决矩阵连乘问题，并返回最小计算次数和最优解
 * 
 * @param dimensions 矩阵的维度数组
 * @param n 矩阵的数量
 * @param dp 存储最小计算次数的二维数组
 * @param split 存储最优划分点的二维数组
 * @return 最小计算次数
 */
int matrixChainOrder(int dimensions[], int n, int dp[MAX_MATRICES][MAX_MATRICES], int split[MAX_MATRICES][MAX_MATRICES]) {
    // 初始化dp数组和split数组
    for (int i = 1; i &lt;= n; i++) {
        dp[i][i] = 0;
    }

    // 计算子问题的最优解和划分点
    for (int len = 2; len &lt;= n; len++) {  // 子问题规模
        for (int i = 1; i &lt;= n - len + 1; i++) {  // 子问题的起始位置
            int j = i + len - 1;  // 子问题的结束位置
            dp[i][j] = INT_MAX;  // 初始化最优值为最大值

            for (int k = i; k &lt; j; k++) {  // 子问题的划分点
                int cost = dp[i][k] + dp[k + 1][j] + dimensions[i - 1] * dimensions[k] * dimensions[j];
                if (cost &lt; dp[i][j]) {  
                    dp[i][j] = cost;  // 更新最优值
                    split[i][j] = k; // 记录最优划分点
                }
            }
        }
    }

    // 返回最小计算次数
    return dp[1][n];
}


/**
 * 输出最优解
 * 
 * @param split 分割矩阵的位置
 * @param i 矩阵的起始位置
 * @param j 矩阵的结束位置
 */
void printOptimalParenthesis(int split[MAX_MATRICES][MAX_MATRICES], int i, int j) {
    if (i == j) {  // 矩阵只有一个时，直接输出
        printf("A%d", i);
    } else {  // 矩阵有多个时，递归输出
        printf("(");  // 输出左括号
        printOptimalParenthesis(split, i, split[i][j]);  // 输出左边的矩阵链的
        printOptimalParenthesis(split, split[i][j] + 1, j);  // 输出右边的矩阵链
        printf(")");  // 输出右括号
    }
}
</code></pre>
<p>在这段代码中，三重循环的循环变量分别代表以下内容：</p>
<ol>
<li>外层循环变量 <code>len</code>：表示<strong>子问题的规模</strong>，从2开始逐渐增加，直到达到矩阵的大小 <code>n</code>。这个循环用于控制子问题的规模。</li>
<li>中间循环变量 <code>i</code>：表示<strong>子问题的起始位置</strong>，从1开始逐渐增加，直到 <code>n - len + 1</code>。这个循环用于遍历所有可能的子问题起始位置。</li>
<li>内层循环变量 <code>k</code>：表示<strong>子问题的划分点</strong>，从 <code>i</code> 开始逐渐增加，直到 <code>j</code>。这个循环用于遍历所有可能的划分点，计算出最优的划分点。</li>
</ol>
<p>{% note info modern %}
关于 len
{% endnote %}</p>
<p>循环从 <code>len = 2</code> 开始，是因为在矩阵乘法中，我们需要先计算较小的子矩阵的乘积，然后逐渐增加子矩阵的大小。通过从较小的子矩阵开始，我们可以利用已经计算出的乘积结果来计算更大的子矩阵的乘积。</p>
<p>例如，对于一个 4x4 的矩阵，我们首先计算 2x2 的子矩阵的乘积，然后计算 3x3 的子矩阵的乘积，最后计算整个 4x4 矩阵的乘积。通过从较小的子矩阵开始，我们可以利用已经计算出的 2x2 子矩阵的乘积来计算 3x3 子矩阵的乘积，从而减少计算量。</p>
<p>因此，循环从 <code>len = 2</code> 开始，以便从较小的子矩阵开始计算乘积，并逐渐增加子矩阵的大小，直到计算整个矩阵的乘积。</p>
<p>{% note warning modern %}
为什么 $i \leq n-len+1$ ？
{% endnote %}</p>
<p>假设矩阵下标从 1 开始到 n。当问题规模为 len 时，即 $[1:len],\ [2:len+1], ...$ 这样<strong>逐个划分计算</strong>，其对应的起始位置就是 $1,2,...$ 。那么，我们要求起始位置的上界，对应的极端情况显然是<strong>长度为 len 的矩阵链在整个矩阵连乘的式子中尽可能地靠右</strong>，这样得到的起始位置就是尽可能大的。</p>
<p>所以问题就变成了，<strong>已知最后（最右的）一个矩阵为 n，从它开始向左推一个长度为 len 的矩阵链，这个链的起始位置是谁?</strong> 显然，答案是 $n-len+1$ 。</p>
<p>{% note warning modern %}
矩阵的维度有两个值，为什么可以通过一维数组 dimensions 来表示？
{% endnote %}</p>
<p>参见上文两矩阵的数乘次数问题，我们可知，对于第 <code>i</code> 个矩阵， <code>dimensions[i-1]</code> 是它的行数， <code>dimensions[i]</code> 是它的列数。这是因为，如果我们按照顺序进行矩阵链乘法，那么第 <code>i</code> 个矩阵的列数必须等于第 <code>i+1</code> 个矩阵的行数（<strong>矩阵可乘，左列等于右行</strong>），所以我们可以用一个连续的数组来存储所有矩阵的行数和列数。</p>
<h2>最长公共子序列</h2>
<h3>问题定义</h3>
<p>给定两个序列 $X={x_1, x_2,..., x_m},, Y={y_1, y_2, ..., y_n}$ ，找出最长的、公共的子序列。</p>
<h3>分析最优子结构</h3>
<p>最长公共子序列问题有没有最优子结构性质，说白了就是，如果有最长公共子序列 $Z_k={z_1,z_2,...,z_k}$ ， $Z_{k-1}$ 是不是第二长的？（<strong>当然是的</strong>）下面给出该问题的最优子结构性质定义：</p>
<p>设序列 $X_m={x_1, x_2,..., x_m},, Y_n={y_1, y_2, ..., y_n}$ 的最长公共子序列为 $Z_k={z_1,z_2,...,z_k}$ ，则</p>
<ul>
<li>若 $x_m=y_n$ ，则 $z_k=x_m=y_n$ ，且 $Z_{k-1}$ 是 $X_{m-1}$ 和 $Y_{n-1}$ 的最长公共子序列</li>
<li>若 $x_m\neq y_n$ 且 $z_k\neq x_m$ ，则 $Z_k$ 是 $X_{m-1}$ 和 $Y$ 的最长公共子序列</li>
<li>若 $x_m\neq y_n$ 且 $z_k\neq y_n$ ，则 $Z_k$ 是 $X$ 和 $Y_{n-1}$ 的最长公共子序列</li>
</ul>
<p>{% note info modern %}
下面分别针对这三点给出证明
{% endnote %}</p>
<p>若 $x_m=y_n$ 时，$z_k\neq x_m$，那意味着最长公共子序列 $Z_k$ 不是最长的。因为 ${z_1,z_2,...z_k,x_m}$ 显然也是公共子序列，且长度为 $k+1$，比 $Z_k$ 还长，构成矛盾。所以当 $x_m=y_n$ 时，必有 $z_k=x_m=y_n$，同时 $Z_{k-1}$ 是 $X_{m-1}$ 和 $Y_{n-1}$ 的<strong>公共子序列</strong>。</p>
<p>接下来证明 $Z_{k-1}$ 是<strong>最长的</strong>。假设 $X_{m-1}$ 和 $Y_{n-1}$ 有长度 $\gt k-1$ 的公共子序列 $W$，那就八达鸟了。因为 $W$ 的长度至少是 $k$，再把两个父串共有的 $x_m$ 加上，$W$ 的长度直接达到 $k+1$，此之谓无中生有，故矛盾。</p>
<hr />
<p>若 $x_m\neq y_n$ 且 $z_k\neq x_m$ ，则 $Z_k$ 肯定是 $X_{m-1}$ 和 $Y$ 的公共子序列，不过是不是最长的呢？还是反证法，我们假设 $X_{m-1}$ 和 $Y$ 有长度大于 $k$ 的公共子序列 $W$，显然 $W$ 也是 $X$ 和 $Y$ 的公共子序列。但 $X$ 和 $Y$ 的最长公共子序列为 $Z_k={z_1,z_2,...,z_k}$ 长度也只有 $k$，故矛盾。</p>
<hr />
<p>第三点证明与二同理，略。</p>
<p>综上，最长公共子序列问题具有最优子结构性质。</p>
<h3>算法思想</h3>
<p>根据最优子结构性质可知，</p>
<ul>
<li>任一串为空时，最长公共子序列长度为 0</li>
<li>当 $x_m=y_n$ 时，找出 $X_{m-1}$ 和 $Y_{n-1}$ 的最长公共子序列，然后尾部加上 $x_m\ \text{or}\ y_n$ 即为 $X$ 和 $Y$ 的最长公共子序列。</li>
<li>当 $x_m\neq y_n$ 时，取 $X_{m-1}$ 和 $Y$、$X$ 和 $Y_{n-1}$ 的最长公共子序列中更长的一个</li>
</ul>
<p>将序列 $X_i$ 和 $Y_j$ 的最长公共子序列长度记为 $c[i][j]$，根据以上内容，构建递推关系式如下：</p>
<p>$$
c[i][j] = \begin{cases}
0 &amp; i=0\ \text{or}\ j=0 \
c[i-1][j-1] + 1 &amp; i,j \gt 0;\ x_i = y_j \
\max{c[i][j-1],c[i-1][j]} &amp; i,j \gt 0;\ x_i \neq y_j
\end{cases}
$$</p>
<h3>代码实现</h3>
<pre><code>/**
 * 使用动态规划解决最长公共子序列问题
 * 
 * @param str1 字符串1
 * @param str2 字符串2
 */
void longestCommonSubsequence(char* str1, char* str2) {
    int m = strlen(str1);
    int n = strlen(str2);

    int dp[m + 1][n + 1];

    // 初始化dp数组的第一行和第一列
    for (int i = 0; i &lt;= m; i++) {  
        dp[i][0] = 0;  
    }
    for (int j = 0; j &lt;= n; j++) {
        dp[0][j] = 0;
    }

    // 计算dp数组的其他元素
    for (int i = 1; i &lt;= m; i++) {
        for (int j = 1; j &lt;= n; j++) {
            if (str1[i - 1] == str2[j - 1]) {  // 两个子串的最后一个字符相同
                dp[i][j] = dp[i - 1][j - 1] + 1;
            } else {
                dp[i][j] = (dp[i - 1][j] &gt; dp[i][j - 1]) ? dp[i - 1][j] : dp[i][j - 1];
            }
        }
    }

    // 构造最长公共子序列
    int lcsLength = dp[m][n];
    char lcs[lcsLength + 1];
    lcs[lcsLength] = '\0';

    int i = m, j = n;
    while (i &gt; 0 &amp;&amp; j &gt; 0) {
        // 串的扫描和LCS的构建都是从右往左，倒着来
        if (str1[i - 1] == str2[j - 1]) {  // 两个串的尾部字符相等，说明该字符属于LCS
            lcs[lcsLength - 1] = str1[i - 1];
            i--;
            j--;
            lcsLength--;
        } else if (dp[i - 1][j] &gt; dp[i][j - 1]) {  // 删去str1最后一个字符得到的LCS比删去str2最后一个字符得到的LCS长，因此i向前回溯
            i--;
        } else {
            j--;
        }
    }
    printf("LAS Length is: %d\n", dp[m][n]);
    printf("LCS is: %s\n", lcs);  // 注意，最长公共子序列并不唯一
}
</code></pre>
]]></content>
    <author><name>Sun2ot</name></author>
    <category term="笔记"/>
  </entry>
  <entry>
    <title>算法-分治法</title>
    <link href="https://blog.085404.xyz/posts/%E7%AE%97%E6%B3%95-%E5%88%86%E6%B2%BB%E6%B3%95/" rel="alternate" type="text/html"/>
    <id>https://blog.085404.xyz/posts/%E7%AE%97%E6%B3%95-%E5%88%86%E6%B2%BB%E6%B3%95/</id>
    <published>2023-12-09T00:00:00.000Z</published>
    <updated>2023-12-09T00:00:00.000Z</updated>
    <summary></summary>
    <content type="html"><![CDATA[<p>完整可执行代码见 <a href="https://github.com/sun2ot/algorithm-learning/tree/master/divide-and-conquer">sun2ot/algorithm-learning</a></p>
<h2>基本思想</h2>
<p>将一个规模为 n 的问题，分解为 k 个规模较小的子问题。这些子问题<strong>相互独立</strong>且<strong>与原问题相同</strong>。递归求解这些子问题，然后将各子问题的解合并得到原问题的解。</p>
<h2>二分搜索</h2>
<h3>算法思想</h3>
<p>算法的核心思想是通过不断缩小搜索范围来逼近目标元素。在每一次迭代中，算法首先计算出中间元素的索引 <code>mid</code> ，然后将其与目标元素进行比较。</p>
<ul>
<li>如果中间元素等于目标元素，则直接返回中间元素的索引</li>
<li>如果中间元素小于目标元素，则将搜索范围缩小为右半部分，将<strong>左边界</strong>更新为 <code>mid + 1</code></li>
<li>如果中间元素大于目标元素，则将搜索范围缩小为左半部分，将<strong>右边界</strong>更新为 <code>mid - 1</code>
通过不断缩小搜索范围，最终可以找到目标元素或确定其不存在于数组中。</li>
</ul>
<p>请注意，二分搜索<strong>假设输入的数组已经按升序排列</strong>。如果数组未排序，需要在进行二分搜索之前先对数组进行排序。</p>
<h3>代码实现</h3>
<pre><code>/**
 * 二分查找算法
 * 在有序数组中查找目标元素的索引
 * 
 * @param nums 有序数组
 * @param size 数组大小
 * @param target 目标元素
 * @return 目标元素的索引，如果不存在则返回-1
 */
int binarySearch(int nums[], int size, int target) {
    
    int left = 0; // 左边界
    int right = size - 1; // 右边界

    while (left &lt;= right) {
        // 因为是升序，所以可以保证 right&gt;=left
        int mid = left + (right - left) / 2;

        if (nums[mid] == target) {
            return mid;
        } else if (nums[mid] &lt; target) {
            left = mid + 1;
        } else {
            right = mid - 1;
        }
    }

    return -1; // 如果目标元素不存在于数组中，则返回-1
}
</code></pre>
<h3>复杂度分析</h3>
<p>每执行一次 while 循环，待搜索数组的大小减少一半。因此最坏情况下，时间复杂度为 $O(log_n)$</p>
<h3>Q&amp;A</h3>
<blockquote>
<p>[!question] 为什么别的算法书是 <code>mid = (left + right) / 2;</code> ？</p>
</blockquote>
<p>这是因为在计算中间索引时，使用 <code>(right - left) / 2</code> 可以避免整数溢出的问题。</p>
<p>假设 <code>left</code> 和 <code>right</code> 都是很大的正整数，它们的和可能会超过 <code>int</code> 类型的最大值。如果我们使用 <code>(right + left) / 2</code> 来计算中间索引，可能会导致溢出错误。通过使用 <code>(right - left) / 2</code> ，我们可以确保在计算中间索引时不会发生溢出。</p>
<h2>棋盘覆盖</h2>
<h3>问题定义</h3>
<p>假设你有一个 $2^n \times 2^n$ 大小的棋盘，其中一个方格已经被预先填充（或者说被“覆盖”）。你的任务是使用 L 型的三个方格形状的骨牌（也就是说，它覆盖了一个 2x2 的方格，但是缺少了一个角落）来覆盖剩余的所有方格，且任何 2 个 L 型骨牌不得重叠覆盖，如此使得整个棋盘都被填满。</p>
<p><img src="https://img.085404.xyz/images/9ca0e18330ae5f2455abfee3e49a2f85.webp" alt="image.png" /></p>
<h3>算法思想</h3>
<p>这个问题可以通过分治算法来解决。基本的思路是将棋盘分成四个更小的棋盘，每个棋盘的大小是原来的一半（指边长为 2 的正方形），则<strong>特殊方格必定位于 4 个较小的子棋盘之一</strong>。</p>
<p>为了将无特殊方格的子棋盘转化为特殊棋盘，可以用一个 L 型的骨牌覆盖这三个较小棋盘的交汇处。</p>
<p><img src="https://img.085404.xyz/images/f55c959af4542fcdf694a3d989275f2a.webp" alt="image.png|200*200" /></p>
<p>然后，对每个更小的棋盘递归地应用同样的过程，直到达到基本情况，即棋盘的大小为 $1\times1$ 。</p>
<h3>代码实现</h3>
<pre><code>/**
 * 在棋盘上放置覆盖物
 * 
 * @param tr 当前棋盘的左上角行坐标(row)
 * @param tc 当前棋盘的左上角列坐标(column)
 * @param dr 特殊方格行坐标
 * @param dc 特殊方格列坐标
 * @param size 当前棋盘的大小
 */
void chessboard(int tr, int tc, int dr, int dc, int size) {
    if (size == 1) {
        return;
    }

    int t = tile++; // L型骨牌编号, 自增确保编号唯一
    int s = size / 2; // 分割后棋盘大小

    // 处理左上角子棋盘
    if (dr &lt; tr + s &amp;&amp; dc &lt; tc + s) {  // 特殊方格在此棋盘中, 直接递归处理
        chessboard(tr, tc, dr, dc, s); 
    } else {  // 特殊方格不在此棋盘中, 先覆盖再处理
        board[tr + s - 1][tc + s - 1] = t; // 用t号L型骨牌覆盖右下角方格
        chessboard(tr, tc, tr + s - 1, tc + s - 1, s); // 处理其余方格
    }

    // 处理右上角子棋盘
    if (dr &lt; tr + s &amp;&amp; dc &gt;= tc + s) {
        chessboard(tr, tc + s, dr, dc, s);
    } else {
        board[tr + s - 1][tc + s] = t; // 覆盖左下角
        chessboard(tr, tc + s, tr + s - 1, tc + s, s);
    }

    // 处理左下角子棋盘
    if (dr &gt;= tr + s &amp;&amp; dc &lt; tc + s) {
        chessboard(tr + s, tc, dr, dc, s);
    } else {
        board[tr + s][tc + s - 1] = t; // 覆盖右上角
        chessboard(tr + s, tc, tr + s, tc + s - 1, s);
    }

    // 处理右下角子棋盘
    if (dr &gt;= tr + s &amp;&amp; dc &gt;= tc + s) {
        chessboard(tr + s, tc + s, dr, dc, s);
    } else {
        board[tr + s][tc + s] = t; // 覆盖左上角
        chessboard(tr + s, tc + s, tr + s, tc + s, s);
    }
}
</code></pre>
<p>请注意，这段代码中假设棋盘的大小为 $8\times8$ ，并且特殊位置的行号和列号为 0。你可以根据需要修改这些值。另外，代码中使用 <code>#define</code> 定义了一些常量，你也可以根据需要进行修改。</p>
<p>最终结果如下：</p>
<p><img src="https://img.085404.xyz/images/cbd55ed1b5b6843ed62c729742575f0e.webp" alt="image.png" /></p>
]]></content>
    <author><name>Sun2ot</name></author>
    <category term="笔记"/>
  </entry>
  <entry>
    <title>Labelimg 标注工具</title>
    <link href="https://blog.085404.xyz/posts/labelimg-%E6%A0%87%E6%B3%A8%E5%B7%A5%E5%85%B7/" rel="alternate" type="text/html"/>
    <id>https://blog.085404.xyz/posts/labelimg-%E6%A0%87%E6%B3%A8%E5%B7%A5%E5%85%B7/</id>
    <published>2023-12-01T00:00:00.000Z</published>
    <updated>2023-12-01T00:00:00.000Z</updated>
    <summary></summary>
    <content type="html"><![CDATA[<h2>安装</h2>
<p>切换到 conda 环境下，执行</p>
<pre><code>pip install labelimg
</code></pre>
<p>{% note info modern %}
如果下载缓慢，请切换清华源，见 <a href="https://mirrors.tuna.tsinghua.edu.cn/help/pypi/">pypi | 镜像站使用帮助 | 清华大学开源软件镜像站 | Tsinghua Open Source Mirror</a>
{% endnote %}</p>
<p>{% note warning modern %}
Python 环境最多只能为 3.9，Labelimg 不支持 3.10 以上版本
{% endnote %}</p>
<h2>数据准备</h2>
<p>首先这里需要准备我们需要打标注的数据集。这里我建议新建一个名为 <code>img_data</code> 的文件夹（这个是约定俗成，不这么做也行），里面创建一个名为 <code>images</code> 的文件夹存放我们需要打标签的图片文件；再创建一个名为 <code>labels</code> 存放标注的标签文件；最后创建一个名为 <code>classes.txt</code> 的 txt 文件来存放所要标注的类别名称。</p>
<p><img src="https://img.085404.xyz/images/934badc4977212eab1d5aa7bf9e9b66c.webp" alt="image.png" /></p>
<p>然后把要标注的图片放在 <code>images/</code> 下</p>
<p><img src="https://img.085404.xyz/images/42b59c80370fce396ed8ca2a905d1b6f.webp" alt="image.png" /></p>
<p>在 <code>classes.txt</code> 中写入所有用到的标签</p>
<p><img src="https://img.085404.xyz/images/16461fcd51820f60ac0650869abab39f.webp" alt="image.png" /></p>
<h2>标注</h2>
<h3>启动 labelimg</h3>
<p>从你的 conda prompt 进入数据集路径</p>
<blockquote>
<p>不然你找不到 Labelimg 这个东西</p>
</blockquote>
<p><img src="https://img.085404.xyz/images/f80f49998c94ad1718b8eb2b76b62c32.webp" alt="image.png" /></p>
<p>执行</p>
<pre><code>labelimg images classes.txt
</code></pre>
<p>拿下！</p>
<p><img src="https://img.085404.xyz/images/a71d9ab8745a9dc2f2ecbedbd4fe60df.webp" alt="image.png" /></p>
<h3>开始标注</h3>
<p>标注过程：</p>
<ol>
<li>选中左侧 <code>create rectbox</code></li>
<li>框选图片中主体</li>
<li>选择图片标签（该标签来自于 <code>classes.txt</code> ）</li>
</ol>
<p><img src="https://img.085404.xyz/images/b6f1673a8be9be65f34a8ca021b8cb95.webp" alt="image.png" /></p>
<h3>保存标注</h3>
<p><img src="https://img.085404.xyz/images/636f09cd8c89191e34ae3cc993fc106f.webp" alt="image.png" /></p>
<p>点击 <code>save</code> 下方的按钮可以切换保存的模式，此处切换为 <code>YOLO</code> 。不同的模型所需的标签格式不同，例如 <code>YOLO</code> 所需的标签类型即为 <code>txt</code> 。</p>
<p>点击 <code>save</code> ，将 <code>image_name.txt</code> 保存在刚刚创建的 <code>img_data\labels</code> 路径下。</p>
<p>{% note info modern %}
例如对一张名为 <code>aaa.jpg</code> 的图片进行了标注，则生成的对应标签为 <code>aaa.txt</code> （YOLO 模式下）
{% endnote %}</p>
]]></content>
    <author><name>Sun2ot</name></author>
    <category term="教程"/>
  </entry>
  <entry>
    <title>PowerShell Docs</title>
    <link href="https://blog.085404.xyz/posts/powershell-docs/" rel="alternate" type="text/html"/>
    <id>https://blog.085404.xyz/posts/powershell-docs/</id>
    <published>2023-11-29T00:00:00.000Z</published>
    <updated>2023-11-29T00:00:00.000Z</updated>
    <summary></summary>
    <content type="html"><![CDATA[<h1>PowerShell Docs</h1>
<h3>无法加载文件，因为在此系统上禁止运行脚本</h3>
<pre><code>get-executionpolicy  # 获取当前执行策略
set-executionpolicy remotesigned  # 设置执行策略
</code></pre>
<blockquote>
<p>[!info] 执行策略
<code>Restricted</code> 执行策略 (默认设置) 不允许任何脚本运行。  
<code>AllSigned</code> 和 <code>RemoteSigned</code> 执行策略可防止 Windows PowerShell 运行没有数字签名的脚本。</p>
</blockquote>
<h3>高级特性</h3>
<p>不清楚具体是哪个版本开始，power shell 具有了自动补全的功能，我还以为是 starship 带来的呢 hhhh</p>
<p>在输入重复指令时，可以看见提示信息以灰色字体出现在当前光标后方，按<strong>右箭头</strong>键即可自动补全。</p>
<p>按下 <code>F2</code>，可以查看不同完成建议的列表，通过上下箭头选中，回车上屏。</p>
<p>按照 <a href="https://www.reddit.com/r/PowerShell/comments/ytkqch/ps_suddenly_has_autocomplete_how_to_use_it/?rdt=63286&amp;onetap_auto=true">reddit</a> 的说法，该功能是巨硬合并了 <code>PSReadline</code> 模块后带来的。</p>
<h2>概念</h2>
<p>PowerShell 命令称为 cmdlet（读作 command-let）。除了 cmdlet 外，使用 PowerShell 还可以在系统上运行任何可用命令。</p>
<p>PowerShell 使用 <strong>“动词 - 名词”</strong> 名称对来命名 cmdlet。例如，PowerShell 中包含的 <code>Get-Command</code> cmdlet 用于获取在命令行界面中注册的所有 cmdlet。谓词标识 cmdlet 执行的操作，名词标识该 cmdlet 执行其操作的所在资源。</p>
<h3>谓词</h3>
<p>谓词是 PowerShell 中的一个重要概念。它是大多数 cmdlet 都遵循的一种命名标准。你在编写自己的命令时，也应遵循此命名标准。其中的思路是，谓词表示你尝试执行的操作，例如读取数据或更改数据。 PowerShell 有一个标准化的谓词列表。要获取所有可用谓词的完整列表，请使用 <code>Get-Verb</code> cmdlet：</p>
<pre><code>PS F:\&gt; get-verb

Verb      AliasPrefix     Group        Description
----        -----------      -----          -----------
......
......
</code></pre>
<h2>环境变量的用法</h2>
<p>不同于 Linux 的直接使用 <code>$key</code>，powershell 中必须加上 <code>$env:</code> 的前缀</p>
<pre><code>$env:your_env
</code></pre>
<h2>Get-Member 发现对象、属性和方法</h2>
<pre><code># 获取当前路径下所有vtt字幕文件
$vtts = Get-ChildItem -Path . -Filter "*.vtt"
# 取出其中一个
$vtt = $vtts[0]
# 查看其具有的对象、属性和方法
$vtt | Get-Member
# 只想查看其属性
$vtt | Get-Member -MemberType Properties
</code></pre>
<h2>Get-Command 查找命令</h2>
<p><code>Get-Command</code> cmdlet 将返回系统上安装的所有可用命令的列表，包含四个属性字段</p>
<h3>根据名称筛选</h3>
<pre><code>#  查找名为Get-Process的命令
Get-Command -Name Get-Process

# 使用通配符进行模糊查询：查找所有名为 "xxx-Process" 的命令
Get-Command -Name *-Process
</code></pre>
<h3>根据名词和谓词进行筛选</h3>
<pre><code># 根据谓词筛选：查找名为 "Get-xxx" 的命令
Get-Command -Verb 'Get'

# 根据名词筛选：查找名为 "xxx-Uxxx" 的命令
Get-Command -Noun U*

# 也可以组合参数缩小搜索范围
Get-Command -Verb Get -Noun U*
</code></pre>
<h3>使用其他 Cmdlet 筛选结果</h3>
<p><code>Select-Object </code> 此通用命令可帮助你从一个或多个对象中选取特定属性。还可以限制返回的项目数。</p>
<pre><code># 返回当前会话中前 5 个可用命令的 Name 和 Source 属性值
Get-Command | Select-Object -First 5 -Property Name, Source
</code></pre>
<h2>文件 IO</h2>
<h3>创建文本文件</h3>
<pre><code># 创建
New-Item .\shell_test\text.txt
# 写入
Set-Content .\shell_test\text.txt 'this is a text written by powershell.'
# 读取
Get-Content .\shell_test\text.txt
</code></pre>
<h3>清空文件内容</h3>
<pre><code>Clear-Content .\shell_test\text.txt
</code></pre>
<h3>插入文件内容</h3>
<pre><code>Set-Content .\shell_test\text.txt 'hello'
Add-Content .\shell_test\text.txt 'world'
Get-Content .\shell_test\text.txt
</code></pre>
<p>结果为</p>
<pre><code>hello
world
</code></pre>
<blockquote>
<p>看起来是会自动写入换行符</p>
</blockquote>
<h2>文件和文件夹</h2>
<p><strong>New-Item</strong> cmdlet 用于通过使用 <code>-Path</code> 作为文件路径和 <code>-ItemType</code> 作为文件传递路径来创建文件。</p>
<h3>创建</h3>
<p><strong>创建文件夹</strong>：在 <code>F:\</code> 下创建 <code>shell_test</code> 文件夹</p>
<pre><code>New-Item -Path 'F:\shell_test' -ItemType Directory
</code></pre>
<p><strong>创建文件</strong>：在 <code>F:\shell_test</code> 下创建 <code>test.txt</code> 文件</p>
<pre><code>New-Item -Path 'F:\shell_test\test.txt' -ItemType File
</code></pre>
<h3>复制</h3>
<p><strong>Copy-Item</strong> cmdlet 用于通过传递<strong>要复制的目录/文件的路径</strong>和<strong>目标路径</strong>来复制目录。</p>
<p><strong>复制文件夹</strong></p>
<pre><code>Copy-Item 'F:\shell_test\' 'F:\copy_shell_test'
</code></pre>
<p><strong>递归复制</strong>文件夹（包含里面的内容）</p>
<pre><code>Copy-Item 'F:\shell_test\' -Recurse 'F:\copy_shell_test'
</code></pre>
<p>复制指定的内容</p>
<pre><code>Copy-Item -Filter *.jpg -Path 'F:\shell_test\' -Recurse 'F:\copy_shell_test'
</code></pre>
<h3>删除</h3>
<p><strong>Remove-Item</strong> cmdlet 用于通过传递要删除的目录/文件的路径来删除。</p>
<pre><code># 删除文件
Remove-Item 'F:\shell_test\aa.jpg'
# 删除文件夹
Remove-Item 'F:\shell_test'
</code></pre>
<p>当删除文件夹不为空时，会提示</p>
<pre><code>Confirm
The item at F:\shell_test has children and the Recurse parameter was not specified. If you continue, all children will
be removed with the item. Are you sure you want to continue?
[Y] Yes  [A] Yes to All  [N] No  [L] No to All  [S] Suspend  [?] Help (default is "Y"):
</code></pre>
<p>因此，可以<strong>递归删除</strong>文件夹</p>
<pre><code># 删除 shell_test 及其目录下所有内容
Remove-Item 'F:\shell_test' -Recurse
# 效果同上
Remove-Item 'F:\shell_test\' -Recurse
# 删除 shell_test 目录下所有内容，但不会删除 shell_test 本身
Remove-Item 'F:\shell_test\*' -Recurse
</code></pre>
<h3>移动</h3>
<p><strong>Move-Item</strong> cmdlet 用于通过传递要移动的目录/文件的路径和目标路径来移动目录。</p>
<pre><code># 移动单个文件
Move-Item 'F:\shell_test\2' [-Destination] F:\shell_test\1

# 移动多个文件
Move-Item file1,file2 -Destination /path/to/target
</code></pre>
<p>注意两个问题：</p>
<ol>
<li>不涉及复杂参数的情况下，路径参数的引号可以不加</li>
<li><code>F:\shell_test\2</code> 路径下的文件也会被移动</li>
<li>移动多个文件时，如果省略 <code>-Destination</code>，则最后一个路径默认作为目的地</li>
<li>移动命令没有 <code>-Recurse</code> 一说</li>
<li><code>-Force</code> 参数可以强制运行命令而不要求用户确认。</li>
</ol>
<h3>重命名</h3>
<p><strong>Rename-Item</strong> cmdlet 用于通过传递要重命名的文件 (夹) 的路径和目标名称来重命名文件 (夹)。</p>
<pre><code># 将 1 重命名为 this1
Rename-Item 'F:\shell_test\1' this1
</code></pre>
<p>需注意，同路径下不可以有同名文件夹，否则会失败。</p>
<h3>检测是否存在</h3>
<p><strong>Test-Path</strong> cmdlet 用于检查文件 (夹) 是否存在。</p>
<pre><code>Test-Path 'F:\shell_test\'
</code></pre>
<p>结果为</p>
<pre><code>1
2
3
4
5
8
</code></pre>
<h2>Measure-Object</h2>
<p><strong>Measure-Object</strong> cmdlet 可用于获取传递的输出的属性，例如最小值、最大值、大小、计数、行等。</p>
<pre><code># 获取文件属性
Get-Content .\shell_test\text.txt | Measure-Object -line -word -Character
</code></pre>
<p>结果为（行数、单词数、字符数）</p>
<pre><code>Lines Words Characters Property
----- ----- ---------- --------
2       2         10
</code></pre>
<p>统计目录下文件数量</p>
<pre><code>Get-ChildItem .\shell_test\ | Measure-Object
</code></pre>
<p>表示共有 3 个文件（文件 + 文件夹）</p>
<pre><code>Count             : 3
Average           :
Sum               :
Maximum           :
Minimum           :
StandardDeviation :
Property          :
</code></pre>
<p>也可以如下操作：</p>
<pre><code>$zips = Get-ChildItem *.zip
$zips.Count  # 也能直接获取数量
</code></pre>
<h2>比较运算符</h2>
<p>https://learn.microsoft.com/zh-cn/powershell/module/microsoft.powershell.core/about/about_comparison_operators?view=powershell-7.4</p>
<p><strong>等式</strong></p>
<ul>
<li><code>-eq</code>、<code>-ieq</code>、<code>-ceq</code> - 等于</li>
<li><code>-ne</code>、<code>-ine</code>、<code>-cne</code> - 不等于</li>
<li><code>-gt</code>、<code>-igt</code>、<code>-cgt</code> - 大于</li>
<li><code>-ge</code>、<code>-ige</code>、<code>-cge</code> - 大于或等于</li>
<li><code>-lt</code>、<code>-ilt</code>、<code>-clt</code> - 小于</li>
<li><code>-le</code>、<code>-ile</code>、<code>-cle</code> - 小于或等于</li>
</ul>
<p><strong>匹配</strong></p>
<ul>
<li><code>-like</code>、<code>-ilike</code>、<code>-clike</code> - 字符串匹配通配符模式</li>
<li><code>-notlike</code>、<code>-inotlike</code>、<code>-cnotlike</code> - 字符串与通配符模式不匹配</li>
<li><code>-match</code>、<code>-imatch</code>、<code>-cmatch</code> - 字符串匹配正则表达式模式</li>
<li><code>-notmatch</code>、<code>-inotmatch</code>、<code>-cnotmatch</code> - 字符串与正则表达式模式不匹配</li>
</ul>
<p><strong>替代功能</strong></p>
<ul>
<li><code>-replace</code>，<code>-ireplace</code>，<code>-creplace</code> - 替换与正则表达式模式匹配的字符串</li>
</ul>
<p><strong>包含</strong></p>
<ul>
<li><code>-contains</code>、<code>-icontains</code>、<code>-ccontains</code> - 集合包含值</li>
<li><code>-notcontains</code>、<code>-inotcontains</code>、<code>-cnotcontains</code> - 集合不包含值</li>
<li><code>-in</code> - 值位于集合中</li>
<li><code>-notin</code> - 值不在集合中</li>
</ul>
<p><strong>类型</strong></p>
<ul>
<li><code>-is</code> - 这两个对象的类型相同</li>
<li><code>-isnot</code> - 对象类型不相同</li>
</ul>
<h2>Alias</h2>
<h3>内置别名</h3>
<pre><code>Get-Alias
</code></pre>
<blockquote>
<p>[!tip] <code>ls</code>, <code>pwd</code>, <code>cd</code> 等指令在 power shell 仍然能够工作，就是得益于内置的 alias</p>
</blockquote>
<h3>为无参 cmdlet 创建 alias</h3>
<pre><code># 将Get-AuthenticodeSignature创建别名gas
New-Alias -Name gas -Value Get-AuthenticodeSignature
# 将现有别名重新分配给其他 cmdlet
Set-Alias -Name gas -Value Get-Location
Get-Alias -Name gas

# 只读别名：防止误删除/修改
Set-Alias -Name loc -Value Get-Location -Option ReadOnly

# 创建可执行文件的别名
Set-Alias -Name np -Value C:\Windows\notepad.exe
</code></pre>
<p>若要更改或删除只读别名，请使用 <code>Force</code> 参数。</p>
<h3>为有参命令创建 alias</h3>
<p><strong>不能</strong>为带有参数和值的命令（例如 <code>Set-Location -Path C:\Windows\System32</code>）创建别名。若要为某个命令创建别名，请创建一个包括该命令的函数，然后<strong>为此函数创建别名</strong>。</p>
<pre><code>Function CD32 {Set-Location -Path C:\Windows\System32}
Set-Alias -Name Go -Value CD32
</code></pre>
<h2>重定向输出</h2>
<p><code>Out-File</code> 将 powershell 的输出写入到文件。</p>
<pre><code># 直接写入
Get-Process | Out-File -FilePath .\Process.txt

# 防止覆盖现有文件
Get-Process | Out-File -FilePath .\Process.txt -NoClobber
</code></pre>
<p>以 ASCII 格式将输出发送到文件：</p>
<pre><code>$Procs = Get-Process
Out-File -FilePath .\Process.txt -InputObject $Procs -Encoding ASCII -Width 50
</code></pre>
<ul>
<li><code>Encoding</code> 参数将输出转换为 ASCII 格式</li>
<li><code>-Width</code> 参数将文件中的每一行限制为 50 个字符，因此某些数据可能会被截断。</li>
</ul>
<h2>关于异常</h2>
<p><strong>终止和非终止错误</strong>：异常通常是终止错误。引发的异常要么被捕获要么会终止当前执行程序。</p>
<blockquote>
<p>默认情况下，<code>Write-Error</code> 会生成一个非终止错误，并将错误添加到输出流，而不引发异常。也就是说，<code>Write-Error</code> 和其他非终止错误不会触发 <code>catch</code>！</p>
</blockquote>
<h3>创建异常</h3>
<p>若要创建自己的异常事件，可以使用 <code>throw</code> 关键字引发异常。</p>
<pre><code>function Start-Something
{
    throw "Bad thing happened"
}
</code></pre>
<p>前面提到过，默认情况下 <code>Write-Error</code> 不会引发终止错误。但如果指定 <code>-ErrorAction Stop</code>，<code>Write-Error</code> 将会生成一个可使用 <code>catch</code> 处理的终止错误。</p>
<pre><code>Write-Error -Message "Houston, we have a problem." -ErrorAction Stop
</code></pre>
<p><strong>创建类型异常</strong></p>
<pre><code>throw [System.IO.FileNotFoundException] "Could not find: $path"
</code></pre>
<h3>try/catch/finally</h3>
<p>PowerShell（以及许多其他语言）中的异常处理方式是，先对一部分代码执行 <code>try</code>，如果引发<strong>终止错误</strong>，则对其执行 <code>catch</code>。可使用 <code>$_</code> 变量访问 <code>catch</code> 块中的异常信息。</p>
<pre><code>try
{
    Start-Something -ErrorAction Stop
}
catch
{
    Write-Output "Something threw an exception or used Write-Error"
    Write-Output $_
}
</code></pre>
<p>有时不需要处理错误，但无论异常是否发生，仍需要执行一些代码，则使用 <code>finally</code>。</p>
<h3>处理异常</h3>
<p><strong>捕获类型化异常</strong></p>
<pre><code>try
{
    Start-Something -Path $path
}
catch [System.IO.DirectoryNotFoundException],[System.IO.FileNotFoundException]
{
    Write-Output "Could not find $path"
}
catch [System.IO.IOException]
{
        Write-Output "IO error with the file: $path"
}
</code></pre>
<h2>脚本模块</h2>
<p>将可复用的函数单独封装进 <code>psm1</code> 模块文件，然后引用</p>
<pre><code># common.psm1
function do-something {
...
}
</code></pre>
<p>然后在其他 <code>ps1</code> 文件中引入该模块</p>
<pre><code># script.ps1
Import-Module "/path/to/common.psm1" -DisableNameChecking
</code></pre>
<p>由于自定义函数/模块的名称未必严格符合 powershell 的 <code>verb</code> 格式，有时会报 warning，通常加上 <code>-DisableNameChecking</code> 忽略即可。</p>
<h2>输出流</h2>
<p>在 PowerShell 中，你可以使用 <code>Write-Host</code> 命令输出内容，并且可以通过修改输出的字体颜色和背景色来增加可读性。你可以使用 <code>-ForegroundColor</code> 和 <code>-BackgroundColor</code> 参数来设置文本的前景色（字体颜色）和背景色。此外，你也可以使用 <code>\n</code> 来添加换行符。</p>
<pre><code># 输出红色文本
Write-Host "This is red text" -ForegroundColor Red

# 输出黄色文本，蓝色背景
Write-Host "This is yellow text with blue background" -ForegroundColor Yellow -BackgroundColor Blue

# 输出包含换行符的文本
Write-Host "This is the first line`nThis is the second line"

# 使用变量输出带颜色的文本
$variable = "Important message"
Write-Host "The message is: $variable" -ForegroundColor Green
</code></pre>
<h2>函数</h2>
<p><strong>函数参数</strong> 使用 <code>param()</code> 声明，可以在声明时指定参数类型。</p>
<pre><code>function Validate-Path {
    # 验证输入路径、输出路径和视频文件格式
    param(
        [string]$i,
        [string]$o,
        [string]$f
    )
    ......
}
</code></pre>
<blockquote>
<p><code>$input</code> 是一个关键字，不可以作为参数使用。</p>
</blockquote>
<p><strong>函数返回值</strong>有多种形式：</p>
<ol>
<li>
<p>返回包含多个值的数组</p>
<pre><code>return @($i, $o, $f)
</code></pre>
<p>返回值的获取</p>
<pre><code>$params = func
Write-Host $params[0]  # 使用索引获取
</code></pre>
</li>
<li>
<p>返回 <code>key-value</code> 形式的哈希表</p>
<pre><code>return @{
    key1 = $i
    key2 = $o
    key3 = $f
}
</code></pre>
<p>返回值的获取</p>
<pre><code>$params = func
Write-Host $params.key1 # .key获取
</code></pre>
</li>
</ol>
<h2>执行默认操作</h2>
<p><code>Invoke-Item</code> 用于对指定项执行默认操作。例如在<strong>与某一文档文件类型关联的应用程序</strong>中打开该文档文件。</p>
<p><strong>打开文件</strong></p>
<pre><code>Invoke-Item "C:\Test\a.doc"

# 打开特定类型的所有文件
Invoke-Item "C:\Users\User1\Documents\*.xls"
</code></pre>
<p><strong>在文件资源管理器打开当前路径</strong></p>
<pre><code>Invoke-Item .
</code></pre>
<h2>路径拼接</h2>
<p>https://learn.microsoft.com/zh-cn/powershell/module/microsoft.powershell.management/join-path?view=powershell-7.4</p>
<h2>网络</h2>
<h3>DNS</h3>
<p>可以使用 <code>Resolve-DnsName</code> cmdlet 来测试一个 URL 的 DNS 查询记录。</p>
<pre><code>Resolve-DnsName example.com [-Type MX] [-Server 8.8.8.8]
</code></pre>
<ul>
<li>使用 <code>-Type</code> 参数指定要查询的 DNS 类型</li>
<li>使用 <code>-Server</code> 参数指定要使用的 DNS 服务器</li>
</ul>
<pre><code>Get-DnsClientServerAddress
</code></pre>
<p>这将显示操作系统当前配置的 DNS 服务器地址。通常情况下，你会看到一个或多个 DNS 服务器的 IP 地址。</p>
<h3>路由表 Route</h3>
<h4>Get-NetRoute</h4>
<p>Windows 路由表控制有一个内置命令 <code>route</code>，大概率是从 cmd 继承过来的。</p>
<pre><code># 打印路由表
route print

# 添加记录(临时)
route add $DestinationIP mask $SubMask $Gateway
# 删除记录
## 如果目标唯一的话，应该是不需要添加额外参数
route delete $DestinationIP
</code></pre>
<p><code>route print</code> 打印出的路由表和 powershell <code>Get-NetRoute</code> cmdlet 打印的结果稍有不同。如下：</p>
<pre><code>route print

IPv4 路由表
===========================================================================
活动路由:
网络目标        网络掩码          网关       接口   跃点数
          0.0.0.0          0.0.0.0   172.26.255.254   172.26.214.232    100
        127.0.0.0        255.0.0.0            在链路上         127.0.0.1    331

# powershell
Get-NetRoute


ifIndex DestinationPrefix                              NextHop                                  RouteMetric ifMetric PolicyStore
------- -----------------                              -------                                  ----------- -------- -----------
19      255.255.255.255/32                             0.0.0.0                                          256 35       ActiveStore
14      255.255.255.255/32                             0.0.0.0                                          256 25       ActiveStore
</code></pre>
<p>可以看见，主要区别在于 powershell 默认使用了 IP-CIDR 格式，因此相比于 route 命令缺少了子网掩码字段。</p>
<h4>New-NetRoute</h4>
<pre><code># 向路由表添加 IP
New-NetRoute -DestinationPrefix "10.0.0.0/24" -InterfaceIndex 12 -NextHop 192.168.0.1
</code></pre>
<blockquote>
<p>注意，不同于 <code>route add</code>，<code>Net-NetRoute</code> 的子网掩码用 IP-CIDR 格式表示。</p>
</blockquote>
<table>
<thead>
<tr>
<th>参数</th>
<th>作用</th>
<th>默认</th>
</tr>
</thead>
<tbody>
<tr>
<td>AddressFamily</td>
<td><code>IPv4/IPv6</code></td>
<td>自适应其余参数</td>
</tr>
<tr>
<td>Confirm</td>
<td>执行前确认</td>
<td>False</td>
</tr>
<tr>
<td>InterfaceAlias</td>
<td>网卡名称</td>
<td>None</td>
</tr>
<tr>
<td>PolicyStore</td>
<td><code>ActiveStore/PersistentStore</code></td>
<td>两个都有</td>
</tr>
</tbody>
</table>
<p>注意，不声明 PolicyStore 的情况下，会插入<strong>永久路由</strong>！</p>
<h2>硬件相关</h2>
<h3>磁盘</h3>
<pre><code># 常用于查看 MediaType (HDD 还是 SDD)
Get-PhysicalDisk

# 常用语查看盘符和磁盘名称
Get-Volume
</code></pre>
<h2>管理服务 Service</h2>
<h3>获取服务</h3>
<pre><code>Get-Service

Get-Service -Name se*

Get-Service -DisplayName ServiceLayer, Server
</code></pre>
<ul>
<li>不带参数将返回所有服务（<code>Status / Name / DisplayName</code>）</li>
<li>可以添加参数（如 <code>-Name</code>）进行筛选，支持通配符过滤或提供显示名称的列表</li>
</ul>
<blockquote>
<p>服务的 <code>Name</code> 和 <code>DisplayName</code> 并不总是一致！</p>
</blockquote>
<p>有时服务的字段过长，导致终端中的输出被截取，可以通过 <code>Format-Table</code> 解决：</p>
<pre><code>Get-Service -name razer* | Format-Table -AutoSize
</code></pre>
<h3>获取必需和从属服务</h3>
<pre><code># 获取 LanmanWorkstation 服务所依赖的服务。
Get-Service -Name LanmanWorkstation -RequiredServices

# 获取需要 LanmanWorkstation 服务的服务。
Get-Service -Name LanmanWorkstation -DependentServices
</code></pre>
<h3>停止、启动、暂停和重启服务</h3>
<pre><code># 停止服务
Stop-Service -Name spooler
# 启动服务
Start-Service -Name spooler
# 暂停服务
Suspend-Service -Name spooler
# 重启服务
Restart-Service -Name spooler
</code></pre>
<h3>设置服务属性</h3>
<p><code>Set-Service</code> cmdlet 更改服务的属性，也可以启动、停止或暂停服务。</p>
<p><strong>更改显示名称</strong></p>
<pre><code>Set-Service -Name LanmanWorkstation -DisplayName "LanMan Workstation"

</code></pre>
<p><strong>更改服务的启动类型</strong></p>
<pre><code>Set-Service -Name BITS -StartupType Automatic
</code></pre>
<ul>
<li>Automatic - 服务将由操作系统在系统启动时启动或在系统启动时已启动。 如果自动启动的服务依赖于手动启动的服务，则手动启动的服务也会在系统启动时自动启动。</li>
<li>AutomaticDelayedStart - 系统启动后不久启（延迟启动）。</li>
<li>Disabled - 服务被禁用，不能由用户或应用程序启动。</li>
<li>InvalidValue - 不起作用。 该 cmdlet 不返回错误，但服务的 StartupType 不会更改。</li>
<li>Manual - 服务只能由用户（使用服务控制管理器）或应用程序手动启动。</li>
</ul>
<p><strong>更改服务的说明</strong></p>
<pre><code># 查看 ipreport 服务当前 Description
Get-Service -Name ipreport | Select-Object Description
# 修改
Set-Service -Name ipreport -Description "ip report to whr"
# 查看修改结果
Get-Service -Name ipreport | Select-Object Description
</code></pre>
<p><strong>处理多个服务</strong></p>
<p><code>Set-Service</code> cmdlet 一次只接受一个服务名称。 但是，可以通过管道将多个服务传递给 <code>Set-Service</code>，以更改多个服务的配置。</p>
<pre><code>Get-Service SQLWriter,spooler |
    Set-Service -StartupType Automatic -PassThru |
    Select-Object Name, StartType
</code></pre>
<h3>删除服务</h3>
<pre><code># 移除名为 TestService 的服务
Remove-Service -Name "TestService"

# 移除使用显示名称的服务
Get-Service -DisplayName "Test Service" | Remove-Service
</code></pre>
<p>使用 <code>-confirm</code> 参数可以在运行 cmdlet 之前进行确认。</p>
<h2>案例</h2>
<h3>如何调用已有脚本实现代码复用？</h3>
<p>可以通过循环，在每次迭代中调用脚本。下面是一个示例代码，演示了如何在 PowerShell 中循环调用脚本来处理多个输入文件：</p>
<pre><code># 假设有一个包含多个输入文件的文件夹，路径为 $inputFolder
$inputFolder = "C:\Path\To\Input\Folder"

# 遍历输入文件夹中的所有文件
$inputFiles = Get-ChildItem -Path $inputFolder -File
foreach ($file in $inputFiles) {
    # 构建参数列表
    $params = @{
        inPath = $file.FullName
        outPath = "C:\Path\To\Output\Folder\" + $file.BaseName + "_converted.csv"
        inEncoding = "gb2312"  # 输入文件的编码格式
        outEncoding = "utf8"   # 想要转换成的编码格式
    }

    # 调用脚本并传入参数
    &amp; "C:\Path\To\Your\Script.ps1" @params
}
</code></pre>
]]></content>
    <author><name>Sun2ot</name></author>
    <category term="教程"/>
  </entry>
  <entry>
    <title>破解网页的粘贴限制(U校园 iWrite)</title>
    <link href="https://blog.085404.xyz/posts/%E7%A0%B4%E8%A7%A3%E7%BD%91%E9%A1%B5%E7%9A%84%E7%B2%98%E8%B4%B4%E9%99%90%E5%88%B6u%E6%A0%A1%E5%9B%AD-iwrite/" rel="alternate" type="text/html"/>
    <id>https://blog.085404.xyz/posts/%E7%A0%B4%E8%A7%A3%E7%BD%91%E9%A1%B5%E7%9A%84%E7%B2%98%E8%B4%B4%E9%99%90%E5%88%B6u%E6%A0%A1%E5%9B%AD-iwrite/</id>
    <published>2023-10-30T00:00:00.000Z</published>
    <updated>2023-10-30T00:00:00.000Z</updated>
    <summary></summary>
    <content type="html"><![CDATA[<h2>前情提要</h2>
<p>英格力士 Teacher 布置了两篇看图写作的作业，在 U 校园的 iWrite 平台上。都 3202 年了，这种东西当然是 GPT 直接 μ 杀。结果......</p>
<p><img src="https://img.085404.xyz/images/13c43a6233f7f5e1a809775a115ac7ce.webp" alt="image.png" /></p>
<p>当然，我敏锐的察觉到了地址栏那个大大的 <code>不安全http</code> 。心想这种臭鱼烂虾必有破解之法。苦寻油猴脚本无果的情况下，在另一个佬的博客里找到了答案</p>
<h2>解决方案</h2>
<p>先 <code>F12</code> 切开发者，然后定位到文本输入框</p>
<p><img src="https://img.085404.xyz/images/2c97c440d8e4c3865107f468bbbbc8dc.webp" alt="image.png" /></p>
<p>然后在右边的事件侦听器里，找到 paste 字段下的三个监听器，直接删了就行</p>
<p><img src="https://img.085404.xyz/images/4ec3a5bf95eab22eb06d65a8ab4be89b.webp" alt="image.png" /></p>
]]></content>
    <author><name>Sun2ot</name></author>
    <category term="教程"/>
  </entry>
  <entry>
    <title>宁夏气象局-初次对接-会议纪要</title>
    <link href="https://blog.085404.xyz/posts/%E5%AE%81%E5%A4%8F%E6%B0%94%E8%B1%A1%E5%B1%80-%E5%88%9D%E6%AC%A1%E5%AF%B9%E6%8E%A5-%E4%BC%9A%E8%AE%AE%E7%BA%AA%E8%A6%81/" rel="alternate" type="text/html"/>
    <id>https://blog.085404.xyz/posts/%E5%AE%81%E5%A4%8F%E6%B0%94%E8%B1%A1%E5%B1%80-%E5%88%9D%E6%AC%A1%E5%AF%B9%E6%8E%A5-%E4%BC%9A%E8%AE%AE%E7%BA%AA%E8%A6%81/</id>
    <published>2023-10-13T00:00:00.000Z</published>
    <updated>2023-10-13T00:00:00.000Z</updated>
    <summary></summary>
    <content type="html"><![CDATA[<h2>对方需求</h2>
<h3>葡萄种植</h3>
<h4>放条、埋土预测</h4>
<p>因葡萄品种、土壤类型、含水量等条件不同，因此其放条、埋土的要求十分多变且难以预测。尤其是测量数据时，每次测量的微小误差累积起来后，会对预测结果产生较大影响。</p>
<h4>全局信息化管理</h4>
<p>咣咣砸钱，做过很多系统，都寄了。因为只采用了单一指标，没有从全局进行考虑，导致模型失败。以控水为例，<code>防水8h或12h</code>这种指标过于宽泛。实际情况下，不同季节需水完全不同。因此，信息化管理<strong>应当结合天气</strong>预测，利用气象驱动。</p>
<p>要在空间体系上，实现经济、生态、水丰效益的最优化。（<s>做梦呢</s>）</p>
<h3>智慧果园</h3>
<p>死系统，信息分散，没有整体化、全局化系统。应当结合土壤、天气信息，利用气象动因驱动系统。</p>
<h3>气象</h3>
<h4>图像处理</h4>
<p>分辨率不够(业内术语称为<strong>降尺度、差值</strong>？)。</p>
<p>数据质量控制：</p>
<ol>
<li>对不对</li>
<li>补全</li>
</ol>
<h4>数据采集</h4>
<p>高光谱、植物长势、物联网、灾情数据</p>
<h4>数据处理</h4>
<p>very important! 从历史数据中，找出线性、非线性的联系，进行预报预测，得出以下几个方面的结论：</p>
<ul>
<li>when: 啥时候种</li>
<li>which: 种啥</li>
<li>manage: 全链条智慧管理
<blockquote>
<p>我就纳闷了，整个蓝不拉几的科技风 UI 界面就智慧了？那还不如买华为呢，华为给我增智慧！</p>
</blockquote>
</li>
</ul>
<h4>图像识别</h4>
<ul>
<li>农民伯伯/姨姨拍张照，识别出来什么虫？用什么药？在哪里买？</li>
<li>无人机拍照，分析作物长得咋样，有没有什么病虫害</li>
</ul>
<h4>其他</h4>
<ul>
<li>粮食安全</li>
<li>自然灾害预测</li>
<li>干旱管理</li>
<li>太阳能</li>
</ul>
<h3>产量预报</h3>
<p>对方目前采用的仍是统计、经验、大数据模型等手段。但对于无法量化的数据，没有好的解决方案，如：</p>
<ol>
<li>国家、自治区的政策、文件</li>
<li>灾情（主要是气象）</li>
<li>人工调查信息</li>
<li>多部门会商的结果</li>
<li>技术发展（例如加大某方面投入）</li>
<li>实时数据（农业部、气象站）</li>
<li>指标数据</li>
<li>......</li>
</ol>
<h3>农事活动监测</h3>
<p>摄像头咔咔一顿拍摄，只上传四张图，然后人工挨个搁那看，这帮狗日的今天到底修没修剪枝叶、有没有打药、滴灌有没有做。接着需要观察成效如何，并根据结果修正指标。</p>
<h3>OA 系统</h3>
<p>目前能够实现业务信息化、报表自动生成。但据对方所说似乎需要什么<code>智能判断</code>？<s>WTF？</s></p>
<h2>我方提供</h2>
<p>你看这个面它又长又宽，你看这个饼它又大又圆</p>
<hr />
<h2>高光谱图像在农业气象领域的相关内容：</h2>
<p>1.1 现有高光谱图像在当前农业气象领域存在的问题：
（1）所采集到的数据分辨率不足；数据质量存在问题，例如：数据残缺，数据是否正确等。
（2）需要对残缺的数据进行补全。</p>
<p>1.2 常见的解决方法：
（1）更换采集设备，升级为具有更高分辨率的摄像头或传感器。
（2）使用多光谱融合技术，将多个低分辨率的图像合并为一个具有更高分辨率的图像。
（3）利用更先进的图像处理算法，采用例如超分辨率深度学习模型，可以从低分辨率图像中提取更多的信息以改善分辨率。</p>
<p>2.高光谱图像的采集：
（1）植物健康监测：通过分析图像，可以监测植物疾病，病虫害，水分情况等。
（2）气象监测：通过捕捉大气中不同波长的光线，可获取湿度，温度等信息，有助于农业生产。
（3）对火灾等监测：高光谱图像可以捕获火灾中产生的烟雾、火焰等，可以及时发现火灾。
（4）</p>
<p>3.高光谱图像处理：
想要处理好农业领域的问题，就要使用气象数据作为驱动。对获取的高光谱图像进行预处理，提取当中与气象和土壤等有关的特征，分析其中存在的关系，并通过历史数据等建立预测模型，对气候等进行预测，并将其应用到农业种植领域。</p>
]]></content>
    <author><name>Sun2ot</name></author>
    <category term="日常"/>
  </entry>
  <entry>
    <title>Linux conda 部署教程</title>
    <link href="https://blog.085404.xyz/posts/linux-conda-%E9%83%A8%E7%BD%B2%E6%95%99%E7%A8%8B/" rel="alternate" type="text/html"/>
    <id>https://blog.085404.xyz/posts/linux-conda-%E9%83%A8%E7%BD%B2%E6%95%99%E7%A8%8B/</id>
    <published>2023-09-14T00:00:00.000Z</published>
    <updated>2023-09-14T00:00:00.000Z</updated>
    <summary></summary>
    <content type="html"><![CDATA[<h2>部署 conda</h2>
<p>登录用户，然后逐行输入下列指令</p>
<pre><code># 建立 miniconda3 文件夹
mkdir -p ~/miniconda3
# 下载安装脚本
wget https://repo.anaconda.com/miniconda/Miniconda3-latest-Linux-x86_64.sh -O ~/miniconda3/miniconda.sh
# 执行脚本并安装 conda 到 ~/miniconda3 路径下
bash ~/miniconda3/miniconda.sh -b -u -p ~/miniconda3
# 删除脚本(可删可不删，无所谓)
rm -rf ~/miniconda3/miniconda.sh
# 初始化 bash
~/miniconda3/bin/conda init bash
# 生效环境变量
source ~/.bashrc
</code></pre>
<h2>自动生效环境变量</h2>
<blockquote>
<p>完成上述操作后，输入 <code>exit</code> 关闭 ssh 连接，然后重新登录。如果发现刚刚的配置又失效了，例如 <code>conda</code> 指令又识别不出来了，再执行下列操作，否则直接跳过。</p>
</blockquote>
<p>环境失效是因为 <code>~/.bashrc</code> 中的内容没有随着登录自动执行</p>
<p>所以我们需要做出如下修改，使刚刚的配置随着我们登录自动生效</p>
<pre><code># 进入用户路径
cd ~
# 创建 .bash_profile 文件
touch .bash_profile
# 修改该文件
vim .bash_profile
</code></pre>
<p>插入以下内容</p>
<blockquote>
<p>按 <code>i</code> 进入编辑模式， <code>ESC</code> 退出编辑模式， <code>:wq</code> 保存并退出</p>
</blockquote>
<pre><code># if running bash
if [ -n "$BASH_VERSION" ]; then
    # include .bashrc if it exists
    if [ -f "$HOME/.bashrc" ]; then
        . "$HOME/.bashrc"
    fi
fi
</code></pre>
<p>然后输入 <code>exit</code> 关闭连接，然后重新 ssh 登录，即可发现 <code>~/.bashrc</code> 的配置自动生效。</p>
]]></content>
    <author><name>Sun2ot</name></author>
    <category term="教程"/>
  </entry>
  <entry>
    <title>FFmpeg Docs</title>
    <link href="https://blog.085404.xyz/posts/ffmpeg-docs/" rel="alternate" type="text/html"/>
    <id>https://blog.085404.xyz/posts/ffmpeg-docs/</id>
    <published>2023-09-12T00:00:00.000Z</published>
    <updated>2023-09-12T00:00:00.000Z</updated>
    <summary></summary>
    <content type="html"><![CDATA[<h1>FFmpeg Docs</h1>
<h2>字幕格式转换</h2>
<pre><code>ffmpeg -i a.ass b.srt  
ffmpeg -i c.vtt d.srt  
ffmpeg -i e.lyric f.srt
</code></pre>
<h3>硬字幕</h3>
<p>字幕以图形方式硬编码到视频中，该过程耗时较长不可逆（无法再把字幕提取出来）</p>
<pre><code>ffmpeg -i file.mp4 -vf subtitles=/path/to/sub.srt file_out.mp4
</code></pre>
<p>如果想使用绝对路径进行文件引用，<strong>要加转义字符</strong>。例如 <code>e\sample\dance. srt</code> 是字幕路径，则指令如下：</p>
<pre><code>ffmpeg -i "e:\\sample\\dance.mp4" -vf subtitles="e:\\sample\\dance.srt" e:\sample\out.mp4
</code></pre>
<h3>软字幕</h3>
<p>软字幕分两种，一是将字幕单独作为文件存储；二是将字幕与视频一起封装到文件中，但兼容性较差，这里给出第二种的实现方案。</p>
<blockquote>
<p>兼容性其实还是取决于播放器，所以软字幕自己用用就可以，不建议作为工程文件或者发送给老师、领导等的文件。</p>
</blockquote>
<pre><code>ffmpeg -i $video.Name -i $srt -c copy -c:s srt out.mkv
ffmpeg -i input.mp4 -i input.srt -c copy -c:s mov_text output.mp4
</code></pre>
<p>注意：</p>
<ol>
<li>vtt 字幕无法设置外挂，可转换为 srt 后进行操作</li>
<li><code>-c:s srt</code> 仅适用于 mkv，因为 mkv 自带字幕轨道，如果使用 mp4 需使用 <code>mov_text</code></li>
</ol>
<h2>图片压缩</h2>
<pre><code>ffmpeg -i input.jpg -qscale:v 5 output.jpg
</code></pre>
<ul>
<li><code>:v</code> 表示视频流参数，取值为 1-31（越小质量越高）</li>
</ul>
<h2>调用 GPU</h2>
<p>一般情况下，会在重新编码视频时产生调用 GPU 进行加速的需求。该过程中，不同的计算机平台可用的编码器不同，可通过如下方式进行查找：</p>
<pre><code>ffmpeg -codecs
</code></pre>
<blockquote>
<p>会产生大量输出结果，并分别标记每个编码器的功能和特性。在 Linux 和 Windows 可分别使用 <code>grep/Select-String</code> 进行筛选。</p>
</blockquote>
<p>下面给出几个常用的编码器：</p>
<pre><code># AMD 核显
ffmpeg -i input.mp4 -c:v av1_amf/h264_amf/hevc_amf output.mp4

# NVIDIA 显卡
ffmpeg.exe -i input.mp4 -c:v av1_nvenc/h264_nvenc/hevc_nvenc -c:a aac output.mp4
</code></pre>
<h2>合并/分离音视频</h2>
<p><strong>合并</strong></p>
<pre><code>ffmpeg -i "input_video.mp4" -i "input_audio.m4a"  -c copy -map 0:v:0 -map 1:a:0  "output.mp4"
</code></pre>
<table>
<thead>
<tr>
<th>参数</th>
<th>含义</th>
</tr>
</thead>
<tbody>
<tr>
<td><code>0:v:0</code></td>
<td>第一个 0 表示第一个输入文件，v 表示 video，第二个 0 表示“第一个输入的视频文件的第一个视频轨道”</td>
</tr>
<tr>
<td><code>1:a:0</code></td>
<td>第二个输入的音频文件的第一个音频轨道</td>
</tr>
</tbody>
</table>
<hr />
<p><strong>分离</strong></p>
<pre><code>ffmpeg -i input.mp4 -c:v copy -an video.mp4
ffmpeg -i input.mp4 -c:a copy -vn audio.mp3
</code></pre>
<p>上述命令中的 <code>input.mp4</code> 是你要处理的视频文件名。</p>
<p>第一个命令 <code>ffmpeg -i input.mp4 -c:v copy -an video.mp4</code> 将从 <code>input.mp4</code> 中提取视频流，并将其保存为 <code>video.mp4</code> 文件。 <code>-c:v copy</code> 表示对视频流进行复制，保持原始编码方式不变， <code>-an</code> 表示忽略音频流。</p>
<p>第二个命令 <code>ffmpeg -i input.mp4 -c:a copy -vn audio.mp3</code> 将从 <code>input.mp4</code> 中提取音频流，并将其保存为 <code>audio.mp3</code> 文件。<code>-c:a copy</code> 表示对音频流进行复制，保持原始编码方式不变，<code>-vn</code> 表示忽略视频流。</p>
<p>请注意，上述命令中的输出文件格式（<code>video.mp4</code> 和 <code>audio.mp3</code>）可以根据你的需求进行修改。另外，如果你想对视频流或音频流进行重新编码或其他处理，可以调整相应的参数。</p>
<h2>重新压制视频</h2>
<pre><code>ffmpeg -i "input.mp4" -c:v hevc_amf -c:a aac -b:v 6M -b:a 320k -ar 48000  "output.mp4"
</code></pre>
<table>
<thead>
<tr>
<th>参数</th>
<th>含义</th>
</tr>
</thead>
<tbody>
<tr>
<td><code>-c:v</code></td>
<td>视频编码器， <code>code:video</code></td>
</tr>
<tr>
<td><code>-c:a</code></td>
<td>音频编码器， <code>code:audio</code></td>
</tr>
<tr>
<td><code>-b:v</code></td>
<td>视频码率， <code>bit:video</code></td>
</tr>
<tr>
<td><code>-b:a</code></td>
<td>音频码率， <code>bit:audio</code></td>
</tr>
<tr>
<td><code>-ar</code></td>
<td>音频采样率，48000=48.000 KHz</td>
</tr>
</tbody>
</table>
<h2>拼接视频</h2>
<pre><code>ffmpeg -f concat -i input.txt -c copy output.mp4
</code></pre>
<p>在 <code>input.txt</code> 中写入要拼接的文件名，格式如下</p>
<pre><code>file 'a.mp4'
file 'b.mp4'
...
</code></pre>
<blockquote>
<p>[!warning]</p>
<ol>
<li>视频名称用单引号</li>
<li>不要用中文名称</li>
<li>名称不能有空格</li>
</ol>
</blockquote>
<h2>裁剪</h2>
<p>从第 3 分钟开始裁剪到结束</p>
<pre><code>ffmpeg -i "输入文件.m4a" -ss 00:03:00 -c copy "输出文件.m4a"
</code></pre>
<blockquote>
<p>[!warning]
请确保在执行命令之前备份原始文件，以免发生意外情况。如果你想要从指定的时间点裁剪到文件结束，可以省略 <code>-t</code> 参数，因为默认会一直裁剪到末尾。</p>
</blockquote>
<p>裁剪从第 3 分钟到第 5 分钟的部分</p>
<pre><code># 方式1：开始时间 + 持续时间
ffmpeg -i "输入文件.m4a" -ss 00:03:00 -t 00:02:00 -c copy "输出文件.m4a"

# 方式2：开始时间 + 结束时间
ffmpeg -i "输入文件.m4a" -ss 00:03:00 -to 00:05:00 -c copy "输出文件.m4a"
</code></pre>
<h2>章节</h2>
<p>面对长视频/信息量大的视频时，可以通过添加章节（chapter）的形式，对内容进行划分，标注出关键时间节点。</p>
<pre><code>ffmpeg -i input.mp4 -i chapters.txt -map_metadata 1 -c copy output.mkv
</code></pre>
<p>其中，<code>chapters.txt</code> 格式如下：</p>
<pre><code>;FFMETADATA1
[CHAPTER]
START=830266666666.67
END=1470383333333.3
title=实操环节
[CHAPTER]
START=1470383333333.3
END=2870033333333.3
title=coding
[CHAPTER]
START=2870033333333.3
END=3351683333333.3
title=homework
</code></pre>
<p>注意：</p>
<ol>
<li><code>;FFMETADATA1</code> 不可缺少</li>
<li><code>chapters.txt</code> 只要是文本文件即可，不一定必须为 <code>txt</code> 后缀</li>
<li>每个 chapter 对应的 <code>[CHAPTER]</code> 不可缺少且必须为<strong>大写</strong></li>
<li>如果输出 <code>mkv</code>，则章节序列一一对应；如果输出 <code>mp4</code>，则所有章节左移一章，即第一个章节必须从 <code>00:00:00</code> 开始</li>
</ol>
<blockquote>
<p>更多 metadata 说明见 https://ffmpeg.org/ffmpeg-formats.html#Metadata-1</p>
</blockquote>
]]></content>
    <author><name>Sun2ot</name></author>
    <category term="教程"/>
  </entry>
  <entry>
    <title>ssh 连接工具使用教程</title>
    <link href="https://blog.085404.xyz/posts/ssh-%E8%BF%9E%E6%8E%A5%E5%B7%A5%E5%85%B7/" rel="alternate" type="text/html"/>
    <id>https://blog.085404.xyz/posts/ssh-%E8%BF%9E%E6%8E%A5%E5%B7%A5%E5%85%B7/</id>
    <published>2023-09-12T00:00:00.000Z</published>
    <updated>2023-09-12T00:00:00.000Z</updated>
    <summary></summary>
    <content type="html"><![CDATA[<h2>下载</h2>
<blockquote>
<p>以 xshell 为例</p>
</blockquote>
<p>https://www.netsarang.com/en/free-for-home-school/</p>
<p><img src="https://img.085404.xyz/images/e73b28bdcf9da94499e59afd1a1c91a0.webp" alt="4e9846d1a7cded8d6be41beb1337d2b9.png" /></p>
<p>左边的是连接工具，右边的是 SFTP 工具</p>
<h2>建立连接</h2>
<p>点击 <code>新建</code></p>
<p><img src="https://img.085404.xyz/images/3c1bd34066a88483c14757b5cbad6b09.webp" alt="image.png" /></p>
<p><img src="https://img.085404.xyz/images/ab18292fd9c702a175ab2572d664347a.webp" alt="image.png" /></p>
<p><img src="https://img.085404.xyz/images/8e753d984fe55c4e880cd3b7216bea33.webp" alt="image.png" /></p>
<p>输入用户名、密码，登录方式选择 <code>password</code> 方式</p>
<p>配置完成后，点击<code>连接</code></p>
<blockquote>
<p>弹出的窗口选择 <code>接受并保存</code> 即可</p>
</blockquote>
<p>shell 中显示出 <code>user_name@server</code> 即成功。</p>
<p><img src="https://img.085404.xyz/images/789bb94f03c7905dc977de081f248f44.webp" alt="image.png" /></p>
]]></content>
    <author><name>Sun2ot</name></author>
    <category term="教程"/>
  </entry>
  <entry>
    <title>漆黑的魅影攻略</title>
    <link href="https://blog.085404.xyz/posts/%E6%BC%86%E9%BB%91%E7%9A%84%E9%AD%85%E5%BD%B1%E6%94%BB%E7%95%A5/" rel="alternate" type="text/html"/>
    <id>https://blog.085404.xyz/posts/%E6%BC%86%E9%BB%91%E7%9A%84%E9%AD%85%E5%BD%B1%E6%94%BB%E7%95%A5/</id>
    <published>2023-08-31T00:00:00.000Z</published>
    <updated>2023-08-31T00:00:00.000Z</updated>
    <summary></summary>
    <content type="html"><![CDATA[<h2>神兽</h2>
<h3>水君</h3>
<p>冥想+冲浪+冷冻光线+空气切割</p>
<p>性格：大胆/保守 特性：贮水</p>
<p>努力HP物防，可以考虑分点给特攻。通关比较推荐的类型，冥想三攻，水冰飞的盲点本作就电灯鱼和钢企鹅，打击面很不错，想节约点药钱的可以把一攻换成睡觉。</p>
<h3>雷公</h3>
<p>紫瑾市精灵中心门口的老头，接任务，拿到<strong>地道钥匙</strong>
从精灵中心往左下走，到钓鱼老哥那，下水，然后往右到洞口小岛</p>
<p>性格推荐：保守
技能：伏特替换、反射壁、十万伏特和神通力</p>
<h3>闪电鸟</h3>
<p>卡那兹市上方，流星瀑布最里面</p>
<p>快龙：龙舞+龙之爪/逆鳞+火焰拳+地震，
性格：固执
特性：精神力
努力：物攻速度</p>
<h2>技能</h2>
<p>学习技能：</p>
<ul>
<li>冬雪市，精灵中心往左第二间屋子，<strong>技能指导员</strong></li>
<li>每次学习需要一个<strong>心之鳞片</strong></li>
</ul>
<p>遗忘技能：</p>
<ul>
<li>水静市，超级市场右边的<strong>遗忘老人</strong></li>
<li>冲浪术必须在背包里另一个精灵也学了的情况下，才能遗忘</li>
</ul>
<h2>金手指</h2>
<ul>
<li>cb： <code>code breaker</code> ，也被称为代码断路器、CB码。格式为：8位十六进制数 4位十六进制数。具体来说，CB码的一行是由<strong>单个空格</strong>分隔的一个<strong>8位十六进制数</strong>和一个<strong>4位十六进制数</strong>，这一行除了两个数字、单个空格和结尾那个看不见的回车符，<strong>没有任何其他符号</strong>。</li>
<li>gs： <code>gameshark</code></li>
</ul>
<table>
<thead>
<tr>
<th>类型</th>
<th>作用</th>
<th>代码</th>
<th>备注</th>
</tr>
</thead>
<tbody>
<tr>
<td>cb</td>
<td>商店购买物品时，替换为制定物品</td>
<td>83005E12 xxxx</td>
<td>xxxx 为物品代码，似乎仅在古辰镇生效</td>
</tr>
<tr>
<td>cb</td>
<td>不会遇到野生精灵</td>
<td>820375D4 0000</td>
<td>等价于喷雾</td>
</tr>
<tr>
<td>gs</td>
<td>快速升级</td>
<td>020241F0:2710</td>
<td>对战前开启，升到需要等级后即可关闭</td>
</tr>
<tr>
<td>gs</td>
<td>开启遇宠（将随机碰到的野生宠物固定为某一个）</td>
<td>0146DCEA 3E32A31D</td>
<td></td>
</tr>
<tr>
<td>cb</td>
<td>指定遇到的宠物</td>
<td>83007E28 xxxx</td>
<td>xxxx 为宠物代码，该指令在 <code>开启遇宠</code> 打开后方可生效</td>
</tr>
</tbody>
</table>
<blockquote>
<p>[!warning]
在 PC 端的 visualboyadvance 模拟器中，本人使用的 <code>visualboyadvance-m v2.1.5</code> 在输入 gameshark 码时，会自动去除中间的空格，且这样的 gs 码无法正常生效。因此输入时，<strong>视情况</strong>先手动去除中间空格再输入金手指</p>
</blockquote>
<h3>物品代码&amp;宠物代码</h3>
<p>https://gist.github.com/sun2ot/468c138c206e770bd8ecd81a83e1b658</p>
<h2>努力值</h2>
<p>凯那市买药，9800/瓶，一瓶加 10 点努力值
努力值总上限 510 点，单项上限 255，<strong>吃药只能加到 100</strong></p>
<ol>
<li>水静市超级市场，可以买编号 21-26 号树果，吃了会下降 10 点相应的努力值，刷错努力值的时候可以拿这个来清空。</li>
<li>紫堇市（电系道馆）的精灵中心门口正下方的男的可以看队首精灵的<strong>个体值</strong>，左边旁边电玩城旁边的戴眼镜的男的可以看队首精灵的<strong>努力值</strong>。</li>
<li>打什么精灵加什么努力的话，最简单的就是“反转世界”轮回镇那里，其他的一周目的精灵的对应努力值。不过建议前期不要在乎这个，等进轮回镇了再刷努力也来得及。</li>
</ol>
]]></content>
    <author><name>Sun2ot</name></author>
    <category term="教程"/>
  </entry>
  <entry>
    <title>conda docs</title>
    <link href="https://blog.085404.xyz/posts/conda-docs/" rel="alternate" type="text/html"/>
    <id>https://blog.085404.xyz/posts/conda-docs/</id>
    <published>2023-08-29T00:00:00.000Z</published>
    <updated>2023-08-29T00:00:00.000Z</updated>
    <summary></summary>
    <content type="html"><![CDATA[<h1>conda docs</h1>
<h2>部署 conda</h2>
<p><a href="https://blog.085404.xyz/method/linux-conda-deploy.html">Linux conda 部署教程</a></p>
<h2>初始化 Windows 命令行的 conda 环境</h2>
<blockquote>
<p>推荐直接使用 <code>Anaconda Powershell Prompt</code> ，无需下列操作</p>
</blockquote>
<p>如果想在 <code>powershell/bash/...</code> 等 shell 中使用 <code>conda</code> ，需要进行初始化</p>
<pre><code>conda init [your shell]
</code></pre>
<blockquote>
<p>[!tip]
在 Windows 平台中，执行上述操作后，会导致 powershell 启动速度缓慢。可通过 <code>conda init --reverse</code> 撤销上述操作。</p>
</blockquote>
<hr />
<h3>两种更优解</h3>
<p><code>2024-01-02</code> 找到两种激活 powershell 下 conda 环境的更优解。</p>
<h4>默认启动 conda 环境</h4>
<p>先找到 Anaconda 安装后自带的 Anaconda Powershell Prompt，右键属性，复制出其中的命令如下</p>
<pre><code>%windir%\System32\WindowsPowerShell\v1.0\powershell.exe -ExecutionPolicy ByPass -NoExit -Command "&amp; 'F:\env\anaconda3\shell\condabin\conda-hook.ps1' ; conda activate 'F:\env\anaconda3' "
</code></pre>
<p>这段指令干了三件事：</p>
<ol>
<li>调用系统的 Windows Powershell 修改了脚本执行权限</li>
<li>执行 conda 的 hook 脚本</li>
<li>激活 base 环境</li>
</ol>
<p>显然，这个过程可以得到精简：</p>
<ol>
<li>脚本执行权限完全可以持久化，无需每次执行</li>
<li>使用更优的 Powershell 取代 Windows Powershell</li>
<li>无需默认激活 base 环境</li>
</ol>
<p>基于此，可以得到新的指令如下：</p>
<pre><code>"C:\Program Files\PowerShell\7\pwsh.exe" -NoExit -Command "&amp; 'F:\env\anaconda3\shell\condabin\conda-hook.ps1'"
</code></pre>
<p>将其添加到 Windows Terminal 即可。</p>
<h4>按需手动激活 conda 环境</h4>
<p>根据上文可吃 conda 的 hook 是通过 <code>F:\env\anaconda3\shell\condabin\conda-hook.ps1</code> 完成的，因此给该脚本指定一个 alias，需要时调用即可。打开 powershell 的 <code>$profile</code>，添加如下内容即可。</p>
<pre><code>New-Alias -Name chook -Value "F:\env\anaconda3\shell\condabin\conda-hook.ps1"
</code></pre>
<h2>生成 conda 默认配置文件</h2>
<pre><code>conda config --write-default
</code></pre>
<p>这将在你的用户根目录下生成一个名为 <code>.condarc</code> 的文件，其中包含 conda 的默认配置选项。</p>
<h2>修改 conda 配置路径</h2>
<p>先看看当前的配置路径：</p>
<pre><code>conda config --show
</code></pre>
<p>复制其中的 <code>envs_dirs</code> 和 <code>pkgs_dirs</code> 两段配置，然后编辑 <code>~/.condarc</code> 文件：</p>
<pre><code>envs_dirs:
  # 这里是新增的路径
  - D:\pkgs\anaconda3\envs
  # 下面是原来的路径，粘贴过来
  - F:\env\anaconda3\envs
  - C:\Users\y2pub\.conda\envs
  - C:\Users\y2pub\AppData\Local\conda\conda\envs
pkgs_dirs:
  # 这里是新增的路径
  - D:\pkgs\anaconda3\pkgs
 # 下面是原来的路径，粘贴过来
  - F:\env\anaconda3\pkgs
  - C:\Users\y2pub\.conda\pkgs
  - C:\Users\y2pub\AppData\Local\conda\conda\pkgs
</code></pre>
<p>重启 shell，执行 <code>conda config --show</code> 查看修改结果。</p>
<h2>修改镜像</h2>
<h3>配置清华源</h3>
<p>先执行</p>
<pre><code>conda config --set show_channel_urls yes
</code></pre>
<p>然后编辑用户路径下的 <code>.condarc</code> ，复制以下内容并覆盖</p>
<pre><code>channels:
  - defaults
show_channel_urls: true
default_channels:
  - https://mirrors.tuna.tsinghua.edu.cn/anaconda/pkgs/main
  - https://mirrors.tuna.tsinghua.edu.cn/anaconda/pkgs/r
  - https://mirrors.tuna.tsinghua.edu.cn/anaconda/pkgs/msys2
custom_channels:
  conda-forge: https://mirrors.tuna.tsinghua.edu.cn/anaconda/cloud
  msys2: https://mirrors.tuna.tsinghua.edu.cn/anaconda/cloud
  bioconda: https://mirrors.tuna.tsinghua.edu.cn/anaconda/cloud
  menpo: https://mirrors.tuna.tsinghua.edu.cn/anaconda/cloud
  pytorch: https://mirrors.tuna.tsinghua.edu.cn/anaconda/cloud
  pytorch-lts: https://mirrors.tuna.tsinghua.edu.cn/anaconda/cloud
  simpleitk: https://mirrors.tuna.tsinghua.edu.cn/anaconda/cloud
  deepmodeling: https://mirrors.tuna.tsinghua.edu.cn/anaconda/cloud/
</code></pre>
<p>即可添加 Anaconda Python 免费仓库。</p>
<p>运行 <code>conda clean -i</code> 清除索引缓存，保证用的是镜像站提供的索引。</p>
<h3>查找配置文件的位置</h3>
<pre><code>conda config --show-sources
</code></pre>
<h3>恢复默认源</h3>
<p>要恢复 Conda 到默认下载源，你可以简单地移除或重置之前设置的频道配置。以下是两种方法：</p>
<ol>
<li>
<p><strong>移除渠道配置</strong>：</p>
<p>你可以通过删除之前设置的频道配置来恢复默认下载源。使用以下命令：</p>
<pre><code>conda config --remove-key channels
</code></pre>
<p>这将删除所有之前设置的频道配置，使 Conda 回到默认状态，使用默认的 Conda 频道源。</p>
</li>
<li>
<p><strong>重置渠道配置</strong>：</p>
<p>另一种方法是重置频道配置为默认值。你可以使用以下命令：</p>
<pre><code>conda config --remove-key channel_alias
conda config --remove-key channel_priority
</code></pre>
<p>这将删除之前设置的频道优先级和频道别名配置，并将它们恢复为默认值。默认情况下，Conda 使用官方的 Conda 频道源。</p>
</li>
</ol>
<p>使用以上任一方法都可以将 Conda 恢复到默认下载源。这样，在执行 <code>conda install</code> 或其他 Conda 命令时，Conda 将使用默认的下载源来获取软件包。</p>
<h2>常用指令</h2>
<table>
<thead>
<tr>
<th>指令</th>
<th>作用</th>
</tr>
</thead>
<tbody>
<tr>
<td><code>conda env list</code></td>
<td>列出所有的 conda 环境路径</td>
</tr>
<tr>
<td><code>conda info</code></td>
<td>列出当前 conda 环境的配置信息</td>
</tr>
<tr>
<td><code>conda create -n [env_name]</code></td>
<td>创建环境</td>
</tr>
<tr>
<td><code>conda activate [env_name]</code></td>
<td>激活指定环境</td>
</tr>
<tr>
<td><code>conda deactivate [env_name]</code></td>
<td>退出指定环境，回到 <code>base</code></td>
</tr>
<tr>
<td><code>conda remove -n [env_name] --all</code></td>
<td>删除环境</td>
</tr>
<tr>
<td><code>conda remove/uninstall [pkg]</code></td>
<td>删除包</td>
</tr>
<tr>
<td><code>conda env remove -n [env_name]</code></td>
<td>删除环境</td>
</tr>
</tbody>
</table>
<p><code>conda env remove -n &lt;环境名称&gt;</code> 和 <code>conda remove --name &lt;环境名称&gt; --all</code> 都用于从 Conda 中删除环境，但它们之间有一些区别：</p>
<ol>
<li>
<p>命令的语法不同：</p>
<ul>
<li><code>conda env remove -n &lt;环境名称&gt;</code> 是一种删除环境的方式，其中 <code>-n</code> 用于指定要删除的环境的名称。</li>
<li><code>conda remove --name &lt;环境名称&gt; --all</code> 是另一种删除环境的方式，其中 <code>--name</code> 用于指定要删除的环境的名称，而 <code>--all</code> 用于删除环境中的所有包和依赖项。</li>
</ul>
</li>
<li>
<p><code>conda env remove</code> 更直观：</p>
<ul>
<li><code>conda env remove</code> 命令的语法更直观，因为它专门用于删除环境。这使得它更容易理解和记忆。</li>
</ul>
</li>
<li>
<p><code>conda remove</code> 的灵活性：</p>
<ul>
<li><code>conda remove</code> 命令不仅可用于删除整个环境，还可用于删除环境中的特定包。如果您只想删除环境中的某个包，您可以使用 <code>conda remove --name &lt;环境名称&gt; &lt;包名称&gt;</code> 命令。</li>
</ul>
</li>
</ol>
<p>综上所述，选择使用哪个命令取决于您的需求。如果您只需要删除整个环境，那么 <code>conda env remove</code> 可能更直观。如果您需要更多的灵活性，包括删除特定包，那么 <code>conda remove</code> 可能更适合。</p>
<h2>指定路径创建 conda 环境</h2>
<pre><code>conda create --prefix=D:\Conda\文件夹名称 python=3.8
</code></pre>
<h2>没有名字的 env 如何启动</h2>
<pre><code>conda env list
# conda environments:
#
base                  *  D:\env\anaconda3
                         D:\env\conda\envs\learn-torch
</code></pre>
<p>此时可以通过路径激活 conda 环境</p>
<pre><code>codna activate D:\env\conda\envs\learn-torch
</code></pre>
<h3>为什么会出现这种情况？</h3>
<p>不推荐使用直接修改 <code>~/.condarc</code> 的方式来修改 conda 的默认路径，因为有权限的限制，或者别的什么原因，导致你的修改可能并不会起作用。</p>
<pre><code>❯ conda config --show envs_dirs
envs_dirs:
  - C:\Users\Administrator\.conda\envs
  - D:\env\anaconda3\envs
  - C:\Users\Administrator\AppData\Local\conda\conda\envs
</code></pre>
<p>可以看见， <code>D:\env\conda\envs\</code> 这个路径并不在 <code>envs_dirs</code> 里面，也就是我们建立的<strong>环境目录没有加进来</strong>。因此，执行下面命令手动添加</p>
<pre><code>conda config --append envs_dirs D:\env\conda\envs 
</code></pre>
<p>再次执行 <code>conda config --show envs_dirs</code> ，可以看见该路径已经成功添加。</p>
<p>接下来同步修改一下 <code>pkgs</code> 的路径，毕竟这玩意才是硬盘占用大头</p>
<pre><code>conda config --add pkgs_dirs D:\env\conda\pkgs
</code></pre>
<blockquote>
<p>已经 <code>create</code> 的 env 是（<s>没法</s>？）修改的，所以建议直接 remove，或者直接新建，这样新产生的 conda 环境就会在我们指定的路径下。</p>
</blockquote>
<h2>如何以指定的 conda 环境运行代码</h2>
<p>这是我作为初学者冒出的一个问题。后来想明白了发现，这其实是并没有理解 conda 作用的情况下才会提出的。</p>
<p>你的代码相当于一块蛋糕，你准备使用不同的 conda 环境去运行，其实就等价于用不同的餐具吃蛋糕：</p>
<ol>
<li>蛋糕不会因为你使用不同的餐具导致其形状、位置发生变化（<strong>你的代码始终在计算机硬盘里某个地方放着，不会被移动、修改</strong>）</li>
<li>你想用什么餐具去吃，就用手拿什么餐具（ <code>conda activate &lt;your_env&gt;</code> ）</li>
</ol>
<p>所以，激活对应的 conda 环境后，该怎么执行代码就怎么执行，不需要移动代码到 conda 所在位置或者别的什么操作。</p>
<h2>如何在某个 conda 环境下添加新包</h2>
<p>下载</p>
<pre><code>conda install &lt;pkgs&gt;=&lt;version&gt;
</code></pre>
<p>查看当前 conda 环境有哪些包</p>
<pre><code>conda list
</code></pre>
<h2>修改已建立的 conda 环境的 Python 版本</h2>
<pre><code>conda activate &lt;env_name&gt;
conda uninstall python
conda install python=x.x
</code></pre>
<h2>重置 conda 环境</h2>
<p>查看历史，按需选择 REV_NUM 数值。一般情况下，0 表示是第一次安装，包括 root 环境和 conda 命令，所以一般为 1 或之后。</p>
<pre><code>conda list --revisions

conda install --revision [revision number]
</code></pre>
<h2>通过文件形式下载依赖</h2>
<p>例如 <code>requirement.txt</code></p>
<pre><code>scikit-learn==1.1.3 
scipy==1.10.1 
seqeval==1.2.2
transformers==4.27.4
pytorch-crf==0.7.2
</code></pre>
<p>通过如下指令下载</p>
<pre><code>conda install --file requirement.txt
</code></pre>
<blockquote>
<p>在 conda 环境中， <code>&lt;pkg&gt;==&lt;version&gt;</code> 和 <code>&lt;pkg&gt;=&lt;version&gt;</code> 之间通常没有区别。两者都表示要安装指定版本的包。</p>
</blockquote>
<h2>conda 环境下通过 pip 安装的包在哪里？</h2>
<p>Windows：</p>
<pre><code>&lt;anaconda安装路径&gt;\envs\&lt;环境名称&gt;\Lib\site-packages\
</code></pre>
<h2>去除 conda 前的 (base) 标识</h2>
<pre><code># 取消
conda config --set changeps1 false
# 激活
conda config --set changeps1 true
</code></pre>
<h2>conda 路径异常</h2>
<p>默认情况下，conda 会安装在 <code>/home/user</code> 下，如果迁移，可能会导致一系列异常。</p>
<p>此时，需要修改：</p>
<pre><code>vim miniconda3/bin/conda
</code></pre>
<blockquote>
<p>没错，conda 是一个文本文件，而不是二进制</p>
</blockquote>
<pre><code>#!/home/yzh/yzh/miniconda3/bin/python
# -*- coding: utf-8 -*-
import sys
# Before any more imports, leave cwd out of sys.path for internal 'conda shell.*' commands.
# see https://github.com/conda/conda/issues/6549
if len(sys.argv) &gt; 1 and sys.argv[1].startswith('shell.') and sys.path and sys.path[0] == '':
    # The standard first entry in sys.path is an empty string,
    # and os.path.abspath('') expands to os.getcwd().
    del sys.path[0]

if __name__ == '__main__':
    from conda.cli import main
    sys.exit(main())
</code></pre>
<p>修改第一行 <code>#!</code> 后的执行文件路径，为迁移之后的 <code>miniconda3</code> 路径。</p>
<h2>复制一个 conda 环境</h2>
<p>请养成良好的习惯，编写代码时记录下 <code>requirements.txt</code>！
请养成良好的习惯，编写代码时记录下 <code>requirements.txt</code>！
请养成良好的习惯，编写代码时记录下 <code>requirements.txt</code>！</p>
<blockquote>
<p>conda 确实可以 clone 一个环境，但这不是写代码不写依赖和 README.md 的理由，all right？</p>
</blockquote>
<p>如果是用户自己克隆自己的某个环境，很简单：</p>
<pre><code>conda create --name &lt;创建的环境名&gt; --clone &lt;要克隆的环境名&gt;
</code></pre>
<p>如果是一个用户，克隆另一个用户的环境，直接给 <code>--clone</code> 传入环境敏感参数是不可行的，因为不同用户之间并不可见。因此，可以采取如下两种方式：</p>
<ol>
<li>
<p>通过环境配置复刻一个环境。</p>
<p>首先，让原环境的所有者导出其环境的配置文件：</p>
<pre><code> conda env export &gt; environment.yml
</code></pre>
<p>这将会创建一个名为 <code>environment.yml</code> 的文件，其中包含了环境中的所有包及其版本信息。将其分享给另一个用户，即可凭此创建一个具有相同配置的新环境：</p>
<pre><code>conda env create -f environment.yml
</code></pre>
<p>新环境的名字默认是文件中指定的名字，如果没有指定则需要手动命名。</p>
</li>
<li>
<p>直接访问源环境克隆。</p>
<p>如果知道另一个用户的环境路径，<strong>并且对该路径拥有读取权限</strong>，理论上可以直接克隆：</p>
<pre><code>conda create --clone /path/to/original/env --name new_env_name
</code></pre>
</li>
</ol>
]]></content>
    <author><name>Sun2ot</name></author>
    <category term="教程"/>
  </entry>
  <entry>
    <title>Vim Docs</title>
    <link href="https://blog.085404.xyz/posts/vim/" rel="alternate" type="text/html"/>
    <id>https://blog.085404.xyz/posts/vim/</id>
    <published>2023-08-26T00:00:00.000Z</published>
    <updated>2023-08-26T00:00:00.000Z</updated>
    <summary></summary>
    <content type="html"><![CDATA[<h1>Vim Docs</h1>
<blockquote>
<p>Unix-style 系统中，shell 的光标默认是<strong>色块</strong>，而不是 Windows 中的 <code>|</code> 。因此讨论光标位置时，要严格区分光标前、光标所在位置与光标后。</p>
</blockquote>
<h2>命令模式</h2>
<p>从<strong>命令模式</strong>切换到<strong>底线命令模式</strong>， <code>:</code></p>
<h3>常规输入</h3>
<table>
<thead>
<tr>
<th>命令</th>
<th>作用</th>
</tr>
</thead>
<tbody>
<tr>
<td><code>i</code></td>
<td>切换到<strong>输入模式</strong>，从<strong>当前光标位置</strong>开始输入</td>
</tr>
<tr>
<td><code>a</code></td>
<td>切换到<strong>输入模式</strong>，从<strong>光标位置后一位</strong>开始输入</td>
</tr>
<tr>
<td><code>o</code></td>
<td>当前行下方插入新行，并进入输入模式</td>
</tr>
<tr>
<td><code>O</code></td>
<td>在当前行上方插入新行，并进入输入模式</td>
</tr>
<tr>
<td><code>v</code></td>
<td>切换到<strong>选择模式</strong>，类似于鼠标框选文本</td>
</tr>
</tbody>
</table>
<h3>光标移动</h3>
<p>以下命令输入后，按 <code>Enter</code> 触发</p>
<table>
<thead>
<tr>
<th>命令</th>
<th>作用</th>
</tr>
</thead>
<tbody>
<tr>
<td><code>&lt;num&gt; + ↑↓←→</code></td>
<td>光标向上/下/左/右移动 num 位，例如 <code>30↑</code> 。左右移动时<strong>不会超过行首/行尾</strong>。</td>
</tr>
<tr>
<td><code>ctrl + u/d</code></td>
<td>屏幕向上/下移动<strong>半页</strong></td>
</tr>
<tr>
<td><code>&lt;num&gt; + Space</code></td>
<td>光标向后移动 num 位（<strong>可换行</strong>）</td>
</tr>
<tr>
<td><code>0/$</code></td>
<td>光标移动到<strong>行首/行尾</strong>（ <code>Home/End</code> 也行 ）</td>
</tr>
<tr>
<td><code>G</code></td>
<td>光标移动到文件<strong>最后一行</strong></td>
</tr>
<tr>
<td><code>gg</code></td>
<td>光标移动到文件<strong>第一行</strong></td>
</tr>
<tr>
<td><code>&lt;num&gt; + G</code></td>
<td>光标<strong>移动到</strong> num 行</td>
</tr>
<tr>
<td><code>&lt;num&gt; + Enter</code></td>
<td>光标向下移动 num 行</td>
</tr>
</tbody>
</table>
<h3>查找替换</h3>
<table>
<thead>
<tr>
<th>命令</th>
<th>作用</th>
</tr>
</thead>
<tbody>
<tr>
<td><code>/word</code></td>
<td>向光标后寻找 <code>word</code> 字符串</td>
</tr>
<tr>
<td><code>?word</code></td>
<td>向光标前寻找 <code>word</code> 字符串</td>
</tr>
<tr>
<td><code>n</code></td>
<td>重复前一个查找动作（等价于<strong>查找下一个/上一个</strong>）</td>
</tr>
<tr>
<td><code>N</code></td>
<td>反向进行前一个查找动作（<strong>向前变向后，向后变向前</strong>）</td>
</tr>
<tr>
<td><code>:&lt;num1&gt;,&lt;num2&gt;s/word1/word2/g</code></td>
<td>在 num1 行到 num2 行之间搜索字符串 <code>word1</code> ，将其替换为 <code>word2</code></td>
</tr>
<tr>
<td><code>:&lt;num1&gt;,&lt;num2&gt;s/word1/word2/gc</code></td>
<td>跟上面效果一样，但是替换时会<strong>请求确认 (confirm)</strong></td>
</tr>
<tr>
<td><code>:%s/word1/word2/g</code></td>
<td>在所有行进行上述操作</td>
</tr>
<tr>
<td><code>:%s/word1/word2/gc</code></td>
<td>同理</td>
</tr>
</tbody>
</table>
<h3>批量操作</h3>
<table>
<thead>
<tr>
<th>命令</th>
<th>作用</th>
</tr>
</thead>
<tbody>
<tr>
<td><code>x</code></td>
<td>删除光标所在处的字符（后面的会自动向前缩进）</td>
</tr>
<tr>
<td><code>X</code></td>
<td>删除光标前一个字符</td>
</tr>
<tr>
<td><code>&lt;num&gt;x</code></td>
<td>从光标所在处开始, 向后删除 num 个字符</td>
</tr>
<tr>
<td><code>dd</code></td>
<td><strong>剪切</strong>当前行 (<strong>带换行符</strong>)</td>
</tr>
<tr>
<td><code>&lt;num&gt;dd</code></td>
<td>从光标所在行开始, 向下剪切 num 行</td>
</tr>
<tr>
<td><code>d0</code></td>
<td>剪切光标前一位到行首</td>
</tr>
<tr>
<td><code>d$</code></td>
<td>剪切光标所在处到行尾 (<strong>不带换行符</strong>)</td>
</tr>
<tr>
<td><code>d&lt;num&gt;G</code></td>
<td>从光标所在行开始, 剪切到 num 行</td>
</tr>
<tr>
<td><code>yy</code></td>
<td><strong>复制</strong>当前行</td>
</tr>
<tr>
<td><code>&lt;num&gt;yy</code></td>
<td>复制光标所在的向下 num 行</td>
</tr>
<tr>
<td><code>y0</code></td>
<td>复制光标前一位到行首</td>
</tr>
<tr>
<td><code>y$</code></td>
<td>复制光标所在处到行尾 (<strong>不带换行符</strong>)</td>
</tr>
<tr>
<td><code>y&lt;num&gt;G</code></td>
<td>从光标所在行开始, 复制到 num 行</td>
</tr>
<tr>
<td>小写 <code>p</code></td>
<td>粘贴到光标<strong>后</strong>(若带有换行符, 则粘贴到下一行)</td>
</tr>
<tr>
<td>大写 <code>P</code></td>
<td>粘贴到光标<strong>前</strong>(若带有换行符, 则粘贴到上一行)</td>
</tr>
<tr>
<td><code>J</code></td>
<td>将光标下一行合并到光标所在行, 用空格隔开</td>
</tr>
<tr>
<td><code>u</code></td>
<td>撤销上一次操作</td>
</tr>
<tr>
<td><code>ctrl + r</code></td>
<td>重做被撤销的操作</td>
</tr>
<tr>
<td>小数点 <code>.</code></td>
<td>重复上一个操作</td>
</tr>
<tr>
<td><code>"+y</code></td>
<td>双引号 + 加号 + y，将 visual 模式中的内容复制到<strong>系统剪切板</strong>（不是 vim 的默认缓冲区）</td>
</tr>
<tr>
<td><code>"+p</code></td>
<td>将系统剪切板中的内容粘贴到 vim</td>
</tr>
<tr>
<td><code>&gt;</code> / <code>&lt;</code></td>
<td>向前或向后缩进<strong>光标选中的</strong>文本d</td>
</tr>
</tbody>
</table>
<h2>底线命令</h2>
<p>从<strong>底线命令模式</strong>切换到<strong>命令模式</strong>， <code>ESC</code></p>
<table>
<thead>
<tr>
<th>命令</th>
<th>作用</th>
</tr>
</thead>
<tbody>
<tr>
<td><code>:w</code></td>
<td>保存文件</td>
</tr>
<tr>
<td><code>:wq</code></td>
<td>保存文件，并退出 Vim 编辑器</td>
</tr>
<tr>
<td><code>:q</code></td>
<td>退出 Vim 编辑器</td>
</tr>
<tr>
<td><code>:q!</code></td>
<td>强制退出 Vim 编辑器，不保存修改</td>
</tr>
<tr>
<td><code>:w &lt;file_name&gt;</code></td>
<td>将编辑的数据另存为新文件</td>
</tr>
<tr>
<td><code>:r &lt;file_name&gt;</code></td>
<td>在编辑的数据中，读入另一个文件（即将新文件内容添加到光标后）</td>
</tr>
<tr>
<td><code>:&lt;num1&gt;,&lt;num2&gt; w &lt;file_name&gt;</code></td>
<td>将 num1 到 num2 行的内容另存为新文件</td>
</tr>
<tr>
<td><code>! &lt;command&gt;</code></td>
<td>暂且离开 vi/vim 环境，执行 command 并显示结果</td>
</tr>
</tbody>
</table>
<h2>输入模式</h2>
<table>
<thead>
<tr>
<th>按键</th>
<th>作用</th>
</tr>
</thead>
<tbody>
<tr>
<td><code>Enter</code></td>
<td>换行</td>
</tr>
<tr>
<td><code>Backspace</code></td>
<td>删除<strong>光标前</strong>字符</td>
</tr>
<tr>
<td><code>Del</code></td>
<td>删除<strong>光标后</strong>字符</td>
</tr>
<tr>
<td><code>Home/End</code></td>
<td>光标移动到<strong>行首/行尾</strong></td>
</tr>
<tr>
<td><code>Page Up/Down</code></td>
<td>上/下翻页</td>
</tr>
</tbody>
</table>
<h2>配置文件</h2>
<p>vim 的配置文件位于 <code>~/.vimrc</code>。配置文件中的 <code>set</code> 指令可以在 vim 窗口中通过 <code>:</code> 底线命令模式直接执行，当然也就只会在当前窗口中生效。写入配置文件时则会在每次编辑前默认加载。</p>
<h3>字符集</h3>
<pre><code>set encoding=utf-8
</code></pre>
<h3>缩进</h3>
<pre><code>set autoindent expandtab tabstop=2 shiftwidth=2
</code></pre>
<ul>
<li>autoindent：换行时自动缩进</li>
<li>expandtab：按下 Tab 键时插入空格而不是制表符</li>
<li>tabstop=2：设置 tab 键的宽度为 2 个空格</li>
<li>shiftwidth：使用 &lt;&lt; 和 &gt;&gt; 命令缩进或移动文本时的宽度为 2 个空格</li>
</ul>
<h3>行号</h3>
<pre><code># 显示行号
set nu
# 取消显示行号
set nonu
</code></pre>
]]></content>
    <author><name>Sun2ot</name></author>
    <category term="教程"/>
  </entry>
  <entry>
    <title>NGINX 源码构建</title>
    <link href="https://blog.085404.xyz/posts/nginx-%E6%BA%90%E7%A0%81%E6%9E%84%E5%BB%BA/" rel="alternate" type="text/html"/>
    <id>https://blog.085404.xyz/posts/nginx-%E6%BA%90%E7%A0%81%E6%9E%84%E5%BB%BA/</id>
    <published>2023-08-03T00:00:00.000Z</published>
    <updated>2023-08-03T00:00:00.000Z</updated>
    <summary></summary>
    <content type="html"><![CDATA[<h2>下载源码及相关依赖</h2>
<ul>
<li><a href="https://nginx.org/en/download.html">nginx</a></li>
<li><a href="http://zlib.net/">zlib</a></li>
<li><a href="https://github.com/PCRE2Project/pcre2/releases">pcre</a></li>
<li><a href="https://github.com/openssl/openssl/releases">openssl</a></li>
</ul>
<pre><code># nginx源码
wget https://nginx.org/download/nginx-1.24.0.tar.gz
# pcre
wget https://github.com/PCRE2Project/pcre2/releases/download/pcre2-10.42/pcre2-10.42.tar.gz
# openssl
wget https://github.com/openssl/openssl/releases/download/openssl-3.2.0/openssl-3.2.0.tar.gz
# 解压
tar -zxf nginx-1.24.0.tar.gz
tar -zxf pcre2-10.42.tar.gz -C /usr/local/lib
tar -zxf openssl-3.2.0.tar.gz -C /usr/local/lib
</code></pre>
<h2>构建指令</h2>
<p>根据实际情况自行修改，见 <a href="https://docshome.gitbook.io/nginx-docs/readme/cong-yuan-ma-gou-jian-nginx">nginx中文文档</a></p>
<p>进入 nginx 解压路径，执行下述指令升成 Makefile</p>
<pre><code>./configure \
--prefix=/usr/local/nginx-1.24.0 \
--with-openssl=/usr/local/lib/openssl-3.2.0 \
--with-pcre=/usr/local/lib/pcre/pcre2-10.42
</code></pre>
<blockquote>
<p>由于我部署时，服务器已经具有 <code>zlib</code> 环境，因此不添加该参数。</p>
</blockquote>
<p>编译安装</p>
<pre><code>make&amp;&amp;make install
</code></pre>
<p>完成后可以在 <code>--prefix</code> 参数指定的路径下看见 <code>nginx-1.24.0</code>。</p>
<p>检查是否部署成功</p>
<pre><code># 进入二进制文件所在路径
cd /usr/local/nginx-1.24.0/sbin
# 启动nginx
./nginx
</code></pre>
<p>访问 <code>http://IP</code>，查看是否显示 nginx 欢迎页。</p>
<p><img src="https://img.085404.xyz/images/78bca63bb27a22ca66f28d778db6b2c5.png" alt="image.png" /></p>
<h2>权限设置</h2>
<p>使用 root 用户运行 nginx 不符合<strong>最小授权原则</strong>。应该使用非 root 帐号来运行，即使该进程被攻击，植入了恶意代码，但恶意代码拥有的权限有限，无法对整个系统造成破坏。</p>
<p>添加 nginx 用户组并授予必要权限</p>
<pre><code># 创建 nginx 组
groupadd nginx
# 将 nginx 管理人员对应的普通用户加入该组
usermod -aG nginx yzh
# 修改 nginx 程序路径的所属组
chown -Rc root:nginx /usr/local/nginx-1.24.0/
# 日志读写权限
chmod g+w /usr/local/nginx-1.24.0/logs/ -R
# 配置文件写权限
chmod g+w /usr/local/nginx-1.24.0/conf/nginx.conf
</code></pre>
<p>通过 Linux Capabilities 使普通用户具备部分 root 权限，此处所需权限为 <code>CAP_NET_BIND_SERVICE</code>，即允许绑定到小于1024的端口：</p>
<pre><code>setcap cap_net_bind_service=+ep /usr/local/nginx-1.24.0/sbin/nginx
</code></pre>
<p>请注意，能力应该授予给源文件，而不是符号链接，否则会报错</p>
<pre><code>Failed to set capabilities on file `/usr/local/bin/nginx' (Invalid argument)
The value of the capability argument is not permitted for a file. Or the file is not a regular (non-symlink) file
</code></pre>
<p>检查能力授予情况</p>
<pre><code>getcap /usr/local/nginx-1.24.0/sbin/nginx
# 应输出
/usr/local/nginx-1.24.0/sbin/nginx = cap_net_bind_service+ep
</code></pre>
<p>创建符号链接，使得不用每次都进入 nginx 根路径执行命令</p>
<pre><code>ln -s /usr/local/nginx-1.24.0/sbin/nginx /usr/local/bin/nginx
</code></pre>
<p>创建成功后，可以发现在 <code>/usr/local/bin</code> 下新增了一个 <code>nginx</code>。</p>
<p>使用普通用户登录服务器，尝试在<strong>任意路径</strong>下执行 <code>nginx</code>。如提示无权限，修改符号链接的所属组为 <code>nginx</code>：</p>
<pre><code>chown root:nginx /usr/local/bin/nginx
</code></pre>
<p>如果报错 <code>nginx: [emerg] open() "/usr/local/nginx-1.24.0/logs/nginx.pid" failed (13: Permission denied)</code>，可以尝试删除 <code>nginx.pid</code> 或者手动修改其权限为组可写。</p>
<h2>nginx 指令</h2>
<pre><code># 启动nginx
nginx
# 重载配置文件
nginx -s reload
# 停止nginx
nginx -s stop
# 校验配置文件语法
nginx -t
</code></pre>
<p>修改配置文件后，只需要 <code>reload</code> 即可生效。除非有异常情况，才需要先 <code>stop</code> 再 <code>reload</code></p>
]]></content>
    <author><name>Sun2ot</name></author>
    <category term="教程"/>
  </entry>
  <entry>
    <title>万能背景句</title>
    <link href="https://blog.085404.xyz/posts/%E4%B8%87%E8%83%BD%E8%83%8C%E6%99%AF%E5%8F%A5/" rel="alternate" type="text/html"/>
    <id>https://blog.085404.xyz/posts/%E4%B8%87%E8%83%BD%E8%83%8C%E6%99%AF%E5%8F%A5/</id>
    <published>2023-08-03T00:00:00.000Z</published>
    <updated>2023-08-03T00:00:00.000Z</updated>
    <summary></summary>
    <content type="html"><![CDATA[<h2>技术</h2>
<ol>
<li>Time and again, science and technology has created wonders and even miracles, thus fortifying our belief in it.</li>
</ol>
<blockquote>
<p>time and again 再三，反复
fort n. 城堡，要塞
fortify v. 加强，加固</p>
</blockquote>
<ol>
<li>The Internet has penetrate every aspect of daily life, affecting our ways of communication.</li>
</ol>
<p><img src="https://img.085404.xyz/images/38c531c21b1c8a49c4fbddcf9266984f.png" alt="" />
<img src="https://img.085404.xyz/images/4bad4294750712b4e4171dcea701a9e1.png" alt="" /></p>
<h3>技术好处</h3>
<p><img src="https://img.085404.xyz/images/e7f22093a9a6fedfa205c850c21b40a4.png" alt="" /></p>
<h3>技术坏处</h3>
<p><img src="https://img.085404.xyz/images/a6725aea110abadef9035edf4cf62229.png" alt="" /></p>
<h2>经济</h2>
<ol>
<li>As one of the largest developing countries, China is enjoying rapid economic growth in the past few years.</li>
</ol>
<p><img src="https://img.085404.xyz/images/1538d9fc2961a41f8fdd726c19d457b0.png" alt="" /></p>
<h2>社会</h2>
<h3>社会压力</h3>
<ol>
<li>With the quickening pace of urban life and ever-increasing pressure, people in growing numbers are suffering from either the physical or mental priblems.</li>
</ol>
<blockquote>
<p>quicken v. 使变快，加快
ever-increasing adj. 不断增加的，日益提高的</p>
</blockquote>
<p><img src="https://img.085404.xyz/images/d71085a83c9c11e5c677d3af1746d0ab.png" alt="" /></p>
<h3>就业困难</h3>
<ol>
<li>With the unemployement rate running high in most big cities, job security is becoming less and less guaranteed.</li>
</ol>
<p><img src="https://img.085404.xyz/images/c6ab502b8910bc3e1a1706a4a6854d02.png" alt="" /></p>
<h3>积极向上</h3>
<ol>
<li>Traditional way of thinking has changed dramatically, and people are exhibiting greater open-mindedness and a buring desire to determine their own destiny.</li>
</ol>
<p><img src="https://img.085404.xyz/images/eb3ab4ebe0202a209fd5d7cf4c037ab8.png" alt="" /></p>
<h2>人口</h2>
<p><img src="https://img.085404.xyz/images/202b7160396b154f9c9947ab3728aa11.png" alt="" /></p>
<ol>
<li>China has a vast population and its substantial growth rate. However, fast increasing population has potentially negative results, such as intense competition.</li>
</ol>
]]></content>
    <author><name>Sun2ot</name></author>
    <category term="笔记"/>
  </entry>
  <entry>
    <title>作文模板</title>
    <link href="https://blog.085404.xyz/posts/%E4%BD%9C%E6%96%87%E6%A8%A1%E6%9D%BF/" rel="alternate" type="text/html"/>
    <id>https://blog.085404.xyz/posts/%E4%BD%9C%E6%96%87%E6%A8%A1%E6%9D%BF/</id>
    <published>2023-08-03T00:00:00.000Z</published>
    <updated>2023-08-03T00:00:00.000Z</updated>
    <summary></summary>
    <content type="html"><![CDATA[<h2>一、大作文</h2>
<h3>1. 第一段</h3>
<p><code>This is a bar/line/pie chart/graph which describes</code>↓</p>
<ul>
<li>the general trend of <code>__________</code> .</li>
<li>the proportion of <code>_________</code>（提炼关键词）.</li>
<li>......（图标标题）</li>
</ul>
<p><code>+时间</code>↓</p>
<ul>
<li>from 2010 to 2020.</li>
<li>between 2010 and 2020.</li>
<li>during the last 20 years.</li>
<li>......</li>
</ul>
<hr />
<p><code>To be specific/Specifically, ______. By stark contrast, ______.</code>（具体化描述一组对比的图表的变动，例如增长、下跌、……）↓</p>
<ul>
<li>柱状图
<ul>
<li>increase sharply / decrease substantially <code>v. + adv.</code></li>
<li>experience(中性)/enjoy(好)/suffer from(坏) a dramatic decline <code>adj. + n.</code></li>
<li>There is a sharp/gentle increase/rise/decrease/decline from <code>____</code> to <code>____</code> in <code>____</code>.</li>
</ul>
</li>
<li>饼图
<ul>
<li><code>______</code> <strong>account for</strong> 60%, <strong>the highest ratio among</strong> the <code>(four)</code> categories <code>突出占比</code></li>
<li>Simultaneously, <code>______</code>% of the (students) <strong>intend to</strong> do sth. <strong>and</strong> <code>____</code>% <strong>to</strong> do sth. , <strong>respectively</strong>. <code>并列表述1(省略动词)</code></li>
<li>Approximately / Nearly / Only <code>____</code>% and <code>____</code>% of <code>(students)</code> intend to do sth. and do sth. , <strong>respectively</strong>. <code>并列表述2(分别)</code></li>
<li>nearly 20% <code>______</code> , another 20% <code>______</code> , the remaining 20% <code>______</code>. <code>并列描述3(同量不同型)</code></li>
</ul>
</li>
</ul>
<hr />
<h3>2. 第二段</h3>
<blockquote>
<p>关键词（社会因素）</p>
<blockquote>
<p>经济发展 econoic growth<br />
社会影响 social impact/influence<br />
人口膨胀 population explosion<br />
教育体系 education system<br />
科技创新 technological innovation</p>
</blockquote>
</blockquote>
<p>There are several/two fundamental factors contributing to this social phenomenon. The primary factor lies in <code>______</code>(原因1). <code>______</code>(阐述1). (回归主题句：Therefore/因此...增长/下降...)<br />
Futhermore, <code>______</code>(原因2). <code>______</code>(阐述2).</p>
<ul>
<li>social impact may give rise to this trend</li>
</ul>
<hr />
<h3>3. 第三段</h3>
<blockquote>
<p>积极地</p>
</blockquote>
<ol>
<li>With regard to this social tendency, I believe it is inspiring and positive. As this trend is still in its infancy, we believe it will no doubt go further and lead to the future prospect.</li>
</ol>
<blockquote>
<p>消极的</p>
</blockquote>
<ol>
<li>To my knowledge, this social tendency is unfavorable(<code>令人不快的</code>). It is high time that(<code>是...的时候了</code>) we took actions to address the quandary(<code>解决困境</code>). I realize it needs to be slowed down since it has already posed negative impact to our society.</li>
</ol>
<blockquote>
<p>中性的</p>
</blockquote>
<ol>
<li>Based on my personal experience, I can safety draw a conclusion that <code>______</code> is a double-edged sword. Whether it is a boon(<code>恩惠</code>) or a bane(<code>毒药</code>) will be discussed for a number of years to come. However, one should make sensible use, being a master rather than a slave of it.</li>
</ol>
<h3>4. 关键词</h3>
<ol>
<li>
<p>数量：<br />
number(可数), amount(不可数), statistics(统计数字)</p>
</li>
<li>
<p>比例：<br />
percentage, proportion, ratio, share</p>
</li>
<li>
<p>上升：<br />
go up, increase, climb, ascend, rise</p>
<blockquote>
<p>急速上升：increase sharply, surge, soar, rocket</p>
</blockquote>
</li>
<li>
<p>下降：<br />
go down, decrease, decline, descend</p>
<blockquote>
<p>极速下落：plunge, slump</p>
</blockquote>
</li>
<li>
<p>程度：</p>
<ul>
<li>剧烈：<br />
dramatically, significantly, sharply, substantially</li>
<li>缓慢：<br />
slightly, steadily, gently, progressively, moderately</li>
</ul>
</li>
<li>
<p>从...到...：<br />
from...to..., between...and...</p>
</li>
<li>
<p>某种/某些/某个<br />
certain, some</p>
</li>
<li>
<p>大约：<br />
about, around, some, roughly, nearly, approximately</p>
</li>
<li>
<p>分别<br />
respectively</p>
</li>
<li>
<p>对比：<br />
On the contrary, In/By (stark) contrast, Conversely</p>
</li>
<li>
<p>转折：<br />
However, Nevertheless, Nonetheless</p>
</li>
<li>
<p>因果：<br />
As a result/consequence, Consequently, Therefore</p>
</li>
<li>
<p>同时：<br />
Simultaneously, In the meantime, At the same time, Meanwhile</p>
</li>
<li>
<p>占比<br />
account for, constitute, make up</p>
</li>
<li>
<p>关注<br />
pay attention to, place emphasis on, lay stress on, attach importance to, emphasize<br />
focus on, concentrate on, care about, be concerned with, be interested in</p>
</li>
<li>
<p>保持不变<br />
remain + steady / static / unchanged / unvaried<br />
波动<br />
fluctuate</p>
</li>
<li>
<p>现象<br />
phenomenon, stituation<br />
趋势<br />
trend, tendency, inclination</p>
</li>
<li>
<p>在毕业之际 upon graduation</p>
</li>
<li>
<p>满意<br />
be satisfied with, be content with, show satisfaction to<br />
不满意<br />
be dissatisfied with, be discontent with, show dissatisfaction to</p>
</li>
<li>
<p>抚养：raise, bring up, breed<br />
赡养：support, maintain</p>
</li>
</ol>
<h2>二、小作文</h2>
<p>Dear <code>______</code>,<br />
I am <code>______</code>. I am writing this letter for the purpose of <code>____________</code>.<br />
To begin with <code>_________</code>. Furthermore, <code>_____________</code>.</p>
<blockquote>
<p>结尾五选二<br />
① Thank you very much for your time and consideration.(感谢)<br />
② I apologize for any inconvenience caused.(道歉)<br />
③ I am looking forward to your reply.(盼复)<br />
④ I hope that you can enjoy your work and life.(祝愿)<br />
⑤ Should you have any further questions, please let me know.(联系)</p>
</blockquote>
<p>提建议句型</p>
<ul>
<li>Will you please...(do)?</li>
<li>Would you mind...(doing)?</li>
<li>Is it possible for ... to do sth.?</li>
</ul>
]]></content>
    <author><name>Sun2ot</name></author>
    <category term="笔记"/>
  </entry>
  <entry>
    <title>聚类</title>
    <link href="https://blog.085404.xyz/posts/%E8%81%9A%E7%B1%BB/" rel="alternate" type="text/html"/>
    <id>https://blog.085404.xyz/posts/%E8%81%9A%E7%B1%BB/</id>
    <published>2023-07-25T00:00:00.000Z</published>
    <updated>2023-07-25T00:00:00.000Z</updated>
    <summary></summary>
    <content type="html"><![CDATA[<blockquote>
<p>第四阶段：
聚类及常用聚类算法
要求：了解什么是聚类？聚类的无监督性，熟悉常见的聚类算法，明确其在适用场景及作用，自选实例搭建模型开展实验并给出结论。提交研学材料包括（研学笔记，实例源码，模型实验讲解视频）（3周）</p>
</blockquote>
<p>机器学习：聚类算法与无监督学习、模型评估标准-阿里云开发者社区
https://developer.aliyun.com/article/1142803</p>
<h2>聚类的任务</h2>
<p>聚类试图将数据集中的样本划分为若干个通常是不相交的子集，每个子集称为一个 “簇” (cluster)。通过这样的划分，每个簇可能对应于一些潜在的概念 (类别)，如 “浅色瓜” “深色瓜”，“有籽瓜” “无籽瓜”，甚至 “本地瓜”“外地瓜”等。</p>
<p>需说明的是，<strong>这些概念对聚类算法而言事先是未知的</strong>，聚类过程仅能自动形成簇结构，簇所对应的概念语义需由使用者来把握和命名。</p>
<h2>性能度量</h2>
<p>聚类性能度量也称为<strong>聚类有效性指标</strong>（validity index），顾名思义，是用来衡量聚类效果的好坏的。</p>
<p>那么，什么样的聚类结果算好呢？俗话说“物以类聚”，在聚类时，我们希望同一簇（同一类）的样本尽可能聚集在一起，而不同簇（不同类）的样本最好远远地隔开，甚至是完全不重合（即没有交集）。这两种情况我们分别用聚类结果的<strong>簇内相似度</strong>和<strong>簇间相似度</strong>来描述，即“簇内相似度高，簇间相似度低”。</p>
<h2>K-Means 算法</h2>
<p><img src="https://img.085404.xyz/images/5ebcaf8aaddaa3a82c80a697158a69d4.webp" alt="image.png" /></p>
<p>均值向量 $\mu$ 的计算方式：</p>
<p>$$
\mu_i = \frac1{C_i} \sum_{x \in C_i} x,i=1,...,k
$$</p>
<p>样本与各均值向量 $\mu_i$ 的距离 $d_{ji}$ 使用 $L_p$ 范数，这里使用的是 $L_2$ 范数。</p>
<h2>学习向量量化</h2>
<p>学习向量量化，Learning Vector Quantization，简称 LVQ。与 k-means 类似， LV Q 也是试图找到一组原型向量来刻画聚类结构，但与一般聚类算法不同的是， LVQ <strong>假设数据样本带有类别标记</strong>，学习过程利用样本的这些监督信息来辅助聚类。</p>
<p><img src="https://img.085404.xyz/images/0409ffe494e0ffed66a302727d35d148.webp" alt="image.png" /></p>
<p>上述算法中，关键是<strong>原型向量的更新</strong>。</p>
<ul>
<li>
<p>如果最近的原型向量 $p_i^<em>$ 与 $x_j$ 的类别<strong>标记相同</strong>（即认为样本 $x_j$ 属于簇 $C_i$ ），则令 $p_i^</em>$ 向 $x_j$ 的方向靠拢，此时新的原型向量 $p'$ 为</p>
<p>$$
p' = p_i^* + \eta \cdot (x_j-p_i^*)<br />
$$</p>
<p>则 $p'$ 与 $x_j$ 的距离为</p>
<p>$$
\begin{align}
||p' - x_j ||_2 &amp;= ||p_i^* + \eta \cdot (x_j-p_i^<em>) - x_j||_2 \
&amp;= (1-\eta) \cdot ||p_i^</em> - x_j ||_2
\end{align}
$$</p>
<p>也就是原型向量 $p_i^*$ 在更新为 $p'$ 后，将会更加接近 $x_j$</p>
<blockquote>
<p>因为在旧的距离上乘了 $1-\eta$ ， $\eta \in (0,1)$ 是学习率</p>
</blockquote>
</li>
<li>
<p>如果最近的原型向量 $p_i^*$ 与 $x_j$ 的类别<strong>标记不同</strong>，那说明聚类得到的簇不对，那就应该让更新后的原型向量远离样本 $x_j$</p>
<p>$$
p' = p_i^* - \eta \cdot (x_j-p_i^*)<br />
$$</p>
<p>同理，也可以推出
$$
||p' - x_j ||_2 = (1+\eta) \cdot ||p_i^* - x_j ||_2
$$
也就是让更新后的原型向量 $p'$ 与 $x_j$ 的距离变大的意思。</p>
</li>
</ul>
<p>通过上面这种方法，我们可以学得一组原型向量 ${p_1,p_2,...,p_q}$ ，这些向量就能够构建出一个对样本空间 $\mathcal{X}$ 的簇划分。对于每个原型向量 $p_i$ ，它定义了一个与之相关的区域 $R_i$ ：</p>
<p>$$
R_i = {x \in \mathcal{X} \mid ||x-p_i||_2 \leq ||x-p_i'||_2,,i' \neq i}
$$</p>
<p>也就是区域 $R_i$ 中的每个样本 $x$ 到 $p_i$ 的距离，都是不超过其他的原型向量和 $x$ 之间的距离的。换言之，这么多的区域 $R$ ，你 $x$ 离我 $R_i$ 是最近的，那你当然是属于我所代表的这个簇的样本，而不是其他的什么簇。</p>
<p>这样形成的对样本空间 $\mathcal{X}$ 的簇划分 ${R_1,R_2,...,R_q}$ ，成为“<strong>Voronoi 剖分</strong>”。</p>
<h2>高斯混合聚类</h2>
<h3>概率密度</h3>
<p>多元高斯分布的<strong>概率密度函数</strong>为</p>
<p>$$
p(x) = \frac1{(2\pi)^\frac{n}2 |{\bf\Sigma}|^\frac1{2}} e^{-\frac12 (\pmb{x}-\pmb{\mu})^T {\bf\Sigma}^{-1} (\pmb x - \pmb\mu)}
$$
其中， $\pmb\mu$ 是 n 维均值向量， $\pmb\Sigma$ 是 nxn 的协方差矩阵。其推导过程如下：</p>
<p><img src="https://img.085404.xyz/images/bcf51060fca03d3cf56d59dc4d89a68c.webp" alt="多元高斯分布概率密度函数.png" /></p>
<blockquote>
<p>协方差矩阵反应的是各元素之间的相关性（正、负 or 零）。而这里我们的实际情况显然是离散的随机变量，因此属于独立分布，即相关性为 0，故对应维度的协方差矩阵的元素也为 0</p>
</blockquote>
<p>为了明确显示高斯分布和相应参数的关系，可以将 $p(x)$ 记作 $p(x \mid \mu,\Sigma)$</p>
<blockquote>
<p>知道这三个玩意都是向量和矩阵就行了，我懒得打加粗显示了。。。麻烦</p>
</blockquote>
<h3>高斯混合分布</h3>
<p>接下来，定义<strong>高斯混合分布</strong>：</p>
<p>$$
\mathcal{p_M}(x) = \sum_{i=1}^k \alpha_i \cdot p(x \mid \mu_i, \Sigma_i)
$$</p>
<ul>
<li>$k$ 指的是有 $k$ 个高斯分布组成了一个高斯混合分布，其中每一个高斯分布叫做“<strong>混合成分</strong>”</li>
<li>$\mu_i$ 和 $\Sigma_i$ 是第 $i$ 个高斯分布（在这里叫做<strong>高斯混合成分</strong>）的参数</li>
<li>$\alpha_i$ 叫做“<strong>混合系数</strong>”。假设样本是通过一个高斯混合分布产生的，那么 $\alpha_i$ 的意思是选择第 $i$ 个混合成分的概率，类似于“初始概率分布”的意思</li>
</ul>
<blockquote>
<p>高斯混合分布其实就是对组成它的每个高斯分布，乘了一个概率分布 $\alpha$ ，然后把它们加起来</p>
</blockquote>
<h3>后验概率</h3>
<p>现在，假设训练集 $D={x_1,x_2,...,x_m}$ 就是通过上面说的高斯混合分布生成的，假设**样本 $x_j$ 属于的那个高斯混合成分记作 $z_j$ ** ，那么有</p>
<p>$$
\mathcal{p_M}(z_j=i \mid x_j) = \frac{p(z_j=i) \cdot \mathcal{p_M}(x_j \mid z_j=i)}{\mathcal{p_M}(x_j)}
$$</p>
<p>先不用纠结这个式子是什么含义，只从等式的结构上看，它成立的依据是<strong>贝叶斯定理</strong>。其实你把分子乘过去的话，等号两边的两个乘式，结果都是 $p(z_j=i, x_j)$</p>
<blockquote>
<p>不要去纠结 $p$ 和 $\mathcal{p_M}$ 怎么乘。它俩就是个符号，表示的都是概率，也就是一个数字而已。</p>
</blockquote>
<p>再来看 $\mathcal{p_M}(z_j=i \mid x_j)$ ，它给出的是样本 $x_j$ 由第 $i$ 个高斯混合成分生成的<strong>后验概率</strong>，或者读作“<strong>已知一个样本 $x_j$ ，它属于第 $i$ 个高斯混合成分的概率是多少</strong> ”。所以不要在意为啥又多了一个符号 $z$ ，其实没啥卵用，只是这帮数学家为了表示方便，引入的一个新符号而已 hhh。</p>
<p>那么这个后验概率等于多少呢？来处理一下等式右边的部分就知道了。我们先把上文定义的<strong>高斯混合分布</strong>那一堆东西给代入进来</p>
<p>$$
\begin{align}
p(z_j=i) &amp;= \alpha_i \
\mathcal{p_M}(x_j \mid z_j=i) &amp;= p(x_j \mid \mu_i, \Sigma_i) \
\mathcal{p_M}(x_j) &amp;= \sum_{l=1}^k \alpha_l \cdot p(x_j \mid \mu_l, \Sigma_l)
\end{align}
$$</p>
<p>第一个式子很简单，咱们定义的 $\alpha_i$ 本来就是这个概率的含义。第三个式子就是上面的高斯混合分布定义照抄下来的，只是原式里的符号 $i$ ，在这里有具体的含义，即第 $i$ 个高斯混合成分，因此给它换了个符号 $l$ 而已。</p>
<p>有人或许会疑惑第二个式子为什么成立。第二个式子的左式，表示的是在“第 i 个高斯混合成分里取一个样本 $x_j$ 的概率”，那对应的不就是第 i 个高斯混合成分的概率密度函数，即 $p(x_j \mid \mu_i, \Sigma_i)$ 吗？</p>
<p>然后把这三个等式，替换到后验概率的式子中，得到</p>
<p>$$
\begin{align}
\mathcal{p_M}(z_j=i \mid x_j) &amp;= \frac{p(z_j=i) \cdot \mathcal{p_M}(x_j \mid z_j=i)}{\mathcal{p_M}(x_j)} \</p>
<p>&amp;= \frac{\alpha_i \cdot p(x_j \mid \mu_i, \Sigma_i)}{\sum_{l=1}^k \alpha_l \cdot p(x_j \mid \mu_l, \Sigma_l)}
\end{align}
$$</p>
<p>然后，天才的（<s>holy shit</s>）数学家们觉得 $\mathcal{p_M}(z_j=i \mid x_j)$ 长得也太复杂了，于是把它记作 $\gamma_{ji},(i=1,2,...,k)$</p>
<blockquote>
<p>默默地在心里背诵三遍， $\gamma_{ji}$ 就是 $\mathcal{p_M}(z_j=i \mid x_j)$ ，就是已知一个样本 $x_j$ ，它属于第 $i$ 个高斯混合成分的概率是多少</p>
</blockquote>
<h3>簇标记</h3>
<p>回归正题，我们的主题是聚类算法，所以结果是要找到样本对应的簇标记，然后划分到不同的簇中。那么如何确定样本 $x_j$ 的簇标记呢？显然， <strong>$x_j$ 属于哪个高斯混合成分的概率最大，自然就是属于这个类的</strong>，也就可以得到簇标记了，即</p>
<p>$$
\lambda_j = \mathop{\arg\max}\limits_{i \in {1,2,...,k}} ; \gamma_{ji}
$$
所以为什么说高斯混合聚类也是属于原型聚类。相比于 k-means 和 LVQ 使用 $L_p$ 范数，GMM 采用的是概率模型对原型进行刻画。也就是从“<strong>谁离得更近</strong>”，变成了“<strong>谁最有可能</strong>”。</p>
<h3>EM 算法求解</h3>
<p>现在，我们知道要解决的问题就是上面这个找最大的 $\gamma$ 。还记得上面背诵三遍的东西吗</p>
<p>$$
\gamma_{ji} = \mathcal{p_M}(z_j=i \mid x_j) = \frac{\alpha_i \cdot p(x_j \mid \mu_i, \Sigma_i)}{\sum_{l=1}^k \alpha_l \cdot p(x_j \mid \mu_l, \Sigma_l)}
$$
显然，这个式子跟三个东西有关，即 ${\alpha, \mu, \Sigma}$ ，因此用 EM 算法进行迭代优化求解。</p>
<p>首选取对数似然函数，</p>
<p>$$
\begin{align}
LL(D) &amp;= ln\left(\prod_{j=1}^m \mathcal{p_M}(x_j)\right) \
&amp;= \sum_{j=1}^m ln\left(\sum_{i=1}^k \alpha_i \cdot p(x_j \mid \mu_i, \Sigma_i)\right)
\end{align}
$$
对 $\mu, \Sigma$ 分别求偏导并令其为 0，可解得</p>
<p>$$
\mu_i = \frac{\sum_{j=1}^m \gamma_{ji} x_j}{\sum_{j=1}^m \gamma_{ji}}
$$</p>
<p>$$
\Sigma_i = \frac{\sum_{j=1}^m \gamma_{ji} (x_j-\mu_i) (x_j-\mu_i)^T}{\sum_{j=1}^m \gamma_{ji}}
$$</p>
<p>这里先给出一部分 $\mu$ 的证明，因为链式求导也就只有这一个难点，至于 $\Sigma$ 就随缘吧 hhh。关于矩阵求导的问题，见<a href="https://zhuanlan.zhihu.com/p/273729929?utm_id=0">矩阵求导的推导</a> <code>2.3</code> 部分。</p>
<blockquote>
<p>这一段“南瓜书”中的矩阵求导法则说法貌似有点小小的不严谨？书里给的直接是 2Ax，但这里只不过对角阵正好满足 $A^T = A$ ，所以是 2Ax</p>
</blockquote>
<p><img src="https://img.085404.xyz/images/2adb88efc3950402e13591280acebedb.webp" alt="EM求高斯混合分布.png" /></p>
<p>对于 $\alpha$ ，除了要求偏导，还需要满足其自身的条件，即 $\alpha_i \geq 0, \sum_{i=1}^k\alpha_i=1$ ，因此要用<strong>拉格朗日乘数法</strong>，解得（证明略）</p>
<p>$$
\alpha_i = \frac1m\sum_{j=1}^m \gamma_{ji}
$$</p>
<h3>算法综述</h3>
<p><img src="https://img.085404.xyz/images/5f2cd93e4965e3fd5fa16b6f9557520c.webp" alt="image.png" /></p>
<h2>基于密度的聚类方法 (DBSCAN)</h2>
<h3>定义</h3>
<p>密度聚类也称“基于密度的聚类”（density-based clustering），顾名思义，此类算法根据样本分布的紧密结构，来进行类别划分。</p>
<p>DBSCAN（Density-Based Spatial Clustering of Applications with Noise）是一种著名的密度聚类算法，它基于一组“邻域”参数 $(\epsilon,MinPts)$ 来刻画<strong>样本分布的紧密程度</strong> 。</p>
<p>给定数据集 $D={x_1,x_2,...,x_m}$ ，定义如下概念</p>
<ul>
<li>
<p>$\epsilon$ - 邻域： $N_\epsilon(x_j)={x_j \in D \mid dist(x_i, x_j) \leq \epsilon}$
对于一个样本 $x_j$ ，我们可以计算出其他样本点到 $x_j$ 的距离 <code>dist</code> ，这个距离默认情况下设为<strong>欧氏距离</strong>。然后我们给定一个 $\epsilon$ 参数，表示一个距离限度值，如果 <code>dist</code> 大于这个值，就认为该样本距离 $x_j$ 太远了，所以不计入邻域；反之，小于等于 $\epsilon$ ，就将该样本放入 $N_\epsilon(x_j)$</p>
</li>
<li>
<p>核心对象：若 $x_j$ 的 $\epsilon$ - 邻域至少包含 $MinPts$ （min+points：最少样本点数）个样本，即 $|N_\epsilon (x_j)| \geq MinPts$ ，则 $x_j$ 是一个核心对象</p>
</li>
</ul>
<blockquote>
<p>也就是说，想成为核心对象，首先周围要有一堆样本。这堆样本要满足两个条件：一是到你的距离得足够近，小于 $\epsilon$ ；二是数量要够，至少得 $MinPts$ 个。</p>
</blockquote>
<ul>
<li>密度直达（<strong>直接密度可达</strong>）：若 $x_j$ 在 $x_i$ 的 $\epsilon$ - 邻域中，且 $x_i$ 是<strong>核心对象</strong>，则称 $x_j$ 由 $x_i$ 密度直达</li>
</ul>
<blockquote>
<p>显然，<strong>密度直达在默认条件下是不满足对称性</strong>的，即 $x_j$ 由 $x_i$ 密度直达时， $x_i$ 不一定由 $x_j$ 密度直达。但如果 $x_i, x_j$ 都是核心对象，那么密度直达就满足对称性。</p>
</blockquote>
<ul>
<li>密度可达：对 $x_i, x_j$ ，若存在<strong>样本序列</strong> $p_1, p_2, ..., p_n$ ，其中 $p_1=x_i, p_n=x_j$ ，且 $p_{i+1}$ 由 $p_i$ 密度<strong>直达</strong>，则称 $x_j$ 由 $x_i$ 密度<strong>可达</strong></li>
</ul>
<blockquote>
<p>对于样本序列{P}， $p_{i+1}$ 由 $p_i$ 密度直达，也就是后一项由前一项密度直达，这意味着 $p_1, p_2, ..., p_{n-1}$ 都是核心对象（ $p_n$ 不一定是 ），且相邻两项的距离小于 $\epsilon$</p>
<p><strong>密度可达在核心对象间也是满足对称性的</strong>。</p>
</blockquote>
<ul>
<li>密度相连：对 $x_i, x_j$ ，若存在 $x_k$ 使得 $x_i, x_j$ 均由 $x_k$ 密度<strong>可达</strong>，则称 $x_i$ 与 $x_j$ 密度相连</li>
</ul>
<p><img src="https://img.085404.xyz/images/89c2e497f252eb03008731a0edbfc7ee.webp" alt="密度聚类.png" /></p>
<p>图中红色为核心对象，绿色为密度可达。两个绿色之间为密度相连。</p>
<h3>DBSCAN 算法</h3>
<p>基于上述概念，DBSCAN 要找的<strong>簇</strong>，就是由 $x$ 密度可达的所有样本组成的集合。</p>
<p><img src="https://img.085404.xyz/images/3e9f186f0d1b7f6f02cd1f57ff38df75.webp" alt="image.png" /></p>
<p><code>\</code> 表示求差集</p>
<p>具体代码见 https://github.com/sun2ot/python-study/blob/main/clustering/dbscan.py</p>
<h2>层次聚类</h2>
<p>层次聚类试图在不同层次对数据集进行划分，从而形成树形的聚类结构。数据集的划分可采用“自底向上”的聚合策略，也可采用 “自顶向下”的分拆策略。</p>
<p>AGENS 是一种采用自底向上聚合策略的层次聚类算法。它先将数据集中每个样本看做一个单独的聚类簇，然后在算法中每一步都寻找距离最近的两个聚类簇合并，重复该过程，直到达到预设的聚类簇个数。</p>
<h3>集合间的距离</h3>
<p>AGENS 算法的重点在于如何计算集合的距离。通常情况下，采用<strong>豪斯多夫距离</strong>（Hausdorff distance）：</p>
<p>对于同一样本空间中的集合 $X$ 和 $Z$ ，</p>
<p>$$
dist_H(X, Z) = max(dist_h(X, Z), dist_h(Z, X))
$$</p>
<p>其中，</p>
<p>$$
dist_h(X, Z) = \max_{x \in X}\min_{z \in Z} ||x-z||_2
$$
<a href="https://www.cnblogs.com/icmzn/p/8531719.html">关于豪斯多夫距离</a></p>
<hr />
<p>也可以用以下式子来计算距离</p>
<p>最小距离：</p>
<p>$$
d_min(C_i, C_j) = \min_{x \in C_i, z \in C_j} dist(x, z)
$$</p>
<p>最大距离：</p>
<p>$$
d_max(C_i, C_j) = \max_{x \in C_i, z \in C_j} dist(x, z)
$$</p>
<p>平均距离：</p>
<p>$$
d_{avg}(C_i, C_j) = \frac1{|C_i| |C_j|} \sum_{x \in C_i} \sum_{z \in C_j} dist(x, z)
$$</p>
<p>显然，最小距离由两个簇的最近样本决定，最大距离由两个簇的最远样本决定, 而平均距离则由两个簇的所有样本共同决定。</p>
<h3>AGENS 算法</h3>
<p><img src="https://img.085404.xyz/images/ec8ca5690c1782a2d2ba734d25680d1c.webp" alt="image.png" /></p>
<p>https://github.com/sun2ot/python-study/blob/main/clustering/agnes.py</p>
<h2>附录</h2>
<ul>
<li>南瓜书： https://github.com/datawhalechina/pumpkin-book</li>
<li>西瓜数据集：https://pan.baidu.com/s/1ix_c0dGHsET4yxPhc025cg （提取码 h24l）</li>
</ul>
]]></content>
    <author><name>Sun2ot</name></author>
    <category term="笔记"/>
  </entry>
  <entry>
    <title>软考中级-软件设计师</title>
    <link href="https://blog.085404.xyz/posts/%E8%BD%AF%E8%80%83%E4%B8%AD%E7%BA%A7-%E8%BD%AF%E4%BB%B6%E8%AE%BE%E8%AE%A1%E5%B8%88/" rel="alternate" type="text/html"/>
    <id>https://blog.085404.xyz/posts/%E8%BD%AF%E8%80%83%E4%B8%AD%E7%BA%A7-%E8%BD%AF%E4%BB%B6%E8%AE%BE%E8%AE%A1%E5%B8%88/</id>
    <published>2023-07-22T00:00:00.000Z</published>
    <updated>2023-07-22T00:00:00.000Z</updated>
    <summary></summary>
    <content type="html"><![CDATA[<h1>软考中级-软件设计师</h1>
<h2>冯诺依曼</h2>
<p>指令和数据在同一存储器</p>
<h2>二进制数</h2>
<h3>浮点表示法</h3>
<p>二进制数 $N = 2^E \times F$ ，其中</p>
<ul>
<li>$E$ 为阶码（<strong>带符号的纯整数</strong>），决定 $N$ 的<strong>大小</strong>（位数更多，数值更大/表示范围越大）</li>
<li>$F$ 为尾数（<strong>带符号的纯小数</strong>），决定 $N$ 的<strong>精度</strong>（位数越多，精度越高）</li>
</ul>
<blockquote>
<p>[!tip]
这种方法称为浮点表示法</p>
</blockquote>
<p><img src="https://img.085404.xyz/images/f55d8e47f67f6892466fd0ef1a5f6639.webp" alt="image.png" /></p>
<p><strong>数符</strong>：尾数的符号，1 为正，0 为负
<strong>阶符</strong>：1 为负，0 为正</p>
<p>尾数 10 位 =&gt; $F = 2^{-10}$</p>
<blockquote>
<p>小数点后面，从 1 次方开始算， $2^1, 2^2, ......$</p>
</blockquote>
<p>阶码补码 (负数) = 0001 =&gt; 阶码原码 = 取反 (0001 - 1) = 1111
即 $E = - (1<em>2^0 + 1</em>2^1 + 1<em>2^2 + 1</em>2^3) = -15$</p>
<p>综上，浮点数为 $2^{-15} \times 2^{-10}$</p>
<h3>浮点数对阶</h3>
<p>浮点数加减运算时，首先要进行对阶，根据对阶的规则，阶码和尾数将进行相应的操作。</p>
<p><strong>对阶的规则</strong>是，小阶向大阶对齐，即<strong>阶码小的尾数算数右移</strong>，每右移一位，阶码加1，直到对接的两个数的阶码相等为止。</p>
<h3>浮点数加减法</h3>
<ol>
<li>零操作数检查</li>
<li>对阶操作</li>
<li>尾数加（减）运算</li>
<li>规格化及舍入处理</li>
</ol>
<h3>移位运算</h3>
<ol>
<li>左移 <code>&lt;&lt;</code> 1 位：乘 $2^1$</li>
<li>带符号右移</li>
<li>无符号右移</li>
</ol>
<h2>校验码</h2>
<h3>奇偶校验码</h3>
<p>只能检测代码中奇数个位出错的编码，但不能发现偶数个位出错的情况。</p>
<h3>海明码</h3>
<ul>
<li>
<p>海明码利用奇偶性进行检错和纠错</p>
</li>
<li>
<p>码距最小为 2n+1</p>
</li>
<li>
<p>数据位 n，校验位 k，则</p>
<p>$$
2^k \geq n+k+1
$$</p>
<p>例：对于 32 位的数据，至少需要加（6）个校验位才能构成海明码。</p>
</li>
</ul>
<h3>循环冗余校验码</h3>
<p>采用<strong>模 2 运算</strong>来构建校验位的，是 CRC 循环冗余校验码</p>
<h2>哈夫曼树</h2>
<ul>
<li>左 0 右 1</li>
<li>节点的度要么为 0，要么为 2（用来<strong>判断一个编码方案是否构成哈夫曼树</strong>）</li>
<li>若有 n 个字符构造哈夫曼树，则会多出 n-1 个节点，也即一个 n+n-1 个节点的哈夫曼树，能够得到 n 个字符的编码</li>
</ul>
<h2>指令</h2>
<ol>
<li>CPU 依据<strong>指令周期的不同阶段</strong>来区分在内存中以二进制编码形式存放的指令和数据</li>
<li><code>VLIW (Very Long Instruction Word)</code>：超长指令字</li>
<li>双核处理器的“双核”指的是在一个CPU中集成两个运算核心以提高运算能力</li>
</ol>
<h3>寄存器</h3>
<ul>
<li>PC（程序计数器）：存放下一条指令的地址</li>
<li>IR（指令寄存器）：存放从内存中读取的指令</li>
<li>AC（累加器）：通用寄存器，可用于传送和暂存数据，也可参与算术逻辑运算，<strong>保存运算结果</strong></li>
</ul>
<h3>指令流水线</h3>
<p><img src="https://img.085404.xyz/images/982dead1bc39852741568dbcb9524613.webp" alt="image.png" /></p>
<ul>
<li>执行周期/流水线周期： $3\triangle t$ （最长的那个时间）</li>
<li>首条指令执行时间：$2\Delta t + 1\Delta t + 3\Delta t + 1\Delta t + 2\Delta t = 9\Delta t$</li>
<li>流水线执行时间：首条指令执行时间 +  (指令数-1)*执行周期 = $9\triangle t + (n-1) 3\triangle t$ ，当 $n \rightarrow \infty$ 时，执行时间 = $3n\triangle t$</li>
<li>吞吐率： $\frac{1}{3\triangle t}$ （指令条数 $n$ / 流水线执行时间 $3n\triangle t$ ）</li>
<li>加速比：不采用流水线的时间 / 采用流水线的时间
<ul>
<li>不采用： $(2+1+3+1+2)\triangle t \times 10 = 90 \triangle t$</li>
<li>采用： $(2+1+3+1+2)\triangle t + (10-1) \times 3\triangle t = 36\triangle t$</li>
<li>加速比 = $90/36 = 5:2$</li>
</ul>
</li>
</ul>
<h3>指令系统</h3>
<p>RISC（精简）：<code>Reduced</code></p>
<ul>
<li>指令数量少、使用频率接近、定长格式</li>
<li>硬布线，<strong>硬件</strong></li>
<li>优化编译</li>
<li><strong>采用流水线</strong></li>
<li>采用<strong>很多</strong>寄存器</li>
</ul>
<p>CISC（复杂）：<code>Complex</code></p>
<ul>
<li>指令数量多、使用频率差别大、可变长格式</li>
<li><strong>微程序</strong>控制技术（微码）</li>
<li>研制周期长</li>
<li>不采用流水线</li>
<li>采用<strong>很少</strong>寄存器</li>
</ul>
<h3>寻址方式</h3>
<ol>
<li>立即寻址：<strong>操作数</strong>就在指令里，获取操作数的速度最快</li>
<li>直接寻址：<strong>操作数的地址</strong>在指令里</li>
<li>寄存器寻址：<strong>存放操作数的寄存器名字</strong>在指令里，执行阶段不用访问主存，<strong>执行速度快</strong></li>
</ol>
<h2>中断</h2>
<ul>
<li><strong>中断方式</strong>下，IO 系统与外设交换数据时，CPU 可以<strong>并行</strong>处理其他任务。</li>
<li>采用中断方式和 DMA 方式控制技术时，CPU 可以与外设并行工作。</li>
<li>管理键盘最适合的 IO 方式是<strong>中断</strong>，不是DMA</li>
</ul>
<h3>中断流程</h3>
<ol>
<li>IO 准备好后，发出中断请求</li>
<li>CPU 收到信号后，保存现场，转入执行 IO，完成后返回被打断的程序继续执行</li>
</ol>
<blockquote>
<p>保存现场、中断处理、恢复现场</p>
</blockquote>
<h3>中断向量</h3>
<p>系统中有多个中断源，用中断向量表保存各个中断服务程序的<strong>入口地址</strong></p>
<h3>DMA</h3>
<ul>
<li>Direct Memory Access，<strong>直接内存存取方式</strong>：数据在主存（内存）和 IO 设备（外设）间直接传送，<strong>不需要 CPU 干预</strong></li>
<li>硬件电路实现</li>
<li><strong>键盘</strong>适合 DMA</li>
<li>一个 DMA 传送只需要执行一个 DMA 周期，即<strong>一个总线读写周期</strong>
<blockquote>
<p>同类说法：
CPU 是在<strong>一个总线周期</strong>结束时响应 DMA 请求的</p>
</blockquote>
</li>
<li>DMA 传送结束事件会触发 CPU 请求，是<strong>中断</strong></li>
</ul>
<blockquote>
<p>[!tip] 总结
DMA 直接从内存读写数据，这个过程中 CPU 该干嘛干嘛，所以是并行的。但结束时，会通过中断的方式通知 CPU 活干完了，来查收。</p>
</blockquote>
<h2>总线</h2>
<h3>叙述</h3>
<ol>
<li>并行总线适合<strong>近距离</strong>高速数据传输</li>
<li>串行总线适合<strong>长距离</strong>数据传输</li>
<li>专用总线在设计上可以与连接设备实现最佳匹配</li>
<li>单总线结构的优点是控制简单方便，扩充方便，性能不高</li>
</ol>
<h3>单双工</h3>
<ul>
<li>单工：A 只能发信号，B 只能收信号（广播）</li>
<li>半双工：AB 都可发可收，但同一时间只能一个发一个收（对讲机）</li>
<li>全双工：AB 都可发可收，而且可以同时进行（电话）</li>
</ul>
<h3>PCI/SCSI</h3>
<ul>
<li>PCI 总线是<strong>并行内总线</strong>（PC 机）</li>
<li>SCSI 是<strong>并行外总线</strong>（软硬盘、扫描仪）</li>
</ul>
<h2>存储</h2>
<h3>Cache</h3>
<ol>
<li>Cache 不是主存，主存指的是内存条</li>
<li>Cache 是<strong>按照内容访问</strong>的存储器</li>
</ol>
<h4>主存与 Cache 的地址映射方式</h4>
<blockquote>
<p>Cache 与主存之间地址映射由<strong>硬件自动完成</strong></p>
</blockquote>
<ol>
<li><strong>全相联</strong>地址映射：主存的任意一块可以映象到 Cache 中的任意一块。</li>
<li><strong>直接相联</strong>映射：主存中一块只能映象到 Cache 的一个特定的块中</li>
<li><strong>组相联</strong>的映射：各区中的某一块只能存入缓存的同组号的空间内，但组内各块地址之间则可以任意存放。即从主存的组到 Cache 的组之间采用直接映象方式，在两个对应的组内部采用全相联映象方式。</li>
</ol>
<h3>RAM</h3>
<blockquote>
<p>[!tip]
RAM = 随机存储器
DRAM = 动态随机存储器 (Dynamic)
SRAM = 静态随机存储器（Static）</p>
</blockquote>
<p>随机存储器可以按地址访问存储器的任一单元。</p>
<p><strong>SRAM</strong>:</p>
<ul>
<li>不刷新，速度快</li>
<li>断电就信息丢失，不断电就不会丢</li>
<li>集成度低，功耗、体积大，成本高</li>
</ul>
<p><strong>DRAM</strong>（内存条）：</p>
<ul>
<li>存取速度慢</li>
<li>用电荷存信息（只能维持 1-2ms），周期性通电刷新</li>
<li>集成度高，功耗低</li>
<li>不刷新、关机，都会丢数据</li>
</ul>
<p><strong>FLASH</strong>可以不加电下长期保存数据（U 盘）</p>
<p><strong>EEPROM</strong>电擦除可编程只读存储器：可以突出，也可以通过电擦除进行改写</p>
<h3>磁盘</h3>
<p>磁盘是一种<strong>直接存储器</strong></p>
<h4>调度算法</h4>
<p>磁盘调度分为移臂调度（控制磁头的移动）和旋转调度（控制磁盘盘片的旋转）两类，并且是<strong>先进行移臂调度，然后进行旋转调度</strong>。</p>
<ul>
<li>移臂调度</li>
</ul>
<p><img src="https://img.085404.xyz/images/108ce6e25d3dae9451d842447dabbdd4.webp" alt="image.png" /></p>
<blockquote>
<p>[!tip]
前两者会随时改变移动臂的运行方向</p>
</blockquote>
<h4>处理时间</h4>
<p><img src="https://img.085404.xyz/images/d66b7e0098a54bc4ebc9f58678ae5568.webp" alt="image.png" /></p>
<ol>
<li>一圈 30ms，一圈有 10 块，=&gt; 读一块要 3ms</li>
<li>读出来的一块放入单缓冲区，处理要 6ms</li>
<li>读完并处理完第一块，一共 9ms，此时磁头已经过了 R2，到了 R4 的开始处，因此想读 R2 就得绕 8 块，<code>8*3=24ms</code> 到达 R2 的开头，然后读+处理 R2，一共 <code>24+9=33ms</code></li>
<li>R2 到 R10 都是如此，因此一共 <code>9*33=297ms</code></li>
<li>加上第一块的 9ms，一共 <code>297+9=306ms</code>（<strong>最长时间</strong>）</li>
</ol>
<p>重新排布，让 R1 读完处理完后，磁头到达的地方改成 R2，以此类推，则<strong>最短时间</strong>= <code>(3+6)*10=90ms</code></p>
<h4>raid 阵列</h4>
<p><img src="https://img.085404.xyz/images/e915209112daf00644005fbe37c972dd.webp" alt="image.png" /></p>
<p><img src="https://img.085404.xyz/images/3114b329919192316209a3ff86ffd7e7.webp" alt="image.png" /></p>
<p>raid5 有 N 块盘，只能用 N-1 块的容量，还有 1 块放校验信息</p>
<h3>计算芯片数</h3>
<blockquote>
<p>内存按字节编址，利用 $8K \times 4bit$ 的存储器芯片构成 84000H 到 8FFFFH 的内存，需要（）片？</p>
</blockquote>
<ol>
<li>8FFFFH-84000H+1=C000H（地址数）</li>
<li>按字节编址 =&gt; 每个地址是 8bit</li>
<li>内存大小=C000H * 8bit = $(12 \times 16^3 \times 8) bit = 48K \times 8bit$</li>
<li>$\frac{48K \times 8bit}{8K \times 4bit} = 12$ ，即 12 片</li>
</ol>
<h3>地址变换</h3>
<p><img src="https://img.085404.xyz/images/d2a3dbae849993debbe0671378031b14.webp" alt="Screenshot_2023-05-23-20-46-38-391_tv.danmaku.bil.png" /></p>
<blockquote>
<p>[!tip]
页面地址 = 页号 + 页内地址
物理地址 = 块号 + 页内地址</p>
</blockquote>
<p>页面大小 $4K = 2^{12}$ ，所以逻辑地址为 12 位。一个 16 进制位是 4 个 2 进制位，所以逻辑地址 25EFH (H 是 16 进制的标志)的后三位 <code>5EF</code> (3x4=12)为<strong>页内地址</strong>，即 <code>2</code> 是<strong>页号</strong>。</p>
<p>从表里查，页号 <code>2</code> 对应的<strong>块号</strong>是 <code>4</code> ，所以物理地址 <code>45EF</code> = <code>4</code> + <code>5EF</code></p>
<h3>位示图</h3>
<ul>
<li>字长：一个字包含多少个物理块</li>
</ul>
<blockquote>
<p>计算机系统字长为 64 位，磁盘容量 512GB，物理块大小为 4MB，则位示图大小为 (2048) 个字</p>
</blockquote>
<p>512G/4MB = $2^{17}$ 个物理块
$2^{17}$ / 64 = 2048 个字</p>
<blockquote>
<p><img src="https://img.085404.xyz/images/13d3876b976b5bfc292cb88cadd85a97.webp" alt="image.png" /></p>
</blockquote>
<p>2053 号块 = 第 2054 个块，字长 32 位 = 一个字 32 个物理块
=&gt; 2054/32 = 64 余 6，即占满了 0-63 号字，从 64 号字开始
=&gt; 余下 6 个物理块，即 64 号字中的 0-5 号位，该块占用
=&gt; 64 号字的位号 5 的位置”1“</p>
<h3>段页式存储</h3>
<p><img src="https://img.085404.xyz/images/0f9634cd4896eb650b82d2e3a8c3392d.webp" alt="image.png" /></p>
<p>31-22+1=10，21-12+1=10，$2^{10}=1024$</p>
<p>最多可有 1024 个段，每个段最大允许有 1024 个页，页的大小为 4K</p>
<h3>文件系统</h3>
<blockquote>
<p>[!question] 某文件系统文件存储采用文件索引|节点法。每个文件索引|节点中有8个地址项，每个地址项大小为4字节，其中5个地址项为直接地址索引，2个地址项是一级间接地址索引，1个地址项是二级间接地址索引。磁盘索引块和磁盘数据块大小均为1KB。若要访问iclsClient.d文件的逻辑块号分别为1、518，则系统应分别采用（）。</p>
</blockquote>
<ul>
<li>A：直接地址索引和直接地址索引</li>
<li>B：直接地址索引和一级间接地址索引</li>
<li>C：直接地址索引引和二级间接地址索引</li>
<li>D：一级间接地址索引和二级间接地址索引</li>
</ul>
<p>直接地址索引|有5个地址项，对应逻辑块号0-4。
一级间接索引|有2个地址项，每个地址项对应1Kb/4B=256个物理块，对应逻辑块号范围是5-516。
二级间接索引|有一个地址项，对应256×256=65536个物理块，对应逻辑块号范围是517以上。</p>
<h3>三种存储器</h3>
<ul>
<li>随机存储器：任何存储单元读写，访问时间相同</li>
<li>顺序存储器：访问数据所需时间与数据存储位置相关</li>
<li>直接存储器：介于上述两者之间，磁道寻址随机，磁道内寻址顺序</li>
</ul>
<h2>多线程</h2>
<p>进程创建了若干线程，则它们的<strong>栈指针不能共享</strong></p>
<h2>PV 操作</h2>
<p><img src="https://img.085404.xyz/images/8c2f1994506bab2445250f3a7dce54da.webp" alt="image.png" /></p>
<h2>安全</h2>
<h3>加密</h3>
<p>浏览器和服务器之间用于加密 HTTP 消息的方式：<strong>会话秘钥+公钥加密</strong></p>
<p>服务器证书被撤销：客户端无法再信任服务器</p>
<p>目前加密技术主要两类：</p>
<ol>
<li>基于<strong>对称</strong>密钥的加密算法（<strong>私钥</strong>加密算法）
<ul>
<li><code>DES</code> <code>AES</code> <code>IDEA</code> <code>RC4</code></li>
<li><strong>分组加密</strong>： <code>AES</code></li>
<li>流加密：用伪随机数序列作为密钥</li>
</ul>
</li>
<li>基于<strong>非对称</strong>密钥的加密算法（<strong>公钥</strong>算法）
<ul>
<li><code>RSA</code> <code>ECC</code></li>
</ul>
</li>
</ol>
<blockquote>
<p>[!tip] <code>MD5</code> <code>SHA</code> 属于消息摘要算法</p>
</blockquote>
<h3>数字证书</h3>
<table>
<thead>
<tr>
<th>证书</th>
<th>密码算法</th>
</tr>
</thead>
<tbody>
<tr>
<td>X.509</td>
<td>RSA</td>
</tr>
<tr>
<td>国密 SM2</td>
<td>ECC</td>
</tr>
</tbody>
</table>
<h3>安防</h3>
<ul>
<li>入侵防御系统包括入侵检测系统。所以直接可以阻断网络攻击行为，<strong>不需要联动入侵检测</strong></li>
<li>配置 <code>ACL</code> 可以阻止外部未授权用户访问内部网络</li>
<li>凡是带 <code>NAT</code> 字眼的，就是做<strong>地址转换</strong>的（Network Address Translation）</li>
<li>SQL 注入确实属于 web 攻击，但是不会被视为入侵，因为它本质上是正常的用户交互行为，只不过因为系统存在漏洞，所以会变成攻击</li>
<li><strong>防火墙类型</strong>：
<ul>
<li>包过滤防火墙：网络层</li>
<li>应用代理网关防火墙：应用层</li>
<li>状态检测防火墙：网络层</li>
</ul>
</li>
<li>防火墙受保护程度：外网 &lt; DMZ &lt; 内网</li>
</ul>
<h3>密码法</h3>
<p>《中华人民共和国密码法》规定国家对密码实行分类管理：<strong>核心密码、普通密码和商用密码</strong></p>
<h2>知识产权</h2>
<table>
<thead>
<tr>
<th>产权</th>
<th>期限</th>
</tr>
</thead>
<tbody>
<tr>
<td>著作权</td>
<td>终生+死后 50 年</td>
</tr>
<tr>
<td>署名权</td>
<td>无限</td>
</tr>
<tr>
<td>修改权</td>
<td>无限</td>
</tr>
<tr>
<td>保护作品完整权</td>
<td>无限</td>
</tr>
</tbody>
</table>
<ul>
<li>软件著作权自软件开发完成之日起<strong>自动产生</strong>，不存在生效一说</li>
<li>国际上保护计算机软件知识产权不受侵犯的主要方式：<strong>版权法</strong></li>
<li>著作权人死亡后，继承人可以按规定继承<strong>除了署名权以外的</strong>其他权利 (财产权利)</li>
<li>受委托的作品，著作权归属由双方通过合同约定。<strong>没得合同，或者合同没有明确规定，著作权归受托人</strong></li>
</ul>
<blockquote>
<p>说人话就是你委托外包干活，你不要著作权那就归外包所有</p>
</blockquote>
<ul>
<li><strong>专利申请</strong>，
<ul>
<li>不是同一天申请的，先来先得</li>
<li>同一天申请，自行协商</li>
</ul>
</li>
<li>软件著作权的两个基本法律文件：
<ul>
<li>《中华人民共和国著作权法》</li>
<li>《计算机软件保护条例》</li>
</ul>
</li>
</ul>
<h3>软件许可</h3>
<table>
<thead>
<tr>
<th>许可类型</th>
<th>范围</th>
</tr>
</thead>
<tbody>
<tr>
<td>独占许可</td>
<td>被授权方用，著作权人和第三方均不可用</td>
</tr>
<tr>
<td>独家许可</td>
<td>被授权方和著作权人能用，第三方不可用</td>
</tr>
<tr>
<td>普通许可</td>
<td>著作权人和任意多的被授权方都可用</td>
</tr>
</tbody>
</table>
<h2>数据流图</h2>
<p><img src="https://img.085404.xyz/images/7c1b370f513e88ffb9b352a4d8c80e54.webp" alt="image.png" /></p>
<p><strong>顶层数据流图</strong>，只包含一个表示整个系统的“加工”，即<strong>输入与输出</strong></p>
<h3>要注意的问题</h3>
<ol>
<li>图中每个数据流、加工、数据存储和外部实体<strong>都要命名</strong></li>
<li>一个加工不适合有过多的数据流</li>
<li>分解尽可能均匀</li>
</ol>
<blockquote>
<p>[!warning] 图中不一定要表示出控制流</p>
</blockquote>
<h3>平衡原则</h3>
<ol>
<li>
<p>父图与子图的平衡</p>
<ul>
<li>输入和输出的条数一致</li>
<li>方向也要一致</li>
</ul>
</li>
<li>
<p>子图内部的平衡：对于每一个加工，要有输入和输出</p>
<ul>
<li>黑洞：只有输入，没有输出（只进不出）</li>
<li>奇迹：只有输出，没有输入（无中生有）</li>
</ul>
</li>
</ol>
<h2>活动图</h2>
<p><a href="https://blog.085404.xyz/note/data-structure.html#%E5%85%B3%E9%94%AE%E8%B7%AF%E5%BE%84">最早、最晚时间计算</a></p>
<ul>
<li>也叫PERT图</li>
<li>可以描述任务间的以来关系，但不能清晰描述各任务之间的<strong>并行情况</strong></li>
</ul>
<p><img src="https://img.085404.xyz/images/9457bd4974d79aab20d5a11ecb7d8a38.webp" alt="image.png" /></p>
<ul>
<li><strong>关键路径</strong>：开始节点到结束节点之间，持续时间最长的路径 <code>ACEHJK</code> 。其长度就是把路径边值加起来的和 <code>23</code></li>
<li><strong>里程碑</strong>：就是图里的节点</li>
<li>关键路径上的<strong>任何活动逾期</strong>，都会影响关键路径的长度 (项目的进度)</li>
</ul>
<blockquote>
<p>[!tip] 所以如果有一个活动在关键路径上，问你：它晚开始几天不会影响项目进度，那答案不就是 0 天吗</p>
</blockquote>
<blockquote>
<p>[!warning]</p>
<ol>
<li>切记是总时间最长的路径，而不是每一步都选择最长的路径，这玩意不是按照贪心算法得到的！</li>
<li>关键路径不一定只有一条！</li>
</ol>
</blockquote>
<blockquote>
<p>[!question] 关键路径上的活动发生变化了，如何计算总时间？</p>
<ol>
<li>根据题意得到新的活动天数</li>
<li>根据新的活动天数，重新审视活动图，得到新的关键路径，以此计算新的时间</li>
</ol>
</blockquote>
<h2>项目管理</h2>
<ul>
<li>n 位成员的开发团队，一共有 $\frac {n (n-1)} 2$ 条<strong>沟通路径</strong></li>
<li><strong>概要设计</strong>：划分模块，确定功能与接口</li>
<li><strong>详细设计</strong>：算法、数据结构、代码、输入/输出格式、用户界面、数据库的物理设计等</li>
<li><strong>原型模型</strong>适用于“需求不明确、经常变化”的情况。</li>
<li>结构化分析方法是分析对象的<strong>数据流</strong>。</li>
<li><strong>软件评审</strong>是软件质量保证的主要活动之一。</li>
</ul>
<p><img src="https://img.085404.xyz/images/c178d3d156e028991000b27a95ed57d5.webp" alt="image.png" /></p>
<h3>瀑布模型</h3>
<ul>
<li><strong>不适合</strong>开发初期对软件需求缺乏准确全面认知的情况。</li>
<li>容易理解，管理成本低、需求明确、文档齐全、风险控制弱。</li>
</ul>
<h3>增量模型</h3>
<p><img src="https://img.085404.xyz/images/78f7ddaa7f14022ec397d48a853e96ca.webp" alt="image.png" /></p>
<p>开发过程划分为若干增量。第一个增量为<strong>核心功能</strong>。所以：</p>
<ul>
<li>快速提交可用产品/快速构造核心产品</li>
<li>让用户尽早熟悉系统</li>
<li>高优先级功能先交付</li>
</ul>
<blockquote>
<p>[!tip]
没有又快又稳的方法。你想稳就得慢，你想快，管理成本就会高</p>
</blockquote>
<h3>敏捷方法</h3>
<p><img src="https://img.085404.xyz/images/3db0fc08deffdd30475169b861de66a7.webp" alt="image.png" /></p>
<h4>敏捷统一过程 AUP</h4>
<ol>
<li><strong>在大型任务上连续，在小型任务上迭代</strong>
<blockquote>
<p>注意跟<strong>并列征求法区分</strong></p>
</blockquote>
</li>
<li>采用经典的 UP 阶段性活动，即<strong>初始、精化、构建和转换</strong>
<ol>
<li>起始阶段：起始阶段专注于项目的初创活动。</li>
<li>精化阶段：精化阶段在理解了最初的领域范围之后进行<strong>需求分析</strong>和<strong>架构演进</strong>。</li>
<li>构建阶段：构建阶段关注系统的构建，产生<strong>实现模型</strong>。</li>
<li>移交阶段：移交阶段关注于软件提交方面的工作，产生<strong>软件增量</strong>。</li>
</ol>
</li>
</ol>
<blockquote>
<p>[!tip] 怎么记？</p>
<ul>
<li>xp极限：极限操作，所以先测试</li>
<li>水晶方法：水晶是个多面体，不同面就是不同的项目，策略也不同</li>
<li>并列争求：都并列了，这么多一起来，那得分一个先后顺序（优先级）</li>
</ul>
</blockquote>
<h3>风险管理</h3>
<ul>
<li>回避风险</li>
<li>转移风险</li>
<li>减轻风险</li>
<li>接受风险并控制损失</li>
</ul>
<blockquote>
<p>[!bug] 风险不可消除（避免）。即使预测到风险，也只能提前做好预案，不一定能避免其发生。</p>
</blockquote>
<h4>ISO/IEC 软件质量模型</h4>
<p><img src="https://img.085404.xyz/images/451d9da09781d8bfa8100e75cd1094f6.webp" alt="image.png" /></p>
<h3>CMM</h3>
<p>CMM模型指“能力成熟度模型”(Capability Maturity Model for Software）。它是对于软件组织在定义、实施、度量、控制和改善其软件过程的实践中各个发展阶段的描述。CMM的核心是把<strong>软件开发视为一个过程</strong>，并根据这一原则对软件开发和维护进行过程监控和研究，以使其更加科学化、标准化，使企业能够更好地实现商业目标。CMM模型分为<strong>5个等级</strong>：①初始级，②可重复级，③已定义级，④定量管理级，⑤优化级。（<strong>按成熟度划分</strong>，1级最低，5级最高）</p>
<h3>基于构建的开发模型</h3>
<ol>
<li>指利用预先打包的构件来构造应用系统</li>
<li>构件可以是组织内部开发的构件，也可以是商品化成品软件构件</li>
<li>本质上是演化模型，需要以迭代方式构建软件</li>
</ol>
<h2>编码设计</h2>
<h3>绑定方式</h3>
<ul>
<li>静态绑定：编译器</li>
<li>动态绑定：运行时</li>
</ul>
<h3>面相对象设计</h3>
<ul>
<li>识别类及对象、定义属性、定义服务、识别关系、识别包</li>
<li>特殊多态：分为<strong>过载多态</strong>和<strong>强制多态</strong>两种
<ul>
<li>过载：同一个名字在不同上下文可代表不同的含义</li>
<li>强制：一种类型变量作为参数传递时，隐式转换为另一种类型</li>
</ul>
</li>
</ul>
<h3>耦合</h3>
<p><img src="https://img.085404.xyz/images/1dccd1bc1e801e1231e03b611bc5c308.webp" alt="image.png" /></p>
<ul>
<li><strong>内容耦合</strong>（危险的）：一个模块<strong>直接使用</strong>另一个模块的数据，或通过<strong>非正常入口</strong>转入另一个模块内部</li>
<li><strong>外部耦合</strong>：模块间没有直接关联，通过外部数据环境中的<strong>简单全局变量</strong>产生关联</li>
</ul>
<blockquote>
<p>[!tip] 公共耦合是通过<strong>复杂全局变量</strong></p>
</blockquote>
<h3>内聚</h3>
<p>7种内聚性由高到低：</p>
<ul>
<li>功能内聚：模块内所有元素完成一个<strong>单一功能</strong>，各个部分协同工作，<strong>缺一不可</strong>。</li>
<li>顺序内聚：处理元素相关，而且必须顺序执行。</li>
<li>通信内聚：模块内所有处理元素都在<strong>同一个数据结构</strong>上操作，或者各处理使用<strong>相同的输入数据</strong>或产生<strong>相同的输出数据</strong>。</li>
<li>过程内聚：一个模块完成多个任务，而且这些任务<strong>必须按特定的次序</strong>执行。</li>
<li>瞬时内聚(时间内聚)：所包含的任务必须在同一时间间隔内执行。</li>
<li>逻辑内聚：模块内执行若干个<strong>逻辑</strong>上相似的功能，<strong>通过参数确定</strong>该模块完成哪一个功能。</li>
<li>偶然内聚(巧合内聚)：完成一组没有关系或松散关系的任务。</li>
</ul>
<h3>设计模式</h3>
<ul>
<li><strong>创建型模式</strong>，共五种：工厂方法模式、抽象工厂模式、单例模式、建造者模式、原型模式。</li>
<li><strong>结构型模式</strong>，共七种：适配器模式、装饰器模式、代理模式、外观模式、桥接模式、组合模式、享元模式。</li>
<li><strong>行为型模式</strong>，共十一种：策略模式、模板方法模式、观察者模式、迭代子模式、责任链模式、命令模式、备忘录模式、状态模式、访问者模式、中介者模式、解释器模式。</li>
</ul>
<h4>责任链模式 Responsibility chain</h4>
<p>使多个对象都有机会处理请求，从而避免请求的发送者和接收者之间的耦合关系。责任链模式将这些对象连成一条链，沿着该链传递请求，直到有一个对象处理它为止。</p>
<p>属于<strong>行为型对象模式</strong>。适用于：</p>
<ol>
<li>有多个对象可以处理一个请求，具体由哪个处理，在运行时自动确定</li>
<li>想在不明确接收者的情况下，向多个对象中的某一个提交请求</li>
<li>可处理一个请求的对象集合应该被动态指定</li>
</ol>
<h4>中介者模式 Mediator</h4>
<p>使用一个中介对象封装一系列的对象交互，使各对象不需要显示的相互引用，从而使其耦合松散，可以独立地改变它们之间的交互，<strong>类似于“电话接线员”</strong>。</p>
<p>属于<strong>行为型对象模式</strong>。适用于：</p>
<ol>
<li>一组对象以定义良好但复杂的方式进行通信，产生的依赖关系结构混乱且难以理解</li>
<li>一个对象引用其他很多对象并且直接与这些对象通信，导致难以复用该对象</li>
<li>想定制一个分布在多个类中的行为，而又不想生成太多的子类</li>
</ol>
<h4>观察者模式 Observer</h4>
<p>定义对象之间的一种一对多依赖关系，使得每当一个对象改变状态，则其相关依赖对象皆得到通知并被自动更新。</p>
<ul>
<li>观察者（Observer）至少维护一个被观察对象（Subject）</li>
<li>一个被观察对象需要维护多个观察者</li>
<li>被观察对象需要通知观察者其自身的状态变化</li>
</ul>
<h4>策略模式 Strategy</h4>
<p>针对一组算法，将每一个算法封装到具有共同接口的独立的类中，从而使得它们<strong>可以相互替换</strong>。策略模式使得算法可以在不影响到客户端的情况下发生变化。</p>
<h4>装饰模式 Decorator</h4>
<p>装饰模式就是<strong>把要添加的附加功能分别放在单独的类中</strong>，并让这个类包含它要装饰的对象，当需要执行时，客户端就可以有选择地、按顺序地使用装饰功能包装对象。<strong>可以给对象动态地添加一些额外的职责，而不改变该对象的结构</strong>。</p>
<h4>适配器模式 Adaptor</h4>
<p>将一个类的接口转换成客户希望的另外一个接口。Adapter模式使得原本由于<strong>接口不兼容</strong>而不能一起工作的那些类可以一起工作。</p>
<h4>享元模式 Flyweight</h4>
<p>通过运用共享技术，有效地支持大量细粒度的对象。系统只使用少量的对象，而这些<strong>对象都很相似</strong>，状态变化很小，对象使用次数增多。</p>
<h4>访问者模式 Visitor</h4>
<p>用于表示一个作用于某对象结构中的各元素的操作，它使得用户可以在<strong>不改变各元素的类的前提下，定义作用于这些元素的新操作</strong>。访问者模式使得新的操作变得很容易，但在一定程度上破坏了封装性。</p>
<h3>设计原则</h3>
<p>类的设计原则主要有七个，包括：开闭原则、里氏代换原则、迪米特原则（最少知道原则）
单一职责原则、接口分隔原则、依赖倒置原则、组合聚合复用原则。</p>
<ul>
<li><strong>共同重用原则</strong>是指一个包中的所有类应该是共同重用的。如果重用了包中的一个类，那么就要重用包中的所有类。</li>
<li><strong>开放封闭原则</strong>，即软件实体（类、模块、函数等）应该是可以扩展的，即开放的。但是不可修改的，即封闭的。</li>
<li><strong>接口分离原则</strong>，是指不应该强迫客户依赖于他们不用的方法，接口属于客户，不属于它所在的类层次结构。即<strong>依赖于抽象，不依赖于具体</strong>，同时，在抽象级别，不应该有对于细节的依赖。</li>
<li><strong>共同封闭原则</strong>，是指包中的所有类对于同一类性质的变化应该是共同封闭的。一个变化若对包产生影响，则将对该包中的所有类产生影响，而对于其他的包不造成任何影响。</li>
<li><strong>开闭原则</strong>：一个软件实体应当对扩展开放，对修改关闭。</li>
<li><strong>里氏替换原则</strong>：子类型必须能够替换它们的基类型，即任何基类对象可以出现的地方，子类对象一定可以出现。</li>
<li><strong>依赖倒转原则</strong>：要依赖于抽象，不要依赖于具体。</li>
</ul>
<h2>系统架构</h2>
<h3>三层 C/S 架构</h3>
<p>将软件在逻辑上分成<strong>表示层、功能层、数据层</strong></p>
<ol>
<li>合理划分三层结构的功能，逻辑上保持相对独立，提高系统可维护、可扩展性</li>
<li>更灵活有效地选用软硬件平台和系统</li>
<li>各层可以并行开发，也可以选择各自最合适的开发语言</li>
</ol>
<h2>UML 建模</h2>
<ul>
<li>构件图：系统静态实现</li>
<li>活动图：将进程或其他计算的结构展示为计算内部一步步的控制流和数据流。</li>
<li>包图：模型本身分解而成的组织单元，以及它们之间的依赖关系</li>
<li><strong>静态结构</strong>：用例图、类图和包图</li>
<li>动态视图：活动图、状态图、序列图和协作图
<ul>
<li>交互视图：序列图、协作图</li>
</ul>
</li>
</ul>
<h3>用例图</h3>
<blockquote>
<p>[!question]
<code>include</code> 和 <code>extend</code> 有什么区别？</p>
</blockquote>
<p><code>include</code> 表示的是<strong>包含关系</strong>。其含义为：当一个用例中所使用的某种功能，而该功能已经被另外的用例所定义，则这两个用例就是包含与被包含的关系，由包含用例 <code>a</code> 指向被包含用例 <code>b</code> ： <code>a ---&lt;&lt;include&gt;&gt;---&gt; b</code></p>
<p><code>extend</code> 表示的是<strong>扩展关系</strong>。其含义为：如果一个用例明显混合了两种及以上的不同场景，即根据实际情况可能发生<strong>多种分支</strong>，则可以将这个用例分为一个基本用例和一个/多个扩展用例，由扩展用例 <code>b</code> 指向基本用例 <code>a</code> ： <code>a &lt;---&lt;&lt;extend&gt;&gt;--- b</code></p>
<hr />
<ul>
<li>用例执行有先后顺序：依赖关系</li>
<li>多个用例的重复功能抽取出来行成抽象用例。抽象用例与使用它的用例称为使用关系。</li>
<li><strong>泛化关系</strong>：表示一个用例是另一个用例的<strong>特殊</strong>情况。（容易和包含关系混淆，主要关注“特殊”这个关键词）</li>
</ul>
<h3>类图</h3>
<p>要求说明类图中的属性和方法时，简要列出名称即可，不需要加上数据类型。例如：</p>
<ul>
<li>属性：
<ul>
<li>部门编号、姓名、住址、......</li>
<li>方法：添加、创建、打开、......</li>
</ul>
</li>
</ul>
<h3>组合与聚合</h3>
<p>都是整体与部分的关系，都是<strong>关联关系</strong>的特殊种类，区别在于：</p>
<ol>
<li>组合：B 是 A 的一部分，但 A 消失后 B 也不存在，即<strong>强聚合</strong></li>
<li>聚合：B 是 A 的一部分，但没有 A，B 也可以独立存在</li>
</ol>
<blockquote>
<p>[!tip]
“碰巧聚在一起，分开也能各自安好”
“我们组成了一个团队，分开了，团队就没了”</p>
</blockquote>
<h3>序列图</h3>
<ul>
<li>序列图的基本元素：对象、生命线和消息</li>
</ul>
<h2>算法</h2>
<ul>
<li>n 个顶点，e 条弧
<ul>
<li>邻接表，广度优先，时间复杂度为 $O(n+e)$</li>
<li>邻接矩阵，广度优先， $O(n^2)$</li>
</ul>
</li>
<li>递归子程序分析属于<strong>自上而下</strong>的分析法</li>
</ul>
<h3>复杂度分析</h3>
<p>算法 A：$T(n)=8T(\frac n 2)+n^2$，算法 B：$T(n)=XT(\frac n 4)+n^2$
问：对充分大的 $n$，若要算法 B 比 A 快，$X$ 的最大值为多少？</p>
<p>对 A 分析如下：
$$
T(n)=8T(\frac n 2)+n^2 \implies T(\frac n 2) = 8T(\frac n 4) + \frac {n^2} {4}
$$</p>
<p>将 $T(\frac n 2)$ 带入左式得：</p>
<p>$$
T(n)=8(8T(\frac n 4) + \frac{n^2}{4})  + n^2= 64T(\frac n 4) + 3n^2
$$</p>
<p>当 $n$ 充分大时，$n^2$ 忽略不计，因此 $X \leq 63$ 即可 B 比 A 快。</p>
<h3>排序总结</h3>
<table>
<thead>
<tr>
<th>排序方法</th>
<th>时间复杂度 (平均)</th>
<th>时间复杂度 (最坏)</th>
<th>时间复杂度 (最好)</th>
<th>空间复杂度</th>
<th>稳定性</th>
</tr>
</thead>
<tbody>
<tr>
<td>插入排序</td>
<td>$O(n^2)$</td>
<td>$O(n^2)$</td>
<td>$O(n)$</td>
<td>$O(1)$</td>
<td>稳定</td>
</tr>
<tr>
<td><strong>希尔排序</strong></td>
<td>$O(n^{1.3})$</td>
<td>$O(n^2)$</td>
<td>$O(n)$</td>
<td>$O(1)$</td>
<td>不稳定</td>
</tr>
<tr>
<td>选择排序</td>
<td>$O(n^2)$</td>
<td>$O(n^2)$</td>
<td>$O(n^2)$</td>
<td>$O(1)$</td>
<td>不稳定</td>
</tr>
<tr>
<td><strong>堆排序</strong></td>
<td>$O(nlog_2n)$</td>
<td>$O(nlog_2n)$</td>
<td>$O(nlog_2n)$</td>
<td>$O(1)$</td>
<td>不稳定</td>
</tr>
<tr>
<td>冒泡排序</td>
<td>$O(n^2)$</td>
<td>$O(n^2)$</td>
<td>$O(n)$</td>
<td>$O(1)$</td>
<td>稳定</td>
</tr>
<tr>
<td><strong>快速排序</strong></td>
<td>$O(nlog_2n)$</td>
<td>$O(n^2)$</td>
<td>$O(nlog_2n)$</td>
<td>$O(nlog_2n)$</td>
<td>不稳定</td>
</tr>
<tr>
<td><strong>归并排序</strong></td>
<td>$O(nlog_2n)$</td>
<td>$O(nlog_2n)$</td>
<td>$O(nlog_2n)$</td>
<td>$O(n)$</td>
<td>稳定</td>
</tr>
<tr>
<td>计数排序</td>
<td>$O(n+k)$</td>
<td>$O(n+k)$</td>
<td>$O(n+k)$</td>
<td>$O(n+k)$</td>
<td>稳定</td>
</tr>
<tr>
<td>桶排序</td>
<td>$O(n+k)$</td>
<td>$O(n^2)$</td>
<td>$O(n)$</td>
<td>$O(n+k)$</td>
<td>稳定</td>
</tr>
<tr>
<td>基数排序</td>
<td>$O(n*k)$</td>
<td>$O(n*k)$</td>
<td>$O(n*k)$</td>
<td>$O(n+k)$</td>
<td>稳定</td>
</tr>
</tbody>
</table>
<ul>
<li>不稳定：<strong>快些选</strong>一<strong>堆</strong>好友来聊天（快速、希尔、选择、堆）</li>
</ul>
<blockquote>
<p>因为情绪不稳定，所以才要“快些选一堆好友来聊天”</p>
</blockquote>
<ul>
<li>时间快：<strong>快些归队</strong>（快速、希尔、归并、堆）
<ul>
<li>除了希尔，快、归、堆的时间复杂度都是 $O(nlog_2n)$</li>
<li>归并的空间复杂度是最高的</li>
</ul>
</li>
</ul>
<h3>折半查找</h3>
<ul>
<li>折半查找是一个分治算法</li>
<li>只能用于有序表</li>
<li>若表长为n，则时间复杂度为$O(log_n)$</li>
</ul>
<p><img src="https://img.085404.xyz/images/662000df9ea13800a0c858efeba67c83.png" alt="image" /></p>
<ol>
<li>先构造二叉查找树</li>
<li>ASL 成功：比 1 次找到的几个，比 2 次找到的几个，......，除以总共的点数</li>
<li>ASL 失败：比 1 次找不到的几个，比 2 次找不到的几个，......，除以总共找不到的个数</li>
</ol>
<blockquote>
<p>[!warning]
查找长度就是从根结点开始的<strong>比较次数</strong>，不要去数树的深度！</p>
</blockquote>
<h3>问题求解</h3>
<ul>
<li>贪心算法：活动安排、01背包、分数背包（部分背包）</li>
<li>分治法：二分搜索、棋盘覆盖</li>
<li>动态规划：矩阵连乘、最长公共子序列</li>
<li>回溯算法：n 后问题、01背包</li>
<li>分支限界：布线问题</li>
</ul>
<h2>软件测试</h2>
<ul>
<li>软件测试的<strong>目标</strong>来自于需求分析阶段</li>
</ul>
<h3>白盒测试</h3>
<ul>
<li>流程图有 n 个判断节点，就需要 2n 个测试用例才能覆盖所有路径</li>
</ul>
<blockquote>
<p>当然，也可以手动数</p>
</blockquote>
<ul>
<li>各种覆盖方法中，<strong>语句覆盖是最弱的</strong></li>
<li>测试原则：
<ul>
<li>程序模块中的所有独立路径至少执行一次</li>
<li>在所有的逻辑判断中，取“真”和取“假”的两种情况至少都能执行一次</li>
<li>每个循环都应在边界条件和一般条件下各执行一次</li>
<li>测试程序内部数据结构的有效性等</li>
</ul>
</li>
</ul>
<h3>McCabe 计算程序复杂度</h3>
<p><img src="https://img.085404.xyz/images/079bf1c369b63ad1166dec8eb692dc43.webp" alt="image.png" /></p>
<ol>
<li>数流程图中的<strong>闭环</strong>数</li>
<li>闭环数+1 即为复杂度</li>
</ol>
<h3>面向对象软件的测试</h3>
<ol>
<li>算法层：测试<strong>类</strong>中定义的每个方法。</li>
<li>类层：测试<strong>封装在同一个类中</strong>的所有方法与属性之间的相互作用。</li>
<li>模板层：测试<strong>一组协同工作的类</strong>之间的相互作用。</li>
<li>系统层：把<strong>各个子系统</strong>组装成完整的面向对象软件系统，在组装过程中同时进行测试。</li>
</ol>
<h2>软件设计</h2>
<p>体系结构设计、接口设计、数据设计、过程设计。</p>
<h2>四种类型的软件维护</h2>
<p><img src="https://img.085404.xyz/images/56cba5e9e94bd3b7a5e4b9363639376a.webp" alt="1684670277356.jpg" /></p>
<blockquote>
<p>完善性 == 改善性</p>
</blockquote>
<h2>Linux</h2>
<ul>
<li>用 Apache 作为服务器时，默认的 Web 站目录为 <code>/var/log/httpd</code></li>
</ul>
<h2>计算机网络</h2>
<h3>协议</h3>
<ul>
<li>SNMP：简单网络管理协议，使用 UDP，没有安全设计</li>
<li>ICMP：控制报文协议</li>
<li>电子邮件
<ul>
<li>安全的协议：PGP</li>
<li>发送：SMTP（端口25）</li>
<li>接受：POP3（端口110）</li>
<li>交互式邮件存取协议：IMAP（端口143）</li>
</ul>
</li>
<li>CSMA/CD 协议：基带冲突检测的载波监听多路访问技术</li>
<li>Telnet：登录协议，基于 TCP，未加密</li>
<li>IPSec：IP 安全协议，对 IP 数据报进行加密</li>
<li>PPTP：点对点隧道协议，用于封装点对点协议（PPP）的数据包</li>
<li>ARP：将IP解析成MAC，反之则为RARP</li>
<li>DHCP：动态IP分配</li>
</ul>
<h3>端口</h3>
<table>
<thead>
<tr>
<th>应用</th>
<th>协议</th>
<th>端口</th>
<th>功能</th>
</tr>
</thead>
<tbody>
<tr>
<td>FTP</td>
<td>TCP</td>
<td>20(数据),21(控制)</td>
<td></td>
</tr>
<tr>
<td>TFTP</td>
<td>UDP</td>
<td>69</td>
<td>简单文件传输</td>
</tr>
<tr>
<td>SMTP</td>
<td>TCP</td>
<td>25</td>
<td>邮件发送</td>
</tr>
<tr>
<td>POP3</td>
<td>TCP</td>
<td>110</td>
<td>邮件收取</td>
</tr>
<tr>
<td>Telnet</td>
<td>TCP</td>
<td>23</td>
<td></td>
</tr>
<tr>
<td>DNS</td>
<td>UDP</td>
<td>53</td>
<td></td>
</tr>
<tr>
<td>DHCP</td>
<td>UDP</td>
<td>67</td>
<td>动态 IP 配置</td>
</tr>
<tr>
<td>SNMP</td>
<td>UDP</td>
<td>161</td>
<td>网络管理</td>
</tr>
</tbody>
</table>
<h3>IP 地址</h3>
<blockquote>
<p>某公司网络的地址是 <code>192.168.192.0/20</code>，要分成 32 个子网，则子网掩码为( )？每个子网可分配的主机地址数是( )？</p>
</blockquote>
<p><code>192.168.192.0/20</code> 为 IP-CIDR 格式，<code>/20</code> 表示前 20 位不变，即<strong>网络号</strong>。
$32 = 2^5$，因此需要 5 位表示<strong>子网号</strong>。
综上，子网掩码一共占据 20+5=25 位，为 <code>255.255.255.128</code>。</p>
<p>32-25=7 位，因此<strong>主机号</strong>为 7 位。
<code>0.0.0.0</code>（本地地址） 和 <code>255.255.255.255</code>（广播地址）不可作为主机号，因此每个子网可分配的主机地址数为 $2^7-2=126$ 个。</p>
<h3>报文</h3>
<ul>
<li>报文交换不能用于语音数据传输</li>
</ul>
<h3>OSI 参考模型</h3>
<ul>
<li>VLAN tag 在<strong>数据链路层</strong>实现</li>
<li>数据链路层可保障物理线路提供可靠的数据传输</li>
<li><strong>表示层</strong>对数据表示进行处理，如数据压缩、加密解密等。</li>
</ul>
<p><img src="https://pic3.zhimg.com/80/v2-db4b32b92d1ebc2cab4c8d06c529e99e_720w.webp" alt="osi" /></p>
<p><img src="https://img.085404.xyz/images/ff0749361a5af0ed825b99f9864a5fa0.webp" alt="image.png" /></p>
<h3>设备</h3>
<ul>
<li>集线器（Hub）在物理层，交换机在数据链路层，路由器在网络层</li>
<li>网络层设备（路由器）既<strong>可以</strong>隔离冲突域，又<strong>可以</strong>隔离广播域；
数据链路层设备（交换机、网桥）<strong>可以</strong>隔离冲突域，<strong>不能</strong>隔离广播域；
物理层设备（中继器、集线器）<strong>无法隔离</strong>冲突域和广播域。</li>
<li><strong>特性</strong>：
<ul>
<li>电气特性：规定传输二进制位时，线路上信号的电压高低、阻抗匹配、传输速率和距离限制等</li>
<li>功能特性：指明某条线路上出现的某一电平表示的<strong>意义</strong>，以及接口部件的信号线的<strong>用途</strong></li>
</ul>
</li>
</ul>
<h2>计算机可靠性</h2>
<h3>平均时间</h3>
<ul>
<li><code>MTTF</code> ：Mean Time to Failure，平均无故障时间</li>
<li><code>MTTR</code> ：Mean Time to Repair，平均修复时间</li>
<li><code>MTBF</code> ：Mean Time Between Failure，平均故障间隔时间</li>
</ul>
<pre><code>MTBF = MTTF + MTTR
可靠性 = MTTF / (1 + MTTF)
可用性 = MTBF / (1 + MTBF)
可维护性 = 1 / (1 + MTTR)
</code></pre>
<h3>计算可靠性</h3>
<ul>
<li>串联系统：$R = R_1 \times R_2 \times ... \times R_n$
<img src="https://img.085404.xyz/images/28cc8798e2f0f87b82801e768c4b3984.webp" alt="image.png" /></li>
<li>并联系统：$R = 1-(1-R_1) \times (1-R_2) \times ...\times (1-R_n)$
<img src="https://img.085404.xyz/images/a8b11610f75319e72e3ad228a07b9920.webp" alt="image.png" /></li>
</ul>
<h2>数据库相关</h2>
<h3>设计</h3>
<ul>
<li>ER图用于<strong>数据建模</strong></li>
<li>概念结构设计阶段的工作步骤：抽象数据 -&gt; 设计局部视图 -&gt; 合并取消冲突 -&gt; 修改重构消除冗余</li>
</ul>
<p><img src="https://img.085404.xyz/images/54bf836e49e711a1fbe1e7f40138c9c4.webp" alt="image.png" /></p>
<h3>三级模式</h3>
<ul>
<li>数据库系统中的<strong>视图、存储文件和基本表</strong>分别对应数据库系统结构中的<strong>外模式、内模式、模式</strong>，或者说对应<strong>用户视图、内部视图、概念视图</strong></li>
<li>创建聚簇索引，属于物理数据修改，所以改变的是数据库的内模式</li>
</ul>
<h3>范式</h3>
<p><img src="https://img.085404.xyz/images/d3260cc81ef7f18e1edb8f14b8a06f08.png" alt="image.png" /></p>
<blockquote>
<p>[!tip]
数据库最高范式就是为了消除冗余和更新异常，换言之，只要你没到最高，就是有冗余和更新异常的</p>
</blockquote>
<h3>SQL</h3>
<ul>
<li>视图在数据字典中保存的是<strong>视图定义</strong></li>
</ul>
<h3>事物</h3>
<ul>
<li>COMMIT：事务<strong>提交</strong>。该操作表示事务<strong>成功的结束</strong>，它将通知事务管理器将该事务的所有更新操作现在可以被提交或永久保留</li>
<li>ROLLBACK：事务<strong>回滚</strong>。该操作表示事务<strong>非成功地结束</strong>，它将通知事务管理器出故障了，数据库可能处于不一致状态，该事务的所有更新操作必须回滚或撤销。</li>
<li>COMMIT 产生的影响无法用 ROLLBACK 撤销</li>
</ul>
<h2>编程相关</h2>
<h3>源码执行流程</h3>
<ol>
<li>词法分析：识别出一个个的单词，删掉无用的信息，报告分析时的错误。</li>
<li>语法分析：将单词划分为语法单位（如表达式、赋值、循环等），确定其语法的正确性（同义说法：正确的逻辑结构）</li>
<li>语义分析：检查是否存在语义错误，例如<strong>类型信息、表达式的除数是否为零等。</strong></li>
<li>优化：对前阶段的中间代码进行变换或改造</li>
<li>目标代码生成：优化后的中间代码变换成指令代码或汇编代码</li>
</ol>
<ul>
<li>编译器会生成用户源程序的目标代码，而解释器则不产生目标代码。</li>
<li>目标代码经<strong>链接</strong>后产生可执行代码，可执行代码可独立加载运行，与源程序和编译程序都不再相关。</li>
<li>而在解释方式下，<strong>在解释器的控制下</strong>执行源程序或其中间代码，因此相对而言，用户程序<strong>执行的速度更慢</strong>。</li>
<li>中间代码生成和优化<strong>不是</strong>编译过程中必需的阶段。</li>
<li>对用户源程序依次进行了词法分析、语法分析和语义分析后，原则上就可以产生目标代码了，只是目标代码的质量和效率可能不够高。</li>
<li>词法分析时编译或解释用户源程序过程中<strong>唯一与源程序打交道的阶段</strong>，其主要功能是按顺序分析出源程序的<strong>记号</strong>。</li>
</ul>
<h3>语义</h3>
<p>程序设计语言的语义分为<strong>静态语义</strong>和<strong>动态语义</strong>。</p>
<ul>
<li>静态语义分析方法是<strong>语法制导翻译</strong>，其基本思想是将语言结构的语义以属性的形式赋予代表此结构的文法符号，而属性的计算以语义规则的形式赋予文法的产生式。</li>
</ul>
<h3>编译错误</h3>
<p><img src="https://img.085404.xyz/images/4f4aaf448068d6b48a980b5b23b2cf33.webp" alt="image.png" /></p>
<h3>中间代码</h3>
<ul>
<li>中间代码的作用是可使程序的结构在逻辑上更为简单明确，特别是可使目标代码的优化比较容易实现。</li>
<li>中间代码有多种形式，常见的有<strong>逆波兰</strong>记号(后缀式)、<strong>四元式</strong>和<strong>三元式</strong>(三地址码)</li>
<li>与具体的机器无关，不依赖于具体的计算机。</li>
</ul>
<h3>汇编语言</h3>
<ul>
<li><strong>汇编程序</strong>的功能是将<strong>汇编语言源程序</strong>翻译为相应的目标程序</li>
<li>用汇编语言编写的程序<strong>必须经过汇编程序翻译</strong>成计算机所能识别的机器语言程序(即目标程序)后，才能被计算机执行。</li>
<li>汇编语言是低级程序设计语言</li>
<li>汇编语言与计算机硬件体系结构密切相关</li>
</ul>
<h3>命名对象</h3>
<p>需要用户定义的标识符为程序中的对象命名，这个叫命名对象。所以常见的命名对象包括：变量、函数、数据类型。<strong>关键字（或保留字）不属于命名对象</strong>。</p>
<h3>多态</h3>
<p>多态分为两种：<strong>通用的多态</strong>和<strong>特定的多态</strong>。两者的区别是：</p>
<ul>
<li>前者对工作的类型不加限制，允许对不同类型的值执行相同的代码；</li>
<li>后者只对有限数量的类型有效，而对不同类型的值可能要执行不同的代码。</li>
</ul>
<p>通用的多态：</p>
<ul>
<li><strong>参数多态</strong>（Parametric）：采用参数化模板，通过给出不同的类型参数，使得一个结构有多种类型。</li>
<li><strong>包含多态</strong>（lnclusion）：同样的操作可用于一个类型及其子类型（注意是子类型，不是子类）。包含多态一般需要进行运行时的类型检查。</li>
</ul>
<p>特定的多态：</p>
<ul>
<li><strong>过载多态</strong>（Overloading）：同一个名（操作符、函数名）在不同的上下文中有不同的类型，程序设计语言中基本类型的大多数操作符都是过载多态的。</li>
<li><strong>强制多态</strong>（Coercion）：编译程序通过语义操作，把操作对象的类型强行加以变换，以符合函数成操作符的要求。程序设计语言中基本类型的大多数操作符，在发生不同类型的数据进行混合运算时，编译程序一般都进行强制多态。</li>
</ul>
<p>多态（Polymorphism）是指<strong>同一个方法调用</strong>可以根据<strong>对象的不同</strong>而表现出<strong>不同的行为</strong>，包括编译时多态和运行时多态。在编译时多态中，主要体现为方法重载；在运行时多态中，主要体现为方法覆盖。</p>
<ul>
<li>方法重载（Overload）是指在一个类中定义多个<strong>名称相同而参数表不同</strong>的方法。在编译时确定调用哪个方法。重载属于<strong>编译时多态</strong>，是<strong>静态多态性</strong>的一种体现。</li>
<li>方法覆盖（Override）是指子类重新定义父类中已定义的方法，子类可以在覆盖的方法中实现自己的逻辑。覆盖通过<strong>动态绑定机制</strong>实现多态，是<strong>运行时多态性</strong>的一种体现。</li>
</ul>
<h3>Java</h3>
<ul>
<li><strong>即时编译</strong></li>
<li>变量在<strong>栈空间</strong>分配</li>
<li>对象在<strong>堆空间</strong>分配</li>
</ul>
<h4>接口</h4>
<ul>
<li>接口中每一个方法都是隐式抽象的，会被隐式的指定为 <strong>public abstract</strong>（只能是 public abstract，其他修饰符都会报错）。</li>
<li><code>abstract</code> 不是返回值，接口中的方法返回值该怎么定义还是怎么定义。</li>
<li>接口中可以含有变量，但是接口中的变量会被隐式的指定为 <strong>public static final</strong> 变量（并且只能是 public，用 private 修饰会报编译错误）。</li>
<li>接口中的方法是不能在接口中实现的，只能由实现接口的类来实现接口中的方法。</li>
<li><code>A implements B</code>，注意看后续方法中所需的参数到底是 A 还是 B（抽象还是具体），如果需要的是B（抽象），那么声明参数对象时为 <code>B b = new A()</code></li>
</ul>
<h3>C</h3>
<ul>
<li>无返回值的函数，必须声明 <code>void</code>：<code>void func();</code></li>
<li>无参数，参数列表可以为空，也可以声明 <code>void</code>：<code>int func(void); // int func();</code></li>
<li>在C/C++中，非静态局部变量分配在栈区</li>
</ul>
]]></content>
    <author><name>Sun2ot</name></author>
    <category term="笔记"/>
  </entry>
  <entry>
    <title>ACME 托管证书</title>
    <link href="https://blog.085404.xyz/posts/acme-%E6%89%98%E7%AE%A1%E8%AF%81%E4%B9%A6/" rel="alternate" type="text/html"/>
    <id>https://blog.085404.xyz/posts/acme-%E6%89%98%E7%AE%A1%E8%AF%81%E4%B9%A6/</id>
    <published>2023-07-19T00:00:00.000Z</published>
    <updated>2023-07-19T00:00:00.000Z</updated>
    <summary></summary>
    <content type="html"><![CDATA[<h1>ACME 托管证书</h1>
<h2>安装 acme. sh</h2>
<pre><code>curl https://get.acme.sh | sh -s email=my@example.com
</code></pre>
<p>acme. sh 将被安装到 <code>~/.acme.sh/</code> 路径下，且<strong>安装过程不会污染已有的系统任何功能和文件</strong>，所有的修改都限制在安装目录中。</p>
<h2>生成证书</h2>
<blockquote>
<p>这里只推荐通过 DNS API 进行生成</p>
</blockquote>
<p>下面以腾讯云 DNSPod 为例，其他的如阿里云、cloudflare 等，参见 <a href="https://github.com/acmesh-official/acme.sh/wiki/%E8%AF%B4%E6%98%8E">wiki 说明</a></p>
<pre><code>export DP_Id="123456"

export DP_Key="asdf123456"

# 这里是申请泛域名，也可以根据实际需求申请单域名
./acme.sh --issue --dns dns_dp -d "your-domain.com" -d "*.your-domain.com"
</code></pre>
<p>剩下的就不用操心了， <code>acme.sh</code> 会帮我们搞定。最后，会输出证书相关文件所处的路径，按需使用即可。</p>
<pre><code>[Wed 19 Jul 2023 07:03:27 AM EDT] Your cert is in: /root/.acme.sh/your-domain.com_ecc/your-domain.com.cer
[Wed 19 Jul 2023 07:03:27 AM EDT] Your cert key is in: /root/.acme.sh/your-domain.com_ecc/your-domain.com.key
[Wed 19 Jul 2023 07:03:27 AM EDT] The intermediate CA cert is in: /root/.acme.sh/your-domain.com_ecc/ca.cer
[Wed 19 Jul 2023 07:03:27 AM EDT] And the full chain certs is there: /root/.acme.sh/your-domain.com_ecc/fullchain.cer
</code></pre>
<h2>查看已安装证书</h2>
<pre><code>acme.sh --info -d example.com
</code></pre>
<h2>更新 acme. sh</h2>
<p>目前由于 acme 协议和 letsencrypt CA 都在频繁的更新, 因此 acme. sh 也经常更新以保持同步.</p>
<p>升级 acme.sh 到最新版 :</p>
<pre><code>acme.sh --upgrade
</code></pre>
<p>如果你不想手动升级，可以开启自动升级:</p>
<pre><code>acme.sh --upgrade --auto-upgrade
</code></pre>
<p>之后, acme. sh 就会自动保持更新了。</p>
<p>你也可以随时关闭自动更新:</p>
<pre><code>acme.sh --upgrade --auto-upgrade  0
</code></pre>
<h2>手动更新证书</h2>
<p>先更新一次 acme 脚本</p>
<pre><code>acme.sh --upgrade
</code></pre>
<p>然后更新证书</p>
<pre><code>acme.sh --renew -d 域名
</code></pre>
<p>也可以一次性更新所有证书</p>
<pre><code>acme.sh --renew-all
</code></pre>
<p>如果证书没有过期，需要强制更新，使用 <code>--force</code></p>
<pre><code>acme.sh --renew -d 域名 --force
</code></pre>
<blockquote>
<p>[!warning]
acme 更新后，别忘了更新 cdn 的证书。</p>
</blockquote>
<p>如果发现证书已经更新了，但网页访问仍然提示过期，不妨重启 nginx 试试。</p>
<h2>自定义脚本</h2>
<p>有时我们希望在 <code>acme.sh</code> 更新证书后，执行一些新的操作，例如部署或是上传证书。</p>
<p>这可以在 <code>--reloadcmd</code> 参数中指定：</p>
<pre><code>acme.sh --issue -d example.com --dns dns_cf --reloadcmd "/path/to/your_custom_script.sh"
</code></pre>
<p>如果你已经配置了 <code>acme.sh</code> 来自动更新证书，也可以直接编辑 <code>~/.acme.sh/</code> 目录下的配置文件 <code>account.conf</code>，在文件末尾添加如下内容：</p>
<pre><code>Le_ReloadCmd="/path/to/your_custom_script.sh"
</code></pre>
<p>这样，在每次更新证书完成后时，<code>acme.sh</code> 都会自动执行你指定的脚本。</p>
<blockquote>
<p>请确保自定义的脚本具有执行权限，并且可以正确处理所需的操作。</p>
</blockquote>
]]></content>
    <author><name>Sun2ot</name></author>
    <category term="教程"/>
  </entry>
  <entry>
    <title>隐马尔可夫模型 HMM</title>
    <link href="https://blog.085404.xyz/posts/%E9%9A%90%E9%A9%AC%E5%B0%94%E5%8F%AF%E5%A4%AB%E6%A8%A1%E5%9E%8B-hmm/" rel="alternate" type="text/html"/>
    <id>https://blog.085404.xyz/posts/%E9%9A%90%E9%A9%AC%E5%B0%94%E5%8F%AF%E5%A4%AB%E6%A8%A1%E5%9E%8B-hmm/</id>
    <published>2023-07-18T00:00:00.000Z</published>
    <updated>2023-07-18T00:00:00.000Z</updated>
    <summary></summary>
    <content type="html"><![CDATA[<h2>基本概念</h2>
<p><img src="https://img.085404.xyz/images/7905df32f224f8277421b04dd782caa4.webp" alt="image.png" /></p>
<p>$q$ 为隐状态， $y$ 为观测值。上图描述的是这样一件事情：假设在某个系统中，按照时间序列可以分为若干时间节点。在每一个时刻，我们可以通过观测得到一个结果（观测值 $y$ ），相应的，该时刻也存在一个隐状态 $q$ 。但这个观测值/隐状态发生的概率，和非常多的因素都有关系，例如之前时刻的历史观测值、历史状态、还有当前时刻的状态，因此非常难以求解。</p>
<p><strong>隐状态</strong>，顾名思义，是一个我们是无法观测到的、隐藏起来的状态值。例如对于当前的股市，到底是牛市还是低谷，谁也不知道。因为并没有一个明确的指标，或者某个事物可以直接观测股市所处的状态，但股市的状态显然会影响到股价，因此“牛市 or 低谷”就是一个隐状态，而“股价”就是我们的观测值。</p>
<p>基于此，我们做出如下两个定义：</p>
<ul>
<li><strong>转移概率</strong>（transition probability）： $P(q_t|q_{t-1})$ ，从上一个时刻  t-1 时的状态，转移到当前时间 t 所处的状态的概率</li>
<li><strong>发射概率</strong>（emission probability）： $P(y_t|q_t)$ ，已知当前隐状态，观测时的概率</li>
</ul>
<h2>两个假设</h2>
<p><strong>齐次马尔可夫假设</strong>：任意时刻的隐状态只依赖前一时刻的隐状态，与其他时刻的状态和观测值无关即
$$
P(q_t|q_1,...q_{t-1},y_1,...,y_{t-1}) = P(q_t|q_{t-1})
$$
Discrete  Transition Probability</p>
<p><strong>观测独立性假设</strong>：任意时刻的观测值只依赖当前时刻的隐状态，与其他状态及观测值无关即
$$
P(y_t|q_1,...q_{t-1},q_t,y_1,...,y_{t-1}) = P(y_t|q_t)
$$
Continous/Discrete Measurement probability</p>
<p>从同一个状态 $q_{t-1}$ 转移到其他状态时，所有的转移概率，加起来和应该为 1。</p>
<blockquote>
<p>这两个假设的作用，就是为了简化上文中，那个“和非常多的因素都有关系”的概率计算问题。</p>
</blockquote>
<h2>隐马尔可夫模型三要素</h2>
<p>$$
P(y_1,y_2,y_3)=\displaystyle{\sum_{q_1=1}^k \sum_{q_2=1}^k \sum_{q_3=1}^k P(y_1,y_2,y_3,q_1,q_2,q_3)}
$$</p>
<p>这个等式是基于<strong>全概率公式</strong>的推导。假设有三个事件 y1、y2和 y3，以及三个条件事件（隐状态） q1、q2和 q3。根据全概率公式，我们可以将事件 y1、y2和 y3的联合概率表示为对条件事件 q1、q2和 q3的求和。</p>
<p>首先，我们可以将向事件 y1、y2和 y3 <strong>添加隐状态</strong> q1、q2和 q3：</p>
<pre><code>p(y1, y2, y3) = p(y1, y2, y3, q1, q2, q3)
</code></pre>
<p>然后，根据全概率公式，我们可以将联合概率表示为对其所有可能取值的求和。在这个特定的情况下，条件事件 q1、q2和 q3<strong>都有 k 个可能的取</strong>值，因此我们需要对 q1、q2和 q3 分别进行求和。</p>
<p><strong>这里的求和符号 $\sum$ 表示对所有可能取值的求和！！！</strong></p>
<blockquote>
<p>[!warning]
切记这个设定，不然你下面推导的时候，会一直怀疑人生：“ $q_t$ 为什么一定是从 1 到 K 的整数取值？”</p>
</blockquote>
<blockquote>
<p>注意，下文中的很多符号，都会和你们最容易搜索到的（李航《统计学习方法》）有所不同，目的是为了简化符号表达，便于理解。但其本质都是类似的，建议可以对比学习一下。</p>
</blockquote>
<p>其中，</p>
<p>$$
\begin{align}
&amp;P(y_1,y_2,y_3,q_1,q_2,q_3) \ \tag1
=&amp;P(y_3|y_1,y_2,q_1,q_2,q_3) \times P(y_1,y_2,q_1,q_2,q_3) \ \tag2
=&amp;P(y_3|q_3) \times P(q_3|y_1,y_2,q_1,q_2) \times P(y_1,y_2,q_1,q_2) \ \tag3
=&amp;P(y_3|q_3) \times P(q_3|q_2) \times P(y_1,y_2,q_1,q_2) \
=&amp;\dotsb
\end{align}
$$</p>
<p>这一段做一些说明。（1）到（2）和（2）到（3）分别使用了 HMM 的<strong>观测独立性假设</strong>和<strong>齐次马尔可夫假设</strong>。等推导出（3）时，不难发现，式子进入了迭代状态，或者说剩下的 $P(y_1,y_2,q_1,q_2)$ 可以利用上面的操作如法炮制。总之，最后可以化简为下式</p>
<p>$$
P(y_3|q_3) \times P(q_3|q_2) \times P(y_2|q_2) \times P(q_2|q_1) \times P(y_1|q_1) \times P(q_1)
$$</p>
<blockquote>
<p>规定 $P (q_1|q_0) = P(q_1)$</p>
</blockquote>
<p>其中， $P(y_t|q_t)$ 的概率是已知的，即发射概率矩阵 B（由本文开头定义的那个发射概率所组成的一个矩阵，转移概率矩阵与之同理）； $P(q_t|q_{t-1})$ 的概率也已知，即转移概率矩阵 A。那 $P(q_1)$ 是多少呢？</p>
<blockquote>
<p>对于转移概率矩阵 A， $P(q_t|q_{t-1})$ 的条件和概率其实分别给出了矩阵对应的行和列。例如 $P(q_t=j|q_{t-1}=i)$ 对应的就是矩阵 A 的 i 行 j 列取值，即 $a_{i,j}$ 。</p>
</blockquote>
<p><strong>隐马尔可夫模型三要素</strong>，The HMM Parameter $\lambda$ contains：</p>
<p>$$
\lambda = {A,B,\pi}
$$</p>
<p>其中， $\pi$ 就是初始状态概率向量 $\pi_i = P (q_1=i_{1,2,...,K})$ ，或者叫初始概率分布，也就是上面式子中最后一个未知项 $P(q_1)$ 。</p>
<p>所以进一步，我们可以总结一下</p>
<p>$$
\begin{align}
P(Y|\lambda) &amp;= \displaystyle{\sum_Q[P(Y,Q|\lambda)]} \</p>
<p>&amp;= \displaystyle{\sum_{q_1=1}^k \dotsb \sum_{q_T=1}^k}[P(y_1,...,y_{T},q_1,...q_T|\lambda)] \</p>
<p>&amp;= \displaystyle{\sum_{q_1=1}^k \dotsb \sum_{q_T=1}^k}[P(y_1,...,y_{T},q_0,q_1,...q_T|\lambda)] \</p>
<p>&amp;= \displaystyle{\sum_{q_1=1}^k \dotsb \sum_{q_T=1}^k} P(q_1)P(y_1|q_1)P(q_2|q_1) \dotsb P(q_t|q_{t-1})P(y_t|q_t) \</p>
<p>&amp;= \displaystyle{\sum_{q_1=1}^k \dotsb \sum_{q_T=1}^k \pi(q_1) \prod_{t=2}^Ta_{q_{t-1},q_t},b_{q_t}(y_t)}
\end{align}
$$</p>
<blockquote>
<p>转移概率 $P (q_t=j|q_{t-1}=i) \implies a_{i,j}$ ，发射概率 $P (y_t|q_t=j) \implies b_j(y_t)$</p>
</blockquote>
<p>那么这长长的一串求了一个什么玩意呢？其实就是 HMM 的一个应用场景，即 $Evaluate;P(Y|\lambda)$ ，也就是预估事件 Y 的概率 $p(y_1,...,y_T)$ 。</p>
<p>显然，由于 sum (求和)太多， $Q$ 的取值会有 $K^T$ 种。这个数值实在过于庞大，因此我们需要更简单的计算方法。</p>
<h2>前向算法</h2>
<p><img src="https://img.085404.xyz/images/202307070030694.webp" alt="image.png" /></p>
<h3>算法推导</h3>
<p>首先，我们来定义一个<strong>前向概率</strong>：
$$
\begin{align}
\alpha_i(t) = p(y_1,...,y_t,q_t=i|\lambda)
\end{align}
$$</p>
<p>其中， $\alpha_i(t)$ 指的是已知 $\lambda$ ，当时间 t 时，状态 $q_t$ 的值为 $i$ 时， $q_t$ 和截至时间 t 为止所有观测值的联合概率。</p>
<p>这不涉及什么数学问题，仅仅是做出了这么一个定义。那么这玩意如何帮助我们解决上面那个复杂计算的问题呢？</p>
<p>假设当 t=1 时，状态为 $i$</p>
<p>$$
\alpha_i(1) = p(y_1,q_1=i) = p(y_1|q_1=i)p(q_1) = b_i(y_1)\pi(q_1)
$$</p>
<blockquote>
<p>这里的 $\lambda$ 就省略不写了</p>
</blockquote>
<p>再来看一个，假设当 t=2 时，状态为 $j$</p>
<p>$$
\begin{align}
\alpha_j(2) &amp;= p(y_1,y_2,q_2=j) \
&amp;= \displaystyle{\sum_{i=1}^K p(y_1,y_2,q_1=i,q_2=j)} \
&amp;= \displaystyle{\sum_{i=1}^K p(y_2|q_2=j)p(q_2=j|q_1=i)p(y_1,q_1=i)}
\end{align}
$$</p>
<p>发现没，最后一行的 $p(y_1,q_1=i)$ ，不就是 $\alpha_i(1)$ 吗？</p>
<p>接着上面的继续推</p>
<p>$$
\begin{align}
&amp;= \displaystyle{\sum_{i=1}^K p(y_2|q_2=j)p(q_2=j|q_1=i)p(y_1,q_1=i)} \
&amp;= p(y_2|q_2=j),\displaystyle{\sum_{i=1}^K [p(q_2=j|q_1=i)\alpha_i(1)]} \
&amp;= b_j(y_2) [\sum_{i=1}^K a_{i,j}\alpha_i(1)]
\end{align}
$$</p>
<p>oh，爷の上帝，瞧瞧出现了什么！又是 TMD 递归！</p>
<p>$$
\alpha_j(T) = b_j(y_T) \displaystyle{[\sum_{i=1}^K a_{i,j} \alpha_i(T-1)]}
$$</p>
<p>根据这个规律不难看出，每个 $\alpha$ 都对应 K 个 sum，因此总共只需要计算 $K \cdot T$ ，比 $K^T$ 小多了。</p>
<p>依据 $\alpha$ 的定义，</p>
<p>$$
\alpha_j(T) = p(y_1,...,y_T,q_T=j)
$$</p>
<p>因此，我们所求的 $p(y_1,...,y_T)$ 跟上式就差了一个 $q_T$ 而已，那么按照老方法，我们把 $q_T$ 塞进去，再积分积掉</p>
<p>$$
p(y_1,...,y_T) = \displaystyle{\sum_{j=1}^K \alpha_j(T)}
$$</p>
<p>上面这个推导过程，其实就是 HMM 的 Forward Algorithm（前向算法）</p>
<blockquote>
<p>当然，有 forward 就有 backward，不过两者求出的概率是一样的</p>
</blockquote>
<h3>代码实现</h3>
<pre><code>import numpy as np

def forward(A, B, PI, observation, only_r = True):
    """观测序列概率的前向算法"""
    n_state = len(A)  # 可能的状态数
    T = len(observation)
    alpha = np.zeros((T, n_state))

    # 初始化前向概率
    for i in range(n_state):
        alpha[0][i] = PI[i] * B[i][observation[0]]

    # 递推计算前向概率
    for t in range(1, T):
        for i in range(n_state):
            alpha[t][i] = np.dot(alpha[t-1], [a[i] for a in A]) * B[i][observation[t]]

    if not only_r: return alpha.tolist()

    return np.sum(alpha[len(alpha)-1])
</code></pre>
<h2>后向算法</h2>
<p>还是先定义<strong>后向概率</strong></p>
<p>$$
\beta_i(t) = p(y_{t+1},...,y_T|q_t=i,\lambda)
$$
$\beta_i(t)$ 指的是已知 $\lambda$ 和 $q_t$ 的值为 $i$ ，未来的观测值的联合概率。</p>
<p>$$
\begin{align}
\beta_i(T-1) &amp;= p(y_T|q_{T-1}=i) \
&amp;= \displaystyle{ \sum_{j=1}^K p(y_T,q_T=j|q_{T-1}=i) } \
&amp;= \displaystyle{ \sum_{j=1}^K p(y_T|q_T=j,q_{T-1}=i) \cdot p(q_T=j|q_{T-1}=i) } \
&amp;= \displaystyle{ \sum_{j=1}^K p(y_T|q_T=j) } \cdot a_{i,j} \cdot \beta_i(T) \
&amp;= \displaystyle{ \sum_{j=1}^K b_j(y_T) } \cdot a_{i,j} \cdot \beta_i(T)
\end{align}
$$</p>
<p>规定， $\beta_i(T) = 1$ （所以上述推导中，乘 1 不影响结果）。这是相邻两个后向概率的递推式。那么我们最后要求的 $P(Y|\lambda)$</p>
<p>$$
\begin{align}
\beta_i(1) &amp;= p(y_2,...,y_T|q_t=i) \
P(Y|\lambda) &amp;= p(y_1,...,y_T|\lambda) \
&amp;= \displaystyle{ \sum_{j=1}^K p(y_1,...,y_T,q_1=i) } \
&amp;= \displaystyle{ \sum_{j=1}^K p(y_1,...,y_T|q_1=i) \cdot p(q_1=i) } \
&amp;= \displaystyle{ \sum_{j=1}^K p(y_1|y_2,...,y_T,q_1=i) \cdot p(y_2,...,y_T|q_1=i) \cdot \pi_i } \
&amp;= \displaystyle{ \sum_{j=1}^K p(y_1|q_1=i) \cdot \beta_i(1) \cdot \pi_i } \
&amp;= \displaystyle{ \sum_{j=1}^K b_i(y_1) \cdot \beta_i(1) \cdot \pi_i }
\end{align}
$$</p>
<h3>代码实现</h3>
<pre><code>def backward(A, B, PI, observation, only_r = True):
    """观测序列概率的后向算法"""
    n_state = len(A)  # 可能的状态数
    T = len(observation)

    # 初始化后向概率
    beta = np.ones((T, n_state))

    # 递推（状态转移）
    for t in range(len(observation) - 2, -1, -1):
        for i in range(n_state):
            beta[t][i] = np.dot(
                np.multiply(A[i], [b[observation[t+1]] for b in B]),
                beta[t+1]
            )

    if not only_r: return beta.tolist()

    return np.dot(
        np.multiply(PI, [b[observation[0]] for b in B]),
        beta[0]
    )
</code></pre>
<p>两种算法结果一致</p>
<blockquote>
<p>参考李航《统计学习方法》 https://github.com/sun2ot/python-study/blob/main/model/HMM.py</p>
</blockquote>
<p><img src="https://img.085404.xyz/images/QQ%E6%88%AA%E5%9B%BE20230709181654.webp" alt="image.png" /></p>
<p><img src="https://img.085404.xyz/images/202307091826915.webp" alt="image.png" /></p>
<h2>Baum-Welch 算法</h2>
<p>假设我们只能获取观测序列 ${O_1,...,O_T}$ ，但无法获取对应的状态序列，则可以通过 BW 算法学习出 HMM 的参数 $\lambda=(A,B,\pi)$ 。</p>
<blockquote>
<p>[!bug]
EM 算法的推理过程还没看懂，这里暂时只能根据数学公式的结果给出实现代码。</p>
</blockquote>
<p><img src="https://img.085404.xyz/images/2460f382a8713c62ba7244289eca2822.webp" alt="image.png" /></p>
<pre><code>def baum_welch(observation, n_state, max_iter=100):
    """Baum-Welch算法学习隐马尔可夫模型

    :param observation: 观测序列
    :param n_state: 可能的状态数
    :param max_iter: 最大迭代次数
    :return: A,B,π
    """
    T = len(observation)  # 样本数
    n_observation = len(set(observation))  # 可能的观测数


    # ---------- 初始化随机模型参数 ----------
    # 初始化状态转移概率矩阵
    a = np.random.rand(n_state, n_state)
    a /= np.sum(a, axis=1, keepdims=True)

    # 初始化观测概率矩阵
    b = np.random.rand(n_state, n_observation)
    b /= np.sum(b, axis=1, keepdims=True)

    # 初始化初始状态概率向量
    p = np.random.rand(n_state)
    p /= np.sum(p)


    for _ in range(max_iter):
        # 计算前向概率
        alpha = forward(a,b,p,observation, only_r=False)
        # 计算后向概率
        beta = backward(a,b,p,observation, only_r=False)

        beta.reverse()

        # ---------- 计算：\gamma_t(i) ----------
        gamma = []
        for t in range(T):
            lst = np.zeros(n_state)
            for i in range(n_state):
                lst[i] = alpha[t][i] * beta[t][i]
            sum_ = np.sum(lst)
            lst /= sum_
            gamma.append(lst)

        # ---------- 计算：\xi_t(i,j) ----------
        xi = []
        for t in range(T - 1):
            sum_ = 0
            lst = [[0.0] * n_state for _ in range(n_state)]
            for i in range(n_state):
                for j in range(n_state):
                    lst[i][j] = alpha[t][i] * a[i][j] * b[j][observation[t + 1]] * beta[t + 1][j]
                    sum_ += lst[i][j]
            for i in range(n_state):
                for j in range(n_state):
                    lst[i][j] /= sum_
            xi.append(lst)

        # ---------- 计算新的状态转移概率矩阵 ----------
        new_a = [[0.0] * n_state for _ in range(n_state)]
        for i in range(n_state):
            for j in range(n_state):
                # 分子，分母
                numerator, denominator = 0, 0
                for t in range(T - 1):
                    numerator += xi[t][i][j]
                    denominator += gamma[t][i]
                new_a[i][j] = numerator / denominator

        # ---------- 计算新的观测概率矩阵 ----------
        new_b = [[0.0] * n_observation for _ in range(n_state)]
        for j in range(n_state):
            for k in range(n_observation):
                numerator, denominator = 0, 0
                for t in range(T):
                    if observation[t] == k:
                        numerator += gamma[t][j]
                    denominator += gamma[t][j]
                new_b[j][k] = numerator / denominator

        # ---------- 计算新的初始状态概率向量 ----------
        new_p = [1 / n_state] * n_state
        for i in range(n_state):
            new_p[i] = gamma[0][i]

        a, b, p = new_a, new_b, new_p
    return a, b, p
</code></pre>
<p>测试数据如下</p>
<pre><code>sequence = [0, 1, 1, 0, 0, 1]

A = [[0.0, 1.0, 0.0, 0.0], [0.4, 0.0, 0.6, 0.0], [0.0, 0.4, 0.0, 0.6], [0.0, 0.0, 0.5, 0.5]]
B = [[0.5, 0.5], [0.3, 0.7], [0.6, 0.4], [0.8, 0.2]]
PI = [0.25, 0.25, 0.25, 0.25]


alpha = forward(A, B, PI, sequence)
print("初始模型参数:", alpha)

A1,B1,P1 = baum_welch(sequence, 4, max_iter=1000)
alpha2 = forward(A1, B1, P1, sequence)
print("BN算法训练后:", alpha2)
</code></pre>
]]></content>
    <author><name>Sun2ot</name></author>
    <category term="笔记"/>
  </entry>
  <entry>
    <title>N-gram 语言模型</title>
    <link href="https://blog.085404.xyz/posts/n-gram-%E8%AF%AD%E8%A8%80%E6%A8%A1%E5%9E%8B/" rel="alternate" type="text/html"/>
    <id>https://blog.085404.xyz/posts/n-gram-%E8%AF%AD%E8%A8%80%E6%A8%A1%E5%9E%8B/</id>
    <published>2023-07-15T00:00:00.000Z</published>
    <updated>2023-07-15T00:00:00.000Z</updated>
    <summary></summary>
    <content type="html"><![CDATA[<h2>概率</h2>
<p>我们的目标是得到一个由 m 个单词组成的任意序列（即一个包含 m 个单词的句子）的概率：</p>
<p>$$
P(w_1, w_2, ..., w_m)
$$</p>
<p>第一步是利用链式法则（chain rule）将<strong>联合概率转换成条件概率的连乘形式</strong>：</p>
<p>$$
P(w_1, w_2, ..., w_m) = P(w_1)P(w_2|w_1)P(w_3|w_1,w_2)...P(w_m|w_1,...,w_{m-1})
$$</p>
<blockquote>
<p>联合概率是指多个事件同时发生的概率，而条件概率是指在某个条件下事件发生的概率。将联合概率转换为条件概率的连乘形式是为了利用概率论中的条件概率规则，将复杂的问题分解为多个简单的条件概率计算。</p>
<p>假设我们有一连串的随机事件 A, B, C, D，我们想计算它们同时发生的联合概率 P(A, B, C, D)。根据条件概率规则，我们可以将其写为：</p>
<p>P(A, B, C, D) = P(A) * P(B|A) * P(C|A, B) * P(D|A, B, C)</p>
<p>这里，P(B|A) 表示在事件 A 发生的条件下事件 B 发生的概率，P(C|A, B) 表示在事件 A 和 B 发生的条件下事件 C 发生的概率，以此类推。</p>
</blockquote>
<h2>马尔可夫假设 The Markov Assumption</h2>
<p>对于上文描述的概率，我们可以再简化一下这个模型。比如对于一个词，它并不需要前面所有的词作为条件概率，也就是说一个词可以只与其前面的若干个（≥0）词有关。</p>
<p>这就是<strong>马尔可夫假设</strong>，即某个单词出现的概率不再依赖于全部上下文，而是取决于离它最近的 n 个单词。</p>
<ul>
<li>
<p>当 $n=1$ 时，N-gram 模型也叫 unigram 模型：
$$P (w_1,..., w_m) = \displaystyle\prod_{i=1}^m P(w_i)$$
在 unigram 模型中，我们假设一个句子出现的概率，等于其中每个单词单独出现的概率的乘积。这意味着<strong>每个单词出现的概率之间相互独立</strong>，即我们并不关心每个单词的上下文。</p>
</li>
<li>
<p>当 $n=2$ 时， bigram 模型：
$$P (w_1,..., w_m) = \displaystyle\prod_{i=1}^m P(w_i|w_{i-1})$$
在 bigram 模型中，我们假设句子中每个单词出现的概率都和它<strong>前一个单词</strong>出现的概率有关。</p>
</li>
<li>
<p>当 $n=3$ 时， trigram 模型：
$$P (w_1,..., w_m) = \displaystyle\prod_{i=1}^m P(w_i|w_{i-2},w_{i-1})$$
在 trigram 模型中，我们假设句子中每个单词出现的概率都和它<strong>前两个单词</strong>出现的概率有关。</p>
</li>
</ul>
<h2>最大似然估计</h2>
<p>对于上文中的这些概率，如何进行计算？其实很简单，我们只需要一个大的用于训练的语料库（corpus），然后我们就可以根据语料库中各个单词的计数（counts），利用最大似然估计来估计该单词出现的概率。</p>
<p>对于 N-gram 模型，</p>
<p>$$
P(w_i|w_{i-n+1},...,w_{i-1}) = \frac {C(w_{i-n+1},...,w_i)} {C(w_{i-n+1},...,w_{i-1})}
$$</p>
<p>即，在前 n-1 个词构成的子句出现的条件下，第 n 个词为 $w_i$ 的概率，等于这 n 个词构成的语句在语料库中出现的次数，除以前 n-1 个词构成的子句出现的次数。</p>
<h2>代码案例</h2>
<pre><code>import torch
from torch import nn
import torch.nn.functional as F
from torch.autograd import Variable

# 莎士比亚的诗
test_sentence = """When forty winters shall besiege thy brow,
And dig deep trenches in thy beauty's field,
Thy youth's proud livery so gazed on now,
Will be a totter'd weed of small worth held:
Then being asked, where all thy beauty lies,
Where all the treasure of thy lusty days;
To say, within thine own deep sunken eyes,
Were an all-eating shame, and thriftless praise.
How much more praise deserv'd thy beauty's use,
If thou couldst answer 'This fair child of mine
Shall sum my count, and make my old excuse,'
Proving his beauty by succession thine!
This were to be new made when thou art old,
And see thy blood warm when thou feel'st it cold.""".split()

# 建立训练集
## 将单词三个分组，前面两个作为输入，最后一个作为预测的结果
## [(('When', 'forty'), 'winters'), ...]
trigram = [((test_sentence[i], test_sentence[i + 1]), test_sentence[i + 2])
           for i in range(len(test_sentence) - 2)]
# print(trigram[0])
# print(len(test_sentence), test_sentence.index('cold.'))


# 建立每个词与数字的编码，据此构建词嵌入
vocb = set(test_sentence) # 使用 set 将重复的元素去掉
word_to_idx = {word: i for i, word in enumerate(vocb)}  # {词语: 索引}
idx_to_word = {word_to_idx[word]: word for word in word_to_idx}  # {索引: 词语}



# 定义模型
## 模型的输入就是前面的两个词，输出就是预测单词(第三个词)的概率

CONTEXT_SIZE = 2  # 依据的单词数，表示我们希望由前面几个单词来预测这个单词，这里使用两个单词(trigram)
EMBEDDING_DIM = 10  # 词向量的维度，表示词嵌入的维度

class n_gram(nn.Module):
    def __init__(self, vocab_size, context_size=CONTEXT_SIZE, n_dim=EMBEDDING_DIM):
        super(n_gram, self).__init__()

        self.embed = nn.Embedding(vocab_size, n_dim)
        self.classify = nn.Sequential(
            nn.Linear(context_size * n_dim, 128),
            nn.ReLU(True),
            nn.Linear(128, vocab_size)
        )

    def forward(self, x):
        voc_embed = self.embed(x) # 得到词嵌入
        voc_embed = voc_embed.view(1, -1) # 将两个词向量拼在一起
        out = self.classify(voc_embed)
        return out


net = n_gram(len(word_to_idx))

# 交叉熵误差
criterion = nn.CrossEntropyLoss()
# 随机梯度下降
optimizer = torch.optim.SGD(net.parameters(), lr=1e-2, weight_decay=1e-5)

# 迭代 100 轮
for e in range(100):
    train_loss = 0
    for word, label in trigram:
        # 将两个词作为输入, 将索引转换为张量
        word = torch.LongTensor([word_to_idx[i] for i in word])
        label = torch.LongTensor([word_to_idx[label]])
        # 前向传播
        out = net(word)
        # 交叉熵误差
        loss = criterion(out, label)
        train_loss += loss.item()
        # 反向传播
        optimizer.zero_grad()  # 梯度归零
        loss.backward()  # 计算梯度
        optimizer.step()  # 更新模型参数
    if (e + 1) % 20 == 0:
        print('epoch: {}, Loss: {:.6f}'.format(e + 1, train_loss / len(trigram)))

# 评估模型
# 将神经网络模型设置为评估模式
net = net.eval()

# 随便找个数据，测试一下结果
word, label = trigram[19]
print('input: {}'.format(word))
print('label: {}'.format(label))
print()

word = torch.LongTensor([word_to_idx[i] for i in word])
out = net(word)
# a = out.max(1)

# 预测值的索引
pred_label_idx = out.max(1).indices.item()
# 预测的单词
predict_word = idx_to_word[pred_label_idx]
print('real word is "{}", predicted word is "{}"'.format(label, predict_word))
</code></pre>
<p>运行上述代码，得到下列输出结果</p>
<pre><code>epoch: 20, Loss: 0.707435
epoch: 40, Loss: 0.150101
epoch: 60, Loss: 0.099814
epoch: 80, Loss: 0.080732
epoch: 100, Loss: 0.069862
input: ('so', 'gazed')
label: on

real word is "on", predicted word is "on"
</code></pre>
]]></content>
    <author><name>Sun2ot</name></author>
    <category term="笔记"/>
  </entry>
  <entry>
    <title>Windows 所有操作都无法以普通权限启动</title>
    <link href="https://blog.085404.xyz/posts/windows-%E6%89%80%E6%9C%89%E6%93%8D%E4%BD%9C%E9%83%BD%E6%97%A0%E6%B3%95%E4%BB%A5%E6%99%AE%E9%80%9A%E6%9D%83%E9%99%90%E5%90%AF%E5%8A%A8/" rel="alternate" type="text/html"/>
    <id>https://blog.085404.xyz/posts/windows-%E6%89%80%E6%9C%89%E6%93%8D%E4%BD%9C%E9%83%BD%E6%97%A0%E6%B3%95%E4%BB%A5%E6%99%AE%E9%80%9A%E6%9D%83%E9%99%90%E5%90%AF%E5%8A%A8/</id>
    <published>2023-07-04T00:00:00.000Z</published>
    <updated>2023-07-04T00:00:00.000Z</updated>
    <summary></summary>
    <content type="html"><![CDATA[<h2>背景</h2>
<p>重装系统时偷了懒，用了 PE 里自带的一键装机，结果默认将 Windows 的超级管理员用户 <code>Administrator</code> 用户开启，还用了一晚上。</p>
<p>结果第二天发现这玩意问题有点多，其中一个就是<strong>所有的终端默认都是以管理员模式启动</strong>，无论你是否点击 <code>以管理员模式启动</code> 。这就导致部分程序完全无法执行</p>
<blockquote>
<p>出于安全考虑，部分软件/程序是无法在 root/admin 模式下执行的</p>
</blockquote>
<h2>解决方案</h2>
<p>如果是家庭版用户，先安装<strong>组策略</strong></p>
<blockquote>
<p>将下列文本拷贝到 txt 文件中，再将文件后缀修改为 <code>.cmd</code> ，以管理员模式执行即可。</p>
</blockquote>
<pre><code>@echo off
pushd "%~dp0"
dir /b C:\Windows\servicing\Packages\Microsoft-Windows-GroupPolicy-ClientExtensions-Package~3*.mum &gt;List.txt
dir /b C:\Windows\servicing\Packages\Microsoft-Windows-GroupPolicy-ClientTools-Package~3*.mum &gt;&gt;List.txt
for /f %%i in ('findstr /i . List.txt 2^&gt;nul') do dism /online /norestart /add-package:"C:\Windows\servicing\Packages\%%i"
pause
</code></pre>
<p><code>win + r</code> 运行 <code>secpol.msc</code> ，将红框的两个选项设置为如图所示，然后<strong>重启电脑</strong>即可。</p>
<p><img src="https://img.085404.xyz/images/202307041518748.webp" alt="image.png" /></p>
<h2>关闭 Administrator</h2>
<p>如果你不想使用超管用户（其实 Windows 默认是关闭这个用户的），可以在<strong>管理员命令提示符</strong>执行如下指令</p>
<pre><code># 关闭超管
net user administrator /active:no
# 开启超管
net user administrator /active:yes
</code></pre>
<blockquote>
<p>[!warning]
如果你电脑上目前就只有一个超管用户，没有其他用户，那就先别慌禁用，去创建一个新用户再禁用，否则你重启之后就进不了系统了，得做 PE 去折腾。。。</p>
</blockquote>
<pre><code># 新建一个用户 username，密码是 password
net user username password /add

# 添加 username 到 Administrators 组
net localgroup Administrators username /add
</code></pre>
]]></content>
    <author><name>Sun2ot</name></author>
    <category term="教程"/>
  </entry>
  <entry>
    <title>记录一次拔智齿的经历</title>
    <link href="https://blog.085404.xyz/posts/%E8%AE%B0%E5%BD%95%E4%B8%80%E6%AC%A1%E6%8B%94%E6%99%BA%E9%BD%BF%E7%9A%84%E7%BB%8F%E5%8E%86/" rel="alternate" type="text/html"/>
    <id>https://blog.085404.xyz/posts/%E8%AE%B0%E5%BD%95%E4%B8%80%E6%AC%A1%E6%8B%94%E6%99%BA%E9%BD%BF%E7%9A%84%E7%BB%8F%E5%8E%86/</id>
    <published>2023-06-25T00:00:00.000Z</published>
    <updated>2023-06-25T00:00:00.000Z</updated>
    <summary></summary>
    <content type="html"><![CDATA[<h2>流程</h2>
<p>第一步，当然是选择医院挂号啦。只推荐三甲医院口腔科（多数医院叫做<strong>口腔颌面外科</strong>），毕竟技术有保证，同时还能走医保，不香吗。</p>
<p><img src="https://img.085404.xyz/images/901a8d0c599253953de7f6f6d4c2b5a1.webp" alt="Screenshot_2023-06-25-21-38-13-899_com.tencent.mm.png" /></p>
<blockquote>
<p>这是我本次拔两颗牙的总支付金额，除了一个医药包还是什么的没走医保，其他全部走医保，也就是我只付了 1.65￥！</p>
</blockquote>
<p>第二步，到达医院后，医生会先给你拍牙片（就是牙齿的 X 光片），这玩意能够清除的看到你的牙齿情况，例如有几颗智齿、哪些需要拔，然后医生可以根据照片给你制定拔牙方案！</p>
<p>第三步，开始拔牙。首先是打麻药，会用针管对准你的牙龈进行注射。这个过程会有明显不适（但是<strong>不疼</strong>，或者说就和静脉注射打点滴一样，进去的时候疼一下），不适感的程度大概相当于男生鸡儿勃起时被裤子勒住的这种阻塞感。打完麻药后你的半边脸是麻木的，嘴巴里好像被一个木棍顶住了一样，这都是正常现象，而且医生会跟你进行确认的（毕竟麻药要是没起作用就乐了 hhh）。</p>
<blockquote>
<p>[!warning]
切记跟医生说明，打进口麻药！进口麻药！进口麻药！我拔的上下两颗牙齿，上面用的进口麻药，下面是国产麻药。结果下面的牙齿还没拔完就开始痛了。。。</p>
</blockquote>
<p>麻药打完后，医生会给你脸上盖一张布，只露出嘴巴，然后你就可以张嘴闭眼歇着了。当然，不是让你睡觉，拔牙期间你要配合医生完成张大嘴、低头、转头等动作。拔牙期间因为有麻药作用，你是没有感觉的。除了你的嘴因为要裂地很大，可能会有点不适。再就是如果你的牙齿十分牢固，可能还需要上锤子，对，你没听错，就是锤子，用锤子来锤你的牙齿。这个过程会有明显感知，震的你脑袋咣咣咣的，不过，不会痛。</p>
<p>除了锤子，可能还需要上钻头。如果你的牙齿过大，导致无法顺利拔出的话，医生可能用钻头把你的牙齿切割成几块，然后分别捞出来。过程不疼，但挺刺激 hhh。</p>
<p>最后，就是收尾阶段了。医生会对在你的牙槽里（牙齿拔出来了，肯定有个坑嘛），塞入一些药物促进伤口修复，同时有一些消炎效果。然后会对你的牙龈伤口进行缝合。缝合结束后会给你的嘴里塞一个棉球（<s>口球</s>）。<strong>用力咬住这个棉球，持续 40-60 分钟！</strong> 然后吐出来就行了。</p>
<blockquote>
<p>这个棉球是特制材质的，所以你不用担心和你的创口贴一样，用力贴紧后会黏在伤口上。</p>
</blockquote>
<h2>术后注意事项</h2>
<h3>不要吐口水</h3>
<p>术后伤口有血液渗出是极度正常的，每个人都会有，所以自然而然你的嘴里会有铁锈味（血的味道）。此时不要闲的蛋疼吐出来，觉得味道不好，亦或是觉得恶心，<strong>必须吞咽下去</strong>！因为一旦有吐的这个动作，就会在你嘴里形成负压，那么机体会认为仍然需要继续出血，这会导致你的伤口无法愈合。</p>
<blockquote>
<p>人的身体是有自愈能力的，因此拔牙术后，会在伤口处形成血凝块，然后随着时间推移慢慢长成肉，从而固定住。而一旦在初期，你的动作过激的话，就会导致这个血凝块始终无法成形，自然创口就无法止血。</p>
</blockquote>
<h3>两个时间节点</h3>
<p>24 小时内禁止刷牙、漱口！2 小时后才可以喝水，吃东西！尽量避开手术的一侧</p>
<blockquote>
<p>所以一次性拔四颗，觉得长痛不如短痛的想法，我个人是觉得挺离谱的。。。你两边都噶了，怎么吃东西。</p>
</blockquote>
<h3>吃温凉的流食</h3>
<p>吃流食好理解，牙齿痛的一笔，有的人甚至张嘴和吞咽都痛，咋吃硬食嘛。而温凉呢，我们都知道，高温会促进机体的血液流动，你本来伤口就没愈合，流着血呢，还塞一嘴热乎乎的食物，那不是帮倒忙嘛，所以要吃温的甚至是凉的，这既对你的伤口有好处，<strong>也能增加你的舒适度</strong>！</p>
<p><strong>禁止使用海鲜、河鲜、牛羊肉、辛辣、油炸等食物</strong>。</p>
<h3>关于伤口流血</h3>
<p>术后医生不是让你用力咬一个棉球嘛，那玩意就是给你初步止血用的（压迫止血应该懂吧）。然后你吐出来后，首先遵守吞咽原则，不要吐口水。过几个小时，可以在吞咽一次口水之后，等下一波口水自然酝酿出来后，吐出来看看。如果是<strong>带有血丝，那很正常</strong>。如果出血量很大，比如一口口水，80%都是血，那显然有问题，赶紧<strong>复查</strong>去。当然，吐一两次就行了，记住前面说的，没事不要吐口水！</p>
<blockquote>
<p>所以这里建议拔完牙后，别几把腼腆，赶紧把医生的微信要来，反正咱也就联系这几天不是。</p>
</blockquote>
<h3>关于消炎、止痛药</h3>
<p>相信医生的专业，如果他没给你开消炎药，那就说明他认为没必要。如果你确信自己发炎了，例如血流不止、脸肿的特别大，建议还是联系医生。或者拔完牙后，直接挂两瓶点滴（我就这么干的）。</p>
<p>关于止痛药，感觉到开始痛了就赶紧吃。一般疼痛的巅峰期就是前 3-4 天。医生给我开的<strong>洛索洛芬钠片</strong>，效果非常 nice！</p>
<p>如果有必要，可以买些冰袋冷敷，止痛消肿都有效果。冰袋有一次性和循环使用两种。一次性的最多管 15 分钟，好处是不用提前冷冻，即开即用，但成本更高。循环使用的就是字面意思啦。</p>
<h2>结尾</h2>
<p>最后的最后，all in all，拔牙也算是个小手术了，术后一定要注意休息，别熬夜！你以为你是肉体修复 max 吗！只有充足的睡眠才能更好地修复机体。<strong>睡觉的时候在枕头上垫个毛巾</strong>，防止半夜血顺着哈喇子流出来了。同时<strong>尽量侧睡</strong>，例如左边手术，就面朝右边侧睡。</p>
]]></content>
    <author><name>Sun2ot</name></author>
    <category term="日常"/>
  </entry>
  <entry>
    <title>驾考-科目一&amp;四</title>
    <link href="https://blog.085404.xyz/posts/%E9%A9%BE%E8%80%83/" rel="alternate" type="text/html"/>
    <id>https://blog.085404.xyz/posts/%E9%A9%BE%E8%80%83/</id>
    <published>2023-06-12T00:00:00.000Z</published>
    <updated>2023-06-12T00:00:00.000Z</updated>
    <summary></summary>
    <content type="html"><![CDATA[<h2>申请限制</h2>
<ul>
<li>
<p>小车：18岁（无上限）</p>
</li>
<li>
<p>公交，中客，大货车，轻挂，电车：20-60岁</p>
</li>
<li>
<p>大客，重挂：22-60岁</p>
</li>
<li>
<p><strong>三年内</strong>吸毒，或者精神药物成瘾的，不能申请</p>
</li>
<li>
<p><strong>初次申请</strong>不能载人，所以客挂不行（客车，挂车）</p>
</li>
<li>
<p>获得驾照 <strong>1 年后，可申请轻挂；2 年后可申请中客</strong></p>
</li>
<li>
<p>申请增驾，需<strong>本周期和上一个记分周期</strong>内没有记满 12 分</p>
</li>
<li>
<p>除了<strong>申请增驾轻挂</strong>考科二和科三的常识，其他的都是靠科二和科三</p>
</li>
<li>
<p>申请增驾轻挂，车管所<strong>受理后直接发放</strong>学习驾驶证明</p>
</li>
<li>
<p>70 岁以上，可以申请小车、轻摩、残疾人</p>
</li>
<li>
<p>无证驾驶致死10年不能申请</p>
</li>
</ul>
<h2>车辆与代码对应关系</h2>
<p>A1大客，A2重挂
B1中客，B2大货（重中载货，重中专业）</p>
<h2>科目一二三</h2>
<ol>
<li>网络学习不少于 5 天，现场学习不少于 2 天（<strong>网 5 现 2</strong>）</li>
<li>科二科三最多<strong>各考5次</strong></li>
<li><strong>学习驾驶证明</strong>有效期3年，期间必须通过科二科三</li>
<li>科一科三满分100，90及格</li>
<li><strong>科目一通过后</strong>，车管所应在 <strong>1 日内发放</strong>学习驾驶证明</li>
<li>科三：
<ul>
<li>道路驾驶技能考试</li>
<li>安全文明驾驶常识考试</li>
</ul>
</li>
<li>小型自动挡科目二：<s>坡道定点停车和起步</s>（<strong>C1 才考这个</strong>）、倒车入库、侧方停车、曲线行驶、直角转弯</li>
<li>预约科目三考试
<ul>
<li><strong>自动挡</strong>取得学习驾驶证明<strong>满 20 天</strong>后</li>
<li><strong>手动挡满 30 天</strong></li>
</ul>
</li>
<li><strong>小车科三考试里程</strong>不少于 3 公里</li>
</ol>
<h2>学车注意事项</h2>
<ol>
<li>高速公路不能试车和学车</li>
<li>学车人违规或者造成事故，由教练担责，处200-2000罚款</li>
<li>新手学习道路驾驶技能时，必须由教练员随车指导，否则罚款200-2000元罚款</li>
</ol>
<h2>实习期</h2>
<ul>
<li>实习期12个月</li>
<li>车身后部粘贴或者悬挂<strong>统一式样</strong>的实习标志</li>
<li>新手<strong>不能单独上高速</strong>，除非<strong>3年以上</strong>老司机陪同（持有相应或更高级驾驶证）</li>
<li>实习期单独上高速，处 20-200 罚款</li>
<li>实习期<strong>开挂车</strong>，处20-200罚款</li>
<li>实习期满12分，注销驾驶证</li>
<li><strong>同类车型增驾后不是新手</strong>，无需 12 个月实习期；异类车型增驾有 12 个月实习期</li>
</ul>
<h2>驾照过期</h2>
<ul>
<li>驾照超过有效期一年，注销</li>
<li>超过一年不到两年，参加道路交通安全法律、法规和相关知识考试合格后，可以恢复驾驶资格</li>
<li>补领驾照后，用<strong>旧证件驾驶</strong>的，处 20-200 罚款</li>
<li>6 年没记满 12 分换 10 年，10 年没记满换长期有效</li>
</ul>
<h2>审验</h2>
<ol>
<li>驾驶人<strong>逾期不参加审验</strong>仍然开车，处 200-500 罚款</li>
<li><strong>驾驶证审验</strong>包括
<ul>
<li>违法、事故处理情况</li>
<li>驾驶人身体</li>
<li>记分和满 12 分后的学习和考试情况</li>
</ul>
</li>
<li>70 岁以上的驾驶人，<strong>每年体检</strong></li>
<li>驾驶人出国、兵役等原因导致无法审验时，<strong>延期审验最长 3 年</strong></li>
<li>体检结果 <strong>6 个月</strong>有效</li>
<li>70 岁以上驾驶发生事故，<strong>30 天</strong>内到交管部门接受审验</li>
<li><strong>审验作假</strong>：成绩无效，罚1k以下，重考
<strong>代替</strong>参加审验罚2k以下
<strong>组织</strong>他人实施，有违法所得处所得3倍以下罚款，不超过2w；没有违法所得罚2w以下</li>
</ol>
<h2>记分规则</h2>
<ol>
<li>一个周期是一年</li>
<li>一个周期内不满12分，缴清罚款，记分清除；有罚款没交，记分转入下一个周期</li>
<li>一个记分周期内<strong>满 12 分</strong>需要<strong>重新学习知识 7 天</strong>（科一），</li>
<li>满 <strong>24-36</strong> 分需要在<strong>通过科目一 30 天后</strong>参加科目三道路考试。不合格的 10 天后重新考试</li>
<li><strong>满36分</strong>重考科一、科二科三（场地+路驾）</li>
<li>1 年内无记分，可以延长驾驶证的审验期</li>
<li>记分周期从<strong>初领驾照</strong>或<strong>临时驾驶许可</strong>之日开始计算</li>
<li>不满 12 分，可以继续用于名下其他车辆的扣分</li>
<li>小车每满一次 12 分，学习时间加 7 天（<strong>现场 2，网络 3，自主 2</strong>），最多 60 天
大车每满一次 12 分，学习时间加 30 天（<strong>现场 5，网络 5，自主 20</strong>），最多 120 天</li>
</ol>
<h4>扣1分</h4>
<ol>
<li>驾驶校车、中型以上载客载货汽车、危险物品运输车辆在<strong>高速公路、城市快速路以外</strong>的道路上超速10-20%；</li>
<li><strong>不按规定会车</strong>，或者在<strong>普通道路</strong>不按规定倒车、掉头的；</li>
<li><strong>违规用灯</strong>；</li>
<li>违反禁令标志、禁止标线指示的；</li>
<li>载货长度、宽度、高度超过规定的；</li>
<li>载货汽车超载小于30%；</li>
<li><strong>不年检</strong>；</li>
<li>改装</li>
<li>驾驶机动车在道路上行驶时，机动车驾驶人未按规定系<strong>安全带</strong>的；</li>
<li>驾驶摩托车，不戴<strong>安全头盔</strong>的。</li>
</ol>
<h4>扣3分</h4>
<ol>
<li>普通载客汽车超载20%-50%</li>
<li><strong>普通汽车</strong>在<strong>普通道路</strong>上超速 20-50%</li>
<li>高速和城市快速路上<strong>不按规定车道行驶</strong></li>
<li>不按规定超车，让行</li>
<li><strong>普通道路逆行</strong></li>
<li>加塞</li>
<li>开车打电话</li>
<li><strong>人行道不避让行人</strong></li>
<li>不避让校车</li>
<li>货车超重30%-50%，或<strong>违规载客</strong></li>
<li><strong>不按规定安装</strong>车牌</li>
<li><strong>在道路上车辆发生故障、事故停车后，不按规定使用灯光或者设置警告标志的</strong></li>
<li>开没有定期安检的车</li>
<li>开车前不检查车辆安全情况</li>
<li>货车连续驾驶4小时未休息，或休息时间少于20分钟（<strong>货车疲劳驾驶</strong>）</li>
<li>高速上车速低于规定的最低速度（<strong>低速行驶</strong>）</li>
</ol>
<h4>扣6分</h4>
<ol>
<li>驾驶<strong>校车、公路客运汽车、旅游客运汽车</strong>超载小于20%，或者驾驶<strong>7座以上载客汽车</strong>超载20-50%，或者驾驶<strong>其他载客汽车</strong>超载50%-100%；</li>
<li>驾驶<strong>校车、中型以上载客载货汽车、危险物品运输车辆</strong>在<strong>高速公路、城市快速路上</strong>行驶超速小于 20%，或者在普通道路上超速 20-50%；</li>
<li>驾驶<strong>普通车辆</strong>（除上述三类车）在<strong>高速公路、城市快速路上</strong>超速 20-50%，或者在<strong>普通道路</strong>超速 50%以上；</li>
<li>载货汽车超载50%以上；</li>
<li><strong>危险品车辆</strong>未按指定的时间、路线、速度行驶或者未悬挂警示标志并采取必要的安全措施的；</li>
<li>驾驶机动车运载<strong>超限</strong>的不可解体的物品，未按指定的时间、路线、速度行驶或者未悬挂警示标志的；</li>
<li>驾驶机动车运输危险化学品，未经批准进入危险化学品运输车辆限制通行的区域的；</li>
<li><strong>不按交通信号灯指示通行</strong>；</li>
<li><strong>无证驾驶</strong>；</li>
<li>造成致人<strong>轻微伤或者财产损失</strong>的交通事故后逃逸，尚不构成犯罪的；</li>
<li>驾驶机动车在高速公路或者城市快速路上<strong>违法占用应急车道</strong>行驶的。</li>
</ol>
<h4>扣 9 分</h4>
<ol>
<li><strong>7 座以上载客汽车</strong>超载 50-100%</li>
<li><strong>校车、中型载客载货车、危险品运输车</strong>，普通道路上超速 50% 以上</li>
<li>高速、城快路上<strong>违法停车</strong></li>
<li><strong>无牌、遮挡车牌</strong>上路行驶</li>
<li>驾驶与准驾车型不符的车</li>
<li>未取得校车驾驶资格<strong>开校车</strong>的</li>
<li>连续驾驶中型以上载客汽车、危险品运输车辆超过 4 小时未休息，或休息时间少于 20 分钟（<strong>客车疲劳驾驶</strong>）</li>
</ol>
<h4>扣12分</h4>
<ol>
<li>酒驾；</li>
<li>造成致人<strong>轻伤以上或者死亡</strong>的交通事故后逃逸，尚不构成犯罪的；</li>
<li>假（冒用）车牌，假（冒用）证；</li>
<li><strong>校车，公汽，旅汽</strong>超载20%以上；<strong>其他载客汽车</strong>（包括 7 座）超载100%以上</li>
<li><strong>校车，中型以上载客载货车，危险品车</strong>，在高速、城快路上超速20%；<strong>其他车辆</strong>在高速、城快路上超速50%</li>
<li><strong>高速、城快</strong>倒车、逆行、穿越中央分隔带掉头；</li>
</ol>
<blockquote>
<p>高快逆行 12 分，普通逆行 3 分</p>
</blockquote>
<ol>
<li>代替别人受处罚并获益
<ul>
<li>被代替者，处支付利益<strong>三倍以下</strong>罚款，<strong>不超过五万元</strong></li>
<li>代替者，处违法所得<strong>三倍以下</strong>罚款，<strong>不超过五万元</strong></li>
<li>组织他人实施此行为牟利者，处违法所得<strong>五倍以下</strong>罚款，<strong>不超过十万元</strong></li>
</ul>
</li>
</ol>
<h2>记分减免</h2>
<table>
<thead>
<tr>
<th>减分形式</th>
<th>学习时间</th>
<th>是否考试</th>
<th>减分</th>
</tr>
</thead>
<tbody>
<tr>
<td>网课学习</td>
<td>(三天内) 30 分钟</td>
<td>是</td>
<td>1</td>
</tr>
<tr>
<td>现场学习</td>
<td>1 小时</td>
<td>是</td>
<td>2</td>
</tr>
<tr>
<td>公益活动</td>
<td>1 小时</td>
<td>否</td>
<td>1</td>
</tr>
</tbody>
</table>
<ol>
<li>学法减分，一个周期内<strong>最多减 6 分</strong></li>
<li><strong>实习期</strong>不减分</li>
<li><strong>两次以上满分教育</strong>不减分</li>
<li><strong>三个周期内有酒驾</strong>的不减分</li>
</ol>
<h2>事故负责</h2>
<ol>
<li>发生交通事故后，当事人故意破坏、伪造现场、毁灭证据的，承担<strong>全部责任</strong></li>
<li>行人<strong>故意</strong>导致事故，开车的不用负责。否则双方都有责任。简称 <strong>“开车原罪论”</strong></li>
<li>非机动车驾驶人（蹬自行车的）、行人与<strong>处于静止状态的机动车</strong>发生交通事故造成损失，机动车一方无事故责任的，不承担赔偿责任</li>
<li>发生交通事故后当事人逃逸的，<strong>逃逸者负全责</strong>。若能证明对方也有过错的，可以&lt;u&gt;减轻责任&lt;/u&gt;（<strong>此时不负全责</strong>）</li>
<li>扰乱公共交通驾驶员，危及公共安全，处 1 年以下有期徒刑、拘役或管制，并 (单)处罚金</li>
<li>发生事故都，<strong>逃逸致人死亡</strong>，处 <strong>7 年</strong>以上有期徒刑</li>
</ol>
<h2>灯光控制</h2>
<ol>
<li>夜间没有路灯、照明不良或者遇有雾、雨、雪、沙尘、冰雹等低能见度情况下行驶时，应当开启<strong>前照灯、示廓灯和后位灯</strong></li>
<li>同方向行驶的后车与前车近距离行驶时，不得使用<strong>远光灯</strong></li>
<li>机动车雾天行驶应当开启<strong>雾灯</strong>和<strong>危险报警闪光灯</strong></li>
</ol>
<h2>考试违法惩罚</h2>
<ol>
<li>虚假材料：<strong>1年</strong>内不能申请驾驶证，处 <strong>500 以下</strong>罚款</li>
<li>考试作弊：<strong>1年</strong>内不能申请驾驶证，处 <strong>2000 以下</strong>罚款</li>
<li>满分学习作假，处1000以下罚款</li>
<li>欺骗，贿赂拿到驾驶证的，<strong>3年</strong>内不能申请驾驶证，处 <strong>2000 以下</strong>罚款</li>
<li>考试时<strong>组织作弊</strong>，
<ul>
<li>处<strong>3年以下</strong>有期徒刑或拘役</li>
<li><strong>情节严重</strong>构成犯罪的，处 <strong>3 年以上 7 年以下</strong>有期徒刑</li>
<li>处违法所得 3 倍以上 5 倍以下罚款，最高不超过 10 万元</li>
</ul>
</li>
<li>代替他人考试，3 年内不得申请，处<strong>拘役或管制</strong></li>
</ol>
<blockquote>
<p>假 1 、骗 3 、毒 3、醉 5、 逃逸犯罪终生</p>
</blockquote>
<h2>可以撤销的违法行为</h2>
<ol>
<li>警车，救护车等执行公务时的违法行为</li>
<li>车或车牌被偷了，别人冒用时的违法行为</li>
<li>救助危难或紧急避险造成的违法行为</li>
<li>已经有交警现场处理完毕的违法行为</li>
<li>交通信号指示不一致导致的违法行为</li>
<li>无证据证明的违法行为</li>
<li>信息错误的违法行为</li>
<li>其他应该被消除的情形</li>
</ol>
<h2>吊销驾照</h2>
<ol>
<li>开<strong>报废车，拼装车</strong>上路
<ul>
<li>罚款200-2000</li>
<li>强制收缴车辆（不拘留人）</li>
<li>吊销驾照</li>
</ul>
</li>
<li><strong>肇事逃逸且构成犯罪</strong>
<ul>
<li>刑事责任</li>
<li>==吊销驾照，终身不可重得==</li>
</ul>
</li>
<li>因酒驾被处罚后，<strong>再次酒驾</strong>的
<ul>
<li>10 日以下拘留</li>
<li>1-2 k 罚款</li>
<li>吊销驾照</li>
</ul>
</li>
<li><strong>醉驾机动车</strong>
<ul>
<li>刑事责任</li>
<li>吊销驾照</li>
<li>5 年内不得重得驾驶证</li>
</ul>
</li>
<li><strong>酒驾运营机动车</strong>
<ul>
<li>吊销驾照</li>
<li>5 年内不得重得驾驶证</li>
</ul>
</li>
<li><strong>醉驾运营机动车</strong>
<ul>
<li>吊销驾照</li>
<li>刑事责任</li>
<li>10 年内不得重得驾驶证</li>
<li>重得驾驶证后，不得驾驶运营机动车</li>
</ul>
</li>
<li><strong>酒驾或醉驾机动车，发生重大交通事故，构成犯罪的</strong>
<ul>
<li>刑事责任</li>
<li>==吊销驾照，终身不可重得==</li>
</ul>
</li>
<li><strong>超载、超速构成犯罪</strong>
<ul>
<li>吊销驾照，5 年内不得申请</li>
</ul>
</li>
</ol>
<h2>机动车相关</h2>
<ol>
<li>用作抵押时，向<strong>登记车辆的交管部门</strong>申请抵押登记</li>
<li>机动车转让不登记罚200以下</li>
<li>登记不用<strong>行驶证</strong>，转让不用<strong>驾驶证</strong></li>
<li>报废车辆交给<strong>回收企业</strong></li>
<li>受理3天
补发，换发号牌需15天
信息修改30天
换证90天</li>
<li>临时号牌3天过期</li>
<li><strong>普通灭火器灭不了电动汽车</strong></li>
</ol>
<h2>保险</h2>
<ol>
<li>一年内没发生事故，保险公司需下调保费，直至最低费率</li>
<li>发生酒驾或醉驾，交通事故责任强制保险（交强险）费率将上浮</li>
</ol>
<h2>啥时候找车管所</h2>
<ol>
<li>
<p>在户籍地住，就找户籍地的</p>
</li>
<li>
<p>户籍地外居住，就找居住地</p>
</li>
<li>
<p>军人（含武警）找部队驻地</p>
</li>
<li>
<p>境外人员找居住地</p>
</li>
<li>
<p>申请增加准驾，找<strong>驾驶证核发地</strong>申请</p>
</li>
<li>
<p>驾驶职业教育的，在受教育地</p>
</li>
<li>
<p>初次登记机动车号牌，找<strong>居住地</strong>车管所</p>
</li>
</ol>
<h2>安全常识</h2>
<ol>
<li>ABS 车轮防抱死，<strong>ABS 找紧急制动</strong>。有紧急制动的字眼判断题是正确的，没有就错</li>
<li>安全带: 安全带在紧急情况下，可以迅速拉紧把人束缚在座椅上，保证安全。可以减轻对驾乘人员的伤害（<strong>安全带找减轻</strong>)</li>
<li>安全头枕是<strong>保护颈部</strong>的。但是需要放在<strong>支撑头部的位置</strong>才可以发挥作用保护到颈部。(安全头枕保护颈部，放在头后)</li>
<li>安全气囊，<strong>辅助</strong>可充气约束系统。</li>
</ol>
<blockquote>
<p>(技巧: 看到辅助就选择) 安全带的作用是 70%，气囊的作用是 30%。<strong>双重保护作用</strong>就是与安全带一起。</p>
</blockquote>
<ol>
<li>前车<strong>左转弯</strong>时不能超车</li>
</ol>
<h2>英文缩写</h2>
<ol>
<li><strong>LDW</strong> （Lane Departure Warning System）：<strong>车道偏离预警系统</strong>，这是一种汽车安全系统，它可以监测车辆是否偏离其行驶的车道，并通过声音、震动或视觉警告来提醒驾驶员。</li>
<li><strong>ACC</strong>（Adaptive Cruise Control）：<strong>自适应巡航控制系统</strong>，这是一种汽车辅助驾驶技术，它可以根据前方车辆的距离和速度，自动调节车辆的速度和间距，并保持与前车相对稳定的距离。这使得驾驶员在高速公路等条件下可以更加轻松地控制车辆。</li>
<li>ALC（Auto Lance Change）：<strong>自动变道辅助系统</strong>，后车超车时，本车左右后视镜会有安全警示灯，部分车型方向盘还会轻微震动</li>
<li>CCS（Cruise  Control System）：<strong>定速巡航系统</strong>，能够自动控制车辆行驶速度的装置</li>
</ol>
<h2>罚款</h2>
<ul>
<li><strong>身体条件不适合驾驶机动车</strong>的处罚是，会被罚款200元以上，500元以下，并且还会收回司机的驾驶证</li>
<li>机动车驾驶证被暂扣期间驾驶机动车的<strong>可处200~2000元罚款，并可以并处15日以下拘留</strong></li>
<li>造成交通事故后逃逸，尚不构成犯罪的，<strong>由公安机关交通管理部门处200~2000元罚款，可以并处十五日以下拘留</strong></li>
<li>违反交通管制强制通行，<strong>不听劝阻</strong>，罚 200-2000，处 15 以下拘留</li>
</ul>
<h2>标识/规定</h2>
<ul>
<li>停车位的线是蓝色的代表该停车位是免费停车位，<strong>黄色停车位为专属停车位</strong></li>
<li>高速限速，小车 120，摩托 80，其他 100</li>
</ul>
<p>两侧通行（如果只有一边有白色，就是左/右侧通行）</p>
<p><img src="https://img.085404.xyz/images/1d630128e3fdf071c883294aa06a54d8.webp" alt="shuangce.png|300*300" /></p>
<p>注意分离式道路</p>
<p><img src="https://img.085404.xyz/images/5079a57b464ed582ee2e640c2c88e195.webp" alt="fenli.png|300*300" /></p>
<h2>科目四</h2>
<p>中客核载 10-19 人</p>
<p>复杂天气、路段，限速 30</p>
<p>校车<strong>同车道</strong>或<strong>相邻车道</strong>停车等待，其他车道缓慢通过</p>
<p>冰雪路面减速/停车，充分利用<strong>发动机</strong>的牵制作用。不能紧急制动、急打方向</p>
<p>雨天超车<strong>不能迅速超越</strong></p>
<p>路边黄线：实线禁停，虚线禁长时</p>
<p>积水路段：<strong>紧握方向盘</strong>，缓慢通过</p>
<p>隧道火灾：开双闪、留下车钥匙弃车逃生、紧急电话求救</p>
<p>长下坡，可间歇使用驻车制动器（<strong>手刹</strong>）辅助制动</p>
<p>上坡路，避免换挡、低档匀速前进、注意下行车辆</p>
<p>严重超载、胎压过高过低、漏气，都会造成爆胎</p>
]]></content>
    <author><name>Sun2ot</name></author>
    <category term="笔记"/>
  </entry>
  <entry>
    <title>深度学习之优化算法</title>
    <link href="https://blog.085404.xyz/posts/%E4%BC%98%E5%8C%96%E7%AE%97%E6%B3%95/" rel="alternate" type="text/html"/>
    <id>https://blog.085404.xyz/posts/%E4%BC%98%E5%8C%96%E7%AE%97%E6%B3%95/</id>
    <published>2023-06-08T00:00:00.000Z</published>
    <updated>2023-06-08T00:00:00.000Z</updated>
    <summary></summary>
    <content type="html"><![CDATA[<h2>随机梯度下降法 SGD</h2>
<blockquote>
<p>stochastic gradient descent</p>
</blockquote>
<p>假设红色部分为一个下凹空间，现在要前往空间的最低点。随机梯度下降法 SGD 低效的根本问题在于，每一步虽然都是立足于当前点的梯度方向（<strong>蓝线</strong>），但梯度的方向并不一定指向最小值的方向（<strong>黑线</strong>）。</p>
<p><img src="https://img.085404.xyz/images/dccef3271e1d3cbc947a58c43701910d.webp" alt="Screenshot_2023-06-08-20-07-55-817_com.newskyer.d.jpg" /></p>
<p><img src="https://img.085404.xyz/images/a40039d20a379e9c4786425e672e3016.webp" alt="image.png" /></p>
<blockquote>
<p>基于SGD的最优化的更新路径：呈“之”字形朝最小值(0, 0)移动，效率低</p>
</blockquote>
<pre><code>class SGD:

    """随机梯度下降法（Stochastic Gradient Descent）"""

    def __init__(self, lr=0.01):
        self.lr = lr
        
    def update(self, params, grads):
        for key in params.keys():
            params[key] -= self.lr * grads[key] 
</code></pre>
<h2>Momentum</h2>
<p>数学式表示
$$
\begin{align}
v &amp; \leftarrow \alpha v - \eta \frac{\partial L}{\partial W} \
W &amp; \leftarrow W + v
\end{align}
$$</p>
<p>$W$ 为要更新的权重参数， $\frac{\partial L}{\partial W}$ 表示损失函数关于 $W$ 的梯度， $\eta$ 表示学习率， $v$ 对应物理上的速度。</p>
<p><img src="https://img.085404.xyz/images/8d8d32e5869bc05d1629c4a105ef055b.webp" alt="Screenshot_2023-06-08-21-27-57-372_com.newskyer.d.png" /></p>
<p>当从 A 点沿梯度方向走到 B 点时，将 B 的梯度方向与动量项合并，作为新的更新方向</p>
<ol>
<li>如果 B 点梯度方向不变，由于加上了动量项，可以使 SGD 加快更新</li>
<li>如果 B 点梯度方向改变，动量项可以减弱更新幅度，防止发生较大改变导致更新不稳定</li>
</ol>
<p><img src="https://img.085404.xyz/images/e784c85ac4ce58d3d989cfaa94b61ce9.webp" alt="image.png" /></p>
<blockquote>
<p>基于 Momentum 的最优化的更新路径</p>
</blockquote>
<p>和 SGD 相比，我们发现“之”字形的“程度”减轻了。这是因为虽然 x 轴方向上受到的力非常小，但是<strong>一直在同一方向上受力</strong>，所以朝同一个方向会有一定的加速。</p>
<p>反过来，虽然 y 轴方向上受到的力很大，但是因为交互地受到正方向和反方向的力，它们会<strong>互相抵消</strong>，所以 y 轴方向上的速度不稳定。因此，和 SGD 时的情形相比，可以更快地朝 x 轴方向靠近，减弱“之”字形的变动程度。</p>
<h3>代码</h3>
<pre><code>class Momentum:

    """Momentum SGD"""

    def __init__(self, lr=0.01, momentum=0.9):
        self.lr = lr
        self.momentum = momentum
        self.v = None
        
    def update(self, params, grads):
        if self.v is None:
            self.v = {}
            for key, val in params.items():                                
                self.v[key] = np.zeros_like(val)
                
        for key in params.keys():
            self.v[key] = self.momentum*self.v[key] - self.lr*grads[key] 
            params[key] += self.v[key]
</code></pre>
<h2>AdaGrad</h2>
<blockquote>
<p>自适应地为各个维度的参数分配不同的学习率</p>
</blockquote>
<p>在神经网络的学习中，学习率过小，会导致学习花费过多时间；反过来，学习率过大，则会导致学习发散而不能正确进行。</p>
<p>在关于学习率的有效技巧中，有一种被称为<strong>学习率衰减</strong>（learning rate decay）的方法，即随着学习的进行，使学习率逐渐减小。AdaGrad（Ada 意为 Adaptive）会为参数的每个元素适当地调整学习率，与此同时进行学习。</p>
<p>$$
\begin{align}
h &amp; \leftarrow h + \frac{\partial L}{\partial W} \odot  \frac{\partial L}{\partial W} \
W &amp; \leftarrow W - \eta \frac1{\sqrt{h+\epsilon}} \frac{\partial L}{\partial W}
\end{align}
$$</p>
<p>$h$ 保存了以前的所有梯度值的平方和， $\odot$ 表示 element-wise 矩阵乘法（对应位置元素相乘）， $\epsilon$ 是一个极小值，防止出现分母为 0 的情况。</p>
<ul>
<li>优点：h 较小时，可以放大梯度；较大时，可以约束梯度（奖励+惩罚）</li>
<li>缺点：
<ul>
<li>梯度累积导致学习率单调递减，后期学习率极小</li>
<li>仍然需要设置一个合适的全局初始学习率</li>
</ul>
</li>
</ul>
<h3>代码</h3>
<pre><code>class AdaGrad:

    """AdaGrad"""

    def __init__(self, lr=0.01):
        self.lr = lr
        self.h = None
        
    def update(self, params, grads):
        if self.h is None:
            self.h = {}
            for key, val in params.items():
                self.h[key] = np.zeros_like(val)
            
        for key in params.keys():
            self.h[key] += grads[key] * grads[key]
            # 分母不得为0，加1e-7极小值
            params[key] -= self.lr * grads[key] / (np.sqrt(self.h[key]) + 1e-7)
</code></pre>
<p><img src="https://img.085404.xyz/images/c54b57952f6911e26ca7e2edda2a5934.webp" alt="image.png" /></p>
<blockquote>
<p>基于 AdaGrad 的最优化的更新路径</p>
</blockquote>
<h2>RMSProp</h2>
<blockquote>
<p>在 AdaGrad 基础上产生的改进版</p>
</blockquote>
<p>$$
\begin{align}
G_t = \textcolor{red}{\beta} G_{t-1} + \textcolor{red}{ (1-\beta) } g_t^2  \
\vartriangle \theta_t = - \frac \eta {G_t + \epsilon} g_t
\end{align}
$$</p>
<p>其中， $\beta$ 默认为 0.9。</p>
<p>可以看见，RMSProp 与 AdaGrad 不同，它通过增加一个<strong>衰减系数</strong>，只关注最近某一时间窗口内的下降梯度，来控制历史梯度信息的获取。</p>
<p>​ $G_t$ 是由历史平方梯度和当前平方梯度加权得到的平均数，而 $\beta$ 控制了历史平方梯度的权重：</p>
<ul>
<li>当 $\beta$ 较小时，平均数会对历史梯度进行平滑，从而减少历史梯度对参数更新的影响；</li>
<li>当 $\beta$ 较大时，平均数会对历史梯度进行加权平均，从而增加历史梯度对参数更新的影响。</li>
</ul>
<p>因此可以在一定程度上缓解 AdaGrad 在后期学习率太小这个问题。</p>
<h3>代码</h3>
<pre><code>class RMSprop:

    def __init__(self, lr=0.01, decay_rate = 0.99):
        self.lr = lr
        # 衰减系数
        self.decay_rate = decay_rate
        self.h = None
        
    def update(self, params, grads):
        if self.h is None:
            self.h = {}
            for key, val in params.items():
                self.h[key] = np.zeros_like(val)
            
        for key in params.keys():
            self.h[key] *= self.decay_rate
            self.h[key] += (1 - self.decay_rate) * grads[key] * grads[key]
            params[key] -= self.lr * grads[key] / (np.sqrt(self.h[key]) + 1e-7)
</code></pre>
<h2>Adam</h2>
<blockquote>
<p>Momentum + RMSProp = Adam</p>
</blockquote>
<p><img src="https://img.085404.xyz/images/b9f19f7b378532902cd597d9789303e6.webp" alt="image.png" /></p>
<p>Adam 采用两个不同的超参数 β1 和 β2 来控制动量以及 RMSProp 中指数加权移动平均 $s_t$ 的更新。</p>
<p>$$
\begin{align}
m_t &amp; = β_1 · m_{t-1} + (1 - β_1) · g_t \
v_t &amp; = β_2 · v_{t-1} + (1 - β_2) · (g_t⊙g_t) \
\theta_t &amp; = \theta_{t-1} - \frac {\eta \cdot \hat{m_t}} { \sqrt{\hat{v_t}} + \epsilon}
\end{align}
$$
通常，β1=0.9，β2=0.999，ϵ=1e−7</p>
<h3>初始化偏差修正</h3>
<p>Adam 的一个改进点是 Adam 对一阶矩估计和二阶矩估计进行了修正，使其近似为对期望的<strong>无偏估计</strong>。修正方式为：</p>
<p>$$
\begin{align}
\hat{m_t} = \frac {m_t} {1-\beta_1^t} \
\hat{v_t} = \frac {v_t} {1-\beta_2^t}
\end{align}
$$
下面对偏差修正进行解释。</p>
<p>通过下列推导得到在前面所有时间步上<strong>只包含梯度和衰减率的函数</strong>，即消去 v：</p>
<p><img src="https://img.085404.xyz/images/d4138ea7dafb01c8e08c8023fe848082.webp" alt="Screenshot_2023-06-09-11-26-29-585_com.newskyer.d.png" /></p>
<p><img src="https://img.085404.xyz/images/2261fdd2ff79ce6257071a69a79c6a0e.webp" alt="image.png" /></p>
<blockquote>
<p>E (aX) = aE (X)，好像是有这么个期望公式？概率论忘干净了 hhhh</p>
</blockquote>
<p><img src="https://img.085404.xyz/images/bb4d5fcf334a7e8fe9e3814c3a02ed5d.webp" alt="Screenshot_2023-06-09-11-35-35-472_com.newskyer.d.png" /></p>
<p>可以发现，当 t 取的很小时，例如 t=1，此时 $v_1 = 0.001g_1^2$ 。显然，在迭代初期，这个偏差很大，因此需要对其作出修正，即 $\hat{v_t} = \frac {v_t} {1-\beta_2^t}$ 。如此一来，当 t 取的很小时，可以起到放大的效果来修正偏差；当 t 足够大时， $\hat{v_t} \approx v_t$ ，即偏差修正几乎没有作用。</p>
<blockquote>
<p>对一阶动量的修正也是同理</p>
</blockquote>
<h3>代码</h3>
<pre><code>class Adam:

    """Adam (http://arxiv.org/abs/1412.6980v8)"""

    def __init__(self, lr=0.001, beta1=0.9, beta2=0.999):
        self.lr = lr
        self.beta1 = beta1
        self.beta2 = beta2
        self.iter = 0
        self.m = None
        self.v = None
        
    def update(self, params, grads):
        if self.m is None:
            self.m, self.v = {}, {}
            for key, val in params.items():
                self.m[key] = np.zeros_like(val)
                self.v[key] = np.zeros_like(val)
        
        self.iter += 1
        lr_t = self.lr * np.sqrt(1.0 - self.beta2**self.iter) / (1.0 - self.beta1**self.iter)
        
        for key in params.keys():
            self.m[key] = self.beta1*self.m[key] + (1-self.beta1)*grads[key]
            self.v[key] = self.beta2*self.v[key] + (1-self.beta2)*(grads[key]**2)
            
            params[key] -= lr_t * self.m[key] / (np.sqrt(self.v[key]) + 1e-7)
            
</code></pre>
<p><img src="https://img.085404.xyz/images/41b288a1e247b8b8ffc4283981c49ac2.webp" alt="image.png" /></p>
<blockquote>
<p>基于 Adam 的最优化的更新路径</p>
</blockquote>
<p>虽然 Momentun 也有类似的移动，但是相比之下，Adam 的 y 轴震荡程度有所减轻，这得益于学习的更新程度被适当地调整了。</p>
<h2>对比</h2>
<pre><code>import matplotlib.pyplot as plt
from d2l.mnist import load_mnist
from d2l.common.util import smooth_curve
from d2l.common.multi_layer_net import MultiLayerNet
from d2l.common.optimizer import *


# 0:读入MNIST数据==========
(x_train, t_train), (x_test, t_test) = load_mnist(normalize=True)

train_size = x_train.shape[0]
batch_size = 128
max_iterations = 2000


# 1:进行实验的设置==========
optimizers = {}
optimizers['SGD'] = SGD()
optimizers['Momentum'] = Momentum()
optimizers['AdaGrad'] = AdaGrad()
optimizers['Adam'] = Adam()
#optimizers['RMSprop'] = RMSprop()

networks = {}
train_loss = {}
for key in optimizers.keys():
    networks[key] = MultiLayerNet(
        input_size=784, hidden_size_list=[100, 100, 100, 100],
        output_size=10)
    train_loss[key] = []    


# 2:开始训练==========
for i in range(max_iterations):
    batch_mask = np.random.choice(train_size, batch_size)
    x_batch = x_train[batch_mask]
    t_batch = t_train[batch_mask]
    
    for key in optimizers.keys():
        grads = networks[key].gradient(x_batch, t_batch)
        optimizers[key].update(networks[key].params, grads)
    
        loss = networks[key].loss(x_batch, t_batch)
        train_loss[key].append(loss)
    
    if i % 100 == 0:
        print( "===========" + "iteration:" + str(i) + "===========")
        for key in optimizers.keys():
            loss = networks[key].loss(x_batch, t_batch)
            print(key + ":" + str(loss))


# 3.绘制图形==========
markers = {"SGD": "o", "Momentum": "x", "AdaGrad": "s", "Adam": "D"}
x = np.arange(max_iterations)
for key in optimizers.keys():
    plt.plot(x, smooth_curve(train_loss[key]), marker=markers[key], markevery=100, label=key)
plt.xlabel("iterations")
plt.ylabel("loss")
plt.ylim(0, 1)
plt.legend()
plt.show()

</code></pre>
<p><img src="https://img.085404.xyz/images/71b39c97106dd1fc6e34df4957444bd5.webp" alt="image.png" /></p>
<blockquote>
<p>基于 MNIST 数据集的4种更新方法的比较：横轴表示学习的迭代次数（iteration），纵轴表示损失函数的值（loss）</p>
</blockquote>
<h2>权重的初始值</h2>
<h3>初始权重可以为 0 吗</h3>
<p>将权重初始值设为 0 的话，将无法正确进行学习。严格地说，<strong>不能将权重初始值设成一样的值</strong>。因为在误差反向传播法中，所有的权重值都会进行相同的更新，因此，权重被更新为相同的值，并拥有了对称的值（重复的值）。</p>
<p>这使得神经网络拥有许多不同的权重的意义丧失了。为了防止“权重均一化”（严格地讲，是为了<strong>瓦解权重的对称结构</strong>），必须随机生成初始值。</p>
<h3>隐藏层的激活值分布</h3>
<pre><code>import numpy as np
import matplotlib.pyplot as plt


def sigmoid(x):
    return 1 / (1 + np.exp(-x))


def ReLU(x):
    return np.maximum(0, x)


def tanh(x):
    return np.tanh(x)

# 随机生成一个元素符合标准正态分布（高斯分布）的数组作为输入层
input_data = np.random.randn(1000, 100)  # 1000个数据
node_num = 100  # 各隐藏层的节点（神经元）数
hidden_layer_size = 5  # 隐藏层有5层
activations = {}  # 激活值的结果保存在这里

x = input_data

for i in range(hidden_layer_size):
    # 如果不是第一层，将前一层输出作为本层输入
    if i != 0:
        x = activations[i-1]

    # 重新生成一个100x100的高斯分布数组作为本层权重
    w = np.random.randn(node_num, node_num) * 1
    
    a = np.dot(x, w)

    # 激活后获得本层输出
    z = sigmoid(a)

    # 保存本层输出，以便下一层使用
    activations[i] = z

# 绘制直方图
for i, a in activations.items():
    # 按层数划分子图
    plt.subplot(1, len(activations), i+1)
    plt.title(str(i+1) + "-layer")
    # 非输入层，隐藏y轴上的刻度标签和刻度线
    if i != 0:
        plt.yticks([], [])
    # plt.xlim(0.1, 1)
    # plt.ylim(0, 7000)
    # 绘制直方图：a扁平化，直方图30条，统计(0,1)间的数据
    plt.hist(a.flatten(), 30, range=(0,1))
plt.show()

</code></pre>
<p>通过上述代码我们可以得到如下的数据分布图。其记载了一个 5 层神经网络，每层有 100 个神经元，激活函数使用 sigmoid，传入<strong>标准正态分布</strong>随机生成的数据后，用直方图绘制各层激活值的数据分布。</p>
<p><img src="https://img.085404.xyz/images/b7d3ae4c194117eb7e7c537cfa5a0a42.webp" alt="image.png" /></p>
<blockquote>
<p>如上图，各层的激活值呈现偏向 0 和 1 的分布</p>
</blockquote>
<p><img src="https://img.085404.xyz/images/93d09eac0bc3871ec4459ca560e3b26b.webp" alt="image.png" /></p>
<p>这是 sigmoid 函数的图像，可见它是一个 S 型函数，随着输出不断地靠近0（或者靠近1），它的导数的值逐渐接近0。因此，偏向0和1的数据分布会造成反向传播中梯度的值不断变小，最后消失。这个问题称为<strong>梯度消失</strong>（gradient vanishing）。层次加深的深度学习中，梯度消失的问题可能会更加严重。</p>
<p>那如果将高斯分布的标准差设为 0.01 呢？</p>
<pre><code>w = np.random.randn(node_num, node_num) * 0.01
</code></pre>
<p><img src="https://img.085404.xyz/images/581803a5dad0f52e908ccd08584f76b8.webp" alt="image.png" /></p>
<p>这次呈集中在0.5附近的分布。因为不像刚才的例子那样偏向0和1，所以不会发生梯度消失的问题。但是，激活值的分布有所偏向，说明在表现力上会有很大问题。</p>
<p>因为如果有多个神经元都输出几乎相同的值，那它们就没有存在的意义了。比如，如果100 个神经元都输出几乎相同的值，那么也可以由1个神经元来表达基本相同的事情。因此，<strong>激活值在分布上有所偏向会出现“表现力受限”的问题</strong>。</p>
<h4>Xavier 初始值</h4>
<p>Xavier Glorot 等人在论文中推荐了一种权重初始值，俗称“Xavier 初始值”。现在，在一般的深度学习框架中，Xavier 初始值已被作为标准使用。其推导出的结论是，<strong>如果前一层的节点数为 n，则初始值使用标准差为 $\frac1{\sqrt{n}}$ 的分布</strong>。</p>
<blockquote>
<p>显然，前一层节点数越多，n 越大，则方差越小，因此初始值的尺度就越小</p>
</blockquote>
<blockquote>
<p>[!warning]
Xavier 初始值是以激活函数是<strong>线性函数</strong>为前提推导出的</p>
</blockquote>
<p>将上文代码中的权重改为</p>
<pre><code>w = np.random.randn(node_num, node_num) * np.sqrt(1.0 / node_num)
</code></pre>
<p><img src="https://img.085404.xyz/images/b4084645a017149659108bf5e06a350a.webp" alt="image.png" /></p>
<p>越是后面的层，图像变得越歪斜，但是呈现了比之前更有广度的分布。因为各层间传递的数据有适当的广度，所以 sigmoid 函数的表现力不受限制，有望进行高效的学习。</p>
<blockquote>
<p>[!tip]
用作激活函数的函数最好具有关于原点对称的性质</p>
</blockquote>
<h4>ReLU 的权重初始值</h4>
<p>当激活函数使用 ReLU 时，一般推荐使用 ReLU 专用的初始值，也就是 Kaiming He 等人推荐的初始值，也称为 <strong>“He 初始值”</strong>。</p>
<p>当前一层的节点数为 n 时，He 初始值使用标准差为 $\sqrt{\frac2{n}}$ 的高斯分布。</p>
<blockquote>
<p>可以理解为，因为 ReLU 函数值域为非负数，因此为了使其具有更大的广度，需要使用 Xavier 初始值的 2 倍（更大的方差，更大的尺度）</p>
</blockquote>
<p><img src="https://img.085404.xyz/images/fef4a86553743215a8ab0831be6502e3.webp" alt="image.png" /></p>
<blockquote>
<p>权重初始值为标准差是 0.01 的高斯分布时</p>
</blockquote>
<p>神经网络上传递的是非常小的值，说明逆向传播时权重的梯度也同样很小。这是很严重的问题，实际上学习基本上没有进展。</p>
<p><img src="https://img.085404.xyz/images/0717e00345edf30e3a457e8d4a3ea041.webp" alt="image.png" /></p>
<blockquote>
<p>权重初始值为 Xavier 初始值时</p>
</blockquote>
<p>随着层的加深，偏向一点点变大。实际上，层加深后，激活值的偏向变大，学习时会出现梯即便层加深，数据的广度也能保持不变，因此逆向传播时，也会传递合适的值。度消失的问题。</p>
<p><img src="https://img.085404.xyz/images/fa0d72bf19073ccc423509af7b917103.webp" alt="image.png" /></p>
<blockquote>
<p>权重初始值为 He 初始值时</p>
</blockquote>
<p>即便层加深，数据的广度也能保持不变，因此逆向传播时，也会传递合适的值。</p>
<h4>基于 mnist 数据集的权重初始值比较</h4>
<p><img src="https://img.085404.xyz/images/0d673a8e4f205bd452a7ae497bbe710b.webp" alt="image.png" /></p>
<ul>
<li>标准差为 0.01 的正态分布压根没法学习，损失函数动都不动</li>
<li>Xavier 和 He 效果很好，且 He 学习进度更快（loss 下降的更快）</li>
</ul>
<h2>Batch Normalization</h2>
<p>Batch Normalization（批量归一化） 的思路是调整各层的激活值分布使其拥有适当的广度, 因此在 affine 层与 ReLU 层之间添加一个 Batch Norm 层以对神经网络中的数据进行正规化。</p>
<p><img src="https://img.085404.xyz/images/ec95eceebbf256d08830c7404c0209d8.webp" alt="image.png" /></p>
<p>其具体操作如下</p>
<p>$$
\begin{align}
\mu_B &amp; \leftarrow \frac1m \sum_{i=1}^m x_i \
\sigma_B^2 &amp; \leftarrow \frac1m \sum_{i=1}^m (x_i - \mu_B)^2 \
\hat{x_i} &amp; \leftarrow \frac{x_i - \mu_B}{  \sqrt{\sigma_B^2 + \epsilon}  }
\end{align}
$$</p>
<p>这里对 mini-batch 的 m 歌输入数据的集合 ${x_1, x_2, ..., x_m}$ 分别求均值 $\mu_B$ 和方差 $\sigma_B^2$ ，然后更新 $x_i$ ，将其<strong>正规化</strong>为均值为 0，方差为 1 的数据 ${\hat{x_1, x_2, ..., x_m}}$ ,以此来<strong>减小数据分布的倾向</strong>。$\epsilon$ 为极小值，防止出现除 0。</p>
<p>接着，Batch Norm 层会对正规化后的数据进行<strong>缩放和平移</strong>的变换，用数学式可以如下表示</p>
<p>$$
y_i \leftarrow \gamma \hat{x_i} + \beta
$$</p>
<p>这里， $\gamma$ 和 $\beta$ 是参数。一开始 $\gamma = 0$ ， $\beta = 1$ （因为这些值可以保持输出的原始分布）后经过学习调整为合适的值。</p>
<h3>推导过程详解</h3>
<ul>
<li>反向传播初始梯度为 $\frac {\partial L} {\partial{y_i}}$</li>
<li>需要计算 $\frac {\partial L} {\partial{x_i}}$ ， $\frac {\partial L} {\partial \gamma}$ ， $\frac {\partial L} {\partial \beta}$</li>
</ul>
<p><img src="https://img.085404.xyz/images/5050fe15cd11c0db43db3f5876657b1c.webp" alt="Screenshot_2023-06-10-13-24-59-958_com.newskyer.d.png" /></p>
<p><img src="https://img.085404.xyz/images/1cd5f6b16876c0d8ffaa30d19e002e8b.webp" alt="Screenshot_2023-06-10-13-25-12-733_com.newskyer.d.png" /></p>
<p>论文中，作者将 $\frac {\partial L} {\partial{x_i}}$ 拆分为如下三个部分</p>
<p>$$
\frac {\partial L} {\partial{x_i}} = \frac {\partial L} {\partial{\hat{x_i}}} \frac {\partial \hat{x_i}} {\partial{x_i}} + \frac {\partial L} {\partial{\sigma^2}} \frac {\partial \sigma^2} {\partial{x_i}} + \frac {\partial L} {\partial{\mu}}\frac {\partial \mu} {\partial{x_i}}
$$</p>
<p>下面是它们的推导过程</p>
<p><img src="https://img.085404.xyz/images/8bdd68f164939e38456e3f7081b8908c.webp" alt="Screenshot_2023-06-10-13-19-10-458_com.newskyer.d.png" /></p>
<p><img src="https://img.085404.xyz/images/32d7db4a79eb1132e949be7bb4a9114b.webp" alt="Screenshot_2023-06-10-13-19-23-721_com.newskyer.d.png" /></p>
<p><img src="https://img.085404.xyz/images/76285b1bb81a4c274af9e070da07084c.webp" alt="Screenshot_2023-06-10-13-19-40-642_com.newskyer.d.png" /></p>
<p>这里漏写了一个， $\frac {\partial \mu} {\partial x_i} = \frac 1 m$</p>
<blockquote>
<p>论文原文
<img src="https://img.085404.xyz/images/9959091ead54fd824728ea41d8b9c629.webp" alt="image.png" /></p>
</blockquote>
<h2>过拟合</h2>
<p>发生过拟合的原因，主要有以下两个。</p>
<ul>
<li>模型拥有大量参数、表现力强</li>
<li>训练数据少</li>
</ul>
<p>例如从 MNIST 数据集原本的6w个训练数据中只选定300个</p>
<p><img src="https://img.085404.xyz/images/b67412dab1aa7a272014a55c4e99f71b.webp" alt="image.png" /></p>
<blockquote>
<p>不使用权值衰减</p>
</blockquote>
<p>过了 100 个 epoch 左右后，用训练数据测量到的识别精度几乎都为 100%。但是，对于测试数据，离 100% 的识别精度还有较大的差距。如此大的识别精度差距，是只拟合了训练数据的结果。从图中可知，模型对训练时没有使用的一般数据（测试数据）拟合得不是很好。</p>
<h3>权值衰减</h3>
<p>权值衰减是一直以来经常被使用的一种抑制过拟合的方法。该方法通过在学习的过程中<strong>对大的权重进行惩罚，来抑制过拟合</strong>。很多过拟合原本就是因为权重参数取值过大才发生的。</p>
<p>如果权值为 $W$ ，则为<strong>损失函数</strong>加上权重的平方范数（<a href="../%E8%AF%8D%E6%9D%A1/L2%E8%8C%83%E6%95%B0.md">L2范数</a>），即 $\frac12 \lambda W^2$ 。因此，在求权重梯度的计算中，要为之前的<strong>误差反向传播法的结果</strong>加上正则化项的导数 $\lambda W$</p>
<ul>
<li>$\lambda$ 是控制正则化强度的超参数，设置得越大，对大的权重施加的惩罚就越重。</li>
<li>$\frac12$ 是为了 $\frac12 \lambda W^2$ 的求导结果变为 $\lambda W$ 而使用的调整常量</li>
</ul>
<pre><code>def loss(self, x, t):

    y = self.predict(x)

    weight_decay = 0
    for idx in range(1, self.hidden_layer_num + 2):
        W = self.params['W' + str(idx)]
        # 权重衰减项会在每次更新时被累加，以确保越来越多的权重被惩罚
        weight_decay += 0.5 * self.weight_decay_lambda * np.sum(W ** 2)

    return self.last_layer.forward(y, t) + weight_decay
</code></pre>
<p><img src="https://img.085404.xyz/images/69a7af1f985194b97cde312d925bc866.webp" alt="image.png" /></p>
<blockquote>
<p>使用了权值衰减</p>
</blockquote>
<p>虽然训练数据的识别精度和测试数据的识别精度之间有差距，但是与没有使用权值衰减的结果相比，差距变小了。这说明<strong>过拟合受到了抑制</strong>。</p>
<h3>Dropout</h3>
<p>Dropout 是一种在学习的过程中<strong>随机删除神经元</strong>的方法。训练时，随机选出隐藏层的神经元，然后将其删除。<strong>被删除的神经元不再进行信号的传递</strong>。训练时，每传递一次数据，就会随机选择要删除的神经元。然后，测试时，虽然会传递所有的神经元信号，但是对于各个神经元的输出，要乘上训练时的删除比例后再输出。</p>
<p><img src="https://img.085404.xyz/images/07ac9e38d944e5b9c791a6b8f0778d1a.webp" alt="image.png" /></p>
<blockquote>
<p>不听话？不听话就夹你！</p>
</blockquote>
<pre><code>class Dropout:

    def __init__(self, dropout_ratio=0.5):
        self.dropout_ratio = dropout_ratio
        self.mask = None

    def forward(self, x, train_flg=True):
        if train_flg:
            # 生成的同型数组中，大于ratio的置为True，否则为False
            self.mask = np.random.rand(*x.shape) &gt; self.dropout_ratio
            # 与x相乘后，为True的保留，为False的被置为0，达到drop节点的作用
            return x * self.mask
        else:
            return x * (1.0 - self.dropout_ratio)

    def backward(self, dout):
        # 反向传播时，行为与ReLU相同，即  
        # 正向传播时传递了信号的神经元，反向传播时按原样传递信号  
        # 正向传播时没有传递信号的，反向传播时信号将停在那里
        return dout * self.mask
</code></pre>
<p><img src="https://img.085404.xyz/images/d8a67f55757dbbd1e81e01b2623dd53f.webp" alt="image.png" /></p>
<blockquote>
<p>没有使用 Dropout</p>
</blockquote>
<p><img src="https://img.085404.xyz/images/897a281af5aa7e3185ca767350dc1744.webp" alt="image.png" /></p>
<blockquote>
<p>使用了 Dropout</p>
</blockquote>
<p>通过使用 Dropout，即便是表现力强的网络，也可以抑制过拟合。</p>
<blockquote>
<p>机器学习中经常使用<strong>集成学习</strong>。所谓集成学习，就是让<strong>多个模型单独进行学习</strong>，推理时再取多个模型的输出的平均值。</p>
<p>用神经网络的语境来说，比如，准备 5个结构相同（或者类似）的网络，分别进行学习，测试时，以这 5个网络的输出的平均值作为答案。实验告诉我们，通过进行集成学习，神经网络的识别精度可以提高好几个百分点。</p>
<p>这个集成学习与 Dropout 有密切的关系。这是因为可以将 Dropout 理解为，通过在学习过程中随机删除神经元，从而<strong>每一次都让不同的模型进行学习</strong>。并且，推理时，通过对神经元的输出乘以删除比例（比如，0.5等），可以取得模型的平均值。也就是说，可以理解成，Dropout 将集成学习的效果（模拟地）通过一个网络实现了。</p>
</blockquote>
<h2>超参数</h2>
<p>超参数是指，比如各层的神经元数量、batch 大小、参数更新时的学习率或权值衰减等。如果这些超参数没有设置合适的值，模型的性能就会很差。</p>
<p>调整超参数时，必须使用超参数专用的确认数据，一般称为<strong>验证数据</strong>（validation data）。我们使用这个验证数据来评估超参数的好坏。<strong>比较理想的是只用一次验证数据</strong>。</p>
<blockquote>
<p>[!warning]
不能使用测试数据评估超参数的性能，否则超参数的值会对测试数据发生<strong>过拟合</strong>！</p>
</blockquote>
<p>根据不同的数据集，有的会事先分成训练数据、验证数据、测试数据三部分，有的只分成训练数据和测试数据两部分，有的则不进行分割。在这种情况下，用户需要自行进行分割。例如用训练数据的20%作为验证数据。</p>
<pre><code>def shuffle_dataset(x, t):
    """
    打乱数据集
    """
    # 随机排列一个数组或者一个整数范围内的数。它返回一个新的、随机排列的数组
    permutation = np.random.permutation(x.shape[0])
    x = x[permutation, :] if x.ndim == 2 else x[permutation, :, :, :]
    t = t[permutation]

    return x, t
</code></pre>
<p>超参数的范围只要 <strong>“大致地指定”</strong> 就可以了。所谓“大致地指定”，是指像0.001（ $10^{−3}$ ）到1000（ $10^3$ ）这样，以“10的阶乘”的尺度指定范围，也表述为“用对数尺度（log scale）指定”。</p>
<pre><code># 指定搜索的超参数的范围
# 生成一个在区间 [-8, -4) 内均匀分布的随机浮点数
weight_decay = 10 ** np.random.uniform(-8, -4)
lr = 10 ** np.random.uniform(-6, -2)
</code></pre>
<p>在超参数的最优化中，要注意的是<strong>深度学习需要很长时间</strong>（比如，几天或几周）。因此，在超参数的搜索中，需要尽早放弃那些不符合逻辑的超参数。于是，在超参数的最优化中，减少学习的 epoch，缩短一次评估所需的时间是一个不错的办法。</p>
<pre><code>Best-1(val acc:0.79) | lr:0.008381149872316536, weight decay:1.2740132675595203e-07
Best-2(val acc:0.61) | lr:0.006798932859815733, weight decay:5.427724799985068e-05
Best-3(val acc:0.52) | lr:0.003081830866896804, weight decay:3.424534700612302e-06
Best-4(val acc:0.47) | lr:0.003997839005016555, weight decay:1.3090155201683637e-06
Best-5(val acc:0.33) | lr:0.001760154887955398, weight decay:6.227114725290454e-07
Best-6(val acc:0.32) | lr:0.002993807782496189, weight decay:7.564414060749376e-07
Best-7(val acc:0.25) | lr:0.0018178836880097615, weight decay:1.4716823735556433e-05
</code></pre>
<p>例如一次训练后，我们发现学习率在0.001到0.01，权值衰减系数在 $10^{-7}$ 到 $10^{-5}$ 之间时，学习效果不错。因此，在这个缩小的范围中重复相同的操作，这样就能缩小到合适的超参数的存在范围，然后在某个阶段，选择一个最终的超参数的值。</p>
<p><img src="https://img.085404.xyz/images/033b8a84795b35c695d9a02f9afdb662.webp" alt="image.png" /></p>
]]></content>
    <author><name>Sun2ot</name></author>
    <category term="笔记"/>
  </entry>
  <entry>
    <title>文献管理软件 Zotero</title>
    <link href="https://blog.085404.xyz/posts/%E6%96%87%E7%8C%AE%E7%AE%A1%E7%90%86%E8%BD%AF%E4%BB%B6zotero/" rel="alternate" type="text/html"/>
    <id>https://blog.085404.xyz/posts/%E6%96%87%E7%8C%AE%E7%AE%A1%E7%90%86%E8%BD%AF%E4%BB%B6zotero/</id>
    <published>2023-06-06T00:00:00.000Z</published>
    <updated>2023-06-06T00:00:00.000Z</updated>
    <summary></summary>
    <content type="html"><![CDATA[<h2>一、下载</h2>
<p><a href="https://www.zotero.org/">https://www.zotero.org/</a></p>
<p>一共包含两个部分，一个是 zotero 软件，还有一个是配套的插件（chromium 内核浏览器与 Firefox 均适配）</p>
<h2>二、文献导入</h2>
<h3>1. 从 endnote 导出，再导入 zotero</h3>
<p>如果之前用的 endnote，想转到 zotero，可以从 endnote 以 <code>bibtex</code> 格式导出为 <code>xml</code> 文件（没有 bibtex 选项就去 edit-output style 里面启用），然后将 <code>xml</code> 文件导入 zotero 即可。</p>
<p><img src="https://img.085404.xyz/images/88c86a89c01581bbc28628870a3834c8.webp" alt="image. png" /></p>
<p><img src="https://img.085404.xyz/images/1f9bc98122034dd0350a31eec56280f6.webp" alt="image.png" /></p>
<h3>2. 从浏览器插件导入</h3>
<p>任意进入一篇文献</p>
<p><img src="https://img.085404.xyz/images/5db8c1b5db7b330f7a5e5fdae1f29cd2.webp" alt="image.png" /></p>
<p>点击右上角 zotero 插件即可 <code>save to zotero</code></p>
<p><img src="https://img.085404.xyz/images/2273afa8579f233d26711b849bc6126f.webp" alt="image.png" /></p>
<blockquote>
<p>通常情况下，外文文献网站都没啥问题，中文文献都是问题。。。可通过github项目<a href="https://github.com/l0o0/translators_CN">Zotero translators 中文维护小组</a>更新zotero转译组件（插件导入文献本质上就是js脚本，不过中国总是特色。。。）。还是无法获取文献的话请前往github提交issue。</p>
</blockquote>
<h3>3. 从剪切板导入（上述方法2不行的话，推荐这一种，也很方便）</h3>
<p>以知网为例，引用文献时，以 endnote 格式导出，复制字符串</p>
<p><img src="https://img.085404.xyz/images/fc1d8969c3a452fbcf2bb97a2e77dc79.webp" alt="image.png" /></p>
<p>进入 zotero，选择 <code>从剪切板导入</code> ，或者快捷键 <code>ctrl + shift + alt + i</code> （<strong>此快捷键非全局快捷键，需进入 zotero！</strong>）</p>
<blockquote>
<p>不是说非得按endnote格式导出，只是这个是老字号，国内外各平台都有罢了。其余的格式也可以试试，比如<code>Refworks</code>格式也是可以的。</p>
</blockquote>
<p><img src="https://img.085404.xyz/images/621927603d27f292281e1b113b46ebf4.webp" alt="image.png" /></p>
<h2>三、修改文献引用格式</h2>
<p>进入 <code>首选项</code></p>
<p><img src="https://img.085404.xyz/images/ac657159fc4d5d0ffbeaa064577bc82a.webp" alt="image.png" /></p>
<p>通常情况下，zotero 不会内置 <code>国标7714</code> （国内论文的参考文献格式，具体是哪个年份的，比如2005或者2015看你自己的需求），需要自行添加</p>
<p><img src="https://img.085404.xyz/images/911b1c6bf34790b3a95bcd09d329db43.webp" alt="image.png" /></p>
<p>在弹出的搜索界面检索 <code>gb/t</code> ，选择 <code>2005(numeric)</code> （通常情况下，高校都是顺序编码制，即 numeric）</p>
<p><img src="https://img.085404.xyz/images/de922d0fd3c6b4c032b85c7856f3aab6.webp" alt="image.png" /></p>
<h3>自定义引用格式下载</h3>
<p><a href="https://www.aliyundrive.com/s/sDXRgJFCnpn">chinese-gb7714-2005-numeric.csl</a></p>
<h2>四、word 中使用 zotero</h2>
<p>zotero 安装成功后，word 中会新增插件选项卡</p>
<p><img src="https://img.085404.xyz/images/90ec812e53b8493710ad7d924c6e301e.webp" alt="image.png" /></p>
<blockquote>
<p>注意 Citation 和 Bibliography 之间的区别：前者为引文，即正文中右上角的文献角标序号，后者为参考书目，即文末参考文献里面的具体文献。<strong>选择</strong> <code>Add/Edit Citation</code> <strong>只会在正文中添加角标序号，需将光标移动至需要添加参考文献的段落，然后</strong> <code>Add/Edit Bibliography</code> <strong>即可。</strong></p>
</blockquote>
<p>如果已经添加了部分文献，然后需修改他们的顺序，或者（例如）在[1]和[2]之间添加新的引文，直接add即可，zotero会自动更新全文的顺序（这也是为什么要用文献管理软件的重要原因之一！）</p>
<blockquote>
<p>初次在word使用zotero时，会让你选择文献格式，选中<strong>第三步</strong>中添加的国标样式即可。</p>
</blockquote>
<h2>五、其他功能</h2>
<p>zotero不仅是免费的，更是开源的！开源！这意味着它会拥有丰富的插件可供使用，见<a href="https://www.zotero.org/support/plugins">zotero插件</a>。</p>
<p>除了插件以外，zotero还内置了一些常用功能。</p>
<h3>1. 点击右上角图标可自行选择主界面的文献显示栏目</h3>
<p><img src="https://img.085404.xyz/images/1040426842ac5576adb0d52b4d2250ca.webp" alt="image.png" /></p>
<h3>2. 右侧的笔记可自行添加文献笔记</h3>
<p><img src="https://img.085404.xyz/images/5fc62dc20d3f14fd581401a7d05b41f0.webp" alt="image.png" /></p>
<blockquote>
<p>通过插件可支持<code>markdown</code>语法，操作见百度（本人野鸡本科，用不上）</p>
</blockquote>
<h3>3. 添加文献标签</h3>
<p>见右侧栏</p>
<p><img src="https://img.085404.xyz/images/6f9cfed43a2e30130b765048aff3a12e.webp" alt="image.png" /></p>
<p>点击 <code>Add</code> 可任意添加若干标签，该标签可于页面左下角见</p>
<p><img src="https://img.085404.xyz/images/6f9cfed43a2e30130b765048aff3a12e.webp" alt="image.png" /></p>
<p>选择某个标签，可筛选出标记了该标签的所有文献</p>
<p><img src="https://img.085404.xyz/images/3728c44203e5dcbec27465a0b8720845.webp" alt="image.png" />
<img src="https://img.085404.xyz/images/b517ba588922651e0a376e9279e7f2c2.webp" alt="image.png" /></p>
<blockquote>
<p>这个功能还是挺好用的，每个技术对应的文献可以分别打上不同的tag。通过浏览器插件导入的文献有时会自带标签，可以删除后自行添加。</p>
</blockquote>
<p>退出标签筛选，只需重新点击左下角的 tag 即可。</p>
<h3>4. 同步</h3>
<p>右上角同步按钮，点击后注册账户即可同步</p>
<p><img src="https://img.085404.xyz/images/fcbb3c81e62bb66943c5bad37ff1efa3.webp" alt="image.png" /></p>
<blockquote>
<p>不清楚要不要翻墙，因为我7x24小时开着代理</p>
</blockquote>
<p>同步内容包含两个，一是文献库，二是文件的附件（文献对应的 PDF、笔记等等）。后者可通过官方服务器同步，限额300M，也可以通过 webdav。</p>
<p>如果你真的有额外文件要同步，个人建议webdav，至于是dropbox还是坚果云或者别的什么，那就各有所好了。</p>
<h2>六、FAQ</h2>
<h3>1. 参考文献中的外文作者名字全部是大写</h3>
<p><a href="https://blog.csdn.net/qq_36501182/article/details/88666849">https://blog.csdn.net/qq_36501182/article/details/88666849</a></p>
<h3>2. 参考文献中的外文作者 lastname 为缩写</h3>
<p><code>.csl</code> 样式文件中，搜索 <code>initialize</code> （也有可能是 <code>initialize-with</code> ），将其修改为 <code>initialize="false"</code> 即可。</p>
]]></content>
    <author><name>Sun2ot</name></author>
    <category term="教程"/>
  </entry>
  <entry>
    <title>浏览器cookie迁移</title>
    <link href="https://blog.085404.xyz/posts/%E6%B5%8F%E8%A7%88%E5%99%A8cookie%E8%BF%81%E7%A7%BB/" rel="alternate" type="text/html"/>
    <id>https://blog.085404.xyz/posts/%E6%B5%8F%E8%A7%88%E5%99%A8cookie%E8%BF%81%E7%A7%BB/</id>
    <published>2023-06-04T00:00:00.000Z</published>
    <updated>2023-06-04T00:00:00.000Z</updated>
    <summary>一种可以迁移浏览器cookie的方法，能够将一个浏览器的登录信息切换到另一个浏览器上，实现无缝对接，避免诸多账号重新登陆的麻烦。</summary>
    <content type="html"><![CDATA[<p>软件出处为<a href="https://www.52pojie.cn/forum.php?mod=viewthread&amp;tid=1516149">吾爱破解</a></p>
<p>也可以直接前往作者提供的<a href="https://www.lanzoui.com/it2eSu9f6af?w">下载地址</a></p>
<p>当然，优质资源自己也要做好备份的。如果上述链接挂了，可邮箱联系我发送。</p>
<h2>使用方式</h2>
<p>解压后，会看到 <code>.exe</code> 文件，双击后会在当前目录下生成两个 <code>.dll</code></p>
<p><img src="https://img.085404.xyz/images/9d19591540c1a3197381e6b8de6babd9.webp" alt="image.png" /></p>
<p>在上面的选择栏中，选中<strong>迁出</strong> cookie 的浏览器，例如 edge 的路径 <code>"C:\Program Files (x86)\Microsoft\Edge\Application\msedge.exe"</code></p>
<p>在下面的选择栏中，选中<strong>迁入</strong> cookie 的浏览器，例如 Chrome 的路径 `"C:\Program Files\Google\Chrome\Application\chrome.exe"</p>
<p>点击迁移，工具左上角会显示进度数值 <code>已完成数/总cookie数</code> ，耐心等待一会即可。</p>
<p><img src="https://img.085404.xyz/images/d6a665b2eef7a5fac51d0e8d42587604.webp" alt="image.png" /></p>
]]></content>
    <author><name>Sun2ot</name></author>
    <category term="教程"/>
  </entry>
  <entry>
    <title>Windows Terminal 优化</title>
    <link href="https://blog.085404.xyz/posts/windows-terminal-%E4%BC%98%E5%8C%96/" rel="alternate" type="text/html"/>
    <id>https://blog.085404.xyz/posts/windows-terminal-%E4%BC%98%E5%8C%96/</id>
    <published>2023-05-28T00:00:00.000Z</published>
    <updated>2023-05-28T00:00:00.000Z</updated>
    <summary>在Windows平台配置scoop包管理工具，并借助其为PowerShell安装模块，以提升使用体验感，达到类zsh的效果。</summary>
    <content type="html"><![CDATA[<h2>Scoop</h2>
<blockquote>
<p>官网：<a href="https://scoop.sh/">Scoop</a></p>
</blockquote>
<h3>下载</h3>
<pre><code># 下载安装脚本
irm get.scoop.sh -outfile 'install.ps1'

# 查看所有可设置项
.\install.ps1 -?
</code></pre>
<blockquote>
<p>[!tip]
原脚本在 github 上，如果天朝环境下载不同，可以添加代理参数
<code>irm get.scoop.sh -Proxy 'http://&lt;ip:port&gt;' -outfile 'install.ps1'</code></p>
</blockquote>
<p>脚本下载后的存储路径取决于<strong>当前命令行的所在路径</strong>。</p>
<h3>安装</h3>
<p><a href="https://github.com/ScoopInstaller/Install#readme">ScoopInstaller/Install: 📥 Next-generation Scoop (un)installer (github.com)</a>
可以自定义 scoop 的安装路径，以及通过 scoop 下载的软件的路径</p>
<pre><code>.\install.ps1 -ScoopDir 'D:\Applications\Scoop' -ScoopGlobalDir 'F:\GlobalScoopApps' -Proxy 'http://ip:port'
</code></pre>
<h3>设置下载代理</h3>
<pre><code>scoop config proxy ip:port
</code></pre>
<p>不要加协议，如 <code>http://xxx</code></p>
<h3>下载软件</h3>
<pre><code>scoop search name
scoop install name
</code></pre>
<h2>gsudo</h2>
<blockquote>
<p>项目地址：<a href="https://github.com/gerardog/gsudo">gerardog/gsudo: Sudo for Windows (github.com)</a></p>
</blockquote>
<p>Windows 出于安全性等问题考虑，是不允许直接将 shell 切换为管理员模式的，因此有时会出现如下尴尬场景：</p>
<ol>
<li>有的指令需要管理员权限，但已经进入路径了才想起来，必须重新开新窗口再一路 cd 进来；</li>
<li>为了图方便，将 terminal 设置为了<strong>默认以管理员权限启动</strong>。但有的指令又不可以在管理员权限下执行，此时再想简单启动普通环境就做不到了，必须重新去 terminal 里设置。</li>
</ol>
<p><code>gsudo</code> 是一个 powershell 模块，可以很好的解决这个问题。安装好后，可以像 Linux 下的 <code>sudo</code> 一样，直接提权！</p>
<h3>安装</h3>
<pre><code># 安装
scoop install gsudo
</code></pre>
<h3>配置</h3>
<p>在 Windows PowerShell 的 <code>$profile</code> 中添加一行如下内容使模块生效</p>
<ol>
<li>打开 <code>$profile</code></li>
</ol>
<pre><code># 电脑装了 vscode 就用这个
code $profile
# 没有 vscode 就用这个
notepad $profile
</code></pre>
<ol>
<li>插入</li>
</ol>
<pre><code># 插入内容
Import-Module (Get-Command 'gsudoModule.psd1').Source
</code></pre>
<p><strong>重启</strong> terminal 即可。</p>
<blockquote>
<p>不知道 <code>$profile</code> 是啥？见我之前的 <a href="https://blog.085404.xyz/oh-my-posh.html#%E9%85%8D%E7%BD%AE">oh-my-posh 命令行美化</a></p>
</blockquote>
<h2>z.lua</h2>
<blockquote>
<p>国人开发，见 <a href="https://github.com/skywind3000/z.lua/blob/master/README.cn.md">z.lua/README.cn.md at master · skywind3000/z.lua · GitHub</a></p>
</blockquote>
<blockquote>
<p>[!quote]
z.lua 是一个快速路径切换工具，它会跟踪你在 shell 下访问过的路径，通过一套称为 Frecent 的机制（源自 FireFox），经过一段简短的学习之后，z.lua 会帮你跳转到所有匹配正则关键字的路径里 Frecent 值最高的那条路径去。</p>
</blockquote>
<p>说人话就是，你再也不用被 <code>cd</code> 折磨了！</p>
<h3>安装</h3>
<pre><code># 安装
scoop install z.lua
</code></pre>
<p><code>z.lua</code> 是需要 <code>lua</code> 环境的。但包管理器的优势就在这里，你不需要去考虑依赖问题， <code>scoop</code> 会帮你自动下载并配置 <code>lua</code> 。</p>
<h3>配置</h3>
<p>同上文一样，在 <code>$profile</code> 中添加一行</p>
<pre><code>Invoke-Expression (&amp; { (lua '/path/to/z.lua' --init powershell) -join "`n" })
</code></pre>
<p>将你的 <code>z.lua</code> <strong>文件路径</strong>替换进去即可。安装路径在上文安装 <code>scoop</code> 时曾设置过。如果你没设置，或者不记得了，可以通过以下指令<strong>查看某个指定 app 的路径</strong></p>
<pre><code>scoop prefix z.lua
</code></pre>
<blockquote>
<p>[!warning]
该指令只会显示到父路径，而配置 <code>z.lua</code> 需要指定具体的文件路径。</p>
</blockquote>
<p>同样，别忘了重启 terminal。以下是效果演示</p>
<p><img src="https://img.085404.xyz/images/396400dc51df7c9a88b89c3b7df55a62.gif" alt="演示效果" /></p>
]]></content>
    <author><name>Sun2ot</name></author>
    <category term="教程"/>
  </entry>
  <entry>
    <title>腾讯云 Coding DevOps 部署 Hexo</title>
    <link href="https://blog.085404.xyz/posts/%E8%85%BE%E8%AE%AF%E4%BA%91-coding-devops-%E9%83%A8%E7%BD%B2-hexo/" rel="alternate" type="text/html"/>
    <id>https://blog.085404.xyz/posts/%E8%85%BE%E8%AE%AF%E4%BA%91-coding-devops-%E9%83%A8%E7%BD%B2-hexo/</id>
    <published>2023-05-24T00:00:00.000Z</published>
    <updated>2023-05-24T00:00:00.000Z</updated>
    <summary>通过集成化云端开发平台，实现 Hexo 博客资源发布到Git平台后，自动checkout代码并部署环境，构建出静态资源文件后，上传至腾讯云COS对象存储平台进行托管。</summary>
    <content type="html"><![CDATA[<h2>存储桶设置</h2>
<p>创建存储桶</p>
<ol>
<li>公有读私有写（暂时，后续配置 CDN 即可私有读写）</li>
<li>配置管理权限
<img src="https://img.085404.xyz/images/24d233f0ac6ad98fb156835d93c1584b.webp" alt="image.png" /></li>
</ol>
<blockquote>
<p>[!tip]
记录下这个子账户的 SecretId 和 SecretKey，后面要用</p>
</blockquote>
<p>开启该存储桶的<strong>静态网站</strong>权限，否则你的 hexo 静态资源文件将无法作为 HTML 在公网预览。</p>
<p><img src="https://img.085404.xyz/images/630f110576751a62cff05284d24e06a8.webp" alt="image.png" /></p>
<h2>自动化部署</h2>
<h3>创建构建计划</h3>
<p>在腾讯云控制台搜索 <code>Coding DevOps</code>，点击 <code>立即使用</code></p>
<p><img src="https://img.085404.xyz/images/9f0c3c2ac0fb47b2ac8c79448f88732d.webp" alt="image.png" /></p>
<p>进入后创建新项目</p>
<p><img src="https://img.085404.xyz/images/562430866018a7b23d9912fef2535846.webp" alt="image.png" /></p>
<p>按需选择三样即可</p>
<p><img src="https://img.085404.xyz/images/1eecb53765685fe85a04ee5bc995d7cc.webp" alt="image.png" /></p>
<p>关联 hexo 的代码仓库</p>
<p><img src="https://img.085404.xyz/images/0b893852cc75e48211539753a511b2a8.webp" alt="image.png" /></p>
<p>进入左侧菜单 <code>持续集成</code> ，然后 <code>创建构建计划</code></p>
<p><img src="https://img.085404.xyz/images/afbad5f18115c944b7bfefd9ab570d2b.webp" alt="image.png" /></p>
<p>选择 <code>React + COS</code> 模板即可</p>
<blockquote>
<p>只是要用它这个形式，因为和我们的需求有相当大的重复</p>
</blockquote>
<p><img src="https://img.085404.xyz/images/c0f7c4acfcb1ca10a39c3058974a999d.webp" alt="image.png" /></p>
<ul>
<li>计划名称随便设置</li>
<li>代码仓库选择<strong>刚刚关联的</strong>自己的仓库</li>
</ul>
<p><img src="https://img.085404.xyz/images/37845e76434397905abe57e74f39fc80.webp" alt="image.png" /></p>
<p>不用纠结于依赖构建等指令，后面都是要改的。
Hexo 没有单元测试，故关闭。
上文记录下的存储桶的子用户信息，填在这里（见 <code>存储桶设置</code> - <code>配置管理权限</code> ）</p>
<p><img src="https://img.085404.xyz/images/27ed68e8c30f02aa5f81dda44289fb35.webp" alt="image.png" /></p>
<blockquote>
<p>[!warning]
记得取消勾选下面的立即构建，还有很多设置项没修改，构建必失败。</p>
</blockquote>
<h3>设置构建计划</h3>
<h4>更新 Node.js</h4>
<blockquote>
<p>[!bug]
截止本文编辑时，最新版 hexo 要求 Node. js&gt;14，Coding 提供的构建节点是 v10，所以不更新会<strong>构建失败</strong>！</p>
</blockquote>
<blockquote>
<p>更多 Coding 环境版本说明见<a href="https://coding.net/help/docs/ci/node/env.html">默认节点环境｜ CODING DevOps</a></p>
</blockquote>
<p>进入<code>设置</code></p>
<p><img src="https://img.085404.xyz/images/6c769cea73b30f7e9b96570f7a7500d5.webp" alt="image.png" /></p>
<p>进入 <code>流程配置</code> ，选择 <code>文本编辑器</code></p>
<p><img src="https://img.085404.xyz/images/6ec929ba649aee2a62d297f144465ced.webp" alt="image.png" /></p>
<p>添加一个新的 <code>stage</code></p>
<pre><code>stage('更新Node.js') {
  steps {
    sh 'rm -rf /usr/lib/node_modules/npm/'
    dir('/root/.cache/downloads') {
      sh 'wget -nc "https://coding-public-generic.pkg.coding.net/public/downloads/node-linux-x64.tar.xz?version=v16.13.0" -O node-v16.13.0-linux-x64.tar.xz | true'
      sh 'tar -xf node-v16.13.0-linux-x64.tar.xz -C /usr --strip-components 1'
    }
    sh 'node -v'
  }
}
</code></pre>
<p>注意添加的位置！</p>
<p><img src="https://img.085404.xyz/images/99c467f610a750e6fb36a67a86d4b42c.webp" alt="image.png" /></p>
<h4>安装依赖</h4>
<p>切回到 <code>图形化编辑器</code> ，来到 <code>3-1 安装依赖</code> ，点击 <code>执行shell脚本</code> 。
将其内容修改为如下内容</p>
<blockquote>
<p>[!warning]
记得将主题部分，更换成你自己使用的主题。安装方式在你所用的主题官网上找。</p>
</blockquote>
<blockquote>
<p>[!tip]
如果你没有修改主题的源文件，采用的是外部主题配置文件的方式修改主题，可以直接照抄我的作业。</p>
<p>如果你修改了主题的源文件，可以考虑上传到你自己的仓库里去。</p>
</blockquote>
<pre><code># 安装hexo
npm install -g hexo-cli
# 安装依赖
npm install
# 安装主题
git clone -b master https://github.com/jerryc127/hexo-theme-butterfly.git themes/butterfly
</code></pre>
<p><img src="https://img.085404.xyz/images/e92b97d58b43d2fc46a17845280f189b.webp" alt="image.png" /></p>
<h4>构建静态资源文件</h4>
<p>操作同上，略</p>
<p><img src="https://img.085404.xyz/images/1be38ad38f24b26e1eb00329a59305ce.webp" alt="image.png" /></p>
<h4>上传到 COS</h4>
<p>对应的 <code>5-1</code> 部分，<strong>无需修改</strong>。</p>
<p><img src="https://img.085404.xyz/images/a42b8f11103751dc8d3b17e5478472b2.webp" alt="image.png" /></p>
<blockquote>
<p>配置完后，记得点击右上角 <code>保存</code></p>
</blockquote>
<h3>修改触发规则</h3>
<p>如果是 github 托管的代码，默认是 <code>main</code> ，不是 <code>master</code></p>
<p><img src="https://img.085404.xyz/images/84e513591d7616e553bedfb66934b438.webp" alt="image.png" /></p>
<h3>修改环境变量</h3>
<p>将上传的文件夹修改为 <code>./public/</code></p>
<p><img src="https://img.085404.xyz/images/6da87fd9eb749426256495e383411e6c.webp" alt="image.png" /></p>
<h3>触发构建计划</h3>
<p>有两种方式，一种是手动点击右上角 <code>立即构建</code> 。或者手动提交一次代码到 <code>main</code> 分支，就能触发上方设置的触发规则。</p>
<p>成功后，根据控制台打印的链接，即可访问。</p>
<p><img src="https://img.085404.xyz/images/5ab0d013c767a439ab05edcecc7ee47c.webp" alt="image.png" /></p>
<p>或者通过存储桶的访问节点进入也可。</p>
<p><img src="https://img.085404.xyz/images/630f110576751a62cff05284d24e06a8.webp" alt="image.png" /></p>
]]></content>
    <author><name>Sun2ot</name></author>
    <category term="教程"/>
  </entry>
  <entry>
    <title>oh-my-posh 命令行美化</title>
    <link href="https://blog.085404.xyz/posts/oh-my-posh-%E5%91%BD%E4%BB%A4%E8%A1%8C%E7%BE%8E%E5%8C%96/" rel="alternate" type="text/html"/>
    <id>https://blog.085404.xyz/posts/oh-my-posh-%E5%91%BD%E4%BB%A4%E8%A1%8C%E7%BE%8E%E5%8C%96/</id>
    <published>2023-05-20T00:00:00.000Z</published>
    <updated>2023-05-20T00:00:00.000Z</updated>
    <summary>在Windows/Linux平台下，使用oh-my-posh工具，对各自平台的命令行进行美化，或者自定义代码高亮提示信息。</summary>
    <content type="html"><![CDATA[<h2>Windows</h2>
<h3>安装</h3>
<p>三种方式：</p>
<ol>
<li>Microsoft Store 搜索 <code>oh-my-posh</code></li>
<li><code>winget install JanDeDobbeleer.OhMyPosh -s winget</code></li>
<li><a href="https://github.com/JanDeDobbeleer/oh-my-posh/releases">Releases · JanDeDobbeleer/oh-my-posh (github.com)</a></li>
</ol>
<blockquote>
<p>[!tip]
winget 经实测，会下载 <code>oh-my-posh.exe</code> 和主题包两样。</p>
<p>微软商店没有试过，请自行尝试，如果没有主题包请前往 github 手动下载 <code>themes.zip</code> 。</p>
<p>我个人喜欢去官方 release 页手动下载这两个文件。</p>
</blockquote>
<blockquote>
<p>[!warning]
手动下载请注意给 <code>oh-my-posh.exe</code> 配置环境变量</p>
</blockquote>
<h3>配置</h3>
<p>官方在<a href="https://ohmyposh.dev/docs/installation/customize#config-syntax">这里</a>给出了各个命令行工具的配置方法。本文只介绍 <code>Windows PowerShell</code> （PowerShell）其实也通用。</p>
<p>打开 <code>Windows PowerShell</code></p>
<pre><code>&gt;&gt;&gt;$profile
D:\文档\WindowsPowerShell\Microsoft.PowerShell_profile.ps1
</code></pre>
<p>在上面的路径下创建这个 <code>.ps1</code> 文件（通常情况下应该是没有的）。这个就是你当前所用 terminal 的配置文件，在每次启动 terminal 时会预先加载该文件。</p>
<pre><code>oh-my-posh init pwsh --config {json主题路径} | Invoke-Expression
</code></pre>
<p>在 <code>.ps1</code> 文件中添加上述内容，保存后重启 terminal 即可。</p>
<p><img src="https://img.085404.xyz/images/44d0aac93afa153c11b535df55562792.png" alt="image.png" /></p>
<blockquote>
<p>[!tip]
为什么说 PowerShell 也通用，你把上面的操作流程，打开 PowerShell 重复一遍就行了，无非就是那个 <code>.ps1</code> 配置文件的路径不一样而已。</p>
</blockquote>
<h3>主题路径问题</h3>
<p>如果是 <code>winget</code> 安装的，主题包<strong>好像是</strong>在 <code>C:\Users\你的win名字\AppData\Local\oh-my-posh\xxx</code> （所以我为啥手动装，微软啥都往 C 盘塞）。</p>
<p>如果你是手动安装的，那主题包就是你解压 <code>themes.zip</code> 的地方。</p>
<p>主题样式预览见 <a href="https://ohmyposh.dev/docs/themes">Themes | Oh My Posh</a></p>
<h2>Linux</h2>
<blockquote>
<p>我用的是 Debian10，其余系统应该差异不大。</p>
</blockquote>
<p>安装</p>
<pre><code>curl -s https://ohmyposh.dev/install.sh | bash -s
</code></pre>
<p>配置文件添加：</p>
<pre><code>eval "$(oh-my-posh init bash --config ~/M365Princess.omp.json)"
</code></pre>
<blockquote>
<p>[!tip]
上面那个 json 文件的主题也是要自己手动下载的，官方给的脚本不会帮你下载主题包。</p>
</blockquote>
<h2>字体问题</h2>
<p>如果你查看官方文档，会反复看见他们推荐你下载<a href="https://www.nerdfonts.com/font-downloads">Nerd Fonts</a> 。这个字体除了基本的文字外，还包含一些特殊符号或者小图像，如果你的 terminal 不支持这些样式，可能就会<strong>显示成一个方框或者乱码</strong>。</p>
<p>具体下载哪一个，随你喜欢就好。</p>
<p>这个字体并不需要在 <code>PowerShell</code> 或者 <code>bash</code> 这类命令行中配置，而是需要在你的终端 <code>Terminal</code> 中配置。</p>
<blockquote>
<p>[!danger]
我就跟个傻逼一样，倒腾了几个小时的 Debian 系统字体更换，最后发了个 issue，人家 3 分钟不到就给我解决问题了</p>
</blockquote>
<p><a href="https://github.com/JanDeDobbeleer/oh-my-posh/issues/3884">How to make Nerd Font work on Debian's bash? · Issue #3884 · JanDeDobbeleer/oh-my-posh (github.com)</a></p>
<p>所以说，你在百度/Google 上看到的 Linux 使用 oh-my-posh 教程，10 个有 9 个是 Ubuntu，9 个里有 8 个在互相 copy，也是 nm 离谱...</p>
<p>其实一般情况下，<strong>只需要在 Windows 平台安装 Nerd Font 字体就行了</strong>，然后在你的 ssh 连接工具（Windows Terminal, Tabby, Alacritty, Electerm 等）中配置字体显示就行了。<strong>Linux 平台压根不用装字体</strong>。</p>
<p>说的就是下面这种</p>
<blockquote>
<p>[!error]</p>
<pre><code> sudo mkdir /usr/share/fonts/ttf
 sudo cp *. ttf /usr/share/fonts/ttf
 cd /usr/share/fonts/ttf
 sudo chmod 744 *
 sudo mkfontscale
 sudo mkfontdir
 sudo fc-cache -f -v
</code></pre>
</blockquote>
]]></content>
    <author><name>Sun2ot</name></author>
    <category term="教程"/>
  </entry>
  <entry>
    <title>Aria2 部署</title>
    <link href="https://blog.085404.xyz/posts/aria2-%E9%83%A8%E7%BD%B2/" rel="alternate" type="text/html"/>
    <id>https://blog.085404.xyz/posts/aria2-%E9%83%A8%E7%BD%B2/</id>
    <published>2023-05-13T00:00:00.000Z</published>
    <updated>2023-05-13T00:00:00.000Z</updated>
    <summary>Windows 环境下如何部署 aria2 下载引擎</summary>
    <content type="html"><![CDATA[<h1>Aria2 部署</h1>
<h2>Windows</h2>
<h3>懒人包下载</h3>
<p>懒人包下载：<a href="http://aria2.baisheng999.com/">Aria2中文网 (baisheng999.com)</a></p>
<blockquote>
<p>[!tip]
下面是手动部署方案。懒人包不用部署，双击就行了</p>
</blockquote>
<h3>手动部署</h3>
<p>下载 aria2 并解压
<a href="https://github.com/aria2/aria2/releases">Releases · aria2/aria2 (github.com)</a></p>
<p><img src="https://img.085404.xyz/images/ca9dd07621ac46710c8c2b36f7cb340b.png" alt="image.png" /></p>
<p>解压出来后，里面的 <code>aria2c.exe</code> 是无法直接运行的（双击直接闪退），需要添加几个文件</p>
<ul>
<li><code>aria2.session</code> ——会话存储文件，直接生成空文件即可</li>
<li><code>aria2.conf</code> ——核心文件（见下文）</li>
<li><code>aria2.vbs</code> ——启动脚本（见下文）</li>
</ul>
<pre><code>## '#'开头为注释内容, 选项都有相应的注释说明, 根据需要修改 ##
## 被注释的选项填写的是默认值, 建议在需要修改时再取消注释  ##

## 文件保存相关 ##

# 文件的保存路径(可使用绝对路径或相对路径), 默认: 当前启动位置
dir=D:\Download
# 启用磁盘缓存, 0为禁用缓存, 需1.16以上版本, 默认:16M
disk-cache=32M
# 文件预分配方式, 能有效降低磁盘碎片, 默认:prealloc
# 预分配所需时间: none &lt; falloc ? trunc &lt; prealloc
# falloc和trunc则需要文件系统和内核支持
# NTFS建议使用falloc, EXT3/4建议trunc, MAC 下需要注释此项
file-allocation=falloc
# 断点续传
continue=true

## 下载连接相关 ##

# 最大同时下载任务数, 运行时可修改, 默认:5
max-concurrent-downloads=5
# 同一服务器连接数, 添加时可指定, 最大:16
max-connection-per-server=16
# 最小文件分片大小, 添加时可指定, 取值范围1M -1024M, 默认:20M
# 假定size=10M, 文件为20MiB 则使用两个来源下载; 文件为15MiB 则使用一个来源下载
#min-split-size=10M
# 单个任务最大线程数, 添加时可指定, 默认:5
split=32
# 整体下载速度限制, 运行时可修改, 默认:0
#max-overall-download-limit=0
# 单个任务下载速度限制, 默认:0
#max-download-limit=0
# 整体上传速度限制, 运行时可修改, 默认:0
#max-overall-upload-limit=0
# 单个任务上传速度限制, 默认:0
#max-upload-limit=0
# 禁用IPv6, 默认:false
disable-ipv6=true

## 进度保存相关 ##

# 从会话文件中读取下载任务
input-file=D:\software\aria2-1.36.0-win-64bit-build1\aria2.session
# 在Aria2退出时保存`错误/未完成`的下载任务到会话文件
save-session=D:\software\aria2-1.36.0-win-64bit-build1\aria2.session
# 定时保存会话, 0为退出时才保存（此处需要设置，否则失去自动保存）, 需1.16.1以上版本, 默认:0 
save-session-interval=60

## RPC相关设置 ##

# 启用RPC, 默认:false
enable-rpc=true
# 允许所有来源, 默认:false
rpc-allow-origin-all=true
# 允许非外部访问, 默认:false
rpc-listen-all=true
# 事件轮询方式, 取值:[epoll, kqueue, port, poll, select], 不同系统默认值不同
#event-poll=select
# RPC监听端口, 端口被占用时可以修改, 默认:6800
#rpc-listen-port=6800

## BT/PT下载相关 ##

# 当下载的是一个种子(以.torrent结尾)时, 自动开始BT任务, 默认:true
#follow-torrent=true
# BT监听端口, 当端口被屏蔽时使用, 默认:6881-6999
listen-port=51413
# 单个种子最大连接数, 默认:55
#bt-max-peers=55
# 打开DHT功能, PT需要禁用, 默认:true
#enable-dht=false
# 打开IPv6 DHT功能, PT需要禁用
#enable-dht6=false
# DHT网络监听端口, 默认:6881-6999
#dht-listen-port=6881-6999
# 本地节点查找, PT需要禁用, 默认:false
#bt-enable-lpd=false
# 种子交换, PT需要禁用, 默认:true
#enable-peer-exchange=false
# 每个种子限速, 对少种的PT很有用, 默认:50K
#bt-request-peer-speed-limit=50K
# 客户端伪装, PT需要
peer-id-prefix=-TR2770-
user-agent=Transmission/2.77
# 当种子的分享率达到这个数时, 自动停止做种, 0为一直做种, 默认:1.0
#seed-ratio=0
# 强制保存会话, 即使任务已经完成, 默认:false
# 较新的版本开启后会在任务完成后依然保留.aria2文件
#force-save=true
# BT校验相关, 默认:true
#bt-hash-check-seed=true
# 继续之前的BT任务时, 无需再次校验, 默认:false
bt-seed-unverified=true
# 保存磁力链接元数据为种子文件(.torrent文件), 默认:false
bt-save-metadata=true

# bt-tracker 更新，解决Aria2 BT下载速度慢没速度的问题
# https://raw.githubusercontent.com/ngosang/trackerslist/master/trackers_best.txt
bt-tracker=udp://tracker.opentrackr.org:1337/announce,udp://tracker.coppersurfer.tk:6969/announce,udp://9.rarbg.to:2710/announce,udp://9.rarbg.me:2710/announce,udp://tracker.internetwarriors.net:1337/announce,udp://tracker.leechers-paradise.org:6969/announce,udp://tracker.cyberia.is:6969/announce,udp://exodus.desync.com:6969/announce,udp://explodie.org:6969/announce,http://tracker3.itzmx.com:6961/announce,http://tracker1.itzmx.com:8080/announce,udp://tracker.tiny-vps.com:6969/announce,udp://open.stealth.si:80/announce,udp://tracker.torrent.eu.org:451/announce,udp://tracker.ds.is:6969/announce,http://open.acgnxtracker.com:80/announce,udp://retracker.lanta-net.ru:2710/announce,udp://tracker.moeking.me:6969/announce,udp://ipv4.tracker.harry.lu:80/announce,udp://zephir.monocul.us:6969/announce
</code></pre>
<pre><code># 此处aria2的路径根据实际情况填写
# 该文件的目的是在Windows隐藏后台运行aria2
CreateObject("WScript.Shell").Run "D:\App\Aria2\aria2c.exe --conf-path=aria2.conf”,0
</code></pre>
<p>上述三个文件成功生成后，双击 <code>aria2.vbs</code> ，即可顺利启动 <code>aria2c.exe</code></p>
<p><img src="https://img.085404.xyz/images/5367cf89c218f74eded392aa42b9e844.png" alt="image.png" /></p>
<h3>设置开启自启</h3>
<p><code>win + R</code> ，执行以下指令，打开开启自启文件夹</p>
<pre><code>shell:Common Startup
</code></pre>
<p>将 <code>aria2.vbs</code> 的快捷方式放入文件夹即可实现开机自启动</p>
<hr />
<p><code>2024-01-30</code> 更新：
可以使用 <code>nssm</code> 将 aria2c.exe 注册为系统服务，然后设置开机启动即可，进而通过 powershell 的 <code>start/stop-service aria2c</code> 来控制。</p>
<h2>Linux</h2>
<h3>安装</h3>
<pre><code>apt update

apt-get install aria2

#检查aria2c是否正确安装
aria2c -v
</code></pre>
<h3>配置文件</h3>
<p>在 <code>/root</code> 路径下（或者别的也行）创建所需的三个文件</p>
<pre><code>cd ~
mkdir aria2
cd aria2

touch aria2.session
touch aria2.conf
touch aria2.log
</code></pre>
<p>在 <code>aria2.conf</code> 中写入 <a href="https://gist.github.com/sun2ot/924f0ba492274e9ba1ff27ccd1217ae3">该配置</a></p>
<blockquote>
<p>[!warning]
注意修改第 2 行的密码</p>
</blockquote>
<h3>启动</h3>
<pre><code># -D表示终端关闭也运行
aria2c --conf-path=/root/aria2/aria2.conf -D
</code></pre>
<p><img src="https://img.085404.xyz/images/cc4ce107ec60cf58f87da552498f1af1.webp" alt="image.png" /></p>
<p>填好了滚轮滚到最下面，点 <code>激活</code></p>
<blockquote>
<p>如果要使用 https，自行配置 NGINX 或类似服务</p>
</blockquote>
<h2>使用 CLI</h2>
<p>Aria2 除了可以作为 Server 部署，也可以直接使用其 CLI <code>aria2c</code>，这里以 Linux 环境为例。因为其支持断点续传和多线程下载，因此非常适合下载大文件，可以作为 <code>curl/wget</code> 等工具的平替。</p>
<blockquote>
<p>Windows 端较少使用，但参数都类似。</p>
</blockquote>
<h3>基本用法</h3>
<pre><code># 下载单个文件
aria2c http://example.com/file.zip

# 多线程下载 (16线程)
aria2c -x 16 http://example.com/file.zip

# 断点续传
# 1. 会自动生成一个 .aria2 文件来记录下载进度
# 2. 如果下载过程中断，可以使用相同的命令继续下载
aria2c -c http://example.com/file.zip

# 保存文件名
aria2c -o newname.zip http://example.com/file.zip
</code></pre>
<h3>设置代理</h3>
<pre><code># http(s) 代理
aria2c --http-proxy="http://proxy.example.com:8080" --https-proxy="https://proxy.example.com:8080" http://example.com/file.zip

# socks 代理
aria2c --all-proxy="socks5://[username:password@]proxy.example.com:1080" http://example.com/file.zip

# 某些 URL 不使用代理
aria2c --all-proxy="http://proxy.example.com:8080" --no-proxy="example.com,http://specific-site.com" http://example.com/file.zip
</code></pre>
]]></content>
    <author><name>Sun2ot</name></author>
    <category term="教程"/>
  </entry>
  <entry>
    <title>steam的cdn问题</title>
    <link href="https://blog.085404.xyz/posts/steam%E7%9A%84cdn%E9%97%AE%E9%A2%98/" rel="alternate" type="text/html"/>
    <id>https://blog.085404.xyz/posts/steam%E7%9A%84cdn%E9%97%AE%E9%A2%98/</id>
    <published>2023-04-12T00:00:00.000Z</published>
    <updated>2023-04-12T00:00:00.000Z</updated>
    <summary>通过steam下载游戏时，本地带宽无法跑满，可能是由于cdn不恰当的问题。本文解析代理和直连模式下访问steam时，对cdn产生的影响。</summary>
    <content type="html"><![CDATA[<ol>
<li>浏览器地址栏输入 <code>steam://open/console</code> 可启动 steam 控制台</li>
<li>控制台内输入 <code>user_info</code> 可显示当前用户信息
<img src="https://img.085404.xyz/images/54fc508f66e6d2c4f42daff43c214379.png" alt="image.png" /></li>
</ol>
<p>其中的 <code>IPCountry</code> 取决于<strong>登录</strong> steam 账号时的网络环境（IP）</p>
<p>所以，如果你是开启代理的情况下登录 steam，那么在国区下载游戏时，<strong>即使给 steam 设置的分流规则为 direct</strong>，steam 仍然会按照你的 <code>IPCountry</code> 来为你就近分配 CDN，如下图（本体 500 M 家宽，下载游戏时实际只有 160 M 的速度）</p>
<p><img src="https://img.085404.xyz/images/de6f48a955e8179512def2e6d14f3d49.png" alt="image.png" /></p>
<p>手动指定下载地区<strong>没有任何卵用</strong>，以你登录时的地区为准</p>
<p><img src="https://img.085404.xyz/images/73e387513da7885722d95d8d927ae416.png" alt="image.png" /></p>
]]></content>
    <author><name>Sun2ot</name></author>
    <category term="教程"/>
  </entry>
  <entry>
    <title>PDF书签目录自动生成</title>
    <link href="https://blog.085404.xyz/posts/pdf%E4%B9%A6%E7%AD%BE%E7%9B%AE%E5%BD%95%E8%87%AA%E5%8A%A8%E7%94%9F%E6%88%90/" rel="alternate" type="text/html"/>
    <id>https://blog.085404.xyz/posts/pdf%E4%B9%A6%E7%AD%BE%E7%9B%AE%E5%BD%95%E8%87%AA%E5%8A%A8%E7%94%9F%E6%88%90/</id>
    <published>2023-03-19T00:00:00.000Z</published>
    <updated>2023-03-19T00:00:00.000Z</updated>
    <summary></summary>
    <content type="html"><![CDATA[<h1>Notice</h1>
<ol>
<li>本文针对图片型 pdf 无效</li>
<li>需要 <a href="https://www.python.org/">Python</a> 环境</li>
<li>本文默认操作系统为 Windows</li>
<li>生成的目录只能精确到页，无法精确到文本锚点</li>
</ol>
<blockquote>
<p>也就是说如果你的标题在一页末尾，只能跳转到所在页的开头，你得滑动页面找到它；如果你的多个标题在同一页，点击目录都只会跳转到该页的开头</p>
</blockquote>
<hr />
<h1>一、准备</h1>
<h2>安装 Python</h2>
<p>你当然可以选择下载 anaconda 或 miniconda 之类的集成环境，这里由于笔者不是专业 Python 人员，因此选择直接下载基础环境。</p>
<p>安装完成后，打开命令行，输入</p>
<pre><code>python
</code></pre>
<p>出现版本信息并进入 Python 交互页面即可</p>
<pre><code>D:\另存&gt;python
Python 3.10.6 (tags/v3.10.6:9c7b4bd, Aug  1 2022, 21:53:49) [MSC v.1932 64 bit (AMD64)] on win32
Type "help", "copyright", "credits" or "license" for more information.
&gt;&gt;&gt;
</code></pre>
<blockquote>
<p>Tips：退出 Python 时，可通过 <code>ctrl + z</code> + <code>enter</code> 或者 <code>exit()</code> 或者 <code>quit()</code> 退出</p>
</blockquote>
<hr />
<h2>下载 pdf. tocgen 工具集</h2>
<p><a href="https://github.com/Krasjet/pdf.tocgen">Krasjet/pdf.tocgen (github.com)</a></p>
<pre><code>pip install -U --user pdf.tocgen
</code></pre>
<p><code>pdf.tocgen</code> 共包含以下三个部分：</p>
<ol>
<li><code>pdfxmeta</code> : extract the metadata (font attributes, positions) of headings to build a <strong>recipe</strong> file.</li>
<li><code>pdftocgen</code> : generate a table of contents (ToC) from the recipe.</li>
<li><code>pdftocio</code> : import the table of contents to the PDF document.</li>
</ol>
<p><strong>官方介绍</strong></p>
<blockquote>
<p><a href="https://krasjet.com/voice/pdf.tocgen/">pdf.tocgen</a> is a set of command-line tools for automatically extracting and generating the table of contents (ToC) of a PDF file. It uses the embedded font attributes and position of headings to deduce the basic outline of a PDF file.    --from GitHub</p>
</blockquote>
<p>简单地说，<code>pdf.tocgen</code> 是一个 Python 工具集。因为 PDF 本身其实是没有标题一说的，全部的排版格式都是通过矢量进行定位，你眼中的标题在 PDF 看来无非就是字体更粗、更大、独占一行的文本罢了。</p>
<p>而 <code>pdf.tocgen</code> 正是利用这种特性，可以根据提取出的“标题”字符特征，推断出全文中其他相同格式的文本，从而形成大纲（outline），最后再嵌入 PDF 文件即可。</p>
<hr />
<h1>二、生成 PDF 目录</h1>
<h2>官方工作流程详解</h2>
<p><a href="https://krasjet.com/voice/pdf.tocgen/#a-worked-example">pdf.tocgen (krasjet.com)</a></p>
<blockquote>
<p><code>pdfxmeta/pdftocgen/pdftocio -h</code> 即可查看指令文档</p>
</blockquote>
<hr />
<h2>提取标题文本特征（pdfxmeta）</h2>
<blockquote>
<p>Windows 环境下请使用 cmd，或者你所安装的 Python 集成环境的命令行工具，不要用 powershell......</p>
</blockquote>
<h3>命令格式</h3>
<pre><code>pdfxmeta -p 页码 -a 标题级别 文件名.pdf "标题文本" [&gt;&gt; recipe.toml]
</code></pre>
<ul>
<li><code>-p</code> ：1, 2, 3，...... 页码</li>
<li><code>-a</code> ：标题级别，一级标题为 1，二级标题为 2，......</li>
<li><code>"标题文本"</code> ：能够<strong>筛选出</strong>标题的文本</li>
</ul>
<blockquote>
<p>一些标题中可能包含特殊字符，如 emojy、空格等，这在 cmd 中也许不能被有效识别（即<strong>执行 pdfxmeta 后输出结果为空</strong>），因此可以选用标题中所包含的部分字符进行筛选。</p>
<p>当然，你也可以使用<strong>正则表达式</strong>（需为 Python 所支持的）</p>
</blockquote>
<ul>
<li><code>&gt;&gt; recipe.toml</code> ：可选。加上该指令可以让标题特征直接输出到 <code>recipe.toml</code> 文件。注意，此处的文件名不一定要是 <code>recipe</code>，可以是任何值。以下为介绍方便，统一为 <code>recipe.toml</code>。</li>
</ul>
<p>根据 pdf 文件中的标题所在位置，<strong>反复执行上述指令</strong>，生成最终的标题特征文件。</p>
<h3>案例</h3>
<pre><code>&gt;&gt;&gt;pdfxmeta -p 1 -a 2 作文模板.pdf "第一段" &gt;&gt; recipe.toml

// 输出

[[heading]] 
# 第一段
level = 2
greedy = true
font.name = "MicrosoftYaHei-Bold"
font.size = 15.75732135772705
# font.size_tolerance = 1e-5
# font.color = 0x3ababa
# font.superscript = false
# font.italic = false
# font.serif = false
# font.monospace = false
# font.bold = true
# bbox.left = 67.04096221923828
# bbox.top = 130.88217163085938
# bbox.right = 113.84020233154297
# bbox.bottom = 151.6790771484375
# bbox.tolerance = 1e-5
</code></pre>
<blockquote>
<p>如果你没有使用 <code>&gt;&gt; recipe.toml</code>，请在 <strong>PDF 文件所在目录下</strong>，新建一个 <code>recipe.toml</code> 并将输出结果复制粘贴到其中并保存文件。</p>
</blockquote>
<h2>生成 ToC 文件 （pdftocgen）</h2>
<h3>命令格式</h3>
<pre><code>pdftocgen [-H] 文件名.pdf &lt; recipe.toml [&gt; toc.txt]
</code></pre>
<ul>
<li><code>-H</code> ：可选。作用是打印出更易读（对人类而言）的目录，但该目录无法被 <code>pdftocio</code> 读取，即<strong>不可用于生成 PDF 目录</strong></li>
<li><code>&gt; toc.txt</code> ：可选。作用是将生成的目录结果直接导出到 <code>toc.txt</code> 文件，因此你当然可以选择不加该指令，然后手动新建一个文件并将结果复制粘贴到其中。与 <code>recipe.toml</code> 相同，此处的文件名不唯一，你可以任意选用。但以下为描述方便，统一为 <code>toc.txt</code></li>
</ul>
<blockquote>
<p>其实 <code>.txt</code> 也是可以不加的，此处的文件只要可读就行了，因此 <code>&gt; toc</code> 同样可行。但 txt 总归更容易双击打开，不是吗？</p>
</blockquote>
<blockquote>
<p>[!bug] The '&lt;' operator is reserved for future use.</p>
</blockquote>
<p>报错是因为 <code>&lt;</code> 在 powershell 中属于保留字之类的？总之是运行不了，要么在 cmd 离运行（如上），要么用 powershell 的 cmdlet 和管道符完成：</p>
<pre><code>Get-Content recipe.toml | pdftocgen -H [&gt; toc.txt]
</code></pre>
<h3>案例</h3>
<pre><code>pdftocgen 作文模板.pdf &lt; recipe.toml &gt; toc.txt

# 输出

"一、大作文" 1
    "1. 第一段" 1
    "2. 第二段" 1
    "3. 第三段" 2
    "4. 关键词" 2
"二、小作文" 4
    "2.1 建议信" 4
    "2.2 投诉信" 5
    "2.3 感谢信" 6
    "2.4 道歉信" 7
    "2.5 邀请信" 8
    "2.6 通知" 9
    "2.7 接收 / 拒绝  邀请" 9
    "2.8 推荐信" 10
    "2.9 求职 / 辞职信" 12
    "2.10 请求信" 13
</code></pre>
<p>如果你使用了 <code>-H</code> 生成目录，则结果如下</p>
<pre><code>pdftocgen -H 作文模板.pdf &lt; recipe.toml
一、大作文 ··· 1
    1. 第一段 ··· 1
    2. 第二段 ··· 1
    3. 第三段 ··· 2
    4. 关键词 ··· 2
二、小作文 ··· 4
    2.1 建议信 ··· 4
    2.2 投诉信 ··· 5
    2.3 感谢信 ··· 6
    2.4 道歉信 ··· 7
    2.5 邀请信 ··· 8
    2.6 通知 ··· 9
    2.7 接收 / 拒绝  邀请 ··· 9
    2.8 推荐信 ··· 10
    2.9 求职 / 辞职信 ··· 12
    2.10 请求信 ··· 13
</code></pre>
<blockquote>
<p>再次声明，<code>-H</code> 生成的目录仅适用于您不想将目录导入 PDF 的情况</p>
</blockquote>
<hr />
<h2>将 ToC 导入 PDF（pdftocio）</h2>
<h3>命令格式</h3>
<p>执行下述指令前，可以手动编辑 <code>toc.txt</code>，排查其中的错误，或者删除不想生成书签的内容。</p>
<pre><code># cmd
pdftocio [-o 输出文件名.pdf] 文件名.pdf &lt; toc.txt
# powershell
get-content toc.txt | pdftocio [-o 输出文件名.pdf] 文件名.pdf
</code></pre>
<ul>
<li><code>-o 输出文件名.pdf</code> ：可选。如果需要导入目录后的 PDF 文件名与原来的不相同即可使用。</li>
</ul>
<p>如果不想生成上述步骤中的目录配方文件（<code>recipe.toml</code>），你甚至可以一步到位, 将 <code>pdftocgen</code> 的输出结果通过管道直接导入 <code>pdftocio</code></p>
<pre><code># cmd
pdftocgen 文件名.pdf &lt; recipe.toml | pdftocio 文件名.pdf
# powershell (也许可行，没试过)
get-content recipe.toml | pdftocgen 文件名.pdf | pdftocio 文件名.pdf
</code></pre>
<p><code>pdftocio</code> 也可用来输出 PDF 文档的现有目录</p>
<pre><code>pdftocio 文件名.pdf

"一、大作文" 1
    "1. 第一段" 1
    "2. 第二段" 1
    "3. 第三段" 2
    "4. 关键词" 2
"二、小作文" 4
    "2.1 建议信" 4
    "2.2 投诉信" 5
    "2.3 感谢信" 6
    "2.4 道歉信" 7
    "2.5 邀请信" 8
    "2.6 通知" 9
    "2.7 接收 / 拒绝  邀请" 9
    "2.8 推荐信" 10
    "2.9 求职 / 辞职信" 12
    "2.10 请求信" 13
</code></pre>
<p>或者利用 <code>-H</code> 输出</p>
<pre><code>pdftocio -H out.pdf

一、大作文 ··· 1
    1. 第一段 ··· 1
    2. 第二段 ··· 1
    3. 第三段 ··· 2
    4. 关键词 ··· 2
二、小作文 ··· 4
    2.1 建议信 ··· 4
    2.2 投诉信 ··· 5
    2.3 感谢信 ··· 6
    2.4 道歉信 ··· 7
    2.5 邀请信 ··· 8
    2.6 通知 ··· 9
    2.7 接收 / 拒绝  邀请 ··· 9
    2.8 推荐信 ··· 10
    2.9 求职 / 辞职信 ··· 12
    2.10 请求信 ··· 13
</code></pre>
]]></content>
    <author><name>Sun2ot</name></author>
    <category term="教程"/>
  </entry>
  <entry>
    <title>部署subconverter</title>
    <link href="https://blog.085404.xyz/posts/%E9%83%A8%E7%BD%B2subconverter/" rel="alternate" type="text/html"/>
    <id>https://blog.085404.xyz/posts/%E9%83%A8%E7%BD%B2subconverter/</id>
    <published>2023-02-04T00:00:00.000Z</published>
    <updated>2023-02-04T00:00:00.000Z</updated>
    <summary>如何在Linux上部署subconverter服务，实现自建订阅转换后端，避免订阅链接泄露，同时实现一个订阅，全平台共用的效果。</summary>
    <content type="html"><![CDATA[<h1>下载 subconverter</h1>
<pre><code>wget https://github.com/tindy2013/subconverter/releases/download/v0.7.2/subconverter_linux64.tar.gz
</code></pre>
<p>进入根目录后， <code>pm2 start subconverter</code> 即可启动</p>
<h1>使用</h1>
<p>随便找个网页前端
<a href="https://acl4ssr-sub.github.io/">ACL4SSR 在线订阅转换 (acl4ssr-sub.github.io)</a></p>
<blockquote>
<p>不用前端，直接码链接当然可以。但 subconverter 仅接受 URLEncoded 的链接，因此还是找个前端简单省事。当然，自行部署也没得问题。</p>
</blockquote>
<p><img src="https://img.085404.xyz/images/811ecf4c8568aafbd64d80250b3b900d.png" alt="image.png" /></p>
<p>复制生成的链接即可使用</p>
<h1>进阶操作</h1>
<h2>调用参数</h2>
<p>这里介绍了 subconverter 可选的链接参数
<a href="https://github.com/tindy2013/subconverter/blob/master/README-cn.md#%E8%B0%83%E7%94%A8%E8%AF%B4%E6%98%8E-%E8%BF%9B%E9%98%B6">subconverter/README-cn.md at master · tindy2013/subconverter (github.com)</a></p>
<p><strong>参数中几个需要关注的地方</strong>：</p>
<ul>
<li><code>&amp;clash.dns=1</code> 时，才可在配置文件中嵌入 dns 信息，详情见配置文件 pref. toml</li>
<li><code>&amp;remove_emoji=false</code> 可以避免节点前的符号被抹除</li>
<li><code>&amp;filename=xxx</code> 可以自定义订阅后的配置文件名</li>
<li><code>&amp;config=https://xxx</code> 或 <code>&amp;config=config/test.ini</code> 可以指定远程/本地的配置文件</li>
<li><code>&amp;expand=false</code> 可以将规则通过 provider 的形式插入配置文件。这点非常重要，因为大的规则集动辄数万条，如果全部写在一个配置文件里很可能会出现缺失，加载也慢</li>
</ul>
<h2>全局配置文件 pref.*</h2>
<p>加载配置文件时会按照 <code>pref.toml</code> 、 <code>pref.yml</code> 、 <code>pref.ini</code> 的优先级顺序加载优先级高的配置文件（这仨都在根路径下）
<a href="https://github.com/tindy2013/subconverter/blob/master/README-cn.md#%E8%B0%83%E7%94%A8%E8%AF%B4%E6%98%8E-%E8%BF%9B%E9%98%B6">subconverter/README-cn.md at master · tindy2013/subconverter (github.com)</a></p>
<h3>[common] 部分</h3>
<p><strong>文件中几个需要关注的配置</strong>：</p>
<h4>api_access_token</h4>
<p>参数可设置为自定义的密码，该密码用于<strong>短链接</strong>的鉴权</p>
<h4>exclude_remarks</h4>
<p>可以<strong>排除部分匹配到的节点</strong>，用于过滤用作提示信息的节点</p>
<pre><code>exclude_remarks=(到期|剩余流量|时间|官网|产品|平台)
</code></pre>
<h4>default_external_config</h4>
<p>可指定 url 链接中 config 参数为空时的<strong>默认配置文件</strong></p>
<pre><code>default_external_config=config/example_external_config.ini
</code></pre>
<h4>proxy_config</h4>
<p>可开启<strong>更新配置文件时使用代理</strong>（否则国内直连 github raw 非常感人）</p>
<pre><code>proxy_config=SYSTEM # 使用系统代理
#  或者使用本地的 1080 端口进行 SOCKS5 代理
proxy_config=socks5://127.0.0.1:1080 # 
# 或者使用CORS代理
proxy_config=cors: https://cors-anywhere.herokuapp.com/ 
</code></pre>
<h4>模板文件 base</h4>
<p>配置文件中有若干个 <code>base</code> 字段，其作用是引用不同平台的<strong>配置模板</strong>。因此，若订阅后生成的配置文件不符合你的需求，请更改 <code>/base/all_base.tpl</code> 或自定义配置模板后在 <code>pref.toml</code> 中修改模板引用路径</p>
<h4>enable_insert</h4>
<p>可以在输出的订阅中<strong>添加节点</strong>，用于自建或是<s>别人共享</s>（白嫖）的节点</p>
<pre><code>;当enable_insert = true时，会添加insert_url中的所有节点
;如果有多个节点，仍然需要使用 "|" 分隔
;支持 单个节点/订阅链接

[ini]
insert_url=ss://xxxxx|vmess://xxxx

[toml]
insert_url=["ss://xxxxx|vmess://xxxx"]
</code></pre>
<h4>规则配置文件</h4>
<pre><code>[custom]  
;必须以 [custom] 开头，否则引用 pref.toml 中引用的本地规则
;注释以分号引起
;update: 2023-02-04 19:00

ruleset=🎯 全球直连,clash-classic:https://ghproxy.com/https://raw.githubusercontent.com/ACL4SSR/ACL4SSR/master/Clash/Providers/Ruleset/SteamCN.yaml

;引用Clash规则中的classical类型的规则集，并将其指向🎯 全球直连这一策略组
;需注意，在clash配置文件中ruleset分为classical, domain, ipcidr三种类型，其中后两者都可通过clash-domain/clash-ipcidr引用，而前者关键词有所变化 classical -&gt; classic

;也可以引用本地规则文件，ruleset=GroupName,rules/ChinaDomain.list
;注意，subconverter在不声明的条件下，仅支持.list类型文件(在线/本地皆可)

ruleset=🚀 节点选择,clash-classic:https://ghproxy.com/https://raw.githubusercontent.com/ACL4SSR/ACL4SSR/master/Clash/Providers/Ruleset/Steam.yaml

ruleset=📺️ 动画疯,clash-classic:https://ghproxy.com/https://raw.githubusercontent.com/ACL4SSR/ACL4SSR/master/Clash/Providers/Ruleset/Bahamut.yaml

ruleset=🅱 Bilibili,clash-classic:https://ghproxy.com/https://raw.githubusercontent.com/zhihang-yi/ClashRules/main/RuleSet/BilibiliHMT.yaml

ruleset=🚀 节点选择,clash-classic:https://ghproxy.com/https://raw.githubusercontent.com/zhihang-yi/ClashRules/main/RuleSet/Weibo.yaml

ruleset=🎯 全球直连,clash-classic:https://ghproxy.com/https://raw.githubusercontent.com/zhihang-yi/ClashRules/main/RuleSet/CustomizeDirect.yaml

ruleset=🚀 节点选择,clash-classic:https://ghproxy.com/https://raw.githubusercontent.com/zhihang-yi/ClashRules/main/RuleSet/CustomizeProxy.yaml

ruleset=🛑 拦截,clash-classic:https://ghproxy.com/https://raw.githubusercontent.com/zhihang-yi/ClashRules/main/RuleSet/CustomizeReject.yaml

ruleset=🚀 节点选择,clash-domain:https://cdn.jsdelivr.net/gh/Loyalsoldier/clash-rules@release/proxy.txt

ruleset=🎯 全球直连,clash-domain:https://cdn.jsdelivr.net/gh/Loyalsoldier/clash-rules@release/cncidr.txt

ruleset=🎯 全球直连,clash-domain:https://cdn.jsdelivr.net/gh/Loyalsoldier/clash-rules@release/direct.txt

ruleset=🛑 拦截,clash-domain:https://cdn.jsdelivr.net/gh/Loyalsoldier/clash-rules@release/reject.txt

ruleset=🐟 漏网之鱼,[]FINAL

;[]FINAL和clash配置文件中的MATCH作用一致，此处也可写为[]MATCH

custom_proxy_group=🚀 节点选择`select`[]DIRECT`.*`http://www.gstatic.com/generate_204`300,5

;策略组节点选择, 类型为select, 引用direct和所有节点. 给定测速链接,每隔300秒测速一次,5秒后判定为timeout

custom_proxy_group=🎯 全球直连`select`[]DIRECT`[]🚀 节点选择

custom_proxy_group=🛑 拦截`select`[]REJECT`[]DIRECT

custom_proxy_group=📺️ 动画疯`select`(🇹🇼|台)`

;注意, proxy_group=GroupName`select`[]DIRECT    ✅最后一个分量不以`结尾
;proxy_group=GroupName`select`(港|台)               ❌筛选规则需要以`完整包裹

custom_proxy_group=🅱 Bilibili`select`[]DIRECT`(🇭🇰|港|🇲🇴|澳门|🇹🇼|台)

custom_proxy_group=🐟 漏网之鱼`select`[]🚀 节点选择`[]🎯 全球直连

enable_rule_generator=true

overwrite_original_rules=true
</code></pre>
<p>分组配置文件中各个字段通过 $`$ 隔开</p>
<pre><code># 各字段按顺序分别为
custom_proxy_group=Group_Name`url-test|fallback|load-balance`Rule_1`Rule_2`...`test_url`interval[,timeout][,tolerance]
custom_proxy_group=Group_Name`select`Rule_1`Rule_2`...

# 格式示例
custom_proxy_group=🍎 苹果服务`url-test`(美国|US)`http://www.gstatic.com/generate_204`300,5,100
# 表示创建一个叫 🍎 苹果服务 的 url-test 策略组,并向其中添加名字含'美国','US'的节点，每隔300秒测试一次，测速超时为5s，切换节点的延迟容差为100ms

custom_proxy_group=🇯🇵 日本延迟最低`url-test`(日|JP)`http://www.gstatic.com/generate_204`300,5
# 表示创建一个叫 🇯🇵 日本延迟最低 的 url-test 策略组,并向其中添加名字含'日','JP'的节点，每隔300秒测试一次，测速超时为5s

custom_proxy_group=负载均衡`load-balance`.*`http://www.gstatic.com/generate_204`300,,100
# 表示创建一个叫 负载均衡 的 load-balance 策略组,并向其中添加所有的节点，每隔300秒测试一次，切换节点的延迟容差为100ms

custom_proxy_group=🇯🇵 JP`select`沪日`日本`[]🇯🇵 日本延迟最低
# 表示创建一个叫 🇯🇵 JP 的 select 策略组,并向其中**依次**添加名字含'沪日','日本'的节点，以及引用上述所创建的 🇯🇵 日本延迟最低 策略组

custom_proxy_group=节点选择`select`(^(?!.*(美国|日本)).*)
# 表示创建一个叫 节点选择 的 select 策略组,并向其中**依次**添加名字不包含'美国'或'日本'的节点
</code></pre>
<h1>FAQ</h1>
<h2>移动端 Clash 的配置文件栏不显示流量信息</h2>
<p>解决方法：为订阅转换域名配置 SSL 证书</p>
<blockquote>
<p><s>经测试，x-ui 脚本通过 acme 申请的泛域名证书，在（国产）安卓手机上是不被信任的</s>，因此订阅时 Clash 会提示 x509 错误。建议通过阿里云等国内平台申请单域名的 SSL 证书</p>
</blockquote>
<blockquote>
<p>2023-05-15 修正：
通过 acme 申请的证书，不是不被信任，而是要使用 <code>fullchain.cer</code> 与 <code>域名.key</code> ，不要使用 <code>域名.cer</code></p>
</blockquote>
]]></content>
    <author><name>Sun2ot</name></author>
    <category term="教程"/>
  </entry>
  <entry>
    <title>考研数据结构自命题速成笔记</title>
    <link href="https://blog.085404.xyz/posts/%E8%80%83%E7%A0%94%E6%95%B0%E6%8D%AE%E7%BB%93%E6%9E%84%E8%87%AA%E5%91%BD%E9%A2%98%E9%80%9F%E6%88%90%E7%AC%94%E8%AE%B0/" rel="alternate" type="text/html"/>
    <id>https://blog.085404.xyz/posts/%E8%80%83%E7%A0%94%E6%95%B0%E6%8D%AE%E7%BB%93%E6%9E%84%E8%87%AA%E5%91%BD%E9%A2%98%E9%80%9F%E6%88%90%E7%AC%94%E8%AE%B0/</id>
    <published>2022-12-01T00:00:00.000Z</published>
    <updated>2022-12-01T00:00:00.000Z</updated>
    <summary>适用于085400死亡代码的自命题考生，重点在于做题，“不求甚解”，拿分才是王道。文内资源为本人考研时所记录的笔记，真实有效，已上岸。</summary>
    <content type="html"><![CDATA[<blockquote>
<p>本文有几道例题的录屏讲解，见链接： https://pan.baidu.com/s/1wl2-3MvBgdWl7ngszDehHg?pwd=6666</p>
</blockquote>
<h2>一、概念</h2>
<p><strong>抽象数据类型</strong>（ADT）定义一个完整的数据结构：数据对象、数据关系、基本操作集</p>
<p><strong>逻辑结构</strong>：线性、非线性</p>
<p><strong>存储结构</strong>：顺序存储、链式存储、索引存储、散列存储</p>
<blockquote>
<p>循环队列是一种数据结构</p>
</blockquote>
<p>存储数据时，除了存储数据元素的值，还要存储<strong>数据元素之间的关系</strong></p>
<p>链式存储设计，<strong>结点内</strong>的存储单元地址一定连续</p>
<p>算法：问题求解步骤的描述，<strong>准确、清晰、有穷</strong>
<strong>算法原地工作</strong>：指算法所需的辅助空间是常量</p>
<hr />
<p>$$
O(n^2) =&gt; T(n) ≤ cn^2
$$</p>
<ol>
<li>执行时间与 $n^2$ 成正比✅</li>
<li><strong>问题规模</strong>仍然是 n</li>
<li>时间复杂度<strong>总考虑最坏的情况</strong>，以确保不会比它更长</li>
<li>同一个算法，实现语言的级别越高，执行效率越低</li>
</ol>
<h2>二、时间复杂度</h2>
<pre><code># 阶乘算法，操作步骤只有一个乘法，因此n次就是O(n)
int fact(int n) {
    if (n&lt;=1) return 1;
    return n*fact(n-1);
}
</code></pre>
<pre><code># 两个升序列表 m, n, 合并为长度 m+n 的升/降序列表，最坏情况是两两错开
# 因此每次选开头两个比较，小的放前，大的随后
# 等较短的列表排完后，另一个列表剩下的依次后放即可
# 因此时间复杂度为 O(max(m, n))，取决于较长的那个
</code></pre>
<ul>
<li>数组排序的最小时间复杂度为 $$O(nlog_2n)$$</li>
<li>在插入链表时进行排序（即先检索链表看看排在哪，然后再插入链表），时间复杂度为 $O(n^2)$</li>
<li>长度为 n 链表接在长度为 m 的单链表后面，时间复杂度为 $O(m)$</li>
</ul>
<blockquote>
<p>先遍历 m，找到尾节点，然后接到 n 的头结点...</p>
</blockquote>
<h3>斐波那契数列</h3>
<h4>递归</h4>
<pre><code>int fibonacci(int n) {   // 第n项
    if (n == 1 || n == 2)
        return 1;
    else 
        return fibonacci(n-1) + fibonacci(n-2);
}
</code></pre>
<p>对于每一项，必须计算前两项，即 2 次计算，n 项就是 $O(2^n)$</p>
<h4>非递归</h4>
<pre><code>int fibonacci(int n) {
    if (n &lt;= 2)
        return 1;
    else {
        int num1 = 1;
        int num2 = 1;
        int i;
        for (i=3; i&lt;n; i++) {
            num2 = num1 + num2;  // 前两项之和
            num1 = num2 - num1;   // num2 被覆盖掉了，所以用加出的和 减 num1 得到原先的 num2 并作为新的 num1
        }
        return num1 + num2;
    }
}
</code></pre>
<p>从第三项开始，只需要按照规律将前两项相加即可，时间复杂度为 $O(n)$</p>
<h2>三、线性表</h2>
<ul>
<li>线性表是具有 n 个<strong>相同类型数据元素</strong>的<strong>有限序列</strong></li>
<li>顺序存储结构的优点：存储密度大</li>
<li>线性表的顺序存储结构是一种<strong>随机存取</strong>的存储结构（不是<s>顺序存取</s>）</li>
</ul>
<blockquote>
<p>随机存取：根据起始地址加上序号，可以访问任一元素
访问元素 $O(1)$，增、删元素 $O(n)$</p>
</blockquote>
<ul>
<li>顺序存储需要<strong>连续的存储空间</strong>，例如 n 个空间已满，申请增加分配 m 个空间，则系统会开辟一个新的 <strong>n+m</strong> 个连续的存储空间，否则申请失败</li>
<li>顺序存储结构既能随机存取又能顺序存取，而链式结构只能进行顺序存取</li>
<li>静态链表需要分配较大的连续空间，插入和删除不需要移动元素</li>
<li><strong>循环双链表判空条件</strong>是头结点（头指针）的 <code>prior</code> 和 <code>next</code> 都指向自身</li>
</ul>
<blockquote>
<p>为了体现循环、双链表的特性，空着的时候也要自给自足捏</p>
</blockquote>
<ul>
<li><strong>静态链表中的指针</strong>表示的是下一个元素在数组中的位置</li>
</ul>
<blockquote>
<p>静态链表就是把一个数组拿来当链表，数组中每个元素记录本身的值和下一个元素的下标，从而形成链表的效果。因此<strong>静态链表插入和删除不需要移动元素</strong>，&lt;u&gt;但需分配较大空间&lt;/u&gt;。</p>
</blockquote>
<h3>例题</h3>
<p><img src="https://img.085404.xyz/images/701a401238373209b336debc711b7cbe.jpg" alt="image" /></p>
<p>注意，这个题有两个考点。</p>
<ol>
<li>存的是<strong>下三角</strong>，问你的是<strong>上三角</strong>。而题目给出的是对称矩阵，因此 <code>a[i][j] = a[j][i]</code>，所以他问 <code>上三角中a[i][i]</code> 对应的下标，其实问的是 <code>下三角中a[j][i]</code> 的下标，因为你压根没存上三角，而<strong>矩阵压缩的意义就在于用压缩后数组中的一个元素对应原来矩阵中的两个对称值</strong>。</li>
<li>如果默认数组下标从 0 开始，则 <code>a[i][j]</code> 在第 i+1 行，算出来结果为 ${\frac{(1+j)j}2} + i$ ，但本题下标从 1 开始，因此结果为<em><strong>B</strong></em></li>
</ol>
<blockquote>
<p>因此，压缩矩阵时，如果求出来的结果不对，不妨考虑换个起始下标。
此题也有另解，因为是<strong>按行存储，因此最后加的数一定是第二个下标对应的字符</strong>，也就是 <code>j</code></p>
</blockquote>
<h3>广义表</h3>
<ol>
<li>广义表的长度：广义表中数据元素的<strong>数量</strong>。一个原子算做是一个元素，<strong>一个子表也只算做一个元素</strong>。</li>
<li>广义表的深度：指的是广义表中<strong>括号的重数</strong>。</li>
</ol>
<pre><code>C = ((a, b))  //长度为 1, 深度为 2
</code></pre>
<h4>头尾链表存储结构</h4>
<ul>
<li>把每一个元素（不管是普通元素还是子表）<strong>都看做一个表</strong>，也就是挂载 <code>1 ? ?</code></li>
<li>元素形式为 <code>0 元素</code>，而且必须挂载在 <code>1 ? ?</code> 下</li>
</ul>
<p><img src="https://img.085404.xyz/images/56c6e34fcd5635687cbdb440117c1627.png" alt="image" /></p>
<p><img src="https://img.085404.xyz/images/cbe4a63d9115d092f11ce1d4b07c4fe1.png" alt="image" /></p>
<p>D=(B, C)</p>
<p><img src="https://img.085404.xyz/images/60591af10b0896db1b7c7ace3a38f233.png" alt="image" /></p>
<h4>扩展线性表存储结构</h4>
<ul>
<li>字母指出来第一个必是 <code>1 ^ ?</code>，<code>1</code> 表示是一个表，<code>?</code> 视情况而定，后面有元素就挂载，没有就也填 <code>^</code></li>
</ul>
<p><img src="https://img.085404.xyz/images/beffbdb2b5ceb7b5f6f9b9ab08cd0bcb.png" alt="|400x200" /></p>
<ul>
<li>最外层的表里有几个元素（即<strong>广义表的长度</strong>），就挂载几个 <code>? ? ?</code>，注意最后一个为 <code>? ? ^</code></li>
<li>如果是普通元素，则挂载 <code>0 元素 -&gt;/^</code></li>
<li>如果是一个子表，则挂载 <code>1 -&gt; -&gt;/^</code>。其中，中间的 <code>-&gt;</code> 以此类推，依次挂载 <code>0 元素 -&gt;/^</code></li>
<li>如果子表中还有子表，如法炮制</li>
</ul>
<p><img src="https://img.085404.xyz/images/bcdf18fade1c091387aae67dff2e7f26.png" alt="image" /></p>
<p><img src="https://img.085404.xyz/images/7f0100cc4de1670cb4eb33132084966d.png" alt="image" /></p>
<p><img src="https://img.085404.xyz/images/131136cd179f91ee9f90807850bcf54f.png" alt="image" /></p>
<p><img src="https://img.085404.xyz/images/455a42ae750322c5bf06aee0e31ea834.png" alt="image" /></p>
<h2>四、栈和队列</h2>
<ul>
<li>栈和队列都是<strong>限制存取点/运算受限</strong>的<strong>线性结构</strong></li>
<li>队列可以在两端运算，栈只能在一端进行运算</li>
<li>对于 n 个不同元素进栈，出栈序列个数为
$$
{\frac{1}{n+1}}C_{2n}^n
$$</li>
<li>共享栈节省存储空间，降低发生上溢的可能</li>
</ul>
<blockquote>
<p>栈是不可能下溢的</p>
</blockquote>
<ul>
<li><strong>循环队列</strong>
入队时，<code>rear = (rear + 1) mod maxsize</code>
出队时，<code>front = (front + 1) mod maxsize</code>
对列长度 = <code>(rear + maxsize - front) mod maxsize</code>
队满条件：<code>(rear + 1) mod maxsize == front</code>
队空条件：<code>front = rear</code></li>
<li>队列先进先出（<strong>队尾进，队头出</strong>），因此入队时，队尾指针+1；出队时，队头指针+1</li>
</ul>
<p><img src="https://img.085404.xyz/images/a109bd2553251205ced06f191aec3da9.png" alt="image" /></p>
<ul>
<li>
<p><strong>适合链队</strong>（核心就是你必须得有办法直接或间接地获得队首、队尾两个指针）</p>
<ol>
<li>带队首&lt;u&gt;和&lt;/u&gt;队尾指针的非循环单链表</li>
<li>只带队尾指针的循环单链表</li>
<li>只有队首&lt;u&gt;或&lt;/u&gt;队尾指针的循环&lt;u&gt;双链表&lt;/u&gt;</li>
</ol>
</li>
<li>
<p><strong>栈的应用</strong>包括：递归、进制转换、迷宫求解、括号匹配、函数的局部变量
<strong>队列的应用</strong>包括：缓冲区</p>
</li>
</ul>
<blockquote>
<p>栈的应用包括递归，但<strong>递归不一定要用栈来消除</strong></p>
</blockquote>
<ul>
<li>执行<strong>广度优先搜索图</strong>时，需要使用队列作为辅助存储空间</li>
<li>三对角矩阵说的是主对角线和与之上下平行的两条线</li>
<li>适用于压缩存储<strong>稀疏矩阵</strong>的结构：三元组表（理想）、十字链表</li>
</ul>
<hr />
<h3>例题</h3>
<p><img src="https://img.085404.xyz/images/0c5ab69f1dae177dc8b7f128b323daf3.png" alt="image" /></p>
<p>A. &lt;u&gt;ABCD&lt;/u&gt; 全入栈 $\implies$ CD <code>小括号</code> 先算，栈内 AB $R_1$ $\implies$ B $R_1$ <code>乘法</code> 先算，栈内 A $R_2$ $\implies$ 结果             <strong>综上，最大需 4 个存储单元</strong></p>
<p>答案 &lt;u&gt;AB&lt;/u&gt; 入栈 $\implies$ AB <code>小括号</code> 先算，栈内 $R_1$ $\implies$ C 入栈，栈内 $R_1$ C <code>乘法</code> 先算，栈内 $R_2$ $\implies$ D 入栈 $\implies$ 结果    <strong>综上，最大需 2 个存储单元</strong></p>
<p><img src="https://img.085404.xyz/images/2a03b6ed8b6ba829fae701dd4076b0b1.png" alt="image" /></p>
<p>答案 <code>A</code></p>
<blockquote>
<p>见录屏讲解——扫描表达式入栈</p>
</blockquote>
<h2>五、树</h2>
<h3>树</h3>
<ul>
<li>n 个结点的树，所有结点的度数之和为 n-1  或  <strong>总度数 + 1 = 结点数</strong></li>
</ul>
<blockquote>
<p>除了根结点外，每个结点都是某个结点的子结点，也就是对应着 1 个度</p>
</blockquote>
<ul>
<li>树的<strong>路径长度</strong>是从树根到每个结点的路径长度<strong>总和</strong></li>
<li>把树看做有向图，则<strong>入度=出度</strong>。除了根结点，所有结点都只有 1 个入度，则总入度 = n-1</li>
<li>度为 n 的树，其结点数等于 $\displaystyle\sum_{i=0}^nn_i$ ，即结点为 0-n 的所有点个数之和</li>
</ul>
<h3>二叉树</h3>
<ul>
<li>度为 2 的树不一定是二叉树</li>
</ul>
<blockquote>
<p>二叉树要分左右，度为 2 的树可以不分</p>
</blockquote>
<ul>
<li>任意二叉树度为 2 的结点数 = 叶子结点数 - 1</li>
</ul>
<blockquote>
<p>对于所有结点数n，总读书d，有$n_0+n_1+n_2 = n = d + 1 = 2n_2 + n_1 + 1 \implies n_2 = n_0 - 1$</p>
</blockquote>
<ul>
<li>$2^1+2^2+...+2^n = 2^{n+1}-2 &lt; 2^{n+1}$ 这意味着再有鬼屎题目给你一个结点数很大的二叉树，不用去慢慢试着加，直接找一个尽可能大的但不超过它的 2 的指数次方，则下一个指数次必然超过</li>
</ul>
<h3>完全二叉树</h3>
<ul>
<li>h 层的<strong>满完全二叉树</strong>，一共有 $2^h-1$ 个结点</li>
<li>完全二叉树，若 $i≤\lfloor {\frac{n}2} \rfloor$，则结点 i 为<strong>分支结点</strong>，否则为<strong>叶子结点</strong></li>
<li>完全二叉树，$n_1$ 要么是 0，要么是 1
因此判断两个结点是否属于同一层，可用 $\lfloor log_2p \rfloor = \lfloor log_2q \rfloor$</li>
</ul>
<h3>树存储方式</h3>
<h4>双亲表示法</h4>
<p><img src="https://img.085404.xyz/images/c9b62c8d090192138a1e3ca27815b624.png" alt="image" /></p>
<h4>孩子表示法</h4>
<p><img src="https://img.085404.xyz/images/e1f7911b37e11b49601a7b8784350c17.png" alt="image" /></p>
<h4>孩子兄弟表示法</h4>
<h5>树转化为二叉树</h5>
<p><strong>左孩子，右兄弟，将普通的树转化为二叉树</strong></p>
<p><img src="https://img.085404.xyz/images/35192175e5745f6aef02ee64430e6b9f.png" alt="image" /></p>
<h5>二叉树转化为森林</h5>
<p>根据<strong>左孩子，右兄弟</strong>的原则，BAD 为三兄弟，则<strong>将兄弟结点拆分为单独的树</strong>，然后<strong>他的子结点继续按照左孩子、右兄弟的原则转化为二叉树</strong></p>
<p><img src="https://img.085404.xyz/images/7c984967b5bd8c3ba6ba44cdad4a4244.png" alt="image" /></p>
<h5>森林转化为二叉树</h5>
<p>按照上一个的顺序，逆向操作即可。</p>
<h3>遍历</h3>
<h4>二叉树的遍历</h4>
<blockquote>
<p>先、中、后其实说的就是根结点的位置</p>
</blockquote>
<ul>
<li>先序遍历：<strong>根</strong>结点、左结点、右结点</li>
<li>中序遍历：左结点、<strong>根</strong>结点、右结点</li>
<li>后序遍历：左结点、右结点、<strong>根</strong>结点</li>
</ul>
<h4>树、森林的遍历</h4>
<blockquote>
<p>先根-先序
后根-中序</p>
</blockquote>
<ul>
<li>先根遍历：先访问根结点，然后从左到右依次访问每一颗子树。&lt;u&gt;先根遍历&lt;/u&gt;访问顺序和这棵树<strong>对应的二叉树</strong>的&lt;u&gt;先序遍历&lt;/u&gt;相同</li>
<li>后根遍历：先从左到右依次访问每一颗子树，然后访问根结点。&lt;u&gt;后根遍历&lt;/u&gt;访问顺序和这棵树<strong>对应的二叉树</strong>的&lt;u&gt;<strong>中序遍历</strong>&lt;/u&gt;相同</li>
</ul>
<table>
<thead>
<tr>
<th>树</th>
<th>二叉树</th>
<th>森林</th>
</tr>
</thead>
<tbody>
<tr>
<td>先根</td>
<td>先序</td>
<td>先序</td>
</tr>
<tr>
<td>后根</td>
<td>中序</td>
<td>中序</td>
</tr>
</tbody>
</table>
<h3>二叉排序树</h3>
<h4>插入结点</h4>
<p><strong>左边小、右边大</strong>，然后依次插入即可</p>
<p><img src="https://img.085404.xyz/images/67c4d4d4c5d2981eb2e49582fcc8692d.png" alt="image" /></p>
<h4>删除结点</h4>
<p><img src="https://img.085404.xyz/images/8d9d5d5bb20aea0cbef6ad373dddd86c.png" alt="image" /></p>
<p>删除后如下</p>
<p><img src="https://img.085404.xyz/images/937edf36ebca60c3349543ac5a69c2e9.png" alt="image" /></p>
<h4>查找</h4>
<p><img src="https://img.085404.xyz/images/d3b223d730b67df8b8fa726ec81ed965.png" alt="image" /></p>
<blockquote>
<p>见录屏讲解——ASL 查找</p>
</blockquote>
<h3>平衡二叉树</h3>
<blockquote>
<p>平衡二叉树是一种<strong>特殊的二叉排序树</strong>，因此插入时就按排序树插入，然后不平衡再来旋转调整</p>
</blockquote>
<ul>
<li>也叫平衡树：任意结点的左右子数高度差的绝对值不超过 1，<strong>且子树都是平衡树</strong></li>
<li>平衡因子：<strong>左</strong>子树和<strong>右</strong>子树的高度差，换言之其值只可能为 0, 1 或 -1</li>
<li><strong>最小平衡二叉树公式</strong>  $F (n) = F (n-1) + F (n-2) + 1$
<ul>
<li>$F(n)$：深度/高度为 n 的最小的(即结点数最少的)平衡二叉树。其中 $F(1)=1, F(2)=2$</li>
<li>按照公式，深度为 1, 2, 3，...... 的最小二叉树所需结点分别为 1, 2, 4, 7, 12, 20......</li>
</ul>
</li>
</ul>
<h4>插入</h4>
<blockquote>
<p>单转，转根；双转，先转子再转根</p>
</blockquote>
<h5>LL 插入（右转）</h5>
<ul>
<li>LL：左孩子的左子树</li>
<li>对<strong>根结点</strong>右转</li>
</ul>
<p><img src="https://img.085404.xyz/images/eea018d0c06b3875e273a1bcbd3bdb83.png" alt="image" /></p>
<h5>RR 插入（左转）</h5>
<ul>
<li>RR：右孩子的右子树</li>
<li>左转：对<strong>根结点</strong>左转</li>
</ul>
<p><img src="https://img.085404.xyz/images/85fb509cace1b5ff13c9109a0d480445.png" alt="image" /></p>
<h5>LR 插入（先左转再右转）</h5>
<ul>
<li>LR：左孩子的右子树</li>
<li><strong>先对左孩子左转</strong>，再对根结点右转</li>
</ul>
<p><img src="https://img.085404.xyz/images/ba4c76d9421be4e181b8cacbdf047d34.png" alt="image" /></p>
<h5>RL 插入（先右转再左转）</h5>
<ul>
<li>RL：<strong>先对右孩子右转</strong>，再对根结点左转</li>
</ul>
<p><img src="https://img.085404.xyz/images/0c14279f471a9d059de1320ceb729ce3.png" alt="image" /></p>
<h4>哈夫曼树</h4>
<p>带权路径长度 (WPL) 最小的二叉树称为哈夫曼树，也称最优二叉树</p>
<h5>构造哈夫曼树</h5>
<ol>
<li>首先，将给出的序列<strong>递增排序</strong>！！</li>
<li>每次找<strong>权值最小的两个点</strong>拼成一个树，<strong>使用过的结点做个标记</strong>；</li>
</ol>
<blockquote>
<p>若给一个字符串，每个字母出现的<strong>次数就是其权值</strong>
若给字符出现的概率，就把<strong>概率当权值</strong>，例如 9%，就取 9</p>
</blockquote>
<ol>
<li>将合并后的根结点<strong>按照排序</strong>插入序列中，继续从序列中选择最小的两个合并，以此类推；</li>
<li>如果要构造 K 叉哈夫曼树，注意看结点是否足够，不够的话采取和<strong>最佳归并树</strong>一样的解决方法</li>
</ol>
<p><img src="https://img.085404.xyz/images/46785b3510b4315b9a1e2429a4a61ec1.png" alt="image" /></p>
<h5>哈夫曼编码</h5>
<ol>
<li>从哈夫曼树的根结点，往左是 1，往右是 0，得出的即为<strong>哈夫曼编码</strong>（此处原稿为<s>往左是 1/0，往右是 0/1</s>，不知何解）</li>
</ol>
<blockquote>
<p><strong>前缀编码</strong>：形成的哈夫曼编码中，没有任何一个编码是其他编码的前缀，则这组编码称为前缀编码</p>
</blockquote>
<blockquote>
<p>每走一步就是一个二进制编码。因此算出每个字母所需的编码乘出现的次数，加起来就是解码的二进制位数    <s>WTF？？这在说什么？</s></p>
</blockquote>
<ol>
<li><strong>编码的总长度</strong>=每个字符对应哈夫曼编码的<strong>位数</strong> 乘出现的次数，再相加</li>
<li>带权路径长度 WPL = 结点的权 x 从跟到结点的路径长度，再相加</li>
</ol>
<p><img src="https://img.085404.xyz/images/94fd4f1db1a34c2413b7fa38863f33f2.png" alt="image" /></p>
<h3>归并树</h3>
<ul>
<li>就是把结点拼在一起变成树，二路归并就是两两拼，三路就是三个一起拼（参考哈夫曼树）</li>
<li>归并树的 WPL（带权路径长度）<strong>x 2</strong> = 归并过程中磁盘 I/O 次数</li>
<li><strong>最佳归并树</strong>：就是 WPL 最小的哈夫曼树</li>
<li>对于 K 路归并，若初始归并段（结点数）数量<strong>无法构成严格的 K 叉归并树</strong>，则需要补充几个<strong>长度为 0 的虚段</strong>(<em><strong>但是别在图里把 0 写出来！！！</strong></em>)，再进行 K 叉哈夫曼树的构造 ^un3nnj</li>
</ul>
<p><img src="https://img.085404.xyz/images/1d0cae74eea388befea92bb1f5f2a89c.png" alt="image" /></p>
<blockquote>
<p>注意要计算的是读字节数、还是写字节数、还是总的。例如上图 <code>2+3=5</code> 指的是<strong>读</strong> 5 个字节，<strong>写</strong> 5 个字节，因此<strong>这一趟</strong>的 I/O 为<code>5x2=10</code>字节</p>
</blockquote>
<hr />
<h3>例题</h3>
<p><img src="https://img.085404.xyz/images/11f07eec9eec3f369f4ac3096b3899b8.jpg" alt="image" /></p>
<p>$n_2 + n_1 + n_0 = 2n$，其中 $n_2 = n_0 - 1$，则 $n_1 = 2 (n-n_0) + 1$，即 $n_1$ <strong>必为奇数</strong>，故选 C</p>
<p><img src="https://img.085404.xyz/images/8b290e8e00f7c112beacd78c0054da0a.jpg" alt="image" /></p>
<p>n 个结点共有 2n 个指针域。由于每个结点都有一个入度，除了根结点外，故总共有 n-1 个入度，即非空指针域为 n-1 个，则空指针域为 2n-(n-1) = n+1 个，选 B</p>
<hr />
<h2>六、图</h2>
<h3>概念</h3>
<p><img src="https://img.085404.xyz/images/f3cbd697a17697de245347da4e2d74a2.png" alt="450x350" /></p>
<ul>
<li>判断有向图是否有环：深度优先、<strong>拓扑排序</strong></li>
</ul>
<h3>连通图</h3>
<p><strong>无向图</strong>，任意两个顶点是联通的，则为连通图；否则为非连通图。对于 n 个顶点的无向图，</p>
<ul>
<li>若连通，最少 n-1 条边</li>
<li>若非连通，最多 $C_{n-1}^2$ 条边</li>
</ul>
<h3>强连通图</h3>
<p><strong>有向图</strong>，任何一对顶点都是强连通的（<strong>可去可回</strong>，不一定非得是两点直接连接），则为强连通图。对于 n 个顶点的有向图，</p>
<ul>
<li>若强连通，最少 n 条边</li>
</ul>
<h3>子图/生成子图</h3>
<p><img src="https://img.085404.xyz/images/50395200c3426912aece39e13746de2c.png" alt="image" /></p>
<ul>
<li><strong>生成子图</strong>：包含了原图的<strong>所有顶点</strong>所构成的子图</li>
</ul>
<h3>连通分量（无向图）</h3>
<p><img src="https://img.085404.xyz/images/0ab0d7c948a6d2e138fe89a3767c8d25.png" alt="image" /></p>
<ul>
<li>首先要是子图，然后是<strong>连通的</strong>，最后要求极大（有尽可能多的点和边）</li>
</ul>
<h3>强连通分量（有向图）</h3>
<p><img src="https://img.085404.xyz/images/7225cceb4522521ee2dc9fbb55a21305.png" alt="image" /></p>
<ul>
<li>首先要是子图，然后是<strong>强连通的</strong>（有去有回），最后要求极大（有尽可能多的点和边）</li>
</ul>
<h3>生成树（连通图）</h3>
<p><img src="https://img.085404.xyz/images/0c6c016fa1b5b2e738fd10063acada3d.png" alt="image" /></p>
<ul>
<li><strong>包括全部顶点</strong></li>
<li>极小连通子图</li>
</ul>
<h3>生成森林（非连通图）</h3>
<p><img src="https://img.085404.xyz/images/3ce2288d2b2011407c742376b4d9f178.png" alt="image" /></p>
<p><img src="https://img.085404.xyz/images/a0a16ea98d7435ba5d5242139cea2515.png" alt="|600x350" /></p>
<ul>
<li>也就是对非连通图，先找他的<strong>极大连通子图</strong>（连通分量），然后对每个连通分量找<strong>包括所有顶点的极小连通子图</strong>（生成树）</li>
</ul>
<h3>图的存储</h3>
<h4>邻接矩阵法</h4>
<p><img src="https://img.085404.xyz/images/5fa6a80e8bfdff8a807870aa9c66e3ae.png" alt="image" /></p>
<p><img src="https://img.085404.xyz/images/86b15b4effb80ad17745f4dd45b2f7eb.png" alt="image" /></p>
<ol>
<li>
<p><strong>无权值时</strong>，有边为 1，没有边为 0</p>
</li>
<li>
<p><strong>如果边有权，则填权值</strong>，没有就<strong>填无穷</strong></p>
</li>
<li>
<p>邻接矩阵为 A，则 $A^n[i][j]$  表示从 i 到 j 长度为 n 的边有几条</p>
</li>
<li>
<p><strong>邻接矩阵找深度优先</strong>：与邻接表法类似，从第一行开始走，到 1 的时候说明有边，然后跳到对应的结点的行；&lt;br /&gt;从新的行按照上面的方法继续走，每当走到一个没出现过的新结点记录，以此类推走到底；&lt;br /&gt;如果发现某个结点后面的都出现过了，当做全部走完了，然后往上<strong>回退到最近的没有走完的一层继续走</strong>；</p>
</li>
<li>
<p><strong>邻接矩阵找深度优先</strong>：从第一行开始，把所有 1 都走一遍，记录下来；&lt;br /&gt;把这个记录当做一个新的表，从第一个点开始，按照之前的方法，走完了就找后面一个点走；&lt;br /&gt;直到所有的点都被遍历到。</p>
</li>
</ol>
<h4>邻接表法</h4>
<p><img src="https://img.085404.xyz/images/7aee96aabb40aae552f7dc20826935b4.png" alt="image" /></p>
<ol>
<li>
<p>n 个结点<strong>竖着写 n 行两列</strong>，左边填结点，右边空着表示指针域；
每个结点连着谁就顺次往后面挂，写完了就在最后一个点的指针域填 <code>^</code> 表示结束；
<strong>无向图 1 to 2 和 2 to 1 都要写</strong>，每个点后面挂着的点<strong>无所谓前后顺序</strong>；
有向图按照箭头顺序写就行了。</p>
</li>
<li>
<p><strong>邻接表找深度优先</strong>：</p>
</li>
</ol>
<p>从头开始往后走，每走一个做个标记；&lt;br /&gt;每出现一个前面没有的新结点，就进到新节点里走，以此类推；&lt;br /&gt;如果发现某个结点后面的都被走过了，全部标记，然后往上<strong>回退到最近的没有走完的一层继续走</strong>；&lt;br /&gt;以此类推直到所有的结点都被标记。</p>
<ol>
<li>用邻接表表示图进行<strong>深度优先遍历（DFS）<strong>时，通常实现其算法采用的结构是</strong>栈</strong>。</li>
</ol>
<p>广度（BFS）采用的是<strong>队列</strong>。</p>
<h4>十字链表法（有向图）</h4>
<p><img src="https://img.085404.xyz/images/189c38772781a04c225e89f8580e19c5.png" alt="image" /></p>
<blockquote>
<p>箭头箭头，当然是有箭的才叫头，另一个叫尾。因此对于弧而言，<strong>出度是弧尾，入度才是弧头</strong></p>
</blockquote>
<p><img src="https://img.085404.xyz/images/1441bb277bb6d5236f4eff6a7b457d20.png" alt="image" /></p>
<p><img src="https://img.085404.xyz/images/6c00618f765e99ffec463f2df7c941af.png" alt="image" /></p>
<h4>邻接多重表（无向图）</h4>
<p><img src="https://img.085404.xyz/images/0e8e54c335c23d4e7de894259a27791b.png" alt="image" /></p>
<ul>
<li>对于无向图，<strong>有几条边，就有几个边表结点</strong>。每个边对应<strong>一个边表结点</strong>，而不是两个，所以显然边表结点不唯一（例如 1-2, 2-1 都是同一条边）</li>
<li>如果边表结点的 <code>ilink</code> 和 <code>jlink</code> 分别是 <code>0-1</code>，则顶点表中，从 0 出来的第一个箭头<strong>直接指 0</strong>，而从 1 出来的箭头如果没有 1 开头的边表结点，就只能指向 1 后面的 <code>jlink</code></li>
<li>边表结点中的<strong>数据域填的是结点对应的数组下标</strong></li>
</ul>
<h3>最小生成树</h3>
<h4>Prim 算法</h4>
<blockquote>
<p><em><strong>最小连通</strong></em>：你首先得跟已知点集连通，然后再去找最小</p>
</blockquote>
<p><strong>从顶点出发</strong>，找权最小的边，然后对<strong>当前所有遍历过的结点</strong>，找与它们相连的所有边中，权最小的，循环往复</p>
<h4>Kruskal 算法</h4>
<p><strong>对整个图</strong>，依次找权最小的边，然后将剩余结点相连</p>
<h3>最短路径（Dijkstra）</h3>
<p><img src="https://img.085404.xyz/images/05bf2c82df77ab963c547086adc1a122.png" alt="image" /></p>
<ol>
<li>从顶点出发，能到的点写权值，不能到的写 $\infty$</li>
<li>找出各路径中最短的，将对应点加入点集，然后下一行<strong>以这个最小值对应的路径作为开始</strong></li>
<li>每次计算路径时，如果新的路径长度<strong>小于旧的，才进行更新</strong>。大于旧的或者为 $\infty$ 则<strong>继承</strong>原来的</li>
</ol>
<h3>关键路径</h3>
<p><img src="https://img.085404.xyz/images/c19538f4f24346e4685a658d619efd59.png" alt="image" /></p>
<p>选择题无所谓，<strong>解答题按照表格形式写！！！</strong> 尤其是第二个表格</p>
<p><img src="https://img.085404.xyz/images/814e7f4e706ef834a55b717d3823912e.png" alt="|600x300" /></p>
<ul>
<li>对于每个结点，有最早时间和最晚时间</li>
<li><strong>事件最早 ve (i)</strong>：把走到这个点的所有路径的权值相加，<strong>取最大的值</strong>
<blockquote>
<p>来的时候取最大值</p>
</blockquote>
</li>
<li><strong>事件最晚 vl (i)</strong>：最早填完了以后，把<strong>最后一个点的最早时间抄下来作为它的最晚时间</strong>。然后倒过去，用弧头的最晚时间依次减弧的权值，得到弧尾的最晚时间。对于有多个值的结点，<strong>取最小的值</strong>
<blockquote>
<p>回的时候取最小值</p>
</blockquote>
</li>
<li><strong>活动的最早时间 e (i)</strong>：就是这条弧起点（弧尾）<strong>对应结点的最早时间</strong></li>
<li><strong>活动的最晚时间 l (i)</strong>：就是这条弧终点（弧头）<strong>对应结点的最晚时间 - 弧的权值</strong></li>
<li><strong>关键活动</strong>：<code>l(i)-e(i)=0</code> 的活动</li>
<li><strong>关键路径</strong>：<code>l(i)-e(i)=0</code> 的活动对应的路径</li>
</ul>
<blockquote>
<p>即 <code>(最早时间，最晚时间)</code> 相等的结点连成的路径（<strong>可能有多条</strong>）为关键路径，其路径上的活动即为关键活动</p>
</blockquote>
<ul>
<li>关键路径的<strong>长度</strong>：关键活动对应数值加起来</li>
<li>要加快/缩短整个工程的工期，必须<strong>同时改变所有的关键路径</strong>对应的关键活动</li>
</ul>
<h2>七、查找</h2>
<h3>折半（二分）查找</h3>
<p><img src="https://img.085404.xyz/images/662000df9ea13800a0c858efeba67c83.png" alt="image" /></p>
<ul>
<li><strong>判定树</strong>就是二分查找的过程，用二叉树表示</li>
<li>判定二叉树是二叉排序树</li>
<li>向二叉查找树中插入一个元素，其时间复杂度大概为 <code>O(log2n)</code></li>
<li>二分查找一个元素，查找长度就是从根结点开始的<strong>比较次数</strong>，不要去数树的深度！</li>
</ul>
<p><s>ASL 成功=层数乘该层节点数，然后相加，再除总节点数</s></p>
<p><s>ASL 失败=所有缺了左/右孩子 (也就是查找失败的地方) 的结点数，然后乘层数，再相加，最后除总的失败地方的个数。注意，失败地方的层数，是按照它的父节点的层数算的，不要算到下一层去了。</s></p>
<h3>分块查找</h3>
<p><img src="https://img.085404.xyz/images/cebde478ad734fb777d54e8ca1e100cb.png" alt="image" /></p>
<h3>散列表</h3>
<ol>
<li>
<p><strong>散列函数</strong>：一个把查找表中的关键字映射成该关键字对应的地址的函数</p>
</li>
<li>
<p><strong>散列表（hash 表）</strong>：根据关键字而进行直接访问的数据结构</p>
</li>
<li>
<p><strong>常用的散列函数</strong></p>
<ol>
<li>直接定址法：题目直接给出散列函数 <code>H(key)</code></li>
<li>除留余数法：设表长为 <code>m</code>，<code>H(key)=key%p</code>，<code>p</code> 为不大于 <code>m</code> 但最接近 <code>m</code> 的质数</li>
</ol>
</li>
<li>
<p><strong>装填因子</strong>
$\alpha = {\frac{n}{m}}$，其中n为表中记录数，m为散列表长度</p>
</li>
<li>
<p>散列表的平均查找长度依赖于散列因子。 $\alpha$ 越大，表示装填记录越<strong>满</strong>，<strong>发生冲突可能性越大</strong>；反之亦然</p>
</li>
</ol>
<h4>处理冲突</h4>
<ol>
<li>新地址的计算公式（<strong>无论是插入时还是处理冲突时</strong>，<em><strong>每一次都是在调用公式！！！</strong></em>）
<ul>
<li>根据不同的冲突处理办法， $d_i$ 取不同的值</li>
<li><strong>%m</strong>是指<strong>表长</strong>，不是给的 hash 函数中的 mod 值！注意和 <code>ASL失败</code> 作区分</li>
</ul>
<blockquote>
<p>说白了，插入的时候是在整个表的范围里插，所以%表长。而计算 <code>ASL失败</code> 时，仅仅只考虑 hash 函数映射出的真实长度，即 hash 函数的 mod 值</p>
</blockquote>
</li>
</ol>
<p>$$
H_i = (H(key) + d_i) % m
$$</p>
<ol>
<li>采用什么方法处理冲突的，计算查找次数时（也就是计算 <code>ASL失败</code> 时）就按照这个方法来</li>
<li><code>ASL成功</code>以插入时的<strong>比较次数</strong>计算（与空比较也算作 1 次），除的是<strong>一共多少个数</strong>（元素个数）</li>
<li><code>ASL失败</code> 以<strong>从当前元素开始，在整个表的范围内向后寻找到空为止</strong>的次数（开始算 1 次，找到空也算 1 次），<em><strong>除的的散列函数中的%数</strong></em></li>
<li>如果没有说明表长，则**%数**是多少，表长就是多少</li>
<li>如果算出来的<strong>散列地址为负数</strong>，就从第一个位置开始倒着数</li>
</ol>
<h5>线性探测法</h5>
<p>根据散列函数存，如果冲突，<strong>依次向后</strong>（ $d_i = 1$ ）寻找空位插入</p>
<p><img src="https://img.085404.xyz/images/62ded1b3fad6fd1d50e07c772f82147c.png" alt="image" /></p>
<h5>平方 (二次) 探测法</h5>
<ul>
<li>$d_i = 0^2, 1^2, -1^2, 2^2, -2^2, ......, k^2, -k^2$ （本身，右左右左......）</li>
<li><code>k &lt;= m/2</code></li>
<li>表长 m 必须为可以表示成 <code>4k+3</code> 的<strong>素数</strong></li>
</ul>
<h5>双 (再) 散列法</h5>
<ul>
<li>$d_i = hash_2(key)$ ，也就是给了一个新的 hash 函数用来计算 d 值</li>
</ul>
<blockquote>
<p>注意，对于一个确定的数 key，解决它的冲突时 $d_i = hash_2(key)$ 显然为<strong>定值</strong>，因此可看做倍乘的线性探测法（ $d_i = hash_2(key)$ = 1 ）。而不同的 key 则 $d_i$ 不同</p>
</blockquote>
<ul>
<li>线性探测<strong>再散列的查找成功的平均查找长度</strong> (告诉你数据个数，要求平均查找次数不超过几次，然后又没给装填因子，问你表长)
$$
ASL_{success} = \frac{1 + \frac1{1-\alpha}}{2}
$$</li>
</ul>
<h5>伪随机序列法</h5>
<p>$d_i$ 为伪随机数序列</p>
<h5>拉链法</h5>
<ul>
<li>按照给出的表长，对每个地址往下挂元素就完事了，冲突几个就挂几个，没有元素的就打 <code>^</code></li>
<li>如果你的表<strong>横着画</strong>，那么<strong>同一行的元素对应的查找次数一样</strong>，从上到下为 1, 2, 3...</li>
<li>如果你的表<strong>竖着画</strong>，那么<strong>同一列的元素对应的查找次数一样</strong>，从左到右为 1, 2, 3...</li>
<li>拉链法计算查找失败时，查找次数为已经挂载的元素个数+1，即<strong>与空指针的比较也要算作 1 次</strong>（<s>也有观点认为不算，but who care?</s>）</li>
</ul>
<p><img src="https://img.085404.xyz/images/426cd3656945ac3574a34356f4c03393.png" alt="image" /></p>
<p><img src="https://img.085404.xyz/images/cb8490035265a20f5e51d3256caac457.png" alt="image" /></p>
<h3>B/B+ 树</h3>
<p>m 阶 B 树</p>
<ul>
<li>每个结点最多 m 个子树，即<strong>至多含有 m-1 个关键字</strong></li>
<li><strong>非叶子结点，至少有两个子树</strong>（也就是必须得开叉）</li>
<li><strong>除根结点外</strong>，非叶子结点至少含有 $\lceil {\frac{m}2} \rceil$ 个子树，即<strong>至少含有 $\lceil {\frac{m}2} \rceil -1$ 个关键字</strong></li>
</ul>
<blockquote>
<p>也就是说<em><strong>不管你几阶 B 树，根结点可以只有 1 个</strong></em></p>
</blockquote>
<blockquote>
<p>就想 3 阶 B 树就行了，每个结点最多 2 个关键字，满 3 个就要“进位”
2 个关键字形成 3 个间隔，每个间隔就是一颗子树</p>
</blockquote>
<h4>插入</h4>
<p><img src="https://img.085404.xyz/images/bfcc2aa8ed5935a59aaaffc9d96278e7.png" alt="image" /></p>
<p><strong>结点分裂方法</strong></p>
<ol>
<li>先将插入的关键字写入结点，然后计算 $\lceil {\frac{m}2} \rceil$ 位置（<strong>按照 1, 2, 3 的顺序</strong>）对应的关键字</li>
</ol>
<blockquote>
<p>其实也就是中间位置</p>
</blockquote>
<ol>
<li>该结点<strong>左边</strong>的关键字原地<strong>不动</strong>，<strong>右边</strong>的放到<strong>新结点</strong>（记住，<strong>分裂一定会造就新结点！</strong>）</li>
<li>$\lceil {\frac{m}2} \rceil$ 位置对应的关键字按顺序插入该结点的<strong>父结点</strong></li>
<li>如果父节点个数也超标了，继续执行上面的步骤</li>
<li><em><strong>注意，插入的时候务必保持排序树！</strong></em></li>
</ol>
<h4>删除</h4>
<h5>被删除关键字不在终端结点（不是叶子结点）</h5>
<p><img src="https://img.085404.xyz/images/20c09b4ade0d3637499fc49572aa9fbc.png" alt="image" /></p>
<ul>
<li>左子树多，找前驱替代</li>
<li>右子树多，找后继替代</li>
<li>左右一样多，合并两个子树，然后再删除目标关键字</li>
</ul>
<h5>被删除关键字在终端结点（是叶子结点）</h5>
<p><img src="https://img.085404.xyz/images/340d42e0a8e6ac3e26326a9ede28bfe1.png" alt="image" /></p>
<ul>
<li>如果叶子结点里关键字够多，直接删</li>
<li>如果叶子结点里关键字不够，找<strong>兄弟借</strong>，然后<strong>父子换位</strong>（父亲替代被删除的，然后借的那个兄弟，即儿子，当父亲）</li>
<li>如果<strong>兄弟不够借</strong>，<strong>父子合并</strong>（<em><strong>父亲下来和兄弟合并</strong></em>）</li>
</ul>
<h3>串的匹配</h3>
<h4>朴素模式匹配算法</h4>
<ul>
<li>主串长度为 n，模式串（你要找的串）长度为 m，将主串中所有长度为 m 的子串和模式串进行对比，直到找到一个完全匹配的子串，或所有子串都不匹配为止</li>
<li>主串中最多有 <code>n-m+1</code> 个子串</li>
</ul>
<h4>KMP 算法</h4>
<h5>next 数组</h5>
<blockquote>
<p>见录屏讲解——KMP 求 next 数组</p>
</blockquote>
<h5>nextval 数组</h5>
<blockquote>
<p>见录屏讲解——KMP 求 nextval</p>
</blockquote>
<h2>八、排序</h2>
<h3>排序总结</h3>
<table>
<thead>
<tr>
<th>排序方法</th>
<th>时间复杂度 (平均)</th>
<th>时间复杂度 (最坏)</th>
<th>时间复杂度 (最好)</th>
<th>空间复杂度</th>
<th>稳定性</th>
</tr>
</thead>
<tbody>
<tr>
<td>插入排序</td>
<td>$O(n^2)$</td>
<td>$O(n^2)$</td>
<td>$O(n)$</td>
<td>$O(1)$</td>
<td>稳定</td>
</tr>
<tr>
<td>希尔排序</td>
<td>$O(n^{1.3})$</td>
<td>$O(n^2)$</td>
<td>$O(n)$</td>
<td>$O(1)$</td>
<td>不稳定</td>
</tr>
<tr>
<td>选择排序</td>
<td>$O(n^2)$</td>
<td>$O(n^2)$</td>
<td>$O(n^2)$</td>
<td>$O(1)$</td>
<td>不稳定</td>
</tr>
<tr>
<td>堆排序</td>
<td>$O(nlog_2n)$</td>
<td>$O(nlog_2n)$</td>
<td>$O(nlog_2n)$</td>
<td>$O(1)$</td>
<td>不稳定</td>
</tr>
<tr>
<td>冒泡排序</td>
<td>$O(n^2)$</td>
<td>$O(n^2)$</td>
<td>$O(n)$</td>
<td>$O(1)$</td>
<td>稳定</td>
</tr>
<tr>
<td>快速排序</td>
<td>$O(nlog_2n)$</td>
<td>$O(n^2)$</td>
<td>$O(nlog_2n)$</td>
<td>$O(nlog_2n)$</td>
<td>不稳定</td>
</tr>
<tr>
<td>归并排序</td>
<td>$O(nlog_2n)$</td>
<td>$O(nlog_2n)$</td>
<td>$O(nlog_2n)$</td>
<td>$O(n)$</td>
<td>稳定</td>
</tr>
<tr>
<td>计数排序</td>
<td>$O(n+k)$</td>
<td>$O(n+k)$</td>
<td>$O(n+k)$</td>
<td>$O(n+k)$</td>
<td>稳定</td>
</tr>
<tr>
<td>桶排序</td>
<td>$O(n+k)$</td>
<td>$O(n^2)$</td>
<td>$O(n)$</td>
<td>$O(n+k)$</td>
<td>稳定</td>
</tr>
<tr>
<td>基数排序</td>
<td>$O(n*k)$</td>
<td>$O(n*k)$</td>
<td>$O(n*k)$</td>
<td>$O(n+k)$</td>
<td>稳定</td>
</tr>
</tbody>
</table>
<ul>
<li>不稳定：<strong>快些选</strong>一<strong>堆</strong>好友来聊天（快速、希尔、选择、堆）</li>
</ul>
<blockquote>
<p>因为情绪不稳定，所以才要“快些选一堆好友来聊天”</p>
</blockquote>
<ul>
<li>时间快：<strong>快些归队</strong>（快速、希尔、归并、堆）</li>
<li>排序时注意，<strong>打下划线的元素</strong>意思为表示其稳定性，因此注意其摆放位置</li>
</ul>
<h3>插入排序</h3>
<p>依次遍历所有元素，将待排序的元素插入前面已经排好序的子序列中</p>
<p><img src="https://img.085404.xyz/images/548c55ef867447adad67c2994259faf2.png" alt="image" /></p>
<h3>希尔排序</h3>
<p><img src="https://img.085404.xyz/images/afaf7bbb526b92a9218c3cc43c0085bc.png" alt="image" /></p>
<p><img src="https://img.085404.xyz/images/ec93b961e02dcf39f41b094373ae9a66.png" alt="image" /></p>
<ul>
<li>d 不一定为每次减半，可能由题目直接给出</li>
<li>如果没有给出 <code>d</code>，则根据 <code>表长/2</code> 自己设定一个初始值</li>
<li>对于某个元素，位置+d 之后超出表格，则<strong>原地不动</strong></li>
<li>希尔排序的子表可以有若干位，只要+d 后仍在表内的都算</li>
</ul>
<h3>冒泡排序</h3>
<p><img src="https://img.085404.xyz/images/c14c4bbd07c996b3cae68ad989ac8744.png" alt="image" /></p>
<ul>
<li>每一趟都逐个比较相邻两个大小</li>
<li>从左边往右比较，冒泡到右边（无论升序还是降序）</li>
<li>从右往左比较，冒泡到左边</li>
</ul>
<h3>快速排序</h3>
<blockquote>
<p>快排快排，当然双管齐下（前、后双指针）才叫快</p>
</blockquote>
<p><img src="https://img.085404.xyz/images/355afbd30a571ddf6420c3b6a8fc9319.png" alt="image" /></p>
<ol>
<li>两个指针，<code>low</code> 指头，<code>high</code> 指尾。</li>
<li>以第一个元素为基准元素（<strong>可以将它暂存到别处，视作删除；也可以保留，总之要将其特别标识出来</strong>），<code>high--</code> 从右边找比基准小的，找到了就放到 <code>low</code>;</li>
<li>接着 <code>low++</code> 从左边找比基准大的，找到了就放到 <code>high</code>;</li>
<li>当 <code>low = high</code> 时，将刚刚暂存的基准元素放到 <code>low = high</code>，中间没有改变的元素照抄下来。至此，一趟排序完毕，且<strong>基准元素的最终位置已找到，后续无需移动！</strong></li>
</ol>
<blockquote>
<p>快速排序一趟的完成标志：对于选定的基准元素，左边比他小，右边比他大，则一趟排序完成</p>
</blockquote>
<h3>选择排序</h3>
<p><img src="https://img.085404.xyz/images/faea258c9666600c48a95212a1e3befd.png" alt="image" /></p>
<h3>堆排序</h3>
<ol>
<li>向堆中插入一个元素，其时间复杂度为 <code>O(log2n)</code></li>
<li><strong>向堆插入</strong>：插入元素<strong>先放到堆底</strong>（数组最后），然后按照大/小根堆调整</li>
<li><strong>从堆删除</strong>：将要删除的元素<strong>和堆底元素交换</strong>，然后把要删的删除，接着按照大/小根堆调整</li>
<li>堆的形状是<strong>一颗完全二叉树</strong>。</li>
</ol>
<blockquote>
<p>大根堆：根≥左、右
小根堆：根≤左、右</p>
</blockquote>
<ol>
<li><strong>堆排序</strong>：
<ul>
<li>将给出的一组数据按照满二叉树<strong>逐行</strong>放进去；</li>
<li>按要求调整为大/小根堆（<strong>调整完成后的堆才叫初始堆</strong>）；</li>
</ul>
<blockquote>
<p>构造大/小根堆时注意，永远把子结点中最大的/最小的放上去
计算比较次数时，<em><strong>选取子结点中最大/最小这一步本身也需要一次对比</strong></em>
调整为大/小根堆时，从 $\lfloor {\frac{m}2} \rfloor$ 位置的结点开始，向前（向堆顶方向）检查</p>
</blockquote>
<ul>
<li>将<strong>堆顶和堆底（即二叉树最后一个结点）元素互换</strong>（从这一步开始才算做一次排序）；</li>
<li>堆顶元素换下去后<strong>用虚线连接</strong>，表示此后不再对它进行处理；</li>
<li>堆底元素换上去后，重新按照大/小根堆调整；</li>
</ul>
<blockquote>
<p>注意，此时应排除虚线连接的结点，将剩余结点视作新堆</p>
</blockquote>
<ul>
<li><strong>调整完成后，视为一趟排序完成</strong></li>
<li>以此类推，直到排序完成。</li>
</ul>
</li>
</ol>
<h3>归并排序</h3>
<p><img src="https://img.085404.xyz/images/17c5e93a57aee0643758a61a001c36e8.png" alt="|500x500" /></p>
<ol>
<li>n 个元素归并排序，<strong>每一趟</strong>归并的时间复杂度为 <code>O(n)</code>。</li>
</ol>
<h3>拓扑排序</h3>
<p>每次找<strong>入度为 0</strong> 的结点，然后将它和<strong>从它射出的所有边</strong>删除，以此类推</p>
<h3>基数排序</h3>
<p>按照个、十、百位的大小去排</p>
<p><img src="https://img.085404.xyz/images/73f27dda0b8dfbea4c857435d84a0279.png" alt="image" /></p>
<p><img src="https://img.085404.xyz/images/ee6afeb4f60103ec9a97b2fa536a564f.png" alt="image" /></p>
<p>第一趟收集：<strong>降序排列时</strong>，个位越大的在前面，因此<strong>从左到右、从上到下</strong>串联起来</p>
<ul>
<li>438，888, 168, 007, 666, 996，985, 233, 211, 111, 520</li>
</ul>
<p><img src="https://img.085404.xyz/images/3b693d89d2432707fd31f9a193a2a1b3.png" alt="image" /></p>
<p>第二趟收集：996, 888, 985, 168, 666, 438, 233, 520, 211, 111, 007</p>
<p><img src="https://img.085404.xyz/images/253997febad666c266816155ca678f3e.png" alt="image" /></p>
<p>第三趟：996, 985, 888, 666, 520, 438, 233, 211, 168, 111, 007（<strong>排序完成</strong>）</p>
]]></content>
    <author><name>Sun2ot</name></author>
    <category term="笔记"/>
  </entry>
  <entry>
    <title>Obsidian图床图片处理方案</title>
    <link href="https://blog.085404.xyz/posts/obsidian%E5%9B%BE%E5%BA%8A%E5%9B%BE%E7%89%87%E5%A4%84%E7%90%86%E6%96%B9%E6%A1%88/" rel="alternate" type="text/html"/>
    <id>https://blog.085404.xyz/posts/obsidian%E5%9B%BE%E5%BA%8A%E5%9B%BE%E7%89%87%E5%A4%84%E7%90%86%E6%96%B9%E6%A1%88/</id>
    <published>2022-11-13T00:00:00.000Z</published>
    <updated>2022-11-13T00:00:00.000Z</updated>
    <summary>本文叙述了多种情况下，对于obsidian的图片需求，采用何种图床方式解决更优。同时，针对常见的图床问题，如app访问不了、如何防盗刷等做出了回答。</summary>
    <content type="html"><![CDATA[<h2>情景一</h2>
<h3>背景</h3>
<p>你准备将其他平台的文章迁移至 Obsidian</p>
<p>文章中的图片全部是对应平台的链接（如语雀）</p>
<p>你希望将这些链接对应的图片上传至你指定的图床/对象存储平台</p>
<h3>操作</h3>
<ol>
<li>下载<code>Image auto upload Plugin</code> 和 <code>Local images</code>（<strong>两个都是插件</strong>）</li>
<li>使用<code>Local images</code>将文内图片下载至本地
(如果你的需求仅仅是<strong>将外链图片转化为本地图片</strong>，你的操作到此为止即可实现需求)</li>
</ol>
<blockquote>
<p>下载下来的图片默认存储到根路径下的<code>media</code>文件夹下</p>
<blockquote>
<p>该插件在进行此操作时，会自动将文内的图片链接替换为本地链接
<img src="https://img.085404.xyz/images/d87785202eb381efd87a8e5c07be4682.png" alt="" /></p>
</blockquote>
</blockquote>
<ol>
<li>再次对该文章，使用<code>Image auto upload Plugin</code>进行图片批量上传</li>
</ol>
<blockquote>
<p>插件会自行嗅探文内的所有图片文件，并进行批量上传
<code>Image auto upload Plugin</code>需配合<strong>PicGo软件</strong>才可完成上传操作
<img src="https://img.085404.xyz/images/c10570a92d842ba9697f69c24dce8d9b.png" alt="" /></p>
</blockquote>
<ol>
<li>删除<code>media</code>文件夹即可</li>
</ol>
<blockquote>
<p>如果不放心，也可以用<code>Clear Unused Images</code>清理一遍，看看<code>media/</code>是不是全部被清空</p>
</blockquote>
<p>Tips： <code>Image auto upload Plugin</code> 一次只能处理一篇文章，而 <code>Local images</code> 可以处理库中所有文件，所以下载容易，上传难</p>
<h2>情景二</h2>
<p>此前一直将图片存储在本地</p>
<p>现在想将图片批量上传至云端</p>
<p>参照情景一，直接对该文章使用 <code>Image auto upload Plugin</code> 进行图片批量上传即可</p>
<h2>Q&amp;A</h2>
<h3>图片名称格式问题</h3>
<p>在默认情况下，不启用第三方插件时，无论是从哪里复制的图片，粘贴到 obsidian 中时都会被格式化为 <code>Paste image YYYY-MM-DD-HH-mm-SS</code> 的格式。其中，由于文件名包含空格，因此 md 文档中的图片链接将会以 <code>%20</code> 代替空格。</p>
<p>因此，部分人会担心这一行为是否会导致分享出去的 md 文档在其他 markdown 编辑器（如 typora）打开时，图片将无法加载的问题。其实无须担心， <code>20%</code> 就是 url 中空格符的转义字符，而你在 markdown 编辑器中输入的图片链接，本质上就是由编辑器（其实就是个浏览器）对该地址进行一次访问，<strong>因此 obsidian 中的图片链接（只要不是 <code>wiki</code> 格式）通常是可以被其他软件识别的</strong>。</p>
<p>具体介绍见<a href="https://jerey.cn/android/2019/01/27/Http%E5%8D%8F%E8%AE%AE%E4%B8%AD%E7%A9%BA%E6%A0%BC%E7%9A%84%E7%BC%96%E7%A0%81%E5%A4%84%E7%90%86/">在http请求中，空格被encode成'+' or '%20的历史 - 夏敏的博客 | Anderson's Blog (jerey.cn)</a></p>
<h3>资源防刷防盗</h3>
<p>如果使用的是对象存储（<strong>不建议直接裸奔对象存储，非常危险！</strong>），就在对象存储的设置里加上跨域规则。</p>
<p>如果使用的对象存储+cdn，就直接在 cdn 上加跨域设置。</p>
<pre><code># 头部设置
Access-Control-Allow-Origin

# 头部参数
app://obsidian.md
capacitor://localhost
http://localhost
</code></pre>
<p>除了上述设置，还需要开启防盗链。</p>
<blockquote>
<p>防盗链的作用是，防止你放在 a.com/b.jpg 的文件，被张三盗用，然后放在他自己的站点，比如 b.xyz/b.jpg 。开启防盗链后，不在 a.com 域名下且针对 b.jpg 的请求，将会全部被无效。</p>
</blockquote>
<pre><code>你的域名1
你的域名2
...
obsidian.md
localhost

# 支持通配符 *，但 * 不可为空，例如下面两条规则并不是包含关系
*.domain.com
domain.com
</code></pre>
<p><strong>切记</strong>，这里要加上 <code>obsidian.md</code> ，否则你的 obsidian app 是无法加载出来的！</p>
<p>至于防盗链中的空 refer，我一般是开启的。关闭之后好像有什么地方访问不了，不过写文的时候记不清了，反正开着就对了 QWQ。</p>
<p>最后是防止图床流量被盗刷的问题。如果你是买的第三方图床，比如什么“去不”之类的，那无所谓，反正成本不是你承担。如果你也是使用的对象存储，<strong>务必上心这个问题！</strong> 否则一晚上刷掉你几千块流量费是分分钟的事。</p>
<p>解决方案也很简单，挂载 cdn 后，给 cdn 加上各种限制，比如 5 分钟内流量封顶 1G、限速 1MB/s、超载后立即下线并且关闭对象存储的公开访问等等，基本就问题不大了。即使被盗刷，成本也不高，一杯 cola 钱足矣。</p>
]]></content>
    <author><name>Sun2ot</name></author>
    <category term="教程"/>
  </entry>
  <entry>
    <title>Obsidian同步方案-基于Remotely Save插件</title>
    <link href="https://blog.085404.xyz/posts/obsidian%E5%90%8C%E6%AD%A5%E6%96%B9%E6%A1%88-%E5%9F%BA%E4%BA%8Eremotely-save%E6%8F%92%E4%BB%B6/" rel="alternate" type="text/html"/>
    <id>https://blog.085404.xyz/posts/obsidian%E5%90%8C%E6%AD%A5%E6%96%B9%E6%A1%88-%E5%9F%BA%E4%BA%8Eremotely-save%E6%8F%92%E4%BB%B6/</id>
    <published>2022-11-12T00:00:00.000Z</published>
    <updated>2022-11-12T00:00:00.000Z</updated>
    <summary>一种适用于obsidian的同步方式，通过remotely-save插件实现，支持增量同步，多端同步，使用对象存储或webdav进行数据存储。</summary>
    <content type="html"><![CDATA[<h1>一、使用 webdav</h1>
<h2>推荐产品</h2>
<p>日本：<a>teracloud</a></p>
<ul>
<li>注册即永久 10G，填邀请码 +5G，活动期间填邀请码可 +10G</li>
<li>国内可直连</li>
<li><strong>两年内不登录账号会删除数据</strong>，不过到期 1 个月前会邮件通知</li>
</ul>
<p>其他：Dropbox等支持webdav的云服务产品</p>
<blockquote>
<p>不推荐坚果云（中国）。不是因为别的，而是坚果云在请求次数上有限制（<s>阉割</s>）：
免费用户 600次/分钟，付费用户 1500次/分钟。</p>
<p>这意味着一旦你进行大批量的同步工作，会<strong>丢数据</strong>。。。</p>
</blockquote>
<h1>二、对象存储</h1>
<blockquote>
<p>这里以阿里云OSS为例</p>
</blockquote>
<h2>1 创建bucket</h2>
<p>访问<a href="https://oss.console.aliyun.com/">阿里云对象存储OSS</a>服务，点击右下角<code>创建bucket</code></p>
<p><img src="https://img.085404.xyz/images/8845fbc33cf6f549ab7ebb60611c68cf.png" alt="image" /></p>
<ul>
<li>
<p><strong>bucket名称</strong>：设置一个你喜欢的名称即可</p>
</li>
<li>
<p><strong>地域</strong>：选择一个离你近的地方即可（其实无所谓，都差不多的速度）</p>
</li>
<li>
<p><strong>存储类型</strong>：选<code>标准存储</code></p>
</li>
<li>
<p><strong>读写权限</strong>：</p>
<ul>
<li>如果你准备用OSS进行全文件同步（.md文档，附件等等），则选择<code>私有</code></li>
</ul>
<blockquote>
<p>==温馨提示==：<code>Remotely Save</code>通过S3进行同步时，似乎并不能够控制同步路径</p>
<blockquote>
<p>见官方描述<a href="https://github.com/remotely-save/remotely-save#s3">S3</a>：The bucket should be empty and solely for syncing <strong>a</strong> vault.</p>
</blockquote>
</blockquote>
<blockquote>
<p>这会有个什么问题呢？就是你一同步，bia ji，OSS的<strong>根目录</strong>就多了一堆你的文件，例如文档啊、图片啊、附件之类的。但是如果你准备建立或者已经建立了多个<code>vault</code>，那你就不得不按上述步骤创建多个<code>bucket</code>🤣</p>
</blockquote>
<ul>
<li>如果你既要同步，还准备用OSS作为<a href="01%20%E6%95%99%E7%A8%8B/011%20%E6%90%9E%E6%9C%BA/Obsidian%E4%BD%BF%E7%94%A8%E6%89%8B%E5%86%8C/PicGo%20+%20%E9%98%BF%E9%87%8C%E4%BA%91OSS%20%E8%87%AA%E5%BB%BA%E5%9B%BE%E5%BA%8A.md">图床工具</a>，则选择<code>公共读</code></li>
</ul>
</li>
<li>
<p><strong>其他选项最好不变</strong>，因为都是付费的，</p>
</li>
</ul>
<p><img src="https://img.085404.xyz/images/5a576771d35e22641ef57139ce285dc4.png" alt="image" /></p>
<h2>2 创建用户</h2>
<p>鼠标悬浮与页面右上角<code>头像</code>处，点击<code>AccessKey 管理</code></p>
<p><img src="https://img.085404.xyz/images/da95e5fc7703b4397d88687fe1bc1e24.png" alt="image" /></p>
<p>选择<code>开始使用子用户AccessKey</code></p>
<blockquote>
<p>这样如果出现安全问题，可以随时删除子用户</p>
</blockquote>
<p><img src="https://img.085404.xyz/images/d98b36c8cd1de98baf7d86412ded2eb5.png" alt="image" /></p>
<p>点击<code>创建用户</code></p>
<p><img src="https://img.085404.xyz/images/45501251791047292bf0a100a1a68508.png" alt="image" /></p>
<p><code>登录名称</code>和<code>显示名称</code>就是字面意思，自己填
勾选第二项 <code>Open API 调用访问</code></p>
<p><img src="https://img.085404.xyz/images/00dacdc277f5fead57e23e9e23a1ae92.png" alt="image" /></p>
<p>之后会显示用户的信息，<strong>务必妥善保存</strong>，该信息<strong>只会出现一次！！！</strong></p>
<h2>3 授权用户</h2>
<p>进入权限编辑页面，选中刚才创立的<code>子用户</code>，授予<code>完全控制</code>权限，点击<code>确定</code></p>
<p><img src="https://img.085404.xyz/images/e385b187480032466e46cdc478eef91e.png" alt="image" /></p>
<p><img src="https://img.085404.xyz/images/8d4d2cf804c5c6f458cff657320fe428.png" alt="image" /></p>
<h2>4 跨域设置</h2>
<p><img src="https://img.085404.xyz/images/50299889cb3ee6eb36999a0087ba38a0.png" alt="image" /></p>
<p>添加如下规则</p>
<pre><code>app://obsidian.md
capacitor://localhost
http://localhost
</code></pre>
<blockquote>
<p>允许 Headers 那里填<code>*</code>（星号）</p>
</blockquote>
<p><img src="https://img.085404.xyz/images/fb4e866fe195dbed22ab285e433a8657.png" alt="image" /></p>
<h2>5 Remotely Save 设置</h2>
<p><img src="https://img.085404.xyz/images/25e2ccdada405203a78cc59398f014f1.png" alt="image" /></p>
<ol>
<li>选中<code>S3或兼容S3的服务</code></li>
<li>输入下面<strong>5</strong>个设置。其中<code>Endpoint</code>和<code>Region</code>分别对应下图中②的上、下两个</li>
<li><code>Access Key ID</code> 和 <code>Secret Access Key</code> 都在刚刚保存的<strong>子用户的信息</strong>里（见 <strong>2 创建用户</strong>的末尾）。</li>
<li>bucket的名字就是第一步创建的阿里云OSS中的bucket的名字</li>
</ol>
<p><img src="https://img.085404.xyz/images/135b044e29706b0e677a25e5acda60ea.png" alt="image" /></p>
<p><strong>点击Remotely Save同步按钮即可！</strong></p>
<h1>三、组合方案</h1>
<h2>webdav + 存储桶</h2>
<p>文件通过<strong>webdav</strong>同步，而图片通过<strong>对象存储 + PicGo</strong></p>
<ul>
<li>对象存储负担小，只负责图片</li>
<li>短期看可有效<strong>减少下载流量和存储费用</strong>（因为 webdav 不要钱，而且存储桶内不存储文档本身）</li>
</ul>
<blockquote>
<p>其实长远看来这个方案并不合适。因为图片存储在图床，除了&lt;u&gt;分享方便&lt;/u&gt;和&lt;u&gt;节省本地存储空间&lt;/u&gt;外，其实没有什么优势。因为图床图片在本地渲染时进行的是&lt;u&gt;缓存，而不是下载&lt;/u&gt;。这意味着两个问题：一是<strong>一旦你的图片缓存失效了，在无网络环境下打开文档时就成了“无图模式”了！</strong>；二是缓存失效后，再次打开时变又会缓存一遍，这相当于对<strong>同一图片产生了两倍的下行流量</strong>(<s>阿里云狂喜</s>)。</p>
</blockquote>
<ul>
<li>对于已经删除的文档，<strong>不能在存储桶内及时清除不再被其引用的图片</strong>。<a href="01%20%E6%95%99%E7%A8%8B/011%20%E6%90%9E%E6%9C%BA/Obsidian%E4%BD%BF%E7%94%A8%E6%89%8B%E5%86%8C/Obsidian%E5%9B%BE%E5%BA%8A%E5%9B%BE%E7%89%87%E5%A4%84%E7%90%86%E6%96%B9%E6%A1%88.md">解决方案</a></li>
<li>移动端可直接分享 <code>.md</code> 源文件，因为其内包含图片链接，被分享者可见</li>
</ul>
<h2>单桶存储</h2>
<blockquote>
<p>即文档和附件存储在一个 bucket 中</p>
</blockquote>
<ul>
<li>
<p>时刻保持<strong>设备在本地拥有完整的数据</strong>，且所有文件都可以在公网被访问，便于分享 (<s>如果你不在意下行流量费用的话</s>)</p>
</li>
<li>
<p>所有文件只会产生一次下行流量就可以永久保存在本地，而且随时随地可以访问，无须在意网络问题</p>
</li>
<li>
<p>如删除了某一文档，可以借助插件及时清理掉其中不再被引用的附件（如只在这篇文档中使用的图片），从而<strong>节约 OSS 存储空间降低资费</strong></p>
</li>
<li>
<p><strong>移动端</strong>由于不能够将 markdown 转换为 PDF 导出，因此分享时只能分享 <code>.md</code> 源文件。但由于附件是通过本地链接插入的，这会导致<strong>被分享者无法查看文档内的附件</strong></p>
<blockquote>
<p>当然，你可以选择截图分享。。。</p>
</blockquote>
</li>
</ul>
<h2>双桶存储</h2>
<blockquote>
<p>即文档和附件分别存储在两个 bucket 中，附件以公网直链的形式插入文档进行渲染访问</p>
</blockquote>
<ul>
<li>
<p>该方法与 <code>webdav + 存储桶</code>（以下简称 <code>wb(webdav + bucket</code>） 的方案类似，无非就是附件同样放在存储桶中，而不是 webdav，因此<strong>二者优缺点相同</strong></p>
</li>
<li>
<p>由于文档也存储在 bucket 中，存储费用相比 wb 反而更贵（<strong>也就 0.000001 元/小时和 0.000002 元/小时的区别吧</strong>）</p>
<blockquote>
<p>这是真实数据，不是随口编的，你可以对照阿里云的价格表自行计算</p>
</blockquote>
</li>
</ul>
<h1>Tips</h1>
<p><code>2022 年 12 月 12 日</code>
remotely save 插件似乎存在 bug，当同步的附件中存在 mp4 时，在 Android 端会直接崩溃。崩溃速度快到你想拼手速关闭自动同步功能都做不到（我设置的是开启时自动同步一次。。。）</p>
]]></content>
    <author><name>Sun2ot</name></author>
    <category term="教程"/>
  </entry>
  <entry>
    <title>descript.xml损坏导致bak备份失效的解决方案</title>
    <link href="https://blog.085404.xyz/posts/descriptxml%E6%8D%9F%E5%9D%8F%E5%AF%BC%E8%87%B4bak%E5%A4%87%E4%BB%BD%E5%A4%B1%E6%95%88%E7%9A%84%E8%A7%A3%E5%86%B3%E6%96%B9%E6%A1%88/" rel="alternate" type="text/html"/>
    <id>https://blog.085404.xyz/posts/descriptxml%E6%8D%9F%E5%9D%8F%E5%AF%BC%E8%87%B4bak%E5%A4%87%E4%BB%BD%E5%A4%B1%E6%95%88%E7%9A%84%E8%A7%A3%E5%86%B3%E6%96%B9%E6%A1%88/</id>
    <published>2022-07-07T00:00:00.000Z</published>
    <updated>2022-07-07T00:00:00.000Z</updated>
    <summary>当小米手机通过自带备份产生的备份文件损坏时，如何进行修复，是APP重新还原</summary>
    <content type="html"><![CDATA[<blockquote>
<p>本文以小米手机为例</p>
</blockquote>
<ol>
<li>任选当前手机上安装的一个app（可联网+数据无需备份的那种最好，<strong>没别的作用，只是单纯的不会造成什么影响，就好比你把王者荣耀卸载了再重装一遍还是一样玩</strong>），在手机备份界面对其进行备份</li>
<li>经上述操作后，在<code>MIUI-Backup-AllBackup</code>路径下会出现<strong>以当前日期为名字</strong>的备份文件夹，里面有一个<code>.bak</code>文件和一个<code>descript.xml</code>文件</li>
<li>复制上述<code>.bak</code>文件的文件名，将其替换到你想要复原的<code>bak</code>的文件名。例如你想复原的为<code>111.bak</code>，刚刚备份的为<code>222.bak</code>，就把<code>111.bak</code>重命名为<code>222.bak</code></li>
<li>将刚刚备份产生的<code>descript.xml</code>文件，同理，<strong>复制</strong>到<code>111.bak</code>（现在已经被你重命名为<code>222.bak</code>）所在的文件夹内，替换其损坏的<code>xml</code>文件</li>
<li>回到手机的备份恢复页面，照常点击恢复，你会发现一个名称为<code>222.bak</code>但图标却并不是<code>222</code>这个应用的APP。选中它，照常备份恢复即可成功恢复原来的<code>111.bak</code>。</li>
</ol>
]]></content>
    <author><name>Sun2ot</name></author>
    <category term="教程"/>
  </entry>
</feed>
