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.
- bertrend/BERTopicModel.py +320 -0
- bertrend/BERTrend.py +1211 -0
- bertrend/__init__.py +59 -0
- bertrend/config/bertopic_default_config.toml +48 -0
- bertrend/config/bertrend_default_config.toml +16 -0
- bertrend/config/parameters.py +92 -0
- bertrend/config/services_default_config.toml +32 -0
- bertrend/demos/__init__.py +4 -0
- bertrend/demos/demos_utils/__init__.py +13 -0
- bertrend/demos/demos_utils/data_loading_component.py +267 -0
- bertrend/demos/demos_utils/embed_documents_component.py +52 -0
- bertrend/demos/demos_utils/icons.py +41 -0
- bertrend/demos/demos_utils/messages.py +11 -0
- bertrend/demos/demos_utils/parameters_component.py +162 -0
- bertrend/demos/demos_utils/state_utils.py +99 -0
- bertrend/demos/start_summarizer_app.sh +9 -0
- bertrend/demos/start_topic_analysis_app.sh +9 -0
- bertrend/demos/start_weak_signals_app.sh +9 -0
- bertrend/demos/summarization/__init__.py +4 -0
- bertrend/demos/summarization/summarizer_app.py +98 -0
- bertrend/demos/topic_analysis/__init__.py +4 -0
- bertrend/demos/topic_analysis/app.py +87 -0
- bertrend/demos/topic_analysis/app_utils.py +69 -0
- bertrend/demos/topic_analysis/demo_pages/__init__.py +4 -0
- bertrend/demos/topic_analysis/demo_pages/explore_topics.py +455 -0
- bertrend/demos/topic_analysis/demo_pages/newsletters_generation.py +178 -0
- bertrend/demos/topic_analysis/demo_pages/temporal_visualizations.py +573 -0
- bertrend/demos/topic_analysis/demo_pages/topic_visualizations.py +280 -0
- bertrend/demos/topic_analysis/demo_pages/training_page.py +219 -0
- bertrend/demos/topic_analysis/messages.py +13 -0
- bertrend/demos/weak_signals/__init__.py +4 -0
- bertrend/demos/weak_signals/app.py +510 -0
- bertrend/demos/weak_signals/messages.py +32 -0
- bertrend/demos/weak_signals/visualizations_utils.py +403 -0
- bertrend/llm_utils/__init__.py +4 -0
- bertrend/llm_utils/newsletter.css +131 -0
- bertrend/llm_utils/newsletter_features.py +272 -0
- bertrend/llm_utils/openai_client.py +235 -0
- bertrend/llm_utils/prompts.py +94 -0
- bertrend/metrics/__init__.py +4 -0
- bertrend/metrics/temporal_metrics_embedding.py +861 -0
- bertrend/metrics/topic_metrics.py +199 -0
- bertrend/resources/common_ngrams.json +23 -0
- bertrend/resources/stopwords-en.json +3 -0
- bertrend/resources/stopwords-fr.json +1 -0
- bertrend/resources/stopwords-rte.json +15 -0
- bertrend/services/__init__.py +4 -0
- bertrend/services/authentication.py +66 -0
- bertrend/services/embedding_client.py +154 -0
- bertrend/services/embedding_service.py +380 -0
- bertrend/services/summarizer.py +45 -0
- bertrend/services/summary/__init__.py +4 -0
- bertrend/services/summary/abstractive_summarizer.py +74 -0
- bertrend/services/summary/chatgpt_summarizer.py +67 -0
- bertrend/services/summary/extractive_summarizer.py +368 -0
- bertrend/services/summary/lexrank.py +126 -0
- bertrend/services/summary/prompts.py +43 -0
- bertrend/tests/__init__.py +4 -0
- bertrend/tests/test_abstractive_summarizer.py +140 -0
- bertrend/tests/test_bertrend.py +1091 -0
- bertrend/tests/test_cache_utils.py +111 -0
- bertrend/tests/test_chatgpt_summarizer.py +150 -0
- bertrend/tests/test_config_utils.py +54 -0
- bertrend/tests/test_data_loading.py +326 -0
- bertrend/tests/test_data_structure.py +53 -0
- bertrend/tests/test_embedding_client.py +546 -0
- bertrend/tests/test_extractive_summarizer.py +250 -0
- bertrend/tests/test_installation.py +183 -0
- bertrend/tests/test_newsletter_features.py +354 -0
- bertrend/tests/test_openai_client.py +210 -0
- bertrend/tests/test_representative_docs.py +125 -0
- bertrend/tests/test_summarizer.py +115 -0
- bertrend/tests/test_temporal_metrics_embedding.py +349 -0
- bertrend/tests/test_topic_description.py +159 -0
- bertrend/tests/test_topic_metrics.py +233 -0
- bertrend/tests/test_topic_model.py +285 -0
- bertrend/tests/test_trend_analysis_data_structure.py +174 -0
- bertrend/tests/test_trend_analysis_prompts.py +225 -0
- bertrend/tests/test_trend_analysis_visualizations.py +307 -0
- bertrend/tests/test_trend_analysis_weak_signals.py +598 -0
- bertrend/tests/test_utils_init.py +70 -0
- bertrend/tests/test_visualizations.py +306 -0
- bertrend/topic_analysis/__init__.py +4 -0
- bertrend/topic_analysis/data_structure.py +13 -0
- bertrend/topic_analysis/prompts.py +42 -0
- bertrend/topic_analysis/representative_docs.py +57 -0
- bertrend/topic_analysis/topic_description.py +66 -0
- bertrend/topic_analysis/visualizations.py +315 -0
- bertrend/trend_analysis/__init__.py +4 -0
- bertrend/trend_analysis/data_structure.py +78 -0
- bertrend/trend_analysis/prompts.py +229 -0
- bertrend/trend_analysis/signal_llm_template_en.html +181 -0
- bertrend/trend_analysis/signal_llm_template_fr.html +181 -0
- bertrend/trend_analysis/visualizations.py +605 -0
- bertrend/trend_analysis/weak_signals.py +425 -0
- bertrend/utils/__init__.py +31 -0
- bertrend/utils/cache_utils.py +31 -0
- bertrend/utils/config_utils.py +41 -0
- bertrend/utils/data_loading.py +329 -0
- bertrend-0.3.1.dist-info/AUTHORS.txt +15 -0
- bertrend-0.3.1.dist-info/LICENSE.md +373 -0
- bertrend-0.3.1.dist-info/METADATA +400 -0
- bertrend-0.3.1.dist-info/RECORD +181 -0
- bertrend-0.3.1.dist-info/WHEEL +4 -0
- bertrend_apps/__init__.py +4 -0
- bertrend_apps/common/__init__.py +4 -0
- bertrend_apps/common/crontab_utils.py +113 -0
- bertrend_apps/common/mail_utils.py +91 -0
- bertrend_apps/config/feeds/arxiv_feed.toml +15 -0
- bertrend_apps/config/feeds/decarbonation_feed.toml +15 -0
- bertrend_apps/config/feeds/llm_en_feed.toml +15 -0
- bertrend_apps/config/feeds/llm_feed.toml +16 -0
- bertrend_apps/config/feeds/nlp_feed.toml +15 -0
- bertrend_apps/config/feeds/rte_feed.toml +15 -0
- bertrend_apps/config/gmail_credentials.json +1 -0
- bertrend_apps/config/newsletters/arxiv_newsletter_from_last.toml +39 -0
- bertrend_apps/config/newsletters/arxiv_newsletter_from_scratch.toml +39 -0
- bertrend_apps/config/newsletters/decarbonation_newsletter_from_last.toml +38 -0
- bertrend_apps/config/newsletters/decarbonation_newsletter_from_scratch.toml +38 -0
- bertrend_apps/config/newsletters/llm_en_newsletter_from_last.toml +38 -0
- bertrend_apps/config/newsletters/llm_newsletter_from_last.toml +39 -0
- bertrend_apps/config/newsletters/nlp_newsletter_from_last.toml +38 -0
- bertrend_apps/config/newsletters/rte_newsletter_from_last.toml +38 -0
- bertrend_apps/config/newsletters/rte_newsletter_from_scratch.toml +38 -0
- bertrend_apps/data_provider/__init__.py +9 -0
- bertrend_apps/data_provider/__main__.py +237 -0
- bertrend_apps/data_provider/arxiv_provider.py +161 -0
- bertrend_apps/data_provider/bing_news_provider.py +82 -0
- bertrend_apps/data_provider/curebot_provider.py +105 -0
- bertrend_apps/data_provider/data_provider.py +194 -0
- bertrend_apps/data_provider/google_news_provider.py +94 -0
- bertrend_apps/data_provider/newscatcher_provider.py +74 -0
- bertrend_apps/data_provider/utils.py +165 -0
- bertrend_apps/exploration/__init__.py +4 -0
- bertrend_apps/exploration/curebot/__init__.py +4 -0
- bertrend_apps/exploration/curebot/app.py +45 -0
- bertrend_apps/exploration/curebot/app_utils.py +317 -0
- bertrend_apps/exploration/curebot/config.toml +33 -0
- bertrend_apps/exploration/curebot/import_extraction_article.ipynb +108 -0
- bertrend_apps/exploration/curebot/old_veille_analyse.py +298 -0
- bertrend_apps/exploration/curebot/prompts.py +24 -0
- bertrend_apps/exploration/curebot/start_newsletter_generator.sh +8 -0
- bertrend_apps/exploration/curebot/tabs/tab1.py +150 -0
- bertrend_apps/exploration/curebot/tabs/tab2.py +95 -0
- bertrend_apps/exploration/curebot/tabs/tab3.py +125 -0
- bertrend_apps/exploration/dataviz/Main_page.py +10 -0
- bertrend_apps/exploration/dataviz/__init__.py +4 -0
- bertrend_apps/exploration/dataviz/pages/1_Data_feeds.py +105 -0
- bertrend_apps/exploration/geolocalization/__init__.py +4 -0
- bertrend_apps/exploration/geolocalization/entity_utils.py +118 -0
- bertrend_apps/exploration/geolocalization/spacy/__init__.py +4 -0
- bertrend_apps/exploration/geolocalization/spacy/common_factory.py +142 -0
- bertrend_apps/exploration/geolocalization/spacy/dbpedia.py +206 -0
- bertrend_apps/exploration/geolocalization/spacy/nlp.config +37 -0
- bertrend_apps/exploration/geolocalization/spacy/utils.py +39 -0
- bertrend_apps/install_feeds_and_newsletters.py +29 -0
- bertrend_apps/newsletters/__init__.py +4 -0
- bertrend_apps/newsletters/__main__.py +277 -0
- bertrend_apps/prospective_demo/.streamlit/config.toml +3 -0
- bertrend_apps/prospective_demo/.streamlit/secrets.toml +9 -0
- bertrend_apps/prospective_demo/__init__.py +70 -0
- bertrend_apps/prospective_demo/app.py +111 -0
- bertrend_apps/prospective_demo/authentication.py +44 -0
- bertrend_apps/prospective_demo/dashboard_analysis.py +132 -0
- bertrend_apps/prospective_demo/dashboard_common.py +79 -0
- bertrend_apps/prospective_demo/dashboard_signals.py +226 -0
- bertrend_apps/prospective_demo/feeds_common.py +38 -0
- bertrend_apps/prospective_demo/feeds_config.py +261 -0
- bertrend_apps/prospective_demo/feeds_data.py +95 -0
- bertrend_apps/prospective_demo/i18n.py +136 -0
- bertrend_apps/prospective_demo/llm_utils.py +34 -0
- bertrend_apps/prospective_demo/models_info.py +361 -0
- bertrend_apps/prospective_demo/perf_utils.py +23 -0
- bertrend_apps/prospective_demo/process_new_data.py +348 -0
- bertrend_apps/prospective_demo/report_generation.py +214 -0
- bertrend_apps/prospective_demo/report_template.html +148 -0
- bertrend_apps/prospective_demo/report_utils.py +132 -0
- bertrend_apps/prospective_demo/start_demo.sh +9 -0
- bertrend_apps/prospective_demo/streamlit_utils.py +37 -0
- bertrend_apps/start_apps.sh +15 -0
- bertrend_apps/stop_apps.sh +11 -0
|
@@ -0,0 +1,329 @@
|
|
|
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
|
+
import gzip
|
|
6
|
+
import re
|
|
7
|
+
from pathlib import Path
|
|
8
|
+
from typing import Literal
|
|
9
|
+
|
|
10
|
+
import pandas as pd
|
|
11
|
+
from loguru import logger
|
|
12
|
+
from sentence_transformers import SentenceTransformer
|
|
13
|
+
from transformers import AutoTokenizer
|
|
14
|
+
|
|
15
|
+
from bertrend import DATA_PATH
|
|
16
|
+
from bertrend.config.parameters import MIN_CHARS_DEFAULT
|
|
17
|
+
|
|
18
|
+
# Define column names
|
|
19
|
+
TEXT_COLUMN = "text"
|
|
20
|
+
TIMESTAMP_COLUMN = "timestamp"
|
|
21
|
+
GROUPED_TIMESTAMP_COLUMN = "grouped_timestamp"
|
|
22
|
+
URL_COLUMN = "url"
|
|
23
|
+
TITLE_COLUMN = "title"
|
|
24
|
+
DOCUMENT_ID_COLUMN = "document_id"
|
|
25
|
+
SOURCE_COLUMN = "source"
|
|
26
|
+
CITATION_COUNT_COL = "citation_count"
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
def find_compatible_files(path: Path, extensions: list[str]) -> list[Path]:
|
|
30
|
+
return [f.relative_to(path) for f in path.rglob("*") if f.suffix[1:] in extensions]
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
def load_data(
|
|
34
|
+
selected_file: Path,
|
|
35
|
+
language: str = "French",
|
|
36
|
+
) -> pd.DataFrame | None:
|
|
37
|
+
"""
|
|
38
|
+
Load and preprocess data (adds additional columns if needed) from a selected file.
|
|
39
|
+
|
|
40
|
+
Args:
|
|
41
|
+
selected_file (Path): A path to the selected file
|
|
42
|
+
language (str): The language of the text data ('French' or 'English').
|
|
43
|
+
|
|
44
|
+
Returns:
|
|
45
|
+
pd.DataFrame: The loaded and preprocessed DataFrame.
|
|
46
|
+
"""
|
|
47
|
+
df = _file_to_pd(selected_file)
|
|
48
|
+
if df is None:
|
|
49
|
+
return None
|
|
50
|
+
|
|
51
|
+
# Convert timestamp column to datetime
|
|
52
|
+
df[TIMESTAMP_COLUMN] = pd.to_datetime(df[TIMESTAMP_COLUMN], errors="coerce")
|
|
53
|
+
|
|
54
|
+
# Drop rows with invalid timestamps
|
|
55
|
+
df = df.dropna(subset=[TIMESTAMP_COLUMN])
|
|
56
|
+
|
|
57
|
+
df = df.sort_values(by=TIMESTAMP_COLUMN, ascending=True).reset_index(drop=True)
|
|
58
|
+
df[DOCUMENT_ID_COLUMN] = df.index
|
|
59
|
+
|
|
60
|
+
if URL_COLUMN in df.columns:
|
|
61
|
+
df[SOURCE_COLUMN] = df[URL_COLUMN].apply(
|
|
62
|
+
lambda x: x.split("/")[2] if pd.notna(x) else None
|
|
63
|
+
)
|
|
64
|
+
else:
|
|
65
|
+
df[SOURCE_COLUMN] = None
|
|
66
|
+
df[URL_COLUMN] = None
|
|
67
|
+
|
|
68
|
+
if language == "French":
|
|
69
|
+
df[TEXT_COLUMN] = df[TEXT_COLUMN].apply(preprocess_french_text)
|
|
70
|
+
|
|
71
|
+
if TITLE_COLUMN in df.columns:
|
|
72
|
+
df.drop_duplicates(subset=[TITLE_COLUMN], keep="first", inplace=True)
|
|
73
|
+
else:
|
|
74
|
+
# Title colupmn used in topic explorations
|
|
75
|
+
df[TITLE_COLUMN] = ""
|
|
76
|
+
|
|
77
|
+
return df
|
|
78
|
+
|
|
79
|
+
|
|
80
|
+
def split_data(
|
|
81
|
+
df: pd.DataFrame,
|
|
82
|
+
min_chars: int = MIN_CHARS_DEFAULT,
|
|
83
|
+
split_by_paragraph: Literal["no", "yes", "enhanced"] = "yes",
|
|
84
|
+
embedding_model_name: str = None,
|
|
85
|
+
) -> pd.DataFrame | None:
|
|
86
|
+
"""
|
|
87
|
+
Load and preprocess data from a selected file.
|
|
88
|
+
|
|
89
|
+
Args:
|
|
90
|
+
df (pd.DataFrame): A dataframe containing loaded documents
|
|
91
|
+
min_chars (int): The minimum number of characters required for a text to be included.
|
|
92
|
+
split_by_paragraph (Literal): Way to split the text data by paragraphs (no, yes, enhanced)
|
|
93
|
+
embedding_model_name (str): Name of the embedding model (if done locally), only useful if split_by_paragraph is 'enhanced'.
|
|
94
|
+
|
|
95
|
+
Returns:
|
|
96
|
+
pd.DataFrame: The split DataFrame.
|
|
97
|
+
"""
|
|
98
|
+
if split_by_paragraph == "yes":
|
|
99
|
+
new_rows = []
|
|
100
|
+
for _, row in df.iterrows():
|
|
101
|
+
# Attempt splitting by \n\n first
|
|
102
|
+
paragraphs = re.split(r"\n\n", row[TEXT_COLUMN])
|
|
103
|
+
if len(paragraphs) == 1: # If no split occurred, attempt splitting by \n
|
|
104
|
+
paragraphs = re.split(r"\n", row[TEXT_COLUMN])
|
|
105
|
+
for paragraph in paragraphs:
|
|
106
|
+
new_row = row.copy()
|
|
107
|
+
new_row[TEXT_COLUMN] = paragraph
|
|
108
|
+
new_row["source"] = row["source"]
|
|
109
|
+
new_rows.append(new_row)
|
|
110
|
+
df = pd.DataFrame(new_rows)
|
|
111
|
+
elif split_by_paragraph == "enhanced":
|
|
112
|
+
# FIXME: requires access to the tokenizer
|
|
113
|
+
df = enhanced_split_df_by_paragraphs(
|
|
114
|
+
dataset=df, embedding_model_name=embedding_model_name
|
|
115
|
+
)
|
|
116
|
+
|
|
117
|
+
if min_chars > 0:
|
|
118
|
+
df = df[df[TEXT_COLUMN].str.len() >= min_chars]
|
|
119
|
+
|
|
120
|
+
df = df[df[TEXT_COLUMN].str.strip() != ""].reset_index(drop=True)
|
|
121
|
+
|
|
122
|
+
return df
|
|
123
|
+
|
|
124
|
+
|
|
125
|
+
def group_by_days(
|
|
126
|
+
df: pd.DataFrame, day_granularity: int = 1
|
|
127
|
+
) -> dict[pd.Timestamp, pd.DataFrame]:
|
|
128
|
+
"""
|
|
129
|
+
Group a DataFrame by a specified number of days.
|
|
130
|
+
|
|
131
|
+
Args:
|
|
132
|
+
df (pd.DataFrame): The input DataFrame containing a TIMESTAMP_COLUMN column.
|
|
133
|
+
day_granularity (int): The number of days to group by (default is 1).
|
|
134
|
+
|
|
135
|
+
Returns:
|
|
136
|
+
Dict[pd.Timestamp, pd.DataFrame]: A dictionary where each key is the timestamp group and the value is the corresponding DataFrame.
|
|
137
|
+
"""
|
|
138
|
+
df[TIMESTAMP_COLUMN] = pd.to_datetime(df[TIMESTAMP_COLUMN])
|
|
139
|
+
grouped = df.groupby(pd.Grouper(key=TIMESTAMP_COLUMN, freq=f"{day_granularity}D"))
|
|
140
|
+
dict_of_dfs = {name: group for name, group in grouped}
|
|
141
|
+
return dict_of_dfs
|
|
142
|
+
|
|
143
|
+
|
|
144
|
+
def _file_to_pd(file_name: Path, base_dir: Path = None) -> pd.DataFrame | None:
|
|
145
|
+
"""
|
|
146
|
+
Read data in various formats and convert it to a DataFrame.
|
|
147
|
+
|
|
148
|
+
Args:
|
|
149
|
+
file_name (str): The name of the file to read.
|
|
150
|
+
base_dir (Path, optional): The base directory of the file.
|
|
151
|
+
|
|
152
|
+
Returns:
|
|
153
|
+
pd.DataFrame: The loaded data.
|
|
154
|
+
"""
|
|
155
|
+
file_ext = file_name.suffix.lower()
|
|
156
|
+
if file_ext == ".csv":
|
|
157
|
+
return pd.read_csv(DATA_PATH / file_name)
|
|
158
|
+
elif file_ext == ".parquet":
|
|
159
|
+
return pd.read_parquet(DATA_PATH / file_name)
|
|
160
|
+
elif file_ext == ".json":
|
|
161
|
+
return pd.read_json(DATA_PATH / file_name)
|
|
162
|
+
elif file_ext == ".jsonl" or file_ext == ".jsonlines":
|
|
163
|
+
return pd.read_json(DATA_PATH / file_name, lines=True)
|
|
164
|
+
elif file_ext == ".jsonl.gz" or file_ext == ".jsonlines.gz":
|
|
165
|
+
with gzip.open(file_name, "rt") as f_in:
|
|
166
|
+
return pd.read_json(f_in, lines=True)
|
|
167
|
+
elif file_ext == ".xslsx":
|
|
168
|
+
return pd.read_excel(DATA_PATH / file_name)
|
|
169
|
+
else:
|
|
170
|
+
return None
|
|
171
|
+
|
|
172
|
+
|
|
173
|
+
def clean_dataset(dataset: pd.DataFrame, length_criteria: int) -> pd.DataFrame:
|
|
174
|
+
"""
|
|
175
|
+
Clean the dataset by removing short texts.
|
|
176
|
+
|
|
177
|
+
Args:
|
|
178
|
+
dataset (pd.DataFrame): The dataset to clean.
|
|
179
|
+
length_criteria (int): The minimum length of text to keep.
|
|
180
|
+
|
|
181
|
+
Returns:
|
|
182
|
+
pd.DataFrame: The cleaned dataset.
|
|
183
|
+
"""
|
|
184
|
+
cleaned_dataset = dataset.loc[dataset[TEXT_COLUMN].str.len() >= length_criteria]
|
|
185
|
+
return cleaned_dataset
|
|
186
|
+
|
|
187
|
+
|
|
188
|
+
def enhanced_split_df_by_paragraphs(
|
|
189
|
+
dataset: pd.DataFrame, embedding_model_name: str = None
|
|
190
|
+
) -> pd.DataFrame:
|
|
191
|
+
"""
|
|
192
|
+
Split texts into multiple paragraphs and return a concatenation of all extracts as a new DataFrame.
|
|
193
|
+
If enhanced is True, it also considers the embedding model's max sequence length.
|
|
194
|
+
|
|
195
|
+
Args:
|
|
196
|
+
dataset (pd.DataFrame): The dataset to split.
|
|
197
|
+
embedding_model_name (str): The name of the embedding model to use (only required if enhanced is True)
|
|
198
|
+
|
|
199
|
+
Returns:
|
|
200
|
+
pd.DataFrame: The dataset with texts split into paragraphs.
|
|
201
|
+
"""
|
|
202
|
+
if embedding_model_name is None:
|
|
203
|
+
raise ValueError("Tokenizer is required for enhanced splitting.")
|
|
204
|
+
|
|
205
|
+
tokenizer = AutoTokenizer.from_pretrained(embedding_model_name)
|
|
206
|
+
max_length = SentenceTransformer(embedding_model_name).get_max_seq_length()
|
|
207
|
+
# Correcting the max seq length anomaly in certain embedding models description
|
|
208
|
+
if max_length == 514:
|
|
209
|
+
max_length = 512
|
|
210
|
+
|
|
211
|
+
df = dataset.copy()
|
|
212
|
+
new_rows = []
|
|
213
|
+
|
|
214
|
+
for _, row in df.iterrows():
|
|
215
|
+
doc = row[TEXT_COLUMN]
|
|
216
|
+
timestamp = row[TIMESTAMP_COLUMN]
|
|
217
|
+
|
|
218
|
+
paragraphs = re.split(r"\n+", doc)
|
|
219
|
+
|
|
220
|
+
for paragraph in paragraphs:
|
|
221
|
+
sentences = re.split(r"(?<=[.!?])\s+", paragraph)
|
|
222
|
+
|
|
223
|
+
current_doc = ""
|
|
224
|
+
for sentence in sentences:
|
|
225
|
+
sentence_ids = tokenizer.encode(
|
|
226
|
+
sentence, padding=False, truncation=False, add_special_tokens=False
|
|
227
|
+
)
|
|
228
|
+
|
|
229
|
+
if len(sentence_ids) > max_length:
|
|
230
|
+
sentence_chunks = [
|
|
231
|
+
sentence[i : i + max_length]
|
|
232
|
+
for i in range(0, len(sentence), max_length)
|
|
233
|
+
]
|
|
234
|
+
for chunk in sentence_chunks:
|
|
235
|
+
new_row = row.copy()
|
|
236
|
+
new_row[TEXT_COLUMN] = chunk
|
|
237
|
+
new_row["is_split"] = True
|
|
238
|
+
new_row["num_tokens"] = len(
|
|
239
|
+
tokenizer.encode(
|
|
240
|
+
chunk,
|
|
241
|
+
padding=False,
|
|
242
|
+
truncation=False,
|
|
243
|
+
add_special_tokens=False,
|
|
244
|
+
)
|
|
245
|
+
)
|
|
246
|
+
new_rows.append(new_row)
|
|
247
|
+
else:
|
|
248
|
+
ids = tokenizer.encode(
|
|
249
|
+
current_doc + " " + sentence,
|
|
250
|
+
padding=False,
|
|
251
|
+
truncation=False,
|
|
252
|
+
add_special_tokens=False,
|
|
253
|
+
)
|
|
254
|
+
num_tokens = len(ids)
|
|
255
|
+
|
|
256
|
+
if num_tokens <= max_length:
|
|
257
|
+
current_doc += " " + sentence
|
|
258
|
+
else:
|
|
259
|
+
if current_doc.strip():
|
|
260
|
+
new_row = row.copy()
|
|
261
|
+
new_row[TEXT_COLUMN] = current_doc.strip()
|
|
262
|
+
new_row["is_split"] = True
|
|
263
|
+
new_row["num_tokens"] = len(
|
|
264
|
+
tokenizer.encode(
|
|
265
|
+
current_doc.strip(),
|
|
266
|
+
padding=False,
|
|
267
|
+
truncation=False,
|
|
268
|
+
add_special_tokens=False,
|
|
269
|
+
)
|
|
270
|
+
)
|
|
271
|
+
new_rows.append(new_row)
|
|
272
|
+
current_doc = sentence
|
|
273
|
+
|
|
274
|
+
if current_doc.strip():
|
|
275
|
+
new_row = row.copy()
|
|
276
|
+
new_row[TEXT_COLUMN] = current_doc.strip()
|
|
277
|
+
new_row["is_split"] = True
|
|
278
|
+
new_row["num_tokens"] = len(
|
|
279
|
+
tokenizer.encode(
|
|
280
|
+
current_doc.strip(),
|
|
281
|
+
padding=False,
|
|
282
|
+
truncation=False,
|
|
283
|
+
add_special_tokens=False,
|
|
284
|
+
)
|
|
285
|
+
)
|
|
286
|
+
new_rows.append(new_row)
|
|
287
|
+
|
|
288
|
+
return pd.DataFrame(new_rows)
|
|
289
|
+
|
|
290
|
+
|
|
291
|
+
def preprocess_french_text(text: str) -> str:
|
|
292
|
+
"""
|
|
293
|
+
Preprocess French text by normalizing apostrophes, replacing hyphens and similar characters with spaces,
|
|
294
|
+
removing specific prefixes, removing unwanted punctuations (excluding apostrophes, periods, commas, and specific other punctuation),
|
|
295
|
+
replacing special characters with a space (preserving accented characters, utils Latin extensions, and newlines),
|
|
296
|
+
normalizing superscripts and subscripts,
|
|
297
|
+
splitting words containing capitals in the middle (while avoiding splitting fully capitalized words),
|
|
298
|
+
and replacing multiple spaces with a single space.
|
|
299
|
+
|
|
300
|
+
Args:
|
|
301
|
+
text (str): The input French text to preprocess.
|
|
302
|
+
|
|
303
|
+
Returns:
|
|
304
|
+
str: The preprocessed French text.
|
|
305
|
+
"""
|
|
306
|
+
# Normalize different apostrophe variations to a standard apostrophe
|
|
307
|
+
text = text.replace("’", "'")
|
|
308
|
+
|
|
309
|
+
# Replace hyphens and similar characters with spaces
|
|
310
|
+
text = re.sub(r"\b(-|/|;|:)", " ", text)
|
|
311
|
+
|
|
312
|
+
# Replace special characters with a space (preserving specified punctuation, accented characters, utils Latin extensions, and newlines)
|
|
313
|
+
text = re.sub(r"[^\w\s\nàâçéèêëîïôûùüÿñæœ.,\'\"\(\)\[\]]", " ", text)
|
|
314
|
+
|
|
315
|
+
# Normalize superscripts and subscripts for both numbers and letters
|
|
316
|
+
superscript_map = str.maketrans(
|
|
317
|
+
"⁰¹²³⁴⁵⁶⁷⁸⁹ᵃᵇᶜᵈᵉᶠᵍʰⁱʲᵏˡᵐⁿᵒᵖᵠʳˢᵗᵘᵛʷˣʸᶻ", "0123456789abcdefghijklmnopqrstuvwxyz"
|
|
318
|
+
)
|
|
319
|
+
subscript_map = str.maketrans("₀₁₂₃₄₅₆₇₈₉ₐₑᵢₒᵣᵤᵥₓ", "0123456789aeioruvx")
|
|
320
|
+
text = text.translate(superscript_map)
|
|
321
|
+
text = text.translate(subscript_map)
|
|
322
|
+
|
|
323
|
+
# Split words that contain capitals in the middle but avoid splitting fully capitalized words
|
|
324
|
+
text = re.sub(r"(?<=[a-z])(?=[A-Z])", " ", text)
|
|
325
|
+
|
|
326
|
+
# Replace multiple spaces with a single space
|
|
327
|
+
text = re.sub(r"[ \t]+", " ", text)
|
|
328
|
+
|
|
329
|
+
return text
|
|
@@ -0,0 +1,15 @@
|
|
|
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
|
+
Lead Developers:
|
|
7
|
+
- Jérôme Picault
|
|
8
|
+
- Guillaume Grosjean
|
|
9
|
+
- Allaa Boutaleb
|
|
10
|
+
|
|
11
|
+
Other Developers:
|
|
12
|
+
-
|
|
13
|
+
|
|
14
|
+
Further Contributions by:
|
|
15
|
+
-
|