本文已参与「新人创作礼」活动,一起开启掘金创作之路。
L2 norm的作用
对于模型的输出,各个特征的模长均不一样, L2 norm是强行把同个维度的特征归一化。经过L2 norm的数据都处于0到1之间,距离就变得有一个上界了,显然样本间差异变小了。
L2 norm的应用场景
在人脸识别,行人重识别等图像分类的任务中,经常使用到该函数。尤其是对于triplet loss这种基于欧式距离的损失函数,若没有对特征进行L2 norm,使用不同尺度的特征计算则会低效。
- 因为尺度不一,由于每次选择的anchor也不一样,所以每次计算优化的方向不一致,导致效率低下。
- 若联合交叉熵损失函数联合训练,会导致两个损失函数的优化方向不一致
交叉熵损失函数的优化方向VS三元损失函数的优化方向:
注:triplet loss 是深度学习的一种损失函数,主要是用于训练差异性小的样本,比如人脸等;其次在训练目标是得到样本的embedding任务中,triplet loss 也经常使用,比如文本、图片的embedding。
图像搜索的arcface损失是利用边角距离,也需要对特征进行L2 norm。arcface在训练过程中就有对特征提取网络输出的embedding进行了L2 norm归一化,在推理时会将arcface去掉,这时候要记得对特征进行L2 norm!
L2 norm的几种实现方法
1、(推荐) torch.nn.functional.normalize(input, p=2, dim=1, eps=1e-12, out=None)
功能:将某一个维度除以那个维度对应的范数。
默认是2范数,默认在第一个维度上操作,因此默认输入维度应是[batchsize, embeddingsize]
推荐用法:可以直接加到网络的forward里
import torch.nn.functional as F
import torch.nn as nn
class my_model(nn.Module):
def __init__(self,):
super().__init__()
self.prj_model = embedding_model
def forward(self,x):
x = self.prj_model.forward_embedding(x)
x = F.normalize(x)
return x
输入、输出示例
import torch
#输入
a = torch.Tensor([[1,2,3],[-3,-2,-1]])
b = torch.nn.functional.normalize(a)
#输出b
#tensor([[ 0.2673, 0.5345, 0.8018],[-0.8018, -0.5345, -0.2673]])
2、sklearn.preprocessing.normalize(X, norm='l2', axis=1, copy=True, return_norm=False)
功能:将某一个维度除以那个维度对应的范数。
默认是2范数,默认在第一个维度上操作,因此默认输入维度应是[batchsize, embeddingsize]
推荐用法:在网络外部归一化,输入维度要正确,输入格式可以是tensor、numpy、list都行
import torch.nn as nn
from sklearn import preprocessing
import numpy as np
class my_model(nn.Module):
def __init__(self,):
super().__init__()
self.prj_model = embedding_model
def forward(self,x):
x = self.prj_model.forward_embedding(x)
return x
model = my_model()
with torch.no_grad():
feature = model(x)
img_feats=feature.cpu().numpy()
feature_256 = np.array(img_feats,dtype = "float32")
feature_256 = preprocessing.normalize(feature_256, norm='l2')
输入、输出示例
import torch
from sklearn import preprocessing
import numpy as np
#输入
a1 = [[1,2,3],[-3,-2,-1]]
a2 = torch.Tensor([[1,2,3],[-3,-2,-1]])
a3 = np.array(a1,dtype = "float32")
#输出
b1 = preprocessing.normalize(a1, norm='l2')
#array([[ 0.26726124, 0.53452248, 0.80178373],
# [-0.80178373, -0.53452248, -0.26726124]])
b2 = preprocessing.normalize(a2, norm='l2')
#array([[ 0.26726124, 0.53452248, 0.80178373],
# [-0.80178373, -0.53452248, -0.26726124]])
b3 = preprocessing.normalize(a3, norm='l2')
#array([[ 0.26726124, 0.5345225 , 0.8017837 ],
# [-0.8017837 , -0.5345225 , -0.26726124]], dtype=float32)
# 注意,如果直接使用np.array(a1)转numpy,默认的格式是dtype('int64')
# 使用float32的话,因为精度原因会有一定影响
3、faiss.normalize_L2(embedding)
功能:对被索引矩阵和查询向量,进行L2归一化
输入维度也应该是[samplesize,embeddingsize]
推荐用法:当前面都没有做归一化时,向量已经保存好,要进行相似度查询了,这个时候可以利用faiss的normalize_L2函数进行L2 norm
import faiss
feature = get_feature()
feature = np.array(feature).astype('float32')
faiss.normalize_L2(feature)
res = faiss.StandardGpuResources()
index_img = faiss.IndexFlatIP(256) # 假设embedding维度是256
index_img = faiss.index_cpu_to_gpu(res, 0, index_img)
index_img.add(feature)
query_embedding = get_query_feature()
query_embedding = np.array(query_embedding).astype('float32')
faiss.normalize_L2(query_embedding)
similarities_img, indexes_img = index_img.search(query_embedding, 10) # 查询top10
输入float32格式,输出同2中的b3
4、torch手动实现
import torch
a = torch.FloatTensor(((1,2),(2,3)))
a_norm = a.div(torch.norm(a, p=2, dim=1, keepdim=True))
输入、输出示例
import torch
#输入
a = torch.Tensor([[1,2,3],[-3,-2,-1]])
# tensor([[ 1., 2., 3.],
# [-3., -2., -1.]])
#输出
b = a.div(torch.norm(a, p=2, dim=1, keepdim=True))
# tensor([[ 0.2673, 0.5345, 0.8018],
[-0.8018, -0.5345, -0.2673]])
# 这种方法精度较低