足球网站怎么做,如何刷网站排名,乐清网络问效平台,常州免费网站制作内容总结归纳自视频#xff1a;【珍藏】从头开始用代码构建GPT - 大神Andrej Karpathy 的“神经网络从Zero到Hero 系列”之七_哔哩哔哩_bilibili 项目#xff1a;https://github.com/karpathy/ng-video-lecture Bigram模型是基于当前Token预测下一个Token的模型。例如#x…内容总结归纳自视频【珍藏】从头开始用代码构建GPT - 大神Andrej Karpathy 的“神经网络从Zero到Hero 系列”之七_哔哩哔哩_bilibili 项目https://github.com/karpathy/ng-video-lecture Bigram模型是基于当前Token预测下一个Token的模型。例如如果输入序列是[A, B, C]那么模型会根据A预测B根据B预测C依此类推实现自回归生成。在生成新Token时通常只需要最后一个Token的信息因为每个预测仅依赖于当前Token。
1. 训练batch数据形式
训练数据是 训练目标是 2. 定义词嵌入层
nn.Embedding 层输出的是可学习的浮点数将token索引 (B,T) 直接映射为logits即输入(4,8)输出 (4,8,65)其中输入每个数字被映射成logit向量这些值通过 F.cross_entropy 内部自动进行 softmax 转换为概率分布比如上面输入tokens有个24被映射成如下。
logits [1.0, 0.5, -2.0, ..., 3.2] # 共65个浮点数
softmax后得到。
probs [0.15, 0.12, 0.01, ..., 0.20] # 和为1的概率分布这样输出的是每个位置的概率分布。
交叉熵函数会自动计算每个位置的概率分布与真实标签之间的损失并取平均。
简单的大语言模型基于Bigram的结构即每个token仅根据前一个token来预测下一个token。具体实现如下。
from torch.nn import functional as F # 导入PyTorch函数模块
torch.manual_seed(1337) # 固定随机种子保证结果可复现class BigramLanguageModel(nn.Module): # 定义Bigram语言模型类def __init__(self, vocab_size):super().__init__() # 继承父类初始化方法# 定义词嵌入层将token索引直接映射为logits# 输入输出维度均为vocab_size词汇表大小self.token_embedding_table nn.Embedding(vocab_size, vocab_size)def forward(self, idx, targets):# 前向传播函数# idx: inputs, 输入序列 (B, T)B批次数T序列长度# targets: 目标序列 (B, T)# (4,8) - (4,8,65)logits self.token_embedding_table(idx) # (B, T, C)# 通过嵌入层获得每个位置的概率分布C词汇表大小# (4*8,C), (4*8,) - (1,)B, T, C logits.shape # 解包维度批次数、序列长度、词表大小logits logits.view(B*T, C) # 展平为二维张量 (B*T, C)targets targets.view(B*T) # 目标展平为一维张量 (B*T)loss F.cross_entropy(logits, targets) # 计算交叉熵损失return logits, loss # 返回logits未归一化概率和损失值# 假设 vocab_size65例如52字母标点
vocab_size 65
m BigramLanguageModel(vocab_size) # 实例化模型# 假设输入数据代码中未定义
# xb: 输入批次 (B4, T8)例如 tensor([[1,2,3,...], ...])
# yb: 目标批次 (B4, T8)
logits, loss m(xb, yb) # 执行前向传播print(logits.shape) # 输出logits形状torch.Size([32, 65])
# 解释32 B*T 4*865词表大小每个位置65种可能print(loss) # 输出损失值tensor(4.8786, grad_fnNllLossBackward)
# 解释初始随机参数下损失值约为-ln(1/65)4.17实际值因参数初始化略有波动
3. 代码逻辑分步解释
# 假设输入和目标的形状均为 (B4, T8) # 输入示例第一个样本 inputs[0] [24, 43, 58, 5, 57, 1, 46, 43] targets[0] [43, 58, 5, 57, 1, 46, 43, 39]
3.1 Softmax后的概率分布意义 当模型处理输入序列时每个位置会输出一个长度为vocab_size的logits向量。即
输入 (4,8)
输出(4,8,65). 65维度向量是每个输入token的下一个token的概率分布。
例如当输入序列为 [24, 43, 58, 5, 57, 1, 46, 43] 时 - 在第1个位置token24模型预测下一个token对应target43的概率分布p[0].shape(65,)下一个输出是43的概率为p[0][target[0]]p[0][43] - 在第2个位置token43模型预测下一个token对应target58的概率分布p[1].shape(65)下一个输出是58的概率为p[1][target[1]]p[1][58] - 以此类推每个位置的logits经过softmax后得到一个概率分布即每个输入位置都会预测下一个token概率分布。 具体来说 logits.shape (4, 8, 65) → softmax后形状不变-p.shape(4,8,65)但每行的65个值变为概率和为1 这些概率表示模型认为「当前token的下一个token」是词汇表中各token的可能性。
3.2 交叉熵计算步骤 假设logits初始形状为 (4, 8, 65) B, T, C logits.shape # B4, T8, C65
# 展平logits和targets logits_flat logits.view(B*T, C) # 形状 (32, 65) targets_flat targets.view(B*T) # 形状 (32,) # 交叉熵计算PyTorch内部过程 # 对logits_flat的每一行共32行做softmax得到概率分布probs (32, 65) # 对每个样本i取probs[i][targets_flat[i]]即真实标签对应的预测概率(此概率是下一个token是targets_flat[i]的概率 # 计算负对数损失loss -mean(log(probs[i][targets_flat[i]]))pytorch实现是将targets_flat所谓索引 loss F.cross_entropy(logits_flat, targets_flat) # 输出标量值
3.3 示例计算 # 以第一个样本的第一个位置为例 # 输入token24目标token43 # 模型输出的logits[0,0]是一个65维向量这里logits.shape[4,8,65]例如 logits_example logits[0,0] # 形状 (65,) probs_example F.softmax(logits_example, dim-1) # 形状 (65,) # 假设probs_example[43] 0.15模型预测下一个token43的概率为15% # 则此位置的损失为 -log(0.15) ≈ 1.897 注意-log(p)是一个x范围在[0,1]之间单调递减函数
# 最终损失是所有32个位置类似计算的均值。 # 初始损失约为4.87接近均匀分布的理论值 -ln(1/65)≈4.17
4. 测试生成文本
# super simple bigram model
class BigramLanguageModel(nn.Module):def __init__(self, vocab_size):super().__init__()# each token directly reads off the logits for the next token from a lookup tableself.token_embedding_table nn.Embedding(vocab_size, vocab_size)def forward(self, idx, targetsNone):# idx and targets are both (B,T) tensor of integerslogits self.token_embedding_table(idx) # (B,T,C)if targets is None:loss Noneelse:B, T, C logits.shapelogits logits.view(B*T, C)targets targets.view(B*T)loss F.cross_entropy(logits, targets)return logits, lossdef generate(self, idx, max_new_tokens):# idx is (B, T) array of indices in the current contextfor _ in range(max_new_tokens):# get the predictionslogits, loss self(idx) # 没有输入target时返回的logits未被展平。 # focus only on the last time steplogits logits[:, -1, :] # (B,T,C) - (B, C)# apply softmax to get probabilitiesprobs F.softmax(logits, dim-1) # (B, C)# sample from the distributionidx_next torch.multinomial(probs, num_samples1) # (B, 1)# append sampled index to the running sequenceidx torch.cat((idx, idx_next), dim1) # (B, T1)return idx
以下是Bigram模型生成过程的逐步详解以输入序列[24, 43, 58, 5, 57, 1, 46, 43]为例说明模型如何从初始输入[24]开始逐步预测下一个词
1. 初始输入[24] 输入形状idx [[24]]B1批次T1序列长度。 前向传播 通过嵌入层模型输出logits形状为(1, 1, 65)表示对当前词24的下一个词的预测分数。 假设logits[0, 0, 43] 5.0词43的logit较高其他位置logits较低如logits[0, 0, :] [..., 5.0, ...]。 概率分布 对logits应用softmax得到概率分布probs。例如 probs [0.01, ..., 0.8对应43, 0.01, ...] # 总和为1 采样 根据probs使用torch.multinomial采样选中词43的概率最大。 更新输入 将43拼接到序列末尾新输入为idx [[24, 43]]形状(1, 2)。 2. 输入[24, 43] 前向传播 模型处理整个序列输出logits形状为(1, 2, 65)对应两个位置的预测 第1个位置词24预测下一个词已生成43。 第2个位置词43预测下一个词。 提取最后一个位置的logitslogits[:, -1, :]形状(1, 65)。 假设logits[0, -1, 58] 6.0词58的logit较高。 概率分布 probs [0.01, ..., 0.85对应58, 0.01, ...]。 采样 选中词58。 更新输入 新输入为idx [[24, 43, 58]]形状(1, 3)。 3. 输入[24, 43, 58] 前向传播 logits形状为(1, 3, 65)。 提取最后一个位置词58的logits假设logits[0, -1, 5] 4.5。 概率分布 probs [0.01, ..., 0.7对应5, ...]。 采样 选中词5。 更新输入 新输入为idx [[24, 43, 58, 5]]形状(1, 4)。 4. 重复生成直到序列完成 后续步骤 输入[24, 43, 58, 5] → 预测词57。 输入[24, 43, 58, 5, 57] → 预测词1。 输入[24, 43, 58, 5, 57, 1] → 预测词46。 输入[24, 43, 58, 5, 57, 1, 46] → 预测词43。 最终序列 idx [[24, 43, 58, 5, 57, 1, 46, 43]]。
注意上面输入序列是越来越长的为何说预测下一个词只跟上一个词有关如果只跟一个词有关为何不每次只输入一个词然后预测下一个词
虽然理论上可以仅传递最后一个词但实际实现中传递完整序列的原因视频作者说的固定generate函数形式我这里理解的是代码简洁
代码简洁性无需在每次生成时截取最后一个词直接复用统一的前向传播逻辑
实验验证
若修改代码每次仅传递最后一个词
def generate(self, idx, max_new_tokens):for _ in range(max_new_tokens):last_token idx[:, -1:] # 仅取最后一个词 (B, 1)logits, _ self(last_token) # 输出形状 (B, 1, C)probs F.softmax(logits[:, -1, :], dim-1)idx_next torch.multinomial(probs, num_samples1)idx torch.cat((idx, idx_next), dim1)return idx