与我共读《Building AI Agents, RAG and Knowledge Graphs》第一篇。
这一篇主要讲LLM的起源,如何用深度学习的方法去分析文本。
引出了embedding向量产生的原因。
如何将文本数据转换为机器能够理解的形式?
计算机不能直接理解"词",它只能处理数值,所以必须把文本编码成向量。 但是文本比起图片来说,转化为机器能够理解的形式更加困难,因为文字表达博大精深,而且具有高度的抽象性。
以下三种经典方式来转化文本数据为向量:
- One-hot encoding
- Beg-of-word
- TF-IDF
首先是One-hot encoding:将每个词映射到一个向量,向量中只有该词的位置为1,其他位置为0。 然后是Beg-of-word:不再只看单个词,而是看一段文本里"每个词出现了几次"。 最后是TF-IDF:让"在当前文本里重要、但在整个语料里不泛滥"的词权重更高。
这些向量计算机能够理解,但它们仍是简单的向量,不能表达词之间的语义关系。 它们都更像"统计表示",不是"语义表示"。
所以它们更多是告诉模型:
- 哪些词出现了
- 出现了多少次
- 哪些词重要
但是它不知道:
- 词之间的语义关系
- 词之间的上下文关系
- 词之间的语法关系
所以,我们还需要其他方法来表达词之间的语义关系。
embedding词向量的来源和作用
word2vec
在开始embedding之前,需要先讲word2vec 。 word2vec是为了解决上一节的问题,one-hot、bag-of-words、TF-IDF都能表示词或文档,但它们很难直接表达"语义接近",提出了一个新的框架。 比如:
king 和 queen car 和 automobile
这些词在稀疏表示里,通常只是不同位置上的不同编号,看不出"接近"。
这个框架的核心目标是:根据单词的上下文来预测该单词的含义。把这些词放进一个连续向量空间里,让"语义相近"变成"向量接近"。
实现的方法里三个关键角色:
-
target word:要学习的中心词,比如句子里当前看到的词。
-
context:它周围的一小圈词,比如前后各两个词。
-
embedding vector:训练完后,每个词对应的一个稠密向量。
简单来说:给定一个中心词 w,看它周围窗口里的词 c,让模型判断:c 是不是 w 的上下文词。
这样子去训练模型就使得语义问题变成一个二分类问题:
-
positive sample正样本:真实出现在上下文里的词
-
negative sample负样本:随机采样出来、不在上下文里的词
这种方式在AI领域也被称为self-supervised learning自监督学习,因为不需要人工标注,模型自己就能判正和负。
最后,我们训练一个模型来区分正样本和负样本,这个模型的参数即为我们后面要讲的嵌入向量embedding。
为什么训练完参数就成了 embedding
这是很多人第一次读时最容易疑惑的地方。 模型训练目标明明是"判断上下文",为什么最后拿到的是"词向量"?
因为模型内部必须给每个词分配一个可训练向量,才能计算它和其他词的关系。 训练过程中,向量不断被调整:
- 经常共现的词,向量会被拉近
- 很少共现或不共现的词,向量会被推远
所以最后得到的不是人工定义的特征,而是模型从大量语料里自己学出来的分布式表示。
这就是 embedding 的本质:
不是人手工规定"这个维度表示动物、那个维度表示颜色",而是模型自己在高维空间里组织出语义结构。
similarity相似度
embedding有了,下一个问题就是如何计算两个embedding之间的相似度。
如果没有 similarity,你就没法回答这些问题:
- king 和 queen 哪个更接近?
- dog 和 cat 比 dog 和 car 更像吗?
- 一个 query 和哪些 document 最相关?
所以流程一定是:
- 先学出向量
- 再定义向量之间的距离/相似度
- 再利用这个相似度去做检索、聚类、类比等任务
这里的相似度计算方法有很多种,比如:
- dot product点积
- cosine similarity余弦相似度
首先最直接的相似度想法是点积: 如果两个向量同方向而且对应维度都大,点积就大。
但点积有几个问题:
- 它偏爱长度很长的向量
- 它偏爱数值大的向量
- 它没有固定范围,不好解释
所以,我们通常使用余弦相似度,它其实是对点积做归一化。
余弦相似度的优点是:
- 范围固定在 -1 到 1
- 更关注方向而不是长度
- 更适合高维文本向量
- 解释更直观
你可以先把它记成一句最重要的话:
cosine similarity 比较的不是"两个向量有多长",而是"它们是不是朝着相近方向"。
这对文本特别重要,因为我们更关心语义方向,而不是绝对数值大小。
embedding
前面已经知道向量怎么学出来,也知道向量怎么比较相似度,现在才有资格讨论:这种向量到底表现出了什么能力。
接下来讲embedding的能力和优点在什么地方。
embedding的能力
embedding 最核心的能力:把语义和语法关系编码进向量空间。词本身被映射成点,词和词之间的关系被映射成方向和距离。所以它不是"词表编码",而更像"语义地图"。
embedding最直接的应用是:相似词搜索。背后的逻辑不是字面匹配,而是这些词在训练语料里经常出现在类似的上下文里。所以,这里有一个典型的认知错觉就是:相似不等于同义。
因为模型依据的是"上下文相似",不是词典定义。
这会带来一个经典现象:
- 同义词可能很近
- 反义词有时也会很近
embedding最精彩的是:向量空间里的类比(analogy)。 它可以用向量运算近似表达这个关系:
king - man + woman ≈ queen
这背后不是魔法,而是一个很强的假设: 某些语义关系在向量空间中表现为稳定的方向差。
换句话说:
- king -> queen
- man -> woman
这两个变化在空间里可能是近似平行的。
利用"parallelogram model"平行四边形模型的本质就是:
把语义关系理解成向量平移。
这是 NLP 历史上很关键的一步,因为它第一次让大家强烈意识到:神经网络学出来的词向量空间,不只是可用,而且有结构。
但是embedding也有它的限制:一个词只有一个embedding向量。那它怎么处理多义?其实embedding实际上是多个意义的加权叠加。 也就是:
- 哪个意义在语料里更常见,权重就更大
- 多个意义共存在同一个向量里 这叫做一种线性叠加或混合表示。
这很重要,因为它揭示了传统词向量的一个根本局限:它是静态词向量。同一个词,不管放在哪个句子里,向量都一样。
所以它没有办法像后来的 BERT 那样,根据上下文区分:
- "Apple released a new phone"
- "I ate an apple"
这也是后来"Contextualized embedding"出现的根本原因。
RNNs, LSTMs, GRUs, and CNNs for text
前面 embedding 解决的是"怎么把词变成可计算的向量",现在要解决的是"怎么理解一串词组成的序列"。也是真正进入到标题的用深度学习分析文本数据。
详细来说,embedding 只告诉模型:king、queen、apple 这些词各自长什么样。
但语言真正难的地方在于: 同一个词,放在不同上下文 里,作用不同;一句话的意思,不只是词的集合,而和顺序强相关。
比如:
dog bites man 和 man bites dog
词几乎一样,但意思完全不同。 所以,光有词向量还不够,还需要一种模型去处理"顺序"和"上下文依赖"。
RNNs循环神经网络
文本的本质是序列,所以最自然的方式是使用RNN这类序列模型来处理。
最直接的想法就是:
- 一个词一个词读进去
- 每读一个词,都更新一次内部状态
- 把前面读到的信息保留下来
- 到某个位置时,用当前状态做预测
这就是 RNN 的基本思想。
你可以把它理解成:
- embedding 是词的静态表示
- RNN 是"边读边记"的机制
所以,RNN 做的事不是重新定义词,而是把词放进时间顺序里处理。
RNN怎么做
RNN 的关键是一个叫hidden state隐藏状态的概念。hidden state 可以理解为:模型在读到当前时刻后,对前文的压缩总结。
处理句子时,它会这样走:
- 输入第一个词的 embedding
- 得到一个隐藏状态
- 再把第二个词的 embedding 和前一个隐藏状态一起输入
- 得到新的隐藏状态
- 一路滚下去
所以每一步的状态,都同时包含:
- 当前词的信息
- 之前上下文的信息
这就是它比 bag-of-words 强很多的地方。bag-of-words 不看顺序,RNN 看顺序。
你可以把它想成一个人在读句子:
- 读到第 1 个词时,脑子里有一个理解
- 读到第 2 个词时,理解会更新
- 读到后面时,前面的意思会影响后面的解释
RNN的问题
- 不能并行
因为hiden state的状态, RNN 必须按顺序算。
- 长期依赖问题
RNN虽然理论上能记住前面的信息,但在实际训练里,记不住太远的内容。这叫长期依赖问题。
比如句子:
The movie, although full of beautiful scenes and excellent acting, was not good.
真正决定情感的,可能是后面那个 not good 。 如果模型前面已经读了一大堆词,普通 RNN 可能会把关键信息冲淡,或者训练时梯度越来越弱,最后学不到远距离依赖。
所以普通 RNN 的问题不是"完全不能处理序列",而是:
- 能处理短依赖
- 处理长依赖很吃力
- 梯度消失和梯度爆炸
这是 RNN 最经典的问题。
训练神经网络时要反向传播梯度。 RNN 因为是沿时间展开的,所以梯度要一层一层往前传很多步。
结果会出现两种情况:
- 梯度越来越小,最后接近 0 这叫
vanishing gradient,模型学不会长距离依赖。 - 梯度越来越大,数值失控 这叫
exploding gradient,训练不稳定。
LSTMs长短期记忆网络和GRUs门控循环单元
为了解决 RNN 的问题,有人提出 LSTMs 和 GRUs,这两个方法让模型更会记,也更会忘。
好的序列模型不是把所有信息都死记住,而是:
- 该保留的保留
- 该丢掉的丢掉
LSTM 的核心思想是加入一种更精细的控制机制,常被解释成几个"门":
- 忘记什么
- 保留什么
- 输出什么
所以 LSTM 本质上是在解决:"信息流过很多时间步之后,怎样尽量不丢关键内容?"
GRU 可以看作是 LSTM 的一个更简化版本。 它参数更少,结构更紧凑,但仍然在试图解决长期依赖问题。
这两种方法都能缓解部分 RNN 的问题,但是依然存在:
- 不能并行
- 在长文本的情况下,仍然存在长期依赖问题
CNNs卷积神经网络
CNN虽然主要是用于图像处理,但也可以用于文本处理。
在做文本处理时,CNN 的作用不是处理整段长依赖,而是擅长抓取局部模式。
比如一句话里,某些短语特别关键:
- not good
- very impressive
- highly recommended
CNN 可以像滑动窗口一样,在文本上扫描,提取这种局部 n-gram 模式。 所以它特别适合做一些分类任务,比如情感分析、垃圾邮件识别、短文本分类。
可以这样区分:
- RNN/LSTM/GRU 更像顺序阅读者,强调时间推进
- CNN 更像模式探测器,强调局部特征
总结
这一部分整个就是一个文本分析的探索史,分别回答了四个问题:
- 词怎么表示:用 embedding
- 词组成句子后,顺序怎么处理:用 RNN
- 如果普通 RNN 记忆太弱怎么办:用 LSTM / GRU
- 如果我更关心局部短语模式怎么办:用 CNN for text
但是上面的框架都有各自的限制,所以后面会介绍诞生复杂的模型,比如 BERT 和 GPT。