116 lines
5.0 KiB
Python
116 lines
5.0 KiB
Python
|
|
import re
|
||
|
|
import numpy as np
|
||
|
|
from sentence_transformers import SentenceTransformer
|
||
|
|
from sklearn.metrics.pairwise import cosine_similarity
|
||
|
|
|
||
|
|
|
||
|
|
class Segmenter:
|
||
|
|
def __init__(self):
|
||
|
|
print("Loading models :")
|
||
|
|
|
||
|
|
self.model_bge = SentenceTransformer("C:/Models/bge-m3", model_kwargs={"use_safetensors": False})
|
||
|
|
self.model_e5 = SentenceTransformer("C:/Models/multilingual-e5-large")
|
||
|
|
self.model_labse = SentenceTransformer("C:/Models/LaBSE_Local")
|
||
|
|
self.model_parsroberta = SentenceTransformer("C:/Models/ParsRoberta")
|
||
|
|
|
||
|
|
print("4 models loaded successfully")
|
||
|
|
|
||
|
|
def get_similarities(self, text1, text2):
|
||
|
|
"""محاسبه میانگین شباهت کسینوسی از ۴ مدل برای دو متن داده شده"""
|
||
|
|
emb_bge = self.model_bge.encode([text1, text2])
|
||
|
|
emb_e5 = self.model_e5.encode([text1, text2])
|
||
|
|
emb_labse = self.model_labse.encode([text1, text2])
|
||
|
|
emb_parsroberta = self.model_parsroberta.encode([text1, text2])
|
||
|
|
|
||
|
|
sim_bge = cosine_similarity([emb_bge[0]], [emb_bge[1]])[0][0]
|
||
|
|
sim_e5 = cosine_similarity([emb_e5[0]], [emb_e5[1]])[0][0]
|
||
|
|
sim_labse = cosine_similarity([emb_labse[0]], [emb_labse[1]])[0][0]
|
||
|
|
sim_parsroberta = cosine_similarity([emb_parsroberta[0]], [emb_parsroberta[1]])[0][0]
|
||
|
|
|
||
|
|
# میانگینگیری از هر ۴ مدل
|
||
|
|
return np.mean([sim_bge, sim_e5, sim_labse, sim_parsroberta])
|
||
|
|
|
||
|
|
def calculate_depth_scores(self, similarities):
|
||
|
|
"""محاسبه عمق درهها (Gap Scores) با استفاده از قلههای چپ و راست"""
|
||
|
|
depth_scores = []
|
||
|
|
n = len(similarities)
|
||
|
|
|
||
|
|
for i in range(n):
|
||
|
|
left_peak = similarities[i]
|
||
|
|
for j in range(i, -1, -1):
|
||
|
|
if similarities[j] >= left_peak:
|
||
|
|
left_peak = similarities[j]
|
||
|
|
else:
|
||
|
|
break
|
||
|
|
|
||
|
|
right_peak = similarities[i]
|
||
|
|
for j in range(i, n):
|
||
|
|
if similarities[j] >= right_peak:
|
||
|
|
right_peak = similarities[j]
|
||
|
|
else:
|
||
|
|
break
|
||
|
|
|
||
|
|
depth = (left_peak - similarities[i]) + (right_peak - similarities[i])
|
||
|
|
depth_scores.append(depth)
|
||
|
|
|
||
|
|
return depth_scores
|
||
|
|
|
||
|
|
def extract_sentences(self, text):
|
||
|
|
"""جداسازی متن اولیه به جملات"""
|
||
|
|
text = text.replace('\n', ' ')
|
||
|
|
sentences = [s.strip() for s in re.split(r'(?<=[.!?؟]) +', text) if s.strip()]
|
||
|
|
return sentences
|
||
|
|
|
||
|
|
def segment_items(self, items, force_count=None, std_multiplier=0.5, min_depth=0.02):
|
||
|
|
"""
|
||
|
|
دریافت جملات یا واحدهای معنایی و کلاسترینگ آنها.
|
||
|
|
اگر پارامتر force_count ارسال شود، الگوریتم موظف است دقیقاً همان تعداد سگمنت تولید کند.
|
||
|
|
"""
|
||
|
|
n_items = len(items)
|
||
|
|
if n_items < 2:
|
||
|
|
return [items]
|
||
|
|
|
||
|
|
similarities = []
|
||
|
|
for i in range(n_items - 1):
|
||
|
|
sim = self.get_similarities(items[i], items[i + 1])
|
||
|
|
similarities.append(sim)
|
||
|
|
|
||
|
|
depth_scores = self.calculate_depth_scores(similarities)
|
||
|
|
|
||
|
|
# -------------------------------------------------------------
|
||
|
|
# حالت اجباری: رساندن تعداد سگمنتها به عدد دقیق (مثلا 7)
|
||
|
|
# -------------------------------------------------------------
|
||
|
|
if force_count and 1 < force_count < n_items:
|
||
|
|
# پیدا کردن عمیقترین درهها برای ایجاد دقیقا force_count - 1 برش
|
||
|
|
top_cut_indices = np.argsort(depth_scores)[-(force_count - 1):]
|
||
|
|
top_cut_indices = sorted(top_cut_indices)
|
||
|
|
|
||
|
|
grouped_segments = []
|
||
|
|
start = 0
|
||
|
|
for idx in top_cut_indices:
|
||
|
|
grouped_segments.append(items[start:idx + 1])
|
||
|
|
start = idx + 1
|
||
|
|
grouped_segments.append(items[start:])
|
||
|
|
return grouped_segments
|
||
|
|
|
||
|
|
# -------------------------------------------------------------
|
||
|
|
# حالت عادی داینامیک: در سطوح پایینتر که هنوز به تعداد هدف نرسیدهایم
|
||
|
|
# -------------------------------------------------------------
|
||
|
|
mean_depth = np.mean(depth_scores)
|
||
|
|
std_depth = np.std(depth_scores)
|
||
|
|
cutoff_threshold = mean_depth + (std_multiplier * std_depth)
|
||
|
|
|
||
|
|
grouped_segments = []
|
||
|
|
current_segment = [items[0]]
|
||
|
|
|
||
|
|
for i in range(len(similarities)):
|
||
|
|
if depth_scores[i] >= cutoff_threshold and depth_scores[i] > min_depth:
|
||
|
|
grouped_segments.append(current_segment)
|
||
|
|
current_segment = [items[i + 1]]
|
||
|
|
else:
|
||
|
|
current_segment.append(items[i + 1])
|
||
|
|
|
||
|
|
if current_segment:
|
||
|
|
grouped_segments.append(current_segment)
|
||
|
|
|
||
|
|
return grouped_segments
|