def cluster (train_loader_cluster,model,cluster_number,args):
model.eval()
features_sum = []
for i, (input, target,cluster_target) in enumerate(train_loader_cluster):
input = input[0].cuda() # input[0]=input[1].shape =(1024, 3, 32, 32)
target =target.cuda() # target.shape=(1024)
with torch.no_grad():
features = model(input) # (1024, 128)
features = features.detach()
features_sum.append(features) # len(features_sum)=11 最后一个feature.shape = (607, 128)
features= torch.cat(features_sum,dim=0) # features.shape = (10847, 128) # 整个cifar100里面的样本数为10847个
features = torch.split(features, args.cls_num_list, dim=0) # features是一个元组,features[0].shape=(500, 128) cls_num_list就是cifar100数据集中的100个类别,每个类别的样本数
if args.train_rule == 'Rank': # cls_num_list = [500, 477, 455, 434, 415, 396, 378, 361, 344, 328, 314, 299,...,5,5,5]
feature_center = [torch.mean(t, dim=0) for t in features] #feature_center.shape=(128) 先大致找到中心点,大致按cls_num_list的数量将整体的数据集划分为一堆一堆的
feature_center = torch.cat(feature_center,axis = 0) # feature_center.shape = (12800) 有100类,每个类别的中心点的维度都是128维
feature_center=feature_center.reshape(args.num_classes,args.feat_dim)
# feature_center.shape=(100, 128)
density = np.zeros(len(cluster_number)) # cluster_number就是每个类别应该划分为多少个小块,len(cluster_number)=100
# 初始化一个长度为类别数的密度向量density,用于后续村粗每个类别的密度值
# 计算每个类别的密度
for i in range(len(cluster_number)): # 对于每个类别i,计算其密度 features[0].shape = (500, 128) 也就是计算划分的第一个类里面的元素到中心点的距离
center_distance = F.pairwise_distance(features[i], feature_center[i], p=2).mean()/np.log(len(features[i])+10)
density[i] = center_distance.cpu().numpy()
density = density.clip(np.percentile(density,20),np.percentile(density,80)) # len(density)=100
#density = args.temperature*np.exp(density/density.mean())
density = args.temperature*(density/density.mean())
for index, value in enumerate(cluster_number): # cluster_number = [50,47,... 1],也就是每个类别应该划分为几个小块,如果某个类别在cluster_number的对应值为1,表示此类别不需要进一步划分,直接将其密度设置为agrs.temperature
if value==1:
density[index] = args.temperature
target = [[] for i in range(len(cluster_number))]
for i in range(len(cluster_number)):
if cluster_number[i] >1:
if args.cluster_method: # false
cluster_ids_x, _ = balanced_kmean(X=features[i], num_clusters=cluster_number[i], distance='cosine', init='k-means++',iol=50,tol=1e-3,device=torch.device("cuda"))
else:# features是一个元组,features[0].shape=(500, 128) cluster_number[0]=50
cluster_ids_x, _ = kmeans(X=features[i], num_clusters=cluster_number[i], distance='cosine', tol=1e-3, iter_limit=35, device=torch.device("cuda"))
#run faster for cluster
target[i]=cluster_ids_x # cluster_idx_x就是每个点距离最近的中心点的索引,target[0].shape=500
else:# features[i].shape=(19, 128) features[i].size()[0]=19
target[i] = torch.zeros(1,features[i].size()[0], dtype=torch.int).squeeze(0) # target[i].shape=19,全是0,因为他们只有一个块,那只有一个中心点
cluster_number_sum=[sum(cluster_number[:i]) for i in range(len(cluster_number))]# cluster_number就是每个类别应该划分为多少个小块,len(cluster_number)=100
for i ,k in enumerate(cluster_number_sum): #cluster_number_sum= [0, 50, 97, 142, 185, 226, 265, 302, 338, 372, 404, 435, 464, 492, ...] len =100
target[i] = torch.add(target[i], k) # 这里面的add(target[i], k)是做什么的
targets=torch.cat(target,dim=0)
targets = targets.numpy().tolist()
if args.train_rule == 'Rank':
return targets,density
else:
return targets
target[i] = torch.add(target[i], k) # 这里面的add(target[i], k)是做什么的
这段代码的功能是使用K-means聚类算法对一个卷积神经网络模型的特征进行聚类,其中model参数是一个预先训练好的模型,它将输入的图像转换成高维的特征向量,train_loader_cluster提供了需要聚类的数据集,cluster_number是一个定义了每个类别应分为多少个簇的列表。
以下是代码中不同部分的详细说明:
-
遍历数据加载器(
train_loader_cluster
),使用模型提取每个批次的特征向量,并将这些特征向量累积在features_sum
列表中。 -
将累积的特征向量拼接在一起(
torch.cat(features_sum, dim=0)
),得到整个数据集的特征矩阵。 -
使用
torch.split
按照各个类别的样本数量将特征矩阵分割成一个元组(分割后的每部分对应一个类别)。 -
计算每一类的平均特征向量,即特征中心(
feature_center
)。 -
计算每个类别的密度
density
,用于后面调整每个类别中聚类的数量。 -
对于每个类别,如果它的
cluster_number[i]
大于1,则使用K-means进行聚类,并将聚类结果存储在target[i]
。 -
如果类别的
cluster_number[i]
等于1,就意味着该类别不需要进行进一步的聚类,因此将对应的target[i]
设置为全零张量,零表示所有样本都属于同一个簇。 -
计算每个类别的累积聚类数量(
cluster_number_sum
)。 -
修改
target
列表中的每一项,基于累积聚类数量来调整簇的编号,保证不同类别的簇编号不会重叠。
下面我将解释target[i] = torch.add(target[i], k)
这一行代码的作用:
这行代码的目的是给每个类别的簇编号添加一个偏移量,使得每个类别的簇编号是唯一的,并且不同类别之间的编号是连续的。比如,如果第一个类别有50个簇,那么第二个类别的簇编号就应该从50开始,以此类推。
torch.add(target[i], k)
这段代码将target[i]
中的每个聚类索引加上一个偏移量k
,其中k
是之前类别中所有簇的累积数量(即该类别前面所有类别的簇总数)。这样确保每个样本点的簇标签在全局范围内是唯一的。