|
import pandas as pd
|
|
import numpy as np
|
|
from transformers import (
|
|
AutoTokenizer,
|
|
AutoModelForSequenceClassification
|
|
)
|
|
import torch
|
|
import os
|
|
import requests
|
|
from collections import Counter
|
|
import warnings
|
|
from nltk.tokenize import word_tokenize
|
|
import nltk
|
|
import re
|
|
import google.generativeai as genai
|
|
from dotenv import load_dotenv
|
|
|
|
warnings.filterwarnings('ignore')
|
|
|
|
|
|
try:
|
|
nltk.download('stopwords', quiet=True)
|
|
nltk.download('punkt', quiet=True)
|
|
except:
|
|
print("NLTK dosyaları indirilemedi, devam ediliyor...")
|
|
|
|
class ReviewAnalyzer:
|
|
def __init__(self):
|
|
|
|
load_dotenv()
|
|
|
|
|
|
genai.configure(api_key=os.getenv('GOOGLE_API_KEY'))
|
|
self.model = genai.GenerativeModel('gemini-pro')
|
|
|
|
|
|
self.setup_sentiment_model()
|
|
|
|
self.turkish_stopwords = self.get_turkish_stopwords()
|
|
|
|
|
|
self.logistics_seller_words = {
|
|
|
|
'kargo', 'kargocu', 'paket', 'paketleme', 'teslimat', 'teslim',
|
|
'gönderi', 'gönderim', 'ulaştı', 'ulaşım', 'geldi', 'kurye',
|
|
'dağıtım', 'hasarlı', 'hasar', 'kutu', 'ambalaj', 'zamanında',
|
|
'geç', 'hızlı', 'yavaş', 'günde', 'saatte',
|
|
|
|
|
|
'satıcı', 'mağaza', 'sipariş', 'trendyol', 'tedarik', 'stok',
|
|
'garanti', 'fatura', 'iade', 'geri', 'müşteri', 'hizmet',
|
|
'destek', 'iletişim', 'şikayet', 'sorun', 'çözüm', 'hediye',
|
|
|
|
|
|
'fiyat', 'ücret', 'para', 'bedava', 'ücretsiz', 'indirim',
|
|
'kampanya', 'taksit', 'ödeme', 'bütçe', 'hesap', 'kur',
|
|
|
|
|
|
'bugün', 'yarın', 'dün', 'hafta', 'gün', 'saat', 'süre',
|
|
'bekleme', 'gecikme', 'erken', 'geç'
|
|
}
|
|
|
|
def get_turkish_stopwords(self):
|
|
"""Genişletilmiş stop words listesini hazırla"""
|
|
github_url = "https://raw.githubusercontent.com/sgsinclair/trombone/master/src/main/resources/org/voyanttools/trombone/keywords/stop.tr.turkish-lucene.txt"
|
|
stop_words = set()
|
|
|
|
try:
|
|
response = requests.get(github_url)
|
|
if response.status_code == 200:
|
|
github_stops = set(word.strip() for word in response.text.split('\n') if word.strip())
|
|
stop_words.update(github_stops)
|
|
except Exception as e:
|
|
print(f"GitHub'dan stop words çekilirken hata oluştu: {e}")
|
|
|
|
stop_words.update(set(nltk.corpus.stopwords.words('turkish')))
|
|
|
|
additional_stops = {'bir', 've', 'çok', 'bu', 'de', 'da', 'için', 'ile', 'ben', 'sen',
|
|
'o', 'biz', 'siz', 'onlar', 'bu', 'şu', 'ama', 'fakat', 'ancak',
|
|
'lakin', 'ki', 'dahi', 'mi', 'mı', 'mu', 'mü', 'var', 'yok',
|
|
'olan', 'içinde', 'üzerinde', 'bana', 'sana', 'ona', 'bize',
|
|
'size', 'onlara', 'evet', 'hayır', 'tamam', 'oldu', 'olmuş',
|
|
'olacak', 'etmek', 'yapmak', 'kez', 'kere', 'defa', 'adet'}
|
|
stop_words.update(additional_stops)
|
|
|
|
print(f"Toplam {len(stop_words)} adet stop words yüklendi.")
|
|
return stop_words
|
|
|
|
def preprocess_text(self, text):
|
|
"""Metin ön işleme"""
|
|
if isinstance(text, str):
|
|
|
|
text = text.lower()
|
|
|
|
text = re.sub(r'[^\w\s]', '', text)
|
|
|
|
text = re.sub(r'\d+', '', text)
|
|
|
|
text = re.sub(r'\s+', ' ', text).strip()
|
|
|
|
words = text.split()
|
|
words = [word for word in words if word not in self.turkish_stopwords]
|
|
return ' '.join(words)
|
|
return ''
|
|
|
|
def setup_sentiment_model(self):
|
|
"""Sentiment analiz modelini hazırla"""
|
|
self.device = "cuda" if torch.cuda.is_available() else "cpu"
|
|
print(f"Using device for sentiment: {self.device}")
|
|
|
|
model_name = "savasy/bert-base-turkish-sentiment-cased"
|
|
self.sentiment_tokenizer = AutoTokenizer.from_pretrained(model_name)
|
|
self.sentiment_model = (
|
|
AutoModelForSequenceClassification.from_pretrained(model_name)
|
|
.to(self.device)
|
|
.to(torch.float32)
|
|
)
|
|
|
|
def filter_reviews(self, df):
|
|
"""Ürün ile ilgili olmayan yorumları filtrele"""
|
|
def is_product_review(text):
|
|
if not isinstance(text, str):
|
|
return False
|
|
return not any(word in text.lower() for word in self.logistics_seller_words)
|
|
|
|
filtered_df = df[df['Yorum'].apply(is_product_review)].copy()
|
|
|
|
print(f"\nFiltreleme İstatistikleri:")
|
|
print(f"Toplam yorum sayısı: {len(df)}")
|
|
print(f"Ürün yorumu sayısı: {len(filtered_df)}")
|
|
print(f"Filtrelenen yorum sayısı: {len(df) - len(filtered_df)}")
|
|
print(f"Filtreleme oranı: {((len(df) - len(filtered_df)) / len(df) * 100):.2f}%")
|
|
|
|
return filtered_df
|
|
|
|
def analyze_sentiment(self, df):
|
|
"""Sentiment analizi yap"""
|
|
def predict_sentiment(text):
|
|
if not isinstance(text, str) or len(text.strip()) == 0:
|
|
return {"label": "Nötr", "score": 0.5}
|
|
|
|
try:
|
|
cleaned_text = self.preprocess_text(text)
|
|
|
|
inputs = self.sentiment_tokenizer(
|
|
cleaned_text,
|
|
return_tensors="pt",
|
|
truncation=True,
|
|
max_length=512,
|
|
padding=True
|
|
).to(self.device)
|
|
|
|
with torch.no_grad():
|
|
outputs = self.sentiment_model(**inputs)
|
|
probs = torch.nn.functional.softmax(outputs.logits, dim=1)
|
|
prediction = probs.cpu().numpy()[0]
|
|
|
|
score = float(prediction[1])
|
|
|
|
if score > 0.75:
|
|
label = "Pozitif"
|
|
elif score < 0.25:
|
|
label = "Negatif"
|
|
elif score > 0.55:
|
|
label = "Pozitif"
|
|
elif score < 0.45:
|
|
label = "Negatif"
|
|
else:
|
|
label = "Nötr"
|
|
|
|
return {"label": label, "score": score}
|
|
|
|
except Exception as e:
|
|
print(f"Error in sentiment prediction: {e}")
|
|
return {"label": "Nötr", "score": 0.5}
|
|
|
|
print("\nSentiment analizi yapılıyor...")
|
|
results = [predict_sentiment(text) for text in df['Yorum']]
|
|
|
|
df['sentiment_score'] = [r['score'] for r in results]
|
|
df['sentiment_label'] = [r['label'] for r in results]
|
|
df['cleaned_text'] = df['Yorum'].apply(self.preprocess_text)
|
|
|
|
return df
|
|
|
|
def get_key_phrases(self, text_series):
|
|
"""En önemli anahtar kelimeleri bul"""
|
|
text = ' '.join(text_series.astype(str))
|
|
words = self.preprocess_text(text).split()
|
|
word_freq = Counter(words)
|
|
|
|
return {word: count for word, count in word_freq.items()
|
|
if count >= 3 and len(word) > 2}
|
|
|
|
def generate_summary(self, df):
|
|
"""Yorumları özetle"""
|
|
|
|
reviews_with_ratings = [
|
|
f"Yıldız: {row['Yıldız Sayısı']}, Yorum: {row['Yorum']}"
|
|
for _, row in df.iterrows()
|
|
]
|
|
|
|
|
|
prompt = f"""
|
|
Aşağıdaki ürün yorumlarını analiz edip özet çıkar:
|
|
|
|
{reviews_with_ratings[:50]} # İlk 50 yorumu al (API limiti için)
|
|
|
|
Lütfen şu başlıklar altında özetle:
|
|
1. Genel Değerlendirme
|
|
2. Olumlu Yönler
|
|
3. Olumsuz Yönler
|
|
4. Öneriler
|
|
|
|
Önemli: Yanıtını Türkçe olarak ver ve madde madde listele.
|
|
"""
|
|
|
|
try:
|
|
response = self.model.generate_content(prompt)
|
|
summary = response.text
|
|
except Exception as e:
|
|
summary = f"Özet oluşturulurken hata oluştu: {str(e)}"
|
|
|
|
return summary
|
|
|
|
def analyze_reviews(self, df):
|
|
"""Tüm yorumları analiz et"""
|
|
try:
|
|
|
|
filtered_df = self.filter_reviews(df)
|
|
|
|
|
|
analyzed_df = self.analyze_sentiment(filtered_df)
|
|
|
|
return analyzed_df
|
|
|
|
except Exception as e:
|
|
print(f"Analiz sırasında hata oluştu: {str(e)}")
|
|
return pd.DataFrame()
|
|
|
|
def analyze_reviews(file_path):
|
|
df = pd.read_csv(file_path)
|
|
|
|
analyzer = ReviewAnalyzer()
|
|
|
|
filtered_df = analyzer.filter_reviews(df)
|
|
|
|
print("Sentiment analizi başlatılıyor...")
|
|
analyzed_df = analyzer.analyze_sentiment(filtered_df)
|
|
|
|
analyzed_df.to_csv('sentiment_analyzed_reviews.csv', index=False, encoding='utf-8-sig')
|
|
print("Sentiment analizi tamamlandı ve kaydedildi.")
|
|
|
|
print("\nÜrün özeti oluşturuluyor...")
|
|
summary = analyzer.generate_summary(analyzed_df)
|
|
|
|
with open('urun_ozeti.txt', 'w', encoding='utf-8') as f:
|
|
f.write(summary)
|
|
|
|
print("\nÜrün Özeti:")
|
|
print("-" * 50)
|
|
print(summary)
|
|
print("\nÖzet 'urun_ozeti.txt' dosyasına kaydedildi.")
|
|
|
|
if __name__ == "__main__":
|
|
analyze_reviews('data/macbook_product_comments_with_ratings.csv') |