在上一篇文章中,談到了深度學(xué)習(xí)是什么以及在 FPGA 上進行深度學(xué)習(xí)的好處。在本課程的后續(xù)文章中,我們將開始開發(fā)針對 FPGA 的深度學(xué)習(xí)設(shè)計。特別是,在本文中,我們將首先在 Python 上運行訓(xùn)練代碼,并創(chuàng)建一個網(wǎng)絡(luò)模型方便在后續(xù) FPGA 上運行。
在后續(xù)文章中,我們將根據(jù)實際源碼進行講解。稍后將提供包括 FPGA 設(shè)計在內(nèi)的所有代碼。
要使用的數(shù)據(jù)集工具
MNIST 數(shù)據(jù)庫
本課程針對的問題是通常稱為圖像分類的任務(wù)。在這個任務(wù)中,我們輸出一個代表輸入圖像的標(biāo)簽。這里的標(biāo)簽例如是圖像中物體的通用名稱。
MNIST 數(shù)據(jù)庫是一個包含 0 到 9 的手寫數(shù)字的數(shù)據(jù)集,并為每個手寫數(shù)字定義了正確的標(biāo)簽 (0-9)。由于輸入是一張28x28的灰度圖,輸出最多10個類,在圖像分類任務(wù)中屬于非常初級的任務(wù)。除了 MNIST 之外,主要的圖像分類數(shù)據(jù)集包括CIFAR10和ImageNet,并且往往會按上訴列出的順序成為更難的問題。
MNIST 在許多深度學(xué)習(xí)教程中都有使用,因為它是一個非常簡單的數(shù)據(jù)集。由于本課程的主要目的是在 FPGA 上實現(xiàn)深度學(xué)習(xí),因此我們將創(chuàng)建一個針對 MNIST 的學(xué)習(xí)模型。
MNIST 數(shù)據(jù)庫——維基百科
PyTorch
PyTorch是來自 Facebook 的開源深度學(xué)習(xí)框架,也是與來自 Google 的TensorFlow一起使用最頻繁的框架之一。在本文中,我們將在 PyTorch 上學(xué)習(xí)和創(chuàng)建網(wǎng)絡(luò)模型。
PyTorch安裝參考官網(wǎng)步驟。我使用的 Ubuntu 16.04 LTS 上安裝的 Python 3.5 不支持最新的 PyTorch,所以我使用以下命令安裝了舊版本。
pipinstalltorch==1.4.0torchvision==0.5.0
由于本文的重點不在于如何使用PyTorch,所以我將省略代碼的解釋,尤其是學(xué)習(xí)部分。如果有興趣,建議嘗試下面的官方教程,盡管它是英文的。
卷積神經(jīng)網(wǎng)絡(luò) (CNN) 是一種旨在在圖像任務(wù)中表現(xiàn)出色的神經(jīng)網(wǎng)絡(luò)。本文創(chuàng)建的網(wǎng)絡(luò)模型是一個卷積神經(jīng)網(wǎng)絡(luò),該網(wǎng)絡(luò)由以下三層和激活函數(shù)的組合組成。
全連接層
卷積層
池化層
激活函數(shù)
下面概述了每一層的處理和作用。
全連接層
全連接層是通過將輸入向量乘以內(nèi)部參數(shù)矩陣來輸出向量的過程。如果只說神經(jīng)網(wǎng)絡(luò)沒有前言比如卷積,往往指的就是這個全連接層。
由于全連接層的輸入是向量,所以無法得到圖像中應(yīng)該注意的與周圍像素點的關(guān)系。在卷積神經(jīng)網(wǎng)絡(luò)中,全連接層主要用于將卷積層和池化層創(chuàng)建的壓縮圖像信息(特征)轉(zhuǎn)換為標(biāo)簽數(shù)據(jù)(0-9)。
卷積層
卷積層是對圖像進行卷積處理的層。如果熟悉圖像處理,可能會稱其為濾鏡處理。
在卷積層中,對于輸入圖像中的每個像素,獲取周圍像素的像素值,每個像素值乘以一個數(shù)組(kernel),它是一個內(nèi)參,求和就是像素值輸出圖像。這以圖形方式表示,如下所示。
卷積神經(jīng)網(wǎng)絡(luò)的許多層都是由這個卷積層組成的,因為它以這種方式針對圖像處理。
池化層
池化層是用于壓縮由卷積層獲得的信息的層。使用本次使用的參數(shù),通過取圖像中2x2塊的最大值將圖像大小減半。下圖顯示了最大值的減少處理。
通過應(yīng)用多個池化層,聚合圖像每個部分的信息,并接近表示最終圖像整體的標(biāo)簽信息。
激活函數(shù)
激活函數(shù)是一個非線性函數(shù),插入在卷積層和全連接層之后。這是為了避免將實際上是線性函數(shù)的兩層合并為一層。已經(jīng)提出了各種類型的激活函數(shù),但近年來 ReLU 是主要使用的激活函數(shù)。
ReLU 是 Rectified Linear Unit 的縮寫。該函數(shù)會將小于0的輸入值置為0,大于0的值原樣輸出。
創(chuàng)建網(wǎng)絡(luò)模型
使用的網(wǎng)絡(luò)模型是著名網(wǎng)絡(luò)模型LeNet的簡化版。LeNet 是早期的卷積神經(jīng)網(wǎng)絡(luò)之一,和這個一樣,都是針對手寫識別的。
本文使用的模型定義如下。與原來的LeNet相比,有一些不同之處,例如卷積層的核大小減小,激活函數(shù)為ReLU。
classNet(nn.Module): def__init__(self,num_output_classes=10): super(Net,self).__init__() self.conv1=nn.Conv2d(in_channels=1,out_channels=4,kernel_size=3,padding=1) #激活函數(shù)ReLU self.relu1=nn.ReLU(inplace=True) # self.pool1=nn.MaxPool2d(kernel_size=2,stride=2) #4ch->8ch,14x14->7x7 self.conv2=nn.Conv2d(in_channels=4,out_channels=8,kernel_size=3,padding=1) self.relu2=nn.ReLU(inplace=True) self.pool2=nn.MaxPool2d(kernel_size=2,stride=2) #全連接層 self.fc1=nn.Linear(8*7*7,32) self.relu3=nn.ReLU(inplace=True) #全連接層 self.fc2=nn.Linear(32,num_output_classes) defforward(self,x): #激活函數(shù)ReLU x=self.conv1(x) x=self.relu1(x) #縮小 x=self.pool1(x) #2層+縮小 x=self.conv2(x) x=self.relu2(x) x=self.pool2(x) #(Batch,Ch,Height,Width)->(Batch,Ch) x=x.view(x.shape[0],-1) #全連接層 x=self.fc1(x) x=self.relu3(x) x=self.fc2(x) returnx
使用netron可視化此模型的數(shù)據(jù)流如下所示:
按照數(shù)據(jù)的順序流動,首先輸入一張手寫圖像(28×28, 1ch),在第一個Conv2d層轉(zhuǎn)換為(28×28, 4ch)的圖像,通過ReLU變成非負(fù)的。
然后通過 Maxpool2d 層將該圖像縮小為 (14×14, 4ch) 圖像。隨后的 Conv2d、ReLU、MaxPool2d 遵循幾乎相同的程序,生成 (7×7, 8ch) 圖像。將此圖像視為7x7x8 = 392度的向量,應(yīng)用兩次全連接層最終將輸出10度的向量。此 10 階向量中具有最大值的元素的索引 (argmax) 是推斷字符 (0-9)。
學(xué)習(xí)/推理
使用上述模型訓(xùn)練 MNIST。這里需要執(zhí)行三個步驟:
加載訓(xùn)練/測試數(shù)據(jù)
循環(huán)學(xué)習(xí)
測試(推理)
首先,讀取數(shù)據(jù)。PyTorch 帶有一個預(yù)定義的 MNIST 加載器,我們用它來加載數(shù)據(jù)(訓(xùn)練集/測試集)。trainloader/testloader 是定義如何讀取每個數(shù)據(jù)集中數(shù)據(jù)的對象。
importtorch importtorchvision importtorchvision.transformsastransforms #2.定義數(shù)據(jù)集讀取方法 #獲取MNIST的學(xué)習(xí)測試數(shù)據(jù) trainset=torchvision.datasets.MNIST(root='./data',train=True,download=True,transform=transforms.ToTensor()) testset=torchvision.datasets.MNIST(root='./data',train=False,download=True,transform=transforms.ToTensor()) #數(shù)據(jù)讀取方法的定義 #1步驟的每個學(xué)習(xí)測試讀取16張圖像 trainloader=torch.utils.data.DataLoader(trainset,batch_size=16,shuffle=True) testloader=torch.utils.data.DataLoader(testset,batch_size=16,shuffle=False)
現(xiàn)在數(shù)據(jù)讀取已經(jīng)完成,是時候開始學(xué)習(xí)了。首先,我們定義損失函數(shù)(誤差函數(shù))和優(yōu)化器。在后續(xù)的學(xué)習(xí)中,我們會朝著損失值減小的方向?qū)W習(xí)。
#損失函數(shù),最優(yōu)化器的定義 loss_func=nn.CrossEntropyLoss() optimizer=torch.optim.Adam(net.parameters(),lr=0.0001)
學(xué)習(xí)循環(huán)像這樣:從trainloader讀取輸入數(shù)據(jù)(inputs),糾正標(biāo)簽(labels),通過網(wǎng)絡(luò)得到輸出(outputs),得到帶有正確標(biāo)簽的error(loss)。
然后我們使用一種稱為誤差反向傳播 (loss.backward()) 的技術(shù)來計算每一層的學(xué)習(xí)方向(梯度)。優(yōu)化器使用獲得的梯度來優(yōu)化模型(optimizer.step())。這是一步學(xué)習(xí)流程,在此代碼中,重復(fù)學(xué)習(xí)直到夠 10 個數(shù)據(jù)集。
由于我們將使用 FPGA 進行推理處理,因此即使不了解此處描述的學(xué)習(xí)過程也沒有問題。如果你對我們?yōu)槭裁催@樣學(xué)習(xí)感興趣,請參考上一篇文章中一些通俗易懂的文獻。
#循環(huán)直到使用數(shù)據(jù)集中的所有圖像10次 forepochinrange(10): running_loss=0 #在數(shù)據(jù)集中循環(huán) fori,datainenumerate(trainloader,0): #導(dǎo)入輸入批(圖像,正確標(biāo)簽) inputs,labels=data #零初始化優(yōu)化程序 optimizer.zero_grad() #通過模型獲取輸入圖像的輸出標(biāo)簽 outputs=net(inputs) #與正確答案的誤差計算+誤差反向傳播 loss=loss_func(outputs,labels) loss.backward() #使用誤差優(yōu)化模型 optimizer.step() running_loss+=loss.item() ifi%1000==999: print('[%d,%5d]loss:%.3f'% (epoch+1,i+1,running_loss/1000)) running_loss=0.0
測試代碼如下所示:該測試與學(xué)習(xí)循環(huán)幾乎相同,但省略了學(xué)習(xí)的損失計算。scikit-learn我們還使用這里的函數(shù)來accuracy_score, confusion_matrix輸出準(zhǔn)確度和混淆矩陣。
fromsklearn.metricsimportaccuracy_score,confusion_matrix #4.測試 ans=[] pred=[] fori,datainenumerate(testloader,0): inputs,labels=data outputs=net(inputs) ans+=labels.tolist() pred+=torch.argmax(outputs,1).tolist() print('accuracy:',accuracy_score(ans,pred)) print('confusionmatrix:') print(confusion_matrix(ans,pred))
通過到目前為止的實施,可以在 PyTorch 上訓(xùn)練和推斷 MNIST。當(dāng)我在 i7 CPU 上實際運行上述代碼時,該過程在大約 3 分鐘內(nèi)完成。日志在下面。
[n, m] loss: X這條線表示第n個epoch(使用數(shù)據(jù)集的次數(shù))和學(xué)習(xí)數(shù)據(jù)集中第m個數(shù)據(jù)時的損失(錯誤)??梢钥闯?,隨著學(xué)習(xí)的進行,損失幾乎接近于 0。即使是這種非常小的網(wǎng)絡(luò)模型也可以成功學(xué)習(xí)。
測試準(zhǔn)確率最終顯示出足夠高的準(zhǔn)確率,達到 97.26%。接下來的矩陣是混淆矩陣(confusion_matrix),其中第i行第j列的值代表正確答案i和推理結(jié)果j。這次數(shù)據(jù)的準(zhǔn)確性足夠了,結(jié)果似乎沒有什么明顯的偏差。這一次,我們一次性得到了足夠的準(zhǔn)確率,但是我們在原來的開發(fā)中并沒有得到我們想要的準(zhǔn)確率,所以我們在這里回顧一下模型和學(xué)習(xí)方法。
[1,1000]loss:1.765 [1,2000]loss:0.655 [1,3000]loss:0.475 ... [10,1000]loss:0.106 [10,2000]loss:0.101 [10,3000]loss:0.104 accuracy:0.9726 confusionmatrix: [[973010020220] [0112821002020] [3399780127101] [10696801205108] [1030960012213] [310508702074] [1021178927020] [1390100100617] [51112433892710] [56031010104970]]
最后,使用以下代碼保存模型。前者是從 PyTorch 重新評估的模型文件,后者用于在 PyTorch 的 C++ API(libtorch)上讀取網(wǎng)絡(luò)模型。
#5.保存模型 #用于從PyTorch正常讀取的模型文件 torch.save(net.state_dict(),'model.pt') #保存用于從libtorch(C++API)讀取的TorchScriptModule example=torch.rand(1,1,28,28) traced_script_module=torch.jit.trace(net,example) traced_script_module.save('traced_model.pt')
總結(jié)
在本文中,我們使用 MNIST 數(shù)據(jù)集作為學(xué)習(xí)目標(biāo)來創(chuàng)建、訓(xùn)練和推斷網(wǎng)絡(luò)模型。
使用基于 LeNet 的輕量級網(wǎng)絡(luò)模型,我們能夠在 MNIST 數(shù)據(jù)集上實現(xiàn)良好的準(zhǔn)確性。在下一篇和后續(xù)文章中,我們的目標(biāo)是在 FPGA 上運行該模型,并在考慮高級綜合(HLS)的情況下開始 C++ 實現(xiàn)。
審核編輯:劉清
-
FPGA
+關(guān)注
關(guān)注
1629文章
21750瀏覽量
604058 -
數(shù)據(jù)庫
+關(guān)注
關(guān)注
7文章
3817瀏覽量
64485 -
python
+關(guān)注
關(guān)注
56文章
4797瀏覽量
84787 -
Ubuntu系統(tǒng)
+關(guān)注
關(guān)注
0文章
91瀏覽量
3986 -
MNIST
+關(guān)注
關(guān)注
0文章
10瀏覽量
3386
原文標(biāo)題:從FPGA說起的深度學(xué)習(xí)(二)
文章出處:【微信號:Open_FPGA,微信公眾號:OpenFPGA】歡迎添加關(guān)注!文章轉(zhuǎn)載請注明出處。
發(fā)布評論請先 登錄
相關(guān)推薦
評論