/
tsps
/
asr_research
Обзор
Документация
Войти
/
tsps
/
asr_research
Код
Запросы
0
Задачи
Вики
Пакеты
0
Релизы
0
CI/CD
Аналитика
master
src/reports.py
318 строк
12 KB
Василий Петров
СОхранение графиков метрик в DVC
07 дек 2025, 13:17
07 дек 2025, 13:17
5f68852
Код
Авторство
О чём код?
import matplotlib.pyplot as plt import numpy as np import os class MetricsReport: def __init__(self, metrics: dict, artifacts_dir: str): """ Инициализация отчёта с метриками Args: metrics (dict): Словарь метрик artifacts_dir (str): Папка для сохранения артефактов (графиков) """ if 'file_metrics' not in metrics or 'aggregated_metrics' not in metrics: raise ValueError("Должны быть ключи file_metrics/aggregated_metrics") self.file_metrics = metrics['file_metrics'] self.aggregated_metrics = metrics['aggregated_metrics'] self.error_analysis = metrics['error_analysis'] self.artifacts_dir = artifacts_dir @property def _wer_values(self): return [m['wer'] for m in self.file_metrics.values()] @property def _cer_values(self): return [m['cer'] for m in self.file_metrics.values()] @property def _filenames(self): return list(self.file_metrics.keys()) @property def _weighted_wer(self): return self.aggregated_metrics['weighted_wer'] @property def _weighted_cer(self): return self.aggregated_metrics['weighted_cer'] def print_text_summary(self) -> None: agg = self.aggregated_metrics errors = self.error_analysis print(f"Общее количество файлов: {agg['total_files']}") print(f"Общее количество слов: {agg['total_words']}") print() print(f"Взвешенный WER: {self._weighted_wer:.4f}") print(f"Взвешенный CER: {self._weighted_cer:.4f}") print() print("Лучший файл по WER:") print(f" Имя: {agg['best_wer']['filename']}") print(f" WER: {agg['best_wer']['value']:.4f}") print(f" Слов: {agg['best_wer']['word_count']}") print() print("Худший файл по WER:") print(f" Имя: {agg['worst_wer']['filename']}") print(f" WER: {agg['worst_wer']['value']:.4f}") print(f" Слов: {agg['worst_wer']['word_count']}") print() print("Лучший файл по CER:") print(f" Имя: {agg['best_cer']['filename']}") print(f" CER: {agg['best_cer']['value']:.4f}") print(f" Слов: {agg['best_cer']['word_count']}") print() print("Худший файл по CER:") print(f" Имя: {agg['worst_cer']['filename']}") print(f" CER: {agg['worst_cer']['value']:.4f}") print(f" Слов: {agg['worst_cer']['word_count']}") print() print("=== Анализ ошибок ===") # Топ подстановок print("Топ-10 подстановок (что было заменено на что):") for (original, hypothesis), count in errors['top_substitutions'][:10]: print(f" '{original}' → '{hypothesis}': {count} раз") print() # Топ вставок print("Топ-10 вставок (лишние слова):") for word, count in errors['top_insertions'][:10]: print(f" '{word}': {count} раз") print() # Топ удалений print("Топ-10 удалений (пропущенные слова):") for word, count in errors['top_deletions'][:10]: print(f" '{word}': {count} раз") def plot_wer_cer_histograms(self, filename: str = "wer_cer_histograms.png") -> None: # Переводим в проценты wer_pct = [v * 100 for v in self._wer_values] cer_pct = [v * 100 for v in self._cer_values] weighted_wer_pct = self._weighted_wer * 100 weighted_cer_pct = self._weighted_cer * 100 # Бины с шагом 5% от 0 до 100 bins = list(range(0, 105, 5)) # [0, 5, 10, ..., 100] fig, (ax1, ax2) = plt.subplots(1, 2, figsize=(10, 4)) # WER ax1.hist(wer_pct, bins=bins, edgecolor='black', alpha=0.7, color='steelblue') ax1.axvline(weighted_wer_pct, color='red', linestyle='--', label=f'Weighted WER = {weighted_wer_pct:.1f}%') ax1.set_xlabel('WER, %') ax1.set_ylabel('Количество файлов') ax1.set_title('Распределение WER') ax1.set_xlim(0, 100) ax1.set_xticks(range(0, 101, 10)) # деления каждые 10%, чтобы не перегружать ax1.legend() ax1.grid(axis='y', linestyle='--', alpha=0.7) # CER ax2.hist(cer_pct, bins=bins, edgecolor='black', alpha=0.7, color='darkorange') ax2.axvline(weighted_cer_pct, color='red', linestyle='--', label=f'Weighted CER = {weighted_cer_pct:.1f}%') ax2.set_xlabel('CER, %') ax2.set_ylabel('Количество файлов') ax2.set_title('Распределение CER') ax2.set_xlim(0, 100) ax2.set_xticks(range(0, 101, 10)) ax2.legend() ax2.grid(axis='y', linestyle='--', alpha=0.7) plt.tight_layout() os.makedirs(self.artifacts_dir, exist_ok=True) filepath = os.path.join(self.artifacts_dir, filename) plt.savefig(filepath, dpi=150, bbox_inches='tight') plt.close(fig) return filepath def plot_wer_vs_cer(self, filename: str = "wer_vs_cer.png") -> None: wer_pct = [v * 100 for v in self._wer_values] cer_pct = [v * 100 for v in self._cer_values] plt.figure(figsize=(7, 7)) plt.scatter(cer_pct, wer_pct, alpha=0.7, color='steelblue') # Линия y = x plt.plot([0, 100], [0, 100], color='gray', linestyle='--', linewidth=0.8, label='WER = CER') # Подписываем каждый файл for i, name in enumerate(self._filenames): plt.text( cer_pct[i] + 0.8, # небольшой отступ вправо wer_pct[i] - 1, name, fontsize=8, ha='left', va='bottom' ) plt.xlabel('CER, %') plt.ylabel('WER, %') plt.title('WER vs CER по файлам') plt.xlim(0, 100) plt.ylim(0, 100) plt.xticks(range(0, 101, 10)) plt.yticks(range(0, 101, 10)) plt.grid(True, linestyle='--', alpha=0.6) plt.tight_layout() # Сохраняем график os.makedirs(self.artifacts_dir, exist_ok=True) filepath = os.path.join(self.artifacts_dir, filename) plt.savefig(filepath, dpi=150, bbox_inches='tight') plt.close() return filepath def plot_wer_cer_boxplot(self, filename: str = "wer_cer_boxplot.png") -> None: wer_pct = [v * 100 for v in self._wer_values] cer_pct = [v * 100 for v in self._cer_values] plt.figure(figsize=(7, 6)) plt.boxplot( [wer_pct, cer_pct], tick_labels=['WER', 'CER'], patch_artist=True, medianprops=dict(color='black', linewidth=1.5), boxprops=dict(facecolor='lightgray', edgecolor='black'), flierprops=dict(marker='o', markerfacecolor='red', markersize=6, alpha=0.8) ) # Определим выбросы вручную def get_outliers(data): q1, q3 = np.percentile(data, [25, 75]) iqr = q3 - q1 lower = q1 - 1.5 * iqr upper = q3 + 1.5 * iqr return set(i for i, x in enumerate(data) if x < lower or x > upper) wer_outliers = get_outliers(wer_pct) cer_outliers = get_outliers(cer_pct) # Подписываем выбросы y_offsets = {} for i in wer_outliers: y = wer_pct[i] key = ('WER', y) offset = y_offsets.get(key, 1.5) plt.text(1, y + offset, self._filenames[i], fontsize=8, ha='left', va='bottom', color='darkred') y_offsets[key] = offset + 2 # избегаем наложения при одинаковых значениях for i in cer_outliers: y = cer_pct[i] key = ('CER', y) offset = y_offsets.get(key, 1.5) plt.text(2, y + offset, self._filenames[i], fontsize=8, ha='left', va='bottom', color='darkred') y_offsets[key] = offset + 2 plt.ylabel('Значение метрики, %') plt.title('Box plot: WER и CER (выбросы подписаны)') plt.ylim(0, 100) plt.grid(axis='y', linestyle='--', alpha=0.7) plt.tight_layout() # Сохраняем os.makedirs(self.artifacts_dir, exist_ok=True) filepath = os.path.join(self.artifacts_dir, filename) plt.savefig(filepath, dpi=150, bbox_inches='tight') plt.close() return filepath def plot_top5_worst_wer_cer(self, filename: str = "top5_worst_wer_cer.png") -> None: # Взвешенные значения (уже в долях, переводим в %) weighted_wer = self._weighted_wer * 100 weighted_cer = self._weighted_cer * 100 top5_wer = self._top_k_by_metric('wer') top5_cer = self._top_k_by_metric('cer') # Данные для WER filenames_wer = [name for name, _ in top5_wer] wer_values = [m['wer'] * 100 for _, m in top5_wer] # Данные для CER filenames_cer = [name for name, _ in top5_cer] cer_values = [m['cer'] * 100 for _, m in top5_cer] display_wer = [self._shorten_filename(name) for name in filenames_wer] display_cer = [self._shorten_filename(name) for name in filenames_cer] fig, (ax1, ax2) = plt.subplots(1, 2, figsize=(10, 4)) # Рисуем WER self._plot_top_k_bar( ax1, display_wer, wer_values, weighted_wer, 'WER', color='salmon', line_color='red' ) # Рисуем CER self._plot_top_k_bar( ax2, display_cer, cer_values, weighted_cer, 'CER', color='skyblue', line_color='blue' ) plt.tight_layout() # Сохраняем os.makedirs(self.artifacts_dir, exist_ok=True) filepath = os.path.join(self.artifacts_dir, filename) plt.savefig(filepath, dpi=150, bbox_inches='tight') plt.close(fig) return filepath def _shorten_filename(self, name: str) -> str: return '...' + name[-22:] if len(name) > 25 else name def _top_k_by_metric(self, metric_key: str, k: int = 5): return sorted( self.file_metrics.items(), key=lambda item: item[1][metric_key], reverse=True )[:k] def _plot_top_k_bar(self, ax, display_names, values, weighted_value, metric_name, color, line_color): bars = ax.bar(display_names, values, color=color, edgecolor='black', alpha=0.8) ax.axhline( weighted_value, color=line_color, linestyle='--', linewidth=1.2, label=f'Weighted {metric_name} = {weighted_value:.1f}%' ) ax.set_xlabel('Файл') ax.set_ylabel(f'{metric_name}, %') ax.set_title(f'ТОП-5 худших файлов по {metric_name}') ax.set_ylim(0, 100) ax.set_xticks(range(len(display_names))) ax.set_xticklabels(display_names, rotation=30, ha='right') ax.grid(axis='y', linestyle='--', alpha=0.7) ax.legend() # Подпись значений над столбцами for bar in bars: ax.text( bar.get_x() + bar.get_width() / 2, bar.get_height() + 1, f'{bar.get_height():.1f}%', ha='center', va='bottom', fontsize=8 )