Closed libertatis closed 2 years ago
非常感谢您的认真反馈! @libertatis
我最近在使用ViT的时候同样出现了与反馈完全相同的问题,如果调整了embed_dim
必须同时调整num_heads
,否则在Attention
计算中将出错。
以下是我个人的一点看法:
embed_dim
和all_head_size
值默认等同了(在默认了all_head_size=d_k=d_v
的简单情况下)。所以我觉得这段代码主要的问题是,由于除法取整的特点,导致embed_dim != all_head_size
,进而导致后面代码出错。
https://github.com/BR-IDL/PaddleViT/blob/a20f3b7d43b38b7a777e3718067114fffda7075b/image_classification/ViT/transformer.py#L113-L114attention_head_size * num_heads = embed_dim
三者只能显式赋值其中两个,而乘法显然是优于除法的。但是不可忽视的一点,在绝大部分论文里对于参数的指定都是给出embed_dim
和num_heads
,而不是给出num_heads
和attention_head_size
。
具体的修改实现还需要@xperzy来定夺。@libertatis 感谢的详细调研和提出这个issue, 也感谢@skpig 参与讨论。 我觉得两位说的都没有问题。 我认为咱们可以同时照顾两种情况,增加一个head_size(单头的dim)作为传入参数,允许这个参数为None,如果是None,我们使用 embed_dim //num_heads + assert 的方式(类似https://github.com/PaddlePaddle/Paddle/blob/develop/python/paddle/nn/layer/transformer.py#L109),如果不是None,按照传入参数进行计算。 @libertatis 这个你有没有兴趣帮忙实现一下然后提PR?
@libertatis 感谢的详细调研和提出这个issue, 也感谢@skpig 参与讨论。 我觉得两位说的都没有问题。 我认为咱们可以同时照顾两种情况,增加一个head_size(单头的dim)作为传入参数,允许这个参数为None,如果是None,我们使用 embed_dim //num_heads + assert 的方式(类似https://github.com/PaddlePaddle/Paddle/blob/develop/python/paddle/nn/layer/transformer.py#L109),如果不是None,按照传入参数进行计算。%EF%BC%8C%E5%A6%82%E6%9E%9C%E4%B8%8D%E6%98%AFNone%EF%BC%8C%E6%8C%89%E7%85%A7%E4%BC%A0%E5%85%A5%E5%8F%82%E6%95%B0%E8%BF%9B%E8%A1%8C%E8%AE%A1%E7%AE%97%E3%80%82) @libertatis 这个你有没有兴趣帮忙实现一下然后提PR?
好哒 ^_^。不过我没有提过 PR
,具体流程不太熟悉,我先看一下教程。谢谢朱老师的回复~
@libertatis 感谢的详细调研和提出这个issue, 也感谢@skpig 参与讨论。 我觉得两位说的都没有问题。 我认为咱们可以同时照顾两种情况,增加一个head_size(单头的dim)作为传入参数,允许这个参数为None,如果是None,我们使用 embed_dim //num_heads + assert 的方式(类似https://github.com/PaddlePaddle/Paddle/blob/develop/python/paddle/nn/layer/transformer.py#L109),如果不是None,按照传入参数进行计算。%EF%BC%8C%E5%A6%82%E6%9E%9C%E4%B8%8D%E6%98%AFNone%EF%BC%8C%E6%8C%89%E7%85%A7%E4%BC%A0%E5%85%A5%E5%8F%82%E6%95%B0%E8%BF%9B%E8%A1%8C%E8%AE%A1%E7%AE%97%E3%80%82) @libertatis 这个你有没有兴趣帮忙实现一下然后提PR?
在
vit transformer
的实现中(ViT Transformer Attention),多头注意力的attn_head_size
的计算是由传入的embed_dim
和num_heads
计算得到的:我认为这里的实现至少有两个问题:
embed_dim
是否能num_heads
整除做检查。当embed_dim
不能被num_heads
整除,或者num_heads > embed_dim
时,transpose_multihead
的操作会出现异常:attn_head_size
的大小受到embed_dim
和num_heads
的限制,当预训练模型时,不能随意设置attn_head_size
的大小,代码不够灵活。解决上述问题的办法,就是为
Attention
的__init__
方法添加一个attn_head_size
的参数,这样即不影响现有预训练模型的加载,又可以在预训练时,灵活设置attn_head_size
的大小。由于attn_head_size
与输入维度embed_dim
无关,也不需要验证embed_dim
是否能被num_heads
整除。 目前主流框架中,两种实现都有: 第一种,由embed_dim
和num_heads
参数计算attn_head_size
的实现,包括:PaddlePaddle
: https://github.com/PaddlePaddle/Paddle/blob/develop/python/paddle/nn/layer/transformer.py#L109PyTorch
: https://github.com/pytorch/pytorch/blob/master/torch/nn/modules/transformer.pytransformers
: https://github.com/huggingface/transformers/blob/master/src/transformers/models/bert/modeling_bert.py#L226 第二种,将attn_head_size
作为参数传入的实现,包括:TensorFlow
: https://github.com/tensorflow/tensorflow/blob/master/tensorflow/python/keras/layers/multi_head_attention.py#L126TensorFlow Addons
: https://github.com/tensorflow/addons/blob/master/tensorflow_addons/layers/multihead_attention.py 我个人非常推荐第二种实现方式,API
使用起来更加灵活,代码看起来也非常顺畅,更加合理。 比如,原实现中all_head_size
的定义:all_head_size == embed_dim
,完全没有必要定义。这个变量,只在__init__
:和
forward
:中用到。
__init__
中的qkv
映射的输出维度self.all_head_size*3
可改为embed_dim*3
,forward
中的new_shape
用到的self.all_head_size
,可以在方法的开始,取出输入x
的维度,修改如下:以上是我对源码中定义
self.all_head_size
的质疑。 还有最后输出加一层Linear Layer
的必要性:在
forward
中,最后输出执行线性映射操作的上面由一行注释reshape
,意思应该是将维度映射回输入维度
embed_dim
,方面后面的残差连接。不过既然all_head_size == embed_dim
,那何来reshape
? 所以,我认为这里对输出的线性映射是不必要的。 不过,如果我们使用第二种方式实现,将attn_head_size
作为参数传入,不依赖embed_size
和num_heads
来计算,以上代码看起来就顺畅多了,合理多了。 第二种实现,将attn_head_size
作为参数传入,只需在源代码基础上更改几行代码即可,实现如下:测试:
输出:
以上是我个人的一点儿不成熟的小建议,望官方评估采纳~