Skip to content

Commit 87c322d

Browse files
committed
RFCT Cleaner neighbor correlation code
1 parent de74835 commit 87c322d

2 files changed

Lines changed: 66 additions & 68 deletions

File tree

‎ch08/corrneighbours.py‎

Lines changed: 36 additions & 68 deletions
Original file line numberDiff line numberDiff line change
@@ -6,75 +6,43 @@
66
# It is made available under the MIT License
77

88
from __future__ import print_function
9-
from all_correlations import all_correlations
109
import numpy as np
11-
from load_ml100k import load
12-
13-
def estimate_user(user, rest, num_neighbors=100):
14-
'''Estimate ratings for user based on the binary rating matrix
15-
16-
Returns
17-
-------
18-
estimates: ndarray
19-
Returns a rating estimate for each movie
20-
'''
21-
22-
# Compute binary matrix correlations:
23-
bu = user > 0
24-
br = rest > 0
25-
ws = all_correlations(bu, br)
26-
27-
# Select top `num_neighbors`:
28-
selected = ws.argsort()[-num_neighbors:]
29-
30-
# Use these to compute estimates:
31-
estimates = rest[selected].mean(0)
32-
estimates /= (.1 + br[selected].mean(0))
33-
return estimates
34-
35-
36-
def train_test(user, rest):
37-
'''Train & test on a single user
38-
39-
Returns both the prediction error and the null error
40-
'''
41-
estimates = estimate_user(user, rest)
42-
bu = user > 0
43-
br = rest > 0
44-
err = estimates[bu] - user[bu]
45-
null = rest.mean(0)
46-
null /= (.1 + br.mean(0))
47-
nerr = null[bu] - user[bu]
48-
return np.dot(err, err), np.dot(nerr, nerr)
49-
50-
51-
def all_estimates(reviews):
52-
estimates = np.zeros_like(reviews)
53-
for i in range(reviews.shape[0]):
54-
estimates[i] = estimate_user(reviews[i], np.delete(reviews, i, 0))
55-
return estimates
56-
57-
def main():
58-
reviews = load()
59-
60-
err = []
61-
for i in range(reviews.shape[0]):
62-
err.append(
63-
train_test(reviews[i], np.delete(reviews, i, 0))
64-
)
65-
revs = (reviews > 0).sum(1)
66-
err = np.array(err)
67-
rmse = np.sqrt(err / revs[:, None])
68-
69-
rmse_model, rmse_null = np.mean(rmse, 0)
70-
71-
print("Average of RMSE / Null-model RMSE")
72-
print("{:.2}\t{:.2} (improvement: {:.1%}".format(rmse_model, rmse_null, (rmse_null-rmse_model)/rmse_null))
73-
print()
74-
75-
rmse_model, rmse_null = np.mean(rmse[revs > 60], 0)
76-
print("Average of RMSE / Null-model RMSE (users with more than 60 reviewed movies)")
77-
print("{:.2}\t{:.2} (improvement: {:.1%}".format(rmse_model, rmse_null, (rmse_null-rmse_model)/rmse_null))
10+
from load_ml100k import get_train_test
11+
from scipy.spatial import distance
12+
from sklearn import metrics
13+
14+
from norm import NormalizePositive
15+
16+
def main(transpose_inputs=False):
17+
otrain, test = get_train_test()
18+
if transpose_inputs:
19+
otrain = otrain.T
20+
test = test.T
21+
22+
binary = (otrain > 0)
23+
norm = NormalizePositive()
24+
train = norm.fit_transform(otrain.T).T
25+
26+
proximity = distance.pdist(binary, 'correlation')
27+
proximity = distance.squareform(proximity)
28+
29+
neighbors = proximity.argsort(axis=1)
30+
filled = train.copy()
31+
for u in range(filled.shape[0]):
32+
n_u = neighbors[u, 1:]
33+
t_u = train[n_u].T
34+
b_u = binary[n_u].T
35+
for m in range(filled.shape[1]):
36+
revs = t_u[m]
37+
brevs = b_u[m]
38+
revs = revs[brevs]
39+
if len(revs):
40+
revs = revs[:len(revs)//2+1]
41+
filled[u,m] = revs.mean()
42+
43+
ifilled = norm.inverse_transform(filled.T).T
44+
r2 = metrics.r2_score(test[test > 0], ifilled[test > 0])
45+
print('R2 score (binary neighbours): {:.1%}'.format(r2))
7846

7947
if __name__ == '__main__':
8048
main()

‎ch08/norm.py‎

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
2+
class NormalizePositive(object):
3+
4+
def fit(self, features):
5+
binary = (features > 0)
6+
count0 = binary.sum(0)
7+
count0 += (count0 == 0)
8+
self.mean = features.sum(0)/count0
9+
diff = (features - self.mean) * binary
10+
diff **= 2
11+
diff += 1e-9
12+
self.std = 1 + diff.sum(0)/count0
13+
return self
14+
15+
def fit_transform(self, features):
16+
return self.fit(features).transform(features)
17+
18+
def transform(self, features):
19+
binary = (features > 0)
20+
features = features - self.mean
21+
features /= self.std
22+
features *= binary
23+
return features
24+
25+
def inverse_transform(self, features, copy=True):
26+
if copy:
27+
features = features.copy()
28+
features *= self.std
29+
features += self.mean
30+
return features

0 commit comments

Comments
 (0)