Neighborhood-Based Collaborative Filtering – Recommendation Systems – Phần 3
Xin chào mọi người, ở bài viết trước thì mình đã giới thiệu qua về phương pháp User-based và item-based trong CF , ở bài viết lần này mình sẽ triển khai code Python để anh em có cái nhìn rõ hơn nhé.
Nội dung trong phần này
Giới thiệu
Dữ liệu kiểm thử lần này mình sẽ lấy bộ dữ liệu MovieLens 100k được công bố năm 1998 bởi GroupLens. Bộ cơ sở dữ liệu này bao gồm 100,000 (100k) ratings từ 943 users cho 1682 bộ phim. Các bạn cũng có thể tìm thấy các bộ cơ sở dữ liệu tương tự với khoảng 1M, 10M, 20M ratings. Trong bài viết này, tôi sử dụng bộ cơ sở dữ liệu nhỏ nhất này nhằm mục đích minh hoạ.
Sau khi download và giải nén, chúng ta sẽ thu được rất nhiều các file nhỏ, chúng ta chỉ cần quan tâm các file sau:
- u.data : Chứa toàn bộ các ratings của 943 users cho 1682 movies. Mỗi user rate ít nhất 20 movies. Thông tin về thời gian rate cũng được cho nhưng chúng ta không sử dụng trong bài viết này.
- ua.base, ua.test, ub.base, ub.test: là hai cách chia toàn bộ dữ liệu ra thành hai tập con, một cho training, một cho test. Chúng ta sẽ thực hành trên ua.base và ua.test. Bạn đọc có thể thử với cách chia dữ liệu còn lại.
- u.user: Chứa thông tin về users, bao gồm: id, tuổi, giới tính, nghề nghiệp, zipcode (vùng miền), vì những thông tin này cũng có thể ảnh hưởng tới sở thích của các users. Tuy nhiên, trong bài viết này, chúng ta sẽ không sử dụng các thông tin này, trừ thông tin về id để xác định các user khác nhau.
Phương pháp đánh giá
Để đánh giá mô hình thì chúng ta sẽ sử dụng hai giá trị đó là :
- Mean Absolute Error (MAE) :độ đo để đánh giá các mô hình hồi quy. MAE được định nghĩa là trung bình tổng trị tuyệt đối sai số giữa đầu ra dự đoán và kết quả thực . Công thức là :
- Root Mean Squared Error (RMSE):độ đo để đánh giá các mô hình hồi quy. RMSE được định nghĩa là căn bậc 2 trung bình tổng bình phương sai số giữa đầu ra dự đoán và kết quả thực:
Code python
import pandas as pd
import numpy as np
from sklearn.metrics.pairwise import cosine_similarity
from scipy import sparse
from numpy.linalg import matrix_power
class CF(object):
#Khai báo cho dữ liệu đầu vào là ma trận tiện ích , K item , hàm tính similarity , Tag User-based hoặc Item-Based
def __init__(self, Y_data, k, dist_func = cosine_similarity, uuCF = 1):
# quy ước user-based (1) và item-based (0) CF
self.uuCF = uuCF
self.Y_data = Y_data if uuCF else Y_data[:, [1, 0, 2]]
self.k = k
self.dist_func = dist_func
self.Ybar_data = None
# Số lượng User và item . Vì id của user hoặc item được đếm từ 0 nên + thêm 1
self.n_users = int(np.max(self.Y_data[:, 0])) + 1
self.n_items = int(np.max(self.Y_data[:, 1])) + 1
def normalize_Y(self):
# Tất cả các user và item
users = self.Y_data[:, 0]
items = self.Y_data[:,1]
# Tạo một ma trận để biến đổi giữ lại ma trận ban đầu để so sánh khi tính RMSE
self.Ybar_data = self.Y_data.copy()
self.mu = np.zeros((self.n_users,))
for n in range(self.n_users):
# Lấy id của User đã ratings
# Convert lại thành kiểu Integer
ids = np.where(users == n)[0].astype(np.int32)
# Lấy id item được User có id ở trên rated
item_ids = self.Y_data[ids, 1]
# Lấy giá trị ratings
ratings = self.Y_data[ids, 2]
# Tính giá trị trung bình
m = np.mean(ratings)
if np.isnan(m):
m = 0 # nếu ratings không có giá trị set = 0
self.mu[n] = m
# chuẩn hóa lại ma trận
self.Ybar_data[ids, 2] = ratings - self.mu[n]
# Để tránh không đủ bộ nhớ khi số lượng người dùng và item quá lớn thì chúng ta chỉ lưu vị trí của các ratings khác 0
self.Ybar = sparse.coo_matrix((self.Ybar_data[:, 2],
(self.Ybar_data[:, 1], self.Ybar_data[:, 0])), (self.n_items, self.n_users))
self.Ybar = self.Ybar.tocsr()
# Hàm tính toán độ tương đồng giữa các User hoặc Item , ở đây tôi dùng thư viện cosine_similarity
def similarity(self):
eps = 1e-6
self.S = self.dist_func(self.Ybar.T, self.Ybar.T)
# Chuẩn hóa lại data và tính toán ma trận Similarity sau khi Đã điền ratings của các item chưa được rate
def refresh(self):
self.normalize_Y()
self.similarity()
def fit(self):
self.refresh()
# Chuẩn đoán rating của user u for item i
def __pred(self, u, i, normalized = 1):
# Step 1: Tìm tất cả user đã rated cho item i
ids = np.where(self.Y_data[:, 1] == i)[0].astype(np.int32)
# Step 2:
users_rated_i = (self.Y_data[ids, 0]).astype(np.int32)
# Step 3: Tìm sự tương đồng giữa User n với các User còn lại
sim = self.S[u, users_rated_i]
# Step 4: Tìm K User có sự tương đồng nhất
a = np.argsort(sim)[-self.k:]
nearest_s = sim[a]
r = self.Ybar[i, users_rated_i[a]]
if normalized:
# Thêm 1 giá trị nhỏ để tránh chia cho 0
return (r*nearest_s)[0]/(np.abs(nearest_s).sum() + 1e-8)
return (r*nearest_s)[0]/(np.abs(nearest_s).sum() + 1e-8) + self.mu[u]
def pred(self, u, i, normalized = 1):
# Dự đoán ratings user u cho item i
if self.uuCF: return self.__pred(u, i, normalized)
return self.__pred(i, u, normalized)
def recommend(self, u):
# Đề xuất item cho User u với ratings > 0
ids = np.where(self.Y_data[:, 0] == u)[0]
items_rated_by_u = self.Y_data[ids, 1].tolist()
recommended_items = []
for i in range(self.n_items):
if i not in items_rated_by_u:
rating = self.__pred(u, i)
if rating > 0:
recommended_items.append(i)
return recommended_items
def recommend2(self, u):
# Để xuất User cho item u với ratings > 0
ids = np.where(self.Y_data[:, 0] == u)[0]
items_rated_by_u = self.Y_data[ids, 1].tolist()
recommended_items = []
for i in range(self.n_items):
if i not in items_rated_by_u:
rating = self.__pred(u, i)
if rating > 0:
recommended_items.append(i)
return recommended_items
def print_recommendation(self):
# Print ra đề xuất
print ('Recommendation: ')
for u in range(self.n_users):
recommended_items = self.recommend(u)
if self.uuCF:
print ('Recommend item(s):', recommended_items , 'for user', u)
else:
print ('Recommend item', u , 'for user(s) : ', recommended_items)
So sánh
User-based
rs = CF(rate_train, k = 30, uuCF = 0)
rs.fit()
n_tests = rate_test.shape[0]
SE = 0 # squared error
AE = 0
for n in range(n_tests):
pred = rs.pred(rate_test[n, 0], rate_test[n, 1], normalized = 0)
SE += (pred - rate_test[n, 2])**2
AE += (pred - rate_test[n,2])
RMSE = np.sqrt(SE/n_tests)
MAE = abs(AE/n_tests)
print (' RMSE =', RMSE)
print (' MAE =', MAE)
Kết quả : RMSE = 0.9867912132705384 và MAE = 0.0582653956596381
Có thể thấy MAE và RMSE rất nhỏ cho thấy hệ thống chạy khá ok
Item-based
rs = CF(rate_train, k = 30, uuCF = 1)
rs.fit()
n_tests = rate_test.shape[0]
SE = 0 # squared error
AE = 0
for n in range(n_tests):
pred = rs.pred(rate_test[n, 0], rate_test[n, 1], normalized = 0)
SE += (pred - rate_test[n, 2])**2
AE += (pred - rate_test[n,2])
MAE = abs(AE/n_tests)
RMSE = np.sqrt(SE/n_tests)
print (' RMSE =', RMSE)
print (' MAE =', MAE)
Kết quả : RMSE = 0.9951981100882598 và MAE = 0.03062686975303437
Kết quả cũng rất ấn tượng khi MAE còn thấp hơn cả phương pháp User-based nhưng về cơ bản không có sự cách biệt quá lớn, các bạn cũng có thể thay hệ số K lớn hơn để thấy rõ được sự khác biệt về kết quả . Thực ra kết quả này bị ảnh hướng rất nhiều từ file data , đôi khi dữ liệu về item lại cực lớn so với user thì phương pháp item based lại tỏ ra mạnh mẽ hơn và ngược lại . Thực tế thì hai cách này luôn đi song song với nhau để đưa ra tư vấn cho người dùng.
Mình cũng xin kết thúc bài viết ở đây , ở bài tiếp theo mình sẽ đề cập đến phương kết hợp giữa cả user và item .
Tài liệu mình hay tham khảo
-
Khá hay và nhiều kiến thức của các pháp sư US-UK AWS Machine Learning Blog
-
Kho Dataset cho anh em GroupLens
-
Mình cũng hay tham khảo ở đây TensorFlowBlog