nlp-segment-analysis / processor.py
GitHub Actions
chore: code and dataset deploy [skip ci]
f7bde18
Raw
History Blame Contribute Delete
6.48 kB
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)}")