天天看點

Pytorch學習筆記(10)———訓練并測試CNN網絡

本章節内容将在CIFAR10資料集上訓練一個簡單的CNN網絡:

  • 基于CIFAR-10資料集,訓練一個簡單CNN網絡。
  • 儲存訓練好的模型,測試。
  • 使用GPU訓練。

CIFAR資料集

CIFAR資料集可分為CIFAR10, CIFAR100。 CIFAR-10是指包含10個種類, CIFAR-100包含100個種類。

CIFAR-10

特點:32x32 彩色圖像;10個類别;總共60000張圖像;50000張訓練樣本 + 10000張測試樣本;每個類别有6000張圖像, 10 x 6000 = 60000;

10個類别:airplane,automobile,bird,cat,deer,dog,frog,horse,ship,truck;

Tips:不需要手動下載下傳, 使用pytorch中的Dataset API自動下載下傳即可

實驗過程

準備資料集

這一步驟在pytorch中非常友善,pytorch已經為我們準備好了常見的資料集合,隻需要導入即可。

資料集在

torchvision.dataset

包裡面:

import torch
import torchvision
import torchvision.transforms as transforms
from torch.utils.data import DataLoader
import torch.nn as nn
import torch.nn.functional as F
import torch.optim as optim
import matplotlib.pyplot as plt
import numpy as np
           

torchvision.dataset.CFAIR10

是一個類, 通過執行個體化該類的一個對象,就可以操作資料集。

參數:

root

-----資料集下載下傳後儲存的路徑

train

-----訓練or測試

download

----是否需要自動下載下傳

transform

----對圖像進行變換, 一般需要對原始圖像進行

ToTensor(), Normalize()

變換

之後,使用

DataLoader

類對資料集進行包裝,目的是為了友善讀取和使用,比如可以min_batch讀取, 采用多線程。

# --------------------準備資料集------------------
# Dataset, DataLoader
transform = transforms.Compose(
    [transforms.ToTensor(),
     transforms.Normalize((0.5, 0.5, 0.5), std =(0.5, 0.5, 0.5))])

trainset = torchvision.datasets.CIFAR10(root='./data', train=True,
                                        download=True, transform=transform)

testset = torchvision.datasets.CIFAR10(root='./data',train=False,
                                       transform=transform, download=True)

trainloader = DataLoader(dataset=trainset, batch_size=4, shuffle=True, num_workers=4)
testloader = DataLoader(dataset=testset, batch_size=4, shuffle=True, num_workers=4)
           
#
dataiter = iter(trainloader)
images, labels = dataiter.next()

imshow(torchvision.utils.make_grid(images))
# print labels
print(' '.join('%5s' % classes[labels[j]] for j in range(4)))

           

定義CNN網絡

簡單起見,采用LeNet網絡,将第一個卷積層的輸入通道改為3,因為CIFAR-10是彩色3通道圖像。

#定義一個簡單的網絡
# LeNet -5
class Net(nn.Module):
    def __init__(self):
        super(Net, self).__init__()
        self.conv1 = nn.Conv2d(in_channels=3, out_channels=6, kernel_size=5)
        self.pool1 = nn.MaxPool2d(kernel_size=2, stride=2)
        self.conv2 = nn.Conv2d(in_channels=6, out_channels=16, kernel_size=5)
        self.fc1 = nn.Linear(in_features=16 * 5 * 5,out_features=120)
        self.fc2 = nn.Linear(in_features=120, out_features=84)
        self.fc3 = nn.Linear(in_features=84, out_features=10)

    def forward(self, x):
        x = self.pool1(F.relu(self.conv1(x)))
        x = self.pool1(F.relu(self.conv2(x)))
        x = x.view(-1, 16 * 5 * 5)              # reshape tensor
        x = F.relu(self.fc1(x))
        x = F.relu(self.fc2(x))
        x = self.fc3(x)
        return x

           

設定網絡的優化、疊代方法,訓練網絡

CNN網絡訓練本質上就是對一個目标函數(損失函數)求最小的問題,在數學中,對于一般的凸函數,優化方法有梯度下降法、牛頓法等。(除此之外還有啟發式搜尋,比如遺傳算法等)。 對于神經網絡的訓練,常用的優化方法為随機梯度下降法SGD。

  • 定義損失函數,優化方法

    采用交叉熵損失函數

    采用SGD随機梯度下降法進行優化(帶動量項)

# 定義損失函數,優化方法
# 采用Cross-Entropy loss,  SGD with moment
criterion = nn.CrossEntropyLoss()
optimizer = optim.SGD(net.parameters(), lr=0.001, momentum=0.9)

           
  • 進行疊代優化,訓練

Iter------一次疊代,是指一個min_batch的一次forward+backward

Epoch------疊代完所有的訓練資料(1次),稱為一個epoch

這裡總共跑20個epoch。

# 訓練網絡
# 疊代epoch
for epoch in range(20):

    running_loss = 0.0
    for i, data in enumerate(trainloader, 0):
        # get the input
        inputs, labels = data

        # zeros the paramster gradients
        optimizer.zero_grad()       # 

        # forward + backward + optimize
        outputs = net(inputs)
        loss = criterion(outputs, labels)  # 計算loss
        loss.backward()     # loss 求導
        optimizer.step()    # 更新參數

        # print statistics
        running_loss += loss.item()  # tensor.item()  擷取tensor的數值
        if i % 2000 == 1999:
            print('[%d, %5d] loss: %.3f' %
                  (epoch + 1, i + 1, running_loss / 2000))  # 每2000次疊代,輸出loss的平均值
            running_loss = 0.0

print('Finished Training')

           

儲存模型

# --------儲存模型-----------
torch.save(net, './model/model_cfair10_2.pth')    # 儲存整個模型,體積比較大
# torch.save(net.state_dict(), './model/model_cfair10.pth')
           

測試模型

import torch
import torchvision
import torchvision.transforms as transforms
from torch.utils.data import DataLoader
import torch.nn as nn
import torch.nn.functional as F
import torch.optim as optim
import matplotlib.pyplot as plt
import numpy as np
from PIL import Image

           

CIFAR-10總共包含10個類别:

載入一張圖像,RBG,要屬于上述類别的某一類,不然識别不出來

# load a image
image = Image.open('/xxxx/image/dog.jpg')

           

對圖像進行相同的變換:

transform = transforms.Compose(
    [transforms.Resize((32, 32)),
     transforms.ToTensor(),
     transforms.Normalize(
         mean=(0.5, 0.5, 0.5),
         std=(0.5, 0.5, 0.5)
     )])

image_transformed = transform(image)
print(image_transformed.size())
           

需要注意的地方

CNN網絡的輸入為4D Tensor (NxCxHxW), 轉換之後的圖像需要變換為4D

torsor1.unsqueeze(0)

即可增加一個次元,這樣輸入的tensor為: 1x3x32x32

#
transform = transforms.Compose(
    [transforms.Resize((32, 32)),
     transforms.ToTensor(),
     transforms.Normalize(
         mean=(0.5, 0.5, 0.5),
         std=(0.5, 0.5, 0.5)
     )])

image_transformed = transform(image)
print(image_transformed.size())


class Net(nn.Module):
    def __init__(self):
        super(Net, self).__init__()
        self.conv1 = nn.Conv2d(in_channels=3, out_channels=6, kernel_size=5)
        self.pool1 = nn.MaxPool2d(kernel_size=2, stride=2)
        self.conv2 = nn.Conv2d(in_channels=6, out_channels=16, kernel_size=5)
        self.fc1 = nn.Linear(in_features=16 * 5 * 5,out_features=120)
        self.fc2 = nn.Linear(in_features=120, out_features=84)
        self.fc3 = nn.Linear(in_features=84, out_features=10)

    def forward(self, x):
        x = self.pool1(F.relu(self.conv1(x)))
        x = self.pool1(F.relu(self.conv2(x)))
        x = x.view(-1, 16 * 5 * 5)              # reshape tensor
        x = F.relu(self.fc1(x))
        x = F.relu(self.fc2(x))
        x = self.fc3(x)
        return x


net = torch.load('./model/model_cfair10.pth')
# print(net)

image_transformed = image_transformed.unsqueeze(0)
output = net(image_transformed)
predict_value, predict_idx = torch.max(output, 1)  # 求指定次元的最大值,傳回最大值以及索引

plt.figure()
plt.imshow(np.array(image))
plt.title(CFAIR10_names[predict_idx])
plt.axis('off')

plt.show()

           

————————————————————————————————————————————————————————————————————————————————————————————————————————

GPU訓練、模型學習率調整

問題:

  • 采用CPU模型訓練了20個Epoch之後,loss下降到了0.6左右,後來在之前訓練的基礎上疊代了20個Epoch,發現loss處于0.5~0.6之間。
  • CPU上訓練确實很慢,跑20個Epoch基本上用了1h多(具體時間不記得),比較漫長。

使用GPU訓練模型

  • 計算機配置 GPU: 1080

首先,需要安裝GPU版本的pytorch, 具體安裝步驟pytorch官網有。使用GPU訓練需要對代碼做一些小調整。

**step1:**在代碼中,首先使用pytorch中的函數判斷是否支援GPU

is_support = torch.cunda.is_available()
if is_support:
  device = torch.device('cuda:0')
 # device = torch.device('cuda:1')
else:
  device = torch.device('cpu')
  
           

step2: 将CPU上的計算轉移到GPU上

net = Net()
net.to(device)   # GPU模式需要添加


# 訓練網絡
# 疊代epoch
for epoch in range(20):

    running_loss = 0.0
    for i, data in enumerate(trainloader, 0):
        # get the input
        inputs, labels = data

        inputs = inputs.to(device)      #  GPU計算
        labels = labels.to(device)      # GPU計算

        # zeros the paramster gradients
        optimizer.zero_grad()       # 

        # forward + backward + optimize
        outputs = net(inputs)
        loss = criterion(outputs, labels)  # 計算loss
        loss.backward()     # loss 求導
        optimizer.step()    # 更新參數

        # print statistics
        running_loss += loss.item()  # tensor.item()  擷取tensor的數值
        if i % 2000 == 1999:
            print('[%d, %5d] loss: %.3f' %
                  (epoch + 1, i + 1, running_loss / 2000))  # 每2000次疊代,輸出loss的平均值
            running_loss = 0.0

print('Finished Training')

           

run, 會發現疊代速度飛起,10min左右就可以完成20個Epoch疊代,速度非常快。

學習率調整

  • 随機梯度下降法SGD重要的一個參數是:學習率 leaning_rate

上面的代碼采用的是固定學習率lr=0.001,。 剛開始疊代時候,學習率可以大一些,這樣收斂速度快,随着疊代次數增加,學習率應該減小防止loss震蕩。

簡單起見,本人将學習率調整為lr=0.0001,然後在之前模型的基礎上疊代20個Epoch。明顯發現Loss變為0.3, 0.2, 0.1。

雖然采用GPU訓練, lr減小為0.0001, loss也減少了(訓練集loss)。 在測試中,1個horse識别為deer, bird識别為cat。 因為,要訓練到一個适合模型,還需要其他政策,包括采用其他網絡模型。

在整個測試集上評價模型性能

  • 計算Acc
import torch
import torchvision
import torchvision.transforms as transforms
from torch.utils.data import DataLoader
import torch.nn as nn
import torch.nn.functional as F
import torch.optim as optim
import matplotlib.pyplot as plt
import numpy as np
from PIL import Image


CFAIR10_names = ['airplane', 'automobile', 'bird', 'cat', 'deer', 'dog', 'forg', 'horse', 'ship', 'truck']

# --------------測試資料集------------------------------
transform = transforms.Compose(
    [transforms.Resize((32, 32)),
     transforms.ToTensor(),
     transforms.Normalize(
         mean=(0.5, 0.5, 0.5),
         std=(0.5, 0.5, 0.5)
     )])

testset = torchvision.datasets.CIFAR10(root='./data', train=False,
                                       download=True, transform=transform)
testloader = torch.utils.data.DataLoader(testset, batch_size=4,
                                         shuffle=False, num_workers=4)

# -----------------網咯模型-------------------------------


class Net(nn.Module):
    def __init__(self):
        super(Net, self).__init__()
        self.conv1 = nn.Conv2d(in_channels=3, out_channels=6, kernel_size=5)
        self.pool1 = nn.MaxPool2d(kernel_size=2, stride=2)
        self.conv2 = nn.Conv2d(in_channels=6, out_channels=16, kernel_size=5)
        self.fc1 = nn.Linear(in_features=16 * 5 * 5,out_features=120)
        self.fc2 = nn.Linear(in_features=120, out_features=84)
        self.fc3 = nn.Linear(in_features=84, out_features=10)

    def forward(self, x):
        x = self.pool1(F.relu(self.conv1(x)))
        x = self.pool1(F.relu(self.conv2(x)))
        x = x.view(-1, 16 * 5 * 5)              # reshape tensor
        x = F.relu(self.fc1(x))
        x = F.relu(self.fc2(x))
        x = self.fc3(x)
        return x


net = torch.load('./model/model_cfair10_20.pth',map_location='cpu')


# ------------在整個測試集上測試-------------------------------------------

correct = 0
total = 0
count = 0
with torch.no_grad():
    for sample_batch in testloader:
        images = sample_batch[0]
        labels = sample_batch[1]
        # forward
        out = net(images)
        #
        _, pred = torch.max(out, 1)
        correct += (pred == labels).sum().item()
        total += labels.size(0)
        print('batch:{}'.format(count + 1))
        count += 1


#
# Acc
accuracy = float(correct) / total
print('Acc = {:.5f}'.format(accuracy))

           

連結:https://www.jianshu.com/p/e704a6f6e8d3

來源:簡書

繼續閱讀