Spaces:
Sleeping
Sleeping
Commit
·
00e640a
1
Parent(s):
0efcdce
Init commit of ease model
Browse files- app.py +120 -0
- data/2k_ids.pkl +3 -0
- data/2k_titles.pkl +3 -0
- func.py +54 -0
- model.py +115 -0
- requirements.txt +4 -0
app.py
ADDED
@@ -0,0 +1,120 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
import streamlit as st
|
2 |
+
from streamlit_option_menu import option_menu
|
3 |
+
from func import *
|
4 |
+
from model import *
|
5 |
+
|
6 |
+
|
7 |
+
if "user_preferences" not in st.session_state:
|
8 |
+
st.session_state["user_preferences"] = {}
|
9 |
+
|
10 |
+
if "user_titles" not in st.session_state:
|
11 |
+
st.session_state["user_titles"] = []
|
12 |
+
|
13 |
+
data = pd.read_pickle("data/2k_titles.pkl")
|
14 |
+
|
15 |
+
def games_recomm(preferences_id):
|
16 |
+
if "rs" in st.session_state:
|
17 |
+
del st.session_state["rs"]
|
18 |
+
|
19 |
+
with st.spinner("Getting recommendation..."):
|
20 |
+
pref_value = []
|
21 |
+
for id in preferences_id:
|
22 |
+
if st.session_state[id] == "Positive":
|
23 |
+
pref_value.append(1)
|
24 |
+
elif st.session_state[id] == "Negative":
|
25 |
+
pref_value.append(0)
|
26 |
+
|
27 |
+
pred_df = pd.DataFrame({
|
28 |
+
'user_id': [999999] * len(preferences_id),
|
29 |
+
'app_id': preferences_id,
|
30 |
+
'is_recommended': pref_value
|
31 |
+
})
|
32 |
+
|
33 |
+
res = ease_model(pred_df=pred_df, k=10)
|
34 |
+
st.session_state['rs'] = res
|
35 |
+
|
36 |
+
# st.write(len(st.session_state['rs']))
|
37 |
+
if len(st.session_state['rs']) >= 1:
|
38 |
+
st.success(f"Go to result page to view top {len(st.session_state['rs'])} recommendations.")
|
39 |
+
else:
|
40 |
+
st.error("Recommendation failed. Please reload the session.")
|
41 |
+
|
42 |
+
# st.write(res)
|
43 |
+
|
44 |
+
# Main Page Header
|
45 |
+
# Consist of Home page, Result page, About page, and Log page
|
46 |
+
def spr_sidebar():
|
47 |
+
menu = option_menu(
|
48 |
+
menu_title=None,
|
49 |
+
options=['Home', 'Result', 'About'],
|
50 |
+
icons=['house', 'joystick', 'info-square'],
|
51 |
+
menu_icon='cast',
|
52 |
+
default_index=0,
|
53 |
+
orientation='horizontal'
|
54 |
+
)
|
55 |
+
|
56 |
+
# Change 'app_mode' state based on current page
|
57 |
+
if menu == 'Home':
|
58 |
+
st.session_state['app_mode'] = 'Home'
|
59 |
+
elif menu == 'Result':
|
60 |
+
st.session_state['app_mode'] = 'Result'
|
61 |
+
elif menu == 'About':
|
62 |
+
st.session_state['app_mode'] = 'About'
|
63 |
+
|
64 |
+
# Home page. One of the page in Main Header
|
65 |
+
def home_page():
|
66 |
+
st.title("Steam Recommendation System")
|
67 |
+
|
68 |
+
# st.session_state['user_title'] = st.session_state['input_title']
|
69 |
+
preferences = st.multiselect(
|
70 |
+
label="Input games you like:",
|
71 |
+
options=list(data),
|
72 |
+
key="user_titles")
|
73 |
+
|
74 |
+
user_input = generate_app_gamebox(preferences)
|
75 |
+
|
76 |
+
state = st.button("Get state")
|
77 |
+
|
78 |
+
if state:
|
79 |
+
st.session_state["user_preferences"] = user_input
|
80 |
+
|
81 |
+
st.markdown("---")
|
82 |
+
games_recomm(st.session_state["user_preferences"])
|
83 |
+
|
84 |
+
st.session_state
|
85 |
+
|
86 |
+
|
87 |
+
|
88 |
+
# Result page
|
89 |
+
# Show the list of predictions for active user
|
90 |
+
def result_page():
|
91 |
+
if "rs" not in st.session_state:
|
92 |
+
st.error('Please input preferences titles and run "Get recommendation"')
|
93 |
+
else:
|
94 |
+
st.success(f'Top {len(st.session_state["rs"])}')
|
95 |
+
st.session_state
|
96 |
+
|
97 |
+
user_res = generate_res_gamebox(ids=st.session_state['rs'])
|
98 |
+
|
99 |
+
# About page
|
100 |
+
# Show the information of the project and the sites
|
101 |
+
|
102 |
+
|
103 |
+
def about_page():
|
104 |
+
pass
|
105 |
+
|
106 |
+
|
107 |
+
def main():
|
108 |
+
spr_sidebar()
|
109 |
+
# st.session_state
|
110 |
+
|
111 |
+
if st.session_state['app_mode'] == 'Home':
|
112 |
+
home_page()
|
113 |
+
elif st.session_state['app_mode'] == 'Result':
|
114 |
+
result_page()
|
115 |
+
elif st.session_state['app_mode'] == 'About':
|
116 |
+
about_page()
|
117 |
+
|
118 |
+
|
119 |
+
if __name__ == '__main__':
|
120 |
+
main()
|
data/2k_ids.pkl
ADDED
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
1 |
+
version https://git-lfs.github.com/spec/v1
|
2 |
+
oid sha256:dd9e84631b0dd97fe7d0fe982a28a03c9c65a4d1042437385137d05c92f829b3
|
3 |
+
size 60873
|
data/2k_titles.pkl
ADDED
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
1 |
+
version https://git-lfs.github.com/spec/v1
|
2 |
+
oid sha256:2689a911254553451cdd573c751192f098d12b3b1cc9d872f7ac2e7b0d840018
|
3 |
+
size 60873
|
func.py
ADDED
@@ -0,0 +1,54 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
import pandas as pd
|
2 |
+
import streamlit as st
|
3 |
+
import requests
|
4 |
+
|
5 |
+
from io import BytesIO
|
6 |
+
from PIL import Image
|
7 |
+
|
8 |
+
data_titles = pd.read_pickle("data/2k_titles.pkl")
|
9 |
+
data_ids = pd.read_pickle("data/2k_ids.pkl")
|
10 |
+
|
11 |
+
def generate_app_gamebox(titles):
|
12 |
+
titles_id = []
|
13 |
+
# selectboxes_id = {}
|
14 |
+
|
15 |
+
for title in titles:
|
16 |
+
titles_id.append(data_titles[title])
|
17 |
+
|
18 |
+
for id in titles_id:
|
19 |
+
url = f"https://cdn.cloudflare.steamstatic.com/steam/apps/{id}/header.jpg"
|
20 |
+
resp = requests.get(url)
|
21 |
+
|
22 |
+
if resp.status_code == 200:
|
23 |
+
container = st.container()
|
24 |
+
img_col, pref_col = container.columns([3, 2])
|
25 |
+
|
26 |
+
img_col.image(BytesIO(resp.content))
|
27 |
+
pref_col.selectbox(
|
28 |
+
"Your rating:",
|
29 |
+
options=["Positive", "Negative"],
|
30 |
+
key=id,
|
31 |
+
)
|
32 |
+
|
33 |
+
return titles_id
|
34 |
+
|
35 |
+
def generate_res_gamebox(ids):
|
36 |
+
# titles_list = []
|
37 |
+
|
38 |
+
for id in ids:
|
39 |
+
# titles_list.append(data_ids[id])
|
40 |
+
url_page = f"https://store.steampowered.com/app/{id}/"
|
41 |
+
url_img = f"https://cdn.cloudflare.steamstatic.com/steam/apps/{id}/header.jpg"
|
42 |
+
resp = requests.get(url_img)
|
43 |
+
|
44 |
+
if resp.status_code == 200:
|
45 |
+
with st.container():
|
46 |
+
st.image(BytesIO(resp.content))
|
47 |
+
st.caption(data_ids[id])
|
48 |
+
|
49 |
+
st.divider()
|
50 |
+
|
51 |
+
# for title in titles_list:
|
52 |
+
# # url = f"https://store.steampowered.com/app/"
|
53 |
+
# with st.container():
|
54 |
+
|
model.py
ADDED
@@ -0,0 +1,115 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
import pandas as pd
|
2 |
+
import torch
|
3 |
+
|
4 |
+
from sklearn.neighbors import KNeighborsClassifier
|
5 |
+
|
6 |
+
# sparse_indices = torch.load("data/sparse_indices.pt")
|
7 |
+
# sparse_values = torch.load("data/sparse_values.pt")
|
8 |
+
class EASE:
|
9 |
+
def __init__(self, train,
|
10 |
+
user_col='user_id',
|
11 |
+
item_col='app_id',
|
12 |
+
score_col='is_recommended',
|
13 |
+
reg=250.):
|
14 |
+
"""
|
15 |
+
train: (DataFrame) data of training set
|
16 |
+
user_col: (String) column name of users column
|
17 |
+
item_col: (String) column name of items column
|
18 |
+
score_col: (String) column name of interactions column
|
19 |
+
reg: (Float) EASE's regularization value
|
20 |
+
"""
|
21 |
+
|
22 |
+
self.user_col = user_col
|
23 |
+
self.item_col = item_col
|
24 |
+
self.score_col = score_col
|
25 |
+
self.train = train
|
26 |
+
self.reg = reg
|
27 |
+
|
28 |
+
self.user_id_col = user_col + "_index"
|
29 |
+
self.item_id_col = item_col + "_index"
|
30 |
+
|
31 |
+
self.item_lookup = self.generate_label(train, self.item_col)
|
32 |
+
|
33 |
+
self.item_map = {}
|
34 |
+
for item, item_index in self.item_lookup.values:
|
35 |
+
self.item_map[item_index] = item
|
36 |
+
|
37 |
+
def generate_label(self, df, col):
|
38 |
+
dist_labels = df[[col]].drop_duplicates()
|
39 |
+
dist_labels[col +
|
40 |
+
"_index"] = dist_labels[col].astype("category").cat.codes
|
41 |
+
|
42 |
+
return dist_labels
|
43 |
+
|
44 |
+
def predict_active(self, pred_df, weight, k=10, remove_owned=True):
|
45 |
+
"""
|
46 |
+
Args:
|
47 |
+
pred_df: (DataFrame) data of user interactions
|
48 |
+
weight: (Tensor) Weight matrix of pre-trained EASE model
|
49 |
+
k: (Integer) number of recommendation to be shown
|
50 |
+
remove_owned: (Boolean) Whether to remove already interacted items
|
51 |
+
"""
|
52 |
+
train = pd.concat([self.train, pred_df], axis=0)
|
53 |
+
user_lookup = self.generate_label(train, self.user_col)
|
54 |
+
|
55 |
+
train = train.merge(user_lookup, on=[self.user_col], sort=False)
|
56 |
+
train = train.merge(self.item_lookup, on=[self.item_col], sort=False)
|
57 |
+
|
58 |
+
pred_df = pred_df[[self.user_col]].drop_duplicates()
|
59 |
+
pred_df = pred_df.merge(user_lookup, on=[self.user_col], sort=False)
|
60 |
+
|
61 |
+
indices = torch.LongTensor(train[[self.user_id_col, self.item_id_col]].values)
|
62 |
+
values = torch.FloatTensor(train[self.score_col])
|
63 |
+
sparse = torch.sparse.FloatTensor(indices.T, values)
|
64 |
+
|
65 |
+
# --------------------------------------------------
|
66 |
+
user_act_tensor = sparse.index_select(
|
67 |
+
dim=0, index=torch.LongTensor(pred_df[self.user_id_col])
|
68 |
+
)
|
69 |
+
|
70 |
+
_preds_act_tensor = user_act_tensor @ weight
|
71 |
+
if remove_owned:
|
72 |
+
_preds_act_tensor += -1. * user_act_tensor
|
73 |
+
|
74 |
+
output_preds = []
|
75 |
+
score_preds = []
|
76 |
+
for _preds in _preds_act_tensor:
|
77 |
+
top_items = _preds.topk(k)
|
78 |
+
|
79 |
+
output_preds.append([self.item_map[id] for id in top_items.indices.tolist()])
|
80 |
+
score_preds.append( top_items.values.tolist() )
|
81 |
+
|
82 |
+
pred_df['predicted_items'] = output_preds
|
83 |
+
pred_df['predicted_score'] = score_preds
|
84 |
+
|
85 |
+
escaped = [
|
86 |
+
ele for i_list in pred_df['predicted_items'].values for ele in i_list
|
87 |
+
]
|
88 |
+
|
89 |
+
return escaped
|
90 |
+
|
91 |
+
def ease_model(pred_df, k=10):
|
92 |
+
ease_B = torch.load("data/ease_B.pt")
|
93 |
+
train = pd.read_csv("data/recs.csv")
|
94 |
+
|
95 |
+
ease = EASE(train)
|
96 |
+
res = ease.predict_active(pred_df=pred_df, weight=ease_B, k=k)
|
97 |
+
|
98 |
+
return res
|
99 |
+
|
100 |
+
|
101 |
+
# def main():
|
102 |
+
# pass
|
103 |
+
# # act_user = pd.DataFrame({
|
104 |
+
# # 'user_id': [999999, 999999, 999999, 999999, 999999, 999999],
|
105 |
+
# # 'app_id': [1689910, 1245620, 814380, 620980, 1551360, 774171],
|
106 |
+
# # 'is_recommended': [0, 1, 1, 0, 1, 1]
|
107 |
+
# # })
|
108 |
+
# # act_indices = torch.FloatTensor(ac)
|
109 |
+
|
110 |
+
# # print(
|
111 |
+
# # torch.sparse.FloatTensor(sparse_indices.T, sparse_values)
|
112 |
+
# # )
|
113 |
+
|
114 |
+
# if __name__ == '__main__':
|
115 |
+
# main()
|
requirements.txt
ADDED
@@ -0,0 +1,4 @@
|
|
|
|
|
|
|
|
|
|
|
1 |
+
pandas
|
2 |
+
requests
|
3 |
+
streamlit
|
4 |
+
streamlit_option_menu
|