# **<p style="text-align: center">RECOMMENDATION SYSTEM CƠ BẢN</p>**
**<p style="text-align: center; font-size: 20px">Steam User Dataset - User-user Collaborative Filtering</p>**
## **1. Lý do chọn đề tài**
<div style="display: flex; gap: 2rem;">
<div>
<p style="text-align: justify;">
Recommend systems từ lâu vốn đã quen thuộc đối với những người dùng các nền tảng mạng xã hội hay các ứng dụng giải trí như Netflix, Youtube hay Spotify. Mặc dù được tiếp xúc mỗi ngày, thế nhưng vẫn chưa có nhiều người hiểu rõ cơ chế hoạt động của Recommendation Systems hoạt động ra sao mà đôi khi lại đưa ra một khuyến nghị "vừa ý" mình đến thế.
</p>
<p style="text-align: justify;">
Để thiết lập một Recommendation Systems, ta cần ít nhất ba nguồn dữ liệu về: người dùng, sản phẩm và đánh giá của người dùng về sản phẩm đó. Trong dự án lần này, chúng tôi quyết định chọn bộ dữ liệu về game trên nền tảng Steam bởi các thành viên nhóm cảm thấy hứng thú khi chủ đề bài học có liên quan đến sở thích của mình, và cùng với đó bộ dữ liệu này đã thỏa mãn các yêu cầu mà nhóm đưa ra.
</p>
</div>
</div>
## **2. Sản phẩm**
**Sơ đồ hệ thống:**
<div style="display: flex; gap: 2rem;">
<div>
<p style="text-align: center"><img src="https://scontent.xx.fbcdn.net/v/t1.15752-9/415758929_1810466132747881_8814619475512233429_n.png?_nc_cat=101&ccb=1-7&_nc_sid=510075&_nc_ohc=Na2jeZE3WpwAX87FTqK&_nc_ad=z-m&_nc_cid=0&_nc_ht=scontent.xx&oh=03_AdT-DgN5byw__7oa6ZfMIjAGKKyPW_bUlyYv_IsYbSBHaQ&oe=660DD3B3"></p>
</div>
</div>
### **2.1 Giao diện chính**
<div style="display: flex; gap: 2rem;">
<div>
<p style="text-align: justify;">
Trong giao diện chính để truy cập vào profile của user, ta cần nhập vào user id của user đó.
</p>
<p style="text-align: center"><img src="https://scontent.xx.fbcdn.net/v/t1.15752-9/416118283_215684341615724_1640067342894927359_n.png?_nc_cat=110&ccb=1-7&_nc_sid=510075&_nc_ohc=7_AYKr-liIYAX-9J249&_nc_ad=z-m&_nc_cid=0&_nc_ht=scontent.xx&oh=03_AdQoBV5KbMXXgaU2TZYstgjhsiGbIDh_jGXUmoe_4QzBmA&oe=660DF3AE"></p>
<p style="text-align: justify;">
Tiếp đó, sau khi nhập id của user cần truy cập, một biểu đồ tròn thể hiện sự phân bố thời gian chơi của user trên các thể loại game, phía dưới là các game được khuyến nghị cho user.
</p>
<p style="text-align: center"><img src="https://scontent.xx.fbcdn.net/v/t1.15752-9/416079925_1595267904549367_7290514276501229499_n.png?_nc_cat=110&ccb=1-7&_nc_sid=510075&_nc_ohc=JD66ydLkfd8AX8WCrJi&_nc_ad=z-m&_nc_cid=0&_nc_ht=scontent.xx&oh=03_AdQorFd0g_XkreM22aJ48HsT4vZyaruoD80ka2HZwhgmFw&oe=660DF7A7"></p>
</div>
</div>
### **2.2 Xử lí dữ liệu**
#### **2.2.1 Bộ dữ liệu đã dùng**
<div style="display: flex; gap: 2rem;">
<div>
<p style="text-align: justify;">
Bộ dữ liệu được tham khảo tại https://www.kaggle.com/datasets/tamber/steam-video-games.</p>
<p style="text-align: justify;">
Để vận hành model, đầu tiên ta cần gọi các thư viện cần thiết như sau:
</p>
</div>
</div>
```
import pandas as pd
import matplotlib.pyplot as plt
import numpy as np
```
<div style="display: flex; gap: 2rem;">
<div>
<p style="text-align: justify;">
Sau đó, ta cần xem tổng quan dữ liệu bên trong có những gì bằng cách plot nó ra bằng thư viện panda. Ở đây vì kích cỡ hình ảnh giới hạn nên sẽ chỉ xem được profile của 20 users đầu, bao gồm user id, game đã chơi, đã mua hay chỉ chơi thử và thời gian chơi.
</p>
<p style="text-align: center"><img src="https://scontent.xx.fbcdn.net/v/t1.15752-9/420107491_401132819023300_703400230585329058_n.png?_nc_cat=104&ccb=1-7&_nc_sid=510075&_nc_ohc=35byY_EERIoAX-ATFNn&_nc_ad=z-m&_nc_cid=0&_nc_ht=scontent.xx&oh=03_AdR5RRog20bm3iw6TzDKkfecvfQcRtY9jpYhDsUoIamtuA&oe=660DF4C6"></p>
<p style="text-align: justify;">
Sau khi xem một lượt tổng thể, bộ dữ liệu ban đầu bao gồm:
</p>
* 20.000 users
* 5155 games
</div>
</div>
<div style="display: flex; gap: 2rem;">
<div>
<p style="text-align: justify;">
Để tiến hành áp dụng các thuật toán đã nghiên cứu, ta cần chuyển dữ liệu ta có thành một ma trận user x game để dễ dàng hơn trong việc tính toán. Ta dùng đoạn code sau:
</p>
</div>
</div>
```
df = df[df['action'] == 'play'].copy()
userbyitem = pd.DataFrame(index=df['user'].unique(), columns=df['game'].unique())
for i in range(len(df)):
user = df.iloc[i]['user']
game = df.iloc[i]['game']
userbyitem.loc[user,game] = df.iloc[i]['hours']
userbyitem.to_csv('userbyitem.csv', index=True)
```
<div style="display: flex; gap: 2rem;">
<div>
<p style="text-align: justify;">
Sau khi chạy đoạn code trên ta thu về ma trận user x game với mỗi phần tử là thời gian chơi của user với game tương ứng.
</p>
<p style="text-align: center"><img src="https://scontent.xx.fbcdn.net/v/t1.15752-9/420052731_1082585919652617_5831547513334661781_n.png?_nc_cat=103&ccb=1-7&_nc_sid=510075&_nc_ohc=3jazhMTyCJ8AX8Hep_H&_nc_oc=AQmyZ7SM-9vDlzAaI-QyrXyj8QAtTxzogsPQdgHGLQ2OylMDa3Yde2N0Nk5edn3W28_22HaMXD1sE5-Egp1eobko&_nc_ad=z-m&_nc_cid=0&_nc_ht=scontent.xx&oh=03_AdTZkYXfw3A4QqBZubuzICfHwC2pLGyquUSAPiMh3I7bJw&oe=660DE9D9"></p>
<p style="text-align: justify;">
Tại đấy, để thuận tiện hơn trong việc xử lý dữ liệu, chúng tôi tiến hành đổi từ thời gian chơi của user sang rating bằng cách lấy Log napier và cộng cho trị tuyệt đối của giá trị nhỏ nhất trong ma trận (scale về thang điểm rating dương) và sau đó loại đi các user chơi ít hơn 3 game.
</p>
<p style="text-align: center"><img src="https://scontent.xx.fbcdn.net/v/t1.15752-9/415522470_1912413812486229_3227240566155350672_n.png?_nc_cat=100&ccb=1-7&_nc_sid=510075&_nc_ohc=ZbStfJGxDDkAX8_4RVP&_nc_ad=z-m&_nc_cid=0&_nc_ht=scontent.xx&oh=03_AdSeKUmNuzQ80fZCNzFvq3CD58QuKlenLmGM312ESDj61w&oe=660DD9BA"></p>
</div>
</div>
#### **2.2.2 User-user collaborative filtering**
<div style="display: flex; gap: 2rem;">
<div>
<p style="text-align: justify;">
Trong Recommendation Systems có ba phương pháp chính là: Content-based, Lọc cộng hưởng (Collaborative Filtering), Hybrid Solutions. Trong phương pháp Collaborative Filtering, còn có nhiều nhánh con khác nhưng chúng tôi đã có một sự quan tâm nhất định tới phương pháp User-user collaborative Filtering (UU-CF). Sau khi xem qua cả ba phương pháp nghiên cứu, nhóm chúng tôi quyết định dùng phương pháp UU-CF vì nó phù hợp nhất với ý tưởng ban đầu của nhóm là: các game thủ chơi nhiều game giống nhau thường chơi các trò chơi giống nhau.
</p>
</div>
</div>
##### **2.2.2.1 Định nghĩa**
<div style="display: flex; gap: 2rem;">
<div>
<p style="text-align: justify;">
Collaborative Filtering là phương pháp xác định mức độ quan tâm của một user tới một item dựa trên hành vi của các user khác gần giống với user này. Trong phương pháp này có hai phương pháp nhỏ hơn là: UU-CF và Item-item Collaborative filtering. Bởi đã xác định được hướng nghiên cứu nên sau đây chỉ nói về phương pháp UU-CF. UU-CF là phương pháp xác định mức độ quan tâm của mỗi user tới một item dựa trên mức độ quan tâm của user tương tự tới item đó.
</p>
</div>
</div>
##### **2.2.2.2 Các hàm đã sử dụng**
<div style="display: flex; gap: 2rem;">
<div>
<p style="text-align: justify;">
Để thuận tiện cho việc biểu diễn các bước thực hiện của phương pháp UU-CF, chúng tôi biểu diễn dữ liệu dưới dạng ma trận tiện ích (Utility Matrix) như sau:</p>
<p style="text-align: center"><img src="https://scontent.xx.fbcdn.net/v/t1.15752-9/415522470_1912413812486229_3227240566155350672_n.png?_nc_cat=100&ccb=1-7&_nc_sid=510075&_nc_ohc=ZbStfJGxDDkAX8_4RVP&_nc_ad=z-m&_nc_cid=0&_nc_ht=scontent.xx&oh=03_AdSeKUmNuzQ80fZCNzFvq3CD58QuKlenLmGM312ESDj61w&oe=660DD9BA"></p>
</div>
</div>
<div style="display: flex; gap: 2rem;">
<div>
<p style="text-align: justify;">
Từ Utility Matrix ban đầu ta có thể nhìn thấy được có nhiều ký hiệu "NaN" trên Utility Matrix (tức là user không chơi game đó). Và để thuận tiện hơn trong việc xác định sự giống nhau giữa các user (hàm similarity), ta sẽ chuẩn hóa Utility Matrix bằng cách trừ mỗi điểm đánh giá đi giá trị đánh giá trung bình này và thay các giá trị chưa biết bằng 0, ta sẽ được một ma trận chuẩn hóa.
</p>
</div>
</div>
```
for user in ubyi_norm.index.values:
for games in ubyi_norm.loc[user][ubyi_norm.loc[user].notna()].index:
ubyi_norm.loc[user][games] -= averages[user]
for games in ubyi_norm.loc[user][ubyi_norm.loc[user].isna()].index:
ubyi_norm.loc[user][games] = 0
```
<div style="display: flex; gap: 2rem;">
<div>
<p style="text-align: center"><img src="https://scontent.xx.fbcdn.net/v/t1.15752-9/415476220_371460582245043_4205622243322367204_n.png?_nc_cat=110&ccb=1-7&_nc_sid=510075&_nc_ohc=PNJH8bypBGkAX_D4pe_&_nc_ht=scontent.xx&oh=03_AdSmCJ2F3j5C45FnDwRluKvmBt2S4aZyWZ2I3awHWw_aBg&oe=660DF919"></p>
</div>
</div>
<div style="display: flex; gap: 2rem;">
<div>
<p style="text-align: justify;">
Để tìm các user có nét tương đồng và lập thành nhóm, ta phải tiến hành khảo sát sự giống nhau giữa các user bằng hàm correlation, hàm correlation thường được sử dụng là correlation:
</p>
</div>
</div>
$$corr(u_1, u_2)=\frac{\sum(x-\bar{x})(y-\bar{y})}{\sum\sqrt{(x-\bar{x})^2(y-\bar{y})^2}}$$
<div style="display: flex; gap: 2rem;">
<div>
<p style="text-align: justify;">
Từ hàm số đã nêu, ta có thể thấy rằng độ mức độ giống nhau của hai vector là một số thực nằm trong đoạn [-1;1]. Nếu độ giống nhau bằng 1 thì hai vector đó có cùng phương, cùng hướng và góc giữa hai vector bằng 0. Ngược lại, nếu độ giống nhau của hai vector bằng -1 thì hai vector này hoàn toàn trái ngược nhau tức cùng phương nhưng khác hướng. Từ điều này ta có thể kết luận rằng hành vi của hai user giống nhau thì giá trị của hàm correlation càng cao và giá trị correlation càng thấp thì hai user càng có ít điểm chung. Sau khi lần lượt áp dụng thuật toán vào code:
</p>
</div>
</div>
```
#construct user correlation matrix
user_corr = pd.DataFrame(index=ubyi_norm.index.values,
columns=ubyi_norm.index.values)
i = 0
for user1 in user_corr.index.values:
#progress indicator
i+=1
if(i%50 == 0):
print(i)
for user2 in user_corr.columns.values:
if user1 == user2:
continue
if not np.isnan(user_corr.loc[user1, user2]):
#if the matrix value is already filled out, we don't need to calculate again
continue
#calculation correlation between two user vectors
a = ubyi_norm.loc[user1].values
b = ubyi_norm.loc[user2].values
empty = ~np.logical_or(np.isnan(a), np.isnan(b))
a=np.compress(empty,a)
b=np.compress(empty,b)
if(len(a) < 5):
#if users have less than 5 items in common, just leave it NA
#pearson similarity doesn't have much meaning in those cases
continue
corr = np.corrcoef(a,b)[0,1]
user_corr.loc[user1,user2] = corr
user_corr.loc[user2,user1] = corr
user_corr = user_corr.fillna(1.0)
```
<div style="display: flex; gap: 2rem;">
<div>
<p style="text-align: justify;">
Sau đó ta tiến hành kiểm tra ma trận sau khi được xử lí bằng hàm correlation, ta được ma trận C như sau:</p>
<p style="text-align: center"><img src="https://scontent.xx.fbcdn.net/v/t1.15752-9/417082019_341962132066642_3773864899652609903_n.png?_nc_cat=102&ccb=1-7&_nc_sid=510075&_nc_ohc=lxwr7Iuqug4AX9bXTHi&_nc_ht=scontent.xx&oh=03_AdTxKVmk8grKwc28OlfRU8sdIjc5-mkWZWSSAiSystnlcw&oe=660DE923"></p>
<p style="text-align: justify;">
Sau khi có ma trận C, ta tiến hành dự đoán đánh giá của user cho item mà user đó chưa dự đoán. Đầu tiên, ta chọn ra các user có độ giống gần giống nhất với user mình đang xét, sau đó ta tiếp tục lập ra list những game user đang xét chưa chơi. Sau đó, tạo thêm 2 list bao gồm: list của những users đã chơi game mà user đang xét chưa chơi và list những users gần giống với user đang xét. Ta tiến hành đi tìm phần giao giữa hai list trên, nếu số lượng user đã đánh giá game đó và có sở thích gần giống dưới 5 users thì ta không thể khuyến nghị đó cho user đang xét. Trường hợp còn lại ta tiến hành sắp xếp độ tương thích của 5 users theo độ tương thích từ cao đến thấp.
</p>
</div>
</div>
```
games_to_consider = ubyi_norm.loc[user_to_recommend][ubyi_norm.loc[user_to_recommend].isna()].index.values
for game in games_to_consider:
#get list of users that have played it
played = ubyi_norm[game][ubyi_norm[game].notna()].index.values
#print(len(played))
#get list of users that have a similarity rating with user
users_with_sim = user_corr[user_to_recommend][user_corr[user_to_recommend].notna()].index.values
#print(len(users_with_sim))
#get intersection of both lists
played_and_sim = set(played).intersection(users_with_sim)
#print(len(played_and_sim))
if(len(played_and_sim) < 5):
#cant make a rec for this game, not enough similar (or dissimilar) users have played it
continue
#get 5 most similar (or most dissimilar) users
sim_tuples = []
for user in played_and_sim:
sim_with_user1 = user_corr.loc[user_to_recommend, user]
sim_tuples.append((user,sim_with_user1))
#sort sim_tuples to find 5 most similar (or dissimilar) users
#consider up to 10 if available
sim_tuples.sort(key=lambda x: abs(x[1]), reverse=True)
users_to_score = [x[0] for x in sim_tuples[:min(len(sim_tuples), 10)]]
```
<div style="display: flex; gap: 2rem;">
<div>
<p style="text-align: justify;">
Khi chạy đoạn code xong, plot dữ liệu ta nhận về các user và độ tương thích từ cao đến thấp như sau:
</p>
<p style="text-align: center"><img src="https://scontent.xx.fbcdn.net/v/t1.15752-9/416801406_826218965927503_4873945640779411206_n.png?_nc_cat=109&ccb=1-7&_nc_sid=510075&_nc_ohc=zBtBprQYTCEAX9p1OxE&_nc_ad=z-m&_nc_cid=0&_nc_ht=scontent.xx&oh=03_AdTTI1kVAgD4a6kdRk2tSXe5Qgky0XIzBanEx1ACaFGepw&oe=660DE06F"></p>
<p style="text-align: justify;">
Bước cuối cùng, ta tìm và chọn 5 users tương thích nhất với user đang xét, lấy giá trị normalized của những users đó trừ cho giá trị normalized của user đang xét và cộng cho trung bình cộng giờ chơi của user đang xét, ta được giá trị đánh giá dự đoán của user đó lên game mà người đó chưa chơi.
</p>
</div>
</div>
```
#create dataframe to organize data
collab_filter_df = pd.DataFrame(index=users_to_score, columns=['rating','user_avg','diff', 'corr_w_user1', 'abs(corr)', 'weight', 'weighted_diff'])
for user in users_to_score:
collab_filter_df.loc[user,'rating'] = ubyi_norm.loc[user,game]
collab_filter_df.loc[user,'user_avg'] = ubyi_norm.loc[user].mean()
collab_filter_df.loc[user,'diff'] = ubyi_norm.loc[user,game] - ubyi_norm.loc[user].mean()
collab_filter_df.loc[user,'corr_w_user1'] = user_corr.loc[user_to_recommend,user]
collab_filter_df.loc[user,'abs(corr)'] = abs(user_corr.loc[user_to_recommend,user])
sum_corr = sum(collab_filter_df['abs(corr)'])
collab_filter_df['weight'] = collab_filter_df['corr_w_user1']/sum_corr
collab_filter_df['weighted_diff'] = collab_filter_df['weight']*collab_filter_df['diff']
learning = sum(collab_filter_df['weighted_diff'])
predicted_rating = user_avg + learning
if(predicted_rating > rec_pred_rating):
recommendation = game
rec_pred_rating = predicted_rating
users_considered = users_to_score
collab_filter_df_mem = collab_filter_df
```
<div style="display: flex; gap: 2rem;">
<div>
<p style="text-align: justify;">
Sau cùng ta nhận được tựa game cùng với giá trị đánh giá dự đoán của user đó lên game được khuyến nghị.
</p>
<p style="text-align: center"><img src="https://scontent.xx.fbcdn.net/v/t1.15752-9/413136213_377777454844948_4002765844008372451_n.png?_nc_cat=111&ccb=1-7&_nc_sid=510075&_nc_ohc=UW8UiW-P4b0AX_R9xkx&_nc_ad=z-m&_nc_cid=0&_nc_ht=scontent.xx&oh=03_AdSJk7XdjiGtb1tG2fSoSVN4o4feITlDDyhERGlJvVbUoQ&oe=660DF8BA"></p>
</div>
</div>
## **3. Kết luận**
<div style="display: flex; gap: 2rem;">
<div>
<p style="text-align: justify;">
Trong Recommendation Systems, ta có thể tìm thấy rất nhiều phương pháp nghiên cứu dữ liệu để đưa ra khuyến nghị cho người dùng. UU-CF là một trong số rất nhiều phương pháp đó. Song vẫn còn nhiều hạn chế trong phương pháp kể trên cụ thể như:
</p>
* UU-CF chưa thật sự hoạt động tốt khi số lượng user lớn hơn nhiều lần số lượng item dẫn đến ma trận correlation rất lớn. Kéo theo việc tính toán correlation thường rất tốn thời gian hay thậm chí không thể thực hiện được.
* Trong trường hợp user có sở thích quá đa dạng thì UU-CF thường cho ra kết quả không chính xác với user đó.
<div style="display: flex; gap: 2rem;">
<div>
<p style="text-align: justify;">
Tựu trung, đây là một bước đệm vô cùng quan trọng trong việc nghiên cứu sâu hơn Recommendation Systems nói riêng hay Machine Learning nói chung. Hiện phương pháp chúng tôi nghiên cứu tuy còn nhiều hạn chế nhưng vẫn hoạt động tốt trong việc khuyến nghị các sản phẩm (game, phim, ảnh,...) tới người dùng hay có thể đưa ra dự đoán cho người dùng ít đánh giá.
</p>
</div>
</div>
# **<p style="text-align: center"></p>**
**<p style="text-align: Left; font-size: 18px">**
**Nhóm nghiên cứu và thực hiện:**
* Trần Nguyễn Thanh Phương
* Cao Minh Khoa
* Hoàng Việt
* Choi Won Seok
* Phan Minh Quân