调研MLBox的EntityEmbedding并用PyTorch重构
文章目录
什么是实体嵌入(Entity Embeddings)
在处理机器学习问题时,有一个问题必须思考:如何编码数据集中的类别变量?
类别编码的两个基本方法是独热编码(onehot encoding)和标签编码(label encoding)。
独热编码
直观来说就是有多少个状态就有多少比特,而且只有一个比特为1,其他全为0的一种码制。
标签编码
将类别变量表示为连续的数值型变量。
举个例子:
假如有三种颜色特征:红、黄、蓝。 我们需要对其进行向量化或者数字化编码。
那么令:红=1,黄=2,蓝=3,就实现了标签编码,即给不同类别以标签。然而这意味着机器可能会学习到“红<黄<蓝”,但这并不是我们的本意,我们只希望机器能区分它们,并无大小的区别。
这时就要引入独热编码。因为有三种颜色状态,所以就有3个比特。即红色:,黄色:,蓝色: 。如此一来每两个向量之间的距离都是 ,在向量空间距离都相等,所以这样不会出现偏序性,基本不会影响基于向量空间度量算法的效果。
但是独热编码变量会导致非常稀疏的向量,这在计算上是无效的,并且难以优化。
实体嵌入则很好的解决了独热编码和标签编码存在的问题。
实体嵌入
实体嵌入基本上将标签编码方法上升了一个层次,它不仅仅是将一个整数分配给一个类别,而是整个向量。这个向量可以是任意尺寸,通过一个神经网络来学习这个表达。
嵌入提供了有关不同类别之间距离的信息。使用嵌入的优点在于,在神经网络的训练期间,也要训练分配给每个类别的向量。因此,在训练过程结束时,我们最终会得到一个代表每个类别的向量。这些训练过的嵌入被可视化,为每个类别提供可视化
用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,这个值是适中的,完全满足需求。
结论
结论:
- EntityEmbedding可以提升线性模型,如LR的表现。
- 这种表现的提升可能最多只能做到与树模型(RF)相当。
- 训练enbeds表示的epoch设置为25等适中值即可,后期是在微调后面的隐层。
- 对3w个样本训练25 epoch需要12秒,但这也是可以接收的,因为可以用缓存系统,在AutoML时只需要训练一次。
- Embeds+LR 比 Embeds+MLP(整个EntityEmbeddingNN) 效果好。
上一篇: Titanic数据集:仅用名字列就取得0.8的正确率
下一篇: 广告点击率平滑