语言模型
语言模型定义🎓
LM 定义
语言模型LM:一种对Token序列
的概率分布。
LM可以为token序列赋予一个概率:
LM具有卓越世界知识,才能通过概率准确评估序列好坏。
LM可以做生成任务,以概率
进行采样。 - 但我们通常不直接从LM进行采样,因为:
- 真实语言模型限制
- 期望得到最佳序列,而非平均序列
- 但我们通常不直接从LM进行采样,因为:
N-gram
- 定义:在
n-gram
模型中,只依赖于相邻前n-1个字符,而非整个历史 🔑。
- 例如:trigram(n=3),只依赖于前2个字符
- 概率计算:基于n-gram在文中的出现次数来计算的,并做数据平滑避免0概率。
- n-gram的限制
- n太小:无法捕获长距离信息
- n太大:统计上不好得到概率估计,很多都会为0.
神经语言模型🐸
- 概率由神经网络计算,而非由统计信息得出。
- 😈上下文长度仍然受n的限制
- 关键发展
- RNN/LSTM:使得
的条件分布可以依赖整个上下文 ,有效使得 ,但难以训练。 - Transformer:2017年提出的新架构,再次使用上下文长度n,但利用GPU并行更易于训练。GPT-3 n=2048。
- RNN/LSTM:使得
自回归LM
自回归生成序列,每次生成1个token:
, 可以用前馈神经网络计算每个条件概率:
链式法则计算条件概率:
温度参数T🎇
🔥随机性温度参数T
定义:
:⭐意味着压缩或放大原始概率,调整概率分布的陡峭程度,💥控制生成的随机性和多样性。 压缩后需要重新进行标准化,即退火调节🔥
3️⃣三种可能性
- T = 1:概率保持不变,从原LM中正常采样
- T > 1 (∞):大的概率会变小📉⬆️,🌟概率会被压平,趋近于均匀分布
- T < 1 (0):大的概率会变大📈⬇️,🌟在每一步都选择最有可能的token
💡举个例子
- T=1:
- T=0.5:
- 计算:
- 标准化:
- 计算:
- T从
的变化:🔑 ,略微增加 ⬆️ ,略微下降 ⬇️
常用数学公式
极大似然估计(MLE)✏️
似然函数
🤔似然和概率
概率(probability)
参数已知
,预测结果
。某个事件发生概率:
, 已知。 概率是
关于结果的函数
,概率和为1。
似然(likelihood):
结果已知
,反推参数
。根据结果估计参数。- 似然函数:
, 已知。给定观察到的数据/样本集合 ,参数 为 的可能性。 - 似然是
关于参数的函数
,所有可能参数的似然值不一定唯一。不是概率,只是一个衡量像不像的指标。
极大似然估计
📕极大似然估计
设
为训练集, 是第 类样本集合,样本独立同分布。 找到让样本似然/可能性最大的参数
- 寻找最大化似然
的参数 ,
- 寻找最大化似然
参数
对 的似然
💥 对数似然和负对数似然
- 对数似然:乘法不好计算,所以取对数变成加法。求参数使其最大。
- 负对数似然: 由于概率[0,1]取对数导致对数似然取值为负数,所以增加负号。求参数
使求其最小👍 。
信息熵
信息/信息量/信息熵📌
信息和信息量
⭐ 常识:概率越低的事件,带来的信息量越大;概率越高的事件,带来的信息量越小。
信息:用来消除不确定性的东西。
💥信息量:衡量信息消除不确定性的程度,越不确定,信息量越大。
🔑信息量的大小和信息发生的概率成反比。概率越高,信息量越小;概率越低,信息量越大。
某事件x的发生概率为
,其信息量为:
信息熵/熵
信息熵(熵)为所有信息量的期望。
- 香浓理论:熵也是把变量
编码/压缩成bit串所需要的最少bit数🐮。
- 香浓理论:熵也是把变量
- 随机变量
的熵越大,说明不确定性也越大。
熵计算实现
计算推导公式
: 的 logits
概率
- 熵公式推导
Verl-FSDP 熵计算
- 和公式一样
# 计算流程
logits = output.logits
logits.div_(temperature)
logits = logits[:, -response_length - 1 : -1, :] # (bsz, response_length, vocab_size)
log_probs = logprobs_from_logits(logits, micro_batch["responses"])
if calculate_entropy:
if not self.config.entropy_checkpointing:
entropy = verl_F.entropy_from_logits(logits) # (bsz, response_length)
else:
entropy = torch.utils.checkpoint.checkpoint(verl_F.entropy_from_logits, logits)
# 核心方法
def entropy_from_logits(logits: torch.Tensor):
"""Calculate entropy from logits."""
# 和公式一样
pd = torch.nn.functional.softmax(logits, dim=-1)
entropy = torch.logsumexp(logits, dim=-1) - torch.sum(pd * logits, dim=-1)
return entropy
Verl-Megatron 熵计算
去掉部分分布式代码。
class _VocabParallelEntropy(torch.autograd.Function):
def forward(ctx, vocab_parallel_logits: torch.Tensor) -> torch.Tensor:
def mul_reduce(a, b):
return (a * b).sum(dim=-1, keepdim=True)
# 稳定性操作,避免溢出,减去最大值
logits_max = vocab_parallel_logits.max(dim=-1, keepdim=True).values
normalized_vocab_parallel_logits = vocab_parallel_logits - logits_max
# exp_logits
normalized_exp_logits = normalized_vocab_parallel_logits.exp_()
# sum_exp_logits
normalized_sum_exp_logits = normalized_exp_logits.sum(dim=-1, keepdim=True)
# 计算每个logit概率
softmax_logits = normalized_exp_logits.div_(normalized_sum_exp_logits)
# p_i * logits_i
sum_softmax_times_logits = mul_reduce(softmax_logits, vocab_parallel_logits)
# 最终的熵,log_sum_exp_logits - sum_softmax_times_logits + logits_max
entropy = logits_max + normalized_sum_exp_logits.log() - sum_softmax_times_logits
ctx.save_for_backward(vocab_parallel_logits, softmax_logits, sum_softmax_times_logits)
return entropy.squeeze(dim=-1)
def backward(ctx, grad_output: torch.Tensor) -> torch.Tensor:
vocab_parallel_logits, softmax_logits, sum_softmax_times_logits = ctx.saved_tensors
# reuse softmax_logits as grad
vocab_parallel_logits.sub_(sum_softmax_times_logits)
softmax_logits.mul_(vocab_parallel_logits)
softmax_logits.mul_(grad_output.unsqueeze(dim=-1))
# recover vocab_parallel_logits
vocab_parallel_logits.add_(sum_softmax_times_logits)
softmax_logits.mul_(-1)
return softmax_logits
KL散度/相对熵
KL 散度 (K1):无偏但高方差
KL散度定义
衡量
两个分布之间的差异
。- p和q越接近,KL散度越小;p和q相同时,为0。
- KL(q, p):从分布q出发,到分布p的距离。但不是真的距离。
用模型p来近似真实数据分布q时,造成的信息损失。
- 本以为数据是按p分布的,但实际上数据是按q分布的。
- KL(q, p):错误认知的代价。
数学公式
- 第一个参数q:真实分布、参考分布、
旧策略
。分子,期望是从q中采样的。 - 第二个参数p:近似分布、模型分布、
新策略
。分母;
- 另一种写法
- k1
KL散度和交叉熵、信息熵的关系
k1定义
- 从q(x)取数据,计算多个
,然后求平均,近似KL值。
- 无偏差。
只要采样样本足够多
,平均值就会收敛,没有系统偏差
。
缺点
- 方差高。
, 当 或 时,方差很大。
- 例子
- 真实KL[q,p]=0.01
- 但样本
1.5, -0.8, 2.1, -3.0, 0.2, .
,离均值0.01非常远,高方差。估计值不稳定
K2:低方差但有偏
定义
低方差
- 永远非负。不会像k1那样正负跳跃。而且KL散度本身也是非负,更切合。
- 平方。不关心谁大谁小,只关心差异的大小。
K2有偏的
为何是有偏的
- f散度
- KL散度和
- KL和K2不同,会有一些偏差,
但实验显示偏差其实很小
;但方差大大降低
K3:无偏且低方差
核心思想
- 使用k1无偏估计,
增加r-1保证期望不变
,同时降低方差
。- r-1期望为0
K3定义
和
定义
为何无偏
- 公式推导出来是无偏的
为何方差低
- 同k2,
永远非负,避免正负大波动
实现代码
交叉熵
👫交叉熵
- 交叉熵:估计模型和真实概率分布之间的差异,以
去近似 的熵。
- 实际应用:
为真实分布, 为预测分布,用交叉熵去判断准确度
- 交叉熵简化:在训练过程中,标签通常采用one-hot编码,真实概率分布
,简化:
- 交叉熵loss:交叉熵求平均,越小越好。
困惑度
😕困惑度
- 困惑度:评估语言模型的基本准则,模型生成某个语料(句子)的概率。
- 模型重构输入的能力、模型原封不动还原原始语料的概率。
幂:做归一化,惩罚因子,避免太长导致数值很低。
- 困惑度和交叉熵的推导:
下面是一个pytorch计算交叉熵loss的一个真实示例:
# 输入数据
import torch
input=torch.rand(4,3)
tensor([[0.0515, 0.6730, 0.2852],
[0.0362, 0.3434, 0.7450],
[0.7136, 0.6566, 0.2402],
[0.6989, 0.0917, 0.7857]])
# log soft max 计算
output=torch.nn.LogSoftmax(dim=1)(input)
tensor([[-1.4170, -0.7956, -1.1834],
[-1.4796, -1.1724, -0.7708],
[-0.9429, -0.9999, -1.4163],
[-0.9691, -1.5762, -0.8823]])
# 假设标签为
[1, 0, 2, 1]
# 手动计算loss
loss=−(−0.7956−1.4796−1.4163−1.5762)/4=1.3169
# pytorch计算loss,pytorch entropyloss 自动有softmax,而nllloss则无
target = torch.tensor([1,0,2,1])
loss = torch.nn.CrossEntropyLoss()
output = loss(input, target)
tensor(1.3169)
分词和词向量
中文分词算法🐒
英文使用空格,但中文没办法,具有以下难点。
- 分词标准不统一:“花草” 或 “花” “草”?
- 切分歧义:如“商务处女干事”,"商务/处女/干事" or “商务处/女干事”?
- 未登录词:新词的影响远超歧义切分,难度更大。
最大匹配法
- 正向最大匹配法:从左到右匹配,匹配度越长越好
- 逆向最大匹配法:从右到左匹配,匹配度越长越好
- 双向匹配分词法:两者混合使用,选择二者词汇数量较少者。
全切分路径选择法
核心思想:所有切分结果全部选出来,从中选择最佳的
- n最短路径选择:切分结果组成有向无环图,找到总词频最大的切分路径
- n元语法模型:采用n最短路径时,考虑词的上下文关系。
核心:转换成序列标注问题B(开始) E(结束) M(中间) S(一个字表示的词)
大模型分词🐹
tokenize的有3种粒度:
- Word-词:词级别独立语义,中文依赖分词算法,但长尾问题会导致词表超级大难以训练,一般不能超过5w。
- Char-字符:字符,字符有限太少会导致字符承载的信息太多,难以训练。
- Subword-子词:介于词和字符之间,平衡了词汇量和语义独立性。常用词保持原状,生僻词拆分成子词。
主要的分词算法
1、BPE(Byte Pair Encoding)
- 将最常见的子词对合并,直到词汇表到达既定大小。
- 步骤:先拆分成字符,再选择出现频率最高的相邻子词进行合并。
2、WordPiece
- BPE变种,使用语言模型概率来合并子词,而非采用出现频率。
- 合并两个字符串A和B,应有最大的
3、Unigram
- 从一个大词汇表出发,逐渐删除一些词汇,直到词汇表达既定大小。
- 选择删除词汇:使预定义的loss最小,挑出使loss增长最小的10%-20%词汇来删除。
- 一般Unigram算法会与SentencePiece算法连用。
4、SentencePiece
- 把句子当做整体,再拆成片段,使用BPE或Unigram算法来构造词表。
词向量🐫
参见之前的旧笔记:
1、早期one-hot编码存在的问题
- 词汇表超级大,经常百万以上。
- 词向量彼此正交,没有体现词和词之间的相互关系
2、通过训练把词向量映射到较短向量,Word2Vec
🧠核心思想
- 基于word和context做文章,学习word和context的co-occurrence。
- 通过训练一个隐藏层神经网络,输入one-hot,输出one-hot,隐藏层则为词向量
🌟优点
- 🚀极快的训练速度。
- 纯学词嵌入,大大简化模型,抛弃之前LM的MLE/困惑度等内容。
- 利用
HSoftmax
和负采样
做加速,小时级别训练完成,之前LM要几周。
- 🤴👸一个很酷炫的man-woman=king-queen的示例,使词向量成为一个热门研究方向,不只是参数。
- 相比one-hot极大提升🆙,保留词间关系,词向量维度由大V降为d,d远小于V。
- word2vec里有大量的tricks。
- 核心思想:中心词c预测上下文o。中心词向量预测每个上下文词向量,根据各位置算loss。
- 适用场景:适合大数据集
- 缺点:基于窗口的模型,无法使用语料库中的共现统计,导致次有词向量
- 核心思想:上下文预测中心词。上下文向量求平均与中心词向量,越相近越好。
- 适用场景:适合小数据集
- 缺点:基于窗口的模型,无法使用语料库中的共现统计,导致次有词向量
优化提效技巧
- 层次化softmax
- 原词向量✖️矩阵后求平均
直接在输入测onehot词向量求平均 - 原隐藏层到输出层矩阵W
哈夫曼树 - 叶节点个数代表词汇数量,根节点到叶节点的路径决定了词向量
- 原词向量✖️矩阵后求平均
- 💥负采样
- 问题:期望设计一种方法每次只更新一部分权重,那么计算复杂度将大大降低。
- 核心思想:不采样整个词表,仅采样少数负样本,来进行计算代替整个词表。
- 采样:可从噪声分布
中进行负采样,采样概率可与 词频
相关。
- 采样:可从噪声分布
- 优点:加速模型计算,保证模型训练效果。
- 每次只更新采样词的权重,不更新所有权重。👍