概述
本文主要讲解tensorflow中涉及embedding的API。之前看了一些文章,写的云山雾绕,花了好长时间才搞懂,太笨了。
embedding 算法主要用于处理稀疏特征,应用于NLP、推荐、广告等领域。所以word2vec 只是embbeding 思想的一个应用,而不是全部。
代码地址:git@github.com:gshtime/tensorflow-api.git
embedding原理
常见的特征降维方法主要有PCA、SVD等。
而embedding的主要目的也是对(稀疏)特征进行降维,它降维的方式可以类比为一个全连接层(没有激活函数),通过 embedding 层的权重矩阵计算来降低维度。
假设:
- feature_num : 原始特征数
- embedding_size: embedding之后的特征数
- [feature_num, embedding_size] 权重矩阵shape
- [m, feature_num] 输入矩阵shape,m为样本数
- [m, embedding_size] 输出矩阵shape,m为样本数
应用中一般将物体嵌入到一个低维空间 $R^{embedding-size} (embedding-size << feature-num)$ ,只需要再compose 上一个从 $R^{feature-num}$ 到 $R^{embedding-size}$ 的线性映射就好了。每一个shape为 $feature-num \times embedding-size$ 的矩阵M(embedding矩阵) 都定义了 $R^{feature-num}$ 到 $R^{embedding-size}$ 的一个线性映射: $x \mapsto Mx$ 。当 $x$ 是一个标准基向量的时候,$Mx$对应矩阵 $M$ 中的一列,这就是对应 $id$ 的向量表示。
从id(索引)找到对应的 One-hot encoding ,然后红色的weight就直接对应了输出节点的值(注意这里没有 activation function),也就是对应的embedding向量(每个稀疏特征用一个embedding向量表示)。
tensorflow API
基础: tf.SparseTensor
构造稀疏向量矩阵,每一行为一个样本
SparseTensor(indices, values, dense_shape)
params:
- indices: A 2-D int64 tensor of dense_shape [N, ndims], which specifies the indices of the elements in the sparse tensor that contain nonzero values (elements are zero-indexed). For example, indices=[[1,3], [2,4]] specifies that the elements with indexes of [1,3] and [2,4] have nonzero values. 是dense_shape这个矩阵中所有values值的位置,与values一一对应。
- values: A 1-D tensor of any type and dense_shape [N], which supplies the values for each element in indices. For example, given indices=[[1,3], [2,4]], the parameter values=[18, 3.6] specifies that element [1,3] of the sparse tensor has a value of 18, and element [2,4] of the tensor has a value of 3.6. 每一个稀疏值,与其位置indices一一对应。
- dense_shape: A 1-D int64 tensor of dense_shape [ndims], which specifies the dense_shape of the sparse tensor. Takes a list indicating the number of elements in each dimension. For example, dense_shape=[3,6] specifies a two-dimensional 3x6 tensor, dense_shape=[2,3,4] specifies a three-dimensional 2x3x4 tensor, and dense_shape=[9] specifies a one-dimensional tensor with 9 elements. 稀疏向量矩阵的shape
Example: The sparse tensor
1 | SparseTensor(indices=[[0, 0], [1, 2]], values=[1, 2], dense_shape=[3, 4]) |
tf.nn.embedding_lookup 和 partition_strategy 参数
1 | # Signature: |
是根据 ids
中的id,寻找 params
中的第id行。比如 ids=[1,3,5]
,则找出params
中第1,3,5行,组成一个tensor返回。
embedding_lookup不是简单的查表,params
对应的向量是可以训练的,训练参数个数应该是 feature_num * embedding_size,即前文表述的embedding层权重矩阵,就是说 lookup 的是一种全连接层。
此外,以下要记录的几个API里,都有参数 partition_strategy (切分方式), 这个参数是当len(params) > 1 时,才生效,即当params 以list [a, b, c] (a,b,c都是tensor) 输入多个tensor时,对params的选择顺序进行切分,(而不是对ids进行切分,ids只有选择的作用,当然也决定了在return中次序)。
输入为单个tensor时
1 | # 当输入单个tensor时,partition_strategy不起作用,不做 id(编号) 的切分 |
输入为多个tensor时
partition_strategy 开始起作用,开始对多个tensor 的第 0 维上的项进行编号,编号的方式有两种,”mod”(默认) 和 “div”。
假设:一共有三个tensor [a,b,c] 作为params 参数,所有tensor
的第 0 维上一共有 10 个项目(id 0 ~ 9)。
“mod” : (id) mod len(params) 得到多少就把 id 分到第几个tensor里面
- a 依次分到id: 0 3 6 9
- b 依次分到id: 1 4 7
- c 依次分到id: 2 5 8
“div” : (id) div len(params) 可以理解为依次排序,但是这两种切分方式在无法均匀切分的情况下都是将前(max_id+1)%len(params)个 partition 多分配一个元素.
- a 依次分到id: 0 1 2 3
- b 依次分到id: 4 5 6
- c 依次分到id: 7 8 9
1 | # partition_strategy='div' 的情况 |
注:如果ids 中 有 3,这个id 虽然被分给 b 这个tensor了,但是b没有,会报一个错误1
2InvalidArgumentError: indices[0] = 1 is not in [0, 1)
[[Node: embedding_5/GatherV2_1 = GatherV2[Taxis=DT_INT32, Tindices=DT_INT32, Tparams=DT_INT64, _device="/job:localhost/replica:0/task:0/device:CPU:0"](Variable_154/read, ConstantFolding/embedding_5/DynamicPartition-folded-1, embedding_5/GatherV2_1/axis)]]
tf.gather()
函数签名如下:1
2
3
4
5tf.gather(
params,
indices,
validate_indices=None,
name=None
参数说明:
- params是一个tensor,
- indices是个值为int的tensor用来指定要从params取得元素的第0维的index。
该函数可看成是tf.nn.embedding_lookup()的特殊形式,所以功能与其类似,即将其看成是embedding_lookup函数的params参数内只有一个tensor时的情形。
tf.nn.embedding_lookup_sparse
1 | tf.nn.embedding_lookup_sparse( |
参数
见官网API,不贴了:python API
- params: A single tensor representing the complete embedding tensor, or a list of P tensors all of same shape except for the first dimension, representing sharded embedding tensors. Alternatively, a PartitionedVariable, created by partitioning along dimension 0. Each element must be appropriately sized for the given partition_strategy.
Returns:
A dense tensor representing the combined embeddings for the sparse ids. For each row in the dense tensor represented by sp_ids, the op looks up the embeddings for all ids in that row, multiplies them by the corresponding weight, and combines these embeddings as specified.
In other words, if
- shape(combined params) = [p0, p1, …, pm]
and
- shape(sp_ids) = shape(sp_weights) = [d0, d1, …, dn]
then
- shape(output) = [d0, d1, …, dn-1, p1, …, pm].
For instance, if params is a 10x20 matrix, and sp_ids / sp_weights are
[0, 0]: id 1, weight 2.0 [0, 1]: id 3, weight 0.5 [1, 0]: id 0, weight 1.0 [2, 3]: id 1, weight 3.0
with combiner=”mean”, then the output will be a 3x20 matrix where
output[0, :] = (params[1, :] 2.0 + params[3, :] 0.5) / (2.0 + 0.5) output[1, :] = (params[0, :] 1.0) / 1.0 output[2, :] = (params[1, :] 3.0) / 3.0
示例:
1 | a = np.arange(8).reshape(2, 4) |
解释:1
2
3
4
5
6
7
8
9
10
11
12#idx = tf.SparseTensor(indices=[[0,0], [0,2], [1,0], [1, 1]], values=[1,2,2,0], dense_shape=(2,3))
构造如下稀疏向量矩阵,每一行视为一个样本的特征向量
[
[[1], [], [2]],
[[2], [0], []]
]
以第一个样本 [[1], [], [2]] 为例:选择id 1 和id 2:
# [[ 8 9 10 11] id 1
# [[16 17 18 19] id 2
根据 combiner="sum" ,把上面两个向量 按 axis=0 加到一起,得到:
# [24. 26. 28. 30.]
即 为第一个样本的 dense 向量
如果len(params) == 1, 就是不存在 partition 了,比较好理解,不再赘述。
喝最烈的果粒橙,钻最深的牛角尖。
end.