深度学习 - 41.Word2vec、EGES 负采样实现 By Keras

news2024/12/25 10:17:51

目录

一.引言

二.实现思路

1.样本构建

2.Word2vec 架构

3.EGES 架构

4.基于 NEG 的 Word2vec 架构

三.Keras 实现 Word2vec

1.样本构建

2.模型构建

3.向量获取

四.keras 实现 EGES

1.样本构建

2.模型构建

3.Dot Layer 详解

3.1 init 方法

3.2 call 方法

3.3 完整代码

4.向量获取

4.1 计算 sku 相似度

4.2 商品 sku 冷启动

4.3 侧信息重要性分析

4.4 引入预训练模型

五.总结


一.引言

前面介绍了基于 Gensim 的 Word2vec 实现以及 EGES 的理论与样本准备,由于 EGES 是在 Word2vec 的基础上为 (Target, Context) 中的 Target 增加 SideInfo 侧信息增加表达能力,所以这里先实现 Word2vec,再加入侧信息实现 EGES,二者实现思路相近。

Tips:

本文实现方式为 Skip-Gram,CBOW 同学们可以基于下面思路自己实现。

二.实现思路

1.样本构建

由于实现形式为 Skip-Gram,所以是 (Target, Context) 即 (中心词, 上下文) 的形式,通过 window_size 可以控制我们从序列 Seq 样本中获取的样本对数量。

2.Word2vec 架构

样本为 (Target, Context),输入为仅 Target 对应位置为 1 的 one-hot 向量,通过 Hidden-Layer 获取 Target 对应的 K 维向量,随后与 SoftMax 输出层的 KxN 的参数矩阵相乘的到 N 维向量,对 N 维向量取 Softmax 并与仅 Target  为 1 的 one-hot label 进行 Loss 的计算并梯度回传。

Tips:

由于 Target 中心词所在词库大小 N 通常很大,所以最后一步的 Softmax 计算相对比较耗时,论文中提出了两种优化方法:

• 层次 Softmax

层次 softmax 基于霍夫曼树生成的词库,优化了 dot 计算的数量与高频词的计算数量。

• 负采样

Negative Sample 这个方法非常简单粗暴但是好用,每次为 Target 生成 ns 个非 Context 的词作为负样本,这样计算量直接从 N 缩减至 ns,一般推荐 ns 为 5-20

由于层次 softmax 构造相对复杂,这里我们采用更好实现的 Negative Sample 即负采样。 

3.EGES 架构

这里 SI 0 代表 Item 即 (Target, Context) 里的 Context,S1 1 - S1 n 为 n 类 SideInfo 侧信息,针对每一个 SI 0 都有一个 Alpha 权重向量,通过 ∑ α * Emb 的形式得到加权平均的 Embedding,后面的计算同 Word2vec 一致。 N 和 P 代表随机采样的 Negative Sample 和 Context 对应的 Positive Sample。

Tips:

这里把图歪过来,架构就很相似了,简单分下下 Word2vec 与 EGES 的区别:

• 样本构造

EGES 采用 Session 截取用户历史行为,Word2vec 基于用户完整历史行为游走。

• Hidden 层输入

EGES 加入 SideInfo 加权得到隐层的输入,Word2vec 直接 lookup 得到 Target Embedding 输入隐层。

4.基于 NEG 的 Word2vec 架构

直接通过 Embedding 层 lookup 获取 (Target, Context) 对应的 K 维度 Embedding,随后执行 Dot 计算并通过 Sigmoid 得到 0-1 的值,将多分类的 Category_Crossentropy 转换为了二分类 Binary_Crossentropy 的问题。其样本构造也很简单,针对正样本 (Target, Context) 其 Label 为 1,再基于 Target 生成 ns 个 (Target, Negative Word) 的负样本,其 Label 为 0。最后获取 Embedding 的向量作为词向量即可,如果是 EGES 则是增加 Weight Merge 即加权求和操作即可。

三.Keras 实现 Word2vec

1.样本构建

    word_target, word_context = zip(*all_pairs)

    word_target = np.array(word_target, dtype="int32")
    word_context = np.array(word_context, dtype="int32")

上篇数据预处理的文章中我们已经基于编码后的 sku 商品序列游走获取了如上图的样本形式,后续将基于该样本与 NEG 的架构实现 Word2vec 与 EGES。

Tips:

注意数据类型的 dtype,否则会报错,因为我们处理完的为字符类型,训练需要 int32 or int64。

 

2.模型构建

- Embedding 层

  input_target = Input((1,))
  input_context = Input((1,))

  embedding = Embedding(sku_num, args.emb_dim, input_length=1, name="embedding")

  # 获取 Emb
  target = embedding(input_target)
  target = Reshape((args.emb_dim, 1))(target)
  context = embedding(input_context)
  context = Reshape((args.emb_dim, 1))(context)

直接构建 Embedding 层获取 (Target, Context) 的 K 维向量。

- Dot 层

  # now perform the dot product operation to get a similarity measure
  dot_product = Dot(axes=1)([target, context])
  dot_product = Reshape((1,))(dot_product)
  # add the sigmoid output layer
  output = Dense(1, activation='sigmoid')(dot_product)

直接 Dot 计算内积。

- Model 层

  # create the primary training model
  model = Model([input_target, input_context], output)
  model.summary()
  model.compile(loss='binary_crossentropy', optimizer='rmsprop', metrics=['accuracy'])

选择 binary_crossentroy 进行模型编译:

Tips:

同样需要注意把 labels 用 np.array 包装起来,否则训练无法进行。

 

3.向量获取

  model.fit((word_target, word_context), np.array(labels), epochs=10, batch_size=128)

  # 获取训练后的 Embedding
  emb = np.array(embedding.get_weights()[0])
  print("商品数量 %d 词向量数量: %s" % (sku_num, emb.shape))
  print(emb[:10])

跑 10 个 epoch 看看:

最后查看商品数量和 Emb 数量是否一致,随后将 Emb 按索引反映射到 word 即 sku 商品上,即实现了每个 sku 对应一个 Embedding:

 

四.keras 实现 EGES

1.样本构建

(Target, Context) 与上面 Word2vec 一致,这里需要给 Target 增加 SideInfo,将样本修改为 ([Target, SI 1, SI 2, SI N], Context) 的形式,其中 SI N 代表第 N 个 SideInfo。本例中 sku 商品共包含 3 个 SideInfo 分别为: brand-品牌、shop_id-店铺、cate-标签。

  word_target = np.array(word_target, dtype="int32")
  word_context = np.array(word_context, dtype="int32")

  word_target_with_side_info = []

  for word in word_target:
      # 获取侧信息并追加
      side_info = sku_side_info[sku_side_info['sku_id'] == word].values[0]
      word_target_with_side_info.append(np.array(side_info, dtype='int32'))

  word_target_with_side_info = np.array(word_target_with_side_info, dtype='int32')

sku_side_info 是上文数据预处理中生成的 Sku 信息的 DataFrame,这里获取 values 并添加到新的样本集合中:

sku、brand、shop_id 与 cate 对应的 Dim 数为样本中对应特征 Unique 去重后的数量。 

2.模型构建

- 输入层

  # 添加 SideInfo: brand,shop_id,cate
  input_target = Input((4,))
  input_context = Input((1,))

 由于添加了 3 类 SideInfo,所以 Input 增加 3 维。

- Dot 层

    dot_layer = EGES_model(feat_num_list, args.emb_dim)([input_target, input_context])

这里 dot_layer 需要实现 EGES 加权求和获取 Merge 合并后 Embedding 的工作,篇幅较长,我们放在后面统一分析。

- Model 层

  # add the sigmoid output layer
  output = Dense(1, activation='sigmoid')(dot_layer)

  # create the primary training model
  model = Model([input_target, input_context], output)
  model.summary()
  model.compile(loss='binary_crossentropy', optimizer='rmsprop', metrics=['accuracy'])

3.Dot Layer 详解

dot_layer 实现继承 tensorflow.python.keras.models.Model 实现,主要包含 init 初始化与 call 调用两个方法的实现。其中 call 方法如下图红框所示,基本包含了 EGES 前置的全部逻辑。

 

3.1 init 方法

根据上图我们分析模型需要如下参数:

• SI 0 参数矩阵

SI 0 为 sku 对应的 Embedding 层,其维度为 len(unique(sku)) x K

• SI 1-N 参数矩阵

SI 1-N 为 Side Info 对应的 Embedding 层,其维度分别为 len(unique(SI I)) x K

• Alpha 权重矩阵

针对每个 sku 有 1+s 个 alpha 权重,所以共需要 len(unique(sku)) x (1+s) 个参数

A.参数准备

这里 embedding_list 存储 SI 0-N 的 Embedding 层参数,feat_num = 1 + s 即总特征数,dim = K 代表 Embedding 维度,sku_num = len(unique(sku_id)) 为全部商品的数量。

B.参数初始化

Sku Num: 34048
Feat && Dim: [['sku_id', 34048], ['brand', 3663], ['shop_id', 4786], ['cate', 80]]

根据每个 Feat 的维度构建其 Embedding 层参数,最后构建 sku_num x feat_num 的 alpha 权重层参数。 

 

3.2 call 方法

根据上图我们再分析下如何实现 call 方法:

• 依次获取 Embedding

按照样本书序从对应 embedding_list 中 lookup 即可获取对应向量

• 拼接 Embedding

tf.stack 直接将获取的向量拼接在一起

• 加权求和 Embedding

lookup 获取 alpha 向量,与 stack 拼接的向量对位相乘再 reduce_sum 求和即可

A.Target && Side 向量获取

原始样本为 None x 4,lookup 后 stack 在一起得到 None x 4 x 128。

B.Alpha 权重向量获取

获取对应 sku 的 1+s 维 Alpha 权重向量,为了保证每个权重的非负性,这里采用 exp 转正并通过 Softmax 的形式对每个权重参数进行了归一化。

C.reduce_sum 加权求和

直接对位相乘再除以求和后的 α 即可。

 

D.Dot 获取内积

加权求和 merge 后的 embedding 与 lookup 得到的 1xK 的 context 即目标词的 embedding 进行内积,最终得到 None x 1 输出到下一层。

 

3.3 完整代码

from abc import ABC

import tensorflow as tf
from tensorflow.python.keras.models import *
from tensorflow.python.keras.layers import *


class EGES_model(Model, ABC):

    def __init__(self, feat_num_list, emb_dim):
        super(EGES_model, self).__init__()

        # Embedding 矩阵
        self.embedding_list = []
        self.feat_num = len(feat_num_list)
        self.dim = emb_dim
        self.sku_num = feat_num_list[0][1]
        print("Sku Num:", self.sku_num)
        print("Feat && Dim:", feat_num_list)

        # word embedding
        for i in range(self.feat_num):
            feat_name = feat_num_list[i][0]
            feat_num = feat_num_list[i][1]
            self.embedding_list.append(self.add_weight(name=feat_name,
                                                       shape=(feat_num, emb_dim),
                                                       initializer='he_normal',
                                                       trainable=True))
        # alpha weight embedding [id x 4]
        self.alpha_weight = self.add_weight(name='weight',
                                            shape=(self.sku_num, self.feat_num),
                                            initializer='he_normal',
                                            trainable=True)

    def call(self, pairs):
        (word_with_side_info, context_info) = pairs

        # 获取侧信息 Embedding 用户3个侧信息  None x 4 x 128
        embed_list = []
        for i in range(self.feat_num):
            # [N x Emb] 10x128
            index = tf.cast(word_with_side_info[:, i], dtype='int32')
            emb_var = tf.nn.embedding_lookup(self.embedding_list[i], index)[:, :self.dim]
            embed_list.append(emb_var)
        # (None, 128, 4)
        combine_emb = tf.stack(embed_list, axis=-1)
        # (None, 4, 128)
        combine_emb = tf.reshape(combine_emb, shape=(-1, self.feat_num, self.dim))

        # 加权求和
        # (None, ) (None, 4) (None, )
        sku_index = tf.cast(word_with_side_info[:, 0], dtype='int32')
        sku_alpha_emb = tf.nn.embedding_lookup(self.alpha_weight, sku_index)[:, :]
        alpha_sum = tf.expand_dims(tf.reduce_sum(tf.exp(sku_alpha_emb), axis=-1), axis=1)

        # (None, 4, 128)
        add_weight_emb = combine_emb * tf.exp(tf.expand_dims(sku_alpha_emb, axis=-1))
        # (None, 128)
        merge_emb = tf.reduce_sum(add_weight_emb, axis=1) / alpha_sum

        # 上下文变量
        # (None, 1, 128)
        context_emb = tf.nn.embedding_lookup(self.embedding_list[0], tf.cast(context_info, dtype='int32'))[:, :self.dim]
        # (None, 128)
        context_emb = tf.reshape(context_emb, shape=(-1, self.dim))

        # None x 1
        dot_product = Dot(axes=1)([merge_emb, context_emb])

        return dot_product

4.向量获取

    model.fit([word_target_with_side_info, word_context], np.array(labels), epochs=10, batch_size=128)

跑 10 个 epoch,同样注意把 labels 用 np.array 包起来:

训练完我们从 EGES_model 对应的 dot 层获取向量即可:

  # 获取训练后的 Embedding
  weight_list = model.layers[2].get_weights()
  weight_map = {}
  for i in range(len(feat_num_list)):
      feat_name = feat_num_list[i][0]
      feat_emb = weight_list[i]
      weight_map[feat_name] = feat_emb

针对 SI 0 - sku 和 SI 1-3 三个侧信息,我们可以获取 4 个向量矩阵,根据后续任务的不同,我们可以做如下事情:

4.1 计算 sku 相似度

使用 sku Embedding 计算内积寻找不同 sku 的相似度,也可以降维观察不同 sku 的分布

 

4.2 商品 sku 冷启动

在冷启动时引入商品对应侧信息优化冷启动商品的初始 Embedding

 

4.3 侧信息重要性分析

    print(np.array(weight_list[-1]))

通过 alpha 参数矩阵,我们使用 exp 归一化可以分析不同 sku 的向量偏好,根据色阶图看以分析不同特征对结果的重要程度

4.4 引入预训练模型

可以将不同特征的 Embedding 用于后续 Deep 任务的参数初始化,例如 FNN 使用 FM 预训练的向量一样

五.总结

当年还未深度接触 DeepLearning 时,总觉得 word2vec 很复杂,但是通过前面的分析、再到框架最后到具体实现,我们发现其思路清晰实现也很简洁,如果把 dot 层的计算更换为其他相似度计算例如 Cos 余弦相似度,那上面的架构就和 DSSM 很类似。

word2vec 是 Google 于 2013 年开源的获取 word vector 的算法包,距今已经10年、EGES 是 2018 年由阿里巴巴算法团队提出,距今也已经5年,在算法日新月异的今天,已经算的是很"古老"的知识了,但是其 Embedding 的思想一直影响着后面深度学习的发展,因此把它搞清楚对于很多 DeepLearning 的知识理解也很有帮助。

上面利用负采样的方法实现了 Skip-gram,除了负采样的优化方法外,源码中还有很多实现的细节:

• σ(x) 的近似计算

sigmoid 函数在 x < -6 或者 x > -6  时变化已经微乎其微,实际计算中,除了 Softmax 计算量巨大外,sigmoid 函数计算也很多,可以通过细分区间缓存 σ(x) 的近似值,从而将计算切换为查表,优化计算速度。

• 向量相似度检索

由于词库的大小 N 通常很大,每次计算都匹配所有向量并内积计算相似度会非常耗时,线上无法接受,可以通过缓存 item-item 之间的相似度做的快速查找,常用的方法有 LSH(局部敏感哈希)算法。

• 低频词与高频词

利用语料构建词库时,开发者可以通过 min_count 控制每个词出现过多少次才能收到到词库中。低频词出现次数较少,但其独一无二的性质有时可以精准描述某些特定场景,而高频词虽然出现很多但却意义不大,例如 '的'、'是' 这些,因此实际场景中,除了过滤停用词外,还需要分析低频词的代表性以及高频词的实际意义。可以通过 SubSampleing 的技巧对高频词做处理,其思想是有一定概率不计算当前高频词从而优化计算速度,就像是 Dropout 一样。当然使用层次 Softmax 也可以很好地解决高频词的效率问题。

• 窗口与上下文

根于 Target 与 window_size ,我们会生成 [-window_size,window_size] -1 个样本,源码中采取先对 [1, window_size] 随机一个整数 c,然后再生成 [-c,c] -1 个样本,相当于 window_size 其实也是随机变化的。

参考:

Word2vec 的一些思考:word2vec的一些遗留思考

Word2vec 的一些理论:word2vec 中的数学原理详解

Word2cec 的一些代码:基于keras实现word2vec

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/442300.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

4月18号软件更新资讯合集

ModStartCMS v6.2.0&#xff0c;VIP 权益配置功能、界面 UI 优化升级 ModStart 是一个基于 Laravel 模块化极速开发框架。模块市场拥有丰富的功能应用&#xff0c;支持后台一键快速安装&#xff0c;让开发者能快的实现业务功能开发。 系统完全开源&#xff0c;基于 Apache 2.…

4月19号软件更新资讯合集....

JavaWeb 微服务前后端分离 EurekaEleVue 版 v1.5.0 发布 v1.5.0 更新如下&#xff1a; 1、解决 token 过期无法跳转至登录页的问题&#xff1b; 2、授权服务进行重构与优化&#xff1b; 一款 Java 语言基于 SpringCloud、SpringSecurity、OAuth2、Eureka、Vue、ElementUI、…

Docker实战笔记1-基础

转载请标明出处&#xff1a;http://blog.csdn.net/zhaoyanjun6/article/details/130181636 本文出自【赵彦军的博客】 文章目录 官网下载安装安装Docker 镜像镜像分层存储 容器常用命令docker infodocker imagesdocker versiondocker ps &#xff1a;查看容器docker ps -a查看容…

如何检查设置的IP是否有效?Storm proxies动态代理IP好用吗?

检查设置的IP是否有效可以通过以下几种方式&#xff1a; 发起网络请求&#xff1a;可以使用HTTP客户端库&#xff08;例如Python的Requests库&#xff09;或者命令行工具&#xff08;例如curl&#xff09;来发起网络请求&#xff0c;使用设置的IP作为代理IP&#xff0c;然后查看…

读SQL进阶教程笔记12_地址与三值逻辑

1. SQL和数据库都在极力提升数据在表现层的抽象度&#xff0c;以及对用户隐藏物理层的概念 2. 关系模型是为摆脱地址而生的 2.1. “地址”不仅包括指针操作的地址&#xff0c;还包括数组下标等 3. 一个优雅的数据结构胜过一百行杂耍般的代码 3.1. 精巧的数据结构搭配笨拙的…

数据结构入门——顺序表(保姆级教程,增,删,改,查)

1.什么是顺序表 1.顺序表&#xff1a;可动态增长的数组&#xff0c;要求数据是连续存储的 2.顺序表的定义&#xff1a; 静态顺序表&#xff1a;使用定长数组存储元素&#xff08;缺点&#xff1a;小了不够用&#xff0c;大了还浪费&#xff09; 动态顺序表&#xff1a;可根…

744. 寻找比目标字母大的最小字母

给你一个字符数组 letters&#xff0c;该数组按非递减顺序排序&#xff0c;以及一个字符 target。letters 里至少有两个不同的字符。 返回 letters 中大于 target 的最小的字符。如果不存在这样的字符&#xff0c;则返回 letters 的第一个字符。 示例 1&#xff1a; 输入: le…

3. VBA术语

在本章中&#xff0c;将介绍常用的Excel VBA术语。这些术语将在很多的模块中使用&#xff0c;因此理解其中的每一个术语都很重要。 3.1 模块 模块是编写代码的区域。如下图中&#xff0c;这是一个新的工作簿&#xff0c;因此没有任何模块。 要插入模块&#xff0c;请导航到插…

Cell--瘤内微生物将开辟新疗法

2023年4月13日&#xff0c;弗雷德哈钦森癌症中心的微生物学家Susan Bullman教授在《Cell》杂志发表了关于肿瘤内微生物群的评论。 微生物群是肿瘤微环境的一个组成部分 在患者的肿瘤内部&#xff0c;恶性细胞处在一个复杂的生态系统中&#xff0c;周围是正常细胞的网络&#xf…

socks5与http代理如何转化?stormproxies怎么解决?

Socks5和HTTP代理之间可以通过一些工具或软件进行转化&#xff0c;具体的方法如下&#xff1a; 使用ProxyCap&#xff1a;ProxyCap是一款常用的代理工具&#xff0c;可以将Socks5代理转化为HTTP代理。在ProxyCap中设置Socks5代理的服务器地址和端口&#xff0c;然后在本地设置H…

Java应用高性能的方法和思路

Java应用高性能的方法和思路 目录概述需求&#xff1a; 设计思路实现思路分析 参考资料和推荐阅读 Survive by day and develop by night. talk for import biz , show your perfect code,full busy&#xff0c;skip hardness,make a better result,wait for change,challenge …

软件测试基础概念

1.软件测试的生命周期 需求分析-》测试计划-》测试设计-》测试开发-》测试执行-》测试评估 需求分析&#xff1a;分析需求是否合理和正确 计划:确定软件由谁测试&#xff1f; 什么时候开始测试&#xff0c;什么时候结束测试 测试那些模块 测试设计和测试开发阶段&#xff1a;…

4.19~20(总结)

项目越到后面&#xff0c;推进越难&#xff0c;已经到了发送消息这里了&#xff0c;这个做完&#xff0c;基本上也就完成得七七八八了&#xff0c;后面只需要优化了&#xff0c;但是现在卡在这里就还是有点难搞。 首先&#xff0c;我的界面已经成这样了。 我的打算是这样可以搜…

C++基础知识-2

本期我们接着来讲C的基础知识&#xff0c;没有看过的朋友可以先看看上一期 (16条消息) C基础知识-----命名空间_KLZUQ的博客-CSDN博客 目录 4.缺省参数 5.函数重载 6.引用 7.内联函数 8.auto关键字&#xff08;C11&#xff09; 9. 基于范围的for循环(C11) 10.指针空值nul…

算法记录 | Day37 贪心算法

738.单调递增的数字 思路&#xff1a; 1.一旦出现strNum[i - 1] > strNum[i]的情况&#xff08;非单调递增&#xff09;&#xff0c;首先想让strNum[i - 1]–&#xff0c;然后strNum[i]给为9&#xff0c;这样这个整数就是89&#xff0c;即小于98的最大的单调递增整数。 2…

初始VUE

目录 什么是vue vue的特点 前置js基础知识 vue2 安装vue devtools 搭建开发环境 Vue对象的el及data写法 el的处理 data的处理 特点 什么是js表达式&#xff0c;js代码&#xff08;语句&#xff09; vue脚手架&#xff08;vue cli&#xff09; 使用说明 具体步骤 …

瑞吉外卖:软件开发基础和项目介绍

文章目录 软件开发基础软件开发流程角色分工软件环境 瑞吉外卖项目介绍项目介绍开发流程技术选型功能架构角色 软件开发基础 软件开发流程 需求分析&#xff1a;产品原型&#xff08;大体结构、页面、功能等&#xff09;和需求规格说明书设计&#xff1a;产品文档、UI界面设计…

计算机组成原理——第五章中央处理器(上)

半生风雨半生伤&#xff0c;半醉半醒半心凉 文章目录 前言5.1 CPU的功能和基本结构5.2 指令周期的数据流5.3.1 单总线结构5.3.2 专用通路结构 前言 之前我们就说过CPU主要包括两个部分&#xff0c;运算器和控制器&#xff0c;运算器主要是实现算数运算.逻辑运算&#xff0c; 运…

python正则表达式与re模块

欢迎关注博主 Mindtechnist 或加入【Linux C/C/Python社区】一起探讨和分享Linux C/C/Python/Shell编程、机器人技术、机器学习、机器视觉、嵌入式AI相关领域的知识和技术。 python正则表达式与re模块 正则表达式元字符① . 通配符② ^③ $④ *⑤ ⑥ ?⑦ {}⑧ []⑨ \ 转义符⑩…

python 怎么使用pip进行包管理

包管理工具是用来对一些应用程序的包进行管理的工具&#xff0c;比如nodejs使用npm&#xff0c;yarn来进行包管理&#xff0c;linux使用apt来进行包管理。python包管理工具或许不如他们有名&#xff08;实际上pip的大名比前几位更响亮&#xff09;&#xff0c;但绝对比他们好用…