BERTopic 主题比例非常不平衡?

jm2pwxwz  于 2个月前  发布在  其他
关注(0)|答案(7)|浏览(48)

我按照教程完成了主题建模,但发现主题似乎并不成比例。你可以看到第一个主题占据了70%以上的比例,而其他主题在比例上占据的非常少。这是我在数据集中的主题比例:

我的超参数调优如下:

import gensim.corpora as corpora
import pandas as pd
import wandb
import os

from gensim.parsing.preprocessing import strip_punctuation
from sklearn.feature_extraction.text import TfidfVectorizer
from gensim.models.coherencemodel import CoherenceModel
from bertopic.vectorizers import ClassTfidfTransformer
from sentence_transformers import SentenceTransformer
# from bertopic.representation import KeyBERTInspired
from bertopic import BERTopic
from hdbscan import HDBSCAN
from umap import UMAP

path_output = os.path.join(os.getcwd(), 'Result', 'RQ1', 'Special Topics')
path_model = os.path.join(os.getcwd(), 'Code', 'RQ1', 'Special Topic Modeling', 'Model')
if not os.path.exists(path_model):
    os.makedirs(path_model)

wandb_project = 'asset-management-topic-modeling'

os.environ["WANDB_API_KEY"] = XXXXXX
os.environ["TOKENIZERS_PARALLELISM"] = "true"
os.environ["WANDB__SERVICE_WAIT"] = "100"

# set default sweep configuration
config_defaults = {
    # Refer to https://www.sbert.net/docs/pretrained_models.html
    'model_name': 'all-mpnet-base-v2',
    'metric_distane': 'cosine',
    'calculate_probabilities': True,
    # 'reduce_frequent_words': True,
    'prediction_data': True,
    'low_memory': False,
    'random_state': 42,
    'ngram_range': 2,
}

config_sweep = {
    'method': 'grid',
    'metric': {
        'name': 'Coherence CV',
        'goal': 'maximize'
    },
    'parameters': {
        'n_components': {
            'values': [3, 4, 5, 6, 7],
        },
    }
}

class TopicModeling:
    def __init__(self, topic_type, min_cluster_size=20):
        # Initialize an empty list to store top models
        self.top_models = []
        self.path_model = path_model
        
        df = pd.read_json(os.path.join(path_output, 'labels.json'))
        if topic_type == 'anomaly':
            df = df[df['Challenge_type'] == 'anomaly']
            self.docs = df[df['Challenge_summary'] != 'na']['Challenge_summary'].tolist() + df[df['Challenge_root_cause'] != 'na']['Challenge_root_cause'].tolist()
        elif topic_type == 'solution':
            docs = df[df['Solution'] != 'na']['Solution'].tolist()
            self.docs = [strip_punctuation(doc) for doc in docs]
        
        config_defaults['min_cluster_size'] = min_cluster_size
        config_sweep['name'] = topic_type
        config_sweep['parameters']['min_samples'] = {
            'values': list(range(1, config_defaults['min_cluster_size'] + 1))
        }
        
    def __train(self):
        # Initialize a new wandb run
        with wandb.init() as run:
            # update any values not set by sweep
            run.config.setdefaults(config_defaults)

            # Step 1 - Extract embeddings
            embedding_model = SentenceTransformer(run.config.model_name)

            # Step 2 - Reduce dimensionality
            umap_model = UMAP(n_components=wandb.config.n_components, metric=run.config.metric_distane,
                              random_state=run.config.random_state, low_memory=run.config.low_memory)

            # Step 3 - Cluster reduced embeddings
            hdbscan_model = HDBSCAN(min_cluster_size=run.config.min_cluster_size,
                                    min_samples=wandb.config.min_samples, prediction_data=run.config.prediction_data)

            # Step 4 - Tokenize topics
            vectorizer_model = TfidfVectorizer(ngram_range=(1, run.config.ngram_range))

            # Step 5 - Create topic representation
            ctfidf_model = ClassTfidfTransformer()
            # ctfidf_model = ClassTfidfTransformer(reduce_frequent_words=run.config.reduce_frequent_words)

            # # Step 6 - Fine-tune topic representation
            # representation_model = KeyBERTInspired()

            # All steps together
            topic_model = BERTopic(
                embedding_model=embedding_model,
                umap_model=umap_model,
                hdbscan_model=hdbscan_model,
                vectorizer_model=vectorizer_model,
                ctfidf_model=ctfidf_model,
                # representation_model=representation_model,
                calculate_probabilities=run.config.calculate_probabilities
            )

            topic_model = topic_model.fit(self.docs)
            topic_model.reduce_topics(self.docs, nr_topics='auto')

            # Preprocess Documents
            documents = pd.DataFrame({"Document": self.docs,
                                      "ID": range(len(self.docs)),
                                      "Topic": topic_model.topics_})
            documents_per_topic = documents.groupby(
                ['Topic'], as_index=False).agg({'Document': ' '.join})
            cleaned_docs = topic_model._preprocess_text(
                documents_per_topic.Document.values)

            # Extract vectorizer and analyzer from BERTopic
            vectorizer = topic_model.vectorizer_model
            analyzer = vectorizer.build_analyzer()

            # Extract features for Topic Coherence evaluation
            tokens = [analyzer(doc) for doc in cleaned_docs]
            dictionary = corpora.Dictionary(tokens)
            corpus = [dictionary.doc2bow(token) for token in tokens]
            topic_words = [[words for words, _ in topic_model.get_topic(
                topic)] for topic in range(len(set(topic_model.topics_))-1)]

            coherence_cv = CoherenceModel(
                topics=topic_words,
                texts=tokens,
                corpus=corpus,
                dictionary=dictionary,
                coherence='c_v'
            )

            coherence_umass = CoherenceModel(
                topics=topic_words,
                texts=tokens,
                corpus=corpus,
                dictionary=dictionary,
                coherence='u_mass'
            )

            coherence_cuci = CoherenceModel(
                topics=topic_words,
                texts=tokens,
                corpus=corpus,
                dictionary=dictionary,
                coherence='c_uci'
            )

            coherence_cnpmi = CoherenceModel(
                topics=topic_words,
                texts=tokens,
                corpus=corpus,
                dictionary=dictionary,
                coherence='c_npmi'
            )

            wandb.log({'Coherence CV': coherence_cv.get_coherence()})
            wandb.log({'Coherence UMASS': coherence_umass.get_coherence()})
            wandb.log({'Coherence UCI': coherence_cuci.get_coherence()})
            wandb.log({'Coherence NPMI': coherence_cnpmi.get_coherence()})
            wandb.log({'Topic Number': topic_model.get_topic_info().shape[0] - 1})
            wandb.log({'Uncategorized Post Number': topic_model.get_topic_info().at[0, 'Count']})

            model_name = f'{config_sweep["name"]}_{run.id}'
            topic_model.save(os.path.join(self.path_model, model_name))

    def sweep(self):
        wandb.login()
        sweep_id = wandb.sweep(config_sweep, project=wandb_project)
        wandb.agent(sweep_id, function=self.__train)

这些主题来自我的最佳主题模型,具有最高的一致性分数。我承认有些标 checkout 现得频繁,但远没有那么频繁(70%以上)。最常见的标签只占数据集不到5%的比例。有可能实现更平衡的主题聚类吗?reduce_frequent_words 会有帮助吗?KeyBERTInspired 又会怎样呢?这是因为 reduce_topics 吗?但是我需要这个函数来进行更好的聚类。我对这种情况感到很迷茫。我真的很需要你的帮助。@MaartenGr

gpnt7bae

gpnt7bae1#

主题来源于我的最佳主题模型,其一致性得分最高。

首先,主题一致性可以是一个非常不精确的度量。它仅仅是一致性的近似值,但在实践中,这也意味着由于一致性的主观性质,该指标也是不完美的。这意味着仅仅遵循这个指标往往不会带来“最佳”的主题模型。这里的“最佳”也是相当主观的,应该根据您的用例自行定义。

例如,您表格中的第三个主题模型实际上可能具有更平衡的主题分布,这是您所追求的。此外,随着更多的主题,一致性可能会逐渐下降。这并不是一个问题,因为主题数量较少可能会夸大一致性得分。

换句话说,不要仅仅为了选择一个主题模型而追求一致性。您的第三个模型实际上可能比一致性得分所暗示的要好得多。

是否可以实现更平衡的主题聚类?
这取决于底层的聚类算法。HDBSCAN是一种基于密度的算法,倾向于找到“不平衡”的聚类。通常情况下,我们不会看到文档之间主题的完美平衡,因为文档中的主题几乎总是不平衡的。您可以选择一种倾向于创建更多“平衡”的算法,如k-Means。

减少频繁词汇有助于吗?KeyBERTInspired呢?
这些并不会影响聚类创建过程,对于“平衡”的目的是无关紧要的。
这是因为 reduce_topics 导致的吗?
这确实有可能发生。如果您将 reduce_topics 设置为任何值,无论是 "auto" 还是整数,它可能会尝试将所有这些“微观聚类”合并为一个更一般的主题。这确实会膨胀和“不平衡”主题。我强烈建议您不要使用 nr_topics='auto'.reduce_topics
但是我需要这个函数来进行更好的聚类。
我不完全同意这一点。除了“更好的聚类”是主观的事实之外,您还可以间接地使用 HDBSCAN 中的 min_cluster_size 参数控制生成的主题数量。这个值越高,生成的聚类就越少。这个值越低,生成的聚类就越多。一般来说,我更满意使用 min_cluster_size 而不是 nr_topics 生成的主题。因此,我建议跳过 nr_topics
总之,我认为再次提到优化不完美的度量(如一致性)将导致可能不适合您的用例的主题模型是很重要的。在这里有一个人为干预是非常重要的。我可以想象不想使用最低一致性得分的主题模型,但追求最高得分也会导致问题。

z4bn682m

z4bn682m2#

主题来自我的最佳主题模型,具有最高的一致性分数。
首先,主题一致性可以是一个非常不精确的度量。它仅仅是一致性的近似值,但在实践中,这也意味着由于一致性的主观性质,该指标也是不完美的。这意味着仅仅遵循这个指标往往不会带来“最佳”的主题模型。这里的“最佳”也是相当主观的,应该根据你自己的使用情况来定义。
例如,你的表格中的第三个主题模型可能实际上具有更平衡的主题分布,这是你所追求的。此外,随着更多的主题,一致性可能会逐渐下降。这并不是一个问题,因为主题数量较少可能会夸大一致性分数。
换句话说,不要只追求一致性来选择主题模型。你的第三个模型可能实际上表现得比一致性分数所暗示的要好得多。
是否可以有更平衡的主题聚类?
这取决于底层的聚类算法。HDBSCAN是一种基于密度的算法,倾向于找到“不平衡”的聚类。通常情况下,我们不会看到文档之间主题的完美平衡,因为文档中的主题几乎总是不平衡的。你可以尝试选择一种倾向于创建更多“平衡”的算法,如k-Means。
减少频繁词汇有助于吗?KeyBERTInspired呢?
这些并不会影响聚类创建,对于“平衡”的目的来说是无关紧要的。
这是因为 reduce_topics 导致的吗?
确实有可能发生这种情况。如果你将 reduce_topics 设置为任何值,无论是 "auto" 还是整数,它可能会试图将所有这些“微观聚类”合并成一个更一般的主题。这确实会膨胀和“不平衡”主题。我强烈建议不要使用 nr_topics='auto'.reduce_topics
但我需要这个函数来进行更好的聚类。
我不完全同意这一点。除了“更好的聚类”是主观的之外,你还可以通过HDBSCAN中的 min_cluster_size 参数间接控制生成的主题数量。这个值越高,生成的主题就越少。这个值越低,生成的主题就越多。一般来说,我更满意使用 min_cluster_size 生成的主题与使用 nr_topics 生成的主题相比。所以我会建议跳过 nr_topics
总之,我认为再次提到优化不完美的度量标准(如一致性)会导致可能不适合你使用场景的主题模型是很重要的。在这里有一个人为干预是非常重要的。我可以想象不想使用最低一致性分数的可能主题模型,但追求最高分数也会导致问题。
嗨,@MaartenGr 谢谢你的建议。我尝试了那些度量方法,但出现了另一个问题。在删除 nr_topicsKeyBERTInspired 之后,一致性分数下降了很多(从0.8降到0.45)。正如你所说,一致性分数不是最重要的度量标准,但在进一步验证实验后,这种下降是由于删除了 KeyBERTInspired 导致的。也就是说,如果我使用 KeyBERTInspired ,一致性分数可以提高很多!如果去掉它,一致性分数就会大幅下降!
我对这个问题感到困惑,这是有意为之的设计吗?还是我的实验设置有问题?手动检查时不考虑任何指标在成千上万个模型中进行比较是痛苦且耗时的,我希望能够快速地确定一个或两个指标来选择最佳模型。关键是,如果我仍然使用 KeyBERTInspired ,那么重复的主题就会存在,我无法通过检查表示词来区分它们。如果我不使用它,那么一致性分数就会下降很多,这对我的实验结果的可信度产生了负面影响。

wfsdck30

wfsdck303#

也就是说,如果我使用KeyBERTInspired,连贯性分数可以提高很多!如果我去掉它,连贯性分数会大大降低!
这种方法倾向于计算主题连贯性的方式,但由于它优化了连贯性,所以并不会突然创建更好的主题。这是我通常更喜欢使用的方法,但它并不是没有缺点。例如,c-TF-IDF在捕捉特定领域的词汇方面要好得多。
我对这一点感到困惑,这是有意为之的设计吗?还是我的实验设置出了问题?事实是,如果我仍然使用KeyBERTInspired,那么重复的主题会持续存在,我无法通过检查表示词来区分它们。
我认为你的实验并没有什么问题。特别是删除nr_topics是一个有帮助的方法,但请注意,它并不是建议删除KeyBERTInspired,因为它只是表示主题的方式,而不是创建主题。
在成千上万种模型中不考虑任何指标地手动检查是非常痛苦和耗时的,我希望有一个或两个指标来快速决定最佳模型。
我不建议只选择两个指标并仅优化这些指标。相反,你可以挑选两个指标,然后查看创建的前n个主题模型,并手动查看它们是否合理。如果前n个主题模型彼此之间有所不同,例如创建的主题数量,这将特别有帮助。
我认为在大多数情况下,如果你在没有手动检查的情况下优化一个或两个指标,你将错过适合你用例的最佳模型。
事实是,如果我仍然使用KeyBERTInspired,那么重复的主题会持续存在,我无法通过检查表示词来区分它们。
KeyBERTInspired 不会以任何方式影响主题的创建,它只是改变了创建的主题的表示方式。换句话说,它们包含的名称或关键词。如果你使用multi-aspect representations(例如ChatGPT或MMR),你仍然可以区分它们。
如果我不使用它,那么连贯性分数会大大降低,这对我的实验结果的可信度产生了负面影响。
我会谨慎地这样做。主题模型不仅仅是主题连贯性和多样性。我强烈建议阅读Is automated topic model evaluation broken? the incoherence of coherence。评估一个主题模型是一个复杂的任务,仅仅因为不完美的连贯性分数下降就绝对不应该对可信度产生重大负面影响。

bfhwhh0e

bfhwhh0e4#

你好,@MaartenGr

在进行主题建模时,我注意到,如果我减小 min_cluter_size 的值,那么主题数量会急剧增加。然而,如果我增大 min_cluter_size 的值,离群值的数量也会急剧增加。前者使得从人类Angular 的主题解释变得极其困难,因为主题表示几乎相同;而后者使得 reduce_outlier 操作不理想,因为大量的离群值污染了原始的主题表示。

我还记得你告诉我们尽量不要使用 nr_topics ,但在我的情况下,当主题数量变大时,它们之间会发生碰撞。如果我减少了主题数量,会出现一个绝对比例(>50%的文章)支配的主题,这让我更加沮丧。

我已经花了很多周时间在这些权衡上,感到非常绝望,有任何实际的建议吗?

zvokhttg

zvokhttg5#

你遇到的问题是由于底层的聚类算法,即HDBSCAN引起的。正如你所看到的,有很多事情可以优化以减少你面临的问题。
最值得注意的是,我相信.reduce_outliers仍然是一个好选择。原因是主题表示主题分配之间的区别。仅凭自身,.reduce_outliers只改变主题分配,这意味着离群值现在被分配到主题,而实际上并没有影响这些主题的表示,即单词/短语/等。如果你想根据主题分配更改主题表示,你可以运行.update_topics。然而,我认为如果它对主题表示产生负面影响,那么这并不总是正确的选择。仅仅省略.update_topics步骤,你就可以保持高质量的主题和表示,同时仍然减少离群值。
这里需要注意的第二件事是,你的困难是针对HDBSCAN的,所以为什么不改用更适合你使用场景的东西呢?理论上,BERTopic支持大多数聚类算法,所以你可以切换到k-Means、层次聚类、sklearn的HDBSCAN、OPTICS等。哪种对你有效取决于算法和你的用例,所以尝试一些不同的可能会有帮助。
而后者由于大量的离群值污染了原始主题表示,使得reduce_outlier操作变得不理想。
总之,我认为在没有.update_topics的情况下使用.reduce_outliers实际上会使你提到的后者仍然是一个有效的解决方案。仅仅因为一个文档被分配给一个主题并不意味着该主题的表示应该受到该文档的影响。

c2e8gylq

c2e8gylq6#

你遇到的问题是由于底层的聚类算法,即HDBSCAN引起的。正如你所看到的,有很多事情可以优化以减少你面临的问题。
最值得注意的是,我相信.reduce_outliers仍然是一个好选择。原因是主题表示主题分配之间的区别。仅凭.reduce_outliers只改变主题分配,这意味着离群值现在被分配到主题,而实际上并没有影响这些主题的表示,即那些词/短语/等。如果你想根据主题分配更改主题表示,你可以运行.update_topics。然而,我认为如果它对主题表示产生负面影响,那并不总是正确的选择。仅仅省略.update_topics步骤,你就可以保持高质量的主题和表示,同时仍然减少离群值。
这里需要注意的第二件事是,你的困难是针对HDBSCAN的,那么为什么不改用更适合你使用场景的东西呢?理论上,BERTopic支持大多数聚类算法,所以你可以切换到k-Means、层次聚类、sklearn的HDBSCAN、OPTICS等。对你有效的算法取决于算法和你的使用场景,所以尝试一些不同的算法可能会有所帮助。
而后者使得reduce_outlier操作变得不理想,因为大量的离群值污染了原始的主题表示。
总之,我认为在使用.reduce_outliers而不使用.update_topics的情况下,实际上会使你提到的后者仍然是一个有效的解决方案。仅仅因为一个文档被分配给一个主题并不意味着该主题的表示应该受到该文档的影响。
我检查了分配的离群值,但它们很少有意义地重新分配给已识别的主题。对于优化离群值分配,还有其他我可以调整的措施吗?

l7wslrjt

l7wslrjt7#

我检查了分配的离群值,但它们很少有意义地重新分配给已识别的主题。
如果它们没有意义地重新分配给已识别的主题,那么也许保留它们作为离群值?除此之外,您可以增加要创建的簇的数量,并确保它们都有唯一的标签。有一些 representation models 可以使用,包括 LLMs ,用于进一步优化标签,这应增加结果主题的可解释性。
对于优化离群值分配,我可以调整哪些其他措施吗?
对于离群值分配,有一些 bunch of strategies 可以使用来调整离群值减少。

相关问题