本文将从三个方面介绍LDA主题模型——整体概况、数学推导、动手实现。

关于LDA的文章网上已经有很多了,大多都是从经典的《LDA 数学八卦》中引出来的,原创性不太多。

本文将用尽量少的公式,跳过不需要的证明,将最核心需要学习的部分与大家分享,展示出直观的理解和基本的数学思想,避免数学八卦中过于详细的推导。最后用python 进行实现。

概况

第一部分,包括以下四部分。

  • 为什么需要
  • LDA是什么
  • LDA的应用
  • LDA的使用

为什么需要

挖掘隐含语义信息。一个经典的例子是

“乔布斯离我们而去了。”

“苹果价格会不会降?”

上面这两个句子没有共同出现的单词,但这两个句子是相似的,如果按传统的方法判断这两个句子肯定不相似。

所以在判断文档相关性的时候需要考虑到文档的语义,而语义挖掘的利器是主题模型,LDA就是其中一种比较有效的模型。


LDA是什么

LDA主题模型,首先是在文本分类领域提出来的,它的本意是挖掘文本中的隐藏主题。它将文本看作是词袋模型(文章中的词之间没有关联)产生的过程看成 先选一堆主题,再在主题中选择词,以此构建了一篇文章。

\(d\)是文章

\(z_1 ... z_n\)是主题

\(w\) 是单词

\(\theta_{mk}\)是文档选择主题的概率。

\(\varphi_{kt}\)是主题选择词的概率。



这里新手比较困惑的一点是选来选取,变量是什么?

你可以这样理解,先不要管狄利克雷分布,明确是从topic分布上选取topic,得到各topic的概率,然后再去另一个词的分布上选取刚才得到topic对应的词。



注意:此时不用想这两个分布怎么来的,只要把这个过程能想明白即可。LDA产生文档的过程。

选主题分布->选主题

设置狄利克雷分布参数α->生成主题分布:
设置狄利克雷分布参数β->生成主题的词分布:
生成主题分布->选取主题t:
生成主题t的词分布->生成词:
选取主题t->生成主题t的词分布:

LDA的应用

通过隐含语义找到关联项。

相似文档发现;

推荐商品;将该商品归属的主题下其他商品推荐给用户

主题评分;分析文档主题倾向,看哪个主题比重大


gensim应用

import jieba
import gensim
def load_stop_words(file_path):
stop_words = []
with open(file_path,encoding='utf8') as f:
for word in f:
stop_words.append(word.strip())
return stop_words
def pre_process(data):
# jieba 分词
cut_list = list(map(lambda x: '/'.join(jieba.cut(x,cut_all=True)).split('/'), data))
# 加载停用词 去除 "的 了 啊 "等
stop_words = load_stop_words('stop_words.txt')
final_word_list = []
for cut in cut_list:
# 去除掉空字符和停用词
final_word_list.append(list(filter(lambda x: x != '' and x not in stop_words, cut)))
print(final_word_list)
word_count_dict = gensim.corpora.Dictionary(final_word_list)
# 转成词袋模型 每篇文章由词字典中的序号构成
bag_of_words_corpus = [word_count_dict.doc2bow(pdoc) for pdoc in final_word_list]
print(bag_of_words_corpus)
#返回 词袋库 词典
return bag_of_words_corpus, word_count_dict def train_lda(bag_of_words_corpus, word_count_dict):
# 生成lda model
lda_model = gensim.models.LdaModel(bag_of_words_corpus, num_topics=10, id2word=word_count_dict)
return lda_model # 新闻地址 http://news.xinhuanet.com/world/2017-12/08/c_1122082791.htm train_data = [u"中方对我们的建交国同台湾开展正常经贸和民间往来不持异议,但坚决反对我们的建交国同台湾发生任何形式的官方往来或签署任何带有主权意涵的协定或合作文件",
u"湾与菲律宾签署了投资保障协定等7项合作文件。菲律宾是台湾推动“新南向”政策中首个和台湾签署投资保障协定的国家。",
u"中方坚决反对建交国同台湾发生任何形式的官方往来或签署任何带有主权意涵的协定或合作文件,已就此向菲方提出交涉"]
processed_train_data = pre_process(train_data) lda_model = train_lda(*processed_train_data)
lda_model.print_topics(10)

数学原理

通过上节内容,在工程上基本可以用起来了。但是大家都是有追求的,不仅满足使用。这节简单介绍背后的数学原理。只会将核心部分的数学知识拿出来,不会面面俱到(我觉得这部分理解就足够了)

(详尽内容推荐去看《数学八卦》)

LDA认为各个主题的概率和各个主题下单词的概率不是固定不变的(比如通过设定3个主题的抽取概率为0.3 0.4 0.3 就一直这么用),而是由先验和样本共同通过贝叶斯计算得到的一个分布,同时还会依据不断新增加的样本进行调整。pLSA(LDA的前身) 看待分布情况就是固定的,求完就求完了,而LDA看待分布情况是 不断依据先验和样本调整。

预备知识

下面我们来介绍一下贝叶斯公式

\(P(θ|X) = \frac{P(X|θ)P(θ)}{P(X)}\)

其中

后验概率 \(P(θ|X)\) 就是说在观察到X个样本情况下,θ的概率

先验概率 \(P(θ)\) 人们历史经验,比如硬币正反概率0.5 骰子每个面是1/6

似然函数 \(P(X|θ)\) 在\(θ\)下,观察到X个样本的概率

贝叶斯估计简单来说

先验分布 + 数据的知识 = 后验分布(严格的数学推导请看数学八卦)

\begin{equation}

Beta(p|\alpha,\beta) + Count(m_1,m_2) = Beta(p|\alpha+m_1,\beta+m_2)

\end{equation}

对于选主题,选单词这个过程,LDA将其主题,单词的分布看作是两个后验概率来求解。因为这两个过程每次的结果都和骰子类似,有多种情况,因此是一个多项式分布对应抽样分布\(P(\theta)\),对于多项式为抽样分布来说,狄利克雷分布是它的共轭分布。

先验分布反映了某种先验信息,后验分布既反映了先验分布提供的信息,又反映了样本提供的信息。若先验分布和抽样分布决定的后验分布与先验分布是同类型分布,则称先验分布为抽样分布的共轭分布。当先验分布与抽样分布共轭时,后验分布与先验分布属于同一种类型,这意味着先验信息和样本信息提供的信息具有一定的同一性

  • Beta的共轭分布是伯努利分布;
  • 多项式分布的共轭分布是狄利克雷分布;
  • 高斯分布的共轭分布是高斯分布。

那么狄利克雷分布什么样子?

先介绍\(\Gamma\)函数和\(B\)函数

\[\Gamma(x)=\int_0^{\infty}t^{x-1}e^{-t}dt \\
B(m,n) = \frac{\Gamma(m)\Gamma(n)}{\Gamma(m+n)}\]

狄利克雷分布为下图,其中\(\alpha_1 ... \alpha_n\)就是每个类型的伪先验(按照历史经验和常识,比如骰子每个面都出现10次)


抽取模型

介绍完了基础的数学知识,现在来看下如何得到LDA模型。

因为LDA是词袋模型,各个主题,各个词之间并没有关联,因此我们对于M篇文章,K个主题,可以两次抽取,第一次抽取M个 topics 生成概率,第二次获取K个主题的词生成概率

主题生成概率

\(\vec{\mathbf{z}}\)是topic主题向量

\(\vec{\alpha}\)是在训练时指定的参数

根据贝叶斯参数估计,可以得到主题的分布概率如下

\(\begin{align}
p(\vec{\mathbf{z}} |\vec{\alpha}) & = \prod_{m=1}^M p(\vec{z}_m |\vec{\alpha}) \notag \\
&= \prod_{m=1}^M \frac{\Delta(\vec{n}_m+\vec{\alpha})}{\Delta(\vec{\alpha})}\quad\quad (*)
\end{align}\)

词生成概率

\(p(\vec{\mathbf{w}}|\vec{\mathbf{z}},\vec{\mathbf{\beta}})\) 是在指定的主题\(z\)和给定的参数\(\beta\)下词生成概率 \(z\) 就是上一步得到的主题

\(\begin{align}
p(\overrightarrow{\mathbf{w}} |\overrightarrow{\mathbf{z}},\overrightarrow{\beta}) &= p(\overrightarrow{\mathbf{w}}' |\overrightarrow{\mathbf{z}}',\overrightarrow{\beta}) \notag \\
&= \prod_{k=1}^K p(\overrightarrow{w}_{(k)} | \overrightarrow{z}_{(k)}, \overrightarrow{\beta}) \notag \\
&= \prod_{k=1}^K \frac{\Delta(\overrightarrow{n}_k+\overrightarrow{\beta})}{\Delta(\overrightarrow{\beta})} \quad\quad (**)
\end{align}\)

模型

综合主题模型和词模型得到下面公式,就是LDA模型的分布

在\(\alpha,\beta\)参数下,依据抽取主题->抽取词过程可以得到下面的分布,这个分布就是LDA模型的分布

\(\begin{align}
p(\overrightarrow{\mathbf{w}},\overrightarrow{\mathbf{z}} |\overrightarrow{\alpha}, \overrightarrow{\beta}) &=
p(\overrightarrow{\mathbf{w}} |\overrightarrow{\mathbf{z}}, \overrightarrow{\beta}) p(\overrightarrow{\mathbf{z}} |\overrightarrow{\alpha}) \notag \\
&= \prod_{k=1}^K \frac{\Delta(\overrightarrow{n}_k+\overrightarrow{\beta})}{\Delta(\overrightarrow{\beta})}
\prod_{m=1}^M \frac{\Delta(\overrightarrow{n}_m+\overrightarrow{\alpha})}{\Delta(\overrightarrow{\alpha})} \quad\quad (***)
\end{align}\)

样本生成

虽然我们得到了模型的分布,但是如何获取到符合这个分布的样本(一个具体的实例)?

这里就涉及到采样的知识了,也就是马尔科夫/Gibbs等知识。

简单来说,马尔科夫链中当前状态仅与前一个状态有关,而与其他状态无关

同时对于大部分有转移矩阵P的马氏链(非周期),从任何一个状态转移,最终都会收敛到一个状态







在这里的思路是,构造一个马氏链让其的转移概率等于我们需要的LDA分布。

Gibbs就是一个效率很高的满足这样方式的一个算法。

这块的推导公式可以看数学八卦,不过理解到对其采样即可。

关于Gibbs采样大家可以参考

计算流程为

具体Gibbs采样方程为

\(\begin{equation}
p(z_i = k|\overrightarrow{\mathbf{z}}_{\neg i}, \overrightarrow{\mathbf{w}}) \propto
\frac{n_{m,\neg i}^{(k)} + \alpha_k}{\sum_{k=1}^K (n_{m,\neg i}^{(k)} + \alpha_k)}
\cdot \frac{n_{k,\neg i}^{(t)} + \beta_t}{\sum_{t=1}^V (n_{k,\neg i}^{(t)} + \beta_t)}
\end{equation}\)

有了公式后,算法流程为


代码编写

运行代码前先设置一下train_file.txt 文件,安装numpy

得到了Gibbs计算公式后,对于每个单词来说(每个单词对应一个主题,就是一个(topic,word)元组) 通过每次去除该词topic词本身在分布中的计数得到了条件分布\(P(z=k,w=t|z_{\neg k},w_{\neg t})\)

然后计算得到本次的topic,在放入计算矩阵doc_word_topic中。

以下为代码,注释详尽

#-*- coding:utf-8 -*-

import random
import codecs
import os
import numpy as np
from collections import OrderedDict
import sys print(sys.stdin.encoding) train_file = 'train_file.txt'
bag_word_file = 'word2id.txt' # save file
# doc-topic
phi_file = 'phi_file.txt'
# word-topic
theta_file = 'theta_file.txt' ############################
alpha = 0.1
beta = 0.1
topic_num = 10
iter_times = 100 ##########################
class Document(object):
def __init__(self):
self.words = []
self.length = 0 class DataDict(object):
def __init__(self):
self.docs_count = 0
self.words_count = 0
self.docs = []
self.word2id = OrderedDict() def add_word(self, word):
if word not in self.word2id:
self.word2id[word] = self.words_count
self.words_count += 1 return self.word2id[word] def add_doc(self, doc):
self.docs.append(doc)
self.docs_count += 1 def save_word2id(self, file):
with codecs.open(file, 'w','utf-8') as f:
for word,id in self.word2id.items():
f.write(word +"\t"+str(id)+"\n") class DataClean(object): def __init__(self, train_file):
self.train_file = train_file
self.data_dict = DataDict() '''
input: text-word matrix
'''
def process_each_doc(self):
for text in self.texts:
doc = Document()
for word in text:
word_id = self.data_dict.add_word(word)
doc.words.append(word_id)
doc.length = len(doc.words)
self.data_dict.add_doc(doc) def clean(self):
with codecs.open(self.train_file, 'r','utf-8') as f:
self.texts = f.readlines() self.texts = list(map(lambda x: x.strip().split(), self.texts))
assert type(self.texts[0]) == list , 'wrong data format, texts should be two dimension'
self.process_each_doc() class LDAModel(object): def __init__(self, data_dict): self.data_dict = data_dict
#
# 模型参数
# 主题数topic_num
# 迭代次数iter_times,
# 每个类特征词个数top_words_num
# 超参数alpha beta
#
self.beta = beta
self.alpha = alpha
self.topic_num = topic_num
self.iter_times = iter_times # p,概率向量 临时变量
self.p = np.zeros(self.topic_num) # word-topic_num: word-topic matrix 一个word在不同topic的数量
# topic_word_sum: 每个topic包含的word数量
# doc_topic_num: doc-topic matrix 一篇文档在不同topic的数量
# doc_word_sum: 每篇文档的词数
self.word_topic_num = np.zeros((self.data_dict.words_count, \
self.topic_num),dtype="int")
self.topic_word_sum = np.zeros(self.topic_num,dtype="int")
self.doc_topic_num = np.zeros((self.data_dict.docs_count, \
self.topic_num),dtype="int")
self.doc_word_sum = np.zeros(data_dict.docs_count,dtype="int") # doc_word_topic 每篇文章每个词的类别 size: len(docs),len(doc)
# theta 文章->类的概率分布 size: len(docs), topic_num
# phi 类->词的概率分布 size: topic_num, len(doc)
self.doc_word_topic = \
np.array([[0 for y in range(data_dict.docs[x].length)] \
for x in range(data_dict.docs_count)])
self.theta = np.array([[0.0 for y in range(self.topic_num)] \
for x in range(self.data_dict.docs_count)])
self.phi = np.array([[0.0 for y in range(self.data_dict.words_count)] \
for x in range(self.topic_num)]) #随机分配类型
for doc_idx in range(len(self.doc_word_topic)):
for word_idx in range(self.data_dict.docs[doc_idx].length):
topic = random.randint(0,self.topic_num - 1)
self.doc_word_topic[doc_idx][word_idx] = topic
# 对应矩阵topic内容增加
word = self.data_dict.docs[doc_idx].words[word_idx]
self.word_topic_num[word][topic] += 1
self.doc_topic_num[doc_idx][topic] += 1
self.doc_word_sum[doc_idx] += 1
self.topic_word_sum[topic] += 1 def sampling(self, doc_idx, word_idx): topic = self.doc_word_topic[doc_idx][word_idx]
word = self.data_dict.docs[doc_idx].words[word_idx]
# Gibbs 采样,是去除上一次原本情况的采样
self.word_topic_num[word][topic] -= 1
self.doc_topic_num[doc_idx][topic] -= 1
self.topic_word_sum[topic] -= 1
self.doc_word_sum[doc_idx] -= 1
# 构造计算公式
Vbeta = self.data_dict.words_count * self.beta
Kalpha = self.topic_num * self.alpha
self.p = (self.word_topic_num[word] + self.beta) / \
(self.topic_word_sum + Vbeta) * \
(self.doc_topic_num[doc_idx] + self.alpha) / \
(self.doc_word_sum[doc_idx] + Kalpha) for k in range(1,self.topic_num):
self.p[k] += self.p[k-1]
# 选取满足本次抽样的topic
u = random.uniform(0,self.p[self.topic_num - 1])
for topic in range(self.topic_num):
if self.p[topic] > u:
break
# 将新topic加回去
self.word_topic_num[word][topic] += 1
self.doc_topic_num[doc_idx][topic] += 1
self.topic_word_sum[topic] += 1
self.doc_word_sum[doc_idx] += 1 return topic def _theta(self):
for i in range(self.data_dict.docs_count):
self.theta[i] = (self.doc_topic_num[i]+self.alpha)/ \
(self.doc_word_sum[i]+self.topic_num * self.alpha)
def _phi(self):
for i in range(self.topic_num):
self.phi[i] = (self.word_topic_num.T[i] + self.beta)/ \
(self.topic_word_sum[i]+self.data_dict.words_count * self.beta) def train_lda(self):
for x in range(self.iter_times):
for i in range(self.data_dict.docs_count):
for j in range(self.data_dict.docs[i].length):
topic = self.sampling(i,j)
self.doc_word_topic[i][j] = topic
print("迭代完成。")
print("计算文章-主题分布")
self._theta()
print("计算词-主题分布")
self._phi() def main():
data_clean = DataClean(train_file)
data_clean.clean()
data_dict = data_clean.data_dict
data_dict.save_word2id(bag_word_file)
lda = LDAModel(data_dict)
lda.train_lda() if __name__ == '__main__':
main()

其他参考

1

2

3

LDA主题模型三连击-入门/理论/代码的更多相关文章

  1. 机器学习入门-文本特征-使用LDA主题模型构造标签 1.LatentDirichletAllocation(LDA用于构建主题模型) 2.LDA.components(输出各个词向量的权重值)

    函数说明 1.LDA(n_topics, max_iters, random_state)  用于构建LDA主题模型,将文本分成不同的主题 参数说明:n_topics 表示分为多少个主题, max_i ...

  2. 机器学习入门-贝叶斯构造LDA主题模型,构造word2vec 1.gensim.corpora.Dictionary(构造映射字典) 2.dictionary.doc2vec(做映射) 3.gensim.model.ldamodel.LdaModel(构建主题模型)4lda.print_topics(打印主题).

    1.dictionary = gensim.corpora.Dictionary(clean_content)  对输入的列表做一个数字映射字典, 2. corpus = [dictionary,do ...

  3. 理解 LDA 主题模型

    前言 gamma函数 0 整体把握LDA 1 gamma函数 beta分布 1 beta分布 2 Beta-Binomial 共轭 3 共轭先验分布 4 从beta分布推广到Dirichlet 分布 ...

  4. 通俗理解LDA主题模型

    通俗理解LDA主题模型 0 前言 印象中,最開始听说"LDA"这个名词,是缘于rickjin在2013年3月写的一个LDA科普系列,叫LDA数学八卦,我当时一直想看来着,记得还打印 ...

  5. Gensim LDA主题模型实验

    本文利用gensim进行LDA主题模型实验,第一部分是基于前文的wiki语料,第二部分是基于Sogou新闻语料. 1. 基于wiki语料的LDA实验 上一文得到了wiki纯文本已分词语料 wiki.z ...

  6. 用scikit-learn学习LDA主题模型

    在LDA模型原理篇我们总结了LDA主题模型的原理,这里我们就从应用的角度来使用scikit-learn来学习LDA主题模型.除了scikit-learn,  还有spark MLlib和gensim库 ...

  7. R语言︱LDA主题模型——最优主题数选取(topicmodels)+LDAvis可视化(lda+LDAvis)

    每每以为攀得众山小,可.每每又切实来到起点,大牛们,缓缓脚步来俺笔记葩分享一下吧,please~ --------------------------- 笔者寄语:在自己学LDA主题模型时候,发现该模 ...

  8. Spark:聚类算法之LDA主题模型算法

    http://blog.csdn.net/pipisorry/article/details/52912179 Spark上实现LDA原理 LDA主题模型算法 [主题模型TopicModel:隐含狄利 ...

  9. LDA主题模型(理解篇)

    何谓“主题”呢?望文生义就知道是什么意思了,就是诸如一篇文章.一段话.一个句子所表达的中心思想.不过从统计模型的角度来说, 我们是用一个特定的词频分布来刻画主题的,并认为一篇文章.一段话.一个句子是从 ...

随机推荐

  1. objc_runtime使用方法的几个简单例子(转)

    1. 给NSObject类动态添加属性h定义部分 [cpp] @interface UIWebView (LoadProgress)  @property (nonatomic, assign) NS ...

  2. 解决pip安装模块报错Cannot fetch index base URL http://pypi.python.org/simple/

    产生这个问题的原因呢和github一样,因为他们用的cdn被墙.经小伙伴反馈,解决办法如下. 通过指定国内镜像源来安装: pip --trusted-host 镜像源 install 模块名 -i 镜 ...

  3. python 中NumPy和Pandas工具包中的函数使用笔记(方便自己查找)

    二.常用库 1.NumPy NumPy是高性能科学计算和数据分析的基础包.部分功能如下: ndarray, 具有矢量算术运算和复杂广播能力的快速且节省空间的多维数组. 用于对整组数据进行快速运算的标准 ...

  4. 获取当前AppDelegate 正在显示的UIViewController

    UIWindow+PazLabs.h (header file) #import <UIKit/UIKit.h> @interface UIWindow (PazLabs) - (UIVi ...

  5. sedna进行xquery查询

    有一个文件book.xml: <books> <book> <name>The Call Of Wild</name> <author>Ja ...

  6. Codeforces Beta Round #25 (Div. 2)--A. IQ test

    IQ test time limit per test 2 seconds memory limit per test 256 megabytes input standard input outpu ...

  7. Android无线测试之—UiAutomator UiDevice API介绍四

    拖拽与滑动 一.概念介绍: 1)拖拽:将组建从一个坐标移动到另一个坐标 2)移动:从一二坐标点移动到另一个坐标点 3)步长:从一点滑动到另一点使用的时间 二.拖拽与滑动的相关API: 返回值 方法名 ...

  8. Oracle raw数据类型

    RAW的声明方式为RAW(L),L为长度,以字节为单位,它存数的是16进制的数据.作为数据库列最大2000,作为变量最大32767字节. RAW类型的好处就是:在网络中的计算机之间传输 RAW 数据时 ...

  9. webService通过response和request对象传输文件

    <code class=" hljs java">package gacl.response.study; 2 3 import java.io.IOException ...

  10. HDU4027(Can you answer these queries?)

    题目链接:传送门 题目大意:有一个长度为n的数组,有m次操作,每次操作输入 v x y,v==0时x~y区间内的数都开平方并且向下取整,v==1时求x~y区间内所有数的和. 题目思路:long lon ...