‘小来早晚安’ 是来也科技旗下最为火爆的公众号(累计用户几千万 ,日活几百万),由小来无限团队独立运营。
每当用户说完早晚安后,公众号都会推送给用户一张精美的打卡图。


图片


为了更好的服务用户,我们用算法计算客户的喜好,为每个人推送个性化打卡图。个性化的关键特征之一就是 -- 用户性别。

以前,用户关注公众号时,我们可以通过微信的接口获取到注册性别。但现在,随着国家数据安全法的实施,微信会把性别当做敏感信息屏蔽掉。

于是,我们选择利用可获得信息进行性别预测,克服千难万阻也要给用户推送让他欢喜的图片~
一. 导读
已知我们当前可利用的数据有:(模糊不清的)微信头像,微信昵称。
头像数据( 132 * 132 像素)
图片
昵称数据
通仔 珍妮 涵

我们利用这两类数据作为输入,预测出客户性别。一旦发现某一类数据有较明显的性别特征,就增加其权重,以提高预测准确性。

二. Baseline 模型 -- Fasttext
Fasttext 属于一个非常简单的浅层神经网络,和 Word2Vec 中的 CBOW 模式很像,输入的自然语言通过 N-Gram 特征工程变成 Token,接下来 Token 通过 Embedding 查表得到对应的 Token 向量,所有 Token 的向量取平均进入全连接网络和分类网络得到结果,模型架构如下图:
图片

在性别预测中,最终目标为二分类(男、女)。只用户昵称作为输入,利用1500万数据进行训练,模型的准确率可达到 74% 。

三. 多模态模型
在 baseline 中,我们没有输入用户头像。不可否认,很多用户头像清晰地展现了性别特征,应该作为我们的重要输入。

对此,朴素的想法就是融入多模态特征 m,图像和昵称一起作为模式输入,基于这个思路,我们设计了第一版模型:
1.多模态 baseline 模型
在 baseline 模型中:


  1. 头像图像经过 CNN 抽取得到图像特征
  2. 昵称文本数据经过 transformer encoder 得到文本特征
  3. 文本特征和图像特征进行concat进行全连接得到预测结果



模型架构如图:
图片
其中 CNN 经过思考选择了 DenseNet121,将 DenseNet121 的 “avg_pool” 层的输出通过全连接非线性变换降低到 256 维作为图像特征。DenseNet 的核心思想是将 CNN 前后的特征进行密集 concat,得到高层抽象特征的同时又能保证像素级别的特征。

下图就是一个 DenseNet 的 block 的示意图,可以清楚看到每一次卷积的输出都会 concat 到后边卷积层的输出上:


其中 Transformer 采用标准的四层 TransformerEncoderLayer 堆叠。

最大字符长度32位,head_num选择4,字符的 embedding 的维度选择128,最终将第 4 层 layer 的输出特征 32X128 经过在 32 个 step 上取平均得到 128 维的向量。

再经过非线性变化得到256维的向量作为昵称的文本编码特征。

Transformer 很大的优势是通过MultiHeadSelfAttention 可以捕获长距离字符间的相关性。

MultiHeadSelfAttention 示意图:

图片

昵称可以直接采用字符作为 token 输入。

虽然是字符,但是由于昵称中掺杂了大量的“火星文”。为了兼顾计算效率和效果,我们本次只采用头部字符作为词表。

但在特征工程统计后发现:emoji 表情有很明显性别相关性。比如:????,女性用的比例要比男性高的多,所以我们找了全部的 emoji 表情加入了字符表,避免 Emoji 字符 OOV。

采用 300 万数据做训练、10 万数据做训练,但是多模态 baseline 模型的准确率只有 67% ,比只利用昵称的 Fasttext 效果还差。

于是我们仔细分析图片数据,进入了第二个版本的模型优化。

2.基于Attention融合的多模态模型
人工分析了 300 张数据,发现一些规律。正是这些数据的存在会导致模型收敛不好:


  • 有 80 条数据无法通过头像和昵称区别性别。如下三张图片昵称分别为:青春永驻、春夏秋冬、随缘:


图片




  • 存在偏中性的图像或昵称。比如图像偏中性,但是可以通过昵称区分性别。如以下两张头像的昵称分别为:X小宝、X国强。能比较容易猜测后者为男性。






上述的第 2 种情况,两张图片经过 CNN 之后图像特征基本类似。但是对于“X国强”这个昵称来说,我们希望图像特征被弱化,昵称文本特征得到加权。

基于这个思路,我们采用了视觉和文本特征的 Attention 机制进行融合,而不是 baseline 中基本的 concat,这个时候模型的架构如下:


其中Attention单元的架构如下:
图片

头像图像特征、文本昵称特征、头像和昵称特征逐位作差,三个向量Concat后得到768维的向量,再经过全连接网络和非线性变换后得到 2维的激活权重,代码如下:


def _attention(self, img_feature, name_semantics):    features = [img_feature, name_semantics, img_feature - name_semantics]    features = tf.concat(features, axis=-1)    f = self.att_dense1(features)    weight = self.att_dense2(f)    weight = tf.reshape(weight, [-1, 1, 2])    concat_feature = tf.concat([img_feature, name_semantics], axis=-1)    feature = tf.reshape(concat_feature, [-1, 2, self.feature_norm_dim])    self.log.info("concat feature shape is (%s)", feature.shape)    feature = tf.matmul(weight, feature)    self.log.info("after attention feature shape is (%s)", feature.shape)    feature = tf.reshape(feature, [-1, self.feature_norm_dim])    return feature

在加入 attention 后预测的准确率达到 83%,采用 ImageNet 的预训练权重作为 CNN 的初始权重,最终的准确率可以达到了 85% 。

3.基于MaskLoss的优化
在上一小节提到,其中有约25%的数据无法通过头像以及昵称区分性别,所以在模型训练最终阶段 loss 抖动会比较明显。

仔细分析后发现这些数据 Sigmoid 的概率区分不大,基本分布在 [0.45,0.65] 之间,这个时候一种方法是人工将预测置信度比较低的数据再次筛选一遍,考虑到数据量巨大而且存在部分性别确实无法预测的可能(对模型的准确率要求不是过高),所以采用了 mask loss 的办法,在模型训练后期将 sigmoid 置信度在一个区间的 loss 直接扔掉,代码如下:


'''     将sigmoid低于阈值的idx mask掉     eg:threshold=0.8  需要将prob>0.8 和prob<0.2 的保留     probs=[0.81,0.7,0.4,0.19,0.3,0.7]     的得到mask=[1,0,0,1,0,0]'''def get_mask(self, probs, threshold):    probs = tf.reshape(probs, [-1])    mask1 = tf.math.less(probs, 1 - threshold)    mask2 = tf.math.greater(probs, threshold)    return tf.math.logical_or(mask1, mask2)

def mask_loss(self, loss, probs): mask = self.get_mask(probs, self.mask_loss_thres) mask = tf.cast(mask, loss.dtype) recall = tf.reduce_mean(mask) loss = loss * mask ''' 扔掉了一部分数据的loss,所以需要将剩下的loss放大 防止模型将结果全部预测到这个区间 ''' loss = tf.math.divide_no_nan(loss, recall) return loss

其中在对 loss 进行 mask 时,需要注意:mask 部分样本的 loss 后,需要将剩下的 loss 根据 recall 的数量等比放大。不然深度学习模型会取巧,会将易于区分的样本也预测成中间置信度。这样的预测结果会因为 mask loss 的原因使 loss 等于 0。

在这个策略下,模型的准确率可达到87%

基于前边的数据分析:有80张图片无法通过头像和昵称区分性别,所以模型预测的准确率上限在 (220+80/2)/300=86.7%。模型的准确率和上限基本一致(已经很准确了)。

四. 其他优化策略
1.CBAM Attention机制
CBAM 是视觉任务上非常优秀的一个注意力机制,既考虑了通道上的注意力机制又考虑了空间上的注意力机制,而且输入和输出的特征维度完全一致可以即插即用。

在常见的计算机任务中经过CBAM的特征处理后,基本上都能获得不错的效果提升。

CBAM原理如图:

图片
图片


我们尝试在 DenseNet 的每一个 Dense Block 输出的特征插入 CBAM,但准确率并没有提升。

猜测是图像只是二分类,和 attention 的细粒度特征关系不大。

2.梯度中心化
梯度中心化是阿里达摩院提出的一种优化器策略,通过简单的将梯度向量中心化为零均值,从而提高模型训练的稳定性和提高最终的结果,使用也非常简单,只需要在优化器中加入很少的代码即可,比如以下是对 AdamW 进行梯度中心化的代码:


class GCAdaW(tfa.optimizers.AdamW):    def get_gradients(self, loss, params):        grads = []        gradients = super().get_gradients()        for grad in gradients:            grad_len = len(grad.shape)            if grad_len > 1:                axis = list(range(grad_len - 1))                grad -= tf.reduce_mean(grad, axis=axis, keep_dims=True)            grads.append(grad)        return grads

使用梯度中心化策略后,准确率提升约 0.2%

3.手工特征
此外,经过对昵称的特征分析后,发现女性用 emoji 的比例偏高,所以又手动设计了一些昵称特征:
[昵称长度,emoj数量,数字数量,字母数量]

该四维向量除以 32(最大昵称长度)作为特征,直接和头像昵称的网络特征拼接输入到全连接网络。加入手工特征后,模型准确率没有提升,说明 Transformer 已经很好的捕获这类特征关系。

五. 部署和工程优化
最终训练好的模型导出 Protobuf,通过 CPU 版本的 Tensorflow Serving 提供 Restful API 供业务方调用。

性能说明:在 8 核 2.3GHz 的 I9 Macbook 上,单次请求的平均耗时在 85ms。

为了提高预测速度,做性能优化如下:将 CNN 的 DenseNet121 替换为 MobileNet V2,准确率降低 0.8% 但是耗时降低到了 25ms。

六.参考文献


  1. https://arxiv.org/pdf/1607.01759.pdf

  2. https://arxiv.org/pdf/1607.01759.pdf

  3. https://arxiv.org/pdf/1706.03762.pdf

  4. https://arxiv.org/pdf/1807.06521.pdf

  5. https://arxiv.org/pdf/2004.01461.pdf

  6. https://arxiv.org/pdf/1711.05101.pdf

  7. https://arxiv.org/pdf/1801.04381.pdf





本文作者:摄影师王同学
本文编辑:刘桐烔