跳至主要内容

使用 Python 实现潜在语义分析

在本教程中,您将学习如何用 Python 的潜在语义分析从给定文档中发现隐藏主题。
· 11分钟 阅读

使用 AI 探索

主题发现在多种场景中都很有用,例如对文档进行聚类、为信息检索与推荐而组织在线内容。许多内容平台和新闻机构使用主题模型向读者推荐文章。同样,招聘公司也用它来抽取职位描述并与候选人的技能进行匹配。以数据科学家为例,其工作核心就是从大量收集的数据中提取“知识”。而这些数据通常是非结构化的。您需要强大的工具与技术来分析并理解海量的非结构化数据。

主题建模是一种文本挖掘技术,它通过识别共现关键词来概括大规模文本信息集合。它有助于发现文档中的隐藏主题,用这些主题为文档加注,并组织大量非结构化数据。

主题建模

主题建模会自动从给定文档中发现隐藏的主题。它是一种无监督的文本分析算法,用于在给定文档中寻找一组词语,这些词语集合代表一个主题。一个文档可能同时关联多个主题。例如,一组词如“patient”“doctor”“disease”“cancer”“health”将代表“医疗健康”主题。与使用正则表达式的基于规则的文本检索不同,主题建模是另一种方法。

diagram

文本分类与主题建模的比较

文本分类是一个有监督的机器学习问题,即将文本文档或文章划分到预先定义的一组类别中。主题建模的过程是发现文本中共现词语的集合,这些彼此相关且共现的词语构成“主题”。它是一种无监督学习,因而可能的主题集合未知。主题建模也可用于解决文本分类问题:主题建模会识别文档中“出现的主题”,而文本分类则将文本划入某个单一类别。

diagram

潜在语义分析

LSA(Latent Semantic Analysis,潜在语义分析)亦称 LSI(Latent Semantic Index,潜在语义索引)。LSA 使用词袋(BoW)模型,生成词项-文档矩阵(词项在文档中的出现情况)。行表示词项,列表示文档。LSA 通过对文档-词项矩阵进行奇异值分解来学习潜在主题。LSA 通常用作降维或降噪技术。

Latent Semantic Analysis

奇异值分解(SVD)

SVD 是一种矩阵分解方法,将一个矩阵表示为两个(更准确地说,三个)矩阵的乘积。它在信号处理、心理学、社会学、气候与大气科学、统计学和天文学中都有广泛应用。

factor
  • M 是一个 m×m 矩阵
  • U 是一个 m×n 的左奇异矩阵
  • Σ 是一个 n×n 的对角矩阵,元素为非负实数
  • V 是一个 m×n 的右奇异矩阵
  • V* 是一个 n×m 的矩阵,为 V 的转置

单位矩阵:主对角线元素全为 1,其余元素全为 0 的方阵。

对角矩阵:主对角线以外的元素全为 0 的矩阵。

奇异矩阵:行列式为 0 的矩阵,或不可逆的方阵。

确定最佳主题数

在主题建模中,如何确定 k(主题数)是最佳的?在给定语料中识别最佳主题数是一项挑战。我们可以采用以下方法来确定最佳主题数:

  • 将每个主题视为一个簇,并使用轮廓系数评估聚类效果。
  • 主题一致性(coherence)度量是识别主题数量的一种更为现实的指标。

主题一致性是评估主题模型的常用指标。它基于潜变量模型。每个生成的主题对应一组词。在一致性度量中,您需要计算该主题内词语两两相似度得分的平均值/中位数。主题一致性分数越高,模型越好。

使用 Gensim 实现 LSA

导入所需库

#import modules
import os.path
from gensim import corpora
from gensim.models import LsiModel
from nltk.tokenize import RegexpTokenizer
from nltk.corpus import stopwords
from nltk.stem.porter import PorterStemmer
from gensim.models.coherencemodel import CoherenceModel
import matplotlib.pyplot as plt

加载数据

我们先创建一个数据加载函数来读取 articles.csv。您可以在此处下载数据。

def load_data(path,file_name):
    """
    Input  : path and file_name
    Purpose: loading text file
    Output : list of paragraphs/documents and
             title(initial 100 words considred as title of document)
    """
    documents_list = []
    titles=[]
    with open( os.path.join(path, file_name) ,"r") as fin:
        for line in fin.readlines():
            text = line.strip()
            documents_list.append(text)
    print("Total Number of Documents:",len(documents_list))
    titles.append( text[0:min(len(text),100)] )
    return documents_list,titles

预处理数据

有了数据加载函数后,您需要对文本进行预处理。预处理包含以下步骤:

  • 对文章进行分词
  • 去除停用词
  • 对文本进行词干提取
def preprocess_data(doc_set):
    """
    Input  : docuemnt list
    Purpose: preprocess text (tokenize, removing stopwords, and stemming)
    Output : preprocessed text
    """
    # initialize regex tokenizer
    tokenizer = RegexpTokenizer(r'\w+')
    # create English stop words list
    en_stop = set(stopwords.words('english'))
    # Create p_stemmer of class PorterStemmer
    p_stemmer = PorterStemmer()
    # list for tokenized documents in loop
    texts = []
    # loop through document list
    for i in doc_set:
        # clean and tokenize document string
        raw = i.lower()
        tokens = tokenizer.tokenize(raw)
        # remove stop words from tokens
        stopped_tokens = [i for i in tokens if not i in en_stop]
        # stem tokens
        stemmed_tokens = [p_stemmer.stem(i) for i in stopped_tokens]
        # add tokens to list
        texts.append(stemmed_tokens)
    return texts

准备语料

下一步是准备语料。在这里,您需要创建文档-词项矩阵以及词典。

def prepare_corpus(doc_clean):
    """
    Input  : clean document
    Purpose: create term dictionary of our courpus and Converting list of documents (corpus) into Document Term Matrix
    Output : term dictionary and Document Term Matrix
    """
    # Creating the term dictionary of our courpus, where every unique term is assigned an index. dictionary = corpora.Dictionary(doc_clean)
    dictionary = corpora.Dictionary(doc_clean)
    # Converting list of documents (corpus) into Document Term Matrix using dictionary prepared above.
    doc_term_matrix = [dictionary.doc2bow(doc) for doc in doc_clean]
    # generate LDA model
    return dictionary,doc_term_matrix

使用 Gensim 创建 LSA 模型

创建好语料后,您可以用 LSA 生成模型。

def create_gensim_lsa_model(doc_clean,number_of_topics,words):
    """
    Input  : clean document, number of topics and number of words associated with each topic
    Purpose: create LSA model using gensim
    Output : return LSA model
    """
    dictionary,doc_term_matrix=prepare_corpus(doc_clean)
    # generate LSA model
    lsamodel = LsiModel(doc_term_matrix, num_topics=number_of_topics, id2word = dictionary)  # train model
    print(lsamodel.print_topics(num_topics=number_of_topics, num_words=words))
    return lsamodel

确定主题数量

要想优化结果,还需要额外一步来确定最佳的主题数量。在这里,您将计算一致性分数来确定最佳主题数。

def compute_coherence_values(dictionary, doc_term_matrix, doc_clean, stop, start=2, step=3):
    """
    Input   : dictionary : Gensim dictionary
              corpus : Gensim corpus
              texts : List of input texts
              stop : Max num of topics
    purpose : Compute c_v coherence for various number of topics
    Output  : model_list : List of LSA topic models
              coherence_values : Coherence values corresponding to the LDA model with respective number of topics
    """
    coherence_values = []
    model_list = []
    for num_topics in range(start, stop, step):
        # generate LSA model
        model = LsiModel(doc_term_matrix, num_topics=number_of_topics, id2word = dictionary)  # train model
        model_list.append(model)
        coherencemodel = CoherenceModel(model=model, texts=doc_clean, dictionary=dictionary, coherence='c_v')
        coherence_values.append(coherencemodel.get_coherence())
    return model_list, coherence_values

我们来绘制一致性分数的曲线。

def plot_graph(doc_clean,start, stop, step):
    dictionary,doc_term_matrix=prepare_corpus(doc_clean)
    model_list, coherence_values = compute_coherence_values(dictionary, doc_term_matrix,doc_clean,
                                                            stop, start, step)
    # Show graph
    x = range(start, stop, step)
    plt.plot(x, coherence_values)
    plt.xlabel("Number of Topics")
    plt.ylabel("Coherence score")
    plt.legend(("coherence_values"), loc='best')
    plt.show()

start,stop,step=2,12,1
plot_graph(clean_text,start,stop,step)
line graph

这张图很好解读。横轴是主题数量,纵轴是一致性分数。在这些主题数量中,7 对应的分数最高,因此最佳主题数是 7。

运行上述所有函数

# LSA Model
number_of_topics=7
words=10
document_list,titles=load_data("","articles.txt")
clean_text=preprocess_data(document_list)
model=create_gensim_lsa_model(clean_text,number_of_topics,words)
Total Number of Documents: 4551
[(0, '0.361*"trump" + 0.272*"say" + 0.233*"said" + 0.166*"would" + 0.160*"clinton" + 0.140*"peopl" + 0.136*"one" + 0.126*"campaign" + 0.123*"year" + 0.110*"time"'), (1, '-0.389*"citi" + -0.370*"v" + -0.356*"h" + -0.355*"2016" + -0.354*"2017" + -0.164*"unit" + -0.159*"west" + -0.157*"manchest" + -0.116*"apr" + -0.112*"dec"'), (2, '0.612*"trump" + 0.264*"clinton" + -0.261*"eu" + -0.148*"say" + -0.137*"would" + 0.135*"donald" + -0.134*"leav" + -0.134*"uk" + 0.119*"republican" + -0.110*"cameron"'), (3, '-0.400*"min" + 0.261*"eu" + -0.183*"goal" + -0.152*"ball" + -0.132*"play" + 0.128*"said" + 0.128*"say" + -0.126*"leagu" + 0.122*"leav" + -0.122*"game"'), (4, '0.404*"bank" + -0.305*"eu" + -0.290*"min" + 0.189*"year" + -0.164*"leav" + -0.153*"cameron" + 0.143*"market" + 0.140*"rate" + -0.139*"vote" + -0.133*"say"'), (5, '0.310*"bank" + -0.307*"say" + -0.221*"peopl" + 0.203*"trump" + 0.166*"1" + 0.164*"min" + 0.163*"0" + 0.152*"eu" + 0.152*"market" + -0.138*"like"'), (6, '0.570*"say" + 0.237*"min" + -0.170*"vote" + 0.158*"govern" + -0.154*"poll" + 0.122*"tax" + 0.115*"statement" + 0.115*"bank" + 0.112*"budget" + -0.108*"one"')]
  • 主题 1:"trump"、"say"、"said"、"would"、"clinton"、"peopl"、"one"、"campaign"、"year"、"time"(美国总统选举)
  • 主题 2:"citi"、"v"、"h"、"2016"、"2017"、"unit"、"west"、"manchest"、"apr"、"dec"(英超联赛)
  • 主题 3:"trump"、"clinton"、"eu"、"say"、"would"、"donald"、"leav"、"uk"、"republican"、"cameron"(美国总统选举,英国脱欧)
  • 主题 4:min、eu、goal、ball、play、said、say、leagu、leav、game(英超联赛)
  • 主题 5:"bank"、"eu"、"min"、"year"、"leav"、"cameron"、"market"、"rate"、"vote"、"say"(英国脱欧与市场状况)
  • 主题 6:"bank"、"say"、"peopl"、"trump"、"1"、"min"、"eu"、"market"、"like"(政治局势与市场状况)
  • 主题 7:"say"、"min"、"vote"、"govern"、"poll"、"tax"、"statement"、"bank"、"budget"、"one"(美国总统选举与财务规划)

这里,我们用潜在语义分析发现了 7 个主题,其中部分主题有重叠。若要以更高准确度捕捉多重语义,我们需要尝试 LDA(潜在狄利克雷分配)。这部分留给您作为练习,试着用 Gensim 实现并分享您的看法。

LSA 的优缺点

LSA 算法最为简单,易于理解与实现。与向量空间模型相比,它通常能给出更好的结果。由于只涉及对文档-词项矩阵的分解,它也比其他算法更快。

潜在主题维度受矩阵秩的限制,无法随意扩展。LSA 分解得到的矩阵非常稠密,难以对单个维度进行索引。LSA 也难以捕捉词语的多重含义。与 LDA(潜在狄利克雷分配)相比,实现起来并不更容易,且准确率低于 LDA。

主题建模的应用场景

该技术的简单应用包括文本分析中的文档聚类、推荐系统与信息检索。更具体的用例包括:

  • 简历摘要:帮助招聘人员快速浏览并评估简历,减少筛选大量简历的工作量。
  • 搜索引擎优化:通过识别主题及其关联关键词,便于为在线文章、博客和文档打标签,从而改进搜索结果。
  • 推荐系统优化:推荐系统会根据用户画像与历史记录进行信息过滤与建议。它可帮助我们基于过往访问发现尚未浏览的相关内容。
  • 改进客户支持:在客户投诉与反馈中发现相关主题及关键词,例如产品与服务规格、部门与网点信息。此类信息有助于公司将投诉直接转到对应部门处理。
  • 在医疗行业,主题建模可帮助我们从非结构化的医疗报告中提取有用而有价值的信息,这些信息可用于患者治疗与医学科研。

结论

在本教程中,您了解了大量关于主题建模的内容。您学习了什么是主题建模、什么是潜在语义分析、如何构建相应模型、以及如何使用 LSA 生成主题。此外,您还回顾了奇异值分解与主题一致性分数等基础概念。

希望您现在可以将主题建模应用到自己的数据集上。感谢阅读!

如果您想进一步学习 Python,欢迎参与 DataCamp 的统计思维案例研究课程。

主题