pytorch實現性別檢測


卷積神經網絡的訓練是耗時的,很多場合不可能每次都從隨機初始化參數開始訓練網絡。
 
1.訓練
pytorch中自帶幾種常用的深度學習網絡預訓練模型,如VGG、ResNet等。往往為了加快學習的進度,在訓練的初期我們直接加載pre-train模型中預先訓練好的參數,所以這里使用的網絡是:
torchvision.models.Resnet34(pretrained=True)

然后更改其最后的全連接層。因為resnet網絡最后一層分類層fc是對1000種類型進行划分,對於自己的數據集,這里進行的是性別檢測,只有男/女2類,所以修改的代碼為:

#提取fc層中固定的參數
fc_features = model.fc.in_features
#修改類別為2
model.fc = nn.Linear(fc_features, 2)

 

在網上看見一些教程僅對最后一層全連接層的參數進行訓練,所以他們的設定為:

#凍結參數,不訓練卷積層網絡
for param in model_conv.parameters():
    param.requires_grad = False

然后下面就是定義你使用的損失函數,優化器和學習率調整的算法:

注意在這里可以看見優化器中輸入的參數為model_conv.fc.parameters(),即僅對全連接層fc的參數進行調參

#定義使用的損失函數為交叉熵代價函數
criterion = nn.CrossEntropyLoss()
#定義使用的優化器
optimizer_conv = optim.SGD(model_conv.fc.parameters(), lr=0.0001, momentum=0.9)
#設置自動遞減的學習率,等間隔調整學習率,即在25個step時,將學習率調整為 lr*gamma
exp_lr_scheduler = optim.lr_scheduler.StepLR(optimizer_conv, step_size=25, gamma=0.1)

 

但是我訓練過程發現這樣做的效果不是很好,我還是對整個網絡的參數都進行了訓練,所以我沒有進行凍結參數的設定,同時使用的算法中傳入的參數是model_conv.parameters()

這樣得到的最好訓練結果是:

train Loss: 0.1020 Acc: 0.9617 val Loss: 0.0622 Acc: 0.9820 Training complete in 336m 56s Best val Acc: 0.982000 

可見其實還沒有訓練完,還是欠擬合的狀態

⚠️補充知識:optimizer.step()和scheduler.step()的區別:
optimizer.step()通常用在每個mini-batch之中,而scheduler.step()通常用在epoch里面,但是不絕對,可以根據具體的需求來做。只有用了optimizer.step(),模型才會更新,而scheduler.step()是對lr進行調整。

 

然后后面我調了一下優化器,從momentum改成了Adam,並將學習率的調整step_size更改為20,讓其一開始能更快收斂:

#定義的優化器
optimizer_conv = optim.Adam(model_conv.parameters(), lr=0.0001, betas=(0.9, 0.99))
#設置自動遞減的學習率,等間隔調整學習率,即在20個step時,將學習率調整為 lr*gamma
exp_lr_scheduler = optim.lr_scheduler.StepLR(optimizer_conv, step_size=20, gamma=0.1)

 運行結果優化為:

train Loss: 0.0668 Acc: 0.9725
val Loss: 0.0630 Acc: 0.9820 
Training complete in 385m 42s
Best val Acc: 0.986000

 可見效果好了一點,但是還是沒能訓練完,之后又對代碼進行了更改,將step_size調節得更大,使得一開始能夠收斂得快一些,以免后面的學習率過小后一直收斂不下去:

optimizer_conv = optim.Adam(model_conv.parameters(), lr=0.0001, betas=(0.9, 0.99))
#設置自動遞減的學習率,等間隔調整學習率,即在45個step時,將學習率調整為 lr*gamma
exp_lr_scheduler = optim.lr_scheduler.StepLR(optimizer_conv, step_size=45, gamma=0.1)

判斷的代碼也進行了更改,不再是以最高的val acc作為最優參數的選擇,而是選擇train acc最高且train acc大於val acc的訓練參數:

            # deep copy the model
            # 對模型進行深度復制
            
            if phase == 'train' and epoch_acc > best_train_acc:
                temp = epoch_acc
            if phase =='val' and epoch_acc > 0 and epoch_acc < temp:
                best_train_acc = temp
                best_val_acc = epoch_acc
                best_iteration = epoch
                best_model_wts = copy.deepcopy(model.state_dict())

這樣的返回值果然更好了一點:

Training complete in 500m 10s
Best epoch: 166.000000
Best train Acc: 0.985175
Best val Acc: 0.984000

 

代碼運行過程中出現了錯誤:

RuntimeError: Input type (torch.cuda.FloatTensor) and weight type (torch.FloatTensor) should be the same 

這個的解決辦法是為model_conv也添加.to(device):

model_conv.to(device)

 

另一個錯誤是:

RuntimeError: Expected object of backend CPU but got backend CUDA for argument #4 'mat1' 

覺得原因可能是因為我的機器上面只有一個cuda,所以要顯示指明使用的是'cuda:0':

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

或者是因為一開始我將model_conv.to(device)寫錯了位置,寫到了model_conv.fc = nn.Linear(fc_features, 2)之前,寫到其后面即可

 

所以最后的訓練代碼為:
# coding:utf8
from torchvision import datasets, models
from torch import nn, optim
from torchvision import transforms as T
from torch.utils import data

import os
import copy
import time
import torch

#首先進行數據的處理
data_dir = './data'
device = torch.device("cuda:0" if torch.cuda.is_available() else "cpu")

#轉換圖片數據
normalize = T.Normalize([0.485, 0.456, 0.406],[0.229, 0.224, 0.225])
data_transforms ={
    'train': T.Compose([
        T.RandomResizedCrop(224),#從圖片中心截取
                T.RandomHorizontalFlip(),#隨機水平翻轉給定的PIL.Image,翻轉概率為0.5  
        T.ToTensor(),#轉成Tensor格式,大小范圍為[0,1]
        normalize
    ]),

    'val': T.Compose([
        T.Resize(256),#重新設定大小 
        T.CenterCrop(224),
        T.ToTensor(),
        normalize
    ]),
}

#加載圖片
#man的label為0, woman的label為1
image_datasets = {x : datasets.ImageFolder(os.path.join(data_dir, x), data_transforms[x]) for x in ['train', 'val']}

#得到train和val中的數據量
dataset_sizes = {x : len(image_datasets[x].imgs) for x in ['train', 'val']}
dataloaders = {x : data.DataLoader(image_datasets[x], batch_size=4, shuffle=True,num_workers=4) for x in ['train', 'val']}



#然后選擇使用的模型
model_conv = models.resnet34(pretrained=True)
#凍結參數,不訓練卷積層網絡
#for param in model_conv.parameters():
#    param.requires_grad = False

#提取fc全連接層中固定的參數,后面的訓練只對全連接層的參數進行優化
fc_features = model_conv.fc.in_features
#修改類別為2,即man和woman
model_conv.fc = nn.Linear(fc_features, 2)
model_conv.to(device)
#定義使用的損失函數為交叉熵代價函數
criterion = nn.CrossEntropyLoss()
#定義使用的優化器
#optimizer_conv = optim.SGD(model_conv.fc.parameters(), lr=0.0001, momentum=0.9)
#optimizer_conv = optim.SGD(model_conv.parameters(), lr=0.0001, momentum=0.9) 
optimizer_conv = optim.Adam(model_conv.parameters(), lr=0.0001, betas=(0.9, 0.99))
#設置自動遞減的學習率,等間隔調整學習率,即在7個step時,將學習率調整為 lr*gamma
exp_lr_scheduler = optim.lr_scheduler.StepLR(optimizer_conv, step_size=45, gamma=0.1)
#exp_lr_scheduler = optim.lr_scheduler.ReduceLROnPlateau(optimizer_conv, mode='min', verbose=True)


# 訓練模型
# 參數說明:
# model:待訓練的模型
# criterion:評價函數
# optimizer:優化器
# scheduler:學習率
# num_epochs:表示實現完整訓練的次數,一個epoch表示一整個訓練周期
def train_model(model, criterion, optimizer, scheduler, num_epochs=200):
    # 定義訓練開始時間
    since = time.time()
    #用於保存最優的權重
    best_model_wts = copy.deepcopy(model.state_dict())
    #最優精度值
    best_train_acc = 0.0
    best_val_acc = 0.0
    best_iteration = 0

    # # meters,統計指標:平滑處理之后的損失,還有混淆矩陣
    # loss_meter = meter.AverageValueMeter()#能夠計算所有數的平均值和標准差,用來統計一個epoch中損失的平均值
    # confusion_matrix = meter.ConfusionMeter(2)#用來統計分類問題中的分類情況,是一個比准確率更詳細的統計指標

    # 對整個數據集進行num_epochs次訓練
    for epoch in range(num_epochs):
        print('Epoch {}/{}'.format(epoch, num_epochs - 1))
        print('-' * 10)
        
        #用於存儲train acc還沒有與val acc比較之前的值
        temp = 0
        # Each epoch has a training and validation phase
        # 每輪訓練訓練包含`train`和`val`的數據
        for phase in ['train', 'val']:
            if phase == 'train':
                # 學習率步進
                scheduler.step()
                # 設置模型的模式為訓練模式(因為在預測模式下,采用了`Dropout`方法的模型會關閉部分神經元)
                model.train()  # Set model to training mode
            else:
            # 預測模式
                model.eval()   # Set model to evaluate mode

            running_loss = 0.0
            running_corrects = 0

            # Iterate over data.
            # 遍歷數據,這里的`dataloaders`近似於一個迭代器,每一次迭代都生成一批`inputs`和`labels`數據,
            # 一批有四個圖片,一共有dataset_sizes['train']/4或dataset_sizes['val']/4批
            # 這里循環幾次就看有幾批數據
            for inputs, labels in dataloaders[phase]:
                inputs = inputs.to(device)   # 當前批次的訓練輸入 
                labels = labels.to(device)  # 當前批次的標簽輸入
                # print('input : ', inputs)
                # print('labels : ', labels)

                # 將梯度參數歸0
                optimizer.zero_grad()

                # 前向計算
                # track history if only in train
                with torch.set_grad_enabled(phase == 'train'):
                    # 相應輸入對應的輸出
                    outputs = model(inputs)
                    # print('outputs : ', outputs)
                    # 取輸出的最大值作為預測值preds,dim=1,得到每行中的最大值的位置索引,用來判別其為0或1
                    _, preds = torch.max(outputs, 1)
                    # print('preds : ', preds)
                    # 計算預測的輸出與實際的標簽之間的誤差
                    loss = criterion(outputs, labels)
                    # backward + optimize only if in training phase
                    if phase == 'train':
                    # 對誤差進行反向傳播
                        loss.backward()
                        #scheduler.step(loss) #當使用的學習率遞減函數為optim.lr_scheduler.ReduceLROnPlateau時,使用在這里
                        # 執行優化器對梯度進行優化
                        optimizer.step()

                        # loss_meter.add(loss.item())
                        # confusion_matrix.add(outputs.detach(), labels.detach()) 

                # statistics
                # 計算`running_loss`和`running_corrects`
                #loss.item()得到的是此時損失loss的值
                #inputs.size(0)得到的是一批圖片的數量,這里為4
                #兩者相乘得到的是4張圖片的總損失
                #疊加得到所有數據的損失
                running_loss += loss.item() * inputs.size(0)
                #torch.sum(preds == labels.data)判斷得到的結果中有幾個正確,running_corrects得到四個中正確的個數
                #疊加得到所有數據中判斷成功的個數
                running_corrects += torch.sum(preds == labels.data)

        # 當前輪的損失,除以所有數據量個數得到平均loss值
            epoch_loss = running_loss / dataset_sizes[phase]
            # 當前輪的精度,除以所有數據量個數得到平均准確度
            epoch_acc = running_corrects.double() / dataset_sizes[phase]

            print('{} Loss: {:.4f} Acc: {:.4f}'.format(phase, epoch_loss, epoch_acc))

            # deep copy the model
            # 對模型進行深度復制         
            if phase == 'train' and epoch_acc > best_train_acc:
                temp = epoch_acc
            if phase =='val' and epoch_acc > 0 and epoch_acc < temp:
                best_train_acc = temp
                best_val_acc = epoch_acc
                best_iteration = epoch
                best_model_wts = copy.deepcopy(model.state_dict())

    # 計算訓練所需要的總時間
    time_elapsed = time.time() - since
    print('Training complete in {:.0f}m {:.0f}s'.format(time_elapsed // 60, time_elapsed % 60))
    print('Best epoch: {:4f}'.format(best_iteration))  
    print('Best train Acc: {:4f}'.format(best_train_acc)) 
    print('Best val Acc: {:4f}'.format(best_val_acc))

    # load best model weights
    # 加載模型的最優權重
    model.load_state_dict(best_model_wts)
    return model


if __name__ == '__main__':
    model_train = train_model(model_conv, criterion, optimizer_conv, exp_lr_scheduler)
    torch.save(model_train, 'GenderTest.pkl')

 

 
2.測試

使用上面生成的GenderTest.pkl進行測試:

測試數據集dataset_test.py的設計為:

# coding:utf8
import os
from torchvision import transforms as T
from PIL import Image
from torch.utils import data

class GenderData(data.Dataset):
    def __init__(self, root, transforms=None):
        #將圖片路徑存儲在imgs列表中
        imgs = [os.path.join(root, img) for img in os.listdir(root)]

        self.imgs = imgs
        if transforms is None:
            #設置對數據進行轉換的transform
            normalize = T.Normalize([0.485, 0.456, 0.406],[0.229, 0.224, 0.225])
            self.transforms = T.Compose([
                T.Resize(224),
                T.CenterCrop(224),
                T.ToTensor(),
                normalize
                ])

    def __getitem__(self, index):
        img_path = self.imgs[index]
        #得到圖片數據
        data = Image.open(img_path)
        #對數據進行轉換
        data = self.transforms(data)
        return data


    def __len__(self):
        return len(self.imgs)

 

測試代碼test.py為:

# coding:utf8
import visdom
from datasets_test import GenderData
from torch.utils import data
from torchvision.utils import make_grid
import torch

def visualize(data, preds):
    viz = visdom.Visdom(env='main')
    # print(data.size()) #一開始的大小為torch.Size([4, 3, 224, 224])
    out = make_grid(data) #這樣得到的輸出的數據就是將四張圖合成了一張圖的大小,為
    # print(out.size()) #torch.Size([3, 228, 906])
    #因為反標准化時需要將圖片的維度從(channels,imgsize,imgsieze)變成(imgsize,imgsieze,channels),這樣才能與下面的std,mean正確計算
    inp = torch.transpose(out, 0, 2)
    # print(inp.size()) #返回torch.Size([906, 228, 3])
    mean = torch.FloatTensor([0.485, 0.456, 0.406])
    std = torch.FloatTensor([0.229, 0.224, 0.225])
    inp = std * inp + mean
    #計算完后還是要將維度變回來,所以再進行一次轉換
    inp = torch.transpose(inp, 0, 2)
    # print(inp.size()) #返回torch.Size([3, 228, 906])

    #注意,這里是因為設置了batch_size為四,所以title中才這樣,如果你的batch_size不是4這里需要做一些更改
    viz.images(inp, opts=dict(title='{},{},{},{}'.format(preds[0].item(), preds[1].item(), preds[2].item(), preds[3].item())))
    #比如下面這個就是將batch_size改成1的結果
    # viz.images(inp, opts=dict(title='{}'.format(preds[0].item())))

def self_dataset():
    data_test_root = './data/test1' #測試數據集所在的路徑
    test_data = GenderData(data_test_root)
#如果只測試一張圖片,這里batch_size要改成1 dataloaders
= data.DataLoader(test_data, batch_size=4 ,shuffle=True,num_workers=4) for inputs in dataloaders: inputs = inputs.to(device) # 當前批次的訓練輸入 outputs = model_test(inputs) _, preds = torch.max(outputs, 1) visualize(inputs,preds) if __name__ == '__main__': device = torch.device('cuda:0' if torch.cuda.is_available() else 'cpu') print(device) #導入上面訓練得到的效果最佳的網絡,因為測試是在只有cpu的機器上跑的,所以這里要添加map_location='cpu' model_test = torch.load('./GenderTest.pkl',map_location='cpu') #如果你是在有GPU的機器上跑的,可以刪掉map_location='cpu',並添加一行 #model_test.to(device) model_test.eval() dataloaders = self_dataset()

 要將visdom打開:

python -m visdom.server

然后運行:

python test.py

即可以在visdom中看見結果,如下面的圖片被判斷為女生,右上角顯示1:

 當然,這其實是國榮哥的劇照,只能說天生麗質的,應該不是我訓練的效果問題


測試時出現一個問題:

OSError: cannot identify image file './data/test/.DS_Store'

這是因為mac系統中會自動生成一個.DS_Store隱藏文件,這里保存着針對這個目錄的特殊信息和設置配置,例如查看方式、圖標大小以及這個目錄的一些附屬元數據

而當我們想要打開圖片的時候它會被當作一個圖片文件路徑加載,進而報錯,因為它並不是一個圖片文件,解決辦法是在該圖片文件夾下輸入刪除命令:

sudo find / -name ".DS_Store" -depth -exec rm {} \;

 或者更簡單的方法是到圖片文件夾處直接運行:

rm .DS_Store

 

后面換成使用resnet18也能達到相近的效果,大家可以試試

 

 


 


免責聲明!

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



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