-
-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathgradient_descent.py
141 lines (116 loc) · 5.51 KB
/
gradient_descent.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
from typing import Optional, Tuple, Sequence
import tensorflow as tf
from tensorflow.keras import Input, Model
from tensorflow.keras.layers import Embedding, Dot, Flatten, Add
from tensorflow.keras.layers.experimental.preprocessing import StringLookup
from tensorflow.keras.regularizers import l2
class RecommenderGD(tf.keras.Model):
"""Collaborative filtering recommender using matrix factorization approach
based on gradient descent.
:param embed_size: size of embeddings.
:param users: unique users.
:param items: unique items.
:param random_seed: random seed.
:param l2_penalty: penalty value for L2 regularization.
:param kwargs: additional arguments to tensorflow.keras.Model.
"""
def __init__(self, embed_size: int, users: Sequence, items: Sequence,
random_seed: Optional[int] = None,
l2_penalty: Optional[float] = None, **kwargs) -> None:
if random_seed is not None:
tf.random.set_seed(random_seed)
super().__init__(**kwargs)
self.l2_penalty = l2_penalty
self.embed_size = embed_size
# Layers
self._user_lookup = StringLookup(vocabulary=users, name='UserLookup')
self._item_lookup = StringLookup(vocabulary=items, name='ItemLookup')
self._flatten = Flatten(name='RatingValue')
self._dot_product = Dot(axes=2, name='EmbeddingProduct')
self._user_embedding = self._get_embed(
len(users), self.embed_size, 'UserEmbedding')
self._item_embedding = self._get_embed(
len(items), self.embed_size, 'ItemEmbedding')
def _get_embed(self, items_count: int, embed_size: int,
name: Optional[str] = None) -> tf.keras.layers.Embedding:
"""Create embedding layer.
:param items_count: number of items which embeddings to create.
:param embed_size: size of embeddings.
:param name: name of layer.
:return: embedding layer.
"""
l2_reg = l2(self.l2_penalty) if self.l2_penalty else None
return Embedding(items_count + 1, embed_size, name=name,
embeddings_initializer='he_normal',
embeddings_regularizer=l2_reg)
def call(self, inputs: Tuple[Sequence, Sequence], **kwargs) -> tf.Tensor:
"""Call the model.
:param inputs: model inputs as user_id and item_id.
:return: model output.
"""
user_ids, item_ids = inputs
user_indices = self._user_lookup(user_ids)
item_indices = self._item_lookup(item_ids)
user_embeds = self._user_embedding(user_indices)
item_embeds = self._item_embedding(item_indices)
product = self._dot_product([user_embeds, item_embeds])
return self._flatten(product)
def build_graph(self) -> tf.keras.Model:
"""Compute model graph for visualization.
:return: model.
"""
user_input = Input(shape=(1,), name='UserID')
item_input = Input(shape=(1,), name='ItemID')
return Model(inputs=(user_input, item_input),
outputs=self.call((user_input, item_input)),
name=self.name)
class RecommenderGDBiased(RecommenderGD):
"""Collaborative filtering recommender using matrix factorization approach
based on the gradient descent.
Uses embeddings with users' and items' biases.
:param embed_size: size of embeddings.
:param users: unique users.
:param items: unique items.
:param random_seed: random seed.
:param l2_penalty: penalty value for L2 regularization.
:param kwargs: additional arguments to tensorflow.keras.Model.
"""
def __init__(self, embed_size: int, users: Sequence, items: Sequence,
random_seed: Optional[int] = None,
l2_penalty: Optional[float] = None, **kwargs) -> None:
super().__init__(embed_size, users, items, random_seed,
l2_penalty, **kwargs)
# Additional layers
self._user_bias = self._get_embed(len(users), 1, 'UserBias')
self._item_bias = self._get_embed(len(items), 1, 'ItemBias')
self._component_sum = Add(name='ComponentSum')
self._global_bias = BiasLayer(name='GlobalBias')
def call(self, inputs: Tuple[Sequence, Sequence], **kwargs) -> tf.Tensor:
"""Call the model.
:param inputs: model inputs as user_id and item_id.
:return: model output.
"""
user_ids, item_ids = inputs
user_indices = self._user_lookup(user_ids)
user_embed = self._user_embedding(user_indices)
item_indices = self._item_lookup(item_ids)
item_embed = self._item_embedding(item_indices)
product = self._dot_product([user_embed, item_embed])
user_bias = self._user_bias(user_indices)
item_bias = self._item_bias(item_indices)
component_sum = self._component_sum([product, user_bias, item_bias])
biased_rating = self._global_bias(component_sum)
return self._flatten(biased_rating)
class BiasLayer(tf.keras.layers.Layer):
"""Layer that adds a value to the input."""
def __init__(self, *args, **kwargs) -> None:
super().__init__(*args, **kwargs)
self.bias = self.add_weight('bias', shape=(1, 1),
initializer='random_uniform',
trainable=True)
def call(self, inputs: tf.Tensor, **kwargs) -> tf.Tensor:
"""Call the layer.
:param inputs: layer inputs.
:return: layer output.
"""
return inputs + self.bias