Skip to content

Commit d97e9cd

Browse files
author
Weichen Shen
authored
Update feature config generation method & Fix bugs
- Adopted a new feature config generation method - Simplified input mechanism for multi-valued features. - Fix bug: in some cases the order of model input placeholder and actual model input list does not match. - Fix bug: Divide by zero overflow in `SequencePoolingLayer` when sequence length is 0.
1 parent 18cf3c0 commit d97e9cd

32 files changed

+408
-346
lines changed

LICENSE

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
The MIT License (MIT)
22

3-
Copyright (c) 2018 Weichen Shen
3+
Copyright (c) 2018-2019 Weichen Shen
44

55
Permission is hereby granted, free of charge, to any person obtaining a copy
66
of this software and associated documentation files (the "Software"), to deal

deepctr/__init__.py

+2-2
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,6 @@
22
from .import layers
33
from .import sequence
44
from . import models
5-
from .utils import check_version
6-
__version__ = '0.2.2'
5+
from .utils import check_version, SingleFeat, VarLenFeat
6+
__version__ = '0.2.3'
77
check_version(__version__)

deepctr/input_embedding.py

+78-44
Original file line numberDiff line numberDiff line change
@@ -1,75 +1,85 @@
1+
from collections import OrderedDict
12
from itertools import chain
23

3-
from tensorflow.python.keras import Input
44
from tensorflow.python.keras.initializers import RandomNormal
5-
from tensorflow.python.keras.layers import Embedding, Dense, Reshape, Concatenate
5+
from tensorflow.python.keras.layers import Embedding, Dense, Reshape, Concatenate, Input, add
66
from tensorflow.python.keras.regularizers import l2
77
from .sequence import SequencePoolingLayer
8-
from .utils import get_linear_logit
98

109

1110
def create_input_dict(feature_dim_dict, prefix=''):
12-
sparse_input = {feat: Input(shape=(1,), name=prefix+'sparse_' + str(i) + '-' + feat) for i, feat in
13-
enumerate(feature_dim_dict["sparse"])}
14-
dense_input = {feat: Input(shape=(1,), name=prefix+'dense_' + str(i) + '-' + feat) for i, feat in
15-
enumerate(feature_dim_dict["dense"])}
11+
sparse_input = OrderedDict()
12+
for i, feat in enumerate(feature_dim_dict["sparse"]):
13+
sparse_input[feat.name] = Input(
14+
shape=(1,), name=prefix+'sparse_' + str(i) + '-' + feat.name)
15+
16+
dense_input = OrderedDict()
17+
18+
for i, feat in enumerate(feature_dim_dict["dense"]):
19+
dense_input[feat] = Input(
20+
shape=(1,), name=prefix+'dense_' + str(i) + '-' + feat.name)
21+
1622
return sparse_input, dense_input
1723

1824

19-
def create_sequence_input_dict(feature_dim_dict):
25+
def create_sequence_input_dict(feature_dim_dict, mask_zero=True):
2026

2127
sequence_dim_dict = feature_dim_dict.get('sequence', [])
2228
sequence_input_dict = {feat.name: Input(shape=(feat.maxlen,), name='seq_' + str(
2329
i) + '-' + feat.name) for i, feat in enumerate(sequence_dim_dict)}
2430
sequence_pooling_dict = {feat.name: feat.combiner
2531
for i, feat in enumerate(sequence_dim_dict)}
26-
sequence_len_dict = {feat.name: Input(shape=(
27-
1,), name='seq_length'+str(i)+'-'+feat.name) for i, feat in enumerate(sequence_dim_dict)}
28-
sequence_max_len_dict = {feat.name: feat.maxlen
29-
for i, feat in enumerate(sequence_dim_dict)}
32+
if mask_zero:
33+
sequence_len_dict, sequence_max_len_dict = None, None
34+
else:
35+
sequence_len_dict = {feat.name: Input(shape=(
36+
1,), name='seq_length'+str(i)+'-'+feat.name) for i, feat in enumerate(sequence_dim_dict)}
37+
sequence_max_len_dict = {feat.name: feat.maxlen
38+
for i, feat in enumerate(sequence_dim_dict)}
3039
return sequence_input_dict, sequence_pooling_dict, sequence_len_dict, sequence_max_len_dict
3140

3241

33-
def create_embedding_dict(feature_dim_dict, embedding_size, init_std, seed, l2_reg, prefix='sparse'):
42+
def create_embedding_dict(feature_dim_dict, embedding_size, init_std, seed, l2_reg, prefix='sparse', seq_mask_zero=True):
3443
if embedding_size == 'auto':
3544

36-
sparse_embedding = {feat: Embedding(feature_dim_dict["sparse"][feat], 6 * int(pow(feature_dim_dict["sparse"][feat], 0.25)),
37-
embeddings_initializer=RandomNormal(
45+
sparse_embedding = {feat.name: Embedding(feat.dimension, 6 * int(pow(feat.dimension, 0.25)),
46+
embeddings_initializer=RandomNormal(
3847
mean=0.0, stddev=init_std, seed=seed),
3948
embeddings_regularizer=l2(l2_reg),
40-
name=prefix+'_emb_' + str(i) + '-' + feat) for i, feat in
49+
name=prefix+'_emb_' + str(i) + '-' + feat.name) for i, feat in
4150
enumerate(feature_dim_dict["sparse"])}
4251
else:
4352

44-
sparse_embedding = {feat: Embedding(feature_dim_dict["sparse"][feat], embedding_size,
45-
embeddings_initializer=RandomNormal(
46-
mean=0.0, stddev=init_std, seed=seed),
47-
embeddings_regularizer=l2(l2_reg),
48-
name=prefix+'_emb_' + str(i) + '-' + feat) for i, feat in
49-
enumerate(feature_dim_dict["sparse"])}
53+
sparse_embedding = {feat.name: Embedding(feat.dimension, embedding_size,
54+
embeddings_initializer=RandomNormal(
55+
mean=0.0, stddev=init_std, seed=seed),
56+
embeddings_regularizer=l2(
57+
l2_reg),
58+
name=prefix+'_emb_' + str(i) + '-' + feat.name) for i, feat in
59+
enumerate(feature_dim_dict["sparse"])}
5060

5161
if 'sequence' in feature_dim_dict:
5262
count = len(sparse_embedding)
5363
sequence_dim_list = feature_dim_dict['sequence']
5464
for feat in sequence_dim_list:
55-
if feat.name not in sparse_embedding:
56-
if embedding_size == "auto":
57-
sparse_embedding[feat.name] = Embedding(feat.dimension, 6 * int(pow(feat.dimension, 0.25)),
58-
embeddings_initializer=RandomNormal(
59-
mean=0.0, stddev=init_std, seed=seed),
60-
embeddings_regularizer=l2(
61-
l2_reg),
62-
name=prefix + '_emb_' + str(count) + '-' + feat.name)
63-
64-
else:
65-
sparse_embedding[feat.name] = Embedding(feat.dimension, embedding_size,
66-
embeddings_initializer=RandomNormal(
67-
mean=0.0, stddev=init_std, seed=seed),
68-
embeddings_regularizer=l2(
69-
l2_reg),
70-
name=prefix+'_emb_' + str(count) + '-' + feat.name)
71-
72-
count += 1
65+
# if feat.name not in sparse_embedding:
66+
if embedding_size == "auto":
67+
sparse_embedding[feat.name] = Embedding(feat.dimension, 6 * int(pow(feat.dimension, 0.25)),
68+
embeddings_initializer=RandomNormal(
69+
mean=0.0, stddev=init_std, seed=seed),
70+
embeddings_regularizer=l2(
71+
l2_reg),
72+
name=prefix + '_emb_' + str(count) + '-' + feat.name, mask_zero=seq_mask_zero)
73+
74+
else:
75+
sparse_embedding[feat.name] = Embedding(feat.dimension, embedding_size,
76+
embeddings_initializer=RandomNormal(
77+
mean=0.0, stddev=init_std, seed=seed),
78+
embeddings_regularizer=l2(
79+
l2_reg),
80+
name=prefix+'_emb_' + str(count) + '-' + feat.name, mask_zero=seq_mask_zero)
81+
82+
count += 1
7383

7484
return sparse_embedding
7585

@@ -109,7 +119,6 @@ def merge_sequence_input(embedding_dict, embed_list, sequence_input_dict, sequen
109119

110120

111121
def get_embedding_vec_list(embedding_dict, input_dict):
112-
113122
return [embedding_dict[feat](v)
114123
for feat, v in input_dict.items()]
115124

@@ -121,12 +130,15 @@ def get_varlen_embedding_vec_dict(embedding_dict, input_dict):
121130

122131

123132
def get_pooling_vec_list(sequence_embed_dict, sequence_len_dict, sequence_max_len_dict, sequence_pooling_dict):
124-
return [SequencePoolingLayer(sequence_max_len_dict[feat], sequence_pooling_dict[feat])(
125-
[v, sequence_len_dict[feat]]) for feat, v in sequence_embed_dict.items()]
133+
if sequence_max_len_dict is None or sequence_len_dict is None:
134+
return [SequencePoolingLayer(-1, sequence_pooling_dict[feat])(v) for feat, v in sequence_embed_dict.items()]
135+
else:
136+
return [SequencePoolingLayer(sequence_max_len_dict[feat], sequence_pooling_dict[feat])(
137+
[v, sequence_len_dict[feat]]) for feat, v in sequence_embed_dict.items()]
126138

127139

128140
def get_inputs_list(inputs):
129-
return list(chain(*list(map(lambda x: x.values(), inputs))))
141+
return list(chain(*list(map(lambda x: x.values(), filter(lambda x: x is not None, inputs)))))
130142

131143

132144
def get_inputs_embedding(feature_dim_dict, embedding_size, l2_reg_embedding, l2_reg_linear, init_std, seed, include_linear=True):
@@ -162,3 +174,25 @@ def get_inputs_embedding(feature_dim_dict, embedding_size, l2_reg_embedding, l2_
162174
inputs_list = get_inputs_list(
163175
[sparse_input_dict, dense_input_dict, sequence_input_dict, sequence_input_len_dict])
164176
return deep_emb_list, linear_logit, inputs_list
177+
178+
179+
def get_linear_logit(linear_term, dense_input_, l2_reg):
180+
if len(linear_term) > 1:
181+
linear_term = add(linear_term)
182+
elif len(linear_term) == 1:
183+
linear_term = linear_term[0]
184+
else:
185+
linear_term = None
186+
187+
dense_input = list(dense_input_.values())
188+
if len(dense_input) > 0:
189+
dense_input__ = dense_input[0] if len(
190+
dense_input) == 1 else Concatenate()(dense_input)
191+
linear_dense_logit = Dense(
192+
1, activation=None, use_bias=False, kernel_regularizer=l2(l2_reg))(dense_input__)
193+
if linear_term is not None:
194+
linear_term = add([linear_dense_logit, linear_term])
195+
else:
196+
linear_term = linear_dense_logit
197+
198+
return linear_term

deepctr/layers.py

+28-20
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import itertools
22
from tensorflow.python.keras.layers import Layer, BatchNormalization
33
from tensorflow.python.keras.regularizers import l2
4-
from tensorflow.python.keras.initializers import Zeros, glorot_normal, glorot_uniform
4+
from tensorflow.python.keras.initializers import Zeros, Ones, glorot_normal, glorot_uniform
55
from tensorflow.python.keras import backend as K
66
import tensorflow as tf
77
from .activations import activation_fun
@@ -355,10 +355,9 @@ def call(self, inputs, **kwargs):
355355
x_0 = tf.expand_dims(inputs, axis=2)
356356
x_l = x_0
357357
for i in range(self.layer_num):
358-
xl_w = tf.tensordot(tf.transpose(
359-
x_l, [0, 2, 1]), self.kernels[i], axes=(-1, 0))
358+
xl_w = tf.tensordot(x_l, self.kernels[i], axes=(1, 0))
360359
dot_ = tf.matmul(x_0, xl_w)
361-
x_l = dot_ + x_l + self.bias[i]
360+
x_l = dot_ + self.bias[i] + x_l
362361
x_l = tf.squeeze(x_l, axis=2)
363362
return x_l
364363

@@ -504,7 +503,6 @@ def get_config(self,):
504503
return dict(list(base_config.items()) + list(config.items()))
505504

506505

507-
508506
class InteractingLayer(Layer):
509507
"""A Layer used in AutoInt that model the correlations between different feature fields by multi-head self-attention mechanism.
510508
@@ -524,6 +522,7 @@ class InteractingLayer(Layer):
524522
References
525523
- [Song W, Shi C, Xiao Z, et al. AutoInt: Automatic Feature Interaction Learning via Self-Attentive Neural Networks[J]. arXiv preprint arXiv:1810.11921, 2018.](https://arxiv.org/abs/1810.11921)
526524
"""
525+
527526
def __init__(self, att_embedding_size=8, head_num=2, use_res=True, seed=1024, **kwargs):
528527
if head_num <= 0:
529528
raise ValueError('head_num must be a int > 0')
@@ -535,7 +534,8 @@ def __init__(self, att_embedding_size=8, head_num=2, use_res=True, seed=1024, **
535534

536535
def build(self, input_shape):
537536
if len(input_shape) != 3:
538-
raise ValueError("Unexpected inputs dimensions %d, expect to be 3 dimensions" % (len(input_shape)))
537+
raise ValueError(
538+
"Unexpected inputs dimensions %d, expect to be 3 dimensions" % (len(input_shape)))
539539
embedding_size = input_shape[-1].value
540540
self.W_Query = self.add_weight(name='query', shape=[embedding_size, self.att_embedding_size * self.head_num], dtype=tf.float32,
541541
initializer=tf.keras.initializers.glorot_uniform(seed=self.seed))
@@ -547,26 +547,32 @@ def build(self, input_shape):
547547
self.W_Res = self.add_weight(name='res', shape=[embedding_size, self.att_embedding_size * self.head_num], dtype=tf.float32,
548548
initializer=tf.keras.initializers.glorot_uniform(seed=self.seed))
549549

550-
super(InteractingLayer, self).build(input_shape) # Be sure to call this somewhere!
550+
# Be sure to call this somewhere!
551+
super(InteractingLayer, self).build(input_shape)
551552

552553
def call(self, inputs, **kwargs):
553554
if K.ndim(inputs) != 3:
554-
raise ValueError("Unexpected inputs dimensions %d, expect to be 3 dimensions" % (K.ndim(inputs)))
555+
raise ValueError(
556+
"Unexpected inputs dimensions %d, expect to be 3 dimensions" % (K.ndim(inputs)))
555557

556-
querys = tf.tensordot(inputs, self.W_Query, axes=(-1, 0)) # None F D*head_num
558+
querys = tf.tensordot(inputs, self.W_Query,
559+
axes=(-1, 0)) # None F D*head_num
557560
keys = tf.tensordot(inputs, self.W_key, axes=(-1, 0))
558561
values = tf.tensordot(inputs, self.W_Value, axes=(-1, 0))
559562

560-
querys = tf.stack(tf.split(querys, self.head_num, axis=2)) # head_num None F D
563+
# head_num None F D
564+
querys = tf.stack(tf.split(querys, self.head_num, axis=2))
561565
keys = tf.stack(tf.split(keys, self.head_num, axis=2))
562566
values = tf.stack(tf.split(values, self.head_num, axis=2))
563567

564-
inner_product = tf.matmul(querys, keys, transpose_b=True) # head_num None F F
568+
inner_product = tf.matmul(
569+
querys, keys, transpose_b=True) # head_num None F F
565570
self.normalized_att_scores = tf.nn.softmax(inner_product)
566571

567-
result = tf.matmul(self.normalized_att_scores, values)#head_num None F D
572+
result = tf.matmul(self.normalized_att_scores,
573+
values) # head_num None F D
568574
result = tf.concat(tf.split(result, self.head_num, ), axis=-1)
569-
result = tf.squeeze(result, axis=0)#None F D*head_num
575+
result = tf.squeeze(result, axis=0) # None F D*head_num
570576

571577
if self.use_res:
572578
result += tf.tensordot(inputs, self.W_Res, axes=(-1, 0))
@@ -648,7 +654,7 @@ def build(self, input_shape):
648654
super(LocalActivationUnit, self).build(
649655
input_shape) # Be sure to call this somewhere!
650656

651-
def call(self, inputs, **kwargs):
657+
def call(self, inputs, training=None, **kwargs):
652658

653659
query, keys = inputs
654660

@@ -657,7 +663,7 @@ def call(self, inputs, **kwargs):
657663

658664
att_input = tf.concat(
659665
[queries, keys, queries - keys, queries * keys], axis=-1)
660-
att_input = tf.layers.batch_normalization(att_input)
666+
661667
att_out = MLP(self.hidden_size, self.activation, self.l2_reg,
662668
self.keep_prob, self.use_bn, seed=self.seed)(att_input)
663669
attention_score = tf.nn.bias_add(tf.tensordot(
@@ -724,7 +730,8 @@ def build(self, input_shape):
724730

725731
super(MLP, self).build(input_shape) # Be sure to call this somewhere!
726732

727-
def call(self, inputs, **kwargs):
733+
def call(self, inputs, training=None, **kwargs):
734+
728735
deep_input = inputs
729736

730737
for i in range(len(self.hidden_size)):
@@ -734,9 +741,10 @@ def call(self, inputs, **kwargs):
734741
# kernel_initializer=glorot_normal(seed=self.seed), \
735742
# kernel_regularizer=l2(self.l2_reg))(deep_input)
736743
if self.use_bn:
737-
fc = BatchNormalization()(fc)
744+
fc = tf.keras.layers.BatchNormalization()(fc)
738745
fc = activation_fun(self.activation, fc)
739-
fc = tf.nn.dropout(fc, self.keep_prob)
746+
#fc = tf.nn.dropout(fc, self.keep_prob)
747+
fc = tf.keras.layers.Dropout(1 - self.keep_prob)(fc,)
740748
deep_input = fc
741749

742750
return deep_input
@@ -901,7 +909,7 @@ class PredictionLayer(Layer):
901909
Arguments
902910
- **activation**: Activation function to use.
903911
904-
- **use_bias**: bool.Whther add bias term.
912+
- **use_bias**: bool.Whether add bias term or not.
905913
"""
906914

907915
def __init__(self, activation='sigmoid', use_bias=True, **kwargs):
@@ -933,4 +941,4 @@ def compute_output_shape(self, input_shape):
933941
def get_config(self,):
934942
config = {'activation': self.activation, 'use_bias': self.use_bias}
935943
base_config = super(PredictionLayer, self).get_config()
936-
return dict(list(base_config.items()) + list(config.items()))
944+
return dict(list(base_config.items()) + list(config.items()))

deepctr/models/afm.py

+3-12
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@
1212
import tensorflow as tf
1313
from ..input_embedding import get_inputs_embedding
1414
from ..layers import PredictionLayer, AFMLayer, FM
15-
from ..utils import concat_fun
15+
from ..utils import concat_fun, check_feature_config_dict
1616

1717

1818
def AFM(feature_dim_dict, embedding_size=8, use_attention=True, attention_factor=8,
@@ -34,21 +34,12 @@ def AFM(feature_dim_dict, embedding_size=8, use_attention=True, attention_factor
3434
:return: A Keras model instance.
3535
"""
3636

37-
if not isinstance(feature_dim_dict,
38-
dict) or "sparse" not in feature_dim_dict or "dense" not in feature_dim_dict:
39-
raise ValueError(
40-
"feature_dim_dict must be a dict like {'sparse':{'field_1':4,'field_2':3,'field_3':2},'dense':['field_4','field_5']}")
41-
if not isinstance(feature_dim_dict["sparse"], dict):
42-
raise ValueError("feature_dim_dict['sparse'] must be a dict,cur is", type(
43-
feature_dim_dict['sparse']))
44-
if not isinstance(feature_dim_dict["dense"], list):
45-
raise ValueError("feature_dim_dict['dense'] must be a list,cur is", type(
46-
feature_dim_dict['dense']))
37+
check_feature_config_dict(feature_dim_dict)
4738

4839
deep_emb_list, linear_logit, inputs_list = get_inputs_embedding(
4940
feature_dim_dict, embedding_size, l2_reg_embedding, l2_reg_linear, init_std, seed)
5041

51-
fm_input = concat_fun(deep_emb_list,axis=1)
42+
fm_input = concat_fun(deep_emb_list, axis=1)
5243
if use_attention:
5344
fm_logit = AFMLayer(attention_factor, l2_reg_att,
5445
keep_prob, seed)(deep_emb_list)

deepctr/models/autoint.py

+2-4
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@
1212
import tensorflow as tf
1313
from ..input_embedding import get_inputs_embedding
1414
from ..layers import PredictionLayer, MLP, InteractingLayer
15-
from ..utils import concat_fun
15+
from ..utils import concat_fun, check_feature_config_dict
1616

1717

1818
def AutoInt(feature_dim_dict, embedding_size=8, att_layer_num=3, att_embedding_size=8, att_head_num=2, att_res=True, hidden_size=(256, 256), activation='relu',
@@ -40,9 +40,7 @@ def AutoInt(feature_dim_dict, embedding_size=8, att_layer_num=3, att_embedding_s
4040

4141
if len(hidden_size) <= 0 and att_layer_num <= 0:
4242
raise ValueError("Either hidden_layer or att_layer_num must > 0")
43-
if not isinstance(feature_dim_dict, dict) or "sparse" not in feature_dim_dict or "dense" not in feature_dim_dict:
44-
raise ValueError(
45-
"feature_dim must be a dict like {'sparse':{'field_1':4,'field_2':3,'field_3':2},'dense':['field_5',]}")
43+
check_feature_config_dict(feature_dim_dict)
4644

4745
deep_emb_list, _, inputs_list = get_inputs_embedding(
4846
feature_dim_dict, embedding_size, l2_reg_embedding, 0, init_std, seed, False)

0 commit comments

Comments
 (0)