-
Notifications
You must be signed in to change notification settings - Fork 16
/
Copy pathRNN+BPR2.py
167 lines (153 loc) · 6.76 KB
/
RNN+BPR2.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
#!/usr/bin/env python2
# -*- coding: utf-8 -*-
"""
Created on 06/09/2017 9:54 PM
Modified on 25/11/2017 8:37 AM
@author: Tangrizzly
"""
import numpy as np
from datetime import datetime
from numpy.random import rand
# from numpy.random import uniform
from collections import Counter
def sigmoid(x):
return 1 / (1 + np.exp(-x))
class RnnBpr:
def __init__(self, d, dataset, v, maxNo, lmd):
self.d = d # 隐层维度
self.lmd = lmd # lambda
self.dataset = dataset
self.v = v # dataset里的items
self.R = rand(maxNo + 1, d) # 所有item的向量表示
self.M = rand(self.d, self.d) # uniform(-0.5, 0.5, (d, d))指定范围
self.W = rand(self.d, self.d)
def forward_propagation(self, x):
K = len(x)
h = np.zeros((K + 1, self.d))
h[-1] = np.zeros(self.d)
y = np.zeros(K).reshape((K, 1)) # 注意reshape函数的格式
yi = np.zeros(K).reshape((K, 1))
for k in np.arange(K):
h[k] = sigmoid(np.dot(self.M, self.R[x[k]]) + np.dot(self.W, h[k - 1]))
# 之前写的是点乘得到的结果
y[k] = np.sum(h[k - 1] * self.R[x[k]]) # 对正样本的偏好值, 应该是pair-wise乘法之后加和
i = int(np.random.choice(self.v, 1)[0]) # 负样本
yi[k] = np.sum(h[k - 1] * self.R[i]) # 对负样本的偏好值
# y_hat = np.argsort(np.dot(h[K-1], self.R.T))[-10:][::-1] # 将所有的评分进行排序之后取前十,计算量过大
# 先得到前十之后进行排序
scores = np.dot(h[K - 1], self.R.T) # user对所有items的评分
max_10 = np.argpartition(scores, -10)[-10:] # 复杂度O(n),获得评分前十个的index
y_hat = max_10[np.argsort(scores[max_10])][::-1] # 复杂度O(nlogn),得到前十个得分最高的items_ids推荐给用户
return h, y, yi, y_hat # 得到前十个得分最大的对应的item_ids,并且最左边的是得分最大的
def calculate_loss(self, lmd, x):
h, y, yi, y_hat = self.forward_propagation(x)
# l = np.sum(np.log(1 + np.exp(-y + yi))) # ???
l = np.sum(np.log(1 + np.exp(y - yi))) # 对正样本偏好减对负样本偏好
l += lmd / 2 * (np.square(np.linalg.norm(self.R))
+ np.square(np.linalg.norm(self.W))
+ np.square(np.linalg.norm(self.M))
)
return l, y_hat
def bptt(self, x, learning_rate):
# 在此写的是BP,而非BPTT(汇总损失,仅更新一大步)
K = len(x)
h, y, yi, y_hat = self.forward_propagation(x)
j = int(np.random.choice(self.v, 1)[0])
dLdh = self.R[x[K - 1]] - self.R[j]
dLdR = np.zeros(self.R.shape)
for t in np.arange(K - 1)[::-1]:
j = int(np.random.choice(self.v, 1)[0])
Xij = np.dot(h[t], self.R[x[t + 1]].T) - np.dot(h[t], self.R[j].T)
dLdR[x[t + 1]] = h[t]
dLdR[j] = -h[t]
df = np.multiply(h[t], (1 - h[t])).reshape(1, self.d)
dLdM = np.dot(dLdh * df, self.R[x[t]].reshape((self.d, 1)))
dLdW = np.dot(dLdh * df, h[t - 1].reshape((self.d, 1)))
dLdh = np.dot(dLdh * df, self.W.T)
self.M += learning_rate * (np.exp(-Xij) * sigmoid(Xij) * dLdM - self.lmd * self.M)
self.W += learning_rate * (np.exp(-Xij) * sigmoid(Xij) * dLdW - self.lmd * self.W)
self.R[x[t + 1]] += learning_rate * (
np.exp(-Xij) * sigmoid(Xij) * dLdR[x[t + 1]] - self.lmd * self.R[x[t + 1]])
self.R[j] += learning_rate * (np.exp(-Xij) * sigmoid(Xij) * dLdR[j] - self.lmd * self.R[j])
# def train(model, learning_rate=0.002):
# """
# :param model: RnnBpr
# :param learning_rate: 一般采用0.1/0.01这种10倍间隔的,中间值可选用0.05这种。
# :return: recall_rate
# """
# losses = []
# recall = 0
# for i in range(0, len(model.x)):
# xi = model.x[0: i + 1] # shape=(i+1, d)
# loss, y_hat = model.calculate_loss(model.lmd, xi)
# if i < len(model.x) - 1:
# if model.x[i + 1] in y_hat:
# recall += 1
# losses.append(loss)
# time = datetime.now().strftime('%Y-%m-%d %H:%M:%S')
# print "%s: Loss %f, recall %d"% (time, loss, recall)
# model.bptt(xi, learning_rate)
# recall_rate = recall / float(len(model.x) - 1)
# return recall_rate
def train(model, learning_rate=0.01):
"""
:param model: RnnBpr
:param learning_rate: 一般采用0.1/0.01这种10倍间隔的,中间值可选用0.05这种。
:return: recall_rate
"""
example = 0
time = datetime.now().strftime('%Y-%m-%d %H:%M:%S')
print "%s: Loss after example=%d : %f" % (time, example, 0)
for x in model.dataset:
example += 1
recall = 0
for i in range(0, len(x)):
xi = x[0: i + 1] # shape=(i+1, d)
loss, y_hat = model.calculate_loss(model.lmd, xi)
if i < len(x) - 1:
if x[i + 1] in y_hat:
recall += 1
# time = datetime.now().strftime('%Y-%m-%d %H:%M:%S')
# print "%s: Loss %f, recall %d"% (time, loss, recall)
model.bptt(xi, learning_rate)
recall_rate = recall / float(len(x) - 1)
time = datetime.now().strftime('%Y-%m-%d %H:%M:%S')
print "%s: Loss after example=%d : %f" % (time, example, loss)
print "Recall rate: %f" % recall_rate
# 读取用户序列历史, n_user=737
with open('./data/user_cart.json', 'r') as f:
data = f.readlines()
train_orig = [] # 嵌套列表,每个用户的序列=[int1, int2, ...]
temp = []
for line in data:
# line = re.sub('[\[|\]|\s++]', '', line)
# odom = line.split(',')
odem = line[1:][:-2].replace(' ', '').split(',')
if len(odem) < 10:
continue
# numbers_float = map(float, odom)
# numbers = map(int, numbers_float)
numbers = [int(float(i)) for i in odem]
train_orig.append(numbers)
temp.extend(numbers)
# 处理后,n_user=706
# remove items appearing less than 3 times
maxNo = 0
v = []
trainSet = [] # 嵌套列表,每个用户的序列=[int1, int2, ...]
# temp_df = pd.DataFrame(temp)
# lessThan3 = temp_df[0].value_counts()[temp_df[0].value_counts()<3].index[:]
items_count = dict(Counter(temp))
lessThan3 = [item for item, count in items_count.items() if count < 3]
for line in train_orig:
cleaned = [x for x in line if x not in lessThan3]
maxNo = np.max([maxNo, np.max(cleaned)])
v = np.append(v, np.unique(cleaned))
if len(cleaned) >= 10:
trainSet.append(cleaned)
np.random.seed(1)
recall = []
# model可以只建1次,一次性初始化所有的items表达
model = RnnBpr(10, trainSet, v, maxNo, 0.01)
train(model)
# np.savetxt('rnn_bpr_result', np.asarray(recall), fmt='%f')