動手學深度學習PyTorch版-task10

目錄:

task1:https://blog.csdn.net/zahidzqj/article/details/104293563

task2:https://blog.csdn.net/zahidzqj/article/details/104309590

task3:https://blog.csdn.net/zahidzqj/article/details/104319328

task4:https://blog.csdn.net/zahidzqj/article/details/104324196

task5:https://blog.csdn.net/zahidzqj/article/details/104324349

task6:https://blog.csdn.net/zahidzqj/article/details/104451810

task8:https://blog.csdn.net/zahidzqj/article/details/104452274

task9:https://blog.csdn.net/zahidzqj/article/details/104452480

task10:本章節


 

1 圖像分類案例2

學習網址:https://www.boyuai.com/elites/course/cZu18YmweLv10OeV/jupyter/6N7O3g4unlGHLY_kfyTa-

在本節中,我們將解決Kaggle競賽中的犬種識別挑戰,比賽的網址是https://www.kaggle.com/c/dog-breed-identification 在這項比賽中,我們嘗試確定120種不同的狗。該比賽中使用的數據集實際上是著名的ImageNet數據集的子集。

數據集:直接在kaggle官網下載(速度很快)

resnet-34預訓練模型,提前下載。

train和test目錄下分別是訓練集和測試集的圖像,訓練集包含10,222張圖像,測試集包含10,357張圖像,圖像格式都是JPEG,每張圖像的文件名是一個唯一的id。labels.csv包含訓練集圖像的標籤,文件包含10,222行,每行包含兩列,第一列是圖像id,第二列是狗的類別。狗的類別一共有120種。

我們希望對數據進行整理,方便後續的讀取,我們的主要目標是:

  • 從訓練集中劃分出驗證數據集,用於調整超參數。劃分之後,數據集應該包含4個部分:劃分後的訓練集、劃分後的驗證集、完整訓練集、完整測試集
  • 對於4個部分,建立4個文件夾:train, valid, train_valid, test。在上述文件夾中,對每個類別都建立一個文件夾,在其中存放屬於該類別的圖像。前三個部分的標籤已知,所以各有120個子文件夾,而測試集的標籤未知,所以僅建立一個名爲unknown的子文件夾,存放所有測試數據。

我們希望整理後的數據集目錄結構爲:

我們希望整理後的數據集目錄結構爲:

準備好之後,將下文代碼直接放在py文件中,根據自己在路徑在代碼中做修改,可在本地訓練。

代碼中分爲不同的功能模塊

數據重組模塊:由於數據集較大,處理過程較慢,耐心等待

# https://www.kaggle.com/boyuai/boyu-d2l-dog-breed-identification-imagenet-dogs
import torch
import torch.nn as nn
import torch.optim as optim
import torchvision
import torchvision.transforms as transforms
import torchvision.models as models
import os
import shutil
import time
import pandas as pd
import random
# 設置隨機數種子
random.seed(0)
torch.manual_seed(0)
torch.cuda.manual_seed(0)
data_dir = 'dog-breed-identification'  # 數據集目錄
label_file, train_dir, test_dir = 'labels.csv', 'train', 'test'  # data_dir中的文件夾、文件
new_data_dir = './train_valid_test'  # 整理之後的數據存放的目錄
valid_ratio = 0.1  # 驗證集所佔比例

def mkdir_if_not_exist(path):
    # 若目錄path不存在,則創建目錄
    if not os.path.exists(os.path.join(*path)):
        os.makedirs(os.path.join(*path))
        
def reorg_dog_data(data_dir, label_file, train_dir, test_dir, new_data_dir, valid_ratio):
    # 讀取訓練數據標籤
    labels = pd.read_csv(os.path.join(data_dir, label_file))
    id2label = {Id: label for Id, label in labels.values}  # (key: value): (id: label)

    # 隨機打亂訓練數據
    train_files = os.listdir(os.path.join(data_dir, train_dir))
    random.shuffle(train_files)    

    # 原訓練集
    valid_ds_size = int(len(train_files) * valid_ratio)  # 驗證集大小
    for i, file in enumerate(train_files):
        img_id = file.split('.')[0]  # file是形式爲id.jpg的字符串
        img_label = id2label[img_id]
        if i < valid_ds_size:
            mkdir_if_not_exist([new_data_dir, 'valid', img_label])
            shutil.copy(os.path.join(data_dir, train_dir, file),
                        os.path.join(new_data_dir, 'valid', img_label))
        else:
            mkdir_if_not_exist([new_data_dir, 'train', img_label])
            shutil.copy(os.path.join(data_dir, train_dir, file),
                        os.path.join(new_data_dir, 'train', img_label))
        mkdir_if_not_exist([new_data_dir, 'train_valid', img_label])
        shutil.copy(os.path.join(data_dir, train_dir, file),
                    os.path.join(new_data_dir, 'train_valid', img_label))

    # 測試集
    mkdir_if_not_exist([new_data_dir, 'test', 'unknown'])
    for test_file in os.listdir(os.path.join(data_dir, test_dir)):
        shutil.copy(os.path.join(data_dir, test_dir, test_file),
                    os.path.join(new_data_dir, 'test', 'unknown'))

reorg_dog_data(data_dir, label_file, train_dir, test_dir, new_data_dir, valid_ratio)

圖像處理和訓練模塊:


#圖像增強
transform_train = transforms.Compose([
    # 隨機對圖像裁剪出面積爲原圖像面積0.08~1倍、且高和寬之比在3/4~4/3的圖像,再放縮爲高和寬均爲224像素的新圖像
    transforms.RandomResizedCrop(224, scale=(0.08, 1.0),  
                                 ratio=(3.0/4.0, 4.0/3.0)),
    # 以0.5的概率隨機水平翻轉
    transforms.RandomHorizontalFlip(),
    # 隨機更改亮度、對比度和飽和度
    transforms.ColorJitter(brightness=0.4, contrast=0.4, saturation=0.4),
    transforms.ToTensor(),
    # 對各個通道做標準化,(0.485, 0.456, 0.406)和(0.229, 0.224, 0.225)是在ImageNet上計算得的各通道均值與方差
    transforms.Normalize([0.485, 0.456, 0.406], [0.229, 0.224, 0.225])  # ImageNet上的均值和方差
])

# 在測試集上的圖像增強只做確定性的操作
transform_test = transforms.Compose([
    transforms.Resize(256),
    # 將圖像中央的高和寬均爲224的正方形區域裁剪出來
    transforms.CenterCrop(224),
    transforms.ToTensor(),
    transforms.Normalize([0.485, 0.456, 0.406], [0.229, 0.224, 0.225])
])

#讀取數據
# new_data_dir目錄下有train, valid, train_valid, test四個目錄
# 這四個目錄中,每個子目錄表示一種類別,目錄中是屬於該類別的所有圖像
train_ds = torchvision.datasets.ImageFolder(root=os.path.join(new_data_dir, 'train'),
                                            transform=transform_train)
valid_ds = torchvision.datasets.ImageFolder(root=os.path.join(new_data_dir, 'valid'),
                                            transform=transform_test)
train_valid_ds = torchvision.datasets.ImageFolder(root=os.path.join(new_data_dir, 'train_valid'),
                                            transform=transform_train)
test_ds = torchvision.datasets.ImageFolder(root=os.path.join(new_data_dir, 'test'),
                                            transform=transform_test)

batch_size = 128
train_iter = torch.utils.data.DataLoader(train_ds, batch_size=batch_size, shuffle=True)#308
valid_iter = torch.utils.data.DataLoader(valid_ds, batch_size=batch_size, shuffle=True)#346
train_valid_iter = torch.utils.data.DataLoader(train_valid_ds, batch_size=batch_size, shuffle=True)#344
test_iter = torch.utils.data.DataLoader(test_ds, batch_size=batch_size, shuffle=False)  # shuffle=False  36

'''
#定義模型
#這個比賽的數據屬於ImageNet數據集的子集,我們使用微調的方法,選用在ImageNet完整數據集上預訓練的模型來抽取圖像特徵,以作爲自定義小規模輸出網絡的輸入。
#此處我們使用與訓練的ResNet-34模型,直接複用預訓練模型在輸出層的輸入,即抽取的特徵,然後我們重新定義輸出層,
#本次我們僅對重定義的輸出層的參數進行訓練,而對於用於抽取特徵的部分,我們保留預訓練模型的參數。
'''
def get_net(device):
    finetune_net = models.resnet34(pretrained=False)  # 預訓練的resnet34網絡
    finetune_net.load_state_dict(torch.load('resnet34-333f7ec4.pth'))
    for param in finetune_net.parameters():  # 凍結參數
        param.requires_grad = False
    # 原finetune_net.fc是一個輸入單元數爲512,輸出單元數爲1000的全連接層
    # 替換掉原finetune_net.fc,新finetuen_net.fc中的模型參數會記錄梯度
    finetune_net.fc = nn.Sequential(
        nn.Linear(in_features=512, out_features=256),
        nn.ReLU(),
        nn.Linear(in_features=256, out_features=120)  # 120是輸出類別數
    )
    return finetune_net

def evaluate_loss_acc(data_iter, net, device):
    # 計算data_iter上的平均損失與準確率
    loss = nn.CrossEntropyLoss()
    is_training = net.training  # Bool net是否處於train模式
    net.eval()
    l_sum, acc_sum, n = 0, 0, 0
    with torch.no_grad():
        for X, y in data_iter:
            X, y = X.to(device), y.to(device)
            y_hat = net(X)
            l = loss(y_hat, y)
            l_sum += l.item() * y.shape[0]
            acc_sum += (y_hat.argmax(dim=1) == y).sum().item()
            n += y.shape[0]
    net.train(is_training)  # 恢復net的train/eval狀態
    return l_sum / n, acc_sum / n

def train(net, train_iter, valid_iter, num_epochs, lr, wd, device, lr_period,
          lr_decay):
    loss = nn.CrossEntropyLoss()
    optimizer = optim.SGD(net.fc.parameters(), lr=lr, momentum=0.9, weight_decay=wd)
    net = net.to(device)
    for epoch in range(num_epochs):
        train_l_sum, n, start = 0.0, 0, time.time()
        if epoch > 0 and epoch % lr_period == 0:  # 每lr_period個epoch,學習率衰減一次
            lr = lr * lr_decay
            for param_group in optimizer.param_groups:
                param_group['lr'] = lr
        for X, y in train_iter:
            X, y = X.to(device), y.to(device)
            optimizer.zero_grad()
            y_hat = net(X)
            l = loss(y_hat, y)
            l.backward()
            optimizer.step()
            train_l_sum += l.item() * y.shape[0]
            n += y.shape[0]
        time_s = "time %.2f sec" % (time.time() - start)
        if valid_iter is not None:
            valid_loss, valid_acc = evaluate_loss_acc(valid_iter, net, device)
            epoch_s = ("epoch %d, train loss %f, valid loss %f, valid acc %f, "
                       % (epoch + 1, train_l_sum / n, valid_loss, valid_acc))
        else:
            epoch_s = ("epoch %d, train loss %f, "
                       % (epoch + 1, train_l_sum / n))
        print(epoch_s + time_s + ', lr ' + str(lr))

#
num_epochs, lr_period, lr_decay = 20, 10, 0.1
lr, wd = 0.03, 1e-4
device = torch.device('cuda' if torch.cuda.is_available() else 'cpu')
net = get_net(device)
#訓練模式一:帶有交叉驗證,用於調參
train(net, train_valid_iter, None, num_epochs, lr, wd, device, lr_period, lr_decay)
#訓練模式二:沒有交叉驗證,也可直接訓練
#train(net, train_iter, valid_iter, num_epochs, lr, wd, device, lr_period, lr_decay)

結果展示:

訓練模式1

訓練模式2 

對測試集分類並提交結果

用訓練好的模型對測試數據進行預測。比賽要求對測試集中的每張圖片,都要預測其屬於各個類別的概率。(這部分運行也很慢,因爲測試集很大)

preds = []
for X, _ in test_iter:
    #X = X.to(device)
    output = net(X)
    output = torch.softmax(output, dim=1)
    preds += output.tolist()
ids = sorted(os.listdir(os.path.join(new_data_dir, 'test/unknown')))
with open('submission.csv', 'w') as f:
    f.write('id,' + ','.join(train_valid_ds.classes) + '\n')
    for i, output in zip(ids, preds):
        f.write(i.split('.')[0] + ',' + ','.join(
            [str(num) for num in output]) + '\n')

2 GAN

對於gan網絡的理解,主要是Gan 網絡由兩個網絡組成,一個是生成網絡Generator 一個是判別網絡Discriminator,生成網絡用來生成假的,判別網絡用來判別,真的數據和生成網絡生成的假數據。所以不僅僅能用來生成圖片,還有語音片段。

爲了達到這個目的,設計損失函數是非常重要的,我們可以假想是兩個人在左右競爭,一個人想往上走,一個人嚮往下走,所以損失函數就很自然是一個minimax的形式。

給我的感覺G更像是一個迴歸,而D是一個分類性質的模型。所以很自然我們用交叉熵作爲D的損失函數。

生成器損失函數是,在實際中,考慮到梯度消失問題,會對損失函數修改:

 

D和G組合成一個具有綜合目標函數的“minimax”:

 

 

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