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
加載和歸一化我們的訓練數據和測試數據。
torchvision
這個東西,實現了常用的一些深度學習的相關的圖像數據的加載功能,比如cifar10、Imagenet、Mnist等等的,保存在torchvision.datasets
模塊中。- 同時,也封裝了一些處理數據的方法。保存在
torchvision.transforms
模塊中 - 還封裝了一些模型和工具封裝在相應模型中,比如
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 一些性質:
- 如果輸入層不算神經網絡的層數,那麼 LeNet-5 是一個 7 層的網絡。(有些地方也可能把 卷積和池化 當作一個 layer)(LeNet-5 名字中的“5”也可以理解爲整個網絡中含可訓練參數的層數爲 5。)
- LeNet-5 大約有 60,000 個參數。
- 隨着網絡越來越深,圖像的高度和寬度在縮小,與此同時,圖像的 channel 數量一直在增加。
- 現在常用的 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)