Pytorch Parameters 结构与 Muon 的调用
近期想测试 Muon 优化器的效果,看了下 Muon 官方 Github 的实现,发现无法像 Adam 或者 AdamW 那样直接简单地调用。还是要学习一下 pytorch
中 model.parameters()
的结构,最终搞明白如何使用 Muon
优化器。
背景
众所周知,我们一般创建 Adam
等优化器时只需要把模型内部的参数 model.parameters()
传给优化器就可以很简单地创建一个优化器,例如以下代码:
=
虽然大多数优化器都能以上面这样简单的方式进行创建,Muon
却不太一样。Muon
是针对二维以及更高维张量的优化器,而剩余的一维张量(例如偏置、Embedding层、输出层)则交给 Adam。按照其官方仓库的说明,要把 AdamW
替换为 Muon
得进行一定程度的修改:
# optimizer = torch.optim.AdamW(model.parameters(), lr=3e-4, betas=(0.90, 0.95), weight_decay=0.01)
# 要把 AdamW 替换为 Muon,则需要使用以下代码:
=
=
=
=
=
可以看出上面的代码涉及到了一些 Pytorch
中 Parameters
的结构,对于没咋写过优化器的炼金师(我)来说还是有点儿不熟悉的,而且可能要根据项目需求进行进一步修改。所以要正确使用 Muon
优化器,还是得充分理解 Pytorch
中 Parameters
的结构。
Model.Parameters() 的本质
Parameter 对象:在 Pytorch
中,一个神经网络模型(对应 torch.nn.Module
类)由多个层组成,每个层又包含一些可以学习的参数(主要是权重 weights
和偏置 bias
),这些参数被封装为 torch.nn.Parameter
对象。torch.nn.Parameter
对象本质上是特殊的 Tensor
,其与普通 Tensor
的区别在于其被注册为模型的参数,在调用 loss.backward()
时 Pytorch
会自动计算其对应的梯度。
model.parameters() :model.parameters()
返回一个迭代器(Iterator),我们可以用它遍历模型中注册的 torch.nn.Parameter
对象。大多数情况下,我们只需要使用这个迭代器就可以创建一个优化器,例如:
= # 包含 weight 和 bias
= # 一个独立的参数
=
# 输出:
# 参数形状: torch.Size([5, 10]), 需要梯度: True <-- self.linear_layer.weight
# 参数形状: torch.Size([5]), 需要梯度: True <-- self.linear_layer.bias
# 参数形状: torch.Size([3]), 需要梯度: True <-- self.lone_parameter
Parameter Groups 参数组:除了使用上述的 parameter iterator 创建优化器外,Pytorch
还支持传入一组不同配置的参数(一个字典组成的列表),列表中每个字典定义了一个参数和对应的超参,包含 params
(torch.nn.Parameter
对象的列表,而非迭代器)以及训练这些模型参数的超参(例如 lr
、weight_decay
等)。例如外面在前面例子的基础上希望对每层参数使用不同的学习率,则可以按下面的方式定义优化器
=
=
=
# 注意 params 键对应的值必须为列表
=
= # 使用SGD举例
注意:model.parameters()
返回的只能返回 iterator。parameter groups 必须要在外部定义。
model.named_parameters() :为了更方便地筛选和命名参数, Pytorch
还支持 named_parameters()
方法,类似于 parameters()
方法,其返回了一个迭代器,但是每步迭代得到的的元素是 (name, parameters)
的元组。那 name
是哪里来的呢?Pytorch 有一套自己的命名方式:
- 直接赋值:例如
self.output_layer = nn.Linear(10,2)
,则对应的两个参数的名称为output_layer.weight
和output_layer.bias
。再比如说self.my_param = nn.Parameter(torch.rand(5))
,则对应的名称为my_param
-
nn.Sequential
结构:例如self.features = nn.Sequential(xxx, xxx)
,则获得的参数的名称为features.0.weight
、features.0.bias
、features.1.weight
… 其中中间的数字表示第n
层。 -
nn.ModuleList
或者 Python 列表/字典:类似于nn.Sequential
。
我们可以使用 'pattern' in name
来确定参数的 name
中是否包含某个 pattern
。例如我们也可以用 name
的方式来构建 param groups
:
=
=
=
=
=
=
Muon 优化器实战
现在我们拥有了所有必要的知识,可以来解读和使用 MuonWithAuxAdam
了(见 Muon/muon.py at master · KellerJordan/Muon),从代码和文档我们可以看出:
-
Muon
是一个混合优化器:其内部同时实现了Muon
和Adam
的更新逻辑; -
Muon
必须传入参数组(不能传入model.parameters()
或者model.named_parameters()
),参数组中使用键'use_muon': True/False
来确定是否使用 Muon 算法。
我们再次来理解 Muon
官方仓库给出的范例
=
=
=
=
=
-
model.body
、model.head
、model.embed
是模型中定义的属性,但这并非是所有模型都有这些属性,因此要根据自己的模型去修改。 - 这里把
2
维及以上的参数设置为hidden_weights
使用Muon
进行优化,1
维偏置以及head
、embed
的参数使用Adam
优化。 - 具体怎么控制是否使用
muon
:创建一个参数组,并使用use_muon
键控制是否使用muon
进行优化。
例如比较常用的,筛选掉维度 >=2
以及 embedding
层的用法可以是:
# 筛选出所有维度 >= 2 的参数,通常是权重 (weights)
=
# 剩余的所有参数都交给 AdamW
# 包括:所有维度 < 2 的参数 (biases, layernorm gains) 以及 Embedding 层的参数
=
# 检查是否所有参数都被分配了
assert == +
=
=