PerSent 0.1.2__tar.gz → 0.1.3__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Potentially problematic release.
This version of PerSent might be problematic. Click here for more details.
- {persent-0.1.2 → persent-0.1.3}/PKG-INFO +1 -1
- {persent-0.1.2 → persent-0.1.3}/PerSent/__init__.py +1 -1
- {persent-0.1.2 → persent-0.1.3}/PerSent/analyzer.py +61 -36
- {persent-0.1.2 → persent-0.1.3}/PerSent.egg-info/PKG-INFO +1 -1
- {persent-0.1.2 → persent-0.1.3}/setup.py +1 -1
- {persent-0.1.2 → persent-0.1.3}/LICENSE.txt +0 -0
- {persent-0.1.2 → persent-0.1.3}/PerSent.egg-info/SOURCES.txt +0 -0
- {persent-0.1.2 → persent-0.1.3}/PerSent.egg-info/dependency_links.txt +0 -0
- {persent-0.1.2 → persent-0.1.3}/PerSent.egg-info/requires.txt +0 -0
- {persent-0.1.2 → persent-0.1.3}/PerSent.egg-info/top_level.txt +0 -0
- {persent-0.1.2 → persent-0.1.3}/README.md +0 -0
- {persent-0.1.2 → persent-0.1.3}/setup.cfg +0 -0
- {persent-0.1.2 → persent-0.1.3}/tests/test_basic.py +0 -0
|
@@ -1,13 +1,16 @@
|
|
|
1
1
|
import pandas as pd
|
|
2
2
|
from hazm import Normalizer, word_tokenize, Stemmer, stopwords_list
|
|
3
3
|
import re
|
|
4
|
-
from tqdm import tqdm
|
|
4
|
+
from tqdm import tqdm
|
|
5
5
|
from gensim.models import Word2Vec
|
|
6
6
|
import numpy as np
|
|
7
7
|
from sklearn.linear_model import LogisticRegression
|
|
8
8
|
from sklearn.model_selection import train_test_split
|
|
9
9
|
import os
|
|
10
10
|
import joblib
|
|
11
|
+
from transformers import AutoModelForSequenceClassification, AutoTokenizer
|
|
12
|
+
import torch
|
|
13
|
+
from torch.nn.functional import softmax
|
|
11
14
|
|
|
12
15
|
class PersianSentimentAnalyzer:
|
|
13
16
|
def __init__(self, model_dir='persian_sentiment/model'):
|
|
@@ -17,19 +20,18 @@ class PersianSentimentAnalyzer:
|
|
|
17
20
|
self.model_dir = model_dir
|
|
18
21
|
self.vectorizer = None
|
|
19
22
|
self.classifier = None
|
|
20
|
-
self.emotion_keywords = {
|
|
21
|
-
'Fear': ['ترس', 'وحشت', 'هراس'],
|
|
22
|
-
'Surprise': ['تعجب', 'شگفت', 'حیرت'],
|
|
23
|
-
'Anger': ['خشم', 'عصبانیت', 'خشونت'],
|
|
24
|
-
'Contempt': ['تحقیر', 'توهین', 'حقارت'],
|
|
25
|
-
'Joy': ['شادی', 'خوشحالی', 'خنده'],
|
|
26
|
-
'Sadness': ['غم', 'اندوه', 'ناراحتی'],
|
|
27
|
-
'Disgust': ['انزجار', 'تهوع', 'نفرت']
|
|
28
|
-
}
|
|
29
23
|
|
|
30
|
-
|
|
24
|
+
# مدل تحلیل احساسات
|
|
25
|
+
self.emotion_tokenizer = AutoTokenizer.from_pretrained(
|
|
26
|
+
"HooshvareLab/bert-fa-base-uncased-emotion")
|
|
27
|
+
self.emotion_model = AutoModelForSequenceClassification.from_pretrained(
|
|
28
|
+
"HooshvareLab/bert-fa-base-uncased-emotion")
|
|
29
|
+
self.emotion_labels = ['ترس', 'تعجب', 'خشم', 'تحقیر', 'شادی', 'غم', 'انزجار']
|
|
31
30
|
|
|
31
|
+
os.makedirs(self.model_dir, exist_ok=True)
|
|
32
|
+
|
|
32
33
|
def _preprocess_text(self, text):
|
|
34
|
+
"""پیشپردازش متن فارسی"""
|
|
33
35
|
text = self.normalizer.normalize(str(text))
|
|
34
36
|
text = re.sub(r'[!()-\[\]{};:\'",؟<>./?@#$%^&*_~۰-۹\d]+', ' ', text)
|
|
35
37
|
text = re.sub(r'\s+', ' ', text).strip()
|
|
@@ -40,8 +42,9 @@ class PersianSentimentAnalyzer:
|
|
|
40
42
|
if token not in self.stopwords and len(token) > 1
|
|
41
43
|
]
|
|
42
44
|
return processed_tokens
|
|
43
|
-
|
|
45
|
+
|
|
44
46
|
def _sentence_vector(self, sentence, model):
|
|
47
|
+
"""تبدیل جمله به بردار با میانگین بردارهای کلمات"""
|
|
45
48
|
vectors = []
|
|
46
49
|
for word in sentence:
|
|
47
50
|
try:
|
|
@@ -49,20 +52,34 @@ class PersianSentimentAnalyzer:
|
|
|
49
52
|
except KeyError:
|
|
50
53
|
vectors.append(np.zeros(100))
|
|
51
54
|
return np.mean(vectors, axis=0) if vectors else np.zeros(100)
|
|
52
|
-
|
|
53
|
-
def _emotion_analysis(self,
|
|
54
|
-
"""تحلیل احساسات
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
55
|
+
|
|
56
|
+
def _emotion_analysis(self, text):
|
|
57
|
+
"""تحلیل احساسات با مدل BERT فارسی"""
|
|
58
|
+
inputs = self.emotion_tokenizer(
|
|
59
|
+
text,
|
|
60
|
+
return_tensors="pt",
|
|
61
|
+
truncation=True,
|
|
62
|
+
padding=True,
|
|
63
|
+
max_length=128
|
|
64
|
+
)
|
|
65
|
+
|
|
66
|
+
with torch.no_grad():
|
|
67
|
+
outputs = self.emotion_model(**inputs)
|
|
68
|
+
|
|
69
|
+
probs = softmax(outputs.logits, dim=1)
|
|
70
|
+
scores = probs.numpy()[0]
|
|
71
|
+
|
|
72
|
+
return {
|
|
73
|
+
label: round(float(score), 4)
|
|
74
|
+
for label, score in zip(self.emotion_labels, scores)
|
|
75
|
+
}
|
|
76
|
+
|
|
62
77
|
def train(self, train_csv, test_size=0.2, vector_size=100, window=5):
|
|
78
|
+
"""آموزش مدل تحلیل توصیه"""
|
|
63
79
|
df = pd.read_csv(train_csv)
|
|
64
80
|
df['tokens'] = df['body'].apply(self._preprocess_text)
|
|
65
81
|
|
|
82
|
+
# آموزش مدل Word2Vec
|
|
66
83
|
self.vectorizer = Word2Vec(
|
|
67
84
|
sentences=df['tokens'],
|
|
68
85
|
vector_size=vector_size,
|
|
@@ -71,6 +88,7 @@ class PersianSentimentAnalyzer:
|
|
|
71
88
|
workers=4
|
|
72
89
|
)
|
|
73
90
|
|
|
91
|
+
# تبدیل جملات به بردار
|
|
74
92
|
X = np.array([self._sentence_vector(s, self.vectorizer) for s in df['tokens']])
|
|
75
93
|
y = df['recommendation_status'].map({
|
|
76
94
|
"no_idea": 2,
|
|
@@ -78,29 +96,34 @@ class PersianSentimentAnalyzer:
|
|
|
78
96
|
"not_recommended": 0
|
|
79
97
|
}).values
|
|
80
98
|
|
|
99
|
+
# تقسیم دادهها و آموزش مدل
|
|
81
100
|
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=test_size)
|
|
82
|
-
|
|
83
101
|
self.classifier = LogisticRegression(max_iter=1000)
|
|
84
102
|
self.classifier.fit(X_train, y_train)
|
|
85
103
|
|
|
104
|
+
# ذخیره مدل
|
|
86
105
|
self.save_model()
|
|
87
106
|
accuracy = self.classifier.score(X_test, y_test)
|
|
88
|
-
return accuracy
|
|
89
|
-
|
|
107
|
+
return round(accuracy, 4)
|
|
108
|
+
|
|
90
109
|
def predict(self, text, mode=0):
|
|
91
|
-
"""
|
|
110
|
+
"""
|
|
111
|
+
پیشبینی احساس متن
|
|
92
112
|
|
|
93
113
|
پارامترها:
|
|
114
|
+
text (str): متن ورودی
|
|
94
115
|
mode (int):
|
|
95
116
|
0 = تحلیل توصیه (پیشفرض)
|
|
96
|
-
1 = تحلیل احساسات
|
|
117
|
+
1 = تحلیل احساسات با مدل BERT
|
|
118
|
+
|
|
119
|
+
بازگشتی:
|
|
120
|
+
dict یا str: نتایج تحلیل
|
|
97
121
|
"""
|
|
98
|
-
if not self.classifier or not self.vectorizer:
|
|
99
|
-
raise Exception("Model not trained! Call train() first or load a pretrained model.")
|
|
100
|
-
|
|
101
|
-
tokens = self._preprocess_text(text)
|
|
102
|
-
|
|
103
122
|
if mode == 0:
|
|
123
|
+
if not self.classifier or not self.vectorizer:
|
|
124
|
+
raise Exception("Recommendation model not trained! Call train() first.")
|
|
125
|
+
|
|
126
|
+
tokens = self._preprocess_text(text)
|
|
104
127
|
vector = self._sentence_vector(tokens, self.vectorizer)
|
|
105
128
|
prediction = self.classifier.predict([vector])[0]
|
|
106
129
|
return {
|
|
@@ -110,15 +133,17 @@ class PersianSentimentAnalyzer:
|
|
|
110
133
|
}[prediction]
|
|
111
134
|
|
|
112
135
|
elif mode == 1:
|
|
113
|
-
return self._emotion_analysis(
|
|
136
|
+
return self._emotion_analysis(text)
|
|
114
137
|
|
|
115
138
|
else:
|
|
116
|
-
raise ValueError("Invalid mode. Use 0 (recommendation
|
|
117
|
-
|
|
139
|
+
raise ValueError("Invalid mode. Use 0 (recommendation) or 1 (emotion)")
|
|
140
|
+
|
|
118
141
|
def save_model(self):
|
|
142
|
+
"""ذخیره مدل تحلیل توصیه"""
|
|
119
143
|
joblib.dump(self.classifier, os.path.join(self.model_dir, 'classifier.joblib'))
|
|
120
144
|
self.vectorizer.save(os.path.join(self.model_dir, 'word2vec.model'))
|
|
121
|
-
|
|
145
|
+
|
|
122
146
|
def load_model(self):
|
|
147
|
+
"""بارگذاری مدل تحلیل توصیه"""
|
|
123
148
|
self.classifier = joblib.load(os.path.join(self.model_dir, 'classifier.joblib'))
|
|
124
149
|
self.vectorizer = Word2Vec.load(os.path.join(self.model_dir, 'word2vec.model'))
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|