前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >主题建模 — 简介与实现

主题建模 — 简介与实现

作者头像
磐创AI
发布2024-05-08 11:18:12
950
发布2024-05-08 11:18:12
举报
企业与其客户进行互动,以更好地了解他们,同时改进其产品和服务。这种互动可以采取电子邮件、文本社交媒体帖子(如Twitter)、客户评论(如亚马逊)等形式。让人类代表浏览所有这些形式的文本通信,然后将通信路由到相关团队以进行审查、采取行动和/或回复客户将是低效且成本高昂的。将这些互动分组并分配给相关团队的一种经济方法是使用主题建模。

在自然语言处理(NLP)的背景下,主题建模是一种无监督(即数据没有标签)的机器学习任务,其中算法的任务是基于文档内容为一组文档分配主题。给定的文档通常以不同比例包含多个主题 — 例如,如果文档是关于汽车的,我们预期汽车的名称会比某些其他主题(例如动物的名称)更突出,而我们预期诸如“the”和“are”之类的词汇会几乎等比例出现。主题模型实施数学方法来量化给定文档集合的这些主题的概率。

在本文中,作为数据科学家角色要求的一部分,我们将扩展我们的NLP知识深度。我们将首先建立一些关于分词、词性和命名实体识别概念的基础知识。然后,我们将实施情感分析练习,并最终使用潜在狄利克雷分配进行主题建模。

学习将通过练习问题和答案来实现。会根据需要在问题中提供提示和解释,以使学习过程更轻松。

让我们开始吧!

数据集

为了实施本文涵盖的概念,我们将使用UCI机器学习仓库中的一个数据集,该数据集基于论文“使用深度特征从群体到个体标签”(Kotzias等,2015),可从此链接(CC BY 4.0)下载。

让我们从导入今天将要使用的一些库开始,然后读取数据集并查看数据框的前10行。每个命令前都有注释,以进一步解释这些步骤。

代码语言:javascript
复制
# Import libraries
import pandas as pd
import numpy as np
import nltk

# Making the full width of the columns viewable
pd.set_option('display.max_colwidth', None)

# Making all rows viewable
pd.set_option('display.max_rows', None)

# Read the data set and drop the df['label'] column
df = pd.read_csv('imdb_labelled.csv').drop('label', axis = 1)

# Taking out a list of strings to clean up the data
df = df.replace(['\n', '\t1', '\t0'],'', regex=True)

# Return top 10 rows of the dataframe
df.head(10)

结果:

text

0

A very, very, very slow-moving, aimless movie about a distressed, drifting young man.

1

Not sure who was more lost - the flat characters or the audience, nearly half of whom walked out.

2

Attempting artiness with black & white and clever camera angles, the movie disappointed - became even more ridiculous - as the acting was poor and the plot and lines almost non-existent.

3

Very little music or anything to speak of.

4

The best scene in the movie was when Gerardo is trying to find a song that keeps running through his head.

5

The rest of the movie lacks art, charm, meaning… If it's about emptiness, it works I guess because it's empty.

6

Wasted two hours.

7

Saw the movie today and thought it was a good effort, good messages for kids.

8

A bit predictable.

9

Loved the casting of Jimmy Buffet as the science teacher.

教程 + 问题与答案

分词

分词是将文本字符串拆分为较小的子字符串。这些子字符串可以在不同的级别上。例如,句子级别上的一个分词策略会将给定字符串分解为句子,而其他分词器可以将句子分解为更小的标记,例如单词、二元组等。

在这个练习中,我们只需要将字符串分解为句子和单词,所以我不会深入研究其他分词策略,但如果你对了解更多感兴趣,我在这里还有另一篇文章,其中更详细地介绍了标记、二元组和N-Gram。

问题1:

定义一个名为“make_sentences”的函数,接受一个系列作为其参数, 默认为数据框的“text”列的前15行,将每个条目分解为句子并返回这些句子的列表。然后将该函数应用于数据框的前10行。

提示:使用nltk.sent_tokenize,它将给定的字符串分割成句子级别的子字符串列表。

答案:

代码语言:javascript
复制
# Import packages
from nltk import sent_tokenize

# Define the function
def make_sentences(text = df['text'].head(15)):

    # Define a lambda function to apply the sent_tokenize to the df['text']
    return text.apply(lambda x: sent_tokenize(x)).tolist()

# Return the results for the top 10 rows of the dataframe
make_sentences(df['text'].head(10))

结果:

代码语言:javascript
复制
[['A very, very, very slow-moving, aimless movie about a distressed, drifting young man.'],
 ['Not sure who was more lost - the flat characters or the audience, nearly half of whom walked out.'],
 ['Attempting artiness with black & white and clever camera angles, the movie disappointed - became even more ridiculous - as the acting was poor and the plot and lines almost non-existent.'],
 ['Very little music or anything to speak of.'],
 ['The best scene in the movie was when Gerardo is trying to find a song that keeps running through his head.'],
 ['The rest of the movie lacks art, charm, meaning...',
  "If it's about emptiness, it works I guess because it's empty."],
 ['Wasted two hours.'],
 ['Saw the movie today and thought it was a good effort, good messages for kids.'],
 ['A bit predictable.'],
 ['Loved the casting of Jimmy Buffet as the science teacher.']]
词性

到目前为止,我们可以将给定的字符串分成句子,由一系列词组成。单词可以分解为词汇类别(类似于分类机器学习任务中的类),包括名词、动词、形容词、副词等。这些词汇组被称为自然语言处理中的词性或(POS)。自动为单词分配词性的过程称为词性标注,这是NLP流程的常见步骤。

标记在各种NLP任务中都很有用,例如,在机器翻译中,任务是提供输入文本(原始语言中的文本)的翻译(目标语言中的翻译)。如果原始文本输入中包含人名,我们不希望机器翻译模型翻译该名称。确保这一点的一种方式是将该人名标记为实体,然后当存在标记实体时,将绕过模型。换句话说,句子中除了那个标记的实体之外的所有内容都将被翻译。然后,在后续的后处理步骤中,标记的实体将映射到最终翻译结果中的正确位置。

有各种不同的方法来创建标记策略,例如基于正则表达式的方法,甚至是经过训练的机器学习模型。在今天的练习中,我们将依赖NLTK提供的现有词性标注。让我们看一个例子,以更好地理解这个概念。

我们从创建一个示例字符串开始,然后将其通过NLTK的词性标注器,并审查结果。

代码语言:javascript
复制
# Create a sample sentence
sample = 'I am impressed by Amazon delivering so quickly in Japan!'

# Import required libraries
from nltk import word_tokenize, pos_tag

# Break down the sample into word tokens
tokens = word_tokenize(sample)

# Create POS tagging
pos = pos_tag(tokens)

# Return the POS tag results
pos

结果:

代码语言:javascript
复制
[('I', 'PRP'),
 ('am', 'VBP'),
 ('impressed', 'VBN'),
 ('by', 'IN'),
 ('Amazon', 'NNP'),
 ('delivering', 'VBG'),
 ('so', 'RB'),
 ('quickly', 'RB'),
 ('in', 'IN'),
 ('Japan', 'NNP'),
 ('!', '.')]

现在我们看到了标记结果是什么样子。例如,“quickly”被标记为“RB”,意思是副词,或者“Amazon”被标记为“NNP”,意思是名词。NLTK为标记提供了文档。例如,如果我们想知道“RB”是什么意思,我们可以运行以下命令:

代码语言:javascript
复制
nltk.help.upenn_tagset('RB')

结果:

代码语言:javascript
复制
RB: adverb
    occasionally unabatingly maddeningly adventurously professedly
    stirringly prominently technologically magisterially predominately
    swiftly fiscally pitilessly ...

如果你想查看所有标记,可以不带参数运行相同的命令。

命名实体识别

现在,我们对句子中的每个单词都进行了词性标注,但并不是所有的名词都是相同的。例如,“Amazon”和“Japan”都被标记为“NNP”,但一个是一个公司的名称,另一个是一个国家。

命名实体识别(NER,也称为命名实体分块)涉及通过将给定的文本输入分类为预定义的类别(如人、组织、地点等)来从文本输入中提取信息。让我们看一个例子,以了解这是如何工作的。

问题2:

首先将示例句子分解为标记,然后应用词性标注,然后进行命名实体识别并返回结果。

答案:

代码语言:javascript
复制
# Import required packages
from nltk import word_tokenize, pos_tag, ne_chunk

# Break down the sample into tokens
tokens = word_tokenize(sample)

# Create POS tagging
part_of_speach = pos_tag(tokens)

# Create named-entity chunks
named_entity_chunks = ne_chunk(part_of_speach)

# Return named_entity_chunks
named_entity_chunks

结果:

让我们看一下结果,特别是对于“Amazon”和“Japan”,因为我们知道这两个都是实体。Amazon被分类为“Person”,这是我们算法的一个改进机会。我更喜欢一个“Corporation”或类似的类。然后,“Japan”被分类为GPE,代表地理政治实体。听起来正确!因此,我们观察到NER如何帮助我们进一步将名词分解为实体类别。

现在我们已经学会了如何进行词性标注和NER,让我们创建一个可以自动执行这些任务的函数。

问题3:

定义一个名为“make_chunks”的函数,接受一个句子列表作为参数,默认为问题1中定义的“make_sentences”函数,并返回一个字典(将称为外部字典),外部字典的键是指向条目的行号的整数。外部字典的值本身是一个字典(将称为内部字典),内部字典的键是句子编号,内部字典的值是命名实体识别的结果(类似于问题2)。例如,回顾问题1的结果,第六行的结果是以下句子列表:

代码语言:javascript
复制
['The rest of the movie lacks art, charm, meaning...',  
"If it's about emptiness, it works I guess because it's empty."],

因此,使用默认参数运行问题3中定义的函数,预计第六行的结果如下:

代码语言:javascript
复制
5: {
     0: [('The', 'DT'),
         ('rest', 'NN'),
         ('of', 'IN'),
         ('the', 'DT'),
         ('movie', 'NN'),
         ('lacks', 'VBZ'),
         ('art', 'RB'),
         (',', ','),
         ('charm', 'NN'),
         (',', ','),
         ('meaning', 'NN'),
         ('...', ':')
     ],
     1: [('If', 'IN'),
         ('it', 'PRP'),
         ("'s", 'VBZ'),
         ('about', 'IN'),
         ('emptiness', 'NN'),
         (',', ','),
         ('it', 'PRP'),
         ('works', 'VBZ'),
         ('I', 'PRP'),
         ('guess', 'NN'),
         ('because', 'IN'),
         ('it', 'PRP'),
         ("'s", 'VBZ'),
         ('empty', 'JJ'),
         ('.', '.')
     ]
 },

答案:

为了定义这个函数,我们将遍历两个字典,其中内部字典将包括标记、词性标注和NER,类似于此问题之前介绍的示例。

代码语言:javascript
复制
# Define the function
def make_chunks(make_sentences = make_sentences):

    # Create the outer dictionary with row number as key
    row_dict = dict()

    # Form the first iteration
    for i, row in enumerate(make_sentences()):

        # Create the inner dictionary with sentence number as key
        sent_dict = dict()

        # Form the second iteration
        for j, sent in enumerate(row):

            # Tokenize
            w = word_tokenize(sent)

            # POS tagging
            pos = pos_tag(w)

            # Add named-entity chunks as the values to the inner dictionary
            sent_dict[j] = list(ne_chunk(pos))

        # Add the inner dictionary as values to the outer dictionary
        row_dict[i] = sent_dict

    # Return the outer dictionary
    return row_dict

# Test on the sixth row of the dataframe
make_chunks()[5]

结果:

代码语言:javascript
复制
{0: [('The', 'DT'),
  ('rest', 'NN'),
  ('of', 'IN'),
  ('the', 'DT'),
  ('movie', 'NN'),
  ('lacks', 'VBZ'),
  ('art', 'RB'),
  (',', ','),
  ('charm', 'NN'),
  (',', ','),
  ('meaning', 'NN'),
  ('...', ':')],
 1: [('If', 'IN'),
  ('it', 'PRP'),
  ("'s", 'VBZ'),
  ('about', 'IN'),
  ('emptiness', 'NN'),
  (',', ','),
  ('it', 'PRP'),
  ('works', 'VBZ'),
  ('I', 'PRP'),
  ('guess', 'NN'),
  ('because', 'IN'),
  ('it', 'PRP'),
  ("'s", 'VBZ'),
  ('empty', 'JJ'),
  ('.', '.')]}

正如预期的那样,结果与问题中提供的示例相匹配。

情感分析

在自然语言处理领域,情感分析是一种用于从文本数据中识别、量化、提取和研究主观信息的工具。在这个练习中,我们将使用极性分数,这是一个范围在[-1.0, 1.0]之间的浮点数,旨在区分文本的情感是积极的还是消极的。这种程度的了解对于本文的目的已经足够了,但如果你对了解更多感兴趣,请参考我在这里链接的有关情感分析的文章。让我们一起看一个例子。

问题4:

创建一个函数,接受一个句子列表作为参数,默认为问题1中定义的“make_sentences”函数,然后返回一个包含“句子”和“情感”两列的数据框。请使用NLTK的“SentimentIntensityAnalyzer”进行情感分析。最后,使用默认参数运行函数并返回结果。

答案:

代码语言:javascript
复制
# Import the package
from nltk.sentiment.vader import SentimentIntensityAnalyzer

# Define the function
def sentiment_analyzer(make_sentences = make_sentences):

    # Create an instance of SentimentIntensityAnalyzer
    sia = SentimentIntensityAnalyzer()

    # Create the dataframe with two columns as described
    df = {
        'sentence' : []
        , 'sentiment' : []
    }

    # Create two loops to add the column vlaues
    for i, row in enumerate(make_sentences()):
        for sent in row:

            # Add the sentence to the dataframe
            df['sentence'].append(sent)

            # Add the polarity score of the sentiment analysis to the sentiment column of the dataframe
            df['sentiment'].append(sia.polarity_scores(sent)['compound'])


    # Return the dataframe
    return pd.DataFrame(df)

# Run the function
sentiment_analyzer()

结果:

主题建模 — 潜在狄利克雷分配

潜在狄利克雷分配(LDA)是用于主题建模的常见模型之一。虽然探索LDA的数学细节超出了本文的范围,但我们可以将其视为将单词与主题和文档连接起来的模型。例如,当将一组文档提供给LDA模型时,它将查看单词,并基于每个文档中包含的单词,为每个文档分配主题及其相应的概率。

幸运的是,我们可以很容易地在scikit-learn中实现LDA。NLTK的LDA类接受文档-词矩阵(DTM)作为参数,因此,让我们首先回顾一下DTM是什么,然后我们将看一个使用scikit-learn的LDA模型进行主题建模的示例。

文档-词矩阵

DTM是一种表示在一组文档中出现的术语频率的矩阵。让我们看两个句子以了解什么是DTM。

假设我们有以下两个句子(在这个示例中,每个句子被认为是一个“文档”):

代码语言:javascript
复制
sentence_1 = 'He is walking down the street.'

sentence_2 = 'She walked up then walked down the street yesterday.'

上述两个句子的DTM将是:

可以使用scikit-learn的CountVectorizer来实现DTM。这对于我们当前的练习目的足够了,但如果你对了解更多关于DTM的内容感兴趣,请访问我在情感分析中的帖子,链接在这里。

让我们实施到目前为止所学的内容。我们将实施以下步骤:

  1. 导入DTM和LDA所需的包,并对它们进行实例化
  2. 创建我们数据框的“text”列的DTM
  3. 使用LDA为提供的DTM创建主题
代码语言:javascript
复制
# Step 1 - Import packages
from sklearn.decomposition import LatentDirichletAllocation
from sklearn.feature_extraction.text import CountVectorizer

# Create an instance of the imported packages
lda = LatentDirichletAllocation()
cvect = CountVectorizer(stop_words='english')

# Step 2 - Create a DTM of 50 randomly-selected rows of the dataframe
dtm = cvect.fit_transform(df['text'])

# Step 3 - Create list of topics using LDA
topics = lda.fit_transform(dtm)

现在我们已经创建了模型,让我们看一下每个主题中包含哪些单词。可以使用lda.components_来查看模型的结果。让我们看一个例子。

问题5:

定义一个名为“top_n_words”的函数,接受两个参数:

  1. “feature_names”,这是从DTM中得出的特征名称
  2. “n”,这是将返回的行数和单词数。

此函数接受上述两个参数,并返回前n个主题中的前n个单词。例如,第一个主题的结果可能如下所示:

代码语言:javascript
复制
Topic 0: film ve really end movies just dialogue choked drama worthless

最后,运行函数并返回每个主题中的前10个单词。

答案:

代码语言:javascript
复制
# Define the function
def top_n_words(feature_names, n): 

    for topic_index, topic in enumerate(lda.components_):

        # Create the "Topic {index}:" portion of the output
        output = "Topic %d: " % topic_index 

        # Add the top n words of that topic
        output += " ".join([feature_names[i] for i in topic.argsort()[:-n - 1:-1]])

        # Print the output
        print(output)

    # Print the output
    print()

# Create function names from the DTM
feature_names = cvect.get_feature_names_out()

# Run the function for top 10 words of each topic
top_n_words(feature_names, n=10)

结果:

代码语言:javascript
复制
Topic 0: film movie movies like sucks script look watch story great
Topic 1: movie bad really film like time awful don good just
Topic 2: just film truly like watching character bad little interesting way
Topic 3: movie film great just bad 10 good really films like
Topic 4: film bad cast actors writing camera true good fun watch
Topic 5: film good just movie characters funny scenes cast kids scamp
Topic 6: plot movie just acting 10 recommended wonderful character real played
Topic 7: film good time seen far best cinematography brilliant acting boring
Topic 8: story acting bad movie just line plot loved look know
Topic 9: movie film time liked good make stupid like watching movies

问题6:

定义一个函数,接受两个参数,“search_word”和“n”,并返回与“search_word”相关的前“n”个最有可能的单词。结果应以数据框的形式呈现,包含两列。第一列将是每个单词的“概率”,第二列将是与所提供主题(即“search_word”)相关联的“特征”或单词。最后,以“action”作为“search_word”运行函数,并返回与该主题相关的前10个单词。

答案:

代码语言:javascript
复制
# Define the function
def word_feature(search_word, n=10):
    search_word_cvect = cvect.transform(np.array([search_word])) 
    probabilities = lda.transform(search_word_cvect)
    topics = lda.components_[np.argmax(probabilities)]
    features = cvect.get_feature_names_out()
    return pd.DataFrame({'probability': topics, 'feature': features}).nlargest(n, 'probability')

# Run the function for the given search_word and return top 10 results
word_feature('action', n=10)

结果:

本文参与 腾讯云自媒体分享计划,分享自微信公众号。
原始发表:2024-05-05,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 磐创AI 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 数据集
  • 教程 + 问题与答案
    • 分词
      • 词性
        • 命名实体识别
          • 情感分析
            • 主题建模 — 潜在狄利克雷分配
              • 文档-词矩阵
              相关产品与服务
              NLP 服务
              NLP 服务(Natural Language Process,NLP)深度整合了腾讯内部的 NLP 技术,提供多项智能文本处理和文本生成能力,包括词法分析、相似词召回、词相似度、句子相似度、文本润色、句子纠错、文本补全、句子生成等。满足各行业的文本智能需求。
              领券
              问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档