最近看了不少关于主题模型的东西,要说起主题模型,现在最火的当然是LDA, LDA全称是Latent Dirichlet Allocation(隐狄利克雷分布), 而不是Linear Discriminant Analysis, 相信大家很多都对lda的理解感到痛苦不已,因为里面涉及到的数学推导实在是太多了,从gamma函数,beta分布,狄利克雷分布,马尔可夫蒙特卡洛模型,看到都觉得反胃,不过今天,我们不从这些来说,就跟大家分析一下怎么从工程上去实现整个LDA
实现之前还是来说一下LDA的起源:
先上一张来自Blei大师之手的图,简单的说一下吧, theta代表文档-主题分布,在工程上可以理解为一个矩阵,如果整个文档语料库包含的词是|W|,包含的文档数是|D|,那么矩阵的大小就是|D| * |W|,直观的来说,这个矩阵中存储的值theta[d][z]表示的是文档d中被分派给主题z的词的个数,更具体的,我们可以认为它就是p(z|d)
主题模型是一种生成模型,什么是生成模型呢,比如我们在构思一篇文档:(1)我们要选择文章的主题,一个主题中可能有多个词; (2)我们现在就要从这个主题中选择我们想要的词;第一个部分的概率就是p(z|d),表示在给定文档d,出现主题z的概率;
举一个例子(例子来源于 Rich jin的LDA数学八卦):
我们平时在构造一篇自然语言处理的文章时,可能会有40%的概率谈论语言学,30%的概率谈论概率统计,20%的概率谈论计算机,还有10%谈论其他主题;选定了主题之后,我们执行第二部,选词,那正常情况下,我们是怎么选词的呢?
- 说到语言学:我们一般会想到 语法,句子,乔姆斯基,据法分析,主语这些词
- 谈到概率统计,我们也很容易想到词:概率,模型,均值,方差,证明,独立,马尔可夫链
- 说到计算机,我们也能联想到 内存,硬盘,编程,二进制,对象,算法,复杂度这些词
class LDAModel:
alpha = float #超参数alpha
beta = float #超参数beta
D = int #文档数目
K = int #主题个数
W = int #词的个数
NumberOfIterations = int #迭代次数
SaveStep = int #存储的步数
Dictionary = object #整个语料的词典
Z = object # D * doc.size()大小的矩阵,Z[i][j]表示第i文档的第j个词背分配的主题
W = object # D * doc.size()大小的矩阵, W[i][j]表示第i文档的第j个词
IDListSet = object # D * doc.size()大小的矩阵, IDListSet[i][j]表示第i篇文档的第j个词在词典中的编号
nw = object # W * K 大小的矩阵, nw[w][z]表示词w被分配到主题z的次数
nd = object # D * K 大小的矩阵,nd[d][z]文档d中被分配为主题z的词的个数
nwsum = object # K * 1 大小的向量,nwsum[z]表示主题z中包含的词的个数
ndsum = object # D * 1 大小的向量,ndsum[d]表示文档d中包含的词的个数
theta = object # D * K 大小的矩阵,p(z|d) = theta[d][z]
phi = object # K * V 大小的矩阵,p(w|z) = phi[z][w]
具体我就不说这些成员的意思,注释上都有,
def __init__(self, alpha, beta, NumberOfIterations, SaveStep, K):
self.alpha = alpha
self.beta = beta
self.NumberOfIterations = NumberOfIterations
self.SaveStep = SaveStep
self.K = K
#初始化大小为K * 1的向量,初始值为0
self.nwsum = ListUtil.Initial(self.K)
有一些列表工具类的方法我已经打包了,先列出来