寒夜雨 - RethinkAI
type: channel
link: https://www.coderlock.site/index.php/tag/rethinkai/
items: 【RethinkAI】IBM Model 1
items: 【RethinkAI】Agent数据集的调研研究
items: 【RethinkAI】OpenClaw的运行流程
items: 【RethinkAI】浅思Agent
items: 【RethinkAI】RAG与知识编辑
items: 【RethinkAI】传统序列模型
items: 【RethinkAI】分词与词嵌入技术
items: 【Rethink AI】Transformer模型
items: 【RethinkAI】循环神经网络
items: 【RethinkAI】神经元与反向传播

【RethinkAI】IBM Model 1
type: item
date: 2026-05-28T07:05:00+00:00
description: 最近在上机器翻译的课程。作为机器翻译领域的统计学模型的开山模型，姑且记录一下吧。背景一段时间之前，机器翻译主要靠专家手写语法规则，这样做机器翻译太慢、太贵、太难维护，比如我们看下面两句话：Time flies like an arrow.Fruit flies like a banana.这两句话的结构极其相似，唯一的区别便是用词不同，词的含义不同。如果想要通过写规则，从语法句法上将这两句话翻译成中文，就会很麻烦。随着技术的不断发展，我们获得了大量的平行语料，所谓平行语料，就是意义相同的句子，用两种语言书写，比如：{ "zh-cn": "我是一只猫", "en-US": "I am a cat", "ja": "私は猫が好き" }那么学者就想，可否应用统计学的方法，从双语语料中自动学习出对应关系？学者发现，不同语言的句子之间，词语之间是有一个大致的对应关系的。比如在我们的例子中，从中文到英语，单词之间是严格对应的，但是从中文到日语，会发现有些语法助词，比如「は」和「が」，没有一个很好的对应关系。所以我们需要在工程上做一点设计。核心思想学者们首先不去想如何解决这个问题，它们首先想，什么是一个好的机器翻译系统。这很简单，给定平行语料，对于一个意义的句子，源端目标端分别是$s$和$t$，那么对于一个理想的机器翻译系统$M$，它必然满足：$$ M(s)=t $$即给定源端，它会输出一个最合适的目标端句子，那么模型的参数就相当于：$$ \theta =\arg\max P(f\mid e) $$让理想的目标端句子出现的概率最高即可，但是当我们部署系统之后，源语言句子和目标语言句子几乎不可能在语料库中出现过，因此这个概率无法直接从语料库统计得到。所以我们不直接学习分布，而是先找到共现关系！我们先引入一个变量，叫做对齐变量：$a_j=i$，表示源端的第$i$个词，会翻译成目标端的第$j$词。那么有些词没有很好的对应关系，就把它们看作是由空白词（记作null）生成过来的。比如：SRC: null 我 喜欢 猫TGT：私 は 猫 が 好き对于目标端（TGT）的「私」，是由源端（SRC）的「我」一次生成过来的，从0开始索引，这就是第一个词……按照这个规律，我们能得到对齐变量为$a=(1,0,3,0,2)$。数学推导根据全概率公式，我们的目标：$$ P(e\mid f) = \sum P(f,a\mid e) $$然后把对齐变量打开，用$t(f_j|e_{a_j})$表示从$e_{a_j}$，源端的第$a_j$个词，翻译到目标端的第$j$个词的概率（$l$为源端句子长，$m$为目标端句子长）：$$ P(e\mid f) = \sum_{a_1=0}^l\sum_{a_2=0}^l\cdots\sum_{a_m=0}^l\frac{1}{(l+1)^m}\prod_{j=1}^mt(f_j|e_{a_j}) $$直接计算这个公式，复杂度太高，做一个简化，我们先考虑目标和源端都有两个词的情况：$$ P(f\mid e)=\sum_{a_1=0}^l\sum_{a_2=0}^lt(f_1|e_{a_1})t(f_2|e_{a_2}) $$根据高等数学的知识，级数前后两项分别只于自己的求和下标有关，拆开：$$ P(f\mid e)=\sum_{a_1=0}^l\sum_{a_2=0}^lt(f_1|e_{a_1})t(f_2|e_{a_2})=(\sum_{a_1=0}^l(f_1|e_i))(\sum_{a_2=0}^l(f_2|e_i)) $$Good，现在推导到$m$个词的情况：$$ P(f\mid e)=\frac{1}{(l+1)^m}\prod_{j=1}^m\sum_{i=0}^lt(f_j|e_i) $$这个公式的感性理解就是：目标词$f_j$可以由源句中任意一个词生成，所以它的概率是所有可能来源的贡献之和。整个目标句由每个目标词独立生成，所以再把每个目标词的概率乘起来。所以，我们的问题从「构造翻译系统」，变成了「找到一个最大化概率的词对齐关系」！但是……不知道怎么去学习！训练推导训练这里就用EM算法，严格的推导需要拉格朗日乘子法，但是没学过太难了看不懂，用一个比较直觉的方法！假设有一个新人进了汉化组，新人什么都不懂，所以觉得：猫 可能是 cats猫 也可能是 like猫 甚至可能是 I看字幕总结一下规律：猫 和 cats 总是一起出现好き 和 like 总是一起出现犬 和 dogs 总是一起出现那么就把：猫 -> cats 的概率高一点好き -> like 的概率高一点犬 -> dogs 的概率高一点最后就能学到一个真正的对应关系。落到形式上，假设我们有双语平行语料：$D=\{(e^{(s)},f^{(s)})\}_{s=1}^S$，我们…

【RethinkAI】Agent数据集的调研研究
type: item
date: 2026-05-09T01:06:00+00:00
description: 最近正在从事一些对Agent的研究，首先研究了一下相关的数据集，发现颇有意思，这次就根据组会汇报的草稿，总结成一个文档的形式。主要回答两个问题：现有的Agent数据集如何分类它们覆盖了哪些任务场景在开始调研之前，我觉得有必要给读者讲述一下所谓Agent究竟是什么。Agent这个概念我在之前的文章中其实是解释过的，简单来说就是「可以使用工具的AI」。知道这一点就够了。发展阶段总体来说，Agent数据集的发展方向分为三个阶段：早期：主要评测静态任务，集中于单能力、短链路的问题评估。中期：引入了可复现环境，开始测试稍稍复杂一些的能力。近期：开始强调动态性，更加贴近真实办公场景。从 2023 年至 2024 年，Agent 数据集总体上仍以单项能力评估为主。常见拆分维度包括工具调用、网页浏览、软件工程、GUI 操作和多步推理等。2024年末发布的 TheAgentCompany 可视为一个重要转折点（示例如图 2所示）：它不再仅评估某一工具能力，而是模拟员工工作流程，涵盖网页浏览、代码编写、程序运行等任务。与此同时，这一阶段仍存在任务偏短、动态性不足等问题。到 2025 年，Agent 数据集明显向场景化与长程化演进。以网络搜索方向为例，任务从短链路网页检索转向长程信息搜集： OpenAI 的 BrowseComp包含 1266 个需持续浏览互联网的问题；Mind2Web 2 提供 130 个真实长程网页任务，并引入 Agent-as-a-judge 评估复杂且时变的答案；字节发布的 WideSearch则聚焦大规模信息搜集，包含 200 个中英文任务，结果显示多数系统成功率接近 0%，最好约为 5%。进入 2026 年后，数据集进一步强调动态性、异步性与真实工作属性。 GAIA2将环境设定为可独立演化的时间线系统，要求 Agent 在时间约束、噪声事件、动态变化与协作条件下行动。示例任务包括「如果三分钟无人回复则叫车」与「找出当前时刻朋友最多的城市」等。针对这个阶段变化，我的想法是，未来的方法类工作，其瓶颈一定不只在模型本身，更在于系统该如何组织多个Agent、工具和记忆。分类学Agent数据集可以分为四类：能力数据集：关注单项能力，环境状态少。比如HumanEval用于评估 Agent 在编程任务上的表现：给定编程问题，通过准确率等基础指标衡量模型性能。沙箱类数据集：在能力数据集的基础上，增加了可复现环境的内容，通常也有自动评分系统。以 WebArena 为例，其通过可复现的 Web 环境（如网页快照）观察模型在复杂任务中的表现，例如要求 Agent 在历史页面快照中完成检索和决策任务。工作流数据集：通常是多应用、多步骤、流程依赖的数据集。例如 TheAgentCompany引入通讯与工作流软件，使 Agent既要完成协作沟通，也要完成流程性任务。其它专项数据集：比如安全领域的数据集AgentHarm，以及评估记忆能力的数据集MemoryArena等。这个分类背后的演进脉络也和我们之前所说的一致：从「单一能力评测」到「简化环境中的复合能力评测」，再到最近的「复杂环境中的真实环境评测」。通俗一点来说，评测对象从「模型答对了没有」逐步过渡到了「系统能否持续完成复杂的目标」。我认为这个转折的一个标志性数据集是TheAgentCompany数据集，它的一个Data Sample如下所示：{ "task_id": "admin-arrange-meeting-rooms", "role": "Administrator", "instruction_summary": "根据多个团队在下周二提交的会议时间，计算最少需要预订多少个会议室；把答案发送给 Chen Xinyi，并写入 /workspace/ans.txt。", "required_capabilities": [ "时间区间重叠计算", "资源调度", "RocketChat 通信", "文件写入" ], "expected_output": { "chat_message": "最少会议室数量", "file": "/workspace/ans.txt" } }Agent不仅需要进行文件的读写，还要进行计算和通信。当然，这个数据集在目前看来，仍然是有些简单和朴素了，不过放在过去那个全都是评估简单问题的时代，的确有着划时代的意义。任务场景最多的任务场景，就是网络环境数据集，也就是评估Web Agent的数据集，比如GAIA，通过联网检索+多模态的信息整合；WebLINX则将网络导航和多轮对话结合到了一起。还有OS环境下的任务场景，比如OSWorld，要求Agent具有在桌面环境进行鼠标移动、点击的能力，完成一些任务，比如Office套件的操作等。至于广受关注的Coding环境，则…

【RethinkAI】OpenClaw的运行流程
type: item
date: 2026-03-30T07:11:00+00:00
description: 最近有工作需要用到这个东西，姑且研究一下。代码太多，没法一行一行审计，就挑重要的说一说吧。不排除以讹传讹的可能性。之前写过一篇【RethinkAI】浅思Agent，研究得还是不够深入。接入层：Channels配置过OpenClaw的小伙伴可能知道，OpenClaw是允许通过各种即时通讯软件(IM)控制的，也允许本地的TUI/Web UI等方法交互。假设我们配置好了OpenClaw+某一个IM（比如Telegram或者飞书），那么信息首先通过接入层的这些IM，然后通过各个IM的原生方法，将信息传到本地的Gateway中。比如WhatsApp会使用Baileys Web，Telegram会走grammY。假设我们配置好了OpenClaw+本地TUI，那么信息就通过WebSocket+JSON的形式传给Gateway。总而言之，这一步做的事情，就是将各个渠道信息，汇总给Gateway，进行后续处理。中控层：GatewayGateway这一层算是比较重要的一层了，这一层长期运行，负责维护本地OpenClaw和所有Channel之间的连接、管理会话的状态、接收消息、发出任务指令、权限认证管理。当消息从channel从外部渠道涌入后，Gateway层会：消息识别与Session选择：把消息归入某个会话上下文。Agent选择（可选）：常规来说，OpenClaw每个session的运行是串行运行的，如果开启了Multi-agent Routing，那么Gateway还会将信息进行指派，分派给特定的agent进行处理。进入队列（可选）：如果当前的session中，已经在跑了，那么Gateway会判断是否能并发跑，如果不能立即执行，就排队；否则就开始跑。这里的目的是，优先避免多个执行流同时改写同一会话状态。上下文组装：真正运行前，Gateway会把当前的输入和相关的上下文组合起来。开跑！：进入了模型推理和工具执行步骤，也就是实际开始运行了。流式返回结果：Gateway会将结果以流式传输的形式，传递给原渠道。这个过程中，消息可以有很多，所以，Gateway也会对消息进行排队处理、并发处理。总而言之，这一步做的事情，就是对信息进行验证、整理并执行。运行层：Multi-agent Routing与Agent运行层Agent运行层这里，就比较直观了，我们可以通过观察日志窥见这里运行的细节，下面的日志我做了很大的简化，去除了一些无关痛痒的信息，比如时间戳等：{"type":"message","message":{"role":"user","content":[{"type":"text","text":"Sender (untrusted metadata):\n```json\n{\n \"label\": \"openclaw-tui\",\n \"id\": \"openclaw-tui\",\n \"name\": \"openclaw-tui\",\n \"username\": \"openclaw-tui\"\n}\n```\n\n[Mon 2026-03-30 13:48 GMT+8] Can you create a directory named boo in my home dir? Create a hello world program in it."}]}} {"type":"message","message":{"role":"assistant","content":[{"type":"toolCall","id":"functions.exec:0","name":"exec","arguments":{"command":"mkdir -p ~/boo && cat > ~/boo/hello.py << 'EOF'\n#!/usr/bin/env python3\nprint(\"Hello, World!\")\nEOF\nchmod +x ~/boo/hello.py","ask":"on-miss"}}],"api":"openai-completions","provider":"custom-dashscope-aliyuncs-com","model":"kimi-k2.5","stopReason":"toolUse",}} {"type":"message","message":{"role":"toolResult","toolCallId":"functions.exec:0","toolName":"exec","content":[{"type":"text","text":"(no output)"}],"details":{"status":"completed","exitCode"…

【RethinkAI】浅思Agent
type: item
date: 2026-02-19T13:14:00+00:00
description: 引子最近的俱乐部群，很多时间都在讨论Coding Agent的相关技术。这让我很诧异，在我的印象中，Agent是一个Reinforcement Learning的terminology。RL的主体是一个Agent，Agent通过与环境互动，从而获得奖惩，优化自身的参数，达到最优的表现。但是，在讨论中我发现，如今这个术语发生了很巧妙的变化。本文就来记录一下，我认知中的术语迁移缘由，和我对AI Agent的看法。术语迁移：什么是AI Agent？所谓AI Agent，就是一个基于大模型的，能够自主规划、调用工具、完成复杂任务的智能体。AI Agent这个词，需要拆成两个词来看——AI和Agent。在现在的语境下，AI基本上等同于自然语言处理领域(NLP)的大语言模型(LLM)。在ChatGPT刚出现那一阵，很多人就把这类技术叫做AI了。实际上，大模型是，且仅是这样一个东西——接收一个不定长的文本，输出一个不定长的文本，仅此而已。而Agent——正如同我们在引子里所说的那样——是一个来自于强化学习领域的术语。强化学习的部分理论可以参见我的博文【AI】强化学习常用算法小记（上）。强化学习和一般的学习方法不同，聚焦于「模型与环境之间的交互」上，从而获得「奖惩」，作为学习依据。而在AI Agent这个词中，剥夺去了「奖惩」这一部分，更强调「模型本身能够与环境进行交互」，能够使用外部工具，能够接收一个模糊的需求，输出一个详细的解决方案来。工作原理：AI Agent其内如何？这个东西究竟是怎么工作的呢？一个AI Agent需要本地和云端两个部分，本地需要部署相应的框架，比如OpenClaw等，而云端则需要购买AI使用权限。举例来说，假设我本地有一个AI Agent，接入了硅基流动的DeepSeek V3模型，然后，在session中输入：Create a file named a.txt for me然后，它就在本地创建了一个名为a.txt的文件。这里我们发现，AI Agent和AI不同，它的输出不仅仅是「不定长的文本」，而是真真切切在本地做了一些操作。那么这是怎么做到的呢？还是以OpenClaw为例，我们查找session历史可以发现：{"type":"thinking","thinking":"The user wants me to create a file named \"a.txt\". This is a straightforward request. I should use the write tool to create this file.\n\nSince they didn't specify any content, I'll create an empty file or add a simple placeholder. Let me just create it with empty content or a minimal placeholder.\n\nI'll create the file in the workspace directory: XXX\n\nAccording to instructions, default behavior is to just do routine tasks without narrating. This is a simple file creation, so I should just do it.","thinkingSignature":"reasoning_content"}, ... {"type":"thinking","thinking":"PowerShell doesn't use echo. > for creating files. Let me try the PowerShell way.","thinkingSignature":"reasoning_content"},{"type":"toolCall","id":"","name":"exec","arguments":{"command":"Out-File -FilePath a.txt"}} ... {"type":"thinking","thinking":"Good, that should have created an empty file. Let me verify it exists.","thinkingSignature":"reasoning_content"}这里我们可以窥探到全貌，工作流程是：需求输入-云端模型处理-返回内容和指令-执行指令-根据指令结果判断正误。AI还是那个AI，那个接收不定长文本，输出不定长文本的AI，只是OpenClaw…

【RethinkAI】RAG与知识编辑
type: item
date: 2025-11-03T04:54:38+00:00
description: 引子大模型的幻觉问题一直是AI领域的痛点之一，如何去解决这个问题？两个比较简单的方法，分别是“开卷考试”和“开颅手术”。本篇就记录一些自己的学习笔记和思考。RAG所谓RAG，就是Retrieve and Generate的简称，即检索后生成技术。其实RAG本身就是一种类似于搜索引擎的技术，既然大语言模型的上下文长度是有限的，那么我们就把一些外部的知识库进行切分，切分成一系列知识，然后让大模型针对问题有需取用即可。这就是一种开卷考试的方法。在前几年，RAG也是研究的热点之一。不过这并不是今天讨论的重点。在我看来，RAG就是这么简单且有效的技术之一。知识编辑如果说RAG是开卷考试，那么知识编辑就是一种开颅手术的方法。知识编辑的问题建模实际上是一个极其简单的公式，假设所有的知识可以建模为三元组的形式，如$(h,r,t)$，那么知识编辑的目的是将某些知识进行更新，更新为$(h,r,t’)$。知识编辑的方法有三大类：外部记忆法、全局优化法以及局部修改法。其中，外部记忆法是指保持模型原有的权重不变，通过外部的结构将知识与大模型结合起来。这种外部的结构可以是一些额外的模型，比如FFN，也可以是外部的知识库。全局优化法是指，对于模型的全部参数进行一次整体的更新，从而达到更新知识的目的。但是，和SFT和全参数微调不同的是，这类方法的训练过程通常被妥善设计，从而不会影响原有的知识。局部修改法的第一步是识别出某一特定知识所在的神经元/权重，然后通过一些方法进行修改。从而达到修改知识的目的。和RAG不同的是，知识编辑不仅仅包括RAG的类似方法（不完全一致），也包括对模型内部进行处理的一些方法。对于外部记忆法来说，比较有代表性的例子是MeLLo，它的工作流程很好地体现了外部记忆法的模式，即通过冻结大语言模型的参数，一切的操作都在外部进行。这个方法主要是为了解决多跳QA问题的。工作流程如下：将一个多跳的QA问题转化为若干子问题。让大语言模型回答这些子问题，提供一个暂时的答案。通过外部知识库检索相关的知识，确定这些答案的正确性。如果答案与外部的知识有冲突，就重新回答。这类方法在我看来，和RAG并没有特别大的差异，唯一的不同之处在于，这些方法通常是选择性地调用外部知识，而非通常RAG方法的一直调用。对于全局优化法来说，比较有代表性的是RECT，该模型通过正则化，提取出每一次修改最大的参数，进行参数更新。对于局部修改法来说，比较有代表性的方法是MEMITCSK。作者观察大模型每次预测的结果，通过一系列结构获取每次预测的过程中，对结果产生最大影响的权重，随后进行修改。也有一些方法是对模型结构进行修改，比如融合门控单元，通过某一阈值决定走外部记忆模块还是原先记忆模块（WISE）。总结这两个方法都能在一定程度上缓解大语言模型的幻觉问题，但是这两个方法都没有解决这个问题。除此之外，这两个方法也没有办法解释为什么会有幻觉现象。在这之外，这两者的效果依赖于子任务和人工提供知识的匹配程度，在一定程度上也是受限制的。

【RethinkAI】传统序列模型
type: item
date: 2025-10-04T10:48:09+00:00
description: 引子在Transformer架构流行之前，自然语言的处理，尤其是NER（命名实体识别）等任务，通常是利用传统的序列模型处理的，比如隐马尔可夫模型、条件随机场等。实际上，这些模型的假设通常是——模型的未来状态只与当前状态有关。这类模型的发展以HMM（隐马尔可夫模型）为起始，经过MEMM（最大熵马尔可夫模型），到CRF（条件随机场）为止。这些模型在现在已经很少使用了，不过依然有着意义。本篇就来探讨一下HMM和MEMM这两个模型。CRF放在新一期讲。马尔可夫假设假设我们有一系列的随机状态发生的概率$P(X_t=x_k)$，其中$t$表示不同的时刻，$X_t=x_k$表示$t$时刻的随机变量取值为$x_k$，那么马尔可夫假设就是这样一个式子：$$ P(X_t=x_k|X_1=x_{k1}, X_2=x_{k2},\cdots,X_{t-1}=x_{kt-1})=P(X_t=x_k|X_{t-1}=x_{kt-1}) $$用语言解释，就是，下一时刻的随机状态的取值，只与当前时刻有关。基础设定我们还是回到自然语言这个设定上来，很多时候，我们需要对序列做标注任务，比如词性的标注、分词。这次我们就用分词任务来说明。这次就用B和I两类字符做分词。所谓B就是一个词的开始（Beginning），也就是提示要分割的地方，在标注B的字符前面分割，而I就是表示“处在一个词的内部”（Intermediate），也就是这个字符前面无需分割，举个例子，对于“我爱吃薯片”这句话来说，如果这五个字符分别被标注为BBIBI，那么最后的切分方式就是“/我/爱吃/薯片”。HMM：隐马尔可夫模型分词显然是在一句话内生效的，那么我们可以把每个字符设为$C_i$，每个字符的分割方式设为$S_i$，其中，$i$表示在序列的位置（index），那么一个句子的分割方式的概率就是$P(S_1S_2\cdots S_n)$，这很好理解。如果我们认定马尔可夫假设，并且认定每个字符的分割方式与和当前字符有关，而当前字符的概率与前一个字符有关，那么我们由浅入深，先考虑前两个字符分割方式与整个句子的联合概率：$$ P(S_1S_2C_1C_2)=P(C_1)P(S_1|C_1)P(C_2|C_1)P(S_2|C_2) $$前三个字符：$$ P(S_1S_2S_3C_1C_2C_3)=P(S_1|C_1)P(C_2|C_1)P(S_2|C_2)P(C_3|C_2)P(S_3|C_3) $$不难看出，每多考虑一个字符，就会增加两项——$P(C_{t}|C_{t-1})$和$P(S_t|C_t)$，这两项分别是在前一个字符的影响下，当前字符的概率；以及在当前字符的影响下，当前字符的分割方式概率。那么我们就可以建模成这样一个网络：其中，我们称字符之间的概率为转移概率，而字符到切割方式的概率为发射概率。当然，之所以HMM是“隐”马尔可夫模型，就表明，实际中，$C$不一定就是字符，可能是一些隐信息。总而言之，HMM依靠两个极其强的假设：当前字符的出现只与前一个字符有关。当前字符的分割方式只与当前字符有关。MEMM：最大熵马尔可夫模型通过HMM不难看出，“当前字符的分割方式只与当前字符有关“这一假设未免过于强了，举例来说，”我是南京市长“和”这是南京市长江大桥“两句中，”长“字的切割方式是与上下文有关联的，所以，一个字符的分割方式不仅与当前字符有关，也与之前的字符或者标注方式有关。MEMM就是为了解决这个问题的，MEMM对条件概率进行建模：$$ P(S_1S_2\cdots S_n|C_1C_2\cdots C_n)=\prod P(S_t|S_{t-1},C_t) $$如式子所示，概率的计算考虑到了序列的前一个元素的分割方式对于当前字符的分割方式影响，这个模型的结构实际上是这样的：当然，到这里可能会有一个疑问：为什么要求”当前字符的概率“而非”当前字符生成的概率“？我们往下看。累乘的每一项的计算方法是：$$ P(S_t|S_{t-1},C_t)=\frac{\exp{\sum_{i} \lambda_if_i(C_t,S_t)}}{Z(S_{t-1}, C_{t})} $$其中，$f_i$是自设置的特征函数，数量可以自行确定，而$\lambda_i$则是参数，可以理解为权重。分母则是一个归一化的因子，因为概率的取值为$[0,1]$，所以需要一个归一化项，确保和为1。这个公式是来源于最大熵原理的，所以该模型也叫做MEMM。

【RethinkAI】分词与词嵌入技术
type: item
date: 2025-08-17T07:10:03+00:00
description: 前言计算机没有办法直接理解、存储自然语言。在AI之外的领域，字符串操作通常是以各种字符编码来进行运算的，比如ASCII码、UTF-8编码等等。在AI中，我们也需要一种利用计算机技术，将自然语言转化为计算机能够理解的表示的方法。通常，我们会将自然语言进行两步操作——分词和词嵌入。其中，分词是指将一个句子分成若干基本单元，词嵌入则是将分割到的基本单元转化为向量。分词所谓分词，就是将句子变成若干基本单元，用作输入的句子处理以及输出。分词算法的要求是：高效。算法不能太复杂。可拓展性。要很好地兼容没有在训练数据中出现的词。我们首先从英语分词入手，看看我们究竟要解决什么问题。想象一下，我们要对语言进行建模，就要处理一个篇章非常大的文段。我们知道，英语是依靠空格进行单词间分隔的，那么我们依靠空格对单词进行分割如何？这是一种极其简单、易于实现的分割方法。但是，这个分词方法有很强的扩展性吗？并没有，除此之外，这个方法有个很致命的问题——同一个单词有很多种表示。比如这一句话中——“The show shows some significant things, showing the show shows lots of things.”——出现了show、shows、showing三种表示，但是它们都是由同一个词变化而来的。这种分词方法会极大扩张词表的大小，造成单词表示的膨胀。除此之外，这种分词方法会导致模型永远没法生成没见过的单词，用专业术语来说，就是没法很好处理OOV（Out-Of-Vocabulary）的情况。针对一词多表示的问题，研究者们提出了一种新的分词算法——Subword分词。这个算法对于一个单词，会将之拆分为前缀、子词和后缀，比如show、shows、showing这三个单词在词表中，会变成show、-s、-ing三个基本单元。这个分词方法导师很好解决了一次多表示的问题，但是，对于OOV问题还是束手无策的。那么，有人就会说了，为什么一定要用分“词”呢？像计算机一样，直接用字符表示是否可以？这便是character-level tokenization。这个方法能够很好地限制词表的大小，也能够解决OOV问题——没有一个单词不是字母组成的。一切都好，对吧？实际上，在一段时间之前，我也觉得这个方法非常好。不过，我们想一下，Transformer模型的输出是逐字符输出的，根据前面的内容预测后面的内容。如果用字符表示，那么一个文段就会占据过多的空间，没有办法很好地生成长文段。网上很多资料说，character-level tokenization没办法学习到字之间的信息——全是狗屁！只要输入够多，字之间的关系很好学习。我们亟需一个折衷的分词方案。这个方案就是BPE——Byte-Pair Encoding。这个算法的流程是这样的：在每个单词后面增加一个分隔符。用Character-level Tokenization构建一个初始词表。计算频率最高的字符模式，将新字符融入词表。重复3，直到下一最高频字符模式频率为1。举个例子，我们的语料是——“The thread shows the meaning.”，首先我们将之处理为“The/thread/shows/the/meaning”。首先构建起最基础的词表：词表频率t3h4e4r1a2d1s2o1w1m1n2i1g1随后寻找到最高频的字符模式：词表频率t3-3=0h4-3=1e4r1a2d1s2o1w1m1n2i1g1th3然后：词表频率h1e4-2-2=0r1a2-2=0d1s2o1w1m1n2i1g1th3-2=1the2ea2这便是我们的词表了：词表频率h1r1d1s2o1w1m1n2i1g1th1the2ea2这个方法在char-level和word-level中找到了一个平衡点，只要输入语料够大，就能够学到语言的基础模式，不仅能够规避OOV问题，也能够让词表不至于过于庞大。当然，这个分词方法对于英语非常有效，但是对于其他语言则无能为力。于是，后来出现了一个新的分词方法——BBPE（Byte-level BPE），通过在文本的Unicode编码基础上进行BPE，支持了几乎全部的语言以及Emoji。词嵌入通过分词，我们将语料转化为了一些基础单元，现在我们要做的是，将这些基本单元转化为向量形式，这些向量就被叫做词向量。一个很显然的嵌入方法是one-hot编码。比如词表中有3个词：我、爱、你。那么这三个词的向量表示为：$(1,0,0),(0,1,0),(0,0,1)$。one-hot就是指向量中只有1位是1，其它都是0。这种编码的好处是简单、方便，但是其问题也显而易见——这种编码会占用大量的空间。比如，如果一个词表很大，那么处理它的模型的参数也就很多，我们需要一种更稠密的嵌入方式。当然，one…

【Rethink AI】Transformer模型
type: item
date: 2025-08-14T11:52:40+00:00
description: 前言作为NLP领域的最重要模型之一，Transformer有着强大的性能。为什么要引入TransformerTransformer这一模型与称呼，来源于2017年的一篇论文——《Attention is All You Need》。在2017年左右，NLP领域的代表性模型无非是RNN系列和CNN系列。RNN系列的模型的计算过程是以迭代整个序列为基础的，想要在其基础上进行并行计算是很困难的，处理长序列所需的时间很多。除此之外，RNN也具有梯度爆炸/梯度消失现象，难以捕捉长距离的词元依赖关系。CNN系列的模型固然可以很好捕捉长序列信息，但其有一个十分致命的缺点，即其卷积核的大小是固定的，难以自适应捕捉不同序列的语义距离。对于简单句来说，卷积核的大小可能太大了——而相同大小的卷积核，对于长难句来说，又太小。Transformer架构就解决了这两类模型的问题——通过注意力机制解决了难以并行计算、梯度爆炸/消失的问题，通过注意力机制中的权重设置，能够自适应捕捉不同序列的语义距离。简单来说，就是将RNN的迭代过程，变成了矩阵的乘法运算；将CNN的卷积过程，化作了注意力机制模块中，矩阵的参数。除此之外，Transformer也具有架构统一、易于拓展的优点。Transformer的首要任务其实和RNN和CNN无异，那就是学习到语言的特征。注意力机制广义的注意力机制，其本质就是矩阵的乘法，其通用结构是：$$ \text{Score}([x_1,x_2,\cdots,x_n])=\mathbf{W}[x_1,x_2,\cdots,x_n] $$其中，$\mathbf{W}$是注意力矩阵，是一个可学习的参数。通过数据集训练这个矩阵，从而得到向量中每一个元素的重要性。Transformer的结构Transformer的结构如下图所示：嵌入模块首先，这里的Input Embedding和Output Embedding都是“词嵌入”的技术，即将自然语言转化为向量的过程。这一步是NLP最基础的技术之一，因为计算机没有办法直接理解自然语言，需要将之转化为数字表示才可理解。有关这个技术，我会在后续的文章中讨论。编码器我们从左往右看，左边的灰色框框被称作“编码器”（Encoder）。其目的是将自然语言的向量表示，转化为模型内部的表示。这个模块接收一个输入，吐出一个输出。实际上，第一个编码器模块的输入就是自然语言的向量。当向量进入编码器后，首先经过一个多头注意力机制模块（Multi-Head Attention Block）。多头注意力机制模块，实际上就是若干个注意力机制模块进行拼接。每一个注意力机制模块都应用下面的公式进行计算：$$ \text{Attention}(\mathbf{Q},\mathbf{K},\mathbf{V})=\text{Softmax}(\frac{\mathbf{Q}\mathbf{K}^T}{\sqrt{d_k}})\mathbf{V} $$我们按照每一部分字母来解释。首先，$\mathbf{Q},\mathbf{K},\mathbf{V}$是由输入$\mathbf{I}$经过运算得到的矩阵：$$ \mathbf{Q}=\mathbf{W}_Q \mathbf{I},\mathbf{Q}\in\mathbb{R}^{n\times d_k} $$$$ \mathbf{K}=\mathbf{W}_K \mathbf{I},\mathbf{K}\in\mathbb{R}^{m\times d_k} $$$$ \mathbf{V}=\mathbf{W}_V \mathbf{I},\mathbf{V}\in\mathbb{R}^{m\times d_v} $$这三个字母分别代表query、key和value，是作者借用的搜索领域的名词。我们不妨考虑一下搜索引擎是怎么运作的，在搜索引擎中输入一个关键字（query），然后蹦出来一堆页面的标题（key），找到符合心意的标题，点进去，就得到了相应的内容（value）。回到我们的任务上，我们的任务是对语言进行建模，给定一个自然语言的句子，我们不妨让key和这个句子一致，那么我们的内容也需要和key一致。输入是什么，输出就是什么，这样才能捕捉到语言的内部规律——当捕捉到语言内部规律时，给定一个自然语言句子，其输出必须要和源端一致，否则就是偏差了。输入所乘的矩阵，实际上是一个投影操作，将输入分别投影到query、key和value空间。我们观察Softmax函数的分母，表面上看是一个矩阵相乘，实际上，这是一个求向量间相似度的操作，还记得余弦相似度怎么算吗：$$ \text{CosSim}(\vec{a},\vec{b})=\frac{ab^T}{||a||||b||} $$分母其实也是这个含义，根据矩…

【RethinkAI】循环神经网络
type: item
date: 2025-08-08T15:28:39+00:00
description: 前言虽说我是搞NLP的，但是老实说，这些基础的Seq2Seq模型我也没有仔细思考过。序列到序列模型引入自然语言是以序列表示的。和普通的点到点任务（比如加法器）不同，自然语言的序列，每个元素之间是有关系的。什么意思？比如这句话：“我是一个学生”，其中“我”和“学生”显然是相关的，“是”表达了一个关系，表达的是“我”和“学生”之间的关系。为了处理这种元素彼此之间有依赖性的格式，相比于使用单纯的神经元层拼接，不得不想出一种新的模型架构。这种架构需要满足几个条件：能够捕捉到序列之间的关系。能够接受变长输入与输出。第一点很好理解，第二点需要解释一下。在语言处理领域，这一点无比重要，因为语言是变长的，所以需要能够接受变长输入。除此之外，输出也要满足可变长性。那么为什么需要一个“新的”模型架构呢？因为传统的模型拼接，往往每一层的参数是确定的，输出层的参数也是确定的，这并不利于处理序列信息。这便是序列到序列任务。直觉的一闪：RNN对于字符串的处理，容易想到的一个算法便是“滑窗算法”，这个算法常用于寻找字符串的重复元素。没有错！序列是变长的，但是，我们可以将序列拆成等长的小块，然后进行分块处理。处理完每一个小块后，输出两个东西，一个是呈现给用户的、作为结果的输出；另一个是传递给下一时间步，处理下一个小块的信息。那么，既然输入是定长的，就可以依靠传统的模型进行处理啦！这便是最基本的RNN（循环神经网络）的思想，其架构图如下：RNN块内部的结构为：值得注意的是，RNN是应用同一个RNN模块对序列进行处理，从而获得整个序列的信息，这表明，只有一套参数。RNN Block的公式可以表达为：$$ \text{Process}(\mathbf{I},\mathbf{P})=f(\mathbf{W}_1\mathbf{I}+\mathbf{W}_2\mathbf{P}+\mathbf{B}_1) $$$$ \text{Output}(\mathbf{I},\mathbf{P})=g(\mathbf{W}_3\text{Process}(\mathbf{I}, \mathbf{P})+\mathbf{B}_2) $$那么，我们来看看RNN的梯度该如何计算，以Process Layer的权重举例子：$$ \frac{\partial L}{\partial \mathbf{W}_2}=\frac{\partial L}{\partial f}\frac{\partial f}{\partial \mathbf{W}_2} $$这个时候，需要注意：$$ \frac{\partial f}{\partial \mathbf{W}_2} = \frac{\partial f}{\partial \mathbf{W}_2\mathbf{P}}\frac{\partial \mathbf{W}_2\mathbf{P}}{\partial \mathbf{W}_2} $$其中，由于RNN是一个时序模型，所以：$$ \frac{\partial \mathbf{W}_2\mathbf{P}}{\partial \mathbf{W}_2}=\mathbf{P}+\mathbf{W}_2\frac{\partial \mathbf{P}}{\partial \mathbf{W}_2} $$这就完了吗？并没有！因为：$$ \frac{\partial \mathbf{P}}{\partial \mathbf{W}_2} = \frac{\partial \mathbf{P}}{\partial \mathbf{P}_{-1}}\frac{\partial \mathbf{P}_{-1}}{\partial \mathbf{W}_2} $$其中，$\mathbf{P}_{-1}$是指上一个时间步的Process Layer结果。从这个公式可以看出来，梯度计算的结果，和之前所有的状态都有关。如果$\frac{\partial \mathbf{P}}{\partial \mathbf{P}_{-1}}$为一个大于1的数，那么会造成梯度的指数级增加，如果小于1，就会造成指数级减小，因为：$$ \lim_{x\to \infty}1.1^x=\infty $$$$ \lim_{x\to\infty}0.9^x=0 $$这便是RNN的梯度爆炸和梯度消失现象，这两个现象注定了RNN对于长序列的处理是有问题的。不过，我们也可以用一些方法缓解这个问题，我们通常应用：$$ f(x)=\tanh(x) $$假设发生了梯度爆炸，那么：$$ \frac{\partial \text{Process}}{\partial \text{Process}_{-1}} = (1-\text{Process}^2)\…

【RethinkAI】神经元与反向传播
type: item
date: 2025-08-08T07:18:26+00:00
description: 前言本篇主要记录激活函数、基本的神经元模型以及反向传播。激活函数的引入与神经元模型在神经网络中，一个神经元象征着这样的运算:$$ \text{BaseNeuron}(\mathbf{I})=\mathbf{WI}+\mathbf{B} $$其中，$\mathbf{I}$代表输入的矩阵（本文只讨论二维情况），$\mathbf{W}$和$\mathbf{B}$是神经网络的两个参数——权重和偏置。当然只依靠$\text{BaseNeuron}$是不够的，因为多层这样的结构和一层无异，比如，两层的计算可以变成：$$ \text{BaseNeuron}_1(\text{BaseNeuron}_2(\mathbf{I}))=\mathbf{W}_1(\mathbf{W}_2\mathbf{I}+\mathbf{B}_2)+\mathbf{B_1} $$根据矩阵乘法的分配律，上式可变成：$$ \text{BaseNeuron}_1(\text{BaseNeuron}_2(\mathbf{I}))=\mathbf{W}_1\mathbf{W}_2\mathbf{I}+\mathbf{W}_1\mathbf{B}_2+\mathbf{B}_1 $$这依然符合$\text{BaseNeuron}$的基本形式，两层的计算，可以通过一个新的层达成目的，新层的权重为$\mathbf{W}_1\mathbf{W}_2$，偏置为$\mathbf{W}_1\mathbf{B}_2+\mathbf{B}_1$即可。这背后的原理是：“线性变换的线性变换依然是一个线性变换”，这一点可以通过线性变换的概念进行证明——因为线性变换是在一个线性空间内做的运算，没有办法“逃出”这个空间。在现实世界中，并非所有的事物都可以用线性变换来解释，比如线性变换没有办法解释异或，也没有办法拟合正余弦等函数。这个时候，我们就需要一个工具，来创造出非线性变换——这便是激活函数。借用这个工具，我们的神经元模型变成了：$$ \text{Neuron}(\mathbf{I})=f(\mathbf{WI}+\mathbf{B}) $$其中，$f(\cdot)$是一个非线性的函数，是我们的激活函数。其作用很简单——向一个线性模型中引入非线性。只要是非线性函数，就可以用作激活函数，只是具体的效果有待考量而已。反向传播模型的层数可以很多，那么，在梯度下降训练过程中，就需要一种方法来计算出每一层的梯度。比如我们的模型有两层：$$ \text{Model}(\mathbf{I})=\text{Neuron}_2\text{Neuron}_1(\mathbf{I})=f_2(\mathbf{W}_2f_1\mathbf{(\mathbf{W}_1\mathbf{I}+\mathbf{B}}_1)+\mathbf{B}_2) $$我们需要求取每一层的梯度，具体来说，是针对于权重和偏置的梯度。当然，只有输出还不够，按照梯度下降的思想，我们需要一个损失函数，求损失函数对每一个参数的导数，亦即$\frac{\partial{L}}{\partial \mathbf{W}_1}$之类。这里，我们要用到的工具是——链式法则。对于$\frac{\partial{L}}{\partial \mathbf{W}_2}$而言，它等于：$$ \frac{\partial{L}}{\partial \mathbf{W}_2}=\frac{\partial{L}}{\partial f_2}\frac{\partial{f_2}}{\partial \mathbf{W}_2} $$所以，我们可以从输出层向输入层逐层求导，因为，前面的层，需要后面层的参数做运算。反向传播就是这么简单的东西——先求出后面一层的全部参数值，然后通过链式法则进行运算。激活函数的几个例子一个好的激活函数需要满足几个条件：形式简单。求导方便。其中，这两点都是因为，我们常用的训练架构，依然是基于梯度下降的。一个形式简单的激活函数，不仅方便理解、分析和编码，也能让计算速度变快；一个求导方便的激活函数，就利于梯度下降。比较常用的激活函数是Sigmoid函数，其公式为：$$ \text{Sigmoid}(x)=\frac{1}{1+e^{-x}} $$易知其导数为：$$ S'(x)=S(x)(1-S(x)) $$其中，$S(x)$即$\text{Sigmoid}(x)$。这个函数的值域为$[0,1]$，常用于二分类任务输出层。因为二分类通常以0、1为标签。对于多分类来说，通常使用Softmax函数，其公式为：$$ \text{Softmax}(\mathbf{X})=\frac{e^{x_k}}{\sum_{i=1}^ne^{x_i}} $$其实，这个函数通常和交叉熵损…