# coding: utf-8
from __future__ import print_function
from __future__ import division
import torch
import torch.nn as nn
import torch.nn.functional as F
from libcity.model.abstract_model import AbstractModel
from math import sin, cos, sqrt, atan2, radians
import numpy as np
[docs]def identity_loss(y_true, y_pred):
return torch.mean(y_pred - 0 * y_true)
[docs]class CARA1(nn.Module):
[docs] def hard_sigmoid(self, x):
x = x / 6 + 0.5
x = F.threshold(-x, -1, -1)
x = F.threshold(-x, 0, 0)
return x
def __init__(self, output_dim, input_dim,
init='glorot_uniform', inner_init='orthogonal', device=None,
**kwargs):
super(CARA1, self).__init__()
self.output_dim = output_dim
self.init = init
self.inner_init = inner_init
self.activation = self.hard_sigmoid
self.inner_activation = nn.Tanh()
self.device = device
self.build(input_dim)
[docs] def add_weight(self, shape, initializer):
ts = torch.zeros(shape)
if initializer == 'glorot_uniform':
ts = nn.init.xavier_normal_(ts)
elif initializer == 'orthogonal':
ts = nn.init.orthogonal_(ts)
return nn.Parameter(ts)
[docs] def build(self, input_shape):
# self.input_spec = [InputSpec(shape=input_shape)]
self.input_dim = input_shape
self.W_z = self.add_weight((self.input_dim, self.output_dim),
initializer=self.init)
self.U_z = self.add_weight((self.output_dim, self.output_dim),
initializer=self.init)
self.b_z = self.add_weight((self.output_dim,),
initializer='zero')
self.W_r = self.add_weight((self.input_dim, self.output_dim),
initializer=self.init)
self.U_r = self.add_weight((self.output_dim, self.output_dim),
initializer=self.init)
self.b_r = self.add_weight((self.output_dim,),
initializer='zero')
self.W_h = self.add_weight((self.input_dim, self.output_dim),
initializer=self.init)
self.U_h = self.add_weight((self.output_dim, self.output_dim),
initializer=self.init)
self.b_h = self.add_weight((self.output_dim,),
initializer='zero')
self.A_h = self.add_weight((self.output_dim, self.output_dim),
initializer=self.init)
self.A_u = self.add_weight((self.output_dim, self.output_dim),
initializer=self.init)
self.b_a_h = self.add_weight((self.output_dim,),
initializer='zero')
self.b_a_u = self.add_weight((self.output_dim,),
initializer='zero')
self.W_t = self.add_weight((self.input_dim, self.output_dim),
initializer=self.init)
self.U_t = self.add_weight((1, self.output_dim),
initializer=self.init)
self.b_t = self.add_weight((self.output_dim,),
initializer='zero')
self.W_g = self.add_weight((self.input_dim, self.output_dim),
initializer=self.init)
self.U_g = self.add_weight((1, self.output_dim),
initializer=self.init)
self.b_g = self.add_weight((self.output_dim,),
initializer='zero')
[docs] def forward(self, x):
"""
X : batch * timeLen * dims(有拓展)
"""
tlen = x.shape[1]
output = torch.zeros((x.shape[0], self.output_dim)).to(self.device)
for i in range(tlen):
output = self.step(x[:, i, :], output)
return output
[docs] def step(self, x, states):
"""
用于多批次同一时间
states为上一次多批次统一时间数据
"""
h_tm1 = states
# phi_t
u = x[:, self.output_dim: 2 * self.output_dim]
# delta_t
t = x[:, 2 * self.output_dim: (2 * self.output_dim) + 1]
# delta_g
g = x[:, (2 * self.output_dim) + 1:]
# phi_v
x = x[:, :self.output_dim]
t = self.inner_activation(torch.matmul(t, self.U_t))
g = self.inner_activation(torch.matmul(g, self.U_g))
# Time-based gate
t1 = self.inner_activation(torch.matmul(x, self.W_t) + t + self.b_t)
# Geo-based gate
g1 = self.inner_activation(torch.matmul(x, self.W_g) + g + self.b_g)
# Contextual Attention Gate
a = self.inner_activation(
torch.matmul(h_tm1, self.A_h) + torch.matmul(u, self.A_u) + self.b_a_h + self.b_a_u)
x_z = torch.matmul(x, self.W_z) + self.b_z
x_r = torch.matmul(x, self.W_r) + self.b_r
x_h = torch.matmul(x, self.W_h) + self.b_h
u_z_ = torch.matmul((1 - a) * u, self.W_z) + self.b_z
u_r_ = torch.matmul((1 - a) * u, self.W_r) + self.b_r
u_h_ = torch.matmul((1 - a) * u, self.W_h) + self.b_h
u_z = torch.matmul(a * u, self.W_z) + self.b_z
u_r = torch.matmul(a * u, self.W_r) + self.b_r
u_h = torch.matmul(a * u, self.W_h) + self.b_h
# update gate
z = self.inner_activation(x_z + torch.matmul(h_tm1, self.U_z) + u_z)
# reset gate
r = self.inner_activation(x_r + torch.matmul(h_tm1, self.U_r) + u_r)
# hidden state
hh = self.activation(x_h + torch.matmul(r * t1 * g1 * h_tm1, self.U_h) + u_h)
h = z * h_tm1 + (1 - z) * hh
h = (1 + u_z_ + u_r_ + u_h_) * h
return h
# return h
[docs]def bpr_triplet_loss(x):
positive_item_latent, negative_item_latent = x
reg = 0
loss = 1 - torch.log(torch.sigmoid(
torch.sum(positive_item_latent, dim=-1, keepdim=True) -
torch.sum(negative_item_latent, dim=-1, keepdim=True))) - reg
return loss
[docs]class Recommender(nn.Module):
def __init__(self, num_users, num_items, num_times, latent_dim, maxvenue=5, device=None):
super(Recommender, self).__init__()
self.maxVenue = maxvenue
self.latent_dim = latent_dim
self.device = device
# num * maxVenue * dim
self.U_Embedding = nn.Embedding(num_users, latent_dim)
self.V_Embedding = nn.Embedding(num_items, latent_dim)
self.T_Embedding = nn.Embedding(num_times, latent_dim)
torch.nn.init.uniform_(self.U_Embedding.weight)
torch.nn.init.uniform_(self.V_Embedding.weight)
torch.nn.init.uniform_(self.T_Embedding.weight)
self.rnn = nn.Sequential(
CARA1(latent_dim, latent_dim, device=self.device, input_shape=(self.maxVenue, (self.latent_dim * 2) + 2,),
unroll=True,
))
# latent_dim * 2 + 2 = v_embedding + t_embedding + time_gap + distance
[docs] def forward(self, x):
# INPUT = [self.user_input, self.time_input, self.gap_time_input, self.pos_distance_input,
# self.neg_distance_input, self.checkins_input,
# self.neg_checkins_input]
# pass
# User latent factor
user_input = x[0]
time_input = x[1]
gap_time_input = x[2]
pos_distance_input = x[3]
neg_distance_input = x[4]
checkins_input = x[5]
neg_checkins_input = x[6]
self.u_latent = self.U_Embedding(user_input)
self.t_latent = self.T_Embedding(time_input)
h, w = gap_time_input.shape
gap_time_input = gap_time_input.view(h, w, 1)
rnn_input = torch.cat([self.V_Embedding(checkins_input), self.T_Embedding(time_input), gap_time_input], -1)
neg_rnn_input = torch.cat([self.V_Embedding(neg_checkins_input), self.T_Embedding(time_input), gap_time_input],
-1)
h, w = pos_distance_input.shape
pos_distance_input = pos_distance_input.view(h, w, 1)
h, w = neg_distance_input.shape
neg_distance_input = neg_distance_input.view(h, w, 1)
rnn_input = torch.cat([rnn_input, pos_distance_input], -1)
neg_rnn_input = torch.cat([neg_rnn_input, neg_distance_input], -1)
self.checkins_emb = self.rnn(rnn_input)
self.neg_checkins_emb = self.rnn(neg_rnn_input)
pred = (self.checkins_emb * self.u_latent).sum(dim=1)
neg_pred = (self.neg_checkins_emb * self.u_latent).sum(dim=1)
return bpr_triplet_loss([pred, neg_pred])
[docs] def rank(self, uid, hist_venues, hist_times, hist_time_gap, hist_distances):
# hist_venues = hist_venues + [candidate_venue]
# hist_times = hist_times + [time]
# hist_time_gap = hist_time_gap + [time_gap]
# hist_distances = hist_distances + [distance]
# u_latent = self.U_Embedding(torch.tensor(uid))
# v_latent = self.V_Embedding(torch.tensor(hist_venues))
# t_latent = self.T_Embedding(torch.tensor(hist_times))
u_latent = self.U_Embedding.weight[uid]
v_latent = self.V_Embedding.weight[hist_venues.reshape(-1)].view(hist_venues.shape[0], hist_venues.shape[1], -1)
t_latent = self.T_Embedding.weight[hist_times.reshape(-1)].view(hist_times.shape[0], hist_times.shape[1], -1)
h, w = hist_time_gap.shape
hist_time_gap = hist_time_gap.reshape(h, w, 1)
h, w = hist_distances.shape
hist_distances = hist_distances.reshape(h, w, 1)
rnn_input = torch.cat([t_latent, torch.FloatTensor(hist_time_gap).to(self.device)], dim=-1)
rnn_input = torch.cat([rnn_input, torch.FloatTensor(hist_distances).to(self.device)], dim=-1)
rnn_input = torch.cat([v_latent, rnn_input], dim=-1)
dynamic_latent = self.rnn(rnn_input)
scores = torch.mul(dynamic_latent, u_latent).sum(1)
# scores = np.dot(dynamic_latent, u_latent)
return scores
[docs]class CARA(AbstractModel):
"""rnn model with long-term history attention"""
def __init__(self, config, data_feature):
super(CARA, self).__init__(config, data_feature)
self.loc_size = data_feature['loc_size']
self.tim_size = data_feature['tim_size']
self.uid_size = data_feature['uid_size']
self.id2locid = data_feature['id2locid']
self.id2loc = []
self.device = config['device']
for i in range(self.loc_size - 1):
self.id2loc.append(self.id2locid[str(i)])
self.id2loc.append(self.loc_size)
self.id2loc = np.array(self.id2loc)
self.coor = data_feature['poi_coor']
self.rec = Recommender(self.uid_size, self.loc_size, self.tim_size, 10, device=self.device)
[docs] def get_time_interval(self, x):
y = x[:, :-1]
y = np.concatenate([x[:, 0, None], y], axis=1)
return x - y
[docs] def get_time_interval2(self, x):
y = x[:-1]
y = np.concatenate([x[0, None], y], axis=0)
return x - y
[docs] def get_pos_distance(self, x):
y = np.concatenate([x[:, 0, None, :], x[:, :-1, :]], axis=1)
r = 6373.0
rx = np.radians(x)
ry = np.radians(y)
d = x - y
a = np.sin(d[:, :, 0] / 2) ** 2 + np.cos(rx[:, :, 0]) * np.cos(ry[:, :, 0]) * np.sin(d[:, :, 1] / 2) ** 2
c = 2 * np.arctan2(np.sqrt(a), np.sqrt(1 - a))
return r * c
[docs] def get_pos_distance2(self, x):
x = np.array(x.tolist())
y = np.concatenate([x[0, None, :], x[:-1, :]], axis=0)
r = 6373.0
rx = np.radians(x)
ry = np.radians(y)
d = x - y
a = np.sin(d[:, 0] / 2) ** 2 + np.cos(rx[:, 0]) * np.cos(ry[:, 0]) * np.sin(d[:, 1] / 2) ** 2
c = 2 * np.arctan2(np.sqrt(a), np.sqrt(1-a))
return r * c
[docs] def get_distance(self, lat1, lng1, lat2, lng2):
r = 6373.0
lat1 = radians(lat1)
lon1 = radians(lng1)
lat2 = radians(lat2)
lon2 = radians(lng2)
dlon = lon2 - lon1
dlat = lat2 - lat1
a = sin(dlat / 2) ** 2 + cos(lat1) * cos(lat2) * sin(dlon / 2) ** 2
c = 2 * atan2(sqrt(a), sqrt(1 - a))
distance = int(r * c)
return distance
[docs] def get_neg_checkins(self, vis, x, y):
len1, len2 = x.shape
x_res = []
x_res_distance = y[:].copy()
for i in range(len1):
visits = x[i]
j = np.random.randint(self.loc_size - 1)
while j in vis[i]:
j = np.random.randint(self.loc_size - 1)
tmp = visits[:].copy()
tmp[-1] = j
x_res.append(tmp)
j1 = self.coor[str(self.id2loc[visits[-1]])]
j = self.coor[str(self.id2loc[j])]
x_res_distance[i, -1] = self.get_distance(j1[0], j1[1], j[0], j[1])
return x_res, x_res_distance
[docs] def forward(self, batch):
hloc = np.array(batch['current_loc'].cpu())[:, :5]
target = np.array(batch['target'].cpu())
h = target.shape
target = target.reshape((*h, 1))
hloc = np.concatenate([hloc, target], axis=1)
hloc1 = self.id2loc[hloc]
tloc = np.array(batch['current_tim'].cpu())[:, :5]
target_tim = np.array(batch['target_tim'].cpu())
h = target_tim.shape
target_tim = target_tim.reshape((*h, 1))
tloc = np.concatenate([tloc, target_tim], axis=1)
x_users = batch['uid']
t_interval = self.get_time_interval(tloc)
titude = []
for i in hloc1.reshape(-1):
titude.append(self.coor[str(i)])
titude = np.array(titude).reshape((hloc.shape[0], hloc.shape[1], 2))
pos_distance = self.get_pos_distance(titude)
x_neg_checkins, x_neg_distance = self.get_neg_checkins(np.array(batch['current_loc'].cpu()), hloc, pos_distance)
x_neg_checkins = np.array(x_neg_checkins)
x = [x_users, torch.tensor(tloc).to(self.device),
torch.FloatTensor(t_interval).to(self.device), torch.FloatTensor(pos_distance).to(self.device),
torch.FloatTensor(x_neg_distance).to(self.device), torch.tensor(hloc).to(self.device),
torch.tensor(x_neg_checkins).to(self.device)]
return self.rec(x)
[docs] def predict(self, batch):
hloc = np.array(batch['current_loc'].cpu())[:, :5]
tloc = np.array(batch['current_tim'].cpu())[:, :5]
x_users = batch['uid']
my_true = batch['target']
my_true_tim = batch['target_tim']
output = []
for id, mloc in enumerate(hloc):
hlocs = []
tlocs = []
users = []
t_intervals = []
distances = []
target = my_true[id].item()
target_tim = my_true_tim[id].item()
mu = x_users[id]
mh, mt = hloc[id], tloc[id].copy()
mt = np.append(mt, target_tim)
mi = self.get_time_interval2(mt)
for i in range(101):
mh = hloc[id].copy()
if i == 0:
mh = np.append(mh, target)
tmh = self.id2loc[mh]
mtt = []
for i in tmh.reshape(-1):
mtt.append(self.coor[str(i)])
mtt = np.array(mtt).reshape((tmh.shape[0], 2))
md = self.get_pos_distance2(mtt)
else:
j = target
while j == target:
j = np.random.randint(0, self.loc_size - 1)
mh = np.append(mh, j)
tmh = self.id2loc[mh]
mtt = []
for i in tmh.reshape(-1):
mtt.append(self.coor[str(i)])
mtt = np.array(mtt).reshape((tmh.shape[0], 2))
md = self.get_pos_distance2(mtt)
hlocs.append(mh)
tlocs.append(mt)
users.append(mu.item())
t_intervals.append(mi)
distances.append(md)
output.append(self.rec.rank(np.array(users), np.array(hlocs), np.array(tlocs), np.array(t_intervals),
np.array(distances)).cpu().detach().numpy())
output = np.array(output)
return torch.tensor(output).to(self.device)
[docs] def calculate_loss(self, batch):
return torch.mean(self.forward(batch))