Skip to content

Commit

Permalink
add comirec model
Browse files Browse the repository at this point in the history
add comirec model
  • Loading branch information
shenweichen authored Oct 31, 2022
2 parents 5dab795 + b681eb8 commit 93ca437
Show file tree
Hide file tree
Showing 25 changed files with 786 additions and 48 deletions.
2 changes: 1 addition & 1 deletion .github/ISSUE_TEMPLATE/bug_report.md
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ Steps to reproduce the behavior:
**Operating environment(运行环境):**
- python version [e.g. 3.6, 3.7, 3.8]
- tensorflow version [e.g. 1.9.0, 1.14.0, 2.5.0]
- deepmatch version [e.g. 0.3.0,]
- deepmatch version [e.g. 0.3.1,]

**Additional context**
Add any other context about the problem here.
2 changes: 1 addition & 1 deletion .github/ISSUE_TEMPLATE/question.md
Original file line number Diff line number Diff line change
Expand Up @@ -17,4 +17,4 @@ Add any other context about the problem here.
**Operating environment(运行环境):**
- python version [e.g. 3.6, 3.7, 3.8]
- tensorflow version [e.g. 1.9.0, 1.14.0, 2.5.0]
- deepmatch version [e.g. 0.3.0,]
- deepmatch version [e.g. 0.3.1,]
21 changes: 19 additions & 2 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ jobs:
timeout-minutes: 120
strategy:
matrix:
python-version: [3.6,3.7,3.8]
python-version: [3.6,3.7,3.8,3.9,3.10.7]
tf-version: [1.9.0,1.14.0,2.5.0]

exclude:
Expand Down Expand Up @@ -57,12 +57,28 @@ jobs:
tf-version: 2.8.0
- python-version: 3.6
tf-version: 2.9.0
- python-version: 3.6
tf-version: 2.10.0
- python-version: 3.9
tf-version: 1.4.0
- python-version: 3.9
tf-version: 1.9.0
- python-version: 3.9
tf-version: 1.15.0
- python-version: 3.9
tf-version: 2.2.0
tf-version: 1.14.0
- python-version: 3.10.7
tf-version: 1.4.0
- python-version: 3.10.7
tf-version: 1.9.0
- python-version: 3.10.7
tf-version: 1.15.0
- python-version: 3.10.7
tf-version: 1.14.0
- python-version: 3.10.7
tf-version: 2.5.0
- python-version: 3.10.7
tf-version: 2.6.0

steps:

Expand All @@ -75,6 +91,7 @@ jobs:

- name: Install dependencies
run: |
sudo apt update && sudo apt install -y pkg-config libhdf5-dev
pip3 install -q tensorflow==${{ matrix.tf-version }}
pip install -q protobuf==3.19.0
pip install -q requests
Expand Down
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
.idea
# Byte-compiled / optimized / DLL files
__pycache__/
*.py[cod]
Expand Down
6 changes: 6 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ Let's [**Get Started!**](https://deepmatch.readthedocs.io/en/latest/Quick-Start.
| NCF | [WWW 2017][Neural Collaborative Filtering](https://arxiv.org/abs/1708.05031) |
| SDM | [CIKM 2019][SDM: Sequential Deep Matching Model for Online Large-scale Recommender System](https://arxiv.org/abs/1909.00385) |
| MIND | [CIKM 2019][Multi-interest network with dynamic routing for recommendation at Tmall](https://arxiv.org/pdf/1904.08030) |
| COMIREC | [KDD 2020][Controllable Multi-Interest Framework for Recommendation](https://arxiv.org/pdf/2005.09347.pdf) |

## Contributors([welcome to join us!](./CONTRIBUTING.md))

Expand Down Expand Up @@ -60,6 +61,11 @@ Let's [**Get Started!**](https://deepmatch.readthedocs.io/en/latest/Quick-Start.
<a href="https://github.com/LeoCai">LeoCai</a>
<p> ByteDance </p>​
</td>
<td>
​ <a href="https://github.com/liyuan97"><img width="70" height="70" src="https://github.com/liyuan97.png?s=40" alt="pic"></a><br>
​ <a href="https://github.com/liyuan97">Li Yuan</a>
<p> Tencent </p>​
</td>
<td>
​ <a href="https://github.com/yangjieyu"><img width="70" height="70" src="https://github.com/yangjieyu.png?s=40" alt="pic"></a><br>
​ <a href="https://github.com/yangjieyu">Yang Jieyu</a>
Expand Down
2 changes: 1 addition & 1 deletion deepmatch/__init__.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
from .utils import check_version

__version__ = '0.3.0'
__version__ = '0.3.1'
check_version(__version__)
1 change: 1 addition & 0 deletions deepmatch/models/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,3 +4,4 @@
from .ncf import NCF
from .mind import MIND
from .sdm import SDM
from .comirec import ComiRec
186 changes: 186 additions & 0 deletions deepmatch/models/comirec.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,186 @@
"""
Author:
Li Yuan, lysysu@qq.com
Reference:
Yukuo Cen, Jianwei Zhang, Xu Zou, et al. Controllable Multi-Interest Framework for Recommendation//Accepted to KDD 2020
"""

import tensorflow as tf
from deepctr.feature_column import SparseFeat, VarLenSparseFeat, DenseFeat, \
embedding_lookup, varlen_embedding_lookup, get_varlen_pooling_list, get_dense_input, build_input_features
from deepctr.layers import DNN, PositionEncoding
from deepctr.layers.utils import NoMask, combined_dnn_input, add_func
from tensorflow.python.keras.layers import Concatenate, Lambda
from tensorflow.python.keras.models import Model

from ..inputs import create_embedding_matrix
from ..layers.core import CapsuleLayer, PoolingLayer, MaskUserEmbedding, LabelAwareAttention, SampledSoftmaxLayer, \
EmbeddingIndex
from ..layers.interaction import SoftmaxWeightedSum
from ..utils import get_item_embedding


def tile_user_otherfeat(user_other_feature, k_max):
return tf.tile(tf.expand_dims(user_other_feature, -2), [1, k_max, 1])


def tile_user_his_mask(hist_len, seq_max_len, k_max):
return tf.tile(tf.sequence_mask(hist_len, seq_max_len), [1, k_max, 1])


def softmax_Weighted_Sum(input):
history_emb_add_pos, mask, attn = input[0], input[1], input[2]
attn = tf.transpose(attn, [0, 2, 1])
pad = tf.ones_like(mask, dtype=tf.float32) * (-2 ** 32 + 1)
attn = tf.where(mask, attn, pad) # [batch_size, seq_len, num_interests]
attn = tf.nn.softmax(attn) # [batch_size, seq_len, num_interests]
high_capsule = tf.matmul(attn, history_emb_add_pos)
return high_capsule


def ComiRec(user_feature_columns, item_feature_columns, k_max=2, p=100, interest_extractor='sa',
add_pos=True,
user_dnn_hidden_units=(64, 32), dnn_activation='relu', dnn_use_bn=False, l2_reg_dnn=0,
l2_reg_embedding=1e-6,
dnn_dropout=0, output_activation='linear', sampler_config=None, seed=1024):
"""Instantiates the ComiRec Model architecture.
:param user_feature_columns: An iterable containing user's features used by the model.
:param item_feature_columns: An iterable containing item's features used by the model.
:param k_max: int, the max size of user interest embedding
:param p: float,the parameter for adjusting the attention distribution in LabelAwareAttention.
:param interest_extractor: string, type of a multi-interest extraction module, 'sa' means self-attentive and 'dr' means dynamic routing
:param add_pos: bool. Whether use positional encoding layer
:param user_dnn_hidden_units: list,list of positive integer or empty list, the layer number and units in each layer of user tower
:param dnn_activation: Activation function to use in deep net
:param dnn_use_bn: bool. Whether use BatchNormalization before activation or not in deep net
:param l2_reg_dnn: L2 regularizer strength applied to DNN
:param l2_reg_embedding: float. L2 regularizer strength applied to embedding vector
:param dnn_dropout: float in [0,1), the probability we will drop out a given DNN coordinate.
:param output_activation: Activation function to use in output layer
:param sampler_config: negative sample config.
:param seed: integer ,to use as random seed.
:return: A Keras model instance.
"""

if len(item_feature_columns) > 1:
raise ValueError("Now ComiRec only support 1 item feature like item_id")
if interest_extractor.lower() not in ['dr', 'sa']:
raise ValueError("Now ComiRec only support dr and sa two interest_extractor")
item_feature_column = item_feature_columns[0]
item_feature_name = item_feature_column.name
item_vocabulary_size = item_feature_columns[0].vocabulary_size
item_embedding_dim = item_feature_columns[0].embedding_dim
if user_dnn_hidden_units[-1] != item_embedding_dim:
user_dnn_hidden_units = tuple(list(user_dnn_hidden_units) + [item_embedding_dim])
# item_index = Input(tensor=tf.constant([list(range(item_vocabulary_size))]))

history_feature_list = [item_feature_name]

features = build_input_features(user_feature_columns)
sparse_feature_columns = list(
filter(lambda x: isinstance(x, SparseFeat), user_feature_columns)) if user_feature_columns else []
dense_feature_columns = list(
filter(lambda x: isinstance(x, DenseFeat), user_feature_columns)) if user_feature_columns else []
varlen_sparse_feature_columns = list(
filter(lambda x: isinstance(x, VarLenSparseFeat), user_feature_columns)) if user_feature_columns else []
history_feature_columns = []
sparse_varlen_feature_columns = []
history_fc_names = list(map(lambda x: "hist_" + x, history_feature_list))
for fc in varlen_sparse_feature_columns:
feature_name = fc.name
if feature_name in history_fc_names:
history_feature_columns.append(fc)
else:
sparse_varlen_feature_columns.append(fc)
seq_max_len = history_feature_columns[0].maxlen
inputs_list = list(features.values())

embedding_matrix_dict = create_embedding_matrix(user_feature_columns + item_feature_columns, l2_reg_embedding,
seed=seed, prefix="")

item_features = build_input_features(item_feature_columns)

query_emb_list = embedding_lookup(embedding_matrix_dict, item_features, item_feature_columns,
history_feature_list,
history_feature_list, to_list=True)
keys_emb_list = embedding_lookup(embedding_matrix_dict, features, history_feature_columns, history_fc_names,
history_fc_names, to_list=True)
dnn_input_emb_list = embedding_lookup(embedding_matrix_dict, features, sparse_feature_columns,
mask_feat_list=history_feature_list, to_list=True)
dense_value_list = get_dense_input(features, dense_feature_columns)

sequence_embed_dict = varlen_embedding_lookup(embedding_matrix_dict, features, sparse_varlen_feature_columns)
sequence_embed_list = get_varlen_pooling_list(sequence_embed_dict, features, sparse_varlen_feature_columns,
to_list=True)

dnn_input_emb_list += sequence_embed_list

# keys_emb = concat_func(keys_emb_list, mask=True)
# query_emb = concat_func(query_emb_list, mask=True)

history_emb = PoolingLayer()(NoMask()(keys_emb_list)) # [None, max_len, emb_dim]
target_emb = PoolingLayer()(NoMask()(query_emb_list))

# target_emb_size = target_emb.get_shape()[-1].value
# max_len = history_emb.get_shape()[1].value
hist_len = features['hist_len']

high_capsule = None
if interest_extractor.lower() == 'dr':
high_capsule = CapsuleLayer(input_units=item_embedding_dim,
out_units=item_embedding_dim, max_len=seq_max_len,
k_max=k_max)((history_emb, hist_len))
elif interest_extractor.lower() == 'sa':
history_emb_add_pos = history_emb
if add_pos:
position_embedding = PositionEncoding()(history_emb)
history_emb_add_pos = add_func([history_emb_add_pos, position_embedding]) # [None, max_len, emb_dim]

attn = DNN((item_embedding_dim * 4, k_max), activation='tanh', l2_reg=l2_reg_dnn,
dropout_rate=dnn_dropout, use_bn=dnn_use_bn, output_activation=None, seed=seed,
name="user_dnn_attn")(history_emb_add_pos)
mask = Lambda(tile_user_his_mask, arguments={'k_max': k_max,
'seq_max_len': seq_max_len})(
hist_len) # [None, k_max, max_len]

high_capsule = Lambda(softmax_Weighted_Sum)((history_emb_add_pos, mask, attn))

if len(dnn_input_emb_list) > 0 or len(dense_value_list) > 0:
user_other_feature = combined_dnn_input(dnn_input_emb_list, dense_value_list)
other_feature_tile = Lambda(tile_user_otherfeat, arguments={'k_max': k_max})(user_other_feature)
user_deep_input = Concatenate()([NoMask()(other_feature_tile), high_capsule])
else:
user_deep_input = high_capsule

user_embeddings = DNN(user_dnn_hidden_units, dnn_activation, l2_reg_dnn,
dnn_dropout, dnn_use_bn, output_activation=output_activation, seed=seed,
name="user_dnn")(
user_deep_input)

item_inputs_list = list(item_features.values())

item_embedding_matrix = embedding_matrix_dict[item_feature_name]

item_index = EmbeddingIndex(list(range(item_vocabulary_size)))(item_features[item_feature_name])

item_embedding_weight = NoMask()(item_embedding_matrix(item_index))

pooling_item_embedding_weight = PoolingLayer()([item_embedding_weight])

user_embedding_final = LabelAwareAttention(k_max=k_max, pow_p=p)((user_embeddings, target_emb))

output = SampledSoftmaxLayer(sampler_config._asdict())(
[pooling_item_embedding_weight, user_embedding_final, item_features[item_feature_name]])
model = Model(inputs=inputs_list + item_inputs_list, outputs=output)

model.__setattr__("user_input", inputs_list)
model.__setattr__("user_embedding", user_embeddings)

model.__setattr__("item_input", item_inputs_list)
model.__setattr__("item_embedding",
get_item_embedding(pooling_item_embedding_weight, item_features[item_feature_name]))

return model
4 changes: 1 addition & 3 deletions deepmatch/models/mind.py
Original file line number Diff line number Diff line change
Expand Up @@ -53,11 +53,9 @@ def MIND(user_feature_columns, item_feature_columns, k_max=2, p=100, dynamic_k=F
:param user_feature_columns: An iterable containing user's features used by the model.
:param item_feature_columns: An iterable containing item's features used by the model.
:param num_sampled: int, the number of classes to randomly sample per batch.
:param k_max: int, the max size of user interest embedding
:param p: float,the parameter for adjusting the attention distribution in LabelAwareAttention.
:param dynamic_k: bool, whether or not use dynamic interest number
:param dnn_use_bn: bool. Whether use BatchNormalization before activation or not in deep net
:param user_dnn_hidden_units: list,list of positive integer or empty list, the layer number and units in each layer of user tower
:param dnn_activation: Activation function to use in deep net
:param dnn_use_bn: bool. Whether use BatchNormalization before activation or not in deep net
Expand Down Expand Up @@ -169,7 +167,7 @@ def MIND(user_feature_columns, item_feature_columns, k_max=2, p=100, dynamic_k=F
user_embedding_final = LabelAwareAttention(k_max=k_max, pow_p=p)((user_embeddings, target_emb, interest_num))
else:
user_embedding_final = LabelAwareAttention(k_max=k_max, pow_p=p)((user_embeddings, target_emb))
print("swc")

output = SampledSoftmaxLayer(sampler_config._asdict())(
[pooling_item_embedding_weight, user_embedding_final, item_features[item_feature_name]])
model = Model(inputs=inputs_list + item_inputs_list, outputs=output)
Expand Down
1 change: 0 additions & 1 deletion deepmatch/models/sdm.py
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,6 @@ def SDM(user_feature_columns, item_feature_columns, history_feature_list, units=
:param user_feature_columns: An iterable containing user's features used by the model.
:param item_feature_columns: An iterable containing item's features used by the model.
:param history_feature_list: list,to indicate short and prefer sequence sparse field
:param num_sampled: int, the number of classes to randomly sample per batch.
:param units: int, dimension for each output layer
:param rnn_layers: int, layer number of rnn
:param dropout_rate: float in [0,1), the probability we will drop out a given DNN coordinate.
Expand Down
1 change: 0 additions & 1 deletion deepmatch/models/youtubednn.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,6 @@ def YoutubeDNN(user_feature_columns, item_feature_columns,
:param user_feature_columns: An iterable containing user's features used by the model.
:param item_feature_columns: An iterable containing item's features used by the model.
:param num_sampled: int, the number of classes to randomly sample per batch.
:param user_dnn_hidden_units: list,list of positive integer or empty list, the layer number and units in each layer of user tower
:param dnn_activation: Activation function to use in deep net
:param dnn_use_bn: bool. Whether use BatchNormalization before activation or not in deep net
Expand Down
Binary file added docs/pics/comirec.jpg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
7 changes: 7 additions & 0 deletions docs/source/Features.md
Original file line number Diff line number Diff line change
Expand Up @@ -96,3 +96,10 @@

[Multi-interest network with dynamic routing for recommendation at Tmall](https://arxiv.org/pdf/1904.08030)

### COMIREC (Controllable Multi-Interest Framework for Recommendation)

[**COMIREC Model API**](./deepmatch.models.comirec.html)

![COMIREC](../pics/comirec.jpg)

[Controllable Multi-Interest Framework for Recommendation](https://arxiv.org/pdf/2005.09347)
1 change: 1 addition & 0 deletions docs/source/History.md
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
# History
- 10/31/2022 : [v0.3.1](https://github.com/shenweichen/DeepMatch/releases/tag/v0.3.1) released.Add `ComiRec` model .
- 07/04/2022 : [v0.3.0](https://github.com/shenweichen/DeepMatch/releases/tag/v0.3.0) released.Support different negative sampling strategies, including `inbatch`, `uniform`, `frequency`, `adaptive`.
- 06/17/2022 : [v0.2.1](https://github.com/shenweichen/DeepMatch/releases/tag/v0.2.1) released.Fix some bugs.
- 10/12/2020 : [v0.2.0](https://github.com/shenweichen/DeepMatch/releases/tag/v0.2.0) released.Support different initializers for different embedding weights and loading pretrained embeddings.
Expand Down
2 changes: 1 addition & 1 deletion docs/source/Models.rst
Original file line number Diff line number Diff line change
Expand Up @@ -9,5 +9,5 @@ DeepMatch Models API
NCF<deepmatch.models.ncf>
SDM<deepmatch.models.sdm>
MIND<deepmatch.models.mind>

COMIREC<deepmatch.models.comirec>

2 changes: 1 addition & 1 deletion docs/source/conf.py
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@
# The short X.Y version
version = ''
# The full version, including alpha/beta/rc tags
release = '0.3.0'
release = '0.3.1'


# -- General configuration ---------------------------------------------------
Expand Down
7 changes: 7 additions & 0 deletions docs/source/deepmatch.models.comirec.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
deepmatch.models.comirec module
============================

.. automodule:: deepmatch.models.comirec
:members:
:no-undoc-members:
:no-show-inheritance:
1 change: 1 addition & 0 deletions docs/source/deepmatch.models.rst
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ Submodules
deepmatch.models.dssm
deepmatch.models.fm
deepmatch.models.mind
deepmatch.models.comirec
deepmatch.models.ncf
deepmatch.models.sdm
deepmatch.models.youtubednn
Expand Down
4 changes: 2 additions & 2 deletions docs/source/index.rst
Original file line number Diff line number Diff line change
Expand Up @@ -37,12 +37,12 @@ You can read the latest code at https://github.com/shenweichen/DeepMatch
News
-----

10/31/2022 : Add `ComiRec` . `Changelog <https://github.com/shenweichen/DeepMatch/releases/tag/v0.3.1>`_

07/04/2022 : Support different negative sampling strategies, including `inbatch` , `uniform` , `frequency` , `adaptive` . `Changelog <https://github.com/shenweichen/DeepMatch/releases/tag/v0.3.0>`_

06/17/2022 : Fix some bugs. `Changelog <https://github.com/shenweichen/DeepMatch/releases/tag/v0.2.1>`_

10/12/2020 : Support different initializers for different embedding weights and loading pretrained embeddings. `Changelog <https://github.com/shenweichen/DeepMatch/releases/tag/v0.2.0>`_

DisscussionGroup
-----------------------

Expand Down
Loading

0 comments on commit 93ca437

Please sign in to comment.