目錄
寫在前面的話 CSDN真的是'sb'中的'sb'軟體, 辛辛苦苦寫半天 我複製個東西過來 他就把前面的刷沒了 還要我重頭寫????????????神經並b
------------------------------------------------------------------------------------------------------------------------------
2022李宏毅作業HW3 是食物的分類 ,但是我怎麼嘗試 再監督學習的模式下 準確率都達不到百分之60 .。半監督也感覺效果不明顯。 所以 這次就想著對比學習能不能用來解決這個問題呢 。?看了一圈,感覺simsiam是對比學習裡比較簡單的一種方法,好像效果也不錯。 所以來看一看這個東西是怎麼玩的。
simsaim 是對比學習很新的文章了。 他的訓練方式簡單來說就是 ,一張圖片 ,用不同的方式去增廣後形成圖片對 。 然後用一張去預測另一張。 不懂得可以看朱老師的視訊。
1 : 事先準備 。
程式碼地址 :
下載解壓。
直接在main函式的 執行 編輯配置中輸入
注意 : 第二次執行可以刪掉download
2 : 程式碼閱讀。
神經網路的一個基本的框架就是 : 資料讀取 , 模型載入, 訓練,測試。 我們接下來根據這四塊來看。
2.1: 資料讀取
執行main檔案 。
進入main 函式 。
是三個資料集的讀取。
train_loader ,, memory_loader 和 test_loader。 train和memory 都是訓練集的資料 他們的不同之處在於, 資料增廣的方式不同。 train的增廣是用來訓練的 memory和test的增廣都是用來測試的。由於在對比學習裡, 資料增廣是很重要的 ,所以這裡看下資料增廣的方式。
**args.aug_kwargs 裡規定了圖片大小是32. 以及這次用的是simsaim。
這裡兩個train 。 只有訓練集的第一個train是true。 而訓練集的增廣方式如下
增廣方式可以參考 官網
這裡依次是 : 隨機resize 然後剪下為輸入大小, 也就是會隨機取圖片裡的一塊。
隨機水平變換
0.8的概率調節亮度對比度和飽和度。
0.2概率灰度化
對於32的照片 不做高斯模糊。
轉化為張量並標準化。
然後 對於一個輸入 這裡會做兩次transform call可以讓這個類像函式那樣被呼叫。
對於 測試用的訓練集 。也就是memory 是下面的增廣方式。 而test也是下面的增廣方式 。
如果輸入是 224 就 先放大到256,然後中心裁剪224,之後標準化。
如果輸入是32 就放大到36 再中心裁剪32 .後標準化。
用的是cifar10的資料。 其實也就相當於很普通的 讀圖片 然後增廣, 加標籤。
我們只要看getitem取出來的資料是什麼就好 。
總結:資料部分 我們需要做一個資料集, 然後訓練集的增廣要返回兩個結果。 當讀取資料時,返回的是圖片資料和標籤資料。
2.2: 模型載入
這一部分我們來看模型 ,我們可以根據下面的虛擬碼來看模型長什麼樣子。 虛擬碼非常容易看懂。 aug就是增廣嘛。 f來提特徵,然後兩個預測。 算loss 回傳。
這句來獲得模型。
backbone就是普通的res18 這裡不需要預訓練的模型 只需要初始模型 。
這個就是simsam的模型了 。 projector 是個三層的普通mlp 。 encoder 就是虛擬碼裡的f了 而predictor就是虛擬碼裡的h了 。 我們具體來看下loss 。
傳說 simsaim的精髓就在於這個loss, 在於這個z.detach 也就是傳說中的stop gradiant。 有了這個梯度停止, simsaim才能夠訓練的起來。 這時的simsaim就和k-means演算法有點類似了。 說法很多 大家可以搜搜看。
其實我們可以看出來一點東西,在算loss時, p是預測值, z是標籤,如果標籤也要算梯度,兩邊就都在變了,參考我們平時的label都是不變的,確實z也不應該算梯度。
那麼什麼是stop gradiant呢 就是不算梯度的意思。比如
x = 2 y = 2**2 z = y+x z.grad = 5 y.detach() z.grad = 1
本來y是x的平方 求導等於4 所以z對x求導是5 然後不算y的梯度了 那麼就只剩1了 。
這就是模型的全部了 ,輸入兩張圖片 ,然後抽特徵 預測 分別算loss
3 訓練過程:
訓練是非常普通的訓練。
從測試集中抽loader 注意抽出的是兩張圖片 由不同transformers形成的。 之後過模型得到loss,梯度回傳。 這裡日誌一直報錯 我直接遮蔽了。
4 測試過程:
測試過程比較的關鍵。
這裡是用knn演算法進行測試的 ,關於knn 可以看
簡單的說, 就是從眾。 在一個大平面上有很多的點, 然後你就看離自己最近的k個點,他們的標籤是啥, 然後選最多的那個當自己的標籤。
注意 這裡的net 只是backbone 也就是resnet 而 memory 就是訓練資料 不過增廣方式不一樣 。還有訓練資料 和k值 取200.
一些初始化和獲取類別數。
獲取大平面上的點。 從訓練集抽資料, 然後獲取他們的特徵。
最後的feature_bank大小是49664*512 也就是將近50000條資料 每個資料都有512 維的特徵。 然後做了一個轉置。
抽取測試集的特徵。
我們來看 knn是如果計算相似度的 ,也就是距離的。torch.mm表示矩陣的乘法。 我舉個例子。
下面只是例子 ,真實資料需要歸一化
a = [[1,2,3], [4,5,6]] b = [[1,2,3], [2,4,6], [3,6,9], [4,8,1]]
a有2個樣本, b有4個樣本。 他們的特徵都是3維。 現在求a[0] 和b中哪些樣本最相似。
就要讓a[0]和b中每一個樣本點乘 得到 14, 28, 42, 23。數越大表示越相似,也就越近。 所以我們讓a和b的轉置相乘,得到:
tensor([[14, 28, 42, 23],
[32, 64, 96, 62]])
我們發現第一排 就是a[0]的相似度, 每一列都是與b中樣本的點乘結果。
所以這裡的sim_matrix 就是一個512 * 49664大小的矩陣。 512 表示有512個樣本, 49664 表示每個樣本和所有點的乘積。
topk 表示取最大的值,和他們下標 這裡取200個 我們就得到了離每一個樣本,最近的那些點,他們的下標是多少。
feature_labels.expand(feature.size(0), -1) 之前的文章說過 ,是一個複製擴充。 -1表示不改變維度。 feature是50000維 擴充後變成512 *50000 (注意label和49664不相等,是因為loader捨棄了最後的一部分,但是沒關係 , 本來就取不到這部分值)。
torch.gather 是按下標取值。
我們對標籤按下標取值,得到了512 *200的矩陣, 每一行都表示這個樣本距離最近的200個樣本的標籤。
看到後面就知道這個knn_t的作用了 。 作用就是 控制相似度的權重。 比如 一個更相似的 他的標籤可以一個頂好幾個不相似的。 那麼頂幾個呢 ? 就是t控制的了 。
我們需要先搞懂scatter函式 。說實話著實有點難。因為官網的scatter都很難理解了 ,何況這個和官網不一樣
我們可以看到 官網的第三個引數是src 也就是資料來源,而這裡是value 。。。真是奇怪。
對於tensor.scatter函式 可以看 這篇
相信大家對scatter 都有了理解。 我們回來。
這裡先建立一個 長是512 *200 = 102400 寬是10的向量。
而sim_labels的大小是 (102400,1) 這個scatter做了什麼呢 ? 如下
也就是把每行標籤對應得數字那一列變為1 ,如果這行特徵得標籤是1 就把第一個數變為1,這樣子。 類似的有102400行。 得到onehot後 按我得想法, 就統計200行中哪一列的1最多唄。 比如前200行裡 第3列的1對多, 就說明第一個樣本最近的200個裡,最多的標籤是2 ,。我們看看他們怎麼做的。
one_hot_label.view(feature.size(0), -1, classes)
這句可以理解。 變回512 *200*10 這樣就可以統計各自的兩百個了。
sim_weight.unsqueeze(dim=-1)
sim_weight 雖然在上面做了一點變換,但是我們其實不用管他,因為上面只是一種歸一化的方式,我們依然可以把它看作最近 當前樣本特徵和兩百個點特徵的乘積。unsqueeze 表示擴充一維 在最後, sim_weight就變成了 512 *200 *1。 我們如何理解這個pred_score呢? 我們不要看512個樣本。 我們只看一個樣本。 對於一個樣本。他的one_label是200*10 而sim_weight就是200 *1 特徵的點乘結果,也就是200個相似度分數 。 從兩行 看兩百行 很顯然 就是讓各行的標籤1 乘上那個相似度分數。 之後再對200這個維度求和,就得到了各個標籤相似的分數的和。 維度1*10
看到這裡我們明白了 。 這裡的knn並不是簡單的從眾,他還要看影響力。 更相似的樣本,他的標籤對我們的結果的影響力更大。 這裡相當於對標籤做了一個加權求和。
回到512維 我們得到了512*10的矩陣 表示512個樣本的各個標籤的相似度分數 我們只要argsort就可以得到最大值的下標啦。 np.argsort這個函式可以對向量排序 然後返回他們原來的下標 des 表示可以降序。
得到標籤 , 回到原來的knn
這裡是計算top1 我估計如果計算top5 估計就是 target in labels[:,4]了 得到預測標籤後準確率久很好算了。
5 :線性驗證
繼續跟著主函式走 。 可以看到一堆儲存的步驟。 然後進入linear_eval函式。 我猜測是用backbone抽特徵然後直接預測結果的函式。
先讀取訓練集和測試集, 然後 model是resnet 一個分類器是 一個全連線。 我好奇的是為什麼不直接把backbone最後一層的恆等對映改為這個分類器呢 ?
載入模型
k長這個樣子 取出那些以backb開頭的層 就是resnet的層。 然後去掉前面9個字母 就是resnet的名字。
定義優化器和loss 最下面這個averagemeter是啥呀
查了一下 就是一個類似於佇列這種的 資料結構。 然後可以更新 關鍵是可以求平均。
然後定義好後 就是一個普通的訓練過程了 。 值得注意的是 model是eval模型 也就是他是凍住的,引數不改變。而classfier是可以改變的, 梯度回傳也只回傳分類頭的梯度, 這裡就只訓練分類器。
普通的測試。
6 : 用自己資料集進行對比學習。
路走遠了,別忘了開始的方向。 我們是用對比學習解決食物分類的問題的。
我們要做的有幾件事情。
第一: 改資料集 :
把它原來的三個資料集全#了。
然後 加入自己的資料集。 使用他的增廣方式。 但在增廣前 需要在 dataset的get里加 topil 因為他的增廣裡沒有這個。
hw3食物分類有三個資料集:
一個有標籤訓練集 我用來當memory
一個無標籤訓練集 我用來當train
一個驗證集 我用來測試。
2 改變batch_size和圖片大小。
在這個檔案裡改batch 你會發現這個對比學習的模型 出奇的佔記憶體,當我圖片大小為224時,我的batch只能設定為32.
main函式裡改imagesize 為自己的。
點執行。O了 。 然後發現效果並不是很好。。。。。