!14143 metric_learn_pr_1.1
From: @y760019473 Reviewed-by: Signed-off-by:
This commit is contained in:
commit
79a6aef7b0
|
|
@ -0,0 +1,350 @@
|
|||
# 目录
|
||||
|
||||
<!-- TOC -->
|
||||
|
||||
- [深度度量学习描述](#深度度量学习描述)
|
||||
- [模型架构](#模型架构)
|
||||
- [数据集](#数据集)
|
||||
- [特性](#特性)
|
||||
- [混合精度](#混合精度)
|
||||
- [环境要求](#环境要求)
|
||||
- [快速入门](#快速入门)
|
||||
- [脚本说明](#脚本说明)
|
||||
- [脚本及样例代码](#脚本及样例代码)
|
||||
- [脚本参数](#脚本参数)
|
||||
- [训练过程](#训练过程)
|
||||
- [评估过程](#评估过程)
|
||||
- [模型描述](#模型描述)
|
||||
- [性能](#性能)
|
||||
- [评估性能](#评估性能)
|
||||
- [随机情况说明](#随机情况说明)
|
||||
- [ModelZoo主页](#ModelZoo主页)
|
||||
|
||||
<!-- /TOC -->
|
||||
|
||||
# 深度度量学习描述
|
||||
|
||||
## 概述
|
||||
|
||||
度量学习是一种特征空间映射方法,即对于给定的数据集,能够学习到一种度量能力,使得在特征空间中,相同类别的样本具有较小的特征距离,不同类别的样本具有较大的特征距离。在深度学习中,基本度量学习的方法都是使用成对成组的样本进行loss计算的,这类方法被称为pair-based deep metric learning。例如训练模型的过程,我们随意的选取两个样本,提取特征,计算特征之间的距离。 如果这两个样本属于同一个类别,那我们希望他们之间的距离应该尽量的小;如果这两个样本属于不同的类别,那我们希望他们之间的距离应该尽量的大。根据这一原则,衍生出了许多不同类型的pair-based loss,使用这些loss对样本对之间的距离进行计算,并根据生成的loss使用各种优化方法对模型进行更新。基于深度神经网络的度量学习方法已经在许多视觉任务上提升了很大的性能,例如:人脸识别、人脸校验、行人重识别和图像检索等等。
|
||||
|
||||
如下为MindSpore使用Triplet loss和Quadruptlet loss在SOP数据集调优ResNet50的示例,Triplet loss可参考[论文1](https://arxiv.org/abs/1503.03832),Quadruptlet loss是Triplet loss的一个变体,可参考[论文2](https://arxiv.org/abs/1704.01719)。
|
||||
|
||||
为了训练度量学习模型,我们需要一个神经网络模型作为骨架模型(ResNet50)和度量学习代价函数来进行优化。残差神经网络(ResNet)由微软研究院何凯明等五位华人提出,效果非常显著。整个网络只需要学习输入和输出的差异部分,简化了学习目标和难度。ResNet的结构大幅提高了神经网络训练的速度,并且大大提高了模型的准确率。正因如此,ResNet十分受欢迎,经常被各个领域用作backbone网络,在这选择ResNet-50结构作为度量学习的主干网络。我们首先使用softmax来进行预训练,然后使用其它的代价函数来进行微调,例如:triplet,quadruplet。下面就是先在SOP数据集上预训练个pretrain模型,然后用triplet,quadruplet来微调模型。使用8卡Ascend 910训练网络模型,仅需30个周期,就可以在SOP数据集的5184种类别上,TOP1准确率达到了73.9%和74.3%。
|
||||
|
||||
## 论文
|
||||
|
||||
1. [论文1](https://arxiv.org/abs/1503.03832):CVPR2015 F Schroff, Kalenichenko D,Philbin J."FaceNet: A Unified Embedding for Face Recognition and Clustering"
|
||||
|
||||
2. [论文2](https://arxiv.org/abs/1704.01719):CVPR2017 Chen W, Chen X, Zhang J."Beyond triplet loss: A deep quadruplet network for person re-identification"
|
||||
|
||||
# 模型架构
|
||||
|
||||
ResNet的总体网络架构如下:
|
||||
[链接](https://arxiv.org/pdf/1512.03385.pdf)
|
||||
|
||||
# 数据集
|
||||
|
||||
使用的数据集:[SOP](<ftp://cs.stanford.edu/cs/cvgl/Stanford_Online_Products.zip>)
|
||||
|
||||
斯坦福在线商品 (SOP) 数据集,共包含 120053 张商品图片,有 22634 个类别。我们将其分成三组数据集,使用半数数据集进行实验。
|
||||
|
||||
```text
|
||||
# 训练数据划分
|
||||
cd Stanford_Online_Products && sed '1d' Ebay_train.txt | awk -F' ' '{print $4" "$2}' > train.txt
|
||||
cd Stanford_Online_Products && sed '1d' Ebay_test.txt | awk -F' ' '{print $4" "$2}' > test.txt
|
||||
cd Stanford_Online_Products && head -n 29437 train.txt > train_half.txt
|
||||
cd Stanford_Online_Products && head -n 30003 test.txt > test_half.txt
|
||||
cd Stanford_Online_Products && head -n 1012 train.txt > train_tiny.txt
|
||||
cd Stanford_Online_Products && head -n 1048 test.txt > test_tiny.txt
|
||||
```
|
||||
|
||||
- 完整数据集大小:共 22634个类、120053个图像
|
||||
- 训练集:59551个图像,11318 个类别
|
||||
- 测试集:60502个图像,11316 个类别
|
||||
|
||||
- 半数数据集大小:共10368个类、59440个图像
|
||||
- 训练集:29437个图像,5184 个类别
|
||||
- 测试集:30003个图像,5184 个类别
|
||||
|
||||
- 小数据集大小:共320个类、2060个图像
|
||||
- 训练集:1012个图像,160 个类别
|
||||
- 测试集:1048个图像,160 个类别
|
||||
- 下载数据集。目录结构如下:
|
||||
|
||||
```text
|
||||
├─Stanford_Online_Products
|
||||
```
|
||||
|
||||
# 特性
|
||||
|
||||
## 混合精度
|
||||
|
||||
采用[混合精度](https://www.mindspore.cn/tutorial/training/en/master/advanced_use/enable_mixed_precision.html)的训练方法使用支持单精度和半精度数据来提高深度学习神经网络的训练速度,同时保持单精度训练所能达到的网络精度。混合精度训练提高计算速度、减少内存使用的同时,支持在特定硬件上训练更大的模型或实现更大批次的训练。
|
||||
以FP16算子为例,如果输入数据类型为FP32,MindSpore后台会自动降低精度来处理数据。用户可打开INFO日志,搜索“reduce precision”查看精度降低的算子。
|
||||
|
||||
# 环境要求
|
||||
|
||||
- 硬件(Ascend/GPU)
|
||||
- 准备Ascend或GPU处理器搭建硬件环境。
|
||||
- 框架
|
||||
- [MindSpore](https://www.mindspore.cn/install/en)
|
||||
- 如需查看详情,请参见如下资源:
|
||||
- [MindSpore教程](https://www.mindspore.cn/tutorial/training/zh-CN/master/index.html)
|
||||
- [MindSpore Python API](https://www.mindspore.cn/doc/api_python/zh-CN/master/index.html)
|
||||
|
||||
# 快速入门
|
||||
|
||||
通过官方网站安装MindSpore后,您可以按照如下步骤进行训练和评估:
|
||||
|
||||
- Ascend处理器环境运行
|
||||
|
||||
```text
|
||||
# 分布式训练
|
||||
用法:bash run_distribute_train.sh [RANK_TABLE_FILE] [DATASET_PATH] [PRETRAINED_CKPT_PATH] [LOSS_NAME]
|
||||
|
||||
# 单机训练
|
||||
用法:bash run_standalone_train.sh [DATASET_PATH] [CKPT_PATH] [DEVICE_ID] [LOSS_NAME]
|
||||
|
||||
# 运行评估示例
|
||||
用法:bash run_eval.sh [DATASET_PATH] [CKPT_PATH] [DEVICE_ID]
|
||||
```
|
||||
|
||||
# 脚本说明
|
||||
|
||||
## 脚本及样例代码
|
||||
|
||||
```shell
|
||||
.
|
||||
└──metric_learn
|
||||
├── README_CN.md
|
||||
├── scripts
|
||||
├── run_distribute_train.sh # 启动Ascend分布式训练(8卡)
|
||||
├── run_standalone_train.sh # 启动Ascend单机训练(单卡)
|
||||
└── run_eval.sh # 启动Ascend评估
|
||||
├── src
|
||||
├── config.py # 参数配置
|
||||
├── dataset.py # 数据预处理
|
||||
├── loss.py # 度量损失的定义
|
||||
├── lr_generator.py # 生成每个步骤的学习率
|
||||
├── resnet.py # 骨干网络ResNet50定义代码
|
||||
└── utility.py # 数据集读取
|
||||
├── eval.py # 评估网络
|
||||
├── export.py # 模型转换
|
||||
└── train.py # 训练网络
|
||||
```
|
||||
|
||||
## 脚本参数
|
||||
|
||||
在config.py中配置训练参数。
|
||||
|
||||
- 配置ResNet50,Softmax在SOP数据集上的预训练参数。
|
||||
|
||||
```text
|
||||
"class_num":5184, # 数据集类数
|
||||
"batch_size":80, # 输入张量的批次大小
|
||||
"loss_scale":1024, # 损失等级
|
||||
"momentum":0.9, # 动量
|
||||
"weight_decay":1e-4, # 权重衰减
|
||||
"epoch_size":30, # 此值仅适用于训练;应用于推理时固定为1
|
||||
"pretrain_epoch_size":0, # 加载预训练检查点之前已经训练好的模型的周期大小;实际训练周期大小等于epoch_size减去pretrain_epoch_size
|
||||
"save_checkpoint":True, # 是否保存检查点
|
||||
"save_checkpoint_epochs":10, # 两个检查点之间的周期间隔;默认情况下,最后一个检查点将在最后一步完成后保存
|
||||
"keep_checkpoint_max":1, # 只保留最后一个keep_checkpoint_max检查点
|
||||
"save_checkpoint_path":"./", # 检查点保存路径
|
||||
"warmup_epochs":0, # 热身周期数
|
||||
"lr_decay_mode":"steps” # 衰减模式可为步骤、策略和默认
|
||||
"lr_init":0.01, # 初始学习率
|
||||
"lr_end":0.0001, # 最终学习率
|
||||
"lr_max":0.3, # 最大学习率
|
||||
```
|
||||
|
||||
- 配置ResNet50, Tripletloss在SOP数据集上的微调参数
|
||||
|
||||
```text
|
||||
"class_num":5184, # 数据集类数
|
||||
"batch_size":60, # 输入张量的批次大小
|
||||
"loss_scale":1024, # 损失等级
|
||||
"momentum":0.9, # 动量
|
||||
"weight_decay":1e-4, # 权重衰减
|
||||
"epoch_size":30, # 此值仅适用于训练;应用于推理时固定为1
|
||||
"pretrain_epoch_size":0, # 加载预训练检查点之前已经训练好的模型的周期大小;实际训练周期大小等于epoch_size减去pretrain_epoch_size
|
||||
"save_checkpoint":True, # 是否保存检查点
|
||||
"save_checkpoint_epochs":10, # 两个检查点之间的周期间隔;默认情况下,最后一个检查点将在最后一步完成后保存
|
||||
"keep_checkpoint_max":1, # 只保留最后一个keep_checkpoint_max检查点
|
||||
"save_checkpoint_path":"./", # 检查点保存路径
|
||||
"warmup_epochs":0, # 热身周期数
|
||||
"lr_decay_mode":"const” # 衰减模式可为步骤、策略和默认
|
||||
"lr_init":0.01, # 初始学习率
|
||||
"lr_end":0.0001, # 最终学习率
|
||||
"lr_max":0.0001, # 最大学习率
|
||||
```
|
||||
|
||||
- 配置ResNet50, Quadruptloss在SOP数据集上的微调参数
|
||||
|
||||
```text
|
||||
"class_num":5184, # 数据集类数
|
||||
"batch_size":60, # 输入张量的批次大小
|
||||
"loss_scale":1024, # 损失等级
|
||||
"momentum":0.9, # 动量
|
||||
"weight_decay":1e-4, # 权重衰减
|
||||
"epoch_size":30, # 此值仅适用于训练;应用于推理时固定为1
|
||||
"pretrain_epoch_size":0, # 加载预训练检查点之前已经训练好的模型的周期大小;实际训练周期大小等于epoch_size减去pretrain_epoch_size
|
||||
"save_checkpoint":True, # 是否保存检查点
|
||||
"save_checkpoint_epochs":10, # 两个检查点之间的周期间隔;默认情况下,最后一个检查点将在最后一步完成后保存
|
||||
"keep_checkpoint_max":1, # 只保留最后一个keep_checkpoint_max检查点
|
||||
"save_checkpoint_path":"./", # 检查点保存路径
|
||||
"warmup_epochs":0, # 热身周期数
|
||||
"lr_decay_mode":"const” # 衰减模式可为步骤、策略和默认
|
||||
"lr_init":0.01, # 初始学习率
|
||||
"lr_end":0.0001, # 最终学习率
|
||||
"lr_max":0.0001, # 最大学习率
|
||||
```
|
||||
|
||||
## 训练过程
|
||||
|
||||
### 用法
|
||||
|
||||
#### Ascend处理器环境运行
|
||||
|
||||
```text
|
||||
# 分布式训练
|
||||
用法:sh run_distribute_train.sh [RANK_TABLE_FILE] [DATASET_PATH] [PRETRAINED_CKPT_PATH] [LOSS_NAME]
|
||||
|
||||
# 单机训练
|
||||
用法:sh run_standalone_train.sh [DATASET_PATH] [CKPT_PATH] [DEVICE_ID] [LOSS_NAME]
|
||||
```
|
||||
|
||||
分布式训练需要提前创建JSON格式的HCCL配置文件。
|
||||
|
||||
具体操作,参见[hccn_tools](https://gitee.com/mindspore/mindspore/tree/master/model_zoo/utils/hccl_tools)中的说明。
|
||||
|
||||
训练结果保存在示例路径中,文件夹名称以“train”或“train_parallel”开头。您可在此路径下的日志中找到检查点文件以及结果,如下所示。
|
||||
|
||||
运行单卡用例时如果想更换运行卡号,可以通过设置环境变量 `export DEVICE_ID=x` 或者在context中设置 `device_id=x`指定相应的卡号。
|
||||
|
||||
### 结果
|
||||
|
||||
- 使用softmax在SOP数据集上预训练ResNet50
|
||||
|
||||
```text
|
||||
# 分布式训练结果(8P)
|
||||
epoch: 1 step: 46, loss is 8.5783054
|
||||
epoch: 2 step: 46, loss is 8.0682616
|
||||
epoch: 3 step: 46, loss is 7.8836588
|
||||
epoch: 4 step: 46, loss is 7.80090446
|
||||
epoch: 5 step: 46, loss is 7.80853784
|
||||
...
|
||||
```
|
||||
|
||||
- 使用Tripletloss在SOP数据集上微调ResNet50
|
||||
|
||||
```text
|
||||
# 分布式训练结果(8P)
|
||||
epoch: 1 step: 62, loss is 0.357934
|
||||
epoch: 2 step: 62, loss is 0.2891967
|
||||
epoch: 3 step: 62, loss is 0.2131956
|
||||
epoch: 4 step: 62, loss is 0.2302577
|
||||
epoch: 5 step: 62, loss is 0.197817
|
||||
...
|
||||
```
|
||||
|
||||
- 使用Quadruptletloss在SOP数据集上微调ResNet50
|
||||
|
||||
```text
|
||||
# 分布式训练结果(8P)
|
||||
epoch:1 step:62, loss is 1.7601055
|
||||
epoch:2 step:62, loss is 1.6955021
|
||||
epoch:3 step:62, loss is 1.5707983
|
||||
epoch:4 step:62, loss is 1.462166
|
||||
epoch:5 step:62, loss is 1.393667
|
||||
...
|
||||
```
|
||||
|
||||
## 导出MINDIR
|
||||
|
||||
修改`export`文件中的`ckpt_file`并运行。
|
||||
|
||||
```bash
|
||||
python export.py --ckpt_file [CKPT_PATH]
|
||||
```
|
||||
|
||||
## 评估过程
|
||||
|
||||
### 用法
|
||||
|
||||
#### Ascend处理器环境运行
|
||||
|
||||
```bash
|
||||
# 评估
|
||||
Usage: sh run_eval.sh [DATASET_PATH] [CHECKPOINT_PATH]
|
||||
```
|
||||
|
||||
```bash
|
||||
# 评估示例
|
||||
sh run_eval.sh ~/Stanford_Online_Products ~/ResNet50.ckpt
|
||||
```
|
||||
|
||||
### 结果
|
||||
|
||||
评估结果保存在示例路径中,文件夹名为“eval”。您可在此路径下的日志找到如下结果:
|
||||
|
||||
- 使用SOP数据集评估ResNet50-triplet的结果
|
||||
|
||||
```text
|
||||
result: {'acc': 0.739} ckpt=~/ResNet50_triplet.ckpt
|
||||
```
|
||||
|
||||
- 使用SOP数据集评估ResNet50-quadrupletloss的结果
|
||||
|
||||
```text
|
||||
result: {'acc': 0.743} ckpt=~/ResNet50_quadruplet.ckpt
|
||||
```
|
||||
|
||||
# 模型描述
|
||||
|
||||
## 性能
|
||||
|
||||
### 评估性能
|
||||
|
||||
#### SOP上的ResNet50-Triplet
|
||||
|
||||
| 参数 | Ascend 910 |
|
||||
| -------------------------- | -------------------------------------- |
|
||||
| 模型版本 | ResNet50-Triplet |
|
||||
| 资源 | Ascend 910;CPU:2.60GHz,192核;内存:755G |
|
||||
| 上传日期 | 2021-03-25 ; |
|
||||
| MindSpore版本 | 1.1.1-alpha |
|
||||
| 数据集 | Stanford_Online_Products |
|
||||
| 训练参数 | epoch=30, steps per epoch=62, batch_size = 60 |
|
||||
| 优化器 | Momentum |
|
||||
| 损失函数 | Triplet loss |
|
||||
| 输出 | 概率 |
|
||||
| 损失 | 0.115702 |
|
||||
| 速度 | 110毫秒/步(8卡) |
|
||||
| 总时长 | 21分钟 |
|
||||
|
||||
#### SOP上的ResNet50-Quadruplet
|
||||
|
||||
| 参数 | Ascend 910 |
|
||||
| -------------------------- | -------------------------------------- |
|
||||
| 模型版本 | ResNet50-Quadruplet |
|
||||
| 资源 | Ascend 910;CPU:2.60GHz,192核;内存:755G |
|
||||
| 上传日期 | 2021-03-25 ; |
|
||||
| MindSpore版本 | 1.1.1-alpha |
|
||||
| 数据集 | Stanford_Online_Products |
|
||||
| 训练参数 | epoch=30, steps per epoch=62, batch_size = 60 |
|
||||
| 优化器 | Momentum |
|
||||
| 损失函数 | Quadruplet loss |
|
||||
| 输出 | 概率 |
|
||||
| 损失 | 0.81702 |
|
||||
| 速度 | 90毫秒/步(8卡) |
|
||||
| 总时长 | 12分钟 |
|
||||
|
||||
# 随机情况说明
|
||||
|
||||
`dataset.py`中设置了“create_dataset”函数内的种子,同时还使用了train.py中的随机种子。
|
||||
|
||||
# ModelZoo主页
|
||||
|
||||
请浏览官网[主页](https://gitee.com/mindspore/mindspore/tree/master/model_zoo)。
|
||||
|
|
@ -0,0 +1,91 @@
|
|||
# Copyright 2021 Huawei Technologies Co., Ltd
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
# You may obtain a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
# ============================================================================
|
||||
"""eval resnet."""
|
||||
import os
|
||||
import ast
|
||||
import argparse
|
||||
import numpy as np
|
||||
from mindspore import context
|
||||
from mindspore.common import set_seed
|
||||
from mindspore.train.model import Model
|
||||
from mindspore.train.serialization import load_checkpoint, load_param_into_net
|
||||
from src.resnet import resnet50
|
||||
from src.dataset import create_dataset0 as create_dataset
|
||||
from src.utility import GetDatasetGenerator_eval, recall_topk_parallel
|
||||
|
||||
parser = argparse.ArgumentParser(description='Image classification')
|
||||
# modelarts parameter
|
||||
parser.add_argument('--data_url', type=str, default=None, help='Dataset path')
|
||||
parser.add_argument('--ckpt_url', type=str, default=None, help='ckpt path')
|
||||
parser.add_argument('--checkpoint_name', type=str, default='resnet-120_625.ckpt', help='Checkpoint file')
|
||||
# Ascend parameter
|
||||
parser.add_argument('--dataset_path', type=str, default=None, help='Dataset path')
|
||||
parser.add_argument('--ckpt_path', type=str, default=None, help='Checkpoint file path')
|
||||
parser.add_argument('--device_id', type=int, default=0, help='Device id')
|
||||
parser.add_argument('--run_modelarts', type=ast.literal_eval, default=False, help='Run distribute')
|
||||
args_opt = parser.parse_args()
|
||||
set_seed(1)
|
||||
|
||||
if __name__ == '__main__':
|
||||
|
||||
if args_opt.run_modelarts:
|
||||
import moxing as mox
|
||||
device_id = int(os.getenv('DEVICE_ID'))
|
||||
device_num = int(os.getenv('RANK_SIZE'))
|
||||
context.set_context(device_id=device_id)
|
||||
local_data_url = '/cache/data/'
|
||||
local_ckpt_url = '/cache/ckpt/'
|
||||
mox.file.copy_parallel(args_opt.data_url, local_data_url)
|
||||
mox.file.copy_parallel(args_opt.ckpt_url, local_ckpt_url)
|
||||
DATA_DIR = local_data_url
|
||||
else:
|
||||
device_id = args_opt.device_id
|
||||
device_num = 1
|
||||
context.set_context(device_id=args_opt.device_id)
|
||||
DATA_DIR = args_opt.dataset_path
|
||||
|
||||
context.set_context(mode=context.GRAPH_MODE, device_target='Ascend', save_graphs=False)
|
||||
|
||||
#dataset
|
||||
VAL_LIST = DATA_DIR + "/test_half.txt"
|
||||
dataset_generator_val = GetDatasetGenerator_eval(DATA_DIR, VAL_LIST)
|
||||
|
||||
eval_dataset = create_dataset(dataset_generator_val, do_train=False, batch_size=30,
|
||||
device_num=device_num, rank_id=device_id)
|
||||
step_size = eval_dataset.get_dataset_size()
|
||||
|
||||
# define net
|
||||
net = resnet50(class_num=5184)
|
||||
|
||||
# load checkpoint
|
||||
if args_opt.run_modelarts:
|
||||
checkpoint_path = os.path.join(local_ckpt_url, args_opt.checkpoint_name)
|
||||
else:
|
||||
checkpoint_path = args_opt.ckpt_path
|
||||
param_dict = load_checkpoint(checkpoint_path)
|
||||
load_param_into_net(net.backbone, param_dict)
|
||||
net.set_train(False)
|
||||
|
||||
# define model
|
||||
model_eval = Model(net.backbone)
|
||||
f, l = [], []
|
||||
for data in eval_dataset.create_dict_iterator():
|
||||
out = model_eval.predict(data['image'])
|
||||
f.append(out.asnumpy())
|
||||
l.append(data['label'].asnumpy())
|
||||
f = np.vstack(f)
|
||||
l = np.hstack(l)
|
||||
recall = recall_topk_parallel(f, l, k=1)
|
||||
print("eval_recall:", recall, "ckpt=", checkpoint_path)
|
||||
|
|
@ -0,0 +1,51 @@
|
|||
# Copyright 2021 Huawei Technologies Co., Ltd
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
# You may obtain a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
# ============================================================================
|
||||
"""
|
||||
##############export checkpoint file into air and onnx models#################
|
||||
python export.py
|
||||
"""
|
||||
import argparse
|
||||
import numpy as np
|
||||
from mindspore import Tensor, load_checkpoint, load_param_into_net, export, context
|
||||
from src.config import config0 as config
|
||||
from src.resnet import resnet50
|
||||
|
||||
parser = argparse.ArgumentParser(description='resnet50 export')
|
||||
parser.add_argument("--device_id", type=int, default=0, help="Device id")
|
||||
parser.add_argument("--batch_size", type=int, default=1, help="batch size")
|
||||
parser.add_argument("--ckpt_file", type=str, required=True, help="Checkpoint file path.")
|
||||
parser.add_argument("--file_name", type=str, default="resnet50", help="output file name.")
|
||||
parser.add_argument('--width', type=int, default=224, help='input width')
|
||||
parser.add_argument('--height', type=int, default=224, help='input height')
|
||||
parser.add_argument("--file_format", type=str, choices=["AIR", "ONNX", "MINDIR"], default="MINDIR", help="file format")
|
||||
parser.add_argument("--device_target", type=str, default="Ascend",
|
||||
choices=["Ascend", "GPU", "CPU"], help="device target(default: Ascend)")
|
||||
args = parser.parse_args()
|
||||
|
||||
context.set_context(mode=context.GRAPH_MODE, device_target=args.device_target)
|
||||
if args.device_target == "Ascend":
|
||||
context.set_context(device_id=args.device_id)
|
||||
|
||||
if __name__ == '__main__':
|
||||
|
||||
net = resnet50(config.class_num)
|
||||
|
||||
assert args.ckpt_file is not None, "checkpoint_path is None."
|
||||
|
||||
param_dict = load_checkpoint(args.ckpt_file)
|
||||
load_param_into_net(net.backbone, param_dict)
|
||||
|
||||
input_arr = Tensor(np.zeros([args.batch_size, 3, args.height, args.width], np.float32))
|
||||
export(net.backbone, input_arr, file_name=args.file_name, file_format=args.file_format)
|
||||
|
|
@ -0,0 +1,67 @@
|
|||
#!/bin/bash
|
||||
# Copyright 2021 Huawei Technologies Co., Ltd
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
# You may obtain a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
# ============================================================================
|
||||
|
||||
if [ $# != 4 ]; then
|
||||
echo "Usage: sh run_distribute_train.sh [RANK_TABLE_FILE] [DATASET_PATH] [CHECKPOINT_PATH] [LOSS_NAME]"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
get_real_path() {
|
||||
if [ "${1:0:1}" == "/" ]; then
|
||||
echo "$1"
|
||||
else
|
||||
echo "$(realpath -m $PWD/$1)"
|
||||
fi
|
||||
}
|
||||
|
||||
PATH1=$(get_real_path $1)
|
||||
PATH2=$(get_real_path $2)
|
||||
PATH3=$(get_real_path $3)
|
||||
|
||||
if [ ! -f $PATH1 ]; then
|
||||
echo "error: RANK_TABLE_FILE=$PATH1 is not a file"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if [ ! -d $PATH2 ]; then
|
||||
echo "error: DATASET_PATH=$PATH2 is not a directory"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if [ ! -f $PATH3 ]; then
|
||||
echo "error: CKPT_PATH=$PATH3 is not a file"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
ulimit -u unlimited
|
||||
export DEVICE_NUM=8
|
||||
export RANK_SIZE=8
|
||||
export RANK_TABLE_FILE=$PATH1
|
||||
|
||||
for ((i = 0; i < ${DEVICE_NUM}; i++)); do
|
||||
export DEVICE_ID=$i
|
||||
export RANK_ID=$i
|
||||
rm -rf ./train_parallel$i
|
||||
mkdir ./train_parallel$i
|
||||
cp ../*.py ./train_parallel$i
|
||||
cp *.sh ./train_parallel$i
|
||||
cp -r ../src ./train_parallel$i
|
||||
cd ./train_parallel$i || exit
|
||||
echo "start training for rank $RANK_ID, device $DEVICE_ID"
|
||||
env >env.log
|
||||
python train.py --dataset_path=$PATH2 --ckpt_path=$PATH3 --loss_name=$4 --run_distribute=True > log.txt 2>&1 &
|
||||
cd ..
|
||||
done
|
||||
|
|
@ -0,0 +1,54 @@
|
|||
#!/bin/bash
|
||||
# Copyright 2021 Huawei Technologies Co., Ltd
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
# You may obtain a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
# ============================================================================
|
||||
|
||||
if [ $# != 3 ]; then
|
||||
echo "Usage: sh run_eval.sh [DATASET_PATH] [CHECKPOINT_PATH] [DEVICE_ID]"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
get_real_path() {
|
||||
if [ "${1:0:1}" == "/" ]; then
|
||||
echo "$1"
|
||||
else
|
||||
echo "$(realpath -m $PWD/$1)"
|
||||
fi
|
||||
}
|
||||
|
||||
PATH1=$(get_real_path $1)
|
||||
PATH2=$(get_real_path $2)
|
||||
|
||||
if [ ! -d $PATH1 ]
|
||||
then
|
||||
echo "error: DATASET_PATH=$PATH1 is not a directory"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if [ ! -f $PATH2 ]
|
||||
then
|
||||
echo "error: CHECKPOINT_PATH=$PATH2 is not a file"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
rm -rf ./eval
|
||||
mkdir ./eval
|
||||
cp ../*.py ./eval
|
||||
cp *.sh ./eval
|
||||
cp -r ../src ./eval
|
||||
cd ./eval || exit
|
||||
env > env.log
|
||||
echo "start evaluation for device $3"
|
||||
python eval.py --dataset_path=$PATH1 --ckpt_path=$PATH2 --device_id=$3 &> eval.log &
|
||||
cd ..
|
||||
|
|
@ -0,0 +1,54 @@
|
|||
#!/bin/bash
|
||||
# Copyright 2021 Huawei Technologies Co., Ltd
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
# You may obtain a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
# ============================================================================
|
||||
|
||||
if [ $# != 4 ]; then
|
||||
echo "Usage: sh run_standalone_train.sh [DATASET_PATH] [CHECKPOINT_PATH] [DEVICE_ID] [LOSS_NAME]"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
get_real_path() {
|
||||
if [ "${1:0:1}" == "/" ]; then
|
||||
echo "$1"
|
||||
else
|
||||
echo "$(realpath -m $PWD/$1)"
|
||||
fi
|
||||
}
|
||||
|
||||
PATH1=$(get_real_path $1)
|
||||
PATH2=$(get_real_path $2)
|
||||
|
||||
if [ ! -d $PATH1 ]
|
||||
then
|
||||
echo "error: DATASET_PATH=$PATH1 is not a directory"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if [ ! -f $PATH2 ]
|
||||
then
|
||||
echo "error: CKPT_PATH=$PATH2 is not a file"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
rm -rf ./train
|
||||
mkdir ./train
|
||||
cp ../*.py ./train
|
||||
cp *.sh ./train
|
||||
cp -r ../src ./train
|
||||
cd ./train || exit
|
||||
env > env.log
|
||||
echo "start training for device $3"
|
||||
python train.py --dataset_path=$PATH1 --ckpt_path=$PATH2 --device_id=$3 --loss_name=$4 > log.txt 2>&1 &
|
||||
cd ..
|
||||
|
|
@ -0,0 +1,78 @@
|
|||
# Copyright 2021 Huawei Technologies Co., Ltd
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
# You may obtain a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
# ============================================================================
|
||||
"""
|
||||
config.
|
||||
"""
|
||||
from easydict import EasyDict as ed
|
||||
|
||||
#sop softmax
|
||||
config0 = ed({
|
||||
"class_num": 5184,
|
||||
"batch_size": 80,
|
||||
"loss_scale": 1024,
|
||||
"momentum": 0.9,
|
||||
"weight_decay": 1e-4,
|
||||
"epoch_size": 30,
|
||||
"pretrain_epoch_size": 0,
|
||||
"save_checkpoint": True,
|
||||
"save_checkpoint_epochs": 10,
|
||||
"keep_checkpoint_max": 5,
|
||||
"save_checkpoint_path": "./softmax/",
|
||||
"warmup_epochs": 0,
|
||||
"lr_decay_mode": "steps",
|
||||
"lr_end": 0.01,
|
||||
"lr_init": 0.0001,
|
||||
"lr_max": 0.3
|
||||
})
|
||||
|
||||
#sop trpletloss
|
||||
config1 = ed({
|
||||
"class_num": 5184,
|
||||
"batch_size": 60,
|
||||
"loss_scale": 1024,
|
||||
"momentum": 0.9,
|
||||
"weight_decay": 1e-4,
|
||||
"epoch_size": 30,
|
||||
"pretrain_epoch_size": 0,
|
||||
"save_checkpoint": True,
|
||||
"save_checkpoint_epochs": 10,
|
||||
"keep_checkpoint_max": 1,
|
||||
"save_checkpoint_path": "./triplet/",
|
||||
"warmup_epochs": 0,
|
||||
"lr_decay_mode": "const",
|
||||
"lr_end": 0.01,
|
||||
"lr_init": 0.0001,
|
||||
"lr_max": 0.0001
|
||||
})
|
||||
|
||||
#sop quadrupletloss
|
||||
config2 = ed({
|
||||
"class_num": 5184,
|
||||
"batch_size": 60,
|
||||
"loss_scale": 1024,
|
||||
"momentum": 0.9,
|
||||
"weight_decay": 1e-4,
|
||||
"epoch_size": 30,
|
||||
"pretrain_epoch_size": 0,
|
||||
"save_checkpoint": True,
|
||||
"save_checkpoint_epochs": 10,
|
||||
"keep_checkpoint_max": 1,
|
||||
"save_checkpoint_path": "./quadruplet/",
|
||||
"warmup_epochs": 0,
|
||||
"lr_decay_mode": "const",
|
||||
"lr_end": 0.01,
|
||||
"lr_init": 0.0001,
|
||||
"lr_max": 0.0001
|
||||
})
|
||||
|
|
@ -0,0 +1,74 @@
|
|||
# Copyright 2021 Huawei Technologies Co., Ltd
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
# You may obtain a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
# ============================================================================
|
||||
"""
|
||||
create train or eval dataset.
|
||||
"""
|
||||
import mindspore.common.dtype as mstype
|
||||
import mindspore.dataset as dss
|
||||
import mindspore.dataset.vision.c_transforms as C
|
||||
import mindspore.dataset.transforms.c_transforms as C2
|
||||
from mindspore.dataset.vision import Inter
|
||||
|
||||
def create_dataset0(dataset_generator, do_train, batch_size=80, device_num=1, rank_id=0):
|
||||
"""softmax dataset"""
|
||||
if device_num == 1:
|
||||
ds = dss.GeneratorDataset(dataset_generator, ["image", "label"], num_parallel_workers=8, shuffle=True)
|
||||
else:
|
||||
ds = dss.GeneratorDataset(dataset_generator, ["image", "label"], num_parallel_workers=8, shuffle=True,
|
||||
num_shards=device_num, shard_id=rank_id)
|
||||
trans = []
|
||||
if do_train:
|
||||
trans += [
|
||||
C.RandomResizedCrop(224, scale=(0.08, 1.0), ratio=(3./4, 4./3), interpolation=Inter.BICUBIC)
|
||||
]
|
||||
trans += [
|
||||
C.Resize((224, 224)),
|
||||
C.Rescale(1.0 / 255.0, 0.0),
|
||||
C.Normalize([0.485, 0.456, 0.406], [0.229, 0.224, 0.225]),
|
||||
C.HWC2CHW(),
|
||||
C2.TypeCast(mstype.float32)
|
||||
]
|
||||
ds = ds.map(operations=trans, input_columns="image", num_parallel_workers=8)
|
||||
type_cast_op = C2.TypeCast(mstype.int32)
|
||||
ds = ds.map(operations=type_cast_op, input_columns="label", num_parallel_workers=8)
|
||||
# apply batch operations
|
||||
ds = ds.batch(batch_size, drop_remainder=True)
|
||||
return ds
|
||||
|
||||
def create_dataset1(dataset_generator, do_train, batch_size=80, device_num=1, rank_id=0):
|
||||
"""triplet/quadruplet dataset"""
|
||||
if device_num == 1:
|
||||
ds = dss.GeneratorDataset(dataset_generator, ["image", "label"], num_parallel_workers=8, shuffle=False)
|
||||
else:
|
||||
ds = dss.GeneratorDataset(dataset_generator, ["image", "label"], num_parallel_workers=8, shuffle=False,
|
||||
num_shards=device_num, shard_id=rank_id)
|
||||
trans = []
|
||||
if do_train:
|
||||
trans += [
|
||||
C.RandomResizedCrop(224, scale=(0.08, 1.0), ratio=(3./4, 4./3), interpolation=Inter.BICUBIC)
|
||||
]
|
||||
trans += [
|
||||
C.Resize((224, 224)),
|
||||
C.Rescale(1.0 / 255.0, 0.0),
|
||||
C.Normalize([0.485, 0.456, 0.406], [0.229, 0.224, 0.225]),
|
||||
C.HWC2CHW(),
|
||||
C2.TypeCast(mstype.float32)
|
||||
]
|
||||
ds = ds.map(operations=trans, input_columns="image", num_parallel_workers=8)
|
||||
type_cast_op = C2.TypeCast(mstype.int32)
|
||||
ds = ds.map(operations=type_cast_op, input_columns="label", num_parallel_workers=8)
|
||||
# apply batch operations
|
||||
ds = ds.batch(batch_size, drop_remainder=False)
|
||||
return ds
|
||||
|
|
@ -0,0 +1,145 @@
|
|||
# Copyright 2021 Huawei Technologies Co., Ltd
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
# You may obtain a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
# ============================================================================
|
||||
"""define loss function for network"""
|
||||
import numpy as np
|
||||
import mindspore
|
||||
import mindspore.nn as nn
|
||||
from mindspore import Tensor
|
||||
from mindspore.common import dtype as mstype
|
||||
from mindspore.nn.loss.loss import _Loss
|
||||
from mindspore.ops import operations as P
|
||||
from mindspore.ops import functional as F
|
||||
|
||||
class Softmaxloss(_Loss):
|
||||
"""Softmaxloss"""
|
||||
def __init__(self, sparse=True, smooth_factor=0.1, num_classes=5184):
|
||||
super(Softmaxloss, self).__init__()
|
||||
self.onehot = P.OneHot()
|
||||
self.sparse = sparse
|
||||
self.on_value = Tensor(1.0 - smooth_factor, mstype.float32)
|
||||
self.off_value = Tensor(1.0 * smooth_factor / (num_classes - 1), mstype.float32)
|
||||
self.ce = nn.SoftmaxCrossEntropyWithLogits(sparse=sparse, reduction="mean")
|
||||
def construct(self, logit, label=None):
|
||||
"""Tripletloss"""
|
||||
if not self.sparse:
|
||||
label = self.onehot(label, F.shape(logit)[1], self.on_value, self.off_value)
|
||||
loss = self.ce(logit, label)
|
||||
return loss
|
||||
|
||||
class Tripletloss(_Loss):
|
||||
"""Tripletloss"""
|
||||
def __init__(self, margin=0.1):
|
||||
super(Tripletloss, self).__init__()
|
||||
self.margin = margin
|
||||
self.sqrt = P.Sqrt()
|
||||
self.reduce_sum = P.ReduceSum(keep_dims=True)
|
||||
self.square = P.Square()
|
||||
self.div = P.Div()
|
||||
self.reshape = P.Reshape()
|
||||
self.split = P.Split(1, 3)
|
||||
self.relu = nn.ReLU()
|
||||
self.expand_dims = P.ExpandDims()
|
||||
def construct(self, logit, label=None):
|
||||
"""Tripletloss c"""
|
||||
fea_dim = logit.shape[1]
|
||||
input_norm = self.sqrt(self.reduce_sum(self.square(logit), 1))
|
||||
logit = self.div(logit, input_norm)
|
||||
output = self.reshape(logit, (-1, 3, fea_dim))
|
||||
anchor, positive, negative = self.split(output)
|
||||
anchor = F.reshape(anchor, (-1, fea_dim))
|
||||
positive = self.reshape(positive, (-1, fea_dim))
|
||||
negative = self.reshape(negative, (-1, fea_dim))
|
||||
a_p = self.square(anchor - positive)
|
||||
a_n = self.square(anchor - negative)
|
||||
a_p = self.reduce_sum(a_p, 1)
|
||||
a_n = self.reduce_sum(a_n, 1)
|
||||
loss = a_p - a_n + self.margin
|
||||
loss = self.relu(loss)
|
||||
return loss
|
||||
|
||||
def generate_index(batch_size, samples_each_class):
|
||||
"""generate_index"""
|
||||
a = np.arange(0, batch_size * batch_size, 1)
|
||||
a = a.reshape(-1, batch_size)
|
||||
#steps = batch_size // samples_each_class
|
||||
res = []
|
||||
for i in range(batch_size):
|
||||
step = i // samples_each_class
|
||||
start = step * samples_each_class
|
||||
end = (step + 1) * samples_each_class
|
||||
p = []
|
||||
n = []
|
||||
for j, k in enumerate(a[i]):
|
||||
if start <= j < end:
|
||||
if j == i:
|
||||
p.insert(0, k)
|
||||
else:
|
||||
p.append(k)
|
||||
else:
|
||||
n.append(k)
|
||||
comb = p + n
|
||||
res += comb
|
||||
res = np.array(res).astype(np.int32)
|
||||
return res
|
||||
|
||||
class Quadrupletloss(_Loss):
|
||||
"""Quadrupletloss"""
|
||||
def __init__(self, train_batch_size=30, samples_each_class=2, margin=0.1):
|
||||
super(Quadrupletloss, self).__init__()
|
||||
self.margin = margin
|
||||
self.samples_each_class = samples_each_class
|
||||
self.train_batch_size = train_batch_size
|
||||
assert self.train_batch_size % samples_each_class == 0
|
||||
self.sqrt = P.Sqrt()
|
||||
self.reduce_sum = P.ReduceSum(keep_dims=True)
|
||||
self.reduce_sum1 = P.ReduceSum(keep_dims=False)
|
||||
self.square = P.Square()
|
||||
self.div = P.Div()
|
||||
self.reshape = P.Reshape()
|
||||
self.relu = nn.ReLU()
|
||||
self.reduce_max = P.ReduceMax()
|
||||
self.reduce_min = P.ReduceMin()
|
||||
self.matmul = nn.MatMul(False, True)
|
||||
self.tensoradd = P.TensorAdd()
|
||||
#self.tensoradd = P.Add()
|
||||
self.assign = P.Assign()
|
||||
self.gather = P.GatherV2()
|
||||
self.index = generate_index(self.train_batch_size, self.samples_each_class)
|
||||
self.index = Tensor(self.index, mstype.int32)
|
||||
self.index_var = mindspore.Parameter(Tensor(np.zeros(self.train_batch_size * self.train_batch_size),
|
||||
mindspore.int32), name='index_var')
|
||||
def construct(self, logit, label=None):
|
||||
"""Quadrupletloss c"""
|
||||
input_norm = self.sqrt(self.reduce_sum(self.square(logit), 1))
|
||||
logit = self.div(logit, input_norm)
|
||||
margin = self.margin
|
||||
feature = self.reshape(logit, (self.train_batch_size, -1))
|
||||
ab = self.matmul(feature, feature)
|
||||
a2 = self.square(feature)
|
||||
a2 = self.reduce_sum1(a2)
|
||||
d = self.tensoradd(-2*ab, a2)
|
||||
d = self.tensoradd(d, a2)
|
||||
d = self.reshape(d, (-1, 1))
|
||||
self.index_var = self.assign(self.index_var, self.index)
|
||||
d = self.gather(d, self.index_var, 0)
|
||||
dd = self.reshape(d, (-1, self.train_batch_size))
|
||||
ignore = dd[:, 0 : 1]
|
||||
ignore = F.stop_gradient(ignore)
|
||||
pos = dd[:, 1 : self.samples_each_class]
|
||||
neg = dd[:, self.samples_each_class: self.train_batch_size]
|
||||
pos_max = self.reduce_max(pos)
|
||||
neg_min = self.reduce_min(neg)
|
||||
loss = self.relu(pos_max - neg_min + margin)
|
||||
return loss
|
||||
|
|
@ -0,0 +1,193 @@
|
|||
# Copyright 2021 Huawei Technologies Co., Ltd
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
# You may obtain a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
# ============================================================================
|
||||
"""learning rate generator"""
|
||||
import math
|
||||
import numpy as np
|
||||
|
||||
|
||||
def _generate_poly_lr(lr_init, lr_end, lr_max, total_steps, warmup_steps):
|
||||
"""
|
||||
Applies polynomial decay to generate learning rate array.
|
||||
|
||||
Args:
|
||||
lr_init(float): init learning rate.
|
||||
lr_end(float): end learning rate
|
||||
lr_max(float): max learning rate.
|
||||
total_steps(int): all steps in training.
|
||||
warmup_steps(int): all steps in warmup epochs.
|
||||
|
||||
Returns:
|
||||
np.array, learning rate array.
|
||||
"""
|
||||
lr_each_step = []
|
||||
if warmup_steps != 0:
|
||||
inc_each_step = (float(lr_max) - float(lr_init)) / float(warmup_steps)
|
||||
else:
|
||||
inc_each_step = 0
|
||||
for i in range(total_steps):
|
||||
if i < warmup_steps:
|
||||
lr = float(lr_init) + inc_each_step * float(i)
|
||||
else:
|
||||
base = (1.0 - (float(i) - float(warmup_steps)) / (float(total_steps) - float(warmup_steps)))
|
||||
lr = float(lr_max) * base * base
|
||||
if lr < 0.0:
|
||||
lr = 0.0
|
||||
lr_each_step.append(lr)
|
||||
return lr_each_step
|
||||
|
||||
def _generate_cosine_lr(lr_init, lr_end, lr_max, total_steps, warmup_steps):
|
||||
"""
|
||||
Applies cosine decay to generate learning rate array.
|
||||
|
||||
Args:
|
||||
lr_init(float): init learning rate.
|
||||
lr_end(float): end learning rate
|
||||
lr_max(float): max learning rate.
|
||||
total_steps(int): all steps in training.
|
||||
warmup_steps(int): all steps in warmup epochs.
|
||||
|
||||
Returns:
|
||||
np.array, learning rate array.
|
||||
"""
|
||||
decay_steps = total_steps - warmup_steps
|
||||
lr_each_step = []
|
||||
for i in range(total_steps):
|
||||
if i < warmup_steps:
|
||||
lr_inc = (float(lr_max) - float(lr_init)) / float(warmup_steps)
|
||||
lr = float(lr_init) + lr_inc * (i + 1)
|
||||
else:
|
||||
linear_decay = (total_steps - i) / decay_steps
|
||||
cosine_decay = 0.5 * (1 + math.cos(math.pi * 2 * 0.47 * i / decay_steps))
|
||||
decayed = linear_decay * cosine_decay + 0.00001
|
||||
lr = lr_max * decayed
|
||||
lr_each_step.append(lr)
|
||||
return lr_each_step
|
||||
|
||||
def _generate_liner_lr(lr_init, lr_end, lr_max, total_steps, warmup_steps):
|
||||
"""
|
||||
Applies liner decay to generate learning rate array.
|
||||
|
||||
Args:
|
||||
lr_init(float): init learning rate.
|
||||
lr_end(float): end learning rate
|
||||
lr_max(float): max learning rate.
|
||||
total_steps(int): all steps in training.
|
||||
warmup_steps(int): all steps in warmup epochs.
|
||||
|
||||
Returns:
|
||||
np.array, learning rate array.
|
||||
"""
|
||||
lr_each_step = []
|
||||
for i in range(total_steps):
|
||||
if i < warmup_steps:
|
||||
lr = lr_init + (lr_max - lr_init) * i / warmup_steps
|
||||
else:
|
||||
lr = lr_max - (lr_max - lr_end) * (i - warmup_steps) / (total_steps - warmup_steps)
|
||||
lr_each_step.append(lr)
|
||||
return lr_each_step
|
||||
|
||||
def _generate_steps_lr(lr_init, lr_max, total_steps, warmup_steps):
|
||||
"""
|
||||
Applies three steps decay to generate learning rate array.
|
||||
|
||||
Args:
|
||||
lr_init(float): init learning rate.
|
||||
lr_max(float): max learning rate.
|
||||
total_steps(int): all steps in training.
|
||||
warmup_steps(int): all steps in warmup epochs.
|
||||
|
||||
Returns:
|
||||
np.array, learning rate array.
|
||||
"""
|
||||
decay_epoch_index = [0.4 * total_steps, 0.8 * total_steps]
|
||||
lr_each_step = []
|
||||
for i in range(total_steps):
|
||||
if i < warmup_steps:
|
||||
lr = lr_init + (lr_max - lr_init) * i / warmup_steps
|
||||
else:
|
||||
if i < decay_epoch_index[0]:
|
||||
lr = lr_max
|
||||
elif i < decay_epoch_index[1]:
|
||||
lr = lr_max * 0.1
|
||||
else:
|
||||
lr = lr_max * 0.01
|
||||
lr_each_step.append(lr)
|
||||
return lr_each_step
|
||||
|
||||
def _generate_const_lr(lr_init, lr_max, total_steps, warmup_steps):
|
||||
"""const lr"""
|
||||
lr_each_step = []
|
||||
for _ in range(total_steps):
|
||||
lr_each_step.append(lr_max)
|
||||
return lr_each_step
|
||||
|
||||
def get_lr(lr_init, lr_end, lr_max, warmup_epochs, total_epochs, steps_per_epoch, lr_decay_mode):
|
||||
"""get lr"""
|
||||
lr_each_step = []
|
||||
total_steps = steps_per_epoch * total_epochs
|
||||
warmup_steps = steps_per_epoch * warmup_epochs
|
||||
|
||||
if lr_decay_mode == 'steps':
|
||||
lr_each_step = _generate_steps_lr(lr_init, lr_max, total_steps, warmup_steps)
|
||||
elif lr_decay_mode == 'poly':
|
||||
lr_each_step = _generate_poly_lr(lr_init, lr_end, lr_max, total_steps, warmup_steps)
|
||||
elif lr_decay_mode == 'cosine':
|
||||
lr_each_step = _generate_cosine_lr(lr_init, lr_end, lr_max, total_steps, warmup_steps)
|
||||
elif lr_decay_mode == 'liner':
|
||||
lr_each_step = _generate_liner_lr(lr_init, lr_end, lr_max, total_steps, warmup_steps)
|
||||
elif lr_decay_mode == 'const':
|
||||
lr_each_step = _generate_const_lr(lr_init, lr_max, total_steps, warmup_steps)
|
||||
else:
|
||||
lr_each_step = _generate_steps_lr(lr_init, lr_max, total_steps, warmup_steps)
|
||||
|
||||
lr_each_step = np.array(lr_each_step).astype(np.float32)
|
||||
return lr_each_step
|
||||
|
||||
def linear_warmup_lr(current_step, warmup_steps, base_lr, init_lr):
|
||||
lr_inc = (float(base_lr) - float(init_lr)) / float(warmup_steps)
|
||||
lr = float(init_lr) + lr_inc * current_step
|
||||
return lr
|
||||
|
||||
def warmup_cosine_annealing_lr(lr, steps_per_epoch, warmup_epochs, max_epoch=120, global_step=0):
|
||||
"""
|
||||
generate learning rate array with cosine
|
||||
|
||||
Args:
|
||||
lr(float): base learning rate
|
||||
steps_per_epoch(int): steps size of one epoch
|
||||
warmup_epochs(int): number of warmup epochs
|
||||
max_epoch(int): total epochs of training
|
||||
global_step(int): the current start index of lr array
|
||||
Returns:
|
||||
np.array, learning rate array
|
||||
"""
|
||||
base_lr = lr
|
||||
warmup_init_lr = 0
|
||||
total_steps = int(max_epoch * steps_per_epoch)
|
||||
warmup_steps = int(warmup_epochs * steps_per_epoch)
|
||||
decay_steps = total_steps - warmup_steps
|
||||
lr_each_step = []
|
||||
for i in range(total_steps):
|
||||
if i < warmup_steps:
|
||||
lr = linear_warmup_lr(i + 1, warmup_steps, base_lr, warmup_init_lr)
|
||||
else:
|
||||
linear_decay = (total_steps - i) / decay_steps
|
||||
cosine_decay = 0.5 * (1 + math.cos(math.pi * 2 * 0.47 * i / decay_steps))
|
||||
decayed = linear_decay * cosine_decay + 0.00001
|
||||
lr = base_lr * decayed
|
||||
lr_each_step.append(lr)
|
||||
lr_each_step = np.array(lr_each_step).astype(np.float32)
|
||||
learning_rate = lr_each_step[global_step:]
|
||||
return learning_rate
|
||||
|
|
@ -0,0 +1,325 @@
|
|||
# Copyright 2021 Huawei Technologies Co., Ltd
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
# You may obtain a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
# ============================================================================
|
||||
"""ResNet."""
|
||||
import math
|
||||
import numpy as np
|
||||
from scipy.stats import truncnorm
|
||||
import mindspore.nn as nn
|
||||
import mindspore.common.dtype as mstype
|
||||
from mindspore.ops import operations as P
|
||||
from mindspore.ops import functional as F
|
||||
from mindspore.common.tensor import Tensor
|
||||
from mindspore.common.initializer import Uniform
|
||||
|
||||
def _conv_variance_scaling_initializer(in_channel, out_channel, kernel_size):
|
||||
fan_in = in_channel * kernel_size * kernel_size
|
||||
scale = 1.0
|
||||
scale /= max(1., fan_in)
|
||||
stddev = (scale ** 0.5) / .87962566103423978
|
||||
mu, sigma = 0, stddev
|
||||
weight = truncnorm(-2, 2, loc=mu, scale=sigma).rvs(out_channel * in_channel * kernel_size * kernel_size)
|
||||
weight = np.reshape(weight, (out_channel, in_channel, kernel_size, kernel_size))
|
||||
return Tensor(weight, dtype=mstype.float32)
|
||||
|
||||
def _weight_variable(shape, factor=0.01):
|
||||
init_value = np.random.randn(*shape).astype(np.float32) * factor
|
||||
return Tensor(init_value)
|
||||
|
||||
def _conv3x3(in_channel, out_channel, stride=1, use_se=False):
|
||||
if use_se:
|
||||
weight = _conv_variance_scaling_initializer(in_channel, out_channel, kernel_size=3)
|
||||
else:
|
||||
weight_shape = (out_channel, in_channel, 3, 3)
|
||||
weight = _weight_variable(weight_shape)
|
||||
return nn.Conv2d(in_channel, out_channel,
|
||||
kernel_size=3, stride=stride, padding=0, pad_mode='same', weight_init=weight)
|
||||
|
||||
def _conv1x1(in_channel, out_channel, stride=1, use_se=False):
|
||||
if use_se:
|
||||
weight = _conv_variance_scaling_initializer(in_channel, out_channel, kernel_size=1)
|
||||
else:
|
||||
weight_shape = (out_channel, in_channel, 1, 1)
|
||||
weight = _weight_variable(weight_shape)
|
||||
return nn.Conv2d(in_channel, out_channel,
|
||||
kernel_size=1, stride=stride, padding=0, pad_mode='same', weight_init=weight)
|
||||
|
||||
def _conv7x7(in_channel, out_channel, stride=1, use_se=False):
|
||||
if use_se:
|
||||
weight = _conv_variance_scaling_initializer(in_channel, out_channel, kernel_size=7)
|
||||
else:
|
||||
weight_shape = (out_channel, in_channel, 7, 7)
|
||||
weight = _weight_variable(weight_shape)
|
||||
return nn.Conv2d(in_channel, out_channel,
|
||||
kernel_size=7, stride=stride, padding=0, pad_mode='same', weight_init=weight)
|
||||
|
||||
def _bn(channel):
|
||||
return nn.BatchNorm2d(channel, eps=1e-4, momentum=0.9,
|
||||
gamma_init=1, beta_init=0, moving_mean_init=0, moving_var_init=1)
|
||||
|
||||
def _bn_last(channel):
|
||||
return nn.BatchNorm2d(channel, eps=1e-4, momentum=0.9,
|
||||
gamma_init=0, beta_init=0, moving_mean_init=0, moving_var_init=1)
|
||||
|
||||
def _fc(in_channel, out_channel, use_se=False):
|
||||
if use_se:
|
||||
weight = np.random.normal(loc=0, scale=0.01, size=out_channel * in_channel)
|
||||
weight = Tensor(np.reshape(weight, (out_channel, in_channel)), dtype=mstype.float32)
|
||||
else:
|
||||
weight_shape = (out_channel, in_channel)
|
||||
weight = _weight_variable(weight_shape)
|
||||
return nn.Dense(in_channel, out_channel, weight_init=weight)
|
||||
|
||||
class ResidualBlock(nn.Cell):
|
||||
"""
|
||||
ResNet V1 residual block definition.
|
||||
|
||||
Args:
|
||||
in_channel (int): Input channel.
|
||||
out_channel (int): Output channel.
|
||||
stride (int): Stride size for the first convolutional layer. Default: 1.
|
||||
use_se (bool): enable SE-ResNet50 net. Default: False.
|
||||
se_block(bool): use se block in SE-ResNet50 net. Default: False.
|
||||
|
||||
Returns:
|
||||
Tensor, output tensor.
|
||||
|
||||
Examples:
|
||||
>>> ResidualBlock(3, 256, stride=2)
|
||||
"""
|
||||
expansion = 4
|
||||
|
||||
def __init__(self,
|
||||
in_channel,
|
||||
out_channel,
|
||||
stride=1,
|
||||
use_se=False, se_block=False):
|
||||
super(ResidualBlock, self).__init__()
|
||||
self.stride = stride
|
||||
self.use_se = use_se
|
||||
self.se_block = se_block
|
||||
channel = out_channel // self.expansion
|
||||
self.conv1 = _conv1x1(in_channel, channel, stride=1, use_se=self.use_se)
|
||||
self.bn1 = _bn(channel)
|
||||
if self.use_se and self.stride != 1:
|
||||
self.e2 = nn.SequentialCell([_conv3x3(channel, channel, stride=1, use_se=True), _bn(channel),
|
||||
nn.ReLU(), nn.MaxPool2d(kernel_size=2, stride=2, pad_mode='same')])
|
||||
else:
|
||||
self.conv2 = _conv3x3(channel, channel, stride=stride, use_se=self.use_se)
|
||||
self.bn2 = _bn(channel)
|
||||
|
||||
self.conv3 = _conv1x1(channel, out_channel, stride=1, use_se=self.use_se)
|
||||
self.bn3 = _bn_last(out_channel)
|
||||
if self.se_block:
|
||||
self.se_global_pool = P.ReduceMean(keep_dims=False)
|
||||
self.se_dense_0 = _fc(out_channel, int(out_channel / 4), use_se=self.use_se)
|
||||
self.se_dense_1 = _fc(int(out_channel / 4), out_channel, use_se=self.use_se)
|
||||
self.se_sigmoid = nn.Sigmoid()
|
||||
self.se_mul = P.Mul()
|
||||
|
||||
self.relu = nn.ReLU()
|
||||
|
||||
self.down_sample = False
|
||||
|
||||
if stride != 1 or in_channel != out_channel:
|
||||
self.down_sample = True
|
||||
self.down_sample_layer = None
|
||||
|
||||
if self.down_sample:
|
||||
if self.use_se:
|
||||
if stride == 1:
|
||||
self.down_sample_layer = nn.SequentialCell([_conv1x1(in_channel, out_channel,
|
||||
stride, use_se=self.use_se), _bn(out_channel)])
|
||||
else:
|
||||
self.down_sample_layer = nn.SequentialCell([nn.MaxPool2d(kernel_size=2, stride=2, pad_mode='same'),
|
||||
_conv1x1(in_channel, out_channel, 1,
|
||||
use_se=self.use_se), _bn(out_channel)])
|
||||
else:
|
||||
self.down_sample_layer = nn.SequentialCell([_conv1x1(in_channel, out_channel, stride,
|
||||
use_se=self.use_se), _bn(out_channel)])
|
||||
self.add = P.TensorAdd()
|
||||
|
||||
def construct(self, x):
|
||||
"""construct"""
|
||||
identity = x
|
||||
out = self.conv1(x)
|
||||
out = self.bn1(out)
|
||||
out = self.relu(out)
|
||||
if self.use_se and self.stride != 1:
|
||||
out = self.e2(out)
|
||||
else:
|
||||
out = self.conv2(out)
|
||||
out = self.bn2(out)
|
||||
out = self.relu(out)
|
||||
out = self.conv3(out)
|
||||
out = self.bn3(out)
|
||||
if self.se_block:
|
||||
out_se = out
|
||||
out = self.se_global_pool(out, (2, 3))
|
||||
out = self.se_dense_0(out)
|
||||
out = self.relu(out)
|
||||
out = self.se_dense_1(out)
|
||||
out = self.se_sigmoid(out)
|
||||
out = F.reshape(out, F.shape(out) + (1, 1))
|
||||
out = self.se_mul(out, out_se)
|
||||
|
||||
if self.down_sample:
|
||||
identity = self.down_sample_layer(identity)
|
||||
|
||||
out = self.add(out, identity)
|
||||
out = self.relu(out)
|
||||
|
||||
return out
|
||||
|
||||
class ResNet_backbone(nn.Cell):
|
||||
"""ResNet_backbone"""
|
||||
def __init__(self,
|
||||
block,
|
||||
layer_nums,
|
||||
in_channels,
|
||||
out_channels,
|
||||
strides,
|
||||
use_se=False):
|
||||
super(ResNet_backbone, self).__init__()
|
||||
|
||||
if not len(layer_nums) == len(in_channels) == len(out_channels) == 4:
|
||||
raise ValueError("the length of layer_num, in_channels, out_channels list must be 4!")
|
||||
self.use_se = use_se
|
||||
self.se_block = False
|
||||
if self.use_se:
|
||||
self.se_block = True
|
||||
|
||||
if self.use_se:
|
||||
self.conv1_0 = _conv3x3(3, 32, stride=2, use_se=self.use_se)
|
||||
self.bn1_0 = _bn(32)
|
||||
self.conv1_1 = _conv3x3(32, 32, stride=1, use_se=self.use_se)
|
||||
self.bn1_1 = _bn(32)
|
||||
self.conv1_2 = _conv3x3(32, 64, stride=1, use_se=self.use_se)
|
||||
else:
|
||||
self.conv1 = _conv7x7(3, 64, stride=2)
|
||||
self.bn1 = _bn(64)
|
||||
self.relu = P.ReLU()
|
||||
self.maxpool = nn.MaxPool2d(kernel_size=3, stride=2, pad_mode="same")
|
||||
self.layer1 = self._make_layer(block,
|
||||
layer_nums[0],
|
||||
in_channel=in_channels[0],
|
||||
out_channel=out_channels[0],
|
||||
stride=strides[0],
|
||||
use_se=self.use_se)
|
||||
self.layer2 = self._make_layer(block,
|
||||
layer_nums[1],
|
||||
in_channel=in_channels[1],
|
||||
out_channel=out_channels[1],
|
||||
stride=strides[1],
|
||||
use_se=self.use_se)
|
||||
self.layer3 = self._make_layer(block,
|
||||
layer_nums[2],
|
||||
in_channel=in_channels[2],
|
||||
out_channel=out_channels[2],
|
||||
stride=strides[2],
|
||||
use_se=self.use_se,
|
||||
se_block=self.se_block)
|
||||
self.layer4 = self._make_layer(block,
|
||||
layer_nums[3],
|
||||
in_channel=in_channels[3],
|
||||
out_channel=out_channels[3],
|
||||
stride=strides[3],
|
||||
use_se=self.use_se,
|
||||
se_block=self.se_block)
|
||||
|
||||
self.mean = P.ReduceMean(keep_dims=True)
|
||||
self.flatten = nn.Flatten()
|
||||
# self.end_point = _fc(out_channels[3], num_classes, use_se=self.use_se)
|
||||
|
||||
def _make_layer(self, block, layer_num, in_channel, out_channel, stride, use_se=False, se_block=False):
|
||||
"""
|
||||
Make stage network of ResNet.
|
||||
|
||||
Args:
|
||||
block (Cell): Resnet block.
|
||||
layer_num (int): Layer number.
|
||||
in_channel (int): Input channel.
|
||||
out_channel (int): Output channel.
|
||||
stride (int): Stride size for the first convolutional layer.
|
||||
se_block(bool): use se block in SE-ResNet50 net. Default: False.
|
||||
Returns:
|
||||
SequentialCell, the output layer.
|
||||
|
||||
Examples:
|
||||
>>> _make_layer(ResidualBlock, 3, 128, 256, 2)
|
||||
"""
|
||||
layers = []
|
||||
|
||||
resnet_block = block(in_channel, out_channel, stride=stride, use_se=use_se)
|
||||
layers.append(resnet_block)
|
||||
if se_block:
|
||||
for _ in range(1, layer_num - 1):
|
||||
resnet_block = block(out_channel, out_channel, stride=1, use_se=use_se)
|
||||
layers.append(resnet_block)
|
||||
resnet_block = block(out_channel, out_channel, stride=1, use_se=use_se, se_block=se_block)
|
||||
layers.append(resnet_block)
|
||||
else:
|
||||
for _ in range(1, layer_num):
|
||||
resnet_block = block(out_channel, out_channel, stride=1, use_se=use_se)
|
||||
layers.append(resnet_block)
|
||||
return nn.SequentialCell(layers)
|
||||
|
||||
def construct(self, x):
|
||||
"""construct"""
|
||||
if self.use_se:
|
||||
x = self.conv1_0(x)
|
||||
x = self.bn1_0(x)
|
||||
x = self.relu(x)
|
||||
x = self.conv1_1(x)
|
||||
x = self.bn1_1(x)
|
||||
x = self.relu(x)
|
||||
x = self.conv1_2(x)
|
||||
else:
|
||||
x = self.conv1(x)
|
||||
|
||||
x = self.bn1(x)
|
||||
x = self.relu(x)
|
||||
c1 = self.maxpool(x)
|
||||
c2 = self.layer1(c1)
|
||||
c3 = self.layer2(c2)
|
||||
c4 = self.layer3(c3)
|
||||
c5 = self.layer4(c4)
|
||||
out = self.mean(c5, (2, 3))
|
||||
out = self.flatten(out)
|
||||
return out
|
||||
|
||||
class resnet_Combine(nn.Cell):
|
||||
def __init__(self, backbone, head):
|
||||
super(resnet_Combine, self).__init__(auto_prefix=False)
|
||||
self.backbone = backbone
|
||||
self.head = head
|
||||
|
||||
def construct(self, x):
|
||||
x = self.backbone(x)
|
||||
x = self.head(x)
|
||||
return x
|
||||
|
||||
def embedding(in_channel, out_channel):
|
||||
stdv = 1.0 / math.sqrt(in_channel * 1.0)
|
||||
return nn.Dense(in_channel, out_channel, weight_init=Uniform(stdv))
|
||||
|
||||
def resnet50(class_num=5184):
|
||||
"""ResNet50"""
|
||||
backbone_net = ResNet_backbone(ResidualBlock, [3, 4, 6, 3], [64, 256, 512, 1024], [256, 512, 1024, 2048],
|
||||
[1, 2, 2, 2])
|
||||
backout_channels = 2048
|
||||
head_net = embedding(backout_channels, class_num)
|
||||
head_net.set_train(True)
|
||||
net = resnet_Combine(backbone_net, head_net)
|
||||
return net
|
||||
|
|
@ -0,0 +1,242 @@
|
|||
# Copyright 2021 Huawei Technologies Co., Ltd
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
# You may obtain a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
# ============================================================================
|
||||
"""Utility."""
|
||||
import random
|
||||
import os
|
||||
import time
|
||||
import multiprocessing as mp
|
||||
import numpy as np
|
||||
import cv2
|
||||
from src.config import config2
|
||||
|
||||
def resize_short(img, target_size):
|
||||
""" resize_short """
|
||||
percent = float(target_size) / min(img.shape[0], img.shape[1])
|
||||
resized_width = int(round(img.shape[1] * percent))
|
||||
resized_height = int(round(img.shape[0] * percent))
|
||||
resized = cv2.resize(img, (resized_width, resized_height), interpolation=cv2.INTER_LANCZOS4)
|
||||
return resized
|
||||
|
||||
def functtt(param):
|
||||
""" fun """
|
||||
sharedlist, s, e = param
|
||||
fea, a, b = sharedlist
|
||||
ab = np.dot(fea[s:e], fea.T)
|
||||
d = a[s:e] + b - 2 * ab
|
||||
for i in range(e - s):
|
||||
d[i][s + i] += 1e8
|
||||
sorted_index = np.argsort(d, 1)[:, :10]
|
||||
return sorted_index
|
||||
|
||||
def recall_topk_parallel(fea, lab, k):
|
||||
""" recall_topk_parallel """
|
||||
fea = np.array(fea)
|
||||
fea = fea.reshape(fea.shape[0], -1)
|
||||
n = np.sqrt(np.sum(fea**2, 1)).reshape(-1, 1)
|
||||
fea = fea / n
|
||||
a = np.sum(fea**2, 1).reshape(-1, 1)
|
||||
b = a.T
|
||||
sharedlist = mp.Manager().list()
|
||||
sharedlist.append(fea)
|
||||
sharedlist.append(a)
|
||||
sharedlist.append(b)
|
||||
N = 100
|
||||
L = fea.shape[0] / N
|
||||
params = []
|
||||
for i in range(N):
|
||||
if i == N - 1:
|
||||
s, e = int(i * L), int(fea.shape[0])
|
||||
else:
|
||||
s, e = int(i * L), int((i + 1) * L)
|
||||
params.append([sharedlist, s, e])
|
||||
pool = mp.Pool(processes=4)
|
||||
sorted_index_list = pool.map(functtt, params)
|
||||
pool.close()
|
||||
pool.join()
|
||||
sorted_index = np.vstack(sorted_index_list)
|
||||
res = 0
|
||||
for i in range(len(fea)):
|
||||
for j in range(k):
|
||||
pred = lab[sorted_index[i][j]]
|
||||
if lab[i] == pred:
|
||||
res += 1.0
|
||||
break
|
||||
res = res / len(fea)
|
||||
return res
|
||||
|
||||
class GetDatasetGenerator_eval():
|
||||
""" GetDatasetGenerator_eval"""
|
||||
def __init__(self, data_dir, train_list):
|
||||
self.DATA_DIR = data_dir
|
||||
self.TRAIN_LIST = train_list
|
||||
train_image_list = []
|
||||
TRAIN_LISTS = open(self.TRAIN_LIST, "r").readlines()
|
||||
for _, item in enumerate(TRAIN_LISTS):
|
||||
items = item.strip().split()
|
||||
if items[0] == 'image_id':
|
||||
continue
|
||||
path = items[0]
|
||||
label = int(items[1]) - 1
|
||||
train_image_list.append((path, label))
|
||||
r = random.random
|
||||
random.seed(int(time.time()))
|
||||
random.shuffle(train_image_list, random=r)
|
||||
self.__data = [i[0] for i in train_image_list]
|
||||
self.__label = [i[1] for i in train_image_list]
|
||||
def __getitem__(self, index):
|
||||
self.__img = cv2.imread(os.path.join(self.DATA_DIR, self.__data[index]))
|
||||
self.__img = resize_short(self.__img, 224)
|
||||
item = (self.__img, self.__label[index])
|
||||
return item
|
||||
def __len__(self):
|
||||
return len(self.__data)
|
||||
|
||||
class GetDatasetGenerator_softmax():
|
||||
""" GetDatasetGenerator_softmax """
|
||||
def __init__(self, data_dir, train_list):
|
||||
self.DATA_DIR = data_dir
|
||||
self.TRAIN_LIST = train_list
|
||||
train_image_list = []
|
||||
TRAIN_LISTS = open(self.TRAIN_LIST, "r").readlines()
|
||||
for _, item in enumerate(TRAIN_LISTS):
|
||||
items = item.strip().split()
|
||||
if items[0] == 'image_id':
|
||||
continue
|
||||
path = items[0]
|
||||
label = int(items[1]) - 1
|
||||
train_image_list.append((path, label))
|
||||
r = random.random
|
||||
random.seed(int(time.time()))
|
||||
random.shuffle(train_image_list, random=r)
|
||||
self.__data = [i[0] for i in train_image_list]
|
||||
self.__label = [i[1] for i in train_image_list]
|
||||
def __getitem__(self, index):
|
||||
self.__img = cv2.imread(os.path.join(self.DATA_DIR, self.__data[index]))
|
||||
item = (self.__img, self.__label[index])
|
||||
return item
|
||||
def __len__(self):
|
||||
return len(self.__data)
|
||||
|
||||
class GetDatasetGenerator_triplet():
|
||||
""" GetDatasetGenerator_triplet """
|
||||
def __init__(self, data_dir, train_list):
|
||||
self.DATA_DIR = data_dir
|
||||
self.TRAIN_LIST = train_list
|
||||
train_data = {}
|
||||
train_image_list_tiplet = []
|
||||
TRAIN_LISTS = open(self.TRAIN_LIST, "r").readlines()
|
||||
count = 0
|
||||
for _, item in enumerate(TRAIN_LISTS):
|
||||
items = item.strip().split()
|
||||
if items[0] == 'image_id':
|
||||
continue
|
||||
path = items[0]
|
||||
label = int(items[1]) - 1
|
||||
if label not in train_data:
|
||||
train_data[label] = []
|
||||
train_data[label].append(path)
|
||||
#shuffle
|
||||
r = random.random
|
||||
random.seed(int(time.time()))
|
||||
#data generates
|
||||
labs = list(train_data.keys())
|
||||
lab_num = len(labs)
|
||||
ind = list(range(0, lab_num))
|
||||
total_count = len(TRAIN_LISTS)
|
||||
while True:
|
||||
random.shuffle(ind, random=r)
|
||||
ind_pos, ind_neg = ind[:2]
|
||||
lab_pos = labs[ind_pos]
|
||||
pos_data_list = train_data[lab_pos]
|
||||
data_ind = list(range(0, len(pos_data_list)))
|
||||
random.shuffle(data_ind, random=r)
|
||||
anchor_ind, pos_ind = data_ind[:2]
|
||||
lab_neg = labs[ind_neg]
|
||||
neg_data_list = train_data[lab_neg]
|
||||
neg_ind = random.randint(0, len(neg_data_list) - 1)
|
||||
anchor_path = self.DATA_DIR + pos_data_list[anchor_ind]
|
||||
train_image_list_tiplet.append((anchor_path, lab_pos))
|
||||
pos_path = self.DATA_DIR + pos_data_list[pos_ind]
|
||||
train_image_list_tiplet.append((pos_path, lab_pos))
|
||||
neg_path = self.DATA_DIR + neg_data_list[neg_ind]
|
||||
train_image_list_tiplet.append((neg_path, lab_neg))
|
||||
count += 3
|
||||
if count >= total_count:
|
||||
break
|
||||
self.__data = [i[0] for i in train_image_list_tiplet]
|
||||
self.__label = [i[1] for i in train_image_list_tiplet]
|
||||
|
||||
def __getitem__(self, index):
|
||||
img = cv2.imread(self.__data[index])
|
||||
item = (img, self.__label[index])
|
||||
return item
|
||||
def __len__(self):
|
||||
return len(self.__data)
|
||||
|
||||
class GetDatasetGenerator_quadruplet():
|
||||
"""GetDatasetGenerator_quadruplet."""
|
||||
def __init__(self, data_dir, train_list):
|
||||
self.DATA_DIR = data_dir
|
||||
self.TRAIN_LIST = train_list
|
||||
self.batch_size = config2.batch_size
|
||||
samples_each_class = 2
|
||||
assert self.batch_size % samples_each_class == 0
|
||||
class_num = self.batch_size // samples_each_class
|
||||
train_data = {}
|
||||
train_image_list_quadruplet = []
|
||||
TRAIN_LISTS = open(self.TRAIN_LIST, "r").readlines()
|
||||
count = 0
|
||||
for _, item in enumerate(TRAIN_LISTS):
|
||||
items = item.strip().split()
|
||||
if items[0] == 'image_id':
|
||||
continue
|
||||
path = items[0]
|
||||
label = int(items[1]) - 1
|
||||
if label not in train_data:
|
||||
train_data[label] = []
|
||||
train_data[label].append(path)
|
||||
#shuffle
|
||||
r = random.random
|
||||
random.seed(int(time.time()))
|
||||
#data generates
|
||||
labs = list(train_data.keys())
|
||||
lab_num = len(labs)
|
||||
ind = list(range(0, lab_num))
|
||||
total_count = len(TRAIN_LISTS)
|
||||
while True:
|
||||
random.shuffle(ind, random=r)
|
||||
ind_sample = ind[:class_num]
|
||||
for ind_i in ind_sample:
|
||||
lab = labs[ind_i]
|
||||
data_list = train_data[lab]
|
||||
data_ind = list(range(0, len(data_list)))
|
||||
random.shuffle(data_ind, random=r)
|
||||
anchor_ind = data_ind[:samples_each_class]
|
||||
for anchor_ind_i in anchor_ind:
|
||||
anchor_path = self.DATA_DIR + data_list[anchor_ind_i]
|
||||
train_image_list_quadruplet.append((anchor_path, lab))
|
||||
count += 1
|
||||
if count >= total_count:
|
||||
break
|
||||
|
||||
self.__data = [i[0] for i in train_image_list_quadruplet]
|
||||
self.__label = [i[1] for i in train_image_list_quadruplet]
|
||||
|
||||
def __getitem__(self, index):
|
||||
img = cv2.imread(self.__data[index])
|
||||
item = (img, self.__label[index])
|
||||
return item
|
||||
def __len__(self):
|
||||
return len(self.__data)
|
||||
|
|
@ -0,0 +1,223 @@
|
|||
# Copyright 2021 Huawei Technologies Co., Ltd
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
# You may obtain a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
# ============================================================================
|
||||
"""train resnet."""
|
||||
import os
|
||||
import time
|
||||
import argparse
|
||||
import ast
|
||||
import numpy as np
|
||||
from mindspore import context
|
||||
from mindspore import Tensor
|
||||
from mindspore.nn.optim.momentum import Momentum
|
||||
from mindspore.train.model import Model
|
||||
from mindspore.context import ParallelMode
|
||||
from mindspore.train.callback import ModelCheckpoint, CheckpointConfig
|
||||
from mindspore.train.loss_scale_manager import FixedLossScaleManager
|
||||
from mindspore.train.serialization import load_checkpoint, load_param_into_net
|
||||
from mindspore.common import set_seed
|
||||
from mindspore.communication.management import init
|
||||
from mindspore.train.callback import Callback
|
||||
|
||||
from src.loss import Softmaxloss
|
||||
from src.loss import Tripletloss
|
||||
from src.loss import Quadrupletloss
|
||||
from src.lr_generator import get_lr
|
||||
from src.resnet import resnet50
|
||||
from src.utility import GetDatasetGenerator_softmax, GetDatasetGenerator_triplet, GetDatasetGenerator_quadruplet
|
||||
|
||||
set_seed(1)
|
||||
|
||||
parser = argparse.ArgumentParser(description='Image classification')
|
||||
# modelarts parameter
|
||||
parser.add_argument('--train_url', type=str, default=None, help='Train output path')
|
||||
parser.add_argument('--data_url', type=str, default=None, help='Dataset path')
|
||||
parser.add_argument('--ckpt_url', type=str, default=None, help='Pretrained ckpt path')
|
||||
parser.add_argument('--checkpoint_name', type=str, default='resnet-120_625.ckpt', help='Checkpoint file')
|
||||
parser.add_argument('--loss_name', type=str, default='softmax',
|
||||
help='loss name: softmax(pretrained) triplet quadruplet')
|
||||
# Ascend parameter
|
||||
parser.add_argument('--dataset_path', type=str, default=None, help='Dataset path')
|
||||
parser.add_argument('--ckpt_path', type=str, default=None, help='ckpt path name')
|
||||
parser.add_argument('--run_distribute', type=ast.literal_eval, default=False, help='Run distribute')
|
||||
parser.add_argument('--device_id', type=int, default=0, help='Device id')
|
||||
parser.add_argument('--run_modelarts', type=ast.literal_eval, default=False, help='Run distribute')
|
||||
args_opt = parser.parse_args()
|
||||
|
||||
class Monitor(Callback):
|
||||
"""Monitor"""
|
||||
def __init__(self, lr_init=None):
|
||||
super(Monitor, self).__init__()
|
||||
self.lr_init = lr_init
|
||||
self.lr_init_len = len(lr_init)
|
||||
def epoch_begin(self, run_context):
|
||||
self.losses = []
|
||||
self.epoch_time = time.time()
|
||||
dataset_generator.__init__(data_dir=DATA_DIR, train_list=TRAIN_LIST)
|
||||
def epoch_end(self, run_context):
|
||||
cb_params = run_context.original_args()
|
||||
epoch_mseconds = (time.time() - self.epoch_time) * 1000
|
||||
per_step_mseconds = epoch_mseconds / cb_params.batch_num
|
||||
print("epoch time: {:5.3f}, per step time: {:5.3f}, avg loss: {:8.5f}"
|
||||
.format(epoch_mseconds, per_step_mseconds, np.mean(self.losses)))
|
||||
print('batch_size:', config.batch_size, 'epochs_size:', config.epoch_size,
|
||||
'lr_model:', config.lr_decay_mode, 'lr:', config.lr_max, 'step_size:', step_size)
|
||||
def step_begin(self, run_context):
|
||||
self.step_time = time.time()
|
||||
def step_end(self, run_context):
|
||||
"""step_end"""
|
||||
cb_params = run_context.original_args()
|
||||
step_mseconds = (time.time() - self.step_time) * 1000
|
||||
step_loss = cb_params.net_outputs
|
||||
if isinstance(step_loss, (tuple, list)) and isinstance(step_loss[0], Tensor):
|
||||
step_loss = step_loss[0]
|
||||
if isinstance(step_loss, Tensor):
|
||||
step_loss = np.mean(step_loss.asnumpy())
|
||||
self.losses.append(step_loss)
|
||||
cur_step_in_epoch = (cb_params.cur_step_num - 1) % cb_params.batch_num
|
||||
print("epochs: [{:3d}/{:3d}], step:[{:5d}/{:5d}], loss:[{:8.5f}/{:8.5f}], time:[{:5.3f}], lr:[{:8.5f}]".format(
|
||||
cb_params.cur_epoch_num, config.epoch_size, cur_step_in_epoch, cb_params.batch_num, step_loss,
|
||||
np.mean(self.losses), step_mseconds, self.lr_init[cb_params.cur_step_num - 1]))
|
||||
|
||||
if __name__ == '__main__':
|
||||
if args_opt.loss_name == 'softmax':
|
||||
from src.config import config0 as config
|
||||
from src.dataset import create_dataset0 as create_dataset
|
||||
elif args_opt.loss_name == 'triplet':
|
||||
from src.config import config1 as config
|
||||
from src.dataset import create_dataset1 as create_dataset
|
||||
elif args_opt.loss_name == 'quadruplet':
|
||||
from src.config import config2 as config
|
||||
from src.dataset import create_dataset1 as create_dataset
|
||||
else:
|
||||
print('loss no')
|
||||
context.set_context(mode=context.GRAPH_MODE, device_target="Ascend", save_graphs=False)
|
||||
# init distributed
|
||||
if args_opt.run_modelarts:
|
||||
import moxing as mox
|
||||
device_id = int(os.getenv('DEVICE_ID'))
|
||||
device_num = int(os.getenv('RANK_SIZE'))
|
||||
context.set_context(device_id=device_id)
|
||||
local_data_url = '/cache/data'
|
||||
local_ckpt_url = '/cache/ckpt'
|
||||
local_train_url = '/cache/train'
|
||||
if device_num > 1:
|
||||
init()
|
||||
context.set_auto_parallel_context(device_num=device_num,
|
||||
parallel_mode=ParallelMode.DATA_PARALLEL,
|
||||
gradients_mean=True)
|
||||
local_data_url = os.path.join(local_data_url, str(device_id))
|
||||
local_ckpt_url = os.path.join(local_ckpt_url, str(device_id))
|
||||
mox.file.copy_parallel(args_opt.data_url, local_data_url)
|
||||
mox.file.copy_parallel(args_opt.ckpt_url, local_ckpt_url)
|
||||
DATA_DIR = local_data_url + '/'
|
||||
else:
|
||||
if args_opt.run_distribute:
|
||||
device_id = int(os.getenv('DEVICE_ID'))
|
||||
device_num = int(os.getenv('RANK_SIZE'))
|
||||
context.set_context(device_id=device_id)
|
||||
init()
|
||||
context.reset_auto_parallel_context()
|
||||
context.set_auto_parallel_context(device_num=device_num,
|
||||
parallel_mode=ParallelMode.DATA_PARALLEL,
|
||||
gradients_mean=True)
|
||||
else:
|
||||
context.set_context(device_id=args_opt.device_id)
|
||||
device_num = 1
|
||||
device_id = args_opt.device_id
|
||||
DATA_DIR = args_opt.dataset_path + '/'
|
||||
|
||||
# create dataset
|
||||
TRAIN_LIST = DATA_DIR + 'train_half.txt'
|
||||
if args_opt.loss_name == 'softmax':
|
||||
dataset_generator = GetDatasetGenerator_softmax(data_dir=DATA_DIR,
|
||||
train_list=TRAIN_LIST)
|
||||
elif args_opt.loss_name == 'triplet':
|
||||
dataset_generator = GetDatasetGenerator_triplet(data_dir=DATA_DIR,
|
||||
train_list=TRAIN_LIST)
|
||||
elif args_opt.loss_name == 'quadruplet':
|
||||
dataset_generator = GetDatasetGenerator_quadruplet(data_dir=DATA_DIR,
|
||||
train_list=TRAIN_LIST)
|
||||
else:
|
||||
print('loss no')
|
||||
dataset = create_dataset(dataset_generator, do_train=True, batch_size=config.batch_size,
|
||||
device_num=device_num, rank_id=device_id)
|
||||
step_size = dataset.get_dataset_size()
|
||||
|
||||
# define net
|
||||
net = resnet50(class_num=config.class_num)
|
||||
|
||||
# init weight
|
||||
if args_opt.run_modelarts:
|
||||
checkpoint_path = os.path.join(local_ckpt_url, args_opt.checkpoint_name)
|
||||
else:
|
||||
checkpoint_path = args_opt.ckpt_path
|
||||
param_dict = load_checkpoint(checkpoint_path)
|
||||
load_param_into_net(net.backbone, param_dict)
|
||||
|
||||
# init lr
|
||||
lr = Tensor(get_lr(lr_init=config.lr_init,
|
||||
lr_end=config.lr_end,
|
||||
lr_max=config.lr_max,
|
||||
warmup_epochs=config.warmup_epochs,
|
||||
total_epochs=config.epoch_size,
|
||||
steps_per_epoch=step_size,
|
||||
lr_decay_mode=config.lr_decay_mode))
|
||||
|
||||
# define opt
|
||||
opt = Momentum(params=net.trainable_params(),
|
||||
learning_rate=lr,
|
||||
momentum=config.momentum,
|
||||
weight_decay=config.weight_decay,
|
||||
loss_scale=config.loss_scale)
|
||||
|
||||
# define loss, model
|
||||
if args_opt.loss_name == 'softmax':
|
||||
loss = Softmaxloss(sparse=True, smooth_factor=0.1, num_classes=config.class_num)
|
||||
elif args_opt.loss_name == 'triplet':
|
||||
loss = Tripletloss(margin=0.1)
|
||||
elif args_opt.loss_name == 'quadruplet':
|
||||
loss = Quadrupletloss(train_batch_size=config.batch_size, samples_each_class=2, margin=0.1)
|
||||
else:
|
||||
print('loss no')
|
||||
|
||||
loss_scale = FixedLossScaleManager(config.loss_scale, drop_overflow_update=False)
|
||||
|
||||
if args_opt.loss_name == 'softmax':
|
||||
model = Model(net, loss_fn=loss, optimizer=opt, loss_scale_manager=loss_scale, metrics=None,
|
||||
amp_level='O3', keep_batchnorm_fp32=False)
|
||||
else:
|
||||
model = Model(net.backbone, loss_fn=loss, optimizer=opt, loss_scale_manager=loss_scale, metrics=None,
|
||||
amp_level='O3', keep_batchnorm_fp32=False)
|
||||
|
||||
#define callback
|
||||
cb = []
|
||||
if config.save_checkpoint and (device_num == 1 or device_id == 0):
|
||||
config_ck = CheckpointConfig(save_checkpoint_steps=config.save_checkpoint_epochs * step_size,
|
||||
keep_checkpoint_max=config.keep_checkpoint_max)
|
||||
|
||||
check_name = 'ResNet50_' + args_opt.loss_name
|
||||
if args_opt.run_modelarts:
|
||||
ckpt_cb = ModelCheckpoint(prefix=check_name, directory=local_train_url, config=config_ck)
|
||||
else:
|
||||
save_ckpt_path = os.path.join(config.save_checkpoint_path, 'model_'+ str(device_id) +'/')
|
||||
ckpt_cb = ModelCheckpoint(prefix=check_name, directory=save_ckpt_path, config=config_ck)
|
||||
cb += [ckpt_cb]
|
||||
cb += [Monitor(lr_init=lr.asnumpy())]
|
||||
|
||||
# train model
|
||||
model.train(config.epoch_size - config.pretrain_epoch_size, dataset, callbacks=cb, dataset_sink_mode=True)
|
||||
|
||||
if args_opt.run_modelarts and config.save_checkpoint and (device_num == 1 or device_id == 0):
|
||||
mox.file.copy_parallel(src_url=local_train_url, dst_url=args_opt.train_url)
|
||||
Loading…
Reference in New Issue