Spaces:
Build error
Build error
Для деплоя на HF
Browse files- .~lock.kw_questions_tested.csv# +0 -1
- classifier_app.py +4 -4
- model_info.json +6 -6
- sdc_classifier.py +73 -36
.~lock.kw_questions_tested.csv#
DELETED
|
@@ -1 +0,0 @@
|
|
| 1 |
-
,docsa,docsa-HP-ProBook-450-G7,03.02.2025 12:17,file:///home/docsa/.config/libreoffice/4;
|
|
|
|
|
|
classifier_app.py
CHANGED
|
@@ -19,7 +19,7 @@ class Config:
|
|
| 19 |
DEFAULT_SIGNATURES_FILE: str = "signatures.npz"
|
| 20 |
CACHE_FILE: str = "embeddings_cache.db"
|
| 21 |
MODEL_INFO_FILE: str = "model_info.json"
|
| 22 |
-
DEFAULT_OPENAI_MODELS: List[str] = field(default_factory=lambda: ["text-embedding-3-large"
|
| 23 |
DEFAULT_LOCAL_MODEL: str = "cambridgeltl/SapBERT-from-PubMedBERT-fulltext"
|
| 24 |
|
| 25 |
config = Config()
|
|
@@ -33,8 +33,8 @@ class ClassifierApp:
|
|
| 33 |
"classes_info": {},
|
| 34 |
"errors": []
|
| 35 |
}
|
| 36 |
-
self.model_type = "Local" # Додати цей рядок
|
| 37 |
-
|
| 38 |
|
| 39 |
def initialize_environment(self) -> Tuple[Dict, Optional[SDCClassifier]]:
|
| 40 |
"""Ініціалізація середовища при першому запуску"""
|
|
@@ -56,7 +56,7 @@ class ClassifierApp:
|
|
| 56 |
with open(config.MODEL_INFO_FILE, 'r') as f:
|
| 57 |
model_info = json.load(f)
|
| 58 |
if not model_info.get('using_local', True):
|
| 59 |
-
signatures_model = "text-embedding-3-
|
| 60 |
|
| 61 |
# Створюємо класифікатор з тією ж моделлю
|
| 62 |
self.classifier = SDCClassifier(openai_api_key=os.getenv("OPENAI_API_KEY"))
|
|
|
|
| 19 |
DEFAULT_SIGNATURES_FILE: str = "signatures.npz"
|
| 20 |
CACHE_FILE: str = "embeddings_cache.db"
|
| 21 |
MODEL_INFO_FILE: str = "model_info.json"
|
| 22 |
+
DEFAULT_OPENAI_MODELS: List[str] = field(default_factory=lambda: ["text-embedding-3-large"])
|
| 23 |
DEFAULT_LOCAL_MODEL: str = "cambridgeltl/SapBERT-from-PubMedBERT-fulltext"
|
| 24 |
|
| 25 |
config = Config()
|
|
|
|
| 33 |
"classes_info": {},
|
| 34 |
"errors": []
|
| 35 |
}
|
| 36 |
+
# self.model_type = "Local" # Додати цей рядок
|
| 37 |
+
self.model_type = "OpenAI" # Нова версія
|
| 38 |
|
| 39 |
def initialize_environment(self) -> Tuple[Dict, Optional[SDCClassifier]]:
|
| 40 |
"""Ініціалізація середовища при першому запуску"""
|
|
|
|
| 56 |
with open(config.MODEL_INFO_FILE, 'r') as f:
|
| 57 |
model_info = json.load(f)
|
| 58 |
if not model_info.get('using_local', True):
|
| 59 |
+
signatures_model = "text-embedding-3-large" # Модель, яка використовувалась
|
| 60 |
|
| 61 |
# Створюємо класифікатор з тією ж моделлю
|
| 62 |
self.classifier = SDCClassifier(openai_api_key=os.getenv("OPENAI_API_KEY"))
|
model_info.json
CHANGED
|
@@ -3,15 +3,15 @@
|
|
| 3 |
"classes_count": 358,
|
| 4 |
"signatures_count": 358,
|
| 5 |
"cache_stats": {
|
| 6 |
-
"total_entries":
|
| 7 |
-
"cache_size_mb":
|
| 8 |
-
"hits":
|
| 9 |
-
"misses":
|
| 10 |
-
"hit_rate_percent":
|
| 11 |
},
|
| 12 |
"local_model": {
|
| 13 |
"model_name": "cambridgeltl/SapBERT-from-PubMedBERT-fulltext",
|
| 14 |
-
"device": "
|
| 15 |
"embedding_size": 768,
|
| 16 |
"max_length": 512,
|
| 17 |
"batch_size": 32
|
|
|
|
| 3 |
"classes_count": 358,
|
| 4 |
"signatures_count": 358,
|
| 5 |
"cache_stats": {
|
| 6 |
+
"total_entries": 29633,
|
| 7 |
+
"cache_size_mb": 179.21,
|
| 8 |
+
"hits": 0,
|
| 9 |
+
"misses": 0,
|
| 10 |
+
"hit_rate_percent": 0
|
| 11 |
},
|
| 12 |
"local_model": {
|
| 13 |
"model_name": "cambridgeltl/SapBERT-from-PubMedBERT-fulltext",
|
| 14 |
+
"device": "cpu",
|
| 15 |
"embedding_size": 768,
|
| 16 |
"max_length": 512,
|
| 17 |
"batch_size": 32
|
sdc_classifier.py
CHANGED
|
@@ -11,6 +11,7 @@ class SDCClassifier:
|
|
| 11 |
def __init__(self,
|
| 12 |
openai_api_key: str = None,
|
| 13 |
cache_path: str = "embeddings_cache.db",
|
|
|
|
| 14 |
local_model: str = "cambridgeltl/SapBERT-from-PubMedBERT-fulltext",
|
| 15 |
device: str = None):
|
| 16 |
"""
|
|
@@ -134,34 +135,47 @@ class SDCClassifier:
|
|
| 134 |
except (FileNotFoundError, IOError):
|
| 135 |
return None
|
| 136 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 137 |
def get_embedding(self, text: str, model_name: str = None) -> list:
|
| 138 |
"""
|
| 139 |
Отримання ембедінгу тексту
|
| 140 |
|
| 141 |
Args:
|
| 142 |
text: текст для ембедінгу
|
| 143 |
-
model_name: назва моделі (OpenAI) або None для
|
| 144 |
|
| 145 |
Returns:
|
| 146 |
list: ембедінг тексту
|
| 147 |
"""
|
| 148 |
# Перевіряємо кеш
|
| 149 |
-
|
|
|
|
| 150 |
if cached_embedding is not None:
|
| 151 |
return cached_embedding.tolist()
|
| 152 |
|
| 153 |
# Отримуємо ембедінг
|
| 154 |
-
if self.using_local
|
| 155 |
embedding = self.local_embedder.get_embeddings(text)[0]
|
| 156 |
else:
|
| 157 |
response = self.client.embeddings.create(
|
| 158 |
input=text,
|
| 159 |
-
model=model_name or "text-embedding-3-large"
|
| 160 |
)
|
| 161 |
embedding = response.data[0].embedding
|
| 162 |
|
| 163 |
# Зберігаємо в кеш
|
| 164 |
-
self.cache.put(text,
|
| 165 |
|
| 166 |
return embedding
|
| 167 |
|
|
@@ -430,8 +444,8 @@ class SDCClassifier:
|
|
| 430 |
|
| 431 |
with open(path, 'w', encoding='utf-8') as f:
|
| 432 |
json.dump(info, f, indent=2)
|
| 433 |
-
|
| 434 |
-
def evaluate_classification(self, csv_path: str, threshold: float = 0.3) -> pd.DataFrame:
|
| 435 |
"""
|
| 436 |
Оцінка класифікації текстів з CSV файлу
|
| 437 |
|
|
@@ -440,50 +454,73 @@ class SDCClassifier:
|
|
| 440 |
threshold: поріг впевненості для класифікації
|
| 441 |
|
| 442 |
Returns:
|
| 443 |
-
pd.DataFrame: результати класифікації
|
| 444 |
"""
|
| 445 |
if self.class_signatures is None:
|
| 446 |
raise ValueError("Спочатку збудуйте signatures!")
|
| 447 |
|
| 448 |
# Завантаження даних
|
|
|
|
| 449 |
df = pd.read_csv(csv_path)
|
| 450 |
if not {'Category', 'Question'}.issubset(df.columns):
|
| 451 |
raise ValueError("CSV повинен містити колонки 'Category' та 'Question'")
|
| 452 |
|
| 453 |
# Підготовка результатів
|
| 454 |
results = []
|
|
|
|
|
|
|
|
|
|
| 455 |
|
| 456 |
for idx, row in df.iterrows():
|
| 457 |
-
#
|
| 458 |
-
|
| 459 |
-
|
| 460 |
-
# Нормалізуємо якщо потрібно
|
| 461 |
-
if self.embeddings_mean is not None and self.embeddings_std is not None and not self.using_local:
|
| 462 |
-
emb = (emb - self.embeddings_mean) / self.embeddings_std
|
| 463 |
|
| 464 |
-
|
| 465 |
-
|
| 466 |
-
|
| 467 |
-
|
| 468 |
-
|
| 469 |
-
|
| 470 |
-
|
| 471 |
-
|
| 472 |
-
|
| 473 |
-
|
| 474 |
-
|
| 475 |
-
|
| 476 |
-
|
| 477 |
-
|
| 478 |
-
|
| 479 |
-
|
| 480 |
-
|
| 481 |
-
|
| 482 |
-
|
| 483 |
-
|
| 484 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 485 |
|
| 486 |
-
return
|
| 487 |
|
| 488 |
def save_evaluation_results(self, df: pd.DataFrame, output_path: str = "evaluation_results.csv") -> str:
|
| 489 |
"""
|
|
|
|
| 11 |
def __init__(self,
|
| 12 |
openai_api_key: str = None,
|
| 13 |
cache_path: str = "embeddings_cache.db",
|
| 14 |
+
openai_model = None, # Модель OpenAI за замовчуванням
|
| 15 |
local_model: str = "cambridgeltl/SapBERT-from-PubMedBERT-fulltext",
|
| 16 |
device: str = None):
|
| 17 |
"""
|
|
|
|
| 135 |
except (FileNotFoundError, IOError):
|
| 136 |
return None
|
| 137 |
|
| 138 |
+
def set_openai_model(self, model_name: str) -> None:
|
| 139 |
+
"""
|
| 140 |
+
Встановлює модель OpenAI для використання
|
| 141 |
+
|
| 142 |
+
Args:
|
| 143 |
+
model_name: назва моделі OpenAI
|
| 144 |
+
"""
|
| 145 |
+
print(f"Встановлення OpenAI моделі: {model_name}")
|
| 146 |
+
self.using_local = False
|
| 147 |
+
self.local_embedder = None # Видаляємо локальний ембедер
|
| 148 |
+
self.openai_model = model_name # Зберігаємо назву моделі
|
| 149 |
+
|
| 150 |
def get_embedding(self, text: str, model_name: str = None) -> list:
|
| 151 |
"""
|
| 152 |
Отримання ембедінгу тексту
|
| 153 |
|
| 154 |
Args:
|
| 155 |
text: текст для ембедінгу
|
| 156 |
+
model_name: назва моделі (OpenAI) або None для використання поточної
|
| 157 |
|
| 158 |
Returns:
|
| 159 |
list: ембедінг тексту
|
| 160 |
"""
|
| 161 |
# Перевіряємо кеш
|
| 162 |
+
model_key = model_name or (self.openai_model if not self.using_local else "local")
|
| 163 |
+
cached_embedding = self.cache.get(text, model_key)
|
| 164 |
if cached_embedding is not None:
|
| 165 |
return cached_embedding.tolist()
|
| 166 |
|
| 167 |
# Отримуємо ембедінг
|
| 168 |
+
if self.using_local:
|
| 169 |
embedding = self.local_embedder.get_embeddings(text)[0]
|
| 170 |
else:
|
| 171 |
response = self.client.embeddings.create(
|
| 172 |
input=text,
|
| 173 |
+
model=model_name or self.openai_model or "text-embedding-3-large"
|
| 174 |
)
|
| 175 |
embedding = response.data[0].embedding
|
| 176 |
|
| 177 |
# Зберігаємо в кеш
|
| 178 |
+
self.cache.put(text, model_key, embedding)
|
| 179 |
|
| 180 |
return embedding
|
| 181 |
|
|
|
|
| 444 |
|
| 445 |
with open(path, 'w', encoding='utf-8') as f:
|
| 446 |
json.dump(info, f, indent=2)
|
| 447 |
+
|
| 448 |
+
def evaluate_classification(self, csv_path: str, threshold: float = 0.3) -> tuple[pd.DataFrame, dict]:
|
| 449 |
"""
|
| 450 |
Оцінка класифікації текстів з CSV файлу
|
| 451 |
|
|
|
|
| 454 |
threshold: поріг впевненості для класифікації
|
| 455 |
|
| 456 |
Returns:
|
| 457 |
+
tuple[pd.DataFrame, dict]: результати класифікації та статистика
|
| 458 |
"""
|
| 459 |
if self.class_signatures is None:
|
| 460 |
raise ValueError("Спочатку збудуйте signatures!")
|
| 461 |
|
| 462 |
# Завантаження даних
|
| 463 |
+
print(f"\nЗавантаження даних з {csv_path}...")
|
| 464 |
df = pd.read_csv(csv_path)
|
| 465 |
if not {'Category', 'Question'}.issubset(df.columns):
|
| 466 |
raise ValueError("CSV повинен містити колонки 'Category' та 'Question'")
|
| 467 |
|
| 468 |
# Підготовка результатів
|
| 469 |
results = []
|
| 470 |
+
total = len(df)
|
| 471 |
+
print(f"Знайдено {total} рядків для класифікації")
|
| 472 |
+
print(f"Використовується {'OpenAI' if not self.using_local else 'локальна'} модель")
|
| 473 |
|
| 474 |
for idx, row in df.iterrows():
|
| 475 |
+
if idx % 10 == 0: # Логуємо прогрес кожні 10 рядків
|
| 476 |
+
print(f"Обробка рядка {idx + 1}/{total}")
|
|
|
|
|
|
|
|
|
|
|
|
|
| 477 |
|
| 478 |
+
try:
|
| 479 |
+
# Отримуємо ембедінг для питання
|
| 480 |
+
emb = np.array(self.get_embedding(row['Question']))
|
| 481 |
+
|
| 482 |
+
# Нормалізуємо ембедінг
|
| 483 |
+
emb_norm = np.linalg.norm(emb)
|
| 484 |
+
if emb_norm > 0:
|
| 485 |
+
emb = emb / emb_norm
|
| 486 |
+
|
| 487 |
+
# Отримуємо всі передбачення
|
| 488 |
+
predictions = self.predict_classes(emb, threshold)
|
| 489 |
+
|
| 490 |
+
# Формуємо список класів за рівнем впевненості
|
| 491 |
+
sorted_classes = list(predictions.keys())
|
| 492 |
+
|
| 493 |
+
# Знаходимо позицію очікуваного класу
|
| 494 |
+
expected_class = row['Category']
|
| 495 |
+
expected_position = sorted_classes.index(expected_class) + 1 if expected_class in sorted_classes else -1
|
| 496 |
+
|
| 497 |
+
# Отримуємо рівень впевненості для очікуваного класу
|
| 498 |
+
expected_confidence = predictions.get(expected_class, 0.0)
|
| 499 |
+
|
| 500 |
+
# Додаємо результат
|
| 501 |
+
results.append({
|
| 502 |
+
'Category': row['Category'],
|
| 503 |
+
'Question': row['Question'],
|
| 504 |
+
'ExpectedClassPosition': expected_position,
|
| 505 |
+
'ExpectedClassConfidence': expected_confidence,
|
| 506 |
+
'ClassificationResults': json.dumps(predictions, ensure_ascii=False)
|
| 507 |
+
})
|
| 508 |
+
|
| 509 |
+
except Exception as e:
|
| 510 |
+
print(f"Помилка при обробці рядка {idx + 1}: {str(e)}")
|
| 511 |
+
results.append({
|
| 512 |
+
'Category': row['Category'],
|
| 513 |
+
'Question': row['Question'],
|
| 514 |
+
'ExpectedClassPosition': -1,
|
| 515 |
+
'ExpectedClassConfidence': 0.0,
|
| 516 |
+
'ClassificationResults': json.dumps({})
|
| 517 |
+
})
|
| 518 |
+
|
| 519 |
+
print("\nОбробка завершена")
|
| 520 |
+
results_df = pd.DataFrame(results)
|
| 521 |
+
statistics = self.get_evaluation_statistics(results_df)
|
| 522 |
|
| 523 |
+
return results_df, statistics
|
| 524 |
|
| 525 |
def save_evaluation_results(self, df: pd.DataFrame, output_path: str = "evaluation_results.csv") -> str:
|
| 526 |
"""
|