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 完畢啦 演示結束 改天看其他模塊