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