四、LDA优化
标准
LDA
的模型训练过程中,对于文档(令
,采用基于位置的更新) :
常规的采样方式是:
- 计算
- 将采样空间划分为:第一段
, 第二段
,… 第
段
,称作分桶。
- 在
之间均匀采样一个点,该点落在哪个桶内则返回对应桶的主题。
- 计算
常规
LDA
采样算法:- 在
之间均匀采样一个点,假设为
。其算法复杂度为
,因为需要计算
。
- 查找下标
,使得满足
,返回
。通过二分查找,其算法复杂度为
。
- 在
事实上如果执行非常多次采样,则常规
LDA
采样算法第一步的成本可以分摊到每一次采样。因此整体算法复杂度可以下降到。如果仅执行一次采样,则整体算法复杂度为
。
与
有关,而
在文档
的不同位置
取值不同。这意味着每一次采样的概率分布都不同,因此采样每个主题的算法复杂度为
。
有三种算法对采样过程进行了加速:
SparseLDA
和AliasLDA
使用基于位置的采样,这是为了方便对采样概率进行有效分解。LightLDA
使用基于单词的采样,这种采样更能够满足LDA
的假设:LDA
假设同一篇文档中同一个单词都由同一个主题生成。
4.1 SparseLDA
sparseLDA
推断比传统LDA
快大约20倍,且内存消耗更少。
4.1.1 概率分解
SparseLDA
主要利用了LDA
的稀疏性。事实上,真实场景下的一篇文档只会包含少数若干个主题,一个词也是参与到少数几个主题中。因此可以将采样公式分解(令,采用基于位置的更新):
令:
其中:
仅仅和主题的单词统计有关;
和文档
的主题统计、主题的单词统计都有关;
和文档
的主题统计、主题的单词统计、文档
的单词统计都有关。
假设
为常数,即:主题的所有单词的先验频数都为常数
。现在仅考虑一篇文档的训练,因此忽略角标
,有:
令
,原始
LDA
的采样方式为:- 将
划分为
个桶,将每个桶根据其
分解继续划分为三个子桶。
- 根据从均匀分布
中采样一个点。该点落在那个桶,则采样该桶对应的主题。
假设该点落在桶
,则有三种情况:
- 若该点落在
r
对应的子桶,其概率为。
- 若该点落在
s
对应的子桶,其概率为。
- 若该点落在
q
对应的子桶,其概率为。
- 将
考虑重新组织分桶,按照
划分为
[0,R)、[R,R+S)、[R+S,U)
三个桶。将第一个桶根据划分为
T
个子桶; 将第二个桶根据划分为
T
个子桶;将第三个桶根据划分为
T
个子桶。则上述采样过程的三种情况等价于:从均匀分布
中采样一个点。该点落在那个桶,则就在桶内的子桶中继续采样主题。
- 该桶落在
R
桶的子桶t
,其概率为:。
- 该桶落在
S
桶的子桶t
,其概率为:。
- 该桶落在
Q
桶的子桶t
,其概率为:。
- 该桶落在
4.1.2 采样过程
sparseLDA
采样过程中假设:在同一篇文档的同一次迭代期间,文档-主题
计数、主题-单词
矩阵保持不变。即:参数延迟更新。sparseLDA
采样过程:从均匀分布中采样一个点
。
如果
,则从
中继续均匀采样一个点,设该点落在子桶
,则返回主题
。
考虑到
对于同一篇文档中的所有位置都相同,这使得对桶
采样的平摊算法复杂度为
。
如果
,则从
中继续均匀采样一个点,设该点落在子桶
,则返回主题
。
此时只需要考虑当前文档中出现的主题,因为对于其它未出现的主题有
,则
。
因此对桶
采样的算法复杂度为
,
为文档中出现的主题数量。 同时内存消耗更少,因为只需要保存非零值对应的分量。
如果
,则从
中继续均匀采样一个点,设该点落在子桶
,则返回主题
。
此时只需要考虑使得当前单词
的
主题-单词
计数非零的那些主题。因为对于其它主题有,则
。
因此对桶
采样的算法复杂度为
,
为当前单词
涉及到的主题数量 。 同时内存消耗更少,因为只需要保存非零值对应的分量。
整体推断的算法复杂度为
。
对于小数据集,
,但是对于大数据集可能发生
。即:单词
出现在了几乎所有主题中。
假设单词
由主题
生成的可能性为
,则
篇文档中
至少由主题
生成的一次的概率为:
因此对于非常大的数据集对于
sparseLDA
是不利的。
4.2 AliasLDA
4.2.1 概率分解
AliasLDA
通过引入Alias Table
和Metropolis-Hastings
方法来进一步加快采样速度。AliasLDA
对采样公式进行不同的分解(令,采用基于位置的更新):
其中
与文档无关。
AliasLDA
采样过程是:从均匀分布中采样一个点
。
如果
,则从
中继续均匀采样一个点,设该点落在子桶
,则返回主题
。
此时只需要考虑当前文档中出现的主题,因为对于其它未出现的主题有
,则
。
因此对桶
采样的算法复杂度为
,
为文档中出现的主题数量。 同时内存消耗更少,因为只需要保存非零值对应的分量。
如果
,则从
中继续均匀采样一个点,设该点落在子桶
,则返回主题
。
如果能够使得该子采样的算法复杂度为
,则整体采样的算法复杂度为
,要大大优于
sparseLDA
的。
4.2.2 Alias Table
Alias Table
用于将离散的、非均匀分布转换成离散的、均匀的分布。这是为了采样方便:离散均匀分布的采样时间复杂度为。
假设一个离散、非均匀分布为
。
常规的采样方式为:
- 分桶:
1:[0,1/2), 2:[1/2,5/6), 3:[5/6,11/12), 4:[11/12,1)
。 - 采样:从
0~1
之间均匀生成一个随机数x
,x
落在哪个桶(二分查找),则返回对应的桶的编号。
其平均每次采样的复杂度为
,其中
为事件的数量。
- 分桶:
一个改进的方法是:
建立四个分桶,桶的编号分别为
1~4
。从
1~4
中均匀采样一个整数,决定落在哪个桶。假设在第个桶。
从
0~1
之间均匀采样一个小数,则:
- 计算非归一化概率:
(即:归一化概率除以其中的最大值)。
- 若
,则接受采样,返回事件
;若
,则拒绝接受,重新采样(重新选择分桶、重新采样小数)。
- 计算非归一化概率:
理想情况下其算法复杂度为
,平均情况下算法复杂度为
。
可以看到:事件
被选中的概率(非归一化)为:
,归一化之后就是
。
Alias Table
在上述思想指导下更进一步,它对概率除以均值而不是最大值。构建
Alias Table
(算法复杂度):
初始化数组
,第
个桶的容量
表示事件
发生的概率;
,
存放第
个桶中的另外一个事件的编号。
第
个桶要么完全由事件
组成,要么由事件
和另外一个事件组成(由
给出)。每个桶最多包含2个事件。
将每个桶的容量乘以
:
。即:计算非归一化概率。
构建队列
A
,存放容量大于1的桶编号;构建队列B
,存放容量小于1的桶编号。处理每个桶,直到满足条件:队列
B
为空。处理方式为:从队列
A
取出一个桶,假设为;从队列
B
取出一个桶,假设为。将
填充到容量为1,填充的容量从
消减。假设消减的容量为
- 记录容量:
;登记:
。
注意:此时不需要更新
,
存放的是事件
的非归一化概率,也等于它在桶中的比例。
- 若
,则继续放入队列
A
;若,则放入队列
B
;若,则桶
处理完成。
最终每个桶的容量都是1,桶内最多存放两个事件(由
记录)。
- 记录容量:
从
1~N
中均匀采样一个整数,决定落在哪个桶。从
0~1
之间均匀采样一个小数。假设在第
个桶:若
,则返回事件
;否则返回事件
。
Alias Table
的算法复杂度:- 构建
Alias Table
步骤复杂度,采样步骤复杂度
- 如果采样1次,则算法复杂度为
。如果采样非常多次,则构建
Alias Table
的代价会被平均,整体的平摊复杂度为。
- 构建
4.2.3 MH 采样算法
如果直接对
采用
Alias Table
采样,则会发现一个严重的问题:对于文档中不同位置的单词
不同,因此概率分布
随位置
发生变化。这就相当于采样 1 次的
Alias Table
算法,完全没有发挥出Alias Table
的优势。解决方式是:提出一个不随位置
变化的概率分布
,它近似原始分布
但是更容易采样。然后采用基于
MH
的MCMC
采样算法。标准的
MH
算法需要构建状态转移矩阵,但是这里的状态转移比较特殊:状态转移概率仅与最终状态有关,与前一个状态无关,即:。
算法输入: 近似概率分布
,原始概率分布
算法输出:满足原始分布的采样序列
算法步骤:
从概率分布
中随机采样一个样本
对
执行迭代。迭代步骤如下:
根据
采样出候选样本
计算接受率
:
根据均匀分布从
内采样出阈值
,如果
,则接受
, 即
。否则拒绝
, 即
。
由于
近似于
,因此大多数情况下会接受
。
返回采样序列
。
注意:返回的序列中,只有充分大的
之后的序列
才是服从原始分布的采样序列。
4.2.4 AliasLDA
AliasLDA
综合采用了AliasTable
和MH
采样算法。对分桶采样的算法步骤:
构建不随文档中的位置
变化的概率分布
,算法复杂度
。
根据
构建
AliasTable
,算法复杂度。
循环:遍历文档的所有位置
:
对于文档的当前位置
根据前述的
MH
算法采样出主题,其算法复杂度为
。
考虑到前面两步的代价可以平摊到每次采样过程,因此平摊算法复杂度为
。
4.3 LightLDA
web-scale
规模的预料库非常庞大也非常复杂,通常需要高容量的主题模型(百万主题、百万词汇表,即:万亿参数级别)来捕捉长尾语义信息。否则,当主题太少时会丢失这些长尾语义信息。这种规模数据集的训练需要上千台机器的集群,而
LightLDA
允许少量的机器来训练一个超大规模的LDA
主题模型,它可以用 8 台机器训练一个包含百万主题&百万词汇表(万亿级参数)、包含千亿级单词的数据集。LightLDA
主要采用了以下方法:- 更高效的
MH
采样算法,其算法复杂度为,并且与当前最先进的
Gibbs
采样器相比收敛效率快了近一个量级。 structure-aware
的模型并行方案,其利用主题模型中的依赖关系,产生一个节省机器内存和网络通信的采样策略。- 用混合数据结构来存储模型,其针对高频单词和低频单词采用不同的数据结构,从而使得内存可以放置更大规模的模型,同时保持高效的推断速度。
- 有界异步数据并行方案,其可以降低网络同步和通信的成本,从而允许通过参数服务器对海量数据进行有效的分布式处理。
- 更高效的
4.3.1 structure-aware 模型并行
数据并行:分割数据集,将不同文档集交给不同的
worker
来处理。所有worker
共享同一份模型参数,即:共享同一份全局主题-单词
矩阵。
如:
YahooLDA
和 基于参数服务器的LDA
实现就是采取数据并行。数据并行+模型并行:分割数据集,将不同文档集交给不同的
worker
来处理。同时分割模型,不同worker
处理不同的主题-单词
分布。即:每个worker
只会看到并处理本地文档出现过的单词的主题-单词
分布。如:
PLDA
和Peacock
,以及LDA
就是采取这种策略。为了解决模型内存需求太大与
worker
内存较小的问题,LightLDA
提出了structure-aware
模型并行方案。在每个
worker
中,采样算法执行之前先将本worker
分割到的数据集进一步划分为数据块Data Block 1,Data Block 2,...
。同时计算每个数据块包含的单词的词汇表,并将该词汇表作为元数据附加到数据块上。该操作只需要线性扫描,其计算复杂度为
。
在
worker
对Data Block i
执行采样时:首先加载数据块
Data Block i
到worker
的内存,取得该block
的元数据。然后根据元数据,将该数据块的词汇表划分为集合
。
对每个词表集合
,拉取参数服务器中的全局
主题-单词
矩阵中涉及
的单词的部分,称作模型切片,记作
。因此
worker
仅仅需要保存模型的一个切片。然后对
Data Block i
中的所有文档执行采样并更新,采样时仅仅考虑出现在
中的单词。
当对
更新完毕时,将本次更新同步到参数服务器中的全局
主题-单词
矩阵。然后继续处理下一个词汇集合
。
一旦对
Data Block i
处理完毕,继续采样下一个数据块Data Block i+1
。
如下图所示为
Data Block 2
的采样过程,表示该数据块包含的文档数量。在
d1,d2,...
中的箭头给出了主题采样的顺序。每个文档通过灰色块表明出现对应的单词(出现一次或多次)、白色块表明没有出现对应的单词。structure-aware
除了降低worker
内存需求之外,还通过以下方式减轻网络通信瓶颈:在处理数据块
Data Block i
时,只有当前模型切片相关的所有单词都被采样时,才会移动到下一个模型切片。这使得在处理数据块
Data Block i
时,每个模型切片只需要被换入换出内存一次,而不是反复换入换出内存。模型切片的换入换出需要和参数服务器进行同步,因此多次交换会增大通信压力。在利用当前模型切片对数据块采样时,可以同步进行下一个模型切片的加载(从参数服务器拉取到本地)、上一个模型切片的同步更新(从本地推送到参数服务器)。
这进一步降低了网络通信的时间延迟。
4.3.2 高效的 MH 采样算法
AliasLDA
采样的计算复杂度为,因此它不擅长处理比较长的文档,比如网页。这是因为:在初始迭代中,文档每个位置的主题都是随机初始化的,因此对于较长的文档
非常大。这会使得
AliasLDA
在迭代初期非常缓慢,消耗大量时间。当利用分布
对
进行采样时,一个好的
需要满足两个条件(从而加速采样速度):从
中采样相对更加容易;马尔科夫链更快混合
mix
。因此在设计分布
时,存在一个折中:
- 如果分布
和
更接近,则马尔科夫链混合的更快,但是从
采样的代价可能与
相差无几。
- 如果分布
非常容易采样但是与
很不一样,则马尔科夫链混合的很慢。
- 如果分布
所谓马尔科夫链的良好的混合意味着:马尔科夫链能够收敛,且收敛速度不会太长。
当拒绝率太高时,
MCMC
采样很可能长期处于某些值附近(如下图所示的平摊区域),搜索整个参数空间要花非常多的时间。下图中,横轴为迭代次数,纵轴表示采样结果。与
AliasLDA
和SparseLDA
不同,LightLDA
用两个近似分布来对
进行采样。
考虑采样概率:
定义 :
其中
为文档内主题
的频次,
为文档长度。则接受率为:
定义:
其中
表示数据集
中由主题
生成的单词中单词
的次数,
表示数据集
中由主题
生成的单词总频数。则接受率为:
为了提高对
的采样效率,对
进一步拆分:
因此采样步骤为:
首先从均匀分布
中采样一个点。
如果该点落在
之间,则使用
进行采样。此时无需建立
AliasTable
,直接从文档中均匀随机选择一个单词,其背后的主题就是采样得到的主题,该分布刚好就是。
如果该点落在
之间,则使用
进行采样。
- 如果
为常数,则此时
为均匀分布,无需建立
AliasTable
。 - 如果
并不是全部相等,则由于
与单词、文档都无关,因此可以建立全局
AliasTable
,它可以跨单词、跨数据块共享,其平均时间、空间复杂度为。
- 如果
因此从
采样的平摊时间、空间复杂度均为
。
为了提高对
的采样效率,采用类似于
AliasLDA
的Alias Table
方式,从而使得对的采样复杂度为
。
注意:虽然在文档内部,
在不同位置处不同,是变化的,因此不满足
Alias Table
的要求。但是在同一个数据块中,对于同一个单词,
是相同的,是不变的,因此满足
Alias Table
的要求。因此是在同一个数据块内,跨文档共享。
建立
的空间复杂度是
,因为它需要存储
个分桶的边界,以及桶内的值。为了降低空间复杂度,可以进一步拆分:
其采样步骤为:
- 首先从均匀分布
中采样一个点。
- 如果该点落在
之间,则使用
进行采样。此时只需要考虑单词
涉及的主题即可,空间复杂度为
。
- 如果该点落在
之间,则使用
进行采样。它与单词
无关,因此可以在所有单词之间共享,这使得平均的空间复杂度为
。
因此对
的采样的平摊时间复杂度为
,平摊空间复杂度为
。
- 首先从均匀分布
考虑到
,
为
文档-主题
概率,与比较接近;
为
主题-单词
概率,与比较接近。
- 如果仅仅用
采样,则有可能出现
较小、
很大的情形。此时
较大,而
较小,使得接受率较低,收敛速度较慢。
- 同理:如果仅仅用
采样,则有可能出现
较小、
很大的情形。此时
较大,而
较小,使得接受率较低,收敛速度较慢。
因此:虽然单独利用
或者单独利用
都可以对
进行采样,但是单独使用时收敛速度较慢。
- 如果仅仅用
LightLDA
联合采用来加速采样的收敛速度,它轮流在二者之间采样。这等价于用
进行采样,其接受率较大、收敛速度较快。
算法输入:原始函数分布
,
文档-主题
近似分布,
主题-单词
近似分布算法输出:满足原始分布的采样序列
算法步骤:
从
中随机采样一个样本
对
执行迭代。迭代步骤如下:
根据
采样出候选样本
,计算接受率
:
根据均匀分布从
内采样出阈值
,如果
,则接受
, 即
。否则拒绝
, 即
。
根据
采样出候选样本
,计算接受率
:
根据均匀分布从
内采样出阈值
,如果
,则接受
, 即
。否则拒绝
, 即
。
返回采样序列
。
4.3.3 混合数据结构
对于百万词汇表 x 百万主题,如果每一项(
单词-主题
计数)是32 bit
,则需要4 TB
内存才能存放。假设一台机器有128 G
内存,则至少需要32
台参数服务器。因此如果能够大幅降低内存,则对硬件资源的需求也能够下降。考虑词频统计,在数十亿网页文件中:
- 在删除停用词之后,几乎所有单词的词频不超过 32 位无符号整数的上限。因此
主题-单词
矩阵的元素采用 32 位无符号整数存放。 - 大多数单词出现的次数少于
次,
为主题数量(论文中为一百万)。这意味着
主题-单词
矩阵中,大多数单词列是稀疏的,如果采用稀疏表示( 如hash
映射) 将显著减少内存需求。
- 在删除停用词之后,几乎所有单词的词频不超过 32 位无符号整数的上限。因此
采用稀疏表示存在一个问题:其随机访问性能很差,这会损害
MCMC
采样的速度。为了保持高采样性能、低内存需求,LightLDA
提出了混合数据结构:- 对于词频较高的单词,其对应的主题计数列是不做修改的、方便随机访问的
dense
表示。这部分单词占据词表的10%
,几乎覆盖了语料库中95%
的位置。 - 对于词频较低的长尾单词,其对应的主题计数列经过
hash
映射的sparse
表示,使得内存需求大幅降低。这部分单词占据词表的90%
,仅仅覆盖了语料库中5%
的位置。
这就使得:
- 在
MCMC
采样中,对大多数主题-单词
列的访问都是dense
表示,这使得采样的吞吐量很高。 - 在存储方面,大多数
主题-单词
列的存储是sparse
表示,这使得内存需求较低。
- 对于词频较高的单词,其对应的主题计数列是不做修改的、方便随机访问的
4.3.4 系统实现
LightLDA
使用开源的分布式机器学习系统Petuum
来实现,特别使用其参数服务器来进行有界异步数据并行。参数服务器用于存放
LDA
的两类模型参数:主题-单词
矩阵,主题频次向量
,其中
为主题
生成的单词的总数(它也等于
第
行的求和)。
因为语料库的规模远大于模型的大小,所以在网络中交换文档的代价对网络压力非常大。因此
LightLDA
并不是交换数据,而是交换模型:先将语料库随机分配到各个worker
的磁盘上,然后在整个算法期间每个worker
仅仅访问本地磁盘上的部分语料库。交换数据的做法是:每轮迭代时,将语料库分发到不同
worker
,每个worker
在不同迭代步处理的数据会有所不同。LightLDA
整体架构如下图所示:各参数的意义位(这里采用论文中的符号表示):
:文档
的长度;
:数据分片中包含的文档数量。
:文档
的第
个词汇的主题。
:语料库中主题
生成的单词
的数量,即
。
:语料库中主题
的频次,即
第
行的求和。
:文档
的主题
的频次。
:数据分片。
:模型分片。
:主题频次向量。
数据分片从
worker
的磁盘加载到worker
的内存,模型分片由worker
从参数服务器进行获取。考虑到每个数据分片可能仍然非常巨大,无法一次性加载进
worker
内存。因此LightLDA
将每个数据分片继续划分成数据块Data Block
,并将这些数据块依次加载进内存处理。
为了加速并行处理,
LightLDA
进行了如下优化:通过流水线来消除IO的延迟:在采样的同时可以加载数据块、拉取下一个模型分片、更新上一个模型分片。
这需要根据采样器的吞吐量、数据块大小、模型切片大小仔细安排参数。
为防止模型切片之间的数据不均衡(热门词和冷门词),需要对词汇表按照词频进行排序,然后对单词进行混洗来生成模型切片。
在生成数据块时,将文档中的单词按照它们在词表中的顺序进行排序,从而确保同一个模型切片的所有单词在数据块中是连续的。这个操作只需要执行一次,与
LDA
整体消耗时间相比该操作非常快。使用有界异步数据并行来消除在相邻迭代边界处发生的网络等待时间。
LightLDA
在每个worker
内部开启多线程来进一步加速算法:- 每个数据块在内存中被划分为不相交的区域,每个区域由不同的线程进行采样。
- 所有线程之间共享模型切片,且采样期间模型切片不变。在将模型切片更新到参数服务器之前,对各线程的更新结果进行聚合。
通过保持模型切片不变,这避免了竞争条件和加锁/解锁等并发问题。虽然这种延迟更新方式需要更多的迭代步才能收敛,但是由于这种方式消除了并发问题,提高了采样器吞吐率,使得每个迭代步的速度快得多,因此整体上仍然可以加速收敛速度。