http://blog.csdn.net/jerr__y/article/details/77751885
关于比赛详情,请戳:2017 知乎看山杯机器学习挑战赛
代码:https://github.com/yongyehuang/zhihu-text-classification
基于:python 2.7, TensorFlow 1.2.1
任务描述:参赛者需要根据知乎给出的问题及话题标签的绑定关系的训练数据,训练出对未标注数据自动标注的模型。
标注数据中包含 300 万个问题,每个问题有 1 个或多个标签,共计1999 个标签。每个标签对应知乎上的一个「话题」,话题之间存在父子关系,并通过父子关系组织成一张有向无环图(DAG)。
由于涉及到用户隐私及数据安全等问题,本次比赛不提供问题、话题描述的原始文本,而是使用字符编号及切词后的词语编号来表示文本信息。同时,鉴于词向量技术在自然语言处理领域的广泛应用,比赛还提供字符级别的 embedding 向量和词语级别的 embedding 向量,这些 embedding 向量利用知乎上的海量文本语料,使用 google word2vec 训练得到。
简单来说,这是一个多标签文本分类问题,基本上都是使用深度学习方法。
结果:在public board 上排名第五,private board 上排名第六。
图1 public board | 图2 private board |
我是从7月1号开始报名比赛,一直到8月16日早上结束,一共一个半月时间,除了开始三周有些其他事情,后面差不多一个月时间基本上都在做比赛。之前也做过一个文本的比赛(命名实体识别+分类),但是之前积累不够,有没有找到合适的队友,所以那个比赛最后成绩很差,后期基本上就放弃了。这次还是一个人参赛(一个人打比赛好累…)这一个半月确实很累,经常写代码写到半夜,然后一大早又起来看模型跑的结果,发现出错了赶紧改过来;跟同学抢机器;各种想法没提升;有一次把验证集写成训练集来用结果过拟合白白高兴了…和我一个实验室的还有两组同学参加了,他们分别拿了第一和第二…唉唉,自己还是太菜了。不过在这次比赛中,确实学习了好多,这里写下比赛中的一些经验,希望对大家特别是对于竞赛入门者能有所帮助。下面的相关经验主要是和深度学习相关。
硬件准备
在这次比赛中,训练集有300万个问题;测试集有21.7万个问题。赛方提供了预训练好的词向量矩阵,一共有40+万个词。所以数据量还是非常大的,如果没有服务器,基本上没法做。我们组一共就一台服务器,一块显卡titan X,64G内存,CPU12进程。但是,我们有十几个同学呀,虽然平时主要就三四个人用,但是和其他大佬们多卡多机相比,这配置确实寒酸。跑一个模型几个小时到二三十个小时,所以,没有机器,根本别想取得好成绩。
此外,一定要准备充足的硬盘空间,最好能有500G, 1T或者更多。因为在这次比赛中,我就因为硬盘满了,只能把数据放在两个分区进行操作,可能是前期没有注意管理,导致后来文件组织非常混乱。
文件管理
所有的比赛都应该一样, 就是“不择手段”地达到更好的成绩,而要取得较好的成绩,不可避免地会用到集成学习,所以你不可能靠一两个模型就能够取得很好的结果。在这次比赛中最后获奖的所有队伍都用了模型融合,模型数量少的有十几个,多个两百多个,我最后提交的结果用了37个模型。所以从一开始,你就应该管理好模型和文件的命名,这样才不至于后期命名混乱,因为缺乏经验,所以我也在这上面吃了不少亏。
模型保存
因为词向量非常大,所以每个模型保存下来都非常大(1G+),这样就非常占空间。所以应该怎么保存模型也是有讲究的,我的经验大概如下:
- 设置一个最低 f1值(last_f1, 这次比赛评价函数是 f1,如果是准确率的话应该设置一个最低的准确率);每迭代 valid_step 步,就预测整个验证集,计算f1值,如果高于last_f1, 则保存模型。
- 每个网络保存最好的模型即可,也就是说设置 max_keep=1
具体代码可以参照这份代码中的 train_epoch() 函数。
为什么只保存最好的一个就好了?我尝试过保存最好的三个模型,假设为m1, m2, m3 然后把这三个模型以平均加权的方式融合得到 M,这样M的结果要比最好模型 m1 的结果好(在这次比赛中,能高2.5个千分点左右,这已经很好了),但是,但是,但是,在后面和其他结构的模型再次融合,M的效果不如直接使用 m1。我觉得可能是与 M 相比,对于每个样本的预测概率,m1 的方差更大些。比如对于样本a和b,m1预测分别属于类别1的概率是0.9 和 0.1; M预测分别属于类别1的概率是0.7和0.3。这种情况下和其他模型融合,m1的作用会更大一些,因为它对正样本的确定性很高,决定性更大。在其他数据或者比赛中,我觉得应该也是一样的。所以,只需要保存最好的一个模型,这样能节省不少硬盘空间。
使用 tensorboard
tensorboard 很好用,通过 tensorboard 查看损失变化能够很好地帮助我们进行调参,当然同时也可以看看模型结构有没有写错了… 但是有一点需要注意,如果你的模型很大,那么tensorboard中保存的数据也会非常大,所以还是很占空间的。
图3 网络结构
图4 损失变化
模型构造与调参
在这次比赛中,主要使用的基本模型有 TextCNN, BiGRU, HAN(Hierarchical Attention Networks ), FastText, RNN+CNN(区别与RCNN)。然后各种结构拼接,修改。比赛的目的很明确,就是取得更好的分数。所以我们的目的并不只是把原论文的结构套过来看看结果,而是要在给定的数据集上不断改进,不断调整参数。同样的一个模型,可能就是某个地方有一点小小的差别,或者学习率没调好结果就会差很多。这些都是需要自己不断积累,在实验过程中不断总结出来的。但是在大体上,有些问题还是需要注意的。
- 关于学习率和学习率衰减。学习率非常重要,一般都会使用自适应的优化器,比如 adam, 但是还是需要严谨地选择初始学习率。至于学习率衰减,TensorFlow 中提供了tf.train.exponential_decay()函数,这函数有个参数staircase,我的经验是一定要设为staircase=True,也就是迭代好多步以后再跳一次,而不是没迭代一次都调整学习率。你会在tensorboard中看到,学习率衰减的那一瞬间,loss都会迅速地下降一次。在TextCNN中特别明显。我看到有些人用adam优化器的时候并没有使用 decay,而是只提供了一个初始学习率,但是我在实验中发现,使用Exponential Decay效果明显要比没用好。而且decay_rate 和 decay_step 的选择也很关键。但是有些队伍根本就没做这个效果却非常好,所以现在我还是比较疑惑。之后弄清楚了再来补充。相关讨论可参考下面:
Tensorflow: Adam Optimizer with Exponential Decay 这里有讨论但是不是很明确。
Mixing learning rate decay and Adam, is it possible?
Should we do learningrate decay for adam optimizer 里边下面这个说法我比较认同。It depends. ADAM updates any parameter with an individual learning rate. This means that every parameter in the network have a specific learning rate associated.
But the single learning rate for parameter is computed using lambda (the initial learning rate) as upper limit. This means that every single learning rate can vary from 0 (no update) to lambda (maximum update).
The learning rates adapt themselves during train steps, it’s true, but if you want to be sure that every update step do not exceed lambda you can than lower lambda using exponential decay or whatever. It can help to reduce loss during the latest step of training, when the computed loss with the previously associated lambda parameter has stopped to decrease.
adam对于每个参数都会自动调整学习率,但是我们传进去的学习率是进行适应学习率计算的基础。所以加上decay还是会有作用的,不过具体问题具体尝试才能知道有没有用。
- 关于模型大小。这个其实很关键,首先要看数据量,像知乎的这次比赛数据量还是非常大的,所以模型也是应该比较大的。就这个数据集,相同结构,大一点的模型可能比小一点的模型更加节省时间。因为模型参数多了,迭代一次学习到的信息要比模型多,所以收敛的速度可能比小模型更加快。所以,在比赛初期,你可以先用小模型把一个结构跑通,然后开始可以尝试改进结构,加深模型,加宽模型。在这次比赛中,第三名的队伍他们的模型比我的基本大了5,6倍,他们最好的CNN模型训了一周,但是对于我来说这是绝对不允许的,因为就一台机器,如果一个模型训上一周意味着我都不用做了,万一效果还不好呢。所以,硬件非常非常关键。
- 关于 batch_size。一般来说,几十到几百的batch_size都是可以接受的,那么选择大batch_size 还是小的batch_size呢?我认为,在这么大的一个数据集下,尽量把GPU撑满是个不错的选择,但是也不能太大,最好别超过1000。batch_size 大些,收敛速度相对来说应该会快些。batch_size越大,那么梯度的方向相对会更贴合“全局梯度”,更加“准确”。这样的话,我觉得学习率也可以稍微调大一点。但是,因为有很多很多的局部最优点,我们不能把 batch_size 设得太大,这样每个batch 的梯度才会带有一些“噪声”来帮助我们跳出这些局部最优点。具体的大小当然还是要根据数据集,通过实验来选择,还有你的显存大小来选择。
- 因为赛方提供了预训练好的词向量和字向量,而且都是在更大的数据集上训练的,所以肯定会比自己训练的要好。但是,embedding 可以在迭代一定轮次(epoch)以后再进行fine-tune,这样有两个好处:一是embedding非常大,如果不对embedding计算梯度的话能减少不少计算,节省时间;二是一开始就是用大学习率优化embedding,容易把原本预训练学习到的信息丢失。在TensorFlow中要想先固定embedding,在一定轮次以后再优化的话,可以通过定义两个优化器来实现。可以参考这个代码中main()部分。对于知乎的这个数据集,这样做对于RNN效果比较好,但是对于TextCNN似乎帮助不大。
- 关于融合。一定要从一开始就为最后模型融合做好准备。模型融合的方法有很多,最常见的比如bagging, stacking 等。在这次比赛中,由于数据量太大,而且计算时间非常长,所以对于我来说,基本上没有办法做stacking,所以最后使用了线性加权。好而不同,这是模型融合最最关键的地方。所以BIGRU与TextCNN融合要比BIGRU与HAN效果好很多,因为TextCNN与BIGRU结构相差非常大,而HAN里边就是用了 BIGRU。所以在训练不同模型时,一定尽量增加模型之间的差异性。在这个比赛中,使用字向量训练的模型要比词向量训练的模型差很多(1个百分点以上),但是在模型融合中,一个好的字向量模型带来的提升非常大。
在这次比赛中,主要使用的模型结构如下:
其他
比赛结束后,学习其他组的参赛方案,确实有很多值得学习的地方。非常感谢知乎组织的这次比赛,最后他们还公布了所有获奖队伍的参赛方案,详细见:「2017 知乎 · 看山杯机器学习挑战赛」结束,谁获奖了?知乎还会做什么?
原文地址:https://www.cnblogs.com/DjangoBlog/p/8280387.html