上次寫了一篇CNN的詳解,可是累壞了老僧我。寫完后拿給朋友看,朋友說你這Pytorch的實現(xiàn)方式對于新人來講會很不友好,然后反問我說里面所有的細節(jié)你都明白了嗎。我想想,的確如此。那個源碼是我當時《動手學pytorch》的時候整理的,里面有很多包裝過的函數(shù),對于新入門的人來講,的確是個大問題。于是,痛定思痛的我決定重新寫Pytorch實現(xiàn)這一部分,理論部分我就不多講了,咱們直接分析代碼,此代碼是來自Pytorch官方給出的LeNet Model。你可以使用Jupyter Notebook一行一行的學習,也可以使用Pycharm進行斷點訓(xùn)練和Debug來學習。
沒有看過理論部分的同學可以看我上篇文章:一文帶你了解CNN(卷積神經(jīng)網(wǎng)絡(luò))。
在整個講解的過程中,其中的一些比較重要的代碼我會引入一些例子來進行解釋它的功能,如果你想先直接跑通代碼,可以直接跳到代碼匯總部分,Here we go~
這是一個對于彩色圖的10分類的問題,具體種類有:'plane', 'car', 'bird', 'cat','deer', 'dog', 'frog', 'horse', 'ship', 'truck',訓(xùn)練一個能夠?qū)ζ溥M行分類的分類器。
這一部分咱們就不說太多了吧,直接上code:
import torch # 張量的有關(guān)運算,如創(chuàng)建、索引、連接、轉(zhuǎn)置....和numpy的操作很像 import torch.nn as nn # 八廓搭建神經(jīng)網(wǎng)絡(luò)層的模塊、loss等等 import torch.nn.functional as F # 常用的激活函數(shù)都在這里面 import torchvision # 專門處理圖像的庫 import torch.optim as optim # 各種參數(shù)優(yōu)化方法,SGD、Adam... import torchvision.transforms as transforms # 提供了一般的圖像轉(zhuǎn)換操作的類,也可以用于圖像增強 import matplotlib.pyplot as plt import numpy as np
我們在定義自己網(wǎng)絡(luò)的時候,需要繼承nn.Module類,并重新實現(xiàn)構(gòu)造函數(shù)__init__和forward兩個方法。forward方法是必須要重寫的,它是實現(xiàn)模型的功能,實現(xiàn)各個層之間的連接關(guān)系的核心。如果你是用我下面的這個方法來定義的模型,在forward中要去連接它們之間的關(guān)系;如果你是用Sequential的方法來定義的模型,一般來講可以直接在構(gòu)造函數(shù)定義好后,在foward函數(shù)中return就行了(如果模型比較復(fù)雜就另當別論)。
class LeNet(nn.Module): """ 下面這個模型定義沒有用Sequential來定義,Sequential的定義方法能夠在init中就給出各個層 之間的關(guān)系,我這里是根據(jù)是否有可學習的參數(shù)。我將可學習參數(shù)的層(如全連接、卷積)放在構(gòu)造函數(shù) 中(其實你想把不具有參數(shù)的層放在里面也可以),把不具有學習參數(shù)的層(如dropout, ReLU等激活函數(shù)、BN層)放在forward。 """ def __init__(self): super(LeNet,self).__init__() # 第一個卷積塊,這里輸入的是3通道,彩色圖。 self.conv1 = nn.Conv2d(3,16,5) self.pool1 = nn.MaxPool2d(2,2) # 第二個卷積塊 self.conv2 = nn.Conv2d(16,32,5) self.pool2 = nn.MaxPool2d(2,2) # 稠密塊,包含三個全連接層 self.fc1 = nn.Linear(32*5*5,120) self.fc2 = nn.Linear(120,84) self.fc3 = nn.Linear(84,10) pass def forward(self,x): # x是輸入數(shù)據(jù),是一個tensor # 正向傳播 x = F.relu(self.conv1(x)) # input(3, 32, 32) output(16, 28, 28) x = self.pool1(x) # output(16, 14, 14) x = F.relu(self.conv2(x)) # output(32, 10, 10) x = self.pool2(x) # output(32, 5, 5) x = x.view(-1, 32*5*5) # output(32*5*5) # 數(shù)據(jù)通過view展成一維向量,第一個參數(shù)-1是batch,自動推理;32x5x5是展平后的個數(shù) x = F.relu(self.fc1(x)) # output(120) x = F.relu(self.fc2(x)) # output(84) x = self.fc3(x) # output(10) # 為什么沒有用softmax函數(shù) --- 在網(wǎng)絡(luò)模型中已經(jīng)計算交叉熵以及概率 return x
我們還可以隨便看一下可訓(xùn)練參數(shù):
model = LeNet() for name,parameters in model.named_parameters(): if param.requires_grad: print(name,':',parameters.size())
看一下實例化的模型:
import torch input1 = torch.rand([32,3,32,32]) model = LeNet() # 模式實例化 print(model) # 看一下模型結(jié)構(gòu) output = model(input1)
這里就不再拓展了,我發(fā)4我發(fā)4,我會專門再寫一篇使用pytorch查看特征矩陣 和卷積核參數(shù)的文章。
# 調(diào)用設(shè)備內(nèi)的GPU并打印出來 device = torch.device("cuda:0" if torch.cuda.is_available() else "cpu") print("using {} device.".format(device)) # 定義圖像數(shù)據(jù)的數(shù)據(jù)預(yù)處理方式 transform = transforms.Compose( [transforms.ToTensor(), transforms.Normalize((0.5, 0.5, 0.5), (0.5, 0.5, 0.5))]) # 如果是第一次運行代碼,沒有下載數(shù)據(jù)集,則將download調(diào)制為True進行下載,并加載訓(xùn)練集 # transform是選擇數(shù)據(jù)預(yù)處理的方式,我們已經(jīng)提前定義 train_set = torchvision.datasets.CIFAR10(root='./data', train=True, download=False,transform=transform) # 如果你是windows系統(tǒng),一定要記得把num_workers設(shè)置為0,不然會報錯。 # 這個是將數(shù)據(jù)集劃為為n個批次,每個批次的數(shù)據(jù)集有batchSize張圖片,shuffle是打亂數(shù)據(jù)集 train_loader = torch.utils.data.DataLoader(train_set, batch_size=36, shuffle=True, num_workers=0) # 上面已經(jīng)下載過的話,download設(shè)置為False val_set = torchvision.datasets.CIFAR10(root='./data', train=False, download=False, transform=transform) # 驗證集不用打亂,把batchsize設(shè)置為1,每次拿出1張來驗證 val_loader = torch.utils.data.DataLoader(val_set, batch_size=1 shuffle=False, num_workers=0) # 定義classes類別 classes = ('plane', 'car', 'bird', 'cat', 'deer', 'dog', 'frog', 'horse', 'ship', 'truck') val_data_iter = iter(val_loader) # 轉(zhuǎn)換成可迭代的迭代器 val_image, val_label = val_data_iter.next() # 定義imshow函數(shù)顯示圖像 def imshow(img): img = img / 2 + 0.5 # unnormalize -> 反標準化處理 npimg = img.numpy() # numpy和tensor的通道順序不同 tensor是通道度、寬度,numpy是高、寬、通 # 使用transpose調(diào)整維度 plt.imshow(np.transpose(npimg, (1, 2, 0))) #(1,2,0)-> 代表高度、寬度 通道 plt.show() imshow(torchvision.utils.make_grid(val_image)) # 顯示圖像結(jié)果:
在這個圖像加載部分,我做了些其它的嘗試,想要去發(fā)現(xiàn)train_set和train_loader之間的不同。這里你可以逐行取消我注釋的代碼,然后去觀察,去對比,你就知道有哪些不一樣了。
""" train_set: 總結(jié):經(jīng)過多次嘗試,發(fā)現(xiàn)train_set是用一個Dataset包裝起來,用索引來提取第n個數(shù)據(jù),提出的數(shù)據(jù)是一個元組。 元組的第一個索引是Tensor的圖像數(shù)據(jù),(channel,height,width),索引的第二個數(shù)據(jù)是標簽 int類型。 可以選擇用enumerate迭代器,也可以直接進行索引,這里因為沒有batchsize的維度,所以可以直接調(diào)用自己寫的 imshow函數(shù)來顯示圖片 """ for i,data in enumerate(train_set): if i == 7: # imshow(data[0]) # print(data[0]) # print(train_set[i][0]) # 查看train-set第七張圖元組 的 索引0 print(train_set[i][0].shape) print(train_set[i][1]) # 查看train-set第七張圖元組 的 索引1 # imshow(train_set[i][0]) print(type(train_set[i][1])) # print(train_set[i].shape) print(data[0]) print(data[0].shape) # print(type(data[i]))
""" train_loader 總結(jié):和Dataset類型不一樣,DataLoader不能夠直接用索引獲取數(shù)據(jù)。需要用enumerate迭代器來獲取 或者 iter. 經(jīng)過enumerate索引后,得到的data類型是擁有兩個變量的列表類型。第一個變量是Tensor類型,用[batchSize,channel,height,width]表示 批圖像數(shù)據(jù),里面是有batchsize張圖的。第二個變量也是Tensor類型,是代表每張圖像的標簽,是個一維torch """ for i,data in enumerate(train_loader): if i == 7: print(type(data)) print(len(data)) print(type(data[0])) print(type(data[1])) print(data[0].shape) print(data[1].shape) print(type(data[1])) # print(data[0]) print(data[1]) # print(type(data[2]))
# 用GPU訓(xùn)練 import time torch.cuda.synchronize() start = time.time() net = LeNet() net.to(device) #使用GPU時把網(wǎng)絡(luò)分配到指定的device中 loss_function = nn.CrossEntropyLoss() optimizer = optim.Adam(net.parameters(),lr=0.001) # Adam優(yōu)化器 Loss = [] for epoch in range(5): # 這里就只訓(xùn)練5個epoch,你可以試試多個 running_loss = 0.0 for step,data in enumerate(train_loader,start=0): inputs,labels = data # data是一個列表,[數(shù)據(jù),標簽] # 清除歷史梯度,加快訓(xùn)練 optimizer.zero_grad() outputs = net(inputs.to(device)) # 將輸入的數(shù)據(jù)分配到指定的GPU中 loss = loss_function(outputs,labels.to(device)) # 將labels分配到指定的device loss.backward() # loss進行反向傳播 optimizer.step() # step進行參數(shù)更新 # 打印數(shù)據(jù) running_loss += loss.item() # 每次計算完loss后加入到running_loss中 if step % 500 == 499: # 每500個mini-batches 就打印一次 with torch.no_grad(): outputs = net(val_image.to(device)) # outputs的shape = [32,10] # dim是max函數(shù)索引的維度,0是每列最大值,1是每行最大值 predict_y = torch.max(outputs,dim=1)[1] # max函數(shù)返回的每個batchSize的最大值 + 索引。獲取索引[1] # == 來比較每個batchSize中的訓(xùn)練結(jié)果標簽和原標簽是否相同,如果預(yù)測正確就返回1,否則返回0,并累計正確的數(shù)量。 # 得到的是tensor,用item轉(zhuǎn)成數(shù)字,CPU時使用 accuracy = (predict_y == val_label.to(device)).sum().item()/val_label.size(0) # val_label.size是驗證集中batchSize的大小 print('[%d %5d] train_loss: %.3f test_accuracy:%.3f' % (epoch+1,step+1, running_loss/500,accuracy)) Loss.append(running_loss) running_loss = 0.0 print('Finished Training') torch.cuda.synchronize() end = time.time() print("訓(xùn)練用時:",end-start,'s')
五個epoch在我的GPU上訓(xùn)練了68s。
model.py
import torch.nn as nn import torch.nn.functional as F class LeNet(nn.Module): # 要繼承于nn.Moudule父類 def __init__(self): # 初始化函數(shù) super(LeNet, self).__init__() # 使用super函數(shù),解決多繼承可能遇到的一些問題;調(diào)用基類的構(gòu)造函數(shù) self.conv1 = nn.Conv2d(3, 16, 5) # 調(diào)用卷積層 (in_channels,out_channels(也是卷積核個數(shù)。輸出的通道數(shù)),kernel_size(卷積核大?。?stride) self.pool1 = nn.MaxPool2d(2, 2) # 最大池化層,進行下采樣 self.conv2 = nn.Conv2d(16, 32, 5) # 輸出的通道數(shù)為32 self.pool2 = nn.MaxPool2d(2, 2) self.fc1 = nn.Linear(32*5*5, 120) # 全連接層輸入是一維向量,這里是32x5x5,我們要展平,120是節(jié)點的個數(shù) # 32是通道數(shù) # Linear(input_features,output_features) self.fc2 = nn.Linear(120, 84) self.fc3 = nn.Linear(84, 10) def forward(self, x): # x是輸入數(shù)據(jù),是一個tensor # 正向傳播 x = F.relu(self.conv1(x)) # input(3, 32, 32) output(16, 28, 28) x = self.pool1(x) # output(16, 14, 14) x = F.relu(self.conv2(x)) # output(32, 10, 10) x = self.pool2(x) # output(32, 5, 5) x = x.view(-1, 32*5*5) # output(32*5*5) # 數(shù)據(jù)通過view展成一維向量,第一個參數(shù)-1是batch,自動推理;32x5x5是展平后的個數(shù) x = F.relu(self.fc1(x)) # output(120) x = F.relu(self.fc2(x)) # output(84) x = self.fc3(x) # output(10) # 為什么沒有用softmax函數(shù) --- 在網(wǎng)絡(luò)模型中已經(jīng)計算交叉熵以及概率 return x import torch input1 = torch.rand([32,3,32,32]) model = LeNet() # 模式實例化 print(model) # 看一下模型結(jié)構(gòu) output = model(input1)
train.py
import torch import torchvision import torch.nn as nn from model import LeNet import torch.optim as optim import torchvision.transforms as transforms import matplotlib.pyplot as plt import numpy as np def main(): device = torch.device("cuda:0" if torch.cuda.is_available() else "cpu") print("using {} device.".format(device)) transform = transforms.Compose( [transforms.ToTensor(), transforms.Normalize((0.5, 0.5, 0.5), (0.5, 0.5, 0.5))]) # 50000張訓(xùn)練圖片 # 第一次使用時要將download設(shè)置為True才會自動去下載數(shù)據(jù)集 train_set = torchvision.datasets.CIFAR10(root='./data', train=True, download=False, transform=transform) train_loader = torch.utils.data.DataLoader(train_set, batch_size=36, shuffle=True, num_workers=0) # 把訓(xùn)練集讀取,別分成一個一個批次的,shuffle可用于隨機打亂;batch_size是一次處理36張圖像 # num_worker在windows下只能設(shè)置成0 # 10000張驗證圖片 # 第一次使用時要將download設(shè)置為True才會自動去下載數(shù)據(jù)集 val_set = torchvision.datasets.CIFAR10(root='./data', train=False, download=False, transform=transform) val_loader = torch.utils.data.DataLoader(val_set, batch_size=5000, shuffle=False, num_workers=0) # 驗證集 一次拿出5000張1出來驗證,不用打亂 val_data_iter = iter(val_loader) # 轉(zhuǎn)換成可迭代的迭代器 val_image, val_label = val_data_iter.next() # 轉(zhuǎn)換成迭代器后,用next方法可以得到測試的圖像和圖像的標簽值 classes = ('plane', 'car', 'bird', 'cat', 'deer', 'dog', 'frog', 'horse', 'ship', 'truck') # 這一部分用來看數(shù)據(jù)集 # def imshow(img): # img = img / 2 + 0.5 # unnormalize -> 反標準化處理 # npimg = img.numpy() # plt.imshow(np.transpose(npimg, (1, 2, 0))) #(1,2,0)-> 代表高度、寬度 通道 # plt.show() # # # print labels # print(' '.join('%5s' % classes[val_label[j]] for j in range(4))) # imshow(torchvision.utils.make_grid(val_image)) net = LeNet() net.to(device) # 使用GPU時將網(wǎng)絡(luò)分配到指定的device中,不使用GPU注釋 loss_function = nn.CrossEntropyLoss() # 已經(jīng)包含了softmax函數(shù) optimizer = optim.Adam(net.parameters(), lr=0.001) #Adam優(yōu)化器 for epoch in range(5): # loop over the dataset multiple times running_loss = 0.0 for step, data in enumerate(train_loader, start=0): # get the inputs; data is a list of [inputs, labels] inputs, labels = data # zero the parameter gradients optimizer.zero_grad() # 一般batch_size根據(jù)硬件設(shè)備來設(shè)置的,這個清楚歷史梯度,不讓梯度累計,可以讓配置低的用戶加快訓(xùn)練 # forward + backward + optimize 、、、、、CPU # outputs = net(inputs) # loss = loss_function(outputs, labels) # GPU使用時添加,不使用時注釋 outputs = net(inputs.to(device)) # 將inputs分配到指定的device中 loss = loss_function(outputs, labels.to(device)) # 將labels分配到指定的device中 loss.backward() # loss進行反向傳播 optimizer.step() # step進行參數(shù)更新 # print statistics running_loss += loss.item() # m每次計算完后就加入到running_loss中 if step % 500 == 499: # print every 500 mini-batches with torch.no_grad(): # 在測試、預(yù)測過程中,這個函數(shù)可以優(yōu)化內(nèi)存,防止爆內(nèi)存 # outputs = net(val_image) # [batch, 10] outputs = net(val_image.to(device)) # 使用GPU時用這行將test_image分配到指定的device中 predict_y = torch.max(outputs, dim=1)[1] #dim=1,因為dim=0是batch;[1]是索引,最大值在哪個位置 # accuracy = torch.eq(predict_y, val_label).sum().item() / val_label.size(0) # eq用來比較,如果預(yù)測正確返回1,錯誤返回0 -> 得到的是tensor,要用item轉(zhuǎn)成數(shù)值 CPU時使用 accuracy = (predict_y==val_label.to(device)).sum().item() / val_label.size(0) print('[%d, %5d] train_loss: %.3f test_accuracy: %.3f' % (epoch + 1, step + 1, running_loss / 500, accuracy)) running_loss = 0.0 print('Finished Training') save_path = './Lenet.pth' torch.save(net.state_dict(), save_path) if __name__ == '__main__': main()
Tips:數(shù)據(jù)集在當前目錄下創(chuàng)建一個data文件夾,然后在train_set導(dǎo)入數(shù)據(jù)那里的download設(shè)置為True就可以下載了。如果你沒有GPU的話,你可以使用CPU訓(xùn)練,只需要把代碼中標記的GPU部分注釋,注釋掉的CPU部分取消注釋就ok了。有條件還是GPU吧,CPU太慢了。
引用:
pytorch官方model
到此這篇關(guān)于CNN的Pytorch實現(xiàn)(LeNet)的文章就介紹到這了,更多相關(guān)CNN的Pytorch實現(xiàn)內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
標簽:呼倫貝爾 葫蘆島 湘西 烏魯木齊 三亞 呼倫貝爾 安慶 銀川
巨人網(wǎng)絡(luò)通訊聲明:本文標題《CNN的Pytorch實現(xiàn)(LeNet)》,本文關(guān)鍵詞 CNN,的,Pytorch,實現(xiàn),LeNet,CNN,;如發(fā)現(xiàn)本文內(nèi)容存在版權(quán)問題,煩請?zhí)峁┫嚓P(guān)信息告之我們,我們將及時溝通與處理。本站內(nèi)容系統(tǒng)采集于網(wǎng)絡(luò),涉及言論、版權(quán)與本站無關(guān)。