4月24日,今天事情好多啊,下午开组会指导本科生写论文,晚上还得给本科生改代码。


因果表征偏标记学习

数据集的两大挑战

领域外泛化问题(Out-Of-Distribution)

候选标签集合(Nosiy-Labels)

计划如下

检查论文内容

检查论文公式

检查论文引用

检查论文格式

改模型改代码

写实验写总结

提交

这是模型流程图

这是模型结果图

偏标记代码

偏标记构造函数
#对标签 y 进行独热编码并转化为torch向量
def binarize_class(y):
label = y.reshape(len(y), -1)
enc = OneHotEncoder(categories='auto')
enc.fit(label)
label = enc.transform(label).toarray().astype(np.float32)
label = torch.from_numpy(label)
return label

#对标签进行加噪声,
def partialize(y, y0, t, p):
# 定义一个名为 partialize 的函数,它接收四个参数:
# y: 一个 PyTorch 张量,通常表示类别标签的概率分布
# y0: 可能是原始的真实标签
# t: 一个字符串,用于指定处理的类型,有 'binomial' 和 'pair' 两种选择
# p: 一个概率值,用于随机过程
new_y = y.clone()
n, c = y.shape[0], y.shape[1]
avgC = 0
# 初始化一个变量 avgC,用于记录所有样本中激活类别的平均数量

if t=='binomial':
for i in range(n):
row = new_y[i, :]
row[np.where(np.random.binomial(1, p, c)==1)] = 1
# 使用 np.random.binomial(1, p, c) 生成一个长度为 c 的二项分布随机数组,
# 其中每个元素以概率 p 取值为 1,以概率 1 - p 取值为 0。
# np.where 找出取值为 1 的元素的索引,将 row 中对应索引的元素设置为 1
while torch.sum(row) == 1:
row[np.random.randint(0, c)] = 1
avgC += torch.sum(row)
# 累加当前行中激活类别的数量到 avgC
new_y[i] = row / torch.sum(row)
# 对当前行进行归一化处理,使所有元素之和为 1

if t=='pair':
P = np.eye(c)
# 创建一个 c x c 的单位矩阵 P
for idx in range(0, c-1):
P[idx, idx], P[idx, idx+1] = 1, p
# 将矩阵 P 中第 idx 行的第 idx 个元素设置为 1,第 idx + 1 个元素设置为 p
P[c-1, c-1], P[c-1, 0] = 1, p
# 将矩阵 P 的最后一行的最后一个元素设置为 1,第一个元素设置为 p
for i in range(n):
row = new_y[i, :]
idx = y0[i]
row[np.where(np.random.binomial(1, P[idx, :], c)==1)] = 1
# 根据原始真实标签 idx 从矩阵 P 中获取一行概率分布,
# 使用 np.random.binomial(1, P[idx, :], c) 生成一个长度为 c 的二项分布随机数组,
# 找出取值为 1 的元素的索引,将 row 中对应索引的元素设置为 1
avgC += torch.sum(row)
new_y[i] = row / torch.sum(row)

avgC = avgC / n
return new_y, avgC
偏标记数据集构造函数
from PIL import Image
import os
import os.path
import sys
import torch
import numpy as np
import pickle
import torch.utils.data as data
from utils.utils_algo import binarize_class, partialize, check_integrity, download_url

class cifar10(data.Dataset):
"""`CIFAR10 <https://www.cs.toronto.edu/~kriz/cifar.html>`_ Dataset.

Args:
root (string): Root directory of dataset where directory
``cifar-10-batches-py`` exists or will be saved to if download is set to True.
train (bool, optional): If True, creates dataset from training set, otherwise
creates from test set.
transform (callable, optional): A function/transform that takes in an PIL image
and returns a transformed version. E.g, ``transforms.RandomCrop``
target_transform (callable, optional): A function/transform that takes in the
target and transforms it.
download (bool, optional): If true, downloads the dataset from the internet and
puts it in root directory. If dataset is already downloaded, it is not
downloaded again.
"""

base_folder = 'cifar-10-batches-py'
url = 'http://www.cs.toronto.edu/~kriz/cifar-10-python.tar.gz'
filename = 'cifar-10-python.tar.gz'
tgz_md5 = 'c58f30108f718f92721af3b95e74349a'
train_list = [
['data_batch_1', 'c99cafc152244af753f735de768cd75f'],
['data_batch_2', 'd4bba439e000b95fd0a9bffe97cbabec'],
['data_batch_3', '54ebc095f3ab1f0389bbae665268c751'],
['data_batch_4', '634d18415352ddfa80567beed471001a'],
['data_batch_5', '482c414d41f54cd18b22e5b47cb7c3cb'],
]
test_list = [
['test_batch', '40351d587109b95175f43aff81a1287e'],
]


def __init__(self, root, train_or_not=True, download=False, transform=None, target_transform=None,
partial_type='binomial', partial_rate=0.1, random_state=0):
self.root = os.path.expanduser(root)
self.transform = transform
self.target_transform = target_transform
self.train = train_or_not
self.dataset = 'cifar10'

if download:
self.download()

if not self._check_integrity():
raise RuntimeError('Dataset not found or corrupted.' +
' You can use download=True to download it')

if self.train:
self.train_data = []
self.train_labels = []
for fentry in self.train_list:
f = fentry[0]
file = os.path.join(self.root, self.base_folder, f)
fo = open(file, 'rb')
entry = pickle.load(fo, encoding='latin1')
self.train_data.append(entry['data'])
self.train_labels += entry['labels']
fo.close()

self.train_data = np.concatenate(self.train_data)
self.train_data = self.train_data.reshape((50000, 3, 32, 32))
self.train_data = self.train_data.transpose((0, 2, 3, 1))

self.train_data = torch.from_numpy(self.train_data)
self.train_labels = torch.tensor(self.train_labels, dtype=torch.long)

if partial_rate != 0.0:
y = binarize_class(self.train_labels)
self.train_final_labels, self.average_class_label = partialize(y, self.train_labels, partial_type, partial_rate)

else:
self.train_final_labels = binarize_class(self.train_labels).float()

else:
f = self.test_list[0][0]
file = os.path.join(self.root, self.base_folder, f)
fo = open(file, 'rb')
if sys.version_info[0] == 2:
entry = pickle.load(fo)
else:
entry = pickle.load(fo, encoding='latin1')
self.test_data = entry['data']
self.test_labels = entry['labels']
fo.close()
self.test_data = self.test_data.reshape((10000, 3, 32, 32))
self.test_data = self.test_data.transpose((0, 2, 3, 1))

self.test_data = torch.from_numpy(self.test_data)
self.test_labels = torch.tensor(self.test_labels, dtype=torch.long)


def __getitem__(self, index):
"""
Args:
index (int): Index

Returns:
tuple: (image, target) where target is index of the target class.
"""
if self.train:
img, target, true = self.train_data[index], self.train_final_labels[index], self.train_labels[index]
else:
img, target, true = self.test_data[index], self.test_labels[index], self.test_labels[index]

img = Image.fromarray(img.numpy(), mode=None)

if self.transform is not None:
img = self.transform(img)

if self.target_transform is not None:
target = self.target_transform(target)

return img, target, true, index


def __len__(self):
if self.train:
return len(self.train_data)
else:
return len(self.test_data)


def _check_integrity(self):
root = self.root
for fentry in (self.train_list + self.test_list):
filename, md5 = fentry[0], fentry[1]
fpath = os.path.join(root, self.base_folder, filename)
if not check_integrity(fpath, md5):
return False
return True


def download(self):
import tarfile

if self._check_integrity():
return

download_url(self.url, self.root, self.filename, self.tgz_md5)

cwd = os.getcwd()
tar = tarfile.open(os.path.join(self.root, self.filename), "r:gz")
os.chdir(self.root)
tar.extractall()
tar.close()
os.chdir(cwd)


def __repr__(self):
fmt_str = 'Dataset ' + self.__class__.__name__ + '\n'
fmt_str += ' Number of datapoints: {}\n'.format(self.__len__())
tmp = 'train' if self.train is True else 'test'
fmt_str += ' Split: {}\n'.format(tmp)
fmt_str += ' Root Location: {}\n'.format(self.root)
tmp = ' Transforms (if any): '
fmt_str += '{0}{1}\n'.format(tmp, self.transform.__repr__().replace('\n', '\n' + ' ' * len(tmp)))
tmp = ' Target Transforms (if any): '
fmt_str += '{0}{1}'.format(tmp, self.target_transform.__repr__().replace('\n', '\n' + ' ' * len(tmp)))
return fmt_str

消歧损失函数可以改进,这里是基础消歧损失函数,baseline Loss Function

原始消歧损失函数
import torch 
import torch.nn.functional as F
import numpy as np

def partial_loss(output1, target, true):
output = F.softmax(output1, dim=1)
l = target * torch.log(output)
loss = (-torch.sum(l)) / l.size(0)

revisedY = target.clone()
revisedY[revisedY > 0] = 1
revisedY = revisedY * output
revisedY = revisedY / revisedY.sum(dim=1).repeat(revisedY.size(1),1).transpose(0,1)

new_target = revisedY


return loss, new_target
#权重逐步更新过程
#net 模型
output = net(images)
loss, new_label = partial_loss(output, labels, trues)
optimizer.zero_grad()
loss.backward()
optimizer.step()

# update weights
for j, k in enumerate(indexes):
train_loader.dataset.train_final_labels[k,:] = new_label[j,:].detach()

领域外泛化代码

查看代码测试
import argparse
import ast
from collections import deque
import numpy as np

import torch
from torch import nn
import torch.nn.functional as F

from models.model_factory import *
from optimizer.optimizer_helper import get_optim_and_scheduler
from data import *
from utils.Logger import Logger
from utils.tools import *
from models.classifier import Masker

import warnings
warnings.filterwarnings("ignore")
from warnings import simplefilter
simplefilter(action='ignore', category=FutureWarning)




def get_args():
parser = argparse.ArgumentParser()
#定义一个命令行参数,表示源域(source domain)用户可以通过命令行指定源域例如--source dataset1 dateset2
#choices 这个参数限制用户只能选择 available_datasets 列表中的数据集作为源域。
#nargs 参数定义了命令行参数应该接受的值的数量。'+' 表示用户可以输入一个或多个源域数据集。
# 例如,用户可以输入 --source dataset1 dataset2 dataset3。
parser.add_argument("--source", choices=available_datasets, help="Source", nargs='+')
# 选择一个目标域
parser.add_argument("--target", choices=available_datasets, help="Target")
parser.add_argument("--input_dir", default=None, help="The directory of dataset lists")
parser.add_argument("--output_dir", default=None, help="The directory to save logs and models")
parser.add_argument("--config", default=None, help="Experiment configs")
parser.add_argument("--tf_logger", type=ast.literal_eval, default=True, help="If true will save tensorboard compatible logs")
args = parser.parse_args()
config_file = "config." + args.config.replace("/", ".")
print(f"\nLoading experiment {args.config}\n")
config = __import__(config_file, fromlist=[""]).config

return args, config


class Trainer:
def __init__(self, args, config, device):
self.args = args
self.config = config
self.device = device
self.global_step = 0

# networks
self.encoder = get_encoder_from_config(self.config["networks"]["encoder"]).to(device)
self.classifier = get_classifier_from_config(self.config["networks"]["classifier"]).to(device)
self.classifier_ad = get_classifier_from_config(self.config["networks"]["classifier"]).to(device)
dim = self.config["networks"]["classifier"]["in_dim"]
self.masker = Masker(in_dim=dim, num_classes = dim,middle = 4*dim,k=self.config["k"]).to(device)

# optimizers
self.encoder_optim, self.encoder_sched = \
get_optim_and_scheduler(self.encoder, self.config["optimizer"]["encoder_optimizer"])
self.classifier_optim, self.classifier_sched = \
get_optim_and_scheduler(self.classifier, self.config["optimizer"]["classifier_optimizer"])
self.classifier_ad_optim, self.classifier_ad_sched = \
get_optim_and_scheduler(self.classifier_ad, self.config["optimizer"]["classifier_optimizer"])
self.masker_optim, self.masker_sched = \
get_optim_and_scheduler(self.masker, self.config["optimizer"]["classifier_optimizer"])

# dataloaders
self.train_loader = get_fourier_train_dataloader(args=self.args, config=self.config)
self.val_loader = get_val_dataloader(args=self.args, config=self.config)
self.test_loader = get_test_loader(args=self.args, config=self.config)
self.eval_loader = {'val': self.val_loader, 'test': self.test_loader}

def _do_epoch(self):
criterion = nn.CrossEntropyLoss()

# turn on train mode
self.encoder.train()
self.classifier.train()
self.classifier_ad.train()
self.masker.train()

for it, (batch, label, domain) in enumerate(self.train_loader):

# preprocessing
batch = torch.cat(batch, dim=0).to(self.device)
labels = torch.cat(label, dim=0).to(self.device)
if self.args.target in pacs_dataset:
labels -= 1
# zero grad
self.encoder_optim.zero_grad()
self.classifier_optim.zero_grad()
self.classifier_ad_optim.zero_grad()
self.masker_optim.zero_grad()

# forward
loss_dict = {}
correct_dict = {}
num_samples_dict = {}
total_loss = 0.0

## --------------------------step 1 : update G and C -----------------------------------
features = self.encoder(batch)
masks_sup = self.masker(features.detach())
masks_inf = torch.ones_like(masks_sup) - masks_sup
if self.current_epoch <= 5:
masks_sup = torch.ones_like(features.detach())
masks_inf = torch.ones_like(features.detach())
features_sup = features * masks_sup
features_inf = features * masks_inf
scores_sup = self.classifier(features_sup)
scores_inf = self.classifier_ad(features_inf)

assert batch.size(0) % 2 == 0
split_idx = int(batch.size(0) / 2)
features_ori, features_aug = torch.split(features, split_idx)
assert features_ori.size(0) == features_aug.size(0)

# classification loss for sup feature
loss_cls_sup = criterion(scores_sup, labels)
loss_dict["sup"] = loss_cls_sup.item()
correct_dict["sup"] = calculate_correct(scores_sup, labels)
num_samples_dict["sup"] = int(scores_sup.size(0))

# classification loss for inf feature
loss_cls_inf = criterion(scores_inf, labels)
loss_dict["inf"] = loss_cls_inf.item()
correct_dict["inf"] = calculate_correct(scores_inf, labels)
num_samples_dict["inf"] = int(scores_inf.size(0))

# factorization loss for features between ori and aug
loss_fac = factorization_loss(features_ori,features_aug)
loss_dict["fac"] = loss_fac.item()

# get consistency weight
const_weight = get_current_consistency_weight(epoch=self.current_epoch,
weight=self.config["lam_const"],
rampup_length=self.config["warmup_epoch"],
rampup_type=self.config["warmup_type"])

# calculate total loss
total_loss = 0.5*loss_cls_sup + 0.5*loss_cls_inf + const_weight*loss_fac
loss_dict["total"] = total_loss.item()

# backward
total_loss.backward()

# update
self.encoder_optim.step()
self.classifier_optim.step()
self.classifier_ad_optim.step()


## ---------------------------------- step2: update masker------------------------------
self.masker_optim.zero_grad()
features = self.encoder(batch)
masks_sup = self.masker(features.detach())
masks_inf = torch.ones_like(masks_sup) - masks_sup
features_sup = features * masks_sup
features_inf = features * masks_inf
scores_sup = self.classifier(features_sup)
scores_inf = self.classifier_ad(features_inf)

loss_cls_sup = criterion(scores_sup, labels)
loss_cls_inf = criterion(scores_inf, labels)
total_loss = 0.5*loss_cls_sup - 0.5*loss_cls_inf
total_loss.backward()
self.masker_optim.step()

self.global_step += 1

# record
self.logger.log(
it=it,
iters=len(self.train_loader),
losses=loss_dict,
samples_right=correct_dict,
total_samples=num_samples_dict
)

# turn on eval mode
self.encoder.eval()
self.classifier.eval()
self.masker.eval()
self.classifier_ad.eval()

# evaluation
with torch.no_grad():
for phase, loader in self.eval_loader.items():
total = len(loader.dataset)
class_correct = self.do_eval(loader)
class_acc = float(class_correct) / total
self.logger.log_test(phase, {'class': class_acc})
self.results[phase][self.current_epoch] = class_acc

# save from best model
if self.results['test'][self.current_epoch] >= self.best_acc:
self.best_acc = self.results['test'][self.current_epoch]
self.best_epoch = self.current_epoch + 1
self.logger.save_best_model(self.encoder, self.classifier, self.best_acc)

def do_eval(self, loader):
correct = 0
for it, (batch, domain) in enumerate(loader):
data, labels, domains = batch[0].to(self.device), batch[1].to(self.device), domain.to(self.device)
if self.args.target in pacs_dataset:
labels -= 1
features = self.encoder(data)
scores = self.classifier(features)
correct += calculate_correct(scores, labels)
return correct


def do_training(self):
self.logger = Logger(self.args, self.config, update_frequency=30)
self.logger.save_config()

self.epochs = self.config["epoch"]
self.results = {"val": torch.zeros(self.epochs), "test": torch.zeros(self.epochs)}

self.best_acc = 0
self.best_epoch = 0

for self.current_epoch in range(self.epochs):

# step schedulers
self.encoder_sched.step()
self.classifier_sched.step()

self.logger.new_epoch([group["lr"] for group in self.encoder_optim.param_groups])
self._do_epoch()
self.logger.finish_epoch()

# save from best model
val_res = self.results['val']
test_res = self.results['test']
self.logger.save_best_acc(val_res, test_res, self.best_acc, self.best_epoch - 1)

return self.logger


def main():
args, config = get_args()
device = torch.device("cuda" if torch.cuda.is_available() else "cpu")
trainer = Trainer(args, config, device)
trainer.do_training()


if __name__ == "__main__":
torch.backends.cudnn.benchmark = True
main()
查看代码测试

代码要改,数据集要改,损失函数要改。目前还不会