原理
- Embedding Layer:嵌入层,用于将input中每个Field特征映射为低维稠密特征,然后每个特征的embedding横向拼接,作为下一层的输入;
- Product Layer:特征交互层,由z和p两部分组成,其中z为上层的输出结果,p为上层输出的特征交互结果,低维与高维特征的直接拼接;以Field为粒度进行特征之间的交叉,交叉方式分为:内积IPNN和外积OPNN。输出的张量形状为[None, field, k],其中None表示batch_size大小,field为原始输入的特征个数,k为每个特征嵌入后对应稠密向量维度;
- Inner Product:field个特征两两内积,每两个k维特征的内积可以得到一个一维向量,总共得到field*(field-1)/2个变量,拼接在一起即为特征交互的结果p,形状为[None, field*(field-1)/2]
- Outer Product:和内积不同,每两个k维特征的外积为[k, k]的二维向量,所以引入等形状的权重矩阵W,与二维张量进行对应元素乘积,然后求和得到一维变量。
- Hidden Layer:三层全连接,最后一层映射到一维接sigmoid得到概率输出
优缺点
优点:
- 显示的进行特征交互,提高模型的表达能力;
- 以field为粒度进行特征交互,保留了域的概念;
- 同时保留了低维和高维的特征;
缺点:
- 外积交互方式参数量较大,随着特征维度平方级增加;
代码实现
class InnerProductLayer(Layer):
def __init__(self):
super(InnerProductLayer, self).__init__()
def call(self, inputs, **kwargs):
field_num = inputs.shape[1]
row, col = [], []
# 先将要相乘的emb找出来,存在两个矩阵中,然后进行点乘即可
for i in range(field_num - 1):
for j in range(i + 1, field_num):
row.append(i)
col.append(j)
# tf.gather根据indices的参数值获取切片
p = tf.transpose(tf.gather(tf.transpose(inputs, [1, 0, 2]), row), [1, 0, 2])
q = tf.transpose(tf.gather(tf.transpose(inputs, [1, 0, 2]), col), [1, 0, 2])
innerProduct = tf.reduce_sum(p * q, axis=-1)
return innerProduct
class OuterProductLayer(Layer):
def __init__(self):
super(OuterProductLayer, self).__init__()
def build(self, input_shape):
self.field_num = input_shape[1]
self.k = input_shape[2]
self.pair_num = self.field_num * (self.field_num - 1) // 2
# 每个外积矩阵对应一个w矩阵,共有pair个
self.w = self.add_weight(name='W', shape=(self.k, self.pair_num, self.k),
initializer=tf.random_normal_initializer(),
regularizer=l2(1e-4),
trainable=True)
def call(self, inputs, **kwargs):
row, col = [], []
for i in range(self.field_num - 1):
for j in range(i + 1, self.field_num):
row.append(i)
col.append(j)
p = tf.transpose(tf.gather(tf.transpose(inputs, [1, 0, 2]), row), [1, 0, 2]) # [None, pair_num, k]
q = tf.transpose(tf.gather(tf.transpose(inputs, [1, 0, 2]), col), [1, 0, 2]) # [None, pair_num, k]
p = tf.expand_dims(p, axis=1) # [None, 1, pair_num, k]
tmp = tf.multiply(p, self.w) # [None, 1, pair_num, k] * [k, pair, k] = [None, k, pair_num, k]
tmp = tf.reduce_sum(tmp, axis=-1) # [None, k, pair_num]
tmp = tf.multiply(tf.transpose(tmp, [0, 2, 1]), q) # [None, pair_num, k]
outputProduct = tf.reduce_sum(tmp, axis=-1) # [None, pair_num]
return outputProduct
参考