├── NeurIPS2022-Camera Ready-Revised.pdf ├── NeurIPS2022_poster.pdf ├── NeurIPS2022_slides.pdf ├── README.md ├── coco.py ├── main_reweight.py ├── models.py ├── noise_rate_estimation_DualT.py ├── noise_rate_estimation_T.py ├── noise_rate_estimation_ours.py ├── script ├── run_coco_ours.sh ├── run_voc2007_ours.sh └── run_voc2012_ours.sh ├── test.py ├── tools.py ├── util.py └── voc.py /NeurIPS2022-Camera Ready-Revised.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tmllab/2022_NeurIPS_Multi-Label-T/2ad38c66fb57646706165537e1a2d305bec90d97/NeurIPS2022-Camera Ready-Revised.pdf -------------------------------------------------------------------------------- /NeurIPS2022_poster.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tmllab/2022_NeurIPS_Multi-Label-T/2ad38c66fb57646706165537e1a2d305bec90d97/NeurIPS2022_poster.pdf -------------------------------------------------------------------------------- /NeurIPS2022_slides.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tmllab/2022_NeurIPS_Multi-Label-T/2ad38c66fb57646706165537e1a2d305bec90d97/NeurIPS2022_slides.pdf -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Estimating Noise Transition Matrix with Label Correlations for Noisy Multi-Label Learning - Official PyTorch Code (NeurIPS 2022) 2 | 3 | ### Abstract: 4 | In label-noise learning, the noise transition matrix, bridging the class posterior for noisy and clean data, has been widely exploited to learn statistically consistent classifiers. The effectiveness of these algorithms relies heavily on estimating the transition matrix. Recently, the problem of label-noise learning in multi-label classification has received increasing attention, and these consistent algorithms can be applied in multi-label cases. However, the estimation of transition matrices in noisy multi-label learning has not been studied and remains challenging, since most of the existing estimators in noisy multi-class learning depend on the existence of anchor points and the accurate fitting of noisy class posterior. To address this problem, in this paper, we first study the identifiability problem of the class-dependent transition matrix in noisy multi-label learning, and then inspired by the identifiability results, we propose a new estimator by exploiting label correlations without neither anchor points nor accurate fitting of noisy class posterior. Specifically, we estimate the occurrence probability of two noisy labels to get noisy label correlations. Then, we perform sample selection to further extract information that implies clean label correlations, which is used to estimate the occurrence probability of one noisy label when a certain clean label appears. By utilizing the mismatch of label correlations implied in these occurrence probabilities, the transition matrix is identifiable, and can then be acquired by solving a simple bilinear decomposition problem. Empirical results demonstrate the effectiveness of our estimator to estimate the transition matrix with label correlations, leading to better classification performance. 5 | 6 | ### Requirements: 7 | * Python 3.8.10 8 | * Pytorch 1.8.0 (torchvision 0.9.0) 9 | * Numpy 1.19.5 10 | * Scikit-learn 1.0.1 11 | 12 | 13 | ### Running the code: 14 | To run the code use the provided scripts in the script folder. The dataset has to be placed in the data folder (should be done automatically). 15 | The arguments for running the code are as follows: 16 | 17 | ``` 18 | * batch_size, the batch size for learning 19 | * num_classes, the number of classes in the dataset 20 | * warmup_epoch, the number of warmup epoch for standard training 21 | * nepochs, the number of training epoch for reweighting 22 | * sample_epoch, the number of epoch for sample selection during warmup training 23 | * sample_th, the threshold of sample selection 24 | * nworkers, the number of workers in dataloder 25 | * dataset, the name of the used dataset, i.e., 'voc2007', 'voc2012', or 'coco' 26 | * seed, the random seed for label noise simulation 27 | * root, the path for the data folder 28 | * out, the path for the output folder 29 | * noise_rate_p, the noise rate for positive class value 30 | * noise_rate_n, the noise rate for negative class value 31 | * lr, learning rate for learning 32 | * weight-decay, the weight decay for learning, 33 | * estimator, the method for estimating transition matrix, i.e.,'T', 'dualT', or 'ours' 34 | * filter_outlier, the parameter for T estimator and dualT estimator 35 | ``` 36 | 37 | ### Citation: 38 | If you find the code useful in your research, please consider citing our paper: 39 | 40 | ``` 41 | @InProceedings{Li2022MLT, 42 | title = {Estimating Noise Transition Matrix with Label Correlations for Noisy Multi-Label Learning}, 43 | authors = {Shikun Li and Xiaobo Xia and Hansong Zhang and Yibing Zhan and Shiming Ge and Tongliang Liu}, 44 | year={2022}, 45 | booktitle ={36th Conference on Neural Information Processing Systems (NeurIPS 2022)}, 46 | } 47 | ``` 48 | 49 | Note: Our implementation uses parts of some public codes [1-2]. 50 | 51 | [1] "Dual T: Reducing Estimation Error for Transition Matrix in Label-noise Learning" https://github.com/a5507203/dual-t-reducing-estimation-error-for-transition-matrix-in-label-noise-learning 52 | 53 | [2] "Classification with Noisy Labels by Importance Reweighting" https://github.com/xiaoboxia/Classification-with-noisy-labels-by-importance-reweighting 54 | -------------------------------------------------------------------------------- /coco.py: -------------------------------------------------------------------------------- 1 | #for coco dataset load 2 | import torch.utils.data as data 3 | import json 4 | import os 5 | import subprocess 6 | from PIL import Image 7 | import numpy as np 8 | import torch 9 | import pickle 10 | from util import * 11 | import sys 12 | 13 | urls = {'train_img':'http://images.cocodataset.org/zips/train2014.zip', 14 | 'val_img' : 'http://images.cocodataset.org/zips/val2014.zip', 15 | 'annotations':'http://images.cocodataset.org/annotations/annotations_trainval2014.zip'} 16 | 17 | def download_coco2014(root, phase): 18 | if not os.path.exists(root): 19 | os.makedirs(root) 20 | tmpdir = os.path.join(root, 'tmp/') 21 | data = os.path.join(root, 'data/') 22 | if not os.path.exists(data): 23 | os.makedirs(data) 24 | if not os.path.exists(tmpdir): 25 | os.makedirs(tmpdir) 26 | if phase == 'train': 27 | filename = 'train2014.zip' 28 | elif phase == 'val': 29 | filename = 'val2014.zip' 30 | cached_file = os.path.join(tmpdir, filename) 31 | if not os.path.exists(cached_file): 32 | print('Downloading: "{}" to {}\n'.format(urls[phase + '_img'], cached_file)) 33 | os.chdir(tmpdir) 34 | subprocess.call('wget ' + urls[phase + '_img'], shell=True) 35 | os.chdir(root) 36 | # extract file 37 | img_data = os.path.join(data, filename.split('.')[0]) 38 | if not os.path.exists(img_data): 39 | print('[dataset] Extracting tar file {file} to {path}'.format(file=cached_file, path=data)) 40 | command = 'unzip {} -d {}'.format(cached_file,data) 41 | os.system(command) 42 | print('[dataset] Done!') 43 | 44 | # train/val images/annotations 45 | cached_file = os.path.join(tmpdir, 'annotations_trainval2014.zip') 46 | if not os.path.exists(cached_file): 47 | print('Downloading: "{}" to {}\n'.format(urls['annotations'], cached_file)) 48 | os.chdir(tmpdir) 49 | subprocess.Popen('wget ' + urls['annotations'], shell=True) 50 | os.chdir(root) 51 | annotations_data = os.path.join(data, 'annotations') 52 | if not os.path.exists(annotations_data): 53 | print('[dataset] Extracting tar file {file} to {path}'.format(file=cached_file, path=data)) 54 | command = 'unzip {} -d {}'.format(cached_file, data) 55 | os.system(command) 56 | print('[annotation] Done!') 57 | 58 | anno = os.path.join(data, '{}_anno.json'.format(phase)) 59 | anno2 = os.path.join(data, '{}_anno2.json'.format(phase)) 60 | img_id = {} 61 | annotations_id = {} 62 | if not (os.path.exists(anno) and os.path.exists(anno2)): 63 | annotations_file = json.load(open(os.path.join(annotations_data, 'instances_{}2014.json'.format(phase)))) 64 | annotations = annotations_file['annotations'] 65 | category = annotations_file['categories'] 66 | category_id = {} 67 | for cat in category: 68 | category_id[cat['id']] = cat['name'] 69 | cat2idx = categoty_to_idx(sorted(category_id.values())) 70 | images = annotations_file['images'] 71 | for annotation in annotations: 72 | if annotation['image_id'] not in annotations_id: 73 | annotations_id[annotation['image_id']] = set() 74 | annotations_id[annotation['image_id']].add(cat2idx[category_id[annotation['category_id']]]) 75 | for img in images: 76 | if img['id'] not in annotations_id: 77 | continue 78 | if img['id'] not in img_id: 79 | img_id[img['id']] = {} 80 | img_id[img['id']]['file_name'] = img['file_name'] 81 | img_id[img['id']]['labels'] = list(annotations_id[img['id']]) 82 | anno_list = [] 83 | for k, v in img_id.items(): 84 | anno_list.append(v) 85 | json.dump(anno_list, open(anno, 'w')) 86 | anno_list_2 = [] 87 | for k, v in img_id.items(): 88 | anno_list_2.append(v['labels']) 89 | json.dump(anno_list_2, open(anno2, 'w')) 90 | if not os.path.exists(os.path.join(data, 'category.json')): 91 | json.dump(cat2idx, open(os.path.join(data, 'category.json'), 'w')) 92 | del img_id 93 | del anno_list 94 | del anno_list_2 95 | del images 96 | del annotations_id 97 | del annotations 98 | del category 99 | del category_id 100 | print('[json] Done!') 101 | 102 | def categoty_to_idx(category): 103 | cat2idx = {} 104 | for cat in category: 105 | cat2idx[cat] = len(cat2idx) 106 | return cat2idx 107 | 108 | 109 | class COCO2014(data.Dataset): 110 | def __init__(self, root, transform=None, phase='train', Train=True, noise_rate=[0,0],random_seed=1): 111 | self.root = root 112 | self.phase = phase 113 | self.img_list = [] 114 | self.transform = transform 115 | download_coco2014(root, phase) 116 | self.get_anno() 117 | self.true_labels=self.get_true_labels() 118 | self.num_classes = len(self.cat2idx) 119 | self.img_list=np.array(self.img_list) 120 | if(phase=='train'): 121 | self.labels= generate_noisy_labels(self.true_labels , noise_rate,random_seed) 122 | if(Train): 123 | self.img_list , self.labels, self.true_labels , _, _, _=dataset_split(self.img_list ,self.labels,self.true_labels, num_classes=self.num_classes) 124 | else: 125 | _, _, _, self.img_list , self.labels, self.true_labels =dataset_split(self.img_list ,self.labels,self.true_labels, num_classes=self.num_classes) 126 | else: 127 | self.labels= self.true_labels 128 | 129 | def get_anno(self): 130 | list_path = os.path.join(self.root, 'data', '{}_anno.json'.format(self.phase)) 131 | self.img_list = json.load(open(list_path, 'r')) 132 | self.cat2idx = json.load(open(os.path.join(self.root, 'data', 'category.json'), 'r')) 133 | 134 | def get_true_labels(self): 135 | list_path = os.path.join(self.root, 'data', '{}_anno2.json'.format(self.phase)) 136 | labels=json.load(open(list_path, 'r')) 137 | true_labels=np.zeros((len(labels),len(self.cat2idx)))-1 138 | for i,label in enumerate(labels): 139 | true_labels[i,label]=1 140 | return true_labels 141 | 142 | def __len__(self): 143 | return len(self.img_list) 144 | 145 | def __getitem__(self, index): 146 | item = self.img_list[index] 147 | target=self.labels[index] 148 | return self.get(item),target 149 | 150 | def get(self, item): 151 | filename = item['file_name'] 152 | #labels = sorted(item['labels']) 153 | img = Image.open(os.path.join(self.root, 'data', '{}2014'.format(self.phase), filename)).convert('RGB') 154 | if self.transform is not None: 155 | img = self.transform(img) 156 | # target = np.zeros(self.num_classes, np.float32) - 1 157 | # target[labels] = 1 158 | return img 159 | 160 | 161 | def generate_noisy_labels(labels, noise_rate,random_seed): 162 | 163 | N, nc = labels.shape 164 | np.random.seed(random_seed) 165 | rand_mat = np.random.rand(N,nc) 166 | mask = np.zeros((N,nc), dtype = np.float) 167 | for j in range(nc): 168 | yj = labels[:,j] 169 | mask[yj!=1,j] = rand_mat[yj!=1,j] 1: 164 | net = nn.DataParallel(net) 165 | net=net.to(device) 166 | if torch.cuda.device_count() > 1: 167 | optimizer_es = torch.optim.Adam(net.parameters(),lr=args.lr,weight_decay=args.weight_decay) 168 | else: 169 | optimizer_es = torch.optim.Adam(net.parameters(),lr=args.lr,weight_decay=args.weight_decay) 170 | 171 | true_tm = np.zeros((args.nc,2,2)) 172 | for i in range(args.nc): 173 | true_tm[i,0,0]=1-noise_rate[0] 174 | true_tm[i,0,1]=noise_rate[0] 175 | true_tm[i,1,0]=noise_rate[1] 176 | true_tm[i,1,1]=1-noise_rate[1] 177 | 178 | # Estimate Transition Matrices 179 | if(args.estimator=="ours"): 180 | from noise_rate_estimation_ours import * 181 | t_m=estimate_noise_rate(net,train_loader, val_loader, estimate_loader,optimizer_es,args,true_tm=true_tm) 182 | elif(args.estimator=="dualT"): 183 | from noise_rate_estimation_DualT import * 184 | t_m=estimate_noise_rate(net,train_loader, val_loader, estimate_loader,optimizer_es,args,true_tm=true_tm,filter_outlier=args.filter_outlier) 185 | elif(args.estimator=="T"): 186 | from noise_rate_estimation_T import * 187 | t_m=estimate_noise_rate(net,train_loader, val_loader, estimate_loader,optimizer_es,args,true_tm=true_tm,filter_outlier=args.filter_outlier) 188 | print('t_m',t_m) 189 | 190 | if torch.cuda.device_count() > 1: 191 | optimizer = torch.optim.Adam(net.parameters(),lr=args.lr,weight_decay=args.weight_decay) 192 | else: 193 | optimizer = torch.optim.Adam(net.parameters(),lr=args.lr,weight_decay=args.weight_decay) 194 | 195 | # Train the network with reweighting 196 | 197 | val_metric=[] 198 | test_metric=[] 199 | 200 | best_val=0 201 | for i in range(args.nepochs): 202 | start = time.time() 203 | train_loss = train(net, train_loader, optimizer,t_m) 204 | map, OP, OR, OF1, CP, CR, CF1= test(net, val_loader) 205 | 206 | print('Epoch',i,' val_map, OP, OR, OF1, CP, CR, CF1 ',round(map,2), round(OP,2), round(OR,2), round(OF1,2), round(CP,2),round(CR,2),round(CF1,2)) 207 | val_metric.append([map, OP, OR, OF1, CP, CR, CF1]) 208 | if(map>best_val): 209 | best_val=map 210 | best_state_dict = copy.deepcopy(net.state_dict()) 211 | 212 | map, OP, OR, OF1, CP, CR, CF1= test(net, test_loader) 213 | print('Epoch',i,'test_map, OP, OR, OF1, CP, CR, CF1 ',round(map,2), round(OP,2), round(OR,2), round(OF1,2), round(CP,2),round(CR,2),round(CF1,2)) 214 | test_metric.append([map, OP, OR, OF1, CP, CR, CF1]) 215 | 216 | end = time.time() 217 | print('time', round(end-start,3)) 218 | log_file.flush() 219 | 220 | # early stop 221 | net.load_state_dict(best_state_dict) 222 | 223 | val_metric=np.array(val_metric) 224 | test_metric=np.array(test_metric) 225 | best_map,_, _,best_OF1,_, _, best_CF1=np.argmax(val_metric,axis=0) 226 | 227 | print('Best map',' val_map, OP, OR, OF1, CP, CR, CF1 ',round(val_metric[best_map,0],2), round(val_metric[best_map,1],2), round(val_metric[best_map,2],2), round(val_metric[best_map,3],2), round(val_metric[best_map,4],2),round(val_metric[best_map,5],2),round(val_metric[best_map,6],2)) 228 | 229 | print('Best map','test_map, OP, OR, OF1, CP, CR, CF1 ',round(test_metric[best_map,0],2), round(test_metric[best_map,1],2), round(test_metric[best_map,2],2), round(test_metric[best_map,3],2), round(test_metric[best_map,4],2),round(test_metric[best_map,5],2),round(test_metric[best_map,6],2)) 230 | 231 | 232 | log_file.close() 233 | -------------------------------------------------------------------------------- /models.py: -------------------------------------------------------------------------------- 1 | #used Resnet50 2 | import torchvision.models as models 3 | from torch.nn import Parameter 4 | import torch 5 | import torch.nn as nn 6 | import sys 7 | 8 | class Resnet(nn.Module): 9 | def __init__(self, model, num_classes): 10 | super(Resnet, self).__init__() 11 | 12 | modules = list(model.children())[:-1] 13 | self.features = nn.Sequential(*modules) 14 | 15 | self.linear = nn.Linear(2048, num_classes) 16 | 17 | def forward(self, feature, get_feature=False): 18 | feature = self.features(feature) 19 | 20 | gf = self.linear(feature.view(feature.size(0), -1)) 21 | gf = gf.view(gf.size(0), -1) 22 | 23 | if(get_feature): 24 | return feature 25 | else: 26 | return gf 27 | 28 | def get_resnet50(num_classes, pretrained=True): 29 | model = models.resnet50(pretrained=pretrained) 30 | return Resnet(model, num_classes) 31 | -------------------------------------------------------------------------------- /noise_rate_estimation_DualT.py: -------------------------------------------------------------------------------- 1 | # DualT estimator 2 | import torch 3 | import sys 4 | import numpy as np 5 | import tools 6 | import copy 7 | import torch.nn as nn 8 | def loss_func_bce(out, batch_y): 9 | bce = torch.nn.BCELoss() 10 | loss = bce(torch.sigmoid(out), batch_y.float()) 11 | return loss 12 | 13 | def loss_func_bce2(out, batch_y): 14 | bce = torch.nn.BCELoss(reduction='sum') 15 | loss = bce(torch.sigmoid(out), batch_y.float()) 16 | return loss 17 | 18 | def estimate_noise_rate(model,train_loader, val_loader, estimate_loader,optimizer_es,args,true_tm,filter_outlier=False): 19 | print('Estimate transition matirx......Waiting......') 20 | 21 | A= torch.zeros((args.nc, args.warmup_epoch, len(train_loader.dataset), 2)) 22 | val_list = [] # for val_loss 23 | val_list_2 = [] # for val_acc 24 | val_list_list = [] # for each class's val_loss 25 | val_list_list_2=[] # for each class's val_acc 26 | best_val=100 27 | for i in range(args.nc): 28 | val_list_list.append([]) 29 | val_list_list_2.append([]) 30 | # warmup training 31 | for epoch in range(args.warmup_epoch): 32 | 33 | print('epoch {}'.format(epoch + 1)) 34 | model.train() 35 | train_loss = 0. 36 | 37 | for batch_x, batch_y in train_loader: 38 | batch_x = batch_x.cuda().float() 39 | batch_y = batch_y.cuda().float() 40 | batch_y[batch_y==0]=1 41 | batch_y[batch_y==-1]=0 42 | optimizer_es.zero_grad() 43 | out = model(batch_x) 44 | loss = loss_func_bce(out, batch_y) 45 | train_loss += loss.item() 46 | loss.backward() 47 | optimizer_es.step() 48 | 49 | 50 | print('Train Loss: {:.6f}'.format(train_loss / (len(train_loader.dataset))*args.bs)) 51 | 52 | with torch.no_grad(): 53 | model.eval() 54 | val_loss = 0. 55 | val_loss_2=[0]*args.nc 56 | val_acc=0 57 | val_acc_2=[0]*args.nc 58 | for batch_x, batch_y in val_loader: 59 | batch_x = batch_x.cuda().float() 60 | batch_y = batch_y.cuda().float() 61 | batch_y[batch_y==0]=1 62 | batch_y[batch_y==-1]=0 63 | out = model(batch_x) 64 | loss = loss_func_bce(out, batch_y) 65 | val_loss += loss.item() 66 | pred = out>0.5 67 | val_correct = (pred == batch_y).sum() 68 | val_acc += val_correct.item() 69 | for i in range(args.nc): 70 | loss_2 = loss_func_bce2(out[:,i], batch_y[:,i]) 71 | val_loss_2[i] += loss_2.item() 72 | val_correct_2 = (pred[:,i] == batch_y[:,i]).sum() 73 | val_acc_2[i] += val_correct_2.item() 74 | if(val_loss0.5 68 | val_correct = (pred == batch_y).sum() 69 | val_acc += val_correct.item() 70 | for i in range(args.nc): 71 | loss_2 = loss_func_bce2(out[:,i], batch_y[:,i]) 72 | val_loss_2[i] += loss_2.item() 73 | val_correct_2 = (pred[:,i] == batch_y[:,i]).sum() 74 | val_acc_2[i] += val_correct_2.item() 75 | if(val_loss0.5 70 | val_correct = (pred == batch_y).sum() 71 | val_acc += val_correct.item() 72 | for i in range(args.nc): 73 | loss_2 = loss_func_bce2(out[:,i], batch_y[:,i]) 74 | val_loss_2[i] += loss_2.item() 75 | val_correct_2 = (pred[:,i] == batch_y[:,i]).sum() 76 | val_acc_2[i] += val_correct_2.item() 77 | if(val_lossth) 149 | 150 | if(True_T[i,0,1]==0): # for multi-label learning with missing labels 151 | select_vec[Y[:,i]==1,i]=1 152 | if(True_T[i,1,0]==0): # for partial multi-label learning 153 | select_vec[Y[:,i]==0,i]=1 154 | 155 | 156 | print('class ',i,' positive ', sum(Y[:,i]),' negative ', sum(1-Y[:,i]), ' selected positive ', sum(Y[select_vec[:,i]==1,i]),' negative ', sum(1-Y[select_vec[:,i]==1,i])) 157 | 158 | print('positive precision',sum((true_train_labels[:,i]==1)*(select_vec[:,i]==1)*(Y[:,i]==1))/sum(Y[select_vec[:,i]==1,i]), 'negative precision', sum((true_train_labels[:,i]==0)*(select_vec[:,i]==1)*(Y[:,i]==0))/sum(1-Y[select_vec[:,i]==1,i])) 159 | 160 | error=0 161 | est_T = np.zeros_like(True_T) 162 | temp_list=[] 163 | for i in range(args.nc): 164 | temp_estimation=[] 165 | selected_i=select_vec[:,i] 166 | selected_Y_i = Y[selected_i==1,:] 167 | if(sum(selected_Y_i[:,i])<50 or sum(1-selected_Y_i[:,i])<50): 168 | continue 169 | for k in range(args.nc): 170 | if(i==k): 171 | continue 172 | print('i',i,' k',k) 173 | P_noisy_11=sum(Y[:,i]*Y[:,k])/len(Y) 174 | P_noisy_10=sum(Y[:,i]*(1-Y[:,k]))/len(Y) 175 | P_noisy_01=sum((1-Y[:,i])*Y[:,k])/len(Y) 176 | P_noisy_00=1-P_noisy_11-P_noisy_10-P_noisy_01 177 | 178 | Pc_clean_1_noisy_1=sum(selected_Y_i[:,k]*selected_Y_i[:,i])/sum(selected_Y_i[:,i]) 179 | Pc_clean_1_noisy_0=sum((1-selected_Y_i[:,k])*selected_Y_i[:,i])/sum(selected_Y_i[:,i]) 180 | Pc_clean_0_noisy_1=sum(selected_Y_i[:,k]*(1-selected_Y_i[:,i]))/sum(1-selected_Y_i[:,i]) 181 | Pc_clean_0_noisy_0=sum((1-selected_Y_i[:,k])*(1-selected_Y_i[:,i]))/sum(1-selected_Y_i[:,i]) 182 | 183 | pi_1 = -(1.0*(P_noisy_00 - 1.0*Pc_clean_0_noisy_0 + P_noisy_10))/(Pc_clean_0_noisy_0 - 1.0*Pc_clean_1_noisy_0) 184 | print( 'pi_1',pi_1 ) 185 | 186 | if( pi_1<0 or pi_1>1 ): 187 | continue 188 | 189 | P_clean_1_noisy_1=Pc_clean_1_noisy_1*pi_1 190 | P_clean_1_noisy_0=Pc_clean_1_noisy_0*pi_1 191 | P_clean_0_noisy_1=Pc_clean_0_noisy_1*(1-pi_1) 192 | P_clean_0_noisy_0=Pc_clean_0_noisy_0*(1-pi_1) 193 | 194 | lo_i_0 = -(1.0*P_clean_1_noisy_0*P_noisy_11 - 1.0*P_clean_1_noisy_1*P_noisy_10)/(P_clean_0_noisy_0*P_clean_1_noisy_1 - 1.0*P_clean_0_noisy_1*P_clean_1_noisy_0) 195 | lo_i_1 =(1.0*(P_clean_0_noisy_0*P_noisy_01 - 1.0*P_clean_0_noisy_1*P_noisy_00))/(P_clean_0_noisy_0*P_clean_1_noisy_1 - 1.0*P_clean_0_noisy_1*P_clean_1_noisy_0) 196 | print('lo_i_0, lo_i_1',lo_i_0, lo_i_1) 197 | 198 | if(lo_i_0<0 and lo_i_0>-0.3/args.nc): 199 | lo_i_0=0 200 | if(lo_i_1<0 and lo_i_1>-0.3/args.nc): 201 | lo_i_1=0 202 | if(lo_i_1<-0.3/args.nc or lo_i_1<-0.3/args.nc): 203 | continue 204 | 205 | if(True_T[i,0,1]==0): # for multi-label learning with missing labels 206 | lo_i_0=0 207 | if(True_T[i,1,0]==0): # for partial multi-label learning 208 | lo_i_1=0 209 | 210 | if(lo_i_0>=0 and lo_i_0<=1 and lo_i_1>=0 and lo_i_1<=1 and lo_i_0+lo_i_1<1): 211 | T=np.array([[1-lo_i_0, lo_i_0], [lo_i_1,1-lo_i_1]]) 212 | 213 | estimate_error = tools.error(T, True_T[i]) 214 | print('class i ', i, ' class k ',k,' our estimation', T[range(2),[1,0]], 'True_T', True_T[i,range(2),[1,0]], 'error', estimate_error,'\n') 215 | temp_estimation.append(T[range(2),[1,0]]) 216 | continue 217 | else: 218 | continue 219 | # select best one from temp estimations 220 | if(len(temp_estimation)==0): 221 | temp_list.append(i) 222 | else: 223 | temp_error=[0]*len(temp_estimation) 224 | for k in range(args.nc): 225 | if(i==k): 226 | continue 227 | P_noisy_11=sum(Y[:,i]*Y[:,k])/len(Y) 228 | P_noisy_10=sum(Y[:,i]*(1-Y[:,k]))/len(Y) 229 | P_noisy_01=sum((1-Y[:,i])*Y[:,k])/len(Y) 230 | P_noisy_00=1-P_noisy_11-P_noisy_10-P_noisy_01 231 | 232 | selected_i=select_vec[:,i] 233 | selected_Y_i = Y[selected_i==1,:] 234 | 235 | Pc_clean_1_noisy_1=sum(selected_Y_i[:,k]*selected_Y_i[:,i])/sum(selected_Y_i[:,i]) 236 | Pc_clean_1_noisy_0=sum((1-selected_Y_i[:,k])*selected_Y_i[:,i])/sum(selected_Y_i[:,i]) 237 | Pc_clean_0_noisy_1=sum(selected_Y_i[:,k]*(1-selected_Y_i[:,i]))/sum(1-selected_Y_i[:,i]) 238 | Pc_clean_0_noisy_0=sum((1-selected_Y_i[:,k])*(1-selected_Y_i[:,i]))/sum(1-selected_Y_i[:,i]) 239 | 240 | 241 | pi_1 = -(1.0*(P_noisy_00 - 1.0*Pc_clean_0_noisy_0 + P_noisy_10))/(Pc_clean_0_noisy_0 - 1.0*Pc_clean_1_noisy_0) 242 | 243 | 244 | P_clean_1_noisy_1=Pc_clean_1_noisy_1*pi_1 245 | P_clean_1_noisy_0=Pc_clean_1_noisy_0*pi_1 246 | P_clean_0_noisy_1=Pc_clean_0_noisy_1*(1-pi_1) 247 | P_clean_0_noisy_0=Pc_clean_0_noisy_0*(1-pi_1) 248 | 249 | for index, lo in enumerate(temp_estimation): 250 | lo_i_0, lo_i_1 =lo[0],lo[1] 251 | temp_error[index] += abs(lo_i_0*(P_clean_0_noisy_0*P_clean_1_noisy_1 - 1.0*P_clean_0_noisy_1*P_clean_1_noisy_0)+(1.0*P_clean_1_noisy_0*P_noisy_11 - 1.0*P_clean_1_noisy_1*P_noisy_10)) 252 | temp_error[index] += abs(lo_i_1*(P_clean_0_noisy_0*P_clean_1_noisy_1 - 1.0*P_clean_0_noisy_1*P_clean_1_noisy_0) - (1.0*(P_clean_0_noisy_0*P_noisy_01 - 1.0*P_clean_0_noisy_1*P_noisy_00))) 253 | temp_error=np.array(temp_error) 254 | print('temp_error',temp_error) 255 | lo=temp_estimation[np.argmin(temp_error)] 256 | lo_i_0, lo_i_1 =lo[0],lo[1] 257 | T=np.array([[1-lo_i_0, lo_i_0], [lo_i_1,1-lo_i_1]]) 258 | 259 | estimate_error = tools.error(T, True_T[i]) 260 | error+=estimate_error 261 | est_T[i] = T 262 | print('class', i, ' final ours estimation', T[range(2),[1,0]], 'True_T', True_T[i,range(2),[1,0]], 'error', estimate_error,'\n') 263 | 264 | # use dualT estimator for unsolvable classes 265 | for i in temp_list: 266 | val_array = np.array(val_list_list[i]) # we use the val loss here for selecting each class's model 267 | model_index = np.argmax(-val_array) 268 | print('model_index',model_index) 269 | prob_=copy.deepcopy(A[i]) 270 | transition_matrix_ = tools.fit(prob_[model_index, :, :], 2, False) 271 | transition_matrix = tools.norm(transition_matrix_) 272 | 273 | T_ = transition_matrix 274 | 275 | T=copy.deepcopy(T_) 276 | 277 | if(True_T[i,0,1]==0): # for multi-label learning with missing labels 278 | T[0,1]=0 279 | T[0,0]=1 280 | if(True_T[i,1,0]==0): # for partial multi-label learning 281 | T[1,0]=0 282 | T[1,1]=1 283 | 284 | estimate_error = tools.error(T, True_T[i]) 285 | print('class', i, 'T estimation max',T[range(2),[1,0]], 'True_T', True_T[i,range(2),[1,0]], 'error', estimate_error) 286 | 287 | pred= np.argmax(prob_[model_index, :, :],axis=-1) 288 | T_spadesuit = np.zeros((2,2)) 289 | for j in range(len(Y)): 290 | T_spadesuit[int(pred[j])][int(Y[j,i])]+=1 291 | T_spadesuit = np.array(T_spadesuit) 292 | sum_matrix = np.tile(T_spadesuit.sum(axis = 1),(2,1)).transpose() 293 | T_spadesuit = T_spadesuit/sum_matrix 294 | T_spadesuit = np.nan_to_num(T_spadesuit) 295 | dual_t_matrix = np.matmul(T_, T_spadesuit) 296 | 297 | if(True_T[i,0,1]==0): # for multi-label learning with missing labels 298 | dual_t_matrix[0,1]=0 299 | dual_t_matrix[0,0]=1 300 | if(True_T[i,1,0]==0): # for partial multi-label learning 301 | dual_t_matrix[1,0]=0 302 | dual_t_matrix[1,1]=1 303 | 304 | 305 | estimate_error = tools.error(dual_t_matrix, True_T[i]) 306 | est_T[i]= dual_t_matrix 307 | error+=estimate_error 308 | print('class', i, 'Dual-T estimation max', dual_t_matrix[range(2),[1,0]], 'True_T', True_T[i,range(2),[1,0]], 'error', estimate_error,'\n') 309 | 310 | print('total error', error) 311 | 312 | return est_T -------------------------------------------------------------------------------- /script/run_coco_ours.sh: -------------------------------------------------------------------------------- 1 | for ((i=2;i<=4;i++)) 2 | do 3 | CUDA_VISIBLE_DEVICES=0 python3 main_reweight.py --batch_size 128 --nepochs 30 --num_classes 80 --nworkers 4 --seed $i --warmup_epoch 30 --dataset 'coco' --root ./data/MS-COCO/ --out ./results/multi-label-reweight_p0.2_coco_ours_resnet50/ --noise_rate_p 0.2 --noise_rate_n 0.0 --estimator 'ours' --sample_epoch 15 4 | CUDA_VISIBLE_DEVICES=0 python3 main_reweight.py --batch_size 128 --nepochs 30 --num_classes 80 --nworkers 4 --seed $i --warmup_epoch 30 --dataset 'coco' --root ./data/MS-COCO/ --out ./results/multi-label-reweight_p0.6_coco_ours_resnet50/ --noise_rate_p 0.6 --noise_rate_n 0.0 --estimator 'ours' --sample_epoch 15 5 | CUDA_VISIBLE_DEVICES=0 python3 main_reweight.py --batch_size 128 --nepochs 30 --num_classes 80 --nworkers 4 --seed $i --warmup_epoch 30 --dataset 'coco' --root ./data/MS-COCO/ --out ./results/multi-label-reweight_n0.2_coco_ours_resnet50/ --noise_rate_p 0 --noise_rate_n 0.2 --estimator 'ours' --sample_epoch 15 6 | CUDA_VISIBLE_DEVICES=0 python3 main_reweight.py --batch_size 128 --nepochs 30 --num_classes 80 --nworkers 4 --seed $i --warmup_epoch 30 --dataset 'coco' --root ./data/MS-COCO/ --out ./results/multi-label-reweight_n0.6_coco_ours_resnet50/ --noise_rate_p 0 --noise_rate_n 0.6 --estimator 'ours' --sample_epoch 15 7 | CUDA_VISIBLE_DEVICES=0 python3 main_reweight.py --batch_size 128 --nepochs 30 --num_classes 80 --nworkers 4 --seed $i --warmup_epoch 30 --dataset 'coco' --root ./data/MS-COCO/ --out ./results/multi-label-reweight_p0.1n0.1_coco_ours_resnet50/ --noise_rate_p 0.1 --noise_rate_n 0.1 --estimator 'ours' --sample_epoch 15 8 | CUDA_VISIBLE_DEVICES=0 python3 main_reweight.py --batch_size 128 --nepochs 30 --num_classes 80 --nworkers 4 --seed $i --warmup_epoch 30 --dataset 'coco' --root ./data/MS-COCO/ --out ./results/multi-label-reweight_p0.2n0.2_coco_ours_resnet50/ --noise_rate_p 0.2 --noise_rate_n 0.2 --estimator 'ours' --sample_epoch 15 9 | CUDA_VISIBLE_DEVICES=0 python3 main_reweight.py --batch_size 128 --nepochs 30 --num_classes 80 --nworkers 4 --seed $i --warmup_epoch 30 --dataset 'coco' --root ./data/MS-COCO/ --out ./results/multi-label-reweight_p0.2n0.00752_coco_ours_resnet50/ --noise_rate_p 0.2 --noise_rate_n 0.00752 --estimator 'ours' --sample_epoch 15 10 | CUDA_VISIBLE_DEVICES=0 python3 main_reweight.py --batch_size 128 --nepochs 30 --num_classes 80 --nworkers 4 --seed $i --warmup_epoch 30 --dataset 'coco' --root ./data/MS-COCO/ --out ./results/multi-label-reweight_p0.4n0.01504_coco_ours_resnet50/ --noise_rate_p 0.4 --noise_rate_n 0.01504 --estimator 'ours' --sample_epoch 15 11 | done -------------------------------------------------------------------------------- /script/run_voc2007_ours.sh: -------------------------------------------------------------------------------- 1 | for ((i=2;i<=6;i++)) 2 | do 3 | CUDA_VISIBLE_DEVICES=0 python3 main_reweight.py --batch_size 128 --nepochs 20 --num_classes 20 --nworkers 4 --seed $i --warmup_epoch 20 --dataset 'voc2007' --root ./data/voc/ --out ./results/multi-label-reweight_p0.2_voc2007_ours_resnet50/ --noise_rate_p 0.2 --noise_rate_n 0.0 --estimator 'ours' --sample_epoch 10 4 | CUDA_VISIBLE_DEVICES=0 python3 main_reweight.py --batch_size 128 --nepochs 20 --num_classes 20 --nworkers 4 --seed $i --warmup_epoch 20 --dataset 'voc2007' --root ./data/voc/ --out ./results/multi-label-reweight_p0.6_voc2007_ours_resnet50/ --noise_rate_p 0.6 --noise_rate_n 0.0 --estimator 'ours' --sample_epoch 10 5 | CUDA_VISIBLE_DEVICES=0 python3 main_reweight.py --batch_size 128 --nepochs 20 --num_classes 20 --nworkers 4 --seed $i --warmup_epoch 20 --dataset 'voc2007' --root ./data/voc/ --out ./results/multi-label-reweight_n0.2_voc2007_ours_resnet50/ --noise_rate_p 0 --noise_rate_n 0.2 --estimator 'ours' --sample_epoch 10 6 | CUDA_VISIBLE_DEVICES=0 python3 main_reweight.py --batch_size 128 --nepochs 20 --num_classes 20 --nworkers 4 --seed $i --warmup_epoch 20 --dataset 'voc2007' --root ./data/voc/ --out ./results/multi-label-reweight_n0.6_voc2007_ours_resnet50/ --noise_rate_p 0 --noise_rate_n 0.6 --estimator 'ours' --sample_epoch 10 7 | CUDA_VISIBLE_DEVICES=0 python3 main_reweight.py --batch_size 128 --nepochs 20 --num_classes 20 --nworkers 4 --seed $i --warmup_epoch 20 --dataset 'voc2007' --root ./data/voc/ --out ./results/multi-label-reweight_p0.1n0.1_voc2007_ours_resnet50/ --noise_rate_p 0.1 --noise_rate_n 0.1 --estimator 'ours' --sample_epoch 10 8 | CUDA_VISIBLE_DEVICES=0 python3 main_reweight.py --batch_size 128 --nepochs 20 --num_classes 20 --nworkers 4 --seed $i --warmup_epoch 20 --dataset 'voc2007' --root ./data/voc/ --out ./results/multi-label-reweight_p0.2n0.2_voc2007_ours_resnet50/ --noise_rate_p 0.2 --noise_rate_n 0.2 --estimator 'ours' --sample_epoch 10 9 | CUDA_VISIBLE_DEVICES=0 python3 main_reweight.py --batch_size 128 --nepochs 20 --num_classes 20 --nworkers 4 --seed $i --warmup_epoch 20 --dataset 'voc2007' --root ./data/voc/ --out ./results/multi-label-reweight_p0.2n0.0172_voc2007_ours_resnet50/ --noise_rate_p 0.2 --noise_rate_n 0.0172 --estimator 'ours' --sample_epoch 10 10 | CUDA_VISIBLE_DEVICES=0 python3 main_reweight.py --batch_size 128 --nepochs 20 --num_classes 20 --nworkers 4 --seed $i --warmup_epoch 20 --dataset 'voc2007' --root ./data/voc/ --out ./results/multi-label-reweight_p0.4n0.0343_voc2007_ours_resnet50/ --noise_rate_p 0.4 --noise_rate_n 0.0343 --estimator 'ours' --sample_epoch 10 11 | done -------------------------------------------------------------------------------- /script/run_voc2012_ours.sh: -------------------------------------------------------------------------------- 1 | for ((i=2;i<=6;i++)) 2 | do 3 | CUDA_VISIBLE_DEVICES=0 python3 main_reweight.py --batch_size 128 --nepochs 20 --num_classes 20 --nworkers 4 --seed $i --warmup_epoch 20 --dataset 'voc2012' --root ./data/voc/ --out ./results/multi-label-reweight_p0.2_voc2012_ours_resnet50/ --noise_rate_p 0.2 --noise_rate_n 0.0 --estimator 'ours' --sample_epoch 10 4 | CUDA_VISIBLE_DEVICES=0 python3 main_reweight.py --batch_size 128 --nepochs 20 --num_classes 20 --nworkers 4 --seed $i --warmup_epoch 20 --dataset 'voc2012' --root ./data/voc/ --out ./results/multi-label-reweight_p0.6_voc2012_ours_resnet50/ --noise_rate_p 0.6 --noise_rate_n 0.0 --estimator 'ours' --sample_epoch 10 5 | CUDA_VISIBLE_DEVICES=0 python3 main_reweight.py --batch_size 128 --nepochs 20 --num_classes 20 --nworkers 4 --seed $i --warmup_epoch 20 --dataset 'voc2012' --root ./data/voc/ --out ./results/multi-label-reweight_n0.2_voc2012_ours_resnet50/ --noise_rate_p 0 --noise_rate_n 0.2 --estimator 'ours' --sample_epoch 10 6 | CUDA_VISIBLE_DEVICES=0 python3 main_reweight.py --batch_size 128 --nepochs 20 --num_classes 20 --nworkers 4 --seed $i --warmup_epoch 20 --dataset 'voc2012' --root ./data/voc/ --out ./results/multi-label-reweight_n0.6_voc2012_ours_resnet50/ --noise_rate_p 0 --noise_rate_n 0.6 --estimator 'ours' --sample_epoch 10 7 | CUDA_VISIBLE_DEVICES=0 python3 main_reweight.py --batch_size 128 --nepochs 20 --num_classes 20 --nworkers 4 --seed $i --warmup_epoch 20 --dataset 'voc2012' --root ./data/voc/ --out ./results/multi-label-reweight_p0.1n0.1_voc2012_ours_resnet50/ --noise_rate_p 0.1 --noise_rate_n 0.1 --estimator 'ours' --sample_epoch 10 8 | CUDA_VISIBLE_DEVICES=0 python3 main_reweight.py --batch_size 128 --nepochs 20 --num_classes 20 --nworkers 4 --seed $i --warmup_epoch 20 --dataset 'voc2012' --root ./data/voc/ --out ./results/multi-label-reweight_p0.2n0.2_voc2012_ours_resnet50/ --noise_rate_p 0.2 --noise_rate_n 0.2 --estimator 'ours' --sample_epoch 10 9 | CUDA_VISIBLE_DEVICES=0 python3 main_reweight.py --batch_size 128 --nepochs 20 --num_classes 20 --nworkers 4 --seed $i --warmup_epoch 20 --dataset 'voc2012' --root ./data/voc/ --out ./results/multi-label-reweight_p0.2n0.0172_voc2012_ours_resnet50/ --noise_rate_p 0.2 --noise_rate_n 0.0172 --estimator 'ours' --sample_epoch 10 10 | CUDA_VISIBLE_DEVICES=0 python3 main_reweight.py --batch_size 128 --nepochs 20 --num_classes 20 --nworkers 4 --seed $i --warmup_epoch 20 --dataset 'voc2012' --root ./data/voc/ --out ./results/multi-label-reweight_p0.4n0.0343_voc2012_ours_resnet50/ --noise_rate_p 0.4 --noise_rate_n 0.0343 --estimator 'ours' --sample_epoch 10 11 | done -------------------------------------------------------------------------------- /test.py: -------------------------------------------------------------------------------- 1 | #for evaluating model 2 | import numpy as np 3 | import torch 4 | import math 5 | 6 | def test(net, loader): 7 | 8 | net.eval() 9 | 10 | ap_meter= AveragePrecisionMeter() 11 | for i, (X, y) in enumerate(loader): 12 | # Pass to gpu or cpu 13 | X, y = X.cuda().float(), y.cuda().float() 14 | 15 | with torch.no_grad(): 16 | out = net(X) 17 | 18 | 19 | ap_meter.add(out.cpu().detach(), y.cpu()) 20 | 21 | map = ap_meter.value().mean().cpu().detach().numpy() 22 | OP, OR, OF1, CP, CR, CF1 = ap_meter.overall() 23 | 24 | return map*100, OP*100, OR*100, OF1*100, CP*100, CR*100, CF1*100 25 | 26 | def compute_cover(labels, outputs): 27 | n_labels = labels.shape[1] 28 | loss = coverage_error(labels, outputs) 29 | 30 | return (loss-1)/n_labels 31 | 32 | class AveragePrecisionMeter(object): 33 | """ 34 | The APMeter measures the average precision per class. 35 | The APMeter is designed to operate on `NxK` Tensors `output` and 36 | `target`, and optionally a `Nx1` Tensor weight where (1) the `output` 37 | contains model output scores for `N` examples and `K` classes that ought to 38 | be higher when the model is more convinced that the example should be 39 | positively labeled, and smaller when the model believes the example should 40 | be negatively labeled (for instance, the output of a sigmoid function); (2) 41 | the `target` contains only values 0 (for negative examples) and 1 42 | (for positive examples); and (3) the `weight` ( > 0) represents weight for 43 | each sample. 44 | """ 45 | 46 | def __init__(self, difficult_examples=True): 47 | super(AveragePrecisionMeter, self).__init__() 48 | self.reset() 49 | self.difficult_examples = difficult_examples 50 | 51 | def reset(self): 52 | """Resets the meter with empty member variables""" 53 | self.scores = torch.FloatTensor(torch.FloatStorage()) 54 | self.targets = torch.LongTensor(torch.LongStorage()) 55 | 56 | def add(self, output, target): 57 | """ 58 | Args: 59 | output (Tensor): NxK tensor that for each of the N examples 60 | indicates the probability of the example belonging to each of 61 | the K classes, according to the model. The probabilities should 62 | sum to one over all classes 63 | target (Tensor): binary NxK tensort that encodes which of the K 64 | classes are associated with the N-th input 65 | (eg: a row [0, 1, 0, 1] indicates that the example is 66 | associated with classes 2 and 4) 67 | weight (optional, Tensor): Nx1 tensor representing the weight for 68 | each example (each weight > 0) 69 | """ 70 | if not torch.is_tensor(output): 71 | output = torch.from_numpy(output) 72 | if not torch.is_tensor(target): 73 | target = torch.from_numpy(target) 74 | 75 | if output.dim() == 1: 76 | output = output.view(-1, 1) 77 | else: 78 | assert output.dim() == 2, \ 79 | 'wrong output size (should be 1D or 2D with one column \ 80 | per class)' 81 | if target.dim() == 1: 82 | target = target.view(-1, 1) 83 | else: 84 | assert target.dim() == 2, \ 85 | 'wrong target size (should be 1D or 2D with one column \ 86 | per class)' 87 | if self.scores.numel() > 0: 88 | assert target.size(1) == self.targets.size(1), \ 89 | 'dimensions for output should match previously added examples.' 90 | 91 | # make sure storage is of sufficient size 92 | if self.scores.storage().size() < self.scores.numel() + output.numel(): 93 | new_size = math.ceil(self.scores.storage().size() * 1.5) 94 | self.scores.storage().resize_(int(new_size + output.numel())) 95 | self.targets.storage().resize_(int(new_size + output.numel())) 96 | 97 | # store scores and targets 98 | offset = self.scores.size(0) if self.scores.dim() > 0 else 0 99 | self.scores.resize_(offset + output.size(0), output.size(1)) 100 | self.targets.resize_(offset + target.size(0), target.size(1)) 101 | self.scores.narrow(0, offset, output.size(0)).copy_(output) 102 | self.targets.narrow(0, offset, target.size(0)).copy_(target) 103 | 104 | def value(self): 105 | """Returns the model's average precision for each class 106 | Return: 107 | ap (FloatTensor): 1xK tensor, with avg precision for each class k 108 | """ 109 | 110 | if self.scores.numel() == 0: 111 | return 0 112 | ap = torch.zeros(self.scores.size(1)) 113 | rg = torch.arange(1, self.scores.size(0)).float() 114 | # compute average precision for each class 115 | for k in range(self.scores.size(1)): 116 | # sort scores 117 | scores = self.scores[:, k] 118 | targets = self.targets[:, k] 119 | # compute average precision 120 | ap[k] = AveragePrecisionMeter.average_precision(scores, targets, self.difficult_examples) 121 | return ap 122 | 123 | @staticmethod 124 | def average_precision(output, target, difficult_examples=True): 125 | 126 | # sort examples 127 | sorted, indices = torch.sort(output, dim=0, descending=True) 128 | 129 | # Computes prec@i 130 | pos_count = 0. 131 | total_count = 0. 132 | precision_at_i = 0. 133 | for i in indices: 134 | label = target[i] 135 | if difficult_examples and label == 0: 136 | continue 137 | if label == 1: 138 | pos_count += 1 139 | total_count += 1 140 | if label == 1: 141 | precision_at_i += pos_count / total_count 142 | precision_at_i /= pos_count 143 | return precision_at_i 144 | 145 | def overall(self): 146 | if self.scores.numel() == 0: 147 | return 0 148 | scores = self.scores.cpu().numpy() 149 | targets = self.targets.cpu().numpy() 150 | targets[targets == -1] = 0 151 | return self.evaluation(scores, targets) 152 | 153 | def overall_topk(self, k): 154 | targets = self.targets.cpu().numpy() 155 | targets[targets == -1] = 0 156 | n, c = self.scores.size() 157 | scores = np.zeros((n, c)) - 1 158 | index = self.scores.topk(k, 1, True, True)[1].cpu().numpy() 159 | tmp = self.scores.cpu().numpy() 160 | for i in range(n): 161 | for ind in index[i]: 162 | scores[i, ind] = 1 if tmp[i, ind] >= 0 else -1 163 | return self.evaluation(scores, targets) 164 | 165 | 166 | def evaluation(self, scores_, targets_): 167 | n, n_class = scores_.shape 168 | Nc, Np, Ng = np.zeros(n_class), np.zeros(n_class), np.zeros(n_class) 169 | for k in range(n_class): 170 | scores = scores_[:, k] 171 | targets = targets_[:, k] 172 | targets[targets == -1] = 0 173 | Ng[k] = np.sum(targets == 1) 174 | Np[k] = np.sum(scores >= 0) 175 | Nc[k] = np.sum(targets * (scores >= 0)) 176 | Np[Np == 0] = 1 177 | OP = np.sum(Nc) / np.sum(Np) 178 | OR = np.sum(Nc) / np.sum(Ng) 179 | OF1 = (2 * OP * OR) / (OP + OR) 180 | 181 | CP = np.sum(Nc / Np) / n_class 182 | CR = np.sum(Nc / Ng) / n_class 183 | CF1 = (2 * CP * CR) / (CP + CR) 184 | return OP, OR, OF1, CP, CR, CF1 -------------------------------------------------------------------------------- /tools.py: -------------------------------------------------------------------------------- 1 | #for T estimator and DualT estimator 2 | import numpy as np 3 | 4 | def norm(T): 5 | row_sum = np.sum(T, 1) 6 | T_norm = T / row_sum 7 | return T_norm 8 | 9 | def error(T, T_true): 10 | error = np.sum(np.abs(T-T_true)) / np.sum(np.abs(T_true)) 11 | return error 12 | 13 | def fit(X, num_classes, filter_outlier=False): 14 | # number of classes 15 | c = num_classes 16 | T = np.empty((c, c)) 17 | eta_corr = X 18 | for i in np.arange(c): 19 | if not filter_outlier: 20 | idx_best = np.argmax(eta_corr[:, i]) 21 | else: 22 | eta_thresh = np.percentile(eta_corr[:, i], 97,interpolation='higher') 23 | robust_eta = eta_corr[:, i] 24 | robust_eta[robust_eta >= eta_thresh] = 0.0 25 | idx_best = np.argmax(robust_eta) 26 | for j in np.arange(c): 27 | T[i, j] = eta_corr[idx_best, j] 28 | return T -------------------------------------------------------------------------------- /util.py: -------------------------------------------------------------------------------- 1 | #for data augmentation and download datasets 2 | import math 3 | from urllib.request import urlretrieve 4 | import torch 5 | from PIL import Image 6 | from tqdm import tqdm 7 | import numpy as np 8 | import random 9 | import torch.nn.functional as F 10 | 11 | class Warp(object): 12 | def __init__(self, size, interpolation=Image.BILINEAR): 13 | self.size = int(size) 14 | self.interpolation = interpolation 15 | 16 | def __call__(self, img): 17 | return img.resize((self.size, self.size), self.interpolation) 18 | 19 | def __str__(self): 20 | return self.__class__.__name__ + ' (size={size}, interpolation={interpolation})'.format(size=self.size, 21 | interpolation=self.interpolation) 22 | 23 | class MultiScaleCrop(object): 24 | 25 | def __init__(self, input_size, scales=None, max_distort=1, fix_crop=True, more_fix_crop=True): 26 | self.scales = scales if scales is not None else [1, 875, .75, .66] 27 | self.max_distort = max_distort 28 | self.fix_crop = fix_crop 29 | self.more_fix_crop = more_fix_crop 30 | self.input_size = input_size if not isinstance(input_size, int) else [input_size, input_size] 31 | self.interpolation = Image.BILINEAR 32 | 33 | def __call__(self, img): 34 | im_size = img.size 35 | crop_w, crop_h, offset_w, offset_h = self._sample_crop_size(im_size) 36 | crop_img_group = img.crop((offset_w, offset_h, offset_w + crop_w, offset_h + crop_h)) 37 | ret_img_group = crop_img_group.resize((self.input_size[0], self.input_size[1]), self.interpolation) 38 | return ret_img_group 39 | 40 | def _sample_crop_size(self, im_size): 41 | image_w, image_h = im_size[0], im_size[1] 42 | 43 | # find a crop size 44 | base_size = min(image_w, image_h) 45 | crop_sizes = [int(base_size * x) for x in self.scales] 46 | crop_h = [self.input_size[1] if abs(x - self.input_size[1]) < 3 else x for x in crop_sizes] 47 | crop_w = [self.input_size[0] if abs(x - self.input_size[0]) < 3 else x for x in crop_sizes] 48 | 49 | pairs = [] 50 | for i, h in enumerate(crop_h): 51 | for j, w in enumerate(crop_w): 52 | if abs(i - j) <= self.max_distort: 53 | pairs.append((w, h)) 54 | 55 | crop_pair = random.choice(pairs) 56 | if not self.fix_crop: 57 | w_offset = random.randint(0, image_w - crop_pair[0]) 58 | h_offset = random.randint(0, image_h - crop_pair[1]) 59 | else: 60 | w_offset, h_offset = self._sample_fix_offset(image_w, image_h, crop_pair[0], crop_pair[1]) 61 | 62 | return crop_pair[0], crop_pair[1], w_offset, h_offset 63 | 64 | def _sample_fix_offset(self, image_w, image_h, crop_w, crop_h): 65 | offsets = self.fill_fix_offset(self.more_fix_crop, image_w, image_h, crop_w, crop_h) 66 | return random.choice(offsets) 67 | 68 | @staticmethod 69 | def fill_fix_offset(more_fix_crop, image_w, image_h, crop_w, crop_h): 70 | w_step = (image_w - crop_w) // 4 71 | h_step = (image_h - crop_h) // 4 72 | 73 | ret = list() 74 | ret.append((0, 0)) # upper left 75 | ret.append((4 * w_step, 0)) # upper right 76 | ret.append((0, 4 * h_step)) # lower left 77 | ret.append((4 * w_step, 4 * h_step)) # lower right 78 | ret.append((2 * w_step, 2 * h_step)) # center 79 | 80 | if more_fix_crop: 81 | ret.append((0, 2 * h_step)) # center left 82 | ret.append((4 * w_step, 2 * h_step)) # center right 83 | ret.append((2 * w_step, 4 * h_step)) # lower center 84 | ret.append((2 * w_step, 0 * h_step)) # upper center 85 | 86 | ret.append((1 * w_step, 1 * h_step)) # upper left quarter 87 | ret.append((3 * w_step, 1 * h_step)) # upper right quarter 88 | ret.append((1 * w_step, 3 * h_step)) # lower left quarter 89 | ret.append((3 * w_step, 3 * h_step)) # lower righ quarter 90 | 91 | return ret 92 | 93 | 94 | def __str__(self): 95 | return self.__class__.__name__ 96 | 97 | def download_url(url, destination=None, progress_bar=True): 98 | """Download a URL to a local file. 99 | 100 | Parameters 101 | ---------- 102 | url : str 103 | The URL to download. 104 | destination : str, None 105 | The destination of the file. If None is given the file is saved to a temporary directory. 106 | progress_bar : bool 107 | Whether to show a command-line progress bar while downloading. 108 | 109 | Returns 110 | ------- 111 | filename : str 112 | The location of the downloaded file. 113 | 114 | Notes 115 | ----- 116 | Progress bar use/example adapted from tqdm documentation: https://github.com/tqdm/tqdm 117 | """ 118 | 119 | def my_hook(t): 120 | last_b = [0] 121 | 122 | def inner(b=1, bsize=1, tsize=None): 123 | if tsize is not None: 124 | t.total = tsize 125 | if b > 0: 126 | t.update((b - last_b[0]) * bsize) 127 | last_b[0] = b 128 | 129 | return inner 130 | 131 | if progress_bar: 132 | with tqdm(unit='B', unit_scale=True, miniters=1, desc=url.split('/')[-1]) as t: 133 | filename, _ = urlretrieve(url, filename=destination, reporthook=my_hook(t)) 134 | else: 135 | filename, _ = urlretrieve(url, filename=destination) -------------------------------------------------------------------------------- /voc.py: -------------------------------------------------------------------------------- 1 | #for voc2007/2012 dataset load 2 | import csv 3 | import os 4 | import os.path 5 | import tarfile 6 | from urllib.parse import urlparse 7 | import sys 8 | import numpy as np 9 | import torch 10 | import torch.utils.data as data 11 | from PIL import Image 12 | import pickle 13 | import util 14 | from util import * 15 | 16 | object_categories = ['aeroplane', 'bicycle', 'bird', 'boat', 17 | 'bottle', 'bus', 'car', 'cat', 'chair', 18 | 'cow', 'diningtable', 'dog', 'horse', 19 | 'motorbike', 'person', 'pottedplant', 20 | 'sheep', 'sofa', 'train', 'tvmonitor'] 21 | 22 | urls = { 23 | 'devkit': 'http://host.robots.ox.ac.uk/pascal/VOC/voc2012/VOCdevkit_18-May-2011.tar', 24 | 'trainval_2007': 'http://host.robots.ox.ac.uk/pascal/VOC/voc2007/VOCtrainval_06-Nov-2007.tar', 25 | 'test_images_2007': 'http://host.robots.ox.ac.uk/pascal/VOC/voc2007/VOCtest_06-Nov-2007.tar', 26 | 'test_anno_2007': 'http://host.robots.ox.ac.uk/pascal/VOC/voc2007/VOCtestnoimgs_06-Nov-2007.tar', 27 | } 28 | 29 | voc12urls = { 30 | 'devkit': 'http://host.robots.ox.ac.uk/pascal/VOC/voc2012/VOCdevkit_18-May-2011.tar', 31 | 'trainval_2012': 'http://host.robots.ox.ac.uk/pascal/VOC/voc2012/VOCtrainval_11-May-2012.tar', 32 | 'test_images_2012': 'http://pjreddie.com/media/files/VOC2012test.tar', 33 | } 34 | 35 | def read_image_label(file): 36 | print('[dataset] read ' + file) 37 | data = dict() 38 | with open(file, 'r') as f: 39 | for line in f: 40 | tmp = line.split(' ') 41 | name = tmp[0] 42 | label = int(tmp[-1]) 43 | data[name] = label 44 | # data.append([name, label]) 45 | # print('%s %d' % (name, label)) 46 | return data 47 | 48 | 49 | def read_object_labels(root, dataset, set): 50 | path_labels = os.path.join(root, 'VOCdevkit', dataset, 'ImageSets', 'Main') 51 | labeled_data = dict() 52 | num_classes = len(object_categories) 53 | 54 | for i in range(num_classes): 55 | file = os.path.join(path_labels, object_categories[i] + '_' + set + '.txt') 56 | data = read_image_label(file) 57 | 58 | if i == 0: 59 | for (name, label) in data.items(): 60 | labels = np.zeros(num_classes) 61 | labels[i] = label 62 | labeled_data[name] = labels 63 | else: 64 | for (name, label) in data.items(): 65 | labeled_data[name][i] = label 66 | 67 | return labeled_data 68 | 69 | 70 | def write_object_labels_csv(file, labeled_data): 71 | # write a csv file 72 | print('[dataset] write file %s' % file) 73 | with open(file, 'w') as csvfile: 74 | fieldnames = ['name'] 75 | fieldnames.extend(object_categories) 76 | writer = csv.DictWriter(csvfile, fieldnames=fieldnames) 77 | 78 | writer.writeheader() 79 | for (name, labels) in labeled_data.items(): 80 | example = {'name': name} 81 | for i in range(20): 82 | example[fieldnames[i + 1]] = int(labels[i]) 83 | writer.writerow(example) 84 | 85 | csvfile.close() 86 | 87 | 88 | def read_object_labels_csv(file, header=True): 89 | images = [] 90 | labels_list = [] 91 | num_categories = 0 92 | print('[dataset] read', file) 93 | with open(file, 'r') as f: 94 | reader = csv.reader(f) 95 | rownum = 0 96 | for row in reader: 97 | if header and rownum == 0: 98 | header = row 99 | else: 100 | if num_categories == 0: 101 | num_categories = len(row) - 1 102 | name = row[0] 103 | labels = (np.asarray(row[1:num_categories + 1])).astype(np.float32) 104 | labels = torch.from_numpy(labels) 105 | #item = (name, labels) 106 | #images.append(item) 107 | images.append(name) 108 | labels_list.append(labels) 109 | rownum += 1 110 | return np.stack(images), np.stack(labels_list) 111 | 112 | 113 | def find_images_classification(root, dataset, set): 114 | path_labels = os.path.join(root, 'VOCdevkit', dataset, 'ImageSets', 'Main') 115 | images = [] 116 | file = os.path.join(path_labels, set + '.txt') 117 | with open(file, 'r') as f: 118 | for line in f: 119 | images.append(line) 120 | return images 121 | 122 | 123 | def download_voc2007(root): 124 | path_devkit = os.path.join(root, 'VOCdevkit') 125 | path_images = os.path.join(root, 'VOCdevkit', 'VOC2007', 'JPEGImages') 126 | tmpdir = os.path.join(root, 'tmp') 127 | 128 | # create directory 129 | if not os.path.exists(root): 130 | os.makedirs(root) 131 | 132 | if not os.path.exists(path_devkit): 133 | 134 | if not os.path.exists(tmpdir): 135 | os.makedirs(tmpdir) 136 | 137 | parts = urlparse(urls['devkit']) 138 | filename = os.path.basename(parts.path) 139 | cached_file = os.path.join(tmpdir, filename) 140 | 141 | if not os.path.exists(cached_file): 142 | print('Downloading: "{}" to {}\n'.format(urls['devkit'], cached_file)) 143 | util.download_url(urls['devkit'], cached_file) 144 | 145 | # extract file 146 | print('[dataset] Extracting tar file {file} to {path}'.format(file=cached_file, path=root)) 147 | cwd = os.getcwd() 148 | tar = tarfile.open(cached_file, "r") 149 | os.chdir(root) 150 | tar.extractall() 151 | tar.close() 152 | os.chdir(cwd) 153 | print('[dataset] Done!') 154 | 155 | # train/val images/annotations 156 | if not os.path.exists(path_images): 157 | 158 | # download train/val images/annotations 159 | parts = urlparse(urls['trainval_2007']) 160 | filename = os.path.basename(parts.path) 161 | cached_file = os.path.join(tmpdir, filename) 162 | 163 | if not os.path.exists(cached_file): 164 | print('Downloading: "{}" to {}\n'.format(urls['trainval_2007'], cached_file)) 165 | util.download_url(urls['trainval_2007'], cached_file) 166 | 167 | # extract file 168 | print('[dataset] Extracting tar file {file} to {path}'.format(file=cached_file, path=root)) 169 | cwd = os.getcwd() 170 | tar = tarfile.open(cached_file, "r") 171 | os.chdir(root) 172 | tar.extractall() 173 | tar.close() 174 | os.chdir(cwd) 175 | print('[dataset] Done!') 176 | 177 | # test annotations 178 | test_anno = os.path.join(path_devkit, 'VOC2007/ImageSets/Main/aeroplane_test.txt') 179 | if not os.path.exists(test_anno): 180 | 181 | # download test annotations 182 | parts = urlparse(urls['test_anno_2007']) 183 | filename = os.path.basename(parts.path) 184 | cached_file = os.path.join(tmpdir, filename) 185 | 186 | if not os.path.exists(cached_file): 187 | print('Downloading: "{}" to {}\n'.format(urls['test_anno_2007'], cached_file)) 188 | util.download_url(urls['test_anno_2007'], cached_file) 189 | 190 | # extract file 191 | print('[dataset] Extracting tar file {file} to {path}'.format(file=cached_file, path=root)) 192 | cwd = os.getcwd() 193 | tar = tarfile.open(cached_file, "r") 194 | os.chdir(root) 195 | tar.extractall() 196 | tar.close() 197 | os.chdir(cwd) 198 | print('[dataset] Done!') 199 | 200 | # test images 201 | test_image = os.path.join(path_devkit, 'VOC2007/JPEGImages/000001.jpg') 202 | if not os.path.exists(test_image): 203 | 204 | # download test images 205 | parts = urlparse(urls['test_images_2007']) 206 | filename = os.path.basename(parts.path) 207 | cached_file = os.path.join(tmpdir, filename) 208 | 209 | if not os.path.exists(cached_file): 210 | print('Downloading: "{}" to {}\n'.format(urls['test_images_2007'], cached_file)) 211 | util.download_url(urls['test_images_2007'], cached_file) 212 | 213 | # extract file 214 | print('[dataset] Extracting tar file {file} to {path}'.format(file=cached_file, path=root)) 215 | cwd = os.getcwd() 216 | tar = tarfile.open(cached_file, "r") 217 | os.chdir(root) 218 | tar.extractall() 219 | tar.close() 220 | os.chdir(cwd) 221 | print('[dataset] Done!') 222 | 223 | 224 | class Voc2007Classification(data.Dataset): 225 | def __init__(self, root, set_name, transform=None, target_transform=None,noise_rate=[0,0],random_seed=1): 226 | self.root = root 227 | self.path_devkit = os.path.join(root, 'VOCdevkit') 228 | self.path_images = os.path.join(root, 'VOCdevkit', 'VOC2007', 'JPEGImages') 229 | if(set_name=='train' or set_name=='val' or set_name=='trainval'): 230 | set = 'trainval' 231 | else: 232 | set = set_name 233 | self.set = set 234 | self.transform = transform 235 | self.target_transform = target_transform 236 | 237 | # download dataset 238 | download_voc2007(self.root) 239 | 240 | # define path of csv file 241 | path_csv = os.path.join(self.root, 'files', 'VOC2007') 242 | # define filename of csv file 243 | file_csv = os.path.join(path_csv, 'classification_' + set + '.csv') 244 | 245 | # create the csv file if necessary 246 | if not os.path.exists(file_csv): 247 | if not os.path.exists(path_csv): # create dir if necessary 248 | os.makedirs(path_csv) 249 | # generate csv file 250 | labeled_data = read_object_labels(self.root, 'VOC2007', self.set) 251 | # write csv file 252 | write_object_labels_csv(file_csv, labeled_data) 253 | 254 | self.classes = object_categories 255 | self.images, self.true_labels = read_object_labels_csv(file_csv) 256 | 257 | self.true_labels[self.true_labels==0]=1 258 | 259 | if(noise_rate[0]==0 and noise_rate[1]==0): 260 | self.labels=self.true_labels 261 | else: 262 | self.labels= generate_noisy_labels(self.true_labels , noise_rate,random_seed) 263 | 264 | if(set_name=='train'): 265 | self.images, self.labels, self.true_labels , _, _, _=dataset_split(self.images,self.labels,self.true_labels, num_classes=len(self.classes)) 266 | elif(set_name=='val'): 267 | _, _, _, self.images, self.labels, self.true_labels=dataset_split(self.images,self.labels,self.true_labels, num_classes=len(self.classes)) 268 | print('[dataset] VOC 2007 classification set=%s number of classes=%d number of images=%d' % ( 269 | set_name, len(self.classes), len(self.images))) 270 | 271 | def __getitem__(self, index): 272 | path, target = self.images[index], self.labels[index] 273 | img = Image.open(os.path.join(self.path_images, path + '.jpg')).convert('RGB') 274 | #img = np.asarray(img,dtype="float32") 275 | if self.transform is not None: 276 | img = self.transform(img) 277 | if self.target_transform is not None: 278 | target = self.target_transform(target) 279 | 280 | return img, target 281 | 282 | def __len__(self): 283 | return len(self.images) 284 | 285 | def get_number_classes(self): 286 | return len(self.classes) 287 | 288 | 289 | def generate_noisy_labels(labels, noise_rate,random_seed): 290 | 291 | N, nc = labels.shape 292 | np.random.seed(random_seed) 293 | rand_mat = np.random.rand(N,nc) 294 | mask = np.zeros((N,nc), dtype = np.float) 295 | for j in range(nc): 296 | yj = labels[:,j] 297 | mask[yj!=1,j] = rand_mat[yj!=1,j]