LDA 学习笔记

2023-11-16

1.LDA算法的介绍

1.1 算法的基本知识

隐含狄利克雷分布(Latent Dirichlet Allocation,LDA)

​ 和机器学习中的线性判别分析(Linear Discriminant Analysis)不一样

首先是对狄利克雷分布的大致介绍

​ 其就是对多项分布的先验分布.先验分布就是指对于可能出现的分布给出一个预设分布(根据背景知识),然后根据实际的样本信息去更新这个分布,最终得到后验分布.

beta分布是二项分布的先验分布,dirichlet分布则是多项分布的先验分布

​ 从另一个方面来讲,狄利克雷分布就是分布的分布.也就是说,在使用多项分布时你所选用的多项分布也是由另一个分布选取的.

​ 使用 AnkurTomar文章中的例子就是你有一批骰子,每一个骰子都是不均匀的,因此在进行掷骰子的实验时,骰子本身就是一个多项分布,选择哪一个骰子也是基于一个分布.这样最终得到的结果就是基于分布的分布也就是dirichlet分布.

LDA主题模型的一些假设前提

​ 每一篇文章由多个主题的分布组成

​ 每一个主题会包含一组词的分布

​ 文章的每个词来自于一个确定的主题

LDA模型的介绍

​ 模型是一个词袋模型,其认为文章是由一组词构成的一个集合.不关心文章中词的先后顺序.每一篇文章会由多个主题组成(主题分布θ),而对于每一个主题,又包括了各单词的出现概率φ

此时就可以获取某一个单词在文章中的出现概率:

​ p(w|d) = p(w|t) * p(t|d)

其中 p(w|t) 可以由分布 φ求得,p(t|d)可以由分布θ求得

模型的获取过程

最初提出LDA时:模型的更新使用的是EM算法,即先固定一个分布,对另一个分布进行更新,更新完成后再对另一个分布进行更新.(这也是sklearn中实现的方法)

​ 对文档中的任意一个词,计算其在任意的topic下的p(w|t),然后就可以句topic对应的p(t|d),获取每一个opic下的p(w|d),取最大的那个,作为当前的词的topic.

具体过程为(来源于wiki百科)

1.2 吉布斯采样

吉布斯采样(Gibbs sampling)是统计学中用于马尔科夫蒙特卡洛(MCMC)的一种算法 .

蒙特卡洛和拉斯维加斯方法:

这是两类随机算法的统称
蒙特卡洛算法的思想在于每一次的采样结束后都会得到一个当前的最优算法,因此,采样越多获得的结果越接近最优解
拉斯维加斯方法则是有一个明确的求解目标,每一次的采样都会增大获得最优解的可能,因此这种方法可能会得不到解.

是基于多变量的条件概率分布,来近似抽取联合分布的样本序列.

也就是对多变量的条件分布进行连续采样的算法.

吉布斯采样的三种用途:

​ 近似联合分布

​ 部分变量的边缘分布

​ 计算某一变量的期望值

具体的含义通过吉布斯采样的实现过程来解释.

在介绍采样的过程之前,需要先了解马尔科夫链

马尔科夫链

​ 就是对于一个序列(包含先后顺序的样本集合),从上一个状态到下一个状态随机转移的过程.转移的过程是不具有记忆性的,即下一个时刻的状态只会由当前时刻的状态来决定,与历史的状态没有关系.因此就需要根据状态转移矩阵来获取下一时刻的状态.

​ 而这个状态转移矩阵其实就是一个条件分布

采样过程

摘自维基百科

在这里插入图片描述

如果对全部的变量进行采样时,得到的就是所有变量的联合分布.

如果只有一部分变量需要变化,得到的是边缘分布

还可以对采样获得的所有样本求取某一变量的平均值来估计该变量的期望

1.3 LDA中的吉布斯采样

这一个系列的LDA的介绍相当的详细

首先基于先验概率 φ和θ,分别是 主题-词分布以及 文章-主题分布

联合分布概率是p(w,z),其中w是已知的,就是直接观测到的文本数据,而z则是隐含的主题分布.其实就是想要知道P(z|w)

对于每一个词的在其他所有词的词和话题的条件下,话题为k的概率为:

在这里插入图片描述

-i 表示去除词i之后剩余的其他所有词

文章中对于上述概率进行了详细的推导

在这里插入图片描述
最终得到:

在这里插入图片描述
其中n(k)m,¬i表示去除样本i后标记为话题k的词的数量

n(k)m,¬i则表示去除样本i后,词t的个数

1.4 模型的训练过程

首先随机初始化所有的词的topic(或者根据预设的分布来确定初始的topic)

重新扫描语料库,对每一个词重新试验吉布斯采样的格式获取他的topic

重复采样过程直到收敛

最终统计样本的topic-word矩阵以及 article-topic矩阵

有时间还是要看看这篇经典的文章(这是知乎上的翻译文章)

Parameter estimation for text analysis

2 完整的模型代码:

import re
import time
import jieba
import jieba.posseg as pseg
import numpy as np
import numpy
from sklearn.externals import joblib
from sklearn.feature_extraction.text import CountVectorizer
from sklearn.decomposition import LatentDirichletAllocation
from sklearn.model_selection import GridSearchCV
def read_data(fname):
    '''
    输入的数据是按照:
        title   content answer  tag tag1
    存储的,在训练lda时,只需要使用title和answer.
    :return: list 每一项是一个问题以及其所有的答案,使用.分隔
    '''
    dic = {}
    with open(fname,'r',encoding='utf8') as f:
        for i in f:
            lst = i.strip().split('\t')
            if lst[0] not in dic:
                dic[lst[0]] = re.sub('[。?!,、….?!:]$','',lst[2])
            else:
                dic[lst[0]] += '.' + re.sub('[。?!,、….?!:]$','',lst[2])
    corpus = [re.sub('[。?!,、….?!:]$','',key) + '.' + dic[key] for key in dic]
    return corpus

def jieba_cut(corpus,cut_file,stopwords_file):
    #结巴分词,并去除停用词(停用词为网上找到的中文停用词库),最后存储在cut_file中
    stopwords = []
    with open(stopwords_file,'r',encoding='utf8') as f:
        for i in f:
            stopwords.append(i.strip())
    corpus_cut = []
    n = 0
    for s in corpus:
        s_cut = [w for w in jieba.cut(s) if w not in stopwords]
        corpus_cut.append(' '.join(s_cut))
        n += 1
        if n % 10000 == 0:
            print(n)
    f1 = open(cut_file, 'a', encoding='utf8')
    for i in corpus_cut:
        f1.write(i + '\n')
    f1.close()
    return corpus_cut

    
    
def vec_model(cut_file):
    with open(cut_file, 'r', encoding='utf8') as f:
        corpus_cut = [i.strip() for i in f.readlines()]
    tf_vectorizer = CountVectorizer(max_df=0.95,min_df=2,stop_words='english')
    x = tf_vectorizer.fit_transform(corpus_cut)
    joblib.dump(tf_vectorizer,tf_ModelPath )
    return x,tf_vectorizer,corpus_cut




def read_vec_model(cut_file,tf_ModelPath):
    # 直接加载模型
    with open(cut_file, 'r', encoding='utf8') as f:
        corpus_cut = [i.strip() for i in f.readlines()]
    tf_vectorizer = joblib.load(tf_ModelPath)
    x = tf_vectorizer.fit_transform(corpus_cut)
    return x,tf_vectorizer,corpus_cut



def train(vec_data,tf_model,n_topics = 14,max_iter = 10,learning_method= 'batch'):
    '''
    训练lda模型并存储
    :param vec_data:
    :param n_topics:
    :param max_iter:
    :param learning_method:
    :return: 返回最终的lda模型
    '''
    lda = LatentDirichletAllocation(n_topics=n_topics,max_iter=max_iter,learning_method=learning_method,max_doc_update_iter=5)
    print('train')
    a = time.time()
    lda.fit(vec_data)
    print(time.time() - a)
    n_top_words = 20
    tf_feature_names = tf_model.get_feature_names()
    print_top_words(lda, tf_feature_names, n_top_words)
    joblib.dump(lda, lda_ModelPath)
    return lda



def print_top_words(model, feature_names, n_top_words):
    #打印每个主题下权重较高的term
    for topic_idx, topic in enumerate(model.components_):
        print( "Topic #%d:" % topic_idx)
        print( " ".join([feature_names[i]for i in topic.argsort()[:-n_top_words - 1:-1]]))
    print()
    print(model.components_)

    
    
def grid_search(vec_data,tf_vectorizer,parameters):
    GridSearchCV

    
    
def jieba_cut_transform(file):
    #读取结巴分词的数据,然后将其中的英文和字母全部去除
    with open(file,'r',encoding='utf8') as f:
        ret_list = f.readlines()
        f1 = open('./jieba_cut_all_drop.txt','a',encoding='utf8')
        lst = []
        for i in [[re.sub(r'[^\u4e00-\u9fa5]+','',j) for j in i.strip().split(' ') if re.sub(r'[a-zA-Z0-9]+','',j)] for i in ret_list]:
            f1.write(' '.join(i) + '\n')
        f1.close()

调用函数训练模型

lda_ModelPath = './lda_model2_all'
tf_ModelPath = './tf_model1_all'
fname = '../train_data'
cut_file = './jieba_cut_all.txt'
stopwords_file = './stopwords'

#读取数据
corpus = read_data(fname)
#分词
cut_data = jieba_cut(corpus, cut_file, stopwords_file)
#训练词向量模型
vec_data,tf_vectorizer,cut_data = vec_model(cut_file)
##这是直接读取数据的函数
#vec_data,tf_vectorizer,cut_data = read_vec_model(cut_file, tf_ModelPath)
lda = train(vec_data,tf_vectorizer,max_iter=50)
# 计算困惑度
# lda = joblib.load(lda_ModelPath)
p = lda.perplexity(vec_data)
print(p)
test_data = vec_data[:10]
ret = lda.transform(vec_data[:10])
print(ret)
print(ret.argmax(1))
print(cut_data[:10])
print(len(cut_data[:10]))

3.sklearn源码的学习

3.1 LDA类的方法

在这里插入图片描述

功能说明:

_check_params:
	检查传入的参数是否满足要求
_init_latent_vars:
	将输入的参数转换为训练时需要使用的参数
_e_step
	em算法中的e步
_em_step
	使用em算法迭代
_check_non_neg_array:
	检查是否包含负数
partial_fit
	Online VB with Mini-Batch update
fit
	训练模型
transform
	使用模型对数据进行预测
_approx_bound
	估算log-likelihood
score
	使用log-likelihood作为评价指标的分数
perplexity
	计算混淆度指标
3.2 LDA的参数介绍
class LatentDirichletAllocation(BaseEstimator, TransformerMixin):
    """Latent Dirichlet Allocation with online variational Bayes algorithm

    .. versionadded:: 0.17

    Read more in the :ref:`User Guide <LatentDirichletAllocation>`.

    Parameters
    ----------
    n_topics : int, optional (default=10)
        话题的数量。
    doc_topic_prior : float, optional (default=None)
       	文档主题分布的先验‘theta’。如果值为None,
		默认为“1 / n_topics”。
		在文献中,这被称为“阿尔法”。
    topic_word_prior : float, optional (default=None)
        主题词分布的先验' beta '。如果该值为None,则为默认值
		' 1 / n_topics '。
		在文献中,这被称为“eta”。
    learning_method : 'batch' | 'online', default='online'
        方法用于更新' _component '。只用于' fit '方法。
		一般来说,如果数据量很大,在线更新就会比批量更新快。
		默认的学习方法将在0.20版本中更改为“batch”。
        Valid options::

            'batch': 分批变分贝叶斯法。使用所有的培训数据
					每个EM更新。
					旧的‘components_’将在每次迭代中被覆盖。
            'online': 在线变分贝叶斯法。在每个EM更新中使用
					更新“components_”的小批培训数据
					变量增量。学习速率由
					' learning_decay ' '和' learning_offset ' '参数。

    learning_decay : float, optional (default=0.7)
        		它是在线学习方法中控制学习速率的一个参数
				。值应该设置在(0.5,1.0)之间以保证
				渐近收敛。当值为0.0且batch_size为n_samples时
				,更新方法与批量学习相同。在文学,这叫做kappa。

    learning_offset : float, optional (default=10.)
        一个(正的)参数,它降低了在线学习中早期迭代的权重。它应该大于1.0。在文献中,这称为tau_0。

    max_iter : integer, optional (default=10)
			最大迭代次数。
    total_samples : int, optional (default=1e6)
        文件总数。只在' partial_fit '方法中使用。

    batch_size : int, optional (default=128)
        在每个EM迭代中使用的文档数量。仅用于在线学习。

    evaluate_every : int optional (default=0)
        在每个EM迭代中使用的文档数量。仅用于在线学习。
只用于' fit '方法。将其设为0或负数,则根本不能评价训练的复杂程度。对perplexity的评估可以帮助你在训练过程中检查收敛性,但是也会增加训练的总时间。在每次迭代中评估perplexity可能会增加两倍的训练时间。

    perp_tol : float, optional (default=1e-1)
        批量学习中的困惑容忍。仅当' ' evaluate_every ' '大于0时使用。

    mean_change_tol : float, optional (default=1e-3)
		停止E-step中更新文档主题分布的容忍。
    max_doc_update_iter : int (default=100)
        E-step中更新文档主题分布的最大迭代次数。

    n_jobs : int, optional (default=1)
        要在E-step中使用的作业数。如果是-1,则使用所有cpu。对于-1以下的' ' n_jobs ' ',则使用(n_cpu + 1 + n_jobs)。

    verbose : int, optional (default=0)
        冗长的水平。

    random_state : int or RandomState instance or None, optional (default=None)
		伪随机数发生器种子控制。
    Attributes
    ----------
    components_ : array, [n_topics, n_features]
       主题字分布。components_[i, j] '代表主题i中的单词j。

    n_batch_iter_ : int
        EM步骤的迭代次数。

    n_iter_ : int
        通过数据集的次数。
    """

    def partial_fit(self, X, y=None):
        """Online VB with Mini-Batch update.

        Parameters
        ----------
        X : array-like or sparse matrix, shape=(n_samples, n_features)
            Document word matrix.

        Returns
        -------
        self
        """
        self._check_params()
        X = self._check_non_neg_array(X,
                                      "LatentDirichletAllocation.partial_fit")
        n_samples, n_features = X.shape
        batch_size = self.batch_size

        # initialize parameters or check
        if not hasattr(self, 'components_'):
            self._init_latent_vars(n_features)

        if n_features != self.components_.shape[1]:
            raise ValueError(
                "The provided data has %d dimensions while "
                "the model was trained with feature size %d." %
                (n_features, self.components_.shape[1]))

        n_jobs = _get_n_jobs(self.n_jobs)
        with Parallel(n_jobs=n_jobs, verbose=max(0, self.verbose - 1)) as parallel:
            for idx_slice in gen_batches(n_samples, batch_size):
                self._em_step(X[idx_slice, :],
                              total_samples=self.total_samples,
                              batch_update=False,
                              parallel=parallel)

        return self

    

    

    

    
3.3 fit部分
def fit(self, X, y=None):
        """Learn model for the data X with variational Bayes method.

        When `learning_method` is 'online', use mini-batch update.
        Otherwise, use batch update.

        Parameters
        ----------
        X : array-like or sparse matrix, shape=(n_samples, n_features)
            Document word matrix.

        Returns
        -------
        self
        """
        #数据的准备
        self._check_params()
        X = self._check_non_neg_array(X, "LatentDirichletAllocation.fit")
        n_samples, n_features = X.shape
        max_iter = self.max_iter
        evaluate_every = self.evaluate_every
        learning_method = self.learning_method
        if learning_method == None:
            warnings.warn("The default value for 'learning_method' will be "
                          "changed from 'online' to 'batch' in the release 0.20. "
                          "This warning was introduced in 0.18.",
                          DeprecationWarning)          
            learning_method = 'online'
        batch_size = self.batch_size
        # initialize parameters
        self._init_latent_vars(n_features)
        # change to perplexity later
        #上一次训练的困惑度
        last_bound = None
        n_jobs = _get_n_jobs(self.n_jobs)
        
        with Parallel(n_jobs=n_jobs, verbose=max(0, self.verbose - 1)) as parallel:
            for i in xrange(max_iter):
                #在online模式下,划分为batch_size大小的数据集训练,batch_update为False
                #在batch模式下batch_update为True
                if learning_method == 'online':
                    for idx_slice in gen_batches(n_samples, batch_size):
                        self._em_step(X[idx_slice, :], total_samples=n_samples,
                                      batch_update=False, parallel=parallel)
                else:
                    # batch update
                    self._em_step(X, total_samples=n_samples,
                                  batch_update=True, parallel=parallel)

                # check perplexity,根据evaluate_every的设定值,来判断是否在训练的时候检查困惑度以及检查的频率,检查困惑度会花费大量的时间.
                if evaluate_every > 0 and (i + 1) % evaluate_every == 0:
                    doc_topics_distr, _ = self._e_step(X, cal_sstats=False,
                                                       random_init=False,
                                                       parallel=parallel)
                    bound = self.perplexity(X, doc_topics_distr,
                                            sub_sampling=False)
                    if self.verbose:
                        print('iteration: %d, perplexity: %.4f'
                              % (i + 1, bound))

                    if last_bound and abs(last_bound - bound) < self.perp_tol:
                        break
                    last_bound = bound
                self.n_iter_ += 1
        return self

使用到的函数:

    def _check_non_neg_array(self, X, whom):
        """check X format

        check X format and make sure no negative value in X.

        Parameters
        ----------
        X :  array-like or sparse matrix

        """
        X = check_array(X, accept_sparse='csr')
        #检查是否包含负数
        check_non_negative(X, whom)
        """
        check_non_negative:
        X = X.data if sp.issparse(X) else X
        if (X < 0).any():
           raise ValueError("Negative values in data passed to %s" % whom)
        """            
        return X
"""
Parallel:
    并行处理数据的类,使用到的两个参数的作用为:
	n_jobs:
    并发运行作业的最大数量,例如后端=“多处理”时Python工作进程的数量或后端=“线程化”时线程池的大小。如果-1,则使用所有cpu。如果给定1,则根本不使用并行计算代码,这对于调试非常有用。对于小于-1的n_jobs,则使用(n_cpu + 1 + n_jobs)。因此,对于n_jobs = -2,将使用除一个cpu之外的所有cpu。
    verbose:
    冗余级别:如果非零,则打印进度消息。在50以上,输出被发送到stdout。消息的频率随冗长级别的增加而增加。如果大于10,则报告所有迭代。
"""
def gen_batches(n, batch_size):
    """Generator to create slices containing batch_size elements, from 0 to n.

    The last slice may contain less than batch_size elements, when batch_size
    does not divide n.

    Examples
    --------
    >>> from sklearn.utils import gen_batches
    >>> list(gen_batches(7, 3))
    [slice(0, 3, None), slice(3, 6, None), slice(6, 7, None)]
    >>> list(gen_batches(6, 3))
    [slice(0, 3, None), slice(3, 6, None)]
    >>> list(gen_batches(2, 3))
    [slice(0, 2, None)]
    """
    start = 0
    for _ in range(int(n // batch_size)):
        end = start + batch_size
        yield slice(start, end)
        start = end
    if start < n:
        yield slice(start, n)
3.4 EM 算法迭代
 def _em_step(self, X, total_samples, batch_update, parallel=None):
        """EM update for 1 iteration.

        update `_component` by batch VB or online VB.

        Parameters
        ----------
        X : array-like or sparse matrix, shape=(n_samples, n_features)
            Document word matrix.

        total_samples : integer
            Total umber of documents. It is only used when batch_update is `False`.

        batch_update : boolean
            Parameter that controls updating method.
            `True` for batch learning, `False` for online learning.

        parallel : joblib.Parallel
            预先初始化的joblib.Parallel实例。

        Returns
        -------
        doc_topic_distr : array, shape=(n_samples, n_topics)
            非规范化文档主题分布.
        """

        # E-step
        #返回值是文章-话题分布:
        _, suff_stats = self._e_step(X, cal_sstats=True, random_init=True,
                                     parallel=parallel)
        # M-step
        #如果是batch模式,则
        if batch_update:
            #使用刚刚得到的文章话题分布更新话题词频的分布
            self.components_ = self.topic_word_prior_ + suff_stats
        else:
            # online update
            # In the literature, the weight is `rho`
            #np.power第一个参数对第二个参数取幂
            weight = np.power(self.learning_offset + self.n_batch_iter_,
                              -self.learning_decay)
            doc_ratio = float(total_samples) / X.shape[0]
            #上一次的结果变为1-w倍,当前的累加值系数为w
            self.components_ *= (1 - weight)
            self.components_ += (weight * (self.topic_word_prior_
                                           + doc_ratio * suff_stats))

        # update `component_` related variables
        self.exp_dirichlet_component_ = np.exp(
            _dirichlet_expectation_2d(self.components_))
        self.n_batch_iter_ += 1
        return

E步:

def _e_step(self, X, cal_sstats, random_init, parallel=None):
        """E-step in EM update.

        Parameters
        ----------
        X : array-like or sparse matrix, shape=(n_samples, n_features)
            Document word matrix.

        cal_sstats : boolean
            参数,指示是否计算足够的统计信息。当需要运行M-step时,将' ' cal_sstats ' '设置为True。

        random_init : boolean
            参数,指示是否在e -步骤中随机初始化文档主题分布。在训练步骤中将它设置为True。

        parallel : joblib.Parallel (optional)
            预先初始化的joblib.Parallel实例。

        Returns
        -------
        (doc_topic_distr, suff_stats) :
            `doc_topic_distr` 为每个文档的非规范化主题分布。在文献中,这叫做 `gamma`.
            `suff_stats` 预计M-step有足够的统计数据。当' cal_sstats == False '时,它将为None。

        """

        # Run e-step in parallel
        random_state = self.random_state_ if random_init else None

        # TODO: make Parallel._effective_n_jobs public instead?
        # 获取计算所需的作业数。
        n_jobs = _get_n_jobs(self.n_jobs)
        if parallel is None:
            #如果为空则创建一个parallel对象
            parallel = Parallel(n_jobs=n_jobs, verbose=max(0, self.verbose - 1))
        #使用parallel对象并行执行更新过程
        #delayed是parallel中用于捕获函数参数的装饰器。 
		#_update_doc_distribution则是最终需要调用来更新的函数
        #parallel的call方法需要的参数是要执行的函数的可迭代序列
        results = parallel(
            delayed(_update_doc_distribution)(X[idx_slice, :],
                                              self.exp_dirichlet_component_,
                                              self.doc_topic_prior_,
                                              self.max_doc_update_iter,
                                              self.mean_change_tol, cal_sstats,
                                              random_state)
            for idx_slice in gen_even_slices(X.shape[0], n_jobs))

        # merge result
        doc_topics, sstats_list = zip(*results)
        doc_topic_distr = np.vstack(doc_topics)

        if cal_sstats:
            # This step finishes computing the sufficient statistics for the
            # M-step.
            suff_stats = np.zeros(self.components_.shape)
            for sstats in sstats_list:
                suff_stats += sstats
            suff_stats *= self.exp_dirichlet_component_
        else:
            suff_stats = None

        return (doc_topic_distr, suff_stats)
3.5 迭代函数
def _update_doc_distribution(X, exp_topic_word_distr, doc_topic_prior,
                             max_iters,
                             mean_change_tol, cal_sstats, random_state):
    """E-step: 更新文档主题分布.

    Parameters
    ----------
    X : array-like or sparse matrix, shape=(n_samples, n_features)
        文档-词矩阵.

    exp_topic_word_distr : dense matrix, shape=(n_topics, n_features)
        对数主题词分布期望的指数值。在文献中,这是 `exp(E[log(beta)])`.

    doc_topic_prior : float
        文档主题分发的优先级 `theta`.

    max_iters : int
        Max number of iterations for updating document topic distribution in
        the E-step.

    mean_change_tol : float
        停止E-setp中更新文档主题分布的容忍。

    cal_sstats : boolean
        指示是否计算足够统计量的参数。
        Set `cal_sstats` to `True` when we need to run M-step.

    random_state : RandomState instance or None
        参数,指示如何初始化文档主题分布。
        Set `random_state` to None will initialize document topic distribution
        to a 常数.

    Returns
    -------
    (doc_topic_distr, suff_stats) :
        `doc_topic_distr` is unnormalized topic distribution for each document.
        In the literature, this is `gamma`. we can calculate `E[log(theta)]`
        from it.
        `suff_stats` is expected sufficient statistics for the M-step.
            When `cal_sstats == False`, this will be None.

    """
    is_sparse_x = sp.issparse(X)
    n_samples, n_features = X.shape
    n_topics = exp_topic_word_distr.shape[0]

    if random_state:
        doc_topic_distr = random_state.gamma(100., 0.01, (n_samples, n_topics))
    else:
        doc_topic_distr = np.ones((n_samples, n_topics))

    # In the literature, this is `exp(E[log(theta)])`
    exp_doc_topic = np.exp(_dirichlet_expectation_2d(doc_topic_distr))

    # diff on `component_` (only calculate it when `cal_diff` is True)
    suff_stats = np.zeros(exp_topic_word_distr.shape) if cal_sstats else None

    if is_sparse_x:
        #是否是稀疏的
        X_data = X.data
        X_indices = X.indices
        X_indptr = X.indptr

    for idx_d in xrange(n_samples):
        if is_sparse_x:
            ids = X_indices[X_indptr[idx_d]:X_indptr[idx_d + 1]]
            cnts = X_data[X_indptr[idx_d]:X_indptr[idx_d + 1]]
        else:
            ids = np.nonzero(X[idx_d, :])[0]
            cnts = X[idx_d, ids]

        doc_topic_d = doc_topic_distr[idx_d, :]
        # The next one is a copy, since the inner loop overwrites it.
        exp_doc_topic_d = exp_doc_topic[idx_d, :].copy()
        exp_topic_word_d = exp_topic_word_distr[:, ids]

        # Iterate between `doc_topic_d` and `norm_phi` until convergence
        for _ in xrange(0, max_iters):
			#迭代max_iters次
            last_d = doc_topic_d
            #迭代公式如下:
            # The optimal phi_{dwk} is proportional to
            # exp(E[log(theta_{dk})]) * exp(E[log(beta_{dw})]).
            norm_phi = np.dot(exp_doc_topic_d, exp_topic_word_d) + EPS
            doc_topic_d = (exp_doc_topic_d *
                           np.dot(cnts / norm_phi, exp_topic_word_d.T))
            # Note: adds doc_topic_prior to doc_topic_d, in-place.
            _dirichlet_expectation_1d(doc_topic_d, doc_topic_prior,
                                      exp_doc_topic_d)

            if mean_change(last_d, doc_topic_d) < mean_change_tol:
                #如果两次的变化值小于设定的阈值,停止迭代
                break
        doc_topic_distr[idx_d, :] = doc_topic_d

        # Contribution of document d to the expected sufficient
        # statistics for the M step.
        if cal_sstats:
            norm_phi = np.dot(exp_doc_topic_d, exp_topic_word_d) + EPS
            #np.outer两个向量的外积
            suff_stats[:, ids] += np.outer(exp_doc_topic_d, cnts / norm_phi)

    return (doc_topic_distr, suff_stats)
3.6 transform
def transform(self, X):
        """Transform data X according to the fitted model.

        Parameters
        ----------
        X : array-like or sparse matrix, shape=(n_samples, n_features)
            Document word matrix.

        Returns
        -------
        doc_topic_distr : shape=(n_samples, n_topics)
            Document topic distribution for X.
        """
        if not hasattr(self, 'components_'):
            #判断是否存在两个分布矩阵 
            raise NotFittedError("no 'components_' attribute in model."
                                 " Please fit model first.")
        # make sure feature size is the same in fitted model and in X
        #对传入数据的特征数量
        X = self._check_non_neg_array(X, "LatentDirichletAllocation.transform")
        n_samples, n_features = X.shape
        if n_features != self.components_.shape[1]:
            raise ValueError(
                "The provided data has %d dimensions while "
                "the model was trained with feature size %d." %
                (n_features, self.components_.shape[1]))
        doc_topic_distr, _ = self._e_step(X, cal_sstats=False,random_init=False)
        # normalize doc_topic_distr
        doc_topic_distr /= doc_topic_distr.sum(axis=1)[:, np.newaxis]
        return doc_topic_distr
3.7 评价指标
	def _approx_bound(self, X, doc_topic_distr, sub_sampling):
        """Estimate the variational bound.

        仅使用作为x传递的文档估计“所有文档”的变分界,因为每个单词的log-likelihood不能直接计算,所以我们使用这个界来估计。

        Parameters
        ----------
        X : array-like or sparse matrix, shape=(n_samples, n_features)
            Document word matrix.

        doc_topic_distr : array, shape=(n_samples, n_topics)
            Document topic distribution. In the literature, this is called
            gamma.

        sub_sampling : boolean, optional, (default=False)
            Compensate for subsampling of documents.
            It is used in calculate bound in online learning.

        Returns
        -------
        score : float

        """

        def _loglikelihood(prior, distr, dirichlet_distr, size):
            # calculate log-likelihood
            score = np.sum((prior - distr) * dirichlet_distr)
            score += np.sum(gammaln(distr) - gammaln(prior))
            score += np.sum(gammaln(prior * size) - gammaln(np.sum(distr, 1)))
            return score

        is_sparse_x = sp.issparse(X)
        n_samples, n_topics = doc_topic_distr.shape
        n_features = self.components_.shape[1]
        score = 0

        dirichlet_doc_topic = _dirichlet_expectation_2d(doc_topic_distr)
        dirichlet_component_ = _dirichlet_expectation_2d(self.components_)
        doc_topic_prior = self.doc_topic_prior_
        topic_word_prior = self.topic_word_prior_

        if is_sparse_x:
            X_data = X.data
            X_indices = X.indices
            X_indptr = X.indptr

        # E[log p(docs | theta, beta)]
        for idx_d in xrange(0, n_samples):
            if is_sparse_x:
                ids = X_indices[X_indptr[idx_d]:X_indptr[idx_d + 1]]
                cnts = X_data[X_indptr[idx_d]:X_indptr[idx_d + 1]]
            else:
                ids = np.nonzero(X[idx_d, :])[0]
                cnts = X[idx_d, ids]
            temp = (dirichlet_doc_topic[idx_d, :, np.newaxis]
                    + dirichlet_component_[:, ids])
            norm_phi = logsumexp(temp)
            score += np.dot(cnts, norm_phi)
        # compute E[log p(theta | alpha) - log q(theta | gamma)]
        score += _loglikelihood(doc_topic_prior, doc_topic_distr,
                                dirichlet_doc_topic, self.n_topics)
        # Compensate for the subsampling of the population of documents
        if sub_sampling:
            doc_ratio = float(self.total_samples) / n_samples
            score *= doc_ratio
        # E[log p(beta | eta) - log q (beta | lambda)]
        score += _loglikelihood(topic_word_prior, self.components_,
                                dirichlet_component_, n_features)
        return score
################################################################################
    def score(self, X, y=None):
        """Calculate approximate log-likelihood as score.

        Parameters
        ----------
        X : array-like or sparse matrix, shape=(n_samples, n_features)
            Document word matrix.

        Returns
        -------
        score : float
            Use approximate bound as score.
        """

        X = self._check_non_neg_array(X, "LatentDirichletAllocation.score")

        doc_topic_distr = self.transform(X)
        score = self._approx_bound(X, doc_topic_distr, sub_sampling=False)
        return score
################################################################################
    def perplexity(self, X, doc_topic_distr=None, sub_sampling=False):
        """Calculate approximate perplexity for data X.

        Perplexity is defined as exp(-1. * log-likelihood per word)

        Parameters
        ----------
        X : array-like or sparse matrix, [n_samples, n_features]
            Document word matrix.

        doc_topic_distr : None or array, shape=(n_samples, n_topics)
            Document topic distribution.
            If it is None, it will be generated by applying transform on X.

        Returns
        -------
        score : float
            Perplexity score.
        """
        if not hasattr(self, 'components_'):
            raise NotFittedError("no 'components_' attribute in model."
                                 " Please fit model first.")

        X = self._check_non_neg_array(X,
                                      "LatentDirichletAllocation.perplexity")

        if doc_topic_distr is None:
            doc_topic_distr = self.transform(X)
        else:
            n_samples, n_topics = doc_topic_distr.shape
            if n_samples != X.shape[0]:
                raise ValueError("Number of samples in X and doc_topic_distr"
                                 " do not match.")
            if n_topics != self.n_topics:
                raise ValueError("Number of topics does not match.")
        current_samples = X.shape[0]
        bound = self._approx_bound(X, doc_topic_distr, sub_sampling)
        if sub_sampling:
            word_cnt = X.sum() * (float(self.total_samples) / current_samples)
        else:
            word_cnt = X.sum()
        perword_bound = bound / word_cnt
        return np.exp(-1.0 * perword_bound) 

4.参数更新

LDA能够更新的参数

根据源码可以看到其包含的参数如下:

 	n_topics : 话题的数量。
        
    doc_topic_prior : 文档主题分布的先验
    topic_word_prior : 主题词分布的先验
        
    learning_method : 'batch' | 'online', default='online'
            'batch': 分批变分贝叶斯法。使用所有的培训数据更新每个EM。
					旧的‘components_’将在每次迭代中被覆盖。
            'online': 在线变分贝叶斯法。在每个EM中使用小批量数据更新“components_”的变量增量。学习速率由 learning_decay和learning_offset。
                
	learning_decay :(default=0.7)在线学习方法中控制学习速率的一个参数。值应该设置在(0.5,1.0)之间以保证渐近收敛。当值为0.0且batch_size为n_samples时,更新方法与批量学习相同。

    learning_offset : (default=10.)一个(正的)参数,它降低了在线学习中早期迭代的权重。它应该大于1.0。

    max_iter : (default=10)最大迭代次数。
        
    total_samples :(default=1e6)文件总数。只在' partial_fit '方法中使用。

    batch_size : (default=128)在每个EM迭代中使用的文档数量。仅用于在线学习。

    evaluate_every : (default=0)在每个EM迭代中使用的文档数量。仅用于在线学习。
只用于' fit '方法。将其设为0或负数,则根本不能评价训练的复杂程度。对perplexity的评估可以帮助你在训练过程中检查收敛性,但是也会增加训练的总时间。在每次迭代中评估perplexity可能会增加两倍的训练时间。(设置这个参数后,会在循环的计数器i对evaluate_every取余为0,输出一次混淆度)

    perp_tol : float, optional (default=1e-1)
        批量学习中的困惑容忍。仅当evaluate_every大于0时使用。

    mean_change_tol :(default=1e-3)
		停止E-step中更新文档主题分布的容忍。
    max_doc_update_iter : (default=100)
        E-step中更新文档主题分布的最大迭代次数。

    n_jobs : int, optional (default=1)
        要在E-step中使用的作业数。如果是-1,则使用所有cpu。对于-1以下的' ' n_jobs ' ',则使用(n_cpu + 1 + n_jobs)。

    verbose : int, optional (default=0)
        冗长的水平。

    random_state : (default=None) 伪随机数发生器种子控制。
    Attributes
    ----------
    components_ : array, [n_topics, n_features]
       主题字分布。components_[i, j] 代表主题i中的单词j。

    n_batch_iter_ : int
        EM步骤的迭代次数。
    n_iter_ : int
        通过数据集的次数。

因此在训练时主要需要调节的参数就是:

1.n_topics这就相当于聚类的个数

2.learning_method 这个是用来控制训练的模式的,如果选择了online模式,还需要对其他的一些参数进行调节,来控制训练的学习率等参数

3.最大迭代次数,包括每一次em算法中的停止迭代的条件和迭代次数以及对于数据集的迭代次数

4.n_jobs是调节并行迭代的参数

5.如果想在训练的过程中观察模型的指标的变化情况,需要对evaluate_every进行设置,但是不会影响模型的结果,训练的时间反而会大幅度的增加.

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

LDA 学习笔记 的相关文章

  • 飞凌RZ/G2L的开发板实上的时视频编码推流设计与实现

    飞凌RZ G2L的开发板测评 实时视频编码推流设计与实现 大信 QQ 8125036 在完成RZ G2L板上视频采集的试用测试基础上 逐渐熟悉了RZ G2L开发板的SDK 在研究过它的音视频硬件与软件包后 想进一步利用该开发板做音视频的深度

随机推荐

  • YOLOv5算法原理与网络结构

    YOLOv5算法原理与网络结构 1 1 YOLOv5算法 YOLOv5算法共有4种网络结构 分别是YOLOv5s YOLOv5m YOLOv5l和YOLOv5x 这四种网络结构在宽度和深度上不同 原理上基本一样 接下来以 YOLOv5s 为
  • 【MySQL】 Linux平台MySQL安装

    Linux平台MySQL安装 太淦了 Linux平台MySQL安装 方式1 使用包管理器进行自动安装 方式2 编译源代码安装 1 下载源代码 1 1在 官网 https dev mysql com downloads mysql 下载源代码
  • 黑盒测试靶机(cmcc)以及对应系统加固方案

    目录 一 信息收集 二 进入web界面 三 redis 6379连接 四 连接蚁剑成功 这是个沙箱 五 redis入手 使用密钥连接 六 尝试docker越狱 七 针对docker进行入侵排查 从系统层面 1 检查进程 2 木马样本可以保留
  • 高通MDM平台-ChargeIC

    Charge IC 是负责管理外部供电以及电池充放电 在MDM平台上采用的是比较便宜的ChargeIC MP2617 其作用也仅限于控制对电池充放电 下面就简单的介绍下内核中关于chargeIC的驱动 以及dts配置 内核DTS 关于cha
  • python中close函数的用法_python中调用open函数打开文件,使用close函数关闭文件,调用什么函数可以实现对文件内容的读取。...

    参考答案如下 中调范仲淹任杭州太守时 遭遇饥荒 他采取的方式是 函e函函数茶多酚 TP 主要包括 数打使用数关什实现茶多酚的主要作用是 开文茶在植物学上的分类位置属于 件内茶多酚对人体的功效 茶太烫时不要用嘴去吹 闭文为了快速冷却你可以用另
  • 成员内部类、静态内部类、局部内部类、匿名内部类的精髓与应用

    目录 1 成员内部类 坐拥外部类 1 1 定义和使用 1 2 优势 2 静态内部类 悠然独立 2 1 定义和使用 2 2 优势 3 局部内部类 精巧隐藏 3 1 定义和使用 3 2 优势 4 匿名内部类 神秘而优雅 4 1 定义和使用 4
  • Java-使用线程池创建多线程

    Java 使用线程池创建多线程 1 概念 提前创建好多个线程 放入线程池总 使用时直接获取 使用完后放入池中 可以避免频繁创建销毁 实现重复利用 corePoolSize 核心池的大小 maximumPoolSize 最大线程数 keepA
  • Angular-组件

    Angular 组件 一 组件是Angular应用的主要构造块 组件包括 1 一个HTML模板 用于声明页面要渲染的内容 2 一个用于定义行为的TypeScript的类 3 一个CSS选择器 定义组件在模板中的使用方式 4 要应用在模板上的
  • 配置Log4j(很详细)

    来自 http www blogjava net zJun archive 2006 06 28 55511 html Log4J的配置文件 Configuration File 就是用来设置记录器的级别 存放器和布局的 它可接key va
  • 机器学习sklearn之贝叶斯网络实战(一)

    贝叶斯网络 贝叶斯网络 信念网络 贝叶斯模型或概率定向无环图形模型是一种概率图形模型 一种统计模型 通过有向无环图 DAG 表示一组随机变量及其条件依赖关系 当我们想要表示随机变量之间的因果关系时 主要使用贝叶斯网络 贝叶斯网络使用条件概率
  • linux查看某天的日志,LINUX查看某段时间的日志

    其一 sed 截选时间段日志 sed n 开始时间 结束时间 p 日志文件 使用sed命令如下 sed n 2020 05 04 09 25 55 2015 05 04 09 28 55 p logfile 这样可以精确地截取出来某个时间段
  • python学习笔记---常用内建模块【廖雪峰】

    常用内建模块 datetime Python处理日期和时间的标准库 datetime表示的时间需要时区信息才能确定一个特定的时间 否则只能视为本地时间 如果要存储datetime 最佳方法是将其转换为timestamp再存储 因为times
  • GD32F303调试小记(九)之FreeRTOS移植

    前言 距离上一次更新GD32系列的文章已经过了一年有余 按照之前的想法 仅仅介绍到GD32中常用的模块就结束了 在后续的开发中 有幸再次能使用这颗IC作为主控 所以既为了自己做个随笔 也为方便各位同行或是同学借鉴 这段时间我会编写几篇文章主
  • freeRTOS出现任务卡死的情况。

    最近在做一个产品二代升级的项目 代码是上一任工程师留下的 很多BUG 而且融合了HAL库和LL库 以及github上下载的GSM源码 很不好用 我这边是将2G模块换成了4G 且添加了单独的BLE模块 因此只在源码的基础上 去除2G和BLE代
  • 论坛直击

    关注ITValue 看企业级最新鲜 最价值报道 2020年对中国而言 是极其特殊的一年 新年伊始 一场突如其来的疫情席卷全国 各行各业均受到不同程度的冲击 房地产开发由于不能实地展业 影响尤甚 为此 在中国房地产业协会的支持下 中国房协数字
  • nginx配置多个前端项目,使用同一个后端

    这几天一直在搞nginx配置的东西 踩了很多坑 特此记录 前后端分离的项目有两种部署方案 第一种是把前端打包好的dist文件夹放在后端的static下面 然后打包后端 在服务器运行后端的这个jar包 没什么好说的 第二种是使用nginx配置
  • key-value store

    1 key value store key value分布式存储系统查询速度快 存放数据量大 支持高并发 非常适合通过主键进行查询 但不能进行复杂的条件查询 数据存储形式为
  • Linux I/O多路复用——poll模型实现服务端Socket通信

    目录 poll函数 参数说明 events相关 与select的不同 程序流程 程序实例 poll函数 poll模型在实现服务端时思路是和select类似的 可以说poll是select的加强版 poll函数原型如下 int poll st
  • c++ 用vs2019编译mxnet1.9.0

    一 编译环境之windows10 编译出动态库libmxnet dll libmxnet lib mxnet 70 lib及include头文件 一 环境及依赖 1 Windows10 2 Cuda11 1 3 Cudnn8 1 0 77
  • LDA 学习笔记

    1 LDA算法的介绍 1 1 算法的基本知识 隐含狄利克雷分布 Latent Dirichlet Allocation LDA 和机器学习中的线性判别分析 Linear Discriminant Analysis 不一样 首先是对狄利克雷分