batch_softmax_loss

每个用户抽取一定数量的困难负样本,然后ssm

    def batch_softmax_loss_neg(self, user_idx, rec_user_emb, pos_idx, item_emb):
        user_emb = rec_user_emb[user_idx]
        product_scores = torch.matmul(F.normalize(user_emb, dim=1), F.normalize(item_emb, dim=1).transpose(0, 1))
        pos_score = (rec_user_emb[user_idx] * item_emb[pos_idx]).sum(dim=-1)
        pos_score = torch.exp(pos_score / self.temp2)
        train_mask = self.data.ui_adj[user_idx, self.data.user_num:].toarray()
        train_mask = torch.tensor(train_mask).cuda()
        product_scores = product_scores * (1 - train_mask)
        neg_score, indices = product_scores.topk(500, dim=1, largest=True, sorted=True)
        neg_score = torch.exp(neg_score[:,400:] / self.temp2).sum(dim=-1)
        loss = -torch.log(pos_score / (pos_score + neg_score + 10e-6))
        return torch.mean(loss)

neg_item_list

    def get_neg_itemlist(self,tau=-1):
        def softmax(prob, tau):
            softsum = (torch.exp(prob/tau)).sum(dim=-1)
            return torch.exp(prob/tau)/softsum
        training_data = self.data.training_data
        item_list = list(self.data.item.keys())
        interact_prob = torch.tensor([len(self.data.training_set_i[item]) for item in item_list] ,dtype=torch.float64) / len(training_data)
        if tau>0:
            interact_prob = softmax(interact_prob, tau)
            neg_item_list = np.random.choice(item_list, p=interact_prob, size=len(training_data))
        elif tau==0:
            neg_item_list = np.random.choice(item_list, p=interact_prob, size=len(training_data))
        else:
            neg_item_list = np.random.choice(item_list, size=len(training_data))
        return neg_item_list

ASCL

   def ascl(self,user_emb,item_emb,temperature):
        user_emb, item_emb = F.normalize(user_emb, dim=1), F.normalize(item_emb, dim=1)
        pos_score = (user_emb * item_emb).sum(dim=-1)
        pos_score = torch.exp(pos_score / temperature)
        ttl_score = torch.matmul(user_emb, item_emb.transpose(0, 1))
        ttl_score = torch.exp(ttl_score / temperature)
        with torch.no_grad():
            q_score  = ttl_score * (1-torch.eye(user_emb.shape[0]).to(device))
            q_score = q_score/q_score.sum(dim=1)
            q = q_score * (torch.log(q_score + 10e-8))
            H_ttl = 1- q/np.log(q.shape[0])
        ttl_score = ttl_score * H_ttl
        loss = -torch.log(pos_score / ttl_score.sum(dim=1) + 10e-6)
        return torch.mean(loss)
    def divide_subset(self):
        self.user_degree = torch.zeros((self.user_num))
        self.item_degree = torch.zeros((self.item_num))
        for u in self.user:
            self.user_degree[self.user[u]] = len(self.training_set_u[u])
        for u in self.item:
            self.item_degree[self.item[u]] = len(self.training_set_i[u])
        u_log_degree = torch.log(self.user_degree)
        i_log_degree = torch.log(self.item_degree)
        self.user_alpha = (u_log_degree-u_log_degree.min()) * 0.6/(u_log_degree.max()-u_log_degree.min()) + 0.8
        self.item_alpha = (i_log_degree-i_log_degree.min()) * 0.6/(i_log_degree.max()-i_log_degree.min()) + 0.8
        _,sort_user = torch.sort(self.user_degree,descending=True)
        _,sort_item = torch.sort(self.item_degree,descending=True)
        self.hot_item = sort_item[:int(self.item_num * 0.05)]
        self.norm_item = sort_item[int(self.item_num * 0.05):int(self.item_num * 0.2)]
        self.cold_item = sort_item[int(self.item_num * 0.2):]

        self.hot_user = sort_user[:int(self.user_num * 0.05)]
        self.norm_user = sort_user[int(self.user_num * 0.05):int(self.user_num * 0.85)]
        self.cold_user  = sort_user[int(self.user_num * 0.85):]

        self.average_interaction = int(len(self.training_data)/self.user_num)
        self.average_popularity = int(len(self.training_data)/self.item_num)
def DNS_bpr(user_emb, pos_item_emb,item_emb, neg_idx):
    pos_score = torch.mul(user_emb, pos_item_emb).sum(dim=1)
    neg_item_emb = item_emb[neg_idx].reshape(-1,64,64)
    scores = (user_emb.unsqueeze(dim=1) * neg_item_emb).sum(dim=-1)
    indices = torch.max(scores, dim=1)[1].detach()
    neg_item_emb = neg_item_emb[torch.arange(user_emb.size(0)),indices]
    neg_score = torch.mul(user_emb, neg_item_emb).sum(dim=1)
    loss = -torch.log(10e-6 + torch.sigmoid(pos_score - neg_score))
    return torch.mean(loss)

adap-tau

    def weighted_SSM(self,user_emb,item_emb,user_idx,temperature,epoch):
        u_weight = self.data.user_alpha[user_idx].cuda().detach()
        # compute tau
        # u_weight = u_weight * (1+(0.05*torch.Tensor(self.recloss[user_idx,2]-self.recloss[user_idx,0]).cuda()))
        # laberw_data = torch.clamp(torch.Tensor(self.recloss[user_idx,2]-self.recloss[user_idx,0])/2, min=-np.e ** (-1), max=10)
        # laberw_data = self.lambertw_table[laberw_data.long()]
        # u_weight = (u_weight * torch.exp(laberw_data)).detach()

        user_emb, item_emb = F.normalize(user_emb, dim=1), F.normalize(item_emb, dim=1)
        pos_score = (user_emb * item_emb).sum(dim=-1) * u_weight
        pos_score = torch.exp(pos_score / temperature)
        ttl_score = torch.matmul(user_emb, item_emb.transpose(0, 1)) * u_weight
        ttl_score = torch.exp(ttl_score / temperature)
        loss = -torch.log(pos_score / ttl_score.sum(dim=1) + 10e-6)
        if epoch>=1:
            # update recloss
            self.recloss[user_idx,0] = (self.recloss[user_idx,0] * self.recloss[user_idx,1] + loss.cpu().detach().numpy())/(self.recloss[user_idx,1]+1)
            self.recloss[user_idx,1] = self.recloss[user_idx,1] + 1
            self.recloss[user_idx,2] = loss.cpu().detach().numpy()
        return torch.mean(loss)
def InfoNCE_mix(view1, view2, temperature):
    view1, view2 = F.normalize(view1, dim=1), F.normalize(view2, dim=1)
    pos_score = (view1 * view2).sum(dim=-1)
    pos_score = torch.exp(pos_score / temperature)
    view2 = view2.unsqueeze(0).expand(view2.size(0), -1, -1)
    alpha = torch.rand_like(view2).cuda().detach()
    view2 = F.normalize((alpha * view1.unsqueeze(dim=1) + (1 - alpha) * view2), dim=-1)
    ttl_score = (view1 * view2).sum(dim=-1)
    ttl_score = torch.exp(ttl_score / temperature).sum(dim=1)
    cl_loss = -torch.log(pos_score / ttl_score + 10e-6)
    return torch.mean(cl_loss)
    
def SSM(user_emb, pos_item_emb,neg_item_emb,temperature,neg_num):
    user_emb, pos_item_emb,neg_item_emb = F.normalize(user_emb, dim=-1), F.normalize(pos_item_emb, dim=-1), F.normalize(neg_item_emb, dim=-1)
    pos_score = (user_emb * pos_item_emb).sum(dim=-1)
    pos_score = torch.exp(pos_score / temperature)
    neg_item_emb = neg_item_emb.reshape(-1,neg_num,64)
    item_emb = torch.cat((pos_item_emb.unsqueeze(dim=1),neg_item_emb),dim=1)
    ttl_score = (user_emb.unsqueeze(dim=1) * item_emb).sum(dim=-1)
    ttl_score = torch.exp(ttl_score / temperature)
    loss = -torch.log(pos_score / ttl_score.sum(dim=-1) + 10e-6)
    return torch.mean(loss)
def batch_softmax_loss_neg(user_emb, pos_item_emb, neg_item_emb, temperature):
    user_emb, pos_item_emb, neg_item_emb = F.normalize(user_emb, dim=1), F.normalize(pos_item_emb, dim=1), F.normalize(neg_item_emb, dim=1)
    pos_score = (user_emb * pos_item_emb).sum(dim=-1)
    pos_score = torch.exp(pos_score / temperature)
    user_emb = user_emb.unsqueeze(1).expand(user_emb.shape[0],neg_item_emb.shape[1],user_emb.shape[1])
    neg_score = (user_emb * neg_item_emb).sum(dim=-1) # user_emb(n*1*d) neg_item_emb = (n*m*d)
    neg_score = torch.exp(neg_score / temperature).sum(dim=-1)
    loss = -torch.log(pos_score / (pos_score + neg_score + 10e-6))
    return torch.mean(loss)

均匀性损失(错误案例)

# def cal_uniform_loss(user_emb, item_emb):
#     user_emb, item_emb = F.normalize(user_emb, dim=1), F.normalize(item_emb, dim=1)
#     distance = user_emb - item_emb  # n*d
#     gaussian_potential = torch.exp(-2 * torch.norm(distance,p=2,dim=1))
#     E_gaussian_potential = gaussian_potential.mean()
#     return torch.log(E_gaussian_potential)

DNS

def DNSbpr(user_emb, pos_item_emb, neg_item_emb):
    pos_score = torch.mul(user_emb, pos_item_emb).sum(dim=1)
    user_emb = user_emb.unsqueeze(1).expand(user_emb.shape[0], neg_item_emb.shape[1], user_emb.shape[1])
    ttl_socre = (user_emb * neg_item_emb).sum(dim=-1)
    neg_score = torch.max(ttl_socre, dim=1).values
    loss = -torch.log(10e-6 + torch.sigmoid(pos_score - neg_score))
    return torch.mean(loss)
 
def SSM(user_emb, pos_item_emb,neg_item_emb,temperature,neg_num):
    user_emb, pos_item_emb,neg_item_emb = F.normalize(user_emb, dim=-1), F.normalize(pos_item_emb, dim=-1), F.normalize(neg_item_emb, dim=-1)
    pos_score = (user_emb * pos_item_emb).sum(dim=-1)
    pos_score = torch.exp(pos_score / temperature)
    neg_item_emb = neg_item_emb.reshape(-1,neg_num,64)
    item_emb = torch.cat((pos_item_emb.unsqueeze(dim=1),neg_item_emb),dim=1)
    ttl_score = (user_emb.unsqueeze(dim=1) * item_emb).sum(dim=-1)
    ttl_score = torch.exp(ttl_score / temperature)
    loss = -torch.log(pos_score / ttl_score.sum(dim=-1) + 10e-6)
    return torch.mean(loss)

带margin的infonce

def InfoNCE_margin(view1, view2, temperature, margin, b_cos = True):
    if b_cos:
        view1, view2 = F.normalize(view1, dim=1), F.normalize(view2, dim=1)
    pos_score = (view1 * view2).sum(dim=-1)
    pos_score = torch.exp(pos_score / temperature)
    margin = margin * torch.eye(view1.shape[0])
    ttl_score = torch.matmul(view1, view2.transpose(0, 1))
    ttl_score += margin.cuda(0)
    ttl_score = torch.exp(ttl_score / temperature).sum(dim=1)
    cl_loss = -torch.log(pos_score / ttl_score+10e-6)
    return torch.mean(cl_loss)


def InfoNCE_tau(view1, view2, temperature):
    view1, view2 = F.normalize(view1, dim=1), F.normalize(view2, dim=1)
    pos_score = (view1 * view2).sum(dim=-1)
    pos_score = torch.exp(pos_score / temperature)
    ttl_score = torch.matmul(view1, view2.transpose(0, 1))
    ttl_score = torch.exp(ttl_score / temperature).sum(dim=1)
    cl_loss = -torch.log(pos_score / ttl_score+10e-6)
    return torch.mean(cl_loss)

def batch_bpr_loss(user_emb, item_emb):
    pos_score = torch.mul(user_emb, item_emb).sum(dim=1)
    neg_score = torch.matmul(user_emb, item_emb.transpose(0, 1)).mean(dim=1)
    loss = -torch.log(10e-6 + torch.sigmoid(pos_score - neg_score))
    return torch.mean(loss)



def Dis_softmax(view1, view2, temperature, b_cos = True):
    if b_cos:
        view1, view2 = F.normalize(view1, dim=1), F.normalize(view2, dim=1)
    N,M = view1.shape
    pos_score = (view1 - view2).norm(p=2, dim=1)
    pos_score = torch.exp(pos_score / temperature)
    view1 = view1.unsqueeze(1).expand(N,N,M)
    view2 = view2.unsqueeze(0).expand(N,N,M)
    ttl_score = (view1 - view2).norm(p=2, dim=-1)
    ttl_score = torch.exp(ttl_score / temperature).sum(dim=1)
    cl_loss = torch.log(pos_score / ttl_score+10e-6)
    return torch.mean(cl_loss)

LightGCN+对比学习

    def forward(self, perturbed=False):
        ego_embeddings = torch.cat([self.embedding_dict['user_emb'], self.embedding_dict['item_emb']], 0)
        all_embeddings = []
        all_embeddings_cl = ego_embeddings
        for k in range(self.n_layers):
            ego_embeddings = torch.sparse.mm(self.sparse_norm_adj, ego_embeddings)
            if perturbed:
                random_noise = torch.rand_like(ego_embeddings).cuda()
                ego_embeddings += torch.sign(ego_embeddings) * F.normalize(random_noise, dim=-1) * self.eps
            all_embeddings.append(ego_embeddings)
            if k==self.layer_cl-1:
                all_embeddings_cl +=  F.normalize(all_embeddings[1]-all_embeddings[0], dim=-1) * self.eps
        final_embeddings = torch.stack(all_embeddings, dim=1)
        final_embeddings = torch.mean(final_embeddings, dim=1)
        user_all_embeddings, item_all_embeddings = torch.split(final_embeddings, [self.data.user_num, self.data.item_num])
        user_all_embeddings_cl, item_all_embeddings_cl = torch.split(all_embeddings_cl, [self.data.user_num, self.data.item_num])
        if perturbed:
            return user_all_embeddings, item_all_embeddings,user_all_embeddings_cl, item_all_embeddings_cl
        return user_all_embeddings, item_all_embeddings
    def train(self):
        model = self.model.cuda()
        optimizer = torch.optim.Adam(model.parameters(), lr=self.lRate)
        hot_uidx, hot_iidx = self.select_ui_idx(500, mode='hot')
        cold_uidx, cold_iidx = self.select_ui_idx(500, mode='cold')
        norm_uidx, norm_iidx = self.select_ui_idx(500, mode='norm')
        iters = 10
        alphas_init = torch.tensor([1, 2], dtype=torch.float64).to(device)
        betas_init = torch.tensor([2, 1], dtype=torch.float64).to(device)
        weights_init = torch.tensor([1 - 0.05, 0.05], dtype=torch.float64).to(device)
        for epoch in range(self.maxEpoch):
            # epoch_rec_loss = []
            bmm_model = BetaMixture1D(iters, alphas_init, betas_init, weights_init)
            rec_user_emb, rec_item_emb, cl_user_emb, cl_item_emb = model(True)
            self.bmm_fit(rec_user_emb, rec_item_emb,torch.arange(self.data.user_num),np.random.randint(0,self.data.item_num, 100),bmm_model)
            for n, batch in enumerate(next_batch_pairwise(self.data, self.batch_size)):
                user_idx, pos_idx, rec_neg_idx = batch
                rec_user_emb, rec_item_emb, cl_user_emb, cl_item_emb = model(True)
                user_emb, pos_item_emb= rec_user_emb[user_idx], rec_item_emb[pos_idx]
                # rec_loss = self.batch_softmax_loss_neg(user_idx, rec_user_emb, pos_idx, rec_item_emb)
                # rec_neg_idx = torch.tensor(rec_neg_idx,dtype=torch.int64)
                # rec_neg_item_emb = rec_item_emb[rec_neg_idx]
                weight = self.getWeightSim(user_emb, pos_item_emb, bmm_model)
                rec_loss = weighted_SSM(user_emb,pos_item_emb,self.temp2,weight)
                cl_loss =  self.cl_rate * self.cal_cl_loss([user_idx,pos_idx],rec_user_emb,cl_user_emb,rec_item_emb,cl_item_emb)
                batch_loss =  rec_loss + l2_reg_loss(self.reg, user_emb, pos_item_emb) + cl_loss
                # epoch_rec_loss.append(rec_loss.item()), epoch_cl_loss.append(cl_loss.item())
                # Backward and optimize
                optimizer.zero_grad()
                batch_loss.backward()
                optimizer.step()
                if n % 100==0 and n>0:
                    print('training:', epoch + 1, 'batch', n, 'rec_loss:', rec_loss.item(), 'cl_loss', cl_loss.item())

            with torch.no_grad():
                self.user_emb, self.item_emb = self.model()
                hot_emb = torch.cat([self.user_emb[hot_uidx],self.item_emb[hot_iidx]],0)
                cold_emb = torch.cat([self.user_emb[cold_uidx],self.item_emb[cold_iidx]],0)
                self.eval_uniform(epoch, hot_emb, cold_emb)
                hot_user_mag = self.cal_sim(epoch, hot_uidx, self.user_emb, self.item_emb,mode='hot')
                self.cal_sim(epoch, norm_uidx, self.user_emb, self.item_emb, mode='norm')
                cold_user_mag= self.cal_sim(epoch, cold_uidx, self.user_emb, self.item_emb, mode='cold')
                hot_item_mag = self.item_magnitude(epoch, hot_iidx, self.item_emb,mode='hot')
                self.item_magnitude(epoch, norm_iidx, self.item_emb, mode='norm')
                cold_item_mag = self.item_magnitude(epoch, cold_iidx, self.item_emb, mode='cold')
                print('training:',epoch + 1,'U_mag_ratio:',hot_user_mag/cold_user_mag, 'I_mag_ratio:',hot_item_mag/cold_item_mag)
                # self.getTopSimNeg(hot_uidx, self.user_emb,self.item_emb, 100)
                # self.getTopSimNeg(norm_uidx,self.user_emb,self.item_emb, 100)
                # self.getTopSimNeg(cold_uidx,self.user_emb,self.item_emb, 100)
                # epoch_rec_loss = np.array(epoch_rec_loss).mean()
                # self.loss.extend([epoch_rec_loss,epoch_cl_loss,hot_pair_uniform_loss.item(),random_item_uniform_loss.item()])
                # if epoch%5==0:
                #     self.save_emb(epoch, hot_emb, mode='hot')
                #     self.save_emb(epoch, random_emb, mode='random')
            self.fast_evaluation(epoch)
        # self.save_loss()
        self.user_emb, self.item_emb = self.best_user_emb, self.best_item_emb
        # self.save_emb(self.bestPerformance[0], hot_emb, mode='best_hot')
        # self.save_emb(self.bestPerformance[0], random_emb, mode='best_random')

hard_neg buffer

    def getHardNeg(self, user_idx, pos_idx, rec_user_emb, rec_item_emb,temperature):
        u_emb,i_emb = F.normalize(rec_user_emb[user_idx], dim=1),F.normalize(rec_item_emb[pos_idx], dim=1)
        pos_score =  (u_emb * i_emb).sum(dim=-1)
        pos_score = torch.exp(pos_score / temperature)
        i_emb = i_emb.unsqueeze(0).expand(u_emb.size(0), -1, -1)
        neg_idx = torch.LongTensor(pos_idx).unsqueeze(0).expand(u_emb.size(0), -1).to(device)
        # if torch.all(self.hardNeg[user_idx])!=0:
        #     preNeg = self.hardNeg[user_idx]
        #     preNeg_emb = F.normalize(rec_item_emb[preNeg], dim=1)
        #     neg_idx = torch.cat([neg_idx,preNeg],1)
        #     i_emb = torch.cat([i_emb, preNeg_emb],1)
        ttl_score = (u_emb.unsqueeze(1) * i_emb).sum(dim=-1)
        indices = torch.topk(ttl_score, k=100)[1].detach()
        ttl_score = torch.exp(ttl_score / temperature).sum(dim=1)
        rec_loss = -torch.log(pos_score / ttl_score + 10e-6)
        chosen_hardNeg = neg_idx[torch.arange(i_emb.size(0)).unsqueeze(1), indices]
        self.hardNeg[user_idx] = chosen_hardNeg
        return torch.mean(rec_loss)

选前100ssm

    def getHardNeg(self, user_idx, pos_idx, rec_user_emb, rec_item_emb,temperature):
        u_emb,i_emb = F.normalize(rec_user_emb[user_idx], dim=1),F.normalize(rec_item_emb[pos_idx], dim=1)
        pos_score =  (u_emb * i_emb).sum(dim=-1)
        pos_score = torch.exp(pos_score / temperature)
        ttl_score = torch.matmul(u_emb, i_emb.transpose(0, 1))
        topk = min(1000,u_emb.size(0))
        indices = torch.topk(ttl_score, k=topk)[1].detach()
        # ttl_score = torch.exp(ttl_score / temperature) * weight
        # mask = torch.ones(u_emb.size(0),i_emb.size(0)).to(device)
        # mask[torch.arange(u_emb.shape[0]).unsqueeze(1),indices]=0
        # top_score = ttl_score * mask
        top_score = ttl_score[torch.arange(u_emb.shape[0]).unsqueeze(1),indices]
        top_score = torch.exp(top_score / temperature)
        rec_loss = -torch.log(pos_score / top_score.sum(dim=1)  + 10e-6)
        # self.hardNeg[user_idx] = top_score
        return torch.mean(rec_loss)

最相似的物品中是买过的ratio

    def getTopSimNeg(self, uidx,user_emb,item_emb, maxN):
        falseNeg_20,falseNeg_50,falseNeg_100,averTrueItem = 0,0,0,0
        for i, uid in enumerate(uidx):
            user = self.data.id2user[uid]
            item_score = torch.matmul(user_emb[uid], item_emb.transpose(0, 1)).cpu().numpy()
            # true_item_str = list(self.data.training_set_u[user].keys()) + list(self.data.test_set[user].keys())
            true_item_str =  list(self.data.test_set[user].keys())
            true_item = [self.data.item[item] for item in true_item_str]
            item_ids, _ = find_k_largest(maxN, item_score)
            falseNeg_20 += len(set(item_ids[:int(0.2 * maxN)]).intersection(set(true_item))) / int(0.2 * maxN)
            falseNeg_50 += len(set(item_ids[:int(0.5 * maxN)]).intersection(set(true_item))) / int(0.5 * maxN)
            falseNeg_100 += len(set(item_ids).intersection(set(true_item))) / maxN
            averTrueItem += len(true_item)
        falseNeg_20, falseNeg_50, falseNeg_100,averTrueItem = falseNeg_20/len(uidx), falseNeg_50/len(uidx), falseNeg_100/len(uidx), averTrueItem/len(uidx)
        print("TrueItemNum:",averTrueItem,"falseNeg@20:",falseNeg_20,"falseNeg@50:",falseNeg_50,"falseNeg@100:",falseNeg_100)
import torch
import torch.nn as nn
from base.graph_recommender import GraphRecommender
from util.conf import OptionConf
from util.sampler import next_batch_pairwise
from base.torch_interface import TorchGraphInterface
import torch.nn.functional as F
import numpy as np
import pandas as pd
from util.loss_torch import bpr_loss, l2_reg_loss, InfoNCE, InfoNCE_mix,batch_softmax_loss
import scipy.stats as stats
device = torch.device("cuda:0")



class KGFCL(GraphRecommender):
    def __init__(self, conf, training_set, test_set):
        super(KGFCL, self).__init__(conf, training_set, test_set)
        args = OptionConf(self.config['KGFCL'])
        self.cl_rate = float(args['-lambda'])
        self.eps = float(args['-eps'])
        self.temp1 = float(args['-tau1'])
        self.temp2 = float(args['-tau2'])
        self.n_layers = int(args['-n_layer'])
        self.layer_cl = int(args['-l*'])
        self.loss = []
        # self.iters = 10
        # self.alphas_init = torch.tensor([1, 2], dtype=torch.float64).to(device)
        # self.betas_init = torch.tensor([2, 1], dtype=torch.float64).to(device)
        # self.weights_init = torch.tensor([1 - 0.05, 0.05], dtype=torch.float64).to(device)
        self.model = KGFCL_Encoder(self.data, self.emb_size, self.eps, self.n_layers,self.layer_cl)
        # self.hardNeg = torch.zeros((self.data.user_num,100)).to(device)

    def train(self):
        model = self.model.cuda()
        optimizer = torch.optim.Adam(model.parameters(), lr=self.lRate)
        hot_uidx, hot_iidx = self.select_ui_idx(500, mode='hot')
        cold_uidx, cold_iidx = self.select_ui_idx(500, mode='cold')
        norm_uidx, norm_iidx = self.select_ui_idx(500, mode='norm')
        # bmm_model = BetaMixture1D(self.iters, self.alphas_init, self.betas_init, self.weights_init)
        for epoch in range(self.maxEpoch):
            # if epoch>=4:
            #     self.bmm_fit(bmm_model)
            for n, batch in enumerate(next_batch_pairwise(self.data, self.batch_size)):
                user_idx, pos_idx, rec_neg_idx = batch
                rec_user_emb, rec_item_emb, cl_user_emb, cl_item_emb = model(True)
                user_emb, pos_item_emb= rec_user_emb[user_idx], rec_item_emb[pos_idx]
                # if epoch>=4:
                #     weight = self.getWeightSim(user_emb, pos_item_emb, bmm_model).detach()
                # else:
                #     weight = torch.ones((user_emb.shape[0],pos_item_emb.shape[0]),requires_grad=False).to(device)
                # rec_loss = weighted_SSM(user_emb,pos_item_emb,self.temp2,weight)
                rec_loss = batch_softmax_loss(user_emb,pos_item_emb,self.temp2)
                # rec_loss = bpr_loss(user_emb, pos_item_emb, rec_item_emb[rec_neg_idx])
                cl_loss =  self.cl_rate * self.cal_cl_loss([user_idx,pos_idx],rec_user_emb,cl_user_emb,rec_item_emb,cl_item_emb)
                batch_loss =  rec_loss + l2_reg_loss(self.reg, user_emb, pos_item_emb) + cl_loss
                # Backward and optimize
                optimizer.zero_grad()
                batch_loss.backward()
                optimizer.step()
                if n % 100==0 and n>0:
                    print('training:', epoch + 1, 'batch', n, 'rec_loss:', rec_loss.item(), 'cl_loss', cl_loss.item())

            with torch.no_grad():
                self.user_emb, self.item_emb = self.model()
                hot_emb = torch.cat([self.user_emb[hot_uidx],self.item_emb[hot_iidx]],0)
                cold_emb = torch.cat([self.user_emb[cold_uidx],self.item_emb[cold_iidx]],0)
                self.eval_uniform(epoch, hot_emb, cold_emb)
                hot_user_mag = self.cal_sim(epoch, hot_uidx, self.user_emb, self.item_emb,mode='hot')
                self.cal_sim(epoch, norm_uidx, self.user_emb, self.item_emb, mode='norm')
                cold_user_mag= self.cal_sim(epoch, cold_uidx, self.user_emb, self.item_emb, mode='cold')
                hot_item_mag = self.item_magnitude(epoch, hot_iidx, self.item_emb,mode='hot')
                self.item_magnitude(epoch, norm_iidx, self.item_emb, mode='norm')
                cold_item_mag = self.item_magnitude(epoch, cold_iidx, self.item_emb, mode='cold')
                print('training:',epoch + 1,'U_mag_ratio:',hot_user_mag/cold_user_mag, 'I_mag_ratio:',hot_item_mag/cold_item_mag)

            self.fast_evaluation(epoch)
        self.user_emb, self.item_emb = self.best_user_emb, self.best_item_emb



    def cal_cl_loss(self, idx, user_view1,user_view2,item_view1,item_view2):
        u_idx = torch.unique(torch.Tensor(idx[0]).type(torch.long)).cuda()
        i_idx = torch.unique(torch.Tensor(idx[1]).type(torch.long)).cuda()
        user_cl_loss = InfoNCE_mix(user_view1[u_idx], user_view2[u_idx], self.temp1)
        item_cl_loss = InfoNCE_mix(item_view1[i_idx], item_view2[i_idx], self.temp1)
        return user_cl_loss + item_cl_loss


    def save(self):
        with torch.no_grad():
            self.best_user_emb, self.best_item_emb = self.model.forward()

    def predict(self, u):
        u = self.data.get_user_id(u)
        score = torch.matmul(self.user_emb[u],self.item_emb.transpose(0, 1))
        return score.cpu().numpy()

    def save_loss(self):
        self.loss = np.array(self.loss).reshape(-1,4)
        df = pd.DataFrame({'rec_loss':self.loss[:,0], 'cl_loss':self.loss[:,1],
                           'hot_uniform_loss':self.loss[:,2], 'random_loss':self.loss[:,3]})
        file_name = self.output['-dir'] + self.config['model.name'] + '_loss.csv'
        df.to_csv(file_name, index=False)
    #
    # def bmm_fit(self, bmm_model):
    #     ui_sim = self.hardNeg
    #     # ui_sim_norm = (ui_sim - ui_sim.min()) / (ui_sim.max() - ui_sim.min())
    #     bmm_model.fit(ui_sim.flatten())
    #
    # def sim(self,z1: torch.Tensor, z2: torch.Tensor):
    #     z1 = F.normalize(z1,dim=1)
    #     z2 = F.normalize(z2,dim=1)
    #     return torch.mm(z1, z2.t())
    #
    # def getWeightSim(self,user_emb,item_emb,bmm_model):
    #     ui_sim = self.sim(user_emb, item_emb)
    #     # ui_sim_norm = (ui_sim - ui_sim.min()) / (ui_sim.max() - ui_sim.min())
    #     weight = bmm_model.posterior(ui_sim, 0)
    #     diag_indices = torch.arange(weight.shape[0])
    #     weight[diag_indices, diag_indices] = 1
    #     return weight

    # def getHardNeg(self, user_idx, pos_idx, rec_user_emb, rec_item_emb,temperature):
    #     u_emb,i_emb = F.normalize(rec_user_emb[user_idx], dim=-1),F.normalize(rec_item_emb[pos_idx], dim=-1)
    #     pos_score =  (u_emb * i_emb).sum(dim=-1)
    #     pos_score = torch.exp(pos_score / temperature)
    #     neg_emb = i_emb.unsqueeze(0).expand(u_emb.size(0), -1, -1)
    #     alpha = torch.rand_like(neg_emb).cuda().detach()
    #     neg_emb = F.normalize(alpha * i_emb.unsqueeze(dim=1) + (1 - alpha) * neg_emb,dim=-1)
    #     ttl_score = (u_emb * neg_emb).sum(dim=-1)
    #     ttl_score = torch.exp(ttl_score / temperature)
    #     rec_loss = -torch.log(pos_score / ttl_score.sum(dim=1)  + 10e-6)
    #     # self.hardNeg[user_idx] = top_score
    #     return torch.mean(rec_loss)

#
# class BetaMixture1D(object):
#     def __init__(self, max_iters,
#                  alphas_init,
#                  betas_init,
#                  weights_init):
#         self.alphas = alphas_init
#         self.betas = betas_init
#         self.weight = weights_init
#         self.max_iters = max_iters
#         self.eps_nan = 1e-12
#
#     def likelihood(self, x, y):   # 计算数据点x在第 y 个分布下的概率密度函数值
#         x_cpu = x.cpu().detach().numpy()
#         alpha_cpu = self.alphas.cpu().detach().numpy()
#         beta_cpu = self.betas.cpu().detach().numpy()
#         return torch.from_numpy(stats.beta.pdf(x_cpu, alpha_cpu[y], beta_cpu[y])).to(x.device)
#
#     def weighted_likelihood(self, x, y):  # 计算数据点x在第 y 个分布下的加权概率密度
#         return self.weight[y] * self.likelihood(x, y)
#
#     def probability(self, x):
#         return self.weighted_likelihood(x, 0) + self.weighted_likelihood(x, 1)
#
#     def posterior(self, x, y):  #公式 5 估计每个数据点属于每个分布的后验概率
#         return self.weighted_likelihood(x, y) / (self.probability(x) + self.eps_nan)
#
#     def responsibilities(self, x):
#         r = torch.cat((self.weighted_likelihood(x, 0).view(1,-1),self.weighted_likelihood(x, 1).view(1,-1)),0)
#         r[r <= self.eps_nan] = self.eps_nan
#         r /= r.sum(0)
#         return r
#
#     def weighted_mean(self,x, w):
#         return torch.sum(w * x) / torch.sum(w)
#
#     def fit_beta_weighted(self,x, w):  # M步,公式 7 计算 alpha, beta
#         x_bar = self.weighted_mean(x, w)
#         s2 = self.weighted_mean((x - x_bar) ** 2, w)
#         alpha = x_bar * ((x_bar * (1 - x_bar)) / s2 - 1)
#         beta = alpha * (1 - x_bar) / x_bar
#         return alpha, beta
#
#     def fit(self, x):
#         eps = 1e-12
#         x[x >= 1 - eps] = 1 - eps
#         x[x <= eps] = eps
#
#         for i in range(self.max_iters):
#             # E-step  #公式 5 估计每个数据点属于每个分布的后验概率
#             r = self.responsibilities(x)
#             # M-step
#             self.alphas[0], self.betas[0] = self.fit_beta_weighted(x, r[0])   # 公式 7 计算新的alpha_c, beta_c
#             self.alphas[1], self.betas[1] = self.fit_beta_weighted(x, r[1])
#             if self.betas[1] < 1:
#                 self.betas[1] = 1.01
#             self.weight = r.sum(1)    # 公式 8
#             self.weight /= self.weight.sum()
#         return self
#
#     def predict(self, x):
#         return self.posterior(x, 1) > 0.5
#
#
#

class KGFCL_Encoder(nn.Module):
    def __init__(self, data,  emb_size, eps, n_layers, layer_cl):
        super(KGFCL_Encoder, self).__init__()
        self.data = data
        self.eps = eps
        self.emb_size = emb_size
        self.n_layers = n_layers
        self.layer_cl = layer_cl
        self.norm_adj = data.norm_adj
        self.embedding_dict = self._init_model()
        self.sparse_norm_adj = TorchGraphInterface.convert_sparse_mat_to_tensor(self.norm_adj).cuda()

    def _init_model(self):
        initializer = nn.init.xavier_uniform_
        embedding_dict = nn.ParameterDict({
            'user_emb': nn.Parameter(initializer(torch.empty(self.data.user_num, self.emb_size))),
            'item_emb': nn.Parameter(initializer(torch.empty(self.data.item_num, self.emb_size))),
        })
        return embedding_dict

    def forward(self, perturbed=False):
        ego_embeddings = torch.cat([self.embedding_dict['user_emb'], self.embedding_dict['item_emb']], 0)
        all_embeddings = []
        all_embeddings_cl = ego_embeddings
        for k in range(self.n_layers):
            ego_embeddings = torch.sparse.mm(self.sparse_norm_adj, ego_embeddings)
            if perturbed:
                random_noise = torch.rand_like(ego_embeddings).cuda()
                ego_embeddings += torch.sign(ego_embeddings) * F.normalize(random_noise, dim=-1) * self.eps
            all_embeddings.append(ego_embeddings)
            if k==self.layer_cl-1:
                all_embeddings_cl = ego_embeddings
        final_embeddings = torch.stack(all_embeddings, dim=1)
        final_embeddings = torch.mean(final_embeddings, dim=1)
        user_all_embeddings, item_all_embeddings = torch.split(final_embeddings, [self.data.user_num, self.data.item_num])
        user_all_embeddings_cl, item_all_embeddings_cl = torch.split(all_embeddings_cl, [self.data.user_num, self.data.item_num])
        if perturbed:
            return user_all_embeddings, item_all_embeddings,user_all_embeddings_cl, item_all_embeddings_cl
        return user_all_embeddings, item_all_embeddings

采用聚类后的节点做bmm

import torch
import torch.nn as nn
from base.graph_recommender import GraphRecommender
from util.conf import OptionConf
from util.sampler import next_batch_pairwise
from base.torch_interface import TorchGraphInterface
import torch.nn.functional as F
import numpy as np
import pandas as pd
from util.loss_torch import  l2_reg_loss, InfoNCE, DNS_bpr,batch_softmax_loss,weighted_SSM
import scipy.stats as stats
import faiss
device = torch.device("cuda:0")



class KGFCL(GraphRecommender):
    def __init__(self, conf, training_set, test_set):
        super(KGFCL, self).__init__(conf, training_set, test_set)
        args = OptionConf(self.config['KGFCL'])
        self.cl_rate = float(args['-lambda'])
        self.eps = float(args['-eps'])
        self.temp1 = float(args['-tau1'])
        self.temp2 = float(args['-tau2'])
        self.n_layers = int(args['-n_layer'])
        self.layer_cl = int(args['-l*'])
        self.loss = []
        self.k = 2000
        self.iters = 1
        self.alphas_init = torch.tensor([1, 2], dtype=torch.float64).to(device)
        self.betas_init = torch.tensor([2, 1], dtype=torch.float64).to(device)
        self.weights_init = torch.tensor([1 - 0.05, 0.05], dtype=torch.float64).to(device)
        self.model = KGFCL_Encoder(self.data, self.emb_size, self.eps, self.n_layers,self.layer_cl)


    def train(self):
        model = self.model.cuda()
        optimizer = torch.optim.Adam(model.parameters(), lr=self.lRate)
        hot_uidx, hot_iidx = self.select_ui_idx(500, mode='hot')
        cold_uidx, cold_iidx = self.select_ui_idx(500, mode='cold')
        norm_uidx, norm_iidx = self.select_ui_idx(500, mode='norm')
        # bmm_model = BetaMixture1D(self.iters, self.alphas_init, self.betas_init, self.weights_init)
        for epoch in range(self.maxEpoch):
            # with torch.no_grad():
            #     self.e_step(bmm_model)
            for n, batch in enumerate(next_batch_pairwise(self.data, self.batch_size,100)):
                user_idx, pos_idx, rec_neg_idx = batch
                rec_user_emb, rec_item_emb, cl_user_emb, cl_item_emb = model(True)
                user_emb, pos_item_emb, neg_item_emb = rec_user_emb[user_idx], rec_item_emb[pos_idx],rec_item_emb[rec_neg_idx]
                # with torch.no_grad():
                #     weight = self.getWeightSim(user_emb, pos_item_emb,bmm_model).detach()
                # weight = torch.ones((user_emb.size(0),pos_item_emb.size(0))).to(device).detach()
                # rec_loss = weighted_SSM(user_emb, pos_item_emb, self.temp2, weight)
                rec_loss = DNS_bpr(user_emb, pos_item_emb,rec_item_emb,rec_neg_idx)
                # rec_loss = bpr_loss(user_emb, pos_item_emb, rec_item_emb[rec_neg_idx])
                # cl_loss =  self.cl_rate * self.cal_cl_loss([user_idx,pos_idx],rec_user_emb,cl_user_emb,rec_item_emb,cl_item_emb)
                batch_loss =  rec_loss + l2_reg_loss(self.reg, user_emb, pos_item_emb)
                # Backward and optimize
                optimizer.zero_grad()
                batch_loss.backward()
                optimizer.step()
                if n % 100==0 and n>0:
                    print('training:', epoch + 1, 'batch', n, 'rec_loss:', rec_loss.item(), 'cl_loss')

            with torch.no_grad():
                self.user_emb, self.item_emb = self.model()
                hot_emb = torch.cat([self.user_emb[hot_uidx],self.item_emb[hot_iidx]],0)
                cold_emb = torch.cat([self.user_emb[cold_uidx],self.item_emb[cold_iidx]],0)
                self.eval_uniform(epoch, hot_emb, cold_emb)
                hot_user_mag = self.cal_sim(epoch, hot_uidx, self.user_emb, self.item_emb,mode='hot')
                self.cal_sim(epoch, norm_uidx, self.user_emb, self.item_emb, mode='norm')
                cold_user_mag= self.cal_sim(epoch, cold_uidx, self.user_emb, self.item_emb, mode='cold')
                hot_item_mag = self.item_magnitude(epoch, hot_iidx, self.item_emb,mode='hot')
                self.item_magnitude(epoch, norm_iidx, self.item_emb, mode='norm')
                cold_item_mag = self.item_magnitude(epoch, cold_iidx, self.item_emb, mode='cold')
                print('training:',epoch + 1,'U_mag_ratio:',hot_user_mag/cold_user_mag, 'I_mag_ratio:',hot_item_mag/cold_item_mag)
                # print('weight:',bmm_model.weight,"alpha:",bmm_model.alphas,"beta:",bmm_model.betas)
            self.fast_evaluation(epoch)
        self.user_emb, self.item_emb = self.best_user_emb, self.best_item_emb

    def e_step(self,bmm_model):
        self.user_emb, self.item_emb = self.model()
        self.item_centroids, self.item_2cluster = self.run_kmeans(self.item_emb.cpu().numpy())
        self.bmm_fit(bmm_model, self.user_emb, self.item_centroids)


    def run_kmeans(self, x):
        """Run K-means algorithm to get k clusters of the input tensor x        """
        kmeans = faiss.Kmeans(d=self.emb_size, k=self.k, gpu=True)
        kmeans.train(x)
        cluster_cents = kmeans.centroids
        _, I = kmeans.index.search(x, 1)
        # convert to cuda Tensors for broadcast
        centroids = torch.Tensor(cluster_cents).cuda()
        node2cluster = torch.LongTensor(I).squeeze().cuda()
        return centroids, node2cluster

    def cal_cl_loss(self, idx, user_view1,user_view2,item_view1,item_view2):
        u_idx = torch.unique(torch.Tensor(idx[0]).type(torch.long)).cuda()
        i_idx = torch.unique(torch.Tensor(idx[1]).type(torch.long)).cuda()
        user_cl_loss = InfoNCE(user_view1[u_idx], user_view2[u_idx], self.temp1)
        item_cl_loss = InfoNCE(item_view1[i_idx], item_view2[i_idx], self.temp1)
        return user_cl_loss + item_cl_loss


    def save(self):
        with torch.no_grad():
            self.best_user_emb, self.best_item_emb = self.model.forward()

    def predict(self, u):
        u = self.data.get_user_id(u)
        score = torch.matmul(self.user_emb[u],self.item_emb.transpose(0, 1))
        return score.cpu().numpy()

    def save_loss(self):
        self.loss = np.array(self.loss).reshape(-1,4)
        df = pd.DataFrame({'rec_loss':self.loss[:,0], 'cl_loss':self.loss[:,1],
                           'hot_uniform_loss':self.loss[:,2], 'random_loss':self.loss[:,3]})
        file_name = self.output['-dir'] + self.config['model.name'] + '_loss.csv'
        df.to_csv(file_name, index=False)



    def bmm_fit(self, bmm_model,user_emb ,item_emb):
        ui_sim = self.sim(user_emb, item_emb)
        # ui_sim_norm = (ui_sim - ui_sim.min()) / (ui_sim.max() - ui_sim.min())
        bmm_model.fit(ui_sim.flatten())

    def sim(self,z1: torch.Tensor, z2: torch.Tensor):
        z1 = F.normalize(z1,dim=-1)
        z2 = F.normalize(z2,dim=-1)
        return torch.mm(z1, z2.t())

    def getWeightSim(self,user_emb,item_emb,bmm_model):
        ui_sim = self.sim(user_emb, item_emb)
        # ui_sim_norm = (ui_sim - ui_sim.min()) / (ui_sim.max() - ui_sim.min())
        weight = bmm_model.posterior(ui_sim, 0)
        diag_indices = torch.arange(weight.shape[0])
        weight[diag_indices, diag_indices] = 1
        return weight



class BetaMixture1D(object):
    def __init__(self, max_iters,
                 alphas_init,
                 betas_init,
                 weights_init):
        self.alphas = alphas_init
        self.betas = betas_init
        self.weight = weights_init
        self.max_iters = max_iters
        self.eps_nan = 1e-12

    def likelihood(self, x, y):   # 计算数据点x在第 y 个分布下的概率密度函数值
        x_cpu = x.cpu().detach().numpy()
        alpha_cpu = self.alphas.cpu().detach().numpy()
        beta_cpu = self.betas.cpu().detach().numpy()
        return torch.from_numpy(stats.beta.pdf(x_cpu, alpha_cpu[y], beta_cpu[y])).to(x.device)

    def weighted_likelihood(self, x, y):  # 计算数据点x在第 y 个分布下的加权概率密度
        return self.weight[y] * self.likelihood(x, y)

    def probability(self, x):
        return self.weighted_likelihood(x, 0) + self.weighted_likelihood(x, 1)

    def posterior(self, x, y):  #公式 5 估计每个数据点属于每个分布的后验概率
        return self.weighted_likelihood(x, y) / (self.probability(x) + self.eps_nan)

    def responsibilities(self, x):
        r = torch.cat((self.weighted_likelihood(x, 0).view(1,-1),self.weighted_likelihood(x, 1).view(1,-1)),0)
        r[r <= self.eps_nan] = self.eps_nan
        r /= r.sum(0)
        return r

    def weighted_mean(self,x, w):
        return torch.sum(w * x) / torch.sum(w)

    def fit_beta_weighted(self,x, w):  # M步,公式 7 计算 alpha, beta
        x_bar = self.weighted_mean(x, w)
        s2 = self.weighted_mean((x - x_bar) ** 2, w)
        alpha = x_bar * ((x_bar * (1 - x_bar)) / s2 - 1)
        beta = alpha * (1 - x_bar) / x_bar
        return alpha, beta

    def fit(self, x):
        eps = 1e-12
        x[x >= 1 - eps] = 1 - eps
        x[x <= eps] = eps

        for i in range(self.max_iters):
            # E-step  #公式 5 估计每个数据点属于每个分布的后验概率
            r = self.responsibilities(x)
            # M-step
            self.alphas[0], self.betas[0] = self.fit_beta_weighted(x, r[0])   # 公式 7 计算新的alpha_c, beta_c
            self.alphas[1], self.betas[1] = self.fit_beta_weighted(x, r[1])
            if self.betas[1] < 1:
                self.betas[1] = 1.01
            self.weight = r.sum(1)    # 公式 8
            self.weight /= self.weight.sum()
        return self

    def predict(self, x):
        return self.posterior(x, 1) > 0.5




class KGFCL_Encoder(nn.Module):
    def __init__(self, data,  emb_size, eps, n_layers, layer_cl):
        super(KGFCL_Encoder, self).__init__()
        self.data = data
        self.eps = eps
        self.emb_size = emb_size
        self.n_layers = n_layers
        self.layer_cl = layer_cl
        self.norm_adj = data.norm_adj
        self.embedding_dict = self._init_model()
        self.sparse_norm_adj = TorchGraphInterface.convert_sparse_mat_to_tensor(self.norm_adj).cuda()

    def _init_model(self):
        initializer = nn.init.xavier_uniform_
        embedding_dict = nn.ParameterDict({
            'user_emb': nn.Parameter(initializer(torch.empty(self.data.user_num, self.emb_size))),
            'item_emb': nn.Parameter(initializer(torch.empty(self.data.item_num, self.emb_size))),
        })
        return embedding_dict

    def forward(self, perturbed=False):
        ego_embeddings = torch.cat([self.embedding_dict['user_emb'], self.embedding_dict['item_emb']], 0)
        all_embeddings = [ego_embeddings]
        all_embeddings_cl = ego_embeddings
        for k in range(self.n_layers):
            ego_embeddings = torch.sparse.mm(self.sparse_norm_adj, ego_embeddings)
            if perturbed:
                random_noise = torch.rand_like(ego_embeddings).cuda()
                ego_embeddings += torch.sign(ego_embeddings) * F.normalize(random_noise, dim=-1) * self.eps
            all_embeddings.append(ego_embeddings)
            if k==self.layer_cl-1:
                all_embeddings_cl = ego_embeddings
        final_embeddings = torch.stack(all_embeddings, dim=1)
        final_embeddings = torch.mean(final_embeddings, dim=1)
        user_all_embeddings, item_all_embeddings = torch.split(final_embeddings, [self.data.user_num, self.data.item_num])
        user_all_embeddings_cl, item_all_embeddings_cl = torch.split(all_embeddings_cl, [self.data.user_num, self.data.item_num])
        if perturbed:
            return user_all_embeddings, item_all_embeddings,user_all_embeddings_cl, item_all_embeddings_cl
        return user_all_embeddings, item_all_embeddings

Latex

\mathcal{L_{ssm}} = -\frac{1}{|D|}\sum_{(u,i)\in D}^{} log\frac{exp(f(u,i)/\tau )} {\sum_{j\in\mathcal{I})}exp(f(u,j)/\tau )}

vgae+adap w_u

import torch
import torch.nn as nn
from base.graph_recommender import GraphRecommender
from util.conf import OptionConf
from util.sampler import next_batch_pairwise
from base.torch_interface import TorchGraphInterface
import torch.nn.functional as F
import numpy as np
import pandas as pd
from util.loss_torch import  l2_reg_loss, InfoNCE,batch_softmax_loss,InfoNCE_weight,weighted_SSM,bpr_loss,calcRegLoss
device = torch.device("cuda:0")



class KGFCL(GraphRecommender):
    def __init__(self, conf, training_set, test_set):
        super(KGFCL, self).__init__(conf, training_set, test_set)
        args = OptionConf(self.config['KGFCL'])
        self.cl_rate = float(args['-lambda'])
        self.eps = float(args['-eps'])
        self.temp1 = float(args['-tau1'])
        self.temp2 = float(args['-tau2'])
        self.n_layers = int(args['-n_layer'])
        self.layer_cl = int(args['-l*'])
        self.loss = []
        self.model = LGCN_Encoder(self.data, self.emb_size, self.n_layers)
        self.vgae = VGAE(self.data, args)



    def train(self):
        model = self.model.cuda()
        vgae = self.vgae.cuda()
        optimizer1 = torch.optim.Adam(model.parameters(), lr=self.lRate)
        optimizer2 = torch.optim.Adam(vgae.parameters(), lr=self.lRate)
        self.select_ids(500)
        for epoch in range(self.maxEpoch):
            for n, batch in enumerate(next_batch_pairwise(self.data, self.batch_size)):
                user_idx, pos_idx, neg_idx = batch
                loss_recon, newAdj = vgae(user_idx, pos_idx, neg_idx)
                # rec_user_emb, rec_item_emb, cl_user_emb, cl_item_emb = model(True)
                rec_user_emb, rec_item_emb = model()
                user_emb_vgae,item_emb_vgae = model(newAdj)
                user_emb, pos_item_emb = rec_user_emb[user_idx], rec_item_emb[pos_idx]
                # rec_loss = batch_softmax_loss(user_emb, pos_item_emb, self.temp2)
                rec_loss = self.weighted_SSM(user_emb,pos_item_emb,user_idx,self.temp2)
                # rec_loss = bpr_loss(user_emb, pos_item_emb, rec_item_emb[neg_idx])
                cl_loss =  self.cl_rate * self.cal_cl_loss([user_idx,pos_idx],rec_user_emb,user_emb_vgae,rec_item_emb,item_emb_vgae)
                reg_loss = calcRegLoss(self.vgae)
                batch_loss =  rec_loss + l2_reg_loss(self.reg, user_emb, pos_item_emb) + cl_loss + loss_recon + self.reg * reg_loss
                # Backward and optimize
                optimizer1.zero_grad()
                optimizer2.zero_grad()
                batch_loss.backward()
                optimizer1.step()
                optimizer2.step()
                if n % 100==0 and n>0:
                    print('training:', epoch + 1, 'batch', n, 'rec_loss:', rec_loss.item(), 'cl_loss', cl_loss.item(),
                          'loss_recon',loss_recon.item())

            with torch.no_grad():
                self.user_emb, self.item_emb = self.model()
                self.evalAU(self.user_emb,self.item_emb,epoch)
                self.evalSim(self.user_emb,self.item_emb,epoch)
                self.eval_innerProduct(self.user_emb,self.item_emb,epoch)
            self.fast_evaluation(epoch)
        self.user_emb, self.item_emb = self.best_user_emb, self.best_item_emb
        self.save_emb(self.user_emb, self.item_emb)


    def cal_cl_loss(self, idx, user_view1,user_view2,item_view1,item_view2):
        u_idx = torch.unique(torch.Tensor(idx[0]).type(torch.long)).cuda()
        i_idx = torch.unique(torch.Tensor(idx[1]).type(torch.long)).cuda()
        u_weight = self.data.user_alpha[u_idx].cuda().detach()
        i_weight = self.data.item_alpha[i_idx].cuda().detach()
        user_cl_loss = InfoNCE_weight(user_view1[u_idx], user_view2[u_idx], self.temp1,1/u_weight)
        item_cl_loss = InfoNCE_weight(item_view1[i_idx], item_view2[i_idx], self.temp1,1/i_weight)
        return user_cl_loss + item_cl_loss

    def weighted_SSM(self,user_emb,item_emb,user_idx,temperature):
        u_weight = self.data.user_alpha[user_idx].cuda().detach()
        user_emb, item_emb = F.normalize(user_emb, dim=1), F.normalize(item_emb, dim=1)
        pos_score = (user_emb * item_emb).sum(dim=-1) * u_weight
        pos_score = torch.exp(pos_score / temperature)
        ttl_score = torch.matmul(user_emb, item_emb.transpose(0, 1)) * u_weight
        ttl_score = torch.exp(ttl_score / temperature)
        loss = -torch.log(pos_score / ttl_score.sum(dim=1) + 10e-6)
        return torch.mean(loss)

    def save(self):
        with torch.no_grad():
            self.best_user_emb, self.best_item_emb = self.model.forward()

    def predict(self, u):
        u = self.data.get_user_id(u)
        score = torch.matmul(self.user_emb[u],self.item_emb.transpose(0, 1))
        return score.cpu().numpy()

    def save_loss(self):
        self.loss = np.array(self.loss).reshape(-1,4)
        df = pd.DataFrame({'rec_loss':self.loss[:,0], 'cl_loss':self.loss[:,1],
                           'hot_uniform_loss':self.loss[:,2], 'random_loss':self.loss[:,3]})
        file_name = self.output['-dir'] + self.config['model.name'] + '_loss.csv'
        df.to_csv(file_name, index=False)


class LGCN_Encoder(nn.Module):
    def __init__(self, data, emb_size, n_layers):
        super(LGCN_Encoder, self).__init__()
        self.data = data
        self.latent_size = emb_size
        self.layers = n_layers
        self.norm_adj = data.norm_adj
        self.embedding_dict = self._init_model()

    def _init_model(self):
        initializer = nn.init.xavier_uniform_
        embedding_dict = nn.ParameterDict({
            'user_emb': nn.Parameter(initializer(torch.empty(self.data.user_num, self.latent_size))),
            'item_emb': nn.Parameter(initializer(torch.empty(self.data.item_num, self.latent_size))),
        })
        return embedding_dict

    def forward(self, norm_adj=False):
        if norm_adj is not False:
            self.sparse_norm_adj = norm_adj
        else:
            self.sparse_norm_adj = TorchGraphInterface.convert_sparse_mat_to_tensor(self.norm_adj).cuda()
        ego_embeddings = torch.cat([self.embedding_dict['user_emb'], self.embedding_dict['item_emb']], 0)
        all_embeddings = []
        # all_embeddings = []
        for k in range(self.layers):
            ego_embeddings = torch.sparse.mm(self.sparse_norm_adj, ego_embeddings)
            all_embeddings += [ego_embeddings]
        all_embeddings = torch.stack(all_embeddings, dim=1)
        all_embeddings = torch.mean(all_embeddings, dim=1)
        user_all_embeddings = all_embeddings[:self.data.user_num]
        item_all_embeddings = all_embeddings[self.data.user_num:]
        return user_all_embeddings, item_all_embeddings



class VGAE(nn.Module):
    def __init__(self,data,conf):
        super(VGAE, self).__init__()
        self.emb_dim = 64
        self.data = data
        self.temp = float(conf['-tau2'])
        #encoder_init
        self.graphConv = LGCN_Encoder(self.data, self.emb_dim, 2)  #2_layer_lightGCN
        self.encoder_mean = nn.Sequential(nn.Linear(self.emb_dim, self.emb_dim), nn.ReLU(inplace=True),
                                          nn.Linear(self.emb_dim, self.emb_dim))
        self.encoder_std = nn.Sequential(nn.Linear(self.emb_dim, self.emb_dim), nn.ReLU(inplace=True),
                                         nn.Linear(self.emb_dim, self.emb_dim),nn.Softplus())
        #decoder_init
        self.decoder = nn.Sequential(nn.ReLU(inplace=True), nn.Linear(self.emb_dim, self.emb_dim),
                                     nn.ReLU(inplace=True),nn.Linear(self.emb_dim, 1))
        self.sigmoid = nn.Sigmoid()
        self.bceloss = nn.BCELoss(reduction='none')

        # generate_init
        self.norm_adj_coo = self.data.norm_adj.tocoo()
        self.edge_index = torch.tensor([self.norm_adj_coo.row, self.norm_adj_coo.col], dtype=torch.int64,device=device)
        self.vals = torch.tensor(self.norm_adj_coo.data,device=device)

    def forward(self, user_idx, pos_idx, neg_idx):
        # encode
        user_emb, item_emb = self.graphConv()
        x = torch.cat((user_emb, item_emb), dim=0)
        x_mean = self.encoder_mean(x)
        x_std = self.encoder_std(x)
        gaussian_noise = torch.randn(x_mean.shape).cuda()
        x = gaussian_noise * x_std + x_mean

        #decode
        x_user, x_item = torch.split(x, [self.data.user_num, self.data.item_num], dim=0)
        edge_pos_pred = self.sigmoid(self.decoder(x_user[user_idx] * x_item[pos_idx]))
        edge_neg_pred = self.sigmoid(self.decoder(x_user[user_idx] * x_item[neg_idx]))

        loss_edge_pos = self.bceloss(edge_pos_pred, torch.ones(edge_pos_pred.shape).cuda())
        loss_edge_neg = self.bceloss(edge_neg_pred, torch.zeros(edge_neg_pred.shape).cuda())
        loss_recon = loss_edge_pos + loss_edge_neg

        kl_divergence = - 0.5 * (1 + 2 * torch.log(x_std) - x_mean ** 2 - x_std ** 2).sum(dim=1)
        loss_bpr = batch_softmax_loss(x_user[user_idx], x_item[pos_idx], self.temp)
        beta = 0.1
        loss = (loss_recon + beta * kl_divergence.mean() + loss_bpr).mean()

        # generate adj
        edge_pred = self.sigmoid(self.decoder(x[self.edge_index[0]] * x[self.edge_index[1]]))[:,0]
        mask = ((edge_pred + 0.5).floor()).type(torch.bool).to(device)
        newVals = self.vals[mask] / (mask.sum() / self.vals.shape[0])
        newIdxs = self.edge_index[:,mask]
        new_norm_adj = torch.sparse.FloatTensor(newIdxs, newVals, self.data.norm_adj.shape)

        return loss, new_norm_adj

adapgcl

class KGFCL(GraphRecommender):
    def __init__(self, conf, training_set, test_set):
        super(KGFCL, self).__init__(conf, training_set, test_set)
        args = OptionConf(self.config['KGFCL'])
        self.cl_rate = float(args['-lambda'])
        self.eps = float(args['-eps'])
        self.temp1 = float(args['-tau1'])
        self.temp2 = float(args['-tau2'])
        self.n_layers = int(args['-n_layer'])
        self.layer_cl = int(args['-l*'])
        self.loss = []
        self.norm_adj_coo = self.data.norm_adj.tocoo()
        edge_index = torch.tensor([self.norm_adj_coo.row, self.norm_adj_coo.col], dtype=torch.int64,device=device)
        vals = torch.tensor(self.norm_adj_coo.data,device=device)
        self.recon_model = AdapGCL_Encoder(self.data, self.emb_size, self.eps, self.n_layers, args, edge_index, vals)
        self.model = SimGCL_Encoder(self.data, self.emb_size, self.eps, self.n_layers)

    def train(self):
        recon_model = self.recon_model.cuda()
        model = self.model.cuda()
        optimizer1 = torch.optim.Adam(recon_model.parameters(), lr=self.lRate)
        optimizer2 = torch.optim.Adam(model.parameters(), lr=self.lRate)
        self.select_ids(500)
        for epoch in range(self.maxEpoch):
            for n, batch in enumerate(next_batch_pairwise(self.data, self.batch_size)):
                user_idx, pos_idx, neg_idx = batch
                recon_user_emb, recon_item_emb, loss_recon, newAdj = recon_model(user_idx, pos_idx, neg_idx)
                rec_user_emb, rec_item_emb = model(perturbed=True)
                cl_user_emb, cl_item_emb = model(newAdj,perturbed=True)
                user_emb, pos_item_emb = rec_user_emb[user_idx], rec_item_emb[pos_idx]
                # rec_loss = batch_softmax_loss(user_emb, pos_item_emb, self.temp2)
                rec_loss = self.weighted_SSM(user_emb,pos_item_emb,user_idx,self.temp2)
                cl_loss =  self.cl_rate * self.cal_cl_loss([user_idx,pos_idx],rec_user_emb,cl_user_emb,rec_item_emb,cl_item_emb)
                batch_loss =  rec_loss + l2_reg_loss(self.reg, user_emb, pos_item_emb) + cl_loss + loss_recon
                # Backward and optimize
                optimizer1.zero_grad()
                optimizer2.zero_grad()
                batch_loss.backward()
                optimizer1.step()
                optimizer2.step()
                if n % 100==0 and n>0:
                    print('training:', epoch + 1, 'batch', n, 'rec_loss:', rec_loss.item(), 'cl_loss', cl_loss.item())

            with torch.no_grad():
                self.user_emb, self.item_emb = self.model()
                self.evalAU(self.user_emb,self.item_emb,epoch)
                self.evalSim(self.user_emb,self.item_emb,epoch)
            self.fast_evaluation(epoch)
        self.user_emb, self.item_emb = self.best_user_emb, self.best_item_emb


    def cal_cl_loss(self, idx, user_view1,user_view2,item_view1,item_view2):
        u_idx = torch.unique(torch.Tensor(idx[0]).type(torch.long)).cuda()
        i_idx = torch.unique(torch.Tensor(idx[1]).type(torch.long)).cuda()
        u_weight = self.data.user_alpha[u_idx].cuda()
        i_weight = self.data.item_alpha[i_idx].cuda()
        user_cl_loss = InfoNCE_weight(user_view1[u_idx], user_view2[u_idx], self.temp1, 1/u_weight)
        item_cl_loss = InfoNCE_weight(item_view1[i_idx], item_view2[i_idx], self.temp1, 1/i_weight)
        # user_cl_loss = InfoNCE(user_view1[u_idx], user_view2[u_idx], self.temp1)
        # item_cl_loss = InfoNCE(item_view1[i_idx], item_view2[i_idx], self.temp1)
        return user_cl_loss + item_cl_loss

    def weighted_SSM(self,user_emb,item_emb,user_idx,temperature):
        u_weight = self.data.user_alpha[user_idx].cuda().detach()
        user_emb, item_emb = F.normalize(user_emb, dim=1), F.normalize(item_emb, dim=1)
        pos_score = (user_emb * item_emb).sum(dim=-1) * u_weight
        pos_score = torch.exp(pos_score / temperature)
        # u_weight = u_weight.unsqueeze(dim=1).expand(u_weight.size(0), u_weight.size(0))
        ttl_score = torch.matmul(user_emb, item_emb.transpose(0, 1)) * u_weight
        ttl_score = torch.exp(ttl_score / temperature)
        loss = -torch.log(pos_score / ttl_score.sum(dim=1) + 10e-6)
        return torch.mean(loss)

    def save(self):
        with torch.no_grad():
            self.best_user_emb, self.best_item_emb = self.model.forward()

    def predict(self, u):
        u = self.data.get_user_id(u)
        score = torch.matmul(self.user_emb[u],self.item_emb.transpose(0, 1))
        return score.cpu().numpy()

    def save_loss(self):
        self.loss = np.array(self.loss).reshape(-1,4)
        df = pd.DataFrame({'rec_loss':self.loss[:,0], 'cl_loss':self.loss[:,1],
                           'hot_uniform_loss':self.loss[:,2], 'random_loss':self.loss[:,3]})
        file_name = self.output['-dir'] + self.config['model.name'] + '_loss.csv'
        df.to_csv(file_name, index=False)


class AdapGCL_Encoder(nn.Module):
    def __init__(self, data, emb_size, eps, n_layers,conf, edge_index, vals):
        super(AdapGCL_Encoder, self).__init__()
        self.data = data
        self.eps = eps
        self.emb_size = emb_size
        self.n_layers = n_layers
        self.norm_adj = data.norm_adj
        self.embedding_dict = self._init_model()
        self.sparse_norm_adj = TorchGraphInterface.convert_sparse_mat_to_tensor(self.norm_adj).cuda()

        # decoder_init
        self.decoder = nn.Sequential(nn.ReLU(inplace=True), nn.Linear(self.emb_size, self.emb_size),
                                     nn.ReLU(inplace=True), nn.Linear(self.emb_size, 1))
        self.sigmoid = nn.Sigmoid()
        self.bceloss = nn.BCELoss(reduction='none')
        # generate_init
        self.edge_index = edge_index
        self.vals = vals
        self.temp = float(conf['-tau2'])


    def _init_model(self):
        initializer = nn.init.xavier_uniform_
        embedding_dict = nn.ParameterDict({
            'user_emb': nn.Parameter(initializer(torch.empty(self.data.user_num, self.emb_size))),
            'item_emb': nn.Parameter(initializer(torch.empty(self.data.item_num, self.emb_size))),
        })
        return embedding_dict

    def forward(self, user_idx, pos_idx, neg_idx):
        ego_embeddings = torch.cat([self.embedding_dict['user_emb'], self.embedding_dict['item_emb']], 0)
        all_embeddings = []
        for k in range(self.n_layers):
            ego_embeddings = torch.sparse.mm(self.sparse_norm_adj, ego_embeddings)
            random_noise = torch.rand_like(ego_embeddings).cuda()
            ego_embeddings += torch.sign(ego_embeddings) * F.normalize(random_noise, dim=-1) * self.eps
            all_embeddings.append(ego_embeddings)
        all_embeddings = torch.stack(all_embeddings, dim=1)
        all_embeddings = torch.mean(all_embeddings, dim=1)
        user_all_embeddings, item_all_embeddings = torch.split(all_embeddings, [self.data.user_num, self.data.item_num])

        # get new adj
        edge_pred = self.sigmoid(self.decoder(all_embeddings[self.edge_index[0]] * all_embeddings[self.edge_index[1]]))[:, 0]
        mask = ((edge_pred + 0.5).floor()).type(torch.bool).to(device)
        newVals = self.vals[mask] / (mask.sum() / self.vals.shape[0])
        newIdxs = self.edge_index[:, mask]
        new_norm_adj = torch.sparse.FloatTensor(newIdxs, newVals, self.data.norm_adj.shape)

        # get vgae loss
        edge_pos_pred = self.sigmoid(self.decoder(user_all_embeddings[user_idx] * item_all_embeddings[pos_idx]))
        edge_neg_pred = self.sigmoid(self.decoder(user_all_embeddings[user_idx] * item_all_embeddings[neg_idx]))

        loss_edge_pos = self.bceloss(edge_pos_pred, torch.ones(edge_pos_pred.shape).cuda())
        loss_edge_neg = self.bceloss(edge_neg_pred, torch.zeros(edge_neg_pred.shape).cuda())
        loss_edge = loss_edge_pos + loss_edge_neg
        # kl_divergence = - 0.5 * (1 + 2 * torch.log(x_std) - x_mean ** 2 - x_std ** 2).sum(dim=1)
        def kl_divergence(mu, logvar):
            return -0.5 * torch.sum(1 + logvar - mu.pow(2) - logvar.exp())

        # 计算all_embeddings的均值和方差
        mean_all_embeddings = torch.mean(all_embeddings, dim=0)
        var_all_embeddings = torch.var(all_embeddings, dim=0)

        # 计算标准正态分布的均值和方差
        mean_standard_normal = torch.zeros_like(mean_all_embeddings).cuda()
        var_standard_normal = torch.ones_like(var_all_embeddings).cuda()

        # 计算KL散度
        kl_loss = kl_divergence(mean_all_embeddings, var_all_embeddings) - kl_divergence(mean_standard_normal,var_standard_normal)

        loss_ssm = batch_softmax_loss(user_all_embeddings[user_idx], item_all_embeddings[pos_idx], self.temp)
        loss_recon = (loss_edge  + loss_ssm).mean() + 0.1 * kl_loss.mean()

        return user_all_embeddings, item_all_embeddings, loss_recon, new_norm_adj


class SimGCL_Encoder(nn.Module):
    def __init__(self, data, emb_size, eps, n_layers):
        super(SimGCL_Encoder, self).__init__()
        self.data = data
        self.eps = eps
        self.emb_size = emb_size
        self.n_layers = n_layers
        self.norm_adj = data.norm_adj
        self.embedding_dict = self._init_model()
        self.sparse_norm_adj = TorchGraphInterface.convert_sparse_mat_to_tensor(self.norm_adj).cuda()

    def _init_model(self):
        initializer = nn.init.xavier_uniform_
        embedding_dict = nn.ParameterDict({
            'user_emb': nn.Parameter(initializer(torch.empty(self.data.user_num, self.emb_size))),
            'item_emb': nn.Parameter(initializer(torch.empty(self.data.item_num, self.emb_size))),
        })
        return embedding_dict

    def forward(self,norm_adj=None,perturbed=False):
        if norm_adj is not None:
            self.sparse_norm_adj = norm_adj
        ego_embeddings = torch.cat([self.embedding_dict['user_emb'], self.embedding_dict['item_emb']], 0)
        all_embeddings = []
        for k in range(self.n_layers):
            ego_embeddings = torch.sparse.mm(self.sparse_norm_adj, ego_embeddings)
            if perturbed:
                random_noise = torch.rand_like(ego_embeddings).cuda()
                ego_embeddings += torch.sign(ego_embeddings) * F.normalize(random_noise, dim=-1) * self.eps
            all_embeddings.append(ego_embeddings)
        all_embeddings = torch.stack(all_embeddings, dim=1)
        all_embeddings = torch.mean(all_embeddings, dim=1)
        user_all_embeddings, item_all_embeddings = torch.split(all_embeddings, [self.data.user_num, self.data.item_num])
        return user_all_embeddings, item_all_embeddings

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值