工作半年以来,大部分时间都在做RNN的研究,尤其是通过lstm(long-short term memory)构建识别模型。我专注的是使用rnnlib工具开展模型的训练工作,以搭建有效的识别模型。Rnnlib(http://sourceforge.net/projects/rnnl/)由Alex Graves提供,是解决序列识别问题的RNN工具包,尤其是对隐含层提供了lstm的算法实现。在Alex Graves的博士论文中(Supervised Sequence Labelling with Recurrent Neural Networks),他提出了CTC(Connectionisttemporal classification)算法,克服了传统识别算法中序列数据需要预先分割的缺陷。在rnnlib中,CTC也得到了实现。
当识别问题的类别非常多时,隐含层到输出层的连接边数目大幅增加,这也使得反向更新权重时需要耗费更多的时间。实际上,这是一个计算密集型的任务。典型地,在我们的训练中,训练样本为十万级别的规模。在双CPU--Xeon(R) CPU E5(2.60GHz,8核16线程)、x64的服务器平台下,一次迭代需要花费18h左右的时间。而这里训练一个模型需要至少100多次的迭代,因此这个训练效率是难以忍受的。实际上,在rnnlib源代码中,作者实现的是一个单线程版本。于是,我开始了漫长的并行编程实战,通过将rnnlib并行化以提高计算的效率。在这里,我将摸索的过程进行了总结。
首先对模型训练的过程进行一下简单的描述。每次迭代都是一样的过程,根据所有训练样本前向计算输出、CTC计算误差项、反向更新权重。在每次迭代中,可以采取一种近似于随机梯度SGD的方式,也就是设置一个batchsize,每批batchsize个样本更新一次模型,而不是对所有样本计算一次更新。
1. 多线程
1.1 多线程框架
在这里,我们写好了多线程的接口框架,使用了线程池的方式。主线程不停读取任务并保存在线程池的任务列表中。线程池中的线程不断从任务列表中读出任务并执行。在读出任务时,需要使用锁实现互斥访问,以保证安全性。此外,主线程存储任务与其他线程读出任务也需要通过锁实现任务列表访问的互斥性。当任务列表中的任务全部读出后,通过条件变量使线程陷入等待,而新存储的任务会通过notify信号量重新激活线程。
当ba