├── FLEM-Appendix.pdf ├── FLEM-D VOC07.tar ├── README.md ├── Test_flem.py ├── datasets └── datasets.py ├── methods ├── flem.py └── template.py ├── run_flem.py └── utils ├── metrics.py └── utils.py /FLEM-Appendix.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ailearn-ml/FLEM/1fa24007282da58b6327d37427e6743bb48faf00/FLEM-Appendix.pdf -------------------------------------------------------------------------------- /FLEM-D VOC07.tar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ailearn-ml/FLEM/1fa24007282da58b6327d37427e6743bb48faf00/FLEM-D VOC07.tar -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Fusion Label Enhancement for Multi-Label Learning 2 | 3 | Code for "Fusion Label Enhancement for Multi-Label Learning" in IJCAI-ECAI 2022. 4 | 5 | If you use the code in this repo for your work, please cite the following bib entries: 6 | 7 | ``` 8 | @inproceedings{Zhao2022FLEM, 9 | author = {Xingyu Zhao and 10 | Yuexuan An and 11 | Ning Xu and 12 | Xin Geng}, 13 | editor = {Luc De Raedt}, 14 | title = {Fusion Label Enhancement for Multi-Label Learning}, 15 | booktitle = {Proceedings of the Thirty-First International Joint Conference on 16 | Artificial Intelligence, {IJCAI} 2022, Vienna, Austria, 23-29 July 17 | 2022}, 18 | pages = {3773--3779}, 19 | publisher = {ijcai.org}, 20 | year = {2022}, 21 | url = {https://doi.org/10.24963/ijcai.2022/524}, 22 | doi = {10.24963/ijcai.2022/524}, 23 | } 24 | ``` 25 | 26 | ## Enviroment 27 | 28 | Python >= 3.8.0 29 | 30 | Pytorch >= 1.10.0 31 | 32 | ## Getting started 33 | 34 | - Create directory `./datasets/data` 35 | - Change directory to `./datasets/data` 36 | - Download [datasets](https://drive.google.com/drive/folders/1v2XAZX4d7GCDct5VzFPOxtcM4_2z2dVO?usp=sharing) 37 | 38 | 39 | ## Test the pre-trained model 40 | 41 | ``` 42 | python Test_flem.py 43 | ``` 44 | 45 | ## Running 46 | 47 | ``` 48 | python run_flem.py 49 | ``` 50 | 51 | 52 | ## Acknowledgment 53 | 54 | Our project references the dataset in the following paper. 55 | 56 | Mark Everingham, S. M. Ali Eslami, Luc Van Gool, Christopher K. I. Williams, John M. Winn, Andrew Zisserman: 57 | The Pascal Visual Object Classes Challenge: A Retrospective. International Journal of Computer Vision. 2015, 111(1): 98-136. 58 | 59 | -------------------------------------------------------------------------------- /Test_flem.py: -------------------------------------------------------------------------------- 1 | import torch.nn 2 | from utils.utils import set_seed, Logger 3 | from methods.flem import FLEM 4 | from datasets.datasets import MLLFeatureDataset 5 | from torch.utils.data import DataLoader 6 | from utils.metrics import evaluation 7 | 8 | 9 | def get_model(): 10 | model = FLEM(num_classes=train_dataset.labels.shape[1], 11 | loss_func=torch.nn.BCEWithLogitsLoss(), 12 | num_feature=num_feature, 13 | alpha=alpha, 14 | beta=beta, 15 | method=method, 16 | max_epoch=max_epoch, 17 | optimizer=optimizer, 18 | lr=lr, 19 | adjust_lr=False, 20 | verbose=verbose, 21 | device=device) 22 | return model 23 | 24 | 25 | def get_dataset(): 26 | feature_extractor = 'imagenet_resnet50' 27 | train_dataset = MLLFeatureDataset(dataset_name, phase='train', feature_extractor=feature_extractor) 28 | val_dataset = MLLFeatureDataset(dataset_name, phase='val', feature_extractor=feature_extractor) 29 | test_dataset = MLLFeatureDataset(dataset_name, phase='test', feature_extractor=feature_extractor) 30 | return train_dataset, val_dataset, test_dataset 31 | 32 | 33 | optimizer = 'adamw' 34 | lr = 0.001 35 | batch_size = 64 36 | num_workers = 0 37 | max_epoch = 30 38 | loss_type = 'bce' 39 | device = 'cpu' 40 | verbose = False 41 | seed = 0 42 | set_seed(seed) 43 | 44 | dataset_name = 'VOC2007' 45 | train_dataset, val_dataset, test_dataset = get_dataset() 46 | train_loader = DataLoader(train_dataset, batch_size=batch_size, shuffle=True, 47 | num_workers=num_workers) 48 | val_loader = DataLoader(val_dataset, batch_size=batch_size, shuffle=False, num_workers=num_workers) 49 | test_loader = DataLoader(test_dataset, batch_size=batch_size, shuffle=False, 50 | num_workers=num_workers) 51 | num_feature = train_dataset.features.shape[1] 52 | alpha, beta, method = 0.001, 0.001, 'ld' 53 | 54 | print('FLEM-D Start Testing!') 55 | model = get_model() 56 | model.load('.', epoch='FLEM-D VOC07') 57 | y_pred, y_test = model.get_result(test_loader) 58 | result = evaluation(y_test, y_pred) 59 | for key in result.keys(): 60 | print(f'{key}: {result[key]}') 61 | -------------------------------------------------------------------------------- /datasets/datasets.py: -------------------------------------------------------------------------------- 1 | import numpy as np 2 | import os 3 | from torch.utils.data import Dataset 4 | import torch 5 | from scipy.sparse import load_npz 6 | 7 | bath_path = os.path.dirname(__file__) 8 | 9 | 10 | def get_metadata(dataset_name): 11 | if dataset_name == 'VOC2007': 12 | meta = dict( 13 | num_classes=20, 14 | ) 15 | else: 16 | raise ValueError('Dataset name Error!') 17 | return meta 18 | 19 | 20 | class MLLFeatureDataset(Dataset): 21 | def __init__(self, dataset_name, path=os.path.join(bath_path, 'data'), 22 | phase='train', feature_extractor='imagenet_resnet50'): 23 | self.dataset_name = dataset_name 24 | self.meta = get_metadata(dataset_name) 25 | self.labels = load_npz(os.path.join(path, f'{dataset_name}_{phase}_labels.npz')).toarray() 26 | self.features = np.load( 27 | os.path.join(path, f'{dataset_name}_{phase}_features_{feature_extractor}.npy')) 28 | self.phase = phase 29 | 30 | def __len__(self): 31 | return self.features.shape[0] 32 | 33 | def __getitem__(self, index): 34 | sample = {} 35 | sample['image'] = torch.Tensor(self.features[index]) 36 | sample['labels'] = torch.Tensor(self.labels[index]) 37 | sample['idx'] = index 38 | return sample 39 | -------------------------------------------------------------------------------- /methods/flem.py: -------------------------------------------------------------------------------- 1 | from torch import nn 2 | from methods.template import MLLTemplate 3 | import torch 4 | from utils.metrics import cosine_similarity 5 | 6 | 7 | def kaiming_normal_init_net(net): 8 | for name, param in net.named_parameters(): 9 | if 'weight' in name and len(param.shape) == 2: 10 | nn.init.kaiming_normal_(param) 11 | elif 'bias' in name: 12 | nn.init.zeros_(param) 13 | 14 | 15 | class LE(nn.Module): 16 | def __init__(self, num_feature, num_classes, hidden_dim=128): 17 | super(LE, self).__init__() 18 | self.fe1 = nn.Sequential( 19 | nn.Linear(num_feature, hidden_dim), 20 | nn.LeakyReLU(), 21 | nn.BatchNorm1d(hidden_dim), 22 | ) 23 | self.fe2 = nn.Linear(hidden_dim, hidden_dim) 24 | self.le1 = nn.Sequential( 25 | nn.Linear(num_classes, hidden_dim), 26 | nn.LeakyReLU(), 27 | nn.BatchNorm1d(hidden_dim), 28 | ) 29 | self.le2 = nn.Linear(hidden_dim, hidden_dim) 30 | self.de1 = nn.Sequential( 31 | nn.Linear(2 * hidden_dim, num_classes), 32 | nn.LeakyReLU(), 33 | nn.BatchNorm1d(num_classes), 34 | ) 35 | self.de2 = nn.Linear(num_classes, num_classes) 36 | 37 | def forward(self, x, y): 38 | x = self.fe1(x) + self.fe2(self.fe1(x)) 39 | y = self.le1(y) + self.le2(self.le1(y)) 40 | d = torch.cat([x, y], dim=-1) 41 | d = self.de1(d) + self.de2(self.de1(d)) 42 | return d 43 | 44 | 45 | class FLEM(MLLTemplate): 46 | def __init__(self, num_classes, num_feature, loss_func, method='ld', alpha=0.01, beta=0.01, 47 | max_epoch=None, optimizer='sgd', lr=0.1, momentum=0.9, weight_decay=1e-4, 48 | threshold=0, adjust_lr=False, verbose=False, device='cuda:0'): 49 | super(FLEM, self).__init__(num_classes=num_classes, num_feature=num_feature, 50 | adjust_lr=adjust_lr, max_epoch=max_epoch, verbose=verbose, device=device) 51 | self.lr = lr 52 | self.lr_now = lr 53 | self.method = method 54 | self.alpha = alpha 55 | self.beta = beta 56 | self.threshold = threshold 57 | self.loss_func = loss_func 58 | self.classifier = nn.Linear(self.num_feature, num_classes) 59 | self.le = LE(num_feature, num_classes) 60 | kaiming_normal_init_net(self.classifier) 61 | kaiming_normal_init_net(self.le) 62 | 63 | params_decay = (p for name, p in self.named_parameters() if 'bias' not in name) 64 | params_no_decay = (p for name, p in self.named_parameters() if 'bias' in name) 65 | if optimizer == 'adam': 66 | self.optimizer = torch.optim.Adam( 67 | [{'params': params_decay, 'lr': lr, 'weight_decay': weight_decay}, 68 | {'params': params_no_decay, 'lr': lr}], amsgrad=True) 69 | if optimizer == 'adamw': 70 | self.optimizer = torch.optim.AdamW( 71 | [{'params': params_decay, 'lr': lr, 'weight_decay': weight_decay}, 72 | {'params': params_no_decay, 'lr': lr}], amsgrad=True) 73 | else: 74 | self.optimizer = torch.optim.SGD( 75 | [{'params': params_decay, 'lr': lr, 'momentum': momentum, 'weight_decay': weight_decay}, 76 | {'params': params_no_decay, 'lr': lr, 'momentum': momentum}]) 77 | self.to(self.device) 78 | 79 | def set_forward(self, x): 80 | x = self.classifier(x) 81 | return x 82 | 83 | def set_forward_loss(self, pred, le, x=None, y=None): 84 | assert y is not None 85 | loss_pred_cls = self.loss_func(pred, y) 86 | loss_pred_ld = nn.CrossEntropyLoss()(pred, torch.softmax(le.detach(), dim=1)) 87 | loss_le_cls = self.loss_enhanced(le, pred, y) 88 | if self.method == 'ld': 89 | loss_le_spec = nn.CrossEntropyLoss()(le, torch.softmax(pred.detach(), dim=1)) 90 | elif self.method == 'threshold': 91 | loss_le_spec = 0 92 | for i in range(pred.shape[0]): 93 | neg_index = y[i] == 0 94 | pos_index = y[i] == 1 95 | if torch.sum(pos_index) == 0: continue 96 | loss_le_spec += torch.maximum(le[i][neg_index][torch.argmax(le[i][neg_index])] - le[i][pos_index][ 97 | torch.argmin(le[i][pos_index])] + self.threshold, torch.tensor([0]).to(self.device)) 98 | elif self.method == 'sim': 99 | with torch.no_grad(): 100 | sim_x = cosine_similarity(x, x).detach() # [n,n] 101 | sim_y = cosine_similarity(le, le) 102 | loss_le_spec = nn.MSELoss()(sim_y, sim_x) 103 | else: 104 | raise ValueError('Wrong E2ELE method!') 105 | loss_pred = self.alpha * loss_pred_ld + (1 - self.alpha) * loss_pred_cls 106 | loss_le = self.beta * loss_le_spec + (1 - self.beta) * loss_le_cls 107 | return loss_le + loss_pred 108 | 109 | def loss_enhanced(self, pred, teach, y): 110 | eps = 1e-7 111 | gamma1 = 0 112 | gamma2 = 1 113 | x_sigmoid = torch.sigmoid(pred) 114 | los_pos = y * torch.log(x_sigmoid.clamp(min=eps, max=1 - eps)) 115 | los_neg = (1 - y) * torch.log((1 - x_sigmoid).clamp(min=eps, max=1 - eps)) 116 | loss = los_pos + los_neg 117 | with torch.no_grad(): 118 | teach_sigmoid = torch.sigmoid(teach) 119 | teach_pos = teach_sigmoid 120 | teach_neg = 1 - teach_sigmoid 121 | pt0 = teach_pos * y 122 | pt1 = teach_neg * (1 - y) # pt = p if t > 0 else 1-p 123 | pt = pt0 + pt1 124 | one_sided_gamma = gamma1 * y + gamma2 * (1 - y) 125 | one_sided_w = torch.pow(1 - pt, one_sided_gamma) 126 | loss *= one_sided_w 127 | return -loss.mean() 128 | 129 | def train_loop(self, epoch, train_loader, log=None, print_freq=10): 130 | self.train() 131 | if not log: 132 | log = print 133 | self.epoch = epoch 134 | if self.adjust_lr: 135 | self.adjust_learning_rate() 136 | total_loss = 0 137 | num_samples = 0 138 | for i, batch in enumerate(train_loader): 139 | x = batch['image'].to(self.device) 140 | y = batch['labels'].to(self.device) 141 | pred = self.classifier(x) 142 | le = self.le(x.detach(), y) 143 | self.optimizer.zero_grad() 144 | loss = self.set_forward_loss(pred, le, x, y) 145 | loss.backward() 146 | self.clip_gradient() 147 | self.optimizer.step() 148 | total_loss += loss.item() 149 | num_samples += x.shape[0] 150 | if self.verbose and (i % print_freq) == 0: 151 | log('Epoch %d | Batch %d/%d | Loss %f' % ( 152 | epoch, i, len(train_loader), total_loss / num_samples)) 153 | avg_loss = total_loss / num_samples 154 | return avg_loss 155 | -------------------------------------------------------------------------------- /methods/template.py: -------------------------------------------------------------------------------- 1 | import torch 2 | import torch.nn as nn 3 | import numpy as np 4 | from abc import abstractmethod 5 | import os 6 | from collections import deque 7 | from utils.metrics import evaluation 8 | 9 | 10 | class MLLTemplate(nn.Module): 11 | 12 | def __init__(self, num_classes, num_feature=None, adjust_lr=False, gradient_clip_value=5.0, 13 | max_epoch=None, verbose=False, device='cuda:0'): 14 | super(MLLTemplate, self).__init__() 15 | self.num_classes = num_classes 16 | self.num_feature = num_feature 17 | self.adjust_lr = adjust_lr 18 | self.epoch = 0 19 | self.gradient_clip_value, self.gradient_norm_queue = gradient_clip_value, deque([np.inf], maxlen=5) 20 | self.max_epoch = max_epoch 21 | self.verbose = verbose 22 | self.device = device 23 | 24 | @abstractmethod 25 | def set_forward(self, x): 26 | # x -> predicted score 27 | pass 28 | 29 | @abstractmethod 30 | def set_forward_loss(self, pred, y): 31 | # batch -> loss value 32 | pass 33 | 34 | def train_loop(self, epoch, train_loader, log=None, print_freq=10): 35 | self.train() 36 | if not log: 37 | log = print 38 | self.epoch = epoch 39 | if self.adjust_lr: 40 | self.adjust_learning_rate() 41 | total_map = 0 42 | total_loss = 0 43 | num_samples = 0 44 | for i, batch in enumerate(train_loader): 45 | x = batch['image'].cuda() 46 | y = batch['labels'].cuda() 47 | pred = self.set_forward(x) 48 | self.optimizer.zero_grad() 49 | loss = self.set_forward_loss(pred, y) 50 | loss.backward() 51 | self.clip_gradient() 52 | self.optimizer.step() 53 | total_loss += loss.item() 54 | num_samples += x.shape[0] 55 | if self.verbose and (i % print_freq) == 0: 56 | log('Epoch %d | Batch %d/%d | Loss %f' % ( 57 | epoch, i, len(train_loader), total_loss / num_samples)) 58 | avg_loss = total_loss / num_samples 59 | return avg_loss 60 | 61 | def test_loop(self, test_loader, return_loss=False): 62 | self.eval() 63 | preds = [] 64 | ys = [] 65 | if return_loss: total_loss = 0 66 | with torch.no_grad(): 67 | for i, batch in enumerate(test_loader): 68 | x = batch['image'].to(self.device) 69 | y = batch['labels'] 70 | pred = self.set_forward(x) 71 | if return_loss: 72 | loss = self.set_forward_loss(pred, y.cuda(), ) 73 | total_loss += loss.item() 74 | pred = pred.detach().cpu().numpy() 75 | preds.extend(pred) 76 | ys.extend(y.numpy()) 77 | y_pred = np.array(preds) 78 | y_test = np.array(ys) 79 | result = evaluation(y_test, y_pred) 80 | if return_loss: 81 | return result, total_loss / y_pred.shape[0] 82 | else: 83 | return result 84 | 85 | def save(self, path, epoch=None, save_optimizer=False): 86 | os.makedirs(path, exist_ok=True) 87 | if type(epoch) is str: 88 | save_path = os.path.join(path, '%s.tar' % epoch) 89 | elif epoch is None: 90 | save_path = os.path.join(path, 'model.tar') 91 | else: 92 | save_path = os.path.join(path, '%d.tar' % epoch) 93 | while True: 94 | try: 95 | if not save_optimizer: 96 | torch.save({'model': self.state_dict(), }, save_path) 97 | else: 98 | torch.save({'model': self.state_dict(), 99 | 'optimizer': self.optimizer.state_dict(), }, save_path) 100 | return 101 | except: 102 | pass 103 | 104 | def load(self, path, epoch=None, load_optimizer=False): 105 | if type(epoch) is str: 106 | load_path = os.path.join(path, '%s.tar' % epoch) 107 | else: 108 | if epoch is None: 109 | files = os.listdir(path) 110 | files = np.array(list(map(lambda x: int(x.replace('.tar', '')), files))) 111 | epoch = np.max(files) 112 | load_path = os.path.join(path, '%d.tar' % epoch) 113 | tmp = torch.load(load_path, map_location=self.device) 114 | self.load_state_dict(tmp['model']) 115 | if load_optimizer: 116 | self.optimizer.load_state_dict(tmp['optimizer']) 117 | 118 | def clip_gradient(self): 119 | if self.gradient_clip_value is not None: 120 | max_norm = max(self.gradient_norm_queue) 121 | total_norm = torch.nn.utils.clip_grad_norm_(self.parameters(), max_norm * self.gradient_clip_value) 122 | self.gradient_norm_queue.append(min(total_norm, max_norm * 2.0, 1.0)) 123 | 124 | def get_result(self, test_loader): 125 | self.eval() 126 | preds = [] 127 | ys = [] 128 | with torch.no_grad(): 129 | for i, batch in enumerate(test_loader): 130 | x = batch['image'].to(self.device) 131 | y = batch['labels'] 132 | pred = self.set_forward(x).detach().cpu().numpy() 133 | preds.extend(pred) 134 | ys.extend(y.numpy()) 135 | preds = np.array(preds) 136 | ys = np.array(ys) 137 | return preds, ys 138 | 139 | def adjust_learning_rate(self): 140 | epoch = self.epoch + 1 141 | if epoch <= 5: 142 | self.lr_now = self.lr * epoch / 5 143 | elif epoch >= int(self.max_epoch * 0.8): 144 | self.lr_now = self.lr * 0.01 145 | elif epoch > int(self.max_epoch * 0.6): 146 | self.lr_now = self.lr * 0.1 147 | else: 148 | self.lr_now = self.lr 149 | for param_group in self.optimizer.param_groups: 150 | param_group['lr'] = self.lr_now 151 | -------------------------------------------------------------------------------- /run_flem.py: -------------------------------------------------------------------------------- 1 | import os 2 | import torch.nn 3 | from utils.utils import set_seed, Logger 4 | from methods.flem import FLEM 5 | from datasets.datasets import MLLFeatureDataset 6 | from torch.utils.data import DataLoader 7 | from utils.metrics import evaluation 8 | 9 | 10 | def get_model(): 11 | model = FLEM(num_classes=train_dataset.labels.shape[1], 12 | loss_func=torch.nn.BCEWithLogitsLoss(), 13 | num_feature=num_feature, 14 | alpha=alpha, 15 | beta=beta, 16 | method=method, 17 | max_epoch=max_epoch, 18 | optimizer=optimizer, 19 | lr=lr, 20 | adjust_lr=False, 21 | verbose=verbose, 22 | device=device) 23 | return model 24 | 25 | 26 | def get_dataset(): 27 | feature_extractor = 'imagenet_resnet50' 28 | train_dataset = MLLFeatureDataset(dataset_name, phase='train', feature_extractor=feature_extractor) 29 | val_dataset = MLLFeatureDataset(dataset_name, phase='val', feature_extractor=feature_extractor) 30 | test_dataset = MLLFeatureDataset(dataset_name, phase='test', feature_extractor=feature_extractor) 31 | return train_dataset, val_dataset, test_dataset 32 | 33 | 34 | def _train(): 35 | print('Start Training!') 36 | model = get_model() 37 | best_Hamming = 1 38 | for epoch in range(max_epoch): 39 | avg_loss = model.train_loop(epoch, train_loader, log=print, print_freq=10) 40 | result = model.test_loop(val_loader, return_loss=False) 41 | Hamming = result['Hamming'] 42 | print('Epoch %d training done | Hamming: %.4f | Loss: %f | lr: %f' % (epoch, Hamming, avg_loss, model.lr_now)) 43 | if Hamming < best_Hamming: 44 | best_Hamming = Hamming 45 | model.save(train_dir, epoch='best_Hamming') 46 | if epoch == max_epoch - 1: 47 | model.save(train_dir, epoch=epoch) 48 | 49 | 50 | def _test(): 51 | print('Start Testing!') 52 | model = get_model() 53 | model.load(train_dir, epoch='best_Hamming') 54 | y_pred, y_test = model.get_result(test_loader) 55 | result = evaluation(y_test, y_pred) 56 | for key in result.keys(): 57 | print(f'{key}: {result[key]}') 58 | 59 | 60 | optimizer = 'adamw' 61 | lr = 0.001 62 | batch_size = 64 63 | num_workers = 0 64 | max_epoch = 30 65 | loss_type = 'bce' 66 | device = 'cpu' 67 | verbose = False 68 | seed = 0 69 | set_seed(seed) 70 | 71 | algorithm = 'flem' 72 | method = 'ld' 73 | alpha = 0.001 74 | beta = 0.001 75 | dataset_name = 'VOC2007' 76 | 77 | train_dataset, val_dataset, test_dataset = get_dataset() 78 | train_loader = DataLoader(train_dataset, batch_size=batch_size, shuffle=True, 79 | num_workers=num_workers) 80 | val_loader = DataLoader(val_dataset, batch_size=batch_size, shuffle=False, num_workers=num_workers) 81 | test_loader = DataLoader(test_dataset, batch_size=batch_size, shuffle=False, 82 | num_workers=num_workers) 83 | num_feature = train_dataset.features.shape[1] 84 | train_dir = os.path.join('save', dataset_name, f'{algorithm}_{method}_{alpha}_{beta}', 85 | f'{optimizer}_{lr}_{batch_size}_{max_epoch}_{loss_type}_{seed}', 86 | 'train') 87 | log_dir = os.path.join('save', dataset_name, f'{algorithm}_{method}_{alpha}_{beta}', 88 | f'{optimizer}_{lr}_{batch_size}_{max_epoch}_{loss_type}_{seed}', 'log') 89 | 90 | os.makedirs(train_dir, exist_ok=True) 91 | os.makedirs(log_dir, exist_ok=True) 92 | 93 | print = Logger(f'{log_dir}/log.txt').logger.warning 94 | print( 95 | f'{dataset_name}, {algorithm}, {method}, {alpha}, {beta}, {optimizer}, {lr}, {batch_size}, {max_epoch}, {seed}') 96 | 97 | if not os.path.exists(os.path.join(train_dir, f'{max_epoch - 1}.tar')): 98 | _train() 99 | _test() 100 | -------------------------------------------------------------------------------- /utils/metrics.py: -------------------------------------------------------------------------------- 1 | import numpy as np 2 | import torch 3 | from sklearn.metrics import average_precision_score, hamming_loss, label_ranking_loss, coverage_error 4 | 5 | 6 | def convert_torch_sparse_to_array(matrix): 7 | if matrix is not None: 8 | if 'sparse' in str(matrix): 9 | matrix = matrix.toarray() 10 | if type(matrix) == torch.Tensor: 11 | matrix = matrix.detach().cpu().numpy() 12 | return matrix 13 | 14 | 15 | def cosine_similarity(x, y): 16 | ''' 17 | Cosine Similarity of two tensors 18 | Args: 19 | x: torch.Tensor, m x d 20 | y: torch.Tensor, n x d 21 | Returns: 22 | result, m x n 23 | ''' 24 | assert x.size(1) == y.size(1) 25 | x = torch.nn.functional.normalize(x, dim=1) 26 | y = torch.nn.functional.normalize(y, dim=1) 27 | return x @ y.transpose(0, 1) 28 | 29 | 30 | def evaluation(y_true, y_prob, y_pred=None): 31 | y_true = convert_torch_sparse_to_array(y_true) 32 | y_prob = convert_torch_sparse_to_array(y_prob) 33 | y_pred = convert_torch_sparse_to_array(y_pred) 34 | 35 | if y_pred is None: 36 | if np.min(y_prob) < 0: 37 | y_pred = np.zeros_like(y_prob) 38 | y_pred[y_prob > 0] = 1 39 | else: 40 | y_pred = np.zeros_like(y_prob) 41 | y_pred[y_prob > 0.5] = 1 42 | class_freq = np.sum(y_true, axis=0) 43 | 44 | Hamming = hamming_loss(y_true, y_pred) 45 | Ranking = label_ranking_loss(y_true, y_prob) 46 | Coverage = (coverage_error(y_true, y_prob) - 1) / y_prob.shape[1] 47 | mAP = average_precision_score(y_true[:, class_freq != 0], y_prob[:, class_freq != 0], average='macro') 48 | 49 | return {'Hamming': Hamming, 'Ranking': Ranking, 'Coverage': Coverage, 'mAP': mAP, } 50 | -------------------------------------------------------------------------------- /utils/utils.py: -------------------------------------------------------------------------------- 1 | import numpy as np 2 | import torch 3 | import random 4 | import logging 5 | from logging import handlers 6 | import torch.multiprocessing 7 | import warnings 8 | 9 | warnings.filterwarnings("ignore") 10 | torch.multiprocessing.set_sharing_strategy('file_system') 11 | 12 | 13 | def set_seed(seed): 14 | torch.manual_seed(seed) # cpu 15 | torch.cuda.manual_seed(seed) # gpu 16 | torch.cuda.manual_seed_all(seed) # multi-gpu 17 | torch.backends.cudnn.deterministic = True # cudnn 18 | torch.backends.cudnn.benchmark = False # cudnn 19 | np.random.seed(seed) # numpy 20 | random.seed(seed) # random and transforms 21 | 22 | 23 | class Logger(object): 24 | level_relations = { 25 | 'debug': logging.DEBUG, 26 | 'info': logging.INFO, 27 | 'warning': logging.WARNING, 28 | 'error': logging.ERROR, 29 | 'crit': logging.CRITICAL 30 | } 31 | 32 | def __init__(self, filename, level='info', when='D', backCount=3, 33 | fmt='%(asctime)s - %(pathname)s[line:%(lineno)d] - %(levelname)s: %(message)s'): 34 | self.logger = logging.getLogger(filename) 35 | format_str = logging.Formatter(fmt) 36 | self.logger.setLevel(self.level_relations.get(level)) 37 | sh = logging.StreamHandler() 38 | th = handlers.TimedRotatingFileHandler(filename=filename, when=when, backupCount=backCount, encoding='utf-8') 39 | th.setFormatter(format_str) 40 | self.logger.addHandler(sh) 41 | self.logger.addHandler(th) 42 | --------------------------------------------------------------------------------