Pytorch CIFAR-10分類(LeNet5)

CSDN只作爲查看網絡結構,具體代碼和結果展示請移步GitHub

1.數據讀取

CIFAR-10 是由 Hinton 的學生 Alex Krizhevsky 和 Ilya Sutskever 整理的一個用於識別普適物體的小型數據集。一共包含 10 個類別的 RGB 彩色圖 片:飛機( arplane )、汽車( automobile )、鳥類( bird )、貓( cat )、鹿( deer )、狗( dog )、蛙類( frog )、馬( horse )、船( ship )和卡車( truck )。圖片的尺寸爲 32×32 ,數據集中一共有 50000 張訓練圄片和 10000 張測試圖片。

與 MNIST 數據集中目比, CIFAR-10 具有以下不同點:

• CIFAR-10 是 3 通道的彩色 RGB 圖像,而 MNIST 是灰度圖像。
• CIFAR-10 的圖片尺寸爲 32×32, 而 MNIST 的圖片尺寸爲 28×28,比 MNIST 稍大。
• 相比於手寫字符, CIFAR-10 含有的是現實世界中真實的物體,不僅噪聲很大,而且物體的比例、 特徵都不盡相同,這爲識別帶來很大困難。

首先使用torchvision加載和歸一化我們的訓練數據和測試數據。

  1. torchvision這個東西,實現了常用的一些深度學習的相關的圖像數據的加載功能,比如cifar10、Imagenet、Mnist等等的,保存在torchvision.datasets模塊中。
  2. 同時,也封裝了一些處理數據的方法。保存在torchvision.transforms模塊中
  3. 還封裝了一些模型和工具封裝在相應模型中,比如torchvision.models當中就包含了AlexNet,VGG,ResNet,SqueezeNet等模型。

由於torchvision的datasets的輸出是[0,1]的PILImage,所以我們先先歸一化爲[-1,1]的Tensor

首先定義了一個變換transform,利用的是上面提到的transforms模塊中的Compose( )把多個變換組合在一起,可以看到這裏面組合了ToTensor和Normalize這兩個變換

transforms.Normalize((0.5, 0.5, 0.5), (0.5, 0.5, 0.5))前面的(0.5,0.5,0.5) 是 R G B 三個通道上的均值, 後面(0.5, 0.5, 0.5)是三個通道的標準差,注意通道順序是 R G B ,用過opencv的同學應該知道openCV讀出來的圖像是 BRG順序。這兩個tuple數據是用來對RGB 圖像做歸一化的,如其名稱 Normalize 所示這裏都取0.5只是一個近似的操作,實際上其均值和方差並不是這麼多,但是就這個示例而言 影響可不計。精確值是通過分別計算R,G,B三個通道的數據算出來的。

import torch
import torchvision
import torchvision.datasets as datasets
import torchvision.transforms as transforms

transform = transforms.Compose([transforms.ToTensor(),transforms.Normalize((0.5, 0.5, 0.5), (0.5, 0.5, 0.5))]) 

# datasets.CIFAR10( )也是封裝好了的,就在我前面提到的torchvision.datasets塊中
trainset = datasets.CIFAR10(root='D:/CIFAR-10', train=True,download=True, transform=transform)

trainloader = torch.utils.data.DataLoader(trainset, batch_size=128,shuffle=True, num_workers=2)

# 對於測試集的操作和訓練集一樣,我就不贅述了
testset = torchvision.datasets.CIFAR10(root='D:/CIFAR-10', train=False,download=True, transform=transform)
testloader = torch.utils.data.DataLoader(testset, batch_size=128,shuffle=False, num_workers=2)
    # 類別信息也是需要我們給定的
classes = ('plane', 'car', 'bird', 'cat','deer', 'dog', 'frog', 'horse', 'ship', 'truck')

2.定義網絡(LeNet5)

手寫字體識別模型LeNet5誕生於1994年,是最早的卷積神經網絡之一。LeNet5通過巧妙的設計,利用卷積、參數共享、池化等操作提取特徵,避免了大量的計算成本,最後再使用全連接神經網絡進行分類識別,這個網絡也是最近大量神經網絡架構的起點。

[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳(img-OBSpCaRR-1587021133464)(attachment:image.png)]

LeNet-5 一些性質:

  1. 如果輸入層不算神經網絡的層數,那麼 LeNet-5 是一個 7 層的網絡。(有些地方也可能把 卷積和池化 當作一個 layer)(LeNet-5 名字中的“5”也可以理解爲整個網絡中含可訓練參數的層數爲 5。)
  2. LeNet-5 大約有 60,000 個參數。
  3. 隨着網絡越來越深,圖像的高度和寬度在縮小,與此同時,圖像的 channel 數量一直在增加。
  4. 現在常用的 LeNet-5 結構和 Yann LeCun 教授在 1998 年論文中提出的結構在某些地方有區別,比如激活函數的使用,現在一般使用 ReLU 作爲激活函數,輸出層一般選擇 softmax。
import torch
import torch.nn as nn

#若能使用cuda,則使用cuda
device = torch.device('cuda' if torch.cuda.is_available() else 'cpu')

#定義網絡
class LeNet5(nn.Module):# nn.Module是所有神經網絡的基類,我們自己定義任何神經網絡,都要繼承nn.Module
    def __init__(self):
        super(LeNet5,self).__init__()
        self.conv1 = nn.Sequential(
            # 卷積層1,3通道輸入,6個卷積核,核大小5*5
            # 經過該層圖像大小變爲32-5+1,28*28
            nn.Conv2d(in_channels=3,out_channels=6,kernel_size=5,stride=1, padding=0),
            #激活函數
            nn.ReLU(),
            # 經2*2最大池化,圖像變爲14*14
            nn.MaxPool2d(kernel_size=2,stride=2,padding=0),
        )
        self.conv2 = nn.Sequential(
            # 卷積層2,6輸入通道,16個卷積核,核大小5*5
            # 經過該層圖像變爲14-5+1,10*10
            nn.Conv2d(in_channels=6,out_channels=16,kernel_size=5,stride=1, padding=0),
            nn.ReLU(),
            # 經2*2最大池化,圖像變爲5*5
            nn.MaxPool2d(kernel_size=2,stride=2,padding=0),
        )
        self.fc = nn.Sequential(
            # 接着三個全連接層
            nn.Linear(16*5*5,120),
            nn.ReLU(),
            nn.Linear(120,84),
            nn.ReLU(),
            nn.Linear(84,10),
        )
        
        # 定義前向傳播過程,輸入爲
    def forward(self,x):
        x = self.conv1(x)
        x = self.conv2(x)
        # nn.Linear()的輸入輸出都是維度爲一的值,所以要把多維度的tensor展平成一維
            
        x = x.view(x.size()[0],-1)
        x = self.fc(x)
        return x
            
net = LeNet5().cuda()
print("LeNet5 out: ", net)

3. 定義損失函數和優化器

pytorch將深度學習中常用的優化方法全部封裝在torch.optim之中,所有的優化方法都是繼承基類optim.Optimizier

損失函數是封裝在神經網絡工具箱nn中的,包含很多損失函數

import torch.optim as optim
#用到了神經網絡工具箱 nn 中的交叉熵損失函數
criterion = nn.CrossEntropyLoss() 
# 使用SGD(隨機梯度下降)優化,學習率爲0.001,動量爲0.9
optimizer = optim.SGD(net.parameters(), lr=0.01, momentum=0.9)  

4.訓練

from torch.autograd import Variable

plotloss = []
plotauc = []
for epoch in range(50):  # 指定訓練一共要循環幾個epoch
    net.train()
    sum_loss = 0.0
    correct = 0.0
    total = 0.0
    
    # 這裏我們遇到了第一步中出現的trailoader,代碼傳入數據,enumerate是python的內置函數,既獲得索引也獲得數據
    for i, (images,labels) in enumerate(trainloader):
        # data是從enumerate返回的data,包含數據和標籤信息,分別賦值給inputs和labels
        # data的結構是:[4x3x32x32的張量,長度4的張量],4是batch_size的數值
 
        # 把input數據從tensor轉爲variable,variable才擁有梯度grad,輸入模型訓練都要轉成Variable        
        if torch.cuda.is_available():
            images=Variable(images).cuda()
            labels=Variable(labels).cuda()
        else:
            images=Variable(images) 
            labels=Variable(labels)

        # 將參數的grad值初始化爲
        optimizer.zero_grad()                
 
        # forward + backward + optimize      
        outputs = net(images)
        # 將output和labels使用叉熵計算損失
        loss = criterion(outputs, labels)
        # 反向傳播
        loss.backward()
        # 用SGD更新參數
        optimizer.step()
        
        # loss.item()轉換爲numpy
        # loss本身爲Variable類型,所以要使用loss.data[0]獲取其Tensor,因爲其爲標量,所以取0
        sum_loss += loss.item()
        _, predicted = torch.max(outputs.data, 1)
        total += labels.size(0)          # 更新測試圖片的數量
        correct += (predicted == labels).sum() # 更新正確分類的圖片的數量
#         if i % 200 == 199:
        print('[epoch:%d, iter:%d] Loss: %.03f | Acc: %.3f%% ' 
              % (epoch + 1, (i + 1 + epoch * len(trainloader)), sum_loss / (i + 1), 100. * correct / total))
        plotloss.append(sum_loss / (i + 1))
        plotauc.append(100. * correct / total)
        
print('Finished Training')
plt.subplot(2,1,1)
plt.plot(plotloss)
plt.subplot(2,1,2)
plt.plot(plotauc)

5.測試

 # 定義2個存儲每類中測試正確的個數的 列表,初始化爲0
class_correct = list(0. for i in range(10))
class_total = list(0. for i in range(10))  
for data in testloader:
    images, labels = data
    images=Variable(images).cuda()
    labels=Variable(labels).cuda()
    outputs = net(images)

    _, predicted = torch.max(outputs.data, 1)
    #4組(batch_size)數據中,輸出於label相同的,標記爲1,否則爲0
    c = (predicted == labels).squeeze()
    for i in range(16):     
        label = labels[i]   # 對各個類的進行各自累加
        class_correct[label] += c[i]
        class_total[label] += 1
 
 
for i in range(10):
    print('Accuracy of %5s : %2d %%' % (classes[i], 100 * class_correct[i] / class_total[i]))

6.保存模型

pytorch保存模型的方式有兩種:

(1)將整個網絡都都保存下來

torch.save(model_object, 'model.pkl')
    
model = torch.load('model.pkl')

這種方式再重新加載的時候不需要自定義網絡結構,保存時已經把網絡結構保存了下來,但是,再讀取模型的時候還需要將以前的網絡結構複製進來且比較死板(pycharm中不用),另外不能調整網絡結構。

(2)僅保存和加載模型參數(推薦使用這樣的方法)

GPU上保存,CPU上加載:

torch.save(model.state_dict(), PATH)
device = torch.device('cpu')
model = TheModelClass(*args, **kwargs)
model.load_state_dict(torch.load(PATH, map_location=device))

GPU上保存,GPU上加載:

torch.save(model.state_dict(), PATH)
         
device = torch.device("cuda")
model = TheModelClass(*args, **kwargs)
model.load_state_dict(torch.load(PATH))
model.to(device)

往模型中輸入數據的時候不要忘記在任意tensor上調用input = input.to(device)

這種方式再重新加載的時候需要自己定義網絡,並且其中的參數名稱與結構要與保存的模型中的一致(可以是部分網絡,比如只使用VGG的前幾層),相對靈活,便於對網絡進行修改。

torch.save(net, 'D:/CIFAR-10/model/LeNet5-128.pth')

7.預測

import torch
from PIL import Image
from torch.autograd import Variable
import torch.nn.functional as F
from torchvision import datasets, transforms
import numpy as np
 
classes = ('plane', 'car', 'bird', 'cat',
           'deer', 'dog', 'frog', 'horse', 'ship', 'truck')

device = torch.device('cuda' if torch.cuda.is_available() else 'cpu')
model = torch.load('D:/CIFAR-10/model/LeNet5-128.pth')  # 加載模型
model = model.to(device)
model.eval()  # 把模型轉爲test模式

# 讀取要預測的圖片
img = Image.open("D:/CIFAR-10/bird1.png").convert('RGB') # 讀取圖像
trans = transforms.Compose([transforms.Scale((32,32)),
                            transforms.ToTensor(),
                            transforms.Normalize(mean=(0.5, 0.5, 0.5), 
                                                 std=(0.5, 0.5, 0.5)),
                           ])
 
img = trans(img)
img = img.to(device)
# 圖片擴展多一維,因爲輸入到保存的模型中是4維的[batch_size,通道,長,寬],而普通圖片只有三維,[通道,長,寬]
img = img.unsqueeze(0)  
    # 擴展後,爲[1,1,28,28]
output = model(img)
prob = F.softmax(output,dim=1) #prob是10個分類的概率
print("概率",prob)
value, predicted = torch.max(output.data, 1)
print("類別",predicted.item())
print(value)
pred_class = classes[predicted.item()]
print("分類",pred_class)
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章