使用Keras学习分类数据的嵌入
传统上,分类数据已被编码为2种常见方式:
一个标签编码器,其中每个唯一类别被分配一个唯一的标签。
one hot 编码,其中分类变量被分解为与该特征的唯一数量的类别相同的特征,并且对于每一行,为表示该行的类别的特征分配1,并且其余特征标记为0。
嵌入学习将每个唯一类别映射到N维向量实数。这种方法在Kaggle比赛中使用,并以相对简单的方法获得三等奖,并在Jeremy Howard的Fast.ai课程中得到推广。
使用嵌入的优点是我们可以确定表示分类特征的维度的数量,而不是在one-hot-embedding 中,我们需要将特征分解为该分类特征的唯一值。
也像单词向量一样,实体嵌入可以被期望学习类别的内在属性并将类似的类别组合在一起。
我们要做的是为每个分类列学习一组权重,这些权重将用于获取该列某些值的嵌入。所以我们为数据集中的每个分类列定义一个模型:
for categoical_var in categoical_vars :
model = Sequential()
no_of_unique_cat = df_train[categorical_var].nunique()
embedding_size = min(np.ceil((no_of_unique_cat)/2), 50 )
embedding_size = int(embedding_size)
vocab = no_of_unique_cat+1
model.add( Embedding(vocab ,embedding_size, input_length = 1 ))
model.add(Reshape(target_shape=(embedding_size,)))
models.append( model )
在上面的代码中,对于数据集中存在的每个分类变量,我们定义了一个嵌入模型。嵌入大小根据Fast.ai课程中给出的规则设置。我们将模型输出重新调整为a single 1-D array of size = embedding size
对于其他非分类数据列。我们只需将它发送给我们就像我们为任何常规网络所做的那样。但由于上述网络是分别处理每个分类数据的,因此我们为其他列定义另一个网络并将它们添加到我们的模型列表中。
model_rest = Sequential()
model_rest.add(Dense(16,input_dim = 1))
models.append(model_rest)
合并模型:
一旦我们有这些(n_cat + 1)不同的模型,我们将它们一起附加在一起使用
full_model.add(Merge(models, mode='concat'))
Keras v2.x开始不再允许在顺序API上使用合并模型,但是我发现使用这个更容易理解。这种concat模式所做的是一个接一个地连接模型。
有关合并模型的一些信息:
如果我们尝试使用sum模式的合并模型:
下面的消息对于理解使用该sum模式进行合并时发生的情况很有帮助。添加模式会在逐个添加元素的同时进行元素concat添加
Code:
full_model = Sequential()
full_model.add(Merge(models, mode=’sum’))
O/p:
ValueError:
ValueError:只能使用求和模式合并具有相同输出形状的图层。图层形状:[(None,2),(None,1),(None,16)]
这里要添加的3个向量的长度为2,1,16,因此无法添加模式连接。
其他可用的模式是dot,mul它们分别执行dot product和multiplies 模型输出。
concat它在一个阵列中将输出一个接一个地追加到模式中。因此,从full_model网络到现在的输出的最终长度将是e1+e2+e3+...e(last category)+ 16 (the number of outputs for the dense layer in model_rest model where `e` are the embedding sizes for the models.
合并网络的输入格式:
我们将传递一个输入列表,除最后一个列表外,每个列表都将拥有关于批次中所有行的单个分类列的信息,而另一个列表将具有所有其他连续列的值。
如果有N列的n_cat编号列作为分类变量,n列其他变量列和其他数据列的M列实例,则输入如下:
输入将是长度(n_cat + 1),即(类别总数+1)。每个输入的(1到n_cat)值都是一个大小为M(实例数)的列表本身,列表'i'的'm-th'值将等于第i列值其中`i`从1到n_cat的第m个数据实例。
另一个最后的列表将是一个大小为M的列表,并且每个值本身都将再次成为一个列表,其中的值由剩余的其他列组成。即对于最后的列表,第m个值将是大小为n_other的列表,并且将具有来自数据中第m列的值。
第一个n_cat列表向每个类别的嵌入网络发送输入,最后一个列表作为处理所有其他列的最终网络的输入。
模型需要的输入可以从错误消息本身中找到。
...检查模型输入时出错:传递给模型的Numpy数组列表不是模型预期的大小。预计会看到3个(这是n-cat + 1)阵列,但取而代之的是....
请参阅以下数据集和相应的输入形状以获得更好的想法:
该数据集有15行(M = 15),2个分类列(n_cat = 2)和2个连续列。
相应的输入是长度(n_cat +1)= 3,每个都是一个列表
元素1和2是一维列表。列表1有第一个分类列的15个值,列表2有第二个分类列的15个值。最后一个列表是一个二维列表,它有15个元素,每个元素有2个值(2个连续列的值)。
请记住,对于我们设置的每个嵌入网络,input-size = 1,我们从所有列表(除最后一个列表)中分别取1个值并将其发送到组合网络进行训练。对于最后一个列表,每个值本身都是一个包含其他列值的列表,并将其发送到models_rest网络。
训练网络:
因此,一旦我们将各个模型合并为一个完整模型,我们可以在网络上添加图层并对其进行训练。
full_model.add(Dense(1024))
full_model.add(Activation('relu'))
full_model.add(Dense(256))
full_model.add(Activation('sigmoid'))
full_model.add(Dense(2))
full_model.add(Activation('sigmoid'))
full_model.compile(loss='binary_crossentropy', optimizer='adam')
full_model.fit( data, values )
实体嵌入看起来是一种很好且简单的方法,可以直接将数据准备好输入神经网络,而不涉及特征工程。