每个用户抽取一定数量的困难负样本,然后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