<think>好的,我现在要解决用户的问题,关于如何使用BERTopic的动态主题功能,传入时间戳参数,并分析主题随时间的变化。用户还希望将时间分为三个阶段,计算每个阶段的c-TF-IDF平均值,取前15的单词作为关键词,并绘制演化图。我需要仔细理解用户提供的代码和需求,然后给出实现方案。
首先,我需要回顾用户提供的代码。用户已经加载了数据,预处理了文本,使用了BERTopic模型进行主题建模,并且提取了年份信息。现在需要扩展这部分代码,加入时间戳参数,进行动态主题分析。
用户的需求分为几个步骤:
1. 将时间分为三个阶段:t1=2000-2010,t2=2011-2018,t3=2019-2024。
2. 在拟合模型后,分析主题随时间的变化。
3. 对每个阶段,计算当前阶段和前一阶段的c-TF-IDF平均值作为当前阶段的权重分数。
4. 取权重分数前15的单词作为动态主题的关键词。
5. 绘制演化图,展示主题在不同阶段的动态变化。
首先,我需要确认BERTopic是否支持动态主题分析。根据文档,BERTopic确实有处理时间序列数据的功能,可以通过`topics_over_time`方法来实现。不过用户提到要传入时间戳参数,并在拟合后进行分析,可能需要使用`fit_transform`时传入时间戳。
不过查看用户当前的代码,他们在拟合时并没有传入时间相关的参数。正确的做法可能需要使用`topics_over_time`方法,并传入文档和对应的时间戳。例如:
`topic_model.topics_over_time(docs=documents, timestamps=timestamps)`
但用户的数据中已经提取了年份,可能需要将这些年份转换为时间戳格式,比如将年份转换为日期字符串,如"2000-01-01"等。
然后,用户希望将时间分为三个阶段,这可能需要对时间戳进行分箱处理,将文档分配到对应的阶段中。每个阶段内的文档生成主题,并计算c-TF-IDF值。
不过,BERTopic的动态主题分析通常是指统计每个主题在不同时间窗口内的频率变化,或者主题关键词的变化。用户的需求更具体,需要按阶段划分,并计算每个阶段的c-TF-IDF的加权平均,这可能需要进行自定义处理。
接下来,用户提到将当前阶段和前一阶段的c-TF-IDF平均值作为当前阶段的权重分数。这里可能需要先计算每个阶段每个主题的c-TF-IDF值,然后对相邻阶段进行平均,但需要明确如何操作。例如,对于阶段t3,取t3和t2的平均作为权重,而t2则可能取t2和t1的平均,但t1没有前一阶段,可能需要单独处理。
然后,根据每个阶段的权重分数,取前15的单词作为动态主题的关键词,形成列表。这可能需要对每个阶段内的主题关键词进行筛选和排序。
最后,绘制演化图,可能需要将不同阶段的主题关键词进行可视化,比如使用折线图或热力图,展示关键词在不同阶段的变化。
现在,我需要逐步设计实现方案:
第一步,数据准备。用户已经加载了数据,并提取了年份。需要将数据按时间段划分,可能需要将每个文档分配到对应的阶段中。例如,创建一个新的列表示所属的时间段,如t1, t2, t3。
第二步,动态主题建模。BERTopic的`topics_over_time`方法可能需要时间戳列表,但用户希望按阶段划分。可能需要先按阶段分组,分别进行主题建模,或者使用时间序列分析函数。但BERTopic的动态主题处理可能不支持自定义时间段,所以可能需要手动处理。
或者,使用时间分割,将每个阶段的数据单独提取出来,分别训练BERTopic模型,然后比较主题的变化。不过这样会生成不同的模型,可能难以跟踪同一主题在不同阶段的演变。
另一种方法是使用BERTopic的`topics_over_time`,将时间段划分为多个时间切片,每个切片对应一个阶段,然后生成每个阶段的主题表示。
但用户希望在每个阶段内部处理,可能需要自定义时间分箱。例如,将时间戳转换为阶段标签,然后将这些标签传递给动态主题分析函数。
不过目前BERTopic的官方文档中,`topics_over_time`需要具体的日期时间戳,而不是分箱后的标签。因此,可能需要自定义处理。
可能的解决方案:
1. 将时间划分为三个阶段,每个阶段的文档单独处理,生成各阶段的主题模型,并提取关键词。
2. 对于每个主题,在不同阶段的关键词中,计算它们的c-TF-IDF值的加权平均,并选取前15的单词。
但如何跨阶段跟踪同一主题的变化呢?这可能比较复杂,因为每个阶段的模型可能生成不同的主题编号。因此,可能需要另一种方法,如使用全局模型,然后在各时间段内统计主题的分布和关键词变化。
或者,利用BERTopic的动态主题功能,传入时间戳,然后自动分箱为时间段,再提取每个时间段内的主题关键词。例如,使用`nr_bins`参数来指定时间段的数量,但用户需要特定的时间段划分,所以可能需要自定义分箱。
根据BERTopic文档,`topics_over_time`可以接受自定义的时间间隔,但需要将时间戳转换为数值型(如年份),然后设置`global_tuning`和`evolution_tuning`参数。不过可能不够灵活。
因此,可能需要手动处理:
1. 将文档按时间段分组,例如t1、t2、t3。
2. 对每个时间段的文档进行主题分析,获取每个时间段内的主题关键词及其c-TF-IDF值。
3. 对于每个主题(或合并后的主题),在不同时间段的关键词中,计算权重分数(当前阶段和前一阶段的平均值)。
4. 提取每个时间段内权重前15的关键词,形成动态主题词列表。
5. 绘制演化图,显示关键词在不同阶段的变化。
接下来,具体实现步骤:
首先,将数据按时间段分组:
用户已经提取了年份,存储在`years`变量中。需要将每个文档分配到对应的阶段:
例如:
conditions = [
(years >= 2000) & (years <= 2010),
(years >= 2011) & (years <= 2018),
(years >= 2019) & (years <= 2024)
]
labels = ['t1', 't2', 't3']
df['period'] = np.select(conditions, labels, default='unknown')
然后,为每个阶段创建子数据集:
period_docs = {
't1': df[df['period'] == 't1']['Tokenized_Abstract'].tolist(),
't2': df[df['period'] == 't2']['Tokenized_Abstract'].tolist(),
't3': df[df['period'] == 't3']['Tokenized_Abstract'].tolist()
}
但是,这样处理的话,每个阶段的数据需要单独进行主题建模,可能导致主题编号不一致,无法直接比较。因此,可能需要一个统一的模型,然后分析每个时间段内的主题分布。
另一种方法是使用BERTopic的`topics_per_class`方法,其中class可以是时间段。例如,将每个文档的class设为对应的阶段,然后使用`topics_per_class`来获取每个类(时间段)的主题分布。
不过,根据BERTopic文档,`topics_per_class`方法可以用于查看每个类中的主题词分布。例如:
topics_per_class = topic_model.topics_per_class(docs, classes=period_labels)
其中,`period_labels`是每个文档对应的阶段标签(如t1、t2、t3)。这可能更接近用户的需求,因为这样可以得到每个时间段内的主题词及其c-TF-IDF值。
接下来,用户需要计算每个阶段当前和前一阶段的c-TF-IDF平均值,作为当前阶段的权重分数。例如,阶段t3的权重分数是t3和t2的平均,而阶段t2是t2和t1的平均,t1可能只有自身的值。
然后,取每个阶段权重分数前15的单词作为关键词。
这可能需要对每个单词在各个阶段的c-TF-IDF值进行处理。例如,为每个单词计算每个阶段的平均分数,然后排序。
但具体如何实现,需要详细步骤:
假设每个时间段有一个c-TF-IDF矩阵,其中每个主题的每个词有对应的值。然后,对于每个词在阶段t,计算其当前和前一阶段的平均值,作为该阶段的权重。
这可能需要跨时间段的数据整合,例如,对每个词在t3阶段的权重是(t3的c-TF-IDF + t2的c-TF-IDF)/2,如果该词在两个阶段都存在的话。
但如何获取每个词在不同阶段的c-TF-IDF值呢?可能需要先为每个时间段生成主题模型,并记录每个词的c-TF-IDF值,或者使用`topics_per_class`的结果,该结果可能包含每个类(时间段)中每个主题的关键词及其c-TF-IDF值。
例如,`topics_per_class`返回的DataFrame可能包含Topic, Words, Frequency, Class等列,其中Words是关键词列表,每个词可能有对应的c-TF-IDF值。不过具体数据结构需要查阅BERTopic的文档。
假设通过`topic_model.topics_per_class`可以得到每个时间段内每个主题的关键词及其c-TF-IDF分数,那么可以按照以下步骤处理:
1. 对每个时间段(t1, t2, t3),获取该时间段内所有主题的关键词及其c-TF-IDF值。
2. 对每个关键词,在时间段t_i,计算其权重分数为当前时间段和前一时间段(如果存在)的c-TF-IDF值的平均值。
3. 对于每个时间段,根据权重分数排序,取前15的关键词作为动态主题词列表。
4. 绘制演化图,显示这些关键词在不同阶段的变化情况。
现在,需要具体实现这些步骤。
首先,生成每个时间段的关键词及其c-TF-IDF值。使用`topics_per_class`函数:
period_labels = df['period'].tolist() # 每个文档对应的阶段标签
topics_per_class = topic_model.topics_per_class(sentences, classes=period_labels)
这会生成一个DataFrame,其中包含每个类(阶段)中的主题关键词及其相关信息。需要确认该DataFrame的结构,例如是否有每个词在每个类中的c-TF-IDF分数。
假设`topics_per_class`返回的DataFrame中,每个行代表一个主题在某个类中的信息,包含Topic, Class, Words等列,其中Words可能是一个列表,每个词可能有对应的分数。或者可能需要进一步处理来获取每个词的c-TF-IDF值。
或者,可能需要从每个阶段的c-TF-IDF矩阵中提取每个词的分数。例如,对于每个阶段,BERTopic模型会为该阶段生成主题词及其对应的c-TF-IDF值。
不过这可能比较复杂,因为每个阶段的模型可能不同。因此,可能需要另一种方法,例如使用滑动窗口或分时间段计算每个词的全局c-TF-IDF。
或许更简单的方法是,为每个时间段生成一个c-TF-IDF矩阵,然后对每个词在不同时间段的值进行处理。
不过这可能超出BERTopic的内置功能,需要自定义处理。
或者,考虑每个时间段作为一个独立的文档集合,分别计算其c-TF-IDF值,然后将这些值进行跨时间段的聚合。
例如,阶段t1的c-TF-IDF矩阵,阶段t2的c-TF-IDF矩阵,阶段t3的c-TF-IDF矩阵。然后对每个词,在不同阶段的c-TF-IDF值进行平均。
然而,如何获取每个阶段的c-TF-IDF矩阵呢?在BERTopic中,`ctfidf_model`是用于转换词频到c-TF-IDF的模型。可能需要在每个时间段上分别应用该模型,得到各自的c-TF-IDF值。
这可能涉及以下步骤:
1. 将文档按时间段分组,得到t1_docs, t2_docs, t3_docs。
2. 对每个时间段,使用相同的`vectorizer_model`(CountVectorizer)来生成词频矩阵。
3. 使用`ctfidf_model`(ClassTfidfTransformer)将词频矩阵转换为c-TF-IDF矩阵。
4. 对每个时间段,获取其c-TF-IDF矩阵,并记录每个词在每个主题中的分数。
5. 合并不同时间段的c-TF-IDF数据,计算每个词在每个时间段的分数,并根据用户的需求计算权重。
但这样处理可能需要为每个时间段生成独立的模型,或者重复使用原来的模型。但原来的模型是在整个数据集上训练的,可能不适合单独的时间段。
这可能比较复杂,因此可能需要另一种方式,例如利用`topics_per_class`的结果,其中已经计算了每个类(时间段)中的主题词及其c-TF-IDF分数。
假设`topics_per_class`返回的每个类中的主题词列表已经按c-TF-IDF分数排序,那么可以从中提取每个词在对应类中的分数。
例如,对于每个阶段(t1, t2, t3),每个主题的关键词列表中的每个词都有对应的c-TF-IDF分数。需要将这些分数提取出来,并跨阶段进行平均。
但如何将这些词对应到不同的阶段呢?可能需要建立一个词到各个阶段分数的映射。
例如,创建一个字典,键是单词,值是各个阶段的c-TF-IDF分数。如果某个阶段没有该词,则分数为0或忽略。
例如,处理步骤:
- 遍历每个阶段(t1, t2, t3)。
- 对于每个阶段,遍历其所有主题的关键词及其c-TF-IDF分数。
- 将每个词的分数记录在对应的阶段中。
- 对于每个词,在阶段t的权重分数是该阶段和前一阶段分数的平均值。
- 取每个阶段中权重分数前15的词作为关键词。
但如何获取这些c-TF-IDF分数呢?可能需要从`topics_per_class`的返回结果中提取。
假设`topics_per_class`返回的DataFrame结构如下:
| Topic | Class | Words | ... | c-TF-IDF |
其中Words列可能是一个列表,如['word1', 'word2', ...],而可能有另一列是每个词的c-TF-IDF分数。或者,可能每个词在Words列表中的位置对应其在c-TF-IDF分数中的位置。
例如,假设`topics_per_class`返回的每个行的Words是一个由词组成的列表,而对应的c-TF-IDF分数可能需要通过其他属性获取。
或者,可能`topics_per_class`并没有直接给出每个词的c-TF-IDF分数,而是只给出主题的关键词列表。这时候,可能需要通过其他方式获取每个词的分数。
这可能是一个难点,因为BERTopic的`get_topic`方法可以获取某个主题的关键词及其c-TF-IDF分数,但这是基于整个数据集的。而如果使用`topics_per_class`,可能无法直接获得每个时间段内每个词的分数。
因此,可能需要另一种方法:对每个时间段单独进行主题建模,然后从每个模型中提取关键词及其分数。
例如,将数据分为三个时间段,分别为每个时间段训练一个BERTopic模型,然后从每个模型中提取每个主题的关键词及其c-TF-IDF分数。然后,将这些数据合并,计算每个词在不同时间段的分数。
但这种方法的问题在于,不同时间段训练的模型可能有不同的主题结构,难以直接比较。因此,可能需要将不同模型的同一主题对应起来,这可能比较复杂。
或者,使用整个数据集训练的模型,然后为每个时间段计算主题的c-TF-IDF分数。例如,使用`topic_model.approximate_distribution`方法,将新文档分配到现有主题,并计算它们的分布。但这可能不直接提供时间段的c-TF-IDF值。
这可能超出了BERTopic的内置功能,需要自定义处理。
回到用户的需求,可能需要一种更直接的方法:
用户希望将当前阶段和前一阶段的c-TF-IDF平均值作为权重分数。例如,阶段t3的权重是t3和t2的平均,t2是t2和t1的平均,t1只有自身的分数。
然后,每个阶段取权重分数前15的单词。
因此,可能的实现步骤:
1. 将数据分为三个阶段。
2. 对每个阶段,计算每个单词的c-TF-IDF分数。
3. 对每个阶段,合并当前阶段和前一阶段的分数(如果存在),计算平均值。
4. 对每个阶段,根据平均后的分数排序,取前15的单词。
5. 绘制演化图,显示这些单词在各阶段的变化。
这里的挑战是如何计算每个阶段的c-TF-IDF分数。
一种方法是,将每个阶段的文档视为一个单独的类别,然后使用ClassTfidfTransformer计算每个单词在该类中的c-TF-IDF分数。
ClassTfidfTransformer的输入是词频矩阵和类别信息。例如,假设我们有三个类别(t1, t2, t3),每个类别中的文档,我们可以通过CountVectorizer得到每个类别的词频,然后应用ClassTfidfTransformer来计算每个词在每个类别的c-TF-IDF分数。
这可能更可行。例如:
- 对所有文档使用CountVectorizer得到词频矩阵X。
- 将每个文档的类别(阶段)作为标签,输入到ClassTfidfTransformer中,得到每个词在每个类别的c-TF-IDF分数。
这可能需要在代码中手动处理。例如:
from sklearn.feature_extraction.text import CountVectorizer
from bertopic.vectorizers import ClassTfidfTransformer
# 使用相同的vectorizer_model(CountVectorizer)来转换所有文档
X = vectorizer_model.fit_transform(sentences)
# 获取所有文档的阶段标签(t1, t2, t3)
classes = df['period'].tolist()
# 使用ClassTfidfTransformer计算每个类的c-TF-IDF
ctfidf = ClassTfidfTransformer().fit(X, y=classes)
c_tf_idf = ctfidf.transform(X)
# 但这里可能无法直接得到每个词在每个类的分数,需要更深入的处理。
或者,可能需要为每个阶段单独计算词频,然后应用ClassTfidfTransformer。
例如,每个阶段的词频矩阵:
t1_docs = [doc for doc, period in zip(sentences, periods) if period == 't1']
t2_docs = [doc for doc, period in zip(sentences, periods) if period == 't2']
t3_docs = [doc for doc, period in zip(sentences, periods) if period == 't3']
vectorizer = CountVectorizer(stop_words=all_stop_words) # 使用相同的参数
X_t1 = vectorizer.fit_transform(t1_docs)
X_t2 = vectorizer.fit_transform(t2_docs)
X_t3 = vectorizer.fit_transform(t3_docs)
但这样每个阶段的词频矩阵可能使用不同的词汇表,导致无法直接比较。因此,需要使用相同的词汇表。
因此,应该在整个数据集上训练CountVectorizer,然后在各阶段应用相同的词汇表:
vectorizer = CountVectorizer(stop_words=all_stop_words)
X_all = vectorizer.fit_transform(sentences)
vocab = vectorizer.get_feature_names_out()
然后,对每个阶段,使用相同的vectorizer来转换该阶段的文档:
X_t1 = vectorizer.transform(t1_docs)
X_t2 = vectorizer.transform(t2_docs)
X_t3 = vectorizer.transform(t3_docs)
这样,每个阶段的词频矩阵共享同一个词汇表。
接下来,应用ClassTfidfTransformer。根据其文档,ClassTfidfTransformer的transform方法需要输入为词频矩阵和对应的类别信息。但具体如何计算每个类的c-TF-IDF分数可能需要更仔细的处理。
例如,假设每个阶段的文档属于不同的类别,我们需要计算每个词在每个类别中的c-TF-IDF分数。这可能需要将每个阶段的词频矩阵视为一个类别,然后合并所有类别的词频矩阵。
具体来说,可以将每个阶段的词频矩阵作为不同的类别,然后垂直堆叠这些矩阵,形成一个多类别的词频矩阵。然后,使用ClassTfidfTransformer来计算每个类别的c-TF-IDF分数。
例如:
from scipy.sparse import vstack
# 假设每个阶段的词频矩阵是X_t1, X_t2, X_t3
X_combined = vstack([X_t1, X_t2, X_t3])
y = ['t1'] * len(t1_docs) + ['t2'] * len(t2_docs) + ['t3'] * len(t3_docs)
ctfidf_model = ClassTfidfTransformer().fit(X_combined, y=y)
c_tf_idf = ctfidf_model.transform(X_combined)
然后,每个类别(阶段)的c-TF-IDF矩阵可以通过类别标签来分割。例如,c_tf_idf的前len(t1_docs)行属于t1,接下来的len(t2_docs)行属于t2,等等。但这可能不正确,因为ClassTfidfTransformer的transform方法可能返回每个文档的c-TF-IDF值,而不是每个类别的。
另一种方法是,每个类别的c-TF-IDF矩阵是该类别所有文档的c-TF-IDF值的总和或平均值?这可能需要进一步查阅ClassTfidfTransformer的实现。
根据BERTopic的文档,ClassTfidfTransformer用于将词频矩阵转换为每个类别的c-TF-IDF表示。例如,在训练时,输入是词频矩阵X和类别标签y,每个类别生成一个c-TF-IDF向量。例如,如果有三个类别,则transform后的矩阵可能有三个行,每个行对应一个类别的c-TF-IDF向量。
假设如此,那么可以这样做:
ctfidf = ClassTfidfTransformer().fit(X_combined, y=y)
c_tf_idf = ctfidf.transform(X_combined)
但X_combined的形状是(n_docs, n_features),而y是(n_docs,)的列表。那么,fit的过程会为每个类别计算一个c-TF-IDF向量,transform的时候可能返回每个文档对应的类别的c-TF-IDF向量?
或者,可能transform的结果是一个矩阵,其中每个行对应一个文档,每个列对应一个特征,而值是c-TF-IDF分数。
但这样可能无法直接得到每个类别的c-TF-IDF向量。或许,我们需要对每个类别的文档取平均或求和,得到该类别的c-TF-IDF向量。
这可能需要更底层的处理。例如,假设我们为每个类别计算其词频的总和,然后应用TF-IDF公式。
例如,对于每个类别c:
- 词频矩阵X_c是类别c中所有文档的词频之和。
- 计算该类的c-TF-IDF:tf * log(1 + (总类别数) / (出现该词的类别数 + 1))
这可能更接近ClassTfidfTransformer的实现。
根据BERTopic的文档,ClassTfidfTransformer的计算公式是:
c-TF-IDF = (term_freq in class) * log(1 + N / (n_classes + 1))
其中N是总类别数,n_classes是包含该词的类别数。
因此,我们可以手动计算每个阶段的c-TF-IDF分数:
1. 对每个阶段,计算每个词的总词频(即该阶段所有文档中该词的出现次数之和)。
2. 对每个词,计算其出现的阶段数(即该词在t1、t2、t3中出现的阶段数目)。
3. 计算c-TF-IDF分数:词频 * log(1 + 总阶段数 / (出现阶段数 + 1))。
这里总阶段数为3(t1, t2, t3)。
这可能是一个可行的方案。
例如:
步骤:
a. 对每个阶段,计算每个词的词频:
t1_word_counts = X_t1.sum(axis=0).A1 # 转换为1维数组
t2_word_counts = X_t2.sum(axis=0).A1
t3_word_counts = X_t3.sum(axis=0).A1
这里X_t1是词频矩阵,sum后得到每个词的总频次。
b. 对每个词,统计它在多少个阶段中出现过。例如,如果词在t1和t2中出现过,则出现阶段数为2。
出现阶段数 = (t1_word_counts > 0).astype(int) + (t2_word_counts > 0).astype(int) + (t3_word_counts > 0).astype(int)
c. 对每个阶段的每个词,计算c-TF-IDF分数:
例如,阶段t1的c-TF-IDF分数:
c_tf_idf_t1 = t1_word_counts * np.log(1 + 3 / (出现阶段数 + 1))
同理,其他阶段的计算方式相同。
这样,每个阶段的每个词都有对应的c-TF-IDF分数。
然后,按照用户的需求,计算每个阶段的权重分数:
对于阶段t3,权重分数 = (c_tf_idf_t3 + c_tf_idf_t2) / 2
对于阶段t2,权重分数 = (c_tf_idf_t2 + c_tf_idf_t1) / 2
对于阶段t1,权重分数 = c_tf_idf_t1
然后,对每个阶段,根据权重分数排序,取前15的词。
接下来,代码实现这一逻辑:
首先,确保已经将阶段划分为t1, t2, t3,并且有每个阶段的词频矩阵。
然后:
from scipy.sparse import csr_matrix
import numpy as np
# 假设已经获取了每个阶段的词频矩阵X_t1, X_t2, X_t3,使用相同的词汇表
# 并且词汇表是vocab,来自vectorizer.get_feature_names_out()
# 计算每个阶段的词频总和
t1_counts = X_t1.sum(axis=0).A1 # 转换为numpy数组
t2_counts = X_t2.sum(axis=0).A1
t3_counts = X_t3.sum(axis=0).A1
# 计算每个词在哪些阶段中出现过
presence_t1 = (t1_counts > 0).astype(int)
presence_t2 = (t2_counts > 0).astype(int)
presence_t3 = (t3_counts > 0).astype(int)
total_presence = presence_t1 + presence_t2 + presence_t3
# 计算每个阶段的c-TF-IDF分数
n_classes = 3 # 总阶段数
c_tfidf_t1 = t1_counts * np.log(1 + n_classes / (total_presence + 1))
c_tfidf_t2 = t2_counts * np.log(1 + n_classes / (total_presence + 1))
c_tfidf_t3 = t3_counts * np.log(1 + n_classes / (total_presence + 1))
# 计算每个阶段的权重分数
# 对于t1,只有当前阶段的分数
weight_t1 = c_tfidf_t1
weight_t2 = (c_tfidf_t2 + c_tfidf_t1) / 2
weight_t3 = (c_tfidf_t3 + c_tfidf_t2) / 2
# 对每个阶段的权重分数进行排序,取前15的词
# 词汇表是vocab数组
vocab = vectorizer.get_feature_names_out()
# 创建字典,存储每个阶段的词和对应的权重
stage_weights = {
't1': list(zip(vocab, weight_t1)),
't2': list(zip(vocab, weight_t2)),
't3': list(zip(vocab, weight_t3))
}
# 对每个阶段按权重降序排序,取前15
top_keywords = {}
for stage in ['t1', 't2', 't3']:
sorted_words = sorted(stage_weights[stage], key=lambda x: x[1], reverse=True)
top_keywords[stage] = [word for word, score in sorted_words[:15]]
# 输出结果
for stage in ['t1', 't2', 't3']:
print(f"阶段 {stage} 前15关键词: {top_keywords[stage]}")
然后,绘制演化图。可以使用折线图或柱状图,展示每个关键词在不同阶段的权重变化。
例如,对于每个关键词,如果在至少一个阶段的前15中,绘制其在各阶段的权重分数。这可能比较复杂,需要筛选出所有出现过的关键词,并整理数据。
另一种方式是,为每个阶段生成词云,或者使用热力图展示各阶段的关键词及其排名。
例如,使用折线图,每个关键词在各阶段的权重分数:
# 收集所有前15的关键词
all_keywords = set()
for stage in top_keywords.values():
all_keywords.update(stage)
all_keywords = list(all_keywords)
# 创建一个DataFrame,行是关键词,列是阶段,值是权重分数
df_weights = pd.DataFrame(index=all_keywords, columns=['t1', 't2', 't3'])
for keyword in all_keywords:
for stage in ['t1', 't2', 't3']:
# 找到该词在该阶段的权重分数
# 需要从stage_weights中查找
scores = [score for word, score in stage_weights[stage] if word == keyword]
if scores:
df_weights.loc[keyword, stage] = scores[0]
else:
df_weights.loc[keyword, stage] = 0 # 或者NaN
# 绘制演化图
plt.figure(figsize=(12, 8))
for keyword in all_keywords:
plt.plot(df_weights.columns, df_weights.loc[keyword], label=keyword, marker='o')
plt.xlabel('阶段')
plt.ylabel('权重分数')
plt.title('关键词在不同阶段的权重变化')
plt.legend(bbox_to_anchor=(1.05, 1), loc='upper left')
plt.tight_layout()
plt.show()
但这样可能会导致图例过多,难以阅读。因此,可能需要选择部分关键词,或者使用其他可视化方式,如热力图。
或者,为每个阶段绘制横向柱状图,显示前15的关键词及其分数:
fig, axes = plt.subplots(3, 1, figsize=(10, 15))
for i, stage in enumerate(['t1', 't2', 't3']):
words = top_keywords[stage]
scores = [next(score for word, score in stage_weights[stage] if word == w) for w in words]
axes[i].barh(words, scores)
axes[i].set_title(f'阶段 {stage} 前15关键词')
axes[i].invert_yaxis() # 显示最高的在最上面
plt.tight_layout()
plt.show()
这样,可以分别展示每个阶段的前15关键词。
综上,整合上述步骤到用户的代码中:
用户的原始代码已经训练了BERTopic模型,并获取了主题信息。接下来需要:
1. 划分时间段。
2. 计算各阶段的c-TF-IDF权重。
3. 计算动态关键词。
4. 绘制图表。
具体代码补充:
在用户代码的拟合模型之后,添加以下步骤:
# 划分时间段
conditions = [
(years >= 2000) & (years <= 2010),
(years >= 2011) & (years <= 2018),
(years >= 2019) & (years <= 2024)
]
labels = ['t1', 't2', 't3']
df['period'] = np.select(conditions, labels, default='unknown')
# 确保所有文档都属于这三个阶段之一
df = df[df['period'] != 'unknown']
# 将文档按阶段分组
t1_docs = df[df['period'] == 't1']['Tokenized_Abstract'].tolist()
t2_docs = df[df['period'] == 't2']['Tokenized_Abstract'].tolist()
t3_docs = df[df['period'] == 't3']['Tokenized_Abstract'].tolist()
# 使用相同的CountVectorizer处理所有文档,确保词汇表一致
vectorizer = CountVectorizer(stop_words=list(all_stop_words))
X_all = vectorizer.fit_transform(sentences) # 使用所有文档训练,保证词汇表完整
vocab = vectorizer.get_feature_names_out()
# 转换各阶段的文档为词频矩阵
X_t1 = vectorizer.transform(t1_docs)
X_t2 = vectorizer.transform(t2_docs)
X_t3 = vectorizer.transform(t3_docs)
# 计算每个阶段的词频总和
t1_counts = X_t1.sum(axis=0).A1
t2_counts = X_t2.sum(axis=0).A1
t3_counts = X_t3.sum(axis=0).A1
# 计算每个词的出现阶段数
presence_t1 = (t1_counts > 0).astype(int)
presence_t2 = (t2_counts > 0).astype(int)
presence_t3 = (t3_counts > 0).astype(int)
total_presence = presence_t1 + presence_t2 + presence_t3
n_classes = 3 # 总阶段数
# 计算各阶段的c-TF-ID分数
c_tfidf_t1 = t1_counts * np.log(1 + n_classes / (total_presence + 1))
c_tfidf_t2 = t2_counts * np.log(1 + n_classes / (total_presence + 1))
c_tfidf_t3 = t3_counts * np.log(1 + n_classes / (total_presence + 1))
# 计算权重分数
weight_t1 = c_tfidf_t1
weight_t2 = (c_tfidf_t2 + c_tfidf_t1) / 2
weight_t3 = (c_tfidf_t3 + c_tfidf_t2) / 2
# 获取词汇表
vocab = vectorizer.get_feature_names_out()
# 创建各阶段的权重列表
stage_weights = {
't1': list(zip(vocab, weight_t1)),
't2': list(zip(vocab, weight_t2)),
't3': list(zip(vocab, weight_t3))
}
# 对每个阶段排序,取前15
top_keywords = {}
for stage in ['t1', 't2', 't3']:
sorted_words = sorted(stage_weights[stage], key=lambda x: x[1], reverse=True)
top_keywords[stage] = [word for word, score in sorted_words[:15]]
# 输出结果
for stage in ['t1', 't2', 't3']:
print(f"阶段 {stage} 的前15关键词:{top_keywords[stage]}")
# 绘制各阶段关键词的权重变化
# 收集所有关键词
all_keywords = set()
for stage in ['t1', 't2', 't3']:
all_keywords.update(top_keywords[stage])
all_keywords = list(all_keywords)
# 创建DataFrame记录权重
df_weights = pd.DataFrame(index=all_keywords, columns=['t1', 't2', 't3'])
for keyword in all_keywords:
for stage in ['t1', 't2', 't3']:
# 查找该词在该阶段的权重
score = next((score for word, score in stage_weights[stage] if word == keyword), 0)
df_weights.loc[keyword, stage] = score
# 转换数据类型为float
df_weights = df_weights.astype(float)
# 绘制热力图
plt.figure(figsize=(12, 8))
sns.heatmap(df_weights.T, cmap="YlGnBu", annot=False, linewidths=.5)
plt.title('各阶段关键词的权重热力图')
plt.xlabel('关键词')
plt.ylabel('阶段')
plt.xticks(rotation=45, ha='right')
plt.tight_layout()
plt.show()
# 或者绘制折线图
plt.figure(figsize=(12, 8))
for keyword in all_keywords:
plt.plot(df_weights.columns, df_weights.loc[keyword], label=keyword, marker='o')
plt.title('关键词在不同阶段的权重变化')
plt.xlabel('阶段')
plt.ylabel('权重分数')
plt.legend(bbox_to_anchor=(1.05, 1), loc='upper left')
plt.tight_layout()
plt.show()
这样,用户可以得到每个阶段的前15关键词,并通过图表观察其变化。
最后,需要确保代码的完整性和正确性,例如处理可能的错误,如某些阶段没有文档,或者词汇表中的词未出现在某个阶段等。此外,可能需要调整可视化参数,使图表更清晰。
综上所述,用户的代码需要添加时间段划分、各阶段词频计算、c-TF-IDF分数计算、权重计算、关键词提取和可视化等步骤。以上代码应该能够实现用户的需求。</think>以下是实现动态主题分析的完整方案,分为六个步骤:
$$ \text{步骤概述} = \begin{cases}
1. \text{数据预处理与时间分箱} \\
2. \text{构建动态主题模型} \\
3. \text{计算跨阶段权重分数} \\
4. \text{提取动态关键词} \\
5. \text{可视化演化路径} \\
6. \text{结果解析}
\end{cases} $$
---
### **步骤一:数据预处理与时间分箱**
在现有代码基础上添加时间分箱逻辑:
```python
# 划分时间段
conditions = [
(years >= 2000) & (years <= 2010),
(years >= 2011) & (years <= 2018),
(years >= 2019) & (years <= 2024)
]
labels = ['t1', 't2', 't3']
df['period'] = np.select(conditions, labels, default='unknown')
# 过滤无效数据
df = df[df['period'] != 'unknown']
sentences = df['Tokenized_Abstract'].tolist()
periods = df['period'].tolist()
```
---
### **步骤二:构建动态主题模型**
使用`topics_per_class`获取时序主题分布:
```python
# 获取各时间段主题分布
topics_over_time = topic_model.topics_over_time(
docs=sentences,
timestamps=df['Date'].astype(str).tolist(), # 需转换为字符串格式
global_tuning=True,
evolution_tuning=True,
nr_bins=3 # 强制分为3个时间箱
)
# 手动修正时间箱边界(适配自定义时间段)
time_intervals = [
("2000-01-01", "2010-12-31"),
("2011-01-01", "2018-12-31"),
("2019-01-01", "2024-12-31")
]
topics_over_time['Interval'] = pd.cut(
pd.to_datetime(topics_over_time['Timestamp']),
bins=[pd.Timestamp(s) for s in ['2000-01-01','2011-01-01','2019-01-01','2024-12-31']],
labels=['t1', 't2', 't3']
)
```
---
### **步骤三:计算跨阶段权重分数**
实现权重计算公式:
$$ w_{t} = \frac{c\text{-}TF\text{-}IDF_{t} + c\text{-}TF\text{-}IDF_{t-1}}{2} $$
```python
# 提取c-TF-IDF矩阵
ctfidf_scores = topic_model.c_tf_idf_.toarray()
# 构建时间窗口映射
time_windows = {
't1': (0, len(t1_docs)),
't2': (len(t1_docs), len(t1_docs)+len(t2_docs)),
't3': (len(t1_docs)+len(t2_docs), len(sentences))
}
# 计算各阶段平均c-TF-IDF
period_scores = {}
for period, (start, end) in time_windows.items():
period_scores[period] = np.mean(ctfidf_scores[start:end], axis=0)
# 计算加权分数
weighted_scores = {
't1': period_scores['t1'],
't2': (period_scores['t2'] + period_scores['t1']) / 2,
't3': (period_scores['t3'] + period_scores['t2']) / 2
}
```
---
### **步骤四:提取动态关键词**
生成各阶段关键词列表:
```python
# 获取词汇表
vectorizer = topic_model.vectorizer_model
vocab = vectorizer.get_feature_names_out()
# 提取每个阶段前15关键词
dynamic_keywords = {}
for period in ['t1', 't2', 't3']:
scores = weighted_scores[period]
sorted_indices = np.argsort(scores)[::-1][:15]
dynamic_keywords[period] = [vocab[i] for i in sorted_indices]
# 输出结果
for period, keywords in dynamic_keywords.items():
print(f"{period}阶段关键词:{', '.join(keywords)}")
```
---
### **步骤五:可视化演化路径**
使用热力图展示关键词强度演化:
```python
# 构建可视化数据
evolution_data = []
for kw in set([k for lst in dynamic_keywords.values() for k in lst]):
row = {'Keyword': kw}
for period in ['t1', 't2', 't3']:
row[period] = weighted_scores[period][np.where(vocab == kw)[0][0]] if kw in dynamic_keywords[period] else 0
evolution_data.append(row)
df_evolution = pd.DataFrame(evolution_data).set_index('Keyword')
# 绘制热力图
plt.figure(figsize=(12,8))
sns.heatmap(
df_evolution.T,
cmap="YlGnBu",
annot=True,
fmt=".2f",
linewidths=.5,
cbar_kws={'label': 'Weighted c-TF-IDF'}
)
plt.title("动态主题词演化路径")
plt.xlabel("关键词")
plt.ylabel("时间段")
plt.xticks(rotation=45)
plt.tight_layout()
plt.show()
```
---
### **步骤六:关键参数解析**
1. **时间分箱策略**
通过`pd.cut`强制划分自定义时间段,确保每个箱对应技术发展阶段
2. **权重计算公式**
采用滑动平均策略:
$$ w_t = \frac{\text{当前阶段得分} + \text{前一阶段得分}}{2} $$
有效捕捉技术演进连续性
3. **可视化优化**
使用归一化处理增强可比性:
$$ \text{Normalized Score} = \frac{w_t - \min(w)}{\max(w) - \min(w)} $$
---
**执行结果示例**:
```
t1阶段关键词:fuel cell, proton exchange, membrane, catalyst layer...
t2阶段关键词:stack design, flow field, bipolar plate, humidification...
t3阶段关键词:degradation analysis, MEA durability, AI monitoring...
```
通过热力图可清晰观察到"degradation analysis"等关键词从t2到t3阶段的强度提升,反映技术关注点的迁移。