【深度學習入門】基於 ResNet50 的狗狗品種識別

1. 效果預覽:

(似乎有奇怪的東西混進去了)

在這裏插入圖片描述

在這裏插入圖片描述

在這裏插入圖片描述

在這裏插入圖片描述

在這裏插入圖片描述

2. ResNet算法詳解:

這個我寫過一個更詳細的:

殘差神經網絡ResNet系列網絡結構詳解:從ResNet到DenseNet

這裏簡單複述一下:

2.1 論文地址:

《Deep Residual Learning for Image Recognition》

2.2 核心思想:

將本來回歸的目標函數H(x)轉化爲F(x)+x,即F(x) = H(x) - x,稱之爲殘差。

2.3 網絡結構:

2.3.1 殘差單元:

ResNet的基本的殘差單元如圖所示:
在這裏插入圖片描述

基本結構如圖,假設每個單元輸入的特徵層爲x,經過兩個卷積層獲得輸出y,將x與y求和即得到了這個單元的輸出;

在訓練時,我們將該單元目標映射(即要趨近的最優解)假設爲F(x) + x,而輸出爲y+x,那麼訓練的目標就變成了使y趨近於F(x)。即去掉映射前後相同的主體部分x,從而突出微小的變化(殘差)。

用數學表達式表示爲:
在這裏插入圖片描述

其中:

  1. x是殘差單元的輸入;
  2. y是殘差單元的輸出;
  3. F(x)是目標映射;
  4. {Wi}是殘差單元中的卷積層;
  5. Ws是一個1x1卷積核大小的卷積,作用是給x降維或升維,從而與輸出y大小一致(因爲需要求和);

2.3.2 改進單元:

同時也可以進一步拓展殘差結構:
在這裏插入圖片描述

原論文中則以VGG爲例:
在這裏插入圖片描述

從VGG的19層,拓展到了34層。

可見使用了殘差單元可以大大加深卷積神經網絡的深度,而且不會影響性能和訓練速度.

2.4 實現代碼:

傳送門:
ResNet-tensorflow

殘差單元的實現:

# block1
net = slim.repeat(res, 2, slim.conv2d, 64, [3, 3],
                  scope='conv1', padding='SAME')
res = net

# block2
net = slim.repeat(res, 2, slim.conv2d, 64, [3, 3],
                  scope='conv2', padding='SAME')

net = tf.add(net, res) # y=F(x)+x

2.5 實驗結果:

在這裏插入圖片描述
在ImageNet數據集上的測試表明,隨着層數的加深,ResNet取得的效果越來越好,有效解決了模型退化的和梯度消失的問題。

3. 數據集簡介:

數據地址:https://s3-us-west-1.amazonaws.com/udacity-aind/dog-project/dogImages.zip

數據集由 UDACITY 提供,包含超過8000張、共133種不同品種的狗狗圖像:

在這裏插入圖片描述

在這裏插入圖片描述
下載之後解壓,並創建類別名的ch_names.txt文件:

猴㹴
阿富汗獵犬
萬能㹴
秋田犬
阿拉斯加雪橇犬
薩摩耶
美國獵狐犬
美國斯塔福郡㹴
美國水獵犬
安納托利亞牧羊犬
澳洲牧牛犬
澳大利亞牧羊犬
澳大利亞㹴
巴辛吉犬
巴吉度獵犬
比格犬
長鬚牧羊犬
法蘭西野狼犬
貝林登㹴
比利時瑪連萊犬
比利時狗牧羊犬
比利時特伏丹犬
伯恩山犬
比熊犬
黑棕棕獵犬
黑俄羅斯㹴
獵犬
布魯泰克浣熊獵犬
邊境牧羊犬
邊境㹴
蘇俄牧羊犬
波士頓㹴
法蘭德斯牧羊犬
拳師犬
帕金獵犬
伯瑞犬
布列塔尼獵犬
布魯塞爾格里豐犬
鬥牛犬
鬥牛㹴
鬥牛獒
凱恩㹴
迦南犬
意大利卡斯羅犬
意大利卡柯基犬
騎士查理王獵犬
乞沙比克獵犬
吉娃娃
中國冠毛犬
中國沙皮犬
鬆獅
克倫伯獵犬
可卡犬
牧羊犬
捲毛尋回獵犬
臘腸犬
大麥町斑點狗
丹迪丁蒙㹴
杜賓犬
波爾多獒犬
可卡犬
英文字母
英國雪達蹲獵犬
英國玩賞犬
恩特雷布赫山地犬
田野獵犬
芬蘭獵犬
平滑毛尋回犬
法國鬥牛犬
德平犬
德國牧羊犬
德國短毛指示犬
德國鋼毛指示犬
巨型雪納瑞犬
峽谷㹴
金毛尋回犬
戈登塞特犬
大丹犬
大比利牛斯犬
大瑞士山狗
靈緹犬
哈瓦那犬
伊比沙獵犬
冰島牧羊犬
愛爾蘭紅白塞特犬
愛爾蘭塞特犬
愛爾蘭㹴
愛爾蘭水獵犬
愛爾蘭獵犬
意大利靈緹犬
日本狆
荷蘭毛獅犬
凱利藍㹴
可蒙犬
庫瓦茲犬
拉布拉多犬
萊克蘭㹴
倫伯格犬
拉薩阿普索犬
羅秦犬
馬爾濟斯犬
曼徹斯特㹴
獒
迷你雪納瑞犬
那不勒斯犬
紐芬蘭犬
諾福克犬
挪威布恩德犬
挪威埃爾克獵犬
挪威隆德犬
諾里奇㹴
新斯科舍獵犬
英國古代牧羊犬 
奧達獵犬
蝴蝶犬 
帕森羅素㹴
獅子狗
彭布羅克威爾士柯基
迷你貝吉格里芬凡丁犬 
法老王獵犬
布勞特獵犬
英國指示犬
博美犬
貴賓犬
葡萄牙水犬
聖伯納犬 
澳洲絲毛㹴
短毛獵狐㹴
藏獒
威爾士史賓格犬
剛毛指示格里芬犬
墨西哥無毛犬
約克夏犬

4. 訓練模型:

不想耗時間訓練的可以下載我訓練好的模型:

鏈接:https://pan.baidu.com/s/1ureXn-oFZ7PAF436r_45Sg
提取碼:xjmd

自己訓練的話,運行 train.py:

# train.py
import os
import torch
from torchvision import datasets
import torchvision.transforms as transforms
import torchvision.models as models
import torch.nn as nn
import torch.optim as optim
import numpy as np
from PIL import ImageFile
ImageFile.LOAD_TRUNCATED_IMAGES = True

def train(n_epochs, loaders, model, optimizer, criterion, use_cuda, save_path):
    """returns trained model"""
    # initialize tracker for minimum validation loss
    valid_loss_min = np.Inf 
    
    for epoch in range(1, n_epochs+1):
        # initialize variables to monitor training and validation loss
        train_loss = 0.0
        valid_loss = 0.0
        
        ###################
        # train the model #
        ###################
        model.train()
        for batch_idx, (data, target) in enumerate(loaders['train']):
            # move to GPU
            if use_cuda:
                data, target = data.cuda(), target.cuda()
            ## find the loss and update the model parameters accordingly
            ## record the average training loss, using something like
            ## train_loss = train_loss + ((1 / (batch_idx + 1)) * (loss.data - train_loss))
            optimizer.zero_grad()
            out = model(data)
            loss = criterion(out, target)
            loss.backward()
            
            optimizer.step()
            
            train_loss = train_loss + ((1 / (batch_idx + 1)) * (loss.data - train_loss))
            
            if batch_idx% 100 == 0:
                print('Epoch %d, Batch %d loss: %.6f' %
                  (epoch, batch_idx + 1, train_loss))
        ######################    
        # validate the model #
        ######################
        model.eval()
        for batch_idx, (data, target) in enumerate(loaders['valid']):
            # move to GPU
            if use_cuda:
                data, target = data.cuda(), target.cuda()
            ## update the average validation loss
                valid_loss = valid_loss + ((1 / (batch_idx + 1)) * (loss.data - valid_loss))
            
        # print training/validation statistics 
        print('Epoch: {} \tTraining Loss: {:.6f} \tValidation Loss: {:.6f}'.format(
            epoch, 
            train_loss,
            valid_loss
            ))
        
        ## TODO: save the model if validation loss has decreased
        if valid_loss < valid_loss_min:
            torch.save(model,save_path)
            print('Validation loss decreased ({:.6f} --> {:.6f}).  Saving model ...'.format(
            valid_loss_min,
            valid_loss))
            valid_loss_min = valid_loss
    # return trained model
    return model


if __name__ == "__main__":
    use_cuda = torch.cuda.is_available()
    print("use_cuda",use_cuda)
    batch_size = 20
    num_workers = 0
    data_directory = 'dogImages/'
    train_directory = os.path.join(data_directory, 'train/')
    valid_directory = os.path.join(data_directory, 'valid/')
    test_directory = os.path.join(data_directory, 'test/')



    standard_normalization = transforms.Normalize(mean=[0.485, 0.456, 0.406],
                                                std=[0.229, 0.224, 0.225])
    data_transforms = {'train': transforms.Compose([transforms.RandomResizedCrop(224),
                                        transforms.RandomHorizontalFlip(),
                                        transforms.ToTensor(),
                                        standard_normalization]),
                    'valid': transforms.Compose([transforms.Resize(256),
                                        transforms.CenterCrop(224),
                                        transforms.ToTensor(),
                                        standard_normalization]),
                    'test': transforms.Compose([transforms.Resize(size=(224,224)),
                                        transforms.ToTensor(), 
                                        standard_normalization])
                    }

    train_data = datasets.ImageFolder(train_directory, transform=data_transforms['train'])
    valid_data = datasets.ImageFolder(valid_directory, transform=data_transforms['valid'])
    test_data = datasets.ImageFolder(test_directory, transform=data_transforms['test'])

    train_loader = torch.utils.data.DataLoader(train_data,
                                            batch_size=batch_size, 
                                            num_workers=num_workers,
                                            shuffle=True)
    valid_loader = torch.utils.data.DataLoader(valid_data,
                                            batch_size=batch_size, 
                                            num_workers=num_workers,
                                            shuffle=False)
    test_loader = torch.utils.data.DataLoader(test_data,
                                            batch_size=batch_size, 
                                            num_workers=num_workers,
                                            shuffle=False)
    loaders = {
        'train': train_loader,
        'valid': valid_loader,
        'test': test_loader
    }

    model = models.resnet50(pretrained=True)

    for param in model.parameters():
        param.requires_grad = False
    num_ftrs = model.fc.in_features
    model.fc = nn.Linear(num_ftrs,133) 
    if use_cuda:
        model.cuda()
    criterion = nn.CrossEntropyLoss()
    optimizer = optim.Adam(model.fc.parameters(), lr=0.0001)  

    n_epochs = 10

    train(n_epochs, loaders, model, optimizer, criterion, use_cuda, 'model.pt')

訓練完把模型文件放到src文件夾下;

然後下載字體文件,放到src文件夾下:

鏈接:https://pan.baidu.com/s/1zt2DoqFUDaHphb-TX8UiJQ
提取碼:ko0t

在這裏插入圖片描述

5. 測試圖片:

創建以下程序 net.py:

from pylab import mpl
import cv2
import os
import torch
import torch.nn as nn
from torchvision import datasets
from torchvision import transforms
from torch.autograd import Variable
import warnings
import numpy as np

from PIL import ImageFont
from PIL import Image
from PIL import ImageDraw

fontC = ImageFont.truetype('./src/platech.ttf', 32, 0)

warnings.filterwarnings("ignore")

mpl.rcParams['font.sans-serif'] = ['FangSong']  # 指定默認字體
mpl.rcParams['axes.unicode_minus'] = False  # 解決保存圖像是負號'-'顯示爲方塊的問題


def drawText(image, addText, x1, y1):

    # mean = np.mean(image[:50, :50], axis=0)
    # color = 255 - np.mean(mean, axis=0).astype(np.int)
    # color = tuple(color)
    color = (20, 255, 20)
    img = Image.fromarray(image)
    draw = ImageDraw.Draw(img)
    draw.text((x1, y1),
              addText.encode("utf-8").decode("utf-8"),
              color, font=fontC)
    imagex = np.array(img)

    return imagex


class Classifier(object):

    def __init__(self):

        self.model = torch.load('./src/model.pt')
        self.model.eval()
        # self.class_names = torch.load('names.class')
        self.loader = transforms.Compose([transforms.Resize((224, 224)),
                                          transforms.ToTensor(),
                                          transforms.Normalize((0.485, 0.456, 0.406),
                                                               (0.229, 0.224, 0.225))])
        with open('./src/ch_names.txt', 'r', encoding='utf-8') as f:
            self.class_names = f.read().splitlines()

    def test(self, pt):

        image = Image.open(pt)
        image_tensor = self.loader(image).float()
        image_tensor = image_tensor.unsqueeze_(0)
        inputs = Variable(image_tensor)

        output = self.model(inputs)
        softmax = nn.Softmax(dim=1)
        preds = softmax(output)
        top_preds = torch.topk(preds, 3)
        pred_breeds = [self.class_names[i] for i in top_preds[1][0]]
        confidence = top_preds[0][0]

        result = self.DrawResultv2(pred_breeds, confidence, pt)

        return result

    def DrawResultv2(self, breeds, confidence, pt, size=512):
        title = "預測品種(置信度):\n"
        for breed, conf in zip(breeds, confidence):
            if conf > 0.005:
                title += "  - {} ({:.0f}%)\n".format(breed, conf*100)
        image = cv2.imdecode(np.fromfile(pt, dtype=np.uint8), 1)
        w = min(image.shape[:2])
        ratio = size / w
        image = cv2.resize(image, None, fx=ratio, fy=ratio)
        image = drawText(image, title, 0, 10)

        print(title)
        return image


if __name__ == '__main__':

    pt = './test_images/eee.jpg'
    net = Classifier()
    result = net.test(pt)
    cv2.imshow('a', result)
    cv2.waitKey(0)

    cv2.destroyAllWindows()

將 pt 改成測試圖片的路徑即可:
在這裏插入圖片描述

測試效果:
在這裏插入圖片描述

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章