Spaces:
Sleeping
Sleeping
File size: 6,475 Bytes
f7bde18 | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 | import pandas as pd
import numpy as np
import torch
from transformers import pipeline, AutoTokenizer, AutoModelForCausalLM
from sentence_transformers import SentenceTransformer
import faiss
import os
import sys
class SentimentRAG:
_instance = None
def __new__(cls, *args, **kwargs):
if not cls._instance:
cls._instance = super(SentimentRAG, cls).__new__(cls)
return cls._instance
def __init__(self, data_path="data/digikala_samples.csv", index_path=None):
if hasattr(self, 'initialized') and self.initialized:
return
if index_path is None:
if data_path == "data/digikala_samples.csv":
index_path = "data/faiss_index.bin"
else:
index_path = data_path.replace(".csv", ".bin")
print(f"Initializing SentimentRAG models (Online Optimized)...")
torch.set_num_threads(2)
self.device = "cuda" if torch.cuda.is_available() else "cpu"
hf_token = os.getenv("HUGGINGFACE_TOKEN")
if hf_token == "": hf_token = None
# 1. Sentiment Model
self.sentiment_pipe = pipeline(
"sentiment-analysis",
model="nlptown/bert-base-multilingual-uncased-sentiment",
device=-1 if self.device == "cpu" else 0,
token=hf_token if hf_token else None,
model_kwargs={"low_cpu_mem_usage": True} if self.device == "cpu" else {}
)
# 2. Embedding Model
self.embed_model = SentenceTransformer(
'sentence-transformers/paraphrase-multilingual-MiniLM-L12-v2',
use_auth_token=hf_token if hf_token else None
)
# 3. Reasoning Model
self.gen_tokenizer = AutoTokenizer.from_pretrained("HooshvareLab/gpt2-fa-comment", token=hf_token if hf_token else None)
self.gen_model = AutoModelForCausalLM.from_pretrained(
"HooshvareLab/gpt2-fa-comment",
token=hf_token if hf_token else None,
low_cpu_mem_usage=True
).to(self.device)
# Load Data & Index
self._load_resources(data_path, index_path)
self.initialized = True
def _load_resources(self, data_path, index_path):
self.df = None
self.texts = []
# Priority 1: Local file (if exists and is not LFS pointer)
if os.path.exists(data_path):
try:
temp_df = pd.read_csv(data_path)
if len(temp_df) > 0 and 'version https://git-lfs' in str(temp_df.columns[0]):
print("Found LFS pointer. Skipping local load.")
else:
self.df = temp_df
self.texts = self.df['text'].tolist()
print(f"Loaded {len(self.texts)} samples from local file.")
except Exception as e:
print(f"Local CSV load failed: {e}")
# Priority 2: Online streaming fallback
if self.df is None:
print("Fetching data from Hugging Face Hub (Streaming)...")
try:
from prepare_data import fetch_all_data
self.df = fetch_all_data()
if self.df is not None:
self.texts = self.df['text'].tolist()
print(f"Streamed {len(self.texts)} samples online.")
except Exception as e:
print(f"Online data streaming failed: {e}")
if self.df is None:
raise FileNotFoundError("System failed to load any data (Local/Online).")
# FAISS Index Handling
if os.path.exists(index_path):
try:
loaded_index = faiss.read_index(index_path)
if loaded_index.ntotal == len(self.texts):
self.index = loaded_index
print("Loaded pre-generated FAISS index.")
return
except Exception:
pass
print("Building FAISS index in memory...")
self._build_index()
def _build_index(self):
embeddings = self.embed_model.encode(self.texts, show_progress_bar=False)
self.index = faiss.IndexFlatL2(embeddings.shape[1])
self.index.add(np.array(embeddings).astype('float32'))
def get_sentiment(self, text):
result = self.sentiment_pipe(text[:512])[0]
score = int(result['label'].split()[0])
return score, result['score']
def retrieve_similar(self, text, k=2):
k = min(k, len(self.texts))
if k <= 0: return []
query_vec = self.embed_model.encode([text])
distances, indices = self.index.search(np.array(query_vec).astype('float32'), k)
return [self.texts[i] for i in indices[0]]
def generate_explanation(self, text, sentiment_score):
similar_comments = self.retrieve_similar(text, k=2)
context = " ".join([f"نمونه: {c[:60]}" for c in similar_comments])
sentiment_label = "مثبت" if sentiment_score > 3 else "منفی" if sentiment_score < 3 else "خنثی"
prompt = f"متن: {text[:100]}\nاحساس: {sentiment_label}\nشواهد: {context}\nدلیل فنی:"
inputs = self.gen_tokenizer(prompt, return_tensors="pt", truncation=True, max_length=400).to(self.device)
with torch.no_grad():
outputs = self.gen_model.generate(
**inputs,
max_new_tokens=50,
do_sample=True,
top_p=0.9,
temperature=0.7,
pad_token_id=self.gen_tokenizer.eos_token_id
)
full_text = self.gen_tokenizer.decode(outputs[0], skip_special_tokens=True)
if "دلیل فنی:" in full_text:
explanation = full_text.split("دلیل فنی:")[-1].strip()
else:
explanation = "تحلیل بر اساس الگوهای متنی مشابه در پایگاه داده دیجیکالا انجام شده است."
return explanation if len(explanation) > 10 else "این نظر به دلیل شباهت با نظرات ثبت شده قبلی و الگوهای کلامی شناسایی شده، دارای بار احساسی مشخص شده است."
if __name__ == "__main__":
rag = SentimentRAG()
test_text = "کیفیتش خوبه ولی قیمتش بالاست"
score, conf = rag.get_sentiment(test_text)
print(f"Sentiment: {score}, Confidence: {conf}")
print(f"Reason: {rag.generate_explanation(test_text, score)}")
|