【Pytorch神经网络实战案例】29 【代码汇总】GitSet模型进行步态与身份识别(CASIA-B数据集)
生活随笔
收集整理的這篇文章主要介紹了
【Pytorch神经网络实战案例】29 【代码汇总】GitSet模型进行步态与身份识别(CASIA-B数据集)
小編覺(jué)得挺不錯(cuò)的,現(xiàn)在分享給大家,幫大家做個(gè)參考.
?
1 GaitSet_DataLoader.py
import numpy as np # 引入基礎(chǔ)庫(kù) import os import torch.utils.data as tordata from PIL import Image from tqdm import tqdm import random# 1.1定義函數(shù),加載文件夾的文件名稱# load_data函數(shù), 分為3個(gè)步驟:# def load_data(dataset_path,imgresize,label_train_num,label_shuffle): # 完成了整體數(shù)據(jù)集的封裝# 主要分為三個(gè)步驟# ①以人物作為標(biāo)簽,將完整的數(shù)據(jù)集分為兩部分,分別用于訓(xùn)練和測(cè)試。# ②分別根據(jù)訓(xùn)練集和測(cè)試集中的人物標(biāo)簽遍歷文件夾,獲得對(duì)應(yīng)的圖片文件名稱。# ③用torch.utils.data接口將圖片文件名稱轉(zhuǎn)化為數(shù)據(jù)集, 使其能夠?qū)D片載入并返回。label_str = sorted(os.listdir(dataset_path)) # 以人物為標(biāo)簽# 將不完整的樣本忽略,只載入完整樣本removelist = ['005','026','037','079','109','088','068','048'] # 對(duì)數(shù)據(jù)集中樣本不完整的人物標(biāo)簽進(jìn)行過(guò)濾,留下可用樣本。代碼中不完整的人物標(biāo)簽可以通過(guò)調(diào)用load_dir函數(shù)來(lái)查找。for removename in removelist:if removename in label_str:label_str.remove(removename)print("label_str",label_str) # -start--------根據(jù)亂序標(biāo)志來(lái)處理樣本標(biāo)簽順序,并將其分為訓(xùn)練集和測(cè)試集----label_index = np.arange(len(label_str)) # 序列數(shù)組if label_shuffle:np.random.seed(0)# 打亂數(shù)組順序label_shuffle_index = np.random.permutation( len(label_str) )train_list = label_shuffle_index[0:label_train_num]test_list = label_shuffle_index[label_train_num:]else:train_list = label_index[0:label_train_num]test_list = label_index[label_train_num:] # -end--------根據(jù)亂序標(biāo)志來(lái)處理樣本標(biāo)簽順序,并將其分為訓(xùn)練集和測(cè)試集----print("train_list",test_list)# 加載人物列表中的圖片文件名稱data_seq_dir,data_label,meta_data = load_dir(dataset_path,train_list,label_str) # 代碼調(diào)用load_dir函數(shù),將標(biāo)簽列表所對(duì)應(yīng)的圖片文件名稱載入。①test_data_seq_dir, test_data_label, test_meta_data = load_dir(dataset_path, test_list, label_str) # 代碼調(diào)用load_dir函數(shù),將標(biāo)簽列表所對(duì)應(yīng)的圖片文件名稱載入。②# 將圖片文件名稱轉(zhuǎn)化為數(shù)據(jù)集train_source = DataSet(data_seq_dir, data_label, meta_data, imgresize,True) # 調(diào)用自定義類DataSet, 返回PyTorch支持的數(shù)據(jù)集對(duì)象,且只對(duì)訓(xùn)練集進(jìn)行緩存處理,測(cè)試集不做緩存處理。①# test數(shù)據(jù)不緩存test_source = DataSet(test_data_seq_dir, test_data_label, test_meta_data, imgresize, False) # 調(diào)用自定義類DataSet, 返回PyTorch支持的數(shù)據(jù)集對(duì)象,且只對(duì)訓(xùn)練集進(jìn)行緩存處理,測(cè)試集不做緩存處理。②return train_source,test_source# 1.2 實(shí)現(xiàn)load_dir函數(shù)加載圖片文件名稱, def load_dir(dataset_path,label_index,label_str):# 在load_dir函數(shù)中, 通過(guò)文件夾的逐級(jí)遍歷, 將標(biāo)簽列表中每個(gè)人物的圖片文件名稱載入。# 該函數(shù)返回3個(gè)列表對(duì)象:圖片文件名稱、圖片文件名稱對(duì)應(yīng)的標(biāo)簽索引、圖片文件名稱對(duì)應(yīng)的元數(shù)據(jù)(人物、行走條件、拍攝角度)data_seq_dir,data_label,meta_data = [],[],[]for i_label in label_index: # 獲取樣本個(gè)體label_path = os.path.join(dataset_path, label_str[i_label]) # 拼接目錄for _seq_type in sorted(os.listdir(label_path)): # 獲取樣本類型,普通條件、穿大衣、攜帶物品seq_type_path = os.path.join(label_path, _seq_type) # 拼接目錄for _view in sorted(os.listdir(seq_type_path)): # 獲取拍攝角度_seq_dir = os.path.join(seq_type_path, _view) # 拼接圖片目錄if len(os.listdir(_seq_dir)) > 0: # 有圖片data_seq_dir.append(_seq_dir) # 圖片目錄data_label.append(i_label) # 圖片目錄對(duì)應(yīng)的標(biāo)簽meta_data.append((label_str[i_label], _seq_type, _view))else:print("No files:", _seq_dir) # 輸出數(shù)據(jù)集中樣本不完整的標(biāo)簽。# 當(dāng)發(fā)現(xiàn)某個(gè)標(biāo)簽文件夾中沒(méi)有圖片時(shí)會(huì)將該標(biāo)簽輸出。在使用時(shí),可以先用load_dir函數(shù)將整個(gè)數(shù)據(jù)集遍歷一遍, 并根據(jù)輸出樣本不完整的標(biāo)簽,回填到第18行代碼。return data_seq_dir, data_label, meta_data # 返回結(jié)果# 1.3 實(shí)現(xiàn)定義數(shù)據(jù)類DataSet # PyTorch提供了一個(gè)torch.utils.data接口,可以用來(lái)對(duì)數(shù)據(jù)集進(jìn)行封裝。 # 在實(shí)現(xiàn)時(shí),只需要繼承torch.utils.data.Dataset類,并重載其__getitem__方法。 # 在使用時(shí),框架會(huì)向getitem方法傳入索引index。在__getitem__方法內(nèi)部,根據(jù)指定index加載數(shù)據(jù)。 class DataSet(tordata.DataLoader):def __init__(self,data_seq_dir,data_label,meta_data,imgresize,cache=True): # 初始化self.data_seq_dir = data_seq_dir # 存儲(chǔ)圖片文件名稱self.data = [None] * len(self.data_seq_dir) # 存放圖片self.cache = cache # 緩存標(biāo)志self.meta_data = meta_data # 數(shù)據(jù)的元信息self.data_label = np.asarray(data_label) # 存放標(biāo)簽self.imgresize = int(imgresize) # 載入的圖片大小self.cut_padding = int(float(imgresize)/64*10) # 指定圖片裁剪的大小def load_all_data(self): # 加載所有數(shù)據(jù)for i in tqdm(range(len(self.data_seq_dir))):self.__getitem__(i)def __loader__(self,path): # 讀取圖片并裁剪frame_imgs = self.img2xarray(path)/255.0# 將圖片橫軸方向的前10列與后10列去掉frame_imgs = frame_imgs[:,:,self.cut_padding:-self.cut_padding]return frame_imgsdef __getitem__(self, index): # 加載指定索引數(shù)據(jù)if self.data[index] is None: # 第一次加載data = self.__loader__(self.data_seq_dir[index])else:data = self.data[index]if self.cache : # 保存到緩存里self.data[index] = datareturn data,self.meta_data[index],self.data_label[index]def img2xarray(self,file_path): # 讀取指定路徑的數(shù)據(jù)frame_list = [] # 存放圖片數(shù)據(jù)imgs = sorted(list(os.listdir(file_path)))for _img in imgs : # 讀取圖片,放到數(shù)組里_img_path = os.path.join(file_path, _img)if os.path.isfile(_img_path):img = np.asarray(Image.open(_img_path).resize((self.imgresize, self.imgresize)))if len(img.shape) == 3: # 加載預(yù)處理后的圖片frame_list.append(img[..., 0])else:frame_list.append(img)return np.asarray(frame_list, dtype=np.float) # [幀數(shù),高,寬]def __len__(self): # 計(jì)算數(shù)據(jù)集長(zhǎng)度return len(self.data_seq_dir)# 1.5 實(shí)現(xiàn)自定義采集器 # 步態(tài)識(shí)別模型需要通過(guò)三元損失進(jìn)行訓(xùn)練。三元損失可以輔助模型特征提取的取向,使相同標(biāo)簽的特征距離更近,不同標(biāo)簽的特征距離更遠(yuǎn)。 # 由于三元損失需要輸入的批次數(shù)據(jù)中,要包含不同標(biāo)簽(這樣才可以使用矩陣方式進(jìn)行正/負(fù)樣本的采樣),需要額外對(duì)數(shù)據(jù)集進(jìn)行處理。 # 這里使用自定義采樣器完成含有不同標(biāo)簽數(shù)據(jù)的采樣功能。# torch.utils.data.sampler類需要配合torch.utils.data.Data Loader模塊一起使用。# torch.utils.data.DataLoader是PyTorch中的數(shù)據(jù)集處理接口。# 根據(jù)torch.utils.data.sampler類的采樣索引,在數(shù)據(jù)源中取出指定的數(shù)據(jù),并放到collate_fn中進(jìn)行二次處理,最終返回所需要的批次數(shù)據(jù)。# 實(shí)現(xiàn)自定義采樣器TripletSampler類,來(lái)從數(shù)據(jù)集中選取不同標(biāo)簽的索引,并將其返回。# 再將兩個(gè)collate_fn函數(shù)collate_fn_for_train、collate_fn_for_test分別用于對(duì)訓(xùn)練數(shù)據(jù)和測(cè)試數(shù)據(jù)的二次處理。class TripletSample(tordata.sampler.Sampler): # 繼承torch.utils.data.sampler類,實(shí)現(xiàn)自定義采樣器。# TripletSampler類的實(shí)現(xiàn),在該類的初始化函數(shù)中,支持兩個(gè)參數(shù)傳入:數(shù)集與批次參數(shù)。其中批次參數(shù)包含兩個(gè)維度的批次大小,分別是標(biāo)簽個(gè)數(shù)與樣本個(gè)數(shù)。def __init__(self,dataset,batch_size):self.dataset = dataset # 獲得數(shù)據(jù)集self.batch_size = batch_size # 獲得批次參數(shù),形狀為(標(biāo)簽個(gè)數(shù),樣本個(gè)數(shù))self.label_set = list(set(dataset.data_label)) # 標(biāo)簽集合def __iter__(self): # 實(shí)現(xiàn)采樣器的取值過(guò)程:從數(shù)據(jù)集中隨機(jī)抽取指定個(gè)數(shù)的標(biāo)簽,并在每個(gè)標(biāo)簽中抽取指定個(gè)數(shù)的樣本,最終以生成器的形式返回。while(True):sample_indices = []# 隨機(jī)抽取指定個(gè)數(shù)的標(biāo)簽label_list = random.sample(self.label_set,self.batch_size[0])# 在每個(gè)標(biāo)簽中抽取指定個(gè)數(shù)的樣本for _label in label_list: # 按照標(biāo)簽個(gè)數(shù)循環(huán)data_index = np.where(self.dataset.data_label == _label)[0]index = np.random.choice(data_index,self.batch_size[1],replace=False)sample_indices += index.tolist()yield np.asarray(sample_indices) # 以生成器的形式返回def __len__(self):return len(self.dataset) # 計(jì)算長(zhǎng)度# 用于訓(xùn)練數(shù)據(jù)的采樣器處理函數(shù) def collate_fn_train(batch,frame_num):# collate_fn_train函數(shù)會(huì)對(duì)采樣器傳入的批次數(shù)據(jù)進(jìn)行重組,并對(duì)每條數(shù)據(jù)按照指定幀數(shù)frame_num進(jìn)行抽取。# 同時(shí)也要保證每條數(shù)據(jù)的帖數(shù)都大于等于幀數(shù)frame_num。如果幀數(shù)小于frame_num,則為其添加重復(fù)幀。batch_data, batch_label,batch_meta = [],[],[]batch_size = len(batch) #獲得數(shù)據(jù)條數(shù)for i in range(batch_size) : # 依次對(duì)每條數(shù)據(jù)進(jìn)行處理batch_label.append(batch[i][2]) # 添加數(shù)據(jù)的標(biāo)簽batch_meta.append(batch[i][1]) # 添加數(shù)據(jù)的元信息data = batch[i][0] # 獲取該數(shù)據(jù)的樣本信息if data.shape[0] < frame_num: # 如果幀數(shù)較少,則隨機(jī)加入幾個(gè)# 復(fù)制幀,用于幀數(shù)很少的情況multy = (frame_num - data.shape[0])//data.shape[0] + 1# 額外隨機(jī)加入的幀的個(gè)數(shù)choicenum = (frame_num - data.shape[0])%data.shape[0]choice_index = np.random(data.shape[0],choicenum,replace = False)choice_index = list(range(0,data.shape[9])) * multy + choice_index.tolist()else: # 隨機(jī)抽取指定個(gè)數(shù)的幀choice_index = np.random.choice(data.shape[0],frame_num,replace = False)batch_data.append(data[choice_index]) # 增加指定個(gè)數(shù)的幀數(shù)據(jù)# 重新組合合成用于訓(xùn)練的樣本數(shù)據(jù)batch = [np.asarray(batch_data),batch_meta,batch_label]return batchdef collate_fn_for_test(batch,frame_num): # 用于測(cè)試數(shù)據(jù)的采樣器處理函數(shù)# collate_fn_for_test函數(shù)會(huì)對(duì)采樣器傳入的批次數(shù)據(jù)進(jìn)行重組,并按照批次數(shù)據(jù)中最大幀數(shù)進(jìn)行補(bǔ)0對(duì)齊。# 同時(shí)也要保證母條數(shù)據(jù)的幀數(shù)都大于等于幀數(shù)frame_num。如果幀數(shù)小于frame_num,則為其添加重復(fù)幀。batch_size = len(batch) # 獲得數(shù)據(jù)的條數(shù)batch_frames = np.zeros(batch_size,np.int)batch_data,batch_label,batch_meta = [],[],[]for i in range(batch_size): # 依次對(duì)每條數(shù)據(jù)進(jìn)行處理batch_label.append(batch[i][2]) # 添加數(shù)據(jù)的標(biāo)簽batch_meta.append(batch[i][1]) # 添加數(shù)據(jù)的元信息data = batch[i][0] # 獲取該數(shù)據(jù)的幀樣本信息if data.shape[0] < frame_num: # 如果幀數(shù)較少,隨機(jī)加入幾個(gè)print(batch_meta, data.shape[0])multy = (frame_num - data.shape[0]) // data.shape[0] + 1choicenum = (frame_num - data.shape[0]) % data.shape[0]choice_index = np.random.choice(data.shape[0], choicenum, replace=False)choice_index = list(range(0, data.shape[0])) * multy + choice_index.tolist()data = np.asarray(data[choice_index])batch_frames[i] = data.shape[0] # 保證所有的都大于等于frame_numbatch_data.append(data)max_frame = np.max(batch_frames) # 獲得最大的幀數(shù)# 對(duì)其他幀進(jìn)行補(bǔ)0填充batch_data = np.asarray([np.pad(batch_data[i], ((0, max_frame - batch_data[i].shape[0]), (0, 0), (0, 0)),'constant', constant_values=0)for i in range(batch_size)])# 重新組合成用于訓(xùn)練的樣本數(shù)據(jù)batch = [batch_data, batch_meta, batch_label]return batch2?train.py
import os import numpy as np from datetime import datetime import sys from functools import partial import matplotlib.pyplot as plt import torchvision import torch.nn as nn import torch import torch.utils.data as tordata from ranger import *# 1.4 測(cè)試數(shù)據(jù)集 # 在完成數(shù)據(jù)集的制作之后,對(duì)其進(jìn)行測(cè)試。 # 將樣本文件夾perdata放到當(dāng)前目錄下,并編寫(xiě)代碼生成數(shù)據(jù)集對(duì)象。 # 從數(shù)據(jù)集對(duì)象中取出一條數(shù)據(jù),并顯示該數(shù)據(jù)的詳細(xì)內(nèi)容。 from GaitSet_DataLoader import load_data # 加載項(xiàng)目模塊# 輸出當(dāng)前CPU-GPU print("torch V",torch.__version__,"cuda V",torch.version.cuda) pathstr = './data/perdata/perdata' label_train_num = 10 # 訓(xùn)練集的個(gè)數(shù)。剩下是測(cè)試集 batch_size = (3, 6) frame_num = 8 hidden_dim = 64 # label_train_num = 70 # 訓(xùn)練數(shù)據(jù)集的個(gè)數(shù),剩下的是測(cè)試數(shù)據(jù)庫(kù)dataconf = { # 方便導(dǎo)入?yún)?shù)'dataset_path':pathstr,'imgresize':'64','label_train_num':label_train_num,'label_shuffle':True, } print("加載訓(xùn)練數(shù)據(jù)...") train_source,test_cource = load_data(**dataconf) # 一次全載入,經(jīng)過(guò)load_data()分別生成訓(xùn)練和測(cè)試數(shù)據(jù)集對(duì)象。 print("訓(xùn)練數(shù)據(jù)集長(zhǎng)度",len(train_source)) # label_num * type10* view11 # 顯示數(shù)據(jù)集里面的標(biāo)簽 train_label_set = set(train_source.data_label) print("數(shù)據(jù)集里面的標(biāo)簽:",train_label_set)dataimg,matedata,lebelimg = train_source.__getitem__(4) # 從數(shù)據(jù)集中獲取一條數(shù)據(jù),并顯示其詳細(xì)信息。print("圖片樣本數(shù)據(jù)形狀:", dataimg.shape," 數(shù)據(jù)的元信息:", matedata," 數(shù)據(jù)標(biāo)簽索引:",lebelimg)plt.imshow(dataimg[0]) # 顯示圖片 plt.axis('off') # 不顯示坐標(biāo)軸 plt.show()def imshow(img):print("圖片形狀",np.shape(img))npimg = img.numpy()plt.axis('off')plt.imshow(np.transpose(npimg, (1, 2, 0)))plt.show()imshow(torchvision.utils.make_grid(torch.from_numpy(dataimg[-10:]).unsqueeze(1),nrow=10)) # 顯示十張圖片# 1.6 測(cè)試采樣器 from GaitSet_DataLoader import TripletSample,collate_fn_trainbatch_size = (4,8) # 定義批次(4個(gè)標(biāo)簽,每個(gè)標(biāo)簽8個(gè)數(shù)據(jù)) frame_num = 32 # 定義幀數(shù) num_workers = torch.cuda.device_count() # 設(shè)置采樣器的線程數(shù)# 在設(shè)置數(shù)據(jù)加載器額外啟動(dòng)進(jìn)程的數(shù)量時(shí),最好要與GPU數(shù)量匹配,即一個(gè)進(jìn)程服務(wù)于一個(gè)GPU。如果額外啟動(dòng)進(jìn)程的數(shù)量遠(yuǎn)遠(yuǎn)大于GPU數(shù)量,則性能瓶頸主要會(huì)卡在GPU運(yùn)行的地方,起不到提升效率的作用。 print("當(dāng)前GPU數(shù)量:",num_workers) if num_workers <= 1 : # 如果只有一塊GPU,或者沒(méi)有GPU,則使用主線程處理num_workers = 0 print("數(shù)據(jù)加載器額外啟動(dòng)進(jìn)程的數(shù)量",num_workers)# 實(shí)例化采樣器:得到對(duì)象triplet_sampler。 triplet_sampler = TripletSample(train_source,batch_size) # 初始化采樣器的處理函數(shù):用偏函數(shù)的方法對(duì)采樣器的處理函數(shù)進(jìn)行初始化。 collate_train = partial(collate_fn_train,frame_num=frame_num)# 定義數(shù)據(jù)加載器:每次迭代,按照采樣器的索引在train_source中取出數(shù)據(jù) # 將對(duì)象triplet_sampler和采樣器的處理函數(shù)collate_train傳入tordata.DataLoader,得到一個(gè)可用于訓(xùn)練的數(shù)據(jù)加載器對(duì)象train_loader。 # 同時(shí)對(duì)數(shù)據(jù)加載器額外啟動(dòng)進(jìn)程的數(shù)量進(jìn)行了設(shè)置,如果額外啟動(dòng)進(jìn)程的數(shù)量num_workers是0,則在加載數(shù)據(jù)時(shí)不額外啟動(dòng)其他進(jìn)程。 train_loader = tordata.DataLoader(dataset=train_source,batch_sampler=triplet_sampler, collate_fn=collate_train,num_workers=num_workers)# 從數(shù)據(jù)加載器中取出一條數(shù)據(jù) batch_data,batch_meta,batch_label = next(iter(train_loader)) print("該批次數(shù)據(jù)的總長(zhǎng)度:",len(batch_data)) # 輸出該數(shù)據(jù)的詳細(xì)信息 print("每條數(shù)據(jù)的形狀為",batch_data.shape) print(batch_label) # 輸出該數(shù)據(jù)的標(biāo)簽# 1.10 訓(xùn)練模型并保存權(quán)重文件:實(shí)例化模型類,并遍歷數(shù)據(jù)加載器,進(jìn)行訓(xùn)練。 from GaitSet import GaitSetNet, TripletLoss, np2varhidden_dim = 256 # 定義樣本的輸出維度 encoder = GaitSetNet(hidden_dim, frame_num).float() encoder = nn.DataParallel(encoder) # 使用多卡并行訓(xùn)練 encoder.cuda() # 將模型轉(zhuǎn)儲(chǔ)到GPU encoder.train() # 設(shè)置模型為訓(xùn)練模型optimizer = Ranger(encoder.parameters(), lr=0.004) # 定義Ranger優(yōu)化器TripletLossmode = 'full' # 設(shè)置三元損失的模式 triplet_loss = TripletLoss(int(np.prod(batch_size)), TripletLossmode, margin=0.2) # 實(shí)例化三元損失 triplet_loss = nn.DataParallel(triplet_loss) # 使用多卡并行訓(xùn)練 triplet_loss.cuda() # 將模型轉(zhuǎn)儲(chǔ)到GPUckp = 'checkpoint' # 設(shè)置模型名稱 os.makedirs(ckp, exist_ok=True) save_name = '_'.join(map(str, [hidden_dim, int(np.prod(batch_size)),frame_num, 'full']))ckpfiles = sorted(os.listdir(ckp)) # 載入預(yù)訓(xùn)練模型 if len(ckpfiles) > 1:modecpk = os.path.join(ckp, ckpfiles[-2])optcpk = os.path.join(ckp, ckpfiles[-1])encoder.module.load_state_dict(torch.load(modecpk)) # 加載模型文件optimizer.load_state_dict(torch.load(optcpk))print("load cpk !!! ", modecpk) # 定義訓(xùn)練參數(shù) hard_loss_metric = [] full_loss_metric = [] full_loss_num = [] dist_list = [] mean_dist = 0.01 restore_iter = 0 total_iter = 1000 # 迭代次數(shù) lastloss = 65535 # 初始的損失值 trainloss = []_time1 = datetime.now() # 計(jì)算迭代時(shí)間 for batch_data, batch_meta, batch_label in train_loader:restore_iter += 1optimizer.zero_grad() # 梯度清零batch_data = np2var(batch_data).float() # torch.cuda.DoubleTensor變?yōu)閠orch.cuda.FloatTensorfeature = encoder(batch_data) # 將標(biāo)簽轉(zhuǎn)為張量# 將標(biāo)簽轉(zhuǎn)化為張量target_label = np2var(np.array(batch_label)).long() # len=32triplet_feature = feature.permute(1, 0, 2).contiguous() # 對(duì)特征結(jié)果進(jìn)行變形,形狀變?yōu)閇62, 32, 256]triplet_label = target_label.unsqueeze(0).repeat(triplet_feature.size(0), 1) # 復(fù)制12份標(biāo)簽,[62, 32]# 計(jì)算三元損失(full_loss_metric_, hard_loss_metric_, mean_dist_, full_loss_num_) = triplet_loss(triplet_feature, triplet_label)if triplet_loss.module.hard_or_full == 'full': #提取損失值loss = full_loss_metric_.mean()else:loss = hard_loss_metric_.mean()trainloss.append(loss.data.cpu().numpy()) # 保存損失值hard_loss_metric.append(hard_loss_metric_.mean().data.cpu().numpy())full_loss_metric.append(full_loss_metric_.mean().data.cpu().numpy())full_loss_num.append(full_loss_num_.mean().data.cpu().numpy())dist_list.append(mean_dist_.mean().data.cpu().numpy())if loss > 1e-9: # 若損失值過(guò)小,則不參加反向傳播loss.backward()optimizer.step()else:print("損失值過(guò)小:", loss)if restore_iter % 1000 == 0:print("restore_iter 1000 time:", datetime.now() - _time1)_time1 = datetime.now()if restore_iter % 100 == 0: # 輸出訓(xùn)練結(jié)果print('iter {}:'.format(restore_iter), end='')print(', hard_loss_metric={0:.8f}'.format(np.mean(hard_loss_metric)), end='')print(', full_loss_metric={0:.8f}'.format(np.mean(full_loss_metric)), end='')print(', full_loss_num={0:.8f}'.format(np.mean(full_loss_num)), end='')print(', mean_dist={0:.8f}'.format(np.mean(dist_list)), end='')print(', lr=%f' % optimizer.param_groups[0]['lr'], end='')print(', hard or full=%r' % TripletLossmode)if lastloss > np.mean(trainloss): # 保存模型print("lastloss:", lastloss, " loss:", np.mean(trainloss), "need save!")lastloss = np.mean(trainloss)modecpk = os.path.join(ckp,'{}-{:0>5}-encoder.pt'.format(save_name, restore_iter))optcpk = os.path.join(ckp,'{}-{:0>5}-optimizer.pt'.format(save_name, restore_iter))torch.save(encoder.module.state_dict(), modecpk) # 一定要用encoder對(duì)象的module中的參數(shù)進(jìn)行保存。否則模型數(shù)的名字中會(huì)含有“module”字符串,使其不能被非并行的模型載入。torch.save(optimizer.state_dict(), optcpk)else:print("lastloss:", lastloss, " loss:", np.mean(trainloss), "don't save")print("__________________")sys.stdout.flush()hard_loss_metric.clear()full_loss_metric.clear()full_loss_num.clear()dist_list.clear()trainloss.clear()if restore_iter == total_iter: # 如果滿足迭代次數(shù),則訓(xùn)練結(jié)束break3 GaitSet_test.py
import os import numpy as np from datetime import datetime from functools import partial from tqdm import tqdm import torch.nn as nn import torch.nn.functional as F import torch import torch.utils.data as tordata from GaitSet_DataLoader import load_data,collate_fn_for_test from GaitSet import GaitSetNet,np2var # 為了測(cè)試模型識(shí)別步態(tài)的效果不依賴于拍攝角度和行走條件,可以多角度識(shí)別人物步分別取3組行走條件(普通、穿大衣、攜帶包裹)的樣本輸入模型,查看該模型所計(jì)算出的生征與其他行走條件的匹配程度。 # 1.11 測(cè)試模型 print("torch v:",torch.__version__,"cuda v:",torch.version.cuda)pathstr = './data/perdata/perdata' label_train_num = 70 # 訓(xùn)練數(shù)據(jù)集的個(gè)數(shù),剩下是測(cè)試數(shù)據(jù)集 batch_size = (8,16) frame_num = 30 hidden_dim = 256# 設(shè)置處理流程 num_workers = torch.cuda.device_count() print("cuda.device_count",num_workers) if num_workers <= 1: # 僅有一塊GPU或沒(méi)有GPU,則使用CPUnum_workers = 0 print("num_workers",num_workers)dataconf = { # 初始化數(shù)據(jù)集參數(shù)'dataset_path':pathstr,'imgresize':'64','label_train_num':label_train_num, # 訓(xùn)練數(shù)據(jù)集的個(gè)數(shù),剩下的是測(cè)試數(shù)據(jù)集'label_shuffle':True, } train_source,test_source = load_data(**dataconf)sampler_batch_size = 4 # 定義采樣批次 # 初始化采樣數(shù)據(jù)的二次處理函數(shù) collate_train = partial(collate_fn_for_test,frame_num=frame_num) # 定義數(shù)據(jù)加載器:每次迭代,按照采樣器的索引在test_source中取出數(shù)據(jù) test_loader = tordata.DataLoader(dataset=test_source,batch_size=sampler_batch_size,sampler=tordata.sampler.SequentialSampler(test_source),collate_fn=collate_train,num_workers=num_workers)# 實(shí)例化模型 encoder = GaitSetNet(hidden_dim,frame_num).float() encoder = nn.DataParallel(encoder) encoder.cuda() encoder.eval()ckp = './checkpoint' # 設(shè)置模型文件路徑 save_name = '_'.join(map(str,[hidden_dim,int(np.prod( batch_size )),frame_num,'full'])) ckpfiles = sorted(os.listdir(ckp)) # 加載模型 print("ckpfiles::::",ckpfiles) if len(ckpfiles) > 1:# modecpk = ckp + '/'+ckpfiles[-1]modecpk = os.path.join(ckp,ckpfiles[-1])encoder.module.load_state_dict(torch.load(modecpk), False) # 加載模型文件print("load cpk !!! ", modecpk) else:print("No cpk!!!")def cuda_dist(x,y): # 計(jì)算距離x = torch.from_numpy(x).cuda()y = torch.from_numpy(y).cuda()dist = torch.sum(x ** 2, 1).unsqueeze(1) + torch.sum(y ** 2, 1).unsqueeze(1).transpose(0, 1) - 2 * torch.matmul(x, y.transpose(0, 1))dist = torch.sqrt(F.relu(dist))return distdef de_diag(acc,each_angle=False): # 計(jì)算多角度準(zhǔn)確率,計(jì)算與其他拍攝角度相關(guān)的準(zhǔn)確率result = np.sum(acc - np.diag(np.diag(acc)), 1) / 10.0if not each_angle:result = np.mean(result)return resultdef evaluation(data): # 評(píng)估模型函數(shù)feature, meta, label = dataview, seq_type = [], []for i in meta:view.append(i[2])seq_type.append(i[1])label = np.array(label)view_list = list(set(view))view_list.sort()view_num = len(view_list)probe_seq = [['nm-05', 'nm-06'], ['bg-01', 'bg-02'], ['cl-01', 'cl-02']] # 定義采集數(shù)據(jù)的行走條件gallery_seq = [['nm-01', 'nm-02', 'nm-03', 'nm-04']] # 定義比較數(shù)據(jù)的行走條件num_rank = 5 # 取前5個(gè)距離最近的數(shù)據(jù)acc = np.zeros([len(probe_seq), view_num, view_num, num_rank])for (p, probe_s) in enumerate(probe_seq): # 依次將采集的數(shù)據(jù)與比較數(shù)據(jù)相比f(wàn)or gallery_s in gallery_seq:# Start---獲取指定條件的樣本特征后,按照采集數(shù)據(jù)特征與比較數(shù)據(jù)特之間的距離大小匹配對(duì)應(yīng)的標(biāo)簽,并計(jì)算其準(zhǔn)確率。# 步驟如下:# ①計(jì)算采集數(shù)據(jù)特征與比較數(shù)據(jù)特征之間的距離。# ②對(duì)距離進(jìn)行排序,返回最小的前5個(gè)排序索引。# ③按照索引從比較數(shù)據(jù)中取出前5個(gè)標(biāo)簽,并與采集數(shù)據(jù)中的標(biāo)簽做比較。# ④將比較結(jié)果的正確數(shù)量累加起來(lái),使每個(gè)樣本對(duì)應(yīng)5個(gè)記錄,分別代表前5個(gè)果中的識(shí)別正確個(gè)數(shù)。如[True,True,True,False,False],# 累加后結(jié)果為[1,2,3,3,3],表明離采集數(shù)據(jù)最近的前3個(gè)樣本特征中識(shí)別出來(lái)3個(gè)正確結(jié)果,前5個(gè)樣本特征中識(shí)別出來(lái)3個(gè)正確結(jié)果。# ⑤將累加結(jié)果與0比較,并判斷每個(gè)排名中大于0的個(gè)數(shù)。# ⑥將排名1-5的識(shí)別正確個(gè)數(shù)分別除以采集樣本個(gè)數(shù),再乘以100,便得到每個(gè)排名的準(zhǔn)確率for (v1, probe_view) in enumerate(view_list):for (v2, gallery_view) in enumerate(view_list): # 遍歷所有視角gseq_mask = np.isin(seq_type, gallery_s) & np.isin(view, [gallery_view])gallery_x = feature[gseq_mask, :] # 取出樣本特征gallery_y = label[gseq_mask] # 取出標(biāo)簽pseq_mask = np.isin(seq_type, probe_s) & np.isin(view, [probe_view])probe_x = feature[pseq_mask, :] # 取出樣本特征probe_y = label[pseq_mask] # 取出標(biāo)簽if len(probe_x) > 0 and len(gallery_x) > 0:dist = cuda_dist(probe_x, gallery_x) # 計(jì)算特征之間的距離idx = dist.sort(1)[1].cpu().numpy() # 對(duì)距離按照由小到大排序,返回排序后的索引(【0】是排序后的值)# 分別計(jì)算前五個(gè)結(jié)果的精確率:步驟③~⑥r(nóng)ank_data = np.round(np.sum(np.cumsum(np.reshape(probe_y,[-1,1]) == gallery_y[idx[:,0:num_rank]],1)>0,0)*100/dist.shape[0],2)# End---獲取指定條件的樣本特征后,按照采集數(shù)據(jù)特征與比較數(shù)據(jù)特之間的距離大小匹配對(duì)應(yīng)的標(biāo)簽,并計(jì)算其準(zhǔn)確率。acc[p, v1, v2, 0:len(rank_data)] = rank_datareturn accprint('test_loader', len(test_loader)) time = datetime.now() print('開(kāi)始評(píng)估模型...') feature_list = list() view_list = list() seq_type_list = list() label_list = list() batch_meta_list = []# 在遍歷數(shù)據(jù)集前加入了withtorch.nograd()語(yǔ)句。該語(yǔ)句可以使模型在運(yùn)行時(shí),不額外創(chuàng)建梯度相關(guān)的內(nèi)存。 # 在顯存不足的情況下,使用withtorch.nogradO語(yǔ)句非常重要,它可以節(jié)省系統(tǒng)資源。 # 雖然在實(shí)例化模型時(shí),使用了模型的eval方法來(lái)設(shè)置模型的使用方式,但這僅注意是修改模型中具有狀態(tài)分支的處理流程(如dropout或BN等),并不會(huì)省去創(chuàng)建顯存存放梯度的開(kāi)銷。 with torch.no_grad():for i, x in tqdm(enumerate(test_loader)): # 遍歷數(shù)據(jù)集batch_data, batch_meta, batch_label = xbatch_data = np2var(batch_data).float() # [2, 212, 64, 44]feature = encoder(batch_data) # 將數(shù)據(jù)載入模型 [4, 62, 64]feature_list.append(feature.view(feature.shape[0], -1).data.cpu().numpy()) # 保存特征結(jié)果,共sampler_batch_size 個(gè)特征batch_meta_list += batch_metalabel_list += batch_label # 保存樣本標(biāo)簽# 將樣本特征、標(biāo)簽以及對(duì)應(yīng)的元信息組合起來(lái) test = (np.concatenate(feature_list, 0), batch_meta_list, label_list) acc = evaluation(test) # 對(duì)組合數(shù)據(jù)進(jìn)行評(píng)估 print('評(píng)估完成. 耗時(shí):', datetime.now() - time)for i in range(1): # 計(jì)算第一個(gè)的精確率print('===Rank-%d 準(zhǔn)確率===' % (i + 1))print('攜帶包裹: %.3f,\t普通: %.3f,\t穿大衣: %.3f' % (np.mean(acc[0, :, :, i]),np.mean(acc[1, :, :, i]),np.mean(acc[2, :, :, i])))for i in range(1): # 計(jì)算第一個(gè)的精確率(除去自身的行走條件)print('===Rank-%d 準(zhǔn)確率(除去自身的行走條件)===' % (i + 1))print('攜帶包裹: %.3f,\t普通: %.3f,\t穿大衣: %.3f' % (de_diag(acc[0, :, :, i]),de_diag(acc[1, :, :, i]),de_diag(acc[2, :, :, i])))np.set_printoptions(precision=2, floatmode='fixed') # 設(shè)置輸出精度 for i in range(1): # 顯示多拍攝角度的詳細(xì)結(jié)果print('===Rank-%d 的每個(gè)角度準(zhǔn)確率 (除去自身的行走條件)===' % (i + 1))print('攜帶包裹:', de_diag(acc[0, :, :, i], True))print('普通:', de_diag(acc[1, :, :, i], True))print('穿大衣:', de_diag(acc[2, :, :, i], True))4 GaitSet.py
import torch import torch.nn as nn import torch.autograd as autograd import torch.nn.functional as F# 搭建GaitSet模型: 分為兩部分:基礎(chǔ)卷積(BasicConv2d) 類和GaitSetNet類。 # 1.7 定義基礎(chǔ)卷積類:對(duì)原始卷積函數(shù)進(jìn)行封裝。在卷積結(jié)束后,用Mish激活函數(shù)和批量正則化處理對(duì)特征進(jìn)行二次處理。 class BasicConv2d(nn.Module):def __init__(self,in_channels,out_channels,kernel_size,**kwargs):super(BasicConv2d,self).__init__()self.conv = nn.Conv2d(in_channels,out_channels,kernel_size,bias=False,**kwargs) # 卷積操作self.BatchNorm = nn.BatchNorm2d(out_channels) # BN操作def forward(self,x): # 自定義前向傳播方法x = self.conv(x)x = x * ( torch.tanh(F.softplus(x))) # 實(shí)現(xiàn)Mish激活函數(shù):PyTorch沒(méi)有現(xiàn)成的Mish激活函數(shù),手動(dòng)實(shí)現(xiàn)Mish激活函數(shù),并對(duì)其進(jìn)行調(diào)用。return self.BatchNorm(x) # 返回卷積結(jié)果# 1.8 定義GaitSetNet類: # ①實(shí)現(xiàn)3個(gè)MGP。 # ②對(duì)MGP的結(jié)果進(jìn)行HPM處理。每層MGP的結(jié)構(gòu)是由兩個(gè)卷積層加一次下采樣組成的。在主分支下采樣之后,與輔助分支所提取的幀級(jí)特征加和,傳入下一個(gè)MGP中。 class GaitSetNet(nn.Module):def __init__(self, hidden_dim, frame_num):super(GaitSetNet, self).__init__()self.hidden_dim = hidden_dim # 輸出的特征維度# 定義MGP部分cnls = [1, 32, 64, 128] # 定義卷積層通道數(shù)量self.set_layer1 = BasicConv2d(cnls[0], cnls[1], 5, padding=2)self.set_layer2 = BasicConv2d(cnls[1], cnls[1], 3, padding=1)self.set_layer1_down = BasicConv2d(cnls[1], cnls[1], 2, stride=2) # 下采樣操作,通過(guò)步長(zhǎng)為2的2x2卷積實(shí)現(xiàn)。self.set_layer3 = BasicConv2d(cnls[1], cnls[2], 3, padding=1)self.set_layer4 = BasicConv2d(cnls[2], cnls[2], 3, padding=1)self.set_layer2_down = BasicConv2d(cnls[2], cnls[2], 2, stride=2)# 下采樣操作,通過(guò)步長(zhǎng)為2的2x2卷積實(shí)現(xiàn)。self.gl_layer2_down = BasicConv2d(cnls[2], cnls[2], 2, stride=2)# 下采樣操作,通過(guò)步長(zhǎng)為2的2x2卷積實(shí)現(xiàn)。self.set_layer5 = BasicConv2d(cnls[2], cnls[3], 3, padding=1)self.set_layer6 = BasicConv2d(cnls[3], cnls[3], 3, padding=1)self.gl_layer1 = BasicConv2d(cnls[1], cnls[2], 3, padding=1)self.gl_layer2 = BasicConv2d(cnls[2], cnls[2], 3, padding=1)self.gl_layer3 = BasicConv2d(cnls[2], cnls[3], 3, padding=1)self.gl_layer4 = BasicConv2d(cnls[3], cnls[3], 3, padding=1)self.bin_num = [1, 2, 4, 8, 16] # 定義MGP部分self.fc_bin = nn.ParameterList([nn.Parameter(nn.init.xavier_uniform_(torch.zeros(sum(self.bin_num) * 2, 128, hidden_dim)))])def frame_max(self, x, n): # 用最大特征方法提取幀級(jí)特征:# 調(diào)用torch.max函數(shù),實(shí)現(xiàn)從形狀[批次個(gè)數(shù),幀數(shù),通道數(shù),高度,寬度]的特征中,沿著幀維度,提取最大值,得到形狀[批次個(gè)數(shù),通道數(shù),高度,寬度]的特征提取幀級(jí)特征的過(guò)程。return torch.max(x.view(n, -1, x.shape[1], x.shape[2], x.shape[3]), 1)[0] # 取max后的值def forward(self, xinput): # 定義前向處理方法n = xinput.size()[0] # 形狀為[批次個(gè)數(shù),幀數(shù),高,寬]x = xinput.reshape(-1, 1, xinput.shape[-2], xinput.shape[-1])del xinput # 刪除不用的變量# MGP 第一層x = self.set_layer1(x)x = self.set_layer2(x)x = self.set_layer1_down(x)gl = self.gl_layer1(self.frame_max(x, n)) # 將每一層的幀取最大值# MGP 第二層gl = self.gl_layer2(gl)gl = self.gl_layer2_down(gl)x = self.set_layer3(x)x = self.set_layer4(x)x = self.set_layer2_down(x)# MGP 第三層gl = self.gl_layer3(gl + self.frame_max(x, n))gl = self.gl_layer4(gl)x = self.set_layer5(x)x = self.set_layer6(x)x = self.frame_max(x, n)gl = gl + x# srart-------HPM處理:按照定義的特征尺度self.bin_num,將輸入特征分成不同尺度,并對(duì)每個(gè)尺度的特征進(jìn)行均值和最大化計(jì)算,從而組合成新的特征,放到列表feature中。feature = list() # 用于存放HPM特征n, c, h, w = gl.size()for num_bin in self.bin_num:z = x.view(n, c, num_bin, -1)z = z.mean(3) + z.max(3)[0]feature.append(z)z = gl.view(n, c, num_bin, -1)z = z.mean(3) + z.max(3)[0]feature.append(z)# end-------HPM處理:按照定義的特征尺度self.bin_num,將輸入特征分成不同尺度,并對(duì)每個(gè)尺度的特征進(jìn)行均值和最大化計(jì)算,從而組合成新的特征,放到列表feature中。# 對(duì)HPM特征中的特征維度進(jìn)行轉(zhuǎn)化# srart-------將每個(gè)特征維度由128轉(zhuǎn)化為指定的輸出的特征維度hidden_dim。因?yàn)檩斎霐?shù)據(jù)是三維的,無(wú)法直接使用全連接API,所以使用矩陣相乘的方式實(shí)現(xiàn)三維數(shù)據(jù)按照最后一個(gè)維度進(jìn)行全連接的效果。feature = torch.cat(feature, 2).permute(2, 0, 1).contiguous() # 62 n cfeature = feature.matmul(self.fc_bin[0])feature = feature.permute(1, 0, 2).contiguous()# end-------將每個(gè)特征維度由128轉(zhuǎn)化為指定的輸出的特征維度hidden_dim。因?yàn)檩斎霐?shù)據(jù)是三維的,無(wú)法直接使用全連接API,所以使用矩陣相乘的方式實(shí)現(xiàn)三維數(shù)據(jù)按照最后一個(gè)維度進(jìn)行全連接的效果。return feature # 返回結(jié)果# 1.9 實(shí)現(xiàn) 自定義三元損失類 # 定義三元損失(TripletLoss)類, 實(shí)現(xiàn)三元損失的計(jì)算。具體步驟: # ①對(duì)輸入樣本中的標(biāo)簽進(jìn)行每?jī)蓚€(gè)一組自由組合,生成標(biāo)簽矩陣,從標(biāo)簽矩陣中得到正/負(fù)樣本對(duì)的掩碼 # ②對(duì)輸入樣本中的特征進(jìn)行每?jī)蓚€(gè)一組自由組合,生成特征矩陣,計(jì)算出特征矩陣的歐氏距離。 # ③按照正/負(fù)樣本對(duì)的掩碼,對(duì)帶有距離的特征矩陣進(jìn)行提取,得到正/負(fù)兩種標(biāo)簽的距離。 # ④將正/負(fù)兩種標(biāo)簽的距離相減,再減去間隔值,得到三元損失。 class TripletLoss(nn.Module): # 定義三元損失類def __init__(self,batch_size,hard_or_full,margin): # 初始化super(TripletLoss, self).__init__()self.batch_size = batch_sizeself.margin =margin # 正/負(fù)樣本的三元損失間隔self.hard_or_full = hard_or_full # 三元損失方式def forward(self,feature,label): # 定義前向傳播方法:# 接收的參數(shù)feature為模型根據(jù)輸入樣本所計(jì)算出來(lái)的特征。該參數(shù)的形狀為[n.m.d],n:HPM處理時(shí)的尺度個(gè)數(shù)62。m:樣本個(gè)數(shù)32。d:維度256。# 在計(jì)算過(guò)程中,將三元損失看作n份,用矩陣的方式對(duì)每份m個(gè)樣本、d維度特征做三元損失計(jì)算,最后將這n份平均。n,m,d = feature.size() # 形狀為[n,m,d]# 生成標(biāo)簽矩陣,并從中找出正/負(fù)樣本對(duì)的編碼,輸出形狀[n,m,m]并且展開(kāi)hp_mask = (label.unsqueeze(1) == label.unsqueeze(2)).view(-1)hn_mask = (label.unsqueeze(1) != label.unsqueeze(2)).view(-1)dist = self.batch_dist(feature) # 計(jì)算出特征矩陣的距離mean_dist = dist.mean(1).mean(1) # 計(jì)算所有的平均距離dist = dist.view(-1)# start-----計(jì)算三元損失的hard模式hard_hp_dist = torch.max(torch.masked_select(dist, hp_mask).view(n, m, -1), 2)[0]hard_hn_dist = torch.min(torch.masked_select(dist, hn_mask).view(n, m, -1), 2)[0]hard_loss_metric = F.relu(self.margin + hard_hp_dist - hard_hn_dist).view(n, -1) # 要讓間隔最小化,到0為止# 對(duì)三元損失取均值,得到最終的hard模式loss[n]hard_loss_metric_mean = torch.mean(hard_loss_metric,1)# end-----計(jì)算三元損失的hard模式# start-----計(jì)算三元損失的full模式# 計(jì)算三元損失的full模型full_hp_dist = torch.masked_select(dist, hp_mask).view(n, m, -1, 1) # 按照編碼得到所有正向樣本距離[n,m,正樣本個(gè)數(shù),1] [62, 32, 8, 1]full_hn_dist = torch.masked_select(dist, hn_mask).view(n, m, 1, -1) # 照編碼得到所有負(fù)向樣本距離[n,m,1,負(fù)樣本個(gè)數(shù)] [62, 32, 1, 24]full_loss_metric = F.relu(self.margin + full_hp_dist - full_hn_dist).view(n, -1) # 讓正/負(fù)間隔最小化,到0為止 [62,32*8*24]# 計(jì)算[n]中每個(gè)三元損失的和full_loss_metric_sum = full_loss_metric.sum(1) # 計(jì)算[62]中每個(gè)loss的和# 計(jì)算[n]中每個(gè)三元損失的個(gè)數(shù)(去掉矩陣對(duì)角線以及符合條件的三元損失)full_loss_num = (full_loss_metric != 0).sum(1).float() # 計(jì)算[62]中每個(gè)loss的個(gè)數(shù)# 計(jì)算均值full_loss_metric_mean = full_loss_metric_sum / full_loss_num # 計(jì)算平均值full_loss_metric_mean[full_loss_num == 0] = 0 # 將無(wú)效值設(shè)為0# end-----計(jì)算三元損失的full模式return full_loss_metric_mean, hard_loss_metric_mean, mean_dist, full_loss_num # ,lossdef batch_dist(self, x): # 計(jì)算特征矩陣的距離x2 = torch.sum(x ** 2, 2) # 平方和 [62, 32]# dist [62, 32, 32]dist = x2.unsqueeze(2) + x2.unsqueeze(2).transpose(1, 2) - 2 * torch.matmul(x, x.transpose(1, 2)) # 計(jì)算特征矩陣的距離dist = torch.sqrt(F.relu(dist)) # 對(duì)結(jié)果進(jìn)行開(kāi)平方return distdef ts2var(x):return autograd.Variable(x).cuda()def np2var(x):return ts2var(torch.from_numpy(x))?
?
總結(jié)
以上是生活随笔為你收集整理的【Pytorch神经网络实战案例】29 【代码汇总】GitSet模型进行步态与身份识别(CASIA-B数据集)的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問(wèn)題。
- 上一篇: 【Pytorch神经网络实战案例】16
- 下一篇: 报错curl: (7) Failed t