欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

调研MLBox的EntityEmbedding并用PyTorch重构

程序员文章站 2024-03-15 19:55:12
...

什么是实体嵌入(Entity Embeddings)

在处理机器学习问题时,有一个问题必须思考:如何编码数据集中的类别变量?

类别编码的两个基本方法是独热编码(onehot encoding)和标签编码(label encoding)。

独热编码

直观来说就是有多少个状态就有多少比特,而且只有一个比特为1,其他全为0的一种码制。

标签编码

将类别变量表示为连续的数值型变量。

举个例子:

假如有三种颜色特征:红、黄、蓝。 我们需要对其进行向量化或者数字化编码。

那么令:红=1,黄=2,蓝=3,就实现了标签编码,即给不同类别以标签。然而这意味着机器可能会学习到“红<黄<蓝”,但这并不是我们的本意,我们只希望机器能区分它们,并无大小的区别。

这时就要引入独热编码。因为有三种颜色状态,所以就有3个比特。即红色:[1,0,0][1, 0, 0],黄色:[0,1,0][0, 1, 0],蓝色:[0,0,1][0, 0, 1] 。如此一来每两个向量之间的距离都是 2\sqrt{2},在向量空间距离都相等,所以这样不会出现偏序性,基本不会影响基于向量空间度量算法的效果。

但是独热编码变量会导致非常稀疏的向量,这在计算上是无效的,并且难以优化。

实体嵌入则很好的解决了独热编码和标签编码存在的问题。

实体嵌入

实体嵌入基本上将标签编码方法上升了一个层次,它不仅仅是将一个整数分配给一个类别,而是整个向量。这个向量可以是任意尺寸,通过一个神经网络来学习这个表达。

调研MLBox的EntityEmbedding并用PyTorch重构

嵌入提供了有关不同类别之间距离的信息。使用嵌入的优点在于,在神经网络的训练期间,也要训练分配给每个类别的向量。因此,在训练过程结束时,我们最终会得到一个代表每个类别的向量。这些训练过的嵌入被可视化,为每个类别提供可视化


用PyTorch重构MLBox的EntityEmbedding

计算相应的嵌入维度

根据每个categories的基数(n_uniques)计算相应的嵌入维度(embed_dims)

exp_ = np.exp(-n_uniques * 0.05)
self.embed_dims = (5 * (1 - exp_) + 1).astype("int64")

计算MLP隐层的神经元数

是通过AB两个参数计算得到的

sum_ = np.log(self.embed_dims).sum()
self.n_layer1 = min(1000,
                    int(A * (n_uniques.size ** 0.5) * sum_ + 1))
self.n_layer2 = int(self.n_layer1 / B) + 2

构造各个类别的线性Embed

TODO: 思考embedding的原理,能否在embedding中加隐层?

self.embeddings = nn.ModuleList([
    nn.Embedding(int(n_unique), int(embed_dim))
    for n_unique, embed_dim in zip(self.n_uniques, self.embed_dims)
])

构造中间两个隐层

self.layer1 = nn.Sequential(
    nn.Linear(self.embed_dims.sum(), self.n_layer1),
    nn.ReLU(inplace=True),
    nn.Dropout(self.dropout1)
)
self.layer2 = nn.Sequential(
    nn.Linear(self.n_layer1, self.n_layer2),
    nn.ReLU(inplace=True),
    nn.Dropout(self.dropout2)
)
self.dense = nn.Sequential(
    self.layer1,
    self.layer2,
)

构造最后的输出层

考虑是二分类还是多分类还是回归

# regression
if n_class == 1:
    self.output = nn.Linear(self.n_layer2, 1)
# binary classification
elif n_class == 2:
    self.output = nn.Sequential(
        nn.Linear(self.n_layer2, 1),
        nn.Sigmoid()
    )
# multi classification
elif n_class > 2:
    self.output = nn.Sequential(
        nn.Linear(self.n_layer2, n_class),
        nn.Softmax()
    )
else:
    raise ValueError(f"Invalid n_class : {n_class}")

权重初始化

for m in chain(self.dense.modules(), self.output.modules(), self.embeddings.modules()):
    if isinstance(m, nn.Linear):
        m.weight.data.normal_(0, 0.02)
        m.bias.data.zero_()

实验

代码: https://github.com/tqichun/entity_encoder

运行 entity_embedding_experiment.py

samples : 32725, features : 8
n_uniques(category cardinals) : [9, 16, 7, 15, 6, 5, 2, 42]

  • epoch=25 (默认值)

training time = 12.74s

roc_auc = 0.857, accuracy = 0.759633307868602

RF score : 0.8668601986249045
Embeds+RF score: 0.8668601986249045
OHE+LR score : 0.8361191749427044
Embeds+LR score: 0.8668601986249045

  • epoch = 50

roc_auc = 0.866, accuracy = 0.8283880825057296

RF score : 0.8668601986249045
Embeds+RF score: 0.8668601986249045
OHE+LR score : 0.8361191749427044
Embeds+LR score: 0.8668601986249045

epoch设为100,基本无变化

将epoch设为50,可能会增加最后的正确率,但是后期训练的主要是后面的隐层了,like word2vector,我们不要隐层只要embeds,所以将epoch设置的过高也是没有意义的。

而MLBox设的就是25,这个值是适中的,完全满足需求。

结论

结论:

  1. EntityEmbedding可以提升线性模型,如LR的表现。
  2. 这种表现的提升可能最多只能做到与树模型(RF)相当。
  3. 训练enbeds表示的epoch设置为25等适中值即可,后期是在微调后面的隐层。
  4. 对3w个样本训练25 epoch需要12秒,但这也是可以接收的,因为可以用缓存系统,在AutoML时只需要训练一次。
  5. Embeds+LR 比 Embeds+MLP(整个EntityEmbeddingNN) 效果好。
相关标签: automl