ConViT實戰:使用ConViT實現植物幼苗的分類(pytorch)
摘要
來自 Facebook 的研究者提出了一種名為 ConViT 的新計算機視覺模型,它結合了兩種廣泛使用的 AI 架構——卷積神經網路 (CNN) 和 Transformer,該模型取長補短,克服了 CNN 和 Transformer 本身的一些侷限性。同時,藉助這兩種架構的優勢,這種基於視覺 Transformer 的模型可以勝過現有架構,尤其是在小資料的情況下,同時在大資料的情況下也能實現類似的優秀效能。
•論文地址:
https://
arxiv。org/pdf/2103。1069
7。pdf
•論文翻譯:ConViT:使用軟卷積歸納偏置改進視覺變換器
[1]
•GitHub 地址:
https://
github。com/facebookrese
arch/convit
•本文用的資料集 連結:
https://
pan。baidu。com/s/1gYb-3X
CZBhBoEFyj6d_kdw
提取碼:q060
其工作原理
ConViT 在 vision Transformer 的基礎上進行了調整,以利用 soft 卷積歸納偏置,從而激勵網路進行卷積操作。同時最重要的是,ConViT 允許模型自行決定是否要保持卷積。為了利用這種 soft 歸納偏置,研究者引入了一種稱為「門控位置自注意力(gated positional self-attention,GPSA)」的位置自注意力形式,其模型學習門控引數 lambda,該引數用於平衡基於內容的自注意力和卷積初始化位置自注意力。
如上圖所示,ConViT(左)在 ViT 的基礎上,將一些自注意力(SA)層用門控位置自注意力層(GPSA,右)替代。因為 GPSA 層涉及位置資訊,因此在最後一個 GPSA層之後,類 token 會與隱藏表徵聯絡到一起。有了 GPSA 層加持,ConViT 的效能優於 Facebook 去年提出的 DeiT 模型。例如,ConViT-S+ 效能略優於 DeiT-B(對比結果為 82。2% vs。 81。8%),而 ConViT-S + 使用的引數量只有 DeiT-B 的一半左右 (48M vs 86M)。而 ConViT 最大的改進是在有限的資料範圍內,soft 卷積歸納偏置發揮了重要作用。例如,僅使用 5% 的訓練資料時,ConViT 的效能明顯優於 DeiT(對比結果為 47。8% vs。 34。8%)。
此外,ConViT 在樣本效率和引數效率方面也都優於 DeiT。如上圖所示,左圖為 ConViT-S 與 DeiT-S 的樣本效率對比結果,這兩個模型是在相同的超引數,且都是在 ImageNet-1k 的子集上訓練完成的。圖中綠色折線是 ConViT 相對於 DeiT 的提升。研究者還在 ImageNet-1k 上比較了 ConViT 模型與其他 ViT 以及 CNN 的 top-1 準確率,如上右圖所示。除了 ConViT 的效能優勢外,門控引數提供了一種簡單的方法來理解模型訓練後每一層的卷積程度。檢視所有層,研究者發現 ConViT 在訓練過程中對卷積位置注意力的關注逐漸減少。對於靠後的層,門控引數最終會收斂到接近 0,這表明卷積歸納偏置實際上被忽略了。然而,對於起始層來說,許多注意力頭保持較高的門控值,這表明該網路利用早期層的卷積歸納偏置來輔助訓練。
上圖展示了 DeiT (b) 及 ConViT (c) 注意力圖的幾個例子。σ(λ) 表示可學習的門控引數。接近 1 的值表示使用了卷積初始化,而接近 0 的值表示只使用了基於內容的注意力。注意,早期的 ConViT 層部分地維護了卷積初始化,而後面的層則完全基於內容。
匯入專案使用的庫
import torch。optim as optim
import torch
import torch。nn as nn
import torch。nn。parallel
import torch。optim
import torch。utils。data
import torch。utils。data。distributed
import torchvision。transforms as transforms
from models import convit_tiny
from torch。autograd import Variable
from mydatasets import SeedlingData
import os
設定全域性引數
設定使用哪塊GPU,0指的是第一塊GPU,設定學習率、BatchSize、epoch等引數
os。environ[‘CUDA_VISIBLE_DEVICE’] = ‘0’
torch。cuda。set_device(0)
# 設定全域性引數
modellr = 1e-4
BATCH_SIZE = 16
EPOCHS = 100
DEVICE = torch。device(‘cuda:0’ if torch。cuda。is_available() else ‘cpu’)
影象預處理
資料處理比較簡單,沒有做複雜的嘗試,有興趣的可以加入一些處理。
# 資料預處理
transform = transforms。Compose([
transforms。Resize((224, 224)),
transforms。ToTensor(),
transforms。Normalize([0。5, 0。5, 0。5], [0。5, 0。5, 0。5])
])
transform_test = transforms。Compose([
transforms。Resize((224, 224)),
transforms。ToTensor(),
transforms。Normalize([0。5, 0。5, 0。5], [0。5, 0。5, 0。5])
])
讀取資料
將資料集解壓後放到data資料夾下面,如圖:
然後我們在dataset資料夾下面新建
init
。py和dataset。py,在mydatasets。py資料夾寫入下面的程式碼:
說一下程式碼的核心邏輯。
第一步 建立字典,定義類別對應的ID,用數字代替類別。
第二步 在__init__裡面編寫獲取圖片路徑的方法。測試集只有一層路徑直接讀取,訓練集在train資料夾下面是類別資料夾,先獲取到類別,再獲取到具體的圖片路徑。然後使用sklearn中切分資料集的方法,按照7:3的比例切分訓練集和驗證集。
第三步 在__getitem__方法中定義讀取單個圖片和類別的方法,由於影象中有位深度32位的,所以我在讀取影象的時候做了轉換。
# coding:utf8
import os
from PIL import Image
from torch。utils import data
from torchvision import transforms as T
from sklearn。model_selection import train_test_split
Labels = {‘Black-grass’: 0, ‘Charlock’: 1, ‘Cleavers’: 2, ‘Common Chickweed’: 3,
‘Common wheat’: 4, ‘Fat Hen’: 5, ‘Loose Silky-bent’: 6, ‘Maize’: 7, ‘Scentless Mayweed’: 8,
‘Shepherds Purse’: 9, ‘Small-flowered Cranesbill’: 10, ‘Sugar beet’: 11}
class SeedlingData (data。Dataset):
def __init__(self, root, transforms=None, train=True, test=False):
“”“
主要目標:獲取所有圖片的地址,並根據訓練,驗證,測試劃分資料
”“”
self。test = test
self。transforms = transforms
if self。test:
imgs = [os。path。join(root, img) for img in os。listdir(root)]
self。imgs = imgs
else:
imgs_labels = [os。path。join(root, img) for img in os。listdir(root)]
imgs = []
for imglable in imgs_labels:
for imgname in os。listdir(imglable):
imgpath = os。path。join(imglable, imgname)
imgs。append(imgpath)
trainval_files, val_files = train_test_split(imgs, test_size=0。3, random_state=42)
if train:
self。imgs = trainval_files
else:
self。imgs = val_files
def __getitem__(self, index):
“”“
一次返回一張圖片的資料
”“”
img_path = self。imgs[index]
img_path=img_path。replace(“\\”,‘/’)
if self。test:
label = -1
else:
labelname = img_path。split(‘/’)[-2]
label = Labels[labelname]
data = Image。open(img_path)。convert(‘RGB’)
data = self。transforms(data)
return data, label
def __len__(self):
return len(self。imgs)
然後我們在train。py呼叫SeedlingData讀取資料 ,記著匯入剛才寫的dataset。py(from mydatasets import SeedlingData)
dataset_train = SeedlingData(‘data/train’, transforms=transform, train=True)
dataset_test = SeedlingData(“data/train”, transforms=transform_test, train=False)
# 讀取資料
print(dataset_train。imgs)
# 匯入資料
train_loader = torch。utils。data。DataLoader(dataset_train, batch_size=BATCH_SIZE, shuffle=True)
test_loader = torch。utils。data。DataLoader(dataset_test, batch_size=BATCH_SIZE, shuffle=False)
設定模型
•設定loss函式為nn。CrossEntropyLoss()。•設定模型為convit_tiny,num_classes設定為12,embed_dim是編碼的個數,設定為48,dropout設定為0。5。•最佳化器設定為adam。
# 例項化模型並且移動到GPU
criterion = nn。CrossEntropyLoss()
model_ft = convit_tiny(pretrained=False, num_classes=12, embed_dim=48, drop_rate=0。5)
model_ft。to(DEVICE)
# 選擇簡單暴力的Adam最佳化器,學習率調低
optimizer = optim。Adam(model_ft。parameters(), lr=modellr)
def adjust_learning_rate(optimizer, epoch):
“”“Sets the learning rate to the initial LR decayed by 10 every 30 epochs”“”
modellrnew = modellr * (0。1 ** (epoch // 50))
print(“lr:”, modellrnew)
for param_group in optimizer。param_groups:
param_group[‘lr’] = modellrnew
定義訓練和驗證函式
# 定義訓練過程
def train(model, device, train_loader, optimizer, epoch):
model。train()
sum_loss = 0
total_num = len(train_loader。dataset)
print(total_num, len(train_loader))
for batch_idx, (data, target) in enumerate(train_loader):
data, target = Variable(data)。to(device), Variable(target)。to(device)
output = model(data)
loss = criterion(output, target)
optimizer。zero_grad()
loss。backward()
optimizer。step()
print_loss = loss。data。item()
sum_loss += print_loss
if (batch_idx + 1) % 10 == 0:
print(‘Train Epoch: {} [{}/{} ({:。0f}%)]\tLoss: {:。6f}’。format(
epoch, (batch_idx + 1) * len(data), len(train_loader。dataset),
100。 * (batch_idx + 1) / len(train_loader), loss。item()))
ave_loss = sum_loss / len(train_loader)
print(‘epoch:{},loss:{}’。format(epoch, ave_loss))
# 驗證過程
def val(model, device, test_loader):
model。eval()
test_loss = 0
correct = 0
total_num = len(test_loader。dataset)
print(total_num, len(test_loader))
with torch。no_grad():
for data, target in test_loader:
data, target = Variable(data)。to(device), Variable(target)。to(device)
output = model(data)
loss = criterion(output, target)
_, pred = torch。max(output。data, 1)
correct += torch。sum(pred == target)
print_loss = loss。data。item()
test_loss += print_loss
correct = correct。data。item()
acc = correct / total_num
avgloss = test_loss / len(test_loader)
print(‘\nVal set: Average loss: {:。4f}, Accuracy: {}/{} ({:。0f}%)\n’。format(
avgloss, correct, len(test_loader。dataset), 100 * acc))
# 訓練
for epoch in range(1, EPOCHS + 1):
adjust_learning_rate(optimizer, epoch)
train(model_ft, DEVICE, train_loader, optimizer, epoch)
val(model_ft, DEVICE, test_loader)
torch。save(model_ft, ‘model。pth’)
測試
我介紹兩種常用的測試方式,第一種是通用的,透過自己手動載入資料集然後做預測,具體操作如下:
測試集存放的目錄如下圖:
第一步 定義類別,這個類別的順序和訓練時的類別順序對應,一定不要改變順序!!!!
第二步 定義transforms,transforms和驗證集的transforms一樣即可,別做資料增強。
第三步 載入model,並將模型放在DEVICE裡,
第四步 讀取圖片並預測圖片的類別,在這裡注意,讀取圖片用PIL庫的Image。不要用cv2,transforms不支援。
import torch。utils。data。distributed
import torchvision。transforms as transforms
from PIL import Image
from torch。autograd import Variable
import os
classes = (‘Black-grass’, ‘Charlock’, ‘Cleavers’, ‘Common Chickweed’,
‘Common wheat’,‘Fat Hen’, ‘Loose Silky-bent’,
‘Maize’,‘Scentless Mayweed’,‘Shepherds Purse’,‘Small-flowered Cranesbill’,‘Sugar beet’)
transform_test = transforms。Compose([
transforms。Resize((224, 224)),
transforms。ToTensor(),
transforms。Normalize([0。5, 0。5, 0。5], [0。5, 0。5, 0。5])
])
DEVICE = torch。device(“cuda:0” if torch。cuda。is_available() else “cpu”)
model = torch。load(“model。pth”)
model。eval()
model。to(DEVICE)
path=‘data/test/’
testList=os。listdir(path)
for file in testList:
img=Image。open(path+file)
img=transform_test(img)
img。unsqueeze_(0)
img = Variable(img)。to(DEVICE)
out=model(img)
# Predict
_, pred = torch。max(out。data, 1)
print(‘Image Name:{},predict:{}’。format(file,classes[pred。data。item()]))
第二種 使用自定義的Dataset讀取圖片
import torch。utils。data。distributed
import torchvision。transforms as transforms
from dataset。dataset import SeedlingData
from torch。autograd import Variable
classes = (‘Black-grass’, ‘Charlock’, ‘Cleavers’, ‘Common Chickweed’,
‘Common wheat’,‘Fat Hen’, ‘Loose Silky-bent’,
‘Maize’,‘Scentless Mayweed’,‘Shepherds Purse’,‘Small-flowered Cranesbill’,‘Sugar beet’)
transform_test = transforms。Compose([
transforms。Resize((224, 224)),
transforms。ToTensor(),
transforms。Normalize([0。5, 0。5, 0。5], [0。5, 0。5, 0。5])
])
DEVICE = torch。device(“cuda:0” if torch。cuda。is_available() else “cpu”)
model = torch。load(“model。pth”)
model。eval()
model。to(DEVICE)
dataset_test =SeedlingData(‘data/test/’, transform_test,test=True)
print(len(dataset_test))
# 對應資料夾的label
for index in range(len(dataset_test)):
item = dataset_test[index]
img, label = item
img。unsqueeze_(0)
data = Variable(img)。to(DEVICE)
output = model(data)
_, pred = torch。max(output。data, 1)
print(‘Image Name:{},predict:{}’。format(dataset_test。imgs[index], classes[pred。data。item()]))
index += 1
完整的訓練程式碼
import torch。optim as optim
import torch
import torch。nn as nn
import torch。nn。parallel
import torch。optim
import torch。utils。data
import torch。utils。data。distributed
import torchvision。transforms as transforms
from models import convit_tiny
from torch。autograd import Variable
from mydatasets import SeedlingData
import os
# 設定全域性引數
modellr = 1e-4
BATCH_SIZE = 32
EPOCHS = 10
DEVICE = torch。device(‘cuda’ if torch。cuda。is_available() else ‘cpu’)
# 資料預處理
transform = transforms。Compose([
transforms。Resize((224, 224)),
transforms。ToTensor(),
transforms。Normalize([0。5, 0。5, 0。5], [0。5, 0。5, 0。5])
])
transform_test = transforms。Compose([
transforms。Resize((224, 224)),
transforms。ToTensor(),
transforms。Normalize([0。5, 0。5, 0。5], [0。5, 0。5, 0。5])
])
dataset_train = SeedlingData(‘data/train’, transforms=transform, train=True)
dataset_test = SeedlingData(“data/train”, transforms=transform_test, train=False)
# 讀取資料
print(dataset_train。imgs)
# 匯入資料
train_loader = torch。utils。data。DataLoader(dataset_train, batch_size=BATCH_SIZE, shuffle=True)
test_loader = torch。utils。data。DataLoader(dataset_test, batch_size=BATCH_SIZE, shuffle=False)
# 例項化模型並且移動到GPU
criterion = nn。CrossEntropyLoss()
model_ft = convit_tiny(pretrained=False, num_classes=12, embed_dim=48, drop_rate=0。5)
model_ft。to(DEVICE)
# 選擇簡單暴力的Adam最佳化器,學習率調低
optimizer = optim。Adam(model_ft。parameters(), lr=modellr)
def adjust_learning_rate(optimizer, epoch):
“”“Sets the learning rate to the initial LR decayed by 10 every 30 epochs”“”
modellrnew = modellr * (0。1 ** (epoch // 50))
print(“lr:”, modellrnew)
for param_group in optimizer。param_groups:
param_group[‘lr’] = modellrnew
# 定義訓練過程
def train(model, device, train_loader, optimizer, epoch):
model。train()
sum_loss = 0
total_num = len(train_loader。dataset)
print(total_num, len(train_loader))
for batch_idx, (data, target) in enumerate(train_loader):
data, target = Variable(data)。to(device), Variable(target)。to(device)
output = model(data)
loss = criterion(output, target)
optimizer。zero_grad()
loss。backward()
optimizer。step()
print_loss = loss。data。item()
sum_loss += print_loss
if (batch_idx + 1) % 10 == 0:
print(‘Train Epoch: {} [{}/{} ({:。0f}%)]\tLoss: {:。6f}’。format(
epoch, (batch_idx + 1) * len(data), len(train_loader。dataset),
100。 * (batch_idx + 1) / len(train_loader), loss。item()))
ave_loss = sum_loss / len(train_loader)
print(‘epoch:{},loss:{}’。format(epoch, ave_loss))
# 驗證過程
def val(model, device, test_loader):
model。eval()
test_loss = 0
correct = 0
total_num = len(test_loader。dataset)
print(total_num, len(test_loader))
with torch。no_grad():
for data, target in test_loader:
data, target = Variable(data)。to(device), Variable(target)。to(device)
output = model(data)
loss = criterion(output, target)
_, pred = torch。max(output。data, 1)
correct += torch。sum(pred == target)
print_loss = loss。data。item()
test_loss += print_loss
correct = correct。data。item()
acc = correct / total_num
avgloss = test_loss / len(test_loader)
print(‘\nVal set: Average loss: {:。4f}, Accuracy: {}/{} ({:。0f}%)\n’。format(
avgloss, correct, len(test_loader。dataset), 100 * acc))
# 訓練
for epoch in range(1, EPOCHS + 1):
adjust_learning_rate(optimizer, epoch)
train(model_ft, DEVICE, train_loader, optimizer, epoch)
val(model_ft, DEVICE, test_loader)
torch。save(model_ft, ‘model。pth’)
程式碼連結:
https://
download。csdn。net/downl
oad/hhhhhhhhhhwwwwwwwwww/20593432?spm=1001。2014。3001。5501
References
[1]
ConViT:使用軟卷積歸納偏置改進視覺變換器:
https://
blog。csdn。net/hhhhhhhhh
hwwwwwwwwww/article/details/119142228?spm=1001。2014。3001。5501