Skip to content

Commit a07132a

Browse files
Fwfm (#237)
* README updated * FwFM interaction layer added * FwFM with deep support added * tests for FwFM added * example to run fwfm
1 parent 9e6be3f commit a07132a

File tree

8 files changed

+251
-9
lines changed

8 files changed

+251
-9
lines changed

README.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,7 @@ Let's [**Get Started!**](https://deepctr-doc.readthedocs.io/en/latest/Quick-Star
4040
| AutoInt | [arxiv 2018][AutoInt: Automatic Feature Interaction Learning via Self-Attentive Neural Networks](https://arxiv.org/abs/1810.11921) |
4141
| Deep Interest Network | [KDD 2018][Deep Interest Network for Click-Through Rate Prediction](https://arxiv.org/pdf/1706.06978.pdf) |
4242
| Deep Interest Evolution Network | [AAAI 2019][Deep Interest Evolution Network for Click-Through Rate Prediction](https://arxiv.org/pdf/1809.03672.pdf) |
43+
| FwFM | [WWW 2018][Field-weighted Factorization Machines for Click-Through Rate Prediction in Display Advertising](https://arxiv.org/pdf/1806.03514.pdf) |
4344
| ONN | [arxiv 2019][Operation-aware Neural Networks for User Response Prediction](https://arxiv.org/pdf/1904.12579.pdf) |
4445
| FGCNN | [WWW 2019][Feature Generation by Convolutional Neural Network for Click-Through Rate Prediction ](https://arxiv.org/pdf/1904.04447) |
4546
| Deep Session Interest Network | [IJCAI 2019][Deep Session Interest Network for Click-Through Rate Prediction ](https://arxiv.org/abs/1905.06482) |

deepctr/layers/__init__.py

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55
from .interaction import (CIN, FM, AFMLayer, BiInteractionPooling, CrossNet,
66
InnerProductLayer, InteractingLayer,
77
OutterProductLayer, FGCNNLayer,SENETLayer,BilinearInteraction,
8-
FieldWiseBiInteraction)
8+
FieldWiseBiInteraction, FwFM)
99
from .normalization import LayerNormalization
1010
from .sequence import (AttentionSequencePoolingLayer, BiasEncoding, BiLSTM,
1111
KMaxPooling, SequencePoolingLayer,WeightedSequenceLayer,
@@ -41,5 +41,6 @@
4141
'BilinearInteraction':BilinearInteraction,
4242
'WeightedSequenceLayer':WeightedSequenceLayer,
4343
'Add':Add,
44-
'FieldWiseBiInteraction':FieldWiseBiInteraction
44+
'FieldWiseBiInteraction':FieldWiseBiInteraction,
45+
'FwFM': FwFM
4546
}

deepctr/layers/interaction.py

Lines changed: 84 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,9 @@
11
# -*- coding:utf-8 -*-
22
"""
33
4-
Author:
5-
Weichen Shen,[email protected]
4+
Authors:
5+
Weichen Shen,[email protected],
6+
Harshit Pande
67
78
"""
89

@@ -11,9 +12,10 @@
1112
import tensorflow as tf
1213
from tensorflow.python.keras import backend as K
1314
from tensorflow.python.keras.initializers import (Zeros, glorot_normal,
14-
glorot_uniform)
15+
glorot_uniform, TruncatedNormal)
1516
from tensorflow.python.keras.layers import Layer
1617
from tensorflow.python.keras.regularizers import l2
18+
from tensorflow.python.keras.backend import batch_dot
1719
from tensorflow.python.layers import utils
1820

1921
from .activation import activation_layer
@@ -1052,7 +1054,7 @@ class FieldWiseBiInteraction(Layer):
10521054
10531055
Output shape
10541056
- 2D tensor with shape: ``(batch_size,embedding_size)``.
1055-
1057+
10561058
Arguments
10571059
- **use_bias** : Boolean, if use bias.
10581060
- **seed** : A Python integer to use as random seed.
@@ -1062,7 +1064,7 @@ class FieldWiseBiInteraction(Layer):
10621064
10631065
"""
10641066

1065-
def __init__(self,use_bias=True, seed=1024, **kwargs):
1067+
def __init__(self, use_bias=True, seed=1024, **kwargs):
10661068
self.use_bias = use_bias
10671069
self.seed = seed
10681070

@@ -1167,3 +1169,80 @@ def get_config(self, ):
11671169
config = {'use_bias': self.use_bias, 'seed': self.seed}
11681170
base_config = super(FieldWiseBiInteraction, self).get_config()
11691171
return dict(list(base_config.items()) + list(config.items()))
1172+
1173+
1174+
class FwFM(Layer):
1175+
"""Field-weighted Factorization Machines
1176+
1177+
Input shape
1178+
- 3D tensor with shape: ``(batch_size,field_size,embedding_size)``.
1179+
1180+
Output shape
1181+
- 2D tensor with shape: ``(batch_size, 1)``.
1182+
1183+
Arguments
1184+
- **num_fields** : integer for number of fields
1185+
- **regularizer** : L2 regularizer weight for the field strength parameters of FwFM
1186+
1187+
References
1188+
- [Field-weighted Factorization Machines for Click-Through Rate Prediction in Display Advertising]
1189+
https://arxiv.org/pdf/1806.03514.pdf
1190+
"""
1191+
1192+
def __init__(self, num_fields=4, regularizer=0.000001, **kwargs):
1193+
self.num_fields = num_fields
1194+
self.regularizer = regularizer
1195+
super(FwFM, self).__init__(**kwargs)
1196+
1197+
def build(self, input_shape):
1198+
if len(input_shape) != 3:
1199+
raise ValueError("Unexpected inputs dimensions % d,\
1200+
expect to be 3 dimensions" % (len(input_shape)))
1201+
1202+
if input_shape[1] != self.num_fields:
1203+
raise ValueError("Mismatch in number of fields {} and \
1204+
concatenated embeddings dims {}".format(self.num_fields, input_shape[1]))
1205+
1206+
self.field_strengths = self.add_weight(name='field_pair_strengths',
1207+
shape=(self.num_fields, self.num_fields),
1208+
initializer=TruncatedNormal(),
1209+
regularizer=l2(self.regularizer),
1210+
trainable=True)
1211+
1212+
super(FwFM, self).build(input_shape) # Be sure to call this somewhere!
1213+
1214+
def call(self, inputs, **kwargs):
1215+
if K.ndim(inputs) != 3:
1216+
raise ValueError(
1217+
"Unexpected inputs dimensions %d, expect to be 3 dimensions"
1218+
% (K.ndim(inputs)))
1219+
1220+
if inputs.shape[1] != self.num_fields:
1221+
raise ValueError("Mismatch in number of fields {} and \
1222+
concatenated embeddings dims {}".format(self.num_fields, inputs.shape[1]))
1223+
1224+
pairwise_inner_prods = []
1225+
for fi, fj in itertools.combinations(range(self.num_fields), 2):
1226+
# get field strength for pair fi and fj
1227+
r_ij = self.field_strengths[fi, fj]
1228+
1229+
# get embeddings for the features of both the fields
1230+
feat_embed_i = tf.squeeze(inputs[0:, fi:fi + 1, 0:], axis=1)
1231+
feat_embed_j = tf.squeeze(inputs[0:, fj:fj + 1, 0:], axis=1)
1232+
1233+
f = tf.scalar_mul(r_ij, batch_dot(feat_embed_i, feat_embed_j, axes=1))
1234+
pairwise_inner_prods.append(f)
1235+
1236+
sum_ = tf.add_n(pairwise_inner_prods)
1237+
return sum_
1238+
1239+
def compute_output_shape(self, input_shape):
1240+
return (None, 1)
1241+
1242+
def get_config(self):
1243+
config = super(FwFM, self).get_config().copy()
1244+
config.update({
1245+
'num_fields': self.num_fields,
1246+
'regularizer': self.regularizer
1247+
})
1248+
return config

deepctr/models/__init__.py

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717
from .dsin import DSIN
1818
from .fibinet import FiBiNET
1919
from .flen import FLEN
20+
from .deepfwfm import DeepFwFM
2021

21-
__all__ = ["AFM", "CCPM","DCN", "MLR", "DeepFM",
22-
"MLR", "NFM", "DIN", "DIEN", "FNN", "PNN", "WDL", "xDeepFM", "AutoInt", "ONN", "FGCNN", "DSIN", "FiBiNET", 'FLEN']
22+
__all__ = ["AFM", "CCPM","DCN", "MLR", "DeepFM", "MLR", "NFM", "DIN", "DIEN", "FNN", "PNN",
23+
"WDL", "xDeepFM", "AutoInt", "ONN", "FGCNN", "DSIN", "FiBiNET", 'FLEN', "DeepFwFM"]

deepctr/models/deepfwfm.py

Lines changed: 73 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,73 @@
1+
# -*- coding:utf-8 -*-
2+
"""
3+
Author:
4+
Harshit Pande
5+
6+
Reference:
7+
[1] Field-weighted Factorization Machines for Click-Through Rate Prediction in Display Advertising
8+
(https://arxiv.org/pdf/1806.03514.pdf)
9+
10+
"""
11+
12+
from itertools import chain
13+
import tensorflow as tf
14+
15+
from ..inputs import input_from_feature_columns, get_linear_logit, build_input_features, combined_dnn_input, DEFAULT_GROUP_NAME
16+
from ..layers.core import PredictionLayer, DNN
17+
from ..layers.interaction import FwFM
18+
from ..layers.utils import concat_func, add_func
19+
20+
21+
def DeepFwFM(linear_feature_columns, dnn_feature_columns, fm_group=[DEFAULT_GROUP_NAME], dnn_hidden_units=(128, 128),
22+
l2_reg_linear=0.00001, l2_reg_embedding=0.00001, l2_reg_field_strength=0.00001, l2_reg_dnn=0,
23+
init_std=0.0001, seed=1024, dnn_dropout=0, dnn_activation='relu', dnn_use_bn=False, task='binary'):
24+
"""Instantiates the DeepFwFM Network architecture.
25+
26+
:param linear_feature_columns: An iterable containing all the features used by linear part of the model.
27+
:param dnn_feature_columns: An iterable containing all the features used by deep part of the model.
28+
:param fm_group: list, group_name of features that will be used to do feature interactions.
29+
:param dnn_hidden_units: list,list of positive integer or empty list if do not want DNN, the layer number and units
30+
in each layer of DNN
31+
:param l2_reg_linear: float. L2 regularizer strength applied to linear part
32+
:param l2_reg_field_strength: float. L2 regularizer strength applied to the field pair strength parameters
33+
:param l2_reg_embedding: float. L2 regularizer strength applied to embedding vector
34+
:param l2_reg_dnn: float. L2 regularizer strength applied to DNN
35+
:param init_std: float,to use as the initialize std of embedding vector
36+
:param seed: integer ,to use as random seed.
37+
:param dnn_dropout: float in [0,1), the probability we will drop out a given DNN coordinate.
38+
:param dnn_activation: Activation function to use in DNN
39+
:param dnn_use_bn: bool. Whether use BatchNormalization before activation or not in DNN
40+
:param task: str, ``"binary"`` for binary logloss or ``"regression"`` for regression loss
41+
:return: A Keras model instance.
42+
"""
43+
44+
features = build_input_features(linear_feature_columns + dnn_feature_columns)
45+
46+
inputs_list = list(features.values())
47+
48+
group_embedding_dict, dense_value_list = input_from_feature_columns(features, dnn_feature_columns,
49+
l2_reg_embedding, init_std, seed,
50+
support_group=True)
51+
52+
linear_logit = get_linear_logit(features, linear_feature_columns, init_std=init_std, seed=seed, prefix='linear',
53+
l2_reg=l2_reg_linear)
54+
55+
fwfm_logit = add_func([FwFM(num_fields=len(v), regularizer=l2_reg_field_strength)
56+
(concat_func(v, axis=1)) for k, v in group_embedding_dict.items() if k in fm_group])
57+
58+
final_logit_components = [linear_logit, fwfm_logit]
59+
60+
if dnn_hidden_units:
61+
dnn_input = combined_dnn_input(list(chain.from_iterable(
62+
group_embedding_dict.values())), dense_value_list)
63+
dnn_output = DNN(dnn_hidden_units, dnn_activation, l2_reg_dnn, dnn_dropout,
64+
dnn_use_bn, seed)(dnn_input)
65+
dnn_logit = tf.keras.layers.Dense(
66+
1, use_bias=False, activation=None)(dnn_output)
67+
final_logit_components.append(dnn_logit)
68+
69+
final_logit = add_func(final_logit_components)
70+
71+
output = PredictionLayer(task)(final_logit)
72+
model = tf.keras.models.Model(inputs=inputs_list, outputs=output)
73+
return model

examples/run_fwfm.py

Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
import pandas as pd
2+
from sklearn.metrics import log_loss, roc_auc_score
3+
from sklearn.model_selection import train_test_split
4+
from sklearn.preprocessing import LabelEncoder, MinMaxScaler
5+
6+
from deepctr.models import DeepFwFM
7+
from deepctr.inputs import SparseFeat, DenseFeat, get_feature_names
8+
9+
if __name__ == "__main__":
10+
data = pd.read_csv('./criteo_sample.txt')
11+
12+
sparse_features = ['C' + str(i) for i in range(1, 27)]
13+
dense_features = ['I' + str(i) for i in range(1, 14)]
14+
15+
data[sparse_features] = data[sparse_features].fillna('-1', )
16+
data[dense_features] = data[dense_features].fillna(0, )
17+
target = ['label']
18+
19+
# 1.Label Encoding for sparse features,and do simple Transformation for dense features
20+
for feat in sparse_features:
21+
lbe = LabelEncoder()
22+
data[feat] = lbe.fit_transform(data[feat])
23+
mms = MinMaxScaler(feature_range=(0, 1))
24+
data[dense_features] = mms.fit_transform(data[dense_features])
25+
26+
# 2.count #unique features for each sparse field,and record dense feature field name
27+
28+
fixlen_feature_columns = [SparseFeat(feat, vocabulary_size=data[feat].nunique(),embedding_dim=4)
29+
for i,feat in enumerate(sparse_features)] + [DenseFeat(feat, 1,)
30+
for feat in dense_features]
31+
32+
dnn_feature_columns = fixlen_feature_columns
33+
linear_feature_columns = fixlen_feature_columns
34+
35+
feature_names = get_feature_names(linear_feature_columns + dnn_feature_columns)
36+
37+
# 3.generate input data for model
38+
39+
train, test = train_test_split(data, test_size=0.2)
40+
train_model_input = {name:train[name] for name in feature_names}
41+
test_model_input = {name:test[name] for name in feature_names}
42+
43+
# 4.Define Model,train,predict and evaluate
44+
model = DeepFwFM(linear_feature_columns, dnn_feature_columns, dnn_hidden_units=(100,100), task='binary')
45+
model.compile("adam", "binary_crossentropy",
46+
metrics=['binary_crossentropy'], )
47+
48+
history = model.fit(train_model_input, train[target].values,
49+
batch_size=256, epochs=10, verbose=2, validation_split=0.2, )
50+
pred_ans = model.predict(test_model_input, batch_size=256)
51+
print("test LogLoss", round(log_loss(test[target].values, pred_ans), 4))
52+
print("test AUC", round(roc_auc_score(test[target].values, pred_ans), 4))

tests/layers/interaction_test.py

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,12 @@
22

33
try:
44
from tensorflow.python.keras.utils import CustomObjectScope
5+
from tensorflow.python.keras.regularizers import l2
6+
from tensorflow.python.keras.initializers import TruncatedNormal
57
except:
68
from tensorflow.keras.utils import CustomObjectScope
9+
from tensorflow.keras.regularizers import l2
10+
from tensorflow.keras.initializers import TruncatedNormal
711
from deepctr import layers
812

913
from tests.utils import layer_test
@@ -13,6 +17,14 @@
1317
EMBEDDING_SIZE = 3
1418
SEQ_LENGTH = 10
1519

20+
@pytest.mark.parametrize(
21+
'reg_strength',
22+
[0.000001]
23+
)
24+
def test_FwFM(reg_strength):
25+
with CustomObjectScope({'FwFM': layers.FwFM}):
26+
layer_test(layers.FwFM, kwargs={'num_fields': FIELD_SIZE, 'regularizer': reg_strength},
27+
input_shape=(BATCH_SIZE, FIELD_SIZE, EMBEDDING_SIZE))
1628

1729
@pytest.mark.parametrize(
1830

tests/models/DeepFwFM_test.py

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
import pytest
2+
from deepctr.models import DeepFwFM
3+
from ..utils import check_model, get_test_data, SAMPLE_SIZE
4+
5+
6+
@pytest.mark.parametrize(
7+
'hidden_size,sparse_feature_num',
8+
[((2,), 1),
9+
((), 1),
10+
]
11+
)
12+
def test_DeepFwFM(hidden_size, sparse_feature_num):
13+
model_name = "DeepFwFM"
14+
sample_size = SAMPLE_SIZE
15+
x, y, feature_columns = get_test_data(sample_size, sparse_feature_num=sparse_feature_num,
16+
dense_feature_num=sparse_feature_num)
17+
model = DeepFwFM(feature_columns, feature_columns, dnn_hidden_units=hidden_size, dnn_dropout=0.5)
18+
19+
check_model(model, model_name, x, y)
20+
21+
22+
if __name__ == "__main__":
23+
pass

0 commit comments

Comments
 (0)