要了解AI,首先要对AI的底层原理有一定的了解,现代大模型一般都是基于神经网络和深度学习的,准确来说是多层神经网络。
一.神经网络的基础数学模型
最开始的模型会有以下几样东西:
- 几层神经网络
- 每层多少个神经元
- 用什么激活函数
其实在这个时候模型就有参数了,这些参数通常是:
- 随机初始化
- 按照某种规则进行初始化
所谓的训练大模型,准确点来说就是训练模型中的参数,这个训练参数的过程一般是通过反向传播算法,后面会细说。
并且这里的参数并不是指的模型的神经元,一个神经元里面通常是有很多个参数。
一个最简单的神经元可以写成如下的形式
还可以写成,其中代表的是参数的集合
这里指的是激活函数,w1,…wn,b就是模型的参数。
可以看到,其中的w1…wn在和最后的偏置参数b构成了一个线性函数,而外面的相当于进行了一下非线性变换。
当然一个神经网络中往往是有多层神经元的
假设输入向量是:
注意这里输入的是向量,这和后面的RAG在数学形式上也有些关联。
假设这一层有m个神经元,那整层可以聚合表示为:
其中:
这里的W是一个参数矩阵,是这一整层很多参数的集合。
最终输入n维,输出m维。
为啥输出是m维呢,其实还有一个更简单的理解方式
每个神经元都是一个函数,都对输入的向量进行运算,这里的参数w可以理解成是一种权重,参数b就是偏置,最终把向量运算得到一个数值。
其中
m个神经元就是m个数值,聚合到一起就是一个m维的向量。
二.输入是怎么进入神经网络的
这个在之前的RAG文章中是有描述过一些的,首先用户输入的文本需要经过tokenizer切成token
比如最近GPT5.5不就是因为token的处理方式变了,所以更加节省token了吗
2.1 tokenizer
tokenizer一般分为两步:
1.文本拆分
比如输入:
how are you
输出可能变为:
"how","are","you"
当然这是比较理想的状况,gpt的tokenizer我记得是开源的,如果感兴趣的话可以自己去试一下
2.映射为token_id
["how", "are", "you"] → [101, 57, 302]
2.2 embedding
token_id本身它只是编号罢了,需要去embedding表里面去查找出对应的向量才行,而这个编号正是它用来进行查表的,查它属于这个表的第几行
其中V代表的是词表大小,就是词表的高度,D是向量维度,也就是词表的长度
当然一句话往往是有多个token的,如一句话被tokenizer成4个token_id
当一次性输入多句话的时候,查出来就是一个三维的张量
在真实情况下,模型一次通常处理一个batch,及一次性送进模型处理的一组样本
2.3 加位置编码
之前在RAG的那篇文章中写过,token_id它本身是不带位置信息的,所以需要有一个位置编码之类的东西来标注所处的位置
三.神经网络是如何学习的
大致是分如下几步:
- 输入数据:如一段文本,或者是一张图片
- 前向传播:即模型 根据当前参数,根据神经元来算出输出
- 计算损失:把预测结果和正确答案相比较,得到loss
- 反向传播:计算每个参数对loss的影响
- 梯度下降:往loss变小的方向去更新模型的参数
这里的输入数据和前向传播我们刚刚已经大致讲过了,所以先讲讲损失它是如何计算的
3.1 计算损失
这里的损失计算是通过一个函数来实现的:
这里的符号表示如下:
:损失函数
:模型预测结果
:真实答案
LLM的损失计算方式一般是通过交叉熵损失来进行计算的
- logits
首先我们都知道模型的输出一般都是一组token
如
token1,token2,token3...
LLM的训练目标是根据前面的token去预测下一个token
模型不是直接输出token,模型本身是有一个词表,这个词表准确来说是个token表,在每一个预测位置上,模型会给整个词表中的token进行打分,假设词表大小是V,那么模型会输出
这里其实也可以用向量来进行表示
这里的表示第i个token作为下一个token的原始分数值,这些分数叫做logits,它们还不是概率,可能是正数,也可能是负数,加起来也不一定等于1
- softmax
下面所需要做的事情,就是把这些原始的分数值变为一堆概率
这里每一个p都是在0-1之间的
并且相加起来是等于1的
这里如果是用普通归一化,如:
这样的话会出现几个问题
1.有正有负
2.分数差异不太明显,大的分数就不够突出
这里我们使用指数函数就可以解决这个问题
首先指数函数的结果一定是正数,并且指数函数增长是很快的,差异也会比较的明显
- 交叉熵损失计算
softmax负责把logits变成概率,交叉熵损失负责看模型给正确答案的概率有多大
如果是第k个token模型经过softmax后给它的概率是
那么交叉熵损失就是
这里因为是大于0小于1的,所以是小于0的,故需要加一个负号
| 模型给正确 token 的概率 (pk) | 损失 (L=-log pk) |
|---|---|
| 1.0 | 0 |
| 0.9 | 0.105 |
| 0.5 | 0.693 |
| 0.1 | 2.303 |
| 0.01 | 4.605 |
| 0.001 | 6.908 |
可以看到
- 如果模型很确定而且正确,损失接近 0
- 如果模型只给正确答案 0.1 的概率,损失明显变大
- 如果模型几乎不相信正确答案,比如 0.001,损失会非常大
还有这里为什么用log呢,可以在表格中看到如果正确答案概率从0.9到0.8损失变化不大,但如果从0.1降低到0.001,差距会明显变大,比简单的1-p更加敏感
还有一个很重要的点,我们都知道模型在输出的时候输出的是一串的token,如果是1-p的话,概率相乘会越来越小,很难计算
如果取一个log,就可以从相乘变成相加
那么整个序列的损失就可以写成
平均一下就是(整个序列的损失通常取平均):
3.2 反向传播
反向传播,即Backpropagation,本质其实是:
用链式法则,从损失函数开始,反过来计算每个参数对loss的影响
前面我们已经知道,神经网络不是一层公式,而是一层嵌套函数
这里在最前面,L即loss在最后面,如果想知道应该怎么改,那么就需要知道
在这一整条链里面对最终loss的影响,这就需要链式法则,而链式法则,就是反向传播的核心
3.2.1 链式法则
最简单的链式法则是,如果:
那么
说人话就是:
如果x影响y,y影响z,那么x对z的影响= x对y的影响 × y对z的影响
当然我一开始看到这个式子,有点印象,但还是忘了这些d是啥了,所以这里做一下补充
这里的d是微积分里的微分符号,也可以表示一个极小的变化量
这里的就是y对x的导数,记得高中课本上讲的是y随x的变化率这样
更直白的说法是:
x每变化一个很小的单位,y大约变化多少,也可以说是对切线的逼近
不过d的话通常用于的是单变量函数,如:
只有一个输入变量,所以写:
如果是多变量函数我们就要换一个符号,这也是下面计算梯度的时候会经常看见的
如果一个函数同时依赖很多变量,如:
这个时候,如果想问
如果只改变,其它东西固定不动,L怎么变?
毕竟我们都知道神经网络中不止一个参数,而我们这里是要算每个参数分别对loss整体的影响
这个时候就写:
这叫偏导数,偏的意思是:只看其中一个变量的影响,其它变量暂时当成常数
举个例子:
,可见L同时依赖x和y,如果只看x对L的影响,y^2就可以当成是常数,那么结果就是2x
3.2.2 计算参数梯度
在前面的网络:
如果想更新最前面的参数,就需要知道
它表示的是如果发生一个很小的变化,最终损失L会如何变化
并且是通过一条链影响的:
通过链式法则可知:
L -> ->…,由此可见反向传播的反向之道
3.2.3 梯度下降更新参数
我们前面已经知道了每个参数对损失函数的影响是:
,接着,我们可以根据梯度来更新参数,最基础的参数更新的方式是梯度下降:
:当前参数
:把左边更新成右边的新值
:学习率,控制每次走多大一点
:损失L对参数的梯度
-:沿梯度的反方向走,因为梯度是loss上升最快的方向
所以如果要让loss下降,需要走反方向
新参数 = 旧参数 - 一小步*梯度
还有这里的学习率也是很重要的,如果没有学习率,可能变的太大,直接冲过最低点
回到本质,其实本质上是一个参数矩阵:如果输入有3个数,隐藏层有4个神经元,那就是一个4×3的矩阵,里面装着12个数字,每个数字代表”输入的某一项对某个神经元的影响有多大”。所谓训练一个模型,说穿了就是把这12个数字(以及网络里其它所有参数)一次次按照梯度调整,直到损失足够小。
到这里,一条完整的链路就闭环了:
输入 → 参数矩阵做线性变换 → 激活函数引入非线性 → 得到输出 → 损失函数衡量差距 → 反向传播用链式法则算梯度 → 梯度下降更新参数 → 再输入下一批数据……
大语言模型看起来神秘,但拆开之后,每一层做的都是这件事。区别只在于矩阵的规模从4×3变成了几千×几千,参数量从十几个变成了几千亿个。规模带来了涌现,但原理没有变。理解了这一层,再去看”温度”、“上下文窗口”、“微调”这些词,就都能落回到矩阵和梯度上了。