本文介绍基于Python的有标签隐式狄利克雷分布(Labeled Latent Dirichlet Allocation, L-LDA)的Gibbs Sampling实现
本文回答了项目实现中的一些问题,作为个人LLDA实现过程中遇到的问题和论文中的思考,写得比较多,比较杂
为什么选择吉布斯采样
问
- 为什么使用Gibbs Sampling而不是变分推断实现?
答
参考文献:Labeled LDA: A supervised topic model for credit attribution in multi-labeled corpora
* 变分推断不能使用(在L-LDA原始文章中只找到Gibbs采样的实现介绍)
- 首先对于LDA模型而言:erbub * 变分推断的优点(Gibbs采样的缺点):
* 速度收敛速度快(慢) * 可以用于分布式(不能用于分布式)
- 变分推断的缺点(Gibbs采样的优点):
- 不够精确,只是近似模拟(也是近似模拟,但是更精确)
- 变分推断收敛后容易陷入局部最优(得到的往往是局部最优的近似后验分布[已知的简单分布]来估计真实后验分布),效果不如Gibbs采样
- 变分推断的缺点(Gibbs采样的优点):
- 其次对于L-LDA而言(即使可以使用变分推断实现L-LDA):
- Gibbs采样虽然速度慢一些,但是在我们的场景中,由于需要生成新的训练数据(半监督学习的模型),所以需要确保模型的精确度,而不是时间效率
- 在半监督训练完成后,实际的应用场景中,为了节约时间,此时我们可以使用已经生成的大量数据集,用变分推断的方法实现,提升用户体验
收敛性的判断
问
- 收敛性的判断使用参数变化量还是使用困惑度?
答
收敛的判断方式
采样一定次数后停止
- 网上太多实现使用的是采样循环(对所有词的主题的完全采样一次算一个循环)一定次数后停止
- 优点:
- 每次循环结束无需任何额外的计算和判断,只需把迭代次数加一即可
- 无需存储参数的中间信息
- 缺点:
- 难以确定采样循环次数,次数太多浪费时间,次数太少容易造成不收敛
使用参数变化量(Gibbs 采样收敛性的判断使用这个感觉更靠谱)
- 基本原理是: 采样收敛以后概率不变,模型参数也会收敛,两次迭代之间参数变化量会很小
- 优点:
- 可以通过参数变化来精确知道模型是否收敛
- 缺点:
- 每次采样循环结束后需要计算参数变化量
- 需要存储之前的参数的中间结果结果(参数是所有概率矩阵,往往并不小)
一种可能可行的方法,使用一个评估指标困惑度的变化量
- 在采样的过程中,模型的困惑度一定是递减的(偶尔可能有微弱的增加,属于正常现象)
- 当困惑度小到一定程度后困惑度应该趋近于收敛,几乎不变,如果困惑度不再有大的变化,我们认为模型收敛
- 优点:
- 无需存储参数,仅仅存储困惑度的值即可(浮点数)
- 缺点:
- 每次评估困惑度时计算量大
- 当前尚未看到有人使用这个指标作为语言模型收敛度的判断
- 在我们的场景中,因为我们想知道每一轮的困惑度,所以就把困惑度的变化量当做收敛性判断了,实际上在训练时我们为了收敛保证,选择的是采样次数在一定范围内(不能太小,也不能太大),同时参数变化量变得很小的双重判断标准(这个判断标准与困惑度不同,不同场景中往往差值很大,我们在GitHub项目中有所实现,允许用户自定义参数)
我们的实现
- 实现了困惑度评估收敛性的方法: 相邻10轮采样(每一轮针对所有词),模型困惑度都不变化,那么认为该模型收敛
- 实际上在不同的实际应用中收敛性的判断应该使用不同的方式,我们这里使用困惑度是因为反正都要计算困惑度的,为了充分利用困惑度的计算结果,顺便将其作为收敛性判断
- 实际论文和工程实验中为了保险使用的是迭代次数的方式
- 测试说明:
- 经测试发现,模型的困惑度10次采样前后变化不大后,参数的确收敛了,而且模型的预测效果也收敛,继续采样50次循环对模型的效果无明显提升
增量更新训练数据
问
- 在向原始训练数据集(TDS)中添加新的训练数据(NewTDS)时,如何利用原始训练数据上一次的训练结果
答
- 我们实现了一种可以增量更新模型的方式: 将上一次模型(TDS训练得到)已经训练好的参数作为新模型的初始参数,提升收敛速度
- 由于Gibbs采样的收敛状态与初始状态无关,所以我们可以从数学理论上证明该方法的正确性
- 测试说明:
- 经测试发现,本方法可以很大程度提升新模型的收敛速度
- 经测试发现,本方法训练的结果与随机初始化参数的训练结果,模型效果相同
- 两个模型的困惑度收敛到近似相等
- 实际场景中测试说明两个模型的精度也相同
推断(预测)
问
- 模型训练收敛后,对新来的文档,如何给出主题预测?
答
参考文献: LDA数学八卦, L-LDA
- 在训练阶段得到模型的主题-词矩阵\(\beta\)后,可以继续进行采样
- 不用存储文档-主题\(\theta\)矩阵,采样时不需要这个参数,训练后这个参数无需存储
- 流程如下:
- 随机初始化, 对文档中的没个词随机赋一个主题
- 重新扫描当前文档,按照吉布斯采样公式
- 公式为:
$$
\begin{align}
P(z_{i}=k|\vec{z}_{\not{i}}, \vec{w}) &\propto E(\theta_{m,k}) E(\phi_{k,t}) \\
&= \hat{\theta}_{m,k} \hat{\phi}_{k,t}
\end{align}
$$ - \(\vec{z}_{\not{i}}\)为除了\(z_{i}\)的所有当前主题\(z_{i}\)是词\(w_{i}\)的主题
- \(\hat{\theta}_{m,k} \hat{\phi}_{k,t}\)是样本均值,这里是用当前均值估计期望
- 显然对每个词,重新采样它的主题(每次采样时实际上当前词的主题只与当前文档其他词的主题和主题-词矩阵相关, 这里推断和训练期间都一样)
- 实际实现时, 我们并没有存储参数 \(\hat{\phi}_{k,t}\)的值,而是存储一个主题-词[数量]矩阵,方便计算和采样,这种实现推断期间 \(\hat{\phi}_{k,t}\)的值还会继续变化,实际上是更符合实际(精确)的做法,这种做法是使得当前推断和训练的采样方法一模一样(连参数\(\hat{\phi}_{k,t}\)的计算都一样,论文推荐的是在推断期间\(\hat{\phi}_{k,t}\)值不变,与训练步骤不同)
- 这里也可以把 \(\hat{\phi}_{k,t}\)的参数(整个矩阵)都存储下来,然后推断期间都不变,这种做法是论文中推荐的,这种做法采样速度快,不需要每次都计算一下当前的\(\hat{\phi}_{k,t}\)值,但是实际上并不精确(当然:当训练数据非常大时,这里近似于精确的,我们的应用场景中考虑到可能训练数据一开始并不多,所以确保精确,我们实现的是前面那种更精确的做法)
- 公式为:
- 重复扫描采样直至收敛
- 统计文档中的主题分布,得到新文档的文档主题分布\(\vec{\theta}_{new}\)
实际使用时主题数的确定
问
- 实际使用中主题数如何确定的?
答
- 一般情况下可以使用困惑度,不同的主题数对应模型的困惑度不一样,k从小到大,对应模型的困惑度应该是先减小后增加,选择困惑度最小的模型对应的主题数即可
- 在我们论文实验中,由于主题数与标签数量一致,直接设定即可
验证集问题
问
- 实际在SemiTagRec实现的时候,只提到训练集和测试集,那网格搜索的时候验证集是什么数据呢?
答
我们的算法中,如果考虑详细情况的话,网格搜索超参数其实需要每次都调一遍
但是幸亏我们测试发现Integrator的超参数很容易调
实际实验时,我们使用80%和90%样本作为训练集,然后只用随机采样50个左右的样本作为验证集基本就收敛了(随机采样多个都是这样,能在0.91到0.09周围得到最优值),而且后面调试几乎不变(基本上就在0.89-0.93和0.11-0.07之间且精度几乎没变化,所以我们实际上用所有测试样本测试通过得到了最优的值为0.91和0.09[取平均值得到的])
最终方法:在我们的算法中,首先经过多次训练和测试(实际上就是每次训练完,然后使用网格搜索法完成了Integrator的超参数设定,基本都是使用0.91和0.09最好),然后接下来的模型迭代训练中我们没有再修改这个超参数了,所以也不用验证集了,这可以为我们之后的训练增加训练和测试数据,对我们来说是个好消息
训练集和测试集的划分
问
- 为什么使用90%这么多的样本作为训练集?测试集使用10%足够了吗?
答
- 实际上我们的算法中,我们一开始的分割方式是60%训练,20%验证,20%测试,这样训练的到的结果不理想(LLDA的训练结果测试就非常差),分析原因其实是3000+的训练集太少了,对我们的训练模型LLDA来说,远远不够
- 由于可用的训练样本数量太少,为了保证训练质量,我们选择90%(5000个左右)用于训练后,10%(550个左右)用于测试,550个测试样本总数有2000+个正确的标签,实际上够用了(多次测试证明,随机选取300个测试样本以后基本上增加测试样本模型的精度几乎没变化)
为什么不使用十折交叉验证法
问
- 既然分配给测试的数据太少,为了增加训练集的同时保证模型评估的精确性,是不是应该使用十折交叉验证法
答
- 我们的模型使用的训练时间太长了,采样花的时间比较多,考虑到时间因素,没有使用十折交叉验证
- 待更新:这个回答真的好吗?
为什么使用多处理器?
- 使用多处理器的初始想法(这是一个错误的想法)
- 预测时文本的预测结果与初始值(当前文本每个词的主题初始分配)有很大关系[错误,这里实际上是采样还没收敛],不同初始值会收敛到不同的结果,为了防止初始值不同带来的误差,我们采用多次初始化并且多次收敛的方法,最终对结果求均值,得到最终的预测结果
- 实验证明,这种多处理器的方法对模型预测能力有很大提升?
- 问题:吉布斯采样应该是能够收敛到目标分布的,为什么预测时这里不会收敛到目标分布?
- 回答:会收敛的,每次迭代次数不要太少,迭代一定次数后开始丢弃之前的一定数量的采样,去后面的采样平均值,会得到收敛的结果,核心是采样次数一定要够
- 进一步实验证明:采样次数不足时多个不同初始值采样的结果取平均的确是有帮助的,但是采样次数非常多以后就不需要了,采样结果收敛后一定是到那个目标分布的!
多处理器依然存在的意义
用于对相同文档同时采样,收敛后每个处理器返回自己的一个平稳分布的样本(可以为一个,可以为多个),然后所有样本构成最终平稳分布的代表样本
- 实际上收敛后这些样本都是服从目标分布产生的
实现上没有问题,但是这里浪费了很多时间采样到收敛的过程(多条不同的采样过程分别采样到收敛,很浪费时间)
从单个处理器来看,不论怎样都需要采样到平稳分布的,在多处理器同时工作时,从平稳状态中采样平稳分布的样本需要的采样次数实际上是被均分到多个处理器上的,从这里来看不考虑内存占用等方面的问题,多处理器工作的确是能节约我们的整体时间的
一般为了避免随机变量统计量(如期望等)估计的偏差,需要产生独立同分布的样本,我们这里就需要: 同时使用多条马尔科夫链可以得到独立同分布的样本,否则同一条链上的样本往往
不是独立的,因为同一条链上的后一个样本由前一个样本通过某种特定的状态转移概率得到.- 实践中,在同一条马尔科夫链上每隔若干个样本才选取一个可以得到近似独立的样本
- 如果仅仅是采样,不需要样本间相互独立,我们一般就直接使用一条链产生多个样本即可(举例:[待更新])
为什么训练的时候不是采样多个样本来预测分布
问
- 为什么训练的时候不使用采样多个样本(每个样本代表当前所有文档的所有词的主题矩阵)?
答
- 由于训练样本非常多,所以单个样本足以代表整体主题-词分布
- 因为我们不评估每个文档的主题分布:由于对每个文档来说,不取多个采样值,无法代表文档本身的词分布
- 而是关注每个主题的词分布:实际上对每个主题来说,词的数量已经非常多了,完全可以代表当前主题-词分布了
- 经过测试证明的,也有论文支持
测试
- 训练收敛后取后面m个样本作为训练结果计算主题-词分布与收敛后取最后一个样本得到的结果(主题-词分布矩阵)相同
- 所以为了节约内存和减少计算量,我们只保留了最后一个样本,丢弃了前面的样本
- 论文原文引用说明
- Parameter estimation for text analysis, Gregor Heinrich*
To obtain the resulting model parameters from a Gibbs sampler, several approaches exist. One is to just use only one read out, another is to average a number of samples, and often it is desirable to leave an interval of L iteration between subsequent read-outs to obtain decorrelated states of the Markov chain. This interval is often called “thinning interval” or sampling lag.
说明:上面这段话的是针对LDA的Gibbs采样方法而言的(虽然上面这段话没提到LDA)
上面的引用说明选取LDA收敛后的训练样本选择有两种方式:
选取一个作为样本(这里只有在LDA训练时可用这种方法,其他的采样模型还要视情况而定的)
- 在LDA中,由于训练样本非常多,所以单个样本足以代表整体主题-词分布(由于LDA同时还关注着文档-主题分布,所以在LDA中私以为还是采样多个样本保险一些,特别是对于词数比较少的文档)
- 但是,在我们的应用场景中(在LLDA中),
- 只关注每个主题的词分布:实际上对每个主题来说,词的数量已经非常多了,完全可以代表当前主题-词分布了,
- 训练时我们不评估每个文档的主题分布
- 预测时:由于对每个文档来说,不取多个采样值,无法代表文档本身的词分布(特别是当文挡中的词比较少时),所以后面的预测过程中对单个文本的预测问题我们需要采样多个收敛后的样本计算均值
选取多个样本的平均值作为样本
- 注意:选取多个样本时,为了得到马尔科夫模型不相关的状态,需要间隔L次迭代进行间隔采样
- 一般来说,在Markov chain收敛后开始从1计数,Gibbs采样(这里不针对LDA)选取一次完整迭代后的结果作为平稳分布的样本即可
- 也就是\([(x_{1}^{t},x_{2}^{t},,,x_{n}^{t}), (x_{1}^{t+1},x_{2}^{t+1},,,x_{n}^{t+1}),,, (x_{1}^{t+s},x_{2}^{t+s},,,x_{n}^{t+s})]\)
- 其中需要的平稳分布的样本数是(s+1)
- 注意\([(x_{1}^{t+1},x_{2}^{t},,,x_{n}^{t}), (x_{1}^{t+1},x_{2}^{t+1},,,x_{n}^{t})]\)这些不完整迭代的结果都不能成为平稳分布的样本,因为这些样本之间相关度太高,不够独立,不能用来代表最终的平稳分布,容易造成局部偏差
- 也就是\([(x_{1}^{t},x_{2}^{t},,,x_{n}^{t}), (x_{1}^{t+1},x_{2}^{t+1},,,x_{n}^{t+1}),,, (x_{1}^{t+s},x_{2}^{t+s},,,x_{n}^{t+s})]\)
论文中为什么选择LLDA而不使用深度学习
问
- 为什么使用LLDA模型,没有考虑过使用深度学习模型吗?
答
- 数据量不够
- 写论文需要可解释性,深度学习模型的解释性远远不如LLDA模型的解释性
- 初始训练时本人测试时也没有拿出能优于LLDA的模型
- 模型较简单,简单的对每个词OneHot编码,然后+固定分类类别数量为1000,所以输出为1000维度
- 模型是对NNLM的一种改进
- 损失函数使用的是binary_crossentropy
- 深度学习在我们的场景中效果不好的原因可能包括以下方面:
- 训练数据量不够,太稀疏
- 没找到合适的神经网络模型,比如可以考虑使用一些权重共享的思想,降低由于数据太少引起的过拟合
- 在未来的想法: 如果可以增加数据量, 或者能够使用一些新的有效模型或者词嵌入的数据集, 可以重新尝试使用神经网络模型