git官方链接:
下了MAE代码 完全看不懂 我要一步一步来 把这篇代码给全部理解了 。我自己觉得看大神代码很有用。 这篇文章当笔记用。
一,跑示例:
怎么说 一上来肯定是把demo里的代码拿出来跑一跑。但是会遇到问题。 下面时demo的代码。 第一个问题是
说函数没这个参数 那很简单 找到位置 删掉就行 为啥我敢删 就是因为他的值是 None ,直接删就行
第二个问题是 我一开始把
这三个模型当成了预训练模型 , 下面左就是得到的结果 这啥啊 还原了个寂寞 。 想了半天kaiming是不是错了 ,再想了半天kaiming怎么会错 ,才发现预训练模型藏在链接里。下面这三个只是他开始训练时使用的预训练模型。
链接在demo里找到 两个large的 模型参数如下 跑的结果如上右 对嘛
复现结束了 (bushi)
终于把演示跑通了。
2 画图
调试这个方法可太神了,我们上面跑通了demo 就让我们跟着demo一览模型全貌吧!
这段 获取图像并且归一化 然后用plt画出来 这里是先归一化 画图时再返回回来。
(吐槽 : 我不理解 为什么要先归一化 再回来 再画图 多此一举? 我直接show img 不香吗)
3 载入模型
3.1准备模型
会进入准备模型的函数里
对于第一局 getattr(models_mae,arch): 是取models_mae模块里的arch 而这个arch是什么 下图可以看到是一个函数 而且是一个没带括号的函数 (我不理解 ) 所以get后要补一个括号
然后我们进入这个函数, 可以看到这个函数了 哦~ 是一个获取模型的函数 大 中小模型有三个不同的函数 不同函数的参数不一样罢了。
然后就是一个大工程了 我们进这个模型内部看一看。
3.2.1_模型内部
模型代码太大了 我就不贴整个的了 我一部分一部分的贴。
3.2.1.1 编码器模块
这个编码 来自于VIT的编码, 然而我并没有看过VIT的代码是什么样子的 。这篇里先不写 ,等到下一篇文章 我就遍历进这个编码函数里 看看是什么东西。 我们就记住 有一个编码的函数 似乎是吧图片 变成一串特征码
cls令牌 加入 位置编码加入 nn.patameter这个函数 就是将一个不可训练的张量或者矩阵 转换为模型内可以训练的参数。 (想写一个要训练的参数 又不是官方的那些层 ,终于知道方法啦)。cls_token大小是 (1,1,1024) 位置编码是 (1,197,1024) 为啥是197呢 ?应该是为了跟嵌入cls后的编码大小保持一致 然后可以cat 我猜。
这里的 block 就是VIT里的那个block 这个block也等到VIT代码时再讲
这里有几个他们用的小trick
nn.ModuleList 其实就是一个列表 把一些块放在这个列表里 与普通列表不同的是 普通的列表不会得到训练 。 这里就是放了24个自注意力块 每个块有12个头 。以上就是编码器用到的模块。
3.2.1.2 解码模块
下面是解码器。
解码器的注意力层只有8层 但也是12头的 输入是512维
3.2.1.3 初始化模块
3.2.1.3.1 找位置编码
第一个的值是false 等会看看有啥用 第二个是一个函数 我们进去看看 。
初始化 第一步 是一个位置编码函数 ,我们进入这个编码函数去看
然后继续进入下层函数 我们继续看 。
再进入下层函数 。
下层函数返回后 再次拼起来 变成 196 *1024 这个位置编码真可谓是历尽艰辛 。我们来看 他是怎么来的 。首先 196, 1024分前后两段。看前半段 。 先做个(256,1)长的矩阵 分布再1,256 表示位置 之后呢 再反向后与网格(14*14)拉平后的值做一个外积 这个网格也是位置信息。之后sin 和cos都上 得到两个位置编码。 再拼起来 得到一个维度的编码 。 再把两个维度拼起来得到整体的位置编码。
这里是 将196 1041 , 变成(197,1024) 拼出CLS那一维。
3.2.1.3.2回到初始化
解码器的位置编码 (1,197,512) 还是比编码器少了一半
这个w是取出weight层的权重值。 正好可以看出 w的大小是 (1024,3,16,16) 1024是输出维度 3是输入维度 。相当于一个卷积 ? 然后参数进行一个初始化 统一于 (1024, 3*16*16)正太分布
mask 和 cls 也要初始化 。
初始化其他层 self.apply应该是对遍历模型 对每一个模块 使用后面这个函数 我们进入初始化权重函数看一看 ,
可以看到是如何初始化的 全连接层的 权重使用xavier的均匀分布 偏置设为0
layer归一化层 的偏置为0 权重为1
过程中可以看到对24个注意力层都初始化 而且注意力层里也有各种各样的linear层。
3.2.1.3.3 初始化完成
至此 模型的初始化完成了 我们得到了这个模型。从这些步骤里 我们可以大概看到模型是什么样子的 , 有一个编码器模块 和一个解码器模块。 编码器模块有24层深的16头自注意力模块。 还有一些位置编码和 cls 编码 而解码器只是多了一个mask编码,而且维度会与编码器不一样。
3.3 模型准备完成。
这个chkpt_dir 也就是下载下来的预训练模型 大概应该只是参数 所以需要下面这句 模型载入参数
这里这个strict 意思是 如果与预训练有的层 就使用预训练的参数 模型里 预训练没有的层 就普通初始化。
msg 记录加载的结果 得到完全体模型。
4处理图片
模型准备好了 我们开始用模型处理一个图片看看 。
4.1数据准备
我们进入了 run_ONE_image函数内部
这里显示了怎么把一个 图片 做成一个batch 第三个einsum 也可以用
torch.transpose() 这个函数来 就是一个维度的转换嘛 把那个3 提到第二维上来。 不过he他们确实精妙 大佬。
进入模型运行了 。 从模型返回的是loss 预测值 和mask 我们进模型内部看看 注意模型中运算的值都是float32 格式的 。
进froward第一句 就是这一句 我们接下来进入前向编码器里看一看 。
4.2编码步骤
这里的mask这里非常难以理解 所以我举个例子 来看看 。
首先 noise是随机生成的 比如说是 noise = [2,0,3,1]
然后 排序argsort: shuffle = [1,3,0,2] 到这里 是为了生成随机数 我们取前两个 也就是随机出来的1,3 作为mask的下标
对shuffle排序 : restore = [2,0,3,1]
mask = [0,0,1,1] 我们根据restore对mask取数 得到[ 1,0,1,0] 下标1,3处就是0. 其实你可以把mask和shuffle看成一样的 你用restore对shuffle 取数 得到【0,1,2,3】发现是排序好的 。 对【1,0,1,0】取数 得到[0,0,1,1]两个是对应起来的。
处理cls
这里x要经历24个多头自注意力的磨练 然后归一化。
4.3解码步骤
回归forward 来到第二局 解码
得到了模型预测的图像结果
4.4 loss探索
下一步是loss
首先进入这个函数 p是一个小图的大小 hw分别是yx方向图的个数 都是14
x 是(1,3,14,16,14,16) -(1,14,14,16,16,3)
然后reshape (1,14,14,16,16,3) -》(1,196,768) 此中过程 不足为外人道也 鬼知道你咋变的啊 。
target = self.patchify(imgs) 这句就是把原来的图片 也编辑成(1,196,768)大小的
这个归一化 没进去
可能因为本来已经归过了
loss是像素差平方 然后对最后一维求平均 变成了 (1,196) 也就是每一个小pat 一个loss
mask在相应没有遮盖的地方是0 所以就是只有遮盖的地方才求loss 返回loss值。回到run
4.5 画图
进图unpatchify 根据这个名字 可以看出是吧patch 还原成大图 。
p 16 h w, 14,14
x (1,196,768) -> (1,14,14,16,16,3) ->(1,3,14,16,14,16) ->imgs(1,3,224,224)
#我忽然想明白了 这里不用知道里面是怎么变化的 只需要操持一致即可 计算机自己就会把他们对应起来 又不用自己管。
回到上面来
y(1,3,224,224)- 》(1,224,224,3)
mask:(1,196 ) ->(1,196,768) ->(1,3,224,224) ->(1,224,224,3)
x (1,3,224,224) ->(1,224,224,3)
1-mask 就是本来是0的 就是没遮盖的变成1 遮盖的变成0 与x相乘 就得到遮盖图片 。
im_paste = x * (1 - mask) + y * mask 遮盖的图片 加上预测的Y与mask相乘 。 因为mask遮盖的地方是1 所以直接相乘
至此得到所有需要画的图像。,
无语泪凝噎 为啥图不是一块出来的 ????
原来是因为我改了代码
ok 完毕啦 演示结束 改天看其他模块