Spaces:
Sleeping
Sleeping
#!/usr/bin/env python | |
""" | |
Скрипт для объединения результатов всех экспериментов в одну Excel-таблицу с форматированием. | |
Анализирует результаты экспериментов и создает сводную таблицу с метриками в различных разрезах. | |
Также строит графики через seaborn и сохраняет их в отдельную директорию. | |
""" | |
import argparse | |
import glob | |
import os | |
import matplotlib.pyplot as plt | |
import pandas as pd | |
import seaborn as sns | |
from openpyxl import Workbook | |
from openpyxl.styles import Alignment, Border, Font, PatternFill, Side | |
from openpyxl.utils import get_column_letter | |
from openpyxl.utils.dataframe import dataframe_to_rows | |
def setup_plot_directory(plots_dir: str) -> None: | |
""" | |
Создает директорию для сохранения графиков, если она не существует. | |
Args: | |
plots_dir: Путь к директории для графиков | |
""" | |
if not os.path.exists(plots_dir): | |
os.makedirs(plots_dir) | |
print(f"Создана директория для графиков: {plots_dir}") | |
else: | |
print(f"Директория для графиков: {plots_dir}") | |
def parse_args(): | |
"""Парсит аргументы командной строки.""" | |
parser = argparse.ArgumentParser(description="Объединение результатов экспериментов в одну Excel-таблицу") | |
parser.add_argument("--results-dir", type=str, default="data", | |
help="Директория с результатами экспериментов (по умолчанию: data)") | |
parser.add_argument("--output-file", type=str, default="combined_results.xlsx", | |
help="Путь к выходному Excel-файлу (по умолчанию: combined_results.xlsx)") | |
parser.add_argument("--plots-dir", type=str, default="plots", | |
help="Директория для сохранения графиков (по умолчанию: plots)") | |
return parser.parse_args() | |
def parse_file_name(file_name: str) -> dict: | |
""" | |
Парсит имя файла и извлекает параметры эксперимента. | |
Args: | |
file_name: Имя файла для парсинга | |
Returns: | |
Словарь с параметрами (words_per_chunk, overlap_words, model) или None при ошибке | |
""" | |
try: | |
# Извлекаем параметры из имени файла | |
parts = file_name.split('_') | |
if len(parts) < 4: | |
return None | |
# Ищем части с w (words) и o (overlap) | |
words_part = None | |
overlap_part = None | |
for part in parts: | |
if part.startswith('w') and part[1:].isdigit(): | |
words_part = part[1:] | |
elif part.startswith('o') and part[1:].isdigit(): | |
# Убираем потенциальную часть .csv или .xlsx из overlap_part | |
overlap_part = part[1:].split('.')[0] | |
if words_part is None or overlap_part is None: | |
return None | |
# Пытаемся извлечь имя модели из оставшейся части имени файла | |
model_part = file_name.split(f"_w{words_part}_o{overlap_part}_", 1) | |
if len(model_part) < 2: | |
return None | |
# Получаем имя модели и удаляем возможное расширение файла | |
model_name_parts = model_part[1].split('.') | |
if len(model_name_parts) > 1: | |
model_name_parts = model_name_parts[:-1] | |
model_name_parts = '_'.join(model_name_parts).split('_') | |
model_name = '/'.join(model_name_parts) | |
return { | |
'words_per_chunk': int(words_part), | |
'overlap_words': int(overlap_part), | |
'model': model_name, | |
'overlap_percentage': round(int(overlap_part) / int(words_part) * 100, 1) | |
} | |
except Exception as e: | |
print(f"Ошибка при парсинге файла {file_name}: {e}") | |
return None | |
def load_data_files(results_dir: str, pattern: str, file_type: str, load_function) -> pd.DataFrame: | |
""" | |
Общая функция для загрузки файлов данных с определенным паттерном имени. | |
Args: | |
results_dir: Директория с результатами | |
pattern: Glob-паттерн для поиска файлов | |
file_type: Тип файлов для сообщений (напр. "результатов", "метрик") | |
load_function: Функция для загрузки конкретного типа файла | |
Returns: | |
DataFrame с объединенными данными или None при ошибке | |
""" | |
print(f"Загрузка {file_type} из {results_dir}...") | |
# Ищем все файлы с указанным паттерном | |
data_files = glob.glob(os.path.join(results_dir, pattern)) | |
if not data_files: | |
print(f"В директории {results_dir} не найдены файлы {file_type}") | |
return None | |
print(f"Найдено {len(data_files)} файлов {file_type}") | |
all_data = [] | |
for file_path in data_files: | |
# Извлекаем информацию о стратегии и модели из имени файла | |
file_name = os.path.basename(file_path) | |
print(f"Обрабатываю файл: {file_name}") | |
# Парсим параметры из имени файла | |
params = parse_file_name(file_name) | |
if params is None: | |
print(f"Пропуск файла {file_name}: не удалось извлечь параметры") | |
continue | |
words_part = params['words_per_chunk'] | |
overlap_part = params['overlap_words'] | |
model_name = params['model'] | |
overlap_percentage = params['overlap_percentage'] | |
print(f" Параметры: words={words_part}, overlap={overlap_part}, model={model_name}") | |
try: | |
# Загружаем данные, используя переданную функцию | |
df = load_function(file_path) | |
# Добавляем информацию о стратегии и модели | |
df['model'] = model_name | |
df['words_per_chunk'] = words_part | |
df['overlap_words'] = overlap_part | |
df['overlap_percentage'] = overlap_percentage | |
all_data.append(df) | |
except Exception as e: | |
print(f"Ошибка при обработке файла {file_path}: {e}") | |
if not all_data: | |
print(f"Не удалось загрузить ни один файл {file_type}") | |
return None | |
# Объединяем все данные | |
combined_data = pd.concat(all_data, ignore_index=True) | |
return combined_data | |
def load_results_files(results_dir: str) -> pd.DataFrame: | |
""" | |
Загружает все файлы результатов из указанной директории. | |
Args: | |
results_dir: Директория с результатами | |
Returns: | |
DataFrame с объединенными результатами | |
""" | |
# Используем общую функцию для загрузки CSV файлов | |
data = load_data_files( | |
results_dir, | |
"results_*.csv", | |
"результатов", | |
lambda f: pd.read_csv(f) | |
) | |
if data is None: | |
raise ValueError("Не удалось загрузить файлы с результатами") | |
return data | |
def load_question_metrics_files(results_dir: str) -> pd.DataFrame: | |
""" | |
Загружает все файлы с метриками по вопросам из указанной директории. | |
Args: | |
results_dir: Директория с результатами | |
Returns: | |
DataFrame с объединенными метриками по вопросам или None, если файлов нет | |
""" | |
# Используем общую функцию для загрузки Excel файлов | |
return load_data_files( | |
results_dir, | |
"question_metrics_*.xlsx", | |
"метрик по вопросам", | |
lambda f: pd.read_excel(f) | |
) | |
def prepare_summary_by_model_top_n(df: pd.DataFrame, macro_metrics: pd.DataFrame = None) -> pd.DataFrame: | |
""" | |
Подготавливает сводную таблицу по моделям и top_n значениям. | |
Если доступны macro метрики, они также включаются в сводную таблицу. | |
Args: | |
df: DataFrame с объединенными результатами | |
macro_metrics: DataFrame с macro метриками (опционально) | |
Returns: | |
DataFrame со сводной таблицей | |
""" | |
# Определяем группировочные колонки и метрики | |
group_by_columns = ['model', 'top_n'] | |
metrics = ['text_precision', 'text_recall', 'text_f1', 'doc_precision', 'doc_recall', 'doc_f1'] | |
# Используем общую функцию для подготовки сводки | |
return prepare_summary(df, group_by_columns, metrics, macro_metrics) | |
def prepare_summary_by_chunking_params_top_n(df: pd.DataFrame, macro_metrics: pd.DataFrame = None) -> pd.DataFrame: | |
""" | |
Подготавливает сводную таблицу по параметрам чанкинга и top_n значениям. | |
Если доступны macro метрики, они также включаются в сводную таблицу. | |
Args: | |
df: DataFrame с объединенными результатами | |
macro_metrics: DataFrame с macro метриками (опционально) | |
Returns: | |
DataFrame со сводной таблицей | |
""" | |
# Определяем группировочные колонки и метрики | |
group_by_columns = ['words_per_chunk', 'overlap_words', 'top_n'] | |
metrics = ['text_precision', 'text_recall', 'text_f1', 'doc_precision', 'doc_recall', 'doc_f1'] | |
# Используем общую функцию для подготовки сводки | |
return prepare_summary(df, group_by_columns, metrics, macro_metrics) | |
def prepare_summary(df: pd.DataFrame, group_by_columns: list, metrics: list, macro_metrics: pd.DataFrame = None) -> pd.DataFrame: | |
""" | |
Общая функция для подготовки сводной таблицы по указанным группировочным колонкам. | |
Если доступны macro метрики, они также включаются в сводную таблицу. | |
Args: | |
df: DataFrame с объединенными результатами | |
group_by_columns: Колонки для группировки | |
metrics: Список метрик для расчета среднего | |
macro_metrics: DataFrame с macro метриками (опционально) | |
Returns: | |
DataFrame со сводной таблицей | |
""" | |
# Группируем по указанным колонкам, вычисляем средние значения метрик | |
summary = df.groupby(group_by_columns).agg({ | |
metric: 'mean' for metric in metrics | |
}).reset_index() | |
# Если среди группировочных колонок есть 'overlap_words' и 'words_per_chunk', | |
# добавляем процент перекрытия | |
if 'overlap_words' in group_by_columns and 'words_per_chunk' in group_by_columns: | |
summary['overlap_percentage'] = (summary['overlap_words'] / summary['words_per_chunk'] * 100).round(1) | |
# Если доступны macro метрики, объединяем их с summary | |
if macro_metrics is not None: | |
# Преобразуем метрики в macro_метрики | |
macro_metric_names = [f"macro_{metric}" for metric in metrics] | |
# Группируем macro метрики по тем же колонкам | |
macro_summary = macro_metrics.groupby(group_by_columns).agg({ | |
metric: 'mean' for metric in macro_metric_names | |
}).reset_index() | |
# Если нужно, добавляем процент перекрытия для согласованности | |
if 'overlap_words' in group_by_columns and 'words_per_chunk' in group_by_columns: | |
macro_summary['overlap_percentage'] = (macro_summary['overlap_words'] / macro_summary['words_per_chunk'] * 100).round(1) | |
merge_on = group_by_columns + ['overlap_percentage'] | |
else: | |
merge_on = group_by_columns | |
# Объединяем с основной сводкой | |
summary = pd.merge(summary, macro_summary, on=merge_on, how='left') | |
# Сортируем по группировочным колонкам | |
summary = summary.sort_values(group_by_columns) | |
# Округляем метрики до 4 знаков после запятой | |
for col in summary.columns: | |
if any(col.endswith(suffix) for suffix in ['precision', 'recall', 'f1']): | |
summary[col] = summary[col].round(4) | |
return summary | |
def prepare_best_configurations(df: pd.DataFrame, macro_metrics: pd.DataFrame = None) -> pd.DataFrame: | |
""" | |
Подготавливает таблицу с лучшими конфигурациями для каждой модели и различных top_n. | |
Выбирает конфигурацию только на основе macro_text_recall и text_recall (weighted), | |
игнорируя F1 метрики как менее важные. | |
Args: | |
df: DataFrame с объединенными результатами | |
macro_metrics: DataFrame с macro метриками (опционально) | |
Returns: | |
DataFrame с лучшими конфигурациями | |
""" | |
# Выбираем ключевые значения top_n | |
key_top_n = [10, 20, 50, 100] | |
# Определяем источник метрик и акцентируем только на recall-метриках | |
if macro_metrics is not None: | |
print("Выбор лучших конфигураций на основе macro метрик (macro_text_recall)") | |
metrics_source = macro_metrics | |
text_recall_metric = 'macro_text_recall' | |
doc_recall_metric = 'macro_doc_recall' | |
else: | |
print("Выбор лучших конфигураций на основе weighted метрик (text_recall)") | |
metrics_source = df | |
text_recall_metric = 'text_recall' | |
doc_recall_metric = 'doc_recall' | |
# Фильтруем только по ключевым значениям top_n | |
filtered_df = metrics_source[metrics_source['top_n'].isin(key_top_n)] | |
# Для каждой модели и top_n находим конфигурацию только с лучшим recall | |
best_configs = [] | |
for model in metrics_source['model'].unique(): | |
for top_n in key_top_n: | |
model_top_n_df = filtered_df[(filtered_df['model'] == model) & (filtered_df['top_n'] == top_n)] | |
if len(model_top_n_df) == 0: | |
continue | |
# Находим конфигурацию с лучшим text_recall | |
best_text_recall_idx = model_top_n_df[text_recall_metric].idxmax() | |
best_text_recall_config = model_top_n_df.loc[best_text_recall_idx].copy() | |
best_text_recall_config['metric_type'] = 'text_recall' | |
# Находим конфигурацию с лучшим doc_recall | |
best_doc_recall_idx = model_top_n_df[doc_recall_metric].idxmax() | |
best_doc_recall_config = model_top_n_df.loc[best_doc_recall_idx].copy() | |
best_doc_recall_config['metric_type'] = 'doc_recall' | |
best_configs.append(best_text_recall_config) | |
best_configs.append(best_doc_recall_config) | |
if not best_configs: | |
return pd.DataFrame() | |
best_configs_df = pd.DataFrame(best_configs) | |
# Выбираем и сортируем нужные столбцы | |
cols_to_keep = ['model', 'top_n', 'metric_type', 'words_per_chunk', 'overlap_words', 'overlap_percentage'] | |
# Добавляем столбцы метрик в зависимости от того, какие доступны | |
if macro_metrics is not None: | |
# Для macro метрик сначала выбираем recall-метрики | |
recall_cols = [col for col in best_configs_df.columns if col.endswith('recall')] | |
# Затем добавляем остальные метрики | |
other_cols = [col for col in best_configs_df.columns if any(col.endswith(m) for m in | |
['precision', 'f1']) and col.startswith('macro_')] | |
metric_cols = recall_cols + other_cols | |
else: | |
# Для weighted метрик сначала выбираем recall-метрики | |
recall_cols = [col for col in best_configs_df.columns if col.endswith('recall')] | |
# Затем добавляем остальные метрики | |
other_cols = [col for col in best_configs_df.columns if any(col.endswith(m) for m in | |
['precision', 'f1']) and not col.startswith('macro_')] | |
metric_cols = recall_cols + other_cols | |
result = best_configs_df[cols_to_keep + metric_cols].sort_values(['model', 'top_n', 'metric_type']) | |
return result | |
def get_grouping_columns(sheet) -> dict: | |
""" | |
Определяет подходящие колонки для группировки данных на листе. | |
Args: | |
sheet: Лист Excel | |
Returns: | |
Словарь с данными о группировке или None | |
""" | |
# Возможные варианты группировки | |
grouping_possibilities = [ | |
{'columns': ['model', 'words_per_chunk', 'overlap_words']}, | |
{'columns': ['model']}, | |
{'columns': ['words_per_chunk', 'overlap_words']}, | |
{'columns': ['top_n']}, | |
{'columns': ['model', 'top_n', 'metric_type']} | |
] | |
# Для каждого варианта группировки проверяем наличие всех колонок | |
for grouping in grouping_possibilities: | |
column_indices = {} | |
all_columns_present = True | |
for column_name in grouping['columns']: | |
column_idx = None | |
for col_idx, cell in enumerate(sheet[1], start=1): | |
if cell.value == column_name: | |
column_idx = col_idx | |
break | |
if column_idx is None: | |
all_columns_present = False | |
break | |
else: | |
column_indices[column_name] = column_idx | |
if all_columns_present: | |
return { | |
'columns': grouping['columns'], | |
'indices': column_indices | |
} | |
return None | |
def apply_header_formatting(sheet): | |
""" | |
Применяет форматирование к заголовкам. | |
Args: | |
sheet: Лист Excel | |
""" | |
# Форматирование заголовков | |
for cell in sheet[1]: | |
cell.font = Font(bold=True) | |
cell.fill = PatternFill(start_color="D9D9D9", end_color="D9D9D9", fill_type="solid") | |
cell.alignment = Alignment(horizontal='center', vertical='center', wrap_text=True) | |
def adjust_column_width(sheet): | |
""" | |
Настраивает ширину столбцов на основе содержимого. | |
Args: | |
sheet: Лист Excel | |
""" | |
# Авторазмер столбцов | |
for column in sheet.columns: | |
max_length = 0 | |
column_letter = get_column_letter(column[0].column) | |
for cell in column: | |
if cell.value: | |
try: | |
if len(str(cell.value)) > max_length: | |
max_length = len(str(cell.value)) | |
except: | |
pass | |
adjusted_width = (max_length + 2) * 1.1 | |
sheet.column_dimensions[column_letter].width = adjusted_width | |
def apply_cell_formatting(sheet): | |
""" | |
Применяет форматирование к ячейкам (границы, выравнивание и т.д.). | |
Args: | |
sheet: Лист Excel | |
""" | |
# Тонкие границы для всех ячеек | |
thin_border = Border( | |
left=Side(style='thin'), | |
right=Side(style='thin'), | |
top=Side(style='thin'), | |
bottom=Side(style='thin') | |
) | |
for row in sheet.iter_rows(min_row=1, max_row=sheet.max_row, min_col=1, max_col=sheet.max_column): | |
for cell in row: | |
cell.border = thin_border | |
# Форматирование числовых значений | |
numeric_columns = [ | |
'text_precision', 'text_recall', 'text_f1', | |
'doc_precision', 'doc_recall', 'doc_f1', | |
'macro_text_precision', 'macro_text_recall', 'macro_text_f1', | |
'macro_doc_precision', 'macro_doc_recall', 'macro_doc_f1' | |
] | |
for col_idx, header in enumerate(sheet[1], start=1): | |
if header.value in numeric_columns or (header.value and str(header.value).endswith(('precision', 'recall', 'f1'))): | |
for row_idx in range(2, sheet.max_row + 1): | |
cell = sheet.cell(row=row_idx, column=col_idx) | |
if isinstance(cell.value, (int, float)): | |
cell.number_format = '0.0000' | |
# Выравнивание для всех ячеек | |
for row in sheet.iter_rows(min_row=2, max_row=sheet.max_row, min_col=1, max_col=sheet.max_column): | |
for cell in row: | |
cell.alignment = Alignment(horizontal='center', vertical='center') | |
def apply_group_formatting(sheet, grouping): | |
""" | |
Применяет форматирование к группам строк. | |
Args: | |
sheet: Лист Excel | |
grouping: Словарь с данными о группировке | |
""" | |
if not grouping or sheet.max_row <= 1: | |
return | |
# Для каждой строки проверяем изменение значений группировочных колонок | |
last_values = {column: None for column in grouping['columns']} | |
# Применяем жирную верхнюю границу к первой строке данных | |
for col_idx in range(1, sheet.max_column + 1): | |
cell = sheet.cell(row=2, column=col_idx) | |
cell.border = Border( | |
left=cell.border.left, | |
right=cell.border.right, | |
top=Side(style='thick'), | |
bottom=cell.border.bottom | |
) | |
for row_idx in range(2, sheet.max_row + 1): | |
current_values = {} | |
for column in grouping['columns']: | |
col_idx = grouping['indices'][column] | |
current_values[column] = sheet.cell(row=row_idx, column=col_idx).value | |
# Если значения изменились, добавляем жирные границы | |
values_changed = False | |
for column in grouping['columns']: | |
if current_values[column] != last_values[column]: | |
values_changed = True | |
break | |
if values_changed and row_idx > 2: | |
# Жирная верхняя граница для текущей строки | |
for col_idx in range(1, sheet.max_column + 1): | |
cell = sheet.cell(row=row_idx, column=col_idx) | |
cell.border = Border( | |
left=cell.border.left, | |
right=cell.border.right, | |
top=Side(style='thick'), | |
bottom=cell.border.bottom | |
) | |
# Жирная нижняя граница для предыдущей строки | |
for col_idx in range(1, sheet.max_column + 1): | |
cell = sheet.cell(row=row_idx-1, column=col_idx) | |
cell.border = Border( | |
left=cell.border.left, | |
right=cell.border.right, | |
top=cell.border.top, | |
bottom=Side(style='thick') | |
) | |
# Запоминаем текущие значения для следующей итерации | |
for column in grouping['columns']: | |
last_values[column] = current_values[column] | |
# Добавляем жирную нижнюю границу для последней строки | |
for col_idx in range(1, sheet.max_column + 1): | |
cell = sheet.cell(row=sheet.max_row, column=col_idx) | |
cell.border = Border( | |
left=cell.border.left, | |
right=cell.border.right, | |
top=cell.border.top, | |
bottom=Side(style='thick') | |
) | |
def apply_formatting(workbook: Workbook) -> None: | |
""" | |
Применяет форматирование к Excel-файлу. | |
Добавляет автофильтры для всех столбцов и улучшает визуальное представление. | |
Args: | |
workbook: Workbook-объект openpyxl | |
""" | |
for sheet_name in workbook.sheetnames: | |
sheet = workbook[sheet_name] | |
# Добавляем автофильтры для всех столбцов | |
if sheet.max_row > 1: # Проверяем, что в листе есть данные | |
sheet.auto_filter.ref = sheet.dimensions | |
# Применяем форматирование | |
apply_header_formatting(sheet) | |
adjust_column_width(sheet) | |
apply_cell_formatting(sheet) | |
# Определяем группирующие колонки и применяем форматирование к группам | |
grouping = get_grouping_columns(sheet) | |
if grouping: | |
apply_group_formatting(sheet, grouping) | |
def create_model_comparison_plot(df: pd.DataFrame, metrics: list | str, top_n: int, plots_dir: str) -> None: | |
""" | |
Создает график сравнения моделей по указанным метрикам для заданного top_n. | |
Args: | |
df: DataFrame с данными | |
metrics: Список метрик или одна метрика для сравнения | |
top_n: Значение top_n для фильтрации | |
plots_dir: Директория для сохранения графиков | |
""" | |
if isinstance(metrics, str): | |
metrics = [metrics] | |
# Фильтруем данные | |
filtered_df = df[df['top_n'] == top_n] | |
if len(filtered_df) == 0: | |
print(f"Нет данных для top_n={top_n}") | |
return | |
# Определяем тип метрик (macro или weighted) | |
metrics_type = "macro" if metrics[0].startswith("macro_") else "weighted" | |
# Создаем фигуру с несколькими подграфиками | |
fig, axes = plt.subplots(1, len(metrics), figsize=(6 * len(metrics), 8)) | |
# Если только одна метрика, преобразуем axes в список для единообразного обращения | |
if len(metrics) == 1: | |
axes = [axes] | |
# Для каждой метрики создаем subplot | |
for i, metric in enumerate(metrics): | |
# Группируем данные по модели | |
columns_to_agg = {metric: 'mean'} | |
model_data = filtered_df.groupby('model').agg(columns_to_agg).reset_index() | |
# Сортируем по значению метрики (по убыванию) | |
model_data = model_data.sort_values(metric, ascending=False) | |
# Определяем цветовую схему | |
palette = sns.color_palette("viridis", len(model_data)) | |
# Строим столбчатую диаграмму на соответствующем subplot | |
ax = sns.barplot(x='model', y=metric, data=model_data, palette=palette, ax=axes[i]) | |
# Добавляем значения над столбцами | |
for j, v in enumerate(model_data[metric]): | |
ax.text(j, v + 0.01, f"{v:.4f}", ha='center', fontsize=8) | |
# Устанавливаем заголовок и метки осей | |
ax.set_title(f"{metric} (top_n={top_n})", fontsize=12) | |
ax.set_xlabel("Модель", fontsize=10) | |
ax.set_ylabel(f"{metric}", fontsize=10) | |
# Поворачиваем подписи по оси X для лучшей читаемости | |
ax.set_xticklabels(ax.get_xticklabels(), rotation=45, ha='right', fontsize=8) | |
# Настраиваем макет | |
plt.tight_layout() | |
# Сохраняем график | |
metric_names = '_'.join([m.replace('macro_', '') for m in metrics]) | |
file_name = f"model_comparison_{metrics_type}_{metric_names}_top{top_n}.png" | |
plt.savefig(os.path.join(plots_dir, file_name), dpi=300) | |
plt.close() | |
print(f"Создан график сравнения моделей: {file_name}") | |
def create_top_n_plot(df: pd.DataFrame, models: list | str, metric: str, plots_dir: str) -> None: | |
""" | |
Создает график зависимости метрики от top_n для заданных моделей. | |
Args: | |
df: DataFrame с данными | |
models: Список моделей или одна модель для сравнения | |
metric: Название метрики | |
plots_dir: Директория для сохранения графиков | |
""" | |
if isinstance(models, str): | |
models = [models] | |
# Создаем фигуру | |
plt.figure(figsize=(12, 8)) | |
# Определяем цветовую схему | |
palette = sns.color_palette("viridis", len(models)) | |
# Ограничиваем количество моделей для читаемости | |
if len(models) > 5: | |
models = models[:5] | |
print("Слишком много моделей для графика, ограничиваем до 5") | |
# Для каждой модели строим линию | |
for i, model in enumerate(models): | |
# Находим наиболее часто используемые параметры чанкинга для этой модели | |
model_df = df[df['model'] == model] | |
if len(model_df) == 0: | |
print(f"Нет данных для модели {model}") | |
continue | |
# Группируем по параметрам чанкинга и подсчитываем частоту | |
common_configs = model_df.groupby(['words_per_chunk', 'overlap_words']).size().reset_index(name='count') | |
if len(common_configs) == 0: | |
continue | |
# Берем наиболее частую конфигурацию | |
common_config = common_configs.sort_values('count', ascending=False).iloc[0] | |
# Фильтруем для этой конфигурации | |
config_df = model_df[ | |
(model_df['words_per_chunk'] == common_config['words_per_chunk']) & | |
(model_df['overlap_words'] == common_config['overlap_words']) | |
].sort_values('top_n') | |
if len(config_df) <= 1: | |
continue | |
# Строим линию | |
plt.plot(config_df['top_n'], config_df[metric], marker='o', linewidth=2, | |
label=f"{model} (w={common_config['words_per_chunk']}, o={common_config['overlap_words']})", | |
color=palette[i]) | |
# Добавляем легенду, заголовок и метки осей | |
plt.legend(title="Модель (параметры)", fontsize=10, loc='best') | |
plt.title(f"Зависимость {metric} от top_n для разных моделей", fontsize=16) | |
plt.xlabel("top_n", fontsize=14) | |
plt.ylabel(metric, fontsize=14) | |
# Включаем сетку | |
plt.grid(True, linestyle='--', alpha=0.7) | |
# Настраиваем макет | |
plt.tight_layout() | |
# Сохраняем график | |
is_macro = "macro" if "macro" in metric else "weighted" | |
file_name = f"top_n_comparison_{is_macro}_{metric.replace('macro_', '')}.png" | |
plt.savefig(os.path.join(plots_dir, file_name), dpi=300) | |
plt.close() | |
print(f"Создан график зависимости от top_n: {file_name}") | |
def create_chunk_size_plot(df: pd.DataFrame, model: str, metrics: list | str, top_n: int, plots_dir: str) -> None: | |
""" | |
Создает график зависимости метрик от размера чанка для заданной модели и top_n. | |
Args: | |
df: DataFrame с данными | |
model: Название модели | |
metrics: Список метрик или одна метрика | |
top_n: Значение top_n | |
plots_dir: Директория для сохранения графиков | |
""" | |
if isinstance(metrics, str): | |
metrics = [metrics] | |
# Фильтруем данные | |
filtered_df = df[(df['model'] == model) & (df['top_n'] == top_n)] | |
if len(filtered_df) <= 1: | |
print(f"Недостаточно данных для модели {model} и top_n={top_n}") | |
return | |
# Создаем фигуру | |
plt.figure(figsize=(14, 8)) | |
# Определяем цветовую схему для метрик | |
palette = sns.color_palette("viridis", len(metrics)) | |
# Группируем по размеру чанка и проценту перекрытия | |
# Вычисляем среднее только для указанных метрик, а не для всех столбцов | |
columns_to_agg = {metric: 'mean' for metric in metrics} | |
chunk_data = filtered_df.groupby(['words_per_chunk', 'overlap_percentage']).agg(columns_to_agg).reset_index() | |
# Получаем уникальные значения процента перекрытия | |
overlap_percentages = sorted(chunk_data['overlap_percentage'].unique()) | |
# Настраиваем маркеры и линии для разных перекрытий | |
markers = ['o', 's', '^', 'D', 'x', '*'] | |
# Для каждого перекрытия строим линии с разными метриками | |
for i, overlap in enumerate(overlap_percentages): | |
subset = chunk_data[chunk_data['overlap_percentage'] == overlap].sort_values('words_per_chunk') | |
for j, metric in enumerate(metrics): | |
plt.plot(subset['words_per_chunk'], subset[metric], | |
marker=markers[i % len(markers)], linewidth=2, | |
label=f"{metric}, overlap={overlap}%", | |
color=palette[j]) | |
# Добавляем легенду и заголовок | |
plt.legend(title="Метрика и перекрытие", fontsize=10, loc='best') | |
plt.title(f"Зависимость метрик от размера чанка для {model} (top_n={top_n})", fontsize=16) | |
plt.xlabel("Размер чанка (слов)", fontsize=14) | |
plt.ylabel("Значение метрики", fontsize=14) | |
# Включаем сетку | |
plt.grid(True, linestyle='--', alpha=0.7) | |
# Настраиваем макет | |
plt.tight_layout() | |
# Сохраняем график | |
metrics_type = "macro" if metrics[0].startswith("macro_") else "weighted" | |
model_name = model.replace('/', '_') | |
metric_names = '_'.join([m.replace('macro_', '') for m in metrics]) | |
file_name = f"chunk_size_{metrics_type}_{metric_names}_{model_name}_top{top_n}.png" | |
plt.savefig(os.path.join(plots_dir, file_name), dpi=300) | |
plt.close() | |
print(f"Создан график зависимости от размера чанка: {file_name}") | |
def create_heatmap(df: pd.DataFrame, models: list | str, metric: str, top_n: int, plots_dir: str) -> None: | |
""" | |
Создает тепловые карты зависимости метрики от размера чанка и процента перекрытия | |
для заданных моделей. | |
Args: | |
df: DataFrame с данными | |
models: Список моделей или одна модель | |
metric: Название метрики | |
top_n: Значение top_n | |
plots_dir: Директория для сохранения графиков | |
""" | |
if isinstance(models, str): | |
models = [models] | |
# Ограничиваем количество моделей для наглядности | |
if len(models) > 4: | |
models = models[:4] | |
# Создаем фигуру с подграфиками | |
fig, axes = plt.subplots(1, len(models), figsize=(6 * len(models), 6), squeeze=False) | |
# Для каждой модели создаем тепловую карту | |
for i, model in enumerate(models): | |
# Фильтруем данные для указанной модели и top_n | |
filtered_df = df[(df['model'] == model) & (df['top_n'] == top_n)] | |
# Проверяем, достаточно ли данных для построения тепловой карты | |
chunk_sizes = filtered_df['words_per_chunk'].unique() | |
overlap_percentages = filtered_df['overlap_percentage'].unique() | |
if len(chunk_sizes) <= 1 or len(overlap_percentages) <= 1: | |
print(f"Недостаточно данных для построения тепловой карты для модели {model} и top_n={top_n}") | |
# Пропускаем этот subplot | |
axes[0, i].text(0.5, 0.5, f"Недостаточно данных для {model}", | |
horizontalalignment='center', verticalalignment='center') | |
axes[0, i].set_title(model) | |
axes[0, i].axis('off') | |
continue | |
# Создаем сводную таблицу для тепловой карты, используя только нужную метрику | |
# Сначала выберем только колонки для pivot_table | |
pivot_columns = ['words_per_chunk', 'overlap_percentage', metric] | |
pivot_df = filtered_df[pivot_columns].copy() | |
# Теперь создаем сводную таблицу | |
pivot_data = pivot_df.pivot_table( | |
index='words_per_chunk', | |
columns='overlap_percentage', | |
values=metric, | |
aggfunc='mean' | |
) | |
# Строим тепловую карту | |
sns.heatmap(pivot_data, annot=True, fmt=".4f", cmap="viridis", | |
linewidths=.5, annot_kws={"size": 8}, ax=axes[0, i]) | |
# Устанавливаем заголовок и метки осей | |
axes[0, i].set_title(model, fontsize=12) | |
axes[0, i].set_xlabel("Процент перекрытия (%)", fontsize=10) | |
axes[0, i].set_ylabel("Размер чанка (слов)", fontsize=10) | |
# Добавляем общий заголовок | |
plt.suptitle(f"Тепловые карты {metric} для разных моделей (top_n={top_n})", fontsize=16) | |
# Настраиваем макет | |
plt.tight_layout(rect=[0, 0, 1, 0.96]) # Оставляем место для общего заголовка | |
# Сохраняем график | |
is_macro = "macro" if "macro" in metric else "weighted" | |
file_name = f"heatmap_{is_macro}_{metric.replace('macro_', '')}_top{top_n}.png" | |
plt.savefig(os.path.join(plots_dir, file_name), dpi=300) | |
plt.close() | |
print(f"Созданы тепловые карты: {file_name}") | |
def find_best_combinations(df: pd.DataFrame, metrics: list | str = None) -> pd.DataFrame: | |
""" | |
Находит наилучшие комбинации параметров на основе агрегированных recall-метрик. | |
Args: | |
df: DataFrame с данными | |
metrics: Список метрик для анализа или None (тогда используются все recall-метрики) | |
Returns: | |
DataFrame с лучшими комбинациями параметров | |
""" | |
if metrics is None: | |
# По умолчанию выбираем все метрики с "recall" в названии | |
metrics = [col for col in df.columns if "recall" in col] | |
elif isinstance(metrics, str): | |
metrics = [metrics] | |
print(f"Поиск лучших комбинаций на основе метрик: {metrics}") | |
# Создаем новую метрику - сумму всех указанных recall-метрик | |
df_copy = df.copy() | |
df_copy['combined_recall'] = df_copy[metrics].sum(axis=1) | |
# Находим лучшие комбинации для различных значений top_n | |
best_combinations = [] | |
for top_n in df_copy['top_n'].unique(): | |
top_n_df = df_copy[df_copy['top_n'] == top_n] | |
if len(top_n_df) == 0: | |
continue | |
# Находим строку с максимальным combined_recall | |
best_idx = top_n_df['combined_recall'].idxmax() | |
best_row = top_n_df.loc[best_idx].copy() | |
best_row['best_for_top_n'] = top_n | |
best_combinations.append(best_row) | |
# Находим лучшие комбинации для разных моделей | |
for model in df_copy['model'].unique(): | |
model_df = df_copy[df_copy['model'] == model] | |
if len(model_df) == 0: | |
continue | |
# Находим строку с максимальным combined_recall | |
best_idx = model_df['combined_recall'].idxmax() | |
best_row = model_df.loc[best_idx].copy() | |
best_row['best_for_model'] = model | |
best_combinations.append(best_row) | |
# Находим лучшие комбинации для разных размеров чанков | |
for chunk_size in df_copy['words_per_chunk'].unique(): | |
chunk_df = df_copy[df_copy['words_per_chunk'] == chunk_size] | |
if len(chunk_df) == 0: | |
continue | |
# Находим строку с максимальным combined_recall | |
best_idx = chunk_df['combined_recall'].idxmax() | |
best_row = chunk_df.loc[best_idx].copy() | |
best_row['best_for_chunk_size'] = chunk_size | |
best_combinations.append(best_row) | |
# Находим абсолютно лучшую комбинацию | |
if len(df_copy) > 0: | |
best_idx = df_copy['combined_recall'].idxmax() | |
best_row = df_copy.loc[best_idx].copy() | |
best_row['absolute_best'] = True | |
best_combinations.append(best_row) | |
if not best_combinations: | |
return pd.DataFrame() | |
result = pd.DataFrame(best_combinations) | |
# Сортируем по combined_recall (по убыванию) | |
result = result.sort_values('combined_recall', ascending=False) | |
print(f"Найдено {len(result)} лучших комбинаций") | |
return result | |
def create_best_combinations_plot(best_df: pd.DataFrame, metrics: list | str, plots_dir: str) -> None: | |
""" | |
Создает график сравнения лучших комбинаций параметров. | |
Args: | |
best_df: DataFrame с лучшими комбинациями | |
metrics: Список метрик для визуализации | |
plots_dir: Директория для сохранения графиков | |
""" | |
if isinstance(metrics, str): | |
metrics = [metrics] | |
if len(best_df) == 0: | |
print("Нет данных для построения графика лучших комбинаций") | |
return | |
# Создаем новый признак для идентификации комбинаций | |
best_df['combo_label'] = best_df.apply( | |
lambda row: f"{row['model']} (w={row['words_per_chunk']}, o={row['overlap_words']}, top_n={row['top_n']})", | |
axis=1 | |
) | |
# Берем только лучшие N комбинаций для читаемости | |
max_combos = 10 | |
if len(best_df) > max_combos: | |
plot_df = best_df.head(max_combos).copy() | |
print(f"Ограничиваем график до {max_combos} лучших комбинаций") | |
else: | |
plot_df = best_df.copy() | |
# Создаем длинный формат данных для seaborn | |
plot_data = plot_df.melt( | |
id_vars=['combo_label', 'combined_recall'], | |
value_vars=metrics, | |
var_name='metric', | |
value_name='value' | |
) | |
# Сортируем по суммарному recall (комбинации) и метрике (для группировки) | |
plot_data = plot_data.sort_values(['combined_recall', 'metric'], ascending=[False, True]) | |
# Создаем фигуру для графика | |
plt.figure(figsize=(14, 10)) | |
# Создаем bar plot | |
sns.barplot( | |
x='combo_label', | |
y='value', | |
hue='metric', | |
data=plot_data, | |
palette='viridis' | |
) | |
# Настраиваем оси и заголовок | |
plt.title('Лучшие комбинации параметров по recall-метрикам', fontsize=16) | |
plt.xlabel('Комбинация параметров', fontsize=14) | |
plt.ylabel('Значение метрики', fontsize=14) | |
# Поворачиваем подписи по оси X для лучшей читаемости | |
plt.xticks(rotation=45, ha='right', fontsize=10) | |
# Настраиваем легенду | |
plt.legend(title='Метрика', fontsize=12) | |
# Добавляем сетку | |
plt.grid(axis='y', linestyle='--', alpha=0.7) | |
# Настраиваем макет | |
plt.tight_layout() | |
# Сохраняем график | |
file_name = f"best_combinations_comparison.png" | |
plt.savefig(os.path.join(plots_dir, file_name), dpi=300) | |
plt.close() | |
print(f"Создан график сравнения лучших комбинаций: {file_name}") | |
def generate_plots(combined_results: pd.DataFrame, macro_metrics: pd.DataFrame, plots_dir: str) -> None: | |
""" | |
Генерирует набор графиков с помощью seaborn и сохраняет их в указанную директорию. | |
Фокусируется в первую очередь на recall-метриках как наиболее важных. | |
Args: | |
combined_results: DataFrame с объединенными результатами (weighted метрики) | |
macro_metrics: DataFrame с macro метриками | |
plots_dir: Директория для сохранения графиков | |
""" | |
# Создаем директорию для графиков, если она не существует | |
setup_plot_directory(plots_dir) | |
# Настраиваем стиль для графиков | |
sns.set_style("whitegrid") | |
plt.rcParams['font.family'] = 'DejaVu Sans' | |
# Получаем список моделей для построения графиков | |
models = combined_results['model'].unique() | |
top_n_values = [10, 20, 50, 100] | |
print(f"Генерация графиков для {len(models)} моделей...") | |
# 0. Добавляем анализ наилучших комбинаций параметров | |
# Определяем метрики для анализа - фокусируемся на recall | |
weighted_recall_metrics = ['text_recall', 'doc_recall'] | |
# Находим лучшие комбинации параметров | |
best_combinations = find_best_combinations(combined_results, weighted_recall_metrics) | |
# Создаем график сравнения лучших комбинаций | |
if not best_combinations.empty: | |
create_best_combinations_plot(best_combinations, weighted_recall_metrics, plots_dir) | |
# Если доступны macro метрики, делаем то же самое для них | |
if macro_metrics is not None: | |
macro_recall_metrics = ['macro_text_recall', 'macro_doc_recall'] | |
macro_best_combinations = find_best_combinations(macro_metrics, macro_recall_metrics) | |
if not macro_best_combinations.empty: | |
create_best_combinations_plot(macro_best_combinations, macro_recall_metrics, plots_dir) | |
# 1. Создаем графики сравнения моделей для weighted метрик | |
# Фокусируемся на recall-метриках | |
weighted_metrics = { | |
'text': ['text_recall'], # Только text_recall | |
'doc': ['doc_recall'] # Только doc_recall | |
} | |
for top_n in top_n_values: | |
for metrics_group, metrics in weighted_metrics.items(): | |
create_model_comparison_plot(combined_results, metrics, top_n, plots_dir) | |
# 2. Если доступны macro метрики, создаем графики на их основе | |
if macro_metrics is not None: | |
print("Создание графиков на основе macro метрик...") | |
macro_metrics_groups = { | |
'text': ['macro_text_recall'], # Только macro_text_recall | |
'doc': ['macro_doc_recall'] # Только macro_doc_recall | |
} | |
for top_n in top_n_values: | |
for metrics_group, metrics in macro_metrics_groups.items(): | |
create_model_comparison_plot(macro_metrics, metrics, top_n, plots_dir) | |
# 3. Создаем графики зависимости от top_n | |
for metrics_type, df in [("weighted", combined_results), ("macro", macro_metrics)]: | |
if df is None: | |
continue | |
metrics_to_plot = [] | |
if metrics_type == "weighted": | |
metrics_to_plot = ['text_recall', 'doc_recall'] # Только recall-метрики | |
else: | |
metrics_to_plot = ['macro_text_recall', 'macro_doc_recall'] # Только macro recall-метрики | |
for metric in metrics_to_plot: | |
create_top_n_plot(df, models, metric, plots_dir) | |
# 4. Для каждой модели создаем графики по размеру чанка | |
for model in models: | |
# Выбираем 2 значения top_n для анализа | |
for top_n in [20, 50]: | |
# Создаем графики с recall-метриками | |
weighted_metrics_to_combine = ['text_recall'] | |
create_chunk_size_plot(combined_results, model, weighted_metrics_to_combine, top_n, plots_dir) | |
doc_metrics_to_combine = ['doc_recall'] | |
create_chunk_size_plot(combined_results, model, doc_metrics_to_combine, top_n, plots_dir) | |
# Если есть macro метрики, создаем соответствующие графики | |
if macro_metrics is not None: | |
macro_metrics_to_combine = ['macro_text_recall'] | |
create_chunk_size_plot(macro_metrics, model, macro_metrics_to_combine, top_n, plots_dir) | |
macro_doc_metrics_to_combine = ['macro_doc_recall'] | |
create_chunk_size_plot(macro_metrics, model, macro_doc_metrics_to_combine, top_n, plots_dir) | |
# 5. Создаем тепловые карты для моделей | |
for top_n in [20, 50]: | |
for metric_prefix in ["", "macro_"]: | |
for metric_type in ["text_recall", "doc_recall"]: | |
metric = f"{metric_prefix}{metric_type}" | |
# Используем соответствующий DataFrame | |
if metric_prefix and macro_metrics is None: | |
continue | |
df_to_use = macro_metrics if metric_prefix else combined_results | |
create_heatmap(df_to_use, models, metric, top_n, plots_dir) | |
print(f"Создание графиков завершено в директории {plots_dir}") | |
def print_best_combinations(best_df: pd.DataFrame) -> None: | |
""" | |
Выводит информацию о лучших комбинациях параметров. | |
Args: | |
best_df: DataFrame с лучшими комбинациями | |
""" | |
if best_df.empty: | |
print("Не найдено лучших комбинаций") | |
return | |
print("\n=== ЛУЧШИЕ КОМБИНАЦИИ ПАРАМЕТРОВ ===") | |
# Выводим абсолютно лучшую комбинацию, если она есть | |
absolute_best = best_df[best_df.get('absolute_best', False) == True] | |
if not absolute_best.empty: | |
row = absolute_best.iloc[0] | |
print(f"\nАБСОЛЮТНО ЛУЧШАЯ КОМБИНАЦИЯ:") | |
print(f" Модель: {row['model']}") | |
print(f" Размер чанка: {row['words_per_chunk']} слов") | |
print(f" Перекрытие: {row['overlap_words']} слов ({row['overlap_percentage']}%)") | |
print(f" top_n: {row['top_n']}") | |
# Выводим значения метрик | |
recall_metrics = [col for col in best_df.columns if 'recall' in col and col != 'combined_recall'] | |
for metric in recall_metrics: | |
print(f" {metric}: {row[metric]:.4f}") | |
print("\n=== ТОП-5 ЛУЧШИХ КОМБИНАЦИЙ ===") | |
for i, row in best_df.head(5).iterrows(): | |
print(f"\n#{i+1}: {row['model']}, w={row['words_per_chunk']}, o={row['overlap_words']}, top_n={row['top_n']}") | |
# Выводим значения метрик | |
recall_metrics = [col for col in best_df.columns if 'recall' in col and col != 'combined_recall'] | |
for metric in recall_metrics: | |
print(f" {metric}: {row[metric]:.4f}") | |
print("\n=======================================") | |
def create_combined_excel(combined_results: pd.DataFrame, question_metrics: pd.DataFrame, | |
macro_metrics: pd.DataFrame = None, output_file: str = "combined_results.xlsx") -> None: | |
""" | |
Создает Excel-файл с несколькими листами, содержащими различные срезы данных. | |
Добавляет автофильтры и применяет форматирование. | |
Args: | |
combined_results: DataFrame с объединенными результатами | |
question_metrics: DataFrame с метриками по вопросам | |
macro_metrics: DataFrame с macro метриками (опционально) | |
output_file: Путь к выходному Excel-файлу | |
""" | |
print(f"Создание Excel-файла {output_file}...") | |
# Создаем новый Excel-файл | |
workbook = Workbook() | |
# Удаляем стандартный лист | |
default_sheet = workbook.active | |
workbook.remove(default_sheet) | |
# Подготавливаем данные для различных листов | |
sheets_data = { | |
"Исходные данные": combined_results, | |
"Сводка по моделям": prepare_summary_by_model_top_n(combined_results, macro_metrics), | |
"Сводка по чанкингу": prepare_summary_by_chunking_params_top_n(combined_results, macro_metrics), | |
"Лучшие конфигурации": prepare_best_configurations(combined_results, macro_metrics) | |
} | |
# Если есть метрики по вопросам, добавляем лист с ними | |
if question_metrics is not None: | |
sheets_data["Метрики по вопросам"] = question_metrics | |
# Если есть macro метрики, добавляем лист с ними | |
if macro_metrics is not None: | |
sheets_data["Macro метрики"] = macro_metrics | |
# Создаем листы и добавляем данные | |
for sheet_name, data in sheets_data.items(): | |
if data is not None and not data.empty: | |
sheet = workbook.create_sheet(title=sheet_name) | |
for r in dataframe_to_rows(data, index=False, header=True): | |
sheet.append(r) | |
# Применяем форматирование | |
apply_formatting(workbook) | |
# Сохраняем файл | |
workbook.save(output_file) | |
print(f"Excel-файл создан: {output_file}") | |
def calculate_macro_metrics(question_metrics: pd.DataFrame) -> pd.DataFrame: | |
""" | |
Вычисляет macro метрики на основе результатов по вопросам. | |
Args: | |
question_metrics: DataFrame с метриками по вопросам | |
Returns: | |
DataFrame с macro метриками | |
""" | |
if question_metrics is None: | |
return None | |
print("Вычисление macro метрик на основе метрик по вопросам...") | |
# Группируем по конфигурации (модель, параметры чанкинга, top_n) | |
grouped_metrics = question_metrics.groupby(['model', 'words_per_chunk', 'overlap_words', 'top_n']) | |
# Для каждой группы вычисляем среднее значение метрик (macro) | |
macro_metrics = grouped_metrics.agg({ | |
'text_precision': 'mean', # Macro precision = среднее precision по всем вопросам | |
'text_recall': 'mean', # Macro recall = среднее recall по всем вопросам | |
'text_f1': 'mean', # Macro F1 = среднее F1 по всем вопросам | |
'doc_precision': 'mean', | |
'doc_recall': 'mean', | |
'doc_f1': 'mean' | |
}).reset_index() | |
# Добавляем префикс "macro_" к названиям метрик для ясности | |
for col in ['text_precision', 'text_recall', 'text_f1', 'doc_precision', 'doc_recall', 'doc_f1']: | |
macro_metrics.rename(columns={col: f'macro_{col}'}, inplace=True) | |
# Добавляем процент перекрытия | |
macro_metrics['overlap_percentage'] = (macro_metrics['overlap_words'] / macro_metrics['words_per_chunk'] * 100).round(1) | |
print(f"Вычислено {len(macro_metrics)} наборов macro метрик") | |
return macro_metrics | |
def main(): | |
"""Основная функция скрипта.""" | |
args = parse_args() | |
# Загружаем результаты из CSV-файлов | |
combined_results = load_results_files(args.results_dir) | |
# Загружаем метрики по вопросам (если есть) | |
question_metrics = load_question_metrics_files(args.results_dir) | |
# Вычисляем macro метрики на основе метрик по вопросам | |
macro_metrics = calculate_macro_metrics(question_metrics) | |
# Находим лучшие комбинации параметров | |
best_combinations_weighted = find_best_combinations(combined_results, ['text_recall', 'doc_recall']) | |
print_best_combinations(best_combinations_weighted) | |
if macro_metrics is not None: | |
best_combinations_macro = find_best_combinations(macro_metrics, ['macro_text_recall', 'macro_doc_recall']) | |
print_best_combinations(best_combinations_macro) | |
# Создаем объединенный Excel-файл с данными | |
create_combined_excel(combined_results, question_metrics, macro_metrics, args.output_file) | |
# Генерируем графики с помощью seaborn | |
print(f"Генерация графиков и сохранение их в директорию: {args.plots_dir}") | |
generate_plots(combined_results, macro_metrics, args.plots_dir) | |
print("Готово! Результаты сохранены в Excel и графики созданы.") | |
if __name__ == "__main__": | |
main() |