深入淺出孿生神經網絡,手把手教你搭建起來 原創(chuàng)
使用孿生神經網絡在PyTorch中實現簽名驗證的方法,以及在數據有限的情況下如何高效訓練模型。
在深度學習的領域里,神經網絡幾乎能處理各種任務,但通常需要依賴于海量數據來達到最佳效果。然而,對于像面部識別和簽名驗證這類任務,我們不可能總是有大量的數據可用。由此產生了一種新型的神經網絡架構,稱為孿生網絡。
孿生神經網絡能夠基于少量數據實現精準預測。本文介紹孿生神經網絡的基本概念,教讀者如何用PyTorch來搭建一個簽名驗證系統(tǒng)。
1 孿生神經網絡概述
孿生神經網絡由兩個或若干個結構和參數完全相同的子網絡構成,這些子網絡在更新參數時同步進行。
這種網絡通過分析輸入樣本的特征向量來評估它們的相似度,這一特性讓其在眾多應用領域中發(fā)揮著重要作用。
傳統(tǒng)神經網絡一般被訓練用于識別多個不同的類別。當數據集中需要添加或移除類別時,這些網絡通常需要經過調整和重新訓練,這個過程不僅耗時,而且通常需要大量的數據支持。
而孿生網絡專注于學習樣本之間的相似性,能夠輕松識別圖像是否相似。這種能力使孿生網絡在處理新類別數據時,無需重新訓練即可進行有效的分類,提供了一種靈活且高效的解決方案。
1.1 優(yōu)劣勢
優(yōu)勢:
- 對類別失衡的適應能力更強:通過一次性學習,孿生網絡僅需少量樣本即可有效識別圖像類別,具有良好的魯棒性和適應性。
- 與頂級分類器的協(xié)同效應:由于其獨特的學習機制,與傳統(tǒng)分類方法相比,孿生網絡與最佳分類器(如GBM和隨機森林)的結合能夠帶來更佳的性能表現。
- 學習語義相似性:孿生網絡專注于深層特征的嵌入學習,將相同類別或概念的樣本聚集在一起,從而能夠捕捉和學習它們之間的語義相似性。
劣勢:
- 訓練耗時較長:孿生網絡采用成對學習機制,需要綜合考慮所有可用信息,因此其訓練過程比逐點學習的分類網絡更為耗時。
- 缺乏概率輸出:孿生網絡的訓練過程專注于成對比較,它不提供預測結果的概率值,而是直接輸出各類別之間的相對距離。
1.2 孿生網絡中使用的損失函數
在孿生網絡的訓練過程中,由于采用的是成對學習方式,傳統(tǒng)的交叉熵損失函數不再適用。目前,主要有兩種損失函數用于訓練這類網絡:
1)三元組損失
三元組是一種特殊的損失函數,它通過比較基線(錨點)輸入與正樣本(真實輸入)和負樣本(虛假輸入)之間的差異來工作。其目標是最小化錨點與正樣本之間的距離,同時最大化錨點與負樣本之間的距離,從而確保網絡能夠準確區(qū)分不同類別的樣本。
在上述方程中,α是控制間隔的參數,用于調整相似樣本對與不相似樣本對之間的距離差異;f(A)、f(P)、f(N)分別代表錨點、正樣本和負樣本的特征嵌入。
訓練時,將由錨點圖像、負樣本圖像和正樣本圖像組成的三元組作為單一樣本輸入到模型中。這樣做的目的是確保錨點與正樣本圖像之間的距離小于錨點與負樣本圖像之間的距離。
2)對比損失
對比損失是當前非常流行的損失函數,它基于距離而非傳統(tǒng)的錯誤預測來計算損失。該損失函數主要應用于特征嵌入的學習,目標是優(yōu)化樣本點在歐幾里得空間(Euclidean space)的分布:對于相似的樣本點,我們希望它們之間的距離盡可能地小,以便能夠準確捕捉它們之間的相似性;對于不相似的樣本點,則希望它們之間的距離足夠大,以便于區(qū)分差異。
定義Dw是歐幾里得距離(Euclidean distance):
Gw是網絡對一張圖像的輸出結果。
2 孿生網絡在簽名驗證領域的應用
孿生網絡具有優(yōu)秀的相似性比較能力,常被應用于需要驗證身份的系統(tǒng)中,例如面部識別和簽名驗證。
接下來,將在PyTorch框架下構建一個基于孿生神經網絡的簽名驗證系統(tǒng),實現高精度的身份驗證功能。
2.1 數據集和數據集預處理
使用ICDAR 2011數據集進行簽名驗證,該數據集收錄了荷蘭用戶的簽名樣本,包括真實和偽造的簽名。數據集分為訓練和測試部分,每個部分都有用戶的真實與偽造簽名文件夾。數據集的標簽信息以CSV文件的形式提供。
ICDAR 數據集中的簽名
在把這些原始數據輸入神經網絡之前,需要將圖像轉換為張量,并整合CSV中的標簽。這可以通過PyTorch的自定義數據集類實現,以下是代碼示例。
#數據預處理和加載
class SiameseDataset():
def __init__(self,training_csv=None,training_dir=None,transform=None):
# used to prepare the labels and images path
self.train_df=pd.read_csv(training_csv)
self.train_df.columns =["image1","image2","label"]
self.train_dir = training_dir
self.transform = transform
def __getitem__(self,index):
# 獲取圖像路徑
image1_path=os.path.join(self.train_dir,self.train_df.iat[index,0])
image2_path=os.path.join(self.train_dir,self.train_df.iat[index,1])
# Loading the image
img0 = Image.open(image1_path)
img1 = Image.open(image2_path)
img0 = img0.convert("L")
img1 = img1.convert("L")
# 應用圖像變換
if self.transform is not None:
img0 = self.transform(img0)
img1 = self.transform(img1)
return img0, img1 , th.from_numpy(np.array([int(self.train_df.iat[index,2])],dtype=np.float32))
def __len__(self):
return len(self.train_df)
預處理數據集后,需要在PyTorch框架中使用Dataloader類來加載這些數據。為了適應計算需求,通過transforms函數對圖像進行尺寸調整,將其高度和寬度統(tǒng)一縮減至105像素。
# 從原始圖像文件夾加載數據集
siamese_dataset = SiameseDataset(training_csv,training_dir,
transform=transforms.Compose([transforms.Resize((105,105)),
transforms.ToTensor()
])
)
2.2 神經網絡架構
現在在PyTorch中創(chuàng)建一個神經網絡。
#創(chuàng)建孿生網絡
class SiameseNetwork(nn.Module):
def __init__(self):
super(SiameseNetwork, self).__init__()
# 設置CNN層序列
self.cnn1 = nn.Sequential(
nn.Conv2d(1, 96, kernel_size=11,stride=1),
nn.ReLU(inplace=True),
nn.LocalResponseNorm(5,alpha=0.0001,beta=0.75,k=2),
nn.MaxPool2d(3, stride=2),
nn.Conv2d(96, 256, kernel_size=5,stride=1,padding=2),
nn.ReLU(inplace=True),
nn.LocalResponseNorm(5,alpha=0.0001,beta=0.75,k=2),
nn.MaxPool2d(3, stride=2),
nn.Dropout2d(p=0.3),
nn.Conv2d(256,384 , kernel_size=3,stride=1,padding=1),
nn.ReLU(inplace=True),
nn.Conv2d(384,256 , kernel_size=3,stride=1,padding=1),
nn.ReLU(inplace=True),
nn.MaxPool2d(3, stride=2),
nn.Dropout2d(p=0.3),
)
# 定義全連接層
self.fc1 = nn.Sequential(
nn.Linear(30976, 1024),
nn.ReLU(inplace=True),
nn.Dropout2d(p=0.5),
nn.Linear(1024, 128),
nn.ReLU(inplace=True),
nn.Linear(128,2))
def forward_once(self, x):
# 前向傳播
output = self.cnn1(x)
output = output.view(output.size()[0], -1)
output = self.fc1(output)
return output
def forward(self, input1, input2):
# 輸入1的前向傳播
output1 = self.forward_once(input1)
# 輸入2的前向傳播
output2 = self.forward_once(input2)
return output1, output2
在代碼實現中,構建了這樣的神經網絡結構:首層卷積層配備了96個11x11大小的卷積核,步長為1像素,用于處理105x105像素的輸入簽名圖像。緊接著的第二層卷積層,以第一層的輸出(經過響應歸一化和池化處理)為輸入,使用256個5x5大小的卷積核進行特征提取。
第三層和第四層卷積層之間不涉及任何池化或歸一化操作。第三層使用384個3x3大小的卷積核,直接連接到第二層卷積層的輸出(該輸出已經經過歸一化、池化和dropout處理)。第四層卷積層則包含256個3x3大小的卷積核。這樣的設計使網絡能夠學習到較少的低級特征,以適應更小的感受野,同時捕捉到更多的高級或抽象特征。
網絡中的第一層全連接層包含1024個神經元,而第二層全連接層則有128個神經元。
通過限制兩個網絡共享相同的權重,實現了使用單一模型連續(xù)處理兩張圖像的策略。這種方法不僅節(jié)省了內存空間,還提升了計算效率。具體操作中,對這兩張圖像計算損失值,并執(zhí)行反向傳播來更新權重。
2.3 損失函數
對于這項任務,使用對比損失函數來學習特征嵌入,其目標是讓相似的樣本點在歐幾里得空間中的距離盡可能小,而不相似的樣本點則保持較大的距離。以下是在PyTorch框架中實現對比損失函數的方法。
class ContrastiveLoss(torch.nn.Module):
"""
Contrastive loss function.
Based on:
"""
def __init__(self, margin=1.0):
super(ContrastiveLoss, self).__init__()
self.margin = margin
def forward(self, x0, x1, y):
# 歐幾里得距離
diff = x0 - x1
dist_sq = torch.sum(torch.pow(diff, 2), 1)
dist = torch.sqrt(dist_sq)
mdist = self.margin - dist
dist = torch.clamp(mdist, min=0.0)
loss = y * dist_sq + (1 - y) * torch.pow(dist, 2)
loss = torch.sum(loss) / 2.0 / x0.size()[0]
return loss
2.4 訓練網絡
孿生網絡的訓練過程如下:
- 初始化網絡、損失函數和優(yōu)化器。
- 通過網絡傳遞圖像對的第一張圖像。
- 通過網絡傳遞圖像對的第二張圖像。
- 使用第一張和第二張圖像的輸出計算損失。
- 將損失反向傳播以計算模型的梯度。
- 使用優(yōu)化器更新權重。
- 保存模型。
# 聲明孿生網絡
net = SiameseNetwork().cuda()
# 聲明損失函數
criterion = ContrastiveLoss()
# 聲明優(yōu)化器
optimizer = th.optim.Adam(net.parameters(), lr=1e-3, weight_decay=0.0005)
#訓練模型
def train():
loss=[]
counter=[]
iteration_number = 0
for epoch in range(1,config.epochs):
for i, data in enumerate(train_dataloader,0):
img0, img1 , label = data
img0, img1 , label = img0.cuda(), img1.cuda() , label.cuda()
optimizer.zero_grad()
output1,output2 = net(img0,img1)
loss_contrastive = criterion(output1,output2,label)
loss_contrastive.backward()
optimizer.step()
print("Epoch {}\n Current loss {}\n".format(epoch,loss_contrastive.item()))
iteration_number += 10
counter.append(iteration_number)
loss.append(loss_contrastive.item())
show_plot(counter, loss)
return net
#將設備設置為cuda
device = torch.device('cuda' if th.cuda.is_available() else 'cpu')
model = train()
torch.save(model.state_dict(), "model.pt")
print("Model Saved Successfully")
該模型在Google Colab上訓練了20個周期,持續(xù)一個小時,以下是隨時間變化的損失圖:
2.5 測試模型
在測試數據集上測試我們的簽名驗證系統(tǒng)。
- 使用Pytorch的DataLoader類加載測試數據集
- 傳遞圖像對和標簽
- 找到圖像之間的歐幾里得距離
- 根據歐幾里得距離輸出結果
# 加載測試數據集
test_dataset = SiameseDataset(training_csv=testing_csv,training_dir=testing_dir,
transform=transforms.Compose([transforms.Resize((105,105)),
transforms.ToTensor()
])
)
test_dataloader = DataLoader(test_dataset,num_workers=6,batch_size=1,shuffle=True)
#測試網絡
count=0
for i, data in enumerate(test_dataloader,0):
x0, x1 , label = data
concat = torch.cat((x0,x1),0)
output1,output2 = model(x0.to(device),x1.to(device))
eucledian_distance = F.pairwise_distance(output1, output2)
if label==torch.FloatTensor([[0]]):
label="Original Pair Of Signature"
else:
label="Forged Pair Of Signature"
imshow(torchvision.utils.make_grid(concat))
print("Predicted Eucledian Distance:-",eucledian_distance.item())
print("Actual Label:-",label)
count=count+1
if count ==10:
break
預測結果如下,
本文轉載自??AI科技論談??,作者: AI科技論談
