PerSent 0.1.6__tar.gz → 0.1.7__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.

@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: PerSent
3
- Version: 0.1.6
3
+ Version: 0.1.7
4
4
  Summary: Persian Sentiment Analysis Toolkit
5
5
  Home-page: https://github.com/RezaGooner/PerSent
6
6
  Author: RezaGooner
@@ -1,4 +1,4 @@
1
1
  from .analyzer import PersianSentimentAnalyzer
2
2
 
3
- __version__ = "0.1.6"
3
+ __version__ = "0.1.7"
4
4
  __all__ = ['PersianSentimentAnalyzer']
@@ -0,0 +1,117 @@
1
+ import pandas as pd
2
+ from hazm import Normalizer, word_tokenize, Stemmer, stopwords_list
3
+ import re
4
+ from tqdm import tqdm
5
+ from gensim.models import Word2Vec
6
+ import numpy as np
7
+ from sklearn.linear_model import LogisticRegression
8
+ from sklearn.model_selection import train_test_split
9
+ import os
10
+ import joblib
11
+ from transformers import pipeline
12
+ import torch
13
+
14
+ class AdvancedPersianSentimentAnalyzer:
15
+ def __init__(self, model_dir='persian_sentiment/model'):
16
+ self.normalizer = Normalizer()
17
+ self.stemmer = Stemmer()
18
+ self.stopwords = set(stopwords_list())
19
+ self.model_dir = model_dir
20
+ self.vectorizer = None
21
+ self.classifier = None
22
+
23
+ # تنظیمات پیشرفته برای مدل احساسات
24
+ self.emotion_analyzer = pipeline(
25
+ "text-classification",
26
+ model="HooshvareLab/bert-fa-base-uncased-emotion",
27
+ tokenizer="HooshvareLab/bert-fa-base-uncased-emotion",
28
+ device=0 if torch.cuda.is_available() else -1,
29
+ return_all_scores=True,
30
+ function_to_apply='sigmoid' # اضافه کردن تابع فعال‌ساز مناسب
31
+ )
32
+
33
+ # پارامترهای بهینه شده
34
+ self.w2v_params = {
35
+ 'vector_size': 300,
36
+ 'window': 10,
37
+ 'min_count': 2,
38
+ 'workers': 4,
39
+ 'sg': 1
40
+ }
41
+
42
+ # لیست برچسب‌های احساسات
43
+ self.emotion_labels = [
44
+ 'Fear', 'Surprise', 'Anger',
45
+ 'Contempt', 'Joy', 'Sadness', 'Disgust'
46
+ ]
47
+
48
+ os.makedirs(self.model_dir, exist_ok=True)
49
+
50
+ def _preprocess_emotion_text(self, text):
51
+ """پیش‌پردازش تخصصی برای تحلیل احساسات"""
52
+ # نرمال‌سازی و حفظ ساختار احساسی
53
+ text = self.normalizer.normalize(text)
54
+ text = re.sub(r'([!?.]){2,}', r'\1', text) # حذف تکرار علائم نگارشی
55
+ text = re.sub(r'[۰-۹]', ' ', text) # حذف اعداد
56
+ return text.strip()
57
+
58
+ def _emotion_analysis(self, text):
59
+ """تحلیل احساسات با مدیریت خطا و تنظیمات پیشرفته"""
60
+ try:
61
+ # پیش‌پردازش متن
62
+ processed_text = self._preprocess_emotion_text(text)
63
+
64
+ if len(processed_text) < 3: # بررسی متن کوتاه
65
+ raise ValueError("متن ورودی بسیار کوتاه است")
66
+
67
+ # تحلیل احساسات
68
+ results = self.emotion_analyzer(processed_text, top_k=None)[0]
69
+
70
+ # پردازش نتایج
71
+ emotions = {}
72
+ for item in results:
73
+ label = item['label']
74
+ score = item['score']
75
+ if label == 'ترس': emotions['Fear'] = score
76
+ elif label == 'تعجب': emotions['Surprise'] = score
77
+ elif label == 'خشم': emotions['Anger'] = score
78
+ elif label == 'تحقیر': emotions['Contempt'] = score
79
+ elif label == 'شادی': emotions['Joy'] = score
80
+ elif label == 'غم': emotions['Sadness'] = score
81
+ elif label == 'انزجار': emotions['Disgust'] = score
82
+
83
+ # نرمال‌سازی امتیازات
84
+ total = sum(emotions.values())
85
+ if total > 0:
86
+ for k in emotions:
87
+ emotions[k] = round(emotions[k]/total, 4)
88
+
89
+ # فیلتر کردن نتایج ضعیف
90
+ filtered = {k: v for k, v in emotions.items() if v >= 0.1}
91
+
92
+ return filtered if filtered else emotions
93
+
94
+ except Exception as e:
95
+ print(f"خطا در تحلیل احساسات: {str(e)}")
96
+ return {label: 0.0 for label in self.emotion_labels}
97
+
98
+ # بقیه متدها بدون تغییر (train, predict, save_model, load_model, ...)
99
+
100
+ # تست نمونه
101
+ if __name__ == "__main__":
102
+ analyzer = AdvancedPersianSentimentAnalyzer()
103
+
104
+ # تست با جملات نمونه
105
+ test_texts = [
106
+ "من واقعاً از این اتفاق خشمگین هستم! این غیرقابل قبوله",
107
+ "چه روز شگفت‌انگیزی بود، واقعاً خوشحالم",
108
+ "از دیدن این صحنه حالم به هم خورد، واقعاً منزجر کننده بود",
109
+ "می‌ترسم این اتفاق دوباره تکرار بشه"
110
+ ]
111
+
112
+ for text in test_texts:
113
+ print(f"\nمتن: {text}")
114
+ emotions = analyzer._emotion_analysis(text)
115
+ print("نتایج تحلیل احساسات:")
116
+ for emotion, score in emotions.items():
117
+ print(f"{emotion}: {score:.4f}")
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: PerSent
3
- Version: 0.1.6
3
+ Version: 0.1.7
4
4
  Summary: Persian Sentiment Analysis Toolkit
5
5
  Home-page: https://github.com/RezaGooner/PerSent
6
6
  Author: RezaGooner
@@ -5,7 +5,7 @@ with open("README.md", "r", encoding="utf-8") as fh:
5
5
 
6
6
  setup(
7
7
  name="PerSent",
8
- version="0.1.6",
8
+ version="0.1.7",
9
9
  author="RezaGooner",
10
10
  author_email="RezaAsadiProgrammer@Gmail.com",
11
11
  description="Persian Sentiment Analysis Toolkit",
@@ -1,251 +0,0 @@
1
- import pandas as pd
2
- from hazm import Normalizer, word_tokenize, Stemmer, stopwords_list
3
- import re
4
- from tqdm import tqdm
5
- from gensim.models import Word2Vec
6
- import numpy as np
7
- from sklearn.linear_model import LogisticRegression
8
- from sklearn.model_selection import train_test_split
9
- import os
10
- import joblib
11
- from transformers import pipeline
12
- import torch
13
- from torch.nn.functional import softmax
14
-
15
- class AdvancedPersianSentimentAnalyzer:
16
- def __init__(self, model_dir='persian_sentiment/model'):
17
- self.normalizer = Normalizer()
18
- self.stemmer = Stemmer()
19
- self.stopwords = set(stopwords_list())
20
- self.model_dir = model_dir
21
- self.vectorizer = None
22
- self.classifier = None
23
-
24
- # تنظیمات پیشرفته برای مدل احساسات
25
- self.emotion_analyzer = pipeline(
26
- "text-classification",
27
- model="HooshvareLab/bert-fa-base-uncased-emotion",
28
- tokenizer="HooshvareLab/bert-fa-base-uncased-emotion",
29
- device=0 if torch.cuda.is_available() else -1,
30
- return_all_scores=True
31
- )
32
-
33
- # پارامترهای بهینه شده
34
- self.w2v_params = {
35
- 'vector_size': 300,
36
- 'window': 10,
37
- 'min_count': 2,
38
- 'workers': 4,
39
- 'sg': 1 # استفاده از skip-gram
40
- }
41
-
42
- # نگاشت برچسب‌های احساسات
43
- self.emotion_labels = {
44
- 'ترس': 'Fear',
45
- 'تعجب': 'Surprise',
46
- 'خشم': 'Anger',
47
- 'تحقیر': 'Contempt',
48
- 'شادی': 'Joy',
49
- 'غم': 'Sadness',
50
- 'انزجار': 'Disgust'
51
- }
52
-
53
- os.makedirs(self.model_dir, exist_ok=True)
54
-
55
- def _preprocess_text(self, text, for_emotion=False):
56
- """پیش‌پردازش متن با قابلیت تنظیم برای تحلیل احساسات"""
57
- text = self.normalizer.normalize(str(text))
58
-
59
- if for_emotion:
60
- # حفظ علائم نگارشی برای تحلیل احساسات
61
- text = re.sub(r'[۰-۹\d]+', ' ', text)
62
- else:
63
- text = re.sub(r'[!()-\[\]{};:\'",؟<>./?@#$%^&*_~۰-۹\d]+', ' ', text)
64
-
65
- text = re.sub(r'\s+', ' ', text).strip()
66
-
67
- if not for_emotion:
68
- tokens = word_tokenize(text)
69
- return [
70
- self.stemmer.stem(token)
71
- for token in tokens
72
- if token not in self.stopwords and len(token) > 1
73
- ]
74
- return text
75
-
76
- def _sentence_vector(self, sentence, model):
77
- """تبدیل جمله به بردار با میانگین وزنی"""
78
- vectors = []
79
- weights = []
80
-
81
- for word in sentence:
82
- try:
83
- vectors.append(model.wv[word])
84
- # وزن‌دهی بر اساس فرکانس کلمه
85
- weights.append(model.wv.get_vecattr(word, "count"))
86
- except KeyError:
87
- continue
88
-
89
- if vectors:
90
- weights = np.array(weights) / sum(weights)
91
- return np.average(vectors, axis=0, weights=weights)
92
- return np.zeros(self.w2v_params['vector_size'])
93
-
94
- def _emotion_analysis(self, text):
95
- """تحلیل احساسات پیشرفته با مدل BERT"""
96
- try:
97
- # پیش‌پردازش متن برای تحلیل احساسات
98
- processed_text = self._preprocess_text(text, for_emotion=True)
99
-
100
- # تحلیل احساسات
101
- results = self.emotion_analyzer(processed_text)[0]
102
-
103
- # پردازش نتایج
104
- emotions = {}
105
- for item in results:
106
- fa_label = item['label']
107
- if fa_label in self.emotion_labels:
108
- en_label = self.emotion_labels[fa_label]
109
- score = round(float(item['score']), 4)
110
- emotions[en_label] = score
111
-
112
- # اگر همه مقادیر صفر بودند، قوی‌ترین احساس را برگردان
113
- if all(v == 0 for v in emotions.values()) and results:
114
- top_emotion = max(results, key=lambda x: x['score'])
115
- fa_label = top_emotion['label']
116
- if fa_label in self.emotion_labels:
117
- en_label = self.emotion_labels[fa_label]
118
- emotions[en_label] = round(float(top_emotion['score']), 4)
119
-
120
- return emotions
121
-
122
- except Exception as e:
123
- print(f"خطا در تحلیل احساسات: {str(e)}")
124
- return {label: 0.0 for label in self.emotion_labels.values()}
125
-
126
- def train(self, train_csv, test_size=0.2):
127
- """آموزش پیشرفته مدل تحلیل توصیه"""
128
- df = pd.read_csv(train_csv)
129
-
130
- # تعادل کلاس‌ها
131
- class_counts = df['recommendation_status'].value_counts()
132
- max_samples = class_counts.max()
133
- balanced_df = pd.concat([
134
- df[df['recommendation_status'] == cls].sample(max_samples, replace=True)
135
- for cls in class_counts.index
136
- ])
137
-
138
- balanced_df['tokens'] = balanced_df['body'].progress_apply(
139
- lambda x: self._preprocess_text(x))
140
-
141
- # آموزش مدل Word2Vec با پارامترهای بهینه
142
- self.vectorizer = Word2Vec(
143
- sentences=balanced_df['tokens'],
144
- **self.w2v_params
145
- )
146
-
147
- # استخراج ویژگی‌های پیشرفته
148
- X = np.array([self._sentence_vector(s, self.vectorizer)
149
- for s in tqdm(balanced_df['tokens'], desc="Vectorizing")])
150
- y = balanced_df['recommendation_status'].map({
151
- "no_idea": 2,
152
- "recommended": 1,
153
- "not_recommended": 0
154
- }).values
155
-
156
- # تقسیم داده‌ها با stratify
157
- X_train, X_test, y_train, y_test = train_test_split(
158
- X, y, test_size=test_size, stratify=y)
159
-
160
- # مدل طبقه‌بندی با تنظیمات بهینه
161
- self.classifier = LogisticRegression(
162
- max_iter=2000,
163
- class_weight='balanced',
164
- C=0.5,
165
- solver='saga'
166
- )
167
- self.classifier.fit(X_train, y_train)
168
-
169
- # ذخیره مدل
170
- self.save_model()
171
-
172
- # ارزیابی دقیق‌تر
173
- train_acc = self.classifier.score(X_train, y_train)
174
- test_acc = self.classifier.score(X_test, y_test)
175
-
176
- return {
177
- 'train_accuracy': round(train_acc, 4),
178
- 'test_accuracy': round(test_acc, 4),
179
- 'model_params': self.w2v_params
180
- }
181
-
182
- def predict(self, text, mode=0, threshold=0.1):
183
- """
184
- پیش‌بینی پیشرفته با قابلیت تنظیم آستانه
185
-
186
- پارامترها:
187
- text (str): متن ورودی
188
- mode (int): حالت تحلیل (0=توصیه، 1=احساسات)
189
- threshold (float): آستانه اطمینان برای تحلیل احساسات
190
-
191
- بازگشتی:
192
- dict یا str: نتایج تحلیل
193
- """
194
- if mode == 1:
195
- emotions = self._emotion_analysis(text)
196
- # فیلتر کردن احساسات با احتمال پایین
197
- return {
198
- k: v for k, v in emotions.items()
199
- if v >= threshold
200
- }
201
-
202
- if not self.classifier or not self.vectorizer:
203
- raise Exception("Model not trained! Call train() first.")
204
-
205
- tokens = self._preprocess_text(text)
206
- vector = self._sentence_vector(tokens, self.vectorizer)
207
- proba = self.classifier.predict_proba([vector])[0]
208
-
209
- # تصمیم‌گیری با در نظر گرفتن احتمال
210
- if max(proba) < 0.6: # آستانه اطمینان
211
- return "no_idea"
212
-
213
- prediction = self.classifier.predict([vector])[0]
214
- return {
215
- 0: "not_recommended",
216
- 1: "recommended",
217
- 2: "no_idea"
218
- }[prediction]
219
-
220
- def save_model(self):
221
- """ذخیره مدل با نسخه‌بندی"""
222
- version = 1
223
- while os.path.exists(os.path.join(self.model_dir, f'classifier_v{version}.joblib')):
224
- version += 1
225
-
226
- joblib.dump(
227
- self.classifier,
228
- os.path.join(self.model_dir, f'classifier_v{version}.joblib')
229
- )
230
- self.vectorizer.save(
231
- os.path.join(self.model_dir, f'word2vec_v{version}.model')
232
- )
233
-
234
- def load_model(self, version=None):
235
- """بارگذاری مدل با قابلیت انتخاب نسخه"""
236
- if version is None:
237
- versions = [
238
- int(f.split('_v')[-1].split('.')[0])
239
- for f in os.listdir(self.model_dir)
240
- if f.startswith('classifier_v')
241
- ]
242
- version = max(versions) if versions else None
243
-
244
- if version:
245
- self.classifier = joblib.load(
246
- os.path.join(self.model_dir, f'classifier_v{version}.joblib')
247
- )
248
- self.vectorizer = Word2Vec.load(
249
- os.path.join(self.model_dir, f'word2vec_v{version}.model')
250
- )
251
-
File without changes
File without changes
File without changes
File without changes