-----------七月在线机器学习笔记
目录
LDA(latent Dirichlet Allocation)
在前面的贝叶斯网络中谈到朴素贝叶斯垃圾邮件分类问题
当然,朴素贝叶斯可以胜任许多文本分类问题,但它也有很多局限:
1.无法解决语料中一词多义和多词一义的问题——它更像是词法分析,而非语义分析
2.如果使用词向量作为文档的特征,一词多义和多词一义会造成计算文档间相似度的不准确性
这时,可以通过增加“主题”的方式,一定程度上解决上述问题:
一个词可能被映射到多个主题中——一词多义
多个词可能被映射到某个主题的概率很高——多词一义
因此,有学者提出基于概率统计的pLSA模型(probabilistic latent semantic analysis,概率隐语义分析),增加了主题模型,形成简单的贝叶斯网络,另外对于隐变量主题z可以使用EM算法学习模型参数。
pLSA模型
D代表文档,Z代表主题(隐含类别),W代表单词;
P(di)表示文档di的出现概率
P(zk|di)表示文档di中主题zk的出现概率
P(wj|zk)表示给定主题zk出现单词wj的概率。
每个主题在所有词项上服从多项分布,每个文档在所有主题上服从多项分布。[一个主题有多个词,一篇文档有多个主题]
整个文档的生成过程(类似于人的写作):
以P(di)的概率选中文档di ;
以P(zk|di)的概率选中主题zk;
以P(wj|zk)的概率产生一个单词wj 。
观察数据为对,主题
是隐含变量
的联合分布为
(1)
(2)
贝叶斯网络tail-to-tail型,而对应了两组多项分布
计算每个文档的主题分布,就是该模型的任务目标。
极大似然估计:N-文档数,M-单词数,K-主题数
,wj在di中出现的次数n,记为n(di,wj)是可以得到的
对数似然函数为【将(1)(2)式带入】:
(3)
目标函数分析:
如(3)式所示,p(di)是先验概率;未知变量/自变量
同理,跟EM算法的目标函数类似(对数函数中含有加和,不便于求解),因此可以使用逐次逼近/坐标上升的方法:
E-step:假定已知,求隐含变量
的后验概率
M-step:在已知的前提下,求关于参数
的似然函数期望的极大值,得到最优解
,带回上一步,循环迭代直到收敛。
E-step:假定已知,求隐含变量,主题
的后验概率
(4)
------------贝叶斯定理及加和规则
M-step:先对目标函数进行分析
(5)
观察上式可知,第二部分为常量(均可先验得到)
故令新的目标函数为
(6)
则其似然函数期望为
(7)
新的目标函数建立完成:
关于参数的函数E,并且,带有概率加和为1的约束条件:
(8)
显然,这是只有等式约束的求极值问题,应用Lagrange乘子法
Lagrange函数为:
(9)
分别对未知变量求偏导,等于0
[对于制定的j,k,i求偏导,对应的求和∑会消去,因为只与指定i,j,k有关,其它均为常数,求导为0]
(10)
同理,
(11)
对(10),(11)式分别化简,并结合约束条件s.t. 分别得到下面公式(13)和(15),
(12)
(13)
------------------------------------------------------------------------------------------------------
(14)
(15)
将公式(13)回带到(12); 公式(15)回带到(14)即得的更新式:
也就是最终的M-step的更新:
(16)
(17)
别忘了之前的E-step的更新公式(4):
(4)
综上,pLSA应用于信息检索、过滤、自然语言处理等领域,pLSA考虑到词分布和主题分布,使用EM算法来学习参数。
LDA(latent Dirichlet Allocation)
两种认识:给定某系统的若干样本,求该系统的参数
频率学派--矩估计/MLE/MaxEnt/EM等:假设参数是某个/某些未知的定值,求这些参数如何取值使得某目标函数取极大/极小
贝叶斯学派--假设参数本身是变化的,服从某个分布。求在该分布约束条件下的参数值使得目标函数极大/极小
无高低好坏之分,只是认识自然的手段。只是在当前人们掌握 的数学工具和需解决的实践问题中,贝叶斯学派的理论体系往 往能够比较好的解释目标函数、分析相互关系等。
贝叶斯定理:通过将观察到的数据融合,将先验概率转化为后验概率
给定某系统的若干样本x,计算该系统的参数,即
P(θ):没有数据支持下,θ发生的概率:先验概率。P(θ|x):在数据x的支持下,θ发生的概率:后验概率。
P(x|θ):给定某参数θ的概率分布:似然函数。
P(x|θ)可以看作参数向量θ的函数,表达了在不同θ下,观测数据出现的可能性的大小
由于x为给定样本,P(x)有时被称为“证据”,仅仅是归一化因子,如果不关心P(θ|x)的具体值,只考察θ取何值时后验概率 P(θ|x)最大,则可将分母省去。
共轭性--在贝叶斯概率理论中,如果后验概率P(θ|x)和先验概率p(θ)满足同样的分布律。
--先验分布和后验分布被叫做共轭分布,同时,先验分布叫做似然函数的共轭先验分布。
共轭先验分布的实践意义:
根据贝叶斯理论:,
似然函数P(x|θ)可以直接求得;先验分布P(θ)也可以根据先验知识获得。
那么,从哪里获得先验知识呢?
--选取似然函数P(x|θ)的共轭先验作为 P(θ)的分布,这样,P(x|θ)乘以P(θ) (然后归一化)得到的P(θ|x)的形式和P(θ)的形式一样。
举例:
投掷一个非均匀硬币,可以使用参数为θ的伯努利模型,θ为硬币为正面的概率,那么结果x的分布形式为:
,即伯努利分布(二项分布) (18)
两点分布/二项分布的共轭先验是Beta分布, 它具有两个(超)参数α和β,Beta分布形式为
(19)
,
Beta分布的均值和方差为
根据贝叶斯理论计算后验概率:
(20)
可知,伯努利分布的共轭先验是Beta分布
我们看到,如果⼀个数据集⾥有x次观测为“正(1)”,有1-x次观测为“反(0)”,那么从先验概率到后验概率,α的值变⼤了x,β的值变⼤了1-x。这让我们可以简单地把先验概率中的超参数α和β分别 看成“正(1)”和“反(0)”的有效观测数(effective number of observation)/“伪计数”。
共轭先验的直接推广:
从2到K:--二项分布——>多项分布;Beta分布——>Dirichlet分布
多项分布: (21)
Dirichlet分布:
,α表示向量,多项分布的参数。0≤μk≤1
,注意, 由于加和的限制,{µk}空间上的分布被限制在K − 1维的单纯形(simplex)当中
简记为,
(22)
注意, 由于加和的限制,{µk}空间上的分布被限制在K − 1维的单纯形(simplex)当中。图2.4给出了K=3时的情形
图2.5给出了在不同的参数αk的情况下,单纯形上的狄利克雷分布的图像。
对称狄利克雷分布
一个非常常见的特殊情况是对称狄利克雷分布,其中构成参数向量的所有元素都有相同的值。对称Dirichlet分布通常在需要Dirichlet先验时使用,因为通常不存在优先于某个组件的先验知识。
因为所有参数向量的元素有相同的值------分布可以用一个标量值参数化α,称为concentration parameter(聚集参数)
此时,狄利克雷分布转化为,
(24)
对称狄利克雷分布的参数分析
如图2.5所示, 当α<1时 ,
的概率增大;
当α=1时 ,退化为均匀分布;
当α>1时, p1=p2=…=pk的概率增大
用似然函数 公式 (22)乘以先验(21),得到关于参数{μk}的后验分布
(23)
(24)
,多项分布的参数。
我们看到后验分布的形式又变成了狄利克雷分布,这说明,狄利克雷分布确实是多项式分布的共轭先验。
LDA的解释
LDA的详细解释:
K为主题个数,M为文档总数,Nm是第m个文档的单词总数。
β是每个Topic下词的多项分布的Dirichlet先验参数,
α是每个文档下Topic的多项分布的Dirichlet先验参数。
是第m个文档中第n个词的主题,
是m个文档中的第n个词。
表示第m篇文章的主题分布(是一个多项分布,超参数α服从Dirichlet分布),k维(k为Topic总数) 向量;
表示第k个主题对应的词分布(也是一个多项分布,超参数β服从Dirichlet分布),v维向量(v为词典中term总数)
----多项分布共轭先验分布为Dirichlet分布,故
,
均可由对应的Dirichlet分布确定
~字典中共有V个term(不可重复),这些term出现在具体的文章中,就是word
——在具体某文章中的word当然是有可能重复的。~语料库中共有m篇文档d1,d2…dm;
~对于文档di,由Ni个word组成,可重复;
~语料库中共有K个主题T1,T2…Tk;
~α和β为先验分布的参数,一般事先给定: 如取0.1的对称Dirichlet分布
——表示在参数学习结束后,期望每个文档的主题不会十分集中。
参数的学习
给定一个文档集合,是可以观察到的已知变量,α和β是根据经验给定的先验参数,其他的变量
、
、
都是未知的隐含变量,需要根据观察到的变量来学习估计的。根据LDA的图模型,可以写出所有变量的联合分布【前面贝叶斯网络的知识】:
似然概率
一篇文章m的一个词初始化为一个词t(不重复)的概率是(文章m出现主题k的概率 乘以 主题k下出现词t的概率,然后枚举所有主题求和得到)
整个文档集合的似然函数为(bag of words,假设词是相对独立的):
Gibbs Sampling
吉布斯采样的每个步骤涉及到将⼀个变量的值替换为以剩余变量的值为条件,从这个概率分布中抽取的那个变量的值。因此我们将
替换为从概率分布
中抽取的 值,其中
表⽰z的第i个元素,
表⽰除
以外的元素。
这个步骤要么按照某种特定的顺序在变量之间进⾏循环,要么每⼀步中按照某个概率分布随机地选择⼀个变量进⾏更新。
对于该步骤能够从所需的概率分布中采样的理论依据(证明)------可以参考PRML第11 章节-采样
Gibbs Sampling算法的运行方式是每次选取概率向量的一个维度, 给定其他维度的变量值采样当前维度的值。不断迭代,直到收敛输出待估计的参数。
1)初始时随机给文本中的每个词分配主题,然后统计每个主题z下出现词t的数量以及每个文档m下出现主题z的数量,每一轮计算
,即排除当前词的主题分布:
——根据其他所有词的主题分布估计当前词分配各个主题的概率。
{ 首先为每个词t随机分配一个主题
,那么所有文档中的每个词都有一个对应的主题
-----那么,词t在不同文档中所属的主题并不一定相同,即词t所属的主题可能有K个(K为主题数量)
然后就可以统计出每个主题下面出现词t的数量
-----这样就可以计算出词t属于每个主题对应的概率,然后再去按照这个概率去采样它属于的一个新的主题
【注意:并不是按概率最大去采样,只是采样时的相对可能性】
以及每个文档m中出现主题z的数量。
}
2)当得到当前词属于所有主题z的概率分布后,根据这个概率分布为该词采样一个新的主题。
3)用同样的方法更新下一个词的主题,直到发现每个文档的主题分布和每个主题的词分布
收敛,算法停止; 输出待估计的参数
和
,同时每个单词的主题
也可同时得出。
4)实际应用中会设置最大迭代次数。每一次计算的公式称 为Gibbs updating rule。
PRML 中的一个示例:
联合分布
根据LDA的图模型结合贝叶斯网络性质,可进行下面转换,
,第一项因子即给定主题采样词的过程
计算因子:
表示词t被观察到分配给主题z的次数,
表示主题k分配给文档m的次数。
,由公式(22)化简可得
计算因子:
Gibbs updating rule
词分布和主题分布
LDA总结
1)由于在词和文档之间加入的主题的概念,可以较好的解决一词多义和多词一义的问题。
2)在实践中发现,LDA用于短文档往往效果不明显
——这是可以解释的:因为一个词被分配给某个主题的次数和一个主题包括 的词数目往往无法收敛。往往需要通过其他方案“连接”成长文档。
如,用户评论 ;Twitter/微博
3)LDA可以和其他算法想结合。如:使用LDA,可以将长度为Ni 的文档“降维”到K维(主题的数目),同时可以给出每个主题的概 率(主题分布),因此可以将其使用if-idf(词频-逆文档频率)继续分析
或者直接作为文档的特征进入聚类或者标签传播算法—— 用于社区挖掘或其他目的。
代码实现
读取的文件是从网上荡的小说txt文件;停用词也是从网上荡的,然后放到了excel里
import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
import pylab
import jieba
from pandas import DataFrame, Series
plt.rcParams['font.sans-serif'] = ['SimHei'] #指定默认字体
plt.rcParams['axes.unicode_minus'] = False #解决保存图像是负号'-'显示为方块的问题
'''
文档数目:M
词数目:V(非重复的,“term”)
主题数目:K
用d表述第几个文档,k表示主题,w表示词汇
(term),n表示词(word)
z[d][w]:第d篇文档的第w个词来自哪个主题。M行,X列,X为相应文档长度:即词(可重复)的数目。
nw[w][t]:第w个词是第t个主题的次数。word-topic矩阵,列向量nw[][t]表示主题t的词频数分布;V行K列
nd[d][t]:第d篇文档中第t个主题出现的次数,doc-topic矩阵,行向量nd[d]表示文档d的主题频数分布。
M行,K列。
辅助向量:
ntSum[t]:第t个主题在所有语料出现的次数,K维
ndSum[d]:第d篇文档中词的数目(可重复),M维;
P[t]:对于当前计算的某词属于主题t的概率,K维。
'''
import random
class LDA(object):
def __init__(self,alpha,beta,doc_num,topic_num):
self.alpha=alpha
self.beta=beta
self.doc_num=doc_num
self.topic_num=topic_num
self.stopwords=self.load_stopwords()
self.dict,self.doc=self.read_document(['datasets/all.txt'],1,self.stopwords)
self.token_num = len(self.dict) # 词汇数目(不重复)
self.nt = np.zeros((self.token_num, self.topic_num)) # 初始化,记录每个token属于主题t的次数
print(self.nt.shape)
# nt=[[0 for t in range(topic_num)] for token in range(token_num)]
self.nd = np.zeros((doc_num, self.topic_num)) # 记录每篇文档出现的主题次数
# nd=[[0 for t in range(topic_num)] for d in range(doc_num)]
self.nt_sum = np.zeros((self.topic_num, 1)) # 每个主题在所有语料中出现的次数
self.nd_sum = np.zeros((doc_num, 1)) # nd_sum[d]:每篇文档出现的word数目
def load_stopwords(self):
df=pd.read_excel('datasets/stop_words.xlsx')
stopwords = list(df['sw'])
print(df)
return stopwords
def read_document(self,filePath,doc_num,stopwords):
dict = {}
doc=[]
for i in range(doc_num):
with open(filePath[i],'r',encoding='utf-8') as f:
t=f.readlines()
f.close()
word_list = []
for i in range(len(t)):
tt=jieba.cut(t[i].strip())
# ww=[]
for word in tt:
if word not in stopwords:
if word !='\n':
# ww.append(word)
word_list.append(word)#每篇doc的单词
if word not in dict:
dict[word]=len(dict)#生成字典(token:index)
# word_list.append(ww)
doc.append(word_list)
print(len(dict),len(doc[0]))
# df=pd.DataFrame(word_list)
# df.to_csv('datasets/output1.csv',index=False)#分好的词按行写入csv文件
return dict,doc
# return doc
def init_topic(self,doc):
z=[[np.random.randint(0,self.topic_num,1)[0] for i in range(len(doc[d]))] for d in range(len(doc))]#随机给文档中的每个词分配主题
for d in range(len(doc)):
self.nd_sum[d]=len(doc[d])
for i in range(len(doc[d])):
print(self.dict[doc[d][i]],z[d][i])
self.nt[self.dict[doc[d][i]],z[d][i]]+=1#统计每个token属于topic的次数
self.nd[d,z[d][i]]+=1#文档中的word出现的对应主题的次数
self.nt_sum=np.sum(self.nt,axis=1)#主题在语料中出现的次数
return z
def gibbs_sampling(self,z,m,i,nt,nd,nt_sum,nd_sum,token):
topic=z[m][i]#为指定词分配主题
nt[token,topic]-=1#去除当前词p(z非i)
nd[m,topic]-=1
nt_sum[topic]-=1
nd_sum[m]-=1
topic_alpha=self.topic_num*self.alpha
token_beta=len(self.dict)*self.beta
p=np.zeros((self.topic_num,1))#p[k]:词token属于主题k的概率
for k in range(self.topic_num):
p[k]=(nd[m,k]+self.alpha)/(nd_sum[m]+topic_alpha)*(nt[token,k]+self.beta)/(nt_sum[k]+token_beta)#Gibbs updating rule
if k >=1:
p[k]+=p[k-1]#累加概率
gs=np.random.random()*p[self.topic_num-1]#为该词采样一个新的主题(随机给定一个(0,1)的数 乘以 概率最大值本身 得到 一个新的主题概率)
new_topic=0
while new_topic<self.topic_num:
if p[new_topic]>gs:#判断gs落在了哪个区间段内,那么该段对应的就是新的主题
break
new_topic+=1
nt[token,new_topic]+=1#还原
nd[m,new_topic]+=1
nt_sum[new_topic]+=1
nd_sum[m]+=1
z[m][i]=new_topic#更新
def cal_theta(self,nd,nd_sum):##计算每个文档的主题分布
doc_num=len(nd)
topic_alpha=self.topic_num*self.alpha
theta=np.zeros((doc_num,self.topic_num))
for m in range(doc_num):
for k in range(self.topic_num):
theta[m,k]=(nd[m,k]+self.alpha)/(nd_sum[m]+topic_alpha)
return theta
def cal_phi(self,nt,nt_sum):##计算每个主题的词分布
token_num = len(nt)
token_beta = token_num * self.beta
phi = np.zeros((self.topic_num,token_num))
for k in range(self.topic_num):
for token in range(token_num):
phi[k, token] = (nt[token, k] + self.beta) / (nt_sum[k] + token_beta)
return phi
def lda(self,z,nt,nd,nt_sum,nd_sum,doc):
doc_num=len(z)
for time in range(50):#正式的终止条件应当是:每个文档的主题分布θi和每个主题的词分布φj收敛,即变化处于一个可接受的阈值内(如1e-5)
for m in range(doc_num):
doc_len=len(z[m])
for i in range(doc_len):
token=self.dict[doc[m][i]]
self.gibbs_sampling(z,m,i,nt,nd,nt_sum,nd_sum,token)
theta=self.cal_theta(nd,nd_sum)#计算每个文档的主题分布
phi=self.cal_phi(nt,nt_sum)#计算每个主题的词分布
return theta,phi
def show_result(self,theta,phi,dict):
# phi = np.zeros((self.topic_num, token_num))
# theta = np.zeros((doc_num, self.topic_num))
sort_phi=np.argsort(phi,axis=0)
sort_phi=sort_phi[:,::-1]
sort_theta=np.argsort(theta,axis=0)
sort_theta=sort_theta[:,::-1]
with open('datasets/output11.txt','w',encoding='utf-8') as f:
for i in range(self.doc_num):
print('doc%d:'%i)
f.write('doc%d:'%i)
for k in range(self.topic_num):
print('topic%d(%s)'%(k,theta[i,sort_theta[i,k]]))
f.write('topic%d(%s)'%(k,theta[i,sort_theta[i,k]]))
print()
f.write('\n')
f.write('===========================================\n')
for i in range(self.topic_num):
print('topic%d:' % i)
f.write('topic%d:' % i)
for j in range(10):
print(list(self.dict.keys())[list(self.dict.values()).index(sort_phi[i,j])],phi[i,sort_phi[i,j]])
f.write('%s(%s)'%(list(self.dict.keys())[list(self.dict.values()).index(sort_phi[i,j])],phi[i,sort_phi[i,j]]))
print()
f.write('\n')
f.close()
def start(self):
#LDA
z=self.init_topic(self.doc)
theta,phi=self.lda(z,self.nt,self.nd,self.nt_sum,self.nd_sum,self.doc)
print(theta,phi)
self.show_result(theta,phi,self.dict)
lda=LDA(0.1,0.1,1,10)
lda.start()
超参数的确定
交叉验证
α表达了不同文档间主题是否鲜明,β度量了有多少近义词能够属于同一个类别。
给定主题数目K,可以使用:
α=50/K
β=0.01
注:不一定普遍适用
一种迭代求超参数的方法