順德網(wǎng)站建設(shè)價(jià)格國(guó)家認(rèn)可的教育培訓(xùn)機(jī)構(gòu)
首先祝賀百度團(tuán)隊(duì)百度斬獲NeurIPS2020挑戰(zhàn)賽冠軍,https://www.jiqizhixin.com/articles/2020-12-09-2。
在此次比賽中使用的是基于飛槳深度學(xué)習(xí)框架開發(fā)的圖像分割套件PaddleSeg。從這篇文章開始,我將持續(xù)更新《圖像分割套件PaddleSeg全面解析》系列文章,由于個(gè)人水平有限,如有錯(cuò)誤之處請(qǐng)見諒,謝謝。
PaddleSeg是百度基于自家的PaddlePaddle開發(fā)的端到端圖像分割開發(fā)套件。包含多種主流的分割網(wǎng)絡(luò)。PaddleSeg采用模塊化的方式設(shè)計(jì),可以通過配置文件方式進(jìn)行模型組合,幫助開發(fā)者在不需要深入了解圖像分割原理的情況,實(shí)現(xiàn)方便快捷的完成模型的訓(xùn)練與部署。 但是在對(duì)需要對(duì)模型進(jìn)行修改優(yōu)化的時(shí)候,還是需要對(duì)圖像分割原理以及PaddleSeg套件有進(jìn)一步了解,本文的主要內(nèi)容就是對(duì)PaddleSeg進(jìn)行代碼解讀,幫助開發(fā)者進(jìn)一步了解圖像分割原理以及PaddleSeg的實(shí)現(xiàn)方法。本文只要介紹PaddleSeg的動(dòng)態(tài)圖的實(shí)現(xiàn)方法。
本代碼解讀基于PaddleSeg動(dòng)態(tài)圖版本V2.0.0-rc。 PaddleSeg套件的源代碼可以從GitHub上進(jìn)行下載,命令如下:
PaddleSeg套件的源代碼可以從GitHub上進(jìn)行下載,命令如下:
!git clone https://github.com/PaddlePaddle/PaddleSeg.git
PaddleSeg目錄包含下幾個(gè)目錄:
- configs:保存不同神經(jīng)網(wǎng)絡(luò)的配置文件。
- contrib:真實(shí)案例相關(guān)配置與數(shù)據(jù)
- legacy:靜態(tài)圖版本代碼,只維護(hù),不更新新功能
- docs:文檔
- paddleseg:PaddleSeg核心代碼,包含訓(xùn)練、評(píng)估、推理等文件。
- tools:工具腳本
- train.py:訓(xùn)練入口文件
- val.py:評(píng)估模型文件
- predict.py:預(yù)測(cè)文件
本文大概分為以下7個(gè)部分:
1.train.py代碼解讀:這里主要講解paddleseg訓(xùn)練入口文件的代碼。該文件里描述了參數(shù)的解析,訓(xùn)練的啟動(dòng)方法,以及為訓(xùn)練準(zhǔn)備的資源等。
圖像分割套件PaddleSeg全面解析(一)train.py代碼解讀
2.Config代碼解讀:這里主要講解了Config類的代碼,config類由train.py實(shí)例化,通過運(yùn)行train.py時(shí)指定的配置文件生成config對(duì)象。
圖像分割套件PaddleSeg全面解析(二)
3.DataSet代碼解讀:這里主要講解了Dataset類,對(duì)每一種數(shù)據(jù)集都抽象為一個(gè)類,通過繼承Dataset類,實(shí)現(xiàn)匿名協(xié)議,構(gòu)建文件列表,供訓(xùn)練使用。
圖像分割套件PaddleSeg全面解析(三)
4.數(shù)據(jù)增強(qiáng)代碼解讀:這里主要講解了數(shù)據(jù)預(yù)處理與增強(qiáng)的一些常用算法。
圖像分割套件PaddleSeg全面解析(四)
5.模型與Backbone代碼解讀:這里主要講解常用的模型以及backbone的網(wǎng)絡(luò)與算法。
圖像分割套件PaddleSeg全面解析(五)
圖像分割套件PaddleSeg全面解析(六)
6.損失函數(shù)代碼解讀:這里主要講解常用的損失函數(shù)的代碼與算法。
圖像分割套件PaddleSeg全面解析(七)
7.評(píng)估模型代碼解讀:這里講解評(píng)估模型性能的代碼與評(píng)估方法。
1.train.py代碼解讀
神經(jīng)網(wǎng)絡(luò)模型訓(xùn)練需要使用train.py來完成。是PaddleSeg中核心代碼。
我們先結(jié)合下圖,來了解一下訓(xùn)練之前的準(zhǔn)備工作。
可以通過以下命令快速開始一個(gè)訓(xùn)練任務(wù)。
python train.py --config configs/quick_start/bisenet_optic_disc_512x512_1k.yml
命令中的–config參數(shù)指定本次訓(xùn)練的配置文件,配置文件的詳細(xì)介紹可以參見后面的第二節(jié)。
在執(zhí)行train.py腳本的最開始會(huì)導(dǎo)入一些包,如下:
from paddleseg.cvlibs import manager, Config
from paddleseg.utils import get_sys_env, logger
from paddleseg.core import train
- 在導(dǎo)入manager模塊時(shí)會(huì)創(chuàng)建圖中左側(cè)manage方框中的5個(gè)ComponentManager對(duì)象,他們分別是MODELS、BACKBONES、DATASETS、TRANSFORMS和LOSSES。這5個(gè)ComponentManager類似字典,用來維護(hù)套件中所有對(duì)應(yīng)的類,比如FCN類、ResNet類等,通過類的名稱就可以找到對(duì)應(yīng)的類。
- 在train.py運(yùn)行時(shí),會(huì)創(chuàng)建config對(duì)象。
cfg = Config(args.cfg,learning_rate=args.learning_rate,iters=args.iters,batch_size=args.batch_size)
在創(chuàng)建config對(duì)象時(shí),會(huì)通過manager獲取到配置文件中指定的類,并實(shí)例化對(duì)象,比如model和loss等。
- train.py調(diào)用train函數(shù),將config作為實(shí)參傳入。train函數(shù)獲取config中的成員來完成訓(xùn)練工作。
下面我們來詳細(xì)解讀一下train.py,首先我們從train.py的入口代碼開始:
if __name__ == '__main__':# 處理運(yùn)行train.py傳入的參數(shù)args = parse_args()#調(diào)用主函數(shù)。main(args)
首先看一下第一行代碼
args = parse_args()
parse_args()的實(shí)現(xiàn)如下:
#配置文件路徑parser.add_argument("--config", dest="cfg", help="The config file.", default=None, type=str)#總訓(xùn)練迭代次數(shù)parser.add_argument('--iters',dest='iters',help='iters for training',type=int,default=None)#batchsize大小parser.add_argument('--batch_size',dest='batch_size',help='Mini batch size of one gpu or cpu',type=int,default=None)#學(xué)習(xí)率parser.add_argument('--learning_rate',dest='learning_rate',help='Learning rate',type=float,default=None)#保存模型間隔parser.add_argument('--save_interval',dest='save_interval',help='How many iters to save a model snapshot once during training.',type=int,default=1000)#如果需要恢復(fù)訓(xùn)練,指定恢復(fù)訓(xùn)練模型路徑parser.add_argument('--resume_model',dest='resume_model',help='The path of resume model',type=str,default=None)#模型保存路徑parser.add_argument('--save_dir',dest='save_dir',help='The directory for saving the model snapshot',type=str,default='./output')#數(shù)據(jù)讀取器線程數(shù)量,目前在AI Studio建議設(shè)置為0.parser.add_argument('--num_workers',dest='num_workers',help='Num workers for data loader',type=int,default=0)#在訓(xùn)練過程中進(jìn)行模型評(píng)估parser.add_argument('--do_eval',dest='do_eval',help='Eval while training',action='store_true')#日志打印間隔parser.add_argument('--log_iters',dest='log_iters',help='Display logging information at every log_iters',default=10,type=int)#開啟可視化訓(xùn)練parser.add_argument('--use_vdl',dest='use_vdl',help='Whether to record the data to VisualDL during training',action='store_true')
然后看下一行代碼:
main(args)
main 的代碼如下:
def main(args):#獲取環(huán)境信息,比如操作系統(tǒng)類型、python版本號(hào)、Paddle版本、GPU數(shù)量、Opencv版本、gcc版本等內(nèi)容env_info = get_environ_info()#打印環(huán)境信息info = ['{}: {}'.format(k, v) for k, v in env_info.items()]info = '\n'.join(['\n', format('Environment Information', '-^48s')] + info +['-' * 48])logger.info(info)#確定是否使用GPUplace = 'gpu' if env_info['Paddle compiled with cuda'] and env_info['GPUs used'] else 'cpu'#設(shè)置使用GPU或者CPUpaddle.set_device(place)#如果沒有指定配置文件這拋出異常。if not args.cfg:raise RuntimeError('No configuration file specified.')#構(gòu)建cfg對(duì)象,該對(duì)象包含數(shù)據(jù)集、圖像增強(qiáng)、模型結(jié)構(gòu)、損失函數(shù)等設(shè)置#該對(duì)象基于命令行傳入?yún)?shù)以及yaml配置文件構(gòu)建cfg = Config(args.cfg,learning_rate=args.learning_rate,iters=args.iters,batch_size=args.batch_size)#從Config對(duì)象中獲取train_data對(duì)象。train_data為迭代器train_dataset = cfg.train_dataset#如果沒有設(shè)置訓(xùn)練集,拋出異常if not train_dataset:raise RuntimeError('The training dataset is not specified in the configuration file.')#如果需要在訓(xùn)練中進(jìn)行模型評(píng)估,則需要獲取到驗(yàn)證集val_dataset = cfg.val_dataset if args.do_eval else None#獲取損失函數(shù)losses = cfg.lossmsg = '\n---------------Config Information---------------\n'msg += str(cfg)msg += '------------------------------------------------'#打印出詳細(xì)設(shè)置。logger.info(msg)#調(diào)用core/train.py中train函數(shù)進(jìn)行訓(xùn)練train(cfg.model,train_dataset,val_dataset=val_dataset,optimizer=cfg.optimizer,save_dir=args.save_dir,iters=cfg.iters,batch_size=cfg.batch_size,resume_model=args.resume_model,save_interval=args.save_interval,log_iters=args.log_iters,num_workers=args.num_workers,use_vdl=args.use_vdl,losses=losses)
在train.py腳本中,除了調(diào)用config對(duì)配置文件進(jìn)行解析,就是調(diào)用core/train.py中的train函數(shù)完成訓(xùn)練工作。下面我先看一下train函數(shù)的工作流程。

從圖中看出,整個(gè)訓(xùn)練過程由兩個(gè)循環(huán)組成,最外層循環(huán)由總迭代次數(shù)控制,需要在yaml文件中配置,如下代碼:
iters: 80000
內(nèi)層循環(huán)由數(shù)據(jù)讀取器控制,循環(huán)會(huì)遍歷數(shù)據(jù)讀取器中所有的數(shù)據(jù),直至全部讀取完畢跳出循環(huán),這個(gè)過程通常也被叫做一個(gè)epoch。
下面我們?cè)敿?xì)解析一下core/train.py中train函數(shù)的代碼。
首先看一下train函數(shù)的代碼概要。

然后我們?cè)倏匆幌略敿?xì)的代碼解讀,
def train(model, #模型對(duì)象train_dataset, #訓(xùn)練集對(duì)象val_dataset=None, #驗(yàn)證集對(duì)象,如果訓(xùn)練過程不需要驗(yàn)證,可以為Noneoptimizer=None, #優(yōu)化器對(duì)象save_dir='output', #模型輸出路徑iters=10000, #訓(xùn)練最大迭代次數(shù)batch_size=2, #batch size大學(xué)resume_model=None, # 是否需要恢復(fù)訓(xùn)練,如果需要指定恢復(fù)訓(xùn)練模型權(quán)重路徑save_interval=1000, # 模型保存間隔log_iters=10, # 設(shè)置日志輸出間隔num_workers=0, #設(shè)置數(shù)據(jù)讀取器線程數(shù),0為不開啟多進(jìn)程use_vdl=False, #是否使用vdllosses=None): # 損失函數(shù)系數(shù),當(dāng)使用多個(gè)損失函數(shù)時(shí),需要指定各個(gè)損失函數(shù)的系數(shù)。#為了兼容多卡訓(xùn)練,這里需要獲取顯卡數(shù)量。nranks = paddle.distributed.ParallelEnv().nranks#在分布式訓(xùn)練中,每個(gè)顯卡都會(huì)執(zhí)行本程序,所以需要在程序里獲取本顯卡的序列號(hào)。local_rank = paddle.distributed.ParallelEnv().local_rank#循環(huán)起始的迭代數(shù)。如果是恢復(fù)訓(xùn)練的話,從恢復(fù)訓(xùn)練中獲得起始的迭代數(shù)。#比如,在2000次迭代的時(shí)候保存了中間訓(xùn)練過程,通過resume恢復(fù)訓(xùn)練,那么start_iter則為2000。start_iter = 0if resume_model is not None:start_iter = resume(model, optimizer, resume_model)#創(chuàng)建保存輸出模型文件的目錄。if not os.path.isdir(save_dir):if os.path.exists(save_dir):os.remove(save_dir)os.makedirs(save_dir)#如果是多卡訓(xùn)練,則需要初始化多卡訓(xùn)練環(huán)境。if nranks > 1:# Initialize parallel training environment.paddle.distributed.init_parallel_env()strategy = paddle.distributed.prepare_context()ddp_model = paddle.DataParallel(model, strategy)#創(chuàng)建一個(gè)批量采樣器,這里指定數(shù)據(jù)集,通過批量采樣器組成一個(gè)batch。這里需要指定batch size,是否隨機(jī)打亂,是否丟棄末尾不能組成一個(gè)batch的數(shù)據(jù)等參數(shù)。batch_sampler = paddle.io.DistributedBatchSampler(train_dataset, batch_size=batch_size, shuffle=True, drop_last=True)#通過數(shù)據(jù)集參數(shù)和批量采樣器等參數(shù)構(gòu)建一個(gè)數(shù)據(jù)讀取器。可以通過num_works設(shè)置多進(jìn)程,這里的多進(jìn)程通過共享內(nèi)存通信,#如果共享內(nèi)存過小可能會(huì)報(bào)錯(cuò),如果報(bào)錯(cuò)可以嘗將num_workers設(shè)置為0,則不開啟多進(jìn)程。loader = paddle.io.DataLoader(train_dataset,batch_sampler=batch_sampler,num_workers=num_workers,return_list=True,)if use_vdl:from visualdl import LogWriterlog_writer = LogWriter(save_dir)#開啟定時(shí)器timer = Timer()avg_loss = 0.0iters_per_epoch = len(batch_sampler)best_mean_iou = -1.0best_model_iter = -1train_reader_cost = 0.0train_batch_cost = 0.0timer.start()iter = start_iter#開始循環(huán),通過迭代次數(shù)控制最外層循環(huán)。while iter < iters:#內(nèi)部循環(huán),遍歷數(shù)據(jù)迭代器中的數(shù)據(jù)。for data in loader:iter += 1if iter > iters:break#記錄讀取器時(shí)間train_reader_cost += timer.elapsed_time()#保存樣本images = data[0]#保存樣本標(biāo)簽labels = data[1].astype('int64')#供BCELoss使用edges = Noneif len(data) == 3:edges = data[2].astype('int64')#如果有多張顯卡,則開啟分布式訓(xùn)練,如果只有一張顯卡則直接調(diào)用模型對(duì)象進(jìn)行訓(xùn)練。if nranks > 1:#通過模型前向運(yùn)算獲得預(yù)測(cè)結(jié)果logits_list = ddp_model(images)else:#通過模型前向運(yùn)算獲得預(yù)測(cè)結(jié)果logits_list = model(images)#通過標(biāo)簽計(jì)算損失loss = loss_computation(logits_list=logits_list,labels=labels,losses=losses,edges=edges)#計(jì)算模型參數(shù)的梯度loss.backward()#執(zhí)行一次優(yōu)化器并進(jìn)行參數(shù)更新optimizer.step()#獲取當(dāng)前優(yōu)化器的學(xué)習(xí)率。lr = optimizer.get_lr()if isinstance(optimizer._learning_rate,paddle.optimizer.lr.LRScheduler):optimizer._learning_rate.step()#清除模型中的梯度model.clear_gradients()#計(jì)算平均損失值avg_loss += loss.numpy()[0]train_batch_cost += timer.elapsed_time()#根據(jù)配置中的log_iters打印訓(xùn)練日志if (iter) % log_iters == 0 and local_rank == 0:avg_loss /= log_itersavg_train_reader_cost = train_reader_cost / log_itersavg_train_batch_cost = train_batch_cost / log_iterstrain_reader_cost = 0.0train_batch_cost = 0.0remain_iters = iters - itereta = calculate_eta(remain_iters, avg_train_batch_cost)logger.info("[TRAIN] epoch={}, iter={}/{}, loss={:.4f}, lr={:.6f}, batch_cost={:.4f}, reader_cost={:.4f} | ETA {}".format((iter - 1) // iters_per_epoch + 1, iter, iters,avg_loss, lr, avg_train_batch_cost,avg_train_reader_cost, eta))if use_vdl:log_writer.add_scalar('Train/loss', avg_loss, iter)log_writer.add_scalar('Train/lr', lr, iter)log_writer.add_scalar('Train/batch_cost',avg_train_batch_cost, iter)log_writer.add_scalar('Train/reader_cost',avg_train_reader_cost, iter)avg_loss = 0.0#根據(jù)配置中的save_interval判斷是否需要對(duì)當(dāng)前模型進(jìn)行評(píng)估。if (iter % save_interval == 0or iter == iters) and (val_dataset is not None):num_workers = 1 if num_workers > 0 else 0mean_iou, acc = evaluate(model, val_dataset, num_workers=num_workers)#評(píng)估后需要將模型訓(xùn)練模式,該模式影響dropout和batchnorm層model.train()#根據(jù)配置中的save_interval判斷是否需要保存當(dāng)前模型。if (iter % save_interval == 0 or iter == iters) and local_rank == 0:current_save_dir = os.path.join(save_dir,"iter_{}".format(iter))#如果輸出路徑不存在,需要?jiǎng)?chuàng)建目錄。if not os.path.isdir(current_save_dir):os.makedirs(current_save_dir)#保存模型權(quán)重paddle.save(model.state_dict(),os.path.join(current_save_dir, 'model.pdparams'))#保存優(yōu)化器權(quán)重,恢復(fù)訓(xùn)練會(huì)用到。paddle.save(optimizer.state_dict(),os.path.join(current_save_dir, 'model.pdopt'))#保存最佳模型。if val_dataset is not None:if mean_iou > best_mean_iou:best_mean_iou = mean_ioubest_model_iter = iterbest_model_dir = os.path.join(save_dir, "best_model")paddle.save(model.state_dict(),os.path.join(best_model_dir, 'model.pdparams'))logger.info('[EVAL] The model with the best validation mIoU ({:.4f}) was saved at iter {}.'.format(best_mean_iou, best_model_iter))if use_vdl:log_writer.add_scalar('Evaluate/mIoU', mean_iou, iter)log_writer.add_scalar('Evaluate/Acc', acc, iter)#重置定時(shí)器timer.restart()# Sleep for half a second to let dataloader release resources.time.sleep(0.5)if use_vdl:log_writer.close()
PaddleSeg套件訓(xùn)練入口train.py文件解讀到此結(jié)束。
PaddleSeg倉(cāng)庫(kù)地址:https://github.com/PaddlePaddle/PaddleSeg