first commit
Browse files- .gitattributes +2 -0
- .gitignore +1 -0
- README.md +4 -4
- app.py +65 -0
- inference.py +87 -0
- products/4470620803936518297/00000000.shard/00000000.snapshot +3 -0
- products/7776351335182402423/00000000.shard/00000000.snapshot +3 -0
- products/dataset_spec.pb +3 -0
- products/snapshot.metadata +1 -0
- recommender_model/index/fingerprint.pb +3 -0
- recommender_model/index/keras_metadata.pb +3 -0
- recommender_model/index/saved_model.pb +3 -0
- recommender_model/index/variables/variables.data-00000-of-00001 +3 -0
- recommender_model/index/variables/variables.index +0 -0
- recommender_model/model/fingerprint.pb +3 -0
- recommender_model/model/keras_metadata.pb +3 -0
- recommender_model/model/saved_model.pb +3 -0
- recommender_model/model/variables/variables.data-00000-of-00001 +3 -0
- recommender_model/model/variables/variables.index +0 -0
- requirements.txt +2 -0
.gitattributes
CHANGED
@@ -33,3 +33,5 @@ saved_model/**/* filter=lfs diff=lfs merge=lfs -text
|
|
33 |
*.zip filter=lfs diff=lfs merge=lfs -text
|
34 |
*.zst filter=lfs diff=lfs merge=lfs -text
|
35 |
*tfevents* filter=lfs diff=lfs merge=lfs -text
|
|
|
|
|
|
33 |
*.zip filter=lfs diff=lfs merge=lfs -text
|
34 |
*.zst filter=lfs diff=lfs merge=lfs -text
|
35 |
*tfevents* filter=lfs diff=lfs merge=lfs -text
|
36 |
+
*.data-00000-of-00001 filter=lfs diff=lfs merge=lfs -text
|
37 |
+
*.snapshot filter=lfs diff=lfs merge=lfs -text
|
.gitignore
ADDED
@@ -0,0 +1 @@
|
|
|
|
|
1 |
+
__pycache__
|
README.md
CHANGED
@@ -1,8 +1,8 @@
|
|
1 |
---
|
2 |
-
title:
|
3 |
-
emoji:
|
4 |
-
colorFrom:
|
5 |
-
colorTo:
|
6 |
sdk: streamlit
|
7 |
sdk_version: 1.38.0
|
8 |
app_file: app.py
|
|
|
1 |
---
|
2 |
+
title: Recommender Engine
|
3 |
+
emoji: 🤗
|
4 |
+
colorFrom: pink
|
5 |
+
colorTo: green
|
6 |
sdk: streamlit
|
7 |
sdk_version: 1.38.0
|
8 |
app_file: app.py
|
app.py
ADDED
@@ -0,0 +1,65 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
import streamlit as st
|
2 |
+
import pandas as pd
|
3 |
+
from datetime import datetime
|
4 |
+
from inference import Inference
|
5 |
+
|
6 |
+
|
7 |
+
class QueryInputForm:
|
8 |
+
def __init__(self):
|
9 |
+
# Title of the Streamlit form
|
10 |
+
st.title("Query Input Form")
|
11 |
+
|
12 |
+
# Predefined options for channel and device type
|
13 |
+
self.channel_options = [
|
14 |
+
'Paid Social', 'Paid Search - Brand', 'Organic', 'Email - Transactional',
|
15 |
+
'Affiliate', 'Paid Search', 'Direct', 'Referral', 'Email - Marketing',
|
16 |
+
'Paid Search - Brand Reactivation', 'SMS - Marketing', 'Email - Trigger',
|
17 |
+
'Referral - Whitelabel', 'Referral - Merchant', 'Social', 'SMS - Trigger',
|
18 |
+
]
|
19 |
+
|
20 |
+
self.device_type_options = [
|
21 |
+
'Mobile', 'Desktop', 'Phablet', 'Tablet', '', 'TV',
|
22 |
+
'Portable Media Player', 'Wearable',
|
23 |
+
]
|
24 |
+
|
25 |
+
# Default values for the form
|
26 |
+
self.default_user_id = "b7485193f4e7f5b8ac3c94f71f4456a9"
|
27 |
+
self.default_query_text = "pizza"
|
28 |
+
|
29 |
+
# Initialize the recommender engine
|
30 |
+
self.recommender_engine = Inference()
|
31 |
+
|
32 |
+
def display_form(self):
|
33 |
+
# Input fields for user ID, channel, device type, and query text
|
34 |
+
self.user_id = st.text_input("User ID", value=self.default_user_id)
|
35 |
+
self.channel = st.selectbox("Channel", options=self.channel_options)
|
36 |
+
self.device_type = st.selectbox("Device Type", options=self.device_type_options)
|
37 |
+
self.query_text = st.text_input("Query Text", value=self.default_query_text)
|
38 |
+
|
39 |
+
# Submit button
|
40 |
+
if st.button("Submit"):
|
41 |
+
self.submit()
|
42 |
+
|
43 |
+
def submit(self):
|
44 |
+
# Pass the query information to the recommender engine
|
45 |
+
raw_query = {
|
46 |
+
'user_id': self.user_id,
|
47 |
+
'channel': self.channel,
|
48 |
+
'device_type': self.device_type,
|
49 |
+
'query_text': self.query_text,
|
50 |
+
'time': datetime.now().strftime("%Y-%m-%d %H:%M:%S.%f"),
|
51 |
+
}
|
52 |
+
|
53 |
+
# Get recommendations
|
54 |
+
self.recommender_engine.get_recommendations(raw_query)
|
55 |
+
self.display_recommendations()
|
56 |
+
|
57 |
+
def display_recommendations(self):
|
58 |
+
# Output the recommendations from the inference engine
|
59 |
+
st.write("Top recommendations:")
|
60 |
+
st.dataframe(self.recommender_engine.recommendations, hide_index=True)
|
61 |
+
|
62 |
+
|
63 |
+
if __name__ == "__main__":
|
64 |
+
form = QueryInputForm()
|
65 |
+
form.display_form()
|
inference.py
ADDED
@@ -0,0 +1,87 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
import tensorflow as tf
|
2 |
+
import os
|
3 |
+
|
4 |
+
|
5 |
+
class Inference():
|
6 |
+
def __init__(self):
|
7 |
+
|
8 |
+
index_path = os.path.join("recommender_model", "index")
|
9 |
+
model_path = os.path.join("recommender_model", "model")
|
10 |
+
self.index = tf.keras.models.load_model(index_path)
|
11 |
+
self.model = tf.keras.models.load_model(model_path)
|
12 |
+
|
13 |
+
products_path = os.path.join("products")
|
14 |
+
self.products = tf.data.Dataset.load(products_path)
|
15 |
+
|
16 |
+
self.model_product_features = [
|
17 |
+
'product_id', 'category_name', 'merchant_name', 'merchant_city', 'merchant_state', 'merchant_region',
|
18 |
+
'free_shipping', 'is_sold_out', 'editor_pick', 'on_sale', 'product_name', 'price_in_cents', 'reviews'
|
19 |
+
]
|
20 |
+
|
21 |
+
def get_recommendations(self, raw_query: dict):
|
22 |
+
self.query_input = {
|
23 |
+
'user_id': tf.convert_to_tensor(raw_query['user_id'], dtype=tf.string),
|
24 |
+
'channel': tf.convert_to_tensor(raw_query['channel'], dtype=tf.string),
|
25 |
+
'device_type': tf.convert_to_tensor(raw_query['device_type'], dtype=tf.string),
|
26 |
+
'query_text': tf.convert_to_tensor(raw_query['query_text'], dtype=tf.string),
|
27 |
+
'time': tf.convert_to_tensor(raw_query['time'], dtype=tf.string),
|
28 |
+
}
|
29 |
+
|
30 |
+
# Get recommendations. Note that I am expanding the dimension to match the batch size expected by the model
|
31 |
+
_, self.top_rec = self.index({k: [v] for k, v in self.query_input.items()})
|
32 |
+
|
33 |
+
# Filter by product id
|
34 |
+
filtered_recs = self.products.filter(self.filter_by_id)
|
35 |
+
# Add query input
|
36 |
+
query_added_recs = filtered_recs.map(lambda x: {**self.query_input, **x})
|
37 |
+
# Get score
|
38 |
+
score_added_recs = query_added_recs.batch(8).map(self.get_score).unbatch()
|
39 |
+
|
40 |
+
# Drop unwanted columns
|
41 |
+
recs = score_added_recs.map(self.desired_output)
|
42 |
+
|
43 |
+
# Order by score
|
44 |
+
ordered_recs = self.order_by_score(recs)
|
45 |
+
|
46 |
+
# Decode values
|
47 |
+
self.recommendations = list(map(self.decode_values, ordered_recs))
|
48 |
+
|
49 |
+
def filter_by_id(self, item):
|
50 |
+
return tf.reduce_any(tf.equal(item['product_id'], self.top_rec[0]))
|
51 |
+
|
52 |
+
def get_score(self, item):
|
53 |
+
input_data = {k: v for k, v in item.items() if k in self.model_product_features + list(self.query_input.keys())}
|
54 |
+
_, _, score = self.model(input_data)
|
55 |
+
item['score'] = score
|
56 |
+
return item
|
57 |
+
|
58 |
+
def desired_output(self, item):
|
59 |
+
return {
|
60 |
+
'product_name': item['product_name'],
|
61 |
+
'category_name': item['category_name'],
|
62 |
+
'price_in_cents': item['price_in_cents'],
|
63 |
+
'reviews': item['reviews'],
|
64 |
+
'merchant_name': item['merchant_name'],
|
65 |
+
'merchant_city': item['merchant_city'],
|
66 |
+
'merchant_state': item['merchant_state'],
|
67 |
+
'merchant_region': item['merchant_region'],
|
68 |
+
'free_shipping': item['free_shipping'],
|
69 |
+
'is_sold_out': item['is_sold_out'],
|
70 |
+
'editor_pick': item['editor_pick'],
|
71 |
+
'on_sale': item['on_sale'],
|
72 |
+
'score': item['score']
|
73 |
+
}
|
74 |
+
|
75 |
+
def order_by_score(self, recs):
|
76 |
+
rec_list = list(recs.as_numpy_iterator())
|
77 |
+
|
78 |
+
# Descending order by score
|
79 |
+
return sorted(rec_list, key=lambda x: x['score'], reverse=True)
|
80 |
+
|
81 |
+
def decode_values(self, item):
|
82 |
+
for key, value in item.items():
|
83 |
+
if isinstance(value, bytes):
|
84 |
+
item[key] = value.decode('utf-8')
|
85 |
+
if key == 'score':
|
86 |
+
item[key] = value[0]
|
87 |
+
return item
|
products/4470620803936518297/00000000.shard/00000000.snapshot
ADDED
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
1 |
+
version https://git-lfs.github.com/spec/v1
|
2 |
+
oid sha256:a6b7c352940d95107a6bfc9035e75d6797ad9f18449205e1f718ec3757835065
|
3 |
+
size 5133290
|
products/7776351335182402423/00000000.shard/00000000.snapshot
ADDED
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
1 |
+
version https://git-lfs.github.com/spec/v1
|
2 |
+
oid sha256:a6b7c352940d95107a6bfc9035e75d6797ad9f18449205e1f718ec3757835065
|
3 |
+
size 5133290
|
products/dataset_spec.pb
ADDED
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
1 |
+
version https://git-lfs.github.com/spec/v1
|
2 |
+
oid sha256:a6e96cdf4683d9d39dfc6f9479faacaf67e96d9e82abf87731908391a2b1b55d
|
3 |
+
size 411
|
products/snapshot.metadata
ADDED
@@ -0,0 +1 @@
|
|
|
|
|
1 |
+
4470620803936518297�ޘ��� * 0�H�>
|
recommender_model/index/fingerprint.pb
ADDED
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
1 |
+
version https://git-lfs.github.com/spec/v1
|
2 |
+
oid sha256:5fa78657cc3dc078663289fc7e2e2269269e30855679fa8dd8fe6ecc1cc784e3
|
3 |
+
size 56
|
recommender_model/index/keras_metadata.pb
ADDED
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
1 |
+
version https://git-lfs.github.com/spec/v1
|
2 |
+
oid sha256:feef2be744045e53d44c33c63dd223c3d14a1fe0d57db1044a5d8c3506bf8fb4
|
3 |
+
size 43956
|
recommender_model/index/saved_model.pb
ADDED
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
1 |
+
version https://git-lfs.github.com/spec/v1
|
2 |
+
oid sha256:54aa27f06f6c7bc92493e16f14fdc4c0d5a4418e68f5a7be3900a986167cd345
|
3 |
+
size 1340342
|
recommender_model/index/variables/variables.data-00000-of-00001
ADDED
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
1 |
+
version https://git-lfs.github.com/spec/v1
|
2 |
+
oid sha256:a8c214a6184250fe8cd49e17760e466ae3aafd7d952cd92338d754183dca2fea
|
3 |
+
size 21942235
|
recommender_model/index/variables/variables.index
ADDED
Binary file (1.02 kB). View file
|
|
recommender_model/model/fingerprint.pb
ADDED
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
1 |
+
version https://git-lfs.github.com/spec/v1
|
2 |
+
oid sha256:73dd5eb15d19b0f7ba30b2974616716ad220ab358f4f5a0ccb63f4a1abb91441
|
3 |
+
size 56
|
recommender_model/model/keras_metadata.pb
ADDED
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
1 |
+
version https://git-lfs.github.com/spec/v1
|
2 |
+
oid sha256:0f5a6e2a033359beeaa8ea2aaa03b19a37affcc7b0ee9be7383acf6d487bec32
|
3 |
+
size 114938
|
recommender_model/model/saved_model.pb
ADDED
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
1 |
+
version https://git-lfs.github.com/spec/v1
|
2 |
+
oid sha256:9ac62f41927afd25da1c115920addf4834d98db64c5a11dbfcb48307e3b5edd5
|
3 |
+
size 2888902
|
recommender_model/model/variables/variables.data-00000-of-00001
ADDED
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
1 |
+
version https://git-lfs.github.com/spec/v1
|
2 |
+
oid sha256:469452fc885fbaa976c2fc4867844c1b77258c1f4265a75699fc2a29316091fe
|
3 |
+
size 37821029
|
recommender_model/model/variables/variables.index
ADDED
Binary file (2.75 kB). View file
|
|
requirements.txt
ADDED
@@ -0,0 +1,2 @@
|
|
|
|
|
|
|
1 |
+
pandas
|
2 |
+
tensorflow==2.15.1
|