first commit
This commit is contained in:
241
namecreate/views.py
Normal file
241
namecreate/views.py
Normal file
@@ -0,0 +1,241 @@
|
||||
import csv
|
||||
import io
|
||||
import json
|
||||
import os
|
||||
import uuid
|
||||
|
||||
from django.http import JsonResponse
|
||||
from django.views.decorators.csrf import csrf_exempt
|
||||
from rest_framework.authentication import SessionAuthentication
|
||||
from rest_framework.decorators import api_view, authentication_classes, permission_classes
|
||||
from rest_framework.permissions import IsAdminUser
|
||||
from rest_framework_simplejwt.authentication import JWTAuthentication
|
||||
|
||||
from namecreate.models import TrainingJob
|
||||
from namecreate.tasks import train_model_task
|
||||
|
||||
|
||||
def _parse_json_data(request):
|
||||
"""
|
||||
JSON body'den veriyi çeker.
|
||||
Beklenen format:
|
||||
{
|
||||
"features": [[1.2, 3.4, 5.6, 7.8], [2.1, 4.3, 6.5, 8.7], ...],
|
||||
"labels": [0, 1, 2, ...]
|
||||
}
|
||||
"""
|
||||
body = json.loads(request.body)
|
||||
features = body.get('features')
|
||||
labels = body.get('labels')
|
||||
|
||||
if not features or not labels:
|
||||
raise ValueError("'features' ve 'labels' alanları zorunludur.")
|
||||
if len(features) != len(labels):
|
||||
raise ValueError("'features' ve 'labels' eleman sayısı eşit olmalıdır.")
|
||||
if len(features) < 10:
|
||||
raise ValueError("En az 10 eğitim örneği gönderiniz.")
|
||||
|
||||
# Tip güvenliği: tüm değerler sayısal olmalı
|
||||
for i, row in enumerate(features):
|
||||
if not all(isinstance(v, (int, float)) for v in row):
|
||||
raise ValueError(f"features[{i}] içinde sayısal olmayan değer var.")
|
||||
if not all(isinstance(v, (int, float)) for v in labels):
|
||||
raise ValueError("labels listesi sadece sayısal değer içermelidir.")
|
||||
|
||||
return features, [int(v) for v in labels]
|
||||
|
||||
|
||||
def _parse_csv_data(file):
|
||||
"""
|
||||
CSV dosyasından veriyi çeker.
|
||||
Beklenen format — son sütun label, geri kalanlar feature:
|
||||
1.2,3.4,5.6,7.8,0
|
||||
2.1,4.3,6.5,8.7,1
|
||||
...
|
||||
Header satırı varsa otomatik atlanır.
|
||||
"""
|
||||
content = file.read().decode('utf-8')
|
||||
reader = csv.reader(io.StringIO(content))
|
||||
|
||||
features, labels = [], []
|
||||
for lineno, row in enumerate(reader, start=1):
|
||||
if not row:
|
||||
continue
|
||||
# Header satırını atla
|
||||
try:
|
||||
values = [float(v) for v in row]
|
||||
except ValueError:
|
||||
if lineno == 1:
|
||||
continue # Başlık satırı
|
||||
raise ValueError(f"CSV satır {lineno}: sayısal olmayan değer.")
|
||||
|
||||
if len(values) < 2:
|
||||
raise ValueError(f"CSV satır {lineno}: en az 2 sütun (özellik + etiket) gerekli.")
|
||||
|
||||
features.append(values[:-1])
|
||||
labels.append(int(values[-1]))
|
||||
|
||||
if len(features) < 10:
|
||||
raise ValueError("En az 10 eğitim örneği gönderiniz.")
|
||||
|
||||
return features, labels
|
||||
|
||||
|
||||
@csrf_exempt
|
||||
def train_and_export_model(request):
|
||||
"""
|
||||
Modeli arka planda eğitmek için Celery task'ı başlatır.
|
||||
|
||||
Veri gönderme yöntemleri:
|
||||
|
||||
1) JSON body:
|
||||
POST /api/v1/ml/train-model/
|
||||
Content-Type: application/json
|
||||
{
|
||||
"features": [[1.2, 3.4, 5.6, 7.8], ...],
|
||||
"labels": [0, 1, 2, ...]
|
||||
}
|
||||
|
||||
2) CSV dosyası:
|
||||
POST /api/v1/ml/train-model/
|
||||
Content-Type: multipart/form-data
|
||||
Form alanı: data=<csv_dosyası>
|
||||
(Son sütun label, geri kalanlar feature)
|
||||
|
||||
3) Veri göndermezseniz yerleşik Iris demo verisi kullanılır.
|
||||
"""
|
||||
if request.method != 'POST':
|
||||
return JsonResponse({'error': 'Sadece POST destekleniyor.'}, status=405)
|
||||
|
||||
try:
|
||||
features, labels = None, None
|
||||
source = 'demo'
|
||||
|
||||
# --- Yöntem 1: JSON body ---
|
||||
ct = request.content_type or ''
|
||||
if 'application/json' in ct and request.body:
|
||||
features, labels = _parse_json_data(request)
|
||||
source = 'json'
|
||||
|
||||
# --- Yöntem 2: CSV dosyası ---
|
||||
elif 'data' in request.FILES:
|
||||
features, labels = _parse_csv_data(request.FILES['data'])
|
||||
source = 'csv'
|
||||
|
||||
# --- Yöntem 3: Demo (Iris) ---
|
||||
# features ve labels None kalır, task default veriyi kullanır
|
||||
|
||||
feature_count = len(features[0]) if features else None
|
||||
sample_count = len(features) if features else None
|
||||
|
||||
task_id = str(uuid.uuid4())
|
||||
TrainingJob.objects.create(
|
||||
task_id=task_id,
|
||||
status='pending',
|
||||
features=features,
|
||||
labels=labels,
|
||||
feature_count=feature_count,
|
||||
sample_count=sample_count,
|
||||
)
|
||||
|
||||
celery_task = train_model_task.delay(task_id)
|
||||
|
||||
return JsonResponse({
|
||||
'status': 'queued',
|
||||
'message': 'Model eğitim görevi başlatıldı.',
|
||||
'task_id': task_id,
|
||||
'celery_task_id': celery_task.id,
|
||||
'data_source': source,
|
||||
'sample_count': sample_count,
|
||||
'feature_count': feature_count,
|
||||
})
|
||||
|
||||
except (ValueError, KeyError) as e:
|
||||
return JsonResponse({'status': 'error', 'message': str(e)}, status=400)
|
||||
except Exception as e:
|
||||
return JsonResponse({'status': 'error', 'message': str(e)}, status=500)
|
||||
|
||||
|
||||
def get_training_status(request):
|
||||
"""Task'ın durumunu sorgular."""
|
||||
task_id = request.GET.get('task_id')
|
||||
|
||||
if not task_id:
|
||||
return JsonResponse({
|
||||
"error": "task_id gerekli"
|
||||
}, status=400)
|
||||
|
||||
try:
|
||||
job = TrainingJob.objects.get(task_id=task_id)
|
||||
|
||||
response = {
|
||||
"task_id": job.task_id,
|
||||
"status": job.status,
|
||||
"created_at": job.created_at.isoformat() if job.created_at else None,
|
||||
"started_at": job.started_at.isoformat() if job.started_at else None,
|
||||
"completed_at": job.completed_at.isoformat() if job.completed_at else None,
|
||||
}
|
||||
|
||||
# Task tamamlandığında metrikleri ekle
|
||||
if job.status == 'completed':
|
||||
response.update({
|
||||
"model_version": job.model_version.isoformat(),
|
||||
"metrics": {
|
||||
"accuracy": job.accuracy,
|
||||
"precision": job.precision,
|
||||
"recall": job.recall,
|
||||
"f1_score": job.f1_score,
|
||||
}
|
||||
})
|
||||
|
||||
# Task başarısız olmuşsa hata mesajı ekle
|
||||
if job.status == 'failed':
|
||||
response["error_message"] = job.error_message
|
||||
|
||||
return JsonResponse(response)
|
||||
|
||||
except TrainingJob.DoesNotExist:
|
||||
return JsonResponse({
|
||||
"error": "Task bulunamadı"
|
||||
}, status=404)
|
||||
|
||||
|
||||
@api_view(['GET'])
|
||||
@authentication_classes([SessionAuthentication, JWTAuthentication])
|
||||
@permission_classes([IsAdminUser])
|
||||
def list_training_jobs(request):
|
||||
"""TrainingJob kayıtlarını listeler."""
|
||||
status_filter = request.GET.get('status')
|
||||
limit = request.GET.get('limit', '50')
|
||||
|
||||
try:
|
||||
limit = max(1, min(int(limit), 200))
|
||||
except ValueError:
|
||||
return JsonResponse({"error": "limit sayısal olmalıdır"}, status=400)
|
||||
|
||||
jobs = TrainingJob.objects.all().order_by('-created_at')
|
||||
if status_filter:
|
||||
jobs = jobs.filter(status=status_filter)
|
||||
|
||||
items = []
|
||||
for job in jobs[:limit]:
|
||||
model_exists = bool(job.model_path and os.path.exists(job.model_path))
|
||||
items.append({
|
||||
'id': job.pk,
|
||||
'task_id': job.task_id,
|
||||
'status': job.status,
|
||||
'model_type': job.model_type,
|
||||
'created_at': job.created_at.isoformat() if job.created_at else None,
|
||||
'started_at': job.started_at.isoformat() if job.started_at else None,
|
||||
'completed_at': job.completed_at.isoformat() if job.completed_at else None,
|
||||
'sample_count': job.sample_count,
|
||||
'feature_count': job.feature_count,
|
||||
'model_path': job.model_path,
|
||||
'model_exists': model_exists,
|
||||
'error_message': job.error_message,
|
||||
})
|
||||
|
||||
return JsonResponse({
|
||||
'count': len(items),
|
||||
'results': items,
|
||||
})
|
||||
Reference in New Issue
Block a user