MXNet/Gluon 中 Triplet Loss 算法
Triplet Loss,即三元组损失,用于训练差异性较小的数据集,数据集中标签较多,标签的样本较少。输入数据包括锚(Anchor)示例⚓️、正(Positive)示例和负(Negative)示例,通过优化模型,使得锚示例与正示例的距离小于锚示例与负示例的距离,实现样本的相似性计算。其中锚示例是样本集中随机选取的一个样本,正示例与锚示例属于同一类的样本,而负示例与锚示例属于不同类的样本。
在训练Triplet Loss模型时,只需要输入样本,不需要输入标签,这样避免标签过多、同标签样本过少的问题,模型只关心样本编码,不关心样本类别。Triplet Loss在相似性计算和检索中的效果较好,可以学习到样本与变换样本之间的关联,检索出与当前样本最相似的其他样本。
Triplet Loss通常应用于个体级别的细粒度识别,比如分类猫与狗等是大类别的识别,但是有些需求要精确至个体级别,比如识别不同种类不同配色的猫等,所以Triplet Loss最主要的应用也是在细粒度检索领域中。
Triplet Loss的对比:
- 如果把不同个体作为类别进行分类训练,Softmax维度可能远大于Feature维度,精度无法保证。
- Triplet Loss一般比分类能学习到更好的特征,在度量样本距离时,效果较好;
- Triplet Loss支持调整阈值Margin,控制正负样本的距离,当特征归一化之后,通过调节阈值提升置信度。
Triplet Loss的公式:
其他请参考Triplet Loss算法的论文。
本文使用MXNet/Gluon
深度学习框架,数据集选用MNIST,实现Triplet Loss算法。
本文的源码:https://github.com/SpikeKing/triplet-loss-gluon
数据集
安装MXNet库:
pip install mxnet
推荐豆瓣源下载,速度较快,-i https://pypi.douban.com/simple
MNIST就是著名的手写数字识别库,其中包含0至9等10个数字的手写体,图片大小为28*28的灰度图,目标是根据图片识别正确的数字。
使用MNIST类加载数据集,获取训练集mnist_train
和测试集mnist_test
的数据和标签。
mnist_train = MNIST(train=True) # 加载训练
tr_data = mnist_train._data.reshape((-1, 28 * 28)) # 数据
tr_label = mnist_train._label # 标签mnist_test = MNIST(train=False) # 加载测试
te_data = mnist_test._data.reshape((-1, 28 * 28)) # 数据
te_label = mnist_test._label # 标签
Triplet Loss训练的一个关键步骤就是准备训练数据。本例继承Dataset类创建Triplet的数据集类TripletDataset:
- 在构造器中:
- 传入原始数据rd、原始标签rl;
_data
和_label
是标准的数据和标签变量;_transform
是标准的转换变量;- 调用
_get_data()
,完成_data
和_label
的赋值;
__getitem__
是数据处理接口,根据索引idx返回数据,支持调用_transform
执行数据转换;__len__
是数据的总数;_get_data()
是数据赋值的核心方法:- 分离索引,获取标签相同数据的索引值Index列表
digit_indices
; - 创建三元组,即锚示例、正示例和负示例的索引组合矩阵;
- 数据是三元组,标签是ones矩阵,因为标签在Triplet Loss中没有实际意义;
- 分离索引,获取标签相同数据的索引值Index列表
具体实现:
class TripletDataset(dataset.Dataset):def __init__(self, rd, rl, transform=None):self.__rd = rd # 原始数据self.__rl = rl # 原始标签self._data = Noneself._label = Noneself._transform = transformself._get_data()def __getitem__(self, idx):if self._transform is not None:return self._transform(self._data[idx], self._label[idx])return self._data[idx], self._label[idx]def __len__(self):return len(self._label)def _get_data(self):label_list = np.unique(self.__rl)digit_indices = [np.where(self.__rl == i)[0] for i in label_list]tl_pairs = create_pairs(self.__rd, digit_indices, len(label_list))self._data = tl_pairsself._label = mx.nd.ones(tl_pairs.shape[0])
create_pairs()
是创建三元组的核心逻辑:
- 确定不同标签的选择样本数,选择最少的标签样本数;
- 将标签d的索引值随机洗牌(Shuffle),选择样本i和i+1作为锚和正示例;
- 随机选择(Randrange)其他标签dn中的样本i作为负示例;
- 循环全部标签和全部样本,生成含有锚、正、负示例的随机组合。
这样所创建的组合矩阵,保证样本的分布均匀,既避免组合过大(对比于全排列),又引入足够的随机性(双重随机)。注意:由于滑动窗口为2,即i和i+1,则19个样本生成18个样本组。
具体实现,如下:
@staticmethod
def create_pairs(x, digit_indices, num_classes):x = x.asnumpy() # 转换数据格式pairs = []n = min([len(digit_indices[d]) for d in range(num_classes)]) - 1 # 最小类别数for d in range(num_classes):for i in range(n):np.random.shuffle(digit_indices[d])z1, z2 = digit_indices[d][i], digit_indices[d][i + 1]inc = random.randrange(1, num_classes)dn = (d + inc) % num_classesz3 = digit_indices[dn][i]pairs += [[x[z1], x[z2], x[z3]]]return np.asarray(pairs))
使用DataLoader将TripletDataset封装为迭代器train_data
和test_data
,支持按批次batch输出样本。train_data
用于训练网络,test_data
用于验证网络。
def transform(data_, label_):return data_.astype(np.float32) / 255., label_.astype(np.float32)train_data = DataLoader(TripletDataset(rd=tr_data, rl=tr_label, transform=transform),batch_size, shuffle=True)test_data = DataLoader(TripletDataset(rd=te_data, rl=te_label, transform=transform),batch_size, shuffle=True)
网络和训练
Triplet Loss的基础网络,选用非常简单的多层感知机,主要为了验证Triplet Loss
的效果。
base_net = Sequential()
with base_net.name_scope():base_net.add(Dense(256, activation='relu'))base_net.add(Dense(128, activation='relu'))base_net.collect_params().initialize(mx.init.Uniform(scale=0.1), ctx=ctx)
初始化参数,使用uniform均匀分布,范围是[-0.1, 0.1]
,效果类似如下:
Gluon中自带TripletLoss损失函数,非常赞,产学结合的非常好!初始化损失函数triplet_loss
和训练器trainer_triplet
。
triplet_loss = gluon.loss.TripletLoss() # TripletLoss损失函数
trainer_triplet = gluon.Trainer(base_net.collect_params(), 'sgd', {'learning_rate': 0.05})
Triplet Loss的训练过程:
- 循环执行epoch,共10轮;
train_data
迭代输出每个批次的训练数据data;- 指定训练的执行环境
as_in_context()
,MXNet的数据环境就是训练环境; - 数据来源于TripletDataset,可以直接分为三个示例;
- 三个示例共享模型
base_net
,计算triplet_loss
的损失函数; - 调用loss.backward(),反向传播求导;
- 设置训练器
trainer_triplet
的step是batch_size
; - 计算损失函数的均值
curr_loss
; - 使用测试数据
test_data
评估网络base_net
;
具体实现:
for epoch in range(10):curr_loss = 0.0for i, (data, _) in enumerate(train_data):data = data.as_in_context(ctx)anc_ins, pos_ins, neg_ins = data[:, 0], data[:, 1], data[:, 2]with autograd.record():inter1 = base_net(anc_ins)inter2 = base_net(pos_ins)inter3 = base_net(neg_ins)loss = triplet_loss(inter1, inter2, inter3) # Triplet Lossloss.backward()trainer_triplet.step(batch_size)curr_loss = mx.nd.mean(loss).asscalar()# print('Epoch: %s, Batch: %s, Triplet Loss: %s' % (epoch, i, curr_loss))print('Epoch: %s, Triplet Loss: %s' % (epoch, curr_loss))evaluate_net(base_net, test_data, ctx=ctx) # 评估网络
评估网络也是一个重要的过程,验证网络的泛化能力:
- 设置
triplet_loss
损失函数,margin设置为0; test_data
迭代输出每个批次的验证数据data;- 指定验证数据的环境,需要与训练一致,因为是在训练的过程中验证;
- 通过模型,预测三元数据,计算损失函数;
- 由于TripletLoss的margin是0,因此只有0才是预测正确,其余全部预测错误;
- 统计整体的样本总数和正确样本数,计算全部测试数据的正确率;
具体实现:
def evaluate_net(model, test_data, ctx):triplet_loss = gluon.loss.TripletLoss(margin=0)sum_correct = 0sum_all = 0rate = 0.0for i, (data, _) in enumerate(test_data):data = data.as_in_context(ctx)anc_ins, pos_ins, neg_ins = data[:, 0], data[:, 1], data[:, 2]inter1 = model(anc_ins) # 训练的时候组合inter2 = model(pos_ins)inter3 = model(neg_ins)loss = triplet_loss(inter1, inter2, inter3) loss = loss.asnumpy()n_all = loss.shape[0]n_correct = np.sum(np.where(loss == 0, 1, 0))sum_correct += n_correctsum_all += n_allrate = safe_div(sum_correct, sum_all)print('准确率: %.4f (%s / %s)' % (rate, sum_correct, sum_all))return rate
在实验输出的效果中,Loss值逐渐减少,验证准确率逐步上升,模型收敛效果较好。具体如下:
Epoch: 0, Triplet Loss: 0.26367417
准确率: 0.9052 (8065 / 8910)
Epoch: 1, Triplet Loss: 0.18126598
准确率: 0.9297 (8284 / 8910)
Epoch: 2, Triplet Loss: 0.15365836
准确率: 0.9391 (8367 / 8910)
Epoch: 3, Triplet Loss: 0.13773362
准确率: 0.9448 (8418 / 8910)
Epoch: 4, Triplet Loss: 0.12188278
准确率: 0.9495 (8460 / 8910)
Epoch: 5, Triplet Loss: 0.115614936
准确率: 0.9520 (8482 / 8910)
Epoch: 6, Triplet Loss: 0.10390957
准确率: 0.9544 (8504 / 8910)
Epoch: 7, Triplet Loss: 0.087059245
准确率: 0.9569 (8526 / 8910)
Epoch: 8, Triplet Loss: 0.10168926
准确率: 0.9588 (8543 / 8910)
Epoch: 9, Triplet Loss: 0.06260935
准确率: 0.9606 (8559 / 8910)
可视化
Triplet Loss的核心功能就是将数据编码为具有可区分性的特征。使用PCA降维,将样本特征转换为可视化的二维分布,通过观察可知,样本特征具有一定的区分性。效果如下:
而原始的数据分布,效果较差:
在训练结束时,执行可视化数据:
- 原始的数据和标签
- Triplet Loss网络输出的数据和标签
具体实现:
te_data, te_label = transform(te_data, te_label)
tb_projector(te_data, te_label, os.path.join(ROOT_DIR, 'logs', 'origin'))
te_res = base_net(te_data)
tb_projector(te_res.asnumpy(), te_label, os.path.join(ROOT_DIR, 'logs', 'triplet'))
可视化工具以tensorboard为基础,通过嵌入向量的可视化接口实现数据分布的可视化。在tb_projector()
方法中,输入数据、标签和路径,即可生成可视化的数据格式。
具体实现:
def tb_projector(X_test, y_test, log_dir):metadata = os.path.join(log_dir, 'metadata.tsv')images = tf.Variable(X_test)with open(metadata, 'w') as metadata_file: # 把标签写入metadatafor row in y_test:metadata_file.write('%d\n' % row)with tf.Session() as sess:saver = tf.train.Saver([images]) # 把数据存储为矩阵sess.run(images.initializer) # 图像初始化saver.save(sess, os.path.join(log_dir, 'images.ckpt')) # 图像存储config = projector.ProjectorConfig() # 配置embedding = config.embeddings.add() # 嵌入向量添加embedding.tensor_name = images.name # Tensor名称embedding.metadata_path = metadata # Metadata的路径projector.visualize_embeddings(tf.summary.FileWriter(log_dir), config) # 可视化嵌入向量
TensorBoard在可视化方面的功能较多,一些其他框架也是使用TensorBoard进行数据可视化,如tensorboard-pytorch等,可视化为深度学习理论提供验证。
TensorBoard需要额外安装TensorFlow:
pip install tensorflow
Triplet Loss在数据编码领域中,有着重要的作用,算法也非常巧妙,适合相似性推荐等需求,是重要的工业界需求之一,如推荐菜谱、推荐音乐、推荐视频等。Triplet Loss模型可以学习到数据集中不同样本的相似性。除了传统的Triplet Loss损失计算方法,还有一些有趣的优化,如Lossless Triplet Loss等。
OK, that’s all! Enjoy it!
MXNet/Gluon 中 Triplet Loss 算法相关推荐
- CV之FRec之ME/LF:人脸识别中常用的模型评估指标/损失函数(Triplet Loss、Center Loss)简介、使用方法之详细攻略
CV之FRec之ME/LF:人脸识别中常用的模型评估指标/损失函数(Triplet Loss.Center Loss)简介.使用方法之详细攻略 目录 T1.Triplet Loss 1.英文原文解释 ...
- yolov3算法中关于loss={'yolo_loss': lambda y_true, y_pred: y_pred}的理解
yolov3算法中关于loss={'yolo_loss': lambda y_true, y_pred: y_pred}的理解 参考文献: (1)https://www.jianshu.com/p/7 ...
- Torch 中添加自己的 nn Modules:以添加 Dropout、 Triplet Loss 为例
Preface 因为要复现前面阅读的一篇论文:<论文笔记:Deep Relative Distance Learning: Tell the Difference Between Similar ...
- 在python中超简单安装mxnet_在Docker容器中搭建MXNet/Gluon开发环境
在这篇文章中没有直接使用MXNet官方提供的docker image,而是从一个干净的nvidia/cuda镜像开始,一步一步部署mxnet需要的相关软件环境,这样做是为了更加细致的了解mxnet的运 ...
- triplet loss 在深度学习中主要应用在什么地方?有什么明显的优势?
作者:罗浩.ZJU 链接:https://www.zhihu.com/question/62486208/answer/199117070 来源:知乎 著作权归作者所有.商业转载请联系作者获得授权,非 ...
- 深度学习中的优化算法与实现
点击上方"3D视觉工坊",选择"星标" 干货第一时间送达 GiantPandaCV导语:这篇文章的内容主要是参考 沐神的mxnet/gluon视频中,Aston ...
- 深度学习1:神经网络基础前馈神经网络Feedforward Neural Network(基于Python MXNet.Gluon框架)
目录 神经网络背景 常用的深度学习框架 机器学习的三个基本要素 模型 学习准则 损失函数 0-1损失函数 0-1 Loss Function 平方损失函数 Quadratic Loss Functio ...
- 一文理解Ranking Loss/Margin Loss/Triplet Loss
点击蓝字 关注我们 作者丨土豆@知乎 来源丨https://zhuanlan.zhihu.com/p/158853633 本文已获授权,未经作者许可,不得二次转载. 前言 Ranking loss在 ...
- MXNET gluon自定义损失函数
在学习李沐老师的目标检测篇章 目标检测中由于负类较多,正类较少,我们可以适当的减少对负类的惩罚 因此根据视频教程我们来重新写一个损失函数 通常mxnet的损失函数需要继承Loss类 from mxne ...
最新文章
- python3 快速排序
- 如何获取微信API的Access Token
- jpa中::::_项目学生:JPA标准查询
- linux macos 界面对比,GNOME 3与Mac OS X 10.7 (Lion)的纵览模式比较
- [Linux C]递归遍历指定目录,以目录树形式展示
- ubuntu14.04配置caffe
- 收藏 | 万字长文带你理解Pytorch官方Faster RCNN代码
- gc问题mysql连接池_数据库连接池引起的FullGC问题,看我如何一步步排查、分析、解决...
- 37 个 Python Web 开发框架总结
- 算法第四版课后习题答案 西安电子科技大学 计算机学院 算法课
- 统信UOS系统连接Windows共享的打印机(飞腾2000CPU)
- Win10 Word背景默认是绿色的怎么取消?
- 学习-Java循环while之求非负数之和
- ppspp android编译,PPSSPP模拟器通用设置,伪福利
- 密码学【java】初探究加密方式之非对称加密
- Microarchitecture: HyperThreading(超线程)
- php设置中国时区方法
- 阿里云windows服务器重置密码并连接远程桌面
- 原生JS--增删改查
- require https rid: 5f30fa30-76a72ecb-495cddc1