Commit
·
f79b859
1
Parent(s):
b58a541
analiz motoru ve küçük değişiklikler
Browse files- .gitignore +4 -0
- Dockerfile +5 -7
- main.py +58 -104
- phonology_engine.py +4 -1
.gitignore
ADDED
|
@@ -0,0 +1,4 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
__pycache__/
|
| 2 |
+
*.py[cod]
|
| 3 |
+
*.pyo
|
| 4 |
+
*.pyd
|
Dockerfile
CHANGED
|
@@ -2,14 +2,14 @@ FROM python:3.9
|
|
| 2 |
|
| 3 |
WORKDIR /code
|
| 4 |
|
| 5 |
-
# FFmpeg kurulumu (Ses
|
| 6 |
RUN apt-get update && apt-get install -y ffmpeg
|
| 7 |
|
| 8 |
# Kütüphaneleri yükle
|
| 9 |
COPY ./requirements.txt /code/requirements.txt
|
| 10 |
RUN pip install --no-cache-dir --upgrade -r /code/requirements.txt
|
| 11 |
|
| 12 |
-
# Kullanıcı
|
| 13 |
RUN useradd -m -u 1000 user
|
| 14 |
USER user
|
| 15 |
ENV HOME=/home/user \
|
|
@@ -18,10 +18,8 @@ ENV HOME=/home/user \
|
|
| 18 |
WORKDIR $HOME/app
|
| 19 |
COPY --chown=user . $HOME/app
|
| 20 |
|
| 21 |
-
# ---
|
| 22 |
-
#
|
| 23 |
-
|
| 24 |
-
RUN python3 -c "from faster_whisper import WhisperModel; WhisperModel('large-v2', device='cpu', compute_type='int8')"
|
| 25 |
|
| 26 |
-
# Uygulamayı başlat
|
| 27 |
CMD ["uvicorn", "main:app", "--host", "0.0.0.0", "--port", "7860"]
|
|
|
|
| 2 |
|
| 3 |
WORKDIR /code
|
| 4 |
|
| 5 |
+
# FFmpeg kurulumu (Ses işlemek için şart)
|
| 6 |
RUN apt-get update && apt-get install -y ffmpeg
|
| 7 |
|
| 8 |
# Kütüphaneleri yükle
|
| 9 |
COPY ./requirements.txt /code/requirements.txt
|
| 10 |
RUN pip install --no-cache-dir --upgrade -r /code/requirements.txt
|
| 11 |
|
| 12 |
+
# Kullanıcı ayarları
|
| 13 |
RUN useradd -m -u 1000 user
|
| 14 |
USER user
|
| 15 |
ENV HOME=/home/user \
|
|
|
|
| 18 |
WORKDIR $HOME/app
|
| 19 |
COPY --chown=user . $HOME/app
|
| 20 |
|
| 21 |
+
# --- ÖNEMLİ ---
|
| 22 |
+
# Hem 'small' (Uygulama için) hem 'large-v2' (Karşılaştırma için) modellerini indiriyoruz.
|
| 23 |
+
RUN python3 -c "from faster_whisper import WhisperModel; WhisperModel('small', device='cpu', compute_type='int8'); WhisperModel('large-v2', device='cpu', compute_type='int8')"
|
|
|
|
| 24 |
|
|
|
|
| 25 |
CMD ["uvicorn", "main:app", "--host", "0.0.0.0", "--port", "7860"]
|
main.py
CHANGED
|
@@ -1,135 +1,89 @@
|
|
| 1 |
-
from fastapi import FastAPI, UploadFile, File, Form
|
| 2 |
from faster_whisper import WhisperModel
|
| 3 |
from phonology_engine import FonolojikVaryasyonMotoru
|
| 4 |
-
import difflib
|
| 5 |
-
import os
|
| 6 |
import shutil
|
|
|
|
| 7 |
import uuid
|
|
|
|
| 8 |
|
| 9 |
-
app = FastAPI(
|
| 10 |
|
| 11 |
-
# ---
|
| 12 |
-
|
| 13 |
-
MODEL_SIZE = "large-v2"
|
| 14 |
-
DEVICE = "cpu"
|
| 15 |
-
COMPUTE_TYPE = "int8"
|
| 16 |
|
| 17 |
-
|
| 18 |
-
|
| 19 |
-
model = WhisperModel(MODEL_SIZE, device=DEVICE, compute_type=COMPUTE_TYPE)
|
| 20 |
-
motor = FonolojikVaryasyonMotoru()
|
| 21 |
-
print("✅ Sistem hazır!")
|
| 22 |
|
| 23 |
-
|
| 24 |
-
|
| 25 |
-
Hedef kelime ile gelen kelimeyi karşılaştırır ve hataları raporlar.
|
| 26 |
-
"""
|
| 27 |
-
hedef = hedef.lower().replace("İ", "i").strip()
|
| 28 |
-
gelen = gelen.lower().replace("İ", "i").strip()
|
| 29 |
-
|
| 30 |
-
# 1. Tam Eşleşme
|
| 31 |
-
if hedef == gelen:
|
| 32 |
-
return {
|
| 33 |
-
"durum": "basarili",
|
| 34 |
-
"skor": 100,
|
| 35 |
-
"mesaj": "Harika! Tamamen doğru söyledin.",
|
| 36 |
-
"hatalar": []
|
| 37 |
-
}
|
| 38 |
|
| 39 |
-
|
| 40 |
-
|
| 41 |
-
hatalar = []
|
| 42 |
-
|
| 43 |
-
# Opcodes: 'replace', 'delete', 'insert', 'equal'
|
| 44 |
-
for tag, i1, i2, j1, j2 in matcher.get_opcodes():
|
| 45 |
-
if tag == 'replace':
|
| 46 |
-
beklenen = hedef[i1:i2]
|
| 47 |
-
soylenen = gelen[j1:j2]
|
| 48 |
-
hatalar.append(f"'{beklenen.upper()}' yerine '{soylenen.upper()}' dedin.")
|
| 49 |
-
elif tag == 'delete':
|
| 50 |
-
beklenen = hedef[i1:i2]
|
| 51 |
-
hatalar.append(f"'{beklenen.upper()}' sesini yuttun.")
|
| 52 |
-
elif tag == 'insert':
|
| 53 |
-
fazla = gelen[j1:j2]
|
| 54 |
-
hatalar.append(f"Fazladan '{fazla.upper()}' sesi çıkardın.")
|
| 55 |
|
|
|
|
|
|
|
|
|
|
| 56 |
skor = int(matcher.ratio() * 100)
|
| 57 |
-
|
| 58 |
-
return {
|
| 59 |
-
"durum": "hatali",
|
| 60 |
-
"skor": skor,
|
| 61 |
-
"mesaj": "Bazı sesleri düzeltmemiz gerekiyor.",
|
| 62 |
-
"hatalar": hatalar,
|
| 63 |
-
"detayli_fark": {
|
| 64 |
-
"beklenen": hedef,
|
| 65 |
-
"algilanan": gelen
|
| 66 |
-
}
|
| 67 |
-
}
|
| 68 |
|
|
|
|
| 69 |
@app.post("/analiz")
|
| 70 |
-
async def
|
| 71 |
file: UploadFile = File(...),
|
| 72 |
hedef_kelime: str = Form(...)
|
| 73 |
):
|
| 74 |
-
|
| 75 |
-
Endpoint: Ses dosyasını ve hedeflenen kelimeyi alır, analiz sonucunu döner.
|
| 76 |
-
"""
|
| 77 |
-
|
| 78 |
-
# 1. Dosyayı Geçici Kaydet
|
| 79 |
-
if not file.filename.lower().endswith(('.wav', '.mp3', '.m4a', '.ogg')):
|
| 80 |
-
raise HTTPException(status_code=400, detail="Geçersiz dosya formatı.")
|
| 81 |
-
|
| 82 |
temp_filename = f"temp_{uuid.uuid4()}.wav"
|
| 83 |
|
|
|
|
|
|
|
|
|
|
|
|
|
| 84 |
try:
|
| 85 |
-
|
| 86 |
-
|
| 87 |
-
|
| 88 |
-
|
| 89 |
-
|
| 90 |
-
|
| 91 |
-
# 3. Whisper Transkripsiyon
|
| 92 |
-
# initial_prompt: Whisper'a bu kelimenin varyasyonlu olabileceğini söylüyoruz.
|
| 93 |
-
segments, _ = model.transcribe(
|
| 94 |
temp_filename,
|
| 95 |
language="tr",
|
| 96 |
-
initial_prompt=
|
| 97 |
-
|
| 98 |
-
|
| 99 |
-
|
| 100 |
-
vad_filter=False, # Sessizlik filtresini KAPAT (Çok önemli)
|
| 101 |
-
word_timestamps=False, # Tek kelime için gereksiz, hız kazandırır
|
| 102 |
-
|
| 103 |
-
# Whisper'ın "Emin değilim" deyip susmasını engellemek için filtreleri kapatıyoruz:
|
| 104 |
-
no_speech_threshold=0.95, # Sessizlik eşiğini yükselt
|
| 105 |
-
log_prob_threshold=None, # Düşük olasılıklı tahminleri de kabul et (Silme)
|
| 106 |
-
compression_ratio_threshold=None, # Tekrar eden bozuk sesleri de al
|
| 107 |
-
temperature=0 # Yaratıcılığı kapat, en net duyduğunu ver
|
| 108 |
)
|
|
|
|
| 109 |
|
| 110 |
-
#
|
| 111 |
-
|
| 112 |
-
|
| 113 |
-
|
| 114 |
-
|
| 115 |
-
|
| 116 |
-
|
| 117 |
-
|
| 118 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 119 |
return {
|
| 120 |
"hedef_kelime": hedef_kelime,
|
| 121 |
-
"
|
| 122 |
-
"
|
|
|
|
|
|
|
|
|
|
|
|
|
| 123 |
}
|
| 124 |
|
| 125 |
except Exception as e:
|
| 126 |
-
return {"
|
| 127 |
|
| 128 |
finally:
|
| 129 |
-
#
|
| 130 |
if os.path.exists(temp_filename):
|
| 131 |
-
os.remove(temp_filename)
|
| 132 |
-
|
| 133 |
-
@app.get("/")
|
| 134 |
-
def root():
|
| 135 |
-
return {"durum": "MnemAI Sunucusu Calisiyor"}
|
|
|
|
| 1 |
+
from fastapi import FastAPI, UploadFile, File, Form
|
| 2 |
from faster_whisper import WhisperModel
|
| 3 |
from phonology_engine import FonolojikVaryasyonMotoru
|
|
|
|
|
|
|
| 4 |
import shutil
|
| 5 |
+
import os
|
| 6 |
import uuid
|
| 7 |
+
import difflib
|
| 8 |
|
| 9 |
+
app = FastAPI()
|
| 10 |
|
| 11 |
+
# --- MODELLERİ YÜKLEME ---
|
| 12 |
+
print("⏳ Modeller yükleniyor (Lütfen bekleyin)...")
|
|
|
|
|
|
|
|
|
|
| 13 |
|
| 14 |
+
# 1. Uygulama Modeli (Small - Hızlı ve Manipüle Edilmiş)
|
| 15 |
+
model_small = WhisperModel("small", device="cpu", compute_type="int8")
|
|
|
|
|
|
|
|
|
|
| 16 |
|
| 17 |
+
# 2. Kontrol Modeli (Large v2 - Yavaş ve Saf)
|
| 18 |
+
model_large = WhisperModel("large-v2", device="cpu", compute_type="int8")
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 19 |
|
| 20 |
+
motor = FonolojikVaryasyonMotoru()
|
| 21 |
+
print("✅ İki model de RAM'e yüklendi ve hazır!")
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 22 |
|
| 23 |
+
# --- YARDIMCI FONKSİYON ---
|
| 24 |
+
def kelime_analizi_yap(hedef, gelen):
|
| 25 |
+
matcher = difflib.SequenceMatcher(None, hedef.lower(), gelen.lower())
|
| 26 |
skor = int(matcher.ratio() * 100)
|
| 27 |
+
durum = "basarili" if skor >= 80 else "hatali"
|
| 28 |
+
return {"skor": skor, "durum": durum}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 29 |
|
| 30 |
+
# --- API ENDPOINT ---
|
| 31 |
@app.post("/analiz")
|
| 32 |
+
async def analiz(
|
| 33 |
file: UploadFile = File(...),
|
| 34 |
hedef_kelime: str = Form(...)
|
| 35 |
):
|
| 36 |
+
# Geçici dosya ismi
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 37 |
temp_filename = f"temp_{uuid.uuid4()}.wav"
|
| 38 |
|
| 39 |
+
# Dosyayı diske kaydet
|
| 40 |
+
with open(temp_filename, "wb") as buffer:
|
| 41 |
+
shutil.copyfileobj(file.file, buffer)
|
| 42 |
+
|
| 43 |
try:
|
| 44 |
+
# --- ADIM 1: SMALL MODEL (PROMPTLU) ---
|
| 45 |
+
# Hedef kelimeye göre varyasyonları üret
|
| 46 |
+
varyasyonlar = motor.varyasyonlari_uret(hedef_kelime)
|
| 47 |
+
prompt_metni = f"Şu kelimelerden birini seç: {', '.join(varyasyonlar)}"
|
| 48 |
+
|
| 49 |
+
segments_small, _ = model_small.transcribe(
|
|
|
|
|
|
|
|
|
|
| 50 |
temp_filename,
|
| 51 |
language="tr",
|
| 52 |
+
initial_prompt=prompt_metni, # <-- Müdahale var
|
| 53 |
+
beam_size=1, # Hızlı olsun
|
| 54 |
+
best_of=1,
|
| 55 |
+
vad_filter=False
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 56 |
)
|
| 57 |
+
small_sonuc = " ".join([s.text for s in segments_small]).strip()
|
| 58 |
|
| 59 |
+
# --- ADIM 2: LARGE MODEL (SAF/PROMPTSUZ) ---
|
| 60 |
+
# Burada modele hiçbir ipucu vermiyoruz.
|
| 61 |
+
segments_large, _ = model_large.transcribe(
|
| 62 |
+
temp_filename,
|
| 63 |
+
language="tr",
|
| 64 |
+
beam_size=5, # Kaliteli olsun (Biraz yavaşlar)
|
| 65 |
+
vad_filter=False
|
| 66 |
+
)
|
| 67 |
+
large_sonuc = " ".join([s.text for s in segments_large]).strip()
|
| 68 |
+
|
| 69 |
+
# --- ADIM 3: SONUÇLARI KARŞILAŞTIR ---
|
| 70 |
+
analiz_sonucu = kelime_analizi_yap(hedef_kelime, small_sonuc)
|
| 71 |
+
|
| 72 |
+
# JSON DÖNÜŞÜ
|
| 73 |
return {
|
| 74 |
"hedef_kelime": hedef_kelime,
|
| 75 |
+
"analiz": analiz_sonucu,
|
| 76 |
+
"modeller": {
|
| 77 |
+
"small_model_tahmini": small_sonuc, # Uygulamanın kullandığı
|
| 78 |
+
"large_model_tahmini": large_sonuc, # Gerçekte duyulan (Saf)
|
| 79 |
+
"varyasyonlar_prompt": varyasyonlar # Modele verdiğimiz ipuçları
|
| 80 |
+
}
|
| 81 |
}
|
| 82 |
|
| 83 |
except Exception as e:
|
| 84 |
+
return {"error": str(e)}
|
| 85 |
|
| 86 |
finally:
|
| 87 |
+
# İşlem bitince dosyayı sil
|
| 88 |
if os.path.exists(temp_filename):
|
| 89 |
+
os.remove(temp_filename)
|
|
|
|
|
|
|
|
|
|
|
|
phonology_engine.py
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
|
|
| 1 |
import itertools
|
| 2 |
|
| 3 |
class FonolojikVaryasyonMotoru:
|
|
@@ -25,7 +26,9 @@ class FonolojikVaryasyonMotoru:
|
|
| 25 |
'ğ': ['ğ', 'y', ''],
|
| 26 |
'h': ['h', '']
|
| 27 |
}
|
| 28 |
-
|
|
|
|
|
|
|
| 29 |
def varyasyonlari_uret(self, hedef_kelime, max_limit=150):
|
| 30 |
"""
|
| 31 |
Hedef kelimenin olası yanlış telaffuz varyasyonlarını üretir.
|
|
|
|
| 1 |
+
from functools import lru_cache
|
| 2 |
import itertools
|
| 3 |
|
| 4 |
class FonolojikVaryasyonMotoru:
|
|
|
|
| 26 |
'ğ': ['ğ', 'y', ''],
|
| 27 |
'h': ['h', '']
|
| 28 |
}
|
| 29 |
+
|
| 30 |
+
# Son 100 kelimenin varyasyonlarını hafızada tut
|
| 31 |
+
@lru_cache(maxsize=100)
|
| 32 |
def varyasyonlari_uret(self, hedef_kelime, max_limit=150):
|
| 33 |
"""
|
| 34 |
Hedef kelimenin olası yanlış telaffuz varyasyonlarını üretir.
|