深入理解embedding
背景: 在广告系统中,ctr、cvr预估模型训练时通常将样本转换成embedding输入网络进行训练,得到最终的预估值。embedding被称为sparse model、大模型等,是模型训练和预估中极其重要的一环。但关于embedding的含义和使用对于非科班人士比较晦涩,在这里对embedding的引入、含义、应用作系统性的介绍。
分类数据 及表示方法
分类数据:从有限候选集中选择一个或多个离散项作为输入特征,如电影集、单词集
在分类数据的表示上,最直接的方法使用id唯一表示候选集中的项,但在训练的时候会带来维度的不统一,以电影集为例,对于不同用户观看过的电影数量不同,导致特征维度不同。一个有效的方法是稀疏向量表示,类似one hot 编码。(另一种说法是将每个稀疏向量表示为数字向量,以便语义上相似的项(电影或单词)在向量空间中具有相似的距离)
ont hot编码:将离散的分类变量转换为稀疏的二进制向量表示,以便更好地在机器学习模型中使用。One-Hot编码将每个分类变量的取值映射为一个只有一个元素为1,其余元素为0的向量。向量的长度等于分类变量的取值个数。对于给定的样本,如果它属于某个分类变量的某个取值,那么该取值对应的位置为1,其他位置为0。
但one hot编码及衍生的离散变量表示仍存在显著的缺点:one hot vector维度太大,以及不同向量之间的距离刻画问题。大型稀疏向量直接导致网络层的节点数巨大,这意味着训练需要的有效数据量也要非常大才行,另一方面训练时的运算量也会成倍增加。 而向量中相邻的两个值并不见得有相近的语义关系,无法表示特征之间的相似性。
Embedding引入
embedding可以很好解决上述分类数据表示的问题。embedding将大型离散向量转换到具有语义相似性的低维向量空间中,即将高维、稠密的向量映射到低维、连续的向量空间中。如大小为999的词汇集,映射到8维的embedding,维度直接少了两个数量级。在embedding中,语义不同的向量表示通常距离很远,这样同时解决了维度、语义两个问题。
embedding是一个矩阵,每列表示item的向量表示,要获取单个item的稠密向量,就检索对应的列。参照tensorflow中的tf.nn.embedding_lookup函数。
在推荐算法中,embedding的获取当做网络训练的一部分,这种方法可以为特定系统定制良好的embedding。
应用
以tensorflow中api的使用来说明分类数据到模型输入embedding的过程
tf.feature_column.categorical_column_with_vocabulary_list
:用于创建分类特征列的方法。它用于将离散的分类特征转换为可以输入到 TensorFlow 模型中的特征列对象。
tf.feature_column.embedding_column
:用于将分类特征转换为嵌入(embedding)向量
具体过程如下(来自于chatgpt):
- 索引化特征列(IndexedCategoricalColumn):首先,将分类特征列转换为索引化特征列。索引化特征列将原始的离散取值映射为整数索引,以便后续处理。这样可以将离散的分类特征转化为数值型的索引表示。
- 嵌入表查找(Embedding Table Lookup):在训练过程中,
tf.feature_column.embedding_column
方法会创建一个嵌入表(embedding table),该表将每个离散取值映射为一个嵌入向量。嵌入表是一个可训练的参数,其维度由指定的嵌入维度决定。 - 嵌入向量提取(Embedding Vector Extraction):通过索引化特征列的取值在嵌入表中进行查找,得到相应的嵌入向量。每个索引对应于嵌入表中的一行,该行表示该索引对应的嵌入向量。
- 嵌入向量合并(Embedding Vector Aggregation):如果数据集中包含多个分类特征列,那么在最后的嵌入层中,这些嵌入向量将被合并为一个更大的嵌入向量或嵌入矩阵。这样可以将不同特征的嵌入向量进行组合,形成更丰富的特征表示。
import tensorflow as tf
# 定义数据集
data = {
'gender': ['male', 'female', 'male', 'female', 'male'],
'occupation': ['engineer', 'doctor', 'teacher', 'doctor', 'engineer']
}
# 创建分类特征列
gender_column = tf.feature_column.categorical_column_with_vocabulary_list(
key='gender',
vocabulary_list=['male', 'female']
)
occupation_column = tf.feature_column.categorical_column_with_vocabulary_list(
key='occupation',
vocabulary_list=['engineer', 'doctor', 'teacher']
)
# 创建嵌入特征列
embedded_gender_column = tf.feature_column.embedding_column(
categorical_column=gender_column,
dimension=3 # 指定嵌入维度
)
embedded_occupation_column = tf.feature_column.embedding_column(
categorical_column=occupation_column,
dimension=5 # 指定嵌入维度
)
# 将特征列转换为输入层
input_layer = tf.keras.layers.DenseFeatures([
embedded_gender_column,
embedded_occupation_column
])
# 构建模型,此处仅为示例
model = tf.keras.Sequential([
input_layer,
# 添加其他层...
])
# 编译和训练模型...
tf.feature_column.embedding_column
方法将离散特征 occupation
转换为嵌入特征。
嵌入表查找的过程可以简述如下:
- 首先,我们定义了一个嵌入矩阵,其中每一行对应一个离散特征的嵌入向量。在这个例子中,假设我们的嵌入矩阵是一个维度为 (4, 3) 的矩阵,表示有 4 个不同的职业(
'engineer'
、'teacher'
、'doctor'
、'artist'
)和每个职业对应的 3 维嵌入向量。 - 当模型输入包含离散特征
occupation
时,我们可以使用tf.nn.embedding_lookup
函数来进行嵌入表的查找。该函数接受两个参数:嵌入矩阵和要查找的索引。 - 对于每个样本中的
occupation
特征,我们将其整数索引作为输入传递给tf.nn.embedding_lookup
函数。函数将会在嵌入矩阵中查找对应索引的嵌入向量。 - 最后,
tf.nn.embedding_lookup
函数返回的是查找到的嵌入向量。这些嵌入向量可以作为模型的输入,用于表示离散特征的信息。
import tensorflow as tf
# 嵌入矩阵,维度为 (4, 3)
embedding_matrix = tf.Variable([[0.1, 0.2, 0.3],
[0.4, 0.5, 0.6],
[0.7, 0.8, 0.9],
[1.0, 1.1, 1.2]])
# 索引化特征列
occupation_column = tf.feature_column.categorical_column_with_vocabulary_list('occupation', vocabulary_list=['engineer', 'teacher', 'doctor', 'artist'])
indexed_occupation_column = tf.feature_column.indicator_column(occupation_column)
# 输入数据
inputs = {'occupation': [2, 0, 1]} # 假设有 3 个样本
# 查找嵌入向量
embedded_occupation = tf.nn.embedding_lookup(embedding_matrix, indexed_occupation_column)
embedded_occupation = tf.keras.layers.Flatten()(embedded_occupation) # 将嵌入向量展平
# 将嵌入向量作为模型的输入进行后续处理
# ...
# 在训练过程中,模型会根据损失函数对嵌入矩阵进行更新
# ...
refs:
深度学习中的embedding