-
Notifications
You must be signed in to change notification settings - Fork 1.2k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
zida
committed
Nov 23, 2020
1 parent
033510e
commit 0d0874f
Showing
12 changed files
with
319 additions
and
1 deletion.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Binary file not shown.
Binary file not shown.
Binary file not shown.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,138 @@ | ||
## 0.背景 | ||
|
||
wide&deep 理论的介绍可以参考我之前写的那个文章。 | ||
|
||
WDL在实际应用的时候,有很多细节需要注意。 | ||
|
||
在我自己的应用中,对WDL模型做了一个简单的修改,加入了多模态(图片加标题)的特征,效果比单纯的xgboost要提升不少。 | ||
|
||
因为涉及到具体业务,所以不能详细展开。 | ||
|
||
不过我之前有读一个很不错的文章:,顺着这个文章的脉络,我们可以来看看WDL需要注意的地方。 | ||
|
||
全文思维导图: | ||
|
||
|
||
|
||
## 1. wide & deep 在贝壳推荐场景的实践 | ||
|
||
WDL应用场景是预测用户是否点击推荐的房源。 | ||
|
||
https://mp.weixin.qq.com/s/rp6H_HydTbKiSanijDZwBQ | ||
|
||
### 1.1 如何构建正负样本 | ||
|
||
首先,模型离不开样本,样本一般从日志中获取。一般是通过埋点,记录用户行为到日志,然后清洗日志,获得用户行为。 | ||
|
||
贝壳这里样本格式是三元组:`userid`,`itemid`和`label`; | ||
|
||
至于对应的特征,一般是需要根据`userid`,`itemid`到对应`hive`表格获取整合。 | ||
|
||
- 正样本:用户点击的房源 | ||
- 负样本:用户点击最大位置以上曝光未点击的房源;从未点击的用户部分曝光未点击的房源。 | ||
|
||
#### 样本构建细节整理 | ||
|
||
在这里,想要详细说一下正负样本构建的细节。 | ||
|
||
首先是对于日志的处理,需要区分`web`端和`app`端。不要增加无效的负样本 | ||
|
||
其次,**用户点击最大位置以上曝光未点击的房源**,这个方法其实叫做`Skip Above`,也就是过滤掉最后一次的点击。这样做我们是基于用户对之后的`item`是没有观测到的。 | ||
|
||
其次为了避免高度活跃用户的对loss的影响,在训练集中对每个用户提取相同数量的样本。 | ||
|
||
然后我们来想一下这个问题:**从未点击的用户部分曝光未点击的房源**。 | ||
|
||
首先,去除了这部分用户,会出现什么问题? | ||
|
||
模型学习到的只有活跃用户和有意向用户的行为习惯,这样线上和线下的数据分布会不一致。我们在线上的遇到的数据,肯定会出现那种不活跃用户。 | ||
|
||
如果不去除这部分用户,会出现什么情况? | ||
|
||
这样的用户在本质上是无效用户。为什么这么说呢?我们模型的作用是为了提升用户点击。 | ||
|
||
如果频繁给这个用户推荐物品,他一直不点击,也就是说没有正反馈。两种情况,一种是我们推荐的是有很大问题的,但是这种概率极低。还有一种情况,就是这个用户是有问题的。 | ||
|
||
所以简单来说,我们需不需要对这样的用户做为样本? | ||
|
||
很简单,做A/B测试,看是否需要这部分用户以及需要多少这部分用户作为样本。 | ||
|
||
还有一定需要注意的是,特征需要控制在样本时间之前,防止特征穿越。 | ||
|
||
### 1.2 如何控制样本不平衡 | ||
|
||
一般来说,负样本,也就是未点击的房源肯定是更多的。所以在训练模型的时候,肯定是存在样本不平衡的问题。 | ||
|
||
贝壳这里采用的是下采样负样本和对样本进行加权。 | ||
|
||
之前写个一个简单的文章,来讲述了一下如何缓解样本不平衡,可以参考这里: | ||
|
||
文章总结的结论就是,无论你使用什么技巧缓解类别不平衡,其实都只能让模型有略微的提升。最本质的操作还是增加标注数据。 | ||
|
||
就拿贝壳的操作来说,下采样和样本加权,本质上都修改了样本的分布情况。 | ||
|
||
就会出现训练样本分布和线上真实分布不一致的情况,那么你现在训练的模型究竟在线上真实环境能不能有好的提升,就看模型在真实数据上的评估情况了。 | ||
|
||
### 1.3 解决噪声样本 | ||
|
||
贝壳指的噪声样本指的是: | ||
|
||
> 在我们的业务场景下,用户在不同时间对同一房源可能会存在不同的行为,导致采集的样本中有正有负。 | ||
我自己的感受是很奇怪的是,只是猜测而已哈,样本特征中没有加入时间特征吗?加入时间特征应该可以学到用户短期兴趣变化。 | ||
|
||
### 1.4 特征处理: | ||
|
||
- 缺失值与异常值处理:常规操作;不同特征使用不同缺失值填补方法;异常值使用四分位; | ||
|
||
- 等频分桶处理:常规操作;比如价格,是一个长尾分布,这就导致大部分样本的特征值都集中在一个小的取值范围内,使得样本特征的区分度减小。 | ||
|
||
不过有意思的是,贝壳使用的是**不同地区的等频分布**,保证每个城市下特征分布均匀。 | ||
|
||
- 归一化:常规操作;效果得到显著提升; | ||
|
||
- 低频过滤:常规操作;对于离散特征,过于低频的归为一类; | ||
|
||
- embedding:常规操作;小区,商圈id做embedding; | ||
|
||
### 1.5 特征工程 | ||
|
||
预测目标是用户是否点击`itme`,所以特征就是从三方面:用户,`item`,交互特征; | ||
|
||
- 用户: | ||
|
||
> 注册时长、上一次访问距今时长等基础特征,最近3/7/15/30/90天活跃/浏览/关注/im数量等行为特征,以及画像偏好特征和转化率特征。 | ||
|
||
|
||
- 房源: | ||
|
||
> 价格、面积、居室、楼层等基础特征,是否地铁房/学区房/有电梯/近医院等二值特征,以及热度值/点击率等连续特征。 | ||
|
||
|
||
- 交叉: | ||
|
||
> 将画像偏好和房源的特征进行交叉,主要包含这几维度:价格、面积、居室、城区、商圈、小区、楼层级别的交叉。交叉值为用户对房源在该维度下的偏好值。 | ||
### 1.6 模型离线训练 | ||
|
||
- 数据切分:采用7天的数据作为训练集,1天的作为测试集 | ||
- embedding:尝试加入,没有获得很好的效果 | ||
- 模型调优: | ||
- 防止过拟合:加入dropOut 与 L2正则 | ||
- 加快收敛:引入了Batch Normalization | ||
- 保证训练稳定和收敛:尝试不同的learning rate(wide侧0.001,deep侧0.01效果较好)和batch_size(目前设置的2048) | ||
- 优化器:我们对比了SGD、Adam、Adagrad等学习器,最终选择了效果最好的Adagrad。 | ||
|
||
### 1.7 模型上线 | ||
|
||
- 模型部署:使用TensorFlow Serving,10ms解决120个请求 | ||
- 解决线上线下特征不一致:将离线特征处理的参数存储在redis中 | ||
- 效果提升: | ||
- CTR:提升6.08% | ||
- CVR::提升15.65% | ||
|
||
## 2. WDL代码实现 | ||
|
||
Github上有太多了,TF也有官方的实现,我就不多说了 |
File renamed without changes.
Binary file not shown.
Binary file not shown.
Binary file not shown.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,178 @@ | ||
没有标注数据不用怕,只用标签名称就可以文本分类!韩家炜组出品 | ||
|
||
对于实际的文本分类需求,没有标注数据是一件很常见的事情。 | ||
|
||
针对这种情况,有一个最朴素的思路可以做: | ||
|
||
1. 首先,根据对应的标签名称,使用W2C找到对应的相近词 | ||
2. 通过相近词,对文本数据做关键词命中,进而映射到对应的类别 | ||
3. 使用上述的标注数据训练文本分类模型 | ||
4. 使用3步骤的文本分类模型对新数据预测,获得置信度高的文本,之后做半监督。 | ||
|
||
上面这个思路,非常的简陋,最终的结果也不会很好。实际工作中,需要有大量的规则去补充。 | ||
|
||
今天分享的这个论文【Text Classification Using Label Names Only: A Language Model Self-Training Approach】,来自韩家炜组。 | ||
|
||
针对上面的场景,这个论文给出了一个思路。 | ||
|
||
整个论文读下来,先给个简单的评价,思路不错,但是也并不成熟,不过其中有很多细节点可以让我学习到。 | ||
|
||
# 1. 背景 | ||
|
||
人们在对一个文本分类的时候,不会看到任何带标签的标注数据,而只是通过一些关于描述分类类别的单词,就可以做出判断。 | ||
|
||
举个例子,人去对文本进行分类的时候,假如文本有一个类别属于`计算机`。脑海中其实是有先验知识,比如如果句子中出现`人工智能`,`深度学习`,`NLP`等词汇的时候,人们基于此可以很大概率的判断出当前这个文本是属于`计算机`这个类别。 | ||
|
||
随后呢,注意上面只是说的是很大的概率,还会出现`苹果`属于`科技`类别,但是不属于`水果`这个类别,也就是单单第一步还会出现语义歧义的错误,所以人们还会通读一遍句子,根据上下文语义再对句子分类。 | ||
|
||
作者类比这个思路,提出了三个步骤: | ||
|
||
(1)找到和标签名称语义相关性较高的词汇; | ||
|
||
(2)查找类别指示性单词并基于这些单词训练单词分类模型 | ||
|
||
(3)自训练提升模型 | ||
|
||
# 2. 步骤 | ||
|
||
## 2.1 Category Understanding via Label Name Replacement | ||
|
||
直译过来就是通过标签名称替换理解类别。 | ||
|
||
这句话直译过来的话可能不好理解,更好的表述是找到与标签名称语义相关性较高的词汇。 | ||
|
||
就像我们上面说的,人们在看到标签名称的时候,会联想到很多与之相关的词汇。 | ||
|
||
类别到NLP中,预训练模型其实就相当于模型的先验知识,可以从中知道标签名称的相近词汇。 | ||
|
||
我们知道,Bert 训练的时候是这样的:`mask`掉一部分词汇,然后通过语言模型预测`mask`部分的输出,计算损失函数; | ||
|
||
现在我当前输入是`人工智能`(为了方便理解,我们可以认为输入它是一个词作为整体输入),那么输出的时候其实是在整个词汇表上做`softmax`; | ||
|
||
基于此,从中挑选出概率最大的50个词汇,也就是当前这个位置最有可能出现的50个单词。 | ||
|
||
进一步的,因为包含`人工智能`这个词的肯定不只是一个句子,我们对每个句子中的`人工智能`做同样的操作,然后都获取对应的前50个词汇。 | ||
|
||
最后把这所有的50个词汇累积起来,按照频率从大到小,选取前100个词汇,作为可以替换`人工智能`这个标签名称的相近词汇。 | ||
|
||
这个思路,简单来说,就是从预训练模型Bert获取标签名称的近义词或者更准确的说是获取与标签相关词汇的过程。 | ||
|
||
其实,看到这里,我想到了一点,就是这个过程和我们使用`GLove`或者`Word2Vec`获取近似词的过程很相似。 | ||
|
||
只不过`Bert`是一个动态的权重,受上下文影响,所以获得结果更加的准确,泛化性也更强。 | ||
|
||
在论文,作者也做了实验论证这个道理。 | ||
|
||
这一步,我们得到的结果是类似这种: | ||
|
||
 | ||
|
||
简单总结一下: | ||
|
||
两个步骤: | ||
|
||
1. 找到每个句子中存在的标签名字,使用Bert预训练模型,找到与之最接近的50个单词。 | ||
2. 每个标签中所有单词汇总,按照频率,找出排在前100个单词,作为当前标签名称(`Label Name`)的类别词汇(`category vocabulary`/`category indicative words `) | ||
|
||
## 2.2 word-level classification via masked category prediction | ||
|
||
这个步骤,简单来说是使用Bert这类的预训练模型在单词这个级别训练分类模型。 | ||
|
||
上个步骤中,针对每个`Label Name`,我们会得到对应的`category vocabulary`/`category indicative words `。 | ||
|
||
这个时候一个最简单的办法,就是只要当前的句子出现了`category vocabulary`中的词汇,我们就认为当前的句子属于相对应的`Label Name`。 | ||
|
||
也就是我们开头说到的关键词命中规则。 | ||
|
||
但是这样做是有很大问题的。 | ||
|
||
首先,我们知道每个单词的词汇意义是与语境有关系的。一个句子出现`苹果`这样的单词,你很难武断的认为这个句子是属于`科技`还是`水果`。 | ||
|
||
其次,我们得到的每个`Label Name`的`category vocabulary`都是有数量限制的。有的单词其实也能表达当前`Label Name`的含义,但是并未包含在`category vocabulary`中。 | ||
|
||
为了缓解这两个问题,作者提出了`Masked Category Prediction (MCP)`任务; | ||
|
||
简单讲,它分为两个步骤: | ||
|
||
1. 针对句子中的每个单词,使用Bert这种预训练模型,找到与之最近接的前50个相关词汇(很类似第一大步骤的第一小步骤);然后将这50个相关和每个标签的`category vocabulary`进行比较,当交集超过20个时候,此时这个句子中的这个单词对应的类别就是对应的这个`Label Name` | ||
2. 句子经过第一个步骤之后,句子中的部分单词就有了类别。那么mask掉这些单词,然后Bert相对应的每一个单词尾端接一个分类器对当前单词做类别的分类。 | ||
|
||
整体流程,如下图: | ||
|
||
 | ||
|
||
## 2.3 self-training on unlabeled corpus for generalization | ||
|
||
经过第二个步骤,当前模型仍然存在问题: | ||
|
||
1. 有的句子没有被找到有类别的单词,所以这些没有被训练到 | ||
2. 训练到文本分类模型使用的是对应类别单词mask那里的输出,而不是cls。而ClS一般可以看到整个句子的全部信息。 | ||
|
||
针对这两个问题,作者提出使用全部的无标签数据,进行自训练。 | ||
|
||
这一块我自己知识积累的不多,就不多说了。具体的大家可以去看一下论文。 | ||
|
||
# 3. 模型架构总结 | ||
|
||
整体的算法流程如下图所示: | ||
|
||
 | ||
|
||
# 4. 实验结果分析 | ||
|
||
`Datasets`使用了四种:`AG News`;`DBPedia`;`IMDB`;`Amazon`; | ||
|
||
实验效果图如下: | ||
|
||
 | ||
|
||
`BERT w. simple match`情况是这样:句子只要含有标签名称的相近词,就认为当前句子是对应的标签类别,以此进行训练。 | ||
|
||
`LOTClass w/o. self train`是代表`LOTClass`只走前两步骤,不进行自训练。 | ||
|
||
从图中可以看到,如果不进行`sefl-training`,`LOTClass`效果在所有数据集上效果也都不错。 | ||
|
||
使用了`sefl-training`之后,`LOTClass` 可以和半监督和监督模型结果媲美。 | ||
|
||
## 4.1 细节1 | ||
|
||
有一个问题,`LOTClass`这种方法,相当于在使用Bert的情况下,标注了多少数据? | ||
|
||
作者做了一个实验图,如下图: | ||
|
||
 | ||
|
||
从效果图可以看到,`LOTClass`的效果和Bert在每个类别有48个标注数据的情况下训练的效果相当。 | ||
|
||
我大概算了一下,`AG News`有4个类别,每个类别48个,也就是总共192个标注样本。 | ||
|
||
## 4.2 细节2 | ||
|
||
这个论文我比较感兴趣的是第一个步骤,获取标签名称的相近词汇。 | ||
|
||
针对这个步骤,做两个方面的修改: | ||
|
||
1. 修改标签词汇:分别使用`commerce`;`economy`;`business`作为label name;标签的名称虽然变化了,但是每个标签名称得到的100个相近词汇表有一半是重复的,另一半的词汇表意思都很类似。 | ||
|
||
这说明,这个方法是有鲁棒性的。不会出现,标签名称换了一个相近的名字表示,而得到的词汇表出现了剧烈的抖动。 | ||
|
||
2. 分别使用300维度的`Glove`和`LOTClass`获取标签名称相近词汇,GLove得到的词汇非常的贫乏,而`LOTClass`效果很好,模型具有很好的泛化性; | ||
|
||
这个思路也给自己赞个思路,获取同义词或者近义词可以使用这种方法。 | ||
|
||
# 5. 简单总结 | ||
|
||
说一下自己学到的东西。 | ||
|
||
其实看到细节1的时候,`LOTClass`方法得到的模型表现相当于使用192个标注数据对Bert进行监督训练。 | ||
|
||
从这里来看,标注的成本并不大;不过,应该可以使用此方法为半监督积累数据。 | ||
|
||
这个方法还不成熟,不过里面有些思路可以积攒。 | ||
|
||
|
||
|
||
 | ||
|
||
|
||
|