MISC_NER / app.py
apak's picture
Update app.py
de81b5a verified
import torch
import wikipedia
from transformers import pipeline
import gradio as gr
import os
import re
# import bitsandbytes # Lokal çalıştırmada gereklidir
# --- Global Log Listesini Tanımla ---
_initial_logs = []
# --- 1. İlk Tarama Modeli (Hızlı NER) ---
_initial_logs.append("1. Standart NER Modeli yükleniyor...")
device_id = 0 if torch.cuda.is_available() else -1
ner_pipe = pipeline(
"ner",
model="xlm-roberta-large-finetuned-conll03-english",
aggregation_strategy="simple",
device=device_id
)
_initial_logs.append("✅ Standart NER Modeli Hazır.")
# --- 2. Akıl Yürütme Modeli (LLM - RAG Karar Verici) ---
_initial_logs.append("2. LLM (Karar Verici) yükleniyor...")
model_id = "Qwen/Qwen2.5-1.5B-Instruct"
llm_model_kwargs = {}
IS_LLM_ENABLED = False
# Cihaz ayarlarını ve optimizasyonları yapma
if torch.cuda.is_available():
llm_model_kwargs["torch_dtype"] = torch.bfloat16
llm_model_kwargs["load_in_4bit"] = True
llm_device_map = "auto"
_initial_logs.append("CUDA/GPU desteği bulundu, model **bfloat16 & 4-bit kuantizasyon** ile yüklenecek.")
else:
_initial_logs.append("CUDA desteği bulunamadı, model CPU üzerinde float32 ile yüklenecek.")
llm_device_map = "cpu"
# Hata Düzeltme: 'trust_remote_code' buradan KALDIRILDI.
# llm_model_kwargs["trust_remote_code"] = True # ❌ Bu satır HATAYA neden oluyordu!
try:
gen_pipe = pipeline(
"text-generation",
model=model_id,
model_kwargs=llm_model_kwargs,
device_map=llm_device_map,
trust_remote_code=True # ✅ Buraya taşındı, artık tekil değer olarak iletiliyor.
)
IS_LLM_ENABLED = True
_initial_logs.append("✅ LLM (Karar Verici) Başarıyla Yüklendi.")
except Exception as e:
_initial_logs.append(f"❌ LLM yüklenirken kritik hata: {e}")
gen_pipe = None
IS_LLM_ENABLED = False
_initial_logs.append("⚠️ LLM boru hattı devre dışı bırakıldı. Uygulama sadece temel NER yapacaktır.")
_initial_logs.append("✅ Modeller Hazır!")
# --- Geri kalan tüm kod (Wikipedia Fonksiyonu, LLM Rafine Etme, Pipeline, Etiketler, Gradio Arayüzü) önceki versiyonunuzdaki gibi aynı kalır. ---
# Not: Tam kodun çalışması için bu noktadan sonraki kısımları aynen eklemelisiniz.
# ... (Wikipedia Fonksiyonu)
wikipedia.set_lang("tr")
def get_wiki_summary(term):
"""Wikipedia'dan bir terim için özet bilgi çeker (otomatik öneri ile)."""
try:
page = wikipedia.page(term, auto_suggest=True, redirect=True)
return wikipedia.summary(page.title, sentences=3, auto_suggest=False)
except (wikipedia.exceptions.PageError,
wikipedia.exceptions.RedirectError,
wikipedia.exceptions.DisambiguationError,
Exception):
return None
# --- LLM ile Etiket Rafine Etme Fonksiyonu ---
def refine_label_with_llm(entity_text, wiki_context, custom_label_definitions):
"""LLM kullanarak MISC etiketini özel etiketlerden biriyle rafine eder."""
if not IS_LLM_ENABLED or gen_pipe is None:
return "MISC", "LLM devre dışı olduğu için rafine edilemedi."
refinable_labels = {k: v for k, v in custom_label_definitions.items() if k not in ["PER", "LOC", "ORG", "MISC"]}
label_definitions_str = "\n".join(
[f"- {k}: {v}" for k, v in refinable_labels.items()]
)
few_shot_examples = """ÖRNEK 1 (NORP İÇİN):
VARLIK: Türk
BAĞLAM: Türkler, Türkiye Cumhuriyeti'nde yaşayan ve Türkçe konuşan büyük bir etnik gruptur. (NORP tanımı ile eşleşir.)
GÖZLEM: 'Türk' varlığı bir etnik grubu veya milliyeti ifade ediyor. Bağlamda 'etnik grup' ve 'Türkiye Cumhuriyeti' geçiyor.
AKIL YÜRÜTME: Etnik veya dini grupları tanımlayan etiket 'NORP'dur. 'Türk' varlığı bu tanıma uymaktadır.
CEVAP: NORP
ÖRNEK 2 (LANGUAGE İÇİN):
VARLIK: Türkçe
BAĞLAM: Türkçenin ilk yazılı örnekleri Orhun Yazıtları'dır. Türk dilleri koluna ait bir dildir. Dünya çapında en çok konuşulan 20. dildir. (LANGUAGE tanımı ile eşleşir.)
GÖZLEM: 'Türkçe', bir iletişim aracı olan dili ifade ediyor. Bağlamda 'Türk dilleri koluna ait bir dildir' ifadesi geçiyor.
AKIL YÜRÜTME: İnsan dillerinin adlarını tanımlayan etiket 'LANGUAGE'dir. Varlık tanımıyla tam olarak eşleşmektedir.
CEVAP: LANGUAGE
ÖRNEK 3 (BOOK İÇİN):
VARLIK: Sinekli Bakkal
BAĞLAM: Halide Edib Adıvar'ın 1935'te yayınlanan ve popüler kültürde önemli yer tutan romanıdır. Eser, II. Dünya Savaşı öncesi İstanbul'u anlatır. (BOOK tanımı ile eşleşir.)
GÖZLEM: 'Sinekli Bakkal' yazar adı ve yayın tarihi ile birlikte bir 'roman' olarak anılıyor. Bağlamda 'romanıdır' ifadesi geçiyor.
AKIL YÜRÜTME: Yazılı veya basılı eserleri, romanları ve yayınları tanımlayan etiket 'BOOK'tur. Bu tanıma uymaktadır.
CEVAP: BOOK
ÖRNEK 4 (TITLE İÇİN):
VARLIK: General
BAĞLAM: Birçok orduda yüksek rütbeli bir subay unvanıdır. Türkiye'de en yüksek rütbelerden biridir. (TITLE tanımı ile eşleşir.)
GÖZLEM: 'General' bir rütbe, unvan veya pozisyon belirtiyor. Bağlamda 'yüksek rütbeli bir subay unvanı' ifadesi geçiyor.
AKIL YÜRÜTME: Kişinin unvanını, rütbesini veya pozisyonunu tanımlayan etiket 'TITLE'dır. 'General' bu tanıma uymaktadır.
CEVAP: TITLE
"""
prompt = f"""Sen uzman bir veri sınıflandırma sistemisin.
Görevin, aşağıdaki varlığı ve bağlamı analiz ederek, **YALNIZCA** ETİKET TANIMLARI'ndan birini (veya uymuyorsa MISC'i) seçmektir.
AKIL YÜRÜTME ZİNCİRİNİ (Chain of Thought) takip ederek adım adım karar ver:
1. GÖZLEM: Varlık ve Wiki Bağlamı arasındaki anahtar eşleşmeleri (isim, tarih, unvan, tür, yayıncı vb.) listele.
2. AKIL YÜRÜTME: Bu gözlemlerin, **ETİKET TANIMLARI**'ndan hangisine **EN YAKIN** olduğunu gerekçesiyle açıkla.
3. KARAR: Yalnızca uygun olan ETİKET'i (veya MISC'i) ÇIKTI FORMATI'na uygun olarak belirt. **MISC'i sadece hiçbir tanıma uymuyorsa kullan.**
{few_shot_examples}
---
ETİKET TANIMLARI:{label_definitions_str}
---
GÖREV VARLIĞI:
VARLIK: {entity_text}
BAĞLAM (Wikipedia): {wiki_context}
ÇIKTI FORMATI:
GÖZLEM: [Tespit edilen anahtar özellikler]
AKIL YÜRÜTME: [Gözlemlerin hangi tanıma uyduğu ve neden]
CEVAP: [YALNIZCA SEÇİLEN ETİKETİ BÜYÜK HARFLERLE YAZ. ÖRNEK: BOOK veya LANGUAGE. SAKIN CEVAP KISMINA BAŞKA BİR ŞEY EKLEME.]
"""
messages = [{"role": "user", "content": prompt}]
try:
outputs = gen_pipe(
messages,
max_new_tokens=150,
do_sample=False,
temperature=0.1
)
full_output = outputs[0]["generated_text"][-1]["content"].strip()
except Exception as e:
return "MISC", f"LLM çalışma zamanı hatası: {e}"
final_label = "MISC"
cog_output = ""
valid_labels = list(custom_label_definitions.keys())
try:
match_cevap = re.search(r'CEVAP:\s*([A-Z_]+)', full_output, re.IGNORECASE)
if match_cevap:
raw_label = match_cevap.group(1).strip().upper()
if raw_label in valid_labels:
final_label = raw_label
match_reasoning = re.search(r'AKIL YÜRÜTME:\s*(.*)', full_output, re.IGNORECASE | re.DOTALL)
if match_reasoning:
cog_output = match_reasoning.group(1).strip().replace('\n', ' ')
except Exception:
pass
return final_label, cog_output
# --- NER Pipeline Fonksiyonu (RAG KISITLAMASI İLE) ---
def advanced_ner_pipeline(text, target_labels, progress=gr.Progress()):
"""
Standart NER modelini çalıştırır. RAG/LLM süreci SADECE MISC etiketleri için çalışır.
"""
log_messages = list(_initial_logs)
try:
initial_results = ner_pipe(text)
except Exception as e:
log_messages.append(f"❌ Hata: Temel NER modelinde sorun oluştu: {e}")
yield log_messages, []
return
final_results = []
total_entities = len(initial_results)
log_messages.append(f"🕵️‍♀️ Toplam {total_entities} varlık inceleniyor...")
yield log_messages, None
STANDARD_LABELS = ["PER", "LOC", "ORG"]
for i, entity in enumerate(initial_results):
word = entity['word']
label = entity['entity_group']
score = entity['score']
current_progress = (i + 1) / total_entities
progress(current_progress, desc=f"İşleniyor: {word}")
result_obj = {
"entity": word,
"initial_label": label,
"initial_score": score,
"final_label": label,
"source": "Model",
"reasoning": ""
}
# RAG KARAR NOKTASI: YALNIZCA MISC VE LLM AÇIKSA RAG YAP
if label == "MISC" and IS_LLM_ENABLED:
standardized_word = word.title()
log_messages.append(f" ⚠️ MISC tespit edildi: '{word}' (Skor: {score:.2f}). Wikipedia'ya soruluyor...")
yield log_messages, None
wiki_context = get_wiki_summary(standardized_word)
if wiki_context:
log_messages.append(f" 📄 Wiki Bağlamı bulundu: {wiki_context[:50]}...")
yield log_messages, None
new_label, reasoning = refine_label_with_llm(standardized_word, wiki_context, target_labels)
result_obj["final_label"] = new_label
result_obj["source"] = "RAG+LLM (CoT)"
result_obj["reasoning"] = reasoning
log_messages.append(f" 🔄 Etiket Güncellendi: MISC -> {new_label}")
if reasoning:
log_messages.append(f" 💡 Akıl Yürütme: {reasoning[:70]}...")
yield log_messages, None
else:
log_messages.append(" ❌ Wiki'de bilgi bulunamadı, MISC olarak kalıyor.")
yield log_messages, None
# Standart etiketler için loglama (RAG atlanır)
elif label in STANDARD_LABELS:
log_messages.append(f" ➡️ {word} etiket: {label} (Standart etiket, RAG atlandı.) (Skor: {score:.2f})")
yield log_messages, None
# Diğer standart etiketler (MISC ve standart 3'lü dışında)
else:
log_messages.append(f" ✅ {word} için etiket: {label} (Skor: {score:.2f})")
yield log_messages, None
final_results.append(result_obj)
log_messages.append("✅ İşlem tamamlandı! Nihai sonuçlar tabloda.")
yield log_messages, final_results
# --- Özel Etiket Tanımları ---
custom_label_definitions = {
# Standart CoNLL-2003 etiketleri
"PER": "Kişi adları, takma adlar, ünlüler.",
"ORG": "Şirketler, kurumlar, hükümet kuruluşları.",
"LOC": "Coğrafi yerler, siyasi bölgeler, binalar.",
# MISC içinden rafine edilecek ince taneli etiketler
"DATE": "Mutlak veya göreceli tarih ifadeleri (yıl, ay, gün).",
"TIME": "Günün saati, zaman aralığı.",
"MONEY": "Parasal değerler, para birimleri.",
"QUANTITY": "Ağırlık, uzunluk, hacim gibi ölçü birimleri ve sayısal değerler.",
"PERCENT": "Yüzdelik ifadeler.",
"NORP": "Milliyetler, etnik, dini veya politik gruplar.",
"LAW": "Resmi kanun, yasa, yönetmelik veya hukuki belge adı.",
"EVENT": "Savaşlar, festivaller, spor turnuvaları, doğal afetler veya kurumsal etkinlikler.",
"BOOK": "Yazılı veya basılı bir eser, yayınlanmış roman, ders kitabı.",
"MOVIE": "Sinema filmi, dizi, belgesel gibi görsel-işitsel yapıt.",
"SONG": "Müzik eseri, şarkı, beste veya albüm adı.",
"ART": "Resim, heykel, spesifik mimari eser adı (Örn: Mona Lisa).",
"AWARD": "Ödül, madalya veya nişan adı (Örn: Nobel, Oscar).",
"PRODUCT": "Ticari olarak satılan somut bir eşya, model, cihaz veya marka serisi (Örn: iPhone 15 Pro, Mercedes C200).",
"SOFTWARE": "Bilgisayar programları, mobil uygulamalar, yapay zeka sistemleri, işletim sistemleri.",
"ORG_SUB": "Şirket birimleri, üniversite fakülteleri, dernek şubeleri gibi büyük bir kurumun alt birimleri.",
"LANGUAGE": "Dillerin adı (Örn: İngilizce, Arapça).",
"TITLE": "Kişinin unvanı, rütbesini veya pozisyonunu (Örn: Profesör, General, Başkan).",
"CYBER": "URL, E-posta adresi, IP adresi, hashtag veya kullanıcı adı.",
# MISC: Kalan her şey
"MISC": "Diğer adlandırılmış varlıklar (LLM tarafından rafine edilemeyen veya uymayanlar)."
}
# --- Gradio Arayüzü Fonksiyonu ---
def process_ner_request(text, progress=gr.Progress()):
"""
Gradio arayüzünden çağrılan ana fonksiyondur.
"""
all_logs = []
final_results = None
for logs_step, results_step in advanced_ner_pipeline(text, custom_label_definitions, progress=progress):
all_logs = logs_step
if results_step is not None:
final_results = results_step
log_output_html = "<div style='max-height: 200px; overflow-y: scroll; border: 1px solid #eee; padding: 10px; margin-bottom: 10px; background-color: #f9f9f9; border-radius: 8px; font-family: monospace; font-size: 12px;'>"
for log in all_logs:
color = 'blue' if '✅' in log else ('orange' if '⚠️' in log else ('red' if '❌' in log else ('gray' if '➡️' in log else 'black')))
log_output_html += f"<p style='margin: 0; color: {color};'>{log.replace(' ', '&nbsp;&nbsp;&nbsp;')}</p>"
log_output_html += "</div>"
current_results_html = ""
if final_results:
color_map = {
"PER": "background-color: #f8c291;", "ORG": "background-color: #b3c99f;", "LOC": "background-color: #a2c4c9;",
"MISC": "background-color: #fef08a; font-weight: bold;",
"DATE": "background-color: #e5ccff;", "TIME": "background-color: #d1d5db;", "MONEY": "background-color: #fcd34d;",
"QUANTITY": "background-color: #bfdbfe;", "PERCENT": "background-color: #99f6e4;", "NORP": "background-color: #fbcfe8;",
"LAW": "background-color: #f0abfc;", "EVENT": "background-color: #a7f3d0;", "BOOK": "background-color: #ffedd5;",
"MOVIE": "background-color: #c7d2fe;", "SONG": "background-color: #e9d5ff;", "ART": "background-color: #bae6fd;",
"AWARD": "background-color: #fee2e2;", "PRODUCT": "background-color: #ffc999;", "SOFTWARE": "background-color: #d1fae5;",
"ORG_SUB": "background-color: #ccfbf1;", "LANGUAGE": "background-color: #fef9c3;", "TITLE": "background-color: #fecaca;",
"CYBER": "background-color: #dbeafe;"
}
current_results_html = """
<style>
.ner-table { width:100%; border-collapse: collapse; font-family: Arial, sans-serif; border-radius: 8px; overflow: hidden; box-shadow: 0 4px 6px rgba(0,0,0,0.1); }
.ner-table th, .ner-table td { padding: 12px 15px; text-align: left; border-bottom: 1px solid #ddd; }
.ner-table th { background-color: #3b82f6; color: white; font-weight: bold; text-transform: uppercase; }
.ner-table tr:nth-child(even) { background-color: #f3f4f6; }
.ner-table tr:hover { background-color: #e5e7eb; }
.tooltip-text { visibility: hidden; background-color: #333; color: #fff; text-align: center; border-radius: 6px; padding: 5px 10px; position: absolute; z-index: 1; bottom: 125%; left: 50%; transform: translateX(-50%); opacity: 0; transition: opacity 0.3s; width: 300px; font-size: 11px; line-height: 1.4; }
.tooltip-container:hover .tooltip-text { visibility: visible; opacity: 1; }
.tooltip-container { position: relative; display: inline-block; }
</style>
<table class='ner-table'>\n"""
current_results_html += " <tr>\n"
current_results_html += " <th>VARLIK</th>\n"
current_results_html += " <th>İLK ETİKET</th>\n"
current_results_html += " <th>İLK TAHMİN SKORU</th>\n"
current_results_html += " <th>RAG SONRASI ETİKETİ (GEREKÇE)</th>\n"
current_results_html += " <th>KAYNAK</th>\n"
current_results_html += " </tr>\n"
for item in final_results:
final_label_style = color_map.get(item['final_label'], "")
reasoning_tooltip = ""
if item.get("reasoning"):
reasoning_tooltip = f"""
<div class='tooltip-container'>
{item['final_label']}
<span class='tooltip-text'>
CoT Akıl Yürütme: {item['reasoning']}
</span>
</div>
"""
else:
reasoning_tooltip = item['final_label']
score_formatted = f"{item['initial_score'] * 100:.2f}%"
score_color = ""
if item['initial_score'] > 0.95: score_color = "color: #10b981;"
elif item['initial_score'] > 0.8: score_color = "color: #f59e0b;"
else: score_color = "color: #ef4444;"
current_results_html += " <tr>\n"
current_results_html += f" <td>{item['entity']}</td>\n"
current_results_html += f" <td>{item['initial_label']}</td>\n"
current_results_html += f" <td style='{score_color} font-weight: bold;'>{score_formatted}</td>\n"
current_results_html += f" <td style='{final_label_style}'>{reasoning_tooltip}</td>\n"
current_results_html += f" <td>{item['source']}</td>\n"
current_results_html += " </tr>\n"
current_results_html += "</table>"
yield log_output_html, current_results_html
# --- Gradio Arayüzünü Tanımla ve Başlat ---
iface = gr.Interface(
fn=process_ner_request,
inputs=gr.Textbox(lines=5, placeholder="Metin giriniz...", label="Giriş Metni"),
outputs=[gr.HTML(label="İşlem Logları"), gr.HTML(label="Zenginleştirilmiş NER Sonuçları")],
title="Gelişmiş İnce Taneli NER (23 Etiket - RAG/LLM Destekli) v2.0 - GPU Optimize",
description="Bu uygulama, Hugging Face Spaces'te GPU kullanacak şekilde optimize edilmiştir. LLM, **bfloat16 ve 4-bit kuantizasyon** ile yüklenerek hem hız hem de bellek verimliliği artırılmıştır. RAG/LLM rafine etme süreci SADECE MISC etiketleri için çalıştırılır.",
examples=[
["Milli Eğitim Bakanlığı'na bağlı Lise Birimleri, 2024 Türkiye Kupası etkinliğine katılacak ve %15 indirim uygulayacak."],
["General Vural, Türkçe dilini kullanan Türk askerlerini, https://example.com üzerinden uyardı. 'Hürriyet Kasidesi' eserini okudu."],
["Windows 11 işletim sistemi, 1000 TL karşılığında satışa sunulmuştur. 'Cumhurbaşkanı' unvanına sahip kişi, Kanun maddesini değiştirdi."],
],
analytics_enabled=False
)
if __name__ == "__main__":
port = int(os.environ.get('PORT', 7860))
iface.launch(share=False, server_port=port)