Skip to content

Commit 5dab795

Browse files
authored
refactor & add some negative sampling strategies (#81)
Support different negative sampling strategies, including `inbatch`, `uniform`, `frequency`, `adaptive`. 1. add `deepmatch.utils.NegativeSampler` 2. remove `deepmatch.layers.core.NegativeSampler` 3. add `temperature`,`sampler_config`,`loss_type` for models 4. add some google colab scripts
1 parent be6c028 commit 5dab795

37 files changed

+1953
-506
lines changed

.github/ISSUE_TEMPLATE/bug_report.md

+2-2
Original file line numberDiff line numberDiff line change
@@ -19,8 +19,8 @@ Steps to reproduce the behavior:
1919

2020
**Operating environment(运行环境):**
2121
- python version [e.g. 3.6, 3.7, 3.8]
22-
- tensorflow version [e.g. 1.4.0, 1.14.0, 2.5.0]
23-
- deepmatch version [e.g. 0.2.1,]
22+
- tensorflow version [e.g. 1.9.0, 1.14.0, 2.5.0]
23+
- deepmatch version [e.g. 0.3.0,]
2424

2525
**Additional context**
2626
Add any other context about the problem here.

.github/ISSUE_TEMPLATE/question.md

+2-2
Original file line numberDiff line numberDiff line change
@@ -16,5 +16,5 @@ Add any other context about the problem here.
1616

1717
**Operating environment(运行环境):**
1818
- python version [e.g. 3.6, 3.7, 3.8]
19-
- tensorflow version [e.g. 1.4.0, 1.14.0, 2.5.0]
20-
- deepmatch version [e.g. 0.2.1,]
19+
- tensorflow version [e.g. 1.9.0, 1.14.0, 2.5.0]
20+
- deepmatch version [e.g. 0.3.0,]

.github/workflows/ci.yml

+21-1
Original file line numberDiff line numberDiff line change
@@ -18,15 +18,35 @@ jobs:
1818
strategy:
1919
matrix:
2020
python-version: [3.6,3.7,3.8]
21-
tf-version: [1.4.0,1.14.0,2.5.0]
21+
tf-version: [1.9.0,1.14.0,2.5.0]
2222

2323
exclude:
2424
- python-version: 3.7
2525
tf-version: 1.4.0
26+
- python-version: 3.7
27+
tf-version: 1.9.0
28+
- python-version: 3.7
29+
tf-version: 1.10.0
30+
- python-version: 3.7
31+
tf-version: 1.11.0
32+
- python-version: 3.7
33+
tf-version: 1.12.0
34+
- python-version: 3.7
35+
tf-version: 1.13.0
2636
- python-version: 3.7
2737
tf-version: 1.15.0
2838
- python-version: 3.8
2939
tf-version: 1.4.0
40+
- python-version: 3.8
41+
tf-version: 1.9.0
42+
- python-version: 3.8
43+
tf-version: 1.10.0
44+
- python-version: 3.8
45+
tf-version: 1.11.0
46+
- python-version: 3.8
47+
tf-version: 1.12.0
48+
- python-version: 3.8
49+
tf-version: 1.13.0
3050
- python-version: 3.8
3151
tf-version: 1.14.0
3252
- python-version: 3.8

README.md

+12-29
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,8 @@
11
# DeepMatch
22

33
[![Python Versions](https://img.shields.io/pypi/pyversions/deepmatch.svg)](https://pypi.org/project/deepmatch)
4-
[![TensorFlow Versions](https://img.shields.io/badge/TensorFlow-1.4+/2.0+-blue.svg)](https://pypi.org/project/deepmatch)
4+
[![TensorFlow Versions](https://img.shields.io/badge/TensorFlow-1.9+/2.0+-blue.svg)](https://pypi.org/project/deepmatch)
5+
[![Downloads](https://pepy.tech/badge/deepmatch)](https://pepy.tech/project/deepmatch)
56
[![PyPI Version](https://img.shields.io/pypi/v/deepmatch.svg)](https://pypi.org/project/deepmatch)
67
[![GitHub Issues](https://img.shields.io/github/issues/shenweichen/deepmatch.svg
78
)](https://github.com/shenweichen/deepmatch/issues)
@@ -11,7 +12,8 @@
1112
[![Documentation Status](https://readthedocs.org/projects/deepmatch/badge/?version=latest)](https://deepmatch.readthedocs.io/)
1213
![CI status](https://github.com/shenweichen/deepmatch/workflows/CI/badge.svg)
1314
[![codecov](https://codecov.io/gh/shenweichen/DeepMatch/branch/master/graph/badge.svg)](https://codecov.io/gh/shenweichen/DeepMatch)
14-
[![Disscussion](https://img.shields.io/badge/chat-wechat-brightgreen?style=flat)](./README.md#disscussiongroup)
15+
[![Codacy Badge](https://app.codacy.com/project/badge/Grade/c5a2769ec35444d8958f6b58ff85029b)](https://www.codacy.com/gh/shenweichen/DeepMatch/dashboard?utm_source=github.com&utm_medium=referral&utm_content=shenweichen/DeepMatch&utm_campaign=Badge_Grade)
16+
[![Disscussion](https://img.shields.io/badge/chat-wechat-brightgreen?style=flat)](https://github.com/shenweichen/DeepMatch#disscussiongroup)
1517
[![License](https://img.shields.io/github/license/shenweichen/deepmatch.svg)](https://github.com/shenweichen/deepmatch/blob/master/LICENSE)
1618

1719
DeepMatch is a deep matching model library for recommendations & advertising. It's easy to **train models** and to **export representation vectors** for user and item which can be used for **ANN search**.You can use any complex model with `model.fit()`and `model.predict()` .
@@ -72,31 +74,12 @@ Let's [**Get Started!**](https://deepmatch.readthedocs.io/en/latest/Quick-Start.
7274
</tbody>
7375
</table>
7476

75-
## DisscussionGroup & Related Projects
77+
## DisscussionGroup
78+
79+
- [Github Discussions](https://github.com/shenweichen/DeepMatch/discussions)
80+
- Wechat Discussions
81+
82+
|公众号:浅梦学习笔记|微信:deepctrbot|学习小组 [加入](https://t.zsxq.com/026UJEuzv) [主题集合](https://mp.weixin.qq.com/mp/appmsgalbum?__biz=MjM5MzY4NzE3MA==&action=getalbum&album_id=1361647041096843265&scene=126#wechat_redirect)|
83+
|:--:|:--:|:--:|
84+
| [![公众号](./docs/pics/code.png)](https://github.com/shenweichen/AlgoNotes)| [![微信](./docs/pics/deepctrbot.png)](https://github.com/shenweichen/AlgoNotes)|[![学习小组](./docs/pics/planet_github.png)](https://t.zsxq.com/026UJEuzv)|
7685

77-
<html>
78-
<table style="margin-left: 20px; margin-right: auto;">
79-
<tr>
80-
<td>
81-
公众号:<b>浅梦的学习笔记</b><br><br>
82-
<a href="https://github.com/shenweichen/deepmatch">
83-
<img align="center" src="./docs/pics/code.png" />
84-
</a>
85-
</td>
86-
<td>
87-
微信:<b>deepctrbot</b><br><br>
88-
<a href="https://github.com/shenweichen/deepmatch">
89-
<img align="center" src="./docs/pics/deepctrbot.png" />
90-
</a>
91-
</td>
92-
<td>
93-
<ul>
94-
<li><a href="https://github.com/shenweichen/AlgoNotes">AlgoNotes</a></li>
95-
<li><a href="https://github.com/shenweichen/DeepCTR">DeepCTR</a></li>
96-
<li><a href="https://github.com/shenweichen/DeepCTR-Torch">DeepCTR-Torch</a></li>
97-
<li><a href="https://github.com/shenweichen/GraphEmbedding">GraphEmbedding</a></li>
98-
</ul>
99-
</td>
100-
</tr>
101-
</table>
102-
</html>

deepmatch/__init__.py

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
11
from .utils import check_version
22

3-
__version__ = '0.2.1'
3+
__version__ = '0.3.0'
44
check_version(__version__)

deepmatch/layers/__init__.py

+3-3
Original file line numberDiff line numberDiff line change
@@ -1,20 +1,20 @@
11
from deepctr.layers import custom_objects
22
from deepctr.layers.utils import reduce_sum
33

4-
from .core import PoolingLayer, Similarity, LabelAwareAttention, CapsuleLayer, SampledSoftmaxLayer, EmbeddingIndex, \
5-
MaskUserEmbedding
4+
from .core import PoolingLayer, LabelAwareAttention, CapsuleLayer, SampledSoftmaxLayer, EmbeddingIndex, \
5+
MaskUserEmbedding, InBatchSoftmaxLayer
66
from .interaction import DotAttention, ConcatAttention, SoftmaxWeightedSum, AttentionSequencePoolingLayer, \
77
SelfAttention, \
88
SelfMultiHeadAttention, UserAttention
99
from .sequence import DynamicMultiRNN
1010
from ..utils import sampledsoftmaxloss
1111

1212
_custom_objects = {'PoolingLayer': PoolingLayer,
13-
'Similarity': Similarity,
1413
'LabelAwareAttention': LabelAwareAttention,
1514
'CapsuleLayer': CapsuleLayer,
1615
'reduce_sum': reduce_sum,
1716
'SampledSoftmaxLayer': SampledSoftmaxLayer,
17+
'InBatchSoftmaxLayer': InBatchSoftmaxLayer,
1818
'sampledsoftmaxloss': sampledsoftmaxloss,
1919
'EmbeddingIndex': EmbeddingIndex,
2020
'DotAttention': DotAttention,

deepmatch/layers/core.py

+104-55
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,15 @@
1+
"""
2+
3+
Author:
4+
Weichen Shen,[email protected]
5+
6+
"""
7+
8+
import numpy as np
19
import tensorflow as tf
2-
from deepctr.layers.activation import activation_layer
310
from deepctr.layers.utils import reduce_max, reduce_mean, reduce_sum, concat_func, div, softmax
4-
from tensorflow.python.keras.initializers import RandomNormal, Zeros, TruncatedNormal
11+
from tensorflow.python.keras.initializers import Zeros
512
from tensorflow.python.keras.layers import Layer
6-
from tensorflow.python.keras.regularizers import l2
713

814

915
class PoolingLayer(Layer):
@@ -45,45 +51,103 @@ def get_config(self, ):
4551

4652

4753
class SampledSoftmaxLayer(Layer):
48-
def __init__(self, num_sampled=5, **kwargs):
49-
self.num_sampled = num_sampled
54+
def __init__(self, sampler_config, temperature=1.0, **kwargs):
55+
self.sampler_config = sampler_config
56+
self.temperature = temperature
57+
self.sampler = self.sampler_config['sampler']
58+
self.item_count = self.sampler_config['item_count']
59+
5060
super(SampledSoftmaxLayer, self).__init__(**kwargs)
5161

5262
def build(self, input_shape):
53-
self.size = input_shape[0][0]
54-
self.zero_bias = self.add_weight(shape=[self.size],
63+
self.vocabulary_size = input_shape[0][0]
64+
self.zero_bias = self.add_weight(shape=[self.vocabulary_size],
5565
initializer=Zeros,
5666
dtype=tf.float32,
5767
trainable=False,
5868
name="bias")
5969
super(SampledSoftmaxLayer, self).build(input_shape)
6070

61-
def call(self, inputs_with_label_idx, training=None, **kwargs):
62-
"""
63-
The first input should be the model as it were, and the second the
64-
target (i.e., a repeat of the training data) to compute the labels
65-
argument
66-
"""
67-
embeddings, inputs, label_idx = inputs_with_label_idx
68-
69-
loss = tf.nn.sampled_softmax_loss(weights=embeddings, # self.item_embedding.
70-
biases=self.zero_bias,
71-
labels=label_idx,
72-
inputs=inputs,
73-
num_sampled=self.num_sampled,
74-
num_classes=self.size, # self.target_song_size
75-
)
71+
def call(self, inputs_with_item_idx, training=None, **kwargs):
72+
item_embeddings, user_vec, item_idx = inputs_with_item_idx
73+
if item_idx.dtype != tf.int64:
74+
item_idx = tf.cast(item_idx, tf.int64)
75+
user_vec /= self.temperature
76+
if self.sampler == "inbatch":
77+
item_vec = tf.gather(item_embeddings, tf.squeeze(item_idx, axis=1))
78+
logits = tf.matmul(user_vec, item_vec, transpose_b=True)
79+
loss = inbatch_softmax_cross_entropy_with_logits(logits, self.item_count, item_idx)
80+
81+
else:
82+
num_sampled = self.sampler_config['num_sampled']
83+
if self.sampler == "frequency":
84+
sampled_values = tf.nn.fixed_unigram_candidate_sampler(item_idx, 1, num_sampled, True,
85+
self.vocabulary_size,
86+
distortion=self.sampler_config['distortion'],
87+
unigrams=np.maximum(self.item_count, 1).tolist(),
88+
seed=None,
89+
name=None)
90+
elif self.sampler == "adaptive":
91+
sampled_values = tf.nn.learned_unigram_candidate_sampler(item_idx, 1, num_sampled, True,
92+
self.vocabulary_size, seed=None, name=None)
93+
elif self.sampler == "uniform":
94+
try:
95+
sampled_values = tf.nn.uniform_candidate_sampler(item_idx, 1, num_sampled, True,
96+
self.vocabulary_size, seed=None, name=None)
97+
except AttributeError:
98+
sampled_values = tf.random.uniform_candidate_sampler(item_idx, 1, num_sampled, True,
99+
self.vocabulary_size, seed=None, name=None)
100+
else:
101+
raise ValueError(' `%s` sampler is not supported ' % self.sampler)
102+
103+
loss = tf.nn.sampled_softmax_loss(weights=item_embeddings,
104+
biases=self.zero_bias,
105+
labels=item_idx,
106+
inputs=user_vec,
107+
num_sampled=num_sampled,
108+
num_classes=self.vocabulary_size,
109+
sampled_values=sampled_values
110+
)
76111
return tf.expand_dims(loss, axis=1)
77112

78113
def compute_output_shape(self, input_shape):
79114
return (None, 1)
80115

81116
def get_config(self, ):
82-
config = {'num_sampled': self.num_sampled}
117+
config = {'sampler_config': self.sampler_config, 'temperature': self.temperature}
83118
base_config = super(SampledSoftmaxLayer, self).get_config()
84119
return dict(list(base_config.items()) + list(config.items()))
85120

86121

122+
class InBatchSoftmaxLayer(Layer):
123+
def __init__(self, sampler_config, temperature=1.0, **kwargs):
124+
self.sampler_config = sampler_config
125+
self.temperature = temperature
126+
self.item_count = self.sampler_config['item_count']
127+
128+
super(InBatchSoftmaxLayer, self).__init__(**kwargs)
129+
130+
def build(self, input_shape):
131+
super(InBatchSoftmaxLayer, self).build(input_shape)
132+
133+
def call(self, inputs_with_item_idx, training=None, **kwargs):
134+
user_vec, item_vec, item_idx = inputs_with_item_idx
135+
if item_idx.dtype != tf.int64:
136+
item_idx = tf.cast(item_idx, tf.int64)
137+
user_vec /= self.temperature
138+
logits = tf.matmul(user_vec, item_vec, transpose_b=True)
139+
loss = inbatch_softmax_cross_entropy_with_logits(logits, self.item_count, item_idx)
140+
return tf.expand_dims(loss, axis=1)
141+
142+
def compute_output_shape(self, input_shape):
143+
return (None, 1)
144+
145+
def get_config(self, ):
146+
config = {'sampler_config': self.sampler_config, 'temperature': self.temperature}
147+
base_config = super(InBatchSoftmaxLayer, self).get_config()
148+
return dict(list(base_config.items()) + list(config.items()))
149+
150+
87151
class LabelAwareAttention(Layer):
88152
def __init__(self, k_max, pow_p=1, **kwargs):
89153
self.k_max = k_max
@@ -128,38 +192,6 @@ def get_config(self, ):
128192
return dict(list(base_config.items()) + list(config.items()))
129193

130194

131-
class Similarity(Layer):
132-
133-
def __init__(self, gamma=1, axis=-1, type='cos', **kwargs):
134-
self.gamma = gamma
135-
self.axis = axis
136-
self.type = type
137-
super(Similarity, self).__init__(**kwargs)
138-
139-
def build(self, input_shape):
140-
# Be sure to call this somewhere!
141-
super(Similarity, self).build(input_shape)
142-
143-
def call(self, inputs, **kwargs):
144-
query, candidate = inputs
145-
if self.type == "cos":
146-
query_norm = tf.norm(query, axis=self.axis)
147-
candidate_norm = tf.norm(candidate, axis=self.axis)
148-
cosine_score = reduce_sum(tf.multiply(query, candidate), -1)
149-
if self.type == "cos":
150-
cosine_score = div(cosine_score, query_norm * candidate_norm + 1e-8)
151-
cosine_score = tf.clip_by_value(cosine_score, -1, 1.0) * self.gamma
152-
return cosine_score
153-
154-
def compute_output_shape(self, input_shape):
155-
return (None, 1)
156-
157-
def get_config(self, ):
158-
config = {'gamma': self.gamma, 'axis': self.axis, 'type': self.type}
159-
base_config = super(Similarity, self).get_config()
160-
return dict(list(base_config.items()) + list(config.items()))
161-
162-
163195
class CapsuleLayer(Layer):
164196
def __init__(self, input_units, out_units, max_len, k_max, iteration_times=3,
165197
init_std=1.0, **kwargs):
@@ -245,6 +277,23 @@ def squash(inputs):
245277
return vec_squashed
246278

247279

280+
def inbatch_softmax_cross_entropy_with_logits(logits, item_count, item_idx):
281+
Q = tf.gather(tf.constant(item_count / np.sum(item_count), 'float32'),
282+
tf.squeeze(item_idx, axis=1))
283+
try:
284+
logQ = tf.reshape(tf.math.log(Q), (1, -1))
285+
logits -= logQ # subtract_log_q
286+
labels = tf.linalg.diag(tf.ones_like(logits[0]))
287+
except AttributeError:
288+
logQ = tf.reshape(tf.log(Q), (1, -1))
289+
logits -= logQ # subtract_log_q
290+
labels = tf.diag(tf.ones_like(logits[0]))
291+
292+
loss = tf.nn.softmax_cross_entropy_with_logits(
293+
labels=labels, logits=logits)
294+
return loss
295+
296+
248297
class EmbeddingIndex(Layer):
249298

250299
def __init__(self, index, **kwargs):

deepmatch/layers/interaction.py

+10-3
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,10 @@
1+
"""
2+
3+
Author:
4+
Weichen Shen,[email protected]
5+
6+
"""
7+
18
import tensorflow as tf
29
from deepctr.layers.normalization import LayerNormalization
310
from deepctr.layers.utils import softmax, reduce_mean
@@ -109,7 +116,7 @@ def call(self, inputs, mask=None, training=None, **kwargs):
109116
lower_tri = tf.ones([length, length])
110117
try:
111118
lower_tri = tf.contrib.linalg.LinearOperatorTriL(lower_tri).to_dense()
112-
except:
119+
except AttributeError:
113120
lower_tri = tf.linalg.LinearOperatorLowerTriangular(lower_tri).to_dense()
114121
masks = tf.tile(tf.expand_dims(lower_tri, 0), [tf.shape(align)[0], 1, 1])
115122
align = tf.where(tf.equal(masks, 0), paddings, align)
@@ -199,8 +206,8 @@ def build(self, input_shape):
199206
super(SelfAttention, self).build(input_shape)
200207

201208
def call(self, inputs, mask=None, **kwargs):
202-
input, key_masks = inputs
203-
querys, keys, values = input, input, input
209+
_input, key_masks = inputs
210+
querys, keys, values = _input, _input, _input
204211
align = self.attention([querys, keys])
205212
output = self.softmax_weight_sum([align, values, key_masks])
206213
if self.use_layer_norm:

deepmatch/layers/sequence.py

+7
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,10 @@
1+
"""
2+
3+
Author:
4+
Weichen Shen,[email protected]
5+
6+
"""
7+
18
import tensorflow as tf
29
from tensorflow.python.keras.layers import Layer
310

0 commit comments

Comments
 (0)