type
status
date
slug
summary
tags
category
icon
password
#深度学习 #Zero2hero
这节课要基于一篇论文,使用 mlp 改进模型

前期准备

导入相关库

读取 name 文件

创建映射

创建字符到整数、整数到字符的映射

创建数据集

这里要根据 word,分出 input 和 label
这里的 blocksize 其实就相当于 n-gram 里的 n,表示下一个预测的单词要基于前 n 个单词的情况
具体解释一下代码:
  1. 设置 block size 大小,创建 X Y 列表,分别是 input 和 label
  1. 开始遍历所有单词
  1. 首先初始化 context,具体是 n 个 0 构成的 list
  1. 遍历单词中的字母
  1. ix 是当前字母映射后得到的整数,这里作为 label 存到 y 中,同时把 context 作为 input 存入 x
  1. 更新 context,这里的规则需要注意,相当于是拿一个长度为 n 的 slide 在单词上滑动,这里相当于是向右滑动一次,因此使用切片,切除原有的 context 中的第一位,把当前的 letter 拼接到 context 中,实现移动
  1. 最后,把 X Y 转化为 tensor 类型

Embedding 嵌入层

这里是对于我而言比较新的内容,我先根据自己的理解解释一下为什么要引入 embedding
在 p1 的部分中,由于我们只是预测字母,因此维度有限,仅仅是 27
但如果要用来预测单词的话,则将面临巨大的维度,无法实现,因此需要转换思路
我们选择将词汇或符号映射到连续向量空间中的方法,用矩阵来表示,这样可以大幅降低维度,同时,对于从没出现过的词语,我们可以看学习后的向量与哪些词语比较接近,即可大概了解含义
Embedding 的优势与意义:

(1)泛化能力强:

  • 即使模型没有见过某个词序列,但如果词向量接近,它仍然能合理地预测其出现概率。

(2)捕捉语义关系:

  • 词嵌入的空间结构捕捉了语义关系
    • 同义词、近义词距离近;
    • 不同词性、不同含义的词语距离远。

(3)提升效率:

  • 大幅降低模型参数量,减少计算负担。
  • 低维稠密向量提升了计算效率和存储效率。
以下是具体代码:

初始化

首先,这里随机初始化一个CC 是一个嵌入矩阵,维度为 27,2,表示从 27 维映射成 2 维 27 代表的是 letter 的个数,2 叫做嵌入维度emb 这里是在做嵌入查找,也就是把 X 这个 input tensor其中的每个 letter 经过 C 的映射,得到映射后的tensor W1 是全链接层的权重,b1是全链接层的偏置 这里的 C W1 b1 全是后续需要更新的参数

非线性激活

这里使用 tanh 作为激活函数,为了让 embW1 相乘,需要把 emb 展开成一个二维的 tensor 这里的 -1 的这个参数表示让 torch 自己来设置这个位置的参数(根据前后的计算,并非随机);而 6 是因为 W1 的形状,或者是是因为 emb 的形状是 xxxx 2 3 然后在加上 b1 ,然后通过 tanh 得到 h, 这里的h的维度是 228146,100

输出层

这里再来一层,把 100 维映射到 27 维,得到 logits,用于预测下一个 letter 是啥

归一化

这里对 logits 取 exp, 把他转化成对应的未归一化的权重 [[Makemore - part 1]] [[../动手学深度学习/3.4 softmax 回归]] 然后对每行进行归一化,得到 prob,也就是每个类的概率 这相当于作用了一次 softmax

损失函数

这里我们的loss 使用 cross entropy

完整实现

初始化

这里的parameters包含了所有的参数,用于后续一通操作

开启梯度记录

小批量 mini batch

由于完整的跑下来那么多数据迭代周期很长,虽然得到的梯度,也就是修正的方向很精确 为了在时间和准确率上做出兼顾,我们采取 mini batch 的方法, 也就是把数据分为小份的 batch,快速的跑完,得到一个近似的更新方向之后进行更新. 这样反复多次的更新会优于几次的精准更新
这里我们用随机数生成一个 ix, 用于从 X 中随机抽取一部分 data, 作为我们的 mini batch 这里我们选择的是先生成随机的 index, 然后再以此为索引进行数据的抽取 这个随机抽取的过程会在每次循环的初始进行

学习率调优 Learning Rate Finder

这里原先似乎确实没学过 首先是用linespace这个方法把-3 到 0 切 1000 份 然后用lre作为指数,以 10 为底数,得到 lrs,作为学习率
因为学习率对训练过程影响极大,但它的“有效值域”通常是以 10 为底的对数尺度分布的 如果你用线性空间(如 0 到 1)探索,那前面 90% 都没用,因为学习率太小了根本不起作用
然后我们进行训练,在第 i次训练中采用 lrs 的第 i 项,使每次训练的 lrs 逐步变化,记录 loss 以及对应的 lrs 用数据做出 loss-lr 的图表,我们可以通过曲线找到 loss 下降最快的那个学习率,以此定位效率/效果最好的 lr 的大概区间 最终确定我们选择的 lr

三种数据集

由于我们的数据有限,并且单纯的对于一个数据集训练会面临 过拟合 的问题 因此,我们需要对于有限的数据进行一些切分,把它分为不同功能的数据集:训练集, 验证集, 测试集 这三者的比例大概是 80%, 10%, 10% 训练集用于训练参数,验证集用于训练超参数,测试集用于测试模型的泛化能力 [[../动手学深度学习/4.4 模型选择、欠拟合与过拟合]]
接下来来动手构建一下数据集, 更改一下以前的代码
这里我们定义了一个构建数据集的函数,前面大部分步骤差不多,主要是把 word 拆分成 inputs 和对应的 label, 也就是 XY 然后我们用基于 random seed, 进行一个打乱, 然后按照刚才说的 80:10:10 的比例对于 XY 进行 slice, 得到三个数据集

训练模型

这里是把训练过程中用的数据改成 Xtr 和 Ytr 了 也就是训练集 此外,还对于 lr 进行了一些修改 由于这里的循环次数很大,因此设置在 10000 次以后 lr 设置为 0.01, 相当于做一些精细的调整吧

评估损失

这里我们用验证集来评估一下此时模型的损失
在 AK 的视频中, 此时训练集和验证集的 loss 很接近,说明此时是欠拟合的,因此我们可以做一些调整让模型从测试集中学到更多的特征 [[../动手学深度学习/4.4 模型选择、欠拟合与过拟合]]

修改隐藏层

于是, 我们就尝试改变模型的结构: 改变隐藏层的维度, 相当于给隐藏层加入更多的神经元 需要注意的是, 这个改动不仅是针对 W, 同时也要改变 b 等参数, 不然无法满足矩阵运算 刚才说了验证集是帮助我们"训练"超参数的(超参数就是人为设置的那些参数,比如模型结构,lr,batch size等等) 其实上面的更改维度就是训练超参数的一种体现

修改嵌入层

在修改了隐藏层的维度后,我们发现模型的提升有限,因此寻找下一个优化点:嵌入层 我们通过嵌入层对于 letter 进行一个映射, 从一个 int 转换成一个嵌入向量 然而, 我们此时设置的嵌入向量的维度仅仅是 2 维, 因此我们猜测可能是维度太低, 表达能力有限,限制了模型的发挥

过拟合 vs 欠拟合 : 我的一些疑惑

一边是 train 与 eval 的 loss 差的太少,欠拟合了; 真差多了之后又过拟合了. 到底什么才算一个好的模型的 loss 呢? 我询问 gpt, 得到了以下回答:
一个理想的状态具备以下特征:
  1. 训练集 loss 持续下降,测试集 loss 也同步下降
  1. eval loss 达到较低水平后开始震荡或略微上升
  1. 此时 eval accuracy 或其他指标在稳定高点
  1. 型没有明显的过度波动、过拟合、不稳定现象 换句话说就是: 训练误差下降,泛化误差也在下降,然后我们在泛化误差“最低点”附近保存模型。 其实感觉主要就是 train 和 eval 的 loss 没有太大差距, 并且 loss 都比较低,应该是个比较理想的状态 AK 当时应该不满足后者这个条件

从模型中采样

和 part 1 一样,训练完了模型,我们像看一下实际的预测效果,因此通过采样得到单词

为什么要采样

这个其实是我的一个疑惑:为什么不直接用模型输出的最大概率对应的字母,而是基于概率分布通过采样得到字母 以下是 GPT 给我的回答:

贪心策略

我说的那种方法其实是贪心策略 这种策略在 CS 50 AI 中曾经学习过, 也常常出现在各种算法中 然而我们可以知道, 贪心只是一种局部最优, 并非全局最优, 并不能得到最好的结果 [[../CS50 AI/Week 0 - Search/Lecture - Search]]

生成结果重复单调

  • 生成的文本“千篇一律”;
  • 出现死循环,比如不停生成相同的几个字符;
  • 模型学到的概率分布没法体现出来(只取了最大那个,忽略了不确定性)

无法体现语言的多样性

比如在给定上下文 “th”,后面可能接 “e”、“a”、“i” 都是合理的词汇开头。但如果你始终只取最大值,你永远只会生成 “the”,而不会生成 “this”、“that”、“thin”等其他可能。

代码实现

设置随机种子

跟之前的方法一样 [[Makemore - part 1]]

初始化

这里设置 out 为一个空的 list, 用于存储后续输出的单词 Context 也就是用于预测下一个单词的 上下文 初始化为 0, 对应的是起始和终止符

映射

通过 emb 矩阵, 将 context 映射成嵌入向量

运算

用上面训练好参数的 W 和 b, 对于这里的 emb 进行同样的运算, 得到 logits, 并计算出对应的概率

采样

使用之前讲过的 multinomial 方法, 依据概率采样, 得到预测的下一个单词

滑动窗口

把 ix 填充到 context 尾部, 滑动并更新 context

结语

至此, part 2 部分完结 后续准备再精读一下本次参考的那篇论文: A Neural Probabilistic Language Model 难得的在期中考试周拿出那么多时间来学习深度学习, 但是很沉浸很开心! 或许学习 AI 是取代平时玩游戏, 作为对我生活的一种调剂 (开始但是并不轻松) 希望期中也能取得好成绩!
2025 Week 15 (期中总结)2025 Week 14
Loading...