Vgg Net Pytorch實現+論文解讀


論文為VERY DEEP CONVOLUTIONAL NETWORKS FOR LARGE-SCALE IMAGE RECOGNITION,主要討論了在大規模圖片識別中,卷積神經網絡的深度對准確率的影響。本篇論文提出的vgg網絡在2014年的ImageNet比賽中分別在定位和分類中獲得了第一和第二的成績。

改進創新點

VGGNet對2012年的AlexNet模型主要提出了兩種改進思路:

  • 小卷積核(kernal size = 3x3)+小步幅(stride = 1)
  • 多尺度:使用多尺度圖片來訓練測試

卷積神經網絡設置

架構


結構特點

  • 整體結構上包含5組卷積層,卷積層不改變輸出圖片的尺寸。
  • 每層包含有1-4個卷積操作,分別在論文中對應不同的VGG結構。卷積核大小為3x3,步幅為1。
  • 每組卷積層后跟1個最大池化層,池化核大小為2x2,步幅為2,因此池化層會使得圖片尺寸縮小為原來一半。論文中共設計了5組池化層,因此圖片的尺寸會變為原來的1/32。
  • 最后跟3層全連接層

對比試驗

論文中針對網絡深度、卷積核尺寸、LRN操作方面做了對比試驗,設計了6個VGG結構。如下圖所示。

為何使用3x3卷積核

  • 2個3x3卷積層的感受野 = 1個5x5卷積層的感受野。同理,3個3x3卷積層感受野 = 1個7x7卷積層感受野。這樣加深了網絡,同時由於激活函數的加入,增加了網絡的非線性。
  • 參數量減少。這里作者舉了一個例子,假設一個含有三層3x3卷積層堆疊的輸入和輸出都包含C個通道的網絡,權重數量為3(32C2)=27C2; 而一個7x7的卷積層,需要72C2=49C2個權重參數,相對增加了81%。

為何使用1x1卷積核

  • 增加非線性,同時不影響感受野
  • 調整維度輸出

訓練

  • 初始化
    batch size為256,學習率初始化為0.01,用包含動量的小批量梯度下降。
    權重隨機初始化,從0均值和0.01方差的正態分布中取值。偏差初始化為0。
  • 調整圖片尺寸
    網絡輸入的圖片尺寸為224x224,因此必須調整圖片的尺寸。選取訓練圖像最小邊為S,若S=224,則不需要裁剪;若S>>224,裁剪圖像就會取圖像的一小部分。這樣選擇的圖片可以選取S>224的圖片,作為多尺寸輸入,只需要裁剪成224x224規格的圖片即可。下面的測試將會分別固定尺寸測試和多尺寸測試。

測試

測試主要針對上面的6鍾結構,然后加入了多尺寸輸入訓練以及測試。

測試的結果:

  • 加入了LRN,沒有效果
  • 從11層到19層,隨着層數的增大,錯誤率降低
  • 結構C(包含3個1x1卷積層)比網絡B性能好,這意味着添加非線性層的確有用,但是使用卷積獲取空間上下文信息更有用(D比C好)
  • 當深度達到19層時,錯誤率達到飽和
  • 加入多尺寸訓練后,網絡抵抗尺寸波動的性能增強

結論

本文評估了深度卷積網絡(到19層)在大規模圖片分類中的應用。
結果表明,深度有益於提高分類的正確率,通過在傳統的卷積網絡框架中使用更深的層能夠在ImageNet數據集上取得優異的結果。

NOTE:

  • 每個圖片或者特征圖的維數看作4維:樣本數 x 通道數c x 高h x 寬w
  • 卷積層看作5維:樣本數 x 輸出通道數cout x 輸入通道數cin x 高h x 寬w
  • 全連接層看作2個維度:樣本數 x (輸出通道數cout * 高h * 寬w)

Pytorch實現VGGNet

import torch
import time
from torch import nn, optim
import torchvision
import sys

#定義VGG各種不同的結構和最后的全連接層結構
cfg = {
    'VGG11': [64, 'M', 128, 'M', 256,'M', 512, 'M', 512,'M'],
    'VGG13': [64, 64, 'M', 128, 128, 'M', 256, 256, 'M', 512, 512, 'M', 512, 512, 'M'],
    'VGG16': [64, 64, 'M', 128, 128, 'M', 256, 256, 256, 'M', 512, 512, 512, 'M', 512, 512, 512, 'M'],
    'VGG19': [64, 64, 'M', 128, 128, 'M', 256, 256, 256, 256, 'M', 512, 512, 512, 512, 'M', 512, 512, 512, 512, 'M'],
    'FC':    [512*7*7, 4096, 10]
}

#將數據展開成二維數據,用在全連接層之前和卷積層之后
class FlattenLayer(torch.nn.Module):
    def __init__(self):
        super(FlattenLayer, self).__init__()
    def forward(self, x): # x shape: (batch, *, *, ...)
        return x.view(x.shape[0], -1)

class VGG(nn.Module):
    # nn.Module是一個特殊的nn模塊,加載nn.Module,這是為了繼承父類
    def __init__(self, vgg_name):
        super(VGG, self).__init__()
        # super 加載父類中的__init__()函數
        self.VGG_layer = self.vgg_block(cfg[vgg_name])
        self.FC_layer = self.fc_block(cfg['FC'])
    #前向傳播算法
    def forward(self, x):
        out_vgg = self.VGG_layer(x)
        out = out_vgg.view(out_vgg.size(0), -1)
        # 這一步將out拉成out.size(0)的一維向量
        out = self.FC_layer(out_vgg)
        return out
    #VGG模塊
    def vgg_block(self, cfg_vgg):
        layers = []
        in_channels = 1
        for out_channels in cfg_vgg:
            if out_channels == 'M':
                layers.append(nn.MaxPool2d(kernel_size=2, stride=2))
            else:
                layers.append(nn.Conv2d(in_channels, out_channels, kernel_size=3,padding=1, bias=False))
                layers.append(nn.BatchNorm2d(out_channels))
                layers.append(nn.ReLU(inplace=True))
                in_channels = out_channels
        return nn.Sequential(*layers)
    #全連接模塊
    def fc_block(self, cfg_fc):
        fc_net = nn.Sequential()
        fc_features, fc_hidden_units, fc_output_units = cfg_fc[0:]
        fc_net.add_module("fc", nn.Sequential(
            FlattenLayer(),
            nn.Linear(fc_features, fc_hidden_units),
            nn.ReLU(),
            nn.Dropout(0.5),
            nn.Linear(fc_hidden_units, fc_hidden_units),
            nn.ReLU(),
            nn.Dropout(0.5),
            nn.Linear(fc_hidden_units, fc_output_units)
        ))
        return fc_net

#加載MNIST數據,返回訓練數據集和測試數據集
def load_data_fashion_mnist(batch_size, resize=None, root='~/chnn/Datasets/FashionMNIST'):
    """Download the fashion mnist dataset and then load into memory."""
    trans = []
    if resize:
        trans.append(torchvision.transforms.Resize(size=resize))
    trans.append(torchvision.transforms.ToTensor())

    transform = torchvision.transforms.Compose(trans)
    mnist_train = torchvision.datasets.FashionMNIST(root=root, train=True, download=True, transform=transform)
    mnist_test = torchvision.datasets.FashionMNIST(root=root, train=False, download=True, transform=transform)
    if sys.platform.startswith('win'):
        num_workers = 0  # 0表示不用額外的進程來加速讀取數據
    else:
        num_workers = 4
    train_iter = torch.utils.data.DataLoader(mnist_train, batch_size=batch_size, shuffle=True, num_workers=num_workers)
    test_iter = torch.utils.data.DataLoader(mnist_test, batch_size=batch_size, shuffle=False, num_workers=num_workers)

    return train_iter, test_iter

#測試准確率
def evaluate_accuracy(data_iter, net, device=None):
    if device is None and isinstance(net, torch.nn.Module):
        # 如果沒指定device就使用net的device
        device = list(net.parameters())[0].device
    acc_sum, n = 0.0, 0
    with torch.no_grad():
        for X, y in data_iter:
            if isinstance(net, torch.nn.Module):
                net.eval() # 評估模式, 這會關閉dropout
                acc_sum += (net(X.to(device)).argmax(dim=1) == y.to(device)).float().sum().cpu().item()
                net.train() # 改回訓練模式
            else: # 自定義的模型, 3.13節之后不會用到, 不考慮GPU
                if('is_training' in net.__code__.co_varnames): # 如果有is_training這個參數
                    # 將is_training設置成False
                    acc_sum += (net(X, is_training=False).argmax(dim=1) == y).float().sum().item()
                else:
                    acc_sum += (net(X).argmax(dim=1) == y).float().sum().item()
            n += y.shape[0]
    return acc_sum / n

#模型訓練,定義損失函數、優化函數
def train_ch5(net, train_iter, test_iter, batch_size, optimizer, device, num_epochs):
    net = net.to(device)
    print("training on ", device)
    loss = torch.nn.CrossEntropyLoss()
    batch_count = 0
    for epoch in range(num_epochs):
        train_l_sum, train_acc_sum, n, start = 0.0, 0.0, 0, time.time()
        for X, y in train_iter:
            X = X.to(device)
            y = y.to(device)
            y_hat = net(X)
            l = loss(y_hat, y)
            optimizer.zero_grad()
            l.backward()
            optimizer.step()
            train_l_sum += l.cpu().item()
            train_acc_sum += (y_hat.argmax(dim=1) == y).sum().cpu().item()
            n += y.shape[0]
            batch_count += 1
        test_acc = evaluate_accuracy(test_iter, net)
        print('epoch %d, loss %.4f, train acc %.3f, test acc %.3f, time %.1f sec'
              % (epoch + 1, train_l_sum / batch_count, train_acc_sum / n, test_acc, time.time() - start))

def main():
    net = VGG('VGG16')
    print(net)

    #一個batch_size為64張圖片,進行梯度下降更新參數
    batch_size = 64
    #使用cuda來訓練
    device = torch.device('cuda' if torch.cuda.is_available() else 'cpu')
    #加載MNIST數據集,返回訓練集和測試集
    train_iter, test_iter = load_data_fashion_mnist(batch_size, resize=224)
    lr, num_epochs = 0.001, 5
    #使用Adam優化算法替代傳統的SGD,能夠自適應學習率
    optimizer = torch.optim.Adam(net.parameters(), lr=lr)
    #訓練--迭代更新參數
    train_ch5(net, train_iter, test_iter, batch_size, optimizer, device, num_epochs)

main()

NOTE:

程序中使用MNIST數據集,pytorch打印的網絡結構為:

訓練結果為:

因為使用原文結構參數量太大,造成顯存爆滿,於是將結構中的通道數變為1/8。訓練結果中,迭代了5次后,訓練集精確度提高,但測試集精度結果不是很理想。

已經將代碼上傳到GitHub:https://github.com/chnngege/vgg-pytorch


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM