主题模型LDA浅析:入门、Java实现以及测试案例
一、什么是主题模型假如有一篇文章text,通过里面的词,来确定他是什么类型的文章,如果文章中出现很多算法类的词,比如,“SVM”、“贝叶斯分类”之类的,那么主题模型就会把它划分为算法类的文章。假如该文章的主题并不单一,文中80%在讲算法,20%稍微讲了下在分词中的应用。因为涉及到中文分词,又被归入了分词类的文章。传统的文本分类器,比如贝叶斯、kNN和SVM,只能将其分到一个确定的类别中。假设给出3
一、什么是主题模型
假如有一篇文章text,通过里面的词,来确定他是什么类型的文章,如果文章中出现很多算法类的词,比如,“SVM”、“贝叶斯分类”之类的,那么主题模型就会把它划分为算法类的文章。
假如该文章的主题并不单一,文中80%在讲算法,20%稍微讲了下在分词中的应用。因为涉及到中文分词,又被归入了分词类的文章。
传统的文本分类器,比如贝叶斯、kNN和SVM,只能将其分到一个确定的类别中。假设给出3个分类“算法”“分词”“文学”让其判断,如果某个分类器将该文归入算法类,结果还算说得过去,如果分入分词,那这个分类器不够准确。
假设一个文艺小青年来看该文章,他完全不懂算法和分词,自然也给不出具体的备选类别,有没有一种模型能够告诉这个“白痴”,这篇文章很可能(80%)是在讲算法,也可能(19%)是在讲分词,几乎不可能(1%)是在讲其它主题呢?
有,这样的模型就是主题模型。
二、什么是LDA
2.1.简述
LDA:潜在狄立克雷分配(Latent Dirichlet Allocation)主题模型,是最简单的主题模型,它描述的是一篇文章是如何产生的。如图所示:
从左往右看,一个主题是由一些词语的分布定义的,比如蓝色主题是由2%几率的data,2%的number……构成的。一篇文章则是由一些主题构成的,比如右边的直方图。具体产生过程是,从主题集合中按概率分布选取一些主题,从该主题中按概率分布选取一些词语,这些词语构成了最终的文档(LDA模型中,词语的无序集合构成文档,也就是说词语的顺序没有关系)。
如果我们能将上述两个概率分布计算清楚,那么我们就得到了一个模型,该模型可以根据某篇文档推断出它的主题分布,即分类。由文档推断主题是文档生成过程的逆过程。
2.2.LDA的通俗定义
LDA是一种无监督的贝叶斯模型。
- 是一种主题模型,它可以将文档集中的每篇文档按照概率分布的形式给出。
- 是一种无监督学习,在训练时不需要手工标注的训练集,需要的是文档集和指定主题的个数。
- 是一种典型的词袋模型,它认为一篇文档是由一组词组成的集合,词与词之间没有顺序和先后关系。
它主要的优点就是可以对每个主题,都找出一些词来描述它。
2.3.概率模型
LDA是一种使用联合分布来计算在给定观测变量下隐藏变量的条件分布(后验分布)的概率模型,观测变量为词的集合,隐藏变量为主题。
在介绍LDA前,先介绍一些预备知识。
2.3.1.Beta分布
Beta分布可以看作一个概率的概率分布,当你不知道一个东西的具体概率是多少时,它可以给出了所有概率出现的可能性大小。
举一个简单的例子,熟悉棒球运动的都知道有一个指标就是棒球击球率(batting average),就是用一个运动员击中的球数除以击球的总数,我们一般认为0.266是正常水平的击球率,而如果击球率高达0.3就被认为是非常优秀的。
现在有一个棒球运动员,我们希望能够预测他在这一赛季中的棒球击球率是多少。你可能就会直接计算棒球击球率,用击中的数除以击球数,但是如果这个棒球运动员只打了一次,而且还命中了,那么他就击球率就是100%了,这显然是不合理的,因为根据棒球的历史信息,我们知道这个击球率应该是0.215到0.36之间才对啊。
对于这个问题,我们可以用一个二项分布表示(一系列成功或失败),一个最好的方法来表示这些经验(在统计中称为先验信息)就是用Beta分布,这表示在我们没有看到这个运动员打球之前,我们就有了一个大概的范围。Beta分布的定义域是(0,1)这就跟概率的范围是一样的。
接下来我们将这些先验信息转换为Beta分布的参数,我们知道一个击球率应该是平均0.27左右,而他的范围是0.21到0.35,那么根据这个信息,我们可以取α=81,β=219
之所以取这两个参数是因为Beta分布的均值是:
从上图中可以看到这个分布主要落在了(0.2,0.35)间,这是从经验中得出的合理的范围。在这个例子里,我们的x轴就表示各个击球率的取值,x对应的y值就是这个击球率所对应的概率,也就是说Beta分布可以看作一个概率的概率分布。
那么有了先验信息后,现在我们考虑一个运动员只打一次球,那么他现在的数据就是”1中;1击”。这时候我们就可以更新我们的分布了,让这个曲线做一些移动去适应我们的新信息。Beta分布在数学上就给我们提供了这一性质,他与二项分布是共轭先验的(Conjugate_prior)。所谓共轭先验就是先验分布是Beta分布,而后验分布同样是Beta分布。结果很简单:
其中α0和β0是一开始的参数,在这里是81和219。所以在这一例子里,α增加了1(击中了一次)。β没有增加(没有漏球)。这就是我们的新的Beta分布:
我们跟原来的比较一下:
可以看到这个分布其实没多大变化,这是因为只打了1次球并不能说明什么问题。但是如果我们得到了更多的数据,假设一共打了300次,其中击中了100次,200次没击中,那么这一新分布就是:
注意到这个曲线变得更加尖,并且平移到了一个右边的位置,表示比平均水平要高。
一个有趣的事情是,根据这个新的Beta分布,我们可以得出他的数学期望为:
这一结果要比直接的估计要小:
你可能已经意识到,我们事实上就是在这个运动员在击球之前可以理解为他已经成功了81次,失败了219次这样一个先验信息。
因此,对于一个我们不知道概率是什么,而又有一些合理的猜测时,Beta分布能很好的作为一个表示概率的概率分布。
2.3.2.Beta分布与二项分布的共轭先验性质
2.3.2.1.二项分布
二项分布即重复n次独立的伯努利试验。在每次试验中只有两种可能的结果,而且两种结果发生与否互相对立,并且相互独立,与其它各次试验结果无关,事件发生与否的概率在每一次独立试验中都保持不变,则这一系列试验总称为n重伯努利实验,当试验次数为1时,二项分布服从0-1分布,二项分布的似然函数:
2.3.2.2.Beta分布
在Beta分布中,B函数是一个标准化函数,它只是为了使得这个分布的概率密度积分等于1才加上的。
2.3.2.3.贝叶斯估计
我们做贝叶斯估计的目的就是要在给定数据的情况下求出 θ 的值,所以我们的目的是求解如下后验概率:
注意到因为 p(data) 与我们所需要估计的 θ 是独立的,因此我们可以不考虑它。
我们称 p(data|θ) 为似然函数, p(θ) 为先验分布。
2.3.2.4.共轭先验
现在我们有了二项分布的似然函数和Beta分布,现在我们将Beta分布代进贝叶斯估计中的p(θ) 中,将二项分布的似然函数代入 p(data|θ) 中,可以得到:
我们设
我们发现这个贝叶斯估计服从 Beta(a', b') 分布的,我们只要用B函数将它标准化就得到我们的后验概率:
2.3.3.联合分布——LDA模型生成
在《LDA数学八卦》一文中,对文档的生成过程有个很形象的描述:
具体来说,LDA方法使生成的文档可以包含多个主题,该模型使用下面方法生成1个文档:
- 在主题分布中抽取一个主题;
- 对抽到的主题所对应的单词分布中随机抽取一个单词;
- 重复上述过程直至遍历整篇文档中的每个单词。
这种方法首先选定一个主题向量θ,确定每个主题被选择的概率。然后在生成每个单词的时候,从主题分布向量θ中选择一个主题z,按主题z的单词概率分布生成一个单词。其图模型如下图所示:
从上图可知LDA的联合概率为:
把上面的式子对应到图上,可以大致按下图理解:
从上图可以看出,LDA的三个表示层被三种颜色表示出来:
1. corpus-level(红色):α和β表示语料级别的参数,也就是每个文档都一样,因此生成过程只采样一次。
2.document-level(橙色):θ是文档级别的变量,每个文档对应一个θ,也就是每个文档产生各个主题z的概率是不同的,所有生成每个文档采样一次θ。
3. word-level(绿色):z和w都是单词级别变量,z由θ生成,w由z和β共同生成,一个单词w对应一个主题z。
通过上面对LDA生成模型的讨论,可以知道LDA模型主要是从给定的输入语料中学习训练两个控制参数α和β,学习出了这两个控制参数就确定了模型,便可以用来生成文档。其中α和β分别对应以下各个信息:
α:分布p(θ)需要一个向量参数,即Dirichlet分布的参数,用于生成一个主题θ向量;
β:各个主题对应的单词概率分布矩阵p(w|z)。
把w当做观察变量,θ和z当做隐藏变量,就可以通过EM算法学习出α和β,求解过程中遇到后验概率p(θ,z|w)无法直接求解,需要找一个似然函数下界来近似求解,可使用基于分解(factorization)假设的变分法(varialtional inference)进行计算,用到了EM算法。每次E-step输入α和β,计算似然函数,M-step最大化这个似然函数,算出α和β,不断迭代直到收敛。
三、案例开源项目:
3.1.概述
JGibbLDA是一个java版本的LDA(Latent Dirichlet Allocation)实现,它使用Gibbs采样来进行快速参数估计和推断。
3.2.Code
请参考JGibbLDA的主页http://jgibblda.sourceforge.net/#Griffiths04
这个主页的代码有点问题,需要自己进行修改,本人修改的代码暂不公开,读者可参考:https://blog.csdn.net/fjssharpsword/article/details/72374085
3.3.参数说明和设置
boolean estc= false;//决定是否是基于先前已有的模型基础上继续用新数据训练模型
boolean est= false;//决定是否用新数据训练全新模型,并且新数据位于同一文件夹
//二者依次判断是否符合条件,est先判断,都默认false,选择性置为true
boolean inf= true; //是否使用先前已经训练好的模型进行推断
String dir= "";//数据来源存储位置
String saveDir ="";//数据模型保存位置
String modelName= ""; //选择使用哪一个迭代的模型结果来进行推断
int K= 100; //类簇数目,谨慎设置
double alpha= 0.2; //平滑系数
double beta= 0.1; //Specify beta
int niters= 1000; //迭代数目,谨慎设置
int savestep= 100;//指定把迭代结果模型保存到硬盘上的迭代跨度,即每迭代100次保存一次
int twords= 100; //对每一个类别(话题)选前多少个最大概率词项
public boolean withrawdata= false; //感觉没有作用
public String wordMapFileName= "wordmap.txt"; //生成的副产品的文件名
3.4.训练模型输入数据格式
数据输入格式统一如下所述:
[document1]
[document2]
...
[documentM]
其中:每一行就是一个文档。
3.5.输出模型文件
建立模型阶段,会输出5类以如下规则命名的文件类型:
model-XXXXX.others:
model-XXXXX.phi
model-XXXXX.theta
model-XXXXX.tassign
model-XXXXX.twords
XXXXX都以数字组成。最后一次迭代所保存的这些数字将会换成“final”。
其中:
- .others为“信息文件”。文件保存的是跟该LDA模型有关的参数,比如alpha,beta,ntopiccs,ndocs,nwords,liter
- .phi文件为“词项-主题概率分布文件”。表现上是一个大矩阵M。其中,假设设类簇的数目topict为1000个,每一个主题需要列出top 100个词项wordw,则M以100为行,1000为列。即M每一行是词项,每一列是主题。M元素值则为条件概率p(wordw|topict),即每个词属于每个主题的概率。
- .theta文件为“文档-主题概率分布文件”。表现上也是一个大矩阵M。每行i代表训练数据的一个文档,每一列代表一个主题,元素值则为条件概率 p(topict|documentm),即该文档属于不同主题的概率。
- .tassign文件为“文档-词项-主题分布文件”。该文件与输入文件的格式一致,一行一个文档,只不过原来的输入文件中的词项换成了一个一个“词项ID:类别”。文件每一行代表训练数据的一条文档,原文档由一组词项组成,现每一行为原来的记录词项指派了其最大可能的所属主题。注意,该文档所属主题分布是在theta文件中,并未在tassign文件中指明。
- .twords文件为“词项-主题推断文件”。这个文件作为模型参数结果推断出了每一个主题下最优的topN个词项及其概率。请注意这里的主题数和N都是事先指定的。
- wordmap.txt 文件副产品“词频统计文件”。是整个语料库中出现的特征词,词的id是按照出现的顺序来编的,但是在wordmap.txt里词是按照字母顺序来排的。
3.6.训练模型
训练模型需要设置一些必要参数,其中:option.dir,option.modleDir,option.est(或者option.estc二选一),option.modelName 必需,其他参考3.3.参数说明和设置选择设置,也可默认。
Code:
LDAOption option = new LDAOption();
option.dir = System.getProperty("user.dir") + "/data/train/" ;//训练语料所在文件夹
option.modleDir= System.getProperty("user.dir") + "/data/model/" ;训练模型保存位置
option.est = true;
option.inf = false;
option.modelName = "model-final";
option.niters = 1000;迭代数目
option.twords=30;//每个主题关键词个数
option.K = 9;//主题个数,默认是10
Estimator estimator = new Estimator();
estimator.init(option);//引入参数设置
estimator.estimate();//开始训练
3.7.测试
Code:
LDAPredictor predictor = new LDAPredictor( System.getProperty("user.dir") + "/data/model3/" , "model-final");
String input = "本系统针对于手机用户开发的一套软件服务系统,方便客户对手机商品的浏览,对比,购买以及评价。有利于提高客户网购手机商品的效率。\\r\\n功能描述:\\t系统主要功能设计有六大部分,分别为:产品管理、手机管理、配件管理、购物车管理、订单管理、登陆管理;";
double [] topic= predictor.excute(input); //主题概率
for (double t : topic) {
System.out.print(t + " ");
}
四、实验
4.1.训练语料
语料来源于网络爬虫采集,总计九个分类,大约269M,每个类别文章不少于3000篇。如下图所示:
4.2训练模型
其中,model.final.twords文件为“词项-主题推断文件”。
4.3.测试实验
4.3.1.主题
下面是待测试出的四个相关主题:
其中:
Topic 0th: | 美妆 |
Topic 1th: | 财经 |
Topic 2th: | 体育 |
Topic 3th: | 数码 |
4.3.2.参数配置
public double alpha = 0.5;
public double beta = 0.02;
public int niters = 1000;
public int savestep = 200;
public int twords = 30;
4.3.3.测试语料
文章ID | 类别 | 标题 | 正文 |
1 | 财经 | “短信托”冲击银行理财 集合信托产品上周收益率8.22% | 羊城晚报讯 据《上海证券报》报道,4月15日—21日一周内共成立了42款集合信托产品,成立规模为68.51亿元,成立数量及规模环比均有下降。当周集合信托产品的平均期限为1.9年,平均收益率为8.22%,比前一周大降1.09个百分点,创下今年以来新低。随着监管趋严,风靡一时的基础产业信托料将发行受挫,信托机构已开始寻求突破。部分信托产品以多期限发行规避“集合信托最低期限一年”的政策限制,正在抢占固定收益类银行理财产品的市场。分析人士指出,信托产品整体收益走低是个长期趋势,短期挑战则主要来自于基础产业信托、房地产信托在一轮需求消化之后发行疲弱,虽然在规模占比上仍是主力,但不少信托公司已开始寻求业务突破。从市场上现有产品的收益情况来看,现金管理类信托产品预期收益要优于可比的货币基金、债券基金,1个月至1年期信托产品预期收益相比同样期限的银行理财产品平均高出1-2个百分点,具有明显优势。 (编辑:吕强) |
2 | 财经 | 广州市民蜂拥抢购黄金 100根金条两小时售罄 | 羊城晚报讯 记者孙晶报道:像买菜一样买黄金?没错,这就是16日广州人买金的情形。随着国际金价“大跳水”,16日,金条价格再次下调,每克下降20元左右,带来市民蜂拥抢购黄金。截至记者发稿时止,广州多家黄金卖场的金条宣布断货,现货黄金价格报收1372美元/盎司,广州市场最便宜的金条价格为275.8元每克,千足首饰金每克也下降30元,为358元。100根金条两小时售罄“生意好到不行,我们一早开门就有大批市民前来排队,队伍一度排到店门外。”广东黄金东风东店的销售人员告诉记者,由于金条需求量大,不少规格已经出现断货,店里新做的100根10克金条不到两小时就售罄了,“由于生意太好,今天我们店要延长营业时间到晚上10点。”“广州人本来就有买金的习俗,现在金价下跌这么多,买实物金总没错。”市民陈姨告诉记者,她作为全家的代表来商场扫货,一下就买了各种规格的金条十多万元。生意好了,销售人员可是累惨了。“我从早上开门到下午4点还没吃上饭,连水都没喝上一口。”广州东山百货相关负责人告诉羊城晚报记者,16日起东山百货的所有金条都出现断货,只能接受预订。“由于前来购买金条的顾客太多,有的顾客就是直接报价格一下子买80万元的金条。所以店内规定连店员的老爸来了都不能插队,必须按照交钱时间来排期拿货。由于货紧,店内目前也只能分期分批补货。”据该人士透露,东百昨日销售黄金超过1000万元,破了11年来的单日销售纪录。不但多家卖金条出名的商家断货,首饰金商家顺带卖的金条也出现断货。记者昨日走访王府井百货内的周大福,该店的销售人员竟然也表示:“金条卖完了。”记者巡城了解,部分商家的金条价格是实时变动,而且手续费不同,昨日一早东百金条的价格为270元每克,加上手续费达到282元每克,中午时分,东百金条的价格已经回升至275.8元每克。首饰金单日降幅最大金条价格连番下跌,首饰金的价格也顶不住了。记者昨日巡城发现,广州全城首饰金价格出现下调。其中,大部分千足金首饰每克从388元下降为358元(未包括手续费),铂金首饰则从478元每克下跌为454元。而K金首饰则纷纷以6.5折左右的折扣吸引消费者。记者走访多家首饰卖场,现场虽然没有买金条的场面火爆,但几乎每家知名首饰店都有多位顾客选购。也有品牌首饰销售人员表示担忧:“销售比平时好,但是金价跌了,有些商品已是亏本销售了。” |
3 | 健身 | 4种易被忽视而又需要的健身 | 据美国《男性健康》杂志最新报道,美国健身教练肖恩・德・威斯皮莱尔表示,多数人喜欢做一些看似轻松的运动,但这样锻炼效果有限,反而是那些易被我们忽视的、令人讨厌的运动,才是我们最需要的。运动比较单一的人不妨多尝试以下4种运动,锻炼全身。 1.平板支撑。平板支撑能锻炼核心肌肉群。在健身间隙可做3次平板支撑,每次30秒。具体动作:俯卧,两肘打开与肩同宽,肘关节支撑于地面,上臂与躯干尽量保持90°。两脚尖并在一起减少支撑面积。颈部自然伸直,眼睛看向前下方,挺胸,头、肩、髋和下肢等部位保持在同一平面,使脊椎骨处于自然生理弯曲的形态。//2.脚踝训练。脚踝是男性锻炼最少的部位之一,脚踝灵活性不足会导致身体缺乏稳定性,使脊椎更易受损。具体动作:双手前伸扶墙,左脚尖抵墙,右腿后伸;身体前倾,尽量使左膝靠近墙壁,坚持10秒,两侧交替进行,每侧3次。3.侧弓箭步。大多数人缺乏侧向运动。侧弓箭步有助于增强身体两侧肌肉,锻炼髋关节灵活性,以及臀部周围肌肉力量。具体动作:站立,两脚打开与肩同宽;左脚向左侧伸出,同时右腿屈膝,身体下蹲,重复10次,然后换另一侧进行。4.箭步蹲。这一动作难度最大,因此很多人避而远之。具体动作:背对凳子站立,距凳子1~2步远,屈左膝使左脚脚背搭于凳子上;身体挺直,右膝弯曲使身体下降,直到左膝几乎触地,然后站直,重复8~12次,再换右腿做。 (!.) .(\"\"); (''[]'')._({:''''}); |
4 | 健身 | 性感腰窝练出来 夏季让你变“背影杀手” | 你有腰窝吗?一如男人性感的人鱼线,有腰窝的女人即便撇开正面的迷人,也总能撩人心弦。而夏季,拥有迷人的腰窝,无疑让你分分钟钟都是男人眼里的“背影杀手”! 在屁屁上方的两个凹下去的窝,就是腰窝,在医学上被称作“麦凯斯菱”。腰窝是理想人体模特的标志之一,一般只有身材匀称的年轻女性才可能有,而且比例极少。关于其成因,一种是可能由遗传所致,另一种则是瘦!这是可以锻炼出来的。因为髂后上棘与皮下组织之间有韧带相互牵拉,外加区域内肌肉等软组织覆盖相对较薄,形成的肉眼可见的凹陷就是“腰窝”。皮下脂肪较少的人的“腰窝”更容易被看到。因此,想要拥有性感的腰窝,你就得瘦!建议在健身运动中将减脂和健身相结合,而且切莫急躁。如何练就性感腰窝呢?这里给大家介绍一组在家就能做的健身动作。反式仰卧起坐图解://动作要领:趴在床上(床不要太软),两个手放在腰侧拉着,依靠腰椎和他下面一点的部分,也就是腰椎部分和臀小肌的力量将上半身挺起大概40°,然后放下,反复15个为一组,每天做3-4组。它主要是运动腰椎两侧的肌肉和臀小肌,可以美化腰背线条,练出腰窝。但运动量一定要够!不要做了几个,或者隔几天做一次,就来抱怨为什么没效果!39健康网(.39.)独家专稿,欢迎分享,请点击获取授权。投稿及合作请联系:020-85501999-8802 (!.) .(\"\"); (''[]'')._({:''''}); |
4.3.4.测试代码
public static void main(String[] args) {
try {
long startTime = System.currentTimeMillis();
LDAOption option = new LDAOption();
option.modleDir = System.getProperty("user.dir") + "/data/model2/" ;
option.est = false;
option.estc = false;
option.modelName = "model-final";
Inferencer inferencer = new Inferencer();
inferencer.init(option);
String car = "";
BufferedReader br = new BufferedReader(new FileReader(System.getProperty("user.dir") + "/data/test.txt"));
List<double[]> groups = new ArrayList<double[]>();
while((car = br.readLine()) != null){
String [] newData = new String[1];
List<Term> terms = ToAnalysis.parse(LDADataset.prepareCleanText(car)).recognition(LDADataset.filter).getTerms();
newData[0] = LDADataset.termToStr(terms);
Model newModel = inferencer.inference(newData);
groups.add(newModel.theta[0]);
}
br.close();
for (int i = 0; i < groups.size(); i++) {
for (int j = i+1; j < groups.size(); j++) {
System.out.println("文章ID:"+ i+ "/"+ j+"余弦相似度:"+ LDAUtils.baoliu(LDAUtils.cosineSimilarity(groups.get(i), groups.get(j)), 5));
}
}
} catch (Exception e) {
// TODO: handle exception
}
}
4.3.5.测试结果
测试结果如下:
文章ID:1/2余弦相似度:0.86092
文章ID:1/3余弦相似度:0.03303
文章ID:1/4余弦相似度:0.01192
文章ID:2/3余弦相似度:0.03602
文章ID:2/4余弦相似度:0.07324
文章ID:3/4余弦相似度:0.97249
从结果中可以看出,文章1和2,3和4比较相似,符合预期结果。
开放原子开发者工作坊旨在鼓励更多人参与开源活动,与志同道合的开发者们相互交流开发经验、分享开发心得、获取前沿技术趋势。工作坊有多种形式的开发者活动,如meetup、训练营等,主打技术交流,干货满满,真诚地邀请各位开发者共同参与!
更多推荐
所有评论(0)