Pytorch深度學習——AlexNet及數據集花分類

使用Pytorch實現AlexNet

AlexNet結構

在這裏插入圖片描述
input是224×224x3,此處padding應爲[1,2],即在特徵矩陣左邊加上一列0,右邊加上兩列0,上面加上一列0,下面加上兩列0在這裏插入圖片描述

特點

  • 使用Relu替換之前的sigmoid的作爲激活函數
  • 使用數據增強Data Augmentation抑制過擬合,比如增加噪聲,翻轉,隨機裁剪等
  • 使用LRN局部響應歸一化
  • 在全連接層使用Dropout隨機失活防止過擬合
  • 使用兩塊GPU 進行並行計算

第一層

在這裏插入圖片描述
Conv1的處理流程是: 卷積–>ReLU–>池化–>歸一化。

  • 卷積,input是224×224x3,此處padding應爲[1,2],即在特徵矩陣左邊加上一列0,右邊加上兩列0,上面加上一列0,下面加上兩列0.使用48*2=96個11×11×3的卷積核,stride=4得到的FeatureMap爲55×55×96,即output.{( 224-11+(1+2))/4+1=55}
  • ReLU,將卷積層輸出的FeatureMap輸入到ReLU函數中,即input是55×55×96.
  • 池化Maxpool1,使用3×3步長爲2的池化單元,padding=0(重疊池化,步長小於池化單元的寬度),輸出爲27×27×96 {55−3)/2+1=27}
  • 局部響應歸一化,使用k=2,n=5,α=10−4,β=0.75進行局部歸一化,輸出的仍然爲27×27×96,輸出分爲兩組,每組的大小爲27×27×48

第二層

在這裏插入圖片描述

Conv2的處理流程是:卷積–>ReLU–>池化–>歸一化

  • 卷積,輸入是2組27×27×48。使用2組,每組128個,尺寸爲5×5×48的卷積核,並作了邊緣填充padding=2,卷積的步長stride=1. 則輸出的FeatureMap爲2組,每組的大小爲27×27x128. {(27+2∗2−5)/1+1=27}
  • ReLU,將卷積層輸出的FeatureMap輸入到ReLU函數中
  • 池化運算的尺寸爲3×3,步長爲2,padding=0,池化後圖像的尺寸爲(27−3)/2+1=13,輸出爲13×13×256
  • 局部響應歸一化,使用k=2,n=5,α=10−4,β=0.75進行局部歸一化,輸出的仍然爲13×13×256,輸出分爲2組,每組的大小爲13×13×128

第三層

在這裏插入圖片描述
Conv3的處理流程是: 卷積–>ReLU

  • 卷積,輸入是13×13×256,使用2組共384,尺寸爲3×3×2563的卷積核,做了邊緣填充padding=1,卷積的步長爲1.則輸出的FeatureMap爲13×13x384
  • ReLU,將卷積層輸出的FeatureMap輸入到ReLU函數中

第四層

在這裏插入圖片描述
Conv4和Conv3類似

  • 卷積,輸入是13×13×384,分爲兩組,每組爲13×13×192.使用2組,每組192個尺寸爲3×3×192的卷積核,做了邊緣填充padding=1,卷積的步長爲1.則輸出的FeatureMap爲13×13x384,分爲兩組,每組爲13×13×192
  • ReLU,將卷積層輸出的FeatureMap輸入到ReLU函數中

第五層

在這裏插入圖片描述
Conv5處理流程爲:卷積–>ReLU–>池化

  • 卷積,輸入爲13×13×384,分爲兩組,每組爲13×13×192。使用2組,每組爲128,尺寸爲3×3×192的卷積核,做了邊緣填充padding=1,卷積的步長爲1.則輸出的FeatureMap爲13×13×256
  • ReLU,將卷積層輸出的FeatureMap輸入到ReLU函數中
  • 池化,池化運算的尺寸爲3×3,步長爲2,池化後圖像的尺寸爲 (13−3)/2+1=6,即池化後的輸出爲6×6×256

第六層

在這裏插入圖片描述

FC6全連接 -->ReLU -->Dropout

  • 卷積->全連接:
    輸入爲6×6×256,該層有4096個卷積核,每個卷積核的大小爲6×6×256。由於卷積核的尺寸剛好與待處理特徵圖(輸入)的尺寸相同,即卷積核中的每個係數只與特徵圖(輸入)尺寸的一個像素值相乘,一一對應,因此,該層被稱爲全連接層。由於卷積核與特徵圖的尺寸相同,卷積運算後只有一個值,因此,卷積後的像素層尺寸爲4096×1×1,即有4096個神經元。
  • ReLU,這4096個運算結果通過ReLU激活函數生成4096個值
  • Dropout,抑制過擬合,隨機的斷開某些神經元的連接或者是不激活某些神經元

第七層

在這裏插入圖片描述
FC7全連接–>ReLU–>Dropout

  • 全連接,輸入爲4096的向量
  • ReLU,這4096個運算結果通過ReLU激活函數生成4096個值
  • Dropout,抑制過擬合,隨機的斷開某些神經元的連接或者是不激活某些神經元

第八層

在這裏插入圖片描述
最後一步不進行失活
第七層輸出的4096個數據與第八層的1000個神經元進行全連接,經過訓練後輸出1000個float型的值,這就是預測結果。

參數表

在這裏插入圖片描述

數據準備

數據集下載
(此處膜拜一波大佬)-----------太陽花的小綠豆
在這裏插入圖片描述

具體實現(由於原文是使用兩塊GPU進行並行運算,此處我們之分析一半的模型)

model.py

import torch.nn as nn
import torch

'''
使用nn.Sequential, 將一系列的層結構打包,形成一個整體
'''


class AlexNet(nn.Module):
    def __init__(self, num_classes=1000, init_weights=False):
        super(AlexNet, self).__init__()
        # 專門用來提取圖像特徵
        self.features = nn.Sequential(
            nn.Conv2d(3, 48, kernel_size=11, stride=4, padding=2),  # input[3, 224, 224]  output[48, 55, 55]
            nn.ReLU(inplace=True),  # inPlace=True, 增加計算量減少內存使用的一個方法
            nn.MaxPool2d(kernel_size=3, stride=2),                  # output[48, 27, 27]
            nn.Conv2d(48, 128, kernel_size=5, padding=2),           # output[128, 27, 27]
            nn.ReLU(inplace=True),
            nn.MaxPool2d(kernel_size=3, stride=2),                  # output[128, 13, 13]
            nn.Conv2d(128, 192, kernel_size=3, padding=1),          # output[192, 13, 13]
            nn.ReLU(inplace=True),
            nn.Conv2d(192, 192, kernel_size=3, padding=1),          # output[192, 13, 13]
            nn.ReLU(inplace=True),
            nn.Conv2d(192, 128, kernel_size=3, padding=1),          # output[128, 13, 13]
            nn.ReLU(inplace=True),
            nn.MaxPool2d(kernel_size=3, stride=2),                  # output[128, 6, 6]
        )
        # 將全連接層作爲一個整體,通過Dropout使其防止過擬合,一般放在全連接層和全連接層之間
        self.classifier = nn.Sequential(
            nn.Dropout(p=0.5),
            nn.Linear(128 * 6 * 6, 2048),
            nn.ReLU(inplace=True),
            nn.Dropout(p=0.5),
            nn.Linear(2048, 2048),
            nn.ReLU(inplace=True),
            nn.Linear(2048, num_classes),
        )
        if init_weights:
            self._initialize_weights()

    def forward(self, x):
        x = self.features(x)
        x = torch.flatten(x, start_dim=1)  # 展平處理,從channel維度開始展平,(第一個維度爲channel)
        x = self.classifier(x)
        return x

    def _initialize_weights(self):
        for m in self.modules():  # 返回一個迭代器,遍歷我們網絡中的所有模塊
            if isinstance(m, nn.Conv2d):  # 判斷層結構是否爲所給定的層,比如此處判斷是否爲卷積層
                nn.init.kaiming_normal_(m.weight, mode='fan_out', nonlinearity='relu')
                if m.bias is not None:  # 此處判斷該層偏置是否爲空
                    nn.init.constant_(m.bias, 0)
            elif isinstance(m, nn.Linear):  # 如果是全連接層
                nn.init.normal_(m.weight, 0, 0.01)  # 通過正態分佈來給權重賦值
                nn.init.constant_(m.bias, 0)

train.py

import torch
import torch.nn as nn
from torchvision import transforms, datasets, utils
import matplotlib.pyplot as plt
import numpy as np
import torch.optim as optim
from model import AlexNet
import os
import json
import time

device = torch.device("cuda:0" if torch.cuda.is_available() else "cpu")
print(device)

# 數據預處理,定義data_transform這個字典
data_transform = {
    "train": transforms.Compose([transforms.RandomResizedCrop(224),  # 隨機裁剪,裁剪到224*224
                                 transforms.RandomHorizontalFlip(),  # 水平方向隨機翻轉
                                 transforms.ToTensor(),
                                 transforms.Normalize((0.5, 0.5, 0.5), (0.5, 0.5, 0.5))]),
    "val": transforms.Compose([transforms.Resize((224, 224)),  # cannot 224, must (224, 224)
                               transforms.ToTensor(),
                               transforms.Normalize((0.5, 0.5, 0.5), (0.5, 0.5, 0.5))])}
# os.getcwd()獲取當前文件所在目錄, "../.."返回上上層目錄,".."返回上層目錄
data_root = os.path.abspath(os.path.join(os.getcwd(), ".."))  # get data root path
image_path = data_root + "/data_set/flower_data/"  # flower data set path
train_dataset = datasets.ImageFolder(root=image_path + "/train",
                                     transform=data_transform["train"])
train_num = len(train_dataset)

flower_list = train_dataset.class_to_idx  # 獲取分類的名稱所對應的索引,即{'daisy':0, 'dandelion':1, 'roses':2, 'sunflower':3, 'tulips':4}
cla_dict = dict((val, key) for key, val in flower_list.items())  # 遍歷獲得的字典,將key和value反過來,即key變爲0,val變爲daisy
# 將key和value反過來的目的是,預測之後返回的索引可以直接通過字典得到所屬類別
# write dict into json file
json_str = json.dumps(cla_dict, indent=4)
with open('class_indices.json', 'w') as json_file:  # 保存入json文件
    json_file.write(json_str)

batch_size = 32
train_loader = torch.utils.data.DataLoader(train_dataset,
                                           batch_size=batch_size, shuffle=True,
                                           num_workers=0)

validate_dataset = datasets.ImageFolder(root=image_path + "/val",
                                        transform=data_transform["val"])
val_num = len(validate_dataset)
validate_loader = torch.utils.data.DataLoader(validate_dataset,
                                              batch_size=batch_size, shuffle=False,
                                              num_workers=0)

# test_data_iter = iter(validate_loader)
# test_image, test_label = test_data_iter.next()
#
# def imshow(img):
#     img = img / 2 + 0.5  # unnormalize
#     npimg = img.numpy()
#     plt.imshow(np.transpose(npimg, (1, 2, 0)))
#     plt.show()
#
# print(' '.join('%5s' % flower_list[test_label[j]] for j in range(4)))
# imshow(utils.make_grid(test_image))


net = AlexNet(num_classes=5, init_weights=True)

net.to(device)
loss_function = nn.CrossEntropyLoss()
pata = list(net.parameters())  # 查看net內的參數
optimizer = optim.Adam(net.parameters(), lr=0.0002)

save_path = './AlexNet.pth'
best_acc = 0.0
for epoch in range(10):
    # train
    net.train()  # 在訓練過程中調用dropout方法
    running_loss = 0.0
    t1 = time.perf_counter()  # 統計訓練一個epoch所需時間
    for step, data in enumerate(train_loader, start=0):
        images, labels = data
        optimizer.zero_grad()
        outputs = net(images.to(device))
        loss = loss_function(outputs, labels.to(device))
        loss.backward()
        optimizer.step()

        # print statistics
        running_loss += loss.item()
        # print train process
        rate = (step + 1) / len(train_loader)
        a = "*" * int(rate * 50)
        b = "." * int((1 - rate) * 50)
        print("\rtrain loss: {:^3.0f}%[{}->{}]{:.3f}".format(int(rate * 100), a, b, loss), end="")
    print()
    print(time.perf_counter()-t1)

    # validate
    net.eval()  # 在測試過程中關掉dropout方法,不希望在測試過程中使用dropout
    acc = 0.0  # accumulate accurate number / epoch
    with torch.no_grad():
        for data_test in validate_loader:
            test_images, test_labels = data_test
            outputs = net(test_images.to(device))
            predict_y = torch.max(outputs, dim=1)[1]
            acc += (predict_y == test_labels.to(device)).sum().item()
        accurate_test = acc / val_num
        if accurate_test > best_acc:
            best_acc = accurate_test
            torch.save(net.state_dict(), save_path)
        print('[epoch %d] train_loss: %.3f  test_accuracy: %.3f' %
              (epoch + 1, running_loss / step, acc / val_num))

print('Finished Training')

訓練結果 在這裏插入圖片描述

predict.py

用自己數據集運算時,要將num_classes改爲自己數據集類別的數目

import torch
from model import AlexNet
from PIL import Image
from torchvision import transforms
import matplotlib.pyplot as plt
import json

data_transform = transforms.Compose(
    [transforms.Resize((224, 224)),
     transforms.ToTensor(),
     transforms.Normalize((0.5, 0.5, 0.5), (0.5, 0.5, 0.5))])

# load image
img = Image.open("../tulips.jpg")
plt.imshow(img)
# [N, C, H, W]
img = data_transform(img)  # 預處理的時候已經將channel這個維度提到最前面
# expand batch dimension
img = torch.unsqueeze(img, dim=0)  # 在最前面增加一個batch維度

# read class_indict
try:
    json_file = open('./class_indices.json', 'r')
    class_indict = json.load(json_file)
except Exception as e:
    print(e)
    exit(-1)

# create model
model = AlexNet(num_classes=5)
# load model weights
model_weight_path = "./AlexNet.pth"
model.load_state_dict(torch.load(model_weight_path))
model.eval()
with torch.no_grad():
    # predict class
    output = torch.squeeze(model(img))  # 將batch維度壓縮掉
    predict = torch.softmax(output, dim=0)  # 變成概率分佈
    predict_cla = torch.argmax(predict).numpy()  # 獲得最大概率處的索引值
print(class_indict[str(predict_cla)], predict[predict_cla].item())
plt.show()

訓練結果

在這裏插入圖片描述

數據存放

1、./是當前目錄
2、…/是父級目錄
3、/是根目錄
根目錄指邏輯驅動器的最上一級目錄,它是相對子目錄來說的。打開“我的電腦”,雙擊C盤就進入C盤的根目錄,雙擊D盤就進入D盤的根目錄。其它類推。根目錄在文件系統建立時即已被創建,其目的就是存儲子目錄(也稱爲文件夾)或文件的目錄項。電腦中的子目錄很好理解,例如:1、C:\是父目錄,C:\Windows就是C:\的子目錄。2、C:\Windows\System32\就是C:\Windows的子目錄。
在這裏插入圖片描述
在這裏插入圖片描述
在這裏插入圖片描述

訓練自己的數據集

創建新的文件夾,每個文件夾對應一個類別,文件夾下存放同一類別的圖片,修改split_data.py,比如修改父文件夾,子文件夾名稱,路徑等進行數據集和測試集的劃分.
最後再從網上下載某一類別的圖片來進行預測,觀察模型準確率.

split_data.py

import os
from shutil import copy
import random


def mkfile(file):
    if not os.path.exists(file):
        os.makedirs(file)


file = 'flower_data/flower_photos'
flower_class = [cla for cla in os.listdir(file) if ".txt" not in cla]
mkfile('flower_data/train')
for cla in flower_class:
    mkfile('flower_data/train/'+cla)

mkfile('flower_data/val')
for cla in flower_class:
    mkfile('flower_data/val/'+cla)

split_rate = 0.1
for cla in flower_class:
    cla_path = file + '/' + cla + '/'
    images = os.listdir(cla_path)
    num = len(images)
    eval_index = random.sample(images, k=int(num*split_rate))
    for index, image in enumerate(images):
        if image in eval_index:
            image_path = cla_path + image
            new_path = 'flower_data/val/' + cla
            copy(image_path, new_path)
        else:
            image_path = cla_path + image
            new_path = 'flower_data/train/' + cla
            copy(image_path, new_path)
        print("\r[{}] processing [{}/{}]".format(cla, index+1, num), end="")  # processing bar
    print()

print("processing done!")

參考文章

參考WZMIAOMIAO的Github
AlexNet詳細解讀
AlexNet論文

發佈了24 篇原創文章 · 獲贊 6 · 訪問量 3694
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章