当前位置:   article > 正文

实习经验小记:如何把数据里面自带的embedding通过网络完成交互(预训练)以及一个Keras报错ValueError: Graph disconnected_the following previous layers were accessed withou

the following previous layers were accessed without issue: []

1. 写在前面

今天在搭建模型的时候遇到了一个报错:

ValueError: Graph disconnected: cannot obtain value for tensor Tensor(“input_14:0, 
shape=(None, 24, 9, 1), dtype=float32) at layer “input_14”. 
The following previous layers were accessed without issue: []
  • 1
  • 2
  • 3

我这边的场景是这样: 在推荐的排序阶段,面临着一堆常规的排序特征, 但还有几个embedding向量, 这几个embedding向量,是通过bert抽取的文章的embedding, 如果在排序模型里面,直接把这几个embedding拼接起来喂入的话,可能会出现过拟合的风险,因为我这里的样本并不是很多,而embedding的维度是上百维,所以我初步的思路是将这几个embedding通过神经网络融合下。 那么怎么融合呢?

我这里的思路,就是将这几个embedding向量过一个Attention网络,然后加权融合, 但由于这几个embedding向量是提取好了的连续特征了, 那么这种情况,怎么把它构建到模型里面呢? 注意这种情况并不像那种类别特征,过一个embedding层那种,然后加Attention, 我这里的问题是已经抽取好了embedding,如果通过输入层直接拼接过Attention网络呢?

这里的思路是这样, 为这些embedding特征的每一维建立输入层(就当做一个dense特征即可), 然后把这些输入层,按照名称切开,然后concat起来,就得到了embedding向量,此时过Attention网络就可以啦。 处理代码如下:

def get_embed_logits(emb_input_dict):
    # 只考虑sparse的二阶交叉,将所有的embedding拼接到一起
    # 这里在实际运行的时候,其实只会将那些非零元素对应的embedding拼接到一起
    # 并且将非零元素对应的embedding拼接到一起本质上相当于已经乘了x, 因为x中的值是1(公式中的x)
    sparse_kd_embed = []
    #print(emb_input_dict)
    embed_name = ['src_content_embedding_', 'rec_content_embedding_', 'src_item_embedding_', 'rec_item_embedding_']
    for emb in embed_name:
        sparse_kd_embed.append(tf.expand_dims(Concatenate(axis=1)([emb_input_dict['src_content_embedding_'+str(i)] for i in range(0,32)]),1))

    # 输入AFM_Layer中的是一个列表,方便计算两两向量之间的对应元素的乘积
    att_logits = AFM_Layer()(sparse_kd_embed)

    return att_logits
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14

这里面的emb_input_dict是embedding特征的输入词典。 如果熟悉deepctr风格代码的话,这个应该能理解。

通过这样的方式,就能把数据里面原先带有的embedding特征进行交互处理了。 然后我在xdeepFM的结构基础上, 加一个注意力网络来单独学习某几个比较重要的向量之间的交互信息。类似于将AFM的注意力网络嵌入到xdeepFM里面去。

结果就遇到了上面这个报错问题。 参考了一篇文章的解决方法,由于基于自己的业务魔改模型是以后的常规操作,所以这个报错感觉会留下不解之缘, 所以记录下。

文章原链接:https://blog.csdn.net/weixin_39653948/article/details/105661155

主要原因三个:

  • 变量名重用:参考这篇文章
  • 调用模型直接合并错误: 参考这个回答
  • 重建Model的时候, 模型的inputs参数输入不全: 参考这个issue

我的目前就是第三个问题,由于我是魔改的xdeepFM吗,相当于在这个的基础上新加了一个模块,也就是多加了一块输入,然后这块输入,是过一个Attention网络, 进行加权融合,然后再和wide端,deep端合并起来。 但由于在input_layers里面忘了加新加的这个输入了,所以如果只合并的话就会报这个错误,只需要在input_layers里面加上这块即可。
在这里插入图片描述
所以这篇文章的关键,第一个是场景,如果发现某几个embedding向量特别重要,并且是事先通过别的方式得到的,如何通过网络进行向量交互, 第二个就是魔改了网络之后,输入输出要对应好。

后记:

关于特征里面自带的embedding向量如何与其它类别特征的embedding向量进行交互的问题, 自带的embedding向量其实相当于预训练好的embedding, 所有此时也可以通过加载预训练向量的方式,把自带的embedding向量加载到embedding层中,去进行交互。 这是和我大意哥学习到的, 这也是正规的方式。 上面那个是独创的野路子, 所以还是学习下正规打法。 这个的原理是这样:

每个样本中,自带embedding向量, 此时先将embedding向量与样本的主键进行关联起来(这样就保证了每个样本对应了唯一的embedding向量), 然后再将主键与索引值关联起来,这样就把上面的embedding向量对应到了词典里面去。 然后把主键的索引当做类别特征值输入,根据这个索引就能找到对应的embedding,拿到之后, 就和原来操作是一样的了。

好吧, 这么说可能还不如实操一遍, 直接上操作吧, 我这边的数据是这样的:

在这里插入图片描述
这是训练集里面的其中一个特征, 是从bert中取到的embedding向量。数据集里面一共有6个已经训练好的embedding。现在要做的事情, 是把这6个已经训练好的特征加载到后面网络的embedding层里面去, 与其它的类别embedding交互,或者是单独交互等。

首先, 把训练集,验证集和测试集concat起来:

data = pd.concat([train_data, val_data, test_data])
  • 1

这样做的目的是基于当前数据先构建item词典。因为我们后面要取对应embedding嘛,必须得把embedding和索引对应。

接下来, 构建词典:

item = set(data['src_item_id']).union(set(data['rec_item_id']))

# item_index映射  
item2index_dict = {id: index for index, id in enumerate(item)}
  • 1
  • 2
  • 3
  • 4

这样就构建了一个item_id的词典。item_id -> index

接下来, 我们将embedding向量,保存成numpy的形式到文件, 注意,此时下标index -> embedding再进行对应。

embed_path = './embed_path/'
for embed_col in embed_cols:  # embed_cols: 'src_content_embedding_bert'  embedding向量的特征名列表
    
    if 'bert' in embed_col:
        embed_dim = 128
    else:
        embed_dim = 32
    
    embed_array = np.zeros((data.shape[0], embed_dim))
    
    data["{}_id".format(embed_col)] = data[embed_col].apply(lambda x: item2index_dict[x])
    
    for i, emb in enumerate(data[embed_col]):
        embed_array[item2index_dict[emb]] += eval(emb)
    
    # 将训练的feed embedding 保存
    np.save(embed_path + embed_col + '_embeds.npy', embed_array)
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17

这里就是说,对于每组embedding向量, 我新生成一个id特征,这个id特征的取值就是embedding向量在词典里面的索引值。 这样就直接把索引和embedding向量进行了关联。 而这个id值,我们就可以当成普通的类别特征输入到网络里面去。 这样, 这个id过embedding层之后,就能取到对应的embedding向量了。

接下来,特征封装:

sparse_columns = [
    'src_content_embedding_id',
    'src_content_embedding_bert_id',
     'rec_content_embedding_id',
     'rec_content_embedding_bert_id',
     'src_item_embedding_id',
     'rec_item_embedding_id'
]

sparse_features = sparse_columns

# 这里要注意, 这里我是为了简单, 把词典大小设置成最大值+1, 但实际上不能这么设置的, 因为这么玩,万一再出来新的item, 这就没法加了, 所以这里的+1可以预留更大的值,以防备新的item加入
dnn_feature_columns = [SparseFeat(feat, vocabulary_size=int(len(item2inde_dict).max())+1, embedding_dim=8)
                       for i, feat in enumerate(sparse_features)]
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14

那么,应该怎么用保存好的numpy embedding呢? 下面是我在Fibinet网络里面的一组应用, 主要是让这6个embedding向量交互, 过SENET和双线性层。

embed_feature_columns = [feat for feat in dnn_feature_columns if 'embedding' in feat.name]
    embed_embedding_list = []
    for embed_feature_column in embed_feature_columns:
        emb_input = features[embed_feature_column.name]
        features.pop(embed_feature_column.name)
        embeddings_matrix = np.load(embed_path + embed_feature_column.name[:-3] + '_embeds.npy')
        
        if 'bert' in embed_feature_column.name:
            emb_out = Embedding(embeddings_matrix.shape[0], 
                            output_dim=128,
                            weights=[embeddings_matrix],
                            name=embed_feature_column.name[:-3]+'_pretrain_emb',
                            trainable=False)(emb_input)
            emb_out = Flatten()(emb_out)
            emb_out = Dense(8, activation='relu')(emb_out)
            emb_out = tf.expand_dims(emb_out, axis=1)
            
        else:
            emb_out = Embedding(embeddings_matrix.shape[0], 
                            output_dim=32,
                            weights=[embeddings_matrix],
                            name=embed_feature_column.name[:-3]+'_pretrain_emb',
                            trainable=False)(emb_input)
        embed_embedding_list.append(emb_out)
    
     senet_embed_embedding_list = SENETLayer(reduction_ratio, seed)(embed_embedding_list)
     senet_embed_bilinear_out = BilinearInteraction(bilinear_type=bilinear_type, seed=seed)(senet_embed_embedding_list)
     embed_bilinear_out = BilinearInteraction(bilinear_type=bilinear_type, seed=seed)(embed_embedding_list)
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28

这里重点是如何加入预训练好的向量的过程, Embedding里面的weights参数, 我们需要把事先保存好的numpy给这个参数,并且设置成trainable=False, 当然也可以设置成TRUE,我这边试过,那样很容易过拟合。 当然,这么玩我这边依然是过拟合。

所以,通过这两天的尝试,我觉得有时候,直接将embedding参数加载为预训练可能还不是一种好方式,尤其是参数量多了之后,很容易过拟合。 这两天,我摸索出来一种方式, 就是把embedding向量通过PCA降维,然后当成连续特征给网络用, 在fibinet上的效果要好的多。 当然, embedding变成连续特征的一般方式是求相似度, 确实,这个也是一种利用embedding的方式了。

所以关于自带embedding的使用方法, 我这里摸索出了三个尝试思路:

  • 把embedding两两求余弦相似度或者欧式距离变成几个连续的特征, 然后上模型
  • 把最重要的1-2个embedding,通过pca降维,变成连续的n个特征,然后上模型。 我这里128维度的bert向量很重要,所以我这里先用PCA降到了64维, 然后取了前32个作为离散特征。之所以这么做,试出来的。64维的话希望能尽量少减少点信息, 而取前32, 可能是不容易太过度拟合, 可能是从减少信息与过拟合之间的一种平衡吧。 再说简单点,玄学。
  • embedding向量,用预训练的方式加载到网络中,进行特征交互, 这种方式感觉很容易过拟合。需要用一些缓解过拟合的策略才行。
声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/你好赵伟/article/detail/499783
推荐阅读
相关标签
  

闽ICP备14008679号