!14143 metric_learn_pr_1.1

From: @y760019473
Reviewed-by: 
Signed-off-by:
This commit is contained in:
mindspore-ci-bot 2021-03-30 20:21:20 +08:00 committed by Gitee
commit 79a6aef7b0
13 changed files with 1947 additions and 0 deletions

View File

@ -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来进行预训练然后使用其它的代价函数来进行微调例如tripletquadruplet。下面就是先在SOP数据集上预训练个pretrain模型然后用tripletquadruplet来微调模型。使用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算子为例如果输入数据类型为FP32MindSpore后台会自动降低精度来处理数据。用户可打开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中配置训练参数。
- 配置ResNet50Softmax在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 910CPU2.60GHz192核内存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 910CPU2.60GHz192核内存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)。

View File

@ -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)

View File

@ -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)

View File

@ -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

View File

@ -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 ..

View File

@ -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 ..

View File

@ -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
})

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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)

View File

@ -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)