写点什么

U2Net 基于 ModelArts Notbook 的仿真实验

发布于: 4 小时前

​​​​​​​​​​​​​​摘要:U2Net 是一个优秀的显著性目标检测算法,由 Qin Xuebin 等人发表在 Pattern Recognition 2020 期刊[Arxiv]。U2Net 名称的来源在于其网络结构由两层嵌套的 Unet 结构,可以在不需要预训练骨干网络的情况下从零开始训练,拥有优异的表现。


本文分享自华为云社区《ModelArtsNotebook快速开源项目实战 — U2Net》,作者:shpity 。

一、U2Net 介绍


U2Net 是一个优秀的显著性目标检测算法,由 Qin Xuebin 等人发表在 Pattern Recognition 2020 期刊[Arxiv]。U2Net 名称的来源在于其网络结构由两层嵌套的 Unet 结构,可以在不需要预训练骨干网络的情况下从零开始训练,拥有优异的表现。其网络结构如图 1 所示。


图 1. U2Net 的主体框架是一个类似于 U-Net 的编解码结构,但是每一个 block 替换为新提出的残差 U-block 模块


项目开源地址:https://github.com/xuebinqin/U-2-Net

二、创建 Notebook 开发环境


1.   进入ModelArts控制台

2.   选择开发环境 -> Notebook -> 创建

3.   创建 Notebook

3.1 可以选择和任务相关的名称,方便管理;

3.2 为了减少不必要的资源消耗,建议开启自动停止;

3.3 U2Net 所需的运行环境在公共镜像中已经包含,可以选择 pytorch1.4-cuda10.1-cudnn7-ubuntu18.04;

3.4 建议选择 GPU 类型,方便模型快速训练;

3.5 选择立即创建 -> 提交,等待 notebook 创建完成后打开 Notebook。





4.   导入开源项目源码(git/手动上传)

4.1 在 Terminal 使用 git 克隆远程仓库


cd work # 注意:只有/home/ma-user/work目录及其子目录下的文件在Notebook实例关闭后会保存git clone https://github.com/xuebinqin/U-2-Net.git
复制代码


4.2 如果 git 速度较慢也可以从本地上传代码,直接将压缩包拖到左侧文件目录栏或者采用 OBS 上传。

三、 数据准备


1.   下载训练数据 APDrawing dataset

使用 Wget 直接下载到 Notebook,也可下载本地后再拖拽到 Notebook 中。


wget https://cg.cs.tsinghua.edu.cn/people/~Yongjin/APDrawingDB.zipunzip APDrawingDB.zip
复制代码


注:如果数据集较大(>5GB)需要下载到其它目录(实例停止后会被删除),建议存放在 OBS 中,需要的时候随时拉取。


#从OBS中拉取代码到指定目录sh-4.4$ source /home/ma-user/anaconda3/bin/activate PyTorch-1.4sh-4.4$ python>>> mox.file.copy_parallel('obs://bucket-xxxx/APDrawingDB', '/home/ma-user/work/APDrawingDB')
复制代码


2.   切分训练数据

数据集中./APDrawingDB/data/train 中包含了 420 张训练图片,分辨率为 512*1024,左侧为输入图像,右侧为对应的 ground truth。我们需要将大图从中间切分为两个子图。



2.1 在 Notebook 开发环境中新建一个 Pytorch-1.4 的 jupyterNotebook 文件,名称可以为 split.ipynb,脚本将会在./APDrawingDB/data/train/split 目录下生成 840 张子图,其中原始图像以.jpg 结尾,gt 图像以.png 结尾,方便后续训练代码读取【test 文件夹切分步骤同理】。


from PIL import Imageimport ostrain_img_dir = os.path.join("./APDrawingDB/data/train")img_list = os.listdir(train_img_dir)for image in img_list:    img_path = os.path.join(train_img_dir, image)    if not os.path.isdir(img_path):        img = Image.open(img_path)        #print(img.size)        save_img_dir = os.path.join(train_img_dir, 'split_train')        if not os.path.exists(save_img_dir):            os.mkdir(save_img_dir)        save_img_path = os.path.join(save_img_dir, image)        cropped_left = img.crop((0, 0, 512, 512))  # (left, upper, right, lower)        cropped_right = img.crop((512, 0, 1024, 512))  # (left, upper, right, lower)        cropped_left.save(save_img_path[:-3] + 'jpg')        cropped_right.save(save_img_path)
test_img_dir = os.path.join("./APDrawingDB/data/test")img_list = os.listdir(test_img_dir)for image in img_list: img_path = os.path.join(test_img_dir, image) if not os.path.isdir(img_path): img = Image.open(img_path) #print(img.size) save_img_dir = os.path.join(test_img_dir, 'split') if not os.path.exists(save_img_dir): os.mkdir(save_img_dir) save_img_path = os.path.join(save_img_dir, image) cropped_left = img.crop((0, 0, 512, 512)) # (left, upper, right, lower) cropped_right = img.crop((512, 0, 1024, 512)) # (left, upper, right, lower) cropped_left.save(save_img_path[:-3] + 'jpg')
复制代码


3.   将切分好的数据按照如下层级结构整理出训练和测试所需的 datasets 文件夹

datasets/

├── test (70 张切分图片,只包含原图)

└── train (840 张切分图片,包含 420 张原图及对应的 gt)

注:可以将切分好的数据集保存到 OBS 目录中,减少./work 的磁盘空间占用。


4.   完整的 U-2-Net 项目结构如下所示:

U-2-Net/

├── .git

├── LICENSE

├── README.md

├── pycache

├── clipping_camera.jpg

├── data_loader.py

├── datasets

├── figures

├── gradio

├── model

├── requirements.txt

├── saved_models

├── setup_model_weights.py

├── test_data

├── u2net_human_seg_test.py

├── u2net_portrait_demo.py

├── u2net_portrait_test.py

├── u2net_test.py

└── u2net_train.py

四、训练


1.   官方提供的训练代码中数据的路径和我们的 datasets 有些区别,需要对训练脚本进行一些修改,建议使用 jupyter notebook 方便排除错误

新建一个 Pytorch-1.4 的 jupyterNotebook 文件,名称可以为 train.ipynb


import moxing as mox# 如果需要从OBS拷贝切分好的训练数据#mox.file.copy_parallel('obs://bucket-test-xxxx', '/home/ma-user/work/U-2-Net/datasets')
复制代码


INFO:root:Using MoXing-v1.17.3-43fbf97fINFO:root:Using OBS-Python-SDK-3.20.7
复制代码


import osimport torchimport torchvisionfrom torch.autograd import Variableimport torch.nn as nnimport torch.nn.functional as F
from torch.utils.data import Dataset, DataLoaderfrom torchvision import transforms, utilsimport torch.optim as optimimport torchvision.transforms as standard_transforms
import numpy as npimport globimport os
from data_loader import Rescalefrom data_loader import RescaleTfrom data_loader import RandomCropfrom data_loader import ToTensorfrom data_loader import ToTensorLabfrom data_loader import SalObjDataset
from model import U2NETfrom model import U2NETP
复制代码


/home/ma-user/anaconda3/envs/PyTorch-1.4/lib/python3.7/site-packages/skimage/io/manage_plugins.py:23: UserWarning: Your installed pillow version is < 7.1.0. Several security issues (CVE-2020-11538, CVE-2020-10379, CVE-2020-10994, CVE-2020-10177) have been fixed in pillow 7.1.0 or higher. We recommend to upgrade this library.from .collection import imread_collection_wrapper
复制代码


bce_loss = nn.BCELoss(size_average=True)
复制代码


/home/ma-user/anaconda3/envs/PyTorch-1.4/lib/python3.7/site-packages/torch/nn/_reduction.py:43: UserWarning: size_average and reduce args will be deprecated, please use reduction='mean' instead.warnings.warn(warning.format(ret))
复制代码


def muti_bce_loss_fusion(d0, d1, d2, d3, d4, d5, d6, labels_v):
loss0 = bce_loss(d0,labels_v) loss1 = bce_loss(d1,labels_v) loss2 = bce_loss(d2,labels_v) loss3 = bce_loss(d3,labels_v) loss4 = bce_loss(d4,labels_v) loss5 = bce_loss(d5,labels_v) loss6 = bce_loss(d6,labels_v)
loss = loss0 + loss1 + loss2 + loss3 + loss4 + loss5 + loss6 print("l0: %3f, l1: %3f, l2: %3f, l3: %3f, l4: %3f, l5: %3f, l6: %3f\n"%(loss0.data.item(),loss1.data.item(),loss2.data.item(),loss3.data.item(),loss4.data.item(),loss5.data.item(),loss6.data.item()))
return loss0, loss
复制代码


model_name = 'u2net' #'u2netp'
data_dir = os.path.join(os.getcwd(), 'datasets', 'train' + os.sep)# tra_image_dir = os.path.join('DUTS', 'DUTS-TR', 'DUTS-TR', 'im_aug' + os.sep)# tra_label_dir = os.path.join('DUTS', 'DUTS-TR', 'DUTS-TR', 'gt_aug' + os.sep)
image_ext = '.jpg'label_ext = '.png'
model_dir = os.path.join(os.getcwd(), 'saved_models', model_name + os.sep)
epoch_num = 100000batch_size_train = 24batch_size_val = 1train_num = 0val_num = 0
复制代码


tra_img_name_list = glob.glob(data_dir  + '*' + image_ext)
tra_lbl_name_list = []for img_path in tra_img_name_list: img_name = img_path.split(os.sep)[-1]
aaa = img_name.split(".") bbb = aaa[0:-1] imidx = bbb[0] for i in range(1,len(bbb)): imidx = imidx + "." + bbb[i]
tra_lbl_name_list.append(data_dir + imidx + label_ext)
print("---")print("train images: ", len(tra_img_name_list))print("train labels: ", len(tra_lbl_name_list))print("---")
train_num = len(tra_img_name_list)
复制代码


---train images:  420train labels:  420---
复制代码


salobj_dataset = SalObjDataset(    img_name_list=tra_img_name_list,    lbl_name_list=tra_lbl_name_list,    transform=transforms.Compose([        RescaleT(320),        RandomCrop(288),        ToTensorLab(flag=0)]))salobj_dataloader = DataLoader(salobj_dataset, batch_size=batch_size_train, shuffle=True, num_workers=1)
复制代码


# ------- 3. define model --------# define the netif(model_name=='u2net'):    net = U2NET(3, 1)elif(model_name=='u2netp'):    net = U2NETP(3,1)
if torch.cuda.is_available(): net.cuda()
# ------- 4. define optimizer --------print("---define optimizer...")optimizer = optim.Adam(net.parameters(), lr=0.001, betas=(0.9, 0.999), eps=1e-08, weight_decay=0)
复制代码


---define optimizer...
复制代码


# ------- 5. training process --------print("---start training...")ite_num = 0running_loss = 0.0running_tar_loss = 0.0ite_num4val = 0save_frq = 2000 # save the model every 2000 iterations
复制代码


---start training...
复制代码


for epoch in range(0, epoch_num):    net.train()
for i, data in enumerate(salobj_dataloader): ite_num = ite_num + 1 ite_num4val = ite_num4val + 1
inputs, labels = data['image'], data['label']
inputs = inputs.type(torch.FloatTensor) labels = labels.type(torch.FloatTensor)
# wrap them in Variable if torch.cuda.is_available(): inputs_v, labels_v = Variable(inputs.cuda(), requires_grad=False), Variable(labels.cuda(), requires_grad=False) else: inputs_v, labels_v = Variable(inputs, requires_grad=False), Variable(labels, requires_grad=False)
# y zero the parameter gradients optimizer.zero_grad()
# forward + backward + optimize d0, d1, d2, d3, d4, d5, d6 = net(inputs_v) loss2, loss = muti_bce_loss_fusion(d0, d1, d2, d3, d4, d5, d6, labels_v)
loss.backward() optimizer.step()
# # print statistics running_loss += loss.data.item() running_tar_loss += loss2.data.item()
# del temporary outputs and loss del d0, d1, d2, d3, d4, d5, d6, loss2, loss print("[epoch: %3d/%3d, batch: %5d/%5d, ite: %d] train loss: %3f, tar: %3f " % ( epoch + 1, epoch_num, (i + 1) * batch_size_train, train_num, ite_num, running_loss / ite_num4val, running_tar_loss / ite_num4val))
if ite_num % save_frq == 0: model_weight = model_dir + model_name+"_bce_itr_%d_train_%3f_tar_%3f.pth" % (ite_num, running_loss / ite_num4val, running_tar_loss / ite_num4val) torch.save(net.state_dict(), model_weight) mox.file.copy_parallel(model_weight, 'obs://bucket-xxxx/output/model_save/' + model_weight.split('/')[-1]) running_loss = 0.0 running_tar_loss = 0.0 net.train() # resume train ite_num4val = 0
复制代码


l0: 0.167562, l1: 0.153742, l2: 0.156246, l3: 0.163096, l4: 0.176632, l5: 0.197176, l6: 0.247590
[epoch: 1/100000, batch: 24/ 420, ite: 500] train loss: 1.189413, tar: 0.159183 l0: 0.188048, l1: 0.179041, l2: 0.180086, l3: 0.187904, l4: 0.198345, l5: 0.218509, l6: 0.269199
[epoch: 1/100000, batch: 48/ 420, ite: 501] train loss: 1.266652, tar: 0.168805 l0: 0.192491, l1: 0.187615, l2: 0.188043, l3: 0.197142, l4: 0.203571, l5: 0.222019, l6: 0.261745
[epoch: 1/100000, batch: 72/ 420, ite: 502] train loss: 1.313146, tar: 0.174727 l0: 0.169403, l1: 0.155883, l2: 0.157974, l3: 0.164012, l4: 0.175975, l5: 0.195938, l6: 0.244896
[epoch: 1/100000, batch: 96/ 420, ite: 503] train loss: 1.303333, tar: 0.173662 l0: 0.171904, l1: 0.157170, l2: 0.156688, l3: 0.162020, l4: 0.175565, l5: 0.200576, l6: 0.258133
[epoch: 1/100000, batch: 120/ 420, ite: 504] train loss: 1.299787, tar: 0.173369 l0: 0.177398, l1: 0.166131, l2: 0.169089, l3: 0.176976, l4: 0.187039, l5: 0.205449, l6: 0.248036
复制代码

五、测试


新建一个 Pytorch-1.4 的 jupyterNotebook 文件,名称可以为 test.ipynb


import moxing as mox# 拷贝数据mox.file.copy_parallel('obs://bucket-xxxx/output/model_save/u2net.pth', '/home/ma-user/work/U-2-Net/saved_models/u2net/u2net.pth')
复制代码


import osimport sysfrom skimage import io, transformimport torchimport torchvisionfrom torch.autograd import Variableimport torch.nn as nnimport torch.nn.functional as Ffrom torch.utils.data import Dataset, DataLoaderfrom torchvision import transforms#, utils# import torch.optim as optim
import numpy as npfrom PIL import Imageimport glob
from data_loader import RescaleTfrom data_loader import ToTensorfrom data_loader import ToTensorLabfrom data_loader import SalObjDataset
from model import U2NET # full size version 173.6 MBfrom model import U2NETP # small version u2net 4.7 MB%matplotlib inlineimport matplotlib.pyplot as pltfrom matplotlib.pyplot import imshow
复制代码


# normalize the predicted SOD probability mapdef normPRED(d):    ma = torch.max(d)    mi = torch.min(d)
dn = (d-mi)/(ma-mi)
return dn
def save_output(image_name,pred,d_dir, show=False):
predict = pred predict = predict.squeeze() predict_np = predict.cpu().data.numpy()
im = Image.fromarray(predict_np*255).convert('RGB') img_name = image_name.split(os.sep)[-1] image = io.imread(image_name) imo = im.resize((image.shape[1],image.shape[0]),resample=Image.BILINEAR)
pb_np = np.array(imo) if show: show_on_notebook(image, im) aaa = img_name.split(".") bbb = aaa[0:-1] imidx = bbb[0] for i in range(1,len(bbb)): imidx = imidx + "." + bbb[i]
imo.save(d_dir+imidx+'.png') return im def show_on_notebook(image_original, pred): #此函数可以在notebook中展示模型的预测效果 plt.subplot(1,2,1) imshow(np.array(image_original)) plt.subplot(1,2,2) imshow(np.array(pred))
复制代码



# --------- 1. get image path and name ---------model_name='u2net'#u2netp
image_dir = os.path.join(os.getcwd(), 'datasets', 'test') #注意这里的test_data/original存放的是datasets/test中的原始图片,不包含gtprediction_dir = os.path.join(os.getcwd(), 'output', model_name + '_results' + os.sep)model_dir = os.path.join(os.getcwd(), 'saved_models', model_name, model_name + '.pth')img_name_list = glob.glob(os.path.join(os.getcwd(), 'datasets/test/*.jpg'))# print(img_name_list)
# --------- 2. dataloader ---------#1. dataloadertest_salobj_dataset = SalObjDataset(img_name_list = img_name_list, lbl_name_list = [], transform=transforms.Compose([RescaleT(320), ToTensorLab(flag=0)]) )test_salobj_dataloader = DataLoader(test_salobj_dataset, batch_size=1, shuffle=False, num_workers=1)
# --------- 3. model define ---------if(model_name=='u2net'): print("...load U2NET---173.6 MB") net = U2NET(3,1)elif(model_name=='u2netp'): print("...load U2NEP---4.7 MB") net = U2NETP(3,1)
if torch.cuda.is_available(): net.load_state_dict(torch.load(model_dir)) net.cuda()else: net.load_state_dict(torch.load(model_dir, map_location='cpu'))net.eval()
复制代码


# --------- 4. inference for each image ---------for i_test, data_test in enumerate(test_salobj_dataloader):
# print("inferencing:",img_name_list[i_test].split(os.sep)[-1])
inputs_test = data_test['image'] inputs_test = inputs_test.type(torch.FloatTensor)
if torch.cuda.is_available(): inputs_test = Variable(inputs_test.cuda()) else: inputs_test = Variable(inputs_test)
d1,d2,d3,d4,d5,d6,d7= net(inputs_test)
# normalization pred = d1[:,0,:,:] pred = normPRED(pred)
# save results to test_results folder if not os.path.exists(prediction_dir): os.makedirs(prediction_dir, exist_ok=True) save_output(img_name_list[i_test],pred,prediction_dir, show=True)# sys.exit(0)
del d1,d2,d3,d4,d5,d6,d7
复制代码


六、附件


附件:附件.zip


想了解更多的 AI 技术干货,欢迎上华为云的 AI 专区,目前有 AI 编程 Python 等六大实战营供大家免费学习。


点击关注,第一时间了解华为云新鲜技术~

发布于: 4 小时前阅读数: 4
用户头像

提供全面深入的云计算技术干货 2020.07.14 加入

华为云开发者社区,提供全面深入的云计算前景分析、丰富的技术干货、程序样例,分享华为云前沿资讯动态,方便开发者快速成长与发展,欢迎提问、互动,多方位了解云计算! 传送门:https://bbs.huaweicloud.com/

评论

发布
暂无评论
U2Net基于ModelArts Notbook的仿真实验