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

自动文本摘要经典模型TextSum运行录(五):平均损失不下降

程序员文章站 2022-07-01 18:17:44
...

1 实验现象

1.1 模型将显存占满

在成功使用显卡运行Textsum模型后,首先发现模型耗用了显存10GB,而GPU利用率却只有25%左右。这是因为我们使用的数据集CNN的单篇篇幅较长,分批次训练时,将一批数据加载入显存,所以占用较高,实际上耗用的计算资源却不多。当然这也可能和模型的框架陈旧,效率较低有关。虽说如此,从训练速度上看,global step从原来的0.6提升到了2.5以上,速度提高了5倍,这证明显卡运行还是成功的。

1.2 平均损失不下降

最重要的实验现象是:Textsum上下波动较大,净下降很慢。这个现象在How to Run Text Summarization with TensorFlow中也提到了,但原文没能给出解决方法。这个问题和我们的数据集是紧密相关的,原模型的数据集是Gigaword,而我们的是CNN,所以可能超参需要调节。开始时我认为是学习率太大的问题,所以修改模型代码,封装入学习率进行预实验,每种学习率跑1k步,部分结果如下,按照学习率由大到小排列:

learning_rate=0.100

自动文本摘要经典模型TextSum运行录(五):平均损失不下降

learning_rate=0.095

自动文本摘要经典模型TextSum运行录(五):平均损失不下降

learning_rate=0.085

自动文本摘要经典模型TextSum运行录(五):平均损失不下降

learning_rate=0.080

自动文本摘要经典模型TextSum运行录(五):平均损失不下降

learning_rate=0.06

自动文本摘要经典模型TextSum运行录(五):平均损失不下降

为了抢GPU,上述实验做的比较潦草,大致上控制了变量唯一性,但看最后一幅图可以发现,那个横轴的步数怎么那么奇怪。另外还要注意第一幅图和最后一幅图的纵轴范围不一样,做的实在太潦草了,我反思:(。忽略这个问题,我们看以上的结果,并不是学习率越小越好,它们的smoothed线的大致特点都是上下波动,大的波动具有一定的周期性,且每次的波峰和波谷比上一个对应低些。最后我决定选取看起来最为平滑的0.095参数进行实验。在后来的实验中也印证了它的波动范围要比原有的0.150参数好些。

关于断点。 后来我明白了,Textsum模型是可以从断点加载的,那么它的步数会接着上次的实验,时间也会接着上次的。其实这并不太好,它对训练速度(Global step)的预估将不准确。另外我还尝试了用CPU在GPU模型的断点跑解码,结果是不行的。貌似断点中会保存设备信息,这里的修改我还没能实现。
自动文本摘要经典模型TextSum运行录(五):平均损失不下降
上图是我使用learning_rate=0.095跑了7个小时的模型。观察第10k步到第40k步可以发现,running_avg_loss一直在4.00到5.00之间波动,顶多很勉强地说波峰逐渐降低,有收敛到4.00的趋势。如果将smoothing值调到0.99可以看到这一段基本就是水平线。将局部放大(下图)可以看出模型的loss一直在毫无进展的上下波动。(注意tensorboard并不是将所有点都绘制进去,随着步数增加,它会合理缩放并抛弃一些细节)。在开始的时候,我很理想地预测了模型会在3小时27分于3w步处收敛,而事实很让人失望,到底是什么原因导致了呢?

2 尝试解决

2.1 解决显存占用问题

本来不打算解决这个问题,但是这样跑模型导致服务器利用率下降,学长向我提出意见。所以我查了一下tensorflow如何限制显存占用率,可以参考这篇博客:tensorflow中使用tf.ConfigProto()配置Session运行参数及GPU设备指定。文中已提到的方法我就不再说了,这里说说代码具体怎么改。

首先找到使用了Session的地方。实际上这还不够,代码中还有一种运行方式:tf.train.Supervisorprepare_or_wait_for_session()方法。这两种方式分别存在于seq2seq_attention.py中的_Eval()_Train()中,将它们修改为如下:

def _Train(model, data_batcher):
...
    config = tf.ConfigProto(allow_soft_placement=True)
    config.gpu_options.allow_growth = True
    config.gpu_options.per_process_gpu_memory_fraction = 0.5
    sess = sv.prepare_or_wait_for_session(config=config)
...
def _Eval(model, data_batcher, vocab=None):
...
    config = tf.ConfigProto(allow_soft_placement=True)
    config.gpu_options.allow_growth = True
    config.gpu_options.per_process_gpu_memory_fraction = 0.5
    sess = tf.Session(config=config)
...

重新编译运行。使用nvidia-smi命令查询GPU使用情况,可以看到效果:

+-----------------------------------------------------------------------------+
| NVIDIA-SMI 410.48                 Driver Version: 410.48                    |
|-------------------------------+----------------------+----------------------+
| GPU  Name        Persistence-M| Bus-Id        Disp.A | Volatile Uncorr. ECC |
| Fan  Temp  Perf  Pwr:Usage/Cap|         Memory-Usage | GPU-Util  Compute M. |
|===============================+======================+======================|
|   0  GeForce RTX 208...  Off  | 00000000:01:00.0  On |                  N/A |
| 43%   55C    P2   126W / 250W |   4571MiB / 10986MiB |     39%      Default |
+-------------------------------+----------------------+----------------------+

+-----------------------------------------------------------------------------+
| Processes:                                                       GPU Memory |
|  GPU       PID   Type   Process name                             Usage      |
|=============================================================================|
|    0      8234      G   /usr/bin/X                                    92MiB |
|    0      8857      G   /usr/bin/gnome-shell                          69MiB |
|    0     29488      C   ...intern/anaconda2/envs/tf_gpu/bin/python  4403MiB |
+-----------------------------------------------------------------------------+

2.2 解决损失不下降问题

我猜想模型陷入了局部最优,那我就应该调大学习率,让模型猛地一振荡,有可能会震荡到更好的点(当然也可能从全局最优反而振荡到了局部最优),只能死马当活马医。结果很令人惊喜,loss值猛然下降了1个多点:
自动文本摘要经典模型TextSum运行录(五):平均损失不下降
但惊喜过后发现,模型又开始在原地振荡。昨天在看博客的时候无意间看到了这篇:神经网络训练loss不下降原因集合。其中有一句话引起了我的注意:

train loss 趋于不变,test loss趋于不变,说明学习遇到瓶颈,需要减小学习率或批量数目;

虽然我只进行了训练而没有进行测试,但是减小学习率是真的有可能。刚刚增大了学习率,现在又开始降低学习率,我在怀疑自己是不是胡搞。结果同样令人惊喜:
自动文本摘要经典模型TextSum运行录(五):平均损失不下降
比起下降的起点,loss这回连续下降了2个点,飞一般地下降。还没等我高兴完,只见loss值又飞一般地涨了上去,和起点持平后开始下降。如下图所示:
自动文本摘要经典模型TextSum运行录(五):平均损失不下降
到现在,我开始有了想法。如果降低学习率,模型一定会下降,而随后模型又会反弹,那么可能是学习率与模型所在位置有关。而原模型的参数使得学习率的下降和模型位置不匹配。换句话说,是学习率下降的速度太慢了。看看学习率的统计图:
自动文本摘要经典模型TextSum运行录(五):平均损失不下降
可以发现,学习率的正常下降比起手动的下降几乎就是条水平线。经历了39.63k步,才从0.095下降到了0.09251,而且从训练时的图像看,它就是匀速的。那么我可以人为地减小学习率,每隔5k步在波谷处手动调低。你可以称它为手动 piecewise_constant,参考Tensorflow 中 learning rate decay 的奇技淫巧。但是是否可以自动调节学习率呢。重新阅读代码,发现在seq2seq_attention_model.py中有如下定义:

self._lr_rate = tf.maximum(
        hps.min_lr,  # min_lr_rate.
        tf.train.exponential_decay(hps.lr, self.global_step, 30000, 0.98)
)

参考这篇博客:Tensorflow中tf.train.exponential_decay函数。原来模型学习率下降是指数衰减的啊,那么应该是参数不合适导致下降太慢,指数曲线和直线似的。其中30000是decay_steps,0.98是decay_rate。函数的几个参数的关系是:
decayed.lr=lr×decay.rateglobal.stepdecay.steps decayed.lr = lr \times decay.rate ^ {\frac{global.step}{decay.steps}}

我们需要将decay_ratedecay_steps都调小一些,下面就是修改代码并反复实验的时候了。修改seq2seq_attention.py

...
tf.app.flags.DEFINE_float('decay_rate', 0.98, 'rate for learning rate decay') # 新加入的参数
tf.app.flags.DEFINE_integer('decay_steps', 30000, 'steps for learning rate decay') # 新加入的参数
...
hps = seq2seq_attention_model.HParams(
      mode=FLAGS.mode,
      min_lr=0.01,
      lr=FLAGS.learning_rate,
      decay_rate=FLAGS.decay_rate, # 新加入的参数
      decay_steps=FLAGS.decay_steps, # 新加入的参数
      batch_size=batch_size,
      enc_layers=4,
      enc_timesteps=120,
      dec_timesteps=30,
      min_input_len=2,
      num_hidden=256,
      emb_dim=128,
      max_grad_norm=2,
      num_softmax_samples=4096)
...

修改seq2seq_attention_model.py

HParams = namedtuple('HParams',
    'mode, min_lr, lr, batch_size, '
    'enc_layers, enc_timesteps, dec_timesteps, '
    'min_input_len, num_hidden, emb_dim, max_grad_norm, '
    'decay_rate, decay_steps, ' # 新加入参数
    'num_softmax_samples')
...
self._lr_rate = tf.maximum(
    hps.min_lr,
    tf.train.exponential_decay(hps.lr, self.global_step, hps.decay_steps, hps.decay_rate))
...

learning_rate=0.15 decay_rate=0.7 decay_steps=1000

自动文本摘要经典模型TextSum运行录(五):平均损失不下降
这次实验的学习率统计图忘了截了,这次失败主要是学习率下降太快了,到结束时学习率已经下降到0.02左右了,显然模型再次下降的动力不足,不可能再收敛了。

learning_rate=0.15 decay_rate=0.85 decay_steps=2000

自动文本摘要经典模型TextSum运行录(五):平均损失不下降自动文本摘要经典模型TextSum运行录(五):平均损失不下降
在以上的基础上继续实验,人工调小学习率到0.08(不知道为什么实际上并不是0.08),实验也继续失败,最后模型仍然跑飞了。我认为是一开始模型学习率下降稍慢,后来学习率下降太快。

自动文本摘要经典模型TextSum运行录(五):平均损失不下降
自动文本摘要经典模型TextSum运行录(五):平均损失不下降
下面我得到了我所有实验中最好的结果,但是因为后来将Batch_size从4改成了32,模型严重回弹,最后惨痛失败,但这也让我看到了另一个问题,即模型反弹的问题,所以再多写一节。
自动文本摘要经典模型TextSum运行录(五):平均损失不下降
它的学习率调参情况对应如下:
自动文本摘要经典模型TextSum运行录(五):平均损失不下降

2.3 解决模型收敛后反弹问题

通过以上的实验我发现,模型在接近收敛,至少是loss值降到很低的时候,会出现反弹,甚至是严重的反弹。这究竟是为什么呢?其实对于超参,我少考虑了一个重要的参数,就是batch_size。我检查了一下代码中设置的batch_size,作者并没有将它封装出来,而且它的值竟然只有4。之前我看过的模型大多在64左右。我猜想,这是因为模型原始的数据集Gigaword的数据更加工整,不同数据的的区分不大。另外还有之前我发现过的处理数据时我的分词和分句方法导致了很大的噪声。这些原因加在一起可能导致我使用较小的batch时,每个batch内效果很好但batch之间迁移的时候模型适应的不好。那么我应该调大batch_size。

这个猜想还需要得到大量实验的证实。我预想在开始训练的时候就将batch_size设到32左右,在收敛后变为48。模型的学习率调参参考上次的最好结果。如果模型能够收敛,后期还考虑修改数据处理脚本,使得数据更加工整一些。

另外,我的同事提示我,模型在反弹之前可以保存一下断点,虽然没有很好收敛,也可以看一下输出结果如何。

--learning_rate=0.55     
--decay_rate=0.8     
--decay_steps=2000     
 
--learning_rate=0.15     
--decay_rate=0.8     
--decay_steps=10000     
 
--learning_rate=0.10     
--decay_rate=0.8     
--decay_steps=10000     
 
--learning_rate=0.06     
--decay_rate=0.8     
--decay_steps=80000     
 
--learning_rate=0.05     
--decay_rate=0.8     
--decay_steps=3000     
 
--learning_rate=0.01     
--decay_rate=0.8     
--decay_steps=3000