码农戏码

新生代农民工的自我修养

0%

标量

标量就是一个数字

只有大小概念,没有方向的概念。通过一个具体的数值就能表达完整。比如:重量、温度、长度、提及、时间、热量等都数据标量

向量

向量是数学和物理学中的一个基本概念,表示既有大小又有方向的量。

与标量(只有大小没有方向)不同

向量主要有2个维度:大小、方向

在数学里,向量可以用一串数字表示,比如 [3, 4]。这串数字可以代表空间中的一个点或者一个方向

矩阵

矩阵是二维的数字表格

矩阵可以看作是”多个向量的堆叠”:

3×4 的矩阵 = 3 个长度为 4 的行向量
或者 = 4 个长度为 3 的列向量

矩阵乘法

可乘条件‌:设 A 为 m×n 矩阵,B 为 n×p 矩阵,只有 A 的列数 (n) 等于 B 的行数 (n) 才能相乘
结果维度‌:乘积 C=AB 是 m×p 矩阵,保留第一个矩阵的行数和第二个矩阵的列数 。
元素计算‌:C 的第 i 行第 j 列元素等于 A 的第 i 行与 B 的第 j 列对应元素乘积之和。【点积(Dot Product)】

矩阵乘法的维度规则:

1
[A, B] × [B, C] = [A, C]

中间的维度必须相同(都是 B),结果取两边的维度。

3行2列矩阵与2行3列矩阵的相乘

举例:

矩阵加法

只有同行数同列数的矩阵才可以相加,结果也是同样尺寸的矩阵,其中的每个元素都是相加矩阵对应位置的元素之和

矩阵加法的一个例子,运算法则比较简单

Transformer中

标量:学习率、温度参数
向量:一个 token 的 embedding
矩阵:一批 token 的 embedding、权重矩阵

现在玩股票有个坏习惯,每次不是自己看盘复盘得出结论,而是看微博,去看别人的观点做结论

表多了时间就不准

而且关注人多,观点也是乱七八糟,脑子就乱了

每个人都说自己预测得多准确

想着来写个统计数据吧

核心思想:根据发微博的时间,交叉验证一下,后面几天博主所阐述的观点的正确率

只统计了短期的,1日 + 5日 + 10日

所以会有偏差,比如某人说未来一个月都是涨势,但可能前10日都在跌,后20日都在涨,那这个统计就不准确了

创建一个Skill,来实现这个需求

博主预测股市正确率看板(1日 + 5日 + 10日后验)


name: weibo-stock-monitor
description: |
微博股票观点监控技能。从指定微博分组抓取财经博主帖子,AI提取股票观点(做多/做空/中性),并用真实行情数据交叉验证博主观点与市场趋势的一致性。输出 Markdown 日报 + HTML 暗色主题看板。

触发场景:用户要求监控微博财经博主观点、查看某分组股票讨论、做微博观点日报等。
示例输入:「监控微博股票观点」「财经分组日报」「微博博主多空分析」「抓取股票分组帖子」

前一段时间,Java服务端经常崩溃,每天基本需要重启两次

之前出现过内存泄漏,但这次通过grafana查看,各项指标看着也正常,内存没有大幅上升

不过在查看整个宿主机时,发现整台机器内存一直在高位运行

所以依然怀疑是内存因素

措施一:把Java应用配置上JVM参数,-Xmx,限制一下整个应用内存使用,Java部署在Docker里面,Docker没有做内存限制
默认使用的内存似乎是32G,不管在128G内存的宿主机上,还是1024G的宿主机上,都差不多是这个数字

当参加了JVM参数,-Xms25G 时,重启次数会减少,三天重启一次

说明,内存因素的确是存在的,但根本原因是什么?

在服务器上发现了一份文件:hs_err_pid781.log

日志是怎么产生的

hs_err_pid<进程号>.log 是 JVM 的默认安全机制——当 JVM 遇到无法恢复的致命错误时,会在进程崩溃前自动生成此文件,相当于 JVM 的”临终遗言”。

触发条件:

错误类型 是否生成 hs_err log 说明
SIGSEGV / SIGBUS(native 内存越界) ✅ 是 JVM 内部访问了非法内存地址
Native OOM(malloc 失败) ✅ 是 malloc 失败
JVM 内部断言失败 ✅ 是 JVM 自身逻辑检测到不一致
Java 堆 OOM(OutOfMemoryError) ❌ 否 这是可恢复的 Java 异常,应用可 catch
StackOverflowError ❌ 否 可恢复

不生成的情况: 可恢复的 Java 层异常(堆 OOM、StackOverflow)不会触发此文件,因为 JVM 还能继续运行。只有 JVM 自身内部崩溃(native 层无法继续)才会生成。

1
2
3
# There is insufficient memory for the Java Runtime Environment to continue.
# Native memory allocation (malloc) failed to allocate 32744 bytes for ChunkPool::allocate
# Out of Memory Error (arena.cpp:81), pid=1, tid=37

JVM 在执行 BulkRevokeBias(批量撤销偏向锁)操作时,需要通过 Arena 内存池分配 32KB 的原生内存,malloc 调用失败,JVM 无法继续运行,直接崩溃。

日志结构

通过分析,文本的整体结构

1
2
3
4
5
6
7
8
9
10
11
12
13
┌─────────────────────────────────────────────┐
│ 1. 头部报错信息 (~27 行, 0.02%) │ ← 崩溃类型与建议
│ 2. SUMMARY (~7 行, 0.01%) │ ← 命令行/主机/时间
│ 3. THREAD (崩溃线程) (~28 行, 0.02%) │ ← 崩溃线程调用栈
│ 4. PROCESS (线程列表) (~40050行, 36.6%) │ ← 31952个线程详情 ⚠️最大段
│ 5. VM state / Mutex (~5 行, <0.01%) │ ← safepoint状态
│ 6. Heap (堆内存详情) (~3400行, 3.1%) │ ← G1 region逐个列出
│ 7. 运行时事件记录 (~70 行, 0.06%) │ ← 各种事件最近20条
│ 8. Dynamic libraries (~65734行,59.9%) │ ← native库内存映射 ⚠️最大段
│ 9. VM Arguments (~50 行, 0.04%) │ ← JVM启动参数
│ 10. SYSTEM (~180 行, 0.16%) │ ← OS/CPU/内存信息
│ 11. vm_info (~3 行, <0.01%) │ ← JVM版本
└─────────────────────────────────────────────┘

最终可以发现

项目 结论
崩溃类型 Native OOM(malloc 失败),不是 Java 堆 OOM
直接触发 BulkRevokeBias 操作时 Arena 内存分配失败
根本原因 线程泄漏——31952个线程消耗了约31GB原生内存
加剧因素 CompressedOops Zero Based 模式限制了原生内存地址空间
堆状态 使用率仅20.6%,堆完全正常,问题不在堆
系统状态 物理内存仅剩592MB,Swap已用9GB

一句话总结:线程泄漏导致原生内存耗尽,Java堆监控看不到这个问题,最终在偏向锁撤销操作时malloc失败导致JVM崩溃。

在日志的线程列表中,的确发现了大量的线程:

1
2
3
4
5
6
7
0x00007efdc0a00000,0x00007efdc0b00000)]
0x00007effeca46000 JavaThread "pool-3068-thread-3" [_thread_blocked, id=4218, stack(0x00007efdb9400000,0x00007efdb9500000)]
0x00007effeca47800 JavaThread "pool-3068-thread-4" [_thread_blocked, id=4219, stack(0x00007efdbbc00000,0x00007efdbbd00000)]
0x00007effec024800 JavaThread "pool-3068-thread-5" [_thread_blocked, id=4220, stack(0x00007efdb8e00000,0x00007efdb8f00000)]
0x00007effec026000 JavaThread "pool-3068-thread-6" [_thread_blocked, id=4221, stack(0x00007efdc0700000,0x00007efdc0800000)]
0x00007effec027000 JavaThread "pool-3068-thread-7" [_thread_blocked, id=4222, stack(0x00007eff30800000,0x00007eff30900000)]
0x00007effec028800 JavaThread "pool-3068-thread-8" [_thread_blocked, id=4223, stack(0x00007efdb8700000,0x00007efdb8800000)]

但这些线程是哪个线程池创建的,还得去排查代码。

这其实再一次印证了使用线程池规范:

关于线程池命名,阿里巴巴Java开发手册中有一条**【强制】**规约,原文表述如下:

【强制】创建线程或线程池时请指定有意义的线程名称,方便出错时回溯。

手册将这条规约与“禁止用Executors创建线程池”放在同等重要的位置,均为强制要求。这足以说明命名规范在生产环境中的重要性。

遵循规范后,当程序出现问题(如死锁、CPU飙升)时,我们通常会使用jstack命令导出当前线程快照进行分析。一个有明确业务含义的线程名,能让开发者在成百上千条线程堆栈信息中,一眼定位到问题线程所属的业务模块,从而大大缩短故障排查时间。

这些规范都是前人的血泪史!

《从零开始构建智能体》第三章 大语言模型基础-读书笔记

语言模型 (Language Model, LM) 是自然语言处理的核心,其根本任务是计算一个词序列(即一个句子)出现的概率。一个好的语言模型能够告诉我们什么样的句子是通顺的、自然的。

在多智能体系统中,语言模型是智能体理解人类指令、生成回应的基础。


最初的 Transformer 模型是为端到端任务机器翻译而设计的。如图所示,它在宏观上遵循了一个经典的编码器-解码器 (Encoder-Decoder) 架构。

Transformer 是一种在自然语言处理(NLP)领域极具影响力的深度学习模型架构,最初由 Google 团队在 2017 年的论文《Attention Is All You Need》中提出。它彻底改变了 NLP 领域,成为了后来 BERT、GPT 等著名模型的基础。

以下是对图中各个部分的详细解析:

整体结构

该架构主要分为两大部分:

  1. 编码器 (Encoder) :任务是“理解”输入的整个句子。它会读取所有输入词元,最终为每个词元生成一个富含上下文信息的向量表示。
  2. 解码器 (Decoder) :任务是“生成”目标句子。它会参考自己已经生成的前文,并“咨询”编码器的理解结果,来生成下一个词。

这是一个典型的“编码器-解码器”架构,常用于机器翻译等序列到序列(Seq2Seq)的任务。

1. 输入部分 (Input)

  • Inputs (输入):这是模型的原始输入序列(例如一句话中的单词)。
  • Input Embedding (输入嵌入):将离散的单词索引转换为连续的向量表示,以便模型处理。
  • Positional Encoding (位置编码):因为 Transformer 不像循环神经网络(RNN)那样按顺序处理数据,它本身没有“顺序”的概念。位置编码被加到嵌入向量中,用来告诉模型单词在句子中的位置信息。

2. 编码器 (Encoder) - 左侧堆叠部分

编码器由 $N$ 个相同的层堆叠而成(图中的 $N\times$ 表示重复 $N$ 次,原论文中 $N=6$)。每一层包含两个主要子层:

  • 多头注意力机制 (Multi-Head Attention):这是 Transformer 的核心。它允许模型在处理一个词时,“关注”输入序列中的其他词。多头意味着模型会在不同的表示子空间中并行地执行多次注意力机制,从而捕捉更丰富的信息。
  • 前馈神经网络 (Feed Forward):这是一个简单的全连接层,对每个位置的向量进行相同的线性变换和激活函数处理。
  • Add & Norm (残差连接与层归一化):在每个子层周围都有残差连接(箭头绕过子层直接相加),然后进行层归一化。这有助于训练深层网络,防止梯度消失。

3. 解码器 (Decoder) - 右侧堆叠部分

解码器同样由 $N$ 个相同的层堆叠而成。每一层包含三个主要子层:

  • 掩码多头注意力机制 (Masked Multi-Head Attention):这与编码器中的注意力机制类似,但增加了一个“掩码”。这是为了在训练时防止模型在预测当前位置的词时“偷看”后面的词(即未来的信息)。
  • 多头注意力机制 (Multi-Head Attention):这一层接收来自解码器上一层的输出,以及来自编码器最终输出的信息。它让解码器能够关注输入序列中的相关部分(例如在翻译时关注源语言句子中的对应单词)。
  • 前馈神经网络 (Feed Forward):与编码器中的结构相同。
  • 同样,每个子层周围也有 Add & Norm

4. 输出部分 (Output)

  • Linear (线性层):解码器的输出经过一个线性变换(全连接层),将向量映射到一个高维空间,其维度等于词汇表的大小。
  • Softmax:将线性层的输出转换为概率分布。每个值代表对应词汇表中单词被选为下一个输出词的概率。
  • Output Probabilities (输出概率):最终模型预测的下一个词的概率。

总结

这张图清晰地展示了 Transformer 如何利用 自注意力机制 (Self-Attention) 来并行处理数据,从而比传统的 RNN/LSTM 模型训练速度更快,且能更好地捕捉长距离依赖关系。

《从零开始构建智能体》第三章 大语言模型基础-读书笔记

语言模型 (Language Model, LM) 是自然语言处理的核心,其根本任务是计算一个词序列(即一个句子)出现的概率。一个好的语言模型能够告诉我们什么样的句子是通顺的、自然的。

在多智能体系统中,语言模型是智能体理解人类指令、生成回应的基础。

📝 LLM发展阶梯

LLM发展的四个层级,每一层都代表了语言模型发展的一次重要飞跃:

  • 第一级:N-gram 模型

    • 能做什么:建立了统计语言模型的基本范式,计算简单快速,结果可解释。
    • 核心思想:马尔可夫假设。
    • 局限:不懂语义,泛化能力差,且受限于固定窗口。
  • 第二级:前馈神经网络语言模型

    • 能做什么:通过词嵌入,让模型能理解词之间的语义相似性,大大增强了泛化能力。
    • 核心思想:将词映射为连续向量。
    • 局限:上下文窗口仍然是固定的,无法处理更长的历史信息。
  • 第三级:循环神经网络 (RNN/LSTM)

    • 能做什么:通过循环结构,理论上能处理任意长度的序列,拥有了“记忆”。LSTM 进一步缓解了长距离依赖问题。
    • 核心思想:信息在序列中循环传递。
    • 局限:循环计算是串行的,无法并行,训练效率低,且长距离依赖问题依然存在挑战。
  • 第四级:Transformer 与大模型

    • 能做什么:完全基于自注意力机制,实现了高效并行计算,并能直接捕捉序列中任意长距离的依赖关系,成为现代大语言模型的基础。
    • 核心思想:自注意力。
    • 局限:目前主要挑战在于计算成本高昂、推理时的显存占用等。

神经网络语言模型虽然引入了词嵌入解决了泛化问题,但它和 N-gram 模型一样,上下文窗口是固定大小的。为了预测下一个词,它只能看到前 n−1 个词,再早的历史信息就被丢弃了。这显然不符合我们人类理解语言的方式。为了打破固定窗口的限制,循环神经网络 (Recurrent Neural Network, RNN) 应运而生,其核心思想非常直观:为网络增加“记忆”能力。

RNN 的设计引入了一个隐藏状态 (hidden state) 向量,我们可以将其理解为网络的短期记忆。在处理序列的每一步,网络都会读取当前的输入词,并结合它上一刻的记忆(即上一个时间步的隐藏状态),然后生成一个新的记忆(即当前时间步的隐藏状态)传递给下一刻。这个循环往复的过程,使得信息可以在序列中不断向后传递

🧠 RNN 的核心:一个携带“记忆”的处理器

可以把RNN想象成一个有“短期记忆”的处理器。它处理信息的方式不是孤立的,而是会把对之前信息的“记忆”带到下一步的计算中。

它的工作流程就像一个“接力”过程:

  1. 读取当前输入:在每一步 t,它都会读取当前的输入信息 x_t(例如,句子中的第 t 个词)。
  2. 结合历史记忆:它会调出上一步结束时留下的“记忆”,也就是上一个隐藏状态 h_{t-1}
  3. 更新记忆:然后,它通过一个函数(可以理解为“思考”过程),把“当前输入”和“历史记忆”整合起来,形成新的记忆 h_t
  4. 传递与输出:这个新的记忆 h_t 有两个用途:一是作为这一步的“思考结果”输出(如果需要的话),二是被传递到下一步,作为下一步的“历史记忆”。

这个过程用公式简化表示就是:h_t = f( x_t, h_{t-1} )。这个循环机制,正是RNN能处理任意长度序列的关键。

📝 生动例子:理解带“上文”的句子

为了让你更直观地感受RNN的作用,我们用一个具体的句子来模拟它的处理过程。假设我们要让RNN处理下面这句话:

我 喜欢 吃 苹果

  • 处理 :RNN读取“我”。此时它没有历史记忆(或记忆为空),结合“我”和空记忆,形成新的记忆 h1,这个 h1 就编码了“我”这个信息。
  • 处理 喜欢:RNN读取“喜欢”,并调出上一步的 h1(即“我”的信息)。它把“喜欢”和“我”结合起来思考,更新记忆为 h2。此时的 h2 就包含了“我”和“喜欢”的上下文信息。
  • 处理 :RNN读取“吃”,并调出上一步的 h2(即“我 喜欢”的上下文)。它把“吃”和“我 喜欢”结合,更新记忆为 h3。此时 h3 包含了“我 喜欢 吃”的信息。
  • 处理 苹果:RNN读取“苹果”,并调出 h3(即“我 喜欢 吃”的上下文)。它结合后形成 h4,最终 h4 就完整地编码了整个句子“我 喜欢吃 苹果”的语义信息。

你看,通过这种“边读边记”的方式,RNN在处理最后一个词“苹果”时,依然清楚地记得句子开头的“我”,理解了是“我”在“吃”。这正是RNN能处理序列、捕捉上下文的关键。

🔍 与之前模型的对比

把RNN和前两代模型放在一起看,它的优势就更清楚了:

模型 核心机制 对“我 喜欢 吃 苹果”的处理方式 关键局限
N-gram 固定窗口,只看前 N-1 个词 预测“吃”后面的词时,只能看到“喜欢 吃”或“吃”,看不到开头的“我” 无法捕捉长距离依赖
前馈神经网络语言模型 词嵌入 + 固定窗口 比N-gram理解语义,但窗口依然固定,看“吃”时仍然可能漏掉“我”。 无法处理不定长序列
RNN / LSTM 循环连接 + 隐藏状态 通过隐藏状态 h把“我”的信息一步步传到“吃”和“苹果”,实现了对全句的“记忆”。 串行计算,效率较低;简单RNN有梯度问题

⚠️ RNN 的局限性

虽然RNN很巧妙,但它也有明显的短板,这直接催生了Transformer:

  • 串行计算:必须一个一个词地处理,第 t 步没算完,就不能开始 t+1 步。这导致训练无法充分利用GPU进行大规模并行计算,速度很慢。
  • 长期依赖问题:简单RNN在实践中很难记住非常久远的信息(比如几十个词之前的主语),因为信息在循环传递中会“衰减”。

💎 总结

RNN通过引入隐藏状态循环连接,实现了对序列信息的“记忆”和传递,成功突破了前代模型固定窗口的限制,能够处理任意长度的序列。这个设计在自然语言处理领域是里程碑式的。

尽管它因为串行计算和长期依赖问题,在后来被Transformer架构取代了主流地位,但它所蕴含的“让模型拥有记忆”的核心思想,至今仍在深刻影响着现代大语言模型的设计。

孤辰、寡宿也叫做隔角煞,又叫绝情煞,男怕孤辰,女怕寡宿。

在年月柱,与亲戚家人缘份薄弱,独立自主命。

在日柱,夫妻男女缘份淡薄。在时柱,与子孙缘份淡薄。

八字里带孤辰寡宿的人,人缘不是太好,缺少人际交往,兄弟朋友之间的情意也大多薄弱不堪,这也是孤寡入命终须寂的道理。

所以命中有孤辰寡宿的人都不需要太追求社交以及过分接近喧嚣场所为好,往往不得意,养身独处反而更能安然。

基本概念

孤辰与寡宿是一对关联神煞,传统多主孤独、寡居、婚姻不顺等信息。

  • 孤辰偏主”孤立、无伴”,对男性影响更常被提及
  • 寡宿偏主”寡居、失偶”,对女性影响更常被提及
  • 命中同时见孤辰与寡宿时,传统认为孤独感更重

现代解读不宜机械断孤寡,应结合十神组合、婚姻宫状态、桃花等综合判断。

取法口诀

寅卯辰人孤巳丑,巳午未人孤申辰,申酉戌人孤亥未,亥子丑人孤寅戌

口诀解读:

  • “孤X”指孤辰在X,”丑/辰/未/戌”指寡宿

地支对照表

季节分组(非三合局),以年支或日支为基准:

地支分组(季节) 孤辰 寡宿
寅卯辰(春)
巳午未(夏)
申酉戌(秋)
亥子丑(冬)

推导规律:

  • 孤辰 = 下一季节的第一个地支
  • 寡宿 = 上一季节的最后一个地支

记忆

方法一

地支三会划分

男怕孤辰,女怕寡宿
从十二长生位讲,孤辰是病地,寡宿是冠带

男人就是最讨厌-无病呻吟
女人就是最讨厌-情关(冠带之年)

方法二

孤辰、寡宿也叫做隔角煞

正是三会隔角的位置:

寅申巳亥四长生(病地) - 孤辰
辰戌丑未四墓地 (冠带)- 寡宿

《从零开始构建智能体》第三章 大语言模型基础-读书笔记

语言模型 (Language Model, LM) 是自然语言处理的核心,其根本任务是计算一个词序列(即一个句子)出现的概率。一个好的语言模型能够告诉我们什么样的句子是通顺的、自然的。

在多智能体系统中,语言模型是智能体理解人类指令、生成回应的基础。

📝 LLM发展阶梯

LLM发展的四个层级,每一层都代表了语言模型发展的一次重要飞跃:

  • 第一级:N-gram 模型

    • 能做什么:建立了统计语言模型的基本范式,计算简单快速,结果可解释。
    • 核心思想:马尔可夫假设。
    • 局限:不懂语义,泛化能力差,且受限于固定窗口。
  • 第二级:前馈神经网络语言模型

    • 能做什么:通过词嵌入,让模型能理解词之间的语义相似性,大大增强了泛化能力。
    • 核心思想:将词映射为连续向量。
    • 局限:上下文窗口仍然是固定的,无法处理更长的历史信息。
  • 第三级:循环神经网络 (RNN/LSTM)

    • 能做什么:通过循环结构,理论上能处理任意长度的序列,拥有了“记忆”。LSTM 进一步缓解了长距离依赖问题。
    • 核心思想:信息在序列中循环传递。
    • 局限:循环计算是串行的,无法并行,训练效率低,且长距离依赖问题依然存在挑战。
  • 第四级:Transformer 与大模型

    • 能做什么:完全基于自注意力机制,实现了高效并行计算,并能直接捕捉序列中任意长距离的依赖关系,成为现代大语言模型的基础。
    • 核心思想:自注意力。
    • 局限:目前主要挑战在于计算成本高昂、推理时的显存占用等。

N-gram 模型的根本缺陷在于它将词视为孤立、离散的符号。
为了克服这个问题,研究者们转向了神经网络,并提出了一种思想:用连续的向量来表示词

2003年,Bengio 等人提出的前馈神经网络语言模型 (Feedforward Neural Network Language Model) 是这一领域的里程碑。

其核心思想可以分为两步:

  1. 构建一个语义空间:创建一个高维的连续向量空间,然后将词汇表中的每个词都映射为该空间中的一个点。这个点(即向量)就被称为词嵌入 (Word Embedding) 或词向量。在这个空间里,语义上相近的词,它们对应的向量在空间中的位置也相近。例如,agent 和 robot 的向量会靠得很近,而 agent 和 apple 的向量会离得很远。
  2. 学习从上下文到下一个词的映射:利用神经网络的强大拟合能力,来学习一个函数。这个函数的输入是前 n−1 个词的词向量,输出是词汇表中每个词在当前上下文后出现的概率分布。

在这个架构中,词嵌入是在模型训练过程中自动学习得到的。

模型为了完成“预测下一个词”这个任务,会不断调整每个词的向量位置,最终使这些向量能够蕴含丰富的语义信息。一旦我们将词转换成了向量,我们就可以用数学工具来度量它们之间的关系。

最常用的方法是余弦相似度 (Cosine Similarity) ,它通过计算两个向量夹角的余弦值来衡量它们的相似性。

  • 如果两个向量方向完全相同,夹角为0°,余弦值为1,表示完全相关。
  • 如果两个向量方向正交,夹角为90°,余弦值为0,表示毫无关系。
  • 如果两个向量方向完全相反,夹角为180°,余弦值为-1,表示完全负相关。

通过这种方式,词向量不仅能捕捉到“同义词”这类简单的关系,还能捕捉到更复杂的类比关系。

一个著名的例子展示了词向量捕捉到的语义关系: vector('King') - vector('Man') + vector('Woman') 这个向量运算的结果,在向量空间中与 vector('Queen') 的位置惊人地接近。

这好比在进行语义的平移:我们从“国王”这个点出发,减去“男性”的向量,再加上“女性”的向量,最终就抵达了“女王”的位置。这证明了词嵌入能够学习到“性别”、“皇室”这类抽象概念。

神经网络语言模型通过词嵌入,成功解决了 N-gram 模型的泛化能力差的问题

然而,它仍然有一个类似 N-gram 的限制:上下文窗口是固定的。

它只能考虑固定数量的前文,这为能处理任意长序列的循环神经网络埋下了伏笔。

📊 核心特点对比

特点 N-gram 模型 前馈神经网络语言模型
核心机制 基于计数和马尔可夫假设 基于神经网络词嵌入
词表示 离散符号,无语义关系 连续向量,语义相近则向量相近
泛化能力 ,没见过的词对直接判零 ,可迁移到相似词语
上下文 固定窗口 固定窗口
主要贡献 奠基 引入词嵌入,解决语义泛化问题

🔗 演进链中的位置:承上启下

理解这个模型在演进史中的位置,能帮你更清晰地把握脉络:

  • 相比N-gram:最大的进步是解决了语义鸿沟。N-gram看到“猫 吃 三文鱼”会不知所措,因为没数到过“吃 三文鱼”;但前馈神经网络语言模型从“猫 吃 鱼”里学过“吃”和“鱼”的向量关系,能类推到“三文鱼”。
  • 它的局限:和N-gram一样,它的上下文窗口仍然是固定的(比如只能看前面3个词)。更早的信息会被截断,无法利用。这个“记不远”的局限,直接催生了后续能处理任意长度序列的循环神经网络(RNN)

💎 总结

“前馈神经网络语言模型”的核心贡献,可以总结为三点:

  1. 开创了“词嵌入”:让计算机能用向量表示词语的语义,这是现代NLP的基石。
  2. 解决了核心痛点:通过词嵌入,有效克服了N-gram模型泛化能力差的致命缺陷。
  3. 指明了演进方向:它暴露了“固定上下文窗口”的瓶颈,为RNN等能处理长距离依赖的模型铺平了道路。

可以说,它是从“统计语言模型”迈向“现代大语言模型”的第一块重要基石。理解它,能帮你更深刻地体会到“词向量”这项基础技术的革命性意义。

《从零开始构建智能体》第三章 大语言模型基础-读书笔记

语言模型 (Language Model, LM) 是自然语言处理的核心,其根本任务是计算一个词序列(即一个句子)出现的概率。一个好的语言模型能够告诉我们什么样的句子是通顺的、自然的。

在多智能体系统中,语言模型是智能体理解人类指令、生成回应的基础。

在深度学习兴起之前,统计方法是语言模型的主流

其核心思想是,一个句子出现的概率,等于该句子中每个词出现的条件概率的连乘。

对于一个由词 w1,w2,⋯,wm 构成的句子 S,其概率 P(S) 可以表示为:

P(S)=P(w1,w2,…,wm)=P(w1)⋅P(w2∣w1)⋅P(w3∣w1,w2)⋯P(wm∣w1,…,wm−1)

这个公式被称为概率的链式法则

然而,直接计算这个公式几乎是不可能的,

因为像 P(wm∣w1,⋯,wm−1) 这样的条件概率太难从语料库中估计了,

词序列 w1,⋯,wm−1 可能从未在训练数据中出现过

这个公式的意义在于,它将一个极其复杂的问题(一个句子有无数种可能),变成了一个简单、可计算的重复问题(下一个词是什么?)。

公式说明

P(w_2|w_1) 是概率论和自然语言处理中一个非常核心的符号。它表示条件概率

你可以把它读作:“在给定第一个词是 w_1 的条件下,第二个词是 w_2 的概率”

用最直白的方式来解释:

想象你在和朋友玩一个“猜下一个词”的游戏。朋友已经说了第一个词“喜欢”,现在要你来猜下一个词是什么。

  • P(吃 | 喜欢) 的意思是:在朋友说了“喜欢”之后,你猜下一个词是“吃”的概率有多大?

    • 如果平时大家常说“喜欢 吃苹果”、“喜欢 吃披萨”,那你猜“吃”的概率就会很高,比如 0.4。
  • P(游泳 | 喜欢) 的意思是:在“喜欢”之后,你猜下一个词是“游泳”的概率有多大?

    • 大家也常说“喜欢 游泳”、“喜欢 跑步”,那这个概率可能也不错,比如 0.3。
  • P(桌子 | 喜欢) 的意思是:在“喜欢”之后,你猜下一个词是“桌子”的概率有多大?

    • 我们几乎不说“喜欢 桌子”,那你猜“桌子”的概率就会非常非常低,比如 0.0001。

这个概率值,完全取决于我们从大量的文本(语料库)中统计出来的“习惯”。



句子:“今天 天气 真好”

根据链式法则,它的概率可以拆解为:

  1. P(今天): “今天”这个词作为句子开头的概率。

  2. P(天气 | 今天): 在已经看到“今天”的情况下,下一个词是“天气”的概率。

  3. P(真好 | 今天, 天气): 在已经看到“今天 天气”的情况下,下一个词是“真好”的概率。

最后,把这个概率乘起来:
P(今天 天气 真好) = P(今天) × P(天气|今天) × P(真好|今天, 天气)

数据稀疏:这是最致命的问题。

由于语言组合无穷,任何合理的词序列(如“吃 耳机”)都可能从未在语料库中出现过,导致其概率为0。

虽然可用平滑技术打个“补丁”,但无法根治。

理论上,要准确计算 P(学习 | 我, 喜欢, 在, 图书馆),我们需要在语料库中看到无数次“我 喜欢 在 图书馆 学习”这个5词序列。
但现实是,即便语料库有百亿词,绝大多数长序列也一次都不会出现。
那么 Count(我, 喜欢, 在, 图书馆, 学习) 就等于0,导致整个句子的概率被判为0。
这显然不合理。

理论上公式很完美,但现实中,词序列 (w₁, ..., wₘ₋₁) 的组合是天文数字,几乎不可能出现完全一样的。所以,直接计算这个概率是不可能的。

马尔可夫假设

为了解决这个问题,研究者们提出了马尔可夫假设,简化成 N-gram 模型

“一个词出现的概率,只取决于它前面的 N-1 个词,而不是整个历史。”

  • Unigram (1-gram): P(wₘ),忽略所有历史,词与词之间独立。这几乎不能称为一个“句子”。

  • Bigram (2-gram): P(wₘ | wₘ₋₁),只考虑前一个词。例如,P(真好 | 天气)

  • Trigram (3-gram): P(wₘ | wₘ₋₂, wₘ₋₁),只考虑前两个词。

现代的大语言模型(如GPT系列)可以看作是一个超级复杂、规模巨大的N-gram模型,它能高效地估算出非常长距离的上下文条件概率,从而生成流畅、连贯的文本。

基于这个原理,N-gram 模型的优缺点都非常鲜明。

优点:简单、快速、可解释

  1. 原理简单,计算高效:模型的核心就是统计和查表,训练时只需遍历语料库统计词频,运行时通过查表和简单乘法就能算出概率,计算量很小。

  2. 结果完全可解释:模型为什么算出某个概率是透明的。例如,P(吃|喜欢) 的概率高,可以直接追溯到语料库中“喜欢 吃”这对词出现了很多次。这种可解释性在金融、医疗等严谨领域很有价值。

  3. 在小规模、领域明确的场景下效果不错:对于语法规范、词汇有限的特定领域(如客服查询、日志分析),N-gram 模型依然能提供稳定、快速的基础服务,常被用作基线模型。

缺点:无法处理语言的复杂性和创造性

  1. 数据稀疏性:这是最核心的缺陷。即使语料库很大,也总会有合理的 N-gram 序列没出现过。比如,在 Bigram 模型中,没见过的“吃 耳机”概率会直接变成 0,这显然不合理。虽然可以用平滑技术打个“补丁”,给零概率词分一点值,但无法从根本上解决问题。

  2. 泛化能力差:模型完全是“死记硬背”,无法理解语义的相似性。比如,它见过“猫 吃 鱼”,但看到“猫 吃 三文鱼”时,如果没出现过“吃 三文鱼”,就会判断其概率为零。它无法将“鱼”和“三文鱼”联系起来,因为模型不知道这两个词在语义上是相似的。

  3. 捕捉长距离依赖的能力弱:受限于固定的上下文窗口 N,当 N=2 或 3 时,模型无法处理依赖关系更远的句子。

    例子:“我昨天晚上在 Datawhale 的会议上认识了王老师,他**…**”
    句末的代词“他”显然指的是“王老师”,但二者之间隔了很多词。在 Bigram 或 Trigram 模型中,预测“他”后面的动词时,根本看不到“王老师”这个信息,很容易预测错误。

  4. 模型大小随 N 指数增长:N 越大,模型能捕捉的信息越多,但词序列的组合数也会指数级增加。例如,一个含 10万词的词表,Trigram 模型理论上最多需要存储 10^15 个参数,这在实际中是无法接受的。

总的来说,可以把 N-gram 模型的优缺点梳理成下面这张表:

维度 优点 缺点
计算效率 训练与预测速度快,资源消耗低 N 增大时模型参数呈指数级增长
可解释性 决策过程完全透明,易于追溯 -
语义理解 - 泛化能力差,无法理解语义相似性
上下文处理 - 无法捕捉超过 N 的长距离依赖
数据依赖 在小规模、规范领域效果尚可 数据稀疏问题严重,大量合理序列概率为零

正是为了克服这些缺点,后来的研究者才发展出了神经网络语言模型(如RNN、LSTM),直到现在能处理长距离依赖的Transformer(通过自注意力机制,让一个词可以直接和序列中的任何其他词交互),它们通过引入词嵌入来理解语义,极大地提升了模型的泛化能力。

《从零开始构建智能体》第三章 大语言模型基础-读书笔记

语言模型 (Language Model, LM) 是自然语言处理的核心,其根本任务是计算一个词序列(即一个句子)出现的概率。一个好的语言模型能够告诉我们什么样的句子是通顺的、自然的。

在多智能体系统中,语言模型是智能体理解人类指令、生成回应的基础。

下面这张图梳理了从 N-gram 到现代 大语言模型 (LLM) 的核心发展路径,重点突出了每个阶段要解决的关键问题。

timeline
    title 语言模型演进史:核心问题与解决方案
    section 统计语言模型 (1948-2003)
        核心挑战 : 如何计算一个句子出现的概率?
        N-gram模型 (1948) : 核心思想: 马尔可夫假设
(一个词只依赖前N-1个词)

优点: 简单、快速、可解释

❌ 缺陷: 数据稀疏、
泛化能力差、
无法捕捉长距离依赖 平滑技术 (1948-1990s) : 为解决“数据稀疏”打补丁
(如:加一平滑、Katz平滑)

效果: 缓解了零概率问题,
但未触及本质。 section 神经网络语言模型 (2003-2017) 核心挑战 : 如何让模型理解语义并泛化? 词嵌入 + NNLM (2003) : 核心思想: 将词映射为
稠密、连续的向量
(词嵌入 Word Embedding)

✅ 优点: 语义相似的词
在向量空间中也相近
(开始具备泛化能力)

❌ 缺陷: 上下文窗口仍固定 RNN/LSTM (1997-2010s) : 核心思想: 引入循环结构,
让信息在网络中循环传递

✅ 优点: 理论上能处理
任意长度的序列

❌ 缺陷: 无法并行计算,
训练慢,有梯度消失/爆炸问题 section Transformer时代 (2017-至今) 核心挑战 : 如何实现并行计算并
真正捕捉长距离依赖? Transformer (2017) : 核心思想: 完全基于
自注意力机制 (Self-Attention)

✅ 优点: 可并行计算、
直接捕捉长距离依赖、
训练效率大幅提升 Decoder-Only 架构 (2018) : 核心思想: 只保留解码器,
通过“预测下一个词”
进行自回归生成

✅ 优点: 训练目标统一、
结构简单易于扩展、
天然适合生成任务

代表模型: GPT 系列、Llama 系列 能力涌现 (2020s) : 模型规模跨过阈值,
展现出小模型没有的能力
(如: 思维链、指令遵循、
代码生成、上下文学习)

图表解读与核心洞察

这个演进过程,可以看作是一场不断突破瓶颈的“闯关游戏”:

  1. 第一关 (统计模型):用“计数”解决了语言建模的可行性问题,但困在了 “死记硬背” 的层面。
  2. 第二关 (神经网络模型):用 “词嵌入” 赋予了模型理解语义的能力,迈出了从“记忆”到“理解”的关键一步,但计算效率成了新瓶颈。
  3. 第三关 (Transformer):用 “自注意力” 机制彻底推翻了循环结构的束缚,同时解决了 “并行计算”“长距离依赖” 两大难题,为大规模训练扫清了道路。
  4. 终局 (Decoder-Only LLM):在Transformer基础上,“预测下一个词” 这一统一而简洁的训练目标,配合海量数据与算力,最终催生了具有 “涌现能力” 的现代大语言模型。

arthas 为例,探索一下Spring AI 中 MCP Client端通信机制的实现。

Client调用流程

MCP 客户端调用服务器工具的流程如下:

1、建立连接 :与 MCP 服务器搭建通信链路。

2、查询工具 :获取服务器上所有外部工具的数量信息。

3、生成列表 :将查询到的外部工具整理成列表,并融入当前对话场景。

4、调用工具 :通过 Function calling 技术调用所需的外部工具。

sequenceDiagram
    participant Client as MCP客户端 (如AI应用)
    participant Server as MCP服务端

    Note over Client,Server: 1. 发现阶段
    Client->>Server: 请求 resources/list
    Server-->>Client: 返回资源元信息列表
(URI, 名称, 描述, MIME类型等) Note over Client,Server: 2. 读取阶段 Client->>Server: 请求 resources/read (指定URI) Server-->>Client: 返回资源实际内容 Note over Client,Server: 可选:实时更新 Client->>Server: resources/subscribe (订阅特定URI) Server-->>Client: notifications/resources/updated (内容变更时推送)

通信协议

MCP 中 Client 与 Server 间使用 JSON-RPC 2.0 作为通信消息格式。

JSON-RPC 2.0是一种基于JSONJavaScript Object Notation)的远程过程调用(RPC)协议。

它是一种轻量级的、无状态的、跨语言的通信协议,常用于客户端与服务端之间的交互。

JSON-RPC 是 RPC(远程过程调用)的一种具体实现,RPC 是一种通信范式,其核心目标是屏蔽网络细节,使远程调用如同本地调用般简单,并可基于多种底层网络协议(如 TCP/HTTP)实现。常见的 RPC 框架有 gRPC、Dubbo 和 Thrift。

Request

1
2
3
4
5
6
{
"jsonrpc": "2.0",
"method": "方法名",
"params": {"参数名": "值"} | ["值 1", "值 2"], // 对象或数组
"id": "唯一ID" // 可选(通知请求可省略)
}

jsonrpc : 必须为 "2.0"

method: 调用的方法名(字符串)。

params: 参数(可省略),支持对象(命名参数)或数组(位置参数)。

id: 请求标识符。

Response

成功

1
2
3
4
5
{
"jsonrpc": "2.0",
"result": "返回值",
"id": "对应请求 ID"
}

失败

1
2
3
4
5
6
7
8
9
{
"jsonrpc": "2.0",
"error": {
"code": -32601,
"message": "Method not found",
"data": "额外错误信息" // 可选
},
"id": "对应请求 ID" // 若请求无 ID,则为 null
}

Spring AI中相应源码

1
2
3
4
5
6
public record JSONRPCRequest( // @formatter:off  
@JsonProperty("jsonrpc") String jsonrpc,
@JsonProperty("method") String method,
@JsonProperty("id") Object id,
@JsonProperty("params") Object params) implements JSONRPCMessage {
}
1
2
3
4
5
public record JSONRPCResponse( // @formatter:off  
@JsonProperty("jsonrpc") String jsonrpc,
@JsonProperty("id") Object id,
@JsonProperty("result") Object result,
@JsonProperty("error") JSONRPCError error) implements JSONRPCMessage {

🚀 三种通信方式

MCP(模型上下文协议)主要支持 3 种通信方式,每种方式适用于不同的场景:

方式 通信协议 适用场景 优势 限制
1. STDIO 标准输入/输出 本地进程通信 简单、低延迟、无需网络 仅限本机、需管理进程生命周期
2. SSE Server-Sent Events 远程服务器通信 支持跨网络、自动重连 单向流(服务器→客户端)
3. HTTP JSON-RPC over HTTP 远程调用、负载均衡 双向请求-响应、可扩展 需处理网络延迟

建立连接

根据MCP的3 种通信方式,Spring AI 中如何选择?

1
2
3
4
5
6
7
8
9
10
11

<dependency>
<groupId>org.springframework.ai</groupId>
<artifactId>spring-ai-starter-mcp-client</artifactId>
</dependency>


<dependency>
<groupId>org.springframework.ai</groupId>
<artifactId>spring-ai-starter-mcp-client-webflux</artifactId>
</dependency>

创建MCPTransport

StreamableHttpWebFluxTransportAutoConfiguration
创建WebClientStreamableHttpTransport

创建MCPClient

根据配置的MCP Client 类型,创建相应类型的MCPClient

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
/**
* Client types supported by the MCP client.
*/
public enum ClientType {

/**
* Synchronous (McpSyncClient) client
*/
SYNC,

/**
* Asynchronous (McpAsyncClient) client
*/
ASYNC

}

连接建立成功后,会发送一个method为initialize的初始化的消息

1
2
3
Sending message for method initialize

Sending message JSONRPCRequest[jsonrpc=2.0, method=initialize, id=faf05893-0, params=InitializeRequest[protocolVersion=2025-06-18, capabilities=ClientCapabilities[experimental=null, roots=null, sampling=null, elicitation=null], clientInfo=Implementation[name=north-agent - arthas-mcp, title=arthas-mcp, version=1.0.0], meta=null]]

查询工具

建立完连接后,会发送 tools/list 请求

1
Sending message JSONRPCRequest[jsonrpc=2.0, method=tools/list, id=5fc929e0-1, params=PaginatedRequest[cursor=null, meta=null]]

会获取到完整的工具列表

生成列表

在Spring AI体系里面,MCP Client 获取的工具列表,会抽象成 ToolCallback

1
2
3
4
SyncMcpToolCallbackProvider mcpProvider = mcpToolCallbackProvider.getIfAvailable();  
List<ToolCallback> arthasCallbacks = mcpProvider == null
? List.of()
: List.of(mcpProvider.getToolCallbacks());

调用工具

比如让查询一下JVM信息,会调用 jvm 命令,让服务端发送 tools/call 命令

1
Sending message JSONRPCRequest[jsonrpc=2.0, method=tools/call, id=5fc929e0-2, params=CallToolRequest[name=jvm, arguments={}, meta={user_id=user-001, _AGENT_=老}]]