|
|
import torch |
|
|
import wikipedia |
|
|
from transformers import pipeline |
|
|
import gradio as gr |
|
|
import os |
|
|
import re |
|
|
|
|
|
|
|
|
|
|
|
_initial_logs = [] |
|
|
|
|
|
|
|
|
_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.") |
|
|
|
|
|
|
|
|
_initial_logs.append("2. LLM (Karar Verici) yükleniyor...") |
|
|
model_id = "Qwen/Qwen2.5-1.5B-Instruct" |
|
|
llm_model_kwargs = {} |
|
|
IS_LLM_ENABLED = False |
|
|
|
|
|
|
|
|
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" |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
try: |
|
|
gen_pipe = pipeline( |
|
|
"text-generation", |
|
|
model=model_id, |
|
|
model_kwargs=llm_model_kwargs, |
|
|
device_map=llm_device_map, |
|
|
trust_remote_code=True |
|
|
) |
|
|
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!") |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
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 |
|
|
|
|
|
|
|
|
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 |
|
|
|
|
|
|
|
|
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": "" |
|
|
} |
|
|
|
|
|
|
|
|
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 |
|
|
|
|
|
|
|
|
elif label in STANDARD_LABELS: |
|
|
log_messages.append(f" ➡️ {word} etiket: {label} (Standart etiket, RAG atlandı.) (Skor: {score:.2f})") |
|
|
yield log_messages, None |
|
|
|
|
|
|
|
|
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 |
|
|
|
|
|
|
|
|
custom_label_definitions = { |
|
|
|
|
|
"PER": "Kişi adları, takma adlar, ünlüler.", |
|
|
"ORG": "Şirketler, kurumlar, hükümet kuruluşları.", |
|
|
"LOC": "Coğrafi yerler, siyasi bölgeler, binalar.", |
|
|
|
|
|
"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": "Diğer adlandırılmış varlıklar (LLM tarafından rafine edilemeyen veya uymayanlar)." |
|
|
} |
|
|
|
|
|
|
|
|
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(' ', ' ')}</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 |
|
|
|
|
|
|
|
|
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) |