123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449 |
- from models import *
- import os
- import sys
- import shutil
- import logging
- import numpy as np
- import random
- import copy
- from operator import itemgetter
- import gc
-
-
- class Trainer:
- def __init__(self, data_loaders, itemnum, parameter,user_train_num,user_train):
- # print(user_train)
- self.parameter = parameter
- # data loader
- self.train_data_loader = data_loaders[0]
- self.dev_data_loader = data_loaders[1]
- self.test_data_loader = data_loaders[2]
- # parameters
- self.batch_size = parameter['batch_size']
- self.learning_rate = parameter['learning_rate']
- self.epoch = parameter['epoch']
- self.device = parameter['device']
- self.MetaTL = MetaTL(itemnum, parameter)
- self.MetaTL.to(parameter['device'])
- self.optimizer = torch.optim.Adam(self.MetaTL.parameters(), self.learning_rate)
- if parameter['eval_epoch']:
- self.eval_epoch = parameter['eval_epoch']
- else:
- self.eval_epoch = 1000
-
- self.varset_size = parameter['varset_size']
- self.user_train = user_train
- self.warmup = parameter['warmup']
- self.alpha = parameter['alpha']
- self.S1 = parameter['S1']
- self.S2_div_S1 = parameter['S2_div_S1']
- self.temperature = parameter['temperature']
- self.itemnum = itemnum
- self.user_train_num = user_train_num
- # init the two candidate sets for monitoring variance
- self.candidate_cur = np.random.choice(itemnum, [user_train_num + 1, self.varset_size])
- # for i in range(1,user_train_num+1):
- # for j in range(self.varset_size):
- # while self.candidate_cur[i, j] in user_train[i]:
- # self.candidate_cur[i, j] = random.randint(1, itemnum)
-
- # self.candidate_nxt = [np.random.choice(itemnum, [user_train_num+1, self.varset_size]) for _ in range(5)]
- # for c in range(5):
- # for i in range(1,user_train_num+1):
- # for j in range(self.varset_size):
- # while self.candidate_nxt[c][i, j] in user_train[i]:
- # self.candidate_nxt[c][i, j] = random.randint(1, itemnum)
-
- self.Mu_idx = {}
- for i in range(user_train_num + 1):
- Mu_idx_tmp = random.sample(list(range(self.varset_size)), self.S1)
- self.Mu_idx[i] = Mu_idx_tmp
-
- # todo : calculate score of positive items
- self.score_cand_cur = {}
- self.score_pos_cur = {}
-
- # final candidate after execution of change_mu (after one_step) (for later epochs)
- self.final_negative_items = {}
-
- def change_mu(self, p_score, n_score, epoch_cur, users, train_task):
- negitems = {}
- negitems_candidates_all = {}
-
- # for i in users:
- # negitems_candidates_all[i] = self.Mu_idx[i]
- negitems_candidates_all = self.Mu_idx.copy()
-
- ratings_positems = p_score.cpu().detach().numpy()
- ratings_positems = np.reshape(ratings_positems, [-1])
-
- # added
- cnt = 0
- for i in users:
- self.score_pos_cur[i] = ratings_positems[cnt]
- cnt += 1
-
- Mu_items_all = {index: value[negitems_candidates_all[i]] for index, value in enumerate(self.candidate_cur)}
-
- task = np.array(train_task[2])
- task = np.tile(task, reps=(1, self.S1, 1))
- task[:, :, 2] = np.array(itemgetter(*users)(Mu_items_all))
-
- ratings_candidates_all = self.MetaTL.fast_forward(task, users)
-
- hisscore_candidates_all = [self.score_cand_cur[i][:, negitems_candidates_all[i]] for user in users]
- hisscore_pos_all = ratings_positems.copy()
-
- hisscore_candidates_all = np.array(hisscore_candidates_all).transpose((1, 0, 2))
-
- hisscore_pos_all = np.array(hisscore_pos_all)
- hisscore_pos_all = hisscore_pos_all[:, np.newaxis]
- hisscore_pos_all = np.tile(hisscore_pos_all, (hisscore_candidates_all.shape[0], 1, 1))
-
- hislikelihood_candidates_all = 1 / (1 + np.exp(hisscore_pos_all - hisscore_candidates_all))
-
- mean_candidates_all = np.mean(hislikelihood_candidates_all[:, :], axis=0)
- variance_candidates_all = np.zeros(mean_candidates_all.shape)
- for i in range(hislikelihood_candidates_all.shape[0]):
- variance_candidates_all += (hislikelihood_candidates_all[i, :, :] - mean_candidates_all) ** 2
- variance_candidates_all = np.sqrt(variance_candidates_all / hislikelihood_candidates_all.shape[0])
-
- likelihood_candidates_all = \
- 1 / (1 + np.exp(np.expand_dims(ratings_positems, -1) - ratings_candidates_all))
-
- # Top sampling strategy by score + alpha * std
- item_arg_all = None
- if self.alpha >= 0:
- # item_arg_all = np.argmax(likelihood_candidates_all +
- # self.alpha * min(1, epoch_cur / self.warmup)
- # * variance_candidates_all, axis=1)
- a = likelihood_candidates_all + self.alpha * min(1, epoch_cur / self.warmup) * variance_candidates_all
- item_arg_all = np.argpartition(a, kth=(-2), axis=1)
- item_arg_all = np.array(item_arg_all)[:, -2:]
- else:
- item_arg_all = np.argmax(variance_candidates_all, axis=1)
-
- # negitems = { user : self.candidate_cur[user][negitems_candidates_all[user][item_arg_all[index]]] for index,user in enumerate(users)}
- negitems0 = { user : self.candidate_cur[user][negitems_candidates_all[user][item_arg_all[index][0]]] for index,user in enumerate(users)}
- negitems1 = { user : self.candidate_cur[user][negitems_candidates_all[user][item_arg_all[index][1]]] for index,user in enumerate(users)}
-
- ###############################
- for i in users:
- self.final_negative_items[i] = [negitems0[i],negitems1[i]]
- ###############################
-
- # update Mu
- negitems_mu_candidates = {}
-
- for i in users:
- Mu_set = set(self.Mu_idx[i])
-
- while len(self.Mu_idx[i]) < self.S1 * (1 + self.S2_div_S1):
- random_item = random.randint(0, self.candidate_cur.shape[1] - 1)
- while random_item in Mu_set:
- random_item = random.randint(0, self.candidate_cur.shape[1] - 1)
- self.Mu_idx[i].append(random_item)
-
- negitems_mu_candidates[i] = self.Mu_idx[i]
-
- negitems_mu = {}
- negitems_mu = {user:self.candidate_cur[user][negitems_mu_candidates[user]] for user in users}
-
- task = np.array(train_task[2])
- task = np.tile(task, reps=(1, self.S1 * (1 + self.S2_div_S1), 1))
- task[:, :, 2] = np.array(itemgetter(*users)(negitems_mu))
-
- ratings_mu_candidates = self.MetaTL.fast_forward(task, users)
- ratings_mu_candidates = ratings_mu_candidates / self.temperature
-
- if np.any(np.isnan(ratings_mu_candidates)):
- print("nan happend in ratings_mu_candidates")
- ratings_mu_candidates = np.nan_to_num(ratings_mu_candidates)
-
- ratings_mu_candidates = np.exp(ratings_mu_candidates) / np.reshape(
- np.sum(np.exp(ratings_mu_candidates), axis=1), [-1, 1])
-
- if np.any(np.isnan(ratings_mu_candidates)):
- print("nan happend__2 in ratings_mu_candidates")
- ratings_mu_candidates = self.MetaTL.fast_forward(task, users)
- ratings_mu_candidates = ratings_mu_candidates / self.temperature
- ratings_mu_candidates = ratings_mu_candidates + 10
- ratings_mu_candidates = np.exp(ratings_mu_candidates) / np.reshape(
- np.sum(np.exp(ratings_mu_candidates), axis=1), [-1, 1])
-
- user_set = set()
- cnt = 0
- for i in users:
- if i in user_set:
- continue
- else:
- user_set.add(i)
- cache_arg = np.random.choice(self.S1 * (1 + self.S2_div_S1), self.S1,
- p=ratings_mu_candidates[cnt], replace=False)
- self.Mu_idx[i] = np.array(self.Mu_idx[i])[cache_arg].tolist()
- cnt += 1
-
- second_cand = 0
- del negitems, ratings_positems, Mu_items_all, task, ratings_candidates_all, hisscore_candidates_all, hisscore_pos_all
- del hislikelihood_candidates_all, mean_candidates_all, variance_candidates_all, likelihood_candidates_all, second_cand
- del negitems_mu, ratings_mu_candidates, user_set
- gc.collect()
-
- def change_candidate(self, epoch_count):
- score_1epoch_nxt = []
- for c in range(5):
- # todo: implement proper funciton
- pred = self.MetaTL(self.MetaTL.rel_q_sharing.keys(), self.candidate_nxt[c])
- score_1epoch_nxt.append(np.array(pred))
-
- # score_1epoch_nxt.append(np.array(/
- # [EvalUser.predict_fast(model, sess, num_user, num_item, parallel_users=100,
- # predict_data=candidate_nxt[c])]))
- # score_1epoch_pos = np.array(
- # [EvalUser.predict_pos(model, sess, num_user, max_posid, parallel_users=100, predict_data=train_pos)])
- # todo: implement proper function
- score_1epoch_pos = self.MetaTL(user_train, train_data)
-
- # delete the score_cand_cur[0,:,:] at the earlist timestamp
- if epoch_count >= 5 or epoch_count == 0:
- self.score_pos_cur = np.delete(self.score_pos_cur, 0, 0)
-
- for c in range(5):
- self.score_cand_nxt[c] = np.concatenate([self.score_cand_nxt[c], score_1epoch_nxt[c]], axis=0)
- self.score_pos_cur = np.concatenate([self.score_pos_cur, score_1epoch_pos], axis=0)
-
- score_cand_cur = np.copy(self.score_cand_nxt[0])
- candidate_cur = np.copy(self.candidate_nxt[0])
- for c in range(4):
- self.candidate_nxt[c] = np.copy(self.candidate_nxt[c + 1])
- self.score_cand_nxt[c] = np.copy(self.score_cand_nxt[c + 1])
- self.candidate_nxt[4] = np.random.choice(list(range(1, self.itemnum)), [self.user_train_num, self.varset_size])
- for i in range(self.user_train_num):
- for j in range(self.varset_size):
- while self.candidate_nxt[4][i, j] in self.user_train[i]:
- self.candidate_nxt[4][i, j] = random.randint(0, self.itemnum - 1)
- self.score_cand_nxt[4] = np.delete(self.score_cand_nxt[4], list(range(5)), 0)
-
- def rank_predict(self, data, x, ranks):
- # query_idx is the idx of positive score
- query_idx = x.shape[0] - 1
- # sort all scores with descending, because more plausible triple has higher score
- _, idx = torch.sort(x, descending=True)
- rank = list(idx.cpu().numpy()).index(query_idx) + 1
- ranks.append(rank)
- # update data
- if rank <= 10:
- data['Hits@10'] += 1
- data['NDCG@10'] += 1 / np.log2(rank + 1)
- if rank <= 5:
- data['Hits@5'] += 1
- data['NDCG@5'] += 1 / np.log2(rank + 1)
- if rank == 1:
- data['Hits@1'] += 1
- data['NDCG@1'] += 1 / np.log2(rank + 1)
- data['MRR'] += 1.0 / rank
-
- def do_one_step(self, task, iseval=False, curr_rel='', epoch=None, train_task=None, epoch_count=None):
- loss, p_score, n_score = 0, 0, 0
-
- if not iseval:
- task_new = copy.deepcopy(np.array(task[2]))
-
- cnt = 0
-
- for user in curr_rel:
- if user in self.final_negative_items:
- for index, t in enumerate(task[1][cnt]):
- if index % 2 == 0:
- t[2] = self.final_negative_items[user][0]
- else:
- t[2] = self.final_negative_items[user][1]
- cnt += 1
-
- self.optimizer.zero_grad()
- p_score, n_score = self.MetaTL(task, iseval, curr_rel)
- y = torch.Tensor([1]).to(self.device)
- loss = self.MetaTL.loss_func(p_score, n_score, y)
- loss.backward()
- self.optimizer.step()
-
- # task_new = np.array(task[2])
- task_new = np.tile(task_new, reps=(1, self.varset_size, 1))
- task_new[:, :, 2] = np.array(itemgetter(*curr_rel)(self.candidate_cur))
- data = self.MetaTL.fast_forward(task_new, curr_rel)
-
- # prepare score_cand_cur (make all users to have the same number of history scores)
- temp = min(epoch_count, 4)
- for index, user in enumerate(curr_rel):
- if (not user in self.score_cand_cur):
- self.score_cand_cur[user] = np.array([data[index]])
- elif len(self.score_cand_cur[user]) <= temp:
- self.score_cand_cur[user] = np.concatenate(
- [self.score_cand_cur[user], np.array([data[index]])], axis=0)
-
- self.change_mu(p_score, n_score, epoch_count, curr_rel, task)
-
- elif curr_rel != '':
- p_score, n_score = self.MetaTL(task, iseval, curr_rel)
- y = torch.Tensor([1]).to(self.device)
- loss = self.MetaTL.loss_func(p_score, n_score, y)
- return loss, p_score, n_score
-
- def train(self):
- # initialization
- best_epoch = 0
- best_value = 0
- bad_counts = 0
- epoch_count = 0
-
- # training by epoch
- for e in range(self.epoch):
- if e % 10 == 0: print("epoch:", e)
-
- # sample one batch from data_loader
- train_task, curr_rel = self.train_data_loader.next_batch()
-
- # change task negative samples using mu_idx
- loss, _, _ = self.do_one_step(train_task, iseval=False, curr_rel=curr_rel, epoch=e, train_task=train_task,
- epoch_count=epoch_count)
-
- # after ten epoch epoch
- if (e % 2500 == 0) and e != 0:
- # init the two candidate sets for monitoring variance
- self.candidate_cur = np.random.choice(self.itemnum, [self.user_train_num + 1, self.varset_size])
- for i in range(1, self.user_train_num + 1):
- for j in range(self.varset_size):
- while self.candidate_cur[i, j] in self.user_train[i]:
- self.candidate_cur[i, j] = random.randint(1, self.itemnum)
-
- self.Mu_idx = {}
- for i in range(self.user_train_num + 1):
- Mu_idx_tmp = random.sample(list(range(self.varset_size)), self.S1)
- self.Mu_idx[i] = Mu_idx_tmp
-
- self.score_cand_cur = {}
- self.score_pos_cur = {}
- self.final_negative_items = {}
-
- # reset epoch_count has many effects on the chnage_mu and one_step and train function
- epoch_count = 0
-
- # after one epoch
- elif e % 25 == 0 and e != 0:
- self.check_complenteness(epoch_count)
- print("epoch_count:", epoch_count)
- print("=========================\n\n")
- epoch_count += 1
-
- # do evaluation on specific epoch
- if e % self.eval_epoch == 0 and e != 0:
- loss_num = loss.detach().item()
- print("Epoch: {}\tLoss: {:.4f}".format(e, loss_num))
-
- print('Epoch {} Validating...'.format(e))
- valid_data = self.eval(istest=False, epoch=e)
-
- print('Epoch {} Testing...'.format(e))
- test_data = self.eval(istest=True, epoch=e)
-
- # original = r'/content/results.txt'
- # target = r'/content/drive/MyDrive/MetaTL/MetaTL_v3/results.txt'
- # shutil.copyfile(original, target)
-
- # print(self.candidate_cur[curr_rel[0]],self.score_cand_cur[curr_rel[0]])
- print('Finish')
-
- def eval(self, istest=False, epoch=None):
- torch.backends.cudnn.enabled = False
- self.MetaTL.eval()
-
- self.MetaTL.rel_q_sharing = dict()
-
- if istest:
- data_loader = self.test_data_loader
- else:
- data_loader = self.dev_data_loader
- data_loader.curr_tri_idx = 0
-
- # initial return data of validation
- data = {'MRR': 0, 'Hits@1': 0, 'Hits@5': 0, 'Hits@10': 0, 'NDCG@1': 0, 'NDCG@5': 0, 'NDCG@10': 0}
- ranks = []
-
- t = 0
- temp = dict()
- total_loss = 0
- while True:
- # sample all the eval tasks
- eval_task, curr_rel = data_loader.next_one_on_eval()
- # at the end of sample tasks, a symbol 'EOT' will return
- if eval_task == 'EOT':
- break
- t += 1
-
- loss, p_score, n_score = self.do_one_step(eval_task, iseval=True, curr_rel=curr_rel)
- total_loss += loss
-
- x = torch.cat([n_score, p_score], 1).squeeze()
-
- self.rank_predict(data, x, ranks)
-
- # print current temp data dynamically
- for k in data.keys():
- temp[k] = data[k] / t
-
- # print overall evaluation result and return it
- for k in data.keys():
- data[k] = round(data[k] / t, 3)
-
- print("\n")
- if istest:
- print("TEST: \t test_loss: ", total_loss.detach().item())
- print(
- "TEST: \tMRR: {:.3f}\tNDCG@10: {:.3f}\tNDCG@5: {:.3f}\tNDCG@1: {:.3f}\tHits@10: {:.3f}\tHits@5: {:.3f}\tHits@1: {:.3f}\r".format(
- temp['MRR'], temp['NDCG@10'], temp['NDCG@5'], temp['NDCG@1'], temp['Hits@10'], temp['Hits@5'],
- temp['Hits@1']))
- with open('results2.txt', 'a') as f:
- f.writelines(
- "TEST: \tMRR: {:.3f}\tNDCG@10: {:.3f}\tNDCG@5: {:.3f}\tNDCG@1: {:.3f}\tHits@10: {:.3f}\tHits@5: {:.3f}\tHits@1: {:.3f}\r\n\n".format(
- temp['MRR'], temp['NDCG@10'], temp['NDCG@5'], temp['NDCG@1'], temp['Hits@10'], temp['Hits@5'],
- temp['Hits@1']))
- else:
- print("VALID: \t validation_loss: ", total_loss.detach().item())
- print(
- "VALID: \tMRR: {:.3f}\tNDCG@10: {:.3f}\tNDCG@5: {:.3f}\tNDCG@1: {:.3f}\tHits@10: {:.3f}\tHits@5: {:.3f}\tHits@1: {:.3f}\r".format(
- temp['MRR'], temp['NDCG@10'], temp['NDCG@5'], temp['NDCG@1'], temp['Hits@10'], temp['Hits@5'],
- temp['Hits@1']))
- with open("results2.txt", 'a') as f:
- f.writelines(
- "VALID: \tMRR: {:.3f}\tNDCG@10: {:.3f}\tNDCG@5: {:.3f}\tNDCG@1: {:.3f}\tHits@10: {:.3f}\tHits@5: {:.3f}\tHits@1: {:.3f}\r".format(
- temp['MRR'], temp['NDCG@10'], temp['NDCG@5'], temp['NDCG@1'], temp['Hits@10'], temp['Hits@5'],
- temp['Hits@1']))
-
- print("\n")
- del total_loss, p_score, n_score
- gc.collect()
- self.MetaTL.train()
- torch.backends.cudnn.enabled = True
- return data
-
- def check_complenteness(self, epoch_count):
-
- # un_users = set()
-
- for user in list(self.user_train.keys()):
- if not user in self.score_cand_cur:
- self.score_cand_cur[user] = np.array([np.zeros(self.varset_size)])
-
- num = epoch_count - len(self.score_cand_cur[user]) + 1
- if num > 0 and len(self.score_cand_cur[user]) < 5:
- # if num!=1 : print("bug happend1")
- # un_users.add(user)
- self.score_cand_cur[user] = np.concatenate(
- [self.score_cand_cur[user], np.array([self.score_cand_cur[user][-1]])], axis=0)
-
- if epoch_count >= 4:
- t = 0
- for user in list(self.score_cand_cur.keys()):
- t = user
- # self.score_cand_cur[user] = np.delete(self.score_cand_cur[user], 0, 0)
- self.score_cand_cur[user] = self.score_cand_cur[user][-4:]
|