From a585cd902f9d47ca98f2a6a26c12c3725c3e5e71 Mon Sep 17 00:00:00 2001 From: Hane Date: Sun, 13 Mar 2022 14:43:08 +0800 Subject: [PATCH 01/10] Add user features and modify candidates format in ranking layer --- recommendation_system/main.py | 6 +++--- recommendation_system/ranking_layer/base.py | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/recommendation_system/main.py b/recommendation_system/main.py index a19e0d4..c50cb30 100644 --- a/recommendation_system/main.py +++ b/recommendation_system/main.py @@ -17,11 +17,11 @@ def recommend(cls, recipient_id: Text) -> List[Dict]: """ experiment_config: Dict = cls._load_experiment_config(recipient_id) user_features: Dict = cls._get_feature(recipient_id) - candidates: List[Dict] = [] + candidates: List[List[Dict]] = [] for cancidate_model in cls._get_candidate_models(experiment_config): - candidates += cancidate_model.get_candidates(user_features) + candidates.append(cancidate_model.get_candidates(user_features)) # TODO: For now, it only support 1 ranking layer. We should suuport multiple ranking layers to sort at some point. - result = cls._get_ranking_model(experiment_config).rank(candidates) + result = cls._get_ranking_model(experiment_config).rank(user_features, candidates) filtered_result = cls._filter(result) return filtered_result diff --git a/recommendation_system/ranking_layer/base.py b/recommendation_system/ranking_layer/base.py index 00e5e7c..05d9710 100644 --- a/recommendation_system/ranking_layer/base.py +++ b/recommendation_system/ranking_layer/base.py @@ -4,5 +4,5 @@ class BaseRankingModel(abc.ABC): @abc.abstractmethod - def rank(user_features: Dict) -> List[Dict]: + def rank(user_features: Dict, candidates: List[List[Dict]]) -> List[Dict]: pass From 80d0d2955046c200d981a53a2a1494d5646f455a Mon Sep 17 00:00:00 2001 From: Hane Date: Sun, 13 Mar 2022 14:44:34 +0800 Subject: [PATCH 02/10] Add simple ranking model --- recommendation_system/ranking_layer/simple.py | 11 +++++++++++ 1 file changed, 11 insertions(+) create mode 100644 recommendation_system/ranking_layer/simple.py diff --git a/recommendation_system/ranking_layer/simple.py b/recommendation_system/ranking_layer/simple.py new file mode 100644 index 0000000..a63ee79 --- /dev/null +++ b/recommendation_system/ranking_layer/simple.py @@ -0,0 +1,11 @@ +from typing import List, Dict + +from recommendation_system.ranking_layer.base import BaseRankingModel + +class SimpleRankingModel(BaseRankingModel): + def __init__(self) -> None: + super().__init__() + + def rank(user_features: Dict, candidates: List[List[Dict]]) -> List[Dict]: + + return candidates From 21ae6d71d1111a833b81752771851d1a27ac3909 Mon Sep 17 00:00:00 2001 From: Hane Date: Sun, 13 Mar 2022 15:12:12 +0800 Subject: [PATCH 03/10] Process data to record all candidate items and per ranker's ranking score --- recommendation_system/ranking_layer/simple.py | 27 +++++++++++++++++-- 1 file changed, 25 insertions(+), 2 deletions(-) diff --git a/recommendation_system/ranking_layer/simple.py b/recommendation_system/ranking_layer/simple.py index a63ee79..1ef504a 100644 --- a/recommendation_system/ranking_layer/simple.py +++ b/recommendation_system/ranking_layer/simple.py @@ -1,4 +1,4 @@ -from typing import List, Dict +from typing import List, Dict, Tuple from recommendation_system.ranking_layer.base import BaseRankingModel @@ -6,6 +6,29 @@ class SimpleRankingModel(BaseRankingModel): def __init__(self) -> None: super().__init__() - def rank(user_features: Dict, candidates: List[List[Dict]]) -> List[Dict]: + def rank(self, user_features: Dict, candidates: List[List[Dict]]) -> List[Dict]: + candidates = self.process_data(candidates=candidates) return candidates + + def process_data(self, candidates: List[List[Dict]]) -> List[List[Tuple]]: + items_mapping = {} + new_candidates = [] + + for ranker in candidates: + new_candidate_per_ranker = {} + for i, candidate in enumerate(ranker): + items_mapping.setdefault(candidate['title'], { + 'title': candidates['title'], + 'subtitle': candidates['subtitle'], + 'image_url': candidates['image_url'], + 'date': candidates['date'], + 'url': candidates['url'] + }) + new_candidate_per_ranker[candidate['title']] = (i + 1, candidate['score']) # (rank, score) + + new_candidates.append(new_candidate_per_ranker) + + self._items_mapping = items_mapping + + return new_candidates From 88f0db8a82f99971f6594efa6a5d6f8839f80186 Mon Sep 17 00:00:00 2001 From: Hane Date: Sun, 13 Mar 2022 15:42:22 +0800 Subject: [PATCH 04/10] Calculate ranking score --- recommendation_system/ranking_layer/simple.py | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/recommendation_system/ranking_layer/simple.py b/recommendation_system/ranking_layer/simple.py index 1ef504a..0cdeafe 100644 --- a/recommendation_system/ranking_layer/simple.py +++ b/recommendation_system/ranking_layer/simple.py @@ -8,9 +8,24 @@ def __init__(self) -> None: def rank(self, user_features: Dict, candidates: List[List[Dict]]) -> List[Dict]: candidates = self.process_data(candidates=candidates) + rankings = self.calc_candidate_score(candidates=candidates) return candidates + def calc_candidate_score(self, candidates): + candidate_ranking = {} + + for item in self._items_mapping: + candidate_ranking.setdefault(item, []) + for ranker in candidates: + k, score = ranker[item] + candidate_ranking[item].append(score/k) + + for item, scores in candidate_ranking.items(): + candidate_ranking[item] = sum(scores) + + return sorted(candidate_ranking.items(), key=lambda d: d[1], reverse=True) + def process_data(self, candidates: List[List[Dict]]) -> List[List[Tuple]]: items_mapping = {} new_candidates = [] From bcb671a0fe0ef5346b6f541a0871ef099d9fb897 Mon Sep 17 00:00:00 2001 From: Hane Date: Sun, 13 Mar 2022 15:48:57 +0800 Subject: [PATCH 05/10] Get ranking item detailed info --- recommendation_system/ranking_layer/simple.py | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/recommendation_system/ranking_layer/simple.py b/recommendation_system/ranking_layer/simple.py index 0cdeafe..4412438 100644 --- a/recommendation_system/ranking_layer/simple.py +++ b/recommendation_system/ranking_layer/simple.py @@ -9,8 +9,14 @@ def __init__(self) -> None: def rank(self, user_features: Dict, candidates: List[List[Dict]]) -> List[Dict]: candidates = self.process_data(candidates=candidates) rankings = self.calc_candidate_score(candidates=candidates) + new_candidates = self.get_items([ + item for item, score in rankings + ]) - return candidates + return new_candidates + + def get_items(self, items): + return [self._items_mapping[i] for i in items] def calc_candidate_score(self, candidates): candidate_ranking = {} From 12e9966ee3c7ad2a9e26373ba1926636424e02a9 Mon Sep 17 00:00:00 2001 From: Hane Date: Sun, 13 Mar 2022 16:47:15 +0800 Subject: [PATCH 06/10] Fix error --- recommendation_system/ranking_layer/simple.py | 33 +++++++++++-------- 1 file changed, 19 insertions(+), 14 deletions(-) diff --git a/recommendation_system/ranking_layer/simple.py b/recommendation_system/ranking_layer/simple.py index 4412438..73335d6 100644 --- a/recommendation_system/ranking_layer/simple.py +++ b/recommendation_system/ranking_layer/simple.py @@ -1,3 +1,4 @@ +from copy import deepcopy from typing import List, Dict, Tuple from recommendation_system.ranking_layer.base import BaseRankingModel @@ -9,14 +10,17 @@ def __init__(self) -> None: def rank(self, user_features: Dict, candidates: List[List[Dict]]) -> List[Dict]: candidates = self.process_data(candidates=candidates) rankings = self.calc_candidate_score(candidates=candidates) - new_candidates = self.get_items([ - item for item, score in rankings - ]) - return new_candidates + new_candidates = [] + for item, score in rankings: + new_item = self.get_item(item) + new_item['score'] = score + new_candidates.append(new_item) + + return new_candidates[:3] - def get_items(self, items): - return [self._items_mapping[i] for i in items] + def get_item(self, item): + return deepcopy(self._items_mapping[item]) def calc_candidate_score(self, candidates): candidate_ranking = {} @@ -24,8 +28,9 @@ def calc_candidate_score(self, candidates): for item in self._items_mapping: candidate_ranking.setdefault(item, []) for ranker in candidates: - k, score = ranker[item] - candidate_ranking[item].append(score/k) + if item in ranker: + k, score = ranker.get(item) + candidate_ranking[item].append(score/k) for item, scores in candidate_ranking.items(): candidate_ranking[item] = sum(scores) @@ -40,13 +45,13 @@ def process_data(self, candidates: List[List[Dict]]) -> List[List[Tuple]]: new_candidate_per_ranker = {} for i, candidate in enumerate(ranker): items_mapping.setdefault(candidate['title'], { - 'title': candidates['title'], - 'subtitle': candidates['subtitle'], - 'image_url': candidates['image_url'], - 'date': candidates['date'], - 'url': candidates['url'] + 'title': candidate['title'], + 'subtitle': candidate['subtitle'], + 'image_url': candidate['image_url'], + 'date': candidate['date'], + 'url': candidate['url'] }) - new_candidate_per_ranker[candidate['title']] = (i + 1, candidate['score']) # (rank, score) + new_candidate_per_ranker[candidate['title']] = (i + 1, candidate.get('score')) # (rank, score) new_candidates.append(new_candidate_per_ranker) From a934450f14fd541c8cb4e4dda974f4a12262c385 Mon Sep 17 00:00:00 2001 From: Hane Date: Sun, 13 Mar 2022 16:51:58 +0800 Subject: [PATCH 07/10] Add user feature construct function --- recommendation_system/ranking_layer/simple.py | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/recommendation_system/ranking_layer/simple.py b/recommendation_system/ranking_layer/simple.py index 73335d6..24ca163 100644 --- a/recommendation_system/ranking_layer/simple.py +++ b/recommendation_system/ranking_layer/simple.py @@ -58,3 +58,9 @@ def process_data(self, candidates: List[List[Dict]]) -> List[List[Tuple]]: self._items_mapping = items_mapping return new_candidates + + def build_user_embedding(self, user_features): + pass + + def calc_similarity(self, user_features): + pass From 991e26dfd75712dc29257082282b8eced0661e45 Mon Sep 17 00:00:00 2001 From: Hane Date: Sun, 13 Mar 2022 16:55:32 +0800 Subject: [PATCH 08/10] Add default score --- recommendation_system/ranking_layer/simple.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/recommendation_system/ranking_layer/simple.py b/recommendation_system/ranking_layer/simple.py index 24ca163..4ca05d8 100644 --- a/recommendation_system/ranking_layer/simple.py +++ b/recommendation_system/ranking_layer/simple.py @@ -51,7 +51,7 @@ def process_data(self, candidates: List[List[Dict]]) -> List[List[Tuple]]: 'date': candidate['date'], 'url': candidate['url'] }) - new_candidate_per_ranker[candidate['title']] = (i + 1, candidate.get('score')) # (rank, score) + new_candidate_per_ranker[candidate['title']] = (i + 1, candidate.get('score', 1)) # (rank, score) new_candidates.append(new_candidate_per_ranker) From 21a25ee9c6506a992e6bc931b53315edd2445583 Mon Sep 17 00:00:00 2001 From: Hane Date: Sun, 13 Mar 2022 16:56:22 +0800 Subject: [PATCH 09/10] Add ranking model --- recommendation_system/main.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/recommendation_system/main.py b/recommendation_system/main.py index c50cb30..ad041ec 100644 --- a/recommendation_system/main.py +++ b/recommendation_system/main.py @@ -35,7 +35,7 @@ def _load_experiment_config(recipient_id): "demo", # 'other candidate model for you guys to implement' ], - "ranking_model": "demo", + "ranking_model": "simple", # TODO: should replace base ranking model with your own! } From 9a7046f9a7adcb2a47078b3afb210f8dee45a2d2 Mon Sep 17 00:00:00 2001 From: Hane Date: Sun, 13 Mar 2022 17:01:05 +0800 Subject: [PATCH 10/10] Add top_k argument --- recommendation_system/ranking_layer/simple.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/recommendation_system/ranking_layer/simple.py b/recommendation_system/ranking_layer/simple.py index 4ca05d8..0a6aa81 100644 --- a/recommendation_system/ranking_layer/simple.py +++ b/recommendation_system/ranking_layer/simple.py @@ -7,7 +7,7 @@ class SimpleRankingModel(BaseRankingModel): def __init__(self) -> None: super().__init__() - def rank(self, user_features: Dict, candidates: List[List[Dict]]) -> List[Dict]: + def rank(self, user_features: Dict, candidates: List[List[Dict]], top_k: int) -> List[Dict]: candidates = self.process_data(candidates=candidates) rankings = self.calc_candidate_score(candidates=candidates) @@ -17,7 +17,7 @@ def rank(self, user_features: Dict, candidates: List[List[Dict]]) -> List[Dict]: new_item['score'] = score new_candidates.append(new_item) - return new_candidates[:3] + return new_candidates[:top_k] def get_item(self, item): return deepcopy(self._items_mapping[item])