bertrend 0.3.1__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (181) hide show
  1. bertrend/BERTopicModel.py +320 -0
  2. bertrend/BERTrend.py +1211 -0
  3. bertrend/__init__.py +59 -0
  4. bertrend/config/bertopic_default_config.toml +48 -0
  5. bertrend/config/bertrend_default_config.toml +16 -0
  6. bertrend/config/parameters.py +92 -0
  7. bertrend/config/services_default_config.toml +32 -0
  8. bertrend/demos/__init__.py +4 -0
  9. bertrend/demos/demos_utils/__init__.py +13 -0
  10. bertrend/demos/demos_utils/data_loading_component.py +267 -0
  11. bertrend/demos/demos_utils/embed_documents_component.py +52 -0
  12. bertrend/demos/demos_utils/icons.py +41 -0
  13. bertrend/demos/demos_utils/messages.py +11 -0
  14. bertrend/demos/demos_utils/parameters_component.py +162 -0
  15. bertrend/demos/demos_utils/state_utils.py +99 -0
  16. bertrend/demos/start_summarizer_app.sh +9 -0
  17. bertrend/demos/start_topic_analysis_app.sh +9 -0
  18. bertrend/demos/start_weak_signals_app.sh +9 -0
  19. bertrend/demos/summarization/__init__.py +4 -0
  20. bertrend/demos/summarization/summarizer_app.py +98 -0
  21. bertrend/demos/topic_analysis/__init__.py +4 -0
  22. bertrend/demos/topic_analysis/app.py +87 -0
  23. bertrend/demos/topic_analysis/app_utils.py +69 -0
  24. bertrend/demos/topic_analysis/demo_pages/__init__.py +4 -0
  25. bertrend/demos/topic_analysis/demo_pages/explore_topics.py +455 -0
  26. bertrend/demos/topic_analysis/demo_pages/newsletters_generation.py +178 -0
  27. bertrend/demos/topic_analysis/demo_pages/temporal_visualizations.py +573 -0
  28. bertrend/demos/topic_analysis/demo_pages/topic_visualizations.py +280 -0
  29. bertrend/demos/topic_analysis/demo_pages/training_page.py +219 -0
  30. bertrend/demos/topic_analysis/messages.py +13 -0
  31. bertrend/demos/weak_signals/__init__.py +4 -0
  32. bertrend/demos/weak_signals/app.py +510 -0
  33. bertrend/demos/weak_signals/messages.py +32 -0
  34. bertrend/demos/weak_signals/visualizations_utils.py +403 -0
  35. bertrend/llm_utils/__init__.py +4 -0
  36. bertrend/llm_utils/newsletter.css +131 -0
  37. bertrend/llm_utils/newsletter_features.py +272 -0
  38. bertrend/llm_utils/openai_client.py +235 -0
  39. bertrend/llm_utils/prompts.py +94 -0
  40. bertrend/metrics/__init__.py +4 -0
  41. bertrend/metrics/temporal_metrics_embedding.py +861 -0
  42. bertrend/metrics/topic_metrics.py +199 -0
  43. bertrend/resources/common_ngrams.json +23 -0
  44. bertrend/resources/stopwords-en.json +3 -0
  45. bertrend/resources/stopwords-fr.json +1 -0
  46. bertrend/resources/stopwords-rte.json +15 -0
  47. bertrend/services/__init__.py +4 -0
  48. bertrend/services/authentication.py +66 -0
  49. bertrend/services/embedding_client.py +154 -0
  50. bertrend/services/embedding_service.py +380 -0
  51. bertrend/services/summarizer.py +45 -0
  52. bertrend/services/summary/__init__.py +4 -0
  53. bertrend/services/summary/abstractive_summarizer.py +74 -0
  54. bertrend/services/summary/chatgpt_summarizer.py +67 -0
  55. bertrend/services/summary/extractive_summarizer.py +368 -0
  56. bertrend/services/summary/lexrank.py +126 -0
  57. bertrend/services/summary/prompts.py +43 -0
  58. bertrend/tests/__init__.py +4 -0
  59. bertrend/tests/test_abstractive_summarizer.py +140 -0
  60. bertrend/tests/test_bertrend.py +1091 -0
  61. bertrend/tests/test_cache_utils.py +111 -0
  62. bertrend/tests/test_chatgpt_summarizer.py +150 -0
  63. bertrend/tests/test_config_utils.py +54 -0
  64. bertrend/tests/test_data_loading.py +326 -0
  65. bertrend/tests/test_data_structure.py +53 -0
  66. bertrend/tests/test_embedding_client.py +546 -0
  67. bertrend/tests/test_extractive_summarizer.py +250 -0
  68. bertrend/tests/test_installation.py +183 -0
  69. bertrend/tests/test_newsletter_features.py +354 -0
  70. bertrend/tests/test_openai_client.py +210 -0
  71. bertrend/tests/test_representative_docs.py +125 -0
  72. bertrend/tests/test_summarizer.py +115 -0
  73. bertrend/tests/test_temporal_metrics_embedding.py +349 -0
  74. bertrend/tests/test_topic_description.py +159 -0
  75. bertrend/tests/test_topic_metrics.py +233 -0
  76. bertrend/tests/test_topic_model.py +285 -0
  77. bertrend/tests/test_trend_analysis_data_structure.py +174 -0
  78. bertrend/tests/test_trend_analysis_prompts.py +225 -0
  79. bertrend/tests/test_trend_analysis_visualizations.py +307 -0
  80. bertrend/tests/test_trend_analysis_weak_signals.py +598 -0
  81. bertrend/tests/test_utils_init.py +70 -0
  82. bertrend/tests/test_visualizations.py +306 -0
  83. bertrend/topic_analysis/__init__.py +4 -0
  84. bertrend/topic_analysis/data_structure.py +13 -0
  85. bertrend/topic_analysis/prompts.py +42 -0
  86. bertrend/topic_analysis/representative_docs.py +57 -0
  87. bertrend/topic_analysis/topic_description.py +66 -0
  88. bertrend/topic_analysis/visualizations.py +315 -0
  89. bertrend/trend_analysis/__init__.py +4 -0
  90. bertrend/trend_analysis/data_structure.py +78 -0
  91. bertrend/trend_analysis/prompts.py +229 -0
  92. bertrend/trend_analysis/signal_llm_template_en.html +181 -0
  93. bertrend/trend_analysis/signal_llm_template_fr.html +181 -0
  94. bertrend/trend_analysis/visualizations.py +605 -0
  95. bertrend/trend_analysis/weak_signals.py +425 -0
  96. bertrend/utils/__init__.py +31 -0
  97. bertrend/utils/cache_utils.py +31 -0
  98. bertrend/utils/config_utils.py +41 -0
  99. bertrend/utils/data_loading.py +329 -0
  100. bertrend-0.3.1.dist-info/AUTHORS.txt +15 -0
  101. bertrend-0.3.1.dist-info/LICENSE.md +373 -0
  102. bertrend-0.3.1.dist-info/METADATA +400 -0
  103. bertrend-0.3.1.dist-info/RECORD +181 -0
  104. bertrend-0.3.1.dist-info/WHEEL +4 -0
  105. bertrend_apps/__init__.py +4 -0
  106. bertrend_apps/common/__init__.py +4 -0
  107. bertrend_apps/common/crontab_utils.py +113 -0
  108. bertrend_apps/common/mail_utils.py +91 -0
  109. bertrend_apps/config/feeds/arxiv_feed.toml +15 -0
  110. bertrend_apps/config/feeds/decarbonation_feed.toml +15 -0
  111. bertrend_apps/config/feeds/llm_en_feed.toml +15 -0
  112. bertrend_apps/config/feeds/llm_feed.toml +16 -0
  113. bertrend_apps/config/feeds/nlp_feed.toml +15 -0
  114. bertrend_apps/config/feeds/rte_feed.toml +15 -0
  115. bertrend_apps/config/gmail_credentials.json +1 -0
  116. bertrend_apps/config/newsletters/arxiv_newsletter_from_last.toml +39 -0
  117. bertrend_apps/config/newsletters/arxiv_newsletter_from_scratch.toml +39 -0
  118. bertrend_apps/config/newsletters/decarbonation_newsletter_from_last.toml +38 -0
  119. bertrend_apps/config/newsletters/decarbonation_newsletter_from_scratch.toml +38 -0
  120. bertrend_apps/config/newsletters/llm_en_newsletter_from_last.toml +38 -0
  121. bertrend_apps/config/newsletters/llm_newsletter_from_last.toml +39 -0
  122. bertrend_apps/config/newsletters/nlp_newsletter_from_last.toml +38 -0
  123. bertrend_apps/config/newsletters/rte_newsletter_from_last.toml +38 -0
  124. bertrend_apps/config/newsletters/rte_newsletter_from_scratch.toml +38 -0
  125. bertrend_apps/data_provider/__init__.py +9 -0
  126. bertrend_apps/data_provider/__main__.py +237 -0
  127. bertrend_apps/data_provider/arxiv_provider.py +161 -0
  128. bertrend_apps/data_provider/bing_news_provider.py +82 -0
  129. bertrend_apps/data_provider/curebot_provider.py +105 -0
  130. bertrend_apps/data_provider/data_provider.py +194 -0
  131. bertrend_apps/data_provider/google_news_provider.py +94 -0
  132. bertrend_apps/data_provider/newscatcher_provider.py +74 -0
  133. bertrend_apps/data_provider/utils.py +165 -0
  134. bertrend_apps/exploration/__init__.py +4 -0
  135. bertrend_apps/exploration/curebot/__init__.py +4 -0
  136. bertrend_apps/exploration/curebot/app.py +45 -0
  137. bertrend_apps/exploration/curebot/app_utils.py +317 -0
  138. bertrend_apps/exploration/curebot/config.toml +33 -0
  139. bertrend_apps/exploration/curebot/import_extraction_article.ipynb +108 -0
  140. bertrend_apps/exploration/curebot/old_veille_analyse.py +298 -0
  141. bertrend_apps/exploration/curebot/prompts.py +24 -0
  142. bertrend_apps/exploration/curebot/start_newsletter_generator.sh +8 -0
  143. bertrend_apps/exploration/curebot/tabs/tab1.py +150 -0
  144. bertrend_apps/exploration/curebot/tabs/tab2.py +95 -0
  145. bertrend_apps/exploration/curebot/tabs/tab3.py +125 -0
  146. bertrend_apps/exploration/dataviz/Main_page.py +10 -0
  147. bertrend_apps/exploration/dataviz/__init__.py +4 -0
  148. bertrend_apps/exploration/dataviz/pages/1_Data_feeds.py +105 -0
  149. bertrend_apps/exploration/geolocalization/__init__.py +4 -0
  150. bertrend_apps/exploration/geolocalization/entity_utils.py +118 -0
  151. bertrend_apps/exploration/geolocalization/spacy/__init__.py +4 -0
  152. bertrend_apps/exploration/geolocalization/spacy/common_factory.py +142 -0
  153. bertrend_apps/exploration/geolocalization/spacy/dbpedia.py +206 -0
  154. bertrend_apps/exploration/geolocalization/spacy/nlp.config +37 -0
  155. bertrend_apps/exploration/geolocalization/spacy/utils.py +39 -0
  156. bertrend_apps/install_feeds_and_newsletters.py +29 -0
  157. bertrend_apps/newsletters/__init__.py +4 -0
  158. bertrend_apps/newsletters/__main__.py +277 -0
  159. bertrend_apps/prospective_demo/.streamlit/config.toml +3 -0
  160. bertrend_apps/prospective_demo/.streamlit/secrets.toml +9 -0
  161. bertrend_apps/prospective_demo/__init__.py +70 -0
  162. bertrend_apps/prospective_demo/app.py +111 -0
  163. bertrend_apps/prospective_demo/authentication.py +44 -0
  164. bertrend_apps/prospective_demo/dashboard_analysis.py +132 -0
  165. bertrend_apps/prospective_demo/dashboard_common.py +79 -0
  166. bertrend_apps/prospective_demo/dashboard_signals.py +226 -0
  167. bertrend_apps/prospective_demo/feeds_common.py +38 -0
  168. bertrend_apps/prospective_demo/feeds_config.py +261 -0
  169. bertrend_apps/prospective_demo/feeds_data.py +95 -0
  170. bertrend_apps/prospective_demo/i18n.py +136 -0
  171. bertrend_apps/prospective_demo/llm_utils.py +34 -0
  172. bertrend_apps/prospective_demo/models_info.py +361 -0
  173. bertrend_apps/prospective_demo/perf_utils.py +23 -0
  174. bertrend_apps/prospective_demo/process_new_data.py +348 -0
  175. bertrend_apps/prospective_demo/report_generation.py +214 -0
  176. bertrend_apps/prospective_demo/report_template.html +148 -0
  177. bertrend_apps/prospective_demo/report_utils.py +132 -0
  178. bertrend_apps/prospective_demo/start_demo.sh +9 -0
  179. bertrend_apps/prospective_demo/streamlit_utils.py +37 -0
  180. bertrend_apps/start_apps.sh +15 -0
  181. bertrend_apps/stop_apps.sh +11 -0
@@ -0,0 +1,280 @@
1
+ # Copyright (c) 2024, RTE (https://www.rte-france.com)
2
+ # See AUTHORS.txt
3
+ # SPDX-License-Identifier: MPL-2.0
4
+ # This file is part of BERTrend.
5
+
6
+ import datamapplot
7
+ import numpy as np
8
+ import pandas as pd
9
+ import plotly.graph_objects as go
10
+ import seaborn as sns
11
+ import streamlit as st
12
+ import streamlit.components.v1 as components
13
+ from loguru import logger
14
+ from umap import UMAP
15
+
16
+ from bertrend import OUTPUT_PATH
17
+ from bertrend.demos.demos_utils.icons import ERROR_ICON, WARNING_ICON
18
+ from bertrend.demos.demos_utils.state_utils import restore_widget_state
19
+ from bertrend.demos.topic_analysis.messages import TRAIN_MODEL_FIRST_ERROR
20
+ from bertrend.demos.weak_signals.visualizations_utils import PLOTLY_BUTTON_SAVE_CONFIG
21
+ from bertrend.utils.data_loading import TEXT_COLUMN
22
+
23
+
24
+ @st.cache_data
25
+ def plot_topics_hierarchy(_topic_model, width=700):
26
+ return _topic_model.visualize_hierarchy(width=width)
27
+
28
+
29
+ @st.cache_data
30
+ def plot_2d_topics(_topic_model, width=700):
31
+ return _topic_model.visualize_topics(width=width)
32
+
33
+
34
+ @st.cache_data
35
+ def overall_results():
36
+ """Display overall results visualization."""
37
+ try:
38
+ return plot_2d_topics(st.session_state["topic_model"])
39
+ except TypeError as te:
40
+ logger.error(f"Error occurred: {te}")
41
+ return None
42
+ except ValueError as ve:
43
+ logger.error(f"Error occurred: {ve}")
44
+ return None
45
+
46
+
47
+ @st.cache_data
48
+ def create_topic_info_dataframe():
49
+ """Create a DataFrame containing topics, number of documents per topic, and list of documents for each topic."""
50
+ docs = st.session_state["time_filtered_df"][TEXT_COLUMN].tolist()
51
+ topic_assignments = st.session_state["topics"]
52
+
53
+ topic_info = pd.DataFrame({"Document": docs, "Topic": topic_assignments})
54
+ topic_info_agg = (
55
+ topic_info.groupby("Topic").agg({"Document": ["count", list]}).reset_index()
56
+ )
57
+
58
+ topic_info_agg.columns = ["topic", "number_of_documents", "list_of_documents"]
59
+ topic_info_agg["topic"] = topic_info_agg["topic"].apply(
60
+ lambda x: (
61
+ ", ".join(
62
+ [word for word, _ in st.session_state["topic_model"].get_topic(x)]
63
+ )
64
+ if x != -1
65
+ else "Outlier"
66
+ )
67
+ )
68
+
69
+ return topic_info_agg[topic_info_agg["topic"] != "Outlier"]
70
+
71
+
72
+ @st.cache_data
73
+ def create_treemap():
74
+ """Create a treemap visualization of topics and their corresponding documents."""
75
+ topic_info_df = create_topic_info_dataframe()
76
+
77
+ labels, parents, values = [], [], []
78
+ for _, row in topic_info_df.iterrows():
79
+ topic_label = f"{row['topic']} ({row['number_of_documents']})"
80
+ labels.append(topic_label)
81
+ parents.append("")
82
+ values.append(row["number_of_documents"])
83
+
84
+ for doc in row["list_of_documents"]:
85
+ labels.append(doc[:50] + "...")
86
+ parents.append(topic_label)
87
+ values.append(1)
88
+
89
+ fig = go.Figure(
90
+ go.Treemap(
91
+ labels=labels,
92
+ parents=parents,
93
+ values=values,
94
+ textinfo="label+value",
95
+ marker=dict(colors=[], line=dict(width=0), pad=dict(t=0)),
96
+ textfont=dict(size=34),
97
+ )
98
+ )
99
+ fig.update_layout(margin=dict(t=50, l=25, r=25, b=25))
100
+ fig.update_traces(marker=dict(cornerradius=10))
101
+ return fig
102
+
103
+
104
+ def get_binary_file_downloader_html(bin_file, file_label="File"):
105
+ with open(bin_file, "rb") as f:
106
+ data = f.read()
107
+ return data
108
+
109
+
110
+ def calculate_document_lengths(documents):
111
+ """Calculate the length of each document in terms of word count."""
112
+ return np.array([len(doc.split()) for doc in documents])
113
+
114
+
115
+ @st.cache_data
116
+ def create_datamap(include_outliers):
117
+ """Create an interactive data map visualization."""
118
+ reduced_embeddings = UMAP(
119
+ n_neighbors=5, n_components=2, min_dist=0.2, metric="cosine"
120
+ ).fit_transform(st.session_state["embeddings"])
121
+
122
+ topic_nums = list(set(st.session_state["topics"]))
123
+ topic_info = st.session_state["topic_model"].get_topic_info()
124
+ topic_representations = {
125
+ row["Topic"]: row["Name"]
126
+ for _, row in topic_info.iterrows()
127
+ if row["Topic"] in topic_nums
128
+ }
129
+ docs = st.session_state["time_filtered_df"][TEXT_COLUMN].tolist()
130
+
131
+ df = pd.DataFrame(
132
+ {
133
+ "document": docs,
134
+ "embedding": list(reduced_embeddings),
135
+ "topic_num": st.session_state["topics"],
136
+ "topic_representation": [
137
+ topic_representations.get(topic, f"-1_{topic}")
138
+ for topic in st.session_state["topics"]
139
+ ],
140
+ }
141
+ )
142
+
143
+ df["is_noise"] = df["topic_representation"].str.contains("-1")
144
+ df["topic_representation"] = df.apply(
145
+ lambda row: (
146
+ "" if row["is_noise"] else topic_representations.get(row["topic_num"], "")
147
+ ),
148
+ axis=1,
149
+ )
150
+ df["topic_color"] = df.apply(
151
+ lambda row: "#999999" if row["is_noise"] else None, axis=1
152
+ )
153
+
154
+ if not include_outliers:
155
+ df = df[~df["is_noise"]]
156
+
157
+ if df.empty:
158
+ return None
159
+
160
+ embeddings_array = np.array(df["embedding"].tolist())
161
+ topic_representations_array = df["topic_representation"].values
162
+
163
+ unique_topics = df["topic_representation"].unique()
164
+ unique_topics = unique_topics[unique_topics != ""]
165
+ color_palette = sns.color_palette("tab20", len(unique_topics)).as_hex()
166
+ color_mapping = dict(zip(unique_topics, color_palette))
167
+ df["topic_color"] = df.apply(
168
+ lambda row: color_mapping.get(row["topic_representation"], row["topic_color"]),
169
+ axis=1,
170
+ )
171
+
172
+ try:
173
+ hover_data = df["document"].tolist()
174
+ document_lengths = calculate_document_lengths(hover_data)
175
+ normalized_sizes = 5 + 45 * (document_lengths - document_lengths.min()) / (
176
+ document_lengths.max() - document_lengths.min()
177
+ )
178
+
179
+ plot = datamapplot.create_interactive_plot(
180
+ embeddings_array,
181
+ topic_representations_array,
182
+ hover_text=hover_data,
183
+ marker_size_array=normalized_sizes,
184
+ inline_data=True,
185
+ noise_label="",
186
+ noise_color="#999999",
187
+ color_label_text=True,
188
+ label_wrap_width=16,
189
+ label_color_map=color_mapping,
190
+ width="100%",
191
+ height="100%",
192
+ darkmode=False,
193
+ marker_color_array=df["topic_color"].values,
194
+ use_medoids=True,
195
+ cluster_boundary_polygons=False,
196
+ enable_search=True,
197
+ search_field="hover_text",
198
+ point_line_width=0,
199
+ )
200
+
201
+ save_path = OUTPUT_PATH / "datamapplot.html"
202
+ with open(save_path, "wb") as f:
203
+ f.write(plot._html_str.encode(encoding="UTF-8", errors="replace"))
204
+
205
+ with open(save_path, "r", encoding="utf-8") as HtmlFile:
206
+ source_code = HtmlFile.read()
207
+ return source_code
208
+
209
+ except Exception as e:
210
+ logger.error(f"Error in creating datamap: {str(e)}")
211
+ return None
212
+
213
+
214
+ def main():
215
+ # Check if a model is trained
216
+ if "topic_model" not in st.session_state:
217
+ st.error(TRAIN_MODEL_FIRST_ERROR, icon=ERROR_ICON)
218
+ st.stop()
219
+
220
+ # Main execution
221
+ st.title("Visualizations")
222
+
223
+ # Sidebar
224
+ with st.sidebar:
225
+ include_outliers = st.checkbox("Include outliers (Topic = -1)", value=True)
226
+
227
+ # Overall Results
228
+ with st.expander("Overall Results", expanded=False):
229
+ overall_results_plot = overall_results()
230
+ if overall_results_plot is not None:
231
+ st.plotly_chart(
232
+ overall_results_plot,
233
+ config=PLOTLY_BUTTON_SAVE_CONFIG,
234
+ use_container_width=True,
235
+ )
236
+ else:
237
+ st.error("Cannot display overall results", icon=ERROR_ICON)
238
+ st.warning("Try to change the UMAP parameters", icon=WARNING_ICON)
239
+
240
+ # Topics Treemap
241
+ with st.expander("Topics Treemap", expanded=False):
242
+ with st.spinner("Computing topics treemap..."):
243
+ treemap_plot = create_treemap()
244
+ st.plotly_chart(
245
+ treemap_plot, config=PLOTLY_BUTTON_SAVE_CONFIG, use_container_width=True
246
+ )
247
+
248
+ # Data Map
249
+ with st.expander("Data Map", expanded=True):
250
+ with st.spinner("Loading Data-map plot..."):
251
+ datamap_html = create_datamap(include_outliers)
252
+ if datamap_html is not None:
253
+ # Using st.html does fix the width and height issue by making it adaptive, but the html never loads (it loops indefinitely)
254
+ # This is the best solution so far, with a button to save the html and view it in fullscreen later.
255
+ components.html(datamap_html, width=1200, height=1000, scrolling=True)
256
+
257
+ # Add the fullscreen button
258
+ save_path = OUTPUT_PATH / "datamapplot.html"
259
+
260
+ # Create a download button
261
+ st.download_button(
262
+ label="View in fullscreen",
263
+ data=get_binary_file_downloader_html(save_path),
264
+ file_name="datamapplot.html",
265
+ mime="text/html",
266
+ use_container_width=True,
267
+ type="secondary",
268
+ )
269
+ else:
270
+ st.warning(
271
+ "No valid topics to visualize. All documents might be classified as outliers.",
272
+ icon=WARNING_ICON,
273
+ )
274
+
275
+
276
+ # Restore widget state
277
+ restore_widget_state()
278
+ main()
279
+ # FIXME: cluster_boundary_polygons=True causes a "pop from an empty set" error in the data map plot's generation process.
280
+ # It's not urgent, but should be looked into to see what's causing the problem and potentially get a better visualization where clusters are delimitted with contours.
@@ -0,0 +1,219 @@
1
+ # Copyright (c) 2024, RTE (https://www.rte-france.com)
2
+ # See AUTHORS.txt
3
+ # SPDX-License-Identifier: MPL-2.0
4
+ # This file is part of BERTrend.
5
+
6
+ import datetime
7
+
8
+ import pandas as pd
9
+ import streamlit as st
10
+ from loguru import logger
11
+
12
+ from bertrend import OUTPUT_PATH
13
+ from bertrend.demos.demos_utils.data_loading_component import (
14
+ display_data_loading_component,
15
+ )
16
+ from bertrend.demos.demos_utils.embed_documents_component import (
17
+ display_embed_documents_component,
18
+ )
19
+ from bertrend.demos.demos_utils.icons import (
20
+ WARNING_ICON,
21
+ SUCCESS_ICON,
22
+ ERROR_ICON,
23
+ SETTINGS_ICON,
24
+ INFO_ICON,
25
+ EMBEDDING_ICON,
26
+ )
27
+ from bertrend.demos.demos_utils.messages import (
28
+ NO_EMBEDDINGS_WARNING_MESSAGE,
29
+ MODEL_TRAINING_COMPLETE_MESSAGE,
30
+ )
31
+ from bertrend.demos.demos_utils.state_utils import (
32
+ restore_widget_state,
33
+ SessionStateManager,
34
+ )
35
+ from bertrend.demos.topic_analysis.messages import (
36
+ EMBEDDINGS_CACHE_INFO,
37
+ SAVE_MODEL_REMINDER,
38
+ NO_MODEL_AVAILABLE_ERROR,
39
+ )
40
+ from bertrend.demos.demos_utils.parameters_component import (
41
+ display_bertopic_hyperparameters,
42
+ display_embedding_hyperparameters,
43
+ )
44
+ from bertrend.demos.weak_signals.visualizations_utils import PLOTLY_BUTTON_SAVE_CONFIG
45
+ from bertrend.metrics.topic_metrics import compute_cluster_metrics
46
+ from bertrend.config.parameters import BERTOPIC_SERIALIZATION
47
+ from bertrend.topic_analysis.visualizations import plot_docs_repartition_over_time
48
+ from bertrend.BERTopicModel import BERTopicModel
49
+ from bertrend.utils.data_loading import (
50
+ TEXT_COLUMN,
51
+ )
52
+
53
+
54
+ def generate_model_name(base_name="topic_model"):
55
+ """
56
+ Generates a dynamic model name with the current date and time.
57
+ If a base name is provided, it uses that instead of the default.
58
+ """
59
+ current_datetime = datetime.datetime.now().strftime("%Y%m%d_%H%M%S")
60
+ model_name = f"{base_name}_{current_datetime}"
61
+ return model_name
62
+
63
+
64
+ def data_distribution(df: pd.DataFrame):
65
+ """Display the distribution of data over time."""
66
+ with st.expander(
67
+ label="Data distribution",
68
+ expanded=False,
69
+ ):
70
+ freq = st.select_slider(
71
+ "Time aggregation",
72
+ options=(
73
+ "1D",
74
+ "2D",
75
+ "1W",
76
+ "2W",
77
+ "1M",
78
+ "2M",
79
+ "1Y",
80
+ "2Y",
81
+ ),
82
+ value="1M",
83
+ )
84
+ fig = plot_docs_repartition_over_time(df, freq)
85
+ st.plotly_chart(fig, config=PLOTLY_BUTTON_SAVE_CONFIG, use_container_width=True)
86
+
87
+
88
+ def save_model_interface():
89
+ """Save the generated topic model to disk."""
90
+ st.write("## Save Model")
91
+
92
+ # Optional text box for custom model name
93
+ base_model_name = st.text_input(
94
+ "Enter a name for the model (optional):", key="base_model_name_input"
95
+ )
96
+
97
+ # Button to save the model
98
+ if st.button("Save Model", key="save_model_button"):
99
+ if "topic_model" in st.session_state:
100
+ dynamic_model_name = generate_model_name(base_model_name or "topic_model")
101
+ model_save_path = OUTPUT_PATH / "saved_models" / dynamic_model_name
102
+ logger.debug(
103
+ f"Saving the model in the following directory: {model_save_path}"
104
+ )
105
+ try:
106
+ st.session_state["topic_model"].save(
107
+ model_save_path,
108
+ serialization=BERTOPIC_SERIALIZATION,
109
+ save_ctfidf=True,
110
+ save_embedding_model=True,
111
+ )
112
+ st.success(
113
+ f"Model saved successfully as {model_save_path}", icon=SUCCESS_ICON
114
+ )
115
+ st.session_state["model_saved"] = True
116
+ logger.success(f"Model saved successfully!", icon=SUCCESS_ICON)
117
+ except Exception as e:
118
+ st.error(f"Failed to save the model: {e}", icon=ERROR_ICON)
119
+ logger.error(f"Failed to save the model: {e}")
120
+ else:
121
+ st.error(
122
+ NO_MODEL_AVAILABLE_ERROR,
123
+ icon=ERROR_ICON,
124
+ )
125
+
126
+
127
+ def train_model():
128
+ """Train a BERTopic model based on provided data."""
129
+ with st.spinner("Training model..."):
130
+ dataset = st.session_state["time_filtered_df"][TEXT_COLUMN]
131
+ # indices = full_dataset.index.tolist()
132
+
133
+ # Initialize topic model
134
+ topic_model = BERTopicModel(st.session_state["bertopic_config"])
135
+ embeddings = st.session_state["embeddings"]
136
+ topic_model_output = topic_model.fit(
137
+ docs=dataset,
138
+ embeddings=embeddings,
139
+ )
140
+ bertopic = topic_model_output.topic_model
141
+
142
+ # Set session_state
143
+ st.session_state["topic_model"] = bertopic
144
+ st.session_state["topics"] = topic_model_output.topics
145
+
146
+ st.success(MODEL_TRAINING_COMPLETE_MESSAGE, icon=SUCCESS_ICON)
147
+ st.info(
148
+ EMBEDDINGS_CACHE_INFO,
149
+ icon=INFO_ICON,
150
+ )
151
+
152
+ topic_info = bertopic.get_topic_info()
153
+ st.session_state["topics_info"] = topic_info[
154
+ topic_info["Topic"] != -1
155
+ ] # exclude -1 topic from topic list
156
+
157
+ # compute cluster metrics (optional)
158
+ compute_cluster_metrics(bertopic, st.session_state["topics"], dataset)
159
+
160
+ # update state
161
+ st.session_state["model_trained"] = True
162
+ if not st.session_state["model_saved"]:
163
+ st.warning(SAVE_MODEL_REMINDER, icon=WARNING_ICON)
164
+
165
+
166
+ def main():
167
+ st.title(":part_alternation_mark: Topic analysis demo")
168
+
169
+ if "model_trained" not in st.session_state:
170
+ st.session_state["model_trained"] = False
171
+ if "model_saved" not in st.session_state:
172
+ st.session_state["model_saved"] = False
173
+
174
+ # In the sidebar form
175
+ with st.sidebar:
176
+ st.header(SETTINGS_ICON + " Settings")
177
+ st.subheader(EMBEDDING_ICON + " Embedding Hyperparameters")
178
+ display_embedding_hyperparameters()
179
+ display_bertopic_hyperparameters()
180
+
181
+ # Load data
182
+ display_data_loading_component()
183
+
184
+ # Data overview
185
+ if "time_filtered_df" not in st.session_state:
186
+ st.stop()
187
+ data_distribution(st.session_state["time_filtered_df"])
188
+ SessionStateManager.set("split_type", st.session_state["split_by_paragraph"])
189
+
190
+ # Embed documents
191
+ try:
192
+ display_embed_documents_component()
193
+ except Exception as e:
194
+ logger.error(f"An error occurred while embedding documents: {e}")
195
+ st.error(f"An error occurred while embedding documents: {e}", icon=ERROR_ICON)
196
+
197
+ if not SessionStateManager.get("data_embedded", False):
198
+ st.warning(NO_EMBEDDINGS_WARNING_MESSAGE, icon=WARNING_ICON)
199
+ st.stop()
200
+
201
+ # Train the model
202
+ if st.button(
203
+ "Train Model",
204
+ type="primary",
205
+ key="train_model_button",
206
+ help="Make sure to review the settings before clicking on this button.",
207
+ ):
208
+ train_model()
209
+
210
+ # Save the model
211
+ save_model_interface()
212
+
213
+ # TODO: Investigate the potentially deprecated save_model_interface() I implemented a while ago
214
+ # to save a BERTopic model to either load it up later or load it up somewhere else
215
+
216
+
217
+ # Restore widget state
218
+ restore_widget_state()
219
+ main()
@@ -0,0 +1,13 @@
1
+ # Copyright (c) 2024, RTE (https://www.rte-france.com)
2
+ # See AUTHORS.txt
3
+ # SPDX-License-Identifier: MPL-2.0
4
+ # This file is part of BERTrend.
5
+
6
+ EMBEDDINGS_CACHE_INFO = "Embeddings aren't saved in cache and thus aren't loaded. Please make sure to train the model without using cached embeddings if you want correct and functional temporal visualizations."
7
+ SAVE_MODEL_REMINDER = "Don't forget to save your model!"
8
+ NO_MODEL_AVAILABLE_ERROR = "No model available to save. Please train a model first."
9
+ NO_DOCUMENT_FOR_TOPIC = "No documents found for the selected topic."
10
+ TRAIN_MODEL_FIRST_ERROR = "Please train a model first."
11
+ REMOTE_EMBEDDING_SEVICE_TYPE_NOT_SUPPORTED_ERROR = (
12
+ "These visualizations are only available if a local embedding service is used."
13
+ )
@@ -0,0 +1,4 @@
1
+ # Copyright (c) 2024, RTE (https://www.rte-france.com)
2
+ # See AUTHORS.txt
3
+ # SPDX-License-Identifier: MPL-2.0
4
+ # This file is part of BERTrend.